diff options
author | mav <mav@FreeBSD.org> | 2014-04-08 20:50:48 +0000 |
---|---|---|
committer | mav <mav@FreeBSD.org> | 2014-04-08 20:50:48 +0000 |
commit | 9e77bb67b8e86fb9888203c0f6f5c7657a7eeb98 (patch) | |
tree | 95a3cc931da8a5065180b81740f088c57be28b04 | |
parent | efe0f3c8dbe131cdb9f8adc9a2e7fd16cf6c69f3 (diff) | |
download | FreeBSD-src-9e77bb67b8e86fb9888203c0f6f5c7657a7eeb98.zip FreeBSD-src-9e77bb67b8e86fb9888203c0f6f5c7657a7eeb98.tar.gz |
Add support for SCSI UNMAP commands to CTL.
This patch adds support for three new SCSI commands: UNMAP, WRITE SAME(10)
and WRITE SAME(16). WRITE SAME commands support both normal write mode
and UNMAP flag. To properly report UNMAP capabilities this patch also adds
support for reporting two new VPD pages: Block limits and Logical Block
Provisioning.
UNMAP support can be enabled per-LUN by adding "-o unmap=on" to `ctladm
create` command line or "option unmap on" to lun sections of /etc/ctl.conf.
At this moment UNMAP supported for ramdisks and device-backed block LUNs.
It was tested to work great with ZFS ZVOLs. For file-backed LUNs UNMAP
support is unfortunately missing due to absence of respective VFS KPI.
Reviewed by: ken
MFC after: 1 month
Sponsored by: iXsystems, Inc
-rw-r--r-- | sys/cam/ctl/ctl.c | 337 | ||||
-rw-r--r-- | sys/cam/ctl/ctl_backend.h | 5 | ||||
-rw-r--r-- | sys/cam/ctl/ctl_backend_block.c | 317 | ||||
-rw-r--r-- | sys/cam/ctl/ctl_backend_ramdisk.c | 31 | ||||
-rw-r--r-- | sys/cam/ctl/ctl_cmd_table.c | 13 | ||||
-rw-r--r-- | sys/cam/ctl/ctl_io.h | 12 | ||||
-rw-r--r-- | sys/cam/ctl/ctl_private.h | 2 | ||||
-rw-r--r-- | sys/cam/scsi/scsi_all.h | 14 |
8 files changed, 702 insertions, 29 deletions
diff --git a/sys/cam/ctl/ctl.c b/sys/cam/ctl/ctl.c index ad96f5d..b0a67d6 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 c64492d..ad93119 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 91700b5..4ae227a 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 191e8e4..1fae35b 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 b4a53d6..8250c17 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 8981b35..e423bb0 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 d2035fb..3d7d909 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); diff --git a/sys/cam/scsi/scsi_all.h b/sys/cam/scsi/scsi_all.h index 1b09324..c09181f 100644 --- a/sys/cam/scsi/scsi_all.h +++ b/sys/cam/scsi/scsi_all.h @@ -854,6 +854,20 @@ struct scsi_unmap uint8_t control; }; +struct scsi_unmap_header +{ + uint8_t length[2]; + uint8_t desc_length[2]; + uint8_t reserved[8]; +}; + +struct scsi_unmap_desc +{ + uint8_t lba[8]; + uint8_t length[4]; + uint8_t reserved[4]; +}; + struct scsi_write_verify_10 { uint8_t opcode; |