]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
sparc: fix intermittent LDom hang waiting for vdc_port_up
authorThomas Tai <thomas.tai@oracle.com>
Tue, 17 Jan 2017 19:43:32 +0000 (11:43 -0800)
committerChuck Anderson <chuck.anderson@oracle.com>
Thu, 9 Mar 2017 03:30:10 +0000 (19:30 -0800)
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 <thomas.tai@oracle.com>
Reviewed-By: Liam Merwick <Liam.Merwick@oracle.com>
Signed-off-by: Allen Pais <allen.pais@oracle.com>
arch/sparc/kernel/ldc.c
drivers/block/sunvdc.c

index 3c90408ea2de44482963a3ffbca0386c3623a63e..db77bf5c456ec1a58519827b3c0358e6a9193193 100644 (file)
@@ -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;
 
index d61b7ea943719fdfd8416e7306addffa8bd18b82..36246ef85746e5291a0f9fa177cea32708b3745a 100644 (file)
@@ -917,7 +917,10 @@ static int vdc_port_up(struct vdc_port *port)
        comp.waiting_for = WAITING_FOR_LINK_UP;
        port->vio.cmp = &comp;
 
+       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;
 }