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

目录
相关文章
|
22天前
|
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配置以确保顺利运行。
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
1月前
|
安全 Linux API
Linux设备模型统一:桥接硬件多样性与应用程序开发的关键
在Linux的宏大世界中,各种各样的硬件设备如星辰般繁多。从常见的USB设备到复杂的网络接口卡,从嵌入式设备到强大的服务器,Linux需要在这些差异极大的硬件上运行。这就引出了一个问题:Linux是如何统一这些不同硬件的设备模型的呢?本文将探讨Linux是如何针对不同的硬件统一设备模型的,这一统一的设备模型对于应用程序开发人员来说又有何意义。让我们一探究竟🕵️‍♂️。
Linux设备模型统一:桥接硬件多样性与应用程序开发的关键
|
23天前
|
Unix Linux Shell
FFmpeg开发笔记(八)Linux交叉编译Android的FFmpeg库
在Linux环境下交叉编译Android所需的FFmpeg so库,首先下载`android-ndk-r21e`,然后解压。接着,上传FFmpeg及相关库(如x264、freetype、lame)源码,修改相关sh文件,将`SYSTEM=windows-x86_64`改为`SYSTEM=linux-x86_64`并删除回车符。对x264的configure文件进行修改,然后编译x264。同样编译其他第三方库。设置环境变量`PKG_CONFIG_PATH`,最后在FFmpeg源码目录执行配置、编译和安装命令,生成的so文件复制到App工程指定目录。
FFmpeg开发笔记(八)Linux交叉编译Android的FFmpeg库
|
1天前
|
Linux Windows 编译器
|
1天前
|
网络协议 Linux 网络架构
|
1天前
|
Linux C语言
|
6天前
|
Linux
linux驱动层输出dev_dbg打印信息
linux驱动层输出dev_dbg打印信息
11 0
|
7天前
|
安全 Linux Android开发
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
该文介绍了如何在Linux服务器上交叉编译Android的FFmpeg库以支持HTTPS视频播放。首先,从GitHub下载openssl源码,解压后通过编译脚本`build_openssl.sh`生成64位静态库。接着,更新环境变量加载openssl,并编辑FFmpeg配置脚本`config_ffmpeg_openssl.sh`启用openssl支持。然后,编译安装FFmpeg。最后,将编译好的库文件导入App工程的相应目录,修改视频链接为HTTPS,App即可播放HTTPS在线视频。
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
|
13天前
|
前端开发 Linux iOS开发
【Flutter前端技术开发专栏】Flutter在桌面应用(Windows/macOS/Linux)的开发实践
【4月更文挑战第30天】Flutter扩展至桌面应用开发,允许开发者用同一代码库构建Windows、macOS和Linux应用,提高效率并保持平台一致性。创建桌面应用需指定目标平台,如`flutter create -t windows my_desktop_app`。开发中注意UI适配、性能优化、系统交互及测试部署。UI适配利用布局组件和`MediaQuery`,性能优化借助`PerformanceLogging`、`Isolate`和`compute`。
【Flutter前端技术开发专栏】Flutter在桌面应用(Windows/macOS/Linux)的开发实践
|
14天前
|
存储 监控 Linux
【专栏】在 Linux 中,了解已安装驱动器是系统管理的关键
【4月更文挑战第28天】在 Linux 中,了解已安装驱动器是系统管理的关键。本文介绍了三种方法:1) 使用 `lsblk` 命令显示设备名、大小和类型;2) `fdisk -l` 命令提供详细分区信息;3) `gnome-disks` 等系统管理工具展示驱动器信息。此外,还讨论了驱动器类型识别、挂载点概念及其应用。通过这些方法,用户能有效地监控和管理 Linux 系统中的驱动器。