1 /*
2  * Copyright (c) 2019 Elliot Berman
3  * Copyright (c) 2020 Travis Geiselbrecht
4  *
5  * Use of this source code is governed by a MIT-style
6  * license that can be found in the LICENSE file or at
7  * https://opensource.org/licenses/MIT
8  */
9 
10 #include <arch/riscv.h>
11 
12 #include <lk/reg.h>
13 #include <lk/compiler.h>
14 #include <lk/debug.h>
15 #include <lk/trace.h>
16 #include <lk/err.h>
17 #include <lk/init.h>
18 #include <lk/main.h>
19 #include <stdlib.h>
20 #include <string.h>
21 
22 #include <arch/atomic.h>
23 #include <arch/ops.h>
24 #include <arch/mp.h>
25 #include <arch/riscv/clint.h>
26 
27 #include "riscv_priv.h"
28 
29 #if WITH_SMP
30 
31 #define LOCAL_TRACE 0
32 
33 // mapping of cpu -> hart
34 static uint cpu_to_hart_map[SMP_MAX_CPUS];
35 
36 // list of IPIs queued per cpu
37 static volatile int ipi_data[SMP_MAX_CPUS];
38 
39 static spin_lock_t boot_cpu_lock = 1;
40 
41 // list of cpus to boot, passed from platform
42 static uint harts_to_boot[SMP_MAX_CPUS];
43 static uint harts_to_boot_count = 0;
44 
45 // modified in start.S to save the physical address of _start as the first cpu boots
46 uintptr_t _start_physical;
47 
arch_mp_send_ipi(mp_cpu_mask_t target,mp_ipi_t ipi)48 status_t arch_mp_send_ipi(mp_cpu_mask_t target, mp_ipi_t ipi) {
49     LTRACEF("target 0x%x, ipi %u\n", target, ipi);
50 
51     mp_cpu_mask_t m = target;
52     ulong hart_mask = 0;
53     for (uint c = 0; c < SMP_MAX_CPUS && m; c++, m >>= 1) {
54         if (m & 1) {
55             int h = cpu_to_hart_map[c];
56             LTRACEF("c %u h %d m %#x\n", c, h, m);
57 
58             // record a pending hart to notify
59             hart_mask |= (1ul << h);
60 
61             // set the ipi_data based on the incoming ipi
62             atomic_or(&ipi_data[h], (1u << ipi));
63         }
64     }
65 
66     mb();
67 #if RISCV_M_MODE
68     clint_send_ipis(&hart_mask);
69 #else
70     sbi_send_ipis(&hart_mask);
71 #endif
72 
73     return NO_ERROR;
74 }
75 
76 // software triggered exceptions, used for cross-cpu calls
riscv_software_exception(void)77 enum handler_return riscv_software_exception(void) {
78     uint curr_cpu = arch_curr_cpu_num();
79 
80 #if RISCV_M_MODE
81     uint ch = riscv_current_hart();
82     clint_ipi_clear(ch);
83 #else
84     sbi_clear_ipi();
85 #endif
86 
87     rmb();
88     int reason = atomic_swap(&ipi_data[curr_cpu], 0);
89     LTRACEF("cpu %u reason %#x\n", curr_cpu, reason);
90 
91     enum handler_return ret = INT_NO_RESCHEDULE;
92     if (reason & (1u << MP_IPI_RESCHEDULE)) {
93         ret = mp_mbx_reschedule_irq();
94         reason &= ~(1u << MP_IPI_RESCHEDULE);
95     }
96     if (reason & (1u << MP_IPI_GENERIC)) {
97         panic("unimplemented MP_IPI_GENERIC\n");
98         reason &= ~(1u << MP_IPI_GENERIC);
99     }
100 
101     if (unlikely(reason)) {
102         TRACEF("unhandled ipi cause %#x, cpu %u\n", reason, curr_cpu);
103         panic("stopping");
104     }
105 
106     return ret;
107 }
108 
109 // called in very early percpu bringup
riscv_configure_percpu_mp_early(uint hart_id,uint cpu_num)110 void riscv_configure_percpu_mp_early(uint hart_id, uint cpu_num) {
111     cpu_to_hart_map[cpu_num] = hart_id;
112     wmb();
113 }
114 
115 // called from assembly
116 void riscv_secondary_entry(uint hart_id, uint __unused, uint cpu_id);
riscv_secondary_entry(uint hart_id,uint __unused,uint cpu_id)117 void riscv_secondary_entry(uint hart_id, uint __unused, uint cpu_id) {
118     // basic bootstrapping of this cpu
119     riscv_early_init_percpu();
120 
121     if (unlikely(arch_curr_cpu_num() >= SMP_MAX_CPUS)) {
122         while (1) {
123             arch_idle();
124         }
125     }
126 
127     // spin here waiting for the main cpu to release us
128     spin_lock(&boot_cpu_lock);
129     spin_unlock(&boot_cpu_lock);
130 
131 #if RISCV_MMU
132     // let the mmu code configure per cpu bits
133     riscv_mmu_init_secondaries();
134 #endif
135 
136     // run early secondary cpu init routines up to the threading level
137     lk_init_level(LK_INIT_FLAG_SECONDARY_CPUS, LK_INIT_LEVEL_EARLIEST, LK_INIT_LEVEL_THREADING - 1);
138 
139     // run threading level initialization on this cpu
140     riscv_init_percpu();
141 
142     dprintf(INFO, "RISCV: secondary hart coming up: mvendorid %#lx marchid %#lx mimpid %#lx mhartid %#x\n",
143             riscv_get_mvendorid(), riscv_get_marchid(),
144             riscv_get_mimpid(), riscv_current_hart());
145 
146     lk_secondary_cpu_entry();
147 }
148 
149 // platform hands the arch layer a list of harts to start, these will be
150 // started later in riscv_boot_secondaries()
riscv_set_secondary_harts_to_start(const uint * harts,size_t count)151 void riscv_set_secondary_harts_to_start(const uint *harts, size_t count) {
152     harts_to_boot_count = MIN(count, SMP_MAX_CPUS);
153     memcpy(harts_to_boot, harts, harts_to_boot_count * sizeof(harts[0]));
154 }
155 
156 // start any secondary cpus we are set to start. called on the boot processor
riscv_boot_secondaries(void)157 void riscv_boot_secondaries(void) {
158     // if theres nothing to start, abort here
159     if (harts_to_boot_count == 0) {
160         dprintf(INFO, "RISCV: No secondary harts to start\n");
161         return;
162     }
163 
164     lk_init_secondary_cpus(harts_to_boot_count);
165 
166 #if RISCV_M_MODE
167     dprintf(INFO, "RISCV: Releasing all secondary harts from purgatory\n");
168 
169     // For machine mode, we simply unblock all the trapped cpus in start.S by releasing
170     // the boot_cpu_lock. Via some mechanism or other, all of the cpus should have already entered
171     // start.S via the main entry point for this to work. This is the default behavior when
172     // running on QEMU, for example.
173     spin_unlock(&boot_cpu_lock);
174 #else
175     dprintf(INFO, "RISCV: Going to try to start %d secondary harts\n", harts_to_boot_count);
176 
177     // May as well release the boot cpu lock now, since there's no reason to hold back secondaries we
178     // haven't started yet.
179     spin_unlock(&boot_cpu_lock);
180 
181     // use SBI HSM to boot the secondaries
182     uint boot_hart = riscv_current_hart();
183     for (uint i = 0; i < harts_to_boot_count; i++) {
184         // skip the boot hart
185         if (harts_to_boot[i] != boot_hart) {
186             dprintf(INFO, "RISCV: using SBI to start hart %u at %#lx\n", harts_to_boot[i], _start_physical);
187             sbi_boot_hart(harts_to_boot[i], _start_physical, 0);
188 
189             // Pause a little bit here to give the secondary an opportunity to start. Not strictly
190             // needed but helps the boot output flow without much of a boot penalty. Remove
191             // if it's a problem.
192             thread_sleep(50);
193         }
194     }
195 #endif
196 }
197 
198 
199 #endif
200