/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2003, 2008 Silicon Graphics International Corp. * Copyright (c) 2012 The FreeBSD Foundation * Copyright (c) 2014-2017 Alexander Motin * 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_ramdisk.c#3 $ */ /* * CAM Target Layer black hole and RAM disk backend. * * 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 #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]) #define PPP (PAGE_SIZE / sizeof(uint8_t **)) #ifdef __LP64__ #define PPPS (PAGE_SHIFT - 3) #else #define PPPS (PAGE_SHIFT - 2) #endif #define SGPP (PAGE_SIZE / sizeof(struct ctl_sg_entry)) #define P_UNMAPPED NULL /* Page is unmapped. */ #define P_ANCHORED ((void *)(uintptr_t)1) /* Page is anchored. */ typedef enum { GP_READ, /* Return data page or zero page. */ GP_WRITE, /* Return data page, try allocate if none. */ GP_ANCHOR, /* Return data page, try anchor if none. */ GP_OTHER, /* Return what present, do not allocate/anchor. */ } getpage_op_t; typedef enum { CTL_BE_RAMDISK_LUN_UNCONFIGURED = 0x01, CTL_BE_RAMDISK_LUN_WAITING = 0x04 } ctl_be_ramdisk_lun_flags; struct ctl_be_ramdisk_lun { struct ctl_be_lun cbe_lun; /* Must be first element. */ struct ctl_lun_create_params params; int indir; uint8_t **pages; uint8_t *zero_page; struct sx page_lock; u_int pblocksize; u_int pblockmul; uint64_t size_bytes; uint64_t size_blocks; uint64_t cap_bytes; uint64_t cap_used; struct ctl_be_ramdisk_softc *softc; ctl_be_ramdisk_lun_flags flags; SLIST_ENTRY(ctl_be_ramdisk_lun) links; struct taskqueue *io_taskqueue; struct task io_task; STAILQ_HEAD(, ctl_io_hdr) cont_queue; struct mtx_padalign queue_lock; }; struct ctl_be_ramdisk_softc { struct sx modify_lock; struct mtx lock; int num_luns; SLIST_HEAD(, ctl_be_ramdisk_lun) lun_list; }; static struct ctl_be_ramdisk_softc rd_softc; extern struct ctl_softc *control_softc; static int ctl_backend_ramdisk_init(void); static int ctl_backend_ramdisk_shutdown(void); static int ctl_backend_ramdisk_move_done(union ctl_io *io, bool samethr); static void ctl_backend_ramdisk_compare(union ctl_io *io); static void ctl_backend_ramdisk_rw(union ctl_io *io); static int ctl_backend_ramdisk_submit(union ctl_io *io); static void ctl_backend_ramdisk_worker(void *context, int pending); static int ctl_backend_ramdisk_config_read(union ctl_io *io); static int ctl_backend_ramdisk_config_write(union ctl_io *io); static uint64_t ctl_backend_ramdisk_lun_attr(struct ctl_be_lun *cbe_lun, const char *attrname); static int ctl_backend_ramdisk_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td); static int ctl_backend_ramdisk_rm(struct ctl_be_ramdisk_softc *softc, struct ctl_lun_req *req); static int ctl_backend_ramdisk_create(struct ctl_be_ramdisk_softc *softc, struct ctl_lun_req *req); static int ctl_backend_ramdisk_modify(struct ctl_be_ramdisk_softc *softc, struct ctl_lun_req *req); static void ctl_backend_ramdisk_lun_shutdown(struct ctl_be_lun *cbe_lun); static struct ctl_backend_driver ctl_be_ramdisk_driver = { .name = "ramdisk", .flags = CTL_BE_FLAG_HAS_CONFIG, .init = ctl_backend_ramdisk_init, .shutdown = ctl_backend_ramdisk_shutdown, .data_submit = ctl_backend_ramdisk_submit, .config_read = ctl_backend_ramdisk_config_read, .config_write = ctl_backend_ramdisk_config_write, .ioctl = ctl_backend_ramdisk_ioctl, .lun_attr = ctl_backend_ramdisk_lun_attr, }; MALLOC_DEFINE(M_RAMDISK, "ctlramdisk", "Memory used for CTL RAMdisk"); CTL_BACKEND_DECLARE(cbr, ctl_be_ramdisk_driver); static int ctl_backend_ramdisk_init(void) { struct ctl_be_ramdisk_softc *softc = &rd_softc; memset(softc, 0, sizeof(*softc)); sx_init(&softc->modify_lock, "ctlrammod"); mtx_init(&softc->lock, "ctlram", NULL, MTX_DEF); SLIST_INIT(&softc->lun_list); return (0); } static int ctl_backend_ramdisk_shutdown(void) { struct ctl_be_ramdisk_softc *softc = &rd_softc; struct ctl_be_ramdisk_lun *lun; mtx_lock(&softc->lock); while ((lun = SLIST_FIRST(&softc->lun_list)) != NULL) { SLIST_REMOVE_HEAD(&softc->lun_list, links); softc->num_luns--; /* * Drop our lock here. Since ctl_remove_lun() can call * back into us, this could potentially lead to a recursive * lock of the same mutex, which would cause a hang. */ mtx_unlock(&softc->lock); ctl_remove_lun(&lun->cbe_lun); mtx_lock(&softc->lock); } mtx_unlock(&softc->lock); mtx_destroy(&softc->lock); sx_destroy(&softc->modify_lock); return (0); } static uint8_t * ctl_backend_ramdisk_getpage(struct ctl_be_ramdisk_lun *be_lun, off_t pn, getpage_op_t op) { uint8_t **p, ***pp; off_t i; int s; if (be_lun->cap_bytes == 0) { switch (op) { case GP_READ: return (be_lun->zero_page); case GP_WRITE: return ((uint8_t *)be_lun->pages); case GP_ANCHOR: return (P_ANCHORED); default: return (P_UNMAPPED); } } if (op == GP_WRITE || op == GP_ANCHOR) { sx_xlock(&be_lun->page_lock); pp = &be_lun->pages; for (s = (be_lun->indir - 1) * PPPS; s >= 0; s -= PPPS) { if (*pp == NULL) { *pp = malloc(PAGE_SIZE, M_RAMDISK, M_WAITOK|M_ZERO); } i = pn >> s; pp = (uint8_t ***)&(*pp)[i]; pn -= i << s; } if (*pp == P_UNMAPPED && be_lun->cap_used < be_lun->cap_bytes) { if (op == GP_WRITE) { *pp = malloc(be_lun->pblocksize, M_RAMDISK, M_WAITOK|M_ZERO); } else *pp = P_ANCHORED; be_lun->cap_used += be_lun->pblocksize; } else if (*pp == P_ANCHORED && op == GP_WRITE) { *pp = malloc(be_lun->pblocksize, M_RAMDISK, M_WAITOK|M_ZERO); } sx_xunlock(&be_lun->page_lock); return ((uint8_t *)*pp); } else { sx_slock(&be_lun->page_lock); p = be_lun->pages; for (s = (be_lun->indir - 1) * PPPS; s >= 0; s -= PPPS) { if (p == NULL) break; i = pn >> s; p = (uint8_t **)p[i]; pn -= i << s; } sx_sunlock(&be_lun->page_lock); if ((p == P_UNMAPPED || p == P_ANCHORED) && op == GP_READ) return (be_lun->zero_page); return ((uint8_t *)p); } }; static void ctl_backend_ramdisk_unmappage(struct ctl_be_ramdisk_lun *be_lun, off_t pn) { uint8_t ***pp; off_t i; int s; if (be_lun->cap_bytes == 0) return; sx_xlock(&be_lun->page_lock); pp = &be_lun->pages; for (s = (be_lun->indir - 1) * PPPS; s >= 0; s -= PPPS) { if (*pp == NULL) goto noindir; i = pn >> s; pp = (uint8_t ***)&(*pp)[i]; pn -= i << s; } if (*pp == P_ANCHORED) { be_lun->cap_used -= be_lun->pblocksize; *pp = P_UNMAPPED; } else if (*pp != P_UNMAPPED) { free(*pp, M_RAMDISK); be_lun->cap_used -= be_lun->pblocksize; *pp = P_UNMAPPED; } noindir: sx_xunlock(&be_lun->page_lock); }; static void ctl_backend_ramdisk_anchorpage(struct ctl_be_ramdisk_lun *be_lun, off_t pn) { uint8_t ***pp; off_t i; int s; if (be_lun->cap_bytes == 0) return; sx_xlock(&be_lun->page_lock); pp = &be_lun->pages; for (s = (be_lun->indir - 1) * PPPS; s >= 0; s -= PPPS) { if (*pp == NULL) goto noindir; i = pn >> s; pp = (uint8_t ***)&(*pp)[i]; pn -= i << s; } if (*pp == P_UNMAPPED && be_lun->cap_used < be_lun->cap_bytes) { be_lun->cap_used += be_lun->pblocksize; *pp = P_ANCHORED; } else if (*pp != P_ANCHORED) { free(*pp, M_RAMDISK); *pp = P_ANCHORED; } noindir: sx_xunlock(&be_lun->page_lock); }; static void ctl_backend_ramdisk_freeallpages(uint8_t **p, int indir) { int i; if (p == NULL) return; if (indir == 0) { free(p, M_RAMDISK); return; } for (i = 0; i < PPP; i++) { if (p[i] == NULL) continue; ctl_backend_ramdisk_freeallpages((uint8_t **)p[i], indir - 1); } free(p, M_RAMDISK); }; static size_t cmp(uint8_t *a, uint8_t *b, size_t size) { size_t i; for (i = 0; i < size; i++) { if (a[i] != b[i]) break; } return (i); } static int ctl_backend_ramdisk_cmp(union ctl_io *io) { struct ctl_be_lun *cbe_lun = CTL_BACKEND_LUN(io); struct ctl_be_ramdisk_lun *be_lun = (struct ctl_be_ramdisk_lun *)cbe_lun; uint8_t *page; uint8_t info[8]; uint64_t lba; u_int lbaoff, lbas, res, off; lbas = io->scsiio.kern_data_len / cbe_lun->blocksize; lba = ARGS(io)->lba + PRIV(io)->len - lbas; off = 0; for (; lbas > 0; lbas--, lba++) { page = ctl_backend_ramdisk_getpage(be_lun, lba >> cbe_lun->pblockexp, GP_READ); lbaoff = lba & ~(UINT_MAX << cbe_lun->pblockexp); page += lbaoff * cbe_lun->blocksize; res = cmp(io->scsiio.kern_data_ptr + off, page, cbe_lun->blocksize); off += res; if (res < cbe_lun->blocksize) break; } free(io->scsiio.kern_data_ptr, M_RAMDISK); if (lbas > 0) { off += io->scsiio.kern_rel_offset - io->scsiio.kern_data_len; scsi_u64to8b(off, info); ctl_set_sense(&io->scsiio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_MISCOMPARE, /*asc*/ 0x1D, /*ascq*/ 0x00, /*type*/ SSD_ELEM_INFO, /*size*/ sizeof(info), /*data*/ &info, /*type*/ SSD_ELEM_NONE); return (1); } return (0); } static int ctl_backend_ramdisk_move_done(union ctl_io *io, bool samethr) { struct ctl_be_ramdisk_lun *be_lun = (struct ctl_be_ramdisk_lun *)CTL_BACKEND_LUN(io); CTL_DEBUG_PRINT(("ctl_backend_ramdisk_move_done\n")); if (io->scsiio.kern_sg_entries > 0) free(io->scsiio.kern_data_ptr, M_RAMDISK); io->scsiio.kern_rel_offset += io->scsiio.kern_data_len; if ((io->io_hdr.flags & CTL_FLAG_ABORT) == 0 && (io->io_hdr.status & CTL_STATUS_MASK) == CTL_STATUS_NONE) { if (ARGS(io)->flags & CTL_LLF_COMPARE) { /* We have data block ready for comparison. */ if (ctl_backend_ramdisk_cmp(io)) goto done; } if (ARGS(io)->len > PRIV(io)->len) { mtx_lock(&be_lun->queue_lock); STAILQ_INSERT_TAIL(&be_lun->cont_queue, &io->io_hdr, links); mtx_unlock(&be_lun->queue_lock); taskqueue_enqueue(be_lun->io_taskqueue, &be_lun->io_task); return (0); } ctl_set_success(&io->scsiio); } done: ctl_data_submit_done(io); return(0); } static void ctl_backend_ramdisk_compare(union ctl_io *io) { struct ctl_be_lun *cbe_lun = CTL_BACKEND_LUN(io); u_int lbas, len; lbas = ARGS(io)->len - PRIV(io)->len; lbas = MIN(lbas, 131072 / cbe_lun->blocksize); len = lbas * cbe_lun->blocksize; io->scsiio.be_move_done = ctl_backend_ramdisk_move_done; io->scsiio.kern_data_ptr = malloc(len, M_RAMDISK, M_WAITOK); io->scsiio.kern_data_len = len; io->scsiio.kern_sg_entries = 0; io->io_hdr.flags |= CTL_FLAG_ALLOCATED; PRIV(io)->len += lbas; ctl_datamove(io); } static void ctl_backend_ramdisk_rw(union ctl_io *io) { struct ctl_be_lun *cbe_lun = CTL_BACKEND_LUN(io); struct ctl_be_ramdisk_lun *be_lun = (struct ctl_be_ramdisk_lun *)cbe_lun; struct ctl_sg_entry *sg_entries; uint8_t *page; uint64_t lba; u_int i, len, lbaoff, lbas, sgs, off; getpage_op_t op; lba = ARGS(io)->lba + PRIV(io)->len; lbaoff = lba & ~(UINT_MAX << cbe_lun->pblockexp); lbas = ARGS(io)->len - PRIV(io)->len; lbas = MIN(lbas, (SGPP << cbe_lun->pblockexp) - lbaoff); sgs = (lbas + lbaoff + be_lun->pblockmul - 1) >> cbe_lun->pblockexp; off = lbaoff * cbe_lun->blocksize; op = (ARGS(io)->flags & CTL_LLF_WRITE) ? GP_WRITE : GP_READ; if (sgs > 1) { io->scsiio.kern_data_ptr = malloc(sizeof(struct ctl_sg_entry) * sgs, M_RAMDISK, M_WAITOK); sg_entries = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; len = lbas * cbe_lun->blocksize; for (i = 0; i < sgs; i++) { page = ctl_backend_ramdisk_getpage(be_lun, (lba >> cbe_lun->pblockexp) + i, op); if (page == P_UNMAPPED || page == P_ANCHORED) { free(io->scsiio.kern_data_ptr, M_RAMDISK); nospc: ctl_set_space_alloc_fail(&io->scsiio); ctl_data_submit_done(io); return; } sg_entries[i].addr = page + off; sg_entries[i].len = MIN(len, be_lun->pblocksize - off); len -= sg_entries[i].len; off = 0; } } else { page = ctl_backend_ramdisk_getpage(be_lun, lba >> cbe_lun->pblockexp, op); if (page == P_UNMAPPED || page == P_ANCHORED) goto nospc; sgs = 0; io->scsiio.kern_data_ptr = page + off; } io->scsiio.be_move_done = ctl_backend_ramdisk_move_done; io->scsiio.kern_data_len = lbas * cbe_lun->blocksize; io->scsiio.kern_sg_entries = sgs; io->io_hdr.flags |= CTL_FLAG_ALLOCATED; PRIV(io)->len += lbas; if ((ARGS(io)->flags & CTL_LLF_READ) && ARGS(io)->len <= PRIV(io)->len) { ctl_set_success(&io->scsiio); if (cbe_lun->serseq >= CTL_LUN_SERSEQ_SOFT) ctl_serseq_done(io); } ctl_datamove(io); } static int ctl_backend_ramdisk_submit(union ctl_io *io) { struct ctl_lba_len_flags *lbalen = ARGS(io); if (lbalen->flags & CTL_LLF_VERIFY) { ctl_set_success(&io->scsiio); ctl_data_submit_done(io); return (CTL_RETVAL_COMPLETE); } PRIV(io)->len = 0; if (lbalen->flags & CTL_LLF_COMPARE) ctl_backend_ramdisk_compare(io); else ctl_backend_ramdisk_rw(io); return (CTL_RETVAL_COMPLETE); } static void ctl_backend_ramdisk_worker(void *context, int pending) { struct ctl_be_ramdisk_lun *be_lun; union ctl_io *io; be_lun = (struct ctl_be_ramdisk_lun *)context; mtx_lock(&be_lun->queue_lock); for (;;) { io = (union ctl_io *)STAILQ_FIRST(&be_lun->cont_queue); if (io != NULL) { STAILQ_REMOVE_HEAD(&be_lun->cont_queue, links); mtx_unlock(&be_lun->queue_lock); if (ARGS(io)->flags & CTL_LLF_COMPARE) ctl_backend_ramdisk_compare(io); else ctl_backend_ramdisk_rw(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); } static int ctl_backend_ramdisk_gls(union ctl_io *io) { struct ctl_be_lun *cbe_lun = CTL_BACKEND_LUN(io); struct ctl_be_ramdisk_lun *be_lun = (struct ctl_be_ramdisk_lun *)cbe_lun; struct scsi_get_lba_status_data *data; uint8_t *page; u_int lbaoff; data = (struct scsi_get_lba_status_data *)io->scsiio.kern_data_ptr; scsi_u64to8b(ARGS(io)->lba, data->descr[0].addr); lbaoff = ARGS(io)->lba & ~(UINT_MAX << cbe_lun->pblockexp); scsi_ulto4b(be_lun->pblockmul - lbaoff, data->descr[0].length); page = ctl_backend_ramdisk_getpage(be_lun, ARGS(io)->lba >> cbe_lun->pblockexp, GP_OTHER); if (page == P_UNMAPPED) data->descr[0].status = 1; else if (page == P_ANCHORED) data->descr[0].status = 2; else data->descr[0].status = 0; ctl_config_read_done(io); return (CTL_RETVAL_COMPLETE); } static int ctl_backend_ramdisk_config_read(union ctl_io *io) { int retval = 0; switch (io->scsiio.cdb[0]) { case SERVICE_ACTION_IN: if (io->scsiio.cdb[1] == SGLS_SERVICE_ACTION) { retval = ctl_backend_ramdisk_gls(io); 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 void ctl_backend_ramdisk_delete(struct ctl_be_lun *cbe_lun, off_t lba, off_t len, int anchor) { struct ctl_be_ramdisk_lun *be_lun = (struct ctl_be_ramdisk_lun *)cbe_lun; uint8_t *page; uint64_t p, lp; u_int lbaoff; getpage_op_t op = anchor ? GP_ANCHOR : GP_OTHER; /* Partially zero first partial page. */ p = lba >> cbe_lun->pblockexp; lbaoff = lba & ~(UINT_MAX << cbe_lun->pblockexp); if (lbaoff != 0) { page = ctl_backend_ramdisk_getpage(be_lun, p, op); if (page != P_UNMAPPED && page != P_ANCHORED) { memset(page + lbaoff * cbe_lun->blocksize, 0, min(len, be_lun->pblockmul - lbaoff) * cbe_lun->blocksize); } p++; } /* Partially zero last partial page. */ lp = (lba + len) >> cbe_lun->pblockexp; lbaoff = (lba + len) & ~(UINT_MAX << cbe_lun->pblockexp); if (p <= lp && lbaoff != 0) { page = ctl_backend_ramdisk_getpage(be_lun, lp, op); if (page != P_UNMAPPED && page != P_ANCHORED) memset(page, 0, lbaoff * cbe_lun->blocksize); } /* Delete remaining full pages. */ if (anchor) { for (; p < lp; p++) ctl_backend_ramdisk_anchorpage(be_lun, p); } else { for (; p < lp; p++) ctl_backend_ramdisk_unmappage(be_lun, p); } } static void ctl_backend_ramdisk_ws(union ctl_io *io) { struct ctl_be_lun *cbe_lun = CTL_BACKEND_LUN(io); struct ctl_be_ramdisk_lun *be_lun = (struct ctl_be_ramdisk_lun *)cbe_lun; struct ctl_lba_len_flags *lbalen = ARGS(io); uint8_t *page; uint64_t lba; u_int lbaoff, lbas; if (lbalen->flags & ~(SWS_LBDATA | SWS_UNMAP | SWS_ANCHOR | SWS_NDOB)) { ctl_set_invalid_field(&io->scsiio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 1, /*bit_valid*/ 0, /*bit*/ 0); ctl_config_write_done(io); return; } if (lbalen->flags & SWS_UNMAP) { ctl_backend_ramdisk_delete(cbe_lun, lbalen->lba, lbalen->len, (lbalen->flags & SWS_ANCHOR) != 0); ctl_set_success(&io->scsiio); ctl_config_write_done(io); return; } for (lba = lbalen->lba, lbas = lbalen->len; lbas > 0; lba++, lbas--) { page = ctl_backend_ramdisk_getpage(be_lun, lba >> cbe_lun->pblockexp, GP_WRITE); if (page == P_UNMAPPED || page == P_ANCHORED) { ctl_set_space_alloc_fail(&io->scsiio); ctl_data_submit_done(io); return; } lbaoff = lba & ~(UINT_MAX << cbe_lun->pblockexp); page += lbaoff * cbe_lun->blocksize; if (lbalen->flags & SWS_NDOB) { memset(page, 0, cbe_lun->blocksize); } else { memcpy(page, io->scsiio.kern_data_ptr, cbe_lun->blocksize); } if (lbalen->flags & SWS_LBDATA) scsi_ulto4b(lba, page); } ctl_set_success(&io->scsiio); ctl_config_write_done(io); } static void ctl_backend_ramdisk_unmap(union ctl_io *io) { struct ctl_be_lun *cbe_lun = CTL_BACKEND_LUN(io); struct ctl_ptr_len_flags *ptrlen = (struct ctl_ptr_len_flags *)ARGS(io); struct scsi_unmap_desc *buf, *end; if ((ptrlen->flags & ~SU_ANCHOR) != 0) { ctl_set_invalid_field(&io->scsiio, /*sks_valid*/ 0, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); ctl_config_write_done(io); return; } buf = (struct scsi_unmap_desc *)ptrlen->ptr; end = buf + ptrlen->len / sizeof(*buf); for (; buf < end; buf++) { ctl_backend_ramdisk_delete(cbe_lun, scsi_8btou64(buf->lba), scsi_4btoul(buf->length), (ptrlen->flags & SU_ANCHOR) != 0); } ctl_set_success(&io->scsiio); ctl_config_write_done(io); } static int ctl_backend_ramdisk_config_write(union ctl_io *io) { struct ctl_be_lun *cbe_lun = CTL_BACKEND_LUN(io); int retval = 0; switch (io->scsiio.cdb[0]) { case SYNCHRONIZE_CACHE: case SYNCHRONIZE_CACHE_16: /* We have no cache to flush. */ ctl_set_success(&io->scsiio); ctl_config_write_done(io); break; case START_STOP_UNIT: { struct scsi_start_stop_unit *cdb; cdb = (struct scsi_start_stop_unit *)io->scsiio.cdb; if ((cdb->how & SSS_PC_MASK) != 0) { ctl_set_success(&io->scsiio); ctl_config_write_done(io); break; } if (cdb->how & SSS_START) { if (cdb->how & SSS_LOEJ) ctl_lun_has_media(cbe_lun); ctl_start_lun(cbe_lun); } else { ctl_stop_lun(cbe_lun); if (cdb->how & SSS_LOEJ) ctl_lun_ejected(cbe_lun); } ctl_set_success(&io->scsiio); ctl_config_write_done(io); break; } case PREVENT_ALLOW: ctl_set_success(&io->scsiio); ctl_config_write_done(io); break; case WRITE_SAME_10: case WRITE_SAME_16: ctl_backend_ramdisk_ws(io); break; case UNMAP: ctl_backend_ramdisk_unmap(io); break; default: ctl_set_invalid_opcode(&io->scsiio); ctl_config_write_done(io); retval = CTL_RETVAL_COMPLETE; break; } return (retval); } static uint64_t ctl_backend_ramdisk_lun_attr(struct ctl_be_lun *cbe_lun, const char *attrname) { struct ctl_be_ramdisk_lun *be_lun = (struct ctl_be_ramdisk_lun *)cbe_lun; uint64_t val; val = UINT64_MAX; if (be_lun->cap_bytes == 0) return (val); sx_slock(&be_lun->page_lock); if (strcmp(attrname, "blocksused") == 0) { val = be_lun->cap_used / be_lun->cbe_lun.blocksize; } else if (strcmp(attrname, "blocksavail") == 0) { val = (be_lun->cap_bytes - be_lun->cap_used) / be_lun->cbe_lun.blocksize; } sx_sunlock(&be_lun->page_lock); return (val); } static int ctl_backend_ramdisk_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { struct ctl_be_ramdisk_softc *softc = &rd_softc; struct ctl_lun_req *lun_req; int retval; retval = 0; switch (cmd) { case CTL_LUN_REQ: lun_req = (struct ctl_lun_req *)addr; switch (lun_req->reqtype) { case CTL_LUNREQ_CREATE: retval = ctl_backend_ramdisk_create(softc, lun_req); break; case CTL_LUNREQ_RM: retval = ctl_backend_ramdisk_rm(softc, lun_req); break; case CTL_LUNREQ_MODIFY: retval = ctl_backend_ramdisk_modify(softc, lun_req); break; default: lun_req->status = CTL_LUN_ERROR; snprintf(lun_req->error_str, sizeof(lun_req->error_str), "%s: invalid LUN request type %d", __func__, lun_req->reqtype); break; } break; default: retval = ENOTTY; break; } return (retval); } static int ctl_backend_ramdisk_rm(struct ctl_be_ramdisk_softc *softc, struct ctl_lun_req *req) { struct ctl_be_ramdisk_lun *be_lun; struct ctl_lun_rm_params *params; int retval; params = &req->reqdata.rm; sx_xlock(&softc->modify_lock); mtx_lock(&softc->lock); SLIST_FOREACH(be_lun, &softc->lun_list, links) { if (be_lun->cbe_lun.lun_id == params->lun_id) { SLIST_REMOVE(&softc->lun_list, be_lun, ctl_be_ramdisk_lun, links); softc->num_luns--; break; } } mtx_unlock(&softc->lock); sx_xunlock(&softc->modify_lock); if (be_lun == NULL) { snprintf(req->error_str, sizeof(req->error_str), "%s: LUN %u is not managed by the ramdisk backend", __func__, params->lun_id); goto bailout_error; } /* * Set the waiting flag before we invalidate the LUN. Our shutdown * routine can be called any time after we invalidate the LUN, * and can be called from our context. * * This tells the shutdown routine that we're waiting, or we're * going to wait for the shutdown to happen. */ mtx_lock(&softc->lock); be_lun->flags |= CTL_BE_RAMDISK_LUN_WAITING; mtx_unlock(&softc->lock); retval = ctl_remove_lun(&be_lun->cbe_lun); if (retval != 0) { snprintf(req->error_str, sizeof(req->error_str), "%s: error %d returned from ctl_remove_lun() for " "LUN %d", __func__, retval, params->lun_id); mtx_lock(&softc->lock); be_lun->flags &= ~CTL_BE_RAMDISK_LUN_WAITING; mtx_unlock(&softc->lock); goto bailout_error; } mtx_lock(&softc->lock); while ((be_lun->flags & CTL_BE_RAMDISK_LUN_UNCONFIGURED) == 0) { retval = msleep(be_lun, &softc->lock, PCATCH, "ctlramrm", 0); if (retval == EINTR) break; } be_lun->flags &= ~CTL_BE_RAMDISK_LUN_WAITING; if (be_lun->flags & CTL_BE_RAMDISK_LUN_UNCONFIGURED) { mtx_unlock(&softc->lock); free(be_lun, M_RAMDISK); } else { mtx_unlock(&softc->lock); return (EINTR); } req->status = CTL_LUN_OK; return (retval); bailout_error: req->status = CTL_LUN_ERROR; return (0); } static int ctl_backend_ramdisk_create(struct ctl_be_ramdisk_softc *softc, struct ctl_lun_req *req) { struct ctl_be_ramdisk_lun *be_lun; struct ctl_be_lun *cbe_lun; struct ctl_lun_create_params *params; const char *value; char tmpstr[32]; uint64_t t; int retval; retval = 0; params = &req->reqdata.create; be_lun = malloc(sizeof(*be_lun), M_RAMDISK, M_ZERO | M_WAITOK); cbe_lun = &be_lun->cbe_lun; cbe_lun->options = nvlist_clone(req->args_nvl); be_lun->params = req->reqdata.create; be_lun->softc = softc; if (params->flags & CTL_LUN_FLAG_DEV_TYPE) cbe_lun->lun_type = params->device_type; else cbe_lun->lun_type = T_DIRECT; be_lun->flags = 0; cbe_lun->flags = 0; value = dnvlist_get_string(cbe_lun->options, "ha_role", NULL); if (value != NULL) { if (strcmp(value, "primary") == 0) cbe_lun->flags |= CTL_LUN_FLAG_PRIMARY; } else if (control_softc->flags & CTL_FLAG_ACTIVE_SHELF) cbe_lun->flags |= CTL_LUN_FLAG_PRIMARY; be_lun->pblocksize = PAGE_SIZE; value = dnvlist_get_string(cbe_lun->options, "pblocksize", NULL); if (value != NULL) { ctl_expand_number(value, &t); be_lun->pblocksize = t; } if (be_lun->pblocksize < 512 || be_lun->pblocksize > 131072) { snprintf(req->error_str, sizeof(req->error_str), "%s: unsupported pblocksize %u", __func__, be_lun->pblocksize); goto bailout_error; } if (cbe_lun->lun_type == T_DIRECT || cbe_lun->lun_type == T_CDROM) { if (params->blocksize_bytes != 0) cbe_lun->blocksize = params->blocksize_bytes; else if (cbe_lun->lun_type == T_CDROM) cbe_lun->blocksize = 2048; else cbe_lun->blocksize = 512; be_lun->pblockmul = be_lun->pblocksize / cbe_lun->blocksize; if (be_lun->pblockmul < 1 || !powerof2(be_lun->pblockmul)) { snprintf(req->error_str, sizeof(req->error_str), "%s: pblocksize %u not exp2 of blocksize %u", __func__, be_lun->pblocksize, cbe_lun->blocksize); goto bailout_error; } if (params->lun_size_bytes < cbe_lun->blocksize) { snprintf(req->error_str, sizeof(req->error_str), "%s: LUN size %ju < blocksize %u", __func__, params->lun_size_bytes, cbe_lun->blocksize); goto bailout_error; } be_lun->size_blocks = params->lun_size_bytes / cbe_lun->blocksize; be_lun->size_bytes = be_lun->size_blocks * cbe_lun->blocksize; be_lun->indir = 0; t = be_lun->size_bytes / be_lun->pblocksize; while (t > 1) { t /= PPP; be_lun->indir++; } cbe_lun->maxlba = be_lun->size_blocks - 1; cbe_lun->pblockexp = fls(be_lun->pblockmul) - 1; cbe_lun->pblockoff = 0; cbe_lun->ublockexp = cbe_lun->pblockexp; cbe_lun->ublockoff = 0; cbe_lun->atomicblock = be_lun->pblocksize; cbe_lun->opttxferlen = SGPP * be_lun->pblocksize; value = dnvlist_get_string(cbe_lun->options, "capacity", NULL); if (value != NULL) ctl_expand_number(value, &be_lun->cap_bytes); } else { be_lun->pblockmul = 1; cbe_lun->pblockexp = 0; } /* Tell the user the blocksize we ended up using */ params->blocksize_bytes = cbe_lun->blocksize; params->lun_size_bytes = be_lun->size_bytes; value = dnvlist_get_string(cbe_lun->options, "unmap", NULL); if (value == NULL || strcmp(value, "off") != 0) cbe_lun->flags |= CTL_LUN_FLAG_UNMAP; value = dnvlist_get_string(cbe_lun->options, "readonly", NULL); if (value != NULL) { if (strcmp(value, "on") == 0) cbe_lun->flags |= CTL_LUN_FLAG_READONLY; } else if (cbe_lun->lun_type != T_DIRECT) cbe_lun->flags |= CTL_LUN_FLAG_READONLY; cbe_lun->serseq = CTL_LUN_SERSEQ_OFF; value = dnvlist_get_string(cbe_lun->options, "serseq", NULL); if (value != NULL && strcmp(value, "on") == 0) cbe_lun->serseq = CTL_LUN_SERSEQ_ON; else if (value != NULL && strcmp(value, "read") == 0) cbe_lun->serseq = CTL_LUN_SERSEQ_READ; else if (value != NULL && strcmp(value, "soft") == 0) cbe_lun->serseq = CTL_LUN_SERSEQ_SOFT; else if (value != NULL && strcmp(value, "off") == 0) cbe_lun->serseq = CTL_LUN_SERSEQ_OFF; if (params->flags & CTL_LUN_FLAG_ID_REQ) { cbe_lun->req_lun_id = params->req_lun_id; cbe_lun->flags |= CTL_LUN_FLAG_ID_REQ; } else cbe_lun->req_lun_id = 0; cbe_lun->lun_shutdown = ctl_backend_ramdisk_lun_shutdown; cbe_lun->be = &ctl_be_ramdisk_driver; if ((params->flags & CTL_LUN_FLAG_SERIAL_NUM) == 0) { snprintf(tmpstr, sizeof(tmpstr), "MYSERIAL%04d", softc->num_luns); strncpy((char *)cbe_lun->serial_num, tmpstr, MIN(sizeof(cbe_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 *)cbe_lun->serial_num, params->serial_num, MIN(sizeof(cbe_lun->serial_num), sizeof(params->serial_num))); } if ((params->flags & CTL_LUN_FLAG_DEVID) == 0) { snprintf(tmpstr, sizeof(tmpstr), "MYDEVID%04d", softc->num_luns); strncpy((char *)cbe_lun->device_id, tmpstr, MIN(sizeof(cbe_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 *)cbe_lun->device_id, params->device_id, MIN(sizeof(cbe_lun->device_id), sizeof(params->device_id))); } STAILQ_INIT(&be_lun->cont_queue); sx_init(&be_lun->page_lock, "ctlram page"); if (be_lun->cap_bytes == 0) { be_lun->indir = 0; be_lun->pages = malloc(be_lun->pblocksize, M_RAMDISK, M_WAITOK); } be_lun->zero_page = malloc(be_lun->pblocksize, M_RAMDISK, M_WAITOK|M_ZERO); mtx_init(&be_lun->queue_lock, "ctlram queue", NULL, MTX_DEF); TASK_INIT(&be_lun->io_task, /*priority*/0, ctl_backend_ramdisk_worker, be_lun); be_lun->io_taskqueue = taskqueue_create("ctlramtq", M_WAITOK, taskqueue_thread_enqueue, /*context*/&be_lun->io_taskqueue); if (be_lun->io_taskqueue == NULL) { snprintf(req->error_str, sizeof(req->error_str), "%s: Unable to create taskqueue", __func__); goto bailout_error; } retval = taskqueue_start_threads_in_proc(&be_lun->io_taskqueue, /*num threads*/1, /*priority*/PUSER, /*proc*/control_softc->ctl_proc, /*thread name*/"ramdisk"); if (retval != 0) goto bailout_error; retval = ctl_add_lun(&be_lun->cbe_lun); if (retval != 0) { snprintf(req->error_str, sizeof(req->error_str), "%s: ctl_add_lun() returned error %d, see dmesg for " "details", __func__, retval); retval = 0; goto bailout_error; } mtx_lock(&softc->lock); softc->num_luns++; SLIST_INSERT_HEAD(&softc->lun_list, be_lun, links); mtx_unlock(&softc->lock); params->req_lun_id = cbe_lun->lun_id; req->status = CTL_LUN_OK; return (retval); bailout_error: req->status = CTL_LUN_ERROR; if (be_lun != NULL) { if (be_lun->io_taskqueue != NULL) taskqueue_free(be_lun->io_taskqueue); nvlist_destroy(cbe_lun->options); free(be_lun->zero_page, M_RAMDISK); ctl_backend_ramdisk_freeallpages(be_lun->pages, be_lun->indir); sx_destroy(&be_lun->page_lock); mtx_destroy(&be_lun->queue_lock); free(be_lun, M_RAMDISK); } return (retval); } static int ctl_backend_ramdisk_modify(struct ctl_be_ramdisk_softc *softc, struct ctl_lun_req *req) { struct ctl_be_ramdisk_lun *be_lun; struct ctl_be_lun *cbe_lun; struct ctl_lun_modify_params *params; const char *value; uint32_t blocksize; int wasprim; params = &req->reqdata.modify; sx_xlock(&softc->modify_lock); mtx_lock(&softc->lock); SLIST_FOREACH(be_lun, &softc->lun_list, links) { if (be_lun->cbe_lun.lun_id == params->lun_id) break; } mtx_unlock(&softc->lock); if (be_lun == NULL) { snprintf(req->error_str, sizeof(req->error_str), "%s: LUN %u is not managed by the ramdisk backend", __func__, params->lun_id); goto bailout_error; } cbe_lun = &be_lun->cbe_lun; if (params->lun_size_bytes != 0) be_lun->params.lun_size_bytes = params->lun_size_bytes; if (req->args_nvl != NULL) { nvlist_destroy(cbe_lun->options); cbe_lun->options = nvlist_clone(req->args_nvl); } wasprim = (cbe_lun->flags & CTL_LUN_FLAG_PRIMARY); value = dnvlist_get_string(cbe_lun->options, "ha_role", NULL); if (value != NULL) { if (strcmp(value, "primary") == 0) cbe_lun->flags |= CTL_LUN_FLAG_PRIMARY; else cbe_lun->flags &= ~CTL_LUN_FLAG_PRIMARY; } else if (control_softc->flags & CTL_FLAG_ACTIVE_SHELF) cbe_lun->flags |= CTL_LUN_FLAG_PRIMARY; else cbe_lun->flags &= ~CTL_LUN_FLAG_PRIMARY; if (wasprim != (cbe_lun->flags & CTL_LUN_FLAG_PRIMARY)) { if (cbe_lun->flags & CTL_LUN_FLAG_PRIMARY) ctl_lun_primary(cbe_lun); else ctl_lun_secondary(cbe_lun); } blocksize = be_lun->cbe_lun.blocksize; if (be_lun->params.lun_size_bytes < blocksize) { snprintf(req->error_str, sizeof(req->error_str), "%s: LUN size %ju < blocksize %u", __func__, be_lun->params.lun_size_bytes, blocksize); goto bailout_error; } be_lun->size_blocks = be_lun->params.lun_size_bytes / blocksize; be_lun->size_bytes = be_lun->size_blocks * blocksize; be_lun->cbe_lun.maxlba = be_lun->size_blocks - 1; ctl_lun_capacity_changed(&be_lun->cbe_lun); /* Tell the user the exact size we ended up using */ params->lun_size_bytes = be_lun->size_bytes; sx_xunlock(&softc->modify_lock); req->status = CTL_LUN_OK; return (0); bailout_error: sx_xunlock(&softc->modify_lock); req->status = CTL_LUN_ERROR; return (0); } static void ctl_backend_ramdisk_lun_shutdown(struct ctl_be_lun *cbe_lun) { struct ctl_be_ramdisk_lun *be_lun = (struct ctl_be_ramdisk_lun *)cbe_lun; struct ctl_be_ramdisk_softc *softc = be_lun->softc; taskqueue_drain_all(be_lun->io_taskqueue); taskqueue_free(be_lun->io_taskqueue); nvlist_destroy(be_lun->cbe_lun.options); free(be_lun->zero_page, M_RAMDISK); ctl_backend_ramdisk_freeallpages(be_lun->pages, be_lun->indir); sx_destroy(&be_lun->page_lock); mtx_destroy(&be_lun->queue_lock); mtx_lock(&softc->lock); be_lun->flags |= CTL_BE_RAMDISK_LUN_UNCONFIGURED; if (be_lun->flags & CTL_BE_RAMDISK_LUN_WAITING) wakeup(be_lun); else free(be_lun, M_RAMDISK); mtx_unlock(&softc->lock); }