平台设备驱动和混杂设备驱动

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
	









  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值