diff --git a/docs/man/xl.cfg.5.pod.in b/docs/man/xl.cfg.5.pod.in
index 64bed30bce..7e220d0c20 100644
--- a/docs/man/xl.cfg.5.pod.in
+++ b/docs/man/xl.cfg.5.pod.in
@@ -660,6 +660,12 @@ in preference. However, the availability of this option is hardware
 specific. If B<xl info> reports B<virt_caps> containing
 B<iommu_hap_pt_share> then this option may be used.
 
+=item B<unspecified>
+
+The default, which chooses between B<disabled> and B<enabled>
+according to whether passthrough devices are enabled in the config
+file.
+
 =back
 
 =back
diff --git a/tools/libxl/libxl_arch.h b/tools/libxl/libxl_arch.h
index d624159e53..ee6641b3e6 100644
--- a/tools/libxl/libxl_arch.h
+++ b/tools/libxl/libxl_arch.h
@@ -73,6 +73,12 @@ void libxl__arch_domain_build_info_setdefault(libxl__gc *gc,
                                               libxl_domain_build_info *b_info);
 
 _hidden
+int libxl__arch_passthrough_mode_setdefault(libxl__gc *gc,
+                                            uint32_t domid,
+                                            libxl_domain_config *d_config,
+                                            const libxl_physinfo *physinfo);
+
+_hidden
 int libxl__arch_extra_memory(libxl__gc *gc,
                              const libxl_domain_build_info *info,
                              uint64_t *out);
diff --git a/tools/libxl/libxl_arm.c b/tools/libxl/libxl_arm.c
index bf31b9b3ca..2f1ca69431 100644
--- a/tools/libxl/libxl_arm.c
+++ b/tools/libxl/libxl_arm.c
@@ -1191,6 +1191,30 @@ void libxl__arch_domain_build_info_setdefault(libxl__gc *gc,
     libxl_domain_build_info_init_type(b_info, LIBXL_DOMAIN_TYPE_PVH);
 }
 
+int libxl__arch_passthrough_mode_setdefault(libxl__gc *gc,
+                                            uint32_t domid,
+                                            libxl_domain_config *d_config,
+                                            const libxl_physinfo *physinfo)
+{
+    int rc;
+    libxl_domain_create_info *const c_info = &d_config->c_info;
+
+    if (c_info->passthrough == LIBXL_PASSTHROUGH_ENABLED) {
+        c_info->passthrough = LIBXL_PASSTHROUGH_SHARE_PT;
+    }
+
+    if (c_info->passthrough == LIBXL_PASSTHROUGH_SYNC_PT) {
+        LOGD(ERROR, domid,
+             "passthrough=\"sync_pt\" not supported on ARM\n");
+        rc = ERROR_INVAL;
+        goto out;
+    }
+
+    rc = 0;
+ out:
+    return rc;
+}
+
 /*
  * Local variables:
  * mode: C
diff --git a/tools/libxl/libxl_create.c b/tools/libxl/libxl_create.c
index 3d2cb52bf7..bcf4896df6 100644
--- a/tools/libxl/libxl_create.c
+++ b/tools/libxl/libxl_create.c
@@ -57,18 +57,6 @@ int libxl__domain_create_info_setdefault(libxl__gc *gc,
     if (!c_info->ssidref)
         c_info->ssidref = SECINITSID_DOMU;
 
-    if (info->cap_hvm_directio &&
-        (c_info->passthrough == LIBXL_PASSTHROUGH_UNKNOWN)) {
-        c_info->passthrough = ((c_info->type == LIBXL_DOMAIN_TYPE_PV) ||
-                               !info->cap_iommu_hap_pt_share) ?
-            LIBXL_PASSTHROUGH_SYNC_PT : LIBXL_PASSTHROUGH_SHARE_PT;
-    } else if (!info->cap_hvm_directio) {
-        c_info->passthrough = LIBXL_PASSTHROUGH_DISABLED;
-    }
-
-    /* An explicit setting should now have been chosen */
-    assert(c_info->passthrough != LIBXL_PASSTHROUGH_UNKNOWN);
-
     return 0;
 }
 
@@ -587,7 +575,7 @@ int libxl__domain_make(libxl__gc *gc, libxl_domain_config *d_config,
                 libxl_defbool_val(info->oos) ? 0 : XEN_DOMCTL_CDF_oos_off;
         }
 
-        assert(info->passthrough != LIBXL_PASSTHROUGH_UNKNOWN);
+        assert(info->passthrough != LIBXL_PASSTHROUGH_UNSPECIFIED);
         LOG(DETAIL, "passthrough: %s",
             libxl_passthrough_to_string(info->passthrough));
 
@@ -904,6 +892,7 @@ int libxl__domain_config_setdefault(libxl__gc *gc,
     libxl_ctx *ctx = libxl__gc_owner(gc);
     int ret;
     bool pod_enabled = false;
+    libxl_domain_create_info *c_info = &d_config->c_info;
 
     libxl_physinfo physinfo;
     ret = libxl_get_physinfo(CTX, &physinfo);
@@ -975,6 +964,34 @@ int libxl__domain_config_setdefault(libxl__gc *gc,
         goto error_out;
     }
 
+    bool need_pt = d_config->num_pcidevs || d_config->num_dtdevs;
+    if (c_info->passthrough == LIBXL_PASSTHROUGH_UNSPECIFIED) {
+        c_info->passthrough = need_pt
+            ? LIBXL_PASSTHROUGH_ENABLED : LIBXL_PASSTHROUGH_DISABLED;
+    }
+
+    bool iommu_enabled = physinfo.cap_hvm_directio;
+    if (c_info->passthrough != LIBXL_PASSTHROUGH_DISABLED && !iommu_enabled) {
+        LOGD(ERROR, domid,
+             "passthrough not supported on this platform\n");
+        ret = ERROR_INVAL;
+        goto error_out;
+    }
+
+    if (c_info->passthrough == LIBXL_PASSTHROUGH_DISABLED && need_pt) {
+        LOGD(ERROR, domid,
+             "passthrough disabled but devices are specified");
+        ret = ERROR_INVAL;
+        goto error_out;
+    }
+
+    ret = libxl__arch_passthrough_mode_setdefault(gc,domid,d_config,&physinfo);
+    if (ret) goto error_out;
+
+    /* An explicit setting should now have been chosen */
+    assert(c_info->passthrough != LIBXL_PASSTHROUGH_UNSPECIFIED);
+    assert(c_info->passthrough != LIBXL_PASSTHROUGH_ENABLED);
+
     /* If target_memkb is smaller than max_memkb, the subsequent call
      * to libxc when building HVM domain will enable PoD mode.
      */
diff --git a/tools/libxl/libxl_types.idl b/tools/libxl/libxl_types.idl
index 3ac9494b80..2cb5a86b60 100644
--- a/tools/libxl/libxl_types.idl
+++ b/tools/libxl/libxl_types.idl
@@ -264,10 +264,11 @@ libxl_vkb_backend = Enumeration("vkb_backend", [
     ])
 
 libxl_passthrough = Enumeration("passthrough", [
-    (0, "unknown"),
+    (0, "unspecified"),
     (1, "disabled"),
-    (2, "sync_pt"),
-    (3, "share_pt"),
+    (2, "enabled"), # becomes {sync,share}_pt once defaults are evaluated
+    (3, "sync_pt"),
+    (4, "share_pt"),
     ])
 
 #
diff --git a/tools/libxl/libxl_x86.c b/tools/libxl/libxl_x86.c
index c0f88a7eaa..226b712cbd 100644
--- a/tools/libxl/libxl_x86.c
+++ b/tools/libxl/libxl_x86.c
@@ -631,6 +631,47 @@ void libxl__arch_domain_build_info_setdefault(libxl__gc *gc,
     libxl_defbool_setdefault(&b_info->acpi, true);
 }
 
+int libxl__arch_passthrough_mode_setdefault(libxl__gc *gc,
+                                            uint32_t domid,
+                                            libxl_domain_config *d_config,
+                                            const libxl_physinfo *physinfo)
+{
+    int rc;
+    libxl_domain_create_info *const c_info = &d_config->c_info;
+
+    if (c_info->passthrough != LIBXL_PASSTHROUGH_DISABLED &&
+        c_info->type == LIBXL_DOMAIN_TYPE_PVH) {
+        LOGD(ERROR, domid,
+             "passthrough not yet supported for x86 PVH guests\n");
+        rc = ERROR_INVAL;
+        goto out;
+    }
+
+    const char *whynot_pt_share =
+        c_info->type == LIBXL_DOMAIN_TYPE_PV ? "not valid for PV domain" :
+        !physinfo->cap_iommu_hap_pt_share ? "not supported on this platform" :
+        !libxl_defbool_val(d_config->c_info.hap) ?"only valid for HAP guests":
+        NULL;
+
+    if (c_info->passthrough == LIBXL_PASSTHROUGH_ENABLED) {
+        c_info->passthrough = whynot_pt_share
+            ? LIBXL_PASSTHROUGH_SYNC_PT : LIBXL_PASSTHROUGH_SHARE_PT;
+    }
+
+    if (c_info->passthrough == LIBXL_PASSTHROUGH_SHARE_PT && whynot_pt_share) {
+        LOGD(ERROR, domid,
+             "passthrough=\"share_pt\" %s\n",
+             whynot_pt_share);
+        rc = ERROR_INVAL;
+        goto out;
+    }
+
+    rc = 0;
+ out:
+    return rc;
+}
+                                        
+
 /*
  * Local variables:
  * mode: C
diff --git a/tools/xl/xl_parse.c b/tools/xl/xl_parse.c
index 79871c22d0..112f8ee026 100644
--- a/tools/xl/xl_parse.c
+++ b/tools/xl/xl_parse.c
@@ -1222,7 +1222,6 @@ void parse_config_data(const char *config_source,
     int pci_seize = 0;
     int i, e;
     char *kernel_basename;
-    bool iommu_enabled, iommu_hap_pt_share;
 
     libxl_domain_create_info *c_info = &d_config->c_info;
     libxl_domain_build_info *b_info = &d_config->b_info;
@@ -1234,8 +1233,6 @@ void parse_config_data(const char *config_source,
         exit(EXIT_FAILURE);
     }
 
-    iommu_enabled = physinfo.cap_hvm_directio;
-    iommu_hap_pt_share = physinfo.cap_iommu_hap_pt_share;
     libxl_physinfo_dispose(&physinfo);
 
     config= xlu_cfg_init(stderr, config_source);
@@ -1509,67 +1506,13 @@ void parse_config_data(const char *config_source,
         }
     }
 
-    if (xlu_cfg_get_string(config, "passthrough", &buf, 0)) {
-        c_info->passthrough =
-            (d_config->num_pcidevs || d_config->num_dtdevs)
-            ? LIBXL_PASSTHROUGH_UNKNOWN : LIBXL_PASSTHROUGH_DISABLED;
-    } else {
-        if (!strcasecmp("enabled", buf))
-            c_info->passthrough = LIBXL_PASSTHROUGH_UNKNOWN;
-        else {
-            libxl_passthrough o;
-
-            e = libxl_passthrough_from_string(buf, &o);
-            if (e || !strcasecmp("unknown", buf)) {
-                fprintf(stderr,
-                        "ERROR: unknown passthrough option '%s'\n",
-                        buf);
-                exit(-ERROR_FAIL);
-            }
-
-            c_info->passthrough = o;
-        }
-    }
-
-    switch (c_info->passthrough) {
-    case LIBXL_PASSTHROUGH_UNKNOWN:
-        /*
-         * Choose a suitable default. libxl would also do this but
-         * choosing here allows the code calculating 'iommu_memkb'
-         * below make an informed decision.
-         */
-        c_info->passthrough =
-            (c_info->type == LIBXL_DOMAIN_TYPE_PV) || !iommu_hap_pt_share
-            ? LIBXL_PASSTHROUGH_SYNC_PT : LIBXL_PASSTHROUGH_SHARE_PT;
-        break;
-
-    case LIBXL_PASSTHROUGH_DISABLED:
-        if (d_config->num_pcidevs || d_config->num_dtdevs) {
+    if (!xlu_cfg_get_string(config, "passthrough", &buf, 0)) {
+        if (libxl_passthrough_from_string(buf, &c_info->passthrough)) {
             fprintf(stderr,
-                    "ERROR: passthrough disabled but devices are specified\n");
-            exit(-ERROR_FAIL);
-        }
-        break;
-    case LIBXL_PASSTHROUGH_SHARE_PT:
-        if (c_info->type == LIBXL_DOMAIN_TYPE_PV) {
-            fprintf(stderr,
-                    "ERROR: passthrough=\"share_pt\" not valid for PV domain\n");
-            exit(-ERROR_FAIL);
-        } else if (!iommu_hap_pt_share) {
-            fprintf(stderr,
-                    "ERROR: passthrough=\"share_pt\" not supported on this platform\n");
-            exit(-ERROR_FAIL);
+                    "ERROR: unknown passthrough option '%s'\n",
+                    buf);
+            exit(1);
         }
-        break;
-    case LIBXL_PASSTHROUGH_SYNC_PT:
-        break;
-    }
-
-    if ((c_info->passthrough != LIBXL_PASSTHROUGH_DISABLED) &&
-        !iommu_enabled) {
-        fprintf(stderr,
-                "ERROR: passthrough not supported on this platform\n");
-        exit(-ERROR_FAIL);
     }
 
     if (!xlu_cfg_get_long(config, "shadow_memory", &l, 0))
