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