Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Initialize machine setup information and I/O. |
| 3 | * |
| 4 | * After running setup() unit tests may query how many cpus they have |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 5 | * (nr_cpus_present), how much memory they have (PHYSICAL_END - PHYSICAL_START), |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 6 | * may use dynamic memory allocation (malloc, etc.), printf, and exit. |
| 7 | * Finally, argc and argv are also ready to be passed to main(). |
| 8 | * |
| 9 | * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com> |
| 10 | * |
| 11 | * This work is licensed under the terms of the GNU LGPL, version 2. |
| 12 | */ |
| 13 | #include <libcflat.h> |
| 14 | #include <libfdt/libfdt.h> |
| 15 | #include <devicetree.h> |
| 16 | #include <alloc.h> |
Paolo Bonzini | dc47ac6 | 2017-10-22 14:53:57 +0200 | [diff] [blame] | 17 | #include <alloc_phys.h> |
Nicholas Piggin | 8e4e051 | 2024-05-04 22:28:21 +1000 | [diff] [blame] | 18 | #include <alloc_page.h> |
Thomas Huth | 63d5cbe | 2017-06-27 06:44:07 +0200 | [diff] [blame] | 19 | #include <argv.h> |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 20 | #include <asm/setup.h> |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 21 | #include <asm/smp.h> |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 22 | #include <asm/page.h> |
Nicholas Piggin | 99bb51c | 2023-12-16 23:42:36 +1000 | [diff] [blame] | 23 | #include <asm/ptrace.h> |
Nicholas Piggin | 610c5a9 | 2024-05-04 22:28:15 +1000 | [diff] [blame] | 24 | #include <asm/processor.h> |
Laurent Vivier | 6842bc3 | 2016-03-21 12:33:30 +0100 | [diff] [blame] | 25 | #include <asm/hcall.h> |
Thomas Huth | 4ff26ec | 2018-06-06 10:35:59 +0200 | [diff] [blame] | 26 | #include "io.h" |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 27 | |
| 28 | extern unsigned long stacktop; |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 29 | |
Andrew Jones | 01c82c0 | 2017-01-13 19:15:24 +0100 | [diff] [blame] | 30 | char *initrd; |
| 31 | u32 initrd_size; |
| 32 | |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 33 | u32 cpu_to_hwid[NR_CPUS] = { [0 ... NR_CPUS-1] = (~0U) }; |
| 34 | int nr_cpus_present; |
Suraj Jitindar Singh | f4d8d93 | 2016-08-19 11:10:13 +1000 | [diff] [blame] | 35 | uint64_t tb_hz; |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 36 | |
| 37 | struct mem_region mem_regions[NR_MEM_REGIONS]; |
| 38 | phys_addr_t __physical_start, __physical_end; |
| 39 | unsigned __icache_bytes, __dcache_bytes; |
| 40 | |
| 41 | struct cpu_set_params { |
| 42 | unsigned icache_bytes; |
| 43 | unsigned dcache_bytes; |
Suraj Jitindar Singh | f4d8d93 | 2016-08-19 11:10:13 +1000 | [diff] [blame] | 44 | uint64_t tb_hz; |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 45 | }; |
| 46 | |
Andrew Jones | 7a20b74 | 2016-12-09 14:05:43 +0100 | [diff] [blame] | 47 | static void cpu_set(int fdtnode, u64 regval, void *info) |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 48 | { |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 49 | const struct fdt_property *prop; |
| 50 | u32 *threads; |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 51 | static bool read_common_info = false; |
| 52 | struct cpu_set_params *params = info; |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 53 | int nr_threads; |
| 54 | int len, i; |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 55 | |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 56 | /* Get the id array of threads on this node */ |
| 57 | prop = fdt_get_property(dt_fdt(), fdtnode, |
| 58 | "ibm,ppc-interrupt-server#s", &len); |
| 59 | assert(prop); |
Andrew Jones | 3a9d03a | 2017-05-22 20:18:20 +0200 | [diff] [blame] | 60 | |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 61 | nr_threads = len >> 2; /* Divide by 4 since 4 bytes per thread */ |
| 62 | threads = (u32 *)prop->data; /* Array of valid ids */ |
| 63 | |
| 64 | for (i = 0; i < nr_threads; i++) { |
| 65 | if (nr_cpus_present >= NR_CPUS) { |
| 66 | static bool warned = false; |
| 67 | if (!warned) { |
| 68 | printf("Warning: Number of present CPUs exceeds maximum supported (%d).\n", NR_CPUS); |
| 69 | warned = true; |
| 70 | } |
| 71 | break; |
| 72 | } |
| 73 | cpu_to_hwid[nr_cpus_present++] = fdt32_to_cpu(threads[i]); |
| 74 | } |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 75 | |
| 76 | if (!read_common_info) { |
| 77 | const struct fdt_property *prop; |
| 78 | u32 *data; |
| 79 | |
| 80 | prop = fdt_get_property(dt_fdt(), fdtnode, |
| 81 | "i-cache-line-size", NULL); |
| 82 | assert(prop != NULL); |
| 83 | data = (u32 *)prop->data; |
| 84 | params->icache_bytes = fdt32_to_cpu(*data); |
| 85 | |
| 86 | prop = fdt_get_property(dt_fdt(), fdtnode, |
| 87 | "d-cache-line-size", NULL); |
| 88 | assert(prop != NULL); |
| 89 | data = (u32 *)prop->data; |
| 90 | params->dcache_bytes = fdt32_to_cpu(*data); |
| 91 | |
Suraj Jitindar Singh | f4d8d93 | 2016-08-19 11:10:13 +1000 | [diff] [blame] | 92 | prop = fdt_get_property(dt_fdt(), fdtnode, |
| 93 | "timebase-frequency", NULL); |
| 94 | assert(prop != NULL); |
| 95 | data = (u32 *)prop->data; |
| 96 | params->tb_hz = fdt32_to_cpu(*data); |
| 97 | |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 98 | read_common_info = true; |
| 99 | } |
| 100 | } |
| 101 | |
Nicholas Piggin | 610c5a9 | 2024-05-04 22:28:15 +1000 | [diff] [blame] | 102 | bool cpu_has_hv; |
Nicholas Piggin | 00af1c8 | 2024-05-04 22:28:18 +1000 | [diff] [blame] | 103 | bool cpu_has_power_mce; /* POWER CPU machine checks */ |
| 104 | bool cpu_has_siar; |
Nicholas Piggin | cd27b4b | 2024-05-04 22:28:16 +1000 | [diff] [blame] | 105 | bool cpu_has_heai; |
Nicholas Piggin | 00af1c8 | 2024-05-04 22:28:18 +1000 | [diff] [blame] | 106 | bool cpu_has_prefix; |
| 107 | bool cpu_has_sc_lev; /* sc interrupt has LEV field in SRR1 */ |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 108 | bool cpu_has_pause_short; |
Nicholas Piggin | 610c5a9 | 2024-05-04 22:28:15 +1000 | [diff] [blame] | 109 | |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 110 | static void cpu_init_params(void) |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 111 | { |
| 112 | struct cpu_set_params params; |
| 113 | int ret; |
| 114 | |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 115 | nr_cpus_present = 0; |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 116 | ret = dt_for_each_cpu_node(cpu_set, ¶ms); |
| 117 | assert(ret == 0); |
| 118 | __icache_bytes = params.icache_bytes; |
| 119 | __dcache_bytes = params.dcache_bytes; |
Suraj Jitindar Singh | f4d8d93 | 2016-08-19 11:10:13 +1000 | [diff] [blame] | 120 | tb_hz = params.tb_hz; |
Laurent Vivier | 6842bc3 | 2016-03-21 12:33:30 +0100 | [diff] [blame] | 121 | |
Nicholas Piggin | cd27b4b | 2024-05-04 22:28:16 +1000 | [diff] [blame] | 122 | switch (mfspr(SPR_PVR) & PVR_VERSION_MASK) { |
| 123 | case PVR_VER_POWER10: |
Nicholas Piggin | 00af1c8 | 2024-05-04 22:28:18 +1000 | [diff] [blame] | 124 | cpu_has_prefix = true; |
| 125 | cpu_has_sc_lev = true; |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 126 | cpu_has_pause_short = true; |
Nicholas Piggin | cd27b4b | 2024-05-04 22:28:16 +1000 | [diff] [blame] | 127 | case PVR_VER_POWER9: |
| 128 | case PVR_VER_POWER8E: |
| 129 | case PVR_VER_POWER8NVL: |
| 130 | case PVR_VER_POWER8: |
Nicholas Piggin | 00af1c8 | 2024-05-04 22:28:18 +1000 | [diff] [blame] | 131 | cpu_has_power_mce = true; |
Nicholas Piggin | cd27b4b | 2024-05-04 22:28:16 +1000 | [diff] [blame] | 132 | cpu_has_heai = true; |
Nicholas Piggin | 00af1c8 | 2024-05-04 22:28:18 +1000 | [diff] [blame] | 133 | cpu_has_siar = true; |
Nicholas Piggin | cd27b4b | 2024-05-04 22:28:16 +1000 | [diff] [blame] | 134 | break; |
| 135 | default: |
| 136 | break; |
| 137 | } |
Nicholas Piggin | 00af1c8 | 2024-05-04 22:28:18 +1000 | [diff] [blame] | 138 | |
| 139 | if (!cpu_has_hv) /* HEIR is HV register */ |
| 140 | cpu_has_heai = false; |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | static void mem_init(phys_addr_t freemem_start) |
| 144 | { |
| 145 | struct dt_pbus_reg regs[NR_MEM_REGIONS]; |
| 146 | struct mem_region primary, mem = { |
| 147 | .start = (phys_addr_t)-1, |
| 148 | }; |
| 149 | int nr_regs, i; |
Nicholas Piggin | 8e4e051 | 2024-05-04 22:28:21 +1000 | [diff] [blame] | 150 | phys_addr_t base, top; |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 151 | |
| 152 | nr_regs = dt_get_memory_params(regs, NR_MEM_REGIONS); |
| 153 | assert(nr_regs > 0); |
| 154 | |
| 155 | primary.end = 0; |
| 156 | |
| 157 | for (i = 0; i < nr_regs; ++i) { |
| 158 | mem_regions[i].start = regs[i].addr; |
| 159 | mem_regions[i].end = regs[i].addr + regs[i].size; |
| 160 | |
| 161 | /* |
| 162 | * pick the region we're in for our primary region |
| 163 | */ |
| 164 | if (freemem_start >= mem_regions[i].start |
| 165 | && freemem_start < mem_regions[i].end) { |
| 166 | mem_regions[i].flags |= MR_F_PRIMARY; |
| 167 | primary = mem_regions[i]; |
| 168 | } |
| 169 | |
| 170 | /* |
| 171 | * set the lowest and highest addresses found, |
| 172 | * ignoring potential gaps |
| 173 | */ |
| 174 | if (mem_regions[i].start < mem.start) |
| 175 | mem.start = mem_regions[i].start; |
| 176 | if (mem_regions[i].end > mem.end) |
| 177 | mem.end = mem_regions[i].end; |
| 178 | } |
| 179 | assert(primary.end != 0); |
| 180 | // assert(!(mem.start & ~PHYS_MASK) && !((mem.end - 1) & ~PHYS_MASK)); |
| 181 | |
| 182 | __physical_start = mem.start; /* PHYSICAL_START */ |
| 183 | __physical_end = mem.end; /* PHYSICAL_END */ |
| 184 | |
| 185 | phys_alloc_init(freemem_start, primary.end - freemem_start); |
| 186 | phys_alloc_set_minimum_alignment(__icache_bytes > __dcache_bytes |
| 187 | ? __icache_bytes : __dcache_bytes); |
Nicholas Piggin | 8e4e051 | 2024-05-04 22:28:21 +1000 | [diff] [blame] | 188 | |
| 189 | phys_alloc_get_unused(&base, &top); |
| 190 | base = PAGE_ALIGN(base); |
| 191 | top &= PAGE_MASK; |
| 192 | page_alloc_init_area(0, base >> PAGE_SHIFT, top >> PAGE_SHIFT); |
| 193 | page_alloc_ops_enable(); |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 194 | } |
| 195 | |
Nicholas Piggin | 2981505 | 2024-05-04 22:28:20 +1000 | [diff] [blame] | 196 | #define EXCEPTION_STACK_SIZE SZ_64K |
| 197 | |
| 198 | static char boot_exception_stack[EXCEPTION_STACK_SIZE]; |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 199 | struct cpu cpus[NR_CPUS]; |
| 200 | |
| 201 | void cpu_init(struct cpu *cpu, int cpu_id) |
| 202 | { |
| 203 | cpu->server_no = cpu_id; |
| 204 | |
| 205 | cpu->stack = (unsigned long)memalign(SZ_4K, SZ_64K); |
| 206 | cpu->stack += SZ_64K - 64; |
| 207 | cpu->exception_stack = (unsigned long)memalign(SZ_4K, SZ_64K); |
| 208 | cpu->exception_stack += SZ_64K - 64; |
| 209 | } |
Nicholas Piggin | 2981505 | 2024-05-04 22:28:20 +1000 | [diff] [blame] | 210 | |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 211 | void setup(const void *fdt) |
| 212 | { |
Andrew Jones | efe4200 | 2017-01-13 19:15:22 +0100 | [diff] [blame] | 213 | void *freemem = &stacktop; |
Andrew Jones | 01c82c0 | 2017-01-13 19:15:24 +0100 | [diff] [blame] | 214 | const char *bootargs, *tmp; |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 215 | struct cpu *cpu; |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 216 | u32 fdt_size; |
| 217 | int ret; |
| 218 | |
Nicholas Piggin | 610c5a9 | 2024-05-04 22:28:15 +1000 | [diff] [blame] | 219 | cpu_has_hv = !!(mfmsr() & (1ULL << MSR_HV_BIT)); |
| 220 | |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 221 | memset(cpus, 0xff, sizeof(cpus)); |
| 222 | |
| 223 | cpu = &cpus[0]; |
| 224 | cpu->server_no = fdt_boot_cpuid_phys(fdt); |
| 225 | cpu->exception_stack = (unsigned long)boot_exception_stack; |
| 226 | cpu->exception_stack += EXCEPTION_STACK_SIZE - 64; |
| 227 | |
| 228 | mtspr(SPR_SPRG0, (unsigned long)cpu); |
| 229 | __current_cpu = cpu; |
Nicholas Piggin | 2981505 | 2024-05-04 22:28:20 +1000 | [diff] [blame] | 230 | |
Nicholas Piggin | 610c5a9 | 2024-05-04 22:28:15 +1000 | [diff] [blame] | 231 | enable_mcheck(); |
| 232 | |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 233 | /* |
Andrew Jones | 01c82c0 | 2017-01-13 19:15:24 +0100 | [diff] [blame] | 234 | * Before calling mem_init we need to move the fdt and initrd |
| 235 | * to safe locations. We move them to construct the memory |
Andrew Jones | efe4200 | 2017-01-13 19:15:22 +0100 | [diff] [blame] | 236 | * map illustrated below: |
| 237 | * |
| 238 | * +----------------------+ <-- top of physical memory |
| 239 | * | | |
| 240 | * ~ ~ |
| 241 | * | | |
Andrew Jones | 01c82c0 | 2017-01-13 19:15:24 +0100 | [diff] [blame] | 242 | * +----------------------+ <-- top of initrd |
| 243 | * | | |
Andrew Jones | efe4200 | 2017-01-13 19:15:22 +0100 | [diff] [blame] | 244 | * +----------------------+ <-- top of FDT |
| 245 | * | | |
| 246 | * +----------------------+ <-- top of cpu0's stack |
| 247 | * | | |
Andrew Jones | 75a5870 | 2017-05-25 12:39:36 +0200 | [diff] [blame] | 248 | * +----------------------+ <-- top of text/data/bss/toc sections, |
| 249 | * | | see powerpc/flat.lds |
Andrew Jones | efe4200 | 2017-01-13 19:15:22 +0100 | [diff] [blame] | 250 | * | | |
| 251 | * +----------------------+ <-- load address |
| 252 | * | | |
| 253 | * +----------------------+ |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 254 | */ |
| 255 | fdt_size = fdt_totalsize(fdt); |
Andrew Jones | efe4200 | 2017-01-13 19:15:22 +0100 | [diff] [blame] | 256 | ret = fdt_move(fdt, freemem, fdt_size); |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 257 | assert(ret == 0); |
Andrew Jones | efe4200 | 2017-01-13 19:15:22 +0100 | [diff] [blame] | 258 | ret = dt_init(freemem); |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 259 | assert(ret == 0); |
Andrew Jones | efe4200 | 2017-01-13 19:15:22 +0100 | [diff] [blame] | 260 | freemem += fdt_size; |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 261 | |
Andrew Jones | 01c82c0 | 2017-01-13 19:15:24 +0100 | [diff] [blame] | 262 | ret = dt_get_initrd(&tmp, &initrd_size); |
| 263 | assert(ret == 0 || ret == -FDT_ERR_NOTFOUND); |
| 264 | if (ret == 0) { |
| 265 | initrd = freemem; |
| 266 | memmove(initrd, tmp, initrd_size); |
| 267 | freemem += initrd_size; |
| 268 | } |
| 269 | |
Nicholas Piggin | 99bb51c | 2023-12-16 23:42:36 +1000 | [diff] [blame] | 270 | assert(STACK_INT_FRAME_SIZE % 16 == 0); |
| 271 | |
Nicholas Piggin | c76b0d0 | 2024-05-04 22:28:22 +1000 | [diff] [blame^] | 272 | /* set parameters from dt */ |
| 273 | cpu_init_params(); |
| 274 | |
| 275 | /* Interrupt Endianness */ |
| 276 | if (machine_is_pseries()) { |
| 277 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ |
| 278 | hcall(H_SET_MODE, 1, 4, 0, 0); |
| 279 | #else |
| 280 | hcall(H_SET_MODE, 0, 4, 0, 0); |
| 281 | #endif |
| 282 | } |
| 283 | |
| 284 | cpu_init_ipis(); |
Andrew Jones | d35482d | 2016-12-09 12:54:34 +0100 | [diff] [blame] | 285 | |
| 286 | /* cpu_init must be called before mem_init */ |
Andrew Jones | efe4200 | 2017-01-13 19:15:22 +0100 | [diff] [blame] | 287 | mem_init(PAGE_ALIGN((unsigned long)freemem)); |
Andrew Jones | d35482d | 2016-12-09 12:54:34 +0100 | [diff] [blame] | 288 | |
| 289 | /* mem_init must be called before io_init */ |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 290 | io_init(); |
| 291 | |
Andrew Jones | efe4200 | 2017-01-13 19:15:22 +0100 | [diff] [blame] | 292 | /* finish setup */ |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 293 | ret = dt_get_bootargs(&bootargs); |
Andrew Jones | d81b83f | 2017-01-13 19:15:19 +0100 | [diff] [blame] | 294 | assert(ret == 0 || ret == -FDT_ERR_NOTFOUND); |
Andrew Jones | 809ebcb | 2016-07-12 10:18:41 +0200 | [diff] [blame] | 295 | setup_args_progname(bootargs); |
Andrew Jones | f266c3e | 2017-01-13 19:15:30 +0100 | [diff] [blame] | 296 | |
| 297 | if (initrd) { |
| 298 | /* environ is currently the only file in the initrd */ |
| 299 | char *env = malloc(initrd_size); |
| 300 | memcpy(env, initrd, initrd_size); |
| 301 | setup_env(env, initrd_size); |
| 302 | } |
Andrew Jones | d72b044 | 2016-02-13 09:59:42 +0100 | [diff] [blame] | 303 | } |