S3C2440 platform总线分层分离概念_总线驱动设备模型(十五)

http://www.cnblogs.com/lifexy/p/7569371.html

platform机制以及分概念

1、分离概念

优点:

  • 将所有设备挂接到一个虚拟的总线上,方便sysfs节点和设备电源的管理
  • 使得驱动代码,具良好的扩展性和跨平台性,就不会因为新的平台而再次编写驱动

介绍:

分离就是在驱动中使用platform机制把硬件相关的代码(固定的,如板子的网卡、中断地址)和驱动(会根据程序做变动,如点哪一个灯)分离开来,即要编写两个文件:XXXdev.c和XXXdrv.c(platform设备和platform驱动)


2、platform机制

基本内容:

platform会存在/sys/bus/里面

扫描二维码关注公众号,回复: 1450355 查看本文章

如下图所示,platform目录下会有两个文件,分别是platform设备devices 和 platform驱动 drivers。


(1)device设备

挂接在platform总线下的设备,platform_device结构体类型

(2)driver驱动

挂接在platform总线下,是一个与某种设备相对的驱动,platform_driver结构体类型

(3)platform总线

是个全局变量,为platform_bus_type,属于虚拟设备总线,通过这个总线将设备和驱动联系起来,属于Linux中bus的一种

该platform_bus_type的结构体定义如下所示:(位于drivers/base/platform.c)

struct bus_type platform_bus_type = {
	.name		= "platform",	//设备名称	
	.dev_attrs	= platform_dev_attrs,	//设备属性、含获取sys文件名,该总线会放在/sys/bus下
	.match		= platform_match,	//匹配设备和驱动,匹配成功就调用driver的.match函数
	.uevent		= platform_uevent,	//消息传递,比如热拔插操作
	.suspend	= platform_suspend,	//电源管理的低功耗挂起
	.suspend_late	= platform_suspend_late,	//电源管理的恢复、唤醒
	.resume_early	= platform_resume_early,
	.resume		= platform_resume,
};
驱动和设备匹配图:

只有一方注册,就会调用platform_bus_type结构体里面的.match匹配函数,来找对方,来使总线将设备和驱动;成功就调用driver驱动结构体里的.probe函数。


3、实例---分析driver驱动:

我们以/drivers/input/keybord/gpio_keys.c内核自带的示例程序为例,

它的代码中只有driver驱动,因为是个示例程序,所以没有device硬件设备代码。

3.1 发现在gpio_keys.c中有一个全局变量driver驱动:

struct platform_driver gpio_keys_device_driver = {	//定义一个platform_driver类型驱动
	.probe		= gpio_keys_probe,	//设备的检测,当匹配成功就会调用这个函数(需要自己编写)
	.remove		= __devexit_p(gpio_keys_remove),	//删除设备(需要自己编写)
	.driver		= {
		.name	= "gpio-keys",	//驱动名称,用来与设备名称匹配用的
	}
};

3.2 然后来找找这个gpio_keys_device_driver被谁调用

发现在驱动层init入口函数通过platform_driver_register()来注册driver驱动

在驱动层exit出口函数中通过platform_driver_unregister()函数来注销driver驱动

代码如下:

static int __init gpio_keys_init(void)	//init出口函数
{
	return platform_driver_register(&gpio_keys_device_driver);	//注册driver驱动
}

static void __exit gpio_keys_exit(void)	//exit出口函数
{
	platform_driver_unregister(&gpio_keys_device_driver);	//注销driver驱动
}

3.3 我们进来platform_driver_register(),看它是如何注册driver的,注册到哪里?

platform_driver_register()函数如下:

int platform_driver_register(struct platform_driver *drv)
{
	drv->driver.bus = &platform_bus_type;	//(1)挂接到虚拟总线platform_bus_type上
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;
	if (drv->remove)
		drv->driver.remove = platform_drv_remove;
	if (drv->shutdown)
		drv->driver.shutdown = platform_drv_shutdown;
	if (drv->suspend)
		drv->driver.suspend = platform_drv_suspend;
	if (drv->resume)
		drv->driver.resume = platform_drv_resume;
	return driver_register(&drv->driver);	//(2)注册带dirver目录下
}
(1)挂接到虚拟总线platform_bus_type上,然后会调用platform_bus_type下的platform_match匹配函数,来匹配device和driver的名字,其中driver的名字如下图所示:

platform_match()函数如下所示:

static int platform_match(struct device * dev, struct device_driver * drv)
{
	//找到所有的device设备
	struct platform_device *pdev = container_of(dev, struct platform_device, dev);

	return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);//找到BUS_ID_SIZE次
}

若名字匹配成功,则调用driver的.probe成员函数。

(2)然后放到/sys/bus/platform/driver目录下,其中driver_register()函数就是用来创建driver目录的。


4、使用platform机制,编写LED驱动层

首先创建设备文件和驱动文件:led_dev.c、led_drv.c

led_dev.c用来指定灯的引脚地址,当更换平台时,只需要修改这个就行

led_drv.c用来初始化灯以及如何控制灯的逻辑,当更换控制逻辑时,只需要修改这个就行


5 编写led_dev.c

5.1编写led_dev.c之前先来看看platform_device结构体和要使用的函数:

platform_device结构体如下:

struct platform_device {
	const char	* name;	//设备名称,要与platform_driver的name一样,这样总线才能匹配成功
	u32		id;	//id号,插入总线下相同name的设备编号(一个驱动可以有多个设备),如果只有一个设备填-1
	struct device	dev;	//内嵌的具体的devices结构体,其中成员.release函数不可缺少
	u32		num_resources;	//资源数量
	struct resource	* resource;	//资源结构体,保存设备的信息
};


其中resource资源结构体,如下:

struct resource {
	resource_size_t start;	//起始资源,如果是地址的话,必须是物理地址
	resource_size_t end;	//结束资源,如果是地址的话,必须是物理地址
	const char *name;	//资源名
	unsigned long flags;	//资源的标志
	//比如IORESOURCE_MEM,表示地址资源,IORESOURCE_IRQ代表中断引脚

	struct resource *parent, *sibling, *child;
};


要用的函数如下,在dev设备的入口出口函数中用到

int platform_device_register(struct platform_device * pdev)
void platform_device_unregister(struct platform_device * pdev)


5.2 接下来写代码:

led_dev.c:

#include <linux/module.h>
#include <linux/version.h>

#include <linux/init.h>

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>


/* 分配/设置/注册一个platform_device */


//资源数组,保存设备的信息
static struct resource led_resource[] = {
	[0] = {
		//led的寄存器GPFCON起始地址
		.start = 0x56000050,//寄存器的起始物理地址,因为GOIO_CON和GPIO_DAT两个寄存器占八个字节,所以需要映射长八个字节
		//led的寄存器GPFDAT结束地址
		.end   = 0x56000050 + 8 -1,//0x56000050,0x56000051,0x56000052,0x56000053,0x56000054,0x56000055,0x56000056,0x56000057,8个字节代表2个寄存器的空间
		//表示地址资源
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		//表示GPF第几个引脚开始
		.start = 4,//哪一个引脚4、5、6(哪一位)
		//结束引脚
		.end   = 4,
		//表示中断资源
		.flags = IORESOURCE_IRQ,	//IORESOURCE_IRQ和IORESOURCE_IO都可以
	}
};

//释放函数
static void led_release(struct device *dev)
{
	
}

//平台设备
static struct platform_device led_dev = {
	.name		= "myled",	//对应的platform_driver驱动的名字
	.id		= -1,	//表示只有一个设备
	.num_resources	= ARRAY_SIZE(led_resource),	//资源数量,ARRAY_SIZE()函数:获取数量
	.resource	= led_resource,	//资源数组
	.dev = { 
		.release = led_release, //platform平台有一个device,device里面有一个release,必须向内核提供一个release函数
	},						//否则卸载时,内核找不到该函数会报错
};

//入口函数,注册dev设备
static int led_dev_init(void)
{
	platform_device_register(&led_dev);//注册平台设备,最终调用device_add
	return 0;
}

static void led_dev_exit(void)
{
	platform_device_unregister(&led_dev);//卸载平台设备	
}

module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

6、编写led_drv.c

6.1 编写led_drv.c之前先来看看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 (*suspend_late)(struct platform_device *, pm_message_t state);
	int (*resume_early)(struct platform_device *);
	int (*resume)(struct platform_device *);
	struct device_driver driver; //内嵌的driver,其中的name成员要等于设备的名称才能匹配
};


int platform_driver_register(struct platform_driver *drv) //注册驱动
void platform_driver_unregister(struct platform_driver *drv) //卸载驱动

platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num)
//获取设备的某个资源,获取成功,则返回一个resource资源结构体
/*
 * 参数:
 * *dev:指向某个platform device设备
 * typed:获取的资源类型
 * num:type资源下的第几个数组  
 */


6.2 接下来开始写代码

led_drv.c:

/* 分配/设置/注册一个platform_driver */

#include <linux/module.h>
#include <linux/version.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>

static int major;
	
static struct class *cls;
static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
static int pin;

static int led_open(struct inode *inode, struct file *file)
{
	//printk("first_drv_open\n");
	
	/* 配置为输出 */
	*gpio_con &= ~(0x3<<(pin*2));
	*gpio_con |= (0x1<<(pin*2));
	return 0;
}

static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;

	//printk("first_drv_write\n");
	
	//用户程序传进来的值,怎样才能取得到,用户空间-》内核空间
	copy_from_user(&val, buf, count);	//	copy_to user()

	if (val == 1)
	{
		//点灯
		*gpio_dat &= ~(1<<pin);
	}
	else
	{
		//灭灯
		*gpio_dat |= (1<<pin);
	}
	return 0;
}

static struct file_operations led_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ //被使用时阻止模块被卸载
    .open   =   led_open,     
	.write	=	led_write,	   
};

static int led_probe(struct platform_device *pdev)
{
	/*
	 *使用platform_get_resource()函数获取LED的地址和引脚,然后初始化LED,并注册字符设备和设备节点"led" 
	 */
	 
	struct resource *res;
	
	/* 根据platform_device的资源进行ipremap */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);	//获得寄存器地址
	gpio_con = ioremap(res->start, res->end - res->start + 1);	//获取虚拟地址
	gpio_dat = gpio_con + 1; //指针加1,相当于加4,指向另一个寄存器

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);	//获得IRQ资源,第0个(在dev里面定义资源,在drv中获取资源)
	pin = res->start;	//获取引脚值

	
	/* 注册字符设备驱动程序 */
	printk("led_probe, found led\n");

	major=register_chrdev(0, "myled", &led_fops);
	
	cls = class_create(THIS_MODULE, "myled");

	//在这个类下面再创建一个设备
	class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
	
	return 0;
}

//删除设备
static int led_remove(struct platform_device *pdev)
{
	/* 卸载字符设备驱动程序 */
	/* iounmap */
	printk("led_remove, remove led\n");

	class_device_destroy(cls, MKDEV(major, 0));
	class_destroy(cls);
	unregister_chrdev(major, "myled");
	iounmap(gpio_con);	//注销虚拟地址
	
	return 0;
}

struct platform_driver led_drv = {
	.probe		= led_probe,	//当与设备匹配,则调用此函数
	.remove		= led_remove,	//删除设备
	.driver		= {
		.name	= "myled",	//当结构体中,drv的name和dev里面的name一致时,才会调用led_probe函数
	}
};

static int led_drv_init(void)
{
	platform_driver_register(&led_drv);
	return 0;
}

static void led_drv_exit(void)
{
	platform_driver_unregister(&led_drv);
}

module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");


7、测试运行:

7.1 如下图,我们先挂载dev设备模块,它在platform/devices目录下生成一个"myled"设备

7.2 然后挂载drv驱动模块,同样在platform/drivers目录了下生成一个"myled"驱动,与devices目录下的"myled"设备匹配成功,进入.probe函数创建设备,接下来就可以使用应用程序来控制led灯了

7.3 卸载驱动时,也会进入.remove函数卸载设备


猜你喜欢

转载自blog.csdn.net/xiaodingqq/article/details/80540122
今日推荐