]> mj.ucw.cz Git - home-hw.git/blobdiff - bsb/firmware/main.c
BSB: Cleanup
[home-hw.git] / bsb / firmware / main.c
index f5d5f1c1fcdfb2853d6fa64279c62b039bc10e20..bb0c48e53bf140ae98ade0e46e9da73180760dbf 100644 (file)
@@ -5,19 +5,24 @@
  */
 
 #include "util.h"
+#include "interface.h"
 
 #include <libopencm3/cm3/cortex.h>
 #include <libopencm3/cm3/nvic.h>
 #include <libopencm3/cm3/systick.h>
+#include <libopencm3/cm3/scb.h>
 #include <libopencm3/stm32/rcc.h>
 #include <libopencm3/stm32/desig.h>
 #include <libopencm3/stm32/gpio.h>
 #include <libopencm3/stm32/timer.h>
 #include <libopencm3/stm32/usart.h>
+#include <libopencm3/usb/dfu.h>
 #include <libopencm3/usb/usbd.h>
 
 #include <string.h>
 
+/*** Hardware init ***/
+
 static void clock_init(void)
 {
        rcc_clock_setup_in_hse_8mhz_out_72mhz();
@@ -26,28 +31,52 @@ static void clock_init(void)
        rcc_periph_clock_enable(RCC_GPIOB);
        rcc_periph_clock_enable(RCC_GPIOC);
        rcc_periph_clock_enable(RCC_USART1);
+       rcc_periph_clock_enable(RCC_USART3);
        rcc_periph_clock_enable(RCC_USB);
 
        rcc_periph_reset_pulse(RST_GPIOA);
        rcc_periph_reset_pulse(RST_GPIOB);
        rcc_periph_reset_pulse(RST_GPIOC);
        rcc_periph_reset_pulse(RST_USART1);
+       rcc_periph_reset_pulse(RST_USART3);
        rcc_periph_reset_pulse(RST_USB);
 }
 
 static void gpio_init(void)
 {
        // PA9 = TXD1 for debugging console
-       gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
-
        // PA10 = RXD1 for debugging console
+       gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
        gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
 
        // PC13 = BluePill LED
        gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
        gpio_clear(GPIOC, GPIO13);
+
+       // PB0 = yellow LED*, PB1 = green LED*
+       gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO0 | GPIO1);
+       gpio_set(GPIOB, GPIO0 | GPIO1);
+
+       // PB10 = TXD3 for BSB
+       // PB11 = RXD3 for BSB
+       gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO10);
+       gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO11);
+}
+
+static void usart_init(void)
+{
+       usart_set_baudrate(USART1, 115200);
+       usart_set_databits(USART1, 8);
+       usart_set_stopbits(USART1, USART_STOPBITS_1);
+       usart_set_mode(USART1, USART_MODE_TX);
+       usart_set_parity(USART1, USART_PARITY_NONE);
+       usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
+
+       usart_enable(USART1);
 }
 
+/*** System ticks ***/
+
 static volatile u32 ms_ticks;
 
 void sys_tick_handler(void)
@@ -57,7 +86,7 @@ void sys_tick_handler(void)
 
 static void tick_init(void)
 {
-       systick_set_frequency(1000, 72000000);
+       systick_set_frequency(1000, CPU_CLOCK_MHZ * 1000000);
        systick_counter_enable();
        systick_interrupt_enable();
 }
@@ -69,19 +98,394 @@ static void delay_ms(uint ms)
                ;
 }
 
-static void usart_init(void)
+/*** Random generator ***/
+
+static u32 rng_state;
+
+static u32 random_u32(void)
 {
-       usart_set_baudrate(USART1, 115200);
-       usart_set_databits(USART1, 8);
-       usart_set_stopbits(USART1, USART_STOPBITS_1);
-       usart_set_mode(USART1, USART_MODE_TX_RX);
-       usart_set_parity(USART1, USART_PARITY_NONE);
-       usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
+       rng_state *= 2654289733;
+       return rng_state;
+}
 
-       usart_enable(USART1);
+static void random_init(void)
+{
+       u32 desig[3];
+       desig_get_unique_id(desig);
+       rng_state = desig[0] ^ desig[1] ^ desig[2];
+}
+
+/*** BSB ***/
+
+#define BSB_MAX_SIZE 32
+#define BSB_RX_TIMEOUT 10      // If no byte was received for this time, end the current frame [ms]
+#define BSB_TX_IDLE_LINE 100   // Consider the line idle if no byte was received for this time [ms]
+#define BSB_TX_MAX_ATTEMPTS 3
+#define BSB_TX_WAIT_LOG 8      // base 2 log of pre-frame waiting time in ms
+#define BSB_TX_TIMEOUT 10000   // Total time to transmit a frame including all retries and waiting for reply [ms]
+#define BSB_REPLY_TIMEOUT 1000 // How long to wait for a reply [ms]
+
+#undef DEBUG_BSB
+#ifdef DEBUG_BSB
+#define BSB_DEBUG(x) debug_putc(x)
+#else
+#define BSB_DEBUG(x) do { } while (0)
+#endif
+
+struct bsb_state {
+       byte rx_len;                    // Bytes received so far
+       byte rx_led;                    // Countdown to turning RX LED off
+       u16 rx_crc;
+       volatile u32 rx_timestamp;      // Last byte received
+
+       byte tx_state;                  // TX_STATE_xxx
+       byte tx_len;
+       byte tx_send_index;
+       byte tx_check_index;
+       byte tx_attempts;
+       byte tx_result;                 // TX_RESULT_xxx
+       u32 tx_start_timestamp;
+       u32 tx_ticks_to_send;
+       u32 tx_end_timestamp;
+       u32 reply_type_mask;            // Expected reply types (0=no reply)
+
+       byte rx_buf[BSB_MAX_SIZE];
+       byte tx_buf[BSB_MAX_SIZE];
+};
+
+static struct bsb_state bsb;
+
+// Passing received frames and status codes to the main loop
+static byte bsb_rx_status_and_frame[1 + BSB_MAX_SIZE];
+static volatile byte bsb_rx_frame_len;
+static byte bsb_tx_result;
+
+enum bsb_tx_state {
+       TX_STATE_OFF,
+       TX_STATE_WAITING_FOR_IDLE_LINE,
+       TX_STATE_PRE_PACKET_DELAY,
+       TX_STATE_SENDING,
+       TX_STATE_SENT,
+       TX_STATE_WAITING_FOR_REPLY,
+       TX_STATE_COLLISION,
+};
+
+static struct bsb_stats bsb_stats;
+
+static void bsb_attempt_tx(void);
+
+static u16 bsb_crc_update(u16 crc, byte data)
+{
+       crc = crc ^ (data << 8);
+       for (uint i=0; i<8; i++) {
+               if (crc & 0x8000)
+                       crc = (crc << 1) ^ 0x1021;
+               else
+                       crc <<= 1;
+       }
+       return crc;
+}
+
+void usart3_isr(void)
+{
+       u32 status = USART_SR(USART3);
+
+       if (status & USART_SR_RXNE) {
+               uint ch = ~usart_recv(USART3) & 0xff;
+               bsb.rx_timestamp = ms_ticks;
+#ifdef DEBUG_BSB
+               debug_printf(" %02x", ch);
+#endif
+
+               if (bsb.tx_state == TX_STATE_SENDING) {
+                       // We are hearing the echo of a transmitted packet, check it for collisions
+                       if (status & (USART_SR_FE | USART_SR_ORE | USART_SR_NE)) {
+                               BSB_DEBUG('e');
+                               bsb.tx_state = TX_STATE_COLLISION;
+                       } else if (ch == bsb.tx_buf[bsb.tx_check_index++]) {
+                               if (bsb.tx_check_index >= bsb.tx_len) {
+                                       BSB_DEBUG('d');
+                                       bsb.tx_state = TX_STATE_SENT;
+                               }
+                       } else {
+                               BSB_DEBUG('c');
+                               bsb.tx_state = TX_STATE_COLLISION;
+                       }
+               } else {
+                       // Regular receive
+                       if (status & (USART_SR_FE | USART_SR_ORE | USART_SR_NE)) {
+                               bsb_stats.rx_errors++;
+                               bsb.rx_len = 0;
+                       } else if (!bsb.rx_len) {
+                               // Start of frame
+                               if (ch == 0xdc) {
+                                       bsb.rx_buf[bsb.rx_len++] = ch;
+                                       bsb.rx_crc = bsb_crc_update(0, ch);
+                                       BSB_DEBUG('<');
+                               } else {
+                                       bsb_stats.rx_noise++;
+                                       BSB_DEBUG('?');
+                               }
+                       } else {
+                               bsb.rx_buf[bsb.rx_len++] = ch;
+                               bsb.rx_crc = bsb_crc_update(bsb.rx_crc, ch);
+                               if (bsb.rx_len < 4) {
+                                       // First three bytes: SOF, source addr, destination addr
+                               } else if (bsb.rx_len == 4) {
+                                       // Received length byte
+                                       if (bsb.rx_buf[BF_LEN] < 7 || bsb.rx_buf[BF_LEN] > BSB_MAX_SIZE) {
+                                               bsb_stats.rx_invalid++;
+                                               bsb.rx_len = 0;
+                                               BSB_DEBUG('L');
+                                       }
+                               } else if (bsb.rx_len == bsb.rx_buf[BF_LEN]) {
+                                       // Received a complete frame: check CRC and pass it to the main loop
+                                       if (bsb.rx_crc) {
+                                               bsb_stats.rx_bad_crc++;
+                                               BSB_DEBUG('C');
+                                       } else {
+                                               if (bsb_rx_frame_len) {
+                                                       // The previous one was not sent yet
+                                                       bsb_stats.rx_overruns++;
+                                                       BSB_DEBUG('O');
+                                               } else {
+                                                       bsb_rx_frame_len = bsb.rx_buf[BF_LEN];
+                                                       memcpy(bsb_rx_status_and_frame + 1, bsb.rx_buf, bsb_rx_frame_len);
+                                                       bsb_stats.rx_ok++;
+                                                       BSB_DEBUG('.');
+                                                       // Will match with expected replies in main loop
+                                               }
+                                               gpio_clear(GPIOB, GPIO1);
+                                               bsb.rx_led = 100;
+                                       }
+                                       bsb.rx_len = 0;
+                               }
+                       }
+               }
+       }
+
+       if (status & USART_SR_TXE) {
+               if (bsb.tx_state == TX_STATE_SENDING && bsb.tx_send_index < bsb.tx_len) {
+                       BSB_DEBUG('>');
+                       usart_send(USART3, bsb.tx_buf[bsb.tx_send_index++] ^ 0xff);
+               } else {
+                       usart_disable_tx_interrupt(USART3);
+                       // Main loop will be notified by receiving the echo of our frame,
+                       // so we do not need to wait for the "transmit complete" flag.
+               }
+       }
+}
+
+static void bsb_rx_step(void)
+{
+       cm_disable_interrupts();
+       barrier();
+       if (bsb.rx_len && ms_ticks - bsb.rx_timestamp >= BSB_RX_TIMEOUT) {
+               bsb.rx_len = 0;
+               bsb_stats.rx_timeouts++;
+               BSB_DEBUG('T');
+       }
+       if (bsb.rx_led) {
+               if (!--bsb.rx_led)
+                       gpio_set(GPIOB, GPIO1);
+       }
+       cm_enable_interrupts();
+}
+
+static void bsb_tx_step(void)
+{
+       switch (bsb.tx_state) {
+               case TX_STATE_WAITING_FOR_IDLE_LINE:
+                       if (ms_ticks - bsb.rx_timestamp < BSB_TX_IDLE_LINE)
+                               return;
+                       bsb.tx_state = TX_STATE_PRE_PACKET_DELAY;
+                       BSB_DEBUG('i');
+                       // Fall thru
+               case TX_STATE_PRE_PACKET_DELAY:
+                       if (ms_ticks - bsb.tx_start_timestamp > BSB_TX_TIMEOUT) {
+                               bsb.tx_state = TX_STATE_OFF;
+                               bsb_tx_result = TX_RESULT_TIMEOUT;
+                               bsb_stats.tx_timeouts++;
+                               return;
+                       }
+                       if (ms_ticks - bsb.rx_timestamp < BSB_TX_IDLE_LINE) {
+                               bsb.tx_state = TX_STATE_WAITING_FOR_IDLE_LINE;
+                               BSB_DEBUG('n');
+                               return;
+                       }
+                       if (--bsb.tx_ticks_to_send)
+                               return;
+                       BSB_DEBUG('s');
+                       bsb.rx_timestamp = ms_ticks;
+                       barrier();
+                       bsb.tx_state = TX_STATE_SENDING;
+                       barrier();
+                       usart_enable_tx_interrupt(USART3);
+                       return;
+               case TX_STATE_SENDING:
+                       // In this state, we can race with the USART interrupt handler
+                       cm_disable_interrupts();
+                       if (bsb.tx_state == TX_STATE_SENDING && ms_ticks - bsb.rx_timestamp >= BSB_RX_TIMEOUT) {
+                               // The echo of our packet was truncated due to collision
+                               bsb.tx_state = TX_STATE_COLLISION;
+                               BSB_DEBUG('t');
+                       }
+                       cm_enable_interrupts();
+                       return;
+               case TX_STATE_COLLISION:
+                       bsb_stats.tx_collisions++;
+                       bsb_attempt_tx();
+                       return;
+               case TX_STATE_SENT:
+                       if (!bsb.reply_type_mask) {
+                               // The frame needs no reply => confirm transmission
+                               bsb_stats.tx_ok_no_reply++;
+                               bsb.tx_state = TX_STATE_OFF;
+                               bsb_tx_result = TX_RESULT_OK;
+                       } else {
+                               bsb.tx_state = TX_STATE_WAITING_FOR_REPLY;
+                               bsb.tx_end_timestamp = ms_ticks;
+                               BSB_DEBUG('w');
+                       }
+                       return;
+               case TX_STATE_WAITING_FOR_REPLY:
+                       if (ms_ticks - bsb.tx_end_timestamp > BSB_REPLY_TIMEOUT) {
+                               // We did not receive a reply. Maybe the request was lost? Re-transmit it.
+                               bsb_stats.tx_reply_timeouts++;
+                               BSB_DEBUG('t');
+                               bsb_attempt_tx();
+                       }
+                       return;
+               default:
+                       ;
+       }
+}
+
+static void bsb_attempt_tx(void)
+{
+       if (bsb.tx_attempts++ >= BSB_TX_MAX_ATTEMPTS) {
+               bsb.tx_state = TX_STATE_OFF;
+               bsb_tx_result = TX_RESULT_TOO_MANY_RETRIES;
+               return;
+       }
+
+       bsb.tx_ticks_to_send = (random_u32() | 0x80000000) >> (32 - BSB_TX_WAIT_LOG - (bsb.tx_attempts-1));
+
+       bsb.tx_state = TX_STATE_WAITING_FOR_IDLE_LINE;
+       bsb.tx_send_index = 0;
+       bsb.tx_check_index = 0;
+       debug_printf("TX: Attempt #%u, wait for %u ms\n", bsb.tx_attempts, (uint) bsb.tx_ticks_to_send);
+}
+
+static void bsb_send(byte *buf, uint len)
+{
+       // If a transmit is in progress, declare overrrun
+       if (bsb_tx_result != TX_RESULT_NONE) {
+               bsb_stats.tx_overruns++;
+               return;
+       }
+       if (bsb.tx_state != TX_STATE_OFF) {
+               bsb_stats.tx_overruns++;
+               bsb_tx_result = TX_RESULT_OVERRUN;
+               return;
+       }
+
+       // Check frame validity
+       if (len < 7 || len > BSB_MAX_SIZE || buf[BF_SOF] != 0xdc || buf[BF_LEN] != len) {
+               bsb_stats.tx_rejects++;
+               bsb_tx_result = TX_RESULT_MALFORMED;
+               return;
+       }
+
+       // Detect frame type and prepare expected reply types
+       if (buf[BF_OP] == BSB_OP_QUERY) {
+               // So far, we allow only QUERY frames
+               bsb.reply_type_mask = (1 << BSB_OP_ANSWER) | (1 << BSB_OP_ERROR);
+       } else {
+               bsb_stats.tx_rejects++;
+               bsb_tx_result = TX_RESULT_FORBIDDEN;
+               return;
+       }
+
+       // Fill in source address
+       buf[BF_SRC] = 0x80 + BSB_ADDR_GATEWAY;
+
+       // Calculate CRC
+       u16 crc = 0;
+       for (uint i = 0; i < len - 2; i++)
+               crc = bsb_crc_update(crc, buf[i]);
+       buf[len-2] = crc >> 8;
+       buf[len-1] = crc;
+       debug_printf("TX: Prepared frame: type=%02x dest=%02x len=%u crc=%04x reply=%08x\n", buf[BF_OP], buf[BF_DEST], len, crc, (uint) bsb.reply_type_mask);
+
+       // Queue frame for transmission
+       memcpy(bsb.tx_buf, buf, len);
+       bsb.tx_len = len;
+       bsb.tx_start_timestamp = ms_ticks;
+       bsb.tx_attempts = 0;
+       bsb_attempt_tx();
+
+       // Light the TX LED
+       gpio_clear(GPIOB, GPIO0);
+}
+
+static void bsb_check_reply(void)
+{
+       // Match received frame in bsb_rx_status_and_frame to expected replies
+       // and set the status in byte 0.
+
+       byte *frame = bsb_rx_status_and_frame + 1;
+       // XXX: This might be executed too fast, so we can still be in TX_STATE_SENT
+       if (bsb.tx_state == TX_STATE_WAITING_FOR_REPLY || bsb.tx_state == TX_STATE_SENT) {
+               if (frame[BF_DEST] == BSB_ADDR_GATEWAY &&
+                   frame[BF_SRC] == bsb.tx_buf[BF_DEST] ^ 0x80 &&
+                   frame[BF_OP] < 32 &&
+                   bsb.reply_type_mask & (1 << frame[BF_OP])) {
+                       debug_printf("BSB: Matched reply\n");
+                       bsb.tx_state = TX_STATE_OFF;
+                       bsb_stats.tx_ok_replied++;
+                       bsb_rx_status_and_frame[0] = TX_RESULT_OK;
+                       gpio_set(GPIOB, GPIO0);         // Turn the TX LED off
+                       return;
+               }
+       }
+
+       bsb_rx_status_and_frame[0] = TX_RESULT_NONE;
+}
+
+static void bsb_init(void)
+{
+       usart_set_baudrate(USART3, 4800);
+       usart_set_databits(USART3, 9);
+       usart_set_stopbits(USART3, USART_STOPBITS_1);
+       usart_set_mode(USART3, USART_MODE_TX_RX);
+       usart_set_parity(USART3, USART_PARITY_ODD);
+       usart_set_flow_control(USART3, USART_FLOWCONTROL_NONE);
+
+       usart_enable(USART3);
+       nvic_enable_irq(NVIC_USART3_IRQ);
+       usart_enable_rx_interrupt(USART3);
 }
 
-static const struct usb_device_descriptor dev = {
+/*** USB ***/
+
+static usbd_device *usbd_dev;
+
+enum usb_string {
+       STR_MANUFACTURER = 1,
+       STR_PRODUCT,
+       STR_SERIAL,
+};
+
+static char usb_serial_number[13];
+
+static const char *usb_strings[] = {
+       "United Computer Wizards",
+       "BSB Gateway",
+       usb_serial_number,
+};
+
+static const struct usb_device_descriptor device = {
        .bLength = USB_DT_DEVICE_SIZE,
        .bDescriptorType = USB_DT_DEVICE,
        .bcdUSB = 0x0200,
@@ -89,23 +493,25 @@ static const struct usb_device_descriptor dev = {
        .bDeviceSubClass = 0,
        .bDeviceProtocol = 0,
        .bMaxPacketSize0 = 64,
-       .idVendor = 0x4242,
-       .idProduct = 0x0003,
-       .bcdDevice = 0x0200,
-       .iManufacturer = 1,
-       .iProduct = 2,
-       .iSerialNumber = 3,
+       .idVendor = BSB_USB_VENDOR,
+       .idProduct = BSB_USB_PRODUCT,
+       .bcdDevice = BSB_USB_VERSION,
+       .iManufacturer = STR_MANUFACTURER,
+       .iProduct = STR_PRODUCT,
+       .iSerialNumber = STR_SERIAL,
        .bNumConfigurations = 1,
 };
 
 static const struct usb_endpoint_descriptor endpoints[] = {{
+       // Bulk end-point for sending frames to BSB
        .bLength = USB_DT_ENDPOINT_SIZE,
        .bDescriptorType = USB_DT_ENDPOINT,
-       .bEndpointAddress = 0x81,
+       .bEndpointAddress = 0x01,
        .bmAttributes = USB_ENDPOINT_ATTR_BULK,
        .wMaxPacketSize = 64,
        .bInterval = 1,
 },{
+       // Interrupt end-point for receiving frames from BSB
        .bLength = USB_DT_ENDPOINT_SIZE,
        .bDescriptorType = USB_DT_ENDPOINT,
        .bEndpointAddress = 0x82,
@@ -127,16 +533,43 @@ static const struct usb_interface_descriptor iface = {
        .endpoint = endpoints,
 };
 
+static const struct usb_dfu_descriptor dfu_function = {
+       .bLength = sizeof(struct usb_dfu_descriptor),
+       .bDescriptorType = DFU_FUNCTIONAL,
+       .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH,
+       .wDetachTimeout = 255,
+       .wTransferSize = 1024,
+       .bcdDFUVersion = 0x0100,
+};
+
+static const struct usb_interface_descriptor dfu_iface = {
+       .bLength = USB_DT_INTERFACE_SIZE,
+       .bDescriptorType = USB_DT_INTERFACE,
+       .bInterfaceNumber = 1,
+       .bAlternateSetting = 0,
+       .bNumEndpoints = 0,
+       .bInterfaceClass = 0xFE,
+       .bInterfaceSubClass = 1,
+       .bInterfaceProtocol = 1,
+       .iInterface = 0,
+
+       .extra = &dfu_function,
+       .extralen = sizeof(dfu_function),
+};
+
 static const struct usb_interface ifaces[] = {{
        .num_altsetting = 1,
        .altsetting = &iface,
+}, {
+       .num_altsetting = 1,
+       .altsetting = &dfu_iface,
 }};
 
 static const struct usb_config_descriptor config = {
        .bLength = USB_DT_CONFIGURATION_SIZE,
        .bDescriptorType = USB_DT_CONFIGURATION,
        .wTotalLength = 0,
-       .bNumInterfaces = 1,
+       .bNumInterfaces = 2,
        .bConfigurationValue = 1,
        .iConfiguration = 0,
        .bmAttributes = 0x80,
@@ -144,57 +577,80 @@ static const struct usb_config_descriptor config = {
        .interface = ifaces,
 };
 
-static char usb_serial_number[13];
-
-static const char *usb_strings[] = {
-       "United Computer Wizards",
-       "BSB Gateway",
-       usb_serial_number,
-};
-
 static byte usb_configured;
+static byte usb_ep82_pending;
 static uint8_t usbd_control_buffer[64];
 
-static enum usbd_request_return_codes control_cb(usbd_device *usbd_dev,
+static enum usbd_request_return_codes control_cb(
+       usbd_device *dev UNUSED,
        struct usb_setup_data *req,
        uint8_t **buf,
        uint16_t *len,
-       void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req) UNUSED)
+       void (**complete)(usbd_device *dev, struct usb_setup_data *req) UNUSED)
 {
        if (req->bmRequestType != 0xc0 || req->bRequest != 0x00)
                return USBD_REQ_NOTSUPP;
 
-       (void) usbd_dev;
-       (void) buf;
-       (void) len;
-       (void) complete;
-       debug_printf("USB: Control request\n");
+       // We support reading of statistics via control requests
+       debug_printf("USB: Reading statistics\n");
+       uint n = MIN(*len, sizeof(bsb_stats));
+       memcpy(*buf, (char *) &bsb_stats, n);
+       *len = n;
+
+       return USBD_REQ_HANDLED;
+}
 
-       byte *b = *buf;
-       b[0] = 0x12;
-       b[1] = 0x34;
-       b[2] = 0x56;
-       b[3] = 0x78;
-       if (*len > 4)
-               *len = 4;
+static void dfu_detach_complete(usbd_device *dev UNUSED, struct usb_setup_data *req UNUSED)
+{
+       // Reset to bootloader, which implements the rest of DFU
+       debug_printf("Switching to DFU\n");
+       debug_flush();
+       scb_reset_core();
+}
 
+static enum usbd_request_return_codes dfu_control_cb(usbd_device *dev UNUSED,
+       struct usb_setup_data *req,
+       uint8_t **buf UNUSED,
+       uint16_t *len UNUSED,
+       void (**complete)(usbd_device *dev, struct usb_setup_data *req))
+{
+       if (req->bmRequestType != 0x21 || req->bRequest != DFU_DETACH)
+               return USBD_REQ_NOTSUPP;
+
+       *complete = dfu_detach_complete;
        return USBD_REQ_HANDLED;
 }
 
-static void ep81_cb(usbd_device *usbd_dev, uint8_t ep UNUSED)
+static void ep01_cb(usbd_device *dev, uint8_t ep UNUSED)
 {
-       byte buf[4];
-       put_u32_be(buf, ms_ticks);
-       usbd_ep_write_packet(usbd_dev, 0x81, buf, sizeof(buf));
-       debug_printf("USB: Bulk write\n");
+       // We received a frame from the USB host
+       byte buf[64];
+       uint len = usbd_ep_read_packet(dev, 0x01, buf, sizeof(buf));
+       debug_printf("USB: Host sent %u bytes\n", len);
+       bsb_send(buf, len);
 }
 
-static void set_config_cb(usbd_device *usbd_dev, uint16_t wValue UNUSED)
+static void ep82_cb(usbd_device *dev UNUSED, uint8_t ep UNUSED)
 {
-       usbd_register_control_callback(usbd_dev, USB_REQ_TYPE_VENDOR, USB_REQ_TYPE_TYPE, control_cb);
-       usbd_ep_setup(usbd_dev, 0x81, USB_ENDPOINT_ATTR_BULK, 64, ep81_cb);
-       usbd_ep_setup(usbd_dev, 0x82, USB_ENDPOINT_ATTR_BULK, 64, NULL);
-       ep81_cb(usbd_dev, 0);
+       // The frame was accepted by the USB host
+       debug_printf("USB: Interrupt done\n");
+       usb_ep82_pending = 0;
+}
+
+static void set_config_cb(usbd_device *dev, uint16_t wValue UNUSED)
+{
+       usbd_register_control_callback(
+               dev,
+               USB_REQ_TYPE_VENDOR,
+               USB_REQ_TYPE_TYPE,
+               control_cb);
+       usbd_register_control_callback(
+               dev,
+               USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
+               USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
+               dfu_control_cb);
+       usbd_ep_setup(dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, ep01_cb);
+       usbd_ep_setup(dev, 0x82, USB_ENDPOINT_ATTR_INTERRUPT, 64, ep82_cb);
        usb_configured = 1;
 }
 
@@ -202,8 +658,48 @@ static void reset_cb(void)
 {
        debug_printf("USB: Reset\n");
        usb_configured = 0;
+       usb_ep82_pending = 0;
+}
+
+static volatile bool usb_event_pending;
+
+void usb_lp_can_rx0_isr(void)
+{
+       /*
+        *  We handle USB in the main loop to avoid race conditions between
+        *  USB interrupts and other code. However, we need an interrupt to
+        *  up the main loop from sleep.
+        *
+        *  We set up only the low-priority ISR, because high-priority ISR handles
+        *  only double-buffered bulk transfers and isochronous transfers.
+        */
+       nvic_disable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
+       usb_event_pending = 1;
+}
+
+static void usb_init(void)
+{
+       // Simulate USB disconnect
+       gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO11 | GPIO12);
+       gpio_clear(GPIOA, GPIO11 | GPIO12);
+       delay_ms(100);
+
+       usbd_dev = usbd_init(
+               &st_usbfs_v1_usb_driver,
+               &device,
+               &config,
+               usb_strings,
+               ARRAY_SIZE(usb_strings),
+               usbd_control_buffer,
+               sizeof(usbd_control_buffer)
+       );
+       usbd_register_reset_callback(usbd_dev, reset_cb);
+       usbd_register_set_config_callback(usbd_dev, set_config_cb);
+       usb_event_pending = 1;
 }
 
+/*** Main ***/
+
 int main(void)
 {
        clock_init();
@@ -211,33 +707,59 @@ int main(void)
        tick_init();
        usart_init();
        desig_get_unique_id_as_dfu(usb_serial_number);
+       random_init();
 
-       debug_printf("Hello, kitty!\n");
+       debug_printf("Hail, Lord Damian! Thy BSB interface is ready.\n");
 
-       // Simulate USB disconnect
-       gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO11 | GPIO12);
-       gpio_clear(GPIOA, GPIO11 | GPIO12);
-       delay_ms(1000);
+       bsb_init();
+       usb_init();
 
-       usbd_device *usbd_dev = usbd_init(&st_usbfs_v1_usb_driver, &dev, &config, usb_strings, ARRAY_SIZE(usb_strings), usbd_control_buffer, sizeof(usbd_control_buffer));
-       usbd_register_reset_callback(usbd_dev, reset_cb);
-       usbd_register_set_config_callback(usbd_dev, set_config_cb);
-       u32 last_ds_step = 0;
+       u32 last_blink = 0;
+       u32 last_step = 0;
 
        for (;;) {
-               if (ms_ticks - last_ds_step >= 100) {
+               if (ms_ticks - last_blink >= 100) {
                        debug_led_toggle();
-                       last_ds_step = ms_ticks;
-                       if (usb_configured) {
-                               byte x[4];
-                               put_u32_be(x, ms_ticks);
-                               usbd_ep_write_packet(usbd_dev, 0x82, x, 4);
+                       last_blink = ms_ticks;
+               }
+
+               if (usb_event_pending) {
+                       usbd_poll(usbd_dev);
+                       usb_event_pending = 0;
+                       nvic_clear_pending_irq(NVIC_USB_LP_CAN_RX0_IRQ);
+                       nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
+               }
+
+               if (usb_configured) {
+                       // Passing of received frames to USB
+                       if (!usb_ep82_pending) {
+                               if (bsb_rx_frame_len) {
+                                       // Received frame, possibly marked as reply
+                                       barrier();
+                                       bsb_check_reply();
+                                       usbd_ep_write_packet(usbd_dev, 0x82, bsb_rx_status_and_frame, 1 + bsb_rx_frame_len);
+                                       usb_ep82_pending = 1;
+                                       barrier();
+                                       bsb_rx_frame_len = 0;
+                                       debug_printf("USB: Sending frame to interrupt EP\n");
+                               } else if (bsb_tx_result != TX_RESULT_NONE) {
+                                       // Only a transmission result
+                                       usbd_ep_write_packet(usbd_dev, 0x82, &bsb_tx_result, 1);
+                                       usb_ep82_pending = 1;
+                                       gpio_set(GPIOB, GPIO0);         // Turn the TX LED off
+                                       debug_printf("USB: Sending status %u to interrupt EP\n", bsb_tx_result);
+                                       bsb_tx_result = TX_RESULT_NONE;
+                               }
                        }
                }
 
-               // XXX: libopencm3 usbd does not use interrupts at the moment, need to poll...
-               // wait_for_interrupt();
-               usbd_poll(usbd_dev);
+               if (ms_ticks != last_step) {
+                       last_step = ms_ticks;
+                       bsb_rx_step();
+                       bsb_tx_step();
+               }
+
+               wait_for_interrupt();
        }
 
        return 0;