Skip to content

kzzzza/LED_driver

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 

Repository files navigation

LED_driver — MMIO LED 字符设备驱动与示例应用(LoongArch)

本项目包含一个基于内存映射 I/O(MMIO)的 LED 字符设备驱动(led_driver.c)以及一个用户态测试程序(led_app.c)。驱动直接对寄存器进行读改写(使用 readl/writel 以两次 32 位访问拼装 64 位),用户态通过 read/write/ioctl 控制 LED 的开关与状态查询。

作为南京大学数字系统实验二的实验课程报告——实验三:字符型设备驱动开发

目录结构

  • led_driver.c:LED 字符设备驱动(内核模块,生成 led_driver.ko
  • led_app.c:用户态测试程序,演示 ioctl/闪烁
  • Makefile:同时构建内核模块与用户程序(交叉编译,LoongArch)
  • README.md:说明文档(本文件)

功能概览

  • 字符设备名称:leddev_mmio
  • 动态分配主设备号(major),需手工 mknod 建立设备节点
  • 可配置参数(模块参数)决定寄存器物理地址、偏移、掩码、方向语义和电平极性
  • 支持操作方式:
    • read(2):读取所有 LED 当前状态,返回由 '0'/'1' 组成的字节串
    • write(2):写入 '0'/'1' 字符串批量设置 LED 状态(按 led_mask 位序)
    • ioctl(2)
      • LED_IOCTL_ON :点亮指定索引的 LED
      • LED_IOCTL_OFF :熄灭指定索引的 LED
      • LED_IOCTL_TOGGLE :翻转指定索引的 LED
      • LED_IOCTL_GET :读取指定索引的 LED 状态

构建前置条件

  • 已安装 LoongArch 交叉工具链:loongarch64-linux-gnu-gcc
  • 可用的目标内核源码或内核构建目录(与目标板内核版本/配置一致)
  • 根据硬件手册确定以下信息(对于Loongson开发板,这些数据已经在代码内加载为默认数据,不需要修改和额外在insmod时候导入):
    • LED 控制寄存器物理基地址(gpio_phys_base
    • 区域大小(region_size),如 0x40/0x100
    • 数据/输出寄存器偏移(out_offset
    • 方向寄存器偏移(dir_offset,若无填 -1)
    • 方向语义(dir_output_clear:1 表示清零为输出;0 表示置位为输出)
    • LED 位掩码(led_mask),如 0x7 表示使用 bit0~bit2
    • 有效电平(active_high,1 表示高电平点亮,0 表示低电平点亮)

注:上述参数也可在加载模块时传入,详见下文“加载模块与参数”。

构建

请先在 Makefile 中检查并按需修改:

  • KDIR:指向你的目标内核源码/构建目录(包含 build 文件)
  • ARCHCROSS_COMPILE:与目标环境一致

编译内核模块与用户程序:

make

清理:

make clean

将驱动的.ko文件与用户测试文件复制到 NFS 根目录(可选,按需在 Makefile 中修改 NFS_DIR):

make install

生成的文件:

  • led_driver.ko:内核模块
  • led_app:用户态程序(LoongArch 目标)

加载模块与参数

常用模块参数(当前代码默认值以源码为准)

  • gpio_phys_base:LED 寄存器物理基地址,默认 0x1FE00500
  • region_size:MMIO 区域大小,默认 0x40
  • out_offset:数据/输出寄存器偏移,默认 0x10
  • dir_offset:方向寄存器偏移,默认 0x00(若无方向寄存器,请传 -1
  • dir_output_clear:方向位语义,默认 1(清零=输出;置位=输入)。若你的硬件相反,请传 0
  • led_mask:LED 位掩码,默认 0x7(bit0~bit2)
  • active_high:有效电平,默认 1(高电平点亮)。若硬件低电平点亮,请传 0

示例(请按你的硬件实际值替换参数):

insmod led_driver.ko \
	gpio_phys_base=0x1fe00500 region_size=0x40 \
	out_offset=0x10 dir_offset=0x00 dir_output_clear=1 \
	led_mask=0x7 active_high=1

加载成功后,查看日志获取主设备号(major)及确认参数:

dmesg | tail -n 20
# 期望看到类似:
# leddev_mmio: initialized (major=248) phys=0x1fe00500 out_off=0x10 dir_off=0x0 dir_output_clear=1 mask=0x7 active_high=1 leds=3

创建设备节点

驱动不会自动创建 /dev 节点,请用上一步日志中的 major 手动创建(或从 /proc/devices 获取):

grep leddev_mmio /proc/devices   # 可从这里取到主设备号,我自己实验时候是248
mknod /dev/leddev_mmio c <major> 0
chmod 666 /dev/leddev_mmio

如果使用 udev 规则,也可自动化创建设备节点(可选)。

快速验证(无需应用)

假设 led_mask=0x70(3 个 LED),可直接通过字符设备测试:

  • 读取当前状态(返回 N 字节,如 010;N=由 led_mask 位数决定):
cat /dev/leddev_mmio
  • 批量设置状态(如:点亮第 1 和第 3 个):
echo -n 101 > /dev/leddev_mmio

使用示例应用 led_app

led_app 使用 ioctl 顺序点亮/熄灭 LED 并测试 TOGGLE,默认操作 3 个 LED:

./led_app

程序会输出时间戳日志;如需更改循环次数/延时,可修改源码中的 loops/delay_ms 后重新编译。

如果你的板上仅有一个 LED 接在某个 GPIO 位(例如 bit2),可以将 led_mask=0x4,这样用户程序只会操作一个 LED,避免其它位没有硬件连接时造成“看似成功但无亮灯”的困扰。

代码主要逻辑与重点片段

以下结合源码说明关键实现(参见 led_driver.c):

  • 模块参数与地址映射

    • 通过 gpio_phys_base/region_size/out_offset/dir_offset 等参数确定寄存器区域与偏移。
    • 使用 ioremap() 将物理地址映射为内核虚拟地址指针 mmio_base
  • 64 位数据寄存器访问(避免使用 writeq/readq)

    • 读:
      • u32 lo = readl(mmio_base + out_offset);
      • u32 hi = readl(mmio_base + out_offset + 4);
      • 组装为 ((u64)hi << 32) | lo
    • 写:
      • writel(低32)writel(高32),最后 wmb() 保证写入次序。
    • 同样的策略也用于方向寄存器(若存在)。
  • 方向寄存器配置

    • dir_offset >= 0,则根据 dir_output_clear 配置:
      • dir_output_clear=1:清零 led_mask 对应位表示输出。
      • dir_output_clear=0:置位 led_mask 对应位表示输出。
  • 位映射与索引

    • led_mask 指定哪些 bit 是 LED;驱动会计算 LED 数量与逻辑索引。
    • 通过 led_index_to_bit(idx, &bitpos) 将逻辑索引(0..N-1)映射到实际 bit 号。
  • 读写与 ioctl 的核心路径

    • read(2):读取数据寄存器,按 led_mask 位序返回 '0'/'1' 串;若 active_high=0 则翻转语义。
    • write(2):读-改-写数据寄存器,按输入串逐位设置/清除目标位;忽略多余字节。
    • ioctl(2)
      • LED_IOCTL_ON/OFF/TOGGLE/GET:按 bitpos 精确操作对应位,不再硬编码具体位号。
  • 卸载时的收尾

    • 将所有 LED 置为“关闭”状态(依据 active_high),随后 iounmap()

说明:驱动中 request_mem_region() 被注释跳过,方便在教学或共享区域的实验环境中加载(若区域被其他模块占用也能映射)。 我自己实验时候发现,loongson开发板启动时候自带有GPIO驱动,会声明对这一部分的寄存器的使用,所以我们自己的驱动代码中不可以加入这一部分

常见问题与排查

  • 加载时报错 request_mem_region failed
    • 物理地址区间已被其他驱动占用;确认 phys_base/region_size 是否正确,或卸载冲突驱动。
    • 按照上面说的,注释request_mem_region()可以解决
  • 加载时报错 ioremap failed
    • 检查地址与大小是否可映射,确认内核内存映射限制与平台 IOMMU/安全设置。
  • 打开设备返回 -ENODEV
    • 模块未正确初始化或地址设置有误;检查 dmesg 日志。
  • ioctl 返回 -ENOTTY
    • 命令号不匹配;确认用户态宏与内核一致(LED_MAGIC/序号)。
  • 读到的位顺序与实际 LED 不一致:
    • 位序由 led_mask 从低位到高位依次映射,确认掩码与硬件连线一致。
  • LED 不亮但寄存器值已变:
    • 检查 active_high(尝试 0/1)。
    • 检查 dir_output_clear(尝试 0/1)。
    • 确认 led_mask 是否覆盖真实连接的位(例如只接了 GPIO2 就设 0x4)。
    • 确认 out_offset/dir_offset 与芯片手册一致。

驱动检测、卸载与清理(全流程)

  1. 检测驱动已加载
lsmod | grep led_driver            # 查看是否在列表中(或使用 busybox lsmod)
grep leddev_mmio /proc/devices     # 查看主设备号
dmesg | tail -n 50                 # 查看初始化日志(包含 base/offset/mask/active_high 等)
  1. 卸载驱动
fuser -v /dev/leddev_mmio 2>/dev/null || true   # 确认无进程占用(可选)
rmmod led_driver
  1. 清理构建产物(开发主机上执行)
make clean

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published