Commit 072f7940 authored by Julian Wiedmann's avatar Julian Wiedmann Committed by Jakub Kicinski

s390/qeth: serialize cmd reply with concurrent timeout

Callbacks for a cmd reply run outside the protection of card->lock, to
allow for additional cmds to be issued & enqueued in parallel.

When qeth_send_control_data() bails out for a cmd without having
received a reply (eg. due to timeout), its callback may concurrently be
processing a reply that just arrived. In this case, the callback
potentially accesses a stale reply->reply_param area that eg. was
on-stack and has already been released.

To avoid this race, add some locking so that qeth_send_control_data()
can (1) wait for a concurrently running callback, and (2) zap any
pending callback that still wants to run.
Signed-off-by: default avatarJulian Wiedmann <jwi@linux.ibm.com>
Signed-off-by: default avatarJakub Kicinski <jakub.kicinski@netronome.com>
parent a1794de8
...@@ -629,6 +629,7 @@ struct qeth_seqno { ...@@ -629,6 +629,7 @@ struct qeth_seqno {
struct qeth_reply { struct qeth_reply {
struct list_head list; struct list_head list;
struct completion received; struct completion received;
spinlock_t lock;
int (*callback)(struct qeth_card *, struct qeth_reply *, int (*callback)(struct qeth_card *, struct qeth_reply *,
unsigned long); unsigned long);
u32 seqno; u32 seqno;
......
...@@ -544,6 +544,7 @@ static struct qeth_reply *qeth_alloc_reply(struct qeth_card *card) ...@@ -544,6 +544,7 @@ static struct qeth_reply *qeth_alloc_reply(struct qeth_card *card)
if (reply) { if (reply) {
refcount_set(&reply->refcnt, 1); refcount_set(&reply->refcnt, 1);
init_completion(&reply->received); init_completion(&reply->received);
spin_lock_init(&reply->lock);
} }
return reply; return reply;
} }
...@@ -799,6 +800,13 @@ static void qeth_issue_next_read_cb(struct qeth_card *card, ...@@ -799,6 +800,13 @@ static void qeth_issue_next_read_cb(struct qeth_card *card,
if (!reply->callback) { if (!reply->callback) {
rc = 0; rc = 0;
goto no_callback;
}
spin_lock_irqsave(&reply->lock, flags);
if (reply->rc) {
/* Bail out when the requestor has already left: */
rc = reply->rc;
} else { } else {
if (cmd) { if (cmd) {
reply->offset = (u16)((char *)cmd - (char *)iob->data); reply->offset = (u16)((char *)cmd - (char *)iob->data);
...@@ -807,7 +815,9 @@ static void qeth_issue_next_read_cb(struct qeth_card *card, ...@@ -807,7 +815,9 @@ static void qeth_issue_next_read_cb(struct qeth_card *card,
rc = reply->callback(card, reply, (unsigned long)iob); rc = reply->callback(card, reply, (unsigned long)iob);
} }
} }
spin_unlock_irqrestore(&reply->lock, flags);
no_callback:
if (rc <= 0) if (rc <= 0)
qeth_notify_reply(reply, rc); qeth_notify_reply(reply, rc);
qeth_put_reply(reply); qeth_put_reply(reply);
...@@ -1749,6 +1759,16 @@ static int qeth_send_control_data(struct qeth_card *card, ...@@ -1749,6 +1759,16 @@ static int qeth_send_control_data(struct qeth_card *card,
rc = (timeout == -ERESTARTSYS) ? -EINTR : -ETIME; rc = (timeout == -ERESTARTSYS) ? -EINTR : -ETIME;
qeth_dequeue_reply(card, reply); qeth_dequeue_reply(card, reply);
if (reply_cb) {
/* Wait until the callback for a late reply has completed: */
spin_lock_irq(&reply->lock);
if (rc)
/* Zap any callback that's still pending: */
reply->rc = rc;
spin_unlock_irq(&reply->lock);
}
if (!rc) if (!rc)
rc = reply->rc; rc = reply->rc;
qeth_put_reply(reply); qeth_put_reply(reply);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment