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 结果

目录
相关文章
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
4956 77
|
Ubuntu 搜索推荐 Linux
详解Ubuntu的strings与grep命令:Linux开发的实用工具。
这就是Ubuntu中的strings和grep命令,透明且强大。我希望你喜欢这个神奇的世界,并能在你的Linux开发旅程上,通过它们找到你的方向。记住,你的电脑是你的舞台,在上面你可以做任何你想做的事,只要你敢于尝试。
542 32
|
11月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
561 0
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
895 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
Oracle 关系型数据库 Linux
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
通过这一连串的步骤,可以专业且有效地在Linux下为Qt编译Oracle驱动库 `libqsqloci.so`,使得Qt应用能够通过OCI与Oracle数据库进行交互。这些步骤适用于具备一定Linux和Qt经验的开发者,并且能够为需要使用Qt开发数据库应用的专业人士提供指导。
784 1
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
|
Web App开发 缓存 Linux
FFmpeg开发笔记(三十六)Linux环境安装SRS实现视频直播推流
《FFmpeg开发实战》书中第10章提及轻量级流媒体服务器MediaMTX,适合测试RTSP/RTMP协议,但不适合生产环境。推荐使用SRS或ZLMediaKit,其中SRS是国产开源实时视频服务器,支持多种流媒体协议。本文简述在华为欧拉系统上编译安装SRS和FFmpeg的步骤,包括安装依赖、下载源码、配置、编译以及启动SRS服务。此外,还展示了如何通过FFmpeg进行RTMP推流,并使用VLC播放器测试拉流。更多FFmpeg开发内容可参考相关书籍。
1424 2
FFmpeg开发笔记(三十六)Linux环境安装SRS实现视频直播推流
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
357 6
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
551 5
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】