Linux设备驱动开发——中断和时钟

/-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------/

中断和定时器

中断是指CPU在执行程序的过程中,遇到突发事件需要相应,CPU必须暂停当前的进程转而去执行处理突发事件,完成后再返回当前进程。
根据中断入口的方式,中断可以被分为向量中断和非向量中断,采用向量中断的处理器为不同的设备分配不同的中断号,当检测到某个中断到来时,由硬件跳转到相应中断号的中断服务函数入口地址,不同中断号有不同的地址;非向量中断的多个共享中断公用一个入口地址,在中断服务函数中由软件来查询哪个设备申请中断,进而为其服务。一个非向量中断示例

static irqreturn_t xxx_irq(int irq, void *dev_id)
{
	int int_src = read_init_status();//判断硬件
	switch (int_src)
	case A:
	...
	case:b
	...
}

ARM多核处理器中常用的是GIC中断控制器,它支持三种中断:SGI软件中断,PPI(CPU私有中断)和SPI(共享外设中断)。

  1. SGI软件中断,由软件产生,可以用于CPU多核通信,一个CPU核可以写SGI中断通知另一个核心。
  2. PPI私有中断,某个CPU核心私有外设中断,只能中断绑定的CPU核心
  3. SPI共享外设中断,这个中断是我们常用的外部中断

Linux中断处理程序框架

设备中断会打断CPU进程中的正常调度和运行,系统对于更高吞吐率的要求使得我们追求中断服务函数尽可能快速完成。但是在大多数中断情况下,需要执行较多的工作,这往往需要耗费大量时间。为了在尽快完成中断和中断服务函数任务较大之间找到平衡,Linux将中断分为顶半部和底半部,这和Windows下的中断延时处理机制有异曲同工之妙。
所谓顶半部就是用于完成任务紧急且用时较短的部分中断服务函数,它往往只是简单读取寄存器,并在清楚中断后将底半部登记,加入到底半部的执行队列中去。现在,中断服务函数的主要任务就交由底半部来完成,底半部几乎做了中断服务函数需要做的所有事情,而且底半部可以被新的中断打断,而顶半部则不能。
需要注意的是,顶半部和底半部是为了解决中断服务函数过长且不能被打断的问题,在分为两部分后,紧急的事情可以在顶半部快速完成且不会被打断,相对不紧急的事情则放到底半部完成,其耗时也相对较长,但可以被打断。


Linux中断编程

在Linux设备驱动中,使用中断的设备需要申请和释放对应的中断

  1. 申请中断

    request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    const char *name, void *dev)
    irq是申请的中断号
    handler是中断回调函数,也就是发生irq的中断后,调用handler作为中断服务函数
    flags是中断处理属性,使用IRQF_SHARED可以指定为共享中断
    dev是传给中断回调函数的私有数据
    int devm_request_irq(struct device * dev, unsigned int irq, irq_handler_t handler,
    unsigned long irqflags, const char * devname, void * dev_id)

    此函数申请的中断是内核的资源,一般不需要出错处理和显式回收

  2. 释放irq

    void free_irq(unsigned int irq, void *dev_id)
    用来释放中断资源

  3. 使能和屏蔽中断

    void disable_irq(int irq);
    void disable_irq_nosync(int irq);
    void enable_irq(int irq);
    disable_irq_nosync执行后会立即返回,而 disable_irq会等待目前的中断处理完成后返回。在n号中断服务函数的顶半部调用disable_irq会引 起死锁,此时只能使用disable_irq_nosync
    #define local_irq_save 和 local_irq_disable可以用来屏蔽所以CPU中断

  4. 底半部机制
    Linux实现底半部机制主要有tasklet,工作队列,软中断和线程化irq
    1)tasklet
    tasklet的上下文是软中断,执行时机通常是顶半部返回时。定义tasklet及其处理函数并将二者关联;

    void my_tasklet_func (unsigned long)
    DECLARE_TASKLET(my_tasklet,my_tasklet_func,data);
    /* 定义一个tasklet结构my_tasklet并且与my_tasklet_func(data)相关 */
    在需要调度tasklet时,引入tasklet_schedule()函数就能完成系统调度

    2)工作队列
    工作队列的使用方法与tasklet类似,但是工作队列的上下文是内核线程,因此可以调度和睡眠

    struct work_struct my_wq;
    void my_wq_func(struct work_struct *work);
    通过INIT_WORK()可以初始化这个队列,并将工作队列和处理函数绑定
    INIT_WORK(&my_wq,my_wq_func)
    调度函数位 schedule_work (&my_wq)

    3)软中断
    软中断是一种传统的底半部处理机制,它的执行时机通常是顶半部返回时。tasklet机制正是从软中断机制中实现,因此也运行与软中断 上 下文中。
    在Linux内核中,使用一个softirq_action结构体来表征一个软中断,这个结构体包含软中断处理函数指针和传递给该函数的参数。使用open_softirq()函数可以注册软中断对应的处理函数。而raise_softirq可以触发一个软中断。

    4)threaded irq

未完待续

按键中断示例

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	struct pin_desc * pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if (pinval)
	{
		/* 松开 */
		key_val = 0x80 | pindesc->key_val;
	}
	else
	{
		/* 按下 */
		key_val = pindesc->key_val;
	}

    ev_press = 1;                  /* 表示中断发生了 */
    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */

	
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int third_drv_open(struct inode *inode, struct file *file)
{
	/* 配置GPF0,2为输入引脚 */
	/* 配置GPG3,11为输入引脚 */
	request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
	request_irq(IRQ_EINT2,  buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
	request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
	request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);	

	return 0;
}

ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	if (size != 1)
		return -EINVAL;

	/* 如果没有按键动作, 休眠 */
	wait_event_interruptible(button_waitq, ev_press);

	/* 如果有按键动作, 返回键值 */
	copy_to_user(buf, &key_val, 1);
	ev_press = 0;
	
	return 1;
}


int third_drv_close(struct inode *inode, struct file *file)
{
	free_irq(IRQ_EINT0, &pins_desc[0]);
	free_irq(IRQ_EINT2, &pins_desc[1]);
	free_irq(IRQ_EINT11, &pins_desc[2]);
	free_irq(IRQ_EINT19, &pins_desc[3]);
	return 0;
}

共享中断

多个设备共享一个中断线的情况在实际中大量存在,Linux支持这种共享中断。
1)共享中断在申请时使用IRQF_SHARED标志,而且一个中断能申请成功的前提是该中断未被申请或被申请为共享中断。
2)尽管内核可访问的全局地址都可以作为request_irq。。(…*dev_id)中的dev_id参数,但是设备结构体指针才是最佳的传入参数。
3)中断到来时,会遍历执行此共享中断的所有中断服务函数,只到返回IRQ_HANDLED,若不匹配,则返回IRQ_NONE

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值