Linux内核基础——Linux驱动模型(bus/driver/device)实训

Linux驱动模型实训(bus/driver/device)

本期实训源码地址:github-train11.3

Linux内核基础——Linux驱动模型(bus/driver/device)实训_第1张图片
Linux内核为驱动开发设计了驱动模型,使总线bus、设备device和驱动driver分别抽象成统一的结构体对象,并且提供了设备与驱动在总线上配对的方法。

在Linux中bus、device、driver的对应结构体对象分别是bus_type、device、device_driver。

通过复写bus_type对象中的match方法,可以定义driver与device之间配对的方法,配对成功后driver中的probe函数就会去做设备device的初始化工作。包括注册设备、挂载设备节点、操作设备资源与方法等。

任务一、创建bus、device、driver模块,体会三者之间的关联逻辑关系

总线模块: my_bus.c

#include
#include
#include
#include
//注册挂载到总线上的驱动与设备对象之间的匹配方法
int my_match(struct device *dev, struct device_driver *drv)
{
	printk("In %s \n", __func__);
	//按照名字相同的方式匹配
	return (strcmp(dev_name(dev), drv->name) == 0);
}
//初始化总线对象
static struct bus_type my_bus = {
	.name = "my_bus",
	.match = my_match,
};
//将总线对象导出供内核其他模块中的设备对象和驱动对象引用
EXPORT_SYMBOL(my_bus);
static int init_my_bus(void)
{
	int ret = 0;
	printk(KERN_INFO "Hello bus module!\n");
	//在内核中注册总线
	ret = bus_register(&my_bus);
	if (ret) {
		printk(KERN_ERR "bus_register error!\n");
		return ret;
	}
	return ret;
}
//清理并卸载内核模块,注意清理顺序是倒序的,因为各结构体之间有依赖关系
static void cleanup_my_bus(void)
{
	bus_unregister(&my_bus);
	printk(KERN_INFO "Bye module!\n");
}
module_init(init_my_bus);
module_exit(cleanup_my_bus);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zjc");
MODULE_DESCRIPTION("A test project");

设备模块:my_device.c

#include
#include
#include
#include

//设备的清理方法,一般要求要写,即使什么也不做
static void my_release(struct device *dev)
{
	printk(KERN_INFO "In %s \n", __func__);
}
//从内核中导入我们需要的总线对象
extern struct bus_type my_bus;
//初始化设备对象,声明名字、总线对象与清理方法
static struct device my_dev = {
	.init_name = "jit_dev",
	.bus = &my_bus,
	.release = my_release,
};
static int init_hello(void)
{
	int ret = 0;
	printk(KERN_INFO "hello device module!\n");
	//注册设备
	ret = device_register(&my_dev);
	if (ret) {
		printk(KERN_ERR "dev_register error!\n");
		return ret;
	}
	return ret;
}
static void cleanup_hello(void)
{
	device_unregister(&my_dev);
	printk(KERN_INFO "Bye device module!\n");
}
module_init(init_hello);
module_exit(cleanup_hello);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zjc");
MODULE_DESCRIPTION("A test project");

驱动模块: my_driver.c

#include
#include
#include
#include

//从内核中导入我们需要的总线对象
extern struct bus_type my_bus;
//总线匹配成功驱动与设备后,会启动驱动的初始化函数,probe(也称探测函数)
static int my_probe(struct device *dev)
{
	printk(KERN_INFO "In %s \n", __func__);
	return 0;
}
static int my_remove(struct device *dev)
{
	printk(KERN_INFO "In %s \n", __func__);
	return 0;
}
//初始化驱动对象,声明名字、总线对象、探测函数方法与卸载方法
static struct device_driver my_drv = {
	.name = "jit_dev",
	.bus = &my_bus,
	.probe = my_probe,
	.remove = my_remove,
};
static int init_hello(void)
{
	int ret = 0;
	printk(KERN_INFO "hello driver module!\n");
	//在内核中注册驱动
	ret = driver_register(&my_drv);
	if (ret) {
		printk(KERN_ERR "drv_register error!\n");
		return ret;
	}
	return ret;
}
static void cleanup_hello(void)
{
	driver_unregister(&my_drv);
	printk(KERN_INFO "Bye driver module!\n");
}
module_init(init_hello);
module_exit(cleanup_hello);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zjc");
MODULE_DESCRIPTION("A test project");

测试:我们的模块加载顺序一定要是先加载总线,因为驱动与设备依赖于总线模块。这里写了一个不推荐的偷懒Makefile脚本。

Makefile

obj-m += my_bus.o
obj-m += my_driver.o
obj-m += my_device.o
all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
	sudo insmod my_bus.ko
	sudo insmod my_driver.ko
	sudo insmod my_device.ko
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
	sudo rmmod my_device
	sudo rmmod my_driver
	sudo rmmod my_bus

结果截图
Linux内核基础——Linux驱动模型(bus/driver/device)实训_第2张图片
可以看到加载总线、驱动、设备模块后,总线会使用match方法匹配驱动与设备是否配对,因为我们在编写驱动与设备对象时名字都是相同的,所以总线match匹配成功,驱动调用了它的探测函数probe。说明测试成功。

这个例子,让我们体会到了linux内核框架下bus、driver与device之间的逻辑依赖与操作关系。但是这只是一个空架子,因为我们没有创建任何一个设备文件,也没有提供任何的设备操作方法。设备中也没有提供任何资源。

任务二、在device、device_driver的基础上封装一套自定义的设备与驱动,并且在总线模块中编写它们的注册与卸载方法,并且在match中定义它们的配对方法是通过额外封装的id与数组表查表的配对方式。

自定义advanced_device、advanced_driver,注册注销方法的头文件:advanced_device.h

#ifndef _ADVANCED_DEVICE_H_
#define _ADVANCED_DEVICE_H_

#include

#define PROBTBSIZE 5
struct advanced_device {
	int id;
	struct device dev;
};

extern int advanced_device_register(struct advanced_device *);
extern void advanced_device_unregister(struct advanced_device *);

struct advanced_driver {
	struct device_driver driver;
	int prob_tb[PROBTBSIZE];
};

extern int advanced_driver_register(struct advanced_driver *);
extern void advanced_driver_unregister(struct advanced_driver *);
#endif

总线模块:my_bus.c

#include
#include
#include
#include

#include"advanced_device.h"

//注册挂载到总线上的驱动与设备对象之间的匹配方法
int my_match(struct device *dev, struct device_driver *drv)
{
	struct advanced_device *adev;
	struct advanced_driver *adrv;
	int i = 0;
	printk("In %s \n", __func__);
	//获得自定义的设备与驱动对象
	adev = container_of(dev, struct advanced_device, dev);
	adrv = container_of(drv, struct advanced_driver, driver);
	//在驱动中查表,id存在则匹配
	for (i = 0; i < PROBTBSIZE; i ++) {
		if(adev->id == adrv->prob_tb[i])
			return 1;
	}
	return 0;
}
//初始化总线对象
static struct bus_type my_bus = {
	.name = "my_bus",
	.match = my_match,
};
EXPORT_SYMBOL_GPL(my_bus);
//自定义advanced_device设备的注册方法,注册时指明了总线
int advanced_device_register(struct advanced_device *adev)
{
	adev->dev.bus = &my_bus;
	device_initialize(&adev->dev);
	return device_add(&adev->dev);
}
EXPORT_SYMBOL_GPL(advanced_device_register);

//自定义advanced_device设备的销毁方法
void advanced_device_unregister(struct advanced_device *adev)
{
	device_del(&adev->dev);
	put_device(&adev->dev);
}
EXPORT_SYMBOL_GPL(advanced_device_unregister);

//自定义advanced_driver驱动的注册方法,注册时指明了总线
int advanced_driver_register(struct advanced_driver *adrv)
{
	adrv->driver.bus = &my_bus;
	return driver_register(&adrv->driver);
}
EXPORT_SYMBOL_GPL(advanced_driver_register);

//自定义advanced_driver驱动的销毁方法
void advanced_driver_unregister(struct advanced_driver *adrv)
{
	driver_unregister(&adrv->driver);
}
EXPORT_SYMBOL_GPL(advanced_driver_unregister);

static int init_my_bus(void)
{
	int ret = 0;
	printk(KERN_INFO "Hello bus module!\n");
	//在内核中注册总线
	ret = bus_register(&my_bus);
	if (ret) {
		printk(KERN_ERR "bus_register error!\n");
		return ret;
	}
	return ret;
}
static void cleanup_my_bus(void)
{
	bus_unregister(&my_bus);
	printk(KERN_INFO "Bye module!\n");
}
module_init(init_my_bus);
module_exit(cleanup_my_bus);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zjc");
MODULE_DESCRIPTION("A test project");

总线模块中利用container_of宏与传入的device对象可以获得包括这个device对象的自定义封装的advanced_device,从而可以获得advanced_device中的设备号id,获得advanced_driver驱动对象中的设备号表也是同理。这样就可以改写probe来达到我们查表配对的需求。

总线模块中还提供了封装对象与驱动的注册和销毁方法,都通过EXPORT_SYMBOL宏导出供其他模块使用。并且由于是该总线模块提供的方法,所以在注册驱动与设备时的总线都自动指明为当前总线对象。

advanced_driver驱动模块:my_driver.c

#include
#include
#include
#include
#include "advanced_device.h"

//从内核中导入我们需要的总线对象
extern struct bus_type my_bus;
//总线匹配成功驱动与设备后,会启动驱动的初始化函数,probe(也称探测函数)
static int my_probe(struct device *dev)
{
	printk(KERN_INFO "In %s \n", __func__);
	return 0;
}
static int my_remove(struct device *dev)
{
	printk(KERN_INFO "In %s \n", __func__);
	return 0;
}
//初始化自定义的驱动对象,声明支持的设备号表、名字、探测函数方法与卸载方法,会在注册时指定总线
static struct advanced_driver my_drv = {
	.prob_tb = {1, 2, 3, 4 ,5},
	.driver = {
		.name = "jit_dev",
		.probe = my_probe,
		.remove = my_remove,
	},
};
static int init_hello(void)
{
	int ret = 0;
	printk(KERN_INFO "hello driver module!\n");
	//在内核中注册驱动
	ret = advanced_driver_register(&my_drv);
	if (ret) {
		printk(KERN_ERR "drv_register error!\n");
		return ret;
	}
	return ret;
}
static void cleanup_hello(void)
{
	advanced_driver_unregister(&my_drv);
	printk(KERN_INFO "Bye driver module!\n");
}
module_init(init_hello);
module_exit(cleanup_hello);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zjc");
MODULE_DESCRIPTION("A test project");

advanced_device设备模块:my_device.c

#include
#include
#include
#include
#include"advanced_device.h"

//设备的清理方法,一般要求要写,即使什么也不做
static void my_release(struct device *dev)
{
	printk(KERN_INFO "In %s \n", __func__);
}
//初始化设备对象,设备id号、声明名字、清理方法,总线在注册时会自动指明
static struct advanced_device my_dev = {
	.id = 1,
	.dev = {
		.init_name = "jit_dev",
		.release = my_release,
	}
};
static int init_hello(void)
{
	int ret = 0;
	printk(KERN_INFO "hello device module!\n");
	//注册自定义的设备,会自动指定总线
	ret = advanced_device_register(&my_dev);
	if (ret) {
		printk(KERN_ERR "dev_register error!\n");
		return ret;
	}
	return ret;
}
static void cleanup_hello(void)
{
	advanced_device_unregister(&my_dev);
	printk(KERN_INFO "Bye device module!\n");
}
module_init(init_hello);
module_exit(cleanup_hello);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zjc");
MODULE_DESCRIPTION("A test project");

结果截图
Linux内核基础——Linux驱动模型(bus/driver/device)实训_第3张图片
配对成功,结果测试验证成功。

任务三、在任务二的基础上在advanced_device中增加struct resource资源属性,并且使得驱动可以获得这段资源

自定义advanced_device、advanced_driver,注册注销方法的头文件:advanced_device.h

struct advanced_device {
	int id;
	struct device dev;
	struct resource *resource;
};

在任务二的基础上增加了resource。

总线模块:my_bus.c与任务二相同,总线模块不参与资源操作

advanced_device设备模块:my_device.c

static int reg = 666;
//初始化设备资源
static struct resource my_resource[] = {
	{
		.name = "设备寄存器地址",
		.start = &reg,
		.end = &reg + 0x04,
		.flags = IORESOURCE_MEM,
	}
};
//初始化设备对象,设备id号、声明名字、清理方法,总线在注册时会自动指明
static struct advanced_device my_dev = {
	.id = 1,
	.resource = my_resource,
	.dev = {
		.init_name = "jit_dev",
		.release = my_release,
	},
};

新增了resource的初始化以及添加该成员到my_dev中。

resource的start属性一般都是指向资源的首地址,end是资源尾地址,flags是资源地址映射的方式。(关于资源resource)

advanced_driver驱动模块:my_driver.c

//总线匹配成功驱动与设备后,会启动驱动的初始化函数,probe(也称探测函数)
static int my_probe(struct device *dev)
{
	struct advanced_device *adev;
	printk(KERN_INFO "In %s \n", __func__);
	adev = container_of(dev, struct advanced_device, dev);
	printk(KERN_INFO "Resource name is %s, content is %x\n", adev->resource[0].name,
	 adev->resource[0].start);
	return 0;
}

驱动模块改写了probe方法,在成功配对后,打印资源信息,表示可以获得资源。

结果截图
Linux内核基础——Linux驱动模型(bus/driver/device)实训_第4张图片

任务四、综合训练,使用platform_device、platform_driver、platform封装好的bus_type,来注册一个字符设备驱动,并且给字符设备驱动提供一些基本操作方法以及尝试给驱动传递resource信息

综合性训练,对于platform来说,内核已经封装好了platform的bus,这个bus的匹配方法是使用名字匹配。并且内核提供的platform_deivce_register和platform_driver_register也就是驱动与设备的注册方法在实现中已经为我们指定了bus总线,就像我的任务二、三所设计的那样。所以我们不需要编写bus模块,只需要写device和driver模块即可。

设备模块: my_deivce.c

#include
#include
#include
#include
#include

//初始化设备资源
static struct resource my_resource[] = {
	{
		.name = "设备寄存器地址",
		.start = 0x666666,
		.end = 0x888888,
		.flags = IORESOURCE_MEM,
	}
};
//初始化platform_device
static struct platform_device pdev = {
		.name = "zjc_dev1",
		.resource = my_resource,
};
static int init_hello(void)
{
	int ret = 0;
	printk(KERN_INFO "hello device module!\n");
		//注册platform_device设备,会默认指明platform_bus_type,并且自动填充release函数
		ret = platform_device_register(&pdev);
	return ret;
}
static void cleanup_hello(void)
{
	platform_device_unregister(&pdev);
	printk(KERN_INFO "Bye device module!\n");
}
module_init(init_hello);
module_exit(cleanup_hello);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zjc");
MODULE_DESCRIPTION("A test project");

设备模块很简单,只是提供了资源以及注册了platform_device,加载到内核中等候platform bus上的同名platform_driver与之匹配即可。

驱动模块:my_driver.c

#include
#include
#include
#include
#include
#include
#include

#define HELLO_MAJOR 252
static dev_t hello_devno;
static struct cdev *hello_cdev;
static struct class *cls;
static struct device *my_dev;

//初始化驱动对象,声明名字、总线对象、探测函数方法与卸载方法
static struct platform_driver my_drv = {
	.probe = my_probe,
	.remove = my_remove,
	.driver = {
		.name = "zjc_dev1",
	}
};

static int my_open(struct inode *pnode, struct file *filp)
{
	printk(KERN_INFO "In %s \n", __func__);
	return 0;
}
//字符设备的文件操作集
static struct file_operations hello_ops = {
	.owner = THIS_MODULE,
	.open = my_open,
};
//总线匹配成功驱动与设备后,会启动驱动的初始化函数,probe(也称探测函数)
static int my_probe(struct platform_device *dev)
{
	printk(KERN_INFO "In %s \n", __func__);
	//打印资源
	printk(KERN_INFO "Resource name is %s, start is 0x%x, end is 0x%x\n", dev->resource[0].name,
			dev->resource[0].start, dev->resource[0].end);
	int ret;
	//利用宏将主次设备号转化为32位的设备号,供注册函数使用
	hello_devno = MKDEV(HELLO_MAJOR, 0);
	//注册字符设备号
	ret = register_chrdev_region(hello_devno, 1, "zjcchar");
	if (ret) {
		//注册失败则由系统分配一个设备号
		ret = alloc_chrdev_region(&hello_devno, 0, 1, "zjcchar");
		if (ret) {
			printk(KERN_ERR "allocchr error!\n");
			goto error0;
		}
	}
	//动态分配空间给cdev字符设备对象
	hello_cdev = cdev_alloc();
	if (hello_cdev == NULL) {
		printk(KERN_ERR "alloc error!\n");
		ret = -1;
		goto error1;	
	}
	//初始化cdev字符设备对象并将操作集赋给它
	cdev_init(hello_cdev, &hello_ops);
	//将cdev字符设备对象注册到内核中
	ret = cdev_add(hello_cdev, hello_devno, 1);
	if (ret) {
		printk(KERN_ERR "add error!\n");
		goto error2;
	}
	//创建class供创建设备节点时使用
	cls = class_create(THIS_MODULE, "testclass");
	if (IS_ERR(cls)) {
		ret = PTR_ERR(dev);
		printk(KERN_ERR "class_create error!\n");
		goto error2;
	}
	//创建设备节点在/dev/下,名字为testdev
	my_dev = device_create(cls, NULL, hello_cdev->dev, NULL, "testdev");
	if (IS_ERR(my_dev)) {
		ret = PTR_ERR(my_dev);
		printk(KERN_ERR "device_create error!\n");
		goto error3;
	}
	printk(KERN_ERR "Hello, World!\n");
	return 0;
error3:
	class_destroy(cls);
error2:
	cdev_del(hello_cdev);
error1:	
	unregister_chrdev_region(hello_devno, 1);
error0:
	return ret;
	return 0;
}
//删除字符设备
static int my_remove(struct platform_device *dev)
{
	device_destroy(cls, hello_devno);
	class_destroy(cls);
	cdev_del(hello_cdev);
	unregister_chrdev_region(hello_devno, 1);
	printk(KERN_INFO "In %s \n", __func__);
	return 0;
}
static int init_hello(void)
{
	int ret = 0;
	printk(KERN_INFO "hello driver module!\n");
	//在内核中注册驱动
	ret = platform_driver_register(&my_drv);
	if (ret) {
		printk(KERN_ERR "drv_register error!\n");
		return ret;
	}
	return ret;
}
static void cleanup_hello(void)
{
	platform_driver_unregister(&my_drv);
	printk(KERN_INFO "Bye driver module!\n");
}
module_init(init_hello);
module_exit(cleanup_hello);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zjc");
MODULE_DESCRIPTION("A test project");

重点在于platform_driver驱动初始化时的姓名与前面写的设备名字相同,这样才能在platform bus总线上被匹配。

匹配后会去执行platform_driver中的probe探测函数,可以看到探测函数主要是做了字符设备创建的工作:注册设备号、创建字符设备、创建设备节点等。字符设备提供了一个open操作,主要是打印信息证明来过。

结果截图
在这里插入图片描述
加载模块设备与驱动模块后,总线匹配成功,查看proc文件系统下设备号的创建、/dev/下设备节点的创建、sys文件系统下总线、设备、驱动的创建。
Linux内核基础——Linux驱动模型(bus/driver/device)实训_第5张图片
验证成功。
在这里插入图片描述
卸载模块后,所有相关信息清空。测试成功。

你可能感兴趣的:(课程笔记——操作系统定制技术)