Linux内核编程(六)平台总线plantform驱动模型


  

前述:为什么引入平台总线模型

   例如:我们有多个硬件设备,每个硬件设备的操作寄存器地址都不同,如果我们使用一个ko文件来编写驱动的话,每当我们更换一个设备时就需要重新写一份代码,这个代码中很多地方其实是不需要更改的,这样就增大了很多没有没必要的工作量。为了解决这个问题,我们可以把驱动的控制代码和硬件层分隔开来编写,每当我们需要更换设备时,只需要更改硬件层的代码即可。

一、知识点

1. 什么是平台总线模型

   平台总线模型也叫plantform总线模型。平台总线是Linux系统虚拟出来的总线。

2. 平台总线模型使用

   平台总线模型将一个驱动分成了俩个部分,分别是device.c和driver.c,device.c用来描述硬件设备资源,driver.c用来获取硬件资源,并控制硬件。将设备层和驱动层分布生成ko文件进行安装。
在这里插入图片描述

3. 平台总线是如何工作的

   平台总线通过字符串比较,将name相同的device.c和driver.c匹配到一起,然后驱动就可以获取与其匹配的设备的硬件资源来来控制硬件。

4. 平台总线模型的优点

  1. 减少编写重复代码,提高效率。
  2. 提高代码的利用率。

二、平台总线设备层

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");
}

// 描述硬件资源。为了方便演示,我们先随机赋值资源的开始地址和结束地址。
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 = "platform_test", // 这个名字要和内核层的名字相同,用于匹配
    .id = -1, // 常为-1,自动分配id号
    .dev = {
			    .release = device_release,
			    .platform_data = my_platform_data, // 平台数据
            },
    .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 *); //恢复函数
};

●描述驱动信息结构体

   当平台驱动结构体没有赋值id_table成员时,设备层的name会与device_driver结构体的name成员进行匹配。如果平台驱动结构体中实现了id_table成员时,会优先与id_table列表中的name进行匹配。

//这里只写出几个常用的成员函数。
struct device_driver {  
		const char *name; /*驱动层的名字,用来和设备层匹配的*/ 
	    struct module *owner;  //模块拥有者,一般为:THIS_MODULE。
};

●id_table列表结构体

   可定义多个与设备层进行匹配的name名字。它的主要作用是实现设备和驱动程序之间的匹配机制。当平台设备被注册时,内核会通过 id_table 查找匹配的设备 ID。
注意:这个成员实现后,会设备层会优先匹配id_table列表中的名称!

struct platform_device_id {
		char name[PLATFORM_NAME_SIZE];   //内核与设备匹配的名字
		kernel_ulong_t driver_data;    //区分不同的设备,任意。
};

(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;
}

//匹配列表
const struct platform_device_id table[] = {
	{"platform_test", 0},
	{"platform_test2", 1},
	{"platform_test3", 2}
};

// 平台驱动结构体
struct platform_driver my_drv = {
    .probe = my_probe,  // 设备和驱动name匹配上时触发
    .remove = my_remove,  // 驱动被卸载或设备被移除时触发
    .driver = {
        .name = "platform_test",   // 要与设备层同名,用于匹配。虽然实现了id_table,但这个name必须要写!!
        .owner = THIS_MODULE,
    },
    .id_table = table,	//匹配的设备列表,实现了这个成员后,driver的name无效,优先匹配id_table中的name。
};

// 模块初始化函数
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");

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值