linux设备模型:pci驱动程序注册过程
一个具有正常探测功能的pci驱动程序应具有基本的pci_driver结构实现,如:
static struct pci_driver driver_ops = {.name = "driver_name", // 驱动名称.id_table = pci_ids, // 驱动ids结构.probe = pci_probe, // 驱动探测函数.remove = pci_remove, // 驱动移除函数
};
其中,驱动ids结构可以按照以下方式实现:
static struct pci_device_id pci_ids[] = {{ PCI_DEVICE(MANF_ID, MODEL_CODE) },{ 0 }
};
MANF_ID 表示厂商ID 是一个数字ID,如0xfead 或 PCI_ANY_ID(表示通用),如果有真实硬件应与硬件ID一致
MODEL_CODE 表示厂商设备 是一个数字ID,如0x1234 或 PCI_ANY_ID(表示通用),如上
默认情况下,子系统的厂商ID和厂商设备ID不用设置,它们采用PCI_ANY_ID(表示通用),class、class_mask等成员默认也不需要指定,在设备创建后,创建相关对象关联设备节点即可(或创建时指定父级)pci_probe:
驱动探测函数,可在函数内部实现bar映射等设备相关的功能设置等等pci_remove:
驱动移除函数,可在函数内部实现驱动的移除等等,同样也可以实现设备取消bar映射等相关的功能设置准备工作完成后,我们开始驱动的执行逻辑分析:
通常情况下,我们通过定义module_init(函数名称),作为驱动程序入口函数执行,如pci_init_module:
static int pci_init_module(void)
{...如字符、块设备注册,class创建,class设备节点关联等等驱动其他的一些定义,如设置变量标志,输出注册相关新等等...dri_register = pci_register_driver(&driver_ops);if (dri_register) {printk(KERN_ERR ": pci driver registration failed !\n");goto fail;}...return dri_register;fail: if(DBUG_PRINT) printk(KERN_ALERT "pci_init_module :failed !\n");return dri_register;
}
现在分析pci_register_driver函数 :
pci_register_driver(driver) \
__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)THIS_MODULE 如果驱动编译选项为m或手动编译驱动,在编译时会生成__this_module结构对象,参考 __this_module。如果为y时,则通过"内置模块条目 /sys/modules"查找,参考 module_add_driver
KBUILD_MODNAME 模块名称,编译时通过如obj-m:=pci_test.o中获取pci_test
driver 注册的driver_ops对象
__pci_register_driver函数内部赋值完成后,执行driver_register(&driver_ops->driver); 驱动分配、探测设备,并注册到总线等等
在arm等架构下,pci_register_driver还包括设备树相关的概念流程,如DMA设置,通过开启CONFIG_OF相关编译选项,启动设备树相关功能,用于动态加载dts目录下的配置文件信息
目录
1. 函数分析
1.1 pci_register_driver
2. 源码结构
3. 部分结构定义
4. 扩展函数/变量
目录预览
1. 函数分析
1.1 pci_register_driver
驱动分配、探测并注册到总线
#define pci_register_driver(driver) \__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)||\/ int __pci_register_driver(struct pci_driver *drv, struct module *owner,const char *mod_name)
{/* 初始化通用驱动程序字段 */drv->driver.name = drv->name; // 驱动名称drv->driver.bus = &pci_bus_type; // 驱动的设备所属的总线drv->driver.owner = owner; // 所属者drv->driver.mod_name = mod_name; // 用于内置模块drv->driver.groups = drv->groups; // 驱动核心自动创建的默认属性drv->driver.dev_groups = drv->dev_groups; // 设备实例绑定到驱动程序后的附加属性spin_lock_init(&drv->dynids.lock); // 初始化动态id锁INIT_LIST_HEAD(&drv->dynids.list); // 初始化动态id链表/* 注册到核心 */return driver_register(&drv->driver); // 驱动分配、探测并注册到总线
}
EXPORT_SYMBOL(__pci_register_driver);
pci_driver
driver_register
2. 源码结构
pci_driver pci驱动结构
struct pci_driver {struct list_head node; // 节点const char *name; // 名称const struct pci_device_id *id_table; // pci设备id结构/* 必须为非NULL,才能调用探测 */int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); /* 探测函数 */// 已插入设备(硬件),并且pci设备id结构 描述的厂商ID等信息正确,可触发此函数执行void (*remove) (struct pci_dev *dev); /* 设备移除(如果不是热插拔驱动则为NULL) */int (*suspend) (struct pci_dev *dev, pm_message_t state); /* 设备挂起 */int (*suspend_late) (struct pci_dev *dev, pm_message_t state);int (*resume_early) (struct pci_dev *dev);int (*resume) (struct pci_dev *dev); /* 唤醒设备 */void (*shutdown) (struct pci_dev *dev);int (*sriov_configure) (struct pci_dev *dev, int num_vfs); /* PF pdev */const struct pci_error_handlers *err_handler; // PCI总线错误事件回调结构struct device_driver driver; /* 设备驱动结构 */struct pci_dynids dynids; // 动态id,用于驱动与设备的匹配// 如果没有找到,再查询静态id
};
pci_device_id
pci_device_id pci设备id结构
struct pci_device_id {__u32 vendor, device; /* 厂商和设备ID or PCI_ANY_ID*/__u32 subvendor, subdevice; /* 子系统ID or PCI_ANY_ID */__u32 class, class_mask; /* (class,subclass,prog-if) triplet */kernel_ulong_t driver_data; /* 驱动程序私有数据 */
};
3. 部分结构定义
driver_private 驱动私有结构
struct driver_private {struct kobject kobj; // 根kobjstruct klist klist_devices; // 设备列表struct klist_node knode_bus; // 设备节点struct module_kobject *mkobj; // 模块kobjstruct device_driver *driver; // 设备驱动结构
};
dev_pin_info 设备的引脚状态容器
struct dev_pin_info {struct pinctrl *p; 引脚控制包含设备的句柄struct pinctrl_state *default_state; // 句柄的默认状态(如果存在)struct pinctrl_state *init_state; // 探测时的状态(如果存在)
#ifdef CONFIG_PMstruct pinctrl_state *sleep_state; // 挂起时的状态(如果存在)struct pinctrl_state *idle_state; // 空闲(运行时挂起)时的状态(如果存在)
#endif
};
devres 设备资源
struct devres {struct devres_node node; // 设备资源节点/** 一些架构希望对kmalloc缓存执行DMA,并且需要比64位整数的对齐更大的保证对齐* 因此,我们在这里使用ARCH_KMALLOC_MINALIGN* 并获得与普通KMALLOC()分配的缓冲区完全相同的对齐方式*/u8 __aligned(ARCH_KMALLOC_MINALIGN) data[];// x86_64 按照 __alignof__(unsigned long long)
};
devres_node
devres_node 设备资源节点
struct devres_node {struct list_head entry; // 列表(链表)dr_release_t release; // 释放函数const char *name; // 名称size_t size; // 大小
};
pinctrl 引脚控制(设备引脚控制状态保持器)
struct pinctrl {struct list_head node; // 全局列表节点struct device *dev; // 使用此引脚控制的设备struct list_head states; // 此设备的状态列表struct pinctrl_state *state; // 当前状态struct list_head dt_maps; // 从设备树动态解析的映射表块(部分架构不具备设备树概念,此概念相关结构的部分成员不使用)struct kref users; // 引用计数
};
4. 扩展函数/变量
driver_register 驱动分配、探测并注册到总线
int driver_register(struct device_driver *drv)
{int ret;struct device_driver *other;BUG_ON(!drv->bus->p); // 子系统私有结构if ((drv->bus->probe && drv->probe) ||(drv->bus->remove && drv->remove) ||(drv->bus->shutdown && drv->shutdown)) // 这种情况应该是已注册驱动// 通常不指定设备驱动结构,由注册过程中自动完成关联printk(KERN_WARNING "Driver '%s' needs updating - please use ""bus_type methods\n", drv->name);other = driver_find(drv->name, drv->bus); // 按名称查找总线上的设备驱动结构if (other) { // 如果存在,应该是已注册驱动printk(KERN_ERR "Error: Driver '%s' is already registered, ""aborting...\n", drv->name);return -EBUSY;}ret = bus_add_driver(drv); // 驱动注册到总线
bus_add_driver
ret = driver_add_groups(drv, drv->groups); // 驱动kobj分配到驱动属性组kobject_uevent(&drv->p->kobj, KOBJ_ADD); // 通过发送uevent通知用户空间deferred_probe_extend_timeout(); // 取消延迟的工作(如果存在),延迟后将工作任务放入全局工作队列// deferred_probe_timeout_workreturn ret;
}
EXPORT_SYMBOL_GPL(driver_register);
bus_add_driver 驱动注册到总线
获取总线对象
分配驱动私有结构对象
初始化klist 设备结构
驱动私有结构的knode_bus 加入 bus私有结构的klist_drivers链表中
驱动程序遍历总线中的设备是否匹配,匹配则执行探测相关函数
驱动增加到模块
驱动创建sysfs属性文件(用户事件)
驱动kobj分配到总线的驱动属性组
驱动创建sysfs属性文件(unbind和bind)
int bus_add_driver(struct device_driver *drv)
{struct bus_type *bus;struct driver_private *priv;int error = 0;bus = bus_get(drv->bus); // 获取总线对象pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);priv = kzalloc(sizeof(*priv), GFP_KERNEL); // 分配驱动私有结构对象klist_init(&priv->klist_devices, NULL, NULL); // 初始化klist 设备结构priv->driver = drv; // 关联设备驱动结构对象drv->p = priv; // 关联驱动私有结构对象priv->kobj.kset = bus->p->drivers_kset; // 关联驱动列表(容器)error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,"%s", drv->name); // 私有结构对象的根kobj初始化// driver_ktype 驱动动态注册的sysfs的ktype(用于释放对象,访问sysfs文件等等)
driver_private
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); // 驱动私有结构的knode_bus 加入 bus私有结构的klist_drivers链表中
// 驱动结构与设备结构的创建过程(如结构的关系形式)大致相同if (drv->bus->p->drivers_autoprobe) { // 自动探测error = driver_attach(drv); // 驱动程序遍历总线中的设备是否匹配,匹配则执行探测相关函数// 驱动检查id列表(动态和静态)是否与设备匹配,匹配的情况下,执行驱动探测设备及延迟处理(如果需要)if (error)goto out_del_list;}
driver_attach
module_add_driver(drv->owner, drv); // 驱动增加到模块
module_add_driver
error = driver_create_file(drv, &driver_attr_uevent); // 驱动创建sysfs属性文件(用户事件)error = driver_add_groups(drv, bus->drv_groups); // 驱动kobj分配到总线的驱动属性组if (!drv->suppress_bind_attrs) { // falseerror = add_bind_files(drv); // 驱动创建sysfs属性文件(unbind和bind)if (error) {/* Ditto */printk(KERN_ERR "%s: add_bind_files(%s) failed\n",__func__, drv->name);}}return 0;
}
driver_attach 驱动程序遍历总线中的设备是否匹配,匹配则执行探测相关函数
驱动检查id列表(动态和静态)是否与设备匹配,匹配的情况下,执行驱动探测设备及延迟处理(如果需要)
int driver_attach(struct device_driver *drv)
{return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); // 遍历总线上的设备// 驱动检查id列表(动态和静态)是否与设备匹配,匹配的情况下,执行驱动探测设备及延迟处理(如果需要)
}
EXPORT_SYMBOL_GPL(driver_attach);
__driver_attach
bus_for_each_dev
__driver_attach 驱动检查id列表(动态和静态)是否与设备匹配,匹配的情况下,执行驱动探测设备及延迟处理(如果需要)
检查驱动关联id列表(动态和静态)是否与设备匹配,匹配则返回1
默认情况下,驱动名称是否匹配保存异步探测驱动程序的名称,或模块异步探测驱动有效
执行驱动探测设备及延迟处理(如果需要)
static int __driver_attach(struct device *dev, void *data)
{struct device_driver *drv = data;bool async = false;int ret;/** 锁定设备并尝试绑定到它* 我们在此处删除错误,并始终返回0* 因为我们需要继续尝试绑定到设备* 如果某些驱动程序不支持该设备,则会返回错误** 如果出现错误,driver_be_device()将发出警告*/ret = driver_match_device(drv, dev); // 检查驱动关联id列表(动态和静态)是否与设备匹配,匹配则返回1// 如果总线实现了match函数,执行match// pci总线实现函数 pci_bus_match 判断一个PCI设备结构是否具有匹配的PCI设备id结构... else if (ret == -EPROBE_DEFER) { // pci不支持此选项dev_dbg(dev, "Device match requests probe deferral\n");dev->can_match = true;driver_deferred_probe_add(dev); // 延迟匹配// 设备私有结构的deferred_probe,放入deferred_probe_pending_list链表// driver_deferred_probe_trigger 启动重新探测延迟设备函数时// 唤醒工作队列,为deferred_probe_pending_list链表中的每一个deferred_probe关联的设备执行探测函数/** 驱动程序无法与设备匹配,但可能与总线上的其他设备匹配*/return 0;}...if (driver_allows_async_probing(drv)) { // 默认情况下,驱动名称是否匹配保存异步探测驱动程序的名称,或模块异步探测驱动有效// #define ASYNC_DRV_NAMES_MAX_LEN 256// static char async_probe_drv_names[ASYNC_DRV_NAMES_MAX_LEN]; 内核命令行保存异步探测驱动程序的名称// static bool async_probe_default; // 异步探测驱动程序是否启动// module->async_probe_requested (drv->owner <-> module) 模块异步探测驱动/** 我们将异步探测设备,而不是同步探测设备,以允许更多的并行性** 我们在这里只使用设备锁,以确保dev->driver和async_driver字段受到保护*/dev_dbg(dev, "probing driver %s asynchronously\n", drv->name);device_lock(dev);if (!dev->driver && !dev->p->async_driver) {get_device(dev);dev->p->async_driver = drv; // 设备私有结构的异步驱动成员关联驱动async = true;}device_unlock(dev);if (async)async_schedule_dev(__driver_attach_async_helper, dev); // 异步执行__driver_attach_async_helper函数// 驱动探测设备及延迟处理(如果需要)return 0;}__device_driver_lock(dev, dev->parent); // 设备加锁// 设备父级存在,父级加锁,否则设备加锁driver_probe_device(drv, dev); // 驱动探测设备及延迟处理(如果需要)__device_driver_unlock(dev, dev->parent); // 设备释放锁return 0;
}
__driver_attach_async_helper
bus_for_each_dev 遍历总线上的设备
int bus_for_each_dev(struct bus_type *bus, struct device *start,void *data, int (*fn)(struct device *, void *))
{struct klist_iter i;struct device *dev;int error = 0;if (!bus || !bus->p)return -EINVAL;klist_iter_init_node(&bus->p->klist_devices, &i,(start ? &start->p->knode_bus : NULL)); // 初始化klist 设备迭代器结构while (!error && (dev = next_device(&i)))error = fn(dev, data); // 如果当前设备节点存在,执行__driver_attach函数klist_iter_exit(&i);return error;
}
EXPORT_SYMBOL_GPL(bus_for_each_dev);
__driver_attach_async_helper 驱动探测设备及延迟处理(如果需要)
static void __driver_attach_async_helper(void *_dev, async_cookie_t cookie)
{struct device *dev = _dev;struct device_driver *drv;int ret;__device_driver_lock(dev, dev->parent); // 设备加锁// 设备父级存在,父级加锁,否则设备加锁// dev->mutexdrv = dev->p->async_driver; dev->p->async_driver = NULL;ret = driver_probe_device(drv, dev); // 驱动探测设备及延迟处理(如果需要)
driver_probe_device
__device_driver_unlock(dev, dev->parent); // 设备释放锁dev_dbg(dev, "driver %s async attach completed: %d\n", drv->name, ret);put_device(dev); // 减少设备引用计数
}
driver_probe_device 驱动探测设备及延迟处理(如果需要)
驱动探测设备
如果返回EPROBE_DEFER相关状态,驱动请求探测重试
放入延迟探测列表
探测过程中发生了触发器,启动重新探测延迟的设备
唤醒probe_waitqueue等待队列
static int driver_probe_device(struct device_driver *drv, struct device *dev)
{int trigger_count = atomic_read(&deferred_trigger_count); // 读取原子计数// 原子 deferred_trigger_count 用于确定在探测驱动程序过程中是否发生了成功的触发// 如果在探测过程中触发计数发生变化,则应再次触发延迟处理int ret;atomic_inc(&probe_count); // 探测计数自增ret = __driver_probe_device(drv, dev); // 驱动探测设备
__driver_probe_device
if (ret == -EPROBE_DEFER || ret == EPROBE_DEFER) {
#define EPROBE_DEFER 517 /* 驱动请求探测重试 */driver_deferred_probe_add(dev); // 放入延迟探测列表/** 探测过程中是否发生了触发器?如果是,需要重新触发*/if (trigger_count != atomic_read(&deferred_trigger_count) &&!defer_all_probes)driver_deferred_probe_trigger(); // 启动重新探测延迟的设备}atomic_dec(&probe_count); // 探测计数自减wake_up_all(&probe_waitqueue); // 唤醒probe_waitqueue等待队列return ret;
}
__driver_probe_device 驱动探测设备
如果驱动允许匹配
恢复(唤醒)厂商设备并修改引用计数
刷新挂起的请求并等待完成
执行探测处理函数
为设备排队执行“空闲检查”
删除对厂商设备的引用
static int __driver_probe_device(struct device_driver *drv, struct device *dev)
{int ret = 0;...dev->can_match = true; // 可以匹配...pm_runtime_get_suppliers(dev); // 恢复(唤醒)厂商设备并修改引用计数// static DEFINE_MUTEX(device_links_lock); 设备链接 写相关的锁// DEFINE_STATIC_SRCU(device_links_srcu); // 设备链接 读相关的锁// SRCU 可休眠读拷贝更新(Sleepable Read-copy update) if (dev->parent)pm_runtime_get_sync(dev->parent); // 启动设备的使用计数器并恢复pm_runtime_barrier(dev); // 刷新挂起的请求并等待完成if (initcall_debug) // /sys/module/kernel/parameters/initcall_debugret = really_probe_debug(dev, drv); // 执行探测处理函数,加debug信息elseret = really_probe(dev, drv); // 探测处理函数
really_probe
pm_request_idle(dev); // 为设备排队执行“空闲检查”
// 对一个工作项进行排队,以异步方式为设备运行相当于pm_runtime_idle()的函数if (dev->parent)pm_runtime_put(dev->parent); // 丢弃设备使用计数器,如果为0则排队“空闲检查”// 减少设备的运行时PM使用计数器,如果它等于0,就像pm_request_idle()一样为设备排队一个工作项pm_runtime_put_suppliers(dev); // 删除对厂商设备的引用return ret;
}
really_probe 探测处理函数
检查是否存在厂商驱动程序
设备设置引脚相关内容
设置DMA配置(如果开启了CONFIG_OF,设备树相关)
驱动通知、链接,及创建sysfs属性文件
驱动探测
设备根kobj关联/分配到设备属性组
驱动探测已完成,更新引脚控制状态
驱动绑定设备
static int really_probe(struct device *dev, struct device_driver *drv)
{bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&!drv->suppress_bind_attrs; // falseint ret, link_ret;link_ret = device_links_check_suppliers(dev); // 检查是否存在厂商驱动程序...
re_probe:dev->driver = drv;/* 如果使用pinctrl,请在探测之前立即绑定引脚 */ret = pinctrl_bind_pins(dev); // 设备设置引脚相关
pinctrl_bind_pins
if (dev->bus->dma_configure) { // 设置DMA配置ret = dev->bus->dma_configure(dev); // 函数更新PCI设备的DMA配置// 使用相同的信息从主机桥的父节点的OF节点或ACPI节点(如果有)if (ret)goto pinctrl_bind_failed;}ret = driver_sysfs_add(dev); // 驱动通知、链接,及创建sysfs属性文件
driver_sysfs_add
if (dev->pm_domain && dev->pm_domain->activate) {ret = dev->pm_domain->activate(dev); // 设备恢复if (ret)goto probe_failed;}ret = call_driver_probe(dev, drv); // 驱动探测
// 执行pci_device_probe函数
// 通过主桥设备对象找到pin值(引脚号)
// 计算出卡槽位置(槽号),然后通过槽号找到对应的中断号
// 写入到中断线(行)并关联到pci设备(由pci驱动使用)
// 最后在连接设备的cpu上执行驱动程序初始化,执行驱动的探测函数(用户注册的驱动)ret = device_add_groups(dev, drv->dev_groups); // 设备根kobj关联/分配到设备属性组
// 获取kobj对象的sysfs所有权数据
// 创建kernfs_node节点及命名空间 (父级kn,如目录)
// 然后为属性组->属性列表中的属性分配kernfs_node节点及初始化(子级kn,如目录/文件)
// 包括关联kernfs_ops对象,将kernfs_node链接到同级rbtree
// 更新哈希值及时间戳,并激活这个节点(属性列表中的节点)if (dev_has_sync_state(dev)) { // 如果设备驱动 或 总线的同步状态函数存在ret = device_create_file(dev, &dev_attr_state_synced); // // 为设备创建sysfs属性文件
}pinctrl_init_done(dev); // 驱动探测已完成,更新引脚控制状态if (dev->pm_domain && dev->pm_domain->sync)dev->pm_domain->sync(dev);driver_bound(dev); // 驱动绑定设备pr_debug("bus: '%s': %s: bound device %s to driver %s\n",drv->bus->name, __func__, dev_name(dev), drv->name);goto done;...done:return ret;
}
driver_bound
pinctrl_bind_pins 设备设置引脚相关
如果设备节点与父级设备共享引脚,不执行以下过程
分配设备的引脚状态容器对象
创建两级引脚控制对象指针,二级指针关联到设备
查找/分配默认的引脚控制状态结构
查找/分配初始的引脚控制状态结构
选择/激活/编程一个引脚控制状态到硬件
查找/分配睡眠引脚控制状态结构
查找/分配空闲引脚控制状态结构
int pinctrl_bind_pins(struct device *dev)
{int ret;if (dev->of_node_reused) // 如果设备(树)节点与上级设备共享return 0;dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL); // 分配设备的引脚状态容器对象// 分配设备资源对象,设备资源节点的列表(链表,node->entry)放入dev->devres_head链表
dev_pin_info
devres
dev->pins->p = devm_pinctrl_get(dev); // 创建两级引脚控制对象指针,二级指针关联到设备
// 一级指针引脚控制放入pinctrl_list(引脚控制链表)
// 二级指针引脚控制作为设备资源对象的data成员 关联到设备 (node->entry 放入 dev->devres_head链表)
pinctrl
create_pinctrl
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,PINCTRL_STATE_DEFAULT); // 查找/分配默认的引脚控制状态结构
// #define PINCTRL_STATE_DEFAULT "default"
// 初始化settings链表
// node链表放入引脚控制的状态链表(p->states)dev->pins->init_state = pinctrl_lookup_state(dev->pins->p,PINCTRL_STATE_INIT); // 查找/分配初始的引脚控制状态结构
// #define PINCTRL_STATE_INIT "init"...
ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state); // 选择/激活/编程一个引脚控制状态到硬件#ifdef CONFIG_PM/** 如果启用了电源管理,我们还将查找可选的睡眠和空闲引脚状态* 语义如<linux/pinchtrl/pincttrlstate.h>中所定义*/dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p,PINCTRL_STATE_SLEEP); // 查找/分配睡眠引脚控制状态结构// #define PINCTRL_STATE_SLEEP "sleep"dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p,PINCTRL_STATE_IDLE); // 查找/分配空闲引脚控制状态结构// #define PINCTRL_STATE_IDLE "idle"
#endifreturn 0;...
}
create_pinctrl 创建引脚控制对象
static struct pinctrl *create_pinctrl(struct device *dev,struct pinctrl_dev *pctldev)
{struct pinctrl *p;const char *devname;struct pinctrl_maps *maps_node;int i;const struct pinctrl_map *map;int ret;/** 为每个映射创建状态cookie持有者结构pinctrl* 这是消费者在使用pinctrl_get()请求pin控制句柄时会得到的*/p = kzalloc(sizeof(*p), GFP_KERNEL); // 分配引脚控制对象p->dev = dev; // 关联设备INIT_LIST_HEAD(&p->states); // 初始化状态链表INIT_LIST_HEAD(&p->dt_maps); // 初始化设备树链表(部分架构不支持设备树概念,此概念相关结构的部分成员不使用)ret = pinctrl_dt_to_map(p, pctldev); // 直接返回0// x86_64默认没有启动 CONFIG_OF 选项,不使用设备树相关内容devname = dev_name(dev); // 设备名称...pinctrl_maps这部分跳过...kref_init(&p->users); // 初始化引用计数器// 专门用于引用计数的atomic-t的变体/* 将引脚控制添加到全局列表 */mutex_lock(&pinctrl_list_mutex);list_add_tail(&p->node, &pinctrl_list); // 引脚控制放入pinctrl_list(引脚控制链表)mutex_unlock(&pinctrl_list_mutex);return p;
}
driver_sysfs_add 驱动通知、链接,及创建sysfs属性文件
执行通知链函数,驱动即将被绑定
驱动私有结构 链接到 设备
设备 链接到 驱动私有结构,链接名称"driver"
为设备创建sysfs属性文件
static int driver_sysfs_add(struct device *dev)
{int ret;if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_BIND_DRIVER, dev); // 执行通知链(注册的)函数// #define BUS_NOTIFY_BIND_DRIVER 0x00000004 /* 驱动即将被绑定 */ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,kobject_name(&dev->kobj)); // 在两个对象之间创建符号链接,驱动私有结构 链接到 设备ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,"driver"); // 设备 链接到 驱动私有结构,链接名称"driver"ret = device_create_file(dev, &dev_attr_coredump); // 为设备创建sysfs属性文件if (!ret)return 0;
}
driver_bound 驱动绑定设备
设备私有结构的knode_driver 放入 设备驱动私有结构的klist_devices
设备分为延迟或同步两种状态类型,分别放入不同的列表
检查设备电源函数是否可以使用
确保设备不再在一个延迟列表中,并开始重新尝试所有挂起的设备
执行通知链(注册的)函数,驱动程序绑定到设备
用户事件通知(绑定通知)
static void driver_bound(struct device *dev)
{...klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);// 设备私有结构的knode_driver 放入 设备驱动私有结构的klist_devicesdevice_links_driver_bound(dev); // 设备分为延迟或同步两种状态类型,分别放入不同的列表
device_links_driver_bound
device_pm_check_callbacks(dev); // 检查设备电源函数是否可以使用/** 确保设备不再在一个延迟列表中,并开始重新尝试所有挂起的设备* /
driver_deferred_probe_del(dev); // 移除延迟列表
driver_deferred_probe_trigger(); // 启动重新探测延迟的设备
// *该函数将所有设备从挂起列表移动到活动列表
// 并调度延迟探测工作队列来处理它们
// 它应该在驱动程序成功绑定到设备时调用
// queue_work(system_unbound_wq, &deferred_probe_work);if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_BOUND_DRIVER, dev); // // 执行通知链(注册的)函数// #define BUS_NOTIFY_BOUND_DRIVER 0x00000005 /* 驱动程序绑定到设备 */kobject_uevent(&dev->kobj, KOBJ_BIND); // 用户事件通知// 绑定通知
}
device_links_driver_bound 设备分为延迟或同步两种状态类型,分别放入不同的列表
void device_links_driver_bound(struct device *dev)
{struct device_link *link, *ln;LIST_HEAD(sync_list); // 定义、初始化sync_list链表对象/** 如果一个设备绑定成功,那么它应该已经创建了它需要的所有设备链接* 或者根据需要创建了新的设备链接* 因此,fw_devlink不再需要创建到设备的任何厂商的设备链接** 另外,如果这个绑定设备的子固件节点到现在还没有被添加为设备* 那么假定它永远不会被添加,并确保其他设备不会通过等待这样的子设备无限期地延迟探测*/if (dev->fwnode && dev->fwnode->dev == dev) {struct fwnode_handle *child;fwnode_links_purge_suppliers(dev->fwnode); // 删除fwnode_handle的所有厂商链接fwnode_for_each_available_child_node(dev->fwnode, child)fw_devlink_purge_absent_suppliers(child);}device_remove_file(dev, &dev_attr_waiting_for_supplier); // 移除sysfs属性文件...if (link->flags & DL_FLAG_AUTOPROBE_CONSUMER)driver_deferred_probe_add(link->consumer); // 放入延迟探测列表// deferred_probe -> deferred_probe_pending_listif (defer_sync_state_count)__device_links_supplier_defer_sync(dev); // 设备放入延迟探测列表(deferred_sync)else__device_links_queue_sync_state(dev, &sync_list); // 设备放入同步状态列表...device_link_drop_managed(link); // 移除device_link相关的链表节点等...dev->links.status = DL_DEV_DRIVER_BOUND;// DL_DEV_DRIVER_BOUND: 驱动已绑定到设备...device_links_flush_sync_list(&sync_list, dev); // 对设备列表调用sync_state函数// 在已排队等待的所有设备上调用
}
module_add_driver 驱动增加到模块
void module_add_driver(struct module *mod, struct device_driver *drv)
{char *driver_name;int no_warn;struct module_kobject *mk = NULL;if (mod) // 指定了THIS_MODULE,并且编译选项为m或手动编译驱动,在编译时会生成__this_module结构对象mk = &mod->mkobj;else if (drv->mod_name) { // 编译选项为y时struct kobject *mkobj;/* 查找中的内置模块条目 /sys/modules */mkobj = kset_find_obj(module_kset, drv->mod_name);if (mkobj) {mk = container_of(mkobj, struct module_kobject, kobj);/* 记住我们的模块结构 */drv->p->mkobj = mk;/* kset_find_obj took a reference */kobject_put(mkobj);}}/* 不要检查返回代码;这些调用是幂等的 */no_warn = sysfs_create_link(&drv->p->kobj, &mk->kobj, "module");// drv->p->kobj 链接到 mk->kobj "module"driver_name = make_driver_name(drv);if (driver_name) {module_create_drivers_dir(mk);no_warn = sysfs_create_link(mk->drivers_dir, &drv->p->kobj,driver_name);// mk->drivers_dir 链接到 drv->p->kobj 驱动名称kfree(driver_name);}
}
__this_module
__this_module 编译选项为m或手动编译驱动,在编译时会生成__this_module结构对象
__visible struct module __this_module
__section(".gnu.linkonce.this_module") = {.name = KBUILD_MODNAME,.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD.exit = cleanup_module,
#endif.arch = MODULE_ARCH_INIT,
};
目录预览
<<linux设备模型:sysfs(kobject)解析>>
<<linux设备模型:kset及设备驱动抽象类(class)分析>>
<<linux设备模型:bus概念及pci_bus分析>>
<<linux设备模型:devtmpfs虚拟文件系统分析>>
<<linux设备模型:设备及设备节点创建过程分析>>
<<linux设备模型:固件设备及efi固件(平台)设备节点创建过程分析>>
linux设备模型:pci驱动程序注册过程相关推荐
- Linux设备驱动程序学习-Linux设备模型(总线、设备、驱动程序和类)
文章的例子和实验使用<LDD3>所配的lddbus模块(稍作修改). 总线 总线是处理器和一个或多个设备之间的通道,在设备模型中, 所有的设备都通过总线相连, 甚至是内部的虚拟" ...
- linux lddbus设备,Linux设备驱动程序学习(14)-Linux设备模型(各环节的整合)
Linux设备驱动程序学习(14) -Linux设备模型(各环节的整合) 通过一个设备在内核中生命周期的各个阶段,可以更好地理解Linux设备模型.我将通过分析lddbus和sculld的源码来了解L ...
- linux设备驱动程序架构的研究,Linux设备驱动程序学习(12)-Linux设备模型(底层原理简介)...
Linux设备驱动程序学习(12) -Linux设备模型(底层原理简介) 以<LDD3>的说法:Linux设备模型这部分内容可以认为是高级教材,对于多数程序作者来说是不必要的.但是我个人认 ...
- 《linux设备驱动程序》——Linux设备模型
一.概论 1.2.6版内核对系统结构的一般性抽象描述.现在内核使用了该抽象支持了多种不同的任务,其中包括: 1).电源管理和系统关机. 2).与用户控件通信. 3).热插拔设备. 4).设备类型. 5 ...
- 学习笔记——《LINUX设备驱动程序(第三版)》Linux设备模型:内核添加、删除设备、驱动程序
文章目录 1. 前言 2. 准备工作 2.1. 概念 2.2. 具体总线.设备.驱动结构体说明 2.3. 注册总线 3. 添加设备 3.1. STEP1 --发现设备并创建设备结构 struct XX ...
- linux 统一设备模型 pci,linux设备模型____宏观印象
linux设备模型____宏观印象 最近一个机会需要研究一个marvell芯片的设备的驱动,涉及驱动和一些用户态相关部分,正好学习一下驱动和sysfs,本文先是原理,后面的文章是详细描述.本文依托的是 ...
- Linux设备模型——设备驱动模型和sysfs文件系统解读笔记
Linux设备模型--设备驱动模型和sysfs文件系统解读笔记 原文:https://blog.csdn.net/yj4231/article/details/7799245 将对Linux系统中的s ...
- Linux设备模型(总结)
转:http://www.360doc.com/content/11/1219/16/1299815_173418267.shtml 看了一段时间的驱动编程,从LDD3的hello wrod到后来的字 ...
- Linux 文件系统与设备文件系统 (二)—— sysfs 文件系统与Linux设备模型
提到 sysfs 文件系统 ,必须先需要了解的是Linux设备模型,什么是Linux设备模型呢? 一.Linux 设备模型 1.设备模型概述 从2.6版本开始,Linux开发团队便为内核建立起一个统一 ...
最新文章
- Spring Cloud 第十一篇:docker部署spring cloud项目
- python爬虫数据提取,Python 信息提取-爬虫,爬虫提取数据, import re
- 教你如何 构建基本的用户控件
- Hash散列算法解析
- 计算机网络多媒体图像矢量图,13多媒体信息处理——图像处理(一)
- 设置下载安装 桌面_小妖精美化app最新版下载-小妖精美化V5.3.9.800下载安装
- 《研磨设计模式》chap18 状态模式state(2)模式介绍
- DNS抓包分析--wireshark
- scrapy设置代理的方法
- Linux JDK升级
- 十个模块_专栏 | ABAQUS Part模块的十个小技巧
- 前端架构设计1:代码核心
- thinkphp连接oracle
- vue中注意watch的执行顺序
- HTML5响应式手机模板:h5手机抽奖游戏活动页面集合模板 HTML+CSS+JavaScript
- getch方法_C++中getch函数使用时注意事项
- 如何用甘特图进行项目进度管理
- 7-79 约分最简分式
- linux Fedora安装桌面,在Fedora Linux上安装Elementary OS桌面的方法
- iOS App thinning【( 通过 LinkMap、mach-o寻找优化点)】1、段迁移rename_section减小__TEXT 段大小(需关闭 Bitcode)2、查无用方法/类/宏/图
热门文章
- Python C# 通信2种路子
- 医疗行业数据泄露惊人,提高医疗信息安全刻不容缓
- 第23讲:多表查询之笛卡尔积的概念
- 成都顾连康复中心专业吗
- luch-request
- [转帖]一个关于国密SM4的故事
- Android真机测试使用百度地图定位到非洲附近的的问题
- matlab实时动态绘图clc,Matlab绘制动态 .gif 图
- mysql neq什么意思_ISO/EDIS 15876-1-2002 NEQ这个标准中EDIS和NEQ是什么意思?
- 输入您的密码来连接到_【对讲机的那点事】如何为您的数字对讲机热点配置安全密码...