diff --git a/docs/all-tests.dox b/docs/all-tests.dox
index 0a8b9130cfe8..66e35babe4d7 100644
--- a/docs/all-tests.dox
+++ b/docs/all-tests.dox
@@ -41,6 +41,8 @@ Coveres XSA-106 and XSA-156.
 
 @subpage test-umip - Guest User-Mode Instruction Prevention support.
 
+@subpage test-misaligned-io-breakpoints - Misaligned I/O port hardware breakpoint test.
+
 
 @section index-xsa XSA Proof-of-Concept tests
 
diff --git a/tests/misaligned-io-breakpoints/Makefile b/tests/misaligned-io-breakpoints/Makefile
new file mode 100644
index 000000000000..9e4cd3eef761
--- /dev/null
+++ b/tests/misaligned-io-breakpoints/Makefile
@@ -0,0 +1,9 @@
+include $(ROOT)/build/common.mk
+
+NAME      := misaligned-io-breakpoints
+CATEGORY  := utility
+TEST-ENVS := pv64
+
+obj-perenv += main.o
+
+include $(ROOT)/build/gen.mk
diff --git a/tests/misaligned-io-breakpoints/main.c b/tests/misaligned-io-breakpoints/main.c
new file mode 100644
index 000000000000..d91222b29288
--- /dev/null
+++ b/tests/misaligned-io-breakpoints/main.c
@@ -0,0 +1,283 @@
+/**
+ * @file tests/misaligned-io-breakpoints/main.c
+ * @ref test-misaligned-io-breakpoints - Misaligned I/O port hardware breakpoint test.
+ *
+ * @page test-misaligned-io-breakpoints Misaligned I/O Breakpoints
+ *
+ * When hardware breakpoints are configured on misaligned IO ports, the
+ * hardware will mask the addresses based on the breakpoint width during
+ * comparison.
+ *
+ * This test checks that the emulated behaviour of misaligned IO breakpoints
+ * in PV guests match that of real hardware.
+ *
+ * @see tests/io-breakpoint-bug/main.c
+ */
+#include <xtf.h>
+
+enum width { _16, _32 };
+
+const char test_title[] = "Misaligned I/O Breakpoints";
+
+static const struct test {
+    unsigned long breakpoint_port;
+    enum width breakpoint_size;
+    uint16_t io_op_port;
+    enum width io_op_len;
+    bool expect_breakpoint_trigger;
+} tests[] = {
+    /* 16-bit breakpoint, 16-bit I/O operation */
+    { 0x09, _16, 0x06, _16, false },
+    { 0x09, _16, 0x07, _16, true  },
+    { 0x09, _16, 0x08, _16, true  },
+    { 0x09, _16, 0x09, _16, true  },
+    { 0x09, _16, 0x0a, _16, false },
+
+    { 0x0b, _16, 0x08, _16, false },
+    { 0x0b, _16, 0x09, _16, true  },
+    { 0x0b, _16, 0x0a, _16, true  },
+    { 0x0b, _16, 0x0b, _16, true  },
+    { 0x0b, _16, 0x0c, _16, false },
+
+    /* 16-bit breakpoint, 32-bit I/O operation */
+    { 0x09, _16, 0x04, _32, false },
+    { 0x09, _16, 0x05, _32, true  },
+    { 0x09, _16, 0x06, _32, true  },
+    { 0x09, _16, 0x07, _32, true  },
+    { 0x09, _16, 0x08, _32, true  },
+    { 0x09, _16, 0x09, _32, true  },
+    { 0x09, _16, 0x0a, _32, false },
+
+    { 0x0b, _16, 0x06, _32, false },
+    { 0x0b, _16, 0x07, _32, true  },
+    { 0x0b, _16, 0x08, _32, true  },
+    { 0x0b, _16, 0x09, _32, true  },
+    { 0x0b, _16, 0x0a, _32, true  },
+    { 0x0b, _16, 0x0b, _32, true  },
+    { 0x0b, _16, 0x0c, _32, false },
+
+    /* 32-bit breakpoint, 16-bit I/O operation */
+    { 0x09, _32, 0x06, _16, false },
+    { 0x09, _32, 0x07, _16, true  },
+    { 0x09, _32, 0x08, _16, true  },
+    { 0x09, _32, 0x09, _16, true  },
+    { 0x09, _32, 0x0a, _16, true  },
+    { 0x09, _32, 0x0b, _16, true  },
+    { 0x09, _32, 0x0c, _16, false },
+
+    { 0x0a, _32, 0x06, _16, false },
+    { 0x0a, _32, 0x07, _16, true  },
+    { 0x0a, _32, 0x08, _16, true  },
+    { 0x0a, _32, 0x09, _16, true  },
+    { 0x0a, _32, 0x0a, _16, true  },
+    { 0x0a, _32, 0x0b, _16, true  },
+    { 0x0a, _32, 0x0c, _16, false },
+
+    { 0x0b, _32, 0x06, _16, false },
+    { 0x0b, _32, 0x07, _16, true  },
+    { 0x0b, _32, 0x08, _16, true  },
+    { 0x0b, _32, 0x09, _16, true  },
+    { 0x0b, _32, 0x0a, _16, true  },
+    { 0x0b, _32, 0x0b, _16, true  },
+    { 0x0b, _32, 0x0c, _16, false },
+
+    { 0x0d, _32, 0x0a, _16, false },
+    { 0x0d, _32, 0x0b, _16, true  },
+    { 0x0d, _32, 0x0c, _16, true  },
+    { 0x0d, _32, 0x0d, _16, true  },
+    { 0x0d, _32, 0x0e, _16, true  },
+    { 0x0d, _32, 0x0f, _16, true  },
+    { 0x0d, _32, 0x10, _16, false },
+
+    { 0x0e, _32, 0x0a, _16, false },
+    { 0x0e, _32, 0x0b, _16, true  },
+    { 0x0e, _32, 0x0c, _16, true  },
+    { 0x0e, _32, 0x0d, _16, true  },
+    { 0x0e, _32, 0x0e, _16, true  },
+    { 0x0e, _32, 0x0f, _16, true  },
+    { 0x0e, _32, 0x10, _16, false },
+
+    { 0x0f, _32, 0x0a, _16, false },
+    { 0x0f, _32, 0x0b, _16, true  },
+    { 0x0f, _32, 0x0c, _16, true  },
+    { 0x0f, _32, 0x0d, _16, true  },
+    { 0x0f, _32, 0x0e, _16, true  },
+    { 0x0f, _32, 0x0f, _16, true  },
+    { 0x0f, _32, 0x10, _16, false },
+
+    /* 32-bit breakpoint, 32-bit I/O operation */
+    { 0x09, _32, 0x04, _32, false },
+    { 0x09, _32, 0x05, _32, true  },
+    { 0x09, _32, 0x06, _32, true  },
+    { 0x09, _32, 0x07, _32, true  },
+    { 0x09, _32, 0x08, _32, true  },
+    { 0x09, _32, 0x09, _32, true  },
+    { 0x09, _32, 0x0a, _32, true  },
+    { 0x09, _32, 0x0b, _32, true  },
+    { 0x09, _32, 0x0c, _32, false },
+
+    { 0x0a, _32, 0x04, _32, false },
+    { 0x0a, _32, 0x05, _32, true  },
+    { 0x0a, _32, 0x06, _32, true  },
+    { 0x0a, _32, 0x07, _32, true  },
+    { 0x0a, _32, 0x08, _32, true  },
+    { 0x0a, _32, 0x09, _32, true  },
+    { 0x0a, _32, 0x0a, _32, true  },
+    { 0x0a, _32, 0x0b, _32, true  },
+    { 0x0a, _32, 0x0c, _32, false },
+
+    { 0x0b, _32, 0x04, _32, false },
+    { 0x0b, _32, 0x05, _32, true  },
+    { 0x0b, _32, 0x06, _32, true  },
+    { 0x0b, _32, 0x07, _32, true  },
+    { 0x0b, _32, 0x08, _32, true  },
+    { 0x0b, _32, 0x09, _32, true  },
+    { 0x0b, _32, 0x0a, _32, true  },
+    { 0x0b, _32, 0x0b, _32, true  },
+    { 0x0b, _32, 0x0c, _32, false },
+
+    { 0x0d, _32, 0x08, _32, false },
+    { 0x0d, _32, 0x09, _32, true  },
+    { 0x0d, _32, 0x0a, _32, true  },
+    { 0x0d, _32, 0x0b, _32, true  },
+    { 0x0d, _32, 0x0c, _32, true  },
+    { 0x0d, _32, 0x0d, _32, true  },
+    { 0x0d, _32, 0x0e, _32, true  },
+    { 0x0d, _32, 0x0f, _32, true  },
+    { 0x0d, _32, 0x10, _32, false },
+
+    { 0x0e, _32, 0x08, _32, false },
+    { 0x0e, _32, 0x09, _32, true  },
+    { 0x0e, _32, 0x0a, _32, true  },
+    { 0x0e, _32, 0x0b, _32, true  },
+    { 0x0e, _32, 0x0c, _32, true  },
+    { 0x0e, _32, 0x0d, _32, true  },
+    { 0x0e, _32, 0x0e, _32, true  },
+    { 0x0e, _32, 0x0f, _32, true  },
+    { 0x0e, _32, 0x10, _32, false },
+
+    { 0x0f, _32, 0x08, _32, false },
+    { 0x0f, _32, 0x09, _32, true  },
+    { 0x0f, _32, 0x0a, _32, true  },
+    { 0x0f, _32, 0x0b, _32, true  },
+    { 0x0f, _32, 0x0c, _32, true  },
+    { 0x0f, _32, 0x0d, _32, true  },
+    { 0x0f, _32, 0x0e, _32, true  },
+    { 0x0f, _32, 0x0f, _32, true  },
+    { 0x0f, _32, 0x10, _32, false },
+};
+
+static void enable_debug_extensions(void)
+{
+    unsigned long cr4 = read_cr4();
+
+    if ( !(cr4 & X86_CR4_DE) )
+        write_cr4(cr4 | X86_CR4_DE);
+}
+
+static void init_breakpoint(enum width width)
+{
+    unsigned long io0, dr7;
+
+    switch (width)
+    {
+    case _16:
+        io0 = DR7_SYM(0, G, IO, 16) | X86_DR7_GE | X86_DR7_DEFAULT;
+        break;
+    case _32:
+        io0 = DR7_SYM(0, G, IO, 32) | X86_DR7_GE | X86_DR7_DEFAULT;
+        break;
+    default:
+        xtf_failure("  Fail: Unknown width %d\n", width);
+        return;
+    }
+
+    write_dr7(io0);
+
+    if ( (dr7 = read_dr7()) != io0 )
+        xtf_failure("  Fail: dr7 %#lx != io0 %#lx\n", dr7, io0);
+}
+
+static void set_breakpoint(unsigned long io_port)
+{
+    unsigned long dr0;
+
+    write_dr0(io_port);
+
+    if ( (dr0 = read_dr0()) != io_port )
+        xtf_failure("  Fail: dr0 %#lx != %#lx\n", dr0, io_port);
+}
+
+/* Set vCPU IOPL to 1 to allow IO calls in kernel space */
+static void set_iopl(void)
+{
+    unsigned int iopl = 1;
+
+    hypercall_physdev_op(PHYSDEVOP_set_iopl, &iopl);
+}
+
+static exinfo_t io_op(uint16_t io_port, enum width io_op_len)
+{
+    exinfo_t fault = 0;
+
+    switch (io_op_len)
+    {
+    case _16:
+        asm volatile ("outw %w0, %w1; 1:"
+                      _ASM_EXTABLE_HANDLER(1b, 1b, %P[rec])
+                      : "+a" (fault)
+                      : "Nd" (io_port),
+                        [rec] "p" (ex_record_fault_eax));
+        break;
+    case _32:
+        asm volatile ("outl %k0, %w1; 1:"
+                      _ASM_EXTABLE_HANDLER(1b, 1b, %P[rec])
+                      : "+a" (fault)
+                      : "Nd" (io_port),
+                        [rec] "p" (ex_record_fault_eax));
+        break;
+    default:
+        xtf_failure("  Fail: Unknown width %d\n", io_op_len);
+        break;
+    }
+
+    return fault;
+}
+
+void test_main(void)
+{
+    unsigned int i;
+
+    enable_debug_extensions();
+    set_iopl();
+
+    for ( i = 0; i < ARRAY_SIZE(tests); ++i )
+    {
+        const struct test *t = &tests[i];
+
+        /* arrange */
+        init_breakpoint(t->breakpoint_size);
+        set_breakpoint(t->breakpoint_port);
+
+        /* act */
+        exinfo_t ex = io_op(t->io_op_port, t->io_op_len);
+
+        /* assert */
+        if ( t->expect_breakpoint_trigger && ex != EXINFO_SYM(DB, 0) )
+            xtf_failure("Fail: Expected trigger with breakpoint port 0x%02lx and io port 0x%02x, but got no trigger.\n", t->breakpoint_port, t->io_op_port);
+        if ( !t->expect_breakpoint_trigger && ex == EXINFO_SYM(DB, 0) )
+            xtf_failure("Fail: Expected no trigger with breakpoint port 0x%02lx and io port 0x%02x, but got trigger.\n", t->breakpoint_port, t->io_op_port);
+    }
+
+    xtf_success(NULL);
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
