1.【PCIe】lspci用法小结
2.linux设备驱动程序——i2c设备驱动源码实现
3.驱动I2C驱动分析(四)-关键API解析
4.linux设备驱动程序——i2c总线的总线总线添加与实现
【PCIe】lspci用法小结
ls pci工具用于展示系统中的所有PCI总线设备或连接到该总线上的所有设备。要使用ls pci进行源码编译,源码原理需要先下载代码:
1:进入tools文件夹并创建lspci文件夹,总线总线随后切换至lspci文件夹。源码原理
cd tools/; mkdir lspci; cd lspci;
通过git命令从指定仓库克隆代码,总线总线例如:
git clone git:// git.kernel.org/pub/scm/...
完成代码下载后,源码原理交易所源码vue进入pciutils文件夹并编译:
2:cd pciutils/;make
编译完成后的总线总线lspci工具可以进行版本检测,例如:
3: $ ./lspci --version
输出版本信息,源码原理如3..0。总线总线
更多详细信息可查阅相关教程,源码原理如马昌伟博客园的总线总线文章。
linux设备驱动程序——i2c设备驱动源码实现
深入了解Linux内核中的源码原理i2c设备驱动程序详解 在Linux内核中,i2c设备驱动程序的总线总线实现是一个关键部分。本文将逐步剖析其形成、源码原理匹配及源码实现,总线总线以帮助理解i2c总线的工作原理。 首先,熟悉I2C的基本知识是必不可少的。作为主从结构,设备通过从机地址寻址,其工作流程涉及主器件对从机的通信。了解了基础后,我们接着来看Linux内核中的驱动程序框架。 Linux的i2c设备驱动程序框架由driver和device两部分构成。当driver和device加载到内存时,会自动调用match函数进行匹配,成功后执行probe()函数。driver中,probe()负责创建设备节点并实现特定功能;device则设置设备的I2C地址和选择适配器,如硬件I2C控制器。 示例代码中,源码编辑器如何做时间i2c_bus_driver.c展示了driver部分的实现,而i2c_bus_device.ko和i2c_bus_device.ko的编译加载则验证了这一过程。加载device后,probe函数会被调用,确认设备注册成功。用户程序可测试驱动,通过读写传感器寄存器进行操作。 在设备创建方面,i2c_new_device接口允许在设备存在时加载驱动,但有时需要检测设备插入状态。这时,i2c_new_probed_device提供了检测功能,确保只有实际存在的设备才会被加载,有效管理资源。 深入源码分析,i2c_new_probed_device主要通过检测来实现设备存在性,最终调用i2c_new_device,但地址分配机制确保了board info中的地址与实际设备地址相符。 至此,关于Linux内核i2c驱动的讨论结束。希望这个深入解析对您理解i2c设备驱动有帮助。如果你对此话题有兴趣,可以加入作者牧野星辰的Linux内核技术交流群,获取更多学习资源。 学习资源Linux内核技术交流群:获取内核学习资料包,包括视频教程、电子书和实战项目代码
内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料
学习直达:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
驱动I2C驱动分析(四)-关键API解析
在Linux内核源代码中的driver目录下包含一个i2c目录
i2c-core.c这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。i2c-dev.c实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访设备时的核导弹起爆点公式源码主设备号都为,次设备号为0-。I2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
busses文件夹这个文件中包含了一些I2C总线的驱动,如针对S3C,S3C,S3C等处理器的I2C控制器驱动为i2c-s3c.c. algos文件夹实现了一些I2C总线适配器的algorithm.
I2C Core
i2c_new_device用于创建一个新的I2C设备,这个函数将会使用info提供的信息建立一个i2c_client并与第一个参数指向的i2c_adapter绑定。返回的参数是一个i2c_client指针。驱动中可以直接使用i2c_client指针和设备通信了。
i2c_device_match 函数根据设备和设备驱动程序之间的不同匹配方式,检查它们之间是否存在匹配关系。这个函数通常在 I2C 子系统的设备驱动程序注册过程中使用,以确定哪个驱动程序适用于给定的设备。
i2c_device_probe 函数执行了 I2C 设备的探测操作。它设置中断信息、处理唤醒功能、设置时钟、关联功耗域,并调用驱动程序的 probe 函数进行设备特定的探测操作。
i2c_device_remove 函数执行了 I2C 设备的移除操作。它调用驱动程序的 remove 函数,并进行功耗域的分离、唤醒中断的清除以及设备唤醒状态的设置。
i2c_register_adapter 函数用于注册一个 I2C 适配器。它进行了一系列的完整性检查和初始化操作,并注册适配器设备。然后,股海踏雪幅图公式源码正版注册与适配器相关的设备节点、ACPI 设备和空间处理器。最后,遍历所有的 I2C 驱动程序,并通知它们有新的适配器注册了。
i2c_add_adapter 函数用于添加一个新的 I2C 适配器。它先尝试从设备树节点中获取适配器的编号,如果成功则使用指定的编号添加适配器。如果没有相关的设备树节点或获取编号失败,函数会在动态范围内分配一个适配器 ID,并将适配器与该 ID 相关联。然后,函数调用 i2c_register_adapter 函数注册适配器,并返回注册函数的返回值。
i2c_detect_address 函数用于检测指定地址上是否存在 I2C 设备,并执行自定义的设备检测函数。它会进行一系列的检查,包括地址的有效性、地址是否已被占用以及地址上是否存在设备。如果检测成功,函数会调用自定义的检测函数并根据检测结果进行相应的处理,包括创建新的设备实例并添加到驱动程序的客户端列表中。
i2c_detect 函数根据给定的适配器和驱动程序,通过遍历地址列表并调用i2c_detect_address函数,检测I2C适配器上连接的设备是否存在。
这段代码是一个用于检测I2C适配器上连接的设备的函数。下面是对代码的详细解释:
I2C device
i2c_dev_init执行了一系列操作,包括注册字符设备、创建设备类、注册总线通知器以及绑定已经存在的最小十进制的源码是多少适配器。它在初始化过程中处理了可能发生的错误,并返回相应的错误码。
i2cdev_attach_adapter作用是将I2C适配器注册到Linux内核中,以便在系统中使用I2C总线。它会获取一个空闲的struct i2c_dev结构体,然后使用device_create函数创建一个I2C设备,并将其与驱动核心相关联。
i2cdev_open通过次设备号获取对应的i2c_dev结构体和适配器,然后分配并初始化一个i2c_client结构体,最后将其赋值给文件的私有数据。
i2cdev_write函数将用户空间的数据复制到内核空间,并使用i2c_master_send函数将数据发送到之前打开的I2C设备中。
i2cdev_read函数在内核中分配一个缓冲区,使用i2c_master_recv函数从I2C设备中接收数据,并将接收到的数据复制到用户空间。
i2cdev_ioctl
i2c_driver
i2c_register_driver将驱动程序注册到I2C驱动核心,并在注册完成后处理所有已经存在的适配器。注册完成后,驱动核心会调用probe()函数来匹配并初始化所有匹配的但未绑定的设备。
I2C 传输
i2c_transfer用于执行I2C传输操作。它首先检查是否支持主控制器,如果支持,则打印调试信息,尝试对适配器进行锁定,然后调用__i2c_transfer函数执行传输操作,并在完成后解锁适配器并返回传输的结果。如果不支持主控制器,则返回不支持的错误码。
i2c_master_send通过I2C主控制器向从设备发送数据。它构建一个i2c_msg结构,设置消息的地址、标志、长度和缓冲区,并将其传递给i2c_transfer函数执行实际的传输操作。函数的返回值是发送的字节数或错误码,用于指示传输是否成功。
i2c_master_recv通过I2C主控制器从从设备接收数据。它构建一个i2c_msg结构,设置消息的地址、标志、长度和缓冲区,并将其传递给i2c_transfer函数执行实际的传输操作。函数的返回值是接收的字节数或错误码,用于指示传输是否成功。
linux设备驱动程序——i2c总线的添加与实现
一文看懂linux内核详解
深入了解使用linux查看磁盘io使用情况
在实际驱动开发过程中,i2c总线也是集成在系统中的,驱动开发者不需要关心i2c总线的初始化,只需要将相应的i2c_client和i2c_driver挂载在总线上进行匹配即可。
那么,i2c总线在系统中是如何初始化得来的呢?
答案就在文件i2c-core-base.c中,它的过程是这样的:
在i2c_init函数中,使用bus_register()将i2c总线注册到系统中,那么这个i2c_init()函数是在哪里被调用的呢?
在内核启动的过程中,进入C语言环境的第一个入口函数是start_kernel(),但是i2c_init()并非由start_kernel()间接调用,而是借助于linux内核中的initcall机制被调用,简而言之,就是将这个函数地址在编译阶段放入特定的段中,在内核初始化时由某个启动函数将这些段中的函数一一取出并执行。
i2c总线通过postcore_initcall()将init函数注册到系统中。
当模块被加载进系统时,就会执行i2c_init函数来进行初始化。
在i2c_bus_type结构体中,定义了match(),probe(),remove()和shutdown()函数,match()函数就是当有新的i2c_client或者i2c_driver添加进来时,试图寻找与新设备匹配的项,返回匹配的结果。
remove()和shutdown(),顾名思义,这两个函数是管理的驱动移除行为的。
对于probe函数,在之前的章节中提到:当相应的device和driver经由总线匹配上时,会调用driver部分提供的probe函数。
那么:
带着这两个疑问,我们接着往下看。
在这里我们不免要回顾一下前面章节所说的,作为一个驱动开发者,如果我们要开发某些基于i2c的设备驱动,需要实现的框架流程是怎样的:
但是问题是,为什么device和driver都注册进去之后,就会调用driver部分提供的probe函数呢?
为了解决这些问题,最好的办法就是看源代码,假设我们是以设备树的方式进行匹配,device(即i2c_client)部分已经被注册到系统中,此时我们向系统中注册(insmod由driver部分程序编译的模块)相应的driver部分,接下来我们跟踪driver部分注册到i2c总线的流程,看看它是怎么实现的:
跟踪i2c_add_driver:
经过一系列的代码跟踪,找到了bus_add_driver(),根据名称可以知道,这个函数就是将当前i2c_driver添加到i2c_bus_type(即i2c总线)中。
接着往下看:
从第一部分可以看到,将当前drv链入到bus->p->klist_drivers链表中,那么可以猜到,在注册device部分的时候,就会将device链入到bus->p->klist_devices中。
然后,再看driver_attach(drv):
driver_attach调用bus_for_each_dev,传入当前驱动bus(即i2c_bus_type),当前驱动drv,以及一个函数__driver_attach。
bus_for_each_dev对每个device部分调用__driver_attach。
在__driver_attach中,对每个drv和dev调用driver_match_device(),并根据函数返回值决定是否继续执行调用driver_probe_device()。
从函数名来看,这两个函数大概就是我们在前文中提到的match和probe函数了,我们不妨再跟踪看看,先看看driver_match_device()函数:
果然不出所料,这个函数十分简单,如果当前驱动的所属的bus有相应的match函数,就调用match函数,否则返回1.
当前driver所属的总线为i2c_bus_type,根据上文i2c总线的初始化部分可以知道,i2c总线在初始化时提供了相应的match函数,所以,总线的match函数被调用,并返回匹配的结果。
接下来我们再看看driver_probe_device()函数:
在driver_probe_device中又调用了really_probe,在really_probe()中,先判断当前bus总线中是否注册probe()函数如果有注册,就调用总线probe函数,否则,就调用当前drv注册的probe()函数。
到这里,我们解决了上一节中的一个疑问:总线和driver部分都有probe函数时,程序是怎么处理的?
答案已经显而易见了:优先调用总线probe函数。
而且我们理清了总线的match()函数和probe()函数是如何被调用的。
那么,上一节中还有一个疑问没有解决:总线的match和probe函数执行了一些什么行为?
对于总线probe函数,获取匹配成功的device,再由device获取driver,优先调用driver->probe_new,因为driver中没有设置,直接调用driver的probe函数。
总线probe和driver的probe函数的关系就是:当match返回成功时,优先调用总线probe,总线probe完成一系列的初始化,再调用driver的probe函数,如果总线probe函数不存在,就直接调用driver的probe函数,所以当我们在系统中添加了相应的device和driver部分之后,driver的probe函数就会被调用。
对于总线match函数,我们直接查看在i2c总线初始化时的函数定义:
i2c_device_match就是i2c_driver与i2c_device匹配的部分,在i2c_device_match函数中,可以看到,match函数并不只是提供一种匹配方式:
接下来再深入函数内部,查看匹配的细节部分:
在i2c_of_match_device中,调用了of_match_device()和i2c_of_match_device_sysfs()两个函数,这两个函数代表了两种匹配方式,先来看看of_match_device:
of_match_device调用of_match_node。
of_match_node调用__of_match_node函数。
如果你对设备树有一定的了解,就知道系统在初始化时会将所有设备树子节点转换成由struct device_node描述的节点。
在被调用的__of_match_node函数中,对device_node中的compatible属性和driver部分的of_match_table中的compatible属性进行匹配,由于compatible属性可以设置多个,所以程序中会对其进行逐一匹配。
我们再回头来看设备树匹配方式中的i2c_of_match_device_sysfs()匹配方式:
由i2c_of_match_device_sysfs()的实现可以看出:当设备树中不存在设备节点时,driver部分的of_match_table中的compatible属性试图去匹配i2c_client(device部分)的.driver.name属性.
因为设备树的默认规则,compatible属性一般形式为"vender_id,product_id",当compatible全字符串匹配不成功时,取product_id部分再进行匹配,这一部分主要是兼容之前的不支持设备树的版本。
acpi匹配方式较为少用且较为复杂,这里暂且不做详细讨论
id_table匹配方式中,这种匹配方式一目了然,就是对id_table(driver部分)中的.name属性和i2c_client(device部分)的.name属性进行匹配。那么i2c_client的.name是怎么来的呢?
在非设备树的匹配方式中,i2c_client的.name属性由驱动开发者指定,而在设备树中,i2c_client由系统对设备树进行解析而来,i2c_client的name属性为设备树compatible属性"vender_id,product_id"中的"product_id",所以,在进行匹配时,默认情况下并不会严格地要求 of_match_table中的compatible属性和设备树中compatible属性完全匹配,driver中.drv.name属性和.id.name属性也可以与设备树中的"product_id"进行匹配。
关于linux i2c总线的初始化以及运行机制的讨论就到此为止啦