| // 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; | |
| } |