Skip to content

Commit

Permalink
x86/acpi, x86/boot: Add multiprocessor wake-up support
Browse files Browse the repository at this point in the history
As per ACPI specification r6.4, sec 5.2.12.19, a new sub
structure – multiprocessor wake-up structure - is added to the
ACPI Multiple APIC Description Table (MADT) to describe the
information of the mailbox. If a platform firmware produces the
multiprocessor wake-up structure, then OS may use this new
mailbox-based mechanism to wake up the APs.

Add ACPI MADT wake table parsing support for x86 platform and if
MADT wake table is present, update apic->wakeup_secondary_cpu with
new API which uses MADT wake mailbox to wake-up CPU.

Co-developed-by: Sean Christopherson <sean.j.christopherson@intel.com>
Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
Reviewed-by: Andi Kleen <ak@linux.intel.com>
Reviewed-by: Rafael J. Wysocki<rafael.j.wysocki@intel.com>
Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
  • Loading branch information
Kuppuswamy Sathyanarayanan committed Oct 1, 2021
1 parent 1802bb0 commit e9bff1e
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 0 deletions.
3 changes: 3 additions & 0 deletions arch/x86/include/asm/apic.h
Expand Up @@ -488,6 +488,9 @@ static inline unsigned int read_apic_id(void)
return apic->get_apic_id(reg);
}

typedef int (*wakeup_cpu_handler)(int apicid, unsigned long start_eip);
extern void acpi_wake_cpu_handler_update(wakeup_cpu_handler handler);

extern int default_apic_id_valid(u32 apicid);
extern int default_acpi_madt_oem_check(char *, char *);
extern void default_setup_apic_routing(void);
Expand Down
96 changes: 96 additions & 0 deletions arch/x86/kernel/acpi/boot.c
Expand Up @@ -64,6 +64,10 @@ int acpi_fix_pin2_polarity __initdata;
static u64 acpi_lapic_addr __initdata = APIC_DEFAULT_PHYS_BASE;
#endif

static struct acpi_madt_multiproc_wakeup_mailbox *acpi_mp_wake_mailbox;
static u64 acpi_mp_wake_mailbox_paddr;
static physid_mask_t apic_id_wakemap = PHYSID_MASK_NONE;

#ifdef CONFIG_X86_IO_APIC
/*
* Locks related to IOAPIC hotplug
Expand Down Expand Up @@ -327,6 +331,68 @@ acpi_parse_lapic_nmi(union acpi_subtable_headers * header, const unsigned long e
return 0;
}

static int acpi_wakeup_cpu(int apicid, unsigned long start_ip)
{
u8 timeout = 0xFF;

/* Remap mailbox memory only for the first call to acpi_wakeup_cpu() */
if (physids_empty(apic_id_wakemap)) {
acpi_mp_wake_mailbox = memremap(acpi_mp_wake_mailbox_paddr,
sizeof(*acpi_mp_wake_mailbox),
MEMREMAP_WB);
}

/*
* According to the ACPI specification r6.4, sec 5.2.12.19, the
* mailbox-based wakeup mechanism cannot be used more than once
* for the same CPU, so skip sending wake commands to already
* awake CPU.
*/
if (physid_isset(apicid, apic_id_wakemap)) {
pr_err("CPU already awake (APIC ID %x), skipping wakeup\n",
apicid);
return -EINVAL;
}

/*
* Mailbox memory is shared between firmware and OS. Firmware will
* listen on mailbox command address, and once it receives the wakeup
* command, CPU associated with the given apicid will be booted. So,
* the value of apic_id and wakeup_vector has to be set before updating
* the wakeup command. So use smp_store_release to let the compiler know
* about it and preserve the order of writes.
*/
smp_store_release(&acpi_mp_wake_mailbox->apic_id, apicid);
smp_store_release(&acpi_mp_wake_mailbox->wakeup_vector, start_ip);
smp_store_release(&acpi_mp_wake_mailbox->command,
ACPI_MP_WAKE_COMMAND_WAKEUP);

/*
* After writing wakeup command, wait for maximum timeout of 0xFF
* for firmware to reset the command address back zero to indicate
* the successful reception of command.
* NOTE: 255 as timeout value is decided based on our experiments.
*
* XXX: Change the timeout once ACPI specification comes up with
* standard maximum timeout value.
*/
while (READ_ONCE(acpi_mp_wake_mailbox->command) && timeout--)
cpu_relax();

if (timeout) {
/*
* If the CPU wakeup process is successful, store the
* status in apic_id_wakemap to prevent re-wakeup
* requests.
*/
physid_set(apicid, apic_id_wakemap);
return 0;
}

/* If timed out (timeout == 0), return error */
return -EIO;
}

#endif /*CONFIG_X86_LOCAL_APIC */

#ifdef CONFIG_X86_IO_APIC
Expand Down Expand Up @@ -1074,6 +1140,30 @@ static int __init acpi_parse_madt_lapic_entries(void)
}
return 0;
}

static int __init acpi_parse_mp_wake(union acpi_subtable_headers *header,
const unsigned long end)
{
struct acpi_madt_multiproc_wakeup *mp_wake;

if (acpi_mp_wake_mailbox)
return -EINVAL;

if (!IS_ENABLED(CONFIG_SMP))
return -ENODEV;

mp_wake = (struct acpi_madt_multiproc_wakeup *)header;
if (BAD_MADT_ENTRY(mp_wake, end))
return -EINVAL;

acpi_table_print_madt_entry(&header->common);

acpi_mp_wake_mailbox_paddr = mp_wake->base_address;

acpi_wake_cpu_handler_update(acpi_wakeup_cpu);

return 0;
}
#endif /* CONFIG_X86_LOCAL_APIC */

#ifdef CONFIG_X86_IO_APIC
Expand Down Expand Up @@ -1269,6 +1359,12 @@ static void __init acpi_process_madt(void)

smp_found_config = 1;
}

/*
* Parse MADT MP Wake entry.
*/
acpi_table_parse_madt(ACPI_MADT_TYPE_MULTIPROC_WAKEUP,
acpi_parse_mp_wake, 1);
}
if (error == -EINVAL) {
/*
Expand Down
8 changes: 8 additions & 0 deletions arch/x86/kernel/apic/apic.c
Expand Up @@ -2551,6 +2551,14 @@ u32 x86_msi_msg_get_destid(struct msi_msg *msg, bool extid)
}
EXPORT_SYMBOL_GPL(x86_msi_msg_get_destid);

void __init acpi_wake_cpu_handler_update(wakeup_cpu_handler handler)
{
struct apic **drv;

for (drv = __apicdrivers; drv < __apicdrivers_end; drv++)
(*drv)->wakeup_secondary_cpu = handler;
}

/*
* Override the generic EOI implementation with an optimized version.
* Only called during early boot when only one CPU is active and with
Expand Down

0 comments on commit e9bff1e

Please sign in to comment.