上一节我们只是能够正常的触发中断,但是程序并不知道我们是按下还是抬起,并且作为字符驱动也没有最基本的open read等函数,应用层是无法操作我们的按键驱动的,那么我们在上节的基础上进行完善。
一. 实现字符设备驱动的框架
1,设定一个全局的设备对象(struct key_desc)
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
2,申请主设备号
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
3,创建设备节点文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major,0), NULL, "key0");
4.硬件的初始化
二. 驱动中将硬件所产生的数据传递给用户
a,硬件如何获取数据
key: 按下和抬起: 1/0
读取key对应的gpio的状态,可以判断按下还是抬起
读取key对应gpio的寄存器--数据寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
b,驱动如何传递给用户
在中断处理中填充数据:
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
在xxx_read中数据传递给用户
ret = copy_to_user(buf, &key_dev->event, count);
c,用户如何拿到--编写应用程序
while(1)
{
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else{
printf("APP__ key enter up\n");
}
}
}
三.程序编写
驱动
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#define GPXCON_REG 0x11000C20
#define KEY_ENTER 28
// 设计一个描述按键的数据的对象
struct key_event{
int code; // 表示按键的类型
int value; // 表示按下还是抬起 1 / 0
};
//设计一个全局设备对象--描述按键信息
struct key_desc{
unsigned int dev_major;
struct class *cls;
struct device *dev;
int irqno;
void *reg_base;
struct key_event event;
};
struct key_desc *key_dev;
int get_irqno_from_node(void)
{
// 获取到设备树中到节点
struct device_node *np = of_find_node_by_path("/key_int_node");
if(np){
printk("find node ok\n");
}else{
printk("find node failed\n");
}
// 通过节点去获取到中断号码
int irqno = irq_of_parse_and_map(np, 0);
printk("irqno = %d\n", irqno);
return irqno;
}
irqreturn_t key_irq_handler(int irqno, void *devid)
{
printk("-------%s-------------\n", __FUNCTION__);
//读取数据寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
if(value){ // 抬起
printk("key3 up\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
}else{//按下
printk("key3 pressed\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 1;
}
return IRQ_HANDLED;
}
int key_drv_open(struct inode *inode, struct file *filp)
{
printk("-------%s-------------\n", __FUNCTION__);
return 0;
}
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------------\n", __FUNCTION__);
int ret;
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
// 传递给用户数据之后,将数据清除掉
memset(&key_dev->event, 0, sizeof(key_dev->event));
return count;
}
ssize_t key_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
printk("-------%s-------------\n", __FUNCTION__);
return 0;
}
int key_drv_close (struct inode *inode, struct file *filp)
{
printk("-------%s-------------\n", __FUNCTION__);
return 0;
}
const struct file_operations key_fops = {
.open = key_drv_open,
.read = key_drv_read,
.write = key_drv_write,
.release = key_drv_close,
};
static int __init key_drv_init(void)
{
int ret;
// 1,设定一个全局的设备对象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
// 2,申请主设备号
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
// 3,创建设备节点文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL,
MKDEV(key_dev->dev_major,0), NULL, "key0");
// 4,硬件的初始化--地址映射或者中断申请
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return ret;
}
// a,硬件如何获取数据--gpx1
key_dev->reg_base = ioremap(GPXCON_REG, 8);
return 0;
}
static void __exit key_drv_exit(void)
{
iounmap(key_dev->reg_base);
free_irq(key_dev->irqno, NULL);
device_destroy(key_dev->cls, MKDEV(key_dev->dev_major,0));
class_destroy(key_dev->cls);
unregister_chrdev(key_dev->dev_major, "key_drv");
kfree(key_dev);
}
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
应用程序
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
struct key_event{
int code; // 表示按键的类型: home, esc, Q,W,E,R,T, ENTER
int value; // 表示按下还是抬起 1 / 0
};
#define KEY_ENTER 28
int main(int argc, char *argv[])
{
struct key_event event;
int fd = open("/dev/key0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
while(1)
{
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else{
printf("APP__ key enter up\n");
}
}
}
close(fd);
return 0;
}
进行编译,然后在终端下,装载驱动insmod key_dev.ko,并执行应用程序./key_test,按下按键,这里功能已经实现了
但是有个问题,通过ps,然后top命令查看一下资源占用情况,发现key的这个程序的资源占用非常的高,程序一直在read,当然占用高。
很容易想到,没数据了就休眠去把,就别老费劲的循环读状态了。
之后再对程序进行优化。