前面分析i2c-tool
测试工具就是基于drivers/i2c/i2c-dev.c
驱动来实现的。i2c-dev驱动在加载时会遍历所有的I2C总线(i2c_bus_type)上所有注册的adapter,并且在linux系统创建对应的字符设备,如:/dev/i2c-0
、/dev/i2c-1
、/dev/i2c-2
等。应用程序通过open打开对应的i2c字符设备,通过ioctl来收发数据。具体的架构如下图:
在i2c-dev.c的驱动入口i2c_dev_init
函数具体操作如下:
i2c_for_each_dev
遍历已经绑定的adapter,有多少个adapter就调用i2cdev_attach_adapter
函数几次。i2c-x
,也就是我们在字符设备创建成功后看到的/dev/i2c-x
设备。备注:
cdev_device_add这里其实调用了cdev_add和device_add。然而,device_create()是device_register()的封装,而device_register()则是device_add()的封装。
open_i2c_dev是i2c-tool
工具open i2c-dev驱动的函数,根据传递的参数最终重要函数应该如下:
file = open("/dev/i2c-0", O_RDWR);
该函数的主要代码如下:
/* File Path = i2c-tools-4.3/tools/i2cbusses.c */
int open_i2c_dev(int i2cbus, char *filename, size_t size, int quiet)
{
int file, len;
len = snprintf(filename, size, "/dev/i2c/%d", i2cbus);
if (len >= (int)size) {
fprintf(stderr, "%s: path truncated\n", filename);
return -EOVERFLOW;
}
file = open(filename, O_RDWR);
if (file < 0 && (errno == ENOENT || errno == ENOTDIR)) {
len = snprintf(filename, size, "/dev/i2c-%d", i2cbus);
if (len >= (int)size) {
fprintf(stderr, "%s: path truncated\n", filename);
return -EOVERFLOW;
}
file = open(filename, O_RDWR);
}
...
return file;
}
应用层调用open后,会对应调用i2c-dev通用驱动的open函数。主要是如下几个步骤:
/* File Path = kernel/drivers/i2c/i2c-dev.c */
static int i2cdev_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct i2c_client *client;
struct i2c_adapter *adap;
adap = i2c_get_adapter(minor);
if (!adap)
return -ENODEV;
/* This creates an anonymous i2c_client, which may later be
* pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
*
* This client is ** NEVER REGISTERED ** with the driver model
* or I2C core code!! It just holds private copies of addressing
* information and maybe a PEC flag.
*/
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client) {
i2c_put_adapter(adap);
return -ENOMEM;
}
snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
client->adapter = adap;
file->private_data = client;
return 0;
}
open_i2c_dev是i2c-tool
工具来设置I2C的设备地址。具体代码如下:
/* File Path = i2c-tools-4.3/tools/i2cbusses.c */
int set_slave_addr(int file, int address, int force)
{
/* With force, let the user read from/write to the registers
even when a driver is also running */
if (ioctl(file, force ? I2C_SLAVE_FORCE : I2C_SLAVE, address) < 0) {
fprintf(stderr,
"Error: Could not set address to 0x%02x: %s\n",
address, strerror(errno));
return -errno;
}
return 0;
}
应用层调用ioctl I2C_SLAVE_FORCE后,会对应调用i2c-dev通用驱动对应的ioctl I2C_SLAVE_FORCE。主要是如下2个步骤:
/* File Path = kernel/drivers/i2c/i2c-dev.c */
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct i2c_client *client = file->private_data;
unsigned long funcs;
switch (cmd) {
case I2C_SLAVE:
case I2C_SLAVE_FORCE:
if ((arg > 0x3ff) ||
(((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
return -EINVAL;
if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
return -EBUSY;
/* REVISIT: address could become busy later */
client->addr = arg;
return 0;
...
}
i2c_read_bytes这个函数是我自己写的demo code,这里只列了read部分来分析,完整的demo code可以查看上一篇我的博客。主要是如下2个步骤:
static int i2c_read_bytes(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t *values, uint8_t len)
{
uint8_t outbuf[1];
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[2];
outbuf[0] = reg_addr;
messages[0].addr = slave_addr;
messages[0].flags = 0;
messages[0].len = sizeof(outbuf);
messages[0].buf = outbuf;
/* The data will get returned in this structure */
messages[1].addr = slave_addr;
messages[1].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
messages[1].len = len;
messages[1].buf = values;
/* Send the request to the kernel and get the result back */
packets.msgs = messages;
packets.nmsgs = 2;
if(ioctl(fd, I2C_RDWR, &packets) < 0)
{
printf("Error: Unable to send data");
return -1;
}
return 0;
}
应用层调用ioctl I2C_RDWR后,会对应调用i2c-dev通用驱动对应的ioctl I2C_RDWR。它将应用层的数据到拷贝的内核,具体如下:
/* File Path = kernel/drivers/i2c/i2c-dev.c */
case I2C_RDWR: {
struct i2c_rdwr_ioctl_data rdwr_arg;
struct i2c_msg *rdwr_pa;
//从用户空间拷贝数据到内核空间
copy_from_user(&rdwr_arg,(struct i2c_rdwr_ioctl_data __user *)arg,sizeof(rdwr_arg));
//分配一块内存空间,将用户空间的数据拷贝进去
rdwr_pa = memdup_user(rdwr_arg.msgs, rdwr_arg.nmsgs * sizeof(struct i2c_msg));
return i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
}
static int i2cdev_ioctl_rdwr(struct i2c_client *client, unsigned nmsgs, struct i2c_msg *msgs)
{
***
res = i2c_transfer(client->adapter, msgs, nmsgs);
***
}
总结:其实应用层初始化的i2c_msg就是直接给内核i2c_transfer
将I2C消息发送出去的。