i.MAX6ULL Linux LED 字符设备驱动代码分析
概述
这是一个简单的针对特定硬件平台的LED字符设备驱动,通过文件操作接口控制GPIO来控制LED状态,仅供参考。
头文件包含
#include <linux/init.h> // 模块初始化和退出
#include <linux/printk.h> // 内核打印
#include <linux/types.h> // 数据类型
#include <linux/fs.h> // 文件系统
#include <linux/cdev.h> // 字符设备
#include <linux/export.h> // 模块导出
#include <linux/kdev_t.h> // 设备号
#include <linux/device.h> // 设备类
#include <asm/io.h> // IO操作
#include <asm/string.h> // 字符串操作
#include <asm/uaccess.h> // 用户空间数据访问
宏定义和设备参数
#define MAJOR_NUM 248 // 主设备号
#define MINOR_NUM 0 // 次设备号
#define DEV_NAME "led" // 设备名称
#define CLASS_NAME "led_class" // 设备类名称
// GPIO寄存器物理地址
#define GPIO1_SW_MUX_CTL 0x20e0068 // 复用控制寄存器
#define GPIO1_SW_PAD_CTL 0x20e02f4 // 引脚控制寄存器
#define GPIO1_DIR 0x209c004 // 方向控制寄存器
#define GPIO1_DATA 0x209c000 // 数据寄存器
全局变量
static volatile unsigned long * sw_mux; // 复用控制寄存器虚拟地址
static volatile unsigned long * sw_pad; // 引脚控制寄存器虚拟地址
static volatile unsigned long * gpio1_dir; // 方向控制寄存器虚拟地址
static volatile unsigned long * gpio1_data;// 数据寄存器虚拟地址
static dev_t devno; // 设备号
static struct cdev cdev; // 字符设备结构体
static struct class *led_class;// 设备类指针
硬件控制函数
LED硬件初始化
static void led_hardware_init(void)
{
*sw_mux &= ~(0xf << 0); // 清除低4位
*sw_mux |= (0x5 << 0); // 设置为GPIO功能(0x5)
*sw_pad = 0x10B0; // 设置引脚电气特性
*gpio1_dir |= (1 << 3); // 设置GPIO1_3为输出模式
*gpio1_data |= (1 << 3); // 初始化为高电平(LED灭)
}
LED控制函数
// 打开LED(低电平点亮)
static void led_on(void)
{
*gpio1_data &= ~(1 << 3); // 清除第3位,输出低电平
}
// 关闭LED(高电平熄灭)
static void led_off(void)
{
*gpio1_data |= (1 << 3); // 设置第3位,输出高电平
}
文件操作接口
打开设备
static int open(struct inode * node, struct file * file)
{
led_hardware_init(); // 初始化LED硬件
printk("kernel led open ...\n");
return 0;
}
读取设备
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * loff)
{
printk("kernel led read ...\n");
return 0; // 未实现实际读取功能
}
写入设备
static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * loff)
{
int ret = 0;
unsigned char data[10] = {0};
// 安全拷贝,防止缓冲区溢出
unsigned int len_cp = len < sizeof(data) ? len : sizeof(data);
ret = copy_from_user(data, buf, len_cp); // 从用户空间拷贝数据
// 根据命令控制LED
if(!strcmp(data, "ledon"))
led_on();
else if(!strcmp(data, "ledoff"))
led_off();
else
ret = -EINVAL; // 无效命令
printk("kernel led write ...\n");
return len_cp;
}
关闭设备
static int close(struct inode * node, struct file * file)
{
printk("kernel led close ...\n");
return 0;
}
文件操作结构体
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.release = close
};
模块初始化函数
static int __init led_init(void)
{
int ret = 0;
// 1. 设备号分配和注册
devno = MKDEV(MAJOR_NUM, MINOR_NUM);
ret = register_chrdev_region(devno, 1, DEV_NAME);
if(ret < 0) {
// 静态分配失败则动态分配
ret = alloc_chrdev_region(&devno, MINOR_NUM, 1, DEV_NAME);
if(ret < 0)
goto err_register_chrdev;
}
// 2. 字符设备初始化
cdev_init(&cdev, &fops);
ret = cdev_add(&cdev, devno, 1);
if(ret < 0)
goto err_cdev_add;
// 3. 创建设备类和设备节点
led_class = class_create(THIS_MODULE, CLASS_NAME);
if(IS_ERR(led_class)) {
ret = PTR_ERR(led_class);
led_class = NULL;
goto err_class;
}
if(IS_ERR(device_create(led_class, NULL, devno, NULL, DEV_NAME))) {
ret = PTR_ERR(device_create(led_class, NULL, devno, NULL, DEV_NAME));
goto err_device;
}
// 4. 内存映射GPIO寄存器
sw_mux = ioremap(GPIO1_SW_MUX_CTL, 4);
sw_pad = ioremap(GPIO1_SW_PAD_CTL, 4);
gpio1_dir = ioremap(GPIO1_DIR, 4);
gpio1_data = ioremap(GPIO1_DATA, 4);
printk("########################################### led_init!\n");
printk("Auto create device node: /dev/%s\n", DEV_NAME);
return 0;
// 错误处理
err_device:
class_destroy(led_class);
led_class = NULL;
err_class:
cdev_del(&cdev);
err_cdev_add:
unregister_chrdev_region(devno, 1);
err_register_chrdev:
printk("led_init failed ret = %d\n", ret);
return ret;
}
模块退出函数
static void __exit led_exit(void)
{
// 1. 销毁设备节点和类
device_destroy(led_class, devno);
class_destroy(led_class);
led_class = NULL;
// 2. 释放内存映射
iounmap(gpio1_dir);
iounmap(gpio1_data);
iounmap(sw_pad);
iounmap(sw_mux);
// 3. 注销字符设备和设备号
cdev_del(&cdev);
unregister_chrdev_region(devno, 1);
printk("########################################### led_exit!\n");
}
模块声明
module_init(led_init); // 指定模块初始化函数
module_exit(led_exit); // 指定模块退出函数
关键特性总结
1. “字符设备驱动”:实现了标准的文件操作接口
2. “GPIO控制”:通过内存映射访问硬件寄存器
3. “用户空间接口”:支持通过write系统调用控制LED
4. “命令格式”:
- `"ledon"` - 打开LED
- `"ledoff"` - 关闭LED
5. “自动创建设备节点”:在`/dev/led`创建设备文件
6. “错误处理”:完整的错误处理机制和资源清理
使用方式
# 加载模块
insmod led_driver.ko
# 控制LED
echo "ledon" > /dev/led # 打开LED
echo "ledoff" > /dev/led # 关闭LED
# 卸载模块
rmmod led_driver