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

目录
相关文章
|
29天前
|
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开发知识可参考相关书籍。
81 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
2月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
3月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
43 6
|
3月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
49 5
|
3月前
|
Ubuntu NoSQL Linux
Linux内核和驱动
Linux内核和驱动
29 2
|
3月前
|
数据采集 Linux
Linux源码阅读笔记20-PCI设备驱动详解
Linux源码阅读笔记20-PCI设备驱动详解
|
4月前
|
存储 JSON Linux
|
3月前
|
编解码 安全 Linux
基于arm64架构国产操作系统|Linux下的RTMP|RTSP低延时直播播放器开发探究
这段内容讲述了国产操作系统背景下,大牛直播SDK针对国产操作系统与Linux平台发布的RTMP/RTSP直播播放SDK。此SDK支持arm64架构,基于X协议输出视频,采用PulseAudio和Alsa Lib处理音频,具备实时静音、快照、缓冲时间设定等功能,并支持H.265编码格式。此外,提供了示例代码展示如何实现多实例播放器的创建与管理,包括窗口布局调整、事件监听、视频分辨率变化和实时快照回调等关键功能。这一技术实现有助于提高直播服务的稳定性和响应速度,适应国产操作系统在各行业中的应用需求。
109 3
|
4月前
|
Oracle 关系型数据库 Linux
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
通过这一连串的步骤,可以专业且有效地在Linux下为Qt编译Oracle驱动库 `libqsqloci.so`,使得Qt应用能够通过OCI与Oracle数据库进行交互。这些步骤适用于具备一定Linux和Qt经验的开发者,并且能够为需要使用Qt开发数据库应用的专业人士提供指导。
149 1
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
|
2月前
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】