summaryrefslogtreecommitdiffstats
path: root/drivers/s390/cio/qdio_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/cio/qdio_main.c')
-rw-r--r--drivers/s390/cio/qdio_main.c150
1 files changed, 54 insertions, 96 deletions
diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c
index 0b4c09cf6a47..744f928a59ea 100644
--- a/drivers/s390/cio/qdio_main.c
+++ b/drivers/s390/cio/qdio_main.c
@@ -74,7 +74,7 @@ static inline int do_siga_input(struct subchannel_id schid, unsigned int mask)
* Note: For IQDC unicast queues only the highest priority queue is processed.
*/
static inline int do_siga_output(unsigned long schid, unsigned long mask,
- u32 *bb, unsigned int fc)
+ unsigned int *bb, unsigned int fc)
{
register unsigned long __fc asm("0") = fc;
register unsigned long __schid asm("1") = schid;
@@ -284,8 +284,7 @@ static int qdio_siga_sync(struct qdio_q *q, unsigned int output,
if (!need_siga_sync(q))
return 0;
- DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "siga-s:");
- DBF_DEV_HEX(DBF_INFO, q->irq_ptr, q, sizeof(void *));
+ DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "siga-s:%1d", q->nr);
qdio_perf_stat_inc(&perf_stats.siga_sync);
cc = do_siga_sync(q->irq_ptr->schid, output, input);
@@ -312,46 +311,37 @@ static inline int qdio_siga_sync_all(struct qdio_q *q)
return qdio_siga_sync(q, ~0U, ~0U);
}
-static inline int qdio_do_siga_output(struct qdio_q *q, unsigned int *busy_bit)
+static int qdio_siga_output(struct qdio_q *q, unsigned int *busy_bit)
{
- unsigned int fc = 0;
unsigned long schid;
+ unsigned int fc = 0;
+ u64 start_time = 0;
+ int cc;
- if (q->u.out.use_enh_siga) {
+ if (q->u.out.use_enh_siga)
fc = 3;
- }
- if (!is_qebsm(q))
- schid = *((u32 *)&q->irq_ptr->schid);
- else {
+
+ if (is_qebsm(q)) {
schid = q->irq_ptr->sch_token;
fc |= 0x80;
}
- return do_siga_output(schid, q->mask, busy_bit, fc);
-}
-
-static int qdio_siga_output(struct qdio_q *q)
-{
- int cc;
- u32 busy_bit;
- u64 start_time = 0;
+ else
+ schid = *((u32 *)&q->irq_ptr->schid);
- DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "siga-w:%1d", q->nr);
- qdio_perf_stat_inc(&perf_stats.siga_out);
again:
- cc = qdio_do_siga_output(q, &busy_bit);
- if (queue_type(q) == QDIO_IQDIO_QFMT && cc == 2 && busy_bit) {
- DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "siga-w bb:%2d", q->nr);
+ cc = do_siga_output(schid, q->mask, busy_bit, fc);
+
+ /* hipersocket busy condition */
+ if (*busy_bit) {
+ WARN_ON(queue_type(q) != QDIO_IQDIO_QFMT || cc != 2);
- if (!start_time)
+ if (!start_time) {
start_time = get_usecs();
- else if ((get_usecs() - start_time) < QDIO_BUSY_BIT_PATIENCE)
+ goto again;
+ }
+ if ((get_usecs() - start_time) < QDIO_BUSY_BIT_PATIENCE)
goto again;
}
-
- if (cc == 2 && busy_bit)
- cc |= QDIO_ERROR_SIGA_BUSY;
- if (cc)
- DBF_ERROR("%4x SIGA-W:%2d", SCH_NO(q), cc);
return cc;
}
@@ -399,7 +389,7 @@ inline void qdio_stop_polling(struct qdio_q *q)
static void announce_buffer_error(struct qdio_q *q, int count)
{
- q->qdio_error = QDIO_ERROR_SLSB_STATE;
+ q->qdio_error |= QDIO_ERROR_SLSB_STATE;
/* special handling for no target buffer empty */
if ((!q->is_input_q &&
@@ -716,68 +706,36 @@ static inline int qdio_outbound_q_moved(struct qdio_q *q)
return 0;
}
-/*
- * VM could present us cc=2 and busy bit set on SIGA-write
- * during reconfiguration of their Guest LAN (only in iqdio mode,
- * otherwise qdio is asynchronous and cc=2 and busy bit there will take
- * the queues down immediately).
- *
- * Therefore qdio_siga_output will try for a short time constantly,
- * if such a condition occurs. If it doesn't change, it will
- * increase the busy_siga_counter and save the timestamp, and
- * schedule the queue for later processing. qdio_outbound_processing
- * will check out the counter. If non-zero, it will call qdio_kick_outbound_q
- * as often as the value of the counter. This will attempt further SIGA
- * instructions. For each successful SIGA, the counter is
- * decreased, for failing SIGAs the counter remains the same, after
- * all. After some time of no movement, qdio_kick_outbound_q will
- * finally fail and reflect corresponding error codes to call
- * the upper layer module and have it take the queues down.
- *
- * Note that this is a change from the original HiperSockets design
- * (saying cc=2 and busy bit means take the queues down), but in
- * these days Guest LAN didn't exist... excessive cc=2 with busy bit
- * conditions will still take the queues down, but the threshold is
- * higher due to the Guest LAN environment.
- *
- * Called from outbound tasklet and do_QDIO handler.
- */
static void qdio_kick_outbound_q(struct qdio_q *q)
{
- int rc;
-
- DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "kickoutq:%1d", q->nr);
+ unsigned int busy_bit;
+ int cc;
if (!need_siga_out(q))
return;
- rc = qdio_siga_output(q);
- switch (rc) {
+ DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "siga-w:%1d", q->nr);
+ qdio_perf_stat_inc(&perf_stats.siga_out);
+
+ cc = qdio_siga_output(q, &busy_bit);
+ switch (cc) {
case 0:
- /* TODO: improve error handling for CC=0 case */
- if (q->u.out.timestamp)
- DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "cc2 rslv:%4x",
- atomic_read(&q->u.out.busy_siga_counter));
- /* went smooth this time, reset timestamp */
- q->u.out.timestamp = 0;
break;
- /* cc=2 and busy bit */
- case (2 | QDIO_ERROR_SIGA_BUSY):
- atomic_inc(&q->u.out.busy_siga_counter);
-
- /* if the last siga was successful, save timestamp here */
- if (!q->u.out.timestamp)
- q->u.out.timestamp = get_usecs();
-
- /* if we're in time, don't touch qdio_error */
- if (get_usecs() - q->u.out.timestamp < QDIO_BUSY_BIT_GIVE_UP) {
- tasklet_schedule(&q->tasklet);
- break;
+ case 2:
+ if (busy_bit) {
+ DBF_ERROR("%4x cc2 REP:%1d", SCH_NO(q), q->nr);
+ q->qdio_error = cc | QDIO_ERROR_SIGA_BUSY;
+ } else {
+ DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "siga-w cc2:%1d",
+ q->nr);
+ q->qdio_error = cc;
}
- DBF_ERROR("%4x cc2 REP:%1d", SCH_NO(q), q->nr);
- default:
- /* for plain cc=1, 2 or 3 */
- q->qdio_error = rc;
+ break;
+ case 1:
+ case 3:
+ DBF_ERROR("%4x SIGA-W:%1d", SCH_NO(q), cc);
+ q->qdio_error = cc;
+ break;
}
}
@@ -808,22 +766,18 @@ static void qdio_kick_outbound_handler(struct qdio_q *q)
static void __qdio_outbound_processing(struct qdio_q *q)
{
- int siga_attempts;
+ unsigned long flags;
qdio_perf_stat_inc(&perf_stats.tasklet_outbound);
-
- /* see comment in qdio_kick_outbound_q */
- siga_attempts = atomic_read(&q->u.out.busy_siga_counter);
- while (siga_attempts--) {
- atomic_dec(&q->u.out.busy_siga_counter);
- qdio_kick_outbound_q(q);
- }
+ spin_lock_irqsave(&q->lock, flags);
BUG_ON(atomic_read(&q->nr_buf_used) < 0);
if (qdio_outbound_q_moved(q))
qdio_kick_outbound_handler(q);
+ spin_unlock_irqrestore(&q->lock, flags);
+
if (queue_type(q) == QDIO_ZFCP_QFMT) {
if (!pci_out_supported(q) && !qdio_outbound_q_done(q))
tasklet_schedule(&q->tasklet);
@@ -1491,7 +1445,7 @@ static inline int buf_in_between(int bufnr, int start, int count)
static void handle_inbound(struct qdio_q *q, unsigned int callflags,
int bufnr, int count)
{
- int used, rc, diff;
+ int used, cc, diff;
if (!q->u.in.polling)
goto set;
@@ -1532,9 +1486,9 @@ set:
return;
if (need_siga_in(q)) {
- rc = qdio_siga_input(q);
- if (rc)
- q->qdio_error = rc;
+ cc = qdio_siga_input(q);
+ if (cc)
+ q->qdio_error = cc;
}
}
@@ -1581,6 +1535,10 @@ static void handle_outbound(struct qdio_q *q, unsigned int callflags,
while (count--)
qdio_kick_outbound_q(q);
}
+
+ /* report CC=2 conditions synchronously */
+ if (q->qdio_error)
+ __qdio_outbound_processing(q);
goto out;
}