1 RK3568框图

- 资源网址
1.2 驱动源码走读感悟
- 在Linux子系统的设计中充满了C++面向对象的设计逻辑
- 设计子系统的步骤
- 先根据某类物质的功能特性制定
interface,也就是ops - 然后使用
interface搭建某种功能的arch arch搭建成功后,就让底层去实现这样的一个interface
- 先根据某类物质的功能特性制定
2 Kernel编译配置,设备树替换
- RK_KERNEL_DTS_NAME
- RK_KERNEL_CFG
- RK_KERNEL_CFG_FRAGMENTS
2.1 替换设备树
- 创建自定义dts文件: kernel/arch/arm64/boot/dts/rockchip/rk3568-atk-custom.dts
- 在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
- 修改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
- alientek_rk3568_defconfig
- make.sh修改RK_DTS
- ATK_DLRK3568: RK_DTS=rk3568-atk-custom
- 验证结果
- 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 修改编译配置
- 创建自定义配置:kernel/arch/arm64/configs/rk3568_atk_custom_defconfig
- alientek_rk3568_defconfig中绑定
- RK_KERNEL_CFG="rk3568_atk_custom_defconfig"
- 重新生成配置并编译
- ./build.sh alientek_rk3568_defconfig
- ./build.sh kernel
- 验证
- grep RK_KERNEL_CFG output/.config
- ls -l kernel/.config
2.3 fragments叠加
- 创建fragments文件 : kernel\arch\arm64\configs
- rk3568_atk_fs.config
- CONFIG_XXX=y
- CONFIG_YYY=n
- rk3568_atk_net.config
- rk3568_atk_fs.config
- alientek_rk3568_defconfig中绑定
- RK_KERNEL_CFG_FRAGMENTS="rk3568_atk_fs.config rk3568_atk_net.config"
- 重新生成+编译
- make ARCH=arm64 rockchip_linux_defconfig
2.4 kernel: make.sh, Kconfig, Makefile,
-
- 根据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
- 方式一: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
- 根据board资源, 定义ARCH, defconfig, CROSS_COMPILE, dts
-
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" -
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) -
编译配置:device\rockchip\common\configs
- 通过环境变量控制编译选项
- Config.in.boot, Config.in.kernel, Config.in.rootfs, Config.in.update
-
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硬件驱动
- 总体架构
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
- 核心状态机 :
rpm_idle(),rmp_suspend,rpm_resume
text
rpm_suspend() rpm_resume()
RPM_ACTIVE ──────────────► RPM_SUSPENDING ──► RPM_SUSPENDED ──┐
▲ │ 失败 ▲ │
│ ▼ │ │
└───────────────── rpm_resume() ───────────┴──── rpm_resume()
RPM_RESUMING
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 可重试
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
- 设备驱动调用
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()
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);
...
- 在bus中给
struct dev_pm_ops *pm赋值(pm_generic_xxx),在pm_generic_xxx()接口中,时机调用的是device的struct dev_pm_ops *pm
3.2 PMU简介
- 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驱动去操作相应寄存器,完成电源域上电
- 电源域框图

- 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初始化:源码走读
- power-management驱动注册
- 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:包含电源域数量;不同寄存器的偏移(控制寄存器,状态寄存等)
- 获取设备of_device_id中的data:设备通过match表中.compatible="rockchip,rk3568-power-controller"与驱动匹配;.data携带了PMU寄存器信息
- 获取PMU寄存器regmap与基地址
syscon_node_to_regmap():把power-controller的父母节点(也就是power-management)转成struct regmap结构体。本质是把power-management的寄存器访问转成统一的struct regmap访问接口of_iomap():把外设寄存器基地址转成内核虚拟空间可以访问的虚拟地址
- 配置 CORE/GPU 电源切换时序
- 遍历子节点创建电源域
- 注册 provider + 挂接 panic 通知
- 分配+初始化 rockchip_pmu 结构
- power-controller的设备节点创建并不会上电所有电源域,电源域的上电发生在引用了该电源域的硬件IP被访问时
4 WDT
- watchdog_core.c :创建看门狗内核任务队列。这是核心框架
subsys_initcall_sync(watchdog_init)- 内部创建watchdogd守护线程:
kthread_create_worker(0, "watchdogd") - 守护线程不断从任务队列中接收来自不同看门狗dev的任务
- 然后执行任务
- 内部创建watchdogd守护线程:
- watchdog_dev.c :提供
watchdog_dev_register()设备注册接口- 所有注册进来的看门狗dev都映射成**/dev/watchdogX**设备文件
- 用户空间通过
struct file_operations访问 - 看门狗内核线程通过看门狗dev的
struct watchdog_ops访问 hrtimer_init():注册定时任务,定时器到期向任务队列中发送任务
- dw_wdt.c :具体的看门狗硬件IP厂商实现的看门狗驱动
- 他们都要调用
watchdog_dev_register(),把自己注册到看门狗字符设备中统一管理
- 他们都要调用
4.1 Linux WDT驱动框架:kernel\drivers\watchdog\
- 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 │
└─────────────────────────────────────────────────────────────┘
-
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;
}; -
struct watchdog_ops *ops:是提供给看门狗功能的服务接口,也是硬件驱动层需要实现的设备操作集合 -
struct file_operations watchdog_fops:文件系统适配接口,这是提供给用户层访问的接口,用户通过文件系统的open, write, ioctl等接口直接访问设备。这里需要使用struct watchdog_ops *ops接口实现open, write, ioctl的逻辑 -
总结
- 先根据功能特性制定
interface,也就是ops - 然后使用
interface搭建功能的arch arch搭建成功后,就让底层去实现这样的一个interface
- 先根据功能特性制定
4.1.1 watchdog_dev.c:字符设备层
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,停止看门狗
};
struct miscdevice watchdog_miscdev:杂项设备
c
static struct miscdevice watchdog_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &watchdog_fops,
};
watchdog_cdev_register():注册字符设备kthread_init_work(&wd_data->work, watchdog_ping_work):创建了一个kernel worker,执行watchdog_ping_work()misc_register():注册杂项设备。第一个看门狗作为杂项设备注册,看门狗0cdev_init():绑定字符设备和fopscdev_device_add():添加字符设备
4.1.2 watchdog_core.c:框架核心
- subsys_initcall_sync(watchdog_init) :注册看门狗设备
watchdog_dev_init():注册看门狗字符设备。/dev/watchdog0class_register(&watchdog_class)alloc_chrdev_region(&watchdog_devt, 0, 32, "watchdog"):申请一个dev_t
watchdog_register_device():注册一个设备,最终与字符设备绑定watchdog_dev_register()→watchdog_cdev_register():最终创建了watchdog字符设备
4.1.3 dw_wt.c:平台wdt硬件驱动
dw_wdt_drv_probe:watchdog_register_device:把wdt硬件IP注册到watchdog管理系统中
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);
};
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 看门狗框架总结
- 设备注册 :
watchdog_cdev_register,watchdog_register_pretimeout- 初始化延迟工作:watchdog_ping_work,内核侧自动喂狗的work
- 高精度hrtimer定时器 CLOCK_MONOTONIC单调时钟,相对模式
misc_register:注册一个/dev/wathdog;生成一个/dev/watchdog0cdev_init,cdev_add:初始化并添加一个字符设备
4.2 RK3568 WDT简介
- RK3568 TRM Part1 WDT简介
- 看门狗定时器(WDT),APB从设备外设,用于防止 SoC 内部部件冲突或者程序异常导致的系统卡死
- 看门狗计数器递减到0时,WDT会产生中断或复位信号,由复位控制器完成系统复位动作
- 芯片包含WDT_NS(非安全看门狗)和WDT_S(安全看门狗)
- 两种工作模式
- 直接产生系统复位
- 先触发预超时中断;如果中断服务程序没有处理清除该中断,等到第二次超时到来时,再触发系统复位
- RK3568 WDT模块框图

- 寄存器
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驱动
- 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";
};
- 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 使用硬件看门狗:超时不喂狗,自动重启;预超时处理
-
前置背景
watchdog_cdev_register():给每个注册的看门狗设备都注册了一个喂狗内核任务
struct watchdog_ops dw_wdt_opsstruct file_operations watchdog_fopswatchdog_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 模拟不喂狗复位
- 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;
}
- 关闭开门狗,不喂狗,不复位:关闭后,交给内核线程去喂狗了
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 预超时 + 自定义中断动作
- 前置背景
struct watchdog_ops dw_wdt_ops.set_pretimeout = dw_wdt_set_pretimeout:设置预超时模式
struct file_operations watchdog_fopswatchdog_ioctl:支持WDIOC_SETPRETIMEOUT配置预超时时间。映射到set_pretimeout中时,入参是预超时模式;超时时间通过WDIOC_SETTIMEOUT设置watchdog_open:执行watchdog_start,启动看门狗
dw_wdt_irq():中断处理接口
- 启用预超时,进入中断执行
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;
}
- 执行结果
text
feed at t=0s
...
>>> STOP FEEDING now, system will reset in ~22s
/*中断处理函数的输出*/
[ 933.809380] watchdog0: pretimeout event
4.5 硬件看门狗接入逻辑:
struct watchdog_device wdd:现在已经有了一个看门狗硬件IP(dw_wdt),将这个硬件IP融入到struct watchdog_device中struct watchdog_ops dw_wdt_ops- 这是框架给出的接口,驱动首先需要实现这些接口
- 这些具体的接口肯定需要访问看门狗硬件IP的寄存器,这就涉及到寄存器映射问题
- 在linux驱动中,CPU访问的是虚拟内存地址,而寄存器所在的地址是实际的物理地址,所以需要先把物理地址映射成虚拟地址之后,才能适用内核中定义的write,read接口访问寄存器
struct platform_driver dw_wdt_driver- 看门狗框架已经有了,具体的看门狗硬件也存在了,看门狗硬件对用的操作函数集也制定好了
- 现在我们需要一个驱动,让这个驱动去调用看门狗的操作函数集
- 为什么让驱动去调用,而不是设备自己去调用 ------ 这是为了解耦
- 看门狗驱动本身驱动的是同一类的dw_dwt硬件IP,所有设备使用同一个驱动,就实现了多个同类看门狗硬件的初始化
struct platform_device- 携带看门狗在SoC上的寄存器映射地址,中断号等
- 在解析设备树时动态创建,并通过总线match,把
driver和device绑定 - 最终调用
drvier的probe接口完成初始化
4.6 自定义软件看门狗:驱动实战
-
项目规划
- 设计一个虚拟看门狗硬件,支持
struct watchdog_ops my_wdt_ops所有操作 - 编写一个虚拟看门狗硬件驱动
- 编写一个虚拟看门狗硬件设备树节点
- 把驱动文件加到内核编译选项中
- 设计一个虚拟看门狗硬件,支持
-
设计要点
- 定时器代替硬件计数器
- 要点学习
- 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
-
类型声明
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; // 保护计数/开关状态的并发
};
-
接口编写
- 硬件接口: struct watchdog_ops
- 驱动接口: probe, remove
- 注册驱动: module_init, module_exit; module_platform_driver
-
变量声明
- 硬件接口: 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, ®map_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
- 是什么
- Linux 内核线程解决了在内核中"创建、控制、销毁后台线程"的通用需求
- 并在此基础上提供了简化的
kthread_worker工作队列框架
- 核心数据
struct kthread_create_info:创建请求载体。threadfn,线程函数;data,函数参数struct kthread:每个内核线程的私有元数据。threadfn,线程函数;data,函数参数;cpu,绑定的CPUstruct 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,实现延迟调度
- 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():创建一个内核线程,无任务队列
- 原型:创建一个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, ¶m);
set_cpus_allowed_ptr(task,
housekeeping_cpumask(HK_FLAG_KTHREAD));
}
kfree(create);
return task;
}
- 特点
- 用户自己写threadfn while循环
- 自己实现唤醒逻辑
- 任务投递:
kthread_stop(task_struct)
5.1.2 kthread_create_worker():创建一个内核线程,内置任务队列
- 原型
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);
}
- 特点
- 可以接收多处投递过来的异步任务
- 可以设置实时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
- 是什么
- 高精度定时器:纳秒级
- 特点
- 回调运行在中断上下文,原子上下文,禁止sleep,禁止mutex_lock
- 核心数据结构
struct hrtimer:_softexpires,到期时间点;function,到期回调函数
- 关键接口
hrtimer_init():创建内核高精度hrtimerhrtimer_start():启动定时器
5.2.2 itimer:进程间隔定时器
- 是什么
- 进程间隔定时器,给用户态应用使用,绑定task_struct,基于进程CPU或真实墙上时间
- 旧版API
5.3 governor:panic,noop
5.4 struct regmap:寄存器读,写
- 物理地址映射成虚拟地址:ioremap
- 虚拟地址创建struct regmap
5.5 平台设备:struct platform_device
5.5.1 struct platform_device:平台设备
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);
};
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;
};
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
- IORESOURCE_IO :I/O端口空间
- 传统DT不直接产,仅x86 PCI/ISA域有
- IORESOURCE_MEM :内存映射IO(MMIO),寄存器所在物理内存区间
- reg = <addr size>属性
- 可通过索引来获取集合中某个元素
- IORESOURCE_REG :寄存器偏移(相对于某个基地址的偏移量)
- DT不直接生成
- 一般由PCI域生成,或用DEFINE_RES_*代码构造
- IORESOURCE_IRQ :中断号
- interrupts = <&gic ...>属性
of_irq_to_resource()完成 GIC 硬件号→linux IRQ 号转换
- IORESOURCE_DMA :DMA通道号
- DT不直接生成
- 多用于PnP/ISA老设备
- IORESOURCE_BUS :总线号
- DT中PCI节点内部生成
- 平台设备不适用
5.5.3 设备资源管理接口:devm_*
- 背景
- 传统写法中,驱动 probe 里申请的资源(ioremap、内存、时钟、IRQ)必须在每一个出错分支和remove函数里手动释放
- devm_*把资源生成周期绑定到struct device
- probe 成功→资源随驱动 detach 自动释放
- probe 中途失败→已申请的自动回滚
- 核心数据结构:
struct devres
c
/*设备管理与自己相关的所有内存资源的链表*/
struct devres {
struct devres_node node; /* 链表节点 + release 回调函数指针 */
u8 data[]; /* 资源数据(如 ioremap 后的地址)*/
};
- 使用流程:
struct devres
c
devres_alloc(release_fn, size, gfp) // ① 分配 devres + data,记录释放回调
... 使用资源 ... // ② (如 ioremap,地址存入 data)
devres_add(dev, res) // ③ 挂入 dev->devres_head 链表
// (devres_add 实现见 devres.c,前面搜到)
__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;
}