发表于 2024-7-16 21:01:07

Structure of Linux Kernel Device Driver(Part II)

Structure of Linux Kernel Device Driver

ref. https://www.youtube.com/watch?v=XoYkHUnmpQo&list=LL&index=1&t=272s
Talk to the hardware

在操作系统中有几种机制能够让CPU与硬件设备举行通信:

[*]Port I/O: 使用一个专用的总线举行用心
[*]Memory-mapped I/O: 与硬件设备共享内存地址空间举行通信,(have the I/O devices mapped to our address spaces, so in the address space you have the registers there where you can talk to the hardware ),这种方法更为常见
在Memory-Mapped I/O(MMIO)机制中,将I/O设备映射到内存地址空间,然后CPU可以通过寄存器与硬件举行通信,以是当用户使用指针向某个地址写入数据,实际上是将数据写入了寄存器中。
https://img2024.cnblogs.com/blog/1670490/202407/1670490-20240715144243318-1476754507.png使用mmio与硬件设备举行通信的三个步调:

[*]向MMIO寄存器发送请求,通过一些内核API完成,比如request_mem_region(), it's recommended, not mandatory
[*]将寄存器的物理地址映射到捏造地址,比如使用函数ioremap()
[*]使用内核API读写寄存器,比如readb()\writeb(), readw()\writew(), readl()\writel(), readq()\writeq(),分别举行8-bit, 16-bit, 32-bit以及64-bit的读写
当然也可以使用函数ioremap()返回的指针举行读写,不外推荐使用内核封装的函数对这个指针举行操作,注意使用iounmap()释放掉这些地址空间。完成地址映射后,可以通过cat \proc\iomem来查看I/O设备的地址映射。
下面这段代码用于控制一个LED灯设备的驱动:
#define GPIO1_BASE        0x0209C000
#define GPIO1_SIZE        8
#define LED_OFF        0
#define LED_ON        1

static struct {
        dev_t devnum;
        struct cdev cdev;
        void __iomem *regbase;
    // device datas area
} drvled_data;

static void drvled_setled(unsigned int status)
{
        u32 val;

        /* set value */
        val = readl(drvled_data.regbase);
        if (status == LED_ON)
                val |= GPIO_BIT;
        else if (status == LED_OFF)
                val &= ~GPIO_BIT;
        writel(val, drvled_data.regbase);

        /* update status */
        drvled_data.led_status = status;
}

static ssize_t my_write(struct file *file, const char __user *buf,
                          size_t count, loff_t *ppos)
{
        char kbuf = 0;
        if (copy_from_user(&kbuf, buf, 1))
                return -EFAULT;

        if (kbuf == '1') {
                drvled_setled(LED_ON);
                pr_info("LED ON!\n");
        } else if (kbuf == '0') {
                drvled_setled(LED_OFF);
                pr_info("LED OFF!\n");
        }
        return count;
}

static const struct file_operations drvled_fops = {
        .owner = THIS_MODULE,
        .write = my_write,
};

static int __init init_module(void)
{
   
        if (!request_mem_region(GPIO1_BASE, GPIO1_SIZE, "my_device_driver")) {
                // handle error
        }

        drvled_data.regbase = ioremap(GPIO1_BASE, GPIO1_SIZE);
        if (!drvled_data.regbase) {
                // handle error
        }

    // Device driver initialization and installation

    return 0;
}

static int __exit exit_module(void)
{
    iounmap(drvled_data.regbase);
        release_mem_region(GPIO1_BASE, GPIO1_SIZE);
    // Unloading and unregistering the device driver
}通过上面的这些API,可以通过读写对应了设备文件控制对应的硬件设备。下图是一个LED的驱动,其整体框架如下:
https://img2024.cnblogs.com/blog/1670490/202407/1670490-20240715195747381-1034349629.png
如许的框架存在一些问题:

[*]用户访问设备文件所使用的接口是自定义的,而没有举行尺度化
[*]从GPIO控制器中为设备驱动分配了两个寄存器,那么其他GPIO将无法访问这两个寄存器,
假设GPIO控制器中有32个GPIO,那么没有人能够使用其余的另外31个GPIO
[*]在设备驱动中采用硬编码的方式写入硬件相关的信息,那么假如修改了硬件,也必须修改驱动
因此,如许的框架还需要肯定程度的解耦合并举行模块化。
Driver Model

Linux驱动模型提供了多个设备驱动抽象(abstraction to device drivers),这能够使驱动代码更模块化、可重用而且轻易维护。
该驱动模型的组成如下:

[*]Framework:根据设备类型导出的尺度化接口
[*]Buses:从设备驱动中抽象出来的设备信息以及设备所连接的位置
https://img2024.cnblogs.com/blog/1670490/202407/1670490-20240715201433545-1885527876.png
使用驱动框架(linux/leds.h for led device)将接口尺度化后,意味着驱动开辟者不再需要定义file_operations来指定回调函数的行为,这些接口以及对应回调函数都由对应的框架完成定义。Users know beforehand the interface provided by a driver based on its class or type.
为了避免硬件资源被一个设备独占,需要使用特定的API进举措态控制,比如对于LED设备,Linux内核中实现了一种生产者/消费者的模型(gpiolib)来管理GPIO资源。

[*]GPIO producer,雷同于GPIO控制器的驱动
[*]GPIO consumer,雷同于LED设备的驱动
如下,使用了框架将用户接口尺度化,并使用内核接口管理硬件资源:
#include <linux/leds.h>
// other header files

struct drvled_data_st {
        struct gpio_desc *desc;         // change from "void __iomem *regbase;"
        struct led_classdev led_cdev;   // change from "cdev"
};

static struct drvled_data_st *drvled_data;

static void drvled_setled(unsigned int status)
{
    // not use the writel()
        if (status == LED_ON)
                gpiod_set_value(drvled_data->desc, 1);
        else
                gpiod_set_value(drvled_data->desc, 0);
}

static void drvled_change_state(struct led_classdev *led_cdev,
                                enum led_brightness brightness)
{
        if (brightness)
                drvled_setled(LED_ON);
        else
                drvled_setled(LED_OFF);
}

static int __init drvled_init(void)
{
        int result = 0;

    // no need for driver initialization
    // no need for iommp for device

        drvled_data = kzalloc(sizeof(*drvled_data), GFP_KERNEL);
        if (!drvled_data) {
                // handle error
        }

        result = gpio_request(GPIO_NUM, DRIVER_NAME);
        if (result) {
                // handle error
        }

        drvled_data->desc = gpio_to_desc(GPIO_NUM);

        drvled_data->led_cdev.name = "ipe:red:user";
        drvled_data->led_cdev.brightness_set = drvled_change_state;

        result = led_classdev_register(NULL, &drvled_data->led_cdev);
        if (result) {
                // handle error
        }
    // ...
}

static void __exit drvled_exit(void)
{
        led_classdev_unregister(&drvled_data->led_cdev);
    // not use the iounmap()       
    gpio_free(GPIO_NUM);
        release_mem_region(GPIO1_BASE, GPIO1_SIZE);
        kfree(drvled_data);
}
// ...使用设备框架对驱动举行重组后,用户不再直接与/dev/目录下的设备文件举行交互,而是在目录/sys/class/led目录下找到全部的LED类的设备,进入所注册的驱动目录下,可以找到控制LED设备的接口,这些接口仍然以文件的形式存在,但是和之前所定义的接口相比会更加尺度化,也就是险些全部的LED设备都可以使用如许的接口举行控制。
Bus infrastructure

末了,可以使用总线框架实现设备与驱动的解耦合。总线框架组成如下:

[*]Bus Core: 对于给定总线类型(USB core, PCI core, etc)所实现的API, (represented in the kernel by the "bus_type" structure)
[*]Bus adapters:总线控制器驱动, (represented in the kernel by the "device_driver" structure)
[*]Bus drivers: 负责管理连接到总线的设备, (represented in the kernel by the "device_driver" structure)
[*]Bus devices: 全部连接到总线的设备, (represented in the kernel by the structure "device")
总线框架如下图所示:
https://img2024.cnblogs.com/blog/1670490/202407/1670490-20240715210645321-2146077344.png
解耦合的实现:在总线框架中,驱动相当于是一种类(class),当用户在总线上注册设备时,将会产生这个类的实例。以I2C总线设备为例,下图演示了这个过程:
https://img2024.cnblogs.com/blog/1670490/202407/1670490-20240716105504977-2027987998.png
这个过程可以大致分为三步:

[*]在Bus Core注册驱动
[*]在Bus Core注册设备,随后Bus Core将会匹配对应的驱动
[*]匹配乐成后,Bus Core将会通过probe()函数调用驱动对应的回调函数,举行实例化
有很多种方法向总线注册一个设备:

[*]使用Bus Core提供的接口,在用户应用中静态注册一个设备,比如I2C Bus提供的i2c_register_board_info(),或者捏造总线Platform Bus提供的platform_device_register()
[*]使用硬件平台提供的注册机制,比如X86提供的ACPI
[*]使用设备树,比如PowerPC以及ARM提供的尺度化机制
[*]有一些总线支持通过设备枚举(device enumeration)来自动添加设备,比如PCI总线的lspci命令

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Structure of Linux Kernel Device Driver(Part II)