xref: /illumos-gate/usr/src/uts/intel/io/ucode_drv.c (revision f48068addb8865f9338d23ffbe1043e369df37a1)
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 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
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 /*ARGSUSED*/
53 static int
54 ucode_getinfo(dev_info_t *devi, ddi_info_cmd_t cmd, void *arg, void **result)
55 {
56 	switch (cmd) {
57 	case DDI_INFO_DEVT2DEVINFO:
58 	case DDI_INFO_DEVT2INSTANCE:
59 		break;
60 	default:
61 		return (DDI_FAILURE);
62 	}
63 
64 	switch (getminor((dev_t)arg)) {
65 	case UCODE_MINOR:
66 		break;
67 	default:
68 		return (DDI_FAILURE);
69 	}
70 
71 	if (cmd == DDI_INFO_DEVT2INSTANCE)
72 		*result = 0;
73 	else
74 		*result = ucode_devi;
75 	return (DDI_SUCCESS);
76 }
77 
78 static int
79 ucode_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
80 {
81 	ASSERT(cmd != DDI_RESUME);
82 
83 	switch (cmd) {
84 	case DDI_RESUME:
85 		return (DDI_SUCCESS);
86 
87 	case DDI_ATTACH:
88 		ucode_devi = devi;
89 		ucode_max_combined_size = UCODE_MAX_COMBINED_SIZE;
90 
91 		if (ddi_create_minor_node(devi, UCODE_NODE_NAME, S_IFCHR,
92 		    UCODE_MINOR, DDI_PSEUDO, 0) != DDI_SUCCESS) {
93 			cmn_err(CE_WARN, "%s: Unable to create minor node",
94 			    UCODE_NODE_NAME);
95 			return (DDI_FAILURE);
96 		}
97 		ddi_report_dev(devi);
98 		return (DDI_SUCCESS);
99 
100 	default:
101 		return (DDI_FAILURE);
102 	}
103 }
104 
105 static int
106 ucode_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
107 {
108 	/*
109 	 * The power management and DR framework should never invoke this
110 	 * driver with DDI_SUSPEND because the ucode pseudo device does not
111 	 * have a reg property or hardware binding.  However, we will return
112 	 * DDI_SUCCESS so that in the unlikely event that it does get
113 	 * called, the system will still suspend and resume.
114 	 */
115 	ASSERT(cmd != DDI_SUSPEND);
116 
117 	switch (cmd) {
118 	case DDI_SUSPEND:
119 		return (DDI_SUCCESS);
120 
121 	case DDI_DETACH:
122 		ddi_remove_minor_node(devi, NULL);
123 		ucode_devi = NULL;
124 		return (DDI_SUCCESS);
125 
126 	default:
127 		return (DDI_FAILURE);
128 	}
129 }
130 
131 /*ARGSUSED1*/
132 static int
133 ucode_open(dev_t *dev, int flag, int otyp, cred_t *cr)
134 {
135 	return (getminor(*dev) == UCODE_MINOR ? 0 : ENXIO);
136 }
137 
138 
139 /*ARGSUSED*/
140 static int
141 ucode_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cr, int *rval)
142 {
143 	switch (cmd) {
144 	case UCODE_GET_VERSION: {
145 		int size;
146 		uint32_t *revp, *rev_array;
147 		ucode_errno_t rc = EM_OK;
148 
149 		STRUCT_DECL(ucode_get_rev_struct, h);
150 		STRUCT_INIT(h, mode);
151 		if (ddi_copyin((void *)arg,
152 		    STRUCT_BUF(h), STRUCT_SIZE(h), mode))
153 			return (EFAULT);
154 
155 		if ((size = STRUCT_FGET(h, ugv_size)) > NCPU)
156 			return (EINVAL);
157 
158 		if ((rev_array = STRUCT_FGETP(h, ugv_rev)) == NULL)
159 			return (EINVAL);
160 
161 		size *= sizeof (uint32_t);
162 
163 		revp = kmem_zalloc(size, KM_SLEEP);
164 		if (ddi_copyin((void *)rev_array, revp, size, mode) != 0) {
165 			kmem_free(revp, size);
166 			return (EINVAL);
167 		}
168 
169 		rc = ucode_get_rev(revp);
170 
171 		STRUCT_FSET(h, ugv_errno, rc);
172 
173 		if (ddi_copyout(revp, (void *)rev_array, size, mode) != 0) {
174 			kmem_free(revp, size);
175 			return (EFAULT);
176 		}
177 
178 		kmem_free(revp, size);
179 
180 		if (ddi_copyout(STRUCT_BUF(h), (void *)arg,
181 		    STRUCT_SIZE(h), mode))
182 			return (EFAULT);
183 
184 		return (0);
185 	}
186 
187 	case UCODE_UPDATE: {
188 		int size;
189 		uint8_t *ucodep, *uw_ucode;
190 		ucode_errno_t rc = EM_OK;
191 
192 		/*
193 		 * Requires all privilege.
194 		 */
195 		if (cr && secpolicy_ucode_update(cr))
196 			return (EPERM);
197 
198 		STRUCT_DECL(ucode_write_struct, h);
199 
200 		STRUCT_INIT(h, mode);
201 		if (ddi_copyin((void *)arg, STRUCT_BUF(h), STRUCT_SIZE(h),
202 		    mode))
203 			return (EFAULT);
204 
205 		/*
206 		 * We allow the size of the combined microcode file to be up to
207 		 * ucode_max_combined_size.  It is initialized to
208 		 * UCODE_MAX_COMBINED_SIZE, and can be patched if necessary.
209 		 */
210 		size = STRUCT_FGET(h, uw_size);
211 		if (size > ucode_max_combined_size || size == 0)
212 			return (EINVAL);
213 
214 		if ((uw_ucode = STRUCT_FGETP(h, uw_ucode)) == NULL)
215 			return (EINVAL);
216 
217 		ucodep = kmem_zalloc(size, KM_SLEEP);
218 		if (ddi_copyin((void *)uw_ucode, ucodep, size, mode) != 0) {
219 			kmem_free(ucodep, size);
220 			return (EFAULT);
221 		}
222 
223 		if ((rc = ucode_validate(ucodep, size)) != EM_OK) {
224 			kmem_free(ucodep, size);
225 			STRUCT_FSET(h, uw_errno, rc);
226 			if (ddi_copyout(STRUCT_BUF(h), (void *)arg,
227 			    STRUCT_SIZE(h), mode))
228 				return (EFAULT);
229 			return (0);
230 		}
231 
232 		mutex_enter(&ucode_update_lock);
233 		rc = ucode_update(ucodep, size);
234 		mutex_exit(&ucode_update_lock);
235 
236 		kmem_free(ucodep, size);
237 
238 		STRUCT_FSET(h, uw_errno, rc);
239 		if (ddi_copyout(STRUCT_BUF(h), (void *)arg,
240 		    STRUCT_SIZE(h), mode))
241 			return (EFAULT);
242 
243 		/*
244 		 * Even if rc is not EM_OK, it is a successful operation
245 		 * from ioctl()'s perspective.  We return the detailed error
246 		 * code via the ucode_write_struct data structure.
247 		 */
248 		return (0);
249 	}
250 
251 
252 	default:
253 		return (ENOTTY);
254 	}
255 }
256 
257 static struct cb_ops ucode_cb_ops = {
258 	ucode_open,
259 	nulldev,	/* close */
260 	nodev,		/* strategy */
261 	nodev,		/* print */
262 	nodev,		/* dump */
263 	nodev,		/* read */
264 	nodev,		/* write */
265 	ucode_ioctl,
266 	nodev,		/* devmap */
267 	nodev,		/* mmap */
268 	nodev,		/* segmap */
269 	nochpoll,	/* poll */
270 	ddi_prop_op,
271 	NULL,
272 	D_64BIT | D_NEW | D_MP
273 };
274 
275 static struct dev_ops ucode_dv_ops = {
276 	DEVO_REV,
277 	0,
278 	ucode_getinfo,
279 	nulldev,	/* identify */
280 	nulldev,	/* probe */
281 	ucode_attach,
282 	ucode_detach,
283 	nodev,		/* reset */
284 	&ucode_cb_ops,
285 	(struct bus_ops *)0
286 };
287 
288 static struct modldrv modldrv = {
289 	&mod_driverops,
290 	"ucode driver v%I%",
291 	&ucode_dv_ops
292 };
293 
294 static struct modlinkage modl = {
295 	MODREV_1,
296 	&modldrv
297 };
298 
299 int
300 _init(void)
301 {
302 	int rc;
303 
304 	if ((rc = mod_install(&modl)) != 0)
305 		return (rc);
306 
307 	mutex_init(&ucode_update_lock, NULL, MUTEX_DRIVER, NULL);
308 
309 	return (0);
310 }
311 
312 int
313 _fini(void)
314 {
315 	int rc;
316 
317 	if ((rc = mod_remove(&modl)) != 0)
318 		return (rc);
319 
320 	mutex_destroy(&ucode_update_lock);
321 
322 	return (0);
323 }
324 
325 int
326 _info(struct modinfo *modinfo)
327 {
328 	return (mod_info(&modl, modinfo));
329 }
330