blob: 6866aabca698101b17ba83dd55f2acaf02f6e1e6 [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_controlep.h"
#include "sw/device/lib/dif/dif_usbdev.h"
#include "sw/device/lib/testing/test_framework/check.h"
#include "sw/device/lib/testing/usb_testutils.h"
// Device descriptor
static uint8_t dev_dscr[] = {
18, // bLength
1, // bDescriptorType
0x00, // bcdUSB[0]
0x02, // bcdUSB[1]
0x00, // bDeviceClass (defined at interface level)
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
64, // bMaxPacketSize0
0xd1, // idVendor[0] 0x18d1 Google Inc.
0x18, // idVendor[1]
0x3a, // idProduct[0] lowRISC generic FS USB
0x50, // idProduct[1] (allocated by Google)
0, // bcdDevice[0]
0x1, // bcdDevice[1]
0, // iManufacturer
0, // iProduct
0, // iSerialNumber
1 // bNumConfigurations
};
// SETUP requests
typedef enum usb_setup_req {
kUsbSetupReqGetStatus = 0,
kUsbSetupReqClearFeature = 1,
kUsbSetupReqSetFeature = 3,
kUsbSetupReqSetAddress = 5,
kUsbSetupReqGetDescriptor = 6,
kUsbSetupReqSetDescriptor = 7,
kUsbSetupReqGetConfiguration = 8,
kUsbSetupReqSetConfiguration = 9,
kUsbSetupReqGetInterface = 10,
kUsbSetupReqSetInterface = 11,
kUsbSetupReqSynchFrame = 12
} usb_setup_req_t;
// Vendor-specific requests defined by our device/test framework
typedef enum vendor_setup_req {
kVendorSetupReqTestConfig = 0x7C,
kVendorSetupReqTestStatus = 0x7E
} vendor_setup_req_t;
typedef enum usb_req_type { // bmRequestType
kUsbReqTypeRecipientMask = 0x1f,
kUsbReqTypeDevice = 0,
kUsbReqTypeInterface = 1,
kUsbReqTypeEndpoint = 2,
kUsbReqTypeOther = 3,
kUsbReqTypeTypeMask = 0x60,
kUsbReqTypeStandard = 0,
kUsbReqTypeClass = 0x20,
kUsbReqTypeVendor = 0x40,
kUsbReqTypeReserved = 0x60,
kUsbReqTypeDirMask = 0x80,
kUsbReqTypeDirH2D = 0x00,
kUsbReqTypeDirD2H = 0x80,
} usb_req_type_t;
typedef enum usb_desc_type { // Descriptor type (wValue hi)
kUsbDescTypeDevice = 1,
kUsbDescTypeConfiguration,
kUsbDescTypeString,
kUsbDescTypeInterface,
kUsbDescTypeEndpoint,
kUsbDescTypeDeviceQualifier,
kUsbDescTypeOtherSpeedConfiguration,
kUsbDescTypeInterfacePower,
} usb_desc_type_t;
typedef enum usb_feature_req {
kUsbFeatureEndpointHalt = 0, // recipient is endpoint
kUsbFeatureDeviceRemoteWakeup = 1, // recipient is device
kUsbFeatureTestMode = 2, // recipient is device
kUsbFeatureBHnpEnable = 3, // recipient is device only if OTG
kUsbFeatureAHnpSupport = 4, // recipient is device only if OTG
kUsbFeatureAAltHnpSupport = 5 // recipient is device only if OTG
} usb_feature_req_t;
typedef enum usb_status {
kUsbStatusSelfPowered = 1, // Device status request
kUsbStatusRemWake = 2, // Device status request
kUsbStatusHalted = 1 // Endpoint status request
} usb_status_t;
static usb_testutils_ctstate_t setup_req(usb_testutils_controlep_ctx_t *ctctx,
usb_testutils_ctx_t *ctx,
int bmRequestType, int bRequest,
int wValue, int wIndex, int wLength) {
size_t len;
uint32_t stat;
int zero, type;
size_t bytes_written;
// Endpoint for SetFeature/ClearFeature/GetStatus requests
dif_usbdev_endpoint_id_t endpoint = {
.number = (uint8_t)wIndex,
.direction = bmRequestType & 0x80,
};
dif_usbdev_buffer_t buffer;
CHECK_DIF_OK(dif_usbdev_buffer_request(ctx->dev, ctx->buffer_pool, &buffer));
switch (bRequest) {
case kUsbSetupReqGetDescriptor:
if ((wValue & 0xff00) == 0x100) {
// Device descriptor
len = sizeof(dev_dscr);
if (wLength < len) {
len = wLength;
}
CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, &buffer, dev_dscr, len,
&bytes_written));
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
return kUsbTestutilsCtWaitIn;
} else if ((wValue & 0xff00) == 0x200) {
// Configuration descriptor
len = ctctx->cfg_dscr_len;
if (wLength < len) {
len = wLength;
}
CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, &buffer, ctctx->cfg_dscr,
len, &bytes_written));
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
return kUsbTestutilsCtWaitIn;
}
return kUsbTestutilsCtError; // unknown
case kUsbSetupReqSetAddress:
TRC_S("SA");
ctctx->new_dev = wValue & 0x7f;
// send zero length packet for status phase
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
return kUsbTestutilsCtAddrStatIn;
case kUsbSetupReqSetConfiguration:
TRC_S("SC");
// only ever expect this to be 1 since there is one config descriptor
ctctx->usb_config = wValue;
// send zero length packet for status phase
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
if (wValue) {
ctctx->device_state = kUsbTestutilsDeviceConfigured;
} else {
// Device deconfigured
ctctx->device_state = kUsbTestutilsDeviceAddressed;
}
return kUsbTestutilsCtStatIn;
case kUsbSetupReqGetConfiguration:
len = sizeof(ctctx->usb_config);
if (wLength < len) {
len = wLength;
}
// return the value that was set
CHECK_DIF_OK(dif_usbdev_buffer_write(
ctx->dev, &buffer, &ctctx->usb_config, len, &bytes_written));
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
return kUsbTestutilsCtWaitIn;
case kUsbSetupReqSetFeature:
if (wValue == kUsbFeatureEndpointHalt) {
CHECK_DIF_OK(dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint,
kDifToggleEnabled));
// send zero length packet for status phase
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
return kUsbTestutilsCtStatIn;
}
return kUsbTestutilsCtError; // unknown
case kUsbSetupReqClearFeature:
if (wValue == kUsbFeatureEndpointHalt) {
CHECK_DIF_OK(dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint,
kDifToggleDisabled));
// send zero length packet for status phase
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
}
return kUsbTestutilsCtStatIn;
case kUsbSetupReqGetStatus:
len = 2;
type = bmRequestType & kUsbReqTypeRecipientMask;
if (type == kUsbReqTypeDevice) {
stat = kUsbStatusSelfPowered;
} else if (type == kUsbReqTypeEndpoint) {
bool halted;
CHECK_DIF_OK(
dif_usbdev_endpoint_stall_get(ctx->dev, endpoint, &halted));
stat = halted ? kUsbStatusHalted : 0;
} else {
stat = 0;
}
if (wLength < len) {
len = wLength;
}
// return the value that was set
CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, &buffer, (uint8_t *)&stat,
len, &bytes_written));
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
return kUsbTestutilsCtWaitIn;
case kUsbSetupReqSetInterface:
// Don't support alternate interfaces, so just ignore
// send zero length packet for status phase
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
return kUsbTestutilsCtStatIn;
case kUsbSetupReqGetInterface:
zero = 0;
len = 1;
if (wLength < len) {
len = wLength;
}
// Don't support interface, so return zero
CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, &buffer, (uint8_t *)&zero,
len, &bytes_written));
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
return kUsbTestutilsCtWaitIn;
case kUsbSetupReqSynchFrame:
zero = 0;
len = 2;
if (wLength < len) {
len = wLength;
}
// Don't support synch_frame so return zero
CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, &buffer, (uint8_t *)&zero,
len, &bytes_written));
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
return kUsbTestutilsCtWaitIn;
default:
// We implement a couple of bespoke, vendor-defined Setup requests to
// allow the DPI model to access the test configuration (Control Read) and
// to report the test status (Control Write)
if ((bmRequestType & kUsbReqTypeTypeMask) == kUsbReqTypeVendor &&
ctctx->test_dscr) {
switch ((vendor_setup_req_t)bRequest) {
case kVendorSetupReqTestConfig: {
TRC_S("TC");
// Test config descriptor
len = ctctx->test_dscr_len;
if (wLength < len) {
len = wLength;
}
CHECK_DIF_OK(dif_usbdev_buffer_write(
ctx->dev, &buffer, ctctx->test_dscr, len, &bytes_written));
CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ctctx->ep, &buffer));
return kUsbTestutilsCtWaitIn;
} break;
case kVendorSetupReqTestStatus: {
// TODO - pass the received test status to the OTTF directly?
} break;
}
}
return kUsbTestutilsCtError;
}
return kUsbTestutilsCtError;
}
static void ctrl_tx_done(void *ctctx_v) {
usb_testutils_controlep_ctx_t *ctctx =
(usb_testutils_controlep_ctx_t *)ctctx_v;
usb_testutils_ctx_t *ctx = ctctx->ctx;
TRC_C('A' + ctctx->ctrlstate);
switch (ctctx->ctrlstate) {
case kUsbTestutilsCtAddrStatIn:
// Now the status was sent on device 0 can switch to new device ID
CHECK_DIF_OK(dif_usbdev_address_set(ctx->dev, ctctx->new_dev));
TRC_I(ctctx->new_dev, 8);
ctctx->ctrlstate = kUsbTestutilsCtIdle;
// We now have a device address on the USB
ctctx->device_state = kUsbTestutilsDeviceAddressed;
return;
case kUsbTestutilsCtStatIn:
ctctx->ctrlstate = kUsbTestutilsCtIdle;
return;
case kUsbTestutilsCtWaitIn:
ctctx->ctrlstate = kUsbTestutilsCtStatOut;
return;
default:
break;
}
TRC_S("USB: unexpected IN ");
TRC_I((ctctx->ctrlstate << 24), 32);
}
static void ctrl_rx(void *ctctx_v, dif_usbdev_rx_packet_info_t packet_info,
dif_usbdev_buffer_t buffer) {
usb_testutils_controlep_ctx_t *ctctx =
(usb_testutils_controlep_ctx_t *)ctctx_v;
usb_testutils_ctx_t *ctx = ctctx->ctx;
CHECK_DIF_OK(dif_usbdev_endpoint_out_enable(ctx->dev, /*endpoint=*/0,
kDifToggleEnabled));
TRC_C('0' + ctctx->ctrlstate);
uint32_t bytes_written;
// TODO: Should check for canceled IN transactions due to receiving a SETUP
// packet.
switch (ctctx->ctrlstate) {
case kUsbTestutilsCtIdle:
// Waiting to be set up
if (packet_info.is_setup && (packet_info.length == 8)) {
alignas(uint32_t) uint8_t bp[8];
CHECK_DIF_OK(dif_usbdev_buffer_read(ctx->dev, ctx->buffer_pool, &buffer,
bp, sizeof(bp), &bytes_written));
int bmRequestType = bp[0];
int bRequest = bp[1];
int wValue = (bp[3] << 8) | bp[2];
int wIndex = (bp[5] << 8) | bp[4];
int wLength = (bp[7] << 8) | bp[6];
TRC_C('0' + bRequest);
ctctx->ctrlstate = setup_req(ctctx, ctx, bmRequestType, bRequest,
wValue, wIndex, wLength);
if (ctctx->ctrlstate != kUsbTestutilsCtError) {
return;
}
TRC_C(':');
for (int i = 0; i < packet_info.length; i++) {
TRC_I(bp[i], 8);
}
}
break;
case kUsbTestutilsCtStatOut:
// Have sent some data, waiting STATUS stage
if (!packet_info.is_setup && (packet_info.length == 0)) {
CHECK_DIF_OK(
dif_usbdev_buffer_return(ctx->dev, ctx->buffer_pool, &buffer));
ctctx->ctrlstate = kUsbTestutilsCtIdle;
return;
}
// anything else is unexpected
break;
default:
// Error
break;
}
dif_usbdev_endpoint_id_t endpoint = {
.number = 0,
.direction = USBDEV_ENDPOINT_DIR_IN,
};
// Enable responding with STALL. Will be cleared by the HW.
CHECK_DIF_OK(
dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, kDifToggleEnabled));
endpoint.direction = USBDEV_ENDPOINT_DIR_OUT;
CHECK_DIF_OK(
dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, kDifToggleEnabled));
TRC_S("USB: unCT ");
TRC_I((ctctx->ctrlstate << 24) | ((int)packet_info.is_setup << 16) |
packet_info.length,
32);
if (buffer.type != kDifUsbdevBufferTypeStale) {
// Return the unused buffer.
CHECK_DIF_OK(dif_usbdev_buffer_return(ctx->dev, ctx->buffer_pool, &buffer));
}
ctctx->ctrlstate = kUsbTestutilsCtIdle;
}
// Callback for the USB link reset
static void ctrl_reset(void *ctctx_v) {
usb_testutils_controlep_ctx_t *ctctx =
(usb_testutils_controlep_ctx_t *)ctctx_v;
ctctx->ctrlstate = kUsbTestutilsCtIdle;
}
void usb_testutils_controlep_init(usb_testutils_controlep_ctx_t *ctctx,
usb_testutils_ctx_t *ctx, int ep,
const uint8_t *cfg_dscr, size_t cfg_dscr_len,
const uint8_t *test_dscr,
size_t test_dscr_len) {
ctctx->ctx = ctx;
usb_testutils_endpoint_setup(ctx, ep, kUsbdevOutMessage, ctctx, ctrl_tx_done,
ctrl_rx, NULL, ctrl_reset);
ctctx->ep = ep;
ctctx->ctrlstate = kUsbTestutilsCtIdle;
ctctx->cfg_dscr = cfg_dscr;
ctctx->cfg_dscr_len = cfg_dscr_len;
ctctx->test_dscr = test_dscr;
ctctx->test_dscr_len = test_dscr_len;
CHECK_DIF_OK(dif_usbdev_interface_enable(ctx->dev, kDifToggleEnabled));
ctctx->device_state = kUsbTestutilsDeviceDefault;
}