1. 程式人生 > >Linux 裝置驅動開發 —— platform裝置驅動應用例項解析

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

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使用的中斷號的開始地址和結束值; 

下面看一個例項:

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 的外殼,下面我們看一下新增的過程:
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,其定義如下:
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 中可以看到
__platform_driver_register()
{
	drv->driver.bus = &platform_bus_type;     536行
}
    在 platform_bus_type 中呼叫 了platform_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);

	匹配裝置樹資訊,如果有裝置樹,就呼叫 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去獲取資源和資料,做到了板相關程式碼和驅動程式碼的分離,使得驅動具有更好的可擴充套件性和跨平臺性。

三、例項

1、device.c

#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

#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   

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
#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);
}