#include <kern/locks.h>
#include <i386/ucode.h>
#include <sys/errno.h>
#include <i386/proc_reg.h>
#include <i386/cpuid.h>
#include <vm/vm_kern.h>
#include <i386/mp.h> // mp_cpus_call
#include <i386/commpage/commpage.h>
#include <i386/fpu.h>
#include <machine/cpu_number.h> // cpu_number
#include <pexpert/pexpert.h> // boot-args
#define IA32_BIOS_UPDT_TRIG (0x79)
struct intel_ucupdate *global_update = NULL;
static void
update_microcode(void)
{
wrmsr64(IA32_BIOS_UPDT_TRIG, (uint64_t)(uintptr_t)&global_update->data);
}
static lck_grp_attr_t *ucode_slock_grp_attr = NULL;
static lck_grp_t *ucode_slock_grp = NULL;
static lck_attr_t *ucode_slock_attr = NULL;
static lck_spin_t *ucode_slock = NULL;
static kern_return_t
register_locks(void)
{
if (ucode_slock_grp_attr && ucode_slock_grp && ucode_slock_attr && ucode_slock) {
return KERN_SUCCESS;
}
if (!(ucode_slock_grp_attr = lck_grp_attr_alloc_init())) {
goto nomem_out;
}
if (!(ucode_slock_grp = lck_grp_alloc_init("uccode_lock", ucode_slock_grp_attr))) {
goto nomem_out;
}
if (!(ucode_slock_attr = lck_attr_alloc_init())) {
goto nomem_out;
}
if (!(ucode_slock = lck_spin_alloc_init(ucode_slock_grp, ucode_slock_attr))) {
goto nomem_out;
}
return KERN_SUCCESS;
nomem_out:
if (ucode_slock) {
lck_spin_free(ucode_slock, ucode_slock_grp);
}
if (ucode_slock_attr) {
lck_attr_free(ucode_slock_attr);
}
if (ucode_slock_grp) {
lck_grp_free(ucode_slock_grp);
}
if (ucode_slock_grp_attr) {
lck_grp_attr_free(ucode_slock_grp_attr);
}
return KERN_NO_SPACE;
}
static int
copyin_update(uint64_t inaddr)
{
struct intel_ucupdate update_header;
struct intel_ucupdate *update;
vm_size_t size;
kern_return_t ret;
int error;
error = copyin((user_addr_t)inaddr, (void *)&update_header, sizeof(update_header));
if (error) {
return error;
}
size = update_header.total_size;
if (size >= 1024 * 1024) {
return ENOMEM;
}
if (size == 0) {
size = 2048;
}
ret = kmem_alloc_kobject(kernel_map, (vm_offset_t *)&update, size, VM_KERN_MEMORY_OSFMK);
if (ret != KERN_SUCCESS) {
return ENOMEM;
}
error = copyin((user_addr_t)inaddr, (void*)update, size);
if (error) {
kmem_free(kernel_map, (vm_offset_t)update, size);
return error;
}
global_update = update;
return 0;
}
static void
cpu_apply_microcode(void)
{
lck_spin_lock(ucode_slock);
update_microcode();
lck_spin_unlock(ucode_slock);
}
static void
cpu_update(__unused void *arg)
{
cpu_apply_microcode();
cpuid_do_was();
}
void
ucode_update_wake()
{
if (global_update) {
kprintf("ucode: Re-applying update after wake (CPU #%d)\n", cpu_number());
cpu_update(NULL);
#if DEBUG
} else {
kprintf("ucode: No update to apply (CPU #%d)\n", cpu_number());
#endif
}
}
static void
ucode_cpuid_set_info(void)
{
uint64_t saved_xcr0, dest_xcr0;
int need_xcr0_restore = 0;
boolean_t intrs_enabled = ml_set_interrupts_enabled(FALSE);
if (fpu_capability == AVX512 || fpu_capability == AVX) {
saved_xcr0 = xgetbv(XCR0);
dest_xcr0 = (fpu_capability == AVX512) ? AVX512_XMASK : AVX_XMASK;
assert((get_cr4() & CR4_OSXSAVE) != 0);
if (saved_xcr0 != dest_xcr0) {
need_xcr0_restore = 1;
xsetbv(dest_xcr0 >> 32, dest_xcr0 & 0xFFFFFFFFUL);
}
}
cpuid_set_info();
if (need_xcr0_restore) {
xsetbv(saved_xcr0 >> 32, saved_xcr0 & 0xFFFFFFFFUL);
}
ml_set_interrupts_enabled(intrs_enabled);
}
static void
xcpu_update(void)
{
cpumask_t dest_cpumask;
if (register_locks() != KERN_SUCCESS) {
return;
}
mp_disable_preemption();
dest_cpumask = CPUMASK_OTHERS;
cpu_apply_microcode();
ucode_cpuid_set_info();
mp_enable_preemption();
mp_cpus_call(dest_cpumask, ASYNC, cpu_update, NULL);
commpage_post_ucode_update();
}
int
ucode_interface(uint64_t addr)
{
int error;
char arg[16];
if (PE_parse_boot_argn("-x", arg, sizeof(arg))) {
printf("ucode: no updates in safe mode\n");
return EPERM;
}
#if !DEBUG
if (global_update) {
return EPERM;
}
#endif
error = copyin_update(addr);
if (error) {
return error;
}
xcpu_update();
return 0;
}