在Linux操作系统中,驱动程序占用了Linux内核代码量的大头,如果不进行管理,将会造成数量庞大的结果。因此引入了驱动的分离。
上图所示就是驱动的分离,相当于驱动使用标准方法去获取到设备信息(比如从设备树中获取到设备信息),然后根据获取到的设备信息来初始化设备。 这样就相当于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可。这个就是 Linux 中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离。总线就是驱动和设备信息的月老,负责给两者牵线搭桥。
为了允许没有总线模型概念的外设使用总线(bus)、驱动(driver)和设备(device)模型。Linux系统提出了platform 这个虚拟总线。
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
platform_bus_type就是platform平台总线,其中最关键的就是.match = platform_match,总线就是通过platform_match函数完成设备与驱动之间的匹配的!
platform_match具体定义如下:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
platform_match函数中包含了4中驱动和设备匹配的方法。分别为:
①设备树匹配的方法
②ACPI匹配方式
③id_table匹配方式
④通过驱动和设备.name匹配
并且四种方式的优先级为:①>②>③>④。主要学习方式①和④。
platform平台设备驱动,它只不过是将 device 进一步封装成为 platform_device,将 device_driver 进一步封装成为 platform_device_driver。
要想使用platform平台驱动,需要定义在设备中定义 platform_device结构体(如果采用设备数匹配方式的话则不用定义设备结构体),在驱动中定义platform_driver结构体。
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
int (*probe)(struct platform_device *)表示probe函数指针,当驱动与设备匹配成功以后 probe 函数就会执行。
struct device_driver driver表示device_driver结构体,在此处定义类似于C++中的继承特性,即platform_driver结构体继承了device_driver结构体的成员变量。device_driver结构体具体定义如下:
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
name表示设备的名字,需要和设备中使用的name相匹配。
probe 函数,当设备和驱动匹配以后此函数就会执行。
remove 函数,当卸载platform驱动的时候此函数就会执行。
of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹
配项都为 of_device_id 结构体类型。定义如下:
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
compatible,对于设备树而言,就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。
使用platform 驱动的时候,首先定义一个 platform_driver 结构体变量,然后实现结构体
中的各个成员变量,重点是实现匹配方法以及 probe 函数。当驱动和设备匹配成功以后 probe
函数就会执行。
当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用
platform_driver_register 函数向 Linux 内核注册一个 platform 驱动。
int platform_driver_register (struct platform_driver *driver)
函数参数和返回值含义如下:
driver:要注册的 platform 驱动。
返回值: 负数,失败; 0,成功
驱动卸载函数中通过platform_driver_unregister函数卸载 platform 驱动。
void platform_driver_unregister(struct platform_driver *drv)
函数参数和返回值含义如下:
drv:要卸载的 platform 驱动。
返回值: 无
struct platform_device {
const char *name;
int id;
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;
};
name表示设备的名字,需要和驱动中使用的name相匹配,方式④就是通过name来完成驱动和设备的匹配的。
num_resources表示struct resource *resource结构体指针指向的资源的大小。
struct resource *resource为结构体指针,指向设备信息。具体定义如下:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止
地址,name 表示资源名字,flags表示资源类型。
在不使用方式①设备树,利用方式④匹配方法中,需要使用两个函数完成platform_device设备的注册和注销。
int platform_device_register(struct platform_device *pdev)
函数参数和返回值含义如下:
pdev:要注册的 platform 设备。
返回值: 负数,失败; 0,成功。
void platform_device_unregister(struct platform_device *pdev)
函数参数和返回值含义如下:
pdev:要注销的 platform 设备。
返回值: 无
platform_driver驱动代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LEDON 1 /*开灯*/
#define LEDOFF 0 /*关灯*/
#define PLATFORMLED_NAME "platformled"
#define PLATFORMLED_COUNT 1
struct newchrled_dev newchrled; /*led设备*/
/*映射后虚拟地址指针*/
static void __iomem *IMX6ULL_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/*LED设备结构体*/
struct newchrled_dev{
struct cdev cdev; /*字符设备结构体*/
dev_t devid; /*定义设备号*/
struct class *class; /*类*/
struct device *device; /*创建设备*/
int major; /*定义主设备号*/
int minor; /*定义次设备号*/
};
static void LED_switch(u8 sta){
u32 val = 0;
if(sta == LEDON){
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
}
else if(sta == LEDOFF){
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
}
}
static int newchrled_open(struct inode *inode, struct file *filp){
filp->private_data = &newchrled;
return 0;
}
static ssize_t newchrled_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos){
int retvalue;
unsigned char databuf[1];
retvalue = copy_from_user(databuf, buf, count);
if(retvalue < 0){
printk("kernel write error \r\n");
return -EFAULT;
}
/*开灯还是关灯*/
LED_switch(databuf[0]);
return 0;
}
static int newchrled_release(struct inode *inode, struct file *filp){
return 0;
}
static const struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.write = newchrled_write,
.open = newchrled_open,
.release = newchrled_release,
};
static int led_probe(struct platform_device *dev){
int ret = 0;
unsigned int val = 0;
int i;
struct resource *ledsource[5];
/*初始化led*/
/*1、从设备中获取资源*/
for(i = 0;i < 5;i++){
ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
if(ledsource[i] == NULL)
return -EINVAL;
}
/*led初始化*/
/*2、地址映射*/
IMX6ULL_CCM_CCGR1 = ioremap(ledsource[0]->start, resource_size(ledsource[0]));
SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, resource_size(ledsource[1]));
SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, resource_size(ledsource[2]));
GPIO1_DR = ioremap(ledsource[3]->start, resource_size(ledsource[3]));
GPIO1_GDIR = ioremap(ledsource[4]->start, resource_size(ledsource[4]));
/*3、初始化*/
/*开启时钟*/
val = readl(IMX6ULL_CCM_CCGR1);
val &= ~(3 << 26); //清除先前的26,27位
val |= (3 << 26); //26,27位置1
writel(val, IMX6ULL_CCM_CCGR1);
/*配置GPIO1_IO03*/
writel(0x5, SW_MUX_GPIO1_IO03); //设置复用
writel(0x10B0, SW_PAD_GPIO1_IO03); //设置电气属性
val = readl(GPIO1_GDIR);
val |= (1 << 3);
writel(val, GPIO1_GDIR);
/*注册字符设备*/
if(newchrled.major){ /*给定主设备号*/
newchrled.devid = MKDEV(newchrled.major, 0); /*获取设备号*/
ret = register_chrdev_region(newchrled.devid, PLATFORMLED_COUNT, PLATFORMLED_NAME);
}
else{ /*s否则自动申请设备号*/
ret = alloc_chrdev_region(&newchrled.devid, 0, PLATFORMLED_COUNT, PLATFORMLED_NAME);
newchrled.major = MAJOR(newchrled.devid);
newchrled.minor = MINOR(newchrled.devid);
}
if(ret < 0){
printk("newchrled register error!\r\n");
goto fail_devid;
}
printk("newchrled major = %d, minor = %d \r\n", newchrled.major, newchrled.minor);//打印主次设备号
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
ret = cdev_add(&newchrled.cdev, newchrled.devid, PLATFORMLED_COUNT);
if(ret < 0){
goto fail_cdev;
}
/*自动创建设备节点*/
newchrled.class = class_create(THIS_MODULE, PLATFORMLED_NAME);
if(IS_ERR(newchrled.class)){
ret = PTR_ERR(newchrled.class);
goto fail_class;
}
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, PLATFORMLED_NAME);
if(IS_ERR(newchrled.device)){
ret = PTR_ERR(newchrled.device);
goto fail_device;
}
return 0;
fail_device:
class_destroy(newchrled.class);
fail_class:
cdev_del(&newchrled.cdev);
fail_cdev:
unregister_chrdev_region(newchrled.devid, PLATFORMLED_COUNT);
fail_devid:
return ret;
}
static int led_remove(struct platform_device *dev){
unsigned int val = 0;
/*关闭LED*/
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
/*取消地址映射*/
iounmap(IMX6ULL_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/*删除字符设备*/
cdev_del(&newchrled.cdev);
/*注销设备号*/
unregister_chrdev_region(newchrled.devid, PLATFORMLED_COUNT);
/*销毁设备*/
device_destroy(newchrled.class, newchrled.devid);
/*销毁类*/
class_destroy(newchrled.class);
return 0;
}
/*platform结构体*/
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-led",
},
.probe = led_probe,
.remove = led_remove,
};
/*驱动加载函数*/
static int __init leddriver_init(void){
/*注册platform驱动*/
return platform_driver_register(&led_driver);
}
/*驱动卸载函数*/
static void __exit leddriver_exit(void){
/*卸载platform驱动*/
platform_driver_unregister(&led_driver);
}
/*驱动入口和出口*/
module_init(leddriver_init);
module_exit(leddriver_exit);
/*协议*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYC");
platform_device驱动代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*寄存器基地址*/
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/*寄存器地址长度*/
#define REGISTER_LENGTH 4
void leddevice_release(struct device *dev){
printk("leddevice release\r\n");
}
static struct resource led_resources[] = {
[0] = {
.start = CCM_CCGR1_BASE,
.end = CCM_CCGR1_BASE + REGISTER_LENGTH - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = SW_MUX_GPIO1_IO03_BASE,
.end = SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
.flags = IORESOURCE_MEM,
},
[2] = {
.start = SW_PAD_GPIO1_IO03_BASE,
.end = SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
.flags = IORESOURCE_MEM,
},
[3] = {
.start = GPIO1_DR_BASE,
.end = GPIO1_DR_BASE + REGISTER_LENGTH - 1,
.flags = IORESOURCE_MEM,
},
[4] = {
.start = GPIO1_GDIR_BASE,
.end = GPIO1_GDIR_BASE + REGISTER_LENGTH - 1,
.flags = IORESOURCE_MEM,
},
};
static struct platform_device leddevice = {
.name = "imx6ul-led",
.id = -1,
.dev = {
.release = &leddevice_release,
},
.num_resources = ARRAY_SIZE(led_resources),
.resource = led_resources,
};
static int __init leddevice_init(void){
/*注册platform设备*/
return platform_device_register(&leddevice);
}
static void __exit leddevice_exit(void){
/*卸载platform设备*/
platform_device_unregister(&leddevice);
}
/*驱动入口和出口*/
module_init(leddevice_init);
module_exit(leddevice_exit);
/*协议*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYC");
代码流程:①注册platform_driver②注册platform_device(①和②顺序可以互换)
③.name是否匹配?执行.probe函数进行初始化:选择其他匹配方法;
④卸载驱动执行.remove函数。
在使用设备树的时候,只需要配置platform_driver驱动,不用配置platform_device。platform 驱动会通过of_match_table来保存兼容性值,也就是表明此驱动兼容哪些设备。
实验代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GPIOLED_COUNT 1
#define GPIOLED_NAME "dtsplatformled"
#define LEDOFF 0
#define LEDON 1
/*gpio设备结构体*/
struct gpioled_dev{
dev_t devid; /*设备号*/
int major; /*主设备号*/
int minor; /*次设备号*/
struct cdev cdev;
struct class *class; /*创建类*/
struct device *device; /*创建设备*/
struct device_node *node; /*设备节点*/
int led_gpio;
};
struct gpioled_dev gpioled;
static int gpioled_open(struct inode *inode, struct file *filp){
filp->private_data = &gpioled; /* 设置私有数据 */
return 0;
}
static ssize_t gpioled_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char databuf[1];
ret = copy_from_user(databuf, buf, cnt);
if(ret < 0){
return -EINVAL;
}
if(databuf[0] == LEDON){
gpio_set_value(gpioled.led_gpio, 0);
}
else if(databuf[0] == LEDOFF){
gpio_set_value(gpioled.led_gpio, 1);
}
return 0;
}
static int gpioled_release(struct inode *inode, struct file *filp){
return 0;
}
/*定义字符操作集*/
static const struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.write = gpioled_write,
.open = gpioled_open,
.release = gpioled_release,
};
static int led_probe(struct platform_device *dev){
int ret = 0;
printk("led_probe! \r\n");
gpioled.major = 0; /*linux内核自动申请设备号*/
/*注册字符设备驱动*/
if(gpioled.major){ /*给定主设备号*/
gpioled.devid = MKDEV(gpioled.major, 0);
ret = register_chrdev_region(gpioled.devid, GPIOLED_COUNT, GPIOLED_NAME);
}
else{ /*没给定设备号*/
ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_COUNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid); /*保存主设备号*/
gpioled.minor = MINOR(gpioled.devid); /*保存次设备号*/
}
if(ret < 0){
goto failed_devid;
}
printk("gpioled major = %d ,minor = %d \r\n",gpioled.major,gpioled.minor);
/*初始化cdev*/
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);
/*添加cdev*/
ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_COUNT);
if(ret < 0){
goto failed_cdev;
}
/*自动创建设备节点*/
/*创建类*/
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)){ /*判断是否创建类成功*/
ret = PTR_ERR(gpioled.class);
goto failed_class;
}
/*创建设备*/
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)){ /*判断是否创建类成功*/
ret = PTR_ERR(gpioled.device);
goto failed_device;
}
/*获取设备节点*/
gpioled.node = of_find_node_by_path("/gpioled");
if(gpioled.node == NULL){ /*寻找节点失败*/
ret = -EINVAL;
goto failed_findnode;
}
/*获取led所对应的gpio*/
gpioled.led_gpio = of_get_named_gpio(gpioled.node, "led-gpios", 0);
if(gpioled.led_gpio < 0){
printk("can't find led gpio \r\n");
ret = -EINVAL;
goto failed_findnode;
}
printk("led gpio num = %d \r\n",gpioled.led_gpio);
/*申请gpio*/
ret = gpio_request(gpioled.led_gpio, "led-gpios");
if(ret){
printk("Failed to request gpio \r\n");
ret = -EINVAL;
goto failed_findnode;
}
/*使用IO,申请为输出*/
ret = gpio_direction_output(gpioled.led_gpio, 1); /*设置为输出,高电平不点亮*/
if(ret < 0){
goto failed_setoutput;
}
/*输出低电平,点亮gpio*/
gpio_set_value(gpioled.led_gpio, 0);
return 0;
failed_setoutput:
gpio_free(gpioled.led_gpio);
failed_findnode:
device_destroy(gpioled.class, gpioled.devid);
failed_device:
class_destroy(gpioled.class);
failed_class:
cdev_del(&gpioled.cdev);
failed_cdev:
unregister_chrdev_region(gpioled.devid, GPIOLED_COUNT);
failed_devid:
return ret;
return 0;
}
static int led_remove(struct platform_device *dev){
printk("led_removes! \r\n");
/*关灯*/
/*输出高电平,关闭gpio*/
gpio_set_value(gpioled.led_gpio, 1);
/*注销字符设备驱动*/
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, GPIOLED_COUNT);
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
/*释放IO*/
gpio_free(gpioled.led_gpio);
return 0;
}
struct of_device_id led_of_match[] = {
{.compatible = "alientek,gpioled"},
{/* Sentinel */},
};
struct platform_driver led_driver = {
.driver ={
.name = "imx6ul-led", /*无设备树的时候,会和设备里面platform_device->driver->name匹配*/
.of_match_table = led_of_match, /*设备树匹配表*/
},
.probe = led_probe,
.remove = led_remove,
};
/*驱动加载函数*/
static int __init leddriver_init(void){
/*注册platform驱动*/
platform_driver_register(&led_driver);
return 0;
}
/*驱动卸载函数*/
static void __exit leddriver_exit(void){
platform_driver_unregister(&led_driver);
}
/*驱动入口和出口*/
module_init(leddriver_init);
module_exit(leddriver_exit);
/*协议*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYC");
代码流程:①注册platform_driver ②.compatible是否匹配?执行.probe函数进行初始化:选择其他匹配方法;③卸载驱动执行.remove函数。
platform平台设备驱动是基于设备总线驱动模型的,它只不过是将 device 进一步封装成为 platform_device,将 device_driver 进一步封装成为 platform_device_driver,将我们之前学过的驱动代码套了一层壳。