天天看點

linux驅動開發之platform總線

1.什麼是platform總線

我們先來思考這樣一個問題,當我們把usb裝置插到電腦上時,電腦是如何識别到這個usb裝置的?其實,每一個usb中都有一個vid(廠商id)和pid(裝置id),vid和pid在usb的生産過程中就已經設定好了。同時,對于一個usb驅動,在驅動裡也儲存着這個pid和vid,當usb裝置中的vid和pid是與usb驅動中的vid和pid相同時,驅動便可以識别到這個裝置。這是因為在linux核心中維護者兩條連結清單,一個是裝置連結清單,另一個是驅動連結清單,linux核心會把裝置加到裝置連結清單,把驅動加到驅動連結清單,然後一個裝置到來時,就在去驅動鍊裡找這個裝置所對應的驅動。但是,對于led裝置,并沒有像usb裝置一樣,有這種裝置鍊和驅動鍊,是以,我們需要虛拟出一條總線,這條總線負責讓裝置找到驅動,驅動找到裝置,我們把這樣的總線稱為platform總線。

現在,我們就可以把驅動和裝置分離,這樣做的好處在與驅動是通用的,一個驅動可以支援多個裝置,而如果将驅動和裝置寫在一起,則這個驅動就隻能支援這個裝置,當裝置改變了,驅動也要改變。

對于platform總線下的裝置和驅動,裝置和驅動就通過name域來找到對方,裝置的name域如果和驅動的name域相同,這個裝置和這個驅動就是互相對應的。

裝置中的name域:

static struct platform_device s3c_led_device = {
 71     .name    = "s3c_led",
 72     .id      = 1,
 73     .dev     =
 74     {
 75         .platform_data = &s3c_led_data,
 76         .release = platform_led_release,
 77     },
 78 };
           

驅動中的name域:

static struct platform_driver s3c_led_driver = {
    .probe      = s3c_led_probe,
    .remove     = s3c_led_remove,
    .driver     = {
        .name       = "s3c_led",
        .owner      = THIS_MODULE,
    },
};
           

2.裝置端完成哪些工作

現在,裝置和驅動是分離的,是以,需要由裝置端向platform虛拟總線告知一些關于裝置端的資訊。例如,對于led裝置,其需要告知的資訊是:led裝置用到的那幾個引腳,如果要點亮led,需要給高電平還是低電平,需要設定成輸入模式還是輸出模式,以及該裝置的name域是多少,這些資訊都是需要在裝置端完成的,

platform_device_register函數用來将裝置注冊到platform總線。其函數原型如下:

int platform_device_register(struct platform_device *pdev)
           

其參數是一個指向platform_device 類型的結構體指針,我們可以将該結構體指針指向包含的裝置資訊的結構體,進而完成了裝置的注冊。

下面是led裝置的注冊代碼:

static int __init platdev_led_init(void)
{
   int       rv = 0;

   rv = platform_device_register(&s3c_led_device);
   if(rv)
   {   
        printk(KERN_ERR "%s:%d: Can't register platform device %d\n", __FUNCTION__,__LINE__,
rv); 
        return rv; 
   }   
   printk("Regist S3C LED Platform Device successfully.\n");

   return 0;
}
           

s3c_led_device結構體包含裝置資訊:

static struct platform_device s3c_led_device = {
    .name    = "s3c_led",
    .id      = 1,
    .dev     =
    {
        .platform_data = &s3c_led_data,
        .release = platform_led_release,
    },
};
           

于是通過s3c_led_device結構體就将裝置資訊注冊到了platform虛拟總線上。platform_device 結構體的定義如下:

struct platform_device {
const char * name; //裝置名稱
int id;            //裝置id
struct device dev;
u32 num_resources; //裝置使用各類資源的數量
struct resource * resource; //裝置使用的資源


struct platform_device_id *id_entry;
};
           

其中struct device 結構體的定義如下:

struct device {
    struct device       *parent;


    struct device_private   *p;


    struct kobject kobj;
    const char      *init_name; /* initial name of the device */
    const 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;
struct dev_power_domain *pwr_domain;


#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;


    struct device_node  *of_node; /* associated device tree node */


    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);
};
           

在device結構體的platform_data成員中儲存着裝置的特定資訊,對于以上講到的led裝置,我們關心以下的這些資訊:

static struct s3c_led_platform_data s3c_led_data = {
    .leds = s3c_leds,
    .nleds = ARRAY_SIZE(s3c_leds),
};

           

其中s3c_led_platform_data是我們自己定義的結構體,結構體儲存着這個led的特定資訊。

通過以上這些步驟,裝置端的資訊基本描述清楚了。

3.驅動端需要完成的工作:

在驅動端,主要完成下面的工作:

1)調用module_init();當我們insmod時,就将從module_init()開始。

2)調用platform_driver_register(),将驅動注冊到platform虛拟總線。

static int __init platdrv_led_init(void)
{
   int       rv = 0;

   rv = platform_driver_register(&s3c_led_driver);
   if(rv)
   {
        printk(KERN_ERR "%s:%d: Can't register platform driver %d\n", __FUNCTION__,__LINE__,
rv);
        return rv;
   }
   printk("Regist S3C LED Platform Driver successfully.\n");

   return 0;
}
           

3)platform_driver_register的參數是一個platform_driver類型的結構體指針,如果是insmod 一個子產品,将會調用probe函數;如果是rmmod一個子產品,将調用remove函數。

platform_driver結構體:

static struct platform_driver s3c_led_driver = {
    .probe      = s3c_led_probe,
    .remove     = s3c_led_remove,
    .driver     = {
        .name       = "s3c_led",
        .owner      = THIS_MODULE,
    },
};
           

4)probe函數的參數以一個platform_device的結構體指針,該結構體指針指向裝置端的裝置的資訊。在probe函數中完成:裝置硬體的初始化;配置設定主次裝置号;申請cdev結構體;cdev結構體的初始化;将cdev結構體與裝置号綁定并将cdev結構體注冊到核心。cdev結構體是linux驅動中的一種關鍵資料結構,在cdev結構體的初始化過程中,會把file_operations 結構體指向cdev結構體的ops成員。

在remove函數中,需要将在probe函數中申請到的資源釋放,但是需要說明的是,remove函數是在rmmod一個子產品時才調用的。

5)如果想在驅動中完成在/dev路徑下建立裝置節點,可以調用class_create函數和device_create函數。當我們調用device_create函數時,就會在核心裡産生一個hotplug事件,核心本身不會處理這個hotplug事件,他會通知應用程式空間來處理這個hotplug事件。linux核心啟動後,挂載根檔案系統,根檔案系統啟動後,就執行init程序,然後管理權就交給了init程序,init程序會讀/etc/inittab這個腳本,在inittab腳本檔案中指定了系統啟動時應該完成哪些事情。

#Use mdev to auto generate device nod and auto mount SD card and USB storage
::sysinit:/bin/echo /sbin/mdev > /proc/sys/kernel/hotplug  
::sysinit:/sbin/mdev -s
           

proc檔案系統是linux核心和應用程式空間進行資訊傳遞的橋梁,就通過proc檔案告訴應用程式。把/sbin/mdev寫到/proc/sys/kernel/hotplug中,一旦産生一個hotplug事件,linux核心通知應用程式空間,調用/proc/sys/kernel/hotplug檔案裡所指定内容的程式,/sbin/medv 程式預設掃描所有的hotplug事件,建立裝置節點。

4.裝置和驅動如何找到對方:

我們知道,裝置有自己的一條鍊,驅動有自己的一條鍊,而裝置和驅動能夠找到對方是借助作業系統來實作的,裝置和驅動是通過name域來識别的,當有新的裝置連接配接時,作業系統就會到驅動一端找是否有該裝置對應的驅動;當有新的驅動時,作業系統會到裝置端找是否有該驅動對應的裝置。

繼續閱讀