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 2009 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/stat.h>
32#include <sys/cred.h>
33#include <sys/modctl.h>
34#include <sys/conf.h>
35#include <sys/devops.h>
36#include <sys/ddi.h>
37#include <sys/x86_archext.h>
38
39#include <sys/amd_iommu.h>
40#include "amd_iommu_impl.h"
41#include "amd_iommu_acpi.h"
42
43
44#define	AMD_IOMMU_MINOR2INST(x)	(x)
45#define	AMD_IOMMU_INST2MINOR(x)	(x)
46#define	AMD_IOMMU_NODETYPE	"ddi_iommu"
47#define	AMD_IOMMU_MINOR_NAME	"amd-iommu"
48
49static int amd_iommu_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
50    void **result);
51static int amd_iommu_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
52static int amd_iommu_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
53static int amd_iommu_open(dev_t *devp, int flag, int otyp, cred_t *credp);
54static int amd_iommu_close(dev_t dev, int flag, int otyp, cred_t *credp);
55static int amd_iommu_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
56    cred_t *credp, int *rvalp);
57static int amd_iommu_quiesce(dev_info_t *dip);
58
59static struct cb_ops amd_iommu_cb_ops = {
60	amd_iommu_open,		/* cb_open */
61	amd_iommu_close,	/* cb_close */
62	nodev,			/* cb_strategy */
63	nodev,			/* cb_print */
64	nodev,			/* cb_dump */
65	nodev,			/* cb_read */
66	nodev,			/* cb_write */
67	amd_iommu_ioctl,	/* cb_ioctl */
68	nodev,			/* cb_devmap */
69	nodev,			/* cb_mmap */
70	nodev,			/* cb_segmap */
71	nochpoll,		/* cb_chpoll */
72	ddi_prop_op,		/* cb_prop_op */
73	NULL,			/* cb_str */
74	D_NEW | D_MP,		/* cb_flag */
75	CB_REV,			/* cb_rev */
76	nodev,			/* cb_aread */
77	nodev			/* cb_awrite */
78};
79
80static struct dev_ops amd_iommu_dev_ops = {
81	DEVO_REV,		/* devo_rev */
82	0,			/* devo_refcnt */
83	amd_iommu_getinfo,	/* devo_getinfo */
84	nulldev,		/* devo_identify */
85	nulldev,		/* devo_probe */
86	amd_iommu_attach,	/* devo_attach */
87	amd_iommu_detach,	/* devo_detach */
88	nodev,			/* devo_reset */
89	&amd_iommu_cb_ops,	/* devo_cb_ops */
90	NULL,			/* devo_bus_ops */
91	nulldev,		/* devo_power */
92	amd_iommu_quiesce,	/* devo_quiesce */
93};
94
95static struct modldrv modldrv = {
96	&mod_driverops,
97	"AMD IOMMU 0.1",
98	&amd_iommu_dev_ops
99};
100
101static struct modlinkage modlinkage = {
102	MODREV_1,
103	(void *)&modldrv,
104	NULL
105};
106
107amd_iommu_debug_t amd_iommu_debug;
108kmutex_t amd_iommu_global_lock;
109const char *amd_iommu_modname = "amd_iommu";
110amd_iommu_alias_t **amd_iommu_alias;
111amd_iommu_page_table_hash_t amd_iommu_page_table_hash;
112static void *amd_iommu_statep;
113int amd_iommu_64bit_bug;
114int amd_iommu_unity_map;
115int amd_iommu_no_RW_perms;
116int amd_iommu_no_unmap;
117int amd_iommu_pageva_inval_all;
118int amd_iommu_disable;		/* disable IOMMU */
119char *amd_iommu_disable_list;	/* list of drivers bypassing IOMMU */
120
121int
122_init(void)
123{
124	int error = ENOTSUP;
125
126#if defined(__amd64) && !defined(__xpv)
127
128	if (get_hwenv() != HW_NATIVE)
129		return (ENOTSUP);
130
131	error = ddi_soft_state_init(&amd_iommu_statep,
132	    sizeof (struct amd_iommu_state), 1);
133	if (error) {
134		cmn_err(CE_WARN, "%s: _init: failed to init soft state.",
135		    amd_iommu_modname);
136		return (error);
137	}
138
139	if (amd_iommu_acpi_init() != DDI_SUCCESS) {
140		if (amd_iommu_debug) {
141			cmn_err(CE_WARN, "%s: _init: ACPI init failed.",
142			    amd_iommu_modname);
143		}
144		ddi_soft_state_fini(&amd_iommu_statep);
145		return (ENOTSUP);
146	}
147
148	amd_iommu_read_boot_props();
149
150	if (amd_iommu_page_table_hash_init(&amd_iommu_page_table_hash)
151	    != DDI_SUCCESS) {
152		cmn_err(CE_WARN, "%s: _init: Page table hash init failed.",
153		    amd_iommu_modname);
154		if (amd_iommu_disable_list) {
155			kmem_free(amd_iommu_disable_list,
156			    strlen(amd_iommu_disable_list) + 1);
157			amd_iommu_disable_list = NULL;
158		}
159		amd_iommu_acpi_fini();
160		ddi_soft_state_fini(&amd_iommu_statep);
161		amd_iommu_statep = NULL;
162		return (EFAULT);
163	}
164
165	error = mod_install(&modlinkage);
166	if (error) {
167		cmn_err(CE_WARN, "%s: _init: mod_install failed.",
168		    amd_iommu_modname);
169		amd_iommu_page_table_hash_fini(&amd_iommu_page_table_hash);
170		if (amd_iommu_disable_list) {
171			kmem_free(amd_iommu_disable_list,
172			    strlen(amd_iommu_disable_list) + 1);
173			amd_iommu_disable_list = NULL;
174		}
175		amd_iommu_acpi_fini();
176		ddi_soft_state_fini(&amd_iommu_statep);
177		amd_iommu_statep = NULL;
178		return (error);
179	}
180	error = 0;
181#endif
182
183	return (error);
184}
185
186int
187_info(struct modinfo *modinfop)
188{
189	return (mod_info(&modlinkage, modinfop));
190}
191
192int
193_fini(void)
194{
195	int error;
196
197	error = mod_remove(&modlinkage);
198	if (error)
199		return (error);
200
201	amd_iommu_page_table_hash_fini(&amd_iommu_page_table_hash);
202	if (amd_iommu_disable_list) {
203		kmem_free(amd_iommu_disable_list,
204		    strlen(amd_iommu_disable_list) + 1);
205		amd_iommu_disable_list = NULL;
206	}
207	amd_iommu_acpi_fini();
208	ddi_soft_state_fini(&amd_iommu_statep);
209	amd_iommu_statep = NULL;
210
211	return (0);
212}
213
214/*ARGSUSED*/
215static int
216amd_iommu_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
217{
218	struct amd_iommu_state *statep;
219
220	ASSERT(result);
221
222	*result = NULL;
223
224	switch (cmd) {
225	case DDI_INFO_DEVT2DEVINFO:
226		statep = ddi_get_soft_state(amd_iommu_statep,
227		    AMD_IOMMU_MINOR2INST(getminor((dev_t)arg)));
228		if (statep) {
229			*result = statep->aioms_devi;
230			return (DDI_SUCCESS);
231		}
232		break;
233	case DDI_INFO_DEVT2INSTANCE:
234		*result = (void *)(uintptr_t)
235		    AMD_IOMMU_MINOR2INST(getminor((dev_t)arg));
236		return (DDI_SUCCESS);
237	}
238
239	return (DDI_FAILURE);
240}
241
242static int
243amd_iommu_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
244{
245	int instance = ddi_get_instance(dip);
246	const char *driver = ddi_driver_name(dip);
247	struct amd_iommu_state *statep;
248
249	ASSERT(instance >= 0);
250	ASSERT(driver);
251
252	switch (cmd) {
253	case DDI_ATTACH:
254		if (ddi_soft_state_zalloc(amd_iommu_statep, instance)
255		    != DDI_SUCCESS) {
256			cmn_err(CE_WARN, "Unable to allocate soft state for "
257			    "%s%d", driver, instance);
258			return (DDI_FAILURE);
259		}
260
261		statep = ddi_get_soft_state(amd_iommu_statep, instance);
262		if (statep == NULL) {
263			cmn_err(CE_WARN, "Unable to get soft state for "
264			    "%s%d", driver, instance);
265			ddi_soft_state_free(amd_iommu_statep, instance);
266			return (DDI_FAILURE);
267		}
268
269		if (ddi_create_minor_node(dip, AMD_IOMMU_MINOR_NAME, S_IFCHR,
270		    AMD_IOMMU_INST2MINOR(instance), AMD_IOMMU_NODETYPE,
271		    0) != DDI_SUCCESS) {
272			cmn_err(CE_WARN, "Unable to create minor node for "
273			    "%s%d", driver, instance);
274			ddi_remove_minor_node(dip, NULL);
275			ddi_soft_state_free(amd_iommu_statep, instance);
276			return (DDI_FAILURE);
277		}
278
279		statep->aioms_devi = dip;
280		statep->aioms_instance = instance;
281		statep->aioms_iommu_start = NULL;
282		statep->aioms_iommu_end = NULL;
283
284		amd_iommu_lookup_conf_props(dip);
285
286		if (amd_iommu_disable_list) {
287			cmn_err(CE_NOTE, "AMD IOMMU disabled for the following"
288			    " drivers:\n%s", amd_iommu_disable_list);
289		}
290
291		if (amd_iommu_disable) {
292			cmn_err(CE_NOTE, "AMD IOMMU disabled by user");
293		} else if (amd_iommu_setup(dip, statep) != DDI_SUCCESS) {
294			cmn_err(CE_WARN, "Unable to initialize AMD IOMMU "
295			    "%s%d", driver, instance);
296			ddi_remove_minor_node(dip, NULL);
297			ddi_soft_state_free(amd_iommu_statep, instance);
298			return (DDI_FAILURE);
299		}
300
301		ddi_report_dev(dip);
302
303		return (DDI_SUCCESS);
304
305	case DDI_RESUME:
306		return (DDI_SUCCESS);
307	default:
308		return (DDI_FAILURE);
309	}
310}
311
312static int
313amd_iommu_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
314{
315	int instance = ddi_get_instance(dip);
316	const char *driver = ddi_driver_name(dip);
317	struct amd_iommu_state *statep;
318
319	ASSERT(instance >= 0);
320	ASSERT(driver);
321
322	switch (cmd) {
323	case DDI_DETACH:
324		statep = ddi_get_soft_state(amd_iommu_statep, instance);
325		if (statep == NULL) {
326			cmn_err(CE_WARN, "%s%d: Cannot get soft state",
327			    driver, instance);
328			return (DDI_FAILURE);
329		}
330		return (DDI_FAILURE);
331	case DDI_SUSPEND:
332		return (DDI_SUCCESS);
333	default:
334		return (DDI_FAILURE);
335	}
336}
337
338/*ARGSUSED*/
339static int
340amd_iommu_open(dev_t *devp, int flag, int otyp, cred_t *credp)
341{
342	int instance = AMD_IOMMU_MINOR2INST(getminor(*devp));
343	struct amd_iommu_state *statep;
344	const char *f = "amd_iommu_open";
345
346	if (instance < 0) {
347		cmn_err(CE_WARN, "%s: invalid instance %d",
348		    f, instance);
349		return (ENXIO);
350	}
351
352	if (!(flag & (FREAD|FWRITE))) {
353		cmn_err(CE_WARN, "%s: invalid flags %d", f, flag);
354		return (EINVAL);
355	}
356
357	if (otyp != OTYP_CHR) {
358		cmn_err(CE_WARN, "%s: invalid otyp %d", f, otyp);
359		return (EINVAL);
360	}
361
362	statep = ddi_get_soft_state(amd_iommu_statep, instance);
363	if (statep == NULL) {
364		cmn_err(CE_WARN, "%s: cannot get soft state: instance %d",
365		    f, instance);
366		return (ENXIO);
367	}
368
369	ASSERT(statep->aioms_instance == instance);
370
371	return (0);
372}
373
374/*ARGSUSED*/
375static int
376amd_iommu_close(dev_t dev, int flag, int otyp, cred_t *credp)
377{
378	int instance = AMD_IOMMU_MINOR2INST(getminor(dev));
379	struct amd_iommu_state *statep;
380	const char *f = "amd_iommu_close";
381
382	if (instance < 0) {
383		cmn_err(CE_WARN, "%s: invalid instance %d", f, instance);
384		return (ENXIO);
385	}
386
387	if (!(flag & (FREAD|FWRITE))) {
388		cmn_err(CE_WARN, "%s: invalid flags %d", f, flag);
389		return (EINVAL);
390	}
391
392	if (otyp != OTYP_CHR) {
393		cmn_err(CE_WARN, "%s: invalid otyp %d", f, otyp);
394		return (EINVAL);
395	}
396
397	statep = ddi_get_soft_state(amd_iommu_statep, instance);
398	if (statep == NULL) {
399		cmn_err(CE_WARN, "%s: cannot get soft state: instance %d",
400		    f, instance);
401		return (ENXIO);
402	}
403
404	ASSERT(statep->aioms_instance == instance);
405	return (0);
406
407}
408
409/*ARGSUSED*/
410static int
411amd_iommu_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
412    int *rvalp)
413{
414	int instance = AMD_IOMMU_MINOR2INST(getminor(dev));
415	struct amd_iommu_state *statep;
416	const char *f = "amd_iommu_ioctl";
417
418	ASSERT(*rvalp);
419
420	if (instance < 0) {
421		cmn_err(CE_WARN, "%s: invalid instance %d", f, instance);
422		return (ENXIO);
423	}
424
425
426	if (!(mode & (FREAD|FWRITE))) {
427		cmn_err(CE_WARN, "%s: invalid mode %d", f, mode);
428		return (EINVAL);
429	}
430
431	if (mode & FKIOCTL) {
432		cmn_err(CE_WARN, "%s: FKIOCTL unsupported mode %d", f, mode);
433		return (EINVAL);
434	}
435
436	statep = ddi_get_soft_state(amd_iommu_statep, instance);
437	if (statep == NULL) {
438		cmn_err(CE_WARN, "%s: cannot get soft state: instance %d",
439		    f, instance);
440		return (ENXIO);
441	}
442
443	ASSERT(statep->aioms_instance == instance);
444
445	return (ENOTTY);
446}
447
448static int
449amd_iommu_quiesce(dev_info_t *dip)
450{
451	int instance = ddi_get_instance(dip);
452	struct amd_iommu_state *statep;
453	const char *f = "amd_iommu_quiesce";
454
455	statep = ddi_get_soft_state(amd_iommu_statep, instance);
456	if (statep == NULL) {
457		cmn_err(CE_WARN, "%s: cannot get soft state: instance %d",
458		    f, instance);
459		return (DDI_FAILURE);
460	}
461
462	if (amd_iommu_teardown(dip, statep, AMD_IOMMU_QUIESCE) != DDI_SUCCESS) {
463		cmn_err(CE_WARN, "%s: Unable to quiesce AMD IOMMU "
464		    "%s%d", f, ddi_driver_name(dip), instance);
465		return (DDI_FAILURE);
466	}
467
468	return (DDI_SUCCESS);
469}
470