immu.c revision e03dceed3deb85ad561202c77277e701f763fa13
/*
* 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
* 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
*/
/*
* Portions Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2009, Intel Corporation.
* All rights reserved.
*/
/*
* Intel IOMMU implementation
* This file contains Intel IOMMU code exported
* to the rest of the system and code that deals
* with the Intel IOMMU as a whole.
*/
#include <sys/pci_impl.h>
#include <sys/sysmacros.h>
#include <sys/ddidmareq.h>
#include <sys/ddi_impldefs.h>
#include <sys/smp_impldefs.h>
#include <sys/archsystm.h>
#include <sys/x86_archext.h>
#include <sys/bootconf.h>
#include <sys/bootinfo.h>
/* ########################### Globals and tunables ######################## */
/*
* Global switches (boolean) that can be toggled either via boot options
*/
/* Various features */
/* accessed in other files so not static */
/* various quirks that need working around */
/* debug messages */
/* Tunables */
/* ############ END OPTIONS section ################ */
/*
* Global used internally by Intel IOMMU code
*/
void *immu_pgtable_cache;
/* ######################## END Globals and tunables ###################### */
/* Globals used only in this file */
static char **black_array;
/* ###################### Utility routines ############################# */
/*
* Check if the device has mobile 4 chipset
*/
static int
{
"vendor-id", -1);
"device-id", -1);
"Force setting IOMMU write buffer");
return (DDI_WALK_TERMINATE);
} else {
return (DDI_WALK_CONTINUE);
}
}
static void
{
int e;
}
}
/*
* Check if the driver requests physical mapping
*/
/*ARGSUSED*/
static void
{
char *val;
/*
* Check for the DVMA unity mapping property on the device
*/
} else {
int e;
"Using unity DVMA mapping for device");
/* for unity mode, map will return USE_PHYSICAL */
ASSERT(e == DDI_DMA_USE_PHYSICAL);
}
}
}
/*
* Check if the device is USB controller
*/
/*ARGSUSED*/
static void
{
return;
}
/* This must come first since it does unity mapping */
if (immu_quirk_usbfullpa == B_TRUE) {
int e;
/* for unity mode, map will return USE_PHYSICAL */
ASSERT(e == DDI_DMA_USE_PHYSICAL);
}
if (immu_quirk_usbrmrr == B_TRUE) {
}
}
/*
* Check if the device is a LPC device
*/
/*ARGSUSED*/
static void
{
/* This will put the immu_devi on the LPC "specials" list */
}
}
/*
* Check if the device is a GFX device
*/
/*ARGSUSED*/
static void
{
int e;
/* This will put the immu_devi on the GFX "specials" list */
/* for unity mode, map will return USE_PHYSICAL */
ASSERT(e == DDI_DMA_USE_PHYSICAL);
}
}
static void
{
int count;
}
static int
{
/* just 1 check right now */
}
static int
{
}
return (DDI_WALK_CONTINUE);
}
static void
pre_setup_quirks(void)
{
}
static void
pre_startup_quirks(void)
{
}
/*
* get_bootopt()
* check a boot option (always a boolean)
*/
static void
{
/*
* All boot options set at the GRUB menu become
* properties on the rootnex.
*/
} else {
"is not set to true or false. Ignoring option.",
}
}
}
static void
{
uint_t n;
/*
* Check the rootnex.conf property
* Fake up a dev_t since searching the global
* property list needs it
*/
&iarray, &n) != DDI_PROP_SUCCESS) {
return;
}
if (n != 1) {
"%s property. Ignoring and using default",
"immu-flush-gran");
return;
}
if (iarray[0] < 0) {
"%s property. Inoring and Using default value",
"immu-flush-gran");
return;
}
}
static void
read_boot_options(void)
{
/* workaround switches */
/* debug printing */
/* get tunables */
}
/*
* Note, this will not catch hardware not enumerated
* in early boot
*/
static boolean_t
blacklisted_driver(void)
{
char **strptr;
int i;
/* need at least 2 strings */
if (nblacks < 2) {
return (B_FALSE);
}
for (i = 0; nblacks - i > 1; i++) {
strptr = &black_array[i];
!= DDI_MAJOR_T_NONE) {
/* is there hardware bound to this drvr */
return (B_TRUE);
}
}
i += 1; /* for loop adds 1, so add only 1 here */
}
}
return (B_FALSE);
}
static boolean_t
blacklisted_smbios(void)
{
char **strptr;
int i;
/* need at least 4 strings for this setting */
if (nblacks < 4) {
return (B_FALSE);
}
return (B_FALSE);
}
for (i = 0; nblacks - i > 3; i++) {
strptr = &black_array[i];
((char *)strptr == '\0' ||
((char *)strptr == '\0' ||
return (B_TRUE);
}
i += 3;
}
}
return (B_FALSE);
}
static boolean_t
blacklisted_acpi(void)
{
if (nblacks == 0) {
return (B_FALSE);
}
}
/*
* Check if system is blacklisted by Intel IOMMU driver
* i.e. should Intel IOMMU be disabled on this system
* Currently a system can be blacklistd based on the
* following bases:
*
* 1. DMAR ACPI table information.
* This information includes things like
* manufacturer and revision number. If rootnex.conf
* has matching info set in its blacklist property
* then Intel IOMMu will be disabled
*
* 2. SMBIOS information
*
* 3. Driver installed - useful if a particular
* driver or hardware is toxic if Intel IOMMU
* is turned on.
*/
static void
blacklist_setup(void)
{
char **string_array;
/*
* Check the rootnex.conf blacklist property.
* Fake up a dev_t since searching the global
* property list needs it
*/
return;
}
/* smallest blacklist criteria works with multiples of 2 */
if (nstrings % 2 != 0) {
"rootnex.conf: number of strings must be a "
"multiple of 2");
return;
}
}
static void
blacklist_destroy(void)
{
if (black_array) {
black_array = NULL;
nblacks = 0;
}
}
/*
* Now set all the fields in the order they are defined
* We do this only as a defensive-coding practice, it is
* not a correctness issue.
*/
static void *
{
/* No more IOMMUs in this segment */
return (NULL);
}
KM_SLEEP);
/*
* the immu_intr_lock mutex is grabbed by the IOMMU
* unit's interrupt handler so we need to use an
* interrupt cookie for the mutex
*/
(void *)ipltospl(IMMU_INTR_IPL));
/* IOMMU regs related */
/* DVMA related */
/* DVMA context related */
/* DVMA domain related */
/* DVMA special device lists */
/* interrupt remapping related */
/* qinv related */
/*
* insert this immu unit into the system-wide list
*/
return (dmar_unit);
}
static void
immu_subsystems_setup(void)
{
int seg;
void *unit_hdl;
"Creating state structures for Intel IOMMU units\n");
sizeof (pgtable_t), 0,
;
}
}
}
/*
* immu_subsystems_startup()
* startup all units that were setup
*/
static void
immu_subsystems_startup(void)
{
/*
* Set IOMMU unit's regs to do
* the actual startup. This will
* set immu->immu_running field
* if the unit is successfully
* started
*/
}
}
/* ################## Intel IOMMU internal interfaces ###################### */
/*
* Internal interfaces for IOMMU code (i.e. not exported to rootnex
* or rest of system)
*/
/*
* ddip can be NULL, in which case we walk up until we find the root dip
* NOTE: We never visit the root dip since its not a hardware node
*/
int
void *arg,
int *lvlp,
{
int level;
int error = DDI_SUCCESS;
/* ddip and immu can be NULL */
/* Hold rdip so that branch is not detached */
error = DDI_FAILURE;
break;
}
break;
}
if (immu_flags & IMMU_FLAGS_DONTPASS) {
break;
}
break;
}
}
if (lvlp)
return (error);
}
/* ######################## Intel IOMMU entry points ####################### */
/*
* immu_init()
* called from rootnex_attach(). setup but don't startup the Intel IOMMU
* This is the first function called in Intel IOMMU code
*/
void
immu_init(void)
{
char *phony_reg = "A thing of beauty is a joy forever";
/* Set some global shorthands that are needed by all of IOMMU code */
/*
* Intel IOMMU only supported only if MMU(CPU) page size is ==
* IOMMU pages size.
*/
/*LINTED*/
if (MMU_PAGESIZE != IMMU_PAGESIZE) {
"MMU page size (%d) is not equal to\n"
"IOMMU page size (%d). "
"Disabling Intel IOMMU. ",
return;
}
/*
* retrieve the Intel IOMMU boot options.
* Do this before parsing immu ACPI table
* as a boot option could potentially affect
* ACPI parsing.
*/
/*
* Check the IOMMU enable boot-option first.
* This is so that we can skip parsing the ACPI table
* if necessary because that may cause problems in
* systems with buggy BIOS or ACPI tables
*/
if (immu_enable == B_FALSE) {
return;
}
/*
* Next, check if the system even has an Intel IOMMU
* We use the presence or absence of the IOMMU ACPI
* table to detect Intel IOMMU.
*/
if (immu_dmar_setup() != DDI_SUCCESS) {
return;
}
/*
* Check blacklists
*/
if (blacklisted_smbios() == B_TRUE) {
return;
}
if (blacklisted_driver() == B_TRUE) {
return;
}
/*
* Read the "raw" DMAR ACPI table to get information
* and convert into a form we can use.
*/
if (immu_dmar_parse() != DDI_SUCCESS) {
return;
}
/*
* now that we have processed the ACPI table
* check if we need to blacklist this system
* based on ACPI info
*/
if (blacklisted_acpi() == B_TRUE) {
return;
}
/*
* Check if system has HW quirks.
*/
/* Now do the rest of the setup */
/*
* Now that the IMMU is setup, create a phony
*/
"rootnex node");
/*NOTREACHED*/
}
immu_setup = B_TRUE;
}
/*
* immu_startup()
* called directly by boot code to startup
* all units of the IOMMU
*/
void
immu_startup(void)
{
/*
* If IOMMU is disabled, do nothing
*/
if (immu_enable == B_FALSE) {
return;
}
if (immu_setup == B_FALSE) {
"skipping IOMU startup");
return;
}
"?Starting Intel IOMMU (dmar) units...\n");
}
/*
* immu_map_sgl()
* called from rootnex_coredma_bindhdl() when Intel
* IOMMU is enabled to build DVMA cookies and map them.
*/
int
{
if (immu_running == B_FALSE) {
return (DDI_DMA_USE_PHYSICAL);
}
}
/*
* immu_unmap_sgl()
* called from rootnex_coredma_unbindhdl(), to unmap DVMA
* cookies and free them
*/
int
{
if (immu_running == B_FALSE) {
return (DDI_DMA_USE_PHYSICAL);
}
}
/*
* Hook to notify IOMMU code of device tree changes
*/
void
immu_device_tree_changed(void)
{
if (immu_setup == B_FALSE) {
return;
}
"does not use device tree updates");
}
/*
* Hook to notify IOMMU code of memory changes
*/
void
{
if (immu_setup == B_FALSE) {
return;
}
}
/*
* immu_quiesce()
* quiesce all units that are running
*/
int
immu_quiesce(void)
{
int ret = DDI_SUCCESS;
if (immu_running == B_FALSE)
return (DDI_SUCCESS);
/* if immu is not running, we dont quiesce */
continue;
/* flush caches */
/*
* Set IOMMU unit's regs to do
* the actual shutdown.
*/
/* if immu is still running, we failed */
ret = DDI_FAILURE;
else
}
if (ret == DDI_SUCCESS) {
}
return (ret);
}
/*
* immu_unquiesce()
* unquiesce all units
*/
int
immu_unquiesce(void)
{
int ret = DDI_SUCCESS;
if (immu_quiesced == B_FALSE)
return (DDI_SUCCESS);
/* if immu was not quiesced, i.e was not running before */
continue;
}
ret = DDI_FAILURE;
continue;
}
/* flush caches before unquiesce */
/*
* Set IOMMU unit's regs to do
* the actual startup. This will
* set immu->immu_regs_running field
* if the unit is successfully
* started
*/
ret = DDI_FAILURE;
} else {
}
}
return (ret);
}
/* ############## END Intel IOMMU entry points ################## */