中断的线程化处理

中断的线程化处理

复杂、耗时的事情,尽量使用内核线程来处理。工作队列用起来挺简单,但是它有一个缺点:工作队列中有多个 work,前一个 work 没处理完会影响后面的 work。解决方法有很多种,比如干脆自己创建一个内核线程,不跟别的 work 凑在一块了。在 Linux 系统中,对于存储设备比如 SD/TF卡,它的驱动程序就是这样做的,它有自己的内核线程。

对于中断处理,还有另一种方法:threaded irq,线程化的中断处理。中断的处理仍然可以认为分为上半部、下半部。上半部用来处理紧急的事情,下半部用一个内核线程来处理,这个内核线程专用于这个中断。

extern int __must_check
request_threaded_irq(unsigned int irq, irq_handler_t handler,
		     irq_handler_t thread_fn,
		     unsigned long flags, const char *name, void *dev);

你可以只提供 thread_fn,系统会为这个函数创建一个内核线程。发生中断时,系统会立刻调用 handler 函数,然后唤醒某个内核线程,内核线程再来执行 thread_fn 函数。

内核机制

1 调用 request_threaded_irq 后内核的数据结构

内核中有一个struct irq_desc结构体数组,有一个属性是struct irqaction* action,这个结构体中有一个属性irq_handler_t thread_fn,就对应的是request_threaded_irq函数中的thread_fn;同时struct irqaction这个结构体还有一个属性irq_handler_t handler,对应的是request_threaded_irq函数中的上半部函数。如果发生中断时内核调用handler函数,返回值是IRQ_HANDLED表示中断处理完毕;如果返回值是IRQ_WAKE_THREAD,则唤醒线程。

内核线程被唤醒后,执行thread_fn函数可能有程序在等待thread_fn函数被执行,调用wake_threads_waitq(desc)来唤醒它。

注意:可以调用synchronize_irq来等待thread_fn被执行。

2 request_threaded_irq

request_threaded_irq 函数,肯定会创建一个内核线程。源码在内核文件 kernel\irq\manage.c

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
			 irq_handler_t thread_fn, unsigned long irqflags,
			 const char *devname, void *dev_id)
{
    // 分配、设置一个 irqaction 结构体
    action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
    
    if (!action)
    return -ENOMEM;
    action->handler = handler;
    action->thread_fn = thread_fn;
    action->flags = irqflags;
    action->name = devname;
    action->dev_id = dev_id;
    retval = __setup_irq(irq, desc, action); // 进一步处理
}

__setup_irq 函数代码如下(只摘取重要部分):

if (new->thread_fn && !nested) {
    ret = setup_irq_thread(new, irq, false);

setup_irq_thread 函数代码如下(只摘取重要部分):

	if (!secondary) {
		t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
				   new->name);
	} else {
		t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
				   new->name);
		param.sched_priority -= 1;
	}

只需要分析__handle_irq_event_percpu 函数 , 它在 kernel\irq\handle.c 中:
在这里插入图片描述
线程的处理函数为 irq_thread,代码在 kernel\irq\handle.c 中:
在这里插入图片描述

内核程序

调用 request_threaded_irq 函数注册中断,调用 free_irq 卸载中断。从前面可知,我们可以提供上半部函数,也可以不提供:

  • 如果不提供,内核会提供默认的上半部处理函数:irq_default_primary_handler,它是直接返回 IRQ_WAKE_THREAD。
  • 如果提供的话, 返回值必须是: IRQ_WAKE_THREAD。在 thread_fn 中,如果中断被正确处理了,应该返回 IRQ_HANDLED。
#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.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/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/current.h>


struct gpio_key{
	int gpio;
	struct gpio_desc *gpiod;
	int flag;
	int irq;
	struct timer_list key_timer;
	struct tasklet_struct tasklet;
	struct work_struct work;
} ;

static struct gpio_key *gpio_keys_100ask;

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_key_class;

/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;

struct fasync_struct *button_fasync;

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
	return (r == w);
}

static int is_key_buf_full(void)
{
	return (r == NEXT_POS(w));
}

static void put_key(int key)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

static int get_key(void)
{
	int key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}


static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);

static void key_timer_expire(unsigned long data)
{
	/* data ==> gpio */
	struct gpio_key *gpio_key = data;
	int val;
	int key;

	val = gpiod_get_value(gpio_key->gpiod);


	printk("key_timer_expire key %d %d\n", gpio_key->gpio, val);
	key = (gpio_key->gpio << 8) | val;
	put_key(key);
	wake_up_interruptible(&gpio_key_wait);
	kill_fasync(&button_fasync, SIGIO, POLL_IN);
}

static void key_tasklet_func(unsigned long data)
{
	/* data ==> gpio */
	struct gpio_key *gpio_key = data;
	int val;
	int key;

	val = gpiod_get_value(gpio_key->gpiod);


	printk("key_tasklet_func key %d %d\n", gpio_key->gpio, val);
}

static void key_work_func(struct work_struct *work)
{
	struct gpio_key *gpio_key = container_of(work, struct gpio_key, work);
	int val;

	val = gpiod_get_value(gpio_key->gpiod);

	printk("key_work_func: the process is %s pid %d\n",current->comm, current->pid);	
	printk("key_work_func key %d %d\n", gpio_key->gpio, val);
}

/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int err;
	int key;

	if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))
		return -EAGAIN;
	
	wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
	key = get_key();
	err = copy_to_user(buf, &key, 4);
	
	return 4;
}

static unsigned int gpio_key_drv_poll(struct file *fp, poll_table * wait)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_key_wait, wait);
	return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

static int gpio_key_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &button_fasync) >= 0)
		return 0;
	else
		return -EIO;
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_key_drv_read,
	.poll    = gpio_key_drv_poll,
	.fasync  = gpio_key_drv_fasync,
};


static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	//printk("gpio_key_isr key %d irq happened\n", gpio_key->gpio);
	tasklet_schedule(&gpio_key->tasklet);
	mod_timer(&gpio_key->key_timer, jiffies + HZ/50);
	schedule_work(&gpio_key->work);
	return IRQ_WAKE_THREAD;
}

static irqreturn_t gpio_key_thread_func(int irq, void *data)
{
	struct gpio_key *gpio_key = data;
	int val;

	val = gpiod_get_value(gpio_key->gpiod);

	printk("gpio_key_thread_func: the process is %s pid %d\n",current->comm, current->pid);	
	printk("gpio_key_thread_func key %d %d\n", gpio_key->gpio, val);
	
	return IRQ_HANDLED;
}


/* 1. 从platform_device获得GPIO
 * 2. gpio=>irq
 * 3. request_irq
 */
static int gpio_key_probe(struct platform_device *pdev)
{
	int err;
	struct device_node *node = pdev->dev.of_node;
	int count;
	int i;
	enum of_gpio_flags flag;
		
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	count = of_gpio_count(node);
	if (!count)
	{
		printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
		return -1;
	}

	gpio_keys_100ask = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
	for (i = 0; i < count; i++)
	{		
		gpio_keys_100ask[i].gpio = of_get_gpio_flags(node, i, &flag);
		if (gpio_keys_100ask[i].gpio < 0)
		{
			printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
			return -1;
		}
		gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
		gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;
		gpio_keys_100ask[i].irq  = gpio_to_irq(gpio_keys_100ask[i].gpio);

		setup_timer(&gpio_keys_100ask[i].key_timer, key_timer_expire, &gpio_keys_100ask[i]);
		gpio_keys_100ask[i].key_timer.expires = ~0;
		add_timer(&gpio_keys_100ask[i].key_timer);

		tasklet_init(&gpio_keys_100ask[i].tasklet, key_tasklet_func, &gpio_keys_100ask[i]);

		INIT_WORK(&gpio_keys_100ask[i].work, key_work_func);
	}

	for (i = 0; i < count; i++)
	{
		//err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpio_keys_100ask[i]);
		err = request_threaded_irq(gpio_keys_100ask[i].irq, gpio_key_isr, gpio_key_thread_func, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpio_keys_100ask[i]);
	}

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_gpio_key", &gpio_key_drv);  /* /dev/gpio_key */

	gpio_key_class = class_create(THIS_MODULE, "100ask_gpio_key_class");
	if (IS_ERR(gpio_key_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_gpio_key");
		return PTR_ERR(gpio_key_class);
	}

	device_create(gpio_key_class, NULL, MKDEV(major, 0), NULL, "100ask_gpio_key"); /* /dev/100ask_gpio_key */
        
    return 0;
    
}

static int gpio_key_remove(struct platform_device *pdev)
{
	//int err;
	struct device_node *node = pdev->dev.of_node;
	int count;
	int i;

	device_destroy(gpio_key_class, MKDEV(major, 0));
	class_destroy(gpio_key_class);
	unregister_chrdev(major, "100ask_gpio_key");

	count = of_gpio_count(node);
	for (i = 0; i < count; i++)
	{
		free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
		del_timer(&gpio_keys_100ask[i].key_timer);
		tasklet_kill(&gpio_keys_100ask[i].tasklet);
	}
	kfree(gpio_keys_100ask);
    return 0;
}


static const struct of_device_id ask100_keys[] = {
    { .compatible = "100ask,gpio_key" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "100ask_gpio_key",
        .of_match_table = ask100_keys,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init gpio_key_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&gpio_keys_driver); 
	
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *     卸载platform_driver
 */
static void __exit gpio_key_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&gpio_keys_driver);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(gpio_key_init);
module_exit(gpio_key_exit);

MODULE_LICENSE("GPL");

编译

在这里插入图片描述

测试

把编译出来的设备树文件拷贝到/boot目录下
在这里插入图片描述
reboot重启
安装驱动,强制安装

insmod gpio_key_drv.ko

在这里插入图片描述
执行测试程序

./button_test /dev/100ask_gpio_key

在这里插入图片描述
在这里插入图片描述
内核线程被调用了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值