1. 程式人生 > >Linux中的平臺裝置驅動模型

Linux中的平臺裝置驅動模型

在 Linux 的裝置驅動模型中,關心匯流排、裝置和驅動這 3 個實體,匯流排將裝置和驅動繫結。在系統每註冊一個裝置的時候,會尋找與之匹配的驅動;相反的,在系統每註冊一個驅動的時候,會尋找與之匹配的裝置,而匹配由匯流排完成。
 

平臺裝置匹配的依據是:
         1)根據平臺裝置結構和平臺驅動結構中的name成員進行匹配。-- 一個裝置對應一個驅動
         2)根據平臺裝置結構中的name和平臺驅動中的id_table 成員中的列表進行匹配  --一個驅動對應多個裝置

匹配過程:
         以name匹配過程:
         1)先安裝驅動,再安裝裝置
         安裝驅動,把驅動結構存放在核心驅動連結串列上,當安裝一個裝置時,核心觸發一個動作,把裝置結構中的name取出來和驅動連結串列上的每一個結構中的name進行字串比較,如果相同,則呼叫驅動中的探測函式
         
         2)先安裝裝置,再安裝驅動
          安裝裝置,把裝置結構存放在核心裝置連結串列上,當安裝一個驅動時,核心觸發一個動作,把驅動結構中的name取出來和裝置連結串列上的每一個結構中的name進行字串比較,如果相同,則呼叫驅動中的探測函式    。

當探測函式呼叫時,傳遞一個實際引數,這個引數就是和它匹配的平臺裝置結構的地址。這樣就實現兩個層關聯。

platform 裝置層程式設計:

需要實現的結構體是: struct platform_device 。
1)初始化 struct resource 結構變數
2)初始化 struct platform_device 結構變數
3)向系統註冊裝置:使用 platform_device_register()函式。
kernel\include\linux\platform_device.h 中, struct platform_device 結構如下:

struct platform_device {
const char * name; //裝置名,要求和驅動中的.name 相同。
int id; //裝置 ID,一般為-1
struct device dev; //內嵌標準 device,一般可以用來傳遞平臺數據
u32 num_resources; //裝置佔用資源個數
struct resource * resource; //裝置佔用資源的首地址。
struct platform_device_id *id_entry;//裝置 id 入口,一般驅動不用
/* arch specific additions */
struct pdev_archdata archdata;
};

kernel\include\linux\ioport.h

struct resource {
resource_size_t start; //資源起始實體地址
resource_size_t end; //資源結束實體地址
const char *name; //資源名稱,可以隨便寫,一般要有意義。
unsigned long flags; //資源型別, IO,記憶體,中斷, DMA。
struct resource *parent, *sibling, *child;
};

struct resource 結構中 flags 成員是指資源的型別,目前可用資源型別定義在 include\linux\ioport.h 檔案
中。 分別是:

#define IORESOURCE_IO 0x00000100 // IO 空間。一般 在 X86 框架中存在, ARM 一般沒有
#define IORESOURCE_MEM 0x00000200 // 記憶體空間,佔用的是 CPU 4G 統一編址空間
#define IORESOURCE_IRQ 0x00000400 // 中斷資源,實際上就是中斷號
#define IORESOURCE_DMA 0x00000800 // 記憶體空間,佔用的是 CPU 4G 統一編址空間,但是這個空
間是用來做 dma 模型使用的緩衝空間。

另外一個很重要的成員是 struct device,這個成員是用來實現裝置模型的,其中的 void
*platform_data 成員用途很大, 一般稱為平臺數據指標, 可以給平臺驅動層傳遞任何資訊需要的資訊,因這
個成員指標型別是 void 型別的,在平臺裝置驅動模型中,隨處可以看到這個成員的蹤跡,在例子中會看到
如何使用。 以下 struct device 的結構資訊:

struct device {
struct device *parent; /* 父裝置指標 */
struct device_private *p;
struct kobject kobj;
const char *init_name; /*邏輯裝置的名字*/
struct device_type *type; /* 裝置型別 */
struct semaphore sem;/* semaphore to synchronize calls to its driver. */
struct bus_type *bus; /* 裝置所屬的匯流排型別 */
struct device_driver *driver; /* 指向開闢 struct device 結構的 driver 指標*/
void *platform_data; /* 平臺裝置的私有資料指標, 要以傳遞任何結構*/
struct dev_pm_infopower;
#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;
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);
};

platform 裝置層 API

int platform_device_register(struct platform_device *pdev)
void platform_device_unregister(struct platform_device *pdev)
int platform_add_devices(struct platform_device **devs, int num)//把 devs 陣列中的 num 個平臺裝置 struct platform_device 結構註冊到核心中,這 個 函 數 內 部 調 用 的 還 是 platform_device_register(struct platform_device *pdev) , 不 同 在 於platform_device_register(struct platform_device *pdev)只註冊單個平臺裝置, platform_add_devices(struct
platform_device **devs, int num)註冊多個平臺裝置結構。
 

platform 驅動層程式設計:

驅動層需要實現的結構體是 struct platform_driver ,以下是對 platform 驅動層程式設計簡要說明:
1. 編寫探測函式 probe
2. 編寫探測函式 remove
3. 填充 struct platform_driver 下 struct device_driver driver 成員的子成員 name,name 值要和裝置層
struct platform_device 的 name 相同。
4. 呼叫 int platform_driver_register(struct platform_driver *drv)函式進行註冊前面填充好的 struct
platform_driver 結構即可。
說明: struct platform_driver 結構中除 probe, remove 要實現外, 其他成員函式可以根據需要決定是否
要實現。 probe, remove 函式所做的工作幾乎是相反的,有特定的函式框架。
 

probe 函式的框架:
1. 獲取平臺裝置私有資料。
2. 獲取平臺裝置佔用的物理資源。
3. 如果是記憶體資源,則向核心申請實體記憶體資源,申請成功後對實體記憶體空間進行重對映,轉換為虛
擬地址空間,再使用虛擬地址空間進行硬體的配置。如果是中斷資源,則進行中斷函式註冊,註冊方法前面
章節已經講述過。
4. 硬體初始化。
5. 註冊使用者空間的介面:可以是/dev/目錄下的介面,也可以是/sys/或/proc/目錄的介面.目前我們已經
學習的是/dev/目錄下生成使用者空間的訪問介面,也就是註冊字元裝置。
remov 函式的框架:
這個函式是和 probe 函式相反的,簡單說, probe 函式中只要申請了資源,在這個函式中就要釋放相應
的資源。

平臺驅動層核心資料結構
在核心中平臺驅動模型的驅動層使用 struct platform_driver 結構來描述一個裝置的驅動資訊,定義在
include\linux\platform_device.h 檔案中,結構如下:

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;
struct platform_device_id *id_table;
};

 這個結構中 probe, remove 是必須的要實現的函式成員,其他函式成員根據需要自行決定是否要實現,struct device_driver driver 成員中的 name 成員很重要,它的內容必須要和裝置層核心結構 struct platform_device 的 name 成員相同,才能實現驅動層和裝置層的繫結。
struct device_driver 結構如下:

struct device_driver {
const char *name; /*驅動層的名字,用來和裝置層匹配的*/
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
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);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};

int platform_driver_register(struct platform_driver *drv)
void platform_driver_unregister(struct platform_driver *drv)
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)

int platform_get_irq(struct platform_device *dev, unsigned int num)
device:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>

static struct resource led_resource[] = {
    [0] = {
        .start = 0x56000010,
        .end   = 0x56000010 + 16 -1,
        .flags = IORESOURCE_MEM,
        //.name = "ledres"
    },  
};

static void led_release(struct device * dev)
{
    printk(KERN_EMERG"%s is call\r\n",__FUNCTION__);
    printk(KERN_EMERG"%s\r\n%d,%s is call\r\n",__FILE__,__LINE__,__FUNCTION__);       
}

/*
一個位表示一個
int dat = 1<<11 | 1<<2 |  1<<13 | 1<<8 ;  

在探測函式中計算 dat 1的個數。--leds個數
  ioctl有使用:
  args > 個數
  arg:0  --dat 低位開始第1個為1的位    2   
 	arg:1  --dat 低位開始第2個為1的位    8
 	arg:3  --dat 低位開始第2個為1的位    11   	
 	arg:4  --dat 低位開始第2個為1的位    13   
*/
int dat = 0xa0;  
static struct platform_device led_dev = {
    .name = "s3c2440leds",
    .id   = -1,
    .num_resources = ARRAY_SIZE(led_resource),
    .resource = led_resource,
    .dev = {
        .release = led_release,
        .platform_data = &dat;
    },
};



static int leddev_init(void)
{
	
    printk(KERN_EMERG"%s is call\r\n",__FUNCTION__);

    platform_device_register(&led_dev);
    return 0;
}

static void leddev_exit(void)
{
	
    printk(KERN_EMERG"%s is call\r\n",__FUNCTION__);

    platform_device_unregister(&led_dev);
}

module_init(leddev_init);
module_exit(leddev_exit);

MODULE_LICENSE("GPL");

driver:

/*
 *   1.如果要在X86下測試驅動,
 *   1)Makefile使用 KDIR := /lib/modules/$(shell uname -r)/build
 *   2)請使用 make EXTRA_CFLAGS+=-DPLATX86 編譯,
 *   3)原因:X86平臺下申請程式碼中設定的地址時候有可能失敗,程式中止,導致後面無法看到效果
 *
 *   2.如果在ARM平臺下測試請修改Makefile中的KDIR為自己的核心原始碼路徑,再使用make編譯
 *
 *   3.不管使用哪個編譯命令,其中的app測試程式都會根據測試平臺使用相應編譯 工具編譯。
 * */

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/ioctl.h>
#include<linux/types.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/fs.h>
//#include <mach/map.h>
#include <asm/io.h>  //包含了IO記憶體的相關操作
#include <linux/platform_device.h>

//探測函式
static int led_probe(struct platform_device *pdev)
{
	printk(KERN_EMERG"%s is call\r\n",__FUNCTION__);
	printk(KERN_EMERG"pdev->name:%s\r\n",pdev->name);	
	
	printk(KERN_EMERG"pdev->resource[0]->flags:%x\r\n",
	      (unsigned int)pdev->resource[0].flags);
	      
	printk(KERN_EMERG"pdev->resource[0]->start:%x,pdev->resource[0]->end:%x\r\n",
		    (unsigned int)pdev->resource[0].start,
		    (unsigned int)pdev->resource[0].end);	
		    
	return 0;
}


//在刪除裝置時候會呼叫
static int led_remove(struct platform_device *pdev)
{
	printk(KERN_EMERG"%s\r\n%d,%s is call\r\n",__FILE__,__LINE__,__FUNCTION__);  //排程資訊
	return 0;
}


//平臺裝置驅動結構
struct platform_driver led_driver = {
	.probe  = led_probe,
	.remove = led_remove,
	.driver = {
		.owner    = THIS_MODULE,
		.name = "s3c2440ledsqqq",
	},
};

//驅動初始化函式
static int __init leddrv_init(void)
{	
	printk(KERN_EMERG"%s is call\r\n",__FUNCTION__);	
	platform_driver_register(&led_driver);
	return 0;
}

//驅動解除安裝函式
static void __exit leddrv_exit(void)
{
	printk(KERN_EMERG"%s is call\r\n",__FUNCTION__);
	platform_driver_unregister(&led_driver);
}

module_init(leddrv_init);
module_exit(leddrv_exit);

MODULE_LICENSE("GPL");

app:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(void)
{
    int fd;
    
    fd = open("/dev/platform_leds", 0);//0表示只讀,驅動下的裝置節點

    perror("open device /dev/cdev_leds!");
    if (fd < 0)
    {
        perror("open device /dev/cdev_leds!");//
        exit(1);
    }

    while(1)
    {
        int i =0;
        int ret =0;
				for(i=0; i < 4;i++)
				{
					ret = ioctl(fd, 0, i);//1:控制代碼;傳給核心的,2.3是傳給驅動的
					if(ret < 0)
					{
						perror("ioctl(fd, 0, 0):");
						exit(i);
					}
					 sleep(1); //睡眠函式
				}
		
				for(i=0; i < 4;i++)
				{
					ret = ioctl(fd, 1, i);//1:控制代碼;傳給核心的,2.3是傳給驅動的
					if(ret < 0)
					{
						perror("ioctl(fd, 1, 0):");
						exit(i);
					}
					 sleep(1); //睡眠函式
				} 
    }
	
    close(fd);
    return 0;
}

makefile:

#X86平臺測試使用 make EXTRA_CFLAGS+=-DPLATX86 編譯,arm平臺測試使用make編譯
#CFLAGS += -D PLATX86
#EXTRA_CFLAGS += -D PLATX86   //可以用來給編譯核心檔案時候給C檔案傳遞全域性巨集定義


# Makefile 2.6
ifneq ($(KERNELRELEASE),)
obj-m += 1th_chardev_device.o
obj-m += 1th_chardev_driver.o
else


#KDIR := /lib/modules/$(shell uname -r)/build
#KDIR := /root/work/source/4412/linux_kernel/FriendlyARM/linux-3.5
KDIR :=  /media/sdb1/4412/linux-3.5
all:
	make  -C $(KDIR) M=$(PWD)  modules 
	cp 1th_chardev_driver.ko  1th_chardev_device.ko  /root/work/root_nfs/home/  
	rm -rf .*.cmd *.o *.mod.o *.mod.c *.symvers *.cmd *.bak *.order *.unsigned .tmp_versions
#	
	$(CC) -g -o 1th_chardev_app_plat 1th_chardev_app_plat.c
	cp 1th_chardev_app_plat  /root/work/root_nfs/home/  

clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.cmd *.bak *.order

endif