先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
一 why
字符设备驱动在我们之前的博文中都已经有过比较详细的介绍了,为什么需要带上一个中断开发呢?我们知道CPU获取外设的数据有两种方式,一种是CPU主动去轮询外设,另一种就是外设通过中断的方式异步地将自己的数据上报给CPU。轮询的方式会造成CPU占用率过高浪费太多的资源,同时实时性得不到保障,中断是一种比较好的解决轮询缺点的解决方案,今天我们就来看看字符设备驱动中的中断开发
二 what
a. 驱动如何获取中断号
//1. 获取到设备树中的节点
of_find_node_by_path()
//2. 通过节点去获取到中断号码
irq_of_parse_and_map()
b. 驱动如何申请中断
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
参数1: 设备对应的中断号
参数2: 中断的处理函数
typedef 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
参数4: 中断的描述,自定义,主要是给用户查看的
/proc/interrupts
参数5: 传递给参数2的参数
返回值: 正确为0,错误为非零
三 how
a. 获取中断号的函数如下
//获取中断号
static int get_irqno_from_node(void)
{
struct device_node *np;
int no;
//获取到设备树中的节点
np = of_find_node_by_path("/key_interrupt");
if (np) {
printk("find node ok\n");
} else {
printk("find node fail\n");
}
//通过节点去获取到中断号码
no = irq_of_parse_and_map(np, 0);
printk("irqno = %d\n", no);
return no;
}
b. 获取完中断号就可以注册中断了
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key3_exit10", NULL);
c. 完整代码
这个程序只是一个字符设备驱动之中断开发的一个框架,暂时还没有实现任何实际的功能,当然这个驱动是可以正确编译并被加载到内核的
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
typedef struct key_desc_s {
unsigned int dev_major;
struct class *chardev_class;
struct device *chardrv_class_dev;
int irqno;
} key_desc_t;
key_desc_t *key_dev;
static int kernel_value1 = 555;
static int kernel_value2;
irqreturn_t key_irq_handler(int irq_no, void *devid)
{
printk("-----------------\n");
return IRQ_HANDLED;
}
static int chardev_drv_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "chardev_drv_open\n");
return 0;
}
static ssize_t chardev_drv_read(struct file *filp, char __user *buf,
size_t size, loff_t *ppos)
{
int ret = 0;
printk(KERN_INFO "chardev_drv_read\n");
ret = copy_to_user(buf, &kernel_value1, size);
if (ret > 0) {
printk(KERN_ERR "copy_to_user error\n");
return -EINVAL;
}
return 0;
}
static ssize_t chardev_drv_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
int ret = 0;
printk(KERN_INFO "chardev_drv_write\n");
ret = copy_from_user(&kernel_value2, buf, size);
if (ret > 0) {
printk(KERN_ERR "copy_from_user error\n");
return -EINVAL;
}
printk("kernel_value2 = %d", kernel_value2);
return 0;
}
int chardev_drv_close(struct inode *inode, struct file *file)
{
printk(KERN_INFO "chardev_drv_close\n");
return 0;
}
static struct file_operations chardev_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = chardev_drv_open,
.read = chardev_drv_read,
.write = chardev_drv_write,
.release = chardev_drv_close,
};
//获取中断号
static int get_irqno_from_node(void)
{
struct device_node *np;
int no;
//获取到设备树中的节点
np = of_find_node_by_path("/key_interrupt");
if (np) {
printk("find node ok\n");
} else {
printk("find node fail\n");
}
//通过节点去获取到中断号码
no = irq_of_parse_and_map(np, 0);
printk("irqno = %d\n", no);
return no;
}
static int __init key_drv_init(void)
{
int ret = 0;
//1. 设定一个全局的设备对象
key_dev = (key_desc_t *)kzalloc(sizeof(key_desc_t), GFP_KERNEL);
if (key_dev == NULL) {
printk("kzalloc failure\n");
return -EINVAL;
}
//2. 申请主设备号
key_dev->dev_major = register_chrdev(0, "chardev0", &chardev_drv_fops);
//3. 创建设备节点文件
key_dev->chardev_class = class_create(THIS_MODULE, "chardev0");
key_dev->chardrv_class_dev = device_create(key_dev->chardev_class, NULL,
MKDEV(key_dev->dev_major, 0), NULL, "chardev0"); /* /dev/xyz */
//4. 注册中断
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key3_exit10", NULL);
return 0;
}
static void __exit key_drv_exit(void)
{
printk(KERN_INFO "new exit chardev0\n");
//释放中断
free_irq(key_dev->irqno, NULL);
unregister_chrdev(key_dev->dev_major, "chardev0");
device_unregister(key_dev->chardrv_class_dev);
class_destroy(key_dev->chardev_class);
kfree(key_dev);
}
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");