1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 *
26 * Copyright 2023 Oxide Computer Company
27 */
28
29 #include <sys/types.h>
30 #include <sys/file.h>
31 #include <sys/errno.h>
32 #include <sys/open.h>
33 #include <sys/cred.h>
34 #include <sys/conf.h>
35 #include <sys/stat.h>
36 #include <sys/policy.h>
37 #include <sys/processor.h>
38 #include <sys/kmem.h>
39 #include <sys/modctl.h>
40 #include <sys/ddi.h>
41 #include <sys/sunddi.h>
42
43 #include <sys/auxv.h>
44 #include <sys/ucode.h>
45 #include <sys/systeminfo.h>
46 #include <sys/x86_archext.h>
47
48 static dev_info_t *ucode_devi;
49 static uint32_t ucode_max_combined_size;
50 static kmutex_t ucode_update_lock;
51
52 static int
ucode_getinfo(dev_info_t * devi,ddi_info_cmd_t cmd,void * arg,void ** result)53 ucode_getinfo(dev_info_t *devi, ddi_info_cmd_t cmd, void *arg, void **result)
54 {
55 switch (cmd) {
56 case DDI_INFO_DEVT2DEVINFO:
57 case DDI_INFO_DEVT2INSTANCE:
58 break;
59 default:
60 return (DDI_FAILURE);
61 }
62
63 switch (getminor((dev_t)arg)) {
64 case UCODE_MINOR:
65 break;
66 default:
67 return (DDI_FAILURE);
68 }
69
70 if (cmd == DDI_INFO_DEVT2INSTANCE)
71 *result = 0;
72 else
73 *result = ucode_devi;
74 return (DDI_SUCCESS);
75 }
76
77 static int
ucode_attach(dev_info_t * devi,ddi_attach_cmd_t cmd)78 ucode_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
79 {
80 ASSERT(cmd != DDI_RESUME);
81
82 switch (cmd) {
83 case DDI_RESUME:
84 return (DDI_SUCCESS);
85
86 case DDI_ATTACH:
87 ucode_devi = devi;
88 ucode_max_combined_size = UCODE_MAX_COMBINED_SIZE;
89
90 if (ddi_create_minor_node(devi, UCODE_NODE_NAME, S_IFCHR,
91 UCODE_MINOR, DDI_PSEUDO, 0) != DDI_SUCCESS) {
92 cmn_err(CE_WARN, "%s: Unable to create minor node",
93 UCODE_NODE_NAME);
94 return (DDI_FAILURE);
95 }
96 ddi_report_dev(devi);
97 return (DDI_SUCCESS);
98
99 default:
100 return (DDI_FAILURE);
101 }
102 }
103
104 static int
ucode_detach(dev_info_t * devi,ddi_detach_cmd_t cmd)105 ucode_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
106 {
107 /*
108 * The power management and DR framework should never invoke this
109 * driver with DDI_SUSPEND because the ucode pseudo device does not
110 * have a reg property or hardware binding. However, we will return
111 * DDI_SUCCESS so that in the unlikely event that it does get
112 * called, the system will still suspend and resume.
113 */
114 ASSERT(cmd != DDI_SUSPEND);
115
116 switch (cmd) {
117 case DDI_SUSPEND:
118 return (DDI_SUCCESS);
119
120 case DDI_DETACH:
121 ddi_remove_minor_node(devi, NULL);
122 ucode_devi = NULL;
123 return (DDI_SUCCESS);
124
125 default:
126 return (DDI_FAILURE);
127 }
128 }
129
130 static int
ucode_open(dev_t * dev,int flag,int otyp,cred_t * cr)131 ucode_open(dev_t *dev, int flag, int otyp, cred_t *cr)
132 {
133 return (getminor(*dev) == UCODE_MINOR ? 0 : ENXIO);
134 }
135
136 static int
ucode_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * cr,int * rval)137 ucode_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cr, int *rval)
138 {
139 switch (cmd) {
140 case UCODE_GET_VERSION: {
141 int size;
142 uint32_t *revp, *rev_array;
143 size_t bufsz = NCPU * sizeof (*revp);
144 ucode_errno_t rc = EM_OK;
145
146 STRUCT_DECL(ucode_get_rev_struct, h);
147 STRUCT_INIT(h, mode);
148 if (ddi_copyin((void *)arg,
149 STRUCT_BUF(h), STRUCT_SIZE(h), mode))
150 return (EFAULT);
151
152 if ((size = STRUCT_FGET(h, ugv_size)) > NCPU || size < 0)
153 return (EINVAL);
154
155 if (size == 0)
156 return (0);
157
158 if ((rev_array = STRUCT_FGETP(h, ugv_rev)) == NULL)
159 return (EINVAL);
160
161 size *= sizeof (uint32_t);
162
163 /* Can't rely on caller for kernel's buffer size. */
164 revp = kmem_zalloc(bufsz, KM_SLEEP);
165 if (ddi_copyin((void *)rev_array, revp, size, mode) != 0) {
166 kmem_free(revp, bufsz);
167 return (EINVAL);
168 }
169
170 rc = ucode_get_rev(revp);
171
172 STRUCT_FSET(h, ugv_errno, rc);
173
174 if (ddi_copyout(revp, (void *)rev_array, size, mode) != 0) {
175 kmem_free(revp, bufsz);
176 return (EFAULT);
177 }
178
179 kmem_free(revp, bufsz);
180
181 if (ddi_copyout(STRUCT_BUF(h), (void *)arg,
182 STRUCT_SIZE(h), mode))
183 return (EFAULT);
184
185 return (0);
186 }
187
188 case UCODE_UPDATE: {
189 int size;
190 uint8_t *ucodep, *uw_ucode;
191 ucode_errno_t rc = EM_OK;
192
193 /*
194 * Requires all privilege.
195 */
196 if (cr && secpolicy_ucode_update(cr))
197 return (EPERM);
198
199 STRUCT_DECL(ucode_write_struct, h);
200
201 STRUCT_INIT(h, mode);
202 if (ddi_copyin((void *)arg, STRUCT_BUF(h), STRUCT_SIZE(h),
203 mode))
204 return (EFAULT);
205
206 /*
207 * We allow the size of the combined microcode file to be up to
208 * ucode_max_combined_size. It is initialized to
209 * UCODE_MAX_COMBINED_SIZE, and can be patched if necessary.
210 */
211 size = STRUCT_FGET(h, uw_size);
212 if (size > ucode_max_combined_size || size == 0)
213 return (EINVAL);
214
215 if ((uw_ucode = STRUCT_FGETP(h, uw_ucode)) == NULL)
216 return (EINVAL);
217
218 ucodep = kmem_zalloc(size, KM_SLEEP);
219 if (ddi_copyin((void *)uw_ucode, ucodep, size, mode) != 0) {
220 kmem_free(ucodep, size);
221 return (EFAULT);
222 }
223
224 if ((rc = ucode_validate(ucodep, size)) != EM_OK) {
225 kmem_free(ucodep, size);
226 STRUCT_FSET(h, uw_errno, rc);
227 if (ddi_copyout(STRUCT_BUF(h), (void *)arg,
228 STRUCT_SIZE(h), mode))
229 return (EFAULT);
230 return (0);
231 }
232
233 mutex_enter(&ucode_update_lock);
234 rc = ucode_update(ucodep, size);
235 mutex_exit(&ucode_update_lock);
236
237 kmem_free(ucodep, size);
238
239 STRUCT_FSET(h, uw_errno, rc);
240 if (ddi_copyout(STRUCT_BUF(h), (void *)arg,
241 STRUCT_SIZE(h), mode))
242 return (EFAULT);
243
244 /*
245 * Even if rc is not EM_OK, it is a successful operation
246 * from ioctl()'s perspective. We return the detailed error
247 * code via the ucode_write_struct data structure.
248 */
249 return (0);
250 }
251
252
253 default:
254 return (ENOTTY);
255 }
256 }
257
258 static struct cb_ops ucode_cb_ops = {
259 ucode_open,
260 nulldev, /* close */
261 nodev, /* strategy */
262 nodev, /* print */
263 nodev, /* dump */
264 nodev, /* read */
265 nodev, /* write */
266 ucode_ioctl,
267 nodev, /* devmap */
268 nodev, /* mmap */
269 nodev, /* segmap */
270 nochpoll, /* poll */
271 ddi_prop_op,
272 NULL,
273 D_64BIT | D_NEW | D_MP
274 };
275
276 static struct dev_ops ucode_dv_ops = {
277 DEVO_REV,
278 0,
279 ucode_getinfo,
280 nulldev, /* identify */
281 nulldev, /* probe */
282 ucode_attach,
283 ucode_detach,
284 nodev, /* reset */
285 &ucode_cb_ops,
286 (struct bus_ops *)0,
287 NULL, /* power */
288 ddi_quiesce_not_needed, /* quiesce */
289 };
290
291 static struct modldrv modldrv = {
292 &mod_driverops,
293 "ucode driver",
294 &ucode_dv_ops
295 };
296
297 static struct modlinkage modl = {
298 MODREV_1,
299 &modldrv
300 };
301
302 int
_init(void)303 _init(void)
304 {
305 int rc;
306
307 if ((rc = mod_install(&modl)) != 0)
308 return (rc);
309
310 mutex_init(&ucode_update_lock, NULL, MUTEX_DRIVER, NULL);
311
312 return (0);
313 }
314
315 int
_fini(void)316 _fini(void)
317 {
318 int rc;
319
320 if ((rc = mod_remove(&modl)) != 0)
321 return (rc);
322
323 mutex_destroy(&ucode_update_lock);
324
325 return (0);
326 }
327
328 int
_info(struct modinfo * modinfo)329 _info(struct modinfo *modinfo)
330 {
331 return (mod_info(&modl, modinfo));
332 }
333