Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Klipper3d/klipper.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin O'Connor <kevin@koconnor.net>2022-06-09 04:03:11 +0300
committerKevin O'Connor <kevin@koconnor.net>2022-06-27 17:50:23 +0300
commit790ff4d8d7ac200527c272af3e58f0d9f06468e4 (patch)
treeb021c1ec8517f6b16950394ca9aef91dcff7dc7d
parentc8cc98ce5dce3771cf2728eee7b1bdb026504f75 (diff)
usb_canbus: Initial support for USB to CAN bridge mode
Support a USB interface that shows up as a canbus adapter to linux. Route both local and real canbus packets over that interface. Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
-rw-r--r--docs/CANBUS.md31
-rw-r--r--src/Kconfig12
-rw-r--r--src/generic/canserial.c7
-rw-r--r--src/generic/canserial.h2
-rw-r--r--src/generic/usb_canbus.c675
-rw-r--r--src/generic/usbstd.h6
-rw-r--r--src/stm32/Kconfig62
-rw-r--r--src/stm32/Makefile2
8 files changed, 782 insertions, 15 deletions
diff --git a/docs/CANBUS.md b/docs/CANBUS.md
index fc85dc76a..90efacf5a 100644
--- a/docs/CANBUS.md
+++ b/docs/CANBUS.md
@@ -91,3 +91,34 @@ the CAN bus to communicate with the device - for example:
[mcu my_can_mcu]
canbus_uuid: 11aa22bb33cc
```
+
+## USB to CAN bus bridge mode
+
+Some micro-controllers support selecting "USB to CAN bus bridge" mode
+during "make menuconfig". This mode may allow one to use a
+micro-controller as both a "USB to CAN bus adapter" and as a Klipper
+node.
+
+When Klipper uses this mode the micro-controller appears as a "USB CAN
+bus adapter" under Linux. The "Klipper bridge mcu" itself will appear
+as if was on this CAN bus - it can be identified via `canbus_query.py`
+and configured like other CAN bus Klipper nodes. It will appear
+alongside other devices that are actually on the CAN bus.
+
+Some important notes when using this mode:
+
+* The "bridge mcu" is not actually on the CAN bus. Messages to and
+ from it do not consume bandwidth on the CAN bus. The mcu can not be
+ seen by other adapters that may be on the CAN bus.
+
+* It is necessary to configure the `can0` (or similar) interface in
+ Linux in order to communicate with the bus. However, Linux CAN bus
+ speed and CAN bus bit-timing options are ignored by Klipper.
+ Currently, the CAN bus frequency is specified during "make
+ menuconfig" and the bus speed specified in Linux is ignored.
+
+* Whenever the "bridge mcu" is reset, Linux will disable the
+ corresponding `can0` interface. Generally, this may require running
+ commands such as "ip up" to restart the interface. Thus, Klipper
+ FIRMWARE_RESTART commands (or regular RESTART after a config change)
+ may require restarting the `can0` interface.
diff --git a/src/Kconfig b/src/Kconfig
index 921370f69..d8d8a19e2 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -55,22 +55,24 @@ config SERIAL_BAUD
# Generic configuration options for USB
config USBSERIAL
bool
+config USBCANBUS
+ bool
config USB_VENDOR_ID
default 0x1d50
config USB_DEVICE_ID
default 0x614e
config USB_SERIAL_NUMBER_CHIPID
- depends on HAVE_CHIPID && USBSERIAL
+ depends on HAVE_CHIPID && (USBSERIAL || USBCANBUS)
default y
config USB_SERIAL_NUMBER
default "12345"
menu "USB ids"
- depends on USBSERIAL && LOW_LEVEL_OPTIONS
+ depends on (USBSERIAL || USBCANBUS) && LOW_LEVEL_OPTIONS
config USB_VENDOR_ID
- hex "USB vendor ID"
+ hex "USB vendor ID" if USBSERIAL
config USB_DEVICE_ID
- hex "USB device ID"
+ hex "USB device ID" if USBSERIAL
config USB_SERIAL_NUMBER_CHIPID
bool "USB serial number from CHIPID" if HAVE_CHIPID
config USB_SERIAL_NUMBER
@@ -82,7 +84,7 @@ config CANSERIAL
bool
config CANBUS
bool
- default y if CANSERIAL
+ default y if CANSERIAL || USBCANBUS
config CANBUS_FREQUENCY
int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANBUS
default 500000
diff --git a/src/generic/canserial.c b/src/generic/canserial.c
index ba0ec4614..34f0ce65c 100644
--- a/src/generic/canserial.c
+++ b/src/generic/canserial.c
@@ -224,7 +224,7 @@ canserial_notify_rx(void)
DECL_CONSTANT("RECEIVE_WINDOW", ARRAY_SIZE(CanData.receive_buf));
// Handle incoming data (called from IRQ handler)
-void
+int
canserial_process_data(struct canbus_msg *msg)
{
uint32_t id = msg->id;
@@ -233,7 +233,7 @@ canserial_process_data(struct canbus_msg *msg)
int rpos = CanData.receive_pos;
uint32_t len = CANMSG_DATA_LEN(msg);
if (len > sizeof(CanData.receive_buf) - rpos)
- len = sizeof(CanData.receive_buf) - rpos;
+ return -1;
memcpy(&CanData.receive_buf[rpos], msg->data, len);
CanData.receive_pos = rpos + len;
canserial_notify_rx();
@@ -243,12 +243,13 @@ canserial_process_data(struct canbus_msg *msg)
uint32_t pushp = CanData.admin_push_pos;
if (pushp >= CanData.admin_pull_pos + ARRAY_SIZE(CanData.admin_queue))
// No space - drop message
- return;
+ return -1;
uint32_t pos = pushp % ARRAY_SIZE(CanData.admin_queue);
memcpy(&CanData.admin_queue[pos], msg, sizeof(*msg));
CanData.admin_push_pos = pushp + 1;
canserial_notify_rx();
}
+ return 0;
}
// Remove from the receive buffer the given number of bytes
diff --git a/src/generic/canserial.h b/src/generic/canserial.h
index ac4b77143..d7a56d3b4 100644
--- a/src/generic/canserial.h
+++ b/src/generic/canserial.h
@@ -13,7 +13,7 @@ void canserial_set_filter(uint32_t id);
// canserial.c
void canserial_notify_tx(void);
-void canserial_process_data(struct canbus_msg *msg);
+int canserial_process_data(struct canbus_msg *msg);
void canserial_set_uuid(uint8_t *raw_uuid, uint32_t raw_uuid_len);
#endif // canbus.h
diff --git a/src/generic/usb_canbus.c b/src/generic/usb_canbus.c
new file mode 100644
index 000000000..1631bebe3
--- /dev/null
+++ b/src/generic/usb_canbus.c
@@ -0,0 +1,675 @@
+// Support for Linux "gs_usb" CANbus adapter emulation
+//
+// Copyright (C) 2018-2022 Kevin O'Connor <kevin@koconnor.net>
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include <string.h> // memmove
+#include "autoconf.h" // CONFIG_USB_VENDOR_ID
+#include "board/canbus.h" // canbus_notify_tx
+#include "board/canserial.h" // canserial_notify_tx
+#include "board/io.h" // readl
+#include "board/misc.h" // console_sendf
+#include "board/pgm.h" // PROGMEM
+#include "board/usb_cdc_ep.h" // USB_CDC_EP_BULK_IN
+#include "byteorder.h" // cpu_to_le16
+#include "generic/usbstd.h" // struct usb_device_descriptor
+#include "sched.h" // sched_wake_task
+#include "usb_cdc.h" // usb_notify_ep0
+
+
+/****************************************************************
+ * Linux "gs_usb" definitions
+ ****************************************************************/
+
+#define USB_GSUSB_1_VENDOR_ID 0x1d50
+#define USB_GSUSB_1_PRODUCT_ID 0x606f
+
+enum gs_usb_breq {
+ GS_USB_BREQ_HOST_FORMAT = 0,
+ GS_USB_BREQ_BITTIMING,
+ GS_USB_BREQ_MODE,
+ GS_USB_BREQ_BERR,
+ GS_USB_BREQ_BT_CONST,
+ GS_USB_BREQ_DEVICE_CONFIG,
+ GS_USB_BREQ_TIMESTAMP,
+ GS_USB_BREQ_IDENTIFY,
+ GS_USB_BREQ_GET_USER_ID,
+ GS_USB_BREQ_SET_USER_ID,
+ GS_USB_BREQ_DATA_BITTIMING,
+ GS_USB_BREQ_BT_CONST_EXT,
+};
+
+struct gs_host_config {
+ uint32_t byte_order;
+} __packed;
+
+struct gs_device_config {
+ uint8_t reserved1;
+ uint8_t reserved2;
+ uint8_t reserved3;
+ uint8_t icount;
+ uint32_t sw_version;
+ uint32_t hw_version;
+} __packed;
+
+struct gs_device_bt_const {
+ uint32_t feature;
+ uint32_t fclk_can;
+ uint32_t tseg1_min;
+ uint32_t tseg1_max;
+ uint32_t tseg2_min;
+ uint32_t tseg2_max;
+ uint32_t sjw_max;
+ uint32_t brp_min;
+ uint32_t brp_max;
+ uint32_t brp_inc;
+} __packed;
+
+struct gs_device_bittiming {
+ uint32_t prop_seg;
+ uint32_t phase_seg1;
+ uint32_t phase_seg2;
+ uint32_t sjw;
+ uint32_t brp;
+} __packed;
+
+struct gs_device_mode {
+ uint32_t mode;
+ uint32_t flags;
+} __packed;
+
+struct gs_host_frame {
+ uint32_t echo_id;
+ uint32_t can_id;
+
+ uint8_t can_dlc;
+ uint8_t channel;
+ uint8_t flags;
+ uint8_t reserved;
+
+ union {
+ uint8_t data[8];
+ uint32_t data32[2];
+ };
+} __packed;
+
+
+/****************************************************************
+ * Message sending
+ ****************************************************************/
+
+// Global storage
+static struct usbcan_data {
+ struct task_wake wake;
+
+ // Canbus data from host
+ union {
+ struct gs_host_frame host_frame;
+ uint8_t rx_frame_pad[USB_CDC_EP_BULK_OUT_SIZE];
+ };
+ uint8_t host_status;
+
+ // Canbus data routed locally
+ uint8_t notify_local;
+ uint32_t assigned_id;
+
+ // Data from physical canbus interface
+ uint32_t pull_pos, push_pos;
+ struct canbus_msg queue[8];
+} UsbCan;
+
+enum {
+ HS_TX_ECHO = 1,
+ HS_TX_HW = 2,
+ HS_TX_LOCAL = 4,
+};
+
+void
+canbus_notify_tx(void)
+{
+ sched_wake_task(&UsbCan.wake);
+}
+
+// Handle incoming data from hw canbus interface (called from IRQ handler)
+void
+canbus_process_data(struct canbus_msg *msg)
+{
+ // Add to admin command queue
+ uint32_t pushp = UsbCan.push_pos;
+ if (pushp - UsbCan.pull_pos >= ARRAY_SIZE(UsbCan.queue))
+ // No space - drop message
+ return;
+ if (UsbCan.assigned_id && (msg->id & ~1) == UsbCan.assigned_id)
+ // Id reserved for local
+ return;
+ uint32_t pos = pushp % ARRAY_SIZE(UsbCan.queue);
+ memcpy(&UsbCan.queue[pos], msg, sizeof(*msg));
+ UsbCan.push_pos = pushp + 1;
+ usb_notify_bulk_out();
+}
+
+// Send a message to the Linux host
+static int
+send_frame(struct canbus_msg *msg)
+{
+ struct gs_host_frame gs = {};
+ gs.echo_id = 0xffffffff;
+ gs.can_id = msg->id;
+ gs.can_dlc = msg->dlc;
+ gs.data32[0] = msg->data32[0];
+ gs.data32[1] = msg->data32[1];
+ return usb_send_bulk_in(&gs, sizeof(gs));
+}
+
+// Send any pending hw frames to host
+static int
+drain_hw_queue(void)
+{
+ for (;;) {
+ uint32_t push_pos = readl(&UsbCan.push_pos);
+ uint32_t pull_pos = UsbCan.pull_pos;
+ if (push_pos != pull_pos) {
+ uint32_t pos = pull_pos % ARRAY_SIZE(UsbCan.queue);
+ int ret = send_frame(&UsbCan.queue[pos]);
+ if (ret < 0)
+ return -1;
+ UsbCan.pull_pos = pull_pos + 1;
+ continue;
+ }
+ return 0;
+ }
+}
+
+void
+usbcan_task(void)
+{
+ if (!sched_check_wake(&UsbCan.wake))
+ return;
+ for (;;) {
+ // Send any pending hw frames to host
+ int ret = drain_hw_queue();
+ if (ret < 0)
+ return;
+
+ // See if previous host frame needs to be transmitted
+ uint_fast8_t host_status = UsbCan.host_status;
+ if (host_status & (HS_TX_HW | HS_TX_LOCAL)) {
+ struct gs_host_frame *gs = &UsbCan.host_frame;
+ struct canbus_msg msg;
+ msg.id = gs->can_id;
+ msg.dlc = gs->can_dlc;
+ msg.data32[0] = gs->data32[0];
+ msg.data32[1] = gs->data32[1];
+ if (host_status & HS_TX_HW) {
+ ret = canbus_send(&msg);
+ if (ret < 0)
+ return;
+ UsbCan.host_status = host_status = host_status & ~HS_TX_HW;
+ }
+ if (host_status & HS_TX_LOCAL) {
+ ret = canserial_process_data(&msg);
+ if (ret < 0) {
+ usb_notify_bulk_out();
+ return;
+ }
+ UsbCan.host_status = host_status & ~HS_TX_LOCAL;
+ }
+ continue;
+ }
+
+ // Send any previous echo frames
+ if (host_status) {
+ ret = usb_send_bulk_in(&UsbCan.host_frame
+ , sizeof(UsbCan.host_frame));
+ if (ret < 0)
+ return;
+ UsbCan.host_status = 0;
+ continue;
+ }
+
+ // See if can read a new frame from host
+ ret = usb_read_bulk_out(&UsbCan.host_frame, USB_CDC_EP_BULK_OUT_SIZE);
+ if (ret > 0) {
+ uint32_t id = UsbCan.host_frame.can_id;
+ UsbCan.host_status = HS_TX_ECHO | HS_TX_HW;
+ if (id == CANBUS_ID_ADMIN)
+ UsbCan.host_status = HS_TX_ECHO | HS_TX_HW | HS_TX_LOCAL;
+ else if (UsbCan.assigned_id && UsbCan.assigned_id == id)
+ UsbCan.host_status = HS_TX_ECHO | HS_TX_LOCAL;
+ continue;
+ }
+
+ // No more work to be done
+ if (UsbCan.notify_local) {
+ UsbCan.notify_local = 0;
+ canserial_notify_tx();
+ }
+ return;
+ }
+}
+DECL_TASK(usbcan_task);
+
+int
+canserial_send(struct canbus_msg *msg)
+{
+ int ret = drain_hw_queue();
+ if (ret < 0)
+ goto retry_later;
+ ret = send_frame(msg);
+ if (ret < 0)
+ goto retry_later;
+ UsbCan.notify_local = 0;
+ return msg->dlc;
+retry_later:
+ UsbCan.notify_local = 1;
+ return -1;
+}
+
+void
+canserial_set_filter(uint32_t id)
+{
+ UsbCan.assigned_id = id;
+}
+
+void
+usb_notify_bulk_out(void)
+{
+ canbus_notify_tx();
+}
+
+void
+usb_notify_bulk_in(void)
+{
+ canbus_notify_tx();
+}
+
+
+/****************************************************************
+ * USB descriptors
+ ****************************************************************/
+
+#define CONCAT1(a, b) a ## b
+#define CONCAT(a, b) CONCAT1(a, b)
+#define USB_STR_MANUFACTURER u"Klipper"
+#define USB_STR_PRODUCT CONCAT(u,CONFIG_MCU)
+#define USB_STR_SERIAL CONCAT(u,CONFIG_USB_SERIAL_NUMBER)
+
+// String descriptors
+enum {
+ USB_STR_ID_MANUFACTURER = 1, USB_STR_ID_PRODUCT, USB_STR_ID_SERIAL,
+};
+
+#define SIZE_cdc_string_langids (sizeof(cdc_string_langids) + 2)
+
+static const struct usb_string_descriptor cdc_string_langids PROGMEM = {
+ .bLength = SIZE_cdc_string_langids,
+ .bDescriptorType = USB_DT_STRING,
+ .data = { cpu_to_le16(USB_LANGID_ENGLISH_US) },
+};
+
+#define SIZE_cdc_string_manufacturer \
+ (sizeof(cdc_string_manufacturer) + sizeof(USB_STR_MANUFACTURER) - 2)
+
+static const struct usb_string_descriptor cdc_string_manufacturer PROGMEM = {
+ .bLength = SIZE_cdc_string_manufacturer,
+ .bDescriptorType = USB_DT_STRING,
+ .data = USB_STR_MANUFACTURER,
+};
+
+#define SIZE_cdc_string_product \
+ (sizeof(cdc_string_product) + sizeof(USB_STR_PRODUCT) - 2)
+
+static const struct usb_string_descriptor cdc_string_product PROGMEM = {
+ .bLength = SIZE_cdc_string_product,
+ .bDescriptorType = USB_DT_STRING,
+ .data = USB_STR_PRODUCT,
+};
+
+#define SIZE_cdc_string_serial \
+ (sizeof(cdc_string_serial) + sizeof(USB_STR_SERIAL) - 2)
+
+static const struct usb_string_descriptor cdc_string_serial PROGMEM = {
+ .bLength = SIZE_cdc_string_serial,
+ .bDescriptorType = USB_DT_STRING,
+ .data = USB_STR_SERIAL,
+};
+
+// Device descriptor
+static const struct usb_device_descriptor gs_device_descriptor PROGMEM = {
+ .bLength = sizeof(gs_device_descriptor),
+ .bDescriptorType = USB_DT_DEVICE,
+ .bcdUSB = cpu_to_le16(0x0200),
+ .bMaxPacketSize0 = USB_CDC_EP0_SIZE,
+ .idVendor = cpu_to_le16(USB_GSUSB_1_VENDOR_ID),
+ .idProduct = cpu_to_le16(USB_GSUSB_1_PRODUCT_ID),
+ .iManufacturer = USB_STR_ID_MANUFACTURER,
+ .iProduct = USB_STR_ID_PRODUCT,
+ .iSerialNumber = USB_STR_ID_SERIAL,
+ .bNumConfigurations = 1,
+};
+
+// Config descriptor
+static const struct config_s {
+ struct usb_config_descriptor config;
+ struct usb_interface_descriptor iface0;
+ struct usb_endpoint_descriptor ep1;
+ struct usb_endpoint_descriptor ep2;
+} PACKED gs_config_descriptor PROGMEM = {
+ .config = {
+ .bLength = sizeof(gs_config_descriptor.config),
+ .bDescriptorType = USB_DT_CONFIG,
+ .wTotalLength = cpu_to_le16(sizeof(gs_config_descriptor)),
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .bmAttributes = 0xC0,
+ .bMaxPower = 50,
+ },
+ .iface0 = {
+ .bLength = sizeof(gs_config_descriptor.iface0),
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 255,
+ .bInterfaceSubClass = 255,
+ .bInterfaceProtocol = 255,
+ },
+ .ep1 = {
+ .bLength = sizeof(gs_config_descriptor.ep1),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_CDC_EP_BULK_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(USB_CDC_EP_BULK_OUT_SIZE),
+ },
+ .ep2 = {
+ .bLength = sizeof(gs_config_descriptor.ep2),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_CDC_EP_BULK_IN | USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(USB_CDC_EP_BULK_IN_SIZE),
+ },
+};
+
+// List of available descriptors
+static const struct descriptor_s {
+ uint_fast16_t wValue;
+ uint_fast16_t wIndex;
+ const void *desc;
+ uint_fast8_t size;
+} usb_descriptors[] PROGMEM = {
+ { USB_DT_DEVICE<<8, 0x0000,
+ &gs_device_descriptor, sizeof(gs_device_descriptor) },
+ { USB_DT_CONFIG<<8, 0x0000,
+ &gs_config_descriptor, sizeof(gs_config_descriptor) },
+ { USB_DT_STRING<<8, 0x0000,
+ &cdc_string_langids, SIZE_cdc_string_langids },
+ { (USB_DT_STRING<<8) | USB_STR_ID_MANUFACTURER, USB_LANGID_ENGLISH_US,
+ &cdc_string_manufacturer, SIZE_cdc_string_manufacturer },
+ { (USB_DT_STRING<<8) | USB_STR_ID_PRODUCT, USB_LANGID_ENGLISH_US,
+ &cdc_string_product, SIZE_cdc_string_product },
+#if !CONFIG_USB_SERIAL_NUMBER_CHIPID
+ { (USB_DT_STRING<<8) | USB_STR_ID_SERIAL, USB_LANGID_ENGLISH_US,
+ &cdc_string_serial, SIZE_cdc_string_serial },
+#endif
+};
+
+// Fill in a USB serial string descriptor from a chip id
+void
+usb_fill_serial(struct usb_string_descriptor *desc, int strlen, void *id)
+{
+ desc->bLength = sizeof(*desc) + strlen * sizeof(desc->data[0]);
+ desc->bDescriptorType = USB_DT_STRING;
+
+ uint8_t *src = id;
+ int i;
+ for (i = 0; i < strlen; i++) {
+ uint8_t c = i & 1 ? src[i/2] & 0x0f : src[i/2] >> 4;
+ desc->data[i] = c < 10 ? c + '0' : c - 10 + 'A';
+ }
+}
+
+
+/****************************************************************
+ * USB endpoint 0 control message handling
+ ****************************************************************/
+
+// State tracking
+enum {
+ UX_READ = 1<<0, UX_SEND = 1<<1, UX_SEND_PROGMEM = 1<<2, UX_SEND_ZLP = 1<<3
+};
+
+static void *usb_xfer_data;
+static uint8_t usb_xfer_size, usb_xfer_flags;
+
+// Set the USB "stall" condition
+static void
+usb_do_stall(void)
+{
+ usb_stall_ep0();
+ usb_xfer_flags = 0;
+}
+
+// Transfer data on the usb endpoint 0
+static void
+usb_do_xfer(void *data, uint_fast8_t size, uint_fast8_t flags)
+{
+ for (;;) {
+ uint_fast8_t xs = size;
+ if (xs > USB_CDC_EP0_SIZE)
+ xs = USB_CDC_EP0_SIZE;
+ int_fast8_t ret;
+ if (flags & UX_READ)
+ ret = usb_read_ep0(data, xs);
+ else if (NEED_PROGMEM && flags & UX_SEND_PROGMEM)
+ ret = usb_send_ep0_progmem(data, xs);
+ else
+ ret = usb_send_ep0(data, xs);
+ if (ret == xs) {
+ // Success
+ data += xs;
+ size -= xs;
+ if (!size) {
+ // Entire transfer completed successfully
+ if (flags & UX_READ) {
+ // Send status packet at end of read
+ flags = UX_SEND;
+ continue;
+ }
+ if (xs == USB_CDC_EP0_SIZE && flags & UX_SEND_ZLP)
+ // Must send zero-length-packet
+ continue;
+ usb_xfer_flags = 0;
+ usb_notify_ep0();
+ return;
+ }
+ continue;
+ }
+ if (ret == -1) {
+ // Interface busy - retry later
+ usb_xfer_data = data;
+ usb_xfer_size = size;
+ usb_xfer_flags = flags;
+ return;
+ }
+ // Error
+ usb_do_stall();
+ return;
+ }
+}
+
+static void
+usb_req_get_descriptor(struct usb_ctrlrequest *req)
+{
+ if (req->bRequestType != USB_DIR_IN)
+ goto fail;
+ void *desc = NULL;
+ uint_fast8_t flags, size, i;
+ for (i=0; i<ARRAY_SIZE(usb_descriptors); i++) {
+ const struct descriptor_s *d = &usb_descriptors[i];
+ if (READP(d->wValue) == req->wValue
+ && READP(d->wIndex) == req->wIndex) {
+ flags = NEED_PROGMEM ? UX_SEND_PROGMEM : UX_SEND;
+ size = READP(d->size);
+ desc = (void*)READP(d->desc);
+ }
+ }
+ if (CONFIG_USB_SERIAL_NUMBER_CHIPID
+ && req->wValue == ((USB_DT_STRING<<8) | USB_STR_ID_SERIAL)
+ && req->wIndex == USB_LANGID_ENGLISH_US) {
+ struct usb_string_descriptor *usbserial_serialid;
+ usbserial_serialid = usbserial_get_serialid();
+ flags = UX_SEND;
+ size = usbserial_serialid->bLength;
+ desc = (void*)usbserial_serialid;
+ }
+ if (desc) {
+ if (size > req->wLength)
+ size = req->wLength;
+ else if (size < req->wLength)
+ flags |= UX_SEND_ZLP;
+ usb_do_xfer(desc, size, flags);
+ return;
+ }
+fail:
+ usb_do_stall();
+}
+
+static void
+usb_req_set_address(struct usb_ctrlrequest *req)
+{
+ if (req->bRequestType || req->wIndex || req->wLength) {
+ usb_do_stall();
+ return;
+ }
+ usb_set_address(req->wValue);
+}
+
+static void
+usb_req_set_configuration(struct usb_ctrlrequest *req)
+{
+ if (req->bRequestType || req->wValue != 1 || req->wIndex || req->wLength) {
+ usb_do_stall();
+ return;
+ }
+ usb_set_configure();
+ usb_notify_bulk_in();
+ usb_notify_bulk_out();
+ usb_do_xfer(NULL, 0, UX_SEND);
+}
+
+struct gs_host_config host_config;
+
+static void
+gs_breq_host_format(struct usb_ctrlrequest *req)
+{
+ // Like candlightfw, little-endian is always used. Read and ignore value.
+ usb_do_xfer(&host_config, sizeof(host_config), UX_READ);
+}
+
+static const struct gs_device_config device_config PROGMEM = {
+ .sw_version = 2,
+ .hw_version = 1,
+};
+
+static void
+gs_breq_device_config(struct usb_ctrlrequest *req)
+{
+ usb_do_xfer((void*)&device_config, sizeof(device_config), UX_SEND);
+}
+
+static const struct gs_device_bt_const bt_const PROGMEM = {
+ // These are just dummy values for now
+ .feature = 0,
+ .fclk_can = 48000000,
+ .tseg1_min = 1,
+ .tseg1_max = 16,
+ .tseg2_min = 1,
+ .tseg2_max = 8,
+ .sjw_max = 4,
+ .brp_min = 1,
+ .brp_max = 1024,
+ .brp_inc = 1,
+};
+
+static void
+gs_breq_bt_const(struct usb_ctrlrequest *req)
+{
+ usb_do_xfer((void*)&bt_const, sizeof(bt_const), UX_SEND);
+}
+
+struct gs_device_bittiming device_bittiming;
+
+static void
+gs_breq_bittiming(struct usb_ctrlrequest *req)
+{
+ // Bit timing is ignored for now
+ usb_do_xfer(&device_bittiming, sizeof(device_bittiming), UX_READ);
+}
+
+struct gs_device_mode device_mode;
+
+static void
+gs_breq_mode(struct usb_ctrlrequest *req)
+{
+ // Mode is ignored for now
+ usb_do_xfer(&device_mode, sizeof(device_mode), UX_READ);
+}
+
+static void
+usb_state_ready(void)
+{
+ struct usb_ctrlrequest req;
+ int_fast8_t ret = usb_read_ep0_setup(&req, sizeof(req));
+ if (ret != sizeof(req))
+ return;
+ uint32_t req_type = req.bRequestType & USB_TYPE_MASK;
+ if (req_type == USB_TYPE_STANDARD) {
+ switch (req.bRequest) {
+ case USB_REQ_GET_DESCRIPTOR: usb_req_get_descriptor(&req); break;
+ case USB_REQ_SET_ADDRESS: usb_req_set_address(&req); break;
+ case USB_REQ_SET_CONFIGURATION: usb_req_set_configuration(&req); break;
+ default: usb_do_stall(); break;
+ }
+ } else if (req_type == USB_TYPE_VENDOR) {
+ switch (req.bRequest) {
+ case GS_USB_BREQ_HOST_FORMAT: gs_breq_host_format(&req); break;
+ case GS_USB_BREQ_DEVICE_CONFIG: gs_breq_device_config(&req); break;
+ case GS_USB_BREQ_BT_CONST: gs_breq_bt_const(&req); break;
+ case GS_USB_BREQ_BITTIMING: gs_breq_bittiming(&req); break;
+ case GS_USB_BREQ_MODE: gs_breq_mode(&req); break;
+ default: usb_do_stall(); break;
+ }
+ } else {
+ usb_do_stall();
+ }
+}
+
+// State tracking dispatch
+static struct task_wake usb_ep0_wake;
+
+void
+usb_notify_ep0(void)
+{
+ sched_wake_task(&usb_ep0_wake);
+}
+
+void
+usb_ep0_task(void)
+{
+ if (!sched_check_wake(&usb_ep0_wake))
+ return;
+ if (usb_xfer_flags)
+ usb_do_xfer(usb_xfer_data, usb_xfer_size, usb_xfer_flags);
+ else
+ usb_state_ready();
+}
+DECL_TASK(usb_ep0_task);
+
+void
+usb_shutdown(void)
+{
+ usb_notify_bulk_in();
+ usb_notify_bulk_out();
+ usb_notify_ep0();
+}
+DECL_SHUTDOWN(usb_shutdown);
diff --git a/src/generic/usbstd.h b/src/generic/usbstd.h
index 07d0c1cad..2cec37dfd 100644
--- a/src/generic/usbstd.h
+++ b/src/generic/usbstd.h
@@ -8,6 +8,12 @@
#define USB_DIR_OUT 0 /* to device */
#define USB_DIR_IN 0x80 /* to host */
+#define USB_TYPE_MASK (0x03 << 5)
+#define USB_TYPE_STANDARD (0x00 << 5)
+#define USB_TYPE_CLASS (0x01 << 5)
+#define USB_TYPE_VENDOR (0x02 << 5)
+#define USB_TYPE_RESERVED (0x03 << 5)
+
#define USB_REQ_GET_STATUS 0x00
#define USB_REQ_CLEAR_FEATURE 0x01
#define USB_REQ_SET_FEATURE 0x03
diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig
index b1035ec10..355b132e5 100644
--- a/src/stm32/Kconfig
+++ b/src/stm32/Kconfig
@@ -108,6 +108,12 @@ config HAVE_STM32_CANBUS
config HAVE_STM32_FDCANBUS
bool
default y if MACH_STM32G0
+config HAVE_STM32_USBCANBUS
+ bool
+ depends on HAVE_STM32_USBFS || HAVE_STM32_USBOTG
+ depends on HAVE_STM32_CANBUS || HAVE_STM32_FDCANBUS
+ depends on !MACH_STM32F103
+ default y
config MCU
string
@@ -327,30 +333,74 @@ choice
bool "CAN bus (on PA9/PA10)" if LOW_LEVEL_OPTIONS
depends on HAVE_STM32_CANBUS && MACH_STM32F042
select CANSERIAL
- config STM32_CANBUS_PB8_PB9
+ config STM32_MMENU_CANBUS_PB8_PB9
bool "CAN bus (on PB8/PB9)" if LOW_LEVEL_OPTIONS
depends on HAVE_STM32_CANBUS || HAVE_STM32_FDCANBUS
select CANSERIAL
- config STM32_CANBUS_PI9_PH13
+ config STM32_MMENU_CANBUS_PI9_PH13
bool "CAN bus (on PI9/PH13)" if LOW_LEVEL_OPTIONS
depends on HAVE_STM32_CANBUS && MACH_STM32F4
select CANSERIAL
- config STM32_CANBUS_PB5_PB6
+ config STM32_MMENU_CANBUS_PB5_PB6
bool "CAN bus (on PB5/PB6)" if LOW_LEVEL_OPTIONS
depends on HAVE_STM32_CANBUS && MACH_STM32F4
select CANSERIAL
- config STM32_CANBUS_PB12_PB13
+ config STM32_MMENU_CANBUS_PB12_PB13
bool "CAN bus (on PB12/PB13)" if LOW_LEVEL_OPTIONS
depends on HAVE_STM32_CANBUS && MACH_STM32F4
select CANSERIAL
- config STM32_CANBUS_PD0_PD1
+ config STM32_MMENU_CANBUS_PD0_PD1
bool "CAN bus (on PD0/PD1)" if LOW_LEVEL_OPTIONS
depends on HAVE_STM32_CANBUS
select CANSERIAL
- config STM32_CANBUS_PB0_PB1
+ config STM32_MMENU_CANBUS_PB0_PB1
bool "CAN bus (on PB0/PB1)"
depends on HAVE_STM32_FDCANBUS
select CANSERIAL
+ config STM32_USBCANBUS_PA11_PA12
+ bool "USB to CAN bus bridge (USB on PA11/PA12)"
+ depends on HAVE_STM32_USBCANBUS
+ select USBCANBUS
+endchoice
+choice
+ prompt "CAN bus interface" if USBCANBUS
+ config STM32_CMENU_CANBUS_PB8_PB9
+ bool "CAN bus (on PB8/PB9)"
+ config STM32_CMENU_CANBUS_PI9_PH13
+ bool "CAN bus (on PI9/PH13)"
+ depends on HAVE_STM32_CANBUS && MACH_STM32F4
+ config STM32_CMENU_CANBUS_PB5_PB6
+ bool "CAN bus (on PB5/PB6)"
+ depends on HAVE_STM32_CANBUS && MACH_STM32F4
+ config STM32_CMENU_CANBUS_PB12_PB13
+ bool "CAN bus (on PB12/PB13)"
+ depends on HAVE_STM32_CANBUS && MACH_STM32F4
+ config STM32_CMENU_CANBUS_PD0_PD1
+ bool "CAN bus (on PD0/PD1)"
+ depends on HAVE_STM32_CANBUS
+ config STM32_CMENU_CANBUS_PB0_PB1
+ bool "CAN bus (on PB0/PB1)"
+ depends on HAVE_STM32_FDCANBUS
endchoice
+
+config STM32_CANBUS_PB8_PB9
+ bool
+ default y if STM32_MMENU_CANBUS_PB8_PB9 || STM32_CMENU_CANBUS_PB8_PB9
+config STM32_CANBUS_PI9_PH13
+ bool
+ default y if STM32_MMENU_CANBUS_PI9_PH13 || STM32_CMENU_CANBUS_PI9_PH13
+config STM32_CANBUS_PB5_PB6
+ bool
+ default y if STM32_MMENU_CANBUS_PB5_PB6 || STM32_CMENU_CANBUS_PB5_PB6
+config STM32_CANBUS_PB12_PB13
+ bool
+ default y if STM32_MMENU_CANBUS_PB12_PB13 || STM32_CMENU_CANBUS_PB12_PB13
+config STM32_CANBUS_PD0_PD1
+ bool
+ default y if STM32_MMENU_CANBUS_PD0_PD1 || STM32_CMENU_CANBUS_PD0_PD1
+config STM32_CANBUS_PB0_PB1
+ bool
+ default y if STM32_MMENU_CANBUS_PB0_PB1 || STM32_CMENU_CANBUS_PB0_PB1
+
endif
diff --git a/src/stm32/Makefile b/src/stm32/Makefile
index 2fbf75498..2ff04f691 100644
--- a/src/stm32/Makefile
+++ b/src/stm32/Makefile
@@ -63,6 +63,8 @@ canbus-src-y := generic/canserial.c ../lib/fast-hash/fasthash.c
canbus-src-$(CONFIG_HAVE_STM32_CANBUS) += stm32/can.c
canbus-src-$(CONFIG_HAVE_STM32_FDCANBUS) += stm32/fdcan.c
src-$(CONFIG_CANSERIAL) += $(canbus-src-y) generic/canbus.c stm32/chipid.c
+src-$(CONFIG_USBCANBUS) += $(usb-src-y) $(canbus-src-y)
+src-$(CONFIG_USBCANBUS) += stm32/chipid.c generic/usb_canbus.c
src-$(CONFIG_HAVE_GPIO_HARD_PWM) += stm32/hard_pwm.c
# Binary output file rules