| 1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
| 2 | /* |
| 3 | * LoongArch boot helper functions. |
| 4 | * |
| 5 | * Copyright (c) 2023 Loongson Technology Corporation Limited |
| 6 | */ |
| 7 | |
| 8 | #include "qemu/osdep.h" |
| 9 | #include "qemu/units.h" |
| 10 | #include "target/loongarch/cpu.h" |
| 11 | #include "hw/loongarch/virt.h" |
| 12 | #include "hw/core/loader.h" |
| 13 | #include "elf.h" |
| 14 | #include "qemu/error-report.h" |
| 15 | #include "system/reset.h" |
| 16 | #include "system/qtest.h" |
| 17 | |
| 18 | /* |
| 19 | * Linux Image Format |
| 20 | * https://docs.kernel.org/arch/loongarch/booting.html |
| 21 | */ |
| 22 | #define LINUX_PE_MAGIC 0x818223cd |
| 23 | #define MZ_MAGIC 0x5a4d /* "MZ" */ |
| 24 | |
| 25 | struct loongarch_linux_hdr { |
| 26 | uint32_t mz_magic; |
| 27 | uint32_t res0; |
| 28 | uint64_t kernel_entry; |
| 29 | uint64_t kernel_size; |
| 30 | uint64_t load_offset; |
| 31 | uint64_t res1; |
| 32 | uint64_t res2; |
| 33 | uint64_t res3; |
| 34 | uint32_t linux_pe_magic; |
| 35 | uint32_t pe_header_offset; |
| 36 | } QEMU_PACKED; |
| 37 | |
| 38 | static const unsigned int aux_boot_code[] = { |
| 39 | /* Configure reset ebase. */ |
| 40 | 0x0400302c, /* csrwr $t0, LOONGARCH_CSR_EENTRY */ |
| 41 | |
| 42 | /* Disable interrupt. */ |
| 43 | 0x0380100c, /* ori $t0, $zero,0x4 */ |
| 44 | 0x04000180, /* csrxchg $zero, $t0, LOONGARCH_CSR_CRMD */ |
| 45 | 0x03400000, /* nop */ |
| 46 | |
| 47 | 0x0400800c, /* csrrd $t0, LOONGARCH_CSR_CPUNUM */ |
| 48 | 0x034ffd8c, /* andi $t0, $t0, 0x3ff */ |
| 49 | 0x0015000d, /* move $t1, $zero */ |
| 50 | 0x5800718d, /* beq $t0, $t1, 112 */ |
| 51 | |
| 52 | /* Clear mailbox. */ |
| 53 | 0x1400002d, /* lu12i.w $t1, 1(0x1) */ |
| 54 | 0x038081ad, /* ori $t1, $t1, CORE_BUF_20 */ |
| 55 | 0x06481da0, /* iocsrwr.d $zero, $t1 */ |
| 56 | |
| 57 | /* Enable IPI interrupt. */ |
| 58 | 0x1400002c, /* lu12i.w $t0, 1(0x1) */ |
| 59 | 0x0400118c, /* csrxchg $t0, $t0, LOONGARCH_CSR_ECFG */ |
| 60 | 0x02fffc0c, /* addi.d $t0, $r0,-1(0xfff) */ |
| 61 | 0x1400002d, /* lu12i.w $t1, 1(0x1) */ |
| 62 | 0x038011ad, /* ori $t1, $t1, CORE_EN_OFF */ |
| 63 | 0x064819ac, /* iocsrwr.w $t0, $t1 */ |
| 64 | 0x1400002d, /* lu12i.w $t1, 1(0x1) */ |
| 65 | 0x038081ad, /* ori $t1, $t1, CORE_BUF_20 */ |
| 66 | |
| 67 | /* Wait for wakeup <.L11>: */ |
| 68 | 0x06488000, /* idle 0x0 */ |
| 69 | 0x03400000, /* andi $zero, $zero, 0x0 */ |
| 70 | 0x064809ac, /* iocsrrd.w $t0, $t1 */ |
| 71 | 0x43fff59f, /* beqz $t0, -12(0x7ffff4) # 48 <.L11> */ |
| 72 | |
| 73 | /* Read and clear IPI interrupt. */ |
| 74 | 0x1400002d, /* lu12i.w $t1, 1(0x1) */ |
| 75 | 0x064809ac, /* iocsrrd.w $t0, $t1 */ |
| 76 | 0x1400002d, /* lu12i.w $t1, 1(0x1) */ |
| 77 | 0x038031ad, /* ori $t1, $t1, CORE_CLEAR_OFF */ |
| 78 | 0x064819ac, /* iocsrwr.w $t0, $t1 */ |
| 79 | |
| 80 | /* Disable IPI interrupt. */ |
| 81 | 0x1400002c, /* lu12i.w $t0, 1(0x1) */ |
| 82 | 0x04001180, /* csrxchg $zero, $t0, LOONGARCH_CSR_ECFG */ |
| 83 | |
| 84 | /* Read mail buf and jump to specified entry */ |
| 85 | 0x1400002d, /* lu12i.w $t1, 1(0x1) */ |
| 86 | 0x038081ad, /* ori $t1, $t1, CORE_BUF_20 */ |
| 87 | 0x06480dac, /* iocsrrd.d $t0, $t1 */ |
| 88 | 0x00150181, /* move $ra, $t0 */ |
| 89 | 0x4c000020, /* jirl $zero, $ra,0 */ |
| 90 | /* BSP Core */ |
| 91 | 0x03400000, /* nop */ |
| 92 | 0x1800000d, /* pcaddi $t1, 0 */ |
| 93 | 0x28c0a1a4, /* ld.d $a0, $t1, 40 */ |
| 94 | 0x1800000d, /* pcaddi $t1, 0 */ |
| 95 | 0x28c0a1a5, /* ld.d $a1, $t1, 40 */ |
| 96 | 0x1800000d, /* pcaddi $t1, 0 */ |
| 97 | 0x28c0a1a6, /* ld.d $a2, $t1, 40 */ |
| 98 | 0x1800000d, /* pcaddi $t1, 0 */ |
| 99 | 0x28c0a1ac, /* ld.d $t0, $t1, 40 */ |
| 100 | 0x00150181, /* move $ra, $t0 */ |
| 101 | 0x4c000020, /* jirl $zero, $ra,0 */ |
| 102 | 0x00000000, /* .dword 0 A0 */ |
| 103 | 0x00000000, |
| 104 | 0x00000000, /* .dword 0 A1 */ |
| 105 | 0x00000000, |
| 106 | 0x00000000, /* .dword 0 A2 */ |
| 107 | 0x00000000, |
| 108 | 0x00000000, /* .dword 0 PC */ |
| 109 | 0x00000000, |
| 110 | }; |
| 111 | |
| 112 | static inline void *guidcpy(void *dst, const void *src) |
| 113 | { |
| 114 | return memcpy(dst, src, sizeof(efi_guid_t)); |
| 115 | } |
| 116 | |
| 117 | static void init_efi_boot_memmap(MachineState *ms, |
| 118 | struct efi_system_table *systab, |
| 119 | void *p, void *start) |
| 120 | { |
| 121 | unsigned i; |
| 122 | struct efi_boot_memmap *boot_memmap = p; |
| 123 | efi_guid_t tbl_guid = LINUX_EFI_BOOT_MEMMAP_GUID; |
| 124 | LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(ms); |
| 125 | struct memmap_entry *memmap_table; |
| 126 | unsigned int memmap_entries; |
| 127 | |
| 128 | /* efi_configuration_table 1 */ |
| 129 | guidcpy(&systab->tables[0].guid, &tbl_guid); |
| 130 | systab->tables[0].table = (struct efi_configuration_table *)(p - start); |
| 131 | systab->nr_tables = 1; |
| 132 | |
| 133 | boot_memmap->desc_size = sizeof(efi_memory_desc_t); |
| 134 | boot_memmap->desc_ver = 1; |
| 135 | boot_memmap->map_size = 0; |
| 136 | |
| 137 | efi_memory_desc_t *map = p + sizeof(struct efi_boot_memmap); |
| 138 | memmap_table = lvms->memmap_table; |
| 139 | memmap_entries = lvms->memmap_entries; |
| 140 | for (i = 0; i < memmap_entries; i++) { |
| 141 | map = (void *)boot_memmap + sizeof(*map); |
| 142 | map[i].type = memmap_table[i].type; |
| 143 | map[i].phys_addr = ROUND_UP(memmap_table[i].address, 64 * KiB); |
| 144 | map[i].num_pages = ROUND_DOWN(memmap_table[i].address + |
| 145 | memmap_table[i].length - map[i].phys_addr, 64 * KiB); |
| 146 | p += sizeof(efi_memory_desc_t); |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | static void init_efi_initrd_table(struct loongarch_boot_info *info, |
| 151 | struct efi_system_table *systab, |
| 152 | void *p, void *start) |
| 153 | { |
| 154 | efi_guid_t tbl_guid = LINUX_EFI_INITRD_MEDIA_GUID; |
| 155 | struct efi_initrd *initrd_table = p; |
| 156 | |
| 157 | /* efi_configuration_table 2 */ |
| 158 | guidcpy(&systab->tables[1].guid, &tbl_guid); |
| 159 | systab->tables[1].table = (struct efi_configuration_table *)(p - start); |
| 160 | systab->nr_tables = 2; |
| 161 | |
| 162 | initrd_table->base = info->initrd_addr; |
| 163 | initrd_table->size = info->initrd_size; |
| 164 | } |
| 165 | |
| 166 | static void init_efi_fdt_table(struct efi_system_table *systab) |
| 167 | { |
| 168 | efi_guid_t tbl_guid = DEVICE_TREE_GUID; |
| 169 | |
| 170 | /* efi_configuration_table 3 */ |
| 171 | guidcpy(&systab->tables[2].guid, &tbl_guid); |
| 172 | systab->tables[2].table = (void *)FDT_BASE; |
| 173 | systab->nr_tables = 3; |
| 174 | } |
| 175 | |
| 176 | static void init_systab(MachineState *ms, size_t size, |
| 177 | struct loongarch_boot_info *info, void *p, void *start) |
| 178 | { |
| 179 | void *bp_tables_start; |
| 180 | struct efi_system_table *systab = p; |
| 181 | LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(ms); |
| 182 | size_t len; |
| 183 | |
| 184 | len = ROUND_UP(sizeof(struct efi_system_table), 64 * KiB); |
| 185 | if (len > size) { |
| 186 | error_report("could not init efi_system_table"); |
| 187 | exit(1); |
| 188 | } |
| 189 | |
| 190 | info->a2 = p - start; |
| 191 | |
| 192 | systab->hdr.signature = EFI_SYSTEM_TABLE_SIGNATURE; |
| 193 | systab->hdr.revision = EFI_SPECIFICATION_VERSION; |
| 194 | systab->hdr.revision = sizeof(struct efi_system_table), |
| 195 | systab->fw_revision = FW_VERSION << 16 | FW_PATCHLEVEL << 8; |
| 196 | systab->runtime = 0; |
| 197 | systab->boottime = 0; |
| 198 | systab->nr_tables = 0; |
| 199 | |
| 200 | p += len; |
| 201 | size -= len; |
| 202 | |
| 203 | systab->tables = p; |
| 204 | bp_tables_start = p; |
| 205 | |
| 206 | len = ROUND_UP(sizeof(struct efi_boot_memmap) + |
| 207 | sizeof(efi_memory_desc_t) * lvms->memmap_entries, 64 * KiB); |
| 208 | if (len > size) { |
| 209 | error_report("could not init efi_boot_memmap"); |
| 210 | exit(1); |
| 211 | } |
| 212 | |
| 213 | init_efi_boot_memmap(ms, systab, p, start); |
| 214 | p += len; |
| 215 | size -= len; |
| 216 | |
| 217 | len = ROUND_UP(sizeof(struct efi_initrd), 64 * KiB); |
| 218 | if (len > size) { |
| 219 | error_report("could not init efi_initrd"); |
| 220 | exit(1); |
| 221 | } |
| 222 | |
| 223 | init_efi_initrd_table(info, systab, p, start); |
| 224 | p += len; |
| 225 | size -= len; |
| 226 | init_efi_fdt_table(systab); |
| 227 | |
| 228 | systab->tables = (struct efi_configuration_table *)(bp_tables_start - start); |
| 229 | } |
| 230 | |
| 231 | static size_t init_cmdline(struct loongarch_boot_info *info, void *p, |
| 232 | void *start, size_t size) |
| 233 | { |
| 234 | hwaddr cmdline_addr = p - start; |
| 235 | size_t len; |
| 236 | |
| 237 | info->a0 = 1; |
| 238 | info->a1 = cmdline_addr; |
| 239 | |
| 240 | len = g_strlcpy(p, info->kernel_cmdline, size); |
| 241 | |
| 242 | /* include terminating null byte */ |
| 243 | if (len < size) { |
| 244 | len += 1; |
| 245 | } else { |
| 246 | len = size; |
| 247 | } |
| 248 | |
| 249 | return len; |
| 250 | } |
| 251 | |
| 252 | static uint64_t cpu_loongarch_virt_to_phys(void *opaque, uint64_t addr) |
| 253 | { |
| 254 | uint64_t *phys_addr_mask = opaque; |
| 255 | return addr & *phys_addr_mask; |
| 256 | } |
| 257 | |
| 258 | static int64_t load_loongarch_linux_image(const char *filename, |
| 259 | uint64_t phys_addr_mask, |
| 260 | uint64_t *kernel_entry, |
| 261 | uint64_t *kernel_low, |
| 262 | uint64_t *kernel_high) |
| 263 | { |
| 264 | gsize len; |
| 265 | ssize_t size; |
| 266 | uint8_t *buffer; |
| 267 | struct loongarch_linux_hdr *hdr; |
| 268 | |
| 269 | /* Load as raw file otherwise */ |
| 270 | if (!g_file_get_contents(filename, (char **)&buffer, &len, NULL)) { |
| 271 | return -1; |
| 272 | } |
| 273 | size = len; |
| 274 | |
| 275 | /* Unpack the image if it is a EFI zboot image */ |
| 276 | if (unpack_efi_zboot_image(&buffer, &size) < 0) { |
| 277 | g_free(buffer); |
| 278 | return -1; |
| 279 | } |
| 280 | |
| 281 | hdr = (struct loongarch_linux_hdr *)buffer; |
| 282 | |
| 283 | if (extract32(le32_to_cpu(hdr->mz_magic), 0, 16) != MZ_MAGIC || |
| 284 | le32_to_cpu(hdr->linux_pe_magic) != LINUX_PE_MAGIC) { |
| 285 | g_free(buffer); |
| 286 | return -1; |
| 287 | } |
| 288 | |
| 289 | /* Early kernel versions may have those fields in virtual address */ |
| 290 | *kernel_entry = le64_to_cpu(hdr->kernel_entry) & phys_addr_mask; |
| 291 | *kernel_low = le64_to_cpu(hdr->load_offset) & phys_addr_mask; |
| 292 | *kernel_high = *kernel_low + size; |
| 293 | |
| 294 | rom_add_blob_fixed(filename, buffer, size, *kernel_low); |
| 295 | |
| 296 | g_free(buffer); |
| 297 | |
| 298 | return size; |
| 299 | } |
| 300 | |
| 301 | static ram_addr_t alloc_initrd_memory(struct loongarch_boot_info *info, |
| 302 | uint64_t advice_start, ssize_t rd_size) |
| 303 | { |
| 304 | hwaddr base, ram_size, gap, low_end; |
| 305 | ram_addr_t initrd_end, initrd_start; |
| 306 | |
| 307 | base = VIRT_LOWMEM_BASE; |
| 308 | gap = VIRT_LOWMEM_SIZE; |
| 309 | initrd_start = advice_start; |
| 310 | initrd_end = initrd_start + rd_size; |
| 311 | |
| 312 | ram_size = info->ram_size; |
| 313 | low_end = base + MIN(ram_size, gap); |
| 314 | if (initrd_end <= low_end) { |
| 315 | return initrd_start; |
| 316 | } |
| 317 | |
| 318 | if (ram_size <= gap) { |
| 319 | error_report("The low memory too small for initial ram disk '%s'," |
| 320 | "You need to expand the ram", |
| 321 | info->initrd_filename); |
| 322 | exit(1); |
| 323 | } |
| 324 | |
| 325 | /* |
| 326 | * Try to load initrd in the high memory |
| 327 | */ |
| 328 | ram_size -= gap; |
| 329 | initrd_start = VIRT_HIGHMEM_BASE; |
| 330 | if (rd_size <= ram_size) { |
| 331 | return initrd_start; |
| 332 | } |
| 333 | |
| 334 | error_report("The high memory too small for initial ram disk '%s'," |
| 335 | "You need to expand the ram", |
| 336 | info->initrd_filename); |
| 337 | exit(1); |
| 338 | } |
| 339 | |
| 340 | static int64_t load_kernel_info(struct loongarch_boot_info *info, |
| 341 | uint64_t phys_addr_mask) |
| 342 | { |
| 343 | uint64_t kernel_entry, kernel_low, kernel_high, initrd_offset = 0; |
| 344 | ssize_t kernel_size; |
| 345 | |
| 346 | kernel_size = load_elf(info->kernel_filename, NULL, |
| 347 | cpu_loongarch_virt_to_phys, &phys_addr_mask, |
| 348 | &kernel_entry, &kernel_low, |
| 349 | &kernel_high, NULL, ELFDATA2LSB, |
| 350 | EM_LOONGARCH, 1, 0); |
| 351 | kernel_entry = cpu_loongarch_virt_to_phys(&phys_addr_mask, kernel_entry); |
| 352 | if (kernel_size < 0) { |
| 353 | kernel_size = load_loongarch_linux_image(info->kernel_filename, |
| 354 | phys_addr_mask, |
| 355 | &kernel_entry, &kernel_low, |
| 356 | &kernel_high); |
| 357 | } |
| 358 | |
| 359 | if (kernel_size < 0) { |
| 360 | error_report("could not load kernel '%s': %s", |
| 361 | info->kernel_filename, |
| 362 | load_elf_strerror(kernel_size)); |
| 363 | exit(1); |
| 364 | } |
| 365 | |
| 366 | if (info->initrd_filename) { |
| 367 | ssize_t initrd_size = get_image_size(info->initrd_filename, NULL); |
| 368 | if (initrd_size > 0) { |
| 369 | initrd_offset = ROUND_UP(kernel_high + 4 * kernel_size, 64 * KiB); |
| 370 | initrd_offset = alloc_initrd_memory(info, initrd_offset, |
| 371 | initrd_size); |
| 372 | initrd_size = load_image_targphys(info->initrd_filename, |
| 373 | initrd_offset, initrd_size, NULL); |
| 374 | } |
| 375 | |
| 376 | if (initrd_size == -1) { |
| 377 | error_report("could not load initial ram disk '%s'", |
| 378 | info->initrd_filename); |
| 379 | exit(1); |
| 380 | } |
| 381 | |
| 382 | info->initrd_addr = initrd_offset; |
| 383 | info->initrd_size = initrd_size; |
| 384 | } |
| 385 | |
| 386 | return kernel_entry; |
| 387 | } |
| 388 | |
| 389 | static void fw_cfg_add_kernel_info(struct loongarch_boot_info *info, |
| 390 | FWCfgState *fw_cfg) |
| 391 | { |
| 392 | /* |
| 393 | * Expose the kernel, the command line, and the initrd in fw_cfg. |
| 394 | * We don't process them here at all, it's all left to the |
| 395 | * firmware. |
| 396 | */ |
| 397 | load_image_to_fw_cfg(fw_cfg, |
| 398 | FW_CFG_KERNEL_SIZE, FW_CFG_KERNEL_DATA, |
| 399 | info->kernel_filename, |
| 400 | false); |
| 401 | |
| 402 | if (info->initrd_filename) { |
| 403 | load_image_to_fw_cfg(fw_cfg, |
| 404 | FW_CFG_INITRD_SIZE, FW_CFG_INITRD_DATA, |
| 405 | info->initrd_filename, false); |
| 406 | } |
| 407 | |
| 408 | if (info->kernel_cmdline) { |
| 409 | fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, |
| 410 | strlen(info->kernel_cmdline) + 1); |
| 411 | fw_cfg_add_string(fw_cfg, FW_CFG_CMDLINE_DATA, |
| 412 | info->kernel_cmdline); |
| 413 | } |
| 414 | } |
| 415 | |
| 416 | static void loongarch_firmware_boot(LoongArchVirtMachineState *lvms, |
| 417 | struct loongarch_boot_info *info) |
| 418 | { |
| 419 | fw_cfg_add_kernel_info(info, lvms->fw_cfg); |
| 420 | } |
| 421 | |
| 422 | static void init_boot_rom(MachineState *ms, size_t size, |
| 423 | struct loongarch_boot_info *info, void *p) |
| 424 | { |
| 425 | void *start = p; |
| 426 | size_t len; |
| 427 | |
| 428 | len = init_cmdline(info, p, start, size); |
| 429 | len = ROUND_UP(len, 64 * KiB); |
| 430 | if (len > size) { |
| 431 | error_report("could not init systab"); |
| 432 | exit(1); |
| 433 | } |
| 434 | |
| 435 | p += len; |
| 436 | init_systab(ms, size - len, info, p, start); |
| 437 | } |
| 438 | |
| 439 | static void loongarch_direct_kernel_boot(MachineState *ms, |
| 440 | struct loongarch_boot_info *info, |
| 441 | uint64_t phys_addr_mask) |
| 442 | { |
| 443 | void *p, *bp; |
| 444 | int64_t kernel_addr = VIRT_FLASH0_BASE; |
| 445 | uint64_t *data; |
| 446 | size_t size; |
| 447 | |
| 448 | if (info->kernel_filename) { |
| 449 | kernel_addr = load_kernel_info(info, phys_addr_mask); |
| 450 | } else { |
| 451 | if (!qtest_enabled()) { |
| 452 | warn_report("No kernel provided, booting from flash drive."); |
| 453 | } |
| 454 | } |
| 455 | |
| 456 | /* Load cmdline and system tables at [0 - 1 MiB] */ |
| 457 | p = g_malloc0(1 * MiB); |
| 458 | bp = p; |
| 459 | size = 1 * MiB; |
| 460 | init_boot_rom(ms, size, info, p); |
| 461 | rom_add_blob_fixed_as("boot_info", bp, size, 0, &address_space_memory); |
| 462 | |
| 463 | /* Load slave boot code at pflash0 . */ |
| 464 | void *boot_code = g_malloc0(VIRT_FLASH0_SIZE); |
| 465 | memcpy(boot_code, &aux_boot_code, sizeof(aux_boot_code)); |
| 466 | data = (uint64_t *)(boot_code + sizeof(aux_boot_code)); |
| 467 | *(data - 4) = cpu_to_le64(info->a0); |
| 468 | *(data - 3) = cpu_to_le64(info->a1); |
| 469 | *(data - 2) = cpu_to_le64(info->a2); |
| 470 | *(data - 1) = cpu_to_le64(kernel_addr); |
| 471 | rom_add_blob_fixed("boot_code", boot_code, VIRT_FLASH0_SIZE, VIRT_FLASH0_BASE); |
| 472 | |
| 473 | g_free(boot_code); |
| 474 | g_free(bp); |
| 475 | } |
| 476 | |
| 477 | void loongarch_load_kernel(MachineState *ms, struct loongarch_boot_info *info, |
| 478 | uint64_t phys_addr_mask) |
| 479 | { |
| 480 | LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(ms); |
| 481 | |
| 482 | info->kernel_filename = ms->kernel_filename; |
| 483 | info->kernel_cmdline = ms->kernel_cmdline; |
| 484 | info->initrd_filename = ms->initrd_filename; |
| 485 | |
| 486 | if (lvms->bios_loaded) { |
| 487 | loongarch_firmware_boot(lvms, info); |
| 488 | } else { |
| 489 | loongarch_direct_kernel_boot(ms, info, phys_addr_mask); |
| 490 | } |
| 491 | } |