此博客主要学习目标:
第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");
对于Makefile
和Kconfig
的配置就不讲了。。。。。
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.ko
和keyApp
这两个文件利用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!!!......