Hello, Douglas & Jeff.

 This is preview implementation of multi-qc SCSI command translation.
New translation type 'sequence' is defined and handled by sat_task
running on ata_sat_wq.  Rationale...

 a. inside qc chaining as in sil m15w workaround is too hacky & fragile.

 b. multi qc chaining without task is possible but, with thread...
        - translation implementation is easier (simple contexts on
          stack, easier code flow...)
        - we are much less restriced in what we can do (in case full
          SAT implementation requires something weird).
        - this isn't a hot path, simplicity & maintainability weigh
          more than performance.

 Current implementation is broken in that each qc's completion status
cannot be acquired.  This can be fixed by adding opaque user data
field to ->complete_fn and unifying ->waiting mechanism into
->complete_fn.  If this task-based approach is okay with other ATA
developers, I'll do that and post splitted patches.

 The following is one big patch containing multi-qc sequencing
infrastructure and stupid & broken MODE_SELECT_10 implementation.
msel10 implemented here only deals with WCE and DRA of caching mode
page.  It's stupid but demonstrates enough, hopefully.

 Thanks.


diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c
--- a/drivers/scsi/libata-core.c
+++ b/drivers/scsi/libata-core.c
@@ -3924,6 +3924,7 @@ static void ata_host_init(struct ata_por
 
        INIT_WORK(&ap->packet_task, atapi_packet_task, ap);
        INIT_WORK(&ap->pio_task, ata_pio_task, ap);
+       INIT_WORK(&ap->sat_task, ata_sat_task, ap);
 
        for (i = 0; i < ATA_MAX_DEVICES; i++)
                ap->device[i].devno = i;
@@ -4530,6 +4531,12 @@ static int __init ata_init(void)
        if (!ata_wq)
                return -ENOMEM;
 
+       ata_sat_wq = create_workqueue("sat");
+       if (!ata_sat_wq) {
+               destroy_workqueue(ata_wq);
+               return -ENOMEM;
+       }
+
        printk(KERN_DEBUG "libata version " DRV_VERSION " loaded.\n");
        return 0;
 }
@@ -4537,6 +4544,7 @@ static int __init ata_init(void)
 static void __exit ata_exit(void)
 {
        destroy_workqueue(ata_wq);
+       destroy_workqueue(ata_sat_wq);
 }
 
 module_init(ata_init);
diff --git a/drivers/scsi/libata-scsi.c b/drivers/scsi/libata-scsi.c
--- a/drivers/scsi/libata-scsi.c
+++ b/drivers/scsi/libata-scsi.c
@@ -36,6 +36,7 @@
 #include <linux/kernel.h>
 #include <linux/blkdev.h>
 #include <linux/spinlock.h>
+#include <linux/workqueue.h>
 #include <scsi/scsi.h>
 #include "scsi.h"
 #include <scsi/scsi_host.h>
@@ -44,10 +45,26 @@
 
 #include "libata.h"
 
-typedef unsigned int (*ata_xlat_func_t)(struct ata_queued_cmd *qc, u8 
*scsicmd);
+enum {
+       ATA_SAT_SIMULATE,       /* scmd is to be simulated */
+       ATA_SAT_XLAT,           /* scmd -> single qc translation */
+       ATA_SAT_SEQUENCE,       /* scmd -> qc sequence translation */
+};
+
+struct ata_sat_info {
+       int mode;
+       union {
+               ata_sat_xlat_func_t xlat_func;
+               ata_sat_seq_func_t seq_func;
+       } f; /* No anonymous union yet */
+};
+
+struct workqueue_struct *ata_sat_wq;
+
 static struct ata_device *
 ata_scsi_find_dev(struct ata_port *ap, struct scsi_device *scsidev);
-
+static unsigned int ata_scsi_rbuf_get(struct scsi_cmnd *cmd, u8 **buf_out);
+static void ata_scsi_rbuf_put(struct scsi_cmnd *cmd, u8 *buf);
 
 /**
  *     ata_std_bios_param - generic bios head/sector/cylinder calculator used 
by sd.
@@ -738,7 +755,7 @@ static int ata_scsi_qc_complete(struct a
 static void ata_scsi_translate(struct ata_port *ap, struct ata_device *dev,
                              struct scsi_cmnd *cmd,
                              void (*done)(struct scsi_cmnd *),
-                             ata_xlat_func_t xlat_func)
+                             ata_sat_xlat_func_t xlat_func)
 {
        struct ata_queued_cmd *qc;
        u8 *scsicmd = cmd->cmnd;
@@ -785,6 +802,132 @@ err_out:
        DPRINTK("EXIT - badcmd\n");
 }
 
+static int ata_scsi_seq_exec_qc(struct ata_queued_cmd *qc)
+{
+       struct ata_port *ap = qc->ap;
+       DECLARE_COMPLETION(wait);
+       unsigned long flags;
+       int rc;
+
+       qc->waiting = &wait;
+
+       spin_lock_irqsave(&ap->host_set->lock, flags);
+       rc = ata_qc_issue(qc);
+       if (rc)
+               ata_qc_free(qc);
+       spin_unlock_irqrestore(&ap->host_set->lock, flags);
+
+       if (!rc)
+               wait_for_completion(&wait);
+       return rc;
+}
+
+static void ata_scsi_modeselect10_seq(struct ata_port *ap,
+                                     struct ata_device *dev,
+                                     struct scsi_cmnd *cmd,
+                                     void (*done)(struct scsi_cmnd *))
+{
+       u8 *rbuf;
+       unsigned buflen;
+       int okay = 0, wce = 0, dra = 0;
+       struct ata_queued_cmd *qc;
+
+       buflen = ata_scsi_rbuf_get(cmd, &rbuf);
+
+       if (buflen >= 21 && rbuf[8 + 0] == 0x08) {
+               wce = rbuf[8 + 2] & 0x04;
+               dra = rbuf[8 + 12] & 0x20;
+               okay = 1;
+       }
+
+       ata_scsi_rbuf_put(cmd, rbuf);
+
+       if (!okay)
+               goto err_out;
+
+       /* WCE first */
+       qc = ata_scsi_qc_new(ap, dev, cmd, done);
+       if (!qc)
+               return;
+
+       qc->tf.flags |= ATA_TFLAG_DEVICE | ATA_TFLAG_ISADDR;
+       qc->tf.protocol = ATA_PROT_NODATA;
+       qc->tf.command = ATA_CMD_SET_FEATURES;
+       qc->tf.feature = wce ? 0x02 : 0x82;
+
+       if (ata_scsi_seq_exec_qc(qc))
+               goto err_out;
+
+       /* DRA now */
+       qc = ata_scsi_qc_new(ap, dev, cmd, done);
+       if (!qc)
+               return;
+
+       qc->tf.flags |= ATA_TFLAG_DEVICE | ATA_TFLAG_ISADDR;
+       qc->tf.protocol = ATA_PROT_NODATA;
+       qc->tf.command = ATA_CMD_SET_FEATURES;
+       qc->tf.feature = !dra ? 0xaa : 0x55;
+
+       if (ata_scsi_seq_exec_qc(qc))
+               goto err_out;
+
+       cmd->result = SAM_STAT_GOOD;
+       done(cmd);
+       return;
+
+ err_out:
+       ata_scsi_badcmd(cmd, done, 0x20, 0x00);
+}
+
+void ata_sat_task(void *_data)
+{
+       struct ata_port *ap = _data;
+
+       ap->sat_seq_func(ap, ap->sat_dev, ap->sat_scsicmd, ap->sat_done);
+       smp_wmb();
+       ap->sat_scsicmd = NULL;
+}
+
+/**
+ *     ata_scsi_sequence - 
+ *     @ap: ATA port to which the command is addressed
+ *     @dev: ATA device to which the command is addressed
+ *     @cmd: SCSI command to execute
+ *     @done: SCSI command completion function
+ *     @seq_func: Actor which sequences @cmd
+ *
+ *     Our ->queuecommand() function has decided that the SCSI
+ *     command issued should be sequenced.  Note that only one
+ *     concurrent sequencing is allowed per port.
+ *
+ *     This function sets up an per dev sequence data and queues
+ *     dev->sat_task.
+ *
+ *     LOCKING:
+ *     spin_lock_irqsave(host_set lock)
+ */
+
+static void ata_scsi_sequence(struct ata_port *ap, struct ata_device *dev,
+                             struct scsi_cmnd *cmd,
+                             void (*done)(struct scsi_cmnd *),
+                             ata_sat_seq_func_t seq_func)
+{
+       if (ap->sat_scsicmd != NULL) {
+               cmd->result = (DID_OK << 16) | (QUEUE_FULL << 1);
+               done(cmd);
+               return;
+       }
+
+       ap->sat_scsicmd = cmd;
+       ap->sat_dev = dev;
+       ap->sat_seq_func = seq_func;
+       ap->sat_done = done;
+
+       queue_work(ata_sat_wq, &ap->sat_task);
+
+       return;
+}
+
 /**
  *     ata_scsi_rbuf_get - Map response buffer.
  *     @cmd: SCSI command containing buffer to be mapped.
@@ -1479,19 +1622,22 @@ ata_scsi_find_dev(struct ata_port *ap, s
 }
 
 /**
- *     ata_get_xlat_func - check if SCSI to ATA translation is possible
+ *     ata_get_sat_info - determine how a SCSI command should be handled
  *     @dev: ATA device
  *     @cmd: SCSI command opcode to consider
  *
  *     Look up the SCSI command given, and determine whether the
- *     SCSI command is to be translated or simulated.
+ *     SCSI command is to be simulated, translated or sequenced.
  *
  *     RETURNS:
  *     Pointer to translation function if possible, %NULL if not.
  */
 
-static inline ata_xlat_func_t ata_get_xlat_func(struct ata_device *dev, u8 cmd)
+static inline struct ata_sat_info ata_get_sat_info(struct ata_device *dev,
+                                                  u8 cmd)
 {
+       struct ata_sat_info si = { .mode = ATA_SAT_SIMULATE };
+
        switch (cmd) {
        case READ_6:
        case READ_10:
@@ -1500,21 +1646,35 @@ static inline ata_xlat_func_t ata_get_xl
        case WRITE_6:
        case WRITE_10:
        case WRITE_16:
-               return ata_scsi_rw_xlat;
+               si.mode = ATA_SAT_XLAT;
+               si.f.xlat_func = ata_scsi_rw_xlat;
+               break;
 
        case SYNCHRONIZE_CACHE:
-               if (ata_try_flush_cache(dev))
-                       return ata_scsi_flush_xlat;
+               if (ata_try_flush_cache(dev)) {
+                       si.mode = ATA_SAT_XLAT;
+                       si.f.xlat_func = ata_scsi_flush_xlat;
+               }
                break;
 
        case VERIFY:
        case VERIFY_16:
-               return ata_scsi_verify_xlat;
+               si.mode = ATA_SAT_XLAT;
+               si.f.xlat_func = ata_scsi_verify_xlat;
+               break;
+
        case START_STOP:
-               return ata_scsi_start_stop_xlat;
+               si.mode = ATA_SAT_XLAT;
+               si.f.xlat_func = ata_scsi_start_stop_xlat;
+               break;
+
+       case MODE_SELECT_10:
+               si.mode = ATA_SAT_SEQUENCE;
+               si.f.seq_func = ata_scsi_modeselect10_seq;
+               break;
        }
 
-       return NULL;
+       return si;
 }
 
 /**
@@ -1578,13 +1738,19 @@ int ata_scsi_queuecmd(struct scsi_cmnd *
        }
 
        if (dev->class == ATA_DEV_ATA) {
-               ata_xlat_func_t xlat_func = ata_get_xlat_func(dev,
-                                                             cmd->cmnd[0]);
+               struct ata_sat_info si = ata_get_sat_info(dev, cmd->cmnd[0]);
 
-               if (xlat_func)
-                       ata_scsi_translate(ap, dev, cmd, done, xlat_func);
-               else
+               switch (si.mode) {
+               case ATA_SAT_SIMULATE:
                        ata_scsi_simulate(dev->id, cmd, done);
+                       break;
+               case ATA_SAT_XLAT:
+                       ata_scsi_translate(ap, dev, cmd, done, si.f.xlat_func);
+                       break;
+               case ATA_SAT_SEQUENCE:
+                       ata_scsi_sequence(ap, dev, cmd, done, si.f.seq_func);
+                       break;
+               }
        } else
                ata_scsi_translate(ap, dev, cmd, done, atapi_xlat);
 
diff --git a/drivers/scsi/libata.h b/drivers/scsi/libata.h
--- a/drivers/scsi/libata.h
+++ b/drivers/scsi/libata.h
@@ -50,6 +50,10 @@ extern void swap_buf_le16(u16 *buf, unsi
 
 
 /* libata-scsi.c */
+extern struct workqueue_struct *ata_sat_wq;
+
+extern void ata_sat_task(void *_data);
+
 extern void ata_to_sense_error(struct ata_queued_cmd *qc, u8 drv_stat);
 extern int ata_scsi_error(struct Scsi_Host *host);
 extern unsigned int ata_scsiop_inq_std(struct ata_scsi_args *args, u8 *rbuf,
diff --git a/include/linux/libata.h b/include/linux/libata.h
--- a/include/linux/libata.h
+++ b/include/linux/libata.h
@@ -172,10 +172,16 @@ enum pio_task_states {
 struct scsi_device;
 struct ata_port_operations;
 struct ata_port;
+struct ata_device;
 struct ata_queued_cmd;
 
 /* typedefs */
 typedef int (*ata_qc_cb_t) (struct ata_queued_cmd *qc, u8 drv_stat);
+typedef unsigned int (*ata_sat_xlat_func_t)(struct ata_queued_cmd *qc,
+                                           u8 *scsicmd);
+typedef void (*ata_sat_seq_func_t)(struct ata_port *ap,struct ata_device *dev,
+                                  struct scsi_cmnd *cmd,
+                                  void (*done)(struct scsi_cmnd *));
 
 struct ata_ioports {
        unsigned long           cmd_addr;
@@ -323,6 +329,12 @@ struct ata_port {
        unsigned int            pio_task_state;
        unsigned long           pio_task_timeout;
 
+       struct work_struct      sat_task;
+       struct scsi_cmnd        *sat_scsicmd;
+       struct ata_device       *sat_dev;
+       ata_sat_seq_func_t      sat_seq_func;
+       void (*sat_done)(struct scsi_cmnd *);
+
        void                    *private_data;
 };
 
-
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to