参考:https://www.cnblogs.com/zzb-Dream-90Time/p/7605473.html
宋宝华《Linux设备驱动开发详解:基于最新的Linux4.0内核》
看过宋宝华《Linux设备驱动开发详解:基于最新的Linux4.0内核》的朋友们会发现,在i2c章有一节是i2c-dev.c文件分析,这一节只做了一个大概的分析,今天咱们看看它到底能做什么。
i2c-dev.c文件完全可以被看作是一个I2C设备驱动,它实现的i2c_client是虚拟、临时的, 主要是为了便于从用户空间操作I2C外设。 i2c-dev.c针对每个I2C适配器生成一个主设备号为89的设备文件, 实现了i2c_driver的成员函数以及文件操作接口, 因此i2c-dev.c的主体是“i2c_driver成员函数+字符设备驱动”。
/* \drivers\i2c\i2c-dev.c */
static int __init i2c_dev_init(void)
{
/* 注册i2c字符设备,连接i2cdev_fops,向应用层提供操作接口
res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
/* 创建/sys/class/ i2c-dev */
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
/* 注册i2cdev_notifier到内核通知链,响应事件就调用其成员函数 */
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
/* Bind to already existing adapters right away */
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
return 0;
}
/* 提供通知链的响应函数 */
static struct notifier_block i2cdev_notifier = {
.notifier_call = i2cdev_notifier_call,
};
static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action, void *data)
{
struct device *dev = data;
switch (action) {
case BUS_NOTIFY_ADD_DEVICE: /* 添加设备事件 */
return i2cdev_attach_adapter(dev, NULL);
case BUS_NOTIFY_DEL_DEVICE: /* 删除设备事件 */
return i2cdev_detach_adapter(dev, NULL);
}
return 0;
}
static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
int res;
adap = to_i2c_adapter(dev);
/* 配并初始化了一个struct i2c_dev结构 */
i2c_dev = get_free_i2c_dev(adap);
/* 注册i2c_dev会被链入链表i2c_dev_list中。再分别以I2C_MAJOR,、adap->nr为主次设备号创建了一个device,创建/dev/i2c-0 */
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr), NULL,
"i2c-%d", adap->nr);
res = device_create_file(i2c_dev->dev, &dev_attr_name);
return 0;
}
此时我们就可以在/dev下看到i2c-*的字符设备了,可以通过read、write、ioctl等系统调用对它操作了,read、write实现的功能比较单一,只进行一次时序读写,所以很少使用,一般都用ioctl访问终端寄存器
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_RDWR:
return i2cdev_ioctl_rdrw(client, arg);
}
return 0;
}
又调用到上章讲的终极函数了。
static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,
unsigned long arg)
{
res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);
}
刚才说到通知链,只有调用到通知链的响应函数才在/dev下添加了设备,那它就很重要了。对通知链原理不做分析,这里只分析i2c设备实现代码,有兴趣的参考以下链接。
参考:https://blog.csdn.net/qq_22340085/article/details/78457005
返回查看i2c-core.c中i2c adapter的注册函数
/* \drivers\i2c\i2c-core.c */
static int i2c_register_adapter(struct i2c_adapter *adap)
{
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
}
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
int device_add(struct device *dev)
{ /* \drivers\base\core.c */
struct device *parent = NULL;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
}
blocking_notifier_call_chain的调用,说明了通知链的链表头、遍历的类型为BUS_NOTIFY_ADD_DEVICE、传递的数据为 struct device dev。
在i2c总线注册时也做了通知链的初始化
/* \drivers\i2c\i2c-core.c */
static int __init i2c_init(void)
{
int retval;
retval = bus_register(&i2c_bus_type);
}
int bus_register(struct bus_type *bus)
{ /* \drivers\base\bus.c */
/* 初始化总线中阻塞通知链 */
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
}
所以当i2c总线发布通知,当i2c-dev.c中的i2c虚拟字符设备注册到系统后,注册的响应函数就开始响应添加设备的事件,然后我们就在文件系统的/dev下看到了i2c-*的字符设备。