Remove most of the zone request specific code from sd_done() and
move it to sd_zbc_complete(), which is called at the end of sd_done()
so that good_bytes is set for REQ_OP_ZONE_REPORT and REQ_OP_ZONE_RESET.
In sd_zbc_complete(), enhance checks on the value of good_bytes for
report zones to avoid eventual problems with bogus hardware.

Signed-off-by: Damien Le Moal <damien.lem...@wdc.com>
---
 drivers/scsi/sd.c     |  36 ++++++------------
 drivers/scsi/sd.h     |  11 +++---
 drivers/scsi/sd_zbc.c | 101 +++++++++++++++++++++++++++++++++-----------------
 3 files changed, 84 insertions(+), 64 deletions(-)

diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index c7839f6..1bcd80a 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -1786,15 +1786,11 @@ static int sd_done(struct scsi_cmnd *SCpnt)
        struct scsi_sense_hdr sshdr;
        struct scsi_disk *sdkp = scsi_disk(SCpnt->request->rq_disk);
        struct request *req = SCpnt->request;
-       int sense_valid = 0;
-       int sense_deferred = 0;
-       unsigned char op = SCpnt->cmnd[0];
-       unsigned char unmap = SCpnt->cmnd[1] & 8;
+       bool sense_valid = false;
 
        switch (req_op(req)) {
        case REQ_OP_DISCARD:
        case REQ_OP_WRITE_SAME:
-       case REQ_OP_ZONE_RESET:
                if (!result) {
                        good_bytes = blk_rq_bytes(req);
                        scsi_set_resid(SCpnt, 0);
@@ -1803,27 +1799,15 @@ static int sd_done(struct scsi_cmnd *SCpnt)
                        scsi_set_resid(SCpnt, blk_rq_bytes(req));
                }
                break;
-       case REQ_OP_ZONE_REPORT:
-               if (!result) {
-                       good_bytes = scsi_bufflen(SCpnt)
-                               - scsi_get_resid(SCpnt);
-                       scsi_set_resid(SCpnt, 0);
-               } else {
-                       good_bytes = 0;
-                       scsi_set_resid(SCpnt, blk_rq_bytes(req));
-               }
-               break;
        }
 
        if (result) {
-               sense_valid = scsi_command_normalize_sense(SCpnt, &sshdr);
-               if (sense_valid)
-                       sense_deferred = scsi_sense_is_deferred(&sshdr);
+               sense_valid = scsi_command_normalize_sense(SCpnt, &sshdr) &&
+                       !scsi_sense_is_deferred(&sshdr);
        }
        sdkp->medium_access_timed_out = 0;
 
-       if (driver_byte(result) != DRIVER_SENSE &&
-           (!sense_valid || sense_deferred))
+       if (driver_byte(result) != DRIVER_SENSE && !sense_valid)
                goto out;
 
        switch (sshdr.sense_key) {
@@ -1847,10 +1831,14 @@ static int sd_done(struct scsi_cmnd *SCpnt)
                        good_bytes = sd_completed_bytes(SCpnt);
                break;
        case ILLEGAL_REQUEST:
-               if (sshdr.asc == 0x10)  /* DIX: Host detected corruption */
+               if (sshdr.asc == 0x10) {
+                       /* DIX: Host detected corruption */
                        good_bytes = sd_completed_bytes(SCpnt);
-               /* INVALID COMMAND OPCODE or INVALID FIELD IN CDB */
-               if (sshdr.asc == 0x20 || sshdr.asc == 0x24) {
+               } else if (sshdr.asc == 0x20 || sshdr.asc == 0x24) {
+                       unsigned char op = SCpnt->cmnd[0];
+                       unsigned char unmap = SCpnt->cmnd[1] & 8;
+
+                       /* INVALID COMMAND OPCODE or INVALID FIELD IN CDB */
                        switch (op) {
                        case UNMAP:
                                sd_config_discard(sdkp, SD_LBP_DISABLE);
@@ -1876,7 +1864,7 @@ static int sd_done(struct scsi_cmnd *SCpnt)
 
  out:
        if (sd_is_zoned(sdkp))
-               sd_zbc_complete(SCpnt, good_bytes, &sshdr);
+               good_bytes = sd_zbc_complete(SCpnt, good_bytes, &sshdr);
 
        SCSI_LOG_HLCOMPLETE(1, scmd_printk(KERN_INFO, SCpnt,
                                           "sd_done: completed %d of %d 
bytes\n",
diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h
index 4dac35e..0fd4886 100644
--- a/drivers/scsi/sd.h
+++ b/drivers/scsi/sd.h
@@ -270,8 +270,9 @@ extern int sd_zbc_setup_write_cmnd(struct scsi_cmnd *cmd);
 extern void sd_zbc_cancel_write_cmnd(struct scsi_cmnd *cmd);
 extern int sd_zbc_setup_report_cmnd(struct scsi_cmnd *cmd);
 extern int sd_zbc_setup_reset_cmnd(struct scsi_cmnd *cmd);
-extern void sd_zbc_complete(struct scsi_cmnd *cmd, unsigned int good_bytes,
-                           struct scsi_sense_hdr *sshdr);
+extern unsigned int sd_zbc_complete(struct scsi_cmnd *cmd,
+                                   unsigned int good_bytes,
+                                   struct scsi_sense_hdr *sshdr);
 
 #else /* CONFIG_BLK_DEV_ZONED */
 
@@ -303,9 +304,9 @@ static inline int sd_zbc_setup_reset_cmnd(struct scsi_cmnd 
*cmd)
        return BLKPREP_INVALID;
 }
 
-static inline void sd_zbc_complete(struct scsi_cmnd *cmd,
-                                  unsigned int good_bytes,
-                                  struct scsi_sense_hdr *sshdr) {}
+static inline unsigned int sd_zbc_complete(struct scsi_cmnd *cmd,
+                                          unsigned int good_bytes,
+                                          struct scsi_sense_hdr *sshdr) {}
 
 #endif /* CONFIG_BLK_DEV_ZONED */
 
diff --git a/drivers/scsi/sd_zbc.c b/drivers/scsi/sd_zbc.c
index 92620c8..789c970 100644
--- a/drivers/scsi/sd_zbc.c
+++ b/drivers/scsi/sd_zbc.c
@@ -177,8 +177,18 @@ static void sd_zbc_report_zones_complete(struct scsi_cmnd 
*scmd,
        unsigned long flags;
        u8 *buf;
 
-       if (good_bytes < 64)
-               return;
+       /*
+        * Make sure good_bytes make sense and is aligned on 64 B.
+        * If it is less than 64B, force the nr_zones field of the
+        * report header to become 0 by setting good_bytes to 64.
+        */
+       if (!good_bytes || good_bytes & 0x3f) {
+               sdev_printk(KERN_INFO, scmd->device,
+                       "Invalid report zone result (xfer=%u, resid=%u)\n",
+                       scsi_bufflen(scmd), scsi_get_resid(scmd));
+               good_bytes = max_t(unsigned int,
+                                  round_down(good_bytes, 64), 64);
+       }
 
        memset(&hdr, 0, sizeof(struct blk_zone_report_hdr));
 
@@ -320,55 +330,76 @@ void sd_zbc_cancel_write_cmnd(struct scsi_cmnd *cmd)
        sd_zbc_unlock_zone(cmd->request);
 }
 
-void sd_zbc_complete(struct scsi_cmnd *cmd,
-                    unsigned int good_bytes,
-                    struct scsi_sense_hdr *sshdr)
+unsigned int sd_zbc_complete(struct scsi_cmnd *scmd,
+                            unsigned int good_bytes,
+                            struct scsi_sense_hdr *sshdr)
 {
-       int result = cmd->result;
-       struct request *rq = cmd->request;
+       int result = scmd->result;
+       struct request *req = scmd->request;
+
+       switch (req_op(req)) {
+
+       case REQ_OP_ZONE_REPORT:
+
+               if (result) {
+                       /* Failed */
+                       good_bytes = 0;
+                       scsi_set_resid(scmd, blk_rq_bytes(req));
+                       break;
+               }
+
+               /* Check good bytes */
+               if (good_bytes >= scsi_get_resid(scmd))
+                       good_bytes -= scsi_get_resid(scmd);
+               sd_zbc_report_zones_complete(scmd, good_bytes);
+               scsi_set_resid(scmd, 0);
+
+               break;
+
+       case REQ_OP_ZONE_RESET:
+
+               if (!result) {
+                       good_bytes = blk_rq_bytes(req);
+                       scsi_set_resid(scmd, 0);
+               } else {
+                       /* Failed */
+                       good_bytes = 0;
+                       scsi_set_resid(scmd, blk_rq_bytes(req));
+                       if (sshdr->sense_key == ILLEGAL_REQUEST &&
+                           sshdr->asc == 0x24)
+                               /*
+                                * ILLEGAL REQUEST / INVALID FIELD IN CDB
+                                * For a zone reset, this means that a reset
+                                * of a conventional zone was attempted.
+                                * Nothing to worry about, so be quiet about
+                                * the error.
+                                */
+                               req->rq_flags |= RQF_QUIET;
+               }
+
+               /* Fallthru (for unlocking the zone) */
 
-       switch (req_op(rq)) {
        case REQ_OP_WRITE:
        case REQ_OP_WRITE_SAME:
-       case REQ_OP_ZONE_RESET:
 
                /* Unlock the zone */
-               sd_zbc_unlock_zone(rq);
+               sd_zbc_unlock_zone(req);
 
-               if (!result ||
-                   sshdr->sense_key != ILLEGAL_REQUEST)
-                       break;
-
-               switch (sshdr->asc) {
-               case 0x24:
-                       /*
-                        * INVALID FIELD IN CDB error: For a zone reset,
-                        * this means that a reset of a conventional
-                        * zone was attempted. Nothing to worry about in
-                        * this case, so be quiet about the error.
-                        */
-                       if (req_op(rq) == REQ_OP_ZONE_RESET)
-                               rq->rq_flags |= RQF_QUIET;
-                       break;
-               case 0x21:
+               if (!result &&
+                   sshdr->sense_key == ILLEGAL_REQUEST &&
+                   sshdr->asc == 0x21)
                        /*
                         * INVALID ADDRESS FOR WRITE error: It is unlikely that
                         * retrying write requests failed with any kind of
                         * alignement error will result in success. So don't.
                         */
-                       cmd->allowed = 0;
-                       break;
-               }
-
-               break;
-
-       case REQ_OP_ZONE_REPORT:
+                       scmd->allowed = 0;
 
-               if (!result)
-                       sd_zbc_report_zones_complete(cmd, good_bytes);
                break;
 
        }
+
+       return good_bytes;
 }
 
 /**
-- 
2.9.3

Reply via email to