summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorroyger <royger@FreeBSD.org>2014-08-04 08:42:29 +0000
committerroyger <royger@FreeBSD.org>2014-08-04 08:42:29 +0000
commiteb7b09e785fab81b14e9ab4facb46c82d1f1b92f (patch)
treec2b2d5b8bf9dbb2e2f2d1cba1c14327943670708
parentf4b5494cddde52e504f6d8e2adc50ab660f543e0 (diff)
downloadFreeBSD-src-eb7b09e785fab81b14e9ab4facb46c82d1f1b92f.zip
FreeBSD-src-eb7b09e785fab81b14e9ab4facb46c82d1f1b92f.tar.gz
xen: implement event channel PIRQ support
This allows Dom0 to manage physical hardware, redirecting the physical interrupts to event channels. Sponsored by: Citrix Systems R&D x86/xen/xen_intr.c: - Expand struct xenisrc to hold the level and triggering of PIRQ event channels. - Implement missing methods in xen_intr_pirq_pic. - Allow xen_intr_alloc_isrc to take a vector parameter that globally identifies the interrupt. This is only used for PIRQs that are bound to a specific hardware IRQ. - Introduce xen_register_pirq used to register IO APIC legacy PIRQ interrupts. - Add support for the dynamic PIRQ EOI map, this shared memory is modified by Xen (if it suppoorts that feature), and notifies the guest if an EOI is needed or not. If it's not available fall back to the old implementation using PHYSDEVOP_irq_status_query. - Rename xen_intr_isrc_count to xen_intr_auto_vector_count and replace it's usages. - Align static variables by name. xen/xen_intr.h: - Add prototype for xen_register_pirq.
-rw-r--r--sys/x86/xen/xen_intr.c220
-rw-r--r--sys/xen/xen_intr.h12
2 files changed, 214 insertions, 18 deletions
diff --git a/sys/x86/xen/xen_intr.c b/sys/x86/xen/xen_intr.c
index 18f88c2..b02917c 100644
--- a/sys/x86/xen/xen_intr.c
+++ b/sys/x86/xen/xen_intr.c
@@ -104,6 +104,9 @@ DPCPU_DECLARE(struct vcpu_info *, vcpu_info);
#define is_valid_evtchn(x) ((x) != 0)
+#define XEN_EEXIST 17 /* Xen "already exists" error */
+#define XEN_ALLOCATE_VECTOR 0 /* Allocate a vector for this event channel */
+
struct xenisrc {
struct intsrc xi_intsrc;
enum evtchn_type xi_type;
@@ -113,8 +116,9 @@ struct xenisrc {
int xi_pirq;
int xi_virq;
u_int xi_close:1; /* close on unbind? */
- u_int xi_needs_eoi:1;
u_int xi_shared:1; /* Shared with other domains. */
+ u_int xi_activehi:1;
+ u_int xi_edgetrigger:1;
};
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
@@ -136,6 +140,9 @@ static void xen_intr_pirq_enable_source(struct intsrc *isrc);
static void xen_intr_pirq_disable_source(struct intsrc *isrc, int eoi);
static void xen_intr_pirq_eoi_source(struct intsrc *isrc);
static void xen_intr_pirq_enable_intr(struct intsrc *isrc);
+static void xen_intr_pirq_disable_intr(struct intsrc *isrc);
+static int xen_intr_pirq_config_intr(struct intsrc *isrc,
+ enum intr_trigger trig, enum intr_polarity pol);
/**
* PIC interface for all event channel port types except physical IRQs.
@@ -163,18 +170,20 @@ struct pic xen_intr_pirq_pic = {
.pic_disable_source = xen_intr_pirq_disable_source,
.pic_eoi_source = xen_intr_pirq_eoi_source,
.pic_enable_intr = xen_intr_pirq_enable_intr,
- .pic_disable_intr = xen_intr_disable_intr,
+ .pic_disable_intr = xen_intr_pirq_disable_intr,
.pic_vector = xen_intr_vector,
.pic_source_pending = xen_intr_source_pending,
.pic_suspend = xen_intr_suspend,
.pic_resume = xen_intr_resume,
- .pic_config_intr = xen_intr_config_intr,
+ .pic_config_intr = xen_intr_pirq_config_intr,
.pic_assign_cpu = xen_intr_assign_cpu
};
-static struct mtx xen_intr_isrc_lock;
-static int xen_intr_isrc_count;
-static struct xenisrc *xen_intr_port_to_isrc[NR_EVENT_CHANNELS];
+static struct mtx xen_intr_isrc_lock;
+static int xen_intr_auto_vector_count;
+static struct xenisrc *xen_intr_port_to_isrc[NR_EVENT_CHANNELS];
+static u_long *xen_intr_pirq_eoi_map;
+static boolean_t xen_intr_pirq_eoi_map_enabled;
/*------------------------- Private Functions --------------------------------*/
/**
@@ -256,7 +265,7 @@ xen_intr_find_unused_isrc(enum evtchn_type type)
KASSERT(mtx_owned(&xen_intr_isrc_lock), ("Evtchn isrc lock not held"));
- for (isrc_idx = 0; isrc_idx < xen_intr_isrc_count; isrc_idx ++) {
+ for (isrc_idx = 0; isrc_idx < xen_intr_auto_vector_count; isrc_idx ++) {
struct xenisrc *isrc;
u_int vector;
@@ -282,27 +291,33 @@ xen_intr_find_unused_isrc(enum evtchn_type type)
* object or NULL.
*/
static struct xenisrc *
-xen_intr_alloc_isrc(enum evtchn_type type)
+xen_intr_alloc_isrc(enum evtchn_type type, int vector)
{
static int warned;
struct xenisrc *isrc;
- int vector;
KASSERT(mtx_owned(&xen_intr_isrc_lock), ("Evtchn alloc lock not held"));
- if (xen_intr_isrc_count > NR_EVENT_CHANNELS) {
+ if (xen_intr_auto_vector_count > NR_EVENT_CHANNELS) {
if (!warned) {
warned = 1;
printf("xen_intr_alloc: Event channels exhausted.\n");
}
return (NULL);
}
- vector = FIRST_EVTCHN_INT + xen_intr_isrc_count;
- xen_intr_isrc_count++;
+
+ if (type != EVTCHN_TYPE_PIRQ) {
+ vector = FIRST_EVTCHN_INT + xen_intr_auto_vector_count;
+ xen_intr_auto_vector_count++;
+ }
+
+ KASSERT((intr_lookup_source(vector) == NULL),
+ ("Trying to use an already allocated vector"));
mtx_unlock(&xen_intr_isrc_lock);
isrc = malloc(sizeof(*isrc), M_XENINTR, M_WAITOK | M_ZERO);
- isrc->xi_intsrc.is_pic = &xen_intr_pic;
+ isrc->xi_intsrc.is_pic =
+ (type == EVTCHN_TYPE_PIRQ) ? &xen_intr_pirq_pic : &xen_intr_pic;
isrc->xi_vector = vector;
isrc->xi_type = type;
intr_register_source(&isrc->xi_intsrc);
@@ -388,7 +403,7 @@ xen_intr_bind_isrc(struct xenisrc **isrcp, evtchn_port_t local_port,
mtx_lock(&xen_intr_isrc_lock);
isrc = xen_intr_find_unused_isrc(type);
if (isrc == NULL) {
- isrc = xen_intr_alloc_isrc(type);
+ isrc = xen_intr_alloc_isrc(type, XEN_ALLOCATE_VECTOR);
if (isrc == NULL) {
mtx_unlock(&xen_intr_isrc_lock);
return (ENOSPC);
@@ -570,7 +585,8 @@ static int
xen_intr_init(void *dummy __unused)
{
struct xen_intr_pcpu_data *pcpu;
- int i;
+ struct physdev_pirq_eoi_gmfn eoi_gmfn;
+ int i, rc;
if (!xen_domain())
return (0);
@@ -591,7 +607,20 @@ xen_intr_init(void *dummy __unused)
xen_intr_intrcnt_add(i);
}
+ /* Try to register PIRQ EOI map */
+ xen_intr_pirq_eoi_map = malloc(PAGE_SIZE, M_XENINTR, M_WAITOK | M_ZERO);
+ eoi_gmfn.gmfn = atop(vtophys(xen_intr_pirq_eoi_map));
+ rc = HYPERVISOR_physdev_op(PHYSDEVOP_pirq_eoi_gmfn_v2, &eoi_gmfn);
+ if (rc != 0 && bootverbose)
+ printf("Xen interrupts: unable to register PIRQ EOI map\n");
+ else
+ xen_intr_pirq_eoi_map_enabled = true;
+
intr_register_pic(&xen_intr_pic);
+ intr_register_pic(&xen_intr_pirq_pic);
+
+ if (bootverbose)
+ printf("Xen interrupt system initialized\n");
return (0);
}
@@ -696,7 +725,7 @@ xen_intr_resume(struct pic *unused, bool suspend_cancelled)
memset(xen_intr_port_to_isrc, 0, sizeof(xen_intr_port_to_isrc));
/* Free unused isrcs and rebind VIRQs and IPIs */
- for (isrc_idx = 0; isrc_idx < xen_intr_isrc_count; isrc_idx++) {
+ for (isrc_idx = 0; isrc_idx < xen_intr_auto_vector_count; isrc_idx++) {
u_int vector;
vector = FIRST_EVTCHN_INT + isrc_idx;
@@ -916,6 +945,9 @@ xen_intr_pirq_disable_source(struct intsrc *base_isrc, int eoi)
isrc = (struct xenisrc *)base_isrc;
evtchn_mask_port(isrc->xi_port);
+
+ if (eoi == PIC_EOI)
+ xen_intr_pirq_eoi_source(base_isrc);
}
/*
@@ -944,7 +976,7 @@ xen_intr_pirq_eoi_source(struct intsrc *base_isrc)
/* XXX Use shared page of flags for this. */
isrc = (struct xenisrc *)base_isrc;
- if (isrc->xi_needs_eoi != 0) {
+ if (test_bit(isrc->xi_pirq, xen_intr_pirq_eoi_map)) {
struct physdev_eoi eoi = { .irq = isrc->xi_pirq };
(void)HYPERVISOR_physdev_op(PHYSDEVOP_eoi, &eoi);
@@ -957,8 +989,116 @@ xen_intr_pirq_eoi_source(struct intsrc *base_isrc)
* \param isrc The interrupt source to enable.
*/
static void
-xen_intr_pirq_enable_intr(struct intsrc *isrc)
+xen_intr_pirq_enable_intr(struct intsrc *base_isrc)
+{
+ struct xenisrc *isrc;
+ struct evtchn_bind_pirq bind_pirq;
+ struct physdev_irq_status_query irq_status;
+ int error;
+
+ isrc = (struct xenisrc *)base_isrc;
+
+ if (!xen_intr_pirq_eoi_map_enabled) {
+ irq_status.irq = isrc->xi_pirq;
+ error = HYPERVISOR_physdev_op(PHYSDEVOP_irq_status_query,
+ &irq_status);
+ if (error)
+ panic("unable to get status of IRQ#%d", isrc->xi_pirq);
+
+ if (irq_status.flags & XENIRQSTAT_needs_eoi) {
+ /*
+ * Since the dynamic PIRQ EOI map is not available
+ * mark the PIRQ as needing EOI unconditionally.
+ */
+ set_bit(isrc->xi_pirq, xen_intr_pirq_eoi_map);
+ }
+ }
+
+ bind_pirq.pirq = isrc->xi_pirq;
+ bind_pirq.flags = isrc->xi_edgetrigger ? 0 : BIND_PIRQ__WILL_SHARE;
+ error = HYPERVISOR_event_channel_op(EVTCHNOP_bind_pirq, &bind_pirq);
+ if (error)
+ panic("unable to bind IRQ#%d", isrc->xi_pirq);
+
+ isrc->xi_port = bind_pirq.port;
+
+ mtx_lock(&xen_intr_isrc_lock);
+ KASSERT((xen_intr_port_to_isrc[bind_pirq.port] == NULL),
+ ("trying to override an already setup event channel port"));
+ xen_intr_port_to_isrc[bind_pirq.port] = isrc;
+ mtx_unlock(&xen_intr_isrc_lock);
+
+ evtchn_unmask_port(isrc->xi_port);
+}
+
+/*
+ * Disable an interrupt source.
+ *
+ * \param isrc The interrupt source to disable.
+ */
+static void
+xen_intr_pirq_disable_intr(struct intsrc *base_isrc)
+{
+ struct xenisrc *isrc;
+ struct evtchn_close close;
+ int error;
+
+ isrc = (struct xenisrc *)base_isrc;
+
+ evtchn_mask_port(isrc->xi_port);
+
+ close.port = isrc->xi_port;
+ error = HYPERVISOR_event_channel_op(EVTCHNOP_close, &close);
+ if (error)
+ panic("unable to close event channel %d IRQ#%d",
+ isrc->xi_port, isrc->xi_pirq);
+
+ mtx_lock(&xen_intr_isrc_lock);
+ xen_intr_port_to_isrc[isrc->xi_port] = NULL;
+ mtx_unlock(&xen_intr_isrc_lock);
+
+ isrc->xi_port = 0;
+}
+
+/**
+ * Perform configuration of an interrupt source.
+ *
+ * \param isrc The interrupt source to configure.
+ * \param trig Edge or level.
+ * \param pol Active high or low.
+ *
+ * \returns 0 if no events are pending, otherwise non-zero.
+ */
+static int
+xen_intr_pirq_config_intr(struct intsrc *base_isrc, enum intr_trigger trig,
+ enum intr_polarity pol)
{
+ struct xenisrc *isrc = (struct xenisrc *)base_isrc;
+ struct physdev_setup_gsi setup_gsi;
+ int error;
+
+ KASSERT(!(trig == INTR_TRIGGER_CONFORM || pol == INTR_POLARITY_CONFORM),
+ ("%s: Conforming trigger or polarity\n", __func__));
+
+ setup_gsi.gsi = isrc->xi_pirq;
+ setup_gsi.triggering = trig == INTR_TRIGGER_EDGE ? 0 : 1;
+ setup_gsi.polarity = pol == INTR_POLARITY_HIGH ? 0 : 1;
+
+ error = HYPERVISOR_physdev_op(PHYSDEVOP_setup_gsi, &setup_gsi);
+ if (error == -XEN_EEXIST) {
+ if ((isrc->xi_edgetrigger && (trig != INTR_TRIGGER_EDGE)) ||
+ (isrc->xi_activehi && (pol != INTR_POLARITY_HIGH)))
+ panic("unable to reconfigure interrupt IRQ#%d",
+ isrc->xi_pirq);
+ error = 0;
+ }
+ if (error)
+ panic("unable to configure IRQ#%d\n", isrc->xi_pirq);
+
+ isrc->xi_activehi = pol == INTR_POLARITY_HIGH ? 1 : 0;
+ isrc->xi_edgetrigger = trig == INTR_TRIGGER_EDGE ? 1 : 0;
+
+ return (0);
}
/*--------------------------- Public Functions -------------------------------*/
@@ -1181,6 +1321,50 @@ xen_intr_alloc_and_bind_ipi(device_t dev, u_int cpu,
}
int
+xen_register_pirq(int vector, enum intr_trigger trig, enum intr_polarity pol)
+{
+ struct physdev_map_pirq map_pirq;
+ struct physdev_irq alloc_pirq;
+ struct xenisrc *isrc;
+ int error;
+
+ if (vector == 0)
+ return (EINVAL);
+
+ if (bootverbose)
+ printf("xen: register IRQ#%d\n", vector);
+
+ map_pirq.domid = DOMID_SELF;
+ map_pirq.type = MAP_PIRQ_TYPE_GSI;
+ map_pirq.index = vector;
+ map_pirq.pirq = vector;
+
+ error = HYPERVISOR_physdev_op(PHYSDEVOP_map_pirq, &map_pirq);
+ if (error) {
+ printf("xen: unable to map IRQ#%d\n", vector);
+ return (error);
+ }
+
+ alloc_pirq.irq = vector;
+ alloc_pirq.vector = 0;
+ error = HYPERVISOR_physdev_op(PHYSDEVOP_alloc_irq_vector, &alloc_pirq);
+ if (error) {
+ printf("xen: unable to alloc PIRQ for IRQ#%d\n", vector);
+ return (error);
+ }
+
+ mtx_lock(&xen_intr_isrc_lock);
+ isrc = xen_intr_alloc_isrc(EVTCHN_TYPE_PIRQ, vector);
+ mtx_unlock(&xen_intr_isrc_lock);
+ KASSERT((isrc != NULL), ("xen: unable to allocate isrc for interrupt"));
+ isrc->xi_pirq = vector;
+ isrc->xi_activehi = pol == INTR_POLARITY_HIGH ? 1 : 0;
+ isrc->xi_edgetrigger = trig == INTR_TRIGGER_EDGE ? 1 : 0;
+
+ return (0);
+}
+
+int
xen_intr_describe(xen_intr_handle_t port_handle, const char *fmt, ...)
{
char descr[MAXCOMLEN + 1];
diff --git a/sys/xen/xen_intr.h b/sys/xen/xen_intr.h
index 3b339a5..a1ff666 100644
--- a/sys/xen/xen_intr.h
+++ b/sys/xen/xen_intr.h
@@ -159,6 +159,18 @@ int xen_intr_alloc_and_bind_ipi(device_t dev, u_int cpu,
xen_intr_handle_t *handlep);
/**
+ * Register a physical interrupt vector and setup the interrupt source.
+ *
+ * \param vector The global vector to use.
+ * \param trig Default trigger method.
+ * \param pol Default polarity of the interrupt.
+ *
+ * \returns 0 on success, otherwise an errno.
+ */
+int xen_register_pirq(int vector, enum intr_trigger trig,
+ enum intr_polarity pol);
+
+/**
* Unbind an interrupt handler from its interrupt source.
*
* \param handlep A pointer to the opaque handle that was initialized
OpenPOWER on IntegriCloud