/*- * Copyright (C) 2012 Intel Corporation * 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. * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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 "nvme_private.h" struct nvme_io_test_thread { uint32_t idx; struct nvme_namespace *ns; enum nvme_nvm_opcode opc; struct timeval start; void *buf; uint32_t size; uint32_t time; uint32_t io_completed; }; struct nvme_io_test_internal { struct nvme_namespace *ns; enum nvme_nvm_opcode opc; struct timeval start; uint32_t time; uint32_t size; uint32_t td_active; uint32_t td_idx; uint32_t flags; uint32_t io_completed[NVME_TEST_MAX_THREADS]; }; static void nvme_ns_bio_test_cb(struct bio *bio) { struct mtx *mtx; mtx = mtx_pool_find(mtxpool_sleep, bio); mtx_lock(mtx); wakeup(bio); mtx_unlock(mtx); } static void nvme_ns_bio_test(void *arg) { struct nvme_io_test_internal *io_test = arg; struct cdevsw *csw; struct mtx *mtx; struct bio *bio; struct cdev *dev; void *buf; struct timeval t; uint64_t offset; uint32_t idx, io_completed = 0; #if __FreeBSD_version >= 900017 int ref; #endif buf = malloc(io_test->size, M_NVME, M_NOWAIT); idx = atomic_fetchadd_int(&io_test->td_idx, 1); dev = io_test->ns->cdev; offset = idx * 2048 * nvme_ns_get_sector_size(io_test->ns); while (1) { bio = g_alloc_bio(); memset(bio, 0, sizeof(*bio)); bio->bio_cmd = (io_test->opc == NVME_OPC_READ) ? BIO_READ : BIO_WRITE; bio->bio_done = nvme_ns_bio_test_cb; bio->bio_dev = dev; bio->bio_offset = offset; bio->bio_data = buf; bio->bio_bcount = io_test->size; if (io_test->flags & NVME_TEST_FLAG_REFTHREAD) { #if __FreeBSD_version >= 900017 csw = dev_refthread(dev, &ref); #else csw = dev_refthread(dev); #endif } else csw = dev->si_devsw; mtx = mtx_pool_find(mtxpool_sleep, bio); mtx_lock(mtx); (*csw->d_strategy)(bio); msleep(bio, mtx, PRIBIO, "biotestwait", 0); mtx_unlock(mtx); if (io_test->flags & NVME_TEST_FLAG_REFTHREAD) { #if __FreeBSD_version >= 900017 dev_relthread(dev, ref); #else dev_relthread(dev); #endif } if ((bio->bio_flags & BIO_ERROR) || (bio->bio_resid > 0)) break; g_destroy_bio(bio); io_completed++; getmicrouptime(&t); timevalsub(&t, &io_test->start); if (t.tv_sec >= io_test->time) break; offset += io_test->size; if ((offset + io_test->size) > nvme_ns_get_size(io_test->ns)) offset = 0; } io_test->io_completed[idx] = io_completed; wakeup_one(io_test); free(buf, M_NVME); atomic_subtract_int(&io_test->td_active, 1); mb(); #if __FreeBSD_version >= 800000 kthread_exit(); #else kthread_exit(0); #endif } static void nvme_ns_io_test_cb(void *arg, const struct nvme_completion *status) { struct nvme_io_test_thread *tth = arg; struct timeval t; tth->io_completed++; if (status->sf_sc || status->sf_sct) { printf("%s: error occurred\n", __func__); wakeup_one(tth); return; } getmicrouptime(&t); timevalsub(&t, &tth->start); if (t.tv_sec >= tth->time) { wakeup_one(tth); return; } switch (tth->opc) { case NVME_OPC_WRITE: nvme_ns_cmd_write(tth->ns, tth->buf, tth->idx * 2048, tth->size/nvme_ns_get_sector_size(tth->ns), nvme_ns_io_test_cb, tth); break; case NVME_OPC_READ: nvme_ns_cmd_read(tth->ns, tth->buf, tth->idx * 2048, tth->size/nvme_ns_get_sector_size(tth->ns), nvme_ns_io_test_cb, tth); break; default: break; } } static void nvme_ns_io_test(void *arg) { struct nvme_io_test_internal *io_test = arg; struct nvme_io_test_thread *tth; struct nvme_completion cpl; int error; tth = malloc(sizeof(*tth), M_NVME, M_NOWAIT | M_ZERO); tth->ns = io_test->ns; tth->opc = io_test->opc; memcpy(&tth->start, &io_test->start, sizeof(tth->start)); tth->buf = malloc(io_test->size, M_NVME, M_NOWAIT); tth->size = io_test->size; tth->time = io_test->time; tth->idx = atomic_fetchadd_int(&io_test->td_idx, 1); memset(&cpl, 0, sizeof(cpl)); nvme_ns_io_test_cb(tth, &cpl); error = tsleep(tth, 0, "test_wait", tth->time*hz*2); if (error) printf("%s: error = %d\n", __func__, error); io_test->io_completed[tth->idx] = tth->io_completed; wakeup_one(io_test); free(tth->buf, M_NVME); free(tth, M_NVME); atomic_subtract_int(&io_test->td_active, 1); mb(); #if __FreeBSD_version >= 800004 kthread_exit(); #else kthread_exit(0); #endif } void nvme_ns_test(struct nvme_namespace *ns, u_long cmd, caddr_t arg) { struct nvme_io_test *io_test; struct nvme_io_test_internal *io_test_internal; void (*fn)(void *); int i; io_test = (struct nvme_io_test *)arg; if ((io_test->opc != NVME_OPC_READ) && (io_test->opc != NVME_OPC_WRITE)) return; if (io_test->size % nvme_ns_get_sector_size(ns)) return; io_test_internal = malloc(sizeof(*io_test_internal), M_NVME, M_NOWAIT | M_ZERO); io_test_internal->opc = io_test->opc; io_test_internal->ns = ns; io_test_internal->td_active = io_test->num_threads; io_test_internal->time = io_test->time; io_test_internal->size = io_test->size; io_test_internal->flags = io_test->flags; if (cmd == NVME_IO_TEST) fn = nvme_ns_io_test; else fn = nvme_ns_bio_test; getmicrouptime(&io_test_internal->start); for (i = 0; i < io_test->num_threads; i++) #if __FreeBSD_version >= 800004 kthread_add(fn, io_test_internal, NULL, NULL, 0, 0, "nvme_io_test[%d]", i); #else kthread_create(fn, io_test_internal, NULL, 0, 0, "nvme_io_test[%d]", i); #endif tsleep(io_test_internal, 0, "nvme_test", io_test->time * 2 * hz); while (io_test_internal->td_active > 0) DELAY(10); memcpy(io_test->io_completed, io_test_internal->io_completed, sizeof(io_test->io_completed)); free(io_test_internal, M_NVME); }