root/usr/src/uts/common/io/usb/hcd/xhci/xhci_polled.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 (c) 2018, Joyent, Inc.
 *
 * Copyright (c) 2019 by Western Digital Corporation
 */

/*
 * This next series of routines is used for USB console polled I/O.
 */

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

#include <sys/cmn_err.h>

static void xhci_polled_panic(xhci_polled_t *xhci_polledp, const char *format,
    ...) __KVPRINTFLIKE(2) __NORETURN;

static void
xhci_polled_panic(xhci_polled_t *xhci_polledp, const char *format, ...)
{
        va_list ap;

        va_start(ap, format);
        vdev_err(xhci_polledp->xhci_polled_xhci->xhci_dip, CE_PANIC, format,
            ap);

        /*
         * We will not reach this call. However the compiler doesn't know
         * that vdev_err(..., CE_PANIC, ...) will not return. So this call
         * convinces it that this function does indeed never return.
         */
        panic(__func__);
}

static void
xhci_polled_set_persistent_error(xhci_polled_t *xhci_polledp, int error)
{
        if (error != USB_SUCCESS &&
            xhci_polledp->xhci_polled_persistent_error == USB_SUCCESS) {
                xhci_polledp->xhci_polled_persistent_error = error;
        }
}

/*
 * xhci_polled_init:
 *
 * Initialize generic information that is needed to provide USB/POLLED
 * support.
 */
static int
xhci_polled_init(usba_pipe_handle_data_t *input_pipe_handle, xhci_t *xhcip,
    usb_console_info_impl_t *console_info)
{
        xhci_pipe_t *xp;
        xhci_endpoint_t *xep;
        xhci_polled_t *xhci_polledp;

        ASSERT(mutex_owned(&xhcip->xhci_lock));

        /*
         * We have already initialized this structure. If the structure
         * has already been initialized, then we don't need to redo it.
         */
        if (console_info->uci_private != NULL)
                return (USB_SUCCESS);

        xp = (xhci_pipe_t *)input_pipe_handle->p_hcd_private;
        if (xp == NULL)
                return (USB_FAILURE);

        /*
         * We only support interrupt (keyboards) and not bulk (serial)
         * endpoints at the moment.
         */
        xep = xp->xp_ep;
        if (xep->xep_type != USB_EP_ATTR_INTR)
                return (USB_NOT_SUPPORTED);

        /* Allocate and initialize a state structure */
        xhci_polledp = kmem_zalloc(sizeof (xhci_polled_t), KM_SLEEP);

        xhci_polledp->xhci_polled_xhci = xhcip;
        xhci_polledp->xhci_polled_input_pipe_handle = input_pipe_handle;
        xhci_polledp->xhci_polled_endpoint = xep;

        console_info->uci_private = (usb_console_info_private_t)xhci_polledp;
        return (USB_SUCCESS);
}

static xhci_endpoint_t *
xhci_polled_get_endpoint(xhci_t *xhcip, xhci_trb_t *trb)
{
        int slot, endpoint;
        xhci_device_t *xd;

        endpoint = XHCI_TRB_GET_EP(LE_32(trb->trb_flags));
        slot = XHCI_TRB_GET_SLOT(LE_32(trb->trb_flags));

        xd = xhci_device_lookup_by_slot(xhcip, slot);
        if (xd == NULL)
                return (NULL);

        /*
         * Endpoint IDs are indexed based on their Device Context Index, which
         * means that we need to subtract one to get the actual ID that we use.
         */
        return (xd->xd_endpoints[endpoint - 1]);
}

static int
xhci_polled_endpoint_transfer(xhci_polled_t *xhci_polledp, xhci_endpoint_t *xep,
    xhci_trb_t *trb, uint_t *num_characters)
{
        xhci_t *xhcip = xhci_polledp->xhci_polled_xhci;
        xhci_device_t *xd = xep->xep_xd;
        uint_t off;
        int code;
        xhci_transfer_t *xt;
        size_t len;
        xhci_transfer_t *rem;
        int sched_err;

        /*
         * This TRB should be part of a transfer. If it's not, then we ignore
         * it. We also check whether or not it's for the first transfer. Because
         * the rings are serviced in order, it should be.
         */
        if ((xt = xhci_endpoint_determine_transfer(xhcip, xep, trb, &off)) ==
            NULL) {
                return (USB_FAILURE);
        }

        code = XHCI_TRB_GET_CODE(LE_32(trb->trb_status));
        if (code != XHCI_CODE_SUCCESS)
                return (USB_FAILURE);

        ASSERT(xep->xep_type == USB_EP_ATTR_INTR);

        if (!xt->xt_data_tohost)
                return (USB_SUCCESS);

        if (xt->xt_short != 0)
                return (USB_CR_DATA_UNDERRUN);

        if (xhci_transfer_sync(xhcip, xt, DDI_DMA_SYNC_FORCPU) != DDI_FM_OK) {
                xhci_polled_panic(xhci_polledp, "failed to process normal "
                    "transfer callback for endpoint %u of device on slot %d "
                    "and port %d: encountered fatal FM error synchronizing "
                    "DMA memory", xhcip, xep->xep_num, xd->xd_slot,
                    xd->xd_port);
        }

        len = xt->xt_buffer.xdb_len;
        if (len > sizeof (xhci_polledp->xhci_polled_buf))
                len = sizeof (xhci_polledp->xhci_polled_buf);
        xhci_transfer_copy(xt, xhci_polledp->xhci_polled_buf, len, B_TRUE);

        *num_characters = (uint_t)len;

        VERIFY(xhci_ring_trb_consumed(&xep->xep_ring, LE_64(trb->trb_addr)));
        rem = list_remove_head(&xep->xep_transfers);
        VERIFY3P(rem, ==, xt);

        xt->xt_short = 0;
        xt->xt_cr = USB_CR_OK;

        /*
         * The call below can fail but there isn't much we can do other
         * than panicing the machine. But that might only re-enter the
         * kernel debugger with now broken keyboard input. So we are
         * simply returning the keyboard input that we have succesfully
         * received because it might enable some progress.
         */
        sched_err = xhci_endpoint_schedule(xhcip, xd, xep, xt, B_TRUE);
        xhci_polled_set_persistent_error(xhci_polledp, sched_err);

        return (USB_SUCCESS);
}

/*
 * Process the event ring
 */
static int
xhci_polled_event_process(xhci_polled_t *xhci_polledp, uint_t *num_characters)
{
        xhci_t *xhcip = xhci_polledp->xhci_polled_xhci;
        xhci_ring_t *xrp = &xhcip->xhci_event.xev_ring;
        uint_t nevents;
        int ret;
        uint64_t addr;

        if (xhcip->xhci_state & XHCI_S_ERROR)
                return (USB_HC_HARDWARE_ERROR);

        VERIFY(xhcip->xhci_event.xev_segs != NULL);

        XHCI_DMA_SYNC(xrp->xr_dma, DDI_DMA_SYNC_FORKERNEL);

        /* Look for any transfer events */
        ret = USB_SUCCESS;
        for (nevents = 0; nevents < xrp->xr_ntrb; nevents++) {
                xhci_trb_t *trb;
                xhci_endpoint_t *xep;
                uint32_t type;

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

                xep = xhci_polled_get_endpoint(xhcip, trb);
                if (xep == NULL) {
                        xhci_polled_set_persistent_error(xhci_polledp,
                            USB_HC_HARDWARE_ERROR);
                        return (USB_HC_HARDWARE_ERROR);
                }
                type = LE_32(trb->trb_flags) & XHCI_TRB_TYPE_MASK;

                if (xep != xhci_polledp->xhci_polled_endpoint ||
                    type != XHCI_EVT_XFER) {
                        /*
                         * We got an event which we are not prepared to
                         * handle here. Call into the normal driver code
                         * which should return here after dispatching a task.
                         */
                        boolean_t processed;

                        mutex_exit(&xhcip->xhci_lock);
                        processed = xhci_event_process_trb(xhcip, trb);
                        mutex_enter(&xhcip->xhci_lock);

                        if (!processed && xhcip->xhci_state & XHCI_S_ERROR)
                                return (USB_HC_HARDWARE_ERROR);
                        continue;
                }

                ret = xhci_polled_endpoint_transfer(xhci_polledp, xep, trb,
                    num_characters);
                if (ret != USB_SUCCESS) {
                        xhci_polled_set_persistent_error(xhci_polledp, ret);
                        break;
                }
        }

        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);

        return (ret);
}

int
xhci_hcdi_console_input_init(usba_pipe_handle_data_t *pipe_handle,
    uchar_t **polled_buf, usb_console_info_impl_t *console_input_info)
{
        xhci_t *xhcip;
        int ret;
        xhci_polled_t *xhci_polledp;

        xhcip = xhci_hcdi_get_xhcip_from_dev(pipe_handle->p_usba_device);

        mutex_enter(&xhcip->xhci_lock);

        ret = xhci_polled_init(pipe_handle, xhcip, console_input_info);
        if (ret != USB_SUCCESS) {
                mutex_exit(&xhcip->xhci_lock);
                return (ret);
        }

        xhci_polledp = (xhci_polled_t *)console_input_info->uci_private;
        *polled_buf = xhci_polledp->xhci_polled_buf;

        mutex_exit(&xhcip->xhci_lock);

        return (ret);
}

int
xhci_hcdi_console_input_fini(usb_console_info_impl_t *console_input_info)
{
        xhci_polled_t *xhci_polledp;

        xhci_polledp = (xhci_polled_t *)console_input_info->uci_private;
        if (xhci_polledp != NULL) {
                kmem_free(xhci_polledp, sizeof (xhci_polled_t));
                console_input_info->uci_private = NULL;
        }

        return (USB_SUCCESS);
}

int
xhci_hcdi_console_input_enter(usb_console_info_impl_t *console_input_info)
{
        xhci_polled_t *xhci_polledp;
        xhci_t *xhcip;
        xhci_endpoint_t *xep;
        uint32_t status;

        xhci_polledp = (xhci_polled_t *)console_input_info->uci_private;
        xhcip = xhci_polledp->xhci_polled_xhci;
        xep = xhci_polledp->xhci_polled_endpoint;

        if (mutex_tryenter(&xhcip->xhci_lock) == 0)
                return (USB_BUSY);

        /*
         * If the controller is already switched over, just return
         */
        if (xhci_polledp->xhci_polled_entry > 0) {
                xhci_polledp->xhci_polled_entry++;
                ASSERT(xep->xep_state & XHCI_ENDPOINT_POLLED);
                mutex_exit(&xhcip->xhci_lock);
                return (USB_SUCCESS);
        }

        /*
         * Check to see if we have a fatal bit set. If this is the case the
         * host controller is not working properly and we don't want to
         * enter the kernel debugger and leave the system unresponsive.
         */
        status = xhci_get32(xhcip, XHCI_R_OPER, XHCI_USBSTS);
        if ((status & (XHCI_STS_HSE | XHCI_STS_SRE | XHCI_STS_HCE)) != 0) {
                mutex_exit(&xhcip->xhci_lock);
                xhci_polled_set_persistent_error(xhci_polledp,
                    USB_HC_HARDWARE_ERROR);
                return (USB_HC_HARDWARE_ERROR);
        }

        /*
         * We only support interrupt (keyboards) and not bulk (serial)
         * endpoints at the moment.
         */
        if (xhci_polledp->xhci_polled_endpoint->xep_type != USB_EP_ATTR_INTR) {
                mutex_exit(&xhcip->xhci_lock);
                return (USB_NOT_SUPPORTED);
        }

        xhci_polledp->xhci_polled_persistent_error = USB_SUCCESS;
        xhci_polledp->xhci_polled_entry++;

        ASSERT(!(xep->xep_state & XHCI_ENDPOINT_POLLED));
        xep->xep_state |= XHCI_ENDPOINT_POLLED;

        mutex_exit(&xhcip->xhci_lock);
        return (USB_SUCCESS);
}

int
xhci_hcdi_console_read(usb_console_info_impl_t *console_input_info,
    uint_t *num_characters)
{
        xhci_polled_t *xhci_polledp;
        xhci_t *xhcip;
        uint32_t status, iman;
        int ret;

        *num_characters = 0;

        xhci_polledp = (xhci_polled_t *)console_input_info->uci_private;
        VERIFY(xhci_polledp != NULL);

        if (xhci_polledp->xhci_polled_persistent_error != USB_SUCCESS)
                return (xhci_polledp->xhci_polled_persistent_error);

        xhcip = xhci_polledp->xhci_polled_xhci;
        if (mutex_tryenter(&xhcip->xhci_lock) == 0)
                return (USB_BUSY);

        /*
         * Before we read the interrupt management register, check to see if we
         * have a fatal bit set. As we cannot reset the host controller while
         * the kernel debugger is running we give up.
         */
        status = xhci_get32(xhcip, XHCI_R_OPER, XHCI_USBSTS);
        if ((status & (XHCI_STS_HSE | XHCI_STS_SRE | XHCI_STS_HCE)) != 0) {
                xhci_polled_panic(xhci_polledp, "found fatal error bit in "
                    "status register, value: 0x%x", xhcip, status);
        }

        iman = xhci_get32(xhcip, XHCI_R_RUN, XHCI_IMAN(0));

        ret = xhci_polled_event_process(xhci_polledp, num_characters);

        if (ret == USB_SUCCESS)
                xhci_put32(xhcip, XHCI_R_RUN, XHCI_IMAN(0), iman);

        mutex_exit(&xhcip->xhci_lock);
        return (ret);
}

int
xhci_hcdi_console_input_exit(usb_console_info_impl_t *console_input_info)
{
        xhci_polled_t *xhci_polledp;
        xhci_t *xhcip;
        xhci_endpoint_t *xep;

        xhci_polledp = (xhci_polled_t *)console_input_info->uci_private;
        VERIFY(xhci_polledp != NULL);

        xhcip = xhci_polledp->xhci_polled_xhci;
        mutex_enter(&xhcip->xhci_lock);

        xep = xhci_polledp->xhci_polled_endpoint;
        ASSERT(xep->xep_state & XHCI_ENDPOINT_POLLED);

        VERIFY(xhci_polledp->xhci_polled_entry > 0);
        xhci_polledp->xhci_polled_entry--;
        if (xhci_polledp->xhci_polled_entry > 0) {
                mutex_exit(&xhcip->xhci_lock);
                return (USB_SUCCESS);
        }

        xep->xep_state &= ~XHCI_ENDPOINT_POLLED;

        /*
         * Initiate a reset of the host controller if we encountered problems
         * or ignored events while in polled mode. The reset will not be
         * performed in this context and instead be scheduled to a
         * task queue. It will therefore only happen once the kernel is
         * fully up and running again which should be perfectly safe.
         */
        if (xhci_polledp->xhci_polled_persistent_error != USB_SUCCESS) {
                xhci_fm_runtime_reset(xhcip);
        }

        mutex_exit(&xhcip->xhci_lock);
        return (USB_SUCCESS);
}

int
xhci_hcdi_console_output_init(usba_pipe_handle_data_t *pipe_handle,
    usb_console_info_impl_t *console_output_info)
{
        return (USB_NOT_SUPPORTED);
}

int
xhci_hcdi_console_output_fini(usb_console_info_impl_t *console_output_info)
{
        return (USB_NOT_SUPPORTED);
}

int
xhci_hcdi_console_output_enter(usb_console_info_impl_t *console_output_info)
{
        return (USB_NOT_SUPPORTED);
}

int
xhci_hcdi_console_write(usb_console_info_impl_t *console_output_info,
    uchar_t *buf, uint_t num_characters, uint_t *num_characters_written)
{
        return (USB_NOT_SUPPORTED);
}

int
xhci_hcdi_console_output_exit(usb_console_info_impl_t *console_output_info)
{
        return (USB_NOT_SUPPORTED);
}