[PATCH 2.6.15-rc7-git3 2/3] libata: framework API for hotswapping drives on libata controllers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch adds a framework for hot-swapping drives on libata-run
controllers.  The functions 'sata_hot_plug' and 'sata_hot_unplug' can
be called by low level hotswap-capable SATA controllers, and
subsequent functions in the call chain will (in the future) be entry
points for PATA warm-swapping of drives.

Signed-off-by:  Luke Kosewski <[email protected]>

Luke Kosewski
29.12.05    Luke Kosewski   <[email protected]>

	* A patch which adds a framework for drive hotswap in libata drivers.
	  For hotswap-capable SATA controllers, the functions sata_hot_plug and
	  sata_hot_unplug will allow handling of new or removed disks in the
	  system.  Further up the call chain are entry points which (in the
	  future) will allow for PATA warm-swapping.

	Signed-off-by:  Luke Kosewski <[email protected]>

--- linux-2.6.15-rc7/drivers/scsi/libata-core.c.old	2005-12-28 18:15:53.000000000 -0500
+++ linux-2.6.15-rc7/drivers/scsi/libata-core.c	2005-12-29 15:53:32.000000000 -0500
@@ -77,6 +77,7 @@ static void __ata_qc_complete(struct ata
 
 static unsigned int ata_unique_id = 1;
 static struct workqueue_struct *ata_wq;
+static struct workqueue_struct *ata_hotplug_wq;
 
 int atapi_enabled = 0;
 module_param(atapi_enabled, int, 0444);
@@ -1110,6 +1111,9 @@ static void ata_dev_identify(struct ata_
 		return;
 	}
 
+	/* wipe all flags; this might be a different drive on hotswap. */
+	dev->flags = 0;
+
 	if (ap->flags & (ATA_FLAG_SRST | ATA_FLAG_SATA_RESET))
 		using_edd = 0;
 	else
@@ -3982,6 +3986,103 @@ idle_irq:
 	return 0;	/* irq not handled */
 }
 
+void ata_check_kill_qc(struct ata_port *ap, unsigned int device)
+{
+	struct ata_queued_cmd *qc = ata_qc_from_tag(ap, ap->active_tag);
+
+	if (unlikely(qc) && device == qc->dev->devno) {
+		/* Kill outstanding qc to device if one exists */
+		ata_qc_complete(qc, ATA_ERR);
+	}
+}
+
+static void ata_hotplug_task(void *_data)
+{
+	struct ata_port *ap = (struct ata_port *)_data;
+	enum hotplug_states hotplug_todo[ATA_MAX_DEVICES];
+	unsigned long flags;
+	int i;
+
+	/* This function could have just one loop, but then we'd have to acquire
+	 * the spin_lock multiple times.  Better to just have two loops.
+	 */
+	spin_lock_irqsave(&ap->host_set->lock, flags);
+	for (i = 0; i < ATA_MAX_DEVICES; ++i) {
+		/* Make sure we don't modify while reading! */
+		hotplug_todo[i] = ap->device[i].plug; 
+		ap->device[i].plug = HOT_NOOP;
+	}
+	spin_unlock_irqrestore(&ap->host_set->lock, flags);
+
+	for (i = 0; i < ATA_MAX_DEVICES; ++i) {
+		switch (hotplug_todo[i]) {
+			case HOT_PLUG:
+				DPRINTK("Got a plug request on port %d\n", ap->id);
+
+				/* This might be necessary if we unplug and plug
+				 * in a drive within ATA_TMOUT_HOTPLUG / HZ
+				 * seconds... due to the debounce timer, one
+				 * event is generated.  Since the last event was
+				 * a plug, the unplug routine never gets called,
+				 * so we need to clean up the mess first.  If
+				 * there was never a drive here in the first
+				 * place, this will just do nothing.  Otherwise,
+				 * it basically prevents 'plug' from being
+				 * called twice with no unplug in between.
+				 */
+				ata_scsi_hot_unplug(ap, i);
+
+				/* This is necessary for some Seagate drives. */
+				if (ap->flags & ATA_FLAG_SATA)
+					ap->flags |= ATA_FLAG_SATA_RESET; 
+				ap->udma_mask = ap->orig_udma_mask;
+
+				if (!ata_bus_probe(ap)) /* Success */
+					ata_scsi_hot_plug(ap, i);
+				break;
+			case HOT_UNPLUG:
+				DPRINTK("Got an unplug request on port %d\n", ap->id);
+
+				ata_scsi_hot_unplug(ap, i);
+				/* Fall through */
+			case HOT_NOOP:
+				/* No-op; do nothing */
+				break;
+			default:
+				/* Should never happen */
+				BUG();
+		}
+	}
+}
+
+void sata_hot_plug(struct ata_port *ap)
+{
+	if (ap->ops->hotplug_plug_janitor)
+		ap->ops->hotplug_plug_janitor(ap);
+
+	/* This line should be protected by a host_set->lock.  If we follow the
+	 * call chain up from this function, it's called from within an
+	 * interrupt handler.  Make sure that, when called, this function is
+	 * protected in said handler.
+	 */
+	ap->device[0].plug = HOT_PLUG;
+
+	queue_delayed_work(ata_hotplug_wq, &ap->hotplug_task, ATA_TMOUT_HOTPLUG);
+}
+
+void sata_hot_unplug(struct ata_port *ap)
+{
+	ap->device[0].class = ATA_DEV_NONE;
+	
+	if (ap->ops->hotplug_unplug_janitor)
+		ap->ops->hotplug_unplug_janitor(ap);
+	
+	/* See comment near similar line in sata_hot_plug function. */
+	ap->device[0].plug = HOT_UNPLUG;
+
+	queue_delayed_work(ata_hotplug_wq, &ap->hotplug_task, ATA_TMOUT_HOTPLUG);
+}
+
 /**
  *	ata_interrupt - Default ATA host interrupt handler
  *	@irq: irq line (unused)
@@ -4224,12 +4325,16 @@ static void ata_host_init(struct ata_por
 	ap->cbl = ATA_CBL_NONE;
 	ap->active_tag = ATA_TAG_POISON;
 	ap->last_ctl = 0xFF;
+	ap->orig_udma_mask = ent->udma_mask;
 
+	INIT_WORK(&ap->hotplug_task, ata_hotplug_task, ap);
 	INIT_WORK(&ap->packet_task, atapi_packet_task, ap);
 	INIT_WORK(&ap->pio_task, ata_pio_task, ap);
 
-	for (i = 0; i < ATA_MAX_DEVICES; i++)
+	for (i = 0; i < ATA_MAX_DEVICES; i++) {
 		ap->device[i].devno = i;
+		ap->device[i].plug = HOT_NOOP;
+	}
 
 #ifdef ATA_IRQ_TRAP
 	ap->stats.unhandled_irq = 1;
@@ -4868,6 +4973,11 @@ static int __init ata_init(void)
 	ata_wq = create_workqueue("ata");
 	if (!ata_wq)
 		return -ENOMEM;
+	ata_hotplug_wq = create_workqueue("ata_hotplug");
+	if (!ata_hotplug_wq) {
+		destroy_workqueue(ata_wq);
+		return -ENOMEM;
+	}
 
 	printk(KERN_DEBUG "libata version " DRV_VERSION " loaded.\n");
 	return 0;
@@ -4876,6 +4986,7 @@ static int __init ata_init(void)
 static void __exit ata_exit(void)
 {
 	destroy_workqueue(ata_wq);
+	destroy_workqueue(ata_hotplug_wq);
 }
 
 module_init(ata_init);
@@ -4953,6 +5064,8 @@ EXPORT_SYMBOL_GPL(ata_dev_classify);
 EXPORT_SYMBOL_GPL(ata_dev_id_string);
 EXPORT_SYMBOL_GPL(ata_dev_config);
 EXPORT_SYMBOL_GPL(ata_scsi_simulate);
+EXPORT_SYMBOL_GPL(sata_hot_plug);
+EXPORT_SYMBOL_GPL(sata_hot_unplug);
 
 EXPORT_SYMBOL_GPL(ata_timing_compute);
 EXPORT_SYMBOL_GPL(ata_timing_merge);
--- linux-2.6.15-rc7/drivers/scsi/libata-scsi.c.old	2005-12-28 18:16:17.000000000 -0500
+++ linux-2.6.15-rc7/drivers/scsi/libata-scsi.c	2005-12-29 16:11:10.000000000 -0500
@@ -1210,6 +1210,11 @@ static int ata_scsi_qc_complete(struct a
 	u8 *cdb = cmd->cmnd;
  	int need_sense = (err_mask != 0);
 
+	if (unlikely(!ata_scsi_find_dev(qc->ap, cmd->device))) {
+		cmd->result = (DID_BAD_TARGET << 16);
+		goto out_no_dev;
+	}
+
 	/* For ATA pass thru (SAT) commands, generate a sense block if
 	 * user mandated it or if there's an error.  Note that if we
 	 * generate because the user forced us to, a check condition
@@ -1239,11 +1244,39 @@ static int ata_scsi_qc_complete(struct a
 		ata_dump_status(qc->ap->id, &qc->tf);
 	}
 
+out_no_dev:
 	qc->scsidone(cmd);
 
 	return 0;
 }
 
+void ata_scsi_hot_plug(struct ata_port *ap, unsigned int device)
+{
+	/* libata uses the 'id' or 'target' value */
+	scsi_add_device(ap->host, 0, device, 0);
+}
+
+void ata_scsi_hot_unplug(struct ata_port *ap, unsigned int device)
+{
+	/* libata uses the 'id' or 'target' value */
+	struct scsi_device *scd = scsi_device_lookup(ap->host, 0, device, 0);
+
+	/* Make sure that we set this here, in case we aren't called as a
+	 * result of sata_hot_unplug */
+	ap->device[device].class = ATA_DEV_NONE;
+
+	if (scd)  /* Set to cancel state to block further I/O */
+		scsi_device_set_state(scd, SDEV_CANCEL);
+
+	/* We might have a pending qc on I/O to a removed device. */
+	ata_check_kill_qc(ap, device);
+	
+	if (scd) {
+		scsi_remove_device(scd);
+		scsi_device_put(scd);
+	}
+}
+
 /**
  *	ata_scsi_translate - Translate then issue SCSI command to ATA device
  *	@ap: ATA port to which the command is addressed
--- linux-2.6.15-rc7/drivers/scsi/libata.h.old	2005-12-28 18:16:08.000000000 -0500
+++ linux-2.6.15-rc7/drivers/scsi/libata.h	2005-12-29 13:01:33.000000000 -0500
@@ -46,6 +46,7 @@ extern void ata_rwcmd_protocol(struct at
 extern void ata_qc_free(struct ata_queued_cmd *qc);
 extern int ata_qc_issue(struct ata_queued_cmd *qc);
 extern int ata_check_atapi_dma(struct ata_queued_cmd *qc);
+extern void ata_check_kill_qc(struct ata_port *ap, unsigned int device);
 extern void ata_dev_select(struct ata_port *ap, unsigned int device,
                            unsigned int wait, unsigned int can_sleep);
 extern void swap_buf_le16(u16 *buf, unsigned int buf_words);
@@ -84,5 +85,7 @@ extern void ata_scsi_set_sense(struct sc
 extern void ata_scsi_rbuf_fill(struct ata_scsi_args *args,
                         unsigned int (*actor) (struct ata_scsi_args *args,
                                            u8 *rbuf, unsigned int buflen));
+extern void ata_scsi_hot_plug(struct ata_port *ap, unsigned int device);
+extern void ata_scsi_hot_unplug(struct ata_port *ap, unsigned int device);
 
 #endif /* __LIBATA_H__ */
--- linux-2.6.15-rc7/include/linux/libata.h.old	2005-12-28 18:16:33.000000000 -0500
+++ linux-2.6.15-rc7/include/linux/libata.h	2005-12-29 15:20:49.000000000 -0500
@@ -136,6 +136,7 @@ enum {
 	ATA_TMOUT_BOOT_QUICK	= 7 * HZ,	/* hueristic */
 	ATA_TMOUT_CDB		= 30 * HZ,
 	ATA_TMOUT_CDB_QUICK	= 5 * HZ,
+	ATA_TMOUT_HOTPLUG	= HZ / 2,	/* FIXME:  Guess value? */
 
 	/* ATA bus states */
 	BUS_UNKNOWN		= 0,
@@ -188,6 +189,12 @@ enum ata_completion_errors {
 	AC_ERR_HOST_BUS		= (1 << 3),
 };
 
+enum hotplug_states {
+	HOT_NOOP,
+	HOT_PLUG,
+	HOT_UNPLUG,
+};
+
 /* forward declarations */
 struct scsi_device;
 struct ata_port_operations;
@@ -311,6 +318,8 @@ struct ata_device {
 	u16			cylinders;	/* Number of cylinders */
 	u16			heads;		/* Number of heads */
 	u16			sectors;	/* Number of sectors per track */
+	/* For hotplug */
+	enum hotplug_states	plug;
 };
 
 struct ata_port {
@@ -348,6 +357,7 @@ struct ata_port {
 	struct ata_host_stats	stats;
 	struct ata_host_set	*host_set;
 
+	struct work_struct	hotplug_task;
 	struct work_struct	packet_task;
 
 	struct work_struct	pio_task;
@@ -355,6 +365,8 @@ struct ata_port {
 	unsigned long		pio_task_timeout;
 
 	void			*private_data;
+
+	unsigned int		orig_udma_mask;
 };
 
 struct ata_port_operations {
@@ -400,6 +412,8 @@ struct ata_port_operations {
 
 	void (*bmdma_stop) (struct ata_queued_cmd *qc);
 	u8   (*bmdma_status) (struct ata_port *ap);
+	void (*hotplug_plug_janitor) (struct ata_port *ap);
+	void (*hotplug_unplug_janitor) (struct ata_port *ap);
 };
 
 struct ata_port_info {
@@ -445,6 +459,8 @@ extern int ata_scsi_queuecmd(struct scsi
 extern int ata_scsi_error(struct Scsi_Host *host);
 extern int ata_scsi_release(struct Scsi_Host *host);
 extern unsigned int ata_host_intr(struct ata_port *ap, struct ata_queued_cmd *qc);
+extern void sata_hot_plug(struct ata_port *ap);
+extern void sata_hot_unplug(struct ata_port *ap);
 extern int ata_ratelimit(void);
 
 /*


[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux