]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/s390/net/qeth_core_main.c
s390/qeth: cancel cmd on early error
[linux.git] / drivers / s390 / net / qeth_core_main.c
index e63e03143ca7fa2f6995c7a24dbef9c981c7f41c..b1a7a35a086ead5b242657c88f5da53c71df5161 100644 (file)
@@ -74,8 +74,7 @@ static void qeth_notify_skbs(struct qeth_qdio_out_q *queue,
 static void qeth_release_skbs(struct qeth_qdio_out_buffer *buf);
 static int qeth_init_qdio_out_buf(struct qeth_qdio_out_q *, int);
 
-struct workqueue_struct *qeth_wq;
-EXPORT_SYMBOL_GPL(qeth_wq);
+static struct workqueue_struct *qeth_wq;
 
 int qeth_card_hw_is_reachable(struct qeth_card *card)
 {
@@ -193,23 +192,6 @@ const char *qeth_get_cardname_short(struct qeth_card *card)
        return "n/a";
 }
 
-void qeth_set_recovery_task(struct qeth_card *card)
-{
-       card->recovery_task = current;
-}
-EXPORT_SYMBOL_GPL(qeth_set_recovery_task);
-
-void qeth_clear_recovery_task(struct qeth_card *card)
-{
-       card->recovery_task = NULL;
-}
-EXPORT_SYMBOL_GPL(qeth_clear_recovery_task);
-
-static bool qeth_is_recovery_task(const struct qeth_card *card)
-{
-       return card->recovery_task == current;
-}
-
 void qeth_set_allowed_threads(struct qeth_card *card, unsigned long threads,
                         int clear_start_mask)
 {
@@ -236,15 +218,6 @@ int qeth_threads_running(struct qeth_card *card, unsigned long threads)
 }
 EXPORT_SYMBOL_GPL(qeth_threads_running);
 
-int qeth_wait_for_threads(struct qeth_card *card, unsigned long threads)
-{
-       if (qeth_is_recovery_task(card))
-               return 0;
-       return wait_event_interruptible(card->wait_q,
-                       qeth_threads_running(card, threads) == 0);
-}
-EXPORT_SYMBOL_GPL(qeth_wait_for_threads);
-
 void qeth_clear_working_pool_list(struct qeth_card *card)
 {
        struct qeth_buffer_pool_entry *pool_entry, *tmp;
@@ -566,6 +539,7 @@ static int __qeth_issue_next_read(struct qeth_card *card)
                QETH_DBF_MESSAGE(2, "error %i on device %x when starting next read ccw!\n",
                                 rc, CARD_DEVID(card));
                atomic_set(&channel->irq_pending, 0);
+               qeth_release_buffer(channel, iob);
                card->read_or_write_problem = 1;
                qeth_schedule_recovery(card);
                wake_up(&card->wait_q);
@@ -592,6 +566,7 @@ static struct qeth_reply *qeth_alloc_reply(struct qeth_card *card)
        if (reply) {
                refcount_set(&reply->refcnt, 1);
                atomic_set(&reply->received, 0);
+               init_waitqueue_head(&reply->wait_q);
        }
        return reply;
 }
@@ -607,6 +582,26 @@ static void qeth_put_reply(struct qeth_reply *reply)
                kfree(reply);
 }
 
+static void qeth_enqueue_reply(struct qeth_card *card, struct qeth_reply *reply)
+{
+       spin_lock_irq(&card->lock);
+       list_add_tail(&reply->list, &card->cmd_waiter_list);
+       spin_unlock_irq(&card->lock);
+}
+
+static void qeth_dequeue_reply(struct qeth_card *card, struct qeth_reply *reply)
+{
+       spin_lock_irq(&card->lock);
+       list_del(&reply->list);
+       spin_unlock_irq(&card->lock);
+}
+
+static void qeth_notify_reply(struct qeth_reply *reply)
+{
+       atomic_inc(&reply->received);
+       wake_up(&reply->wait_q);
+}
+
 static void qeth_issue_ipa_msg(struct qeth_ipa_cmd *cmd, int rc,
                struct qeth_card *card)
 {
@@ -683,19 +678,15 @@ static struct qeth_ipa_cmd *qeth_check_ipa_data(struct qeth_card *card,
 
 void qeth_clear_ipacmd_list(struct qeth_card *card)
 {
-       struct qeth_reply *reply, *r;
+       struct qeth_reply *reply;
        unsigned long flags;
 
        QETH_CARD_TEXT(card, 4, "clipalst");
 
        spin_lock_irqsave(&card->lock, flags);
-       list_for_each_entry_safe(reply, r, &card->cmd_waiter_list, list) {
-               qeth_get_reply(reply);
+       list_for_each_entry(reply, &card->cmd_waiter_list, list) {
                reply->rc = -EIO;
-               atomic_inc(&reply->received);
-               list_del_init(&reply->list);
-               wake_up(&reply->wait_q);
-               qeth_put_reply(reply);
+               qeth_notify_reply(reply);
        }
        spin_unlock_irqrestore(&card->lock, flags);
 }
@@ -752,6 +743,10 @@ void qeth_release_buffer(struct qeth_channel *channel,
        spin_lock_irqsave(&channel->iob_lock, flags);
        iob->state = BUF_STATE_FREE;
        iob->callback = qeth_send_control_data_cb;
+       if (iob->reply) {
+               qeth_put_reply(iob->reply);
+               iob->reply = NULL;
+       }
        iob->rc = 0;
        spin_unlock_irqrestore(&channel->iob_lock, flags);
        wake_up(&channel->wait_q);
@@ -765,6 +760,17 @@ static void qeth_release_buffer_cb(struct qeth_card *card,
        qeth_release_buffer(channel, iob);
 }
 
+static void qeth_cancel_cmd(struct qeth_cmd_buffer *iob, int rc)
+{
+       struct qeth_reply *reply = iob->reply;
+
+       if (reply) {
+               reply->rc = rc;
+               qeth_notify_reply(reply);
+       }
+       qeth_release_buffer(iob->channel, iob);
+}
+
 static struct qeth_cmd_buffer *qeth_get_buffer(struct qeth_channel *channel)
 {
        struct qeth_cmd_buffer *buffer = NULL;
@@ -800,9 +806,10 @@ static void qeth_send_control_data_cb(struct qeth_card *card,
                                      struct qeth_cmd_buffer *iob)
 {
        struct qeth_ipa_cmd *cmd = NULL;
-       struct qeth_reply *reply, *r;
+       struct qeth_reply *reply = NULL;
+       struct qeth_reply *r;
        unsigned long flags;
-       int keep_reply;
+       int keep_reply = 0;
        int rc = 0;
 
        QETH_CARD_TEXT(card, 4, "sndctlcb");
@@ -834,44 +841,40 @@ static void qeth_send_control_data_cb(struct qeth_card *card,
                        goto out;
        }
 
+       /* match against pending cmd requests */
        spin_lock_irqsave(&card->lock, flags);
-       list_for_each_entry_safe(reply, r, &card->cmd_waiter_list, list) {
-               if ((reply->seqno == QETH_IDX_COMMAND_SEQNO) ||
-                   ((cmd) && (reply->seqno == cmd->hdr.seqno))) {
+       list_for_each_entry(r, &card->cmd_waiter_list, list) {
+               if ((r->seqno == QETH_IDX_COMMAND_SEQNO) ||
+                   (cmd && (r->seqno == cmd->hdr.seqno))) {
+                       reply = r;
+                       /* take the object outside the lock */
                        qeth_get_reply(reply);
-                       list_del_init(&reply->list);
-                       spin_unlock_irqrestore(&card->lock, flags);
-                       keep_reply = 0;
-                       if (reply->callback != NULL) {
-                               if (cmd) {
-                                       reply->offset = (__u16)((char *)cmd -
-                                                       (char *)iob->data);
-                                       keep_reply = reply->callback(card,
-                                                       reply,
-                                                       (unsigned long)cmd);
-                               } else
-                                       keep_reply = reply->callback(card,
-                                                       reply,
-                                                       (unsigned long)iob);
-                       }
-                       if (cmd)
-                               reply->rc = (u16) cmd->hdr.return_code;
-                       else if (iob->rc)
-                               reply->rc = iob->rc;
-                       if (keep_reply) {
-                               spin_lock_irqsave(&card->lock, flags);
-                               list_add_tail(&reply->list,
-                                             &card->cmd_waiter_list);
-                               spin_unlock_irqrestore(&card->lock, flags);
-                       } else {
-                               atomic_inc(&reply->received);
-                               wake_up(&reply->wait_q);
-                       }
-                       qeth_put_reply(reply);
-                       goto out;
+                       break;
                }
        }
        spin_unlock_irqrestore(&card->lock, flags);
+
+       if (!reply)
+               goto out;
+
+       if (reply->callback) {
+               if (cmd) {
+                       reply->offset = (u16)((char *)cmd - (char *)iob->data);
+                       keep_reply = reply->callback(card, reply,
+                                                    (unsigned long)cmd);
+               } else
+                       keep_reply = reply->callback(card, reply,
+                                                    (unsigned long)iob);
+       }
+       if (cmd)
+               reply->rc = (u16) cmd->hdr.return_code;
+       else if (iob->rc)
+               reply->rc = iob->rc;
+
+       if (!keep_reply)
+               qeth_notify_reply(reply);
+       qeth_put_reply(reply);
+
 out:
        memcpy(&card->seqno.pdu_hdr_ack,
                QETH_PDU_HEADER_SEQ_NO(iob->data),
@@ -1002,9 +1005,8 @@ static int qeth_get_problem(struct qeth_card *card, struct ccw_device *cdev,
        return 0;
 }
 
-static long qeth_check_irb_error(struct qeth_card *card,
-                                struct ccw_device *cdev, unsigned long intparm,
-                                struct irb *irb)
+static int qeth_check_irb_error(struct qeth_card *card, struct ccw_device *cdev,
+                               unsigned long intparm, struct irb *irb)
 {
        if (!IS_ERR(irb))
                return 0;
@@ -1015,7 +1017,7 @@ static long qeth_check_irb_error(struct qeth_card *card,
                                 CCW_DEVID(cdev));
                QETH_CARD_TEXT(card, 2, "ckirberr");
                QETH_CARD_TEXT_(card, 2, "  rc%d", -EIO);
-               break;
+               return -EIO;
        case -ETIMEDOUT:
                dev_warn(&cdev->dev, "A hardware operation timed out"
                        " on the device\n");
@@ -1027,14 +1029,14 @@ static long qeth_check_irb_error(struct qeth_card *card,
                                wake_up(&card->wait_q);
                        }
                }
-               break;
+               return -ETIMEDOUT;
        default:
                QETH_DBF_MESSAGE(2, "unknown error %ld on channel %x\n",
                                 PTR_ERR(irb), CCW_DEVID(cdev));
                QETH_CARD_TEXT(card, 2, "ckirberr");
                QETH_CARD_TEXT(card, 2, "  rc???");
+               return PTR_ERR(irb);
        }
-       return PTR_ERR(irb);
 }
 
 static void qeth_irq(struct ccw_device *cdev, unsigned long intparm,
@@ -1069,10 +1071,11 @@ static void qeth_irq(struct ccw_device *cdev, unsigned long intparm,
        if (qeth_intparm_is_iob(intparm))
                iob = (struct qeth_cmd_buffer *) __va((addr_t)intparm);
 
-       if (qeth_check_irb_error(card, cdev, intparm, irb)) {
+       rc = qeth_check_irb_error(card, cdev, intparm, irb);
+       if (rc) {
                /* IO was terminated, free its resources. */
                if (iob)
-                       qeth_release_buffer(iob->channel, iob);
+                       qeth_cancel_cmd(iob, rc);
                atomic_set(&channel->irq_pending, 0);
                wake_up(&card->wait_q);
                return;
@@ -1127,6 +1130,8 @@ static void qeth_irq(struct ccw_device *cdev, unsigned long intparm,
                rc = qeth_get_problem(card, cdev, irb);
                if (rc) {
                        card->read_or_write_problem = 1;
+                       if (iob)
+                               qeth_cancel_cmd(iob, rc);
                        qeth_clear_ipacmd_list(card);
                        qeth_schedule_recovery(card);
                        goto out;
@@ -1430,7 +1435,6 @@ static void qeth_setup_card(struct qeth_card *card)
        spin_lock_init(&card->thread_mask_lock);
        mutex_init(&card->conf_mutex);
        mutex_init(&card->discipline_mutex);
-       mutex_init(&card->vid_list_mutex);
        INIT_WORK(&card->kernel_thread_starter, qeth_start_kernel_thread);
        INIT_LIST_HEAD(&card->cmd_waiter_list);
        init_waitqueue_head(&card->wait_q);
@@ -1466,6 +1470,10 @@ static struct qeth_card *qeth_alloc_card(struct ccwgroup_device *gdev)
        CARD_RDEV(card) = gdev->cdev[0];
        CARD_WDEV(card) = gdev->cdev[1];
        CARD_DDEV(card) = gdev->cdev[2];
+
+       card->event_wq = alloc_ordered_workqueue("%s", 0, dev_name(&gdev->dev));
+       if (!card->event_wq)
+               goto out_wq;
        if (qeth_setup_channel(&card->read, true))
                goto out_ip;
        if (qeth_setup_channel(&card->write, true))
@@ -1481,6 +1489,8 @@ static struct qeth_card *qeth_alloc_card(struct ccwgroup_device *gdev)
 out_channel:
        qeth_clean_channel(&card->read);
 out_ip:
+       destroy_workqueue(card->event_wq);
+out_wq:
        dev_set_drvdata(&gdev->dev, NULL);
        kfree(card);
 out:
@@ -1809,6 +1819,7 @@ static int qeth_idx_activate_get_answer(struct qeth_card *card,
                QETH_DBF_MESSAGE(2, "Error2 in activating channel rc=%d\n", rc);
                QETH_DBF_TEXT_(SETUP, 2, "2err%d", rc);
                atomic_set(&channel->irq_pending, 0);
+               qeth_release_buffer(channel, iob);
                wake_up(&card->wait_q);
                return rc;
        }
@@ -1878,6 +1889,7 @@ static int qeth_idx_activate_channel(struct qeth_card *card,
                        rc);
                QETH_DBF_TEXT_(SETUP, 2, "1err%d", rc);
                atomic_set(&channel->irq_pending, 0);
+               qeth_release_buffer(channel, iob);
                wake_up(&card->wait_q);
                return rc;
        }
@@ -2008,7 +2020,7 @@ void qeth_prepare_control_data(struct qeth_card *card, int len,
        card->seqno.pdu_hdr++;
        memcpy(QETH_PDU_HEADER_ACK_SEQ_NO(iob->data),
               &card->seqno.pdu_hdr_ack, QETH_SEQ_NO_LENGTH);
-       QETH_DBF_HEX(CTRL, 2, iob->data, QETH_DBF_CTRL_LEN);
+       QETH_DBF_HEX(CTRL, 2, iob->data, min(len, QETH_DBF_CTRL_LEN));
 }
 EXPORT_SYMBOL_GPL(qeth_prepare_control_data);
 
@@ -2037,12 +2049,12 @@ EXPORT_SYMBOL_GPL(qeth_prepare_control_data);
  * field 'param' of the structure qeth_reply.
  */
 
-int qeth_send_control_data(struct qeth_card *card, int len,
-               struct qeth_cmd_buffer *iob,
-               int (*reply_cb)(struct qeth_card *cb_card,
-                               struct qeth_reply *cb_reply,
-                               unsigned long cb_cmd),
-               void *reply_param)
+static int qeth_send_control_data(struct qeth_card *card, int len,
+                                 struct qeth_cmd_buffer *iob,
+                                 int (*reply_cb)(struct qeth_card *cb_card,
+                                                 struct qeth_reply *cb_reply,
+                                                 unsigned long cb_cmd),
+                                 void *reply_param)
 {
        struct qeth_channel *channel = iob->channel;
        int rc;
@@ -2058,12 +2070,15 @@ int qeth_send_control_data(struct qeth_card *card, int len,
        }
        reply = qeth_alloc_reply(card);
        if (!reply) {
+               qeth_release_buffer(channel, iob);
                return -ENOMEM;
        }
        reply->callback = reply_cb;
        reply->param = reply_param;
 
-       init_waitqueue_head(&reply->wait_q);
+       /* pairs with qeth_release_buffer(): */
+       qeth_get_reply(reply);
+       iob->reply = reply;
 
        while (atomic_cmpxchg(&channel->irq_pending, 0, 1)) ;
 
@@ -2078,9 +2093,7 @@ int qeth_send_control_data(struct qeth_card *card, int len,
        }
        qeth_prepare_control_data(card, len, iob);
 
-       spin_lock_irq(&card->lock);
-       list_add_tail(&reply->list, &card->cmd_waiter_list);
-       spin_unlock_irq(&card->lock);
+       qeth_enqueue_reply(card, reply);
 
        timeout = jiffies + event_timeout;
 
@@ -2093,10 +2106,8 @@ int qeth_send_control_data(struct qeth_card *card, int len,
                QETH_DBF_MESSAGE(2, "qeth_send_control_data on device %x: ccw_device_start rc = %i\n",
                                 CARD_DEVID(card), rc);
                QETH_CARD_TEXT_(card, 2, " err%d", rc);
-               spin_lock_irq(&card->lock);
-               list_del_init(&reply->list);
+               qeth_dequeue_reply(card, reply);
                qeth_put_reply(reply);
-               spin_unlock_irq(&card->lock);
                qeth_release_buffer(channel, iob);
                atomic_set(&channel->irq_pending, 0);
                wake_up(&card->wait_q);
@@ -2118,21 +2129,16 @@ int qeth_send_control_data(struct qeth_card *card, int len,
                }
        }
 
+       qeth_dequeue_reply(card, reply);
        rc = reply->rc;
        qeth_put_reply(reply);
        return rc;
 
 time_err:
-       reply->rc = -ETIME;
-       spin_lock_irq(&card->lock);
-       list_del_init(&reply->list);
-       spin_unlock_irq(&card->lock);
-       atomic_inc(&reply->received);
-       rc = reply->rc;
+       qeth_dequeue_reply(card, reply);
        qeth_put_reply(reply);
-       return rc;
+       return -ETIME;
 }
-EXPORT_SYMBOL_GPL(qeth_send_control_data);
 
 static int qeth_cm_enable_cb(struct qeth_card *card, struct qeth_reply *reply,
                unsigned long data)
@@ -2389,11 +2395,12 @@ static int qeth_init_qdio_out_buf(struct qeth_qdio_out_q *q, int bidx)
        return 0;
 }
 
-static void qeth_free_qdio_out_buf(struct qeth_qdio_out_q *q)
+static void qeth_free_output_queue(struct qeth_qdio_out_q *q)
 {
        if (!q)
                return;
 
+       qeth_clear_outq_buffers(q, 1);
        qdio_free_buffers(q->qdio_bufs, QDIO_MAX_BUFFERS_PER_Q);
        kfree(q);
 }
@@ -2467,10 +2474,8 @@ static int qeth_alloc_qdio_buffers(struct qeth_card *card)
                card->qdio.out_qs[i]->bufs[j] = NULL;
        }
 out_freeoutq:
-       while (i > 0) {
-               qeth_free_qdio_out_buf(card->qdio.out_qs[--i]);
-               qeth_clear_outq_buffers(card->qdio.out_qs[i], 1);
-       }
+       while (i > 0)
+               qeth_free_output_queue(card->qdio.out_qs[--i]);
        kfree(card->qdio.out_qs);
        card->qdio.out_qs = NULL;
 out_freepool:
@@ -2503,10 +2508,8 @@ static void qeth_free_qdio_buffers(struct qeth_card *card)
        qeth_free_buffer_pool(card);
        /* free outbound qdio_qs */
        if (card->qdio.out_qs) {
-               for (i = 0; i < card->qdio.no_out_queues; ++i) {
-                       qeth_clear_outq_buffers(card->qdio.out_qs[i], 1);
-                       qeth_free_qdio_out_buf(card->qdio.out_qs[i]);
-               }
+               for (i = 0; i < card->qdio.no_out_queues; i++)
+                       qeth_free_output_queue(card->qdio.out_qs[i]);
                kfree(card->qdio.out_qs);
                card->qdio.out_qs = NULL;
        }
@@ -2835,14 +2838,20 @@ static void qeth_fill_ipacmd_header(struct qeth_card *card,
        cmd->hdr.prot_version = prot;
 }
 
-void qeth_prepare_ipa_cmd(struct qeth_card *card, struct qeth_cmd_buffer *iob)
+void qeth_prepare_ipa_cmd(struct qeth_card *card, struct qeth_cmd_buffer *iob,
+                         u16 cmd_length)
 {
+       u16 total_length = IPA_PDU_HEADER_SIZE + cmd_length;
        u8 prot_type = qeth_mpc_select_prot_type(card);
 
        memcpy(iob->data, IPA_PDU_HEADER, IPA_PDU_HEADER_SIZE);
+       memcpy(QETH_IPA_PDU_LEN_TOTAL(iob->data), &total_length, 2);
        memcpy(QETH_IPA_CMD_PROT_TYPE(iob->data), &prot_type, 1);
+       memcpy(QETH_IPA_PDU_LEN_PDU1(iob->data), &cmd_length, 2);
+       memcpy(QETH_IPA_PDU_LEN_PDU2(iob->data), &cmd_length, 2);
        memcpy(QETH_IPA_CMD_DEST_ADDR(iob->data),
               &card->token.ulp_connection_r, QETH_MPC_TOKEN_LENGTH);
+       memcpy(QETH_IPA_PDU_LEN_PDU3(iob->data), &cmd_length, 2);
 }
 EXPORT_SYMBOL_GPL(qeth_prepare_ipa_cmd);
 
@@ -2853,7 +2862,7 @@ struct qeth_cmd_buffer *qeth_get_ipacmd_buffer(struct qeth_card *card,
 
        iob = qeth_get_buffer(&card->write);
        if (iob) {
-               qeth_prepare_ipa_cmd(card, iob);
+               qeth_prepare_ipa_cmd(card, iob, sizeof(struct qeth_ipa_cmd));
                qeth_fill_ipacmd_header(card, __ipa_cmd(iob), ipacmd, prot);
        } else {
                dev_warn(&card->gdev->dev,
@@ -2877,11 +2886,12 @@ int qeth_send_ipa_cmd(struct qeth_card *card, struct qeth_cmd_buffer *iob,
                        unsigned long),
                void *reply_param)
 {
+       u16 length;
        int rc;
 
        QETH_CARD_TEXT(card, 4, "sendipa");
-       rc = qeth_send_control_data(card, IPA_CMD_LENGTH,
-                                               iob, reply_cb, reply_param);
+       memcpy(&length, QETH_IPA_PDU_LEN_TOTAL(iob->data), 2);
+       rc = qeth_send_control_data(card, length, iob, reply_cb, reply_param);
        if (rc == -ETIME) {
                qeth_clear_ipacmd_list(card);
                qeth_schedule_recovery(card);
@@ -3558,8 +3568,6 @@ static void qeth_qdio_cq_handler(struct qeth_card *card, unsigned int qdio_err,
        card->qdio.c_q->next_buf_to_init = (card->qdio.c_q->next_buf_to_init
                                   + count) % QDIO_MAX_BUFFERS_PER_Q;
 
-       netif_wake_queue(card->dev);
-
        if (card->options.performance_stats) {
                int delta_t = qeth_get_micros();
                delta_t -= card->perf_stats.cq_start_time;
@@ -3928,7 +3936,6 @@ static int qeth_fill_buffer(struct qeth_qdio_out_q *queue,
 {
        struct qdio_buffer *buffer = buf->buffer;
        bool is_first_elem = true;
-       int flush_cnt = 0;
 
        __skb_queue_tail(&buf->skb_list, skb);
 
@@ -3949,24 +3956,22 @@ static int qeth_fill_buffer(struct qeth_qdio_out_q *queue,
 
        if (!queue->do_pack) {
                QETH_CARD_TEXT(queue->card, 6, "fillbfnp");
-               /* set state to PRIMED -> will be flushed */
-               atomic_set(&buf->state, QETH_QDIO_BUF_PRIMED);
-               flush_cnt = 1;
        } else {
                QETH_CARD_TEXT(queue->card, 6, "fillbfpa");
                if (queue->card->options.performance_stats)
                        queue->card->perf_stats.skbs_sent_pack++;
-               if (buf->next_element_to_fill >=
-                               QETH_MAX_BUFFER_ELEMENTS(queue->card)) {
-                       /*
-                        * packed buffer if full -> set state PRIMED
-                        * -> will be flushed
-                        */
-                       atomic_set(&buf->state, QETH_QDIO_BUF_PRIMED);
-                       flush_cnt = 1;
-               }
+
+               /* If the buffer still has free elements, keep using it. */
+               if (buf->next_element_to_fill <
+                   QETH_MAX_BUFFER_ELEMENTS(queue->card))
+                       return 0;
        }
-       return flush_cnt;
+
+       /* flush out the buffer */
+       atomic_set(&buf->state, QETH_QDIO_BUF_PRIMED);
+       queue->next_buf_to_fill = (queue->next_buf_to_fill + 1) %
+                                 QDIO_MAX_BUFFERS_PER_Q;
+       return 1;
 }
 
 static int qeth_do_send_packet_fast(struct qeth_qdio_out_q *queue,
@@ -3982,7 +3987,6 @@ static int qeth_do_send_packet_fast(struct qeth_qdio_out_q *queue,
         */
        if (atomic_read(&buffer->state) != QETH_QDIO_BUF_EMPTY)
                return -EBUSY;
-       queue->next_buf_to_fill = (index + 1) % QDIO_MAX_BUFFERS_PER_Q;
        qeth_fill_buffer(queue, buffer, skb, hdr, offset, hd_len);
        qeth_flush_buffers(queue, index, 1);
        return 0;
@@ -4040,10 +4044,9 @@ int qeth_do_send_packet(struct qeth_card *card, struct qeth_qdio_out_q *queue,
                        }
                }
        }
-       tmp = qeth_fill_buffer(queue, buffer, skb, hdr, offset, hd_len);
-       queue->next_buf_to_fill = (queue->next_buf_to_fill + tmp) %
-                                 QDIO_MAX_BUFFERS_PER_Q;
-       flush_count += tmp;
+
+       flush_count += qeth_fill_buffer(queue, buffer, skb, hdr, offset,
+                                       hd_len);
        if (flush_count)
                qeth_flush_buffers(queue, start_index, flush_count);
        else if (!atomic_read(&queue->set_pci_flags_count))
@@ -4487,27 +4490,6 @@ static int qeth_mdio_read(struct net_device *dev, int phy_id, int regnum)
        return rc;
 }
 
-static int qeth_send_ipa_snmp_cmd(struct qeth_card *card,
-               struct qeth_cmd_buffer *iob, int len,
-               int (*reply_cb)(struct qeth_card *, struct qeth_reply *,
-                       unsigned long),
-               void *reply_param)
-{
-       u16 s1, s2;
-
-       QETH_CARD_TEXT(card, 4, "sendsnmp");
-
-       /* adjust PDU length fields in IPA_PDU_HEADER */
-       s1 = (u32) IPA_PDU_HEADER_SIZE + len;
-       s2 = (u32) len;
-       memcpy(QETH_IPA_PDU_LEN_TOTAL(iob->data), &s1, 2);
-       memcpy(QETH_IPA_PDU_LEN_PDU1(iob->data), &s2, 2);
-       memcpy(QETH_IPA_PDU_LEN_PDU2(iob->data), &s2, 2);
-       memcpy(QETH_IPA_PDU_LEN_PDU3(iob->data), &s2, 2);
-       return qeth_send_control_data(card, IPA_PDU_HEADER_SIZE + len, iob,
-                                     reply_cb, reply_param);
-}
-
 static int qeth_snmp_command_cb(struct qeth_card *card,
                struct qeth_reply *reply, unsigned long sdata)
 {
@@ -4613,10 +4595,13 @@ static int qeth_snmp_command(struct qeth_card *card, char __user *udata)
                rc = -ENOMEM;
                goto out;
        }
+
+       /* for large requests, fix-up the length fields: */
+       qeth_prepare_ipa_cmd(card, iob, QETH_SETADP_BASE_LEN + req_len);
+
        cmd = __ipa_cmd(iob);
        memcpy(&cmd->data.setadapterparms.data.snmp, &ureq->cmd, req_len);
-       rc = qeth_send_ipa_snmp_cmd(card, iob, QETH_SETADP_BASE_LEN + req_len,
-                                   qeth_snmp_command_cb, (void *)&qinfo);
+       rc = qeth_send_ipa_cmd(card, iob, qeth_snmp_command_cb, &qinfo);
        if (rc)
                QETH_DBF_MESSAGE(2, "SNMP command failed on device %x: (%#x)\n",
                                 CARD_DEVID(card), rc);
@@ -5028,6 +5013,7 @@ static void qeth_core_free_card(struct qeth_card *card)
        qeth_clean_channel(&card->read);
        qeth_clean_channel(&card->write);
        qeth_clean_channel(&card->data);
+       destroy_workqueue(card->event_wq);
        qeth_free_qdio_buffers(card);
        unregister_service_level(&card->qeth_service_level);
        dev_set_drvdata(&card->gdev->dev, NULL);
@@ -5154,13 +5140,6 @@ int qeth_core_hardsetup_card(struct qeth_card *card, bool *carrier_ok)
                *carrier_ok = true;
        }
 
-       if (qeth_netdev_is_registered(card->dev)) {
-               if (*carrier_ok)
-                       netif_carrier_on(card->dev);
-               else
-                       netif_carrier_off(card->dev);
-       }
-
        card->options.ipa4.supported_funcs = 0;
        card->options.ipa6.supported_funcs = 0;
        card->options.adp.supported_funcs = 0;
@@ -5937,9 +5916,6 @@ int qeth_do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
        if (!card)
                return -ENODEV;
 
-       if (!qeth_card_hw_is_reachable(card))
-               return -ENODEV;
-
        if (card->info.type == QETH_CARD_TYPE_OSN)
                return -EPERM;
 
@@ -6250,8 +6226,6 @@ int qeth_core_ethtool_get_link_ksettings(struct net_device *netdev,
        /* Check if we can obtain more accurate information.     */
        /* If QUERY_CARD_INFO command is not supported or fails, */
        /* just return the heuristics that was filled above.     */
-       if (!qeth_card_hw_is_reachable(card))
-               return -ENODEV;
        rc = qeth_query_card_info(card, &carrier_info);
        if (rc == -EOPNOTSUPP) /* for old hardware, return heuristic */
                return 0;
@@ -6316,119 +6290,91 @@ int qeth_core_ethtool_get_link_ksettings(struct net_device *netdev,
 }
 EXPORT_SYMBOL_GPL(qeth_core_ethtool_get_link_ksettings);
 
-/* Callback to handle checksum offload command reply from OSA card.
- * Verify that required features have been enabled on the card.
- * Return error in hdr->return_code as this value is checked by caller.
- *
- * Always returns zero to indicate no further messages from the OSA card.
- */
-static int qeth_ipa_checksum_run_cmd_cb(struct qeth_card *card,
-                                       struct qeth_reply *reply,
-                                       unsigned long data)
+static int qeth_start_csum_cb(struct qeth_card *card, struct qeth_reply *reply,
+                             unsigned long data)
 {
        struct qeth_ipa_cmd *cmd = (struct qeth_ipa_cmd *) data;
-       struct qeth_checksum_cmd *chksum_cb =
-                               (struct qeth_checksum_cmd *)reply->param;
+       u32 *features = reply->param;
 
-       QETH_CARD_TEXT(card, 4, "chkdoccb");
        if (qeth_setassparms_inspect_rc(cmd))
                return 0;
 
-       memset(chksum_cb, 0, sizeof(*chksum_cb));
-       if (cmd->data.setassparms.hdr.command_code == IPA_CMD_ASS_START) {
-               chksum_cb->supported =
-                               cmd->data.setassparms.data.chksum.supported;
-               QETH_CARD_TEXT_(card, 3, "strt:%x", chksum_cb->supported);
-       }
-       if (cmd->data.setassparms.hdr.command_code == IPA_CMD_ASS_ENABLE) {
-               chksum_cb->supported =
-                               cmd->data.setassparms.data.chksum.supported;
-               chksum_cb->enabled =
-                               cmd->data.setassparms.data.chksum.enabled;
-               QETH_CARD_TEXT_(card, 3, "supp:%x", chksum_cb->supported);
-               QETH_CARD_TEXT_(card, 3, "enab:%x", chksum_cb->enabled);
-       }
+       *features = cmd->data.setassparms.data.flags_32bit;
        return 0;
 }
 
-/* Send command to OSA card and check results. */
-static int qeth_ipa_checksum_run_cmd(struct qeth_card *card,
-                                    enum qeth_ipa_funcs ipa_func,
-                                    __u16 cmd_code, long data,
-                                    struct qeth_checksum_cmd *chksum_cb,
-                                    enum qeth_prot_versions prot)
+static int qeth_set_csum_off(struct qeth_card *card, enum qeth_ipa_funcs cstype,
+                            enum qeth_prot_versions prot)
 {
-       struct qeth_cmd_buffer *iob;
-
-       QETH_CARD_TEXT(card, 4, "chkdocmd");
-       iob = qeth_get_setassparms_cmd(card, ipa_func, cmd_code,
-                                      sizeof(__u32), prot);
-       if (!iob)
-               return -ENOMEM;
-
-       __ipa_cmd(iob)->data.setassparms.data.flags_32bit = (__u32) data;
-       return qeth_send_ipa_cmd(card, iob, qeth_ipa_checksum_run_cmd_cb,
-                                chksum_cb);
+       return qeth_send_simple_setassparms_prot(card, cstype,
+                                                IPA_CMD_ASS_STOP, 0, prot);
 }
 
-static int qeth_send_checksum_on(struct qeth_card *card, int cstype,
-                                enum qeth_prot_versions prot)
+static int qeth_set_csum_on(struct qeth_card *card, enum qeth_ipa_funcs cstype,
+                           enum qeth_prot_versions prot)
 {
        u32 required_features = QETH_IPA_CHECKSUM_UDP | QETH_IPA_CHECKSUM_TCP;
-       struct qeth_checksum_cmd chksum_cb;
+       struct qeth_cmd_buffer *iob;
+       struct qeth_ipa_caps caps;
+       u32 features;
        int rc;
 
-       if (prot == QETH_PROT_IPV4)
+       /* some L3 HW requires combined L3+L4 csum offload: */
+       if (IS_LAYER3(card) && prot == QETH_PROT_IPV4 &&
+           cstype == IPA_OUTBOUND_CHECKSUM)
                required_features |= QETH_IPA_CHECKSUM_IP_HDR;
-       rc = qeth_ipa_checksum_run_cmd(card, cstype, IPA_CMD_ASS_START, 0,
-                                      &chksum_cb, prot);
-       if (!rc) {
-               if ((required_features & chksum_cb.supported) !=
-                   required_features)
-                       rc = -EIO;
-               else if (!(QETH_IPA_CHECKSUM_LP2LP & chksum_cb.supported) &&
-                        cstype == IPA_INBOUND_CHECKSUM)
-                       dev_warn(&card->gdev->dev,
-                                "Hardware checksumming is performed only if %s and its peer use different OSA Express 3 ports\n",
-                                QETH_CARD_IFNAME(card));
-       }
-       if (rc) {
-               qeth_send_simple_setassparms_prot(card, cstype,
-                                                 IPA_CMD_ASS_STOP, 0, prot);
-               dev_warn(&card->gdev->dev,
-                        "Starting HW IPv%d checksumming for %s failed, using SW checksumming\n",
-                        prot, QETH_CARD_IFNAME(card));
+
+       iob = qeth_get_setassparms_cmd(card, cstype, IPA_CMD_ASS_START, 0,
+                                      prot);
+       if (!iob)
+               return -ENOMEM;
+
+       rc = qeth_send_ipa_cmd(card, iob, qeth_start_csum_cb, &features);
+       if (rc)
                return rc;
+
+       if ((required_features & features) != required_features) {
+               qeth_set_csum_off(card, cstype, prot);
+               return -EOPNOTSUPP;
        }
-       rc = qeth_ipa_checksum_run_cmd(card, cstype, IPA_CMD_ASS_ENABLE,
-                                      chksum_cb.supported, &chksum_cb,
+
+       iob = qeth_get_setassparms_cmd(card, cstype, IPA_CMD_ASS_ENABLE, 4,
                                       prot);
-       if (!rc) {
-               if ((required_features & chksum_cb.enabled) !=
-                   required_features)
-                       rc = -EIO;
+       if (!iob) {
+               qeth_set_csum_off(card, cstype, prot);
+               return -ENOMEM;
        }
+
+       if (features & QETH_IPA_CHECKSUM_LP2LP)
+               required_features |= QETH_IPA_CHECKSUM_LP2LP;
+       __ipa_cmd(iob)->data.setassparms.data.flags_32bit = required_features;
+       rc = qeth_send_ipa_cmd(card, iob, qeth_setassparms_get_caps_cb, &caps);
        if (rc) {
-               qeth_send_simple_setassparms_prot(card, cstype,
-                                                 IPA_CMD_ASS_STOP, 0, prot);
-               dev_warn(&card->gdev->dev,
-                        "Enabling HW IPv%d checksumming for %s failed, using SW checksumming\n",
-                        prot, QETH_CARD_IFNAME(card));
+               qeth_set_csum_off(card, cstype, prot);
                return rc;
        }
 
+       if (!qeth_ipa_caps_supported(&caps, required_features) ||
+           !qeth_ipa_caps_enabled(&caps, required_features)) {
+               qeth_set_csum_off(card, cstype, prot);
+               return -EOPNOTSUPP;
+       }
+
        dev_info(&card->gdev->dev, "HW Checksumming (%sbound IPv%d) enabled\n",
                 cstype == IPA_INBOUND_CHECKSUM ? "in" : "out", prot);
+       if (!qeth_ipa_caps_enabled(&caps, QETH_IPA_CHECKSUM_LP2LP) &&
+           cstype == IPA_OUTBOUND_CHECKSUM)
+               dev_warn(&card->gdev->dev,
+                        "Hardware checksumming is performed only if %s and its peer use different OSA Express 3 ports\n",
+                        QETH_CARD_IFNAME(card));
        return 0;
 }
 
 static int qeth_set_ipa_csum(struct qeth_card *card, bool on, int cstype,
                             enum qeth_prot_versions prot)
 {
-       int rc = (on) ? qeth_send_checksum_on(card, cstype, prot)
-                     : qeth_send_simple_setassparms_prot(card, cstype,
-                                                         IPA_CMD_ASS_STOP, 0,
-                                                         prot);
+       int rc = (on) ? qeth_set_csum_on(card, cstype, prot) :
+                       qeth_set_csum_off(card, cstype, prot);
        return rc ? -EIO : 0;
 }
 
@@ -6534,8 +6480,6 @@ static int qeth_set_ipa_rx_csum(struct qeth_card *card, bool on)
        return (rc_ipv6) ? rc_ipv6 : rc_ipv4;
 }
 
-#define QETH_HW_FEATURES (NETIF_F_RXCSUM | NETIF_F_IP_CSUM | NETIF_F_TSO | \
-                         NETIF_F_IPV6_CSUM | NETIF_F_TSO6)
 /**
  * qeth_enable_hw_features() - (Re-)Enable HW functions for device features
  * @dev:       a net_device
@@ -6545,17 +6489,20 @@ void qeth_enable_hw_features(struct net_device *dev)
        struct qeth_card *card = dev->ml_priv;
        netdev_features_t features;
 
-       rtnl_lock();
        features = dev->features;
-       /* force-off any feature that needs an IPA sequence.
+       /* force-off any feature that might need an IPA sequence.
         * netdev_update_features() will restart them.
         */
-       dev->features &= ~QETH_HW_FEATURES;
+       dev->features &= ~dev->hw_features;
+       /* toggle VLAN filter, so that VIDs are re-programmed: */
+       if (IS_LAYER2(card) && IS_VM_NIC(card)) {
+               dev->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER;
+               dev->wanted_features |= NETIF_F_HW_VLAN_CTAG_FILTER;
+       }
        netdev_update_features(dev);
        if (features != dev->features)
                dev_warn(&card->gdev->dev,
                         "Device recovery failed to restore all offload features\n");
-       rtnl_unlock();
 }
 EXPORT_SYMBOL_GPL(qeth_enable_hw_features);
 
@@ -6624,10 +6571,7 @@ netdev_features_t qeth_fix_features(struct net_device *dev,
                features &= ~NETIF_F_TSO;
        if (!qeth_is_supported6(card, IPA_OUTBOUND_TSO))
                features &= ~NETIF_F_TSO6;
-       /* if the card isn't up, remove features that require hw changes */
-       if (card->state == CARD_STATE_DOWN ||
-           card->state == CARD_STATE_RECOVER)
-               features &= ~QETH_HW_FEATURES;
+
        QETH_DBF_HEX(SETUP, 2, &features, sizeof(features));
        return features;
 }
@@ -6659,6 +6603,46 @@ netdev_features_t qeth_features_check(struct sk_buff *skb,
 }
 EXPORT_SYMBOL_GPL(qeth_features_check);
 
+int qeth_open(struct net_device *dev)
+{
+       struct qeth_card *card = dev->ml_priv;
+
+       QETH_CARD_TEXT(card, 4, "qethopen");
+       if (card->state == CARD_STATE_UP)
+               return 0;
+       if (card->state != CARD_STATE_SOFTSETUP)
+               return -ENODEV;
+
+       if (qdio_stop_irq(CARD_DDEV(card), 0) < 0)
+               return -EIO;
+
+       card->data.state = CH_STATE_UP;
+       card->state = CARD_STATE_UP;
+       netif_start_queue(dev);
+
+       napi_enable(&card->napi);
+       local_bh_disable();
+       napi_schedule(&card->napi);
+       /* kick-start the NAPI softirq: */
+       local_bh_enable();
+       return 0;
+}
+EXPORT_SYMBOL_GPL(qeth_open);
+
+int qeth_stop(struct net_device *dev)
+{
+       struct qeth_card *card = dev->ml_priv;
+
+       QETH_CARD_TEXT(card, 4, "qethstop");
+       netif_tx_disable(dev);
+       if (card->state == CARD_STATE_UP) {
+               card->state = CARD_STATE_SOFTSETUP;
+               napi_disable(&card->napi);
+       }
+       return 0;
+}
+EXPORT_SYMBOL_GPL(qeth_stop);
+
 static int __init qeth_core_init(void)
 {
        int rc;