linux驱动---platform框架的按键驱动

简介: linux驱动---platform框架的按键驱动

普通的驱动就是实现file_operation结构体的各个函数,然后使用register_chrdev来注册,使用device_create来创建设备节点。这种方式是把驱动和设备资源合在一个C文件里面了,当我们的设备资源换一个板子或者换一个引脚的时候,我们需要重新修改驱动的各个函数。非常不方便。而platform架构的驱动是将设备资源和设备资源分开的,当我们的设备有更改的时候,只需要修改设备资源这个C文件即可。


1. 编程步骤

platform驱动分为两个部分,平台设备和平台驱动,所有需要两个C文件。其实套路都差不多,都是什么注册函数,入口函数,出口函数这些。

1. 1 平台设备

1.1.1 定义资源

使用结构体struct resource来定义


1b11a1ae710f40349484e3779df886c3.png

  • start 表示资源的其实地址,学过单片机的都知道,操作硬件无非就是操作寄存器地址。
  • end 表示资源的结束地址



18b4942a009a4a46b57854eb4d810a55.png

  • name 给该资源去一个名字
  • flags 表示资源的类型
    eg:
struct resource button_resource[] = {
  {
    .start = 0x20c406c,
    .end = 0x20c406c+3,        // IORESOURCE_MEM必须要加上end,不然会报错
    .name = "CCGR",
    .flags = IORESOURCE_MEM,
  },
  {
    .start = 0x229000c,
    .end = 0x229000c+3,
    .name = "SW_MUX_CTL",
    .flags = IORESOURCE_MEM,
  },
  {
    .start = 0x2290050,
    .end = 0x2290050+3,
    .name = "SW_PAD_CTL",
    .flags = IORESOURCE_MEM,
  },
  {
    .start = 0x20ac004,
    .end = 0x20ac004+3,
    .name = "GDIR",
    .flags = IORESOURCE_MEM,
  },
  {
    .start = 0x20ac008,
    .end = 0x20ac008+3,
    .name = "PSR",
    .flags = IORESOURCE_MEM,
  },
  {
    .start = 1,
    .end = 0,
    .name = "pin",
    .flags = IORESOURCE_IRQ,
  },
  {
    .start = 30,
    .end = 0,
    .name = "clock_offset",
    .flags = IORESOURCE_IRQ,
  },
};

1.1.2 实现平台资源结构体

5ff65dbe722d47fcb95af0e2ae4ee81d.png


  • name: 平台设备结构体的名字,相当重要,在平台驱动和平台设备匹配的时候比较的就是这个name
  • dev下面的release函数必须要指定,不然会报错,目前也还没用到该函数。
  • num_resources 表示资源的数量,即资源结构体的大小
  • resource 表示资源,即1.1.1中实现的资源结构体数组
    eg:
struct platform_device button_device = {
  .name = "my_button",
  .id = -1,
  .num_resources = ARRAY_SIZE(button_resource),
  .resource = button_resource,
  .dev = {
    .release = button_release,
  },
};

1.1.3 定义入口函数

其内部调用平台设备注册函数platform_device_register来注册前面实现的设备

// 入口函数
static int button_platform_device_init(void)
{
  // 注册设备信息
  // int platform_device_register(struct platform_device *pdev)
  return 0;
}

1.1.4 定义出口函数

其内部调用函数platform_device_unregister来取消对设备的注册

// 出口函数
static void button_platform_device_exit(void)
{
  // 取消设备
  // void platform_device_unregister(struct platform_device *pdev)
}

1.1.5 绑定出口和入口函数

当我们在终端使用ismod来加载模块或使用rmmod来卸载模块的时候,就会调用你绑定的函数来实现

module_init(button_platform_driver_init);
module_exit(button_platform_driver_exit);
MODULE_LICENSE("GPL");   // 这个必须要有

1.2 平台驱动

1.2.1 实现file_operation结构体的各个函数(按需实现)

hello驱动已经详细说了,这里不细说了。

// 这些函数名是自定的,我这里写的是按键相关的,所以就是button什么什么的,只不过参数和返回值不能改。
static ssize_t button_drv_read(struct file * file, char __user * buf, size_t size, loff_t *offset)
{
  // 读取寄存器的值
  // 通过copy_to_user(to, from, size)将结果返回给上层系统调用open
  // 返回值就是读取到的字节大小,由于目前遇到的都是字符设备,所以大小一般都是1
  return size;
}
static ssize_t button_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
  // 通过copy_from_user()获取到buf里面的数据
  // 写入寄存器
  // 返回值就是写入的字节大小
  return 0;
}
static int button_drv_open(struct inode *node, struct file *file)
{
  // 初始化设备
  return 0;
}
static int button_drv_close(struct inode *node, struct file *file)
{
  // 关闭设备
  return 0;
}
static struct file_operations button_opr = {
  .open = button_drv_open,
  .release = button_drv_close,
  .read = button_drv_read,
  .write = button_drv_write,
};

1.2.2 定义platform_driver结构体

75414d43f25f4fdb86945ca55ef637f8.png


并填充实现里面的函数,其中probe和remove必须实现

1.2.2.1 probe函数

该函数是在平台设备模块和平台驱动模块成功匹配后调用的,当匹配成功了表明设备资源我有了,设备驱动我也有了,所以该函数里面主要来注册驱动,读取资源,物理地址到虚拟地址的映射,添加设备节点等等初始化工作。

static int button_probe(struct platform_device *pdev)
{
  // 获取资源   platform_get_resource(pdev, IORESOURCE_MEM, i);
  // 地址映射   ioremap
  // 注册驱动   register_chrdev()
  // 添加设备节点 class_create()   device_create()
}

1.2.2.2 remove函数

remove函数时候probe函数相反的,当平台驱动和平台设备如何一个被注销时,该函数就会被执行。该函数主要工作就是注销驱动,释放虚拟地址的映射,注销设备节点

static int button_remove(struct platform_device *pdev)

1.2.3 实现平台驱动入口函数

该函数主要工作就是使用platform_driver_register来注册平台驱动

static int button_platform_driver_init(void)
{
  // 使用platform_driver_register来注册平台驱动
  return 0;
}

1.2.4 实现平台驱动出口函数

该函数主要工作就是使用platform_driver_unregister来注销平台驱动

static void button_platform_driver_exit(void)
{
  // platform_driver_unregister来注销平台驱动
}

1.2.5 绑定入口函数和出口函数

当使用ismod和rmmod指令的时候会调用指定的函数

module_init(button_platform_driver_init);  // 与ismod相关
module_exit(button_platform_driver_exit);  // 与rmmod先关
MODULE_LICENSE("GPL");   // 必须要有

2. 实例

实现简单地按键驱动(基于野火I.MX6ULL PRO开发板)

由于使用的轮询方式,且驱动写的很简单,造成了我按一下按钮,那个变量bnt直接飙升,不是一个数一个数的递增。

2.1 平台设备文件

button_device.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
static void button_release(struct device *dev)
{
  printk("%s\n", __FUNCTION__);
  return;
}
struct resource button_resource[] = {
  {
    .start = 0x20c406c,
    .end = 0x20c406c+3,        // IORESOURCE_MEM必须要加上end,不然会报错
    .name = "CCGR",
    .flags = IORESOURCE_MEM,
  },
  {
    .start = 0x229000c,
    .end = 0x229000c+3,
    .name = "SW_MUX_CTL",
    .flags = IORESOURCE_MEM,
  },
  {
    .start = 0x2290050,
    .end = 0x2290050+3,
    .name = "SW_PAD_CTL",
    .flags = IORESOURCE_MEM,
  },
  {
    .start = 0x20ac004,
    .end = 0x20ac004+3,
    .name = "GDIR",
    .flags = IORESOURCE_MEM,
  },
  {
    .start = 0x20ac008,
    .end = 0x20ac008+3,
    .name = "PSR",
    .flags = IORESOURCE_MEM,
  },
  {
    .start = 1,
    .end = 0,
    .name = "pin",
    .flags = IORESOURCE_IRQ,
  },
  {
    .start = 30,
    .end = 0,
    .name = "clock_offset",
    .flags = IORESOURCE_IRQ,
  },
};
struct platform_device button_device = {
  .name = "my_button",     // 这个名字必须要和设备资源结构体里面的name一致,不然这两个模块匹配不了
  .id = -1,
  .num_resources = ARRAY_SIZE(button_resource),
  .resource = button_resource,
  .dev = {
    .release = button_release,
  },
};
// 入口函数
static int button_platform_device_init(void)
{
  int ret = 0;
  printk("%s\n", __FUNCTION__);
  ret = platform_device_register(&button_device);  // 注册设备信息
  return ret;
}
// 出口函数
static void button_platform_device_exit(void)
{
  printk("%s\n", __FUNCTION__);
  platform_device_unregister(&button_device);
}
module_init(button_platform_device_init);
module_exit(button_platform_device_exit);
MODULE_LICENSE("GPL");

2.2 平台驱动文件

button_driver.c

#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 <asm/mach/map.h>
#include <linux/platform_device.h>
#include <asm/io.h>
static u32 *vi_ccgr;
static u32 *vi_mux_ctl;
static u32 *vi_pad_ctl;
static u32 *vi_gdir;
static u32 *vi_psr;
static u32 pin;
static u32 clock_offset;
static int major = 0;
static struct class *button_class;
static ssize_t button_drv_read(struct file * file, char __user * buf, size_t size, loff_t *offset)
{
  char status = 0;
  int ret;
  if (*vi_psr & (1<<pin)) {
    //printk("按键点击了\n");
    status = 1;
  } else {
    status = 0;
  }
  ret = copy_to_user(buf, &status, 1);
  return 1;
}
static ssize_t button_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  return 0;
}
static int button_drv_open(struct inode *node, struct file *file)
{
  // 初始化设备
  *(vi_ccgr) |= (3<<clock_offset);
  *(vi_mux_ctl) &= ~0xf;
  *(vi_mux_ctl) |= 0x5;
  *(vi_gdir) &= ~(1<<pin);
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  return 0;
}
static int button_drv_close(struct inode *node, struct file *file)
{
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  return 0;
}
static struct file_operations button_opr = {
  .open = button_drv_open,
  .release = button_drv_close,
  .read = button_drv_read,
  .write = button_drv_write,
};
static int button_probe(struct platform_device *pdev)
{
  int i = 0;
  struct resource *res;
  u32 ph_addr[5];
  int err;
  // 获取资源
  for (i=0;i<5;i++) {
    res = platform_get_resource(pdev, IORESOURCE_MEM, i);
    if (res == NULL) {
      printk("not get resource\n");
      return -1;
    }
    ph_addr[i] = res->start;
  }
  res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
  pin = res->start;
  res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
  clock_offset = res->start;
  // 内存映射
  vi_ccgr = ioremap(ph_addr[0], 4);
  vi_mux_ctl = ioremap(ph_addr[1], 4);
  vi_pad_ctl = ioremap(ph_addr[2], 4);
  vi_gdir = ioremap(ph_addr[3], 4);
  vi_psr = ioremap(ph_addr[4], 4);
  // 完成设备注册的操作
  // register_chrdev() class_create()  device_create()
  major = register_chrdev(0, "my_button", &button_opr);
  button_class = class_create(THIS_MODULE, "button_class");
  err = PTR_ERR(button_class);
  if (IS_ERR(button_class)) {
    unregister_chrdev(major, "my_button");
    return -1;
  }
  device_create(button_class, NULL, MKDEV(major, 0), NULL, "my_button");
  printk("%s\n", __FUNCTION__);
  return 0;
}
static int button_remove(struct platform_device *pdev)
{
  printk("%s\n", __FUNCTION__);
  // 释放虚拟内存
  iounmap(vi_ccgr);
  iounmap(vi_mux_ctl);
  iounmap(vi_pad_ctl);
  iounmap(vi_gdir);
  iounmap(vi_psr);
  // 完成设备取消注册的操作
  device_destroy(button_class, MKDEV(major,0));
  class_destroy(button_class);
  unregister_chrdev(major, "my_button");
  printk("%s\n", __FUNCTION__);
  return 0;
}
struct platform_driver button_driver = {
  .probe = button_probe,
  .remove = button_remove,
  .driver = {
    .name = "my_button",
  },
};
// 入口函数
static int button_platform_driver_init(void)
{
  int ret = 0;
  printk("%s\n", __FUNCTION__);
  ret = platform_driver_register(&button_driver);  // 注册设备信息
  return ret;
}
// 出口函数
static void button_platform_driver_exit(void)
{
  printk("%s\n", __FUNCTION__);
  platform_driver_unregister(&button_driver);  // 注册设备信息
}
module_init(button_platform_driver_init);
module_exit(button_platform_driver_exit);
MODULE_LICENSE("GPL");

2.3 应用层测试程序

button_test.c

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main(int argc, char **argv)
{
  int fd;
  char status = 0;
  int ret = 0;
  int bnt = 0;
  if (argc !=2 ) {
    printf("Usage: %s <dev-path>\n", argv[0]);
    return -1;
  }
  fd = open(argv[1], O_RDWR);
  if (fd < 0) {
    perror("open error");
    return -1;
  }
  while(1) {
    ret = read(fd, &status, 1);
    if (ret < 0) {
      perror("read error");
      return -1;
    } else if (ret == 1 && status == 1) {
      printf("button %d\n", bnt++);
    }
  }
  return 0;
}

2.3 Makefile文件

Makefile

ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-   // 该参数与你的交叉编译工具有关
export  ARCH  CROSS_COMPILE
KERN_DIR = /home/hxd/workdir/ebf_linux_kernel_6ull_depth1/build_image/build   // 该目录与你的编译好的内核目录有关
all:
  make -C $(KERN_DIR) M=`pwd` modules 
  $(CROSS_COMPILE)gcc -o button_test button_test.c 
clean:
  make -C $(KERN_DIR) M=`pwd` modules clean
  rm -rf modules.order
  rm -f button_test
obj-m += button_device.o button_driver.o

3. 编译运行

3.1 编译好后将得到的button_device.ko,button_driver.ko和button_test拷贝到开发板上。

我是直接用的nfs服务器

216c277ab83143e494f748e91840bf77.png

3.2 insmod来安装模块

bb32211dbd5f4ea291a6fbb4d02b6fa9.png


3.3 运行测试程序

4. 遇到的问题

4.1 在定义资源的时候(struct resource),必须要要给end赋值

4.2 虚拟地址没有映射成功

编译没有报错,insmod没有报错,但运行就会报错。是因为你忘了映射或者映射失败,造成空地址的操作,从而报错

[12003.876125] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[12003.885014] pgd = b0deced9
[12003.887738] [00000000] *pgd=00000000
[12003.894107] Internal error: Oops: 5 [#1] PREEMPT SMP ARM
[12003.899448] Modules linked in: button_driver(O) button_device(O) g_multi snd_soc_imx_wm8960 snd_soc_wm8960 goodix brcmfmac brcmutil snd_soc_fsl_sai snd_soc_fsl_asrc imx_pcm_dma_v2 snd_soc_core snd_pcm_dmaengine snd_pcm snd_timer [last unloaded: button_driver]
[12003.922477] CPU: 0 PID: 948 Comm: button_test Tainted: G           O      4.19.35-imx6 #1.2202stable
[12003.931615] Hardware name: Freescale i.MX6 UltraLite (Device Tree)
[12003.937821] PC is at button_drv_open+0x10/0x70 [button_driver]
[12003.943673] LR is at chrdev_open+0xac/0x194

4.3 报错处理经验

在遇到报错时,可以查看报错信息,一般都是有报错的堆栈信息,一级一级就能找到最初的报错函数,从而缩小范围。

也可以在函数内部使用printk打印一些信息来确定哪里出错了。


目录
相关文章
|
10天前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
|
28天前
|
Linux
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
|
28天前
|
Linux 程序员 芯片
【Linux驱动】普通字符设备驱动程序框架
【Linux驱动】普通字符设备驱动程序框架
|
10天前
|
Linux 开发者
Linux底层驱动社区饮水机系统详解
在Linux驱动开发中,入门时通常会关注驱动程序的三大核心步骤:入口函数、出口函数和声明许可证。这些步骤构成了驱动程序的基本结构,是驱动与内核交互的基础。下面是对这三个步骤的简要说明:
|
28天前
|
Linux
【Linux驱动学习(1)】USB与input子系统,linux统一设备模型,枚举,USB描述符深入剖析
【Linux驱动学习(1)】USB与input子系统,linux统一设备模型,枚举,USB描述符深入剖析
|
4天前
|
数据挖掘 Linux 数据处理
Linux命令sprof详解
**`sprof`是Linux下的共享库性能分析工具,补充`gprof`,专注分析`.profile`文件以识别性能瓶颈。通过调用次数、执行时间数据优化资源和代码。使用参数如`-F`、`-I`、`-d`进行定制化分析。示例:先设置`LD_PROFILE`环境变量,运行程序生成`.profile`,然后用`sprof`分析。注意需用`-g`编译程序,并在代表性的负载下分析。结合其他工具如`perf`、`valgrind`提升分析效果。**
|
4天前
|
存储 数据挖掘 Linux
Linux命令split详解:大文件处理的得力助手
`split`命令是Linux用于将大文件分割成小文件的工具,常用于日志处理、备份。它支持按行数(-l)、字节数(-b)分割,并能自定义输出文件名(-a, -d)。例如,`split -b 10M largefile.txt smallfile_`会按10MB切割`largefile.txt`,生成`smallfile_`开头的文件。注意确保磁盘空间充足,避免文件名冲突,并备份原始文件。结合其他命令使用,能提高文件管理效率。
|
1天前
|
关系型数据库 MySQL Linux
Linux命令systemctl详解
`systemctl`是Linux系统用于管理systemd服务的核心命令,它与systemd守护进程交互,实现启动、停止、重启服务及查看服务状态等功能。主要参数包括`start`、`stop`、`restart`、`status`、`enable`和`disable`等。例如,启动Apache服务使用`systemctl start httpd.service`,查看服务状态用`systemctl status &lt;service&gt;`。使用时需注意权限,服务名通常以`.service`结尾,但命令中可省略。最佳实践包括利用tab键补全、定期查看服务状态和合理配置服务自启。