From f416266b349a0778a213dcac138440f5e562fa2c Mon Sep 17 00:00:00 2001 From: mndza Date: Tue, 16 Jan 2024 09:24:54 +0100 Subject: [PATCH 1/8] firmware: 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. 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 an "honor FPGA_ADV" (0xc2) vendor request arrives. --- firmware/src/boards/cynthion_d11/fpga_adv.c | 153 ++++++++++++++++++ firmware/src/boards/cynthion_d21/fpga_adv.c | 29 ++++ firmware/src/boards/daisho/fpga_adv.c | 29 ++++ firmware/src/boards/qtpy/fpga_adv.c | 29 ++++ .../src/boards/samd11_xplained/fpga_adv.c | 29 ++++ firmware/src/fpga_adv.h | 29 ++++ firmware/src/main.c | 7 +- firmware/src/usb_switch.c | 29 ++++ firmware/src/vendor.c | 15 ++ 9 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 firmware/src/boards/cynthion_d11/fpga_adv.c create mode 100644 firmware/src/boards/cynthion_d21/fpga_adv.c create mode 100644 firmware/src/boards/daisho/fpga_adv.c create mode 100644 firmware/src/boards/qtpy/fpga_adv.c create mode 100644 firmware/src/boards/samd11_xplained/fpga_adv.c create mode 100644 firmware/src/fpga_adv.h diff --git a/firmware/src/boards/cynthion_d11/fpga_adv.c b/firmware/src/boards/cynthion_d11/fpga_adv.c new file mode 100644 index 0000000..4af6a9b --- /dev/null +++ b/firmware/src/boards/cynthion_d11/fpga_adv.c @@ -0,0 +1,153 @@ +/** + * FPGA advertisement pin handling code. + * + * This file is part of Apollo. + * + * Copyright (c) 2023 Great Scott Gadgets + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "fpga_adv.h" +#include "usb_switch.h" +#include "apollo_board.h" +#include + +#include +#include +#include +#include + +#ifdef BOARD_HAS_USB_SWITCH + +// 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_byte_received_cb(uint8_t byte, int parity_error); + +#endif + +/** + * Initialize FPGA_ADV receive-only serial port + */ +void fpga_adv_init(void) +{ +#ifdef BOARD_HAS_USB_SWITCH + gpio_set_pin_direction(FPGA_ADV, GPIO_DIRECTION_IN); + gpio_set_pin_pull_mode(FPGA_ADV, GPIO_PULL_UP); + + // 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(); +#endif +} + +/** + * Task for things related with the advertisement pin + */ +void fpga_adv_task(void) +{ +#ifdef BOARD_HAS_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 BOARD_HAS_USB_SWITCH + if (board_millis() - last_phy_adv < TIMEOUT) { + hand_off_usb(); + } +#endif +} + + +#ifdef BOARD_HAS_USB_SWITCH +/** + * 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 \ No newline at end of file diff --git a/firmware/src/boards/cynthion_d21/fpga_adv.c b/firmware/src/boards/cynthion_d21/fpga_adv.c new file mode 100644 index 0000000..b2c85f9 --- /dev/null +++ b/firmware/src/boards/cynthion_d21/fpga_adv.c @@ -0,0 +1,29 @@ +/** + * FPGA advertisement pin handling code. + * + * This file is part of Apollo. + * + * Copyright (c) 2023 Great Scott Gadgets + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + * Initialize FPGA_ADV receive-only serial port + */ +void fpga_adv_init(void) +{ +} + +/** + * Task for things related with the advertisement pin + */ +void fpga_adv_task(void) +{ +} + +/** + * Honor requests from FPGA_ADV again + */ +void honor_fpga_adv(void) +{ +} diff --git a/firmware/src/boards/daisho/fpga_adv.c b/firmware/src/boards/daisho/fpga_adv.c new file mode 100644 index 0000000..b2c85f9 --- /dev/null +++ b/firmware/src/boards/daisho/fpga_adv.c @@ -0,0 +1,29 @@ +/** + * FPGA advertisement pin handling code. + * + * This file is part of Apollo. + * + * Copyright (c) 2023 Great Scott Gadgets + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + * Initialize FPGA_ADV receive-only serial port + */ +void fpga_adv_init(void) +{ +} + +/** + * Task for things related with the advertisement pin + */ +void fpga_adv_task(void) +{ +} + +/** + * Honor requests from FPGA_ADV again + */ +void honor_fpga_adv(void) +{ +} diff --git a/firmware/src/boards/qtpy/fpga_adv.c b/firmware/src/boards/qtpy/fpga_adv.c new file mode 100644 index 0000000..b2c85f9 --- /dev/null +++ b/firmware/src/boards/qtpy/fpga_adv.c @@ -0,0 +1,29 @@ +/** + * FPGA advertisement pin handling code. + * + * This file is part of Apollo. + * + * Copyright (c) 2023 Great Scott Gadgets + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + * Initialize FPGA_ADV receive-only serial port + */ +void fpga_adv_init(void) +{ +} + +/** + * Task for things related with the advertisement pin + */ +void fpga_adv_task(void) +{ +} + +/** + * Honor requests from FPGA_ADV again + */ +void honor_fpga_adv(void) +{ +} diff --git a/firmware/src/boards/samd11_xplained/fpga_adv.c b/firmware/src/boards/samd11_xplained/fpga_adv.c new file mode 100644 index 0000000..b2c85f9 --- /dev/null +++ b/firmware/src/boards/samd11_xplained/fpga_adv.c @@ -0,0 +1,29 @@ +/** + * FPGA advertisement pin handling code. + * + * This file is part of Apollo. + * + * Copyright (c) 2023 Great Scott Gadgets + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + * Initialize FPGA_ADV receive-only serial port + */ +void fpga_adv_init(void) +{ +} + +/** + * Task for things related with the advertisement pin + */ +void fpga_adv_task(void) +{ +} + +/** + * Honor requests from FPGA_ADV again + */ +void honor_fpga_adv(void) +{ +} diff --git a/firmware/src/fpga_adv.h b/firmware/src/fpga_adv.h new file mode 100644 index 0000000..6e9b5ab --- /dev/null +++ b/firmware/src/fpga_adv.h @@ -0,0 +1,29 @@ +/** + * FPGA advertisement pin handling code. + * + * This file is part of Apollo. + * + * Copyright (c) 2023 Great Scott Gadgets + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __FPGA_ADV_H__ +#define __FPGA_ADV_H__ + +/** + * Initialize FPGA_ADV receive-only serial port + */ +void fpga_adv_init(void); + +/** + * Task for things related with the advertisement pin + */ +void fpga_adv_task(void); + +/** + * Honor requests from FPGA_ADV again + */ +void honor_fpga_adv(void); + + +#endif diff --git a/firmware/src/main.c b/firmware/src/main.c index 4be342f..ab3bfba 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -39,6 +39,7 @@ #include "debug_spi.h" #include "usb_switch.h" #include "button.h" +#include "fpga_adv.h" @@ -59,8 +60,8 @@ int main(void) * Interrupted start-up: Force the FPGA offline and take * control of the USB port. */ - force_fpga_offline(); - take_over_usb(); + force_fpga_offline(); + take_over_usb(); /* * Now that the FPGA is being held offline, release the @@ -79,12 +80,14 @@ int main(void) hand_off_usb(); } + fpga_adv_init(); while (1) { tud_task(); // tinyusb device task console_task(); heartbeat_task(); button_task(); + fpga_adv_task(); } return 0; diff --git a/firmware/src/usb_switch.c b/firmware/src/usb_switch.c index 8b13ac7..2b38d54 100644 --- a/firmware/src/usb_switch.c +++ b/firmware/src/usb_switch.c @@ -11,6 +11,18 @@ #include "apollo_board.h" #include +#include +#include + +enum { + SWITCH_UNKNOWN = 0, + SWITCH_MCU = 1, + SWITCH_FPGA = 2, +}; + +#ifdef BOARD_HAS_USB_SWITCH +static int switch_state = SWITCH_UNKNOWN; +#endif /** * Hand off shared USB port to FPGA. @@ -18,8 +30,16 @@ void hand_off_usb(void) { #ifdef BOARD_HAS_USB_SWITCH + if (switch_state == SWITCH_FPGA) 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); + + switch_state = SWITCH_FPGA; #endif } @@ -30,7 +50,16 @@ void hand_off_usb(void) void take_over_usb(void) { #ifdef BOARD_HAS_USB_SWITCH + if (switch_state == SWITCH_MCU) 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(); + + switch_state = SWITCH_MCU; #endif } diff --git a/firmware/src/vendor.c b/firmware/src/vendor.c index 6320db8..a18a5f8 100644 --- a/firmware/src/vendor.c +++ b/firmware/src/vendor.c @@ -19,6 +19,8 @@ #include "fpga.h" //#include "selftest.h" #include "debug_spi.h" +#include "usb_switch.h" +#include "fpga_adv.h" // Supported vendor requests. @@ -44,6 +46,7 @@ enum { // General programming requests. VENDOR_REQUEST_TRIGGER_RECONFIGURATION = 0xc0, VENDOR_REQUEST_FORCE_FPGA_OFFLINE = 0xc1, + VENDOR_REQUEST_HONOR_FPGA_ADV = 0xc2, // @@ -103,6 +106,16 @@ bool handle_force_fpga_offline(uint8_t rhport, tusb_control_request_t const* req } +/** + * Request Apollo to honor FPGA advertisement messages. + */ +bool handle_honor_fpga_adv(uint8_t rhport, tusb_control_request_t const* request) +{ + honor_fpga_adv(); + return tud_control_xfer(rhport, request, NULL, 0); +} + + /** * 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: From 0b4b85548cddc38bc578084e88053077d34bb4b6 Mon Sep 17 00:00:00 2001 From: mndza Date: Mon, 16 Oct 2023 16:55:23 +0200 Subject: [PATCH 2/8] apollo_fpga.gateware: add ApolloAdvertiser When this is added as a submodule to a design, an advertisement message is sent periodically to Apollo. Apollo will take over the port when these announcements are interrupted or when the PROGRAM button is pressed. An optional request handler is added: REQUEST_APOLLO_ADV_STOP (0xf0). It returns the CONTROL port to Apollo by stopping announcements. --- apollo_fpga/gateware/__init__.py | 1 + apollo_fpga/gateware/advertiser.py | 108 +++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 apollo_fpga/gateware/__init__.py create mode 100644 apollo_fpga/gateware/advertiser.py diff --git a/apollo_fpga/gateware/__init__.py b/apollo_fpga/gateware/__init__.py new file mode 100644 index 0000000..1580919 --- /dev/null +++ b/apollo_fpga/gateware/__init__.py @@ -0,0 +1 @@ +from .advertiser import ApolloAdvertiser \ No newline at end of file diff --git a/apollo_fpga/gateware/advertiser.py b/apollo_fpga/gateware/advertiser.py new file mode 100644 index 0000000..24fc190 --- /dev/null +++ b/apollo_fpga/gateware/advertiser.py @@ -0,0 +1,108 @@ +# +# This file is part of LUNA. +# +# Copyright (c) 2023 Great Scott Gadgets +# SPDX-License-Identifier: BSD-3-Clause + +""" Controllers for communicating with Apollo through the FPGA_ADV pin """ + +from amaranth import Elaboratable, Module, Signal, Mux +from amaranth_stdio.serial import AsyncSerialTX + +from luna.gateware.usb.usb2.request import USBRequestHandler +from usb_protocol.types import USBRequestType, USBRequestRecipient + + +class ApolloAdvertiser(Elaboratable): + """ Gateware that implements a periodic announcement to Apollo using the FPGA_ADV pin. + + Currently it is used to tell Apollo that the gateware wants to use the CONTROL port. + Apollo will keep the port switch connected to the FPGA after a reset as long as this + message is received periodically. + Once the port is lost, Apollo will ignore further messages until a specific vendor + request is called. + + I/O ports: + I: stop -- Advertisement messages are stopped if this line is asserted. + """ + def __init__(self): + self.stop = Signal() + + def default_request_handler(self, if_number): + return ApolloAdvertiserRequestHandler(if_number, self.stop) + + def elaborate(self, platform): + m = Module() + + clk_freq = platform.DEFAULT_CLOCK_FREQUENCIES_MHZ["sync"] * 1e6 + + # Communication is done with a serial transmitter (unidirectional) + baudrate = 9600 + divisor = int(clk_freq // baudrate) + fpga_adv = AsyncSerialTX(divisor=divisor, data_bits=8, parity="even") + m.submodules += fpga_adv + + # Counter with 50ms period + period = int(clk_freq * 50e-3) + timer = Signal(range(period)) + m.d.sync += timer.eq(Mux(timer == period-1, 0, timer+1)) + + # Trigger announcement when the counter overflows + m.d.comb += [ + fpga_adv.data .eq(ord('A')), + fpga_adv.ack .eq((timer == 0) & ~self.stop), + ] + + # Drive the FPGA_ADV pin with the serial transmitter + m.d.comb += platform.request("int").o.eq(fpga_adv.o) + + return m + + +class ApolloAdvertiserRequestHandler(USBRequestHandler): + """ Request handler for ApolloAdvertiser. + + Implements default vendor requests related to ApolloAdvertiser. + """ + REQUEST_APOLLO_ADV_STOP = 0xF0 + + def __init__(self, if_number, stop_pin): + super().__init__() + self.if_number = if_number + self.stop_pin = stop_pin + + def elaborate(self, platform): + m = Module() + + interface = self.interface + setup = self.interface.setup + + # + # Vendor request handlers. + + with m.If((setup.type == USBRequestType.VENDOR) & \ + (setup.recipient == USBRequestRecipient.INTERFACE) & \ + (setup.index == self.if_number)): + + with m.Switch(setup.request): + + with m.Case(self.REQUEST_APOLLO_ADV_STOP): + + # Once the receive is complete, respond with an ACK. + with m.If(interface.rx_ready_for_response): + m.d.comb += interface.handshakes_out.ack.eq(1) + + # If we reach the status stage, send a ZLP. + with m.If(interface.status_requested): + m.d.comb += self.send_zlp() + m.d.usb += self.stop_pin.eq(1) + + with m.Case(): + + # + # Stall unhandled requests. + # + with m.If(interface.status_requested | interface.data_requested): + m.d.comb += interface.handshakes_out.stall.eq(1) + + return m From b1e75fbe6684df38fa8b6bbadae8fd45f76b161f Mon Sep 17 00:00:00 2001 From: mndza Date: Mon, 16 Oct 2023 16:58:48 +0200 Subject: [PATCH 3/8] apollo_fpga.cli: FPGA_ADV support, hand off USB port to Apollo if needed The host tools can request gateware to hand off the USB port to Apollo if the necessary request is available (REQUEST_APOLLO_ADV_STOP). --- apollo_fpga/__init__.py | 54 ++++++++++++++++++++++++++++++++++--- apollo_fpga/commands/cli.py | 6 +++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/apollo_fpga/__init__.py b/apollo_fpga/__init__.py index d88c37c..fecb20e 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,35 @@ 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 None: + continue + for cfg in fpga_device: + intf = usb.util.find_descriptor(cfg, bInterfaceClass=0xFF, bInterfaceSubClass=0x00) + if intf is None: + continue + find_again = self._request_handoff_to_apollo(fpga_device, intf.bInterfaceNumber) + 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 +124,17 @@ def __init__(self): self.registers = self.spi + @staticmethod + def _request_handoff_to_apollo(device, intf_number): + """ Requests the gateware to liberate the USB port. """ + REQUEST_APOLLO_ADV_STOP = 0xF0 + request_type = usb.ENDPOINT_OUT | usb.RECIP_INTERFACE | usb.TYPE_VENDOR + try: + device.ctrl_transfer(request_type, REQUEST_APOLLO_ADV_STOP, wIndex=intf_number, timeout=5000) + except usb.USBError: + return False + return True + def detect_connected_version(self): """ Attempts to determine the revision of the connected hardware. @@ -235,6 +277,10 @@ def force_fpga_offline(self): """ Resets the target (FPGA/etc) connected to the debug controller. """ self.out_request(self.REQUEST_FORCE_FPGA_OFFLINE) + def honor_fpga_adv(self): + """ Tell Apollo to honor requests from FPGA_ADV again. Useful after reconfiguration. """ + self.out_request(self.REQUEST_HONOR_FPGA_ADV) + def close(self): """ Closes the USB device so it can be reused, possibly by another ApolloDebugger """ diff --git a/apollo_fpga/commands/cli.py b/apollo_fpga/commands/cli.py index b1b9ccb..aa0c0cb 100755 --- a/apollo_fpga/commands/cli.py +++ b/apollo_fpga/commands/cli.py @@ -130,6 +130,9 @@ def configure_fpga(device, args): programmer.configure(bitstream) + # Let the LUNA gateware take over in devices with shared USB port + device.honor_fpga_adv() + def ensure_unconfigured(device): with device.jtag as jtag: @@ -210,6 +213,9 @@ def reconfigure_fpga(device, args): """ Command that requests the attached ECP5 reconfigure itself from its SPI flash. """ device.soft_reset() + # Let the LUNA gateware take over in devices with shared USB port + device.honor_fpga_adv() + def force_fpga_offline(device, args): """ Command that requests the attached ECP5 be held unconfigured. """ From f68195efa66bb84b41f55d8b1d57896e901dfbfe Mon Sep 17 00:00:00 2001 From: mndza Date: Mon, 4 Dec 2023 10:20:16 +0100 Subject: [PATCH 4/8] apollo_fpga.gateware: convert request handler to new interface --- apollo_fpga/gateware/advertiser.py | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/apollo_fpga/gateware/advertiser.py b/apollo_fpga/gateware/advertiser.py index 24fc190..0b7064c 100644 --- a/apollo_fpga/gateware/advertiser.py +++ b/apollo_fpga/gateware/advertiser.py @@ -84,25 +84,18 @@ def elaborate(self, platform): (setup.recipient == USBRequestRecipient.INTERFACE) & \ (setup.index == self.if_number)): - with m.Switch(setup.request): + with m.If(setup.request == self.REQUEST_APOLLO_ADV_STOP): - with m.Case(self.REQUEST_APOLLO_ADV_STOP): + # Notify that we want to manage this request + m.d.comb += interface.claim.eq(1) - # Once the receive is complete, respond with an ACK. - with m.If(interface.rx_ready_for_response): - m.d.comb += interface.handshakes_out.ack.eq(1) + # Once the receive is complete, respond with an ACK. + with m.If(interface.rx_ready_for_response): + m.d.comb += interface.handshakes_out.ack.eq(1) - # If we reach the status stage, send a ZLP. - with m.If(interface.status_requested): - m.d.comb += self.send_zlp() - m.d.usb += self.stop_pin.eq(1) + # If we reach the status stage, send a ZLP. + with m.If(interface.status_requested): + m.d.comb += self.send_zlp() + m.d.usb += self.stop_pin.eq(1) - with m.Case(): - - # - # Stall unhandled requests. - # - with m.If(interface.status_requested | interface.data_requested): - m.d.comb += interface.handshakes_out.stall.eq(1) - - return m + return m From 3cd4f79132ef608c9c30a086563ee1caefe44611 Mon Sep 17 00:00:00 2001 From: mndza Date: Mon, 22 Jan 2024 11:38:48 +0100 Subject: [PATCH 5/8] firmware: delay `honor_fpga_adv` until request is finished --- firmware/src/vendor.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/firmware/src/vendor.c b/firmware/src/vendor.c index a18a5f8..3a7c1da 100644 --- a/firmware/src/vendor.c +++ b/firmware/src/vendor.c @@ -111,10 +111,15 @@ bool handle_force_fpga_offline(uint8_t rhport, tusb_control_request_t const* req */ bool handle_honor_fpga_adv(uint8_t rhport, tusb_control_request_t const* request) { - honor_fpga_adv(); return tud_control_xfer(rhport, request, NULL, 0); } +bool handle_honor_fpga_adv_finish(uint8_t rhport, tusb_control_request_t const* request) +{ + honor_fpga_adv(); + return true; +} + /** @@ -201,6 +206,19 @@ static bool handle_vendor_request_complete(uint8_t rhport, tusb_control_request_ } +/** + * Called when a vendor request is finished. + */ +static bool handle_vendor_request_finish(uint8_t rhport, tusb_control_request_t const * request) +{ + switch (request->bRequest) { + case VENDOR_REQUEST_HONOR_FPGA_ADV: + return handle_honor_fpga_adv_finish(rhport, request); + default: + return true; + } +} + bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { @@ -209,10 +227,11 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ return handle_vendor_request_setup(rhport, request); case CONTROL_STAGE_DATA: return handle_vendor_request_complete(rhport, request); + case CONTROL_STAGE_ACK: + return handle_vendor_request_finish(rhport, request); default: - return true;; + return true; } - } From 2e2b40e29ee8de79724912793b6b79cf53428085 Mon Sep 17 00:00:00 2001 From: mndza Date: Fri, 1 Mar 2024 12:30:31 +0100 Subject: [PATCH 6/8] apollo_fpga: refactor ApolloDebugger USB handoff support Simplify the control flow and add error messages. --- apollo_fpga/__init__.py | 77 +++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/apollo_fpga/__init__.py b/apollo_fpga/__init__.py index fecb20e..08cfb3f 100644 --- a/apollo_fpga/__init__.py +++ b/apollo_fpga/__init__.py @@ -76,38 +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.APOLLO_USB_IDS: - device = usb.core.find(idVendor=vid, idProduct=pid) - if device is not None: - break + device = self._find_device(self.APOLLO_USB_IDS) - # 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 None: - continue - for cfg in fpga_device: - intf = usb.util.find_descriptor(cfg, bInterfaceClass=0xFF, bInterfaceSubClass=0x00) - if intf is None: - continue - find_again = self._request_handoff_to_apollo(fpga_device, intf.bInterfaceNumber) - 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 Apollo VID/PID is not found, try to find a gateware VID/PID with a valid Apollo stub + # interface. If found, request the gateware to liberate the USB port. In devices with a + # shared port, this effectively hands off the USB port to Apollo. if device is None: - raise DebuggerNotFound() + + # First, find the candidate device... + fpga_device = self._find_device(self.LUNA_USB_IDS, custom_match=self._device_has_stub_iface) + if fpga_device is None: + raise DebuggerNotFound("No Apollo or valid LUNA device found. " + "The LUNA_USB_IDS environment variable can be used to add custom VID:PID pairs.") + + # ... and now request a USB handoff to Apollo + stub_if = self._device_has_stub_iface(fpga_device, return_iface=True) + retry, err_msg = self._request_handoff_to_apollo(fpga_device, stub_if.bInterfaceNumber) + if not retry: + raise DebuggerNotFound(f"Handoff request failed: {err_msg}") + + # Wait for Apollo to enumerate and try again + time.sleep(2) + device = self._find_device(self.APOLLO_USB_IDS) + if device is None: + raise DebuggerNotFound("Handoff was requested, but Apollo is not available") self.device = device self.major, self.minor = self.get_hardware_revision() @@ -123,6 +115,25 @@ def __init__(self): self.spi = DebugSPIConnection(self) self.registers = self.spi + @staticmethod + def _find_device(ids, custom_match=None): + for vid, pid in ids: + device = usb.core.find(idVendor=vid, idProduct=pid, custom_match=custom_match) + if device is not None: + return device + return None + + @staticmethod + def _device_has_stub_iface(device, return_iface=False): + """ Checks if a device has an Apollo stub interface present. + + Optionally return the interface itself. + """ + for cfg in device: + stub_if = usb.util.find_descriptor(cfg, bInterfaceClass=0xFF, bInterfaceSubClass=0x00) + if stub_if is not None: + return stub_if if return_iface else True + return None if return_iface else False @staticmethod def _request_handoff_to_apollo(device, intf_number): @@ -131,9 +142,9 @@ def _request_handoff_to_apollo(device, intf_number): request_type = usb.ENDPOINT_OUT | usb.RECIP_INTERFACE | usb.TYPE_VENDOR try: device.ctrl_transfer(request_type, REQUEST_APOLLO_ADV_STOP, wIndex=intf_number, timeout=5000) - except usb.USBError: - return False - return True + except usb.USBError as e: + return False, e.strerror + return True, "" def detect_connected_version(self): From a4d0c33aad5b0b80f3348bcb6711739e5a80b1c3 Mon Sep 17 00:00:00 2001 From: mndza Date: Fri, 1 Mar 2024 14:31:36 +0100 Subject: [PATCH 7/8] apollo_fpga: add `_request_handoff` class method to `ApolloDebugger` Requests handoff to Apollo using an existing device handle. --- apollo_fpga/__init__.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/apollo_fpga/__init__.py b/apollo_fpga/__init__.py index 08cfb3f..8025093 100644 --- a/apollo_fpga/__init__.py +++ b/apollo_fpga/__init__.py @@ -90,10 +90,10 @@ def __init__(self): "The LUNA_USB_IDS environment variable can be used to add custom VID:PID pairs.") # ... and now request a USB handoff to Apollo - stub_if = self._device_has_stub_iface(fpga_device, return_iface=True) - retry, err_msg = self._request_handoff_to_apollo(fpga_device, stub_if.bInterfaceNumber) - if not retry: - raise DebuggerNotFound(f"Handoff request failed: {err_msg}") + try: + self._request_handoff(fpga_device) + except usb.USBError as e: + raise DebuggerNotFound(f"Handoff request failed: {e.strerror}") # Wait for Apollo to enumerate and try again time.sleep(2) @@ -115,6 +115,20 @@ def __init__(self): self.spi = DebugSPIConnection(self) self.registers = self.spi + @classmethod + def _request_handoff(cls, device): + """ Requests the gateware to liberate the USB port. """ + # Find the Apollo stub interface first + stub_if = cls._device_has_stub_iface(device, return_iface=True) + if stub_if is None: + raise DebuggerNotFound("No Apollo stub interface found") + + # Send the request + intf_number = stub_if.bInterfaceNumber + REQUEST_APOLLO_ADV_STOP = 0xF0 + request_type = usb.ENDPOINT_OUT | usb.RECIP_INTERFACE | usb.TYPE_VENDOR + device.ctrl_transfer(request_type, REQUEST_APOLLO_ADV_STOP, wIndex=intf_number, timeout=5000) + @staticmethod def _find_device(ids, custom_match=None): for vid, pid in ids: @@ -135,18 +149,6 @@ def _device_has_stub_iface(device, return_iface=False): return stub_if if return_iface else True return None if return_iface else False - @staticmethod - def _request_handoff_to_apollo(device, intf_number): - """ Requests the gateware to liberate the USB port. """ - REQUEST_APOLLO_ADV_STOP = 0xF0 - request_type = usb.ENDPOINT_OUT | usb.RECIP_INTERFACE | usb.TYPE_VENDOR - try: - device.ctrl_transfer(request_type, REQUEST_APOLLO_ADV_STOP, wIndex=intf_number, timeout=5000) - except usb.USBError as e: - return False, e.strerror - return True, "" - - def detect_connected_version(self): """ Attempts to determine the revision of the connected hardware. From f411c96049e3efcb2b9d4c8db742e5da9641b9b9 Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Fri, 1 Mar 2024 15:30:51 +0100 Subject: [PATCH 8/8] firmware: Apply suggested style change in `fpga_adv_task` Co-authored-by: Martin Ling --- firmware/src/boards/cynthion_d11/fpga_adv.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/firmware/src/boards/cynthion_d11/fpga_adv.c b/firmware/src/boards/cynthion_d11/fpga_adv.c index 4af6a9b..329d5d0 100644 --- a/firmware/src/boards/cynthion_d11/fpga_adv.c +++ b/firmware/src/boards/cynthion_d11/fpga_adv.c @@ -107,9 +107,10 @@ void fpga_adv_init(void) void fpga_adv_task(void) { #ifdef BOARD_HAS_USB_SWITCH - // Take over USB after timeout - if (board_millis() - last_phy_adv < TIMEOUT) return; - take_over_usb(); + // Take over USB after timeout + if (board_millis() - last_phy_adv >= TIMEOUT) { + take_over_usb(); + } #endif }