基于RK3568的Linux驱动开发:实战——WDT

1 RK3568框图

  1. 资源网址

1.2 驱动源码走读感悟

  1. 在Linux子系统的设计中充满了C++面向对象的设计逻辑
  2. 设计子系统的步骤
    • 先根据某类物质的功能特性制定interface,也就是ops
    • 然后使用interface搭建某种功能的arch
    • arch搭建成功后,就让底层去实现这样的一个interface

2 Kernel编译配置,设备树替换

复制代码
- RK_KERNEL_DTS_NAME
- RK_KERNEL_CFG
- RK_KERNEL_CFG_FRAGMENTS

2.1 替换设备树

  1. 创建自定义dts文件: kernel/arch/arm64/boot/dts/rockchip/rk3568-atk-custom.dts
  2. 在dts Makefile中注册 : kernel/arch/arm64/boot/dts/rockchip/Makefile
    • dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3568-atk-custom.dtb
    • 验证: make ARCH=arm64 rockchip/rk3568-atk-custom.dtb
  3. 修改SDK配置源头
    • alientek_rk3568_defconfig
      • RK_KERNEL_DTS_NAME="rk3568-atk-custom"
      • 重新编译: ./build.sh alientek_rk3568_defconfig
      • ./build.sh kernel
    • 临时覆盖替代方案
      • RK_KERNEL_DTS_NAME=rk3568-atk-custom ./build.sh kernel
  4. make.sh修改RK_DTS
    • ATK_DLRK3568: RK_DTS=rk3568-atk-custom
  5. 验证结果
    • grep RK_KERNEL_DTS output/.config
    • ls -l kernel/arch/arm64/boot/dts/rockchip/rk3568-atk-custom.dtb
    • ls -l output/firmware/boot.img # FIT 内应为新 dtb

2.2 修改编译配置

  1. 创建自定义配置:kernel/arch/arm64/configs/rk3568_atk_custom_defconfig
  2. alientek_rk3568_defconfig中绑定
    • RK_KERNEL_CFG="rk3568_atk_custom_defconfig"
  3. 重新生成配置并编译
    • ./build.sh alientek_rk3568_defconfig
    • ./build.sh kernel
  4. 验证
    • grep RK_KERNEL_CFG output/.config
    • ls -l kernel/.config

2.3 fragments叠加

  1. 创建fragments文件 : kernel\arch\arm64\configs
    • rk3568_atk_fs.config
      • CONFIG_XXX=y
      • CONFIG_YYY=n
    • rk3568_atk_net.config
  2. alientek_rk3568_defconfig中绑定
    • RK_KERNEL_CFG_FRAGMENTS="rk3568_atk_fs.config rk3568_atk_net.config"
  3. 重新生成+编译
    • make ARCH=arm64 rockchip_linux_defconfig

2.4 kernel: make.sh, Kconfig, Makefile,

  1. make.sh

    • 根据board资源, 定义ARCH, defconfig, CROSS_COMPILE, dts
      • RK_ARCH=arm64
      • RK_DEFCONFIG="rockchip_linux_defconfig"
      • ADDON_ARGS="CROSS_COMPILE=.../prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-"
      • RK_DTS=rk3568-atk-evb1-ddr4-v10-linux
    • 执行make
      • 方式一:make ADDONARGSARCH=ADDON_ARGS ARCH=ADDONARGSARCH=RK_ARCH RKDTS.img−jRK_DTS.img -jRKDTS.img−jRK_JOBS
        • 不修改.config, 仅修改dts时, 快速编译
      • 方式二:make clean && make ADDONARGSARCH=ADDON_ARGS ARCH=ADDONARGSARCH=RK_ARCH $RK_DEFCONFIG && make ADDONARGSARCH=ADDON_ARGS ARCH=ADDONARGSARCH=RK_ARCH RKDTS.img−jRK_DTS.img -jRKDTS.img−jRK_JOBS
    • 关键变量
      • ARCH=, CROSS_COMPILE= :ARCH说明架构, CROSS_COMPILE说明编译器位置, 这种形式的变量会传递进Makefile
    • 其他依赖配置: alientek_rk3568_defconfig
  2. Kconfig:kbuild加载顶层Kconfig, 递归执行source全部Kconfig, 根据Kconfig的依赖规则, 补全全部所有配置项

    source "init/Kconfig"
    source "kernel/Kconfig.freezer"
    source "mm/Kconfig"
    source "net/Kconfig"
    source "drivers/Kconfig"
    source "fs/Kconfig"
    source "security/Kconfig"
    source "crypto/Kconfig"
    source "lib/Kconfig"
    source "lib/Kconfig.debug"
    source "Documentation/Kconfig"

  3. Makefile:主要步骤

    读取 Makefile
    ├─ ① 版本/默认目标声明 (L1-19)
    ├─ ② 递归 make 自举 (L37-188) ← 关键分支
    ├─ ③ 最终调用的初始化 (L191-355)
    │ ├─ 目标分类: config-build / mixed-build / single-build
    │ └─ include scripts/Kbuild.include (通用函数)
    ├─ ④ 工具链与环境变量 (L364-615)
    ├─ ⑤ 配置加载 (L671-767)
    ├─ ⑥ 编译标志 KBUILD_CFLAGS 体系 (L769-1099)
    ├─ ⑦ vmlinux 构建规则 (L1239-1307)
    ├─ ⑧ prepare 准备阶段 (L1328-1417)
    ├─ ⑨ dtbs 规则 (L1457-1496)
    ├─ ⑩ modules 规则 (L1501-1588)
    ├─ ⑪ 递归 descend 机制 (L1927-1937)
    └─ ⑫ 清理/打包/帮助/杂项 (L1590-2076)

  4. 编译配置:device\rockchip\common\configs

    • 通过环境变量控制编译选项
    • Config.in.boot, Config.in.kernel, Config.in.rootfs, Config.in.update
  5. make编译

    make ARCH=arm64 CROSS_COMPILE=... rk3568-atk-evb1-ddr4-v10-linux.img

    ├─ 第一次 make 解析(sub_make_done=0)
    │ ├─ 判定 need-sub-make(O= 源码树内构建时不触发)
    │ └─ 最终调用继续 ↓

    ├─ 目标分类:非 config/mixed/single → 正常编译分支
    ├─ include scripts/Kbuild.include
    ├─ include arch/arm64/Makefile ← 加入 %.img 规则、boot=y、KBUILD_IMAGE=Image.gz
    ├─ include include/config/auto.conf(若过期先 syncconfig)
    ├─ 组装 KBUILD_CFLAGS(约 100+ 行各路开关)

    ├─ make .img
    │ └─ arch/arm64/Makefile %.img 规则:
    │ ① make rockchip/.dtb Image.lz4 modules
    │ ② scripts/mkimg → resource.img / boot.img / zboot.img
    │ ├─ %.dtb → descend 到 arch/arm64/boot/dts 编译 dtc
    │ ├─ Image.lz4 → vmlinux → Image → Image.lz4
    │ │ └─ vmlinux 依赖链:
    │ │ prepare(archprepare→prepare0→...)
    │ │ ↓
    │ │ descend(每个 vmlinux-dirs 递归编译 built-in.a)
    │ │ ↓
    │ │ scripts/link-vmlinux.sh 链接
    │ │ ↓ (+ arch postlink)
    │ │ scripts/Makefile.lib 压缩 → Image.lz4
    │ └─ modules → modpost 生成 .ko

    └─ 最终产物: kernel/vmlinux、Image、Image.lz4、.dtb、resource.img、boot.img、zboot.img

3 PMU:Power Manger Unit

3.1 Linux通用PM框架:PM框架(运行时状态机) -> GenPD -> RK PMU硬件驱动

  1. 总体架构
text 复制代码
┌─────────────────────────────────────────────────────────────┐
│ ① drivers/base/power/runtime.c(Linux 标准运行时PM)         │
│    per-device 状态机: RPM_ACTIVE/SUSPENDING/SUSPENDED/RESUMING│
│    引用计数 usage_count / 自动挂起 autosuspend timer         │
└──────────────┬──────────────────────────────────────────────┘
               │ pm_genpd_runtime_suspend / _resume (genpd内嵌)
┌──────────────▼──────────────────────────────────────────────┐
│ ② generic power domain(drivers/base/power/domain.c, 内核通用)│
│    genpd: power_off/power_on 回调 + 子域 + 状态跟踪          │
└──────────────┬──────────────────────────────────────────────┘
               │ rockchip_pd_power_off / rockchip_pd_power_on
┌──────────────▼──────────────────────────────────────────────┐
│ ③ drivers/soc/rockchip/pm_domains.c(Rockchip PMU 硬件驱动) │
│    rockchip_pd_power() → 寄存器序列控制 (PMU_PWR/STATUS/REQ) │
│    + QoS 保存恢复 + 时钟门控                                 │
└─────────────────────────────────────────────────────────────┘

3.2.1 runtime.c:kernel\drivers\base\power\runtime.c

  1. 核心状态机rpm_idle()rmp_suspendrpm_resume
text 复制代码
        rpm_suspend()                      rpm_resume()
RPM_ACTIVE ──────────────► RPM_SUSPENDING ──► RPM_SUSPENDED ──┐
    ▲                        │ 失败             ▲              │
    │                        ▼                 │              │
    └───────────────── rpm_resume() ───────────┴──── rpm_resume()
                          RPM_RESUMING
  1. rpm_suspend()
    • 条件检查:rpm_check_suspend_allowed:usage_count0、disable_depth0、无 child 占用
    • 自动挂起延迟:RPM_AUTO 且有 autosuspend_delay → 启动 hrtimer 延迟挂起
    • 并发处理:已在挂起中:RPM_ASYNC/NOWAIT 直接 -EINPROGRESS,否则等待队列睡眠
    • 异步化:RPM_ASYNC → 设 request 标志,queue_work(pm_wq, &dev->power.work)
    • 同步挂起:状态→RPM_SUSPENDING,调用 ->runtime_suspend 回调
    • 成功:状态→RPM_SUSPENDED,父设备 child_count--,递归尝试挂起父设备
    • 失败:回滚 RPM_ACTIVE,-EAGAIN/-EBUSY 可重试
  2. rpm_resume()
    • 前置检查:runtime_error/disable_depth 判断
    • 父设备先激活:pm_runtime_get_noresume(parent) → 递归 rpm_resume(parent)------父子设备层次唤醒
    • 同步恢复:状态→RPM_RESUMING,调用 ->runtime_resume 回调
    • 成功:状态→RPM_ACTIVE,pm_runtime_mark_last_busy,父 child_count++
    • 完成后:rpm_idle(dev, RPM_ASYNC) 尝试再次自动挂起

3.2.2 GenPM:generic_pm_domain

  1. 设备驱动调用 pm_runtime_get_sync() / pm_runtime_put_autosuspend()
c 复制代码
pm_runtime_get_sync(dev)
  └→ rpm_resume(dev)
       └→ genpd 的 runtime_resume 回调 (pm_genpd_runtime_resume)
            └→ 检查 genpd 状态,若 off → 调用 genpd->power_on()  ← Rockchip 注册的回调
                 └→ rockchip_pd_power_on()
  1. pm_generic_xxx:kernel\drivers\base\power\generic_ops.c
c 复制代码
EXPORT_SYMBOL_GPL(pm_generic_runtime_suspend);
EXPORT_SYMBOL_GPL(pm_generic_runtime_resume);
EXPORT_SYMBOL_GPL(pm_generic_suspend_noirq);
EXPORT_SYMBOL_GPL(pm_generic_suspend_late);
EXPORT_SYMBOL_GPL(pm_generic_suspend);
EXPORT_SYMBOL_GPL(pm_generic_freeze_noirq);
...
  1. 在bus中给struct dev_pm_ops *pm赋值(pm_generic_xxx),在pm_generic_xxx()接口中,时机调用的是device的struct dev_pm_ops *pm

3.2 PMU简介

  1. RK3568-TRM-Part1 PMU
    • 为了满足低功耗需求,RK3568 设计了一个电源管理单元(PMU)用于控制其电源资源。RK3568 PMU 专门负责管理整个芯片的电源
    • 特性
      • 支持多电压域:VD_CORE、VD_LOGIC、VD_PMU、VD_GPU、VD_NPU
      • 两种操作模式:正常模式低功耗模式
      • 片内门控电源域名(GenPD,power-controller:SoC内部PMU寄存器做开关,电压由外部PMIC持续提供,只做内部通断
    • 设备电源管理
      • 设备驱动的 Runtime PM(运行时电源管理)触发
      • 谁使用 IP,谁的驱动去申请电源域;没有设备使用就保持硬件断电,实现省电
      • pm_runtime_put():控制PMU驱动去操作相应寄存器,完成电源域下电
      • pm_runtime_get():控制PMU驱动去操作相应寄存器,完成电源域上电
  2. 电源域框图
  3. rk3568.dtsi PMU节点
text 复制代码
pmu: power-management@fdd90000 {
    compatible = "rockchip,rk3568-pmu", "syscon", "simple-mfd";
    reg = <0x0 0xfdd90000 0x0 0x1000>;

    power: power-controller {
        compatible = "rockchip,rk3568-power-controller";
        #power-domain-cells = <1>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "okay";

        /*管理NPU供电*/
        pd_npu@RK3568_PD_NPU {
            reg = <RK3568_PD_NPU>;
            clocks = <&cru ACLK_NPU_PRE>,
                    <&cru HCLK_NPU_PRE>,
                    <&cru PCLK_NPU_PRE>;
            /*NPU访问DDR的带宽QoS, NPU运行时预留DDR带宽*/
            pm_qos = <&qos_npu>;
        };
        
        /*管理Mali‑G52 GPU 3D 显卡的供电*/
        pd_gpu@RK3568_PD_GPU {
            reg = <RK3568_PD_GPU>;
            clocks = <&cru ACLK_GPU_PRE>,
                    <&cru PCLK_GPU_PRE>;
            /*GPU 读写 DDR 带宽保障*/
            pm_qos = <&qos_gpu>;
        };
        
        /*管理VI模块供电:ISP 图像信号处理器、MIPI CSI 接收、VICAP 摄像头采集单元*/
        pd_vi@RK3568_PD_VI {
            reg = <RK3568_PD_VI>;
            clocks = <&cru HCLK_VI>,
                    <&cru PCLK_VI>;
            pm_qos = <&qos_isp>,
                    <&qos_vicap0>,
                    <&qos_vicap1>;
        };

        /*管理VO模块供电:VOP2(两个 VOP 控制器)、MIPI DSI0/DSI1、HDMI、HDCP 加密模块*/
        pd_vo@RK3568_PD_VO {
            reg = <RK3568_PD_VO>;
            clocks = <&cru HCLK_VO>,
                    <&cru PCLK_VO>,
                    <&cru ACLK_VOP_PRE>;
            pm_qos = <&qos_hdcp>,
                    <&qos_vop_m0>,
                    <&qos_vop_m1>;
        };

        /*RGA2D 硬件图像加速器、IEP 图像增强、JPEG 编解码硬件*/
        pd_rga@RK3568_PD_RGA {
            reg = <RK3568_PD_RGA>;
            clocks = <&cru HCLK_RGA_PRE>,
                    <&cru PCLK_RGA_PRE>;
            pm_qos = <&qos_ebc>,
                    <&qos_iep>,
                    <&qos_jpeg_dec>,
                    <&qos_jpeg_enc>,
                    <&qos_rga_rd>,
                    <&qos_rga_wr>;
        };

        /*老式 VPU 硬件*/
        pd_vpu@RK3568_PD_VPU {
            reg = <RK3568_PD_VPU>;
            clocks = <&cru HCLK_VPU_PRE>;
            pm_qos = <&qos_vpu>;
        };

        /*RKVDEC 硬件解码器:H264 H265 VP9 硬解码*/
        pd_rkvdec@RK3568_PD_RKVDEC {
            clocks = <&cru HCLK_RKVDEC_PRE>;
            reg = <RK3568_PD_RKVDEC>;
            pm_qos = <&qos_rkvdec>;
        };

        /*RKVENC 硬件编码器:H264/H265 硬编码*/
        pd_rkvenc@RK3568_PD_RKVENC {
            reg = <RK3568_PD_RKVENC>;
            clocks = <&cru HCLK_RKVENC_PRE>;
            pm_qos = <&qos_rkvenc_rd_m0>,
                    <&qos_rkvenc_rd_m1>,
                    <&qos_rkvenc_wr_m0>;
        };

        /*高速外设集合:SATA0‑2、PCIE2.0/3.0 控制器、USB3.0 (usb3_0 usb3_1)*/
        pd_pipe@RK3568_PD_PIPE {
            reg = <RK3568_PD_PIPE>;
            clocks = <&cru PCLK_PIPE>;
                    /*PCIE*/
            pm_qos = <&qos_pcie2x1>,
                    <&qos_pcie3x1>,
                    <&qos_pcie3x2>,
                    /*SATA*/
                    <&qos_sata0>,
                    <&qos_sata1>,
                    <&qos_sata2>,
                    /*USB3*/
                    <&qos_usb3_0>,
                    <&qos_usb3_1>;
        };
    };
};

3.3 RK3568 PMU初始化:源码走读

  1. power-management驱动注册
  2. power-controller驱动注册platform_driver_register
    • 源码文件:kernel\drivers\soc\rockchip\pm_domains.c
    • 驱动注册:postcore_initcall(rockchip_pm_domain_drv_register)
    • rockchip_pm_domain_probe:驱动probe初始化电源域逻辑
      • 分配+初始化 rockchip_pmu 结构
        • 获取设备of_device_id中的data:设备通过match表中.compatible="rockchip,rk3568-power-controller"与驱动匹配;.data携带了PMU寄存器信息struct rockchip_pmu_info信息
        • struct rockchip_pmu_info:包含电源域数量;不同寄存器的偏移(控制寄存器,状态寄存等)
      • 获取PMU寄存器regmap与基地址
        • syscon_node_to_regmap():把power-controller的父母节点(也就是power-management)转成struct regmap结构体。本质是把power-management的寄存器访问转成统一的struct regmap访问接口
        • of_iomap():把外设寄存器基地址转成内核虚拟空间可以访问的虚拟地址
      • 配置 CORE/GPU 电源切换时序
      • 遍历子节点创建电源域
      • 注册 provider + 挂接 panic 通知
  3. power-controller的设备节点创建并不会上电所有电源域,电源域的上电发生在引用了该电源域的硬件IP被访问时

4 WDT

  1. watchdog_core.c :创建看门狗内核任务队列。这是核心框架
    • subsys_initcall_sync(watchdog_init)
      • 内部创建watchdogd守护线程:kthread_create_worker(0, "watchdogd")
      • 守护线程不断从任务队列中接收来自不同看门狗dev的任务
      • 然后执行任务
  2. watchdog_dev.c :提供watchdog_dev_register()设备注册接口
    • 所有注册进来的看门狗dev都映射成**/dev/watchdogX**设备文件
    • 用户空间通过struct file_operations访问
    • 看门狗内核线程通过看门狗dev的struct watchdog_ops访问
    • hrtimer_init():注册定时任务,定时器到期向任务队列中发送任务
  3. dw_wdt.c :具体的看门狗硬件IP厂商实现的看门狗驱动
    • 他们都要调用watchdog_dev_register(),把自己注册到看门狗字符设备中统一管理

4.1 Linux WDT驱动框架:kernel\drivers\watchdog\

  1. watchdog 子系统整体架构(四层)
c 复制代码
┌────────────────────────────────────────────────────────────┐
│ 用户空间                                                     │
│   /dev/watchdog  (misc 设备, 主10 次130)                     │
│   userspace 程序: open → ioctl(WDIOF_...) → write('V') 喂狗  │
└───────────────┬────────────────────────────────────────────┘
┌───────────────▼────────────────────────────────────────────┐
│ watchdog_dev.c  ── 字符设备层 (misc_register + file ops)     │
│   /dev/watchdogN / watchdog_cdev_ioctl / watchdog_write      │
│   私有数据 watchdog_device; 自动喂狗定时器 (内核补喂)         │
└───────────────┬────────────────────────────────────────────┘
┌───────────────▼────────────────────────────────────────────┐
│ watchdog_core.c ── 核心注册层                                │
│   watchdog_register_device() → ida 分配ID + 重启通知链       │
│   超时校验 watchdog_init_timeout()                           │
└───────────────┬────────────────────────────────────────────┘
┌───────────────▼────────────────────────────────────────────┐
│ dw_wdt.c ── 具体硬件驱动 (platform_driver)                   │
│   probe: 时钟/reset/IO映射 → watchdog_register_device()      │
│   ops: start/stop/ping/set_timeout/restart/get_timeleft      │
└─────────────────────────────────────────────────────────────┘
  1. struct watchdog_device :定义了一个看门狗类,看门狗类定义了一套抽象接口 struct watchdog_ops *ops,看门狗框架使用这套抽象接口完成看门狗的所有功能。struct watchdog_device的创建就发生在平台WDT设备(struct platform_device)的probe过程中

    struct watchdog_device {
    int id;
    struct device *parent;
    const struct attribute_group **groups;
    const struct watchdog_info *info;
    const struct watchdog_ops *ops;
    const struct watchdog_governor *gov;
    unsigned int bootstatus;
    unsigned int timeout;
    unsigned int pretimeout;
    unsigned int min_timeout;
    unsigned int max_timeout;
    unsigned int min_hw_heartbeat_ms;
    unsigned int max_hw_heartbeat_ms;
    struct notifier_block reboot_nb;
    struct notifier_block restart_nb;
    void *driver_data;
    struct watchdog_core_data wd_data;
    unsigned long status;
    /
    Bit numbers for status flags /
    #define WDOG_ACTIVE 0 /
    Is the watchdog running/active /
    #define WDOG_NO_WAY_OUT 1 /
    Is 'nowayout' feature set ? /
    #define WDOG_STOP_ON_REBOOT 2 /
    Should be stopped on reboot /
    #define WDOG_HW_RUNNING 3 /
    True if HW watchdog running /
    #define WDOG_STOP_ON_UNREGISTER 4 /
    Should be stopped on unregister */
    struct list_head deferred;
    };

  2. struct watchdog_ops *ops:是提供给看门狗功能的服务接口,也是硬件驱动层需要实现的设备操作集合

  3. struct file_operations watchdog_fops :文件系统适配接口,这是提供给用户层访问的接口,用户通过文件系统的open, write, ioctl等接口直接访问设备。这里需要使用struct watchdog_ops *ops接口实现open, write, ioctl的逻辑

  4. 总结

    • 先根据功能特性制定interface,也就是ops
    • 然后使用interface搭建功能的arch
    • arch搭建成功后,就让底层去实现这样的一个interface

4.1.1 watchdog_dev.c:字符设备层

  1. struct file_operations watchdog_fops:文件系统操作集
c 复制代码
static const struct file_operations watchdog_fops = {
	.owner		= THIS_MODULE,
	.write		= watchdog_write,			// 最终执行struct watchdog_ops中的ping或者start,也就是喂狗,或者启动看门狗
	.unlocked_ioctl	= watchdog_ioctl,	// 先调用struct watchdog_ops中的ioctl。也就是先从硬件拿数据,然后执行配置超时时间;获取剩余超时时间;设置预超时时间等反馈给用户层
	.compat_ioctl	= compat_ptr_ioctl,
	.open		= watchdog_open, // 如果看门狗正在运行,则喂狗;没有运行,则执行struct watchdog_ops中的start启动看门狗
	.release	= watchdog_release,	// 执行struct watchdog_ops中的stop,停止看门狗
};
  1. struct miscdevice watchdog_miscdev:杂项设备
c 复制代码
static struct miscdevice watchdog_miscdev = {
	.minor		= WATCHDOG_MINOR,
	.name		= "watchdog",
	.fops		= &watchdog_fops,
};
  1. watchdog_cdev_register() :注册字符设备
    • kthread_init_work(&wd_data->work, watchdog_ping_work):创建了一个kernel worker,执行watchdog_ping_work()
    • misc_register():注册杂项设备。第一个看门狗作为杂项设备注册,看门狗0
    • cdev_init():绑定字符设备和fops
    • cdev_device_add():添加字符设备

4.1.2 watchdog_core.c:框架核心

  1. subsys_initcall_sync(watchdog_init) :注册看门狗设备
    • watchdog_dev_init():注册看门狗字符设备。/dev/watchdog0
      • class_register(&watchdog_class)
      • alloc_chrdev_region(&watchdog_devt, 0, 32, "watchdog"):申请一个dev_t
  2. watchdog_register_device() :注册一个设备,最终与字符设备绑定
    • watchdog_dev_register()watchdog_cdev_register():最终创建了watchdog字符设备

4.1.3 dw_wt.c:平台wdt硬件驱动

  1. dw_wdt_drv_probe
    • watchdog_register_device:把wdt硬件IP注册到watchdog管理系统中
  2. struct watchdog_ops:驱动最终需要实现这样的抽象接口
c 复制代码
struct watchdog_ops {
	struct module *owner;
	/* mandatory operations */
	int (*start)(struct watchdog_device *);
	/* optional operations */
	int (*stop)(struct watchdog_device *);
	int (*ping)(struct watchdog_device *);
	unsigned int (*status)(struct watchdog_device *);
	int (*set_timeout)(struct watchdog_device *, unsigned int);
	int (*set_pretimeout)(struct watchdog_device *, unsigned int);
	unsigned int (*get_timeleft)(struct watchdog_device *);
	int (*restart)(struct watchdog_device *, unsigned long, void *);
	long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
};
  1. struct watchdog_ops dw_wdt_ops
c 复制代码
static const struct watchdog_ops dw_wdt_ops = {
	.owner		= THIS_MODULE,
	/*开启看门狗硬件,启动硬件计数器,开始倒计时;超时后系统复位*/
	.start		= dw_wdt_start,
	/*关闭看门狗硬件,停止倒计时*/
	.stop		= dw_wdt_stop,
	/*喂狗*/
	.ping		= dw_wdt_ping,
	/*设置看门狗超时时间*/
	.set_timeout	= dw_wdt_set_timeout,
	/*预超时:看门狗还没到真正复位之前,先触发一次中断,给系统保留日志的机会*/
	.set_pretimeout	= dw_wdt_set_pretimeout,
	/*读取剩余倒计时*/
	.get_timeleft	= dw_wdt_get_timeleft,
	/*使用看门狗硬件执行系统重启*/
	.restart	= dw_wdt_restart,
};

4.1.4 看门狗框架总结

  1. 设备注册watchdog_cdev_registerwatchdog_register_pretimeout
    • 初始化延迟工作:watchdog_ping_work,内核侧自动喂狗的work
    • 高精度hrtimer定时器 CLOCK_MONOTONIC单调时钟,相对模式
    • misc_register:注册一个/dev/wathdog;生成一个/dev/watchdog0
    • cdev_init,cdev_add:初始化并添加一个字符设备

4.2 RK3568 WDT简介

  1. RK3568 TRM Part1 WDT简介
    • 看门狗定时器(WDT),APB从设备外设,用于防止 SoC 内部部件冲突或者程序异常导致的系统卡死
    • 看门狗计数器递减到0时,WDT会产生中断或复位信号,由复位控制器完成系统复位动作
    • 芯片包含WDT_NS(非安全看门狗)和WDT_S(安全看门狗)
    • 两种工作模式
      • 直接产生系统复位
      • 先触发预超时中断;如果中断服务程序没有处理清除该中断,等到第二次超时到来时,再触发系统复位
  2. RK3568 WDT模块框图
  3. 寄存器
    • WDOG_TIMEOUT_RANGE_REG:挡位寄存器。寄存器低4位选择0~15挡位
    • 每个挡位对应一个计数器装载值
    • 超时时间:sec = (2^(16 + N) / clk_rate)。N是挡位
      • 这里看门狗的超时时间与STM32,GD32中的看门狗有所不同
      • 它只有16个挡位,每个挡位的超时时间是固定的
      • STM32和GD32中的看门狗超时时间是可以设置任意值,也就是它有2^16次方个挡位
    • 响应模式
      • 直接RESET:到时间直接硬件复位
      • IRQ模式:两级计数器涉及,第一级TOP到点产生预超时中断,第二级TOP到点才复位

4.3 RK3568 WDT驱动

  1. rk3568.dtsi WDT:compatible = "snps,dw-wdt"
text 复制代码
wdt: watchdog@fe600000 {
	compatible = "snps,dw-wdt";
	reg = <0x0 0xfe600000 0x0 0x100>;
	clocks = <&cru TCLK_WDT_NS>, <&cru PCLK_WDT_NS>;
	clock-names = "tclk", "pclk";
	interrupts = <GIC_SPI 149 IRQ_TYPE_LEVEL_HIGH>;
	status = "okay";
};
  1. struct driver:kernel\drivers\watchdog\dw_wdt.c
c 复制代码
static const struct of_device_id dw_wdt_of_match[] = {
	{ .compatible = "snps,dw-wdt", },
	{ /* sentinel */ }
};
static struct platform_driver dw_wdt_driver = {
	.probe		= dw_wdt_drv_probe,
	.remove		= dw_wdt_drv_remove,
	.driver		= {
		.name	= "dw_wdt",
		.of_match_table = of_match_ptr(dw_wdt_of_match),
		.pm	= &dw_wdt_pm_ops,
	},
};
module_platform_driver(dw_wdt_driver);

4.4 使用硬件看门狗:超时不喂狗,自动重启;预超时处理

  1. 前置背景

    • watchdog_cdev_register():给每个注册的看门狗设备都注册了一个喂狗内核任务

    struct watchdog_ops dw_wdt_ops

    • struct file_operations watchdog_fops
      • watchdog_write:如果写'V',则看门狗处于ALLOW_RELEASE状态
      • watchdog_ioctl
      • watchdog_open:执行watchdog_start,启动看门狗
      • watchdog_release:如果看门狗没有处于Active状态,什么都不做;如果处于ALLOW_RELEASE状态,则执行watchdog_stop。如果没有激活,且看门狗硬件处于运行状态,则交给内核去喂狗;如果激活了,且没有超时,则交给内核去喂狗
    • watchdog_core.c:看门狗框架
    • watchdog_dev.c:看门狗文件系统接口服务;看门狗字符设备注册接口

4.4.1 模拟不喂狗复位

  1. 20s不喂狗,自动复位
c 复制代码
#include <stdio.h>
#include <stdlib.h>

#include <fcntl.h>
#include <unistd.h>

#include <sys/ioctl.h>
#include <linux/watchdog.h>

/*模拟停止喂狗 → 20s 后 SoC 复位*/

int main()
{
    printf("hello\r\n");

    /*首先打开/dev/watchdog, /dev/watchdog0, 然后不写任何东西*/
    /*首先打开/dev/watchdog, /dev/watchdog0, ioctl控制超时时间*/
    int fd = -1;
    int timeout = 20;
    int flags = 0;

    fd = open("/dev/watchdog", O_WRONLY);
    if (fd < 0)
    {
        perror("open");
        return -1;
    }

    /*设置超时时间*/
    if (ioctl(fd, WDIOC_SETTIMEOUT, &timeout) < 0)
        perror("SETTIMEOUT");
    
    /*正常跑, 喂狗10s, 证明系统稳定*/
    for (int i = 0; i < 10; i++) {
        write(fd, "\0", 1);          /* 喂狗 */
        printf("feed at t=%ds\n", i);
        sleep(1);
    }

    /*【模拟崩溃】不再喂狗,进程也不退出(保持 fd 打开、保持 active) */
    printf(">>> STOP FEEDING now, system will reset in ~%ds\n", timeout);
    for (;;)
        pause();

    return 0;
}
  1. 关闭开门狗,不喂狗,不复位:关闭后,交给内核线程去喂狗了
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/watchdog.h>

int main()
{
    printf("hello\r\n");

    /*首先打开/dev/watchdog, /dev/watchdog0, 然后不写任何东西*/
    /*首先打开/dev/watchdog, /dev/watchdog0, ioctl控制超时时间*/
    int fd = -1;
    int timeout = 20;
    int flags = 0;

    fd = open("/dev/watchdog", O_WRONLY);
    if (fd < 0)
    {
        perror("open");
        return -1;
    }

    /*设置超时时间*/
    if (ioctl(fd, WDIOC_SETTIMEOUT, &timeout) < 0)
        perror("SETTIMEOUT");
    
    /*正常跑, 喂狗10s, 证明系统稳定*/
    for (int i = 0; i < 10; i++) {
        write(fd, "\0", 1);          /* 喂狗 */
        printf("feed at t=%ds\n", i);
        sleep(1);
    }

    /*关闭看门狗, 超时不复位*/
    write(fd, "V", 1);
    close(fd);   /* 触发 watchdog_stop:有 reset 控制器时真正停止 */

    /*【模拟崩溃】不再喂狗,进程也不退出(保持 fd 打开、保持 active) */
    printf(">>> STOP FEEDING now, system will reset in ~%ds\n", timeout);
    for (;;)
        pause();

    return 0;
}

4.4.2 预超时 + 自定义中断动作

  1. 前置背景
    • struct watchdog_ops dw_wdt_ops
      • .set_pretimeout = dw_wdt_set_pretimeout:设置预超时模式
    • struct file_operations watchdog_fops
      • watchdog_ioctl:支持WDIOC_SETPRETIMEOUT配置预超时时间。映射到set_pretimeout中时,入参是预超时模式;超时时间通过WDIOC_SETTIMEOUT设置
      • watchdog_open:执行watchdog_start,启动看门狗
    • dw_wdt_irq():中断处理接口
  2. 启用预超时,进入中断执行
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/watchdog.h>

int main()
{
    printf("hello\r\n");

    /*首先打开/dev/watchdog, /dev/watchdog0, 然后不写任何东西*/
    /*首先打开/dev/watchdog, /dev/watchdog0, ioctl控制超时时间*/
    int fd = -1;
    int wmod = 2;
    int timeout = 20;
    int flags = 0;

    fd = open("/dev/watchdog", O_WRONLY);
    if (fd < 0)
    {
        perror("open");
        return -1;
    }

    /*设置预超时模式*/
    if (ioctl(fd, WDIOC_SETPRETIMEOUT, &wmod) < 0)
        perror("SETPRETIMEOUT");

    /*设置超时时间*/
    if (ioctl(fd, WDIOC_SETTIMEOUT, &timeout) < 0)
        perror("SETTIMEOUT");
    
    /*正常跑, 喂狗10s, 证明系统稳定*/
    for (int i = 0; i < 10; i++) {
        write(fd, "\0", 1);          /* 喂狗 */
        printf("feed at t=%ds\n", i);
        sleep(1);
    }
    
    /*【模拟崩溃】不再喂狗,进程也不退出(保持 fd 打开、保持 active) */
    printf(">>> STOP FEEDING now, system will reset in ~%ds\n", timeout);
    for (;;)
        pause();

    return 0;
}
  1. 执行结果
text 复制代码
feed at t=0s
...
>>> STOP FEEDING now, system will reset in ~22s
/*中断处理函数的输出*/
[  933.809380] watchdog0: pretimeout event

4.5 硬件看门狗接入逻辑:

  1. struct watchdog_device wdd :现在已经有了一个看门狗硬件IP(dw_wdt),将这个硬件IP融入到struct watchdog_device
  2. struct watchdog_ops dw_wdt_ops
    • 这是框架给出的接口,驱动首先需要实现这些接口
    • 这些具体的接口肯定需要访问看门狗硬件IP的寄存器,这就涉及到寄存器映射问题
    • 在linux驱动中,CPU访问的是虚拟内存地址,而寄存器所在的地址是实际的物理地址,所以需要先把物理地址映射成虚拟地址之后,才能适用内核中定义的write,read接口访问寄存器
  3. struct platform_driver dw_wdt_driver
    • 看门狗框架已经有了,具体的看门狗硬件也存在了,看门狗硬件对用的操作函数集也制定好了
    • 现在我们需要一个驱动,让这个驱动去调用看门狗的操作函数集
    • 为什么让驱动去调用,而不是设备自己去调用 ------ 这是为了解耦
    • 看门狗驱动本身驱动的是同一类的dw_dwt硬件IP,所有设备使用同一个驱动,就实现了多个同类看门狗硬件的初始化
  4. struct platform_device
    • 携带看门狗在SoC上的寄存器映射地址,中断号等
    • 在解析设备树时动态创建,并通过总线match,把driverdevice绑定
    • 最终调用drvierprobe接口完成初始化

4.6 自定义软件看门狗:驱动实战

  1. 项目规划

    • 设计一个虚拟看门狗硬件,支持struct watchdog_ops my_wdt_ops所有操作
    • 编写一个虚拟看门狗硬件驱动
    • 编写一个虚拟看门狗硬件设备树节点
    • 把驱动文件加到内核编译选项中
  2. 设计要点

    • 定时器代替硬件计数器
    • 要点学习
      • reg: 使用DDR内存来模拟寄存器
      • pinctrl: 配置复用, 上拉/下拉配置
      • gpio: 配置一个指示灯引脚, 作为喂狗/超时的指示灯
      • clk: 复用硬件看门狗的wdt, 用来学习clk子系统
      • kthread: 创建要给kthread_worker, 用来处于看门狗超时触发内核线程调用
        • 重点编写work任务
      • hrtimer: 纳秒高精度定时器
        • 重点编写定时器回调
      • of_property_read_x: 从device_node的property链表中读取属性值
    • 看门狗驱动私有数据设计
      • struct watchdog_device
      • struct clk
      • struct gpio_desc
      • struct pinctrl
      • struct hrtimer
      • struct kthread_worker, struct kthread_work
      • struct regmap, void __iomem *base
      • struct resource
  3. 类型声明

c 复制代码
struct dy_wdt {
    struct watchdog_device wdd; // 标准watchdog框架

    struct hrtimer timer;       // 使用一个定时器模式看门狗倒计时
                                // 定时器也有很多种: one time, 脉冲, 循环
    ktime_t timeout;            // 每个节拍的周期: timer的计时时间

    u32 counter;                // 这是要定时多少次
    u32 max_counter;            // 这是期望的看门狗超时时间
    u32 pretimeout_cnt;         // 预超时对应的计数值
    bool running;               // 运行状态

    /* clock子系统 */
    struct clk *pclk;

    /* pinctrl子系统 */
    struct pinctrl *pinctrl;
    struct pinctrl_state *default_state;    // "default" 引脚状态
    struct pinctrl_state *active_state;     // "active" 引脚状态

    /* gpio子系统 */
    struct gpio_desc *indicate_gpio;        // 指示gpio

    /* regmap子系统 */
    void __iomem *base;                 // ioremap的虚拟寄存器地址
    struct regmap *regmap;              // 模拟寄存器映射
    phys_addr_t mem_phys;               // 预留DDR物理地址
    resource_size_t mem_size;           // 预留大小

    /* kthread子系统 */
    struct kthread_worker *worker;      // 内核线程 worker
    struct kthread_work work;           // 超时处理工作, 在kthread中执行

    spinlock_t lock;                    // 保护计数/开关状态的并发
};
  1. 接口编写

    • 硬件接口: struct watchdog_ops
    • 驱动接口: probe, remove
    • 注册驱动: module_init, module_exit; module_platform_driver
  2. 变量声明

    • 硬件接口: struct watchdog_ops
    • 驱动示例: struct platform_driver
    • 匹配表: struct of_device_id
    • 模块声明: MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_LICENSE

4.6.1 设备树节点描述:rk3568-atk-evb1-ddr4-v10-linux.dts

text 复制代码
/ {
	dy_soft_watchdog: dy-soft-watchdog {
		compatible = "dy-wdt";
		status = "okay";

		memory-region = <&dy_wdt_reg>;
		memory-region-names = "vir_reg";
		//reg = <0x0 0xefff0000 0x0 0x100>;
		clocks = <&cru PCLK_WDT_NS>;
		clock-names = "pclk";
		pinctrl-names = "default";
		pinctrl-0 = <&dy_wdt_feed_pin>;
		indicate-gpios = <&gpio4 RK_PC4 GPIO_ACTIVE_HIGH>;
		dy,timeout-ms = <5000>;
		dy,pretimeout-ms = <1000>;
		dy,max-counter = <100>;
	};
};

&reserved_memory {
	dy_wdt_reg: dy-wdt-reg@efff0000 {
		reg = <0x0 0xefff0000 0x0 0x100>;
		no-map;
	};
};

&pinctrl {
	dy_wdt {
		dy_wdt_feed_pin: dy-wdt-feed-pin {
			rockchip,pins = <4 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};
};

4.6.2 驱动源码:dy_soft_watchdog.c

c 复制代码
/*****************************************
 * RK3568和STM32407看门狗
 *      内部递减计数器, 计数器到0没有喂狗, 触发系统复位
 *      独立时钟源, 不依赖CPU主时钟
 *      可以开启/关闭, 可配置超时时间
 *      复位后保留复位原因标志位
 * 
 * STM32F407
 *      独立看门狗IWDG, 窗口看门狗WWDG
 *      IWDG: LSI内部低速RC, 完全独立, 不受主时钟、PLL影响
 *      WDDG: APB1总线时钟
 *      看门狗超时复位, 只复位MCU内核, 外设、SRAM、Flash; 外部电路不会复位
 * 
 * RK3568
 *      只有一个硬件看门狗WDT, 挂载PMU模块, 属于PMU子模块
 *      时钟源: 
 *      WDT寄存器属于PMU寄存器组, 通过regmap访问
 *      WDT超时, 复位信号交给PMU硬件状态机处理, 不是单纯CPU复位
 * 
 ******************************************/

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_reserved_mem.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/spinlock.h>

#include <linux/clk.h>			/* clock 子系统 */
#include <linux/pinctrl/consumer.h>	/* pinctrl 子系统 */
#include <linux/gpio/consumer.h>	/* gpio 子系统 */
#include <linux/regmap.h>		/* regmap 子系统 */
#include <linux/hrtimer.h>		/* hrtimer 高精度定时器 */
#include <linux/ktime.h>
#include <linux/kthread.h>		/* kthread worker/work */
#include <linux/watchdog.h>		/* watchdog framework */

/************************************
 * 这是一个虚拟看门狗
 *      1. 依赖高精度定时器实现: 
 *      2. 可以配置一个计数值
 *          计数值的配置依赖于定时器的位数
 *      3. 当定时器到达这个值时触发复位或中断
 * 
 * Clock子系统
 * 
 * Pinctrl和GPIO子系统
 * 
 * struct regmap子系统
 * 
 * hrtime 高精度定时器
 * 
 * kthread_init_work内核线程
 * 
 * GPIO
 *      GPIO0_C0: LED
 *      GPIO4_C4: LED ------ 这个LED可以用. 高电平亮
 * 
 * 虚拟看门狗设计
 *      用户态打开 /dev/watchdogX -> 驱动start->hrtimer启动倒计时
 *      用户态喂狗ping            -> regmap写计数值"回满" + 翻转GPIO指示
 *      hrtimer到期              -> 读取regmap中的计数器值
 *          计数值未到0 -> 再次 hrtimer_start 继续倒计时
 *          计数值到0   -> kthread_queue_work调度内核线程
 *                          -> 执行"复位动作"(拉GPIO/panic/记录日志)
 * 
 * 设备树设计
 *      dy_soft_watchdog: dy-soft-watchdog {
 *          compatible = "dy-wdt";
 *          status = "okay";
 * 
 *          //时钟, 复用硬件看门狗的时钟
 *          clocks = <&cru PCLK_WDT>,
 *          clock-names = "pclk";
 * 
 *          //pinctrl
 *          pinctrl-names = "default";
 *          pinctrl-0 = <&dy_wdt_feed_pin>:
 *          
 *          //gpio: 喂狗指示 + 超时复位输出
 *          feed-gpios = <&gpio4 RK_PC4 GPIO_ACTIVE_HIGH>;
 *          reset-gpios = <&gpio4 RK_PC4 GPIO_ACTIVE_LOW>;
 *          
 *          //reg: 寄存器, 复用硬件看门狗的寄存器, 只读
 *          reg = <0x0 0xfe600000 0x0 0x100>;
 *          reg-names = "dy_wdt_regs";
 *          
 *          //自定义属性: 
 *          dy,timeout-ms = <5000>          // 5s超时
 *          dy,pretimeout-ms = <1000>;      // 提前1s进入预处理阶段
 *          dy,max-counter = <100>;         // 虚拟计数器最大值
 *      };
 * 
 *      &pinctrl {
 *          dy-wdt {
 *              dy_wdt_feed_pin: dy-wdt-feed-pin {
 *                  rockchip,pins = <4 RK_PC4 RK_FUNC_GPIO &pcfg_pull_up>,
 *              };
 *          };
 *      };
 * 
 */

#define DY_WDT_REG_CTRL		0x00	/* 控制寄存器:   bit0=使能   */
#define DY_WDT_REG_COUNTER	0x04	/* 计数器寄存器: 递减到0 => 超时 */
#define DY_WDT_REG_TIMEOUT	0x08	/* 超时配置寄存器(仅记录, 教学用) */
#define DY_WDT_REG_STATUS	0x0c	/* 状态寄存器:   bit0=超时标志
					 *                bit1=预超时标志    */

#define DY_WDT_CTRL_ENABLE	BIT(0)			/* 启动看门狗 */
#define DY_WDT_STATUS_TIMEOUT	BIT(0)			/* 已超时     */
#define DY_WDT_STATUS_PRETIMEOUT BIT(1)			/* 预超时     */

#define DY_WDT_TICK_MS		100			/* 每个虚拟节拍=100ms(可调) */

struct dy_wdt {
    struct watchdog_device wdd; // 标准watchdog框架

    struct hrtimer timer;       // 使用一个定时器模式看门狗倒计时
                                // 定时器也有很多种: one time, 脉冲, 循环
    ktime_t timeout;            // 每个节拍的周期: timer的计时时间

    u32 counter;                // 这是要定时多少次
    u32 max_counter;            // 这是期望的看门狗超时时间
    u32 pretimeout_cnt;         // 预超时对应的计数值
    bool running;               // 运行状态

    /* clock子系统 */
    struct clk *pclk;

    /* pinctrl子系统 */
    struct pinctrl *pinctrl;
    struct pinctrl_state *default_state;    // "default" 引脚状态
    struct pinctrl_state *active_state;     // "active" 引脚状态

    /* gpio子系统 */
    struct gpio_desc *indicate_gpio;        // 指示gpio

    /* regmap子系统 */
    void __iomem *base;                 // ioremap的虚拟寄存器地址
    struct regmap *regmap;              // 模拟寄存器映射
    phys_addr_t mem_phys;               // 预留DDR物理地址
    resource_size_t mem_size;           // 预留大小

    /* kthread子系统 */
    struct kthread_worker *worker;      // 内核线程 worker
    struct kthread_work work;           // 超时处理工作, 在kthread中执行

    spinlock_t lock;                    // 保护计数/开关状态的并发
};

/*************************************************************************
 *                              内存寄存器
 *************************************************************************/
static u32 dy_wdt_reg_read(struct dy_wdt *wdt, u32 reg)
{
	return readl(wdt->base + reg);		/* 原子读 IO 映射的 DDR */
}

static void dy_wdt_reg_write(struct dy_wdt *wdt, u32 reg, u32 val)
{
	writel(val, wdt->base + reg);		/* 原子写 IO 映射的 DDR */
}

/*************************************************************************
 *                              定时器回调: 比如定的超时粒度是1ms
 *************************************************************************/
static enum hrtimer_restart dy_wdt_timer_handler(struct hrtimer *timer)
{
    struct dy_wdt *wdt = container_of(timer, struct dy_wdt, timer);
    /* 因为已经到时间了 */

    u32 cnt;
    bool timeout = false;

    /** 临界区: 使用内存模拟的寄存器, 先上锁 */
    spin_lock(&wdt->lock);

    if (!wdt->running)
    {
        spin_unlock(&wdt->lock);
        return HRTIMER_NORESTART;       // 已经被stop, 不再继续
    }

    cnt = dy_wdt_reg_read(wdt, DY_WDT_REG_COUNTER); // 读取当前计数
    printk("readl counter = %u\r\n", cnt);

    regmap_read(wdt->regmap, DY_WDT_REG_COUNTER, &cnt);
    printk("regmap read counter = %u\r\n", cnt);

    if (cnt == 0)
    {
        /* 记录看门狗状态 */
        dy_wdt_reg_write(wdt, DY_WDT_REG_STATUS, DY_WDT_STATUS_TIMEOUT);
        timeout = true;
    }
    else 
    {
         /* 模拟硬件计数器递减 */
        cnt--;
        dy_wdt_reg_write(wdt, DY_WDT_REG_COUNTER, cnt);
        regmap_write(wdt->regmap, DY_WDT_REG_COUNTER, cnt); // 向寄存器里面写入计数值, 也就是存下来当前过去了多少秒

        /* 预超时处理 */
        if (wdt->pretimeout_cnt && cnt == (wdt->pretimeout_cnt))
        {
            /* 记录当前pretimeout状态 */
            u32 st = dy_wdt_reg_read(wdt, DY_WDT_REG_STATUS);
			dy_wdt_reg_write(wdt, DY_WDT_REG_STATUS,
					 st | DY_WDT_STATUS_PRETIMEOUT);
			printk("pretimeout reached (cnt=%u)\n", cnt);
        }
    }
    spin_unlock(&wdt->lock);

    if (timeout)
    {
        /* 超时未喂狗: 通知内核线程执行 */
        kthread_queue_work(wdt->worker, &wdt->work);
        return HRTIMER_NORESTART;
    }

    /* 重新启动, 继续倒计时 */
    hrtimer_forward_now(timer, wdt->timeout);
    return HRTIMER_RESTART;
}

/*************************************************************************
 *                              worker回调: 超时处理
 *************************************************************************/
static void dy_wdt_reset_work(struct kthread_work *work)
{
    struct dy_wdt *wdt = container_of(work, struct dy_wdt, work);
    dev_err(wdt->wdd.parent, "watchdog timeout! reset now...\n");

    /* 拉低引脚: 灭灯 */
    gpiod_set_value(wdt->indicate_gpio, 0);
}


/*************************************************************************
 *                              看门狗操作集
 *************************************************************************/
int dy_wdt_start(struct watchdog_device *);
int dy_wdt_stop(struct watchdog_device *);
int dy_wdt_ping(struct watchdog_device *wdd);
unsigned int dy_wdt_status(struct watchdog_device *);
int dy_wdt_set_timeout(struct watchdog_device *, unsigned int);
int dy_wdt_set_pretimeout(struct watchdog_device *, unsigned int);
unsigned int dy_wdt_get_timeleft(struct watchdog_device *);
int dy_wdt_restart(struct watchdog_device *, unsigned long, void *);
long dy_wdt_ioctl(struct watchdog_device *, unsigned int, unsigned long);

/** 首先定义这样一个虚拟看门狗操作函数集合 */
static const struct watchdog_ops dy_wdt_ops = {
    .owner = THIS_MODULE,
    .start = dy_wdt_start,
    .stop = dy_wdt_stop,
    .ping = dy_wdt_ping,
    .status = dy_wdt_status,
    .set_timeout = dy_wdt_set_timeout,
    .set_pretimeout = dy_wdt_set_pretimeout,
    .get_timeleft = dy_wdt_get_timeleft,
    .restart = dy_wdt_restart,
    .ioctl = dy_wdt_ioctl,
};


int dy_wdt_start(struct watchdog_device *wdd)
{
    /* 启动定时器 */
    struct dy_wdt *wdt = watchdog_get_drvdata(wdd);
    unsigned long flags;

    spin_lock_irqsave(&wdt->lock, flags);   // 如果flags表示需要调度, 则进行调度

    /* 往计数器中写入数据 */
    dy_wdt_reg_write(wdt, DY_WDT_REG_COUNTER, wdt->max_counter);
	dy_wdt_reg_write(wdt, DY_WDT_REG_TIMEOUT, wdd->timeout * 1000);
	dy_wdt_reg_write(wdt, DY_WDT_REG_STATUS, 0);

    /* 使能寄存器 */
    dy_wdt_reg_write(wdt, DY_WDT_REG_CTRL, DY_WDT_CTRL_ENABLE);

    wdt->running = true;
    spin_unlock_irqrestore(&wdt->lock, flags);

    /* 启动定时器 */
    hrtimer_start(&wdt->timer, wdt->timeout, HRTIMER_MODE_REL);

    /* 启动后点亮看门狗 */
    if (wdt->indicate_gpio)
    {
        gpiod_set_value(wdt->indicate_gpio, 1);
    }
    printk("soft watchdog started (timeout=%us)\n", wdd->timeout);
    return 0;
}
int dy_wdt_stop(struct watchdog_device *wdd)
{
    struct dy_wdt *wdt = watchdog_get_drvdata(wdd);
	unsigned long flags;

	spin_lock_irqsave(&wdt->lock, flags);
	wdt->running = false;			/* 回调看到 false 会停止重排 */
	dy_wdt_reg_write(wdt, DY_WDT_REG_CTRL, 0);	/* 关使能 */
	spin_unlock_irqrestore(&wdt->lock, flags);

	/*
	 * 关键: hrtimer_cancel() 会等待回调执行完毕, 期间可能睡眠
	 * (wait_for_hrtimer_range), 因此【必须在自旋锁外】调用,
	 * 否则触发 "scheduling while atomic" 内核 BUG.
	 */
	hrtimer_cancel(&wdt->timer);		/* 停止节拍 */

	if (wdt->indicate_gpio)
    {
        gpiod_set_value(wdt->indicate_gpio, 0);
    }

	printk("soft watchdog stopped\n");
    return 0;
}
int dy_wdt_ping(struct watchdog_device *wdd)
{
    struct dy_wdt *wdt = watchdog_get_drvdata(wdd);
	unsigned long flags;
	u32 st;

	spin_lock_irqsave(&wdt->lock, flags);

	/* 模拟硬件"重装载": 计数器写回最大值 */
	dy_wdt_reg_write(wdt, DY_WDT_REG_COUNTER, wdt->max_counter);

	/* 清除超时/预超时标志 */
	st = dy_wdt_reg_read(wdt, DY_WDT_REG_STATUS);
	dy_wdt_reg_write(wdt, DY_WDT_REG_STATUS,
			 st & ~(DY_WDT_STATUS_TIMEOUT | DY_WDT_STATUS_PRETIMEOUT));

	spin_unlock_irqrestore(&wdt->lock, flags);

	/* 喂狗心跳: 闪烁 */
	if (wdt->indicate_gpio) 
    {
		gpiod_set_value(wdt->indicate_gpio, (gpiod_get_value(wdt->indicate_gpio) ? 0 : 1));
	}
    return 0;
}
unsigned int dy_wdt_status(struct watchdog_device *wdd)
{
    return 0;
}
int dy_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
{
    struct dy_wdt *wdt = watchdog_get_drvdata(wdd);
	unsigned long flags;

	/* 内部换算: 计数值上限 = timeout(ms) / 每拍(ms) */
	spin_lock_irqsave(&wdt->lock, flags);
	wdt->max_counter = (timeout * 1000) / DY_WDT_TICK_MS;
	if (wdt->max_counter == 0)
    {
        wdt->max_counter = 1;
    }

	/* 运行中则立即重装载 */
	if (wdt->running)
    {
		dy_wdt_reg_write(wdt, DY_WDT_REG_COUNTER, wdt->max_counter);
		dy_wdt_reg_write(wdt, DY_WDT_REG_TIMEOUT, timeout * 1000);
	}
	spin_unlock_irqrestore(&wdt->lock, flags);

	wdd->timeout = timeout;
    return 0;
}
int dy_wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int pretimeout)
{
    struct dy_wdt *wdt = watchdog_get_drvdata(wdd);
	unsigned long flags;

	spin_lock_irqsave(&wdt->lock, flags);
	if (pretimeout)
		wdt->pretimeout_cnt = (pretimeout * 1000) / DY_WDT_TICK_MS;
	else
		wdt->pretimeout_cnt = 0;
	spin_unlock_irqrestore(&wdt->lock, flags);

	wdd->pretimeout = pretimeout;
    return 0;
}
unsigned int dy_wdt_get_timeleft(struct watchdog_device *wdd)
{
    struct dy_wdt *wdt = watchdog_get_drvdata(wdd);
	u32 cnt;

	cnt = dy_wdt_reg_read(wdt, DY_WDT_REG_COUNTER);
	return cnt * DY_WDT_TICK_MS / 1000;	/* 换算成秒 */
}
int dy_wdt_restart(struct watchdog_device *wdd, unsigned long timeout, void *arg)
{
    return 0;
}
long dy_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd, unsigned long arg)
{
    return 0;
}


static const struct watchdog_info dy_wdt_info = {
	.identity	= "DY Soft Watchdog",
	.options	= WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_PRETIMEOUT |
			  WDIOF_MAGICCLOSE,
};

/*************************************************************************
 *                              看门狗驱动
 *************************************************************************/
int dy_wdt_probe(struct platform_device *pdev)
{
    /* 首先, 我需要一个dy_wdt用来承载 */
    struct device *dev = &pdev->dev;
    struct dy_wdt *wdt = NULL;
    u32 timeout_ms = 0;

    struct regmap_config regmap_cfg = {};
    struct resource res;
    struct device_node *rmem_np;

    u32 pretimeout_ms = 0;
    int ret;

    /** 分配私有数据 */
    wdt = devm_kzalloc(dev, sizeof(struct dy_wdt), GFP_KERNEL);
    if (!wdt)
    {
        return -ENOMEM;
    }
    platform_set_drvdata(pdev, wdt);
    spin_lock_init(&wdt->lock);

    wdt->timeout = ms_to_ktime(DY_WDT_TICK_MS); // 每个虚拟节拍100ms

    /** clock */
    wdt->pclk = devm_clk_get_optional(dev, "pclk");
    if (IS_ERR(wdt->pclk))
    {
        return dev_err_probe(dev, PTR_ERR(wdt->pclk), "failed to get clk\r\n");
    }
    // 这里其实CLK已经做过了一次初始化, 但是clk是引用计数, 多次prepare_enable不会导致程序错误
    if (wdt->pclk)
    {
        ret = clk_prepare_enable(wdt->pclk);
        if (ret)
        {
            return dev_err_probe(dev, ret, "failed to enable clk\r\n");
        }
        dev_info(dev, "clk enabled, rate = %luHz\r\n", clk_get_rate(wdt->pclk));
    }
    else
    {
        dev_info(dev, "no pclk in DT, running in demo mode\r\n");
    }

    /** 寄存器: 这里的是reserved_memory, 这里应该是根据句柄获取实际所在的节点 */
    rmem_np = of_parse_phandle(dev->of_node, "memory-region", 0);
    if (rmem_np)
    {
        ret = of_address_to_resource(rmem_np, 0, &res);
        if (ret)
        {
            dev_err(dev, "failed to get reserved-memory addr: %d\n", ret);
            of_node_put(rmem_np);
            goto err_clk;
        }

        wdt->mem_phys = res.start;
        wdt->mem_size = resource_size(&res);
        of_node_put(rmem_np);

        /** 获取name, 作为演示 */
        ret = of_reserved_mem_device_init_by_name(dev, dev->of_node, "memory-region-names");
        if (ret && ret != -ENODEV)
        {
            dev_warn(dev, "reserved-mem by name 'vir_reg': %d\r\n", ret);
        }

        /** 对物理地址完成虚拟地址映射 */
        wdt->base = devm_ioremap(dev, wdt->mem_phys, wdt->mem_size);
        if (!wdt->base)
        {
            dev_err(dev, "failed to ioremap reserved DDR\r\n");
            ret = -ENOMEM;
            goto err_clk;
        }

        dev_info(dev, "using reserved DDR [mem %pa-%pa] size %pa\r\n",
            &res.start, &res.end, &wdt->mem_size);
    }
    else
    {
        struct resource *reg_res;
        reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!reg_res)
        {
            dev_err(dev, "no memory-region nor reg in DT\r\n");
            ret = -EINVAL;
            goto err_clk;
        }

        wdt->base = devm_ioremap_resource(dev, reg_res);
        if (IS_ERR(wdt->base)) {
			ret = PTR_ERR(wdt->base);
			goto err_clk;
		}
        wdt->mem_phys = reg_res->start;
		wdt->mem_size = resource_size(reg_res);
		dev_info(dev, "using reg (fallback) resource\n");
    }

    /** 通过虚拟地址, 创建regmap */
    regmap_cfg.name = "dy-wdt";
    regmap_cfg.reg_bits = 32;
    regmap_cfg.val_bits = 32;
    regmap_cfg.reg_stride = 4;
    regmap_cfg.max_register = DY_WDT_REG_STATUS;
    wdt->regmap = devm_regmap_init_mmio(dev, wdt->base, &regmap_cfg);
    if (IS_ERR(wdt->regmap))
    {
        ret = PTR_ERR(wdt->regmap);
        dev_err(dev, "failed to init regmap: %d\r\n", ret);
        goto err_clk;
    }

    /** 用 regmap 初始化模拟寄存器(进程上下文, 安全) */
	regmap_write(wdt->regmap, DY_WDT_REG_CTRL, 0);
	regmap_write(wdt->regmap, DY_WDT_REG_COUNTER, 0);
	regmap_write(wdt->regmap, DY_WDT_REG_STATUS, 0);

    /** pinctrl子系统 */
    wdt->pinctrl = devm_pinctrl_get(dev);
    if (IS_ERR(wdt->pinctrl))
    {
        ret = PTR_ERR(wdt->pinctrl);
        if (ret == -ENODEV)
        {
            wdt->pinctrl = NULL;
        }
        else
        {
            goto err_clk;
        }
    }

    if (wdt->pinctrl)
    {
        wdt->default_state = pinctrl_lookup_state(wdt->pinctrl, PINCTRL_STATE_DEFAULT);
        if (IS_ERR(wdt->default_state))
        {
            dev_warn(dev, "no default pinctrl state\r\n");
            wdt->default_state = NULL;
        }
        else
        {
            pinctrl_select_state(wdt->pinctrl, wdt->default_state);
            dev_info(dev, "pinctrl default state selected\r\n");
        }

        wdt->active_state = pinctrl_lookup_state(wdt->pinctrl, "active");
        if (IS_ERR(wdt->active_state))
        {
            dev_warn(dev, "no active pinctrl state\r\n");
            wdt->active_state = NULL;
        }
        else
        {
            pinctrl_select_state(wdt->pinctrl, wdt->active_state);
            dev_info(dev, "pinctrl active state selected\r\n");
        }
    }

    /** gpio子系统 */
    wdt->indicate_gpio = devm_gpiod_get(dev, "indicate", GPIOD_OUT_LOW);
    if (IS_ERR(wdt->indicate_gpio))
    {
        ret = PTR_ERR(wdt->indicate_gpio);
        goto err_clk;
    }

    if (wdt->indicate_gpio)
    {
        gpiod_set_value(wdt->indicate_gpio, 0);
        dev_info(dev, "indicate GPIO ready\r\n");
    }

    /** hrtimer子系统 */
    hrtimer_init(&wdt->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    wdt->timer.function = dy_wdt_timer_handler;

    /** kthread子系统 */
    wdt->worker = kthread_create_worker(0, "dy-wdt-worker");
    if (IS_ERR(wdt->worker))
    {
        ret = PTR_ERR(wdt->worker);
        dev_err(dev, "failed to create kthread worker: %d\r\n", ret);
        goto err_clk;
    }
    kthread_init_work(&wdt->work, dy_wdt_reset_work);

    /** watchdog 框架 */
    wdt->wdd.parent = dev;
    wdt->wdd.info = &dy_wdt_info;
    wdt->wdd.ops = &dy_wdt_ops;
    wdt->wdd.min_timeout = 1;
    wdt->wdd.max_timeout = 60;
    wdt->wdd.timeout = 5;
    wdt->wdd.max_hw_heartbeat_ms = 60 * 1000;

    watchdog_init_timeout(&wdt->wdd, 0, dev);
    
    /** 自定义属性 */
    of_property_read_u32(dev->of_node, "dy,pretimeout-ms", &pretimeout_ms);
    if (pretimeout_ms)
    {
        wdt->pretimeout_cnt = pretimeout_ms / DY_WDT_TICK_MS;
        wdt->wdd.pretimeout = pretimeout_ms / 1000;
    }
    of_property_read_u32(dev->of_node, "dy,timeout-ms", &timeout_ms);
    if (timeout_ms)
    {
        wdt->counter = timeout_ms / DY_WDT_TICK_MS;
    }
    wdt->max_counter = (wdt->wdd.max_timeout * 1000) / DY_WDT_TICK_MS;
    dev_info(dev, "pretimeout counter = %u, counter = %u, max_counter = %u\r\n",
        wdt->pretimeout_cnt, wdt->counter, wdt->max_counter);

    watchdog_set_drvdata(&wdt->wdd, wdt);
    watchdog_set_nowayout(&wdt->wdd, false);

    ret = watchdog_register_device(&wdt->wdd);
    if (ret)
    {
        dev_err(dev, "failed to register watchdog: %d\r\n", ret);
        goto err_worker;
    }

    dev_info(dev, "dy-soft-watchdog probed: timeout=%us, max_counter=%u,"
		 " mem_phys=%pa, mem_size=%pa\n",
		 wdt->wdd.timeout, wdt->max_counter,
		 &wdt->mem_phys, &wdt->mem_size);
	return 0;

err_worker:
    kthread_destroy_worker(wdt->worker); 

err_clk:
    if (wdt->pclk && !IS_ERR(wdt->pclk))
    {
        clk_disable_unprepare(wdt->pclk);
    }
    return ret;
}

int dy_wdt_remove(struct platform_device *pdev)
{
    struct dy_wdt *wdt = platform_get_drvdata(pdev);

    watchdog_unregister_device(&wdt->wdd);

    hrtimer_cancel(&wdt->timer);
    kthread_destroy_worker(wdt->worker);
    if (wdt->pclk && !IS_ERR(wdt->pclk))
    {
        clk_disable_unprepare(wdt->pclk);
    }
    dev_info(&pdev->dev, "dy-soft-watchdog removed\n");

    return 0;
}
void dy_wdt_shutdown(struct platform_device *pdev)
{
    
}
int dy_wdt_suspend(struct platform_device *pdev, pm_message_t state)
{
    return 0;
}
int dy_wdt_resume(struct platform_device *pdev)
{
    return 0;
}

const static struct platform_device_id of_table[] = {
    {.name = "dy-wdt", .driver_data = 0},
    {}
};

const static struct of_device_id dy_wdt_of_match[] = {
    {.compatible = "dy-wdt"},
    {}
};

static struct platform_driver dy_wdt_driver = {
    .probe = dy_wdt_probe,
    .remove = dy_wdt_remove,
    .shutdown = dy_wdt_shutdown,
    .suspend = dy_wdt_suspend,
    .resume = dy_wdt_resume,
    .driver = {
        .name = "dy-wdt",
        .of_match_table = dy_wdt_of_match,
    },
    .id_table = of_table,
    .prevent_deferred_probe = false
};

module_platform_driver(dy_wdt_driver);


MODULE_AUTHOR("DYWorker001");
MODULE_DESCRIPTION("DY Watchdog Driver");
MODULE_LICENSE("GPL");

4.6.3 测试demo:启动亮灯;喂狗指示灯闪烁;超时关灯

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/watchdog.h>



int main()
{
    printf("hello\r\n");

    /*首先打开/dev/watchdog, /dev/watchdog0, 然后不写任何东西*/
    /*首先打开/dev/watchdog, /dev/watchdog0, ioctl控制超时时间*/
    int fd = -1;
    int wmod = 2;
    int timeout = 20;
    int flags = 0;

    fd = open("/dev/watchdog1", O_WRONLY);
    if (fd < 0)
    {
        perror("open");
        return -1;
    }

    /*设置预超时模式*/
    if (ioctl(fd, WDIOC_SETPRETIMEOUT, &wmod) < 0)
        perror("SETPRETIMEOUT");

    /*设置超时时间*/
    if (ioctl(fd, WDIOC_SETTIMEOUT, &timeout) < 0)
        perror("SETTIMEOUT");
    
    /*正常跑, 喂狗10s, 证明系统稳定*/
    for (int i = 0; i < 10; i++) {
        write(fd, "\0", 1);          /* 喂狗 */
        printf("feed at t=%ds\n", i);
        sleep(1);
    }

    /*【模拟崩溃】不再喂狗,进程也不退出(保持 fd 打开、保持 active) */
    printf(">>> STOP FEEDING now, system will reset in ~%ds\n", timeout);
    for (;;)
        pause();

    return 0;
}

5 关键组件:struct regmap,kthread_worker,hrtimer,devm_*

5.1 Linux内核线程机制:kthread.c

  1. 是什么
    • Linux 内核线程解决了在内核中"创建、控制、销毁后台线程"的通用需求
    • 并在此基础上提供了简化的kthread_worker工作队列框架
  2. 核心数据
    • struct kthread_create_info:创建请求载体。threadfn,线程函数;data,函数参数
    • struct kthread:每个内核线程的私有元数据。threadfn,线程函数;data,函数参数;cpu,绑定的CPU
    • struct kthread_worker :工作线程容器。含 raw_spinlock_t lock 保护、work_list(就绪队列)、delayed_work_list(延迟队列)、task(实际线程)、current_work(正在执行的工作)
    • struct kthread_work:一项工作,包含链表节点、回调func、所属worker、canceling取消计数器
    • struct kthread_delayed_work:内嵌kthread_work+timer_list,实现延迟调度
  3. kthreadd守护线程 :检查struct kthread_create_info链表,非空则创建内核线程kernel_thread()
c 复制代码
int kthreadd(void *unused)
{
	struct task_struct *tsk = current;

	/* Setup a clean context for our children to inherit. */
	set_task_comm(tsk, "kthreadd");
	ignore_signals(tsk);
	set_cpus_allowed_ptr(tsk, housekeeping_cpumask(HK_FLAG_KTHREAD));
	set_mems_allowed(node_states[N_MEMORY]);

	current->flags |= PF_NOFREEZE;
	cgroup_init_kthreadd();

	for (;;) {
		set_current_state(TASK_INTERRUPTIBLE);
		/*无请求,调度器其他线程执行*/
		if (list_empty(&kthread_create_list))
			schedule();
		__set_current_state(TASK_RUNNING);

		spin_lock(&kthread_create_lock);
		/*有请求,循环创建内核线程执行:create_kthread() → kernel_thread()*/
		while (!list_empty(&kthread_create_list)) {
			struct kthread_create_info *create;

			create = list_entry(kthread_create_list.next,
					    struct kthread_create_info, list);
			list_del_init(&create->list);
			spin_unlock(&kthread_create_lock);
			/*创建内核线程*/
			create_kthread(create);

			spin_lock(&kthread_create_lock);
		}
		spin_unlock(&kthread_create_lock);
	}

	return 0;
}

5.1.1 kthread_create():创建一个内核线程,无任务队列

  1. 原型:创建一个task,放在调度器中调度
c 复制代码
#define kthread_create(threadfn, data, namefmt, arg...) \
	kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)

#define kthread_run(threadfn, data, namefmt, ...)			   \
({									   \
	struct task_struct *__k						   \
		= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
	if (!IS_ERR(__k))						   \
		wake_up_process(__k);					   \
	__k;								   \
})

struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
						    void *data, int node,
						    const char namefmt[],
						    va_list args)
{
	DECLARE_COMPLETION_ONSTACK(done);
	struct task_struct *task;
	struct kthread_create_info *create = kmalloc(sizeof(*create),
						     GFP_KERNEL);

	if (!create)
		return ERR_PTR(-ENOMEM);
	create->threadfn = threadfn;
	create->data = data;
	create->node = node;
	create->done = &done;

	spin_lock(&kthread_create_lock);
	list_add_tail(&create->list, &kthread_create_list);
	spin_unlock(&kthread_create_lock);

	wake_up_process(kthreadd_task);
	/*
	 * Wait for completion in killable state, for I might be chosen by
	 * the OOM killer while kthreadd is trying to allocate memory for
	 * new kernel thread.
	 */
	if (unlikely(wait_for_completion_killable(&done))) {
		/*
		 * If I was SIGKILLed before kthreadd (or new kernel thread)
		 * calls complete(), leave the cleanup of this structure to
		 * that thread.
		 */
		if (xchg(&create->done, NULL))
			return ERR_PTR(-EINTR);
		/*
		 * kthreadd (or new kernel thread) will call complete()
		 * shortly.
		 */
		wait_for_completion(&done);
	}
	task = create->result;
	if (!IS_ERR(task)) {
		static const struct sched_param param = { .sched_priority = 0 };
		char name[TASK_COMM_LEN];

		/*
		 * task is already visible to other tasks, so updating
		 * COMM must be protected.
		 */
		vsnprintf(name, sizeof(name), namefmt, args);
		set_task_comm(task, name);
		/*
		 * root may have changed our (kthreadd's) priority or CPU mask.
		 * The kernel thread should not inherit these properties.
		 */
		sched_setscheduler_nocheck(task, SCHED_NORMAL, &param);
		set_cpus_allowed_ptr(task,
				     housekeeping_cpumask(HK_FLAG_KTHREAD));
	}
	kfree(create);
	return task;
}
  1. 特点
    • 用户自己写threadfn while循环
    • 自己实现唤醒逻辑
    • 任务投递:kthread_stop(task_struct)

5.1.2 kthread_create_worker():创建一个内核线程,内置任务队列

  1. 原型
c 复制代码
int kthread_worker_fn(void *worker_ptr)
{
	struct kthread_worker *worker = worker_ptr;
	struct kthread_work *work;
	
	WARN_ON(worker->task && worker->task != current);
	worker->task = current;

	if (worker->flags & KTW_FREEZABLE)
		set_freezable();

repeat:
	set_current_state(TASK_INTERRUPTIBLE);
	if (kthread_should_stop()) {
		__set_current_state(TASK_RUNNING);
		raw_spin_lock_irq(&worker->lock);
		worker->task = NULL;
		raw_spin_unlock_irq(&worker->lock);
		return 0;
	}

	work = NULL;
	raw_spin_lock_irq(&worker->lock);
	if (!list_empty(&worker->work_list)) {
		work = list_first_entry(&worker->work_list,
					struct kthread_work, node);
		list_del_init(&work->node);
	}
	worker->current_work = work;
	raw_spin_unlock_irq(&worker->lock);

	if (work) {
		__set_current_state(TASK_RUNNING);
		work->func(work);
	} else if (!freezing(current))
		schedule();

	try_to_freeze();
	cond_resched();
	goto repeat;
}

static __printf(3, 0) struct kthread_worker *
__kthread_create_worker(int cpu, unsigned int flags,
			const char namefmt[], va_list args)
{
	struct kthread_worker *worker;
	struct task_struct *task;
	int node = NUMA_NO_NODE;

	worker = kzalloc(sizeof(*worker), GFP_KERNEL);
	if (!worker)
		return ERR_PTR(-ENOMEM);

	kthread_init_worker(worker);

	if (cpu >= 0)
		node = cpu_to_node(cpu);
	
	/*调用kthread_create,以kthread_worker_fn为线程函数创建一个task*/
	task = __kthread_create_on_node(kthread_worker_fn, worker,
						node, namefmt, args);
	if (IS_ERR(task))
		goto fail_task;

	if (cpu >= 0)
		kthread_bind(task, cpu);

	worker->flags = flags;
	worker->task = task;
	wake_up_process(task);
	return worker;

fail_task:
	kfree(worker);
	return ERR_CAST(task);
}
  1. 特点
    • 可以接收多处投递过来的异步任务
    • 可以设置实时FIFO优先级:sched_set_fifo(watchdog_kworker->task)
    • 任务投递:kthread_queue_work()投递任务
    • 停止接口:kthread_destroy_worker(worker)

5.2 timer:定时器,kernel/kernel/time/

5.2.1 hrtimer:High-resolution timer

  1. 是什么
    • 高精度定时器:纳秒级
    • 特点
      • 回调运行在中断上下文,原子上下文,禁止sleep,禁止mutex_lock
  2. 核心数据结构
    • struct hrtimer:_softexpires,到期时间点;function,到期回调函数
  3. 关键接口
    • hrtimer_init():创建内核高精度hrtimer
    • hrtimer_start():启动定时器

5.2.2 itimer:进程间隔定时器

  1. 是什么
    • 进程间隔定时器,给用户态应用使用,绑定task_struct,基于进程CPU或真实墙上时间
    • 旧版API

5.3 governor:panic,noop

5.4 struct regmap:寄存器读,写

  1. 物理地址映射成虚拟地址:ioremap
  2. 虚拟地址创建struct regmap

5.5 平台设备:struct platform_device

5.5.1 struct platform_device:平台设备

  1. struct platorm_device
c 复制代码
struct platform_device {
	const char	*name;
	int		id;
	/*标记id是否是内核自动分配*/
	bool		id_auto;
	/*设备基类*/
	struct device	dev;
	/*DMA相关参数:DMA掩码,规定该设备支持的DMA物理地址范围;DMA参数*/
	u64		platform_dma_mask;
	struct device_dma_parameters dma_parms;
	/*这里描述了资源:资源的数量,资源集合. 对应dts里的reg, interrupts*/
	u32		num_resources;
	struct resource	*resource;
	
	/*用于传统非设备树平台驱动匹配**,`platform_driver`里的`id_table`*/
	const struct platform_device_id	*id_entry;
	/*强制驱动匹配:手动指定一个驱动名字,强制该 platform_device 绑定到指定 platform_driver. 调试用*/
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;

	ANDROID_KABI_RESERVE(1);
	ANDROID_KABI_RESERVE(2);
};
  1. struct device_dma_parameters
c 复制代码
struct device_dma_parameters {
	/*
	 * a low level driver may set these to teach IOMMU code about
	 * sg limitations.
	 */
	unsigned int max_segment_size;
	unsigned int min_align_mask;
	unsigned long segment_boundary_mask;
};
  1. struct resource:是对"一条硬件资源区间"的抽象描述,即设备关联的reg,interrupts等资源
c 复制代码
struct resource {
	resource_size_t start;       /* 起始地址/编号 */
	resource_size_t end;         /* 结束地址/编号(闭区间,size = end - start + 1)*/
	const char *name;            /* 资源名 */
	unsigned long flags;         /* 类型标志(见问题 2)*/
	unsigned long desc;
	struct resource *parent, *sibling, *child;  /* 资源树关联 */
};

5.5.2 IORESOURCE:IO,MEM,REG,IRQ,DMA,BUS

  1. IORESOURCE_IO :I/O端口空间
    • 传统DT不直接产,仅x86 PCI/ISA域有
  2. IORESOURCE_MEM :内存映射IO(MMIO),寄存器所在物理内存区间
    • reg = <addr size>属性
    • 可通过索引来获取集合中某个元素
  3. IORESOURCE_REG :寄存器偏移(相对于某个基地址的偏移量)
    • DT不直接生成
    • 一般由PCI域生成,或用DEFINE_RES_*代码构造
  4. IORESOURCE_IRQ :中断号
    • interrupts = <&gic ...>属性
    • of_irq_to_resource() 完成 GIC 硬件号→linux IRQ 号转换
  5. IORESOURCE_DMA :DMA通道号
    • DT不直接生成
    • 多用于PnP/ISA老设备
  6. IORESOURCE_BUS :总线号
    • DT中PCI节点内部生成
    • 平台设备不适用

5.5.3 设备资源管理接口:devm_*

  1. 背景
    • 传统写法中,驱动 probe 里申请的资源(ioremap、内存、时钟、IRQ)必须在每一个出错分支和remove函数里手动释放
    • devm_*把资源生成周期绑定到struct device
      • probe 成功→资源随驱动 detach 自动释放
      • probe 中途失败→已申请的自动回滚
  2. 核心数据结构:struct devres
c 复制代码
/*设备管理与自己相关的所有内存资源的链表*/
struct devres {
	struct devres_node node;   /* 链表节点 + release 回调函数指针 */
	u8 data[];                 /* 资源数据(如 ioremap 后的地址)*/
};
  1. 使用流程:struct devres
c 复制代码
devres_alloc(release_fn, size, gfp)   // ① 分配 devres + data,记录释放回调
...  使用资源 ...                    // ② (如 ioremap,地址存入 data)
devres_add(dev, res)                 // ③ 挂入 dev->devres_head 链表
                                     //     (devres_add 实现见 devres.c,前面搜到)
  1. __devm_request_region():devm管理资源示例
c 复制代码
struct resource *
__devm_request_region(struct device *dev, struct resource *parent,
		      resource_size_t start, resource_size_t n, const char *name)
{
	struct region_devres *dr = NULL;
	struct resource *res;
	
	/*申请资源节点*/
	dr = devres_alloc(devm_region_release, sizeof(struct region_devres),
			  GFP_KERNEL);
	if (!dr)
		return NULL;

	dr->parent = parent;
	dr->start = start;
	dr->n = n;

	res = __request_region(parent, start, n, name, 0);
	if (res)
		/*添加到资源链表*/
		devres_add(dev, dr);
	else
		devres_free(dr);

	return res;
}
相关推荐
爱和冰阔落11 分钟前
【Linux】多线程打印为什么会乱?pthread 创建、等待、退出、取消与分离全实战
linux·运维·c++·redis
初願致夕霞13 分钟前
五种IO模型(详解非阻塞IO与多路转接)
linux·前端·网络·数据库·tcp/ip
技术猿禁19 分钟前
Linux运维开发
linux·运维·运维开发
脚踏实地,坚持不懈!25 分钟前
Android ANR 内核底层全解析:从一次触摸到“应用无响应”
android·linux
不会就选b28 分钟前
Linux之socket编程(三)
linux·运维·服务器
秋风&萧瑟37 分钟前
【Linux系统编程】Linux IPC 的使用
linux·运维·网络
玖石书2 小时前
WSL2 手动安装 Ubuntu 24.04 到指定磁盘位置
linux·运维·ubuntu·wsl
库玛西2 小时前
深入浅出传输层:UDP 与 TCP 协议全景指南
linux·服务器·网络·c++·笔记·tcp/ip·udp
薛定谔的悦6 小时前
储能 EMS 的功率策略:从一块电表到一次逆流的 200 毫秒
大数据·linux·能源·储能·bms