学习目的:
- 理解内核分层、分离设计思想
- 使用内核platform机制编写驱动程序,实现硬件和驱动分离
1、软件分层、分离思想引入
将软件进行分层、分离设计应该是软件工程中最基本的一个思想,前面分析的linux输入子系统的框架就是基于软件分层、分离的思想设计的,今天学习的platform,基于总线-设备-驱动的框架也是基于这种思想设计的。
分层机制子系统自顶向下进行依赖,下层向上提供了统一的接口,每一层专注于做自己的事情,将复杂的问题层层分解实现;分离则是把软件设计过程中比较稳定的部分和需要容易修改的部分分离开,容易修改的那部分变更时只需修改一边就能完成需求,这样使得软件的设计更加灵活。
2、platform机制
2.1 platform机制的概述
platform是在linux2.6内核中开始引入的,提供了一套新的驱动管理和注册的机制,它在内核中的重要的三个数据结构如下:
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 (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
struct platform_device {--------------------------------------->②
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
struct bus_type {---------------------------------------------->③
const char * name;
struct module * owner;
.................
struct klist klist_devices;
struct klist klist_drivers;
..................
int (*match)(struct device * dev, struct device_driver * drv);
int (*uevent)(struct device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
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 (*suspend_late)(struct device * dev, pm_message_t state);
int (*resume_early)(struct device * dev);
int (*resume)(struct device * dev);
unsigned int drivers_autoprobe:1;
};
① platform driver:基于底层的device driver模块,抽象出platform driver,用于驱动platform设备
② platform device:基于底层的device,抽象出platform device,用于描述设备
③ platform bus:基于底层的bus总线,抽象出一条虚拟的platform bus总线,用于挂载platform设备
2.2 platform驱动、设备注册
platform的驱动、设备分别使用platform_driver_register和platform_device_register函数向上注册,注册好的driver和device分别被挂入到platform_bus_type的虚拟总线上。
1、platform_driver_register函数
int platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type;--------------->① if (drv->probe) drv->driver.probe = platform_drv_probe;--------->② if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; if (drv->suspend) drv->driver.suspend = platform_drv_suspend; if (drv->resume) drv->driver.resume = platform_drv_resume; return driver_register(&drv->driver);-------------->③ }
① 设置platform_driver挂接到虚拟平台总线platform_bus_type
② platform_driver继承于device_driver,设置device_driver函数指针的.probe、.remove和platform_driver函数指针.probe、remove指向同一函数,相当于OOB设计中重载,此处会在后面用到
③ 调用driver_register进行后续的注册
接着分析driver_register
int driver_register(struct device_driver * drv) { ... klist_init(&drv->klist_devices, NULL, NULL); return bus_add_driver(drv); ... }
driver_register初始化了传入的device_driver参数的类型成员klist_devices,又调用了bus_add_driver函数
接着分析bus_add_drivers
int bus_add_driver(struct device_driver *drv) { ... if (drv->bus->drivers_autoprobe) {--------------->① error = driver_attach(drv); if (error) goto out_unregister; } ... }
① platform_bus_type总线在注册时成员drivers_autoprobe被设置成1(内核的driversaseplatform.c的入口函数platform_bus_init调用bus_register实现),所以if语句真
② 调用driver_attach函数
接着分析driver_attach
int driver_attach(struct device_driver * drv) { return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); }
driver_attach函数直接调用bus_for_each_dev函数
接着分析bus_for_each_dev
int bus_for_each_dev(struct bus_type * bus, struct device * start, void * data, int (*fn)(struct device *, void *)) { ... klist_iter_init_node(&bus->klist_devices, &i,--------------->① (start ? &start->knode_bus : NULL)); while ((dev = next_device(&i)) && !error)------------------->② error = fn(dev, data); klist_iter_exit(&i); ... }
① 查找总线中klist_devices
② 遍历链表中所有挂接的device,并通过__driver_attach函数
我就继续分析__driver_attach函数
static int __driver_attach(struct device * dev, void * data) { ... if (!dev->driver) driver_probe_device(drv, dev) ... } int driver_probe_device(struct device_driver * drv, struct device * dev) { ... if (drv->bus->match && !drv->bus->match(dev, drv)) goto done; ... ret = really_probe(dev, drv); done: return ret; }
__driver_attach函数中接着调用driver_porbe_device函数,该函数判断driver挂接的bus函数中是否实现了match函数,并调用总线总的match函数,匹配完成后继续调用really_probe函数
platform_bus_type中的match函数
static int platform_match(struct device * dev, struct device_driver * drv) { struct platform_device *pdev = container_of(dev, struct platform_device, dev); return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); }
platform_match函数可以看出paltform总线下的驱动和设备通过比较驱动和设备的名称进行匹配,名称相同即匹配成功
继续分析really_probe函数
static int really_probe(struct device *dev, struct device_driver *drv) { ... dev->driver = drv;-------------->① ... if (dev->bus->probe) {---------->② ret = dev->bus->probe(dev); if (ret) goto probe_failed; } else if (drv->probe) { ret = drv->probe(dev); if (ret) goto probe_failed; } ... }
① 绑定device的驱动程序,由此可以看出每个device中仅有一个支持的驱动程序
② 优先判断dev挂接的总线中是否有probe函数,如果有调用bus中的probe函数,紧接着判断driver中是否实现了probe函数,如果实现了就调用drive中的probe函数。platform_bus_type中未实现probe函数,因此really_probe函数中最终要调用的时device_diver中的probe函数,device_drver中的probe函数在platform_driver_register中设置成和platform_driver的probe函数相同,最终调用的是platform_driver成员中注册的probe函数
2、platform_device_register函数
device函数同platform_driver_register类似,通过层层调用也会调用driver_probe_device,就不同层层的列出代码分析了,它的调用顺序如下:
platform_device_register
|——platform_device_add
|——device_add
|——bus_attach_device
|——device_attach
|——bus_for_each_drv
|——__device_attach
|——driver_probe_device
|——really_probe
通过以上分析,可以总结出platform的device、driver注册流程框图如下所示:
基于platform总线的device和driver实现了驱动和设备分开注册,注册1个dev时侯,并不需要driver已经存在,而1个driver被注册时,也不需要对应的dev已经被注册。dev和driver各自涌向内核,bus总线成为衔接两者之间的纽带。每个dev和driver涌入内核时,都会寻找自己的另一半,如果匹配成功,将调用driver中的probe函数。匹配成功的dev和driver可以通过总线彼此对方的资源。
驱动设计时device部分实现的是硬件相关资源描述,driver部分实现对硬件资源访问的接口
注意:文章中对platform的分析中忽略了复杂的kobect、kset、sysfs等部分,仅仅着重于注册devce、device直接是如何联系的
3、基于platform的驱动程序实现
上面简单分析platform的框架之后,现在开始基于platform机制,编写点灯驱动程序
3.1 驱动device部分实现
1、定义platform_device结构体
static struct platform_device led_dev = { .name = "platform-led",------------------>① .num_resources = ARRAY_SIZE(led_dev_resources),------------>② .resource = &led_dev_resources,---------->② .dev = { .release = led_release, }, };
① 设备名称,总线的match函数可通过比较设备名称将设备和驱动匹配
② 设备资源的描述,对硬件资源的抽象
2、struct resource设备的资源描述
static struct resource led_dev_resources[] = { [0] = { ------------------>① .start = LED_GPIO_REG_START_ADDR, .end = LED_GPIO_REG_START_ADDR + 8 -1, .flags = IORESOURCE_MEM, }, [1] = { ------------------->② .start = 5, .end = 5, .flags = IORESOURCE_IO, }, };
① 主控芯片连接LED的GPIO寄存器的地址资源
② 主控芯片连接LED的GPIO引脚号
这些资源在driver的probe函数中通过platform_get_resource获取,用于实现硬件的初始化
注:在Linux中,系统资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型。这些资源大多具有独占性,不允许多个设备同时使用,因此Linux内核提供了一些API,用于分配、管理这些资源。
当某个设备需要使用某些资源时,只需利用struct
resource组织这些资源(如名称、类型、起始、结束地址等),并保存在该设备的resource指针中即可。然后在设备probe时,设备需求会调用资源管理接口,分配、使用这些资源。而内核的资源管理逻辑,可以判断这些资源是否已被使用、是否可被使用等等。
3、注册、卸载platform_device结构体
在入口函数、出口函数处完成注册和卸载platform_device
static int led_dev_init(void) { platform_device_register(&led_dev); return 0; } static void led_dev_exit(void) { platform_device_unregister(&led_dev); }
3.2 驱动driver部分实现
1、定义platform_driver结构体
static struct platform_driver led_drv = { .probe = led_drv_probe,------------------>① .remove = led_drv_remove,---------------->② .driver = { .name = "platform-led",-------------->③ }, };
① probe函数指针,指向led_drv_probe函数,该函数在driver部分实现。注册时一旦device和driver匹配成功,将调用该指针指向函数
② 卸载时自动调用的函数,该书好在driver部分实现
③ 驱动名称,总线的match函数可通过比较驱动名称将设备和驱动匹配
2、led_drv_probe函数
static int led_drv_probe(struct platform_device *pdev) { struct resource *pres; pres = platform_get_resource(pdev, IORESOURCE_MEM, 0);--------------------->① gpio_con = ioremap(pres->start, (pres->end - pres->start + 1));------------>② gpio_dat = gpio_con + 1; pres = platform_get_resource(pdev, IORESOURCE_IO, 0); pin_num = pres->start; major = register_chrdev(0, "led_drv", &led_drv_fops);---------------------->③ led_drv_class = class_create(THIS_MODULE, "led_drv_class");---------------->③ led_drv_class_dev = device_create(led_drv_class, NULL, MKDEV(major, 0), NULL, "led"); return 0; }
① 调用platform_get_resource函数获取匹配设备中的描述硬件的资源
② GPIO寄存器物理地址到虚拟地址的映射
③ 注册字符设备
④ 创建led_drv_class,并在该类中创建设备,mdev机制根据这些信息在/dev目录下自动创建设备节点
3、file_operations结构体
static int led_drv_open(struct inode *inode, struct file *file) { *gpio_con &= ~(3 << pin_num * 2); *gpio_con |= (1 << pin_num * 2); return 0; } static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int status; if(count != 4) return -EINVAL; copy_from_user(&status, buf, count); if(status == 1) { *gpio_dat &= ~(1 << pin_num); } else if(status == 0) { *gpio_dat |= (1 << pin_num); } return count; } static struct file_operations led_drv_fops = { .open = led_drv_open,--------------------->① .write = led_drv_write,------------------->② };
file_operations结构体中只实现了open函数write函数,open函数中根据probe函数中获取的device中的硬件资源,初始化设备,write函数根据系统调用传入的命令设置GPIO引脚电平,控制led的点亮和熄灭
4、入口和出口函数
static int led_drv_init(void) { platform_driver_register(&led_drv); return 0; } static void led_drv_exit(void) { platform_driver_unregister(&led_drv); }
入口和出口函数中,调用platform_driver_register、platform_driver_unregister向内核注册和卸载led的平台设备驱动程序
4、测试
1)如下图,我们先加载device模块,和我们之前分析的一样,它在platform/devices目录下生成一个"platform-led"设备
2)如下图,我们再来挂载drv驱动模块,同样的在platform/drivers目录下生成一个"platform-led"目录
3)运行测试应用程序,能控制led正常点亮和熄灭
完整驱动和测试程序
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/irq.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <plat/gpio-fns.h> #include <mach/gpio-nrs.h> #include <linux/interrupt.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/device.h> #include <linux/gpio.h> #include <linux/poll.h> #include <linux/input.h> #include <linux/platform_device.h> int major; int pin_num; static unsigned long volatile *gpio_con = NULL; static unsigned long volatile *gpio_dat = NULL; static struct class *led_drv_class; static struct class_device *led_drv_class_dev; static int led_drv_open(struct inode *inode, struct file *file) { *gpio_con &= ~(3 << pin_num * 2); *gpio_con |= (1 << pin_num * 2); return 0; } static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int status; if(count != 4) return -EINVAL; copy_from_user(&status, buf, count); if(status == 1) { *gpio_dat &= ~(1 << pin_num); } else if(status == 0) { *gpio_dat |= (1 << pin_num); } return count; } static struct file_operations led_drv_fops = { .open = led_drv_open, .write = led_drv_write, }; static int led_drv_probe(struct platform_device *pdev) { struct resource *pres; pres = platform_get_resource(pdev, IORESOURCE_MEM, 0); gpio_con = ioremap(pres->start, (pres->end - pres->start + 1)); gpio_dat = gpio_con + 1; pres = platform_get_resource(pdev, IORESOURCE_IO, 0); pin_num = pres->start; major = register_chrdev(0, "led_drv", &led_drv_fops); led_drv_class = class_create(THIS_MODULE, "led_drv_class"); led_drv_class_dev = device_create(led_drv_class, NULL, MKDEV(major, 0), NULL, "led"); return 0; } static int led_drv_remove(struct platform_device *pdev) { device_unregister(led_drv_class_dev); class_destroy(led_drv_class); unregister_chrdev(major, "led_drv"); iounmap(gpio_con); } static struct platform_driver led_drv = { .probe = led_drv_probe, .remove = led_drv_remove, .driver = { .name = "platform-led", }, }; static int led_drv_init(void) { platform_driver_register(&led_drv); return 0; } static void led_drv_exit(void) { platform_driver_unregister(&led_drv); } module_init(led_drv_init); module_exit(led_drv_exit); MODULE_LICENSE("GPL");
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/irq.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <plat/gpio-fns.h> #include <mach/gpio-nrs.h> #include <linux/interrupt.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/device.h> #include <linux/gpio.h> #include <linux/poll.h> #include <linux/input.h> #include <linux/platform_device.h> #define LED_GPIO_REG_START_ADDR 0x56000050 static void led_release(struct device * dev); static struct resource led_dev_resources[] = { [0] = { .start = LED_GPIO_REG_START_ADDR, .end = LED_GPIO_REG_START_ADDR + 8 -1, .flags = IORESOURCE_MEM, }, [1] = { .start = 5, .end = 5, .flags = IORESOURCE_IO, }, }; static struct platform_device led_dev = { .name = "platform-led", .num_resources = ARRAY_SIZE(led_dev_resources), .resource = &led_dev_resources, .dev = { .release = led_release, }, }; static void led_release(struct device * dev) { } static int led_dev_init(void) { platform_device_register(&led_dev); return 0; } static void led_dev_exit(void) { platform_device_unregister(&led_dev); } module_init(led_dev_init); module_exit(led_dev_exit); MODULE_LICENSE("GPL");
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/fcntl.h> #include <stdlib.h> #include <stdio.h> void help_info(void) { printf("LED Contrl Function: "); printf("s---->Turn On "); printf("o---->Trun Off "); printf("q---->Quit "); printf("h---->Help Info "); } int main(int argc, char **argv) { int fd, val, old_val = -1; char com; fd = open("/dev/led", O_RDWR); if(fd == -1) { printf("can't open... "); exit(EXIT_FAILURE); } help_info(); printf("----> "); while(1) { com = getchar(); switch(com) { case 's': val = 1; break; case 'o': val = 0; break; case 'q': exit(EXIT_SUCCESS); case 'h': help_info(); break; case ' ': printf("----> "); break; default: break; } if(val != old_val) { write(fd, &val, 4); old_val = val; } } exit(EXIT_SUCCESS); }