Linux 驱动开发基础知识—— 驱动设计的思想(六)

简介: Linux 驱动开发基础知识—— 驱动设计的思想(六)

一、设计思想

1.1 面向对象

       字符设备驱动程序抽象出一个 file_operations 结构体;

       硬件程序针对硬件部分抽象出 led_operations 结构体。

1.2 分层

       上下分层,比如我们前面写的 LED 驱动程序就分为 2 层:

       上层实现硬件无关的操作,比如注册字符设备驱动:leddrv.c

       下层实现硬件相关的操作,比如 board_A.c 实现单板 A 的 LED 操作

1.3 分离

       在 board_A.c 中,实现了一个 led_operations,为 LED 引脚实现了初始化函数、控制函数:

static struct led_operations board_demo_led_opr = {
    .num = 1,
    .init = board_demo_led_init,
    .ctl = board_demo_led_ctl,
};

       如果硬件上更换一个引脚来控制 LED 怎么办?你要去修改上面结构体中的 init、ctl 函数。

        实际情况是,每一款芯片它的 GPIO 操作都是类似的。比如:GPIO1_3、 GPIO5_4 这 2 个引脚接到 LED:

       (1)GPIO1_3 属于第 1 组,即 GPIO1。

                a) 有方向寄存器 DIR、数据寄存器 DR 等,基础地址是 addr_base_addr_gpio1。

                b) 设置为 output 引脚:修改 GPIO1 的 DIR 寄存器的 bit3。

                c) 设置输出电平:修改 GPIO1 的 DR 寄存器的 bit3。

      (2) GPIO5_4 属于第 5 组,即 GPIO5。

               a) 有方向寄存器 DIR、数据寄存器 DR 等,基础地址是 addr_base_addr_gpio5。

               b) 设置为 output 引脚:修改 GPIO5 的 DIR 寄存器的 bit4。

               c) 设置输出电平:修改 GPIO5 的 DR 寄存器的 bit4。

       既然引脚操作那么有规律,并且这是跟主芯片相关的,那可以针对该芯片写 出比较通用的硬件操作代码。

       比如 board_A.c 使用芯片 chipY,那就可以写出:chipY_gpio.c,它实现芯片 Y 的 GPIO 操作,适用于芯片 Y 的所有 GPIO 引脚。

       使用时,我们只需要在 board_A_led.c 中指定使用哪一个引脚即可。程序结构如下:

       以面向对象的思想,在 board_A_led.c 中实现 led_resouce 结构体,它定义“资源”──要用哪一个引脚。

        在 chipY_gpio.c 中仍是实现 led_operations 结构体,它要写得更完善, 支持所有 GPIO。

二、代码分析

       程序仍分为上下结构:

       上层 leddrv.c 向内核注册 file_operations 结构体;下层 chip_demo_gpio.c 提供 led_operations 结构体来操作硬件。

       下层的代码分为 2 个:chip_demo_gpio.c 实现通用的 GPIO 操作 board_A_led.c 指定使用哪个 GPIO,即“资源”。

2.1 led_resource.h

      led_resource.h 中定义了 led_resource 结构体,用来描述 GPIO

#ifndef _LED_RESOURCE_H
#define _LED_RESOURCE_H
 
/* GPIO3_0 */
/* bit[31:16] = group */
/* bit[15:0]  = which pin */
#define GROUP(x) (x>>16)
#define PIN(x)   (x&0xFFFF)
#define GROUP_PIN(g,p) ((g<<16) | (p))
 
struct led_resource {
  int pin;
};
 
struct led_resource *get_led_resouce(void);
 
#endif

2.2 board_A_led.c

       board_A_led.c 指定使用哪个 GPIO,它实现一个 led_resource 结构体,并提供访问函数:

       当我们以后换了开发板我们只需要修改这里的资源函数

 
#include "led_resource.h"
 
static struct led_resource board_A_led = {
  .pin = GROUP_PIN(3,1),
};
 
struct led_resource *get_led_resouce(void)
{
  return &board_A_led;
}

第5行:表示第3组第1个引脚

.pin = GROUP_PIN(3,1)

第8~11行:便于访问这里的变量

2.3 chip_demo_gpio.c

       chip_demo_gpio.c 中,首先获得 board_A_led.c 实现的 led_resource 结构体,然后再进行其他操作。

#include <linux/module.h>
 
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "led_opr.h"
#include "led_resource.h"
 
static struct led_resource *led_rsc;
static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */     
{ 
  //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
  if (!led_rsc)
  {
    led_rsc = get_led_resouce();
  }
  
  printk("init gpio: group %d, pin %d\n", GROUP(led_rsc->pin), PIN(led_rsc->pin));
  switch(GROUP(led_rsc->pin))
  {
    case 0:
    {
      printk("init pin of group 0 ...\n");
      break;
    }
    case 1:
    {
      printk("init pin of group 1 ...\n");
      break;
    }
    case 2:
    {
      printk("init pin of group 2 ...\n");
      break;
    }
    case 3:
    {
      printk("init pin of group 3 ...\n");
      break;
    }
  }
  
  return 0;
}
 
static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{
  //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
  printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(led_rsc->pin), PIN(led_rsc->pin));
 
  switch(GROUP(led_rsc->pin))
  {
    case 0:
    {
      printk("set pin of group 0 ...\n");
      break;
    }
    case 1:
    {
      printk("set pin of group 1 ...\n");
      break;
    }
    case 2:
    {
      printk("set pin of group 2 ...\n");
      break;
    }
    case 3:
    {
      printk("set pin of group 3 ...\n");
      break;
    }
  }
 
  return 0;
}
 
static struct led_operations board_demo_led_opr = {
  .init = board_demo_led_init,
  .ctl  = board_demo_led_ctl,
};
 
struct led_operations *get_board_led_opr(void)
{
  return &board_demo_led_opr;
}
 

第26行:先获得 board_A_led.c 实现的 led_resource 结构体

第29~52行:查看初始化的GPIO哪一组

printk("init gpio: group %d, pin %d\n", GROUP(led_rsc->pin), PIN(led_rsc->pin));
  switch(GROUP(led_rsc->pin))
  {
    case 0:
    {
      printk("init pin of group 0 ...\n");
      break;
    }
    case 1:
    {
      printk("init pin of group 1 ...\n");
      break;
    }
    case 2:
    {
      printk("init pin of group 2 ...\n");
      break;
    }
    case 3:
    {
      printk("init pin of group 3 ...\n");
      break;
    }
  }

第60~84行:查看控制GPIO哪一组

  printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(led_rsc->pin), PIN(led_rsc->pin));
 
  switch(GROUP(led_rsc->pin))
  {
    case 0:
    {
      printk("set pin of group 0 ...\n");
      break;
    }
    case 1:
    {
      printk("set pin of group 1 ...\n");
      break;
    }
    case 2:
    {
      printk("set pin of group 2 ...\n");
      break;
    }
    case 3:
    {
      printk("set pin of group 3 ...\n");
      break;
    }
  }

2.4 Makefile

 
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册
 
KERN_DIR = /home/book/100ask_roc-rk3399-pc/linux-4.4
 
all:
  make -C $(KERN_DIR) M=`pwd` modules 
  $(CROSS_COMPILE)gcc -o ledtest ledtest.c 
 
clean:
  make -C $(KERN_DIR) M=`pwd` modules clean
  rm -rf modules.order
  rm -f ledtest
 
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o
 
# leddrv.c board_demo.c 编译成 100ask.ko
100ask_led-y := leddrv.o chip_demo_gpio.o board_A_led.o
obj-m += 100ask_led.o

2.5 ledtest

 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
 
/*
 * ./ledtest /dev/100ask_led0 on
 * ./ledtest /dev/100ask_led0 off
 */
int main(int argc, char **argv)
{
  int fd;
  char status;
  
  /* 1. 判断参数 */
  if (argc != 3) 
  {
    printf("Usage: %s <dev> <on | off>\n", argv[0]);
    return -1;
  }
 
  /* 2. 打开文件 */
  fd = open(argv[1], O_RDWR);
  if (fd == -1)
  {
    printf("can not open file %s\n", argv[1]);
    return -1;
  }
 
  /* 3. 写文件 */
  if (0 == strcmp(argv[2], "on"))
  {
    status = 1;
    write(fd, &status, 1);
  }
  else
  {
    status = 0;
    write(fd, &status, 1);
  }
  
  close(fd);
  
  return 0;
}
 
 

三、上机测试  

3.1编译

编译程序,把代码上传代服务器后执行 make 命令。

cp 100ask_led.ko ledtest ~/nfs_rootfs/

3.2 挂载到开发板

在开发板上挂载 NFS

3.3 测试

最后在开发板上加载驱动程序,执行测试程序,如下:

echo "7    4    1    7" > /proc/sys/kernel/printk      //调整内核printk的打印级别

3.4 结果

目录
相关文章
|
6天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
1月前
|
Linux API 调度
Linux系统驱动跟裸机驱动的区别
Linux系统驱动跟裸机驱动的区别
29 0
|
1月前
|
Linux 应用服务中间件 Apache
Linux Apache服务详解——Apache服务基础知识
Linux Apache服务详解——Apache服务基础知识
35 2
|
1月前
|
域名解析 缓存 网络协议
Linux DNS服务详解——DNS基础知识
Linux DNS服务详解——DNS基础知识
81 1
|
1月前
|
Linux C语言 SoC
嵌入式linux总线设备驱动模型分析
嵌入式linux总线设备驱动模型分析
32 1
|
1月前
|
存储 缓存 Linux
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
36 0
|
12天前
|
Linux Go
Linux命令Top 100驱动人生! 面试必备
探索Linux命令不再迷茫!本文分10部分详解20个基础命令,带你由浅入深掌握文件、目录管理和文本处理。 [1]: <https://cloud.tencent.com/developer/article/2396114> [2]: <https://pan.quark.cn/s/865a0bbd5720> [3]: <https://yv4kfv1n3j.feishu.cn/docx/MRyxdaqz8ow5RjxyL1ucrvOYnnH>
64 0
|
23天前
|
Linux API C语言
FFmpeg开发笔记(一)搭建Linux系统的开发环境
本文指导初学者如何在Linux上搭建FFmpeg开发环境。首先,由于FFmpeg依赖第三方库,可以免去编译源码的复杂过程,直接安装预编译的FFmpeg动态库。推荐网站<https://github.com/BtbN/FFmpeg-Builds/releases>提供适用于不同系统的FFmpeg包。但在安装前,需确保系统有不低于2.22版本的glibc库。详细步骤包括下载glibc-2.23源码,配置、编译和安装。接着,下载Linux版FFmpeg安装包,解压至/usr/local/ffmpeg,并设置环境变量。最后编写和编译简单的C或C++测试程序验证FFmpeg环境是否正确配置。
40 8
FFmpeg开发笔记(一)搭建Linux系统的开发环境
|
25天前
|
Linux
Linux驱动运行灯 Heartbeat
Linux驱动运行灯 Heartbeat
12 0
|
1月前
|
存储 缓存 Linux
探秘Linux块设备驱动程序:成为内核开发大师的第一步
探秘Linux块设备驱动程序:成为内核开发大师的第一步
94 0