From: Martin Schwidefsky <schwidefsky@de.ibm.com>

s390 tape driver changes:
 - Prevent offline while device is in use.
 - Do not use bus_id string in debug feature messages.
 - Check for IS_ERR(irb) error conditions in interrupt handler.
 - Fix removing tape discipline modules.


---

 25-akpm/drivers/s390/char/tape.h       |    5 
 25-akpm/drivers/s390/char/tape_34xx.c  |   24 ++--
 25-akpm/drivers/s390/char/tape_block.c |    7 +
 25-akpm/drivers/s390/char/tape_core.c  |  196 +++++++++++++++++++++++++++------
 25-akpm/drivers/s390/char/tape_std.c   |   22 ++-
 5 files changed, 198 insertions(+), 56 deletions(-)

diff -puN drivers/s390/char/tape_34xx.c~s390-tape drivers/s390/char/tape_34xx.c
--- 25/drivers/s390/char/tape_34xx.c~s390-tape	2004-03-26 12:16:03.423006976 -0800
+++ 25-akpm/drivers/s390/char/tape_34xx.c	2004-03-26 12:16:03.433005456 -0800
@@ -202,8 +202,7 @@ tape_34xx_unsolicited_irq(struct tape_de
 		tape_34xx_delete_sbid_from(device, 0);
 		tape_34xx_schedule_work(device, TO_MSEN);
 	} else {
-		DBF_EVENT(3, "unsol.irq! dev end: %s\n",
-				device->cdev->dev.bus_id);
+		DBF_EVENT(3, "unsol.irq! dev end: %08x\n", device->cdev_id);
 		PRINT_WARN("Unsolicited IRQ (Device End) caught.\n");
 		tape_dump_sense(device, NULL, irb);
 	}
@@ -1314,17 +1313,18 @@ static struct ccw_device_id tape_34xx_id
 };
 
 static int
-tape_34xx_enable(struct ccw_device *cdev)
+tape_34xx_online(struct ccw_device *cdev)
 {
-	return tape_enable_device(cdev->dev.driver_data,
-				  &tape_discipline_34xx);
+	return tape_generic_online(
+		cdev->dev.driver_data,
+		&tape_discipline_34xx
+	);
 }
 
 static int
-tape_34xx_disable(struct ccw_device *cdev)
+tape_34xx_offline(struct ccw_device *cdev)
 {
-	tape_disable_device(cdev->dev.driver_data);
-	return 0;
+	return tape_generic_offline(cdev->dev.driver_data);
 }
 
 static struct ccw_driver tape_34xx_driver = {
@@ -1333,8 +1333,8 @@ static struct ccw_driver tape_34xx_drive
 	.ids = tape_34xx_ids,
 	.probe = tape_generic_probe,
 	.remove = tape_generic_remove,
-	.set_online = tape_34xx_enable,
-	.set_offline = tape_34xx_disable,
+	.set_online = tape_34xx_online,
+	.set_offline = tape_34xx_offline,
 };
 
 static int
@@ -1342,7 +1342,7 @@ tape_34xx_init (void)
 {
 	int rc;
 
-	DBF_EVENT(3, "34xx init: $Revision: 1.18 $\n");
+	DBF_EVENT(3, "34xx init: $Revision: 1.19 $\n");
 	/* Register driver for 3480/3490 tapes. */
 	rc = ccw_driver_register(&tape_34xx_driver);
 	if (rc)
@@ -1361,7 +1361,7 @@ tape_34xx_exit(void)
 MODULE_DEVICE_TABLE(ccw, tape_34xx_ids);
 MODULE_AUTHOR("(C) 2001-2002 IBM Deutschland Entwicklung GmbH");
 MODULE_DESCRIPTION("Linux on zSeries channel attached 3480 tape "
-		   "device driver ($Revision: 1.18 $)");
+		   "device driver ($Revision: 1.19 $)");
 MODULE_LICENSE("GPL");
 
 module_init(tape_34xx_init);
diff -puN drivers/s390/char/tape_block.c~s390-tape drivers/s390/char/tape_block.c
--- 25/drivers/s390/char/tape_block.c~s390-tape	2004-03-26 12:16:03.425006672 -0800
+++ 25-akpm/drivers/s390/char/tape_block.c	2004-03-26 12:16:03.434005304 -0800
@@ -274,12 +274,19 @@ tapeblock_cleanup_device(struct tape_dev
 	flush_scheduled_work();
 	device->blk_data.requeue_task.data = tape_put_device(device);
 
+	if (!device->blk_data.disk) {
+		PRINT_ERR("(%s): No gendisk to clean up!\n",
+			device->cdev->dev.bus_id);
+		goto cleanup_queue;
+	}
+
 	del_gendisk(device->blk_data.disk);
 	device->blk_data.disk->private_data =
 		tape_put_device(device->blk_data.disk->private_data);
 	put_disk(device->blk_data.disk);
 
 	device->blk_data.disk = NULL;
+cleanup_queue:
 	device->blk_data.request_queue->queuedata = tape_put_device(device);
 
 	blk_cleanup_queue(device->blk_data.request_queue);
diff -puN drivers/s390/char/tape_core.c~s390-tape drivers/s390/char/tape_core.c
--- 25/drivers/s390/char/tape_core.c~s390-tape	2004-03-26 12:16:03.427006368 -0800
+++ 25-akpm/drivers/s390/char/tape_core.c	2004-03-26 12:16:03.436005000 -0800
@@ -69,6 +69,34 @@ const char *tape_op_verbose[TO_SIZE] =
 	[TO_UNASSIGN] = "UAS"
 };
 
+static inline int
+busid_to_int(char *bus_id)
+{
+	int	dec;
+	int	d;
+	char *	s;
+
+	for(s = bus_id, d = 0; *s != '\0' && *s != '.'; s++)
+		d = (d * 10) + (*s - '0');
+	dec = d;
+	for(s++, d = 0; *s != '\0' && *s != '.'; s++)
+		d = (d * 10) + (*s - '0');
+	dec = (dec << 8) + d;
+
+	for(s++; *s != '\0'; s++) {
+		if (*s >= '0' && *s <= '9') {
+			d = *s - '0';
+		} else if (*s >= 'a' && *s <= 'f') {
+			d = *s - 'a' + 10;
+		} else {
+			d = *s - 'A' + 10;
+		}
+		dec = (dec << 4) + d;
+	}
+
+	return dec;
+}
+
 /*
  * Some channel attached tape specific attributes.
  *
@@ -296,10 +324,15 @@ tape_remove_minor(struct tape_device *de
 }
 
 /*
- * Enable tape device
+ * Set a device online.
+ *
+ * This function is called by the common I/O layer to move a device from the
+ * detected but offline into the online state.
+ * If we return an error (RC < 0) the device remains in the offline state. This
+ * can happen if the device is assigned somewhere else, for example.
  */
 int
-tape_enable_device(struct tape_device *device,
+tape_generic_online(struct tape_device *device,
 		   struct tape_discipline *discipline)
 {
 	int rc;
@@ -328,6 +361,9 @@ tape_enable_device(struct tape_device *d
 		goto out_char;
 
 	tape_state_set(device, TS_UNUSED);
+
+	DBF_LH(3, "(%08x): Drive set online\n", device->cdev_id);
+
 	return 0;
 
 out_char:
@@ -342,37 +378,49 @@ out:
 }
 
 /*
- * Disable tape device. Check if there is a running request and
- * terminate it. Post all queued requests with -EIO.
+ * Set device offline.
+ *
+ * Called by the common I/O layer if the drive should set offline on user
+ * request. We may prevent this by returning an error.
+ * Manual offline is only allowed while the drive is not in use.
  */
-void
-tape_disable_device(struct tape_device *device)
+int
+tape_generic_offline(struct tape_device *device)
 {
-	struct list_head *l, *n;
-	struct tape_request *request;
+	if (!device) {
+		PRINT_ERR("tape_generic_offline: no such device\n");
+		return -ENODEV;
+	}
+
+	DBF_LH(3, "(%08x): tape_generic_offline(%p)\n",
+		device->cdev_id, device);
 
 	spin_lock_irq(get_ccwdev_lock(device->cdev));
-	/* Post remaining requests with -EIO */
-	list_for_each_safe(l, n, &device->req_queue) {
-		request = list_entry(l, struct tape_request, list);
-		if (request->status == TAPE_REQUEST_IN_IO)
-			__tape_halt_io(device, request);
-		list_del(&request->list);
-		/* Decrease ref_count for removed request. */
-		request->device = tape_put_device(device);
-		request->rc = -EIO;
-		if (request->callback != NULL)
-			request->callback(request, request->callback_data);
+	switch (device->tape_state) {
+		case TS_INIT:
+		case TS_NOT_OPER:
+			break;
+		case TS_UNUSED:
+			tapeblock_cleanup_device(device);
+			tapechar_cleanup_device(device);
+			device->discipline->cleanup_device(device);
+			tape_remove_minor(device);
+		default:
+			DBF_EVENT(3, "(%08x): Set offline failed "
+				"- drive in use.\n",
+				device->cdev_id);
+			PRINT_WARN("(%s): Set offline failed "
+				"- drive in use.\n",
+				device->cdev->dev.bus_id);
+			spin_unlock_irq(get_ccwdev_lock(device->cdev));
+			return -EBUSY;
 	}
 	spin_unlock_irq(get_ccwdev_lock(device->cdev));
 
-	tapeblock_cleanup_device(device);
-	tapechar_cleanup_device(device);
-	device->discipline->cleanup_device(device);
-	tape_remove_minor(device);
-
 	tape_med_state_set(device, MS_UNKNOWN);
-	device->tape_state = TS_INIT;
+
+	DBF_LH(3, "(%08x): Drive set offline.\n", device->cdev_id);
+	return 0;
 }
 
 /*
@@ -479,14 +527,14 @@ int
 tape_generic_probe(struct ccw_device *cdev)
 {
 	struct tape_device *device;
-	char *bus_id = cdev->dev.bus_id;
 
 	device = tape_alloc_device();
 	if (IS_ERR(device))
 		return -ENODEV;
-	PRINT_INFO("tape device %s found\n", bus_id);
+	PRINT_INFO("tape device %s found\n", cdev->dev.bus_id);
 	cdev->dev.driver_data = device;
 	device->cdev = cdev;
+	device->cdev_id = busid_to_int(cdev->dev.bus_id);
 	cdev->handler = __tape_do_irq;
 
 	ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP);
@@ -497,15 +545,58 @@ tape_generic_probe(struct ccw_device *cd
 
 /*
  * Driverfs tape remove function.
+ *
+ * This function is called whenever the common I/O layer detects the device
+ * gone. This can happen at any time and we cannot refuse.
  */
 void
 tape_generic_remove(struct ccw_device *cdev)
 {
-	ccw_device_set_offline(cdev);
+	struct tape_device *	device;
+	struct tape_request *	request;
+	struct list_head *	l, *n;
+
+	device = cdev->dev.driver_data;
+	DBF_LH(3, "(%08x): tape_generic_remove(%p)\n", device->cdev_id, cdev);
+
+	/*
+	 * No more requests may be processed. So just post them as i/o errors.
+	 */
+	spin_lock_irq(get_ccwdev_lock(device->cdev));
+	list_for_each_safe(l, n, &device->req_queue) {
+		request = list_entry(l, struct tape_request, list);
+		if (request->status == TAPE_REQUEST_IN_IO)
+			request->status = TAPE_REQUEST_DONE;
+		list_del(&request->list);
+
+		/* Decrease ref_count for removed request. */
+		request->device = tape_put_device(device);
+		request->rc = -EIO;
+		if (request->callback != NULL)
+			request->callback(request, request->callback_data);
+	}
+
+	if (device->tape_state != TS_UNUSED && device->tape_state != TS_INIT) {
+		DBF_EVENT(3, "(%08x): Drive in use vanished!\n",
+			device->cdev_id);
+		PRINT_WARN("(%s): Drive in use vanished - expect trouble!\n",
+			device->cdev->dev.bus_id);
+		PRINT_WARN("State was %i\n", device->tape_state);
+		device->tape_state = TS_NOT_OPER;
+		tapeblock_cleanup_device(device);
+		tapechar_cleanup_device(device);
+		device->discipline->cleanup_device(device);
+		tape_remove_minor(device);
+	}
+	device->tape_state = TS_NOT_OPER;
+	tape_med_state_set(device, MS_UNKNOWN);
+	spin_unlock_irq(get_ccwdev_lock(device->cdev));
+
 	if (cdev->dev.driver_data != NULL) {
 		sysfs_remove_group(&cdev->dev.kobj, &tape_attr_group);
 		cdev->dev.driver_data = tape_put_device(cdev->dev.driver_data);
 	}
+
 }
 
 /*
@@ -665,7 +756,7 @@ tape_dump_sense_dbf(struct tape_device *
 		op = "---";
 	DBF_EVENT(3, "DSTAT : %02x   CSTAT: %02x\n",
 		  irb->scsw.dstat,irb->scsw.cstat);
-	DBF_EVENT(3, "DEVICE: %s OP\t: %s\n", device->cdev->dev.bus_id,op);
+	DBF_EVENT(3, "DEVICE: %08x OP\t: %s\n", device->cdev_id, op);
 	sptr = (unsigned int *) irb->ecw;
 	DBF_EVENT(3, "%08x %08x\n", sptr[0], sptr[1]);
 	DBF_EVENT(3, "%08x %08x\n", sptr[2], sptr[3]);
@@ -815,7 +906,7 @@ tape_do_io_interruptible(struct tape_dev
 	spin_lock_irq(get_ccwdev_lock(device->cdev));
 	rc = __tape_halt_io(device, request);
 	if (rc == 0) {
-		DBF_EVENT(3, "IO stopped on %s\n", device->cdev->dev.bus_id);
+		DBF_EVENT(3, "IO stopped on %08x\n", device->cdev_id);
 		rc = -ERESTARTSYS;
 	}
 	spin_unlock_irq(get_ccwdev_lock(device->cdev));
@@ -823,6 +914,24 @@ tape_do_io_interruptible(struct tape_dev
 }
 
 /*
+ * Handle requests that return an i/o error in the irb.
+ */
+static inline void
+tape_handle_killed_request(
+	struct tape_device *device,
+	struct tape_request *request)
+{
+	if(request != NULL) {
+		/* Set ending status. FIXME: Should the request be retried? */
+		request->rc = -EIO;
+		request->status = TAPE_REQUEST_DONE;
+		__tape_remove_request(device, request);
+	} else {
+		__tape_do_io_list(device);
+	}
+}
+
+/*
  * Tape interrupt routine, called from the ccw_device layer
  */
 static void
@@ -835,7 +944,7 @@ __tape_do_irq (struct ccw_device *cdev, 
 
 	device = (struct tape_device *) cdev->dev.driver_data;
 	if (device == NULL) {
-		PRINT_ERR("could not get device structure for bus_id %s "
+		PRINT_ERR("could not get device structure for %s "
 			  "in interrupt\n", cdev->dev.bus_id);
 		return;
 	}
@@ -843,6 +952,23 @@ __tape_do_irq (struct ccw_device *cdev, 
 
 	DBF_LH(6, "__tape_do_irq(device=%p, request=%p)\n", device, request);
 
+	/* On special conditions irb is an error pointer */
+	if (IS_ERR(irb)) {
+		switch (PTR_ERR(irb)) {
+			case -ETIMEDOUT:
+				PRINT_WARN("(%s): Request timed out\n",
+					cdev->dev.bus_id);
+			case -EIO:
+				tape_handle_killed_request(device, request);
+				break;
+			default:
+				PRINT_ERR("(%s): Unexpected i/o error %li\n",
+					cdev->dev.bus_id,
+					PTR_ERR(irb));
+		}
+		return;
+	}
+
 	/* May be an unsolicited irq */
 	if(request != NULL)
 		request->rescnt = irb->scsw.count;
@@ -1023,7 +1149,7 @@ tape_init (void)
 #ifdef DBF_LIKE_HELL
 	debug_set_level(tape_dbf_area, 6);
 #endif
-	DBF_EVENT(3, "tape init: ($Revision: 1.44 $)\n");
+	DBF_EVENT(3, "tape init: ($Revision: 1.48 $)\n");
 	tape_proc_init();
 	tapechar_init ();
 	tapeblock_init ();
@@ -1048,7 +1174,7 @@ tape_exit(void)
 MODULE_AUTHOR("(C) 2001 IBM Deutschland Entwicklung GmbH by Carsten Otte and "
 	      "Michael Holzheu (cotte@de.ibm.com,holzheu@de.ibm.com)");
 MODULE_DESCRIPTION("Linux on zSeries channel attached "
-		   "tape device driver ($Revision: 1.44 $)");
+		   "tape device driver ($Revision: 1.48 $)");
 MODULE_LICENSE("GPL");
 
 module_init(tape_init);
@@ -1056,9 +1182,9 @@ module_exit(tape_exit);
 
 EXPORT_SYMBOL(tape_dbf_area);
 EXPORT_SYMBOL(tape_generic_remove);
-EXPORT_SYMBOL(tape_disable_device);
 EXPORT_SYMBOL(tape_generic_probe);
-EXPORT_SYMBOL(tape_enable_device);
+EXPORT_SYMBOL(tape_generic_online);
+EXPORT_SYMBOL(tape_generic_offline);
 EXPORT_SYMBOL(tape_put_device);
 EXPORT_SYMBOL(tape_get_device_reference);
 EXPORT_SYMBOL(tape_state_verbose);
diff -puN drivers/s390/char/tape.h~s390-tape drivers/s390/char/tape.h
--- 25/drivers/s390/char/tape.h~s390-tape	2004-03-26 12:16:03.428006216 -0800
+++ 25-akpm/drivers/s390/char/tape.h	2004-03-26 12:16:03.432005608 -0800
@@ -198,6 +198,7 @@ struct tape_device {
 	/* entry in tape_device_list */
 	struct list_head		node;
 
+	int				cdev_id;
 	struct ccw_device *		cdev;
 	struct tape_class_device *	nt;
 	struct tape_class_device *	rt;
@@ -263,8 +264,8 @@ extern int tape_release(struct tape_devi
 extern int tape_mtop(struct tape_device *, int, int);
 extern void tape_state_set(struct tape_device *, enum tape_state);
 
-extern int tape_enable_device(struct tape_device *, struct tape_discipline *);
-extern void tape_disable_device(struct tape_device *device);
+extern int tape_generic_online(struct tape_device *, struct tape_discipline *);
+extern int tape_generic_offline(struct tape_device *device);
 
 /* Externals from tape_devmap.c */
 extern int tape_generic_probe(struct ccw_device *);
diff -puN drivers/s390/char/tape_std.c~s390-tape drivers/s390/char/tape_std.c
--- 25/drivers/s390/char/tape_std.c~s390-tape	2004-03-26 12:16:03.430005912 -0800
+++ 25-akpm/drivers/s390/char/tape_std.c	2004-03-26 12:16:03.437004848 -0800
@@ -42,8 +42,8 @@ tape_std_assign_timeout(unsigned long da
 
 	spin_lock_irq(get_ccwdev_lock(device->cdev));
 	if (request->callback != NULL) {
-		DBF_EVENT(3, "%s: Assignment timeout. Device busy.\n",
-			device->cdev->dev.bus_id);
+		DBF_EVENT(3, "%08x: Assignment timeout. Device busy.\n",
+			device->cdev_id);
 		PRINT_ERR("%s: Assignment timeout. Device busy.\n",
 			device->cdev->dev.bus_id);
 		ccw_device_clear(device->cdev, (long) request);
@@ -84,10 +84,10 @@ tape_std_assign(struct tape_device *devi
 	if (rc != 0) {
 		PRINT_WARN("%s: assign failed - device might be busy\n",
 			device->cdev->dev.bus_id);
-		DBF_EVENT(3, "%s: assign failed - device might be busy\n",
-			device->cdev->dev.bus_id);
+		DBF_EVENT(3, "%08x: assign failed - device might be busy\n",
+			device->cdev_id);
 	} else {
-		DBF_EVENT(3, "%s: Tape assigned\n", device->cdev->dev.bus_id);
+		DBF_EVENT(3, "%08x: Tape assigned\n", device->cdev_id);
 	}
 	tape_free_request(request);
 	return rc;
@@ -102,6 +102,14 @@ tape_std_unassign (struct tape_device *d
 	int                  rc;
 	struct tape_request *request;
 
+	if (device->tape_state == TS_NOT_OPER) {
+		DBF_EVENT(3, "(%08x): Can't unassign device\n",
+			device->cdev_id);
+		PRINT_WARN("(%s): Can't unassign device - device gone\n",
+			device->cdev->dev.bus_id);
+		return -EIO;
+	}
+
 	request = tape_alloc_request(2, 11);
 	if (IS_ERR(request))
 		return PTR_ERR(request);
@@ -111,10 +119,10 @@ tape_std_unassign (struct tape_device *d
 	tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
 
 	if ((rc = tape_do_io(device, request)) != 0) {
-		DBF_EVENT(3, "%s: Unassign failed\n", device->cdev->dev.bus_id);
+		DBF_EVENT(3, "%08x: Unassign failed\n", device->cdev_id);
 		PRINT_WARN("%s: Unassign failed\n", device->cdev->dev.bus_id);
 	} else {
-		DBF_EVENT(3, "%s: Tape unassigned\n", device->cdev->dev.bus_id);
+		DBF_EVENT(3, "%08x: Tape unassigned\n", device->cdev_id);
 	}
 	tape_free_request(request);
 	return rc;

_