Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | /* * ucode.c * * Microcode updater interface sysctl */ #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) /* microcode update trigger MSR */ struct intel_ucupdate *global_update = NULL; /* Exceute the actual update! */ static void update_microcode(void) { /* SDM Example 9-8 code shows that we load the * address of the UpdateData within the microcode blob, * not the address of the header. */ wrmsr64(IA32_BIOS_UPDT_TRIG, (uint64_t)(uintptr_t)&global_update->data); } /* locks */ 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) { /* already allocated? */ if (ucode_slock_grp_attr && ucode_slock_grp && ucode_slock_attr && ucode_slock) return KERN_SUCCESS; /* allocate lock group attribute and group */ 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; /* Allocate lock attribute */ if (!(ucode_slock_attr = lck_attr_alloc_init())) goto nomem_out; /* Allocate the spin lock */ /* We keep one global spin-lock. We could have one per update * request... but srsly, why would you update microcode like that? */ if (!(ucode_slock = lck_spin_alloc_init(ucode_slock_grp, ucode_slock_attr))) goto nomem_out; return KERN_SUCCESS; nomem_out: /* clean up */ 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; } /* Copy in an update */ 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; /* Copy in enough header to peek at the size */ error = copyin((user_addr_t)inaddr, (void *)&update_header, sizeof(update_header)); if (error) return error; /* Get the actual, alleged size */ size = update_header.total_size; /* huge bogus piece of data that somehow made it through? */ if (size >= 1024 * 1024) return ENOMEM; /* Old microcodes? */ if (size == 0) size = 2048; /* default update size; see SDM */ /* * create the buffer for the update * It need only be aligned to 16-bytes, according to the SDM. * This also wires it down */ ret = kmem_alloc_kobject(kernel_map, (vm_offset_t *)&update, size, VM_KERN_MEMORY_OSFMK); if (ret != KERN_SUCCESS) return ENOMEM; /* Copy it in */ 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; } /* * This is called once by every CPU on a wake from sleep/hibernate * and is meant to re-apply a microcode update that got lost * by sleeping. */ void ucode_update_wake() { if (global_update) { kprintf("ucode: Re-applying update after wake (CPU #%d)\n", cpu_number()); update_microcode(); #ifdef DEBUG } else { kprintf("ucode: No update to apply (CPU #%d)\n", cpu_number()); #endif } } static void cpu_update(__unused void *arg) { /* grab the lock */ lck_spin_lock(ucode_slock); /* execute the update */ update_microcode(); /* release the lock */ lck_spin_unlock(ucode_slock); } /* Farm an update out to all CPUs */ static void xcpu_update(void) { if (register_locks() != KERN_SUCCESS) return; /* Get all CPUs to perform the update */ mp_broadcast(cpu_update, NULL); /* Update the cpuid info */ cpuid_set_info(); } /* * sysctl function * */ 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 /* * Userland may only call this once per boot. Anything else * would not make sense (all updates are cumulative), and also * leak memory, because we don't free previous updates. */ if (global_update) return EPERM; #endif /* Get the whole microcode */ error = copyin_update(addr); if (error) return error; /* Farm out the updates */ xcpu_update(); return 0; } |