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