/* * Copyright (C) 2024 Michael Brown . * * 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 #include #include #include #include /** 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 );