linux I2C驱动分析(二)


我们之前说过I2C设备驱动有两种一种是用户模式的驱动,另一种是我们自己编写的i2c设备驱动,我们先分析用户模式的驱动,它已经由内核编写好代码在I2C-dev.c当中。


一. 用户模式I2C设备驱动分析

I2C设备驱动属于总线,设备,驱动模型当中的驱动。代码如下:

static struct i2c_driver i2cdev_driver = {
.driver = {
.name = "dev_driver",
},
   
  /* 依附i2c_adapter_class指针,在向系统添加i2c_driver时,驱动注册函数会遍历适配器设备类i2c_adapter_class中所有的设备并调用该驱动的attach_adapter方法进行依        附。相应的,在添加i2c_adapter时,适配器注册函数会遍历总线i2c_bus_type上所有的驱动。如果驱动定义了attach_adapter方法,它也将得到调用。*/
.attach_adapter = i2cdev_attach_adapter,     
.detach_adapter = i2cdev_detach_adapter,
};


static int __init i2c_dev_init(void)
{
int res;

printk(KERN_INFO "i2c /dev entries driver\n");


res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);    //注册字符设备,函数操作集当中提供了大量了读写和操作接口给应用程序
if (res)
goto out;

i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");  注册了一个名为“i2c-dev”设备类
if (IS_ERR(i2c_dev_class)) {
res = PTR_ERR(i2c_dev_class);
goto out_unreg_chrdev;
}

res = i2c_add_driver(&i2cdev_driver);         向系统注册i2c设备驱动。
if (res)
goto out_unreg_class;


return 0;

out_unreg_class:
class_destroy(i2c_dev_class);
out_unreg_chrdev:
unregister_chrdev(I2C_MAJOR, "i2c");
out:
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res;
}

i2c_dev_init(void)这个函数主要做了1.注册字符设备,函数操作集当中提供了大量了读写和操作接口给应用。2.注册了一个名为“i2c-dev”设备类。3.  向系统注册i2c设备驱动。
这三件事。现在追踪一下i2c_add_driver(&i2cdev_driver)这个函数。看看它做了什么工作。


static inline int i2c_add_driver(struct i2c_driver *driver)
{
return i2c_register_driver(THIS_MODULE, driver);
}

继续追踪i2c_register_driver(THIS_MODULE, driver)这个函数。

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;

if (unlikely(WARN_ON(!i2c_bus_type.p)))
return -EAGAIN;

if (is_newstyle_driver(driver)) {
if (driver->detach_adapter || driver->detach_client) {
printk(KERN_WARNING
"i2c-core: driver [%s] is confused\n",
driver->driver.name);
return -EINVAL;
}
}


driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;    //将I2C设备驱动挂到I2C总线上

res = driver_register(&driver->driver);
if (res)
return res;
mutex_lock(&core_lock);


pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);


INIT_LIST_HEAD(&driver->clients);
/* Walk the adapters that are already present */
class_for_each_device(&i2c_adapter_class, NULL, driver,        //遍历2c_adapter_class,调用__attach_adapter函数
     __attach_adapter);

mutex_unlock(&core_lock);
return 0;
}

i2c_register_driver()这个函数主要将I2C设备驱动挂到i2c总线上和调用class_for_each_device(&i2c_adapter_class, NULL, driver,  __attach_adapter);来遍历所有属于i2c_adapter_class类的设备,而只有添加到系统中的适配器设备才属于该类。所以,只有适配器设备才会执行__attach_adapter函数,并最终调用在2cdev_driver中定义好的
i2cdev_attach_adapter函数。现在追踪i2cdev_attach_adapter这个函数。


static int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
struct i2c_dev *i2c_dev;
int res;


i2c_dev = get_free_i2c_dev(adap);   //该函数分配了一个i2c_dev的对象,并将该对象添加到全局链表i2c_dev_list上。
if (IS_ERR(i2c_dev))
return PTR_ERR(i2c_dev);

i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,  //创建设备号为 MKDEV(I2C_MAJOR, adap->nr),名称为“i2c-%d”的字符设备节点。
    MKDEV(I2C_MAJOR, adap->nr), NULL,
    "i2c-%d", adap->nr);
if (IS_ERR(i2c_dev->dev)) {
res = PTR_ERR(i2c_dev->dev);
goto error;
}
res = device_create_file(i2c_dev->dev, &dev_attr_name);
if (res)
goto error_destroy;


pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
adap->name, adap->nr);
return 0;
error_destroy:
device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
error:
return_i2c_dev(i2c_dev);
return res;
}

i2cdev_attach_adapter这个函数主要做了两件事1.调用 get_free_i2c_dev(adap);   //该函数分配了一个i2c_dev的对象,并将该对象添加到全局链表i2c_dev_list上。2.调用

device_create()创建设备号为 MKDEV(I2C_MAJOR, adap->nr),名称为“i2c-%d”的字符设备节点。

除了在注册i2c_driver驱动时会遍历所有的适配器设备外,在调用i2c_add-addpter()注册适配器时也会遍历i2c_bus_type总线上所有的驱动,并调用它们的attcah_adapter成员方法。因此,不管是先注册驱动i2cdev_driver,还是先添加i2c_adapter对象,子系统都会为每个适配器设备创建相应的设备节点。


之前我们讲解了这么多讲述都是I2c设备驱动,那么与I2c设备驱动相对应的设备信息i2c_client是在哪里完成注册的呢?i2c子系统利用i2c_register_board_info()函数在添加i2c_adapter对象之前,将一组i2c_board_info对象注册进内核。i2c_board_info对象描述一条I2C设备信息,它实际上是用于创建i2c_client对象的临时信息,i2c_register_board_info()函数会将所用的i2c_board_info对象添加到一个名为__i2c_board_list链表上。我们通常将一组i2c_board_info写入板级初始化文件,如mach-tq2440.同时在板级初始化函数中调用i2c_register_board_info()。


而我们在I2C总线驱动的proble中使用i2c_add_adapter注册适配器时,就会遍历_i2c_board_liat链表,并取出i2c_board_info对象调用i2c_new_device()创建i2c_client设备对象。


现在我们又会带i2c设备驱动当中,我们重点看一下之提供给用户程序使用的一些read .write .open 函数。


static int i2cdev_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);     //inode中获得次设备号
struct i2c_client *client;
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
int ret = 0;


lock_kernel();
i2c_dev = i2c_dev_get_by_minor(minor);  //从链表i2c_dev_list中获得与minor对应的i2c_dev对象(i2c_dev->adap等于minor的对象)
if (!i2c_dev) {
ret = -ENODEV;
goto out;
}


adap = i2c_get_adapter(i2c_dev->adap->nr);   //获得id为i2c_dev->adap->nr的适配器对象;
if (!adap) {
ret = -ENODEV;
goto out;
}


client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client) {
i2c_put_adapter(adap);
ret = -ENOMEM;
goto out;
}
snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
client->driver = &i2cdev_driver;


client->adapter = adap;
file->private_data = client;


out:
unlock_kernel();
return ret;
}


这个open函数主要的做了1:调用iminor(inode)获得次设备号并从链表i2c_dev_list中获得与minor对应的i2c_dev对象。

                                              2:调用 i2c_dev_get_by_minor(minor);  从链表i2c_dev_list中获得与minor对应的i2c_dev对象(i2c_dev->adap等于minor的对象)

                                              3:从 i2c_get_adapter(i2c_dev->对象,并用adap->nr)中获得id为i2c_dev->adap->nr的适配器对象

                                              4:分配一个i2c_client对象,并用adap. i2cdev_driver初始化adapter,driver成员,让设备同相应的适配器和驱动绑定。


现在我们把重点放在ictol函数上

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct i2c_client *client = (struct i2c_client *)file->private_data;
unsigned long funcs;


dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
cmd, arg);


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;

client->addr = arg;
return 0;
case I2C_TENBIT:
if (arg)
client->flags |= I2C_M_TEN;
else
client->flags &= ~I2C_M_TEN;
return 0;
case I2C_PEC:
if (arg)
client->flags |= I2C_CLIENT_PEC;
else
client->flags &= ~I2C_CLIENT_PEC;
return 0;
case I2C_FUNCS:
funcs = i2c_get_functionality(client->adapter);
return put_user(funcs, (unsigned long __user *)arg);


case I2C_RDWR:
return i2cdev_ioctl_rdrw(client, arg);   //重点


case I2C_SMBUS:
return i2cdev_ioctl_smbus(client, arg);


case I2C_RETRIES:
client->adapter->retries = arg;
break;
case I2C_TIMEOUT:

client->adapter->timeout = msecs_to_jiffies(arg * 10);
break;
default:

return -ENOTTY;
}
return 0;
}

这个函数最重要的是 i2cdev_ioctl_rdrw(client, arg),我们追踪一下这个函数。其中的参数arg是我们的应用程序传进去的,必须是 (struct i2c_rdwr_ioctl_data __user )

类型的。

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,          //将arg的数据拷给rdwr_arg
  sizeof(rdwr_arg)))
return -EFAULT;


if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
return -EINVAL;


rdwr_pa = (struct i2c_msg *)
kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg),
GFP_KERNEL);
if (!rdwr_pa)
return -ENOMEM;


if (copy_from_user(rdwr_pa, rdwr_arg.msgs,
  rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {
kfree(rdwr_pa);
return -EFAULT;
}


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++) {

if ((rdwr_pa[i].len > 8192) ||
   (rdwr_pa[i].flags & I2C_M_RECV_LEN)) {
res = -EINVAL;
break;
}
data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf;
rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL);
if (rdwr_pa[i].buf == NULL) {
res = -ENOMEM;
break;
}
if (copy_from_user(rdwr_pa[i].buf, data_ptrs[i],
  rdwr_pa[i].len)) {
++i; /* Needs to be kfreed too */
res = -EFAULT;
break;
}
}
if (res < 0) {
int j;
for (j = 0; j < i; ++j)
kfree(rdwr_pa[j].buf);
kfree(data_ptrs);
kfree(rdwr_pa);
return res;
}


/*  i2c_transfe()调用了 master_xfer 函数进行的数据传输。至于 master_xfer 干什么的,我们再 I2C 总线层驱动分析中已经讲的很清楚了, master_xfer 就是我们适配器的  I2C  通信协议函数。*/
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;
}

I2C 的设备驱动层我们也讲完了,回顾一下, I2C 的设备驱动层主要是填充了 i2c_driver i2c_client 结构体,然后注册了 i2c_driver ,在 i2c_driver 中,我们探测了适配器。同时为了给用户提供 API ,我们还注册了一个字符设备,在字符设备中的 open 函数中,我们完成了 i2c_client 结构体的填充,并获取了匹配的适配器。最后我们讲了 ioctl 函数,重点分析了 I2C_RDWR 标签下最终调用适配器的 I2C 通信协议函数 master_xfer 完成数据传输。
      _

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值