字符设备驱动——点亮led
1 代码
1.1 驱动代码
led_cdev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/uaccess.h>
//定义一个宏表示设备名字
#define dev_name "led_chrdev"
//定义宏表示设备数量
#define dev_count (3)
//定义字符设备号(普通定义)
static dev_t devno;
//用于创建文件
struct class *led_chrdev_class;
//定义一个led结构体用来
static struct led_chrdev
{
//包含字符结构体
struct cdev dev;
//定义变量接收虚拟地址
unsigned int __iomem *va_dr;
unsigned int __iomem *va_gdir;
unsigned int __iomem *va_iomuxc_mux;
unsigned int __iomem *va_ccm_ccgrx;
unsigned int __iomem *va_iomux_pad;
//定义变量存储物理地址
unsigned long pa_dr;
unsigned long pa_gdir;
unsigned long pa_iomuxc_mux;
unsigned long pa_ccm_ccgrx;
unsigned long pa_iomux_pad;
//引脚的编号
unsigned int led_pin;
unsigned int clock_offset;
};
static struct led_chrdev led_cdev[dev_count] = {
{.pa_dr = 0x0209C000,.pa_gdir = 0x0209C004,.pa_iomuxc_mux =\
0x20E006C,.pa_ccm_ccgrx = 0x20C406C,.pa_iomux_pad =\
0x20E02F8,.led_pin = 4,.clock_offset = 26},
{.pa_dr = 0x20A8000,.pa_gdir = 0x20A8004,.pa_iomuxc_mux =\
0x20E01E0,.pa_ccm_ccgrx = 0x20C4074,.pa_iomux_pad =\
0x20E046C,.led_pin = 20,.clock_offset = 12},
{.pa_dr = 0x20A8000,.pa_gdir = 0x20A8004,.pa_iomuxc_mux =\
0x20E01DC,.pa_ccm_ccgrx = 0x20C4074,.pa_iomux_pad =\
0x20E0468,.led_pin = 19,.clock_offset = 12},
};
static int led_chrdev_open(struct inode *inode, struct file *filp);
static int led_chrdev_release(struct inode *inode, struct file *filp);
static ssize_t led_chrdev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos);
static struct file_operations led_chrdev_fops =
{
.owner = THIS_MODULE,
.open = led_chrdev_open,
.release = led_chrdev_release,
.write = led_chrdev_write,
};
static int led_chrdev_open(struct inode *inode, struct file *filp)
{
unsigned int val = 0;
//定义led结构体指针变量led_cdev
struct led_chrdev *led_cdev = (struct led_chrdev *)container_of(inode->i_cdev,struct led_chrdev,dev);
filp->private_data =container_of(inode->i_cdev,struct led_chrdev,dev);
printk("open sucess!\n");
//将物理地址转为虚拟地址
led_cdev->va_dr = ioremap(led_cdev->pa_dr,4);
led_cdev->va_gdir = ioremap(led_cdev->pa_gdir,4);
led_cdev->va_iomuxc_mux = ioremap(led_cdev->pa_iomuxc_mux,4);
led_cdev->va_ccm_ccgrx = ioremap(led_cdev->pa_ccm_ccgrx,4);
led_cdev->va_iomux_pad = ioremap(led_cdev->pa_iomux_pad,4);
//操作各虚拟地址内的数据
val = ioread32(led_cdev->va_ccm_ccgrx);
val &= ~(3 << led_cdev->clock_offset);
val |= (3 << led_cdev->clock_offset);
iowrite32(val,led_cdev->va_ccm_ccgrx);
iowrite32(5,led_cdev->va_iomuxc_mux);
iowrite32(0x1F838,led_cdev->va_iomux_pad);
val = ioread32(led_cdev->va_gdir);
val &= ~(1 << led_cdev->led_pin);
val |= (1 << led_cdev->led_pin);
iowrite32(val,led_cdev->va_gdir);
val = ioread32(led_cdev->va_dr);
val |= (0X01 << led_cdev->led_pin);
iowrite32(val,led_cdev->va_dr);
return 0;
}
static int led_chrdev_release(struct inode *inode, struct file *filp)
{
struct led_chrdev *led_cdev =(struct led_chrdev *)container_of(inode->i_cdev, struct led_chrdev,dev);
//将映射的虚拟地址释放
iounmap(led_cdev->va_dr);
iounmap(led_cdev->va_gdir);
iounmap(led_cdev->va_iomuxc_mux);
iounmap(led_cdev->va_ccm_ccgrx);
iounmap(led_cdev->va_iomux_pad);
printk("release sucess!\n");
return 0;
}
static ssize_t led_chrdev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos)
{
unsigned long val = 0;
unsigned long ret = 0;
int tmp = count;
//将一个字符串转换成一个无符号长整型的数据\
实现user到内核的数据拷贝,将buf里字符串转为10进制的无符号数,存在ret中
kstrtoul_from_user(buf,tmp,10,&ret);
struct led_chrdev * led_cdev = (struct led_chrdev*)filp->private_data;
val = ioread32(led_cdev->va_dr);
if(ret == 0)
val &= ~(0X01 << led_cdev->led_pin);
else
val |= (0X01 << led_cdev->led_pin);
iowrite32(val,led_cdev->va_dr);
*ppos += tmp;
return tmp;
}
static __init int led_chrdev_init(void)
{
int i = 0;
dev_t cur_dev;
printk("led chrdev init!\n");
int ret = 0;
//采用动态分配的方式,获取主设备编号,次设备号为0,设备名称和设备数量
//设备名称(char_dev)可通过命令cat /proc/devices查看
ret = alloc_chrdev_region(&devno, 0, dev_count, dev_name);
if (ret < 0) {
printk("fail to alloc devno\n");
goto alloc_err;
}
//创建设备文件
led_chrdev_class = class_create(THIS_MODULE,"led_chrdev");
for(;i < dev_count;i++)
{
//字符设备结构体cdev与文件操作结构体file_operations关联起来
cdev_init(&led_cdev[i].dev, &led_chrdev_fops);
led_cdev[i].dev.owner = THIS_MODULE;
//设备号
cur_dev = MKDEV(MAJOR(devno),MINOR(devno)+i);
//添加设备至cdev_map散列表中
ret = cdev_add(&led_cdev[i].dev, cur_dev, 1);
if (ret < 0) {
printk("fail to add cdev\n");
goto add_err;
}
//创建设备文件
device_create(led_chrdev_class,NULL,cur_dev,NULL,dev_name"%d",i);
}
return 0;
add_err:
//添加设备失败时,注销设备号
unregister_chrdev_region(devno, dev_count);
alloc_err:
return ret;
}
static void __exit led_chrdev_exit(void)
{
int i;
dev_t cur_dev;
printk("char_device exit!\n");
for(i = 0;i < dev_count;i++)
{
cur_dev = MKDEV(MAJOR(devno),MINOR(devno)+i);
//销毁设备文件
device_destroy(led_chrdev_class,cur_dev);
//将cdev结构体从内核中移除
cdev_del(&led_cdev[i].dev);
}
//注销设备号
unregister_chrdev_region(devno,dev_count);
class_destroy(led_chrdev_class);
}
module_init(led_chrdev_init);
module_exit(led_chrdev_exit);
MODULE_LICENSE("GPL");
1.2 应用代码
led_test.c代码,用来测试驱动程序。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
char *led_on = "0\n";
char *led_off = "1\n";
int main(void)
{
printf("led_cdev led start!!!\n");
//打开设备文件
int fd = open("/dev/led_chrdev0", O_RDWR);
if(fd > 0)
printf("led_chrdev0 open success,red led on!\n");
else
printf("led_chrdev0 open fail!\n");
//写入数据,red on
write(fd, led_on, strlen(led_on));
//写入完毕,关闭文件
close(fd);
sleep(1);
//打开设备文件
fd = open("/dev/led_chrdev1", O_RDWR);
if(fd > 0)
printf("led_chrdev1 open success,green led on!\n");
else
printf("led_chrdev1 open fail!\n");
//写入数据,green on
write(fd, led_on, strlen(led_on));
//写入完毕,关闭文件
close(fd);
sleep(1);
//打开设备文件
fd = open("/dev/led_chrdev2", O_RDWR);
if(fd > 0)
printf("led_chrdev2 open success,blue red on!\n");
else
printf("led_chrdev2 open fail!\n");
//写入数据,blue on
write(fd, led_on, strlen(led_on));
//写入完毕,关闭文件
close(fd);
sleep(1);
//关闭设备
fd = open("/dev/led_chrdev0", O_RDWR);
write(fd, led_off, strlen(led_off));
close(fd);
fd = open("/dev/led_chrdev1", O_RDWR);
write(fd, led_off, strlen(led_off));
close(fd);
fd = open("/dev/led_chrdev2", O_RDWR);
write(fd, led_off, strlen(led_off));
close(fd);
return 0;
}
1.3 Makefile文件
#这里具体要根据当前文件和内核所在目录的位置设定
KERNEL_DIR=../ebf_linux_kernel/build_image/build
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m := led_cdev.o
out = led_test
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
$(CROSS_COMPILE)gcc -o $(out) led_test.c
.PHONY:clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
rm $(out)
2 程序运行结果
2.1 编译文件
使用make命令使用Makefile编译文件
make
将编译好的led_test和led_cdev.ko文件,通过NFS移动到开发板
2.2 加载驱动模块
使用命令加载模块,可以看到我们驱动文件编写时设置的打印信息led chrdev init!
sudo insmod /mnt/led_cdev.ko
驱动模块加载成功后,在/proc/devices目录下,会生成一个字符设备led_chrdev,主设备号为244。
cat /proc/devices
2.3 查看设备文件
查看/dev/目录下,创建的设备文件,在应用程序中打开
ls /dev/le*
2.4 运行测试程序
运行led_test可执行文件,这里我将led_test文件拷贝到当前目录了。
sudo ./led_test
执行应用程序,屏幕上打印信息,且开发板依次红灯、绿灯、蓝灯亮。最后关闭。
2.5 卸载内核模块
使用命令卸载
sudo rmmod led_cdev