Add a working CDC TTY USB device
I'm pretty sure it reliably moves data in both directions, but it has a
lot of TODOs left wrt error handling.
Change-Id: I1d96a63eea7bdc08f6ec78c868c4a1ccbfbda600
diff --git a/motors/usb/cdc.h b/motors/usb/cdc.h
new file mode 100644
index 0000000..f7c7437
--- /dev/null
+++ b/motors/usb/cdc.h
@@ -0,0 +1,133 @@
+#ifndef MOTORS_USB_CDC_H_
+#define MOTORS_USB_CDC_H_
+
+#include <array>
+
+#include "motors/usb/usb.h"
+#include "motors/usb/queue.h"
+#include "motors/util.h"
+
+// CDC (Communications Device Class) is a "class standard" which takes 30 pages
+// to explain that a communications device has communications and data, and they
+// can come via separate interfaces or the same ones, and the data can be in a
+// lot of formats. The only commonality across the "class" seems to be some
+// constants and a few descriptors.
+
+namespace frc971 {
+namespace teensy {
+
+struct CdcLineCoding {
+ static constexpr uint8_t stop_bits_1() { return 0; }
+ static constexpr uint8_t stop_bits_1_5() { return 1; }
+ static constexpr uint8_t stop_bits_2() { return 2; }
+
+ static constexpr uint8_t parity_none() { return 0; }
+ static constexpr uint8_t parity_odd() { return 1; }
+ static constexpr uint8_t parity_even() { return 2; }
+ static constexpr uint8_t parity_mark() { return 3; }
+ static constexpr uint8_t parity_space() { return 4; }
+
+ // The baud rate in bits/second.
+ uint32_t rate; // dwDTERate
+
+ uint8_t stop_bits; // bCharFormat
+
+ uint8_t parity; // bParityType
+
+ // 5, 6, 7, 8, or 16 according to the standard.
+ uint8_t data_bits; // bDataBits
+} __attribute__((packed));
+static_assert(sizeof(CdcLineCoding) == 7, "wrong size");
+
+// Implements a pretty dumb serial port via CDC's ACM (Abstract Control
+// Management) and Call Management functions.
+class AcmTty final : public UsbFunction {
+ public:
+ AcmTty(UsbDevice *device) : UsbFunction(device) {}
+ ~AcmTty() override = default;
+
+ size_t Read(void *buffer, size_t buffer_size);
+ size_t Write(const void *buffer, size_t buffer_size);
+
+ private:
+ enum class NextEndpoint0Out {
+ kNone,
+ kLineCoding,
+ };
+
+ // We're going with the largest allowable sizes for full speed devices for
+ // the data endpoints to maximize throughput.
+ static constexpr uint16_t kDataMaxPacketSize = 64;
+
+ // We have no information to send here, so no point allocating bus bandwidth
+ // for it.
+ static constexpr uint16_t kStatusMaxPacketSize = 1;
+
+ ::std::array<::std::array<char, kDataMaxPacketSize>, 2> tx_buffers_,
+ rx_buffers_;
+
+ void Initialize() override;
+
+ SetupResponse HandleEndpoint0SetupPacket(
+ const UsbDevice::SetupPacket &setup_packet) override;
+ SetupResponse HandleEndpoint0OutPacket(void *data, int data_length) override;
+ void HandleOutFinished(int endpoint, BdtEntry *bdt_entry) override;
+ void HandleInFinished(int endpoint, BdtEntry *bdt_entry,
+ EvenOdd odd) override;
+ void HandleConfigured(int endpoint) override;
+ void HandleReset() override {
+ // TODO(Brian): Handle data already in the buffers correctly.
+ DisableInterrupts disable_interrupts;
+ tx_state_ = EndpointBufferState::kBothEmptyEvenFirst;
+ device()->SetBdtEntry(status_endpoint_, Direction::kTx, EvenOdd::kEven,
+ {0, nullptr});
+ device()->SetBdtEntry(status_endpoint_, Direction::kTx, EvenOdd::kOdd,
+ {0, nullptr});
+ device()->SetBdtEntry(data_tx_endpoint_, Direction::kTx, EvenOdd::kEven,
+ {0, nullptr});
+ device()->SetBdtEntry(data_tx_endpoint_, Direction::kTx, EvenOdd::kOdd,
+ {0, nullptr});
+ device()->SetBdtEntry(data_rx_endpoint_, Direction::kRx, EvenOdd::kEven,
+ {0, nullptr});
+ device()->SetBdtEntry(data_rx_endpoint_, Direction::kRx, EvenOdd::kOdd,
+ {0, nullptr});
+ }
+
+ char *tx_buffer_for(EvenOdd odd) {
+ return tx_buffers_[EvenOddIndex(odd)].data();
+ }
+
+ void EnqueueTxData(const DisableInterrupts &);
+
+ // In theory, we could sent notifications over this about things like the line
+ // state, but we don't have anything to report so we pretty much just ignore
+ // this.
+ int status_interface_;
+ int data_interface_;
+ int status_endpoint_, data_tx_endpoint_, data_rx_endpoint_;
+
+ Queue tx_queue_{1024}, rx_queue_{1024};
+
+ NextEndpoint0Out next_endpoint0_out_ = NextEndpoint0Out::kNone;
+
+ // This is only manipulated with interrupts disabled.
+ EndpointBufferState tx_state_;
+ Data01 next_tx_toggle_;
+
+ // These are BdtEntries which we're holding into without releasing back to the
+ // hardware so that we won't receive any more data until we have space for it.
+ // They are only manipulated with interrupts disabled.
+ BdtEntry *first_rx_held_ = nullptr, *second_rx_held_ = nullptr;
+ Data01 next_rx_toggle_;
+
+ CdcLineCoding line_coding_{0, CdcLineCoding::stop_bits_1(),
+ CdcLineCoding::parity_none(), 8};
+ CdcLineCoding line_coding_to_send_;
+
+ uint16_t control_line_state_ = 0;
+};
+
+} // namespace teensy
+} // namespace frc971
+
+#endif // MOTORS_USB_CDC_H_