platform平台总线——讯为笔记
概念
平台(platform)总线是一种虚拟的总线。
Linux内核要求每出现一个device就要向总线汇报(注册),出现一个driver,也要向总线汇报(注册)。
注册device/driver时,总线会寻找与之匹配的driver/device。如果对应的name相同则发生匹配。
匹配发生时,会调用platform_driver中的probe函数。所以驱动的关键的处理、初始化等都要从probe函数开始。
以杂项设备使用平台总线为例:将之前的一个驱动文件分成两个:device.c和driver.c
把稳定不变的放在driver.c里(类似文件操作集里的一些函数),把需要经常或根据平台不同改变的放在device.c里
平台设备 device
主要使用的两个函数
int platform_device_register(struct platform_device *); //注册平台设备
void platform_device_unregister(struct platform_device *); //注销平台设备
使用的结构体
//include/linux/platform_device.h
struct platform_device {
const char *name; //在/sys/bus/platform/
int id; //多个同name的设备使用,只有一个时写-1
bool id_auto;
struct device dev; //设备结构体
u32 num_resources; //资源数量
struct resource *resource; //硬件资源
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
//include/linux/ioport.h //硬件资源resource结构体
struct resource {
resource_size_t start; //资源起始地址
resource_size_t end; //资源的结束地址
const char *name; //资源名字
unsigned long flags; //资源的类型
unsigned long desc;
struct resource *parent, *sibling, *child;
};
//资源类型的宏定义也在ioport.h里,就在resource结构体下方
注册device之后,在/sys/bus/platform/devices目录下,会有以platform_device里name成员命名的文件夹
平台驱动 driver
注册和注销函数
#define platform_driver_register(drv) __platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,
struct module *);
extern void platform_driver_unregister(struct platform_driver *);
platform_driver结构体
struct platform_driver {
int (*probe)(struct platform_device *); //device和driver匹配时执行
int (*remove)(struct platform_device *); //当device和driver任意一个remove时执行
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver; //driver结构体
const struct platform_device_id *id_table; //优先匹配id_table里的名字,如果id_table为空,才会再匹配driver里的name
bool prevent_deferred_probe;
};
device_driver结构体
// include/linux/device.h
struct device_driver {
const char *name; //用于和device匹配的名字
struct bus_type *bus;
struct module *owner;
const struct of_device_id *of_match_table; //也可以用来匹配,一般和设备树做匹配时使用
/*---省略了其他成员---*/
};
probe函数注意事项
思路:
- 从device里获取硬件资源
- 完善file_operations,注册杂项/字符设备,生成设备节点
从device里获取硬件资源
直接获得:probe函数的形参platform_device结构体,从中直接获得device里定义的参数
不安全
推荐使用:
使用函数
struct resource *platform_get_resource(struct platform_device *,
unsigned int, unsigned int);
//参数:device结构体,资源类型,同类型资源在device结构体的第几位
如果获取的资源有地址之类需要使用的参数,则注册设备之前先向内核登记资源
//include/linux/ioport.h
requset_mem_region(start,n) //登记,确保硬件资源没有被占用
//起始地址,长度
release_mem_region(start,n) //释放,登记失败也要释放
完善file_operations,注册杂项/字符设备,生成设备节点
注册设备,生成节点(和字符或杂项设备一样的操作)
总结
如果使用平台总线设计驱动,要将代码分成两部分:device和driver。
device代码放一些跟随平台改变(比如芯片不同)的参数,driver里放稳定不变的代码。
以控制gpio为例,将寄存器等信息放在device里,文件操作集、注册设备等放在driver里。
platform总线会对比device里platform_device结构体的name成员 和 platform_driver.driver.name或platform_driver.id_table的值 ,如果相同,平台总线会将device和driver匹配。
device和driver模块不关注谁先加载进内核。
如果发生匹配,会执行platform_driver.probe函数。
一般驱动设计中,会在probe函数中进行:获取device中定义的硬件资源、申请设备号、注册设备、创建设备节点等操作。
代码
device.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
static struct resource pResource[] = {
[0] = {
.name = "ABCD",
.flags = IORESOURCE_MEM
}
};
static struct platform_device pdevice = {
.name = "platform_test", //用于匹配的名字
.id = -1,
.resource = pResource, //资源,一般用于提供给driver
.num_resources = ARRAY_SIZE(pResource) //资源大小
};
static int device_init_led(void){
int ret=0;
ret = platform_device_register(&pdevice); //注册平台设备
if(ret < 0){
printk("platform device regist failed\n");
return -1;
}
return 0;
}
static void device_exit_led(void){
platform_device_unregister(&pdevice);
printk("platform device exit!\n");
}
module_init(device_init_led);
module_exit(device_exit_led);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TAXUE");
driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
int misc_open(struct inode *a,struct file *b){
return 0;
}
int misc_release (struct inode * a, struct file * b){
return 0;
}
ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t){
return 0;
}
ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
return 0;
}
static struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write
};
static struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR, //自动分配从设备号
.name = "hello_led", //设备节点名
.fops = &misc_fops //文件操作集
};
int drProbe(struct platform_device *dev){
struct resource *res;
int ret;
printk("probe here\n");
res = platform_get_resource( dev, IORESOURCE_MEM, 0);
printk("device resource name is %s\n",res->name);
ret = misc_register(&misc_dev); //注册杂项设备
if(ret < 0){
printk("misc regist failed\n");
return -1;
}
printk("misc regist succeed\n");
return 0;
}
int drRemove(struct platform_device *dev){
misc_deregister(&misc_dev);
printk("driver remove\n");
return 0;
}
static struct platform_driver pdrv = {
.probe = drProbe, //匹配成功时执行
.remove = drRemove, //device或driver任一个remove时执行
.driver = {
.name = "platform_test", //用于匹配的名字
.owner = THIS_MODULE
}
};
static int driver_init_led(void){
int ret=0;
ret = platform_driver_register(&pdrv); //注册driver
if(ret < 0){
printk("platform driver regist failed\n");
return -1;
}
return 0;
}
static void driver_exit_led(void){
platform_driver_unregister(&pdrv); //注销driver
printk("platform driver exit!\n");
}
module_init(driver_init_led);
module_exit(driver_exit_led);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TAXUE");
单独加载进kernel后,在/sys/bus/platform/devices或/sys/bus/platform/drivers目录下会有对应的文件夹。
两个模块都加载之后,会看到执行了probe函数创建了一个杂项设备。