diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c
index 4d46831..2037da3 100644
--- a/hw/scsi-bus.c
+++ b/hw/scsi-bus.c
@@ -811,33 +811,80 @@ retry:
     abort();
 }
 
-/* Extract bus and target from the given LUN and use it to identify a
-   SCSIDevice from a SCSIBus.  Right now, only 1 target per bus is
-   supported.  In the future a SCSIDevice could host its own SCSIBus,
-   in an alternation of devices that select a bus (target ports) and
-   devices that select a target (initiator ports).  */
-SCSIDevice *scsi_decode_lun(SCSIBus *sbus, uint64_t sam_lun, int *lun)
+/* Reusable implementation of the decode_lun entry in SCSIBusOps.  */
+SCSIDevice *scsi_decode_bus_from_lun(SCSIBus *sbus, uint64_t sam_lun,
+                                     uint64_t *next_lun)
 {
-    int bus, target, decoded_lun;
-    uint64_t next_lun;
+    int bus, target;
+    uint64_t my_next_lun;
+    SCSIDevice *sdev;
 
-    if (!scsi_decode_level(sam_lun, &bus, &target, &next_lun)) {
+    if (!scsi_decode_level(sam_lun, &bus, &target, &my_next_lun)) {
          /* Unsupported LUN format.  */
          return NULL;
     }
-    if (bus >= sbus->ndev || (bus == 0 && target > 0)) {
+    if (bus >= sbus->ndev) {
         /* Out of range.  */
         return NULL;
     }
-    if (target != 0) {
-        /* Only one target for now.  */
+
+    sdev = sbus->devs[bus];
+    if (!sdev) {
+	return NULL;
+    } else if (bus == 0 || !sdev->children) {
+        return target ? NULL : sdev;
+    } else {
+        /* Next we'll decode the target, so pass down the same LUN we got.  */
+        return sdev->children->ops.decode_lun(sbus, sam_lun, next_lun);
+    }
+}
+
+SCSIDevice *scsi_decode_target_from_lun(SCSIBus *sbus, uint64_t sam_lun,
+                                        uint64_t *next_lun)
+{
+    int bus, target;
+    SCSIDevice *sdev;
+
+    if (!scsi_decode_level(sam_lun, &bus, &target, next_lun)) {
+         /* Unsupported LUN format.  */
+         return NULL;
+    }
+    if (target >= sbus->ndev) {
+        /* Out of range.  */
         return NULL;
     }
 
+    sdev = sbus->devs[target];
+    if (!sdev || !sdev->children || (*next_lun >> 56) == ADDR_WELL_KNOWN_LUN) {
+        return sdev;
+    } else {
+        return sdev->children->ops.decode_lun(sbus, *next_lun, next_lun);
+    }
+}
+
+/* Extract bus and target from the given LUN and use it to identify a
+   SCSIDevice from a SCSIBus.  Right now, only 1 target per bus is
+   supported.  In the future a SCSIDevice could host its own SCSIBus,
+   in an alternation of devices that select a bus (target ports) and
+   devices that select a target (initiator ports).  */
+SCSIDevice *scsi_decode_lun(SCSIBus *sbus, uint64_t sam_lun,
+                            uint8_t *cdb, int *lun)
+{
+    int decoded_lun;
+    uint64_t next_lun;
+    SCSIDevice *sdev;
+
+    sdev = sbus->ops.decode_lun(sbus, sam_lun, &next_lun);
+    if (!sdev) {
+        return NULL;
+    }
     decoded_lun = scsi_get_lun(next_lun);
-    if (decoded_lun != LUN_INVALID) {
-        *lun = decoded_lun;
-        return sbus->devs[bus];
+    if (decoded_lun == LUN_INVALID) {
+        return NULL;
+    }
+    if ((decoded_lun & ~LUN_WLUN_MASK) == LUN_WLUN_BASE) {
+        return sdev;
     }
-    return NULL;
+    *lun = decoded_lun;
+    return scsi_find_lun(sdev, decoded_lun, cdb);
 }
diff --git a/hw/scsi.h b/hw/scsi.h
index 438dd89..c4cca0b 100644
--- a/hw/scsi.h
+++ b/hw/scsi.h
@@ -88,6 +88,8 @@ struct SCSIBusOps {
     void (*transfer_data)(SCSIRequest *req, uint32_t arg);
     void (*complete)(SCSIRequest *req, uint32_t arg);
     void (*cancel)(SCSIRequest *req);
+    SCSIDevice *(*decode_lun)(SCSIBus *sbus, uint64_t sam_lun,
+                              uint64_t *next_lun);
 };
 
 struct SCSIBus {
@@ -145,7 +147,12 @@ extern const struct SCSISense sense_code_LUN_FAILURE;
 int scsi_build_sense(SCSISense sense, uint8_t *buf, int len, int fixed);
 int scsi_sense_valid(SCSISense sense);
 
-SCSIDevice *scsi_decode_lun(SCSIBus *sbus, uint64_t sam_lun, int *lun);
+SCSIDevice *scsi_decode_lun(SCSIBus *sbus, uint64_t sam_lun, uint8_t *cdb,
+                            int *lun);
+SCSIDevice *scsi_decode_bus_from_lun(SCSIBus *sbus, uint64_t sam_lun,
+                                     uint64_t *next_lun);
+SCSIDevice *scsi_decode_target_from_lun(SCSIBus *sbus, uint64_t sam_lun,
+                                        uint64_t *next_lun);
 SCSIDevice *scsi_find_lun(SCSIDevice *sdev, int lun, uint8_t *cdb);
 
 SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t lun);
diff --git a/hw/spapr_vscsi.c b/hw/spapr_vscsi.c
index ee88ff6..d46ab30 100644
--- a/hw/spapr_vscsi.c
+++ b/hw/spapr_vscsi.c
@@ -640,7 +640,8 @@ static int vscsi_queue_cmd(VSCSIState *s, vscsi_req *req)
     SCSIDevice *sdev;
     int n, lun;
 
-    sdev = scsi_decode_lun(&s->bus, be64_to_cpu(srp->cmd.lun), &lun);
+    sdev = scsi_decode_lun(&s->bus, be64_to_cpu(srp->cmd.lun),
+			   srp->cmd.cdb, &lun);
     if (!sdev) {
         if (srp->cmd.cdb[0] == INQUIRY) {
             vscsi_inquiry_no_target(s, req);
@@ -918,7 +919,8 @@ static int vscsi_do_crq(struct VIOsPAPRDevice *dev, uint8_t *crq_data)
 static struct SCSIBusOps vscsi_scsi_ops = {
     .transfer_data = vscsi_transfer_data,
     .complete = vscsi_command_complete,
-    .cancel = vscsi_request_cancelled
+    .cancel = vscsi_request_cancelled,
+    .decode_lun = scsi_decode_bus_from_lun
 };
 
 static int spapr_vscsi_init(VIOsPAPRDevice *dev)
