硬件平台:RK3128
Android版本:6.0
Kernel版本:3.10.92
最近调试一款gps芯片,u-blox的MAX-7Q,支持I2C及UART接口,由于UART口不够用,只能用I2C接口,之前没有接触过I2C接口的gps芯片,并且在hardware层调用I2C也是第一次接触,故记录在此。
1、在hardware目录下新建文件夹u-blox,将客户提供的hal层源码复制到该目录下。
修改hardware/u-blox/gps/u-blox.conf中SERIAL_DEVICE为 /dev/i2c-2,如下:
# Device to be used by the driver
#SERIAL_DEVICE /dev/ttyACM0 # USB (Default interface)
#SERIAL_DEVICE /dev/ttymxc4 # UART on Sabre SD (IMX6Q)
SERIAL_DEVICE /dev/i2c-2 # I2C on Sabre SD (IMX6Q)
#SERIAL_DEVICE /dev/ttyO3 # UART on Panda board
#SERIAL_DEVICE /dev/i2c-4 # I2C on Panda board
2、复制ubx-setup.sh到device/rockchip/rk312x/目录下,并在device/rockchip/rk312x/rk312x.mk中添加如下内容:
# add for u-blox
PRODUCT_COPY_FILES += \
device/rockchip/rk312x/ubx-setup.sh:system/etc/ubx-setup.sh \
hardware/u-blox/gps/gps.conf:system/etc/gps.conf \
hardware/u-blox/gps/u-blox.conf:system/etc/u-blox.conf
3、在device/rockchip/common/init.rockchip.rc中添加服务:
# add for u-blox
service ubx-setup /system/etc/ubx-setup.sh
user root
group root
oneshot
并在on boot中开启服务:
# add for u-blox
chmod 0755 /system/etc/ubx-setup.sh
start ubx-setup
4、使用 mmm hardware/u-blox/gps/编译hal,然后make一下。这个地方比较奇怪,我用make全编hardware/u-blox/gps/并没有编译到,又用mmm才编到。生成out/target/product/rk312x/system/lib/hw/gps.default.so文件说明编译成功。
5、烧录后,先用打印logcat搜索u-blox确认服务运行,如果没有运行,可能是selinux导致,用adb shell setenforce 0来确认是否是selinux问题。
遇到的问题:
移植完成后,logcat中有大量以下错误:
01-18 08:51:06.274 289 1027 D u-blox : IOCTL for reading nob registers failed. io = -1
01-18 08:51:06.324 289 1027 D u-blox : IOCTL for reading nob registers failed. io = -1
01-18 08:51:06.375 289 1027 D u-blox : IOCTL for reading nob registers failed. io = -1
01-18 08:51:06.425 289 1027 D u-blox : IOCTL for reading nob registers failed. io = -1
先来看hardware层出错的地方代码:
ubx_serial.cpp
ssize_t CSerialPort::i2cGetNumAvailBytes(void) const
{
if (m_fd <= 0)
return -1;
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs;
__u32 nmsgs;
};
struct i2c_rdwr_ioctl_data rdwr;
struct i2c_msg out_in[2], *out, *in;
unsigned char out_data;
unsigned char in_data[2];
ssize_t result = -1;
out = out_in;
in = out_in + 1;
out_data = 0xFD;
out->addr = s_i2cRecvAddr;
out->flags = 0; // indicates a write
out->len = 1;
out->buf = &out_data;
out->scl_rate = 200 * 1000;
in->addr = s_i2cRecvAddr;
in->flags = I2C_M_RD;
in->len = 2;
in->buf = in_data;
in->scl_rate = 200 * 1000;
in_data[0] = 0;
in_data[1] = 0;
rdwr.nmsgs = 2;
rdwr.msgs = out_in;
int io = ioctl(m_fd, I2C_RDWR, &rdwr);
if (io < 0)
{
UBXSERLOG("IOCTL for reading nob registers failed. io = %d",io);
}
else
{
result = in_data[0] * 256 + in_data[1];
}
return result;
}
说明ioctl出错了,在kernel中添加打印,发现在i2cdev_ioctl_rdrw出错,rdwr_pa[1].len是一个超大值,导致校验不过,代码如下:
I2c-dev.c
static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,
unsigned long arg)
{
struct i2c_rdwr_ioctl_data rdwr_arg;
struct i2c_msg *rdwr_pa;
u8 __user **data_ptrs;
int i, res;
if (copy_from_user(&rdwr_arg,
(struct i2c_rdwr_ioctl_data __user *)arg,
sizeof(rdwr_arg)))
return -EFAULT;
/* Put an arbitrary limit on the number of messages that can
* be sent at once */
if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
return -EINVAL;
rdwr_pa = memdup_user(rdwr_arg.msgs,
rdwr_arg.nmsgs * sizeof(struct i2c_msg));
if (IS_ERR(rdwr_pa))
return PTR_ERR(rdwr_pa);
data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);
if (data_ptrs == NULL) {
kfree(rdwr_pa);
return -ENOMEM;
}
res = 0;
for (i = 0; i < rdwr_arg.nmsgs; i++) {
/* Limit the size of the message to a sane amount */
if (rdwr_pa[i].len > 8192) { //这里出错
res = -EINVAL;
break;
}
data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf;
rdwr_pa[i].buf = memdup_user(data_ptrs[i], rdwr_pa[i].len);
if (IS_ERR(rdwr_pa[i].buf)) {
res = PTR_ERR(rdwr_pa[i].buf);
break;
}
/*
* If the message length is received from the slave (similar
* to SMBus block read), we must ensure that the buffer will
* be large enough to cope with a message length of
* I2C_SMBUS_BLOCK_MAX as this is the maximum underlying bus
* drivers allow. The first byte in the buffer must be
* pre-filled with the number of extra bytes, which must be
* at least one to hold the message length, but can be
* greater (for example to account for a checksum byte at
* the end of the message.)
*/
if (rdwr_pa[i].flags & I2C_M_RECV_LEN) {
if (!(rdwr_pa[i].flags & I2C_M_RD) ||
rdwr_pa[i].buf[0] < 1 ||
rdwr_pa[i].len < rdwr_pa[i].buf[0] +
I2C_SMBUS_BLOCK_MAX) {
res = -EINVAL;
break;
}
rdwr_pa[i].len = rdwr_pa[i].buf[0];
}
}
if (res < 0) {
int j;
for (j = 0; j < i; ++j)
kfree(rdwr_pa[j].buf);
kfree(data_ptrs);
kfree(rdwr_pa);
return res;
}
res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);
while (i-- > 0) {
if (res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) {
if (copy_to_user(data_ptrs[i], rdwr_pa[i].buf,
rdwr_pa[i].len))
res = -EFAULT;
}
kfree(rdwr_pa[i].buf);
}
kfree(data_ptrs);
kfree(rdwr_pa);
return res;
}
查了好久,明明hardware层len=2,怎么到kernel就不对了?
原因在于i2c_msg这个结构体,它在kernel中定义在:
kernel/include/uapi/linux/i2c.h
而hardware层引用的定义在:
bionic/libc/kernel/uapi/linux/i2c.h
在hardware层调用I2C,i2c_msg的定义必须与内核一致,rockchip修改了内核,在kernel/include/uapi/linux/i2c.h中为i2c_msg添加了一个成员,并且没有同步到bionic。所以两边引用的i2c_msg定义不一样,结果就是当然出错了。
bionic/libc/kernel/uapi/linux/i2c.h这个文件是自动生成的,当修改了内核后,运行bionic/libc/kernel/tools/update_all.py脚本来同步(注意:此脚本慎用,因为我发现rockchip并没有用此脚本同步,而是直接修改了bionic下文件)。
将bionic/libc/kernel/uapi/linux/i2c.h中i2c_msg定义修改成跟内核一样就可以了。
注意:可能还要在device/rockchip/common/ueventd.rockchip.rc中添加i2c-2的权限。
/dev/i2c-2 0666 system system