<think>好的,用户之前问过Linux字符设备驱动模板的实现,现在他想了解基于platform平台驱动编写,并且要增加设备树信息读写和中断处理。首先,我需要确定用户的需求。他可能正在开发一个需要与硬件交互的驱动,特别是使用设备树来管理硬件资源,同时需要处理中断。这可能涉及到嵌入式Linux开发,比如在树莓派或类似开发板上工作。
用户之前已经了解了基本的字符设备驱动结构,现在需要更高级的内容。Platform驱动和设备树是Linux内核中管理硬件资源的重要机制,尤其是当硬件不是即插即用时。设备树允许将硬件描述从内核代码中分离出来,提高可移植性。中断处理则是驱动中处理异步事件的必要部分。
接下来,我需要考虑如何组织回答。首先应该介绍platform驱动的基本结构,包括platform_driver和platform_device的注册,或者使用设备树替代platform_device。然后,详细说明如何在驱动中读取设备树信息,比如使用of_函数族获取资源。中断处理部分需要包括申请中断号、注册中断处理函数,以及处理中断的注意事项,比如顶半部和底半部的区分。
用户可能还关心如何将设备树信息与驱动绑定,比如在设备树中添加节点,以及驱动如何匹配这些节点。需要注意设备树节点的compatible属性与驱动中的匹配表是否一致。另外,中断部分需要说明设备树中如何指定中断号、触发方式,以及驱动中如何获取这些信息。
可能用户在实际操作中会遇到问题,比如设备树信息未正确解析,或者中断无法触发,因此需要强调调试方法,比如使用of_get_property是否正确,检查中断申请是否成功,以及中断处理函数是否被正确调用。
另外,需要提醒用户关于并发和资源管理的问题,比如在中断处理中使用自旋锁而不是可能导致休眠的函数。同时,模块卸载时要正确释放资源,比如释放中断、注销平台驱动等。
最后,提供一个完整的代码示例会帮助用户更好地理解,但需要注意代码的结构和关键步骤的注释,确保示例的正确性。同时,总结关键点和注意事项,帮助用户避免常见错误。</think>$$ 基于platform框架的驱动开发模板(含设备树和中断处理) $$
```c
// 设备树节点示例(.dts文件)
/*
my_device {
compatible = "my_vendor,my_device";
reg = <0x12345000 0x100>;
interrupt-parent = <&gpio>;
interrupts = <5 IRQ_TYPE_EDGE_RISING>;
my-special-param = <0x55>;
};
*/
// 驱动代码核心结构
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/interrupt.h>
/* 私有数据结构体 */
struct my_priv {
void __iomem *reg_base;
int irq_num;
u32 special_param;
};
/* 中断处理函数(顶半部) */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
struct my_priv *priv = dev_id;
// 1.快速处理关键操作
// 2.清除中断标志(根据硬件特性)
// 3.触发底半部处理(如tasklet/workqueue)
return IRQ_HANDLED;
}
/* 设备树信息解析 */
static int parse_dt(struct platform_device *pdev, struct my_priv *priv)
{
struct device_node *np = pdev->dev.of_node;
// 获取寄存器地址
priv->reg_base = of_iomap(np, 0);
if (!priv->reg_base) {
dev_err(&pdev->dev, "Failed to iomap\n");
return -ENOMEM;
}
// 获取中断号
priv->irq_num = platform_get_irq(pdev, 0);
if (priv->irq_num < 0) {
dev_err(&pdev->dev, "Failed to get IRQ\n");
return priv->irq_num;
}
// 读取自定义参数
if (of_property_read_u32(np, "my-special-param", &priv->special_param)) {
dev_warn(&pdev->dev, "Using default parameter\n");
priv->special_param = 0x00;
}
return 0;
}
/* Platform驱动主体 */
static int my_probe(struct platform_device *pdev)
{
struct my_priv *priv;
int ret;
// 1.分配私有数据结构
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
// 2.解析设备树
if ((ret = parse_dt(pdev, priv)) != 0)
return ret;
// 3.注册中断处理
ret = devm_request_irq(&pdev->dev, priv->irq_num, my_irq_handler,
IRQF_TRIGGER_RISING, "my_irq", priv);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ %d\n", priv->irq_num);
return ret;
}
// 4.初始化硬件
// iowrite32(0x01, priv->reg_base + REG_CTRL);
// 5.注册字符设备(可选)
// cdev_add(...);
platform_set_drvdata(pdev, priv);
return 0;
}
static int my_remove(struct platform_device *pdev)
{
struct my_priv *priv = platform_get_drvdata(pdev);
// 释放资源
// iounmap(priv->reg_base);
// free_irq(...);
return 0;
}
/* 设备树匹配表 */
static const struct of_device_id my_of_match[] = {
{ .compatible = "my_vendor,my_device" },
{}
};
MODULE_DEVICE_TABLE(of, my_of_match);
/* Platform驱动结构体 */
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_driver",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_driver);
```
$$ 关键实现要素分析 $$
1. **设备树集成**
- 通过$of_iomap()$获取寄存器物理地址映射
- 使用$platform_get_irq()$获取设备树中定义的中断号
- 通过$of_property_read_xxx()$系列函数读取自定义参数
2. **中断处理机制**
- 申请中断:`devm_request_irq()`
- 中断处理函数需返回$IRQ_HANDLED$或$IRQ_NONE$
- 顶半部要求快速执行,复杂操作应使用:
- Tasklet(原子上下文)
- Workqueue(可休眠)
3. **资源管理**
- 使用$devm_$系列API(自动释放资源):
- $devm_kzalloc()$
- $devm_request_irq()$
- $devm_ioremap()$
$$ 开发流程要点 $$
1. **设备树配置**
```dts
// 修改设备树文件(如my_device.dtsi)
my_device {
compatible = "my_vendor,my_device";
reg = <0x12345000 0x100>; // 寄存器物理地址及长度
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>; // 中断号及触发方式
my-custom-param = <0xabcd>; // 自定义参数
};
```
2. **驱动编译配置**
```makefile
obj-$(CONFIG_MY_DRIVER) += my_driver.o
```
3. **调试命令**
```bash
# 查看已加载驱动
lsmod | grep my_driver
# 查看设备树节点信息
dtc -I fs /proc/device-tree
# 检查中断注册情况
cat /proc/interrupts | grep my_irq
```
$$ 注意事项 $$
1. **并发与同步**
- 中断上下文中**不能**使用可能休眠的函数(如$mutex_lock$)
- 共享数据保护建议使用$spin_lock_irqsave()$
2. **错误处理**
- 检查所有可能失败的操作:
```c
if (!priv->reg_base) {
dev_err(&pdev->dev, "iomap failed at %pa\n", &res->start);
return -ENOMEM;
}
```
3. **兼容性验证**
- 确保设备树节点的compatible字符串与驱动中的定义完全匹配
4. **中断调试**
- 使用$cat /proc/interrupts$验证中断触发计数
- 通过$devm_request_irq()$的返回值判断中断申请是否成功
$$ 扩展功能实现 $$
1. **IOCTL控制接口**
```c
long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct my_priv *priv = file->private_data;
switch (cmd) {
case GET_SPECIAL_PARAM:
return put_user(priv->special_param, (int __user *)arg);
case SET_SPECIAL_PARAM:
return get_user(priv->special_param, (int __user *)arg);
default:
return -ENOTTY;
}
}
```
2. **Proc文件系统接口**
```c
static int my_proc_show(struct seq_file *m, void *v)
{
struct my_priv *priv = m->private;
seq_printf(m, "Current status:\n");
seq_printf(m, "Special param: 0x%x\n", priv->special_param);
return 0;
}
// 在probe函数中创建
proc_create_single_data("my_device", 0, NULL, my_proc_show, priv);
```
该模板完整展示了结合设备树、中断处理和平台驱动框架的实现方式,适用于需要精确硬件控制的设备(如GPIO控制器、自定义外设等)。实际开发中需根据具体硬件手册补充寄存器操作逻辑。