mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 14:42:24 +00:00
8e30538eca
The dma_map_single() doesn't permit zero length mapping. It causes a follow
panic.
A panic was reported on arm64:
[ 60.137988] ------------[ cut here ]------------
[ 60.142630] kernel BUG at kernel/dma/swiotlb.c:624!
[ 60.147508] Internal error: Oops - BUG: 0 [#1] PREEMPT SMP
[ 60.152992] Modules linked in: dw_hdmi_cec crct10dif_ce simple_bridge rcar_fdp1 vsp1 rcar_vin videobuf2_vmalloc rcar_csi2 v4l
2_mem2mem videobuf2_dma_contig videobuf2_memops pci_endpoint_test videobuf2_v4l2 videobuf2_common rcar_fcp v4l2_fwnode v4l2_asyn
c videodev mc gpio_bd9571mwv max9611 pwm_rcar ccree at24 authenc libdes phy_rcar_gen3_usb3 usb_dmac display_connector pwm_bl
[ 60.186252] CPU: 0 PID: 508 Comm: pcitest Not tainted 6.0.0-rc1rpci-dev+ #237
[ 60.193387] Hardware name: Renesas Salvator-X 2nd version board based on r8a77951 (DT)
[ 60.201302] pstate: 00000005 (nzcv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[ 60.208263] pc : swiotlb_tbl_map_single+0x2c0/0x590
[ 60.213149] lr : swiotlb_map+0x88/0x1f0
[ 60.216982] sp : ffff80000a883bc0
[ 60.220292] x29: ffff80000a883bc0 x28: 0000000000000000 x27: 0000000000000000
[ 60.227430] x26: 0000000000000000 x25: ffff0004c0da20d0 x24: ffff80000a1f77c0
[ 60.234567] x23: 0000000000000002 x22: 0001000040000010 x21: 000000007a000000
[ 60.241703] x20: 0000000000200000 x19: 0000000000000000 x18: 0000000000000000
[ 60.248840] x17: 0000000000000000 x16: 0000000000000000 x15: ffff0006ff7b9180
[ 60.255977] x14: ffff0006ff7b9180 x13: 0000000000000000 x12: 0000000000000000
[ 60.263113] x11: 0000000000000000 x10: 0000000000000000 x9 : 0000000000000000
[ 60.270249] x8 : 0001000000000010 x7 : ffff0004c6754b20 x6 : 0000000000000000
[ 60.277385] x5 : ffff0004c0da2090 x4 : 0000000000000000 x3 : 0000000000000001
[ 60.284521] x2 : 0000000040000000 x1 : 0000000000000000 x0 : 0000000040000010
[ 60.291658] Call trace:
[ 60.294100] swiotlb_tbl_map_single+0x2c0/0x590
[ 60.298629] swiotlb_map+0x88/0x1f0
[ 60.302115] dma_map_page_attrs+0x188/0x230
[ 60.306299] pci_endpoint_test_ioctl+0x5e4/0xd90 [pci_endpoint_test]
[ 60.312660] __arm64_sys_ioctl+0xa8/0xf0
[ 60.316583] invoke_syscall+0x44/0x108
[ 60.320334] el0_svc_common.constprop.0+0xcc/0xf0
[ 60.325038] do_el0_svc+0x2c/0xb8
[ 60.328351] el0_svc+0x2c/0x88
[ 60.331406] el0t_64_sync_handler+0xb8/0xc0
[ 60.335587] el0t_64_sync+0x18c/0x190
[ 60.339251] Code: 52800013 d2e00414 35fff45c d503201f (d4210000)
[ 60.345344] ---[ end trace 0000000000000000 ]---
To fix it, this patch adds a checking the payload length if it is zero.
Fixes: 343dc693f7
("misc: pci_endpoint_test: Prevent some integer overflows")
Cc: stable <stable@kernel.org>
Signed-off-by: Shunsuke Mie <mie@igel.co.jp>
Link: https://lore.kernel.org/r/20220907020100.122588-2-mie@igel.co.jp
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1019 lines
25 KiB
C
1019 lines
25 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/**
|
|
* Host side test driver to test endpoint functionality
|
|
*
|
|
* Copyright (C) 2017 Texas Instruments
|
|
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
|
*/
|
|
|
|
#include <linux/crc32.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/io.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/random.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/pci_ids.h>
|
|
|
|
#include <linux/pci_regs.h>
|
|
|
|
#include <uapi/linux/pcitest.h>
|
|
|
|
#define DRV_MODULE_NAME "pci-endpoint-test"
|
|
|
|
#define IRQ_TYPE_UNDEFINED -1
|
|
#define IRQ_TYPE_LEGACY 0
|
|
#define IRQ_TYPE_MSI 1
|
|
#define IRQ_TYPE_MSIX 2
|
|
|
|
#define PCI_ENDPOINT_TEST_MAGIC 0x0
|
|
|
|
#define PCI_ENDPOINT_TEST_COMMAND 0x4
|
|
#define COMMAND_RAISE_LEGACY_IRQ BIT(0)
|
|
#define COMMAND_RAISE_MSI_IRQ BIT(1)
|
|
#define COMMAND_RAISE_MSIX_IRQ BIT(2)
|
|
#define COMMAND_READ BIT(3)
|
|
#define COMMAND_WRITE BIT(4)
|
|
#define COMMAND_COPY BIT(5)
|
|
|
|
#define PCI_ENDPOINT_TEST_STATUS 0x8
|
|
#define STATUS_READ_SUCCESS BIT(0)
|
|
#define STATUS_READ_FAIL BIT(1)
|
|
#define STATUS_WRITE_SUCCESS BIT(2)
|
|
#define STATUS_WRITE_FAIL BIT(3)
|
|
#define STATUS_COPY_SUCCESS BIT(4)
|
|
#define STATUS_COPY_FAIL BIT(5)
|
|
#define STATUS_IRQ_RAISED BIT(6)
|
|
#define STATUS_SRC_ADDR_INVALID BIT(7)
|
|
#define STATUS_DST_ADDR_INVALID BIT(8)
|
|
|
|
#define PCI_ENDPOINT_TEST_LOWER_SRC_ADDR 0x0c
|
|
#define PCI_ENDPOINT_TEST_UPPER_SRC_ADDR 0x10
|
|
|
|
#define PCI_ENDPOINT_TEST_LOWER_DST_ADDR 0x14
|
|
#define PCI_ENDPOINT_TEST_UPPER_DST_ADDR 0x18
|
|
|
|
#define PCI_ENDPOINT_TEST_SIZE 0x1c
|
|
#define PCI_ENDPOINT_TEST_CHECKSUM 0x20
|
|
|
|
#define PCI_ENDPOINT_TEST_IRQ_TYPE 0x24
|
|
#define PCI_ENDPOINT_TEST_IRQ_NUMBER 0x28
|
|
|
|
#define PCI_ENDPOINT_TEST_FLAGS 0x2c
|
|
#define FLAG_USE_DMA BIT(0)
|
|
|
|
#define PCI_DEVICE_ID_TI_AM654 0xb00c
|
|
#define PCI_DEVICE_ID_TI_J7200 0xb00f
|
|
#define PCI_DEVICE_ID_TI_AM64 0xb010
|
|
#define PCI_DEVICE_ID_LS1088A 0x80c0
|
|
|
|
#define is_am654_pci_dev(pdev) \
|
|
((pdev)->device == PCI_DEVICE_ID_TI_AM654)
|
|
|
|
#define PCI_DEVICE_ID_RENESAS_R8A774A1 0x0028
|
|
#define PCI_DEVICE_ID_RENESAS_R8A774B1 0x002b
|
|
#define PCI_DEVICE_ID_RENESAS_R8A774C0 0x002d
|
|
#define PCI_DEVICE_ID_RENESAS_R8A774E1 0x0025
|
|
|
|
static DEFINE_IDA(pci_endpoint_test_ida);
|
|
|
|
#define to_endpoint_test(priv) container_of((priv), struct pci_endpoint_test, \
|
|
miscdev)
|
|
|
|
static bool no_msi;
|
|
module_param(no_msi, bool, 0444);
|
|
MODULE_PARM_DESC(no_msi, "Disable MSI interrupt in pci_endpoint_test");
|
|
|
|
static int irq_type = IRQ_TYPE_MSI;
|
|
module_param(irq_type, int, 0444);
|
|
MODULE_PARM_DESC(irq_type, "IRQ mode selection in pci_endpoint_test (0 - Legacy, 1 - MSI, 2 - MSI-X)");
|
|
|
|
enum pci_barno {
|
|
BAR_0,
|
|
BAR_1,
|
|
BAR_2,
|
|
BAR_3,
|
|
BAR_4,
|
|
BAR_5,
|
|
};
|
|
|
|
struct pci_endpoint_test {
|
|
struct pci_dev *pdev;
|
|
void __iomem *base;
|
|
void __iomem *bar[PCI_STD_NUM_BARS];
|
|
struct completion irq_raised;
|
|
int last_irq;
|
|
int num_irqs;
|
|
int irq_type;
|
|
/* mutex to protect the ioctls */
|
|
struct mutex mutex;
|
|
struct miscdevice miscdev;
|
|
enum pci_barno test_reg_bar;
|
|
size_t alignment;
|
|
const char *name;
|
|
};
|
|
|
|
struct pci_endpoint_test_data {
|
|
enum pci_barno test_reg_bar;
|
|
size_t alignment;
|
|
int irq_type;
|
|
};
|
|
|
|
static inline u32 pci_endpoint_test_readl(struct pci_endpoint_test *test,
|
|
u32 offset)
|
|
{
|
|
return readl(test->base + offset);
|
|
}
|
|
|
|
static inline void pci_endpoint_test_writel(struct pci_endpoint_test *test,
|
|
u32 offset, u32 value)
|
|
{
|
|
writel(value, test->base + offset);
|
|
}
|
|
|
|
static inline u32 pci_endpoint_test_bar_readl(struct pci_endpoint_test *test,
|
|
int bar, int offset)
|
|
{
|
|
return readl(test->bar[bar] + offset);
|
|
}
|
|
|
|
static inline void pci_endpoint_test_bar_writel(struct pci_endpoint_test *test,
|
|
int bar, u32 offset, u32 value)
|
|
{
|
|
writel(value, test->bar[bar] + offset);
|
|
}
|
|
|
|
static irqreturn_t pci_endpoint_test_irqhandler(int irq, void *dev_id)
|
|
{
|
|
struct pci_endpoint_test *test = dev_id;
|
|
u32 reg;
|
|
|
|
reg = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_STATUS);
|
|
if (reg & STATUS_IRQ_RAISED) {
|
|
test->last_irq = irq;
|
|
complete(&test->irq_raised);
|
|
reg &= ~STATUS_IRQ_RAISED;
|
|
}
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS,
|
|
reg);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void pci_endpoint_test_free_irq_vectors(struct pci_endpoint_test *test)
|
|
{
|
|
struct pci_dev *pdev = test->pdev;
|
|
|
|
pci_free_irq_vectors(pdev);
|
|
test->irq_type = IRQ_TYPE_UNDEFINED;
|
|
}
|
|
|
|
static bool pci_endpoint_test_alloc_irq_vectors(struct pci_endpoint_test *test,
|
|
int type)
|
|
{
|
|
int irq = -1;
|
|
struct pci_dev *pdev = test->pdev;
|
|
struct device *dev = &pdev->dev;
|
|
bool res = true;
|
|
|
|
switch (type) {
|
|
case IRQ_TYPE_LEGACY:
|
|
irq = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_LEGACY);
|
|
if (irq < 0)
|
|
dev_err(dev, "Failed to get Legacy interrupt\n");
|
|
break;
|
|
case IRQ_TYPE_MSI:
|
|
irq = pci_alloc_irq_vectors(pdev, 1, 32, PCI_IRQ_MSI);
|
|
if (irq < 0)
|
|
dev_err(dev, "Failed to get MSI interrupts\n");
|
|
break;
|
|
case IRQ_TYPE_MSIX:
|
|
irq = pci_alloc_irq_vectors(pdev, 1, 2048, PCI_IRQ_MSIX);
|
|
if (irq < 0)
|
|
dev_err(dev, "Failed to get MSI-X interrupts\n");
|
|
break;
|
|
default:
|
|
dev_err(dev, "Invalid IRQ type selected\n");
|
|
}
|
|
|
|
if (irq < 0) {
|
|
irq = 0;
|
|
res = false;
|
|
}
|
|
|
|
test->irq_type = type;
|
|
test->num_irqs = irq;
|
|
|
|
return res;
|
|
}
|
|
|
|
static void pci_endpoint_test_release_irq(struct pci_endpoint_test *test)
|
|
{
|
|
int i;
|
|
struct pci_dev *pdev = test->pdev;
|
|
struct device *dev = &pdev->dev;
|
|
|
|
for (i = 0; i < test->num_irqs; i++)
|
|
devm_free_irq(dev, pci_irq_vector(pdev, i), test);
|
|
|
|
test->num_irqs = 0;
|
|
}
|
|
|
|
static bool pci_endpoint_test_request_irq(struct pci_endpoint_test *test)
|
|
{
|
|
int i;
|
|
int err;
|
|
struct pci_dev *pdev = test->pdev;
|
|
struct device *dev = &pdev->dev;
|
|
|
|
for (i = 0; i < test->num_irqs; i++) {
|
|
err = devm_request_irq(dev, pci_irq_vector(pdev, i),
|
|
pci_endpoint_test_irqhandler,
|
|
IRQF_SHARED, test->name, test);
|
|
if (err)
|
|
goto fail;
|
|
}
|
|
|
|
return true;
|
|
|
|
fail:
|
|
switch (irq_type) {
|
|
case IRQ_TYPE_LEGACY:
|
|
dev_err(dev, "Failed to request IRQ %d for Legacy\n",
|
|
pci_irq_vector(pdev, i));
|
|
break;
|
|
case IRQ_TYPE_MSI:
|
|
dev_err(dev, "Failed to request IRQ %d for MSI %d\n",
|
|
pci_irq_vector(pdev, i),
|
|
i + 1);
|
|
break;
|
|
case IRQ_TYPE_MSIX:
|
|
dev_err(dev, "Failed to request IRQ %d for MSI-X %d\n",
|
|
pci_irq_vector(pdev, i),
|
|
i + 1);
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool pci_endpoint_test_bar(struct pci_endpoint_test *test,
|
|
enum pci_barno barno)
|
|
{
|
|
int j;
|
|
u32 val;
|
|
int size;
|
|
struct pci_dev *pdev = test->pdev;
|
|
|
|
if (!test->bar[barno])
|
|
return false;
|
|
|
|
size = pci_resource_len(pdev, barno);
|
|
|
|
if (barno == test->test_reg_bar)
|
|
size = 0x4;
|
|
|
|
for (j = 0; j < size; j += 4)
|
|
pci_endpoint_test_bar_writel(test, barno, j, 0xA0A0A0A0);
|
|
|
|
for (j = 0; j < size; j += 4) {
|
|
val = pci_endpoint_test_bar_readl(test, barno, j);
|
|
if (val != 0xA0A0A0A0)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool pci_endpoint_test_legacy_irq(struct pci_endpoint_test *test)
|
|
{
|
|
u32 val;
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE,
|
|
IRQ_TYPE_LEGACY);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 0);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
|
|
COMMAND_RAISE_LEGACY_IRQ);
|
|
val = wait_for_completion_timeout(&test->irq_raised,
|
|
msecs_to_jiffies(1000));
|
|
if (!val)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool pci_endpoint_test_msi_irq(struct pci_endpoint_test *test,
|
|
u16 msi_num, bool msix)
|
|
{
|
|
u32 val;
|
|
struct pci_dev *pdev = test->pdev;
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE,
|
|
msix == false ? IRQ_TYPE_MSI :
|
|
IRQ_TYPE_MSIX);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, msi_num);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
|
|
msix == false ? COMMAND_RAISE_MSI_IRQ :
|
|
COMMAND_RAISE_MSIX_IRQ);
|
|
val = wait_for_completion_timeout(&test->irq_raised,
|
|
msecs_to_jiffies(1000));
|
|
if (!val)
|
|
return false;
|
|
|
|
if (pci_irq_vector(pdev, msi_num - 1) == test->last_irq)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int pci_endpoint_test_validate_xfer_params(struct device *dev,
|
|
struct pci_endpoint_test_xfer_param *param, size_t alignment)
|
|
{
|
|
if (!param->size) {
|
|
dev_dbg(dev, "Data size is zero\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (param->size > SIZE_MAX - alignment) {
|
|
dev_dbg(dev, "Maximum transfer data size exceeded\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool pci_endpoint_test_copy(struct pci_endpoint_test *test,
|
|
unsigned long arg)
|
|
{
|
|
struct pci_endpoint_test_xfer_param param;
|
|
bool ret = false;
|
|
void *src_addr;
|
|
void *dst_addr;
|
|
u32 flags = 0;
|
|
bool use_dma;
|
|
size_t size;
|
|
dma_addr_t src_phys_addr;
|
|
dma_addr_t dst_phys_addr;
|
|
struct pci_dev *pdev = test->pdev;
|
|
struct device *dev = &pdev->dev;
|
|
void *orig_src_addr;
|
|
dma_addr_t orig_src_phys_addr;
|
|
void *orig_dst_addr;
|
|
dma_addr_t orig_dst_phys_addr;
|
|
size_t offset;
|
|
size_t alignment = test->alignment;
|
|
int irq_type = test->irq_type;
|
|
u32 src_crc32;
|
|
u32 dst_crc32;
|
|
int err;
|
|
|
|
err = copy_from_user(¶m, (void __user *)arg, sizeof(param));
|
|
if (err) {
|
|
dev_err(dev, "Failed to get transfer param\n");
|
|
return false;
|
|
}
|
|
|
|
err = pci_endpoint_test_validate_xfer_params(dev, ¶m, alignment);
|
|
if (err)
|
|
return false;
|
|
|
|
size = param.size;
|
|
|
|
use_dma = !!(param.flags & PCITEST_FLAGS_USE_DMA);
|
|
if (use_dma)
|
|
flags |= FLAG_USE_DMA;
|
|
|
|
if (irq_type < IRQ_TYPE_LEGACY || irq_type > IRQ_TYPE_MSIX) {
|
|
dev_err(dev, "Invalid IRQ type option\n");
|
|
goto err;
|
|
}
|
|
|
|
orig_src_addr = kzalloc(size + alignment, GFP_KERNEL);
|
|
if (!orig_src_addr) {
|
|
dev_err(dev, "Failed to allocate source buffer\n");
|
|
ret = false;
|
|
goto err;
|
|
}
|
|
|
|
get_random_bytes(orig_src_addr, size + alignment);
|
|
orig_src_phys_addr = dma_map_single(dev, orig_src_addr,
|
|
size + alignment, DMA_TO_DEVICE);
|
|
if (dma_mapping_error(dev, orig_src_phys_addr)) {
|
|
dev_err(dev, "failed to map source buffer address\n");
|
|
ret = false;
|
|
goto err_src_phys_addr;
|
|
}
|
|
|
|
if (alignment && !IS_ALIGNED(orig_src_phys_addr, alignment)) {
|
|
src_phys_addr = PTR_ALIGN(orig_src_phys_addr, alignment);
|
|
offset = src_phys_addr - orig_src_phys_addr;
|
|
src_addr = orig_src_addr + offset;
|
|
} else {
|
|
src_phys_addr = orig_src_phys_addr;
|
|
src_addr = orig_src_addr;
|
|
}
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_LOWER_SRC_ADDR,
|
|
lower_32_bits(src_phys_addr));
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_UPPER_SRC_ADDR,
|
|
upper_32_bits(src_phys_addr));
|
|
|
|
src_crc32 = crc32_le(~0, src_addr, size);
|
|
|
|
orig_dst_addr = kzalloc(size + alignment, GFP_KERNEL);
|
|
if (!orig_dst_addr) {
|
|
dev_err(dev, "Failed to allocate destination address\n");
|
|
ret = false;
|
|
goto err_dst_addr;
|
|
}
|
|
|
|
orig_dst_phys_addr = dma_map_single(dev, orig_dst_addr,
|
|
size + alignment, DMA_FROM_DEVICE);
|
|
if (dma_mapping_error(dev, orig_dst_phys_addr)) {
|
|
dev_err(dev, "failed to map destination buffer address\n");
|
|
ret = false;
|
|
goto err_dst_phys_addr;
|
|
}
|
|
|
|
if (alignment && !IS_ALIGNED(orig_dst_phys_addr, alignment)) {
|
|
dst_phys_addr = PTR_ALIGN(orig_dst_phys_addr, alignment);
|
|
offset = dst_phys_addr - orig_dst_phys_addr;
|
|
dst_addr = orig_dst_addr + offset;
|
|
} else {
|
|
dst_phys_addr = orig_dst_phys_addr;
|
|
dst_addr = orig_dst_addr;
|
|
}
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_LOWER_DST_ADDR,
|
|
lower_32_bits(dst_phys_addr));
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_UPPER_DST_ADDR,
|
|
upper_32_bits(dst_phys_addr));
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE,
|
|
size);
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_FLAGS, flags);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
|
|
COMMAND_COPY);
|
|
|
|
wait_for_completion(&test->irq_raised);
|
|
|
|
dma_unmap_single(dev, orig_dst_phys_addr, size + alignment,
|
|
DMA_FROM_DEVICE);
|
|
|
|
dst_crc32 = crc32_le(~0, dst_addr, size);
|
|
if (dst_crc32 == src_crc32)
|
|
ret = true;
|
|
|
|
err_dst_phys_addr:
|
|
kfree(orig_dst_addr);
|
|
|
|
err_dst_addr:
|
|
dma_unmap_single(dev, orig_src_phys_addr, size + alignment,
|
|
DMA_TO_DEVICE);
|
|
|
|
err_src_phys_addr:
|
|
kfree(orig_src_addr);
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static bool pci_endpoint_test_write(struct pci_endpoint_test *test,
|
|
unsigned long arg)
|
|
{
|
|
struct pci_endpoint_test_xfer_param param;
|
|
bool ret = false;
|
|
u32 flags = 0;
|
|
bool use_dma;
|
|
u32 reg;
|
|
void *addr;
|
|
dma_addr_t phys_addr;
|
|
struct pci_dev *pdev = test->pdev;
|
|
struct device *dev = &pdev->dev;
|
|
void *orig_addr;
|
|
dma_addr_t orig_phys_addr;
|
|
size_t offset;
|
|
size_t alignment = test->alignment;
|
|
int irq_type = test->irq_type;
|
|
size_t size;
|
|
u32 crc32;
|
|
int err;
|
|
|
|
err = copy_from_user(¶m, (void __user *)arg, sizeof(param));
|
|
if (err != 0) {
|
|
dev_err(dev, "Failed to get transfer param\n");
|
|
return false;
|
|
}
|
|
|
|
err = pci_endpoint_test_validate_xfer_params(dev, ¶m, alignment);
|
|
if (err)
|
|
return false;
|
|
|
|
size = param.size;
|
|
|
|
use_dma = !!(param.flags & PCITEST_FLAGS_USE_DMA);
|
|
if (use_dma)
|
|
flags |= FLAG_USE_DMA;
|
|
|
|
if (irq_type < IRQ_TYPE_LEGACY || irq_type > IRQ_TYPE_MSIX) {
|
|
dev_err(dev, "Invalid IRQ type option\n");
|
|
goto err;
|
|
}
|
|
|
|
orig_addr = kzalloc(size + alignment, GFP_KERNEL);
|
|
if (!orig_addr) {
|
|
dev_err(dev, "Failed to allocate address\n");
|
|
ret = false;
|
|
goto err;
|
|
}
|
|
|
|
get_random_bytes(orig_addr, size + alignment);
|
|
|
|
orig_phys_addr = dma_map_single(dev, orig_addr, size + alignment,
|
|
DMA_TO_DEVICE);
|
|
if (dma_mapping_error(dev, orig_phys_addr)) {
|
|
dev_err(dev, "failed to map source buffer address\n");
|
|
ret = false;
|
|
goto err_phys_addr;
|
|
}
|
|
|
|
if (alignment && !IS_ALIGNED(orig_phys_addr, alignment)) {
|
|
phys_addr = PTR_ALIGN(orig_phys_addr, alignment);
|
|
offset = phys_addr - orig_phys_addr;
|
|
addr = orig_addr + offset;
|
|
} else {
|
|
phys_addr = orig_phys_addr;
|
|
addr = orig_addr;
|
|
}
|
|
|
|
crc32 = crc32_le(~0, addr, size);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_CHECKSUM,
|
|
crc32);
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_LOWER_SRC_ADDR,
|
|
lower_32_bits(phys_addr));
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_UPPER_SRC_ADDR,
|
|
upper_32_bits(phys_addr));
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, size);
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_FLAGS, flags);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
|
|
COMMAND_READ);
|
|
|
|
wait_for_completion(&test->irq_raised);
|
|
|
|
reg = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_STATUS);
|
|
if (reg & STATUS_READ_SUCCESS)
|
|
ret = true;
|
|
|
|
dma_unmap_single(dev, orig_phys_addr, size + alignment,
|
|
DMA_TO_DEVICE);
|
|
|
|
err_phys_addr:
|
|
kfree(orig_addr);
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static bool pci_endpoint_test_read(struct pci_endpoint_test *test,
|
|
unsigned long arg)
|
|
{
|
|
struct pci_endpoint_test_xfer_param param;
|
|
bool ret = false;
|
|
u32 flags = 0;
|
|
bool use_dma;
|
|
size_t size;
|
|
void *addr;
|
|
dma_addr_t phys_addr;
|
|
struct pci_dev *pdev = test->pdev;
|
|
struct device *dev = &pdev->dev;
|
|
void *orig_addr;
|
|
dma_addr_t orig_phys_addr;
|
|
size_t offset;
|
|
size_t alignment = test->alignment;
|
|
int irq_type = test->irq_type;
|
|
u32 crc32;
|
|
int err;
|
|
|
|
err = copy_from_user(¶m, (void __user *)arg, sizeof(param));
|
|
if (err) {
|
|
dev_err(dev, "Failed to get transfer param\n");
|
|
return false;
|
|
}
|
|
|
|
err = pci_endpoint_test_validate_xfer_params(dev, ¶m, alignment);
|
|
if (err)
|
|
return false;
|
|
|
|
size = param.size;
|
|
|
|
use_dma = !!(param.flags & PCITEST_FLAGS_USE_DMA);
|
|
if (use_dma)
|
|
flags |= FLAG_USE_DMA;
|
|
|
|
if (irq_type < IRQ_TYPE_LEGACY || irq_type > IRQ_TYPE_MSIX) {
|
|
dev_err(dev, "Invalid IRQ type option\n");
|
|
goto err;
|
|
}
|
|
|
|
orig_addr = kzalloc(size + alignment, GFP_KERNEL);
|
|
if (!orig_addr) {
|
|
dev_err(dev, "Failed to allocate destination address\n");
|
|
ret = false;
|
|
goto err;
|
|
}
|
|
|
|
orig_phys_addr = dma_map_single(dev, orig_addr, size + alignment,
|
|
DMA_FROM_DEVICE);
|
|
if (dma_mapping_error(dev, orig_phys_addr)) {
|
|
dev_err(dev, "failed to map source buffer address\n");
|
|
ret = false;
|
|
goto err_phys_addr;
|
|
}
|
|
|
|
if (alignment && !IS_ALIGNED(orig_phys_addr, alignment)) {
|
|
phys_addr = PTR_ALIGN(orig_phys_addr, alignment);
|
|
offset = phys_addr - orig_phys_addr;
|
|
addr = orig_addr + offset;
|
|
} else {
|
|
phys_addr = orig_phys_addr;
|
|
addr = orig_addr;
|
|
}
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_LOWER_DST_ADDR,
|
|
lower_32_bits(phys_addr));
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_UPPER_DST_ADDR,
|
|
upper_32_bits(phys_addr));
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, size);
|
|
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_FLAGS, flags);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1);
|
|
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
|
|
COMMAND_WRITE);
|
|
|
|
wait_for_completion(&test->irq_raised);
|
|
|
|
dma_unmap_single(dev, orig_phys_addr, size + alignment,
|
|
DMA_FROM_DEVICE);
|
|
|
|
crc32 = crc32_le(~0, addr, size);
|
|
if (crc32 == pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_CHECKSUM))
|
|
ret = true;
|
|
|
|
err_phys_addr:
|
|
kfree(orig_addr);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static bool pci_endpoint_test_clear_irq(struct pci_endpoint_test *test)
|
|
{
|
|
pci_endpoint_test_release_irq(test);
|
|
pci_endpoint_test_free_irq_vectors(test);
|
|
return true;
|
|
}
|
|
|
|
static bool pci_endpoint_test_set_irq(struct pci_endpoint_test *test,
|
|
int req_irq_type)
|
|
{
|
|
struct pci_dev *pdev = test->pdev;
|
|
struct device *dev = &pdev->dev;
|
|
|
|
if (req_irq_type < IRQ_TYPE_LEGACY || req_irq_type > IRQ_TYPE_MSIX) {
|
|
dev_err(dev, "Invalid IRQ type option\n");
|
|
return false;
|
|
}
|
|
|
|
if (test->irq_type == req_irq_type)
|
|
return true;
|
|
|
|
pci_endpoint_test_release_irq(test);
|
|
pci_endpoint_test_free_irq_vectors(test);
|
|
|
|
if (!pci_endpoint_test_alloc_irq_vectors(test, req_irq_type))
|
|
goto err;
|
|
|
|
if (!pci_endpoint_test_request_irq(test))
|
|
goto err;
|
|
|
|
return true;
|
|
|
|
err:
|
|
pci_endpoint_test_free_irq_vectors(test);
|
|
return false;
|
|
}
|
|
|
|
static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int ret = -EINVAL;
|
|
enum pci_barno bar;
|
|
struct pci_endpoint_test *test = to_endpoint_test(file->private_data);
|
|
struct pci_dev *pdev = test->pdev;
|
|
|
|
mutex_lock(&test->mutex);
|
|
switch (cmd) {
|
|
case PCITEST_BAR:
|
|
bar = arg;
|
|
if (bar > BAR_5)
|
|
goto ret;
|
|
if (is_am654_pci_dev(pdev) && bar == BAR_0)
|
|
goto ret;
|
|
ret = pci_endpoint_test_bar(test, bar);
|
|
break;
|
|
case PCITEST_LEGACY_IRQ:
|
|
ret = pci_endpoint_test_legacy_irq(test);
|
|
break;
|
|
case PCITEST_MSI:
|
|
case PCITEST_MSIX:
|
|
ret = pci_endpoint_test_msi_irq(test, arg, cmd == PCITEST_MSIX);
|
|
break;
|
|
case PCITEST_WRITE:
|
|
ret = pci_endpoint_test_write(test, arg);
|
|
break;
|
|
case PCITEST_READ:
|
|
ret = pci_endpoint_test_read(test, arg);
|
|
break;
|
|
case PCITEST_COPY:
|
|
ret = pci_endpoint_test_copy(test, arg);
|
|
break;
|
|
case PCITEST_SET_IRQTYPE:
|
|
ret = pci_endpoint_test_set_irq(test, arg);
|
|
break;
|
|
case PCITEST_GET_IRQTYPE:
|
|
ret = irq_type;
|
|
break;
|
|
case PCITEST_CLEAR_IRQ:
|
|
ret = pci_endpoint_test_clear_irq(test);
|
|
break;
|
|
}
|
|
|
|
ret:
|
|
mutex_unlock(&test->mutex);
|
|
return ret;
|
|
}
|
|
|
|
static const struct file_operations pci_endpoint_test_fops = {
|
|
.owner = THIS_MODULE,
|
|
.unlocked_ioctl = pci_endpoint_test_ioctl,
|
|
};
|
|
|
|
static int pci_endpoint_test_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *ent)
|
|
{
|
|
int err;
|
|
int id;
|
|
char name[24];
|
|
enum pci_barno bar;
|
|
void __iomem *base;
|
|
struct device *dev = &pdev->dev;
|
|
struct pci_endpoint_test *test;
|
|
struct pci_endpoint_test_data *data;
|
|
enum pci_barno test_reg_bar = BAR_0;
|
|
struct miscdevice *misc_device;
|
|
|
|
if (pci_is_bridge(pdev))
|
|
return -ENODEV;
|
|
|
|
test = devm_kzalloc(dev, sizeof(*test), GFP_KERNEL);
|
|
if (!test)
|
|
return -ENOMEM;
|
|
|
|
test->test_reg_bar = 0;
|
|
test->alignment = 0;
|
|
test->pdev = pdev;
|
|
test->irq_type = IRQ_TYPE_UNDEFINED;
|
|
|
|
if (no_msi)
|
|
irq_type = IRQ_TYPE_LEGACY;
|
|
|
|
data = (struct pci_endpoint_test_data *)ent->driver_data;
|
|
if (data) {
|
|
test_reg_bar = data->test_reg_bar;
|
|
test->test_reg_bar = test_reg_bar;
|
|
test->alignment = data->alignment;
|
|
irq_type = data->irq_type;
|
|
}
|
|
|
|
init_completion(&test->irq_raised);
|
|
mutex_init(&test->mutex);
|
|
|
|
if ((dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(48)) != 0) &&
|
|
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)) != 0) {
|
|
dev_err(dev, "Cannot set DMA mask\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = pci_enable_device(pdev);
|
|
if (err) {
|
|
dev_err(dev, "Cannot enable PCI device\n");
|
|
return err;
|
|
}
|
|
|
|
err = pci_request_regions(pdev, DRV_MODULE_NAME);
|
|
if (err) {
|
|
dev_err(dev, "Cannot obtain PCI resources\n");
|
|
goto err_disable_pdev;
|
|
}
|
|
|
|
pci_set_master(pdev);
|
|
|
|
if (!pci_endpoint_test_alloc_irq_vectors(test, irq_type)) {
|
|
err = -EINVAL;
|
|
goto err_disable_irq;
|
|
}
|
|
|
|
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
|
|
if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) {
|
|
base = pci_ioremap_bar(pdev, bar);
|
|
if (!base) {
|
|
dev_err(dev, "Failed to read BAR%d\n", bar);
|
|
WARN_ON(bar == test_reg_bar);
|
|
}
|
|
test->bar[bar] = base;
|
|
}
|
|
}
|
|
|
|
test->base = test->bar[test_reg_bar];
|
|
if (!test->base) {
|
|
err = -ENOMEM;
|
|
dev_err(dev, "Cannot perform PCI test without BAR%d\n",
|
|
test_reg_bar);
|
|
goto err_iounmap;
|
|
}
|
|
|
|
pci_set_drvdata(pdev, test);
|
|
|
|
id = ida_simple_get(&pci_endpoint_test_ida, 0, 0, GFP_KERNEL);
|
|
if (id < 0) {
|
|
err = id;
|
|
dev_err(dev, "Unable to get id\n");
|
|
goto err_iounmap;
|
|
}
|
|
|
|
snprintf(name, sizeof(name), DRV_MODULE_NAME ".%d", id);
|
|
test->name = kstrdup(name, GFP_KERNEL);
|
|
if (!test->name) {
|
|
err = -ENOMEM;
|
|
goto err_ida_remove;
|
|
}
|
|
|
|
if (!pci_endpoint_test_request_irq(test)) {
|
|
err = -EINVAL;
|
|
goto err_kfree_test_name;
|
|
}
|
|
|
|
misc_device = &test->miscdev;
|
|
misc_device->minor = MISC_DYNAMIC_MINOR;
|
|
misc_device->name = kstrdup(name, GFP_KERNEL);
|
|
if (!misc_device->name) {
|
|
err = -ENOMEM;
|
|
goto err_release_irq;
|
|
}
|
|
misc_device->parent = &pdev->dev;
|
|
misc_device->fops = &pci_endpoint_test_fops;
|
|
|
|
err = misc_register(misc_device);
|
|
if (err) {
|
|
dev_err(dev, "Failed to register device\n");
|
|
goto err_kfree_name;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_kfree_name:
|
|
kfree(misc_device->name);
|
|
|
|
err_release_irq:
|
|
pci_endpoint_test_release_irq(test);
|
|
|
|
err_kfree_test_name:
|
|
kfree(test->name);
|
|
|
|
err_ida_remove:
|
|
ida_simple_remove(&pci_endpoint_test_ida, id);
|
|
|
|
err_iounmap:
|
|
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
|
|
if (test->bar[bar])
|
|
pci_iounmap(pdev, test->bar[bar]);
|
|
}
|
|
|
|
err_disable_irq:
|
|
pci_endpoint_test_free_irq_vectors(test);
|
|
pci_release_regions(pdev);
|
|
|
|
err_disable_pdev:
|
|
pci_disable_device(pdev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void pci_endpoint_test_remove(struct pci_dev *pdev)
|
|
{
|
|
int id;
|
|
enum pci_barno bar;
|
|
struct pci_endpoint_test *test = pci_get_drvdata(pdev);
|
|
struct miscdevice *misc_device = &test->miscdev;
|
|
|
|
if (sscanf(misc_device->name, DRV_MODULE_NAME ".%d", &id) != 1)
|
|
return;
|
|
if (id < 0)
|
|
return;
|
|
|
|
misc_deregister(&test->miscdev);
|
|
kfree(misc_device->name);
|
|
kfree(test->name);
|
|
ida_simple_remove(&pci_endpoint_test_ida, id);
|
|
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
|
|
if (test->bar[bar])
|
|
pci_iounmap(pdev, test->bar[bar]);
|
|
}
|
|
|
|
pci_endpoint_test_release_irq(test);
|
|
pci_endpoint_test_free_irq_vectors(test);
|
|
|
|
pci_release_regions(pdev);
|
|
pci_disable_device(pdev);
|
|
}
|
|
|
|
static const struct pci_endpoint_test_data default_data = {
|
|
.test_reg_bar = BAR_0,
|
|
.alignment = SZ_4K,
|
|
.irq_type = IRQ_TYPE_MSI,
|
|
};
|
|
|
|
static const struct pci_endpoint_test_data am654_data = {
|
|
.test_reg_bar = BAR_2,
|
|
.alignment = SZ_64K,
|
|
.irq_type = IRQ_TYPE_MSI,
|
|
};
|
|
|
|
static const struct pci_endpoint_test_data j721e_data = {
|
|
.alignment = 256,
|
|
.irq_type = IRQ_TYPE_MSI,
|
|
};
|
|
|
|
static const struct pci_device_id pci_endpoint_test_tbl[] = {
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_DRA74x),
|
|
.driver_data = (kernel_ulong_t)&default_data,
|
|
},
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_DRA72x),
|
|
.driver_data = (kernel_ulong_t)&default_data,
|
|
},
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, 0x81c0),
|
|
.driver_data = (kernel_ulong_t)&default_data,
|
|
},
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, PCI_DEVICE_ID_LS1088A),
|
|
.driver_data = (kernel_ulong_t)&default_data,
|
|
},
|
|
{ PCI_DEVICE_DATA(SYNOPSYS, EDDA, NULL) },
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_AM654),
|
|
.driver_data = (kernel_ulong_t)&am654_data
|
|
},
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_RENESAS, PCI_DEVICE_ID_RENESAS_R8A774A1),},
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_RENESAS, PCI_DEVICE_ID_RENESAS_R8A774B1),},
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_RENESAS, PCI_DEVICE_ID_RENESAS_R8A774C0),},
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_RENESAS, PCI_DEVICE_ID_RENESAS_R8A774E1),},
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_J721E),
|
|
.driver_data = (kernel_ulong_t)&j721e_data,
|
|
},
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_J7200),
|
|
.driver_data = (kernel_ulong_t)&j721e_data,
|
|
},
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_AM64),
|
|
.driver_data = (kernel_ulong_t)&j721e_data,
|
|
},
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, pci_endpoint_test_tbl);
|
|
|
|
static struct pci_driver pci_endpoint_test_driver = {
|
|
.name = DRV_MODULE_NAME,
|
|
.id_table = pci_endpoint_test_tbl,
|
|
.probe = pci_endpoint_test_probe,
|
|
.remove = pci_endpoint_test_remove,
|
|
.sriov_configure = pci_sriov_configure_simple,
|
|
};
|
|
module_pci_driver(pci_endpoint_test_driver);
|
|
|
|
MODULE_DESCRIPTION("PCI ENDPOINT TEST HOST DRIVER");
|
|
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
|
MODULE_LICENSE("GPL v2");
|