---- 整理自 王利涛老师 课程
实验环境:宅学部落 www.zhaixue.cc
文章目录
1. 中断的基本概念
- 示例:烧水 - 打游戏
- 阻塞:等水烧好再去打游戏
- 轮询:每隔一分钟过来看下水烧没烧好
- 中断:等水烧好发出响声,暂停打游戏去处理热水
- 什么是轮询,什么是中断?
- 异常、中断
- 为什么处理器需要中断?
2. 中断子系统框架
- CPU
- 中断控制器
- 外设
- 中断向量表
- 中断号
- Linux 内核中断子系统
- 中断编程接口
- 中断发生后…
- 当前正在执行的程序
- 中断向量表
- 中断服务程序
- 回到被打断的程序,继续执行
- 其实没这么简单…
- 当前正在执行的程序
- 保存被打断的上下文
- 中断向量表
- 找到发生中断的设备
- 中断服务程序
- 退出中断,调度程序运行:
- 恢复被打断的上下文
- 回到被打断的程序,继续执行…
- 或者:
- 恢复高优先级的进程的上下文
- 切换到高优先级的程序执行…
3. 中断控制器:GIC
3.1 中断控制器概念
- 51 单片机的中断
- ARM SoC 处理器的中断
3.2 中断控制器的作用
- 负责处理各种中断
- 优先级、屏蔽、使能
3.3 多核下的中断控制器
3.4 中断分类
- SGI:16 Software Generated Interrupts
- 中断号 ID0~ID15,用于多核之间通讯
- PPI:16 external Private Peripheral Interrupts
- 每个 CPU core 私有的中断,如本地时钟,ID16~ID31
- SPI:Shared Peripheral Interrupt
- 所有 core 共享的中断,可以在多个 core 上运行
支持范围可配置:32~1019,步进 32,从 ID 32 开始
- 所有 core 共享的中断,可以在多个 core 上运行
3.5 中断触发类型
- edge-triggered 边沿触发
- level-sensitive 电平触发
3.6 中断号
- HW interrupt ID - 芯片的中断号(硬中断号)
- IRQ number - 内核中使用的中断号(软中断号)
- IRQ_domain
驱动 - 映射 - 硬中断号
4. GIC 控制器中断处理流程
4.1 中断状态
Inactive
Pending
Active
Active and pending
4.2 GIC处理中断流程
- GIC 检测到使能的中断发生,将中断状态设为 pending
- GIC 的仲裁器将最高优先级的 pending 中断发送到指定的 CPU interface
- CPU interface 根据配置,将中断信号发送到 CPU
- CPU 应答该中断,读取寄存器获取 interrupt ID,GIC 更新中断状态为 active
- Pending ⇒ active
- Pending ⇒ active and pending 正在处理的中断重新产生
- Active ⇒ active and pending 若中断状态为 active
- CPU 处理完中断后,发送 中断结束信号 EOI 给 GIC
4.3 中断示例
vexpress ARM A9 开发板 + GIC:PL390 + timer:SP804
5. 编写 RTC 裸机中断程序
6. 中断函数的编写规则
- 编写中断函数需要注意的几个地方
- 无传参
- 无返回值
- 短小精悍
7. 在 Linux 下编写 RTC 驱动中断程序
7.1 Linux 内核中断接口
// linux-5.10.4\include\linux\interrupt.h
request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
// irq为中断号,handler为中断处理函数,flags为中断标志位,name为中断处理函数的名称,dev为设备指针。
// 中断处理函数就是在中断发生时执行的函数。中断标志位决定了中断的类型,比如是边沿触发还是电平触发。
- request_irq()
- free_irq()
- HW interrupt ID 和中断号
- 物理地址与虚拟地址
7.2 编程实验
ifneq ($(KERNELRELEASE),)
obj-m := rtc.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR := /home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
// rtc.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
}rtc_reg_t;
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
int counter = 0;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
printk("counter: %d, irqnum: %d\n", counter++, irq);
set_rtc_alarm(regs); // 下一秒继续发送中断
return IRQ_HANDLED;
}
static int __init rtc_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
// 设置RTC下一秒发送一个中断
set_rtc_alarm(regs);
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
return 0;
}
static void __exit rtc_exit(void)
{
free_irq(39, NULL);
printk("Goodbye rtc module!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
8. Linux中断处理流程
- 中断处理,CPU 硬件自动完成的部分
保存 CPSR 到 SPSR_irq 寄存器
设置 CPSR 控制位,让 CPU 进入 ARM 状态、IRQ 模式
CPSR 中的 IRQ 位置一,硬件自动关闭 IRQ
将当前中断地址(返回地址)保存到 LR_irq 寄存器
设置 PC 指针 PC=0x00000018,跳转到中断向量表执行 - ARM 中断向量表
内核定义了中断向量表,当中断发生时,可以跳到 L_vectors_start + 偏移地址
// kernel\linux-5.10.4\arch\arm\kernel\entry-armv.S
.L__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, .L__vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq // <==
W(b) vector_fiq
- IRQ 中断向量表
// kernel\linux-5.10.4\arch\arm\kernel\entry-armv.S
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32) // <==
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
// kernel\linux-5.10.4\arch\arm\kernel\entry-armv.S
.align 5
__irq_usr:
usr_entry //保存现场
kuser_cmpxchg_check
irq_handler // <== 处理中断
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq // 恢复
UNWIND(.fnend )
ENDPROC(__irq_usr)
// kernel\linux-5.10.4\arch\arm\kernel\entry-armv.S
/*
* Interrupt handling.
*/
.macro irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
badr lr, 9997f
ldr pc, [r1]
#else
arch_irq_handler_default // <==
#endif
9997:
.endm
- 根据 HW interrupt ID 找到 IRQ number,调用 asm_do_IRQ
// kernel\linux-5.10.4\arch\arm\include\asm\entry-macro-multi.S
/*
* Interrupt handling. Preserves r7, r8, r9
*/
.macro arch_irq_handler_default
get_irqnr_preamble r6, lr
1: get_irqnr_and_base r0, r2, r6, lr
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
badrne lr, 1b
bne asm_do_IRQ // <==
// kernel\linux-5.10.4\arch\arm\kernel\irq.c
/*
* asm_do_IRQ is the interface to be used from assembly code.
*/
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
handle_IRQ(irq, regs); // <==
}
...
/*
* handle_IRQ handles all hardware IRQ's. Decoded IRQs should
* not come via this function. Instead, they should provide their
* own 'handler'. Used by platform code implementing C-based 1st
* level decoding.
*/
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
__handle_domain_irq(NULL, irq, false, regs); // <==
}
// kernel\linux-5.10.4\kernel\irq\irqdesc.c
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
unsigned int irq = hwirq;
int ret = 0;
irq_enter();
#ifdef CONFIG_IRQ_DOMAIN
if (lookup)
irq = irq_find_mapping(domain, hwirq); // <==
#endif
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {
generic_handle_irq(irq); // <==
}
irq_exit();
set_irq_regs(old_regs);
return ret;
}
...
/**
* generic_handle_irq - Invoke the handler for a particular irq
* @irq: The irq number to handle
*
*/
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
struct irq_data *data;
if (!desc)
return -EINVAL;
data = irq_desc_get_irq_data(desc);
if (WARN_ON_ONCE(!in_irq() && handle_enforce_irqctx(data)))
return -EPERM;
generic_handle_irq_desc(desc); // <==
return 0;
}
// kernel\linux-5.10.4\include\linux\irqdesc.h
/*
* Architectures call this to let the generic IRQ layer
* handle an interrupt.
*/
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
desc->handle_irq(desc);
}
// kernel\linux-5.10.4\kernel\irq\chip.c
void handle_fasteoi_irq(struct irq_desc *desc)
{
struct irq_chip *chip = desc->irq_data.chip;
...
handle_irq_event(desc); // <==
...
}
// kernel\linux-5.10.4\kernel\irq\handle.c
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
irqreturn_t ret;
desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
ret = handle_irq_event_percpu(desc); // <==
raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
return ret;
}
// kernel\linux-5.10.4\kernel\irq\handle.c
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;
...
for_each_action_of_desc(desc, action) {
irqreturn_t res;
...
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, res);
...
retval |= res;
}
return retval;
}
- 系统启动过程中,每个控制器(irq_domain)初始化,会建立硬件中断号(HW interrupt ID)和软中断号(IRQ number)之间的映射
// kernel\linux-5.10.4\drivers\irqchip\irq-gic.c
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
struct gic_chip_data *gic = d->host_data;
struct irq_data *irqd = irq_desc_get_irq_data(irq_to_desc(irq));
switch (hw) {
case 0 ... 15: // <==
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
handle_percpu_devid_fasteoi_ipi,
NULL, NULL);
break;
case 16 ... 31: // <==
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);
break;
default: // <==
irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
handle_fasteoi_irq, NULL, NULL); // <==
irq_set_probe(irq);
irqd_set_single_target(irqd);
break;
}
/* Prevents SW retriggers which mess up the ACK/EOI ordering */
irqd_set_handle_enforce_irqctx(irqd);
return 0;
}
// kernel\linux-5.10.4\kernel\irq\irqdomain.c
void irq_domain_set_info(struct irq_domain *domain, unsigned int virq,
irq_hw_number_t hwirq, struct irq_chip *chip,
void *chip_data, irq_flow_handler_t handler,
void *handler_data, const char *handler_name)
{
irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);
__irq_set_handler(virq, handler, 0, handler_name); // <==
irq_set_handler_data(virq, handler_data);
}
// kernel\linux-5.10.4\kernel\irq\chip.c
void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
const char *name)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);
if (!desc)
return;
__irq_do_set_handler(desc, handle, is_chained, name); // <==
irq_put_desc_busunlock(desc, flags);
}
...
static void
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
int is_chained, const char *name)
{
...
desc->handle_irq = handle;
desc->name = name;
...
}
- 用户注册部分
// kernel\drivers\11rtc\rtc.c
...
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
...
// kernel\linux-5.10.4\include\linux\interrupt.h
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
// request_threaded_irq(39, rtc_alarm_handler, NULL, 0, "rtc0-test", NULL);
}
// kernel\linux-5.10.4\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)
{
struct irqaction *action;
struct irq_desc *desc;
...
desc = irq_to_desc(irq); // 申请中断描述结构体
...
// 分配action
action->handler = handler; // rtc_alarm_handler
action->thread_fn = thread_fn; // NULL
action->flags = irqflags; // 0
action->name = devname; // "rtc0-test"
action->dev_id = dev_id; // NULL
...
retval = __setup_irq(irq, desc, action); // 开启中断
...
- 中断触发
根据中断号找到 struct irq_desc,遍历 desc->action 链表,执行 action->handler
9. 中断的上半部和下半部
9.1 为什么要分上半部和下半部?
- 上半部:响应中断,硬件配置,发送 EOI 给 GIC
- 下半部:数据复制、数据包封装转发、编解码…
- 为了在 中断执行时间尽可能短和中断处理需完成大量工作 之间找到一个平衡点,Linux 将中断处理程序分解为两个半部:上半部(top half)和下半部(bottom half)。
- 上半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将下半部处理程序挂到该设备的下半部执行队列中去。这样,上半部执行的速度就会很快,可以服务更多的中断请求。
- 现在,中断处理工作的重心就落在了下半部的头上,它来完成中断事件的绝大多数任务。下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是上半部和下半部的最大不同,因为上半部往往被设计成不可中断。下半部则相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。
9.2 中断下半部
- 软中断
- tasklet
- 工作队列:workqueue
- 中断线程化
9.3 编程实验
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
}rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static void time_translate(void)
{
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
set_rtc_alarm(regs);
time_translate();
return IRQ_HANDLED;
}
static int __init rtc_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
set_rtc_alarm(regs);
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
return 0;
}
static void __exit rtc_exit(void)
{
free_irq(39, NULL);
printk("Goodbye rtc module!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
10. 中断下半部 - SoftIRQ:软中断
10.1 编程接口
void open_softirq(int nr, void (*action)(struct softirq_action *)); //注册
void raise_softirq(unsigned int nr); // 触发软中断
void raise_softirq_irqoff(unsigned int nr); // 中断下半部中关掉此中断
enum {
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ,
NR_SOFTIRQS
};
- 数字越小,优先级越高。
10.2 编程实验:给内核添加一个软中断
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
static void hello_softirq_handler(struct softirq_action *sa)
{
printk("soft irq handler run!\n");
}
static int __init hello_init(void)
{
printk("hello module!\n");
open_softirq(HELLO_SOFTIRQ, hello_softirq_handler);
raise_softirq(HELLO_SOFTIRQ);
return 0;
}
static void __exit hello_exit(void)
{
printk("Goodbye hello module!\n");
}
module_init(hello_init);
module_exit(hello_exit);
内核中修改如下位置:
接着修改我们之前的 rtc 驱动:(编译时需要关闭 rtc pl031 )
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
}rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
// 中断下半部
static void rtc_softirq_handler(struct softirq_action *sa)
{
printk("enter rtc_softirq_handler\n");
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
set_rtc_alarm(regs);
raise_softirq(HELLO_SOFTIRQ); // 触发软中断
return IRQ_HANDLED;
}
static int __init rtc_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
set_rtc_alarm(regs);
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
// 可以使用我们在内核中添加的自己的软中断号,也可以使用本身就存在的软中断号
// 如 TASKLET_SOFTIRQ
open_softirq(HELLO_SOFTIRQ, rtc_softirq_handler);
return 0;
}
static void __exit rtc_exit(void)
{
free_irq(39, NULL);
printk("Goodbye rtc module!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
10.3 软中断的运行
10.3.1 软中断的实现
softirq 的数据结构:struct softirq_action
软中断描述符数组:struct softirq_action softirq_vec[]
10.3.2 软中断的运行过程
- 运行时机:中断退出后的某个时机
(软中断一般会在硬件中断处理程序(上半部)退出时开始执行, 一个软中断不会抢占另外一个软中断,唯一可以抢占软中断的是硬件中断处理程序。) - 开中断,可以被打断,不允许嵌套
- 中断延后到线程执行:ksoftirqd
- 过程如下:
- 普通进程 1 正在运行,一个 IRQ 中断产生
- CPU 自动执行部分:关闭 IRQ 中断、寄存器备份
- 跳转到中断向量表执行流程:vector_irq
- irq_handler
asm_do_IRQ ⇒ handle_IRQ ⇒ __handle_domain_irq
irq_enter
generic_handle_irq:具体的外设 IRQ 中断处理
irq_exit - 检测是否有pending 的软中断,有则执行
- 软中断是否频繁执行,有则放到 softirqd 执行
- 退出中断,检测是否有更高优先级的进程:
- 无,恢复被打断进程 1 的上下文,继续执行进程 1 …
- 有,调度更高优先级的进程 2 执行 …
- 普通进程 1 正在运行,一个 IRQ 中断产生
11. 中断下半部 - tasklet
因为内核已经定义好了 10 种软中断类型,并且不建议我们自行添加额外的软中断,并且由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个 CPU 上并行执行,那么软中断其实是没有必要的。但是,对于某些情况下,我们不希望一些操作直接在中断的 handler 中执行,但是又希望在稍后的时间里得到快速地处理,这就需要使用 tasklet 机制。 tasklet 是建立在软中断上的一种延迟执行机制,它的实现基于 TASKLET_SOFTIRQ 和 HI_SOFTIRQ 这两个软中断类型。
- 它具有以下特性:
a)一种特定类型的 tasklet 只能运行在一个 CPU 上,不能并行,只能串行执行。
b)多个不同类型的 tasklet 可以并行在多个 CPU 上。
c)软中断是静态分配的,在内核编译好之后,就不能改变。但 tasklet 就灵活许多,可以在运行时改变(比如添加模块时)。
11.1 编程接口
DECLARE_TASKLET(name, _callback);
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data
);
void tasklet_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule(struct tasklet_struct *t);
void tasklet_kill(struct tasklet_struct *t);
11.2 编程实验:将 RTC 驱动的下半部改为 tasklet 来实现
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
}rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
static struct tasklet_struct rtc_tasklet;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
// 中断下半部
static void rtc_tasklet_handler(unsigned long a)
{
printk("enter rtc_softirq_handler\n");
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
printk("args = %ld\n", a);
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
set_rtc_alarm(regs);
// raise_softirq(HELLO_SOFTIRQ); // 触发软中断
tasklet_schedule(&rtc_tasklet);
return IRQ_HANDLED;
}
static int __init rtc_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
set_rtc_alarm(regs);
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
// open_softirq(HELLO_SOFTIRQ, rtc_softirq_handler);
tasklet_init(&rtc_tasklet, rtc_tasklet_handler, 10);
return 0;
}
static void __exit rtc_exit(void)
{
tasklet_kill(&rtc_tasklet);
free_irq(39, NULL);
printk("Goodbye rtc module!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
也可以在 tasklet_schedule(&rtc_tasklet); 之前通过 rtc_tasklet.data = 100; 重新赋值:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
}rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
static struct tasklet_struct rtc_tasklet;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
// 中断下半部
static void rtc_tasklet_handler(unsigned long a)
{
printk("enter rtc_softirq_handler\n");
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
printk("args = %ld\n", a);
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
set_rtc_alarm(regs);
// raise_softirq(HELLO_SOFTIRQ); // 触发软中断
rtc_tasklet.data = 100;
tasklet_schedule(&rtc_tasklet);
return IRQ_HANDLED;
}
static int __init rtc_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
set_rtc_alarm(regs);
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
// open_softirq(HELLO_SOFTIRQ, rtc_softirq_handler);
tasklet_init(&rtc_tasklet, rtc_tasklet_handler, 10);
return 0;
}
static void __exit rtc_exit(void)
{
tasklet_kill(&rtc_tasklet);
free_irq(39, NULL);
printk("Goodbye rtc module!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
注:上述的代码,在 rtc_exit 退出的里面,需要加上 tasklet_kill。
- tasklet_schedule() 触发的软中断,优先级是 TASKLET_SOFTIRQ,tasklet_hi_schedule() 触发的软中断,优先级是更高的 HI_SOFTIRQ。每个 CPU 对应维护了两个链表:tasklet_vec 和 tasklet_hi_vec。
11.3 tasklet 的运行
11.3.1 tasklet 的实现:基于软中断
tasklet_vec 链表:软中断优先级 6,TASKLET_SOFTIRQ
tasklet_hi_vec 链表:软中断优先级 0,HI_SOFTIRQ
11.3.2 tasklet 的执行过程
- 普通进程 1 正在运行,一个 IRQ 中断产生
- CPU 自动执行部分:关闭 IRQ 中断、寄存器备份
- 跳转到中断向量表执行流程:vector_irq
- irq_handler
asm_do_IRQ ⇒ handle_IRQ ⇒ __handle_domain_irq
irq_enter
generic_handle_irq:具体的外设 IRQ 中断处理
irq_exit - 检测是否有 pending 的软中断,有则执行
执行 tasklet
tasklet 是否频繁执行,有则放到 softirqd 执行 - 退出中断,检测是否有更高优先级的进程:
- 无,恢复被打断进程 1 的上下文,继续执行进程 1 …
- 有,调度更高优先级的进程 2 执行 …
12. 中断下半部 - 工作队列 workqueue
12.1 编程接口
DECLARE_WORK(n, f)
INIT_WORK(_work, _func)
schedule_work (struct work_struct *work);
cancel_work_sync (struct work_struct *work);
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
typedef void (*work_func_t)(struct work_struct *work);
12.2 实验:将 RTC 驱动的中断下半部改用工作队列实现
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
}rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
static struct work_struct rtc_workstruct;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
// 中断下半部
static void rtc_work_handler(struct work_struct *work)
{
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
set_rtc_alarm(regs);
schedule_work(&rtc_workstruct);
return IRQ_HANDLED;
}
static int __init rtc_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
set_rtc_alarm(regs);
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
INIT_WORK(&rtc_workstruct, rtc_work_handler);
return 0;
}
static void __exit rtc_exit(void)
{
cancel_work_sync(&rtc_workstruct);
free_irq(39, NULL);
printk("Goodbye rtc module!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");
12.3 延迟工作队列
12.3.1 编程接口
DECLARE_DELAYED_WORK(n, f)
INIT_DELAYED_WORK(_work, _func)
schedule_delayed_work(delayed_work, jiffies);
bool flush_delayed_work (struct delayed_work *dwork);
cancel_delayed_work_sync (struct work_struct *work);
struct delayed_work {
struct work_struct work;
struct timer_list timer;
/* target workqueue and CPU ->timer uses to queue ->work */
struct workqueue_struct *wq;
int cpu;
};
typedef void (*work_func_t)(struct work_struct *work);
12.3.2 实验:将 RTC 驱动的下半部改用延迟工作队列实现
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
}rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
static struct delayed_work rtc_delayed_work;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
// 中断下半部
static void rtc_delayed_work_handler(struct work_struct *work)
{
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
set_rtc_alarm(regs);
schedule_delayed_work(&rtc_delayed_work, 3*HZ); // 注意这里
return IRQ_HANDLED;
}
static int __init rtc_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
set_rtc_alarm(regs);
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
INIT_DELAYED_WORK(&rtc_delayed_work, rtc_delayed_work_handler);
return 0;
}
static void __exit rtc_exit(void)
{
cancel_delayed_work_sync(&rtc_delayed_work);
free_irq(39, NULL);
printk("Goodbye rtc module!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");
12.4 工作队列的运行
12.4.1 工作队列的实现
- 先 struct 一个 work (内含中断下半部的处理函数)
- schedule_work:将 work 添加 workqueue
- 执行 workqueue 的线程:worker thread
12.4.2 运行模式
- 单线程式
- 多线程式
12.4.3 workqueue 队列的弊端
- 假设现在 work item 工作项 work0、work1 和 work2 被绑定到同一 CPU 的工作队列 worqueue0上。work0 执行 5 毫秒后休眠 10 毫秒,接着再次运行 5 毫秒,然后结束。work1 和 work2 都是运行 5 毫秒后休眠 10 毫秒。
- 弊端:因为只有 1 个 worker 线程,所以即便在执行某个 work item 的时候休眠,其他的 work item 也得不到执行,因此将这 3 个 work item 执行完毕将总共需要 50ms 的时间。
13. 并发管理工作队列 CMWQ
13.1 CMWQ:Concurrency Managed Workqueue
一个 CPU 上是不可能“同时”运行多个线程的,所以这里的名称是 concurrency(并发),而不是 parallelism(并行)。
- 在一个 CPU 上运行多个 worker 线程
- 工作项:work item,添加到工作队列(workqueue)中
- 提供两种 worker 线程池:bound 和 unbound
- workqueue 可通过 flag 参数绑定到指定类型的线程池执行
- max_active:工作队列在每个 CPU 上并发处理的 work 个数
13.2 编程接口
struct workqueue_struct * alloc_workqueue(const char *fmt, unsigned int flags, int max_active, ...);
/*
flags:
WQ_UNBOUND
WQ_FREEZABLE
WQ_MEM_RECLAIM
WQ_HIGHPRI
WQ_CPU_INTENSIVE
*/
INIT_WORK (_work, _func)
bool queue_work (struct workqueue_struct *wq,struct work_struct *work);
flush_workqueue();
13.3 CMWQ 工作队列编程实战
- 实验:将 RTC 驱动的中断下半部改用 CMWQ 实现
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
}rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
static struct work_struct rtc_work;
struct workqueue_struct *rtc_cmwq_workqueue;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
// 中断下半部
static void rtc_work_handler(struct work_struct *work)
{
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
set_rtc_alarm(regs);
queue_work(rtc_cmwq_workqueue, &rtc_work);
return IRQ_HANDLED;
}
static int __init rtc_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
set_rtc_alarm(regs);
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
rtc_cmwq_workqueue = alloc_workqueue("rtc", WQ_MEM_RECLAIM, 3);
INIT_WORK(&rtc_work, rtc_work_handler);
return 0;
}
static void __exit rtc_exit(void)
{
flush_workqueue(rtc_cmwq_workqueue);
cancel_work_sync(&rtc_work);
free_irq(39, NULL);
printk("Goodbye rtc module!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");
13.4 CMWQ 的运行
假设现在 work item 工作项 work0、work1 和 work2 被绑定到同一 CPU 的工作队列 worqueue0 上。work0 执行 5 毫秒后休眠 10 毫秒,接着再次运行 5 毫秒,然后结束。work1 和 work2 都是运行 5 毫秒后休眠 10 毫秒。
13.4.1 simple FIFO scheduling
13.4.2 max_active >= 3
13.4.3 max_active = 2
13.4.4 w1 and w2 are queued to wq q1
w0 在 wq q0 上,w1 和 w2 在 wq q1 上
14. 中断线程化:request_threaded_irq
14.1 编程接口
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags,
const char *name,
void *dev);
14.2 实验:将 RTC 驱动的中断下半部改为线程化执行
- 将 RTC 驱动的中断下半部改为线程化执行
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
}rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static irqreturn_t rtc_irq_thread(int irq, void *p)
{
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
return IRQ_HANDLED;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
set_rtc_alarm(regs);
return IRQ_WAKE_THREAD;
}
static int __init rtc_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
set_rtc_alarm(regs);
ret = request_threaded_irq(39, rtc_alarm_handler, rtc_irq_thread, \
0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
return 0;
}
static void __exit rtc_exit(void)
{
free_irq(39, NULL);
printk("Goodbye rtc module!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");