x86: Generate a valid MultiProcessor (MP) table
authorBin Meng <bmeng.cn@gmail.com>
Tue, 23 Jun 2015 04:18:52 +0000 (12:18 +0800)
committerSimon Glass <sjg@chromium.org>
Wed, 15 Jul 2015 00:03:17 +0000 (18:03 -0600)
Implement write_mp_table() to create a minimal working MP table.
This includes an MP floating table, a configuration table header
and all of the 5 base configuration table entries. The I/O interrupt
assignment table entry is created based on the same information used
in the creation of PIRQ routing table from device tree. A check
duplicated entry logic is applied to prevent writing multiple I/O
interrupt entries with the same information.

Use a Kconfig option GENERATE_MP_TABLE to tell U-Boot whether we
need actually write the MP table at the F seg, just like we did for
PIRQ routing and SFI tables. With MP table existence, linux kernel
will switch to I/O APIC and local APIC to process all the peripheral
interrupts instead of 8259 PICs. This takes full advantage of the
multicore hardware and the SMP kernel.

Signed-off-by: Bin Meng <bmeng.cn@gmail.com>
Acked-by: Simon Glass <sjg@chromium.org>
arch/x86/Kconfig
arch/x86/include/asm/mpspec.h
arch/x86/lib/mpspec.c
arch/x86/lib/tables.c

index 36e97c804880c1231e504760b42fa8f98fe455d0..b52a80e1c2017215766723bc3bbce1e04657bf49 100644 (file)
@@ -313,6 +313,15 @@ config GENERATE_SFI_TABLE
 
          For more information, see http://simplefirmware.org
 
+config GENERATE_MP_TABLE
+       bool "Generate an MP (Multi-Processor) table"
+       default n
+       help
+         Generate an MP (Multi-Processor) table for this board. The MP table
+         provides a way for the operating system to support for symmetric
+         multiprocessing as well as symmetric I/O interrupt handling with
+         the local APIC and I/O APIC.
+
 endmenu
 
 config MAX_PIRQ_LINKS
index 849113a940a78b57d9018ec4a33b9ec3776f9486..efa9231f922f96041928060b7d90087871ff19e7 100644 (file)
@@ -431,4 +431,14 @@ void mp_write_compat_address_space(struct mp_config_table *mc, int busid,
  */
 u32 mptable_finalize(struct mp_config_table *mc);
 
+/**
+ * write_mp_table() - Write MP table
+ *
+ * This writes MP table at a given address.
+ *
+ * @addr:      start address to write MP table
+ * @return:    end address of MP table
+ */
+u32 write_mp_table(u32 addr);
+
 #endif /* __ASM_MPSPEC_H */
index 26d102544d5afc919ecc489a71b98877470bdeba..f16fbcbb0d4ca72621675050b1c7fdc537dfd35c 100644 (file)
@@ -9,13 +9,18 @@
 #include <common.h>
 #include <cpu.h>
 #include <dm.h>
+#include <errno.h>
+#include <fdtdec.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;
+
 struct mp_config_table *mp_write_floating_table(struct mp_floating_table *mf)
 {
        u32 mc;
@@ -223,3 +228,155 @@ u32 mptable_finalize(struct mp_config_table *mc)
 
        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++)
+               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;
+}
+
+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;
+       int node;
+       int len, count;
+       const u32 *cell;
+       int i;
+
+       /* Legacy Interrupts */
+       debug("Writing ISA IRQs\n");
+       mptable_add_isa_interrupts(mc, bus_isa, apicid, 0);
+
+       /* Get I/O interrupt information from device tree */
+       node = fdtdec_next_compatible(blob, 0, COMPAT_INTEL_IRQ_ROUTER);
+       if (node < 0) {
+               debug("%s: Cannot find irq router node\n", __func__);
+               return -ENOENT;
+       }
+
+       cell = fdt_getprop(blob, node, "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;
+
+               pr.bdf = fdt_addr_to_cpu(cell[0]);
+               pr.pin = fdt_addr_to_cpu(cell[1]);
+               pr.pirq = fdt_addr_to_cpu(cell[2]);
+
+               if (check_dup_entry(intsrc_base, intsrc_entries,
+                                   PCI_BUS(pr.bdf), PCI_DEV(pr.bdf), pr.pin)) {
+                       debug("found entry for bus %d device %d INT%c, skipping\n",
+                             PCI_BUS(pr.bdf), PCI_DEV(pr.bdf),
+                             'A' + pr.pin - 1);
+                       cell += sizeof(struct pirq_routing) / sizeof(u32);
+                       continue;
+               }
+
+               /* PIRQ[A-H] are always connected to I/O APIC INTPIN#16-23 */
+               mp_write_pci_intsrc(mc, MP_INT, PCI_BUS(pr.bdf),
+                                   PCI_DEV(pr.bdf), pr.pin, apicid,
+                                   pr.pirq + 16);
+               intsrc_entries++;
+               cell += sizeof(struct pirq_routing) / sizeof(u32);
+       }
+
+       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);
+}
+
+u32 write_mp_table(u32 addr)
+{
+       struct mp_config_table *mc;
+       int ioapic_id, ioapic_ver;
+       int bus_isa = 0xff;
+       int ret;
+       u32 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;
+}
index 41c50bc876ee03801d8ea8086b3cf7f006dbf61a..75ffbc1b220456bd6fbc6f2a266a94129963a01b 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <common.h>
 #include <asm/sfi.h>
+#include <asm/mpspec.h>
 #include <asm/tables.h>
 
 u8 table_compute_checksum(void *v, int len)
@@ -46,4 +47,8 @@ void write_tables(void)
        rom_table_end = write_sfi_table(rom_table_end);
        rom_table_end = ALIGN(rom_table_end, 1024);
 #endif
+#ifdef CONFIG_GENERATE_MP_TABLE
+       rom_table_end = write_mp_table(rom_table_end);
+       rom_table_end = ALIGN(rom_table_end, 1024);
+#endif
 }