xref: /illumos-gate/usr/src/uts/intel/io/ucode_drv.c (revision da7fc762b82ced1a0ec19a51e04cdf823187ec77)
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
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
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
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
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
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
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
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
329 _info(struct modinfo *modinfo)
330 {
331 	return (mod_info(&modl, modinfo));
332 }
333