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可以方便查看整个过程。


















评论 (0)
  • 案例概况在丹麦哥本哈根,西门子工程师们成功完成了一项高安全设施的数据集成项目。他们利用宏集Cogent DataHub软件,将高安全设施内的设备和仪器与远程监控位置连接起来,让技术人员能够在不违反安全规定、不引入未经授权人员的情况下,远程操作所需设备。突破OPC 服务器的远程连接难题该项目最初看似是一个常规的 OPC 应用:目标是将高安全性设施中的冷水机(chiller)设备及其 OPC DA 服务器,与远程监控站的两套 SCADA 系统(作为 OPC DA 客户端)连接起来。然而,在实际实施过
    宏集科技 2025-03-27 13:20 109浏览
  •       知识产权保护对工程师的双向影响      正向的激励,保护了工程师的创新成果与权益,给企业带来了知识产权方面的收益,企业的创新和发明大都是工程师的劳动成果,他们的职务发明应当受到奖励和保护,是企业发展的重要源泉。专利同时也成了工程师职称评定的指标之一,专利体现了工程师的创新能力,在求职、竞聘技术岗位或参与重大项目时,专利证书能显著增强个人竞争力。专利将工程师的创意转化为受法律保护的“无形资产”,避免技术成果被他人抄袭或无偿使
    广州铁金刚 2025-03-25 11:48 181浏览
  • 长期以来,智能家居对于大众家庭而言就像空中楼阁一般,华而不实,更有甚者,还将智能家居认定为资本家的营销游戏。商家们举着“智慧家居、智慧办公”的口号,将原本价格亲民、能用几十年的家电器具包装成为了高档商品,而消费者们最终得到的却是家居设备之间缺乏互操作性、不同品牌生态之间互不兼容的碎片化体验。这种早期的生态割裂现象致使消费者们对智能家居兴趣缺失,也造就了“智能家居无用论”的刻板印象。然而,自Matter协议发布之后,“命运的齿轮”开始转动,智能家居中的生态割裂现象与品牌生态之间的隔阂正被基于IP架
    华普微HOPERF 2025-03-27 09:46 109浏览
  • 家电,在人们的日常生活中扮演着不可或缺的角色,也是提升人们幸福感的重要组成部分,那你了解家电的发展史吗?#70年代结婚流行“四大件”:手表、自行车、缝纫机,收音机,合成“三转一响”。#80年代随着改革开放的深化,中国经济开始飞速发展,黑白电视机、冰箱、洗衣机这“新三件”,成为了人们对生活的新诉求。#90年代彩电、冰箱、全自动洗衣机开始大量进入普通家庭,快速全面普及,90年代末,家电产品实现了从奢侈品到必需品的转变。#00年代至今00年代,随着人们追求高品质生活的愿望,常用的电视机、洗衣机等已经远
    启英AI平台 2025-03-25 14:12 89浏览
  • 六西格玛首先是作为一个量度质量水平的指标,它代表了近乎完美的质量的水平。如果你每天都吃一个苹果,有一间水果店的老板跟你说,他们所卖的苹果,质量达到六西格玛水平,换言之,他们每卖一百万个苹果,只会有3.4个是坏的。你算了一下,发现你如果要从这个店里买到一个坏苹果,需要805年。你会还会选择其他店吗?首先发明六西格玛这个词的人——比尔·史密斯(Bill Smith)他是摩托罗拉(Motorloa)的工程师,在追求这个近乎完美的质量水平的时候,发明了一套方法模型,开始时是MAIC,后来慢慢演变成DMA
    优思学院 2025-03-27 11:47 149浏览
  • 汽车导航系统市场及应用环境参照调研机构GII的研究报告中的市场预测,全球汽车导航系统市场预计将于 2030年达到472亿美元的市场规模,而2024年至2030年的年复合成长率则为可观的6.7%。汽车导航系统无疑已成为智能汽车不可或缺的重要功能之一。随着人们在日常生活中对汽车导航功能的日渐依赖,一旦出现定位不准确或地图错误等问题,就可能导致车主开错路线,平白浪费更多行车时间,不仅造成行车不便,甚或可能引发交通事故的发生。有鉴于此,如果想要提供消费者完善的使用者体验,在车辆开发阶段便针对汽车导航功能
    百佳泰测试实验室 2025-03-27 14:51 187浏览
  • 在智能语音产品的开发过程中,麦克风阵列的选型直接决定了用户体验的优劣。广州唯创电子提供的单麦克风与双麦克风解决方案,为不同场景下的语音交互需求提供了灵活选择。本文将深入解析两种方案的性能差异、适用场景及工程实现要点,为开发者提供系统化的设计决策依据。一、基础参数对比分析维度单麦克风方案双麦克风方案BOM成本¥1.2-2.5元¥4.8-6.5元信噪比(1m)58-62dB65-68dB拾音角度全向360°波束成形±30°功耗8mW@3.3V15mW@3.3V典型响应延迟120ms80ms二、技术原
    广州唯创电子 2025-03-27 09:23 154浏览
  • 在电子设计中,电磁兼容性(EMC)是确保设备既能抵御外部电磁干扰(EMI),又不会对自身或周围环境产生过量电磁辐射的关键。电容器、电感和磁珠作为三大核心元件,通过不同的机制协同作用,有效抑制电磁干扰。以下是其原理和应用场景的详细解析:1. 电容器:高频噪声的“吸尘器”作用原理:电容器通过“通高频、阻低频”的特性,为高频噪声提供低阻抗路径到地,形成滤波效果。例如,在电源和地之间并联电容,可吸收电源中的高频纹波和瞬态干扰。关键应用场景:电源去耦:在IC电源引脚附近放置0.1μF陶瓷电容,滤除数字电路
    时源芯微 2025-03-27 11:19 152浏览
  • 在嵌入式语音系统的开发过程中,广州唯创电子推出的WT588系列语音芯片凭借其优异的音质表现和灵活的编程特性,广泛应用于智能终端、工业控制、消费电子等领域。作为该系列芯片的关键状态指示信号,BUSY引脚的设计处理直接影响着系统交互的可靠性和功能拓展性。本文将从电路原理、应用场景、设计策略三个维度,深入解析BUSY引脚的技术特性及其工程实践要点。一、BUSY引脚工作原理与信号特性1.1 电气参数电平标准:输出3.3V TTL电平(与VDD同源)驱动能力:典型值±8mA(可直接驱动LED)响应延迟:语
    广州唯创电子 2025-03-26 09:26 204浏览
  • 文/陈昊编辑/cc孙聪颖‍2025 年,作为中国实施制造强国战略第一个十年计划的关键里程碑,被赋予了极为重大的意义。两会政府工作报告清晰且坚定地指出,要全力加速新质生产力的发展进程,推动传统产业全方位向高端化、智能化与绿色化转型。基于此,有代表敏锐提议,中国制造应从前沿技术的应用切入,逐步拓展至产业生态的构建,最终延伸到提升用户体验的维度,打出独树一帜、具有鲜明特色的发展牌。正是在这样至关重要的时代背景之下,于 AWE 2025(中国家电及消费电子博览会)这一备受瞩目的舞台上,高端厨房的中国方案
    华尔街科技眼 2025-03-25 16:10 82浏览
  • 在当今竞争激烈的工业环境中,效率和响应速度已成为企业制胜的关键。为了满足这一需求,我们隆重推出宏集Panorama COOX,这是Panorama Suite中首款集成的制造执行系统(MES)产品。这一创新产品将Panorama平台升级为全面的工业4.0解决方案,融合了工业SCADA和MES技术的双重优势,帮助企业实现生产效率和运营能力的全面提升。深度融合SCADA与MES,开启工业新纪元宏集Panorama COOX的诞生,源于我们对创新和卓越运营的不懈追求。通过战略性收购法国知名MES领域专
    宏集科技 2025-03-27 13:22 182浏览
  • WT588F02B是广州唯创电子推出的一款高性能语音芯片,广泛应用于智能家电、安防设备、玩具等领域。然而,在实际开发中,用户可能会遇到烧录失败的问题,导致项目进度受阻。本文将从下载连线、文件容量、线路长度三大核心因素出发,深入分析烧录失败的原因并提供系统化的解决方案。一、检查下载器与芯片的物理连接问题表现烧录时提示"连接超时"或"设备未响应",或烧录进度条卡顿后报错。原因解析接口错位:WT588F02B采用SPI/UART双模通信,若下载器引脚定义与芯片引脚未严格对应(如TXD/RXD交叉错误)
    广州唯创电子 2025-03-26 09:05 146浏览
  • 在智能终端设备开发中,语音芯片与功放电路的配合直接影响音质表现。广州唯创电子的WTN6、WT588F等系列芯片虽功能强大,但若硬件设计不当,可能导致输出声音模糊、杂音明显。本文将以WTN6与WT588F系列为例,解析音质劣化的常见原因及解决方法,帮助开发者实现清晰纯净的语音输出。一、声音不清晰的典型表现与核心原因当语音芯片输出的音频信号存在以下问题时,需针对性排查:背景杂音:持续的“沙沙”声或高频啸叫,通常由信号干扰或滤波不足导致。语音失真:声音断断续续或含混不清,可能与信号幅度不匹配或功放参数
    广州唯创电子 2025-03-25 09:32 112浏览
  • 在智慧城市领域中,当一个智慧路灯项目因信号盲区而被迫增设数百个网关时,当一个传感器网络因入网设备数量爆增而导致系统通信失效时,当一个智慧交通系统因基站故障而导致交通瘫痪时,星型网络拓扑与蜂窝网络拓扑在构建广覆盖与高节点数物联网网络时的局限性便愈发凸显,行业内亟需一种更高效、可靠与稳定的组网技术以满足构建智慧城市海量IoT网络节点的需求。星型网络的无线信号覆盖范围高度依赖网关的部署密度,同时单一网关的承载设备数量有限,难以支撑海量IoT网络节点的城市物联系统;而蜂窝网络的无线信号覆盖范围同样高度依
    华普微HOPERF 2025-03-24 17:00 235浏览
  • ​2025年3月27日​,贞光科技授权代理品牌紫光同芯正式发布新一代汽车安全芯片T97-415E。作为T97-315E的迭代升级产品,该芯片以大容量存储、全球化合规认证、双SPI接口协同为核心突破,直击智能网联汽车"多场景安全并行"与"出口合规"两大行业痛点,助力车企抢占智能驾驶与全球化市场双赛道。行业趋势锚定:三大升级回应智能化浪潮1. 大容量存储:破解车联网多任务瓶颈随着​车机功能泛在化​(数字钥匙、OTA、T-BOX等安全服务集成),传统安全芯片面临存储资源挤占难题。T97-415E创新性
    贞光科技 2025-03-27 13:50 148浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦