今天想要把一份驱动放到Linux内核下运行,所以开启一段Linux模块的编写历程,之前接触过一些相关的知识,但多停留在纸面上,今天实战一次。目前手上有一份工作正常的内核源码和一块工作正常的板卡,开发环境都是就绪的。
1. 模块信息
MODULE_AUTHOR("xflm"); // 作者
MODULE_DESCRIPTION("mac test"); // 简介
MODULE_LICENSE("Dual MIT/GPL"); // 版权,我随便找了一个写上
2. 驱动框架
- 先定义一个驱动实体
struct platform_driver mac_platform_driver
。struct platform_driver mac_platform_driver
定义在include/linux/platform_device.h
。
struct platform_driver { // 平台驱动抽象模型 int (*probe)(struct platform_device *); /* 设备探测,设备和驱动匹配后自动调用,必须提供 */ int (*remove)(struct platform_device *); /* 设备移除,设备卸载时自动调用,必须提供 */ int (*shutdown)(struct platform_device *); /* 关机 */ int (*suspend)(struct platform_device *, pm_message_t state); /* 挂起,电源关闭 */ int (*resume)(struct platform_device *); /* 恢复,电源打开 */ struct device_driver driver; /* 设备驱动抽象模型 */ const struct platfoem_device_id *id_table; /* 平台设备ID,指向一个以全0结尾的结构体数组 */ bool prevent_deferred_probe; } // include/linux/device.h struct device_driver { const char *name; /* 设备驱动名称 */ struct bus_type *bus; /* 设备所属的总线类型 */ struct module *owner; /* 模块的拥有者,通常为宏`THIS_MODULE` */ const char *mod_name; /* 模块内部使用 */ ... const struct of_device_id *of_match_table; /* 设备树中匹配的设备名称,指向一个以全0结尾的结构体数组 */ ... }
platform_match()
定义在drivers/base/platform.c
,定义了设备和驱动匹配的过程,匹配成功返回1
。
static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev = to_platform_device(dev); struct platform_driver *pdrv = to_platform_driver(drv); /* 1. 若设备驱动之前匹配过驱动(比如注册了多个相同的设备,则他们的驱动应当相同),则直接比较`struct device_driver->name`。 */ if (pdev->driver-override) return !strcmp(pdev->driver_override, drv->name); /* 2. 匹配设备树(OF)中注册的设备 。*/ if (of_driver_match_device(dev, drv)) return 1; /* 3. 匹配ACPI中注册的设备。 */ if (acpi_driver_match_device(dev, drv)) return 1; /* 4. 匹配`struct platform_driver->id_table`。 */ if (pdrv->id_table) return platform_match_id(pdrv->id_table, pdev) != NULL; /* 5. 匹配`struct platform_device->name` 和`struct device_driver->name` */ return (strcmp(pdev->name, drv->name) == 0); }
- 填充驱动实体
mac_platform_driver
。
static int mac_probe(struct platform_device *mac_platform_device) { printk("%s\n", __func__); return 0; } static int mac_remove(struct platform_device *mac_platform_device) { printk("%s\n", __func__); return 0; } static struct platform_driver mac_paltform_driver = { .probe = mac_probe, .remove = mac_remove, .driver = { .name = "mac"; /* 根据`platform_match()`,定义该值即可匹配成功。 */ }, }
- 驱动注册。
- 可以使用
module_platform_driver()
注册,module_platform_driver()
是个宏定义,在include/linux/device.h
中,展开后如下。
static int __init __driver_init(void) { return __platform_driver_register(&mac_platform_driver, THIS_MODULE); } module_init(__driver_init); static void __exit __driver_exit(void) { __platform_driver_unregister(&mac_platform_driver, THIS_MODULE); } module_exit(__driver_exit);
- 由于不打算修改设备树,所以注册驱动时还需要使用
platform_device_register()
注册个设备,module_init()
定义了模块加载时的入口函数,故需要将platform_device_register()
函数写到__driver_init()
中,也就不能使用module_platform_driver()
了。
static int __init mac_module_init(void) { int ret; ret = platform_device_register(&mac_platform_device); // device和driver可以先可以后,没有必然联系 if (!ret) ret = platform_driver_register(&mac_paltfoem_driver); return ret; } module_init(mac_module_init); static void __exit mac_module_exit(void) { platform_driver_unregister(&mac_platform_driver); // device和driver可以先可以后,没有必然联系 platform_device_unregister(&mac_platform_device); } module_exit(mac_module_exit);
- 可以使用
- 定义一个设备实体
struct platform_device mac_platform_device
。struct platform_device
定义在include/linux/platform_device.h
中。
struct platform_device { const char *name; // 设备名称 int id; // 设备编号,多个相同的设备编号为0 1 2... struct device dev; // 设备基本数据 ... } // include/linux/device.h struct device { ... void (*release)(struct device *dev); // 设备释放时自动调用,必须提供,这个第一次测试未关心,通过打印日志看到的 ... }
- 填充设备实体
mac_platform_device
。
static void mac_release(struct device *dev) { printk("%s\n", __func__); } struct platform_device mac_platform_device = { .name = "mac", // 必须和`mac_platform_driver.driver.name`相同 .id = 0, // 只定义一个,定义多个需要使用不同的id,或者使能`mac_platform_device.id_auto` .dev = { .release = mac_release, // 这个第一次测试未关心,通过打印日志看到的 }, }
3. 模块源码
#include <linux/module.h>
#include <linux/platform_device.h>
static int mac_probe(struct platform_device *mac_platform_device)
{
printk("%s\n", __func__);
return 0;
}
static int mac_remove(struct platform_device *mac_platform_device)
{
printk("%s\n", __func__);
return 0;
}
static struct platform_driver mac_paltform_driver = {
.probe = mac_probe,
.remove = mac_remove,
.driver = {
.name = "mac"; /* 根据`platform_match()`,定义该值即可匹配成功。 */
},
}
static void mac_release(struct device *dev)
{
printk("%s\n", __func__);
}
struct platform_device mac_platform_device = {
.name = "mac", // 必须和`mac_platform_driver.driver.name`相同
.id = 0, // 只定义一个,定义多个需要使用不同的id,或者使能`mac_platform_device.id_auto`
.dev = {
.release = mac_release,
},
}
static int __init mac_module_init(void)
{
int ret;
ret = platform_device_register(&mac_platform_device); // device和driver可以先可以后
if (!ret)
ret = platform_driver_register(&mac_paltfoem_driver);
return ret;
}
module_init(mac_module_init);
static void __exit mac_module_exit(void)
{
platform_driver_unregister(&mac_platform_driver); // device和driver可以先可以后
platform_device_unregister(&mac_platform_device);
}
module_exit(mac_module_exit);
MODULE_AUTHOR("xflm"); // 作者
MODULE_DESCRIPTION("mac test"); // 简介
MODULE_LICENSE("Dual MIT/GPL"); // 版权,我随便找了一个写上
4. 模块编译
- 编写
Makefile
文件。
# CPU架构
export ARCH = csky
# 交叉工具链
export CROSS_COMPILE = /opt/linux-glibc/csky-avbiv2-linux-
# 内核源码的编译目录
KSRC = /home/xflm/workspace/csky/kernel/build
# 编译模块
obj-m += mac.o
# make缺省目标,即编译模块
modules:
$(MAKE) -C $(KSRC) M=`pwd` modules
# make其他目标,比如make clean
%:
$(MAKE) -C $(KSRC) M=`pwd` $@
- 编译,下载(略),并运行。
# 编译
$ make
# 插入模块,执行了`mac_probe()`但是报了错误警告`加载了个设备树以外的设备,污染了内核`
$ insmod mac.ko
[18745.095224] mac: loading out-of-tree module taints kernel.
[18745.104503] mac_probe
# 查看模块插入情况,插入成功
$ lsmod
tainted: G
mac 999 0 - Live 0xc017e000 (o)
# 卸载模块,执行了`mac_remove()`但是报了错误警告`缺少release()`函数,内核仍工作正常
$ rmmod mac.ko
[19907.272900] mac_remove
[19907.275568] -------[ cut here ]------
...
[19907.296775] Device 'mac.0' doed not have a release() function, it is broken and must be fixed.
# 查看模块卸载情况,卸载成功
$ lsmod
tainted: G
- 修复了
release()
函数问题后测试。
# 插入模块,执行了`mac_probe()`但是没报错误警告,之前的警告只会报一次
$ insmod mac.ko
[22407.680126] mac_probe
# 查看模块插入情况,插入成功
$ lsmod
tainted: G
mac 1063 0 - Live 0xc018a000 (o)
# 卸载模块,执行了`mac_remove()`但是报了错误警告`缺少release()`函数,内核仍工作正常
$ rmmod mac.ko
[22415.180496] mac_remove
[22415.183102] mac_release
...
[19907.296775] Device 'mac.0' doed not have a release() function, it is broken and must be fixed.
# 查看模块卸载情况,卸载成功
$ lsmod
tainted: G
今天先到这。下一篇Linux模块(2) - 创建设备节点。
5. 参考
module_platform_driver()
Linux Platform驱动模型(二) _驱动方法
Linux驱动–of_match_ptr 宏定义