当前位置:   article > 正文

linux驱动---platform框架的按键驱动_platform.h

platform.h

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


1. 编程步骤

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

1. 1 平台设备

1.1.1 定义资源

使用结构体struct resource来定义
在这里插入图片描述

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

  • end 表示资源的结束地址在这里插入图片描述

  • 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

1.1.2 实现平台资源结构体

在这里插入图片描述

  • 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

1.1.3 定义入口函数

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

// 入口函数
static int button_platform_device_init(void)
{
	// 注册设备信息
	// int platform_device_register(struct platform_device *pdev)
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

1.1.4 定义出口函数

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

// 出口函数
static void button_platform_device_exit(void)
{
	// 取消设备
	// void platform_device_unregister(struct platform_device *pdev)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

1.1.5 绑定出口和入口函数

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

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

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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

1.2.2 定义platform_driver结构体

在这里插入图片描述

并填充实现里面的函数,其中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
  • 3
  • 4
  • 5
  • 6
  • 7
1.2.2.2 remove函数

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

static int button_remove(struct platform_device *pdev)
  • 1

1.2.3 实现平台驱动入口函数

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

static int button_platform_driver_init(void)
{
	// 使用platform_driver_register来注册平台驱动
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5

1.2.4 实现平台驱动出口函数

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

static void button_platform_driver_exit(void)
{
	// platform_driver_unregister来注销平台驱动
}
  • 1
  • 2
  • 3
  • 4

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

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

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

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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171

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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3. 编译运行

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

我是直接用的nfs服务器
在这里插入图片描述

3.2 insmod来安装模块

在这里插入图片描述

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.3 报错处理经验

在遇到报错时,可以查看报错信息,一般都是有报错的堆栈信息,一级一级就能找到最初的报错函数,从而缩小范围。
也可以在函数内部使用printk打印一些信息来确定哪里出错了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/319114
推荐阅读
相关标签
  

闽ICP备14008679号