裝置、驅動、匯流排模型簡介
前言
在Linux系統中,有很多的框架提供驅動編寫者使用,比如前面我寫過的一篇文章:input子系統的架構分析及應用。都是將純軟體相關的程式碼和操作硬體相關的程式碼分離開,這樣就使得驅動的編寫者省去編寫大量的、重複的程式碼,只要專注於底層硬體相關的程式碼就可以了。這次我們介紹Linux系統中另外一個常見的框架模型:device-driver-bus模型,也就是裝置、驅動、匯流排模型。
正文
下面先給出模型的整體概念圖:
由上面的圖可以看出來,和我們以前的input子系統框架有點像,也是講軟硬體的程式碼分離,從而做到抽象上的分層。以前我們寫過一個點燈的驅動程式:
裝置
/* * led_dev.c */ #include <linux/kernel.h> #include <linux/types.h> #include <linux/interrupt.h> #include <linux/list.h> #include <linux/timer.h> #include <linux/init.h> #include <linux/serial_core.h> #include <linux/platform_device.h> #include <linux/module.h> #include <linux/version.h> /* * 分配、設定、註冊platform_device */ static struct resource led_source[] = { [0] = { .start = 0x56000050, .end = 0x56000050 + 8 - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = 4, .end = 4, .flags = IORESOURCE_IRQ, }, }; static void led_release(struct device *dev) { /*為了簡單,先不寫任何東西*/ } static struct platform_device led_dev = { .name = "myLed", .id = -1, .num_resources = ARRAY_SIZE(led_source), .resource = led_source, .dev = { .release = led_release, }, }; static int led_dev_init(void) { platform_device_register(&led_dev); return 0; } static int led_dev_exit(void) { platform_device_unregister(&led_dev); return 0; } module_init(led_dev_init); module_exit(led_dev_exit); MODULE_LICENSE("GPL");
(1)init函式裡面是將自己註冊進匯流排
(2)struct resource用於描述我們物理裝置的一些資源,比如GPFCON暫存器的實體地址,如下圖所示:
還有4表示的是希望點亮的led燈的暫存器GPF4。
(3)platform_device結構體則包含上面所說的資源結構體外,還有一個重要的成員就是name,註冊後匯流排將會呼叫match函式去尋找同名的驅動
驅動
/* * led_drv.c */ #include <linux/kernel.h> #include <linux/types.h> #include <linux/interrupt.h> #include <linux/list.h> #include <linux/timer.h> #include <linux/init.h> #include <linux/serial_core.h> #include <linux/platform_device.h> #include <asm/io.h> #include <asm/uaccess.h> #include <linux/module.h> #include <linux/version.h> /* * 分配、設定、註冊platform_driver */ static int major; static struct class *led_class =NULL; static struct class_device *led_class_device = NULL; static volatile unsigned long *gpio_con = NULL; static volatile unsigned long *gpio_dat = NULL; static int pin; static int led_open(struct inode *inode, struct file *file) { /*配置為輸出引腳*/ *gpio_con &= ~(0x3<<pin*2); //先清空初始化 *gpio_con |= (0x1<<pin*2); //設定為輸出引腳 return 0; } static void led_write(struct file *file, const __user *buf, size_t count, loff_t *ppos) { int val; copy_from_user(&val, buf, count); if (1 == val) { //開燈,低電平有效 *gpio_dat &= ~(0x1<<pin); } else { //關燈 *gpio_dat |= (0x1<<pin); } return 0; } static struct file_operations led_fop = { .open = led_open, .write = led_write, .owner = THIS_MODULE, }; static int led_probe(struct platform_device *pdev) { struct resource *res; /*根據platform_device的資源進行ioremap*/ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res){ goto err0; } gpio_con = (volatile unsigned long *)ioremap(res->start, res->end - res->start + 1); gpio_dat = gpio_con + 1; res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res){ goto err0; } pin = res->start; /*註冊字元裝置驅動程式*/ printk("led_probe, found led\n"); major = register_chrdev(0, "myLed", &led_fop); led_class = class_create(THIS_MODULE, "myLed"); led_class_device = class_device_create(led_class, NULL, MKDEV(major, 0), NULL, "led"); return 0; err0: printk("platform_get_resource failed\n"); return -ENOMEM; } static int led_remove(struct platform_device *pdev) { /*解除安裝字元裝置驅動程式*/ /* iounmap */ printk("led_remove, remove led\n"); class_device_destroy(led_class_device, MKDEV(major, 0)); class_destroy(led_class); unregister_chrdev(major, "myLed"); iounmap(gpio_con); return 0; } static struct platform_driver led_drv = { .probe = led_probe, .remove = led_remove, .driver = { .name = "myLed", }, }; static int led_drv_init(void) { platform_driver_register(&led_drv); return 0; } static int led_drv_exit(void) { platform_driver_unregister(&led_drv); return 0; } module_init(led_drv_init); module_exit(led_drv_exit); MODULE_LICENSE("GPL");
(1)以前我們驅動程式的init函式裡面都是建立字元裝置和做GPIO暫存器的ioremap等等的一些硬體相關的操作,但是現在只是簡單的使用platform_driver_register函式將驅動註冊進匯流排,並且只有當找到了匹配的(相同的.name)裝置後才會呼叫.probe函式
(2)以前做的一些建立字元裝置和ioremap操作我們放到了probe函式裡面。值得關注的就是platform_get_resource函式,通過使用flags(IORESOURCE_MEM或者IORESOURCE_IRQ)找到不同的資源。
(3)probe成功後,剩下的就是以前的常規操作了,也是open和write函式等等
最後
我們將上面兩個程式分別編譯成led_dev.ko和led_drv.ko,同時insmod進系統,就能看到相關成功匹配的列印了。上面我們只是點亮了GPF4的燈,如果想點亮其他的燈,只要修改裝置程式碼中的資源結構體中的值就可以了。