Commit 564337f3 authored by Peter Oberparleiter's avatar Peter Oberparleiter Committed by Martin Schwidefsky

[S390] cio: subchannel evaluation function operates without lock

css_evaluate_subchannel() operates subchannel without lock which can
lead to erratic behavior caused by concurrent device access. Also
split evaluation function to make it more readable.
Signed-off-by: default avatarPeter Oberparleiter <peter.oberparleiter@de.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent 28bdc6f6
...@@ -182,136 +182,141 @@ get_subchannel_by_schid(struct subchannel_id schid) ...@@ -182,136 +182,141 @@ get_subchannel_by_schid(struct subchannel_id schid)
return dev ? to_subchannel(dev) : NULL; return dev ? to_subchannel(dev) : NULL;
} }
static inline int css_get_subchannel_status(struct subchannel *sch)
static inline int
css_get_subchannel_status(struct subchannel *sch, struct subchannel_id schid)
{ {
struct schib schib; struct schib schib;
int cc;
cc = stsch(schid, &schib); if (stsch(sch->schid, &schib) || !schib.pmcw.dnv)
if (cc)
return CIO_GONE;
if (!schib.pmcw.dnv)
return CIO_GONE; return CIO_GONE;
if (sch && sch->schib.pmcw.dnv && if (sch->schib.pmcw.dnv && (schib.pmcw.dev != sch->schib.pmcw.dev))
(schib.pmcw.dev != sch->schib.pmcw.dev))
return CIO_REVALIDATE; return CIO_REVALIDATE;
if (sch && !sch->lpm) if (!sch->lpm)
return CIO_NO_PATH; return CIO_NO_PATH;
return CIO_OPER; return CIO_OPER;
} }
static int static int css_evaluate_known_subchannel(struct subchannel *sch, int slow)
css_evaluate_subchannel(struct subchannel_id schid, int slow)
{ {
int event, ret, disc; int event, ret, disc;
struct subchannel *sch;
unsigned long flags; unsigned long flags;
enum { NONE, UNREGISTER, UNREGISTER_PROBE, REPROBE } action;
sch = get_subchannel_by_schid(schid); spin_lock_irqsave(&sch->lock, flags);
disc = sch ? device_is_disconnected(sch) : 0; disc = device_is_disconnected(sch);
if (disc && slow) { if (disc && slow) {
if (sch) /* Disconnected devices are evaluated directly only.*/
put_device(&sch->dev); spin_unlock_irqrestore(&sch->lock, flags);
return 0; /* Already processed. */ return 0;
} }
/* /* No interrupt after machine check - kill pending timers. */
* We've got a machine check, so running I/O won't get an interrupt. device_kill_pending_timer(sch);
* Kill any pending timers.
*/
if (sch)
device_kill_pending_timer(sch);
if (!disc && !slow) { if (!disc && !slow) {
if (sch) /* Non-disconnected devices are evaluated on the slow path. */
put_device(&sch->dev); spin_unlock_irqrestore(&sch->lock, flags);
return -EAGAIN; /* Will be done on the slow path. */ return -EAGAIN;
} }
event = css_get_subchannel_status(sch, schid); event = css_get_subchannel_status(sch);
CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n", CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n",
schid.ssid, schid.sch_no, event, sch->schid.ssid, sch->schid.sch_no, event,
sch?(disc?"disconnected":"normal"):"unknown", disc ? "disconnected" : "normal",
slow?"slow":"fast"); slow ? "slow" : "fast");
/* Analyze subchannel status. */
action = NONE;
switch (event) { switch (event) {
case CIO_NO_PATH: case CIO_NO_PATH:
case CIO_GONE: if (disc) {
if (!sch) { /* Check if paths have become available. */
/* Never used this subchannel. Ignore. */ action = REPROBE;
ret = 0;
break; break;
} }
if (disc && (event == CIO_NO_PATH)) { /* fall through */
/* case CIO_GONE:
* Uargh, hack again. Because we don't get a machine /* Prevent unwanted effects when opening lock. */
* check on configure on, our path bookkeeping can cio_disable_subchannel(sch);
* be out of date here (it's fine while we only do device_set_disconnected(sch);
* logical varying or get chsc machine checks). We /* Ask driver what to do with device. */
* need to force reprobing or we might miss devices action = UNREGISTER;
* coming operational again. It won't do harm in real if (sch->driver && sch->driver->notify) {
* no path situations.
*/
spin_lock_irqsave(&sch->lock, flags);
device_trigger_reprobe(sch);
spin_unlock_irqrestore(&sch->lock, flags); spin_unlock_irqrestore(&sch->lock, flags);
ret = 0; ret = sch->driver->notify(&sch->dev, event);
break; spin_lock_irqsave(&sch->lock, flags);
} if (ret)
if (sch->driver && sch->driver->notify && action = NONE;
sch->driver->notify(&sch->dev, event)) {
cio_disable_subchannel(sch);
device_set_disconnected(sch);
ret = 0;
break;
} }
/*
* Unregister subchannel.
* The device will be killed automatically.
*/
cio_disable_subchannel(sch);
css_sch_device_unregister(sch);
/* Reset intparm to zeroes. */
sch->schib.pmcw.intparm = 0;
cio_modify(sch);
put_device(&sch->dev);
ret = 0;
break; break;
case CIO_REVALIDATE: case CIO_REVALIDATE:
/* /* Device will be removed, so no notify necessary. */
* Revalidation machine check. Sick. if (disc)
* We don't notify the driver since we have to throw the device /* Reprobe because immediate unregister might block. */
* away in any case. action = REPROBE;
*/ else
if (!disc) { action = UNREGISTER_PROBE;
css_sch_device_unregister(sch);
/* Reset intparm to zeroes. */
sch->schib.pmcw.intparm = 0;
cio_modify(sch);
put_device(&sch->dev);
ret = css_probe_device(schid);
} else {
/*
* We can't immediately deregister the disconnected
* device since it might block.
*/
spin_lock_irqsave(&sch->lock, flags);
device_trigger_reprobe(sch);
spin_unlock_irqrestore(&sch->lock, flags);
ret = 0;
}
break; break;
case CIO_OPER: case CIO_OPER:
if (disc) { if (disc)
spin_lock_irqsave(&sch->lock, flags);
/* Get device operational again. */ /* Get device operational again. */
device_trigger_reprobe(sch); action = REPROBE;
spin_unlock_irqrestore(&sch->lock, flags); break;
} }
ret = sch ? 0 : css_probe_device(schid); /* Perform action. */
ret = 0;
switch (action) {
case UNREGISTER:
case UNREGISTER_PROBE:
/* Unregister device (will use subchannel lock). */
spin_unlock_irqrestore(&sch->lock, flags);
css_sch_device_unregister(sch);
spin_lock_irqsave(&sch->lock, flags);
/* Reset intparm to zeroes. */
sch->schib.pmcw.intparm = 0;
cio_modify(sch);
/* Probe if necessary. */
if (action == UNREGISTER_PROBE)
ret = css_probe_device(sch->schid);
break;
case REPROBE:
device_trigger_reprobe(sch);
break; break;
default: default:
BUG(); break;
ret = 0; }
spin_unlock_irqrestore(&sch->lock, flags);
return ret;
}
static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow)
{
struct schib schib;
if (!slow) {
/* Will be done on the slow path. */
return -EAGAIN;
} }
if (stsch(schid, &schib) || !schib.pmcw.dnv) {
/* Unusable - ignore. */
return 0;
}
CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, unknown, "
"slow path.\n", schid.ssid, schid.sch_no, CIO_OPER);
return css_probe_device(schid);
}
static int css_evaluate_subchannel(struct subchannel_id schid, int slow)
{
struct subchannel *sch;
int ret;
sch = get_subchannel_by_schid(schid);
if (sch) {
ret = css_evaluate_known_subchannel(sch, slow);
put_device(&sch->dev);
} else
ret = css_evaluate_new_subchannel(schid, slow);
return ret; return ret;
} }
......
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