mirror of
https://github.com/torvalds/linux
synced 2024-10-03 09:48:02 +00:00
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: Julian Wiedmann <jwi@linux.ibm.com> Signed-off-by: Jakub Kicinski <jakub.kicinski@netronome.com>
This commit is contained in:
parent
a1794de8b9
commit
072f794000
|
@ -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)
|
||||||
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,
|
||||||
|
|
||||||
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,
|
||||||
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,
|
||||||
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);
|
||||||
|
|
Loading…
Reference in a new issue