Thank you for your review, Alan Stern,
> > With the patch above, the kernel successfully detects USB mass storage
> > devices. However, it also causes another crash:
> >
> > ------------[ cut here ]------------
> > WARNING: CPU: 0 PID: 15 at ./include/linux/dma-mapping.h:530
> > hcd_buffer_free+0x130/0x200
> > Modules linked in:
> > CPU: 0 PID: 15 Comm: kworker/0:1 Not tainted 4.14.0+ #719
> > Workqueue: usb_hub_wq hub_event
> > Stack : 81f3b5b8 80402608 00000003 0000000b 000000f6 805611c4 81ccc280
> > 804f398c
> > 802ee5e0 804fd520 00000009 00000212 00000001 10058400 81c09a38
> > 00000001
> > 00000000 00000000 00000000 00000003 0000000a 00000002 00000001
> > 00000000
> > 00000001 00003368 81c09878 81c09840 00000000 00000000 802ee5e0
> > 804fd520
> > 00000009 00000212 00000001 fffffff3 00000002 00000000 00000000
> > 805b0000
> > ...
> > Call Trace:
> > [<80021104>] show_stack+0x74/0x104
> > [<80036310>] __warn+0x114/0x11c
> > [<800363ac>] warn_slowpath_null+0x1c/0x30
> > [<802ee5e0>] hcd_buffer_free+0x130/0x200
> > [<802e56ec>] usb_hcd_unmap_urb_for_dma+0x160/0x16c
> > [<802e576c>] __usb_hcd_giveback_urb+0x54/0xf0
> > [<802f5d64>] finish_urb+0x98/0x138
> > [<802f716c>] ohci_work+0x14c/0x494
> > [<802f9878>] ohci_irq+0x13c/0x2e4
> > [<802e4e0c>] usb_hcd_irq+0x2c/0x44
> > [<8006366c>] __handle_irq_event_percpu+0x98/0x158
> > [<8006374c>] handle_irq_event_percpu+0x20/0x64
> > [<800637cc>] handle_irq_event+0x3c/0x70
> > [<80067024>] handle_level_irq+0xe4/0x120
> > [<80062d70>] generic_handle_irq+0x28/0x38
> > [<80409b58>] do_IRQ+0x18/0x24
> > ---[ end trace fa1f0201799de649 ]---
>
> This is caused by a deficiency in the DMA core: dma_free_coherent()
> wants interrupts to be enabled when it is called. I'm not sure how the
> other host controller drivers cope with this.
The OHCI drivers
drivers/usb/host/ohci-sm501.c and
drivers/usb/host/ohci-tmio.c
appear to be very similar to my driver. They both use HCD_LOCAL_MEM and
dma_declare_coherent_memory(). Curiously, though, they don't seem to disable
scatter-gather, or handle DMA in any special way, which I think is a bit odd.
I wonder how they could possibly avoid these two DMA crashes? (Especially
given the explicit WARN_ON_ONCE note in commit 4307a28eb012 "USB: EHCI: fix
NULL pointer dererence in HCDs that use HCD_LOCAL_MEM".)
One notable difference is that my driver does
hcd->regs = (void __iomem *)res->start;
where the other drivers use ioremap such that
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
in the OHCI HCD probe function. (The hardware address logic is somewhat
involved and partially undocumented so there might be a good reason for this
difference in my driver.)
> Be aware that your driver should utilize ohci_init_driver(), like
> several of the other platform-specific OHCI drivers do. Unless there's
> some very good reason, new drivers should never use the old interface.
Agreed, please find updated patch with the new interface. (I suppose the
changes to drivers/usb/host/ohci-hcd.c eventually will have to be clarified
and moved elsewhere too.)
Fredrik
diff --git a/arch/mips/ps2/setup.c b/arch/mips/ps2/setup.c
index d9957711283e..d9caceacfaac 100644
--- a/arch/mips/ps2/setup.c
+++ b/arch/mips/ps2/setup.c
@@ -45,6 +45,29 @@ const char *get_system_type(void)
return "Sony PlayStation 2";
}
+#define IOP_REG_BASE 0xbf801460
+#define IOP_USB_BASE (IOP_REG_BASE + 0x1a0)
+
+static struct resource ps2_usb_ohci_resources[] = {
+ [0] = {
+ .start = IOP_USB_BASE,
+ .end = IOP_USB_BASE + 0xff,
+ .flags = IORESOURCE_MEM, /* 256 byte HCCA */
+ },
+ [1] = {
+ .start = IRQ_SBUS_USB,
+ .end = IRQ_SBUS_USB,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct platform_device ps2_usb_ohci_device = {
+ .name = "ps2_ohci",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(ps2_usb_ohci_resources),
+ .resource = ps2_usb_ohci_resources,
+};
+
void __init plat_mem_setup(void)
{
ps2_reset_init();
@@ -68,6 +91,10 @@ void __init plat_mem_setup(void)
set_io_port_base(CKSEG1);
}
+static struct platform_device *ps2_platform_devices[] __initdata = {
+ &ps2_usb_ohci_device,
+};
+
static int __init ps2_board_setup(void)
{
ps2dma_init();
@@ -85,6 +112,8 @@ static int __init ps2_board_setup(void)
if (load_module_firmware("ps2/dev9_dma.irx", 0) < 0)
pr_err("loading ps2/dev9_dma.irx failed\n");
+ platform_add_devices(ps2_platform_devices,
ARRAY_SIZE(ps2_platform_devices));
+
return 0;
}
arch_initcall(ps2_board_setup);
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index fa5692dec832..0f74d3420066 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -571,6 +571,13 @@ config USB_OHCI_EXYNOS
help
Enable support for the Samsung Exynos SOC's on-chip OHCI controller.
+config USB_OHCI_HCD_PS2
+ tristate "OHCI support for the Sony PlayStation 2"
+ depends on SONY_PS2
+ default y
+ help
+ Enable support for the Sony PlayStation 2 OHCI controller.
+
config USB_CNS3XXX_OHCI
bool "Cavium CNS3XXX OHCI Module (DEPRECATED)"
depends on ARCH_CNS3XXX
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index 4ab2689c8952..2c6546fb2de6 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_USB_OHCI_HCD_S3C2410) += ohci-s3c2410.o
obj-$(CONFIG_USB_OHCI_HCD_LPC32XX) += ohci-nxp.o
obj-$(CONFIG_USB_OHCI_HCD_PXA27X) += ohci-pxa27x.o
obj-$(CONFIG_USB_OHCI_HCD_DAVINCI) += ohci-da8xx.o
+obj-$(CONFIG_USB_OHCI_HCD_PS2) += ohci-ps2.o
obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o
obj-$(CONFIG_USB_FHCI_HCD) += fhci.o
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 44924824fa41..485ded7469ac 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -590,6 +590,11 @@ static int ohci_run (struct ohci_hcd *ohci)
}
udelay (1);
}
+#ifdef CONFIG_SONY_PS2
+ /* Enable USB. Leave PS2DEV enabled. */
+ outl(inl(0x1F801570) | 0x08000080, 0x1F801570);
+ outl(1, 0x1F801680);
+#endif
/* now we're in the SUSPEND state ... must go OPERATIONAL
* within 2msec else HC enters RESUME
@@ -875,6 +880,10 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
/* We only care about interrupts that are enabled */
ints &= ohci_readl(ohci, ®s->intrenable);
+#ifdef CONFIG_SONY_PS2
+ ohci_writel(ohci, OHCI_INTR_MIE, ®s->intrdisable);
+#endif
+
/* interrupt for some other device? */
if (ints == 0 || unlikely(ohci->rh_state == OHCI_RH_HALTED))
return IRQ_NOTMINE;
diff --git a/drivers/usb/host/ohci-ps2.c b/drivers/usb/host/ohci-ps2.c
new file mode 100755
index 000000000000..c160a1649500
--- /dev/null
+++ b/drivers/usb/host/ohci-ps2.c
@@ -0,0 +1,183 @@
+/*
+ * USB OHCI HCD (Host Controller Driver) for the PlayStation 2.
+ *
+ * Copyright (C) 2010 Jürgen Urban
+ * Copyright (C) 2017 Fredrik Noring
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include <asm/mach-ps2/sifdefs.h>
+
+#include "ohci.h"
+
+#define DRIVER_DESC "OHCI PS2 driver"
+
+#define DMA_BUFFER_SIZE (256 * 1024) /* Size allocated from IOP heap. */
+
+static dma_addr_t iop_dma_addr = 0;
+
+static const char hcd_name[] = "ohci-ps2";
+static struct hc_driver __read_mostly ohci_ps2_hc_driver;
+
+static int ohci_ps2_reset(struct usb_hcd *hcd)
+{
+ const int ret = ohci_setup(hcd);
+
+ /*
+ * Native scatter-gather support needs to be disabled since
+ * HCD_LOCAL_MEM and dma_declare_coherent_memory() are used
+ * to enforce the host controller's local memory utilization,
+ * otherwise hcd_alloc_coherent() in map_urb_for_dma() is
+ * called with urb->transfer_buffer == NULL, that triggers a
+ * NULL pointer dereference.
+ */
+ hcd->self.sg_tablesize = 0;
+
+ return ret;
+}
+
+static int iopheap_alloc_coherent(struct device *dev, size_t size, int flags)
+{
+ if (iop_dma_addr != 0)
+ return 0;
+
+ iop_dma_addr = ps2sif_allociopheap(size);
+ if (iop_dma_addr == 0) {
+ dev_err(dev, "ps2sif_allociopheap failed\n");
+ return -ENOMEM;
+ }
+
+ if (dma_declare_coherent_memory(dev, ps2sif_bustophys(iop_dma_addr),
+ iop_dma_addr, size, flags)) {
+ dev_err(dev, "dma_declare_coherent_memory failed\n");
+ ps2sif_freeiopheap(iop_dma_addr);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void iopheap_free_coherent(struct device *dev)
+{
+ if (iop_dma_addr == 0)
+ return;
+
+ dma_release_declared_memory(dev);
+ ps2sif_freeiopheap(iop_dma_addr);
+ iop_dma_addr = 0;
+}
+
+static int ohci_hcd_ps2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct usb_hcd *hcd;
+ int irq;
+ int ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "platform_get_irq failed\n");
+ return irq;
+ }
+
+ hcd = usb_create_hcd(&ohci_ps2_hc_driver, dev, hcd_name);
+ if (hcd == NULL)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ dev_err(dev, "platform_get_resource failed\n");
+ ret = -ENOENT;
+ goto err;
+ }
+ hcd->rsrc_start = res->start;
+ hcd->rsrc_len = resource_size(res);
+ hcd->regs = (void __iomem *)res->start;
+ if (IS_ERR(hcd->regs)) {
+ ret = PTR_ERR(hcd->regs);
+ goto err;
+ }
+
+ ret = iopheap_alloc_coherent(dev, DMA_BUFFER_SIZE,
DMA_MEMORY_EXCLUSIVE);
+ if (ret != 0)
+ goto err;
+
+ ret = usb_add_hcd(hcd, irq, 0);
+ if (ret != 0)
+ goto err_free;
+
+ ret = device_wakeup_enable(hcd->self.controller);
+ if (ret != 0)
+ goto err_remove_hcd;
+
+ return ret;
+
+err_remove_hcd:
+ usb_remove_hcd(hcd);
+err_free:
+ iopheap_free_coherent(dev);
+err:
+ usb_put_hcd(hcd);
+
+ return ret;
+}
+
+static int ohci_hcd_ps2_remove(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(pdev);
+
+ usb_remove_hcd(hcd);
+ usb_put_hcd(hcd);
+
+ iopheap_free_coherent(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver ohci_hcd_ps2_driver = {
+ .probe = ohci_hcd_ps2_probe,
+ .remove = ohci_hcd_ps2_remove,
+ .shutdown = usb_hcd_platform_shutdown,
+ .driver = {
+ .name = "ps2_ohci",
+ },
+};
+
+static const struct ohci_driver_overrides ps2_overrides __initconst = {
+ .reset = ohci_ps2_reset,
+ .product_desc = "PS2 OHCI",
+};
+
+static int __init ohci_ps2_init(void)
+{
+ if (usb_disabled())
+ return -ENODEV;
+
+ pr_info("%s: " DRIVER_DESC "\n", hcd_name);
+
+ ohci_init_driver(&ohci_ps2_hc_driver, &ps2_overrides);
+ ohci_ps2_hc_driver.flags |= HCD_LOCAL_MEM;
+
+ return platform_driver_register(&ohci_hcd_ps2_driver);
+}
+module_init(ohci_ps2_init);
+
+static void __exit ohci_ps2_cleanup(void)
+{
+ platform_driver_unregister(&ohci_hcd_ps2_driver);
+}
+module_exit(ohci_ps2_cleanup);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ps2_ohci");
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html