Merge "Add footprints and packages I forgot for the LED board"
diff --git a/WORKSPACE b/WORKSPACE
index c896d86..f220d3c 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -105,3 +105,19 @@
path = '/usr',
build_file = 'debian/libusb.BUILD',
)
+
+# Created by combining libusb-1.0-0_2%3a1.0.19-1_amd64,
+# libusb-1.0-0-dev_2%3a1.0.19-1, and libudev1_215-17+deb8u7.
+new_http_archive(
+ name = 'libusb_1_0',
+ build_file = 'debian/libusb-1.0.BUILD',
+ url = 'http://frc971.org/Build-Dependencies/libusb-1.0-1.0.19.tar.xz',
+ sha256 = '12acb30faacd10e9aa7f3a5e074701e167ce9bbd45694db37d13d55de5398816',
+)
+
+# Recompressed from libusb-1.0.21.7z.
+http_file(
+ name = 'libusb_1_0_windows',
+ url = 'http://frc971.org/Build-Dependencies/libusb-1.0.21-windows.tar.xz',
+ sha256 = 'fc2ba03992f343aabbaf9eb90559c6e00cdc6a2bd914d7cebea85857d5244015',
+)
diff --git a/debian/libusb-1.0.BUILD b/debian/libusb-1.0.BUILD
new file mode 100644
index 0000000..a307cec
--- /dev/null
+++ b/debian/libusb-1.0.BUILD
@@ -0,0 +1,14 @@
+cc_library(
+ name = 'libusb_1_0',
+ visibility = ['//visibility:public'],
+ hdrs = [
+ 'usr/include/libusb-1.0/libusb.h',
+ ],
+ srcs = [
+ 'usr/lib/x86_64-linux-gnu/libusb-1.0.so',
+ ],
+ includes = [
+ 'usr/include',
+ ],
+ restricted_to = ['@//tools:k8'],
+)
diff --git a/motors/pistol_grip/BUILD b/motors/pistol_grip/BUILD
index b1239c5..9eda585 100644
--- a/motors/pistol_grip/BUILD
+++ b/motors/pistol_grip/BUILD
@@ -8,10 +8,12 @@
],
deps = [
'//motors:util',
+ '//motors/peripheral:can',
'//motors/core',
'//motors/usb',
'//motors/usb:cdc',
'//motors/usb:hid',
+ '//motors/usb:interrupt_out',
],
restricted_to = mcu_cpus,
)
@@ -20,3 +22,37 @@
name = 'drivers_station',
restricted_to = mcu_cpus,
)
+
+cc_binary(
+ name = 'usb_forward_linux',
+ srcs = [
+ 'usb_forward.cc',
+ ],
+ deps = [
+ # Don't add anything else here. :usb_forward_windows still has to build it
+ # without any other dependencies.
+ '@libusb_1_0',
+ ],
+ restricted_to = ['//tools:k8'],
+)
+
+genrule(
+ name = 'usb_forward_windows',
+ outs = [
+ 'usb_forward.exe',
+ ],
+ srcs = [
+ 'usb_forward.cc',
+ '@libusb_1_0_windows//file',
+ ],
+ tools = [
+ 'usb_forward_windows_build.sh',
+ ],
+ cmd = ' '.join([
+ '$(location usb_forward_windows_build.sh)',
+ '$(location usb_forward.cc)',
+ '$(location @libusb_1_0_windows//file)',
+ '$@',
+ ]),
+ output_to_bindir = True,
+)
diff --git a/motors/pistol_grip/drivers_station.cc b/motors/pistol_grip/drivers_station.cc
index ecc5347..4c68afc 100644
--- a/motors/pistol_grip/drivers_station.cc
+++ b/motors/pistol_grip/drivers_station.cc
@@ -6,9 +6,11 @@
#include "motors/core/time.h"
#include "motors/core/kinetis.h"
+#include "motors/peripheral/can.h"
#include "motors/usb/usb.h"
#include "motors/usb/cdc.h"
#include "motors/usb/hid.h"
+#include "motors/usb/interrupt_out.h"
#include "motors/util.h"
namespace frc971 {
@@ -97,10 +99,20 @@
}
}
-void MoveJoysticks(teensy::HidFunction *joysticks) {
+void MoveJoysticks(teensy::HidFunction *joysticks,
+ teensy::InterruptOut *interrupt_out) {
static uint8_t x_axis = 0, y_axis = 97, z_axis = 5, rz_axis = 8;
while (true) {
{
+ char buffer[64];
+ if (interrupt_out->ReceiveData(buffer) > 0) {
+ if (buffer[0]) {
+ GPIOC_PCOR = 1 << 5;
+ } else {
+ GPIOC_PSOR = 1 << 5;
+ }
+ }
+
DisableInterrupts disable_interrupts;
uint8_t buttons = 0;
if (x_axis % 8u) {
@@ -118,6 +130,29 @@
}
}
+void ForwardJoystickData(teensy::HidFunction *joysticks) {
+ uint32_t last_command_time = micros();
+ while (true) {
+ uint8_t data[8];
+ int length;
+ can_receive_command(data, &length);
+ if (length == 3) {
+ last_command_time = micros();
+ DisableInterrupts disable_interrupts;
+ joysticks->UpdateReport(data, 3, disable_interrupts);
+ }
+
+ static constexpr uint32_t kTimeout = 10000;
+ if (!time_after(time_add(last_command_time, kTimeout), micros())) {
+ // Avoid wrapping back into the valid range.
+ last_command_time = time_subtract(micros(), kTimeout);
+ uint8_t zeros[] = {0, 0, 0x80};
+ DisableInterrupts disable_interrupts;
+ joysticks->UpdateReport(zeros, 3, disable_interrupts);
+ }
+ }
+}
+
} // namespace
extern "C" {
@@ -154,22 +189,25 @@
GPIO_BITBAND(GPIOC_PDDR, 5) = 1;
PORTC_PCR5 = PORT_PCR_DSE | PORT_PCR_MUX(1);
+ // Set up the CAN pins.
+ PORTA_PCR12 = PORT_PCR_DSE | PORT_PCR_MUX(2);
+ PORTA_PCR13 = PORT_PCR_DSE | PORT_PCR_MUX(2);
+
delay(100);
- teensy::UsbDevice usb_device(0, 0x16c0, 0x0492);
+ teensy::UsbDevice usb_device(0, 0x16c0, 0x0491);
usb_device.SetManufacturer("FRC 971 Spartan Robotics");
usb_device.SetProduct("Pistol Grip Controller interface");
- teensy::HidFunction joysticks(&usb_device, 10);
- // TODO(Brian): Figure out why Windows refuses to recognize the joystick along
- // with a TTY or two.
-#if 0
teensy::AcmTty tty1(&usb_device);
teensy::AcmTty tty2(&usb_device);
+ teensy::HidFunction joysticks(&usb_device, 10);
+ teensy::InterruptOut interrupt_out(&usb_device, "JoystickForce");
global_stdout.store(&tty2, ::std::memory_order_release);
-#endif
usb_device.Initialize();
- MoveJoysticks(&joysticks);
+ can_init();
+
+ MoveJoysticks(&joysticks, &interrupt_out);
return 0;
}
diff --git a/motors/pistol_grip/usb_forward.cc b/motors/pistol_grip/usb_forward.cc
new file mode 100644
index 0000000..830f8f7
--- /dev/null
+++ b/motors/pistol_grip/usb_forward.cc
@@ -0,0 +1,165 @@
+// This file is designed to be compiled for Windows to run on the driver's
+// station. It forwards UDP packets to the pistol grip controller over USB.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <string.h>
+
+#ifdef __WINNT__
+#include <winsock2.h>
+
+#define PRId8 "hhd"
+#define PRIx8 "hhx"
+#else
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+
+#include <string>
+
+#include "libusb-1.0/libusb.h"
+
+#define CHECK_LIBUSB(expression) \
+ ({ \
+ const int result = (expression); \
+ if (result < 0) { \
+ fprintf(stderr, #expression " failed with %d: %s\n", result, \
+ libusb_error_name(result)); \
+ abort(); \
+ } \
+ result; \
+ })
+
+#define CHECK(expression) \
+ do { \
+ if (!(expression)) { \
+ fprintf(stderr, #expression " failed\n"); \
+ abort(); \
+ } \
+ } while (false)
+
+#define PCHECK(expression) \
+ ({ \
+ const int result = (expression); \
+ if (result == -1) { \
+ fprintf(stderr, #expression " failed with %d: %s\n", result, \
+ strerror(result)); \
+ abort(); \
+ } \
+ result; \
+ })
+
+namespace {
+
+void OpenDevice(libusb_device_handle **handle) {
+ libusb_device **devices;
+ const ssize_t count = CHECK_LIBUSB(libusb_get_device_list(nullptr, &devices));
+ for (ssize_t i = 0; i < count; ++i) {
+ libusb_device_descriptor device_descriptor;
+ libusb_device *const device = devices[i];
+ CHECK_LIBUSB(libusb_get_device_descriptor(device, &device_descriptor));
+ if (device_descriptor.idVendor == 0x16C0 &&
+ device_descriptor.idProduct == 0x0491) {
+ // TODO(Brian): Check for the right interface too, instead of relying on
+ // us choosing different PIDs for each kind of Teensy.
+ fprintf(stderr, "Found device at %" PRId8 ":%" PRId8 "\n",
+ libusb_get_bus_number(device), libusb_get_port_number(device));
+ CHECK_LIBUSB(libusb_open(device, handle));
+ libusb_free_device_list(devices, true);
+ return;
+ }
+ }
+ fprintf(stderr, "Could not find device\n");
+ abort();
+}
+
+void FindEndpoint(libusb_device_handle *handle, uint8_t *endpoint) {
+ libusb_device *const device = libusb_get_device(handle);
+
+ libusb_config_descriptor *config_descriptor;
+ CHECK_LIBUSB(libusb_get_config_descriptor(device, 0, &config_descriptor));
+
+ for (uint8_t i = 0; i < config_descriptor->bNumInterfaces; ++i) {
+ const libusb_interface *const interface = &config_descriptor->interface[i];
+ CHECK(interface->num_altsetting == 1);
+ const libusb_interface_descriptor *const interface_descriptor =
+ &interface->altsetting[0];
+ if (interface_descriptor->bInterfaceClass == 0xFF &&
+ interface_descriptor->bInterfaceSubClass == 0x97 &&
+ interface_descriptor->bInterfaceProtocol == 0x97) {
+ CHECK(interface_descriptor->bNumEndpoints == 1);
+ const libusb_endpoint_descriptor *const endpoint_descriptor =
+ &interface_descriptor->endpoint[0];
+ CHECK_LIBUSB(libusb_claim_interface(
+ handle, interface_descriptor->bInterfaceNumber));
+ fprintf(stderr, "Found endpoint 0x%" PRIx8 "\n",
+ endpoint_descriptor->bEndpointAddress);
+ *endpoint = endpoint_descriptor->bEndpointAddress;
+ libusb_free_config_descriptor(config_descriptor);
+ return;
+ }
+ }
+
+ fprintf(stderr, "Could not find interface\n");
+ abort();
+}
+
+constexpr uint16_t port_number() { return 10971; }
+
+} // namespace
+
+int main() {
+ CHECK_LIBUSB(libusb_init(nullptr));
+ libusb_set_debug(nullptr, LIBUSB_LOG_LEVEL_INFO);
+
+ libusb_set_debug(nullptr, LIBUSB_LOG_LEVEL_DEBUG);
+
+#ifdef __WINNT__
+ {
+ WSADATA wsa;
+ PCHECK(WSAStartup(MAKEWORD(2, 2), &wsa));
+ }
+#endif
+
+ libusb_device_handle *handle;
+ OpenDevice(&handle);
+ uint8_t endpoint;
+ FindEndpoint(handle, &endpoint);
+
+ const int socket_fd = PCHECK(socket(AF_INET, SOCK_DGRAM, 0));
+ {
+ sockaddr_in bind_address;
+ memset(&bind_address, 0, sizeof(bind_address));
+ bind_address.sin_family = AF_INET;
+ bind_address.sin_addr.s_addr = htonl(INADDR_ANY);
+ bind_address.sin_port = htons(port_number());
+ PCHECK(bind(socket_fd, reinterpret_cast<sockaddr *>(&bind_address),
+ sizeof(bind_address)));
+ }
+
+ while (true) {
+ char buffer[64];
+#ifdef __WINNT__
+ // TODO(Brian): Figure out how to detect too-large UDP packets.
+ static constexpr int kFlags = 0;
+#else
+ static constexpr int kFlags = MSG_TRUNC;
+#endif
+ const int length = PCHECK(recv(socket_fd, buffer, sizeof(buffer), kFlags));
+ if (static_cast<size_t>(length) > sizeof(buffer)) {
+ fprintf(stderr, "Too-long packet of %d: ignoring\n", length);
+ continue;
+ }
+
+ int transferred;
+ CHECK_LIBUSB(libusb_interrupt_transfer(
+ handle, endpoint, reinterpret_cast<unsigned char *>(buffer), length,
+ &transferred, 100));
+ if (transferred != length) {
+ fprintf(stderr, "Transferred %d instead of %d\n", transferred, length);
+ }
+ }
+}
diff --git a/motors/pistol_grip/usb_forward_windows_build.sh b/motors/pistol_grip/usb_forward_windows_build.sh
new file mode 100755
index 0000000..3f1022b
--- /dev/null
+++ b/motors/pistol_grip/usb_forward_windows_build.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+# This script cross-compiles usb_forward for Windows.
+
+set -e
+set -u
+set -o pipefail
+
+check_installed() {
+ if ! dpkg -l "$1" > /dev/null 2>&1 ; then
+ echo "Must install $1" >&2
+ exit 1
+ fi
+}
+
+check_installed g++-mingw-w64-x86-64
+
+readonly SOURCE_FILE="$1"
+readonly LIBUSB_ARCHIVE="$2"
+readonly OUTPUT="$3"
+
+readonly TEMP="${OUTPUT}.build"
+rm -rf "${TEMP}"
+mkdir -p "${TEMP}/libusb"
+tar xJf "${LIBUSB_ARCHIVE}" -C "${TEMP}/libusb"
+
+x86_64-w64-mingw32-g++-posix \
+ --std=gnu++11 \
+ -D__USE_MINGW_ANSI_STDIO \
+ -o "${OUTPUT}" \
+ -I "${TEMP}/libusb/include" \
+ "${SOURCE_FILE}" \
+ -static -static-libgcc -static-libstdc++ \
+ -lws2_32 \
+ -L "${TEMP}/libusb/MinGW64/static" -lusb-1.0
diff --git a/motors/usb/BUILD b/motors/usb/BUILD
index 7d43447..c0600bf 100644
--- a/motors/usb/BUILD
+++ b/motors/usb/BUILD
@@ -63,6 +63,23 @@
)
cc_library(
+ name = 'interrupt_out',
+ visibility = ['//visibility:public'],
+ hdrs = [
+ 'interrupt_out.h',
+ ],
+ srcs = [
+ 'interrupt_out.cc',
+ ],
+ deps = [
+ ':usb',
+ '//motors/core',
+ '//motors:util',
+ ],
+ restricted_to = mcu_cpus,
+)
+
+cc_library(
name = 'queue',
hdrs = [
'queue.h',
diff --git a/motors/usb/cdc.cc b/motors/usb/cdc.cc
index 87ce0dc..575b4c1 100644
--- a/motors/usb/cdc.cc
+++ b/motors/usb/cdc.cc
@@ -119,17 +119,12 @@
data_tx_endpoint_ = AddEndpoint();
data_rx_endpoint_ = AddEndpoint();
- {
- const auto iad_descriptor = CreateDescriptor(
- iad_descriptor_length(), UsbDescriptorType::kInterfaceAssociation);
- iad_descriptor->AddByte(status_interface_); // bFirstInterface
- iad_descriptor->AddByte(2); // bInterfaceCount
- iad_descriptor->AddByte(communications_class()); // bFunctionClass
- iad_descriptor->AddByte(communications_subclass::
- abstract_control_model()); // bFunctionSubClass
- iad_descriptor->AddByte(0); // bFunctionProtocol
- iad_descriptor->AddByte(device()->AddString("UsbTty")); // iFunction
- }
+ CreateIadDescriptor(
+ /*first_interface=*/status_interface_,
+ /*interface_count=*/2,
+ /*function_class=*/communications_class(),
+ /*function_subclass=*/communications_subclass::abstract_control_model(),
+ /*function_protocol=*/0, "UsbTty");
{
const auto interface_descriptor = CreateDescriptor(
diff --git a/motors/usb/cdc.h b/motors/usb/cdc.h
index 00eec4c..c02873d 100644
--- a/motors/usb/cdc.h
+++ b/motors/usb/cdc.h
@@ -116,7 +116,7 @@
EndpointBufferState tx_state_;
Data01 next_tx_toggle_;
- // These are BdtEntries which we're holding into without releasing back to the
+ // 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;
diff --git a/motors/usb/hid.cc b/motors/usb/hid.cc
index 24ffe24..0c5075e 100644
--- a/motors/usb/hid.cc
+++ b/motors/usb/hid.cc
@@ -61,16 +61,12 @@
interface_ = AddInterface();
in_endpoint_ = AddEndpoint();
- {
- const auto iad_descriptor = CreateDescriptor(
- iad_descriptor_length(), UsbDescriptorType::kInterfaceAssociation);
- iad_descriptor->AddByte(interface_); // bFirstInterface
- iad_descriptor->AddByte(1); // bInterfaceCount
- iad_descriptor->AddByte(hid_class()); // bFunctionClass
- iad_descriptor->AddByte(0); // bFunctionSubClass
- iad_descriptor->AddByte(0); // bFunctionProtocol
- iad_descriptor->AddByte(device()->AddString("HidIad")); // iFunction
- }
+ CreateIadDescriptor(
+ /*first_interface=*/interface_,
+ /*interface_count=*/1,
+ /*function_class=*/hid_class(),
+ /*function_subclass=*/0,
+ /*function_protocol=*/0, "HidIad");
{
const auto interface_descriptor = CreateDescriptor(
diff --git a/motors/usb/hid.h b/motors/usb/hid.h
index 4b2dae4..37aa86b 100644
--- a/motors/usb/hid.h
+++ b/motors/usb/hid.h
@@ -51,10 +51,6 @@
SetupResponse HandleGetDescriptor(
const UsbDevice::SetupPacket &setup_packet) override;
- void HandleOutFinished(int /*endpoint*/, BdtEntry * /*bdt_entry*/) override {
- // We don't support any out endpoints.
- }
-
void HandleInFinished(int endpoint, BdtEntry *bdt_entry,
EvenOdd odd) override;
diff --git a/motors/usb/interrupt_out.cc b/motors/usb/interrupt_out.cc
new file mode 100644
index 0000000..9af650a
--- /dev/null
+++ b/motors/usb/interrupt_out.cc
@@ -0,0 +1,91 @@
+#include "motors/usb/interrupt_out.h"
+
+namespace frc971 {
+namespace teensy {
+
+void InterruptOut::Initialize() {
+ interface_ = AddInterface();
+ endpoint_ = AddEndpoint();
+
+ SetMicrosoftDeviceInterfaceGuids("{D4FA286B-C60D-4B99-B49B-9656139F5771}");
+
+ CreateIadDescriptor(
+ /*first_interface=*/interface_,
+ /*interface_count=*/1,
+ /*function_class=*/vendor_specific_class(),
+ /*function_subclass=*/0,
+ /*function_protocol=*/0, name_);
+
+ {
+ const auto interface_descriptor = CreateDescriptor(
+ interface_descriptor_length(), UsbDescriptorType::kInterface);
+ interface_descriptor->AddByte(interface_); // bInterfaceNumber
+ interface_descriptor->AddByte(0); // bAlternateSetting
+ interface_descriptor->AddByte(1); // bNumEndpoints
+ interface_descriptor->AddByte(vendor_specific_class()); // bInterfaceClass
+ interface_descriptor->AddByte(0x97); // bInterfaceSubClass
+ interface_descriptor->AddByte(0x97); // bInterfaceProtocol
+ interface_descriptor->AddByte(device()->AddString(name_)); // iInterface
+ }
+
+ {
+ const auto endpoint_descriptor = CreateDescriptor(
+ endpoint_descriptor_length(), UsbDescriptorType::kEndpoint);
+ endpoint_descriptor->AddByte(endpoint_); // bEndpointAddress
+ endpoint_descriptor->AddByte(
+ m_endpoint_attributes_interrupt()); // bmAttributes
+ endpoint_descriptor->AddUint16(kSize); // wMaxPacketSize
+ endpoint_descriptor->AddByte(1); // bInterval
+ }
+}
+
+void InterruptOut::HandleOutFinished(int endpoint, BdtEntry *bdt_entry) {
+ if (endpoint == endpoint_) {
+
+ DisableInterrupts disable_interrupts;
+ if (first_rx_held_ == nullptr) {
+ first_rx_held_ = bdt_entry;
+ } else {
+ second_rx_held_ = bdt_entry;
+ }
+ }
+}
+
+int InterruptOut::ReceiveData(char *buffer) {
+ DisableInterrupts disable_interrupts;
+ if (first_rx_held_ == nullptr) {
+ return -1;
+ }
+
+ BdtEntry *const bdt_entry = first_rx_held_;
+ const size_t data_size = G_USB_BD_BC(bdt_entry->buffer_descriptor);
+ memcpy(buffer, bdt_entry->address, kSize);
+ dma_memory_barrier();
+
+ first_rx_held_->buffer_descriptor = M_USB_BD_OWN | M_USB_BD_DTS |
+ V_USB_BD_BC(kSize) |
+ static_cast<uint32_t>(next_rx_toggle_);
+ next_rx_toggle_ = Data01Inverse(next_rx_toggle_);
+
+ first_rx_held_ = second_rx_held_;
+ second_rx_held_ = nullptr;
+ return data_size;
+}
+
+void InterruptOut::HandleConfigured(int endpoint) {
+ if (endpoint == endpoint_) {
+ device()->ConfigureEndpointFor(endpoint_, true, false, true);
+ next_rx_toggle_ = Data01::kData0;
+ device()->SetBdtEntry(
+ endpoint_, Direction::kRx, EvenOdd::kEven,
+ {M_USB_BD_OWN | M_USB_BD_DTS | V_USB_BD_BC(buffers_[0].size()),
+ buffers_[0].data()});
+ device()->SetBdtEntry(endpoint_, Direction::kRx, EvenOdd::kOdd,
+ {M_USB_BD_OWN | M_USB_BD_DTS |
+ V_USB_BD_BC(buffers_[1].size()) | M_USB_BD_DATA1,
+ buffers_[1].data()});
+ }
+}
+
+} // namespace teensy
+} // namespace frc971
diff --git a/motors/usb/interrupt_out.h b/motors/usb/interrupt_out.h
new file mode 100644
index 0000000..9a7232c
--- /dev/null
+++ b/motors/usb/interrupt_out.h
@@ -0,0 +1,64 @@
+#ifndef MOTORS_USB_INTERRUPT_OUT_H_
+#define MOTORS_USB_INTERRUPT_OUT_H_
+
+#include "motors/usb/usb.h"
+#include "motors/util.h"
+
+#include <array>
+#include <string>
+
+namespace frc971 {
+namespace teensy {
+
+// A simple function that just has an interrupt out endpoint and exposes the
+// data received.
+class InterruptOut final : public UsbFunction {
+ public:
+ static constexpr size_t kSize = 64;
+
+ InterruptOut(UsbDevice *device, const ::std::string &name)
+ : UsbFunction(device), name_(name) {}
+ ~InterruptOut() override = default;
+
+ // Copies the next packet into buffer.
+ // buffer must have kSize of space available.
+ // Returns the size of the packet, or -1 if there isn't one.
+ int ReceiveData(char *buffer);
+
+ private:
+ void Initialize() override;
+
+ void HandleOutFinished(int endpoint, BdtEntry *bdt_entry) override;
+ void HandleConfigured(int endpoint) override;
+ void HandleReset() override {
+ device()->SetBdtEntry(endpoint_, Direction::kRx, EvenOdd::kEven,
+ {0, nullptr});
+ device()->SetBdtEntry(endpoint_, Direction::kRx, EvenOdd::kOdd,
+ {0, nullptr});
+ }
+
+ ::std::string MicrosoftExtendedCompatibleId() override {
+ ::std::string result = "WINUSB";
+ result.resize(16);
+ return result;
+ }
+
+ ::std::array<::std::array<char, kSize>, 2> buffers_;
+
+ int interface_;
+ int endpoint_;
+
+ // These are BdtEntries which we're holding onto until the data is copied out.
+ // This also has the advantage of avoiding any more data being sent until
+ // we're ready.
+ // They are only manipulated with interrupts disabled.
+ BdtEntry *first_rx_held_ = nullptr, *second_rx_held_ = nullptr;
+ Data01 next_rx_toggle_;
+
+ const ::std::string name_;
+};
+
+} // namespace teensy
+} // namespace frc971
+
+#endif // MOTORS_USB_INTERRUPT_OUT_H_
diff --git a/motors/usb/usb.cc b/motors/usb/usb.cc
index 36c6456..4a1b115 100644
--- a/motors/usb/usb.cc
+++ b/motors/usb/usb.cc
@@ -2,6 +2,8 @@
#include <string.h>
+#include <map>
+
#include "motors/util.h"
namespace frc971 {
@@ -67,6 +69,10 @@
// The device protocol for using IADs.
constexpr uint8_t iad_device_protocol() { return 0x01; }
+// The Microsoft "vendor code" we're going to use. It's pretty arbitrary (just
+// has to not be some other request we want to use).
+constexpr uint8_t microsoft_vendor_code() { return 0x67; }
+
// The total number of endpoints supported by this hardware.
constexpr int number_endpoints() { return 16; }
@@ -122,7 +128,9 @@
device_descriptor_->AddByte(kEndpoint0MaxSize); // bMaxPacketSize0
device_descriptor_->AddUint16(vendor_id); // idVendor
device_descriptor_->AddUint16(product_id); // idProduct
- device_descriptor_->AddUint16(0); // bcdDevice
+ // Increment this whenever you need Windows boxes to actually pay attention to
+ // changes.
+ device_descriptor_->AddUint16(7); // bcdDevice
// We might overwrite these string descriptor indices later if we get strings
// to put there.
device_descriptor_->AddByte(0); // iManufacturer
@@ -148,6 +156,53 @@
function->Initialize();
}
+ {
+ const uint32_t length = 16 + 24 * functions_.size();
+ microsoft_extended_id_descriptor_.resize(length);
+ int index = 0;
+
+ // dwLength
+ microsoft_extended_id_descriptor_[index++] = length & 0xFF;
+ microsoft_extended_id_descriptor_[index++] = (length >> UINT32_C(8)) & 0xFF;
+ microsoft_extended_id_descriptor_[index++] =
+ (length >> UINT32_C(16)) & 0xFF;
+ microsoft_extended_id_descriptor_[index++] =
+ (length >> UINT32_C(24)) & 0xFF;
+
+ // bcdVersion
+ microsoft_extended_id_descriptor_[index++] = 0x00;
+ microsoft_extended_id_descriptor_[index++] = 0x01;
+
+ // wIndex
+ microsoft_extended_id_descriptor_[index++] =
+ microsoft_feature_descriptors::kExtendedCompatibilityId;
+ microsoft_extended_id_descriptor_[index++] = 0;
+
+ // bCount
+ microsoft_extended_id_descriptor_[index++] = functions_.size();
+
+ // Reserved
+ index += 7;
+
+ for (UsbFunction *function : functions_) {
+ // bFirstInterfaceNumber
+ microsoft_extended_id_descriptor_[index++] = function->first_interface_;
+
+ // Reserved
+ index++;
+
+ // compatibleID and subCompatibleID
+ microsoft_extended_id_descriptor_.replace(
+ index, 16, function->MicrosoftExtendedCompatibleId());
+ index += 16;
+
+ // Reserved
+ index += 6;
+ }
+
+ assert(index == length);
+ }
+
config_descriptor_->AddUint16(
config_descriptor_list_.CurrentSize()); // wTotalLength
config_descriptor_->AddByte(interface_mapping_.size()); // bNumInterfaces
@@ -696,6 +751,29 @@
return;
case UsbDescriptorType::kString:
+ // Skip any other checks on the other fields. Who knows what
+ // Microsoft is going to set them to; not like they document it
+ // anywhere obvious...
+ if (descriptor_index == 0xEE && setup_packet.index == 0) {
+ static uint8_t
+ kMicrosoftOsStringDescriptor
+ [] = {
+ 0x12, // bLength
+ static_cast<uint8_t>(
+ UsbDescriptorType::kString), // bDescriptorType
+ 0x4D,
+ 0x00, 0x53, 0x00, 0x46, 0x00, 0x54, 0x00, 0x31,
+ 0x00, 0x30, 0x00, 0x30,
+ 0x00, // qwSignature
+ microsoft_vendor_code(), // bMS_VendorCode
+ 0x00 // bPad
+ };
+ QueueEndpoint0Data(
+ reinterpret_cast<char *>(kMicrosoftOsStringDescriptor),
+ ::std::min<int>(setup_packet.length,
+ sizeof(kMicrosoftOsStringDescriptor)));
+ return;
+ }
if (descriptor_index != 0 &&
setup_packet.index != english_us_code()) {
break;
@@ -716,6 +794,62 @@
}
break;
+ case SetupRequestType::kVendor:
+ switch (setup_packet.request) {
+ case microsoft_vendor_code():
+ if (!in) {
+ break;
+ }
+
+ switch (recipient) {
+ case standard_setup_recipients::kDevice:
+ if (setup_packet.value != 0) {
+ // Ignoring weird things and descriptors larger than 64K for
+ // now.
+ break;
+ }
+ switch (setup_packet.index) {
+ case microsoft_feature_descriptors::kExtendedCompatibilityId:
+ QueueEndpoint0Data(
+ microsoft_extended_id_descriptor_.data(),
+ ::std::min<int>(
+ setup_packet.length,
+ microsoft_extended_id_descriptor_.size()));
+ return;
+ }
+ break;
+
+ case standard_setup_recipients::kInterface:
+ switch (setup_packet.index) {
+ case microsoft_feature_descriptors::kExtendedProperties:
+ const int interface = setup_packet.value & 0xFF;
+ const int page_number = (setup_packet.value >> 8) & 0xFF;
+
+ if (page_number != 0) {
+ // Ignoring weird things and descriptors larger than 64K for
+ // now.
+ break;
+ }
+
+ const UsbFunction *const function =
+ interface_mapping_[interface];
+ const ::std::string &descriptor =
+ function->microsoft_extended_property_descriptor_;
+ if (descriptor.empty() ||
+ interface != function->first_interface_) {
+ break;
+ }
+ QueueEndpoint0Data(
+ descriptor.data(),
+ ::std::min<int>(setup_packet.length, descriptor.size()));
+ return;
+ }
+ break;
+ }
+ break;
+ }
+ break;
+
default:
for (UsbFunction *function : functions_) {
switch (function->HandleEndpoint0SetupPacket(setup_packet)) {
@@ -827,6 +961,100 @@
return r;
}
+void UsbFunction::CreateIadDescriptor(int first_interface, int interface_count,
+ int function_class, int function_subclass,
+ int function_protocol,
+ const ::std::string &function) {
+ first_interface_ = first_interface;
+ const auto iad_descriptor = CreateDescriptor(
+ iad_descriptor_length(), UsbDescriptorType::kInterfaceAssociation);
+ iad_descriptor->AddByte(first_interface); // bFirstInterface
+ iad_descriptor->AddByte(interface_count); // bInterfaceCount
+ iad_descriptor->AddByte(function_class); // bFunctionClass
+ iad_descriptor->AddByte(function_subclass); // bFunctionSubClass
+ iad_descriptor->AddByte(function_protocol); // bFunctionProtocol
+ iad_descriptor->AddByte(device()->AddString(function)); // iFunction
+}
+
+void UsbFunction::SetMicrosoftDeviceInterfaceGuids(const ::std::string &guids) {
+ ::std::map<::std::string, ::std::string> properties;
+ properties["DeviceInterfaceGUIDs"] = guids + ::std::string(1, '\0');
+ ::std::string *const descriptor = µsoft_extended_property_descriptor_;
+
+ uint32_t length = 10;
+ for (auto &pair : properties) {
+ length += 14;
+ length += (pair.first.size() + 1) * 2;
+ length += (pair.second.size() + 1) * 2;
+ }
+ descriptor->resize(length);
+ int index = 0;
+
+ // dwLength
+ (*descriptor)[index++] = length & 0xFF;
+ (*descriptor)[index++] = (length >> UINT32_C(8)) & 0xFF;
+ (*descriptor)[index++] = (length >> UINT32_C(16)) & 0xFF;
+ (*descriptor)[index++] = (length >> UINT32_C(24)) & 0xFF;
+
+ // bcdVersion
+ (*descriptor)[index++] = 0x00;
+ (*descriptor)[index++] = 0x01;
+
+ // wIndex
+ (*descriptor)[index++] = microsoft_feature_descriptors::kExtendedProperties;
+ (*descriptor)[index++] = 0;
+
+ // wCount
+ (*descriptor)[index++] = properties.size() & 0xFF;
+ (*descriptor)[index++] = (properties.size() >> 8) & 0xFF;
+
+ for (auto &pair : properties) {
+ const uint32_t size = 14 + (pair.first.size() + pair.second.size() + 2) * 2;
+ // dwSize
+ (*descriptor)[index++] = size & 0xFF;
+ (*descriptor)[index++] = (size >> UINT32_C(8)) & 0xFF;
+ (*descriptor)[index++] = (size >> UINT32_C(16)) & 0xFF;
+ (*descriptor)[index++] = (size >> UINT32_C(24)) & 0xFF;
+
+ // Need to get this from the map if we ever do others in the future.
+ const uint32_t data_type = 7; // REG_MULTI_SZ
+ // dwPropertyDataType
+ (*descriptor)[index++] = data_type & 0xFF;
+ (*descriptor)[index++] = (data_type >> UINT32_C(8)) & 0xFF;
+ (*descriptor)[index++] = (data_type >> UINT32_C(16)) & 0xFF;
+ (*descriptor)[index++] = (data_type >> UINT32_C(24)) & 0xFF;
+
+ // wPropertyNameLength
+ (*descriptor)[index++] = ((pair.first.size() + 1) * 2) & 0xFF;
+ (*descriptor)[index++] = (((pair.first.size() + 1) * 2) >> 8) & 0xFF;
+
+ // bPropertyName
+ for (size_t i = 0; i < pair.first.size(); ++i) {
+ (*descriptor)[index] = pair.first[i];
+ index += 2;
+ }
+ index += 2;
+
+ // wPropertyDataLength
+ (*descriptor)[index++] = ((pair.second.size() + 1) * 2) & 0xFF;
+ (*descriptor)[index++] =
+ (((pair.second.size() + 1) * 2) >> UINT8_C(8)) & 0xFF;
+ (*descriptor)[index++] =
+ (((pair.second.size() + 1) * 2) >> UINT8_C(16)) & 0xFF;
+ (*descriptor)[index++] =
+ (((pair.second.size() + 1) * 2) >> UINT8_C(24)) & 0xFF;
+
+ // bPropertyData
+ for (size_t i = 0; i < pair.second.size(); ++i) {
+ (*descriptor)[index] = pair.second[i];
+ index += 2;
+ }
+ index += 2;
+ }
+
+ assert(index == length);
+}
+
void UsbDevice::SetBdtEntry(int endpoint, Direction direction, EvenOdd odd,
BdtEntry bdt_entry) {
*MutableBdtEntry(endpoint, direction, odd) = bdt_entry;
diff --git a/motors/usb/usb.h b/motors/usb/usb.h
index 4db29dc..8fd2fb3 100644
--- a/motors/usb/usb.h
+++ b/motors/usb/usb.h
@@ -106,6 +106,11 @@
constexpr int kOther = 3;
} // namespace standard_setup_recipients
+namespace microsoft_feature_descriptors {
+constexpr int kExtendedCompatibilityId = 4;
+constexpr int kExtendedProperties = 5;
+} // namespace microsoft_feature_descriptors
+
// The HID class specification says this. Can't find any mention in the main
// standard.
#define G_DESCRIPTOR_TYPE_TYPE(descriptor_type) \
@@ -116,6 +121,8 @@
constexpr int kVendor = 2;
} // namespace standard_descriptor_type_types
+constexpr uint8_t vendor_specific_class() { return 0xFF; }
+
class UsbFunction;
// Allows building up a list of descriptors. This supports a much nicer API than
@@ -430,6 +437,9 @@
// All of the functions (without duplicates).
::std::vector<UsbFunction *> functions_;
+ // Filled out during Initialize().
+ ::std::string microsoft_extended_id_descriptor_;
+
friend void usb_isr(void);
friend class UsbFunction;
};
@@ -497,6 +507,23 @@
UsbDevice *device() const { return device_; }
+ void CreateIadDescriptor(int first_interface, int interface_count,
+ int function_class, int function_subclass,
+ int function_protocol,
+ const ::std::string &function);
+
+ // Sets the interface GUIDs for this function. Each GUID (one per interface?)
+ // should be followed by a NUL.
+ //
+ // If this is never called, no GUID extended property will be reported.
+ //
+ // This is needed to pass to Windows so WinUSB will be happy. Generate them at
+ // https://www.guidgenerator.com/online-guid-generator.aspx (Uppcase, Braces,
+ // and Hyphens).
+ //
+ // May only be called during setup.
+ void SetMicrosoftDeviceInterfaceGuids(const ::std::string &guids);
+
private:
virtual void Initialize() = 0;
@@ -515,9 +542,15 @@
return SetupResponse::kIgnored;
}
- virtual void HandleOutFinished(int endpoint, BdtEntry *bdt_entry) = 0;
- virtual void HandleInFinished(int endpoint, BdtEntry *bdt_entry,
- EvenOdd odd) = 0;
+ // Returns the concatenated compatible ID and subcompatible ID.
+ virtual ::std::string MicrosoftExtendedCompatibleId() {
+ // Default to both of them being "unused".
+ return ::std::string(16, '\0');
+ }
+
+ virtual void HandleOutFinished(int /*endpoint*/, BdtEntry * /*bdt_entry*/) {}
+ virtual void HandleInFinished(int /*endpoint*/, BdtEntry * /*bdt_entry*/,
+ EvenOdd /*odd*/) {}
// Called when a given interface is configured (aka "experiences a
// configuration event"). This means all rx and tx buffers have been cleared
@@ -528,6 +561,11 @@
// Should reset everything to use the even buffers next.
virtual void HandleReset() = 0;
+ int first_interface_ = -1;
+
+ // Filled out during Initialize().
+ ::std::string microsoft_extended_property_descriptor_;
+
UsbDevice *const device_;
friend class UsbDevice;