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域來識别的,當有新的裝置連接配接時,作業系統就會到驅動一端找是否有該裝置對應的驅動;當有新的驅動時,作業系統會到裝置端找是否有該驅動對應的裝置。