From 790ff4d8d7ac200527c272af3e58f0d9f06468e4 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 8 Jun 2022 21:03:11 -0400 Subject: [PATCH] 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 --- docs/CANBUS.md | 31 ++ src/Kconfig | 12 +- src/generic/canserial.c | 7 +- src/generic/canserial.h | 2 +- src/generic/usb_canbus.c | 675 +++++++++++++++++++++++++++++++++++++++ src/generic/usbstd.h | 6 + src/stm32/Kconfig | 62 +++- src/stm32/Makefile | 2 + 8 files changed, 782 insertions(+), 15 deletions(-) create mode 100644 src/generic/usb_canbus.c diff --git a/docs/CANBUS.md b/docs/CANBUS.md index fc85dc76..90efacf5 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 921370f6..d8d8a19e 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 ba0ec461..34f0ce65 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 ac4b7714..d7a56d3b 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 00000000..1631bebe --- /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 +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // 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; iwValue) == 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 07d0c1ca..2cec37df 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 b1035ec1..355b132e 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 2fbf7549..2ff04f69 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