Linux下的platform匯流排驅動
一.Platform裝置驅動概念
主要講解平臺裝置驅動的模型和基本概念,同時因為驅動載入的方式有動態載入和靜態載入兩種方式,這裡我們分別對動態載入和靜態載入兩種情況下,如何使用平臺裝置和驅動加以敘述。最後使用mini2440開發板,運用Platform和device_attribute機制,編寫按鍵驅動程式碼和測試程式碼。
我們知道linux核心中常見的的匯流排有I2C匯流排,PCI匯流排,串列埠匯流排,SPI匯流排,PCI匯流排,CAN匯流排,單匯流排等,所以有些裝置和驅動就可以掛在這些總線上,然後通過總線上的match進行裝置和驅動的匹配。但是有的裝置並不屬於這些常見匯流排,所以我們引入了一種虛擬匯流排,也就是platform匯流排的概念,對應的裝置叫做platform裝置,對應的驅動叫做platform驅動。當然引入platform的概念,可以做的與板子相關的程式碼和驅動的程式碼分離,使得驅動有更好的可擴充套件性和跨平臺性。
1.Platform匯流排
struct bus_type platform_bus_type = {
.name = "platform", //名
.dev_attrs = platform_dev_attrs, //屬性
.match = platform_match, //裝置和驅動的匹配函式
.uevent = platform_uevent, //解除安裝處理
.pm = &platform_dev_pm_ops, //電源管理
};
我們看看裝置和驅動的匹配函式match
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev); //獲得平臺裝置
struct platform_driver *pdrv = to_platform_driver(drv); //獲得平臺驅動
if (pdrv->id_table) //如果平臺驅動有支援項,進入platform_match_id
return platform_match_id(pdrv->id_table, pdev) != NULL;
return (strcmp(pdev->name, drv->name) == 0); //沒有支援項,則老實匹配名字
}
通過上面這個match函式我們知道,如果驅動中定義了驅動支援項,那麼在匯流排執行match函式中,就會將驅動支援項中每一個名字和裝置名字匹配,看看是否匹配成功。如果驅動沒有設定支援項,就會把驅動的名字和裝置的名字匹配,如果一樣,則匹配成功。
2.Platform裝置
struct platform_device {
const char * name; //名
int id;
struct device dev; //內嵌裝置
u32 num_resources; //資源個數
struct resource * resource; //資源結構體
struct platform_device_id *id_entry;
struct pdev_archdata archdata;
};
我們重點來看看platform_device中資源結構體的定義
struct resource {
resource_size_t start; //起始地址
resource_size_t end; //結束地址
const char *name; //名
unsigned long flags; //標號
struct resource *parent, *sibling, *child;
};
對於這個資源結構體中的flags標號可以有IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA四種選擇,重點是申請記憶體(IORESOURCE_MEM)和申請中斷號(IORESOURCE_IRQ)用的比較多。
2.1Platform裝置的靜態載入
所謂的靜態載入,就是把platform裝置編譯進核心,對於platform_device的定義常常在BSP中實現,我們這裡拿Mini2440舉例,看看對於的BSP檔案mach-smdk2440.c
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
這是基於Mini2440的LCD平臺裝置在BSP檔案中的定義,那麼我們怎麼把它加入核心呢?
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd, //新增LCD平臺裝置
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
};
嗯,原來我們建立了一個platform_device陣列,然後把LCD的platform_device新增到這個陣列中,那麼這個platform_device陣列怎麼註冊到核心的呢?
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));//加到核心
smdk_machine_init();
}
看到了吧,在smdk2440_machine_init中,我們呼叫了platform_add_devices函式來把platform_device註冊到核心,再繼續跟蹤下platform_add_devices
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]);
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]); //註冊裝置
break;
}
}
return ret;
}
好了,到此為止,我們已經看到了如果新增platform_device,以及這個platform_device又是如何被註冊到核心的全過程。
除了BSP中定義的資源外,有的裝置可能還會有一些配置資訊,而這些配置資訊依賴於板子,不適合放到驅動中,為此,我們的platform提供了平臺數據platform_data的支援。在核心中新增平臺數據有兩種方式,仍然以LCD為例
static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = { //平臺數據
.displays = &smdk2440_lcd_cfg,
.num_displays = 1,
.default_display = 0,
.lpcsel = ((0xCE6) & ~7) | 1<<4,
};
上面的smdk2440_fb_info就是LCD的平臺數據,我們怎麼把這個LCD的平臺數據告訴LCD的platform_device呢?
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info); //新增平臺數據
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
看到沒?上面的s3c24xx_fb_set_platdata函式就完成了平臺數據的新增,繼續跟蹤這個s3c24xx_fb_set_platdata函式
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
struct s3c2410fb_mach_info *npd;
npd = kmalloc(sizeof(*npd), GFP_KERNEL);
if (npd) {
memcpy(npd, pd, sizeof(*npd));
s3c_device_lcd.dev.platform_data = npd; //平臺數據新增的實現
} else {
printk(KERN_ERR "no memory for LCD platform data\n");
}
}
好了,我們可以看到其實把這個平臺數據儲存在了平臺裝置中內嵌的裝置結構體的platform_data中。剛才說了新增平臺數據有兩種方式,根據上面的原理,其實我們可以直接把平臺數據儲存在了平臺裝置中內嵌的裝置結構體的platform_data中,具體程式碼如下
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
.platform_data=&smdk2440_fb_info //新增平臺數據
}
};
到此為止,我們已經明白了platform_device的靜態新增全過程。
2.2 Platform裝置的動態載入
由於靜態新增platform_device需要最後編譯核心,這個不利於修改,所以在開發階段,我們可以採用platform裝置的動態載入方法。具體操作是:先分配platform_device,然後向platform_device中新增資源結構體,最後把platform_device註冊到核心,對應三個函式如下
struct platform_device my_device = platform_device_alloc("s3c2410-buttons", -1);
platform_device_add_resources(my_device, s3c_buttons_resource, 3);
ret = platform_device_add(my_device);
當然,上面三個函式還是封裝在模組載入函式中,也就是把平臺裝置的載入寫成一個模組的形式。
3. Platform驅動
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; //驅動支援項
};
根據上面的platform_driver結構體的定義,我們需要思考下platform驅動名字在哪裡呢?實際上在內嵌的裝置驅動中定義的。
3.1 Platform驅動的靜態載入
寫一個驅動,測試驅動階段我們一般採用動態載入的方式,當驅動已經成型,我們就會採用靜態載入的方式,把驅動編譯入核心。把驅動靜態編譯入核心的方式主要是根據Makefile和Kconfig兩張地圖,在Makefile中新增驅動檔名,在Kconfig中新增對應的驅動選單選項,當我面make zImage時就會自動編譯我們的驅動檔案。
3.2 Platform驅動的動態載入
拿一個基於平臺裝置的按鍵驅動例子看看
static struct platform_driver my_driver = {
.probe = my_probe, //探測函式
.remove = my_remove,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-buttons",
},
};
上面是按鍵驅動中定義了的一個platform_driver,然後我們只需要在驅動模組載入函式中執行platform_driver_register(&my_driver)就可以把platform驅動加入核心了。在我們進行insmod載入時就會呼叫這個模組載入函式,從而註冊platform驅動。
二.平臺裝置的資源
1.平臺數據和私有資料的區別
前面在講平臺裝置的靜態載入的時候,我們提到平臺數據的概念,在核心驅動程式碼中還會出現私有資料這一名詞。那麼平臺數據和私有資料有什麼區別呢?首先平臺數據是由於引入平臺裝置而產生的,平臺數據主要儲存的是一些依賴的板子的配置資訊,平臺數據的定義是定義在平臺裝置所在的BSP中的,我們在平臺驅動中可以進行讀取到在BSP中定義的平臺數據。而私有資料是作為一個驅動中儲存裝置資訊的一個結構體,它定義在平臺驅動中,而不是BSP中,我們在平臺驅動中可以把一個裝置結構體設定為這個平臺驅動的私有資料,也可以根據這個平臺裝置,讀取這個平臺裝置的私有資料。
好了,下面我們先看看怎麼在平臺驅動中讀取在BSP中定義的平臺數據,仍然以LCD為例,只需要在裝置驅動需要獲取平臺數據的地方執行如下程式碼
struct s3c2410fb_mach_info *pdata=pdev->dev.platform_data;
接下來,我們研究下私有資料。私有資料的定義各種各樣,總之是一個結構體。那麼怎麼將一個裝置結構體設定為平臺裝置的私有資料呢?
struct buttons *key;
platform_set_drvdata(pdev, key);
同樣怎麼根據這個平臺裝置,讀取這個平臺裝置的私有資料呢?
Struct buttons *keyt=platform_get_drvdata(pdev);
最後補充兩個點:第一,根據經驗發現平臺數據是為私有資料服務的,也就是平臺數據可能成為私有資料的一部分。第二,對於由裝置獲得平臺裝置的情況,我們可以通過*pdev=to_platform_device(dev)程式碼獲得。
2. Platform裝置資源的讀取
我們在BSP中定義了平臺裝置的資源,那麼怎麼獲取這些資源呢?首先我們要明白,裝置和驅動的第一次匹配是發生在總線上的match函式中,這次匹配成功後所做的操作只是把裝置和驅動相連。當我們執行平臺驅動中的probe時,會發生第二次裝置和驅動的匹配,也就是所謂的探測。所以,我們對在BSP中定義的平臺裝置的資源會在平臺驅動的probe函式中讀取到,下面我們就看看如何讀取這些資源了。
對於資源中的儲存空間的資源讀取,首先讀取資源,然後申請空間,最後完成由虛擬地址到實體地址的對映。具體函式如下
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
struct resource *buttons_mem = request_mem_region(res->start,
res->end-res->start+1, pdev->name);
void __iomem *buttons_base = ioremap(res->start, res->end - res->start + 1);
對於中斷資源的讀取,只要一步如下操作即可。
struct resource *buttons_irq1 = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
三.平臺裝置驅動測試
這裡我們採用Mini2440開發板,編寫基於平臺裝置的按鍵驅動,要求按鍵驅動觸發方式為單邊沿觸發,同時要求新增裝置屬性項。因為這個驅動比較簡單,我就不去細緻分析了,如果對硬體不理解可以參考mini2440開發板資料手冊,如果對軟體不理解,可以參考上文平臺裝置的講解。在此,我提供platform裝置模組程式碼,platform驅動模組程式碼,應用層測試程式碼,需要注意的是在動態載入測試時需要先載入裝置模組,再載入驅動模組。
1. platform裝置模組程式碼
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#define GPGCON 0x56000060 //控制埠地址
#define GPGDAT 0x56000064 //資料埠地址
ssize_t test_show(struct device *dev, struct attribute *attr, char *buf);
ssize_t test_store(struct device *dev, struct attribute *attr, char *buf,size_t count);
static DEVICE_ATTR(buttons, S_IRWXUGO, test_show, test_store); //裝置屬性
ssize_t test_show(struct device *dev, struct attribute *attr, char *buf) //讀裝置屬性
{
printk("call : test_show . \n");
printk("attrname:%s . \n",attr->name);
sprintf(buf,"%s\n",attr->name);
return strlen(attr->name)+2;
}
ssize_t test_store(struct device *dev, struct attribute *attr, char *buf,size_t count) //寫裝置屬性
{
printk("call : test_store . \n");
printk("write : %s . \n",buf);
strcpy(attr->name,buf);
return count;
}
static struct resource s3c_buttons_resource[]=
{
[0]={ //記憶體資源
.start = GPGCON,
.end = GPGDAT,
.flags=IORESOURCE_MEM,
},
[1]={ //中斷號
//KEY1
.start = IRQ_EINT8,
.end = IRQ_EINT8,
.flags=IORESOURCE_IRQ,
},
[2]={
//KEY2
.start = IRQ_EINT11,
.end = IRQ_EINT11,
.flags=IORESOURCE_IRQ,
},
};
MODULE_AUTHOR("WJB");
MODULE_LICENSE("Dual BSD/GPL");
static struct platform_device *my_device = NULL;
static int __init my_device_init(void)
{
int ret = 0;
my_device = platform_device_alloc("s3c2410-buttons", -1); //申請平臺裝置
platform_device_add_resources(my_device, s3c_buttons_resource, 3); //新增資源
ret = platform_device_add(my_device); //註冊平臺裝置
device_create_file(&my_device->dev,&dev_attr_buttons); //新增裝置屬性
if(ret)
platform_device_put(my_device);
return ret;
}
static void my_device_exit(void)
{
platform_device_unregister(my_device);
device_remove_file(&my_device->dev,&dev_attr_buttons);
}
module_init(my_device_init);
module_exit(my_device_exit);
2. platform驅動模組程式碼
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
MODULE_AUTHOR("WJB");
MODULE_LICENSE("Dual BSD/GPL");
#define BUTTONS_12INPUT_MASK 0x41
struct button_irq_desc { //私有資料結構體
int number;
char *name;
};
static struct button_irq_desc buttons_irqs [] = { //私有資料
{ 0, "KEY1"},
{ 1, "KEY2"},
};
static volatile char key_values [] = {'0', '0'};
static DECLARE_WAIT_QUEUE_HEAD(button_waitq); //定義等待佇列
static volatile int ev_press = 0;
static struct resource *buttons_irq1;
static struct resource *buttons_irq2;
static struct resource *buttons_mem;
static void __iomem *buttons_base;
static irqreturn_t s3c2410buttons_irq(int irq, void *dev_id)
{
struct button_irq_desc *buttons_irqs = (struct button_irq_desc *)dev_id;
unsigned int tmp;
void __iomem *base = buttons_base;
tmp=readb(base+0x04);
if(buttons_irqs->number==0)
{
tmp &=0x01;
}else{
tmp &=0x08;
}
// process data
if (tmp == (key_values[buttons_irqs->number] & 1)) { // Changed
key_values[buttons_irqs->number] = '1' ;
}
ev_press = 1;
wake_up_interruptible(&button_waitq);
return IRQ_RETVAL(IRQ_HANDLED);
}
static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{ int ret;
unsigned int tmp;
void __iomem *base = buttons_base;
// set key1 and key2 input
tmp=readb(base);
writeb(tmp|BUTTONS_12INPUT_MASK ,base);
ret = request_irq(buttons_irq1->start, s3c2410buttons_irq,IRQ_TYPE_EDGE_FALLING, "KET1", (void *)&buttons_irqs[0]);
if (ret != 0) {
printk( "failed to install irq (%d)\n", ret);
goto err1;
}
ret = request_irq(buttons_irq2->start, s3c2410buttons_irq, IRQ_TYPE_EDGE_FALLING, "KET2", (void *)&buttons_irqs[1]);
if (ret != 0) {
printk( "failed to install irq (%d)\n", ret);
goto err2;
}
ev_press = 1;
return 0;
err2: disable_irq(buttons_irq2->start);
free_irq(buttons_irq2->start, (void *)&buttons_irqs[1]);
err1: disable_irq(buttons_irq1->start);
free_irq(buttons_irq1->start, (void *)&buttons_irqs[0]);
return -EBUSY;
}
static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
disable_irq(buttons_irq2->start);
free_irq(buttons_irq2->start, (void *)&buttons_irqs[1]);
disable_irq(buttons_irq1->start);
free_irq(buttons_irq1->start, (void *)&buttons_irqs[0]);
return 0;
}
static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
unsigned long err;
int i;
if (!ev_press) {
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
else
wait_event_interruptible(button_waitq, ev_press);
}
ev_press = 0;
err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count));
for (i=0;i<2;i++)
{
key_values[i]='0';
}
return err ? -EFAULT : min(sizeof(key_values), count);
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = s3c24xx_buttons_open,
.release = s3c24xx_buttons_close,
.read = s3c24xx_buttons_read,
};
static struct miscdevice s3c2410buttons_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "s3c2410-buttons",
.fops = &dev_fops,
};
static int my_probe(struct platform_device* pdev)
{
int ret;
struct resource *res;
struct device *dev;
dev = &pdev->dev;
// get resource
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory region resource\n");
return -ENOENT;
}
buttons_mem = request_mem_region(res->start,
res->end-res->start+1,
pdev->name);
if (buttons_mem == NULL) {
dev_err(&pdev->dev, "failed to reserve memory region\n");
ret = -ENOENT;
goto err_nores;
}
buttons_base = ioremap(res->start, res->end - res->start + 1);
if (buttons_base == NULL) {
dev_err(&pdev->dev, "failed ioremap()\n");
ret = -EINVAL;
goto err_nores;
}
//get key1 interrupt
buttons_irq1 = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (buttons_irq1 == NULL) {
dev_err(dev, "no irq resource specified\n");
ret = -ENOENT;
goto err_map;
}
//get key2 interrupt
buttons_irq2 = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
if (buttons_irq2 == NULL) {
dev_err(dev, "no irq resource specified\n");
ret = -ENOENT;
goto err_map;
}
// register misc device
ret = misc_register(&s3c2410buttons_miscdev);
if (ret) {
dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
WATCHDOG_MINOR, ret);
goto err_map;
}
printk("driver found device which my driver can handle!\n");
err_map:
iounmap(buttons_base);
err_nores:
release_resource(buttons_mem);
kfree(buttons_mem);
return ret;
}
static int my_remove(struct platform_device* pdev)
{
release_resource(buttons_mem);
kfree(buttons_mem);
buttons_mem = NULL;
free_irq(buttons_irq1->start, (void *)&buttons_irqs[0]);
buttons_irq1 = NULL;
free_irq(buttons_irq2->start, (void *)&buttons_irqs[1]);
buttons_irq2 = NULL;
iounmap(buttons_base);
misc_deregister(&s3c2410buttons_miscdev);
printk("drvier found device unpluged!/n");
return 0;
}
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-buttons",
},
};
static int __init my_driver_init(void)
{
return platform_driver_register(&my_driver);
}
static void my_driver_exit(void)
{
platform_driver_unregister(&my_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
3.應用層測試程式碼
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
int main(void)
{
int buttons_fd;
char buttons[2] = {'0', '0'};
buttons_fd = open("/dev/s3c2410-buttons", 0);
if (buttons_fd < 0) {
perror("open device buttons");
exit(1);
}
for (;;) {
char current_buttons[2];
int count_of_changed_key;
int i;
if (read(buttons_fd, current_buttons, sizeof current_buttons) != sizeof current_buttons) {
perror("read buttons:");
exit(1);
}
for (i = 0, count_of_changed_key = 0; i < sizeof buttons / sizeof buttons[0]; i++) {
if (buttons[i] != current_buttons[i]) {
printf("%skey %d is %s", count_of_changed_key? ", ": "", i+1, buttons[i] == '0' ? "up" : "down");
count_of_changed_key++;
}
}
if (count_of_changed_key) {
printf("\n");
}
}
close(buttons_fd);
return 0;
}
平臺裝置測試:
在超級終端下:
cd /home/platform/device
insmod device.ko
cd ../driver
insmod driver.ko
cd ../
./buttons
然後通過Mini2440開發板的按鍵,觀察到超級終端的按鍵資訊。
裝置屬性項測試:
在超級終端下:
cd /sys/platform/ s3c2410-buttons
ls後,會顯示buttons這一目錄
讀取裝置屬性:cat buttons
修改裝置屬性:echo modify>buttons