@@ -32,6 +32,7 @@
#include "migration/vmstate.h"
#include "hw/scsi/emulation.h"
#include "scsi/constants.h"
+#include "scsi/utils.h"
#include "sysemu/block-backend.h"
#include "sysemu/blockdev.h"
#include "hw/block/block.h"
@@ -42,6 +43,7 @@
#include "qemu/cutils.h"
#include "trace.h"
#include "qom/object.h"
+#include "block/block_int.h"
#ifdef __linux
#include <scsi/sg.h>
@@ -1474,6 +1476,362 @@ static void scsi_disk_emulate_read_data(SCSIRequest *req)
scsi_req_complete(&r->req, GOOD);
}
+typedef struct SCSIPrReadKeys {
+ uint32_t generation;
+ uint32_t num_keys;
+ uint64_t *keys;
+ SCSIDiskReq *req;
+} SCSIPrReadKeys;
+
+typedef struct SCSIPrReadReservation {
+ uint32_t generation;
+ uint64_t key;
+ BlockPrType type;
+ SCSIDiskReq *req;
+} SCSIPrReadReservation;
+
+static void scsi_pr_read_keys_complete(void *opaque, int ret)
+{
+ int num_keys;
+ uint8_t *buf;
+ SCSIPrReadKeys *blk_keys = (SCSIPrReadKeys *)opaque;
+ SCSIDiskReq *r = blk_keys->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert(blk_get_aio_context(s->qdev.conf.blk) ==
+ qemu_get_current_aio_context());
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ if (scsi_disk_req_check_error(r, ret, true)) {
+ goto done;
+ }
+
+ buf = scsi_req_get_buf(&r->req);
+ num_keys = MIN(blk_keys->num_keys, ret);
+ blk_keys->generation = cpu_to_be32(blk_keys->generation);
+ memcpy(&buf[0], &blk_keys->generation, 4);
+ for (int i = 0; i < num_keys; i++) {
+ blk_keys->keys[i] = cpu_to_be64(blk_keys->keys[i]);
+ memcpy(&buf[8 + i * 8], &blk_keys->keys[i], 8);
+ }
+ num_keys = cpu_to_be32(num_keys * 8);
+ memcpy(&buf[4], &num_keys, 4);
+
+ scsi_req_data(&r->req, r->buflen);
+done:
+ scsi_req_unref(&r->req);
+ g_free(blk_keys->keys);
+ g_free(blk_keys);
+}
+
+static void scsi_disk_emulate_pr_read_keys(SCSIRequest *req)
+{
+ SCSIPrReadKeys *blk_keys;
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ int buflen = MIN(r->req.cmd.xfer, r->buflen);
+ int num_keys = (buflen - sizeof(uint32_t) * 2) / sizeof(uint64_t);
+
+ if (num_keys <= 0) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN));
+ return;
+ }
+
+ blk_keys = g_new0(SCSIPrReadKeys, 1);
+ blk_keys->generation = 0;
+ /* num_keys is the maximum number of keys that can be transmitted */
+ blk_keys->num_keys = num_keys;
+ blk_keys->keys = g_malloc(sizeof(uint64_t) * num_keys);
+ blk_keys->req = r;
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ r->req.aiocb = blk_aio_pr_read_keys(s->qdev.conf.blk, &blk_keys->generation,
+ blk_keys->num_keys, blk_keys->keys,
+ scsi_pr_read_keys_complete, blk_keys);
+ return;
+}
+
+static void scsi_pr_read_reservation_complete(void *opaque, int ret)
+{
+ uint8_t *buf;
+ uint32_t additional_len = 0;
+ SCSIPrReadReservation *blk_rsv = (SCSIPrReadReservation *)opaque;
+ SCSIDiskReq *r = blk_rsv->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert(blk_get_aio_context(s->qdev.conf.blk) ==
+ qemu_get_current_aio_context());
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ if (scsi_disk_req_check_error(r, ret, true)) {
+ goto done;
+ }
+
+ buf = scsi_req_get_buf(&r->req);
+ blk_rsv->generation = cpu_to_be32(blk_rsv->generation);
+ memcpy(&buf[0], &blk_rsv->generation, 4);
+ if (ret) {
+ additional_len = cpu_to_be32(16);
+ blk_rsv->key = cpu_to_be64(blk_rsv->key);
+ memcpy(&buf[8], &blk_rsv->key, 8);
+ buf[21] = block_pr_type_to_scsi(blk_rsv->type) & 0xf;
+ } else {
+ additional_len = cpu_to_be32(0);
+ }
+
+ memcpy(&buf[4], &additional_len, 4);
+ scsi_req_data(&r->req, r->buflen);
+
+done:
+ scsi_req_unref(&r->req);
+ g_free(blk_rsv);
+}
+
+static void scsi_disk_emulate_pr_read_reservation(SCSIRequest *req)
+{
+ SCSIPrReadReservation *blk_rsv;
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ int buflen = MIN(r->req.cmd.xfer, r->buflen);
+ int count = sizeof(uint32_t) * 2 + sizeof(uint64_t) +
+ sizeof(uint32_t) + sizeof(uint8_t) * 2;
+
+ if (buflen < count) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN));
+ return;
+ }
+
+ blk_rsv = g_malloc(sizeof(*blk_rsv));
+ blk_rsv->generation = 0;
+ blk_rsv->key = 0;
+ blk_rsv->type = 0;
+ blk_rsv->req = r;
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ r->req.aiocb = blk_aio_pr_read_reservation(s->qdev.conf.blk,
+ &blk_rsv->generation, &blk_rsv->key, &blk_rsv->type,
+ scsi_pr_read_reservation_complete, blk_rsv);
+ return;
+}
+
+static void scsi_disk_emulate_pr_report_capabilities(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ BlockDriverState *bs = blk_bs(s->qdev.conf.blk);
+ uint8_t blk_pr_cap = bs->bl.pr_cap;
+ uint8_t *buf = scsi_req_get_buf(req);
+ uint16_t pr_cap;
+ /*
+ * according to response of report_capabilities
+ * command of chapter 6.13 of spc4
+ */
+ int len = sizeof(uint16_t) * 2 + sizeof(uint8_t) * 4;
+
+ if (len > r->buflen) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN));
+ return;
+ }
+
+ len = cpu_to_be16(len);
+ memcpy(&buf[0], &len, 2);
+ /* enable PTPL_C */
+ buf[2] = 1;
+ buf[3] = (blk_pr_cap & BLK_PR_CAP_PTPL) ? 1 : 0;
+ /* enable TMV */
+ buf[3] |= 1 << 7;
+
+ /* enable persistent reservation type */
+ pr_cap = block_pr_cap_to_scsi(blk_pr_cap);
+ buf[4] = pr_cap >> 8 & 0xff;
+ buf[5] = pr_cap & 0xff;
+
+ scsi_req_data(&r->req, len);
+ return;
+}
+
+static void scsi_aio_pr_complete(void *opaque, int ret)
+{
+ SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ /* The request must only run in the BlockBackend's AioContext */
+ assert(blk_get_aio_context(s->qdev.conf.blk) ==
+ qemu_get_current_aio_context());
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ if (scsi_disk_req_check_error(r, ret, true)) {
+ goto done;
+ }
+
+ scsi_req_complete(&r->req, GOOD);
+
+done:
+ scsi_req_unref(&r->req);
+}
+
+static void scsi_disk_emulate_pr_register(SCSIDiskReq *r, uint64_t r_key,
+ uint64_t sa_key, SCSIPrType type,
+ uint8_t aptpl, bool ignore_key)
+{
+ SCSIRequest *req = &r->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ r->req.aiocb = blk_aio_pr_register(s->qdev.conf.blk, r_key, sa_key,
+ scsi_pr_type_to_block(type),
+ aptpl ? true : false,
+ ignore_key, scsi_aio_pr_complete, r);
+
+ return;
+}
+
+static void scsi_disk_emulate_pr_reserve(SCSIDiskReq *r, uint64_t r_key,
+ SCSIPrType type)
+{
+ SCSIRequest *req = &r->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ r->req.aiocb = blk_aio_pr_reserve(s->qdev.conf.blk, r_key,
+ scsi_pr_type_to_block(type),
+ scsi_aio_pr_complete, r);
+
+ return;
+}
+
+static void scsi_disk_emulate_pr_release(SCSIDiskReq *r, uint64_t r_key,
+ SCSIPrType type)
+{
+ SCSIRequest *req = &r->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ r->req.aiocb = blk_aio_pr_release(s->qdev.conf.blk, r_key,
+ scsi_pr_type_to_block(type),
+ scsi_aio_pr_complete, r);
+
+ return;
+}
+
+static void scsi_disk_emulate_pr_clear(SCSIDiskReq *r, uint64_t r_key)
+{
+ SCSIRequest *req = &r->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ r->req.aiocb = blk_aio_pr_clear(s->qdev.conf.blk, r_key,
+ scsi_aio_pr_complete, r);
+
+ return;
+}
+
+static void scsi_disk_emulate_pr_preempt(SCSIDiskReq *r, uint64_t r_key,
+ uint64_t sa_key, SCSIPrType type,
+ bool abort)
+{
+ SCSIRequest *req = &r->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ r->req.aiocb = blk_aio_pr_preempt(s->qdev.conf.blk, r_key, sa_key,
+ scsi_pr_type_to_block(type), abort,
+ scsi_aio_pr_complete, r);
+
+ return;
+}
+
+static void scsi_disk_emulate_pr_in(SCSIRequest *req)
+{
+ SCSIPrInAction action = req->cmd.buf[1] & 0x1f;
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+
+ switch (action) {
+ case SCSI_PR_IN_READ_KEYS:
+ scsi_disk_emulate_pr_read_keys(req);
+ break;
+ case SCSI_PR_IN_READ_RESERVATION:
+ scsi_disk_emulate_pr_read_reservation(req);
+ break;
+ case SCSI_PR_IN_REPORT_CAPABILITIES:
+ scsi_disk_emulate_pr_report_capabilities(req);
+ break;
+ default:
+ scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE));
+ return;
+ }
+
+ return;
+}
+
+static void scsi_disk_emulate_pr_out(SCSIDiskReq *r, uint8_t *inbuf)
+{
+ uint8_t aptpl;
+ uint64_t r_key, sa_key;
+ SCSIPrOutAction action;
+ SCSIPrScope scope;
+ SCSIPrType type;
+ SCSIRequest *req = &r->req;
+
+ memcpy(&r_key, &inbuf[0], 8);
+ r_key = be64_to_cpu(r_key);
+ memcpy(&sa_key, &inbuf[8], 8);
+ sa_key = be64_to_cpu(sa_key);
+ memcpy(&aptpl, &inbuf[20], 1);
+ aptpl = aptpl & 0x01;
+ action = req->cmd.buf[1] & 0x1f;
+ scope = (req->cmd.buf[2] >> 4) & 0x0f;
+ type = req->cmd.buf[2] & 0x0f;
+
+ if (scope != SCSI_PR_LU_SCOPE) {
+ scsi_req_complete(&r->req, TASK_ABORTED);
+ return;
+ }
+
+ switch (action) {
+ case SCSI_PR_OUT_REGISTER:
+ scsi_disk_emulate_pr_register(r, r_key, sa_key, type,
+ aptpl, false);
+ break;
+ case SCSI_PR_OUT_REG_AND_IGNORE_KEY:
+ scsi_disk_emulate_pr_register(r, r_key, sa_key, type, aptpl, true);
+ break;
+ case SCSI_PR_OUT_RESERVE:
+ scsi_disk_emulate_pr_reserve(r, r_key, type);
+ break;
+ case SCSI_PR_OUT_RELEASE:
+ scsi_disk_emulate_pr_release(r, r_key, type);
+ break;
+ case SCSI_PR_OUT_CLEAR:
+ scsi_disk_emulate_pr_clear(r, r_key);
+ break;
+ case SCSI_PR_OUT_PREEMPT:
+ scsi_disk_emulate_pr_preempt(r, r_key, sa_key, type, false);
+ break;
+ case SCSI_PR_OUT_PREEMPT_AND_ABORT:
+ scsi_disk_emulate_pr_preempt(r, r_key, sa_key, type, true);
+ break;
+ default:
+ scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE));
+ return;
+ }
+
+ return;
+}
+
static int scsi_disk_check_mode_select(SCSIDiskState *s, int page,
uint8_t *inbuf, int inlen)
{
@@ -1957,6 +2315,9 @@ static void scsi_disk_emulate_write_data(SCSIRequest *req)
scsi_req_complete(&r->req, GOOD);
break;
+ case PERSISTENT_RESERVE_OUT:
+ scsi_disk_emulate_pr_out(r, r->iov.iov_base);
+ break;
default:
abort();
}
@@ -2213,6 +2574,11 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf)
case FORMAT_UNIT:
trace_scsi_disk_emulate_command_FORMAT_UNIT(r->req.cmd.xfer);
break;
+ case PERSISTENT_RESERVE_OUT:
+ break;
+ case PERSISTENT_RESERVE_IN:
+ scsi_disk_emulate_pr_in(req);
+ return 0;
default:
trace_scsi_disk_emulate_command_UNKNOWN(buf[0],
scsi_command_name(buf[0]));
@@ -2632,6 +2998,8 @@ static const SCSIReqOps *const scsi_disk_reqops_dispatch[256] = {
[VERIFY_12] = &scsi_disk_emulate_reqops,
[VERIFY_16] = &scsi_disk_emulate_reqops,
[FORMAT_UNIT] = &scsi_disk_emulate_reqops,
+ [PERSISTENT_RESERVE_IN] = &scsi_disk_emulate_reqops,
+ [PERSISTENT_RESERVE_OUT] = &scsi_disk_emulate_reqops,
[READ_6] = &scsi_disk_dma_reqops,
[READ_10] = &scsi_disk_dma_reqops,