diff options
author | Peter Oberparleiter <peter.oberparleiter@de.ibm.com> | 2006-09-20 10:00:01 -0400 |
---|---|---|
committer | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2006-09-20 10:00:01 -0400 |
commit | 564337f34cc10fd8f30329e4e5f14f8995db5711 (patch) | |
tree | b2f4791c1b696d1ce4202b14c6dcae3b3211de01 | |
parent | 28bdc6f6233f380ddc0b430cabd88ffeafea34c7 (diff) |
[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: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
-rw-r--r-- | drivers/s390/cio/css.c | 203 |
1 files changed, 104 insertions, 99 deletions
diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c index 13eeea3d547f..7086a74e9871 100644 --- a/drivers/s390/cio/css.c +++ b/drivers/s390/cio/css.c | |||
@@ -182,136 +182,141 @@ get_subchannel_by_schid(struct subchannel_id schid) | |||
182 | return dev ? to_subchannel(dev) : NULL; | 182 | return dev ? to_subchannel(dev) : NULL; |
183 | } | 183 | } |
184 | 184 | ||
185 | 185 | static inline int css_get_subchannel_status(struct subchannel *sch) | |
186 | static inline int | ||
187 | css_get_subchannel_status(struct subchannel *sch, struct subchannel_id schid) | ||
188 | { | 186 | { |
189 | struct schib schib; | 187 | struct schib schib; |
190 | int cc; | ||
191 | 188 | ||
192 | cc = stsch(schid, &schib); | 189 | if (stsch(sch->schid, &schib) || !schib.pmcw.dnv) |
193 | if (cc) | ||
194 | return CIO_GONE; | ||
195 | if (!schib.pmcw.dnv) | ||
196 | return CIO_GONE; | 190 | return CIO_GONE; |
197 | if (sch && sch->schib.pmcw.dnv && | 191 | if (sch->schib.pmcw.dnv && (schib.pmcw.dev != sch->schib.pmcw.dev)) |
198 | (schib.pmcw.dev != sch->schib.pmcw.dev)) | ||
199 | return CIO_REVALIDATE; | 192 | return CIO_REVALIDATE; |
200 | if (sch && !sch->lpm) | 193 | if (!sch->lpm) |
201 | return CIO_NO_PATH; | 194 | return CIO_NO_PATH; |
202 | return CIO_OPER; | 195 | return CIO_OPER; |
203 | } | 196 | } |
204 | 197 | ||
205 | static int | 198 | static int css_evaluate_known_subchannel(struct subchannel *sch, int slow) |
206 | css_evaluate_subchannel(struct subchannel_id schid, int slow) | ||
207 | { | 199 | { |
208 | int event, ret, disc; | 200 | int event, ret, disc; |
209 | struct subchannel *sch; | ||
210 | unsigned long flags; | 201 | unsigned long flags; |
202 | enum { NONE, UNREGISTER, UNREGISTER_PROBE, REPROBE } action; | ||
211 | 203 | ||
212 | sch = get_subchannel_by_schid(schid); | 204 | spin_lock_irqsave(&sch->lock, flags); |
213 | disc = sch ? device_is_disconnected(sch) : 0; | 205 | disc = device_is_disconnected(sch); |
214 | if (disc && slow) { | 206 | if (disc && slow) { |
215 | if (sch) | 207 | /* Disconnected devices are evaluated directly only.*/ |
216 | put_device(&sch->dev); | 208 | spin_unlock_irqrestore(&sch->lock, flags); |
217 | return 0; /* Already processed. */ | 209 | return 0; |
218 | } | 210 | } |
219 | /* | 211 | /* No interrupt after machine check - kill pending timers. */ |
220 | * We've got a machine check, so running I/O won't get an interrupt. | 212 | device_kill_pending_timer(sch); |
221 | * Kill any pending timers. | ||
222 | */ | ||
223 | if (sch) | ||
224 | device_kill_pending_timer(sch); | ||
225 | if (!disc && !slow) { | 213 | if (!disc && !slow) { |
226 | if (sch) | 214 | /* Non-disconnected devices are evaluated on the slow path. */ |
227 | put_device(&sch->dev); | 215 | spin_unlock_irqrestore(&sch->lock, flags); |
228 | return -EAGAIN; /* Will be done on the slow path. */ | 216 | return -EAGAIN; |
229 | } | 217 | } |
230 | event = css_get_subchannel_status(sch, schid); | 218 | event = css_get_subchannel_status(sch); |
231 | CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n", | 219 | CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n", |
232 | schid.ssid, schid.sch_no, event, | 220 | sch->schid.ssid, sch->schid.sch_no, event, |
233 | sch?(disc?"disconnected":"normal"):"unknown", | 221 | disc ? "disconnected" : "normal", |
234 | slow?"slow":"fast"); | 222 | slow ? "slow" : "fast"); |
223 | /* Analyze subchannel status. */ | ||
224 | action = NONE; | ||
235 | switch (event) { | 225 | switch (event) { |
236 | case CIO_NO_PATH: | 226 | case CIO_NO_PATH: |
237 | case CIO_GONE: | 227 | if (disc) { |
238 | if (!sch) { | 228 | /* Check if paths have become available. */ |
239 | /* Never used this subchannel. Ignore. */ | 229 | action = REPROBE; |
240 | ret = 0; | ||
241 | break; | 230 | break; |
242 | } | 231 | } |
243 | if (disc && (event == CIO_NO_PATH)) { | 232 | /* fall through */ |
244 | /* | 233 | case CIO_GONE: |
245 | * Uargh, hack again. Because we don't get a machine | 234 | /* Prevent unwanted effects when opening lock. */ |
246 | * check on configure on, our path bookkeeping can | 235 | cio_disable_subchannel(sch); |
247 | * be out of date here (it's fine while we only do | 236 | device_set_disconnected(sch); |
248 | * logical varying or get chsc machine checks). We | 237 | /* Ask driver what to do with device. */ |
249 | * need to force reprobing or we might miss devices | 238 | action = UNREGISTER; |
250 | * coming operational again. It won't do harm in real | 239 | if (sch->driver && sch->driver->notify) { |
251 | * no path situations. | ||
252 | */ | ||
253 | spin_lock_irqsave(&sch->lock, flags); | ||
254 | device_trigger_reprobe(sch); | ||
255 | spin_unlock_irqrestore(&sch->lock, flags); | 240 | spin_unlock_irqrestore(&sch->lock, flags); |
256 | ret = 0; | 241 | ret = sch->driver->notify(&sch->dev, event); |
257 | break; | 242 | spin_lock_irqsave(&sch->lock, flags); |
258 | } | 243 | if (ret) |
259 | if (sch->driver && sch->driver->notify && | 244 | action = NONE; |
260 | sch->driver->notify(&sch->dev, event)) { | ||
261 | cio_disable_subchannel(sch); | ||
262 | device_set_disconnected(sch); | ||
263 | ret = 0; | ||
264 | break; | ||
265 | } | 245 | } |
266 | /* | ||
267 | * Unregister subchannel. | ||
268 | * The device will be killed automatically. | ||
269 | */ | ||
270 | cio_disable_subchannel(sch); | ||
271 | css_sch_device_unregister(sch); | ||
272 | /* Reset intparm to zeroes. */ | ||
273 | sch->schib.pmcw.intparm = 0; | ||
274 | cio_modify(sch); | ||
275 | put_device(&sch->dev); | ||
276 | ret = 0; | ||
277 | break; | 246 | break; |
278 | case CIO_REVALIDATE: | 247 | case CIO_REVALIDATE: |
279 | /* | 248 | /* Device will be removed, so no notify necessary. */ |
280 | * Revalidation machine check. Sick. | 249 | if (disc) |
281 | * We don't notify the driver since we have to throw the device | 250 | /* Reprobe because immediate unregister might block. */ |
282 | * away in any case. | 251 | action = REPROBE; |
283 | */ | 252 | else |
284 | if (!disc) { | 253 | action = UNREGISTER_PROBE; |
285 | css_sch_device_unregister(sch); | ||
286 | /* Reset intparm to zeroes. */ | ||
287 | sch->schib.pmcw.intparm = 0; | ||
288 | cio_modify(sch); | ||
289 | put_device(&sch->dev); | ||
290 | ret = css_probe_device(schid); | ||
291 | } else { | ||
292 | /* | ||
293 | * We can't immediately deregister the disconnected | ||
294 | * device since it might block. | ||
295 | */ | ||
296 | spin_lock_irqsave(&sch->lock, flags); | ||
297 | device_trigger_reprobe(sch); | ||
298 | spin_unlock_irqrestore(&sch->lock, flags); | ||
299 | ret = 0; | ||
300 | } | ||
301 | break; | 254 | break; |
302 | case CIO_OPER: | 255 | case CIO_OPER: |
303 | if (disc) { | 256 | if (disc) |
304 | spin_lock_irqsave(&sch->lock, flags); | ||
305 | /* Get device operational again. */ | 257 | /* Get device operational again. */ |
306 | device_trigger_reprobe(sch); | 258 | action = REPROBE; |
307 | spin_unlock_irqrestore(&sch->lock, flags); | 259 | break; |
308 | } | 260 | } |
309 | ret = sch ? 0 : css_probe_device(schid); | 261 | /* Perform action. */ |
262 | ret = 0; | ||
263 | switch (action) { | ||
264 | case UNREGISTER: | ||
265 | case UNREGISTER_PROBE: | ||
266 | /* Unregister device (will use subchannel lock). */ | ||
267 | spin_unlock_irqrestore(&sch->lock, flags); | ||
268 | css_sch_device_unregister(sch); | ||
269 | spin_lock_irqsave(&sch->lock, flags); | ||
270 | |||
271 | /* Reset intparm to zeroes. */ | ||
272 | sch->schib.pmcw.intparm = 0; | ||
273 | cio_modify(sch); | ||
274 | |||
275 | /* Probe if necessary. */ | ||
276 | if (action == UNREGISTER_PROBE) | ||
277 | ret = css_probe_device(sch->schid); | ||
278 | break; | ||
279 | case REPROBE: | ||
280 | device_trigger_reprobe(sch); | ||
310 | break; | 281 | break; |
311 | default: | 282 | default: |
312 | BUG(); | 283 | break; |
313 | ret = 0; | 284 | } |
285 | spin_unlock_irqrestore(&sch->lock, flags); | ||
286 | |||
287 | return ret; | ||
288 | } | ||
289 | |||
290 | static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow) | ||
291 | { | ||
292 | struct schib schib; | ||
293 | |||
294 | if (!slow) { | ||
295 | /* Will be done on the slow path. */ | ||
296 | return -EAGAIN; | ||
314 | } | 297 | } |
298 | if (stsch(schid, &schib) || !schib.pmcw.dnv) { | ||
299 | /* Unusable - ignore. */ | ||
300 | return 0; | ||
301 | } | ||
302 | CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, unknown, " | ||
303 | "slow path.\n", schid.ssid, schid.sch_no, CIO_OPER); | ||
304 | |||
305 | return css_probe_device(schid); | ||
306 | } | ||
307 | |||
308 | static int css_evaluate_subchannel(struct subchannel_id schid, int slow) | ||
309 | { | ||
310 | struct subchannel *sch; | ||
311 | int ret; | ||
312 | |||
313 | sch = get_subchannel_by_schid(schid); | ||
314 | if (sch) { | ||
315 | ret = css_evaluate_known_subchannel(sch, slow); | ||
316 | put_device(&sch->dev); | ||
317 | } else | ||
318 | ret = css_evaluate_new_subchannel(schid, slow); | ||
319 | |||
315 | return ret; | 320 | return ret; |
316 | } | 321 | } |
317 | 322 | ||