diff options
Diffstat (limited to 'sys/dev/nvme/nvme_test.c')
-rw-r--r-- | sys/dev/nvme/nvme_test.c | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/sys/dev/nvme/nvme_test.c b/sys/dev/nvme/nvme_test.c new file mode 100644 index 0000000..4177227 --- /dev/null +++ b/sys/dev/nvme/nvme_test.c @@ -0,0 +1,305 @@ +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/bio.h> +#include <sys/conf.h> +#include <sys/fcntl.h> +#include <sys/kthread.h> +#include <sys/module.h> +#include <sys/proc.h> +#include <sys/syscallsubr.h> +#include <sys/sysctl.h> +#include <sys/sysproto.h> +#include <sys/systm.h> +#include <sys/unistd.h> + +#include <geom/geom.h> + +#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); +} |