// SPDX-License-Identifier: GPL-2.0 /* * Author: Yun Liu, liuyun@loongson.cn * Copyright (C) 2020 Loongson Technology Co., Ltd. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. */ #include #include #include #include #include #include #include #include #include #include "legacy_boot.h" #define MAX_CORE_PIC 256 #define PREFIX "ACPI: " #define MSI_MSG_ADDRESS 0x2FF00000 #define MSI_MSG_DEFAULT_COUNT 0xC0 struct boot_params *efi_bp; struct loongsonlist_mem_map *g_mmap; struct acpi_madt_lio_pic *acpi_liointc; struct acpi_madt_eio_pic *acpi_eiointc[MAX_IO_PICS]; struct acpi_madt_ht_pic *acpi_htintc; struct acpi_madt_lpc_pic *acpi_pchlpc; struct acpi_madt_msi_pic *acpi_pchmsi[MAX_IO_PICS]; struct acpi_madt_bio_pic *acpi_pchpic[MAX_IO_PICS]; struct irq_domain *cpu_domain; struct irq_domain *liointc_domain; struct irq_domain *pch_lpc_domain; struct irq_domain *pch_msi_domain[MAX_IO_PICS]; struct irq_domain *pch_pic_domain[MAX_IO_PICS]; char arcs_cmdline[COMMAND_LINE_SIZE]; int nr_io_pics; int bpi_version; struct acpi_madt_lio_pic liointc_default = { .address = LOONGSON_REG_BASE + 0x1400, .size = 256, .cascade = {2, 3}, .cascade_map = {0x00FFFFFF, 0xff000000}, }; struct acpi_madt_lpc_pic pchlpc_default = { .address = LS7A_LPC_REG_BASE, .size = SZ_4K, .cascade = 19, }; struct acpi_madt_eio_pic eiointc_default[MAX_IO_PICS]; struct acpi_madt_msi_pic pchmsi_default[MAX_IO_PICS]; struct acpi_madt_bio_pic pchpic_default[MAX_IO_PICS]; static int acpi_parse_lapic(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_local_apic *processor = NULL; processor = (struct acpi_madt_local_apic *)header; if (BAD_MADT_ENTRY(processor, end)) return -EINVAL; acpi_table_print_madt_entry(&header->common); set_processor_mask(processor->id, processor->lapic_flags); return 0; } static int bad_pch_pic(unsigned long address) { if (nr_io_pics >= MAX_IO_PICS) { pr_warn("WARNING: Max # of I/O PCH_PICs (%d) exceeded (found %d), skipping\n", MAX_IO_PICS, nr_io_pics); return 1; } if (!address) { pr_warn("WARNING: Bogus (zero) I/O PCH_PIC address found in table, skipping!\n"); return 1; } return 0; } void register_default_pic(int id, u32 address, u32 irq_base) { int j, idx, entries, cores; unsigned long addr; u64 node_map = 0; if (bad_pch_pic(address)) return; idx = nr_io_pics; cores = (cpu_has_hypervisor ? MAX_CORES_PER_EIO_NODE : CORES_PER_EIO_NODE); pchpic_default[idx].address = address; if (idx) pchpic_default[idx].address |= nid_to_addrbase(id) | HT1LO_OFFSET; pchpic_default[idx].id = id; pchpic_default[idx].version = 0; pchpic_default[idx].size = 0x1000; pchpic_default[idx].gsi_base = irq_base; msi_group[nr_io_pics].pci_segment = nr_io_pics; pch_group[nr_io_pics].node = msi_group[nr_io_pics].node = id; addr = pchpic_default[idx].address; /* Read INT_ID.int_num */ entries = (((unsigned long)ls7a_readq(addr) >> 48) & 0xff) + 1; pchmsi_default[idx].msg_address = MSI_MSG_ADDRESS; pchmsi_default[idx].start = entries; pchmsi_default[idx].count = MSI_MSG_DEFAULT_COUNT; for_each_possible_cpu(j) { int node = cpu_logical_map(j) / cores; node_map |= (1 << node); } eiointc_default[idx].cascade = 3 + idx; eiointc_default[idx].node = id; eiointc_default[idx].node_map = node_map; if (idx) { int i; for (i = 0; i < idx + 1; i++) { node_map = 0; for_each_possible_cpu(j) { int node = cpu_logical_map(j) / cores; if (((node & 7) < 4) ? !i : i) node_map |= (1 << node); } eiointc_default[i].node_map = node_map; } } acpi_pchpic[idx] = &pchpic_default[idx]; acpi_pchmsi[idx] = &pchmsi_default[idx]; acpi_eiointc[idx] = &eiointc_default[idx]; nr_io_pics++; } static int acpi_parse_legacy_pch_pic(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_io_apic *pch_pic = NULL; pch_pic = (struct acpi_madt_io_apic *)header; if (BAD_MADT_ENTRY(pch_pic, end)) return -EINVAL; acpi_table_print_madt_entry(&header->common); register_default_pic(pch_pic->id, pch_pic->address, pch_pic->global_irq_base); return 0; } __init int legacy_madt_table_init(void) { /* Parse MADT LAPIC entries */ acpi_table_parse_madt(ACPI_MADT_TYPE_LOCAL_APIC, acpi_parse_lapic, MAX_CORE_PIC); acpi_table_parse_madt(ACPI_MADT_TYPE_IO_APIC, acpi_parse_legacy_pch_pic, MAX_IO_PICS); acpi_liointc = &liointc_default; acpi_pchlpc = &pchlpc_default; return 0; } int setup_legacy_IRQ(void) { int i, ret; struct irq_domain *pic_domain; if (!acpi_eiointc[0]) cpu_data[0].options &= ~LOONGARCH_CPU_EXTIOI; ret = cpuintc_acpi_init(NULL, 0); if (ret) { pr_err("CPU domain init error!\n"); return -1; } cpu_domain = get_cpudomain(); ret = liointc_acpi_init(cpu_domain, acpi_liointc); if (ret) { pr_err("Liointc domain init error!\n"); return -1; } liointc_domain = irq_find_matching_fwnode(liointc_handle, DOMAIN_BUS_ANY); if (cpu_has_extioi) { pr_info("Using EIOINTC interrupt mode\n"); for (i = 0; i < nr_io_pics; i++) { ret = eiointc_acpi_init(cpu_domain, acpi_eiointc[i]); if (ret) { pr_err("Eiointc domain init error!\n"); return -1; } pch_pic_parse_madt((union acpi_subtable_headers *)acpi_pchpic[i], 0); pch_msi_parse_madt((union acpi_subtable_headers *)acpi_pchmsi[i], 0); } /* HTVECINTC maybe not use */ } else { pr_info("Using HTVECINTC interrupt mode\n"); ret = htvec_acpi_init(liointc_domain, acpi_htintc); if (ret) { pr_err("HTVECintc domain init error!\n"); return -1; } pch_pic_parse_madt((union acpi_subtable_headers *)acpi_pchpic[0], 0); pch_msi_parse_madt((union acpi_subtable_headers *)acpi_pchmsi[0], 0); } pic_domain = get_pchpic_irq_domain(); if (pic_domain && !cpu_has_hypervisor) pch_lpc_acpi_init(pic_domain, acpi_pchlpc); return 0; } /* * Manage initrd */ #ifdef CONFIG_BLK_DEV_INITRD static __init int rd_start_early(char *p) { phys_initrd_start = __pa(memparse(p, NULL)); return 0; } early_param("rd_start", rd_start_early); static __init int rd_size_early(char *p) { phys_initrd_size = memparse(p, NULL); return 0; } early_param("rd_size", rd_size_early); #endif __init void fw_init_cmdline(unsigned long argc, unsigned long cmdp) { int i; char **_fw_argv; _fw_argv = (char **)cmdp; arcs_cmdline[0] = '\0'; for (i = 1; i < argc; i++) { strlcat(arcs_cmdline, _fw_argv[i], COMMAND_LINE_SIZE); if (i < (argc - 1)) strlcat(arcs_cmdline, " ", COMMAND_LINE_SIZE); } strscpy(boot_command_line, arcs_cmdline, COMMAND_LINE_SIZE); } static u8 ext_listhdr_checksum(u8 *buffer, u32 length) { u8 sum = 0; u8 *end = buffer + length; while (buffer < end) sum = (u8)(sum + *(buffer++)); return sum; } static int parse_mem(struct _extention_list_hdr *head) { g_mmap = (struct loongsonlist_mem_map *)head; if (ext_listhdr_checksum((u8 *)g_mmap, head->length)) { pr_err("mem checksum error\n"); return -EPERM; } return 0; } /* legacy firmware passed, add use this info if need vbios */ static int parse_vbios(struct _extention_list_hdr *head) { struct loongsonlist_vbios *pvbios; pvbios = (struct loongsonlist_vbios *)head; if (ext_listhdr_checksum((u8 *)pvbios, head->length)) { pr_err("vbios_addr checksum error\n"); return -EPERM; } return 0; } /* legacy firmware passed, add use this info if need screeninfo KVM? */ static int parse_screeninfo(struct _extention_list_hdr *head) { struct loongsonlist_screeninfo *pscreeninfo; pscreeninfo = (struct loongsonlist_screeninfo *)head; if (ext_listhdr_checksum((u8 *)pscreeninfo, head->length)) { pr_err("screeninfo_addr checksum error\n"); return -EPERM; } memcpy(&screen_info, &pscreeninfo->si, sizeof(screen_info)); return 0; } static int list_find(struct boot_params *bp) { struct _extention_list_hdr *fhead = NULL; unsigned long index; fhead = bp->extlist; if (!fhead) { pr_err("the bp ext struct empty!\n"); return -1; } do { if (memcmp(&(fhead->signature), LOONGSON_MEM_SIGNATURE, 3) == 0) { if (parse_mem(fhead) != 0) { pr_err("parse mem failed\n"); return -EPERM; } } else if (memcmp(&(fhead->signature), LOONGSON_VBIOS_SIGNATURE, 5) == 0) { if (parse_vbios(fhead) != 0) { pr_err("parse vbios failed\n"); return -EPERM; } } else if (memcmp(&(fhead->signature), LOONGSON_SCREENINFO_SIGNATURE, 5) == 0) { if (parse_screeninfo(fhead) != 0) { pr_err("parse screeninfo failed\n"); return -EPERM; } } fhead = (struct _extention_list_hdr *)fhead->next; index = (unsigned long)fhead; } while (index); return 0; } unsigned int bpi_init(void) { return list_find(efi_bp); } static int get_bpi_version(u64 *signature) { u8 data[9]; int version = BPI_VERSION_NONE; data[8] = 0; memcpy(data, signature, sizeof(*signature)); if (kstrtoint(&data[3], 10, &version)) return BPI_VERSION_NONE; return version; } static void __init parse_bpi_flags(void) { if (efi_bp->flags & BPI_FLAGS_UEFI_SUPPORTED) set_bit(EFI_BOOT, &efi.flags); else clear_bit(EFI_BOOT, &efi.flags); } __init unsigned long legacy_boot_init(unsigned long argc, unsigned long cmdptr, unsigned long bpi) { int ret; if (!bpi || argc < 2) return -1; efi_bp = (struct boot_params *)bpi; bpi_version = get_bpi_version(&efi_bp->signature); pr_info("BPI%d with boot flags %llx.\n", bpi_version, efi_bp->flags); if (bpi_version == BPI_VERSION_NONE) { if (cpu_has_hypervisor) pr_err(FW_BUG "Fatal error, bpi ver NONE!\n"); else panic(FW_BUG "Fatal error, bpi ver NONE!\n"); } else if (bpi_version == BPI_VERSION_V2) parse_bpi_flags(); fw_init_cmdline(argc, cmdptr); ret = bpi_init(); if (ret) { pr_err("init legacy firmware error!\n"); return -1; } return 0; } static int __init add_legacy_isa_io(struct fwnode_handle *fwnode, unsigned long isa_base) { int ret = 0; unsigned long vaddr; struct logic_pio_hwaddr *range; range = kzalloc(sizeof(*range), GFP_ATOMIC); if (!range) return -ENOMEM; range->fwnode = fwnode; range->size = ISA_IOSIZE; range->hw_start = isa_base; range->flags = LOGIC_PIO_CPU_MMIO; ret = logic_pio_register_range(range); if (ret) { kfree(range); return ret; } if (range->io_start != 0) { logic_pio_unregister_range(range); kfree(range); return -EINVAL; } vaddr = (unsigned long)(PCI_IOBASE + range->io_start); ret = vmap_page_range(vaddr, vaddr + range->size, range->hw_start, pgprot_device(PAGE_KERNEL)); return ret; } static struct fwnode_handle * __init parse_isa_base(u64 *cpu_addr) { struct device_node *np; const __be32 *ranges = NULL; int len; struct device_node *node; for_each_node_by_name(np, "isa") { node = of_node_get(np); if (!node) break; ranges = of_get_property(node, "ranges", &len); if (!ranges || (ranges && len > 0)) break; } if (ranges) { ranges += 2; *cpu_addr = of_translate_address(np, ranges); return &np->fwnode; } return NULL; } static int __init register_legacy_isa_io(void) { struct fwnode_handle *fwnode; u64 cpu_addr; if (!acpi_disabled) { cpu_addr = ISA_PHY_IOBASE; fwnode = kzalloc(sizeof(*fwnode), GFP_ATOMIC); } else { fwnode = parse_isa_base(&cpu_addr); } if (fwnode) add_legacy_isa_io(fwnode, cpu_addr); return 0; } arch_initcall(register_legacy_isa_io);