[mp] Define an API for multiprocessor functions

Define an API for executing very limited functions on application
processors in a multiprocessor system, along with an x86-only
implementation.

The normal iPXE runtime environment is effectively non-existent on
application processors.  There is no ability to make firmware calls
(e.g. to write to a console), and there may be no stack space
available.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
pull/1174/head
Michael Brown 2024-03-13 15:08:10 +00:00
parent df2f23e333
commit 1ab4d3079d
11 changed files with 530 additions and 0 deletions

View File

@ -0,0 +1,12 @@
#ifndef _BITS_MP_H
#define _BITS_MP_H
/** @file
*
* ARM-specific multiprocessor API implementation
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#endif /* _BITS_MP_H */

View File

@ -0,0 +1,12 @@
#ifndef _BITS_MP_H
#define _BITS_MP_H
/** @file
*
* LoongArch64-specific multiprocessor API implementation
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#endif /* _BITS_MP_H */

View File

@ -0,0 +1,197 @@
/*
* 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
*
* Multiprocessor functions
*
*/
.section ".note.GNU-stack", "", @progbits
.text
/* Selectively assemble code for 32-bit/64-bit builds */
#if defined ( __x86_64__ ) && ! defined ( PLATFORM_pcbios )
#define codemp code64
#define DI rdi
#define SP rsp
#define if32 if 0
#define if64 if 1
#else
#define codemp code32
#define DI edi
#define SP esp
#define if32 if 1
#define if64 if 0
#endif
/* Standard features CPUID leaf */
#define CPUID_FEATURES 0x00000001
/* x2APIC is supported */
#define CPUID_FEATURES_ECX_X2APIC 0x00200000
/* Extended topology enumeration CPUID leaf */
#define CPUID_XT_ENUM 0x0000000b
/*
* Call multiprocessor function from C code
*
* Parameters:
* 4(%esp)/%rdi Multiprocessor function
* 8(%esp)/%rsi Opaque data pointer
*/
.section ".text.mp_call", "ax", @progbits
.codemp
.globl mp_call
mp_call:
.if64 /* Preserve registers, load incoming parameters into registers */
pushq %rax
pushq %rcx
pushq %rdx
pushq %rbx
pushq %rsp
pushq %rbp
pushq %rsi
pushq %rdi
pushq %r8
pushq %r9
pushq %r10
pushq %r11
pushq %r12
pushq %r13
pushq %r14
pushq %r15
.else
pushal
movl 36(%esp), %eax
movl 40(%esp), %edx
.endif
/* Call multiprocessor function */
call mp_jump
.if64 /* Restore registers and return */
popq %r15
popq %r14
popq %r13
popq %r12
popq %r11
popq %r10
popq %r9
popq %r8
popq %rdi
popq %rsi
popq %rbp
leaq 8(%rsp), %rsp /* discard */
popq %rbx
popq %rdx
popq %rcx
popq %rax
.else
popal
.endif
ret
.size mp_call, . - mp_call
/*
* Jump to multiprocessor function
*
* Parameters:
* %eax/%rdi Multiprocessor function
* %edx/%rsi Opaque data pointer
* %esp/%rsp Stack, or NULL to halt AP upon completion
*
* Obtain the CPU identifier (i.e. the APIC ID) and perform a tail
* call into the specified multiprocessor function.
*
* This code may run with no stack on an application processor.
*/
.section ".text.mp_jump", "ax", @progbits
.codemp
.globl mp_jump
mp_jump:
.if32 /* Move function parameters to available registers */
movl %eax, %edi
movl %edx, %esi
.endif
/* Get 8-bit APIC ID and x2APIC feature bit */
movl $CPUID_FEATURES, %eax
cpuid
shrl $24, %ebx
movl %ebx, %edx
/* Get 32-bit x2APIC ID if applicable */
testl $CPUID_FEATURES_ECX_X2APIC, %ecx
jz 1f
movl $CPUID_XT_ENUM, %eax
xorl %ecx, %ecx
cpuid
1:
.if64 /* Tail call to function */
movq %rdi, %rax
movq %rsi, %rdi
movl %edx, %esi
jmp *%rax
.else
movl %esi, %eax
jmp *%edi
.endif
.size mp_jump, . - mp_jump
/*
* Update maximum CPU identifier
*
* Parameters:
* %eax/%rdi Pointer to shared maximum APIC ID
* %edx/%rsi CPU identifier (APIC ID)
* %esp/%rsp Stack, or NULL to halt AP upon completion
*
* This code may run with no stack on an application processor.
*/
.section ".text.mp_update_max_cpuid", "ax", @progbits
.codemp
.globl mp_update_max_cpuid
mp_update_max_cpuid:
.if32 /* Move function parameters to available registers */
movl %eax, %edi
movl %edx, %esi
.endif
/* Update maximum APIC ID (atomically) */
movl (%DI), %eax
1: cmpl %esi, %eax
jae 2f
lock cmpxchgl %esi, (%DI)
jnz 1b
2:
/* Return to caller (if stack exists), or halt application processor */
test %SP, %SP
jz 3f
ret
3: cli
hlt
jmp 3b
.size mp_update_max_cpuid, . - mp_update_max_cpuid

View File

@ -0,0 +1,12 @@
#ifndef _BITS_MP_H
#define _BITS_MP_H
/** @file
*
* x86-specific multiprocessor API implementation
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#endif /* _BITS_MP_H */

View File

@ -25,6 +25,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define REBOOT_EFI
#define ACPI_EFI
#define FDT_EFI
#define MPAPI_NULL
#define NET_PROTO_IPV6 /* IPv6 protocol */
#define NET_PROTO_LLDP /* Link Layer Discovery protocol */

View File

@ -22,6 +22,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#define PCIAPI_LINUX
#define DMAAPI_FLAT
#define ACPI_LINUX
#define MPAPI_NULL
#define DRIVERS_LINUX

View File

@ -24,6 +24,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define TIME_RTC
#define REBOOT_PCBIOS
#define ACPI_RSDP
#define MPAPI_NULL
#ifdef __x86_64__
#define IOMAP_PAGES

67
src/core/mp.c 100644
View File

@ -0,0 +1,67 @@
/*
* 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
*
* Multiprocessor functions
*
*/
#include <ipxe/timer.h>
#include <ipxe/mp.h>
/** Time to wait for application processors */
#define MP_MAX_CPUID_WAIT_MS 10
/**
* Get boot CPU identifier
*
* @ret id Boot CPU identifier
*/
unsigned int mp_boot_cpuid ( void ) {
unsigned int max = 0;
/* Update maximum to accommodate boot processor */
mp_exec_boot ( mp_update_max_cpuid, &max );
DBGC ( &mp_call, "MP boot processor ID is %#x\n", max );
return max;
}
/**
* Get maximum CPU identifier
*
* @ret max Maximum CPU identifier
*/
unsigned int mp_max_cpuid ( void ) {
unsigned int max = mp_boot_cpuid();
/* Update maximum to accommodate application processors */
mp_start_all ( mp_update_max_cpuid, &max );
mdelay ( MP_MAX_CPUID_WAIT_MS );
DBGC ( &mp_call, "MP observed maximum CPU ID is %#x\n", max );
return max;
}

37
src/core/null_mp.c 100644
View File

@ -0,0 +1,37 @@
/*
* 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
*
* Null multiprocessor API
*
*/
#include <ipxe/mp.h>
PROVIDE_MPAPI_INLINE ( null, mp_address );
PROVIDE_MPAPI_INLINE ( null, mp_exec_boot );
PROVIDE_MPAPI_INLINE ( null, mp_start_all );

View File

@ -0,0 +1,154 @@
#ifndef _IPXE_MP_H
#define _IPXE_MP_H
/** @file
*
* Multiprocessor functions
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
#include <ipxe/api.h>
#include <config/defaults.h>
/**
* An address within the address space for a multiprocessor function
*
* Application processors may be started in a different address space
* from the normal iPXE runtime environment. For example: under
* legacy BIOS the application processors will use flat 32-bit
* physical addressing (with no paging or virtual address offset).
*/
typedef unsigned long mp_addr_t;
/** A multiprocessor function
*
* @v opaque Opaque data pointer
* @v cpuid CPU identifier
*
* iPXE does not set up a normal multiprocessor environment. In
* particular, there is no support for dispatching code to individual
* processors and there is no per-CPU stack allocation.
*
* Multiprocessor code must be prepared to run wth no stack space (and
* with a zero stack pointer). Functions may use the CPU identifier
* to construct a pointer to per-CPU result storage.
*
* Multiprocessor functions are permitted to overwrite all registers
* apart from the stack pointer. On exit, the function should check
* the stack pointer value: if zero then the function should halt the
* CPU, if non-zero then the function should return in the normal way.
*
* Multiprocessor functions do not have access to any capabilities
* typically provided by the firmware: they cannot, for example, write
* any console output.
*
* All parameters are passed in registers, since there may be no stack
* available. These functions cannot be called directly from C code.
*/
typedef void ( mp_func_t ) ( mp_addr_t opaque, unsigned int cpuid );
/**
* Call a multiprocessor function from C code on the current CPU
*
* @v func Multiprocessor function
* @v opaque Opaque data pointer
*
* This function must be provided for each CPU architecture to bridge
* the normal C ABI to the iPXE multiprocessor function ABI. It must
* therefore preserve any necessary registers, determine the CPU
* identifier, call the multiprocessor function (which may destroy any
* registers other than the stack pointer), restore registers, and
* return to the C caller.
*
* This function must be called from within the multiprocessor address
* space (e.g. with flat 32-bit physical addressing for BIOS). It can
* be called directly from C code if the multiprocessor address space
* is identical to the address space used for C code (e.g. under EFI,
* where everything uses flat physical addresses).
*/
extern void __asmcall mp_call ( mp_addr_t func, mp_addr_t opaque );
/**
* Calculate static inline multiprocessor API function name
*
* @v _prefix Subsystem prefix
* @v _api_func API function
* @ret _subsys_func Subsystem API function
*/
#define MPAPI_INLINE( _subsys, _api_func ) \
SINGLE_API_INLINE ( MPAPI_PREFIX_ ## _subsys, _api_func )
/**
* Provide a multiprocessor API implementation
*
* @v _prefix Subsystem prefix
* @v _api_func API function
* @v _func Implementing function
*/
#define PROVIDE_MPAPI( _subsys, _api_func, _func ) \
PROVIDE_SINGLE_API ( MPAPI_PREFIX_ ## _subsys, _api_func, _func )
/**
* Provide a static inline multiprocessor API implementation
*
* @v _prefix Subsystem prefix
* @v _api_func API function
*/
#define PROVIDE_MPAPI_INLINE( _subsys, _api_func ) \
PROVIDE_SINGLE_API_INLINE ( MPAPI_PREFIX_ ## _subsys, _api_func )
/* Include all architecture-independent multiprocessor API headers */
#include <ipxe/null_mp.h>
/* Include all architecture-dependent multiprocessor API headers */
#include <bits/mp.h>
/**
* Calculate address as seen by a multiprocessor function
*
* @v address Address in normal iPXE address space
* @ret address Address in application processor address space
*/
mp_addr_t mp_address ( void *address );
/**
* Execute a multiprocessor function on the boot processor
*
* @v func Multiprocessor function
* @v opaque Opaque data pointer
*
* This is a blocking operation: the call will return only when the
* multiprocessor function exits.
*/
void mp_exec_boot ( mp_func_t func, void *opaque );
/**
* Start a multiprocessor function on all application processors
*
* @v func Multiprocessor function
* @v opaque Opaque data pointer
*
* This is a non-blocking operation: it is the caller's responsibility
* to provide a way to determine when the multiprocessor function has
* finished executing and halted its CPU.
*/
void mp_start_all ( mp_func_t func, void *opaque );
/**
* Update maximum observed CPU identifier
*
* @v opaque Opaque data pointer
* @v cpuid CPU identifier
*
* This may be invoked on each processor to update a shared maximum
* CPU identifier value.
*/
extern mp_func_t mp_update_max_cpuid;
extern unsigned int mp_boot_cpuid ( void );
extern unsigned int mp_max_cpuid ( void );
#endif /* _IPXE_MP_H */

View File

@ -0,0 +1,36 @@
#ifndef _IPXE_NULL_MP_H
#define _IPXE_NULL_MP_H
/** @file
*
* Null multiprocessor API implementation
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#ifdef MPAPI_NULL
#define MPAPI_PREFIX_null
#else
#define MPAPI_PREFIX_null __null_
#endif
static inline __attribute__ (( always_inline )) mp_addr_t
MPAPI_INLINE ( null, mp_address ) ( void *address ) {
return ( ( mp_addr_t ) address );
}
static inline __attribute__ (( always_inline )) void
MPAPI_INLINE ( null, mp_exec_boot ) ( mp_func_t func __unused,
void *opaque __unused ) {
/* Do nothing */
}
static inline __attribute__ (( always_inline )) void
MPAPI_INLINE ( null, mp_start_all ) ( mp_func_t func __unused,
void *opaque __unused ) {
/* Do nothing */
}
#endif /* _IPXE_NULL_MP_H */