RISCVLinuxkernel启动代码分析之八:tick定时器相关流程

原创 嵌入式Lee 2024-11-21 17:15

一. 前言

LinuxRTOS需要一个tick心跳进行调度处理,linux中的tick处理函数是scheduler_tick。本文从整体流程上来分析下执行到scheduler_tick的流程,以及过程中定时器中断相关的回调是如何注册的,以stime即中断号为5为例。

先上流程图方便对照

二. 设置异常入口handle_exception

arch/riscv/kernel/head.S

.align 2setup_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_commonsecondary_start_common:
#ifdef CONFIG_MMU /* Enable virtual memory and relocate to virtual address */ la a0, swapper_pg_dir call relocate#endif call setup_trap_vector tail smp_callin#endif /* CONFIG_SMP */

查看寄存器的值

(gdb) i r stvecstvec          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》,这里模式为0direct,即所有中断都走stvec值对应的位置,然后处理函数里面再去判断是什么中断。

如果配置为vec模式,则产生中断时根据中断号跳转到stvec+中断号*4的位置执行。

.设置中断处理c入口riscv_intc_irq与调用过程

drivers/irqchip/irq-riscv-intc.c

rc = set_handle_irq(&riscv_intc_irq);

调用路径如下

(gdb) hb set_handle_irqHardware assisted breakpoint 1 at 0xffffffe000008a66: file ../kernel/irq/handle.c, line 223.(gdb) cContinuing.
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:281Backtrace stopped: frame did not save the PC(gdb)

即设置全局变量handle_arch_irq=riscv_intc_irq

#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLERint __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的流程

对应如下

.riscv_timer_init_dt注册调用过程

这里是核心节点,用于根据设备树信息注册定时器。

drivers/clocksource/Makefile中如下编译文 timer-riscv.c

obj-$(CONFIG_RISCV_TIMER) += timer-riscv.o

linux/linux-custom/arch/riscv/Kconfig中如果是RISCV自动选择

config RISCVdef_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

#define TIMER_OF_DECLARE(name, compat, fn) \    OF_DECLARE_1_RET(timer, name, compat, fn)    

include/linux/of.h

#define OF_DECLARE_1_RET(table, name, compat, fn) \        _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

#if defined(CONFIG_OF) && !defined(MODULE)#define _OF_DECLARE(table, name, compat, fn, fn_type)           \    static const struct of_device_id __of_table_##name      \        __used __section("__" #table "_of_table")       \        __aligned(__alignof__(struct of_device_id))     \         = { .compatible = compat,              \             .data = (fn == (fn_type)NULL) ? fn : fn  }#else#define _OF_DECLARE(table, name, compat, fn, fn_type)           \    static const struct of_device_id __of_table_##name      \        __attribute__((unused))                 \         = { .compatible = compat,              \             .data = (fn == (fn_type)NULL) ? fn : fn }#endif

展开为

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

staticconst 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:243Backtrace stopped: frame did not save the PC(gdb)

间隔时间

.设置中断号对应的deschandle_irq

这里使用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对应deschandle_irqhandle_percpu_devid_irq

下面通过GDB分别跟踪上述三个过程。

7.1 查找desc 回调handle_percpu_devid_irq

#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

对应流程如下

7.2创建desc注册到irq_desc_tree并修改handle

如下位置是分别是创建desc添加到tree和修改handle_irq

7.2.1创建desc添加到irq_desc_tree的索引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:281Backtrace stopped: frame did not save the PC(gdb)

对应流程如下

7.2.2修改irq对应deschandle_irqhandle_percpu_devid_irq

#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:281Backtrace stopped: frame did not save the PC
p *(struct irq_desc *) 0xffffffe001e17e00$6 = {irq_common_data = {state_use_accessors = 196608, handler_data = 0x0, msi_desc = 0x0, affinity = {{bits = { 18446744073709551615}}}}, 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)

对应流程如下

.GDB跟踪运行到scheduler_tick的流程

定时器中断产生时进入

arch/riscv/kernel/entry.Shandle_exception

读取s4寄存器

csrr s4, CSR_CAUSE

这里S模式运行则是

# define CSR_CAUSE CSR_SCAUSE

(gdb) i r scausescause         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 a11:

bge s4, zero, 1f 如果s4scause大于等于0(即最高位为0),则跳到后面的1:处执行即异常处理。否则继续后面的中断处理。

这里最高位为1是负数所以是中断,继续

设置ra返回地址,ret时返回到这里执行

la ra, ret_from_exception

move a0, sp /* pt_regs */

第一个参数a0sp

la a1, handle_arch_irq 获取变量地址

即之前设置的变量handle_arch_irq其值为riscv_intc_irq

然后获取该变量地址处的值REG_L a1, (a1)

此时a1就是riscv_intc_irq

然后

jr a1跳到a1riscv_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) {#ifdef CONFIG_SMP    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;#endif    default:        handle_domain_irq(intc_domain, cause, regs);        break;    }}

struct pt_regs *regsregssp

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();
#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;}

此时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);
#if defined(CONFIG_HIGH_RES_TIMERS) || defined(CONFIG_NO_HZ_COMMON)    /*     * 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;#endif
    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);#ifdef CONFIG_IRQ_WORK    if (in_irq())        irq_work_tick();#endif    scheduler_tick();    if (IS_ENABLED(CONFIG_POSIX_TIMERS))        run_posix_cpu_timers();}

最后调用

scheduler_tick();

.总结

本文分享了riscv中使用stimer作为tick的相关内容,

重点关注流程,即如何注册回调,如何初始化,执行路径,暂时未关注定时器模块相关的细节比如间隔时间如何计算等,后续再分享。前期适配调试先保证流程跑通,重点关注执行流,借助GDB可以方便查看整个过程。


















评论
  • 日前,商务部等部门办公厅印发《手机、平板、智能手表(手环)购新补贴实施方案》明确,个人消费者购买手机、平板、智能手表(手环)3类数码产品(单件销售价格不超过6000元),可享受购新补贴。每人每类可补贴1件,每件补贴比例为减去生产、流通环节及移动运营商所有优惠后最终销售价格的15%,每件最高不超过500元。目前,京东已经做好了承接手机、平板等数码产品国补优惠的落地准备工作,未来随着各省市关于手机、平板等品类的国补开启,京东将第一时间率先上线,满足消费者的换新升级需求。为保障国补的真实有效发放,基于
    华尔街科技眼 2025-01-17 10:44 221浏览
  • 本文介绍瑞芯微开发板/主板Android配置APK默认开启性能模式方法,开启性能模式后,APK的CPU使用优先级会有所提高。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。源码修改修改源码根目录下文件device/rockchip/rk3562/package_performance.xml并添加以下内容,注意"+"号为添加内容,"com.tencent.mm"为AP
    Industio_触觉智能 2025-01-17 14:09 164浏览
  • 现在为止,我们已经完成了Purple Pi OH主板的串口调试和部分配件的连接,接下来,让我们趁热打铁,完成剩余配件的连接!注:配件连接前请断开主板所有供电,避免敏感电路损坏!1.1 耳机接口主板有一路OTMP 标准四节耳机座J6,具备进行音频输出及录音功能,接入耳机后声音将优先从耳机输出,如下图所示:1.21.2 相机接口MIPI CSI 接口如上图所示,支持OV5648 和OV8858 摄像头模组。接入摄像头模组后,使用系统相机软件打开相机拍照和录像,如下图所示:1.3 以太网接口主板有一路
    Industio_触觉智能 2025-01-20 11:04 150浏览
  • 随着消费者对汽车驾乘体验的要求不断攀升,汽车照明系统作为确保道路安全、提升驾驶体验以及实现车辆与环境交互的重要组成,日益受到业界的高度重视。近日,2024 DVN(上海)国际汽车照明研讨会圆满落幕。作为照明与传感创新的全球领导者,艾迈斯欧司朗受邀参与主题演讲,并现场展示了其多项前沿技术。本届研讨会汇聚来自全球各地400余名汽车、照明、光源及Tier 2供应商的专业人士及专家共聚一堂。在研讨会第一环节中,艾迈斯欧司朗系统解决方案工程副总裁 Joachim Reill以深厚的专业素养,主持该环节多位
    艾迈斯欧司朗 2025-01-16 20:51 198浏览
  • Ubuntu20.04默认情况下为root账号自动登录,本文介绍如何取消root账号自动登录,改为通过输入账号密码登录,使用触觉智能EVB3568鸿蒙开发板演示,搭载瑞芯微RK3568,四核A55处理器,主频2.0Ghz,1T算力NPU;支持OpenHarmony5.0及Linux、Android等操作系统,接口丰富,开发评估快人一步!添加新账号1、使用adduser命令来添加新用户,用户名以industio为例,系统会提示设置密码以及其他信息,您可以根据需要填写或跳过,命令如下:root@id
    Industio_触觉智能 2025-01-17 14:14 122浏览
  • 2024年是很平淡的一年,能保住饭碗就是万幸了,公司业绩不好,跳槽又不敢跳,还有一个原因就是老板对我们这些员工还是很好的,碍于人情也不能在公司困难时去雪上加霜。在工作其间遇到的大问题没有,小问题还是有不少,这里就举一两个来说一下。第一个就是,先看下下面的这个封装,你能猜出它的引脚间距是多少吗?这种排线座比较常规的是0.6mm间距(即排线是0.3mm间距)的,而这个规格也是我们用得最多的,所以我们按惯性思维来看的话,就会认为这个座子就是0.6mm间距的,这样往往就不会去细看规格书了,所以这次的运气
    wuliangu 2025-01-21 00:15 186浏览
  • 80,000人到访的国际大展上,艾迈斯欧司朗有哪些亮点?感未来,光无限。近日,在慕尼黑electronica 2024现场,ams OSRAM通过多款创新DEMO展示,以及数场前瞻洞察分享,全面展示自身融合传感器、发射器及集成电路技术,精准捕捉并呈现环境信息的卓越能力。同时,ams OSRAM通过展会期间与客户、用户等行业人士,以及媒体朋友的深度交流,向业界传达其以光电技术为笔、以创新为墨,书写智能未来的深度思考。electronica 2024electronica 2024构建了一个高度国际
    艾迈斯欧司朗 2025-01-16 20:45 434浏览
  • 临近春节,各方社交及应酬也变得多起来了,甚至一月份就排满了各式约见。有的是关系好的专业朋友的周末“恳谈会”,基本是关于2025年经济预判的话题,以及如何稳定工作等话题;但更多的预约是来自几个客户老板及副总裁们的见面,他们为今年的经济预判与企业发展焦虑而来。在聊天过程中,我发现今年的聊天有个很有意思的“点”,挺多人尤其关心我到底是怎么成长成现在的多领域风格的,还能掌握一些经济趋势的分析能力,到底学过哪些专业、在企业管过哪些具体事情?单单就这个一个月内,我就重复了数次“为什么”,再辅以我上次写的:《
    牛言喵语 2025-01-22 17:10 41浏览
  •  万万没想到!科幻电影中的人形机器人,正在一步步走进我们人类的日常生活中来了。1月17日,乐聚将第100台全尺寸人形机器人交付北汽越野车,再次吹响了人形机器人疯狂进厂打工的号角。无独有尔,银河通用机器人作为一家成立不到两年时间的创业公司,在短短一年多时间内推出革命性的第一代产品Galbot G1,这是一款轮式、双臂、身体可折叠的人形机器人,得到了美团战投、经纬创投、IDG资本等众多投资方的认可。作为一家成立仅仅只有两年多时间的企业,智元机器人也把机器人从梦想带进了现实。2024年8月1
    刘旷 2025-01-21 11:15 399浏览
  • 电竞鼠标应用环境与客户需求电竞行业近年来发展迅速,「鼠标延迟」已成为决定游戏体验与比赛结果的关键因素。从技术角度来看,传统鼠标的延迟大约为20毫秒,入门级电竞鼠标通常为5毫秒,而高阶电竞鼠标的延迟可降低至仅2毫秒。这些差异看似微小,但在竞技激烈的游戏中,尤其在对反应和速度要求极高的场景中,每一毫秒的优化都可能带来致胜的优势。电竞比赛的普及促使玩家更加渴望降低鼠标延迟以提升竞技表现。他们希望通过精确的测试,了解不同操作系统与设定对延迟的具体影响,并寻求最佳配置方案来获得竞技优势。这样的需求推动市场
    百佳泰测试实验室 2025-01-16 15:45 339浏览
  • 数字隔离芯片是一种实现电气隔离功能的集成电路,在工业自动化、汽车电子、光伏储能与电力通信等领域的电气系统中发挥着至关重要的作用。其不仅可令高、低压系统之间相互独立,提高低压系统的抗干扰能力,同时还可确保高、低压系统之间的安全交互,使系统稳定工作,并避免操作者遭受来自高压系统的电击伤害。典型数字隔离芯片的简化原理图值得一提的是,数字隔离芯片历经多年发展,其应用范围已十分广泛,凡涉及到在高、低压系统之间进行信号传输的场景中基本都需要应用到此种芯片。那么,电气工程师在进行电路设计时到底该如何评估选择一
    华普微HOPERF 2025-01-20 16:50 73浏览
  •     IPC-2581是基于ODB++标准、结合PCB行业特点而指定的PCB加工文件规范。    IPC-2581旨在替代CAM350格式,成为PCB加工行业的新的工业规范。    有一些免费软件,可以查看(不可修改)IPC-2581数据文件。这些软件典型用途是工艺校核。    1. Vu2581        出品:Downstream     
    电子知识打边炉 2025-01-22 11:12 53浏览
  • 嘿,咱来聊聊RISC-V MCU技术哈。 这RISC-V MCU技术呢,简单来说就是基于一个叫RISC-V的指令集架构做出的微控制器技术。RISC-V这个啊,2010年的时候,是加州大学伯克利分校的研究团队弄出来的,目的就是想搞个新的、开放的指令集架构,能跟上现代计算的需要。到了2015年,专门成立了个RISC-V基金会,让这个架构更标准,也更好地推广开了。这几年啊,这个RISC-V的生态系统发展得可快了,好多公司和机构都加入了RISC-V International,还推出了不少RISC-V
    丙丁先生 2025-01-21 12:10 112浏览
  •  光伏及击穿,都可视之为 复合的逆过程,但是,复合、光伏与击穿,不单是进程的方向相反,偏置状态也不一样,复合的工况,是正偏,光伏是零偏,击穿与漂移则是反偏,光伏的能源是外来的,而击穿消耗的是结区自身和电源的能量,漂移的载流子是 客席载流子,须借外延层才能引入,客席载流子 不受反偏PN结的空乏区阻碍,能漂不能漂,只取决于反偏PN结是否处于外延层的「射程」范围,而穿通的成因,则是因耗尽层的过度扩张,致使跟 端子、外延层或其他空乏区 碰触,当耗尽层融通,耐压 (反向阻断能力) 即告彻底丧失,
    MrCU204 2025-01-17 11:30 182浏览
  • 高速先生成员--黄刚这不马上就要过年了嘛,高速先生就不打算给大家上难度了,整一篇简单但很实用的文章给大伙瞧瞧好了。相信这个标题一出来,尤其对于PCB设计工程师来说,心就立马凉了半截。他们辛辛苦苦进行PCB的过孔设计,高速先生居然说设计多大的过孔他们不关心!另外估计这时候就跳出很多“挑刺”的粉丝了哈,因为翻看很多以往的文章,高速先生都表达了过孔孔径对高速性能的影响是很大的哦!咋滴,今天居然说孔径不关心了?别,别急哈,听高速先生在这篇文章中娓娓道来。首先还是要对各位设计工程师的设计表示肯定,毕竟像我
    一博科技 2025-01-21 16:17 100浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦