20bbd81201
As discussed on the mailing list and voted upon, the coreboot project is going to move the majority of copyrights out of the headers and into an AUTHORS file. This will happen a bit at a time, as we'll be unifying license headers at the same time. Signed-off-by: Martin Roth <martin@coreboot.org> Change-Id: Ifd4329905847d9dd06de67b9a443c8ee50c0e7a7 Reviewed-on: https://review.coreboot.org/c/coreboot/+/35177 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Patrick Georgi <pgeorgi@google.com>
657 lines
18 KiB
C
657 lines
18 KiB
C
/*
|
|
* This file is part of the coreboot project.
|
|
*
|
|
* 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; version 2 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <console/console.h>
|
|
#include <device/path.h>
|
|
#include <device/pci_ids.h>
|
|
#include <arch/smp/mpspec.h>
|
|
#include <string.h>
|
|
#include <arch/cpu.h>
|
|
#include <cpu/x86/lapic.h>
|
|
#include <drivers/generic/ioapic/chip.h>
|
|
|
|
/* Initialize the specified "mc" struct with initial values. */
|
|
void mptable_init(struct mp_config_table *mc, u32 lapic_addr)
|
|
{
|
|
int i;
|
|
|
|
memset(mc, 0, sizeof(*mc));
|
|
|
|
memcpy(mc->mpc_signature, MPC_SIGNATURE, 4);
|
|
|
|
mc->mpc_length = sizeof(*mc); /* Initially just the header size. */
|
|
mc->mpc_spec = 0x04; /* MultiProcessor specification 1.4 */
|
|
mc->mpc_checksum = 0; /* Not yet computed. */
|
|
mc->mpc_oemptr = 0;
|
|
mc->mpc_oemsize = 0;
|
|
mc->mpc_entry_count = 0; /* No entries yet... */
|
|
mc->mpc_lapic = lapic_addr;
|
|
mc->mpe_length = 0;
|
|
mc->mpe_checksum = 0;
|
|
mc->reserved = 0;
|
|
|
|
strncpy(mc->mpc_oem, CONFIG_MAINBOARD_VENDOR, 8);
|
|
strncpy(mc->mpc_productid, CONFIG_MAINBOARD_PART_NUMBER, 12);
|
|
|
|
/*
|
|
* The oem/productid fields are exactly 8/12 bytes long. If the resp.
|
|
* entry is shorter, the remaining bytes are filled with spaces.
|
|
*/
|
|
for (i = MIN(strlen(CONFIG_MAINBOARD_VENDOR), 8); i < 8; i++)
|
|
mc->mpc_oem[i] = ' ';
|
|
for (i = MIN(strlen(CONFIG_MAINBOARD_PART_NUMBER), 12); i < 12; i++)
|
|
mc->mpc_productid[i] = ' ';
|
|
}
|
|
|
|
static unsigned char smp_compute_checksum(void *v, int len)
|
|
{
|
|
unsigned char *bytes;
|
|
unsigned char checksum;
|
|
int i;
|
|
bytes = v;
|
|
checksum = 0;
|
|
for (i = 0; i < len; i++)
|
|
checksum -= bytes[i];
|
|
return checksum;
|
|
}
|
|
|
|
static void *smp_write_floating_table_physaddr(uintptr_t addr,
|
|
uintptr_t mpf_physptr, unsigned int virtualwire)
|
|
{
|
|
struct intel_mp_floating *mf;
|
|
void *v;
|
|
|
|
v = (void *)addr;
|
|
mf = v;
|
|
mf->mpf_signature[0] = '_';
|
|
mf->mpf_signature[1] = 'M';
|
|
mf->mpf_signature[2] = 'P';
|
|
mf->mpf_signature[3] = '_';
|
|
mf->mpf_physptr = mpf_physptr;
|
|
mf->mpf_length = 1;
|
|
mf->mpf_specification = 4;
|
|
mf->mpf_checksum = 0;
|
|
mf->mpf_feature1 = 0;
|
|
mf->mpf_feature2 = virtualwire?MP_FEATURE_PIC:MP_FEATURE_VIRTUALWIRE;
|
|
mf->mpf_feature3 = 0;
|
|
mf->mpf_feature4 = 0;
|
|
mf->mpf_feature5 = 0;
|
|
mf->mpf_checksum = smp_compute_checksum(mf, mf->mpf_length*16);
|
|
return v;
|
|
}
|
|
|
|
void *smp_write_floating_table(unsigned long addr, unsigned int virtualwire)
|
|
{
|
|
/* 16 byte align the table address */
|
|
addr = (addr + 0xf) & (~0xf);
|
|
return smp_write_floating_table_physaddr(addr, addr
|
|
+ SMP_FLOATING_TABLE_LEN, virtualwire);
|
|
}
|
|
|
|
void *smp_next_mpc_entry(struct mp_config_table *mc)
|
|
{
|
|
void *v;
|
|
v = (void *)(((char *)mc) + mc->mpc_length);
|
|
|
|
return v;
|
|
}
|
|
static void smp_add_mpc_entry(struct mp_config_table *mc, u16 length)
|
|
{
|
|
mc->mpc_length += length;
|
|
mc->mpc_entry_count++;
|
|
}
|
|
|
|
void *smp_next_mpe_entry(struct mp_config_table *mc)
|
|
{
|
|
void *v;
|
|
v = (void *)(((char *)mc) + mc->mpc_length + mc->mpe_length);
|
|
|
|
return v;
|
|
}
|
|
static void smp_add_mpe_entry(struct mp_config_table *mc, mpe_t mpe)
|
|
{
|
|
mc->mpe_length += mpe->mpe_length;
|
|
}
|
|
|
|
/*
|
|
* Type 0: Processor Entries:
|
|
* Entry Type, LAPIC ID, LAPIC Version, CPU Flags EN/BP,
|
|
* CPU Signature (Stepping, Model, Family), Feature Flags
|
|
*/
|
|
void smp_write_processor(struct mp_config_table *mc,
|
|
u8 apicid, u8 apicver, u8 cpuflag,
|
|
u32 cpufeature, u32 featureflag)
|
|
{
|
|
struct mpc_config_processor *mpc;
|
|
mpc = smp_next_mpc_entry(mc);
|
|
memset(mpc, '\0', sizeof(*mpc));
|
|
mpc->mpc_type = MP_PROCESSOR;
|
|
mpc->mpc_apicid = apicid;
|
|
mpc->mpc_apicver = apicver;
|
|
mpc->mpc_cpuflag = cpuflag;
|
|
mpc->mpc_cpufeature = cpufeature;
|
|
mpc->mpc_featureflag = featureflag;
|
|
smp_add_mpc_entry(mc, sizeof(*mpc));
|
|
}
|
|
|
|
/*
|
|
* If we assume a symmetric processor configuration we can
|
|
* get all of the information we need to write the processor
|
|
* entry from the bootstrap processor.
|
|
* Plus I don't think linux really even cares.
|
|
* Having the proper apicid's in the table so the non-bootstrap
|
|
* processors can be woken up should be enough.
|
|
*/
|
|
void smp_write_processors(struct mp_config_table *mc)
|
|
{
|
|
int boot_apic_id;
|
|
int order_id;
|
|
unsigned int apic_version;
|
|
unsigned int cpu_features;
|
|
unsigned int cpu_feature_flags;
|
|
struct device *cpu;
|
|
|
|
boot_apic_id = lapicid();
|
|
apic_version = lapic_read(LAPIC_LVR) & 0xff;
|
|
cpu_features = cpu_get_cpuid();
|
|
cpu_feature_flags = cpu_get_feature_flags_edx();
|
|
/* order the output of the cpus to fix a bug in kernel 2.6.11 */
|
|
for (order_id = 0; order_id < 256; order_id++) {
|
|
for (cpu = all_devices; cpu; cpu = cpu->next) {
|
|
unsigned long cpu_flag;
|
|
if ((cpu->path.type != DEVICE_PATH_APIC) ||
|
|
(cpu->bus->dev->path.type !=
|
|
DEVICE_PATH_CPU_CLUSTER))
|
|
continue;
|
|
|
|
if (!cpu->enabled)
|
|
continue;
|
|
|
|
cpu_flag = MPC_CPU_ENABLED;
|
|
|
|
if (boot_apic_id == cpu->path.apic.apic_id)
|
|
cpu_flag = MPC_CPU_ENABLED
|
|
| MPC_CPU_BOOTPROCESSOR;
|
|
|
|
if (cpu->path.apic.apic_id == order_id) {
|
|
smp_write_processor(mc,
|
|
cpu->path.apic.apic_id, apic_version,
|
|
cpu_flag, cpu_features,
|
|
cpu_feature_flags
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Type 1: Bus Entries:
|
|
* Entry Type, Bus ID, Bus Type
|
|
*/
|
|
static void smp_write_bus(struct mp_config_table *mc,
|
|
u8 id, const char *bustype)
|
|
{
|
|
struct mpc_config_bus *mpc;
|
|
mpc = smp_next_mpc_entry(mc);
|
|
memset(mpc, '\0', sizeof(*mpc));
|
|
mpc->mpc_type = MP_BUS;
|
|
mpc->mpc_busid = id;
|
|
memcpy(mpc->mpc_bustype, bustype, sizeof(mpc->mpc_bustype));
|
|
smp_add_mpc_entry(mc, sizeof(*mpc));
|
|
}
|
|
|
|
/*
|
|
* Type 2: I/O APIC Entries:
|
|
* Entry Type, APIC ID, Version,
|
|
* APIC Flags:EN, Address
|
|
*/
|
|
void smp_write_ioapic(struct mp_config_table *mc,
|
|
u8 id, u8 ver, void *apicaddr)
|
|
{
|
|
struct mpc_config_ioapic *mpc;
|
|
mpc = smp_next_mpc_entry(mc);
|
|
memset(mpc, '\0', sizeof(*mpc));
|
|
mpc->mpc_type = MP_IOAPIC;
|
|
mpc->mpc_apicid = id;
|
|
mpc->mpc_apicver = ver;
|
|
mpc->mpc_flags = MPC_APIC_USABLE;
|
|
mpc->mpc_apicaddr = apicaddr;
|
|
smp_add_mpc_entry(mc, sizeof(*mpc));
|
|
}
|
|
|
|
/*
|
|
* Type 3: I/O Interrupt Table Entries:
|
|
* Entry Type, Int Type, Int Polarity, Int Level,
|
|
* Source Bus ID, Source Bus IRQ, Dest APIC ID, Dest PIN#
|
|
*/
|
|
void smp_write_intsrc(struct mp_config_table *mc,
|
|
u8 irqtype, u16 irqflag,
|
|
u8 srcbus, u8 srcbusirq,
|
|
u8 dstapic, u8 dstirq)
|
|
{
|
|
struct mpc_config_intsrc *mpc;
|
|
mpc = smp_next_mpc_entry(mc);
|
|
memset(mpc, '\0', sizeof(*mpc));
|
|
mpc->mpc_type = MP_INTSRC;
|
|
mpc->mpc_irqtype = irqtype;
|
|
mpc->mpc_irqflag = irqflag;
|
|
mpc->mpc_srcbus = srcbus;
|
|
mpc->mpc_srcbusirq = srcbusirq;
|
|
mpc->mpc_dstapic = dstapic;
|
|
mpc->mpc_dstirq = dstirq;
|
|
smp_add_mpc_entry(mc, sizeof(*mpc));
|
|
#ifdef DEBUG_MPTABLE
|
|
printk(BIOS_DEBUG,
|
|
"add intsrc srcbus 0x%x srcbusirq 0x%x, dstapic 0x%x, dstirq 0x%x\n",
|
|
srcbus, srcbusirq, dstapic, dstirq);
|
|
hexdump(__func__, mpc, sizeof(*mpc));
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Type 3: I/O Interrupt Table Entries for PCI Devices:
|
|
* This has the same fields as 'Type 3: I/O Interrupt Table Entries'
|
|
* but the Source Bus IRQ field has a slightly different
|
|
* definition:
|
|
* Bits 1-0: PIRQ pin: INT_A# = 0, INT_B# = 1, INT_C# = 2, INT_D# = 3
|
|
* Bits 2-6: Originating PCI Device Number (Not its parent bridge device number)
|
|
* Bit 7: Reserved
|
|
*/
|
|
void smp_write_pci_intsrc(struct mp_config_table *mc,
|
|
u8 irqtype, u8 srcbus, u8 dev, u8 pirq,
|
|
u8 dstapic, u8 dstirq)
|
|
{
|
|
u8 srcbusirq = (dev << 2) | pirq;
|
|
printk(BIOS_SPEW,
|
|
"\tPCI srcbusirq = 0x%x from dev = 0x%x and pirq = %x\n",
|
|
srcbusirq, dev, pirq);
|
|
smp_write_intsrc(mc, irqtype, MP_IRQ_TRIGGER_LEVEL
|
|
| MP_IRQ_POLARITY_LOW, srcbus, srcbusirq, dstapic, dstirq);
|
|
}
|
|
|
|
void smp_write_intsrc_pci_bridge(struct mp_config_table *mc,
|
|
u8 irqtype, u16 irqflag, struct device *dev,
|
|
unsigned char dstapic, unsigned char *dstirq)
|
|
{
|
|
struct device *child;
|
|
|
|
int i;
|
|
int srcbus;
|
|
int slot;
|
|
|
|
struct bus *link;
|
|
unsigned char dstirq_x[4];
|
|
|
|
for (link = dev->link_list; link; link = link->next) {
|
|
|
|
child = link->children;
|
|
srcbus = link->secondary;
|
|
|
|
while (child) {
|
|
if (child->path.type != DEVICE_PATH_PCI)
|
|
goto next;
|
|
|
|
slot = (child->path.pci.devfn >> 3);
|
|
/* round pins */
|
|
for (i = 0; i < 4; i++)
|
|
dstirq_x[i] = dstirq[(i + slot) % 4];
|
|
|
|
if ((child->class >> 16) != PCI_BASE_CLASS_BRIDGE) {
|
|
/* pci device */
|
|
printk(BIOS_DEBUG, "route irq: %s\n",
|
|
dev_path(child));
|
|
for (i = 0; i < 4; i++)
|
|
smp_write_intsrc(mc, irqtype, irqflag,
|
|
srcbus, (slot<<2)|i, dstapic,
|
|
dstirq_x[i]);
|
|
goto next;
|
|
}
|
|
|
|
switch (child->class>>8) {
|
|
case PCI_CLASS_BRIDGE_PCI:
|
|
case PCI_CLASS_BRIDGE_PCMCIA:
|
|
case PCI_CLASS_BRIDGE_CARDBUS:
|
|
printk(BIOS_DEBUG, "route irq bridge: %s\n",
|
|
dev_path(child));
|
|
smp_write_intsrc_pci_bridge(mc, irqtype,
|
|
irqflag, child, dstapic, dstirq_x);
|
|
}
|
|
|
|
next:
|
|
child = child->sibling;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Type 4: Local Interrupt Assignment Entries:
|
|
* Entry Type, Int Type, Int Polarity, Int Level,
|
|
* Source Bus ID, Source Bus IRQ, Dest LAPIC ID,
|
|
* Dest LAPIC LINTIN#
|
|
*/
|
|
void smp_write_lintsrc(struct mp_config_table *mc,
|
|
u8 irqtype, u16 irqflag,
|
|
u8 srcbusid, u8 srcbusirq,
|
|
u8 destapic, u8 destapiclint)
|
|
{
|
|
struct mpc_config_lintsrc *mpc;
|
|
mpc = smp_next_mpc_entry(mc);
|
|
memset(mpc, '\0', sizeof(*mpc));
|
|
mpc->mpc_type = MP_LINTSRC;
|
|
mpc->mpc_irqtype = irqtype;
|
|
mpc->mpc_irqflag = irqflag;
|
|
mpc->mpc_srcbusid = srcbusid;
|
|
mpc->mpc_srcbusirq = srcbusirq;
|
|
mpc->mpc_destapic = destapic;
|
|
mpc->mpc_destapiclint = destapiclint;
|
|
smp_add_mpc_entry(mc, sizeof(*mpc));
|
|
}
|
|
|
|
/*
|
|
* Type 128: System Address Space Mapping Entries
|
|
* Entry Type, Entry Length, Bus ID, Address Type,
|
|
* Address Base Lo/Hi, Address Length Lo/Hi
|
|
*/
|
|
void smp_write_address_space(struct mp_config_table *mc,
|
|
u8 busid, u8 address_type,
|
|
u32 address_base_low, u32 address_base_high,
|
|
u32 address_length_low, u32 address_length_high)
|
|
{
|
|
struct mp_exten_system_address_space *mpe;
|
|
mpe = smp_next_mpe_entry(mc);
|
|
memset(mpe, '\0', sizeof(*mpe));
|
|
mpe->mpe_type = MPE_SYSTEM_ADDRESS_SPACE;
|
|
mpe->mpe_length = sizeof(*mpe);
|
|
mpe->mpe_busid = busid;
|
|
mpe->mpe_address_type = address_type;
|
|
mpe->mpe_address_base_low = address_base_low;
|
|
mpe->mpe_address_base_high = address_base_high;
|
|
mpe->mpe_address_length_low = address_length_low;
|
|
mpe->mpe_address_length_high = address_length_high;
|
|
smp_add_mpe_entry(mc, (mpe_t)mpe);
|
|
}
|
|
|
|
/*
|
|
* Type 129: Bus Hierarchy Descriptor Entry
|
|
* Entry Type, Entry Length, Bus ID, Bus Info,
|
|
* Parent Bus ID
|
|
*/
|
|
void smp_write_bus_hierarchy(struct mp_config_table *mc,
|
|
u8 busid, u8 bus_info, u8 parent_busid)
|
|
{
|
|
struct mp_exten_bus_hierarchy *mpe;
|
|
mpe = smp_next_mpe_entry(mc);
|
|
memset(mpe, '\0', sizeof(*mpe));
|
|
mpe->mpe_type = MPE_BUS_HIERARCHY;
|
|
mpe->mpe_length = sizeof(*mpe);
|
|
mpe->mpe_busid = busid;
|
|
mpe->mpe_bus_info = bus_info;
|
|
mpe->mpe_parent_busid = parent_busid;
|
|
smp_add_mpe_entry(mc, (mpe_t)mpe);
|
|
}
|
|
|
|
/*
|
|
* Type 130: Compatibility Bus Address Space Modifier Entry
|
|
* Entry Type, Entry Length, Bus ID, Address Modifier
|
|
* Predefined Range List
|
|
*/
|
|
void smp_write_compatibility_address_space(struct mp_config_table *mc,
|
|
u8 busid, u8 address_modifier,
|
|
u32 range_list)
|
|
{
|
|
struct mp_exten_compatibility_address_space *mpe;
|
|
mpe = smp_next_mpe_entry(mc);
|
|
memset(mpe, '\0', sizeof(*mpe));
|
|
mpe->mpe_type = MPE_COMPATIBILITY_ADDRESS_SPACE;
|
|
mpe->mpe_length = sizeof(*mpe);
|
|
mpe->mpe_busid = busid;
|
|
mpe->mpe_address_modifier = address_modifier;
|
|
mpe->mpe_range_list = range_list;
|
|
smp_add_mpe_entry(mc, (mpe_t)mpe);
|
|
}
|
|
|
|
void mptable_lintsrc(struct mp_config_table *mc, unsigned long bus_isa)
|
|
{
|
|
smp_write_lintsrc(mc, mp_ExtINT, MP_IRQ_TRIGGER_EDGE
|
|
| MP_IRQ_POLARITY_HIGH, bus_isa, 0x0, MP_APIC_ALL, 0x0);
|
|
smp_write_lintsrc(mc, mp_NMI, MP_IRQ_TRIGGER_EDGE
|
|
| MP_IRQ_POLARITY_HIGH, bus_isa, 0x0, MP_APIC_ALL, 0x1);
|
|
}
|
|
|
|
void mptable_add_isa_interrupts(struct mp_config_table *mc,
|
|
unsigned long bus_isa, unsigned long apicid, int external_int2)
|
|
{
|
|
/*I/O Ints: Type Trigger Polarity
|
|
* Bus ID IRQ APIC ID PIN# */
|
|
smp_write_intsrc(mc, external_int2?mp_INT:mp_ExtINT,
|
|
MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0x0, apicid, 0x0);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0x1, apicid, 0x1);
|
|
smp_write_intsrc(mc, external_int2?mp_ExtINT:mp_INT,
|
|
MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0x0, apicid, 0x2);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0x3, apicid, 0x3);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0x4, apicid, 0x4);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0x6, apicid, 0x6);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0x7, apicid, 0x7);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0x8, apicid, 0x8);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0x9, apicid, 0x9);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0xa, apicid, 0xa);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0xb, apicid, 0xb);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0xc, apicid, 0xc);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0xd, apicid, 0xd);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0xe, apicid, 0xe);
|
|
smp_write_intsrc(mc, mp_INT, MP_IRQ_TRIGGER_EDGE | MP_IRQ_POLARITY_HIGH,
|
|
bus_isa, 0xf, apicid, 0xf);
|
|
}
|
|
|
|
void mptable_write_buses(struct mp_config_table *mc, int *max_pci_bus,
|
|
int *isa_bus)
|
|
{
|
|
int dummy, i, highest;
|
|
char buses[256];
|
|
struct device *dev;
|
|
|
|
if (!max_pci_bus)
|
|
max_pci_bus = &dummy;
|
|
if (!isa_bus)
|
|
isa_bus = &dummy;
|
|
|
|
*max_pci_bus = 0;
|
|
highest = 0;
|
|
memset(buses, 0, sizeof(buses));
|
|
|
|
for (dev = all_devices; dev; dev = dev->next) {
|
|
struct bus *bus;
|
|
for (bus = dev->link_list; bus; bus = bus->next) {
|
|
if (bus->secondary > 255) {
|
|
printk(BIOS_ERR,
|
|
"A bus claims to have a bus ID > 255?!? Aborting");
|
|
return;
|
|
}
|
|
buses[bus->secondary] = 1;
|
|
if (highest < bus->secondary)
|
|
highest = bus->secondary;
|
|
}
|
|
}
|
|
for (i = 0; i <= highest; i++) {
|
|
if (buses[i]) {
|
|
smp_write_bus(mc, i, "PCI ");
|
|
*max_pci_bus = i;
|
|
}
|
|
}
|
|
*isa_bus = *max_pci_bus + 1;
|
|
smp_write_bus(mc, *isa_bus, "ISA ");
|
|
}
|
|
|
|
void *mptable_finalize(struct mp_config_table *mc)
|
|
{
|
|
mc->mpe_checksum = smp_compute_checksum(smp_next_mpc_entry(mc),
|
|
mc->mpe_length);
|
|
mc->mpc_checksum = smp_compute_checksum(mc, mc->mpc_length);
|
|
printk(BIOS_DEBUG, "Wrote the mp table end at: %p - %p\n",
|
|
mc, smp_next_mpe_entry(mc));
|
|
return smp_next_mpe_entry(mc);
|
|
}
|
|
|
|
static const struct device *find_next_ioapic(unsigned int last_ioapic_id)
|
|
{
|
|
const struct device *dev;
|
|
const struct device *result = NULL;
|
|
unsigned int ioapic_id = MAX_APICS;
|
|
|
|
for (dev = all_devices; dev; dev = dev->next) {
|
|
if (dev->path.type == DEVICE_PATH_IOAPIC &&
|
|
dev->path.ioapic.ioapic_id > last_ioapic_id &&
|
|
dev->path.ioapic.ioapic_id <= ioapic_id) {
|
|
result = dev;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
unsigned long __weak write_smp_table(unsigned long addr)
|
|
{
|
|
struct drivers_generic_ioapic_config *ioapic_config;
|
|
struct mp_config_table *mc;
|
|
int isa_bus, pin, parentpin;
|
|
const struct device *dev;
|
|
const struct device *parent;
|
|
const struct device *oldparent;
|
|
void *tmp, *v;
|
|
int isaioapic = -1, have_fixed_entries;
|
|
const struct pci_irq_info *pci_irq_info;
|
|
unsigned int ioapic_id = 0;
|
|
|
|
v = smp_write_floating_table(addr, 0);
|
|
mc = (void *)(((char *)v) + SMP_FLOATING_TABLE_LEN);
|
|
|
|
mptable_init(mc, LOCAL_APIC_ADDR);
|
|
|
|
smp_write_processors(mc);
|
|
|
|
mptable_write_buses(mc, NULL, &isa_bus);
|
|
|
|
while ((dev = find_next_ioapic(ioapic_id))) {
|
|
ioapic_config = dev->chip_info;
|
|
if (!ioapic_config) {
|
|
printk(BIOS_ERR, "%s has no config, ignoring\n",
|
|
dev_path(dev));
|
|
ioapic_id++;
|
|
continue;
|
|
}
|
|
|
|
ioapic_id = dev->path.ioapic.ioapic_id;
|
|
smp_write_ioapic(mc, ioapic_id,
|
|
ioapic_config->version,
|
|
ioapic_config->base);
|
|
|
|
if (ioapic_config->have_isa_interrupts) {
|
|
if (isaioapic >= 0)
|
|
printk(BIOS_ERR,
|
|
"More than one IOAPIC with ISA interrupts?\n");
|
|
else
|
|
isaioapic = dev->path.ioapic.ioapic_id;
|
|
}
|
|
}
|
|
|
|
if (isaioapic >= 0) {
|
|
/* Legacy Interrupts */
|
|
printk(BIOS_DEBUG, "Writing ISA IRQs\n");
|
|
mptable_add_isa_interrupts(mc, isa_bus, isaioapic, 0);
|
|
}
|
|
|
|
for (dev = all_devices; dev; dev = dev->next) {
|
|
|
|
if (dev->path.type != DEVICE_PATH_PCI || !dev->enabled)
|
|
continue;
|
|
|
|
have_fixed_entries = 0;
|
|
for (pin = 0; pin < 4; pin++) {
|
|
if (dev->pci_irq_info[pin].ioapic_dst_id) {
|
|
printk(BIOS_DEBUG,
|
|
"fixed IRQ entry for: %s: INT%c# -> IOAPIC %d PIN %d\n",
|
|
dev_path(dev),
|
|
pin + 'A',
|
|
dev->pci_irq_info[pin].ioapic_dst_id,
|
|
dev->pci_irq_info[pin].ioapic_irq_pin);
|
|
smp_write_intsrc(mc, mp_INT,
|
|
dev->pci_irq_info[pin].ioapic_flags,
|
|
dev->bus->secondary,
|
|
((dev->path.pci.devfn & 0xf8) >> 1)
|
|
| pin,
|
|
dev->pci_irq_info[pin].ioapic_dst_id,
|
|
dev->pci_irq_info[pin].ioapic_irq_pin);
|
|
have_fixed_entries = 1;
|
|
}
|
|
}
|
|
|
|
if (!have_fixed_entries) {
|
|
pin = (dev->path.pci.devfn & 7) % 4;
|
|
oldparent = parent = dev;
|
|
while ((parent = parent->bus->dev)) {
|
|
parentpin = (oldparent->path.pci.devfn >> 3)
|
|
+ (oldparent->path.pci.devfn & 7);
|
|
parentpin += dev->path.pci.devfn & 7;
|
|
parentpin += dev->path.pci.devfn >> 3;
|
|
parentpin %= 4;
|
|
|
|
pci_irq_info = &parent->pci_irq_info[parentpin];
|
|
if (pci_irq_info->ioapic_dst_id) {
|
|
printk(BIOS_DEBUG,
|
|
"automatic IRQ entry for %s: INT%c# -> IOAPIC %d PIN %d\n",
|
|
dev_path(dev), pin + 'A',
|
|
pci_irq_info->ioapic_dst_id,
|
|
pci_irq_info->ioapic_irq_pin);
|
|
smp_write_intsrc(mc, mp_INT,
|
|
pci_irq_info->ioapic_flags,
|
|
dev->bus->secondary,
|
|
((dev->path.pci.devfn & 0xf8)
|
|
>> 1) | pin,
|
|
pci_irq_info->ioapic_dst_id,
|
|
pci_irq_info->ioapic_irq_pin);
|
|
|
|
break;
|
|
}
|
|
|
|
if (parent->path.type == DEVICE_PATH_DOMAIN) {
|
|
printk(BIOS_WARNING,
|
|
"no IRQ found for %s\n",
|
|
dev_path(dev));
|
|
break;
|
|
}
|
|
oldparent = parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
mptable_lintsrc(mc, isa_bus);
|
|
tmp = mptable_finalize(mc);
|
|
printk(BIOS_INFO, "MPTABLE len: %d\n", (unsigned int)((uintptr_t)tmp -
|
|
(uintptr_t)v));
|
|
return (unsigned long)tmp;
|
|
}
|