diff options
Diffstat (limited to 'src/arch/x86/interface')
-rw-r--r-- | src/arch/x86/interface/pcbios/bios_mp.c | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/src/arch/x86/interface/pcbios/bios_mp.c b/src/arch/x86/interface/pcbios/bios_mp.c new file mode 100644 index 00000000..9e1179cc --- /dev/null +++ b/src/arch/x86/interface/pcbios/bios_mp.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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; either version 2 of the + * License, or any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * BIOS multiprocessor API implementation + * + */ + +#include <registers.h> +#include <ipxe/uaccess.h> +#include <ipxe/timer.h> +#include <ipxe/msr.h> +#include <ipxe/mp.h> + +/** Local APIC base address MSR */ +#define MSR_APIC_BASE 0x0000001b + +/** Local APIC is in x2APIC mode */ +#define MSR_APIC_BASE_X2APIC 0x400 + +/** Local APIC base address mask */ +#define MSR_APIC_BASE_MASK ( ~0xfffULL ) + +/** Interrupt command register */ +#define APIC_ICR 0x0300 + +/** Interrupt command register (x2APIC) */ +#define MSR_X2APIC_ICR 0x830 + +/** Interrupt command register: send to all excluding self */ +#define APIC_ICR_ALL_NOT_SELF 0x000c0000 + +/** Interrupt command register: level mode */ +#define APIC_ICR_LEVEL 0x00008000 + +/** Interrupt command register: level asserted */ +#define APIC_ICR_LEVEL_ASSERT 0x00004000 + +/** Interrupt command register: INIT */ +#define APIC_ICR_INIT 0x00000500 + +/** Interrupt command register: SIPI */ +#define APIC_ICR_SIPI( vector ) ( 0x00000600 | (vector) ) + +/** Time to wait for an IPI to complete */ +#define IPI_WAIT_MS 10 + +/** + * Startup IPI vector + * + * The real-mode startup IPI code must be copied to a page boundary in + * base memory. We fairly arbitrarily choose to place this at 0x8000. + */ +#define SIPI_VECTOR 0x08 + +/** Protected-mode startup IPI handler */ +extern void __asmcall mp_jump ( mp_addr_t func, mp_addr_t opaque ); + +/** + * Execute a multiprocessor function on the boot processor + * + * @v func Multiprocessor function + * @v opaque Opaque data pointer + */ +static void bios_mp_exec_boot ( mp_func_t func, void *opaque ) { + + /* Call multiprocessor function with physical addressing */ + __asm__ __volatile__ ( PHYS_CODE ( "pushl %k2\n\t" + "pushl %k1\n\t" + "call *%k0\n\t" + "addl $8, %%esp\n\t" ) + : : "r" ( mp_address ( mp_call ) ), + "r" ( mp_address ( func ) ), + "r" ( mp_address ( opaque ) ) ); +} + +/** + * Send an interprocessor interrupt + * + * @v apic APIC base address + * @v x2apic x2APIC mode enabled + * @v icr Interrupt control register value + */ +static void bios_mp_ipi ( void *apic, int x2apic, uint32_t icr ) { + + /* Write ICR according to APIC/x2APIC mode */ + DBGC ( MSR_APIC_BASE, "BIOSMP sending IPI %#08x\n", icr ); + if ( x2apic ) { + wrmsr ( MSR_X2APIC_ICR, icr ); + } else { + writel ( icr, ( apic + APIC_ICR ) ); + } + + /* Allow plenty of time for delivery to complete */ + mdelay ( IPI_WAIT_MS ); +} + +/** + * Start a multiprocessor function on all application processors + * + * @v func Multiprocessor function + * @v opaque Opaque data pointer + */ +static void bios_mp_start_all ( mp_func_t func, void *opaque ) { + struct i386_regs regs; + uint64_t base; + uint32_t ipi; + void *apic; + int x2apic; + + /* Prepare SIPI handler */ + regs.eax = mp_address ( func ); + regs.edx = mp_address ( opaque ); + setup_sipi ( SIPI_VECTOR, virt_to_phys ( mp_jump ), ®s ); + + /* Get local APIC base address and mode */ + base = rdmsr ( MSR_APIC_BASE ); + x2apic = ( base & MSR_APIC_BASE_X2APIC ); + DBGC ( MSR_APIC_BASE, "BIOSMP local %sAPIC base %#llx\n", + ( x2apic ? "x2" : "" ), ( ( unsigned long long ) base ) ); + + /* Map local APIC */ + apic = ioremap ( ( base & MSR_APIC_BASE_MASK ), PAGE_SIZE ); + if ( ! apic ) + goto err_ioremap; + + /* Assert INIT IPI */ + ipi = ( APIC_ICR_ALL_NOT_SELF | APIC_ICR_LEVEL | + APIC_ICR_LEVEL_ASSERT | APIC_ICR_INIT ); + bios_mp_ipi ( apic, x2apic, ipi ); + + /* Clear INIT IPI */ + ipi &= ~APIC_ICR_LEVEL_ASSERT; + bios_mp_ipi ( apic, x2apic, ipi ); + + /* Send SIPI */ + ipi = ( APIC_ICR_ALL_NOT_SELF | APIC_ICR_SIPI ( SIPI_VECTOR ) ); + bios_mp_ipi ( apic, x2apic, ipi ); + + iounmap ( apic ); + err_ioremap: + /* No way to handle errors: caller must check that + * multiprocessor function executed as expected. + */ + return; +} + +PROVIDE_MPAPI_INLINE ( pcbios, mp_address ); +PROVIDE_MPAPI ( pcbios, mp_exec_boot, bios_mp_exec_boot ); +PROVIDE_MPAPI ( pcbios, mp_start_all, bios_mp_start_all ); |