2023年6月26日发(作者:)
LinuxI2C驱动框架⼀、I2C总线概述I2C是由Philips公司开发的⼀种简单的、双向同步串⾏总线,它只需要两条线即可在连接于总线上的器件之间传送信息,其硬件连接框图如下所⽰:SCL:串⾏时钟线,数据传输过程中⽤于同步的时钟信号,低电平时允许SDA线上数据改变。SDA:串⾏数据线,在时钟信号作⽤下,数据按位在数据线上进⾏传输。I2C总线上的设备之间通信都要遵从I2C总线协议,I2C总线由起始信号、停⽌信号、应答信号、⾮应答信号组成。起始信号:当时钟线SCL为⾼期间,数据线SDA由⾼到低的跳变。停⽌信号:当时钟线SCL为⾼期间,数据线SDA由低到⾼的跳变。应答信号(ACK):应答位为低电平时,规定为有效应答位,表⽰接收器已经成功接收到该字节。⾮应答信号(NACK):应答位为⾼电平时,规定为⾮应答位,⼀般表⽰接收器接收该字节没有成功。挂接在同⼀条I2C总线上的设备都要⾃⼰的物理地址,I2C主机控制器在和设备通信前需要先发送设备的地址,设备接收到总线上传过来的地址,看是否是⾃⼰的地址,如果是产⽣后续的应答。主机控制器和设备通信⼀般是由⼀个起始信号开始和⼀个停⽌信号结束,地址信息⼀般是7bit,发送地址的最后⼀位代表数据传输的⽅向,1表⽰是读,0表⽰写操作,其发送时序⼀般如下所⽰:主机发送数据主机读取数据 前⾯对I2C总线的⼀些基本概念和I2C协议的做了简单的介绍,下⾯开始来分析Linux内核的I2C驱动框架,看看内核中如何实现对I2C设备的⽀持。⼆、Linux内核I2C驱动1、 ⼏个重要对象内核中的I2C驱动框架使⽤了总线设备驱动模型,在分析内核I2C驱动之前,先讨论这⼏个重要的数据结构。1.1、I2C总线I2C总线是⼀条虚拟的bus总线(同platform总线⼀样,位于/sys/bus⽬录),其在driversi2ci2c-core.c实现,具体内容如下:struct bus_type i2c_bus_type = { .name = "i2c", .dev_attrs = i2c_dev_attrs, .match = i2c_device_match, .uevent = i2c_device_uevent, .probe = i2c_device_probe, .remove = i2c_device_remove, .shutdown = i2c_device_shutdown, .suspend = i2c_device_suspend, .resume = i2c_device_resume,};这个i2c总线结构管理着I2C设备与I2C驱动的匹配、删除等操作。当有新的I2C驱动和设备加⼊时,会⾃动调⽤总线.match指针指向的函数i2c_device_match进⾏I2C设备和驱动匹配,如果匹配成功调⽤总线的.probe指针指向的函数i2c_device_probe。1.2、I2C驱动struct i2c_driver { int id; unsigned int class; int (*attach_adapter)(struct i2c_adapter *); /* 设备探测函数 */ int (*detach_adapter)(struct i2c_adapter *); int (*detach_client)(struct i2c_client *); int (*probe)(struct i2c_client *); /* probe函数 */ int (*remove)(struct i2c_client *);
void (*shutdown)(struct i2c_client *); int (*suspend)(struct i2c_client *, pm_message_t mesg); int (*resume)(struct i2c_client *); int (*command)(struct i2c_client *client,unsigned int cmd, void *arg); struct device_driver driver; /* 表⽰这是⼀个驱动 */ struct list_head list;};驱动对应的核⼼数据结构,对应⼀个I2C驱动对象1.3、I2C设备struct i2c_client { unsigned short flags;
unsigned short addr; /* 芯⽚设备地址,7bit的格式*/ char name[I2C_NAME_SIZE]; struct i2c_adapter *adapter; /* 指向适配器指针,设备挂接的i2c总线上主机控制器的抽象 */ struct i2c_driver *driver; /* 设备的驱动程序指针 */ int usage_count;
struct device dev; /* 代表这是⼀个设备 */ int irq; /* 中断号 */ char driver_name[KOBJ_NAME_LEN]; /* 设备名称 */ struct list_head list; struct completion released;};设备对应的核⼼数据结构,I2C设备的抽象,对应的是I2C设备1.4、I2C适配器通过前⾯的介绍,知道了I2C驱动和I2C设备,我们通过I2C驱动去和I2C设备进⾏通讯,但驱动和设备间如何进⾏通信呢,这就需要⼀个I2C适配器,内核的I2C适配器对应的就是SOC上的I2C控制器。struct i2c_adapter { struct module *owner; unsigned int id; /* 设备编号 */ unsigned int class; const struct i2c_algorithm *algo; /* 算法,发送时序 */ void *algo_data; int (*client_register)(struct i2c_client *); int (*client_unregister)(struct i2c_client *); u8 level;
struct mutex bus_lock; struct mutex clist_lock; int timeout; int retries; struct device dev; /* 表⽰这是⼀个设备 */ int nr; struct list_head clients; struct list_head list; char name[48]; struct completion dev_released;};其中i2c_algorithm是算法的意思,知道如何去发送I2C时序来访问I2C总线struct i2c_algorithm { int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs, int num); int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data * data); int (*algo_control)(struct i2c_adapter *, unsigned int, unsigned long); u32 (*functionality) (struct i2c_adapter *);};总的可以概括为I2C驱动有4个重要的数据结构,分别⽤于描述I2C总线、I2C驱动、I2C设备、I2C适配器I2C总线:挂接I2C驱动和设备,管理I2C驱动和设备的匹配和删除操作I2C驱动:对应的是I2C设备的驱动程序I2C设备:具体的硬件设备的抽象,抽象I2C总线上连接的I2C物理设备I2C适配器:I2C驱动访问I2C设备时使⽤的,具体对应的是SOC上的I2C控制器的抽象上⾯讲解了内核I2C中的⼏个重要对象,对I2C驱动的⼏个重要数据结构有了基本的了解,下⾯通过分析内核源码来深⼊学习I2C驱动框架。2、内核源码分析2.1、注册I2C驱动编写I2C驱动程序时,通过调⽤i2c_add_driver函数来注册驱动程序,下⾯来分析这个函数,看看注册驱动程序时发⽣了什么。static inline int i2c_add_driver(struct i2c_driver *driver){ return i2c_register_driver(THIS_MODULE, driver);}int i2c_register_driver(struct module *owner, struct i2c_driver *driver){ ... if (is_newstyle_driver(driver)) { //判断是否是新类型的驱动⽅式 if (driver->attach_adapter || driver->detach_adapter || driver->detach_client) { printk(KERN_WARNING "i2c-core: driver [%s] is confusedn", driver->); return -EINVAL; } } driver-> = &i2c_bus_type; //绑定i2c_bus_type res = driver_register(&driver->driver); //向总线注册驱动 if (res) return res; list_add_tail(&driver->list,&drivers);
if (driver->attach_adapter) { //遍历内核中所有的adapter,使⽤driver中的attach_adapter去寻找⽀持的adapter struct i2c_adapter *adapter; list_for_each_entry(adapter, &adapters, list) { driver->attach_adapter(adapter); } } ...}由上⾯的代码可以看出,在调⽤i2c_add_driver后做了三件事情,第⼀,绑定了i2c_bus_type总线;第⼆,向总线注册了驱动;第三,遍历内核中所有注册的adapter,调⽤driver中的attach_adapter函数去寻找⽀持该设备的adapter。乍⼀看,会感觉第三件事情⽐较重要,匹配规则应该在这⾥,其实并⾮如此,这⾥仅包含了⼀部分的匹配规则,还有⼀种匹配规则在driver_register函数中。内核将i2c驱动绑定到设备分为两种⽅式,⼀种是称为“新式”驱动程序遵循标准的Linux驱动程序模型,另⼀种是“旧版”驱动程序⾃⼰创建设备节点。这么说的原因可以从注册I2C驱动⼊⼝处调⽤is_newstyle_driver判断做出分析,is_newstyle_driver是个宏,其内容如下:#define is_newstyle_driver(d) ((d)->probe || (d)->remove)注册的i2c_driver中如果有probe和remove函数,被认为是”新式“驱动,并且”新式"驱动不能有attach_adapter、detach_adapter等函数,“新式”驱动driver->attach_adapter为NULL,所以前⾯讲的第三件事情⾥的操作对于“新式”驱动不会执⾏。由此看见,“新式”驱动使⽤driver_register函数进⾏匹配,“旧式”驱动使⽤i2c_driver中的attach_adapter函数去寻找⽀持该设备的adapter进⾏匹配。下⾯来分析这两种匹配⽅式,先来看driver_register函数。driver_register函数是内核总线设备驱动模型中驱动部分的注册函数,内容如下:int driver_register(struct device_driver * drv){ ... return bus_add_driver(drv);}int bus_add_driver(struct device_driver *drv){ ... if (drv->bus->drivers_autoprobe) { error = driver_attach(drv);
if (error) goto out_unregister; } /* 将驱动添加到总线的驱动链(bus->p->klist_drivers) */ klist_add_tail(&drv->knode_bus, &bus->klist_drivers); ...}driver_attach函数是将注册的driver和bus上的设备进⾏匹配的,这个函数是重头,接下来详细分析这个函数int driver_attach(struct device_driver * drv){ return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);}driver_attach函数调⽤bus_for_each_dev,来看看这个函数做了什么int bus_for_each_dev(struct bus_type * bus, struct device * start, void * data, int (*fn)(struct device *, void *)){ ... /* 遍历bus上挂载的所有设备(bus->p->klist_devices),执⾏fn函数 */ while ((dev = next_device(&i)) && !error) error = fn(dev, data); ...}执⾏fn函数即__driver_attach函数,也就是说driver_attach函数会遍历总线上的所有设备执⾏__driver_attach函数,接下来分析__driver_attach函数static int __driver_attach(struct device * dev, void * data){ ... /* 如果dev没有匹配驱动程序,调⽤driver_probe_device函数 */ if (!dev->driver) driver_probe_device(drv, dev); ...}继续看driver_probe_device函数,通过这个函数我们可以知道匹配规则int driver_probe_device(struct device_driver * drv, struct device * dev){ ... if (drv->bus->match && !drv->bus->match(dev, drv)) goto done; ... ret = really_probe(dev, drv); ...}判断注册的i2c_driver驱动挂载的总线上是否有match函数,如果存在就调⽤总线上的match函数进⾏设备和驱动的匹配。在前⾯我们已经列出了i2c_driver挂载的i2c总线,match函数指针指向i2c_device_match函数static int i2c_device_match(struct device *dev, struct device_driver *drv){ struct i2c_client *client = to_i2c_client(dev); //使⽤container_of获取i2c_client struct i2c_driver *driver = to_i2c_driver(drv); //使⽤container_of获取i2c_driver ... if (!is_newstyle_driver(driver)) return 0; ... return strcmp(client->driver_name, drv->name) == 0;}驱动和设备的匹配很简单,通过⽐较名称,如果驱动名称和设备名称⼀致则代表匹配成功。好了,现在弄清楚匹配规则了,下⾯来看⼀看如果匹配成功后要⼲嘛,也就是要分析really_probe函数static int really_probe(struct device *dev, struct device_driver *drv){ ... dev->driver = drv; /* 让dev的driver指向当前匹配的drv,和前⾯相互呼应,对于⼀个设备只能匹配⼀个驱动程序 */ ... if (dev->bus->probe) { ret = dev->bus->probe(dev); if (ret) goto probe_failed; } else if (drv->probe) { ret = drv->probe(dev); if (ret) goto probe_failed; } ...}really_probe函数判断设备挂载的总线上的probe函数是否存在,如果存在调⽤总线上的probe函数,显然dev肯定也是挂载到前⾯我们已经列出了i2c_driver挂载的i2c总线,probe函数指针指向i2c_device_probe,probe函数存在static int i2c_device_probe(struct device *dev){ struct i2c_client *client = to_i2c_client(dev); struct i2c_driver *driver = to_i2c_driver(dev->driver); ... return driver->probe(client);}⾄此,“新式”驱动i2c_driver注册分析完成,当向内核注册i2c驱动时,会将i2c驱动添加到总线的链表中,遍历总线上所有设备,通过i2c_client->driver_name, i2c_driver->name进⾏字符串匹配,如果匹配成功,就调⽤驱动程序的probe函数。下⾯来分析另⼀种匹配⽅式,”旧式“驱动程序匹配,继续看代码int i2c_register_driver(struct module *owner, struct i2c_driver *driver){ ... /* legacy drivers scan i2c busses directly */ if (driver->attach_adapter) { struct i2c_adapter *adapter; list_for_each_entry(adapter, &adapters, list) { driver->attach_adapter(adapter); } } ...}list_for_each_entry:找到内核中注册的所有adapter,内核注册的adapter挂接在adapters为头部链表中,查找内核中注册的所有adapter即遍历adapters为头部链表中的所有节点。调⽤注册driver中的attach_adapter函数,那么attach_adapter函数内容时什么呢?搜索内核找到内核⾃带的i2c设备驱动,以eeprom驱动为例,发现其实现的attach_adapter即调⽤i2c_probe函数int i2c_probe(struct i2c_adapter *adapter, struct i2c_client_address_data *address_data, int (*found_proc) (struct i2c_adapter *, int, int)){ ... err = i2c_probe_address(adapter, address_data->normal_i2c[i], -1, found_proc); ...}static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind, int (*found_proc) (struct i2c_adapter *, int, int)){ ... if (i2c_check_addr(adapter, addr)) return 0; ... if (i2c_smbus_xfer(adapter, addr, 0, 0, 0, I2C_SMBUS_QUICK, NULL) < 0) return 0; /* prevent 24RF08 corruption */ if ((addr & ~0x0f) == 0x50) i2c_smbus_xfer(adapter, addr, 0, 0, 0, I2C_SMBUS_QUICK, NULL); } err = found_proc(adapter, addr, kind); ...}i2c_probe函数做了三件事情,第⼀,调⽤i2c_check_addr,该函数判断传⼊的i2c设备的地址是否合法;第⼆,调⽤i2c_smbus_xfer,该函数即调⽤adapter->algo->smbus_xfer函数,该函数使⽤SOC上I2C控制器向I2C总线发送特定设备地址,看设备是否有应答;第三,如果第⼆步检测但设备,调⽤found_proc函数,found_proc函数是驱动编写者实现的,⼀般即创建并初始化i2c_client,调⽤i2c_attach_client向内核注册,注册字符设备驱动等。为什么会有“旧式”这种⽅式?如果在不知道i2c设备在哪⼀条总线的情况下,这种⽅式就发挥了作⽤上⾯分析了注册I2C驱动,下⾯来分析注册I2C设备2.2、注册I2C设备内核通过i2c_new_device注册i2c设备struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)注册设备时需要指定i2c适配器和设备信息⼀台机器上可能有多个i2c控制器,不同的i2c控制器在linux内核中被抽象成适配器来表⽰,适配器中包含了它所其连接的i2c总线的访问⽅法。所有在注册设备时,设备接在了哪条i2c总线上,就设置哪⼀条总线的适配器。i2c_adapter可以通过i2c_get_adapter来获取struct i2c_adapter* i2c_get_adapter(int id)i2c_board_info结构体描述了设备的硬件信息struct i2c_board_info { char driver_name[KOBJ_NAME_LEN]; //设备名称,⽤于与驱动匹配 char type[I2C_NAME_SIZE]; unsigned short flags; unsigned short addr; //设备地址 void *platform_data; int irq; //中断号};下⾯来详细分析i2c_new_device函数struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info){ struct i2c_client *client; int status; client = kzalloc(sizeof *client, GFP_KERNEL); //分配⼀个i2c_client设备 if (!client) return NULL; client->adapter = adap; //设置i2c_client成员 client->rm_data = info->platform_data; client->flags = info->flags; client->addr = info->addr; client->irq = info->irq; strlcpy(client->driver_name, info->driver_name, sizeof(client->driver_name)); strlcpy(client->name, info->type, sizeof(client->name)); status = i2c_attach_client(client); //调⽤i2c_attach_client if (status < 0) { kfree(client); client = NULL; } return client;}i2c_new_device函数中分配了⼀个i2c_client设备,并根据传⼊的适配器和设备信息初始化i2c_client,最后调⽤i2c_attach_client函数要想弄清楚i2c_new_device函数,还需要继续在分析i2c_attach_client函数int i2c_attach_client(struct i2c_client *client){ struct i2c_adapter *adapter = client->adapter; ... list_add_tail(&client->list,&adapter->clients); ... client-> = &i2c_bus_type; ... res = device_register(&client->dev); ...}i2c_attach_client函数将注册的i2c_client挂载到i2c_bus_type这条i2c总线上,并调⽤了device_register函数这个device_register和前⾯介绍的i2c_driver注册时调⽤的driver_register的功能相似。注册⼀个设备并查找bus总线下所有挂载的driver,调⽤bus的match函数进⾏匹配,如果匹配成功调⽤i2c_driver中的probe函数⾄于匹配的规则我们在前⾯介绍i2c_driver时已经分析过了,这⾥就不做详细的介绍。2.3、注册I2C适配器每注册⼀个I2C适配器代表机器的上⼀个物理的I2C控制器,注册的I2C适配器中包含了I2C控制器发送I2C协议访问I2C总线的⽅法,内核中使⽤i2c_add_adapter函数来注册⼀个i2c适配器int i2c_add_adapter(struct i2c_adapter *adapter){ ... return i2c_register_adapter(adapter);}static int i2c_register_adapter(struct i2c_adapter *adap){ ... list_add_tail(&adap->list, &adapters); ... ... adap-> = &i2c_adapter_class; res = device_register(&adap->dev); ... list_for_each(item,&drivers) { driver = list_entry(item, struct i2c_driver, list); if (driver->attach_adapter) driver->attach_adapter(adap); }}将注册的i2c_adapter添加到adapters链表的尾部,调⽤device_register注册⼀个i2c_adapter设备,注册的device将存在虚拟⽂件系统的/sys/bus/platform/devices⽬录。最核⼼的是函数的最后⼀部分内容,遍历内核中注册的i2c_driver,调⽤i2c_driver中的attach_adapter函数。由此可见注册⼀个i2c适配器时,使⽤“旧式”驱动去扫描该i2c控制器的挂接的总线上挂接的所有i2c设备。三、总结内核的i2c框架中实现了两种驱动和设备的匹配⽅式,⼀种是基于总线——设备——驱动模型的⽅式,另⼀种是直接找到内核中注册的所有适配器,调⽤适配器中的算法发送i2c协议来查看i2c设备是否应答,从⽽创建i2c设备的⽅式。内核在注册驱动时去分辨这两种注册的⽅式,如果注册的i2c_driver中实现了probe函数,被识别成使⽤总线设备驱动模型的⽅式,这种⽅式需要实现⼀个i2c_client,i2c_client中指定设备名称和⽀持该设备的适配器。设备或驱动在注册时,分别调⽤总线中挂载的驱动或设备,调⽤总线的.match函数⽐较两者名称,名称相同驱动和设备匹配成功,如果匹配成功调⽤i2c_driver的probe函数。第⼆种⽅式,在不知道i2c设备在哪⼀条总线的情况下,这种⽅式就发挥了作⽤。这种⽅式调⽤i2c_driver中.attach_adapter函数查找到驱动⽀持的适配器,找到对应的适配器需要⾃⼰创建i2c_client,创建设备节点等,所有对应第⼆种驱动需要驱动编写者去实现i2c_driver中.attach_adapter函数。
发布者:admin,转转请注明出处:http://www.yc00.com/xiaochengxu/1687755695a39878.html
评论列表(0条)