blob: c02873d28c5ba634a295220d99d9c9b69ab11d5d [file] [log] [blame]
#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);
bool write_queue_empty() const { return tx_queue_.empty(); }
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;
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 &);
::std::array<::std::array<char, kDataMaxPacketSize>, 2> tx_buffers_,
rx_buffers_;
// 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 onto 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_