Linux LED驱动程序框架分析
@TOC
前言
本篇文章我将为大家分析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.运行测试程序
总结
有了百问网韦老师讲的驱动程序框架一切都变得简单起来了。
- 点赞
- 收藏
- 关注作者
评论(0)