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