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.
This commit is contained in:
Roger Pau Monné 2014-08-04 08:42:29 +00:00
parent 2db8752cef
commit 25e34dd327
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=269507
2 changed files with 214 additions and 18 deletions

View File

@ -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 -------------------------------*/
@ -1180,6 +1320,50 @@ xen_intr_alloc_and_bind_ipi(device_t dev, u_int cpu,
#endif
}
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, ...)
{

View File

@ -158,6 +158,18 @@ int xen_intr_alloc_and_bind_ipi(device_t dev, u_int cpu,
driver_filter_t filter, enum intr_type irqflags,
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.
*