12.1 Linux 驱动的软件架构
7.2.5 中驱动软件架构的四个核心抽象:
| 抽象 | 头文件 | 作用 |
|---|---|---|
struct bus_type |
include/linux/device/bus.h |
总线类型(匹配规则) |
struct device |
include/linux/device.h |
设备实例(挂在哪条总线、有哪些资源) |
struct device_driver |
include/linux/device/driver.h |
驱动(能驱动哪些设备) |
struct class |
include/linux/device/class.h |
用户空间视角的设备分类(/sys/class/) |
匹配流程:
device_register() -> bus_add_device() -> device_attach()
-> bus_probe_device() -> bus->match(dev, drv)
-> 匹配成功 -> really_probe() -> drv->probe(dev)
7.2.5 的头文件拆分(重要变化):
4.0 时代所有设备模型结构体都堆在 include/linux/device.h 里。7.2.5 已经拆分:
| 结构体 | 7.2.5 所在头文件 |
|---|---|
struct class、class_create()、class_register() |
include/linux/device/class.h |
struct bus_type |
include/linux/device/bus.h |
struct device_driver、module_driver() |
include/linux/device/driver.h |
struct device、device_create()、devm_*() |
include/linux/device.h |
struct of_device_id、platform_device_id、i2c_device_id |
include/linux/device-id/*.h |
include/linux/mod_devicetable.h |
仅作为汇总转发头(只有 81 行) |
class_create() 的签名变化(移植必看):
cpp
/* Linux 4.0 及更早 */
struct class *class_create(struct module *owner, const char *name);
/* Linux 6.4+ / 7.2.5:只有一个参数 */
struct class *class_create(const char *name);
现代代码中 owner 由 device_driver 自己管理,不再需要传给 class_create()。
12.2 platform 设备驱动
12.2.1 platform 总线、设备与驱动
platform 总线是"伪总线",用于不依赖标准总线枚举的片上设备(SoC 内部外设、QEMU 虚拟设备)。
cpp
/* include/linux/platform_device.h */
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
...
};
struct platform_driver {
int (*probe)(struct platform_device *);
void (*remove)(struct platform_device *); /* 7.2.5:返回 void! */
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
bool driver_managed_dma;
};
7.2.5 的重要变化:
.remove返回void(4.0 时代返回int)。这是移植旧代码时最常见的编译错误之一。- 新增
driver_managed_dma字段:只有自己管理 DMA(如 VFIO)的驱动才设为true。 - 新增
.shutdown回调的推荐用法。 platform_driver_register()现在是宏,自动传入THIS_MODULE和KBUILD_MODNAME。
最小 platform 驱动:
cpp
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
static int my_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
int irq;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
dev_info(&pdev->dev, "probed, base=%pR irq=%d\n", res, irq);
return 0;
}
static void my_remove(struct platform_device *pdev) /* 注意 void */
{
dev_info(&pdev->dev, "removed\n");
}
static const struct of_device_id my_of_match[] = {
{ .compatible = "vendor,my-device" },
{ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my-device",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("minimal platform driver, Linux 7.2.5");
设备实例化的现代方式(按推荐程度排序):
| 方式 | 说明 | 7.2.5 推荐度 |
|---|---|---|
设备树(of_match_table) |
ARM/ARM64/RISC-V 主路径 | ★★★★★ |
ACPI(acpi_match_table) |
x86 服务器主路径 | ★★★★★ |
software node(software_node) |
板载不可枚举设备、单元测试 | ★★★★ |
platform_device_register_full() |
运行时动态创建 | ★★★ |
platform_device_register_simple() |
legacy,注释明确标注 | ★ |
直接 platform_device_register() + 手填 resource |
老式板级文件做法 | ✗ |
QEMU 中创建 platform 设备的三条路径:
- 设备树 (
qemu-system-aarch64 -machine virt -dtb ...):在arch/arm64/boot/dts/或自建 DTS 中加入节点。 - ACPI (x86 用
-acpitable或qemu自带 SSDT)。 - 模块内自注册 :在
module_init()中platform_device_register_full(),卸载时platform_device_unregister()------这是 QEMU 实验最方便的方式。
cpp
/* 自注册 platform 设备(QEMU 实验用) */
static struct platform_device *my_pdev;
static int __init my_init(void)
{
struct resource res[] = {
{ .start = 0x10000000, .end = 0x10000fff, .flags = IORESOURCE_MEM },
{ .start = 20, .end = 20, .flags = IORESOURCE_IRQ },
};
int ret = platform_driver_register(&my_driver);
if (ret)
return ret;
my_pdev = platform_device_register_simple("my-device", -1, res, 2);
if (IS_ERR(my_pdev)) {
platform_driver_unregister(&my_driver);
return PTR_ERR(my_pdev);
}
return 0;
}
12.2.2 将 globalfifo 作为 platform 设备
把第 8 章的 globalfifo 从"模块自己申请设备号"改成"由 platform 总线提供资源":
cpp
struct globalfifo_dev {
struct cdev cdev;
struct device *device;
struct mutex lock;
struct kfifo fifo;
wait_queue_head_t r_wait, w_wait;
struct fasync_struct *async_queue;
};
static int globalfifo_probe(struct platform_device *pdev)
{
struct globalfifo_dev *dev;
dev_t devno;
int ret;
/* 用 id 生成次设备号,支持多个实例 */
ret = alloc_chrdev_region(&devno, pdev->id < 0 ? 0 : pdev->id, 1,
"globalfifo");
if (ret)
return ret;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev) { ret = -ENOMEM; goto err_region; }
mutex_init(&dev->lock);
init_waitqueue_head(&dev->r_wait);
init_waitqueue_head(&dev->w_wait);
ret = kfifo_alloc(&dev->fifo, GLOBALFIFO_SIZE, GFP_KERNEL);
if (ret) goto err_region;
cdev_init(&dev->cdev, &globalfifo_fops);
dev->cdev.owner = THIS_MODULE;
ret = cdev_add(&dev->cdev, devno, 1);
if (ret) goto err_fifo;
dev->device = device_create(globalfifo_class, &pdev->dev, devno, dev,
"globalfifo%d", pdev->id);
if (IS_ERR(dev->device)) { ret = PTR_ERR(dev->device); goto err_cdev; }
platform_set_drvdata(pdev, dev);
dev_info(&pdev->dev, "globalfifo probed\n");
return 0;
err_cdev:
cdev_del(&dev->cdev);
err_fifo:
kfifo_free(&dev->fifo);
err_region:
unregister_chrdev_region(devno, 1);
return ret;
}
优点:设备号、资源、生命周期都交给设备模型管理,支持多实例、热插拔、电源管理。
12.2.3 platform 设备资源和数据
cpp
/* 取资源 */
struct resource *platform_get_resource(struct platform_device *pdev,
unsigned int type, unsigned int num);
struct resource *platform_get_resource_byname(struct platform_device *pdev,
unsigned int type, const char *name);
int platform_get_irq(struct platform_device *pdev, unsigned int num);
int platform_get_irq_optional(struct platform_device *pdev, unsigned int num);
int platform_irq_count(struct platform_device *pdev);
/* 私有数据 */
void platform_set_drvdata(struct platform_device *pdev, void *data);
void *platform_get_drvdata(struct platform_device *pdev);
7.2.5 推荐用 devm_* 组合接口:
cpp
void __iomem *devm_platform_ioremap_resource(struct platform_device *pdev,
unsigned int index);
void __iomem *devm_platform_ioremap_resource_byname(struct platform_device *pdev,
const char *name);
void __iomem *devm_platform_get_and_ioremap_resource(struct platform_device *pdev,
unsigned int index,
struct resource **res);
设备树资源示例:
cpp
my-device@10000000 {
compatible = "vendor,my-device";
reg = <0x0 0x10000000 0x0 0x1000>;
interrupts = <0 20 4>; /* GIC SPI 20,高电平触发 */
clocks = <&clk 5>;
clock-names = "core";
gpios = <&gpio 3 GPIO_ACTIVE_LOW>;
};
资源类型枚举:
| 类型 | 含义 |
|---|---|
IORESOURCE_MEM |
MMIO 区间 |
IORESOURCE_IO |
I/O 端口区间 |
IORESOURCE_IRQ |
中断号 |
IORESOURCE_DMA |
DMA 通道 |
IORESOURCE_BUS |
总线号 |
12.3 设备驱动的分层思想
分层模型:
用户空间 (/dev, /sys)
↑
设备驱动核心层(子系统提供的通用框架)
↑
例化驱动(你的硬件驱动)
↑
硬件
为什么分层:
- 核心层统一处理用户接口、文件操作、并发、电源管理;
- 例化驱动只处理"硬件差异";
- 上层应用不需要知道具体硬件。
12.3.1 设备驱动核心层和例化
| 子系统 | 核心层 | 例化驱动需要实现 |
|---|---|---|
| 输入 | drivers/input/input.c |
input_dev + input_register_device() |
| RTC | drivers/rtc/class.c |
struct rtc_class_ops |
| Framebuffer | drivers/video/fbdev/core/ |
struct fb_info + fb_ops |
| TTY | drivers/tty/tty_io.c |
struct tty_operations |
| misc | drivers/char/misc.c |
struct miscdevice |
| I²C | drivers/i2c/i2c-core*.c |
struct i2c_driver |
| SPI | drivers/spi/spi.c |
struct spi_driver |
| 网络 | net/core/dev.c |
struct net_device_ops |
| 块 | block/blk-core.c |
block_device_operations |
12.3.2 输入设备驱动
cpp
/* include/linux/input.h */
struct input_dev *dev;
struct input_dev __must_check *input_allocate_device(void);
struct input_dev __must_check *devm_input_allocate_device(struct device *);
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);
void input_set_capability(struct input_dev *dev, unsigned int type,
unsigned int code);
void input_report_key(struct input_dev *dev, unsigned int code, int value);
void input_report_rel(struct input_dev *dev, unsigned int code, int value);
void input_report_abs(struct input_dev *dev, unsigned int code, int value);
void input_sync(struct input_dev *dev);
最小输入驱动:
cpp
static int btn_probe(struct platform_device *pdev)
{
struct input_dev *input;
int ret;
input = devm_input_allocate_device(&pdev->dev);
if (!input)
return -ENOMEM;
input->name = "sim-button";
input->phys = "sim-button/input0";
input->id.bustype = BUS_HOST;
input_set_capability(input, EV_KEY, KEY_POWER);
ret = input_register_device(input);
if (ret)
return ret;
/* 在中断/工作中上报事件 */
input_report_key(input, KEY_POWER, 1); /* 按下 */
input_sync(input);
input_report_key(input, KEY_POWER, 0); /* 抬起 */
input_sync(input);
return 0;
}
7.2.5 要点:
input_allocate_device()必须配input_free_device();devm_input_allocate_device()自动释放。- 必须调用
input_sync()才算一个完整事件,否则用户空间读不到。 EV_KEY/EV_REL/EV_ABS/EV_MSC等类型定义在include/uapi/linux/input-event-codes.h。- 事件通过
/dev/input/eventN暴露,可用evtest/libevdev验证。
QEMU 验证:
bash
qemu-system-x86_64 ... -device virtio-keyboard-pci # 或 -device virtio-input-host-pci
# guest 中
ls /dev/input/
cat /proc/bus/input/devices
evtest /dev/input/event0
12.3.3 RTC 设备驱动
cpp
/* include/linux/rtc.h */
struct rtc_class_ops {
int (*read_time)(struct device *dev, struct rtc_time *tm);
int (*set_time)(struct device *dev, struct rtc_time *tm);
int (*read_alarm)(struct device *dev, struct rtc_wkalrm *alarm);
int (*set_alarm)(struct device *dev, struct rtc_wkalrm *alarm);
int (*alarm_irq_enable)(struct device *dev, unsigned int enabled);
...
};
struct rtc_device *devm_rtc_allocate_device(struct device *dev);
#define devm_rtc_register_device(device) \
__devm_rtc_register_device(THIS_MODULE, device)
7.2.5 推荐流程:
cpp
static int my_rtc_probe(struct platform_device *pdev)
{
struct rtc_device *rtc;
struct my_rtc *my;
my = devm_kzalloc(&pdev->dev, sizeof(*my), GFP_KERNEL);
if (!my)
return -ENOMEM;
my->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(my->base))
return PTR_ERR(my->base);
rtc = devm_rtc_allocate_device(&pdev->dev);
if (IS_ERR(rtc))
return PTR_ERR(rtc);
rtc->ops = &my_rtc_ops;
rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
rtc->range_max = RTC_TIMESTAMP_END_2199;
return devm_rtc_register_device(rtc);
}
7.2.5 变化:
rtc_device_register()已废弃,改用devm_rtc_allocate_device()+devm_rtc_register_device()。- 必须设置
range_min/range_max,否则hwclock可能失败。 - RTC 通过
/dev/rtc0和/sys/class/rtc/rtc0/暴露。
QEMU 验证:
bash
# QEMU 自带 RTC(-rtc base=utc,clock=host)
guest 中:
cat /sys/class/rtc/rtc0/date
cat /sys/class/rtc/rtc0/time
hwclock -r
hwclock -w
12.3.4 Framebuffer 设备驱动
cpp
/* include/linux/fb.h */
struct fb_info {
int node;
int flags;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_ops *fbops;
struct device *device;
struct screen_buffer *screen_buffer;
...
};
int register_framebuffer(struct fb_info *fb_info);
int devm_register_framebuffer(struct device *dev, struct fb_info *fb_info);
void unregister_framebuffer(struct fb_info *fb_info);
7.2.5 的重要变化:
- 传统
fbdev(drivers/video/fbdev/)已被 DRM/KMS 取代,新硬件应写 DRM 驱动。 fbdev仍保留,用于兼容和简单显示(如 QEMU 的-vga std)。- 新增
devm_register_framebuffer()。 struct fb_info的screen_buffer字段是 5.x 之后新增的(替代screen_base/screen_size的旧组合)。fb_ops中的fb_read/fb_write/fb_mmap已由核心层提供通用实现。
QEMU 验证:
bash
qemu-system-x86_64 ... -vga std -display none -serial mon:stdio
# guest 中
ls /dev/fb*
cat /sys/class/graphics/fb0/virtual_size
fbset -i
cat /dev/urandom > /dev/fb0 # 屏幕花屏(需要能看到显示)
注意 :现代 QEMU 上 virtio-gpu + DRM 是首选;fbdev 只是兼容层。
12.3.5 终端设备驱动
cpp
/* include/linux/tty_driver.h */
struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner,
unsigned long flags);
#define tty_alloc_driver(lines, flags) \
__tty_alloc_driver(lines, THIS_MODULE, flags)
int tty_register_driver(struct tty_driver *driver);
void tty_unregister_driver(struct tty_driver *driver);
void tty_driver_kref_put(struct tty_driver *driver);
关键结构:
| 结构 | 作用 |
|---|---|
struct tty_driver |
驱动级:名称、主次设备号、tty_operations |
struct tty_operations |
open/close/write/ioctl/set_termios 等 |
struct tty_port |
端口级:缓冲、锁、tty_port_* 辅助函数 |
struct tty_struct |
每个打开的 tty 实例 |
7.2.5 推荐模式 :使用 tty_port 辅助函数(tty_port_init()、tty_port_register_device()、tty_port_open()、tty_port_close()),避免手写锁和缓冲管理。
QEMU 验证:
bash
# 串口是天然的 tty 设备
cat /proc/tty/drivers
ls -l /dev/ttyS0
stty -F /dev/ttyS0 -a
echo hello > /dev/ttyS0
注意 :真实驱动开发中,先确认是否已有子系统能承载你的设备 (如串口用 serial8250,USB 串口用 usb-serial),避免重复造轮子。
12.3.6 misc 设备驱动
misc 设备是"共享主设备号 10 的小型字符设备",适合只有一个设备实例的简单驱动。
cpp
/* include/linux/miscdevice.h */
struct miscdevice {
int minor; /* MISC_DYNAMIC_MINOR 表示动态 */
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename; /* 7.x 新增:可自定义节点名 */
umode_t mode; /* 7.x 新增:节点权限 */
};
int misc_register(struct miscdevice *misc);
void misc_deregister(struct miscdevice *misc);
#define module_misc_device(__misc_device) \
module_driver(__misc_device, misc_register, misc_deregister)
完整示例:
cpp
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
static ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
const char msg[] = "hello from misc\n";
return simple_read_from_buffer(buf, count, ppos, msg, sizeof(msg) - 1);
}
static ssize_t my_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
char kbuf[64];
if (count > sizeof(kbuf))
count = sizeof(kbuf);
if (copy_from_user(kbuf, buf, count))
return -EFAULT;
pr_info("misc got: %.*s\n", (int)count, kbuf);
return count;
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.read = my_read,
.write = my_write,
.llseek = no_llseek,
};
static struct miscdevice my_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mymisc",
.fops = &my_fops,
.mode = 0666, /* 7.x:直接指定权限,不需要 udev 规则 */
};
module_misc_device(my_misc);
MODULE_LICENSE("GPL");
7.2.5 变化:
- 新增
nodename字段:可以让/dev节点名与name不同。 - 新增
mode字段:直接指定节点权限,避免写 udev 规则。 MISC_DYNAMIC_MINOR仍是动态次设备号的推荐值。
QEMU 验证:
bash
insmod mymisc.ko
ls -l /dev/mymisc
cat /dev/mymisc
echo test > /dev/mymisc
dmesg | tail -3
12.3.7 驱动核心层
核心层提供的通用能力:
| 能力 | 说明 |
|---|---|
| 文件操作 | 核心层实现 file_operations,例化驱动只实现回调 |
| 设备节点 | 核心层调用 device_create()/cdev_device_add() |
| 并发控制 | 核心层提供锁、引用计数、open/release 计数 |
| 电源管理 | 核心层统一 runtime PM/system PM 转发 |
| sysfs | 核心层暴露标准属性 |
| 热插拔 | 核心层处理 uevent |
给新人的建议 :先找核心层,再写例化驱动 。例如写一个按键驱动,不要从 cdev 开始,而应从 input 子系统开始。
12.4 主机驱动与外设驱动分离的设计思想
12.4.1 主机驱动与外设驱动分离
这是 Linux 驱动架构中最重要的设计模式之一:
主机驱动(host/adapter/controller) ------ 操作控制器硬件,产生总线时序
↑
子系统核心层(i2c-core / spi.c / usb-core)
↑
外设驱动(client/device driver) ------ 操作具体外设,调用核心层 API
优点:
- 同一个外设驱动可以跑在不同 SoC 的控制器上(如 AT24 EEPROM 驱动在 Tegra I²C 和 QEMU virtio-i2c 上都能工作)。
- 控制器驱动只写一次,支持任意外设。
- 匹配、实例化、电源管理都由核心层统一处理。
典型对应关系:
| 子系统 | 主机驱动 | 外设驱动 | 核心层 |
|---|---|---|---|
| I²C | struct i2c_adapter + i2c_algorithm |
struct i2c_driver + struct i2c_client |
drivers/i2c/i2c-core*.c |
| SPI | struct spi_controller |
struct spi_driver + struct spi_device |
drivers/spi/spi.c |
| USB | struct usb_hcd |
struct usb_driver + struct usb_interface |
drivers/usb/core/ |
12.4.2 Linux SPI 主机和设备驱动
注意 :7.2.5 中 struct spi_master 已改名为 struct spi_controller (include/linux/spi/spi.h),旧名字通过宏保留了一段时间但已不再是推荐写法。
主机(控制器)驱动骨架:
cpp
#include <linux/spi/spi.h>
static int my_spi_probe(struct platform_device *pdev)
{
struct spi_controller *ctlr;
int ret;
ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(struct my_spi));
if (!ctlr)
return -ENOMEM;
ctlr->bus_num = -1; /* 动态分配 */
ctlr->num_chipselect = 4;
ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
ctlr->transfer_one = my_spi_transfer_one; /* 现代核心 API */
ret = devm_spi_register_controller(&pdev->dev, ctlr);
if (ret)
return ret;
platform_set_drvdata(pdev, ctlr);
return 0;
}
核心 API(7.2.5):
| API | 作用 |
|---|---|
spi_alloc_host(dev, size) / devm_spi_alloc_host() |
分配 controller |
spi_register_controller() / devm_spi_register_controller() |
注册 |
spi_unregister_controller() |
注销 |
spi_sync() / spi_async() |
外设侧同步/异步传输 |
spi_new_device() / spi_add_device() |
创建 spi_device |
spi_transfer_one_message / transfer_one |
控制器侧实现 |
外设驱动骨架:
cpp
static int my_spi_dev_probe(struct spi_device *spi)
{
u8 buf[2];
struct spi_transfer xfer = {
.tx_buf = buf,
.len = sizeof(buf),
};
struct spi_message msg;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
buf[0] = 0x9f; /* 读 ID 命令 */
return spi_sync(spi, &msg);
}
static const struct of_device_id my_spi_of_match[] = {
{ .compatible = "vendor,my-spi-dev" },
{ }
};
MODULE_DEVICE_TABLE(of, my_spi_of_match);
static struct spi_driver my_spi_driver = {
.probe = my_spi_dev_probe,
.driver = {
.name = "my-spi-dev",
.of_match_table = my_spi_of_match,
},
};
module_spi_driver(my_spi_driver);
QEMU 验证:
bash
# 内核配置
CONFIG_SPI=y
CONFIG_SPI_VIRTIO=m
CONFIG_SPI_LOOPBACK_TEST=m
CONFIG_SPI_SPIDEV=m
# QEMU 启动(若支持 virtio-spi)
qemu-system-aarch64 -machine virt -device virtio-spi-device ...
# guest 中
ls /sys/bus/spi/devices/
ls /dev/spidev*
spi-loopback-test # 内核自带测试
如果 QEMU 版本没有 virtio-spi,可用 GPIO bit-bang 控制器 (CONFIG_SPI_GPIO + gpio-sim)作为软件 SPI 主机。
12.5 常见错误
| 错误 | 现象 | 修正 |
|---|---|---|
class_create() 仍传两个参数 |
编译错误 | 7.2.5 只传 name |
.remove 返回 int |
编译错误/类型警告 | 改成 void |
忘记 platform_set_drvdata() |
后续取不到私有数据 | probe 中设置 |
platform_get_irq() 返回负值未检查 |
用负值注册中断 | 检查 irq < 0 |
devm_input_allocate_device() 后手动 input_free_device() |
双重释放 | devm 版本不要手动释放 |
忘记 input_sync() |
用户空间读不到事件 | 每次上报后同步 |
RTC 未设 range_min/max |
hwclock 失败 |
显式设置范围 |
用 spi_master 而非 spi_controller |
已废弃/编译警告 | 改用新名字 |
| 重复实现已有子系统功能 | 代码冗长、bug 多 | 先用子系统 |
misc_register 失败未处理 |
空设备节点 | 检查返回值 |
12.6 本章实验清单
| 实验 | 目的 | 关键观察 |
|---|---|---|
| 最小 platform 驱动 | 理解总线/设备/驱动 | dmesg 中 probe/remove |
| 自注册 platform 设备 | 无设备树也能实验 | platform_device_register_full() |
| globalfifo 平台化 | 理解资源管理 | /sys/bus/platform/devices/ |
| misc 设备 | 学习最简字符设备 | /dev/mymisc 权限为 0666 |
| 输入设备驱动 | 学习核心层 | evtest 收到按键 |
| RTC 驱动 | 学习 RTC 框架 | hwclock -r 正常 |
| SPI 外设驱动 | 学习主机/外设分离 | spidev 可收发 |
12.7 总结
- 设备模型 = 总线 + 设备 + 驱动 + 类,匹配由
bus->match()完成。 - 7.2.5 中
class_create()只有一个参数,platform_driver.remove返回void。 devm_*资源管理接口是 7.2.5 的默认写法,能自动处理错误路径与卸载。- 分层思想让例化驱动只关心硬件差异,核心层统一处理用户接口。
- 主机驱动/外设驱动分离让外设驱动跨平台复用;SPI 的类型名已改为
spi_controller。 - 写驱动前先找子系统:输入、RTC、fbdev/DRM、TTY、misc、I²C、SPI、网络、块设备都有成熟框架。