|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [PATCH v5 for-xen-4.5 2/3] dpci: Safeguard against race with hvm_dirq_assist crashing and pt_irq_[create|destroy]_bind
There is a potential for race where the pt_irq_destroy_bind is called while
the interrupt for the PIRQ has been taken - and hvm_dirq_assist is scheduled
for execution.
The timing window is short enough - and the issue we can encounter is that
the hvm_dirq_assist could get a page fault while trying to access the
'struct domain' which was extracted from 'struct hvm_pirq_dpci->dom' field
- while the pt_irq_destroy_bind resets 'struct hvm_pirq_dpci->dom' to
NULL. Imagine this example:
CPU0 CPU1
do_IRQ pt_irq_destroy_bind
-> __do_IRQ_guest
-> schedule_dpci_for
... pirq_guest_unbind
[action unbound]
pirq_dpci->dom = NULL;
...
dpci_softirq
hvm_dirq_assist
pirq_dpci->dom dereference
[BOOM!]
The way to fix this is by:
a) Do not reset ->dom field to NULL at all. Only allow 'dpci_kill'
once it has established that the hvm_dirq_assist is done - to
reset the field.
b) At the start of 'pt_irq_create_bind' check if the softirq
is still running (by using dpci_kill) - and if it is spin
- but only for less than a second. If the time is greater
than a second return -EAGAIN and let QEMU handle it
(which is that it returns to the OS that the MSI was not set).
If it was less than a second restart the creation/re-use process.
The b) could be done in 'pt_irq_destroy_bind' as well if that
would be better. Either option works.
The return of -EAGAIN could be removed as QEMU is not that smart
to handle the error.
Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@xxxxxxxxxx>
---
xen/drivers/passthrough/io.c | 54 ++++++++++++++++++++++++++++++++++----------
1 file changed, 42 insertions(+), 12 deletions(-)
diff --git a/xen/drivers/passthrough/io.c b/xen/drivers/passthrough/io.c
index 5f82aba..13433a5 100644
--- a/xen/drivers/passthrough/io.c
+++ b/xen/drivers/passthrough/io.c
@@ -163,6 +163,7 @@ int pt_irq_create_bind(
struct pirq *info;
int rc, pirq = pt_irq_bind->machine_irq;
+ restart:
if ( pirq < 0 || pirq >= d->nr_pirqs )
return -EINVAL;
@@ -194,15 +195,34 @@ int pt_irq_create_bind(
}
pirq_dpci = pirq_dpci(info);
- /* Something is horrible wrong if it has been scheduled or is running. */
- ASSERT(pirq_dpci->state == 0);
+ /*
+ * We can hit this scenario if the dpci_softirq is running while we are
+ * doing pt_irq_destroy_bind followed by this call on another CPU.
+ * We have to wait until the dpci_softirq is done.
+ */
+ if ( pirq_dpci->state )
+ {
+ s_time_t start;
- /* Guest unbinds (pt_irq_destroy_bind) and rebinds it back. */
- if ( !pirq_dpci->dom )
- pirq_dpci->dom = d;
+ /* Need to drop the lock so hvm_dirq_assist can progress. */
+ spin_unlock(&d->event_lock);
+ start = NOW();
+ while ( dpci_kill(pirq_dpci) )
+ {
+ process_pending_softirqs();
+ if ( (NOW() - start) >> 30 )
+ {
+ return -EAGAIN;
+ }
+ }
+ /* And since we dropped the lock, another pt_irq_[destroy|create]_bind
+ * could be called. Lets start from scratch.
+ */
+ goto restart;
+ }
/* Otherwise 'schedule_dpci_for' will crash. */
- ASSERT(pirq_dpci->dom == d);
+ pirq_dpci->dom = d;
switch ( pt_irq_bind->irq_type )
{
@@ -229,9 +249,15 @@ int pt_irq_create_bind(
{
pirq_dpci->gmsi.gflags = 0;
pirq_dpci->gmsi.gvec = 0;
- pirq_dpci->dom = NULL;
pirq_dpci->flags = 0;
- dpci_kill(pirq_dpci);
+ /*
+ * If we fail, we have flags reset and hvm_dirq_assist will
+ * just exit. We cannot touch pirq_dpci->dom unless
+ * the softirq is truly dead - and we reap it at the start or
+ * in pci_clean_dpci_irq.
+ */
+ if ( !dpci_kill(pirq_dpci) )
+ pirq_dpci->dom = NULL;
pirq_cleanup_check(info, d);
spin_unlock(&d->event_lock);
return rc;
@@ -332,12 +358,18 @@ int pt_irq_create_bind(
{
if ( pt_irq_need_timer(pirq_dpci->flags) )
kill_timer(&pirq_dpci->timer);
- pirq_dpci->dom = NULL;
list_del(&girq->list);
list_del(&digl->list);
hvm_irq_dpci->link_cnt[link]--;
pirq_dpci->flags = 0;
- dpci_kill(pirq_dpci);
+ /*
+ * If we fail, we have flags reset and hvm_dirq_assist will
+ * just exit. We cannot touch pirq_dpci->dom unless
+ * the softirq is truly dead - and we reap it at the start or
+ * in pci_clean_dpci_irq.
+ */
+ if ( !dpci_kill(pirq_dpci) )
+ pirq_dpci->dom = NULL;
pirq_cleanup_check(info, d);
spin_unlock(&d->event_lock);
xfree(girq);
@@ -466,7 +498,6 @@ int pt_irq_destroy_bind(
msixtbl_pt_unregister(d, pirq);
if ( pt_irq_need_timer(pirq_dpci->flags) )
kill_timer(&pirq_dpci->timer);
- pirq_dpci->dom = NULL;
pirq_dpci->flags = 0;
dpci_kill(pirq_dpci);
pirq_cleanup_check(pirq, d);
@@ -491,7 +522,6 @@ void pt_pirq_init(struct domain *d, struct hvm_pirq_dpci
*dpci)
{
INIT_LIST_HEAD(&dpci->digl_list);
INIT_LIST_HEAD(&dpci->softirq_list);
- dpci->dom = d;
dpci->gmsi.dest_vcpu_id = -1;
}
--
1.9.3
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
http://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |