| /* |
| * 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 ); |