diff options
author | Cornelia Huck <cornelia.huck@de.ibm.com> | 2008-07-14 03:58:45 -0400 |
---|---|---|
committer | Heiko Carstens <heiko.carstens@de.ibm.com> | 2008-07-14 04:02:05 -0400 |
commit | c820de39bd083222f5be2563181c87493e436f7c (patch) | |
tree | 4861db1aeca00d55d76b5844ad209d81a2795105 /drivers/s390/cio/device.c | |
parent | 7e9db9eaefdb8798730790214ff1b7746006ec98 (diff) |
[S390] cio: Rework css driver.
Rework the css driver methods to provide sane callbacks for
subchannels of all types.
As a bonus, this cleans up and simplyfies the machine check
handling for I/O subchannels a lot.
Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Diffstat (limited to 'drivers/s390/cio/device.c')
-rw-r--r-- | drivers/s390/cio/device.c | 365 |
1 files changed, 279 insertions, 86 deletions
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index 23b129fd4d8d..9281b25087a6 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c | |||
@@ -2,8 +2,7 @@ | |||
2 | * drivers/s390/cio/device.c | 2 | * drivers/s390/cio/device.c |
3 | * bus driver for ccw devices | 3 | * bus driver for ccw devices |
4 | * | 4 | * |
5 | * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, | 5 | * Copyright IBM Corp. 2002,2008 |
6 | * IBM Corporation | ||
7 | * Author(s): Arnd Bergmann (arndb@de.ibm.com) | 6 | * Author(s): Arnd Bergmann (arndb@de.ibm.com) |
8 | * Cornelia Huck (cornelia.huck@de.ibm.com) | 7 | * Cornelia Huck (cornelia.huck@de.ibm.com) |
9 | * Martin Schwidefsky (schwidefsky@de.ibm.com) | 8 | * Martin Schwidefsky (schwidefsky@de.ibm.com) |
@@ -126,19 +125,17 @@ struct bus_type ccw_bus_type; | |||
126 | static void io_subchannel_irq(struct subchannel *); | 125 | static void io_subchannel_irq(struct subchannel *); |
127 | static int io_subchannel_probe(struct subchannel *); | 126 | static int io_subchannel_probe(struct subchannel *); |
128 | static int io_subchannel_remove(struct subchannel *); | 127 | static int io_subchannel_remove(struct subchannel *); |
129 | static int io_subchannel_notify(struct subchannel *, int); | ||
130 | static void io_subchannel_verify(struct subchannel *); | ||
131 | static void io_subchannel_ioterm(struct subchannel *); | ||
132 | static void io_subchannel_shutdown(struct subchannel *); | 128 | static void io_subchannel_shutdown(struct subchannel *); |
129 | static int io_subchannel_sch_event(struct subchannel *, int); | ||
130 | static int io_subchannel_chp_event(struct subchannel *, void *, int); | ||
133 | 131 | ||
134 | static struct css_driver io_subchannel_driver = { | 132 | static struct css_driver io_subchannel_driver = { |
135 | .owner = THIS_MODULE, | 133 | .owner = THIS_MODULE, |
136 | .subchannel_type = SUBCHANNEL_TYPE_IO, | 134 | .subchannel_type = SUBCHANNEL_TYPE_IO, |
137 | .name = "io_subchannel", | 135 | .name = "io_subchannel", |
138 | .irq = io_subchannel_irq, | 136 | .irq = io_subchannel_irq, |
139 | .notify = io_subchannel_notify, | 137 | .sch_event = io_subchannel_sch_event, |
140 | .verify = io_subchannel_verify, | 138 | .chp_event = io_subchannel_chp_event, |
141 | .termination = io_subchannel_ioterm, | ||
142 | .probe = io_subchannel_probe, | 139 | .probe = io_subchannel_probe, |
143 | .remove = io_subchannel_remove, | 140 | .remove = io_subchannel_remove, |
144 | .shutdown = io_subchannel_shutdown, | 141 | .shutdown = io_subchannel_shutdown, |
@@ -786,7 +783,7 @@ static void sch_attach_device(struct subchannel *sch, | |||
786 | sch_set_cdev(sch, cdev); | 783 | sch_set_cdev(sch, cdev); |
787 | cdev->private->schid = sch->schid; | 784 | cdev->private->schid = sch->schid; |
788 | cdev->ccwlock = sch->lock; | 785 | cdev->ccwlock = sch->lock; |
789 | device_trigger_reprobe(sch); | 786 | ccw_device_trigger_reprobe(cdev); |
790 | spin_unlock_irq(sch->lock); | 787 | spin_unlock_irq(sch->lock); |
791 | } | 788 | } |
792 | 789 | ||
@@ -1265,11 +1262,7 @@ static int io_subchannel_notify(struct subchannel *sch, int event) | |||
1265 | cdev = sch_get_cdev(sch); | 1262 | cdev = sch_get_cdev(sch); |
1266 | if (!cdev) | 1263 | if (!cdev) |
1267 | return 0; | 1264 | return 0; |
1268 | if (!cdev->drv) | 1265 | return ccw_device_notify(cdev, event); |
1269 | return 0; | ||
1270 | if (!cdev->online) | ||
1271 | return 0; | ||
1272 | return cdev->drv->notify ? cdev->drv->notify(cdev, event) : 0; | ||
1273 | } | 1266 | } |
1274 | 1267 | ||
1275 | static void io_subchannel_verify(struct subchannel *sch) | 1268 | static void io_subchannel_verify(struct subchannel *sch) |
@@ -1281,22 +1274,98 @@ static void io_subchannel_verify(struct subchannel *sch) | |||
1281 | dev_fsm_event(cdev, DEV_EVENT_VERIFY); | 1274 | dev_fsm_event(cdev, DEV_EVENT_VERIFY); |
1282 | } | 1275 | } |
1283 | 1276 | ||
1284 | static void io_subchannel_ioterm(struct subchannel *sch) | 1277 | static int check_for_io_on_path(struct subchannel *sch, int mask) |
1285 | { | 1278 | { |
1286 | struct ccw_device *cdev; | 1279 | int cc; |
1287 | 1280 | ||
1288 | cdev = sch_get_cdev(sch); | 1281 | cc = stsch(sch->schid, &sch->schib); |
1289 | if (!cdev) | 1282 | if (cc) |
1290 | return; | 1283 | return 0; |
1291 | /* Internal I/O will be retried by the interrupt handler. */ | 1284 | if (sch->schib.scsw.actl && sch->schib.pmcw.lpum == mask) |
1292 | if (cdev->private->flags.intretry) | 1285 | return 1; |
1286 | return 0; | ||
1287 | } | ||
1288 | |||
1289 | static void terminate_internal_io(struct subchannel *sch, | ||
1290 | struct ccw_device *cdev) | ||
1291 | { | ||
1292 | if (cio_clear(sch)) { | ||
1293 | /* Recheck device in case clear failed. */ | ||
1294 | sch->lpm = 0; | ||
1295 | if (cdev->online) | ||
1296 | dev_fsm_event(cdev, DEV_EVENT_VERIFY); | ||
1297 | else | ||
1298 | css_schedule_eval(sch->schid); | ||
1293 | return; | 1299 | return; |
1300 | } | ||
1294 | cdev->private->state = DEV_STATE_CLEAR_VERIFY; | 1301 | cdev->private->state = DEV_STATE_CLEAR_VERIFY; |
1302 | /* Request retry of internal operation. */ | ||
1303 | cdev->private->flags.intretry = 1; | ||
1304 | /* Call handler. */ | ||
1295 | if (cdev->handler) | 1305 | if (cdev->handler) |
1296 | cdev->handler(cdev, cdev->private->intparm, | 1306 | cdev->handler(cdev, cdev->private->intparm, |
1297 | ERR_PTR(-EIO)); | 1307 | ERR_PTR(-EIO)); |
1298 | } | 1308 | } |
1299 | 1309 | ||
1310 | static void io_subchannel_terminate_path(struct subchannel *sch, u8 mask) | ||
1311 | { | ||
1312 | struct ccw_device *cdev; | ||
1313 | |||
1314 | cdev = sch_get_cdev(sch); | ||
1315 | if (!cdev) | ||
1316 | return; | ||
1317 | if (check_for_io_on_path(sch, mask)) { | ||
1318 | if (cdev->private->state == DEV_STATE_ONLINE) | ||
1319 | ccw_device_kill_io(cdev); | ||
1320 | else { | ||
1321 | terminate_internal_io(sch, cdev); | ||
1322 | /* Re-start path verification. */ | ||
1323 | dev_fsm_event(cdev, DEV_EVENT_VERIFY); | ||
1324 | } | ||
1325 | } else | ||
1326 | /* trigger path verification. */ | ||
1327 | dev_fsm_event(cdev, DEV_EVENT_VERIFY); | ||
1328 | |||
1329 | } | ||
1330 | |||
1331 | static int io_subchannel_chp_event(struct subchannel *sch, void *data, | ||
1332 | int event) | ||
1333 | { | ||
1334 | int mask; | ||
1335 | struct res_acc_data *res_data; | ||
1336 | |||
1337 | res_data = data; | ||
1338 | mask = chp_ssd_get_mask(&sch->ssd_info, res_data); | ||
1339 | if (!mask) | ||
1340 | return 0; | ||
1341 | switch (event) { | ||
1342 | case CHP_VARY_OFF: | ||
1343 | sch->opm &= ~mask; | ||
1344 | sch->lpm &= ~mask; | ||
1345 | io_subchannel_terminate_path(sch, mask); | ||
1346 | break; | ||
1347 | case CHP_VARY_ON: | ||
1348 | sch->opm |= mask; | ||
1349 | sch->lpm |= mask; | ||
1350 | io_subchannel_verify(sch); | ||
1351 | break; | ||
1352 | case CHP_OFFLINE: | ||
1353 | if (stsch(sch->schid, &sch->schib)) | ||
1354 | return -ENXIO; | ||
1355 | if (!css_sch_is_valid(&sch->schib)) | ||
1356 | return -ENODEV; | ||
1357 | io_subchannel_terminate_path(sch, mask); | ||
1358 | break; | ||
1359 | case CHP_ONLINE: | ||
1360 | if (stsch(sch->schid, &sch->schib)) | ||
1361 | return -ENXIO; | ||
1362 | sch->lpm |= mask & sch->opm; | ||
1363 | io_subchannel_verify(sch); | ||
1364 | break; | ||
1365 | } | ||
1366 | return 0; | ||
1367 | } | ||
1368 | |||
1300 | static void | 1369 | static void |
1301 | io_subchannel_shutdown(struct subchannel *sch) | 1370 | io_subchannel_shutdown(struct subchannel *sch) |
1302 | { | 1371 | { |
@@ -1326,6 +1395,195 @@ io_subchannel_shutdown(struct subchannel *sch) | |||
1326 | cio_disable_subchannel(sch); | 1395 | cio_disable_subchannel(sch); |
1327 | } | 1396 | } |
1328 | 1397 | ||
1398 | static int io_subchannel_get_status(struct subchannel *sch) | ||
1399 | { | ||
1400 | struct schib schib; | ||
1401 | |||
1402 | if (stsch(sch->schid, &schib) || !schib.pmcw.dnv) | ||
1403 | return CIO_GONE; | ||
1404 | if (sch->schib.pmcw.dnv && (schib.pmcw.dev != sch->schib.pmcw.dev)) | ||
1405 | return CIO_REVALIDATE; | ||
1406 | if (!sch->lpm) | ||
1407 | return CIO_NO_PATH; | ||
1408 | return CIO_OPER; | ||
1409 | } | ||
1410 | |||
1411 | static int device_is_disconnected(struct ccw_device *cdev) | ||
1412 | { | ||
1413 | if (!cdev) | ||
1414 | return 0; | ||
1415 | return (cdev->private->state == DEV_STATE_DISCONNECTED || | ||
1416 | cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID); | ||
1417 | } | ||
1418 | |||
1419 | static int recovery_check(struct device *dev, void *data) | ||
1420 | { | ||
1421 | struct ccw_device *cdev = to_ccwdev(dev); | ||
1422 | int *redo = data; | ||
1423 | |||
1424 | spin_lock_irq(cdev->ccwlock); | ||
1425 | switch (cdev->private->state) { | ||
1426 | case DEV_STATE_DISCONNECTED: | ||
1427 | CIO_MSG_EVENT(3, "recovery: trigger 0.%x.%04x\n", | ||
1428 | cdev->private->dev_id.ssid, | ||
1429 | cdev->private->dev_id.devno); | ||
1430 | dev_fsm_event(cdev, DEV_EVENT_VERIFY); | ||
1431 | *redo = 1; | ||
1432 | break; | ||
1433 | case DEV_STATE_DISCONNECTED_SENSE_ID: | ||
1434 | *redo = 1; | ||
1435 | break; | ||
1436 | } | ||
1437 | spin_unlock_irq(cdev->ccwlock); | ||
1438 | |||
1439 | return 0; | ||
1440 | } | ||
1441 | |||
1442 | static void recovery_work_func(struct work_struct *unused) | ||
1443 | { | ||
1444 | int redo = 0; | ||
1445 | |||
1446 | bus_for_each_dev(&ccw_bus_type, NULL, &redo, recovery_check); | ||
1447 | if (redo) { | ||
1448 | spin_lock_irq(&recovery_lock); | ||
1449 | if (!timer_pending(&recovery_timer)) { | ||
1450 | if (recovery_phase < ARRAY_SIZE(recovery_delay) - 1) | ||
1451 | recovery_phase++; | ||
1452 | mod_timer(&recovery_timer, jiffies + | ||
1453 | recovery_delay[recovery_phase] * HZ); | ||
1454 | } | ||
1455 | spin_unlock_irq(&recovery_lock); | ||
1456 | } else | ||
1457 | CIO_MSG_EVENT(4, "recovery: end\n"); | ||
1458 | } | ||
1459 | |||
1460 | static DECLARE_WORK(recovery_work, recovery_work_func); | ||
1461 | |||
1462 | static void recovery_func(unsigned long data) | ||
1463 | { | ||
1464 | /* | ||
1465 | * We can't do our recovery in softirq context and it's not | ||
1466 | * performance critical, so we schedule it. | ||
1467 | */ | ||
1468 | schedule_work(&recovery_work); | ||
1469 | } | ||
1470 | |||
1471 | static void ccw_device_schedule_recovery(void) | ||
1472 | { | ||
1473 | unsigned long flags; | ||
1474 | |||
1475 | CIO_MSG_EVENT(4, "recovery: schedule\n"); | ||
1476 | spin_lock_irqsave(&recovery_lock, flags); | ||
1477 | if (!timer_pending(&recovery_timer) || (recovery_phase != 0)) { | ||
1478 | recovery_phase = 0; | ||
1479 | mod_timer(&recovery_timer, jiffies + recovery_delay[0] * HZ); | ||
1480 | } | ||
1481 | spin_unlock_irqrestore(&recovery_lock, flags); | ||
1482 | } | ||
1483 | |||
1484 | static void device_set_disconnected(struct ccw_device *cdev) | ||
1485 | { | ||
1486 | if (!cdev) | ||
1487 | return; | ||
1488 | ccw_device_set_timeout(cdev, 0); | ||
1489 | cdev->private->flags.fake_irb = 0; | ||
1490 | cdev->private->state = DEV_STATE_DISCONNECTED; | ||
1491 | if (cdev->online) | ||
1492 | ccw_device_schedule_recovery(); | ||
1493 | } | ||
1494 | |||
1495 | static int io_subchannel_sch_event(struct subchannel *sch, int slow) | ||
1496 | { | ||
1497 | int event, ret, disc; | ||
1498 | unsigned long flags; | ||
1499 | enum { NONE, UNREGISTER, UNREGISTER_PROBE, REPROBE } action; | ||
1500 | struct ccw_device *cdev; | ||
1501 | |||
1502 | spin_lock_irqsave(sch->lock, flags); | ||
1503 | cdev = sch_get_cdev(sch); | ||
1504 | disc = device_is_disconnected(cdev); | ||
1505 | if (disc && slow) { | ||
1506 | /* Disconnected devices are evaluated directly only.*/ | ||
1507 | spin_unlock_irqrestore(sch->lock, flags); | ||
1508 | return 0; | ||
1509 | } | ||
1510 | /* No interrupt after machine check - kill pending timers. */ | ||
1511 | if (cdev) | ||
1512 | ccw_device_set_timeout(cdev, 0); | ||
1513 | if (!disc && !slow) { | ||
1514 | /* Non-disconnected devices are evaluated on the slow path. */ | ||
1515 | spin_unlock_irqrestore(sch->lock, flags); | ||
1516 | return -EAGAIN; | ||
1517 | } | ||
1518 | event = io_subchannel_get_status(sch); | ||
1519 | CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n", | ||
1520 | sch->schid.ssid, sch->schid.sch_no, event, | ||
1521 | disc ? "disconnected" : "normal", | ||
1522 | slow ? "slow" : "fast"); | ||
1523 | /* Analyze subchannel status. */ | ||
1524 | action = NONE; | ||
1525 | switch (event) { | ||
1526 | case CIO_NO_PATH: | ||
1527 | if (disc) { | ||
1528 | /* Check if paths have become available. */ | ||
1529 | action = REPROBE; | ||
1530 | break; | ||
1531 | } | ||
1532 | /* fall through */ | ||
1533 | case CIO_GONE: | ||
1534 | /* Prevent unwanted effects when opening lock. */ | ||
1535 | cio_disable_subchannel(sch); | ||
1536 | device_set_disconnected(cdev); | ||
1537 | /* Ask driver what to do with device. */ | ||
1538 | action = UNREGISTER; | ||
1539 | spin_unlock_irqrestore(sch->lock, flags); | ||
1540 | ret = io_subchannel_notify(sch, event); | ||
1541 | spin_lock_irqsave(sch->lock, flags); | ||
1542 | if (ret) | ||
1543 | action = NONE; | ||
1544 | break; | ||
1545 | case CIO_REVALIDATE: | ||
1546 | /* Device will be removed, so no notify necessary. */ | ||
1547 | if (disc) | ||
1548 | /* Reprobe because immediate unregister might block. */ | ||
1549 | action = REPROBE; | ||
1550 | else | ||
1551 | action = UNREGISTER_PROBE; | ||
1552 | break; | ||
1553 | case CIO_OPER: | ||
1554 | if (disc) | ||
1555 | /* Get device operational again. */ | ||
1556 | action = REPROBE; | ||
1557 | break; | ||
1558 | } | ||
1559 | /* Perform action. */ | ||
1560 | ret = 0; | ||
1561 | switch (action) { | ||
1562 | case UNREGISTER: | ||
1563 | case UNREGISTER_PROBE: | ||
1564 | /* Unregister device (will use subchannel lock). */ | ||
1565 | spin_unlock_irqrestore(sch->lock, flags); | ||
1566 | css_sch_device_unregister(sch); | ||
1567 | spin_lock_irqsave(sch->lock, flags); | ||
1568 | |||
1569 | /* Reset intparm to zeroes. */ | ||
1570 | sch->schib.pmcw.intparm = 0; | ||
1571 | cio_modify(sch); | ||
1572 | break; | ||
1573 | case REPROBE: | ||
1574 | ccw_device_trigger_reprobe(cdev); | ||
1575 | break; | ||
1576 | default: | ||
1577 | break; | ||
1578 | } | ||
1579 | spin_unlock_irqrestore(sch->lock, flags); | ||
1580 | /* Probe if necessary. */ | ||
1581 | if (action == UNREGISTER_PROBE) | ||
1582 | ret = css_probe_device(sch->schid); | ||
1583 | |||
1584 | return ret; | ||
1585 | } | ||
1586 | |||
1329 | #ifdef CONFIG_CCW_CONSOLE | 1587 | #ifdef CONFIG_CCW_CONSOLE |
1330 | static struct ccw_device console_cdev; | 1588 | static struct ccw_device console_cdev; |
1331 | static struct ccw_device_private console_private; | 1589 | static struct ccw_device_private console_private; |
@@ -1558,71 +1816,6 @@ ccw_device_get_subchannel_id(struct ccw_device *cdev) | |||
1558 | return sch->schid; | 1816 | return sch->schid; |
1559 | } | 1817 | } |
1560 | 1818 | ||
1561 | static int recovery_check(struct device *dev, void *data) | ||
1562 | { | ||
1563 | struct ccw_device *cdev = to_ccwdev(dev); | ||
1564 | int *redo = data; | ||
1565 | |||
1566 | spin_lock_irq(cdev->ccwlock); | ||
1567 | switch (cdev->private->state) { | ||
1568 | case DEV_STATE_DISCONNECTED: | ||
1569 | CIO_MSG_EVENT(4, "recovery: trigger 0.%x.%04x\n", | ||
1570 | cdev->private->dev_id.ssid, | ||
1571 | cdev->private->dev_id.devno); | ||
1572 | dev_fsm_event(cdev, DEV_EVENT_VERIFY); | ||
1573 | *redo = 1; | ||
1574 | break; | ||
1575 | case DEV_STATE_DISCONNECTED_SENSE_ID: | ||
1576 | *redo = 1; | ||
1577 | break; | ||
1578 | } | ||
1579 | spin_unlock_irq(cdev->ccwlock); | ||
1580 | |||
1581 | return 0; | ||
1582 | } | ||
1583 | |||
1584 | static void recovery_work_func(struct work_struct *unused) | ||
1585 | { | ||
1586 | int redo = 0; | ||
1587 | |||
1588 | bus_for_each_dev(&ccw_bus_type, NULL, &redo, recovery_check); | ||
1589 | if (redo) { | ||
1590 | spin_lock_irq(&recovery_lock); | ||
1591 | if (!timer_pending(&recovery_timer)) { | ||
1592 | if (recovery_phase < ARRAY_SIZE(recovery_delay) - 1) | ||
1593 | recovery_phase++; | ||
1594 | mod_timer(&recovery_timer, jiffies + | ||
1595 | recovery_delay[recovery_phase] * HZ); | ||
1596 | } | ||
1597 | spin_unlock_irq(&recovery_lock); | ||
1598 | } else | ||
1599 | CIO_MSG_EVENT(4, "recovery: end\n"); | ||
1600 | } | ||
1601 | |||
1602 | static DECLARE_WORK(recovery_work, recovery_work_func); | ||
1603 | |||
1604 | static void recovery_func(unsigned long data) | ||
1605 | { | ||
1606 | /* | ||
1607 | * We can't do our recovery in softirq context and it's not | ||
1608 | * performance critical, so we schedule it. | ||
1609 | */ | ||
1610 | schedule_work(&recovery_work); | ||
1611 | } | ||
1612 | |||
1613 | void ccw_device_schedule_recovery(void) | ||
1614 | { | ||
1615 | unsigned long flags; | ||
1616 | |||
1617 | CIO_MSG_EVENT(4, "recovery: schedule\n"); | ||
1618 | spin_lock_irqsave(&recovery_lock, flags); | ||
1619 | if (!timer_pending(&recovery_timer) || (recovery_phase != 0)) { | ||
1620 | recovery_phase = 0; | ||
1621 | mod_timer(&recovery_timer, jiffies + recovery_delay[0] * HZ); | ||
1622 | } | ||
1623 | spin_unlock_irqrestore(&recovery_lock, flags); | ||
1624 | } | ||
1625 | |||
1626 | MODULE_LICENSE("GPL"); | 1819 | MODULE_LICENSE("GPL"); |
1627 | EXPORT_SYMBOL(ccw_device_set_online); | 1820 | EXPORT_SYMBOL(ccw_device_set_online); |
1628 | EXPORT_SYMBOL(ccw_device_set_offline); | 1821 | EXPORT_SYMBOL(ccw_device_set_offline); |