[S390] qdio: prevent starvation on PCI devices
[pandora-kernel.git] / drivers / s390 / cio / qdio_main.c
index 62b654a..f4fd6cd 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/kernel.h>
 #include <linux/timer.h>
 #include <linux/delay.h>
+#include <linux/gfp.h>
 #include <asm/atomic.h>
 #include <asm/debug.h>
 #include <asm/qdio.h>
@@ -392,6 +393,20 @@ static inline void qdio_stop_polling(struct qdio_q *q)
                set_buf_state(q, q->u.in.ack_start, SLSB_P_INPUT_NOT_INIT);
 }
 
+static inline void account_sbals(struct qdio_q *q, int count)
+{
+       int pos = 0;
+
+       q->q_stats.nr_sbal_total += count;
+       if (count == QDIO_MAX_BUFFERS_MASK) {
+               q->q_stats.nr_sbals[7]++;
+               return;
+       }
+       while (count >>= 1)
+               pos++;
+       q->q_stats.nr_sbals[pos]++;
+}
+
 static void announce_buffer_error(struct qdio_q *q, int count)
 {
        q->qdio_error |= QDIO_ERROR_SLSB_STATE;
@@ -487,16 +502,22 @@ static int get_inbound_buffer_frontier(struct qdio_q *q)
                q->first_to_check = add_buf(q->first_to_check, count);
                if (atomic_sub(count, &q->nr_buf_used) == 0)
                        qperf_inc(q, inbound_queue_full);
+               if (q->irq_ptr->perf_stat_enabled)
+                       account_sbals(q, count);
                break;
        case SLSB_P_INPUT_ERROR:
                announce_buffer_error(q, count);
                /* process the buffer, the upper layer will take care of it */
                q->first_to_check = add_buf(q->first_to_check, count);
                atomic_sub(count, &q->nr_buf_used);
+               if (q->irq_ptr->perf_stat_enabled)
+                       account_sbals_error(q, count);
                break;
        case SLSB_CU_INPUT_EMPTY:
        case SLSB_P_INPUT_NOT_INIT:
        case SLSB_P_INPUT_ACK:
+               if (q->irq_ptr->perf_stat_enabled)
+                       q->q_stats.nr_sbal_nop++;
                DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "in nop");
                break;
        default:
@@ -514,7 +535,7 @@ static int qdio_inbound_q_moved(struct qdio_q *q)
 
        if ((bufnr != q->last_move) || q->qdio_error) {
                q->last_move = bufnr;
-               if (!is_thinint_irq(q->irq_ptr) && !MACHINE_IS_VM)
+               if (!is_thinint_irq(q->irq_ptr) && MACHINE_IS_LPAR)
                        q->u.in.timestamp = get_usecs();
                return 1;
        } else
@@ -568,10 +589,11 @@ static void qdio_kick_handler(struct qdio_q *q)
        if (q->is_input_q) {
                qperf_inc(q, inbound_handler);
                DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "kih s:%02x c:%02x", start, count);
-       } else
+       } else {
                qperf_inc(q, outbound_handler);
                DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "koh: s:%02x c:%02x",
                              start, count);
+       }
 
        q->handler(q->irq_ptr->cdev, q->qdio_error, q->nr, start, count,
                   q->irq_ptr->int_parm);
@@ -584,7 +606,7 @@ static void qdio_kick_handler(struct qdio_q *q)
 static void __qdio_inbound_processing(struct qdio_q *q)
 {
        qperf_inc(q, tasklet_inbound);
-again:
+
        if (!qdio_inbound_q_moved(q))
                return;
 
@@ -593,7 +615,10 @@ again:
        if (!qdio_inbound_q_done(q)) {
                /* means poll time is not yet over */
                qperf_inc(q, tasklet_inbound_resched);
-               goto again;
+               if (likely(q->irq_ptr->state != QDIO_IRQ_STATE_STOPPED)) {
+                       tasklet_schedule(&q->tasklet);
+                       return;
+               }
        }
 
        qdio_stop_polling(q);
@@ -603,7 +628,8 @@ again:
         */
        if (!qdio_inbound_q_done(q)) {
                qperf_inc(q, tasklet_inbound_resched2);
-               goto again;
+               if (likely(q->irq_ptr->state != QDIO_IRQ_STATE_STOPPED))
+                       tasklet_schedule(&q->tasklet);
        }
 }
 
@@ -643,15 +669,21 @@ static int get_outbound_buffer_frontier(struct qdio_q *q)
 
                atomic_sub(count, &q->nr_buf_used);
                q->first_to_check = add_buf(q->first_to_check, count);
+               if (q->irq_ptr->perf_stat_enabled)
+                       account_sbals(q, count);
                break;
        case SLSB_P_OUTPUT_ERROR:
                announce_buffer_error(q, count);
                /* process the buffer, the upper layer will take care of it */
                q->first_to_check = add_buf(q->first_to_check, count);
                atomic_sub(count, &q->nr_buf_used);
+               if (q->irq_ptr->perf_stat_enabled)
+                       account_sbals_error(q, count);
                break;
        case SLSB_CU_OUTPUT_PRIMED:
                /* the adapter has not fetched the output yet */
+               if (q->irq_ptr->perf_stat_enabled)
+                       q->q_stats.nr_sbal_nop++;
                DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "out primed:%1d", q->nr);
                break;
        case SLSB_P_OUTPUT_NOT_INIT:
@@ -927,6 +959,9 @@ void qdio_int_handler(struct ccw_device *cdev, unsigned long intparm,
                return;
        }
 
+       if (irq_ptr->perf_stat_enabled)
+               irq_ptr->perf_stat.qdio_int++;
+
        if (IS_ERR(irb)) {
                switch (PTR_ERR(irb)) {
                case -EIO: