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]