一、驱动的分离与分层
1.1、驱动的分隔与分离
通过驱动的分隔,也就是将主机驱动和设备驱动分隔开来,通过总线就行匹配,当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配
的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。
1.2、驱动的分层
驱动分层类似网络的7层模型,不同的层负责不同的内容。
二、platform平台驱动模型
对于总线(bus)、驱动(driver)和设备(device)模型,比如 I2C、SPI、USB 等总线。但是在 SOC 中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?为了解决此问题,Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driver 和 platform_device。
2.1、platform总线
Linux系统内核使用bus_type结构体表示总线,此结构体定义在文include/linux/device.h
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs;
const struct attribute_group **bus_groups; //总线属性
const struct attribute_group **dev_groups; //设备属性
const struct attribute_group **drv_groups; //驱动属性
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
match 函数,此函数很重要,单词 match 的意思就是“匹配、相配”,因此此函数就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。match 函数有两个参数:dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。
platform 总线是 bus_type 的一个具体实例,定义在文件 drivers/base/platform.c
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 平台总线,其中 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);
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
if (of_driver_match_device(dev, drv))
return 1;
if (acpi_driver_match_device(dev, drv))
return 1;
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
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 字段,看看是不是相等,如果相等的话就匹配成功。
对于支持设备树的 Linux 版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,也就是直接比较驱动和设备的 name 字段,毕竟这种方式最简单了。
2.2、platform驱动
platform_driver 结 构 体 表 示 platform 驱 动 , 此 结 构 体 定 义 在 文 件
include/linux/platform_device.h 中。
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 函数,当驱动与设备匹配成功以后 probe 函数就会执行,非常重要的函数!!一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么 probe 就需要自行实现。
id_table 是个表(也就是数组),每个元素的类型为 platform_device_id,
platform_device_id 结构体内容如下struct platform_device_id { char name[PLATFORM_NAME_SIZE]; kernel_ulong_t driver_data; };device_driver 结构体定义在 include/linux/device.h
struct device_driver { const char *name; struct bus_type *bus; struct module *owner; const char *mod_name; bool suppress_bind_attrs; 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 结构体类型,此结构体定义在文件 include/linux/mod_devicetable.h 中
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 函数里面编写,比如字符设备驱动等等。
当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用platform_driver_register 函数向 Linux 内核注册一个 platform 驱动。
int platform_driver_register (struct platform_driver *driver)
还需要在驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动
void platform_driver_unregister(struct platform_driver *drv)
platform驱动框架示例:
struct xxx_dev {
struct cdev cdev;
}
struct xxx_dev xxxdev;
static int xxx_open()
{
return 0;
}
static ssize_t xxx_write()
{
return 0;
}
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.write = xxx_write,
};
//驱动和设备匹配后此函数就会执行
static int xxx_probe(struct platform_device *dev)
{
cdev_init(&xxxdev.cdev, &xxx_fops); //注册字符设备
return 0;
}
//关闭platform驱动的时候此函数就会执行
static int xxx_remove(struct platform_device *dev)
{
cdev_del(&xxxdev.cdev); //删除cdev
return 0;
}
//匹配表
//最后一个匹配项必须为空
static const struct of_device_id xxx_of_match[] = {
{.compatible = "xxx-gpio" },
{ }
};
static struct platform_driver xxx_driver = {
.driver = {
.name = "xxx",
.of_match_table = xxx_of_match,
},
.probe = xxx_probe,
.remove = xxx_remove,
};
static int __init xxxdriver_init(void)
{
return platform_driver_register(&xxx_driver);
}
static void __exit xxxdriver_exit(void)
{
platform_driver_unregister(&xxx_driver);
}
module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zk");
2.3、platform设备
platform_device 这个结构体表示 platform 设备,这里我们要注意,如果内核支持设备树的话就不要再使用 platform_device 来描述设备了,因为改用设备树去描述了。当然了,你如果一定要用 platform_device 来描述设备信息的话也是可以的
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;
struct mfd_cell *mfd_cell;
struct pdev_archdata archdata;
};
name 表示设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为“xxx-gpio”,那么此 name字段也要设置为“xxx-gpio”。
num_resources 表示资源数量,一般为第 28 行 resource 资源的大小。
resource 表示资源,也就是设备信息,比如外设寄存器等在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中
int platform_device_register(struct platform_device *pdev)如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform设备
void platform_device_unregister(struct platform_device *pdev)
三、程序编写
#include#include #include #include #include #include #include #include #include #include #define LEDDEV_CNT 1 #define LEDDEV_NAME "platformled" #define LED_ON 1 #define LED_OFF 0 //映射后的虚拟地址 static void __iomem *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; //设备结构体 struct leddev_dev{ dev_t devid; struct cdev cdev; struct class *class; struct device *device; int major; int minor; }; struct leddev_dev leddev; static void led_switch(uint8_t sta) { uint32_t val; if (sta == LED_ON) { val = readl(GPIO1_DR); val &= ~(1 << 3); writel(val, GPIO1_DR); } else if (sta == LED_OFF) { val = readl(GPIO1_DR); val |= (1 << 3); writel(val, GPIO1_DR); } } static int leddev_open(struct inode *inode, struct file *filp) { filp->private_data = &leddev; printk("leddev open!n"); return 0; } static ssize_t leddev_read(struct file *filp, __user char *buf, size_t count, loff_t *ppos) { printk("leddev readn"); return 0; } static ssize_t leddev_write(struct file *filp, const char __user *buf, size_t count, loff_t *oppos) { int ret; uint8_t data_buf[1]; ret = copy_from_user(data_buf, buf, count); if (ret < 0) { printk("write data failn"); return -EFAULT; } led_switch(data_buf[0]); return 0; } static int leddev_release(struct inode *inode, struct file *filp) { printk("led releasen"); return 0; } static const struct file_operations leddev_fops = { .owner = THIS_MODULE, .open = leddev_open, .read = leddev_read, .write = leddev_write, .release = leddev_release, }; static int leddev_probe(struct platform_device *dev) { int i = 0; unsigned int val = 0; int ressize[5]; struct resource *ledsource[5]; printk("led driver and device has matchrn"); for (i = 0; i < 5; i++) { ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); if (!ledsource[i]) { dev_err(&dev->dev, "NO MEM resource for always onrn"); return -ENXIO; } ressize[i] = resource_size(ledsource[i]); } 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(CCM_CCGR1); val &= ~(3 << 26); val |= 3 << 26; writel(val, CCM_CCGR1); writel(0x05, SW_MUX_GPIO1_IO03); writel(0x10B0, SW_PAD_GPIO1_IO03); val = readl(GPIO1_GDIR); val |= 1 << 3; writel(val, GPIO1_GDIR); val = readl(GPIO1_DR); val &= ~(1 << 3); writel(val, GPIO1_DR); //分配设备号 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); leddev.minor = MINOR(leddev.devid); } printk("leddev major=%d, minor= %drn", leddev.major, leddev.minor); //初始化cdev leddev.cdev.owner = THIS_MODULE; cdev_init(&leddev.cdev, &leddev_fops); //添加cdev cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT); //创建类 leddev.class = class_create(THIS_MODULE, LEDDEV_NAME); //创建设备 leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME); return 0; } static int leddev_remove(struct platform_device *dev) { led_switch(LED_OFF); iounmap(CCM_CCGR1); iounmap(SW_MUX_GPIO1_IO03); iounmap(SW_PAD_GPIO1_IO03); iounmap(GPIO1_DR); iounmap(GPIO1_GDIR); //删除设备的类 device_destroy(leddev.class, leddev.devid); //删除类 class_destroy(leddev.class); //删除设备 cdev_del(&leddev.cdev); //注销设备号 unregister_chrdev_region(leddev.devid, LEDDEV_CNT); } static struct platform_driver led_driver = { .driver = { .name = "imx6ul-led", }, .probe = leddev_probe, .remove = leddev_remove, }; static int __init leddriver_init(void) { return platform_driver_register(&led_driver); } static void __exit leddriver_exit(void) { platform_driver_unregister(&led_driver); printk("newchrdev exitn"); } module_init(leddriver_init); module_exit(leddriver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ZK");
#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 static void led_release(struct device *dev) { printk("led device releasedrn"); } 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 = &led_release, }, .num_resources = ARRAY_SIZE(led_resources), .resource = led_resources, }; static int __init leddevice_init(void) { printk("leddevice initrn"); return platform_device_register(&leddevice); } static void __exit leddevice_exit(void) { printk("newchrdev exitn"); platform_device_unregister(&leddevice); } module_init(leddevice_init); module_exit(leddevice_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ZK");
四、运行测试



