diff --git a/tools/libxc/include/xenctrl.h b/tools/libxc/include/xenctrl.h
index 02d0db8..cc78ed6 100644
--- a/tools/libxc/include/xenctrl.h
+++ b/tools/libxc/include/xenctrl.h
@@ -2114,6 +2114,14 @@ int xc_domain_bind_pt_isa_irq(xc_interface *xch,
                               uint32_t domid,
                               uint8_t machine_irq);
 
+int xc_domain_bind_pt_spi_irq(xc_interface *xch,
+                              uint32_t domid,
+                              uint16_t spi);
+
+int xc_domain_unbind_pt_spi_irq(xc_interface *xch,
+                                uint32_t domid,
+                                uint16_t spi);
+
 int xc_domain_set_machine_address_size(xc_interface *xch,
 				       uint32_t domid,
 				       unsigned int width);
diff --git a/tools/libxc/xc_domain.c b/tools/libxc/xc_domain.c
index 1f2c80c..676ec50 100644
--- a/tools/libxc/xc_domain.c
+++ b/tools/libxc/xc_domain.c
@@ -1773,15 +1773,16 @@ int xc_domain_unbind_msi_irq(
 }
 
 /* Pass-through: binds machine irq to guests irq */
-int xc_domain_bind_pt_irq(
-    xc_interface *xch,
-    uint32_t domid,
-    uint8_t machine_irq,
-    uint8_t irq_type,
-    uint8_t bus,
-    uint8_t device,
-    uint8_t intx,
-    uint8_t isa_irq)
+static int xc_domain_bind_pt_irq_int(
+           xc_interface *xch,
+           uint32_t domid,
+           uint8_t machine_irq,
+           uint8_t irq_type,
+           uint8_t bus,
+           uint8_t device,
+           uint8_t intx,
+           uint8_t isa_irq,
+           uint16_t spi)
 {
     int rc;
     xen_domctl_bind_pt_irq_t * bind;
@@ -1805,6 +1806,9 @@ int xc_domain_bind_pt_irq(
     case PT_IRQ_TYPE_ISA:
         bind->u.isa.isa_irq = isa_irq;
         break;
+    case PT_IRQ_TYPE_SPI:
+        bind->u.spi.spi = spi;
+        break;
     default:
         errno = EINVAL;
         return -1;
@@ -1814,7 +1818,7 @@ int xc_domain_bind_pt_irq(
     return rc;
 }
 
-int xc_domain_unbind_pt_irq(
+int xc_domain_bind_pt_irq(
     xc_interface *xch,
     uint32_t domid,
     uint8_t machine_irq,
@@ -1824,6 +1828,21 @@ int xc_domain_unbind_pt_irq(
     uint8_t intx,
     uint8_t isa_irq)
 {
+    return xc_domain_bind_pt_irq_int(xch, domid, machine_irq, irq_type,
+                                     bus, device, intx, isa_irq, 0);
+}
+
+static int xc_domain_unbind_pt_irq_int(
+           xc_interface *xch,
+           uint32_t domid,
+           uint8_t machine_irq,
+           uint8_t irq_type,
+           uint8_t bus,
+           uint8_t device,
+           uint8_t intx,
+           uint8_t isa_irq,
+           uint8_t spi)
+{
     int rc;
     xen_domctl_bind_pt_irq_t * bind;
     DECLARE_DOMCTL;
@@ -1846,6 +1865,8 @@ int xc_domain_unbind_pt_irq(
     case PT_IRQ_TYPE_ISA:
         bind->u.isa.isa_irq = isa_irq;
         break;
+    case PT_IRQ_TYPE_SPI:
+        bind->u.spi.spi = spi;
     default:
         errno = EINVAL;
         return -1;
@@ -1855,6 +1876,20 @@ int xc_domain_unbind_pt_irq(
     return rc;
 }
 
+int xc_domain_unbind_pt_irq(
+    xc_interface *xch,
+    uint32_t domid,
+    uint8_t machine_irq,
+    uint8_t irq_type,
+    uint8_t bus,
+    uint8_t device,
+    uint8_t intx,
+    uint8_t isa_irq)
+{
+    return xc_domain_unbind_pt_irq_int(xch, domid, machine_irq, irq_type,
+                                       bus, device, intx, isa_irq, 0);
+}
+
 int xc_domain_bind_pt_pci_irq(
     xc_interface *xch,
     uint32_t domid,
@@ -1878,6 +1913,25 @@ int xc_domain_bind_pt_isa_irq(
                                   PT_IRQ_TYPE_ISA, 0, 0, 0, machine_irq));
 }
 
+int xc_domain_bind_pt_spi_irq(
+    xc_interface *xch,
+    uint32_t domid,
+    uint16_t spi)
+{
+    /* vSPI == SPI */
+    return (xc_domain_bind_pt_irq_int(xch, domid, spi,
+                                      PT_IRQ_TYPE_SPI, 0, 0, 0, 0, spi));
+}
+
+int xc_domain_unbind_pt_spi_irq(
+    xc_interface *xch,
+    uint32_t domid,
+    uint16_t spi)
+{
+    return (xc_domain_unbind_pt_irq_int(xch, domid, spi,
+                                        PT_IRQ_TYPE_SPI, 0, 0, 0, 0, spi));
+}
+
 int xc_unmap_domain_meminfo(xc_interface *xch, struct xc_domain_meminfo *minfo)
 {
     struct domain_info_context _di = { .guest_width = minfo->guest_width,
diff --git a/xen/arch/arm/domctl.c b/xen/arch/arm/domctl.c
index 6f30af7..531bb13 100644
--- a/xen/arch/arm/domctl.c
+++ b/xen/arch/arm/domctl.c
@@ -10,6 +10,8 @@
 #include <xen/errno.h>
 #include <xen/sched.h>
 #include <xen/hypercall.h>
+#include <xen/iocap.h>
+#include <xsm/xsm.h>
 #include <public/domctl.h>
 
 long arch_do_domctl(struct xen_domctl *domctl, struct domain *d,
@@ -30,6 +32,81 @@ long arch_do_domctl(struct xen_domctl *domctl, struct domain *d,
 
         return p2m_cache_flush(d, s, e);
     }
+    case XEN_DOMCTL_bind_pt_irq:
+    {
+        int rc;
+        xen_domctl_bind_pt_irq_t *bind = &domctl->u.bind_pt_irq;
+        uint32_t irq = bind->u.spi.spi;
+        uint32_t virq = bind->machine_irq;
+
+        /* We only support PT_IRQ_TYPE_SPI */
+        if ( bind->irq_type != PT_IRQ_TYPE_SPI )
+            return -EOPNOTSUPP;
+
+        /*
+         * XXX: For now map the interrupt 1:1. Other support will require to
+         * modify domain_pirq_to_irq macro.
+         */
+        if ( irq != virq )
+            return -EINVAL;
+
+        /*
+         * ARM doesn't require to separate IRQ assignation in 2
+         * hypercalls (PHYSDEVOP_map_pirq and DOMCTL_bind_pt_irq).
+         *
+         * Call xsm_map_domain_irq in order to keep the same XSM checks
+         * done by the 2 hypercalls
+         */
+        rc = xsm_map_domain_irq(XSM_HOOK, d, irq, NULL);
+        if ( rc )
+            return rc;
+
+        rc = xsm_bind_pt_irq(XSM_HOOK, d, bind);
+        if ( rc )
+            return rc;
+
+        if ( !irq_access_permitted(current->domain, irq) )
+            return -EPERM;
+
+        if ( !vgic_reserve_virq(d, virq) )
+            return -EBUSY;
+
+        rc = route_irq_to_guest(d, virq, irq, "routed IRQ");
+        if ( rc )
+            vgic_free_virq(d, virq);
+
+        return rc;
+    }
+    case XEN_DOMCTL_unbind_pt_irq:
+    {
+        int rc;
+        xen_domctl_bind_pt_irq_t *bind = &domctl->u.bind_pt_irq;
+        uint32_t irq = bind->u.spi.spi;
+        uint32_t virq = bind->machine_irq;
+
+        /* We only support PT_IRQ_TYPE_SPI */
+        if ( bind->irq_type != PT_IRQ_TYPE_SPI )
+            return -EOPNOTSUPP;
+
+        /* For now map the interrupt 1:1 */
+        if ( irq != virq )
+            return -EINVAL;
+
+        rc = xsm_unbind_pt_irq(XSM_HOOK, d, bind);
+        if ( rc )
+            return rc;
+
+        if ( !irq_access_permitted(current->domain, irq) )
+            return -EPERM;
+
+        rc = release_guest_irq(d, virq);
+        if ( rc )
+            return rc;
+
+        vgic_free_virq(d, virq);
+
+        return 0;
+    }
     default:
         return subarch_do_domctl(domctl, d, u_domctl);
     }
diff --git a/xen/include/public/domctl.h b/xen/include/public/domctl.h
index d2e8db0..d94c647 100644
--- a/xen/include/public/domctl.h
+++ b/xen/include/public/domctl.h
@@ -504,6 +504,7 @@ typedef enum pt_irq_type_e {
     PT_IRQ_TYPE_ISA,
     PT_IRQ_TYPE_MSI,
     PT_IRQ_TYPE_MSI_TRANSLATE,
+    PT_IRQ_TYPE_SPI,    /* ARM: valid range 32-1019 */
 } pt_irq_type_t;
 struct xen_domctl_bind_pt_irq {
     uint32_t machine_irq;
@@ -524,6 +525,9 @@ struct xen_domctl_bind_pt_irq {
             uint32_t gflags;
             uint64_aligned_t gtable;
         } msi;
+        struct {
+            uint16_t spi;
+        } spi;
     } u;
 };
 typedef struct xen_domctl_bind_pt_irq xen_domctl_bind_pt_irq_t;
diff --git a/xen/include/xsm/dummy.h b/xen/include/xsm/dummy.h
index 4227093..c36e05f 100644
--- a/xen/include/xsm/dummy.h
+++ b/xen/include/xsm/dummy.h
@@ -445,6 +445,18 @@ static XSM_INLINE int xsm_unmap_domain_pirq(XSM_DEFAULT_ARG struct domain *d)
     return xsm_default_action(action, current->domain, d);
 }
 
+static XSM_INLINE int xsm_bind_pt_irq(XSM_DEFAULT_ARG struct domain *d, struct xen_domctl_bind_pt_irq *bind)
+{
+    XSM_ASSERT_ACTION(XSM_HOOK);
+    return xsm_default_action(action, current->domain, d);
+}
+
+static XSM_INLINE int xsm_unbind_pt_irq(XSM_DEFAULT_ARG struct domain *d, struct xen_domctl_bind_pt_irq *bind)
+{
+    XSM_ASSERT_ACTION(XSM_HOOK);
+    return xsm_default_action(action, current->domain, d);
+}
+
 static XSM_INLINE int xsm_unmap_domain_irq(XSM_DEFAULT_ARG struct domain *d, int irq, void *data)
 {
     XSM_ASSERT_ACTION(XSM_HOOK);
@@ -631,18 +643,6 @@ static XSM_INLINE int xsm_priv_mapping(XSM_DEFAULT_ARG struct domain *d, struct
     return xsm_default_action(action, d, t);
 }
 
-static XSM_INLINE int xsm_bind_pt_irq(XSM_DEFAULT_ARG struct domain *d, struct xen_domctl_bind_pt_irq *bind)
-{
-    XSM_ASSERT_ACTION(XSM_HOOK);
-    return xsm_default_action(action, current->domain, d);
-}
-
-static XSM_INLINE int xsm_unbind_pt_irq(XSM_DEFAULT_ARG struct domain *d, struct xen_domctl_bind_pt_irq *bind)
-{
-    XSM_ASSERT_ACTION(XSM_HOOK);
-    return xsm_default_action(action, current->domain, d);
-}
-
 static XSM_INLINE int xsm_ioport_permission(XSM_DEFAULT_ARG struct domain *d, uint32_t s, uint32_t e, uint8_t allow)
 {
     XSM_ASSERT_ACTION(XSM_HOOK);
diff --git a/xen/include/xsm/xsm.h b/xen/include/xsm/xsm.h
index c2049f3..b7446be 100644
--- a/xen/include/xsm/xsm.h
+++ b/xen/include/xsm/xsm.h
@@ -107,6 +107,8 @@ struct xsm_operations {
     int (*map_domain_irq) (struct domain *d, int irq, void *data);
     int (*unmap_domain_pirq) (struct domain *d);
     int (*unmap_domain_irq) (struct domain *d, int irq, void *data);
+    int (*bind_pt_irq) (struct domain *d, struct xen_domctl_bind_pt_irq *bind);
+    int (*unbind_pt_irq) (struct domain *d, struct xen_domctl_bind_pt_irq *bind);
     int (*irq_permission) (struct domain *d, int pirq, uint8_t allow);
     int (*iomem_permission) (struct domain *d, uint64_t s, uint64_t e, uint8_t allow);
     int (*iomem_mapping) (struct domain *d, uint64_t s, uint64_t e, uint8_t allow);
@@ -169,8 +171,6 @@ struct xsm_operations {
     int (*mmuext_op) (struct domain *d, struct domain *f);
     int (*update_va_mapping) (struct domain *d, struct domain *f, l1_pgentry_t pte);
     int (*priv_mapping) (struct domain *d, struct domain *t);
-    int (*bind_pt_irq) (struct domain *d, struct xen_domctl_bind_pt_irq *bind);
-    int (*unbind_pt_irq) (struct domain *d, struct xen_domctl_bind_pt_irq *bind);
     int (*ioport_permission) (struct domain *d, uint32_t s, uint32_t e, uint8_t allow);
     int (*ioport_mapping) (struct domain *d, uint32_t s, uint32_t e, uint8_t allow);
 #endif
@@ -419,6 +419,18 @@ static inline int xsm_unmap_domain_irq (xsm_default_t def, struct domain *d, int
     return xsm_ops->unmap_domain_irq(d, irq, data);
 }
 
+static inline int xsm_bind_pt_irq(xsm_default_t def, struct domain *d,
+                                  struct xen_domctl_bind_pt_irq *bind)
+{
+    return xsm_ops->bind_pt_irq(d, bind);
+}
+
+static inline int xsm_unbind_pt_irq(xsm_default_t def, struct domain *d,
+                                    struct xen_domctl_bind_pt_irq *bind)
+{
+    return xsm_ops->unbind_pt_irq(d, bind);
+}
+
 static inline int xsm_irq_permission (xsm_default_t def, struct domain *d, int pirq, uint8_t allow)
 {
     return xsm_ops->irq_permission(d, pirq, allow);
@@ -643,18 +655,6 @@ static inline int xsm_priv_mapping(xsm_default_t def, struct domain *d, struct d
     return xsm_ops->priv_mapping(d, t);
 }
 
-static inline int xsm_bind_pt_irq(xsm_default_t def, struct domain *d,
-                                                struct xen_domctl_bind_pt_irq *bind)
-{
-    return xsm_ops->bind_pt_irq(d, bind);
-}
-
-static inline int xsm_unbind_pt_irq(xsm_default_t def, struct domain *d,
-                                                struct xen_domctl_bind_pt_irq *bind)
-{
-    return xsm_ops->unbind_pt_irq(d, bind);
-}
-
 static inline int xsm_ioport_permission (xsm_default_t def, struct domain *d, uint32_t s, uint32_t e, uint8_t allow)
 {
     return xsm_ops->ioport_permission(d, s, e, allow);
diff --git a/xen/xsm/dummy.c b/xen/xsm/dummy.c
index 25fca68..a3b8aab 100644
--- a/xen/xsm/dummy.c
+++ b/xen/xsm/dummy.c
@@ -81,6 +81,8 @@ void xsm_fixup_ops (struct xsm_operations *ops)
     set_to_dummy_if_null(ops, map_domain_irq);
     set_to_dummy_if_null(ops, unmap_domain_pirq);
     set_to_dummy_if_null(ops, unmap_domain_irq);
+    set_to_dummy_if_null(ops, bind_pt_irq);
+    set_to_dummy_if_null(ops, unbind_pt_irq);
     set_to_dummy_if_null(ops, irq_permission);
     set_to_dummy_if_null(ops, iomem_permission);
     set_to_dummy_if_null(ops, iomem_mapping);
@@ -140,8 +142,6 @@ void xsm_fixup_ops (struct xsm_operations *ops)
     set_to_dummy_if_null(ops, mmuext_op);
     set_to_dummy_if_null(ops, update_va_mapping);
     set_to_dummy_if_null(ops, priv_mapping);
-    set_to_dummy_if_null(ops, bind_pt_irq);
-    set_to_dummy_if_null(ops, unbind_pt_irq);
     set_to_dummy_if_null(ops, ioport_permission);
     set_to_dummy_if_null(ops, ioport_mapping);
 #endif
diff --git a/xen/xsm/flask/hooks.c b/xen/xsm/flask/hooks.c
index ab5141d..688ba2a 100644
--- a/xen/xsm/flask/hooks.c
+++ b/xen/xsm/flask/hooks.c
@@ -580,12 +580,14 @@ static int flask_domctl(struct domain *d, int cmd)
 #ifdef HAS_MEM_ACCESS
     case XEN_DOMCTL_vm_event_op:
 #endif
+
+    /* These have individual XSM hooks (arch/../domctl.c) */
+    case XEN_DOMCTL_bind_pt_irq:
+    case XEN_DOMCTL_unbind_pt_irq:
 #ifdef CONFIG_X86
     /* These have individual XSM hooks (arch/x86/domctl.c) */
     case XEN_DOMCTL_shadow_op:
     case XEN_DOMCTL_ioport_permission:
-    case XEN_DOMCTL_bind_pt_irq:
-    case XEN_DOMCTL_unbind_pt_irq:
     case XEN_DOMCTL_ioport_mapping:
     /* These have individual XSM hooks (drivers/passthrough/iommu.c) */
     case XEN_DOMCTL_get_device_group:
@@ -911,6 +913,36 @@ static int flask_unmap_domain_irq (struct domain *d, int irq, void *data)
     return rc;
 }
 
+static int flask_bind_pt_irq (struct domain *d, struct xen_domctl_bind_pt_irq *bind)
+{
+    u32 dsid, rsid;
+    int rc = -EPERM;
+    int irq;
+    struct avc_audit_data ad;
+
+    rc = current_has_perm(d, SECCLASS_RESOURCE, RESOURCE__ADD);
+    if ( rc )
+        return rc;
+
+    irq = domain_pirq_to_irq(d, bind->machine_irq);
+
+    rc = get_irq_sid(irq, &rsid, &ad);
+    if ( rc )
+        return rc;
+
+    rc = avc_current_has_perm(rsid, SECCLASS_HVM, HVM__BIND_IRQ, &ad);
+    if ( rc )
+        return rc;
+
+    dsid = domain_sid(d);
+    return avc_has_perm(dsid, rsid, SECCLASS_RESOURCE, RESOURCE__USE, &ad);
+}
+
+static int flask_unbind_pt_irq (struct domain *d, struct xen_domctl_bind_pt_irq *bind)
+{
+    return current_has_perm(d, SECCLASS_RESOURCE, RESOURCE__REMOVE);
+}
+
 static int flask_irq_permission (struct domain *d, int pirq, uint8_t access)
 {
     /* the PIRQ number is not useful; real IRQ is checked during mapping */
@@ -1468,36 +1500,6 @@ static int flask_priv_mapping(struct domain *d, struct domain *t)
 {
     return domain_has_perm(d, t, SECCLASS_MMU, MMU__TARGET_HACK);
 }
-
-static int flask_bind_pt_irq (struct domain *d, struct xen_domctl_bind_pt_irq *bind)
-{
-    u32 dsid, rsid;
-    int rc = -EPERM;
-    int irq;
-    struct avc_audit_data ad;
-
-    rc = current_has_perm(d, SECCLASS_RESOURCE, RESOURCE__ADD);
-    if ( rc )
-        return rc;
-
-    irq = domain_pirq_to_irq(d, bind->machine_irq);
-
-    rc = get_irq_sid(irq, &rsid, &ad);
-    if ( rc )
-        return rc;
-
-    rc = avc_current_has_perm(rsid, SECCLASS_HVM, HVM__BIND_IRQ, &ad);
-    if ( rc )
-        return rc;
-
-    dsid = domain_sid(d);
-    return avc_has_perm(dsid, rsid, SECCLASS_RESOURCE, RESOURCE__USE, &ad);
-}
-
-static int flask_unbind_pt_irq (struct domain *d, struct xen_domctl_bind_pt_irq *bind)
-{
-    return current_has_perm(d, SECCLASS_RESOURCE, RESOURCE__REMOVE);
-}
 #endif /* CONFIG_X86 */
 
 long do_flask_op(XEN_GUEST_HANDLE_PARAM(xsm_op_t) u_flask_op);
@@ -1556,6 +1558,8 @@ static struct xsm_operations flask_ops = {
     .map_domain_irq = flask_map_domain_irq,
     .unmap_domain_pirq = flask_unmap_domain_pirq,
     .unmap_domain_irq = flask_unmap_domain_irq,
+    .bind_pt_irq = flask_bind_pt_irq,
+    .unbind_pt_irq = flask_unbind_pt_irq,
     .irq_permission = flask_irq_permission,
     .iomem_permission = flask_iomem_permission,
     .iomem_mapping = flask_iomem_mapping,
@@ -1616,8 +1620,6 @@ static struct xsm_operations flask_ops = {
     .mmuext_op = flask_mmuext_op,
     .update_va_mapping = flask_update_va_mapping,
     .priv_mapping = flask_priv_mapping,
-    .bind_pt_irq = flask_bind_pt_irq,
-    .unbind_pt_irq = flask_unbind_pt_irq,
     .ioport_permission = flask_ioport_permission,
     .ioport_mapping = flask_ioport_mapping,
 #endif
