主要内容: 中断开发--按键驱动
1,linux中file, cdev, inode之间的关系
2,新的注册字符设备的方式
3, 中断申请
4, 文件io模型实现之阻塞和非阻塞
-----------------------------------------
linux中file和inode结构体的关系:
struct file对象:描述进程中打开open一个文件的信息:文件名,标志(可读写),文件偏移
open("/dev/led", O_RDWR|O_CREAT, 0666);
struct file {
struct path f_path;
const struct file_operations *f_op;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos; //文件偏移
void *private_data;//万能指针
}
struct cdev对象:描述一个字符设备对象信息(设备号+文件操作对象),任何一个字符设备驱动都有该对象,
struct cdev {
struct kobject kobj;// 基类
struct module *owner;
const struct file_operations *ops;//文件操作对象
struct list_head list;// 链表
dev_t dev; //设备号
unsigned int count;
};
struct inode对象: 描述文件系统中的某个文件的属性(文件权限,类型,uid,gid,修改时间等)
struct inode {
umode_t i_mode;
uid_t i_uid;
gid_t i_gid;
dev_t i_rdev; //设备号
const struct file_operations *i_fop;
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
}
int led_drv_open(struct inode *inode, struct file *filp)
{
printk("-------^_^ %s-------\n", __FUNCTION__);
static int a = 38;
filp->private_data = &a;
xxxxxxxx
}
long led_drv_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
// 通过filp找到inode
struct inode *node = filp->f_path.dentry->d_inode;
//通过inode获取到注册设备号
int minor = iminor(node);
int major = imajor(node);
//获取到数据
int *p = (int *)filp->private_data;
xxxxxxxx
}
2,新的注册字符设备的方式
cdev_alloc(void)
cdev_init(struct cdev * cdev,const struct file_operations * fops)
cdev_add(struct cdev * p,dev_t dev,unsigned count)
//静态申请设备号---仅仅是得到一个设备号而已
// 参数1---设备号
// 参数2---设备的个数
// 参数3--描述字符串--/proc/devices
//返回负数出错
dev_t devno = MKDEV(260, 0);
ret = register_chrdev_region(devno, 1, "key_new_drv");
if(ret < 0)
{
printk(KERN_ERR"register_chrdev_region error\n");
goto err_free;
}
// 动态分配一个struct cdev 对象
key_dev->cdev = cdev_alloc();
//初始化cdev中fops
cdev_init(key_dev->cdev, &key_fops);
//将当前cdev注册到系统中去
//参数2---设备号
//参数3--设备的个数,一般都填1
cdev_add(key_dev->cdev, devno, 1);
3,申请中断:
// 参数1---中断号
// 中断号获取: IRQ_EINT(1)或者去找irqs.h
//参数2--中断的处理方法irqreturn_t (*irq_handler_t)(int, void *);
//参数3--中断的触发方式
/*
#define IRQF_TRIGGER_NONE 0x00000000 //内部中断触发
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
*/
//参数3--表示一个字符串--自定义--/proc/interrupts
// 参数4--参数给第二个参数的数据
// 正确返回0
request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char * name,void * dev)
中断处理函数:
irqreturn_t key_irq_svc(int irqno, void *dev_id)
{
return IRQ_HANDLED;
}
//参数1--中断号码
//参数2--和request_irq中最后一个参数保持一致
free_irq(int irqno,void * dev_id)
实现阻塞: 默认情况,大部分的函数默认都是阻塞
scanf()-- fgets()
accept(); read/recv/recvfrom
0---需要一个等待队列头
struct __wait_queue_head wait_queue_head_t;
init_waitqueue_head(struct wait_queue_head_t *q)
// 参数1---表示等待队列头
// 参数2---表示一个条件--如果为假,就在此休眠,如果为真,就不休眠
1,根据条件可以让进程进入到休眠状态
wait_event_interruptible(struct wait_queue_head_t wq, int condition)
2, 资源可达的时候需要唤醒
wake_up_interruptible(wait_queue_head_t *q)
/***************************************************************************************************/
中断下半部的实现方法:
waitqueue,tasklet,work,软中断
tasklet:
struct tasklet_struct
tasklet_init
tasklet_schdule
tasklet_kill
工作队列:
struct work_struct *work
INIT_WORK
schdule_work
等待队列:
struct __wait_queue_head
init_waitqueue_head
wait_event_interruptible
wake_up_interruptible
/***************************************************************************************************/
非阻塞:
在应用中设定非阻塞模式:
int fd = open("/dev/key0", O_RDWR|O_NONBLOCK)
read() 有数据就得到数据,没有数据就得到一个出错码--EAGAIN;
-----------------------------------------------------------
驱动:
xxx_read
{
区分阻塞还是非阻塞
if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data)
{
return -EAGAIN;
}
}
代码示例:
/*******************key_app.c***********************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/input.h>
struct key_event{
int code; //按键的名字---下键,回车键,ESC--KEY_ESC
int value; //按键的状态---按下和抬起--1/0
};
int main(int argc, char *argv[])
{
int ret;
struct key_event event;
int fd = open("/dev/key0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
while(1)
{
ret = read(fd, &event, sizeof(struct key_event));
if(ret < 0)
{
perror("read");
exit(1);
}
if(event.code == KEY_DOWN)
{
if(event.value)
{
printf("<APP>-------KEY_DOWN pressed\n");
}else{
printf("<APP>-------KEY_DOWN up\n");
}
}
}
return 0;
}
/***********************key_drv.c***************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <asm/io.h>
#include <asm/uaccess.h>
//#define USE_STATIC_MAJOR 1
#define KEY_MAJOR 260
//设计一个按键数据包
struct key_event{
int code; //按键的名字---下键,回车键,ESC--KEY_ESC
int value; //按键的状态---按下和抬起--1/0
};
//设计一个全局的设备对象类
struct s5pv210_key{
dev_t devno;
int irqno;
struct cdev *cdev;
struct class *cls;
struct device *dev;
struct key_event event;
wait_queue_head_t wq_head;
int have_data; //表示一个标志,是否有数据
};
//声明一个对象
struct s5pv210_key *key_dev;
int key_drv_open(struct inode *inode, struct file *filp)
{
printk("-------^_^ %s-------\n", __FUNCTION__);
memset(&key_dev->event, 0, sizeof(struct key_event));
key_dev->have_data = 0;
return 0;
}
// write(fd, buf, size);
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;
}
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret;
//区分阻塞还是非阻塞
if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data)
{
return -EAGAIN;
}
//判断是否有资源
wait_event_interruptible(key_dev->wq_head, key_dev->have_data);
//将中断处理函数获取到数据给用户
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
//清空event
memset(&key_dev->event, 0, sizeof(struct key_event));
// 拷贝数据之后表示没有数据
key_dev->have_data = 0;
return count;
}
const struct file_operations key_fops = {
.open = key_drv_open,
.write = key_drv_write,
.read = key_drv_read,
.release = key_drv_close,
};
//表示当前的中断号码
irqreturn_t key_irq_svc(int irqno, void *dev_id)
{
printk("-------^_^ %s-------\n", __FUNCTION__);
// 区分是按下还是抬起
int value = gpio_get_value(S5PV210_GPH0(1));
if(value){
//抬起
printk("<KERNEL>-------KEY_DOWN pressed\n");
key_dev->event.code = KEY_DOWN;
key_dev->event.value = 0;
}else{
printk("<KERNEL>-------KEY_DOWN up\n");
key_dev->event.code = KEY_DOWN;
key_dev->event.value = 1;
}
//此时就有数据
key_dev->have_data = 1;
//唤醒等待队列
wake_up_interruptible(&key_dev->wq_head);
return IRQ_HANDLED;
}
static int __init key_drv_init(void)
{
/*
编写驱动的套路
0, 实例化全局的设备对象-- kzalloc
1, 申请主设备号---register_chrdev
2, 自动创建设备节点---class_create, device_create
3, 初始化硬件--ioremap
4,实现 file_operation
*/
// 模块加载函数中主要完成系统资源的申请
printk("-------^_^ %s-------\n", __FUNCTION__);
int ret;
// 0, 实例化全局的设备对象
//参数1---分配大小
//参数2--分配的标志, GFP_KERNEL--如果当前暂时没有内存,会尝试等待
key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);
if(key_dev == NULL)
{
printk(KERN_ERR"kzalloc error\n");
return -ENOMEM;
}
// 1, 申请主设备号
#ifdef USE_STATIC_MAJOR
//静态申请设备号---仅仅是得到一个设备号而已
// 参数1---设备号
// 参数2---设备的个数
// 参数3--描述字符串--/proc/devices
//返回负数出错
key_dev->devno = MKDEV(KEY_MAJOR, 32);
ret = register_chrdev_region(devno, 1, "key_new_drv");
if(ret < 0)
{
printk(KERN_ERR"register_chrdev_region error\n");
goto err_free;
}
#else
// 参数1---系统动态分配之后的设备号
// 参数2--次设备号的起始值
// 参数3---设备的个数
// 参数4--描述字符串--/proc/devices
//正确返回0
ret = alloc_chrdev_region(&key_dev->devno,32, 1, "key_new_drv");
if(ret != 0)
{
printk(KERN_ERR"register_chrdev_region error\n");
goto err_free;
}
#endif
// 动态分配一个struct cdev 对象
key_dev->cdev = cdev_alloc();
//初始化cdev中fops
cdev_init(key_dev->cdev, &key_fops);
//将当前cdev注册到系统中去
//参数2---设备号
//参数3--设备的个数,一般都填1
cdev_add(key_dev->cdev, key_dev->devno, 1);
// 2 ---自动创建设备节点
//创建一个类
// 参数1---当前模块--THIS_MODULE
// 参数2---字符串,表示类的名字
//返回值--struct class指针类型
key_dev->cls = class_create(THIS_MODULE,"key_cls");
if(IS_ERR(key_dev->cls))
{
printk("class_create error\n");
ret = PTR_ERR(key_dev->cls);
goto err_unregister;
}
//创建一个设备节点
// 参数1---class_create返回的指针
// 参数2---该设备非父类--一般都是填NULL
//参数3--设备号--包含了主设备号major和次设备号minor
//参数4---私有数据指针---一般都是填NULL
//参数5---设备节点的名字
//结果 /dev/led
// 返回值--struct device指针
key_dev->dev = device_create(key_dev->cls, NULL,key_dev->devno, NULL, "key%d", 0);
if(IS_ERR(key_dev->dev))
{
printk("device_create error\n");
ret = PTR_ERR(key_dev->dev);
goto err_class_destroy;
}
// 3, 初始化硬件---要么映射地址/申请中断
// 参数1---中断号
// 中断号获取: IRQ_EINT(1)或者去找irqs.h
key_dev->irqno = IRQ_EINT(1);
ret = request_irq(key_dev->irqno, key_irq_svc, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"eint1-keydown", NULL);
if(ret != 0)
{
printk("request_irq error\n");
ret = -EBUSY;
goto err_device_destrory;
}
//初始化等待队列头
init_waitqueue_head(&key_dev->wq_head);
return 0;
err_device_destrory:
device_destroy(key_dev->cls, key_dev->devno);
err_class_destroy:
class_destroy(key_dev->cls);
err_unregister:
cdev_del(key_dev->cdev);
unregister_chrdev_region(key_dev->devno, 1);
err_free:
kfree(key_dev);
return ret;
}
static void __exit key_drv_exit(void)
{
printk("-------^_^ %s-------\n", __FUNCTION__);
//释放中断
//参数1--中断号码
//参数2--和request_irq中最后一个参数保持一致
free_irq(key_dev->irqno, NULL);
// 模块卸载函数中主要完成系统资源的释放
device_destroy(key_dev->cls, key_dev->devno);
class_destroy(key_dev->cls);
cdev_del(key_dev->cdev);
unregister_chrdev_region(key_dev->devno, 1);
kfree(key_dev);
}
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("[email protected]");