Linux LED驱动程序框架分析

简介: Linux LED驱动程序框架分析

前言

本篇文章我将为大家分析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,所以我们这里需要转换一下。

870920b136c145e3b69a869d2508323e.png

7c925ae3b04749c09b0d58c0587587bb.png

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.装载驱动程序

f9287ffdbdf44f3aa6d3b7ee7b29107e.png

3.查看设备节点和引脚信息

36e2c65fc95f4d72887a3919e5510387.png

8fcb1b763e8f4f65aecf31758c1565b0.png

4.运行测试程序

4f24c11a22ec40e285eb5bedd60176a9.png

总结

有了百问网韦老师讲的驱动程序框架一切都变得简单起来了。

相关文章
|
2月前
|
监控 Shell Linux
【Shell 命令集合 网络通讯 】Linux 分析串口的状态 statserial命令 使用指南
【Shell 命令集合 网络通讯 】Linux 分析串口的状态 statserial命令 使用指南
33 0
|
2月前
|
Linux Shell C语言
【Shell 命令集合 设备管理 】Linux控制Linux系统的键盘LED setleds命令 使用指南
【Shell 命令集合 设备管理 】Linux控制Linux系统的键盘LED setleds命令 使用指南
38 0
|
8天前
|
Linux Shell Android开发
自动化脚本之GPIO/LED相关适用于Android/Linux
自动化脚本之GPIO/LED相关适用于Android/Linux
14 0
|
27天前
|
Prometheus 监控 数据可视化
linux分析方法与技巧
【4月更文挑战第3天】在Linux环境中,进行日志分析和系统性能分析的关键方法包括:使用`cat`, `less`, `tail`查看和过滤日志,`logrotate`管理日志文件,`rsyslog`或`syslog-ng`聚合日志,以及通过`top`, `mpstat`, `pidstat`, `free`, `iostat`, `netstat`, `strace`, `sar`, `dstat`等工具监控CPU、内存、磁盘I/O和网络。对于高级分析,可利用Brendan Gregg的性能工具,以及Grafana、Prometheus等可视化工具。
19 2
linux分析方法与技巧
|
2月前
|
监控 Linux Shell
Linux 进程问题调查探秘:分析和排查频繁创建进程问题
Linux 进程问题调查探秘:分析和排查频繁创建进程问题
39 0
|
2月前
|
缓存 监控 算法
Linux内存碎片深度剖析:原理、处理与分析全指南
Linux内存碎片深度剖析:原理、处理与分析全指南
124 0
Linux内存碎片深度剖析:原理、处理与分析全指南
|
2月前
|
消息中间件 存储 网络协议
Linux IPC 进程间通讯方式的深入对比与分析和权衡
Linux IPC 进程间通讯方式的深入对比与分析和权衡
69 0
|
2月前
|
存储 监控 Linux
Linux 使用getrusage系统调用获取cpu信息:一个C++实例分析
Linux 使用getrusage系统调用获取cpu信息:一个C++实例分析
50 0
|
2月前
|
存储 算法 Linux
【Linux 系统标准 进程资源】Linux 创建一个最基本的进程所需的资源分析,以及线程资源与之的差异
【Linux 系统标准 进程资源】Linux 创建一个最基本的进程所需的资源分析,以及线程资源与之的差异
136 0
|
2月前
|
存储 缓存 Linux
探秘Linux块设备驱动程序:成为内核开发大师的第一步
探秘Linux块设备驱动程序:成为内核开发大师的第一步
97 0