31.【linux驱动】一文读懂linux设备驱动模型(总线bus)

总线模型

简单的设备驱动都是一个文件,init_module,exit_module之后就是一通操作。
但是总线这个词常常听说,但是却不明所以然。下面大话一下如何从最基本的驱动一步步演变成为总线模型的。首先从代码的角度看,代码分为数据(设备地址,初始参数,依赖的GPIO)和逻辑,为了复用逻辑于是吧数据单独拿出来。这样就可以实现多个设备共用一套逻辑,最后这些数据在内核代码中就被称为了设备,而逻辑就被称为驱动。只需要在合适的时候将设备和驱动匹配在一起就完成了设备驱动的加载。在内核中不是设备和驱动被匹配,而是他们自己还要约定一个见面的地点,这个见面的地点就被称作总线了。设备和驱动都注册到总线上,由总线决定两个人是否能在一起,判断标准非常简单,就是两者的名字是否相同。
对内核来说只存在模块(module)的概念,所以总线(bus)、设备(device)还有驱动(driver)都被封装成模块。下面简单演示一下。

bus

首先是总线,在device.h中定义了总线的规范bus_type,只需要注册这个结构体到内核就算注册了这个总线。

struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	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 (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	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 dev_pm_ops *pm;

	struct iommu_ops *iommu_ops;

	struct subsys_private *p;
};

一般只需要填充match就可以使用,match是用来匹配设备和驱动的,匹配上返回非零值,匹配不上返回零。注册的时候吧bus_type导出来给devicedriver

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

MODULE_LICENSE("GPL");

static int hello_match(struct device * dev, struct device_driver * drv)
{
	printk("match dev:%s driver:%s\n",dev_name(dev),drv->name);

	return !strcmp(dev_name(dev), drv->name);
}

struct bus_type hello_bus = {
	.name = "hello_bus",
	.match = hello_match,
};

EXPORT_SYMBOL(hello_bus);

static int __init hello_init(void){
	bus_register(&hello_bus);
	printk("hello bus init\n");
	return 0;
}

static void __exit hello_exit(void){
	bus_unregister(&hello_bus);
	printk("hello bus exit\n");
}

module_init(hello_init);
module_exit(hello_exit);
device

接下来是device,也在device.h中定义了结构体

struct device {
	struct device		*parent;
	struct device_private	*p;
	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	const struct device_type *type;
	struct mutex		mutex;	/* mutex 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;
	struct dev_pm_domain	*pm_domain;
#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;
	struct device_node	*of_node; /* associated device tree node */
	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
	u32			id;	/* device instance */
	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);
};

简单填充个名字

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

MODULE_LICENSE("GPL");

extern struct bus_type hello_bus;

static struct device hello_dev = {
	.init_name = "hello_1",
	.bus = &hello_bus,
};

static int __init hello_init(void){
	int ret;
	ret = device_register(&hello_dev);
	printk("hello device init\n");
	return ret;
}

static void __exit hello_exit(void){
	device_unregister(&hello_dev);
	printk("hello device exit\n");
}

module_init(hello_init);
module_exit(hello_exit);
driver

最后是driver,结构体不贴了,代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

MODULE_LICENSE("GPL");

extern struct bus_type hello_bus;

static int probe(struct device *dev){
	printk("hello driver probe\n");
	return 0;
}

static int remove(struct device *dev){
	printk("hello driver remove\n");
	return 0;
}

static struct device_driver hello_driver = {
	.name = "hello_1",
	.bus = &hello_bus,
	.owner = THIS_MODULE,
	.probe = probe,
	.remove = remove,
};

static int __init hello_init(void){
	int ret;
	ret = driver_register(&hello_driver);
	printk("hello driver init\n");
	return ret;
}

static void __exit hello_exit(void){
	driver_unregister(&hello_driver);
	printk("hello driver exit\n");
}

module_init(hello_init);
module_exit(hello_exit);
Makefile
KERNEL_DIR := /home/minicoco/disk1/Dev/nanopi/kernel/linux-3.4.y

bus:
	make -C ${KERNEL_DIR} M=`pwd` modules

.PHONY:
clean:
	make -C ${KERNEL_DIR} M=`pwd` clean

obj-m += bus.o
obj-m += device.o
obj-m += driver.o
GPIO中断设备

复杂一点的例子,多个设备的注册

bus
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

MODULE_LICENSE("GPL");

static int hello_match(struct device * dev, struct device_driver * drv)
{
	if(!strncmp(drv->name, dev_name(dev), strlen(drv->name))){
		printk("match dev:%s driver:%s\n",dev_name(dev),drv->name);
		return 1;
	}
	else
	{
		printk("unmatch dev:%s driver:%s\n",dev_name(dev),drv->name);
		return 0;
	}
}

struct bus_type hello_bus = {
	.name = "hello_bus",
	.match = hello_match,
};

EXPORT_SYMBOL(hello_bus);

static int __init hello_init(void){
	bus_register(&hello_bus);
	printk("hello bus init\n");
	return 0;
}

static void __exit hello_exit(void){
	bus_unregister(&hello_bus);
	printk("hello bus exit\n");
}

module_init(hello_init);
module_exit(hello_exit);
driver

probe函数在驱动匹配设备的时候被调用,主要做对设备的初始化,这一点要与init_module区别开,后者是做驱动的初始化,例如注册class。传入的参数就是device结构体的地址,在注册中断的时候吧这个地址注册进去,中断触发的时候就可以拿到这个设备上的数据。驱动是公共的,dev_set_drvdatadev_get_drvdata分别设置和获取设备的私有数据。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <mach/platform.h>
#include <mach/soc.h>
#include "device.h"

MODULE_LICENSE("GPL");

extern struct bus_type hello_bus;

struct private_data {
	dev_t dev_id;
	struct hello_pdata * pdata;
	struct cdev char_dev;
	int irq_no;
};
struct class * char_class;

irqreturn_t key_int(int irq, struct device * dev){
	struct private_data * pri_data = dev_get_drvdata(dev);

	printk("key int:%d\n",pri_data->pdata->gpio);
	return IRQ_HANDLED;
}

static ssize_t hello_read(struct file * fl,const char __user * u_buffer,size_t len,loff_t * offset){

}

struct file_operations opts= {
	.owner = THIS_MODULE,
	.read = hello_read,
};

static int hello_probe(struct device *dev){
	static int dev_idx = 0;
	int ret;
	struct private_data *pri_data;
	struct hello_pdata * pdata = dev->platform_data;
	pri_data = kzalloc(sizeof(struct private_data), GFP_KERNEL);
	pri_data->pdata = pdata;
	dev_set_drvdata(dev,pri_data);

	ret = gpio_request(pdata->gpio,dev_name(dev));
	if(ret < 0){
		printk("gpio request error\n");
		goto err0;
	}
	gpio_direction_input(pdata->gpio);
	nxp_soc_gpio_set_io_pull_enb(pdata->gpio,1);
	nxp_soc_gpio_set_io_pull_sel(pdata->gpio,1);
	nxp_soc_gpio_set_int_enable(pdata->gpio,1);
	nxp_soc_gpio_set_int_mode(pdata->gpio,3);
	pri_data->irq_no = gpio_to_irq(pdata->gpio);
	ret = request_irq(pri_data->irq_no, key_int, IRQF_TRIGGER_RISING, dev_name(dev), dev);
	if(!ret){
        printk("irq registed %d %d\n", pdata->gpio, pri_data->irq_no);
	}else{
    	printk("irq regist fail %d %d\n", pdata->gpio, pri_data->irq_no);
    	goto err1;
	}

	pri_data->dev_id = MKDEV(MAJOR(345),++dev_idx);
	ret = register_chrdev_region(pri_data->dev_id,1,dev_name(dev));
	if(ret < 0){
		printk("register_chrdev error\n");
		goto err2;
	}

	cdev_init(&(pri_data->char_dev),&opts);
	pri_data->char_dev.owner = THIS_MODULE;
	ret = cdev_add(&pri_data->char_dev,pri_data->dev_id,1);
	if(ret < 0){
		printk("cdev add fail\n");
		goto err3;
	}

	device_create(char_class, NULL, pri_data->dev_id, NULL, dev_name(dev), 1);
	printk("hello driver probe\n");
	return 0;

	err3:
	    unregister_chrdev_region(pri_data->dev_id, 1);
	err2:
		free_irq(pri_data->irq_no,dev);
	err1:
		gpio_free(pdata->gpio);
	err0:
		kfree(pri_data);
		return ret;
}

static int hollo_remove(struct device *dev){
	int ret = 0;
	struct private_data *pri_data;
	pri_data = dev_get_drvdata(dev);
	device_destroy(char_class, pri_data->dev_id);
	unregister_chrdev_region(pri_data->dev_id,1);
	nxp_soc_gpio_set_io_pull_enb(pri_data->pdata->gpio,0);
	nxp_soc_gpio_set_int_enable(pri_data->pdata->gpio,0);
	if(pri_data->irq_no){
		free_irq(pri_data->irq_no,dev);
	}
	gpio_free(pri_data->pdata->gpio);
	kfree(pri_data);
	printk("hello driver remove\n");
	return ret;
}

static struct device_driver hello_driver = {
	.name = "hello",
	.bus = &hello_bus,
	.owner = THIS_MODULE,
	.probe = hello_probe,
	.remove = hollo_remove,
};

static int __init hello_init(void){
	int ret;
	char_class = class_create(THIS_MODULE,"gpio_int");
	ret = driver_register(&hello_driver);
	if(ret){
		printk("hello driver fail\n");
	}else{
		printk("hello driver init\n");
	}
	return ret;
}

static void __exit hello_exit(void){
	driver_unregister(&hello_driver);
	class_destroy(char_class);
	printk("hello driver exit\n");
}

module_init(hello_init);
module_exit(hello_exit);
device1

对于设备的注册可以使用platform_data自定义数据

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <mach/gpio.h>
#include <mach/platform.h>
#include "device.h"

extern struct bus_type hello_bus;

static struct hello_pdata pdata = {
	.gpio = PAD_GPIO_D + 20,
};

static struct device hello_dev = {
	.init_name = "hello1",
	.id = 1,
	.bus = &hello_bus,
	.platform_data = &pdata
};

static int __init hello_init(void){
	int ret;
	ret = device_register(&hello_dev);
	printk("hello device init\n");
	return ret;
}

static void __exit hello_exit(void){
	device_del(&hello_dev);
	printk("hello device exit\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

device2
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <mach/gpio.h>
#include <mach/platform.h>
#include "device.h"

extern struct bus_type hello_bus;

static struct hello_pdata pdata = {
	.gpio = PAD_GPIO_D + 16,
};

static struct device hello_dev = {
	.init_name = "hello2",
	.id = 2,
	.bus = &hello_bus,
	.platform_data = &pdata
};

static int __init hello_init(void){
	int ret;
	ret = device_register(&hello_dev);
	printk("hello device init\n");
	return ret;
}

static void __exit hello_exit(void){
	device_unregister(&hello_dev);
	printk("hello device exit\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

猜你喜欢

转载自blog.csdn.net/qq_16054639/article/details/107102214