linux設備驅動之platform平臺總線工作原理(三)
設備為數據,驅動為加工著
1、以led-s3c24xx.c為例來分析platform設備和驅動的註冊過程
其中關於led的驅動數據結構為:
static struct platform_driver s3c24xx_led_driver = { .probe = s3c24xx_led_probe, .remove = s3c24xx_led_remove, .driver = { .name = "s3c24xx_led", .owner = THIS_MODULE, }, }; struct 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; };
關於led的設備數據結構為
static struct platform_device mini2440_led1 = { .name = "s3c24xx_led", .id = 1, .dev = { .platform_data = &mini2440_led1_pdata, }, };
其中設備的name和驅動中的name一致,設備數據結構中的id值用來區分不同的led設備,因為一個板子上可能有多個led,用這個id值來進行區分不同的led燈,這個id值可以自己隨意去排,比如有四個led,id值就可以設置為從1到4的數字,還有一種方法是,我們不去指定的id的值,給id值設置為-1.表示板子上led的id,由內核去幫我們分配。成員dev中的platform_data是設備的數據部分。
struct platform_device的數據結構內容為
struct platform_device { const char * name; int id; struct device dev; u32 num_resources; struct resource * resource; const struct platform_device_id *id_entry; /* arch specific additions */ struct pdev_archdata archdata; };
可以看到dev成員變量確實在struct platform_device結構體中,看一下這個struct platform_device結構體中dev變量的結構體類型struct device的數據結構內容
struct device { struct device *parent; struct device_private *p; struct kobject kobj; const char *init_name; /* initial name of the device */ struct device_type *type; struct mutex mutex; /* mutex 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 *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 device_dma_parameters *dma_parms; 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; #ifdef CONFIG_OF struct device_node *of_node; #endif dev_t devt; /* dev_t, creates the sysfs "dev" */ spinlock_t devres_lock; struct list_head devres_head; struct klist_node knode_class; struct class *class; const struct attribute_group **groups; /* optional groups */ void (*release)(struct device *dev); };
其中可以看到在這個struct device結構體類型中有一個void *platform_data;成員,這個成員是一個可以指向任意一個類型的指針,他就是設備的數據部分。回到led設備的數據結構中
static struct platform_device mini2440_led1 = { .name = "s3c24xx_led", .id = 1, .dev = { .platform_data = &mini2440_led1_pdata, }, };
struct device類型的成員dev中的成員platform_data,這個platform_data是一個可以指向任意一個類型的指針,這個位置是一個留白位置,我們可以自己寫一個
東西,將寫完的這個東西綁定到這個platform_data中成員中,這個void *類型的platform_data成員變量所指向的東西是留給我們的,因為是void *類型的,所以我們可以自己去定義一個類型然後綁定到這裏面去,這個地方可以說是一個設備中特有的數據部分,因為內核設備和驅動在設計的時候,並不能把所有的設備有的東西都定義好,所以有了這麽一個void *類型的成員變量讓我們去定義設備特有的數據部分。
這個led的設備數據結構中的platform_data成員變量指向的是一個min2440_led1_pdata這麽一個數據結構,這個數據結構是寫led設備的人自己定義的一個數據結構。看下這個mini2440_led1_pdata變量的數據結構
static struct s3c24xx_led_platdata mini2440_led1_pdata = { .name = "led1", .gpio = S3C2410_GPB(5), .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, .def_trigger = "heartbeat", };
看下這個mini2440_led1_pdata變量的結構體類型 struct s3c24xx_led_platdata
struct s3c24xx_led_platdata { unsigned int gpio; unsigned int flags; char *name; char *def_trigger; };
這是一個自己定義的一個數據結構類型,並不是內核事先定義的一個數據結構類型,因為設備中的platform_data成員指向的部分的內容是什麽是由我們來決定的。這個struct s3c24xx_led_platdata定義的是led所特有的一些數據部分,platform平臺總線的不同設備的platform_data肯定是不一樣的,是由我們來去定義這個不一樣的地方的,platfrom_data成員指向的那個數據結構是關鍵,是用來區分platform平臺總線什麽設備的,這個設備的特有部分的。
針對於這個led的設備中platform_data成員指向的led設備特有的自己定義的數據結構struct s3c24xx_led_platdata中,gpio表示這個led所用的引腳號,flags對應的led的屬性。name表示led的名字,def_trigger可能表示這個led燈是要跟其他的硬件綁定起來的。
下面在繼續分下這個min2440_led1_pdata變量,led設備特有的數據部分,移植的人自己定義的
static struct s3c24xx_led_platdata mini2440_led1_pdata = { .name = "led1", .gpio = S3C2410_GPB(5), .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, .def_trigger = "heartbeat", };
name名字為led1,用到的gpio偏移為S3C2410_GPB(5),屬性flags為.....,def_trgiger值為heatbeat,說明這個led1將來是用作為心跳燈的。
我們將led特有的數據部分寫完後,然後綁定到plotform_data成員中,讓這個成員指向我們寫的led特有的設備數據部分,在來看下怎麽綁定的,有點繞
static struct platform_device mini2440_led1 = { .name = "s3c24xx_led", .id = 1, .dev = { .platform_data = &mini2440_led1_pdata, }, };
將mini2440_led1_pdata這個設備特有的數據結構綁定到platform_data成員中,特有的數據部分我們實現為led設備特有的數據部分,最後這個struct plat_device 類型的mini2440_led1設備的數據結構就填充好了,之後使用register函數將這個mini2440_led1變量進行註冊就行,這個變量就表示一個led的設備。
總結:platform_data其實就是設備註冊時提供的設備有關的一些數據(譬如設備對應的gpio、使用到的中斷號、設備名稱.....),在註冊一個平臺設備的時候,我們為這個設備先提供這些設備數據,這些數據在我們的設備和驅動match之後,match函數會被調用會識別設備和驅動,將設備和驅動匹配上,會由設備方轉給驅動方,提供的這些數據就是為了將來設備和驅動匹配上後,設備數據結構中會將這些數據部分轉給驅動,驅動拿到這些數據後,通過這些數據得知設備的具體信息,然後來操作設備。這樣做的好處是我們的驅動源碼中不攜帶數據,只負責對硬件的操作方法,但是並不知道我們具體操作的硬件是哪一個硬件,驅動是通過設備和驅動match匹配上之後,設備將我們提供的數據部分轉交給驅動後,驅動才知道要操作哪個設備,因為數據中包括了設備的所用到的資源(譬如設備對應的gpio、使用到的中斷號等等),而驅動又有對這一類型設備的操作方法,所以驅動拿到這個設備的數據後,直接用操作方法就可以操作這個設備了。數據和操作邏輯分開實現是一個好的設計邏輯,因為這樣,這個驅動就具有一般性,當硬件換了的時候,也就是設備用的gpio換了的時候,我們不需要改驅動的源碼,而是在移植的時候,將更換硬件後的設備的資源進行更改就行,驅動源碼是不用變的。
既然說設備和驅動match之後,設備會將我們提供的這個設備的特有的數據部分轉交給驅動,然後驅動就可以知道用自己的方法操作哪個硬件了,那麽設備將數據部分轉交給驅動是轉交的呢,在probe函數中
static int s3c24xx_led_probe(struct platform_device *dev) { struct s3c24xx_led_platdata *pdata = dev->dev.platform_data; struct s3c24xx_gpio_led *led; int ret; led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL); if (led == NULL) { dev_err(&dev->dev, "No memory for device\n"); return -ENOMEM; } platform_set_drvdata(dev, led); led->cdev.brightness_set = s3c24xx_led_set; led->cdev.default_trigger = pdata->def_trigger; led->cdev.name = pdata->name; led->cdev.flags |= LED_CORE_SUSPENDRESUME; led->pdata = pdata; /* no point in having a pull-up if we are always driving */ if (pdata->flags & S3C24XX_LEDF_TRISTATE) { s3c2410_gpio_setpin(pdata->gpio, 0); s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT); } else { s3c2410_gpio_pullup(pdata->gpio, 0); s3c2410_gpio_setpin(pdata->gpio, 0); s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT); } /* register our new led device */ ret = led_classdev_register(&dev->dev, &led->cdev); if (ret < 0) { dev_err(&dev->dev, "led_classdev_register failed\n"); kfree(led); return ret; } return 0; }
我們可以看到在led驅動代碼probe函數中開始有一行代碼
struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;
這行代碼就是將設備的數據部分轉交給了驅動,因為驅動的probe函數中定義了一個struct s3c24xx_led_platdata類型的pdata結構體指針,而probe函數的參數是
struct platform_device類型的dev指針,經過上面的分析我們知道,這個一個設備的數據結構,這個設備的數據結構中有一個成員變量dev,這個dev是一個struct device類型的結構體,這個struct device類型的結構體中有一個platform_data成員變量,用來指向我們寫的這個設備所特有的數據部分,在這個led中,這個特有的數據部分的數據類型為s3c24xx_led_platdata,因此我們從上面的代碼就可以看出來,驅動代碼的probe函數中,通過pdata這個指針指向了led設備的特有數據部分platform_data,這就是設備將我們提供的數據部分轉交給驅動的過程。
本文出自 “whylinux” 博客,請務必保留此出處http://whylinux.blog.51cto.com/10900429/1930747
linux設備驅動之platform平臺總線工作原理(三)