summaryrefslogtreecommitdiffstats
path: root/sys/powerpc/ps3/ps3bus.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/powerpc/ps3/ps3bus.c')
-rw-r--r--sys/powerpc/ps3/ps3bus.c561
1 files changed, 561 insertions, 0 deletions
diff --git a/sys/powerpc/ps3/ps3bus.c b/sys/powerpc/ps3/ps3bus.c
new file mode 100644
index 0000000..11dbba5
--- /dev/null
+++ b/sys/powerpc/ps3/ps3bus.c
@@ -0,0 +1,561 @@
+/*-
+ * Copyright (C) 2010 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/malloc.h>
+#include <sys/bus.h>
+#include <sys/cpu.h>
+#include <sys/resource.h>
+#include <sys/rman.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <machine/bus.h>
+#include <machine/platform.h>
+#include <machine/pmap.h>
+#include <machine/resource.h>
+
+#include "ps3bus.h"
+#include "ps3-hvcall.h"
+#include "iommu_if.h"
+
+static void ps3bus_identify(driver_t *, device_t);
+static int ps3bus_probe(device_t);
+static int ps3bus_attach(device_t);
+static int ps3bus_print_child(device_t dev, device_t child);
+static int ps3bus_read_ivar(device_t bus, device_t child, int which,
+ uintptr_t *result);
+static struct resource *ps3bus_alloc_resource(device_t bus, device_t child,
+ int type, int *rid, u_long start, u_long end,
+ u_long count, u_int flags);
+static int ps3bus_activate_resource(device_t bus, device_t child, int type,
+ int rid, struct resource *res);
+static bus_dma_tag_t ps3bus_get_dma_tag(device_t dev, device_t child);
+static int ps3_iommu_map(device_t dev, bus_dma_segment_t *segs, int *nsegs, bus_addr_t min, bus_addr_t max, bus_size_t alignment,
+ bus_size_t boundary, void *cookie);
+static int ps3_iommu_unmap(device_t dev, bus_dma_segment_t *segs,
+ int nsegs, void *cookie);
+
+struct ps3bus_devinfo {
+ int bus;
+ int dev;
+ uint64_t bustype;
+ uint64_t devtype;
+
+ struct resource_list resources;
+ bus_dma_tag_t dma_tag;
+
+ struct mtx iommu_mtx;
+ bus_addr_t dma_base[4];
+};
+
+static MALLOC_DEFINE(M_PS3BUS, "ps3bus", "PS3 system bus device information");
+
+enum ps3bus_irq_type {
+ SB_IRQ = 2,
+ OHCI_IRQ = 3,
+ EHCI_IRQ = 4,
+};
+
+static device_method_t ps3bus_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_identify, ps3bus_identify),
+ DEVMETHOD(device_probe, ps3bus_probe),
+ DEVMETHOD(device_attach, ps3bus_attach),
+
+ /* Bus interface */
+ DEVMETHOD(bus_driver_added, bus_generic_driver_added),
+ DEVMETHOD(bus_add_child, bus_generic_add_child),
+ DEVMETHOD(bus_get_dma_tag, ps3bus_get_dma_tag),
+ DEVMETHOD(bus_print_child, ps3bus_print_child),
+ DEVMETHOD(bus_read_ivar, ps3bus_read_ivar),
+ DEVMETHOD(bus_alloc_resource, ps3bus_alloc_resource),
+ DEVMETHOD(bus_activate_resource, ps3bus_activate_resource),
+ DEVMETHOD(bus_setup_intr, bus_generic_setup_intr),
+ DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr),
+
+ /* IOMMU interface */
+ DEVMETHOD(iommu_map, ps3_iommu_map),
+ DEVMETHOD(iommu_unmap, ps3_iommu_unmap),
+
+ { 0, 0 }
+};
+
+struct ps3bus_softc {
+ struct rman sc_mem_rman;
+ struct mem_region *regions;
+ int rcount;
+};
+
+static driver_t ps3bus_driver = {
+ "ps3bus",
+ ps3bus_methods,
+ sizeof(struct ps3bus_softc)
+};
+
+static devclass_t ps3bus_devclass;
+
+DRIVER_MODULE(ps3bus, nexus, ps3bus_driver, ps3bus_devclass, 0, 0);
+
+static void
+ps3bus_identify(driver_t *driver, device_t parent)
+{
+ if (strcmp(installed_platform(), "ps3") != 0)
+ return;
+
+ if (device_find_child(parent, "ps3bus", -1) == NULL)
+ BUS_ADD_CHILD(parent, 0, "ps3bus", 0);
+}
+
+static int
+ps3bus_probe(device_t dev)
+{
+ /* Do not attach to any OF nodes that may be present */
+
+ device_set_desc(dev, "Playstation 3 System Bus");
+
+ return (BUS_PROBE_NOWILDCARD);
+}
+
+static void
+ps3bus_resources_init(struct rman *rm, int bus_index, int dev_index,
+ struct ps3bus_devinfo *dinfo)
+{
+ uint64_t irq_type, irq, outlet;
+ uint64_t reg_type, paddr, len;
+ uint64_t ppe, junk;
+ int i, result;
+ int thread;
+
+ resource_list_init(&dinfo->resources);
+
+ lv1_get_logical_ppe_id(&ppe);
+ thread = 32 - fls(mfctrl());
+
+ /* Scan for interrupts */
+ for (i = 0; i < 10; i++) {
+ result = lv1_get_repository_node_value(PS3_LPAR_ID_PME,
+ (lv1_repository_string("bus") >> 32) | bus_index,
+ lv1_repository_string("dev") | dev_index,
+ lv1_repository_string("intr") | i, 0, &irq_type, &irq);
+
+ if (result != 0)
+ break;
+
+ switch (irq_type) {
+ case SB_IRQ:
+ lv1_construct_event_receive_port(&outlet);
+ lv1_connect_irq_plug_ext(ppe, thread, outlet, outlet,
+ 0);
+ lv1_connect_interrupt_event_receive_port(dinfo->bus,
+ dinfo->dev, outlet, irq);
+ break;
+ case OHCI_IRQ:
+ case EHCI_IRQ:
+ lv1_construct_io_irq_outlet(irq, &outlet);
+ lv1_connect_irq_plug_ext(ppe, thread, outlet, outlet,
+ 0);
+ break;
+ default:
+ printf("Unknown IRQ type %ld for device %d.%d\n",
+ irq_type, dinfo->bus, dinfo->dev);
+ break;
+ }
+
+ resource_list_add(&dinfo->resources, SYS_RES_IRQ, i,
+ outlet, outlet, 1);
+ }
+
+ /* Scan for registers */
+ for (i = 0; i < 10; i++) {
+ result = lv1_get_repository_node_value(PS3_LPAR_ID_PME,
+ (lv1_repository_string("bus") >> 32) | bus_index,
+ lv1_repository_string("dev") | dev_index,
+ lv1_repository_string("reg") | i,
+ lv1_repository_string("type"), &reg_type, &junk);
+
+ if (result != 0)
+ break;
+
+ result = lv1_get_repository_node_value(PS3_LPAR_ID_PME,
+ (lv1_repository_string("bus") >> 32) | bus_index,
+ lv1_repository_string("dev") | dev_index,
+ lv1_repository_string("reg") | i,
+ lv1_repository_string("data"), &paddr, &len);
+
+ result = lv1_map_device_mmio_region(dinfo->bus, dinfo->dev,
+ paddr, len, 12 /* log_2(4 KB) */, &paddr);
+
+ if (result != 0) {
+ printf("Mapping registers failed for device "
+ "%d.%d (%ld.%ld): %d\n", dinfo->bus, dinfo->dev,
+ dinfo->bustype, dinfo->devtype, result);
+ continue;
+ }
+
+ rman_manage_region(rm, paddr, paddr + len - 1);
+ resource_list_add(&dinfo->resources, SYS_RES_MEMORY, i,
+ paddr, paddr + len, len);
+ }
+}
+
+static int
+ps3bus_attach(device_t self)
+{
+ struct ps3bus_softc *sc;
+ struct ps3bus_devinfo *dinfo;
+ int bus_index, dev_index, result;
+ uint64_t bustype, bus, devs;
+ uint64_t dev, devtype;
+ uint64_t junk;
+ device_t cdev;
+
+ sc = device_get_softc(self);
+ sc->sc_mem_rman.rm_type = RMAN_ARRAY;
+ sc->sc_mem_rman.rm_descr = "PS3Bus Memory Mapped I/O";
+ rman_init(&sc->sc_mem_rman);
+
+ /* Get memory regions for DMA */
+ mem_regions(&sc->regions, &sc->rcount, &sc->regions, &sc->rcount);
+
+ /*
+ * Probe all the PS3's buses.
+ */
+
+ for (bus_index = 0; bus_index < 5; bus_index++) {
+ result = lv1_get_repository_node_value(PS3_LPAR_ID_PME,
+ (lv1_repository_string("bus") >> 32) | bus_index,
+ lv1_repository_string("type"), 0, 0, &bustype, &junk);
+
+ if (result != 0)
+ continue;
+
+ result = lv1_get_repository_node_value(PS3_LPAR_ID_PME,
+ (lv1_repository_string("bus") >> 32) | bus_index,
+ lv1_repository_string("id"), 0, 0, &bus, &junk);
+
+ if (result != 0)
+ continue;
+
+ result = lv1_get_repository_node_value(PS3_LPAR_ID_PME,
+ (lv1_repository_string("bus") >> 32) | bus_index,
+ lv1_repository_string("num_dev"), 0, 0, &devs, &junk);
+
+ for (dev_index = 0; dev_index < devs; dev_index++) {
+ result = lv1_get_repository_node_value(PS3_LPAR_ID_PME,
+ (lv1_repository_string("bus") >> 32) | bus_index,
+ lv1_repository_string("dev") | dev_index,
+ lv1_repository_string("type"), 0, &devtype, &junk);
+
+ if (result != 0)
+ continue;
+
+ result = lv1_get_repository_node_value(PS3_LPAR_ID_PME,
+ (lv1_repository_string("bus") >> 32) | bus_index,
+ lv1_repository_string("dev") | dev_index,
+ lv1_repository_string("id"), 0, &dev, &junk);
+
+ if (result != 0)
+ continue;
+
+ dinfo = malloc(sizeof(*dinfo), M_PS3BUS,
+ M_WAITOK | M_ZERO);
+
+ dinfo->bus = bus;
+ dinfo->dev = dev;
+ dinfo->bustype = bustype;
+ dinfo->devtype = devtype;
+
+ if (dinfo->bustype == PS3_BUSTYPE_SYSBUS)
+ lv1_open_device(bus, dev, 0);
+
+ ps3bus_resources_init(&sc->sc_mem_rman, bus_index,
+ dev_index, dinfo);
+
+ cdev = device_add_child(self, NULL, -1);
+ if (cdev == NULL) {
+ device_printf(self,
+ "device_add_child failed\n");
+ free(dinfo, M_PS3BUS);
+ continue;
+ }
+
+ mtx_init(&dinfo->iommu_mtx, "iommu", NULL, MTX_DEF);
+ device_set_ivars(cdev, dinfo);
+ }
+ }
+
+ return (bus_generic_attach(self));
+}
+
+static int
+ps3bus_print_child(device_t dev, device_t child)
+{
+ struct ps3bus_devinfo *dinfo = device_get_ivars(child);
+ int retval = 0;
+
+ retval += bus_print_child_header(dev, child);
+ retval += resource_list_print_type(&dinfo->resources, "mem",
+ SYS_RES_MEMORY, "%#lx");
+ retval += resource_list_print_type(&dinfo->resources, "irq",
+ SYS_RES_IRQ, "%ld");
+
+ retval += bus_print_child_footer(dev, child);
+
+ return (retval);
+}
+
+static int
+ps3bus_read_ivar(device_t bus, device_t child, int which, uintptr_t *result)
+{
+ struct ps3bus_devinfo *dinfo = device_get_ivars(child);
+
+ switch (which) {
+ case PS3BUS_IVAR_BUS:
+ *result = dinfo->bus;
+ break;
+ case PS3BUS_IVAR_DEVICE:
+ *result = dinfo->dev;
+ break;
+ case PS3BUS_IVAR_BUSTYPE:
+ *result = dinfo->bustype;
+ break;
+ case PS3BUS_IVAR_DEVTYPE:
+ *result = dinfo->devtype;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
+static struct resource *
+ps3bus_alloc_resource(device_t bus, device_t child, int type, int *rid,
+ u_long start, u_long end, u_long count, u_int flags)
+{
+ struct ps3bus_devinfo *dinfo;
+ struct ps3bus_softc *sc;
+ int needactivate;
+ struct resource *rv;
+ struct rman *rm;
+ u_long adjstart, adjend, adjcount;
+ struct resource_list_entry *rle;
+
+ sc = device_get_softc(bus);
+ dinfo = device_get_ivars(child);
+ needactivate = flags & RF_ACTIVE;
+ flags &= ~RF_ACTIVE;
+
+ switch (type) {
+ case SYS_RES_MEMORY:
+ rle = resource_list_find(&dinfo->resources, SYS_RES_MEMORY,
+ *rid);
+ if (rle == NULL) {
+ device_printf(bus, "no rle for %s memory %d\n",
+ device_get_nameunit(child), *rid);
+ return (NULL);
+ }
+
+ if (start < rle->start)
+ adjstart = rle->start;
+ else if (start > rle->end)
+ adjstart = rle->end;
+ else
+ adjstart = start;
+
+ if (end < rle->start)
+ adjend = rle->start;
+ else if (end > rle->end)
+ adjend = rle->end;
+ else
+ adjend = end;
+
+ adjcount = adjend - adjstart;
+
+ rm = &sc->sc_mem_rman;
+ break;
+ case SYS_RES_IRQ:
+ return (resource_list_alloc(&dinfo->resources, bus, child,
+ type, rid, start, end, count, flags));
+ default:
+ device_printf(bus, "unknown resource request from %s\n",
+ device_get_nameunit(child));
+ return (NULL);
+ }
+
+ rv = rman_reserve_resource(rm, adjstart, adjend, adjcount, flags,
+ child);
+ if (rv == NULL) {
+ device_printf(bus,
+ "failed to reserve resource %#lx - %#lx (%#lx)"
+ " for %s\n", adjstart, adjend, adjcount,
+ device_get_nameunit(child));
+ return (NULL);
+ }
+
+ rman_set_rid(rv, *rid);
+
+ if (needactivate) {
+ if (bus_activate_resource(child, type, *rid, rv) != 0) {
+ device_printf(bus,
+ "failed to activate resource for %s\n",
+ device_get_nameunit(child));
+ rman_release_resource(rv);
+ return (NULL);
+ }
+ }
+
+ return (rv);
+}
+
+static int
+ps3bus_activate_resource(device_t bus, device_t child, int type, int rid,
+ struct resource *res)
+{
+ void *p;
+
+ if (type == SYS_RES_IRQ)
+ return (bus_activate_resource(bus, type, rid, res));
+
+ if (type == SYS_RES_MEMORY) {
+ vm_offset_t start;
+
+ start = (vm_offset_t) rman_get_start(res);
+
+ if (bootverbose)
+ printf("ps3 mapdev: start %zx, len %ld\n", start,
+ rman_get_size(res));
+
+ p = pmap_mapdev(start, (vm_size_t) rman_get_size(res));
+ if (p == NULL)
+ return (ENOMEM);
+ rman_set_virtual(res, p);
+ rman_set_bustag(res, &bs_be_tag);
+ rman_set_bushandle(res, (u_long)p);
+ }
+
+ return (rman_activate_resource(res));
+}
+
+static bus_dma_tag_t
+ps3bus_get_dma_tag(device_t dev, device_t child)
+{
+ struct ps3bus_devinfo *dinfo = device_get_ivars(child);
+ struct ps3bus_softc *sc = device_get_softc(dev);
+ int i, err, flags;
+
+ if (dinfo->bustype != PS3_BUSTYPE_SYSBUS)
+ return (bus_get_dma_tag(dev));
+
+ mtx_lock(&dinfo->iommu_mtx);
+ if (dinfo->dma_tag != NULL) {
+ mtx_unlock(&dinfo->iommu_mtx);
+ return (dinfo->dma_tag);
+ }
+
+ flags = 0; /* 32-bit mode */
+ if (dinfo->bustype == PS3_BUSTYPE_SYSBUS &&
+ dinfo->devtype == PS3_DEVTYPE_USB)
+ flags = 2; /* 8-bit mode */
+
+ for (i = 0; i < sc->rcount; i++) {
+ err = lv1_allocate_device_dma_region(dinfo->bus, dinfo->dev,
+ sc->regions[i].mr_size, 24 /* log_2(16 MB) */, flags,
+ &dinfo->dma_base[i]);
+ if (err != 0) {
+ device_printf(child,
+ "could not allocate DMA region %d: %d\n", i, err);
+ goto fail;
+ }
+
+ err = lv1_map_device_dma_region(dinfo->bus, dinfo->dev,
+ sc->regions[i].mr_start, dinfo->dma_base[i],
+ sc->regions[i].mr_size,
+ 0xf800000000000800UL /* Cell Handbook Figure 7.3.4.1 */);
+ if (err != 0) {
+ device_printf(child,
+ "could not map DMA region %d: %d\n", i, err);
+ goto fail;
+ }
+ }
+
+ err = bus_dma_tag_create(bus_get_dma_tag(dev),
+ 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR,
+ NULL, NULL, BUS_SPACE_MAXSIZE, 0, BUS_SPACE_MAXSIZE,
+ 0, NULL, NULL, &dinfo->dma_tag);
+
+ bus_dma_tag_set_iommu(dinfo->dma_tag, dev, dinfo);
+
+fail:
+ mtx_unlock(&dinfo->iommu_mtx);
+
+ if (err)
+ return (NULL);
+
+ return (dinfo->dma_tag);
+}
+
+static int
+ps3_iommu_map(device_t dev, bus_dma_segment_t *segs, int *nsegs,
+ bus_addr_t min, bus_addr_t max, bus_size_t alignment, bus_size_t boundary,
+ void *cookie)
+{
+ struct ps3bus_devinfo *dinfo = cookie;
+ struct ps3bus_softc *sc = device_get_softc(dev);
+ int i, j;
+
+ for (i = 0; i < *nsegs; i++) {
+ for (j = 0; j < sc->rcount; j++) {
+ if (segs[i].ds_addr >= sc->regions[j].mr_start &&
+ segs[i].ds_addr < sc->regions[j].mr_start +
+ sc->regions[j].mr_size)
+ break;
+ }
+ KASSERT(j < sc->rcount,
+ ("Trying to map address %#lx not in physical memory",
+ segs[i].ds_addr));
+
+ segs[i].ds_addr = dinfo->dma_base[j] +
+ (segs[i].ds_addr - sc->regions[j].mr_start);
+ }
+
+ return (0);
+}
+
+
+static int
+ps3_iommu_unmap(device_t dev, bus_dma_segment_t *segs, int nsegs, void *cookie)
+{
+
+ return (0);
+}
+
OpenPOWER on IntegriCloud