歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

Linux 設備驅動開發 —— platform設備驅動應用實例解析

前面我們已經學習了platform設備的理論知識Linux 設備驅動開發 —— platform 設備驅動 ,下面將通過一個實例來深入我們的學習。
一、platform 驅動的工作過程
platform模型驅動編程,需要實現platform_device(設備)platform_driver(驅動)platform(虛擬總線)上的注冊、匹配,相互綁定,然後再做為一個普通的字符設備進行相應的應用,總之如果編寫的是基於字符設備的platform驅動,在遵循並實現platform總線上驅動與設備的特定接口的情況下,最核心的還是字符設備的核心結構:cdev、
file_operations(他包含的操作函數接口)、dev_t(設備號)、設備文件(/dev)等,因為用platform機制編寫的字符驅動,它的本質是字符驅動。
我們要記住,platform 驅動只是在字符設備驅動外套一層platform_driver 的外殼。
在一般情況下,2.6內核中已經初始化並掛載了一條platform總線在sysfs文件系統中。那麼我們編寫platform模型驅動時,需要完成兩個工作:
a -- 實現platform驅動
b -- 實現platform設備
然而在實現這兩個工作的過程中還需要實現其他的很多小工作,在後面介紹。platform模型驅動的實現過程核心架構就很簡單,如下所示:

platform驅動模型三個對象:platform總線platform設備platform驅動
platform總線對應的內核結構:struct bus_type-->它包含的最關鍵的函數:match() (要注意的是,這塊由內核完成,我們不參與)
platform設備對應的內核結構:struct platform_device-->注冊:platform_device_register(unregister)
platform驅動對應的內核結構:struct platform_driver-->注冊:platform_driver_register(unregister)
那具體platform驅動的工作過程是什麼呢:
設備(或驅動)注冊的時候,都會引發總線調用自己的match函數來尋找目前platform總線是否掛載有與該設備(或驅動)名字匹配的驅動(或設備),如果存在則將雙方綁定;
如果先注冊設備,驅動還沒有注冊,那麼設備在被注冊到總線上時,將不會匹配到與自己同名的驅動,然後在驅動注冊到總線上時,因為設備已注冊,那麼總線會立即匹配與綁定這時的同名的設備與驅動,再調用驅動中的probe函數等;
如果是驅動先注冊,同設備驅動一樣先會匹配失敗,匹配失敗將導致它的probe函數暫不調用,而是要等到設備注冊成功並與自己匹配綁定後才會調用。
二、實現platform 驅動與設備的詳細過程
1、思考問題?
在分析platform 之前,可以先思考一下下面的問題:
a -- 為什麼要用 platform 驅動?不用platform驅動可以嗎?
b -- 設備驅動中引入platform 概念有什麼好處?
現在先不回答,看完下面的分析就明白了,後面會附上總結。
2、platform_device 結構體 VS platform_driver 結構體
這兩個結構體分別描述了設備和驅動,二者有什麼關系呢?先看一下具體結構體對比
設備(硬件部分):中斷號,寄存器,DMA等
platform_device 結構體
驅動(軟件部分)
platform_driver 結構體
struct platform_device {
const char *name; 名字
int id;
bool id_auto;
struct device dev; 硬件模塊必須包含該結構體
u32 num_resources; 資源個數
struct resource *resource; 資源 人脈
const struct platform_device_id *id_entry;
/* arch specific additions */
struct pdev_archdata archdata;
};
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;內核裡所有的驅動程序必須包含該結構體
const struct platform_device_id *id_table; 八字
};
設備實例:
static struct platform_device hello_device=
{
.name = "bigbang",
.id = -1,
.dev.release = hello_release,
};
驅動實例:
static struct platform_driver hello_driver=
{
.driver.name = "bigbang",
.probe = hello_probe,
.remove = hello_remove,
};
前面提到,實現platform模型的過程就是總線對設備和驅動的匹配過程 。打個比方,就好比相親,總線是紅娘,設備是男方,驅動是女方:
a -- 紅娘(總線)負責男方(設備)和女方(驅動)的撮合;
b -- 男方(女方)找到紅娘,說我來登記一下,看有沒有合適的姑娘(漢子)—— 設備或驅動的注冊
c -- 紅娘這時候就需要看看有沒有八字(二者的name 字段)匹配的姑娘(漢子)——match 函數進行匹配,看name是否相同;
d -- 如果八字不合,就告訴男方(女方)沒有合適的對象,先等著,別急著亂做事 —— 設備和驅動會等待,直到匹配成功;
e -- 終於遇到八字匹配的了,那就結婚呗!接完婚,男方就向女方交代,我有多少存款,我的房子在哪,錢放在哪等等(struct resource *resource),女方說好啊,於是去房子裡拿錢,去給男方買菜啦,給自己買衣服、化妝品、首飾啊等等(int (*probe)(struct
platform_device *
) 匹配成功後驅動執行的第一個函數),當然如果男的跟小三跑了(設備卸載),女方也不會繼續待下去的( int (*remove)(struct platform_device *))。
3、設備資源結構體
在struct platform_device 結構體中有一重要成員 struct resource *resource
[cpp] view
plain copy





struct resource {
resource_size_t start; 資源起始地址
resource_size_t end; 資源結束地址
const char *name;
unsigned long flags; 區分是資源什麼類型的
struct resource *parent, *sibling, *child;
};
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_IRQ 0x00000400
flags 指資源類型,我們常用的是 IORESOURCE_MEM、IORESOURCE_IRQ 這兩種。start 和 end 的含義會隨著 flags而變更,如
a -- flags為IORESOURCE_MEM 時,start 、end 分別表示該platform_device占據的內存的開始地址和結束值
b -- flags為 IORESOURCE_IRQ 時,start 、end 分別表示該platform_device使用的中斷號的開始地址和結束值
下面看一個實例:
[cpp] view
plain copy





static struct resource beep_resource[] =
{
[0] = {
.start = 0x114000a0,
.end = 0x114000a0+0x4,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 0x139D0000,
.end = 0x139D0000+0x14,
.flags = IORESOURCE_MEM,
},
};
4、將字符設備添加到 platform的driver中
前面我們提到platform 驅動只是在字符設備驅動外套一層platform_driver 的外殼,下面我們看一下添加的過程:
[cpp] view
plain copy





static struct file_operations hello_ops=
{
.open = hello_open,
.release = hello_release,
.unlocked_ioctl = hello_ioctl,
};
static int hello_remove(struct platform_device *pdev)
{
注銷分配的各種資源
}
static int hello_probe(struct platform_device *pdev)
{
1.申請設備號
2.cdev初始化注冊,&hello_ops
3.從pdev讀出硬件資源
4.對硬件資源初始化,ioremap,request_irq( )
}
static int hello_init(void)
{
只注冊 platform_driver
}
static void hello_exit(void)
{
只注銷 platform_driver
}
可以看到,模塊加載和卸載函數僅僅通過paltform_driver_register()、paltform_driver_unregister() 函數進行 platform_driver 的注冊和注銷,而原先注冊和注銷字符設備的工作已經被移交到 platform_driver
的 probe() 和 remove() 成員函數中

5、platform是如何匹配device和driver
這時就該總線出場了,系統為platform總線定義了一個bus_type 的實例platform_bus_type,其定義如下:
[cpp] view
plain copy





struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
其又是怎樣工作的呢?在platform.c (e:\linux-3.14-fs4412\drivers\base) 31577 2014/3/31 中可以看到
[cpp] view
plain copy





__platform_driver_register()
{
drv->driver.bus = &platform_bus_type; 536行
}
在 platform_bus_type 中調用 了platform_match:
[cpp] view
plain copy





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);
匹配設備樹信息,如果有設備樹,就調用 of_driver_match_device() 函數進行匹配
if (of_driver_match_device(dev, drv))
return 1;
匹配id_table
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
最基本匹配規則
return (strcmp(pdev->name, drv->name) == 0);
}
6、解決問題
現在可以回答這兩個問題了
a -- 為什麼要用 platform 驅動?不用platform驅動可以嗎?
b -- 設備驅動中引入platform 概念有什麼好處?
引入platform模型符合Linux 設備模型 —— 總線、設備、驅動,設備模型中配套的sysfs節點都可以用,方便我們的開發;當然你也可以選擇不用,不過就失去了一些platform帶來的便利;
設備驅動中引入platform 概念,隔離BSP和驅動。在BSP中定義platform設備和設備使用的資源、設備的具體匹配信息,而在驅動中,只需要通過API去獲取資源和數據,做到了板相關代碼和驅動代碼的分離,使得驅動具有更好的可擴展性和跨平台性。
三、實例
這是一個蜂鳴器的驅動,其實前面已經有解析 Linux 字符設備驅動開發基礎(二)—— 編寫簡單 PWM 設備驅動, 下面來看一下,套上platform 外殼後的程序:
1、device.c
[cpp] view
plain copy





#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
static struct resource beep_resource[] =
{
[0] ={
.start = 0x114000a0,
.end = 0x114000a0 + 0x4,
.flags = IORESOURCE_MEM,
},
[1] ={
.start = 0x139D0000,
.end = 0x139D0000 + 0x14,
.flags = IORESOURCE_MEM,
}
};
static void hello_release(struct device *dev)
{
printk("hello_release\n");
return ;
}
static struct platform_device hello_device=
{
.name = "bigbang",
.id = -1,
.dev.release = hello_release,
.num_resources = ARRAY_SIZE(beep_resource),
.resource = beep_resource,
};
static int hello_init(void)
{
printk("hello_init");
return platform_device_register(&hello_device);
}
static void hello_exit(void)
{
printk("hello_exit");
platform_device_unregister(&hello_device);
return;
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);
2、driver.c
[cpp] view
plain copy





#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/io.h>
static int major = 250;
static int minor=0;
static dev_t devno;
static struct class *cls;
static struct device *test_device;
#define TCFG0 0x0000
#define TCFG1 0x0004
#define TCON 0x0008
#define TCNTB0 0x000C
#define TCMPB0 0x0010
static unsigned int *gpd0con;
static void *timer_base;
#define MAGIC_NUMBER 'k'
#define BEEP_ON _IO(MAGIC_NUMBER ,0)
#define BEEP_OFF _IO(MAGIC_NUMBER ,1)
#define BEEP_FREQ _IO(MAGIC_NUMBER ,2)
static void fs4412_beep_init(void)
{
writel ((readl(gpd0con)&~(0xf<<0)) | (0x2<<0),gpd0con);
writel ((readl(timer_base +TCFG0 )&~(0xff<<0)) | (0xff <<0),timer_base +TCFG0);
writel ((readl(timer_base +TCFG1 )&~(0xf<<0)) | (0x2 <<0),timer_base +TCFG1 );
writel (500, timer_base +TCNTB0 );
writel (250, timer_base +TCMPB0 );
writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x2 <<0),timer_base +TCON );
}
void fs4412_beep_on(void)
{
writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x9 <<0),timer_base +TCON );
}
void fs4412_beep_off(void)
{
writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x0 <<0),timer_base +TCON );
}
static void beep_unmap(void)
{
iounmap(gpd0con);
iounmap(timer_base);
}
static int beep_open (struct inode *inode, struct file *filep)
{
fs4412_beep_on();
return 0;
}
static int beep_release(struct inode *inode, struct file *filep)
{
fs4412_beep_off();
return 0;
}
#define BEPP_IN_FREQ 100000
static void beep_freq(unsigned long arg)
{
writel(BEPP_IN_FREQ/arg, timer_base +TCNTB0 );
writel(BEPP_IN_FREQ/(2*arg), timer_base +TCMPB0 );
}
static long beep_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case BEEP_ON:
fs4412_beep_on();
break;
case BEEP_OFF:
fs4412_beep_off();
break;
case BEEP_FREQ:
beep_freq( arg );
break;
default :
return -EINVAL;
}
return 0;
}
static struct file_operations beep_ops=
{
.open = beep_open,
.release = beep_release,
.unlocked_ioctl = beep_ioctl,
};
static int beep_probe(struct platform_device *pdev)
{
int ret;
printk("match ok!");
gpd0con = ioremap(pdev->resource[0].start,pdev->resource[0].end - pdev->resource[0].start);
timer_base = ioremap(pdev->resource[1].start, pdev->resource[1].end - pdev->resource[1].start);
devno = MKDEV(major,minor);
ret = register_chrdev(major,"beep",&beep_ops);
cls = class_create(THIS_MODULE, "myclass");
if(IS_ERR(cls))
{
unregister_chrdev(major,"beep");
return -EBUSY;
}
test_device = device_create(cls,NULL,devno,NULL,"beep");//mknod /dev/hello
if(IS_ERR(test_device))
{
class_destroy(cls);
unregister_chrdev(major,"beep");
return -EBUSY;
}
fs4412_beep_init();
return 0;
}
static int beep_remove(struct platform_device *pdev)
{
beep_unmap();
device_destroy(cls,devno);
class_destroy(cls);
unregister_chrdev(major,"beep");
return 0;
}
static struct platform_driver beep_driver=
{
.driver.name = "bigbang",
.probe = beep_probe,
.remove = beep_remove,
};
static int beep_init(void)
{
printk("beep_init");
return platform_driver_register(&beep_driver);
}
static void beep_exit(void)
{
printk("beep_exit");
platform_driver_unregister(&beep_driver);
return;
}
MODULE_LICENSE("GPL");
module_init(beep_init);
module_exit(beep_exit);
3、makefile
[cpp] view
plain copy





ifneq ($(KERNELRELEASE),)
obj-m:=device.o driver.o
$(info "2nd")
else
#KDIR := /lib/modules/$(shell uname -r)/build
KDIR := /home/fs/linux/linux-3.14-fs4412
PWD:=$(shell pwd)
all:
$(info "1st")
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order
endif
4、test.c
[cpp] view
plain copy





#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
main()
{
int fd,i,lednum;
fd = open("/dev/beep",O_RDWR);
if(fd<0)
{
perror("open fail \n");
return ;
}
sleep(10);
close(fd);
}
Copyright © Linux教程網 All Rights Reserved