Get a basic USB device working
It enumerates, takes its address, gets configured, and then Linux kind
of gives up because it has no endpoints.
Change-Id: I01f75acee419b585e455f428ee45bcd37f0ce189
diff --git a/motors/usb/usb.cc b/motors/usb/usb.cc
new file mode 100644
index 0000000..3067695
--- /dev/null
+++ b/motors/usb/usb.cc
@@ -0,0 +1,812 @@
+#include "motors/usb/usb.h"
+
+#include <string.h>
+
+#include "motors/util.h"
+
+namespace frc971 {
+namespace teensy {
+namespace {
+
+// The mask of interrupts we care about.
+constexpr uint32_t usb_enabled_interrupts() {
+ // Deliberately not turning the sleep interrupt on here because we just
+ // want to ignore that anyways.
+ return USB_INTEN_TOKDNEEN | USB_INTEN_SOFTOKEN | USB_INTEN_ERROREN |
+ USB_INTEN_USBRSTEN;
+}
+
+// The names of all the standard setup requests which come in on endpoint 0.
+namespace standard_setup_requests {
+constexpr int kGetStatus = 0;
+constexpr int kClearFeature = 1;
+constexpr int kSetFeature = 3;
+constexpr int kSetAddress = 5;
+constexpr int kGetDescriptor = 6;
+constexpr int kSetDescriptor = 7;
+constexpr int kGetConfiguration = 8;
+constexpr int kSetConfiguration = 9;
+constexpr int kGetInterface = 10;
+constexpr int kSetInterface = 11;
+constexpr int kSynchFrame = 12;
+} // namespace standard_setup_requests
+
+// The names of the standard feature selectors.
+namespace standard_feature_selectors {
+constexpr int kDeviceRemoteWakeup = 1;
+constexpr int kEndpointHalt = 0;
+constexpr int kTestMode = 2;
+} // namespace standard_feature_selectors
+
+// The names of all the PIDs (Packet IDs) from the USB standard. Note that this
+// USB hardware doesn't expose most of them, especially in device mode.
+enum class UsbPid {
+ kOut = 0x1,
+ kIn = 0x9,
+ kSof = 0x5,
+ kSetup = 0xD,
+ kData0 = 0x3,
+ kData1 = 0xB,
+ kData2 = 0x7,
+ kMData = 0xF,
+ kAck = 0x2,
+ kNak = 0xA,
+ kStall = 0xE,
+ kNYet = 0x6,
+ kPre = 0xC,
+ kErr = 0xC,
+ kSplit = 0x8,
+ kPing = 0x4,
+ kReserved = 0x0,
+};
+
+// The device class for using IADs.
+constexpr uint8_t iad_device_class() { return 0xEF; }
+// The device subclass for using IADs.
+constexpr uint8_t iad_device_subclass() { return 0x02; }
+// The device protocol for using IADs.
+constexpr uint8_t iad_device_protocol() { return 0x01; }
+
+// The total number of endpoints supported by this hardware.
+constexpr int number_endpoints() { return 16; }
+
+__attribute__((aligned(512))) BdtEntry
+ usb0_buffer_descriptor_table[number_endpoints() * 2 /* rx/tx */ *
+ 2 /* even/odd */];
+
+// Returns the specified BDT entry.
+BdtEntry *MutableBdtEntry(int endpoint, Direction direction, EvenOdd odd) {
+ return &usb0_buffer_descriptor_table[static_cast<uint32_t>(endpoint << 2) |
+ static_cast<uint32_t>(direction) |
+ static_cast<uint32_t>(odd)];
+}
+
+// Returns the BDT entry corresponding to a USBx_STAT value.
+BdtEntry *MutableBdtEntryFromStat(uint8_t stat) {
+ return &usb0_buffer_descriptor_table[static_cast<uint32_t>(stat) >> 2];
+}
+
+// A pointer to the object we're going to ask to handle interrupts.
+UsbDevice *volatile global_usb0_device = nullptr;
+
+} // namespace
+
+UsbDevice::UsbDevice(int index, uint16_t vendor_id, uint16_t product_id)
+ : index_(index) {
+ // TODO(Brian): Pass index_ into all the register access macros. Also sort out
+ // how to deal with it for the interrupts.
+ assert(index == 0);
+
+ assert(global_usb0_device == nullptr);
+ global_usb0_device = this;
+
+ // Endpoint 0 isn't a normal endpoint, so it doesn't show up in here.
+ endpoint_mapping_.push_back(nullptr);
+
+ // Set up the "String Descriptor Zero, Specifying Languages Supported by the
+ // Device" (aka english_us_code() only).
+ strings_.emplace_back(4, '\0');
+ strings_.back()[0] = 4;
+ strings_.back()[1] = static_cast<uint8_t>(UsbDescriptorType::kString);
+ strings_.back()[2] = english_us_code() & 0xFF;
+ strings_.back()[3] = (english_us_code() >> 8) & 0xFF;
+
+ device_descriptor_ =
+ device_descriptor_list_.CreateDescriptor(18, UsbDescriptorType::kDevice);
+ device_descriptor_->AddUint16(0x0200); // bcdUSB
+ device_descriptor_->AddByte(iad_device_class()); // bDeviceClass
+ device_descriptor_->AddByte(iad_device_subclass()); // bDeviceSubClass
+ device_descriptor_->AddByte(iad_device_protocol()); // bDeviceProtocol
+ device_descriptor_->AddByte(kEndpoint0MaxSize); // bMaxPacketSize0
+ device_descriptor_->AddUint16(vendor_id); // idVendor
+ device_descriptor_->AddUint16(product_id); // idProduct
+ device_descriptor_->AddUint16(0); // bcdDevice
+ // We might overwrite these string descriptor indices later if we get strings
+ // to put there.
+ device_descriptor_->AddByte(0); // iManufacturer
+ device_descriptor_->AddByte(0); // iProduct
+ device_descriptor_->AddByte(0); // iSerialNumber
+ device_descriptor_->AddByte(1); // bNumConfigurations
+
+ config_descriptor_ = config_descriptor_list_.CreateDescriptor(
+ 9, UsbDescriptorType::kConfiguration);
+}
+
+UsbDevice::~UsbDevice() {
+ NVIC_DISABLE_IRQ(IRQ_USBOTG);
+ dma_memory_barrier();
+ assert(global_usb0_device == this);
+ global_usb0_device = nullptr;
+}
+
+void UsbDevice::Initialize() {
+ assert(!is_set_up_);
+
+ for (UsbFunction *function : functions_) {
+ function->Initialize();
+ }
+
+ config_descriptor_->AddUint16(
+ config_descriptor_list_.CurrentSize()); // wTotalLength
+ config_descriptor_->AddByte(interface_mapping_.size()); // bNumInterfaces
+ config_descriptor_->AddByte(1); // bConfigurationValue
+ // Doesn't seem to be much point naming our one and only configuration.
+ config_descriptor_->AddByte(0); // iConfiguration
+ config_descriptor_->AddByte((1 << 7) /* Reserved */ |
+ (1 << 6) /* Self-powered */); // bmAttribute
+ config_descriptor_->AddByte(2 /* 4mA */); // bMaxPower
+
+ device_descriptor_.reset();
+ config_descriptor_.reset();
+ device_descriptor_list_.CheckFinished();
+ config_descriptor_list_.CheckFinished();
+ is_set_up_ = true;
+
+ // Make sure all the buffer descriptors are clear.
+ for (int i = 0; i < number_endpoints(); ++i) {
+ for (Direction direction : {Direction::kTx, Direction::kRx}) {
+ for (EvenOdd odd : {EvenOdd::kOdd, EvenOdd::kEven}) {
+ MutableBdtEntry(i, direction, odd)->buffer_descriptor = 0;
+ MutableBdtEntry(i, direction, odd)->address = nullptr;
+ }
+ }
+ }
+ dma_memory_barrier();
+
+ // The other startup code handles getting the incoming 48MHz clock running.
+ SIM_SCGC4 |= SIM_SCGC4_USBOTG;
+ MPU_RGDAAC0 |= 0x03000000;
+
+ // Reset it.
+ USB0_USBTRC0 = USB_USBTRC_USBRESET;
+ // TRM says to wait "two USB clock cycles", so assume that's at 48MHz and then
+ // round up, being pessimistic in assuming each read from the peripheral is
+ // only a single core clock. This wildly overapproximates how long we need to
+ // wait, but whatever.
+ for (int i = 0; i < ((F_CPU / 48000000) + 1) * 2; ++i) {
+ while ((USB0_USBTRC0 & USB_USBTRC_USBRESET) != 0) {
+ }
+ }
+
+ USB0_BDTPAGE1 =
+ reinterpret_cast<uintptr_t>(&usb0_buffer_descriptor_table[0]) >> 8;
+ USB0_BDTPAGE2 =
+ reinterpret_cast<uintptr_t>(&usb0_buffer_descriptor_table[0]) >> 16;
+ USB0_BDTPAGE3 =
+ reinterpret_cast<uintptr_t>(&usb0_buffer_descriptor_table[0]) >> 24;
+
+ // The Quick Reference User Guide says to clear all the interrupts.
+ ClearInterrupts();
+ USB0_OTGISTAT = USB_OTGISTAT_ONEMSEC | USB_OTGISTAT_LINE_STATE_CHG;
+
+ // Now enable the module.
+ USB0_CTL = USB_CTL_USBENSOFEN;
+
+ // Un-suspend the transceiver and disable weak pulldowns.
+ USB0_USBCTRL = 0;
+ // And enable the D+ pullup which indicates we're a full-speed device.
+ USB0_CONTROL = USB_CONTROL_DPPULLUPNONOTG;
+
+ // Enable the reset interrupt (which is the first one we care about).
+ USB0_INTEN = USB_INTEN_USBRSTEN;
+
+ dma_memory_barrier();
+ NVIC_ENABLE_IRQ(IRQ_USBOTG);
+}
+
+void usb_isr(void) {
+ UsbDevice *const usb0_device = global_usb0_device;
+ if (usb0_device == nullptr) {
+ NVIC_DISABLE_IRQ(IRQ_USBOTG);
+ } else {
+ usb0_device->HandleInterrupt();
+ }
+}
+
+void UsbDevice::ClearInterrupts() {
+ USB0_ISTAT = USB_ISTAT_ATTACH | USB_ISTAT_RESUME | USB_ISTAT_SLEEP |
+ USB_ISTAT_TOKDNE | USB_ISTAT_SOFTOK | USB_ISTAT_ERROR |
+ USB_ISTAT_USBRST;
+ USB0_ERRSTAT = USB_ERRSTAT_BTSERR | USB_ERRSTAT_DMAERR | USB_ERRSTAT_BTOERR |
+ USB_ERRSTAT_DFN8 | USB_ERRSTAT_CRC16 | USB_ERRSTAT_CRC5EOF |
+ USB_ERRSTAT_PIDERR;
+}
+
+void UsbDevice::HandleInterrupt() {
+ while (true) {
+ const uint32_t status = USB0_ISTAT;
+ if ((status & usb_enabled_interrupts()) == 0) {
+ return;
+ }
+
+ // If we just got a start-of-frame token, then ask all the functions what to
+ // do.
+ if (status & USB_ISTAT_SOFTOK) {
+ // TODO(Brian): Actually ask the functions, maybe only if we're
+ // configured.
+ USB0_ISTAT = USB_ISTAT_SOFTOK;
+ }
+
+ // If we just finished processing a token.
+ if (status & USB_ISTAT_TOKDNE) {
+ const uint8_t stat = USB0_STAT;
+ const int endpoint = G_USB_STAT_ENDP(stat);
+
+ if (endpoint == 0) {
+ HandleEndpoint0Token(stat);
+ } else {
+ BdtEntry *const bdt_entry = MutableBdtEntryFromStat(stat);
+ const UsbPid pid = G_USB_BD_PID(bdt_entry->buffer_descriptor);
+ UsbFunction *const function = endpoint_mapping_[endpoint];
+ if (function == nullptr) {
+ // Should never happen, so stall if we do get here somehow.
+ StallEndpoint(endpoint);
+ } else {
+ switch (pid) {
+ case UsbPid::kOut:
+ function->HandleOutFinished(endpoint, bdt_entry);
+ break;
+
+ case UsbPid::kIn:
+ function->HandleInFinished(
+ endpoint, bdt_entry,
+ (stat & M_USB_STAT_ODD) ? EvenOdd::kOdd : EvenOdd::kEven);
+ break;
+
+ case UsbPid::kSetup:
+ default:
+ // Should never happen, so stall if we do get here somehow.
+ StallEndpoint(endpoint);
+ break;
+ }
+ }
+ }
+
+ USB0_ISTAT = USB_ISTAT_TOKDNE;
+ }
+
+ if (status & USB_ISTAT_USBRST) {
+ // Use DATA0 for all endpoints.
+ USB0_CTL = USB_CTL_ODDRST;
+ endpoint0_tx_odd_ = EvenOdd::kEven;
+ endpoint0_tx_toggle_ = Data01::kData0;
+
+ for (UsbFunction *function : functions_) {
+ function->HandleReset();
+ }
+
+ MutableBdtEntry(0, Direction::kRx, EvenOdd::kEven)->buffer_descriptor =
+ M_USB_BD_OWN | M_USB_BD_DTS | V_USB_BD_BC(kEndpoint0MaxSize);
+ MutableBdtEntry(0, Direction::kRx, EvenOdd::kEven)->address =
+ &endpoint0_receive_buffer_[0][0];
+
+ MutableBdtEntry(0, Direction::kRx, EvenOdd::kOdd)->buffer_descriptor =
+ M_USB_BD_OWN | M_USB_BD_DTS | V_USB_BD_BC(kEndpoint0MaxSize);
+ MutableBdtEntry(0, Direction::kRx, EvenOdd::kOdd)->address =
+ &endpoint0_receive_buffer_[1][0];
+
+ MutableBdtEntry(0, Direction::kTx, EvenOdd::kEven)->buffer_descriptor = 0;
+ MutableBdtEntry(0, Direction::kTx, EvenOdd::kOdd)->buffer_descriptor = 0;
+
+ USB0_ENDPT0 = USB_ENDPT_EPRXEN | USB_ENDPT_EPTXEN | USB_ENDPT_EPHSHK;
+
+ ClearInterrupts();
+
+ // Set the address to 0 for enumeration.
+ USB0_ADDR = 0;
+ new_address_ = 0;
+
+ endpoint0_data_ = nullptr;
+ endpoint0_data_left_ = 0;
+
+ USB0_INTEN = usb_enabled_interrupts();
+ USB0_ERREN = USB_ERREN_BTSERREN | USB_ERREN_DMAERREN |
+ USB_ERREN_BTOERREN | USB_ERREN_DFN8EN | USB_ERREN_CRC16EN |
+ USB_ERREN_CRC5EOFEN | USB_ERREN_PIDERREN;
+
+ // Start the peripheral going.
+ dma_memory_barrier();
+ USB0_CTL = USB_CTL_USBENSOFEN;
+
+ continue;
+ }
+
+ // TODO(Brian): Handle errors more intelligently.
+ if (status & USB_ISTAT_ERROR) {
+ const uint8_t error = USB0_ERRSTAT;
+ USB0_ERRSTAT = error;
+ USB0_ISTAT = USB_ISTAT_ERROR;
+ }
+ }
+}
+
+void UsbDevice::HandleEndpoint0Token(const uint8_t stat) {
+ BdtEntry *const bdt_entry = MutableBdtEntryFromStat(stat);
+ const UsbPid pid = G_USB_BD_PID(bdt_entry->buffer_descriptor);
+ switch (pid) {
+ case UsbPid::kSetup:
+ // Unstall it if it was previously stalled.
+ USB0_ENDPT0 = USB_ENDPT_EPRXEN | USB_ENDPT_EPTXEN | USB_ENDPT_EPHSHK;
+
+ SetupPacket setup_packet;
+ memcpy(&setup_packet, bdt_entry->address, sizeof(setup_packet));
+
+ // Give the buffer back now.
+ dma_memory_barrier();
+ // Next IN and OUT packet for this endpoint (data stage/status stage)
+ // should both be DATA1.
+ // TODO(Brian): Does this actually deal with received toggles correctly?
+ bdt_entry->buffer_descriptor =
+ M_USB_BD_OWN | M_USB_BD_DTS | V_USB_BD_BC(kEndpoint0MaxSize);
+ MutableBdtEntryFromStat(stat ^ M_USB_STAT_ODD)->buffer_descriptor =
+ M_USB_BD_OWN | M_USB_BD_DTS | V_USB_BD_BC(kEndpoint0MaxSize) |
+ M_USB_BD_DATA1;
+ endpoint0_tx_toggle_ = Data01::kData1;
+
+ // TODO(Brian): Tell the functions a new setup packet is starting.
+ // CdcTty: next_endpoint0_out_ = NextEndpoint0Out::kNone;
+
+ // Forget about any pending transactions on this endpoint. There shouldn't
+ // be any, so if we think there are something's out of sync and we should
+ // just drop it. Important to do this before clearing TXD_SUSPEND in
+ // USBx_CTL. Standard says "If a Setup transaction is received by an
+ // endpoint before a previously initiated control transfer is completed,
+ // the device must abort the current transfer/operation".
+ endpoint0_data_ = nullptr;
+ endpoint0_data_left_ = 0;
+ MutableBdtEntry(0, Direction::kTx, EvenOdd::kEven)->buffer_descriptor = 0;
+ MutableBdtEntry(0, Direction::kTx, EvenOdd::kOdd)->buffer_descriptor = 0;
+
+ HandleEndpoint0SetupPacket(setup_packet);
+
+ break;
+
+ case UsbPid::kOut:
+ for (UsbFunction *function : functions_) {
+ switch (function->HandleEndpoint0OutPacket(
+ bdt_entry->address, G_USB_BD_BC(bdt_entry->buffer_descriptor))) {
+ case SetupResponse::kIgnored:
+ break;
+ case SetupResponse::kHandled:
+ dma_memory_barrier();
+ bdt_entry->buffer_descriptor =
+ M_USB_BD_OWN | M_USB_BD_DTS | V_USB_BD_BC(kEndpoint0MaxSize);
+ return;
+ case SetupResponse::kStall:
+ bdt_entry->buffer_descriptor =
+ M_USB_BD_OWN | M_USB_BD_DTS | V_USB_BD_BC(kEndpoint0MaxSize);
+ StallEndpoint0();
+ return;
+ }
+ }
+ bdt_entry->buffer_descriptor =
+ M_USB_BD_OWN | M_USB_BD_DTS | V_USB_BD_BC(kEndpoint0MaxSize);
+ StallEndpoint0();
+ return;
+
+ case UsbPid::kIn:
+ // The functions are allowed to queue data in {endpoint0_data_,
+ // endpoint0_data_left_}, so this case deals with sending their data too.
+
+ // An IN transaction completed, so set up for the next one if appropriate.
+ if (!BufferEndpoint0TxPacket()) {
+ // After we're done, any further requests from the host should result in
+ // stalls (until the next setup token).
+ // TODO(Brian): Keep track of which direction it is and how much we've
+ // finished so we actually know when to stall it, both here and for
+ // kOut tokens.
+ //StallEndpoint0();
+ }
+
+ // If we have a new address, there is nothing left in the setup request
+ // besides a single IN packet forming the status stage, so we know the
+ // changes must be done now.
+ if (new_address_ != 0) {
+ USB0_ADDR = new_address_;
+ new_address_ = 0;
+ }
+
+ break;
+
+ default:
+ // Should never happen, but give the buffer back anyways if necessary.
+ if (!(bdt_entry->buffer_descriptor & M_USB_BD_OWN)) {
+ bdt_entry->buffer_descriptor =
+ M_USB_BD_OWN | M_USB_BD_DTS | V_USB_BD_BC(kEndpoint0MaxSize);
+ }
+ break;
+ }
+
+ // Clear the TXD_SUSPEND flag.
+ dma_memory_barrier();
+ USB0_CTL = USB_CTL_USBENSOFEN;
+}
+
+void UsbDevice::HandleEndpoint0SetupPacket(const SetupPacket &setup_packet) {
+ const bool in = setup_packet.request_type & M_SETUP_REQUEST_TYPE_IN;
+ const uint8_t recipient =
+ G_SETUP_REQUEST_TYPE_RECIPIENT(setup_packet.request_type);
+ switch (G_SETUP_REQUEST_TYPE_TYPE(setup_packet.request_type)) {
+ case SetupRequestType::kStandard:
+ switch (setup_packet.request) {
+ case standard_setup_requests::kSetAddress:
+ if (in || recipient != standard_setup_recipients::kDevice ||
+ setup_packet.index != 0 || setup_packet.length != 0) {
+ break;
+ }
+ new_address_ = setup_packet.value;
+ SendEmptyEndpoint0Packet();
+ return;
+
+ case standard_setup_requests::kSetConfiguration:
+ if (in || recipient != standard_setup_recipients::kDevice ||
+ setup_packet.index != 0 || setup_packet.length != 0) {
+ break;
+ }
+ configuration_ = setup_packet.value;
+
+ // No need to mess with endpoint0_tx_toggle_ because we reset it with
+ // each setup packet anyways.
+
+ for (int endpoint = 0;
+ endpoint < static_cast<int>(endpoint_mapping_.size());
+ ++endpoint) {
+ if (endpoint_mapping_[endpoint]) {
+ endpoint_mapping_[endpoint]->HandleConfigured(endpoint);
+ }
+ }
+
+ SendEmptyEndpoint0Packet();
+ return;
+
+ case standard_setup_requests::kClearFeature:
+ if (in || setup_packet.length != 0) {
+ break;
+ }
+ if (recipient == standard_setup_recipients::kEndpoint &&
+ setup_packet.value == standard_feature_selectors::kEndpointHalt) {
+ const int endpoint =
+ G_SETUP_REQUEST_INDEX_ENDPOINT(setup_packet.index);
+ // Our endpoint 0 doesn't support the halt feature because that's
+ // weird and not recommended by the standard.
+ if (endpoint == 0) {
+ break;
+ }
+ if (endpoint >= number_endpoints()) {
+ break;
+ }
+ USB0_ENDPTn(endpoint) &= ~USB_ENDPT_EPSTALL;
+ if (endpoint_mapping_[endpoint] != nullptr) {
+ endpoint_mapping_[endpoint]->HandleConfigured(endpoint);
+ }
+ SendEmptyEndpoint0Packet();
+ return;
+ }
+ // We should never get kDeviceRemoteWakeup because we don't advertise
+ // support for it in our configuration descriptors.
+ // We should never get kTestMode because we're not high-speed.
+ break;
+
+ case standard_setup_requests::kSetFeature:
+ if (in || setup_packet.length != 0) {
+ break;
+ }
+ if (recipient == standard_setup_recipients::kEndpoint &&
+ setup_packet.value == standard_feature_selectors::kEndpointHalt) {
+ const int endpoint =
+ G_SETUP_REQUEST_INDEX_ENDPOINT(setup_packet.index);
+ // Our endpoint 0 doesn't support the halt feature because that's
+ // weird and not recommended by the standard.
+ if (endpoint == 0) {
+ break;
+ }
+ if (endpoint >= number_endpoints()) {
+ break;
+ }
+ StallEndpoint(endpoint);
+ // TODO(Brian): Tell the appropriate function it's now stalled.
+ SendEmptyEndpoint0Packet();
+ return;
+ }
+ // We should never get kDeviceRemoteWakeup because we don't advertise
+ // support for it in our configuration descriptors.
+ // We should never get kTestMode because we're not high-speed.
+ break;
+
+ case standard_setup_requests::kGetConfiguration:
+ if (!in || recipient != standard_setup_recipients::kDevice ||
+ setup_packet.index != 0 || setup_packet.length != 1) {
+ break;
+ }
+ endpoint0_transmit_buffer_[0] = configuration_;
+ QueueEndpoint0Data(endpoint0_transmit_buffer_, 1);
+ return;
+
+ case standard_setup_requests::kGetInterface:
+ if (!in || recipient != standard_setup_recipients::kInterface ||
+ setup_packet.value != 0 || setup_packet.length != 1) {
+ break;
+ }
+ // Standard says it's unspecified in the default state and must
+ // respond with an error in the address state, so just do an error for
+ // both of them.
+ if (configuration_ == 0) {
+ break;
+ }
+ // TODO(Brian): Ask the appropriate function what alternate setting
+ // the interface has, and stall if there isn't one.
+ endpoint0_transmit_buffer_[0] = 0;
+ QueueEndpoint0Data(endpoint0_transmit_buffer_, 1);
+ return;
+
+ case standard_setup_requests::kSetInterface:
+ if (in || recipient != standard_setup_recipients::kInterface ||
+ setup_packet.length != 0) {
+ break;
+ }
+ // Standard says it's unspecified in the default state and must
+ // respond with an error in the address state, so just do an error for
+ // both of them.
+ if (configuration_ == 0) {
+ break;
+ }
+
+ // TODO(Brian): Pass to the appropriate function instead.
+ if (setup_packet.value != 0) {
+ break;
+ }
+ SendEmptyEndpoint0Packet();
+ return;
+
+ case standard_setup_requests::kGetStatus:
+ if (!in || setup_packet.value != 0 || setup_packet.length != 2) {
+ break;
+ }
+ if (recipient == standard_setup_recipients::kDevice) {
+ if (setup_packet.index != 0) {
+ break;
+ }
+ // Say that we're currently self powered.
+ endpoint0_transmit_buffer_[0] = 1;
+ endpoint0_transmit_buffer_[1] = 0;
+ QueueEndpoint0Data(endpoint0_transmit_buffer_, 2);
+ return;
+ }
+ if ((recipient == standard_setup_recipients::kInterface &&
+ setup_packet.index == 0) ||
+ (recipient == standard_setup_recipients::kEndpoint &&
+ G_SETUP_REQUEST_INDEX_ENDPOINT(setup_packet.index) == 0)) {
+ endpoint0_transmit_buffer_[0] = 0;
+ endpoint0_transmit_buffer_[1] = 0;
+ QueueEndpoint0Data(endpoint0_transmit_buffer_, 2);
+ return;
+ }
+ // Standard says it's unspecified in the default state and must
+ // respond with an error in the address state, so just do an error
+ // for both of them.
+ if (configuration_ == 0) {
+ break;
+ }
+
+ if (recipient == standard_setup_recipients::kInterface) {
+ // TODO(Brian): Check if it's actually an interface we have?
+ endpoint0_transmit_buffer_[0] = 0;
+ endpoint0_transmit_buffer_[1] = 0;
+ QueueEndpoint0Data(endpoint0_transmit_buffer_, 2);
+ return;
+ }
+
+ if (recipient == standard_setup_recipients::kEndpoint) {
+ const int endpoint =
+ G_SETUP_REQUEST_INDEX_ENDPOINT(setup_packet.index);
+ // TODO(Brian): Check if it's actually an endpoint we have?
+ if (USB0_ENDPTn(endpoint) & USB_ENDPT_EPSTALL) {
+ endpoint0_transmit_buffer_[0] = 1;
+ } else {
+ endpoint0_transmit_buffer_[0] = 0;
+ }
+ endpoint0_transmit_buffer_[1] = 0;
+ QueueEndpoint0Data(endpoint0_transmit_buffer_, 2);
+ return;
+ }
+ break;
+
+ case standard_setup_requests::kSetDescriptor:
+ // Not implementing anything for this.
+ break;
+
+ case standard_setup_requests::kSynchFrame:
+ // We don't implement any classes which use this.
+ break;
+
+ case standard_setup_requests::kGetDescriptor:
+ if (!in || recipient != standard_setup_recipients::kDevice) {
+ break;
+ }
+ const uint8_t descriptor_type_byte = (setup_packet.value >> 8) & 0xFF;
+ if (descriptor_type_byte < kUsbDescriptorTypeMin ||
+ descriptor_type_byte > kUsbDescriptorTypeMax) {
+ break;
+ }
+ const UsbDescriptorType descriptor_type =
+ static_cast<UsbDescriptorType>(descriptor_type_byte);
+ const uint8_t descriptor_index = setup_packet.value & 0xFF;
+ switch (descriptor_type) {
+ case UsbDescriptorType::kDevice:
+ if (setup_packet.index != 0 || descriptor_index != 0) {
+ break;
+ }
+ QueueEndpoint0Data(
+ device_descriptor_list_.data_.data(),
+ ::std::min<int>(setup_packet.length,
+ device_descriptor_list_.data_.size()));
+ return;
+
+ case UsbDescriptorType::kConfiguration:
+ if (setup_packet.index != 0 || descriptor_index != 0) {
+ break;
+ }
+ QueueEndpoint0Data(
+ config_descriptor_list_.data_.data(),
+ ::std::min<int>(setup_packet.length,
+ config_descriptor_list_.data_.size()));
+ return;
+
+ case UsbDescriptorType::kString:
+ if (descriptor_index != 0 && setup_packet.index != english_us_code()) {
+ break;
+ }
+ if (descriptor_index >= strings_.size()) {
+ break;
+ }
+ QueueEndpoint0Data(
+ strings_[descriptor_index].data(),
+ ::std::min<int>(setup_packet.length,
+ strings_[descriptor_index].size()));
+ return;
+
+ default:
+ // TODO(Brian): Handle other types of descriptor too.
+ break;
+ }
+ }
+ break;
+
+ default:
+ for (UsbFunction *function : functions_) {
+ switch (function->HandleEndpoint0SetupPacket(setup_packet)) {
+ case SetupResponse::kIgnored:
+ continue;
+ case SetupResponse::kHandled:
+ return;
+ case SetupResponse::kStall:
+ break;
+ }
+ break;
+ }
+ break;
+ }
+
+ StallEndpoint0();
+}
+
+// We're supposed to continue returning stalls until the next kSetup packet.
+// Code might continue putting stuff in the TX buffers, but the hardware won't
+// actually send it as long as the EPSTALL bit is set.
+void UsbDevice::StallEndpoint0() {
+ USB0_ENDPT0 = USB_ENDPT_EPSTALL | USB_ENDPT_EPRXEN | USB_ENDPT_EPTXEN |
+ USB_ENDPT_EPHSHK;
+}
+
+bool UsbDevice::BufferEndpoint0TxPacket() {
+ if (endpoint0_data_ == nullptr) {
+ return false;
+ }
+
+ const int to_transmit = ::std::min(endpoint0_data_left_, kEndpoint0MaxSize);
+ BdtEntry *const tx_bdt_entry =
+ MutableBdtEntry(0, Direction::kTx, endpoint0_tx_odd_);
+ // const_cast is safe because the hardware is only going to read from
+ // this, not write.
+ tx_bdt_entry->address =
+ const_cast<void *>(static_cast<const void *>(endpoint0_data_));
+ dma_memory_barrier();
+ tx_bdt_entry->buffer_descriptor =
+ V_USB_BD_BC(to_transmit) | static_cast<uint32_t>(endpoint0_tx_toggle_) |
+ M_USB_BD_OWN | M_USB_BD_DTS;
+
+ endpoint0_tx_odd_ = EvenOddInverse(endpoint0_tx_odd_);
+ endpoint0_tx_toggle_ = Data01Inverse(endpoint0_tx_toggle_);
+
+ endpoint0_data_ += to_transmit;
+ endpoint0_data_left_ -= to_transmit;
+ if (to_transmit < kEndpoint0MaxSize) {
+ endpoint0_data_ = nullptr;
+ }
+
+ return true;
+}
+
+void UsbDevice::SendEmptyEndpoint0Packet() {
+ // Really doesn't matter what we put here as long as it's not nullptr.
+ endpoint0_data_ = reinterpret_cast<char *>(this);
+ endpoint0_data_left_ = 0;
+ BufferEndpoint0TxPacket();
+}
+
+void UsbDevice::QueueEndpoint0Data(const char *data, int size) {
+ endpoint0_data_ = data;
+ endpoint0_data_left_ = size;
+ // There are 2 TX buffers, so fill them both up.
+ BufferEndpoint0TxPacket();
+ BufferEndpoint0TxPacket();
+}
+
+void UsbDevice::StallEndpoint(int endpoint) {
+ for (Direction direction : {Direction::kTx, Direction::kRx}) {
+ for (EvenOdd odd : {EvenOdd::kOdd, EvenOdd::kEven}) {
+ MutableBdtEntry(endpoint, direction, odd)->buffer_descriptor = 0;
+ dma_memory_barrier();
+ MutableBdtEntry(endpoint, direction, odd)->address = nullptr;
+ }
+ }
+ USB0_ENDPTn(endpoint) |= USB_ENDPT_EPSTALL;
+}
+
+void UsbDevice::ConfigureEndpointFor(int endpoint, bool rx, bool tx,
+ bool handshake) {
+ uint8_t control = 0;
+ if (rx) {
+ control |= USB_ENDPT_EPRXEN;
+ }
+ if (tx) {
+ control |= USB_ENDPT_EPTXEN;
+ }
+ if (handshake) {
+ control |= USB_ENDPT_EPHSHK;
+ }
+ USB0_ENDPTn(endpoint) = control;
+}
+
+int UsbFunction::AddEndpoint() {
+ const int r = device_->endpoint_mapping_.size();
+ assert(r < number_endpoints());
+ device_->endpoint_mapping_.push_back(this);
+ return r;
+}
+
+int UsbFunction::AddInterface() {
+ const int r = device_->interface_mapping_.size();
+ // bInterfaceNumber is only one byte.
+ assert(r < 255);
+ device_->interface_mapping_.push_back(this);
+ return r;
+}
+
+void UsbDevice::SetBdtEntry(int endpoint, Direction direction, EvenOdd odd,
+ BdtEntry bdt_entry) {
+ *MutableBdtEntry(endpoint, direction, odd) = bdt_entry;
+}
+
+} // namespace teensy
+} // namespace frc971