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.h b/motors/usb/usb.h
new file mode 100644
index 0000000..1322ce1
--- /dev/null
+++ b/motors/usb/usb.h
@@ -0,0 +1,506 @@
+#ifndef MOTORS_USB_USB_H_
+#define MOTORS_USB_USB_H_
+
+#include <assert.h>
+#include <string>
+#include <vector>
+#include <memory>
+
+#include "aos/common/macros.h"
+#include "motors/core/kinetis.h"
+#include "motors/usb/constants.h"
+
+namespace frc971 {
+namespace teensy {
+
+// A sufficient memory barrier between writing some data and telling the USB
+// hardware to read it or having the USB hardware say some data is readable and
+// actually reading it.
+static inline void dma_memory_barrier() {
+ __asm__ __volatile__("" :: : "memory");
+}
+
+// Aligned for faster access via memcpy etc.
+typedef void *DataPointer __attribute__((aligned(4)));
+
+// An entry in the Buffer Descriptor Table.
+struct BdtEntry {
+ uint32_t buffer_descriptor;
+ DataPointer address;
+};
+
+#define V_USB_BD_BC(value) \
+ static_cast<uint32_t>(static_cast<uint32_t>(value) << 16)
+#define G_USB_BD_BC(bd) (((bd) >> 16) & UINT32_C(0x3FF))
+#define M_USB_BD_OWN UINT32_C(1 << 7)
+#define M_USB_BD_DATA1 UINT32_C(1 << 6)
+static_assert(static_cast<uint32_t>(Data01::kData1) == M_USB_BD_DATA1,
+ "Wrong value");
+#define M_USB_BD_KEEP UINT32_C(1 << 5)
+#define M_USB_BD_NINC UINT32_C(1 << 4)
+#define M_USB_BD_DTS UINT32_C(1 << 3)
+#define M_USB_BD_STALL UINT32_C(1 << 2)
+#define V_USB_BD_PID(value) \
+ static_cast<uint32_t>(static_cast<uint32_t>(value) << 2)
+#define G_USB_BD_PID(bd) static_cast<UsbPid>(((bd) >> 2) & UINT32_C(0xF))
+
+#define G_USB_STAT_ENDP(stat) (((stat) >> 4) & UINT32_C(0xF))
+#define M_USB_STAT_TX UINT32_C(1 << 3)
+#define M_USB_STAT_ODD UINT32_C(1 << 2)
+
+// The various types of descriptors defined in the standard for retrieval via
+// GetDescriptor.
+static constexpr uint8_t kUsbDescriptorTypeMin = 1;
+static constexpr uint8_t kUsbDescriptorTypeMax = 11;
+enum class UsbDescriptorType : uint8_t {
+ kDevice = 1,
+ kConfiguration = 2,
+ kString = 3,
+ kInterface = 4,
+ kEndpoint = 5,
+ kDeviceQualifier = 6,
+ kOtherSpeedConfiguration = 7,
+ kInterfacePower = 8,
+ kOtg = 9,
+ kDebug = 10,
+ kInterfaceAssociation = 11,
+};
+
+// The class-specific descriptor types.
+enum class UsbClassDescriptorType : uint8_t {
+ kDevice = 0x21,
+ kConfiguration = 0x22,
+ kString = 0x23,
+ kInterface = 0x24,
+ kEndpoint = 0x25,
+};
+
+// The names of the setup request types from the standard.
+enum class SetupRequestType {
+ kStandard = 0,
+ kClass = 1,
+ kVendor = 2,
+ kReserved = 3,
+};
+
+// Set means device-to-host, clear means host-to-device.
+#define M_SETUP_REQUEST_TYPE_IN UINT8_C(1 << 7)
+#define G_SETUP_REQUEST_TYPE_TYPE(type) \
+ static_cast<SetupRequestType>(((type) >> 5) & UINT8_C(3))
+#define G_SETUP_REQUEST_TYPE_RECIPIENT(type) ((type)&UINT8_C(0x1F))
+#define G_SETUP_REQUEST_INDEX_ENDPOINT(index) ((index)&UINT8_C(0x7F))
+
+// The names of the standard recipients for setup requests.
+namespace standard_setup_recipients {
+constexpr int kDevice = 0;
+constexpr int kInterface = 1;
+constexpr int kEndpoint = 2;
+constexpr int kOther = 3;
+} // namespace standard_setup_recipients
+
+class UsbFunction;
+
+// Allows building up a list of descriptors. This supports a much nicer API than
+// the usual "hard-code a char[] with all the sizes and offsets at compile
+// time". Space for each descriptor is reserved, and then it may be filled out
+// from beginning to end at any time.
+//
+// An instance is the thing that the GetDescriptor operation sends to the host.
+// This is not the concept that the core and class standards call "Foo
+// Descriptor" etc; see Descriptor for that.
+class UsbDescriptorList {
+ public:
+ // Represents a single descriptor. All of the contents must be written before
+ // this object is destroyed.
+ //
+ // Create one via UsbDescriptorList::CreateDescriptor.
+ class Descriptor {
+ public:
+ // All of the allocated space must be filled first.
+ ~Descriptor() {
+ if (descriptor_list_ == nullptr) {
+ return;
+ }
+ // Verify we wrote all the bytes first.
+ assert(next_index_ == end_index_);
+ --descriptor_list_->open_descriptors_;
+ }
+
+ void AddUint16(uint16_t value) {
+ AddByte(value & 0xFF);
+ AddByte((value >> 8) & 0xFF);
+ }
+
+ void AddByte(uint8_t value) {
+ assert(next_index_ < end_index_);
+ data()[next_index_] = value;
+ ++next_index_;
+ }
+
+ // Overwrites an already-written byte.
+ void SetByte(int index, uint8_t value) {
+ assert(index + start_index_ < end_index_);
+ data()[index + start_index_] = value;
+ }
+
+ private:
+ Descriptor(UsbDescriptorList *descriptor_list, int start_index,
+ int end_index)
+ : descriptor_list_(descriptor_list),
+ start_index_(start_index),
+ end_index_(end_index),
+ next_index_(start_index_) {}
+
+ char *data() const {
+ return &descriptor_list_->data_[0];
+ }
+
+ UsbDescriptorList *const descriptor_list_;
+ const int start_index_, end_index_;
+ int next_index_;
+
+ friend class UsbDescriptorList;
+
+ DISALLOW_COPY_AND_ASSIGN(Descriptor);
+ };
+
+ UsbDescriptorList() = default;
+ ~UsbDescriptorList() = default;
+
+ // Creates a new descriptor at the end of the list.
+ // length is the number of bytes, including the length byte.
+ // descriptor_type is the descriptor type, which is the second byte after the
+ // length.
+ ::std::unique_ptr<Descriptor> CreateDescriptor(
+ uint8_t length, UsbDescriptorType descriptor_type) {
+ return CreateDescriptor(length, static_cast<uint8_t>(descriptor_type));
+ }
+
+ ::std::unique_ptr<Descriptor> CreateDescriptor(
+ uint8_t length, UsbClassDescriptorType descriptor_type) {
+ assert(data_.size() > 0);
+ return CreateDescriptor(length, static_cast<uint8_t>(descriptor_type));
+ }
+
+ void CheckFinished() const { assert(open_descriptors_ == 0); }
+
+ int CurrentSize() const { return data_.size(); }
+
+ private:
+ ::std::unique_ptr<Descriptor> CreateDescriptor(uint8_t length,
+ uint8_t descriptor_type) {
+ const int start_index = data_.size();
+ const int end_index = start_index + length;
+ data_.resize(end_index);
+ ++open_descriptors_;
+ auto r = ::std::unique_ptr<Descriptor>(
+ new Descriptor(this, start_index, end_index));
+ r->AddByte(length); // bLength
+ r->AddByte(descriptor_type); // bDescriptorType
+ return r;
+ }
+
+ int open_descriptors_ = 0;
+
+ ::std::string data_;
+
+ friend class UsbDevice;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbDescriptorList);
+};
+
+extern "C" void usb_isr(void);
+
+// USB state events are managed by asking each function if it wants to handle
+// them, sequentially. For the small number of functions which can be
+// practically supported with the limited number of endpoints, this performs
+// better than fancier things like hash maps.
+
+// Manages one of the Teensy's USB peripherals as a USB slave device.
+//
+// This supports being a composite device with multiple functions.
+//
+// Attaching functions etc is called "setup", and must be completed before
+// Initialize() is called.
+//
+// Detaching functions is called "teardown" and must happen after Shutdown().
+// TODO(Brian): Implement Shutdown().
+class UsbDevice final {
+ public:
+ // Represents the data that comes with a UsbPid::kSetup.
+ // Note that the order etc is important because we memcpy into this.
+ struct SetupPacket {
+ uint8_t request_type; // bmRequestType
+ uint8_t request; // bRequest
+ uint16_t value; // wValue
+ uint16_t index; // wIndex
+ uint16_t length; // wLength
+ } __attribute__((aligned(4)));
+ static_assert(sizeof(SetupPacket) == 8, "wrong size");
+
+ enum class SetupResponse {
+ // Indicates this function doesn't recognize the setup packet.
+ kIgnored,
+
+ // Indicates the endpoint should be stalled.
+ //
+ // Don't return this if the packet is for another function.
+ kStall,
+
+ // Indicates this setup packet was handled. Functions must avoid eating
+ // packets intended for other functions.
+ kHandled,
+ };
+
+ static constexpr int kEndpoint0MaxSize = 64;
+
+ // The only language code we support.
+ static constexpr uint16_t english_us_code() { return 0x0409; }
+
+ UsbDevice(int index, uint16_t vendor_id, uint16_t product_id);
+ ~UsbDevice();
+
+ // Ends setup and starts being an actual USB device.
+ void Initialize();
+
+ // Adds a string to the table and returns its index.
+ //
+ // For simplicity, we only support strings with english_us_code().
+ //
+ // May only be called during setup.
+ int AddString(const ::std::string &string) {
+ assert(!is_set_up_);
+ const int r = strings_.size();
+ strings_.emplace_back(string.size() * 2 + 2, '\0');
+ strings_.back()[0] = 2 + string.size() * 2;
+ strings_.back()[1] = static_cast<uint8_t>(UsbDescriptorType::kString);
+ for (size_t i = 0; i < string.size(); ++i) {
+ strings_.back()[i * 2 + 2] = string[i];
+ }
+ return r;
+ }
+
+ // Sets the manufacturer string.
+ //
+ // May only be called during setup.
+ void SetManufacturer(const ::std::string &string) {
+ device_descriptor_->SetByte(14, AddString(string)); // iManufacturer
+ }
+
+ // Sets the product string.
+ //
+ // May only be called during setup.
+ void SetProduct(const ::std::string &string) {
+ device_descriptor_->SetByte(15, AddString(string)); // iProduct
+ }
+
+ // Sets the serial number string.
+ //
+ // May only be called during setup.
+ void SetSerialNumber(const ::std::string &string) {
+ device_descriptor_->SetByte(16, AddString(string)); // iSerialNumber
+ }
+
+ // Queues up an empty IN packet for endpoint 0. This is a common way to
+ // respond to various kinds of configuration commands.
+ //
+ // This may only be called from the appropriate function callbacks.
+ void SendEmptyEndpoint0Packet();
+
+ // Queues some data to send on endpoint 0. This includes putting the initial
+ // packets into the TX buffers.
+ //
+ // This may only be called from the appropriate function callbacks.
+ void QueueEndpoint0Data(const char *data, int size);
+
+ // Stalls an endpoint until it's cleared.
+ //
+ // This should only be called by or on behalf of the function which owns
+ // endpoint.
+ void StallEndpoint(int endpoint);
+
+ // Configures an endpoint to send and/or receive, with or without DATA0/DATA1
+ // handshaking. handshake should probably be true for everything except
+ // isochronous endpoints.
+ //
+ // This should only be called by or on behalf of the function which owns
+ // endpoint.
+ void ConfigureEndpointFor(int endpoint, bool rx, bool tx, bool handshake);
+
+ void SetBdtEntry(int endpoint, Direction direction, EvenOdd odd,
+ BdtEntry bdt_entry);
+
+ private:
+ // Clears all pending interrupts.
+ void ClearInterrupts();
+
+ // Deals with an interrupt that has occured.
+ void HandleInterrupt();
+
+ // Processes a token on endpoint 0.
+ void HandleEndpoint0Token(uint8_t stat);
+
+ // Processes a setup packet on endpoint 0.
+ void HandleEndpoint0SetupPacket(const SetupPacket &setup_packet);
+
+ // Sets endpoint 0 to return STALL tokens. We clear this condition upon
+ // receiving the next SETUP token.
+ void StallEndpoint0();
+
+ // Places the first packet from {endpoint0_data_, endpoint0_data_left_} into
+ // the TX buffers (if there is any data). This may only be called when the
+ // next TX buffer is empty.
+ bool BufferEndpoint0TxPacket();
+
+ // Which USB peripheral this is.
+ const int index_;
+
+ // The string descriptors in order.
+ ::std::vector<::std::string> strings_;
+
+ // TODO(Brian): Refactor into something more generic, because I think this is
+ // shared with all non-isochronous endpoints?
+ Data01 endpoint0_tx_toggle_;
+ EvenOdd endpoint0_tx_odd_;
+ uint8_t endpoint0_receive_buffer_[2][kEndpoint0MaxSize]
+ __attribute__((aligned(4)));
+
+ // A temporary buffer for holding data to transmit on endpoint 0. Sometimes
+ // this is used and sometimes the data is sent directly from some other
+ // location (like for descriptors).
+ char endpoint0_transmit_buffer_[kEndpoint0MaxSize];
+
+ // The data we're waiting to send from endpoint 0. The data must remain
+ // constant until this transmission is done.
+ //
+ // When overwriting this, we ignore if it's already non-nullptr. The host is
+ // supposed to read all of the data before asking for more. If it doesn't do
+ // that, it will just get garbage data because it's unclear what it expects.
+ //
+ // Do note that endpoint0_data_ != nullptr && endpoint0_data_left_ == 0 is an
+ // important state. This means we're going to return a 0-length packet the
+ // next time the host asks. However, depending on the length it asked for,
+ // that might never happen.
+ const char *endpoint0_data_ = nullptr;
+ int endpoint0_data_left_ = 0;
+
+ // If non-0, the new address we're going to start using once the status stage
+ // of the current setup request is finished.
+ uint16_t new_address_ = 0;
+
+ UsbDescriptorList device_descriptor_list_;
+ UsbDescriptorList config_descriptor_list_;
+
+ ::std::unique_ptr<UsbDescriptorList::Descriptor> device_descriptor_,
+ config_descriptor_;
+
+ int configuration_ = 0;
+
+ bool is_set_up_ = false;
+
+ // The function which owns each endpoint.
+ ::std::vector<UsbFunction *> endpoint_mapping_;
+ // The function which owns each interface.
+ ::std::vector<UsbFunction *> interface_mapping_;
+ // All of the functions (without duplicates).
+ ::std::vector<UsbFunction *> functions_;
+
+ friend void usb_isr(void);
+ friend class UsbFunction;
+};
+
+// Represents a USB function. This consists of a set of descriptors and
+// interfaces.
+//
+// Each instance is a single function, so there can be multiple instances of the
+// same subclass in the same devices (ie two serial ports).
+class UsbFunction {
+ public:
+ UsbFunction(UsbDevice *device) : device_(device) {
+ device_->functions_.push_back(this);
+ }
+ virtual ~UsbFunction() = default;
+
+ protected:
+ using SetupResponse = UsbDevice::SetupResponse;
+
+ static constexpr uint8_t iad_descriptor_length() { return 8; }
+ static constexpr uint8_t interface_descriptor_length() { return 9; }
+ static constexpr uint8_t endpoint_descriptor_length() { return 7; }
+
+ static constexpr uint8_t m_endpoint_address_in() { return 1 << 7; }
+ static constexpr uint8_t m_endpoint_attributes_control() { return 0x00; }
+ static constexpr uint8_t m_endpoint_attributes_isochronous() { return 0x01; }
+ static constexpr uint8_t m_endpoint_attributes_bulk() { return 0x03; }
+ static constexpr uint8_t m_endpoint_attributes_interrupt() { return 0x03; }
+
+ // Adds a new endpoint and returns its index.
+ //
+ // Note that at least one descriptor for this newly created endpoint must be
+ // added via CreateConfigDescriptor.
+ //
+ // TODO(Brian): Does this hardware actually only support a single direction
+ // per endpoint number, or can it get a total of 30 endpoints max?
+ //
+ // May only be called during setup.
+ int AddEndpoint();
+
+ // Adds a new interface and returns its index.
+ //
+ // You'll probably want to put this new interface in at least one descriptor
+ // added via CreateConfigDescriptor.
+ //
+ // May only be called during setup.
+ int AddInterface();
+
+ // Adds a new descriptor in the configuration descriptor list. See
+ // UsbDescriptorList::CreateDescriptor for details.
+ //
+ // Note that the order of calls to this is highly significant. In general,
+ // this should only be called from Initialize().
+ //
+ // May only be called during setup.
+ template <typename T>
+ ::std::unique_ptr<UsbDescriptorList::Descriptor> CreateDescriptor(
+ uint8_t length, T descriptor_type) {
+ return device_->config_descriptor_list_.CreateDescriptor(length,
+ descriptor_type);
+ }
+
+ UsbDevice *device() const { return device_; }
+
+ private:
+ virtual void Initialize() = 0;
+
+ virtual SetupResponse HandleEndpoint0SetupPacket(
+ const UsbDevice::SetupPacket & /*setup_packet*/) {
+ return SetupResponse::kIgnored;
+ }
+
+ virtual SetupResponse HandleEndpoint0OutPacket(void * /*data*/,
+ int /*data_length*/) {
+ return SetupResponse::kIgnored;
+ }
+
+ virtual void HandleOutFinished(int endpoint, BdtEntry *bdt_entry) = 0;
+ virtual void HandleInFinished(int endpoint, BdtEntry *bdt_entry,
+ EvenOdd odd) = 0;
+
+ // Called when a given interface is configured (aka "experiences a
+ // configuration event"). This means all rx and tx buffers have been cleared
+ // and should be filled as appropriate, starting from data0. Also,
+ // ConfigureEndpointFor should be called with the appropriate arguments.
+ virtual void HandleConfigured(int endpoint) = 0;
+
+ // Should reset everything to use the even buffers next.
+ virtual void HandleReset() = 0;
+
+ UsbDevice *const device_;
+
+ friend class UsbDevice;
+};
+
+} // namespace teensy
+} // namespace frc971
+
+#endif // MOTORS_USB_USB_H_