Linux中RTOS需要一个tick心跳进行调度处理,linux中的tick处理函数是scheduler_tick。本文从整体流程上来分析下执行到scheduler_tick的流程,以及过程中定时器中断相关的回调是如何注册的,以stime即中断号为5为例。
先上流程图方便对照
arch/riscv/kernel/head.S中
.align 2
setup_trap_vector:
/* Set trap vector to exception handler */
la a0, handle_exception
csrw CSR_TVEC, a0
/*
* Set sup0 scratch register to 0, indicating to exception vector that
* we are presently executing in kernel.
*/
csrw CSR_SCRATCH, zero
ret
在relocate后调用
call setup_vm
#ifdef CONFIG_MMU
la a0, early_pg_dir
call relocate
#endif /* CONFIG_MMU */
call setup_trap_vector
或者
.global secondary_start_common
secondary_start_common:
/* Enable virtual memory and relocate to virtual address */
la a0, swapper_pg_dir
call relocate
call setup_trap_vector
tail smp_callin
查看寄存器的值
(gdb) i r stvec
stvec 0xffffffe000201a1c -137436849636
(gdb) p &handle_exception
$1 = (<text variable, no debug info> *) 0xffffffe000201a1c <handle_exception>
(gdb)
寄存器的含义参考《The RISC-V Instruction Set Manual: Volume II Privileged Architecture Version 20240411》,这里模式为0,direct,即所有中断都走stvec值对应的位置,然后处理函数里面再去判断是什么中断。
如果配置为vec模式,则产生中断时根据中断号跳转到stvec+中断号*4的位置执行。
drivers/irqchip/irq-riscv-intc.c中
rc = set_handle_irq(&riscv_intc_irq);
调用路径如下
(gdb) hb set_handle_irq
Hardware assisted breakpoint 1 at 0xffffffe000008a66: file ../kernel/irq/handle.c, line 223.
(gdb) c
Continuing.
Breakpoint 1, set_handle_irq (handle_irq=handle_irq@entry=0xffffffe0005cfe7e
) at ../kernel/irq/handle.c:223
␦␦/home/qinyunti/tianshan_sdk/linux/linux-custom/kernel/irq/handle.c:223:5850:beg:0xffffffe000008a66
(gdb) bt
#0 set_handle_irq (handle_irq=handle_irq@entry=0xffffffe0005cfe7e
) at ../kernel/irq/handle.c:223 #1 0xffffffe000019c46 in riscv_intc_init (node=
, parent= ) at ../drivers/irqchip/irq-riscv-intc.c:122
#2 0xffffffe000023d68 in of_irq_init (matches=
) at ../drivers/of/irq.c:540 #3 0xffffffe000019bae in irqchip_init () at ../drivers/irqchip/irqchip.c:31
#4 0xffffffe0000044f4 in init_IRQ () at ../arch/riscv/kernel/irq.c:21
#5 0xffffffe000002a86 in start_kernel () at ../init/main.c:951
#6 0xffffffe000002092 in _start_kernel () at ../arch/riscv/kernel/head.S:281
Backtrace stopped: frame did not save the PC
(gdb)
即设置全局变量handle_arch_irq=riscv_intc_irq
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq)
return -EBUSY;
handle_arch_irq = handle_irq;
return 0;
}
#endif
初始化对应流程如下
执行过程见后面的”GDB跟踪运行到scheduler_tick的流程”
对应如下
这里是核心节点,用于根据设备树信息注册定时器。
drivers/clocksource/Makefile中如下编译文 timer-riscv.c
obj-$(CONFIG_RISCV_TIMER) += timer-riscv.o
linux/linux-custom/arch/riscv/Kconfig中如果是RISCV自动选择
config RISCV
def_bool y
select RISCV_TIMER if RISCV_SBI
drivers/clocksource/Kconfig中
config RISCV_TIMER
bool "Timer for the RISC-V platform" if COMPILE_TEST
depends on GENERIC_SCHED_CLOCK && RISCV && RISCV_SBI
select TIMER_PROBE
select TIMER_OF
help
This enables the per-hart timer built into all RISC-V systems, which
is accessed via both the SBI and the rdcycle instruction. This is
required for all RISC-V systems.
menuconfig之后
output/.config中
CONFIG_RISCV_TIMER=y
output/include/generated/autoconf.h中
#define CONFIG_RISCV_TIMER 1
源码drivers/clocksource/timer-riscv.c中
TIMER_OF_DECLARE(riscv_timer, "riscv", riscv_timer_init_dt);
include/linux/clocksource.h中
OF_DECLARE_1_RET(timer, name, compat, fn)
include/linux/of.h中
_OF_DECLARE(table, name, compat, fn, of_init_fn_1_ret)
所以展开为
_OF_DECLARE(timer, riscv_timer, "riscv", riscv_timer_init_dt, of_init_fn_1_ret)
继续展开include/linux/of.h中
static const struct of_device_id __of_table_
__used __section("__"
__aligned(__alignof__(struct of_device_id)) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
static const struct of_device_id __of_table_
__attribute__((unused)) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
展开为
static const struct of_device_id __of_riscv_timer \
__used __section("__timer_of_table") \
__aligned(__alignof__(struct of_device_id)) \
= { .compatible = "riscv", \
.data = (riscv_timer_init_dt== (of_init_fn_1_ret)NULL) ? riscv_timer_init_dt: riscv_timer_init_dt}
其中结构体在include/linux/mod_devicetable.h中定义如下
/*
* Struct used for matching a device
*/
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
所以这里在段__timer_of_table放了一个结构体变量
static const struct of_device_id __of_riscv_timer
内容
.compatible = "riscv"
.data=riscv_timer_init_dt
drivers/clocksource/timer-probe.c中申明变量即可访问该段
extern struct of_device_id __timer_of_table[];
linux/linux-custom/include/asm-generic/vmlinux.lds.h中
#define ___OF_TABLE(cfg, name) _OF_TABLE_##cfg(name)
#define __OF_TABLE(cfg, name) ___OF_TABLE(cfg, name)
#define OF_TABLE(cfg, name) __OF_TABLE(IS_ENABLED(cfg), name)
#define _OF_TABLE_0(name)
#define _OF_TABLE_1(name) \
. = ALIGN(8); \
__##name##_of_table = .; \
KEEP(*(__##name##_of_table)) \
KEEP(*(__##name##_of_table_end))
#define TIMER_OF_TABLES() OF_TABLE(CONFIG_TIMER_OF, timer)
init and exit section handling */
#define INIT_DATA \
KEEP(*(SORT(___kentry+*))) \
*(.init.data init.data.*) \
MEM_DISCARD(init.data*) \
KERNEL_CTORS() \
MCOUNT_REC() \
*(.init.rodata .init.rodata.*) \
FTRACE_EVENTS() \
TRACE_SYSCALLS() \
KPROBE_BLACKLIST() \
ERROR_INJECT_WHITELIST() \
MEM_DISCARD(init.rodata) \
CLK_OF_TABLES() \
RESERVEDMEM_OF_TABLES() \
TIMER_OF_TABLES() \
CPU_METHOD_OF_TABLES() \
CPUIDLE_METHOD_OF_TABLES() \
KERNEL_DTB() \
IRQCHIP_OF_MATCH_TABLE() \
ACPI_PROBE_TABLE(irqchip) \
ACPI_PROBE_TABLE(timer) \
THERMAL_TABLE(governor) \
EARLYCON_TABLE() \
LSM_TABLE() \
EARLY_LSM_TABLE() \
KUNIT_TABLE()
所以最终链接脚本output/arch/riscv/kernel/vmlinux.lds中
.init.data中
. = ALIGN(8); __timer_of_table = .; KEEP(*(__timer_of_table)) KEEP(*(__timer_of_table_end))
以下代码中
void __init timer_probe(void)
{
struct device_node *np;
const struct of_device_id *match;
of_init_fn_1_ret init_func_ret;
unsigned timers = 0;
int ret;
for_each_matching_node_and_match(np, __timer_of_table, &match) {
if (!of_device_is_available(np))
continue;
init_func_ret = match->data;
ret = init_func_ret(np);
if (ret) {
if (ret != -EPROBE_DEFER)
pr_err("Failed to initialize '%pOF': %d\n", np,
ret);
continue;
}
timers++;
}
timers += acpi_probe_device_table(timer);
if (!timers)
pr_crit("%s: no matching timers found\n", __func__);
}
for_each_matching_node_and_match遍历__timer_of_table
如果设备树中读出的match和代码中的match匹配
则调用data处的函数,即riscv_timer_init_dt
init_func_ret = match->data;
ret = init_func_ret(np);
这里是找到段中对应的结构体
struct device_node *of_find_matching_node_and_match(struct device_node *from,
const struct of_device_id *matches,
const struct of_device_id **match)
{
struct device_node *np;
const struct of_device_id *m;
unsigned long flags;
if (match)
*match = NULL;
raw_spin_lock_irqsave(&devtree_lock, flags);
for_each_of_allnodes_from(from, np) {
m = __of_match_node(matches, np);
if (m && of_node_get(np)) {
if (match)
*match = m;
break;
}
}
of_node_put(from);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
}
__of_match_node函数中要比对compatible
static
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
const struct device_node *node)
{
const struct of_device_id *best_match = NULL;
int score, best_score = 0;
if (!matches)
return NULL;
for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
score = __of_device_is_compatible(node, matches->compatible,
matches->type, matches->name);
if (score > best_score) {
best_match = matches;
best_score = score;
}
}
return best_match;
}
__of_device_is_compatible中找设备树节点有属性compatible的
然后比对of_node_name_eq
static int __of_device_is_compatible(const struct device_node *device,
const char *compat, const char *type, const char *name)
{
struct property *prop;
const char *cp;
int index = 0, score = 0;
/* Compatible match has highest priority */
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score)
return 0;
}
/* Matching type is better than matching name */
if (type && type[0]) {
if (!__of_node_is_type(device, type))
return 0;
score += 2;
}
/* Matching name is a bit better than not */
if (name && name[0]) {
if (!of_node_name_eq(device, name))
return 0;
score++;
}
return score;
}
即strcmp比较
static bool __of_node_is_type(const struct device_node *np, const char *type)
{
const char *match = __of_get_property(np, "device_type", NULL);
return np && match && type && !strcmp(match, type);
}
对应设备树如下
和段中的结构体对应
static const struct of_device_id __of_riscv_timer
{.compatible = "riscv"
.data=riscv_timer_init_dt}
对应如下部分
riscv_timer_interrupt时关闭定时器中断
/* called directly from the low-level interrupt handler */
static irqreturn_t riscv_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *evdev = this_cpu_ptr(&riscv_clock_event);
csr_clear(CSR_IE, IE_TIE);
evdev->event_handler(evdev);
return IRQ_HANDLED;
}
对应如下进入中断处理时先关中断,然后处理完最后设置下一次中断时间开中断
drivers/clocksource/timer-riscv.c中
static int riscv_clock_next_event(unsigned long delta,
struct clock_event_device *ce)
{
csr_set(CSR_IE, IE_TIE);
sbi_set_timer(get_cycles64() + delta);
return 0;
}
调用sbi设置sbi_set_timer
默认设备初始化了 .set_next_event = riscv_clock_next_event,
static DEFINE_PER_CPU(struct clock_event_device, riscv_clock_event) = {
.name = "riscv_timer_clockevent",
.features = CLOCK_EVT_FEAT_ONESHOT,
.rating = 100,
.set_next_event = riscv_clock_next_event,
};
调用路径如下
初始化时,第一次配置
第一产生中断后,如下路径
Breakpoint 1, riscv_clock_next_event (delta=40000, ce=0xffffffe007c2cd80) at ../drivers/clocksource/timer-riscv.c:27
␦␦/home/qinyunti/tianshan_sdk/linux/linux-custom/drivers/clocksource/timer-riscv.c:27:793:beg:0xffffffe0006fd20e
(gdb) bt
#0 riscv_clock_next_event (delta=40000, ce=0xffffffe007c2cd80) at ../drivers/clocksource/timer-riscv.c:27
#1 0xffffffe00027d796 in clockevents_program_event (dev=dev@entry=0xffffffe007c2cd80, expires=expires@entry=8000000,
force=force@entry=false) at ../kernel/time/clockevents.c:334
#2 0xffffffe00027dcf8 in tick_handle_periodic (dev=0xffffffe007c2cd80) at ../kernel/time/tick-common.c:132
#3 0xffffffe0006fd14a in riscv_timer_interrupt (irq=
out >, dev_id=out>) at ../drivers/clocksource/timer-riscv.c:88
#4 0xffffffe00025ecae in handle_percpu_devid_irq (desc=0xffffffe001e17e00) at ../arch/riscv/include/asm/current.h:31
#5 0xffffffe000259b24 in generic_handle_irq_desc (desc=
out >) at ../include/linux/irqdesc.h:152#6 generic_handle_irq (irq=
out >) at ../kernel/irq/irqdesc.c:650#7 __handle_domain_irq (domain=0xffffffe001e17800, hwirq=
out >, lookup=lookup@entry=true, regs=out>) at ../kernel/irq/irqdesc.c:687
#8 0xffffffe0005cfeb6 in handle_domain_irq (regs=
out >, hwirq=out>, domain= out>) at ../include/linux/irqdesc.h:170
#9 riscv_intc_irq (regs=
out >) at ../drivers/irqchip/irq-riscv-intc.c:40#10 0xffffffe000201b5e in handle_exception () at ../arch/riscv/kernel/entry.S:243
Backtrace stopped: frame did not save the PC
(gdb)
间隔时间
这里使用radix_tree关联中断号irq和其对应的信息desc。
关键数据是全局变量
static RADIX_TREE(irq_desc_tree, GFP_KERNEL);
即树结构,初始化时在该树下添加索引irq对应desc信息,中断时从索引irq处读出desc,回调desc->handle_irq处理函数。
整体框图如下
其中1是”查找desc 回调handle_percpu_devid_irq”
2是”创建desc添加到irq_desc_tree的索引irq处”
3是修改irq对应desc的handle_irq为handle_percpu_devid_irq
下面通过GDB分别跟踪上述三个过程。
0 radix_tree_lookup (root=root@entry=0xffffffe001a89178
, index=index@entry=5) at ../lib/radix-tree.c:817
1 0xffffffe000259d32 in irq_to_desc (irq=5) at ../kernel/irq/irqdesc.c:353
2 irq_set_percpu_devid_partition (irq=
, affinity=affinity@entry=0x0) at ../kernel/irq/irqdesc.c:910 3 0xffffffe000259d9e in irq_set_percpu_devid (irq=irq@entry=5) at ../kernel/irq/irqdesc.c:934
4 0xffffffe0005cfefc in riscv_intc_domain_map (d=0xffffffe001e17800, irq=
, hwirq=5) at ../drivers/irqchip/irq-riscv-intc.c:83
5 0xffffffe00026010a in irq_domain_associate (domain=domain@entry=0xffffffe001e17800, virq=virq@entry=5,
hwirq=hwirq@entry=5) at ../kernel/irq/irqdomain.c:537
6 0xffffffe00026090e in irq_create_mapping_affinity (domain=0xffffffe001e17800, hwirq=hwirq@entry=5,
affinity=affinity@entry=0x0) at ../kernel/irq/irqdomain.c:672
7 0xffffffe00002239e in irq_create_mapping (hwirq=5, host=
) at ../include/linux/irqdomain.h:396 8 riscv_timer_init_dt (n=0xffffffe007c3d818) at ../drivers/clocksource/timer-riscv.c:128
9 0xffffffe00002221e in timer_probe () at ../drivers/clocksource/timer-probe.c:30
10 0xffffffe000004a74 in time_init () at ../arch/riscv/kernel/time.c:27
11 0xffffffe000002ae6 in start_kernel () at ../init/main.c:972
12 0xffffffe000002092 in _start_kernel () at ../arch/riscv/kernel/head.S:281
对应流程如下
如下位置是分别是创建desc添加到tree和修改handle_irq。
(gdb) bt
#0 radix_tree_insert (root=root@entry=0xffffffe001a89178
, index=index@entry=5, item=item@entry=0xffffffe001e17e00) at ../lib/radix-tree.c:708
#1 0xffffffe000873ef2 in irq_insert_desc (desc=0xffffffe001e17e00, irq=5) at ../kernel/irq/irqdesc.c:348
#2 alloc_descs (node=0, owner=0x0, affinity=0x0, cnt=
, start=5) at ../kernel/irq/irqdesc.c:498 #3 __irq_alloc_descs (irq=irq@entry=-1, from=
, cnt=cnt@entry=1, node=node@entry=-1, owner=owner@entry=0x0, affinity=affinity@entry=0x0) at ../kernel/irq/irqdesc.c:806
#4 0xffffffe00025fcfe in irq_domain_alloc_descs (cnt=cnt@entry=1, hwirq=hwirq@entry=5, node=node@entry=-1,
affinity=affinity@entry=0x0, virq=-1) at ../kernel/irq/irqdomain.c:1028
#5 0xffffffe0002608fc in irq_domain_alloc_descs (affinity=0x0, node=-1, hwirq=5, cnt=1, virq=-1)
at ../kernel/irq/irqdomain.c:1021
#6 irq_create_mapping_affinity (domain=0xffffffe001e17800, hwirq=hwirq@entry=5, affinity=affinity@entry=0x0)
at ../kernel/irq/irqdomain.c:665
#7 0xffffffe00002239e in irq_create_mapping (hwirq=5, host=
) at ../include/linux/irqdomain.h:396 #8 riscv_timer_init_dt (n=0xffffffe007c3d818) at ../drivers/clocksource/timer-riscv.c:128
#9 0xffffffe00002221e in timer_probe () at ../drivers/clocksource/timer-probe.c:30
#10 0xffffffe000004a74 in time_init () at ../arch/riscv/kernel/time.c:27
#11 0xffffffe000002ae6 in start_kernel () at ../init/main.c:972
#12 0xffffffe000002092 in _start_kernel () at ../arch/riscv/kernel/head.S:281
Backtrace stopped: frame did not save the PC
(gdb)
对应流程如下
#0 0xffffffe000873edc in irq_insert_desc (desc=0xffffffe001e17e00, irq=5) at ../kernel/irq/irqdesc.c:348
#1 alloc_descs (node=0, owner=0x0, affinity=0x0, cnt=
, start=5) at ../kernel/irq/irqdesc.c:498 #2 __irq_alloc_descs (irq=irq@entry=-1, from=
, cnt=cnt@entry=1, node=node@entry=-1, owner=owner@entry=0x0, affinity=affinity@entry=0x0) at ../kernel/irq/irqdesc.c:806
#3 0xffffffe00025fcfe in irq_domain_alloc_descs (cnt=cnt@entry=1, hwirq=hwirq@entry=5, node=node@entry=-1,
affinity=affinity@entry=0x0, virq=-1) at ../kernel/irq/irqdomain.c:1028
#4 0xffffffe0002608fc in irq_domain_alloc_descs (affinity=0x0, node=-1, hwirq=5, cnt=1, virq=-1)
at ../kernel/irq/irqdomain.c:1021
#5 irq_create_mapping_affinity (domain=0xffffffe001e17800, hwirq=hwirq@entry=5, affinity=affinity@entry=0x0)
at ../kernel/irq/irqdomain.c:665
#6 0xffffffe00002239e in irq_create_mapping (hwirq=5, host=
) at ../include/linux/irqdomain.h:396 #7 riscv_timer_init_dt (n=0xffffffe007c3d818) at ../drivers/clocksource/timer-riscv.c:128
#8 0xffffffe00002221e in timer_probe () at ../drivers/clocksource/timer-probe.c:30
#9 0xffffffe000004a74 in time_init () at ../arch/riscv/kernel/time.c:27
#10 0xffffffe000002ae6 in start_kernel () at ../init/main.c:972
#11 0xffffffe000002092 in _start_kernel () at ../arch/riscv/kernel/head.S:281
Backtrace stopped: frame did not save the PC
p *(struct irq_desc *) 0xffffffe001e17e00
{irq_common_data = {state_use_accessors = 196608, handler_data = 0x0, msi_desc = 0x0, affinity = {{bits = { =
irq_data = {mask = 0, irq = 5, hwirq = 0, common = 0xffffffe001e17e00,
chip = 0xffffffe001a89548
, domain = 0x0, parent_data = 0x0, chip_data = 0x0}, kstat_irqs = 0xffffffe000045b5c, handle_irq = 0xffffffe000259f52
, action = 0x0, status_use_accessors = 0, core_internal_state__do_not_mess_with_it = 0, depth = 1, wake_depth = 0, tot_count = 0,
irq_count = 0, last_unhandled = 0, irqs_unhandled = 0, threads_handled = {counter = 0}, threads_handled_last = 0,
lock = {raw_lock = {lock = 0}}, percpu_enabled = 0x0, percpu_affinity = 0x0, affinity_hint = 0x0,
affinity_notify = 0x0, threads_oneshot = 0, threads_active = {counter = 0}, wait_for_threads = {lock = {{rlock = {
raw_lock = {lock = 0}}}}, head = {next = 0x0, prev = 0x0}}, dir = 0x0, rcu = {next = 0x0, func = 0x0},
kobj = {name = 0x0, entry = {next = 0xffffffe001e17f08, prev = 0xffffffe001e17f08}, parent = 0x0, kset = 0x0,
ktype = 0xffffffe001a891a8
, sd = 0x0, kref = {refcount = {refs = {counter = 1}}}, state_initialized = 1, state_in_sysfs = 0, state_add_uevent_sent = 0, state_remove_uevent_sent = 0,
uevent_suppress = 0}, request_mutex = {owner = {counter = 0}, wait_lock = {{rlock = {raw_lock = {lock = 0}}}},
osq = {tail = {counter = 0}}, wait_list = {next = 0xffffffe001e17f50, prev = 0xffffffe001e17f50}}, parent_irq = 0,
owner = 0x0, name = 0x0}
(gdb)
对应流程如下
定时器中断产生时进入
arch/riscv/kernel/entry.S的handle_exception
读取s4寄存器
csrr s4, CSR_CAUSE
这里S模式运行则是
# define CSR_CAUSE CSR_SCAUSE
i r scause
scause 0x8000000000000005 -9223372036854775803
(gdb)
Scause高位为1表示是中断,否则是异常。
所以如下代码即用于判断是中断还是异常
/*
MSB of cause differentiates between
interrupts and exceptions
*/
bge s4, zero, 1f
la ra, ret_from_exception
Handle interrupts */
move a0, sp /* pt_regs */
la a1, handle_arch_irq
REG_L a1, (a1)
jr a1
1:
bge s4, zero, 1f 如果s4即scause大于等于0(即最高位为0),则跳到后面的1:处执行即异常处理。否则继续后面的中断处理。
这里最高位为1是负数所以是中断,继续
设置ra返回地址,ret时返回到这里执行
la ra, ret_from_exception
move a0, sp /* pt_regs */
第一个参数a0为sp
la a1, handle_arch_irq 获取变量地址
即之前设置的变量handle_arch_irq其值为riscv_intc_irq
然后获取该变量地址处的值REG_L a1, (a1)
此时a1就是riscv_intc_irq了
然后
jr a1跳到a1即riscv_intc_irq处运行。
linux/linux-custom/drivers/irqchip/irq-riscv-intc.c中
handle_domain_irq
static asmlinkage void riscv_intc_irq(struct pt_regs *regs)
{
unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG;
if (unlikely(cause >= BITS_PER_LONG))
panic("unexpected interrupt cause");
switch (cause) {
case RV_IRQ_SOFT:
/*
* We only use software interrupts to pass IPIs, so if a
* non-SMP system gets one, then we don't know what to do.
*/
handle_IPI(regs);
break;
default:
handle_domain_irq(intc_domain, cause, regs);
break;
}
}
struct pt_regs *regs的regs即sp,
linux/linux-custom/arch/riscv/include/asm/ptrace.h中
struct pt_regs {
unsigned long epc;
unsigned long ra;
unsigned long sp;
unsigned long gp;
unsigned long tp;
unsigned long t0;
unsigned long t1;
unsigned long t2;
unsigned long s0;
unsigned long s1;
unsigned long a0;
unsigned long a1;
unsigned long a2;
unsigned long a3;
unsigned long a4;
unsigned long a5;
unsigned long a6;
unsigned long a7;
unsigned long s2;
unsigned long s3;
unsigned long s4;
unsigned long s5;
unsigned long s6;
unsigned long s7;
unsigned long s8;
unsigned long s9;
unsigned long s10;
unsigned long s11;
unsigned long t3;
unsigned long t4;
unsigned long t5;
unsigned long t6;
/* Supervisor/Machine CSRs */
unsigned long status;
unsigned long badaddr;
unsigned long cause;
/* a0 value before the syscall */
unsigned long orig_a0;
};
和linux/linux-custom/arch/riscv/kernel/entry.S中对应
_save_context:
REG_S sp, TASK_TI_USER_SP(tp)
REG_L sp, TASK_TI_KERNEL_SP(tp)
addi sp, sp, -(PT_SIZE_ON_STACK)
REG_S x1, PT_RA(sp)
REG_S x3, PT_GP(sp)
REG_S x5, PT_T0(sp)
REG_S x6, PT_T1(sp)
REG_S x7, PT_T2(sp)
REG_S x8, PT_S0(sp)
REG_S x9, PT_S1(sp)
REG_S x10, PT_A0(sp)
REG_S x11, PT_A1(sp)
REG_S x12, PT_A2(sp)
REG_S x13, PT_A3(sp)
REG_S x14, PT_A4(sp)
REG_S x15, PT_A5(sp)
REG_S x16, PT_A6(sp)
REG_S x17, PT_A7(sp)
REG_S x18, PT_S2(sp)
REG_S x19, PT_S3(sp)
REG_S x20, PT_S4(sp)
REG_S x21, PT_S5(sp)
REG_S x22, PT_S6(sp)
REG_S x23, PT_S7(sp)
REG_S x24, PT_S8(sp)
REG_S x25, PT_S9(sp)
REG_S x26, PT_S10(sp)
REG_S x27, PT_S11(sp)
REG_S x28, PT_T3(sp)
REG_S x29, PT_T4(sp)
REG_S x30, PT_T5(sp)
REG_S x31, PT_T6(sp)
/*
Disable user-mode memory access as it should only be set in the
actual user copy routines.
*
Disable the FPU to detect illegal usage of floating point in kernel
space.
*/
li t0, SR_SUM | SR_FS | SR_VS
REG_L s0, TASK_TI_USER_SP(tp)
csrrc s1, CSR_STATUS, t0 //status
csrr s2, CSR_EPC
csrr s3, CSR_TVAL //badaddr
csrr s4, CSR_CAUSE //cause
csrr s5, CSR_SCRATCH
REG_S s0, PT_SP(sp)
REG_S s1, PT_STATUS(sp)
REG_S s2, PT_EPC(sp)
REG_S s3, PT_BADADDR(sp)
REG_S s4, PT_CAUSE(sp)
REG_S s5, PT_TP(sp)
所以先获取cause
unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG;
其中linux/linux-custom/arch/riscv/include/asm/csr.h中
#define CAUSE_IRQ_FLAG (_AC(1, UL) << (__riscv_xlen - 1))
即不取最高位,最高位表示是中断还是异常,低位表示中断号。
这里中断号是5会走
default:
handle_domain_irq(intc_domain, cause, regs);
break;
即include/linux/irqdesc.h中
/*
* Convert a HW interrupt number to a logical one using a IRQ domain,
* and handle the result interrupt number. Return -EINVAL if
* conversion failed. Providing a NULL domain indicates that the
* conversion has already been done.
*/
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs);
static inline int handle_domain_irq(struct irq_domain *domain,
unsigned int hwirq, struct pt_regs *regs)
{
return __handle_domain_irq(domain, hwirq, true, regs);
}
进入linux/linux-custom/kernel/irq/irqdesc.c
/**
* __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain
* @domain: The domain where to perform the lookup
* @hwirq: The HW irq number to convert to a logical one
* @lookup: Whether to perform the domain lookup or not
* @regs: Register file coming from the low-level handling code
*
* Returns: 0 on success, or -EINVAL if conversion has failed
*/
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();
if (lookup)
irq = irq_find_mapping(domain, hwirq);
/*
* 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;
}
此时irq即为5
unsigned int irq = hwirq;
继续走generic_handle_irq(irq);
继续走
generic_handle_irq_desc(desc);
/*
* 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);
}
此时
(gdb) p (*(struct irq_desc *) 0xffffffe001e17e00).handle_irq
$9 = (irq_flow_handler_t) 0xffffffe00025ec22
(gdb)
进入handle_percpu_devid_irq
在以下时
trace_irq_handler_entry(irq, action);
res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));
(gdb) p action->handler
$2 = (irq_handler_t) 0xffffffe0006fd118
(gdb)
drivers/clocksource/timer-riscv.c中
/* called directly from the low-level interrupt handler */
static irqreturn_t riscv_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *evdev = this_cpu_ptr(&riscv_clock_event);
csr_clear(CSR_IE, IE_TIE);
evdev->event_handler(evdev);
return IRQ_HANDLED;
}
此时
(gdb) p evdev->event_handler
$3 = (void (*)(struct clock_event_device *)) 0xffffffe00027dca6
(gdb)
进入kernel/time/tick-common.c中
/*
* Event handler for periodic ticks
*/
void tick_handle_periodic(struct clock_event_device *dev)
{
int cpu = smp_processor_id();
ktime_t next = dev->next_event;
tick_periodic(cpu);
/*
* The cpu might have transitioned to HIGHRES or NOHZ mode via
* update_process_times() -> run_local_timers() ->
* hrtimer_run_queues().
*/
if (dev->event_handler != tick_handle_periodic)
return;
if (!clockevent_state_oneshot(dev))
return;
for (;;) {
/*
* Setup the next period for devices, which do not have
* periodic mode:
*/
next = ktime_add(next, tick_period);
if (!clockevents_program_event(dev, next, false))
return;
/*
* Have to be careful here. If we're in oneshot mode,
* before we call tick_periodic() in a loop, we need
* to be sure we're using a real hardware clocksource.
* Otherwise we could get trapped in an infinite
* loop, as the tick_periodic() increments jiffies,
* which then will increment time, possibly causing
* the loop to trigger again and again.
*/
if (timekeeping_valid_for_hres())
tick_periodic(cpu);
}
}
调用tick_periodic(cpu);
/*
* Periodic tick
*/
static void tick_periodic(int cpu)
{
if (tick_do_timer_cpu == cpu) {
raw_spin_lock(&jiffies_lock);
write_seqcount_begin(&jiffies_seq);
/* Keep track of the next tick event */
tick_next_period = ktime_add(tick_next_period, tick_period);
do_timer(1);
write_seqcount_end(&jiffies_seq);
raw_spin_unlock(&jiffies_lock);
update_wall_time();
}
update_process_times(user_mode(get_irq_regs()));
profile_tick(CPU_PROFILING);
}
调用update_process_times(user_mode(get_irq_regs()));
linux/linux-custom/kernel/time/timer.c中
/*
* Called from the timer interrupt handler to charge one tick to the current
* process. user_tick is 1 if the tick is user time, 0 for system.
*/
void update_process_times(int user_tick)
{
struct task_struct *p = current;
PRANDOM_ADD_NOISE(jiffies, user_tick, p, 0);
/* Note: this timer irq context must be accounted for as well. */
account_process_tick(p, user_tick);
run_local_timers();
rcu_sched_clock_irq(user_tick);
if (in_irq())
irq_work_tick();
scheduler_tick();
if (IS_ENABLED(CONFIG_POSIX_TIMERS))
run_posix_cpu_timers();
}
最后调用
scheduler_tick();
本文分享了riscv中使用stimer作为tick的相关内容,
重点关注流程,即如何注册回调,如何初始化,执行路径,暂时未关注定时器模块相关的细节比如间隔时间如何计算等,后续再分享。前期适配调试先保证流程跑通,重点关注执行流,借助GDB可以方便查看整个过程。