root/sys/powerpc/ps3/ps3bus.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (C) 2010 Nathan Whitehorn
 * Copyright (C) 2011 glevand (geoffrey.levand@mail.ru)
 * 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.
 */

#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/clock.h>
#include <sys/cpu.h>
#include <sys/lock.h>
#include <sys/mutex.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/resource.h>

#include "ps3bus.h"
#include "ps3-hvcall.h"
#include "iommu_if.h"
#include "clock_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 rman *ps3bus_get_rman(device_t bus, int type, u_int flags);
static struct resource *ps3bus_alloc_resource(device_t bus, device_t child,
                    int type, int rid, rman_res_t start, rman_res_t end,
                    rman_res_t count, u_int flags);
static int      ps3bus_map_resource(device_t bus, device_t child,
                    struct resource *r, struct resource_map_request *argsp,
                    struct resource_map *map);
static int      ps3bus_unmap_resource(device_t bus, device_t child,
                    struct resource *r, struct resource_map *map);
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_addr_t boundary, void *cookie);
static int      ps3_iommu_unmap(device_t dev, bus_dma_segment_t *segs,
                    int nsegs, void *cookie);
static int      ps3_gettime(device_t dev, struct timespec *ts);
static int      ps3_settime(device_t dev, struct timespec *ts);

struct ps3bus_devinfo {
        int bus;
        int dev;
        uint64_t bustype;
        uint64_t devtype;
        int busidx;
        int devidx;

        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,
};

enum ps3bus_reg_type {
        OHCI_REG = 3,
        EHCI_REG = 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_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_get_rman,         ps3bus_get_rman),
        DEVMETHOD(bus_alloc_resource,   ps3bus_alloc_resource),
        DEVMETHOD(bus_adjust_resource,  bus_generic_rman_adjust_resource),
        DEVMETHOD(bus_activate_resource, bus_generic_rman_activate_resource),
        DEVMETHOD(bus_deactivate_resource, bus_generic_rman_deactivate_resource),
        DEVMETHOD(bus_map_resource,     ps3bus_map_resource),
        DEVMETHOD(bus_unmap_resource,   ps3bus_unmap_resource),
        DEVMETHOD(bus_release_resource, bus_generic_rman_release_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),

        /* Clock interface */
        DEVMETHOD(clock_gettime,        ps3_gettime),
        DEVMETHOD(clock_settime,        ps3_settime),

        DEVMETHOD_END
};

struct ps3bus_softc {
        struct rman sc_mem_rman;
        struct rman sc_intr_rman;
        struct mem_region *regions;
        int rcount;
};

static driver_t ps3bus_driver = {
        "ps3bus",
        ps3bus_methods,
        sizeof(struct ps3bus_softc)
};

DRIVER_MODULE(ps3bus, nexus, ps3bus_driver, 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", DEVICE_UNIT_ANY) == 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 void
ps3bus_resources_init_by_type(struct rman *rm, int bus_index, int dev_index,
    uint64_t irq_type, uint64_t reg_type, 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;

                if (_irq_type != irq_type)
                        continue;

                lv1_construct_io_irq_outlet(irq, &outlet);
                lv1_connect_irq_plug_ext(ppe, thread, outlet, outlet,
                    0);
                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;

                if (_reg_type != reg_type)
                        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("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);
                        break;
                }

                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";
        sc->sc_intr_rman.rm_type = RMAN_ARRAY;
        sc->sc_intr_rman.rm_descr = "PS3Bus Interrupts";
        rman_init(&sc->sc_mem_rman);
        rman_init(&sc->sc_intr_rman);
        rman_manage_region(&sc->sc_intr_rman, 0, ~0);

        /* Get memory regions for DMA */
        mem_regions(&sc->regions, &sc->rcount, NULL, NULL);

        /*
         * 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;
                        
                        switch (devtype) {
                        case PS3_DEVTYPE_USB:
                                /* USB device has OHCI and EHCI USB host controllers */

                                lv1_open_device(bus, dev, 0);

                                /* OHCI host controller */

                                dinfo = malloc(sizeof(*dinfo), M_PS3BUS,
                                    M_WAITOK | M_ZERO);

                                dinfo->bus = bus;
                                dinfo->dev = dev;
                                dinfo->bustype = bustype;
                                dinfo->devtype = devtype;
                                dinfo->busidx = bus_index;
                                dinfo->devidx = dev_index;

                                ps3bus_resources_init_by_type(&sc->sc_mem_rman, bus_index,
                                    dev_index, OHCI_IRQ, OHCI_REG, dinfo);

                                cdev = device_add_child(self, "ohci", DEVICE_UNIT_ANY);
                                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);

                                /* EHCI host controller */

                                dinfo = malloc(sizeof(*dinfo), M_PS3BUS,
                                    M_WAITOK | M_ZERO);

                                dinfo->bus = bus;
                                dinfo->dev = dev;
                                dinfo->bustype = bustype;
                                dinfo->devtype = devtype;
                                dinfo->busidx = bus_index;
                                dinfo->devidx = dev_index;

                                ps3bus_resources_init_by_type(&sc->sc_mem_rman, bus_index,
                                    dev_index, EHCI_IRQ, EHCI_REG, dinfo);

                                cdev = device_add_child(self, "ehci", DEVICE_UNIT_ANY);
                                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);
                                break;
                        default:
                                dinfo = malloc(sizeof(*dinfo), M_PS3BUS,
                                    M_WAITOK | M_ZERO);

                                dinfo->bus = bus;
                                dinfo->dev = dev;
                                dinfo->bustype = bustype;
                                dinfo->devtype = devtype;
                                dinfo->busidx = bus_index;
                                dinfo->devidx = dev_index;

                                if (dinfo->bustype == PS3_BUSTYPE_SYSBUS ||
                                    dinfo->bustype == PS3_BUSTYPE_STORAGE)
                                        lv1_open_device(bus, dev, 0);

                                ps3bus_resources_init(&sc->sc_mem_rman, bus_index,
                                    dev_index, dinfo);

                                cdev = device_add_child(self, NULL, DEVICE_UNIT_ANY);
                                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);
                        }
                }
        }

        clock_register(self, 1000);

        bus_attach_children(self);
        return (0);
}

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, "%#jx");
        retval += resource_list_print_type(&dinfo->resources, "irq",
            SYS_RES_IRQ, "%jd");

        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;
        case PS3BUS_IVAR_BUSIDX:
                *result = dinfo->busidx;
                break;
        case PS3BUS_IVAR_DEVIDX:
                *result = dinfo->devidx;
                break;
        default:
                return (EINVAL);
        }

        return (0);
}

static struct rman *
ps3bus_get_rman(device_t bus, int type, u_int flags)
{
        struct  ps3bus_softc *sc;

        sc = device_get_softc(bus);
        switch (type) {
        case SYS_RES_MEMORY:
                return (&sc->sc_mem_rman);
        case SYS_RES_IRQ:
                return (&sc->sc_intr_rman);
        default:
                return (NULL);
        }
}

static struct resource *
ps3bus_alloc_resource(device_t bus, device_t child, int type, int rid,
    rman_res_t start, rman_res_t end, rman_res_t count, u_int flags)
{
        struct  ps3bus_devinfo *dinfo;
        rman_res_t      adjstart, adjend, adjcount;
        struct  resource_list_entry *rle;

        dinfo = device_get_ivars(child);

        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;
                break;
        case SYS_RES_IRQ:
                rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ,
                    rid);
                adjstart = rle->start;
                adjcount = ulmax(count, rle->count);
                adjend = ulmax(rle->end, rle->start + adjcount - 1);
                break;
        default:
                device_printf(bus, "unknown resource request from %s\n",
                              device_get_nameunit(child));
                return (NULL);
        }

        return (bus_generic_rman_alloc_resource(bus, child, type, rid, adjstart,
            adjend, adjcount, flags));
}

static int
ps3bus_map_resource(device_t bus, device_t child, struct resource *r,
    struct resource_map_request *argsp, struct resource_map *map)
{
        struct resource_map_request args;
        rman_res_t length, start;
        int error;

        /* Resources must be active to be mapped. */
        if (!(rman_get_flags(r) & RF_ACTIVE))
                return (ENXIO);

        /* Mappings are only supported on memory resources. */
        switch (rman_get_type(r)) {
        case SYS_RES_MEMORY:
                break;
        default:
                return (EINVAL);
        }

        resource_init_map_request(&args);
        error = resource_validate_map_request(r, argsp, &args, &start, &length);
        if (error)
                return (error);

        if (bootverbose)
                printf("ps3 mapdev: start %jx, len %jd\n", start, length);

        map->r_vaddr = pmap_mapdev_attr(start, length, args.memattr);
        if (map->r_vaddr == NULL)
                return (ENOMEM);
        map->r_bustag = &bs_be_tag;
        map->r_bushandle = (vm_offset_t)map->r_vaddr;
        map->r_size = length;
        return (0);
}

static int
ps3bus_unmap_resource(device_t bus, device_t child, struct resource *r,
    struct resource_map *map)
{

        switch (rman_get_type(r)) {
        case SYS_RES_MEMORY:
                pmap_unmapdev(map->r_vaddr, map->r_size);
                return (0);
        default:
                return (EINVAL);
        }
}

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, pagesize;

        if (dinfo->bustype != PS3_BUSTYPE_SYSBUS &&
            dinfo->bustype != PS3_BUSTYPE_STORAGE)
                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 */

        pagesize = 24; /* log_2(16 MB) */
        if (dinfo->bustype == PS3_BUSTYPE_STORAGE)
                pagesize = 12; /* 4 KB */

        for (i = 0; i < sc->rcount; i++) {
                err = lv1_allocate_device_dma_region(dinfo->bus, dinfo->dev,
                    sc->regions[i].mr_size, pagesize, 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);

        /*
         * Note: storage devices have IOMMU mappings set up by the hypervisor,
         * but use physical, non-translated addresses. The above IOMMU
         * initialization is necessary for the hypervisor to be able to set up
         * the mappings, but actual DMA mappings should not use the IOMMU
         * routines.
         */
        if (dinfo->bustype != PS3_BUSTYPE_STORAGE)
                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_addr_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);
}

#define Y2K 946684800

static int
ps3_gettime(device_t dev, struct timespec *ts)
{
        uint64_t rtc, tb;
        int result;

        result = lv1_get_rtc(&rtc, &tb);
        if (result)
                return (result);

        ts->tv_sec = rtc + Y2K;
        ts->tv_nsec = 0;
        return (0);
}

static int
ps3_settime(device_t dev, struct timespec *ts)
{
        return (-1);
}