本期实训源码地址:github-train11.3
Linux内核为驱动开发设计了驱动模型,使总线bus、设备device和驱动driver分别抽象成统一的结构体对象,并且提供了设备与驱动在总线上配对的方法。
在Linux中bus、device、driver的对应结构体对象分别是bus_type、device、device_driver。
通过复写bus_type对象中的match方法,可以定义driver与device之间配对的方法,配对成功后driver中的probe函数就会去做设备device的初始化工作。包括注册设备、挂载设备节点、操作设备资源与方法等。
总线模块: 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
结果截图
可以看到加载总线、驱动、设备模块后,总线会使用match方法匹配驱动与设备是否配对,因为我们在编写驱动与设备对象时名字都是相同的,所以总线match匹配成功,驱动调用了它的探测函数probe。说明测试成功。
这个例子,让我们体会到了linux内核框架下bus、driver与device之间的逻辑依赖与操作关系。但是这只是一个空架子,因为我们没有创建任何一个设备文件,也没有提供任何的设备操作方法。设备中也没有提供任何资源。
自定义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");
自定义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 = ®,
.end = ® + 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方法,在成功配对后,打印资源信息,表示可以获得资源。
综合性训练,对于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文件系统下总线、设备、驱动的创建。
验证成功。
卸载模块后,所有相关信息清空。测试成功。