资讯动态

ET6238驱动C源码解析:probe、中断与DMA调试实践

发布时间:2026/10/26 10:53:07 来源:尧图企业网站定制
简介面向嵌入式驱动开发者的 ET6238 LED 显示控制驱动源码基于 I2C 总线与芯片通信可驱动最多 16 行 8 列 LED 段位矩阵适合数字仪表盘、信息屏及小型显示设备等场景的移植与二次开发。压缩包共 2 个文件包含 1 个 C 源文件与 1 个 H 头文件约 2KB头文件负责初始化、写命令与写数据等接口声明源文件实现 I2C 底层收发、从机地址设置、段位显示刷新与亮度调节并包含超时重试等错误处理思路。已有 407 人学习下载。读者可从中获得 I2C 设备寻址、位操作映射与 PWM 灰度控制的完整参考实现便于快速接入现有工程或作为总线外设驱动学习范本。1. 从一块总是枚举失败的板子说起ET6238 驱动能解决什么上电后 ET6238 的寄存器读出来全是 0xff换了两版内核、重写 probe 都没用最后发现是复位脚没按手册时序拉高。这个场景几乎概括了 ET6238 驱动 C源码 的脾气硬件时序、总线访问和中断清理任何一环掉链子软件层看起来都像玄学。这份资源是一套面向嵌入式平台的 ET6238 驱动实现核心是寄存器访问、初始化、中断处理和 DMA 数据通路适合正在调外设 bring-up 的驱动工程师也适合想拿一份完整 C 源码练平台设备模型、字符设备与并发控制的开发者。它不提供业务逻辑价值在于把硬件操作拆成可读、可改的骨架。你拿到源码后先确认目标板的总线形态和复位时序再按 probe、中断、DMA 的顺序读基本不会迷路。2. 架构拆解ET6238 驱动 C源码 里的总线层、寄存器层和中断层怎么分工拿到一份 ET6238 驱动 C源码我第一眼不看 probe而是先找它挂在哪种总线上。原因很直接总线决定寄存器怎么访问、中断怎么拿、DMA 地址怎么约束后面所有代码都是围着这三件事转。源码包里的文件名可能叫 core、bus、hal、reg命名不统一但职责跑不出四层总线适配层、寄存器访问层、中断和 DMA 数据层、用户态接口层。先画职责表再读函数比从 module_init 一行行啃快得多。分层典型文件对外接口改动风险总线适配层bus.c、core.cprobe、remove、of_match_table换板子先改这里寄存器访问层regs.h、hal.creadl、writel、regmap偏移和位定义不能错中断与 DMA 层irq.c、dma.crequest_irq、dma_map_single并发和缓存一致性容易翻车用户态接口层chrdev.c、ioctl.hopen、ioctl、read、write参数校验和权限要补这张表不是让你照搬文件名而是用来判断一份 C源码 是否“分层清楚”。如果所有函数都挤在一个文件里读的时候也要按这四层拆开看先找总线注册再找寄存器读写宏再找中断入口最后找设备节点。分层清楚的项目改一个板子通常只动总线适配层和设备树分层不清楚的项目改一个寄存器偏移都可能牵出十几个文件。2.1 先定位总线类型grep 比通读快我一般先跑三条 grep找 platform_driver、i2c_driver、spi_driver找 readl/writel/regmap找 request_irq/request_threaded_irq。三条命令下去总线形态、寄存器访问方式、中断模型基本就清楚了。不要一上来读几千行 C源码先让代码自己告诉你它属于哪类驱动。# 找总线注册方式platform、SPI、I2C 还是 PCI grep -R -n platform_driver\|spi_driver\|i2c_driver\|pci_driver src include # 找寄存器访问方式直接 readl/writel 还是走 regmap grep -R -n readl\|writel\|regmap_read\|regmap_write src include # 找中断模型普通中断、线程化中断还是轮询 grep -R -n request_irq\|request_threaded_irq\|threaded_irq src include这三条命令的输出就是读源码的地图。第一条告诉你设备树里 compatible 应该怎么匹配第二条告诉你寄存器偏移改在哪里第三条告诉你中断上下文能不能睡眠。参数上重点看 grep 的-R递归目录、-n带行号目录按实际源码包替换不要硬编码。执行完如果发现同时存在 platform_driver 和 spi_driver通常说明项目做了总线抽象核心层不直接绑总线这种情况先读核心层的 ops 结构体。2.2 总线适配层probe 只做资源获取不做业务总线适配层最容易写胖。我见过不少 ET6238 驱动在 probe 里直接初始化寄存器、申请中断、创建设备节点最后 remove 写不全rmmod 就 oops。合格的做法是 probe 只做资源获取和硬件复位业务初始化放到单独函数remove 按相反顺序释放。这样换板子时只改资源获取部分核心逻辑不动。以 platform 总线为例probe 里通常要拿 memory resource 或 SPI/I2C 设备、拿中断号、拿时钟、拿复位 GPIO然后做一次硬件自检。自检失败要能回滚不能把半初始化状态留给系统。C源码 里如果看到devm_前缀说明资源由设备管理框架托管remove 可以少写一部分释放但中断、定时器、工作队列、DMA 映射这些不一定全托管还是要逐个确认。提示probe 里每拿到一个资源就在纸上记一笔“谁申请、谁释放”。remove 写不全十有八九是这笔账没记清。2.3 寄存器与并发readl/writel 不是普通变量寄存器访问层看起来最简单实际最容易埋雷。readl/writel有内存屏障语义但不能替代锁多核并发下读-改-写序列要加自旋锁或使用原子操作。ET6238 的控制寄存器如果有“写 1 清中断”或“写使能位后等待 ready”的规则顺序错了就会出现中断丢事件、状态位读不到、DMA 启动失败。C源码 里如果封装了et6238_read/et6238_write先看它有没有做地址偏移校验和屏障再看调用点有没有锁保护。/* 寄存器读写封装偏移校验和屏障不能省 */ static inline u32 et6238_read(struct et6238_dev *priv, u32 off) { if (WARN_ON(off priv-reg_size)) return 0xffffffff; return readl(priv-base off); } static inline void et6238_write(struct et6238_dev *priv, u32 off, u32 val) { if (WARN_ON(off priv-reg_size)) return; writel(val, priv-base off); }这段代码的关键参数是priv-base和priv-reg_size前者是 ioremap 后的虚拟基址后者是寄存器窗口大小通常来自设备树 reg 属性或芯片手册。WARN_ON只用于调试期暴露越界访问量产版可以改成返回错误码。读函数返回0xffffffff是让调用方能明显看出异常不要静默返回 0否则后续判断 ready 位会误判。写函数先校验再写避免一个错误的偏移把别的模块寄存器打乱。3. 核心链路probe、寄存器初始化、中断与 DMA 的代码骨架这一章按“设备被系统发现之后会发生什么”来拆。ET6238 驱动 C源码 的核心链路一般是总线匹配成功probe 拿资源复位芯片初始化寄存器和中断启动 DMA 或数据通路最后把设备节点暴露给用户态。每一步都可能失败失败路径要能回滚。下面用骨架代码讲寄存器和函数名按实际手册替换不要直接抄偏移。3.1 probe资源获取和失败回滚probe 的第一原则是“拿不到的不要硬撑”。memory resource 拿不到、中断号拿不到、时钟使能失败都要立刻返回错误码让总线框架知道设备没起来。下面这段是 platform 驱动的常见骨架重点看devm_资源管理和goto回滚顺序。static int et6238_probe(struct platform_device *pdev) { struct et6238_dev *priv; struct resource *res; int ret; /* 1. 设备私有数据devm 托管remove 自动释放 */ priv devm_kzalloc(pdev-dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv-dev pdev-dev; priv-pdev pdev; /* 2. 映射寄存器窗口 */ res platform_get_resource(pdev, IORESOURCE_MEM, 0); priv-base devm_ioremap_resource(pdev-dev, res); if (IS_ERR(priv-base)) return PTR_ERR(priv-base); priv-reg_size resource_size(res); /* 3. 拿中断号先不申请等硬件初始化完成再申请 */ priv-irq platform_get_irq(pdev, 0); if (priv-irq 0) return priv-irq; /* 4. 时钟和复位顺序按手册来 */ priv-clk devm_clk_get(pdev-dev, ref); if (IS_ERR(priv-clk)) return PTR_ERR(priv-clk); ret clk_prepare_enable(priv-clk); if (ret) return ret; /* 5. 硬件初始化失败要回滚时钟 */ ret et6238_hw_init(priv); if (ret) goto err_clk; /* 6. 初始化完成后申请中断避免早期中断风暴 */ ret devm_request_irq(pdev-dev, priv-irq, et6238_irq, IRQF_SHARED, dev_name(pdev-dev), priv); if (ret) goto err_clk; platform_set_drvdata(pdev, priv); dev_info(pdev-dev, ET6238 ready, irq%d\n, priv-irq); return 0; err_clk: clk_disable_unprepare(priv-clk); return ret; }这段代码的参数说明pdev是 platform 设备IORESOURCE_MEM表示寄存器窗口platform_get_irq(pdev, 0)取第一个中断devm_clk_get的ref要和设备树 clock-names 对上。devm_request_irq把中断申请托管给设备管理框架remove 时不用手动 free_irq但中断处理函数里不能再访问已经释放的私有数据。失败路径只回滚了时钟因为 ioremap 和 kzalloc 都是 devm 托管如果项目里还有手动申请的 DMA 缓冲区、定时器、工作队列要在这里补上对应释放。3.2 初始化与寄存器先复位再等 ready硬件初始化不要一上来就写控制寄存器。常见顺序是释放复位、使能时钟、等待电源稳定、写软复位、轮询 ready 位、配置中断掩码、最后使能数据通路。ET6238 如果有“配置前必须解锁”的寄存器还要先写解锁序列。C源码 里如果看到msleep和readl_poll_timeout说明作者在等硬件状态这类等待不要换成忙循环否则启动阶段 CPU 会被吃满。static int et6238_hw_init(struct et6238_dev *priv) { u32 val; int ret; /* 1. 软复位等待内部状态机回到 idle */ et6238_write(priv, ET6238_REG_RESET, ET6238_RESET_SW); ret readl_poll_timeout(priv-base ET6238_REG_STATUS, val, !(val ET6238_STATUS_BUSY), 1000, 100000); if (ret) return ret; /* 2. 清中断状态再配掩码顺序反了会丢早期事件 */ et6238_write(priv, ET6238_REG_IRQ_STATUS, 0xffffffff); et6238_write(priv, ET6238_REG_IRQ_MASK, ET6238_IRQ_RX | ET6238_IRQ_TX | ET6238_IRQ_ERR); /* 3. 使能数据通路最后读回确认 */ val et6238_read(priv, ET6238_REG_CTRL); val | ET6238_CTRL_EN | ET6238_CTRL_DMA_EN; et6238_write(priv, ET6238_REG_CTRL, val); val et6238_read(priv, ET6238_REG_STATUS); if (!(val ET6238_STATUS_READY)) return -EIO; return 0; }参数说明ET6238_REG_RESET、ET6238_REG_STATUS、ET6238_CTRL都是占位宏实际偏移以芯片手册为准readl_poll_timeout的 1000 是首次轮询前的微秒延时100000 是总超时微秒按手册的复位时间调整。先清中断状态再配掩码是为了避免初始化过程中产生的旧事件在中断使能后立刻触发。最后读回READY位是自检读不到就返回-EIO让 probe 走失败路径不要带着半死状态继续注册设备节点。3.3 中断与 DMA读清状态、映射正确、释放彻底中断处理函数要短。ET6238 的中断状态寄存器通常是读后清或写 1 清先读状态、再清状态、再分派处理顺序不能反。共享中断还要判断状态是否为 0是 0 就返回IRQ_NONE否则内核会认为中断线被独占。DMA 部分重点看方向、地址位宽和 unmap 时机尤其是 64 位系统上设备只支持 32 位地址时必须设置 DMA mask。static irqreturn_t et6238_irq(int irq, void *data) { struct et6238_dev *priv data; u32 status et6238_read(priv, ET6238_REG_IRQ_STATUS); /* 共享中断不是自己的中断就快速返回 */ if (!status) return IRQ_NONE; /* 先清状态再处理避免处理期间同一事件重复入栈 */ et6238_write(priv, ET6238_REG_IRQ_STATUS, status); if (status ET6238_IRQ_RX) et6238_rx_schedule(priv); if (status ET6238_IRQ_TX) et6238_tx_complete(priv); if (status ET6238_IRQ_ERR) dev_warn_ratelimited(priv-dev, ET6238 irq err: 0x%08x\n, status); return IRQ_HANDLED; } static int et6238_dma_start(struct et6238_dev *priv, void *buf, size_t len, enum dma_data_direction dir) { dma_addr_t dma; /* 映射方向要和数据流向一致方向错会读到旧缓存 */ dma dma_map_single(priv-dev, buf, len, dir); if (dma_mapping_error(priv-dev, dma)) return -ENOMEM; /* 设备只支持 32 位地址时这里要检查高位 */ et6238_write(priv, ET6238_REG_DMA_ADDR, lower_32_bits(dma)); et6238_write(priv, ET6238_REG_DMA_LEN, (u32)len); et6238_write(priv, ET6238_REG_DMA_CTRL, ET6238_DMA_START); priv-dma_addr dma; priv-dma_len len; priv-dma_dir dir; return 0; }中断函数的参数irq是中断号data是 request_irq 时传进来的私有数据。IRQ_NONE表示不是本设备中断IRQ_HANDLED表示已处理。DMA 函数的dir必须是DMA_TO_DEVICE、DMA_FROM_DEVICE或双向不能图省事全用双向dma_mapping_error必须检查映射失败还写寄存器会把错误地址发给设备。lower_32_bits只适合 32 位地址设备如果 ET6238 支持 64 位地址要拆成高低两个寄存器写。传输完成后记得dma_unmap_single不要在中断里反复 map/unmap 大块内存。4. 跑起来交叉编译、设备树挂载和用户态自测的完整步骤驱动能不能跑不只看 C源码 编不编得过还要看内核版本、架构、设备树和根文件系统是否对齐。我一般按“先编模块、再挂设备树、再看 dmesg、最后跑用户态”的顺序走。每一步只验证一件事失败时容易定位。下面给的是通用骨架路径和交叉编译器按你的板子替换。4.1 交叉编译Makefile 只做三件事驱动 Makefile 不需要写得太花核心就三件事告诉内核构建系统要编哪个模块、源码文件有哪些、内核构建目录在哪里。obj-m是模块入口et6238_driver-objs列出组成模块的多个 .oKDIR指向目标板内核的 build 目录。交叉编译时不要在主机内核目录里编否则 vermagic 对不上insmod 会直接拒绝。# 模块名和组成文件 obj-m et6238_driver.o et6238_driver-objs : src/et6238_core.o src/et6238_bus.o \ src/et6238_irq.o src/et6238_dma.o # 目标板内核构建目录交叉编译时用 KDIR 覆盖 KDIR ? /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) cleanobj-m里的名字要和最终生成的 .ko 对得上et6238_driver-objs里的路径按实际 C源码 目录改。M$(PWD)告诉内核构建系统只编当前目录的外部模块不会重编整个内核。KDIR必须指向已经配置好的目标内核不是源码根目录如果板子内核是交叉编译过的先确认include/generated/autoconf.h和Module.symvers存在。# 交叉编译示例架构、编译器和内核目录按板子替换 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- \ KDIR/path/to/target-kernel/build -j4ARCH和CROSS_COMPILE要和目标内核一致-j4按主机核数调整。编译完先看有没有 warning尤其是implicit declaration和incompatible pointer type这类警告在驱动里经常是真 bug。生成的 .ko 用modinfo看一下 vermagic和板子uname -r对不上就先解决内核版本问题不要急着 insmod。4.2 设备树compatible、reg、interrupts 三件套设备树节点不用写全先把匹配、寄存器窗口、中断、复位、时钟这几项写对。compatible必须和驱动of_match_table里的字符串完全一致reg的地址和长度要和手册的寄存器窗口一致interrupts要按中断控制器要求写类型。复位 GPIO 和时钟不是每个板子都有有就写没有就删不要留空属性。/* 总线节点按实际总线替换这里只展示字段关系 */ spi0 { status okay; et6238: et62380 { compatible vendor,et6238; /* 必须和驱动匹配表一致 */ reg 0; /* 片选号内存映射设备改成地址 */ spi-max-frequency 1000000; /* 先降频跑通再提 */ interrupt-parent gpio1; interrupts 12 IRQ_TYPE_EDGE_FALLING; reset-gpios gpio1 13 GPIO_ACTIVE_LOW; clocks clk_et6238; clock-names ref; /* 和 devm_clk_get 的 name 对上 */ status okay; }; };compatible里的vendor是占位实际用项目定义的厂商前缀但不要和已有节点冲突。reg 0表示 SPI 片选 0如果 ET6238 是内存映射设备这里要写成地址和长度驱动侧也要从spi_driver换成platform_driver。spi-max-frequency第一次调试不要拉满先 1MHz 跑通再逐步提高中断类型按硬件实际电平或边沿选选错会丢中断或中断风暴。clock-names必须和 C源码 里devm_clk_get的字符串一致否则 probe 直接返回-ENOENT。4.3 加载与自测dmesg 是第一现场模块加载后先看 dmesg不要急着跑业务。probe 成功会打印设备名和中断号失败会打印错误码。设备节点没出现先查字符设备注册和 class_create/device_create中断没触发先查/proc/interrupts计数有没有涨寄存器读写异常先用用户态工具读几个已知偏移确认总线通路。# 加载模块并看第一现场 sudo insmod et6238_driver.ko dmesg | tail -n 30 # 确认设备节点和中断计数 ls -l /dev/et6238 grep et6238 /proc/interrupts # 卸载前先确认没有进程占用 sudo rmmod et6238_driverinsmod失败常见原因是 vermagic 不匹配、依赖模块没加载、设备树没有对应节点。dmesg里看到probe failed要往回找第一个错误码不要只看最后一行。/proc/interrupts里中断计数不涨先确认硬件是否真的产生中断再用示波器或逻辑分析仪看中断脚。rmmod前用lsof /dev/et6238确认没有用户态进程占用否则 remove 不会执行。/* 用户态最小自测读一个寄存器偏移 */ #include fcntl.h #include stdio.h #include sys/ioctl.h #include unistd.h #include et6238_ioctl.h int main(void) { int fd open(/dev/et6238, O_RDWR); if (fd 0) { perror(open); return 1; } struct et6238_reg_io reg { .offset 0x00 }; if (ioctl(fd, ET6238_IOCTL_READ_REG, reg) 0) { perror(ioctl); close(fd); return 1; } printf(reg[0x%02x]0x%08x\n, reg.offset, reg.value); close(fd); return 0; }这段测试程序的参数是设备节点路径、ioctl 命令号和寄存器偏移。ET6238_IOCTL_READ_REG要和驱动里的定义一致最好放在共享头文件里不要两边各写一份。偏移先读一个手册里已知的只读寄存器比如 ID 或版本寄存器读出来符合预期再读控制寄存器。读出来全 0 或全 0xff先查 ioremap 基址和总线片选不要先怀疑 ioctl。5. 避坑与排查五个让 ET6238 驱动翻车的细节ET6238 这类外设驱动问题往往不在大逻辑而在时序、状态清理和释放顺序。下面五条是我在 bring-up 阶段最常遇到的翻车点每条按“现象、原因、解决”写。你调试时不用按顺序排查但看到类似现象可以直接对号入座。5.1 现象insmod 成功/dev/et6238 不出现现象是模块加载没有报错dmesg 里 probe 也打印了但/dev/et6238就是没有。原因通常是字符设备注册和 class_create/device_create 的顺序不对或者 udev/mdev 没有收到设备创建事件。有些精简根文件系统根本没有 udev只靠 mdevclass 创建了也不会自动建节点。还有一种情况是主设备号冲突register_chrdev_region返回失败但错误码被忽略。解决分三步先看 dmesg 里有没有cdev_add或device_create的失败打印再手动mknod /dev/et6238 c major minor验证驱动本身是否可用最后检查根文件系统的热插拔机制。C源码 里如果用了alloc_chrdev_region主设备号是动态分配的手动 mknod 前要先cat /proc/devices找到 et6238 对应的主设备号。不要图省事写死主设备号换内核或换板子容易冲突。5.2 现象寄存器读出来全是 0xff 或全 0现象是 probe 能进但读 ID 寄存器全 0xff或者全 0。原因通常不在 C源码而在硬件通路片选没拉对、时钟没输出、复位没释放、总线模式配错。ET6238 如果有复位脚要确认上电后复位脚按手册时序拉高如果有时钟要求要确认 clk_prepare_enable 之后时钟真的在跑。SPI 设备还要看 CPOL/CPHA 和字长I2C 设备要看从地址是否左移。解决顺序是先用逻辑分析仪看总线波形再确认复位和时钟最后才改驱动。寄存器读全 0xff 常见于总线没有从设备响应读全 0 常见于设备没上电或复位没释放。C源码 里如果 probe 一开始就读 ID 做自检把自检失败打印的偏移和返回值带上比只打印probe failed有用得多。这类问题软件层能给的信息有限血泪经验是先把硬件时序查完再回来怀疑代码。5.3 现象中断计数暴涨CPU 占用 100%现象是/proc/interrupts里 ET6238 的中断计数飞快上涨系统卡顿。原因通常是中断状态没清、清错位、或者没有屏蔽未处理的中断源。ET6238 如果是“写 1 清”的状态寄存器写 0 或写错位都会导致中断一直拉高共享中断里没有判断状态就返回IRQ_HANDLED也会让内核认为中断处理完了但硬件还在拉低。解决方法是进中断先读状态状态为 0 返回IRQ_NONE非 0 先写回状态寄存器清中断再按位分派。没处理的中断源要临时 mask 掉并打印警告不要让它们反复触发。调试时可以在中断里加计数但不要用printk刷屏用ratelimited或 tracepoint。C源码 里如果中断处理函数第一行就是写清所有状态要确认写清之后硬件是否真的释放中断线有些芯片要求先 mask 再清再 unmask。5.4 现象DMA 数据偶发错位小包正常大包出错现象是小数据量传输正常数据量一大就偶发错位或校验失败。原因常见三类DMA 映射方向写错、缓存没有同步、地址位宽截断。dma_map_single的方向必须和数据流向一致DMA_TO_DEVICE和DMA_FROM_DEVICE用反了会读到旧缓存。64 位系统上设备只支持 32 位地址时没有设置 DMA mask高位地址被截断设备会写到错误内存。解决方法是先确认dma_set_mask_and_coherent的位宽再检查 map/unmap 是否成对传输前后是否需要dma_sync_single_for_device或dma_sync_single_for_cpu。大包出错还要看长度寄存器位宽有些设备长度字段只有 16 位超过 64KB 要拆包。C源码 里如果 DMA 缓冲区是kmalloc分配的注意不要用栈上变量做长期 DMA函数返回后地址就失效了。5.5 现象rmmod 后 oops或者再次 insmod 失败现象是卸载模块时内核 oops或者卸载后再次加载 probe 失败。原因通常是 remove 释放顺序不对中断没关就释放了私有数据定时器/工作队列没取消就卸载了模块DMA 还在跑就 unmap 了缓冲区。ET6238 如果有后台轮询或延迟工作remove 里必须先停硬件、关中断、取消工作队列再释放内存。解决方法是 remove 按 probe 的相反顺序写先写控制寄存器停数据通路再free_irq或确认 devm 中断已经释放再cancel_work_sync/del_timer_sync再停 DMA 并 unmap最后关时钟。C源码 里如果用了devm_request_irqremove 阶段不要手动再 free 一次如果用了request_irq必须手动释放。调试时可以在 remove 每个步骤加一条 dev_info确认走到哪一步挂的。6. 二次开发给 ET6238 驱动补上 sysfs 调试和统计接口第一版驱动跑通后下一步不是加业务而是加可观测性。我一般先补 sysfs 调试接口把关键寄存器、中断计数、DMA 错误计数暴露出来再扩 ioctl把批量读写和自测命令做成用户态工具最后加 tracepoint 或 debugfs定位偶发问题。改造点不用多但要能验证。改造点具体做法验证方法风险sysfs 寄存器查看只读属性输出 ID、状态、中断计数cat 属性对比手册不要暴露写接口ioctl 扩展增加批量读、软复位、自测命令用户态工具回归参数必须校验长度和偏移统计与 trace记录中断次数、DMA 错误、超时perf/trace 观察热路径避免 printksysfs 接口不要做成万能寄存器读写太危险。只读属性最安全写操作走 ioctl 并加权限检查。下面这个骨架把状态和中断计数暴露出来et6238_show_status里读硬件寄存器et6238_show_irq_count读软件计数。属性组注册到设备节点下用户态cat就能看。/* sysfs 只读属性状态和中断计数 */ static ssize_t et6238_show_status(struct device *dev, struct device_attribute *attr, char *buf) { struct et6238_dev *priv dev_get_drvdata(dev); u32 status et6238_read(priv, ET6238_REG_STATUS); return sysfs_emit(buf, 0x%08x\n, status); } static ssize_t et6238_show_irq_count(struct device *dev, struct device_attribute *attr, char *buf) { struct et6238_dev *priv dev_get_drvdata(dev); return sysfs_emit(buf, %llu\n, priv-irq_count); } static DEVICE_ATTR(status, 0444, et6238_show_status, NULL); static DEVICE_ATTR(irq_count, 0444, et6238_show_irq_count, NULL);0444表示只读不要给写权限。sysfs_emit比sprintf安全能防止缓冲区越界。priv-irq_count要在中断里用原子变量或 per-cpu 计数更新不要用普通 u64 裸加。属性注册放在 probe 成功之后remove 里用device_remove_file或属性组自动清理。验证时先cat /sys/class/.../status再触发一次中断看计数涨不涨涨了说明中断链路是通的。从那以后我每次拿到新的 ET6238 板子都先只读 ID 寄存器、再看中断计数、最后跑 DMA 回环三步不过不往下写业务。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑