嵌入式Linux驱动开发 - 新字符设备LED驱动
嵌入式Linux驱动开发 - 新字符设备LED驱动
一、项目概述
本项目实现了一个基于IMX6ULL开发板的LED字符设备驱动程序。驱动程序控制开发板上的GPIO引脚来实现LED的开关控制,配套的应用程序可以向驱动发送控制指令。
二、驱动程序分析 (newchrled.c)
1. 头文件与宏定义
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>#define LED_NAME "newchrled"
#define LED_MAJOR 200 // 主设备号
#define LEDON 1 // LED开启状态
#define LEDOFF 0 // LED关闭状态
2. 寄存器地址定义
#define CCM_CGR1_BASE 0x020C406C // 时钟控制寄存器
#define SW_MUX_GPIO1_IO03_BASE 0x020E0068 // 复用选择寄存器
#define SW_PAD_GPIO1_IO03_BASE 0x020E02F4 // PAD配置寄存器
#define GPIO1_DR_BASE 0x0209C000 // GPIO数据寄存器
#define GPIO1_GDIR_BASE 0x0209C004 // GPIO方向寄存器
3. 设备结构体定义
struct newchrled_dev
{dev_t devid; // 设备号struct cdev cdev; // cdev结构体struct class *class; // 类struct device *device;// 设备int major; // 主设备号int minor; // 次设备号
};
struct newchrled_dev newchrled; /* led设备 */
4. 硬件操作函数
led_switch - LED开关控制函数
void led_switch(int sta)
{u32 val = 0;if (sta == LEDON){val = readl(GPIO1_DR);val &= ~(1 << 3); // 清除第3位writel(val, GPIO1_DR);}else if (sta == LEDOFF){val = readl(GPIO1_DR);val |= (1 << 3); // 设置第3位writel(val, GPIO1_DR);}
}
led_write - 写操作函数
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];retvalue = copy_from_user(databuf, buf, cnt);if (retvalue < 0){printk("kernel write failed!\r\n");return -EFAULT;}led_switch(databuf[0]);return 0;
}
led_read - 读操作函数
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}
文件操作结构体
static struct file_operations led_fops = {.owner = THIS_MODULE,.write = led_write,.read = led_read,.open = led_open,.release = led_release,
};
5. 驱动初始化与退出
初始化函数
static int __init led_init(void)
{u32 val = 0;// 映射寄存器物理地址到内核虚拟地址CCM_CGR1 = ioremap(CCM_CGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);// 配置时钟val = readl(CCM_CGR1);val &= ~(3 << 26); // 清除时钟配置val |= (3 << 26); // 使能GPIO1时钟writel(val, CCM_CGR1);// 配置GPIO复用writel(0x5, SW_MUX_GPIO1_IO03); // 设置为GPIO模式writel(0x10B0, SW_PAD_GPIO1_IO03); // 设置PAD配置// 配置GPIO方向val = readl(GPIO1_GDIR);val &= ~(1 << 3); // 清除方向配置val |= (1 << 3); // 设置为输出writel(val, GPIO1_GDIR);// 初始化LED状态val = readl(GPIO1_DR);val |= (1 << 3); // 设置为高电平(LED熄灭)writel(val, GPIO1_DR);// 注册设备号if (newchrled.major){newchrled.devid = MKDEV(newchrled.major, 0);register_chrdev_region(newchrled.devid, 1, LED_NAME);}else{alloc_chrdev_region(&newchrled.devid, 0, 1, LED_NAME);newchrled.major = MAJOR(newchrled.devid);newchrled.minor = MINOR(newchrled.devid);}// 初始化cdevnewchrled.cdev.owner = THIS_MODULE;cdev_init(&newchrled.cdev, &led_fops);cdev_add(&newchrled.cdev, newchrled.devid, 1);// 创建类和设备newchrled.class = class_create(THIS_MODULE, LED_NAME);if (IS_ERR(newchrled.class)){printk(KERN_ERR "Failed to create class\n");return PTR_ERR(newchrled.class);}newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, LED_NAME);if (IS_ERR(newchrled.device)){printk(KERN_ERR "Failed to create device\n");class_destroy(newchrled.class);return PTR_ERR(newchrled.device);}return 0;
}
退出函数
static void __exit led_exit(void)
{// 取消地址映射iounmap(CCM_CGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);// 删除cdevcdev_del(&newchrled.cdev);// 销毁设备和类device_destroy(newchrled.class, newchrled.devid);class_destroy(newchrled.class);// 注销设备号unregister_chrdev_region(newchrled.devid, 1);
}
6. 模块注册
module_init(led_init);
module_exit(led_exit);
7. 模块信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("alentek");
三、应用程序分析 (newchrledAPP.c)
1. 程序功能
该应用程序用于控制LED设备,通过命令行参数接收设备文件路径和LED状态参数。
2. 主要代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main(int argc, char *argv[])
{// 参数检查if (argc != 3) {fprintf(stderr, "Usage: %s <led_device> <0|1>\n", argv[0]);return -1;}char* filename;unsigned char databuf[1];filename = argv[1];databuf[0] = atoi(argv[2]);int fd = 0;int ret = 0;// 打开设备文件fd = open(filename, O_RDWR);if (fd < 0){perror("open led device error");return -1;}// 写入数据ret = write(fd, databuf, 1);if (ret < 0){perror("write led device error");close(fd);return -1;}// 关闭文件close(fd);return 0;
}
四、Makefile分析
KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)obj-m := newchrled.obuild : kernel_moduleskernel_modules:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modulesclean:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean
1. 变量定义
KERNERDIR
: 内核源码目录CURRENTDIR
: 当前工作目录obj-m
: 指定编译成内核模块
2. 编译目标
build
: 编译内核模块kernel_modules
: 具体的编译规则clean
: 清理编译结果
五、驱动编译与测试流程
1. 驱动编译
make -C /path/to/kernel/source M=$(pwd) modules
2. 加载驱动
insmod newchrled.ko
3. 创建设备文件
mknod /dev/newchrled c 200 0
4. 运行应用程序
./newchrledAPP /dev/newchrled 1 # 打开LED
./newchrledAPP /dev/newchrled 0 # 关闭LED
六、硬件配置说明
1. GPIO配置
- 使用GPIO1_IO03引脚控制LED
- 配置时钟:CCM_CGR1寄存器第26位
- 复用选择:SW_MUX_GPIO1_IO03寄存器设置为0x5
- PAD配置:SW_PAD_GPIO1_IO03寄存器设置为0x10B0
- 方向设置:GPIO1_GDIR寄存器第3位设为1(输出)
- 数据设置:通过GPIO1_DR寄存器读写第3位
七、字符设备驱动开发要点
1. 设备号管理
- 使用
dev_t
类型表示设备号 - 使用
MKDEV
宏创建设备号 - 使用
MAJOR
和MINOR
宏提取主次设备号 - 使用
register_chrdev_region
或alloc_chrdev_region
注册设备号
2. cdev操作
- 使用
cdev_init
初始化字符设备 - 使用
cdev_add
将设备添加到内核
3. 自动创建设备节点
- 使用
class_create
创建设备类 - 使用
device_create
创建设备节点
4. 寄存器访问
- 使用
ioremap
将物理地址映射到内核虚拟地址 - 使用
readl
和writel
进行寄存器读写
5. 文件操作
- 实现
file_operations
结构体中的操作函数 open
和release
用于设备打开和关闭read
和write
用于数据读写
八、用户空间与内核空间通信
1. copy_from_user
用于将用户空间数据复制到内核空间
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
2. copy_to_user
用于将内核空间数据复制到用户空间
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
九、模块参数与信息
1. MODULE_LICENSE
指定模块的许可证,这里是GPL
2. MODULE_AUTHOR
指定模块作者信息
十、错误处理
1. 返回值检查
- 检查
open
函数返回值 - 检查
write
函数返回值 - 检查
device_create
等内核API返回值
2. 错误清理
- 出现错误时需要释放已申请的资源
- 包括取消地址映射、删除cdev、销毁设备和类等
十一、总结
本驱动程序完整实现了字符设备驱动的开发流程,包括:
- 寄存器配置与硬件操作
- 字符设备注册与管理
- 用户空间与内核空间通信
- 设备文件创建与访问
- 模块加载与卸载
- 错误处理与资源释放
Gitee 源码仓库