12- Linux 设备驱动的软件架构思想

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 的重要变化:

  1. .remove 返回 void (4.0 时代返回 int)。这是移植旧代码时最常见的编译错误之一。
  2. 新增 driver_managed_dma 字段:只有自己管理 DMA(如 VFIO)的驱动才设为 true。
  3. 新增 .shutdown 回调的推荐用法。
  4. 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 设备的三条路径:

  1. 设备树 (qemu-system-aarch64 -machine virt -dtb ...):在 arch/arm64/boot/dts/ 或自建 DTS 中加入节点。
  2. ACPI (x86 用 -acpitable 或 qemu 自带 SSDT)。
  3. 模块内自注册 :在 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 总结

  1. 设备模型 = 总线 + 设备 + 驱动 + 类,匹配由 bus->match() 完成。
  2. 7.2.5 中 class_create() 只有一个参数,platform_driver.remove 返回 void。
  3. devm_* 资源管理接口是 7.2.5 的默认写法,能自动处理错误路径与卸载。
  4. 分层思想让例化驱动只关心硬件差异,核心层统一处理用户接口。
  5. 主机驱动/外设驱动分离让外设驱动跨平台复用;SPI 的类型名已改为 spi_controller。
  6. 写驱动前先找子系统:输入、RTC、fbdev/DRM、TTY、misc、I²C、SPI、网络、块设备都有成熟框架。
相关推荐
Snasph3 小时前
23-从 Linux 4.0 到 7.2.5 的迁移
linux 驱动学习
Snasph11 天前
01-设备驱动概述与开发环境构建
linux 驱动学习
Snasph11 天前
05-Linux 文件系统与设备文件
linux 驱动学习
Snasph11 天前
04-Linux 内核模块
linux 驱动学习