2026-01-29 22:25:33 +08:00

489 lines
11 KiB
C

// 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 <linux/acpi.h>
#include <linux/efi.h>
#include <linux/export.h>
#include <linux/memblock.h>
#include <linux/of_fdt.h>
#include <linux/initrd.h>
#include <asm/early_ioremap.h>
#include <asm/bootinfo.h>
#include <asm/loongson.h>
#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);