[PATCH v5 13/15] x86/domain: enable per-domain I/O port bitmaps

dmukhin@xen.org posted 15 patches 2 months ago
There is a newer version of this series
[PATCH v5 13/15] x86/domain: enable per-domain I/O port bitmaps
Posted by dmukhin@xen.org 2 months ago
From: Denis Mukhin <dmukhin@ford.com> 

Current design enables all HVM domains share the same I/O port bitmap.

It is necessary for domains crafting its own I/O port address space depending
on the user configuration.

Ensure NS16550 emulator does not share I/O ports with the physical I/O ports,
which is essential for emulation in PVH hwdom case (dom0).

Not a functional change.

Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch 
- Link tp v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-4-dmukhin@ford.com/
---
 xen/arch/x86/Makefile                    |   1 +
 xen/arch/x86/dom0_build.c                | 111 +--------------
 xen/arch/x86/hvm/hvm.c                   |  35 +----
 xen/arch/x86/hvm/nestedhvm.c             |   8 +-
 xen/arch/x86/hvm/quirks.c                |   3 -
 xen/arch/x86/hvm/svm/nestedsvm.c         |   2 +-
 xen/arch/x86/hvm/vmx/vvmx.c              |   4 +-
 xen/arch/x86/include/asm/hvm/nestedhvm.h |   3 +-
 xen/arch/x86/include/asm/hvm/support.h   |   2 -
 xen/arch/x86/include/asm/iocap.h         |   2 +
 xen/arch/x86/ioport.c                    | 163 +++++++++++++++++++++++
 xen/arch/x86/pv/dom0_build.c             |   4 +
 xen/common/emul/vuart/ns16x50.c          |  11 ++
 13 files changed, 200 insertions(+), 149 deletions(-)
 create mode 100644 xen/arch/x86/ioport.c

diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile
index 9d67ea7cd4a8..5726ecc180eb 100644
--- a/xen/arch/x86/Makefile
+++ b/xen/arch/x86/Makefile
@@ -44,6 +44,7 @@ obj-y += msi.o
 obj-y += msr.o
 obj-$(CONFIG_INDIRECT_THUNK) += indirect-thunk.o
 obj-$(CONFIG_RETURN_THUNK) += indirect-thunk.o
+obj-y += ioport.o
 obj-$(CONFIG_PV) += ioport_emulate.o
 obj-y += irq.o
 obj-$(CONFIG_KEXEC) += machine_kexec.o
diff --git a/xen/arch/x86/dom0_build.c b/xen/arch/x86/dom0_build.c
index 0b467fd4a4fc..26202b33345c 100644
--- a/xen/arch/x86/dom0_build.c
+++ b/xen/arch/x86/dom0_build.c
@@ -298,9 +298,6 @@ int __init parse_arch_dom0_param(const char *s, const char *e)
     return 0;
 }
 
-static char __initdata opt_dom0_ioports_disable[200] = "";
-string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
-
 static bool __initdata ro_hpet = true;
 boolean_param("ro-hpet", ro_hpet);
 
@@ -433,122 +430,20 @@ unsigned long __init dom0_compute_nr_pages(
     return nr_pages;
 }
 
-static void __init process_dom0_ioports_disable(struct domain *dom0)
-{
-    unsigned long io_from, io_to;
-    char *t, *s = opt_dom0_ioports_disable;
-    const char *u;
-
-    if ( *s == '\0' )
-        return;
-
-    while ( (t = strsep(&s, ",")) != NULL )
-    {
-        io_from = simple_strtoul(t, &u, 16);
-        if ( u == t )
-        {
-        parse_error:
-            printk("Invalid ioport range <%s> "
-                   "in dom0_ioports_disable, skipping\n", t);
-            continue;
-        }
-
-        if ( *u == '\0' )
-            io_to = io_from;
-        else if ( *u == '-' )
-            io_to = simple_strtoul(u + 1, &u, 16);
-        else
-            goto parse_error;
-
-        if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
-            goto parse_error;
-
-        printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
-            io_from, io_to);
-
-        if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
-            BUG();
-    }
-}
-
+/* Modify I/O memory access permissions. */
 int __init dom0_setup_permissions(struct domain *d)
 {
     unsigned long mfn;
-    unsigned int i, offs;
-    int rc;
+    unsigned int i;
+    int rc = 0;
 
     if ( pv_shim )
         return 0;
 
-    /* The hardware domain is initially permitted full I/O capabilities. */
-    rc = ioports_permit_access(d, 0, 0xFFFF);
     rc |= iomem_permit_access(d, 0UL,
                               PFN_DOWN(1UL << domain_max_paddr_bits(d)) - 1);
     rc |= irqs_permit_access(d, 1, nr_irqs_gsi - 1);
 
-    /* Modify I/O port access permissions. */
-
-    for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
-          offs <= i8259A_alias_mask; offs += i )
-    {
-        if ( offs & ~i8259A_alias_mask )
-            continue;
-        /* Master Interrupt Controller (PIC). */
-        rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
-        /* Slave Interrupt Controller (PIC). */
-        rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
-    }
-
-    /* ELCR of both PICs. */
-    rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
-
-    /* Interval Timer (PIT). */
-    for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
-          offs <= pit_alias_mask; offs += i )
-        if ( !(offs & ~pit_alias_mask) )
-            rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
-
-    /* PIT Channel 2 / PC Speaker Control. */
-    rc |= ioports_deny_access(d, 0x61, 0x61);
-
-    /* INIT# and alternative A20M# control. */
-    rc |= ioports_deny_access(d, 0x92, 0x92);
-
-    /* IGNNE# control. */
-    rc |= ioports_deny_access(d, 0xF0, 0xF0);
-
-    /* ACPI PM Timer. */
-    if ( pmtmr_ioport )
-        rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
-
-    /* Reset control. */
-    rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
-
-    /* PCI configuration space (NB. 0xCF8 has special treatment). */
-    rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
-
-#ifdef CONFIG_HVM
-    if ( is_hvm_domain(d) )
-    {
-        /* ISA DMA controller, channels 0-3 (incl possible aliases). */
-        rc |= ioports_deny_access(d, 0x00, 0x1F);
-        /* ISA DMA controller, page registers (incl various reserved ones). */
-        rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
-        /* ISA DMA controller, channels 4-7 (incl usual aliases). */
-        rc |= ioports_deny_access(d, 0xC0, 0xDF);
-
-        /* HVM debug console IO port. */
-        rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
-                                  XEN_HVM_DEBUGCONS_IOPORT);
-        if ( amd_acpi_c1e_quirk )
-            rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
-    }
-#endif
-    /* Command-line I/O ranges. */
-    process_dom0_ioports_disable(d);
-
-    /* Modify I/O memory access permissions. */
-
     /* Local APIC. */
     if ( mp_lapic_addr != 0 )
     {
diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
index 26760cf995df..12736fc61c11 100644
--- a/xen/arch/x86/hvm/hvm.c
+++ b/xen/arch/x86/hvm/hvm.c
@@ -51,6 +51,7 @@
 #include <asm/hvm/vm_event.h>
 #include <asm/hvm/vpt.h>
 #include <asm/i387.h>
+#include <asm/iocap.h>
 #include <asm/mc146818rtc.h>
 #include <asm/mce.h>
 #include <asm/monitor.h>
@@ -81,14 +82,6 @@ integer_param("hvm_debug", opt_hvm_debug_level);
 
 struct hvm_function_table __ro_after_init hvm_funcs;
 
-/*
- * The I/O permission bitmap is globally shared by all HVM guests except
- * the hardware domain which needs a more permissive one.
- */
-#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
-unsigned long __section(".bss.page_aligned") __aligned(PAGE_SIZE)
-    hvm_io_bitmap[HVM_IOBITMAP_SIZE / BYTES_PER_LONG];
-
 /* Xen command-line option to enable HAP */
 static bool __initdata opt_hap_enabled = true;
 boolean_param("hap", opt_hap_enabled);
@@ -205,15 +198,6 @@ static int __init cf_check hvm_enable(void)
     if ( opt_hvm_fep )
         warning_add(warning_hvm_fep);
 
-    /*
-     * Allow direct access to the PC debug ports 0x80 and 0xed (they are
-     * often used for I/O delays, but the vmexits simply slow things down).
-     */
-    memset(hvm_io_bitmap, ~0, sizeof(hvm_io_bitmap));
-    if ( hvm_port80_allowed )
-        __clear_bit(0x80, hvm_io_bitmap);
-    __clear_bit(0xed, hvm_io_bitmap);
-
     register_cpu_notifier(&cpu_nfb);
 
     return 0;
@@ -645,19 +629,12 @@ int hvm_domain_initialise(struct domain *d,
 
     rwlock_init(&d->arch.hvm.pl_time->pt_migrate);
 
-    /* Set the default IO Bitmap. */
-    if ( is_hardware_domain(d) )
+    rc = ioports_setup_access(d);
+    if ( rc )
     {
-        d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
-        if ( d->arch.hvm.io_bitmap == NULL )
-        {
-            rc = -ENOMEM;
-            goto fail1;
-        }
-        memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
+        printk("%pd failed to setup I/O bitmap: %d\n", d, rc);
+        goto fail1;
     }
-    else
-        d->arch.hvm.io_bitmap = hvm_io_bitmap;
 
     register_g2m_portio_handler(d);
     register_vpci_portio_handler(d);
@@ -684,6 +661,8 @@ int hvm_domain_initialise(struct domain *d,
         break;
     }
 
+    BUG_ON(!d->arch.ioport_caps);
+
     vpic_init(d);
 
     rc = vioapic_init(d);
diff --git a/xen/arch/x86/hvm/nestedhvm.c b/xen/arch/x86/hvm/nestedhvm.c
index bddd77d8109b..d4e03123d910 100644
--- a/xen/arch/x86/hvm/nestedhvm.c
+++ b/xen/arch/x86/hvm/nestedhvm.c
@@ -107,7 +107,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
  * The users of the bitmap patterns are in SVM/VMX specific code.
  *
  * bitmap        port 0x80  port 0xed
- * hvm_io_bitmap cleared    cleared
+ * hvm.io_bitmap cleared    cleared
  * iomap[0]      cleared    set
  * iomap[1]      set        cleared
  * iomap[2]      set        set
@@ -115,7 +115,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
 
 static int __init cf_check nestedhvm_setup(void)
 {
-    /* Same format and size as hvm_io_bitmap (Intel needs only 2 pages). */
+    /* Same format and size as hvm.io_bitmap (Intel needs only 2 pages). */
     unsigned nr = cpu_has_vmx ? 2 : 3;
     unsigned int i, order = get_order_from_pages(nr);
 
@@ -165,7 +165,7 @@ static int __init cf_check nestedhvm_setup(void)
 __initcall(nestedhvm_setup);
 
 unsigned long *
-nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
+nestedhvm_vcpu_iomap_get(struct vcpu *v, bool ioport_80, bool ioport_ed)
 {
     int i;
 
@@ -174,7 +174,7 @@ nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
 
     if (ioport_80 == 0) {
         if (ioport_ed == 0)
-            return hvm_io_bitmap;
+            return v->domain->arch.hvm.io_bitmap;
         i = 0;
     } else {
         if (ioport_ed == 0)
diff --git a/xen/arch/x86/hvm/quirks.c b/xen/arch/x86/hvm/quirks.c
index 9202f5a47fe9..f4d95441fcff 100644
--- a/xen/arch/x86/hvm/quirks.c
+++ b/xen/arch/x86/hvm/quirks.c
@@ -73,9 +73,6 @@ static int __init cf_check check_port80(void)
 
     dmi_check_system(hvm_no_port80_dmi_table);
 
-    if ( !hvm_port80_allowed )
-        __set_bit(0x80, hvm_io_bitmap);
-
     return 0;
 }
 __initcall(check_port80);
diff --git a/xen/arch/x86/hvm/svm/nestedsvm.c b/xen/arch/x86/hvm/svm/nestedsvm.c
index dc2b6a42534a..cc8500b61665 100644
--- a/xen/arch/x86/hvm/svm/nestedsvm.c
+++ b/xen/arch/x86/hvm/svm/nestedsvm.c
@@ -381,7 +381,7 @@ static int nsvm_vmrun_permissionmap(struct vcpu *v, bool viopm)
         hvm_unmap_guest_frame(ns_viomap, 0);
     }
 
-    svm->ns_iomap = nestedhvm_vcpu_iomap_get(ioport_80, ioport_ed);
+    svm->ns_iomap = nestedhvm_vcpu_iomap_get(v, ioport_80, ioport_ed);
 
     nv->nv_ioport80 = ioport_80;
     nv->nv_ioportED = ioport_ed;
diff --git a/xen/arch/x86/hvm/vmx/vvmx.c b/xen/arch/x86/hvm/vmx/vvmx.c
index e4f3a5fe4c71..4da3e6e90e6c 100644
--- a/xen/arch/x86/hvm/vmx/vvmx.c
+++ b/xen/arch/x86/hvm/vmx/vvmx.c
@@ -554,7 +554,7 @@ unsigned long *_shadow_io_bitmap(struct vcpu *v)
     port80 = bitmap[0x80 >> 3] & (1 << (0x80 & 0x7)) ? 1 : 0;
     portED = bitmap[0xed >> 3] & (1 << (0xed & 0x7)) ? 1 : 0;
 
-    return nestedhvm_vcpu_iomap_get(port80, portED);
+    return nestedhvm_vcpu_iomap_get(v, port80, portED);
 }
 
 static void update_msrbitmap(struct vcpu *v, uint32_t shadow_ctrl)
@@ -622,7 +622,7 @@ void nvmx_update_exec_control(struct vcpu *v, u32 host_cntrl)
              * L1 VMM doesn't intercept IO instruction.
              * Use host configuration and reset IO_BITMAP
              */
-            bitmap = hvm_io_bitmap;
+            bitmap = v->domain->arch.hvm.io_bitmap;
         }
         else {
             /* use IO bitmap */
diff --git a/xen/arch/x86/include/asm/hvm/nestedhvm.h b/xen/arch/x86/include/asm/hvm/nestedhvm.h
index ea2c1bc328c7..d691ccb07dd6 100644
--- a/xen/arch/x86/include/asm/hvm/nestedhvm.h
+++ b/xen/arch/x86/include/asm/hvm/nestedhvm.h
@@ -50,7 +50,8 @@ int nestedhvm_hap_nested_page_fault(struct vcpu *v, paddr_t *L2_gpa,
                                     struct npfec npfec);
 
 /* IO permission map */
-unsigned long *nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed);
+unsigned long *nestedhvm_vcpu_iomap_get(struct vcpu *v,
+                                        bool ioport_80, bool ioport_ed);
 
 /* Misc */
 #define nestedhvm_paging_mode_hap(v) (!!nhvm_vmcx_hap_enabled(v))
diff --git a/xen/arch/x86/include/asm/hvm/support.h b/xen/arch/x86/include/asm/hvm/support.h
index 2a7ba36af06f..7e36d00cc188 100644
--- a/xen/arch/x86/include/asm/hvm/support.h
+++ b/xen/arch/x86/include/asm/hvm/support.h
@@ -41,8 +41,6 @@ extern unsigned int opt_hvm_debug_level;
 #define HVM_DBG_LOG(level, _f, _a...) do {} while (0)
 #endif
 
-extern unsigned long hvm_io_bitmap[];
-
 enum hvm_translation_result {
     HVMTRANS_okay,
     HVMTRANS_bad_linear_to_gfn,
diff --git a/xen/arch/x86/include/asm/iocap.h b/xen/arch/x86/include/asm/iocap.h
index f948b7186e95..1083f6171cf7 100644
--- a/xen/arch/x86/include/asm/iocap.h
+++ b/xen/arch/x86/include/asm/iocap.h
@@ -22,6 +22,8 @@
 #define cache_flush_permitted(d) \
     (has_arch_io_resources(d) || has_arch_pdevs(d))
 
+int ioports_setup_access(struct domain *d);
+
 static inline int ioports_permit_access(struct domain *d, unsigned long s,
                                         unsigned long e)
 {
diff --git a/xen/arch/x86/ioport.c b/xen/arch/x86/ioport.c
new file mode 100644
index 000000000000..dbcd52d37a4f
--- /dev/null
+++ b/xen/arch/x86/ioport.c
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Guest I/O port address space configuration.
+ *
+ * Copyright 2025 Ford Motor Company
+ */
+
+#include <xen/domain.h>
+#include <xen/param.h>
+
+#include <asm/amd.h>
+#include <asm/acpi.h>
+#include <asm/io-ports.h>
+#include <asm/iocap.h>
+#include <asm/pv/shim.h>
+#include <asm/setup.h>
+
+static char __initdata opt_dom0_ioports_disable[200] = "";
+string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
+
+/*
+ * The I/O permission bitmap size.
+ * See: comment in nestedhvm_setup()
+ */
+#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
+
+/* Hide user-defined I/O ports from the guest OS. */
+static void process_dom0_ioports_disable(struct domain *dom0)
+{
+    unsigned long io_from, io_to;
+    char *t, *s = opt_dom0_ioports_disable;
+    const char *u;
+
+    if ( *s == '\0' )
+        return;
+
+    while ( (t = strsep(&s, ",")) != NULL )
+    {
+        io_from = simple_strtoul(t, &u, 16);
+        if ( u == t )
+        {
+        parse_error:
+            printk("Invalid ioport range <%s> "
+                   "in dom0_ioports_disable, skipping\n", t);
+            continue;
+        }
+
+        if ( *u == '\0' )
+            io_to = io_from;
+        else if ( *u == '-' )
+            io_to = simple_strtoul(u + 1, &u, 16);
+        else
+            goto parse_error;
+
+        if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
+            goto parse_error;
+
+        printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
+            io_from, io_to);
+
+        if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
+            BUG();
+    }
+}
+
+/* Set the default IO Bitmap. */
+int ioports_setup_access(struct domain *d)
+{
+    unsigned int i, offs;
+    int rc;
+
+    if ( pv_shim )
+        return 0;
+
+#ifdef CONFIG_HVM
+    d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
+    if ( d->arch.hvm.io_bitmap == NULL )
+        return -ENOMEM;
+
+    memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
+
+    if ( !is_hardware_domain(d) )
+    {
+        /*
+         * Allow direct access to the PC debug ports 0x80 and 0xed (they are
+         * often used for I/O delays, but the vmexits simply slow things down).
+         */
+        if ( hvm_port80_allowed )
+            __clear_bit(0x80, d->arch.hvm.io_bitmap);
+
+        __clear_bit(0xed, d->arch.hvm.io_bitmap);
+
+        return 0;
+    }
+#endif
+
+    /* The hardware domain is initially permitted full I/O capabilities. */
+    rc = ioports_permit_access(d, 0, 0xFFFF);
+
+    /* Modify I/O port access permissions. */
+
+    for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
+          offs <= i8259A_alias_mask; offs += i )
+    {
+        if ( offs & ~i8259A_alias_mask )
+            continue;
+        /* Master Interrupt Controller (PIC). */
+        rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
+        /* Slave Interrupt Controller (PIC). */
+        rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
+    }
+
+    /* ELCR of both PICs. */
+    rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
+
+    /* Interval Timer (PIT). */
+    for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
+          offs <= pit_alias_mask; offs += i )
+        if ( !(offs & ~pit_alias_mask) )
+            rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
+
+    /* PIT Channel 2 / PC Speaker Control. */
+    rc |= ioports_deny_access(d, 0x61, 0x61);
+
+    /* INIT# and alternative A20M# control. */
+    rc |= ioports_deny_access(d, 0x92, 0x92);
+
+    /* IGNNE# control. */
+    rc |= ioports_deny_access(d, 0xF0, 0xF0);
+
+    /* ACPI PM Timer. */
+    if ( pmtmr_ioport )
+        rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
+
+    /* Reset control. */
+    rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
+
+    /* PCI configuration space (NB. 0xCF8 has special treatment). */
+    rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
+
+#ifdef CONFIG_HVM
+    if ( is_hvm_domain(d) )
+    {
+        /* ISA DMA controller, channels 0-3 (incl possible aliases). */
+        rc |= ioports_deny_access(d, 0x00, 0x1F);
+        /* ISA DMA controller, page registers (incl various reserved ones). */
+        rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
+        /* ISA DMA controller, channels 4-7 (incl usual aliases). */
+        rc |= ioports_deny_access(d, 0xC0, 0xDF);
+
+        /* HVM debug console IO port. */
+        rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
+                                  XEN_HVM_DEBUGCONS_IOPORT);
+        if ( amd_acpi_c1e_quirk )
+            rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
+    }
+#endif
+
+    /* Command-line I/O ranges. */
+    process_dom0_ioports_disable(d);
+
+    return rc;
+}
diff --git a/xen/arch/x86/pv/dom0_build.c b/xen/arch/x86/pv/dom0_build.c
index 21158ce1812e..2b8b4d869ee7 100644
--- a/xen/arch/x86/pv/dom0_build.c
+++ b/xen/arch/x86/pv/dom0_build.c
@@ -17,6 +17,7 @@
 #include <asm/bootinfo.h>
 #include <asm/bzimage.h>
 #include <asm/dom0_build.h>
+#include <asm/iocap.h>
 #include <asm/guest.h>
 #include <asm/page.h>
 #include <asm/pv/mm.h>
@@ -1033,6 +1034,9 @@ static int __init dom0_construct(const struct boot_domain *bd)
     if ( test_bit(XENFEAT_supervisor_mode_kernel, parms.f_required) )
         panic("Dom0 requires supervisor-mode execution\n");
 
+    rc = ioports_setup_access(d);
+    BUG_ON(rc != 0);
+
     rc = dom0_setup_permissions(d);
     BUG_ON(rc != 0);
 
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 5c1be854b544..8860f25ffdeb 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -780,9 +780,20 @@ static int ns16x50_init(void *arg)
     struct vuart_ns16x50 *vdev = arg;
     const struct vuart_info *info = vdev->info;
     struct domain *d = vdev->owner;
+    int rc;
 
     ASSERT(vdev);
 
+    /* Disallow sharing physical I/O port */
+    rc = ioports_deny_access(d, info->base_addr,
+                             info->base_addr + info->size - 1);
+    if ( rc )
+    {
+        ns16x50_err(info, " virtual I/O port range [0x%04lx"PRIx64"..0x%04lx"PRIx64"]: conflict w/ physical range\n",
+                    info->base_addr, info->base_addr + info->size - 1);
+        return rc;
+    }
+
     /* NB: report 115200 baud rate. */
     vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
     vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
-- 
2.51.0
Re: [PATCH v5 13/15] x86/domain: enable per-domain I/O port bitmaps
Posted by Stefano Stabellini 2 months ago
On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> From: Denis Mukhin <dmukhin@ford.com> 
> 
> Current design enables all HVM domains share the same I/O port bitmap.
> 
> It is necessary for domains crafting its own I/O port address space depending
> on the user configuration.
> 
> Ensure NS16550 emulator does not share I/O ports with the physical I/O ports,
> which is essential for emulation in PVH hwdom case (dom0).
> 
> Not a functional change.
> 
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v4:
> - new patch 
> - Link tp v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-4-dmukhin@ford.com/
> ---
>  xen/arch/x86/Makefile                    |   1 +
>  xen/arch/x86/dom0_build.c                | 111 +--------------
>  xen/arch/x86/hvm/hvm.c                   |  35 +----
>  xen/arch/x86/hvm/nestedhvm.c             |   8 +-
>  xen/arch/x86/hvm/quirks.c                |   3 -
>  xen/arch/x86/hvm/svm/nestedsvm.c         |   2 +-
>  xen/arch/x86/hvm/vmx/vvmx.c              |   4 +-
>  xen/arch/x86/include/asm/hvm/nestedhvm.h |   3 +-
>  xen/arch/x86/include/asm/hvm/support.h   |   2 -
>  xen/arch/x86/include/asm/iocap.h         |   2 +
>  xen/arch/x86/ioport.c                    | 163 +++++++++++++++++++++++
>  xen/arch/x86/pv/dom0_build.c             |   4 +
>  xen/common/emul/vuart/ns16x50.c          |  11 ++
>  13 files changed, 200 insertions(+), 149 deletions(-)
>  create mode 100644 xen/arch/x86/ioport.c
> 
> diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile
> index 9d67ea7cd4a8..5726ecc180eb 100644
> --- a/xen/arch/x86/Makefile
> +++ b/xen/arch/x86/Makefile
> @@ -44,6 +44,7 @@ obj-y += msi.o
>  obj-y += msr.o
>  obj-$(CONFIG_INDIRECT_THUNK) += indirect-thunk.o
>  obj-$(CONFIG_RETURN_THUNK) += indirect-thunk.o
> +obj-y += ioport.o
>  obj-$(CONFIG_PV) += ioport_emulate.o
>  obj-y += irq.o
>  obj-$(CONFIG_KEXEC) += machine_kexec.o
> diff --git a/xen/arch/x86/dom0_build.c b/xen/arch/x86/dom0_build.c
> index 0b467fd4a4fc..26202b33345c 100644
> --- a/xen/arch/x86/dom0_build.c
> +++ b/xen/arch/x86/dom0_build.c
> @@ -298,9 +298,6 @@ int __init parse_arch_dom0_param(const char *s, const char *e)
>      return 0;
>  }
>  
> -static char __initdata opt_dom0_ioports_disable[200] = "";
> -string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
> -
>  static bool __initdata ro_hpet = true;
>  boolean_param("ro-hpet", ro_hpet);
>  
> @@ -433,122 +430,20 @@ unsigned long __init dom0_compute_nr_pages(
>      return nr_pages;
>  }
>  
> -static void __init process_dom0_ioports_disable(struct domain *dom0)
> -{
> -    unsigned long io_from, io_to;
> -    char *t, *s = opt_dom0_ioports_disable;
> -    const char *u;
> -
> -    if ( *s == '\0' )
> -        return;
> -
> -    while ( (t = strsep(&s, ",")) != NULL )
> -    {
> -        io_from = simple_strtoul(t, &u, 16);
> -        if ( u == t )
> -        {
> -        parse_error:
> -            printk("Invalid ioport range <%s> "
> -                   "in dom0_ioports_disable, skipping\n", t);
> -            continue;
> -        }
> -
> -        if ( *u == '\0' )
> -            io_to = io_from;
> -        else if ( *u == '-' )
> -            io_to = simple_strtoul(u + 1, &u, 16);
> -        else
> -            goto parse_error;
> -
> -        if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
> -            goto parse_error;
> -
> -        printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
> -            io_from, io_to);
> -
> -        if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
> -            BUG();
> -    }
> -}
> -
> +/* Modify I/O memory access permissions. */
>  int __init dom0_setup_permissions(struct domain *d)
>  {
>      unsigned long mfn;
> -    unsigned int i, offs;
> -    int rc;
> +    unsigned int i;
> +    int rc = 0;
>  
>      if ( pv_shim )
>          return 0;
>  
> -    /* The hardware domain is initially permitted full I/O capabilities. */
> -    rc = ioports_permit_access(d, 0, 0xFFFF);
>      rc |= iomem_permit_access(d, 0UL,
>                                PFN_DOWN(1UL << domain_max_paddr_bits(d)) - 1);
>      rc |= irqs_permit_access(d, 1, nr_irqs_gsi - 1);
>  
> -    /* Modify I/O port access permissions. */
> -
> -    for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
> -          offs <= i8259A_alias_mask; offs += i )
> -    {
> -        if ( offs & ~i8259A_alias_mask )
> -            continue;
> -        /* Master Interrupt Controller (PIC). */
> -        rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
> -        /* Slave Interrupt Controller (PIC). */
> -        rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
> -    }
> -
> -    /* ELCR of both PICs. */
> -    rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
> -
> -    /* Interval Timer (PIT). */
> -    for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
> -          offs <= pit_alias_mask; offs += i )
> -        if ( !(offs & ~pit_alias_mask) )
> -            rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
> -
> -    /* PIT Channel 2 / PC Speaker Control. */
> -    rc |= ioports_deny_access(d, 0x61, 0x61);
> -
> -    /* INIT# and alternative A20M# control. */
> -    rc |= ioports_deny_access(d, 0x92, 0x92);
> -
> -    /* IGNNE# control. */
> -    rc |= ioports_deny_access(d, 0xF0, 0xF0);
> -
> -    /* ACPI PM Timer. */
> -    if ( pmtmr_ioport )
> -        rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
> -
> -    /* Reset control. */
> -    rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
> -
> -    /* PCI configuration space (NB. 0xCF8 has special treatment). */
> -    rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
> -
> -#ifdef CONFIG_HVM
> -    if ( is_hvm_domain(d) )
> -    {
> -        /* ISA DMA controller, channels 0-3 (incl possible aliases). */
> -        rc |= ioports_deny_access(d, 0x00, 0x1F);
> -        /* ISA DMA controller, page registers (incl various reserved ones). */
> -        rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
> -        /* ISA DMA controller, channels 4-7 (incl usual aliases). */
> -        rc |= ioports_deny_access(d, 0xC0, 0xDF);
> -
> -        /* HVM debug console IO port. */
> -        rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
> -                                  XEN_HVM_DEBUGCONS_IOPORT);
> -        if ( amd_acpi_c1e_quirk )
> -            rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
> -    }
> -#endif
> -    /* Command-line I/O ranges. */
> -    process_dom0_ioports_disable(d);
> -
> -    /* Modify I/O memory access permissions. */
> -
>      /* Local APIC. */
>      if ( mp_lapic_addr != 0 )
>      {
> diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
> index 26760cf995df..12736fc61c11 100644
> --- a/xen/arch/x86/hvm/hvm.c
> +++ b/xen/arch/x86/hvm/hvm.c
> @@ -51,6 +51,7 @@
>  #include <asm/hvm/vm_event.h>
>  #include <asm/hvm/vpt.h>
>  #include <asm/i387.h>
> +#include <asm/iocap.h>
>  #include <asm/mc146818rtc.h>
>  #include <asm/mce.h>
>  #include <asm/monitor.h>
> @@ -81,14 +82,6 @@ integer_param("hvm_debug", opt_hvm_debug_level);
>  
>  struct hvm_function_table __ro_after_init hvm_funcs;
>  
> -/*
> - * The I/O permission bitmap is globally shared by all HVM guests except
> - * the hardware domain which needs a more permissive one.
> - */
> -#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
> -unsigned long __section(".bss.page_aligned") __aligned(PAGE_SIZE)
> -    hvm_io_bitmap[HVM_IOBITMAP_SIZE / BYTES_PER_LONG];
> -
>  /* Xen command-line option to enable HAP */
>  static bool __initdata opt_hap_enabled = true;
>  boolean_param("hap", opt_hap_enabled);
> @@ -205,15 +198,6 @@ static int __init cf_check hvm_enable(void)
>      if ( opt_hvm_fep )
>          warning_add(warning_hvm_fep);
>  
> -    /*
> -     * Allow direct access to the PC debug ports 0x80 and 0xed (they are
> -     * often used for I/O delays, but the vmexits simply slow things down).
> -     */
> -    memset(hvm_io_bitmap, ~0, sizeof(hvm_io_bitmap));
> -    if ( hvm_port80_allowed )
> -        __clear_bit(0x80, hvm_io_bitmap);
> -    __clear_bit(0xed, hvm_io_bitmap);
> -
>      register_cpu_notifier(&cpu_nfb);
>  
>      return 0;
> @@ -645,19 +629,12 @@ int hvm_domain_initialise(struct domain *d,
>  
>      rwlock_init(&d->arch.hvm.pl_time->pt_migrate);
>  
> -    /* Set the default IO Bitmap. */
> -    if ( is_hardware_domain(d) )
> +    rc = ioports_setup_access(d);
> +    if ( rc )
>      {
> -        d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
> -        if ( d->arch.hvm.io_bitmap == NULL )
> -        {
> -            rc = -ENOMEM;
> -            goto fail1;
> -        }
> -        memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
> +        printk("%pd failed to setup I/O bitmap: %d\n", d, rc);
> +        goto fail1;
>      }
> -    else
> -        d->arch.hvm.io_bitmap = hvm_io_bitmap;
>  
>      register_g2m_portio_handler(d);
>      register_vpci_portio_handler(d);
> @@ -684,6 +661,8 @@ int hvm_domain_initialise(struct domain *d,
>          break;
>      }
>  
> +    BUG_ON(!d->arch.ioport_caps);
> +
>      vpic_init(d);
>  
>      rc = vioapic_init(d);
> diff --git a/xen/arch/x86/hvm/nestedhvm.c b/xen/arch/x86/hvm/nestedhvm.c
> index bddd77d8109b..d4e03123d910 100644
> --- a/xen/arch/x86/hvm/nestedhvm.c
> +++ b/xen/arch/x86/hvm/nestedhvm.c
> @@ -107,7 +107,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
>   * The users of the bitmap patterns are in SVM/VMX specific code.
>   *
>   * bitmap        port 0x80  port 0xed
> - * hvm_io_bitmap cleared    cleared
> + * hvm.io_bitmap cleared    cleared
>   * iomap[0]      cleared    set
>   * iomap[1]      set        cleared
>   * iomap[2]      set        set
> @@ -115,7 +115,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
>  
>  static int __init cf_check nestedhvm_setup(void)
>  {
> -    /* Same format and size as hvm_io_bitmap (Intel needs only 2 pages). */
> +    /* Same format and size as hvm.io_bitmap (Intel needs only 2 pages). */
>      unsigned nr = cpu_has_vmx ? 2 : 3;
>      unsigned int i, order = get_order_from_pages(nr);
>  
> @@ -165,7 +165,7 @@ static int __init cf_check nestedhvm_setup(void)
>  __initcall(nestedhvm_setup);
>  
>  unsigned long *
> -nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
> +nestedhvm_vcpu_iomap_get(struct vcpu *v, bool ioport_80, bool ioport_ed)
>  {
>      int i;
>  
> @@ -174,7 +174,7 @@ nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
>  
>      if (ioport_80 == 0) {
>          if (ioport_ed == 0)
> -            return hvm_io_bitmap;
> +            return v->domain->arch.hvm.io_bitmap;
>          i = 0;
>      } else {
>          if (ioport_ed == 0)
> diff --git a/xen/arch/x86/hvm/quirks.c b/xen/arch/x86/hvm/quirks.c
> index 9202f5a47fe9..f4d95441fcff 100644
> --- a/xen/arch/x86/hvm/quirks.c
> +++ b/xen/arch/x86/hvm/quirks.c
> @@ -73,9 +73,6 @@ static int __init cf_check check_port80(void)
>  
>      dmi_check_system(hvm_no_port80_dmi_table);
>  
> -    if ( !hvm_port80_allowed )
> -        __set_bit(0x80, hvm_io_bitmap);
> -
>      return 0;
>  }
>  __initcall(check_port80);
> diff --git a/xen/arch/x86/hvm/svm/nestedsvm.c b/xen/arch/x86/hvm/svm/nestedsvm.c
> index dc2b6a42534a..cc8500b61665 100644
> --- a/xen/arch/x86/hvm/svm/nestedsvm.c
> +++ b/xen/arch/x86/hvm/svm/nestedsvm.c
> @@ -381,7 +381,7 @@ static int nsvm_vmrun_permissionmap(struct vcpu *v, bool viopm)
>          hvm_unmap_guest_frame(ns_viomap, 0);
>      }
>  
> -    svm->ns_iomap = nestedhvm_vcpu_iomap_get(ioport_80, ioport_ed);
> +    svm->ns_iomap = nestedhvm_vcpu_iomap_get(v, ioport_80, ioport_ed);
>  
>      nv->nv_ioport80 = ioport_80;
>      nv->nv_ioportED = ioport_ed;
> diff --git a/xen/arch/x86/hvm/vmx/vvmx.c b/xen/arch/x86/hvm/vmx/vvmx.c
> index e4f3a5fe4c71..4da3e6e90e6c 100644
> --- a/xen/arch/x86/hvm/vmx/vvmx.c
> +++ b/xen/arch/x86/hvm/vmx/vvmx.c
> @@ -554,7 +554,7 @@ unsigned long *_shadow_io_bitmap(struct vcpu *v)
>      port80 = bitmap[0x80 >> 3] & (1 << (0x80 & 0x7)) ? 1 : 0;
>      portED = bitmap[0xed >> 3] & (1 << (0xed & 0x7)) ? 1 : 0;
>  
> -    return nestedhvm_vcpu_iomap_get(port80, portED);
> +    return nestedhvm_vcpu_iomap_get(v, port80, portED);
>  }
>  
>  static void update_msrbitmap(struct vcpu *v, uint32_t shadow_ctrl)
> @@ -622,7 +622,7 @@ void nvmx_update_exec_control(struct vcpu *v, u32 host_cntrl)
>               * L1 VMM doesn't intercept IO instruction.
>               * Use host configuration and reset IO_BITMAP
>               */
> -            bitmap = hvm_io_bitmap;
> +            bitmap = v->domain->arch.hvm.io_bitmap;
>          }
>          else {
>              /* use IO bitmap */
> diff --git a/xen/arch/x86/include/asm/hvm/nestedhvm.h b/xen/arch/x86/include/asm/hvm/nestedhvm.h
> index ea2c1bc328c7..d691ccb07dd6 100644
> --- a/xen/arch/x86/include/asm/hvm/nestedhvm.h
> +++ b/xen/arch/x86/include/asm/hvm/nestedhvm.h
> @@ -50,7 +50,8 @@ int nestedhvm_hap_nested_page_fault(struct vcpu *v, paddr_t *L2_gpa,
>                                      struct npfec npfec);
>  
>  /* IO permission map */
> -unsigned long *nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed);
> +unsigned long *nestedhvm_vcpu_iomap_get(struct vcpu *v,
> +                                        bool ioport_80, bool ioport_ed);
>  
>  /* Misc */
>  #define nestedhvm_paging_mode_hap(v) (!!nhvm_vmcx_hap_enabled(v))
> diff --git a/xen/arch/x86/include/asm/hvm/support.h b/xen/arch/x86/include/asm/hvm/support.h
> index 2a7ba36af06f..7e36d00cc188 100644
> --- a/xen/arch/x86/include/asm/hvm/support.h
> +++ b/xen/arch/x86/include/asm/hvm/support.h
> @@ -41,8 +41,6 @@ extern unsigned int opt_hvm_debug_level;
>  #define HVM_DBG_LOG(level, _f, _a...) do {} while (0)
>  #endif
>  
> -extern unsigned long hvm_io_bitmap[];
> -
>  enum hvm_translation_result {
>      HVMTRANS_okay,
>      HVMTRANS_bad_linear_to_gfn,
> diff --git a/xen/arch/x86/include/asm/iocap.h b/xen/arch/x86/include/asm/iocap.h
> index f948b7186e95..1083f6171cf7 100644
> --- a/xen/arch/x86/include/asm/iocap.h
> +++ b/xen/arch/x86/include/asm/iocap.h
> @@ -22,6 +22,8 @@
>  #define cache_flush_permitted(d) \
>      (has_arch_io_resources(d) || has_arch_pdevs(d))
>  
> +int ioports_setup_access(struct domain *d);
> +
>  static inline int ioports_permit_access(struct domain *d, unsigned long s,
>                                          unsigned long e)
>  {
> diff --git a/xen/arch/x86/ioport.c b/xen/arch/x86/ioport.c
> new file mode 100644
> index 000000000000..dbcd52d37a4f
> --- /dev/null
> +++ b/xen/arch/x86/ioport.c
> @@ -0,0 +1,163 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Guest I/O port address space configuration.
> + *
> + * Copyright 2025 Ford Motor Company
> + */
> +
> +#include <xen/domain.h>
> +#include <xen/param.h>
> +
> +#include <asm/amd.h>
> +#include <asm/acpi.h>
> +#include <asm/io-ports.h>
> +#include <asm/iocap.h>
> +#include <asm/pv/shim.h>
> +#include <asm/setup.h>
> +
> +static char __initdata opt_dom0_ioports_disable[200] = "";
> +string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
> +
> +/*
> + * The I/O permission bitmap size.
> + * See: comment in nestedhvm_setup()
> + */
> +#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
> +
> +/* Hide user-defined I/O ports from the guest OS. */
> +static void process_dom0_ioports_disable(struct domain *dom0)
> +{
> +    unsigned long io_from, io_to;
> +    char *t, *s = opt_dom0_ioports_disable;
> +    const char *u;
> +
> +    if ( *s == '\0' )
> +        return;
> +
> +    while ( (t = strsep(&s, ",")) != NULL )
> +    {
> +        io_from = simple_strtoul(t, &u, 16);
> +        if ( u == t )
> +        {
> +        parse_error:
> +            printk("Invalid ioport range <%s> "
> +                   "in dom0_ioports_disable, skipping\n", t);
> +            continue;
> +        }
> +
> +        if ( *u == '\0' )
> +            io_to = io_from;
> +        else if ( *u == '-' )
> +            io_to = simple_strtoul(u + 1, &u, 16);
> +        else
> +            goto parse_error;
> +
> +        if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
> +            goto parse_error;
> +
> +        printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
> +            io_from, io_to);
> +
> +        if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
> +            BUG();
> +    }
> +}
> +
> +/* Set the default IO Bitmap. */
> +int ioports_setup_access(struct domain *d)
> +{
> +    unsigned int i, offs;
> +    int rc;
> +
> +    if ( pv_shim )
> +        return 0;
> +
> +#ifdef CONFIG_HVM
> +    d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
> +    if ( d->arch.hvm.io_bitmap == NULL )
> +        return -ENOMEM;
> +
> +    memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
> +
> +    if ( !is_hardware_domain(d) )
> +    {
> +        /*
> +         * Allow direct access to the PC debug ports 0x80 and 0xed (they are
> +         * often used for I/O delays, but the vmexits simply slow things down).
> +         */
> +        if ( hvm_port80_allowed )
> +            __clear_bit(0x80, d->arch.hvm.io_bitmap);
> +
> +        __clear_bit(0xed, d->arch.hvm.io_bitmap);
> +
> +        return 0;
> +    }
> +#endif
> +
> +    /* The hardware domain is initially permitted full I/O capabilities. */
> +    rc = ioports_permit_access(d, 0, 0xFFFF);
> +
> +    /* Modify I/O port access permissions. */
> +
> +    for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
> +          offs <= i8259A_alias_mask; offs += i )
> +    {
> +        if ( offs & ~i8259A_alias_mask )
> +            continue;
> +        /* Master Interrupt Controller (PIC). */
> +        rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
> +        /* Slave Interrupt Controller (PIC). */
> +        rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
> +    }
> +
> +    /* ELCR of both PICs. */
> +    rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
> +
> +    /* Interval Timer (PIT). */
> +    for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
> +          offs <= pit_alias_mask; offs += i )
> +        if ( !(offs & ~pit_alias_mask) )
> +            rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
> +
> +    /* PIT Channel 2 / PC Speaker Control. */
> +    rc |= ioports_deny_access(d, 0x61, 0x61);
> +
> +    /* INIT# and alternative A20M# control. */
> +    rc |= ioports_deny_access(d, 0x92, 0x92);
> +
> +    /* IGNNE# control. */
> +    rc |= ioports_deny_access(d, 0xF0, 0xF0);
> +
> +    /* ACPI PM Timer. */
> +    if ( pmtmr_ioport )
> +        rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
> +
> +    /* Reset control. */
> +    rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
> +
> +    /* PCI configuration space (NB. 0xCF8 has special treatment). */
> +    rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
> +
> +#ifdef CONFIG_HVM
> +    if ( is_hvm_domain(d) )
> +    {
> +        /* ISA DMA controller, channels 0-3 (incl possible aliases). */
> +        rc |= ioports_deny_access(d, 0x00, 0x1F);
> +        /* ISA DMA controller, page registers (incl various reserved ones). */
> +        rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
> +        /* ISA DMA controller, channels 4-7 (incl usual aliases). */
> +        rc |= ioports_deny_access(d, 0xC0, 0xDF);
> +
> +        /* HVM debug console IO port. */
> +        rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
> +                                  XEN_HVM_DEBUGCONS_IOPORT);
> +        if ( amd_acpi_c1e_quirk )
> +            rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
> +    }
> +#endif
> +
> +    /* Command-line I/O ranges. */
> +    process_dom0_ioports_disable(d);
> +
> +    return rc;
> +}
> diff --git a/xen/arch/x86/pv/dom0_build.c b/xen/arch/x86/pv/dom0_build.c
> index 21158ce1812e..2b8b4d869ee7 100644
> --- a/xen/arch/x86/pv/dom0_build.c
> +++ b/xen/arch/x86/pv/dom0_build.c
> @@ -17,6 +17,7 @@
>  #include <asm/bootinfo.h>
>  #include <asm/bzimage.h>
>  #include <asm/dom0_build.h>
> +#include <asm/iocap.h>
>  #include <asm/guest.h>
>  #include <asm/page.h>
>  #include <asm/pv/mm.h>
> @@ -1033,6 +1034,9 @@ static int __init dom0_construct(const struct boot_domain *bd)
>      if ( test_bit(XENFEAT_supervisor_mode_kernel, parms.f_required) )
>          panic("Dom0 requires supervisor-mode execution\n");
>  
> +    rc = ioports_setup_access(d);
> +    BUG_ON(rc != 0);
> +
>      rc = dom0_setup_permissions(d);
>      BUG_ON(rc != 0);
>  
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index 5c1be854b544..8860f25ffdeb 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -780,9 +780,20 @@ static int ns16x50_init(void *arg)
>      struct vuart_ns16x50 *vdev = arg;
>      const struct vuart_info *info = vdev->info;
>      struct domain *d = vdev->owner;
> +    int rc;
>  
>      ASSERT(vdev);
>  
> +    /* Disallow sharing physical I/O port */
> +    rc = ioports_deny_access(d, info->base_addr,
> +                             info->base_addr + info->size - 1);

I would be tempted to move ioports_deny_access to hvm_domain_initialise
before vuart_init


> +    if ( rc )
> +    {
> +        ns16x50_err(info, " virtual I/O port range [0x%04lx"PRIx64"..0x%04lx"PRIx64"]: conflict w/ physical range\n",
> +                    info->base_addr, info->base_addr + info->size - 1);

0x%04lx"PRIx64 seems wrong


> +        return rc;
> +    }
> +
>      /* NB: report 115200 baud rate. */
>      vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
>      vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
> -- 
> 2.51.0
>
Re: [PATCH v5 13/15] x86/domain: enable per-domain I/O port bitmaps
Posted by dmukhin@xen.org 1 month, 3 weeks ago
On Fri, Aug 29, 2025 at 02:43:18PM -0700, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > From: Denis Mukhin <dmukhin@ford.com> 
> > 
> > Current design enables all HVM domains share the same I/O port bitmap.
> > 
> > It is necessary for domains crafting its own I/O port address space depending
> > on the user configuration.
> > 
> > Ensure NS16550 emulator does not share I/O ports with the physical I/O ports,
> > which is essential for emulation in PVH hwdom case (dom0).
> > 
> > Not a functional change.
> > 
> > Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> > ---
> > Changes since v4:
> > - new patch 
> > - Link tp v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-4-dmukhin@ford.com/
> > ---
> >  xen/arch/x86/Makefile                    |   1 +
> >  xen/arch/x86/dom0_build.c                | 111 +--------------
> >  xen/arch/x86/hvm/hvm.c                   |  35 +----
> >  xen/arch/x86/hvm/nestedhvm.c             |   8 +-
> >  xen/arch/x86/hvm/quirks.c                |   3 -
> >  xen/arch/x86/hvm/svm/nestedsvm.c         |   2 +-
> >  xen/arch/x86/hvm/vmx/vvmx.c              |   4 +-
> >  xen/arch/x86/include/asm/hvm/nestedhvm.h |   3 +-
> >  xen/arch/x86/include/asm/hvm/support.h   |   2 -
> >  xen/arch/x86/include/asm/iocap.h         |   2 +
> >  xen/arch/x86/ioport.c                    | 163 +++++++++++++++++++++++
> >  xen/arch/x86/pv/dom0_build.c             |   4 +
> >  xen/common/emul/vuart/ns16x50.c          |  11 ++
> >  13 files changed, 200 insertions(+), 149 deletions(-)
> >  create mode 100644 xen/arch/x86/ioport.c
> > 
> > diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile
> > index 9d67ea7cd4a8..5726ecc180eb 100644
> > --- a/xen/arch/x86/Makefile
> > +++ b/xen/arch/x86/Makefile
> > @@ -44,6 +44,7 @@ obj-y += msi.o
> >  obj-y += msr.o
> >  obj-$(CONFIG_INDIRECT_THUNK) += indirect-thunk.o
> >  obj-$(CONFIG_RETURN_THUNK) += indirect-thunk.o
> > +obj-y += ioport.o
> >  obj-$(CONFIG_PV) += ioport_emulate.o
> >  obj-y += irq.o
> >  obj-$(CONFIG_KEXEC) += machine_kexec.o
> > diff --git a/xen/arch/x86/dom0_build.c b/xen/arch/x86/dom0_build.c
> > index 0b467fd4a4fc..26202b33345c 100644
> > --- a/xen/arch/x86/dom0_build.c
> > +++ b/xen/arch/x86/dom0_build.c
> > @@ -298,9 +298,6 @@ int __init parse_arch_dom0_param(const char *s, const char *e)
> >      return 0;
> >  }
> >  
> > -static char __initdata opt_dom0_ioports_disable[200] = "";
> > -string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
> > -
> >  static bool __initdata ro_hpet = true;
> >  boolean_param("ro-hpet", ro_hpet);
> >  
> > @@ -433,122 +430,20 @@ unsigned long __init dom0_compute_nr_pages(
> >      return nr_pages;
> >  }
> >  
> > -static void __init process_dom0_ioports_disable(struct domain *dom0)
> > -{
> > -    unsigned long io_from, io_to;
> > -    char *t, *s = opt_dom0_ioports_disable;
> > -    const char *u;
> > -
> > -    if ( *s == '\0' )
> > -        return;
> > -
> > -    while ( (t = strsep(&s, ",")) != NULL )
> > -    {
> > -        io_from = simple_strtoul(t, &u, 16);
> > -        if ( u == t )
> > -        {
> > -        parse_error:
> > -            printk("Invalid ioport range <%s> "
> > -                   "in dom0_ioports_disable, skipping\n", t);
> > -            continue;
> > -        }
> > -
> > -        if ( *u == '\0' )
> > -            io_to = io_from;
> > -        else if ( *u == '-' )
> > -            io_to = simple_strtoul(u + 1, &u, 16);
> > -        else
> > -            goto parse_error;
> > -
> > -        if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
> > -            goto parse_error;
> > -
> > -        printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
> > -            io_from, io_to);
> > -
> > -        if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
> > -            BUG();
> > -    }
> > -}
> > -
> > +/* Modify I/O memory access permissions. */
> >  int __init dom0_setup_permissions(struct domain *d)
> >  {
> >      unsigned long mfn;
> > -    unsigned int i, offs;
> > -    int rc;
> > +    unsigned int i;
> > +    int rc = 0;
> >  
> >      if ( pv_shim )
> >          return 0;
> >  
> > -    /* The hardware domain is initially permitted full I/O capabilities. */
> > -    rc = ioports_permit_access(d, 0, 0xFFFF);
> >      rc |= iomem_permit_access(d, 0UL,
> >                                PFN_DOWN(1UL << domain_max_paddr_bits(d)) - 1);
> >      rc |= irqs_permit_access(d, 1, nr_irqs_gsi - 1);
> >  
> > -    /* Modify I/O port access permissions. */
> > -
> > -    for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
> > -          offs <= i8259A_alias_mask; offs += i )
> > -    {
> > -        if ( offs & ~i8259A_alias_mask )
> > -            continue;
> > -        /* Master Interrupt Controller (PIC). */
> > -        rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
> > -        /* Slave Interrupt Controller (PIC). */
> > -        rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
> > -    }
> > -
> > -    /* ELCR of both PICs. */
> > -    rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
> > -
> > -    /* Interval Timer (PIT). */
> > -    for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
> > -          offs <= pit_alias_mask; offs += i )
> > -        if ( !(offs & ~pit_alias_mask) )
> > -            rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
> > -
> > -    /* PIT Channel 2 / PC Speaker Control. */
> > -    rc |= ioports_deny_access(d, 0x61, 0x61);
> > -
> > -    /* INIT# and alternative A20M# control. */
> > -    rc |= ioports_deny_access(d, 0x92, 0x92);
> > -
> > -    /* IGNNE# control. */
> > -    rc |= ioports_deny_access(d, 0xF0, 0xF0);
> > -
> > -    /* ACPI PM Timer. */
> > -    if ( pmtmr_ioport )
> > -        rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
> > -
> > -    /* Reset control. */
> > -    rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
> > -
> > -    /* PCI configuration space (NB. 0xCF8 has special treatment). */
> > -    rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
> > -
> > -#ifdef CONFIG_HVM
> > -    if ( is_hvm_domain(d) )
> > -    {
> > -        /* ISA DMA controller, channels 0-3 (incl possible aliases). */
> > -        rc |= ioports_deny_access(d, 0x00, 0x1F);
> > -        /* ISA DMA controller, page registers (incl various reserved ones). */
> > -        rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
> > -        /* ISA DMA controller, channels 4-7 (incl usual aliases). */
> > -        rc |= ioports_deny_access(d, 0xC0, 0xDF);
> > -
> > -        /* HVM debug console IO port. */
> > -        rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
> > -                                  XEN_HVM_DEBUGCONS_IOPORT);
> > -        if ( amd_acpi_c1e_quirk )
> > -            rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
> > -    }
> > -#endif
> > -    /* Command-line I/O ranges. */
> > -    process_dom0_ioports_disable(d);
> > -
> > -    /* Modify I/O memory access permissions. */
> > -
> >      /* Local APIC. */
> >      if ( mp_lapic_addr != 0 )
> >      {
> > diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
> > index 26760cf995df..12736fc61c11 100644
> > --- a/xen/arch/x86/hvm/hvm.c
> > +++ b/xen/arch/x86/hvm/hvm.c
> > @@ -51,6 +51,7 @@
> >  #include <asm/hvm/vm_event.h>
> >  #include <asm/hvm/vpt.h>
> >  #include <asm/i387.h>
> > +#include <asm/iocap.h>
> >  #include <asm/mc146818rtc.h>
> >  #include <asm/mce.h>
> >  #include <asm/monitor.h>
> > @@ -81,14 +82,6 @@ integer_param("hvm_debug", opt_hvm_debug_level);
> >  
> >  struct hvm_function_table __ro_after_init hvm_funcs;
> >  
> > -/*
> > - * The I/O permission bitmap is globally shared by all HVM guests except
> > - * the hardware domain which needs a more permissive one.
> > - */
> > -#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
> > -unsigned long __section(".bss.page_aligned") __aligned(PAGE_SIZE)
> > -    hvm_io_bitmap[HVM_IOBITMAP_SIZE / BYTES_PER_LONG];
> > -
> >  /* Xen command-line option to enable HAP */
> >  static bool __initdata opt_hap_enabled = true;
> >  boolean_param("hap", opt_hap_enabled);
> > @@ -205,15 +198,6 @@ static int __init cf_check hvm_enable(void)
> >      if ( opt_hvm_fep )
> >          warning_add(warning_hvm_fep);
> >  
> > -    /*
> > -     * Allow direct access to the PC debug ports 0x80 and 0xed (they are
> > -     * often used for I/O delays, but the vmexits simply slow things down).
> > -     */
> > -    memset(hvm_io_bitmap, ~0, sizeof(hvm_io_bitmap));
> > -    if ( hvm_port80_allowed )
> > -        __clear_bit(0x80, hvm_io_bitmap);
> > -    __clear_bit(0xed, hvm_io_bitmap);
> > -
> >      register_cpu_notifier(&cpu_nfb);
> >  
> >      return 0;
> > @@ -645,19 +629,12 @@ int hvm_domain_initialise(struct domain *d,
> >  
> >      rwlock_init(&d->arch.hvm.pl_time->pt_migrate);
> >  
> > -    /* Set the default IO Bitmap. */
> > -    if ( is_hardware_domain(d) )
> > +    rc = ioports_setup_access(d);
> > +    if ( rc )
> >      {
> > -        d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
> > -        if ( d->arch.hvm.io_bitmap == NULL )
> > -        {
> > -            rc = -ENOMEM;
> > -            goto fail1;
> > -        }
> > -        memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
> > +        printk("%pd failed to setup I/O bitmap: %d\n", d, rc);
> > +        goto fail1;
> >      }
> > -    else
> > -        d->arch.hvm.io_bitmap = hvm_io_bitmap;
> >  
> >      register_g2m_portio_handler(d);
> >      register_vpci_portio_handler(d);
> > @@ -684,6 +661,8 @@ int hvm_domain_initialise(struct domain *d,
> >          break;
> >      }
> >  
> > +    BUG_ON(!d->arch.ioport_caps);
> > +
> >      vpic_init(d);
> >  
> >      rc = vioapic_init(d);
> > diff --git a/xen/arch/x86/hvm/nestedhvm.c b/xen/arch/x86/hvm/nestedhvm.c
> > index bddd77d8109b..d4e03123d910 100644
> > --- a/xen/arch/x86/hvm/nestedhvm.c
> > +++ b/xen/arch/x86/hvm/nestedhvm.c
> > @@ -107,7 +107,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
> >   * The users of the bitmap patterns are in SVM/VMX specific code.
> >   *
> >   * bitmap        port 0x80  port 0xed
> > - * hvm_io_bitmap cleared    cleared
> > + * hvm.io_bitmap cleared    cleared
> >   * iomap[0]      cleared    set
> >   * iomap[1]      set        cleared
> >   * iomap[2]      set        set
> > @@ -115,7 +115,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
> >  
> >  static int __init cf_check nestedhvm_setup(void)
> >  {
> > -    /* Same format and size as hvm_io_bitmap (Intel needs only 2 pages). */
> > +    /* Same format and size as hvm.io_bitmap (Intel needs only 2 pages). */
> >      unsigned nr = cpu_has_vmx ? 2 : 3;
> >      unsigned int i, order = get_order_from_pages(nr);
> >  
> > @@ -165,7 +165,7 @@ static int __init cf_check nestedhvm_setup(void)
> >  __initcall(nestedhvm_setup);
> >  
> >  unsigned long *
> > -nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
> > +nestedhvm_vcpu_iomap_get(struct vcpu *v, bool ioport_80, bool ioport_ed)
> >  {
> >      int i;
> >  
> > @@ -174,7 +174,7 @@ nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
> >  
> >      if (ioport_80 == 0) {
> >          if (ioport_ed == 0)
> > -            return hvm_io_bitmap;
> > +            return v->domain->arch.hvm.io_bitmap;
> >          i = 0;
> >      } else {
> >          if (ioport_ed == 0)
> > diff --git a/xen/arch/x86/hvm/quirks.c b/xen/arch/x86/hvm/quirks.c
> > index 9202f5a47fe9..f4d95441fcff 100644
> > --- a/xen/arch/x86/hvm/quirks.c
> > +++ b/xen/arch/x86/hvm/quirks.c
> > @@ -73,9 +73,6 @@ static int __init cf_check check_port80(void)
> >  
> >      dmi_check_system(hvm_no_port80_dmi_table);
> >  
> > -    if ( !hvm_port80_allowed )
> > -        __set_bit(0x80, hvm_io_bitmap);
> > -
> >      return 0;
> >  }
> >  __initcall(check_port80);
> > diff --git a/xen/arch/x86/hvm/svm/nestedsvm.c b/xen/arch/x86/hvm/svm/nestedsvm.c
> > index dc2b6a42534a..cc8500b61665 100644
> > --- a/xen/arch/x86/hvm/svm/nestedsvm.c
> > +++ b/xen/arch/x86/hvm/svm/nestedsvm.c
> > @@ -381,7 +381,7 @@ static int nsvm_vmrun_permissionmap(struct vcpu *v, bool viopm)
> >          hvm_unmap_guest_frame(ns_viomap, 0);
> >      }
> >  
> > -    svm->ns_iomap = nestedhvm_vcpu_iomap_get(ioport_80, ioport_ed);
> > +    svm->ns_iomap = nestedhvm_vcpu_iomap_get(v, ioport_80, ioport_ed);
> >  
> >      nv->nv_ioport80 = ioport_80;
> >      nv->nv_ioportED = ioport_ed;
> > diff --git a/xen/arch/x86/hvm/vmx/vvmx.c b/xen/arch/x86/hvm/vmx/vvmx.c
> > index e4f3a5fe4c71..4da3e6e90e6c 100644
> > --- a/xen/arch/x86/hvm/vmx/vvmx.c
> > +++ b/xen/arch/x86/hvm/vmx/vvmx.c
> > @@ -554,7 +554,7 @@ unsigned long *_shadow_io_bitmap(struct vcpu *v)
> >      port80 = bitmap[0x80 >> 3] & (1 << (0x80 & 0x7)) ? 1 : 0;
> >      portED = bitmap[0xed >> 3] & (1 << (0xed & 0x7)) ? 1 : 0;
> >  
> > -    return nestedhvm_vcpu_iomap_get(port80, portED);
> > +    return nestedhvm_vcpu_iomap_get(v, port80, portED);
> >  }
> >  
> >  static void update_msrbitmap(struct vcpu *v, uint32_t shadow_ctrl)
> > @@ -622,7 +622,7 @@ void nvmx_update_exec_control(struct vcpu *v, u32 host_cntrl)
> >               * L1 VMM doesn't intercept IO instruction.
> >               * Use host configuration and reset IO_BITMAP
> >               */
> > -            bitmap = hvm_io_bitmap;
> > +            bitmap = v->domain->arch.hvm.io_bitmap;
> >          }
> >          else {
> >              /* use IO bitmap */
> > diff --git a/xen/arch/x86/include/asm/hvm/nestedhvm.h b/xen/arch/x86/include/asm/hvm/nestedhvm.h
> > index ea2c1bc328c7..d691ccb07dd6 100644
> > --- a/xen/arch/x86/include/asm/hvm/nestedhvm.h
> > +++ b/xen/arch/x86/include/asm/hvm/nestedhvm.h
> > @@ -50,7 +50,8 @@ int nestedhvm_hap_nested_page_fault(struct vcpu *v, paddr_t *L2_gpa,
> >                                      struct npfec npfec);
> >  
> >  /* IO permission map */
> > -unsigned long *nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed);
> > +unsigned long *nestedhvm_vcpu_iomap_get(struct vcpu *v,
> > +                                        bool ioport_80, bool ioport_ed);
> >  
> >  /* Misc */
> >  #define nestedhvm_paging_mode_hap(v) (!!nhvm_vmcx_hap_enabled(v))
> > diff --git a/xen/arch/x86/include/asm/hvm/support.h b/xen/arch/x86/include/asm/hvm/support.h
> > index 2a7ba36af06f..7e36d00cc188 100644
> > --- a/xen/arch/x86/include/asm/hvm/support.h
> > +++ b/xen/arch/x86/include/asm/hvm/support.h
> > @@ -41,8 +41,6 @@ extern unsigned int opt_hvm_debug_level;
> >  #define HVM_DBG_LOG(level, _f, _a...) do {} while (0)
> >  #endif
> >  
> > -extern unsigned long hvm_io_bitmap[];
> > -
> >  enum hvm_translation_result {
> >      HVMTRANS_okay,
> >      HVMTRANS_bad_linear_to_gfn,
> > diff --git a/xen/arch/x86/include/asm/iocap.h b/xen/arch/x86/include/asm/iocap.h
> > index f948b7186e95..1083f6171cf7 100644
> > --- a/xen/arch/x86/include/asm/iocap.h
> > +++ b/xen/arch/x86/include/asm/iocap.h
> > @@ -22,6 +22,8 @@
> >  #define cache_flush_permitted(d) \
> >      (has_arch_io_resources(d) || has_arch_pdevs(d))
> >  
> > +int ioports_setup_access(struct domain *d);
> > +
> >  static inline int ioports_permit_access(struct domain *d, unsigned long s,
> >                                          unsigned long e)
> >  {
> > diff --git a/xen/arch/x86/ioport.c b/xen/arch/x86/ioport.c
> > new file mode 100644
> > index 000000000000..dbcd52d37a4f
> > --- /dev/null
> > +++ b/xen/arch/x86/ioport.c
> > @@ -0,0 +1,163 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * Guest I/O port address space configuration.
> > + *
> > + * Copyright 2025 Ford Motor Company
> > + */
> > +
> > +#include <xen/domain.h>
> > +#include <xen/param.h>
> > +
> > +#include <asm/amd.h>
> > +#include <asm/acpi.h>
> > +#include <asm/io-ports.h>
> > +#include <asm/iocap.h>
> > +#include <asm/pv/shim.h>
> > +#include <asm/setup.h>
> > +
> > +static char __initdata opt_dom0_ioports_disable[200] = "";
> > +string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
> > +
> > +/*
> > + * The I/O permission bitmap size.
> > + * See: comment in nestedhvm_setup()
> > + */
> > +#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
> > +
> > +/* Hide user-defined I/O ports from the guest OS. */
> > +static void process_dom0_ioports_disable(struct domain *dom0)
> > +{
> > +    unsigned long io_from, io_to;
> > +    char *t, *s = opt_dom0_ioports_disable;
> > +    const char *u;
> > +
> > +    if ( *s == '\0' )
> > +        return;
> > +
> > +    while ( (t = strsep(&s, ",")) != NULL )
> > +    {
> > +        io_from = simple_strtoul(t, &u, 16);
> > +        if ( u == t )
> > +        {
> > +        parse_error:
> > +            printk("Invalid ioport range <%s> "
> > +                   "in dom0_ioports_disable, skipping\n", t);
> > +            continue;
> > +        }
> > +
> > +        if ( *u == '\0' )
> > +            io_to = io_from;
> > +        else if ( *u == '-' )
> > +            io_to = simple_strtoul(u + 1, &u, 16);
> > +        else
> > +            goto parse_error;
> > +
> > +        if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
> > +            goto parse_error;
> > +
> > +        printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
> > +            io_from, io_to);
> > +
> > +        if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
> > +            BUG();
> > +    }
> > +}
> > +
> > +/* Set the default IO Bitmap. */
> > +int ioports_setup_access(struct domain *d)
> > +{
> > +    unsigned int i, offs;
> > +    int rc;
> > +
> > +    if ( pv_shim )
> > +        return 0;
> > +
> > +#ifdef CONFIG_HVM
> > +    d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
> > +    if ( d->arch.hvm.io_bitmap == NULL )
> > +        return -ENOMEM;
> > +
> > +    memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
> > +
> > +    if ( !is_hardware_domain(d) )
> > +    {
> > +        /*
> > +         * Allow direct access to the PC debug ports 0x80 and 0xed (they are
> > +         * often used for I/O delays, but the vmexits simply slow things down).
> > +         */
> > +        if ( hvm_port80_allowed )
> > +            __clear_bit(0x80, d->arch.hvm.io_bitmap);
> > +
> > +        __clear_bit(0xed, d->arch.hvm.io_bitmap);
> > +
> > +        return 0;
> > +    }
> > +#endif
> > +
> > +    /* The hardware domain is initially permitted full I/O capabilities. */
> > +    rc = ioports_permit_access(d, 0, 0xFFFF);
> > +
> > +    /* Modify I/O port access permissions. */
> > +
> > +    for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
> > +          offs <= i8259A_alias_mask; offs += i )
> > +    {
> > +        if ( offs & ~i8259A_alias_mask )
> > +            continue;
> > +        /* Master Interrupt Controller (PIC). */
> > +        rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
> > +        /* Slave Interrupt Controller (PIC). */
> > +        rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
> > +    }
> > +
> > +    /* ELCR of both PICs. */
> > +    rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
> > +
> > +    /* Interval Timer (PIT). */
> > +    for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
> > +          offs <= pit_alias_mask; offs += i )
> > +        if ( !(offs & ~pit_alias_mask) )
> > +            rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
> > +
> > +    /* PIT Channel 2 / PC Speaker Control. */
> > +    rc |= ioports_deny_access(d, 0x61, 0x61);
> > +
> > +    /* INIT# and alternative A20M# control. */
> > +    rc |= ioports_deny_access(d, 0x92, 0x92);
> > +
> > +    /* IGNNE# control. */
> > +    rc |= ioports_deny_access(d, 0xF0, 0xF0);
> > +
> > +    /* ACPI PM Timer. */
> > +    if ( pmtmr_ioport )
> > +        rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
> > +
> > +    /* Reset control. */
> > +    rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
> > +
> > +    /* PCI configuration space (NB. 0xCF8 has special treatment). */
> > +    rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
> > +
> > +#ifdef CONFIG_HVM
> > +    if ( is_hvm_domain(d) )
> > +    {
> > +        /* ISA DMA controller, channels 0-3 (incl possible aliases). */
> > +        rc |= ioports_deny_access(d, 0x00, 0x1F);
> > +        /* ISA DMA controller, page registers (incl various reserved ones). */
> > +        rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
> > +        /* ISA DMA controller, channels 4-7 (incl usual aliases). */
> > +        rc |= ioports_deny_access(d, 0xC0, 0xDF);
> > +
> > +        /* HVM debug console IO port. */
> > +        rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
> > +                                  XEN_HVM_DEBUGCONS_IOPORT);
> > +        if ( amd_acpi_c1e_quirk )
> > +            rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
> > +    }
> > +#endif
> > +
> > +    /* Command-line I/O ranges. */
> > +    process_dom0_ioports_disable(d);
> > +
> > +    return rc;
> > +}
> > diff --git a/xen/arch/x86/pv/dom0_build.c b/xen/arch/x86/pv/dom0_build.c
> > index 21158ce1812e..2b8b4d869ee7 100644
> > --- a/xen/arch/x86/pv/dom0_build.c
> > +++ b/xen/arch/x86/pv/dom0_build.c
> > @@ -17,6 +17,7 @@
> >  #include <asm/bootinfo.h>
> >  #include <asm/bzimage.h>
> >  #include <asm/dom0_build.h>
> > +#include <asm/iocap.h>
> >  #include <asm/guest.h>
> >  #include <asm/page.h>
> >  #include <asm/pv/mm.h>
> > @@ -1033,6 +1034,9 @@ static int __init dom0_construct(const struct boot_domain *bd)
> >      if ( test_bit(XENFEAT_supervisor_mode_kernel, parms.f_required) )
> >          panic("Dom0 requires supervisor-mode execution\n");
> >  
> > +    rc = ioports_setup_access(d);
> > +    BUG_ON(rc != 0);
> > +
> >      rc = dom0_setup_permissions(d);
> >      BUG_ON(rc != 0);
> >  
> > diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> > index 5c1be854b544..8860f25ffdeb 100644
> > --- a/xen/common/emul/vuart/ns16x50.c
> > +++ b/xen/common/emul/vuart/ns16x50.c
> > @@ -780,9 +780,20 @@ static int ns16x50_init(void *arg)
> >      struct vuart_ns16x50 *vdev = arg;
> >      const struct vuart_info *info = vdev->info;
> >      struct domain *d = vdev->owner;
> > +    int rc;
> >  
> >      ASSERT(vdev);
> >  
> > +    /* Disallow sharing physical I/O port */
> > +    rc = ioports_deny_access(d, info->base_addr,
> > +                             info->base_addr + info->size - 1);
> 
> I would be tempted to move ioports_deny_access to hvm_domain_initialise
> before vuart_init

I thought about it originally, but I decided to keep vuart setup in one place.

> 
> 
> > +    if ( rc )
> > +    {
> > +        ns16x50_err(info, " virtual I/O port range [0x%04lx"PRIx64"..0x%04lx"PRIx64"]: conflict w/ physical range\n",
> > +                    info->base_addr, info->base_addr + info->size - 1);
> 
> 0x%04lx"PRIx64 seems wrong

Agreed, will fix, thanks.

> 
> 
> > +        return rc;
> > +    }
> > +
> >      /* NB: report 115200 baud rate. */
> >      vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
> >      vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
> > -- 
> > 2.51.0
> >