blob: a59380a154cd745e9f31c5ff07c70721381f6c77 [file] [log] [blame]
// 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);