From: Thomas Tai Date: Tue, 17 Jan 2017 19:43:32 +0000 (-0800) Subject: sparc: fix intermittent LDom hang waiting for vdc_port_up X-Git-Tag: v4.1.12-92~1^2~14 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=3d910550ba0e428cd338c37c9785c66406f1be28;p=users%2Fjedix%2Flinux-maple.git sparc: fix intermittent LDom hang waiting for vdc_port_up When an LDom boots, sunvdc probes the disk using the LDC channel. If the channel was previously configured, we need to wait for the channel state to change from UP to RESETTING so that the seqid is properly reset in the primary. Otherwise the primary will expect that the ldc packet contains a seqid other than 0. Also disable ldc hypervisor interrupt before calling vio_port_up, because interrupts can happen once ldc_bind is called. disabling the interrupt ensures everything is configured before getting an interrupt request. orabug: 25409637 Signed-off-by: Thomas Tai Reviewed-By: Liam Merwick Signed-off-by: Allen Pais --- diff --git a/arch/sparc/kernel/ldc.c b/arch/sparc/kernel/ldc.c index 3c90408ea2de4..db77bf5c456ec 100644 --- a/arch/sparc/kernel/ldc.c +++ b/arch/sparc/kernel/ldc.c @@ -1359,6 +1359,9 @@ EXPORT_SYMBOL(ldc_free); int ldc_bind(struct ldc_channel *lp) { unsigned long hv_err, flags; + unsigned long head, tail, chan_state; + int limit = 1000; + bool wait_for_channel_reset = false; int err = -EINVAL; if (lp->state != LDC_STATE_INIT) @@ -1372,6 +1375,19 @@ int ldc_bind(struct ldc_channel *lp) lp->flags |= LDC_FLAG_REGISTERED_IRQS; err = -ENODEV; + + /* sun4v_ldc_rx_get_state() will return 0 if this ldc channel + * was previously configured. If so, after setting the new + * configuration, we need to wait for the channel to reset + * so that the seqid is reset to 0. + */ + hv_err = sun4v_ldc_rx_get_state(lp->id, + &head, + &tail, + &chan_state); + if (hv_err == 0) + wait_for_channel_reset = true; + hv_err = sun4v_ldc_tx_qconf(lp->id, 0, 0); if (hv_err) goto out_free_irqs; @@ -1400,10 +1416,25 @@ int ldc_bind(struct ldc_channel *lp) lp->tx_acked = lp->tx_head; - hv_err = sun4v_ldc_rx_get_state(lp->id, - &lp->rx_head, - &lp->rx_tail, - &lp->chan_state); + /* if the channel was previously configured, wait for + * channel state to go into resetting or wait until we + * have received a packet from the other end. + */ + while (limit-- > 0) { + hv_err = sun4v_ldc_rx_get_state(lp->id, + &lp->rx_head, + &lp->rx_tail, + &lp->chan_state); + if (!wait_for_channel_reset) + break; + if ((lp->chan_state == LDC_CHANNEL_RESETTING) || + (lp->rx_head != lp->rx_tail)) + break; + udelay(1); + } + if (limit <= 0) + pr_warn("ldc: channel %lu failed to reset\n", lp->id); + if (hv_err) goto out_unmap_rx; diff --git a/drivers/block/sunvdc.c b/drivers/block/sunvdc.c index d61b7ea943719..36246ef85746e 100644 --- a/drivers/block/sunvdc.c +++ b/drivers/block/sunvdc.c @@ -917,7 +917,10 @@ static int vdc_port_up(struct vdc_port *port) comp.waiting_for = WAITING_FOR_LINK_UP; port->vio.cmp = ∁ + ldc_disable_hv_intr(port->vio.lp); vio_port_up(&port->vio); + ldc_enable_hv_intr(port->vio.lp); + wait_for_completion(&comp.com); return comp.err; }