什麼是匯流排:
通俗來說就是:一個匯流排是處理器和一個或多個裝置之間的通道。所有的裝置都透過一個匯流排連線,比如STM32的APB2匯流排,在其上面掛載著相應的外設(GPIOx等外設)。
驅動程式分離/分層的概念:回顧之前講解的控制LED亮滅的Linux驅動程式,我們瞭解到,想要點亮LED主要需要兩個部分:1,硬體部分:設定相應的硬體,比如:重對映某個暫存器的地址,然後設定為輸出模式。2,軟體部分:編寫相應的驅動程式碼,比如註冊驅動裝置等。
當我們修改硬體連線,即換成其它引腳控制LED時,那麼我們按照之前的方法就是重新編寫整個程式,這樣顯得比較麻煩。此時就引入一個分離/分層的概念,通俗來講就是,將上述所說的兩部分(硬體部分和軟體部分)分離出來,以後修改硬體連線時,就直接修改硬體部分,而那些通用的軟體部分(所謂通用:就是不管我們的燈連線在哪個引腳,但都需要註冊相應的裝置,這些註冊程式碼就通用的)就不用修改,這樣就可以減少開發時間。
問題:怎麼將上述分離/分層的兩個部分連線起來?
方法:引入我們上述講的“匯流排”,即使用一個虛擬匯流排掛接兩個部分(注:這裡的虛擬匯流排不是物理上真實存在的匯流排,是用軟體程式碼模擬出來的匯流排)
簡單檢視核心提供的匯流排相關程式碼:linux-2.6.22.6\Documentation\driver-model這裡面有相應的描述檔案
匯流排型別:
struct bus_type { char * name; struct subsystem subsys; struct kset drivers; struct kset devices; struct bus_attribute * bus_attrs; struct device_attribute * dev_attrs; struct driver_attribute * drv_attrs; int (*match)(struct device * dev, struct device_driver * drv); int (*hotplug) (struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size); int (*suspend)(struct device * dev, pm_message_t state); int (*resume)(struct device * dev);};
平臺裝置
struct platform_device { const char * name; u32 id; struct device dev; u32 num_resources; struct resource * resource;};
備註:struct device dev;
device
struct device { struct klist klist_children; struct klist_node knode_parent; /* node in sibling list */ struct klist_node knode_driver; struct klist_node knode_bus; struct device *parent; struct kobject kobj; char bus_id[BUS_ID_SIZE]; /* position on parent bus */ 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; /* semaphore to synchronize calls to * its driver. */ struct bus_type * bus; /* type of bus device is on */ struct device_driver *driver; /* which driver has allocated this device */ void *driver_data; /* data private to the driver */ void *platform_data; /* Platform specific data, device core doesn't touch it */ struct dev_pm_info power;#ifdef CONFIG_NUMA int numa_node; /* NUMA node this device is close to */#endif u64 *dma_mask; /* dma mask (if dma'able device) */ u64 coherent_dma_mask;/* Like dma_mask, but for alloc_coherent mappings as not all hardware supports 64 bit addresses for consistent allocations such descriptors. */ struct list_head dma_pools; /* dma pools (if dma'ble) */ struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */ /* arch specific additions */ struct dev_archdata archdata; spinlock_t devres_lock; struct list_head devres_head; /* class_device migration path */ struct list_head node; struct class *class; dev_t devt; /* dev_t, creates the sysfs "dev" */ struct attribute_group **groups; /* optional groups */ void (*release)(struct device * dev);};
平臺驅動
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 device_driver driver;
device_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; /* used for built-in modules */ 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);};
平臺驅動與平臺裝置的關係平臺設備註冊函式執行主要流程:我們從上圖可知,int platform_device_register(struct platform_device * pdev) 不僅向上將設備註冊到匯流排bus上,而且最終還會檢視是否有合適的驅動,如果有就呼叫 int device_bind_driver(struct device *dev)函式進行裝置與驅動的繫結。
平臺驅動註冊函式執行主要流程:我們從上圖可知,int platform_driver_register(struct platform_driver *drv)不僅向上將驅動註冊到匯流排bus上,而且最終還會檢視是否有合適的裝置。
關聯:上述講解了相應的結構體和相關函式的主要執行過程,但引入一個問題:核心中這麼多的匯流排,平臺裝置和平臺驅動是怎麼知道它們是屬於同一個虛擬匯流排的?
方法:
我們可以從下圖中標記的數字1就可知道,它們在執行時會繫結到同一個總線上。
備註:數字1是:
pdev->dev.bus = &platform_bus_type;
drv->driver.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,};
總結:通俗來講就是:平臺設備註冊函式在名為platform的總線上進行裝置的註冊,同時檢視是否有合適的平臺驅動,如有就進行繫結;平臺驅動註冊函式在名為platform的總線上進行驅動的註冊,同時檢視是否有合適的裝置,如有就進行繫結。上述過程需透過match函式(.match = platform_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);}
補充:(匯流排初始化相關呼叫過程)1, start_kernel2, rest_init3, kernel_thread4, kernel_init5, do_basic_setup6, driver_init7, platform_bus_init8, device_register(&platform_bus);struct device platform_bus = { .bus_id = "platform",};9, bus_register(&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,};
編寫程式驗證
編寫一個簡單的控制LED的程式,分別是兩個C檔案:一個編寫平臺裝置,一個編寫平臺驅動。
1,編寫平臺裝置
//平臺裝置相關程式碼用於硬體相關#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 <asm/arch/regs-gpio.h>#include <asm/hardware.h>#include <linux/platform_device.h>static struct resource led_dev_resource[] = { [0] = { .start = 0x56000050, .end = 0x56000058, .name = "doubixioaohanhan", .flags = IORESOURCE_MEM, }, [1] = { .start = 4, .end = 4, .flags = IORESOURCE_IRQ, },};static void led_dev_release(struct device * dev){ printk("doubixiaohanhan"); }static struct platform_device led_dev = { .name = "led_bus", .id = 0, .resource = led_dev_resource, .num_resources = ARRAY_SIZE(led_dev_resource), .dev ={ .release = led_dev_release, }};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");
2,編寫平臺驅動
#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 <linux/platform_device.h>#include <asm/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>#include <linux/device.h>#define CLASS_NAME "doubixiaohanhan"#define OUTPUT_HIGH 1#define OUTPUT_LOW 0 int major = 0;static struct class *led_class;static struct class_device *led_class_device;static volatile unsigned long *gpiof_con;static volatile unsigned long *gpiof_dat;static int led_pin;static int led_drv_open(struct inode * inode, struct file * file){ *gpiof_con &= ~(3<<(led_pin * 2)); *gpiof_con |= (1<<(led_pin * 2)); printk("led_pin configurate output mode"); return 0;}static ssize_t led_drv_write(struct file * file, const char __user * userbuf, size_t count, loff_t * off){ int val = 0; if( copy_from_user(&val, userbuf, 1)) ; if(OUTPUT_HIGH == val) { *gpiof_dat |= (1<<led_pin); } else { *gpiof_dat &= ~(1<<led_pin); } return 0; }static struct file_operations led_drv_fops={ .owner = THIS_MODULE, .open = led_drv_open, .write = led_drv_write, };static int led_drv_probe(struct platform_device *pdev){ struct resource *res; printk("I am doubixiaohanhan probe"); //register device driver major = register_chrdev(0,"led",&led_drv_fops); //create class and class-device led_class = class_create(THIS_MODULE, CLASS_NAME); led_class_device = class_device_create(led_class, NULL, MKDEV(major, 1), NULL, "LED"); //PORT REMAP res = platform_get_resource(pdev, IORESOURCE_MEM, 0); gpiof_con = ioremap(res->start, res->end - res->start +1); gpiof_dat = gpiof_con + 1; res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); led_pin = res->start; return 0;} static int led_drv_remove(struct platform_device *pdev){ printk("I am doubixiaohanhan remove"); iounmap(gpiof_con); class_destroy(led_class); //class_device_destroy(led_class,led_class_device); class_device_destroy(led_class,MKDEV(major, 1)); unregister_chrdev(major,"led"); return 0;}static struct platform_driver led_drv = { .probe = led_drv_probe, .remove = led_drv_remove, .driver = { .name = "led_bus", .owner = THIS_MODULE, }, };//drv->driver.probe = platform_drv_probe;//int (*probe)(struct platform_device *);//led_drv->driver.probe=platform_drv_probe//led_drv->led_drv_probestatic int led_drv_init(void){ platform_driver_register(&led_drv);//driver_register(&drv->driver); 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");
3,編寫應用測試程式
#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>//輸入數字1熄滅LED//輸入其它字元開啟LEDint main(int argc,char **argv){ int fd; char pin[2] = {1,0}; int val; fd = open("/dev/LED",O_RDWR); if(fd < 0) { printf("/dev/LED can't open\n"); return ; } printf("\n"); while(1) { printf("\ninput integer select light on/off\n"); scanf("%d",&val); if(val == 1) write(fd,&pin[0],1); else write(fd,&pin[1],1); }}
4,編寫Makefile
#compile regularKERN_DIR = /work/system/linux-2.6.22.6all: make -C $(KERN_DIR) M=`pwd` modules rm -rf modules.order Module.symvers .PHONY:clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order Module.symversobj-m += led_dev.oobj-m += led_drv.o
然後編譯測試成功。