| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include "usbdpi.h" |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include "usb_utils.h" |
| #include "usbdpi_test.h" |
| |
| // Indexed directly by ctx->state (ST_) |
| static const char *st_states[] = {"ST_IDLE 0", "ST_SEND 1", "ST_GET 2", |
| "ST_SYNC 3", "ST_EOP 4", "ST_EOP0 5"}; |
| |
| // Indexed directly by ct-x>hostSt (HS_) |
| static const char *hs_states[] = { |
| "HS_STARTFRAME 0", "HS_WAITACK 1", "HS_SET_DATASTAGE 2", "HS_DS_RXDATA 3", |
| "HS_DS_SENDACK 4", "HS_DONEDADR 5", "HS_REQDATA 6", "HS_WAITDATA 7", |
| "HS_SENDACK 8", "HS_WAIT_PKT 9", "HS_ACKIFDATA 10", "HS_SENDHI 11", |
| "HS_EMPTYDATA 12", "HS_WAITACK2 13", "HS_STREAMOUT 14", "HS_STREAMIN 15", |
| "HS_NEXTFRAME 16"}; |
| |
| static const char *decode_usb[] = {"SE0", "0-K", "1-J", "SE1"}; |
| |
| // Optionally invert the signals the host is driving, according to bus |
| // configuration |
| static uint32_t inv_driving(usbdpi_ctx_t *ctx, uint32_t d2p); |
| |
| // Request IN transfer. Get back NAK or DATA0/DATA1. |
| static void pollRX(usbdpi_ctx_t *ctx, uint8_t endpoint, bool send_hi, |
| bool nak_data); |
| // Get Baud (Vendor-specific) |
| static void readBaud(usbdpi_ctx_t *ctx, uint8_t endpoint); |
| |
| // Get Test Configuration (Vendor-specific) |
| static void getTestConfig(usbdpi_ctx_t *ctx, uint16_t desc_len); |
| |
| // Get Descriptor |
| static void getDescriptor(usbdpi_ctx_t *ctx, uint8_t desc_type, |
| uint8_t desc_idx, uint16_t desc_len); |
| // Set Baud (Vendor-specific) |
| static void setBaud(usbdpi_ctx_t *ctx, uint8_t endpoint); |
| |
| // Set device address (with null data stage) |
| static void setDeviceAddress(usbdpi_ctx_t *ctx, uint8_t dev_addr); |
| |
| // Set device configuration |
| static void setDeviceConfiguration(usbdpi_ctx_t *ctx, uint8_t config); |
| |
| // Set test status, reporting progress/success/failure (Vendor-specific) |
| static void setTestStatus(usbdpi_ctx_t *ctx, uint32_t status, const char *msg); |
| |
| // Change DP and DN outputs from host |
| static uint32_t set_driving(usbdpi_ctx_t *ctx, uint32_t d2p, uint32_t newval); |
| |
| // Try to send OUT transfer. Optionally expect Status packet (eg. ACK|NAK) in |
| // response |
| static void tryTX(usbdpi_ctx_t *ctx, uint8_t endpoint, bool expect_status); |
| |
| // Test the ischronous transfers (without ACKs) |
| // static void testIso(usbdpi_ctx_t *ctx); |
| #define testIso(ctx) tryTX((ctx), ENDPOINT_ISOCHRONOUS, false) |
| |
| // Callback for USB data detection |
| static void usbdpi_data_callback(void *ctx_v, usbmon_data_type_t type, |
| uint8_t d); |
| |
| /** |
| * Create a USB DPI instance, returning a 'chandle' for later use |
| */ |
| void *usbdpi_create(const char *name, int loglevel) { |
| // Use calloc for zero-initialisation |
| usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)calloc(1, sizeof(usbdpi_ctx_t)); |
| assert(ctx); |
| |
| // Note: calloc has initialized most of the fields for us |
| // ctx->tick = 0; |
| // ctx->tick_bits = 0; |
| // ctx->frame = 0; |
| // ctx->framepend = 0; |
| // ctx->frame_start = 0; |
| // ctx->last_pu = 0; |
| // ctx->driving = 0; |
| // ctx->baudrate_set_successfully = 0; |
| |
| // Initialize state for each endpoint and direction |
| for (unsigned ep = 0U; ep < USBDPI_MAX_ENDPOINTS; ep++) { |
| // First DATAx received shall be DATA0 |
| ctx->ep_in[ep].next_data = USB_PID_DATA0; |
| |
| // First DATAx transmitted shall be DATA0 because it must follow a SETUP |
| // transaction |
| ctx->ep_out[ep].next_data = USB_PID_DATA0; |
| } |
| |
| ctx->state = ST_IDLE; |
| ctx->hostSt = HS_NEXTFRAME; |
| ctx->loglevel = loglevel; |
| |
| ctx->step = STEP_BUS_RESET; |
| |
| char cwd[FILENAME_MAX]; |
| char *cwd_rv; |
| const char *test_out_dir = getenv("TEST_UNDECLARED_OUTPUTS_DIR"); |
| if (test_out_dir) { |
| cwd_rv = strncpy(cwd, test_out_dir, sizeof(cwd)); |
| } else { |
| cwd_rv = getcwd(cwd, sizeof(cwd)); |
| } |
| assert(cwd_rv != NULL); |
| |
| // Monitor log file |
| int rv = snprintf(ctx->mon_pathname, FILENAME_MAX, "%s/%s.log", cwd, name); |
| assert(rv <= FILENAME_MAX && rv > 0); |
| |
| ctx->mon = usb_monitor_init(ctx->mon_pathname, usbdpi_data_callback, ctx); |
| |
| // Prepare the transfer descriptors for use |
| usb_transfer_setup(ctx); |
| |
| return (void *)ctx; |
| } |
| |
| void usbdpi_device_to_host(void *ctx_void, const svBitVecVal *usb_d2p) { |
| usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)ctx_void; |
| assert(ctx); |
| |
| // Ascertain the state of the D+/D- signals from the device |
| // TODO - migrate to a simple function |
| uint32_t d2p = usb_d2p[0]; |
| unsigned dp, dn; |
| if (d2p & D2P_TX_USE_D_SE0) { |
| // Single-ended mode uses D and SE0 |
| if (d2p & D2P_D_EN) { |
| if (d2p & D2P_DNPU) { |
| // Pullup says swap i.e. D is inverted |
| dp = (d2p & D2P_SE0) ? 0 : ((d2p & D2P_D) ? 0 : 1); |
| dn = (d2p & D2P_SE0) ? 0 : ((d2p & D2P_D) ? 1 : 0); |
| } else { |
| dp = (d2p & D2P_SE0) ? 0 : ((d2p & D2P_D) ? 1 : 0); |
| dn = (d2p & D2P_SE0) ? 0 : ((d2p & D2P_D) ? 0 : 1); |
| } |
| } else { |
| dp = (d2p & D2P_PU) ? 1 : 0; |
| dn = 0; |
| } |
| } else { |
| // Normal D+/D- mode |
| if (d2p & D2P_DNPU) { |
| // Assertion of DN pullup suggests DP and DN are swapped |
| dp = ((d2p & D2P_DN_EN) && (d2p & D2P_DN)) || |
| (!(d2p & D2P_DN_EN) && (d2p & D2P_DNPU)); |
| dn = (d2p & D2P_DP_EN) && (d2p & D2P_DP); |
| } else { |
| // No DN pullup so normal orientation |
| dp = ((d2p & D2P_DP_EN) && (d2p & D2P_DP)) || |
| (!(d2p & D2P_DP_EN) && (d2p & D2P_DPPU)); |
| dn = (d2p & D2P_DN_EN) && (d2p & D2P_DN); |
| } |
| } |
| |
| // TODO - check the timing of the device responses to ensure compliance with |
| // the specification; the response time of acknowledgements, for example, has |
| // a hard real time requirement that is specified in terms of bit intervals |
| if (d2p & (D2P_DP_EN | D2P_DN_EN | D2P_D_EN)) { |
| switch (ctx->state) { |
| // Host to device transmission |
| case ST_SYNC: |
| case ST_SEND: |
| case ST_EOP: |
| case ST_EOP0: |
| printf( |
| "[usbdpi] frame 0x%x tick_bits 0x%x error state %s hs %s and " |
| "device " |
| "drives\n", |
| ctx->frame, ctx->tick_bits, st_states[ctx->state], |
| hs_states[ctx->hostSt]); |
| break; |
| |
| // Device to host transmission; collect the bits |
| case ST_GET: |
| // TODO - perform bit-level decoding and packet construction here rather |
| // than relying upon usb_monitor to do that |
| // TODO - synchronize with the device transmission and check that the |
| // signals remain stable across all 4 cycles of the bit interval |
| break; |
| |
| case ST_IDLE: |
| // Nothing to do |
| break; |
| |
| default: |
| assert(!"Invalid/unknown state"); |
| break; |
| } |
| |
| ctx->state = ST_GET; |
| } else { |
| if (ctx->state == ST_GET) { |
| ctx->state = ST_IDLE; |
| } |
| } |
| |
| if ((d2p & D2P_DNPU) && (d2p & D2P_DPPU)) { |
| printf("[usbdpi] frame 0x%x tick_bits 0x%x error both pullups are driven\n", |
| ctx->frame, ctx->tick_bits); |
| } |
| if ((d2p & D2P_PU) != ctx->last_pu) { |
| usb_monitor_log(ctx->mon, "0x%-3x 0x%-8x Pullup change to %s%s%s\n", |
| ctx->frame, ctx->tick_bits, |
| (d2p & D2P_DPPU) ? "DP Pulled up " : "", |
| (d2p & D2P_DNPU) ? "DN Pulled up " : "", |
| (d2p & D2P_TX_USE_D_SE0) ? "SingleEnded" : "Differential"); |
| |
| ctx->last_pu = d2p & D2P_PU; |
| } |
| |
| // TODO - prime candidate for a function |
| if (ctx->loglevel & LOG_BIT) { |
| char raw_str[D2P_BITS + 1]; |
| { |
| int i; |
| for (i = 0; i < D2P_BITS; i++) { |
| raw_str[D2P_BITS - i - 1] = d2p & (1 << i) ? '1' : '0'; |
| } |
| } |
| raw_str[D2P_BITS] = 0; |
| |
| const char *pullup = (d2p & D2P_PU) ? "PU" : " "; |
| const char *state = |
| (ctx->state == ST_GET) ? decode_usb[dp << 1 | dn] : "ZZ "; |
| usb_monitor_log(ctx->mon, "0x%-3x 0x%-8x %s %s %s %x\n", ctx->frame, |
| ctx->tick_bits, raw_str, pullup, state, d2p); |
| } |
| |
| // Device-to-Host EOP |
| if (ctx->state == ST_GET && dp == 0 && dn == 0) { |
| switch (ctx->bus_state) { |
| // Control Transfers |
| case kUsbControlSetup: |
| ctx->bus_state = kUsbControlSetupAck; |
| break; |
| case kUsbControlDataOut: |
| ctx->bus_state = kUsbControlDataOutAck; |
| break; |
| case kUsbControlStatusInToken: |
| ctx->bus_state = kUsbControlStatusInData; |
| break; |
| case kUsbControlDataInToken: |
| ctx->bus_state = kUsbControlDataInData; |
| break; |
| case kUsbControlStatusOut: |
| ctx->bus_state = kUsbControlStatusOutAck; |
| break; |
| |
| // Bulk Transfers |
| case kUsbBulkOut: |
| ctx->bus_state = kUsbBulkOutAck; |
| break; |
| case kUsbBulkInToken: |
| ctx->bus_state = kUsbBulkInData; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| } |
| |
| // Callback for USB data detection |
| // - the DPI host model presently does not duplicate the bit-level decoding and |
| // packet construction of the usb_monitor, so we piggyback on its decoding and |
| // trust it to be neutral. |
| // |
| // Note: this is invoked for any byte transferred over the USB; both |
| // host-to-device and device-to-host traffic |
| void usbdpi_data_callback(void *ctx_v, usbmon_data_type_t type, uint8_t d) { |
| usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)ctx_v; |
| assert(ctx); |
| if (!ctx->recving) { |
| ctx->recving = transfer_alloc(ctx); |
| } |
| |
| // We are interested only in the packets from device to host |
| if (ctx->state != ST_GET) { |
| return; |
| } |
| |
| usbdpi_transfer_t *tr = ctx->recving; |
| // TODO - commute to run time error indicating buffer exhaustion |
| assert(tr); |
| if (tr) { |
| if (false) { // ctx->loglevel & LOG_MON) { |
| printf("[usbdpi] data type %u d 0x%02x\n", type, d); |
| } |
| |
| bool ok = false; |
| switch (type) { |
| case UsbMon_DataType_Sync: |
| // Initialize/rewind the received transfer |
| transfer_init(tr); |
| ok = true; |
| break; |
| case UsbMon_DataType_EOP: |
| ok = true; |
| break; |
| // Collect the PID and any subsequent data bytes |
| case UsbMon_DataType_PID: |
| switch (d) { |
| case USB_PID_DATA0: |
| case USB_PID_DATA1: { |
| // TODO - this records the start of the data field with the |
| // current transfer descriptors |
| uint8_t *dp = transfer_data_start(tr, d, 0U); |
| ok = (dp != NULL); |
| } break; |
| default: |
| ok = transfer_append(tr, &d, 1); |
| break; |
| } |
| break; |
| // Collect data field |
| case UsbMon_DataType_Byte: |
| ok = transfer_append(tr, &d, 1); |
| break; |
| default: |
| assert(!"Unknown/unhandled rx type from monitor"); |
| break; |
| } |
| |
| // TODO - commute to run time error indicating excessive packet length |
| assert(ok); |
| } |
| } |
| |
| // Set device address (with null data stage) |
| void setDeviceAddress(usbdpi_ctx_t *ctx, uint8_t dev_addr) { |
| usbdpi_transfer_t *tr = ctx->sending; |
| uint8_t *dp; |
| switch (ctx->hostSt) { |
| case HS_STARTFRAME: |
| // Setting device address, uses address 0 initially |
| transfer_token(tr, USB_PID_SETUP, 0, ENDPOINT_ZERO); |
| |
| dp = transfer_data_start(tr, USB_PID_DATA0, 8); |
| if (INSERT_ERR_PID) { |
| *(dp - 1) = 0xc4U; |
| } |
| dp[0] = 0; // h2d, std, device |
| dp[1] = USB_REQ_SET_ADDRESS; |
| dp[2] = dev_addr; // device address |
| dp[3] = 0; |
| // Trigger bitstuffing, technically the device |
| // behavior is unspecified with wIndex != 0 |
| dp[4] = 0xFF; // wIndex = 0xFF00 |
| dp[5] = 0; |
| dp[6] = 0; // wLength = 0 |
| dp[7] = 0; |
| transfer_data_end(tr, dp + 8); |
| if (INSERT_ERR_CRC) { |
| // Flip the last CRC bit to emulate a CRC error |
| dp[9] ^= 0x01u; |
| } |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlSetup; |
| ctx->hostSt = HS_WAITACK; |
| break; |
| case HS_WAITACK: |
| ctx->wait = ctx->tick_bits + 532; // HACK |
| ctx->hostSt = HS_SET_DATASTAGE; |
| break; |
| case HS_SET_DATASTAGE: |
| if (ctx->bus_state == kUsbControlSetupAck && |
| ctx->tick_bits >= ctx->wait) { |
| transfer_token(tr, USB_PID_IN, 0, 0); |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlStatusInToken; |
| ctx->hostSt = HS_DS_RXDATA; |
| } |
| break; |
| case HS_DS_RXDATA: |
| ctx->wait = ctx->tick_bits + 24; // HACK -- 2 bytes |
| ctx->hostSt = HS_DS_SENDACK; |
| break; |
| case HS_DS_SENDACK: |
| if (ctx->bus_state == kUsbControlStatusInData || |
| ctx->tick_bits >= ctx->wait) { |
| transfer_status(ctx, tr, USB_PID_ACK); |
| ctx->bus_state = kUsbIdle; |
| ctx->hostSt = HS_NEXTFRAME; |
| |
| // Remember the assigned device address |
| ctx->dev_address = dev_addr; |
| |
| printf("[usbdpi] setDeviceAddress done\n"); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Set device configuration |
| void setDeviceConfiguration(usbdpi_ctx_t *ctx, uint8_t config) { |
| usbdpi_transfer_t *tr = ctx->sending; |
| uint8_t *dp; |
| switch (ctx->hostSt) { |
| case HS_STARTFRAME: |
| transfer_token(tr, USB_PID_SETUP, ctx->dev_address, ENDPOINT_ZERO); |
| |
| dp = transfer_data_start(tr, USB_PID_DATA0, 8); |
| dp[0] = 0; // h2d, std, device |
| dp[1] = USB_REQ_SET_CONFIGURATION; |
| dp[2] = config; |
| dp[3] = 0; |
| dp[4] = 0; // wIndex = 0 |
| dp[5] = 0; |
| dp[6] = 0; // wLength = 0 |
| dp[7] = 0; |
| transfer_data_end(tr, dp + 8); |
| |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlSetup; |
| ctx->hostSt = HS_WAITACK; |
| break; |
| case HS_WAITACK: |
| ctx->wait = ctx->tick_bits + 532; // HACK |
| ctx->hostSt = HS_SET_DATASTAGE; |
| break; |
| case HS_SET_DATASTAGE: |
| if (ctx->bus_state == kUsbControlSetupAck && |
| ctx->tick_bits >= ctx->wait) { |
| transfer_token(tr, USB_PID_IN, ctx->dev_address, ENDPOINT_ZERO); |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlStatusInToken; |
| ctx->hostSt = HS_DS_RXDATA; |
| } |
| break; |
| case HS_DS_RXDATA: |
| ctx->wait = ctx->tick_bits + 24; // HACK -- 2 bytes |
| ctx->hostSt = HS_DS_SENDACK; |
| break; |
| case HS_DS_SENDACK: |
| if (ctx->bus_state == kUsbControlStatusInData || |
| ctx->tick_bits >= ctx->wait) { |
| transfer_status(ctx, tr, USB_PID_ACK); |
| ctx->bus_state = kUsbIdle; |
| ctx->hostSt = HS_NEXTFRAME; |
| printf("[usbdpi] setDeviceConfiguration done\n"); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Get Descriptor |
| void getDescriptor(usbdpi_ctx_t *ctx, uint8_t desc_type, uint8_t desc_idx, |
| uint16_t desc_len) { |
| usbdpi_transfer_t *tr = ctx->sending; |
| uint8_t *dp; |
| switch (ctx->hostSt) { |
| case HS_STARTFRAME: { |
| // TODO - build the bmRequestType by ORing values |
| const uint8_t bmRequestType = 0x80; // d2h, vendor, endpoint |
| uint16_t wValue = (desc_type << 8) | (uint8_t)desc_idx; |
| transfer_setup(ctx, tr, bmRequestType, USB_REQ_GET_DESCRIPTOR, wValue, 0U, |
| desc_len); |
| } break; |
| case HS_WAITACK: |
| ctx->wait = ctx->tick_bits + 1164; // HACK |
| ctx->hostSt = HS_REQDATA; |
| break; |
| case HS_REQDATA: |
| // TODO - we must wait before trying the IN because we currently |
| // do not retry after a NAK response, but the CPU software can be tardy |
| if (ctx->tick_bits >= ctx->wait && |
| ctx->bus_state == kUsbControlSetupAck) { |
| transfer_token(tr, USB_PID_IN, ctx->dev_address, ENDPOINT_ZERO); |
| |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlDataInToken; |
| ctx->hostSt = HS_WAITDATA; |
| } |
| break; |
| case HS_WAITDATA: |
| ctx->wait = ctx->tick_bits + 2000; // HACK |
| ctx->hostSt = HS_SENDACK; |
| break; |
| case HS_SENDACK: |
| if (ctx->bus_state == kUsbControlDataInData) { |
| if (ctx->step == STEP_GET_CONFIG_DESCRIPTOR) { |
| // Check the GET_DESCRIPTOR response |
| assert(ctx->recving); |
| |
| // TODO - detect when the returned data falls short of the requested |
| // transfer length |
| uint8_t *dp = transfer_data_field(ctx->recving); |
| assert(dp); |
| // Collect the returned values |
| // uint8_t bLength = dp[0]; |
| // uint8_t bDescriptorType = dp[1]; |
| uint16_t wTotalLength = dp[2] | (dp[3] << 8); |
| // uint8_t bNumInterfaces = dp[4]; |
| |
| ctx->cfg_desc_len = wTotalLength; |
| } |
| |
| transfer_token(tr, USB_PID_ACK, ctx->dev_address, ENDPOINT_ZERO); |
| |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlDataInAck; |
| ctx->hostSt = HS_WAIT_PKT; |
| |
| ctx->wait = ctx->tick_bits + 200; // HACK |
| } else if (ctx->tick_bits >= ctx->wait) { |
| printf("[usbdpi] Timed out waiting for device\n"); |
| ctx->hostSt = HS_NEXTFRAME; |
| ctx->bus_state = kUsbIdle; |
| } |
| break; |
| case HS_WAIT_PKT: |
| // TODO - introduce support for multiple packets when we've requested |
| // longer transfers |
| if (ctx->tick_bits >= ctx->wait) { |
| ctx->hostSt = HS_EMPTYDATA; |
| } |
| break; |
| case HS_EMPTYDATA: |
| // Status stage of Control Read |
| // Transmit zero-length data packet and await handshake |
| transfer_token(tr, USB_PID_OUT, ctx->dev_address, ENDPOINT_ZERO); |
| |
| dp = transfer_data_start(tr, USB_PID_DATA1, 0); |
| transfer_data_end(tr, dp); |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlStatusOut; |
| ctx->hostSt = HS_WAITACK2; |
| ctx->wait = ctx->tick_bits + 200; // HACK |
| break; |
| case HS_WAITACK2: |
| if (ctx->bus_state == kUsbControlStatusOutAck) { |
| switch (ctx->lastrxpid) { |
| case USB_PID_ACK: |
| ctx->ep_out[ENDPOINT_ZERO].next_data = |
| DATA_TOGGLE_ADVANCE(ctx->ep_out[ENDPOINT_ZERO].next_data); |
| ctx->hostSt = HS_NEXTFRAME; |
| break; |
| |
| case USB_PID_NAK: |
| // TODO - this means that the device is still busy |
| ctx->hostSt = HS_WAITACK2; |
| ctx->wait = ctx->tick_bits + 200; // HACK |
| break; |
| |
| // TODO - commute these other responses into test failures |
| case USB_PID_STALL: |
| printf("[usbdpi] Device stalled\n"); |
| ctx->hostSt = HS_NEXTFRAME; |
| break; |
| default: |
| printf("[usbdpi] Unexpected handshake response 0x%02x\n", |
| ctx->lastrxpid); |
| ctx->hostSt = HS_NEXTFRAME; |
| break; |
| } |
| } else if (ctx->tick_bits >= ctx->wait) { |
| printf( |
| "[usbdpi] Time out waiting for device response in Status Stage\n"); |
| ctx->hostSt = HS_NEXTFRAME; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Get Test Configuration (Vendor-specific) |
| void getTestConfig(usbdpi_ctx_t *ctx, uint16_t desc_len) { |
| usbdpi_transfer_t *tr = ctx->sending; |
| uint8_t *dp; |
| assert(tr); |
| switch (ctx->hostSt) { |
| case HS_STARTFRAME: { |
| const uint8_t bmRequestType = 0xc2; // d2h, vendor, endpoint |
| transfer_setup(ctx, tr, bmRequestType, USBDPI_VENDOR_TEST_CONFIG, 0U, 0U, |
| desc_len); |
| } break; |
| case HS_WAITACK: |
| ctx->wait = ctx->tick_bits + 1164; // HACK |
| ctx->hostSt = HS_REQDATA; |
| break; |
| case HS_REQDATA: |
| // TODO - we must wait before trying the IN because we currently |
| // do not retry after a NAK response, but the CPU software can be tardy |
| if (ctx->tick_bits >= ctx->wait && |
| ctx->bus_state == kUsbControlSetupAck) { |
| transfer_token(tr, USB_PID_IN, ctx->dev_address, ENDPOINT_ZERO); |
| |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlDataInToken; |
| ctx->hostSt = HS_WAITDATA; |
| } |
| break; |
| case HS_WAITDATA: |
| ctx->wait = ctx->tick_bits + 2000; // HACK |
| ctx->hostSt = HS_SENDACK; |
| break; |
| case HS_SENDACK: |
| if (ctx->bus_state == kUsbControlDataInData) { |
| switch (ctx->lastrxpid) { |
| case USB_PID_DATA1: { |
| usbdpi_transfer_t *rx = ctx->recving; |
| assert(rx); |
| // TODO - check the length of the received data field! |
| uint8_t *dp = transfer_data_field(rx); |
| assert(dp); |
| // Validate the first part of the test descriptor |
| printf("[usbdpi] Test descriptor 0x%.8X\n", get_le32(dp)); |
| |
| transfer_dump(rx, stdout); |
| |
| // Check the header signature |
| const uint8_t test_sig_head[] = {0x7eu, 0x57u, 0xc0u, 0xf1u}; |
| const uint8_t test_sig_tail[] = {0x1fu, 0x0cu, 0x75u, 0xe7u}; |
| if (!memcmp(dp, test_sig_head, 4) && 0x10 == get_le16(&dp[4]) && |
| !memcmp(&dp[12], test_sig_tail, 4)) { |
| ctx->test_number = get_le16(&dp[6]); |
| ctx->test_arg[0] = dp[8]; |
| ctx->test_arg[1] = dp[9]; |
| ctx->test_arg[2] = dp[10]; |
| ctx->test_arg[3] = dp[11]; |
| |
| printf("[usbdpi] Test number 0x%04x args %02x %02x %02x %02x\n", |
| ctx->test_number, ctx->test_arg[0], ctx->test_arg[1], |
| ctx->test_arg[2], ctx->test_arg[3]); |
| |
| usbdpi_test_init(ctx); |
| } else { |
| printf( |
| "[usbdpi] Invalid/unrecognised test descriptor received\n"); |
| assert(!"Cannot proceed without test descriptor"); |
| } |
| } break; |
| |
| case USB_PID_NAK: |
| // TODO - we should retry the request in this case |
| printf("[usbdpi] Unable to retrieve test config\n"); |
| assert(!"DPI is unable to retrieve test config"); |
| ctx->hostSt = HS_NEXTFRAME; |
| break; |
| |
| case USB_PID_STALL: |
| printf("[usbdpi] Device stalled\n"); |
| assert(!"Device is stalled"); |
| ctx->hostSt = HS_NEXTFRAME; |
| break; |
| default: |
| printf("[usbdpi] Unexpected handshake response 0x%02x\n", |
| ctx->lastrxpid); |
| assert(!"Unexpected handshake response"); |
| ctx->hostSt = HS_NEXTFRAME; |
| break; |
| } |
| transfer_token(tr, USB_PID_ACK, ctx->dev_address, ENDPOINT_ZERO); |
| |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlDataInAck; |
| ctx->hostSt = HS_WAIT_PKT; |
| |
| ctx->wait = ctx->tick_bits + 200; // HACK |
| } else if (ctx->tick_bits >= ctx->wait) { |
| printf("[usbdpi] Timed out waiting for device\n"); |
| ctx->hostSt = HS_NEXTFRAME; |
| ctx->bus_state = kUsbIdle; |
| } |
| break; |
| case HS_WAIT_PKT: |
| if (ctx->tick_bits >= ctx->wait) { |
| ctx->hostSt = HS_EMPTYDATA; |
| } |
| break; |
| case HS_EMPTYDATA: |
| // Status stage of Control Read |
| // Transmit zero-length data packet and await handshake |
| transfer_token(tr, USB_PID_OUT, ctx->dev_address, ENDPOINT_ZERO); |
| |
| dp = transfer_data_start(tr, USB_PID_DATA1, 0); |
| transfer_data_end(tr, dp); |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlStatusOut; |
| ctx->hostSt = HS_WAITACK2; |
| ctx->wait = ctx->tick_bits + 200; // HACK |
| break; |
| case HS_WAITACK2: |
| if (ctx->bus_state == kUsbControlStatusOutAck) { |
| switch (ctx->lastrxpid) { |
| case USB_PID_ACK: |
| ctx->ep_out[ENDPOINT_ZERO].next_data = |
| DATA_TOGGLE_ADVANCE(ctx->ep_out[ENDPOINT_ZERO].next_data); |
| ctx->hostSt = HS_NEXTFRAME; |
| break; |
| |
| case USB_PID_NAK: |
| // TODO - this means that the device is still busy |
| ctx->hostSt = HS_WAITACK2; |
| ctx->wait = ctx->tick_bits + 200; // HACK |
| break; |
| |
| // TODO - commute these other responses into test failures |
| case USB_PID_STALL: |
| printf("[usbdpi] Device stalled\n"); |
| ctx->hostSt = HS_NEXTFRAME; |
| break; |
| default: |
| printf("[usbdpi] Unexpected handshake response 0x%02x\n", |
| ctx->lastrxpid); |
| ctx->hostSt = HS_NEXTFRAME; |
| break; |
| } |
| } else if (ctx->tick_bits >= ctx->wait) { |
| printf( |
| "[usbdpi] Time out waiting for device response in Status Stage\n"); |
| ctx->hostSt = HS_NEXTFRAME; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Set Test Status, reporting progress/success/failure (Vendor-specific) |
| void setTestStatus(usbdpi_ctx_t *ctx, uint32_t status, const char *msg) { |
| // TODO - placeholder for reporting of test status reporting and termination |
| } |
| |
| // Get Baud (Vendor-specific) |
| void readBaud(usbdpi_ctx_t *ctx, uint8_t endpoint) { |
| usbdpi_transfer_t *tr = ctx->sending; |
| uint8_t *dp; |
| switch (ctx->hostSt) { |
| case HS_STARTFRAME: |
| transfer_token(tr, USB_PID_SETUP, ctx->dev_address, endpoint); |
| |
| dp = transfer_data_start(tr, USB_PID_DATA0, 0); |
| dp[0] = 0xc2; // d2h, vendor, endpoint |
| dp[1] = 2; // get baud |
| dp[2] = 0; // index 0 |
| dp[3] = 0; // type device |
| dp[4] = 0; // wIndex = 0 |
| dp[5] = 0; |
| dp[6] = 0x2; // wLength = 2 |
| dp[7] = 0; |
| transfer_data_end(tr, dp + 8); |
| |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlSetup; |
| ctx->hostSt = HS_WAITACK; |
| break; |
| case HS_WAITACK: |
| ctx->wait = ctx->tick_bits + 32; // HACK |
| ctx->hostSt = HS_REQDATA; |
| break; |
| case HS_REQDATA: |
| if (ctx->tick_bits >= ctx->wait) { |
| // NOTE: This IN request produces a NAK from the device because there is |
| // nothing available, at which point a REAL host should surely |
| // retry the request at a later time! |
| transfer_token(tr, USB_PID_IN, ctx->dev_address, endpoint); |
| |
| transfer_send(ctx, tr); |
| ctx->hostSt = HS_WAITDATA; |
| } |
| break; |
| case HS_WAITDATA: |
| ctx->wait = ctx->tick_bits + 200; // HACK |
| ctx->hostSt = HS_SENDACK; |
| break; |
| case HS_SENDACK: |
| // TODO - are we not ACKing the NAK at this point? |
| if (ctx->tick_bits >= ctx->wait) { |
| transfer_status(ctx, tr, USB_PID_ACK); |
| ctx->hostSt = HS_EMPTYDATA; |
| } |
| break; |
| case HS_EMPTYDATA: |
| // Transmit OUT transaction with zero-length DATA packet |
| transfer_token(tr, USB_PID_OUT, ctx->dev_address, endpoint); |
| if (INSERT_ERR_DATA_TOGGLE) { |
| // NOTE: This raises a LinkOutErr on the USBDEV because it is expecting |
| // DATA0 |
| uint8_t bad_pid = DATA_TOGGLE_ADVANCE(ctx->ep_out[endpoint].next_data); |
| dp = transfer_data_start(tr, bad_pid, 0); |
| } else { |
| dp = transfer_data_start(tr, ctx->ep_out[endpoint].next_data, 0); |
| } |
| transfer_data_end(tr, dp); |
| |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlStatusOut; |
| ctx->hostSt = HS_WAITACK2; |
| |
| ctx->wait = ctx->tick_bits + 200; // HACK |
| break; |
| case HS_WAITACK2: |
| if (ctx->tick_bits >= ctx->wait || |
| ctx->bus_state == kUsbControlStatusOutAck) { |
| if (ctx->lastrxpid == USB_PID_ACK) { |
| ctx->ep_out[endpoint].next_data = |
| DATA_TOGGLE_ADVANCE(ctx->ep_out[endpoint].next_data); |
| } |
| ctx->hostSt = HS_NEXTFRAME; |
| printf("[usbdpi] readBaud done\n"); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Set Baud (Vendor-specific) |
| void setBaud(usbdpi_ctx_t *ctx, uint8_t endpoint) { |
| usbdpi_transfer_t *tr = ctx->sending; |
| uint8_t *dp; |
| switch (ctx->hostSt) { |
| case HS_STARTFRAME: |
| transfer_token(tr, USB_PID_SETUP, ctx->dev_address, endpoint); |
| |
| dp = transfer_data_start(tr, USB_PID_DATA0, 0); |
| dp[0] = 0x42; // h2d, vendor, endpoint |
| dp[1] = 3; // set baud |
| dp[2] = 96; // index 0 |
| dp[3] = 0; // type device |
| dp[4] = 0; // wIndex = 0 |
| dp[5] = 0; |
| dp[6] = 0; // wLength = 0 |
| dp[7] = 0; |
| transfer_data_end(tr, dp + 8); |
| |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbControlSetup; |
| ctx->hostSt = HS_WAITACK; |
| break; |
| case HS_WAITACK: |
| ctx->wait = ctx->tick_bits + 32; // HACK |
| ctx->hostSt = HS_REQDATA; |
| break; |
| case HS_REQDATA: |
| if (ctx->tick_bits >= ctx->wait) { |
| transfer_token(tr, USB_PID_IN, ctx->dev_address, endpoint); |
| |
| transfer_send(ctx, tr); |
| ctx->hostSt = HS_WAITDATA; |
| } |
| break; |
| case HS_WAITDATA: |
| ctx->wait = ctx->tick_bits + 40; // HACK |
| ctx->hostSt = HS_SENDACK; |
| break; |
| case HS_SENDACK: |
| if (ctx->tick_bits >= ctx->wait) { |
| transfer_status(ctx, tr, USB_PID_ACK); |
| ctx->hostSt = HS_NEXTFRAME; |
| ctx->baudrate_set_successfully = 1; |
| printf("[usbdpi] setBaud done\n"); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Try an OUT transfer to the device, optionally expecting a Status |
| // packet (eg. ACK|NAK) in response; this is not expected for |
| // Isochronous transfers |
| void tryTX(usbdpi_ctx_t *ctx, uint8_t endpoint, bool expect_status) { |
| const uint8_t pattern[] = { |
| "AbCdEfGhIjKlMnOpQrStUvWxYz+0123456789-aBcDeFgHiJkLmNoPqRsTuVwXyZ"}; |
| usbdpi_transfer_t *tr = ctx->sending; |
| uint8_t *dp; |
| switch (ctx->hostSt) { |
| case HS_STARTFRAME: |
| transfer_token(tr, USB_PID_OUT, ctx->dev_address, endpoint); |
| |
| dp = transfer_data_start(tr, ctx->ep_out[endpoint].next_data, 0); |
| memcpy(dp, pattern, sizeof(pattern)); |
| transfer_data_end(tr, dp + sizeof(pattern)); |
| |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbBulkOut; |
| ctx->hostSt = HS_WAITACK; |
| break; |
| case HS_WAITACK: // no actual ACK if Isochronous transfer |
| ctx->wait = ctx->tick_bits + 32; |
| ctx->hostSt = HS_REQDATA; |
| break; |
| case HS_REQDATA: |
| if (ctx->tick_bits >= ctx->wait) { |
| // Note: Isochronous transfers are not acknowledged and do not employ |
| // Data Toggle Synchronization |
| if (expect_status && ctx->lastrxpid == USB_PID_ACK) { |
| ctx->ep_out[endpoint].next_data = |
| DATA_TOGGLE_ADVANCE(ctx->ep_out[endpoint].next_data); |
| } |
| |
| transfer_token(tr, USB_PID_IN, ctx->dev_address, endpoint); |
| transfer_send(ctx, tr); |
| |
| ctx->hostSt = HS_WAITDATA; |
| } |
| break; |
| case HS_WAITDATA: |
| ctx->wait = ctx->tick_bits + 40; // HACK |
| ctx->hostSt = HS_NEXTFRAME; |
| printf("[usbdpi] testIso done\n"); |
| default: |
| break; |
| } |
| } |
| |
| // Request IN. Get back DATA0/DATA1 or NAK. |
| // |
| // send_hi -> also send OUT packet |
| // nak_data -> send NAK instead of ACK if there is data |
| void pollRX(usbdpi_ctx_t *ctx, uint8_t endpoint, bool send_hi, bool nak_data) { |
| usbdpi_transfer_t *tr = ctx->sending; |
| uint8_t *dp; |
| switch (ctx->hostSt) { |
| case HS_STARTFRAME: |
| transfer_token(tr, USB_PID_IN, ctx->dev_address, endpoint); |
| |
| transfer_send(ctx, tr); |
| ctx->bus_state = kUsbBulkInToken; |
| ctx->hostSt = HS_WAIT_PKT; |
| ctx->lastrxpid = 0; |
| break; |
| case HS_WAIT_PKT: |
| // Wait max time for a response + packet |
| ctx->wait = ctx->tick_bits + 18 + 8 + 8 + 64 * 8 + 16; |
| ctx->hostSt = HS_ACKIFDATA; |
| break; |
| case HS_ACKIFDATA: |
| if (ctx->bus_state == kUsbBulkInData) { |
| // TODO - have we got a LFSR-generated data packet? |
| if (ctx->lastrxpid != USB_PID_NAK) { |
| transfer_status(ctx, tr, nak_data ? USB_PID_NAK : USB_PID_ACK); |
| } |
| ctx->hostSt = HS_SENDHI; |
| } else if (ctx->tick_bits >= ctx->wait) { |
| printf("[usbdpi] Timed out waiting for IN response\n"); |
| ctx->hostSt = HS_SENDHI; |
| } |
| break; |
| case HS_SENDHI: |
| if (send_hi) { |
| transfer_token(tr, USB_PID_OUT, ctx->dev_address, endpoint); |
| |
| dp = transfer_data_start(tr, ctx->ep_out[endpoint].next_data, 0); |
| dp[0] = 0x48; // "H" |
| dp[1] = 0x69; // "i" |
| dp[2] = 0x21; // "!" |
| transfer_data_end(tr, dp + 3); |
| |
| transfer_send(ctx, tr); |
| ctx->wait = ctx->tick_bits + 532; // HACK |
| ctx->hostSt = HS_WAITACK; |
| } else { |
| ctx->hostSt = HS_NEXTFRAME; |
| } |
| break; |
| case HS_WAITACK: |
| if (ctx->tick_bits >= ctx->wait) { |
| if (ctx->lastrxpid == USB_PID_ACK) { |
| ctx->ep_out[endpoint].next_data = |
| DATA_TOGGLE_ADVANCE(ctx->ep_out[endpoint].next_data); |
| } |
| ctx->hostSt = HS_NEXTFRAME; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Test behavior in (non-)response to other device and unimplemented endpoints |
| void testUnimplEp(usbdpi_ctx_t *ctx, uint8_t pid, uint8_t device, |
| uint8_t endpoint) { |
| usbdpi_transfer_t *tr = ctx->sending; |
| uint8_t *dp; |
| switch (ctx->hostSt) { |
| case HS_STARTFRAME: |
| if ((pid == USB_PID_SETUP) || (pid == USB_PID_OUT)) { |
| transfer_token(tr, pid, device, endpoint); |
| |
| dp = transfer_data_start(tr, USB_PID_DATA0, 0); |
| dp[0] = 0; // h2d, std, device |
| dp[1] = 5; // set address |
| dp[2] = 2; // device address |
| dp[3] = 0; |
| // Trigger bitstuffing, technically the device |
| // behavior is unspecified with wIndex != 0 |
| dp[4] = 0xFF; // wIndex = 0xFF00 |
| dp[5] = 0; |
| dp[6] = 0; // wLength = 0 |
| dp[7] = 0; |
| transfer_data_end(tr, dp + 8); |
| |
| transfer_send(ctx, tr); |
| ctx->hostSt = HS_WAITACK; |
| break; |
| } else if (pid == USB_PID_IN) { |
| transfer_token(tr, pid, device, endpoint); |
| transfer_send(ctx, tr); |
| |
| // Since the endpoint is not implemented, the device should respond with |
| // a STALL packet (not DATA0/1 or NAK). |
| ctx->hostSt = HS_WAITACK; |
| break; |
| } else { |
| ctx->hostSt = HS_NEXTFRAME; |
| printf( |
| "[usbdpi] testUnimplEp supports SETUP, OUT and IN transactions " |
| "only\n"); |
| break; |
| } |
| case HS_WAITACK: |
| // Note: We currently can't observe the responses sent by the device, but |
| // usb_monitor() does log all transactions from host and device and does |
| // some basic decoding. |
| // Depending on the transaction type to unimplemented endpoints, we would |
| // expect the following response: |
| // - SETUP: no response (must be ignored by the device) |
| // - OUT/IN: a STALL packet from the device |
| ctx->wait = ctx->tick_bits + 32; // HACK |
| ctx->hostSt = HS_NEXTFRAME; |
| printf("[usbdpi] testUnimplEp done\n"); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Change DP and DN outputs from host |
| uint32_t set_driving(usbdpi_ctx_t *ctx, uint32_t d2p, uint32_t newval) { |
| // Always maintain the current state of VBUS |
| uint32_t driving = ctx->driving & P2D_SENSE; |
| if (d2p & D2P_DNPU) { |
| // Have dn pull-up, so must be flipping pins |
| if (newval & P2D_DP) { |
| driving |= P2D_DN | P2D_D; |
| } else if (newval & P2D_DN) { |
| driving |= P2D_DP; |
| } |
| } else { |
| if (newval & P2D_DP) { |
| driving |= P2D_DP | P2D_D; |
| } else if (newval & P2D_DN) { |
| driving |= P2D_DN; |
| } |
| } |
| return driving; |
| } |
| |
| // Optionally invert the signals the host is driving, according to bus |
| // configuration |
| uint32_t inv_driving(usbdpi_ctx_t *ctx, uint32_t d2p) { |
| // works for either orientation |
| return ctx->driving ^ (P2D_DP | P2D_DN | P2D_D); |
| } |
| |
| uint8_t usbdpi_host_to_device(void *ctx_void, const svBitVecVal *usb_d2p) { |
| usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)ctx_void; |
| assert(ctx); |
| int d2p = usb_d2p[0]; |
| uint32_t last_driving = ctx->driving; |
| int force_stat = 0; |
| int dat; |
| |
| // The 48MHz clock runs at 4 times the bus clock for a full speed (12Mbps) |
| // device |
| // |
| // TODO - vary the phase over the duration of the test to check device |
| // synchronization |
| ctx->tick++; |
| ctx->tick_bits = ctx->tick >> 2; |
| if (ctx->tick & 3) { |
| return ctx->driving; |
| } |
| |
| // Monitor, analyse and record USB bus activity |
| usb_monitor(ctx->mon, ctx->loglevel, ctx->tick_bits, |
| (ctx->state != ST_IDLE) && (ctx->state != ST_GET), ctx->driving, |
| d2p, &(ctx->lastrxpid)); |
| |
| if (ctx->tick_bits == SENSE_AT) { |
| ctx->driving |= P2D_SENSE; |
| } |
| |
| if ((d2p & D2P_PU) == 0) { |
| ctx->recovery_time = ctx->tick + 4 * 48; |
| return ctx->driving; |
| } |
| |
| // Are allowed to start transmitting yet; device recovery time elapsed? |
| if (ctx->tick < ctx->recovery_time) { |
| ctx->frame_start = ctx->tick_bits; |
| return ctx->driving; |
| } |
| |
| // Time to commence a new bus frame? |
| if ((ctx->tick_bits - ctx->frame_start) >= FRAME_INTERVAL) { |
| if (ctx->state != ST_IDLE) { |
| if (ctx->framepend == 0) { |
| printf( |
| "[usbdpi] frame 0x%x tick_bits 0x%x error state %d at frame 0x%x " |
| "time\n", |
| ctx->frame, ctx->tick, ctx->state, ctx->frame + 1); |
| } |
| ctx->framepend = 1; |
| } else { |
| if (ctx->framepend == 1) { |
| printf("[usbdpi] frame 0x%x tick_bits 0x%x can send frame 0x%x SOF\n", |
| ctx->frame, ctx->tick, ctx->frame + 1); |
| } |
| ctx->framepend = 0; |
| ctx->frame++; |
| ctx->frame_start = ctx->tick_bits; |
| |
| if (ctx->step >= STEP_IDLE_START && ctx->step < STEP_IDLE_END) { |
| // Test suspend behavior by dropping the SOF signalling |
| ctx->state = ST_IDLE; |
| printf("[usbdpi] idle frame 0x%x\n", ctx->frame); |
| } else { |
| // Ensure that a buffer is available for constructing a transfer |
| usbdpi_transfer_t *tr = ctx->sending; |
| if (!tr) { |
| tr = transfer_alloc(ctx); |
| assert(tr); |
| |
| ctx->sending = tr; |
| } |
| |
| transfer_frame_start(ctx, tr, ctx->frame); |
| ctx->state = ST_SYNC; |
| } |
| printf("[usbdpi] frame 0x%x tick_bits 0x%x CRC5 0x%x\n", ctx->frame, |
| ctx->tick, CRC5(ctx->frame, 11)); |
| |
| if (ctx->hostSt == HS_NEXTFRAME) { |
| ctx->step = usbdpi_test_seq_next(ctx, ctx->step); |
| ctx->hostSt = HS_STARTFRAME; |
| } else { |
| // TODO - this surely means that something went wrong; |
| // but what shall we do at this point?! |
| assert(!"DPI Host not ready to start new frame"); |
| } |
| } |
| } |
| |
| switch (ctx->state) { |
| // Host state machine advances when the bit-level activity is idle |
| case ST_IDLE: { |
| // Ensure that a buffer is available for constructing a transfer |
| usbdpi_transfer_t *tr = ctx->sending; |
| if (!tr) { |
| tr = transfer_alloc(ctx); |
| assert(tr); |
| ctx->sending = tr; |
| } |
| |
| switch (ctx->step) { |
| case STEP_SET_DEVICE_ADDRESS: |
| setDeviceAddress(ctx, USBDEV_ADDRESS); |
| break; |
| |
| // TODO - an actual host issues a number of GET_DESCRIPTOR control/ |
| // transfers to read descriptions of the configurations, |
| // interfaces and endpoints |
| |
| case STEP_GET_DEVICE_DESCRIPTOR: |
| // Initially we fetch just the minimal descriptor length of 0x12U |
| // bytes and the returned information will indicate the full length |
| // |
| // TODO - Set the descriptor length to the minimum because the DPI |
| // model does not yet catch and report errors properly |
| ctx->cfg_desc_len = 12U; |
| getDescriptor(ctx, USB_DESC_TYPE_DEVICE, 0U, 0x12U); |
| break; |
| |
| case STEP_GET_CONFIG_DESCRIPTOR: |
| getDescriptor(ctx, USB_DESC_TYPE_CONFIGURATION, 0U, 0x9U); |
| break; |
| |
| case STEP_GET_FULL_CONFIG_DESCRIPTOR: { |
| uint16_t wLength = ctx->cfg_desc_len; |
| if (wLength >= USBDEV_MAX_PACKET_SIZE) { |
| // Note: getDescriptor cannot yet receive multiple packets |
| wLength = USBDEV_MAX_PACKET_SIZE; |
| } |
| getDescriptor(ctx, USB_DESC_TYPE_CONFIGURATION, 0U, wLength); |
| } break; |
| |
| // TODO - we must receive and respond to test configuration at some |
| // point; perhaps we can make the software advertise itself |
| // with different vendor/device combinations to indicate the |
| // testing we must do |
| |
| case STEP_SET_DEVICE_CONFIG: |
| setDeviceConfiguration(ctx, 1); |
| break; |
| |
| // Test configuration and status |
| case STEP_GET_TEST_CONFIG: |
| getTestConfig(ctx, 0x10U); |
| break; |
| |
| case STEP_SET_TEST_STATUS: |
| setTestStatus(ctx, ctx->test_status, ctx->test_msg); |
| break; |
| |
| // These should be at 3 and 4 but the read needs the host |
| // not to be sending (until skip fifo is implemented in in_pe engine) |
| // so for now push later when things are quiet (could also adjust |
| // hello_world to not use the uart until frame 4) |
| |
| case STEP_FIRST_READ: |
| pollRX(ctx, ENDPOINT_SERIAL0, true, true); |
| break; |
| case STEP_READ_BAUD: |
| readBaud(ctx, ENDPOINT_ZERO); |
| break; |
| case STEP_SECOND_READ: |
| pollRX(ctx, ENDPOINT_SERIAL0, true, false); |
| break; |
| case STEP_SET_BAUD: |
| setBaud(ctx, ENDPOINT_ZERO); |
| break; |
| case STEP_THIRD_READ: |
| pollRX(ctx, ENDPOINT_SERIAL0, false, true); |
| break; |
| case STEP_TEST_ISO1: |
| testIso(ctx); |
| break; |
| case STEP_TEST_ISO2: |
| testIso(ctx); |
| break; |
| |
| // Test each of SETUP, OUT and IN to an unimplemented endpoint |
| case STEP_ENDPT_UNIMPL_SETUP: |
| testUnimplEp(ctx, USB_PID_SETUP, ctx->dev_address, |
| ENDPOINT_UNIMPLEMENTED); |
| break; |
| case STEP_ENDPT_UNIMPL_OUT: |
| testUnimplEp(ctx, USB_PID_OUT, ctx->dev_address, |
| ENDPOINT_UNIMPLEMENTED); |
| break; |
| case STEP_ENDPT_UNIMPL_IN: |
| testUnimplEp(ctx, USB_PID_IN, ctx->dev_address, |
| ENDPOINT_UNIMPLEMENTED); |
| break; |
| case STEP_DEVICE_UK_SETUP: |
| testUnimplEp(ctx, USB_PID_SETUP, UKDEV_ADDRESS, 1u); |
| break; |
| |
| case STEP_STREAM_SERVICE: |
| // After the initial testing of the (current) fixed DPI behavior, |
| // we repeatedly try IN transfers, checking and scrambling any |
| // data packets that we received before sending them straight back |
| // to the device for software to check |
| streams_service(ctx); |
| break; |
| |
| default: |
| if (ctx->step < STEP_IDLE_START || ctx->step >= STEP_IDLE_END) { |
| pollRX(ctx, ENDPOINT_SERIAL0, false, false); |
| } |
| break; |
| } |
| } break; |
| |
| case ST_SYNC: |
| dat = ((USB_SYNC & ctx->bit)) ? P2D_DP : P2D_DN; |
| ctx->driving = set_driving(ctx, d2p, dat); |
| force_stat = 1; |
| ctx->bit <<= 1; |
| if (ctx->bit == 0x100) { |
| ctx->bit = 1; |
| ctx->linebits = 1; // The KK at end of SYNC counts for bit stuffing! |
| ctx->state = ST_SEND; |
| } |
| break; |
| |
| case ST_SEND: { |
| usbdpi_transfer_t *sending = ctx->sending; |
| assert(sending); |
| if ((ctx->linebits & 0x3f) == 0x3f && |
| !INSERT_ERR_BITSTUFF) { // sent 6 ones |
| // bit stuff and force a transition |
| ctx->driving = inv_driving(ctx, d2p); |
| force_stat = 1; |
| ctx->linebits = (ctx->linebits << 1); |
| } else if (ctx->byte >= sending->num_bytes) { |
| ctx->state = ST_EOP; |
| ctx->driving = set_driving(ctx, d2p, 0); // SE0 |
| ctx->bit = 1; |
| force_stat = 1; |
| } else { |
| int nextbit = (sending->data[ctx->byte] & ctx->bit) ? 1 : 0; |
| if (nextbit == 0) { |
| ctx->driving = inv_driving(ctx, d2p); |
| } |
| ctx->linebits = (ctx->linebits << 1) | nextbit; |
| force_stat = 1; |
| ctx->bit <<= 1; |
| if (ctx->bit == 0x100) { |
| ctx->bit = 1; |
| ctx->byte++; |
| if (ctx->byte == sending->data_start) { |
| ctx->state = ST_EOP0; |
| } |
| } |
| } |
| } break; |
| |
| case ST_EOP0: |
| ctx->driving = set_driving(ctx, d2p, 0); // SE0 |
| ctx->state = ST_EOP; |
| break; |
| |
| case ST_EOP: // SE0 SE0 J |
| if (ctx->bit == 4) { |
| ctx->driving = set_driving(ctx, d2p, P2D_DP); // J |
| } |
| if (ctx->bit == 8) { |
| usbdpi_transfer_t *sending = ctx->sending; |
| assert(sending); |
| // Stop driving: host pulldown to SE0 unless there is a pullup on DP |
| ctx->driving = set_driving(ctx, d2p, (d2p & D2P_PU) ? P2D_DP : 0); |
| if (ctx->byte == sending->data_start) { |
| ctx->bit = 1; |
| ctx->state = ST_SYNC; |
| } else { |
| ctx->state = ST_IDLE; |
| } |
| } |
| ctx->bit <<= 1; |
| break; |
| |
| case ST_GET: |
| // Device is driving the bus; nothing to do here |
| break; |
| |
| default: |
| assert(!"Unknown/invalid USBDPI drive state"); |
| break; |
| } |
| |
| if ((ctx->loglevel & LOG_BIT) && |
| (force_stat || (ctx->driving != last_driving))) { |
| usb_monitor_log( |
| ctx->mon, "0x%-3x 0x-%8x %s %s %s\n", ctx->frame, |
| ctx->tick_bits, ctx->driving & P2D_SENSE ? "VBUS" : " ", |
| (ctx->state != ST_IDLE) ? decode_usb[(ctx->driving >> 1) & 3] : "ZZ ", |
| (ctx->driving & P2D_D) ? "1" : "0"); |
| } |
| return ctx->driving; |
| } |
| |
| // Export some internal diagnostic state for visibility in waveforms |
| void usbdpi_diags(void *ctx_void, svBitVecVal *diags) { |
| usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)ctx_void; |
| |
| // Check for overflow, which would cause confusion in waveform interpretation; |
| // if an assertion fires, the mapping from fields to svBitVecVal will need |
| // to be changed, both here and in usbdpi.sv |
| assert(ctx->state <= 0xfU); |
| assert(ctx->hostSt <= 0x1fU); |
| assert(ctx->bus_state <= 0x3fU); |
| assert(ctx->step <= 0x7fU); |
| |
| diags[2] = usb_monitor_diags(ctx->mon); |
| diags[1] = |
| (ctx->step << 25) | (ctx->bus_state << 20) | (ctx->tick_bits >> 12); |
| diags[0] = (ctx->tick_bits << 20) | ((ctx->frame & 0x7ffU) << 9) | |
| ((ctx->hostSt & 0x1fU) << 4) | (ctx->state & 0xfU); |
| } |
| |
| // Close the USBDPI model and release resources |
| void usbdpi_close(void *ctx_void) { |
| usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)ctx_void; |
| if (!ctx) { |
| return; |
| } |
| usb_monitor_fin(ctx->mon); |
| free(ctx); |
| } |