由于TINY4412被学长借去做毕设了,因此从本章开始,以后章节的示例代码均基于iTOP4412_SCP精英版
如读者使用TINY4412开发板,可自行修改代码
本章所说的总线是虚拟的总线,只是为了让设备属性和驱动行为更好的分离所提出的概念
实际的Linux设备和驱动通常都会挂接在一种总线上,对于USB、I2C、SPI等总线设备而言,自然不是问题。但是挂接在SoC之外的外设却不依附于此类总线,因此Linux发明了虚拟的总线,称为platform总线,所有直接通过内存寻址的设备都映射到这条总线上。总线相应的结构体为struct bus_type,相应的设备为platform_device,相应的驱动为platform_drvier
在使用总线分层时,如果设备代码需要更改,而驱动代码不需要更改。那么我们只需要更改设备代码即可,而不需要大片地更改驱动代码
在以下章节中,我会依次介绍platform_device、platform_driver和总线结构体platform_bus_type
一、platform_device
之前说过platform_device定义的是属性,其结构体定义如下:
struct platform_device { const char * name; // 名字,用于与driver匹配 int id; struct device dev; u32 num_resources; // resource的个数 struct resource * resource; // 存储数据 const struct platform_device_id *id_entry; /* MFD cell pointer */ struct mfd_cell *mfd_cell; /* arch specific additions */ struct pdev_archdata archdata;};
我们需要重点关注的是struct resource * resource;,此变量存储设备资源信息,其定义和示例如下:
struct resource { resource_size_t start; // 起始地址 resource_size_t end; // 结束地址 const char *name; unsigned long flags; // 资源类型 struct resource *parent, *sibling, *child;};static struct resource led_resource[] = { /* 在iTOP4412中LED3对应GPK1_1 */ [0] = { .start = 0x11000060, // GPK1CON地址 .end = 0x11000060 + 8 - 1, .flags = IORESOURCE_MEM, // 内存 }, /* 要控制哪个I/O口(它属于参数,这里为了方便,用resource传参) */ [1] = { .start = 1, .end = 1, .flags = IORESOURCE_IRQ, // 中断属性 },};
资源有以下几种常用类型:
#define IORESOURCE_IO 0x00000100#define IORESOURCE_MEM 0x00000200#define IORESOURCE_IRQ 0x00000400#define IORESOURCE_DMA 0x00000800#define IORESOURCE_BUS 0x00001000
因为ARM中寄存器和内存是统一编址的,所以GPIO所使用的资源标志用IORESOURCE_MEM和IORESOURCE_REG都是可以的
需要注意的是,这里不能用I/O,这里的IORESOURCE_IO特指PCI/ISA总线的I/O
在定义完成resource之后,我们可以定义如下platform_device:
1 static struct platform_device led_platform_dev = {2 .name = "led",3 .id = 0,4 .resource = led_resource,5 .num_resources = ARRAY_SIZE(led_resource),6 };
在paltform_device定义完成后,我们需要使用如下函数向总线bus_type注册/注销:
/* 注册platform_device */platform_device_register(&led_platform_dev);/* 注销platform_driver */platform_device_unregister(&led_platform_dev);
platform_device_register()函数调用过程如下:
platform_device_register() -> device_initialize(&pdev->dev); // 初始化struct device -> platform_device_add(pdev); // 添加设备到链表中 -> pdev->dev.bus = &platform_bus_type; // 指定总线 -> device_add(&pdev->dev); -> bus_probe_device(struct device *dev) -> device_attach(struct device *dev) -> bus_for_each_drv(dev->bus, NULL, dev, __device_attach); -> __device_attach() -> driver_match_device(drv, dev); // 调用总线成员函数match() -> return drv->bus->match ? drv->bus->match(dev, drv) : 1; -> driver_probe_device(drv, dev); -> really_probe(dev, drv); -> drv->probe(dev); // 调用probe()函数
二、platform_driver
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;};
platform_driver示例如下:
static struct platform_driver led_platform_drv = { .driver = { .name = "led", .owner = THIS_MODULE, }, .probe = led_probe, .remove = __devexit_p(led_remove), /* __devexit_p(x) x */};
和platform_device一样,在paltform_driver定义完成后,我们需要使用如下函数向总线bus_type注册/注销:
/* 注册platform_driver */platform_driver_register(&led_platform_drv);/* 注销platform_driver */platform_driver_unregister(&led_platform_drv);
platform_driver_register()函数调用过程如下:
platform_driver_register() -> drv->driver.bus = &platform_bus_type; // 指定总线 -> driver_register(&drv->driver); -> bus_add_driver(drv); // 添加驱动到链表中 -> driver_attach(drv); -> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); -> __driver_attach() -> driver_match_device(drv, dev); // 调用总线成员函数match() -> return drv->bus->match ? drv->bus->match(dev, drv) : 1; -> driver_probe_device(drv, dev); -> really_probe(dev, drv); -> drv->probe(dev); // 调用probe()函数
和platform_device_register()一样,platform_driver_register()也会调用总线的成员函数match();匹配成功则会调用paltform_driver的probe()函数
因此我们要在paltform_driver结构体中提供probe()函数
下面,我们来分析总线结构体platform_bus_type
三、platform_bus_type
platform_bus_type定义如下:
struct bus_type platform_bus_type = { .name = "platform", .dev_attrs = platform_dev_attrs, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops,};
我们需要分析其match()函数:
/* 1. 使用设备树进行匹配,我们没有用到 */of_driver_match_device(dev, drv)/* 2. 使用platform_driver的id_table进行匹配,我们也没有用到 */platform_match_id(pdrv->id_table, pdev)/* 3. 匹配名字,我们使用这个 */strcmp(pdev->name, drv->name)
四、总结
总线与输入子系统不同,总线并没有提供struct file_operations结构体,因此仍需要我们自己定义
1. 注册platform_driver:platform_driver_register()
1.1 设置platform_driver的总线为platform_bus_type
1.2 添加platform_driver到总线的drv链表中
1.3 调用drv->bus->match(dev, drv)进行匹配
2. 注册platform_device:platform_device_register()
2.1 设置platform_device的总线为platform_bus_type
2.2 添加platform_device到总线的dev链表中
2.3 调用drv->bus->match(dev, drv)进行匹配
3. 匹配:drv->bus->match(dev, drv)
3.1 匹配设备树信息
3.2 匹配dev和drv->id_table
3.3 匹配dev->name和drv->name
3.4 成功,调用drv->probe(dev)
4. 驱动初始化:probe()
4.1 在probe()中做之前init()函数所做的事
5. 驱动注销:remove()
5.1 在remove()中做之前exit()函数所做的事
五、更改led.c为总线设备驱动
此代码我们需要完成platform_device和platform_driver两个结构体,因此分为两个文件
我们在probe()函数中需要获取platform_device的数据,此时需要使用platform_get_resource()函数,示例代码如下:
struct resource *led_resource;led_resource = platform_get_resource(platdev, IORESOURCE_MEM, 0);GPFCON = (volatile unsigned long *)ioremap(led_resource->start, led_resource->end - led_resource->start + 1);GPFDAT = GPFCON + 1; // 映射led_resource = platform_get_resource(platdev, IORESOURCE_IRQ, 0);pin = led_resource->start;
device源代码:
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 8 #include 9 #include 10 #include 11 12 static struct resource led_resource[] = { 13 /* 在iTOP4412中LED3对应GPK1_1 */14 [0] = { 15 .start = 0x11000060, // GPK1CON地址16 .end = 0x11000060 + 8 - 1,17 .flags = IORESOURCE_MEM, // 内存18 },19 /* 要控制哪个I/O口(它属于参数,这里为了方便,用resource传参) */20 [1] = { 21 .start = 1,22 .end = 1,23 .flags = IORESOURCE_IRQ, // 中断属性24 },25 };26 27 static void led_release(struct device *dev)28 {29 /* NULL */30 }31 32 static struct platform_device led_platform_dev = {33 .name = "led",34 .id = 0,35 .resource = led_resource,36 .num_resources = ARRAY_SIZE(led_resource),37 .dev = {38 .release = led_release,39 },40 };41 42 static int led_dev_init(void)43 {44 return platform_device_register(&led_platform_dev);45 }46 47 static void led_dev_exit(void)48 {49 platform_device_unregister(&led_platform_dev);50 }51 52 module_init(led_dev_init);53 module_exit(led_dev_exit);54 55 MODULE_LICENSE("GPL");
driver源代码:
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 12 #include 13 #include 14 #include 15 16 /* 定义文件内私有结构体 */ 17 struct led_device { 18 struct cdev cdev; 19 int stat; /* 用于保存LED状态,0为灭,1为亮 */ 20 }; 21 22 static int g_major; 23 module_param(g_major, int, S_IRUGO); 24 25 static struct led_device* dev; 26 static struct class* scls; 27 static struct device* sdev; 28 29 static volatile unsigned long *con; 30 static volatile unsigned long *dat; 31 32 static int pin; 33 34 /* LED write()函数 */ 35 static ssize_t led_write(struct file *filep, const char __user * buf, size_t len, loff_t *ppos) 36 { 37 struct led_device *dev = filep->private_data; 38 39 if (copy_from_user(&(dev->stat), buf, 1)) 40 return -EFAULT; 41 42 if (dev->stat == 1) 43 *dat |= (1 << pin); 44 else 45 *dat &= ~(1 << pin); 46 47 return 1; 48 } 49 50 /* LED open()函数 */ 51 static int led_open(struct inode *inodep, struct file *filep) 52 { 53 struct led_device *dev; 54 55 dev = container_of(inodep->i_cdev, struct led_device, cdev); 56 // 放入私有数据中 57 filep->private_data = dev; 58 59 // 设为输出引脚,灭灯 60 *con |= (1 << (4 * pin)); 61 *dat &= ~(1 << pin); 62 63 return 0; 64 } 65 66 static int led_close(struct inode *inodep, struct file *filep) 67 { 68 return 0; 69 } 70 71 /* 把定义的函数接口集合起来,方便系统调用 */ 72 static const struct file_operations led_fops = { 73 .write = led_write, 74 .open = led_open, 75 .release = led_close, 76 }; 77 78 static int led_probe(struct platform_device *platdev) 79 { 80 printk("%s\n", __FUNCTION__); 81 82 int ret; 83 dev_t devt; 84 85 /* 1. 申请设备号 */ 86 if (g_major) { 87 devt = MKDEV(g_major, 0); 88 ret = register_chrdev_region(devt, 1, "led"); 89 } 90 else { 91 ret = alloc_chrdev_region(&devt, 0, 1, "led"); 92 g_major = MAJOR(devt); 93 } 94 if (ret) 95 return ret; 96 97 /* 2. 申请文件内私有结构体 */ 98 dev = kzalloc(sizeof(struct led_device), GFP_KERNEL); 99 if (dev == NULL) {100 ret = -ENOMEM;101 goto fail_malloc;102 }103 104 /* 3. 注册字符设备驱动 */105 cdev_init(&dev->cdev, &led_fops); /* 初始化cdev并链接file_operations和cdev */106 ret = cdev_add(&dev->cdev, devt, 1); /* 注册cdev */107 if (ret)108 return ret;109 110 /* 4. 创建类设备,insmod后会生成/dev/led设备文件 */111 scls = class_create(THIS_MODULE, "led");112 sdev = device_create(scls, NULL, devt, NULL, "led");113 114 struct resource *led_resource;115 led_resource = platform_get_resource(platdev, IORESOURCE_MEM, 0);116 con = (volatile unsigned long *)ioremap(led_resource->start, led_resource->end - led_resource->start + 1);117 dat = con + 1; // 映射118 119 printk("con = %p, dat = %p\n", con, dat);120 121 led_resource = platform_get_resource(platdev, IORESOURCE_IRQ, 0);122 pin = led_resource->start;123 124 return 0;125 126 fail_malloc:127 unregister_chrdev_region(devt, 1);128 129 return ret;130 }131 132 static int led_remove(struct platform_device *platdev)133 {134 printk("%s\n", __FUNCTION__);135 136 iounmap(con);137 138 /* 镜像注销 */139 dev_t devt = MKDEV(g_major, 0);140 141 device_destroy(scls, devt);142 class_destroy(scls);143 144 cdev_del(&(dev->cdev));145 kfree(dev);146 147 unregister_chrdev_region(devt, 1);148 149 return 0;150 }151 152 static struct platform_driver led_platform_drv = {153 .driver = {154 .name = "led",155 .owner = THIS_MODULE,156 },157 .probe = led_probe,158 .remove = __devexit_p(led_remove), /* __devexit_p(x) x */159 };160 161 static int __init led_init(void)162 {163 return platform_driver_register(&led_platform_drv);164 }165 166 static void __exit led_exit(void)167 { 168 platform_driver_unregister(&led_platform_drv);169 }170 171 /* 声明段属性 */172 module_init(led_init);173 module_exit(led_exit);174 175 MODULE_LICENSE("GPL");
Makefile:
1 KERN_DIR = /work/itop4412/tools/linux-3.5 2 3 all: 4 make -C $(KERN_DIR) M=`pwd` modules 5 6 clean: 7 make -C $(KERN_DIR) M=`pwd` modules clean 8 rm -rf modules.order 9 10 obj-m += device.o driver.o
测试文件:
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 8 int main(int argc, char** argv) 9 {10 if (argc != 2) {11 printf("Usage: \n");12 printf("%s \n", argv[0]);13 return -1;14 }15 16 int fd;17 fd = open("/dev/led", O_RDWR);18 if (fd < 0) {19 printf("can't open /dev/led\n");20 return -1;21 }22 23 char stat;24 if (0 == strcmp(argv[1], "off")) {25 stat = 0;26 write(fd, &stat, 1);27 } else {28 stat = 1;29 write(fd, &stat, 1);30 }31 close(fd);32 33 return 0;34 }
在rmmod device时,产生如下图错误:
也就是说我们需要提供release()函数,更改platform_device如下:
1 static void led_release(struct device *dev) 2 { 3 /* NULL */ 4 } 5 6 static struct platform_device led_platform_dev = { 7 .name = "led", 8 .id = 0, 9 .resource = led_resource,10 .num_resources = ARRAY_SIZE(led_resource),11 .dev = {12 .release = led_release,13 },14 };
下一章