BSP-浅谈Linux驱动到设备模型再到设备树

1.最初Linux驱动架构

  Linux驱动会在初始化函数中向内核注册file_operations结构体,结构体里面就包含一些基本的open,close函数。Linux驱动中也会去实现这些函数。并且相对应的硬件信息也在这个驱动中。以LED为例,驱动程序中会将LED的引脚地址映射成虚拟地址,然后在open函数里面进行写操作。

  当APP调用open函数的时候,就会通过一系列转换,最后调用到驱动中的open函数。(这边就不具体描述APP怎么调用到驱动中的open函数)。

弊端:  

  可以发现这种驱动,设备和驱动没有分离。也就是说,设备的信息是硬编码在驱动代码中的,会给驱动程序造成极大的限制。如果硬件有改动,那么必然要修改驱动代码,驱动的通用性将会非常的差。

2.总线、设备和驱动

  以USB总线为例:当接入一个USB设备时,USB总线会立即感知到这件事,并去遍历所有注册在USB总线上的驱动,然后调用驱动中的一段代码来测探是否能够驱动刚插入的USB设备,如果可以,那么总线完成驱动和设备之间的绑定。

  Linux设备模型为这三种对象各自定义了对应的类:

  •   struct bus_type代表总线
  •   srtuct device代表设备
  •   struct device_driver代表驱动

  这样就将设备的硬件信息和驱动分离开来。设备专门用来描述硬件相关的信息,而驱动和设备绑定成功后,驱动负责从设备中动态获取这些资源信息,当设备的资源改变后,只是设备改变而已,驱动的代码可以不做任何修改,这就大大提高了驱动代码的通用性。

  通常情况下,总线已经在内核中实现好,我们只需要写对应总线的驱动,有时候还会编写相应的设备注册代码。

3.平台设备及其驱动

  platform总线:有的设备并没有对应的物理总线,比如LED、RTC和蜂鸣器等。为此,内核专门开发一种虚拟总线——platform总线,用来连接这些没有物理总线的设备或者一些不支持热插拔的设备。

  平台设备是用struct platform_device结构来表示的。

4.Linux设备树

  在Linux内核源码的ARM体系结构引入设备树之前,相关的BSP代码中充斥了大量的平台设备(Platform Device)代码,而这些代码大多数都是重复的。之前的内核移植工作有很大一部分工作就是在复制一份BSP代码,并修改BSP代码中和目标板中与特定硬件相关的平台设备信息。

  设备树是一个描述硬件的数据结构。它只是提供了一种语言,将硬件配置从Linux内核源码中提取出来。

处理器访问硬件设备的方式

1.内存方式

2.I/O接口 寄存器

3.管脚 可以对芯片进行复位、接收来自设备的中断信号

X86平台中 I/O地址空间与内存地址空间是分开的,寄存器位于I/O空间时称为I/O端口

ARM平台中I/O地址空间与内存地址空间是统一编址的,也称I/O内存,是系统中访问速度最快的内存

//实例代码

platform_driver_register( &therm_of_driver );

static struct platform_driver therm_of_driver = {
    .driver = {
        .name = "temperature",
        .of_match_table = therm_of_match,
    },
    .probe        = therm_of_probe,
    .remove        = therm_of_remove,
};


therm_of_probe
    i2c_add_driver
    static struct i2c_driver g4fan_driver = {
    .driver = {
        .name    = "therm_windtunnel",
    },
    .attach_adapter = do_attach,
    .probe        = do_probe,
    .remove        = do_remove,
    .id_table    = therm_windtunnel_id,
};

do_attach
    i2c_new_probed_device


ad5064_init
    ad5064_spi_register_driver
        static struct spi_driver ad5064_spi_driver = {
            .driver = {
                   .name = "ad5064",
            },
            .probe = ad5064_spi_probe,
            .remove = ad5064_spi_remove,
            .id_table = ad5064_spi_ids,
        };
        spi_register_driver(&ad5064_spi_driver);
            sdrv->driver.bus = &spi_bus_type;
            driver_register(&sdrv->driver);

    ad5064_i2c_register_driver
        static struct i2c_driver ad5064_i2c_driver = {
            .driver = {
                   .name = "ad5064",
            },
            .probe = ad5064_i2c_probe,
            .remove = ad5064_i2c_remove,
            .id_table = ad5064_i2c_ids,
        };
        i2c_add_driver(&ad5064_i2c_driver);
            i2c_register_driver
                driver->driver.bus = &i2c_bus_type;
                res = driver_register(&driver->driver);


driver_register
    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 - Add a driver to the bus.
        bus = bus_get(drv->bus);//获取总线
        pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
            driver_attach(drv);
            driver_create_file
                sysfs_create_file

//probe
//驱动在probe中创建设备
ad5064_spi_probe
    ad5064_probe
        ret = iio_device_register(indio_dev); //创建设备
            device_add

/**
 * device_attach - try to attach device to a driver.
 * @dev: device.
 *
 * Walk the list of drivers that the bus has and call
 * driver_probe_device() for each pair. If a compatible
 * pair is found, break out and return.
 *
 * Returns 1 if the device was bound to a driver;
 * 0 if no matching driver was found;
 * -ENODEV if the device is not registered.
 *
 * When called for a USB interface, @dev->parent lock must be held.
 */
device_attach
    bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
        __device_attach_driver
            driver_match_device(drv, dev);
            driver_probe_device
                if (!device_is_registered(dev))
                really_probe
                    dev->bus->probe(dev);

//总线
int __init platform_bus_init(void)
{
    int error;
 
    early_platform_cleanup();
 
    error = device_register(&platform_bus);
    if (error)
        return error;
    error =  bus_register(&platform_bus_type); //这里调用总线的注册函数。
    if (error)
        device_unregister(&platform_bus);
    of_platform_register_reconfig_notifier();
    return error;
}

驱动模型
//bus
struct bus_type
//device
struct device
//driver
truct device_driver


 1、注册驱动
    注册一个驱动,首先把驱动链入到驱动(driver)链表中,然后从设备(device)链表中逐个寻找,看有没有可以关联的设备。如果找到关联的设备,就执行probe函数。
    具体流程如下:
platform_driver_register-》driver_register-》bus_add_driver-》driver_attach-》bus_for_each_dev-》__driver_attach
-》driver_probe_device-》really_probe
    2、添加设备
    添加一个设备,首先把设备链入到设备(device)链表中,然后从驱动(driver)链表中逐个寻找,看有没有可以关联的驱动。如果找到关联的驱动,就执行probe函数。
    具体流程如下:
device_add-》bus_add_device-》bus_probe_device-》device_attach-》bus_for_each_drv-》__device_attach
-》driver_probe_device-》really_probe

一个现实的linux设备和驱动通常都需要挂接在一种总线上,比较常见的总线有USB、PCI总线等。但是,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。基于这样的背景下,2.6内核加入了platform虚拟总线。platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序使用这些资源时使用统一的接口。platform总线对加入到该总线的设备和驱动分别封装了两个结构体——platform_device和platform_driver。并且提供了对应的注册函数。

主机驱动和外设驱动分离

1)主机端的驱动。根据具体的IC、SPI、USB等控制器的硬件手册,操作具体的IPC、SPI、USB等控制器,产生总线的各种波形。
2)连接主机和外设的纽带。外设不直接调用主机端的驱动来产生波形,而是调一个标准的API。由这个标准的API把这个波形的传输请求间接“转发”给了具体的主机端驱动。当然,在这里,最好把关于波形的描述也以某种数据结构标准化。
3)外设端的驱动。外设接在I-C、SPI、USB这样的总线上,但是它们本身可以是触摸屏、网卡、声卡或者任意一种类型的设备。我们在相关的i2c_driver、spi_driver、usb_driver这种xxx_driver的probe () 函数中去注册它具体的类型。当这些外设要求IP℃、SPI、USB等去访问它的时候,它调用“连接主机和外设的纽带”模块的标准API。
4)板级逻辑。板级逻辑用来描述主机和外设是如何互联的,它相当于一个“路由表”。假设板子上有多个SPI控制器和多个SPI外设,那究竟谁接在谁上面管理互联关系,既不是主机端的责任,也不是外设端的责任,这属于板级逻辑的责任。这部分通常出现在arch/arm/mach-xxx下面或者arch/arm/bootldts下面。

  1. 驱动与设备树

platform_get_resource(pdev, IORESOURCE_IRQ, 0)

常见的资源标志就是IORESOURCE_MEM IORESOURCE_REG IORESOURCE_IRQ ,与设备树中的对应关系如下:

你可能感兴趣的:(BSP,linux,驱动开发,运维)