aboutsummaryrefslogtreecommitdiff
path: root/sys/cam/ctl
diff options
context:
space:
mode:
Diffstat (limited to 'sys/cam/ctl')
-rw-r--r--sys/cam/ctl/ctl.c337
-rw-r--r--sys/cam/ctl/ctl_backend.h5
-rw-r--r--sys/cam/ctl/ctl_backend_block.c317
-rw-r--r--sys/cam/ctl/ctl_backend_ramdisk.c31
-rw-r--r--sys/cam/ctl/ctl_cmd_table.c13
-rw-r--r--sys/cam/ctl/ctl_io.h12
-rw-r--r--sys/cam/ctl/ctl_private.h2
7 files changed, 688 insertions, 29 deletions
diff --git a/sys/cam/ctl/ctl.c b/sys/cam/ctl/ctl.c
index ad96f5dec60e..b0a67d6642f3 100644
--- a/sys/cam/ctl/ctl.c
+++ b/sys/cam/ctl/ctl.c
@@ -331,9 +331,10 @@ SYSCTL_INT(_kern_cam_ctl, OID_AUTO, verbose, CTLFLAG_RWTUN,
&verbose, 0, "Show SCSI errors returned to initiator");
/*
- * Serial number (0x80), device id (0x83), and supported pages (0x00)
+ * Serial number (0x80), device id (0x83), supported pages (0x00),
+ * Block limits (0xB0) and Logical Block Provisioning (0xB2)
*/
-#define SCSI_EVPD_NUM_SUPPORTED_PAGES 3
+#define SCSI_EVPD_NUM_SUPPORTED_PAGES 5
static void ctl_isc_event_handler(ctl_ha_channel chanel, ctl_ha_event event,
int param);
@@ -391,6 +392,9 @@ static void ctl_hndl_per_res_out_on_other_sc(union ctl_ha_msg *msg);
static int ctl_inquiry_evpd_supported(struct ctl_scsiio *ctsio, int alloc_len);
static int ctl_inquiry_evpd_serial(struct ctl_scsiio *ctsio, int alloc_len);
static int ctl_inquiry_evpd_devid(struct ctl_scsiio *ctsio, int alloc_len);
+static int ctl_inquiry_evpd_block_limits(struct ctl_scsiio *ctsio,
+ int alloc_len);
+static int ctl_inquiry_evpd_lbp(struct ctl_scsiio *ctsio, int alloc_len);
static int ctl_inquiry_evpd(struct ctl_scsiio *ctsio);
static int ctl_inquiry_std(struct ctl_scsiio *ctsio);
static int ctl_get_lba_len(union ctl_io *io, uint64_t *lba, uint32_t *len);
@@ -5787,6 +5791,195 @@ ctl_write_buffer(struct ctl_scsiio *ctsio)
return (CTL_RETVAL_COMPLETE);
}
+int
+ctl_write_same(struct ctl_scsiio *ctsio)
+{
+ struct ctl_lun *lun;
+ struct ctl_lba_len_flags lbalen;
+ uint64_t lba;
+ uint32_t num_blocks;
+ int len, retval;
+ uint8_t byte2;
+
+ retval = CTL_RETVAL_COMPLETE;
+
+ CTL_DEBUG_PRINT(("ctl_write_same\n"));
+
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+
+ switch (ctsio->cdb[0]) {
+ case WRITE_SAME_10: {
+ struct scsi_write_same_10 *cdb;
+
+ cdb = (struct scsi_write_same_10 *)ctsio->cdb;
+
+ lba = scsi_4btoul(cdb->addr);
+ num_blocks = scsi_2btoul(cdb->length);
+ byte2 = cdb->byte2;
+ break;
+ }
+ case WRITE_SAME_16: {
+ struct scsi_write_same_16 *cdb;
+
+ cdb = (struct scsi_write_same_16 *)ctsio->cdb;
+
+ lba = scsi_8btou64(cdb->addr);
+ num_blocks = scsi_4btoul(cdb->length);
+ byte2 = cdb->byte2;
+ break;
+ }
+ default:
+ /*
+ * We got a command we don't support. This shouldn't
+ * happen, commands should be filtered out above us.
+ */
+ ctl_set_invalid_opcode(ctsio);
+ ctl_done((union ctl_io *)ctsio);
+
+ return (CTL_RETVAL_COMPLETE);
+ break; /* NOTREACHED */
+ }
+
+ /*
+ * The first check is to make sure we're in bounds, the second
+ * check is to catch wrap-around problems. If the lba + num blocks
+ * is less than the lba, then we've wrapped around and the block
+ * range is invalid anyway.
+ */
+ if (((lba + num_blocks) > (lun->be_lun->maxlba + 1))
+ || ((lba + num_blocks) < lba)) {
+ ctl_set_lba_out_of_range(ctsio);
+ ctl_done((union ctl_io *)ctsio);
+ return (CTL_RETVAL_COMPLETE);
+ }
+
+ /* Zero number of blocks means "to the last logical block" */
+ if (num_blocks == 0) {
+ if ((lun->be_lun->maxlba + 1) - lba > UINT32_MAX) {
+ ctl_set_invalid_field(ctsio,
+ /*sks_valid*/ 0,
+ /*command*/ 1,
+ /*field*/ 0,
+ /*bit_valid*/ 0,
+ /*bit*/ 0);
+ ctl_done((union ctl_io *)ctsio);
+ return (CTL_RETVAL_COMPLETE);
+ }
+ num_blocks = (lun->be_lun->maxlba + 1) - lba;
+ }
+
+ len = lun->be_lun->blocksize;
+
+ /*
+ * If we've got a kernel request that hasn't been malloced yet,
+ * malloc it and tell the caller the data buffer is here.
+ */
+ if ((ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) == 0) {
+ ctsio->kern_data_ptr = malloc(len, M_CTL, M_WAITOK);;
+ ctsio->kern_data_len = len;
+ ctsio->kern_total_len = len;
+ ctsio->kern_data_resid = 0;
+ ctsio->kern_rel_offset = 0;
+ ctsio->kern_sg_entries = 0;
+ ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED;
+ ctsio->be_move_done = ctl_config_move_done;
+ ctl_datamove((union ctl_io *)ctsio);
+
+ return (CTL_RETVAL_COMPLETE);
+ }
+
+ lbalen.lba = lba;
+ lbalen.len = num_blocks;
+ lbalen.flags = byte2;
+ memcpy(ctsio->io_hdr.ctl_private[CTL_PRIV_LBA_LEN].bytes, &lbalen,
+ sizeof(lbalen));
+ retval = lun->backend->config_write((union ctl_io *)ctsio);
+
+ return (retval);
+}
+
+int
+ctl_unmap(struct ctl_scsiio *ctsio)
+{
+ struct ctl_lun *lun;
+ struct scsi_unmap *cdb;
+ struct ctl_ptr_len_flags ptrlen;
+ struct scsi_unmap_header *hdr;
+ struct scsi_unmap_desc *buf, *end;
+ uint64_t lba;
+ uint32_t num_blocks;
+ int len, retval;
+ uint8_t byte2;
+
+ retval = CTL_RETVAL_COMPLETE;
+
+ CTL_DEBUG_PRINT(("ctl_unmap\n"));
+
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+ cdb = (struct scsi_unmap *)ctsio->cdb;
+
+ len = scsi_2btoul(cdb->length);
+ byte2 = cdb->byte2;
+
+ /*
+ * If we've got a kernel request that hasn't been malloced yet,
+ * malloc it and tell the caller the data buffer is here.
+ */
+ if ((ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) == 0) {
+ ctsio->kern_data_ptr = malloc(len, M_CTL, M_WAITOK);;
+ ctsio->kern_data_len = len;
+ ctsio->kern_total_len = len;
+ ctsio->kern_data_resid = 0;
+ ctsio->kern_rel_offset = 0;
+ ctsio->kern_sg_entries = 0;
+ ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED;
+ ctsio->be_move_done = ctl_config_move_done;
+ ctl_datamove((union ctl_io *)ctsio);
+
+ return (CTL_RETVAL_COMPLETE);
+ }
+
+ len = ctsio->kern_total_len - ctsio->kern_data_resid;
+ hdr = (struct scsi_unmap_header *)ctsio->kern_data_ptr;
+ if (len < sizeof (*hdr) ||
+ len < (scsi_2btoul(hdr->length) + sizeof(hdr->length)) ||
+ len < (scsi_2btoul(hdr->desc_length) + sizeof (*hdr)) ||
+ scsi_2btoul(hdr->desc_length) % sizeof(*buf) != 0) {
+ ctl_set_invalid_field(ctsio,
+ /*sks_valid*/ 0,
+ /*command*/ 0,
+ /*field*/ 0,
+ /*bit_valid*/ 0,
+ /*bit*/ 0);
+ ctl_done((union ctl_io *)ctsio);
+ return (CTL_RETVAL_COMPLETE);
+ }
+ len = scsi_2btoul(hdr->desc_length);
+ buf = (struct scsi_unmap_desc *)(hdr + 1);
+ end = buf + len / sizeof(*buf);
+
+ ptrlen.ptr = (void *)buf;
+ ptrlen.len = len;
+ ptrlen.flags = byte2;
+ memcpy(ctsio->io_hdr.ctl_private[CTL_PRIV_LBA_LEN].bytes, &ptrlen,
+ sizeof(ptrlen));
+
+ for (; buf < end; buf++) {
+ lba = scsi_8btou64(buf->lba);
+ num_blocks = scsi_4btoul(buf->length);
+ if (((lba + num_blocks) > (lun->be_lun->maxlba + 1))
+ || ((lba + num_blocks) < lba)) {
+ ctl_set_lba_out_of_range(ctsio);
+ ctl_done((union ctl_io *)ctsio);
+ return (CTL_RETVAL_COMPLETE);
+ }
+ }
+
+ retval = lun->backend->config_write((union ctl_io *)ctsio);
+
+ return (retval);
+}
+
/*
* Note that this function currently doesn't actually do anything inside
* CTL to enforce things if the DQue bit is turned on.
@@ -6909,6 +7102,8 @@ ctl_read_capacity_16(struct ctl_scsiio *ctsio)
scsi_ulto4b(lun->be_lun->blocksize, data->length);
data->prot_lbppbe = lun->be_lun->pblockexp & SRC16_LBPPBE;
scsi_ulto2b(lun->be_lun->pblockoff & SRC16_LALBA_A, data->lalba_lbp);
+ if (lun->be_lun->flags & CTL_LUN_FLAG_UNMAP)
+ data->lalba_lbp[0] |= SRC16_LBPME;
ctsio->scsi_status = SCSI_STATUS_OK;
@@ -8995,8 +9190,8 @@ ctl_inquiry_evpd_supported(struct ctl_scsiio *ctsio, int alloc_len)
lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
- sup_page_size = sizeof(struct scsi_vpd_supported_pages) +
- SCSI_EVPD_NUM_SUPPORTED_PAGES;
+ sup_page_size = sizeof(struct scsi_vpd_supported_pages) *
+ SCSI_EVPD_NUM_SUPPORTED_PAGES;
ctsio->kern_data_ptr = malloc(sup_page_size, M_CTL, M_WAITOK | M_ZERO);
pages = (struct scsi_vpd_supported_pages *)ctsio->kern_data_ptr;
ctsio->kern_sg_entries = 0;
@@ -9032,6 +9227,10 @@ ctl_inquiry_evpd_supported(struct ctl_scsiio *ctsio, int alloc_len)
pages->page_list[1] = SVPD_UNIT_SERIAL_NUMBER;
/* Device Identification */
pages->page_list[2] = SVPD_DEVICE_ID;
+ /* Block limits */
+ pages->page_list[3] = SVPD_BLOCK_LIMITS;
+ /* Logical Block Provisioning */
+ pages->page_list[4] = SVPD_LBP;
ctsio->scsi_status = SCSI_STATUS_OK;
@@ -9296,11 +9495,117 @@ ctl_inquiry_evpd_devid(struct ctl_scsiio *ctsio, int alloc_len)
}
static int
+ctl_inquiry_evpd_block_limits(struct ctl_scsiio *ctsio, int alloc_len)
+{
+ struct scsi_vpd_block_limits *bl_ptr;
+ struct ctl_lun *lun;
+ int bs;
+
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+ bs = lun->be_lun->blocksize;
+
+ ctsio->kern_data_ptr = malloc(sizeof(*bl_ptr), M_CTL, M_WAITOK | M_ZERO);
+ bl_ptr = (struct scsi_vpd_block_limits *)ctsio->kern_data_ptr;
+ ctsio->kern_sg_entries = 0;
+
+ if (sizeof(*bl_ptr) < alloc_len) {
+ ctsio->residual = alloc_len - sizeof(*bl_ptr);
+ ctsio->kern_data_len = sizeof(*bl_ptr);
+ ctsio->kern_total_len = sizeof(*bl_ptr);
+ } else {
+ ctsio->residual = 0;
+ ctsio->kern_data_len = alloc_len;
+ ctsio->kern_total_len = alloc_len;
+ }
+ ctsio->kern_data_resid = 0;
+ ctsio->kern_rel_offset = 0;
+ ctsio->kern_sg_entries = 0;
+
+ /*
+ * The control device is always connected. The disk device, on the
+ * other hand, may not be online all the time. Need to change this
+ * to figure out whether the disk device is actually online or not.
+ */
+ if (lun != NULL)
+ bl_ptr->device = (SID_QUAL_LU_CONNECTED << 5) |
+ lun->be_lun->lun_type;
+ else
+ bl_ptr->device = (SID_QUAL_LU_OFFLINE << 5) | T_DIRECT;
+
+ bl_ptr->page_code = SVPD_BLOCK_LIMITS;
+ scsi_ulto2b(sizeof(*bl_ptr), bl_ptr->page_length);
+ scsi_ulto4b((16 * 1024 * 1024) / bs, bl_ptr->max_txfer_len);
+ scsi_ulto4b(MAXPHYS / bs, bl_ptr->opt_txfer_len);
+ if (lun->be_lun->flags & CTL_LUN_FLAG_UNMAP) {
+ scsi_ulto4b(0xffffffff, bl_ptr->max_unmap_lba_cnt);
+ scsi_ulto4b(0xffffffff, bl_ptr->max_unmap_blk_cnt);
+ }
+ scsi_u64to8b(UINT64_MAX, bl_ptr->max_write_same_length);
+
+ ctsio->scsi_status = SCSI_STATUS_OK;
+ ctsio->be_move_done = ctl_config_move_done;
+ ctl_datamove((union ctl_io *)ctsio);
+
+ return (CTL_RETVAL_COMPLETE);
+}
+
+static int
+ctl_inquiry_evpd_lbp(struct ctl_scsiio *ctsio, int alloc_len)
+{
+ struct scsi_vpd_logical_block_prov *lbp_ptr;
+ struct ctl_lun *lun;
+ int bs;
+
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+ bs = lun->be_lun->blocksize;
+
+ ctsio->kern_data_ptr = malloc(sizeof(*lbp_ptr), M_CTL, M_WAITOK | M_ZERO);
+ lbp_ptr = (struct scsi_vpd_logical_block_prov *)ctsio->kern_data_ptr;
+ ctsio->kern_sg_entries = 0;
+
+ if (sizeof(*lbp_ptr) < alloc_len) {
+ ctsio->residual = alloc_len - sizeof(*lbp_ptr);
+ ctsio->kern_data_len = sizeof(*lbp_ptr);
+ ctsio->kern_total_len = sizeof(*lbp_ptr);
+ } else {
+ ctsio->residual = 0;
+ ctsio->kern_data_len = alloc_len;
+ ctsio->kern_total_len = alloc_len;
+ }
+ ctsio->kern_data_resid = 0;
+ ctsio->kern_rel_offset = 0;
+ ctsio->kern_sg_entries = 0;
+
+ /*
+ * The control device is always connected. The disk device, on the
+ * other hand, may not be online all the time. Need to change this
+ * to figure out whether the disk device is actually online or not.
+ */
+ if (lun != NULL)
+ lbp_ptr->device = (SID_QUAL_LU_CONNECTED << 5) |
+ lun->be_lun->lun_type;
+ else
+ lbp_ptr->device = (SID_QUAL_LU_OFFLINE << 5) | T_DIRECT;
+
+ lbp_ptr->page_code = SVPD_LBP;
+ if (lun->be_lun->flags & CTL_LUN_FLAG_UNMAP)
+ lbp_ptr->flags = SVPD_LBP_UNMAP | SVPD_LBP_WS16 | SVPD_LBP_WS10;
+
+ ctsio->scsi_status = SCSI_STATUS_OK;
+ ctsio->be_move_done = ctl_config_move_done;
+ ctl_datamove((union ctl_io *)ctsio);
+
+ return (CTL_RETVAL_COMPLETE);
+}
+
+static int
ctl_inquiry_evpd(struct ctl_scsiio *ctsio)
{
struct scsi_inquiry *cdb;
+ struct ctl_lun *lun;
int alloc_len, retval;
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
cdb = (struct scsi_inquiry *)ctsio->cdb;
retval = CTL_RETVAL_COMPLETE;
@@ -9317,6 +9622,12 @@ ctl_inquiry_evpd(struct ctl_scsiio *ctsio)
case SVPD_DEVICE_ID:
retval = ctl_inquiry_evpd_devid(ctsio, alloc_len);
break;
+ case SVPD_BLOCK_LIMITS:
+ retval = ctl_inquiry_evpd_block_limits(ctsio, alloc_len);
+ break;
+ case SVPD_LBP:
+ retval = ctl_inquiry_evpd_lbp(ctsio, alloc_len);
+ break;
default:
ctl_set_invalid_field(ctsio,
/*sks_valid*/ 1,
@@ -9687,6 +9998,24 @@ ctl_get_lba_len(union ctl_io *io, uint64_t *lba, uint32_t *len)
*len = scsi_4btoul(cdb->length);
break;
}
+ case WRITE_SAME_10: {
+ struct scsi_write_same_10 *cdb;
+
+ cdb = (struct scsi_write_same_10 *)io->scsiio.cdb;
+
+ *lba = scsi_4btoul(cdb->addr);
+ *len = scsi_2btoul(cdb->length);
+ break;
+ }
+ case WRITE_SAME_16: {
+ struct scsi_write_same_16 *cdb;
+
+ cdb = (struct scsi_write_same_16 *)io->scsiio.cdb;
+
+ *lba = scsi_8btou64(cdb->addr);
+ *len = scsi_4btoul(cdb->length);
+ break;
+ }
default:
return (1);
break; /* NOTREACHED */
diff --git a/sys/cam/ctl/ctl_backend.h b/sys/cam/ctl/ctl_backend.h
index c64492d52094..ad93119afc97 100644
--- a/sys/cam/ctl/ctl_backend.h
+++ b/sys/cam/ctl/ctl_backend.h
@@ -71,6 +71,8 @@
* valid for use in SCSI INQUIRY VPD page 0x83.
*
* The DEV_TYPE flag tells us that the device_type field is filled in.
+ *
+ * The UNMAP flag tells us that this LUN supports UNMAP.
*/
typedef enum {
CTL_LUN_FLAG_ID_REQ = 0x01,
@@ -79,7 +81,8 @@ typedef enum {
CTL_LUN_FLAG_PRIMARY = 0x08,
CTL_LUN_FLAG_SERIAL_NUM = 0x10,
CTL_LUN_FLAG_DEVID = 0x20,
- CTL_LUN_FLAG_DEV_TYPE = 0x40
+ CTL_LUN_FLAG_DEV_TYPE = 0x40,
+ CTL_LUN_FLAG_UNMAP = 0x80
} ctl_backend_lun_flags;
#ifdef _KERNEL
diff --git a/sys/cam/ctl/ctl_backend_block.c b/sys/cam/ctl/ctl_backend_block.c
index 91700b5cb28b..4ae227a6b4c3 100644
--- a/sys/cam/ctl/ctl_backend_block.c
+++ b/sys/cam/ctl/ctl_backend_block.c
@@ -49,6 +49,7 @@ __FBSDID("$FreeBSD$");
#include <sys/kthread.h>
#include <sys/bio.h>
#include <sys/fcntl.h>
+#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/condvar.h>
@@ -150,6 +151,7 @@ struct ctl_be_block_lun {
union ctl_be_block_bedata backend;
cbb_dispatch_t dispatch;
cbb_dispatch_t lun_flush;
+ cbb_dispatch_t unmap;
struct mtx lock;
uma_zone_t lun_zone;
uint64_t size_blocks;
@@ -205,6 +207,7 @@ struct ctl_be_block_io {
uint64_t io_offset;
struct ctl_be_block_softc *softc;
struct ctl_be_block_lun *lun;
+ void (*beio_cont)(struct ctl_be_block_io *beio); /* to continue processing */
};
static int cbb_num_threads = 14;
@@ -225,6 +228,8 @@ static void ctl_be_block_dispatch_file(struct ctl_be_block_lun *be_lun,
struct ctl_be_block_io *beio);
static void ctl_be_block_flush_dev(struct ctl_be_block_lun *be_lun,
struct ctl_be_block_io *beio);
+static void ctl_be_block_unmap_dev(struct ctl_be_block_lun *be_lun,
+ struct ctl_be_block_io *beio);
static void ctl_be_block_dispatch_dev(struct ctl_be_block_lun *be_lun,
struct ctl_be_block_io *beio);
static void ctl_be_block_cw_dispatch(struct ctl_be_block_lun *be_lun,
@@ -333,8 +338,12 @@ ctl_complete_beio(struct ctl_be_block_io *beio)
/*now*/ NULL,
/*then*/&beio->ds_t0);
- ctl_free_beio(beio);
- ctl_done(io);
+ if (beio->beio_cont != NULL) {
+ beio->beio_cont(beio);
+ } else {
+ ctl_free_beio(beio);
+ ctl_done(io);
+ }
}
static int
@@ -480,11 +489,12 @@ ctl_be_block_biodone(struct bio *bio)
}
/*
- * If this is a write or a flush, we're all done.
+ * If this is a write, a flush or a delete, we're all done.
* If this is a read, we can now send the data to the user.
*/
if ((beio->bio_cmd == BIO_WRITE)
- || (beio->bio_cmd == BIO_FLUSH)) {
+ || (beio->bio_cmd == BIO_FLUSH)
+ || (beio->bio_cmd == BIO_DELETE)) {
ctl_set_success(&io->scsiio);
ctl_complete_beio(beio);
} else {
@@ -750,6 +760,77 @@ ctl_be_block_flush_dev(struct ctl_be_block_lun *be_lun,
}
static void
+ctl_be_block_unmap_dev_range(struct ctl_be_block_lun *be_lun,
+ struct ctl_be_block_io *beio,
+ uint64_t off, uint64_t len, int last)
+{
+ struct bio *bio;
+ struct ctl_be_block_devdata *dev_data;
+
+ dev_data = &be_lun->backend.dev;
+ while (len > 0) {
+ bio = g_alloc_bio();
+ bio->bio_cmd = BIO_DELETE;
+ bio->bio_flags |= beio->bio_flags;
+ bio->bio_dev = dev_data->cdev;
+ bio->bio_offset = off;
+ bio->bio_length = MIN(len, LONG_MAX);
+ bio->bio_data = 0;
+ bio->bio_done = ctl_be_block_biodone;
+ bio->bio_caller1 = beio;
+ bio->bio_pblkno = beio->io_offset / be_lun->blocksize;
+
+ off += bio->bio_length;
+ len -= bio->bio_length;
+
+ mtx_lock(&be_lun->lock);
+ beio->num_bios_sent++;
+ if (last && len == 0)
+ beio->send_complete = 1;
+ mtx_unlock(&be_lun->lock);
+
+ (*dev_data->csw->d_strategy)(bio);
+ }
+}
+
+static void
+ctl_be_block_unmap_dev(struct ctl_be_block_lun *be_lun,
+ struct ctl_be_block_io *beio)
+{
+ union ctl_io *io;
+ struct ctl_be_block_devdata *dev_data;
+ struct ctl_ptr_len_flags ptrlen;
+ struct scsi_unmap_desc *buf, *end;
+ uint64_t len;
+
+ dev_data = &be_lun->backend.dev;
+ io = beio->io;
+
+ DPRINTF("entered\n");
+
+ binuptime(&beio->ds_t0);
+ devstat_start_transaction(be_lun->disk_stats, &beio->ds_t0);
+
+ if (beio->io_offset == -1) {
+ beio->io_len = 0;
+ memcpy(&ptrlen, io->io_hdr.ctl_private[CTL_PRIV_LBA_LEN].bytes,
+ sizeof(ptrlen));
+ buf = (struct scsi_unmap_desc *)ptrlen.ptr;
+ end = buf + ptrlen.len / sizeof(*buf);
+ for (; buf < end; buf++) {
+ len = (uint64_t)scsi_4btoul(buf->length) *
+ be_lun->blocksize;
+ beio->io_len += len;
+ ctl_be_block_unmap_dev_range(be_lun, beio,
+ scsi_8btou64(buf->lba) * be_lun->blocksize, len,
+ (end - buf < 32) ? TRUE : FALSE);
+ }
+ } else
+ ctl_be_block_unmap_dev_range(be_lun, beio,
+ beio->io_offset, beio->io_len, TRUE);
+}
+
+static void
ctl_be_block_dispatch_dev(struct ctl_be_block_lun *be_lun,
struct ctl_be_block_io *beio)
{
@@ -837,6 +918,208 @@ ctl_be_block_dispatch_dev(struct ctl_be_block_lun *be_lun,
}
static void
+ctl_be_block_cw_done_ws(struct ctl_be_block_io *beio)
+{
+ union ctl_io *io;
+
+ io = beio->io;
+ ctl_free_beio(beio);
+ if (((io->io_hdr.status & CTL_STATUS_MASK) != CTL_STATUS_NONE)
+ && ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS)) {
+ ctl_config_write_done(io);
+ return;
+ }
+
+ ctl_be_block_config_write(io);
+}
+
+static void
+ctl_be_block_cw_dispatch_ws(struct ctl_be_block_lun *be_lun,
+ union ctl_io *io)
+{
+ struct ctl_be_block_io *beio;
+ struct ctl_be_block_softc *softc;
+ struct ctl_lba_len_flags lbalen;
+ uint64_t len_left, lba;
+ int i, seglen;
+ uint8_t *buf, *end;
+
+ DPRINTF("entered\n");
+
+ beio = io->io_hdr.ctl_private[CTL_PRIV_BACKEND].ptr;
+ softc = be_lun->softc;
+ memcpy(&lbalen, io->io_hdr.ctl_private[CTL_PRIV_LBA_LEN].bytes,
+ sizeof(lbalen));
+
+ if (lbalen.flags & ~(SWS_LBDATA | SWS_UNMAP) ||
+ (lbalen.flags & SWS_UNMAP && be_lun->unmap == NULL)) {
+ ctl_free_beio(beio);
+ ctl_set_invalid_field(&io->scsiio,
+ /*sks_valid*/ 1,
+ /*command*/ 1,
+ /*field*/ 1,
+ /*bit_valid*/ 0,
+ /*bit*/ 0);
+ ctl_config_write_done(io);
+ return;
+ }
+
+ /*
+ * If the I/O came down with an ordered or head of queue tag, set
+ * the BIO_ORDERED attribute. For head of queue tags, that's
+ * pretty much the best we can do.
+ */
+ if ((io->scsiio.tag_type == CTL_TAG_ORDERED)
+ || (io->scsiio.tag_type == CTL_TAG_HEAD_OF_QUEUE))
+ beio->bio_flags = BIO_ORDERED;
+
+ switch (io->scsiio.tag_type) {
+ case CTL_TAG_ORDERED:
+ beio->ds_tag_type = DEVSTAT_TAG_ORDERED;
+ break;
+ case CTL_TAG_HEAD_OF_QUEUE:
+ beio->ds_tag_type = DEVSTAT_TAG_HEAD;
+ break;
+ case CTL_TAG_UNTAGGED:
+ case CTL_TAG_SIMPLE:
+ case CTL_TAG_ACA:
+ default:
+ beio->ds_tag_type = DEVSTAT_TAG_SIMPLE;
+ break;
+ }
+
+ if (lbalen.flags & SWS_UNMAP) {
+ beio->io_offset = lbalen.lba * be_lun->blocksize;
+ beio->io_len = (uint64_t)lbalen.len * be_lun->blocksize;
+ beio->bio_cmd = BIO_DELETE;
+ beio->ds_trans_type = DEVSTAT_FREE;
+
+ be_lun->unmap(be_lun, beio);
+ return;
+ }
+
+ beio->bio_cmd = BIO_WRITE;
+ beio->ds_trans_type = DEVSTAT_WRITE;
+
+ DPRINTF("WRITE SAME at LBA %jx len %u\n",
+ (uintmax_t)lbalen.lba, lbalen.len);
+
+ len_left = (uint64_t)lbalen.len * be_lun->blocksize;
+ for (i = 0, lba = 0; i < CTLBLK_MAX_SEGS && len_left > 0; i++) {
+
+ /*
+ * Setup the S/G entry for this chunk.
+ */
+ seglen = MIN(MAXPHYS, len_left);
+ seglen -= seglen % be_lun->blocksize;
+ beio->sg_segs[i].len = seglen;
+ beio->sg_segs[i].addr = uma_zalloc(be_lun->lun_zone, M_WAITOK);
+
+ DPRINTF("segment %d addr %p len %zd\n", i,
+ beio->sg_segs[i].addr, beio->sg_segs[i].len);
+
+ beio->num_segs++;
+ len_left -= seglen;
+
+ buf = beio->sg_segs[i].addr;
+ end = buf + seglen;
+ for (; buf < end; buf += be_lun->blocksize) {
+ memcpy(buf, io->scsiio.kern_data_ptr, be_lun->blocksize);
+ if (lbalen.flags & SWS_LBDATA)
+ scsi_ulto4b(lbalen.lba + lba, buf);
+ lba++;
+ }
+ }
+
+ beio->io_offset = lbalen.lba * be_lun->blocksize;
+ beio->io_len = lba * be_lun->blocksize;
+
+ /* We can not do all in one run. Correct and schedule rerun. */
+ if (len_left > 0) {
+ lbalen.lba += lba;
+ lbalen.len -= lba;
+ memcpy(io->io_hdr.ctl_private[CTL_PRIV_LBA_LEN].bytes, &lbalen,
+ sizeof(lbalen));
+ beio->beio_cont = ctl_be_block_cw_done_ws;
+ }
+
+ be_lun->dispatch(be_lun, beio);
+}
+
+static void
+ctl_be_block_cw_dispatch_unmap(struct ctl_be_block_lun *be_lun,
+ union ctl_io *io)
+{
+ struct ctl_be_block_io *beio;
+ struct ctl_be_block_softc *softc;
+ struct ctl_ptr_len_flags ptrlen;
+
+ DPRINTF("entered\n");
+
+ beio = io->io_hdr.ctl_private[CTL_PRIV_BACKEND].ptr;
+ softc = be_lun->softc;
+ memcpy(&ptrlen, io->io_hdr.ctl_private[CTL_PRIV_LBA_LEN].bytes,
+ sizeof(ptrlen));
+
+ if (ptrlen.flags != 0 || be_lun->unmap == NULL) {
+ ctl_free_beio(beio);
+ ctl_set_invalid_field(&io->scsiio,
+ /*sks_valid*/ 0,
+ /*command*/ 1,
+ /*field*/ 0,
+ /*bit_valid*/ 0,
+ /*bit*/ 0);
+ ctl_config_write_done(io);
+ return;
+ }
+
+ /*
+ * If the I/O came down with an ordered or head of queue tag, set
+ * the BIO_ORDERED attribute. For head of queue tags, that's
+ * pretty much the best we can do.
+ */
+ if ((io->scsiio.tag_type == CTL_TAG_ORDERED)
+ || (io->scsiio.tag_type == CTL_TAG_HEAD_OF_QUEUE))
+ beio->bio_flags = BIO_ORDERED;
+
+ switch (io->scsiio.tag_type) {
+ case CTL_TAG_ORDERED:
+ beio->ds_tag_type = DEVSTAT_TAG_ORDERED;
+ break;
+ case CTL_TAG_HEAD_OF_QUEUE:
+ beio->ds_tag_type = DEVSTAT_TAG_HEAD;
+ break;
+ case CTL_TAG_UNTAGGED:
+ case CTL_TAG_SIMPLE:
+ case CTL_TAG_ACA:
+ default:
+ beio->ds_tag_type = DEVSTAT_TAG_SIMPLE;
+ break;
+ }
+
+ beio->io_len = 0;
+ beio->io_offset = -1;
+
+ beio->bio_cmd = BIO_DELETE;
+ beio->ds_trans_type = DEVSTAT_FREE;
+
+ DPRINTF("WRITE SAME at LBA %jx len %u\n",
+ (uintmax_t)lbalen.lba, lbalen.len);
+
+ be_lun->unmap(be_lun, beio);
+}
+
+static void
+ctl_be_block_cw_done(struct ctl_be_block_io *beio)
+{
+ union ctl_io *io;
+
+ io = beio->io;
+ ctl_free_beio(beio);
+ ctl_config_write_done(io);
+}
+
+static void
ctl_be_block_cw_dispatch(struct ctl_be_block_lun *be_lun,
union ctl_io *io)
{
@@ -847,11 +1130,9 @@ ctl_be_block_cw_dispatch(struct ctl_be_block_lun *be_lun,
softc = be_lun->softc;
beio = ctl_alloc_beio(softc);
- KASSERT(beio != NULL, ("ctl_alloc_beio() failed"));
-
beio->io = io;
- beio->softc = softc;
beio->lun = be_lun;
+ beio->beio_cont = ctl_be_block_cw_done;
io->io_hdr.ctl_private[CTL_PRIV_BACKEND].ptr = beio;
switch (io->scsiio.cdb[0]) {
@@ -863,6 +1144,13 @@ ctl_be_block_cw_dispatch(struct ctl_be_block_lun *be_lun,
beio->io_len = 0;
be_lun->lun_flush(be_lun, beio);
break;
+ case WRITE_SAME_10:
+ case WRITE_SAME_16:
+ ctl_be_block_cw_dispatch_ws(be_lun, io);
+ break;
+ case UNMAP:
+ ctl_be_block_cw_dispatch_unmap(be_lun, io);
+ break;
default:
panic("Unhandled CDB type %#x", io->scsiio.cdb[0]);
break;
@@ -918,10 +1206,7 @@ ctl_be_block_dispatch(struct ctl_be_block_lun *be_lun,
}
beio = ctl_alloc_beio(softc);
- KASSERT(beio != NULL, ("ctl_alloc_beio() failed"));
-
beio->io = io;
- beio->softc = softc;
beio->lun = be_lun;
io->io_hdr.ctl_private[CTL_PRIV_BACKEND].ptr = beio;
@@ -1271,6 +1556,7 @@ ctl_be_block_open_dev(struct ctl_be_block_lun *be_lun, struct ctl_lun_req *req)
be_lun->dev_type = CTL_BE_BLOCK_DEV;
be_lun->dispatch = ctl_be_block_dispatch_dev;
be_lun->lun_flush = ctl_be_block_flush_dev;
+ be_lun->unmap = ctl_be_block_unmap_dev;
be_lun->backend.dev.cdev = be_lun->vn->v_rdev;
be_lun->backend.dev.csw = dev_refthread(be_lun->backend.dev.cdev,
&be_lun->backend.dev.dev_ref);
@@ -1530,7 +1816,7 @@ ctl_be_block_create(struct ctl_be_block_softc *softc, struct ctl_lun_req *req)
struct ctl_lun_create_params *params;
struct ctl_be_arg *file_arg;
char tmpstr[32];
- int retval, num_threads;
+ int retval, num_threads, unmap;
int i;
params = &req->reqdata.create;
@@ -1621,6 +1907,7 @@ ctl_be_block_create(struct ctl_be_block_softc *softc, struct ctl_lun_req *req)
* XXX This searching loop might be refactored to be combined with
* the loop above,
*/
+ unmap = 0;
for (i = 0; i < req->num_be_args; i++) {
if (strcmp(req->kern_be_args[i].kname, "num_threads") == 0) {
struct ctl_be_arg *thread_arg;
@@ -1649,6 +1936,9 @@ ctl_be_block_create(struct ctl_be_block_softc *softc, struct ctl_lun_req *req)
}
num_threads = tmp_num_threads;
+ } else if (strcmp(req->kern_be_args[i].kname, "unmap") == 0 &&
+ strcmp(req->kern_be_args[i].kvalue, "on") == 0) {
+ unmap = 1;
} else if (strcmp(req->kern_be_args[i].kname, "file") != 0 &&
strcmp(req->kern_be_args[i].kname, "dev") != 0) {
struct ctl_be_lun_option *opt;
@@ -1664,6 +1954,8 @@ ctl_be_block_create(struct ctl_be_block_softc *softc, struct ctl_lun_req *req)
be_lun->flags = CTL_BE_BLOCK_LUN_UNCONFIGURED;
be_lun->ctl_be_lun.flags = CTL_LUN_FLAG_PRIMARY;
+ if (unmap)
+ be_lun->ctl_be_lun.flags |= CTL_LUN_FLAG_UNMAP;
be_lun->ctl_be_lun.be_lun = be_lun;
be_lun->ctl_be_lun.blocksize = be_lun->blocksize;
be_lun->ctl_be_lun.pblockexp = be_lun->pblockexp;
@@ -2139,6 +2431,9 @@ ctl_be_block_config_write(union ctl_io *io)
switch (io->scsiio.cdb[0]) {
case SYNCHRONIZE_CACHE:
case SYNCHRONIZE_CACHE_16:
+ case WRITE_SAME_10:
+ case WRITE_SAME_16:
+ case UNMAP:
/*
* The upper level CTL code will filter out any CDBs with
* the immediate bit set and return the proper error.
diff --git a/sys/cam/ctl/ctl_backend_ramdisk.c b/sys/cam/ctl/ctl_backend_ramdisk.c
index 191e8e4ab6e5..1fae35bc0561 100644
--- a/sys/cam/ctl/ctl_backend_ramdisk.c
+++ b/sys/cam/ctl/ctl_backend_ramdisk.c
@@ -491,7 +491,7 @@ ctl_backend_ramdisk_create(struct ctl_be_ramdisk_softc *softc,
struct ctl_lun_create_params *params;
uint32_t blocksize;
char tmpstr[32];
- int i, retval;
+ int i, retval, unmap;
retval = 0;
params = &req->reqdata.create;
@@ -547,18 +547,25 @@ ctl_backend_ramdisk_create(struct ctl_be_ramdisk_softc *softc,
be_lun->softc = softc;
for (i = 0; i < req->num_be_args; i++) {
- struct ctl_be_lun_option *opt;
-
- opt = malloc(sizeof(*opt), M_RAMDISK, M_WAITOK);
- opt->name = malloc(strlen(req->kern_be_args[i].kname) + 1, M_RAMDISK, M_WAITOK);
- strcpy(opt->name, req->kern_be_args[i].kname);
- opt->value = malloc(strlen(req->kern_be_args[i].kvalue) + 1, M_RAMDISK, M_WAITOK);
- strcpy(opt->value, req->kern_be_args[i].kvalue);
- STAILQ_INSERT_TAIL(&be_lun->ctl_be_lun.options, opt, links);
+ if (strcmp(req->kern_be_args[i].kname, "unmap") == 0 &&
+ strcmp(req->kern_be_args[i].kvalue, "on") == 0) {
+ unmap = 1;
+ } else {
+ struct ctl_be_lun_option *opt;
+
+ opt = malloc(sizeof(*opt), M_RAMDISK, M_WAITOK);
+ opt->name = malloc(strlen(req->kern_be_args[i].kname) + 1, M_RAMDISK, M_WAITOK);
+ strcpy(opt->name, req->kern_be_args[i].kname);
+ opt->value = malloc(strlen(req->kern_be_args[i].kvalue) + 1, M_RAMDISK, M_WAITOK);
+ strcpy(opt->value, req->kern_be_args[i].kvalue);
+ STAILQ_INSERT_TAIL(&be_lun->ctl_be_lun.options, opt, links);
+ }
}
be_lun->flags = CTL_BE_RAMDISK_LUN_UNCONFIGURED;
be_lun->ctl_be_lun.flags = CTL_LUN_FLAG_PRIMARY;
+ if (unmap)
+ be_lun->ctl_be_lun.flags = CTL_LUN_FLAG_UNMAP;
be_lun->ctl_be_lun.be_lun = be_lun;
if (params->flags & CTL_LUN_FLAG_ID_REQ) {
@@ -882,6 +889,12 @@ ctl_backend_ramdisk_config_write(union ctl_io *io)
ctl_config_write_done(io);
break;
}
+ case WRITE_SAME_10:
+ case WRITE_SAME_16:
+ case UNMAP:
+ ctl_set_success(&io->scsiio);
+ ctl_config_write_done(io);
+ break;
default:
ctl_set_invalid_opcode(&io->scsiio);
ctl_config_write_done(io);
diff --git a/sys/cam/ctl/ctl_cmd_table.c b/sys/cam/ctl/ctl_cmd_table.c
index b4a53d685c10..8250c17ac4bb 100644
--- a/sys/cam/ctl/ctl_cmd_table.c
+++ b/sys/cam/ctl/ctl_cmd_table.c
@@ -331,10 +331,13 @@ struct ctl_cmd_entry ctl_cmd_table[] =
{NULL, CTL_SERIDX_INVLD, CTL_CMD_FLAG_NONE, CTL_LUN_PAT_NONE},
/* 41 WRITE SAME(10) */
-{NULL, CTL_SERIDX_INVLD, CTL_CMD_FLAG_NONE, CTL_LUN_PAT_NONE},
+{ctl_write_same, CTL_SERIDX_WRITE, CTL_CMD_FLAG_OK_ON_SLUN |
+ CTL_FLAG_DATA_OUT,
+ CTL_LUN_PAT_WRITE | CTL_LUN_PAT_RANGE},
-/* 42 READ SUB-CHANNEL */
-{NULL, CTL_SERIDX_INVLD, CTL_CMD_FLAG_NONE, CTL_LUN_PAT_NONE},
+/* 42 READ SUB-CHANNEL / UNMAP */
+{ctl_unmap, CTL_SERIDX_WRITE, CTL_CMD_FLAG_OK_ON_SLUN | CTL_FLAG_DATA_OUT,
+ CTL_LUN_PAT_WRITE},
/* 43 READ TOC/PMA/ATIP */
{NULL, CTL_SERIDX_INVLD, CTL_CMD_FLAG_NONE, CTL_LUN_PAT_NONE},
@@ -623,7 +626,9 @@ struct ctl_cmd_entry ctl_cmd_table[] =
{NULL, CTL_SERIDX_INVLD, CTL_CMD_FLAG_NONE, CTL_LUN_PAT_NONE},
/* 93 WRITE SAME(16) */
-{NULL, CTL_SERIDX_INVLD, CTL_CMD_FLAG_NONE, CTL_LUN_PAT_NONE},
+{ctl_write_same, CTL_SERIDX_WRITE, CTL_CMD_FLAG_OK_ON_SLUN |
+ CTL_FLAG_DATA_OUT,
+ CTL_LUN_PAT_WRITE | CTL_LUN_PAT_RANGE},
/* 94 */
{NULL, CTL_SERIDX_INVLD, CTL_CMD_FLAG_NONE, CTL_LUN_PAT_NONE},
diff --git a/sys/cam/ctl/ctl_io.h b/sys/cam/ctl/ctl_io.h
index 8981b358e3c5..e423bb065ba6 100644
--- a/sys/cam/ctl/ctl_io.h
+++ b/sys/cam/ctl/ctl_io.h
@@ -134,6 +134,18 @@ struct ctl_lba_len {
uint32_t len;
};
+struct ctl_lba_len_flags {
+ uint64_t lba;
+ uint32_t len;
+ uint32_t flags;
+};
+
+struct ctl_ptr_len_flags {
+ uint8_t *ptr;
+ uint32_t len;
+ uint32_t flags;
+};
+
union ctl_priv {
uint8_t bytes[sizeof(uint64_t) * 2];
uint64_t integer;
diff --git a/sys/cam/ctl/ctl_private.h b/sys/cam/ctl/ctl_private.h
index d2035fb1d244..3d7d909f77bb 100644
--- a/sys/cam/ctl/ctl_private.h
+++ b/sys/cam/ctl/ctl_private.h
@@ -470,6 +470,8 @@ int ctl_start_stop(struct ctl_scsiio *ctsio);
int ctl_sync_cache(struct ctl_scsiio *ctsio);
int ctl_format(struct ctl_scsiio *ctsio);
int ctl_write_buffer(struct ctl_scsiio *ctsio);
+int ctl_write_same(struct ctl_scsiio *ctsio);
+int ctl_unmap(struct ctl_scsiio *ctsio);
int ctl_mode_select(struct ctl_scsiio *ctsio);
int ctl_mode_sense(struct ctl_scsiio *ctsio);
int ctl_read_capacity(struct ctl_scsiio *ctsio);