| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include "sw/device/lib/testing/usb_testutils.h" |
| |
| #include "sw/device/lib/dif/dif_usbdev.h" |
| #include "sw/device/lib/testing/test_framework/check.h" |
| |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" |
| |
| #define USBDEV_BASE_ADDR TOP_EARLGREY_USBDEV_BASE_ADDR |
| |
| static dif_usbdev_t usbdev; |
| static dif_usbdev_buffer_pool_t buffer_pool; |
| |
| void usb_testutils_poll(usb_testutils_ctx_t *ctx) { |
| uint32_t istate; |
| |
| // Collect a set of interrupts |
| CHECK_DIF_OK(dif_usbdev_irq_get_state(ctx->dev, &istate)); |
| |
| if (!istate) { |
| // Nothing new to do right now, but keep buffers available for reception |
| CHECK_DIF_OK(dif_usbdev_fill_available_fifo(ctx->dev, ctx->buffer_pool)); |
| return; |
| } |
| |
| // Record bus frame |
| if ((istate & (1u << kDifUsbdevIrqFrame))) { |
| // The first bus frame is 1 |
| CHECK_DIF_OK(dif_usbdev_status_get_frame(ctx->dev, &ctx->frame)); |
| ctx->got_frame = true; |
| } |
| |
| // Process IN completions first so we get the fact that send completed |
| // before processing a response to that transmission |
| // This is also important for device IN performance |
| if (istate & (1u << kDifUsbdevIrqPktSent)) { |
| uint16_t sentep; |
| CHECK_DIF_OK(dif_usbdev_get_tx_sent(ctx->dev, &sentep)); |
| TRC_C('a' + sentep); |
| for (unsigned ep = 0; ep < USBDEV_NUM_ENDPOINTS; ep++) { |
| if (sentep & (1 << ep)) { |
| // Free up the buffer and optionally callback |
| CHECK_DIF_OK( |
| dif_usbdev_clear_tx_status(ctx->dev, ctx->buffer_pool, ep)); |
| |
| if (ctx->tx_done_callback[ep]) { |
| ctx->tx_done_callback[ep](ctx->ep_ctx[ep]); |
| } |
| } |
| } |
| |
| // Clear the interrupt sooner so that we can respond more promptly to |
| // subsequent transmitted packets |
| CHECK_DIF_OK(dif_usbdev_irq_acknowledge(ctx->dev, kDifUsbdevIrqPktSent)); |
| istate &= ~(1u << kDifUsbdevIrqPktSent); |
| } |
| |
| // Keep buffers available for packet reception |
| CHECK_DIF_OK(dif_usbdev_fill_available_fifo(ctx->dev, ctx->buffer_pool)); |
| |
| if (istate & (1u << kDifUsbdevIrqPktReceived)) { |
| while (true) { |
| bool is_empty; |
| CHECK_DIF_OK(dif_usbdev_status_get_rx_fifo_empty(ctx->dev, &is_empty)); |
| if (is_empty) { |
| break; |
| } |
| |
| dif_usbdev_rx_packet_info_t packet_info; |
| dif_usbdev_buffer_t buffer; |
| CHECK_DIF_OK(dif_usbdev_recv(ctx->dev, &packet_info, &buffer)); |
| |
| uint8_t endpoint = packet_info.endpoint; |
| if (ctx->rx_callback[endpoint]) { |
| ctx->rx_callback[endpoint](ctx->ep_ctx[endpoint], packet_info, buffer); |
| } else { |
| TRC_S("USB: unexpected RX "); |
| TRC_I(endpoint, 8); |
| CHECK_DIF_OK( |
| dif_usbdev_buffer_return(ctx->dev, ctx->buffer_pool, &buffer)); |
| } |
| } |
| } |
| if (istate & (1u << kDifUsbdevIrqLinkReset)) { |
| TRC_S("USB: Bus reset"); |
| // Link reset |
| for (int ep = 0; ep < USBDEV_NUM_ENDPOINTS; ep++) { |
| if (ctx->reset[ep]) { |
| ctx->reset[ep](ctx->ep_ctx[ep]); |
| } |
| } |
| } |
| |
| uint32_t accepted = istate; |
| dif_usbdev_irq_t irq = 0U; |
| while (accepted) { |
| if (accepted & 1U) { |
| CHECK_DIF_OK(dif_usbdev_irq_acknowledge(ctx->dev, irq)); |
| } |
| accepted >>= 1; |
| irq++; |
| } |
| |
| // Note: LinkInErr will be raised in response to a packet being NAKed by the |
| // host which is not expected behavior on a physical USB but this is something |
| // that the DPI model does to exercise packet resending when running |
| // usbdev_stream_test |
| // |
| // We can expect AVFIFO empty and RXFIFO full interrupts when using a real |
| // host and also 'LinkOut' errors because these can be triggered by a lack of |
| // space in the RXFIFO |
| |
| if (istate & |
| ~((1u << kDifUsbdevIrqLinkReset) | (1u << kDifUsbdevIrqPktReceived) | |
| (1u << kDifUsbdevIrqPktSent) | (1u << kDifUsbdevIrqFrame) | |
| (1u << kDifUsbdevIrqAvEmpty) | (1u << kDifUsbdevIrqRxFull) | |
| (1u << kDifUsbdevIrqLinkOutErr) | (1u << kDifUsbdevIrqLinkInErr))) { |
| // Report anything that really should not be happening during testing, |
| // at least for now |
| // |
| // TODO - introduce deliberate generation of each of these errors, and |
| // modify usb_testutils_ to return the information without faulting |
| // it? |
| if (istate & |
| ((1u << kDifUsbdevIrqRxFull) | (1u << kDifUsbdevIrqAvOverflow) | |
| (1u << kDifUsbdevIrqLinkInErr) | (1u << kDifUsbdevIrqRxCrcErr) | |
| (1u << kDifUsbdevIrqRxPidErr) | (1u << kDifUsbdevIrqRxBitstuffErr) | |
| (1u << kDifUsbdevIrqLinkOutErr))) { |
| LOG_INFO("USB: Unexpected interrupts: 0x%08x", istate); |
| } else { |
| // Other events are optionally reported |
| TRC_C('I'); |
| TRC_I(istate, 12); |
| TRC_C(' '); |
| } |
| } |
| |
| // TODO - clean this up |
| // Frame ticks every 1ms, use to flush data every 16ms |
| // (faster in DPI but this seems to work ok) |
| // |
| // Ensure that we do not flush until we have received a frame |
| if (ctx->got_frame && (ctx->frame & 0xf) == 1) { |
| if (ctx->flushed == 0) { |
| for (int i = 0; i < USBDEV_NUM_ENDPOINTS; i++) { |
| if (ctx->flush[i]) { |
| ctx->flush[i](ctx->ep_ctx[i]); |
| } |
| } |
| ctx->flushed = 1; |
| } |
| } else { |
| ctx->flushed = 0; |
| } |
| // TODO Errors? What Errors? |
| } |
| |
| void usb_testutils_endpoint_setup( |
| usb_testutils_ctx_t *ctx, int ep, |
| usb_testutils_out_transfer_mode_t out_mode, void *ep_ctx, |
| void (*tx_done)(void *), |
| void (*rx)(void *, dif_usbdev_rx_packet_info_t, dif_usbdev_buffer_t), |
| void (*flush)(void *), void (*reset)(void *)) { |
| ctx->ep_ctx[ep] = ep_ctx; |
| ctx->tx_done_callback[ep] = tx_done; |
| ctx->rx_callback[ep] = rx; |
| ctx->flush[ep] = flush; |
| ctx->reset[ep] = reset; |
| |
| // Enable IN traffic from device to host |
| dif_usbdev_endpoint_id_t endpoint = { |
| .number = ep, |
| .direction = USBDEV_ENDPOINT_DIR_IN, |
| }; |
| CHECK_DIF_OK( |
| dif_usbdev_endpoint_enable(ctx->dev, endpoint, kDifToggleEnabled)); |
| |
| if (out_mode != kUsbdevOutDisabled) { |
| // Enable OUT traffic from host to device |
| endpoint.direction = USBDEV_ENDPOINT_DIR_OUT; |
| CHECK_DIF_OK( |
| dif_usbdev_endpoint_enable(ctx->dev, endpoint, kDifToggleEnabled)); |
| // Enable reception of OUT packets |
| CHECK_DIF_OK(dif_usbdev_endpoint_out_enable(ctx->dev, endpoint.number, |
| kDifToggleEnabled)); |
| } |
| if (out_mode == kUsbdevOutMessage) { |
| CHECK_DIF_OK(dif_usbdev_endpoint_set_nak_out_enable( |
| ctx->dev, endpoint.number, kDifToggleEnabled)); |
| } |
| } |
| |
| void usb_testutils_init(usb_testutils_ctx_t *ctx, bool pinflip, |
| bool en_diff_rcvr, bool tx_use_d_se0) { |
| CHECK(ctx != NULL); |
| ctx->dev = &usbdev; |
| ctx->buffer_pool = &buffer_pool; |
| |
| // Ensure that we do not invoke the endpoint 'flush' functions before |
| // detection of the first bus frame |
| ctx->got_frame = false; |
| ctx->frame = 0u; |
| |
| CHECK_DIF_OK( |
| dif_usbdev_init(mmio_region_from_addr(USBDEV_BASE_ADDR), ctx->dev)); |
| |
| dif_usbdev_config_t config = { |
| .have_differential_receiver = dif_bool_to_toggle(en_diff_rcvr), |
| .use_tx_d_se0 = dif_bool_to_toggle(tx_use_d_se0), |
| .single_bit_eop = kDifToggleDisabled, |
| .pin_flip = dif_bool_to_toggle(pinflip), |
| .clock_sync_signals = kDifToggleEnabled, |
| }; |
| CHECK_DIF_OK(dif_usbdev_configure(ctx->dev, ctx->buffer_pool, config)); |
| |
| // Set up context |
| for (int i = 0; i < USBDEV_NUM_ENDPOINTS; i++) { |
| usb_testutils_endpoint_setup(ctx, i, kUsbdevOutDisabled, NULL, NULL, NULL, |
| NULL, NULL); |
| } |
| |
| // All about polling... |
| CHECK_DIF_OK(dif_usbdev_irq_disable_all(ctx->dev, NULL)); |
| |
| // Provide buffers for any reception |
| CHECK_DIF_OK(dif_usbdev_fill_available_fifo(ctx->dev, ctx->buffer_pool)); |
| |
| dif_usbdev_endpoint_id_t endpoint = { |
| .number = 0, |
| .direction = 1, |
| }; |
| CHECK_DIF_OK( |
| dif_usbdev_endpoint_enable(ctx->dev, endpoint, kDifToggleEnabled)); |
| CHECK_DIF_OK( |
| dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, kDifToggleDisabled)); |
| |
| endpoint.direction = 0; |
| CHECK_DIF_OK( |
| dif_usbdev_endpoint_enable(ctx->dev, endpoint, kDifToggleEnabled)); |
| CHECK_DIF_OK( |
| dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, kDifToggleDisabled)); |
| CHECK_DIF_OK(dif_usbdev_endpoint_setup_enable(ctx->dev, endpoint.number, |
| kDifToggleEnabled)); |
| CHECK_DIF_OK(dif_usbdev_endpoint_out_enable(ctx->dev, endpoint.number, |
| kDifToggleEnabled)); |
| } |
| |
| // `extern` declarations to give the inline functions in the |
| // corresponding header a link location. |
| |
| extern int usb_testutils_halted(usb_testutils_ctx_t *ctx, |
| dif_usbdev_endpoint_id_t endpoint); |