嵌入式学习——杂项设备、Platform总线和设备树源文件

一、杂项设备

1、设备驱动三大分类

  • 字符设备
  • 块设备
  • 网络设备

普通字符设备的注册流程,太过繁琐

  • 自己分配或申请主设备号,确保设备在系统中的唯一标识
  • 初始化 cdev 结构体,并与 file_operations 绑定
  • 将 cdev 结构体注册到内核
  • 注册设备类(class),用于在 /sys/class 下创建对应的目录
  • 手动创建 /dev/ 下的设备节点,方便用户空间程序访问

2、杂项设备

是字符设备的简化版本,属于字符设备的子集。

所有杂项设备都属于字符设备,但字符设备不一定是杂项设备。

  • 主设备号固定为10
  • 次设备号可以指定,也可用 MISC_DYNAMIC_MINOR 让内核自
  • 动分配
  • 设备节点由内核自动在 /dev 下生成,无需手动创建
  • 注册接口是 misc_register(),代码量小,流程简单
  • 适用于简单设备:LED、按键、看门狗等
  • 不适用于复杂设备:高速、大数据量、需要缓存的场景
  • 识别方法:ls -l 查看主设备号是否为10,或查看 /proc/misc

3、杂项设备示例(LED举例)

一、杂项设备 vs 普通字符设备
  • 主设备号:普通字符设备需要动态或静态分配,杂项设备固定为10
  • 次设备号:普通字符设备自己管理,杂项设备可自动分配或指定
  • /dev/节点:普通字符设备需要手动或通过class创建,杂项设备自动创建
  • 代码量:普通字符设备较多,杂项设备简洁
  • 适合设备:普通字符设备适合复杂设备(如串口、USB),杂项设备适合简单设备(如LED、按键、蜂鸣器)
二、普通字符设备驱动代码框架
复制代码
/* 定义文件操作函数集 */
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = demo_read,
    .write = demo_write,
};

/* 定义cdev和设备号 */
static struct cdev cdev;
static dev_t dev_num;
static struct class *cls;

/* 模块初始化 */
static int __init demo_init(void)
{
    /* 动态分配设备号 */
    alloc_chrdev_region(&dev_num, 0, 1, "demo");
    
    /* 初始化cdev并绑定fops */
    cdev_init(&cdev, &fops);
    cdev_add(&cdev, dev_num, 1);
    
    /* 创建设备类和设备节点 */
    cls = class_create(THIS_MODULE, "demo_class");
    device_create(cls, NULL, dev_num, NULL, "demo");
    return 0;
}

/* 模块退出 */
static void __exit demo_exit(void)
{
    /* 注销设备号和cdev */
    unregister_chrdev_region(dev_num, 1);
    cdev_del(&cdev);
    
    /* 销毁设备和类 */
    device_destroy(cls, dev_num);
    class_destroy(cls);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
三、杂项设备驱动核心代码
复制代码
/* 定义miscdevice结构体 */
static struct miscdevice misc = {
    .minor = MISC_DYNAMIC_MINOR,  // 自动分配次设备号
    .name = "misc_led",            // 设备名 /dev/misc_led
    .fops = &fops,                 // 文件操作函数集
};

/* 注册杂项设备 */
misc_register(&misc);

/* 注销杂项设备 */
misc_deregister(&misc);
四、完整的杂项设备驱动模板
复制代码
#include <linux/miscdevice.h>

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = led_read,
    .write = led_write,
};

static struct miscdevice misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "misc_led",
    .fops = &fops,
};

static int __init led_init(void)
{
    return misc_register(&misc);
}

static void __exit led_exit(void)
{
    misc_deregister(&misc);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
五、在Kconfig中添加
复制代码
config MISC_LED
    tristate "This is misc led driver"
    default y
六、模块加载命令
  • insmod misc_led.koc 加载模块
  • lsmod 查看已加载模块
  • rmmod misc_led 卸载模块

二、Platform

1、什么是Platform总线

  • Linux内核中的一种虚拟总线,用于管理不需要PCI、USB、I2C等传统总线的设备
  • 设备直接集成在SoC内部或固定在特定内存地址上,如GPIO控制器、UART、I2C控制器、看门狗、RTC、LED、按键
  • 将硬件资源信息与驱动逻辑分离,传统方式需要在驱动中写死硬件地址,更换板卡必须修改驱动源码
  • Platform总线支持一个驱动管理多个相同类型的设备

2、两个核心结构体

  • platform_device:描述硬件资源(寄存器地址、中断号、GPIO)

  • platform_driver:实现硬件操作逻辑(probe、remove等)

  • 两者通过name字段进行匹配,匹配成功自动调用probe

    /* platform_device核心结构 */
    static struct resource led_res[] = {
    [0] = {
    .start = 0x12345678,
    .end = 0x1234567b,
    .flags = IORESOURCE_MEM,
    },
    };

    static struct platform_device led_device = {
    .name = "my_led",
    .id = 0,
    .num_resources = ARRAY_SIZE(led_res),
    .resource = led_res,
    };

    /* platform_driver核心结构 */
    static struct platform_driver led_driver = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
    .name = "my_led",
    },
    };

3、probe函数的核心作用

  • 获取硬件资源

  • 初始化硬件

  • 注册杂项设备或字符设备,生成/dev节点

    static int led_probe(struct platform_device pdev)
    {
    /
    获取硬件资源 */
    struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

    复制代码
      /* 注册杂项设备 */
      misc_register(&misc);
      return 0;

    }

4、Platform总线与杂项设备的关系

  • Platform总线是设备和驱动的匹配机制
  • 杂项设备是字符设备的简化类型
  • 两者配合使用:Platform驱动的probe函数中注册杂项设备

5、注意事项

  • device.c和driver.c是分开的
  • 添加完device和driver后,记得修改Kconfig和Makefile
  • 然后执行make modules编译
  • 进入内核加载这两个模块
  • 在应用层写一个ledapp测试即可

三、设备树

1、设备树的作用

上面我们编写的device.c是过时的操作,设备树源文件(DTS)是现代Linux内核中用来替代devices.c的方式

2、两种方式的对比

devices.c方式:硬件信息通过C代码静态定义struct resource和struct platform_device,调用platform_device_register提交给内核

设备树方式:硬件信息写在.dts文本文件中,内核启动时解析,动态创建出完全相同的struct platform_device对象

3、具体操作

把devices.c的信息都写到.dts中,然后把driver.c改写一下

4、设备树编写步骤

从内核中找到默认的设备树源文件进行修改,最好把默认的dts拷贝一份,在中间添加一个我们写的led设备树描述

修改设备树的makefile,让dts能够被编译进去

内核顶层目录输入make xxx.dtb编译设备树

把编译出来的设备树拷到tftp中启动

5、驱动中获取设备树信息

需要用到struct device_node *pnode

of_find_node_by_path():找到节点对应的设备树描述信息

of_property_read_string():读取字符串的值

of_property_read_u32_array():读取数组的值

6、测试

写好驱动后,在根文件系统中加载模块,使用应用层写好的ledapp测试

7、遇到的问题及解决办法

加载驱动模块时报错加载不进去:设备树源文件的设备节点不要写成别人的子节点,写到了别人的大括号里面成了子节点,所以无法加载模块

make dtb要到内核顶层目录执行

相关推荐
wuxinyan1232 小时前
大模型学习之路03:提示工程从入门到精通(第三篇)
人工智能·python·学习
十安_数学好题速析3 小时前
【多选】曲线方程:四步避坑判断曲线类型
笔记·学习·高考
千寻girling4 小时前
五一劳动节快乐 [特殊字符][特殊字符][特殊字符]
java·c++·git·python·学习·github·php
波特率1152004 小时前
git指令学习
git·学习
eLIN TECE5 小时前
Golang 构建学习
开发语言·学习·golang
chase。5 小时前
【学习笔记】skrl: 模块化、灵活的强化学习库深度解析
笔记·学习
nashane6 小时前
HarmonyOS 6学习:HAR包与HSP包的选择与优化指南
学习·华为·harmonyos·harmonyos 5
MegaDataFlowers6 小时前
英语六级我还在背单词:Unit 1(Lesson 1)
学习
maaath6 小时前
【maaath】Flutter for OpenHarmony 学习答题应用实战开发
学习·flutter·华为·harmonyos