本文目录
- 前述:为什么引入平台总线模型
- 一、知识点
- 1. 什么是平台总线模型
- 2. 平台总线模型使用
- 3. 平台总线是如何工作的
- 4. 平台总线模型的优点
- 二、平台总线设备层
- 1. 常用API
- (1) 注册一个平台设备
- (2) 注销一个平台设备
- (3)平台设备结构体
- (4)设备资源结构体
- (5)内嵌标准 device结构体
- 2. 设备层框架代码编写
- 三、平台总线驱动层
- 1. 常用API
- (1)注册一个平台驱动
- (2)注销一个平台驱动
- (3)平台驱动结构体
- (4)描述驱动信息结构体
- (4)获取设备层的硬件资源
- (5)获取设备层自定义平台数据
- 2. 驱动层框架代码编写
前述:为什么引入平台总线模型
例如:我们有多个硬件设备,每个硬件设备的操作寄存器地址都不同,如果我们使用一个ko文件来编写驱动的话,每当我们更换一个设备时就需要重新写一份代码,这个代码中很多地方其实是不需要更改的,这样就增大了很多没有没必要的工作量。为了解决这个问题,我们可以把驱动的控制代码和硬件层分隔开来编写,每当我们需要更换设备时,只需要更改硬件层的代码即可。
一、知识点
1. 什么是平台总线模型
平台总线模型也叫plantform总线模型。平台总线是Linux系统虚拟出来的总线。
2. 平台总线模型使用
平台总线模型将一个驱动分成了俩个部分,分别是device.c和driver.c,device.c用来描述硬件设备资源,driver.c用来获取硬件资源,并控制硬件。将设备层和驱动层分布生成ko文件进行安装。
3. 平台总线是如何工作的
平台总线通过字符串比较,将name相同的device.c和driver.c匹配到一起,然后驱动就可以获取与其匹配的设备的硬件资源来来控制硬件。
4. 平台总线模型的优点
- 减少编写重复代码,提高效率。
- 提高代码的利用率。
二、平台总线设备层
1. 常用API
头文件:linux/platform_device.h
(1) 注册一个平台设备
返回:0:注册成功;负数:注册失败
int platform_device_register(struct platform_device *pdev)
//pdev: 要注册的 struct platform_device 结构指针。
(2) 注销一个平台设备
void platform_device_unregister(struct platform_device *pdev)
//pdev: 要注销的 struct platform_device 结构指针。
(3)平台设备结构体
struct platform_device {
const char * name; //设备名,要求和驱动中的.name 相同。用于与内核层name进行匹配。
int id; //设备 ID,一般为-1。
struct device dev; //内嵌标准 device,一般可以用来传递平台数据
u32 num_resources; //设备占用资源个数
struct resource * resource; //设备占用资源的首地址。
//下面两个成员,我们一般不使用。
struct platform_device_id *id_entry;//设备 id 入口,一般驱动不用
struct pdev_archdata archdata;
};
(4)设备资源结构体
含头文件:linux\ioport.h
struct resource {
resource_size_t start; //硬件资源的起始地址。
resource_size_t end; //硬件资源的结束地址。
const char *name; //资源名称,自定义,主要用于区分资源,也可以不写。
unsigned long flags; //资源类型。这里不做过多介绍,详情查看其他博客。
//下面成员一般不使用。
struct resource *parent, *sibling, *child;
};
/*flags部分内容:
#define IORESOURCE_TYPE_BITS 0x00001f00
#define IORESOURCE_IO 0x00000100 //IO资源类型
#define IORESOURCE_MEM 0x00000200 //内存资源类型
#define IORESOURCE_REG 0x00000300 //寄存器资源类型
#define IORESOURCE_IRQ 0x00000400 //中断资源类型
#define IORESOURCE_DMA 0x00000800 //DMA资源类型
#define IORESOURCE_BUS 0x00001000 //总线资源类型
*/
(5)内嵌标准 device结构体
注意: 我们必须要实现这个结构体的release
函数!当设备被移除时,触发该函数。
*platform_data
称为平台数据指针,可以给平台驱动层传递任何需要的信息。
struct device {
//重要
void (*release)(struct device *dev); //设备被移除时触发。
void *platform_data; /* 平台设备的私有数据指针,要以传递任何结构*/
//下面的不常用
struct device *parent; /* 父设备指针 */
const char *init_name; /*逻辑设备的名字*/
struct device_type *type; /* 设备类型 */
struct bus_type *bus; /* 设备所属的总线类型 */
struct device_driver *driver; /* 指向开辟 struct device 结构的 driver 指针*/
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;
dev_t devt; /* 存放设备号 dev_t, creates the sysfs "dev" */
struct class *class; /* 设备所属类*/
};
2. 设备层框架代码编写
device.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
char my_platform_data[] = {"hello, world"}; // 自定义数据类型
void device_release(struct device *dev) // 设备被卸载时触发
{
pr_err("device_release\n");
}
// 初始化 device 结构体
struct device my_dev = {
.release = device_release,
.platform_data = my_platform_data, // 平台数据
};
// 描述硬件资源。为了方便演示,我们先随机赋值资源的开始地址和结束地址。
struct resource my_resource[] = {
// 第0个硬件资源
[0] = {
.start = 100, // 资源开始地址
.end = 200, // 资源结束地址
.name = "led", // 非必须,主要为标识作用
.flags = IORESOURCE_MEM, // 资源类型,内存类型
},
// 第1个硬件资源
[1] = {
.start = 0, // 资源开始地址
.end = 50, // 资源结束地址
.name = "beep", // 非必须,主要为标识作用
.flags = IORESOURCE_IRQ, // 资源类型,中断类型
},
};
// 平台设备结构体
struct platform_device my_pdev = {
.name = "my_device", // 这个名字要和内核层的名字相同,用于匹配
.id = -1, // 常为-1,自动分配id号
.dev = my_dev,
.resource = my_resource, // 硬件资源
.num_resources = ARRAY_SIZE(my_resource), // 资源的数量
};
// 入口函数
static int __init mydevice_init(void)
{
int ret;
ret = platform_device_register(&my_pdev); // 注册一个平台设备
if (ret < 0) {
pr_err("platform_device_register error\n");
return -1;
}
return 0;
}
// 出口函数
static void __exit mydevice_exit(void)
{
platform_device_unregister(&my_pdev); // 注销一个平台设备
}
module_init(mydevice_init);
module_exit(mydevice_exit);
MODULE_LICENSE("GPL");
三、平台总线驱动层
1. 常用API
(1)注册一个平台驱动
返回值 0:注册成功;负数:注册失败
int platform_driver_register(struct platform_driver *drv)
//要注册的 struct platform_driver 结构指针。
(2)注销一个平台驱动
void platform_driver_unregister(struct platform_driver *drv)
//要注销的 struct platform_driver 结构指针。
(3)平台驱动结构体
我们需要在设备和驱动匹配上时触发的probe
函数中,注册杂项设备!
struct platform_driver {
//常用
int (*probe)(struct platform_device *); //当驱动层和设备层匹配上时触发。
int (*remove)(struct platform_device *); //当驱动被卸载,或者设备被移除时触发。
struct device_driver driver; //用于描述任何类型的设备驱动程序。
struct platform_device_id *id_table; //支持的设备 id 列表(多个name列表)
//电源类函数
void (*shutdown)(struct platform_device *); //关闭设备
int (*suspend)(struct platform_device *, pm_message_t state); //挂起函数
int (*resume)(struct platform_device *); //恢复函数
};
(4)描述驱动信息结构体
当平台驱动结构体没有赋值id_table
成员时,设备层的name会与device_driver
结构体的name成员进行匹配。如果平台驱动结构体中实现了id_table
成员时,会优先与id_table
列表中的name进行匹配。
//这里只写出几个常用的成员函数。
struct device_driver {
const char *name; /*驱动层的名字,用来和设备层匹配的*/
struct module *owner; //模块拥有者,一般为:THIS_MODULE。
};
(4)获取设备层的硬件资源
返回:NULL:获取失败,资源不存在;非 NULL:获得成功,指向资源地址。
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num)
//struct platform_device *dev: 设备层的设备结构体。
//unsigned int type :要获取哪个类型的资源。
//unsigned int num :获取该类型资源的第几个。
(5)获取设备层自定义平台数据
设备层传输的平台数据是什么类型,就用什么类型来接收该函数的返回值。
static inline void *dev_get_platdata(const struct device *dev)
//const struct device *dev:设备层结构体中的device。
2. 驱动层框架代码编写
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
// 打开函数
int my_open(struct inode *node, struct file *fp)
{
return 0;
}
// 释放函数
int my_release(struct inode *node, struct file *fp)
{
return 0;
}
// IO控制函数
long my_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
return 0;
}
// 文件操作结构体
const struct file_operations my_fops = {
.open = my_open,
.release = my_release,
.unlocked_ioctl = my_ioctl,
};
// 杂项设备结构体
struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc_device", // 驱动设备的名称
.fops = &my_fops,
};
// probe函数,在设备与驱动匹配时触发
int my_probe(struct platform_device *pdev)
{
int ret;
struct resource *dev_resource; // 用于接收设备的硬件资源
char *platform_data;
// 获取设备层的平台数据资源
platform_data = dev_get_platdata(&pdev->dev);
pr_err("platform_data: %s\n", platform_data); // 将获取的平台数据打印出来
// 获取设备层的硬件资源
dev_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 接收内存类型的第一个资源
if (dev_resource == NULL) {
pr_err("dev_resource NULL\n");
return -ENODEV;
}
pr_err("start: %pa, end: %pa\n", &dev_resource->start, &dev_resource->end); // 将获取的资源信息打印出来
// 注册杂项设备
ret = misc_register(&misc);
if (ret < 0) {
pr_err("misc_register error\n");
return ret;
}
return 0;
}
// remove函数,在驱动被卸载或设备被移除时触发
int my_remove(struct platform_device *pdev)
{
misc_deregister(&misc); // 注销杂项设备
return 0;
}
// 平台驱动结构体
struct platform_driver my_drv = {
.probe = my_probe, // 设备和驱动name匹配上时触发
.remove = my_remove, // 驱动被卸载或设备被移除时触发
.driver = {
.name = "platform_test", // 要与设备层同名,用于匹配
.owner = THIS_MODULE,
},
};
// 模块初始化函数
static int __init my_driver_init(void)
{
int ret;
ret = platform_driver_register(&my_drv); // 注册一个平台驱动
if (ret < 0) {
pr_err("platform_driver_register error\n");
}
return ret;
}
// 模块退出函数
static void __exit my_driver_exit(void)
{
platform_driver_unregister(&my_drv); // 注销一个平台驱动
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");