/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "amd_iommu_impl.h" #include "amd_iommu_acpi.h" #define AMD_IOMMU_MINOR2INST(x) (x) #define AMD_IOMMU_INST2MINOR(x) (x) #define AMD_IOMMU_NODETYPE "ddi_iommu" #define AMD_IOMMU_MINOR_NAME "amd-iommu" static int amd_iommu_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result); static int amd_iommu_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int amd_iommu_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int amd_iommu_open(dev_t *devp, int flag, int otyp, cred_t *credp); static int amd_iommu_close(dev_t dev, int flag, int otyp, cred_t *credp); static int amd_iommu_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp); static int amd_iommu_quiesce(dev_info_t *dip); static struct cb_ops amd_iommu_cb_ops = { amd_iommu_open, /* cb_open */ amd_iommu_close, /* cb_close */ nodev, /* cb_strategy */ nodev, /* cb_print */ nodev, /* cb_dump */ nodev, /* cb_read */ nodev, /* cb_write */ amd_iommu_ioctl, /* cb_ioctl */ nodev, /* cb_devmap */ nodev, /* cb_mmap */ nodev, /* cb_segmap */ nochpoll, /* cb_chpoll */ ddi_prop_op, /* cb_prop_op */ NULL, /* cb_str */ D_NEW | D_MP, /* cb_flag */ CB_REV, /* cb_rev */ nodev, /* cb_aread */ nodev /* cb_awrite */ }; static struct dev_ops amd_iommu_dev_ops = { DEVO_REV, /* devo_rev */ 0, /* devo_refcnt */ amd_iommu_getinfo, /* devo_getinfo */ nulldev, /* devo_identify */ nulldev, /* devo_probe */ amd_iommu_attach, /* devo_attach */ amd_iommu_detach, /* devo_detach */ nodev, /* devo_reset */ &amd_iommu_cb_ops, /* devo_cb_ops */ NULL, /* devo_bus_ops */ nulldev, /* devo_power */ amd_iommu_quiesce, /* devo_quiesce */ }; static struct modldrv modldrv = { &mod_driverops, "AMD IOMMU 0.1", &amd_iommu_dev_ops }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modldrv, NULL }; amd_iommu_debug_t amd_iommu_debug; kmutex_t amd_iommu_global_lock; const char *amd_iommu_modname = "amd_iommu"; amd_iommu_alias_t **amd_iommu_alias; amd_iommu_page_table_hash_t amd_iommu_page_table_hash; static void *amd_iommu_statep; int amd_iommu_64bit_bug; int amd_iommu_unity_map; int amd_iommu_no_RW_perms; int amd_iommu_no_unmap; int amd_iommu_pageva_inval_all; int amd_iommu_disable; /* disable IOMMU */ char *amd_iommu_disable_list; /* list of drivers bypassing IOMMU */ int _init(void) { int error = ENOTSUP; #if defined(__amd64) && !defined(__xpv) if (get_hwenv() != HW_NATIVE) return (ENOTSUP); error = ddi_soft_state_init(&amd_iommu_statep, sizeof (struct amd_iommu_state), 1); if (error) { cmn_err(CE_WARN, "%s: _init: failed to init soft state.", amd_iommu_modname); return (error); } if (amd_iommu_acpi_init() != DDI_SUCCESS) { if (amd_iommu_debug) { cmn_err(CE_WARN, "%s: _init: ACPI init failed.", amd_iommu_modname); } ddi_soft_state_fini(&amd_iommu_statep); return (ENOTSUP); } amd_iommu_read_boot_props(); if (amd_iommu_page_table_hash_init(&amd_iommu_page_table_hash) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: _init: Page table hash init failed.", amd_iommu_modname); if (amd_iommu_disable_list) { kmem_free(amd_iommu_disable_list, strlen(amd_iommu_disable_list) + 1); amd_iommu_disable_list = NULL; } amd_iommu_acpi_fini(); ddi_soft_state_fini(&amd_iommu_statep); amd_iommu_statep = NULL; return (EFAULT); } error = mod_install(&modlinkage); if (error) { cmn_err(CE_WARN, "%s: _init: mod_install failed.", amd_iommu_modname); amd_iommu_page_table_hash_fini(&amd_iommu_page_table_hash); if (amd_iommu_disable_list) { kmem_free(amd_iommu_disable_list, strlen(amd_iommu_disable_list) + 1); amd_iommu_disable_list = NULL; } amd_iommu_acpi_fini(); ddi_soft_state_fini(&amd_iommu_statep); amd_iommu_statep = NULL; return (error); } error = 0; #endif return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int _fini(void) { int error; error = mod_remove(&modlinkage); if (error) return (error); amd_iommu_page_table_hash_fini(&amd_iommu_page_table_hash); if (amd_iommu_disable_list) { kmem_free(amd_iommu_disable_list, strlen(amd_iommu_disable_list) + 1); amd_iommu_disable_list = NULL; } amd_iommu_acpi_fini(); ddi_soft_state_fini(&amd_iommu_statep); amd_iommu_statep = NULL; return (0); } /*ARGSUSED*/ static int amd_iommu_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) { struct amd_iommu_state *statep; ASSERT(result); *result = NULL; switch (cmd) { case DDI_INFO_DEVT2DEVINFO: statep = ddi_get_soft_state(amd_iommu_statep, AMD_IOMMU_MINOR2INST(getminor((dev_t)arg))); if (statep) { *result = statep->aioms_devi; return (DDI_SUCCESS); } break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)(uintptr_t) AMD_IOMMU_MINOR2INST(getminor((dev_t)arg)); return (DDI_SUCCESS); } return (DDI_FAILURE); } static int amd_iommu_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int instance = ddi_get_instance(dip); const char *driver = ddi_driver_name(dip); struct amd_iommu_state *statep; ASSERT(instance >= 0); ASSERT(driver); switch (cmd) { case DDI_ATTACH: if (ddi_soft_state_zalloc(amd_iommu_statep, instance) != DDI_SUCCESS) { cmn_err(CE_WARN, "Unable to allocate soft state for " "%s%d", driver, instance); return (DDI_FAILURE); } statep = ddi_get_soft_state(amd_iommu_statep, instance); if (statep == NULL) { cmn_err(CE_WARN, "Unable to get soft state for " "%s%d", driver, instance); ddi_soft_state_free(amd_iommu_statep, instance); return (DDI_FAILURE); } if (ddi_create_minor_node(dip, AMD_IOMMU_MINOR_NAME, S_IFCHR, AMD_IOMMU_INST2MINOR(instance), AMD_IOMMU_NODETYPE, 0) != DDI_SUCCESS) { cmn_err(CE_WARN, "Unable to create minor node for " "%s%d", driver, instance); ddi_remove_minor_node(dip, NULL); ddi_soft_state_free(amd_iommu_statep, instance); return (DDI_FAILURE); } statep->aioms_devi = dip; statep->aioms_instance = instance; statep->aioms_iommu_start = NULL; statep->aioms_iommu_end = NULL; amd_iommu_lookup_conf_props(dip); if (amd_iommu_disable_list) { cmn_err(CE_NOTE, "AMD IOMMU disabled for the following" " drivers:\n%s", amd_iommu_disable_list); } if (amd_iommu_disable) { cmn_err(CE_NOTE, "AMD IOMMU disabled by user"); } else if (amd_iommu_setup(dip, statep) != DDI_SUCCESS) { cmn_err(CE_WARN, "Unable to initialize AMD IOMMU " "%s%d", driver, instance); ddi_remove_minor_node(dip, NULL); ddi_soft_state_free(amd_iommu_statep, instance); return (DDI_FAILURE); } ddi_report_dev(dip); return (DDI_SUCCESS); case DDI_RESUME: return (DDI_SUCCESS); default: return (DDI_FAILURE); } } static int amd_iommu_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int instance = ddi_get_instance(dip); const char *driver = ddi_driver_name(dip); struct amd_iommu_state *statep; ASSERT(instance >= 0); ASSERT(driver); switch (cmd) { case DDI_DETACH: statep = ddi_get_soft_state(amd_iommu_statep, instance); if (statep == NULL) { cmn_err(CE_WARN, "%s%d: Cannot get soft state", driver, instance); return (DDI_FAILURE); } return (DDI_FAILURE); case DDI_SUSPEND: return (DDI_SUCCESS); default: return (DDI_FAILURE); } } /*ARGSUSED*/ static int amd_iommu_open(dev_t *devp, int flag, int otyp, cred_t *credp) { int instance = AMD_IOMMU_MINOR2INST(getminor(*devp)); struct amd_iommu_state *statep; const char *f = "amd_iommu_open"; if (instance < 0) { cmn_err(CE_WARN, "%s: invalid instance %d", f, instance); return (ENXIO); } if (!(flag & (FREAD|FWRITE))) { cmn_err(CE_WARN, "%s: invalid flags %d", f, flag); return (EINVAL); } if (otyp != OTYP_CHR) { cmn_err(CE_WARN, "%s: invalid otyp %d", f, otyp); return (EINVAL); } statep = ddi_get_soft_state(amd_iommu_statep, instance); if (statep == NULL) { cmn_err(CE_WARN, "%s: cannot get soft state: instance %d", f, instance); return (ENXIO); } ASSERT(statep->aioms_instance == instance); return (0); } /*ARGSUSED*/ static int amd_iommu_close(dev_t dev, int flag, int otyp, cred_t *credp) { int instance = AMD_IOMMU_MINOR2INST(getminor(dev)); struct amd_iommu_state *statep; const char *f = "amd_iommu_close"; if (instance < 0) { cmn_err(CE_WARN, "%s: invalid instance %d", f, instance); return (ENXIO); } if (!(flag & (FREAD|FWRITE))) { cmn_err(CE_WARN, "%s: invalid flags %d", f, flag); return (EINVAL); } if (otyp != OTYP_CHR) { cmn_err(CE_WARN, "%s: invalid otyp %d", f, otyp); return (EINVAL); } statep = ddi_get_soft_state(amd_iommu_statep, instance); if (statep == NULL) { cmn_err(CE_WARN, "%s: cannot get soft state: instance %d", f, instance); return (ENXIO); } ASSERT(statep->aioms_instance == instance); return (0); } /*ARGSUSED*/ static int amd_iommu_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { int instance = AMD_IOMMU_MINOR2INST(getminor(dev)); struct amd_iommu_state *statep; const char *f = "amd_iommu_ioctl"; ASSERT(*rvalp); if (instance < 0) { cmn_err(CE_WARN, "%s: invalid instance %d", f, instance); return (ENXIO); } if (!(mode & (FREAD|FWRITE))) { cmn_err(CE_WARN, "%s: invalid mode %d", f, mode); return (EINVAL); } if (mode & FKIOCTL) { cmn_err(CE_WARN, "%s: FKIOCTL unsupported mode %d", f, mode); return (EINVAL); } statep = ddi_get_soft_state(amd_iommu_statep, instance); if (statep == NULL) { cmn_err(CE_WARN, "%s: cannot get soft state: instance %d", f, instance); return (ENXIO); } ASSERT(statep->aioms_instance == instance); return (ENOTTY); } static int amd_iommu_quiesce(dev_info_t *dip) { int instance = ddi_get_instance(dip); struct amd_iommu_state *statep; const char *f = "amd_iommu_quiesce"; statep = ddi_get_soft_state(amd_iommu_statep, instance); if (statep == NULL) { cmn_err(CE_WARN, "%s: cannot get soft state: instance %d", f, instance); return (DDI_FAILURE); } if (amd_iommu_teardown(dip, statep, AMD_IOMMU_QUIESCE) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: Unable to quiesce AMD IOMMU " "%s%d", f, ddi_driver_name(dip), instance); return (DDI_FAILURE); } return (DDI_SUCCESS); }