前言
编写字符设备驱动基本上都要实现以下内容:
1.实现入口函数xxx_init()和卸载函数xxx_exit()
2.申请设备号register_chrdev(与内核有关)
3.注册字符设备驱动cdev_alloc cdev_init cdev_add(与内核有关)
4.利用udev/mdev机制创建设备文件(节点),
class_create,device_create(与内核有关)
5.硬件部分初始化
io资源映射ioremap,内核提供gpio库函数(与硬件相关)
注册中断(与硬件相关)
初始化等待队列(与内核有关)
初始化定时器(与内核有关)
6.构建file_operation结构(与内核相关)
实现操作硬件方法xxx_open,xxx_read,xxx_write…(与硬件有关)
如果我们编写不同的字符设备驱动,要实现上述内容,基本上就是硬件相关部分不一样,其他部分基本上去都是“体力活”,为了减少体力活,我们可以将代码重用,就是“体力活”部分重复用,每次实现不同设备字符设备驱动实现硬件部分就行,这就是驱动的分离与分层思想,实现这一思想,最常用的就是platform框架。这是一个虚拟的总线。
一、驱动和设备的匹配
platform的match 函数,此函数很重要,单词 match 的意思就是“匹配、相配”,因此此函数就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。 match 函数有两个参数: dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。
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); /* 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); }
第一种匹配方式, OF 类型的匹配,也就是设备树采用的匹配方式,of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。 device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行。
第二种匹配方式, ACPI 匹配方式。
第三种匹配方式, id_table 匹配,每个 platform_driver 结构体有一个 id_table成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。
第四种匹配方式,如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。
二、platform调用关系
platform_device_register platform_device_add device_add bus_add_device // 放入链表 bus_probe_device // probe 枚举设备,即找到匹配的(dev, drv) device_initial_probe __device_attach bus_for_each_drv(...,__device_attach_driver,...) __device_attach_driver driver_match_device(drv, dev) // 是否匹配 driver_probe_device // 调用 drv 的 probe platform_driver_register __platform_driver_register driver_register bus_add_driver // 放入链表 driver_attach(drv) bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); __driver_attach driver_match_device(drv, dev) // 是否匹配 driver_probe_device // 调用 drv 的 probe
三、platform相关结构体
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; };
在编写驱动时,我们要实现probe函数和remove函数。probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行。driver 成员,为 device_driver 结构体变量, Linux 内核里面大量使用到了面向对象的思维, device_driver 相当于基类,提供了最基础的驱动框架。 plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量。id_table 表,也就是我们上一小节讲解 platform 总线匹配驱动和设备的时候采用的第三种方法,id_table 是个表(也就是数组),每个元素的类型为 platform_device_id,platform_device_id 结构体内容如下:
struct platform_device_id { char name[PLATFORM_NAME_SIZE]; kernel_ulong_t driver_data; };
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; };
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函数就会执行,具体的驱动程序在 probe 函数里面编写,比如字符设备驱动等等。
四、常用函数
int platform_driver_register (struct platform_driver *driver)
driver:要注册的 platform 驱动。
返回值: 负数,失败; 0,成功。
void platform_driver_unregister(struct platform_driver *drv)
drv:要卸载的 platform 驱动。
返回值: 无。
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
dev:平台设备。描述设备信息的,有设备树描述。
type: 资源类型。下文详细讲解。
num: 资源索引。同类型资源进行重新编号后的下标编号,
struct resource *platform_get_resource_byname(struct platform_device *dev, unsigned int type, const char *name)
dev:平台设备。描述设备信息的,有设备树描述。
type: 资源类型。下文详细讲解。
num: 资源索引。同类型资源进行重新编号后的下标编号,
附录
device.c
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/irq.h> #include <linux/wait.h> #include <linux/poll.h> #include <linux/fs.h> #include <linux/fcntl.h> #include <linux/platform_device.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> /* * 寄存器地址定义 */ #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 /* @description : 释放flatform设备模块的时候此函数会执行 * @param - dev : 要释放的设备 * @return : 无 */ static void led_release(struct device *dev) { printk("led device released!\r\n"); } /* * 设备资源信息,也就是LED0所使用的所有寄存器 */ 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, }, }; /* * platform设备结构体 */ static struct platform_device leddevice = { .name = "imx6ul-led", .id = -1, .dev = { .release = &led_release, }, .num_resources = ARRAY_SIZE(led_resources), .resource = led_resources, }; /* * @description : 设备模块加载 * @param : 无 * @return : 无 */ static int __init leddevice_init(void) { return platform_device_register(&leddevice); } /* * @description : 设备模块注销 * @param : 无 * @return : 无 */ static void __exit leddevice_exit(void) { platform_device_unregister(&leddevice); } module_init(leddevice_init); module_exit(leddevice_exit); MODULE_LICENSE("GPL");
driver.c
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/irq.h> #include <linux/wait.h> #include <linux/poll.h> #include <linux/fs.h> #include <linux/fcntl.h> #include <linux/platform_device.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #define LEDDEV_CNT 1 /* 设备号长度 */ #define LEDDEV_NAME "platled" /* 设备名字 */ #define LEDOFF 0 #define LEDON 1 /* 寄存器名 */ static void __iomem *IMX6U_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; /* leddev设备结构体 */ struct leddev_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ }; struct leddev_dev leddev; /* led设备 */ /* * @description : LED打开/关闭 * @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED * @return : 无 */ void led0_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); } } /* * @description : 打开设备 * @param - inode : 传递给驱动的inode * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 * 一般在open的时候将private_data指向设备结构体。 * @return : 0 成功;其他 失败 */ static int led_open(struct inode *inode, struct file *filp) { filp->private_data = &leddev; /* 设置私有数据 */ return 0; } /* * @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符 * @param - buf : 要写给设备写入的数据 * @param - cnt : 要写入的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 写入的字节数,如果为负值,表示写入失败 */ static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { int retvalue; unsigned char databuf[1]; unsigned char ledstat; retvalue = copy_from_user(databuf, buf, cnt); if(retvalue < 0) { return -EFAULT; } ledstat = databuf[0]; /* 获取状态值 */ if(ledstat == LEDON) { led0_switch(LEDON); /* 打开LED灯 */ }else if(ledstat == LEDOFF) { led0_switch(LEDOFF); /* 关闭LED灯 */ } return 0; } /* 设备操作函数 */ static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .write = led_write, }; /* * @description : flatform驱动的probe函数,当驱动与 * 设备匹配以后此函数就会执行 * @param - dev : platform设备 * @return : 0,成功;其他负值,失败 */ static int led_probe(struct platform_device *dev) { int i = 0; int ressize[5]; u32 val = 0; struct resource *ledsource[5]; printk("led driver and device has matched!\r\n"); /* 1、获取资源 */ for (i = 0; i < 5; i++) { ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM类型资源 */ if (!ledsource[i]) { dev_err(&dev->dev, "No MEM resource for always on\n"); return -ENXIO; } ressize[i] = resource_size(ledsource[i]); } /* 2、初始化LED */ /* 寄存器地址映射 */ IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]); SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]); SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]); GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]); GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]); val = readl(IMX6U_CCM_CCGR1); val &= ~(3 << 26); /* 清除以前的设置 */ val |= (3 << 26); /* 设置新值 */ writel(val, IMX6U_CCM_CCGR1); /* 设置GPIO1_IO03复用功能,将其复用为GPIO1_IO03 */ writel(5, SW_MUX_GPIO1_IO03); writel(0x10B0, SW_PAD_GPIO1_IO03); /* 设置GPIO1_IO03为输出功能 */ val = readl(GPIO1_GDIR); val &= ~(1 << 3); /* 清除以前的设置 */ val |= (1 << 3); /* 设置为输出 */ writel(val, GPIO1_GDIR); /* 默认关闭LED1 */ val = readl(GPIO1_DR); val |= (1 << 3) ; writel(val, GPIO1_DR); /* 注册字符设备驱动 */ /*1、创建设备号 */ if (leddev.major) { /* 定义了设备号 */ leddev.devid = MKDEV(leddev.major, 0); register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME); } else { /* 没有定义设备号 */ alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME); /* 申请设备号 */ leddev.major = MAJOR(leddev.devid); /* 获取分配号的主设备号 */ } /* 2、初始化cdev */ leddev.cdev.owner = THIS_MODULE; cdev_init(&leddev.cdev, &led_fops); /* 3、添加一个cdev */ cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT); /* 4、创建类 */ leddev.class = class_create(THIS_MODULE, LEDDEV_NAME); if (IS_ERR(leddev.class)) { return PTR_ERR(leddev.class); } /* 5、创建设备 */ leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME); if (IS_ERR(leddev.device)) { return PTR_ERR(leddev.device); } return 0; } /* * @description : platform驱动的remove函数,移除platform驱动的时候此函数会执行 * @param - dev : platform设备 * @return : 0,成功;其他负值,失败 */ static int led_remove(struct platform_device *dev) { iounmap(IMX6U_CCM_CCGR1); iounmap(SW_MUX_GPIO1_IO03); iounmap(SW_PAD_GPIO1_IO03); iounmap(GPIO1_DR); iounmap(GPIO1_GDIR); cdev_del(&leddev.cdev);/* 删除cdev */ unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */ device_destroy(leddev.class, leddev.devid); class_destroy(leddev.class); return 0; } /* platform驱动结构体 */ static struct platform_driver led_driver = { .driver = { .name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */ }, .probe = led_probe, .remove = led_remove, }; /* * @description : 驱动模块加载函数 * @param : 无 * @return : 无 */ static int __init leddriver_init(void) { return platform_driver_register(&led_driver); } /* * @description : 驱动模块卸载函数 * @param : 无 * @return : 无 */ static void __exit leddriver_exit(void) { platform_driver_unregister(&led_driver); } module_init(leddriver_init); module_exit(leddriver_exit); MODULE_LICENSE("GPL");