Linux学习笔记(17.10)—— 按键的中断线程化处理

本文主要参考韦东山老师视频及文档

  1. Linux中断处理流程
    在这里插入图片描述

  2. 中断系统中相关数据结构
    在这里插入图片描述

    ​ 每一个irq_desc数组项中都有一个函数:handle_irq,还有一个action链表。要理解它们,需要先看中断结构图: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GEIBRiW6-1647057964616)(E:/Glen/Linux/Linux%E9%A9%B1%E5%8A%A8%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/Linux%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E4%B9%8B%E4%B8%AD%E6%96%AD%E7%BA%BF%E7%A8%8B%E5%8C%96%E5%A4%84%E7%90%86.assets/image-20220312100124272.png)]

    ​ 外部设备1、外部设备n共享一个GPIO中断B,多个GPIO中断汇聚到GIC(通用中断控制器)的A号中断,GIC再去中断CPU。那么软件处理时就是反过来,先读取GIC获得中断号A,再细分出GPIO中断B,最后判断是哪一个外部芯片发生了中断。

    ​ 所以,中断的处理函数来源有三:

    ① GIC的处理函数:

    ​ 假设irq_desc[A].handle_irqXXX_gpio_irq_handler,这个函数需要读取芯片的GPIO控制器,细分发生的是哪一个GPIO中断(假设是B),再去调用irq_desc[B]. handle_irq

    注意irq_desc[A].handle_irq细分出中断后B,调用对应的irq_desc[B].handle_irq

    显然中断A是CPU接收到的顶层中断,GIC中断CPU时,CPU读取GIC状态得到中断A。

    ② 模块的中断处理函数:

    ​ 比如对于GPIO模块向GIC发出的中断B,它的处理函数是irq_desc[B].handle_irq

    BSP开发人员会设置对应的处理函数,一般是handle_level_irqhandle_edge_irq,是用来处理电平触发的中断、边沿触发的中断。

    注意:导致GPIO中断B发生的原因很多,可能是外部设备1,可能是外部设备n,可能只是某一个设备,也可能是多个设备。所以irq_desc[B].handle_irq会调用某个链表里的函数,这些函数由外部设备提供。这些函数自行判断该中断是否自己产生,若是则处理。

    ③ 外部设备提供的处理函数:

    ​ 这里说的“外部设备”可能是芯片,也可能总是简单的按键。它们的处理函数由自己驱动程序提供,这是最熟悉这个设备的“人”:它知道如何判断设备是否发生了中断,如何处理中断。

    ​ 对于共享中断,比如GPIO中断B,它的中断来源可能有多个,每个中断源对应一个中断处理函数。所以irq_desc[B]中应该有一个链表,存放着多个中断源的处理函数。一旦程序确定发生了GPIO中断B,那么就会从链表里把那些函数取出来一一执行。这个链表就是action链表。

    • struct irq_desc结构体

      /**
       * struct irq_desc - interrupt descriptor
       * @irq_data:		per irq and chip data passed down to chip functions
       * @preflow_handler:	handler called before the flow handler (currently used by sparc)
       * @action:		the irq action chain
       * @name:		flow handler name for /proc/interrupts output
       */
      struct irq_desc {
      	struct irq_data		irq_data;
      	irq_flow_handler_t	handle_irq;
      	struct irqaction	*action;	/* IRQ action list */
      	int			threads_handled_last;
      	const char		*name;
          ......
      };
      
    • struct irqaction结构体

      struct irqaction {
      	irq_handler_t		handler;
      	void			*dev_id;
      	void __percpu		*percpu_dev_id;
      	struct irqaction	*next;
      	irq_handler_t		thread_fn;
      	struct task_struct	*thread;
      	unsigned int		irq;
      	unsigned int		flags;
      	unsigned long		thread_flags;
      	unsigned long		thread_mask;
      	const char		*name;
      	struct proc_dir_entry	*dir;
      };
      

      ​ 当调用request_irqrequest_threaded_irq注册中断处理函数时,内核就会构造一个irqaction结构体。在里面保存namedev_id等,最重要的是handlerthread_fnthread

      handler是中断处理的上半部函数,用来处理紧急的事情。

      thread_fn对应一个内核线程thread,当handler执行完毕,Linux内核会唤醒对应的内核线程。在内核线程里,会调用thread_fn函数。

      ​ 可以提供handler而不提供thread_fn,就退化为一般的request_irq函数。

      ​ 可以不提供handler只提供thread_fn,完全由内核线程来处理中断。

      ​ 也可以既提供handler也提供thread_fn,这就是中断上半部、下半部。

      reqeust_irq时可以传入dev_id,其作用有以下两点:

      ① 中断处理函数执行时,可以使用dev_id

      ② 卸载中断时要传入dev_id,这样才能在action链表中根据dev_id找到对应项

      所以在共享中断中必须提供dev_id,非共享中断可以不提供。

    • struct irq_data结构体

      struct irq_data {
      	u32			mask;
      	unsigned int		irq;
      	unsigned long		hwirq;
      	unsigned int		node;
      	unsigned int		state_use_accessors;
      	struct irq_chip		*chip;
      	struct irq_domain	*domain;
      #ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
      	struct irq_data		*parent_data;
      #endif
      	void			*handler_data;
      	void			*chip_data;
      	struct msi_desc		*msi_desc;
      	cpumask_var_t		affinity;
      };
      

      ​ 它就是个中转站,里面有irq_chip指针 irq_domain指针,都是指向别的结构体。

      irqhwirqirq是软件中断号,hwirq是硬件中断号。上面的例子,在GPIO中断B是软件中断号,可以找到irq_desc[B]这个数组项;GPIO里的第x号中断,这就是hwirq

      ​ 谁来建立irqhwirq之间的联系呢?由irq_domain来建立。irq_domain会把本地的hwirq映射为全局的irq,什么意思?比如GPIO控制器里有第1号中断,UART模块里也有第1号中断,这两个“第1号中断”是不一样的,它们属于不同的“域”──irq_domain。

    • struct irq_chip结构体

      struct irq_chip {
      	void		(*irq_enable)(struct irq_data *data);
      	void		(*irq_disable)(struct irq_data *data);
      
      	void		(*irq_ack)(struct irq_data *data);
      	void		(*irq_mask)(struct irq_data *data);
      	void		(*irq_mask_ack)(struct irq_data *data);
          ......
      };
      

      ​ 这个结构体跟“chip”即芯片相关,里面各成员的作用在头文件中也列得很清楚,摘录部分如下:

      * @irq_startup:	start up the interrupt (defaults to ->enable if NULL)
      * @irq_shutdown:	shut down the interrupt (defaults to ->disable if NULL)
      * @irq_enable:		enable the interrupt (defaults to chip->unmask if NULL)
      * @irq_disable:	disable the interrupt
      * @irq_ack:		start of a new interrupt
      * @irq_mask:		mask an interrupt source
      * @irq_mask_ack:	ack and mask an interrupt source
      * @irq_unmask:		unmask an interrupt source
      * @irq_eoi:		end of interrupt
      

      ​ 在request_irq后,并不需要手动去使能中断,原因就是系统调用对应的irq_chip里的函数已使能了中断。

      ​ 我们提供的中断处理函数中,也不需要执行主芯片相关的清中断操作,也是系统帮我们调用irq_chip中的相关函数。

      ​ 但是对于外部设备相关的清中断操作,还是需要我们自己做的。

      ​ 就像上面图里的“外部设备1“、“外部设备n”,外设备千变万化,内核里可没有对应的清除中断操作。

    • struct irq_domain结构体

      struct irq_domain {
      	const struct irq_domain_ops *ops;
      
      	irq_hw_number_t hwirq_max;
      	unsigned int revmap_size;
      	unsigned int linear_revmap[];
      };
      

      ​ 在驱动中会使用request_irq(irq, handler)这样的函数来注册中断,irq是什么?它是软件中断号,它可以从设备树中interrupt-parent = <&www>interrupts = <n xxx>转换得来。

      ​ 谁把hwirq转换为irq?由www的相关数据结构,就是www对应的irq_domain结构体。

      irq_domain结构体中有一个irq_domain_ops结构体,里面有各种操作函数,主要是:

      xlate 用来解析设备树的中断属性,提取出hwirqtype等信息。

      maphwirq转换为irq

    • struct irq_domain_ops结构体

      struct irq_domain_ops {
      	int (*match)(struct irq_domain *d, struct device_node *node);
      	int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
      	void (*unmap)(struct irq_domain *d, unsigned int virq);
      	int (*xlate)(struct irq_domain *d, struct device_node *node,
      		     const u32 *intspec, unsigned int intsize,
      		     unsigned long *out_hwirq, unsigned int *out_type);
          ......
      };
      
  3. 中断的线程化处理

    ​ 中断线程化主要是考虑到实时性的要求。如果实时任务不能抢占ISR,那么其执行的延迟(latency)时间将不确定,这不符合实时性的要求。如果内核在编译的时候配置了CONFIG_PREEMPT选项,那么将支持线程抢占,借此就可以实现中断线程化,线程化的中断处理部分被称为threaded interrupt handler在这里插入图片描述

    __do_softirq()执行次数/时间超过限制后触发ksoftirqd只是中断线程化的一种情况,更通用的是使用request_threaded_irq(),只要没有在flag参数中指定"IRQF_NO_THREAD",那么就将按中断线程化的方式处理。

    ​ 当使用ps命令查看的时候,格式为"irq/%d-%s"就是这些threaded interrupt handler,其中"d"为irq号,"s"为名称:

    /drv_module # ps -ef | grep "\[irq"
       46 0         0:00 [irq/40-edt-ft54]
       47 0         0:00 [irq/225-mmc0]
       48 0         0:00 [irq/50-2190000.]
       49 0         0:00 [irq/226-mmc1]
       59 0         0:00 [irq/205-imx_the]
       83 0         0:00 grep \[irq
    
    

    这些线程是在__setup_irq()中创建的:

    __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
    {
    	...
        /*
    	 * Create a handler thread when a thread function is supplied
    	 * and the interrupt does not nest into another interrupt
    	 * thread.
    	 */
    	if (new->thread_fn && !nested) {
    		struct task_struct *t;
    		static const struct sched_param param = {
    			.sched_priority = MAX_USER_RT_PRIO/2,
    		};
    
    		t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
    				   new->name);
    		...
    		sched_setscheduler_nocheck(t, SCHED_FIFO, &param);
    
    		/*
    		 * We keep the reference to the task struct even if
    		 * the thread dies to avoid that the interrupt code
    		 * references an already freed task_struct.
    		 */
    		get_task_struct(t);
    		new->thread = t;
    		/*
    		 * Tell the thread to set its affinity. This is
    		 * important for shared interrupt handlers as we do
    		 * not invoke setup_affinity() for the secondary
    		 * handlers as everything is already set up. Even for
    		 * interrupts marked with IRQF_NO_BALANCE this is
    		 * correct as we want the thread to move to the cpu(s)
    		 * on which the requesting code placed the interrupt.
    		 */
    		set_bit(IRQTF_AFFINITY, &new->thread_flags);
    	}
    	...
    }
    

    ​ 线程的入口函数存放在new->thread_fn中,而这个thread_fn就是request_threaded_irq()相较于传统的request_irq()新增的那个参数。

    ​ 实际上,threaded irq,为每一个中断创建一个内核线程;多个中断的内核线程可以分配到多个CPU上执行,这提高了效率。

  4. 以按键进行编程实践

    5.1 按键设备驱动文件
    button_drv.c文件中,

    • 加载模块时,btn_hw_drv_probe函数中调用request_threaded_irq将按键工作中断上半部处理函数gpio_btn_isrbutton_threaded_isr_fun赋给struct irq_desc数组的action指针;

    • gpio_btn_isr按键中断服务程序(中断上半部)处理完毕,返回IRQ_WAKE_THREAD,用以后续调用button_threaded_isr_fun处理比较耗时的事情;

    • button_threaded_isr_fun为内核线程所调用,进一步执行。

/**
 * 文件    : button_drv.c
 * 作者    : glen  
 * 描述    : button driver文件
 */
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/workqueue.h>

#define KEY_BUF_LEN 16
#define NEXT_POS(x) ((x+1) % KEY_BUF_LEN)

struct gbtn_irq {
    int gpio;
    struct gpio_desc *gpiod;
    int flag;
    int irq;
    int idx;
    char kval[KEY_BUF_LEN];
    int r, w;
    struct fasync_struct *fp;
    struct timer_list key_timer;
    struct tasklet_struct tasklet;
    struct work_struct wq;
};

struct button_drv {
    struct class *class;
    struct gbtn_irq *gbtn_irq;
    
    char  *name;
    int count;
    int major;
};

static struct button_drv *btn_drv;

static int is_key_buf_empty(void *arg)
{
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    return (p->r == p->w);
}

static int is_key_buf_full(void *arg)
{
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    return (p->r == NEXT_POS(p->w));
}

static void put_key(char key, void *arg)
{
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    if (!is_key_buf_full(arg)) {
        p->kval[p->w] = key;
        p->w = NEXT_POS(p->w);
    }
}

static char get_key(void *arg)
{
    char key = 'N';
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    if (!is_key_buf_full(arg)) {
        key = p->kval[p->r];
        p->r = NEXT_POS(p->r);
    }

    return key;
}

/* 等待队列头的静态初始化 */
static DECLARE_WAIT_QUEUE_HEAD(gpio_button_wait);

static void key_timer_expire(unsigned long data)
{
    int val;
    char key;
    struct gbtn_irq *ops = (struct gbtn_irq *)data;

    /* 读取按键的值 */
    val = gpiod_get_value(ops->gpiod);

    printk("button%d %d %d\n", ops->idx, ops->gpio, val);
    key = (ops->gpio << 4) | val;

    put_key(key, ops);

    /* 唤醒等待队列 */
    wake_up_interruptible(&gpio_button_wait);

    kill_fasync(&ops->fp, SIGIO, POLL_IN);

    /* enable btn*/
    enable_irq(ops->irq);
}

/* 实现file_operations结构体成员 read 函数 */
ssize_t button_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
    int minor = iminor(filp->f_inode);
    struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;
    char kval;

    size = (size >= 1) ? 1 : 0;
    if (ops == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    if (is_key_buf_empty(ops) && (filp->f_flags & O_NONBLOCK))
        return -EAGAIN;

    wait_event_interruptible(gpio_button_wait, !is_key_buf_empty(ops));
    kval = get_key(ops);

    if (copy_to_user(buf, &kval, size))
        return -EFAULT;

    printk("Read button%d value successfully:", minor);
    return size;
}

/* 实现file_operations结构体成员 open 函数 */
int button_drv_open(struct inode *nd, struct file *filp)
{
    int ret;
    int minor = iminor(nd);
    struct gbtn_irq *ops; 
    
    if (btn_drv == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    ops = &btn_drv->gbtn_irq[minor];

    ret = gpiod_direction_input(ops->gpiod);
    if (ret) 
        printk("Set the button pin as input error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
    else 
        printk("Set the button%d pin as input successfully!\n", minor);

    filp->private_data = ops;

    return 0;
}

/* 实现file_operations结构体成员 release 函数 */
int button_drv_release (struct inode *nd, struct file *filp)
{
    struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;

    if (ops == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    filp->private_data = NULL;

    return 0;
}

/* 实现file_operations结构体成员 poll 函数 */
unsigned int button_drv_poll (struct file *filp, struct poll_table_struct * wait)
{
    int minor = iminor(filp->f_inode);

    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    poll_wait(filp, &gpio_button_wait, wait);
    return (is_key_buf_empty(&btn_drv->gbtn_irq[minor]) ? 0 : POLLIN | POLLRDNORM);
}

static int button_drv_fasync(int fd, struct file *filp, int on)
{
    struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;

	if (fasync_helper(fd, filp, on, &ops->fp) >= 0)
		return 0;
	else
		return -EIO;
}

/**
 * 1. 构造file_operations结构体 
 */
static struct file_operations button_drv_ops = {
    .owner   = THIS_MODULE,
    .read    = button_drv_read,
    .open    = button_drv_open,
    .release = button_drv_release,
    .poll    = button_drv_poll,
    .fasync  = button_drv_fasync,
};

/* 中断服务函数 */
static irqreturn_t gpio_btn_isr (int irq, void *dev_id)
{
    struct gbtn_irq *ops = dev_id;

    // printk("gpio_btn_isr key %d irq happened\n", ops->gpio);
    tasklet_schedule(&ops->tasklet);
    mod_timer(&ops->key_timer, jiffies + HZ / 50);
    schedule_work(&ops->wq);
    disable_irq_nosync(irq);
    
    return IRQ_WAKE_THREAD;
}

/* tasklet action function */
static void button_tasklet_func (unsigned long data)
{
    int val;
    struct gbtn_irq *ops = (struct gbtn_irq *)data;

    /* 读取按键的值 */
    val = gpiod_get_value(ops->gpiod);

    printk("button_tasklet_func key%d %d %d\n", ops->idx, ops->gpio, val);

}

static void button_work_func (struct work_struct *work)
{
    int val;
    struct gbtn_irq *ops = container_of(work, struct gbtn_irq, wq);

    /* 读取按键的值 */
    val = gpiod_get_value(ops->gpiod);
    printk("button_work_func: the process is %s pid %d\n", current->comm, current->pid);
    printk("button_work_func key%d %d %d\n", ops->idx, ops->gpio, val);
}

static irqreturn_t button_threaded_isr_fun (int irq, void *dev)
{
    int val;
    struct gbtn_irq *ops = dev;

    /* 读取按键的值 */
    val = gpiod_get_value(ops->gpiod);
    printk("button_threaded_isr_fun: the process is %s pid %d\n", current->comm, current->pid);
    printk("button_threaded_isr_fun key%d %d %d\n", ops->idx, ops->gpio, val);
    return IRQ_HANDLED;
}

/* platform_driver结构体的 probe成员函数实现 */
int btn_hw_drv_probe (struct platform_device *pdev)
{
    int i;
    int ret;
    int count;
    // enum of_gpio_flags flag;
    struct device_node *node = pdev->dev.of_node;

    /* 从设备节点获取gpio数量 */
    count = of_gpio_count(node);
    if (!count) {
        printk("%s %s line %d, there isn't any gpio available!\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }
    
    btn_drv = kzalloc(sizeof(struct button_drv), GFP_KERNEL);
    if (btn_drv == NULL) 
        return -ENOMEM;

    btn_drv->gbtn_irq = kzalloc(sizeof(struct gbtn_irq) * count, GFP_KERNEL);
    if (btn_drv->gbtn_irq == NULL)
        return -ENOMEM;

    for (i = 0; i < count; i++) {
        btn_drv->gbtn_irq[i].gpiod = gpiod_get_index_optional(&pdev->dev, NULL, i, GPIOD_ASIS);
        if (btn_drv->gbtn_irq[i].gpiod == NULL) {
            printk("%s %s line %d, gpiod_get_index_optional failed!\n", __FILE__, __FUNCTION__, __LINE__);
            return -EIO;
        }

        btn_drv->gbtn_irq[i].irq = gpiod_to_irq(btn_drv->gbtn_irq[i].gpiod);
        btn_drv->gbtn_irq[i].gpio = desc_to_gpio(btn_drv->gbtn_irq[i].gpiod);

        setup_timer(&btn_drv->gbtn_irq[i].key_timer, key_timer_expire, &btn_drv->gbtn_irq[i]);
        btn_drv->gbtn_irq[i].key_timer.expires = ~0;
        add_timer(&btn_drv->gbtn_irq[i].key_timer);

        tasklet_init(&btn_drv->gbtn_irq[i].tasklet, button_tasklet_func, &btn_drv->gbtn_irq[i]);

        INIT_WORK(&btn_drv->gbtn_irq[i].wq, button_work_func);

        btn_drv->gbtn_irq[i].idx = i;
    }

    for (i = 0; i < count; i++) 
        /* 申请irq中断, 将中断服务程序注册到上半部 */
        // ret = request_irq(btn_drv->gbtn_irq[i].irq, gpio_btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
        //                  "gpio_btn", &btn_drv->gbtn_irq[i]);

        request_threaded_irq(btn_drv->gbtn_irq[i].irq, gpio_btn_isr, button_threaded_isr_fun, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "gpio_btn", &btn_drv->gbtn_irq[i]);
        

    /* 注册file_operationss结构体对象 -- button_drv_ops  */
    btn_drv->major = register_chrdev(btn_drv->major, "gbtn", &button_drv_ops);
    btn_drv->class = class_create(THIS_MODULE, "gbtn");
    if (IS_ERR(btn_drv->class)) {
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        unregister_chrdev(btn_drv->major, "gbtn");
        return PTR_ERR(btn_drv->class);
    }

    for (i = 0; i < count; i++)
        device_create(btn_drv->class, NULL, MKDEV(btn_drv->major, i), NULL, "gbtn%d", i);

    btn_drv->count = count;
        
    return 0;
}

/* platform_driver结构体的 remove成员函数实现 */
int btn_hw_drv_remove(struct platform_device *pdev)
{
    int i;
    struct device_node *node = pdev->dev.of_node;
    int count = of_gpio_count(node);

    for (i = 0; i < count; i++) {
        device_destroy(btn_drv->class, MKDEV(btn_drv->major, i));
        free_irq(btn_drv->gbtn_irq[i].irq, &btn_drv->gbtn_irq[i]);
        del_timer(&btn_drv->gbtn_irq[i].key_timer);
        tasklet_kill(&btn_drv->gbtn_irq[i].tasklet);
    }
    class_destroy(btn_drv->class);
    unregister_chrdev(btn_drv->major, "gbtn");

    kfree(btn_drv);
    return 0;
}

/* 构造用于配置的设备属性 */
static const struct of_device_id gbtns_id[] = {
    {.compatible = "glen,gbtn"},
    { },
};

/* 构造(初始化)file_operations结构体 */
static struct platform_driver btn_hw_drv = {
    .driver = {
        .name = "gbtn",
        .of_match_table = gbtns_id,
    },
    .probe = btn_hw_drv_probe,
    .remove = btn_hw_drv_remove,
};

/* 初始化 */
static int __init button_drv_init(void)
{
    int ret;
    ret = platform_driver_register(&btn_hw_drv);
    if (ret)
        pr_err("Unable to initialize button driver\n");
    else
        pr_info("The button driver is registered.\n");

    
    return 0;
}
module_init(button_drv_init);

static void __exit button_drv_exit(void)
{
    platform_driver_unregister(&btn_hw_drv);
    printk(" %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
}
module_exit(button_drv_exit);

/* insert author information for module */
MODULE_AUTHOR("glen");
/* insert license for module */
MODULE_LICENSE("GPL");
 

probe函数采用了先获取按键节点数量,然后分别读取gpio描述符并通过其获取为gpio和irq号,并申请注册中断服务程序。

3.2 设备树文件(不作更改)

 		pinctrl_btn0:btn0 {
 			fsl,pins = <
 				MX6UL_PAD_UART1_CTS_B__GPIO1_IO18	0xF080	/* KEY0 */ 
 			>;
 		};
 
 		pinctrl_btn1:btn1 {
 			fsl,pins = <
                 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0xF080	/* KEY1  此按键不存在 */
 			>;
 		};
     /* 在根节点下添加基于pinctrl的gbtns设备节点 */
     gbtns {
         compatible = "glen,gbtn";
         #address-cells = <1>;
 
         pinctrl-names = "default";
         pinctrl-0 = <&pinctrl_btn0 
 		             &pinctrl_btn1>;
 
         gpio-controller;
         #gpio-cells = <2>;
         gpios = <&gpio1 18 GPIO_ACTIVE_LOW /* button0 */
                  &gpio1 3 GPIO_ACTIVE_LOW>;   /* button1 */
 
     };
 
  • 取消了gpios前缀“xxx-",相应地,在驱动程序用gpiod_get_index_optional函数获取gpio描述符时,第2个形参 ”const char *con_id“ 传递NULL即可;

  • 将pinctrl-0、gpios属性值由 “<>,<>;” 改为 “<>;",效果是一样的

3.3 应用程序

应用程序文件button_drv_test.c提供:

  • 定义sig_fun信号处理函数并注册信号,以后APP收到SIGIO信号时,这个函数会被自动调用;
  • fcntl(fd, F_SETOWN, getpid()); 把APP的PID(进程ID)告诉驱动程序,这个调用不涉及驱动程序,在内核的文件系统层次记录PID;
  • oflags = fcntl(fd, F_GETFL); 读取驱动程序文件oflags
  • fcntl(fd, F_SETFL, oflags | FASYNC); 设置oflags里面的FASYNC位为1:当FASYNC位发生变化时,会导致驱动程序的fasync被调用
 /*
  * 文件名   :  button_drv_test.c
  * 作者     :  glen
  * 描述     :  button_drv应用程序
  */
 
 #include "stdio.h"
 #include "sys/types.h"
 #include "sys/stat.h"
 #include "stdlib.h"
 #include "string.h"
 #include "poll.h"
 #include <signal.h>
 #include <unistd.h>
 #include <fcntl.h>
 
 int fd;
 
 static void sig_fun(int sig)
 {
     char kval;
     read(fd, &kval, 1);
     printf("The glen button value is: %d!\n", kval);
 }
 
 /**
  * @brief   : main函数
  * @par     : argc  argv数组元素的个数
  *            argv  参数数组
  * @retval  : 0 成功    其它 失败
  */
 int main(int argc, char *argv[])
 {
     int ret;
     int oflags;
     char *filename;
 
     if (argc != 2) {
         printf("Error Usage!\r\n");
         return -1;
     }
 
     signal(SIGIO, sig_fun);
 
     filename = argv[1];
 
     /* 打开驱动文件 */
     fd = open(filename, O_RDWR);
     if (fd < 0) {
         printf("Can't open file %s\r\n", filename);
         return -1;
     }
 
     fcntl(fd, F_SETOWN, getpid());
     oflags = fcntl(fd, F_GETFL);
     fcntl(fd, F_SETFL, oflags | FASYNC);
 
     while (1) {
         sleep(2);
         printf("Read the glen button in sleepping!\n");
     }
 
     /* 关闭文件 */
     ret = close(fd);
     if (ret < 0) {
         printf("file %s close failed!\r\n", argv[1]);
         return -1;
     }
     return 0;
 }
 

3.4 在alientek_linux_alpha开发板实测验证如下

/drv_module # insmod button_drv.ko
The button driver is registered.
/drv_module # ./btn_drv_test /dev/gbtn0
/drv_module # button_tasklet_func key0 18 1
button_threaded_isr_fun: the process is irq/49-gpio_btn pid 83
button_threaded_isr_fun key0 18 1
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1
button_tasklet_func key0 18 0
button_threaded_isr_fun: the process is irq/49-gpio_btn pid 83
button_threaded_isr_fun key0 18 0
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 0
button0 18 0
button_tasklet_func key0 18 1
button_threaded_isr_fun: the process is irq/49-gpio_btn pid 83
button_threaded_isr_fun key0 18 1
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1
button_tasklet_func key0 18 0
button_threaded_isr_fun: the process is irq/49-gpio_btn pid 83
button_threaded_isr_fun key0 18 0
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 0
button0 18 0

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值