The SMP AP bring-up used the ICR "all excluding self" shorthand for the
INIT assert/deassert and the two STARTUP IPIs. Some hypervisors reject
that broadcast shorthand (bhyve returns an error on the ICR write and
aborts the VM), and it is not the documented per-AP INIT/SIPI sequence.
Send INIT (assert then deassert) and two STARTUP IPIs targeted at each
AP's APIC ID taken from the MADT, with STARTUP level asserted.
No other change to the AP bring-up path.
Tested in QEMU/NVMM on current master with "i386: allocate AP
descriptor tables before starting secondary CPUs" applied (sent
separately; without it the APs corrupt kernel memory once they run),
i386 PAE: -smp 1, 2 and 4 boot, with --enable-ncpus=4 all APs come
online, and 300 s runs of a heavy load (apt-get update plus three
parallel 200 MB downloads) are clean. Earlier, on b46c2dfa, it was
also tested in QEMU TCG and with x86_64 guests: with --enable-ncpus=2
and -smp 4 the guest boots once instead of resetting in a loop.
---
i386/i386/smp.c | 36 ++++++++++++++++++++++++------------
1 file changed, 24 insertions(+), 12 deletions(-)
diff --git a/i386/i386/smp.c b/i386/i386/smp.c
index 6b610020..510fd4cb 100644
--- a/i386/i386/smp.c
+++ b/i386/i386/smp.c
@@ -85,7 +85,7 @@ wait_for_ipi(void)
}
static int
-smp_send_ipi_init(int bsp_apic_id)
+smp_send_ipi_init(int apic_id)
{
int err;
@@ -94,9 +94,10 @@ smp_send_ipi_init(int bsp_apic_id)
/* Assert INIT IPI:
*
- * This is EDGE triggered to match the deassert
+ * This is EDGE triggered to match the deassert, and is targeted at one
+ * AP by its APIC ID rather than broadcast.
*/
- apic_send_ipi(ALL_EXCLUDING_SELF, INIT, PHYSICAL, ASSERT, EDGE, 0,
bsp_apic_id);
+ apic_send_ipi(NO_SHORTHAND, INIT, PHYSICAL, ASSERT, EDGE, 0, apic_id);
/* Wait for delivery */
wait_for_ipi();
@@ -108,7 +109,7 @@ smp_send_ipi_init(int bsp_apic_id)
* that does not support STARTUP IPIs at all, and instead jump
* via a warm reset vector.
*/
- apic_send_ipi(ALL_EXCLUDING_SELF, INIT, PHYSICAL, DE_ASSERT, EDGE, 0,
bsp_apic_id);
+ apic_send_ipi(NO_SHORTHAND, INIT, PHYSICAL, DE_ASSERT, EDGE, 0, apic_id);
/* Wait for delivery */
wait_for_ipi();
@@ -121,7 +122,7 @@ smp_send_ipi_init(int bsp_apic_id)
}
static int
-smp_send_ipi_startup_twice(int bsp_apic_id, int vector)
+smp_send_ipi_startup_twice(int apic_id, int vector)
{
int i, accept_err, send_err;
volatile int err;
@@ -137,7 +138,7 @@ smp_send_ipi_startup_twice(int bsp_apic_id, int vector)
* Have not seen any documentation for trigger mode for this IPI
* but it seems to work with EDGE. (AMD BKDG FAM16h document
specifies dont care)
*/
- apic_send_ipi(ALL_EXCLUDING_SELF, STARTUP, PHYSICAL, DE_ASSERT, EDGE,
vector, bsp_apic_id);
+ apic_send_ipi(NO_SHORTHAND, STARTUP, PHYSICAL, ASSERT, EDGE, vector,
apic_id);
pit_udelay(10);
@@ -166,6 +167,8 @@ smp_send_ipi_startup_twice(int bsp_apic_id, int vector)
int smp_startup_cpus(unsigned bsp_apic_id, phys_addr_t start_eip)
{
int err;
+ uint8_t ncpus;
+ int cpu;
#if 0
/* This block goes with a legacy method of INIT that only works with
* old hardware that does not support SIPIs.
@@ -190,12 +193,21 @@ int smp_startup_cpus(unsigned bsp_apic_id, phys_addr_t
start_eip)
printf("Sending IPIs from BSP APIC ID %u...\n", bsp_apic_id);
- smp_send_ipi_init(bsp_apic_id);
- err = smp_send_ipi_startup_twice(bsp_apic_id, start_eip >>
STARTUP_VECTOR_SHIFT);
- if (err) {
- printf("FATAL: APs failed to start\n");
- while(1) {
- cpu_pause();
+ /* Target each AP from the MADT individually. The previous broadcast
+ * "all excluding self" shorthand is rejected by some hypervisors
+ * (bhyve) and is not the documented per-AP INIT/SIPI sequence. */
+ ncpus = apic_get_numcpus();
+ for (cpu = 0; cpu < ncpus; cpu++) {
+ int apic_id = apic_get_cpu_apic_id(cpu);
+ if (apic_id < 0 || apic_id == (int)bsp_apic_id)
+ continue;
+ smp_send_ipi_init(apic_id);
+ err = smp_send_ipi_startup_twice(apic_id, start_eip >>
STARTUP_VECTOR_SHIFT);
+ if (err) {
+ printf("FATAL: AP %d failed to start\n", cpu);
+ while(1) {
+ cpu_pause();
+ }
}
}
printf("done\n");
--
2.47.3