[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH v2 4/4 gnumach] smp: Fix INIT/STARTUP IPI sequence
From: |
Damien Zammit |
Subject: |
[PATCH v2 4/4 gnumach] smp: Fix INIT/STARTUP IPI sequence |
Date: |
Tue, 06 Feb 2024 03:06:28 +0000 |
To be fixed: Find a way to allocate memory below 1MiB.
Otherwise, this hardcodes 0x3000 as the starting eip.
TESTED: works in qemu
TESTED: works hardware with AMD cpu
---
i386/i386/mp_desc.c | 15 +++--
i386/i386/smp.c | 126 +++++++++++++++++++++++++++++-----------
i386/i386/smp.h | 4 +-
i386/i386at/cram.h | 5 ++
i386/i386at/model_dep.c | 2 +-
5 files changed, 111 insertions(+), 41 deletions(-)
diff --git a/i386/i386/mp_desc.c b/i386/i386/mp_desc.c
index 15568ae6..ea6be435 100644
--- a/i386/i386/mp_desc.c
+++ b/i386/i386/mp_desc.c
@@ -292,17 +292,22 @@ cpu_ap_main()
kern_return_t
cpu_start(int cpu)
{
+ int err;
+
assert(machine_slot[cpu].running != TRUE);
uint16_t apic_id = apic_get_cpu_apic_id(cpu);
- printf("Trying to enable: %d\n", apic_id);
-
- smp_startup_cpu(apic_id, apboot_addr);
+ printf("Trying to enable: %d at 0x%x\n", apic_id, apboot_addr);
- printf("Started cpu %d (lapic id %04x)\n", cpu, apic_id);
+ err = smp_startup_cpu(apic_id, apboot_addr);
- return KERN_SUCCESS;
+ if (!err) {
+ printf("Started cpu %d (lapic id %04x)\n", cpu, apic_id);
+ return KERN_SUCCESS;
+ }
+ printf("FATAL: Cannot init AP %d\n", cpu);
+ for (;;);
}
void
diff --git a/i386/i386/smp.c b/i386/i386/smp.c
index 87f59913..7948fe40 100644
--- a/i386/i386/smp.c
+++ b/i386/i386/smp.c
@@ -18,10 +18,14 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+#include <string.h>
#include <i386/apic.h>
#include <i386/smp.h>
#include <i386/cpu.h>
+#include <i386/pio.h>
+#include <i386/vm_param.h>
#include <i386at/idt.h>
+#include <i386at/cram.h>
#include <i386at/acpi_parse_apic.h>
#include <kern/printf.h>
#include <mach/machine.h>
@@ -75,59 +79,113 @@ void smp_pmap_update(unsigned apic_id)
smp_send_ipi(apic_id, CALL_PMAP_UPDATE);
}
-/* See Intel IA32/64 Software Developer's Manual 3A Section 8.4.4.1 */
-void smp_startup_cpu(unsigned apic_id, unsigned vector)
+static void
+wait_for_ipi(void)
{
- /* Clear APIC errors */
- lapic->error_status.r = 0;
+ /* This could have a timeout, but if the IPI
+ * is never delivered, its a disaster anyway */
+ while (lapic->icr_low.delivery_status == SEND_PENDING) {
+ cpu_pause();
+ }
+}
+
+static int
+smp_send_ipi_init(int apic_id)
+{
+ int err;
- printf("Sending IPIs to APIC ID %u...", apic_id);
+ lapic->error_status.r = 0;
- /* Assert INIT IPI */
- apic_send_ipi(NO_SHORTHAND, INIT, PHYSICAL, ASSERT, LEVEL, 0, apic_id);
+ /* Assert INIT IPI:
+ *
+ * This is EDGE triggered to match the deassert
+ */
+ apic_send_ipi(NO_SHORTHAND, INIT, PHYSICAL, ASSERT, EDGE, 0, apic_id);
/* Wait for delivery */
- do {
- cpu_pause();
- } while(lapic->icr_low.delivery_status == SEND_PENDING);
+ wait_for_ipi();
+ hpet_mdelay(10);
- /* Deassert INIT IPI */
- apic_send_ipi(NO_SHORTHAND, INIT, PHYSICAL, DE_ASSERT, LEVEL, 0, apic_id);
+ /* Deassert INIT IPI:
+ *
+ * NB: This must be an EDGE triggered deassert signal.
+ * A LEVEL triggered deassert is only supported on very old hardware
+ * that does not support STARTUP IPIs at all, and instead jump
+ * via a warm reset vector.
+ */
+ apic_send_ipi(NO_SHORTHAND, INIT, PHYSICAL, DE_ASSERT, EDGE, 0, apic_id);
/* Wait for delivery */
- do {
- cpu_pause();
- } while(lapic->icr_low.delivery_status == SEND_PENDING);
+ wait_for_ipi();
- /* Wait 10 msec */
- hpet_mdelay(10);
+ err = lapic->error_status.r;
+ if (err) {
+ printf("ESR error upon INIT 0x%x\n", err);
+ }
+ return 0;
+}
- /* Clear APIC errors */
- lapic->error_status.r = 0;
+static int
+smp_send_ipi_startup(int apic_id, int vector)
+{
+ int err;
- /* First StartUp IPI */
- apic_send_ipi(NO_SHORTHAND, STARTUP, PHYSICAL, ASSERT, LEVEL, vector >>
12, apic_id);
+ lapic->error_status.r = 0;
- /* Wait 200 usec */
- hpet_udelay(200);
+ /* StartUp IPI:
+ *
+ * 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(NO_SHORTHAND, STARTUP, PHYSICAL, ASSERT, EDGE, vector,
apic_id);
/* Wait for delivery */
- do {
- cpu_pause();
- } while(lapic->icr_low.delivery_status == SEND_PENDING);
+ wait_for_ipi();
- /* Second StartUp IPI */
- apic_send_ipi(NO_SHORTHAND, STARTUP, PHYSICAL, ASSERT, LEVEL, vector >>
12, apic_id);
+ err = lapic->error_status.r;
+ if (err) {
+ printf("ESR error upon STARTUP 0x%x\n", err);
+ }
+ return 0;
+}
- /* Wait 200 usec */
+/* See Intel IA32/64 Software Developer's Manual 3A Section 8.4.4.1 */
+int smp_startup_cpu(unsigned apic_id, phys_addr_t start_eip)
+{
+ int err;
+
+#if 0
+ /* This block goes with a legacy method of INIT that only works with
+ * old hardware that does not support SIPIs.
+ * Must use INIT DEASSERT LEVEL triggered IPI to use this block.
+ * (At least one AMD FCH does not support this IPI mode,
+ * See AMD BKDG FAM16h document # 48751 page 461).
+ */
+
+ /* Tell CMOS to warm reset through through 40:67 */
+ outb(CMOS_ADDR, CMOS_SHUTDOWN);
+ outb(CMOS_DATA, CM_JMP_467);
+
+ /* Set warm reset vector to point to AP startup code */
+ uint16_t dword[2];
+ dword[0] = 0;
+ dword[1] = start_eip >> 4;
+ memcpy((uint8_t *)phystokv(0x467), dword, 4);
+#endif
+
+ /* Local cache flush */
+ asm("wbinvd":::"memory");
+
+ printf("Sending IPIs to APIC ID %u...\n", apic_id);
+ err = smp_send_ipi_init(apic_id);
+ hpet_mdelay(10);
+ err = smp_send_ipi_startup(apic_id, start_eip / PAGE_SIZE);
+ hpet_udelay(200);
+ err = smp_send_ipi_startup(apic_id, start_eip / PAGE_SIZE);
hpet_udelay(200);
-
- /* Wait for delivery */
- do {
- cpu_pause();
- } while(lapic->icr_low.delivery_status == SEND_PENDING);
printf("done\n");
+ return 0;
}
/*
diff --git a/i386/i386/smp.h b/i386/i386/smp.h
index 784936ea..620f0f2c 100644
--- a/i386/i386/smp.h
+++ b/i386/i386/smp.h
@@ -21,10 +21,12 @@
#ifndef _SMP_H_
#define _SMP_H_
+#include <mach/machine/vm_types.h>
+
int smp_init(void);
void smp_remote_ast(unsigned apic_id);
void smp_pmap_update(unsigned apic_id);
-void smp_startup_cpu(unsigned apic_id, unsigned vector);
+int smp_startup_cpu(unsigned apic_id, phys_addr_t start_eip);
#define cpu_pause() asm volatile ("pause" : : : "memory")
diff --git a/i386/i386at/cram.h b/i386/i386at/cram.h
index 8a3a6ec9..ac40cf13 100644
--- a/i386/i386at/cram.h
+++ b/i386/i386at/cram.h
@@ -71,6 +71,11 @@ WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
/* Addresses, related masks, and potential results */
+#define CMOS_SHUTDOWN 0xf
+#define CM_NORM_RST 0x0
+#define CM_LOAD_SYS 0x4
+#define CM_JMP_467 0xa
+
#define CMOS_EB 0x14 /* read Equipment Byte */
#define CM_SCRMSK 0x30 /* mask for EB query to get screen */
#define CM_EGA_VGA 0x00 /* "not CGA or MONO" */
diff --git a/i386/i386at/model_dep.c b/i386/i386at/model_dep.c
index b5f56c7d..05892791 100644
--- a/i386/i386at/model_dep.c
+++ b/i386/i386at/model_dep.c
@@ -220,7 +220,7 @@ void machine_init(void)
* Grab an early page for AP boot code
*/
/* FIXME: this may not allocate from below 1MB, if within first 16MB */
- apboot_addr = vm_page_to_pa(vm_page_grab_contig(PAGE_SIZE,
VM_PAGE_SEL_DMA));
+ apboot_addr = 0x3000; //vm_page_to_pa(vm_page_grab_contig(PAGE_SIZE,
VM_PAGE_SEL_DMA));
assert (apboot_addr < 0x100000);
/*
--
2.43.0