1 /*
2  * Copyright (c) 2024 Travis Geiselbrecht
3  *
4  * Use of this source code is governed by a MIT-style
5  * license that can be found in the LICENSE file or at
6  * https://opensource.org/licenses/MIT
7  */
8 
9 #include "platform_p.h"
10 
11 #include <kernel/thread.h>
12 #include <kernel/vm.h>
13 #include <lib/acpi_lite.h>
14 #include <lk/err.h>
15 #include <lk/main.h>
16 #include <lk/trace.h>
17 #include <string.h>
18 #include <arch/x86/lapic.h>
19 
20 #if WITH_SMP
21 
22 #define TRAMPOLINE_ADDRESS 0x4000
23 
24 #define LOCAL_TRACE 1
25 
26 extern void mp_boot_start(void);
27 extern void mp_boot_end(void);
28 
29 struct bootstrap_args {
30     // referenced in mp-boot.S, do not move without updating assembly
31     uintptr_t trampoline_cr3;
32     uintptr_t stack_top;
33 
34     // referenced in C, okay to move
35     uintptr_t cpu_num;
36     volatile uint32_t *boot_completed_ptr; // set by the secondary cpu when it's done
37 };
38 
39 // called from assembly code in mp-boot.S
secondary_entry(struct bootstrap_args * args)40 __NO_RETURN void secondary_entry(struct bootstrap_args *args) {
41     volatile uint32_t *boot_completed = args->boot_completed_ptr;
42     uint cpu_num = args->cpu_num;
43 
44     // context switch to the kernels cr3
45     x86_set_cr3(vmm_get_kernel_aspace()->arch_aspace.cr3_phys);
46     // from now on out the boot args structure is not visible
47 
48     // we're done, let the primary cpu know so it can reuse the args
49     *boot_completed = 1;
50 
51     x86_secondary_entry(cpu_num);
52 }
53 
start_cpu(uint cpu_num,uint32_t apic_id,struct bootstrap_args * args)54 static status_t start_cpu(uint cpu_num, uint32_t apic_id, struct bootstrap_args *args) {
55     LTRACEF("cpu_num %u, apic_id %u\n", cpu_num, apic_id);
56 
57     // assert that this thread is pinned to the current cpu
58     DEBUG_ASSERT(thread_pinned_cpu(get_current_thread()) == (int)arch_curr_cpu_num());
59 
60     volatile uint32_t boot_completed = 0;
61     args->boot_completed_ptr = &boot_completed;
62 
63     // start x86 secondary cpu
64 
65     // send INIT IPI
66     lapic_send_init_ipi(apic_id, true);
67     thread_sleep(10);
68 
69     // deassert INIT
70     lapic_send_init_ipi(apic_id, false);
71     thread_sleep(10);
72 
73     // send Startup IPI up to 2 times as recommended by Intel
74     for (int i = 0; i < 2; i++) {
75         lapic_send_startup_ipi(apic_id, TRAMPOLINE_ADDRESS);
76 
77         // Wait a little bit for the cpu to start before trying a second time
78         thread_sleep(10);
79         if (boot_completed) {
80             goto booted;
81         }
82     }
83 
84     // Wait up to a second for the cpu to finish starting
85     for (int i = 0; i < 1000; i++) {
86         if (boot_completed) {
87             goto booted;
88         }
89         thread_sleep(10);
90     }
91 
92     // we have failed to start this core
93     // TODO: handle trying to shut the core down before moving on.
94     printf("PC: failed to start cpu %u\n", cpu_num);
95     return ERR_TIMED_OUT;
96 
97 booted:
98     LTRACEF("cpu %u booted\n", cpu_num);
99     return NO_ERROR;
100 }
101 
102 struct detected_cpus {
103     uint32_t num_detected;
104     uint32_t apic_ids[SMP_MAX_CPUS];
105 };
106 
local_apic_callback(const void * _entry,size_t entry_len,void * cookie)107 static void local_apic_callback(const void *_entry, size_t entry_len, void *cookie) {
108     const struct acpi_madt_local_apic_entry *entry = _entry;
109     struct detected_cpus *cpus = cookie;
110 
111     if ((entry->flags & ACPI_MADT_FLAG_ENABLED) == 0) {
112         return;
113     }
114 
115     // TODO: read the current APIC id and skip it, instead of assuming 0 is the boot cpu
116     // read BSP from X86_IA32_APIC_BASE_MSR bit 8?
117     if (entry->apic_id == 0) {
118         // skip the boot cpu
119         return;
120     }
121     if (cpus->num_detected < SMP_MAX_CPUS) {
122         cpus->apic_ids[cpus->num_detected++] = entry->apic_id;
123     }
124 }
125 
platform_start_secondary_cpus(void)126 void platform_start_secondary_cpus(void) {
127     struct detected_cpus cpus;
128     cpus.num_detected = 1;
129     cpus.apic_ids[0] = 0; // the boot cpu
130 
131     acpi_process_madt_entries_etc(ACPI_MADT_TYPE_LOCAL_APIC, &local_apic_callback, &cpus);
132 
133     // TODO: fall back to legacy methods if ACPI fails
134     // TODO: deal with cpu topology
135 
136     // start up the secondary cpus
137     if (cpus.num_detected < 2) {
138         dprintf(INFO, "PC: no secondary cpus detected\n");
139         return;
140     }
141 
142     // create a new aspace to build an identity map in
143     vmm_aspace_t *aspace;
144     status_t err = vmm_create_aspace(&aspace, "identity map", 0);
145     if (err < 0) {
146         panic("failed to create identity map aspace\n");
147     }
148 
149     // set up an identity map for the trampoline code
150 
151     void *ptr = (void *)TRAMPOLINE_ADDRESS;
152     err = vmm_alloc_physical(aspace, "trampoline", 0x10000, &ptr, 0,
153         TRAMPOLINE_ADDRESS, VMM_FLAG_VALLOC_SPECIFIC, ARCH_MMU_FLAG_CACHED);
154     if (err < 0) {
155         panic("failed to allocate trampoline memory\n");
156     }
157 
158     vmm_aspace_t *old_aspace = vmm_set_active_aspace(aspace);
159 
160     // set up bootstrap code page at TRAMPOLINE_ADDRESS for secondary cpu
161     memcpy(ptr, mp_boot_start, mp_boot_end - mp_boot_start);
162 
163     // next page has args in it
164     struct bootstrap_args *args = (struct bootstrap_args *)((uintptr_t)ptr + 0x1000);
165     args->trampoline_cr3 = aspace->arch_aspace.cr3_phys;
166 
167     dprintf(INFO, "PC: detected %u cpus\n", cpus.num_detected);
168 
169     lk_init_secondary_cpus(cpus.num_detected - 1);
170     err = x86_allocate_percpu_array(cpus.num_detected - 1);
171     if (err < 0) {
172         panic("failed to allocate percpu array\n");
173     }
174 
175     for (uint i = 1; i < cpus.num_detected; i++) {
176         dprintf(INFO, "PC: starting cpu %u\n", cpus.apic_ids[i]);
177 
178         args->cpu_num = i;
179 
180         x86_percpu_t *percpu = x86_get_percpu_for_cpu(i);
181         args->stack_top = (uintptr_t)percpu->bootstrap_stack + sizeof(percpu->bootstrap_stack);
182 
183         LTRACEF("args for cpu %lu: trampoline_cr3 %#lx, stack_top 0x%lx\n", args->cpu_num, args->trampoline_cr3, args->stack_top);
184 
185         start_cpu(i, cpus.apic_ids[i], args);
186     }
187 
188     // restore old aspace
189     vmm_set_active_aspace(old_aspace);
190 
191     // free the trampoline aspace
192     vmm_free_aspace(aspace);
193 }
194 
195 #endif // WITH_SMP