qingke-rt 0.8.0

Minimal runtime / startup for WCH's RISC-V MCUs, managed by the ch32-rs team
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
#![no_std]
#![allow(unexpected_cfgs)]
//! # Differences vs the riscv-rt version
//!
//! - The structure of exception handlers is different
//! - The structure of core interrupt handlers is different
//! - Hardware stack push is available, so no need to push manually
use qingke::{
    register::mtvec::{self, TrapMode},
    riscv::register::mcause,
};
#[cfg(feature = "highcode")]
pub use qingke_rt_macros::highcode;
pub use qingke_rt_macros::{entry, interrupt};

use core::arch::global_asm;

mod asm;

// Let this crate conflicts with riscv-rt
#[unsafe(export_name = "error: riscv-rt appears more than once in the dependency graph")]
#[doc(hidden)]
pub static __ONCE__: () = ();

unsafe extern "C" {
    fn Exception();

    fn InstructionMisaligned();
    fn InstructionFault();
    fn IllegalInstruction();
    fn LoadMisaligned();
    fn LoadFault();
    fn StoreMisaligned();
    fn StoreFault();

    fn NonMaskableInt();
    fn MachineEnvCall();
    fn UserEnvCall();
    fn Breakpoint();
    fn SysTick();
    fn Software();
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[unsafe(link_section = ".vector_table.exceptions")]
pub static __EXCEPTIONS: [Option<unsafe extern "C" fn()>; 12] = [
    Some(InstructionMisaligned), // 0
    Some(InstructionFault),
    Some(IllegalInstruction),
    Some(Breakpoint),
    Some(LoadMisaligned),
    Some(LoadFault), // 5, Not accurate, async
    Some(StoreMisaligned),
    Some(StoreFault),  // 7, Not accurate, async
    Some(UserEnvCall), // not available for Qingke V2
    None,
    None,
    Some(MachineEnvCall),
];

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum CoreInterrupt {
    NonMaskableInt = 2,
    Exception = 3,
    MachineEnvCall = 5,
    UserEnvCall = 8,
    Breakpoint = 9,
    SysTick = 12,
    Software = 14,
}

impl CoreInterrupt {
    pub fn try_from(irq: u8) -> Result<Self, u8> {
        match irq {
            2 => Ok(CoreInterrupt::NonMaskableInt),
            3 => Ok(CoreInterrupt::Exception),
            5 => Ok(CoreInterrupt::MachineEnvCall),
            8 => Ok(CoreInterrupt::UserEnvCall),
            9 => Ok(CoreInterrupt::Breakpoint),
            12 => Ok(CoreInterrupt::SysTick),
            14 => Ok(CoreInterrupt::Software),

            _ => Err(irq),
        }
    }
}

/// Core interrupts, without the first one
#[doc(hidden)]
#[unsafe(no_mangle)]
#[used]
#[unsafe(link_section = ".vector_table.core_interrupts")]
pub static __CORE_INTERRUPTS: [Option<unsafe extern "C" fn()>; 15] = [
    // None, // skip 0
    None,
    Some(NonMaskableInt), // 2
    Some(Exception),      // 3
    None,
    Some(MachineEnvCall), // 5
    None,
    None,
    Some(UserEnvCall), // 8
    Some(Breakpoint),  // 9
    None,
    None,
    Some(SysTick), // 12
    None,
    Some(Software), // 14
    None,
];
// followed by .vector_table.external_interrupts

#[unsafe(link_section = ".init.rust")]
#[unsafe(export_name = "_setup_interrupts")]
unsafe extern "C" fn qingke_setup_interrupts() {
    // enable hardware stack push
    // intsyscr(0x804): Open nested interrupts and hardware stack functions
    // 0x3 both nested interrupts and hardware stack
    // 0x1 only hardware stack

    // for user mode: mstatus = 0x80
    // mpp(m-mode previous privilege) = 0b00 = U
    // mpie(m-mode previous interrupt enable) = 0b1
    // mie(m-mode interrupt enable) = 0b0
    // interrupts will be enabled when mret at the end of handle_reset
    // jumps to main (mret does mie = mpie)
    // for machine mode: mstatus = 0x1880
    // mpp = 0b11
    // mpie = 0b1
    // mie = 0b0

    // Qingke V2A, V2C
    // (does not have user mode)
    #[cfg(qingke_v2)]
    unsafe {
        core::arch::asm!(
            "
            li t0, 0x1880
            csrw mstatus, t0
            li t0, 0x3
            csrw 0x804, t0
            "
        );
    }

    // Qingke V3A, V3B, V3C, V3V (non-V3F V3 variants).
    // Leaves corecfgr / intsyscr / nest-level at reset defaults; only
    // OR's a couple of bits into mstatus.
    #[cfg(all(qingke_v3, not(feature = "v3f")))]
    unsafe {
        #[cfg(feature = "u-mode")]
        core::arch::asm!(
            "
            li t0, 0x80
            csrs mstatus, t0
            "
        );
        #[cfg(not(feature = "u-mode"))]
        core::arch::asm!(
            "
            li t0, 0x1880
            csrs mstatus, t0
            "
        );
    }

    // Qingke V3F (CH32H417 primary core).
    // Unlike V3A/V3B, V3F's WCH startup writes the full set of QingKe
    // control CSRs: pipeline/branch-prediction config (corecfgr 0xBC0),
    // 2-level nest control (inestcr 0xBC1), interrupt nesting +
    // hardware-stack enable (intsyscr 0x804), and a full `csrw` of
    // mstatus selecting U-mode return + FP-Dirty. Values mirror
    // `startup_ch32h417_v3f.S:541-551`.
    //
    // Note: the post-block FP-enable code below (`#[cfg(any(riscvf,
    // riscvd))]`) will subsequently downgrade FS from Dirty (0b11) to
    // Initial (0b01); the first FP register write bumps it back to
    // Dirty automatically.
    #[cfg(feature = "v3f")]
    unsafe {
        use qingke::register::inestcr::{self, NestLevel};
        inestcr::write(NestLevel::Two as usize);
        core::arch::asm!(
            "
            li t0, 0x123703E1
            csrw 0xBC0, t0
            li t0, 0x07
            csrw 0x804, t0
            li t0, 0x6088
            csrw mstatus, t0
            "
        );
    }

    // Qingke V5F (CH32H417 secondary core).
    // V5F is a deeper / wider core than V3F: 8-level interrupt nesting
    // (vs V3F's 2), pmtcfg=11 in intsyscr (vs V3F's 01), and extra
    // pipeline tuning bits in corecfgr (NLP_EN + a couple of reserved
    // defaults). Values mirror `startup_ch32h417_v5f.S:481-491`.
    //
    // Note: ICache and PMP setup that the WCH SDK does immediately
    // after this block (using `_cache_beg` / `_cache_end` linker
    // symbols) is intentionally NOT performed here — it requires the
    // user's linker script to define those symbols. ICache support
    // will land as a separate opt-in feature.
    //
    // Same FS-downgrade note as v3f applies: the riscvf/d post-block
    // will reset FS from Dirty to Initial.
    #[cfg(feature = "v5f")]
    unsafe {
        use qingke::register::inestcr::{self, NestLevel};
        inestcr::write(NestLevel::Eight as usize);
        core::arch::asm!(
            "
            li t0, 0x1237B3E0
            csrw 0xBC0, t0
            li t0, 0x0F
            csrw 0x804, t0
            li t0, 0x6088
            csrw mstatus, t0
            "
        );
    }

    // corecfgr (0xBC0): pipeline control and branch prediction
    #[cfg(any(
        qingke_v4,
        not(any(qingke_v2, qingke_v3, qingke_v4, qingke_v5)) // Fallback when no leaf is selected
    ))]
    unsafe {
        #[cfg(feature = "u-mode")]
        core::arch::asm!(
            "
            li t0, 0x1f
            csrw 0xbc0, t0
            li t0, 0x3
            csrw 0x804, t0
            li t0, 0x80
            csrs mstatus, t0
            "
        );
        #[cfg(not(feature = "u-mode"))]
        core::arch::asm!(
            "
            li t0, 0x1f
            csrw 0xbc0, t0
            li t0, 0x3
            csrw 0x804, t0
            li t0, 0x1880
            csrs mstatus, t0
            "
        );
        qingke::register::gintenr::set_enable();
    }

    // V3A: no VectoredAddress support, use Direct mode + software dispatch.
    #[cfg(feature = "v3a")]
    unsafe {
        unsafe extern "C" {
            fn _unified_trap_handler();
        }
        mtvec::write(_unified_trap_handler as *const () as usize, TrapMode::Direct);
    }

    // Qingke V2's mtvec must be 1KB aligned.

    #[cfg(not(feature = "v3a"))]
    unsafe {
        #[cfg(feature = "highcode")]
        {
            unsafe extern "C" {
                static _highcode_vma_start: u8;
            }
            mtvec::write(
                &raw const _highcode_vma_start as usize,
                TrapMode::VectoredAddress,
            );
        }
        #[cfg(not(feature = "highcode"))]
        {
            unsafe extern "C" {
                static _start: u8;
            }
            mtvec::write(&raw const _start as usize, TrapMode::VectoredAddress);
        }
    }

    unsafe {
        qingke::pfic::wfi_to_wfe(true);
    }
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub fn DefaultInterruptHandler() {
    loop {
        // Prevent this from turning into a UDF instruction
        // see rust-lang/rust#28728 for details
        continue;
    }
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub fn DefaultExceptionHandler() -> ! {
    loop {
        // Prevent this from turning into a UDF instruction
        // see rust-lang/rust#28728 for details
        continue;
    }
}

// override _start_trap in riscv-rt
global_asm!(
    r#"
        .section .trap, "ax"
        .global _exception_handler
    _exception_handler:
        addi sp, sp, -4
        sw ra, 0(sp)
        jal _exception_handler_rust
        lw ra, 0(sp)
        addi sp, sp, 4
        mret
    "#
);

// V3A software dispatch handler for Direct mode.
// Reads mcause, looks up handler address from the vector table, and jumps to it.
#[cfg(all(feature = "v3a", feature = "highcode"))]
global_asm!(
    r#"
        .section .trap, "ax"
        .global _unified_trap_handler
        .align 2
    _unified_trap_handler:
        csrr t0, mcause
        bgez t0, _exception_handler
        slli t0, t0, 1
        srli t0, t0, 1
        slli t0, t0, 2
        la t1, _highcode_vma_start
        add t0, t0, t1
        lw t0, 0(t0)
        beqz t0, 1f
        jr t0
    1:
        la t0, DefaultInterruptHandler
        jr t0
    "#
);

#[cfg(all(feature = "v3a", not(feature = "highcode")))]
global_asm!(
    r#"
        .section .trap, "ax"
        .global _unified_trap_handler
        .align 2
    _unified_trap_handler:
        csrr t0, mcause
        bgez t0, _exception_handler
        slli t0, t0, 1
        srli t0, t0, 1
        slli t0, t0, 2
        la t1, _start
        add t0, t0, t1
        lw t0, 0(t0)
        beqz t0, 1f
        jr t0
    1:
        j DefaultInterruptHandler
    "#
);

#[doc(hidden)]
#[unsafe(link_section = ".trap.rust")]
#[unsafe(export_name = "_exception_handler_rust")]
pub unsafe extern "C" fn qingke_exception_handler() {
    // jump according to the __EXCEPTIONS table
    unsafe extern "C" {
        fn ExceptionHandler();
    }

    let cause = mcause::read();
    let code = cause.code();

    if cause.is_exception() {
        if code < __EXCEPTIONS.len() {
            let h = &__EXCEPTIONS[code];
            if let Some(handler) = h {
                unsafe { handler() };
            } else {
                unsafe { ExceptionHandler() };
            }
        } else {
            unsafe { ExceptionHandler() };
        }
    } else {
        loop {
            // Prevent this from turning into a UDF instruction
            // see rust-lang/rust#28728 for details
            continue;
        }
    }
}