[x86][iommu] Enable IOMMU by default on most systems

This moves to the IOMMU being enabled whenever we don't know the device
is buggy.

TODO: Fix tests
TODO: Fix plumbing of cmdline flag

Change-Id: Ic1857410566e1d8777e8d9e1c076191d46bf6574
diff --git a/zircon/docs/kernel_cmdline.md b/zircon/docs/kernel_cmdline.md
index ae3e058..9ad70f3 100644
--- a/zircon/docs/kernel_cmdline.md
+++ b/zircon/docs/kernel_cmdline.md
@@ -128,8 +128,8 @@
 
 ## iommu.enable=\<bool>
 
-This option (disabled by default) allows the system to use a hardware IOMMU
-if present.
+This option allows the system to use a hardware IOMMU if present.  By default,
+the IOMMU is used if it is both present and not known to be buggy.
 
 ## kernel.bypass-debuglog=\<bool>
 
diff --git a/zircon/system/dev/board/x86/BUILD.gn b/zircon/system/dev/board/x86/BUILD.gn
index 840b973..fff9119 100644
--- a/zircon/system/dev/board/x86/BUILD.gn
+++ b/zircon/system/dev/board/x86/BUILD.gn
@@ -27,6 +27,22 @@
   cflags_c = [ "-fno-strict-aliasing" ]
 }
 
+source_set("iommu") {
+  visibility = [ ":*" ]
+
+  sources = [
+    "iommu.c",
+  ]
+
+  configs += [ ":config" ]
+
+  deps = [
+    "$zx/system/ulib/ddk",
+    "$zx/system/ulib/zircon",
+    "$zx/third_party/lib/acpica",
+  ]
+}
+
 source_set("smbios") {
   visibility = [ ":*" ]
 
@@ -61,7 +77,6 @@
     "acpi-nswalk.c",
     "debug.c",
     "init.c",
-    "iommu.c",
     "methods.cc",
     "nhlt.c",
     "pciroot.cc",
@@ -82,6 +97,7 @@
   }
   configs += [ ":config" ]
   deps = [
+    ":iommu",
     ":smbios",
     "$zx/system/banjo/ddk.protocol.acpi",
     "$zx/system/banjo/ddk.protocol.hidbus",
@@ -168,10 +184,12 @@
 test("x86-board-test") {
   test_group = "ddk"
   sources = [
+    "iommu-test.cc",
     "smbios-test.cc",
   ]
   configs += [ ":config" ]
   deps = [
+    ":iommu",
     ":smbios",
     "$zx/system/ulib/fdio",
     "$zx/system/ulib/zxtest",
diff --git a/zircon/system/dev/board/x86/acpi-nswalk.c b/zircon/system/dev/board/x86/acpi-nswalk.c
index 787c3bf..cddceb9 100644
--- a/zircon/system/dev/board/x86/acpi-nswalk.c
+++ b/zircon/system/dev/board/x86/acpi-nswalk.c
@@ -688,8 +688,8 @@
     };
 }
 
-zx_status_t acpi_init(bool use_hardware_iommu) {
-    return acpi_to_zx_status(init(use_hardware_iommu));
+zx_status_t acpi_init(bool use_hardware_iommu, const char* board_name, size_t board_name_size) {
+    return acpi_to_zx_status(acpi_internal_init(use_hardware_iommu, board_name, board_name_size));
 }
 
 zx_status_t publish_acpi_devices(zx_device_t* parent, zx_device_t* sys_root, zx_device_t* acpi_root) {
diff --git a/zircon/system/dev/board/x86/include/acpi.h b/zircon/system/dev/board/x86/include/acpi.h
index 7949b59..bb0c476 100644
--- a/zircon/system/dev/board/x86/include/acpi.h
+++ b/zircon/system/dev/board/x86/include/acpi.h
@@ -10,7 +10,7 @@
 
 __BEGIN_CDECLS
 
-zx_status_t acpi_init(bool use_iommu);
+zx_status_t acpi_init(bool use_hardware_iommu, const char* board_name, size_t board_name_size);
 zx_status_t publish_acpi_devices(zx_device_t* parent, zx_device_t* sys_root, zx_device_t* acpi_root);
 zx_status_t acpi_suspend(uint32_t flags);
 
diff --git a/zircon/system/dev/board/x86/include/init.h b/zircon/system/dev/board/x86/include/init.h
index 48e1dec..0602e40 100644
--- a/zircon/system/dev/board/x86/include/init.h
+++ b/zircon/system/dev/board/x86/include/init.h
@@ -6,4 +6,5 @@
 
 #include <acpica/acpi.h>
 
-ACPI_STATUS init(bool use_hardware_iommu);
+ACPI_STATUS acpi_internal_init(bool use_hardware_iommu, const char* board_name,
+                               size_t board_name_size);
diff --git a/zircon/system/dev/board/x86/include/iommu.h b/zircon/system/dev/board/x86/include/iommu.h
index 9c4a20d..68e76d8 100644
--- a/zircon/system/dev/board/x86/include/iommu.h
+++ b/zircon/system/dev/board/x86/include/iommu.h
@@ -13,7 +13,8 @@
 // the IOMMU manager will be left in a well-defined empty state, and
 // iommu_manager_iommu_for_bdf() can still succeed (yielding dummy IOMMU
 // handles).
-zx_status_t iommu_manager_init(bool use_hardware_iommu);
+zx_status_t iommu_manager_init(bool use_hardware_iommu, const char* board_name,
+                               size_t board_name_size);
 
 // Returns a handle to the IOMMU that is responsible for the given BDF. The
 // returned handle is borrowed from the iommu_manager.  The caller
@@ -24,4 +25,7 @@
 // the iommu_manager.
 zx_status_t iommu_manager_get_dummy_iommu(zx_handle_t* iommu);
 
+// Returns true if the IOMMU should be used.  Exposed here for use in tests.
+bool iommu_use_hardware_if_present(const char* board_name, size_t board_name_size);
+
 __END_CDECLS
diff --git a/zircon/system/dev/board/x86/init.c b/zircon/system/dev/board/x86/init.c
index d5e8468..b391431 100644
--- a/zircon/system/dev/board/x86/init.c
+++ b/zircon/system/dev/board/x86/init.c
@@ -105,7 +105,8 @@
     return AE_OK; // We want to keep going even if we bailed out
 }
 
-ACPI_STATUS init(bool use_hardware_iommu) {
+ACPI_STATUS acpi_internal_init(bool use_hardware_iommu, const char* board_name,
+                               size_t board_name_size) {
     // This sequence is described in section 10.1.2.1 (Full ACPICA Initialization)
     // of the ACPICA developer's reference.
     ACPI_STATUS status = AcpiInitializeSubsystem();
@@ -144,7 +145,7 @@
         return status;
     }
 
-    zx_status_t zx_status = iommu_manager_init(use_hardware_iommu);
+    zx_status_t zx_status = iommu_manager_init(use_hardware_iommu, board_name, board_name_size);
     if (zx_status != ZX_OK) {
         zxlogf(INFO, "acpi: Failed to initialize IOMMU manager\n");
     }
diff --git a/zircon/system/dev/board/x86/iommu-test.cc b/zircon/system/dev/board/x86/iommu-test.cc
new file mode 100644
index 0000000..fdaf91f
--- /dev/null
+++ b/zircon/system/dev/board/x86/iommu-test.cc
@@ -0,0 +1,47 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <zxtest/zxtest.h>
+#include "iommu.h"
+
+TEST(IommuTestCase, EnableControlEnvFlag) {
+    const char board_name[] = "fake board name";
+
+    // By default, we should use the IOMMU
+    ASSERT_NULL(getenv("iommu.enable"));
+    EXPECT_TRUE(iommu_use_hardware_if_present(board_name, sizeof(board_name)));
+
+    for (auto value : {"0", "false", "off"}) {
+        setenv("iommu.enable", value, true);
+        EXPECT_FALSE(iommu_use_hardware_if_present(board_name, sizeof(board_name)));
+    }
+
+    for (auto value : {"1", "true", "on"}) {
+        setenv("iommu.enable", value, true);
+        EXPECT_TRUE(iommu_use_hardware_if_present(board_name, sizeof(board_name)));
+    }
+    unsetenv("iommu.enable");
+}
+
+
+TEST(IommuTestCase, EnableControlBuggyDevice) {
+    // This device name is marked as buggy.
+    const char board_name[] = "NUC6i3SYB";
+
+    // By default, we respect the list of buggy devices
+    ASSERT_NULL(getenv("iommu.enable"));
+    EXPECT_FALSE(iommu_use_hardware_if_present(board_name, sizeof(board_name)));
+
+    // If a flag setting is given, use that as an override
+    for (auto value : {"0", "false", "off"}) {
+        setenv("iommu.enable", value, true);
+        EXPECT_FALSE(iommu_use_hardware_if_present(board_name, sizeof(board_name)));
+    }
+
+    for (auto value : {"1", "true", "on"}) {
+        setenv("iommu.enable", value, true);
+        EXPECT_TRUE(iommu_use_hardware_if_present(board_name, sizeof(board_name)));
+    }
+    unsetenv("iommu.enable");
+}
diff --git a/zircon/system/dev/board/x86/iommu.c b/zircon/system/dev/board/x86/iommu.c
index bd06a78..a6ee542 100644
--- a/zircon/system/dev/board/x86/iommu.c
+++ b/zircon/system/dev/board/x86/iommu.c
@@ -423,7 +423,39 @@
     return status;
 }
 
-zx_status_t iommu_manager_init(bool use_hardware_iommu) {
+bool iommu_use_hardware_if_present(bool use_hardware_iommu, const char* board_name,
+                                   size_t board_name_size) {
+    const char* const kKnownBuggyDevices[] = {
+        // Intel NUC6i3SYB.  The GPU hangs when the IOMMU is enabled.
+        "NUC6i3SYB",
+        // Acer Switch Alpha 12 has a buggy RMRR entries for the XHCI controller
+        "Switch SA5-271",
+        NULL,
+    };
+
+    // Check if we're running on a device that is known to have an IOMMU that
+    // interacts with us poorly.
+    bool known_buggy = false;
+    for (const char* const* buggy_board = kKnownBuggyDevices; *buggy_board != NULL; ++buggy_board) {
+        // Note that board_name_size includes the trailing null
+        if (!strncmp(board_name, *buggy_board, board_name_size)) {
+            known_buggy = true;
+            break;
+        }
+    }
+
+    const char* value = getenv("iommu.enable");
+    if (value == NULL) {
+        return use_iommu; // Default to whatever we detected
+    } else if (!strcmp(value, "0") || !strcmp(value, "false") || !strcmp(value, "off")) {
+        return false;
+    } else {
+        return true;
+    }
+}
+
+zx_status_t iommu_manager_init(bool use_hardware_iommu, const char* board_name,
+                               size_t board_name_size) {
     int err = mtx_init(&iommu_mgr.lock, mtx_plain);
     if (err != thrd_success) {
         return ZX_ERR_INTERNAL;
@@ -441,8 +473,8 @@
         return status;
     }
 
-    if (!use_hardware_iommu) {
-        zxlogf(INFO, "acpi-bus: not using IOMMU\n");
+    if (!iommu_use_hardware_if_present(use_hardware_iommu, board_name, board_name_size)) {
+        zxlogf(INFO, "acpi-bus: not using IOMMU (if present)\n");
         return ZX_OK;
     }
 
diff --git a/zircon/system/dev/board/x86/x86.c b/zircon/system/dev/board/x86/x86.c
index 02fd905..2bdfed2 100644
--- a/zircon/system/dev/board/x86/x86.c
+++ b/zircon/system/dev/board/x86/x86.c
@@ -68,6 +68,21 @@
         return ZX_ERR_NOT_SUPPORTED;
     }
 
+    char board_name[fuchsia_sysinfo_SYSINFO_BOARD_NAME_LEN + 1];
+    size_t board_name_actual = 0;
+    status = smbios_get_board_name(board_name, sizeof(board_name), &board_name_actual);
+    if (status != ZX_OK) {
+        if (status == ZX_ERR_BUFFER_TOO_SMALL) {
+            zxlogf(INFO, "acpi: smbios board name too big for sysinfo\n");
+        } else if (status != ZX_ERR_NOT_FOUND) {
+            zxlogf(ERROR, "acpi: smbios board name could not be read: %s\n",
+                   zx_status_get_string(status));
+        }
+        strcpy(board_name, "pc");
+        board_name_actual = strlen(board_name) + 1;
+    }
+    zxlogf(INFO, "acpi: board_name='%s'\n", board_name);
+
     bool use_iommu = true;
     size_t actual;
     status = device_get_metadata(parent, DEVICE_METADATA_BOARD_PRIVATE, &use_iommu,
@@ -78,7 +93,7 @@
     }
 
     // Do ACPI init.
-    status = acpi_init(use_iommu);
+    status = acpi_init(use_iommu, board_name, board_name_actual);
     if (status != ZX_OK) {
         free(x86);
         zxlogf(ERROR, "%s: failed to initialize ACPI %d \n", __func__, status);
@@ -111,20 +126,6 @@
         return status;
     }
 
-    char board_name[fuchsia_sysinfo_SYSINFO_BOARD_NAME_LEN + 1];
-    size_t board_name_actual = 0;
-    status = smbios_get_board_name(board_name, sizeof(board_name), &board_name_actual);
-    if (status != ZX_OK) {
-        if (status == ZX_ERR_BUFFER_TOO_SMALL) {
-            zxlogf(INFO, "acpi: smbios board name too big for sysinfo\n");
-        } else if (status != ZX_ERR_NOT_FOUND) {
-            zxlogf(ERROR, "acpi: smbios board name could not be read: %s\n",
-                   zx_status_get_string(status));
-        }
-        strcpy(board_name, "pc");
-        board_name_actual = strlen(board_name) + 1;
-    }
-
     // Publish board name to sysinfo driver
     status = device_publish_metadata(acpi_root, "/dev/misc/sysinfo", DEVICE_METADATA_BOARD_NAME,
                                      board_name, board_name_actual);