#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_broadcast
#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;
lck_grp_attr_setstat(ucode_slock_grp_attr);
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;
}
void
ucode_update_wake()
{
if (global_update) {
kprintf("ucode: Re-applying update after wake (CPU #%d)\n", cpu_number());
update_microcode();
#if DEBUG
} else {
kprintf("ucode: No update to apply (CPU #%d)\n", cpu_number());
#endif
}
}
static void
cpu_update(__unused void *arg)
{
lck_spin_lock(ucode_slock);
update_microcode();
lck_spin_unlock(ucode_slock);
}
static void
xcpu_update(void)
{
if (register_locks() != KERN_SUCCESS)
return;
mp_broadcast(cpu_update, NULL);
cpuid_set_info();
}
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;
}