字符设备驱动-按键读入

此博客主要学习目标:

1点:学会字符设备的注册
2点:原子操作的实际应用
3点:file_operations结构体的实际应用

1、在dts的根节点上添加:

key {
		status = "okay";			
		compatible = "rockchip,key";
		//pinctrl-names = "default";
	    //pinctrl-0 = <&key_pinctrl_gpio>;
		key-gpio = <&gpio1 RK_PB5 GPIO_ACTIVE_LOW>;
    };

2、在源码的kernel\drivers中建立一个test文件夹存放key.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/bitmap.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/atomic.h>
/*设备号的个数*/
#define KEY_CNT 1 
#define KEY_NAME "key"

/* 定义按键值 */
#define KEY0VALUE 1 
/* 无效的按键值 */
#define INVAKEY   0 

/*key 设备结构体*/
struct key_dev{
	         dev_t devid;          /*通过成员dev_t来定义设备号(分为主、次设备号)
				                         以确定字符设备的唯一性*/
			struct cdev cdev;      //字符设备
			struct class *class;   //
			struct device *device; //设备
			int major;             //主设备号
			int minor;             //次设备号
			struct device_node *nd;//设备节点
			int key_gpio;          //该驱动使用的GPIO的编号
			atomic64_t keyvalue;   //按键值,处理器为32位的则使用atomic_t
};
struct key_dev keydev;//定义一个key设备

	/*
	 * 初始化按键 IO,open 函数打开驱动的时候
	 * 初始化按键所使用的 GPIO 引脚。
	 * param : 
	 * return : 
	 */
static int keyio_init(void){
	 keydev.nd = of_find_node_by_path("/key");
	 if (keydev.nd== NULL) {
		return -EINVAL;
	}

	keydev.key_gpio = of_get_named_gpio(keydev.nd ,"key-gpio", 0);
	if (keydev.key_gpio < 0) {
		printk("can't get key0\r\n");
		return -EINVAL;
	}
	printk("key_gpio=%d\r\n", keydev.key_gpio);
	/* 初始化 key 所使用的 IO */
	gpio_request(keydev.key_gpio, "key0"); /* 请求 IO */
	gpio_direction_input(keydev.key_gpio); /* 设置为输入 */
	return 0;
}

   /*
	*file_operations的int (*open)成员函数填写
	* 功能     : 打开设备
	* 参数inode: 传递给驱动的 inode
	* 参数filp :  设备文件,file 结构体有个叫做 private_data 的成员变量
	*             一般在 open 的时候将 private_data 指向设备结构体。
	* return   : 0  成功;其他 失败
	*/
static int key_open(struct inode *inode, struct file *filp)
	{
		int ret = 0;
		filp->private_data = &keydev; /* 设置私有数据 */
		ret = keyio_init(); /* 初始化按键 IO */
		if (ret < 0) {
			return ret;
		}
		return 0;
	}

   /*
	* file_operations的ssize_t (*read)成员函数填写
	* 功能     :从设备读取数据
	* 参数filp : 要打开的设备文件(文件描述符)
	* 参数buf  : 返回给用户空间的数据缓冲区
	* 参数cnt  : 要读取的数据长度
	* 参数offt : 相对于文件首地址的偏移
	* return   : 读取的字节数,如果为负值,表示读取失败
    */
static ssize_t key_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt){
	int ret = 0;
	unsigned char value;
	struct key_dev *dev = filp->private_data;

	if (gpio_get_value(dev->key_gpio) == 0) { /* key0 按下 */
		while(!gpio_get_value(dev->key_gpio)); /* 等待按键释放 */
		atomic64_set(&dev->keyvalue, KEY0VALUE);
	   } 
	else { /* 无效的按键值 */
		atomic64_set(&dev->keyvalue, INVAKEY);
	   }

	value = atomic64_read(&dev->keyvalue); /* 保存按键值 */
	ret = copy_to_user(buf, &value, sizeof(value));
	return ret;
}
	/* 
	 * 设备操作函数 file_operations,用户使用file_operations结构访
	 * 问驱动程序的函数这个结构的每一个成员的名字都对应着一个调用 
	 * 成员1:ssize_t (*read)  (struct file *, char __user *, size_t, loff_t *);
     * 成员2:ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	 * 成员3:int (*open) (struct inode *, struct file *);
	 * *****
	 * ***
	 */
static struct file_operations key_fops  = {
	                .owner = THIS_MODULE,
					.open  = key_open,
					.read  = key_read,
};
/* **************************** 驱动出口函数  ********************************* */
 static void __exit mykey_exit(void){
     /* 注销字符设备驱动 */
	 cdev_del(&keydev.cdev); /* 删除 cdev */
	 unregister_chrdev_region(keydev.devid, KEY_CNT); /* 注销设备号 */
	 device_destroy(keydev.class, keydev.devid);
	 class_destroy(keydev.class);
	 printk("mykey_exit exit!!!......\n");
 };
/* **************************** 驱动入口函数  ********************************* */
static int __init mykey_init(void){
	 
	 int err; 
	 
	/*初始化一个原子变量,因为是64位的RAM所以使用atomic64_set*/
	atomic64_set(&keydev.keyvalue,INVAKEY);
	
/* *******************************1、创建设备号 ******************************* */
   /* int alloc_chrdev_region(dev_t *dev, unsigned baseminor, 
							 unsigned count,const char *name)
	* 是让内核分配给我们一个尚未使用的主设备号不是由我们自己指定的
	* 该函数的四个传参意义如下:
	* dev       :alloc_chrdev_region函数向内核申请下来的设备号
	* baseminor :次设备号的起始
	* count     :申请次设备号的个数
	* name      :执行 cat /proc/devices显示的名称 
	*/
	err = alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME); 
	keydev.major = MAJOR(keydev.devid); /* 获取分配设备号的主设备号 */
	keydev.minor = MINOR(keydev.devid); /* 获取分配设备号的次设备号 */	
	if (err < 0)   {  
        printk("alloc_chrdev_region can't get major %d\n", keydev.major);   
        return err; 
		goto fail;
     }		
    else {
		printk("alloc_chrdev_region get major!!\n");
		//打印分配的主设备号
		printk("MAJOR Number is %d\n",keydev.major);
		//打印分配的次设备号
		printk("MINOR Number is %d\n",keydev.minor);
	}
/* *******************************2、初始化cdev ******************************* */
	/* cdev_alloc函数与cdev_init函数针都可以初始化cdev
	 * cdev_alloc函数针:对于需要空间申请的操作
	 * cdev_init函数针: 对于不需要空间申请的操作
	 * 如果定义的是一个指针,那么需要使用cdev_alloc函数并在其后做一个ops的赋值操作
	 * 如果定义的是一个结构体,那么只需要使用cdev_init函数就可以了
	 */    
    keydev.cdev.owner = THIS_MODULE;
	cdev_init(&keydev.cdev, &key_fops);//初始化list,将字符结构体与文件结构体对接
	
/* *******************************3、注册字符设备 ****************************** */
   
     err = cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);//添加字符设备      
     if(err){  
        printk("Error %d adding cdev\n", err);   
        goto fail;   
     }
     else{
		 printk("success... %d adding cdev\n", err);  
	 } 

/* *********************************4、创建类 ********************************** */
	keydev.class = class_create(THIS_MODULE, KEY_NAME);
	if (IS_ERR(keydev.class)) {
		printk("fail adding keydev.class\n");
		return PTR_ERR(keydev.class);
	} 
	else 
		 printk("success adding keydev.class\n");
/* *********************************5、创建设备 ******************************** */	
    keydev.device = device_create(keydev.class, NULL, keydev.devid,NULL, KEY_NAME);
	 if (IS_ERR(keydev.device)) {
		return PTR_ERR(keydev.device);
		printk("fail adding keydev.device_create\n");
	 }
	 else 
		 printk("success adding keydev.device_create\n");
	 
	 printk("mykey_init secess......\n");	
     return 0;   
fail:   
     mykey_exit();  
     return err;  
}  

	/*
	 * 字符设备注册的流程:
	 * 第一:向内核申请一个字符设备下来( 本程序使用:alloc_chrdev_region )
	 * 第二:对申请的字符设备进行初始化 ( cdev_init )
	 * 第三:向内核注册字符设备 ( cdev_add )
	 */
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("pangxiwen");

对于MakefileKconfig的配置就不讲了。。。。。
3、编写一个测试的APP----->keyApp.c

 #include "stdio.h"
 #include "unistd.h"
 #include "sys/types.h"
 #include "sys/stat.h"
 #include "fcntl.h"
 #include "stdlib.h"
 #include "string.h"
 /***************************************************************
描述 : 按键输入测试应用程序
***************************************************************/

/* 定义按键值 */
#define KEY0VALUE 1
#define INVAKEY 0X00

/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
	int fd, ret;
	char *filename;
	unsigned char keyvalue;

	if(argc != 2){
		printf("Error Usage!\r\n");
		return -1;
	}
	filename = argv[1];

/* 打开 key 驱动 */
		fd = open(filename, O_RDWR);
		printf("open the key device seccess......\n");
		if(fd < 0){
			 printf("file %s open failed!\r\n", argv[1]);
			 return -1;
			}

/* 循环读取按键值数据! */
	while(1) {
		read(fd, &keyvalue, sizeof(keyvalue));
			if (keyvalue == KEY0VALUE) { /* KEY */
					printf("KEY0 Press, value = %d \n", keyvalue);/* 按下 */
				}
			}
		ret= close(fd); /* 关闭文件 */
		if(ret < 0){
					printf("file %s close failed!\r\n", argv[1]);
					return -1;
					}
		return 0;
}

在服务器上或者自己的linux主机上使用交叉编译工具链

arm-linux-gnueabihf-gcc keyApp.c -o keyApp -static

这里有一个坑,如果不加后面的-static这个参数,编译出来的keyApp运行不起来。

将生成的key.kokeyApp这两个文件利用adb push 到板子的system/bin下面运行keyApp是要点adb root,或者在打印终端su一下。

4、测试驱动
第1步:

key.ko安装一下:insmod key.ko,加载驱动时的打印驱动如下:

rk3326_m2g:/system/bin # insmod key.ko
[  886.286993] alloc_chrdev_region get major!!
[  886.287050] MAJOR Number is 234
[  886.287059] MINOR Number is 0
[  886.287073] success... 0 adding cdev
[  886.287869] success adding keydev.class
[  886.288570] success adding keydev.device_create
[  886.288605] mykey_init secess......

第2步:
运行测试APP: ./keyApp /dev/key会打印:

[  972.059222] key_gpio=45
open the key device seccess......

按下按键打印:KEY0 Press, value = 1

 130|rk3326_m2g:/system/bin # ./keyApp  /dev/key                                 
[  972.059222] key_gpio=45
open the key device seccess......
KEY0 Press, value = 1 
KEY0 Press, value = 1 
KEY0 Press, value = 1 
KEY0 Press, value = 1 
KEY0 Press, value = 1 
KEY0 Press, value = 1 
KEY0 Press, value = 1 
KEY0 Press, value = 1 
KEY0 Press, value = 1 

卸载key.ko驱动

rk3326_m2g:/system/bin # rmmod key.ko
[ 3322.925590] mykey_exit exit!!!......
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

那肯定是很多年以后!

你的鼓励就我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值