Merge tag 'u-boot-atmel-fixes-2020.07-a' of https://gitlab.denx.de/u-boot/custodians...
[oweals/u-boot.git] / arch / x86 / lib / mpspec.c
index 26d102544d5afc919ecc489a71b98877470bdeba..1c49c41b310e79fef7da23d884d18b0dd27ef23b 100644 (file)
@@ -1,27 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0+
 /*
  * Copyright (C) 2015, Bin Meng <bmeng.cn@gmail.com>
  *
  * Adapted from coreboot src/arch/x86/boot/mpspec.c
- *
- * SPDX-License-Identifier:    GPL-2.0+
  */
 
 #include <common.h>
 #include <cpu.h>
 #include <dm.h>
+#include <errno.h>
+#include <fdtdec.h>
+#include <log.h>
 #include <asm/cpu.h>
+#include <asm/irq.h>
 #include <asm/ioapic.h>
 #include <asm/lapic.h>
 #include <asm/mpspec.h>
 #include <asm/tables.h>
 #include <dm/uclass-internal.h>
 
+DECLARE_GLOBAL_DATA_PTR;
+
+static bool isa_irq_occupied[16];
+
 struct mp_config_table *mp_write_floating_table(struct mp_floating_table *mf)
 {
-       u32 mc;
+       ulong mc;
 
        memcpy(mf->mpf_signature, MPF_SIGNATURE, 4);
-       mf->mpf_physptr = (u32)mf + sizeof(struct mp_floating_table);
+       mf->mpf_physptr = (ulong)mf + sizeof(struct mp_floating_table);
        mf->mpf_length = 1;
        mf->mpf_spec = MPSPEC_V14;
        mf->mpf_checksum = 0;
@@ -34,7 +41,7 @@ struct mp_config_table *mp_write_floating_table(struct mp_floating_table *mf)
        mf->mpf_feature5 = 0;
        mf->mpf_checksum = table_compute_checksum(mf, mf->mpf_length * 16);
 
-       mc = (u32)mf + sizeof(struct mp_floating_table);
+       mc = (ulong)mf + sizeof(struct mp_floating_table);
        return (struct mp_config_table *)mc;
 }
 
@@ -212,14 +219,191 @@ void mp_write_compat_address_space(struct mp_config_table *mc, int busid,
 
 u32 mptable_finalize(struct mp_config_table *mc)
 {
-       u32 end;
+       ulong end;
 
        mc->mpe_checksum = table_compute_checksum((void *)mp_next_mpc_entry(mc),
                                                  mc->mpe_length);
        mc->mpc_checksum = table_compute_checksum(mc, mc->mpc_length);
        end = mp_next_mpe_entry(mc);
 
-       debug("Write the MP table at: %x - %x\n", (u32)mc, end);
+       debug("Write the MP table at: %lx - %lx\n", (ulong)mc, end);
+
+       return end;
+}
+
+static void mptable_add_isa_interrupts(struct mp_config_table *mc, int bus_isa,
+                                      int apicid, int external_int2)
+{
+       int i;
+
+       mp_write_intsrc(mc, external_int2 ? MP_INT : MP_EXTINT,
+                       MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
+                       bus_isa, 0, apicid, 0);
+       mp_write_intsrc(mc, MP_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
+                       bus_isa, 1, apicid, 1);
+       mp_write_intsrc(mc, external_int2 ? MP_EXTINT : MP_INT,
+                       MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
+                       bus_isa, 0, apicid, 2);
+
+       for (i = 3; i < 16; i++) {
+               /*
+                * Do not write ISA interrupt entry if it is already occupied
+                * by the platform devices.
+                */
+               if (isa_irq_occupied[i])
+                       continue;
+
+               mp_write_intsrc(mc, MP_INT,
+                               MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
+                               bus_isa, i, apicid, i);
+       }
+}
+
+/*
+ * Check duplicated I/O interrupt assignment table entry, to make sure
+ * there is only one entry with the given bus, device and interrupt pin.
+ */
+static bool check_dup_entry(struct mpc_config_intsrc *intsrc_base,
+                           int entry_num, int bus, int device, int pin)
+{
+       struct mpc_config_intsrc *intsrc = intsrc_base;
+       int i;
+
+       for (i = 0; i < entry_num; i++) {
+               if (intsrc->mpc_srcbus == bus &&
+                   intsrc->mpc_srcbusirq == ((device << 2) | (pin - 1)))
+                       break;
+               intsrc++;
+       }
+
+       return (i == entry_num) ? false : true;
+}
+
+/* TODO: move this to driver model */
+__weak int mp_determine_pci_dstirq(int bus, int dev, int func, int pirq)
+{
+       /* PIRQ[A-H] are connected to I/O APIC INTPIN#16-23 */
+       return pirq + 16;
+}
+
+static int mptable_add_intsrc(struct mp_config_table *mc,
+                             int bus_isa, int apicid)
+{
+       struct mpc_config_intsrc *intsrc_base;
+       int intsrc_entries = 0;
+       const void *blob = gd->fdt_blob;
+       struct udevice *dev;
+       int len, count;
+       const u32 *cell;
+       int i, ret;
+
+       ret = uclass_first_device_err(UCLASS_IRQ, &dev);
+       if (ret && ret != -ENODEV) {
+               debug("%s: Cannot find irq router node\n", __func__);
+               return ret;
+       }
+
+       /* Get I/O interrupt information from device tree */
+       cell = fdt_getprop(blob, dev_of_offset(dev), "intel,pirq-routing",
+                          &len);
+       if (!cell)
+               return -ENOENT;
+
+       if ((len % sizeof(struct pirq_routing)) == 0)
+               count = len / sizeof(struct pirq_routing);
+       else
+               return -EINVAL;
+
+       intsrc_base = (struct mpc_config_intsrc *)mp_next_mpc_entry(mc);
+
+       for (i = 0; i < count; i++) {
+               struct pirq_routing pr;
+               int bus, dev, func;
+               int dstirq;
+
+               pr.bdf = fdt_addr_to_cpu(cell[0]);
+               pr.pin = fdt_addr_to_cpu(cell[1]);
+               pr.pirq = fdt_addr_to_cpu(cell[2]);
+               bus = PCI_BUS(pr.bdf);
+               dev = PCI_DEV(pr.bdf);
+               func = PCI_FUNC(pr.bdf);
+
+               if (check_dup_entry(intsrc_base, intsrc_entries,
+                                   bus, dev, pr.pin)) {
+                       debug("found entry for bus %d device %d INT%c, skipping\n",
+                             bus, dev, 'A' + pr.pin - 1);
+                       cell += sizeof(struct pirq_routing) / sizeof(u32);
+                       continue;
+               }
+
+               dstirq = mp_determine_pci_dstirq(bus, dev, func, pr.pirq);
+               /*
+                * For PIRQ which is connected to I/O APIC interrupt pin#0-15,
+                * mark it as occupied so that we can skip it later.
+                */
+               if (dstirq < 16)
+                       isa_irq_occupied[dstirq] = true;
+               mp_write_pci_intsrc(mc, MP_INT, bus, dev, pr.pin,
+                                   apicid, dstirq);
+               intsrc_entries++;
+               cell += sizeof(struct pirq_routing) / sizeof(u32);
+       }
+
+       /* Legacy Interrupts */
+       debug("Writing ISA IRQs\n");
+       mptable_add_isa_interrupts(mc, bus_isa, apicid, 0);
+
+       return 0;
+}
+
+static void mptable_add_lintsrc(struct mp_config_table *mc, int bus_isa)
+{
+       mp_write_lintsrc(mc, MP_EXTINT,
+                        MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
+                        bus_isa, 0, MP_APIC_ALL, 0);
+       mp_write_lintsrc(mc, MP_NMI,
+                        MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
+                        bus_isa, 0, MP_APIC_ALL, 1);
+}
+
+ulong write_mp_table(ulong addr)
+{
+       struct mp_config_table *mc;
+       int ioapic_id, ioapic_ver;
+       int bus_isa = 0xff;
+       int ret;
+       ulong end;
+
+       /* 16 byte align the table address */
+       addr = ALIGN(addr, 16);
+
+       /* Write floating table */
+       mc = mp_write_floating_table((struct mp_floating_table *)addr);
+
+       /* Write configuration table header */
+       mp_config_table_init(mc);
+
+       /* Write processor entry */
+       mp_write_processor(mc);
+
+       /* Write bus entry */
+       mp_write_bus(mc, bus_isa, BUSTYPE_ISA);
+
+       /* Write I/O APIC entry */
+       ioapic_id = io_apic_read(IO_APIC_ID) >> 24;
+       ioapic_ver = io_apic_read(IO_APIC_VER) & 0xff;
+       mp_write_ioapic(mc, ioapic_id, ioapic_ver, IO_APIC_ADDR);
+
+       /* Write I/O interrupt assignment entry */
+       ret = mptable_add_intsrc(mc, bus_isa, ioapic_id);
+       if (ret)
+               debug("Failed to write I/O interrupt assignment table\n");
+
+       /* Write local interrupt assignment entry */
+       mptable_add_lintsrc(mc, bus_isa);
+
+       /* Finalize the MP table */
+       end = mptable_finalize(mc);
 
        return end;
 }