您的位置:首页 > 财经 > 产业 > Linux驱动开发基础(总线驱动设备模型)

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

2024/10/7 2:23:24 来源:https://blog.csdn.net/weixin_62822414/article/details/141247555  浏览:    关键词: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");

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com