| /* | |
| * 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) 2015 Intel Corporation. All rights reserved. | |
| * | |
| * 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 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: | |
| * | |
| * * 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 Intel 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 Linux driver | |
| */ | |
| #include <linux/init.h> | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/kthread.h> | |
| #include <linux/time.h> | |
| #include <linux/timer.h> | |
| #include <linux/dma-mapping.h> | |
| #include <linux/pci.h> | |
| #include <linux/slab.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/debugfs.h> | |
| #include <linux/dmaengine.h> | |
| #include <linux/delay.h> | |
| #include <linux/sizes.h> | |
| #include <linux/ntb.h> | |
| #include <linux/mutex.h> | |
| #define DRIVER_NAME "ntb_perf" | |
| #define DRIVER_DESCRIPTION "PCIe NTB Performance Measurement Tool" | |
| #define DRIVER_LICENSE "Dual BSD/GPL" | |
| #define DRIVER_VERSION "1.0" | |
| #define DRIVER_AUTHOR "Dave Jiang <dave.jiang@intel.com>" | |
| #define PERF_LINK_DOWN_TIMEOUT 10 | |
| #define PERF_VERSION 0xffff0001 | |
| #define MAX_THREADS 32 | |
| #define MAX_TEST_SIZE SZ_1M | |
| #define MAX_SRCS 32 | |
| #define DMA_OUT_RESOURCE_TO msecs_to_jiffies(50) | |
| #define DMA_RETRIES 20 | |
| #define SZ_4G (1ULL << 32) | |
| #define MAX_SEG_ORDER 20 /* no larger than 1M for kmalloc buffer */ | |
| #define PIDX NTB_DEF_PEER_IDX | |
| MODULE_LICENSE(DRIVER_LICENSE); | |
| MODULE_VERSION(DRIVER_VERSION); | |
| MODULE_AUTHOR(DRIVER_AUTHOR); | |
| MODULE_DESCRIPTION(DRIVER_DESCRIPTION); | |
| static struct dentry *perf_debugfs_dir; | |
| static unsigned long max_mw_size; | |
| module_param(max_mw_size, ulong, 0644); | |
| MODULE_PARM_DESC(max_mw_size, "Limit size of large memory windows"); | |
| static unsigned int seg_order = 19; /* 512K */ | |
| module_param(seg_order, uint, 0644); | |
| MODULE_PARM_DESC(seg_order, "size order [2^n] of buffer segment for testing"); | |
| static unsigned int run_order = 32; /* 4G */ | |
| module_param(run_order, uint, 0644); | |
| MODULE_PARM_DESC(run_order, "size order [2^n] of total data to transfer"); | |
| static bool use_dma; /* default to 0 */ | |
| module_param(use_dma, bool, 0644); | |
| MODULE_PARM_DESC(use_dma, "Using DMA engine to measure performance"); | |
| static bool on_node = true; /* default to 1 */ | |
| module_param(on_node, bool, 0644); | |
| MODULE_PARM_DESC(on_node, "Run threads only on NTB device node (default: true)"); | |
| struct perf_mw { | |
| phys_addr_t phys_addr; | |
| resource_size_t phys_size; | |
| resource_size_t xlat_align; | |
| resource_size_t xlat_align_size; | |
| void __iomem *vbase; | |
| size_t xlat_size; | |
| size_t buf_size; | |
| void *virt_addr; | |
| dma_addr_t dma_addr; | |
| }; | |
| struct perf_ctx; | |
| struct pthr_ctx { | |
| struct task_struct *thread; | |
| struct perf_ctx *perf; | |
| atomic_t dma_sync; | |
| struct dma_chan *dma_chan; | |
| int dma_prep_err; | |
| int src_idx; | |
| void *srcs[MAX_SRCS]; | |
| wait_queue_head_t *wq; | |
| int status; | |
| u64 copied; | |
| u64 diff_us; | |
| }; | |
| struct perf_ctx { | |
| struct ntb_dev *ntb; | |
| spinlock_t db_lock; | |
| struct perf_mw mw; | |
| bool link_is_up; | |
| struct delayed_work link_work; | |
| wait_queue_head_t link_wq; | |
| u8 perf_threads; | |
| /* mutex ensures only one set of threads run at once */ | |
| struct mutex run_mutex; | |
| struct pthr_ctx pthr_ctx[MAX_THREADS]; | |
| atomic_t tsync; | |
| atomic_t tdone; | |
| }; | |
| enum { | |
| VERSION = 0, | |
| MW_SZ_HIGH, | |
| MW_SZ_LOW, | |
| MAX_SPAD | |
| }; | |
| static void perf_link_event(void *ctx) | |
| { | |
| struct perf_ctx *perf = ctx; | |
| if (ntb_link_is_up(perf->ntb, NULL, NULL) == 1) { | |
| schedule_delayed_work(&perf->link_work, 2*HZ); | |
| } else { | |
| dev_dbg(&perf->ntb->pdev->dev, "link down\n"); | |
| if (!perf->link_is_up) | |
| cancel_delayed_work_sync(&perf->link_work); | |
| perf->link_is_up = false; | |
| } | |
| } | |
| static void perf_db_event(void *ctx, int vec) | |
| { | |
| struct perf_ctx *perf = ctx; | |
| u64 db_bits, db_mask; | |
| db_mask = ntb_db_vector_mask(perf->ntb, vec); | |
| db_bits = ntb_db_read(perf->ntb); | |
| dev_dbg(&perf->ntb->dev, "doorbell vec %d mask %#llx bits %#llx\n", | |
| vec, db_mask, db_bits); | |
| } | |
| static const struct ntb_ctx_ops perf_ops = { | |
| .link_event = perf_link_event, | |
| .db_event = perf_db_event, | |
| }; | |
| static void perf_copy_callback(void *data) | |
| { | |
| struct pthr_ctx *pctx = data; | |
| atomic_dec(&pctx->dma_sync); | |
| } | |
| static ssize_t perf_copy(struct pthr_ctx *pctx, char __iomem *dst, | |
| char *src, size_t size) | |
| { | |
| struct perf_ctx *perf = pctx->perf; | |
| struct dma_async_tx_descriptor *txd; | |
| struct dma_chan *chan = pctx->dma_chan; | |
| struct dma_device *device; | |
| struct dmaengine_unmap_data *unmap; | |
| dma_cookie_t cookie; | |
| size_t src_off, dst_off; | |
| struct perf_mw *mw = &perf->mw; | |
| void __iomem *vbase; | |
| void __iomem *dst_vaddr; | |
| dma_addr_t dst_phys; | |
| int retries = 0; | |
| if (!use_dma) { | |
| memcpy_toio(dst, src, size); | |
| return size; | |
| } | |
| if (!chan) { | |
| dev_err(&perf->ntb->dev, "DMA engine does not exist\n"); | |
| return -EINVAL; | |
| } | |
| device = chan->device; | |
| src_off = (uintptr_t)src & ~PAGE_MASK; | |
| dst_off = (uintptr_t __force)dst & ~PAGE_MASK; | |
| if (!is_dma_copy_aligned(device, src_off, dst_off, size)) | |
| return -ENODEV; | |
| vbase = mw->vbase; | |
| dst_vaddr = dst; | |
| dst_phys = mw->phys_addr + (dst_vaddr - vbase); | |
| unmap = dmaengine_get_unmap_data(device->dev, 1, GFP_NOWAIT); | |
| if (!unmap) | |
| return -ENOMEM; | |
| unmap->len = size; | |
| unmap->addr[0] = dma_map_page(device->dev, virt_to_page(src), | |
| src_off, size, DMA_TO_DEVICE); | |
| if (dma_mapping_error(device->dev, unmap->addr[0])) | |
| goto err_get_unmap; | |
| unmap->to_cnt = 1; | |
| do { | |
| txd = device->device_prep_dma_memcpy(chan, dst_phys, | |
| unmap->addr[0], | |
| size, DMA_PREP_INTERRUPT); | |
| if (!txd) { | |
| set_current_state(TASK_INTERRUPTIBLE); | |
| schedule_timeout(DMA_OUT_RESOURCE_TO); | |
| } | |
| } while (!txd && (++retries < DMA_RETRIES)); | |
| if (!txd) { | |
| pctx->dma_prep_err++; | |
| goto err_get_unmap; | |
| } | |
| txd->callback = perf_copy_callback; | |
| txd->callback_param = pctx; | |
| dma_set_unmap(txd, unmap); | |
| cookie = dmaengine_submit(txd); | |
| if (dma_submit_error(cookie)) | |
| goto err_set_unmap; | |
| dmaengine_unmap_put(unmap); | |
| atomic_inc(&pctx->dma_sync); | |
| dma_async_issue_pending(chan); | |
| return size; | |
| err_set_unmap: | |
| dmaengine_unmap_put(unmap); | |
| err_get_unmap: | |
| dmaengine_unmap_put(unmap); | |
| return 0; | |
| } | |
| static int perf_move_data(struct pthr_ctx *pctx, char __iomem *dst, char *src, | |
| u64 buf_size, u64 win_size, u64 total) | |
| { | |
| int chunks, total_chunks, i; | |
| int copied_chunks = 0; | |
| u64 copied = 0, result; | |
| char __iomem *tmp = dst; | |
| u64 perf, diff_us; | |
| ktime_t kstart, kstop, kdiff; | |
| unsigned long last_sleep = jiffies; | |
| chunks = div64_u64(win_size, buf_size); | |
| total_chunks = div64_u64(total, buf_size); | |
| kstart = ktime_get(); | |
| for (i = 0; i < total_chunks; i++) { | |
| result = perf_copy(pctx, tmp, src, buf_size); | |
| copied += result; | |
| copied_chunks++; | |
| if (copied_chunks == chunks) { | |
| tmp = dst; | |
| copied_chunks = 0; | |
| } else | |
| tmp += buf_size; | |
| /* Probably should schedule every 5s to prevent soft hang. */ | |
| if (unlikely((jiffies - last_sleep) > 5 * HZ)) { | |
| last_sleep = jiffies; | |
| set_current_state(TASK_INTERRUPTIBLE); | |
| schedule_timeout(1); | |
| } | |
| if (unlikely(kthread_should_stop())) | |
| break; | |
| } | |
| if (use_dma) { | |
| pr_debug("%s: All DMA descriptors submitted\n", current->comm); | |
| while (atomic_read(&pctx->dma_sync) != 0) { | |
| if (kthread_should_stop()) | |
| break; | |
| msleep(20); | |
| } | |
| } | |
| kstop = ktime_get(); | |
| kdiff = ktime_sub(kstop, kstart); | |
| diff_us = ktime_to_us(kdiff); | |
| pr_debug("%s: copied %llu bytes\n", current->comm, copied); | |
| pr_debug("%s: lasted %llu usecs\n", current->comm, diff_us); | |
| perf = div64_u64(copied, diff_us); | |
| pr_debug("%s: MBytes/s: %llu\n", current->comm, perf); | |
| pctx->copied = copied; | |
| pctx->diff_us = diff_us; | |
| return 0; | |
| } | |
| static bool perf_dma_filter_fn(struct dma_chan *chan, void *node) | |
| { | |
| /* Is the channel required to be on the same node as the device? */ | |
| if (!on_node) | |
| return true; | |
| return dev_to_node(&chan->dev->device) == (int)(unsigned long)node; | |
| } | |
| static int ntb_perf_thread(void *data) | |
| { | |
| struct pthr_ctx *pctx = data; | |
| struct perf_ctx *perf = pctx->perf; | |
| struct pci_dev *pdev = perf->ntb->pdev; | |
| struct perf_mw *mw = &perf->mw; | |
| char __iomem *dst; | |
| u64 win_size, buf_size, total; | |
| void *src; | |
| int rc, node, i; | |
| struct dma_chan *dma_chan = NULL; | |
| pr_debug("kthread %s starting...\n", current->comm); | |
| node = on_node ? dev_to_node(&pdev->dev) : NUMA_NO_NODE; | |
| if (use_dma && !pctx->dma_chan) { | |
| dma_cap_mask_t dma_mask; | |
| dma_cap_zero(dma_mask); | |
| dma_cap_set(DMA_MEMCPY, dma_mask); | |
| dma_chan = dma_request_channel(dma_mask, perf_dma_filter_fn, | |
| (void *)(unsigned long)node); | |
| if (!dma_chan) { | |
| pr_warn("%s: cannot acquire DMA channel, quitting\n", | |
| current->comm); | |
| return -ENODEV; | |
| } | |
| pctx->dma_chan = dma_chan; | |
| } | |
| for (i = 0; i < MAX_SRCS; i++) { | |
| pctx->srcs[i] = kmalloc_node(MAX_TEST_SIZE, GFP_KERNEL, node); | |
| if (!pctx->srcs[i]) { | |
| rc = -ENOMEM; | |
| goto err; | |
| } | |
| } | |
| win_size = mw->phys_size; | |
| buf_size = 1ULL << seg_order; | |
| total = 1ULL << run_order; | |
| if (buf_size > MAX_TEST_SIZE) | |
| buf_size = MAX_TEST_SIZE; | |
| dst = (char __iomem *)mw->vbase; | |
| atomic_inc(&perf->tsync); | |
| while (atomic_read(&perf->tsync) != perf->perf_threads) | |
| schedule(); | |
| src = pctx->srcs[pctx->src_idx]; | |
| pctx->src_idx = (pctx->src_idx + 1) & (MAX_SRCS - 1); | |
| rc = perf_move_data(pctx, dst, src, buf_size, win_size, total); | |
| atomic_dec(&perf->tsync); | |
| if (rc < 0) { | |
| pr_err("%s: failed\n", current->comm); | |
| rc = -ENXIO; | |
| goto err; | |
| } | |
| for (i = 0; i < MAX_SRCS; i++) { | |
| kfree(pctx->srcs[i]); | |
| pctx->srcs[i] = NULL; | |
| } | |
| atomic_inc(&perf->tdone); | |
| wake_up(pctx->wq); | |
| rc = 0; | |
| goto done; | |
| err: | |
| for (i = 0; i < MAX_SRCS; i++) { | |
| kfree(pctx->srcs[i]); | |
| pctx->srcs[i] = NULL; | |
| } | |
| if (dma_chan) { | |
| dma_release_channel(dma_chan); | |
| pctx->dma_chan = NULL; | |
| } | |
| done: | |
| /* Wait until we are told to stop */ | |
| for (;;) { | |
| set_current_state(TASK_INTERRUPTIBLE); | |
| if (kthread_should_stop()) | |
| break; | |
| schedule(); | |
| } | |
| __set_current_state(TASK_RUNNING); | |
| return rc; | |
| } | |
| static void perf_free_mw(struct perf_ctx *perf) | |
| { | |
| struct perf_mw *mw = &perf->mw; | |
| struct pci_dev *pdev = perf->ntb->pdev; | |
| if (!mw->virt_addr) | |
| return; | |
| ntb_mw_clear_trans(perf->ntb, PIDX, 0); | |
| dma_free_coherent(&pdev->dev, mw->buf_size, | |
| mw->virt_addr, mw->dma_addr); | |
| mw->xlat_size = 0; | |
| mw->buf_size = 0; | |
| mw->virt_addr = NULL; | |
| } | |
| static int perf_set_mw(struct perf_ctx *perf, resource_size_t size) | |
| { | |
| struct perf_mw *mw = &perf->mw; | |
| size_t xlat_size, buf_size; | |
| int rc; | |
| if (!size) | |
| return -EINVAL; | |
| xlat_size = round_up(size, mw->xlat_align_size); | |
| buf_size = round_up(size, mw->xlat_align); | |
| if (mw->xlat_size == xlat_size) | |
| return 0; | |
| if (mw->buf_size) | |
| perf_free_mw(perf); | |
| mw->xlat_size = xlat_size; | |
| mw->buf_size = buf_size; | |
| mw->virt_addr = dma_alloc_coherent(&perf->ntb->pdev->dev, buf_size, | |
| &mw->dma_addr, GFP_KERNEL); | |
| if (!mw->virt_addr) { | |
| mw->xlat_size = 0; | |
| mw->buf_size = 0; | |
| } | |
| rc = ntb_mw_set_trans(perf->ntb, PIDX, 0, mw->dma_addr, mw->xlat_size); | |
| if (rc) { | |
| dev_err(&perf->ntb->dev, "Unable to set mw0 translation\n"); | |
| perf_free_mw(perf); | |
| return -EIO; | |
| } | |
| return 0; | |
| } | |
| static void perf_link_work(struct work_struct *work) | |
| { | |
| struct perf_ctx *perf = | |
| container_of(work, struct perf_ctx, link_work.work); | |
| struct ntb_dev *ndev = perf->ntb; | |
| struct pci_dev *pdev = ndev->pdev; | |
| u32 val; | |
| u64 size; | |
| int rc; | |
| dev_dbg(&perf->ntb->pdev->dev, "%s called\n", __func__); | |
| size = perf->mw.phys_size; | |
| if (max_mw_size && size > max_mw_size) | |
| size = max_mw_size; | |
| ntb_peer_spad_write(ndev, PIDX, MW_SZ_HIGH, upper_32_bits(size)); | |
| ntb_peer_spad_write(ndev, PIDX, MW_SZ_LOW, lower_32_bits(size)); | |
| ntb_peer_spad_write(ndev, PIDX, VERSION, PERF_VERSION); | |
| /* now read what peer wrote */ | |
| val = ntb_spad_read(ndev, VERSION); | |
| if (val != PERF_VERSION) { | |
| dev_dbg(&pdev->dev, "Remote version = %#x\n", val); | |
| goto out; | |
| } | |
| val = ntb_spad_read(ndev, MW_SZ_HIGH); | |
| size = (u64)val << 32; | |
| val = ntb_spad_read(ndev, MW_SZ_LOW); | |
| size |= val; | |
| dev_dbg(&pdev->dev, "Remote MW size = %#llx\n", size); | |
| rc = perf_set_mw(perf, size); | |
| if (rc) | |
| goto out1; | |
| perf->link_is_up = true; | |
| wake_up(&perf->link_wq); | |
| return; | |
| out1: | |
| perf_free_mw(perf); | |
| out: | |
| if (ntb_link_is_up(ndev, NULL, NULL) == 1) | |
| schedule_delayed_work(&perf->link_work, | |
| msecs_to_jiffies(PERF_LINK_DOWN_TIMEOUT)); | |
| } | |
| static int perf_setup_mw(struct ntb_dev *ntb, struct perf_ctx *perf) | |
| { | |
| struct perf_mw *mw; | |
| int rc; | |
| mw = &perf->mw; | |
| rc = ntb_mw_get_align(ntb, PIDX, 0, &mw->xlat_align, | |
| &mw->xlat_align_size, NULL); | |
| if (rc) | |
| return rc; | |
| rc = ntb_peer_mw_get_addr(ntb, 0, &mw->phys_addr, &mw->phys_size); | |
| if (rc) | |
| return rc; | |
| perf->mw.vbase = ioremap_wc(mw->phys_addr, mw->phys_size); | |
| if (!mw->vbase) | |
| return -ENOMEM; | |
| return 0; | |
| } | |
| static ssize_t debugfs_run_read(struct file *filp, char __user *ubuf, | |
| size_t count, loff_t *offp) | |
| { | |
| struct perf_ctx *perf = filp->private_data; | |
| char *buf; | |
| ssize_t ret, out_off = 0; | |
| struct pthr_ctx *pctx; | |
| int i; | |
| u64 rate; | |
| if (!perf) | |
| return 0; | |
| buf = kmalloc(1024, GFP_KERNEL); | |
| if (!buf) | |
| return -ENOMEM; | |
| if (mutex_is_locked(&perf->run_mutex)) { | |
| out_off = scnprintf(buf, 64, "running\n"); | |
| goto read_from_buf; | |
| } | |
| for (i = 0; i < MAX_THREADS; i++) { | |
| pctx = &perf->pthr_ctx[i]; | |
| if (pctx->status == -ENODATA) | |
| break; | |
| if (pctx->status) { | |
| out_off += scnprintf(buf + out_off, 1024 - out_off, | |
| "%d: error %d\n", i, | |
| pctx->status); | |
| continue; | |
| } | |
| rate = div64_u64(pctx->copied, pctx->diff_us); | |
| out_off += scnprintf(buf + out_off, 1024 - out_off, | |
| "%d: copied %llu bytes in %llu usecs, %llu MBytes/s\n", | |
| i, pctx->copied, pctx->diff_us, rate); | |
| } | |
| read_from_buf: | |
| ret = simple_read_from_buffer(ubuf, count, offp, buf, out_off); | |
| kfree(buf); | |
| return ret; | |
| } | |
| static void threads_cleanup(struct perf_ctx *perf) | |
| { | |
| struct pthr_ctx *pctx; | |
| int i; | |
| for (i = 0; i < MAX_THREADS; i++) { | |
| pctx = &perf->pthr_ctx[i]; | |
| if (pctx->thread) { | |
| pctx->status = kthread_stop(pctx->thread); | |
| pctx->thread = NULL; | |
| } | |
| } | |
| } | |
| static void perf_clear_thread_status(struct perf_ctx *perf) | |
| { | |
| int i; | |
| for (i = 0; i < MAX_THREADS; i++) | |
| perf->pthr_ctx[i].status = -ENODATA; | |
| } | |
| static ssize_t debugfs_run_write(struct file *filp, const char __user *ubuf, | |
| size_t count, loff_t *offp) | |
| { | |
| struct perf_ctx *perf = filp->private_data; | |
| int node, i; | |
| DECLARE_WAIT_QUEUE_HEAD(wq); | |
| if (wait_event_interruptible(perf->link_wq, perf->link_is_up)) | |
| return -ENOLINK; | |
| if (perf->perf_threads == 0) | |
| return -EINVAL; | |
| if (!mutex_trylock(&perf->run_mutex)) | |
| return -EBUSY; | |
| perf_clear_thread_status(perf); | |
| if (perf->perf_threads > MAX_THREADS) { | |
| perf->perf_threads = MAX_THREADS; | |
| pr_info("Reset total threads to: %u\n", MAX_THREADS); | |
| } | |
| /* no greater than 1M */ | |
| if (seg_order > MAX_SEG_ORDER) { | |
| seg_order = MAX_SEG_ORDER; | |
| pr_info("Fix seg_order to %u\n", seg_order); | |
| } | |
| if (run_order < seg_order) { | |
| run_order = seg_order; | |
| pr_info("Fix run_order to %u\n", run_order); | |
| } | |
| node = on_node ? dev_to_node(&perf->ntb->pdev->dev) | |
| : NUMA_NO_NODE; | |
| atomic_set(&perf->tdone, 0); | |
| /* launch kernel thread */ | |
| for (i = 0; i < perf->perf_threads; i++) { | |
| struct pthr_ctx *pctx; | |
| pctx = &perf->pthr_ctx[i]; | |
| atomic_set(&pctx->dma_sync, 0); | |
| pctx->perf = perf; | |
| pctx->wq = &wq; | |
| pctx->thread = | |
| kthread_create_on_node(ntb_perf_thread, | |
| (void *)pctx, | |
| node, "ntb_perf %d", i); | |
| if (IS_ERR(pctx->thread)) { | |
| pctx->thread = NULL; | |
| goto err; | |
| } else { | |
| wake_up_process(pctx->thread); | |
| } | |
| } | |
| wait_event_interruptible(wq, | |
| atomic_read(&perf->tdone) == perf->perf_threads); | |
| threads_cleanup(perf); | |
| mutex_unlock(&perf->run_mutex); | |
| return count; | |
| err: | |
| threads_cleanup(perf); | |
| mutex_unlock(&perf->run_mutex); | |
| return -ENXIO; | |
| } | |
| static const struct file_operations ntb_perf_debugfs_run = { | |
| .owner = THIS_MODULE, | |
| .open = simple_open, | |
| .read = debugfs_run_read, | |
| .write = debugfs_run_write, | |
| }; | |
| static int perf_debugfs_setup(struct perf_ctx *perf) | |
| { | |
| struct pci_dev *pdev = perf->ntb->pdev; | |
| struct dentry *debugfs_node_dir; | |
| struct dentry *debugfs_run; | |
| struct dentry *debugfs_threads; | |
| struct dentry *debugfs_seg_order; | |
| struct dentry *debugfs_run_order; | |
| struct dentry *debugfs_use_dma; | |
| struct dentry *debugfs_on_node; | |
| if (!debugfs_initialized()) | |
| return -ENODEV; | |
| /* Assumpion: only one NTB device in the system */ | |
| if (!perf_debugfs_dir) { | |
| perf_debugfs_dir = debugfs_create_dir(KBUILD_MODNAME, NULL); | |
| if (!perf_debugfs_dir) | |
| return -ENODEV; | |
| } | |
| debugfs_node_dir = debugfs_create_dir(pci_name(pdev), | |
| perf_debugfs_dir); | |
| if (!debugfs_node_dir) | |
| goto err; | |
| debugfs_run = debugfs_create_file("run", S_IRUSR | S_IWUSR, | |
| debugfs_node_dir, perf, | |
| &ntb_perf_debugfs_run); | |
| if (!debugfs_run) | |
| goto err; | |
| debugfs_threads = debugfs_create_u8("threads", S_IRUSR | S_IWUSR, | |
| debugfs_node_dir, | |
| &perf->perf_threads); | |
| if (!debugfs_threads) | |
| goto err; | |
| debugfs_seg_order = debugfs_create_u32("seg_order", 0600, | |
| debugfs_node_dir, | |
| &seg_order); | |
| if (!debugfs_seg_order) | |
| goto err; | |
| debugfs_run_order = debugfs_create_u32("run_order", 0600, | |
| debugfs_node_dir, | |
| &run_order); | |
| if (!debugfs_run_order) | |
| goto err; | |
| debugfs_use_dma = debugfs_create_bool("use_dma", 0600, | |
| debugfs_node_dir, | |
| &use_dma); | |
| if (!debugfs_use_dma) | |
| goto err; | |
| debugfs_on_node = debugfs_create_bool("on_node", 0600, | |
| debugfs_node_dir, | |
| &on_node); | |
| if (!debugfs_on_node) | |
| goto err; | |
| return 0; | |
| err: | |
| debugfs_remove_recursive(perf_debugfs_dir); | |
| perf_debugfs_dir = NULL; | |
| return -ENODEV; | |
| } | |
| static int perf_probe(struct ntb_client *client, struct ntb_dev *ntb) | |
| { | |
| struct pci_dev *pdev = ntb->pdev; | |
| struct perf_ctx *perf; | |
| int node; | |
| int rc = 0; | |
| if (ntb_spad_count(ntb) < MAX_SPAD) { | |
| dev_err(&ntb->dev, "Not enough scratch pad registers for %s", | |
| DRIVER_NAME); | |
| return -EIO; | |
| } | |
| if (!ntb->ops->mw_set_trans) { | |
| dev_err(&ntb->dev, "Need inbound MW based NTB API\n"); | |
| return -EINVAL; | |
| } | |
| if (ntb_peer_port_count(ntb) != NTB_DEF_PEER_CNT) | |
| dev_warn(&ntb->dev, "Multi-port NTB devices unsupported\n"); | |
| node = on_node ? dev_to_node(&pdev->dev) : NUMA_NO_NODE; | |
| perf = kzalloc_node(sizeof(*perf), GFP_KERNEL, node); | |
| if (!perf) { | |
| rc = -ENOMEM; | |
| goto err_perf; | |
| } | |
| perf->ntb = ntb; | |
| perf->perf_threads = 1; | |
| atomic_set(&perf->tsync, 0); | |
| mutex_init(&perf->run_mutex); | |
| spin_lock_init(&perf->db_lock); | |
| perf_setup_mw(ntb, perf); | |
| init_waitqueue_head(&perf->link_wq); | |
| INIT_DELAYED_WORK(&perf->link_work, perf_link_work); | |
| rc = ntb_set_ctx(ntb, perf, &perf_ops); | |
| if (rc) | |
| goto err_ctx; | |
| perf->link_is_up = false; | |
| ntb_link_enable(ntb, NTB_SPEED_AUTO, NTB_WIDTH_AUTO); | |
| ntb_link_event(ntb); | |
| rc = perf_debugfs_setup(perf); | |
| if (rc) | |
| goto err_ctx; | |
| perf_clear_thread_status(perf); | |
| return 0; | |
| err_ctx: | |
| cancel_delayed_work_sync(&perf->link_work); | |
| kfree(perf); | |
| err_perf: | |
| return rc; | |
| } | |
| static void perf_remove(struct ntb_client *client, struct ntb_dev *ntb) | |
| { | |
| struct perf_ctx *perf = ntb->ctx; | |
| int i; | |
| dev_dbg(&perf->ntb->dev, "%s called\n", __func__); | |
| mutex_lock(&perf->run_mutex); | |
| cancel_delayed_work_sync(&perf->link_work); | |
| ntb_clear_ctx(ntb); | |
| ntb_link_disable(ntb); | |
| debugfs_remove_recursive(perf_debugfs_dir); | |
| perf_debugfs_dir = NULL; | |
| if (use_dma) { | |
| for (i = 0; i < MAX_THREADS; i++) { | |
| struct pthr_ctx *pctx = &perf->pthr_ctx[i]; | |
| if (pctx->dma_chan) | |
| dma_release_channel(pctx->dma_chan); | |
| } | |
| } | |
| kfree(perf); | |
| } | |
| static struct ntb_client perf_client = { | |
| .ops = { | |
| .probe = perf_probe, | |
| .remove = perf_remove, | |
| }, | |
| }; | |
| module_ntb_client(perf_client); |