root/usr/src/uts/common/io/usb/hcd/xhci/xhci_event.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2016 Joyent, Inc.
 * Copyright (c) 2019 by Western Digital Corporation
 */

/*
 * Event Ring Management
 *
 * All activity in xHCI is reported to an event ring, which corresponds directly
 * with an interrupt. Whether a command is issued or an I/O is issued to a given
 * device endpoint, it will end up being acknowledged, positively or negatively,
 * on an event ring.
 *
 * Unlike other rings, the OS is a consumer of the event rings, not a producer.
 * For more information on how the ring is used, see xhci_ring.c. For more
 * information generally, see xhci.c.
 *
 * All of the rings are described in the ERST -- Event Ring Segment Table. As we
 * only have a single interrupt and a single event ring, we only write a single
 * entry here.
 */

#include <sys/usb/hcd/xhci/xhci.h>


void
xhci_event_fini(xhci_t *xhcip)
{
        xhci_event_ring_t *xev = &xhcip->xhci_event;
        xhci_ring_free(&xev->xev_ring);
        if (xev->xev_segs != NULL)
                xhci_dma_free(&xev->xev_dma);
        xev->xev_segs = NULL;
}

/*
 * Make sure that if we leave here we either have both the ring and table
 * addresses initialized or neither.
 */
static int
xhci_event_alloc(xhci_t *xhcip, xhci_event_ring_t *xev)
{
        int ret;
        ddi_dma_attr_t attr;
        ddi_device_acc_attr_t acc;

        /*
         * This is allocating the segment table. It doesn't have any particular
         * requirements. Though it could be larger, we can get away with our
         * default data structure attributes unless we add a lot more entries.
         */
        xhci_dma_acc_attr(xhcip, &acc);
        xhci_dma_dma_attr(xhcip, &attr);
        if (!xhci_dma_alloc(xhcip, &xev->xev_dma, &attr, &acc, B_FALSE,
            sizeof (xhci_event_segment_t) * XHCI_EVENT_NSEGS, B_FALSE))
                return (ENOMEM);
        if ((ret = xhci_ring_alloc(xhcip, &xev->xev_ring)) != 0) {
                xhci_dma_free(&xev->xev_dma);
                return (ret);
        }

        xev->xev_segs = (void *)xev->xev_dma.xdb_va;
        return (0);
}

int
xhci_event_init(xhci_t *xhcip)
{
        int ret;
        uint32_t reg;
        xhci_event_ring_t *xev = &xhcip->xhci_event;

        if (xev->xev_segs == NULL) {
                if ((ret = xhci_event_alloc(xhcip, xev)) != 0)
                        return (ret);
        }

        if ((ret = xhci_ring_reset(xhcip, &xev->xev_ring)) != 0) {
                xhci_event_fini(xhcip);
                return (ret);
        }

        bzero(xev->xev_segs, sizeof (xhci_event_segment_t) * XHCI_EVENT_NSEGS);
        xev->xev_segs[0].xes_addr = LE_64(xhci_dma_pa(&xev->xev_ring.xr_dma));
        xev->xev_segs[0].xes_size = LE_16(xev->xev_ring.xr_ntrb);

        reg = xhci_get32(xhcip, XHCI_R_RUN, XHCI_ERSTSZ(0));
        reg &= ~XHCI_ERSTS_MASK;
        reg |= XHCI_ERSTS_SET(XHCI_EVENT_NSEGS);
        xhci_put32(xhcip, XHCI_R_RUN, XHCI_ERSTSZ(0), reg);

        xhci_put64(xhcip, XHCI_R_RUN, XHCI_ERDP(0),
            xhci_dma_pa(&xev->xev_ring.xr_dma));
        xhci_put64(xhcip, XHCI_R_RUN, XHCI_ERSTBA(0),
            xhci_dma_pa(&xev->xev_dma));
        if (xhci_check_regs_acc(xhcip) != DDI_FM_OK) {
                xhci_event_fini(xhcip);
                ddi_fm_service_impact(xhcip->xhci_dip, DDI_SERVICE_LOST);
                return (EIO);
        }

        return (0);
}

static boolean_t
xhci_event_process_psc(xhci_t *xhcip, xhci_trb_t *trb)
{
        uint32_t port;

        if (XHCI_TRB_GET_CODE(LE_32(trb->trb_status)) != XHCI_CODE_SUCCESS) {
                return (B_TRUE);
        }

        port = XHCI_TRB_PORTID(LE_64(trb->trb_addr));
        if (port < 1 || port > xhcip->xhci_caps.xcap_max_ports) {
                /*
                 * At some point we may want to send a DDI_FM_DEVICE_INVAL_STATE
                 * ereport as part of this.
                 */
                return (B_FALSE);
        }

        xhci_root_hub_psc_callback(xhcip);
        return (B_TRUE);
}

boolean_t
xhci_event_process_trb(xhci_t *xhcip, xhci_trb_t *trb)
{
        uint32_t type;

        type = LE_32(trb->trb_flags) & XHCI_TRB_TYPE_MASK;
        switch (type) {
        case XHCI_EVT_PORT_CHANGE:
                if (!xhci_event_process_psc(xhcip, trb))
                        return (B_FALSE);
                break;
        case XHCI_EVT_CMD_COMPLETE:
                if (!xhci_command_event_callback(xhcip, trb))
                        return (B_FALSE);
                break;
        case XHCI_EVT_DOORBELL:
                /*
                 * Because we don't have any VF hardware, this event
                 * should never happen. If it does, that probably means
                 * something bad has happened and we should reset the
                 * device.
                 */
                xhci_error(xhcip, "received xHCI VF interrupt even "
                    "though virtual functions are not supported, "
                    "resetting device");
                xhci_fm_runtime_reset(xhcip);
                return (B_FALSE);
        case XHCI_EVT_XFER:
                if (!xhci_endpoint_transfer_callback(xhcip, trb))
                        return (B_FALSE);
                break;
        /*
         * Ignore other events that come in.
         */
        default:
                break;
        }

        return (B_TRUE);
}

/*
 * Process the event ring, note we're in interrupt context while doing this.
 */
boolean_t
xhci_event_process(xhci_t *xhcip)
{
        int nevents;
        uint64_t addr;
        xhci_ring_t *xrp = &xhcip->xhci_event.xev_ring;

        /*
         * While it may be possible for us to transition to an error state at
         * any time because we are reasonably not holding the xhci_t's lock
         * during the entire interrupt (as it doesn't protect any of the event
         * ring's data), we still do an initial test to ensure that we don't go
         * too far down the path.
         */
        mutex_enter(&xhcip->xhci_lock);
        if (xhcip->xhci_state & XHCI_S_ERROR) {
                mutex_exit(&xhcip->xhci_lock);
                return (B_FALSE);
        }
        mutex_exit(&xhcip->xhci_lock);

        /*
         * We've seen a few cases, particularly when dealing with controllers
         * where BIOS takeover is involved, that an interrupt gets injected into
         * the system before we've actually finished setting things up. If for
         * some reason that happens, and we don't actually have a ring yet,
         * don't try and do anything.
         */
        if (xhcip->xhci_event.xev_segs == NULL)
                return (B_TRUE);

        XHCI_DMA_SYNC(xrp->xr_dma, DDI_DMA_SYNC_FORKERNEL);
        if (xhci_check_dma_handle(xhcip, &xrp->xr_dma) != DDI_FM_OK) {
                xhci_error(xhcip, "encountered fatal FM error trying to "
                    "synchronize event ring: resetting device");
                xhci_fm_runtime_reset(xhcip);
                return (B_FALSE);
        }

        /*
         * Process at most a full ring worth of events.
         */
        for (nevents = 0; nevents < xrp->xr_ntrb; nevents++) {
                xhci_trb_t *trb;

                if ((trb = xhci_ring_event_advance(xrp)) == NULL)
                        break;

                if (!xhci_event_process_trb(xhcip, trb))
                        return (B_FALSE);
        }

        addr = xhci_dma_pa(&xrp->xr_dma) + sizeof (xhci_trb_t) * xrp->xr_tail;
        addr |= XHCI_ERDP_BUSY;
        xhci_put64(xhcip, XHCI_R_RUN, XHCI_ERDP(0), addr);
        if (xhci_check_regs_acc(xhcip) != DDI_FM_OK) {
                xhci_error(xhcip, "failed to write to event ring dequeue "
                    "pointer: encountered fatal FM error, resetting device");
                xhci_fm_runtime_reset(xhcip);
                return (B_FALSE);
        }

        return (B_TRUE);
}