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

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

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

驱动linux_驱动之家_linux spi驱动

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

驱动linux_驱动之家_linux spi驱动

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

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

总线在内核中的具象

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

struct bus_type {
    const char *name; /*总线名称*/
    int (*match) (struct device *dev, struct
device_driver *drv); /*驱动与设备的匹配函数*/
………
}

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

总线的注册与注销

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

注销:voidbus_unregister(structbus_type*bus)。

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

随意步入一个目录linux spi驱动,如SPI目录

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

下边以一个示例来注册一条总线到系统中,通常情况下,是不须要另外添加总线到设备中的。添加的总线名子叫my_bus,加载驱动以后,会在/sys/bus目录下见到一个my_bus目录。

新建bus.c:

#include 
#include 
#include 
#include 
 
int my_match(struct device *dev, struct device_driver *drv)
{
    printk("my_match was runn");
    return !strncmp(dev->kobj.name,drv->name,strlen(drv->name));
}
 
struct bus_type my_bus_type = {
    .name = "my_bus",//总线名称
    .match = my_match,//驱动与设备匹配函数
};
 
EXPORT_SYMBOL(my_bus_type);
 
static int my_bus_init()
{
    int ret;
   
    ret = bus_register(&my_bus_type);
   
    return ret;
}
 
static void my_bus_exit()
{
    bus_unregister(&my_bus_type);
}
 
module_init(my_bus_init);
module_exit(my_bus_exit);
 
MODULE_LICENSE("GPL");

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

EXPORT_SYMBOL(my_bus_type);将my_bus_type结构导入给外部文件用linux spi驱动,由于设备和驱动都须要指明要挂载到哪条总线上。

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

驱动描述结构

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

struct device_driver {
{
  const char *name; /*驱动名称*/
  struct bus_type *bus; /*驱动程序所在的总线*/
  int (*probe) (struct device *dev);
  ………
}

Name表示驱动的名子;bus表示驱动要挂载到哪条总线上,赶忙将挂载到刚才创建的my_bus总线上;probe表示驱动和设备匹配成功以后要运行的函数。

驱动的注册与注销:

驱动的注册使用:intdriver_register(structdevice_driver*drv)

驱动的注销使用:voiddriver_unregister(structdevice_driver*drv)

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

#include 
#include 
#include 
#include 
 
extern struct bus_type my_bus_type;
 
int my_probe(struct device *dev)
{
    printk("driver found the devicre it can handlen");
    return 0;
}
 
struct device_driver my_driver =
{
    .name = "yty",//驱动名字
    .bus = &my_bus_type,//属于哪条总线
    .probe = my_probe,
};
 
static int my_driver_init()
{
    return driver_register(&my_driver);
}
 
static int my_driver_exit()
{
    driver_unregister(&my_driver);
}
 
module_init(my_driver_init);
module_exit(my_driver_exit);
 
MODULE_LICENSE("GPL");

驱动的名子叫“yty”,属于bus.c中的my_bus_type这条总线,驱动和设备匹配成功以后,都会运行my_probe函数,也就是会复印出"driverfoundthedevicreitcanhandlen"信息。

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

设备描述结构

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

struct device {
{
const char *init_name; /*设备的名字*/
struct bus_type *bus; /*设备所在的总线*/
………
}

设备的注册与注销

设备的注册使用intdevice_register(structdevice*dev)

设备的注销使用:voiddevice_unregister(structdevice*dev)

编撰device.c文件:

#include 
#include 
#include 
#include 
 
extern struct bus_type my_bus_type;
 
struct device my_dev =
{
    .init_name = "yty",
    .bus = &my_bus_type
};
 
static int my_device_init()
{
    int ret;
    ret = device_register(&my_dev);
    return ret;
}
 
static void my_device_exit()
{
    device_unregister(&my_dev);
}
 
module_init(my_device_init);
module_exit(my_device_exit);
 
MODULE_LICENSE("GPL");

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

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

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

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

SPI总线设备驱动剖析

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

驱动linux_linux spi驱动_驱动之家

在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中。

驱动之家_驱动linux_linux spi驱动

由上面的范例代码晓得,注册一个驱动使用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函数,将设备注册到系统。

驱动之家_linux spi驱动_驱动linux

用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,之后调用returnmaster->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_bytei2c_smbus_write_byte等。

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

本文原创地址:https://www.linuxprobe.com/jyzxsbqdmxdl.html编辑:刘遄,审核员:暂无