* like ioctls and character device requests - this is because
* we essentially just inject a request into the queue for the
* device.
+ *
+ * In order to support the scsi_device_quiesce function, we
+ * now inject requests on the *head* of the device queue
+ * rather than the tail.
*/
void scsi_do_req(struct scsi_request *sreq, const void *cmnd,
void *buffer, unsigned bufflen,
sreq->sr_cmd_len = COMMAND_SIZE(sreq->sr_cmnd[0]);
/*
- * At this point, we merely set up the command, stick it in the normal
- * request queue, and return. Eventually that request will come to the
- * top of the list, and will be dispatched.
+ * head injection *required* here otherwise quiesce won't work
*/
- scsi_insert_special_req(sreq, 0);
+ scsi_insert_special_req(sreq, 1);
}
static void scsi_wait_done(struct scsi_cmnd *cmd)
}
/* OK, we only allow special commands (i.e. not
* user initiated ones */
- specials_only = 1;
+ specials_only = sdev->sdev_state;
}
/*
} else if (req->flags & (REQ_CMD | REQ_BLOCK_PC)) {
if(unlikely(specials_only)) {
+ if(specials_only == SDEV_QUIESCE)
+ return BLKPREP_DEFER;
+
printk(KERN_ERR "scsi%d (%d:%d): rejecting I/O to device being removed\n",
sdev->host->host_no, sdev->id, sdev->lun);
return BLKPREP_KILL;
return ret;
}
+
+/**
+ * scsi_device_set_state - Take the given device through the device
+ * state model.
+ * @sdev: scsi device to change the state of.
+ * @state: state to change to.
+ *
+ * Returns zero if unsuccessful or an error if the requested
+ * transition is illegal.
+ **/
+int
+scsi_device_set_state(struct scsi_device *sdev, enum scsi_device_state state)
+{
+ enum scsi_device_state oldstate = sdev->sdev_state;
+
+ /* FIXME: eventually we will enforce all the state model
+ * transitions here */
+
+ if(oldstate == state)
+ return 0;
+
+ switch(state) {
+ case SDEV_RUNNING:
+ if(oldstate != SDEV_CREATED && oldstate != SDEV_QUIESCE)
+ return -EINVAL;
+ break;
+
+ case SDEV_QUIESCE:
+ if(oldstate != SDEV_RUNNING)
+ return -EINVAL;
+ break;
+
+ default:
+ break;
+ }
+ sdev->sdev_state = state;
+
+ return 0;
+}
+EXPORT_SYMBOL(scsi_device_set_state);
+
+/**
+ * scsi_device_quiesce - Block user issued commands.
+ * @sdev: scsi device to quiesce.
+ *
+ * This works by trying to transition to the SDEV_QUIESCE state
+ * (which must be a legal transition). When the device is in this
+ * state, only special requests will be accepted, all others will
+ * be deferred. Since special requests may also be requeued requests,
+ * a successful return doesn't guarantee the device will be
+ * totally quiescent.
+ *
+ * Must be called with user context, may sleep.
+ *
+ * Returns zero if unsuccessful or an error if not.
+ **/
+int
+scsi_device_quiesce(struct scsi_device *sdev)
+{
+ int err = scsi_device_set_state(sdev, SDEV_QUIESCE);
+ if(err)
+ return err;
+
+ scsi_run_queue(sdev->request_queue);
+ while(sdev->device_busy) {
+ schedule_timeout(HZ/5);
+ scsi_run_queue(sdev->request_queue);
+ }
+ return 0;
+}
+EXPORT_SYMBOL(scsi_device_quiesce);
+
+/**
+ * scsi_device_resume - Restart user issued commands to a quiesced device.
+ * @sdev: scsi device to resume.
+ *
+ * Moves the device from quiesced back to running and restarts the
+ * queues.
+ *
+ * Must be called with user context, may sleep.
+ **/
+void
+scsi_device_resume(struct scsi_device *sdev)
+{
+ if(sdev->sdev_state != SDEV_QUIESCE)
+ return;
+
+ scsi_device_set_state(sdev, SDEV_RUNNING);
+ scsi_run_queue(sdev->request_queue);
+}
+EXPORT_SYMBOL(scsi_device_resume);
+
int scsi_sysfs_add_sdev(struct scsi_device *sdev)
{
struct class_device_attribute **attrs;
- int error = -EINVAL, i;
+ int error, i;
- if (sdev->sdev_state != SDEV_CREATED)
+ if ((error = scsi_device_set_state(sdev, SDEV_RUNNING)) != 0)
return error;
- sdev->sdev_state = SDEV_RUNNING;
-
error = device_add(&sdev->sdev_gendev);
if (error) {
printk(KERN_INFO "error 1\n");
clean_device2:
class_device_del(&sdev->sdev_classdev);
clean_device:
- sdev->sdev_state = SDEV_CANCEL;
+ scsi_device_set_state(sdev, SDEV_CANCEL);
device_del(&sdev->sdev_gendev);
put_device(&sdev->sdev_gendev);
void scsi_remove_device(struct scsi_device *sdev)
{
if (sdev->sdev_state == SDEV_RUNNING || sdev->sdev_state == SDEV_CANCEL) {
- sdev->sdev_state = SDEV_DEL;
+ scsi_device_set_state(sdev, SDEV_DEL);
class_device_unregister(&sdev->sdev_classdev);
class_device_unregister(&sdev->transport_classdev);
device_del(&sdev->sdev_gendev);
* sdev state
*/
enum scsi_device_state {
- SDEV_CREATED, /* device created but not added to sysfs
+ SDEV_CREATED = 1, /* device created but not added to sysfs
* Only internal commands allowed (for inq) */
SDEV_RUNNING, /* device properly configured
* All commands allowed */
* Only error handler commands allowed */
SDEV_DEL, /* device deleted
* no commands allowed */
+ SDEV_QUIESCE, /* Device quiescent. No block commands
+ * will be accepted, only specials (which
+ * originate in the mid-layer) */
};
struct scsi_device {
extern int scsi_mode_sense(struct scsi_device *sdev, int dbd, int modepage,
unsigned char *buffer, int len, int timeout,
int retries, struct scsi_mode_data *data);
+extern int scsi_device_set_state(struct scsi_device *sdev,
+ enum scsi_device_state state);
+extern int scsi_device_quiesce(struct scsi_device *sdev);
+extern void scsi_device_resume(struct scsi_device *sdev);
#endif /* _SCSI_SCSI_DEVICE_H */