欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

linux驱动开发之输入子系统编程(一)使用工作队列实现中断下半部

程序员文章站 2022-07-14 11:32:02
...

基本功能:使用工作队列实现中断下半部
在Linux内核中,可以通过工作队列来实现中断下半部。当中断发生时,将当前的进程加入到一个工作队列 struct work_struct中,加入到工作队列中的中断事件会通过队列的方式出队,得到处理。
新增功能:在linux内核中使用定时器消抖(工程需要)
1.linux内核使用 struct timer_list 结构对象来描述一个定时器
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;
unsigned long expires;
struct tvec_base *base;

void (*function)(unsigned long);    //当定时到达后,执行这个函数接口,有用户实现
unsigned long data;

int slack;
#ifdef CONFIG_TIMER_STATS
    int start_pid;
    void *start_site;
    char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

2.linux内核中初始化一个定时器
//参数:timer—–struct timer_list结构对像
#define init_timer(timer)

3.linux内核中将一个定时器加入到内核
//参数:timer—–struct timer_list结构对像
void add_timer(struct timer_list *timer)

4.linux内核中开始启用定时器,
//参数1:timer—–struct timer_list结构对像
//参数2:expires—定时器的计数值
int mod_timer(struct timer_list *timer, unsigned long expires)

5.linux内核中使用 jiffies 变量来记录从系统启动到当前的计算值,表示为一个滴答数

6.linux内核中使用 HZ 代表 jiffies 1s中计数的次数, 通常为1000,既每次计数 1ms

input_simple.c程序

#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/workqueue.h>
#include <linux/timer.h>
#include <mach/gpio.h>
#include <asm/irq.h>
#include <asm/io.h>
//
//新增功能:在linux内核中使用定时器消抖
struct button_type {
    struct input_dev *button_dev;
    int irqno;
    struct work_struct work;
    struct timer_list buttons_timer;//定时器
};

struct button_type *button_input_dev;

void Key_Dithering_timer(unsigned long arg)
{
    if(gpio_get_value(S5PV210_GPH0(5)))
    {
        //上报数据 到 input handler
        input_report_key(button_input_dev->button_dev, KEY_ESC, 1);
        //同步数据到用户空间
        input_sync(button_input_dev->button_dev);
    }
    else
    {
        //上报数据 到 input handler
        input_report_key(button_input_dev->button_dev, KEY_ESC, 0);
        //同步数据到用户空间
        input_sync(button_input_dev->button_dev);
    }
}


void work_button_event(struct work_struct *work)
{
    //linux内核中开始启用定时器 
    //参数1:timer-----struct timer_list结构对像
    //参数2:expires---定时器的计数值
    //linux内核中使用 jiffies 变量来记录从系统启动到当前的计算值,表示为一个滴答数
    //linux内核中使用 HZ 代表 jiffies 1s中计数的次数,  通常为1000,既每次计数 1ms
    mod_timer(&button_input_dev->buttons_timer, jiffies+(HZ/50));//延时20ms
}


static irqreturn_t button_interrupt(int irq, void *dummy)
{
    schedule_work(&button_input_dev->work);//把工作任务加入到全局队列
    return IRQ_HANDLED;
}

static int __init button_init(void)
{
    int error;

    // 1.实例化设备对象
    button_input_dev = kzalloc(sizeof(struct button_type), GFP_KERNEL);//分配内存给结构体
    if(IS_ERR(button_input_dev))
    {
        printk(KERN_ERR"kzalloc struct button_type error!\n");
        return -ENODEV;
    }

    // 2.申请 input 输入设备
    button_input_dev->button_dev = input_allocate_device();
    if (!button_input_dev->button_dev) {
        printk(KERN_ERR "Not enough memory\n");
        error = -ENOMEM;
        goto err_free_zalloc;
    }

//  button_input_dev->button_dev->name = "KEY_ESC";
    // 3.设置位表
    button_input_dev->button_dev->evbit[0] |= BIT_MASK(EV_KEY); //支持按键事件
    button_input_dev->button_dev->keybit[BIT_WORD(KEY_ESC)] |= BIT_MASK(KEY_ESC);//支持 KEY_ESC按键
    button_input_dev->button_dev->keybit[BIT_WORD(KEY_UP)] |= BIT_MASK(KEY_UP);//支持 KEY_UP按键

    // 4.将设备注册到input子系统中
    error = input_register_device(button_input_dev->button_dev);
    if (error) {
        printk(KERN_ERR "Failed to register device\n");
        goto err_free_dev;
    }

    // 5. 初始化硬件
    button_input_dev->irqno = gpio_to_irq(S5PV210_GPH0(5));
    if (request_irq(button_input_dev->irqno, button_interrupt, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "KEY_ESC", NULL))  
    {
        printk(KERN_ERR " Can't allocate irq %d\n", button_input_dev->irqno);
        error = -EBUSY;
        goto err_unregister_dev;
    }

    // 6.初始化工作队列
    INIT_WORK(&button_input_dev->work, work_button_event);


    //初始化定时器
    //参数:timer-----struct timer_list结构对象
    init_timer(&button_input_dev->buttons_timer);
    button_input_dev->buttons_timer.function = Key_Dithering_timer;
    //将一个定时器加入到内核
    add_timer(&button_input_dev->buttons_timer);

    return 0;
err_unregister_dev:
    input_unregister_device(button_input_dev->button_dev);
 err_free_dev:
    input_free_device(button_input_dev->button_dev);
err_free_zalloc:
    kfree(button_input_dev);
    return error;
}

static void __exit button_exit(void)
{
    free_irq(button_input_dev->irqno, 0);
    input_unregister_device(button_input_dev->button_dev);
    input_free_device(button_input_dev->button_dev);
    kfree(button_input_dev);
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");

app_input.c程序

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <unistd.h>

int main(int argc,char ** argv)
{
    int fd;
    int ret = 0;
    struct input_event button_event;//用户读取到输入设备的数据包

    fd = open("/dev/event0", O_RDONLY, S_IRWXU);
    if(fd < 0)
    {
        perror("open filed!\n");
        exit(1);
    }

    while(1)
    {
        ret = read(fd, &button_event, sizeof(struct input_event));
        if(!ret)
        {
            printf("read button event filed!!\n");
            exit(1);
        }

        if(button_event.type == EV_KEY)//表示是按键类型
        {
            if(button_event.code == KEY_ESC)//哪一个按键
            {
                if(button_event.value)
                {
                    printf("---KEY_ESC---up---\n");
                }
                else
                {
                    printf("---KEY_ESC---down---\n");
                }
            }
        }
    }
    return 0;
}

开发板型号:s5pv210
程序链接:https://pan.baidu.com/s/1jJhZmtw 密码:yy03
效果:
按下按键KEY_ESC,屏幕显示相关信息。
linux驱动开发之输入子系统编程(一)使用工作队列实现中断下半部