1. 搭建嵌入式Linux BSP开发环境
任务说明
本案例实现一个最简字符设备驱动,通过操作GPIO寄存器点亮开发板上的LED。驱动加载时自动初始化GPIO并点亮LED,卸载时关闭LED并释放资源。同时提供设备文件操作接口,支持用户程序通过write()控制LED亮灭,以此掌握Linux驱动模块的加载、卸载与基本字符设备框架。
硬件前提
以i.MX6ULL开发板为例,LED接在GPIO1_IO03引脚(低电平点亮)。寄存器基地址:
- CCM_CCGR1: 0x020C406C(使能GPIO1时钟)
- IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03: 0x020E0068(复用为GPIO功能)
- IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03: 0x020E02F4(电气属性)
- GPIO1_GDIR: 0x0209C004(方向寄存器,bit3=1输出)
- GPIO1_DR: 0x0209C000(数据寄存器,bit3=0点亮)
驱动代码实现
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/slab.h>
#define LED_MAJOR 0
#define LED_NAME "led_drv"
/* 寄存器物理地址 */
#define CCM_CCGR1_ADDR 0x020C406C
#define IOMUX_MUX_ADDR 0x020E0068
#define IOMUX_PAD_ADDR 0x020E02F4
#define GPIO1_GDIR_ADDR 0x0209C004
#define GPIO1_DR_ADDR 0x0209C000
static void __iomem *ccm_ccgr1;
static void __iomem *iomux_mux;
static void __iomem *iomux_pad;
static void __iomem *gpio1_gdir;
static void __iomem *gpio1_dr;
static dev_t led_devno;
static struct cdev led_cdev;
static struct class *led_class;
static struct device *led_device;
/* 打开设备 */
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
/* 写操作:buf[0]=1点亮,buf[0]=0熄灭 */
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
char val;
int ret;
if (count < 1)
return -EINVAL;
ret = copy_from_user(&val, buf, 1);
if (ret)
return -EFAULT;
if (val == '1')
writel(readl(gpio1_dr) & ~(1 << 3), gpio1_dr); /* 低电平点亮 */
else if (val == '0')
writel(readl(gpio1_dr) | (1 << 3), gpio1_dr); /* 高电平熄灭 */
else
return -EINVAL;
return count;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
/* 初始化LED相关GPIO */
static void led_hw_init(void)
{
u32 val;
/* 1. 使能GPIO1时钟 */
ccm_ccgr1 = ioremap(CCM_CCGR1_ADDR, 4);
val = readl(ccm_ccgr1);
val |= (3 << 26); /* bit26-27: GPIO1时钟 */
writel(val, ccm_ccgr1);
/* 2. 引脚复用为GPIO */
iomux_mux = ioremap(IOMUX_MUX_ADDR, 4);
val = readl(iomux_mux);
val &= ~0xF;
val |= 0x5; /* ALT5 = GPIO1_IO03 */
writel(val, iomux_mux);
/* 3. 电气属性:上拉,200K,SRE=0 */
iomux_pad = ioremap(IOMUX_PAD_ADDR, 4);
writel(0x10B0, iomux_pad);
/* 4. 方向:输出 */
gpio1_gdir = ioremap(GPIO1_GDIR_ADDR, 4);
val = readl(gpio1_gdir);
val |= (1 << 3);
writel(val, gpio1_gdir);
/* 5. 默认点亮LED */
gpio1_dr = ioremap(GPIO1_DR_ADDR, 4);
val = readl(gpio1_dr);
val &= ~(1 << 3);
writel(val, gpio1_dr);
}
static int __init led_init(void)
{
int ret;
/* 1. 注册字符设备区域 */
ret = alloc_chrdev_region(&led_devno, 0, 1, LED_NAME);
if (ret < 0)
return ret;
/* 2. 初始化cdev并添加 */
cdev_init(&led_cdev, &led_fops);
led_cdev.owner = THIS_MODULE;
ret = cdev_add(&led_cdev, led_devno, 1);
if (ret)
goto err_cdev;
/* 3. 创建设备类 */
led_class = class_create(THIS_MODULE, LED_NAME);
if (IS_ERR(led_class)) {
ret = PTR_ERR(led_class);
goto err_class;
}
/* 4. 创建设备节点 /dev/led_drv */
led_device = device_create(led_class, NULL, led_devno, NULL, LED_NAME);
if (IS_ERR(led_device)) {
ret = PTR_ERR(led_device);
goto err_device;
}
/* 5. 硬件初始化并点亮LED */
led_hw_init();
printk(KERN_INFO "led_drv: initialized, LED ON\n");
return 0;
err_device:
class_destroy(led_class);
err_class:
cdev_del(&led_cdev);
err_cdev:
unregister_chrdev_region(led_devno, 1);
return ret;
}
static void __exit led_exit(void)
{
u32 val;
/* 关闭LED */
val = readl(gpio1_dr);
val |= (1 << 3);
writel(val, gpio1_dr);
/* 释放映射 */
iounmap(ccm_ccgr1);
iounmap(iomux_mux);
iounmap(iomux_pad);
iounmap(gpio1_gdir);
iounmap(gpio1_dr);
/* 注销设备 */
device_destroy(led_class, led_devno);
class_destroy(led_class);
cdev_del(&led_cdev);
unregister_chrdev_region(led_devno, 1);
printk(KERN_INFO "led_drv: unloaded, LED OFF\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("BSP Course");
MODULE_DESCRIPTION("First char device driver: GPIO LED control");
Makefile
obj-m := led_drv.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
编译与加载
# 在开发板或同架构主机上执行
make
sudo insmod led_drv.ko
ls /dev/led_drv
dmesg | tail -5
预期输出(dmesg):led_drv: initialized, LED ON,同时开发板上LED点亮。
用户程序测试
/* test_led.c */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char cmd;
if (argc != 2) {
printf("Usage: %s [0|1]\n", argv[0]);
return -1;
}
fd = open("/dev/led_drv", O_WRONLY);
if (fd < 0) {
perror("open");
return -1;
}
cmd = argv[1][0];
if (write(fd, &cmd, 1) != 1)
perror("write");
close(fd);
return 0;
}
gcc test_led.c -o test_led
./test_led 1 # LED点亮
./test_led 0 # LED熄灭
卸载驱动
sudo rmmod led_drv
dmesg | tail -3
预期输出:led_drv: unloaded, LED OFF,LED熄灭。
验证判定标准
- insmod后dmesg出现初始化日志,LED点亮,/dev/led_drv节点存在;
- 执行
./test_led 1LED点亮,./test_led 0LED熄灭; - rmmod后dmesg出现卸载日志,LED熄灭,设备节点消失。