随着技术不断进步,系统的拓扑结构越来越复杂,对热插拔、跨平台移植性的要求越来越高,早期的内核难以满足这些要求,从linux2.6内核开始,引入了总线设备驱动模型。其实在linux2.4总线的概念就已经提出来了,直到2.6版本的内核才运用。

Linux系统中有很多条总线,如I2C、USB、platform、PCI等。

以spi为例,假如有M种不同类型CPU,N中不同SPI外设,在写裸机驱动的时候,M种CPU驱动同一个外设需要M份代码,而N种外设使用同一个cpu又需要N份代码,所以需要M*N份代码,这是典型的高内聚低耦合架构。

这种网状的拓扑结构是不符合人的逻辑思维的,将M*N种耦合变成M+1+N中耦合,将大大减少linux移植工作。

在系统中抽象出一条SPI总线,然后总线中(总线注册的那个文件 spi.c 和spi.h,I2C总线注册是i2c-core.c和i2c-core.h)包含SPI控制器抽象结构体spi_master等,spi控制器和外设之间交互采用spi总线提供的标准api来进行,控制器设备和外设驱动填充相关结构体。

试想一下usb,当我们把鼠标或者键盘插入电脑时,是不是会有个驱动加载的过程?这就是在寻找总线上的驱动。总线有一种义务,就是感知设备在总线上的挂载和卸载,同时有义务去寻找与设备匹配的驱动。我们的spi也一样,当有外设挂载到spi总线上的时候,就会寻找总线上所有的驱动与之匹配,匹配成功,则由该驱动服务这个设备。反过来,总线有义务感知驱动在总线上的挂载和卸载,当驱动挂载到总线时,会寻找与之匹配的设备,该驱动就服务于匹配的设备。

总线在内核中的抽象

在linux内核中,总线由bus_type结构描述,定义在linux/device.h中。

[cpp] view plain copy
  1. struct bus_type {
  2. const char *name; /*总线名称*/
  3. int (*match) (struct device *dev, struct
  4. device_driver *drv); /*驱动与设备的匹配函数*/
  5. ………
  6. }

主要关注match函数,当有一个设备挂载到一条总线上的时候,总线要把这个设备和挂载到这条总线上的驱动一一进行匹配,匹配的函数就是这个match指针。

总线的注册与注销

注册:bus_register(struct bus_type *bus)若成功,新的总线将被添加进系统,并可在/sys/bus 下看到相应的目录。

注销:void bus_unregister(struct bus_type *bus)。

进入到板子的/sys/bus目录,ls一下,可以看到系统所有的总线。

随便进入一个目录,如SPI目录

Devices目录表示这条总线上所有挂载的设备。Drivers目录表示这条总线上所有的设备。

下面以一个示例来注册一条总线到系统中,一般情况下,是不需要另外添加总线到设备中的。添加的总线名字叫my_bus,加载驱动之后,会在/sys/bus目录下看到一个my_bus目录。

新建bus.c:

[cpp] view plain copy
  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/kernel.h>
  4. #include <linux/device.h>
  5. int my_match(struct device *dev, struct device_driver *drv)
  6. {
  7. printk("my_match was run\n");
  8. return !strncmp(dev->kobj.name,drv->name,strlen(drv->name));
  9. }
  10. struct bus_type my_bus_type = {
  11. .name = "my_bus",//总线名称
  12. .match = my_match,//驱动与设备匹配函数
  13. };
  14. EXPORT_SYMBOL(my_bus_type);
  15. static int my_bus_init()
  16. {
  17. int ret;
  18. ret = bus_register(&my_bus_type);
  19. return ret;
  20. }
  21. static void my_bus_exit()
  22. {
  23. bus_unregister(&my_bus_type);
  24. }
  25. module_init(my_bus_init);
  26. module_exit(my_bus_exit);
  27. MODULE_LICENSE("GPL");

首先,总线也是内核的一个模块,我们把它编译成.ko的方式加载到内核,总线的名字是”my_bus”,总线的匹配函数是my_match,当总线上的驱动和设备都挂载上去时,会调用my_match函数进行配对,配对也很简单,就是对比驱动和设备名字是否相同。返回非0表示my_match匹配成功,返回0表示匹配失败。

EXPORT_SYMBOL(my_bus_type);将my_bus_type结构导出给外部文件用,因为设备和驱动都需要指明要挂载到哪条总线上。

编写Makefile,使之生成.ko模块,加载bus.ko,然后在/sys/bus目录下会生成my_bus目录

驱动描述结构

在 Linux内核中, 驱动由 device_driver结构表示。

[cpp] view plain copy
  1. struct device_driver {
  2. {
  3. const char *name; /*驱动名称*/
  4. struct bus_type *bus; /*驱动程序所在的总线*/
  5. int (*probe) (struct device *dev);
  6. ………
  7. }

Name表示驱动的名字;bus表示驱动要挂载到哪条总线上,待会儿将挂载到刚刚创建的my_bus总线上;probe表示驱动和设备匹配成功之后要运行的函数。

驱动的注册与注销:

驱动的注册使用:int driver_register(struct device_driver *drv)

驱动的注销使用:void driver_unregister(struct device_driver *drv)

接下来编写driver.c文件,编译成模块,将驱动加载到内核并挂载到my_bus总线上。

[cpp] view plain copy
  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/kernel.h>
  4. #include <linux/device.h>
  5. extern struct bus_type my_bus_type;
  6. int my_probe(struct device *dev)
  7. {
  8. printk("driver found the devicre it can handle\n");
  9. return 0;
  10. }
  11. struct device_driver my_driver =
  12. {
  13. .name = "yty",//驱动名字
  14. .bus = &my_bus_type,//属于哪条总线
  15. .probe = my_probe,
  16. };
  17. static int my_driver_init()
  18. {
  19. return driver_register(&my_driver);
  20. }
  21. static int my_driver_exit()
  22. {
  23. driver_unregister(&my_driver);
  24. }
  25. module_init(my_driver_init);
  26. module_exit(my_driver_exit);
  27. MODULE_LICENSE("GPL");

驱动的名字叫“yty”,属于bus.c中的my_bus_type这条总线,驱动和设备匹配成功之后,就会运行my_probe函数,也就是会打印出"driver found the devicre it can handle\n"信息。

编译成.ko文件,然后insmod,在/sys/bus/my_bus/drivers目录下就生成了yty目录。

设备描述结构

在 Linux内核中, 设备由struct device结构表示。

[cpp] view plain copy
  1. struct device {
  2. {
  3. const char *init_name; /*设备的名字*/
  4. struct bus_type *bus; /*设备所在的总线*/
  5. ………
  6. }

设备的注册与注销

设备的注册使用int device_register(struct device *dev)

设备的注销使用:void device_unregister(struct device *dev)

编写device.c文件:

[cpp] view plain copy
  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/kernel.h>
  4. #include <linux/device.h>
  5. extern struct bus_type my_bus_type;
  6. struct device my_dev =
  7. {
  8. .init_name = "yty",
  9. .bus = &my_bus_type
  10. };
  11. static int my_device_init()
  12. {
  13. int ret;
  14. ret = device_register(&my_dev);
  15. return ret;
  16. }
  17. static void my_device_exit()
  18. {
  19. device_unregister(&my_dev);
  20. }
  21. module_init(my_device_init);
  22. module_exit(my_device_exit);
  23. MODULE_LICENSE("GPL");

.init_name要和驱动的.那么一样,要不然匹配不上,.bus仍然是要属于my_bus总线。

编译并加载.ko文件,然后会出现如下打印:

由图可知,当挂载设备到my_bus总线上时,先调用总线上的my_match函数,然后驱动来处理这个设备,驱动中的my_probe就运行了。

总线的感性认识就到此结束了。

SPI总线设备驱动分析

在sourceInsight中打开内核代码drivers/spi/spi.c文件,然后分析。

在spi_init函数中,调用了bus_register注册一条总线,总线的名字叫做spi,spi_bus_type结构就是我们需要关注的,顺便看看.match。


看内核代码挑重要的看,不要每一行都看,直接跳到strcmp函数去,可以知道总线上驱动和设备的配备是通过比较驱动和设备的名字。如果有多个相同的设备,那么就应该定义.id了,靠id来区别我这个驱动到底是服务哪个设备。

总线的注册就讲解完毕。在spi.c中,提供了注册设备和注册驱动的标准api、提供了spi收发函数、spi初始化函数等。可以理解为spi总线向我们提供了标准的API接口。

以系统提供的范例spidev.c为例:

我们知道,在注册一个spi驱动是调用系统给我们提供的函数-spi_register_driver,这个标准的api也是由spi.c提供给我们的。通过sourceInsight跳转到spi_register_driver函数,这个函数就在spi.c中。

由前面的范例代码知道,注册一个驱动使用driver_register。

sdrv->driver.bus = &spi_bus_type;表示这个驱动属于spi这条总线。另外spidev中的probe,remove都通过指针传到了spi_register_driver函数中。设备和驱动匹配成功,调用spi_drv_probe,它经过赋值之后,是指向spidev.c中的spidev_probe。在spi通用外设驱动spidev.c中,调用spi_async来实现发送和接收数据的,spi_async也是由spi.c提供的,即”总线提供标准API”。

Spi设备挂载分析:

添加外设之后,一般都是需要修改板级逻辑的,使用spi通用驱动也不例外。在borad-sam9x5ek.c中要添加。其它cpu类似。


在ek_board_init中调用了at91_add_device_spi函数,将设备注册到系统。

用sourceInsight继续追踪该函数。at91_add_device_spi调用spi_register_board_info 调用spi_register_board_info。spi_register_board_info这个函数就是在spi.c中,也就是说,总线提供标准的API注册设备到总线上。这个API其实最终还是调用device_register将设备注册到总线上。

接下来看看spi_async是如何访问到spi相关寄存器的。追踪spi_async,spi_async调用__spi_async,然后调用return master->transfer(spi, message);也就是调用master的transfer指针函数,这个函数在哪里被赋值了呢?

找到atmel_spi.c文件。S3c6410板子是spi_s3c64xx.c。然后找到probe函数,atmel是atmel_spi_probe。就会看到如下代码:

spi_alloc_master也是spi总线提供的标准API,用于申请一个spi_master结构,然后对这个结构初始化,所以spi_async将调用atmel_spi_transfer,然后我们进一步追踪代码,atmel_spi_transfer调用atmel_spi_next_message调用atmel_spi_next_xfer 调用atmel_spi_next_xfer_pio,atmel_spi_next_xfer_pio函数就是真正读写寄存器的操作了。访问寄存器不能直接写哦,需要iomap哦,而且要采用专门的读写函数,如readl、readb、writel、writeb、spi_wrtel等。


假如有多个控制器,那么外设怎么和某个控制器建立关系呢?这个任务是由板级逻辑来联系的。就以刚刚的spidev板级代码来说

max_speed_hz是说明我这个spidev外设,需要使控制器100万Hz的时钟频率,bus_num说明说明spidev外设需要使用spi0控制器。

总结:

SPI,I2C,USB等采用总线的方式,将主机驱动和外设驱动分离,这样就涉及到四个软件模块:

1.主机端的驱动。根据具体的cpu芯片手册操作IIC、SPI、USB等寄存器,产生各种波形。主机端驱动大部分由原厂实现好。

2.连接主机和外设的纽带。外设驱动不直接调用主机端的驱动来产生波形,而是调用一个标准的API,由这个标准的API把这个波形的传输请求间接转发给了具体的主机端驱动。

3.外设端驱动。外设挂载到IIC、SPI、USB等总线上,我们在probe()函数中去注册它的具体类型(I2C,SPI,USB等类型),当要去访问外设的时候,就调用标准的API。如SPI读写函数spi_async,I2C读写函数:i2c_smbus_read_byte  i2c_smbus_write_byte 等。

4.板级逻辑。板级逻辑用来描述主机和外设如何联系在一起的,假如cpu有多个SPI控制器,cpu又接有多个SPI外设,那究竟用哪个SPI控制器去控制外设?这个管理属于板级逻辑的责任。如board-sam9x5ek.c中:.bus_num= 0,表示用SPI0去控制spi通用外设驱动spidev。

Linux SPI总线设备驱动模型详解相关推荐

  1. linux一个spi总线挂多个设备,Linux SPI总线设备驱动模型详解

    随着技术不断进步,系统的拓扑结构越来越复杂,对热插拔.跨平台移植性的要求越来越高,早期的内核难以满足这些要求,从linux2.6内核开始,引入了总线设备驱动模型.其实在linux2.4总线的概念就已经 ...

  2. linux分区模型,linux设备驱动模型详解.pdf

    Linux 模型之 介 设备 详细 绍 以<LDD3>的 法:Linux 模型 部分内容可以 是高 教材, 于多数程序作者来 说 设备 这 认为 级 对 是不必要的.但是我个人 : 于一个 ...

  3. 驱动开发中platform设备驱动架构详解

    1.什么是platform总线 从Linux2.6开始Linux加入了一套驱动管理和注册机制-platform总线驱动模型.platform总线是一条虚拟总线(只有一条),这类总线没有对应的硬件结构. ...

  4. 《Linux设备驱动开发详解 A》一一2.3 接口与总线

    本节书摘来华章计算机出版社<Linux设备驱动开发详解 A>一书中的第2章,第2.3节,作者:宋宝华 更多章节内容可以访问云栖社区"华章计算机"公众号查看.1 2.3 ...

  5. 《Linux设备驱动开发详解(第2版)》隆重出版

    Linux设备驱动开发详解(第2版)(前一版狂销3万册,畅销书最新升级) [新品] 点击看大图     基本信息 * 作者: 宋宝华       * 出版社:人民邮电出版社     * ISBN:97 ...

  6. 《linux设备驱动开发详解》笔记——15 linux i2c驱动

    <linux设备驱动开发详解>笔记--15 linux i2c驱动 15.1 总体结构 如下图,i2c驱动分为如下几个重要模块 核心层core,完成i2c总线.设备.驱动模型,对用户提供s ...

  7. linux设备驱动总结,《Linux设备驱动开发详解(第3版)》海量更新总结

    本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 2015.2.26 几乎完成初稿. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux ...

  8. 《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)进展同步更新

    本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 目前已经完成稿件. 2015年8月9日,china-pub开始上线预售: http://product.china-pub ...

  9. linux中流设备_[快速上手Linux设备驱动]之块设备驱动流程详解一

    [快速上手Linux设备驱动]之块设备驱动流程详解一 walfred已经在[快速上手Linux设备驱动]之我看字符设备驱动一 文中详细讲解了linux下字符设备驱动,并紧接着用四篇文章描述了Linux ...

最新文章

  1. pyinstaller打包教程及错误RuntimeError: Unable to open ./shape_predictor_68_face_landmarks.dat
  2. Java基础小常识-继承-(10)
  3. 机器学习笔记:感知器
  4. 前端学习(2880):数据代理和劫持
  5. asp.net oracle 分页,asp.net教程之利用ASP实现Oracle数据记录的分页显示
  6. 用qt的qml写的安卓摄像头程序
  7. js ajax异步提交,jquery ajax异步提交表单数据的方法
  8. Cplex20.1版本bin包Linux安装过程
  9. 科学计算机怎么算定积分,定积分计算方法的归纳和分析
  10. 健身房菜鸟入门小常识
  11. 【小米】风口的猪-中国牛市
  12. #Unity _ 简体转繁体
  13. SHAP 可视化解释机器学习模型简介
  14. 友情检测北京某大学网站
  15. ASP.NET Core: 全新的ASP.NET !
  16. 【VHDL语言学习笔记(二)】 4位向量加法器
  17. 淘宝滑块的解决方法和api接口
  18. 转:詹姆斯·马奇:领导者容易把自己看成是组织中的英雄
  19. OPPO Watch 2系列正式发布 双擎混动技术兼顾智能与续航
  20. 还记得曾经风起云涌的雨林木风了吗?

热门文章

  1. Go包导入与Java的差别
  2. 电商系统设计之商品(下)
  3. 10道关于Java泛型的面试题
  4. 模拟三:STEMA 考试选择题模拟练习试卷(初级组)及答案 + 自我解题笔记
  5. mysql数据库array_mysql数据库array
  6. 显示屏连接控制卡超时_小间距led显示屏的安装步骤
  7. php 发送微信请求失败的原因,微信小程序模拟正常 真机服务器请求出错
  8. “东数西算”工程正式启动,全面解读来了!
  9. 成功解决ValueError: numpy.ufunc size changed, may indicate binary incompatibility. Expected 216 from C h
  10. Python:一行代码将以e为结尾的科学计算法类型的数值转为小数点类型数值