| // 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/usb_controlep.h" |
| |
| #include "sw/device/lib/common.h" |
| #include "sw/device/lib/usb_consts.h" |
| #include "sw/device/lib/usbdev.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 |
| }; |
| |
| static ctstate_t setup_req(usb_controlep_ctx_t *ctctx, void *ctx, |
| usbbufid_t buf, int bmRequestType, int bRequest, |
| int wValue, int wIndex, int wLength) { |
| size_t len; |
| uint32_t stat; |
| int zero, type; |
| switch (bRequest) { |
| case kUsbSetupReqGetDescriptor: |
| if ((wValue & 0xff00) == 0x100) { |
| // Device descriptor |
| len = sizeof(dev_dscr); |
| if (wLength < len) { |
| len = wLength; |
| } |
| usbdev_buf_copyto_byid(ctx, buf, dev_dscr, len); |
| usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep); |
| return kCtWaitIn; |
| } else if ((wValue & 0xff00) == 0x200) { |
| // Configuration descriptor |
| len = ctctx->cfg_dscr_len; |
| if (wLength < len) { |
| len = wLength; |
| } |
| usbdev_buf_copyto_byid(ctx, buf, ctctx->cfg_dscr, len); |
| usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep); |
| return kCtWaitIn; |
| } |
| return kCtError; // unknown |
| |
| case kUsbSetupReqSetAddress: |
| ctctx->new_dev = wValue & 0x7f; |
| // send zero length packet for status phase |
| usbdev_sendbuf_byid(ctx, buf, 0, ctctx->ep); |
| return kCtAddrStatIn; |
| |
| case kUsbSetupReqSetConfiguration: |
| // only ever expect this to be 1 since there is one config descriptor |
| ctctx->usb_config = wValue; |
| // send zero length packet for status phase |
| usbdev_sendbuf_byid(ctx, buf, 0, ctctx->ep); |
| return kCtStatIn; |
| |
| case kUsbSetupReqGetConfiguration: |
| len = sizeof(ctctx->usb_config); |
| if (wLength < len) { |
| len = wLength; |
| } |
| // return the value that was set |
| usbdev_buf_copyto_byid(ctx, buf, &ctctx->usb_config, len); |
| usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep); |
| return kCtWaitIn; |
| |
| case kUsbSetupReqSetFeature: |
| if (wValue == kUsbFeatureEndpointHalt) { |
| usbdev_halt(ctx, wIndex, 1); |
| } else if (wValue == kUsbFeatureDeviceRemoteWakeup) { |
| usbdev_rem_wake_en(ctx, 1); |
| } |
| // send zero length packet for status phase |
| usbdev_sendbuf_byid(ctx, buf, 0, ctctx->ep); |
| return kCtStatIn; |
| |
| case kUsbSetupReqClearFeature: |
| if (wValue == kUsbFeatureEndpointHalt) { |
| usbdev_halt(ctx, wIndex, 0); |
| } else if (wValue == kUsbFeatureDeviceRemoteWakeup) { |
| usbdev_rem_wake_en(ctx, 0); |
| } |
| // send zero length packet for status phase |
| usbdev_sendbuf_byid(ctx, buf, 0, ctctx->ep); |
| return kCtStatIn; |
| |
| case kUsbSetupReqGetStatus: |
| len = 2; |
| type = bmRequestType & kUsbReqTypeRecipientMask; |
| if (type == kUsbReqTypeDevice) { |
| stat = (usbdev_can_rem_wake(ctx) ? kUsbStatusRemWake : 0) | |
| kUsbStatusSelfPowered; |
| } else if (type == kUsbReqTypeEndpoint) { |
| stat = usbdev_halted(ctx, wIndex) ? kUsbStatusHalted : 0; |
| } else { |
| stat = 0; |
| } |
| if (wLength < len) { |
| len = wLength; |
| } |
| // return the value that was set |
| usbdev_buf_copyto_byid(ctx, buf, &stat, len); |
| usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep); |
| return kCtWaitIn; |
| |
| case kUsbSetupReqSetInterface: |
| // Don't support alternate interfaces, so just ignore |
| // send zero length packet for status phase |
| usbdev_sendbuf_byid(ctx, buf, 0, ctctx->ep); |
| return kCtStatIn; |
| |
| case kUsbSetupReqGetInterface: |
| zero = 0; |
| len = 1; |
| if (wLength < len) { |
| len = wLength; |
| } |
| // Don't support interface, so return zero |
| usbdev_buf_copyto_byid(ctx, buf, &zero, len); |
| usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep); |
| return kCtWaitIn; |
| |
| case kUsbSetupReqSynchFrame: |
| zero = 0; |
| len = 2; |
| if (wLength < len) { |
| len = wLength; |
| } |
| // Don't support synch_frame so return zero |
| usbdev_buf_copyto_byid(ctx, buf, &zero, len); |
| usbdev_sendbuf_byid(ctx, buf, len, ctctx->ep); |
| return kCtWaitIn; |
| } |
| return kCtError; |
| } |
| |
| static void ctrl_tx_done(void *ctctx_v) { |
| usb_controlep_ctx_t *ctctx = (usb_controlep_ctx_t *)ctctx_v; |
| void *ctx = ctctx->ctx; |
| TRC_C('A' + ctctx->ctrlstate); |
| switch (ctctx->ctrlstate) { |
| case kCtAddrStatIn: |
| // Now the status was sent on device 0 can switch to new device ID |
| usbdev_set_deviceid(ctx, ctctx->new_dev); |
| TRC_I(ctctx->new_dev, 8); |
| ctctx->ctrlstate = kCtIdle; |
| return; |
| case kCtStatIn: |
| ctctx->ctrlstate = kCtIdle; |
| return; |
| case kCtWaitIn: |
| ctctx->ctrlstate = kCtStatOut; |
| return; |
| default: |
| break; |
| } |
| TRC_S("USB: unexpected IN "); |
| TRC_I((ctctx->ctrlstate << 24), 32); |
| } |
| |
| static void ctrl_rx(void *ctctx_v, usbbufid_t buf, int size, int setup) { |
| usb_controlep_ctx_t *ctctx = (usb_controlep_ctx_t *)ctctx_v; |
| void *ctx = ctctx->ctx; |
| volatile uint8_t *bp = (volatile uint8_t *)usbdev_buf_idtoaddr(ctx, buf); |
| if (size > BUF_LENGTH) { |
| size = BUF_LENGTH; |
| } |
| |
| TRC_C('0' + ctctx->ctrlstate); |
| switch (ctctx->ctrlstate) { |
| case kCtIdle: |
| // Waiting to be set up |
| if (setup && (size == 8)) { |
| 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, buf, bmRequestType, bRequest, |
| wValue, wIndex, wLength); |
| if (ctctx->ctrlstate != kCtError) { |
| return; |
| } |
| } |
| break; |
| |
| case kCtStatOut: |
| // Have sent some data, waiting STATUS stage |
| if (!setup && (size == 0)) { |
| ctctx->ctrlstate = kCtIdle; |
| return; |
| } |
| // anything else is unexpected |
| break; |
| |
| default: |
| // Error |
| break; |
| } |
| usbdev_set_ep0_stall(ctx, 1); // send a STALL, will be cleared by the HW |
| TRC_S("USB: unCT "); |
| TRC_I((ctctx->ctrlstate << 24) | setup << 16 | size, 32); |
| TRC_C(':'); |
| for (int i = 0; i < size; i++) { |
| TRC_I(bp[i], 8); |
| TRC_C(' '); |
| } |
| usbdev_buf_free_byid(ctx, buf); |
| ctctx->ctrlstate = kCtIdle; |
| } |
| |
| // Callback for the USB link reset |
| void ctrl_reset(void *ctctx_v) { |
| usb_controlep_ctx_t *ctctx = (usb_controlep_ctx_t *)ctctx_v; |
| ctctx->ctrlstate = kCtIdle; |
| } |
| |
| void usb_controlep_init(usb_controlep_ctx_t *ctctx, usbdev_ctx_t *ctx, int ep, |
| const uint8_t *cfg_dscr, size_t cfg_dscr_len) { |
| ctctx->ctx = ctx; |
| usbdev_endpoint_setup(ctx, ep, 1, ctctx, ctrl_tx_done, ctrl_rx, NULL, |
| ctrl_reset); |
| ctctx->ctrlstate = kCtIdle; |
| ctctx->cfg_dscr = cfg_dscr; |
| ctctx->cfg_dscr_len = cfg_dscr_len; |
| } |