嵌入式驱动开发新方向(嵌入式设备驱动程序基础笔记第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
然后编译测试成功。
欢迎关注“逗比小憨憨”,更多内容正在持续不断更新中。。。。。。