Linux驱动开发基础(总线驱动设备模型)

 所学来自百问网

目录

1.驱动设计的思想:面向对象/分层/分离

1.1 面向对象

1.2 分层

1.3 分离

2.总线驱动设备模型

2.1 相关函数和结构体

2.1.1 platform_device

2.1.2 platform_driver

2.1.3 相关函数

2.2 platfrom_driver和platfrom_device的注册过程

2.3 匹配规则

2.3.1 先注册驱动

2.3.2 先注册设备

2.3.3 比较顺序

2.4 driver获取device数据

2.5 总线驱动设备示例代码

2.5.1 led.drv.c

2.5.2 led_dev.c

2.5.3 led.dev2.c


1.驱动设计的思想:面向对象/分层/分离

1.1 面向对象

面向对象就是指的根据某一类事物的属性,进行抽象。

字符设备驱动程序抽象出一个file_operations结构体;

file_operations:对我们驱动程序经常需要用到的open、read、write这些公共的函数或属性封装成一个结构体,由于不同的硬件有不同的操作方法,故对这部分函数或属性进行抽象,在编写字符设备驱动程序时,只需对该结构体进行实现即可

1.2 分层

上层实现硬件无关的操作,比如注册字符设备驱动:leddrv.c

下层实现硬件相关的操作,比如board_A.c实现单板A的LED操作

如:

1.3 分离

当我们想对分层后的代码进行修改时,比如驱动不同的引脚的led灯,我们就要重新编写初始化和控制代码,这样显然很麻烦;由于每一款芯片的GPIO的操作都是类似的,故我们可以针对该芯片书写一个硬件通用代码

2.总线驱动设备模型

platform_driver结构体负责对硬件的寄存器代码的编写、初始化file_operation、映射寄存器等对硬件设备的初始化操作,即驱动

platform_device结构体负责对硬件功能的实现,即资源

在linux内核中,platform_bus_type是platform总线的数据结构,它是一个虚拟的总线,总线负责设备和驱动的匹配和绑定,用于将platform_deviceplatform_driver连接起来

2.1 相关函数和结构体

2.1.1 platform_device

platform_device包含了对描述设备所需的各种信息,如设备的名称、ID、资源(如 IO 端口、内存地址、中断号等)以及指向设备特定数据的指针。

部分字段的含义:
struct platform_device {
    const char  *name;          // 设备的名称,用于与驱动程序进行匹配
    int     id;                 // 设备的 ID 号,用于区分具有相同名称的不同设备实例。
    struct device   dev;        // 一个 device 结构体,表示设备在内核设备模型中的抽象。
    u32     num_resources;      // 设备使用的资源数量
    struct resource *resource;  // 资源描述符数组
    char *driver_override;      // 一个指向字符数组的指针,用于指定要强制匹配的驱动程序名称
};

2.1.2 platform_driver

platform_driver提供了注册和注销设备驱动程序的接口,负责对硬件驱动程序的书写

部分字段的含义:
struct platform_driver {
    int (*probe)(struct platform_device *);    //函数指针,指向驱动程序的 probe 函数 用于初始化设备并分配必要的资源
    int (*remove)(struct platform_device *);   //函数指针,指向驱动程序的 remove 函数 释放之前分配的资源并执行清理操作
    struct device_driver driver;               //device_driver 结构体,包含了驱动程序的通用信息,如驱动程序的名称、所属模块等
    const struct platform_device_id *id_table; //指向 platform_device_id 结构体数组的指针,用于基于设备 ID 的匹配
};

2.1.3 相关函数

  • 用于向内核注册一个平台驱动程序

    • int platform_driver_register(struct platform_driver *drv);

    • drv 是一个指向 platform_driver 结构体的指针,该结构体包含了驱动程序的信息和函数指针

    • 返回值:注册成功,返回 0;如果失败,返回非零错误码

  • 从内核中注销一个已经注册的平台驱动程序

    • void platform_driver_unregister(struct platform_driver *drv);

    • drv 是一个指向要注销的平台驱动程序结构体的指针

  • 用于向内核注册一个平台设备

    • struct platform_device *platform_device_register(struct platform_device *pdev);

    • pdev 是一个指向 platform_device 结构体的指针,该结构体包含了设备的信息和资源

    • 返回值:注册成功,返回 pdev 指针本身;如果失败,返回 NULL

  • 用于从内核中注销一个已经注册的平台设备

    • void platform_device_unregister(struct platform_device *pdev);

    • pdev 是一个指向要注销的平台设备结构体的指针。

  • 从给定的平台设备(platform_device)中获取指定类型的资源

    • struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);

    • dev:指向要查询资源的平台设备的指针。

    • type:要查询的资源类型,如IORESOURCE_MEM(内存资源)、IORESOURCE_IRQ(中断资源)等。

    • num:如果同一类型有多个资源,此参数指定要查询的资源的编号(通常从0开始)

    • 返回值:函数返回一个指向struct resource结构体的指针,该结构体包含了资源的详细信息,如开始地址、结束地址、资源名称等。如果找不到指定的资源,则返回NULL

  • 将私有数据与平台设备(platform_device)相关联

    • void platform_set_drvdata(struct platform_device *pdev, void *data);

    • pdev:指向要设置私有数据的平台设备的指针。

    • data:指向要与平台设备关联的私有数据的指针。这个数据可以是任何类型,但通常是一个指向设备特定数据结构的指针。

  • 用于获取与平台设备(platform_device)相关联的私有数据

    • void *platform_get_drvdata(const struct platform_device *pdev);

    • pdev:指向要获取私有数据的平台设备的指针。

    • 返回值:函数返回一个指向之前通过platform_set_drvdata设置的私有数据的指针。如果没有设置私有数据,则返回NULL。

2.2 platfrom_driver和platfrom_device的注册过程

图解:即将注册的设备或驱动添加到总线的链表中,依次遍历链表,去匹配对应的驱动或设备,若匹配则调用相应的函数

2.3 匹配规则

2.3.1 先注册驱动

图解:总线的drvier链表已经构造并注册了hello_drv,系统会对device链表中的设备一一比较,匹配得到则直接调用,若无,系统会直接返回,则需要去构造并注册hello_dev,构造完成后,系统会对drvier链表进行一一比较,找到则直接调用

2.3.2 先注册设备

图解:总线的device链表已经构造并注册了hello_dev,系统会对drvier链表中的设备一一比较,匹配得到则直接调用,若无,系统会直接返回,则需要去构造并注册hello_drv,构造完成后,系统会对device链表进行一一比较,找到则直接调用

2.3.3 比较顺序

最先比较

platform_device.driver_override 和 platform_driver.driver.name 可以设置platform_device 的driver_override,强制选择某个 platform_driver。

然后比较

platform_device.name和platform_driver.id_table[i].name platform_driver.id_table 是“platform_device_id”指针,表示该 drv 支持若干个device,它里面列出了各个device的{.name, .driver_data},其中的“name”表示该 drv 支持的设备的名字,driver_data是些提供给该device的私有数据。

最后比较

platform_device.name 和 platform_driver.driver.name platform_driver.id_table 可能为空,这时可以根据platform_driver.driver.name来寻找同名的platform_device。

2.4 driver获取device数据

通过调用

resource结构体下的参数获得数据

例如

2.5 总线驱动设备示例代码

2.5.1 led.drv.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
​
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
​
#define LED_MAX_CNT 10
// 结构体记录led信息
struct led_desc {
    int pin;
    int minor;
};
​
/* 1. 确定主设备号 */ 
static int major = 0;
static struct class *led_class;
​
static int g_ledcnt = 0;
static struct led_desc leds_desc[LED_MAX_CNT];
​
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    int err;
    char status;
    // 记录次设备号
    struct inode *inode = file_inode(file);
    int minor = iminor(inode);   
    
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = copy_from_user(&status, buf, 1);
​
    /* 根据次设备号和status控制LED */
    printk("set led pin 0x%x as %d\n", leds_desc[minor].pin, status);
    
    return 1;
}
​
static int led_drv_open (struct inode *node, struct file *file)
{
    int minor = iminor(node);
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
​
    /* 根据次设备号初始化LED */
    printk("init led pin 0x%x as output\n", leds_desc[minor].pin);
    
    return 0;
}
​
/* 2. 定义自己的file_operations结构体                                              */
static struct file_operations led_drv = {
    .owner   = THIS_MODULE,
    .open    = led_drv_open,
    .write   = led_drv_write,
};
​
/* B.1 实现platform_driver的probe函数  */
static int led_probe(struct platform_device *pdev)
{   
    int minor;
    int i = 0;
​
    struct resource *res;
        
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
​
    res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);
    if (!res)
        return -EINVAL;
​
    /* 记录引脚和次设备号 */
    minor = g_ledcnt;
    leds_desc[minor].pin = res->start;
    leds_desc[minor].minor = minor;
​
    /* 7.2 辅助信息 */
    /* 创建设备节点 */
    device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); /* /dev/100ask_led0,1,... */
    
    platform_set_drvdata(pdev, &leds_desc[minor]);
    g_ledcnt++;
    
    return 0;
}
​
/* B.2 实现platform_driver的remove函数  */
static int led_remove(struct platform_device *pdev)
{
    struct led_desc *led = platform_get_drvdata(pdev);
    device_destroy(led_class, MKDEV(major, led->minor)); /* /dev/100ask_led0,1,... */
​
    return 0;
}
// 记录可与driver相匹配的device设备
static const struct platform_device_id led_id_table[] = {
    {"100ask_led",   1},
    {"100ask_led_3", 2},
    {"100ask_led_4", 3},
    { },
};
​
​
/* A. 实现platform_driver  */
static struct platform_driver led_driver = {
    .probe      = led_probe,
    .remove     = led_remove,
    .driver     = {
        .name   = "100ask_led",
    },
    .id_table = led_id_table, // 获取匹配的device
};
​
/* 4. 把file_operations结构体告诉内核:注册驱动程序register_chrdev                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{
    int err;
    
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */
​
    /* 7.1 辅助信息 */
    led_class = class_create(THIS_MODULE, "100ask_led_class");
    err = PTR_ERR(led_class);
    if (IS_ERR(led_class)) {
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        unregister_chrdev(major, "led");
        return -1;
    }
    /* C. 注册platform_driver */
    err = platform_driver_register(&led_driver); 
    
    return err;
}
​
/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit led_exit(void)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
​
    /* C. 反注册platform_driver    */
    platform_driver_unregister(&led_driver); 
​
    class_destroy(led_class);
    unregister_chrdev(major, "100ask_led");
}
​
/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

2.5.2 led_dev.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
// 设置硬件资源 用于driver获取
static struct resource resources[] = {
    {
            .start = (3<<8)|(1),            
            .flags = IORESOURCE_IRQ,
    },
};
​
static void led_dev_release(struct device *dev)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}
​
static struct platform_device led_dev = {
        .name = "100ask_led",   // 名称匹配
        .num_resources = ARRAY_SIZE(resources),
        .resource = resources,
        .dev = {
                .release = led_dev_release,
         },
};
​
static int __init led_dev_init(void)
{
    int err;
    
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = platform_device_register(&led_dev);   
    
    return err;
}
​
static void __exit led_dev_exit(void)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    platform_device_unregister(&led_dev);
}
​
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

2.5.3 led.dev2.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
// 设置硬件资源 用于driver获取
static struct resource resources[] = {
    {
            .start = (3<<8)|(2),            
            .flags = IORESOURCE_IRQ,
    },
};
​
static void led_dev_release(struct device *dev)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}
​
static struct platform_device led_dev = {
        .name = "100ask_led_second",
        .num_resources = ARRAY_SIZE(resources),
        .resource = resources,
        .dev = {
                .release = led_dev_release,
         },
        .driver_override = "100ask_led", // 强制匹配
};
​
static int __init led_dev_init(void)
{
    int err;
    
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = platform_device_register(&led_dev);   
    
    return err;
}
​
static void __exit led_dev_exit(void)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    platform_device_unregister(&led_dev);
}
​
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值