summaryrefslogtreecommitdiff
path: root/drivers/pci/endpoint/functions/pci-epf-test.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/endpoint/functions/pci-epf-test.c')
-rw-r--r--drivers/pci/endpoint/functions/pci-epf-test.c1222
1 files changed, 965 insertions, 257 deletions
diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c
index 53fff8030337..debd235253c5 100644
--- a/drivers/pci/endpoint/functions/pci-epf-test.c
+++ b/drivers/pci/endpoint/functions/pci-epf-test.c
@@ -1,41 +1,38 @@
-/**
+// SPDX-License-Identifier: GPL-2.0
+/*
* Test driver to test endpoint functionality
*
* Copyright (C) 2017 Texas Instruments
* Author: Kishon Vijay Abraham I <kishon@ti.com>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 of
- * the License as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/crc32.h>
#include <linux/delay.h>
+#include <linux/dmaengine.h>
#include <linux/io.h>
#include <linux/module.h>
+#include <linux/msi.h>
#include <linux/slab.h>
#include <linux/pci_ids.h>
#include <linux/random.h>
#include <linux/pci-epc.h>
#include <linux/pci-epf.h>
+#include <linux/pci-ep-msi.h>
#include <linux/pci_regs.h>
-#define COMMAND_RAISE_LEGACY_IRQ BIT(0)
+#define IRQ_TYPE_INTX 0
+#define IRQ_TYPE_MSI 1
+#define IRQ_TYPE_MSIX 2
+
+#define COMMAND_RAISE_INTX_IRQ BIT(0)
#define COMMAND_RAISE_MSI_IRQ BIT(1)
-#define MSI_NUMBER_SHIFT 2
-#define MSI_NUMBER_MASK (0x3f << MSI_NUMBER_SHIFT)
-#define COMMAND_READ BIT(8)
-#define COMMAND_WRITE BIT(9)
-#define COMMAND_COPY BIT(10)
+#define COMMAND_RAISE_MSIX_IRQ BIT(2)
+#define COMMAND_READ BIT(3)
+#define COMMAND_WRITE BIT(4)
+#define COMMAND_COPY BIT(5)
+#define COMMAND_ENABLE_DOORBELL BIT(6)
+#define COMMAND_DISABLE_DOORBELL BIT(7)
#define STATUS_READ_SUCCESS BIT(0)
#define STATUS_READ_FAIL BIT(1)
@@ -46,25 +43,56 @@
#define STATUS_IRQ_RAISED BIT(6)
#define STATUS_SRC_ADDR_INVALID BIT(7)
#define STATUS_DST_ADDR_INVALID BIT(8)
+#define STATUS_DOORBELL_SUCCESS BIT(9)
+#define STATUS_DOORBELL_ENABLE_SUCCESS BIT(10)
+#define STATUS_DOORBELL_ENABLE_FAIL BIT(11)
+#define STATUS_DOORBELL_DISABLE_SUCCESS BIT(12)
+#define STATUS_DOORBELL_DISABLE_FAIL BIT(13)
+
+#define FLAG_USE_DMA BIT(0)
#define TIMER_RESOLUTION 1
+#define CAP_UNALIGNED_ACCESS BIT(0)
+#define CAP_MSI BIT(1)
+#define CAP_MSIX BIT(2)
+#define CAP_INTX BIT(3)
+
static struct workqueue_struct *kpcitest_workqueue;
struct pci_epf_test {
- void *reg[6];
+ void *reg[PCI_STD_NUM_BARS];
struct pci_epf *epf;
+ enum pci_barno test_reg_bar;
+ size_t msix_table_offset;
struct delayed_work cmd_handler;
+ struct dma_chan *dma_chan_tx;
+ struct dma_chan *dma_chan_rx;
+ struct dma_chan *transfer_chan;
+ dma_cookie_t transfer_cookie;
+ enum dma_status transfer_status;
+ struct completion transfer_complete;
+ bool dma_supported;
+ bool dma_private;
+ const struct pci_epc_features *epc_features;
+ struct pci_epf_bar db_bar;
};
struct pci_epf_test_reg {
- u32 magic;
- u32 command;
- u32 status;
- u64 src_addr;
- u64 dst_addr;
- u32 size;
- u32 checksum;
+ __le32 magic;
+ __le32 command;
+ __le32 status;
+ __le64 src_addr;
+ __le64 dst_addr;
+ __le32 size;
+ __le32 checksum;
+ __le32 irq_type;
+ __le32 irq_number;
+ __le32 flags;
+ __le32 caps;
+ __le32 doorbell_bar;
+ __le32 doorbell_offset;
+ __le32 doorbell_data;
} __packed;
static struct pci_epf_header test_header = {
@@ -74,329 +102,980 @@ static struct pci_epf_header test_header = {
.interrupt_pin = PCI_INTERRUPT_INTA,
};
-static int bar_size[] = { 512, 1024, 16384, 131072, 1048576 };
+static size_t bar_size[] = { 512, 512, 1024, 16384, 131072, 1048576 };
-static int pci_epf_test_copy(struct pci_epf_test *epf_test)
+static void pci_epf_test_dma_callback(void *param)
{
- int ret;
- void __iomem *src_addr;
- void __iomem *dst_addr;
- phys_addr_t src_phys_addr;
- phys_addr_t dst_phys_addr;
+ struct pci_epf_test *epf_test = param;
+ struct dma_tx_state state;
+
+ epf_test->transfer_status =
+ dmaengine_tx_status(epf_test->transfer_chan,
+ epf_test->transfer_cookie, &state);
+ if (epf_test->transfer_status == DMA_COMPLETE ||
+ epf_test->transfer_status == DMA_ERROR)
+ complete(&epf_test->transfer_complete);
+}
+
+/**
+ * pci_epf_test_data_transfer() - Function that uses dmaengine API to transfer
+ * data between PCIe EP and remote PCIe RC
+ * @epf_test: the EPF test device that performs the data transfer operation
+ * @dma_dst: The destination address of the data transfer. It can be a physical
+ * address given by pci_epc_mem_alloc_addr or DMA mapping APIs.
+ * @dma_src: The source address of the data transfer. It can be a physical
+ * address given by pci_epc_mem_alloc_addr or DMA mapping APIs.
+ * @len: The size of the data transfer
+ * @dma_remote: remote RC physical address
+ * @dir: DMA transfer direction
+ *
+ * Function that uses dmaengine API to transfer data between PCIe EP and remote
+ * PCIe RC. The source and destination address can be a physical address given
+ * by pci_epc_mem_alloc_addr or the one obtained using DMA mapping APIs.
+ *
+ * The function returns '0' on success and negative value on failure.
+ */
+static int pci_epf_test_data_transfer(struct pci_epf_test *epf_test,
+ dma_addr_t dma_dst, dma_addr_t dma_src,
+ size_t len, dma_addr_t dma_remote,
+ enum dma_transfer_direction dir)
+{
+ struct dma_chan *chan = (dir == DMA_MEM_TO_DEV) ?
+ epf_test->dma_chan_tx : epf_test->dma_chan_rx;
+ dma_addr_t dma_local = (dir == DMA_MEM_TO_DEV) ? dma_src : dma_dst;
+ enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
struct pci_epf *epf = epf_test->epf;
+ struct dma_async_tx_descriptor *tx;
+ struct dma_slave_config sconf = {};
struct device *dev = &epf->dev;
- struct pci_epc *epc = epf->epc;
- struct pci_epf_test_reg *reg = epf_test->reg[0];
+ int ret;
- src_addr = pci_epc_mem_alloc_addr(epc, &src_phys_addr, reg->size);
- if (!src_addr) {
- dev_err(dev, "failed to allocate source address\n");
- reg->status = STATUS_SRC_ADDR_INVALID;
- ret = -ENOMEM;
- goto err;
+ if (IS_ERR_OR_NULL(chan)) {
+ dev_err(dev, "Invalid DMA memcpy channel\n");
+ return -EINVAL;
}
- ret = pci_epc_map_addr(epc, src_phys_addr, reg->src_addr, reg->size);
- if (ret) {
- dev_err(dev, "failed to map source address\n");
- reg->status = STATUS_SRC_ADDR_INVALID;
- goto err_src_addr;
+ if (epf_test->dma_private) {
+ sconf.direction = dir;
+ if (dir == DMA_MEM_TO_DEV)
+ sconf.dst_addr = dma_remote;
+ else
+ sconf.src_addr = dma_remote;
+
+ if (dmaengine_slave_config(chan, &sconf)) {
+ dev_err(dev, "DMA slave config fail\n");
+ return -EIO;
+ }
+ tx = dmaengine_prep_slave_single(chan, dma_local, len, dir,
+ flags);
+ } else {
+ tx = dmaengine_prep_dma_memcpy(chan, dma_dst, dma_src, len,
+ flags);
}
- dst_addr = pci_epc_mem_alloc_addr(epc, &dst_phys_addr, reg->size);
- if (!dst_addr) {
- dev_err(dev, "failed to allocate destination address\n");
- reg->status = STATUS_DST_ADDR_INVALID;
- ret = -ENOMEM;
- goto err_src_map_addr;
+ if (!tx) {
+ dev_err(dev, "Failed to prepare DMA memcpy\n");
+ return -EIO;
}
- ret = pci_epc_map_addr(epc, dst_phys_addr, reg->dst_addr, reg->size);
+ reinit_completion(&epf_test->transfer_complete);
+ epf_test->transfer_chan = chan;
+ tx->callback = pci_epf_test_dma_callback;
+ tx->callback_param = epf_test;
+ epf_test->transfer_cookie = dmaengine_submit(tx);
+
+ ret = dma_submit_error(epf_test->transfer_cookie);
if (ret) {
- dev_err(dev, "failed to map destination address\n");
- reg->status = STATUS_DST_ADDR_INVALID;
- goto err_dst_addr;
+ dev_err(dev, "Failed to do DMA tx_submit %d\n", ret);
+ goto terminate;
}
- memcpy(dst_addr, src_addr, reg->size);
+ dma_async_issue_pending(chan);
+ ret = wait_for_completion_interruptible(&epf_test->transfer_complete);
+ if (ret < 0) {
+ dev_err(dev, "DMA wait_for_completion interrupted\n");
+ goto terminate;
+ }
- pci_epc_unmap_addr(epc, dst_phys_addr);
+ if (epf_test->transfer_status == DMA_ERROR) {
+ dev_err(dev, "DMA transfer failed\n");
+ ret = -EIO;
+ }
-err_dst_addr:
- pci_epc_mem_free_addr(epc, dst_phys_addr, dst_addr, reg->size);
+terminate:
+ dmaengine_terminate_sync(chan);
-err_src_map_addr:
- pci_epc_unmap_addr(epc, src_phys_addr);
+ return ret;
+}
-err_src_addr:
- pci_epc_mem_free_addr(epc, src_phys_addr, src_addr, reg->size);
+struct epf_dma_filter {
+ struct device *dev;
+ u32 dma_mask;
+};
-err:
- return ret;
+static bool epf_dma_filter_fn(struct dma_chan *chan, void *node)
+{
+ struct epf_dma_filter *filter = node;
+ struct dma_slave_caps caps;
+
+ memset(&caps, 0, sizeof(caps));
+ dma_get_slave_caps(chan, &caps);
+
+ return chan->device->dev == filter->dev
+ && (filter->dma_mask & caps.directions);
}
-static int pci_epf_test_read(struct pci_epf_test *epf_test)
+/**
+ * pci_epf_test_init_dma_chan() - Function to initialize EPF test DMA channel
+ * @epf_test: the EPF test device that performs data transfer operation
+ *
+ * Function to initialize EPF test DMA channel.
+ */
+static int pci_epf_test_init_dma_chan(struct pci_epf_test *epf_test)
{
- int ret;
- void __iomem *src_addr;
- void *buf;
- u32 crc32;
- phys_addr_t phys_addr;
struct pci_epf *epf = epf_test->epf;
struct device *dev = &epf->dev;
- struct pci_epc *epc = epf->epc;
- struct pci_epf_test_reg *reg = epf_test->reg[0];
+ struct epf_dma_filter filter;
+ struct dma_chan *dma_chan;
+ dma_cap_mask_t mask;
+ int ret;
- src_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size);
- if (!src_addr) {
- dev_err(dev, "failed to allocate address\n");
- reg->status = STATUS_SRC_ADDR_INVALID;
- ret = -ENOMEM;
- goto err;
+ filter.dev = epf->epc->dev.parent;
+ filter.dma_mask = BIT(DMA_DEV_TO_MEM);
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_chan = dma_request_channel(mask, epf_dma_filter_fn, &filter);
+ if (!dma_chan) {
+ dev_info(dev, "Failed to get private DMA rx channel. Falling back to generic one\n");
+ goto fail_back_tx;
}
- ret = pci_epc_map_addr(epc, phys_addr, reg->src_addr, reg->size);
- if (ret) {
- dev_err(dev, "failed to map address\n");
- reg->status = STATUS_SRC_ADDR_INVALID;
- goto err_addr;
+ epf_test->dma_chan_rx = dma_chan;
+
+ filter.dma_mask = BIT(DMA_MEM_TO_DEV);
+ dma_chan = dma_request_channel(mask, epf_dma_filter_fn, &filter);
+
+ if (!dma_chan) {
+ dev_info(dev, "Failed to get private DMA tx channel. Falling back to generic one\n");
+ goto fail_back_rx;
}
- buf = kzalloc(reg->size, GFP_KERNEL);
- if (!buf) {
- ret = -ENOMEM;
- goto err_map_addr;
+ epf_test->dma_chan_tx = dma_chan;
+ epf_test->dma_private = true;
+
+ init_completion(&epf_test->transfer_complete);
+
+ return 0;
+
+fail_back_rx:
+ dma_release_channel(epf_test->dma_chan_rx);
+ epf_test->dma_chan_rx = NULL;
+
+fail_back_tx:
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+
+ dma_chan = dma_request_chan_by_mask(&mask);
+ if (IS_ERR(dma_chan)) {
+ ret = PTR_ERR(dma_chan);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get DMA channel\n");
+ return ret;
}
+ init_completion(&epf_test->transfer_complete);
- memcpy(buf, src_addr, reg->size);
+ epf_test->dma_chan_tx = epf_test->dma_chan_rx = dma_chan;
- crc32 = crc32_le(~0, buf, reg->size);
- if (crc32 != reg->checksum)
- ret = -EIO;
+ return 0;
+}
- kfree(buf);
+/**
+ * pci_epf_test_clean_dma_chan() - Function to cleanup EPF test DMA channel
+ * @epf_test: the EPF test device that performs data transfer operation
+ *
+ * Helper to cleanup EPF test DMA channel.
+ */
+static void pci_epf_test_clean_dma_chan(struct pci_epf_test *epf_test)
+{
+ if (!epf_test->dma_supported)
+ return;
+
+ if (epf_test->dma_chan_tx) {
+ dma_release_channel(epf_test->dma_chan_tx);
+ if (epf_test->dma_chan_tx == epf_test->dma_chan_rx) {
+ epf_test->dma_chan_tx = NULL;
+ epf_test->dma_chan_rx = NULL;
+ return;
+ }
+ epf_test->dma_chan_tx = NULL;
+ }
+
+ if (epf_test->dma_chan_rx) {
+ dma_release_channel(epf_test->dma_chan_rx);
+ epf_test->dma_chan_rx = NULL;
+ }
+}
-err_map_addr:
- pci_epc_unmap_addr(epc, phys_addr);
+static void pci_epf_test_print_rate(struct pci_epf_test *epf_test,
+ const char *op, u64 size,
+ struct timespec64 *start,
+ struct timespec64 *end, bool dma)
+{
+ struct timespec64 ts = timespec64_sub(*end, *start);
+ u64 rate = 0, ns;
-err_addr:
- pci_epc_mem_free_addr(epc, phys_addr, src_addr, reg->size);
+ /* calculate the rate */
+ ns = timespec64_to_ns(&ts);
+ if (ns)
+ rate = div64_u64(size * NSEC_PER_SEC, ns * 1000);
-err:
- return ret;
+ dev_info(&epf_test->epf->dev,
+ "%s => Size: %llu B, DMA: %s, Time: %ptSp s, Rate: %llu KB/s\n",
+ op, size, dma ? "YES" : "NO", &ts, rate);
}
-static int pci_epf_test_write(struct pci_epf_test *epf_test)
+static void pci_epf_test_copy(struct pci_epf_test *epf_test,
+ struct pci_epf_test_reg *reg)
{
- int ret;
- void __iomem *dst_addr;
- void *buf;
- phys_addr_t phys_addr;
+ int ret = 0;
+ struct timespec64 start, end;
struct pci_epf *epf = epf_test->epf;
- struct device *dev = &epf->dev;
struct pci_epc *epc = epf->epc;
- struct pci_epf_test_reg *reg = epf_test->reg[0];
+ struct device *dev = &epf->dev;
+ struct pci_epc_map src_map, dst_map;
+ u64 src_addr = le64_to_cpu(reg->src_addr);
+ u64 dst_addr = le64_to_cpu(reg->dst_addr);
+ size_t orig_size, copy_size;
+ ssize_t map_size = 0;
+ u32 flags = le32_to_cpu(reg->flags);
+ u32 status = 0;
+ void *copy_buf = NULL, *buf;
+
+ orig_size = copy_size = le32_to_cpu(reg->size);
+
+ if (flags & FLAG_USE_DMA) {
+ if (!dma_has_cap(DMA_MEMCPY, epf_test->dma_chan_tx->device->cap_mask)) {
+ dev_err(dev, "DMA controller doesn't support MEMCPY\n");
+ ret = -EINVAL;
+ goto set_status;
+ }
+ } else {
+ copy_buf = kzalloc(copy_size, GFP_KERNEL);
+ if (!copy_buf) {
+ ret = -ENOMEM;
+ goto set_status;
+ }
+ buf = copy_buf;
+ }
+
+ while (copy_size) {
+ ret = pci_epc_mem_map(epc, epf->func_no, epf->vfunc_no,
+ src_addr, copy_size, &src_map);
+ if (ret) {
+ dev_err(dev, "Failed to map source address\n");
+ status = STATUS_SRC_ADDR_INVALID;
+ goto free_buf;
+ }
+
+ ret = pci_epc_mem_map(epf->epc, epf->func_no, epf->vfunc_no,
+ dst_addr, copy_size, &dst_map);
+ if (ret) {
+ dev_err(dev, "Failed to map destination address\n");
+ status = STATUS_DST_ADDR_INVALID;
+ pci_epc_mem_unmap(epc, epf->func_no, epf->vfunc_no,
+ &src_map);
+ goto free_buf;
+ }
- dst_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size);
- if (!dst_addr) {
- dev_err(dev, "failed to allocate address\n");
- reg->status = STATUS_DST_ADDR_INVALID;
+ map_size = min_t(size_t, dst_map.pci_size, src_map.pci_size);
+
+ ktime_get_ts64(&start);
+ if (flags & FLAG_USE_DMA) {
+ ret = pci_epf_test_data_transfer(epf_test,
+ dst_map.phys_addr, src_map.phys_addr,
+ map_size, 0, DMA_MEM_TO_MEM);
+ if (ret) {
+ dev_err(dev, "Data transfer failed\n");
+ goto unmap;
+ }
+ } else {
+ memcpy_fromio(buf, src_map.virt_addr, map_size);
+ memcpy_toio(dst_map.virt_addr, buf, map_size);
+ buf += map_size;
+ }
+ ktime_get_ts64(&end);
+
+ copy_size -= map_size;
+ src_addr += map_size;
+ dst_addr += map_size;
+
+ pci_epc_mem_unmap(epc, epf->func_no, epf->vfunc_no, &dst_map);
+ pci_epc_mem_unmap(epc, epf->func_no, epf->vfunc_no, &src_map);
+ map_size = 0;
+ }
+
+ pci_epf_test_print_rate(epf_test, "COPY", orig_size, &start, &end,
+ flags & FLAG_USE_DMA);
+
+unmap:
+ if (map_size) {
+ pci_epc_mem_unmap(epc, epf->func_no, epf->vfunc_no, &dst_map);
+ pci_epc_mem_unmap(epc, epf->func_no, epf->vfunc_no, &src_map);
+ }
+
+free_buf:
+ kfree(copy_buf);
+
+set_status:
+ if (!ret)
+ status |= STATUS_COPY_SUCCESS;
+ else
+ status |= STATUS_COPY_FAIL;
+ reg->status = cpu_to_le32(status);
+}
+
+static void pci_epf_test_read(struct pci_epf_test *epf_test,
+ struct pci_epf_test_reg *reg)
+{
+ int ret = 0;
+ void *src_buf, *buf;
+ u32 crc32;
+ struct pci_epc_map map;
+ phys_addr_t dst_phys_addr;
+ struct timespec64 start, end;
+ struct pci_epf *epf = epf_test->epf;
+ struct pci_epc *epc = epf->epc;
+ struct device *dev = &epf->dev;
+ struct device *dma_dev = epf->epc->dev.parent;
+ u64 src_addr = le64_to_cpu(reg->src_addr);
+ size_t orig_size, src_size;
+ ssize_t map_size = 0;
+ u32 flags = le32_to_cpu(reg->flags);
+ u32 checksum = le32_to_cpu(reg->checksum);
+ u32 status = 0;
+
+ orig_size = src_size = le32_to_cpu(reg->size);
+
+ src_buf = kzalloc(src_size, GFP_KERNEL);
+ if (!src_buf) {
ret = -ENOMEM;
- goto err;
+ goto set_status;
}
+ buf = src_buf;
- ret = pci_epc_map_addr(epc, phys_addr, reg->dst_addr, reg->size);
- if (ret) {
- dev_err(dev, "failed to map address\n");
- reg->status = STATUS_DST_ADDR_INVALID;
- goto err_addr;
+ while (src_size) {
+ ret = pci_epc_mem_map(epc, epf->func_no, epf->vfunc_no,
+ src_addr, src_size, &map);
+ if (ret) {
+ dev_err(dev, "Failed to map address\n");
+ status = STATUS_SRC_ADDR_INVALID;
+ goto free_buf;
+ }
+
+ map_size = map.pci_size;
+ if (flags & FLAG_USE_DMA) {
+ dst_phys_addr = dma_map_single(dma_dev, buf, map_size,
+ DMA_FROM_DEVICE);
+ if (dma_mapping_error(dma_dev, dst_phys_addr)) {
+ dev_err(dev,
+ "Failed to map destination buffer addr\n");
+ ret = -ENOMEM;
+ goto unmap;
+ }
+
+ ktime_get_ts64(&start);
+ ret = pci_epf_test_data_transfer(epf_test,
+ dst_phys_addr, map.phys_addr,
+ map_size, src_addr, DMA_DEV_TO_MEM);
+ if (ret)
+ dev_err(dev, "Data transfer failed\n");
+ ktime_get_ts64(&end);
+
+ dma_unmap_single(dma_dev, dst_phys_addr, map_size,
+ DMA_FROM_DEVICE);
+
+ if (ret)
+ goto unmap;
+ } else {
+ ktime_get_ts64(&start);
+ memcpy_fromio(buf, map.virt_addr, map_size);
+ ktime_get_ts64(&end);
+ }
+
+ src_size -= map_size;
+ src_addr += map_size;
+ buf += map_size;
+
+ pci_epc_mem_unmap(epc, epf->func_no, epf->vfunc_no, &map);
+ map_size = 0;
}
- buf = kzalloc(reg->size, GFP_KERNEL);
- if (!buf) {
+ pci_epf_test_print_rate(epf_test, "READ", orig_size, &start, &end,
+ flags & FLAG_USE_DMA);
+
+ crc32 = crc32_le(~0, src_buf, orig_size);
+ if (crc32 != checksum)
+ ret = -EIO;
+
+unmap:
+ if (map_size)
+ pci_epc_mem_unmap(epc, epf->func_no, epf->vfunc_no, &map);
+
+free_buf:
+ kfree(src_buf);
+
+set_status:
+ if (!ret)
+ status |= STATUS_READ_SUCCESS;
+ else
+ status |= STATUS_READ_FAIL;
+ reg->status = cpu_to_le32(status);
+}
+
+static void pci_epf_test_write(struct pci_epf_test *epf_test,
+ struct pci_epf_test_reg *reg)
+{
+ int ret = 0;
+ void *dst_buf, *buf;
+ struct pci_epc_map map;
+ phys_addr_t src_phys_addr;
+ struct timespec64 start, end;
+ struct pci_epf *epf = epf_test->epf;
+ struct pci_epc *epc = epf->epc;
+ struct device *dev = &epf->dev;
+ struct device *dma_dev = epf->epc->dev.parent;
+ u64 dst_addr = le64_to_cpu(reg->dst_addr);
+ size_t orig_size, dst_size;
+ ssize_t map_size = 0;
+ u32 flags = le32_to_cpu(reg->flags);
+ u32 status = 0;
+
+ orig_size = dst_size = le32_to_cpu(reg->size);
+
+ dst_buf = kzalloc(dst_size, GFP_KERNEL);
+ if (!dst_buf) {
ret = -ENOMEM;
- goto err_map_addr;
+ goto set_status;
}
+ get_random_bytes(dst_buf, dst_size);
+ reg->checksum = cpu_to_le32(crc32_le(~0, dst_buf, dst_size));
+ buf = dst_buf;
+
+ while (dst_size) {
+ ret = pci_epc_mem_map(epc, epf->func_no, epf->vfunc_no,
+ dst_addr, dst_size, &map);
+ if (ret) {
+ dev_err(dev, "Failed to map address\n");
+ status = STATUS_DST_ADDR_INVALID;
+ goto free_buf;
+ }
+
+ map_size = map.pci_size;
+ if (flags & FLAG_USE_DMA) {
+ src_phys_addr = dma_map_single(dma_dev, buf, map_size,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(dma_dev, src_phys_addr)) {
+ dev_err(dev,
+ "Failed to map source buffer addr\n");
+ ret = -ENOMEM;
+ goto unmap;
+ }
+
+ ktime_get_ts64(&start);
+
+ ret = pci_epf_test_data_transfer(epf_test,
+ map.phys_addr, src_phys_addr,
+ map_size, dst_addr,
+ DMA_MEM_TO_DEV);
+ if (ret)
+ dev_err(dev, "Data transfer failed\n");
+ ktime_get_ts64(&end);
+
+ dma_unmap_single(dma_dev, src_phys_addr, map_size,
+ DMA_TO_DEVICE);
+
+ if (ret)
+ goto unmap;
+ } else {
+ ktime_get_ts64(&start);
+ memcpy_toio(map.virt_addr, buf, map_size);
+ ktime_get_ts64(&end);
+ }
+
+ dst_size -= map_size;
+ dst_addr += map_size;
+ buf += map_size;
- get_random_bytes(buf, reg->size);
- reg->checksum = crc32_le(~0, buf, reg->size);
+ pci_epc_mem_unmap(epc, epf->func_no, epf->vfunc_no, &map);
+ map_size = 0;
+ }
- memcpy(dst_addr, buf, reg->size);
+ pci_epf_test_print_rate(epf_test, "WRITE", orig_size, &start, &end,
+ flags & FLAG_USE_DMA);
/*
* wait 1ms inorder for the write to complete. Without this delay L3
* error in observed in the host system.
*/
- mdelay(1);
+ usleep_range(1000, 2000);
- kfree(buf);
+unmap:
+ if (map_size)
+ pci_epc_mem_unmap(epc, epf->func_no, epf->vfunc_no, &map);
-err_map_addr:
- pci_epc_unmap_addr(epc, phys_addr);
+free_buf:
+ kfree(dst_buf);
-err_addr:
- pci_epc_mem_free_addr(epc, phys_addr, dst_addr, reg->size);
+set_status:
+ if (!ret)
+ status |= STATUS_WRITE_SUCCESS;
+ else
+ status |= STATUS_WRITE_FAIL;
+ reg->status = cpu_to_le32(status);
+}
-err:
- return ret;
+static void pci_epf_test_raise_irq(struct pci_epf_test *epf_test,
+ struct pci_epf_test_reg *reg)
+{
+ struct pci_epf *epf = epf_test->epf;
+ struct device *dev = &epf->dev;
+ struct pci_epc *epc = epf->epc;
+ u32 status = le32_to_cpu(reg->status);
+ u32 irq_number = le32_to_cpu(reg->irq_number);
+ u32 irq_type = le32_to_cpu(reg->irq_type);
+ int count;
+
+ /*
+ * Set the status before raising the IRQ to ensure that the host sees
+ * the updated value when it gets the IRQ.
+ */
+ status |= STATUS_IRQ_RAISED;
+ WRITE_ONCE(reg->status, cpu_to_le32(status));
+
+ switch (irq_type) {
+ case IRQ_TYPE_INTX:
+ pci_epc_raise_irq(epc, epf->func_no, epf->vfunc_no,
+ PCI_IRQ_INTX, 0);
+ break;
+ case IRQ_TYPE_MSI:
+ count = pci_epc_get_msi(epc, epf->func_no, epf->vfunc_no);
+ if (irq_number > count || count <= 0) {
+ dev_err(dev, "Invalid MSI IRQ number %d / %d\n",
+ irq_number, count);
+ return;
+ }
+ pci_epc_raise_irq(epc, epf->func_no, epf->vfunc_no,
+ PCI_IRQ_MSI, irq_number);
+ break;
+ case IRQ_TYPE_MSIX:
+ count = pci_epc_get_msix(epc, epf->func_no, epf->vfunc_no);
+ if (irq_number > count || count <= 0) {
+ dev_err(dev, "Invalid MSI-X IRQ number %d / %d\n",
+ irq_number, count);
+ return;
+ }
+ pci_epc_raise_irq(epc, epf->func_no, epf->vfunc_no,
+ PCI_IRQ_MSIX, irq_number);
+ break;
+ default:
+ dev_err(dev, "Failed to raise IRQ, unknown type\n");
+ break;
+ }
}
-static void pci_epf_test_raise_irq(struct pci_epf_test *epf_test)
+static irqreturn_t pci_epf_test_doorbell_handler(int irq, void *data)
{
- u8 irq;
- u8 msi_count;
+ struct pci_epf_test *epf_test = data;
+ enum pci_barno test_reg_bar = epf_test->test_reg_bar;
+ struct pci_epf_test_reg *reg = epf_test->reg[test_reg_bar];
+ u32 status = le32_to_cpu(reg->status);
+
+ status |= STATUS_DOORBELL_SUCCESS;
+ reg->status = cpu_to_le32(status);
+ pci_epf_test_raise_irq(epf_test, reg);
+
+ return IRQ_HANDLED;
+}
+
+static void pci_epf_test_doorbell_cleanup(struct pci_epf_test *epf_test)
+{
+ struct pci_epf_test_reg *reg = epf_test->reg[epf_test->test_reg_bar];
+ struct pci_epf *epf = epf_test->epf;
+
+ free_irq(epf->db_msg[0].virq, epf_test);
+ reg->doorbell_bar = cpu_to_le32(NO_BAR);
+
+ pci_epf_free_doorbell(epf);
+}
+
+static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test,
+ struct pci_epf_test_reg *reg)
+{
+ u32 status = le32_to_cpu(reg->status);
struct pci_epf *epf = epf_test->epf;
struct pci_epc *epc = epf->epc;
- struct pci_epf_test_reg *reg = epf_test->reg[0];
+ struct msi_msg *msg;
+ enum pci_barno bar;
+ size_t offset;
+ int ret;
- reg->status |= STATUS_IRQ_RAISED;
- msi_count = pci_epc_get_msi(epc);
- irq = (reg->command & MSI_NUMBER_MASK) >> MSI_NUMBER_SHIFT;
- if (irq > msi_count || msi_count <= 0)
- pci_epc_raise_irq(epc, PCI_EPC_IRQ_LEGACY, 0);
- else
- pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSI, irq);
+ ret = pci_epf_alloc_doorbell(epf, 1);
+ if (ret)
+ goto set_status_err;
+
+ msg = &epf->db_msg[0].msg;
+ bar = pci_epc_get_next_free_bar(epf_test->epc_features, epf_test->test_reg_bar + 1);
+ if (bar < BAR_0)
+ goto err_doorbell_cleanup;
+
+ ret = request_threaded_irq(epf->db_msg[0].virq, NULL,
+ pci_epf_test_doorbell_handler, IRQF_ONESHOT,
+ "pci-ep-test-doorbell", epf_test);
+ if (ret) {
+ dev_err(&epf->dev,
+ "Failed to request doorbell IRQ: %d\n",
+ epf->db_msg[0].virq);
+ goto err_doorbell_cleanup;
+ }
+
+ reg->doorbell_data = cpu_to_le32(msg->data);
+ reg->doorbell_bar = cpu_to_le32(bar);
+
+ msg = &epf->db_msg[0].msg;
+ ret = pci_epf_align_inbound_addr(epf, bar, ((u64)msg->address_hi << 32) | msg->address_lo,
+ &epf_test->db_bar.phys_addr, &offset);
+
+ if (ret)
+ goto err_doorbell_cleanup;
+
+ reg->doorbell_offset = cpu_to_le32(offset);
+
+ epf_test->db_bar.barno = bar;
+ epf_test->db_bar.size = epf->bar[bar].size;
+ epf_test->db_bar.flags = epf->bar[bar].flags;
+
+ ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf_test->db_bar);
+ if (ret)
+ goto err_doorbell_cleanup;
+
+ status |= STATUS_DOORBELL_ENABLE_SUCCESS;
+ reg->status = cpu_to_le32(status);
+ return;
+
+err_doorbell_cleanup:
+ pci_epf_test_doorbell_cleanup(epf_test);
+set_status_err:
+ status |= STATUS_DOORBELL_ENABLE_FAIL;
+ reg->status = cpu_to_le32(status);
}
-static void pci_epf_test_cmd_handler(struct work_struct *work)
+static void pci_epf_test_disable_doorbell(struct pci_epf_test *epf_test,
+ struct pci_epf_test_reg *reg)
{
+ enum pci_barno bar = le32_to_cpu(reg->doorbell_bar);
+ u32 status = le32_to_cpu(reg->status);
+ struct pci_epf *epf = epf_test->epf;
+ struct pci_epc *epc = epf->epc;
int ret;
- u8 irq;
- u8 msi_count;
+
+ if (bar < BAR_0)
+ goto set_status_err;
+
+ pci_epf_test_doorbell_cleanup(epf_test);
+
+ /*
+ * The doorbell feature temporarily overrides the inbound translation
+ * to point to the address stored in epf_test->db_bar.phys_addr, i.e.,
+ * it calls set_bar() twice without ever calling clear_bar(), as
+ * calling clear_bar() would clear the BAR's PCI address assigned by
+ * the host. Thus, when disabling the doorbell, restore the inbound
+ * translation to point to the memory allocated for the BAR.
+ */
+ ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf->bar[bar]);
+ if (ret)
+ goto set_status_err;
+
+ status |= STATUS_DOORBELL_DISABLE_SUCCESS;
+ reg->status = cpu_to_le32(status);
+
+ return;
+
+set_status_err:
+ status |= STATUS_DOORBELL_DISABLE_FAIL;
+ reg->status = cpu_to_le32(status);
+}
+
+static void pci_epf_test_cmd_handler(struct work_struct *work)
+{
+ u32 command;
struct pci_epf_test *epf_test = container_of(work, struct pci_epf_test,
cmd_handler.work);
struct pci_epf *epf = epf_test->epf;
- struct pci_epc *epc = epf->epc;
- struct pci_epf_test_reg *reg = epf_test->reg[0];
+ struct device *dev = &epf->dev;
+ enum pci_barno test_reg_bar = epf_test->test_reg_bar;
+ struct pci_epf_test_reg *reg = epf_test->reg[test_reg_bar];
+ u32 irq_type = le32_to_cpu(reg->irq_type);
- if (!reg->command)
+ command = le32_to_cpu(READ_ONCE(reg->command));
+ if (!command)
goto reset_handler;
- if (reg->command & COMMAND_RAISE_LEGACY_IRQ) {
- reg->status = STATUS_IRQ_RAISED;
- pci_epc_raise_irq(epc, PCI_EPC_IRQ_LEGACY, 0);
- goto reset_handler;
- }
+ WRITE_ONCE(reg->command, 0);
+ WRITE_ONCE(reg->status, 0);
- if (reg->command & COMMAND_WRITE) {
- ret = pci_epf_test_write(epf_test);
- if (ret)
- reg->status |= STATUS_WRITE_FAIL;
- else
- reg->status |= STATUS_WRITE_SUCCESS;
- pci_epf_test_raise_irq(epf_test);
+ if ((le32_to_cpu(READ_ONCE(reg->flags)) & FLAG_USE_DMA) &&
+ !epf_test->dma_supported) {
+ dev_err(dev, "Cannot transfer data using DMA\n");
goto reset_handler;
}
- if (reg->command & COMMAND_READ) {
- ret = pci_epf_test_read(epf_test);
- if (!ret)
- reg->status |= STATUS_READ_SUCCESS;
- else
- reg->status |= STATUS_READ_FAIL;
- pci_epf_test_raise_irq(epf_test);
+ if (irq_type > IRQ_TYPE_MSIX) {
+ dev_err(dev, "Failed to detect IRQ type\n");
goto reset_handler;
}
- if (reg->command & COMMAND_COPY) {
- ret = pci_epf_test_copy(epf_test);
- if (!ret)
- reg->status |= STATUS_COPY_SUCCESS;
- else
- reg->status |= STATUS_COPY_FAIL;
- pci_epf_test_raise_irq(epf_test);
- goto reset_handler;
- }
-
- if (reg->command & COMMAND_RAISE_MSI_IRQ) {
- msi_count = pci_epc_get_msi(epc);
- irq = (reg->command & MSI_NUMBER_MASK) >> MSI_NUMBER_SHIFT;
- if (irq > msi_count || msi_count <= 0)
- goto reset_handler;
- reg->status = STATUS_IRQ_RAISED;
- pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSI, irq);
- goto reset_handler;
+ switch (command) {
+ case COMMAND_RAISE_INTX_IRQ:
+ case COMMAND_RAISE_MSI_IRQ:
+ case COMMAND_RAISE_MSIX_IRQ:
+ pci_epf_test_raise_irq(epf_test, reg);
+ break;
+ case COMMAND_WRITE:
+ pci_epf_test_write(epf_test, reg);
+ pci_epf_test_raise_irq(epf_test, reg);
+ break;
+ case COMMAND_READ:
+ pci_epf_test_read(epf_test, reg);
+ pci_epf_test_raise_irq(epf_test, reg);
+ break;
+ case COMMAND_COPY:
+ pci_epf_test_copy(epf_test, reg);
+ pci_epf_test_raise_irq(epf_test, reg);
+ break;
+ case COMMAND_ENABLE_DOORBELL:
+ pci_epf_test_enable_doorbell(epf_test, reg);
+ pci_epf_test_raise_irq(epf_test, reg);
+ break;
+ case COMMAND_DISABLE_DOORBELL:
+ pci_epf_test_disable_doorbell(epf_test, reg);
+ pci_epf_test_raise_irq(epf_test, reg);
+ break;
+ default:
+ dev_err(dev, "Invalid command 0x%x\n", command);
+ break;
}
reset_handler:
- reg->command = 0;
-
queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler,
msecs_to_jiffies(1));
}
-static void pci_epf_test_linkup(struct pci_epf *epf)
+static int pci_epf_test_set_bar(struct pci_epf *epf)
{
+ int bar, ret;
+ struct pci_epc *epc = epf->epc;
+ struct device *dev = &epf->dev;
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
+ enum pci_barno test_reg_bar = epf_test->test_reg_bar;
- queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler,
- msecs_to_jiffies(1));
+ for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
+ if (!epf_test->reg[bar])
+ continue;
+
+ ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no,
+ &epf->bar[bar]);
+ if (ret) {
+ pci_epf_free_space(epf, epf_test->reg[bar], bar,
+ PRIMARY_INTERFACE);
+ epf_test->reg[bar] = NULL;
+ dev_err(dev, "Failed to set BAR%d\n", bar);
+ if (bar == test_reg_bar)
+ return ret;
+ }
+ }
+
+ return 0;
}
-static void pci_epf_test_unbind(struct pci_epf *epf)
+static void pci_epf_test_clear_bar(struct pci_epf *epf)
{
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
struct pci_epc *epc = epf->epc;
int bar;
- cancel_delayed_work(&epf_test->cmd_handler);
- pci_epc_stop(epc);
- for (bar = BAR_0; bar <= BAR_5; bar++) {
- if (epf_test->reg[bar]) {
- pci_epf_free_space(epf, epf_test->reg[bar], bar);
- pci_epc_clear_bar(epc, bar);
- }
+ for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
+ if (!epf_test->reg[bar])
+ continue;
+
+ pci_epc_clear_bar(epc, epf->func_no, epf->vfunc_no,
+ &epf->bar[bar]);
}
}
-static int pci_epf_test_set_bar(struct pci_epf *epf)
+static void pci_epf_test_set_capabilities(struct pci_epf *epf)
{
- int flags;
- int bar;
- int ret;
- struct pci_epf_bar *epf_bar;
+ struct pci_epf_test *epf_test = epf_get_drvdata(epf);
+ enum pci_barno test_reg_bar = epf_test->test_reg_bar;
+ struct pci_epf_test_reg *reg = epf_test->reg[test_reg_bar];
struct pci_epc *epc = epf->epc;
- struct device *dev = &epf->dev;
+ u32 caps = 0;
+
+ if (epc->ops->align_addr)
+ caps |= CAP_UNALIGNED_ACCESS;
+
+ if (epf_test->epc_features->msi_capable)
+ caps |= CAP_MSI;
+
+ if (epf_test->epc_features->msix_capable)
+ caps |= CAP_MSIX;
+
+ if (epf_test->epc_features->intx_capable)
+ caps |= CAP_INTX;
+
+ reg->caps = cpu_to_le32(caps);
+}
+
+static int pci_epf_test_epc_init(struct pci_epf *epf)
+{
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
+ struct pci_epf_header *header = epf->header;
+ const struct pci_epc_features *epc_features = epf_test->epc_features;
+ struct pci_epc *epc = epf->epc;
+ struct device *dev = &epf->dev;
+ bool linkup_notifier = false;
+ int ret;
- flags = PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_32;
- if (sizeof(dma_addr_t) == 0x8)
- flags |= PCI_BASE_ADDRESS_MEM_TYPE_64;
+ epf_test->dma_supported = true;
+
+ ret = pci_epf_test_init_dma_chan(epf_test);
+ if (ret)
+ epf_test->dma_supported = false;
- for (bar = BAR_0; bar <= BAR_5; bar++) {
- epf_bar = &epf->bar[bar];
- ret = pci_epc_set_bar(epc, bar, epf_bar->phys_addr,
- epf_bar->size, flags);
+ if (epf->vfunc_no <= 1) {
+ ret = pci_epc_write_header(epc, epf->func_no, epf->vfunc_no, header);
if (ret) {
- pci_epf_free_space(epf, epf_test->reg[bar], bar);
- dev_err(dev, "failed to set BAR%d\n", bar);
- if (bar == BAR_0)
- return ret;
+ dev_err(dev, "Configuration header write failed\n");
+ return ret;
+ }
+ }
+
+ pci_epf_test_set_capabilities(epf);
+
+ ret = pci_epf_test_set_bar(epf);
+ if (ret)
+ return ret;
+
+ if (epc_features->msi_capable) {
+ ret = pci_epc_set_msi(epc, epf->func_no, epf->vfunc_no,
+ epf->msi_interrupts);
+ if (ret) {
+ dev_err(dev, "MSI configuration failed\n");
+ return ret;
}
}
+ if (epc_features->msix_capable) {
+ ret = pci_epc_set_msix(epc, epf->func_no, epf->vfunc_no,
+ epf->msix_interrupts,
+ epf_test->test_reg_bar,
+ epf_test->msix_table_offset);
+ if (ret) {
+ dev_err(dev, "MSI-X configuration failed\n");
+ return ret;
+ }
+ }
+
+ linkup_notifier = epc_features->linkup_notifier;
+ if (!linkup_notifier)
+ queue_work(kpcitest_workqueue, &epf_test->cmd_handler.work);
+
return 0;
}
+static void pci_epf_test_epc_deinit(struct pci_epf *epf)
+{
+ struct pci_epf_test *epf_test = epf_get_drvdata(epf);
+
+ cancel_delayed_work_sync(&epf_test->cmd_handler);
+ pci_epf_test_clean_dma_chan(epf_test);
+ pci_epf_test_clear_bar(epf);
+}
+
+static int pci_epf_test_link_up(struct pci_epf *epf)
+{
+ struct pci_epf_test *epf_test = epf_get_drvdata(epf);
+
+ queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler,
+ msecs_to_jiffies(1));
+
+ return 0;
+}
+
+static int pci_epf_test_link_down(struct pci_epf *epf)
+{
+ struct pci_epf_test *epf_test = epf_get_drvdata(epf);
+
+ cancel_delayed_work_sync(&epf_test->cmd_handler);
+
+ return 0;
+}
+
+static const struct pci_epc_event_ops pci_epf_test_event_ops = {
+ .epc_init = pci_epf_test_epc_init,
+ .epc_deinit = pci_epf_test_epc_deinit,
+ .link_up = pci_epf_test_link_up,
+ .link_down = pci_epf_test_link_down,
+};
+
static int pci_epf_test_alloc_space(struct pci_epf *epf)
{
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
struct device *dev = &epf->dev;
+ size_t msix_table_size = 0;
+ size_t test_reg_bar_size;
+ size_t pba_size = 0;
void *base;
- int bar;
+ enum pci_barno test_reg_bar = epf_test->test_reg_bar;
+ enum pci_barno bar;
+ const struct pci_epc_features *epc_features = epf_test->epc_features;
+ size_t test_reg_size;
+
+ test_reg_bar_size = ALIGN(sizeof(struct pci_epf_test_reg), 128);
+
+ if (epc_features->msix_capable) {
+ msix_table_size = PCI_MSIX_ENTRY_SIZE * epf->msix_interrupts;
+ epf_test->msix_table_offset = test_reg_bar_size;
+ /* Align to QWORD or 8 Bytes */
+ pba_size = ALIGN(DIV_ROUND_UP(epf->msix_interrupts, 8), 8);
+ }
+ test_reg_size = test_reg_bar_size + msix_table_size + pba_size;
- base = pci_epf_alloc_space(epf, sizeof(struct pci_epf_test_reg),
- BAR_0);
+ base = pci_epf_alloc_space(epf, test_reg_size, test_reg_bar,
+ epc_features, PRIMARY_INTERFACE);
if (!base) {
- dev_err(dev, "failed to allocated register space\n");
+ dev_err(dev, "Failed to allocated register space\n");
return -ENOMEM;
}
- epf_test->reg[0] = base;
+ epf_test->reg[test_reg_bar] = base;
+
+ for (bar = BAR_0; bar < PCI_STD_NUM_BARS; bar++) {
+ bar = pci_epc_get_next_free_bar(epc_features, bar);
+ if (bar == NO_BAR)
+ break;
- for (bar = BAR_1; bar <= BAR_5; bar++) {
- base = pci_epf_alloc_space(epf, bar_size[bar - 1], bar);
+ if (bar == test_reg_bar)
+ continue;
+
+ if (epc_features->bar[bar].type == BAR_FIXED)
+ test_reg_size = epc_features->bar[bar].fixed_size;
+ else
+ test_reg_size = bar_size[bar];
+
+ base = pci_epf_alloc_space(epf, test_reg_size, bar,
+ epc_features, PRIMARY_INTERFACE);
if (!base)
- dev_err(dev, "failed to allocate space for BAR%d\n",
+ dev_err(dev, "Failed to allocate space for BAR%d\n",
bar);
epf_test->reg[bar] = base;
}
@@ -404,38 +1083,74 @@ static int pci_epf_test_alloc_space(struct pci_epf *epf)
return 0;
}
+static void pci_epf_test_free_space(struct pci_epf *epf)
+{
+ struct pci_epf_test *epf_test = epf_get_drvdata(epf);
+ int bar;
+
+ for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
+ if (!epf_test->reg[bar])
+ continue;
+
+ pci_epf_free_space(epf, epf_test->reg[bar], bar,
+ PRIMARY_INTERFACE);
+ epf_test->reg[bar] = NULL;
+ }
+}
+
static int pci_epf_test_bind(struct pci_epf *epf)
{
int ret;
- struct pci_epf_header *header = epf->header;
+ struct pci_epf_test *epf_test = epf_get_drvdata(epf);
+ const struct pci_epc_features *epc_features;
+ enum pci_barno test_reg_bar = BAR_0;
struct pci_epc *epc = epf->epc;
- struct device *dev = &epf->dev;
if (WARN_ON_ONCE(!epc))
return -EINVAL;
- ret = pci_epc_write_header(epc, header);
- if (ret) {
- dev_err(dev, "configuration header write failed\n");
- return ret;
+ epc_features = pci_epc_get_features(epc, epf->func_no, epf->vfunc_no);
+ if (!epc_features) {
+ dev_err(&epf->dev, "epc_features not implemented\n");
+ return -EOPNOTSUPP;
}
- ret = pci_epf_test_alloc_space(epf);
- if (ret)
- return ret;
+ test_reg_bar = pci_epc_get_first_free_bar(epc_features);
+ if (test_reg_bar < 0)
+ return -EINVAL;
- ret = pci_epf_test_set_bar(epf);
- if (ret)
- return ret;
+ epf_test->test_reg_bar = test_reg_bar;
+ epf_test->epc_features = epc_features;
- ret = pci_epc_set_msi(epc, epf->msi_interrupts);
+ ret = pci_epf_test_alloc_space(epf);
if (ret)
return ret;
return 0;
}
-static int pci_epf_test_probe(struct pci_epf *epf)
+static void pci_epf_test_unbind(struct pci_epf *epf)
+{
+ struct pci_epf_test *epf_test = epf_get_drvdata(epf);
+ struct pci_epc *epc = epf->epc;
+
+ cancel_delayed_work_sync(&epf_test->cmd_handler);
+ if (epc->init_complete) {
+ pci_epf_test_clean_dma_chan(epf_test);
+ pci_epf_test_clear_bar(epf);
+ }
+ pci_epf_test_free_space(epf);
+}
+
+static const struct pci_epf_device_id pci_epf_test_ids[] = {
+ {
+ .name = "pci_epf_test",
+ },
+ {},
+};
+
+static int pci_epf_test_probe(struct pci_epf *epf,
+ const struct pci_epf_device_id *id)
{
struct pci_epf_test *epf_test;
struct device *dev = &epf->dev;
@@ -449,35 +1164,20 @@ static int pci_epf_test_probe(struct pci_epf *epf)
INIT_DELAYED_WORK(&epf_test->cmd_handler, pci_epf_test_cmd_handler);
- epf_set_drvdata(epf, epf_test);
- return 0;
-}
-
-static int pci_epf_test_remove(struct pci_epf *epf)
-{
- struct pci_epf_test *epf_test = epf_get_drvdata(epf);
+ epf->event_ops = &pci_epf_test_event_ops;
- kfree(epf_test);
+ epf_set_drvdata(epf, epf_test);
return 0;
}
-static struct pci_epf_ops ops = {
+static const struct pci_epf_ops ops = {
.unbind = pci_epf_test_unbind,
.bind = pci_epf_test_bind,
- .linkup = pci_epf_test_linkup,
-};
-
-static const struct pci_epf_device_id pci_epf_test_ids[] = {
- {
- .name = "pci_epf_test",
- },
- {},
};
static struct pci_epf_driver test_driver = {
.driver.name = "pci_epf_test",
.probe = pci_epf_test_probe,
- .remove = pci_epf_test_remove,
.id_table = pci_epf_test_ids,
.ops = &ops,
.owner = THIS_MODULE,
@@ -489,9 +1189,15 @@ static int __init pci_epf_test_init(void)
kpcitest_workqueue = alloc_workqueue("kpcitest",
WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
+ if (!kpcitest_workqueue) {
+ pr_err("Failed to allocate the kpcitest work queue\n");
+ return -ENOMEM;
+ }
+
ret = pci_epf_register_driver(&test_driver);
if (ret) {
- pr_err("failed to register pci epf test driver --> %d\n", ret);
+ destroy_workqueue(kpcitest_workqueue);
+ pr_err("Failed to register pci epf test driver --> %d\n", ret);
return ret;
}
@@ -501,6 +1207,8 @@ module_init(pci_epf_test_init);
static void __exit pci_epf_test_exit(void)
{
+ if (kpcitest_workqueue)
+ destroy_workqueue(kpcitest_workqueue);
pci_epf_unregister_driver(&test_driver);
}
module_exit(pci_epf_test_exit);