设备驱动模型和sysfs
Linux设备驱动开发流程
- 实现入口函数xxx_init()和卸载函数xxx_exit()
- 申请设备号 register_chrdev
- 利用udev/mdev机制创建设备文件(节点)class_create,device_create
- 硬件部分初始化
io资源映射ioremap,内核提供gpio库函数 注册中断 - 构建file_operations结构
- 实现操作硬件方法xxx_open,xxx_read,xxx_write
思考:当我们写相似设备的驱动代码时,有大量代码的重复,比如上述步骤中除红色字体部分的步骤。 措施:我们可以把通用代码(上述黑色字体步骤)写一份,每一个设备写不同的差异化代码(上述红色字体步骤),就能实现部分代码的重用。
我们可以设想一个设备驱动模型
Device:代表设备对象(描述地址、中断等信息) Driver:代表设备驱动对象 (申请设备号,创建节点,实现硬件操作方法……) Bus:代表总线对象(进行device和driver的匹配,device和driver名字进行匹配)
这三个对象,可以在根文件系统/sys目录下可以看到Buses,Device,Class三个文件夹 Sysfs文件系统拓扑图,一个设备信息可以通过总线去查看,也可以通过类(classes)去查看 进入sys/目录
book@ubuntu:/$ cd sys/
book@ubuntu:/sys$ ls
block class devices fs kernel power
bus dev firmware hypervisor module
sys文件系统是告诉用户,内核驱动的信息, 进入class目录下
book@ubuntu:/sys/class$ cd input/
book@ubuntu:/sys/class/input$ ls
event0 event2 event4 input1 input4 mice mouse1
event1 event3 input0 input3 input5 mouse0 mouse2
book@ubuntu:/sys/class/input$ cd event0
book@ubuntu:/sys/class/input/event0$ ls
dev device power subsystem uevent
book@ubuntu:/sys/class/input/event0$ cat uevent
MAJOR=13
MINOR=64
DEVNAME=input/event0
book@ubuntu:/sys/class/input/event0$
book@ubuntu:/sys/class/input/event0/device$ cat name
Power Button
book@ubuntu:/sys/class/input/event0/device$
通过以上指令可以查看设备驱动的信息(主设备号,次设备号,名字等)。 进入bus目录下
book@ubuntu:/sys$ cd bus/
book@ubuntu:/sys/bus$ cd platform/
book@ubuntu:/sys/bus/platform$ ls
devices drivers drivers_autoprobe drivers_probe uevent
book@ubuntu:/sys/bus/platform$ cd devices/
book@ubuntu:/sys/bus/platform/devices$ ls
ACPI0003:00 Fixed MDIO bus.0 pcspkr PNP0001:00 reg-dummy
alarmtimer i8042 platform-framebuffer.0 PNP0800:00 serial8250
book@ubuntu:/sys/bus/platform/devices$
总线中管理两条链表,devices链表和drivers链表,总线进行匹配,匹配成功后调用driver中的probe方法,取消匹配会调用remove方法。
平台总线模型
编写bus总线对象
struct bus_type
struct bus_type {
const char * name;
struct module * owner;
struct kset subsys;
struct kset drivers;
struct kset devices;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
struct bus_attribute * bus_attrs;
struct device_attribute * dev_attrs;
struct driver_attribute * drv_attrs;
struct bus_attribute drivers_autoprobe_attr;
struct bus_attribute drivers_probe_attr;
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;
};
注册和注销总线
int bus_register(struct bus_type * bus);
void bus_unregister(struct bus_type * bus);
最基本的总线模块框架
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
struct bus_type mybus = {
.name = "mybus",
};
EXPORT_SYMBOL(mybus);
static int __int mybus_init(void){
bus_register(&mybus)
if(ret != 0){
printk("bus_register error\n");
return ret;
}
return 0;
}
static void __exit mybus_exit(void){
bus_unregister(&mybus);
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
执行make指令,insmod指令,可以看到/sys/bus/下出现mybus目录,但目录中的devbices和drivers目录中并没有内容。
创建device对象
device对象,对象中包含描述设备信息(地址,中断号,自定义数据)
struct device {
struct klist klist_children;
struct klist_node knode_parent;
struct klist_node knode_driver;
struct klist_node knode_bus;
struct device *parent;
struct kobject kobj;
char bus_id[BUS_ID_SIZE];
struct device_type *type;
unsigned is_registered:1;
unsigned uevent_suppress:1;
struct device_attribute uevent_attr;
struct device_attribute *devt_attr;
struct semaphore sem;
struct bus_type * bus;
struct device_driver *driver;
void *driver_data;
void *platform_data;
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node;
#endif
u64 *dma_mask;
u64 coherent_dma_mask;
struct list_head dma_pools;
struct dma_coherent_mem *dma_mem;
struct dev_archdata archdata;
spinlock_t devres_lock;
struct list_head devres_head;
struct list_head node;
struct class *class;
dev_t devt;
struct attribute_group **groups;
void (*release)(struct device * dev);
};
注册和注销的方法
int device_register(struct device * dev);
void device_unregister(struct device * dev);
最基本的device模块框架
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
extern struct bus_type mybus;
void mydev_release(struct device *dev){
printk("-------------%s---------------",__FUNCTION__);
}
struct device mydev = {
.init_name = "mydev",
.bus = &mybus;
.release = mydev_release;
};
static int __init mydev_init(void){
int ret = device_register(&mydev);
if(ret < 0){
printk("device_register error");
return ret;
}
return 0;
}
static void __exit mydev_exit(void){
device_unregister(&mydev);
}
module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");
driver对象:描述设备驱动的方法(代码逻辑)
struct device_driver {
const char * name;
struct bus_type * bus;
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module * owner;
const char * mod_name;
struct module_kobject * mkobj;
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);
};
dev注册和注销
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);
最基本driver框架
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
extern struct bus_type mybus;
int mydrv_probe(struct device *dev){
return 0;
}
int mydrv_remove(struct device *dev){
}
extern struct bus_type mybus;
struct device_driver mydrv = {
.name = "mydrv",
.bus = &mybus,
.probe = mydrv_probe,
.remove = mydrv_remove,
};
static int __init mydrv_init(void){
printk("------------%s-------------\n",__FUNCTION__);
int ret = driver_register(&mydrv);
if(ret < 0){
printk("driver_register error\n");
return ret;
}
return 0;
}
static void __exit mydrv_exit(void){
printk("------------%s-------------\n",__FUNCTION__);
driver_unregister(&mydrv);
}
module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL");
总线模块中的匹配方法
在总线中要能实现匹配的话,必须要实现bus_type对象中的match接口
int match(struct device * dev, struct device_driver * drv);
匹配成功后,driver模块会自动调用probe方法
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
int mybus_match(struct device *dev, struct device_driver *drv){
if(!strncmp(drv->name, dev->kobj.name,strlen(drv->name))){
printk("match ok\n");
return 1;
}else{
printk("match failed\n");
return 0;
}
return 0;
}
struct bus_type mybus = {
.name = "mybus",
.match = mybus_match,
};
EXPORT_SYMBOL(mybus);
static int __int mybus_init(void){
……
return 0;
}
static void __exit mybus_exit(void){
bus_unregister(&mybus);
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
如何结合device对象和driver对象
- 在dev对象中的device对象的platform_data接口中放入数据(void *platform_data)
- dev和drv匹配成功
- drv调用probe方法,并从probe接口中获得dev的数据(platform_data接口中)
在drv对象中实现probe方法 在dev中存入数据
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
extern struct bus_type mybus;
struct mydev_desc{
char *name;
int irqno;
unsigned long addr;
};
struct mydev_desc devinfo = {
.name = "testdev",
.irqno = 9999,
.addr = 0x30008000,
};
void mydev_release(struct device *dev){
printk("-------------%s---------------",__FUNCTION__);
}
struct device mydev = {
.init_name = "mydev",
.bus = &mybus;
.release = mydev_release,
.platform_data = &devinfo,
};
static int __init mydev_init(void){
int ret = device_register(&mydev);
if(ret < 0){
printk("device_register error");
return ret;
}
return 0;
}
static void __exit mydev_exit(void){
device_unregister(&mydev);
}
module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");
dev中调用probe方法
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/io.h>
extern struct bus_type mybus;
struct mydev_desc *pdesc;
int mydrv_probe(struct device *dev){
printk("-------------%s-------------\n",__FUNCTION__);
pdesc =(struct mydev_desc *)dev->platform_data;
printk("name - %s\n", pdesc->name);
printk("irqno = %d\n", pdesc->irqno);
unsigned long *paddr = ioremap(pdesc->addr, 0);
return 0;
}
int mydrv_remove(struct device *dev){
printk("-------------%s-------------\n",__FUNCTION__);
return 0;
}
extern struct bus_type mybus;
struct device_driver mydrv = {
.name = "mydrv",
.bus = &mybus,
.probe = mydrv_probe,
.remove = mydrv_remove,
};
static int __init mydrv_init(void){
printk("------------%s-------------\n",__FUNCTION__);
int ret = driver_register(&mydrv);
if(ret < 0){
printk("driver_register error\n");
return ret;
}
return 0;
}
static void __exit mydrv_exit(void){
printk("------------%s-------------\n",__FUNCTION__);
driver_unregister(&mydrv);
}
module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL");
平台总线驱动编写
为什么会有平台总线 平台升级导致地址空间发生变化,但操作方式类似,换句话说逻辑操作一样,地址不一样。所以引入平台总线。一个驱动能驱动多个平台相似的模块,并且修改的代码很少。 平台总线三元素
- bus
platform_bus:不需要自己创建,系统自带。 总线中有platform_bus_type对象
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};
bus模块入口函数
int __init platform_bus_init(void)
{
int error;
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
匹配方法(match)
- 优先匹配driver中的id_table,里面包含了支持不同平台的名字
- 直接匹配driver中名字和device中名字
- device对象
device 中有platform_device对象
struct platform_device {
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
- 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 (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
3.14版本中的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(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);
}
2.6版本内核
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);
}
drv和dev的注册和注销
int platform_device_register(struct platform_device * pdev)
void platform_device_unregister(struct platform_device * pdev)
int platform_driver_register(struct platform_driver * drv)
void platform_driver_unregister(struct platform_driver * drv)
编写平台总线代码
编写一个能在多个平台下使用的led驱动
- 注册一个platform_device,定义资源:地址和中断
- 注册一个platform_driver,实现操作设备的代码
在platform_device对象中有个resource成员,resource不需要我们初始化
struct platform_device {
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
platform_device_led.c程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <plat/irqs.h>
#define GPIO_REG_BASE 0x11400000
#define GPF3_CON GPIO_REG_BASE + 0x01E0
#define GPF3_SIZE 24
#define GPX1_CON GPIO_REG_BASE + 0x0C20
#define GPX1_SIZE 24
struct resource led_res[] = {
[0] = {
.start = GPF3_CON,
.end = GPF3_CON + GPF3_SIZE - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = GPX1_CON,
.end = GPX1_CON + GPX1_SIZE - 1,
.flags = IORESOURCE_MEM,
}
[2] = {
.start = IRQ_EINT(4),
.end = IRQ_EINT(4),
.flags = IORESOURCE_IRQ,
},
};
struct platform_device led_pdev = {
.name = "s3c2440_led",
.id = -1,
.num_resources = ARRAY_SIZE(led_res),
.resource = led_res,
};
static int _init plat_led_dev_init(void){
platform_device_register(&led_pdev);
return 0;
}
static void _exit plat_led_dev_exit(void){
platform_device_unregister(&led_pdev);
}
module_init(plat_led_dev_init);
module_exit(plat_led_dev_exit);
MODULE_LICENSE("GPL");
platform_driver,实现操作设备的代码,注册完毕,同时如果和pdev匹配成功,自动调用probe方法: probe方法:对硬件进行操作 a.注册设备号,并且注册fops——为用户提供一个设备标识,同时提供文件操作io接口 b.创建设备节点 c.初始化硬件 ioremap(地址); //地址要从pdev获取到 readl()/writel(); d.实现各种io接口:xxx_open,xxx_read,……
获取资源的方式:
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
platform_driver_led.c程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
struct led_dev{
int dev_major;
struct class *cls;
struct device *dev;
struct resource *res;
void *reg_base;
};
struct led_dev *samsung_led;
ssize_t led_pdrv_write(struct file *file, const char __user *buf, size_t count, loff_t *fpos){
int val;
int ret;
ret = copy_from_user(&val, buf, count);
if(ret > 0){
printk("copy_from_user error\n")
return -EFAULT;
}
if(val){
writel((readl(samsung_led->reg_base+4) | (0x3<<4)), samsung_led->reg_base+4);
}else{
writel((readl(samsung_led->reg_base+4) & ~(0x3<<4)), samsung_led->reg_base+4);
}
return count;
}
int led_pdrv_open(struct inode *, struct file *){
printk("--------%s--------\n", __FUNCTION__);
return 0;
}
int led_pdrv_close(struct inode *, struct file *){
printk("--------%s--------\n", __FUNCTION__);
return 0;
}
const struct file_operations led_fops = {
.open = led_pdrv_open,
.release = led_pdrv_close,
.write = led_pdrv_write,
};
int led_pdrv_probe(struct platform_device *pdev){
printk("------------%s-----------\n", __FUNCTION__);
samsung_led = kzalloc(sizeof(struct led_dev), GFP_KERNEL);
if(samsung_led == NULL){
printk("kzalloc erron\n");
return -ENOMEM;
}
samsung_led->dev_major = register_chrdev(0, "led_drv", &led_fops *);
samsung_led->cls = class_create(THIS_MODULE, "led_new_cls");
samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->major, 0), "led0");
samsung_led->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
int irqno = platform_get_irq(pdev, 0);
printk("--------irqno-------- = %d\n", irqno);
samsung_led->reg_base = ioremap(samsung_led->res->start, samsung_led->res->start - samsung_led->res->end + 1);
writel((readl(samsung_led->reg_base)&~(0xff<<16)|(0x11<<16)), samsung_led->reg_base);
return 0;
}
int led_pdrv_remove(struct platform_device *){
printk("------------%s-----------", __FUNCTION__);
iounmap(samsung_led->reg_base);
device_destroy(samsung_led->cls, MKDEV(samsung_led->dev_major,0));
class_destroy(samsung_led->cls);
unregister_chrdev(MKDEV(samsung_led->dev_major, 0), "led_drv");
kfree(samsung_led);
return 0;
}
struct device_driver driver;
const struct platform_device_id led_id_table[] = {
{"s3c2440_led", 0x4444},
{"s5pv210_led", 0x3333},
{"s3c2410_led", 0x2222},
};
struct platform_driver led_pdrv = {
.probe = led_pdrv_probe,
.remove = led_pdrv_remove,
.driver = {
.name = "samsung_led_drv",
}
.id_table = led_id_table,
};
static int _init plat_led_pdrv_init(void){
return platform_driver_register(&led_pdrv);
}
static void _exit plat_led_pdrv_exit(void){
platform_driver_unregister(&led_pdrv);
}
module_init(plat_led_pdrv_init);
module_exit(plat_led_pdrv_exit);
MODULE_LICENSE("GPL");
应用程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys.types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
int on = 0;
fd = open("/dev/led0", O_RDWR);
if(fd < 0){
perror("open");
exit(1);
}
while(1){
on = 0;
write(fd, &on, 4);
sleep(1);
on = 0;
write(fd, &on, 4);
sleep(1);
}
return 0;
}
|