前言
本篇文章我将为大家分析LED驱动程序的框架,驱动程序框架是来自于韦东山老师所讲。
一、如何编写一个字符设备驱动程序
1.确定主设备号,也可以让内核分配。
每一个设备都有自己的主设备号和次设备号用于区分不同的设备,这里我们暂时不使用次设备号只使用到了主设备号。
2.定义自己的file_operations结构体
编写字符设备驱动程序时需要提供一个file_operations结构体,这个结构体里面包含了read和write函数,在应用程序中调用read和write函数将会调用到驱动程序中的read和write函数。
static struct file_operations gpio_key_drv = { .owner = THIS_MODULE, .read = led_drv_read, .write = led_drv_write, };
3.实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { return 0; } static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { return 0; }
4.把file_operations结构体告诉内核:register_chrdev
major = register_chrdev(0, "100ask_led", &gpio_key_drv);
5.谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init led_drv_init(void) { return 0; }
6.有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
static void __exit led_drv_exit(void) { }
7.其他完善:提供设备信息,自动创建设备节点:class_create, device_create
led_class = class_create(THIS_MODULE, "100ask_led_class"); if (IS_ERR(led_class)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); unregister_chrdev(major, "100ask_led"); return PTR_ERR(led_class); } device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led");
二、具体LED驱动程序编写
1.确定LED引脚标号
由原理图可知LED标号为GPIO5_3,由于我们使用的开发板是imx6ull,所以我们这里需要转换一下。
imx6ull内部对GPIO进行了分组32个引脚为一组,从gpiochip0开始计数,所以GPIO5_3对应的编号为:32*4+3=131,这样我们就得到了GPIO5_3的编号了。
2.定义描述GPIO的结构体
这里我们定义一个结构体,这个结构体里面含有gpio引脚的各种信息,一个gpio引脚就分别对应一个结构体。
struct gpio_desc{ int gpio;//gpio编号 int irq;//中断号 char *name;//引脚名字 int key; struct timer_list key_timer; } ; static struct gpio_desc gpios[2] = { {131, 0, "led1", }, //{132, 0, "led2", }, };
3.入口函数与出口函数
通过class_create和device_create这些辅助信息我们就能创建出一个名为/dev/100ask_led 的设备节点了
/* 入口函数 */ static int __init led_drv_init(void) { int err; int i; int count = sizeof(gpios)/sizeof(gpios[0]);//计算共有几个LED灯 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); for (i = 0; i < count; i++) { /* get gpio */ err = gpio_request(gpios[i].gpio, gpios[i].name);//得到GPIO引脚 if (err) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return -1; } /* set gpio as output */ gpio_direction_output(gpios[i].gpio, 1);//设置GPIO引脚为输出模式 } /* 注册file_operations */ major = register_chrdev(0, "100ask_led", &gpio_key_drv); /* /dev/gpio_desc */ led_class = class_create(THIS_MODULE, "100ask_led_class"); if (IS_ERR(led_class)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); unregister_chrdev(major, "100ask_led"); return PTR_ERR(led_class); } device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led"); /* /dev/100ask_gpio */ return err; } /* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */ static void __exit led_drv_exit(void) { int i; int count = sizeof(gpios)/sizeof(gpios[0]); printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); device_destroy(led_class, MKDEV(major, 0)); class_destroy(led_class); unregister_chrdev(major, "100ask_led"); for (i = 0; i < count; i++) { gpio_free(gpios[i].gpio); } }
4.读写函数
在read和write函数中我们一般都是通过copy_to_user和copy_from_user这两个函数和应用程序进行数据的交互。
/* 实现对应的open/read/write等函数,填入file_operations结构体 */ static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { //printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); unsigned char led_buf[2]; int err; int count = sizeof(gpios)/sizeof(gpios[0]); if (size != 2) return -EINVAL; err = copy_from_user(led_buf, buf, 1); if(led_buf[0] > count) return -EINVAL; led_buf[1] = gpio_get_value(gpios[led_buf[0]].gpio);//读取引脚电平 err = copy_to_user(buf, led_buf, 2); return 2; } static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { unsigned char ker_buf[2]; int err; if (size != 2) return -EINVAL; err = copy_from_user(ker_buf, buf, size); if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0])) return -EINVAL; gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);//设置引脚电平 return 2; }
三、测试程序编写
应用程序比较简单大家自己就能看懂,这里就不多赘述。
#include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <poll.h> #include <signal.h> static int fd; /* * ./led_test <0|1|2...> on|off * */ int main(int argc, char **argv) { int fd; char buf[2]; int ret; if(argc < 2) { printf("Usage :%s <0|1|2...> [on|off]\n",argv[0]); return -1; } fd = open("/dev/100ask_led",O_RDWR); if(fd < 0) { printf("open /dev/100ask_led err\n"); return -1; } if(argc == 3) { buf[0] = strtol(argv[1], NULL, 0); if(strcmp(argv[2],"on") == 0) { buf[1] = 0; } else { buf[1] = 1; } write(fd, buf, 2); } else { buf[0] = strtol(argv[1], NULL, 0); ret = read(fd, buf, 2); if (ret == 2) { printf("led %d status is %s\n", buf[0], buf[1] == 0 ? "on" : "off"); } } close(fd); return 0; }
四、上机实验
1.挂载网络文件系统,打开内核打印信息
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
echo 7 4 1 7 > /proc/sys/kernel/printk
2.装载驱动程序
3.查看设备节点和引脚信息
4.运行测试程序
总结
有了百问网韦老师讲的驱动程序框架一切都变得简单起来了。