Index: stable/10/sys/cam/ctl/ctl_backend_block.c =================================================================== --- stable/10/sys/cam/ctl/ctl_backend_block.c (revision 279003) +++ stable/10/sys/cam/ctl/ctl_backend_block.c (revision 279004) @@ -1,2931 +1,2935 @@ /*- * Copyright (c) 2003 Silicon Graphics International Corp. * Copyright (c) 2009-2011 Spectra Logic Corporation * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * substantially similar to the "NO WARRANTY" disclaimer below * ("Disclaimer") and any redistribution must be conditioned upon * including a substantially similar Disclaimer requirement for further * binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * * $Id: //depot/users/kenm/FreeBSD-test2/sys/cam/ctl/ctl_backend_block.c#5 $ */ /* * CAM Target Layer driver backend for block devices. * * Author: Ken Merry */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * The idea here is that we'll allocate enough S/G space to hold a 1MB * I/O. If we get an I/O larger than that, we'll split it. */ #define CTLBLK_HALF_IO_SIZE (512 * 1024) #define CTLBLK_MAX_IO_SIZE (CTLBLK_HALF_IO_SIZE * 2) #define CTLBLK_MAX_SEG MAXPHYS #define CTLBLK_HALF_SEGS MAX(CTLBLK_HALF_IO_SIZE / CTLBLK_MAX_SEG, 1) #define CTLBLK_MAX_SEGS (CTLBLK_HALF_SEGS * 2) #ifdef CTLBLK_DEBUG #define DPRINTF(fmt, args...) \ printf("cbb(%s:%d): " fmt, __FUNCTION__, __LINE__, ##args) #else #define DPRINTF(fmt, args...) do {} while(0) #endif #define PRIV(io) \ ((struct ctl_ptr_len_flags *)&(io)->io_hdr.ctl_private[CTL_PRIV_BACKEND]) #define ARGS(io) \ ((struct ctl_lba_len_flags *)&(io)->io_hdr.ctl_private[CTL_PRIV_LBA_LEN]) SDT_PROVIDER_DEFINE(cbb); typedef enum { CTL_BE_BLOCK_LUN_UNCONFIGURED = 0x01, CTL_BE_BLOCK_LUN_CONFIG_ERR = 0x02, CTL_BE_BLOCK_LUN_WAITING = 0x04, CTL_BE_BLOCK_LUN_MULTI_THREAD = 0x08 } ctl_be_block_lun_flags; typedef enum { CTL_BE_BLOCK_NONE, CTL_BE_BLOCK_DEV, CTL_BE_BLOCK_FILE } ctl_be_block_type; struct ctl_be_block_devdata { struct cdev *cdev; struct cdevsw *csw; int dev_ref; }; struct ctl_be_block_filedata { struct ucred *cred; }; union ctl_be_block_bedata { struct ctl_be_block_devdata dev; struct ctl_be_block_filedata file; }; struct ctl_be_block_io; struct ctl_be_block_lun; typedef void (*cbb_dispatch_t)(struct ctl_be_block_lun *be_lun, struct ctl_be_block_io *beio); typedef uint64_t (*cbb_getattr_t)(struct ctl_be_block_lun *be_lun, const char *attrname); /* * Backend LUN structure. There is a 1:1 mapping between a block device * and a backend block LUN, and between a backend block LUN and a CTL LUN. */ struct ctl_be_block_lun { struct ctl_lun_create_params params; struct ctl_block_disk *disk; char lunname[32]; char *dev_path; ctl_be_block_type dev_type; struct vnode *vn; union ctl_be_block_bedata backend; cbb_dispatch_t dispatch; cbb_dispatch_t lun_flush; cbb_dispatch_t unmap; cbb_dispatch_t get_lba_status; cbb_getattr_t getattr; uma_zone_t lun_zone; uint64_t size_blocks; uint64_t size_bytes; uint32_t blocksize; int blocksize_shift; uint16_t pblockexp; uint16_t pblockoff; uint16_t ublockexp; uint16_t ublockoff; uint32_t atomicblock; uint32_t opttxferlen; struct ctl_be_block_softc *softc; struct devstat *disk_stats; ctl_be_block_lun_flags flags; STAILQ_ENTRY(ctl_be_block_lun) links; struct ctl_be_lun ctl_be_lun; struct taskqueue *io_taskqueue; struct task io_task; int num_threads; STAILQ_HEAD(, ctl_io_hdr) input_queue; STAILQ_HEAD(, ctl_io_hdr) config_read_queue; STAILQ_HEAD(, ctl_io_hdr) config_write_queue; STAILQ_HEAD(, ctl_io_hdr) datamove_queue; struct mtx_padalign io_lock; struct mtx_padalign queue_lock; }; /* * Overall softc structure for the block backend module. */ struct ctl_be_block_softc { struct mtx lock; int num_disks; STAILQ_HEAD(, ctl_block_disk) disk_list; int num_luns; STAILQ_HEAD(, ctl_be_block_lun) lun_list; }; static struct ctl_be_block_softc backend_block_softc; /* * Per-I/O information. */ struct ctl_be_block_io { union ctl_io *io; struct ctl_sg_entry sg_segs[CTLBLK_MAX_SEGS]; struct iovec xiovecs[CTLBLK_MAX_SEGS]; int bio_cmd; int num_segs; int num_bios_sent; int num_bios_done; int send_complete; int num_errors; struct bintime ds_t0; devstat_tag_type ds_tag_type; devstat_trans_flags ds_trans_type; uint64_t io_len; 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; TUNABLE_INT("kern.cam.ctl.block.num_threads", &cbb_num_threads); SYSCTL_NODE(_kern_cam_ctl, OID_AUTO, block, CTLFLAG_RD, 0, "CAM Target Layer Block Backend"); SYSCTL_INT(_kern_cam_ctl_block, OID_AUTO, num_threads, CTLFLAG_RW, &cbb_num_threads, 0, "Number of threads per backing file"); static struct ctl_be_block_io *ctl_alloc_beio(struct ctl_be_block_softc *softc); static void ctl_free_beio(struct ctl_be_block_io *beio); static void ctl_complete_beio(struct ctl_be_block_io *beio); static int ctl_be_block_move_done(union ctl_io *io); static void ctl_be_block_biodone(struct bio *bio); static void ctl_be_block_flush_file(struct ctl_be_block_lun *be_lun, struct ctl_be_block_io *beio); 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_gls_file(struct ctl_be_block_lun *be_lun, struct ctl_be_block_io *beio); static uint64_t ctl_be_block_getattr_file(struct ctl_be_block_lun *be_lun, const char *attrname); 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 uint64_t ctl_be_block_getattr_dev(struct ctl_be_block_lun *be_lun, const char *attrname); static void ctl_be_block_cr_dispatch(struct ctl_be_block_lun *be_lun, union ctl_io *io); static void ctl_be_block_cw_dispatch(struct ctl_be_block_lun *be_lun, union ctl_io *io); static void ctl_be_block_dispatch(struct ctl_be_block_lun *be_lun, union ctl_io *io); static void ctl_be_block_worker(void *context, int pending); static int ctl_be_block_submit(union ctl_io *io); static int ctl_be_block_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td); static int ctl_be_block_open_file(struct ctl_be_block_lun *be_lun, struct ctl_lun_req *req); static int ctl_be_block_open_dev(struct ctl_be_block_lun *be_lun, struct ctl_lun_req *req); static int ctl_be_block_close(struct ctl_be_block_lun *be_lun); static int ctl_be_block_open(struct ctl_be_block_softc *softc, struct ctl_be_block_lun *be_lun, struct ctl_lun_req *req); static int ctl_be_block_create(struct ctl_be_block_softc *softc, struct ctl_lun_req *req); static int ctl_be_block_rm(struct ctl_be_block_softc *softc, struct ctl_lun_req *req); static int ctl_be_block_modify_file(struct ctl_be_block_lun *be_lun, struct ctl_lun_req *req); static int ctl_be_block_modify_dev(struct ctl_be_block_lun *be_lun, struct ctl_lun_req *req); static int ctl_be_block_modify(struct ctl_be_block_softc *softc, struct ctl_lun_req *req); static void ctl_be_block_lun_shutdown(void *be_lun); static void ctl_be_block_lun_config_status(void *be_lun, ctl_lun_config_status status); static int ctl_be_block_config_write(union ctl_io *io); static int ctl_be_block_config_read(union ctl_io *io); static int ctl_be_block_lun_info(void *be_lun, struct sbuf *sb); static uint64_t ctl_be_block_lun_attr(void *be_lun, const char *attrname); int ctl_be_block_init(void); static struct ctl_backend_driver ctl_be_block_driver = { .name = "block", .flags = CTL_BE_FLAG_HAS_CONFIG, .init = ctl_be_block_init, .data_submit = ctl_be_block_submit, .data_move_done = ctl_be_block_move_done, .config_read = ctl_be_block_config_read, .config_write = ctl_be_block_config_write, .ioctl = ctl_be_block_ioctl, .lun_info = ctl_be_block_lun_info, .lun_attr = ctl_be_block_lun_attr }; MALLOC_DEFINE(M_CTLBLK, "ctlblk", "Memory used for CTL block backend"); CTL_BACKEND_DECLARE(cbb, ctl_be_block_driver); static uma_zone_t beio_zone; static struct ctl_be_block_io * ctl_alloc_beio(struct ctl_be_block_softc *softc) { struct ctl_be_block_io *beio; beio = uma_zalloc(beio_zone, M_WAITOK | M_ZERO); beio->softc = softc; return (beio); } static void ctl_free_beio(struct ctl_be_block_io *beio) { int duplicate_free; int i; duplicate_free = 0; for (i = 0; i < beio->num_segs; i++) { if (beio->sg_segs[i].addr == NULL) duplicate_free++; uma_zfree(beio->lun->lun_zone, beio->sg_segs[i].addr); beio->sg_segs[i].addr = NULL; /* For compare we had two equal S/G lists. */ if (ARGS(beio->io)->flags & CTL_LLF_COMPARE) { uma_zfree(beio->lun->lun_zone, beio->sg_segs[i + CTLBLK_HALF_SEGS].addr); beio->sg_segs[i + CTLBLK_HALF_SEGS].addr = NULL; } } if (duplicate_free > 0) { printf("%s: %d duplicate frees out of %d segments\n", __func__, duplicate_free, beio->num_segs); } uma_zfree(beio_zone, beio); } static void ctl_complete_beio(struct ctl_be_block_io *beio) { union ctl_io *io = beio->io; if (beio->beio_cont != NULL) { beio->beio_cont(beio); } else { ctl_free_beio(beio); ctl_data_submit_done(io); } } static int ctl_be_block_move_done(union ctl_io *io) { struct ctl_be_block_io *beio; struct ctl_be_block_lun *be_lun; struct ctl_lba_len_flags *lbalen; #ifdef CTL_TIME_IO struct bintime cur_bt; #endif int i; beio = (struct ctl_be_block_io *)PRIV(io)->ptr; be_lun = beio->lun; DPRINTF("entered\n"); #ifdef CTL_TIME_IO getbintime(&cur_bt); bintime_sub(&cur_bt, &io->io_hdr.dma_start_bt); bintime_add(&io->io_hdr.dma_bt, &cur_bt); io->io_hdr.num_dmas++; #endif io->scsiio.kern_rel_offset += io->scsiio.kern_data_len; /* * We set status at this point for read commands, and write * commands with errors. */ if (io->io_hdr.flags & CTL_FLAG_ABORT) { ; } else if ((io->io_hdr.port_status == 0) && ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_STATUS_NONE)) { lbalen = ARGS(beio->io); if (lbalen->flags & CTL_LLF_READ) { ctl_set_success(&io->scsiio); } else if (lbalen->flags & CTL_LLF_COMPARE) { /* We have two data blocks ready for comparison. */ for (i = 0; i < beio->num_segs; i++) { if (memcmp(beio->sg_segs[i].addr, beio->sg_segs[i + CTLBLK_HALF_SEGS].addr, beio->sg_segs[i].len) != 0) break; } if (i < beio->num_segs) ctl_set_sense(&io->scsiio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_MISCOMPARE, /*asc*/ 0x1D, /*ascq*/ 0x00, SSD_ELEM_NONE); else ctl_set_success(&io->scsiio); } } else if ((io->io_hdr.port_status != 0) && ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_STATUS_NONE || (io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS)) { /* * For hardware error sense keys, the sense key * specific value is defined to be a retry count, * but we use it to pass back an internal FETD * error code. XXX KDM Hopefully the FETD is only * using 16 bits for an error code, since that's * all the space we have in the sks field. */ ctl_set_internal_failure(&io->scsiio, /*sks_valid*/ 1, /*retry_count*/ io->io_hdr.port_status); } /* * If this is a read, or a write with errors, it is done. */ if ((beio->bio_cmd == BIO_READ) || ((io->io_hdr.flags & CTL_FLAG_ABORT) != 0) || ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_STATUS_NONE)) { ctl_complete_beio(beio); return (0); } /* * At this point, we have a write and the DMA completed * successfully. We now have to queue it to the task queue to * execute the backend I/O. That is because we do blocking * memory allocations, and in the file backing case, blocking I/O. * This move done routine is generally called in the SIM's * interrupt context, and therefore we cannot block. */ mtx_lock(&be_lun->queue_lock); /* * XXX KDM make sure that links is okay to use at this point. * Otherwise, we either need to add another field to ctl_io_hdr, * or deal with resource allocation here. */ STAILQ_INSERT_TAIL(&be_lun->datamove_queue, &io->io_hdr, links); mtx_unlock(&be_lun->queue_lock); taskqueue_enqueue(be_lun->io_taskqueue, &be_lun->io_task); return (0); } static void ctl_be_block_biodone(struct bio *bio) { struct ctl_be_block_io *beio; struct ctl_be_block_lun *be_lun; union ctl_io *io; int error; beio = bio->bio_caller1; be_lun = beio->lun; io = beio->io; DPRINTF("entered\n"); error = bio->bio_error; mtx_lock(&be_lun->io_lock); if (error != 0) beio->num_errors++; beio->num_bios_done++; /* * XXX KDM will this cause WITNESS to complain? Holding a lock * during the free might cause it to complain. */ g_destroy_bio(bio); /* * If the send complete bit isn't set, or we aren't the last I/O to * complete, then we're done. */ if ((beio->send_complete == 0) || (beio->num_bios_done < beio->num_bios_sent)) { mtx_unlock(&be_lun->io_lock); return; } /* * At this point, we've verified that we are the last I/O to * complete, so it's safe to drop the lock. */ devstat_end_transaction(beio->lun->disk_stats, beio->io_len, beio->ds_tag_type, beio->ds_trans_type, /*now*/ NULL, /*then*/&beio->ds_t0); mtx_unlock(&be_lun->io_lock); /* * If there are any errors from the backing device, we fail the * entire I/O with a medium error. */ if (beio->num_errors > 0) { if (error == EOPNOTSUPP) { ctl_set_invalid_opcode(&io->scsiio); } else if (error == ENOSPC) { ctl_set_space_alloc_fail(&io->scsiio); } else if (beio->bio_cmd == BIO_FLUSH) { /* XXX KDM is there is a better error here? */ ctl_set_internal_failure(&io->scsiio, /*sks_valid*/ 1, /*retry_count*/ 0xbad2); } else ctl_set_medium_error(&io->scsiio); ctl_complete_beio(beio); return; } /* * If this is a write, a flush, a delete or verify, 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_DELETE) || (ARGS(io)->flags & CTL_LLF_VERIFY)) { ctl_set_success(&io->scsiio); ctl_complete_beio(beio); } else { if ((ARGS(io)->flags & CTL_LLF_READ) && beio->beio_cont == NULL) ctl_set_success(&io->scsiio); #ifdef CTL_TIME_IO getbintime(&io->io_hdr.dma_start_bt); #endif ctl_datamove(io); } } static void ctl_be_block_flush_file(struct ctl_be_block_lun *be_lun, struct ctl_be_block_io *beio) { union ctl_io *io = beio->io; struct mount *mountpoint; int error, lock_flags; DPRINTF("entered\n"); binuptime(&beio->ds_t0); mtx_lock(&be_lun->io_lock); devstat_start_transaction(beio->lun->disk_stats, &beio->ds_t0); mtx_unlock(&be_lun->io_lock); (void) vn_start_write(be_lun->vn, &mountpoint, V_WAIT); if (MNT_SHARED_WRITES(mountpoint) || ((mountpoint == NULL) && MNT_SHARED_WRITES(be_lun->vn->v_mount))) lock_flags = LK_SHARED; else lock_flags = LK_EXCLUSIVE; vn_lock(be_lun->vn, lock_flags | LK_RETRY); error = VOP_FSYNC(be_lun->vn, MNT_WAIT, curthread); VOP_UNLOCK(be_lun->vn, 0); vn_finished_write(mountpoint); mtx_lock(&be_lun->io_lock); devstat_end_transaction(beio->lun->disk_stats, beio->io_len, beio->ds_tag_type, beio->ds_trans_type, /*now*/ NULL, /*then*/&beio->ds_t0); mtx_unlock(&be_lun->io_lock); if (error == 0) ctl_set_success(&io->scsiio); else { /* XXX KDM is there is a better error here? */ ctl_set_internal_failure(&io->scsiio, /*sks_valid*/ 1, /*retry_count*/ 0xbad1); } ctl_complete_beio(beio); } SDT_PROBE_DEFINE1(cbb, kernel, read, file_start, "uint64_t"); SDT_PROBE_DEFINE1(cbb, kernel, write, file_start, "uint64_t"); SDT_PROBE_DEFINE1(cbb, kernel, read, file_done,"uint64_t"); SDT_PROBE_DEFINE1(cbb, kernel, write, file_done, "uint64_t"); static void ctl_be_block_dispatch_file(struct ctl_be_block_lun *be_lun, struct ctl_be_block_io *beio) { struct ctl_be_block_filedata *file_data; union ctl_io *io; struct uio xuio; struct iovec *xiovec; int flags; int error, i; DPRINTF("entered\n"); file_data = &be_lun->backend.file; io = beio->io; flags = 0; if (ARGS(io)->flags & CTL_LLF_DPO) flags |= IO_DIRECT; if (beio->bio_cmd == BIO_WRITE && ARGS(io)->flags & CTL_LLF_FUA) flags |= IO_SYNC; bzero(&xuio, sizeof(xuio)); if (beio->bio_cmd == BIO_READ) { SDT_PROBE(cbb, kernel, read, file_start, 0, 0, 0, 0, 0); xuio.uio_rw = UIO_READ; } else { SDT_PROBE(cbb, kernel, write, file_start, 0, 0, 0, 0, 0); xuio.uio_rw = UIO_WRITE; } xuio.uio_offset = beio->io_offset; xuio.uio_resid = beio->io_len; xuio.uio_segflg = UIO_SYSSPACE; xuio.uio_iov = beio->xiovecs; xuio.uio_iovcnt = beio->num_segs; xuio.uio_td = curthread; for (i = 0, xiovec = xuio.uio_iov; i < xuio.uio_iovcnt; i++, xiovec++) { xiovec->iov_base = beio->sg_segs[i].addr; xiovec->iov_len = beio->sg_segs[i].len; } binuptime(&beio->ds_t0); mtx_lock(&be_lun->io_lock); devstat_start_transaction(beio->lun->disk_stats, &beio->ds_t0); mtx_unlock(&be_lun->io_lock); if (beio->bio_cmd == BIO_READ) { vn_lock(be_lun->vn, LK_SHARED | LK_RETRY); /* * UFS pays attention to IO_DIRECT for reads. If the * DIRECTIO option is configured into the kernel, it calls * ffs_rawread(). But that only works for single-segment * uios with user space addresses. In our case, with a * kernel uio, it still reads into the buffer cache, but it * will just try to release the buffer from the cache later * on in ffs_read(). * * ZFS does not pay attention to IO_DIRECT for reads. * * UFS does not pay attention to IO_SYNC for reads. * * ZFS pays attention to IO_SYNC (which translates into the * Solaris define FRSYNC for zfs_read()) for reads. It * attempts to sync the file before reading. * * So, to attempt to provide some barrier semantics in the * BIO_ORDERED case, set both IO_DIRECT and IO_SYNC. */ error = VOP_READ(be_lun->vn, &xuio, flags, file_data->cred); VOP_UNLOCK(be_lun->vn, 0); SDT_PROBE(cbb, kernel, read, file_done, 0, 0, 0, 0, 0); } else { struct mount *mountpoint; int lock_flags; (void)vn_start_write(be_lun->vn, &mountpoint, V_WAIT); if (MNT_SHARED_WRITES(mountpoint) || ((mountpoint == NULL) && MNT_SHARED_WRITES(be_lun->vn->v_mount))) lock_flags = LK_SHARED; else lock_flags = LK_EXCLUSIVE; vn_lock(be_lun->vn, lock_flags | LK_RETRY); /* * UFS pays attention to IO_DIRECT for writes. The write * is done asynchronously. (Normally the write would just * get put into cache. * * UFS pays attention to IO_SYNC for writes. It will * attempt to write the buffer out synchronously if that * flag is set. * * ZFS does not pay attention to IO_DIRECT for writes. * * ZFS pays attention to IO_SYNC (a.k.a. FSYNC or FRSYNC) * for writes. It will flush the transaction from the * cache before returning. * * So if we've got the BIO_ORDERED flag set, we want * IO_SYNC in either the UFS or ZFS case. */ error = VOP_WRITE(be_lun->vn, &xuio, flags, file_data->cred); VOP_UNLOCK(be_lun->vn, 0); vn_finished_write(mountpoint); SDT_PROBE(cbb, kernel, write, file_done, 0, 0, 0, 0, 0); } mtx_lock(&be_lun->io_lock); devstat_end_transaction(beio->lun->disk_stats, beio->io_len, beio->ds_tag_type, beio->ds_trans_type, /*now*/ NULL, /*then*/&beio->ds_t0); mtx_unlock(&be_lun->io_lock); /* * If we got an error, set the sense data to "MEDIUM ERROR" and * return the I/O to the user. */ if (error != 0) { char path_str[32]; ctl_scsi_path_string(io, path_str, sizeof(path_str)); printf("%s%s command returned errno %d\n", path_str, (beio->bio_cmd == BIO_READ) ? "READ" : "WRITE", error); if (error == ENOSPC) { ctl_set_space_alloc_fail(&io->scsiio); } else ctl_set_medium_error(&io->scsiio); ctl_complete_beio(beio); return; } /* * If this is a write or a verify, we're all done. * If this is a read, we can now send the data to the user. */ if ((beio->bio_cmd == BIO_WRITE) || (ARGS(io)->flags & CTL_LLF_VERIFY)) { ctl_set_success(&io->scsiio); ctl_complete_beio(beio); } else { if ((ARGS(io)->flags & CTL_LLF_READ) && beio->beio_cont == NULL) ctl_set_success(&io->scsiio); #ifdef CTL_TIME_IO getbintime(&io->io_hdr.dma_start_bt); #endif ctl_datamove(io); } } static void ctl_be_block_gls_file(struct ctl_be_block_lun *be_lun, struct ctl_be_block_io *beio) { union ctl_io *io = beio->io; struct ctl_lba_len_flags *lbalen = ARGS(io); struct scsi_get_lba_status_data *data; off_t roff, off; int error, status; DPRINTF("entered\n"); off = roff = ((off_t)lbalen->lba) << be_lun->blocksize_shift; vn_lock(be_lun->vn, LK_SHARED | LK_RETRY); error = VOP_IOCTL(be_lun->vn, FIOSEEKHOLE, &off, 0, curthread->td_ucred, curthread); if (error == 0 && off > roff) status = 0; /* mapped up to off */ else { error = VOP_IOCTL(be_lun->vn, FIOSEEKDATA, &off, 0, curthread->td_ucred, curthread); if (error == 0 && off > roff) status = 1; /* deallocated up to off */ else { status = 0; /* unknown up to the end */ off = be_lun->size_bytes; } } VOP_UNLOCK(be_lun->vn, 0); off >>= be_lun->blocksize_shift; data = (struct scsi_get_lba_status_data *)io->scsiio.kern_data_ptr; scsi_u64to8b(lbalen->lba, data->descr[0].addr); scsi_ulto4b(MIN(UINT32_MAX, off - lbalen->lba), data->descr[0].length); data->descr[0].status = status; ctl_complete_beio(beio); } static uint64_t ctl_be_block_getattr_file(struct ctl_be_block_lun *be_lun, const char *attrname) { struct vattr vattr; struct statfs statfs; int error; if (be_lun->vn == NULL) return (UINT64_MAX); if (strcmp(attrname, "blocksused") == 0) { error = VOP_GETATTR(be_lun->vn, &vattr, curthread->td_ucred); if (error != 0) return (UINT64_MAX); return (vattr.va_bytes >> be_lun->blocksize_shift); } if (strcmp(attrname, "blocksavail") == 0) { error = VFS_STATFS(be_lun->vn->v_mount, &statfs); if (error != 0) return (UINT64_MAX); return ((statfs.f_bavail * statfs.f_bsize) >> be_lun->blocksize_shift); } return (UINT64_MAX); } static void ctl_be_block_dispatch_zvol(struct ctl_be_block_lun *be_lun, struct ctl_be_block_io *beio) { struct ctl_be_block_devdata *dev_data; union ctl_io *io; struct uio xuio; struct iovec *xiovec; int flags; int error, i; DPRINTF("entered\n"); dev_data = &be_lun->backend.dev; io = beio->io; flags = 0; if (ARGS(io)->flags & CTL_LLF_DPO) flags |= IO_DIRECT; if (beio->bio_cmd == BIO_WRITE && ARGS(io)->flags & CTL_LLF_FUA) flags |= IO_SYNC; bzero(&xuio, sizeof(xuio)); if (beio->bio_cmd == BIO_READ) { SDT_PROBE(cbb, kernel, read, file_start, 0, 0, 0, 0, 0); xuio.uio_rw = UIO_READ; } else { SDT_PROBE(cbb, kernel, write, file_start, 0, 0, 0, 0, 0); xuio.uio_rw = UIO_WRITE; } xuio.uio_offset = beio->io_offset; xuio.uio_resid = beio->io_len; xuio.uio_segflg = UIO_SYSSPACE; xuio.uio_iov = beio->xiovecs; xuio.uio_iovcnt = beio->num_segs; xuio.uio_td = curthread; for (i = 0, xiovec = xuio.uio_iov; i < xuio.uio_iovcnt; i++, xiovec++) { xiovec->iov_base = beio->sg_segs[i].addr; xiovec->iov_len = beio->sg_segs[i].len; } binuptime(&beio->ds_t0); mtx_lock(&be_lun->io_lock); devstat_start_transaction(beio->lun->disk_stats, &beio->ds_t0); mtx_unlock(&be_lun->io_lock); if (beio->bio_cmd == BIO_READ) { error = (*dev_data->csw->d_read)(dev_data->cdev, &xuio, flags); SDT_PROBE(cbb, kernel, read, file_done, 0, 0, 0, 0, 0); } else { error = (*dev_data->csw->d_write)(dev_data->cdev, &xuio, flags); SDT_PROBE(cbb, kernel, write, file_done, 0, 0, 0, 0, 0); } mtx_lock(&be_lun->io_lock); devstat_end_transaction(beio->lun->disk_stats, beio->io_len, beio->ds_tag_type, beio->ds_trans_type, /*now*/ NULL, /*then*/&beio->ds_t0); mtx_unlock(&be_lun->io_lock); /* * If we got an error, set the sense data to "MEDIUM ERROR" and * return the I/O to the user. */ if (error != 0) { if (error == ENOSPC) { ctl_set_space_alloc_fail(&io->scsiio); } else ctl_set_medium_error(&io->scsiio); ctl_complete_beio(beio); return; } /* * If this is a write or a verify, we're all done. * If this is a read, we can now send the data to the user. */ if ((beio->bio_cmd == BIO_WRITE) || (ARGS(io)->flags & CTL_LLF_VERIFY)) { ctl_set_success(&io->scsiio); ctl_complete_beio(beio); } else { if ((ARGS(io)->flags & CTL_LLF_READ) && beio->beio_cont == NULL) ctl_set_success(&io->scsiio); #ifdef CTL_TIME_IO getbintime(&io->io_hdr.dma_start_bt); #endif ctl_datamove(io); } } static void ctl_be_block_gls_zvol(struct ctl_be_block_lun *be_lun, struct ctl_be_block_io *beio) { struct ctl_be_block_devdata *dev_data = &be_lun->backend.dev; union ctl_io *io = beio->io; struct ctl_lba_len_flags *lbalen = ARGS(io); struct scsi_get_lba_status_data *data; off_t roff, off; int error, status; DPRINTF("entered\n"); off = roff = ((off_t)lbalen->lba) << be_lun->blocksize_shift; error = (*dev_data->csw->d_ioctl)(dev_data->cdev, FIOSEEKHOLE, (caddr_t)&off, FREAD, curthread); if (error == 0 && off > roff) status = 0; /* mapped up to off */ else { error = (*dev_data->csw->d_ioctl)(dev_data->cdev, FIOSEEKDATA, (caddr_t)&off, FREAD, curthread); if (error == 0 && off > roff) status = 1; /* deallocated up to off */ else { status = 0; /* unknown up to the end */ off = be_lun->size_bytes; } } off >>= be_lun->blocksize_shift; data = (struct scsi_get_lba_status_data *)io->scsiio.kern_data_ptr; scsi_u64to8b(lbalen->lba, data->descr[0].addr); scsi_ulto4b(MIN(UINT32_MAX, off - lbalen->lba), data->descr[0].length); data->descr[0].status = status; ctl_complete_beio(beio); } static void ctl_be_block_flush_dev(struct ctl_be_block_lun *be_lun, struct ctl_be_block_io *beio) { struct bio *bio; union ctl_io *io; struct ctl_be_block_devdata *dev_data; dev_data = &be_lun->backend.dev; io = beio->io; DPRINTF("entered\n"); /* This can't fail, it's a blocking allocation. */ bio = g_alloc_bio(); bio->bio_cmd = BIO_FLUSH; bio->bio_flags |= BIO_ORDERED; bio->bio_dev = dev_data->cdev; bio->bio_offset = 0; bio->bio_data = 0; bio->bio_done = ctl_be_block_biodone; bio->bio_caller1 = beio; bio->bio_pblkno = 0; /* * We don't need to acquire the LUN lock here, because we are only * sending one bio, and so there is no other context to synchronize * with. */ beio->num_bios_sent = 1; beio->send_complete = 1; binuptime(&beio->ds_t0); mtx_lock(&be_lun->io_lock); devstat_start_transaction(be_lun->disk_stats, &beio->ds_t0); mtx_unlock(&be_lun->io_lock); (*dev_data->csw->d_strategy)(bio); } 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; uint64_t maxlen; dev_data = &be_lun->backend.dev; maxlen = LONG_MAX - (LONG_MAX % be_lun->blocksize); while (len > 0) { bio = g_alloc_bio(); bio->bio_cmd = BIO_DELETE; bio->bio_dev = dev_data->cdev; bio->bio_offset = off; bio->bio_length = MIN(len, maxlen); bio->bio_data = 0; bio->bio_done = ctl_be_block_biodone; bio->bio_caller1 = beio; bio->bio_pblkno = off / be_lun->blocksize; off += bio->bio_length; len -= bio->bio_length; mtx_lock(&be_lun->io_lock); beio->num_bios_sent++; if (last && len == 0) beio->send_complete = 1; mtx_unlock(&be_lun->io_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); mtx_lock(&be_lun->io_lock); devstat_start_transaction(be_lun->disk_stats, &beio->ds_t0); mtx_unlock(&be_lun->io_lock); if (beio->io_offset == -1) { beio->io_len = 0; ptrlen = (struct ctl_ptr_len_flags *)&io->io_hdr.ctl_private[CTL_PRIV_LBA_LEN]; 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 < 2) ? 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) { TAILQ_HEAD(, bio) queue = TAILQ_HEAD_INITIALIZER(queue); int i; struct bio *bio; struct ctl_be_block_devdata *dev_data; off_t cur_offset; int max_iosize; DPRINTF("entered\n"); dev_data = &be_lun->backend.dev; /* * We have to limit our I/O size to the maximum supported by the * backend device. Hopefully it is MAXPHYS. If the driver doesn't * set it properly, use DFLTPHYS. */ max_iosize = dev_data->cdev->si_iosize_max; if (max_iosize < PAGE_SIZE) max_iosize = DFLTPHYS; cur_offset = beio->io_offset; for (i = 0; i < beio->num_segs; i++) { size_t cur_size; uint8_t *cur_ptr; cur_size = beio->sg_segs[i].len; cur_ptr = beio->sg_segs[i].addr; while (cur_size > 0) { /* This can't fail, it's a blocking allocation. */ bio = g_alloc_bio(); KASSERT(bio != NULL, ("g_alloc_bio() failed!\n")); bio->bio_cmd = beio->bio_cmd; bio->bio_dev = dev_data->cdev; bio->bio_caller1 = beio; bio->bio_length = min(cur_size, max_iosize); bio->bio_offset = cur_offset; bio->bio_data = cur_ptr; bio->bio_done = ctl_be_block_biodone; bio->bio_pblkno = cur_offset / be_lun->blocksize; cur_offset += bio->bio_length; cur_ptr += bio->bio_length; cur_size -= bio->bio_length; TAILQ_INSERT_TAIL(&queue, bio, bio_queue); beio->num_bios_sent++; } } binuptime(&beio->ds_t0); mtx_lock(&be_lun->io_lock); devstat_start_transaction(be_lun->disk_stats, &beio->ds_t0); beio->send_complete = 1; mtx_unlock(&be_lun->io_lock); /* * Fire off all allocated requests! */ while ((bio = TAILQ_FIRST(&queue)) != NULL) { TAILQ_REMOVE(&queue, bio, bio_queue); (*dev_data->csw->d_strategy)(bio); } } static uint64_t ctl_be_block_getattr_dev(struct ctl_be_block_lun *be_lun, const char *attrname) { struct ctl_be_block_devdata *dev_data = &be_lun->backend.dev; struct diocgattr_arg arg; int error; if (dev_data->csw == NULL || dev_data->csw->d_ioctl == NULL) return (UINT64_MAX); strlcpy(arg.name, attrname, sizeof(arg.name)); arg.len = sizeof(arg.value.off); error = dev_data->csw->d_ioctl(dev_data->cdev, DIOCGATTR, (caddr_t)&arg, FREAD, curthread); if (error != 0) return (UINT64_MAX); return (arg.value.off); } 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.flags & CTL_FLAG_ABORT) || ((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, pb, pbo, adj; + uint64_t len_left, lba; + uint32_t pb, pbo, adj; int i, seglen; uint8_t *buf, *end; DPRINTF("entered\n"); beio = (struct ctl_be_block_io *)PRIV(io)->ptr; softc = be_lun->softc; lbalen = ARGS(beio->io); if (lbalen->flags & ~(SWS_LBDATA | SWS_UNMAP | SWS_ANCHOR | SWS_NDOB) || (lbalen->flags & (SWS_UNMAP | SWS_ANCHOR) && 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; } 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 | SWS_ANCHOR)) { 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); - pb = (uint64_t)be_lun->blocksize << be_lun->pblockexp; - pbo = pb - (uint64_t)be_lun->blocksize * be_lun->pblockoff; + pb = be_lun->blocksize << be_lun->pblockexp; + if (be_lun->pblockoff > 0) + pbo = pb - be_lun->blocksize * be_lun->pblockoff; + else + pbo = 0; 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(CTLBLK_MAX_SEG, len_left); if (pb > be_lun->blocksize) { adj = ((lbalen->lba + lba) * be_lun->blocksize + seglen - pbo) % pb; if (seglen > adj) seglen -= adj; else seglen -= seglen % be_lun->blocksize; } else 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; 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 = (struct ctl_be_block_io *)PRIV(io)->ptr; softc = be_lun->softc; ptrlen = (struct ctl_ptr_len_flags *)&io->io_hdr.ctl_private[CTL_PRIV_LBA_LEN]; if ((ptrlen->flags & ~SU_ANCHOR) != 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; } 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("UNMAP\n"); be_lun->unmap(be_lun, beio); } static void ctl_be_block_cr_done(struct ctl_be_block_io *beio) { union ctl_io *io; io = beio->io; ctl_free_beio(beio); ctl_config_read_done(io); } static void ctl_be_block_cr_dispatch(struct ctl_be_block_lun *be_lun, union ctl_io *io) { struct ctl_be_block_io *beio; struct ctl_be_block_softc *softc; DPRINTF("entered\n"); softc = be_lun->softc; beio = ctl_alloc_beio(softc); beio->io = io; beio->lun = be_lun; beio->beio_cont = ctl_be_block_cr_done; PRIV(io)->ptr = (void *)beio; switch (io->scsiio.cdb[0]) { case SERVICE_ACTION_IN: /* GET LBA STATUS */ beio->bio_cmd = -1; beio->ds_trans_type = DEVSTAT_NO_DATA; beio->ds_tag_type = DEVSTAT_TAG_ORDERED; beio->io_len = 0; if (be_lun->get_lba_status) be_lun->get_lba_status(be_lun, beio); else ctl_be_block_cr_done(beio); break; default: panic("Unhandled CDB type %#x", io->scsiio.cdb[0]); break; } } 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) { struct ctl_be_block_io *beio; struct ctl_be_block_softc *softc; DPRINTF("entered\n"); softc = be_lun->softc; beio = ctl_alloc_beio(softc); beio->io = io; beio->lun = be_lun; beio->beio_cont = ctl_be_block_cw_done; PRIV(io)->ptr = (void *)beio; switch (io->scsiio.cdb[0]) { case SYNCHRONIZE_CACHE: case SYNCHRONIZE_CACHE_16: beio->bio_cmd = BIO_FLUSH; beio->ds_trans_type = DEVSTAT_NO_DATA; beio->ds_tag_type = DEVSTAT_TAG_ORDERED; 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; } } SDT_PROBE_DEFINE1(cbb, kernel, read, start, "uint64_t"); SDT_PROBE_DEFINE1(cbb, kernel, write, start, "uint64_t"); SDT_PROBE_DEFINE1(cbb, kernel, read, alloc_done, "uint64_t"); SDT_PROBE_DEFINE1(cbb, kernel, write, alloc_done, "uint64_t"); static void ctl_be_block_next(struct ctl_be_block_io *beio) { struct ctl_be_block_lun *be_lun; union ctl_io *io; io = beio->io; be_lun = beio->lun; ctl_free_beio(beio); if ((io->io_hdr.flags & CTL_FLAG_ABORT) || ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_STATUS_NONE && (io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS)) { ctl_data_submit_done(io); return; } io->io_hdr.status &= ~CTL_STATUS_MASK; io->io_hdr.status |= CTL_STATUS_NONE; mtx_lock(&be_lun->queue_lock); /* * XXX KDM make sure that links is okay to use at this point. * Otherwise, we either need to add another field to ctl_io_hdr, * or deal with resource allocation here. */ STAILQ_INSERT_TAIL(&be_lun->input_queue, &io->io_hdr, links); mtx_unlock(&be_lun->queue_lock); taskqueue_enqueue(be_lun->io_taskqueue, &be_lun->io_task); } static void ctl_be_block_dispatch(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; struct ctl_ptr_len_flags *bptrlen; uint64_t len_left, lbas; int i; softc = be_lun->softc; DPRINTF("entered\n"); lbalen = ARGS(io); if (lbalen->flags & CTL_LLF_WRITE) { SDT_PROBE(cbb, kernel, write, start, 0, 0, 0, 0, 0); } else { SDT_PROBE(cbb, kernel, read, start, 0, 0, 0, 0, 0); } beio = ctl_alloc_beio(softc); beio->io = io; beio->lun = be_lun; bptrlen = PRIV(io); bptrlen->ptr = (void *)beio; 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 & CTL_LLF_WRITE) { beio->bio_cmd = BIO_WRITE; beio->ds_trans_type = DEVSTAT_WRITE; } else { beio->bio_cmd = BIO_READ; beio->ds_trans_type = DEVSTAT_READ; } DPRINTF("%s at LBA %jx len %u @%ju\n", (beio->bio_cmd == BIO_READ) ? "READ" : "WRITE", (uintmax_t)lbalen->lba, lbalen->len, bptrlen->len); if (lbalen->flags & CTL_LLF_COMPARE) lbas = CTLBLK_HALF_IO_SIZE; else lbas = CTLBLK_MAX_IO_SIZE; lbas = MIN(lbalen->len - bptrlen->len, lbas / be_lun->blocksize); beio->io_offset = (lbalen->lba + bptrlen->len) * be_lun->blocksize; beio->io_len = lbas * be_lun->blocksize; bptrlen->len += lbas; for (i = 0, len_left = beio->io_len; len_left > 0; i++) { KASSERT(i < CTLBLK_MAX_SEGS, ("Too many segs (%d >= %d)", i, CTLBLK_MAX_SEGS)); /* * Setup the S/G entry for this chunk. */ beio->sg_segs[i].len = min(CTLBLK_MAX_SEG, len_left); 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); /* Set up second segment for compare operation. */ if (lbalen->flags & CTL_LLF_COMPARE) { beio->sg_segs[i + CTLBLK_HALF_SEGS].len = beio->sg_segs[i].len; beio->sg_segs[i + CTLBLK_HALF_SEGS].addr = uma_zalloc(be_lun->lun_zone, M_WAITOK); } beio->num_segs++; len_left -= beio->sg_segs[i].len; } if (bptrlen->len < lbalen->len) beio->beio_cont = ctl_be_block_next; io->scsiio.be_move_done = ctl_be_block_move_done; /* For compare we have separate S/G lists for read and datamove. */ if (lbalen->flags & CTL_LLF_COMPARE) io->scsiio.kern_data_ptr = (uint8_t *)&beio->sg_segs[CTLBLK_HALF_SEGS]; else io->scsiio.kern_data_ptr = (uint8_t *)beio->sg_segs; io->scsiio.kern_data_len = beio->io_len; io->scsiio.kern_data_resid = 0; io->scsiio.kern_sg_entries = beio->num_segs; io->io_hdr.flags |= CTL_FLAG_ALLOCATED | CTL_FLAG_KDPTR_SGLIST; /* * For the read case, we need to read the data into our buffers and * then we can send it back to the user. For the write case, we * need to get the data from the user first. */ if (beio->bio_cmd == BIO_READ) { SDT_PROBE(cbb, kernel, read, alloc_done, 0, 0, 0, 0, 0); be_lun->dispatch(be_lun, beio); } else { SDT_PROBE(cbb, kernel, write, alloc_done, 0, 0, 0, 0, 0); #ifdef CTL_TIME_IO getbintime(&io->io_hdr.dma_start_bt); #endif ctl_datamove(io); } } static void ctl_be_block_worker(void *context, int pending) { struct ctl_be_block_lun *be_lun; struct ctl_be_block_softc *softc; union ctl_io *io; be_lun = (struct ctl_be_block_lun *)context; softc = be_lun->softc; DPRINTF("entered\n"); mtx_lock(&be_lun->queue_lock); for (;;) { io = (union ctl_io *)STAILQ_FIRST(&be_lun->datamove_queue); if (io != NULL) { struct ctl_be_block_io *beio; DPRINTF("datamove queue\n"); STAILQ_REMOVE(&be_lun->datamove_queue, &io->io_hdr, ctl_io_hdr, links); mtx_unlock(&be_lun->queue_lock); beio = (struct ctl_be_block_io *)PRIV(io)->ptr; be_lun->dispatch(be_lun, beio); mtx_lock(&be_lun->queue_lock); continue; } io = (union ctl_io *)STAILQ_FIRST(&be_lun->config_write_queue); if (io != NULL) { DPRINTF("config write queue\n"); STAILQ_REMOVE(&be_lun->config_write_queue, &io->io_hdr, ctl_io_hdr, links); mtx_unlock(&be_lun->queue_lock); ctl_be_block_cw_dispatch(be_lun, io); mtx_lock(&be_lun->queue_lock); continue; } io = (union ctl_io *)STAILQ_FIRST(&be_lun->config_read_queue); if (io != NULL) { DPRINTF("config read queue\n"); STAILQ_REMOVE(&be_lun->config_read_queue, &io->io_hdr, ctl_io_hdr, links); mtx_unlock(&be_lun->queue_lock); ctl_be_block_cr_dispatch(be_lun, io); mtx_lock(&be_lun->queue_lock); continue; } io = (union ctl_io *)STAILQ_FIRST(&be_lun->input_queue); if (io != NULL) { DPRINTF("input queue\n"); STAILQ_REMOVE(&be_lun->input_queue, &io->io_hdr, ctl_io_hdr, links); mtx_unlock(&be_lun->queue_lock); /* * We must drop the lock, since this routine and * its children may sleep. */ ctl_be_block_dispatch(be_lun, io); mtx_lock(&be_lun->queue_lock); continue; } /* * If we get here, there is no work left in the queues, so * just break out and let the task queue go to sleep. */ break; } mtx_unlock(&be_lun->queue_lock); } /* * Entry point from CTL to the backend for I/O. We queue everything to a * work thread, so this just puts the I/O on a queue and wakes up the * thread. */ static int ctl_be_block_submit(union ctl_io *io) { struct ctl_be_block_lun *be_lun; struct ctl_be_lun *ctl_be_lun; DPRINTF("entered\n"); ctl_be_lun = (struct ctl_be_lun *)io->io_hdr.ctl_private[ CTL_PRIV_BACKEND_LUN].ptr; be_lun = (struct ctl_be_block_lun *)ctl_be_lun->be_lun; /* * Make sure we only get SCSI I/O. */ KASSERT(io->io_hdr.io_type == CTL_IO_SCSI, ("Non-SCSI I/O (type " "%#x) encountered", io->io_hdr.io_type)); PRIV(io)->len = 0; mtx_lock(&be_lun->queue_lock); /* * XXX KDM make sure that links is okay to use at this point. * Otherwise, we either need to add another field to ctl_io_hdr, * or deal with resource allocation here. */ STAILQ_INSERT_TAIL(&be_lun->input_queue, &io->io_hdr, links); mtx_unlock(&be_lun->queue_lock); taskqueue_enqueue(be_lun->io_taskqueue, &be_lun->io_task); return (CTL_RETVAL_COMPLETE); } static int ctl_be_block_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { struct ctl_be_block_softc *softc; int error; softc = &backend_block_softc; error = 0; switch (cmd) { case CTL_LUN_REQ: { struct ctl_lun_req *lun_req; lun_req = (struct ctl_lun_req *)addr; switch (lun_req->reqtype) { case CTL_LUNREQ_CREATE: error = ctl_be_block_create(softc, lun_req); break; case CTL_LUNREQ_RM: error = ctl_be_block_rm(softc, lun_req); break; case CTL_LUNREQ_MODIFY: error = ctl_be_block_modify(softc, lun_req); break; default: lun_req->status = CTL_LUN_ERROR; snprintf(lun_req->error_str, sizeof(lun_req->error_str), "invalid LUN request type %d", lun_req->reqtype); break; } break; } default: error = ENOTTY; break; } return (error); } static int ctl_be_block_open_file(struct ctl_be_block_lun *be_lun, struct ctl_lun_req *req) { struct ctl_be_block_filedata *file_data; struct ctl_lun_create_params *params; char *value; struct vattr vattr; off_t ps, pss, po, pos, us, uss, uo, uos; int error; error = 0; file_data = &be_lun->backend.file; params = &be_lun->params; be_lun->dev_type = CTL_BE_BLOCK_FILE; be_lun->dispatch = ctl_be_block_dispatch_file; be_lun->lun_flush = ctl_be_block_flush_file; be_lun->get_lba_status = ctl_be_block_gls_file; be_lun->getattr = ctl_be_block_getattr_file; error = VOP_GETATTR(be_lun->vn, &vattr, curthread->td_ucred); if (error != 0) { snprintf(req->error_str, sizeof(req->error_str), "error calling VOP_GETATTR() for file %s", be_lun->dev_path); return (error); } /* * Verify that we have the ability to upgrade to exclusive * access on this file so we can trap errors at open instead * of reporting them during first access. */ if (VOP_ISLOCKED(be_lun->vn) != LK_EXCLUSIVE) { vn_lock(be_lun->vn, LK_UPGRADE | LK_RETRY); if (be_lun->vn->v_iflag & VI_DOOMED) { error = EBADF; snprintf(req->error_str, sizeof(req->error_str), "error locking file %s", be_lun->dev_path); return (error); } } file_data->cred = crhold(curthread->td_ucred); if (params->lun_size_bytes != 0) be_lun->size_bytes = params->lun_size_bytes; else be_lun->size_bytes = vattr.va_size; /* * We set the multi thread flag for file operations because all * filesystems (in theory) are capable of allowing multiple readers * of a file at once. So we want to get the maximum possible * concurrency. */ be_lun->flags |= CTL_BE_BLOCK_LUN_MULTI_THREAD; /* * For files we can use any logical block size. Prefer 512 bytes * for compatibility reasons. If file's vattr.va_blocksize * (preferred I/O block size) is bigger and multiple to chosen * logical block size -- report it as physical block size. */ if (params->blocksize_bytes != 0) be_lun->blocksize = params->blocksize_bytes; else be_lun->blocksize = 512; us = ps = vattr.va_blocksize; uo = po = 0; value = ctl_get_opt(&be_lun->ctl_be_lun.options, "pblocksize"); if (value != NULL) ctl_expand_number(value, &ps); value = ctl_get_opt(&be_lun->ctl_be_lun.options, "pblockoffset"); if (value != NULL) ctl_expand_number(value, &po); pss = ps / be_lun->blocksize; pos = po / be_lun->blocksize; if ((pss > 0) && (pss * be_lun->blocksize == ps) && (pss >= pos) && ((pss & (pss - 1)) == 0) && (pos * be_lun->blocksize == po)) { be_lun->pblockexp = fls(pss) - 1; be_lun->pblockoff = (pss - pos) % pss; } value = ctl_get_opt(&be_lun->ctl_be_lun.options, "ublocksize"); if (value != NULL) ctl_expand_number(value, &us); value = ctl_get_opt(&be_lun->ctl_be_lun.options, "ublockoffset"); if (value != NULL) ctl_expand_number(value, &uo); uss = us / be_lun->blocksize; uos = uo / be_lun->blocksize; if ((uss > 0) && (uss * be_lun->blocksize == us) && (uss >= uos) && ((uss & (uss - 1)) == 0) && (uos * be_lun->blocksize == uo)) { be_lun->ublockexp = fls(uss) - 1; be_lun->ublockoff = (uss - uos) % uss; } /* * Sanity check. The media size has to be at least one * sector long. */ if (be_lun->size_bytes < be_lun->blocksize) { error = EINVAL; snprintf(req->error_str, sizeof(req->error_str), "file %s size %ju < block size %u", be_lun->dev_path, (uintmax_t)be_lun->size_bytes, be_lun->blocksize); } be_lun->opttxferlen = CTLBLK_MAX_IO_SIZE / be_lun->blocksize; return (error); } static int ctl_be_block_open_dev(struct ctl_be_block_lun *be_lun, struct ctl_lun_req *req) { struct ctl_lun_create_params *params; struct vattr vattr; struct cdev *dev; struct cdevsw *devsw; char *value; int error, atomic, maxio; off_t ps, pss, po, pos, us, uss, uo, uos; params = &be_lun->params; be_lun->dev_type = CTL_BE_BLOCK_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); if (be_lun->backend.dev.csw == NULL) panic("Unable to retrieve device switch"); if (strcmp(be_lun->backend.dev.csw->d_name, "zvol") == 0) { be_lun->dispatch = ctl_be_block_dispatch_zvol; be_lun->get_lba_status = ctl_be_block_gls_zvol; atomic = maxio = CTLBLK_MAX_IO_SIZE; } else { be_lun->dispatch = ctl_be_block_dispatch_dev; atomic = 0; maxio = be_lun->backend.dev.cdev->si_iosize_max; if (maxio <= 0) maxio = DFLTPHYS; if (maxio > CTLBLK_MAX_IO_SIZE) maxio = CTLBLK_MAX_IO_SIZE; } be_lun->lun_flush = ctl_be_block_flush_dev; be_lun->unmap = ctl_be_block_unmap_dev; be_lun->getattr = ctl_be_block_getattr_dev; error = VOP_GETATTR(be_lun->vn, &vattr, NOCRED); if (error) { snprintf(req->error_str, sizeof(req->error_str), "error getting vnode attributes for device %s", be_lun->dev_path); return (error); } dev = be_lun->vn->v_rdev; devsw = dev->si_devsw; if (!devsw->d_ioctl) { snprintf(req->error_str, sizeof(req->error_str), "no d_ioctl for device %s!", be_lun->dev_path); return (ENODEV); } error = devsw->d_ioctl(dev, DIOCGSECTORSIZE, (caddr_t)&be_lun->blocksize, FREAD, curthread); if (error) { snprintf(req->error_str, sizeof(req->error_str), "error %d returned for DIOCGSECTORSIZE ioctl " "on %s!", error, be_lun->dev_path); return (error); } /* * If the user has asked for a blocksize that is greater than the * backing device's blocksize, we can do it only if the blocksize * the user is asking for is an even multiple of the underlying * device's blocksize. */ if ((params->blocksize_bytes != 0) && (params->blocksize_bytes > be_lun->blocksize)) { uint32_t bs_multiple, tmp_blocksize; bs_multiple = params->blocksize_bytes / be_lun->blocksize; tmp_blocksize = bs_multiple * be_lun->blocksize; if (tmp_blocksize == params->blocksize_bytes) { be_lun->blocksize = params->blocksize_bytes; } else { snprintf(req->error_str, sizeof(req->error_str), "requested blocksize %u is not an even " "multiple of backing device blocksize %u", params->blocksize_bytes, be_lun->blocksize); return (EINVAL); } } else if ((params->blocksize_bytes != 0) && (params->blocksize_bytes != be_lun->blocksize)) { snprintf(req->error_str, sizeof(req->error_str), "requested blocksize %u < backing device " "blocksize %u", params->blocksize_bytes, be_lun->blocksize); return (EINVAL); } error = devsw->d_ioctl(dev, DIOCGMEDIASIZE, (caddr_t)&be_lun->size_bytes, FREAD, curthread); if (error) { snprintf(req->error_str, sizeof(req->error_str), "error %d returned for DIOCGMEDIASIZE " " ioctl on %s!", error, be_lun->dev_path); return (error); } if (params->lun_size_bytes != 0) { if (params->lun_size_bytes > be_lun->size_bytes) { snprintf(req->error_str, sizeof(req->error_str), "requested LUN size %ju > backing device " "size %ju", (uintmax_t)params->lun_size_bytes, (uintmax_t)be_lun->size_bytes); return (EINVAL); } be_lun->size_bytes = params->lun_size_bytes; } error = devsw->d_ioctl(dev, DIOCGSTRIPESIZE, (caddr_t)&ps, FREAD, curthread); if (error) ps = po = 0; else { error = devsw->d_ioctl(dev, DIOCGSTRIPEOFFSET, (caddr_t)&po, FREAD, curthread); if (error) po = 0; } us = ps; uo = po; value = ctl_get_opt(&be_lun->ctl_be_lun.options, "pblocksize"); if (value != NULL) ctl_expand_number(value, &ps); value = ctl_get_opt(&be_lun->ctl_be_lun.options, "pblockoffset"); if (value != NULL) ctl_expand_number(value, &po); pss = ps / be_lun->blocksize; pos = po / be_lun->blocksize; if ((pss > 0) && (pss * be_lun->blocksize == ps) && (pss >= pos) && ((pss & (pss - 1)) == 0) && (pos * be_lun->blocksize == po)) { be_lun->pblockexp = fls(pss) - 1; be_lun->pblockoff = (pss - pos) % pss; } value = ctl_get_opt(&be_lun->ctl_be_lun.options, "ublocksize"); if (value != NULL) ctl_expand_number(value, &us); value = ctl_get_opt(&be_lun->ctl_be_lun.options, "ublockoffset"); if (value != NULL) ctl_expand_number(value, &uo); uss = us / be_lun->blocksize; uos = uo / be_lun->blocksize; if ((uss > 0) && (uss * be_lun->blocksize == us) && (uss >= uos) && ((uss & (uss - 1)) == 0) && (uos * be_lun->blocksize == uo)) { be_lun->ublockexp = fls(uss) - 1; be_lun->ublockoff = (uss - uos) % uss; } be_lun->atomicblock = atomic / be_lun->blocksize; be_lun->opttxferlen = maxio / be_lun->blocksize; return (0); } static int ctl_be_block_close(struct ctl_be_block_lun *be_lun) { DROP_GIANT(); if (be_lun->vn) { int flags = FREAD | FWRITE; switch (be_lun->dev_type) { case CTL_BE_BLOCK_DEV: if (be_lun->backend.dev.csw) { dev_relthread(be_lun->backend.dev.cdev, be_lun->backend.dev.dev_ref); be_lun->backend.dev.csw = NULL; be_lun->backend.dev.cdev = NULL; } break; case CTL_BE_BLOCK_FILE: break; case CTL_BE_BLOCK_NONE: break; default: panic("Unexpected backend type."); break; } (void)vn_close(be_lun->vn, flags, NOCRED, curthread); be_lun->vn = NULL; switch (be_lun->dev_type) { case CTL_BE_BLOCK_DEV: break; case CTL_BE_BLOCK_FILE: if (be_lun->backend.file.cred != NULL) { crfree(be_lun->backend.file.cred); be_lun->backend.file.cred = NULL; } break; case CTL_BE_BLOCK_NONE: break; default: panic("Unexpected backend type."); break; } be_lun->dev_type = CTL_BE_BLOCK_NONE; } PICKUP_GIANT(); return (0); } static int ctl_be_block_open(struct ctl_be_block_softc *softc, struct ctl_be_block_lun *be_lun, struct ctl_lun_req *req) { struct nameidata nd; int flags; int error; /* * XXX KDM allow a read-only option? */ flags = FREAD | FWRITE; error = 0; if (rootvnode == NULL) { snprintf(req->error_str, sizeof(req->error_str), "Root filesystem is not mounted"); return (1); } if (!curthread->td_proc->p_fd->fd_cdir) { curthread->td_proc->p_fd->fd_cdir = rootvnode; VREF(rootvnode); } if (!curthread->td_proc->p_fd->fd_rdir) { curthread->td_proc->p_fd->fd_rdir = rootvnode; VREF(rootvnode); } if (!curthread->td_proc->p_fd->fd_jdir) { curthread->td_proc->p_fd->fd_jdir = rootvnode; VREF(rootvnode); } again: NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, be_lun->dev_path, curthread); error = vn_open(&nd, &flags, 0, NULL); if (error) { /* * This is the only reasonable guess we can make as far as * path if the user doesn't give us a fully qualified path. * If they want to specify a file, they need to specify the * full path. */ if (be_lun->dev_path[0] != '/') { char *dev_path = "/dev/"; char *dev_name; /* Try adding device path at beginning of name */ dev_name = malloc(strlen(be_lun->dev_path) + strlen(dev_path) + 1, M_CTLBLK, M_WAITOK); if (dev_name) { sprintf(dev_name, "%s%s", dev_path, be_lun->dev_path); free(be_lun->dev_path, M_CTLBLK); be_lun->dev_path = dev_name; goto again; } } snprintf(req->error_str, sizeof(req->error_str), "error opening %s: %d", be_lun->dev_path, error); return (error); } NDFREE(&nd, NDF_ONLY_PNBUF); be_lun->vn = nd.ni_vp; /* We only support disks and files. */ if (vn_isdisk(be_lun->vn, &error)) { error = ctl_be_block_open_dev(be_lun, req); } else if (be_lun->vn->v_type == VREG) { error = ctl_be_block_open_file(be_lun, req); } else { error = EINVAL; snprintf(req->error_str, sizeof(req->error_str), "%s is not a disk or plain file", be_lun->dev_path); } VOP_UNLOCK(be_lun->vn, 0); if (error != 0) { ctl_be_block_close(be_lun); return (error); } be_lun->blocksize_shift = fls(be_lun->blocksize) - 1; be_lun->size_blocks = be_lun->size_bytes >> be_lun->blocksize_shift; return (0); } static int ctl_be_block_create(struct ctl_be_block_softc *softc, struct ctl_lun_req *req) { struct ctl_be_block_lun *be_lun; struct ctl_lun_create_params *params; char num_thread_str[16]; char tmpstr[32]; char *value; int retval, num_threads, unmap; int tmp_num_threads; params = &req->reqdata.create; retval = 0; req->status = CTL_LUN_OK; num_threads = cbb_num_threads; be_lun = malloc(sizeof(*be_lun), M_CTLBLK, M_ZERO | M_WAITOK); be_lun->params = req->reqdata.create; be_lun->softc = softc; STAILQ_INIT(&be_lun->input_queue); STAILQ_INIT(&be_lun->config_read_queue); STAILQ_INIT(&be_lun->config_write_queue); STAILQ_INIT(&be_lun->datamove_queue); sprintf(be_lun->lunname, "cblk%d", softc->num_luns); mtx_init(&be_lun->io_lock, "cblk io lock", NULL, MTX_DEF); mtx_init(&be_lun->queue_lock, "cblk queue lock", NULL, MTX_DEF); ctl_init_opts(&be_lun->ctl_be_lun.options, req->num_be_args, req->kern_be_args); be_lun->lun_zone = uma_zcreate(be_lun->lunname, CTLBLK_MAX_SEG, NULL, NULL, NULL, NULL, /*align*/ 0, /*flags*/0); if (be_lun->lun_zone == NULL) { snprintf(req->error_str, sizeof(req->error_str), "error allocating UMA zone"); goto bailout_error; } if (params->flags & CTL_LUN_FLAG_DEV_TYPE) be_lun->ctl_be_lun.lun_type = params->device_type; else be_lun->ctl_be_lun.lun_type = T_DIRECT; if (be_lun->ctl_be_lun.lun_type == T_DIRECT) { value = ctl_get_opt(&be_lun->ctl_be_lun.options, "file"); if (value == NULL) { snprintf(req->error_str, sizeof(req->error_str), "no file argument specified"); goto bailout_error; } be_lun->dev_path = strdup(value, M_CTLBLK); be_lun->blocksize = 512; be_lun->blocksize_shift = fls(be_lun->blocksize) - 1; retval = ctl_be_block_open(softc, be_lun, req); if (retval != 0) { retval = 0; req->status = CTL_LUN_WARNING; } } else { /* * For processor devices, we don't have any size. */ be_lun->blocksize = 0; be_lun->pblockexp = 0; be_lun->pblockoff = 0; be_lun->ublockexp = 0; be_lun->ublockoff = 0; be_lun->size_blocks = 0; be_lun->size_bytes = 0; be_lun->ctl_be_lun.maxlba = 0; /* * Default to just 1 thread for processor devices. */ num_threads = 1; } /* * XXX This searching loop might be refactored to be combined with * the loop above, */ value = ctl_get_opt(&be_lun->ctl_be_lun.options, "num_threads"); if (value != NULL) { tmp_num_threads = strtol(value, NULL, 0); /* * We don't let the user specify less than one * thread, but hope he's clueful enough not to * specify 1000 threads. */ if (tmp_num_threads < 1) { snprintf(req->error_str, sizeof(req->error_str), "invalid number of threads %s", num_thread_str); goto bailout_error; } num_threads = tmp_num_threads; } unmap = (be_lun->dispatch == ctl_be_block_dispatch_zvol); value = ctl_get_opt(&be_lun->ctl_be_lun.options, "unmap"); if (value != NULL) unmap = (strcmp(value, "on") == 0); be_lun->flags = CTL_BE_BLOCK_LUN_UNCONFIGURED; be_lun->ctl_be_lun.flags = CTL_LUN_FLAG_PRIMARY; if (be_lun->vn == NULL) be_lun->ctl_be_lun.flags |= CTL_LUN_FLAG_OFFLINE; if (unmap) be_lun->ctl_be_lun.flags |= CTL_LUN_FLAG_UNMAP; if (be_lun->dispatch != ctl_be_block_dispatch_dev) be_lun->ctl_be_lun.flags |= CTL_LUN_FLAG_SERSEQ_READ; be_lun->ctl_be_lun.be_lun = be_lun; be_lun->ctl_be_lun.maxlba = (be_lun->size_blocks == 0) ? 0 : (be_lun->size_blocks - 1); be_lun->ctl_be_lun.blocksize = be_lun->blocksize; be_lun->ctl_be_lun.pblockexp = be_lun->pblockexp; be_lun->ctl_be_lun.pblockoff = be_lun->pblockoff; be_lun->ctl_be_lun.ublockexp = be_lun->ublockexp; be_lun->ctl_be_lun.ublockoff = be_lun->ublockoff; be_lun->ctl_be_lun.atomicblock = be_lun->atomicblock; be_lun->ctl_be_lun.opttxferlen = be_lun->opttxferlen; /* Tell the user the blocksize we ended up using */ params->lun_size_bytes = be_lun->size_bytes; params->blocksize_bytes = be_lun->blocksize; if (params->flags & CTL_LUN_FLAG_ID_REQ) { be_lun->ctl_be_lun.req_lun_id = params->req_lun_id; be_lun->ctl_be_lun.flags |= CTL_LUN_FLAG_ID_REQ; } else be_lun->ctl_be_lun.req_lun_id = 0; be_lun->ctl_be_lun.lun_shutdown = ctl_be_block_lun_shutdown; be_lun->ctl_be_lun.lun_config_status = ctl_be_block_lun_config_status; be_lun->ctl_be_lun.be = &ctl_be_block_driver; if ((params->flags & CTL_LUN_FLAG_SERIAL_NUM) == 0) { snprintf(tmpstr, sizeof(tmpstr), "MYSERIAL%4d", softc->num_luns); strncpy((char *)be_lun->ctl_be_lun.serial_num, tmpstr, MIN(sizeof(be_lun->ctl_be_lun.serial_num), sizeof(tmpstr))); /* Tell the user what we used for a serial number */ strncpy((char *)params->serial_num, tmpstr, MIN(sizeof(params->serial_num), sizeof(tmpstr))); } else { strncpy((char *)be_lun->ctl_be_lun.serial_num, params->serial_num, MIN(sizeof(be_lun->ctl_be_lun.serial_num), sizeof(params->serial_num))); } if ((params->flags & CTL_LUN_FLAG_DEVID) == 0) { snprintf(tmpstr, sizeof(tmpstr), "MYDEVID%4d", softc->num_luns); strncpy((char *)be_lun->ctl_be_lun.device_id, tmpstr, MIN(sizeof(be_lun->ctl_be_lun.device_id), sizeof(tmpstr))); /* Tell the user what we used for a device ID */ strncpy((char *)params->device_id, tmpstr, MIN(sizeof(params->device_id), sizeof(tmpstr))); } else { strncpy((char *)be_lun->ctl_be_lun.device_id, params->device_id, MIN(sizeof(be_lun->ctl_be_lun.device_id), sizeof(params->device_id))); } TASK_INIT(&be_lun->io_task, /*priority*/0, ctl_be_block_worker, be_lun); be_lun->io_taskqueue = taskqueue_create(be_lun->lunname, M_WAITOK, taskqueue_thread_enqueue, /*context*/&be_lun->io_taskqueue); if (be_lun->io_taskqueue == NULL) { snprintf(req->error_str, sizeof(req->error_str), "unable to create taskqueue"); goto bailout_error; } /* * Note that we start the same number of threads by default for * both the file case and the block device case. For the file * case, we need multiple threads to allow concurrency, because the * vnode interface is designed to be a blocking interface. For the * block device case, ZFS zvols at least will block the caller's * context in many instances, and so we need multiple threads to * overcome that problem. Other block devices don't need as many * threads, but they shouldn't cause too many problems. * * If the user wants to just have a single thread for a block * device, he can specify that when the LUN is created, or change * the tunable/sysctl to alter the default number of threads. */ retval = taskqueue_start_threads(&be_lun->io_taskqueue, /*num threads*/num_threads, /*priority*/PWAIT, /*thread name*/ "%s taskq", be_lun->lunname); if (retval != 0) goto bailout_error; be_lun->num_threads = num_threads; mtx_lock(&softc->lock); softc->num_luns++; STAILQ_INSERT_TAIL(&softc->lun_list, be_lun, links); mtx_unlock(&softc->lock); retval = ctl_add_lun(&be_lun->ctl_be_lun); if (retval != 0) { mtx_lock(&softc->lock); STAILQ_REMOVE(&softc->lun_list, be_lun, ctl_be_block_lun, links); softc->num_luns--; mtx_unlock(&softc->lock); snprintf(req->error_str, sizeof(req->error_str), "ctl_add_lun() returned error %d, see dmesg for " "details", retval); retval = 0; goto bailout_error; } mtx_lock(&softc->lock); /* * Tell the config_status routine that we're waiting so it won't * clean up the LUN in the event of an error. */ be_lun->flags |= CTL_BE_BLOCK_LUN_WAITING; while (be_lun->flags & CTL_BE_BLOCK_LUN_UNCONFIGURED) { retval = msleep(be_lun, &softc->lock, PCATCH, "ctlblk", 0); if (retval == EINTR) break; } be_lun->flags &= ~CTL_BE_BLOCK_LUN_WAITING; if (be_lun->flags & CTL_BE_BLOCK_LUN_CONFIG_ERR) { snprintf(req->error_str, sizeof(req->error_str), "LUN configuration error, see dmesg for details"); STAILQ_REMOVE(&softc->lun_list, be_lun, ctl_be_block_lun, links); softc->num_luns--; mtx_unlock(&softc->lock); goto bailout_error; } else { params->req_lun_id = be_lun->ctl_be_lun.lun_id; } mtx_unlock(&softc->lock); be_lun->disk_stats = devstat_new_entry("cbb", params->req_lun_id, be_lun->blocksize, DEVSTAT_ALL_SUPPORTED, be_lun->ctl_be_lun.lun_type | DEVSTAT_TYPE_IF_OTHER, DEVSTAT_PRIORITY_OTHER); return (retval); bailout_error: req->status = CTL_LUN_ERROR; if (be_lun->io_taskqueue != NULL) taskqueue_free(be_lun->io_taskqueue); ctl_be_block_close(be_lun); if (be_lun->dev_path != NULL) free(be_lun->dev_path, M_CTLBLK); if (be_lun->lun_zone != NULL) uma_zdestroy(be_lun->lun_zone); ctl_free_opts(&be_lun->ctl_be_lun.options); mtx_destroy(&be_lun->queue_lock); mtx_destroy(&be_lun->io_lock); free(be_lun, M_CTLBLK); return (retval); } static int ctl_be_block_rm(struct ctl_be_block_softc *softc, struct ctl_lun_req *req) { struct ctl_lun_rm_params *params; struct ctl_be_block_lun *be_lun; int retval; params = &req->reqdata.rm; mtx_lock(&softc->lock); be_lun = NULL; STAILQ_FOREACH(be_lun, &softc->lun_list, links) { if (be_lun->ctl_be_lun.lun_id == params->lun_id) break; } mtx_unlock(&softc->lock); if (be_lun == NULL) { snprintf(req->error_str, sizeof(req->error_str), "LUN %u is not managed by the block backend", params->lun_id); goto bailout_error; } retval = ctl_disable_lun(&be_lun->ctl_be_lun); if (retval != 0) { snprintf(req->error_str, sizeof(req->error_str), "error %d returned from ctl_disable_lun() for " "LUN %d", retval, params->lun_id); goto bailout_error; } retval = ctl_invalidate_lun(&be_lun->ctl_be_lun); if (retval != 0) { snprintf(req->error_str, sizeof(req->error_str), "error %d returned from ctl_invalidate_lun() for " "LUN %d", retval, params->lun_id); goto bailout_error; } mtx_lock(&softc->lock); be_lun->flags |= CTL_BE_BLOCK_LUN_WAITING; while ((be_lun->flags & CTL_BE_BLOCK_LUN_UNCONFIGURED) == 0) { retval = msleep(be_lun, &softc->lock, PCATCH, "ctlblk", 0); if (retval == EINTR) break; } be_lun->flags &= ~CTL_BE_BLOCK_LUN_WAITING; if ((be_lun->flags & CTL_BE_BLOCK_LUN_UNCONFIGURED) == 0) { snprintf(req->error_str, sizeof(req->error_str), "interrupted waiting for LUN to be freed"); mtx_unlock(&softc->lock); goto bailout_error; } STAILQ_REMOVE(&softc->lun_list, be_lun, ctl_be_block_lun, links); softc->num_luns--; mtx_unlock(&softc->lock); taskqueue_drain(be_lun->io_taskqueue, &be_lun->io_task); taskqueue_free(be_lun->io_taskqueue); ctl_be_block_close(be_lun); if (be_lun->disk_stats != NULL) devstat_remove_entry(be_lun->disk_stats); uma_zdestroy(be_lun->lun_zone); ctl_free_opts(&be_lun->ctl_be_lun.options); free(be_lun->dev_path, M_CTLBLK); mtx_destroy(&be_lun->queue_lock); mtx_destroy(&be_lun->io_lock); free(be_lun, M_CTLBLK); req->status = CTL_LUN_OK; return (0); bailout_error: req->status = CTL_LUN_ERROR; return (0); } static int ctl_be_block_modify_file(struct ctl_be_block_lun *be_lun, struct ctl_lun_req *req) { struct vattr vattr; int error; struct ctl_lun_create_params *params = &be_lun->params; if (params->lun_size_bytes != 0) { be_lun->size_bytes = params->lun_size_bytes; } else { vn_lock(be_lun->vn, LK_SHARED | LK_RETRY); error = VOP_GETATTR(be_lun->vn, &vattr, curthread->td_ucred); VOP_UNLOCK(be_lun->vn, 0); if (error != 0) { snprintf(req->error_str, sizeof(req->error_str), "error calling VOP_GETATTR() for file %s", be_lun->dev_path); return (error); } be_lun->size_bytes = vattr.va_size; } return (0); } static int ctl_be_block_modify_dev(struct ctl_be_block_lun *be_lun, struct ctl_lun_req *req) { struct ctl_be_block_devdata *dev_data; int error; struct ctl_lun_create_params *params = &be_lun->params; uint64_t size_bytes; dev_data = &be_lun->backend.dev; if (!dev_data->csw->d_ioctl) { snprintf(req->error_str, sizeof(req->error_str), "no d_ioctl for device %s!", be_lun->dev_path); return (ENODEV); } error = dev_data->csw->d_ioctl(dev_data->cdev, DIOCGMEDIASIZE, (caddr_t)&size_bytes, FREAD, curthread); if (error) { snprintf(req->error_str, sizeof(req->error_str), "error %d returned for DIOCGMEDIASIZE ioctl " "on %s!", error, be_lun->dev_path); return (error); } if (params->lun_size_bytes != 0) { if (params->lun_size_bytes > size_bytes) { snprintf(req->error_str, sizeof(req->error_str), "requested LUN size %ju > backing device " "size %ju", (uintmax_t)params->lun_size_bytes, (uintmax_t)size_bytes); return (EINVAL); } be_lun->size_bytes = params->lun_size_bytes; } else { be_lun->size_bytes = size_bytes; } return (0); } static int ctl_be_block_modify(struct ctl_be_block_softc *softc, struct ctl_lun_req *req) { struct ctl_lun_modify_params *params; struct ctl_be_block_lun *be_lun; uint64_t oldsize; int error; params = &req->reqdata.modify; mtx_lock(&softc->lock); be_lun = NULL; STAILQ_FOREACH(be_lun, &softc->lun_list, links) { if (be_lun->ctl_be_lun.lun_id == params->lun_id) break; } mtx_unlock(&softc->lock); if (be_lun == NULL) { snprintf(req->error_str, sizeof(req->error_str), "LUN %u is not managed by the block backend", params->lun_id); goto bailout_error; } be_lun->params.lun_size_bytes = params->lun_size_bytes; oldsize = be_lun->size_bytes; if (be_lun->vn == NULL) error = ctl_be_block_open(softc, be_lun, req); else if (be_lun->vn->v_type == VREG) error = ctl_be_block_modify_file(be_lun, req); else error = ctl_be_block_modify_dev(be_lun, req); if (error == 0 && be_lun->size_bytes != oldsize) { be_lun->size_blocks = be_lun->size_bytes >> be_lun->blocksize_shift; /* * The maximum LBA is the size - 1. * * XXX: Note that this field is being updated without locking, * which might cause problems on 32-bit architectures. */ be_lun->ctl_be_lun.maxlba = (be_lun->size_blocks == 0) ? 0 : (be_lun->size_blocks - 1); be_lun->ctl_be_lun.blocksize = be_lun->blocksize; be_lun->ctl_be_lun.pblockexp = be_lun->pblockexp; be_lun->ctl_be_lun.pblockoff = be_lun->pblockoff; be_lun->ctl_be_lun.ublockexp = be_lun->ublockexp; be_lun->ctl_be_lun.ublockoff = be_lun->ublockoff; be_lun->ctl_be_lun.atomicblock = be_lun->atomicblock; be_lun->ctl_be_lun.opttxferlen = be_lun->opttxferlen; ctl_lun_capacity_changed(&be_lun->ctl_be_lun); if (oldsize == 0 && be_lun->size_blocks != 0) ctl_lun_online(&be_lun->ctl_be_lun); } /* Tell the user the exact size we ended up using */ params->lun_size_bytes = be_lun->size_bytes; req->status = error ? CTL_LUN_WARNING : CTL_LUN_OK; return (0); bailout_error: req->status = CTL_LUN_ERROR; return (0); } static void ctl_be_block_lun_shutdown(void *be_lun) { struct ctl_be_block_lun *lun; struct ctl_be_block_softc *softc; lun = (struct ctl_be_block_lun *)be_lun; softc = lun->softc; mtx_lock(&softc->lock); lun->flags |= CTL_BE_BLOCK_LUN_UNCONFIGURED; if (lun->flags & CTL_BE_BLOCK_LUN_WAITING) wakeup(lun); mtx_unlock(&softc->lock); } static void ctl_be_block_lun_config_status(void *be_lun, ctl_lun_config_status status) { struct ctl_be_block_lun *lun; struct ctl_be_block_softc *softc; lun = (struct ctl_be_block_lun *)be_lun; softc = lun->softc; if (status == CTL_LUN_CONFIG_OK) { mtx_lock(&softc->lock); lun->flags &= ~CTL_BE_BLOCK_LUN_UNCONFIGURED; if (lun->flags & CTL_BE_BLOCK_LUN_WAITING) wakeup(lun); mtx_unlock(&softc->lock); /* * We successfully added the LUN, attempt to enable it. */ if (ctl_enable_lun(&lun->ctl_be_lun) != 0) { printf("%s: ctl_enable_lun() failed!\n", __func__); if (ctl_invalidate_lun(&lun->ctl_be_lun) != 0) { printf("%s: ctl_invalidate_lun() failed!\n", __func__); } } return; } mtx_lock(&softc->lock); lun->flags &= ~CTL_BE_BLOCK_LUN_UNCONFIGURED; lun->flags |= CTL_BE_BLOCK_LUN_CONFIG_ERR; wakeup(lun); mtx_unlock(&softc->lock); } static int ctl_be_block_config_write(union ctl_io *io) { struct ctl_be_block_lun *be_lun; struct ctl_be_lun *ctl_be_lun; int retval; retval = 0; DPRINTF("entered\n"); ctl_be_lun = (struct ctl_be_lun *)io->io_hdr.ctl_private[ CTL_PRIV_BACKEND_LUN].ptr; be_lun = (struct ctl_be_block_lun *)ctl_be_lun->be_lun; 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. * * We don't really need to worry about what LBA range the * user asked to be synced out. When they issue a sync * cache command, we'll sync out the whole thing. */ mtx_lock(&be_lun->queue_lock); STAILQ_INSERT_TAIL(&be_lun->config_write_queue, &io->io_hdr, links); mtx_unlock(&be_lun->queue_lock); taskqueue_enqueue(be_lun->io_taskqueue, &be_lun->io_task); break; case START_STOP_UNIT: { struct scsi_start_stop_unit *cdb; cdb = (struct scsi_start_stop_unit *)io->scsiio.cdb; if (cdb->how & SSS_START) retval = ctl_start_lun(ctl_be_lun); else { retval = ctl_stop_lun(ctl_be_lun); /* * XXX KDM Copan-specific offline behavior. * Figure out a reasonable way to port this? */ #ifdef NEEDTOPORT if ((retval == 0) && (cdb->byte2 & SSS_ONOFFLINE)) retval = ctl_lun_offline(ctl_be_lun); #endif } /* * In general, the above routines should not fail. They * just set state for the LUN. So we've got something * pretty wrong here if we can't start or stop the LUN. */ if (retval != 0) { ctl_set_internal_failure(&io->scsiio, /*sks_valid*/ 1, /*retry_count*/ 0xf051); retval = CTL_RETVAL_COMPLETE; } else { ctl_set_success(&io->scsiio); } ctl_config_write_done(io); break; } default: ctl_set_invalid_opcode(&io->scsiio); ctl_config_write_done(io); retval = CTL_RETVAL_COMPLETE; break; } return (retval); } static int ctl_be_block_config_read(union ctl_io *io) { struct ctl_be_block_lun *be_lun; struct ctl_be_lun *ctl_be_lun; int retval = 0; DPRINTF("entered\n"); ctl_be_lun = (struct ctl_be_lun *)io->io_hdr.ctl_private[ CTL_PRIV_BACKEND_LUN].ptr; be_lun = (struct ctl_be_block_lun *)ctl_be_lun->be_lun; switch (io->scsiio.cdb[0]) { case SERVICE_ACTION_IN: if (io->scsiio.cdb[1] == SGLS_SERVICE_ACTION) { mtx_lock(&be_lun->queue_lock); STAILQ_INSERT_TAIL(&be_lun->config_read_queue, &io->io_hdr, links); mtx_unlock(&be_lun->queue_lock); taskqueue_enqueue(be_lun->io_taskqueue, &be_lun->io_task); retval = CTL_RETVAL_QUEUED; break; } ctl_set_invalid_field(&io->scsiio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 1, /*bit_valid*/ 1, /*bit*/ 4); ctl_config_read_done(io); retval = CTL_RETVAL_COMPLETE; break; default: ctl_set_invalid_opcode(&io->scsiio); ctl_config_read_done(io); retval = CTL_RETVAL_COMPLETE; break; } return (retval); } static int ctl_be_block_lun_info(void *be_lun, struct sbuf *sb) { struct ctl_be_block_lun *lun; int retval; lun = (struct ctl_be_block_lun *)be_lun; retval = 0; retval = sbuf_printf(sb, "\t"); if (retval != 0) goto bailout; retval = sbuf_printf(sb, "%d", lun->num_threads); if (retval != 0) goto bailout; retval = sbuf_printf(sb, "\n"); bailout: return (retval); } static uint64_t ctl_be_block_lun_attr(void *be_lun, const char *attrname) { struct ctl_be_block_lun *lun = (struct ctl_be_block_lun *)be_lun; if (lun->getattr == NULL) return (UINT64_MAX); return (lun->getattr(lun, attrname)); } int ctl_be_block_init(void) { struct ctl_be_block_softc *softc; int retval; softc = &backend_block_softc; retval = 0; mtx_init(&softc->lock, "ctlblock", NULL, MTX_DEF); beio_zone = uma_zcreate("beio", sizeof(struct ctl_be_block_io), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); STAILQ_INIT(&softc->disk_list); STAILQ_INIT(&softc->lun_list); return (retval); } Index: stable/10/sys/cam/ctl/ctl_tpc.c =================================================================== --- stable/10/sys/cam/ctl/ctl_tpc.c (revision 279003) +++ stable/10/sys/cam/ctl/ctl_tpc.c (revision 279004) @@ -1,2260 +1,2304 @@ /*- * Copyright (c) 2014 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TPC_MAX_CSCDS 64 #define TPC_MAX_SEGS 64 #define TPC_MAX_SEG 0 #define TPC_MAX_LIST 8192 #define TPC_MAX_INLINE 0 #define TPC_MAX_LISTS 255 #define TPC_MAX_IO_SIZE (1024 * 1024) #define TPC_MAX_IOCHUNK_SIZE (TPC_MAX_IO_SIZE * 16) #define TPC_MIN_TOKEN_TIMEOUT 1 #define TPC_DFL_TOKEN_TIMEOUT 60 #define TPC_MAX_TOKEN_TIMEOUT 600 MALLOC_DEFINE(M_CTL_TPC, "ctltpc", "CTL TPC"); typedef enum { TPC_ERR_RETRY = 0x000, TPC_ERR_FAIL = 0x001, TPC_ERR_MASK = 0x0ff, TPC_ERR_NO_DECREMENT = 0x100 } tpc_error_action; struct tpc_list; TAILQ_HEAD(runl, tpc_io); struct tpc_io { union ctl_io *io; uint64_t lun; struct tpc_list *list; struct runl run; TAILQ_ENTRY(tpc_io) rlinks; TAILQ_ENTRY(tpc_io) links; }; struct tpc_token { uint8_t token[512]; uint64_t lun; uint32_t blocksize; uint8_t *params; struct scsi_range_desc *range; int nrange; int active; time_t last_active; uint32_t timeout; TAILQ_ENTRY(tpc_token) links; }; struct tpc_list { uint8_t service_action; int init_port; uint32_t init_idx; uint32_t list_id; uint8_t flags; uint8_t *params; struct scsi_ec_cscd *cscd; struct scsi_ec_segment *seg[TPC_MAX_SEGS]; uint8_t *inl; int ncscd; int nseg; int leninl; struct tpc_token *token; struct scsi_range_desc *range; int nrange; off_t offset_into_rod; int curseg; off_t cursectors; off_t curbytes; int curops; int stage; uint8_t *buf; off_t segsectors; off_t segbytes; int tbdio; int error; int abort; int completed; time_t last_active; TAILQ_HEAD(, tpc_io) allio; struct scsi_sense_data sense_data; uint8_t sense_len; uint8_t scsi_status; struct ctl_scsiio *ctsio; struct ctl_lun *lun; int res_token_valid; uint8_t res_token[512]; TAILQ_ENTRY(tpc_list) links; }; static void tpc_timeout(void *arg) { struct ctl_softc *softc = arg; struct ctl_lun *lun; struct tpc_token *token, *ttoken; struct tpc_list *list, *tlist; /* Free completed lists with expired timeout. */ STAILQ_FOREACH(lun, &softc->lun_list, links) { mtx_lock(&lun->lun_lock); TAILQ_FOREACH_SAFE(list, &lun->tpc_lists, links, tlist) { if (!list->completed || time_uptime < list->last_active + TPC_DFL_TOKEN_TIMEOUT) continue; TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); } mtx_unlock(&lun->lun_lock); } /* Free inactive ROD tokens with expired timeout. */ TAILQ_FOREACH_SAFE(token, &softc->tpc_tokens, links, ttoken) { if (token->active || time_uptime < token->last_active + token->timeout + 1) continue; TAILQ_REMOVE(&softc->tpc_tokens, token, links); free(token->params, M_CTL); free(token, M_CTL); } callout_schedule(&softc->tpc_timeout, hz); } void ctl_tpc_init(struct ctl_softc *softc) { TAILQ_INIT(&softc->tpc_tokens); callout_init_mtx(&softc->tpc_timeout, &softc->ctl_lock, 0); callout_reset(&softc->tpc_timeout, hz, tpc_timeout, softc); } void ctl_tpc_shutdown(struct ctl_softc *softc) { struct tpc_token *token; callout_drain(&softc->tpc_timeout); /* Free ROD tokens. */ mtx_lock(&softc->ctl_lock); while ((token = TAILQ_FIRST(&softc->tpc_tokens)) != NULL) { TAILQ_REMOVE(&softc->tpc_tokens, token, links); free(token->params, M_CTL); free(token, M_CTL); } mtx_unlock(&softc->ctl_lock); } void ctl_tpc_lun_init(struct ctl_lun *lun) { TAILQ_INIT(&lun->tpc_lists); } void ctl_tpc_lun_shutdown(struct ctl_lun *lun) { struct ctl_softc *softc = lun->ctl_softc; struct tpc_list *list; struct tpc_token *token, *ttoken; /* Free lists for this LUN. */ while ((list = TAILQ_FIRST(&lun->tpc_lists)) != NULL) { TAILQ_REMOVE(&lun->tpc_lists, list, links); KASSERT(list->completed, ("Not completed TPC (%p) on shutdown", list)); free(list, M_CTL); } /* Free ROD tokens for this LUN. */ mtx_assert(&softc->ctl_lock, MA_OWNED); TAILQ_FOREACH_SAFE(token, &softc->tpc_tokens, links, ttoken) { if (token->lun != lun->lun || token->active) continue; TAILQ_REMOVE(&softc->tpc_tokens, token, links); free(token->params, M_CTL); free(token, M_CTL); } } int ctl_inquiry_evpd_tpc(struct ctl_scsiio *ctsio, int alloc_len) { struct scsi_vpd_tpc *tpc_ptr; struct scsi_vpd_tpc_descriptor *d_ptr; struct scsi_vpd_tpc_descriptor_bdrl *bdrl_ptr; struct scsi_vpd_tpc_descriptor_sc *sc_ptr; struct scsi_vpd_tpc_descriptor_sc_descr *scd_ptr; struct scsi_vpd_tpc_descriptor_pd *pd_ptr; struct scsi_vpd_tpc_descriptor_sd *sd_ptr; struct scsi_vpd_tpc_descriptor_sdid *sdid_ptr; struct scsi_vpd_tpc_descriptor_rtf *rtf_ptr; struct scsi_vpd_tpc_descriptor_rtf_block *rtfb_ptr; struct scsi_vpd_tpc_descriptor_srt *srt_ptr; struct scsi_vpd_tpc_descriptor_srtd *srtd_ptr; struct scsi_vpd_tpc_descriptor_gco *gco_ptr; struct ctl_lun *lun; int data_len; lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; data_len = sizeof(struct scsi_vpd_tpc) + sizeof(struct scsi_vpd_tpc_descriptor_bdrl) + roundup2(sizeof(struct scsi_vpd_tpc_descriptor_sc) + 2 * sizeof(struct scsi_vpd_tpc_descriptor_sc_descr) + 11, 4) + sizeof(struct scsi_vpd_tpc_descriptor_pd) + roundup2(sizeof(struct scsi_vpd_tpc_descriptor_sd) + 4, 4) + roundup2(sizeof(struct scsi_vpd_tpc_descriptor_sdid) + 2, 4) + sizeof(struct scsi_vpd_tpc_descriptor_rtf) + sizeof(struct scsi_vpd_tpc_descriptor_rtf_block) + sizeof(struct scsi_vpd_tpc_descriptor_srt) + 2*sizeof(struct scsi_vpd_tpc_descriptor_srtd) + sizeof(struct scsi_vpd_tpc_descriptor_gco); ctsio->kern_data_ptr = malloc(data_len, M_CTL, M_WAITOK | M_ZERO); tpc_ptr = (struct scsi_vpd_tpc *)ctsio->kern_data_ptr; ctsio->kern_sg_entries = 0; if (data_len < alloc_len) { ctsio->residual = alloc_len - data_len; ctsio->kern_data_len = data_len; ctsio->kern_total_len = data_len; } 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. */ if (lun != NULL) tpc_ptr->device = (SID_QUAL_LU_CONNECTED << 5) | lun->be_lun->lun_type; else tpc_ptr->device = (SID_QUAL_LU_OFFLINE << 5) | T_DIRECT; tpc_ptr->page_code = SVPD_SCSI_TPC; scsi_ulto2b(data_len - 4, tpc_ptr->page_length); /* Block Device ROD Limits */ d_ptr = (struct scsi_vpd_tpc_descriptor *)&tpc_ptr->descr[0]; bdrl_ptr = (struct scsi_vpd_tpc_descriptor_bdrl *)d_ptr; scsi_ulto2b(SVPD_TPC_BDRL, bdrl_ptr->desc_type); scsi_ulto2b(sizeof(*bdrl_ptr) - 4, bdrl_ptr->desc_length); scsi_ulto2b(TPC_MAX_SEGS, bdrl_ptr->maximum_ranges); scsi_ulto4b(TPC_MAX_TOKEN_TIMEOUT, bdrl_ptr->maximum_inactivity_timeout); scsi_ulto4b(TPC_DFL_TOKEN_TIMEOUT, bdrl_ptr->default_inactivity_timeout); scsi_u64to8b(0, bdrl_ptr->maximum_token_transfer_size); scsi_u64to8b(0, bdrl_ptr->optimal_transfer_count); /* Supported commands */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); sc_ptr = (struct scsi_vpd_tpc_descriptor_sc *)d_ptr; scsi_ulto2b(SVPD_TPC_SC, sc_ptr->desc_type); sc_ptr->list_length = 2 * sizeof(*scd_ptr) + 11; scsi_ulto2b(roundup2(1 + sc_ptr->list_length, 4), sc_ptr->desc_length); scd_ptr = &sc_ptr->descr[0]; scd_ptr->opcode = EXTENDED_COPY; scd_ptr->sa_length = 5; scd_ptr->supported_service_actions[0] = EC_EC_LID1; scd_ptr->supported_service_actions[1] = EC_EC_LID4; scd_ptr->supported_service_actions[2] = EC_PT; scd_ptr->supported_service_actions[3] = EC_WUT; scd_ptr->supported_service_actions[4] = EC_COA; scd_ptr = (struct scsi_vpd_tpc_descriptor_sc_descr *) &scd_ptr->supported_service_actions[scd_ptr->sa_length]; scd_ptr->opcode = RECEIVE_COPY_STATUS; scd_ptr->sa_length = 6; scd_ptr->supported_service_actions[0] = RCS_RCS_LID1; scd_ptr->supported_service_actions[1] = RCS_RCFD; scd_ptr->supported_service_actions[2] = RCS_RCS_LID4; scd_ptr->supported_service_actions[3] = RCS_RCOP; scd_ptr->supported_service_actions[4] = RCS_RRTI; scd_ptr->supported_service_actions[5] = RCS_RART; /* Parameter data. */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); pd_ptr = (struct scsi_vpd_tpc_descriptor_pd *)d_ptr; scsi_ulto2b(SVPD_TPC_PD, pd_ptr->desc_type); scsi_ulto2b(sizeof(*pd_ptr) - 4, pd_ptr->desc_length); scsi_ulto2b(TPC_MAX_CSCDS, pd_ptr->maximum_cscd_descriptor_count); scsi_ulto2b(TPC_MAX_SEGS, pd_ptr->maximum_segment_descriptor_count); scsi_ulto4b(TPC_MAX_LIST, pd_ptr->maximum_descriptor_list_length); scsi_ulto4b(TPC_MAX_INLINE, pd_ptr->maximum_inline_data_length); /* Supported Descriptors */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); sd_ptr = (struct scsi_vpd_tpc_descriptor_sd *)d_ptr; scsi_ulto2b(SVPD_TPC_SD, sd_ptr->desc_type); scsi_ulto2b(roundup2(sizeof(*sd_ptr) - 4 + 4, 4), sd_ptr->desc_length); sd_ptr->list_length = 4; sd_ptr->supported_descriptor_codes[0] = EC_SEG_B2B; sd_ptr->supported_descriptor_codes[1] = EC_SEG_VERIFY; sd_ptr->supported_descriptor_codes[2] = EC_SEG_REGISTER_KEY; sd_ptr->supported_descriptor_codes[3] = EC_CSCD_ID; /* Supported CSCD Descriptor IDs */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); sdid_ptr = (struct scsi_vpd_tpc_descriptor_sdid *)d_ptr; scsi_ulto2b(SVPD_TPC_SDID, sdid_ptr->desc_type); scsi_ulto2b(roundup2(sizeof(*sdid_ptr) - 4 + 2, 4), sdid_ptr->desc_length); scsi_ulto2b(2, sdid_ptr->list_length); scsi_ulto2b(0xffff, &sdid_ptr->supported_descriptor_ids[0]); /* ROD Token Features */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); rtf_ptr = (struct scsi_vpd_tpc_descriptor_rtf *)d_ptr; scsi_ulto2b(SVPD_TPC_RTF, rtf_ptr->desc_type); scsi_ulto2b(sizeof(*rtf_ptr) - 4 + sizeof(*rtfb_ptr), rtf_ptr->desc_length); rtf_ptr->remote_tokens = 0; scsi_ulto4b(TPC_MIN_TOKEN_TIMEOUT, rtf_ptr->minimum_token_lifetime); scsi_ulto4b(UINT32_MAX, rtf_ptr->maximum_token_lifetime); scsi_ulto4b(TPC_MAX_TOKEN_TIMEOUT, rtf_ptr->maximum_token_inactivity_timeout); scsi_ulto2b(sizeof(*rtfb_ptr), rtf_ptr->type_specific_features_length); rtfb_ptr = (struct scsi_vpd_tpc_descriptor_rtf_block *) &rtf_ptr->type_specific_features; rtfb_ptr->type_format = SVPD_TPC_RTF_BLOCK; scsi_ulto2b(sizeof(*rtfb_ptr) - 4, rtfb_ptr->desc_length); scsi_ulto2b(0, rtfb_ptr->optimal_length_granularity); scsi_u64to8b(0, rtfb_ptr->maximum_bytes); scsi_u64to8b(0, rtfb_ptr->optimal_bytes); scsi_u64to8b(TPC_MAX_IOCHUNK_SIZE, rtfb_ptr->optimal_bytes_to_token_per_segment); scsi_u64to8b(TPC_MAX_IOCHUNK_SIZE, rtfb_ptr->optimal_bytes_from_token_per_segment); /* Supported ROD Tokens */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); srt_ptr = (struct scsi_vpd_tpc_descriptor_srt *)d_ptr; scsi_ulto2b(SVPD_TPC_SRT, srt_ptr->desc_type); scsi_ulto2b(sizeof(*srt_ptr) - 4 + 2*sizeof(*srtd_ptr), srt_ptr->desc_length); scsi_ulto2b(2*sizeof(*srtd_ptr), srt_ptr->rod_type_descriptors_length); srtd_ptr = (struct scsi_vpd_tpc_descriptor_srtd *) &srt_ptr->rod_type_descriptors; scsi_ulto4b(ROD_TYPE_AUR, srtd_ptr->rod_type); srtd_ptr->flags = SVPD_TPC_SRTD_TIN | SVPD_TPC_SRTD_TOUT; scsi_ulto2b(0, srtd_ptr->preference_indicator); srtd_ptr++; scsi_ulto4b(ROD_TYPE_BLOCK_ZERO, srtd_ptr->rod_type); srtd_ptr->flags = SVPD_TPC_SRTD_TIN; scsi_ulto2b(0, srtd_ptr->preference_indicator); /* General Copy Operations */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); gco_ptr = (struct scsi_vpd_tpc_descriptor_gco *)d_ptr; scsi_ulto2b(SVPD_TPC_GCO, gco_ptr->desc_type); scsi_ulto2b(sizeof(*gco_ptr) - 4, gco_ptr->desc_length); scsi_ulto4b(TPC_MAX_LISTS, gco_ptr->total_concurrent_copies); scsi_ulto4b(TPC_MAX_LISTS, gco_ptr->maximum_identified_concurrent_copies); scsi_ulto4b(TPC_MAX_SEG, gco_ptr->maximum_segment_length); gco_ptr->data_segment_granularity = 0; gco_ptr->inline_data_granularity = 0; ctl_set_success(ctsio); 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); } int ctl_receive_copy_operating_parameters(struct ctl_scsiio *ctsio) { struct scsi_receive_copy_operating_parameters *cdb; struct scsi_receive_copy_operating_parameters_data *data; int retval; int alloc_len, total_len; CTL_DEBUG_PRINT(("ctl_report_supported_tmf\n")); cdb = (struct scsi_receive_copy_operating_parameters *)ctsio->cdb; retval = CTL_RETVAL_COMPLETE; total_len = sizeof(*data) + 4; alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; if (total_len < alloc_len) { ctsio->residual = alloc_len - total_len; ctsio->kern_data_len = total_len; ctsio->kern_total_len = total_len; } 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; data = (struct scsi_receive_copy_operating_parameters_data *)ctsio->kern_data_ptr; scsi_ulto4b(sizeof(*data) - 4 + 4, data->length); data->snlid = RCOP_SNLID; scsi_ulto2b(TPC_MAX_CSCDS, data->maximum_cscd_descriptor_count); scsi_ulto2b(TPC_MAX_SEGS, data->maximum_segment_descriptor_count); scsi_ulto4b(TPC_MAX_LIST, data->maximum_descriptor_list_length); scsi_ulto4b(TPC_MAX_SEG, data->maximum_segment_length); scsi_ulto4b(TPC_MAX_INLINE, data->maximum_inline_data_length); scsi_ulto4b(0, data->held_data_limit); scsi_ulto4b(0, data->maximum_stream_device_transfer_size); scsi_ulto2b(TPC_MAX_LISTS, data->total_concurrent_copies); data->maximum_concurrent_copies = TPC_MAX_LISTS; data->data_segment_granularity = 0; data->inline_data_granularity = 0; data->held_data_granularity = 0; data->implemented_descriptor_list_length = 4; data->list_of_implemented_descriptor_type_codes[0] = EC_SEG_B2B; data->list_of_implemented_descriptor_type_codes[1] = EC_SEG_VERIFY; data->list_of_implemented_descriptor_type_codes[2] = EC_SEG_REGISTER_KEY; data->list_of_implemented_descriptor_type_codes[3] = EC_CSCD_ID; ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } static struct tpc_list * tpc_find_list(struct ctl_lun *lun, uint32_t list_id, uint32_t init_idx) { struct tpc_list *list; mtx_assert(&lun->lun_lock, MA_OWNED); TAILQ_FOREACH(list, &lun->tpc_lists, links) { if ((list->flags & EC_LIST_ID_USAGE_MASK) != EC_LIST_ID_USAGE_NONE && list->list_id == list_id && list->init_idx == init_idx) break; } return (list); } int ctl_receive_copy_status_lid1(struct ctl_scsiio *ctsio) { struct ctl_lun *lun; struct scsi_receive_copy_status_lid1 *cdb; struct scsi_receive_copy_status_lid1_data *data; struct tpc_list *list; struct tpc_list list_copy; int retval; int alloc_len, total_len; uint32_t list_id; CTL_DEBUG_PRINT(("ctl_receive_copy_status_lid1\n")); cdb = (struct scsi_receive_copy_status_lid1 *)ctsio->cdb; lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; retval = CTL_RETVAL_COMPLETE; list_id = cdb->list_identifier; mtx_lock(&lun->lun_lock); list = tpc_find_list(lun, list_id, ctl_get_resindex(&ctsio->io_hdr.nexus)); if (list == NULL) { mtx_unlock(&lun->lun_lock); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); ctl_done((union ctl_io *)ctsio); return (retval); } list_copy = *list; if (list->completed) { TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); } mtx_unlock(&lun->lun_lock); total_len = sizeof(*data); alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; if (total_len < alloc_len) { ctsio->residual = alloc_len - total_len; ctsio->kern_data_len = total_len; ctsio->kern_total_len = total_len; } 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; data = (struct scsi_receive_copy_status_lid1_data *)ctsio->kern_data_ptr; scsi_ulto4b(sizeof(*data) - 4, data->available_data); if (list_copy.completed) { if (list_copy.error || list_copy.abort) data->copy_command_status = RCS_CCS_ERROR; else data->copy_command_status = RCS_CCS_COMPLETED; } else data->copy_command_status = RCS_CCS_INPROG; scsi_ulto2b(list_copy.curseg, data->segments_processed); if (list_copy.curbytes <= UINT32_MAX) { data->transfer_count_units = RCS_TC_BYTES; scsi_ulto4b(list_copy.curbytes, data->transfer_count); } else { data->transfer_count_units = RCS_TC_MBYTES; scsi_ulto4b(list_copy.curbytes >> 20, data->transfer_count); } ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } int ctl_receive_copy_failure_details(struct ctl_scsiio *ctsio) { struct ctl_lun *lun; struct scsi_receive_copy_failure_details *cdb; struct scsi_receive_copy_failure_details_data *data; struct tpc_list *list; struct tpc_list list_copy; int retval; int alloc_len, total_len; uint32_t list_id; CTL_DEBUG_PRINT(("ctl_receive_copy_failure_details\n")); cdb = (struct scsi_receive_copy_failure_details *)ctsio->cdb; lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; retval = CTL_RETVAL_COMPLETE; list_id = cdb->list_identifier; mtx_lock(&lun->lun_lock); list = tpc_find_list(lun, list_id, ctl_get_resindex(&ctsio->io_hdr.nexus)); if (list == NULL || !list->completed) { mtx_unlock(&lun->lun_lock); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); ctl_done((union ctl_io *)ctsio); return (retval); } list_copy = *list; TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); mtx_unlock(&lun->lun_lock); total_len = sizeof(*data) + list_copy.sense_len; alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; if (total_len < alloc_len) { ctsio->residual = alloc_len - total_len; ctsio->kern_data_len = total_len; ctsio->kern_total_len = total_len; } 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; data = (struct scsi_receive_copy_failure_details_data *)ctsio->kern_data_ptr; if (list_copy.completed && (list_copy.error || list_copy.abort)) { scsi_ulto4b(sizeof(*data) - 4 + list_copy.sense_len, data->available_data); data->copy_command_status = RCS_CCS_ERROR; } else scsi_ulto4b(0, data->available_data); scsi_ulto2b(list_copy.sense_len, data->sense_data_length); memcpy(data->sense_data, &list_copy.sense_data, list_copy.sense_len); ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } int ctl_receive_copy_status_lid4(struct ctl_scsiio *ctsio) { struct ctl_lun *lun; struct scsi_receive_copy_status_lid4 *cdb; struct scsi_receive_copy_status_lid4_data *data; struct tpc_list *list; struct tpc_list list_copy; int retval; int alloc_len, total_len; uint32_t list_id; CTL_DEBUG_PRINT(("ctl_receive_copy_status_lid4\n")); cdb = (struct scsi_receive_copy_status_lid4 *)ctsio->cdb; lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; retval = CTL_RETVAL_COMPLETE; list_id = scsi_4btoul(cdb->list_identifier); mtx_lock(&lun->lun_lock); list = tpc_find_list(lun, list_id, ctl_get_resindex(&ctsio->io_hdr.nexus)); if (list == NULL) { mtx_unlock(&lun->lun_lock); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); ctl_done((union ctl_io *)ctsio); return (retval); } list_copy = *list; if (list->completed) { TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); } mtx_unlock(&lun->lun_lock); total_len = sizeof(*data) + list_copy.sense_len; alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; if (total_len < alloc_len) { ctsio->residual = alloc_len - total_len; ctsio->kern_data_len = total_len; ctsio->kern_total_len = total_len; } 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; data = (struct scsi_receive_copy_status_lid4_data *)ctsio->kern_data_ptr; scsi_ulto4b(sizeof(*data) - 4 + list_copy.sense_len, data->available_data); data->response_to_service_action = list_copy.service_action; if (list_copy.completed) { if (list_copy.error) data->copy_command_status = RCS_CCS_ERROR; else if (list_copy.abort) data->copy_command_status = RCS_CCS_ABORTED; else data->copy_command_status = RCS_CCS_COMPLETED; } else data->copy_command_status = RCS_CCS_INPROG_FG; scsi_ulto2b(list_copy.curops, data->operation_counter); scsi_ulto4b(UINT32_MAX, data->estimated_status_update_delay); data->transfer_count_units = RCS_TC_BYTES; scsi_u64to8b(list_copy.curbytes, data->transfer_count); scsi_ulto2b(list_copy.curseg, data->segments_processed); data->length_of_the_sense_data_field = list_copy.sense_len; data->sense_data_length = list_copy.sense_len; memcpy(data->sense_data, &list_copy.sense_data, list_copy.sense_len); ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } int ctl_copy_operation_abort(struct ctl_scsiio *ctsio) { struct ctl_lun *lun; struct scsi_copy_operation_abort *cdb; struct tpc_list *list; int retval; uint32_t list_id; CTL_DEBUG_PRINT(("ctl_copy_operation_abort\n")); cdb = (struct scsi_copy_operation_abort *)ctsio->cdb; lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; retval = CTL_RETVAL_COMPLETE; list_id = scsi_4btoul(cdb->list_identifier); mtx_lock(&lun->lun_lock); list = tpc_find_list(lun, list_id, ctl_get_resindex(&ctsio->io_hdr.nexus)); if (list == NULL) { mtx_unlock(&lun->lun_lock); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); ctl_done((union ctl_io *)ctsio); return (retval); } list->abort = 1; mtx_unlock(&lun->lun_lock); ctl_set_success(ctsio); ctl_done((union ctl_io *)ctsio); return (retval); } static uint64_t -tpc_resolve(struct tpc_list *list, uint16_t idx, uint32_t *ss) +tpc_resolve(struct tpc_list *list, uint16_t idx, uint32_t *ss, + uint32_t *pb, uint32_t *pbo) { if (idx == 0xffff) { if (ss && list->lun->be_lun) *ss = list->lun->be_lun->blocksize; + if (pb && list->lun->be_lun) + *pb = list->lun->be_lun->blocksize << + list->lun->be_lun->pblockexp; + if (pbo && list->lun->be_lun) + *pbo = list->lun->be_lun->blocksize * + list->lun->be_lun->pblockoff; return (list->lun->lun); } if (idx >= list->ncscd) return (UINT64_MAX); return (tpcl_resolve(list->lun->ctl_softc, - list->init_port, &list->cscd[idx], ss)); + list->init_port, &list->cscd[idx], ss, pb, pbo)); } static int tpc_process_b2b(struct tpc_list *list) { struct scsi_ec_segment_b2b *seg; struct scsi_ec_cscd_dtsp *sdstp, *ddstp; struct tpc_io *tior, *tiow; struct runl run, *prun; uint64_t sl, dl; off_t srclba, dstlba, numbytes, donebytes, roundbytes; int numlba; - uint32_t srcblock, dstblock; + uint32_t srcblock, dstblock, pb, pbo, adj; if (list->stage == 1) { while ((tior = TAILQ_FIRST(&list->allio)) != NULL) { TAILQ_REMOVE(&list->allio, tior, links); ctl_free_io(tior->io); free(tior, M_CTL); } free(list->buf, M_CTL); if (list->abort) { ctl_set_task_aborted(list->ctsio); return (CTL_RETVAL_ERROR); } else if (list->error) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x0d, /*ascq*/ 0x01, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } list->cursectors += list->segsectors; list->curbytes += list->segbytes; return (CTL_RETVAL_COMPLETE); } TAILQ_INIT(&list->allio); seg = (struct scsi_ec_segment_b2b *)list->seg[list->curseg]; - sl = tpc_resolve(list, scsi_2btoul(seg->src_cscd), &srcblock); - dl = tpc_resolve(list, scsi_2btoul(seg->dst_cscd), &dstblock); + sl = tpc_resolve(list, scsi_2btoul(seg->src_cscd), &srcblock, NULL, NULL); + dl = tpc_resolve(list, scsi_2btoul(seg->dst_cscd), &dstblock, &pb, &pbo); if (sl >= CTL_MAX_LUNS || dl >= CTL_MAX_LUNS) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x08, /*ascq*/ 0x04, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } + if (pbo > 0) + pbo = pb - pbo; sdstp = &list->cscd[scsi_2btoul(seg->src_cscd)].dtsp; if (scsi_3btoul(sdstp->block_length) != 0) srcblock = scsi_3btoul(sdstp->block_length); ddstp = &list->cscd[scsi_2btoul(seg->dst_cscd)].dtsp; if (scsi_3btoul(ddstp->block_length) != 0) dstblock = scsi_3btoul(ddstp->block_length); numlba = scsi_2btoul(seg->number_of_blocks); if (seg->flags & EC_SEG_DC) numbytes = (off_t)numlba * dstblock; else numbytes = (off_t)numlba * srcblock; srclba = scsi_8btou64(seg->src_lba); dstlba = scsi_8btou64(seg->dst_lba); // printf("Copy %ju bytes from %ju @ %ju to %ju @ %ju\n", // (uintmax_t)numbytes, sl, scsi_8btou64(seg->src_lba), // dl, scsi_8btou64(seg->dst_lba)); if (numbytes == 0) return (CTL_RETVAL_COMPLETE); if (numbytes % srcblock != 0 || numbytes % dstblock != 0) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x26, /*ascq*/ 0x0A, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } list->buf = malloc(numbytes, M_CTL, M_WAITOK); list->segbytes = numbytes; list->segsectors = numbytes / dstblock; donebytes = 0; TAILQ_INIT(&run); prun = &run; list->tbdio = 1; while (donebytes < numbytes) { - roundbytes = MIN(numbytes - donebytes, TPC_MAX_IO_SIZE); + roundbytes = numbytes - donebytes; + if (roundbytes > TPC_MAX_IO_SIZE) { + roundbytes = TPC_MAX_IO_SIZE; + roundbytes -= roundbytes % dstblock; + if (pb > dstblock) { + adj = (dstlba * dstblock + roundbytes - pbo) % pb; + if (roundbytes > adj) + roundbytes -= adj; + } + } tior = malloc(sizeof(*tior), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tior->run); tior->list = list; TAILQ_INSERT_TAIL(&list->allio, tior, links); tior->io = tpcl_alloc_io(); ctl_scsi_read_write(tior->io, /*data_ptr*/ &list->buf[donebytes], /*data_len*/ roundbytes, /*read_op*/ 1, /*byte2*/ 0, /*minimum_cdb_size*/ 0, - /*lba*/ srclba + donebytes / srcblock, + /*lba*/ srclba, /*num_blocks*/ roundbytes / srcblock, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tior->io->io_hdr.retries = 3; tior->lun = sl; tior->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tior; tiow = malloc(sizeof(*tior), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tiow->run); tiow->list = list; TAILQ_INSERT_TAIL(&list->allio, tiow, links); tiow->io = tpcl_alloc_io(); ctl_scsi_read_write(tiow->io, /*data_ptr*/ &list->buf[donebytes], /*data_len*/ roundbytes, /*read_op*/ 0, /*byte2*/ 0, /*minimum_cdb_size*/ 0, - /*lba*/ dstlba + donebytes / dstblock, + /*lba*/ dstlba, /*num_blocks*/ roundbytes / dstblock, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tiow->io->io_hdr.retries = 3; tiow->lun = dl; tiow->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tiow; TAILQ_INSERT_TAIL(&tior->run, tiow, rlinks); TAILQ_INSERT_TAIL(prun, tior, rlinks); prun = &tior->run; donebytes += roundbytes; + srclba += roundbytes / srcblock; + dstlba += roundbytes / dstblock; } while ((tior = TAILQ_FIRST(&run)) != NULL) { TAILQ_REMOVE(&run, tior, rlinks); if (tpcl_queue(tior->io, tior->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); } list->stage++; return (CTL_RETVAL_QUEUED); } static int tpc_process_verify(struct tpc_list *list) { struct scsi_ec_segment_verify *seg; struct tpc_io *tio; uint64_t sl; if (list->stage == 1) { while ((tio = TAILQ_FIRST(&list->allio)) != NULL) { TAILQ_REMOVE(&list->allio, tio, links); ctl_free_io(tio->io); free(tio, M_CTL); } if (list->abort) { ctl_set_task_aborted(list->ctsio); return (CTL_RETVAL_ERROR); } else if (list->error) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x0d, /*ascq*/ 0x01, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } else return (CTL_RETVAL_COMPLETE); } TAILQ_INIT(&list->allio); seg = (struct scsi_ec_segment_verify *)list->seg[list->curseg]; - sl = tpc_resolve(list, scsi_2btoul(seg->src_cscd), NULL); + sl = tpc_resolve(list, scsi_2btoul(seg->src_cscd), NULL, NULL, NULL); if (sl >= CTL_MAX_LUNS) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x08, /*ascq*/ 0x04, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } // printf("Verify %ju\n", sl); if ((seg->tur & 0x01) == 0) return (CTL_RETVAL_COMPLETE); list->tbdio = 1; tio = malloc(sizeof(*tio), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tio->run); tio->list = list; TAILQ_INSERT_TAIL(&list->allio, tio, links); tio->io = tpcl_alloc_io(); ctl_scsi_tur(tio->io, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tio->io->io_hdr.retries = 3; tio->lun = sl; tio->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tio; list->stage++; if (tpcl_queue(tio->io, tio->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); return (CTL_RETVAL_QUEUED); } static int tpc_process_register_key(struct tpc_list *list) { struct scsi_ec_segment_register_key *seg; struct tpc_io *tio; uint64_t dl; int datalen; if (list->stage == 1) { while ((tio = TAILQ_FIRST(&list->allio)) != NULL) { TAILQ_REMOVE(&list->allio, tio, links); ctl_free_io(tio->io); free(tio, M_CTL); } free(list->buf, M_CTL); if (list->abort) { ctl_set_task_aborted(list->ctsio); return (CTL_RETVAL_ERROR); } else if (list->error) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x0d, /*ascq*/ 0x01, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } else return (CTL_RETVAL_COMPLETE); } TAILQ_INIT(&list->allio); seg = (struct scsi_ec_segment_register_key *)list->seg[list->curseg]; - dl = tpc_resolve(list, scsi_2btoul(seg->dst_cscd), NULL); + dl = tpc_resolve(list, scsi_2btoul(seg->dst_cscd), NULL, NULL, NULL); if (dl >= CTL_MAX_LUNS) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x08, /*ascq*/ 0x04, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } // printf("Register Key %ju\n", dl); list->tbdio = 1; tio = malloc(sizeof(*tio), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tio->run); tio->list = list; TAILQ_INSERT_TAIL(&list->allio, tio, links); tio->io = tpcl_alloc_io(); datalen = sizeof(struct scsi_per_res_out_parms); list->buf = malloc(datalen, M_CTL, M_WAITOK); ctl_scsi_persistent_res_out(tio->io, list->buf, datalen, SPRO_REGISTER, -1, scsi_8btou64(seg->res_key), scsi_8btou64(seg->sa_res_key), /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tio->io->io_hdr.retries = 3; tio->lun = dl; tio->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tio; list->stage++; if (tpcl_queue(tio->io, tio->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); return (CTL_RETVAL_QUEUED); } static off_t tpc_ranges_length(struct scsi_range_desc *range, int nrange) { off_t length = 0; int r; for (r = 0; r < nrange; r++) length += scsi_4btoul(range[r].length); return (length); } static int tpc_skip_ranges(struct scsi_range_desc *range, int nrange, off_t skip, int *srange, off_t *soffset) { off_t off; int r; r = 0; off = 0; while (r < nrange) { if (skip - off < scsi_4btoul(range[r].length)) { *srange = r; *soffset = skip - off; return (0); } off += scsi_4btoul(range[r].length); r++; } return (-1); } static int tpc_process_wut(struct tpc_list *list) { struct tpc_io *tio, *tior, *tiow; struct runl run, *prun; int drange, srange; off_t doffset, soffset; off_t srclba, dstlba, numbytes, donebytes, roundbytes; - uint32_t srcblock, dstblock; + uint32_t srcblock, dstblock, pb, pbo, adj; if (list->stage > 0) { /* Cleanup after previous rounds. */ while ((tio = TAILQ_FIRST(&list->allio)) != NULL) { TAILQ_REMOVE(&list->allio, tio, links); ctl_free_io(tio->io); free(tio, M_CTL); } free(list->buf, M_CTL); if (list->abort) { ctl_set_task_aborted(list->ctsio); return (CTL_RETVAL_ERROR); } else if (list->error) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x0d, /*ascq*/ 0x01, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } list->cursectors += list->segsectors; list->curbytes += list->segbytes; } /* Check where we are on destination ranges list. */ if (tpc_skip_ranges(list->range, list->nrange, list->cursectors, &drange, &doffset) != 0) return (CTL_RETVAL_COMPLETE); dstblock = list->lun->be_lun->blocksize; + pb = dstblock << list->lun->be_lun->pblockexp; + if (list->lun->be_lun->pblockoff > 0) + pbo = pb - dstblock * list->lun->be_lun->pblockoff; + else + pbo = 0; /* Check where we are on source ranges list. */ srcblock = list->token->blocksize; if (tpc_skip_ranges(list->token->range, list->token->nrange, list->offset_into_rod + list->cursectors * dstblock / srcblock, &srange, &soffset) != 0) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x0d, /*ascq*/ 0x04, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } srclba = scsi_8btou64(list->token->range[srange].lba) + soffset; - numbytes = srcblock * omin(TPC_MAX_IOCHUNK_SIZE / srcblock, - (scsi_4btoul(list->token->range[srange].length) - soffset)); dstlba = scsi_8btou64(list->range[drange].lba) + doffset; - numbytes = omin(numbytes, - dstblock * omin(TPC_MAX_IOCHUNK_SIZE / dstblock, - (scsi_4btoul(list->range[drange].length) - doffset))); + numbytes = srcblock * + (scsi_4btoul(list->token->range[srange].length) - soffset); + numbytes = omin(numbytes, dstblock * + (scsi_4btoul(list->range[drange].length) - doffset)); + if (numbytes > TPC_MAX_IOCHUNK_SIZE) { + numbytes = TPC_MAX_IOCHUNK_SIZE; + numbytes -= numbytes % dstblock; + if (pb > dstblock) { + adj = (dstlba * dstblock + numbytes - pbo) % pb; + if (numbytes > adj) + numbytes -= adj; + } + } if (numbytes % srcblock != 0 || numbytes % dstblock != 0) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x26, /*ascq*/ 0x0A, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } list->buf = malloc(numbytes, M_CTL, M_WAITOK | (list->token == NULL ? M_ZERO : 0)); list->segbytes = numbytes; list->segsectors = numbytes / dstblock; //printf("Copy chunk of %ju sectors from %ju to %ju\n", list->segsectors, // srclba, dstlba); donebytes = 0; TAILQ_INIT(&run); prun = &run; list->tbdio = 1; TAILQ_INIT(&list->allio); while (donebytes < numbytes) { - roundbytes = MIN(numbytes - donebytes, TPC_MAX_IO_SIZE); + roundbytes = numbytes - donebytes; + if (roundbytes > TPC_MAX_IO_SIZE) { + roundbytes = TPC_MAX_IO_SIZE; + roundbytes -= roundbytes % dstblock; + if (pb > dstblock) { + adj = (dstlba * dstblock + roundbytes - pbo) % pb; + if (roundbytes > adj) + roundbytes -= adj; + } + } tior = malloc(sizeof(*tior), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tior->run); tior->list = list; TAILQ_INSERT_TAIL(&list->allio, tior, links); tior->io = tpcl_alloc_io(); ctl_scsi_read_write(tior->io, /*data_ptr*/ &list->buf[donebytes], /*data_len*/ roundbytes, /*read_op*/ 1, /*byte2*/ 0, /*minimum_cdb_size*/ 0, - /*lba*/ srclba + donebytes / srcblock, + /*lba*/ srclba, /*num_blocks*/ roundbytes / srcblock, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tior->io->io_hdr.retries = 3; tior->lun = list->token->lun; tior->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tior; tiow = malloc(sizeof(*tiow), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tiow->run); tiow->list = list; TAILQ_INSERT_TAIL(&list->allio, tiow, links); tiow->io = tpcl_alloc_io(); ctl_scsi_read_write(tiow->io, /*data_ptr*/ &list->buf[donebytes], /*data_len*/ roundbytes, /*read_op*/ 0, /*byte2*/ 0, /*minimum_cdb_size*/ 0, - /*lba*/ dstlba + donebytes / dstblock, + /*lba*/ dstlba, /*num_blocks*/ roundbytes / dstblock, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tiow->io->io_hdr.retries = 3; tiow->lun = list->lun->lun; tiow->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tiow; TAILQ_INSERT_TAIL(&tior->run, tiow, rlinks); TAILQ_INSERT_TAIL(prun, tior, rlinks); prun = &tior->run; donebytes += roundbytes; + srclba += roundbytes / srcblock; + dstlba += roundbytes / dstblock; } while ((tior = TAILQ_FIRST(&run)) != NULL) { TAILQ_REMOVE(&run, tior, rlinks); if (tpcl_queue(tior->io, tior->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); } list->stage++; return (CTL_RETVAL_QUEUED); } static int tpc_process_zero_wut(struct tpc_list *list) { struct tpc_io *tio, *tiow; struct runl run, *prun; int r; uint32_t dstblock, len; if (list->stage > 0) { complete: /* Cleanup after previous rounds. */ while ((tio = TAILQ_FIRST(&list->allio)) != NULL) { TAILQ_REMOVE(&list->allio, tio, links); ctl_free_io(tio->io); free(tio, M_CTL); } free(list->buf, M_CTL); if (list->abort) { ctl_set_task_aborted(list->ctsio); return (CTL_RETVAL_ERROR); } else if (list->error) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x0d, /*ascq*/ 0x01, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } list->cursectors += list->segsectors; list->curbytes += list->segbytes; return (CTL_RETVAL_COMPLETE); } dstblock = list->lun->be_lun->blocksize; list->buf = malloc(dstblock, M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&run); prun = &run; list->tbdio = 1; TAILQ_INIT(&list->allio); list->segsectors = 0; for (r = 0; r < list->nrange; r++) { len = scsi_4btoul(list->range[r].length); if (len == 0) continue; tiow = malloc(sizeof(*tiow), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tiow->run); tiow->list = list; TAILQ_INSERT_TAIL(&list->allio, tiow, links); tiow->io = tpcl_alloc_io(); ctl_scsi_write_same(tiow->io, /*data_ptr*/ list->buf, /*data_len*/ dstblock, /*byte2*/ 0, /*lba*/ scsi_8btou64(list->range[r].lba), /*num_blocks*/ len, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tiow->io->io_hdr.retries = 3; tiow->lun = list->lun->lun; tiow->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tiow; TAILQ_INSERT_TAIL(prun, tiow, rlinks); prun = &tiow->run; list->segsectors += len; } list->segbytes = list->segsectors * dstblock; if (TAILQ_EMPTY(&run)) goto complete; while ((tiow = TAILQ_FIRST(&run)) != NULL) { TAILQ_REMOVE(&run, tiow, rlinks); if (tpcl_queue(tiow->io, tiow->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); } list->stage++; return (CTL_RETVAL_QUEUED); } static void tpc_process(struct tpc_list *list) { struct ctl_lun *lun = list->lun; struct ctl_softc *softc = lun->ctl_softc; struct scsi_ec_segment *seg; struct ctl_scsiio *ctsio = list->ctsio; int retval = CTL_RETVAL_COMPLETE; if (list->service_action == EC_WUT) { if (list->token != NULL) retval = tpc_process_wut(list); else retval = tpc_process_zero_wut(list); if (retval == CTL_RETVAL_QUEUED) return; if (retval == CTL_RETVAL_ERROR) { list->error = 1; goto done; } } else { //printf("ZZZ %d cscd, %d segs\n", list->ncscd, list->nseg); while (list->curseg < list->nseg) { seg = list->seg[list->curseg]; switch (seg->type_code) { case EC_SEG_B2B: retval = tpc_process_b2b(list); break; case EC_SEG_VERIFY: retval = tpc_process_verify(list); break; case EC_SEG_REGISTER_KEY: retval = tpc_process_register_key(list); break; default: ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x26, /*ascq*/ 0x09, SSD_ELEM_NONE); goto done; } if (retval == CTL_RETVAL_QUEUED) return; if (retval == CTL_RETVAL_ERROR) { list->error = 1; goto done; } list->curseg++; list->stage = 0; } } ctl_set_success(ctsio); done: //printf("ZZZ done\n"); free(list->params, M_CTL); list->params = NULL; if (list->token) { mtx_lock(&softc->ctl_lock); if (--list->token->active == 0) list->token->last_active = time_uptime; mtx_unlock(&softc->ctl_lock); list->token = NULL; } mtx_lock(&lun->lun_lock); if ((list->flags & EC_LIST_ID_USAGE_MASK) == EC_LIST_ID_USAGE_NONE) { TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); } else { list->completed = 1; list->last_active = time_uptime; list->sense_data = ctsio->sense_data; list->sense_len = ctsio->sense_len; list->scsi_status = ctsio->scsi_status; } mtx_unlock(&lun->lun_lock); ctl_done((union ctl_io *)ctsio); } /* * For any sort of check condition, busy, etc., we just retry. We do not * decrement the retry count for unit attention type errors. These are * normal, and we want to save the retry count for "real" errors. Otherwise, * we could end up with situations where a command will succeed in some * situations and fail in others, depending on whether a unit attention is * pending. Also, some of our error recovery actions, most notably the * LUN reset action, will cause a unit attention. * * We can add more detail here later if necessary. */ static tpc_error_action tpc_checkcond_parse(union ctl_io *io) { tpc_error_action error_action; int error_code, sense_key, asc, ascq; /* * Default to retrying the command. */ error_action = TPC_ERR_RETRY; scsi_extract_sense_len(&io->scsiio.sense_data, io->scsiio.sense_len, &error_code, &sense_key, &asc, &ascq, /*show_errors*/ 1); switch (error_code) { case SSD_DEFERRED_ERROR: case SSD_DESC_DEFERRED_ERROR: error_action |= TPC_ERR_NO_DECREMENT; break; case SSD_CURRENT_ERROR: case SSD_DESC_CURRENT_ERROR: default: switch (sense_key) { case SSD_KEY_UNIT_ATTENTION: error_action |= TPC_ERR_NO_DECREMENT; break; case SSD_KEY_HARDWARE_ERROR: /* * This is our generic "something bad happened" * error code. It often isn't recoverable. */ if ((asc == 0x44) && (ascq == 0x00)) error_action = TPC_ERR_FAIL; break; case SSD_KEY_NOT_READY: /* * If the LUN is powered down, there likely isn't * much point in retrying right now. */ if ((asc == 0x04) && (ascq == 0x02)) error_action = TPC_ERR_FAIL; /* * If the LUN is offline, there probably isn't much * point in retrying, either. */ if ((asc == 0x04) && (ascq == 0x03)) error_action = TPC_ERR_FAIL; break; } } return (error_action); } static tpc_error_action tpc_error_parse(union ctl_io *io) { tpc_error_action error_action = TPC_ERR_RETRY; switch (io->io_hdr.io_type) { case CTL_IO_SCSI: switch (io->io_hdr.status & CTL_STATUS_MASK) { case CTL_SCSI_ERROR: switch (io->scsiio.scsi_status) { case SCSI_STATUS_CHECK_COND: error_action = tpc_checkcond_parse(io); break; default: break; } break; default: break; } break; case CTL_IO_TASK: break; default: panic("%s: invalid ctl_io type %d\n", __func__, io->io_hdr.io_type); break; } return (error_action); } void tpc_done(union ctl_io *io) { struct tpc_io *tio, *tior; /* * Very minimal retry logic. We basically retry if we got an error * back, and the retry count is greater than 0. If we ever want * more sophisticated initiator type behavior, the CAM error * recovery code in ../common might be helpful. */ tio = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; if (((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS) && (io->io_hdr.retries > 0)) { ctl_io_status old_status; tpc_error_action error_action; error_action = tpc_error_parse(io); switch (error_action & TPC_ERR_MASK) { case TPC_ERR_FAIL: break; case TPC_ERR_RETRY: default: if ((error_action & TPC_ERR_NO_DECREMENT) == 0) io->io_hdr.retries--; old_status = io->io_hdr.status; io->io_hdr.status = CTL_STATUS_NONE; io->io_hdr.flags &= ~CTL_FLAG_ABORT; io->io_hdr.flags &= ~CTL_FLAG_SENT_2OTHER_SC; if (tpcl_queue(io, tio->lun) != CTL_RETVAL_COMPLETE) { printf("%s: error returned from ctl_queue()!\n", __func__); io->io_hdr.status = old_status; } else return; } } if ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS) tio->list->error = 1; else atomic_add_int(&tio->list->curops, 1); if (!tio->list->error && !tio->list->abort) { while ((tior = TAILQ_FIRST(&tio->run)) != NULL) { TAILQ_REMOVE(&tio->run, tior, rlinks); atomic_add_int(&tio->list->tbdio, 1); if (tpcl_queue(tior->io, tior->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); } } if (atomic_fetchadd_int(&tio->list->tbdio, -1) == 1) tpc_process(tio->list); } int ctl_extended_copy_lid1(struct ctl_scsiio *ctsio) { struct scsi_extended_copy *cdb; struct scsi_extended_copy_lid1_data *data; struct ctl_lun *lun; struct tpc_list *list, *tlist; uint8_t *ptr; char *value; int len, off, lencscd, lenseg, leninl, nseg; CTL_DEBUG_PRINT(("ctl_extended_copy_lid1\n")); lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; cdb = (struct scsi_extended_copy *)ctsio->cdb; len = scsi_4btoul(cdb->length); if (len < sizeof(struct scsi_extended_copy_lid1_data) || len > sizeof(struct scsi_extended_copy_lid1_data) + TPC_MAX_LIST + TPC_MAX_INLINE) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 9, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* * 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); } data = (struct scsi_extended_copy_lid1_data *)ctsio->kern_data_ptr; lencscd = scsi_2btoul(data->cscd_list_length); lenseg = scsi_4btoul(data->segment_list_length); leninl = scsi_4btoul(data->inline_data_length); if (len < sizeof(struct scsi_extended_copy_lid1_data) + lencscd + lenseg + leninl || leninl > TPC_MAX_INLINE) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); goto done; } if (lencscd > TPC_MAX_CSCDS * sizeof(struct scsi_ec_cscd)) { ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x06, SSD_ELEM_NONE); goto done; } if (lencscd + lenseg > TPC_MAX_LIST) { ctl_set_param_len_error(ctsio); goto done; } list = malloc(sizeof(struct tpc_list), M_CTL, M_WAITOK | M_ZERO); list->service_action = cdb->service_action; value = ctl_get_opt(&lun->be_lun->options, "insecure_tpc"); if (value != NULL && strcmp(value, "on") == 0) list->init_port = -1; else list->init_port = ctsio->io_hdr.nexus.targ_port; list->init_idx = ctl_get_resindex(&ctsio->io_hdr.nexus); list->list_id = data->list_identifier; list->flags = data->flags; list->params = ctsio->kern_data_ptr; list->cscd = (struct scsi_ec_cscd *)&data->data[0]; ptr = &data->data[lencscd]; for (nseg = 0, off = 0; off < lenseg; nseg++) { if (nseg >= TPC_MAX_SEGS) { free(list, M_CTL); ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x08, SSD_ELEM_NONE); goto done; } list->seg[nseg] = (struct scsi_ec_segment *)(ptr + off); off += sizeof(struct scsi_ec_segment) + scsi_2btoul(list->seg[nseg]->descr_length); } list->inl = &data->data[lencscd + lenseg]; list->ncscd = lencscd / sizeof(struct scsi_ec_cscd); list->nseg = nseg; list->leninl = leninl; list->ctsio = ctsio; list->lun = lun; mtx_lock(&lun->lun_lock); if ((list->flags & EC_LIST_ID_USAGE_MASK) != EC_LIST_ID_USAGE_NONE) { tlist = tpc_find_list(lun, list->list_id, list->init_idx); if (tlist != NULL && !tlist->completed) { mtx_unlock(&lun->lun_lock); free(list, M_CTL); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } if (tlist != NULL) { TAILQ_REMOVE(&lun->tpc_lists, tlist, links); free(tlist, M_CTL); } } TAILQ_INSERT_TAIL(&lun->tpc_lists, list, links); mtx_unlock(&lun->lun_lock); tpc_process(list); return (CTL_RETVAL_COMPLETE); done: if (ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) { free(ctsio->kern_data_ptr, M_CTL); ctsio->io_hdr.flags &= ~CTL_FLAG_ALLOCATED; } ctl_done((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } int ctl_extended_copy_lid4(struct ctl_scsiio *ctsio) { struct scsi_extended_copy *cdb; struct scsi_extended_copy_lid4_data *data; struct ctl_lun *lun; struct tpc_list *list, *tlist; uint8_t *ptr; char *value; int len, off, lencscd, lenseg, leninl, nseg; CTL_DEBUG_PRINT(("ctl_extended_copy_lid4\n")); lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; cdb = (struct scsi_extended_copy *)ctsio->cdb; len = scsi_4btoul(cdb->length); if (len < sizeof(struct scsi_extended_copy_lid4_data) || len > sizeof(struct scsi_extended_copy_lid4_data) + TPC_MAX_LIST + TPC_MAX_INLINE) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 9, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* * 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); } data = (struct scsi_extended_copy_lid4_data *)ctsio->kern_data_ptr; lencscd = scsi_2btoul(data->cscd_list_length); lenseg = scsi_2btoul(data->segment_list_length); leninl = scsi_2btoul(data->inline_data_length); if (len < sizeof(struct scsi_extended_copy_lid4_data) + lencscd + lenseg + leninl || leninl > TPC_MAX_INLINE) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); goto done; } if (lencscd > TPC_MAX_CSCDS * sizeof(struct scsi_ec_cscd)) { ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x06, SSD_ELEM_NONE); goto done; } if (lencscd + lenseg > TPC_MAX_LIST) { ctl_set_param_len_error(ctsio); goto done; } list = malloc(sizeof(struct tpc_list), M_CTL, M_WAITOK | M_ZERO); list->service_action = cdb->service_action; value = ctl_get_opt(&lun->be_lun->options, "insecure_tpc"); if (value != NULL && strcmp(value, "on") == 0) list->init_port = -1; else list->init_port = ctsio->io_hdr.nexus.targ_port; list->init_idx = ctl_get_resindex(&ctsio->io_hdr.nexus); list->list_id = scsi_4btoul(data->list_identifier); list->flags = data->flags; list->params = ctsio->kern_data_ptr; list->cscd = (struct scsi_ec_cscd *)&data->data[0]; ptr = &data->data[lencscd]; for (nseg = 0, off = 0; off < lenseg; nseg++) { if (nseg >= TPC_MAX_SEGS) { free(list, M_CTL); ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x08, SSD_ELEM_NONE); goto done; } list->seg[nseg] = (struct scsi_ec_segment *)(ptr + off); off += sizeof(struct scsi_ec_segment) + scsi_2btoul(list->seg[nseg]->descr_length); } list->inl = &data->data[lencscd + lenseg]; list->ncscd = lencscd / sizeof(struct scsi_ec_cscd); list->nseg = nseg; list->leninl = leninl; list->ctsio = ctsio; list->lun = lun; mtx_lock(&lun->lun_lock); if ((list->flags & EC_LIST_ID_USAGE_MASK) != EC_LIST_ID_USAGE_NONE) { tlist = tpc_find_list(lun, list->list_id, list->init_idx); if (tlist != NULL && !tlist->completed) { mtx_unlock(&lun->lun_lock); free(list, M_CTL); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } if (tlist != NULL) { TAILQ_REMOVE(&lun->tpc_lists, tlist, links); free(tlist, M_CTL); } } TAILQ_INSERT_TAIL(&lun->tpc_lists, list, links); mtx_unlock(&lun->lun_lock); tpc_process(list); return (CTL_RETVAL_COMPLETE); done: if (ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) { free(ctsio->kern_data_ptr, M_CTL); ctsio->io_hdr.flags &= ~CTL_FLAG_ALLOCATED; } ctl_done((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } static void tpc_create_token(struct ctl_lun *lun, struct ctl_port *port, off_t len, struct scsi_token *token) { static int id = 0; struct scsi_vpd_id_descriptor *idd = NULL; struct scsi_ec_cscd_id *cscd; struct scsi_read_capacity_data_long *dtsd; int targid_len; scsi_ulto4b(ROD_TYPE_AUR, token->type); scsi_ulto2b(0x01f8, token->length); scsi_u64to8b(atomic_fetchadd_int(&id, 1), &token->body[0]); if (lun->lun_devid) idd = scsi_get_devid_desc((struct scsi_vpd_id_descriptor *) lun->lun_devid->data, lun->lun_devid->len, scsi_devid_is_lun_naa); if (idd == NULL && lun->lun_devid) idd = scsi_get_devid_desc((struct scsi_vpd_id_descriptor *) lun->lun_devid->data, lun->lun_devid->len, scsi_devid_is_lun_eui64); if (idd != NULL) { cscd = (struct scsi_ec_cscd_id *)&token->body[8]; cscd->type_code = EC_CSCD_ID; cscd->luidt_pdt = T_DIRECT; memcpy(&cscd->codeset, idd, 4 + idd->length); scsi_ulto3b(lun->be_lun->blocksize, cscd->dtsp.block_length); } scsi_u64to8b(0, &token->body[40]); /* XXX: Should be 128bit value. */ scsi_u64to8b(len, &token->body[48]); /* ROD token device type specific data (RC16 without first field) */ dtsd = (struct scsi_read_capacity_data_long *)&token->body[88 - 8]; scsi_ulto4b(lun->be_lun->blocksize, dtsd->length); dtsd->prot_lbppbe = lun->be_lun->pblockexp & SRC16_LBPPBE; scsi_ulto2b(lun->be_lun->pblockoff & SRC16_LALBA_A, dtsd->lalba_lbp); if (lun->be_lun->flags & CTL_LUN_FLAG_UNMAP) dtsd->lalba_lbp[0] |= SRC16_LBPME | SRC16_LBPRZ; if (port->target_devid) { targid_len = port->target_devid->len; memcpy(&token->body[120], port->target_devid->data, targid_len); } else targid_len = 32; arc4rand(&token->body[120 + targid_len], 384 - targid_len, 0); }; int ctl_populate_token(struct ctl_scsiio *ctsio) { struct scsi_populate_token *cdb; struct scsi_populate_token_data *data; struct ctl_softc *softc; struct ctl_lun *lun; struct ctl_port *port; struct tpc_list *list, *tlist; struct tpc_token *token; int len, lendesc; CTL_DEBUG_PRINT(("ctl_populate_token\n")); lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; softc = lun->ctl_softc; port = softc->ctl_ports[ctl_port_idx(ctsio->io_hdr.nexus.targ_port)]; cdb = (struct scsi_populate_token *)ctsio->cdb; len = scsi_4btoul(cdb->length); if (len < sizeof(struct scsi_populate_token_data) || len > sizeof(struct scsi_populate_token_data) + TPC_MAX_SEGS * sizeof(struct scsi_range_desc)) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 9, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* * 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); } data = (struct scsi_populate_token_data *)ctsio->kern_data_ptr; lendesc = scsi_2btoul(data->range_descriptor_length); if (len < sizeof(struct scsi_populate_token_data) + lendesc) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* printf("PT(list=%u) flags=%x to=%d rt=%x len=%x\n", scsi_4btoul(cdb->list_identifier), data->flags, scsi_4btoul(data->inactivity_timeout), scsi_4btoul(data->rod_type), scsi_2btoul(data->range_descriptor_length)); */ if ((data->flags & EC_PT_RTV) && scsi_4btoul(data->rod_type) != ROD_TYPE_AUR) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 8, /*bit_valid*/ 0, /*bit*/ 0); goto done; } list = malloc(sizeof(struct tpc_list), M_CTL, M_WAITOK | M_ZERO); list->service_action = cdb->service_action; list->init_port = ctsio->io_hdr.nexus.targ_port; list->init_idx = ctl_get_resindex(&ctsio->io_hdr.nexus); list->list_id = scsi_4btoul(cdb->list_identifier); list->flags = data->flags; list->ctsio = ctsio; list->lun = lun; mtx_lock(&lun->lun_lock); tlist = tpc_find_list(lun, list->list_id, list->init_idx); if (tlist != NULL && !tlist->completed) { mtx_unlock(&lun->lun_lock); free(list, M_CTL); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } if (tlist != NULL) { TAILQ_REMOVE(&lun->tpc_lists, tlist, links); free(tlist, M_CTL); } TAILQ_INSERT_TAIL(&lun->tpc_lists, list, links); mtx_unlock(&lun->lun_lock); token = malloc(sizeof(*token), M_CTL, M_WAITOK | M_ZERO); token->lun = lun->lun; token->blocksize = lun->be_lun->blocksize; token->params = ctsio->kern_data_ptr; token->range = &data->desc[0]; token->nrange = scsi_2btoul(data->range_descriptor_length) / sizeof(struct scsi_range_desc); list->cursectors = tpc_ranges_length(token->range, token->nrange); list->curbytes = (off_t)list->cursectors * lun->be_lun->blocksize; tpc_create_token(lun, port, list->curbytes, (struct scsi_token *)token->token); token->active = 0; token->last_active = time_uptime; token->timeout = scsi_4btoul(data->inactivity_timeout); if (token->timeout == 0) token->timeout = TPC_DFL_TOKEN_TIMEOUT; else if (token->timeout < TPC_MIN_TOKEN_TIMEOUT) token->timeout = TPC_MIN_TOKEN_TIMEOUT; else if (token->timeout > TPC_MAX_TOKEN_TIMEOUT) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 4, /*bit_valid*/ 0, /*bit*/ 0); } memcpy(list->res_token, token->token, sizeof(list->res_token)); list->res_token_valid = 1; list->curseg = 0; list->completed = 1; list->last_active = time_uptime; mtx_lock(&softc->ctl_lock); TAILQ_INSERT_TAIL(&softc->tpc_tokens, token, links); mtx_unlock(&softc->ctl_lock); ctl_set_success(ctsio); ctl_done((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); done: if (ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) { free(ctsio->kern_data_ptr, M_CTL); ctsio->io_hdr.flags &= ~CTL_FLAG_ALLOCATED; } ctl_done((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } int ctl_write_using_token(struct ctl_scsiio *ctsio) { struct scsi_write_using_token *cdb; struct scsi_write_using_token_data *data; struct ctl_softc *softc; struct ctl_lun *lun; struct tpc_list *list, *tlist; struct tpc_token *token; int len, lendesc; CTL_DEBUG_PRINT(("ctl_write_using_token\n")); lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; softc = lun->ctl_softc; cdb = (struct scsi_write_using_token *)ctsio->cdb; len = scsi_4btoul(cdb->length); if (len < sizeof(struct scsi_populate_token_data) || len > sizeof(struct scsi_populate_token_data) + TPC_MAX_SEGS * sizeof(struct scsi_range_desc)) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 9, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* * 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); } data = (struct scsi_write_using_token_data *)ctsio->kern_data_ptr; lendesc = scsi_2btoul(data->range_descriptor_length); if (len < sizeof(struct scsi_populate_token_data) + lendesc) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* printf("WUT(list=%u) flags=%x off=%ju len=%x\n", scsi_4btoul(cdb->list_identifier), data->flags, scsi_8btou64(data->offset_into_rod), scsi_2btoul(data->range_descriptor_length)); */ list = malloc(sizeof(struct tpc_list), M_CTL, M_WAITOK | M_ZERO); list->service_action = cdb->service_action; list->init_port = ctsio->io_hdr.nexus.targ_port; list->init_idx = ctl_get_resindex(&ctsio->io_hdr.nexus); list->list_id = scsi_4btoul(cdb->list_identifier); list->flags = data->flags; list->params = ctsio->kern_data_ptr; list->range = &data->desc[0]; list->nrange = scsi_2btoul(data->range_descriptor_length) / sizeof(struct scsi_range_desc); list->offset_into_rod = scsi_8btou64(data->offset_into_rod); list->ctsio = ctsio; list->lun = lun; mtx_lock(&lun->lun_lock); tlist = tpc_find_list(lun, list->list_id, list->init_idx); if (tlist != NULL && !tlist->completed) { mtx_unlock(&lun->lun_lock); free(list, M_CTL); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } if (tlist != NULL) { TAILQ_REMOVE(&lun->tpc_lists, tlist, links); free(tlist, M_CTL); } TAILQ_INSERT_TAIL(&lun->tpc_lists, list, links); mtx_unlock(&lun->lun_lock); /* Block device zero ROD token -> no token. */ if (scsi_4btoul(data->rod_token) == ROD_TYPE_BLOCK_ZERO) { tpc_process(list); return (CTL_RETVAL_COMPLETE); } mtx_lock(&softc->ctl_lock); TAILQ_FOREACH(token, &softc->tpc_tokens, links) { if (memcmp(token->token, data->rod_token, sizeof(data->rod_token)) == 0) break; } if (token != NULL) { token->active++; list->token = token; if (data->flags & EC_WUT_DEL_TKN) token->timeout = 0; } mtx_unlock(&softc->ctl_lock); if (token == NULL) { mtx_lock(&lun->lun_lock); TAILQ_REMOVE(&lun->tpc_lists, list, links); mtx_unlock(&lun->lun_lock); free(list, M_CTL); ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x23, /*ascq*/ 0x04, SSD_ELEM_NONE); goto done; } tpc_process(list); return (CTL_RETVAL_COMPLETE); done: if (ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) { free(ctsio->kern_data_ptr, M_CTL); ctsio->io_hdr.flags &= ~CTL_FLAG_ALLOCATED; } ctl_done((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } int ctl_receive_rod_token_information(struct ctl_scsiio *ctsio) { struct ctl_lun *lun; struct scsi_receive_rod_token_information *cdb; struct scsi_receive_copy_status_lid4_data *data; struct tpc_list *list; struct tpc_list list_copy; uint8_t *ptr; int retval; int alloc_len, total_len, token_len; uint32_t list_id; CTL_DEBUG_PRINT(("ctl_receive_rod_token_information\n")); cdb = (struct scsi_receive_rod_token_information *)ctsio->cdb; lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; retval = CTL_RETVAL_COMPLETE; list_id = scsi_4btoul(cdb->list_identifier); mtx_lock(&lun->lun_lock); list = tpc_find_list(lun, list_id, ctl_get_resindex(&ctsio->io_hdr.nexus)); if (list == NULL) { mtx_unlock(&lun->lun_lock); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); ctl_done((union ctl_io *)ctsio); return (retval); } list_copy = *list; if (list->completed) { TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); } mtx_unlock(&lun->lun_lock); token_len = list_copy.res_token_valid ? 2 + sizeof(list_copy.res_token) : 0; total_len = sizeof(*data) + list_copy.sense_len + 4 + token_len; alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; if (total_len < alloc_len) { ctsio->residual = alloc_len - total_len; ctsio->kern_data_len = total_len; ctsio->kern_total_len = total_len; } 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; data = (struct scsi_receive_copy_status_lid4_data *)ctsio->kern_data_ptr; scsi_ulto4b(sizeof(*data) - 4 + list_copy.sense_len + 4 + token_len, data->available_data); data->response_to_service_action = list_copy.service_action; if (list_copy.completed) { if (list_copy.error) data->copy_command_status = RCS_CCS_ERROR; else if (list_copy.abort) data->copy_command_status = RCS_CCS_ABORTED; else data->copy_command_status = RCS_CCS_COMPLETED; } else data->copy_command_status = RCS_CCS_INPROG_FG; scsi_ulto2b(list_copy.curops, data->operation_counter); scsi_ulto4b(UINT32_MAX, data->estimated_status_update_delay); data->transfer_count_units = RCS_TC_LBAS; scsi_u64to8b(list_copy.cursectors, data->transfer_count); scsi_ulto2b(list_copy.curseg, data->segments_processed); data->length_of_the_sense_data_field = list_copy.sense_len; data->sense_data_length = list_copy.sense_len; memcpy(data->sense_data, &list_copy.sense_data, list_copy.sense_len); ptr = &data->sense_data[data->length_of_the_sense_data_field]; scsi_ulto4b(token_len, &ptr[0]); if (list_copy.res_token_valid) { scsi_ulto2b(0, &ptr[4]); memcpy(&ptr[6], list_copy.res_token, sizeof(list_copy.res_token)); } /* printf("RRTI(list=%u) valid=%d\n", scsi_4btoul(cdb->list_identifier), list_copy.res_token_valid); */ ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } int ctl_report_all_rod_tokens(struct ctl_scsiio *ctsio) { struct ctl_softc *softc; struct ctl_lun *lun; struct scsi_report_all_rod_tokens *cdb; struct scsi_report_all_rod_tokens_data *data; struct tpc_token *token; int retval; int alloc_len, total_len, tokens, i; CTL_DEBUG_PRINT(("ctl_receive_rod_token_information\n")); cdb = (struct scsi_report_all_rod_tokens *)ctsio->cdb; lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; softc = lun->ctl_softc; retval = CTL_RETVAL_COMPLETE; tokens = 0; mtx_lock(&softc->ctl_lock); TAILQ_FOREACH(token, &softc->tpc_tokens, links) tokens++; mtx_unlock(&softc->ctl_lock); if (tokens > 512) tokens = 512; total_len = sizeof(*data) + tokens * 96; alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; if (total_len < alloc_len) { ctsio->residual = alloc_len - total_len; ctsio->kern_data_len = total_len; ctsio->kern_total_len = total_len; } 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; data = (struct scsi_report_all_rod_tokens_data *)ctsio->kern_data_ptr; i = 0; mtx_lock(&softc->ctl_lock); TAILQ_FOREACH(token, &softc->tpc_tokens, links) { if (i >= tokens) break; memcpy(&data->rod_management_token_list[i * 96], token->token, 96); i++; } mtx_unlock(&softc->ctl_lock); scsi_ulto4b(sizeof(*data) - 4 + i * 96, data->available_data); /* printf("RART tokens=%d\n", i); */ ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } Index: stable/10/sys/cam/ctl/ctl_tpc.h =================================================================== --- stable/10/sys/cam/ctl/ctl_tpc.h (revision 279003) +++ stable/10/sys/cam/ctl/ctl_tpc.h (revision 279004) @@ -1,39 +1,39 @@ /*- * Copyright (c) 2014 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _CTL_TPC_H #define _CTL_TPC_H 1 void tpc_done(union ctl_io *io); uint64_t tpcl_resolve(struct ctl_softc *softc, int init_port, - struct scsi_ec_cscd *cscd, uint32_t *ss); + struct scsi_ec_cscd *cscd, uint32_t *ss, uint32_t *ps, uint32_t *pso); union ctl_io * tpcl_alloc_io(void); int tpcl_queue(union ctl_io *io, uint64_t lun); #endif /* _CTL_TPC_H */ Index: stable/10/sys/cam/ctl/ctl_tpc_local.c =================================================================== --- stable/10/sys/cam/ctl/ctl_tpc_local.c (revision 279003) +++ stable/10/sys/cam/ctl/ctl_tpc_local.c (revision 279004) @@ -1,367 +1,373 @@ /*- * Copyright (c) 2014 Alexander Motin * Copyright (c) 2004, 2005 Silicon Graphics International Corp. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct tpcl_softc { struct ctl_port port; int cur_tag_num; }; static struct tpcl_softc tpcl_softc; static int tpcl_init(void); static void tpcl_shutdown(void); static void tpcl_online(void *arg); static void tpcl_offline(void *arg); static int tpcl_lun_enable(void *arg, struct ctl_id target_id, int lun_id); static int tpcl_lun_disable(void *arg, struct ctl_id target_id, int lun_id); static void tpcl_datamove(union ctl_io *io); static void tpcl_done(union ctl_io *io); static struct ctl_frontend tpcl_frontend = { .name = "tpc", .init = tpcl_init, .shutdown = tpcl_shutdown, }; CTL_FRONTEND_DECLARE(ctltpc, tpcl_frontend); static int tpcl_init(void) { struct tpcl_softc *tsoftc = &tpcl_softc; struct ctl_port *port; struct scsi_transportid_spi *tid; int len; memset(tsoftc, 0, sizeof(*tsoftc)); port = &tsoftc->port; port->frontend = &tpcl_frontend; port->port_type = CTL_PORT_INTERNAL; port->num_requested_ctl_io = 100; port->port_name = "tpc"; port->port_online = tpcl_online; port->port_offline = tpcl_offline; port->onoff_arg = tsoftc; port->lun_enable = tpcl_lun_enable; port->lun_disable = tpcl_lun_disable; port->targ_lun_arg = tsoftc; port->fe_datamove = tpcl_datamove; port->fe_done = tpcl_done; port->max_targets = 1; port->max_target_id = 0; port->max_initiators = 1; if (ctl_port_register(port) != 0) { printf("%s: tpc frontend registration failed\n", __func__); return (0); } len = sizeof(struct scsi_transportid_spi); port->init_devid = malloc(sizeof(struct ctl_devid) + len, M_CTL, M_WAITOK | M_ZERO); port->init_devid->len = len; tid = (struct scsi_transportid_spi *)port->init_devid->data; tid->format_protocol = SCSI_TRN_SPI_FORMAT_DEFAULT | SCSI_PROTO_SPI; scsi_ulto2b(0, tid->scsi_addr); scsi_ulto2b(port->targ_port, tid->rel_trgt_port_id); ctl_port_online(port); return (0); } void tpcl_shutdown(void) { struct tpcl_softc *tsoftc = &tpcl_softc; struct ctl_port *port; port = &tsoftc->port; ctl_port_offline(port); if (ctl_port_deregister(&tsoftc->port) != 0) printf("%s: ctl_frontend_deregister() failed\n", __func__); } static void tpcl_online(void *arg) { } static void tpcl_offline(void *arg) { } static int tpcl_lun_enable(void *arg, struct ctl_id target_id, int lun_id) { return (0); } static int tpcl_lun_disable(void *arg, struct ctl_id target_id, int lun_id) { return (0); } static void tpcl_datamove(union ctl_io *io) { struct ctl_sg_entry *ext_sglist, *kern_sglist; struct ctl_sg_entry ext_entry, kern_entry; int ext_sg_entries, kern_sg_entries; int ext_sg_start, ext_offset; int len_to_copy, len_copied; int kern_watermark, ext_watermark; struct ctl_scsiio *ctsio; int i, j; ext_sg_start = 0; ext_offset = 0; ext_sglist = NULL; CTL_DEBUG_PRINT(("%s\n", __func__)); ctsio = &io->scsiio; /* * If this is the case, we're probably doing a BBR read and don't * actually need to transfer the data. This will effectively * bit-bucket the data. */ if (ctsio->ext_data_ptr == NULL) goto bailout; /* * To simplify things here, if we have a single buffer, stick it in * a S/G entry and just make it a single entry S/G list. */ if (ctsio->io_hdr.flags & CTL_FLAG_EDPTR_SGLIST) { int len_seen; ext_sglist = (struct ctl_sg_entry *)ctsio->ext_data_ptr; ext_sg_entries = ctsio->ext_sg_entries; ext_sg_start = 0; ext_offset = 0; len_seen = 0; for (i = 0; i < ext_sg_entries; i++) { if ((len_seen + ext_sglist[i].len) >= ctsio->ext_data_filled) { ext_sg_start = i; ext_offset = ctsio->ext_data_filled - len_seen; break; } len_seen += ext_sglist[i].len; } } else { ext_sglist = &ext_entry; ext_sglist->addr = ctsio->ext_data_ptr; ext_sglist->len = ctsio->ext_data_len; ext_sg_entries = 1; ext_sg_start = 0; ext_offset = ctsio->ext_data_filled; } if (ctsio->kern_sg_entries > 0) { kern_sglist = (struct ctl_sg_entry *)ctsio->kern_data_ptr; kern_sg_entries = ctsio->kern_sg_entries; } else { kern_sglist = &kern_entry; kern_sglist->addr = ctsio->kern_data_ptr; kern_sglist->len = ctsio->kern_data_len; kern_sg_entries = 1; } kern_watermark = 0; ext_watermark = ext_offset; len_copied = 0; for (i = ext_sg_start, j = 0; i < ext_sg_entries && j < kern_sg_entries;) { uint8_t *ext_ptr, *kern_ptr; len_to_copy = min(ext_sglist[i].len - ext_watermark, kern_sglist[j].len - kern_watermark); ext_ptr = (uint8_t *)ext_sglist[i].addr; ext_ptr = ext_ptr + ext_watermark; if (io->io_hdr.flags & CTL_FLAG_BUS_ADDR) { /* * XXX KDM fix this! */ panic("need to implement bus address support"); #if 0 kern_ptr = bus_to_virt(kern_sglist[j].addr); #endif } else kern_ptr = (uint8_t *)kern_sglist[j].addr; kern_ptr = kern_ptr + kern_watermark; kern_watermark += len_to_copy; ext_watermark += len_to_copy; if ((ctsio->io_hdr.flags & CTL_FLAG_DATA_MASK) == CTL_FLAG_DATA_IN) { CTL_DEBUG_PRINT(("%s: copying %d bytes to user\n", __func__, len_to_copy)); CTL_DEBUG_PRINT(("%s: from %p to %p\n", __func__, kern_ptr, ext_ptr)); memcpy(ext_ptr, kern_ptr, len_to_copy); } else { CTL_DEBUG_PRINT(("%s: copying %d bytes from user\n", __func__, len_to_copy)); CTL_DEBUG_PRINT(("%s: from %p to %p\n", __func__, ext_ptr, kern_ptr)); memcpy(kern_ptr, ext_ptr, len_to_copy); } len_copied += len_to_copy; if (ext_sglist[i].len == ext_watermark) { i++; ext_watermark = 0; } if (kern_sglist[j].len == kern_watermark) { j++; kern_watermark = 0; } } ctsio->ext_data_filled += len_copied; CTL_DEBUG_PRINT(("%s: ext_sg_entries: %d, kern_sg_entries: %d\n", __func__, ext_sg_entries, kern_sg_entries)); CTL_DEBUG_PRINT(("%s: ext_data_len = %d, kern_data_len = %d\n", __func__, ctsio->ext_data_len, ctsio->kern_data_len)); /* XXX KDM set residual?? */ bailout: io->scsiio.be_move_done(io); } static void tpcl_done(union ctl_io *io) { tpc_done(io); } uint64_t tpcl_resolve(struct ctl_softc *softc, int init_port, - struct scsi_ec_cscd *cscd, uint32_t *ss) + struct scsi_ec_cscd *cscd, uint32_t *ss, uint32_t *ps, uint32_t *pso) { struct scsi_ec_cscd_id *cscdid; struct ctl_port *port; struct ctl_lun *lun; uint64_t lunid = UINT64_MAX; if (cscd->type_code != EC_CSCD_ID) return (lunid); cscdid = (struct scsi_ec_cscd_id *)cscd; mtx_lock(&softc->ctl_lock); if (init_port >= 0) port = softc->ctl_ports[ctl_port_idx(init_port)]; else port = NULL; STAILQ_FOREACH(lun, &softc->lun_list, links) { if (port != NULL && ctl_lun_map_to_port(port, lun->lun) >= CTL_MAX_LUNS) continue; if (lun->lun_devid == NULL) continue; if (scsi_devid_match(lun->lun_devid->data, lun->lun_devid->len, &cscdid->codeset, cscdid->length + 4) == 0) { lunid = lun->lun; if (ss && lun->be_lun) *ss = lun->be_lun->blocksize; + if (ps && lun->be_lun) + *ps = lun->be_lun->blocksize << + lun->be_lun->pblockexp; + if (pso && lun->be_lun) + *pso = lun->be_lun->blocksize * + lun->be_lun->pblockoff; break; } } mtx_unlock(&softc->ctl_lock); return (lunid); }; union ctl_io * tpcl_alloc_io(void) { struct tpcl_softc *tsoftc = &tpcl_softc; return (ctl_alloc_io(tsoftc->port.ctl_pool_ref)); }; int tpcl_queue(union ctl_io *io, uint64_t lun) { struct tpcl_softc *tsoftc = &tpcl_softc; io->io_hdr.nexus.initid.id = 0; io->io_hdr.nexus.targ_port = tsoftc->port.targ_port; io->io_hdr.nexus.targ_target.id = 0; io->io_hdr.nexus.targ_lun = lun; io->scsiio.tag_num = atomic_fetchadd_int(&tsoftc->cur_tag_num, 1); io->scsiio.ext_data_filled = 0; return (ctl_queue(io)); } Index: stable/10 =================================================================== --- stable/10 (revision 279003) +++ stable/10 (revision 279004) Property changes on: stable/10 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r278625