From 4eb486dbd5e4c023282c77bbba285f0ddb5c4f11 Mon Sep 17 00:00:00 2001 From: mndza Date: Tue, 29 Aug 2023 16:43:13 +0200 Subject: [PATCH] Add support for one-way FPGA advertisement pin Apollo firmware will keep the USB switch handed over to the FPGA as long as the gateware keeps advertising its usage through the FPGA_ADV pin (FPGA_INT). When these messages stop arriving, Apollo will take over the port. If the advertisement resumes, the port will not be handed off to the FPGA again until a "reconfiguration" (0xc0) or a "honor FPGA_ADV" (0xc2) vendor request arrives. Additionally, the host tools can request a gateware to hand off the USB port to Apollo if the necessary request is available. - firmware: Adds HONOR_FPGA_ADV vendor request (0xc2) - apollo_fpga: Use REQUEST_APOLLO_ADV_STOP (0xf0) to enumerate Apollo --- apollo_fpga/__init__.py | 52 ++++- apollo_fpga/ecp5.py | 2 + firmware/src/boards/daisho/usb_switch.c | 14 ++ firmware/src/boards/luna_d11/usb_switch.c | 189 ++++++++++++++++-- firmware/src/boards/luna_d21/usb_switch.c | 14 ++ firmware/src/boards/qtpy/usb_switch.c | 45 +++++ .../src/boards/samd11_xplained/usb_switch.c | 14 ++ firmware/src/main.c | 2 +- firmware/src/usb_switch.h | 9 + firmware/src/vendor.c | 15 ++ 10 files changed, 338 insertions(+), 18 deletions(-) create mode 100644 firmware/src/boards/qtpy/usb_switch.c diff --git a/apollo_fpga/__init__.py b/apollo_fpga/__init__.py index c2a93e3..aaf1d08 100644 --- a/apollo_fpga/__init__.py +++ b/apollo_fpga/__init__.py @@ -4,6 +4,8 @@ # Copyright (c) 2020 Great Scott Gadgets # SPDX-License-Identifier: BSD-3-Clause +import os +import time import usb.core from .jtag import JTAGChain @@ -32,13 +34,18 @@ def create_ila_frontend(ila, *, use_cs_multiplexing=False): class ApolloDebugger: """ Class representing a link to an Apollo Debug Module. """ - # This VID/PID pair is unique to development LUNA boards. - # TODO: potentially change this to an OpenMoko VID, like other LUNA boards. - USB_IDS = [(0x1d50, 0x615c), (0x16d0, 0x05a5)] + # VID/PID pairs for Apollo and gateware. + APOLLO_USB_IDS = [(0x1d50, 0x615c)] + LUNA_USB_IDS = [(0x1d50, 0x615b)] + + # If we have a LUNA_USB_IDS variable, we can use it to find the LUNA device. + if os.getenv("LUNA_USB_IDS"): + LUNA_USB_IDS += tuple([int(x, 16) for x in os.getenv("LUNA_USB_IDS").split(":")]) REQUEST_SET_LED_PATTERN = 0xa1 REQUEST_RECONFIGURE = 0xc0 REQUEST_FORCE_FPGA_OFFLINE = 0xc1 + REQUEST_HONOR_FPGA_ADV = 0xc2 LED_PATTERN_IDLE = 500 LED_PATTERN_UPLOAD = 50 @@ -69,11 +76,30 @@ def __init__(self): """ Sets up a connection to the debugger. """ # Try to create a connection to our Apollo debug firmware. - for vid, pid in self.USB_IDS: + for vid, pid in self.APOLLO_USB_IDS: device = usb.core.find(idVendor=vid, idProduct=pid) if device is not None: break + # If no Apollo VID/PID is found but a gateware VID/PID is, we request the gateware + # to liberate the USB port. In devices with a shared port, this effectively hands off + # the USB port to Apollo. + find_again = False + if device is None: + for vid, pid in self.LUNA_USB_IDS: + fpga_device = usb.core.find(idVendor=vid, idProduct=pid) + if fpga_device is not None: + find_again = self._request_handoff_to_apollo(fpga_device) + break + + # If we requested a handoff, retry the connection to Apollo debug firmware + if find_again: + time.sleep(2) # wait for Apollo enumeration + for vid, pid in self.APOLLO_USB_IDS: + device = usb.core.find(idVendor=vid, idProduct=pid) + if device is not None: + break + # If we couldn't find an Apollo device, bail out. if device is None: raise DebuggerNotFound() @@ -93,6 +119,17 @@ def __init__(self): self.registers = self.spi + @staticmethod + def _request_handoff_to_apollo(self, device): + """ Requests the gateware to liberate the USB port. """ + REQUEST_APOLLO_ADV_STOP = 0xF0 + request_type = usb.ENDPOINT_OUT | usb.RECIP_DEVICE | usb.TYPE_VENDOR + try: + device.ctrl_transfer(request_type, REQUEST_APOLLO_ADV_STOP, timeout=5000) + except usb.USBError: + return False + return True + def detect_connected_version(self): """ Attempts to determine the revision of the connected hardware. @@ -241,6 +278,13 @@ def force_fpga_offline(self): except usb.core.USBError: pass + def honor_fpga_adv(self): + """ Tell Apollo to honor requests from FPGA_ADV again. Useful after reconfiguration. """ + try: + self.out_request(self.REQUEST_HONOR_FPGA_ADV) + except usb.core.USBError: + pass + def close(self): """ Closes the USB device so it can be reused, possibly by another ApolloDebugger """ diff --git a/apollo_fpga/ecp5.py b/apollo_fpga/ecp5.py index 5c53fe5..3e742e2 100644 --- a/apollo_fpga/ecp5.py +++ b/apollo_fpga/ecp5.py @@ -463,6 +463,8 @@ def configure(self, bitstream): finally: self.chain.debugger.set_led_pattern(self.chain.debugger.LED_PATTERN_IDLE) + # Let the LUNA gateware take over in devices with shared USB port + self.chain.debugger.honor_fpga_adv() def _restart_configuration_process(self): diff --git a/firmware/src/boards/daisho/usb_switch.c b/firmware/src/boards/daisho/usb_switch.c index 21646b3..1c1901c 100644 --- a/firmware/src/boards/daisho/usb_switch.c +++ b/firmware/src/boards/daisho/usb_switch.c @@ -9,6 +9,13 @@ #include "usb_switch.h" +/** + * Initialize USB switch control + */ +void usb_switch_init(void) +{ +} + /** * Hand off shared USB port to FPGA. */ @@ -23,6 +30,13 @@ void take_over_usb(void) { } +/** + * Honor requests from FPGA_ADV again + */ +void honor_fpga_adv(void) +{ +} + /** * Handle switch control user request. */ diff --git a/firmware/src/boards/luna_d11/usb_switch.c b/firmware/src/boards/luna_d11/usb_switch.c index 845e14b..289cf9f 100644 --- a/firmware/src/boards/luna_d11/usb_switch.c +++ b/firmware/src/boards/luna_d11/usb_switch.c @@ -12,17 +12,72 @@ #include "led.h" #include +#include +#include +#include +#include +#include + + +#if (((_BOARD_REVISION_MAJOR_ == 0) && (_BOARD_REVISION_MINOR_ >= 6)) || (_BOARD_REVISION_MAJOR_ == 1)) +#define WITH_USB_SWITCH +#endif + +#ifdef WITH_USB_SWITCH + +static bool control_to_fpga = false; + +// Store the timestamp of the last physical port advertisement +#define TIMEOUT 100UL +static uint32_t last_phy_adv = 0; + +// Create a reference to our SERCOM object. +typedef Sercom sercom_t; +static sercom_t *sercom = SERCOM1; + +static void fpga_adv_init(void); +static void fpga_adv_byte_received_cb(uint8_t byte, int parity_error); + +#endif + +/** + * Initialize USB switch control + */ +void usb_switch_init(void) +{ +#ifndef WITH_USB_SWITCH + gpio_set_pin_pull_mode(PROGRAM_BUTTON, GPIO_PULL_UP); + gpio_set_pin_direction(PROGRAM_BUTTON, GPIO_DIRECTION_IN); +#else + gpio_set_pin_pull_mode(PROGRAM_BUTTON, GPIO_PULL_OFF); + gpio_set_pin_direction(PROGRAM_BUTTON, GPIO_DIRECTION_IN); + + gpio_set_pin_direction(FPGA_INT, GPIO_DIRECTION_IN); + gpio_set_pin_pull_mode(FPGA_INT, GPIO_PULL_UP); + + gpio_set_pin_direction(USB_SWITCH, GPIO_DIRECTION_OUT); + gpio_set_pin_level(USB_SWITCH, false); + control_to_fpga = true; + + fpga_adv_init(); +#endif +} + /** * Hand off shared USB port to FPGA. */ void hand_off_usb(void) { -#if ((_BOARD_REVISION_MAJOR_ == 0) && (_BOARD_REVISION_MINOR_ < 6)) +#ifndef WITH_USB_SWITCH led_on(LED_D); #else + if (control_to_fpga == true) return; + // Disable internal pull-up resistor on D+/D- pins for a moment to force a disconnection + tud_disconnect(); + board_delay(100); gpio_set_pin_level(USB_SWITCH, false); - gpio_set_pin_direction(USB_SWITCH, GPIO_DIRECTION_OUT); led_off(LED_D); + control_to_fpga = true; #endif } @@ -31,11 +86,17 @@ void hand_off_usb(void) */ void take_over_usb(void) { -#if (((_BOARD_REVISION_MAJOR_ == 0) && (_BOARD_REVISION_MINOR_ >= 6)) || (_BOARD_REVISION_MAJOR_ == 1)) +#ifndef WITH_USB_SWITCH + led_on(LED_D); +#else + if (control_to_fpga == false) return; gpio_set_pin_level(USB_SWITCH, true); - gpio_set_pin_direction(USB_SWITCH, GPIO_DIRECTION_OUT); + // Disable internal pull-up resistor on D+/D- pins for a moment to force a disconnection + tud_disconnect(); + board_delay(100); + tud_connect(); + control_to_fpga = false; #endif - led_on(LED_D); } /** @@ -43,14 +104,116 @@ void take_over_usb(void) */ void switch_control_task(void) { -#if ((_BOARD_REVISION_MAJOR_ == 0) && (_BOARD_REVISION_MINOR_ < 6)) - gpio_set_pin_pull_mode(PROGRAM_BUTTON, GPIO_PULL_UP); -#else - gpio_set_pin_pull_mode(PROGRAM_BUTTON, GPIO_PULL_OFF); -#endif - gpio_set_pin_direction(PROGRAM_BUTTON, GPIO_DIRECTION_IN); - gpio_set_pin_direction(FPGA_INT, GPIO_DIRECTION_IN); - if ((gpio_get_pin_level(PROGRAM_BUTTON) == false) || (gpio_get_pin_level(FPGA_INT) == true)) { + if ((gpio_get_pin_level(PROGRAM_BUTTON) == false)) { take_over_usb(); } + +#ifdef WITH_USB_SWITCH + // Take over USB after timeout + if (board_millis() - last_phy_adv < TIMEOUT) return; + take_over_usb(); +#endif +} + +/** + * Honor requests from FPGA_ADV again + */ +void honor_fpga_adv(void) +{ +#ifdef WITH_USB_SWITCH + if (board_millis() - last_phy_adv < TIMEOUT) { + hand_off_usb(); + } +#endif +} + +#ifdef WITH_USB_SWITCH +/** + * Initialize FPGA_ADV receive-only serial port + */ +static void fpga_adv_init(void) +{ + // Disable the SERCOM before configuring it, to 1) ensure we're not transacting + // during configuration; and 2) as many of the registers are R/O when the SERCOM is enabled. + while(sercom->USART.SYNCBUSY.bit.ENABLE); + sercom->USART.CTRLA.bit.ENABLE = 0; + + // Software reset the SERCOM to restore initial values. + while(sercom->USART.SYNCBUSY.bit.SWRST); + sercom->USART.CTRLA.bit.SWRST = 1; + + // The SWRST bit becomes accessible again once the software reset is + // complete -- we'll use this to wait for the reset to be finshed. + while(sercom->USART.SYNCBUSY.bit.SWRST); + + // Ensure we can work with the full SERCOM. + while(sercom->USART.SYNCBUSY.bit.SWRST || sercom->USART.SYNCBUSY.bit.ENABLE); + + // Pinmux the relevant pins to be used for the SERCOM. + gpio_set_pin_function(PIN_PA09, MUX_PA09C_SERCOM1_PAD3); + + // Set up clocking for the SERCOM peripheral. + _pm_enable_bus_clock(PM_BUS_APBC, SERCOM1); + _gclk_enable_channel(SERCOM1_GCLK_ID_CORE, GCLK_CLKCTRL_GEN_GCLK0_Val); + + // Configure the SERCOM for UART mode. + sercom->USART.CTRLA.reg = + SERCOM_USART_CTRLA_DORD | // LSB first + SERCOM_USART_CTRLA_RXPO(3) | // RX on PA09 (PAD[3]) + SERCOM_USART_CTRLA_SAMPR(0) | // use 16x oversampling + SERCOM_USART_CTRLA_FORM(1) | // enable parity + SERCOM_USART_CTRLA_RUNSTDBY | // don't autosuspend the clock + SERCOM_USART_CTRLA_MODE_USART_INT_CLK; // use internal clock + + // Configure our baud divisor. + const uint32_t baudrate = 9600; + const uint32_t baud = (((uint64_t)CONF_CPU_FREQUENCY << 16) - ((uint64_t)baudrate << 20)) / CONF_CPU_FREQUENCY; + sercom->USART.BAUD.reg = baud; + + // Configure TX/RX and framing. + sercom->USART.CTRLB.reg = + SERCOM_USART_CTRLB_CHSIZE(0) | //8-bit words + SERCOM_USART_CTRLB_RXEN; // Enable RX. + + // Wait for our changes to apply. + while (sercom->USART.SYNCBUSY.bit.CTRLB); + + // Enable our receive interrupt, as we want to asynchronously dump data into + // the UART console. + sercom->USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC; + + // Enable the UART IRQ. + NVIC_EnableIRQ(SERCOM1_IRQn); + + // Finally, enable the SERCOM. + sercom->USART.CTRLA.bit.ENABLE = 1; + while(sercom->USART.SYNCBUSY.bit.ENABLE); + + // Update timestamp + last_phy_adv = board_millis(); +} + +/** + * FPGA_ADV interrupt handler. + */ +void SERCOM1_Handler(void) +{ + // If we've just received a character, handle it. + if (sercom->USART.INTFLAG.bit.RXC) + { + // Read the relevant character, which marks this interrupt as serviced. + uint16_t byte = sercom->USART.DATA.reg; + fpga_adv_byte_received_cb(byte, sercom->USART.STATUS.bit.PERR); + } +} + +static void fpga_adv_byte_received_cb(uint8_t byte, int parity_error) { + if (parity_error) { + return; + } + + if (byte == 'A') { + last_phy_adv = board_millis(); + } } +#endif diff --git a/firmware/src/boards/luna_d21/usb_switch.c b/firmware/src/boards/luna_d21/usb_switch.c index 3fc31aa..b253621 100644 --- a/firmware/src/boards/luna_d21/usb_switch.c +++ b/firmware/src/boards/luna_d21/usb_switch.c @@ -10,6 +10,13 @@ #include "led.h" #include "usb_switch.h" +/** + * Initialize USB switch control + */ +void usb_switch_init(void) +{ +} + /** * Hand off shared USB port to FPGA. */ @@ -26,6 +33,13 @@ void take_over_usb(void) led_on(LED_D); } +/** + * Honor requests from FPGA_ADV again + */ +void honor_fpga_adv(void) +{ +} + /** * Handle switch control user request. */ diff --git a/firmware/src/boards/qtpy/usb_switch.c b/firmware/src/boards/qtpy/usb_switch.c new file mode 100644 index 0000000..1c1901c --- /dev/null +++ b/firmware/src/boards/qtpy/usb_switch.c @@ -0,0 +1,45 @@ +/** + * switch control for USB port shared by Apollo and FPGA + * + * This file is part of Apollo. + * + * Copyright (c) 2023 Great Scott Gadgets + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "usb_switch.h" + +/** + * Initialize USB switch control + */ +void usb_switch_init(void) +{ +} + +/** + * Hand off shared USB port to FPGA. + */ +void hand_off_usb(void) +{ +} + +/** + * Take control of USB port from FPGA. + */ +void take_over_usb(void) +{ +} + +/** + * Honor requests from FPGA_ADV again + */ +void honor_fpga_adv(void) +{ +} + +/** + * Handle switch control user request. + */ +void switch_control_task(void) +{ +} diff --git a/firmware/src/boards/samd11_xplained/usb_switch.c b/firmware/src/boards/samd11_xplained/usb_switch.c index 21646b3..1c1901c 100644 --- a/firmware/src/boards/samd11_xplained/usb_switch.c +++ b/firmware/src/boards/samd11_xplained/usb_switch.c @@ -9,6 +9,13 @@ #include "usb_switch.h" +/** + * Initialize USB switch control + */ +void usb_switch_init(void) +{ +} + /** * Hand off shared USB port to FPGA. */ @@ -23,6 +30,13 @@ void take_over_usb(void) { } +/** + * Honor requests from FPGA_ADV again + */ +void honor_fpga_adv(void) +{ +} + /** * Handle switch control user request. */ diff --git a/firmware/src/main.c b/firmware/src/main.c index d5a57c6..007ab7b 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -53,7 +53,7 @@ int main(void) fpga_io_init(); led_init(); debug_spi_init(); - hand_off_usb(); + usb_switch_init(); // Trigger an FPGA reconfiguration; so the FPGA automatically // configures itself from its SPI ROM on reset. This effectively diff --git a/firmware/src/usb_switch.h b/firmware/src/usb_switch.h index 1e9d9be..6262a96 100644 --- a/firmware/src/usb_switch.h +++ b/firmware/src/usb_switch.h @@ -10,6 +10,10 @@ #ifndef __USB_SWITCH_H__ #define __USB_SWITCH_H__ +/** + * Initialize USB switch control + */ +void usb_switch_init(void); /** * Hand off shared USB port to FPGA. @@ -21,6 +25,11 @@ void hand_off_usb(void); */ void take_over_usb(void); +/** + * Honor requests from FPGA_ADV again + */ +void honor_fpga_adv(void); + /** * Handle switch control user request. */ diff --git a/firmware/src/vendor.c b/firmware/src/vendor.c index fe0572f..5105a42 100644 --- a/firmware/src/vendor.c +++ b/firmware/src/vendor.c @@ -19,6 +19,7 @@ #include "fpga.h" //#include "selftest.h" #include "debug_spi.h" +#include "usb_switch.h" // Supported vendor requests. @@ -44,6 +45,7 @@ enum { // General programming requests. VENDOR_REQUEST_TRIGGER_RECONFIGURATION = 0xc0, VENDOR_REQUEST_FORCE_FPGA_OFFLINE = 0xc1, + VENDOR_REQUEST_HONOR_FPGA_ADV = 0xc2, // @@ -89,6 +91,7 @@ bool handle_set_led_pattern(uint8_t rhport, tusb_control_request_t const* reques bool handle_trigger_fpga_reconfiguration(uint8_t rhport, tusb_control_request_t const* request) { trigger_fpga_reconfiguration(); + honor_fpga_adv(); return true; } @@ -103,6 +106,16 @@ bool handle_force_fpga_offline(uint8_t rhport, tusb_control_request_t const* req } +/** + * Request that forces the FPGA offline, preventing bricking. + */ +bool handle_honor_fpga_adv(uint8_t rhport, tusb_control_request_t const* request) +{ + honor_fpga_adv(); + return true; +} + + /** * Primary vendor request handler. @@ -116,6 +129,8 @@ static bool handle_vendor_request_setup(uint8_t rhport, tusb_control_request_t c return handle_trigger_fpga_reconfiguration(rhport, request); case VENDOR_REQUEST_FORCE_FPGA_OFFLINE: return handle_force_fpga_offline(rhport, request); + case VENDOR_REQUEST_HONOR_FPGA_ADV: + return handle_honor_fpga_adv(rhport, request); // JTAG requests case VENDOR_REQUEST_JTAG_CLEAR_OUT_BUFFER: