Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/ntb/test/ntb_perf.c
- This file was added.
/*- | |||||
* This file is provided under a dual BSD/GPLv2 license. When using or | |||||
* redistributing this file, you may do so under either license. | |||||
* | |||||
* GPL LICENSE SUMMARY | |||||
* | |||||
* Copyright(c) 2019 Shreyank Amartya <shreyank.amartya@amd.com> | |||||
* Copyright(c) 2019 Advanced Mirco Devices, Inc. | |||||
* | |||||
* This program is free software; you can redistribute it and/or modify | |||||
* it under the terms of version 2 of the GNU General Public License as | |||||
* published by the Free Software Foundation. | |||||
* | |||||
* BSD LICENSE | |||||
* | |||||
* Copyright(c) 2015 AMD 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: | |||||
* | |||||
* * Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* * Redistributions in binary form must reproduce the above copy | |||||
* notice, this list of conditions and the following disclaimer in | |||||
* the documentation and/or other materials provided with the | |||||
* distribution. | |||||
* * Neither the name of AMD Corporation nor the names of its | |||||
* contributors may be used to endorse or promote products derived | |||||
* from this software without specific prior written permission. | |||||
* | |||||
* 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 MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||||
* OWNER 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. | |||||
* | |||||
* PCIe NTB Perf FreeBSD driver | |||||
*/ | |||||
/* | |||||
* How to use this tool, by example. | |||||
* | |||||
* Suppose aside from local device there is at least one remote device | |||||
* connected to NTB with index 0. | |||||
*----------------------------------------------------------------------------- | |||||
* Eg: install driver with specified chunk/total orders and dma-enabled flag | |||||
* | |||||
* root@freebsd:~ #kldload ntb_perf | |||||
*----------------------------------------------------------------------------- | |||||
* Eg: Set test parameter total order using sysctl total_order | |||||
* | |||||
* root@freebsd:~ #sysctl dev.ntb_perf.0.total_order=20 | |||||
*----------------------------------------------------------------------------- | |||||
* Eg: Set test parameter chunk order using sysctl data_order | |||||
* | |||||
* root@freebsd:~ #sysctl dev.ntb_perf.0.data_order=20 | |||||
*----------------------------------------------------------------------------- | |||||
* Eg: start performance test with peer (index 0) and get the test metrics | |||||
* | |||||
* root@freebsd:~ #sysctl dev.ntb_perf.0.run=0 | |||||
* root@freebsd:~ #sysctl dev.ntb_perf.0.read_stats | |||||
*----------------------------------------------------------------------------- | |||||
* Eg: check NTB ports (index) and MW mapping information | |||||
* | |||||
* root@freebsd:~ #sysctl dev.ntb_perf.0.info | |||||
*----------------------------------------------------------------------------- | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/param.h> | |||||
#include <sys/bus.h> | |||||
#include <sys/kernel.h> | |||||
#include <sys/libkern.h> | |||||
#include <sys/limits.h> | |||||
#include <sys/lock.h> | |||||
#include <sys/malloc.h> | |||||
#include <sys/mbuf.h> | |||||
#include <sys/module.h> | |||||
#include <sys/mutex.h> | |||||
#include <sys/proc.h> | |||||
#include <sys/sbuf.h> | |||||
#include <sys/sched.h> | |||||
#include <sys/sysctl.h> | |||||
#include <sys/systm.h> | |||||
#include <sys/taskqueue.h> | |||||
#include <sys/time.h> | |||||
#include <vm/vm.h> | |||||
#include <vm/pmap.h> | |||||
#include <dev/pci/pcireg.h> | |||||
#include <dev/pci/pcivar.h> | |||||
#include <machine/atomic.h> | |||||
#include <machine/bus.h> | |||||
#include <linux/bitops.h> | |||||
#include <linux/ktime.h> | |||||
#include "../ntb.h" | |||||
#define DRIVER_NAME "ntb_perf" | |||||
#define DRIVER_VERSION "1.0" | |||||
#define MAX_THREADS_CNT 32 | |||||
#define DEF_THREADS_CNT 1 | |||||
#define MAX_CHUNK_SIZE 0x100000 | |||||
#define MAX_CHUNK_ORDER 20 /* no larger than 1M */ | |||||
#define MSG_TRIES 500 | |||||
#define lower_32_bits(n) ((uint32_t)(n)) | |||||
#define upper_32_bits(n) ((uint32_t)((n) >> 32)) | |||||
MALLOC_DEFINE(M_PERF, "ntb_perf","ntb perf data"); | |||||
SYSCTL_NODE(_debug, OID_AUTO, ntb_perf, CTLFLAG_RWTUN, NULL, "NTB Perf Debugging"); | |||||
static unsigned g_ntb_perf_debug_level; | |||||
SYSCTL_UINT(_debug_ntb_perf, OID_AUTO, debug_level, CTLFLAG_RWTUN, | |||||
&g_ntb_perf_debug_level, 0, "NTB Perf log level -- higher is verbose"); | |||||
#define ntb_perf_printf(lvl, ...) do { \ | |||||
if (lvl <= g_ntb_perf_debug_level) \ | |||||
device_printf(perf->dev, __VA_ARGS__); \ | |||||
} while (0) | |||||
/* | |||||
*============================================================================== | |||||
* Static data declarations | |||||
*============================================================================== | |||||
*/ | |||||
static unsigned int max_mw_size = 0x100000; | |||||
static unsigned int chunk_order = 19; | |||||
static unsigned int total_order = 30; | |||||
static bool use_dma = false; | |||||
static struct taskqueue *work_queue; | |||||
/* | |||||
*============================================================================== | |||||
* Perf driver data definition | |||||
*============================================================================== | |||||
*/ | |||||
enum perf_cmd { | |||||
PERF_CMD_INVAL = -1,/* invalid spad command */ | |||||
PERF_CMD_SSIZE = 0, /* send out buffer size */ | |||||
PERF_CMD_RSIZE = 1, /* recv in buffer size */ | |||||
PERF_CMD_SXLAT = 2, /* send in buffer xlat */ | |||||
PERF_CMD_RXLAT = 3, /* recv out buffer xlat */ | |||||
PERF_CMD_CLEAR = 4, /* clear allocated memory */ | |||||
PERF_STS_DONE = 5, /* init is done */ | |||||
PERF_STS_LNKUP = 6, /* link up state flag */ | |||||
}; | |||||
struct perf_ctx; | |||||
struct perf_peer { | |||||
struct perf_ctx *perf; | |||||
int pidx; | |||||
int gidx; | |||||
/* Outbound MW params */ | |||||
bus_addr_t addr_limit; | |||||
caddr_t outbuf; | |||||
size_t outbuf_size; | |||||
size_t xlat_align_size; | |||||
size_t xlat_align; | |||||
uint64_t outbuf_xlat; | |||||
/* Inbound MW params */ | |||||
bus_dmamap_t dma_map; | |||||
bus_dma_tag_t dma_tag; | |||||
caddr_t *inbuf; | |||||
size_t inbuf_size; | |||||
bus_addr_t inbuf_xlat; | |||||
/* NTB connection setup service */ | |||||
unsigned long sts; | |||||
struct task service_task; | |||||
}; | |||||
struct perf_thread { | |||||
struct perf_ctx *perf; | |||||
int tidx; | |||||
/* Data source and measured statistics */ | |||||
ktime_t duration; | |||||
void *src; | |||||
int status; | |||||
uint64_t copied; | |||||
struct task work_task; | |||||
}; | |||||
struct perf_ctx { | |||||
device_t dev; | |||||
/* Global device index and peers descriptors */ | |||||
int gidx; | |||||
int pcnt; | |||||
struct perf_peer *peers; | |||||
/* Performance measuring work-threads interface */ | |||||
size_t tcnt; | |||||
unsigned int tsync; | |||||
unsigned long busy_flag; | |||||
struct callout clout; | |||||
struct perf_peer *test_peer; | |||||
struct perf_thread threads[MAX_THREADS_CNT]; | |||||
/* Scratchpad/Message IO operations */ | |||||
int (*cmd_send)(struct perf_peer *peer, enum perf_cmd cmd, uint64_t data); | |||||
int (*cmd_recv)(struct perf_ctx *perf, int *pidx, enum perf_cmd *cmd, | |||||
uint64_t *data); | |||||
}; | |||||
struct ntb_load_cb_args { | |||||
bus_addr_t addr; | |||||
int error; | |||||
}; | |||||
/* | |||||
* Scratchpads-base commands interface | |||||
*/ | |||||
#define PERF_SPAD_CNT(_pcnt) (3*((_pcnt) + 1)) | |||||
#define PERF_SPAD_CMD(_gidx) (3*(_gidx)) | |||||
#define PERF_SPAD_LDATA(_gidx) (3*(_gidx) + 1) | |||||
#define PERF_SPAD_HDATA(_gidx) (3*(_gidx) + 2) | |||||
#define PERF_SPAD_NOTIFY(_gidx) (BIT_ULL(_gidx)) | |||||
#define BIT_ULL_MASK(nr) (1ULL << ((nr) % BITS_PER_LONG_LONG)) | |||||
/* | |||||
*============================================================================== | |||||
* NTB cross-link commands execution service | |||||
*============================================================================== | |||||
*/ | |||||
static void perf_terminate_test(struct perf_ctx *perf); | |||||
static inline bool | |||||
perf_link_is_up(struct perf_peer *peer) | |||||
{ | |||||
uint64_t link; | |||||
link = ntb_link_is_up(peer->perf->dev, NULL, NULL); | |||||
return !!(link & BIT_ULL_MASK(peer->pidx)); | |||||
} | |||||
static int | |||||
perf_spad_cmd_send(struct perf_peer *peer, enum perf_cmd cmd, | |||||
uint64_t data) | |||||
{ | |||||
struct perf_ctx *perf = peer->perf; | |||||
int try, ret; | |||||
uint32_t val = 0; | |||||
for (try = 0; try < MSG_TRIES; try++) { | |||||
if (!perf_link_is_up(peer)) | |||||
return (ENOLINK); | |||||
ret = ntb_peer_spad_read(perf->dev, PERF_SPAD_CMD(perf->gidx), | |||||
&val); | |||||
if (val != PERF_CMD_INVAL) { | |||||
DELAY(2000); | |||||
continue; | |||||
} | |||||
ntb_peer_spad_write(perf->dev, | |||||
PERF_SPAD_LDATA(perf->gidx), lower_32_bits(data)); | |||||
ntb_peer_spad_write(perf->dev, | |||||
PERF_SPAD_HDATA(perf->gidx), upper_32_bits(data)); | |||||
barrier(); | |||||
ntb_peer_spad_write(perf->dev, PERF_SPAD_CMD(perf->gidx), cmd); | |||||
barrier(); | |||||
ntb_peer_db_set(perf->dev, PERF_SPAD_NOTIFY(peer->gidx)); | |||||
ntb_perf_printf(1, "%s: DB ring peer %#llx\n", __func__, | |||||
PERF_SPAD_NOTIFY(peer->gidx)); | |||||
break; | |||||
} | |||||
return (try < MSG_TRIES ? 0 : EAGAIN); | |||||
} | |||||
static int | |||||
perf_spad_cmd_recv(struct perf_ctx *perf, int *pidx, | |||||
enum perf_cmd *cmd, uint64_t *data) | |||||
{ | |||||
struct perf_peer *peer; | |||||
uint32_t val, ret; | |||||
ntb_db_clear(perf->dev, PERF_SPAD_NOTIFY(perf->gidx)); | |||||
for (*pidx = 0; *pidx < perf->pcnt; (*pidx)++) { | |||||
peer = &perf->peers[*pidx]; | |||||
if (!perf_link_is_up(peer)) | |||||
continue; | |||||
ret = ntb_spad_read(perf->dev, PERF_SPAD_CMD(peer->gidx), &val); | |||||
if (val == PERF_CMD_INVAL) | |||||
continue; | |||||
*cmd = val; | |||||
ret = ntb_spad_read(perf->dev, PERF_SPAD_LDATA(peer->gidx), | |||||
&val); | |||||
*data = val; | |||||
ret = ntb_spad_read(perf->dev, PERF_SPAD_HDATA(peer->gidx), | |||||
&val); | |||||
*data |= (uint64_t)val << 32; | |||||
/* Next command can be retrieved from now */ | |||||
ntb_spad_write(perf->dev, PERF_SPAD_CMD(peer->gidx), | |||||
PERF_CMD_INVAL); | |||||
ntb_perf_printf(1, "%s: CMD recv: %d 0x%llx\n", | |||||
__func__, *cmd, (unsigned long long)*data); | |||||
return (0); | |||||
} | |||||
return (EINVAL); | |||||
} | |||||
static int | |||||
perf_cmd_send(struct perf_peer *peer, enum perf_cmd cmd, uint64_t data) | |||||
{ | |||||
struct perf_ctx *perf = peer->perf; | |||||
if (cmd == PERF_CMD_SSIZE || cmd == PERF_CMD_SXLAT) | |||||
return perf->cmd_send(peer, cmd, data); | |||||
return (EINVAL); | |||||
} | |||||
static int | |||||
perf_cmd_exec(struct perf_peer *peer, enum perf_cmd cmd) | |||||
{ | |||||
struct perf_ctx *perf = peer->perf; | |||||
switch(cmd) { | |||||
case PERF_CMD_SSIZE: | |||||
case PERF_CMD_RSIZE: | |||||
case PERF_CMD_SXLAT: | |||||
case PERF_CMD_RXLAT: | |||||
case PERF_CMD_CLEAR: | |||||
break; | |||||
default: | |||||
ntb_perf_printf(1, "%s: Exec invalid command\n", __func__); | |||||
return (EINVAL); | |||||
} | |||||
set_bit(cmd, &peer->sts); | |||||
ntb_perf_printf(1, "%s: CMD exec: %d\n", __func__, cmd); | |||||
taskqueue_enqueue(taskqueue_swi, &peer->service_task); | |||||
return (0); | |||||
} | |||||
static int | |||||
perf_cmd_recv(struct perf_ctx *perf) | |||||
{ | |||||
struct perf_peer *peer; | |||||
int ret, pidx, cmd; | |||||
uint64_t data; | |||||
while (!ntb_link_is_up(perf->dev, NULL, NULL)); | |||||
while (!(ret = perf->cmd_recv(perf, &pidx, &cmd, &data))) { | |||||
peer = &perf->peers[pidx]; | |||||
switch(cmd) { | |||||
case PERF_CMD_SSIZE: | |||||
peer->inbuf_size = data; | |||||
return perf_cmd_exec(peer, PERF_CMD_RSIZE); | |||||
case PERF_CMD_SXLAT: | |||||
peer->outbuf_xlat = data; | |||||
return perf_cmd_exec(peer, PERF_CMD_RXLAT); | |||||
default: | |||||
ntb_perf_printf(1, "%s: Received invalid command\n", | |||||
__func__); | |||||
return (EINVAL); | |||||
} | |||||
} | |||||
return (0); | |||||
} | |||||
static void | |||||
perf_link_event(void *ctx) | |||||
{ | |||||
struct perf_ctx *perf = ctx; | |||||
struct perf_peer *peer; | |||||
bool lnk_up; | |||||
int pidx; | |||||
for (pidx = 0; pidx < perf->pcnt; pidx++) { | |||||
peer = &perf->peers[pidx]; | |||||
lnk_up = perf_link_is_up(peer); | |||||
ntb_perf_printf(1, "%s: Link status:%x\n", __func__, lnk_up); | |||||
if (lnk_up && !test_and_set_bit(PERF_STS_LNKUP, &peer->sts)) { | |||||
perf_cmd_exec(peer, PERF_CMD_SSIZE); | |||||
} else if (!lnk_up && | |||||
test_and_clear_bit(PERF_STS_LNKUP, &peer->sts)) { | |||||
perf_cmd_exec(peer, PERF_CMD_CLEAR); | |||||
} | |||||
} | |||||
} | |||||
static void | |||||
perf_db_event(void *ctx, uint32_t vec) | |||||
{ | |||||
struct perf_ctx *perf = ctx; | |||||
ntb_perf_printf(1, "%s: DB vec %d mask %#llx bits %#llx\n", __func__, vec, | |||||
(unsigned long long)ntb_db_vector_mask(perf->dev, vec), | |||||
(unsigned long long)ntb_db_read(perf->dev)); | |||||
/* Just receive all available commands */ | |||||
perf_cmd_recv(perf); | |||||
} | |||||
static const struct ntb_ctx_ops perf_ops = { | |||||
.link_event = perf_link_event, | |||||
.db_event = perf_db_event, | |||||
}; | |||||
static int | |||||
perf_setup_outbuf(struct perf_peer *peer) | |||||
{ | |||||
/* Initialization is finally done */ | |||||
set_bit(PERF_STS_DONE, &peer->sts); | |||||
return (0); | |||||
} | |||||
static void | |||||
perf_free_inbuf(struct perf_peer *peer) | |||||
{ | |||||
if (peer->inbuf == NULL) | |||||
return; | |||||
ntb_mw_clear_trans(peer->perf->dev, peer->gidx); | |||||
if (peer->dma_tag) { | |||||
bus_dmamap_unload(peer->dma_tag, peer->dma_map); | |||||
bus_dmamem_free(peer->dma_tag, peer->inbuf, peer->dma_map); | |||||
bus_dma_tag_destroy(peer->dma_tag); | |||||
} | |||||
peer->inbuf_size = 0; | |||||
peer->inbuf = NULL; | |||||
} | |||||
static void | |||||
ntb_load_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int error) | |||||
{ | |||||
struct ntb_load_cb_args *cba = (struct ntb_load_cb_args *)xsc; | |||||
if (!(cba->error = error)) | |||||
cba->addr = segs[0].ds_addr; | |||||
} | |||||
static int | |||||
perf_setup_inbuf(struct perf_peer *peer) | |||||
{ | |||||
struct perf_ctx *perf = peer->perf; | |||||
struct ntb_load_cb_args cba; | |||||
int ret; | |||||
if (peer->inbuf_size > peer->outbuf_size) { | |||||
ntb_perf_printf(1, "%s: Too big inbuf size %pa > %pa", __func__, | |||||
&peer->inbuf_size, &peer->outbuf_size); | |||||
return (EINVAL); | |||||
} | |||||
perf_free_inbuf(peer); | |||||
if (bus_dma_tag_create(bus_get_dma_tag(perf->dev),peer->xlat_align, 0, | |||||
peer->addr_limit, BUS_SPACE_MAXADDR, | |||||
NULL, NULL, peer->inbuf_size, 1, peer->inbuf_size, | |||||
0, NULL, NULL, &peer->dma_tag)) { | |||||
ntb_perf_printf(1, | |||||
"%s: Unable to create MW tag of size %zu/%zu\n", | |||||
__func__, peer->inbuf_size, peer->outbuf_size); | |||||
peer->outbuf_size = 0; | |||||
peer->inbuf_size = 0; | |||||
return (ENOMEM); | |||||
} | |||||
if (bus_dmamem_alloc(peer->dma_tag, (void **)&peer->inbuf, | |||||
BUS_DMA_WAITOK | BUS_DMA_ZERO, &peer->dma_map)) { | |||||
bus_dma_tag_destroy(peer->dma_tag); | |||||
ntb_perf_printf(1, | |||||
"%s: Unable to allocate MW buffer of size %zu/%zu\n", | |||||
__func__, peer->inbuf_size, peer->outbuf_size); | |||||
peer->outbuf_size = 0; | |||||
peer->inbuf_size = 0; | |||||
return (ENOMEM); | |||||
} | |||||
if (bus_dmamap_load(peer->dma_tag, peer->dma_map, peer->inbuf, | |||||
peer->inbuf_size, ntb_load_cb, &cba, BUS_DMA_NOWAIT) || cba.error) { | |||||
bus_dma_tag_destroy(peer->dma_tag); | |||||
ntb_perf_printf(1, "%s: Unable to load MW buffer of size %zu/%zu\n", | |||||
__func__, peer->inbuf_size, peer->outbuf_size); | |||||
peer->outbuf_size = 0; | |||||
peer->inbuf_size = 0; | |||||
} | |||||
peer->inbuf_xlat = cba.addr; | |||||
ret = ntb_mw_set_trans(perf->dev, peer->gidx, peer->inbuf_xlat, peer->inbuf_size); | |||||
if (ret) { | |||||
ntb_perf_printf(1, "%s: Failed to set inbuf translation\n", | |||||
__func__); | |||||
goto err_free_inbuf; | |||||
} | |||||
perf_cmd_exec(peer, PERF_CMD_SXLAT); | |||||
return (0); | |||||
err_free_inbuf: | |||||
perf_free_inbuf(peer); | |||||
return (ret); | |||||
} | |||||
static int | |||||
perf_init_service(struct perf_ctx *perf) | |||||
{ | |||||
uint64_t mask; | |||||
/* Check MW count */ | |||||
mask = GENMASK_ULL(perf->pcnt, 0); | |||||
if (ntb_spad_count(perf->dev) >= PERF_SPAD_CNT(perf->pcnt) && | |||||
(ntb_db_valid_mask(perf->dev) & mask) == mask) { | |||||
perf->cmd_send = perf_spad_cmd_send; | |||||
perf->cmd_recv = perf_spad_cmd_recv; | |||||
ntb_perf_printf(1, "%s: Scratchpad service initialized\n", | |||||
__func__); | |||||
return (0); | |||||
} | |||||
ntb_perf_printf(1, "%s: Scratchpad service unsupported\n", __func__); | |||||
return (EINVAL); | |||||
} | |||||
static int | |||||
perf_enable_service(struct perf_ctx *perf) | |||||
{ | |||||
uint64_t mask, incmd_bit; | |||||
int ret, sidx, scnt; | |||||
mask = ntb_db_valid_mask(perf->dev); | |||||
ntb_db_set_mask(perf->dev, mask); | |||||
ret = ntb_set_ctx(perf->dev, perf, &perf_ops); | |||||
if(ret) | |||||
return (ret); | |||||
if (perf->cmd_send == perf_spad_cmd_send) { | |||||
scnt = ntb_spad_count(perf->dev); | |||||
for (sidx = 0; sidx < scnt; sidx++) | |||||
ntb_spad_write(perf->dev, sidx, PERF_CMD_INVAL); | |||||
incmd_bit = PERF_SPAD_NOTIFY(perf->gidx); | |||||
ntb_db_clear_mask(perf->dev, incmd_bit); | |||||
ntb_perf_printf(1, "%s: DB bits unmasked %#llx\n", | |||||
__func__, (unsigned long long)incmd_bit); | |||||
} | |||||
ntb_link_enable(perf->dev, NTB_SPEED_AUTO, NTB_WIDTH_AUTO); | |||||
ntb_perf_printf(1, "%s: Service Enabled\n", __func__); | |||||
return (0); | |||||
} | |||||
static void | |||||
perf_service_work(void *arg, int npending) | |||||
{ | |||||
struct perf_peer *peer = arg; | |||||
struct perf_ctx *perf = peer->perf; | |||||
if (test_and_clear_bit(PERF_CMD_SSIZE, &peer->sts)) | |||||
perf_cmd_send(peer, PERF_CMD_SSIZE, peer->outbuf_size); | |||||
if (test_and_clear_bit(PERF_CMD_RSIZE, &peer->sts)) | |||||
perf_setup_inbuf(peer); | |||||
if (test_and_clear_bit(PERF_CMD_SXLAT, &peer->sts)) | |||||
perf_cmd_send(peer, PERF_CMD_SXLAT, peer->inbuf_xlat); | |||||
if (test_and_clear_bit(PERF_CMD_RXLAT, &peer->sts)) | |||||
perf_setup_outbuf(peer); | |||||
if (test_and_clear_bit(PERF_CMD_CLEAR, &peer->sts)) { | |||||
clear_bit(PERF_STS_DONE, &peer->sts); | |||||
if (test_bit(0, &peer->perf->busy_flag) && | |||||
peer == peer->perf->test_peer) { | |||||
ntb_perf_printf(1, "%s: Freeing while test on-fly\n", | |||||
__func__); | |||||
perf_terminate_test(peer->perf); | |||||
} | |||||
perf_free_inbuf(peer); | |||||
} | |||||
} | |||||
static void | |||||
perf_disable_service(struct perf_ctx *perf) | |||||
{ | |||||
int pidx; | |||||
ntb_link_disable(perf->dev); | |||||
ntb_db_set_mask(perf->dev, PERF_SPAD_NOTIFY(perf->gidx)); | |||||
ntb_clear_ctx(perf->dev); | |||||
for (pidx = 0; pidx < perf->pcnt; pidx++) | |||||
perf_cmd_exec(&perf->peers[pidx], PERF_CMD_CLEAR); | |||||
for (pidx = 0; pidx < perf->pcnt; pidx++) | |||||
taskqueue_drain(taskqueue_swi, &perf->peers[pidx].service_task); | |||||
} | |||||
/* | |||||
*============================================================================== | |||||
* Performance measuring work-thread | |||||
*============================================================================== | |||||
*/ | |||||
static int | |||||
perf_copy_chunk(struct perf_thread *pthr, | |||||
void *dst, void *src, size_t len) | |||||
{ | |||||
if (!use_dma) { | |||||
memcpy(dst, src, len); | |||||
goto ret_check_tsync; | |||||
} | |||||
ret_check_tsync: | |||||
return likely(atomic_load_int(&pthr->perf->tsync) > 0) ? 0 : EINTR; | |||||
} | |||||
static int | |||||
perf_init_test(struct perf_thread *pthr) | |||||
{ | |||||
struct perf_ctx *perf = pthr->perf; | |||||
pthr->src = malloc(perf->test_peer->outbuf_size, M_PERF, M_WAITOK); | |||||
if (!pthr->src) | |||||
return (ENOMEM); | |||||
arc4rand(pthr->src, perf->test_peer->outbuf_size, 1); | |||||
ntb_perf_printf(1, "%s: Test init, alloc %llu random bytes\n", | |||||
__func__, (unsigned long long)perf->test_peer->outbuf_size); | |||||
return (0); | |||||
} | |||||
static int | |||||
perf_run_test(struct perf_thread *pthr) | |||||
{ | |||||
struct perf_peer *peer = pthr->perf->test_peer; | |||||
struct perf_ctx *perf = pthr->perf; | |||||
void *flt_dst, *bnd_dst; | |||||
uint64_t total_size, chunk_size; | |||||
void *flt_src; | |||||
int ret = 0; | |||||
total_size = 1ULL << total_order; | |||||
chunk_size = 1ULL << chunk_order; | |||||
chunk_size = min(peer->outbuf_size, chunk_size); | |||||
flt_src = pthr->src; | |||||
bnd_dst = peer->outbuf + peer->outbuf_size; | |||||
flt_dst = peer->outbuf; | |||||
pthr->duration = ktime_get(); | |||||
/* Copied field is cleared on test launch stage */ | |||||
while (pthr->copied < total_size) { | |||||
ret = perf_copy_chunk(pthr, flt_dst, flt_src, chunk_size); | |||||
if (ret) { | |||||
ntb_perf_printf(1, "%s: %d: Got error %d on test\n", | |||||
__func__, pthr->tidx, ret); | |||||
return (ret); | |||||
} | |||||
pthr->copied += chunk_size; | |||||
flt_dst = (char*)flt_dst + chunk_size; | |||||
flt_src = (char*)flt_src + chunk_size; | |||||
if (flt_dst >= bnd_dst || flt_dst < (void*)peer->outbuf) { | |||||
flt_dst = peer->outbuf; | |||||
flt_src = pthr->src; | |||||
} | |||||
sched_relinquish(curthread); | |||||
} | |||||
ntb_perf_printf(1, "%s: Data transfer complete\n", __func__); | |||||
return (0); | |||||
} | |||||
static int | |||||
perf_sync_test(struct perf_thread *pthr) | |||||
{ | |||||
struct perf_ctx *perf = pthr->perf; | |||||
pthr->duration = ktime_sub(ktime_get(), pthr->duration); | |||||
ntb_perf_printf(1, "%s: %d: copied %llu bytes\n", | |||||
__func__, pthr->tidx, (unsigned long long)pthr->copied); | |||||
ntb_perf_printf(1, "%s: %d: lasted %llu usecs\n", | |||||
__func__, pthr->tidx, | |||||
(unsigned long long)ktime_to_us(pthr->duration)); | |||||
ntb_perf_printf(1, "%s: %d: %llu MBytes/s\n", __func__, pthr->tidx, | |||||
(unsigned long long)(pthr->copied / ktime_to_us(pthr->duration))); | |||||
return (0); | |||||
} | |||||
static void | |||||
perf_clear_test(struct perf_thread *pthr) | |||||
{ | |||||
atomic_subtract_int(&pthr->perf->tsync, 1); | |||||
free(pthr->src, M_PERF); | |||||
if (atomic_load_int(&pthr->perf->tsync) == 0) { | |||||
clear_bit(0, &pthr->perf->busy_flag); | |||||
} | |||||
} | |||||
static void | |||||
perf_thread_work(void *arg, int npending) | |||||
{ | |||||
struct perf_thread *pthr = arg; | |||||
struct perf_ctx *perf = pthr->perf; | |||||
int ret; | |||||
ntb_perf_printf(1, "%s: Perf thread work tidx: %d\n", | |||||
__func__, pthr->tidx); | |||||
/* | |||||
* Perform stages in compliance with use_dma flag value. | |||||
* Test status is changed only if error happened, otherwise | |||||
* status -EINVAL is kept while test is on-fly. Results | |||||
* synchronization is performed only if test fininshed | |||||
* without an error or interruption. | |||||
*/ | |||||
ret = perf_init_test(pthr); | |||||
if (ret) { | |||||
pthr->status = ret; | |||||
return; | |||||
} | |||||
ret = perf_run_test(pthr); | |||||
if (ret) { | |||||
pthr->status = ret; | |||||
goto err_clear_test; | |||||
} | |||||
pthr->status = perf_sync_test(pthr); | |||||
err_clear_test: | |||||
perf_clear_test(pthr); | |||||
} | |||||
static int | |||||
perf_submit_test(struct perf_peer *peer) | |||||
{ | |||||
struct perf_ctx *perf = peer->perf; | |||||
struct perf_thread *pthr; | |||||
int tidx; | |||||
ntb_perf_printf(1, "%s: Perf test submitted\n", __func__); | |||||
callout_init(&perf->clout, 1); | |||||
taskqueue_start_threads(&work_queue, perf->tcnt, | |||||
PI_DISK, "ntbtqthread"); | |||||
if (!test_bit(PERF_STS_DONE, &peer->sts)) | |||||
return (ENOLINK); | |||||
if (test_and_set_bit(0, &perf->busy_flag)) | |||||
return (EBUSY); | |||||
perf->test_peer = peer; | |||||
atomic_set_int(&perf->tsync, perf->tcnt); | |||||
for (tidx = 0; tidx < MAX_THREADS_CNT; tidx++) { | |||||
pthr = &perf->threads[tidx]; | |||||
pthr->status = -EINVAL; | |||||
pthr->copied = 0; | |||||
pthr->duration = ktime_set(0, 0); | |||||
if (tidx < perf->tcnt) | |||||
taskqueue_enqueue(work_queue, &pthr->work_task); | |||||
} | |||||
return (0); | |||||
} | |||||
static void | |||||
perf_terminate_test(struct perf_ctx *perf) | |||||
{ | |||||
atomic_set_int(&perf->tsync, -1); | |||||
} | |||||
static int | |||||
perf_read_stats(struct perf_ctx *perf, struct sysctl_req *req) | |||||
{ | |||||
struct perf_thread *pthr; | |||||
struct sbuf *sb; | |||||
int tidx; | |||||
int rc; | |||||
size_t size = 1024; | |||||
if (!perf->test_peer) | |||||
return (0); | |||||
if (test_bit(0, &perf->busy_flag)) | |||||
return (EBUSY); | |||||
sb = sbuf_new_for_sysctl(NULL, NULL, size, req); | |||||
if (sb == NULL) { | |||||
rc = sb->s_error; | |||||
return (rc); | |||||
} | |||||
sbuf_printf(sb, "\nPeer %d test statistics:\n" , perf->test_peer->pidx); | |||||
for (tidx = 0; tidx < MAX_THREADS_CNT; tidx++) { | |||||
pthr = &perf->threads[tidx]; | |||||
if (pthr->status == -EINVAL) | |||||
continue; | |||||
if (pthr->status) { | |||||
sbuf_printf(sb, "%d: error status %d\n", | |||||
tidx, pthr->status); | |||||
continue; | |||||
} | |||||
sbuf_printf(sb, "%d: copied %llu bytes in %llu usecs,\ | |||||
%llu MBytes/s\n", tidx, (unsigned long long)pthr->copied, | |||||
(unsigned long long)ktime_to_us(pthr->duration), | |||||
(unsigned long long)(pthr->copied / | |||||
ktime_to_us(pthr->duration))); | |||||
} | |||||
rc = sbuf_finish(sb); | |||||
sbuf_delete(sb); | |||||
return (rc); | |||||
} | |||||
static void | |||||
perf_init_threads(struct perf_ctx *perf) | |||||
{ | |||||
struct perf_thread *pthr; | |||||
int tidx; | |||||
perf->tcnt = DEF_THREADS_CNT; | |||||
perf->test_peer = &perf->peers[0]; | |||||
for (tidx = 0; tidx < MAX_THREADS_CNT; tidx++) { | |||||
pthr = &perf->threads[tidx]; | |||||
pthr->perf = perf; | |||||
pthr->tidx = tidx; | |||||
pthr->status = -EINVAL; | |||||
TASK_INIT(&pthr->work_task, 0, perf_thread_work, pthr); | |||||
} | |||||
} | |||||
static void | |||||
perf_clear_threads(struct perf_ctx *perf) | |||||
{ | |||||
perf_terminate_test(perf); | |||||
} | |||||
/* | |||||
*============================================================================== | |||||
* Sysctl functions | |||||
*============================================================================== | |||||
*/ | |||||
static int | |||||
ntb_sysctl_info_handler(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct perf_ctx* perf = (struct perf_ctx*)arg1; | |||||
struct perf_peer *peer; | |||||
struct sbuf *sb; | |||||
int rc, pidx; | |||||
uint32_t size = 4096; | |||||
rc = sysctl_wire_old_buffer(req, 0); | |||||
if (rc != 0) | |||||
goto error; | |||||
sb = sbuf_new_for_sysctl(NULL, NULL, size, req); | |||||
if (sb == NULL) { | |||||
rc = sb->s_error; | |||||
goto error; | |||||
} | |||||
sbuf_printf(sb, "NTB Performance measuring tool info:\n\n"); | |||||
sbuf_printf(sb, "Local Port %d, Global Index %d\n", | |||||
ntb_port_number(perf->dev),perf->gidx); | |||||
sbuf_printf(sb, "Test status: "); | |||||
if (test_bit(0, &perf->busy_flag)) { | |||||
sbuf_printf(sb, "on-fly with port %d (%d)\n", | |||||
ntb_peer_port_number(perf->dev, | |||||
perf->test_peer->pidx), perf->test_peer->pidx); | |||||
} else { | |||||
sbuf_printf(sb, "idle\n"); | |||||
} | |||||
for (pidx = 0; pidx < perf->pcnt; pidx++) { | |||||
peer = &perf->peers[pidx]; | |||||
sbuf_printf(sb, "Port %d (%d), Global index %d:\n", | |||||
ntb_peer_port_number(perf->dev, peer->pidx), | |||||
peer->pidx, peer->gidx); | |||||
sbuf_printf(sb, "\tLink Status: %s\n", | |||||
test_bit(PERF_STS_LNKUP, &peer->sts) ? "up" : "down"); | |||||
sbuf_printf(sb, "\tOut buffer addr %pK\n", | |||||
peer->outbuf); | |||||
sbuf_printf(sb, "\tOut buffer size 0x%zx\n", | |||||
peer->outbuf_size); | |||||
sbuf_printf(sb, "\tOut buffer xlat 0x%pad[p]\n", | |||||
&peer->outbuf_xlat); | |||||
if (peer->inbuf == NULL) { | |||||
sbuf_printf(sb, "\tIn buffer addr: unallocated\n"); | |||||
continue; | |||||
} | |||||
sbuf_printf(sb, "\tIn buffer addr %pK\n", | |||||
peer->inbuf); | |||||
sbuf_printf(sb, "\tIn buffer size 0x%zx\n", | |||||
peer->inbuf_size); | |||||
sbuf_printf(sb, "\tIn buffer xlat %pad[p]\n", | |||||
&peer->inbuf_xlat); | |||||
} | |||||
rc = sbuf_finish(sb); | |||||
sbuf_delete(sb); | |||||
error: | |||||
return (rc); | |||||
} | |||||
static int | |||||
ntb_sysctl_run_handler(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct perf_ctx* perf = (struct perf_ctx*)arg1; | |||||
struct perf_peer *peer = perf->test_peer; | |||||
int ret; | |||||
int pidx = -1; | |||||
if (peer) | |||||
pidx = peer->pidx; | |||||
else | |||||
pidx = -1; | |||||
ret = sysctl_handle_int(oidp, &pidx, 0, req); | |||||
if (ret != 0 || req->newptr == NULL) | |||||
goto error; | |||||
if (pidx < 0 || pidx >= perf->pcnt) { | |||||
ret = EINVAL; | |||||
goto error; | |||||
} | |||||
peer = &perf->peers[pidx]; | |||||
ret = perf_submit_test(peer); | |||||
error: | |||||
return (ret); | |||||
} | |||||
static int | |||||
ntb_sysctl_tcount_handler(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct perf_ctx* perf = (struct perf_ctx*)arg1; | |||||
int ret = 0; | |||||
size_t tcnt = 0; | |||||
tcnt = perf->tcnt; | |||||
ret = sysctl_handle_int(oidp, &tcnt, 0, req); | |||||
if (ret != 0 || req->newptr == NULL) | |||||
goto error; | |||||
if (tcnt <= MAX_THREADS_CNT) | |||||
perf->tcnt = tcnt; | |||||
else { | |||||
ret = EINVAL; | |||||
goto error; | |||||
} | |||||
ntb_perf_printf(1, "%s: Thread count set to:%zi\n", | |||||
__func__, perf->tcnt); | |||||
error: | |||||
return (ret); | |||||
} | |||||
static int | |||||
ntb_sysctl_mwsize_handler(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct perf_ctx* perf = (struct perf_ctx*)arg1; | |||||
int ret = 0; | |||||
ret = sysctl_handle_int(oidp, &max_mw_size, 0, req); | |||||
if (ret != 0 || req->newptr == NULL) | |||||
goto error; | |||||
ntb_perf_printf(1, "%s: Max MW size set to:%i\n", | |||||
__func__, max_mw_size); | |||||
error: | |||||
return (ret); | |||||
} | |||||
static int | |||||
ntb_sysctl_corder_handler(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct perf_ctx* perf = (struct perf_ctx*)arg1; | |||||
int ret = 0; | |||||
ret = sysctl_handle_int(oidp, &chunk_order, 0, req); | |||||
if (ret != 0 || req->newptr == NULL) | |||||
goto error; | |||||
if (chunk_order > MAX_CHUNK_ORDER) | |||||
chunk_order = MAX_CHUNK_ORDER; | |||||
ntb_perf_printf(1, "%s: Chunk order set to:%u\n", | |||||
__func__, chunk_order); | |||||
error: | |||||
return (ret); | |||||
} | |||||
static int | |||||
ntb_sysctl_torder_handler(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct perf_ctx* perf = (struct perf_ctx*)arg1; | |||||
int ret = 0; | |||||
ret = sysctl_handle_int(oidp, &total_order, 0, req); | |||||
if (ret != 0 || req->newptr == NULL) | |||||
goto error; | |||||
if (total_order < chunk_order) | |||||
total_order = chunk_order; | |||||
ntb_perf_printf(1, "%s: Total order set to:%u\n", | |||||
__func__, total_order); | |||||
error: | |||||
return (ret); | |||||
} | |||||
static int | |||||
ntb_sysctl_usedma_handler(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct perf_ctx* perf = (struct perf_ctx*)arg1; | |||||
int ret = 0; | |||||
ret = sysctl_handle_bool(oidp, &use_dma, 0, req); | |||||
if (ret != 0 || req->newptr == NULL) | |||||
goto error; | |||||
ntb_perf_printf(1, "%s: Use DMA: %s\n", __func__, | |||||
use_dma ? "True" : "False"); | |||||
error: | |||||
return (ret); | |||||
} | |||||
static int | |||||
ntb_sysctl_read_stats_handler(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct perf_ctx *perf = (struct perf_ctx*)arg1; | |||||
int ret; | |||||
ret = perf_read_stats(perf, req); | |||||
if (ret != 0 || req->newptr == NULL) | |||||
goto error; | |||||
error: | |||||
return (ret); | |||||
} | |||||
static void | |||||
perf_setup_sysctl(struct perf_ctx *perf) | |||||
{ | |||||
struct sysctl_oid_list *globals; | |||||
struct sysctl_ctx_list *ctx; | |||||
ctx = device_get_sysctl_ctx(perf->dev); | |||||
globals = SYSCTL_CHILDREN(device_get_sysctl_tree(perf->dev)); | |||||
SYSCTL_ADD_PROC(ctx, globals, OID_AUTO, "info", | |||||
CTLTYPE_STRING | CTLFLAG_RD, perf, 0, | |||||
ntb_sysctl_info_handler, "A", | |||||
"NTB performance information" ); | |||||
SYSCTL_ADD_PROC(ctx, globals, OID_AUTO, "run", | |||||
CTLTYPE_INT | CTLFLAG_RW, perf, 0, | |||||
ntb_sysctl_run_handler, "I", "NTB run" ); | |||||
SYSCTL_ADD_PROC(ctx, globals, OID_AUTO, "thread_count", | |||||
CTLTYPE_UINT | CTLFLAG_RW, perf, 0, | |||||
ntb_sysctl_tcount_handler, "IU", "NTB thread count" ); | |||||
SYSCTL_ADD_PROC(ctx, globals, OID_AUTO, "mw_size", | |||||
CTLTYPE_UINT | CTLFLAG_RW, perf, 0, | |||||
ntb_sysctl_mwsize_handler, "IU", | |||||
"Upper limit of memory window size" ); | |||||
SYSCTL_ADD_PROC(ctx, globals, OID_AUTO, "data_order", | |||||
CTLTYPE_UINT | CTLFLAG_RW, perf, 0, | |||||
ntb_sysctl_corder_handler, "IU", | |||||
"Data chunk order [2^n] to transfer" ); | |||||
SYSCTL_ADD_PROC(ctx, globals, OID_AUTO, "total_order", | |||||
CTLTYPE_UINT | CTLFLAG_RW, perf, 0, | |||||
ntb_sysctl_torder_handler, "IU", | |||||
"Total data order [2^n] to transfer" ); | |||||
SYSCTL_ADD_PROC(ctx, globals, OID_AUTO, "use_dma", | |||||
CTLTYPE_U8 | CTLFLAG_RW, perf, 0, | |||||
ntb_sysctl_usedma_handler, "CU", | |||||
"Use DMA engine to measure performance" ); | |||||
SYSCTL_ADD_PROC(ctx, globals, OID_AUTO, "read_stats", | |||||
CTLTYPE_STRING | CTLFLAG_RW, perf, 0, | |||||
ntb_sysctl_read_stats_handler, "A", | |||||
"NTB Perf test statistics" ); | |||||
} | |||||
/* | |||||
*============================================================================== | |||||
* Basic driver initialization | |||||
*============================================================================== | |||||
*/ | |||||
static int | |||||
perf_init(struct perf_ctx* perf) | |||||
{ | |||||
work_queue = taskqueue_create("perf_wq", M_WAITOK | M_ZERO, | |||||
taskqueue_thread_enqueue, &work_queue); | |||||
return (0); | |||||
} | |||||
static int | |||||
perf_setup_peer_mw(struct perf_peer *peer) | |||||
{ | |||||
struct perf_ctx *perf = peer->perf; | |||||
vm_paddr_t bus_addr; | |||||
int ret; | |||||
/* Get outbound MW parameters and map it */ | |||||
ret = ntb_mw_get_range(perf->dev, peer->gidx, &bus_addr, | |||||
&peer->outbuf, &peer->outbuf_size, &peer->xlat_align, | |||||
&peer->xlat_align_size, &peer->addr_limit); | |||||
if (ret) | |||||
return (ret); | |||||
if (!peer->outbuf) | |||||
return (ENOMEM); | |||||
ret = ntb_mw_set_wc(perf->dev, peer->gidx, VM_MEMATTR_WRITE_COMBINING); | |||||
if (ret) | |||||
return (ret); | |||||
if (max_mw_size && peer->outbuf_size > max_mw_size) { | |||||
peer->outbuf_size = max_mw_size; | |||||
ntb_perf_printf(1, | |||||
"%s: Warning: Peer %d outbuf reduced to %zx\n", | |||||
__func__, peer->pidx, peer->outbuf_size); | |||||
} | |||||
return (0); | |||||
} | |||||
static int | |||||
perf_init_peers(struct perf_ctx *perf) | |||||
{ | |||||
struct perf_peer *peer; | |||||
int pidx, lport, ret; | |||||
perf->test_peer = NULL; | |||||
perf->pcnt = ntb_peer_port_count(perf->dev); | |||||
perf->peers = malloc(perf->pcnt*sizeof(*perf->peers), M_PERF, M_WAITOK); | |||||
if (!perf->peers) | |||||
return (ENOMEM); | |||||
lport = ntb_port_number(perf->dev); | |||||
perf->gidx = -1; | |||||
for (pidx = 0; pidx < perf->pcnt; pidx++) { | |||||
peer = &perf->peers[pidx]; | |||||
peer->perf = perf; | |||||
peer->pidx = pidx; | |||||
if (lport < ntb_peer_port_number(perf->dev, pidx)) { | |||||
if (perf->gidx == -1) | |||||
perf->gidx = pidx; | |||||
peer->gidx = pidx + 1; | |||||
} else { | |||||
peer->gidx = pidx; | |||||
} | |||||
peer->sts = 0; | |||||
TASK_INIT(&peer->service_task, 0, perf_service_work, peer); | |||||
} | |||||
if (perf->gidx == -1) | |||||
perf->gidx = pidx; | |||||
ntb_perf_printf(1,"%s: Peer Count: %d\n",__func__, perf->pcnt); | |||||
for (pidx = 0; pidx < perf->pcnt; pidx++) { | |||||
ret = perf_setup_peer_mw(&perf->peers[pidx]); | |||||
if (ret) | |||||
return (ret); | |||||
} | |||||
ntb_perf_printf(1, "%s: Global port index %d\n", __func__, perf->gidx); | |||||
return (0); | |||||
} | |||||
static int | |||||
ntb_perf_probe(device_t dev) | |||||
{ | |||||
device_set_desc(dev, "NTB Perf"); | |||||
return (0); | |||||
} | |||||
static int | |||||
ntb_perf_attach(device_t dev) | |||||
{ | |||||
struct perf_ctx *perf = device_get_softc(dev); | |||||
int ret=0; | |||||
perf->dev = dev; | |||||
perf_init(perf); | |||||
ret = perf_init_peers(perf); | |||||
if (ret) | |||||
return (ret); | |||||
perf_init_threads(perf); | |||||
ret = perf_init_service(perf); | |||||
if (ret) | |||||
return (ret); | |||||
ret = perf_enable_service(perf); | |||||
if (ret) | |||||
return (ret); | |||||
perf_setup_sysctl(perf); | |||||
return (0); | |||||
} | |||||
static int | |||||
ntb_perf_detach(device_t dev) | |||||
{ | |||||
struct perf_ctx *perf = device_get_softc(dev); | |||||
taskqueue_free(work_queue); | |||||
perf_disable_service(perf); | |||||
perf_clear_threads(perf); | |||||
free(perf->peers, M_PERF); | |||||
return (0); | |||||
} | |||||
static device_method_t ntb_perf_methods[] = { | |||||
/* Device interface */ | |||||
DEVMETHOD(device_probe, ntb_perf_probe), | |||||
DEVMETHOD(device_attach, ntb_perf_attach), | |||||
DEVMETHOD(device_detach, ntb_perf_detach), | |||||
DEVMETHOD_END | |||||
}; | |||||
devclass_t ntb_perf_devclass; | |||||
static DEFINE_CLASS_0(ntb_perf, ntb_perf_driver, | |||||
ntb_perf_methods, sizeof(struct perf_ctx)); | |||||
DRIVER_MODULE(ntb_perf, ntb_hw, ntb_perf_driver, | |||||
ntb_perf_devclass, NULL, NULL); | |||||
MODULE_DEPEND(ntb_perf, ntb, 1, 1, 1); | |||||
MODULE_VERSION(ntb_perf, 1); |