嵌入式驱动开发新方向(嵌入式设备驱动程序基础笔记第17期)
嵌入式驱动开发新方向(嵌入式设备驱动程序基础笔记第17期)struct platform_device { const char * name; u32 id; struct device dev; u32 num_resources; struct resource * resource; };备注:struct device dev;struct bus_type { char * name; struct subsystem subsys; struct kset drivers; struct kset devices; struct bus_attribute * bus_attrs; struct device_attribute * dev_attrs; struct driver_attribute * drv_attrs; int (*match)(struct device * d
什么是总线:通俗来说就是:一个总线是处理器和一个或多个设备之间的通道。所有的设备都通过一个总线连接 比如STM32的APB2总线,在其上面挂载着相应的外设(GPIOx等外设)。
驱动程序分离/分层的概念:回顾之前讲解的控制LED亮灭的Linux驱动程序,我们了解到,想要点亮LED主要需要两个部分:1,硬件部分:设置相应的硬件,比如:重映射某个寄存器的地址,然后设置为输出模式。2,软件部分:编写相应的驱动代码,比如注册驱动设备等。
当我们修改硬件连接,即换成其它引脚控制LED时,那么我们按照之前的方法就是重新编写整个程序,这样显得比较麻烦。此时就引入一个分离/分层的概念,通俗来讲就是,将上述所说的两部分(硬件部分和软件部分)分离出来,以后修改硬件连接时,就直接修改硬件部分,而那些通用的软件部分(所谓通用:就是不管我们的灯连接在哪个引脚,但都需要注册相应的设备,这些注册代码就通用的)就不用修改,这样就可以减少开发时间。
问题:怎么将上述分离/分层的两个部分连接起来?
方法:引入我们上述讲的“总线”,即使用一个虚拟总线挂接两个部分(注:这里的虚拟总线不是物理上真实存在的总线,是用软件代码模拟出来的总线)
简单查看内核提供的总线相关代码:linux-2.6.22.6\Documentation\driver-model这里面有相应的描述文件
总线类型:
struct bus_type {
	char			* name;
	struct subsystem	subsys;
	struct kset		drivers;
	struct kset		devices;
	struct bus_attribute	* bus_attrs;
	struct device_attribute	* dev_attrs;
	struct driver_attribute	* drv_attrs;
	int		(*match)(struct device * dev  struct device_driver * drv);
	int		(*hotplug) (struct device *dev  char **envp  
				    int num_envp  char *buffer  int buffer_size);
	int		(*suspend)(struct device * dev  pm_message_t state);
	int		(*resume)(struct device * dev);
};
    
平台设备
struct platform_device {
	const char	* name;
	u32		id;
	struct device	dev;
	u32		num_resources;
	struct resource	* resource;
};
    
备注:struct device dev;
device
struct device {
	struct klist		klist_children;
	struct klist_node	knode_parent;		/* node in sibling list */
	struct klist_node	knode_driver;
	struct klist_node	knode_bus;
	struct device		*parent;
	struct kobject kobj;
	char	bus_id[BUS_ID_SIZE];	/* position on parent bus */
	struct device_type	*type;
	unsigned		is_registered:1;
	unsigned		uevent_suppress:1;
	struct device_attribute uevent_attr;
	struct device_attribute *devt_attr;
	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		*driver_data;	/* data private to the driver */
	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 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;
	spinlock_t		devres_lock;
	struct list_head	devres_head;
	/* class_device migration path */
	struct list_head	node;
	struct class		*class;
	dev_t			devt;		/* dev_t  creates the sysfs "dev" */
	struct attribute_group	**groups;	/* optional groups */
	void	(*release)(struct device * dev);
};
    
平台驱动
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;
};
    
备注:struct device_driver driver;
device_driver
struct device_driver {
	const char		* name;
	struct bus_type		* bus;
	struct kobject		kobj;
	struct klist		klist_devices;
	struct klist_node	knode_bus;
	struct module		* owner;
	const char 		* mod_name;	/* used for built-in modules */
	struct module_kobject	* mkobj;
	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);
};平台驱动与平台设备的关系
    


我们从上图可知,int platform_device_register(struct platform_device * pdev) 不仅向上将设备注册到总线bus上,而且最终还会查看是否有合适的驱动,如果有就调用 int device_bind_driver(struct device *dev)函数进行设备与驱动的绑定。
平台驱动注册函数执行主要流程:
我们从上图可知,int platform_driver_register(struct platform_driver *drv)不仅向上将驱动注册到总线bus上,而且最终还会查看是否有合适的设备。
关联:上述讲解了相应的结构体和相关函数的主要执行过程,但引入一个问题:内核中这么多的总线,平台设备和平台驱动是怎么知道它们是属于同一个虚拟总线的?
方法:
我们可以从下图中标记的数字1就可知道,它们在执行时会绑定到同一个总线上。

备注:数字1是:
pdev->dev.bus = &platform_bus_type;
drv->driver.bus = &platform_bus_type;
具体的平台总线定义如下:
struct bus_type platform_bus_type = {
	.name		= "platform" 
	.dev_attrs	= platform_dev_attrs 
	.match		= platform_match 
	.uevent		= platform_uevent 
	.suspend	= platform_suspend 
	.suspend_late	= platform_suspend_late 
	.resume_early	= platform_resume_early 
	.resume		= platform_resume 
};总结:通俗来讲就是:平台设备注册函数在名为platform的总线上进行设备的注册,同时查看是否有合适的平台驱动,如有就进行绑定;平台驱动注册函数在名为platform的总线上进行驱动的注册,同时查看是否有合适的设备,如有就进行绑定。上述过程需通过match函数(.match		= platform_match)。
    
static int platform_match(struct device * dev  struct device_driver * drv)
{
	struct platform_device *pdev = container_of(dev  struct platform_device  dev);
	return (strncmp(pdev->name  drv->name  BUS_ID_SIZE) == 0);
}补充:(总线初始化相关调用过程)
    
1, start_kernel
2, rest_init
3, kernel_thread
4, kernel_init
5, do_basic_setup
6, driver_init
7, platform_bus_init
8, device_register(&platform_bus);
struct device platform_bus = {
	.bus_id		= "platform" 
};
9, bus_register(&platform_bus_type);
struct bus_type platform_bus_type = {
	.name		= "platform" 
	.dev_attrs	= platform_dev_attrs 
	.match		= platform_match 
	.uevent		= platform_uevent 
	.suspend	= platform_suspend 
	.suspend_late	= platform_suspend_late 
	.resume_early	= platform_resume_early 
	.resume		= platform_resume 
};编写程序验证
    
编写一个简单的控制LED的程序,分别是两个C文件:一个编写平台设备,一个编写平台驱动。
1,编写平台设备
//平台设备相关代码用于硬件相关
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/platform_device.h>
static struct resource led_dev_resource[] = {
      [0] = {
	  	.start          =  0x56000050 
	       .end            =  0x56000058 
	       .name        = "doubixioaohanhan" 
	       .flags          = IORESOURCE_MEM 
      	} 
	[1] = {
	  	.start          =  4 
	       .end            =  4 
	       .flags          = IORESOURCE_IRQ 
      	} 
};
static void  led_dev_release(struct device * dev)
{
	printk("doubixiaohanhan");	
}
static struct platform_device led_dev = {
       .name           = "led_bus" 
       .id                 = 0 
       .resource       = led_dev_resource 
       .num_resources  = ARRAY_SIZE(led_dev_resource) 
       .dev ={
		.release = led_dev_release 
	 }
};
static int led_dev_init(void)
{
	platform_device_register(&led_dev);
	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");
    
2,编写平台驱动
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/device.h>
#define CLASS_NAME   "doubixiaohanhan"
#define OUTPUT_HIGH     1
#define OUTPUT_LOW      0 
int major = 0;
static struct class                      *led_class;
static struct class_device            *led_class_device;
static volatile unsigned long         *gpiof_con;
static volatile unsigned long         *gpiof_dat;
static int  led_pin;
static int led_drv_open(struct inode * inode  struct file * file)
{
	*gpiof_con  &= ~(3<<(led_pin * 2));	
	*gpiof_con  |=  (1<<(led_pin * 2));	
	printk("led_pin configurate output mode");
	return 0;
}
static ssize_t led_drv_write(struct file * file  const char __user * userbuf 
		     size_t count  loff_t * off)
{
	int val = 0;
	
	if( copy_from_user(&val  userbuf  1))
		;
	if(OUTPUT_HIGH == val)
	{
		*gpiof_dat  |= (1<<led_pin); 
	}
	else
	{
		*gpiof_dat  &=  ~(1<<led_pin); 
	}
	return 0;	
}
static struct file_operations led_drv_fops={
	.owner = THIS_MODULE 
	.open   = led_drv_open 
	.write   = led_drv_write  
};
static int  led_drv_probe(struct platform_device *pdev)
{
	struct resource *res;
	
	printk("I am doubixiaohanhan probe");
	//register device driver 
	major = register_chrdev(0 "led" &led_drv_fops);
	//create class and class-device
	led_class = class_create(THIS_MODULE  CLASS_NAME);
	led_class_device = class_device_create(led_class  NULL  MKDEV(major  1)  NULL  "LED");
	//PORT REMAP
	res = platform_get_resource(pdev  IORESOURCE_MEM  0);
	
	gpiof_con = ioremap(res->start  res->end - res->start  1);
	gpiof_dat = gpiof_con   1;
	res = platform_get_resource(pdev  IORESOURCE_IRQ  0);
	
	led_pin = res->start;
	return 0;
}
 static int led_drv_remove(struct platform_device *pdev)
{
	printk("I am doubixiaohanhan remove");
	iounmap(gpiof_con);
	class_destroy(led_class);
	//class_device_destroy(led_class led_class_device);
	class_device_destroy(led_class MKDEV(major  1));
	unregister_chrdev(major "led");
	
	return 0;
}
static struct platform_driver led_drv = {
	.probe		=  led_drv_probe 
	.remove		=  led_drv_remove 
	.driver		= {
		.name	= "led_bus" 
		.owner     = THIS_MODULE 
	} 	
};
//drv->driver.probe = platform_drv_probe;
//int (*probe)(struct platform_device *);
//led_drv->driver.probe=platform_drv_probe
//led_drv->led_drv_probe
static int led_drv_init(void)
{
	platform_driver_register(&led_drv);//driver_register(&drv->driver);
	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");
    
3,编写应用测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
//输入数字1熄灭LED
//输入其它字符开启LED
int main(int argc char **argv)
{
	int fd;
	char pin[2] = {1 0};
	int val;
	
	fd = open("/dev/LED" O_RDWR);
	if(fd < 0)
	{
		printf("/dev/LED can't open\n");
		return ;
	}
	printf("\n");
	while(1)
	{
		printf("\ninput integer select light on/off\n");
		scanf("%d" &val);
		if(val == 1)
			write(fd &pin[0] 1);
		else
			write(fd &pin[1] 1);
	}
}
    
4,编写Makefile
#compile regular
KERN_DIR = /work/system/linux-2.6.22.6
all:
	make -C $(KERN_DIR) M=`pwd` modules
	rm -rf modules.order Module.symvers 
.PHONY:
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order Module.symvers
obj-m    = led_dev.o
obj-m    = led_drv.o
    
然后编译测试成功。
欢迎关注“逗比小憨憨”,更多内容正在持续不断更新中。。。。。。



