From 526180408b815aa7b96fd48bd23cdd33ef04e38e Mon Sep 17 00:00:00 2001 From: Kelvin Cao Date: Mon, 10 Dec 2018 17:12:20 +0800 Subject: switchtec: Remove immediate status check after submitting MRPC command After submitting a Firmware Download MRPC command, Switchtec firmware will delay Management EP BAR MemRd TLP responses by more than 10ms. This is a firmware limitation. Delayed MemRd completions are a problem for systems with a low Completion Timeout (CTO). The current driver checks the MRPC status immediately after submitting an MRPC command, which results in a delayed MemRd completion that may cause a Completion Timeout. Remove the immediate status check and rely on the check after receiving an interrupt or timing out. This is only a software workaround to the READ issue and a proper fix of this should be done in firmware. Fixes: 080b47def5e5 ("MicroSemi Switchtec management interface driver") Signed-off-by: Kelvin Cao Signed-off-by: Wesley Sheng Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe --- drivers/pci/switch/switchtec.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/switch/switchtec.c b/drivers/pci/switch/switchtec.c index 54a8b30dda38..d2bca2dd9889 100644 --- a/drivers/pci/switch/switchtec.c +++ b/drivers/pci/switch/switchtec.c @@ -134,10 +134,6 @@ static void mrpc_cmd_submit(struct switchtec_dev *stdev) stuser->data, stuser->data_len); iowrite32(stuser->cmd, &stdev->mmio_mrpc->cmd); - stuser->status = ioread32(&stdev->mmio_mrpc->status); - if (stuser->status != SWITCHTEC_MRPC_STATUS_INPROGRESS) - mrpc_complete_cmd(stdev); - schedule_delayed_work(&stdev->mrpc_timeout, msecs_to_jiffies(500)); } -- cgit v1.2.3 From aff614c6339c1ee2a74ea0afc491d6e30d0bad61 Mon Sep 17 00:00:00 2001 From: Boris Glimcher Date: Mon, 10 Dec 2018 17:12:21 +0800 Subject: switchtec: Set DMA coherent mask Switchtec hardware supports 64-bit DMA, so set the correct DMA mask. This allows the CMA to allocate larger buffers for memory windows. Signed-off-by: Boris Glimcher Signed-off-by: Wesley Sheng Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe --- drivers/pci/switch/switchtec.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/switch/switchtec.c b/drivers/pci/switch/switchtec.c index d2bca2dd9889..480107ecb2ab 100644 --- a/drivers/pci/switch/switchtec.c +++ b/drivers/pci/switch/switchtec.c @@ -1237,6 +1237,10 @@ static int switchtec_init_pci(struct switchtec_dev *stdev, if (rc) return rc; + rc = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64)); + if (rc) + return rc; + pci_set_master(pdev); stdev->mmio = pcim_iomap_table(pdev)[0]; -- cgit v1.2.3 From e4a7dca5de625018b29417ecc39dc5037d9a5a36 Mon Sep 17 00:00:00 2001 From: Joey Zhang Date: Mon, 10 Dec 2018 17:12:22 +0800 Subject: switchtec: Fix SWITCHTEC_IOCTL_EVENT_IDX_ALL flags overwrite In the ioctl_event_ctl() SWITCHTEC_IOCTL_EVENT_IDX_ALL case, we call event_ctl() several times with the same "ctl" struct. Each call clobbers ctl.flags, which leads to the problem that we may not actually enable or disable all events as the user requested. Preserve the event flag value with a temporary variable. Fixes: 52eabba5bcdb ("switchtec: Add IOCTLs to the Switchtec driver") Signed-off-by: Joey Zhang Signed-off-by: Wesley Sheng [bhelgaas: changelog] Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe --- drivers/pci/switch/switchtec.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/switch/switchtec.c b/drivers/pci/switch/switchtec.c index 480107ecb2ab..a90867099ff8 100644 --- a/drivers/pci/switch/switchtec.c +++ b/drivers/pci/switch/switchtec.c @@ -796,6 +796,7 @@ static int ioctl_event_ctl(struct switchtec_dev *stdev, { int ret; int nr_idxs; + unsigned int event_flags; struct switchtec_ioctl_event_ctl ctl; if (copy_from_user(&ctl, uctl, sizeof(ctl))) @@ -817,7 +818,9 @@ static int ioctl_event_ctl(struct switchtec_dev *stdev, else return -EINVAL; + event_flags = ctl.flags; for (ctl.index = 0; ctl.index < nr_idxs; ctl.index++) { + ctl.flags = event_flags; ret = event_ctl(stdev, &ctl); if (ret < 0) return ret; -- cgit v1.2.3 From 52d8db8e0cd7c28316514568fe5df0cfd4fa2075 Mon Sep 17 00:00:00 2001 From: Kelvin Cao Date: Mon, 10 Dec 2018 17:12:23 +0800 Subject: switchtec: Improve MRPC efficiency by enabling write combining The MRPC Input buffer is mostly memory without any side effects, so we can improve the access time by enabling write combining on this region of the BAR. In a few places, we still need to flush the WC buffer. To do this, we simply read from the Outbound Doorbell register because reads to this register are processed by low latency hardware. Signed-off-by: Kelvin Cao Signed-off-by: Wesley Sheng Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe --- drivers/pci/switch/switchtec.c | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/switch/switchtec.c b/drivers/pci/switch/switchtec.c index a90867099ff8..0b8862b50ad2 100644 --- a/drivers/pci/switch/switchtec.c +++ b/drivers/pci/switch/switchtec.c @@ -113,6 +113,19 @@ static void stuser_set_state(struct switchtec_user *stuser, static void mrpc_complete_cmd(struct switchtec_dev *stdev); +static void flush_wc_buf(struct switchtec_dev *stdev) +{ + struct ntb_dbmsg_regs __iomem *mmio_dbmsg; + + /* + * odb (outbound doorbell) register is processed by low latency + * hardware and w/o side effect + */ + mmio_dbmsg = (void __iomem *)stdev->mmio_ntb + + SWITCHTEC_NTB_REG_DBMSG_OFFSET; + ioread32(&mmio_dbmsg->odb); +} + static void mrpc_cmd_submit(struct switchtec_dev *stdev) { /* requires the mrpc_mutex to already be held when called */ @@ -132,6 +145,7 @@ static void mrpc_cmd_submit(struct switchtec_dev *stdev) stdev->mrpc_busy = 1; memcpy_toio(&stdev->mmio_mrpc->input_data, stuser->data, stuser->data_len); + flush_wc_buf(stdev); iowrite32(stuser->cmd, &stdev->mmio_mrpc->cmd); schedule_delayed_work(&stdev->mrpc_timeout, @@ -1231,23 +1245,38 @@ static int switchtec_init_pci(struct switchtec_dev *stdev, struct pci_dev *pdev) { int rc; + void __iomem *map; + unsigned long res_start, res_len; rc = pcim_enable_device(pdev); if (rc) return rc; - rc = pcim_iomap_regions(pdev, 0x1, KBUILD_MODNAME); - if (rc) - return rc; - rc = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64)); if (rc) return rc; pci_set_master(pdev); - stdev->mmio = pcim_iomap_table(pdev)[0]; - stdev->mmio_mrpc = stdev->mmio + SWITCHTEC_GAS_MRPC_OFFSET; + res_start = pci_resource_start(pdev, 0); + res_len = pci_resource_len(pdev, 0); + + if (!devm_request_mem_region(&pdev->dev, res_start, + res_len, KBUILD_MODNAME)) + return -EBUSY; + + stdev->mmio_mrpc = devm_ioremap_wc(&pdev->dev, res_start, + SWITCHTEC_GAS_TOP_CFG_OFFSET); + if (!stdev->mmio_mrpc) + return -ENOMEM; + + map = devm_ioremap(&pdev->dev, + res_start + SWITCHTEC_GAS_TOP_CFG_OFFSET, + res_len - SWITCHTEC_GAS_TOP_CFG_OFFSET); + if (!map) + return -ENOMEM; + + stdev->mmio = map - SWITCHTEC_GAS_TOP_CFG_OFFSET; stdev->mmio_sw_event = stdev->mmio + SWITCHTEC_GAS_SW_EVENT_OFFSET; stdev->mmio_sys_info = stdev->mmio + SWITCHTEC_GAS_SYS_INFO_OFFSET; stdev->mmio_flash_info = stdev->mmio + SWITCHTEC_GAS_FLASH_INFO_OFFSET; -- cgit v1.2.3 From f7eb7b8a4f72b0d9dea69b09f58185ffab97fd35 Mon Sep 17 00:00:00 2001 From: Wesley Sheng Date: Mon, 10 Dec 2018 17:12:24 +0800 Subject: switchtec: Add MRPC DMA mode support MRPC normal mode requires the host to read the MRPC command status and output data from BAR. This results in high latency responses from the Memory Read TLP and potential Completion Timeout (CTO). Add support for MRPC DMA mode, including related macro definitions and data structures and code to: * Retrieve MRPC DMA mode version from adapter firmware * Allocate DMA buffer, register ISR, and enable DMA during init * Check MRPC execution status and get execution results from DMA buffer * Release DMA buffer and disable DMA function when unloading module MRPC DMA mode is a new feature of firmware, and the driver will fall back to MRPC normal mode if there is no support in the legacy firmware. Add a module parameter, "use_dma_mrpc", to select between MRPC DMA mode and MRPC normal mode. Since the driver automatically detects DMA support in the firmware, this parameter is just for debugging and testing. Include so that readq/writeq is replaced by two readl/writel on systems that do not support it. Signed-off-by: Wesley Sheng [bhelgaas: changelog, simplify dma_ver check] Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe --- drivers/pci/switch/switchtec.c | 108 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 98 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/switch/switchtec.c b/drivers/pci/switch/switchtec.c index 0b8862b50ad2..6c5536d3d42a 100644 --- a/drivers/pci/switch/switchtec.c +++ b/drivers/pci/switch/switchtec.c @@ -13,7 +13,7 @@ #include #include #include - +#include #include MODULE_DESCRIPTION("Microsemi Switchtec(tm) PCIe Management Driver"); @@ -25,6 +25,11 @@ static int max_devices = 16; module_param(max_devices, int, 0644); MODULE_PARM_DESC(max_devices, "max number of switchtec device instances"); +static bool use_dma_mrpc = 1; +module_param(use_dma_mrpc, bool, 0644); +MODULE_PARM_DESC(use_dma_mrpc, + "Enable the use of the DMA MRPC feature"); + static dev_t switchtec_devt; static DEFINE_IDA(switchtec_minor_ida); @@ -141,6 +146,11 @@ static void mrpc_cmd_submit(struct switchtec_dev *stdev) stuser = list_entry(stdev->mrpc_queue.next, struct switchtec_user, list); + if (stdev->dma_mrpc) { + stdev->dma_mrpc->status = SWITCHTEC_MRPC_STATUS_INPROGRESS; + memset(stdev->dma_mrpc->data, 0xFF, SWITCHTEC_MRPC_PAYLOAD_SIZE); + } + stuser_set_state(stuser, MRPC_RUNNING); stdev->mrpc_busy = 1; memcpy_toio(&stdev->mmio_mrpc->input_data, @@ -180,7 +190,11 @@ static void mrpc_complete_cmd(struct switchtec_dev *stdev) stuser = list_entry(stdev->mrpc_queue.next, struct switchtec_user, list); - stuser->status = ioread32(&stdev->mmio_mrpc->status); + if (stdev->dma_mrpc) + stuser->status = stdev->dma_mrpc->status; + else + stuser->status = ioread32(&stdev->mmio_mrpc->status); + if (stuser->status == SWITCHTEC_MRPC_STATUS_INPROGRESS) return; @@ -190,13 +204,19 @@ static void mrpc_complete_cmd(struct switchtec_dev *stdev) if (stuser->status != SWITCHTEC_MRPC_STATUS_DONE) goto out; - stuser->return_code = ioread32(&stdev->mmio_mrpc->ret_value); + if (stdev->dma_mrpc) + stuser->return_code = stdev->dma_mrpc->rtn_code; + else + stuser->return_code = ioread32(&stdev->mmio_mrpc->ret_value); if (stuser->return_code != 0) goto out; - memcpy_fromio(stuser->data, &stdev->mmio_mrpc->output_data, - stuser->read_len); - + if (stdev->dma_mrpc) + memcpy(stuser->data, &stdev->dma_mrpc->data, + stuser->read_len); + else + memcpy_fromio(stuser->data, &stdev->mmio_mrpc->output_data, + stuser->read_len); out: complete_all(&stuser->comp); list_del_init(&stuser->list); @@ -231,7 +251,10 @@ static void mrpc_timeout_work(struct work_struct *work) mutex_lock(&stdev->mrpc_mutex); - status = ioread32(&stdev->mmio_mrpc->status); + if (stdev->dma_mrpc) + status = stdev->dma_mrpc->status; + else + status = ioread32(&stdev->mmio_mrpc->status); if (status == SWITCHTEC_MRPC_STATUS_INPROGRESS) { schedule_delayed_work(&stdev->mrpc_timeout, msecs_to_jiffies(500)); @@ -239,7 +262,6 @@ static void mrpc_timeout_work(struct work_struct *work) } mrpc_complete_cmd(stdev); - out: mutex_unlock(&stdev->mrpc_mutex); } @@ -1030,10 +1052,24 @@ static void enable_link_state_events(struct switchtec_dev *stdev) } } +static void enable_dma_mrpc(struct switchtec_dev *stdev) +{ + writeq(stdev->dma_mrpc_dma_addr, &stdev->mmio_mrpc->dma_addr); + flush_wc_buf(stdev); + iowrite32(SWITCHTEC_DMA_MRPC_EN, &stdev->mmio_mrpc->dma_en); +} + static void stdev_release(struct device *dev) { struct switchtec_dev *stdev = to_stdev(dev); + if (stdev->dma_mrpc) { + iowrite32(0, &stdev->mmio_mrpc->dma_en); + flush_wc_buf(stdev); + writeq(0, &stdev->mmio_mrpc->dma_addr); + dma_free_coherent(&stdev->pdev->dev, sizeof(*stdev->dma_mrpc), + stdev->dma_mrpc, stdev->dma_mrpc_dma_addr); + } kfree(stdev); } @@ -1189,10 +1225,27 @@ static irqreturn_t switchtec_event_isr(int irq, void *dev) return ret; } + +static irqreturn_t switchtec_dma_mrpc_isr(int irq, void *dev) +{ + struct switchtec_dev *stdev = dev; + irqreturn_t ret = IRQ_NONE; + + iowrite32(SWITCHTEC_EVENT_CLEAR | + SWITCHTEC_EVENT_EN_IRQ, + &stdev->mmio_part_cfg->mrpc_comp_hdr); + schedule_work(&stdev->mrpc_work); + + ret = IRQ_HANDLED; + return ret; +} + static int switchtec_init_isr(struct switchtec_dev *stdev) { int nvecs; int event_irq; + int dma_mrpc_irq; + int rc; nvecs = pci_alloc_irq_vectors(stdev->pdev, 1, 4, PCI_IRQ_MSIX | PCI_IRQ_MSI); @@ -1207,9 +1260,29 @@ static int switchtec_init_isr(struct switchtec_dev *stdev) if (event_irq < 0) return event_irq; - return devm_request_irq(&stdev->pdev->dev, event_irq, + rc = devm_request_irq(&stdev->pdev->dev, event_irq, switchtec_event_isr, 0, KBUILD_MODNAME, stdev); + + if (rc) + return rc; + + if (!stdev->dma_mrpc) + return rc; + + dma_mrpc_irq = ioread32(&stdev->mmio_mrpc->dma_vector); + if (dma_mrpc_irq < 0 || dma_mrpc_irq >= nvecs) + return -EFAULT; + + dma_mrpc_irq = pci_irq_vector(stdev->pdev, dma_mrpc_irq); + if (dma_mrpc_irq < 0) + return dma_mrpc_irq; + + rc = devm_request_irq(&stdev->pdev->dev, dma_mrpc_irq, + switchtec_dma_mrpc_isr, 0, + KBUILD_MODNAME, stdev); + + return rc; } static void init_pff(struct switchtec_dev *stdev) @@ -1294,6 +1367,19 @@ static int switchtec_init_pci(struct switchtec_dev *stdev, pci_set_drvdata(pdev, stdev); + if (!use_dma_mrpc) + return 0; + + if (ioread32(&stdev->mmio_mrpc->dma_ver) == 0) + return 0; + + stdev->dma_mrpc = dma_zalloc_coherent(&stdev->pdev->dev, + sizeof(*stdev->dma_mrpc), + &stdev->dma_mrpc_dma_addr, + GFP_KERNEL); + if (stdev->dma_mrpc == NULL) + return -ENOMEM; + return 0; } @@ -1325,6 +1411,9 @@ static int switchtec_pci_probe(struct pci_dev *pdev, &stdev->mmio_part_cfg->mrpc_comp_hdr); enable_link_state_events(stdev); + if (stdev->dma_mrpc) + enable_dma_mrpc(stdev); + rc = cdev_device_add(&stdev->cdev, &stdev->dev); if (rc) goto err_devadd; @@ -1350,7 +1439,6 @@ static void switchtec_pci_remove(struct pci_dev *pdev) cdev_device_del(&stdev->cdev, &stdev->dev); ida_simple_remove(&switchtec_minor_ida, MINOR(stdev->dev.devt)); dev_info(&stdev->dev, "unregistered.\n"); - stdev_kill(stdev); put_device(&stdev->dev); } -- cgit v1.2.3