libata: fix shutdown warning message printing

Unlocking ap->lock and ssleeping don't work because SCSI commands can
be issued from completion path without context.  Reimplement delayed
completion by allowing translation functions to override
qc->scsidone(), storing the original completion function to
scmd->scsi_done() and overriding qc->scsidone() with a function which
schedules delayed invocation of scmd->scsi_done().

This isn't pretty at all but all the ugly parts are thankfully
contained in the stop translation path where the compat feature is
implemented.

Signed-off-by: Tejun Heo <htejun@gmail.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
This commit is contained in:
Tejun Heo 2007-05-14 17:26:18 +02:00 committed by Jeff Garzik
parent 3cadbcc098
commit da071b42f7
1 changed files with 27 additions and 8 deletions

View File

@ -893,6 +893,23 @@ int ata_scsi_change_queue_depth(struct scsi_device *sdev, int queue_depth)
return queue_depth;
}
/* XXX: for ata_spindown_compat */
static void ata_delayed_done_timerfn(unsigned long arg)
{
struct scsi_cmnd *scmd = (void *)arg;
scmd->scsi_done(scmd);
}
/* XXX: for ata_spindown_compat */
static void ata_delayed_done(struct scsi_cmnd *scmd)
{
static struct timer_list timer;
setup_timer(&timer, ata_delayed_done_timerfn, (unsigned long)scmd);
mod_timer(&timer, jiffies + 5 * HZ);
}
/**
* ata_scsi_start_stop_xlat - Translate SCSI START STOP UNIT command
* @qc: Storage for translated ATA taskfile
@ -952,19 +969,21 @@ static unsigned int ata_scsi_start_stop_xlat(struct ata_queued_cmd *qc)
if (ata_spindown_compat &&
(system_state == SYSTEM_HALT ||
system_state == SYSTEM_POWER_OFF)) {
static int warned = 0;
static unsigned long warned = 0;
if (!warned) {
spin_unlock_irq(qc->ap->lock);
if (!test_and_set_bit(0, &warned)) {
ata_dev_printk(qc->dev, KERN_WARNING,
"DISK MIGHT NOT BE SPUN DOWN PROPERLY. "
"UPDATE SHUTDOWN UTILITY\n");
ata_dev_printk(qc->dev, KERN_WARNING,
"For more info, visit "
"http://linux-ata.org/shutdown.html\n");
warned = 1;
ssleep(5);
spin_lock_irq(qc->ap->lock);
/* ->scsi_done is not used, use it for
* delayed completion.
*/
scmd->scsi_done = qc->scsidone;
qc->scsidone = ata_delayed_done;
}
scmd->result = SAM_STAT_GOOD;
return 1;
@ -1488,14 +1507,14 @@ static int ata_scsi_translate(struct ata_device *dev, struct scsi_cmnd *cmd,
early_finish:
ata_qc_free(qc);
done(cmd);
qc->scsidone(cmd);
DPRINTK("EXIT - early finish (good or error)\n");
return 0;
err_did:
ata_qc_free(qc);
cmd->result = (DID_ERROR << 16);
done(cmd);
qc->scsidone(cmd);
err_mem:
DPRINTK("EXIT - internal\n");
return 0;