1. 程式人生 > >平臺裝置驅動和混雜裝置驅動

平臺裝置驅動和混雜裝置驅動

1. 平臺裝置驅動

在linux2.6以後的裝置驅動模型中,只關心裝置、驅動和匯流排這三個實體,匯流排將裝置驅動繫結。在向系統註冊一個裝置時會由匯流排匹配對應的驅動,相反當向系統註冊一個驅動時由匯流排匹配出對應的裝置。

在linux裝置和驅動通常要掛接在某條總線上,對於IIC、SPI、USB等裝置有自己的物理匯流排,但是在嵌入式的系統中很多的裝置並不能找到自己的物理匯流排。基於這一背景linux發明了一種虛擬匯流排,稱為platform匯流排,與之對應的裝置稱之為platform_device,驅動稱之為platform_driver。platform_device裝置是字元裝置。

1.1 platform_device結構體

struct platform_device {
	const char	* name; //裝置名
	int		id;
	struct device	dev;   //裝置結構體
	u32		num_resources; //裝置資源個數
	struct resource	* resource; //裝置資源

	struct platform_device_id	*id_entry;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

1.1.1 裝置資源結構體

struct resource {
	resource_size_t start; //資源開始位置(地址||中斷號之類)
	resource_size_t end; //資源結束的位置
	const char *name; //資源的名稱
	unsigned long flags; //何種資源
	struct resource *parent, *sibling, *child; 
};
flages標誌可以為: IORESOURCE_TYPE_BITS、IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA,當為IORESOURCE_MEM時start為開始地址,end為結束地址。當為IORESOURCE_IRQ時start為中斷號開始值,end為中中斷號結束值。可以使用struct resource *platform_get_resource(struct platform_device *,unsigned int ,unsigned int);來獲得資源。

1.1.2 裝置結構體

struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	struct device_type	*type;

	struct semaphore	sem;	/* semaphore to synchronize calls to
					 * its driver.
					 */

	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	struct dev_pm_info	power;

#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_data,攜帶裝置的相關描述資訊。使用dev_get_platdata()獲得相關的描述資訊。

1.1.3 向核心註冊/登出裝置

int platform_device_register(struct platform_device *); //向核心註冊裝置
void platform_device_unregister(struct platform_device *);//登出裝置

1.2 platform_driver結構體

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

1.2.1 struct device_drivice

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

1.2.2 驅動的註冊與登出

int platform_driver_register(struct platform_driver *); //向核心註冊驅動

void platform_driver_unregister(struct platform_driver *); //從核心登出裝置

1.3 bus_type的例項platform_bus_type(核心原始碼/driver/base/platform.c檔案)

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_attrs	= platform_dev_attrs,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};

1.3.1 匹配函式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);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}
由此可見裝置和驅動的匹配有四種:①通過裝置樹②通過ACPI③通過ID④通過name

2. 混雜裝置

linux驅動程式的設計都傾向分層的思想,所以各個具體的裝置都能找到自己的歸屬型別,從而套用他的架構裡去,並且只需要實現其底層的那一部分。但是有些裝置確實無法找的它的型別,此類裝置一般使用miscdevice裝置。

2.1 miscdevice結構體

struct miscdevice  {
	int minor; //次裝置號
	const char *name; //裝置名稱
	const struct file_operations *fops; //操作
	struct list_head list;
	struct device *parent;
	struct device *this_device;
	const char *nodename;
	mode_t mode; //所屬模組
};
miscdevice裝置的主裝置號是固定的,MIC_MAJOR為10,MISC_DYNAMIC_MINOR核心分配次裝置號。

2.2 註冊和登出miscdevice裝置

int misc_register(struct miscdevice * misc); //註冊
int misc_deregister(struct miscdevice *misc); //登出

3. 例項

3.1 裝置

/*
 * dev_key.c
 *
 *  Created on: 2017年5月29日
 *      Author: chy
 */
#include <linux/miscdevice.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>


#define GPGCON 0x56000060
#define EINTPEND 0x560000a8

struct resource key_resource[] = {
		[0] = { //mem
			.start = GPGCON,
			.end = GPGCON + 8,
			.name = "gpgcon",
			.flags = IORESOURCE_MEM,
		},
		[1] = { //irq
			.start = IRQ_EINT8,
			.end = IRQ_EINT13,
			.name = "irq",
			.flags = IORESOURCE_IRQ,
		},
};

struct platform_device dev;

int  init_dev()
{
	printk(KERN_WARNING "dev start!\n");

	dev.num_resources = ARRAY_SIZE(key_resource);
	dev.resource = key_resource;
	dev.name = "keys";

	int ans = platform_device_register(&dev); // platform_device_add

	if(ans){
		printk(KERN_WARNING "ans dev faile!\n");
		platform_device_put(&dev);
		return ans;
	}

	return 0;
}

int __exit exit_dev()
{
	platform_device_unregister(&dev);
}

MODULE_LICENSE("GPL");
module_init(init_dev);
module_exit(exit_dev);

3.2 驅動

/*
 * mini2440_keys.c
 *
 *  Created on: 2017年5月29日
 *      Author: chy
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/wait.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/param.h>
#include <linux/string.h>

struct keys_dri{
	wait_queue_head_t wait_key; //等待佇列
	struct work_struct* work_key; //工作佇列
	struct timer_list timer_key; //定時器
	struct miscdevice mis_dev;   //混雜裝置
	struct resource *re_dev;
	unsigned long *gpgcon;
	unsigned long *gpgdat;
	unsigned long even_date;
	char buffer[30];
};

static struct keys_dri key_dri;

void work_func(struct work_struct *work) //處理底半部中斷函式
{
	mod_timer(&key_dri.timer_key,jiffies + HZ / 10); //時鐘中斷由系統定時硬體以週期性的間隔產生,這個間隔由核心根據 HZ 值來設定,
	                                                 //HZ 是一個體系依賴的值,在 <Linux/param.h>中定義或該檔案包含的某個子平
	                                                 //臺相關檔案中。作為通用的規則,即便如果知道 HZ 的值,在程式設計時應當不依賴這個
	                                                 //特定值,而始終使用HZ。對於當前版本,我們應完全信任核心開發者,他們已經選擇
	                                                 //了最適合的HZ值,最好保持 HZ 的預設值。

	return;
}

void time_func(unsigned long data)//定時器中斷函式
{
	 unsigned long date;
	date = readw(key_dri.gpgdat) & 0xffff; //讀取gpgdat暫存器的值

        //判斷那個按鍵被按下
	if(!(date & (0x1)))
		key_dri.even_date = 1;
	else if(!(date & (0x1 << 3)))
		key_dri.even_date = 2;
	else if(!(date & (0x1 << 5)))
			key_dri.even_date = 3;
	else key_dri.even_date = date;

	sprintf(key_dri.buffer,"你按下了%d號按鍵",key_dri.even_date);

	wake_up(&key_dri.wait_key);
	return;
}

irqreturn_t interrupt_func(int riq,void *dev) //處理頂半部中斷
{
	schedule_work(key_dri.work_key); //排程工作佇列

	return IRQ_HANDLED;
}

int open_func(struct noid* noid_key,struct file* file_key)//開啟裝置
{
	unsigned long data;
	data = readw(key_dri.gpgcon); //設定埠為中斷工作模式
	data |= 0x222;
	writew(data,key_dri.gpgcon);

	return 0;
}

ssize_t read_func(struct file* file_key,char __user* buf_key,size_t len,loff_t *off_key) //讀取按鍵狀態
{
	wait_event(key_dri.wait_key,key_dri.even_date); //等待even_date事件發生

	unsigned long size;

	size = copy_to_user(buf_key,key_dri.buffer,sizeof(key_dri.buffer) + 1); //由核心態複製到使用者態

	key_dri.even_date = 0; //事件清空

	return sizeof(key_dri.buffer) - size;
}

int close_key() //關閉裝置檔案
{
	return 0;
}

struct file_operations file_oper = {
		.read = read_func,
		.open = open_func,
		.release  = close_key,
};

int  probe_keys(struct platfrom_device *dev)
{
	key_dri.mis_dev.minor = MISC_DYNAMIC_MINOR; //混雜裝置次裝置號
	key_dri.mis_dev.name = "key"; //混雜裝置名
	key_dri.mis_dev.fops = &file_oper; //操作結構體

	key_dri.even_date = 0;//事件
	strcpy(key_dri.buffer,"你按下的是");

	key_dri.work_key = kmalloc(sizeof(struct work_struct),GFP_KERNEL); //給工作佇列申請記憶體
	INIT_WORK(key_dri.work_key,work_func); //初始化工作佇列

	init_timer(&key_dri.timer_key); //初始化定時器
	key_dri.timer_key.function = time_func; //定時器中斷處理函式
	add_timer(&key_dri.timer_key); //向系統註冊定時器

	key_dri.re_dev = platform_get_resource(dev,IORESOURCE_IRQ,0); //獲取裝置資源
	request_irq(key_dri.re_dev->start,interrupt_func,IRQF_TRIGGER_FALLING,"key1",(void*)1); //GPG0
	request_irq(key_dri.re_dev->start +3 ,interrupt_func,IRQF_TRIGGER_FALLING,"key2",(void*)2); //GPG3
	request_irq(key_dri.re_dev->end,interrupt_func,IRQF_TRIGGER_FALLING,"key3",(void*)3); //GPG5

	key_dri.re_dev = platform_get_resource(dev,IORESOURCE_MEM,0); //獲取裝置資源
	key_dri.gpgcon = ioremap(key_dri.re_dev->start,key_dri.re_dev->end - key_dri.re_dev->start + 1);
	key_dri.gpgdat = key_dri.gpgcon + 1;

	init_waitqueue_head(&key_dri.wait_key); //初始化等待佇列

	if(misc_register(&key_dri.mis_dev)) //註冊混雜裝置
		printk(KERN_WARNING " 註冊失敗!");

	return 0;
}

int remove_keys(struct platform_device *dev)
{
	free_irq(key_dri.re_dev->start,(void*)1);
	free_irq(key_dri.re_dev->start +3,(void*)2);
	free_irq(key_dri.re_dev->end,(void*)3);
	iounmap(key_dri.gpgcon);

	misc_deregister(&key_dri.mis_dev);

	return 0;
}

static struct platform_driver platform_keys = {
		.driver = {
			.name = "keys",
			.owner = THIS_MODULE,
		},
		.probe = probe_keys,
		.remove = remove_keys
};

int init_keys(void)
{
	return platform_driver_register(&platform_keys);
}

void exit_keys(void)
{
	platform_driver_unregister(&platform_keys);
}

MODULE_LICENSE("GPL");
module_init(init_keys);
module_exit(exit_keys);

3.3 應用

/*
 * key_app.c
 *
 *  Created on: 2017年5月30日
 *      Author: chy
 */

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFSIZE 40
char buffer[BUFSIZE];

int main(int argc,char *argv[])
{
	int fd;

	fd = open("/dev/key", O_RDONLY ); //開啟裝置檔案
	if(fd < 0){
		fprintf(stderr,"/dev/key檔案開啟失敗\n");
		return 0;
	}

	printf("/dev/key開啟成功\n");



	if(read(fd,buffer,BUFSIZE) < 0 ){ //讀取按鍵的值
			fprintf(stderr,"讀取失敗\n");
			return 0;
	}

	printf("%s\n",buffer);

	close(fd); //關閉檔案

	return 0;
}


3.4 Makefile

./src/device/Makefile

obj-m += dev_key.o

clean:
	rm -fr *.ko *.o *mod* Mod*

./src/driver/Makefile
obj-m += mini2440_keys.o

clean:
	rm -fr *.ko *.o *mod* Mod*
./src/Makefle
kernel = /home/chy/work/linux-2.6.32.2
device = $(PWD)src/device
driver = $(PWD)src/driver
app = $(PWD)src/app

all:
	make -C $(kernel)   M=$(device) modules
	make -C $(kernel)   M=$(driver) modules
	make -C $(app)                  all

clean:
	make -C $(device)              clean
	make -C $(driver)              clean
	make -C $(app)                 clean
	






相關推薦

no