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 #include <arch/x86/mp.h>
9 
10 #include <assert.h>
11 #include <lk/trace.h>
12 #include <lk/err.h>
13 #include <lk/init.h>
14 #include <lk/main.h>
15 #include <arch/mp.h>
16 #include <string.h>
17 #include <arch/x86.h>
18 #include <arch/x86/descriptor.h>
19 #include <arch/arch_ops.h>
20 #include <sys/types.h>
21 #include <arch/x86/lapic.h>
22 
23 #define LOCAL_TRACE 0
24 
25 #if WITH_SMP
26 
27 // the boot cpu's percpu struct
28 static x86_percpu_t x86_boot_percpu;
29 // pointer to an array of percpu structs for each of the secondary cpus
30 static x86_percpu_t *x86_ap_percpus;
31 
x86_get_percpu_for_cpu(uint cpu_num)32 x86_percpu_t *x86_get_percpu_for_cpu(uint cpu_num) {
33     DEBUG_ASSERT(cpu_num < SMP_MAX_CPUS);
34     if (cpu_num == 0) {
35         return &x86_boot_percpu;
36     }
37     DEBUG_ASSERT(x86_ap_percpus);
38     return &x86_ap_percpus[cpu_num - 1];
39 }
40 
x86_configure_percpu_early(uint cpu_num,uint apic_id)41 void x86_configure_percpu_early(uint cpu_num, uint apic_id) {
42     x86_percpu_t *percpu = x86_get_percpu_for_cpu(cpu_num);
43 
44     // initialize the percpu structure for this cpu
45     percpu->self = percpu;
46     percpu->cpu_num = cpu_num;
47     percpu->apic_id = apic_id;
48 
49 #if ARCH_X86_64
50     // use the 64-bit gs base msr to set up a pointer to the percpu struct
51     write_msr(X86_MSR_IA32_KERNEL_GS_BASE, 0);
52     write_msr(X86_MSR_IA32_GS_BASE, (uint64_t)percpu);
53 #else
54     // set up a gs descriptor for this cpu
55     uint16_t selector = PERCPU_SELECTOR_BASE + cpu_num * 8;
56     x86_set_gdt_descriptor(selector, percpu, sizeof(*percpu), 1, 0, 1, SEG_TYPE_DATA_RW, 0, 1);
57     x86_set_gs(selector);
58 #endif
59 }
60 
arch_mp_send_ipi(mp_cpu_mask_t target,mp_ipi_t ipi)61 status_t arch_mp_send_ipi(mp_cpu_mask_t target, mp_ipi_t ipi) {
62     LTRACEF("cpu %u target 0x%x, ipi 0x%x\n", arch_curr_cpu_num(), target, ipi);
63 
64     DEBUG_ASSERT(arch_ints_disabled());
65     uint curr_cpu_num = arch_curr_cpu_num();
66 
67     // translate the target bitmap to apic id
68     while (target) {
69         uint cpu_num = __builtin_ctz(target);
70         target &= ~(1u << cpu_num);
71 
72         // skip the current cpu
73         if (cpu_num == curr_cpu_num) {
74             continue;
75         }
76 
77         x86_percpu_t *percpu = x86_get_percpu_for_cpu(cpu_num);
78         uint32_t apic_id = percpu->apic_id;
79 
80         // send the ipi to the target cpu
81         lapic_send_ipi(apic_id, ipi);
82     }
83 
84     return NO_ERROR;
85 }
86 
arch_mp_init_percpu(void)87 void arch_mp_init_percpu(void) {}
88 
x86_get_apic_id_from_hardware(void)89 uint32_t x86_get_apic_id_from_hardware(void) {
90     // read the apic id out of cpuid leaf 1, which should be present if SMP is enabled.
91     uint32_t apic_id, unused;
92     cpuid(0x1, &unused, &apic_id, &unused, &unused);
93 
94     apic_id >>= 24;
95 
96     // TODO: read full 32bit apic id from x2apic msr if available
97 
98     return apic_id;
99 }
100 
x86_secondary_entry(uint cpu_num)101 void x86_secondary_entry(uint cpu_num) {
102     uint32_t apic_id = x86_get_apic_id_from_hardware();
103     x86_configure_percpu_early(cpu_num, apic_id);
104 
105     x86_early_init_percpu();
106 
107     // run early secondary cpu init routines up to the threading level
108     lk_init_level(LK_INIT_FLAG_SECONDARY_CPUS, LK_INIT_LEVEL_EARLIEST, LK_INIT_LEVEL_THREADING - 1);
109 
110     dprintf(INFO, "SMP: secondary cpu %u started, apic id %u\n", arch_curr_cpu_num(), apic_id);
111 
112     lk_secondary_cpu_entry();
113 
114     // should never get here except for an error condition
115     for (;;);
116 }
117 
x86_allocate_percpu_array(uint num_cpus)118 status_t x86_allocate_percpu_array(uint num_cpus) {
119     x86_ap_percpus = memalign(_Alignof(x86_percpu_t), num_cpus * sizeof(x86_percpu_t));
120     if (!x86_ap_percpus) {
121         return ERR_NO_MEMORY;
122     }
123 
124     memset(x86_ap_percpus, 0, num_cpus * sizeof(x86_percpu_t));
125     return NO_ERROR;
126 }
127 
128 #else
129 
x86_configure_percpu_early(uint cpu_num,uint apic_id)130 void x86_configure_percpu_early(uint cpu_num, uint apic_id) {}
131 
132 #endif