Pārlūkot izejas kodu

Add serial logging to GD32F205 builds

Serial logging should work for all PlatformIO environments
based on the GD32F205.

There is an unresolved issue where the the GD32F205 platform library
is being pulled into the RP2040 builds. The quick fix is
to add a lib_ignore to the GD32F205 platform library for RP2040 builds.
This should be figured out though.
Morio 2 gadi atpakaļ
vecāks
revīzija
be12e3cd8e

+ 1 - 1
.github/workflows/firmware_build.yml

@@ -24,7 +24,7 @@ jobs:
       - name: Build firmware
         run: |
           cd ZuluSCSI
-          pio run -v
+          pio run -ve ZuluSCSIv1_2
     
       - name: Rename firmware files
         run: |

+ 47 - 1
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.cpp

@@ -24,11 +24,14 @@
 #include "gd32f20x_fmc.h"
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_config.h"
+#include "usbd_conf.h"
+#include "usb_serial.h"
 #include "greenpak.h"
 #include <SdFat.h>
 #include <scsi.h>
 #include <assert.h>
 
+
 extern "C" {
 
 const char *g_platform_name = PLATFORM_NAME;
@@ -243,6 +246,9 @@ void platform_init()
 
 void platform_late_init()
 {
+    // Initialize usb for CDC serial output
+    usb_fs_init();
+
     logmsg("Platform: ", g_platform_name);
     logmsg("FW Version: ", g_log_firmwareversion);
     
@@ -339,6 +345,35 @@ static void adc_poll()
 #endif
 }
 
+/*****************************************/
+/* Debug logging and watchdog            */
+/*****************************************/
+
+// Send log data to USB UART if USB is connected.
+// Data is retrieved from the shared log ring buffer and
+// this function sends as much as fits in USB CDC buffer.
+
+static void usb_log_poll()
+{
+    static uint32_t logpos = 0;
+
+    if (usb_serial_ready())
+    {
+        // Retrieve pointer to log start and determine number of bytes available.
+        uint32_t available = 0;
+        const char *data = log_get_buffer(&logpos, &available);
+        // Limit to CDC packet size
+        uint32_t len = available;
+        if (len == 0) return;
+        if (len > USB_CDC_DATA_PACKET_SIZE) len = USB_CDC_DATA_PACKET_SIZE;
+
+        // Update log position by the actual number of bytes sent
+        // If USB CDC buffer is full, this may be 0
+        usb_serial_send((uint8_t*)data, len);
+        logpos -= available - len;
+    }
+}
+
 /*****************************************/
 /* Crash handlers                        */
 /*****************************************/
@@ -358,6 +393,10 @@ void platform_log(const char *s)
 
 void platform_emergency_log_save()
 {
+#ifdef ZULUSCSI_HARDWARE_CONFIG
+    if (g_hw_config.is_active())
+        return;
+#endif
     platform_set_sd_callback(NULL, NULL);
 
     SD.begin(SD_CONFIG_CRASH);
@@ -411,11 +450,12 @@ void show_hardfault(uint32_t *sp)
         logmsg("STACK ", (uint32_t)p, ":    ", p[0], " ", p[1], " ", p[2], " ", p[3]);
         p += 4;
     }
-
+ 
     platform_emergency_log_save();
 
     while (1)
     {
+        usb_log_poll();
         // Flash the crash address on the LED
         // Short pulse means 0, long pulse means 1
         int base_delay = 1000;
@@ -488,6 +528,7 @@ void __assert_func(const char *file, int line, const char *func, const char *exp
 
     while(1)
     {
+        usb_log_poll();
         LED_OFF();
         for (int j = 0; j < 1000; j++) delay_ns(100000);
         LED_ON();
@@ -509,6 +550,10 @@ void platform_reset_watchdog()
     // It gives us opportunity to collect better debug info than the
     // full hardware reset that would be caused by hardware watchdog.
     g_watchdog_timeout = WATCHDOG_CRASH_TIMEOUT;
+
+    // USB log is polled here also to make sure any log messages in fault states
+    // get passed to USB.
+    usb_log_poll();
 }
 
 // Poll function that is called every few milliseconds.
@@ -516,6 +561,7 @@ void platform_reset_watchdog()
 void platform_poll()
 {
     adc_poll();
+    usb_log_poll();
 }
 
 uint8_t platform_get_buttons()

+ 1 - 1
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.h

@@ -27,7 +27,7 @@
 #include <gd32f20x.h>
 #include <gd32f20x_gpio.h>
 #include <scsi2sd.h>
-#include "ZuluSCSI_config.h"
+
 
 #ifdef __cplusplus
 extern "C" {

+ 0 - 3
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_v1_2_gpio.h

@@ -252,9 +252,6 @@
 #define LED_EJECT_OFF() gpio_bit_set(LED_EJECT_PORT, LED_EJECT_PIN)
 
 // Ejection button is on GPIO PA3 and USER button is on GPIO PA2
-// Eject and user buttons masks
-#define EJECT_BTN_MASK (1|2)
-#define USER_BTN_MASK  (4)
 #define EJECT_BTN_PORT  GPIOA
 #define EJECT_BTN_PIN   GPIO_PIN_3
 #define USER_BTN_PORT   GPIOA

+ 519 - 0
lib/ZuluSCSI_platform_GD32F205/gd32_cdc_acm_core.c

@@ -0,0 +1,519 @@
+/*!
+    \file    cdc_acm_core.c
+    \brief   CDC ACM driver
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2020-12-10, V3.0.1, firmware for GD32F20x
+    \version 2020-12-14, V3.0.2, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2020, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this 
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice, 
+       this list of conditions and the following disclaimer in the documentation 
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors 
+       may be used to endorse or promote products derived from this software without 
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
+OF SUCH DAMAGE.
+*/
+
+#include "gd32_cdc_acm_core.h"
+#include "usbd_core.h"
+
+#define USBD_VID                          0x28E9U
+#define USBD_PID                          0x018AU
+
+/* note:it should use the C99 standard when compiling the below codes */
+/* USB standard device descriptor */
+const usb_desc_dev gd32_cdc_dev_desc =
+{
+    .header = 
+     {
+         .bLength          = USB_DEV_DESC_LEN, 
+         .bDescriptorType  = USB_DESCTYPE_DEV,
+     },
+    .bcdUSB                = 0x0200U,
+    .bDeviceClass          = USB_CLASS_CDC,
+    .bDeviceSubClass       = 0x00U,
+    .bDeviceProtocol       = 0x00U,
+    .bMaxPacketSize0       = USB_FS_EP0_MAX_LEN,
+    .idVendor              = USBD_VID,
+    .idProduct             = USBD_PID,
+    .bcdDevice             = 0x0100U,
+    .iManufacturer         = STR_IDX_MFC,
+    .iProduct              = STR_IDX_PRODUCT,
+    .iSerialNumber         = STR_IDX_SERIAL,
+    .bNumberConfigurations = USBD_CFG_MAX_NUM,
+};
+
+/* USB device configuration descriptor */
+const usb_cdc_desc_config_set gd32_cdc_config_desc = 
+{
+    .config = 
+    {
+        .header = 
+         {
+             .bLength         = sizeof(usb_desc_config), 
+             .bDescriptorType = USB_DESCTYPE_CONFIG,
+         },
+        .wTotalLength         = USB_CDC_ACM_CONFIG_DESC_SIZE,
+        .bNumInterfaces       = 0x02U,
+        .bConfigurationValue  = 0x01U,
+        .iConfiguration       = 0x00U,
+        .bmAttributes         = 0x80U,
+        .bMaxPower            = 0x32U
+    },
+
+    .cmd_itf = 
+    {
+        .header = 
+         {
+             .bLength         = sizeof(usb_desc_itf), 
+             .bDescriptorType = USB_DESCTYPE_ITF 
+         },
+        .bInterfaceNumber     = 0x00U,
+        .bAlternateSetting    = 0x00U,
+        .bNumEndpoints        = 0x01U,
+        .bInterfaceClass      = USB_CLASS_CDC,
+        .bInterfaceSubClass   = USB_CDC_SUBCLASS_ACM,
+        .bInterfaceProtocol   = USB_CDC_PROTOCOL_AT,
+        .iInterface           = 0x00U
+    },
+
+    .cdc_header = 
+    {
+        .header =
+         {
+            .bLength         = sizeof(usb_desc_header_func), 
+            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
+         },
+        .bDescriptorSubtype  = 0x00U,
+        .bcdCDC              = 0x0110U
+    },
+
+    .cdc_call_managment = 
+    {
+        .header = 
+         {
+            .bLength         = sizeof(usb_desc_call_managment_func), 
+            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
+         },
+        .bDescriptorSubtype  = 0x01U,
+        .bmCapabilities      = 0x00U,
+        .bDataInterface      = 0x01U
+    },
+
+    .cdc_acm = 
+    {
+        .header = 
+         {
+            .bLength         = sizeof(usb_desc_acm_func), 
+            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
+         },
+        .bDescriptorSubtype  = 0x02U,
+        .bmCapabilities      = 0x02U,
+    },
+
+    .cdc_union = 
+    {
+        .header = 
+         {
+            .bLength         = sizeof(usb_desc_union_func), 
+            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
+         },
+        .bDescriptorSubtype  = 0x06U,
+        .bMasterInterface    = 0x00U,
+        .bSlaveInterface0    = 0x01U,
+    },
+
+    .cdc_cmd_endpoint = 
+    {
+        .header = 
+         {
+            .bLength         = sizeof(usb_desc_ep), 
+            .bDescriptorType = USB_DESCTYPE_EP,
+         },
+        .bEndpointAddress    = CDC_CMD_EP,
+        .bmAttributes        = USB_EP_ATTR_INT,
+        .wMaxPacketSize      = USB_CDC_CMD_PACKET_SIZE,
+        .bInterval           = 0x0AU
+    },
+
+    .cdc_data_interface = 
+    {
+        .header = 
+         {
+            .bLength         = sizeof(usb_desc_itf), 
+            .bDescriptorType = USB_DESCTYPE_ITF,
+         },
+        .bInterfaceNumber    = 0x01U,
+        .bAlternateSetting   = 0x00U,
+        .bNumEndpoints       = 0x02U,
+        .bInterfaceClass     = USB_CLASS_DATA,
+        .bInterfaceSubClass  = 0x00U,
+        .bInterfaceProtocol  = USB_CDC_PROTOCOL_NONE,
+        .iInterface          = 0x00U
+    },
+
+    .cdc_out_endpoint = 
+    {
+        .header = 
+         {
+             .bLength         = sizeof(usb_desc_ep), 
+             .bDescriptorType = USB_DESCTYPE_EP, 
+         },
+        .bEndpointAddress     = CDC_DATA_OUT_EP,
+        .bmAttributes         = USB_EP_ATTR_BULK,
+        .wMaxPacketSize       = USB_CDC_DATA_PACKET_SIZE,
+        .bInterval            = 0x00U
+    },
+
+    .cdc_in_endpoint = 
+    {
+        .header = 
+         {
+             .bLength         = sizeof(usb_desc_ep), 
+             .bDescriptorType = USB_DESCTYPE_EP 
+         },
+        .bEndpointAddress     = CDC_DATA_IN_EP,
+        .bmAttributes         = USB_EP_ATTR_BULK,
+        .wMaxPacketSize       = USB_CDC_DATA_PACKET_SIZE,
+        .bInterval            = 0x00U
+    }
+};
+
+/* USB language ID Descriptor */
+static const usb_desc_LANGID usbd_language_id_desc = 
+{
+    .header = 
+     {
+         .bLength         = sizeof(usb_desc_LANGID), 
+         .bDescriptorType = USB_DESCTYPE_STR,
+     },
+    .wLANGID              = ENG_LANGID
+};
+
+/* USB manufacture string */
+static const usb_desc_str manufacturer_string = 
+{
+    .header = 
+     {
+         .bLength         = USB_STRING_LEN(19), 
+         .bDescriptorType = USB_DESCTYPE_STR,
+     },
+    .unicode_string = {'R', 'a', 'b', 'b', 'i', 't', 'H', 'o', 'l','e','C','o','m','p','u','t','i','n','g'}
+};
+
+/* USB product string */
+static const usb_desc_str product_string = 
+{
+    .header = 
+     {
+         .bLength         = USB_STRING_LEN(8), 
+         .bDescriptorType = USB_DESCTYPE_STR,
+     },
+    .unicode_string = {'Z', 'u', 'l', 'u', 'S', 'C', 'S', 'I'}
+};
+
+/* USBD serial string */
+static usb_desc_str serial_string = 
+{
+    .header = 
+     {
+         .bLength         = USB_STRING_LEN(12), 
+         .bDescriptorType = USB_DESCTYPE_STR,
+     }
+};
+
+/* USB string descriptor set */
+void *const gd32_usbd_cdc_strings[] = 
+{
+    [STR_IDX_LANGID]  = (uint8_t *)&usbd_language_id_desc,
+    [STR_IDX_MFC]     = (uint8_t *)&manufacturer_string,
+    [STR_IDX_PRODUCT] = (uint8_t *)&product_string,
+    [STR_IDX_SERIAL]  = (uint8_t *)&serial_string
+};
+
+usb_desc gd32_cdc_desc = 
+{
+    .dev_desc    = (uint8_t *)&gd32_cdc_dev_desc,
+    .config_desc = (uint8_t *)&gd32_cdc_config_desc,
+    .strings     = gd32_usbd_cdc_strings
+};
+
+/* local function prototypes ('static') */
+static uint8_t cdc_acm_init   (usb_dev *udev, uint8_t config_index);
+static uint8_t cdc_acm_deinit (usb_dev *udev, uint8_t config_index);
+static uint8_t cdc_acm_req    (usb_dev *udev, usb_req *req);
+static uint8_t cdc_acm_ctlx_out (usb_dev *udev);
+static uint8_t cdc_acm_in     (usb_dev *udev, uint8_t ep_num);
+static uint8_t cdc_acm_out    (usb_dev *udev, uint8_t ep_num);
+
+/* USB CDC device class callbacks structure */
+usb_class_core gd32_cdc_class =
+{
+    .command   = NO_CMD,
+    .alter_set = 0U,
+
+    .init      = cdc_acm_init,
+    .deinit    = cdc_acm_deinit,
+
+    .req_proc  = cdc_acm_req,
+
+    .ctlx_out  = cdc_acm_ctlx_out,
+    .data_in   = cdc_acm_in,
+    .data_out  = cdc_acm_out
+};
+
+/*!
+    \brief      check CDC ACM is ready for data transfer
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     0 if CDC is ready, 5 else
+*/
+uint8_t gd32_cdc_acm_check_ready(usb_dev *udev)
+{
+    if (udev->dev.class_data[CDC_COM_INTERFACE] != NULL) {
+        gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+
+        if ((1U == cdc->packet_sent)) {
+            return 1U;
+        }
+    }
+
+    return 0U;
+}
+
+/*!
+    \brief      send CDC ACM data
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     USB device operation status
+*/
+void gd32_cdc_acm_data_send(usb_dev *udev, uint8_t *data, uint32_t length)
+{
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+    if (cdc->packet_sent == 1)
+    {
+        cdc->packet_sent = 0U;
+        usbd_ep_send (udev, CDC_DATA_IN_EP, data, length);
+    }
+}
+
+/*!
+    \brief      receive CDC ACM data
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     USB device operation status
+*/
+void gd32_cdc_acm_data_receive (usb_dev *udev)
+{
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+    usbd_ep_recev(udev, CDC_DATA_OUT_EP, (uint8_t*)(cdc->data), USB_CDC_DATA_PACKET_SIZE);
+}
+
+/*!
+    \brief      initialize the CDC ACM device
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  config_index: configuration index
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t cdc_acm_init (usb_dev *udev, uint8_t config_index)
+{
+    static gd32_usb_cdc_handler cdc_handler;
+
+    /* initialize the data Tx endpoint */
+    usbd_ep_setup (udev, &(gd32_cdc_config_desc.cdc_in_endpoint));
+
+    /* initialize the data Rx endpoint */
+    usbd_ep_setup (udev, &(gd32_cdc_config_desc.cdc_out_endpoint));
+
+    /* initialize the command Tx endpoint */
+    usbd_ep_setup (udev, &(gd32_cdc_config_desc.cdc_cmd_endpoint));
+
+    /* initialize CDC handler structure */
+    cdc_handler.packet_receive = 1U;
+    cdc_handler.packet_sent = 1U;
+    cdc_handler.receive_length = 0U;
+
+    cdc_handler.line_coding = (acm_line){
+        .dwDTERate   = 115200,
+        .bCharFormat = 0,
+        .bParityType = 0,
+        .bDataBits   = 0x08
+    };
+
+    udev->dev.class_data[CDC_COM_INTERFACE] = (void *)&cdc_handler;
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      deinitialize the CDC ACM device
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  config_index: configuration index
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t cdc_acm_deinit (usb_dev *udev, uint8_t config_index)
+{
+    /* deinitialize the data Tx/Rx endpoint */
+    usbd_ep_clear (udev, CDC_DATA_IN_EP);
+    usbd_ep_clear (udev, CDC_DATA_OUT_EP);
+
+    /* deinitialize the command Tx endpoint */
+    usbd_ep_clear (udev, CDC_CMD_EP);
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      handle the CDC ACM class-specific requests
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  req: device class-specific request
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t cdc_acm_req (usb_dev *udev, usb_req *req)
+{
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+
+    usb_transc *transc = NULL;
+
+    switch (req->bRequest) {
+    case SEND_ENCAPSULATED_COMMAND:
+        /* no operation for this driver */
+        break;
+
+    case GET_ENCAPSULATED_RESPONSE:
+        /* no operation for this driver */
+        break;
+
+    case SET_COMM_FEATURE:
+        /* no operation for this driver */
+        break;
+
+    case GET_COMM_FEATURE:
+        /* no operation for this driver */
+        break;
+
+    case CLEAR_COMM_FEATURE:
+        /* no operation for this driver */
+        break;
+
+    case SET_LINE_CODING:
+        /* set the value of the current command to be processed */
+        udev->dev.class_core->alter_set = req->bRequest;
+
+        /* enable EP0 prepare to receive command data packet */
+        transc = &udev->dev.transc_in[0];
+        transc->remain_len = req->wLength;
+        transc->xfer_buf = cdc->cmd;
+        break;
+
+    case GET_LINE_CODING:
+        cdc->cmd[0] = (uint8_t)(cdc->line_coding.dwDTERate);
+        cdc->cmd[1] = (uint8_t)(cdc->line_coding.dwDTERate >> 8);
+        cdc->cmd[2] = (uint8_t)(cdc->line_coding.dwDTERate >> 16);
+        cdc->cmd[3] = (uint8_t)(cdc->line_coding.dwDTERate >> 24);
+        cdc->cmd[4] = cdc->line_coding.bCharFormat;
+        cdc->cmd[5] = cdc->line_coding.bParityType;
+        cdc->cmd[6] = cdc->line_coding.bDataBits;
+
+        transc = &udev->dev.transc_out[0];
+        transc->xfer_buf = cdc->cmd;
+        transc->remain_len = 7U;
+        break;
+
+    case SET_CONTROL_LINE_STATE:
+        /* no operation for this driver */
+        break;
+
+    case SEND_BREAK:
+        /* no operation for this driver */
+        break;
+
+    default:
+        break;
+    }
+
+    return USBD_OK;
+}
+
+static uint8_t cdc_acm_ctlx_out (usb_dev *udev)
+{
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+
+    if (udev->dev.class_core->alter_set != NO_CMD) {
+        /* process the command data */
+        cdc->line_coding.dwDTERate = (uint32_t)((uint32_t)cdc->cmd[0] | 
+                                               ((uint32_t)cdc->cmd[1] << 8U) | 
+                                               ((uint32_t)cdc->cmd[2] << 16U) | 
+                                               ((uint32_t)cdc->cmd[3] << 24U));
+
+        cdc->line_coding.bCharFormat = cdc->cmd[4];
+        cdc->line_coding.bParityType = cdc->cmd[5];
+        cdc->line_coding.bDataBits = cdc->cmd[6];
+
+        udev->dev.class_core->alter_set = NO_CMD;
+    }
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      handle CDC ACM data
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: endpoint identifier
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t cdc_acm_in (usb_dev *udev, uint8_t ep_num)
+{
+    usb_transc *transc = &udev->dev.transc_in[EP_ID(ep_num)];
+
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+
+    if ((0U == transc->xfer_len % transc->max_len) && (0U != transc->xfer_len)) {
+        usbd_ep_send (udev, ep_num, NULL, 0U);
+    } else {
+        cdc->packet_sent = 1U;
+    }
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      handle CDC ACM data
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: endpoint identifier
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t cdc_acm_out (usb_dev *udev, uint8_t ep_num)
+{
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+
+    cdc->packet_receive = 1U;
+    cdc->receive_length = ((usb_core_driver *)udev)->dev.transc_out[ep_num].xfer_count;
+
+    return USBD_OK;
+}

+ 66 - 0
lib/ZuluSCSI_platform_GD32F205/gd32_cdc_acm_core.h

@@ -0,0 +1,66 @@
+/*!
+    \file    cdc_acm_core.h
+    \brief   the header file of CDC ACM driver
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2020, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this 
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice, 
+       this list of conditions and the following disclaimer in the documentation 
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors 
+       may be used to endorse or promote products derived from this software without 
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
+OF SUCH DAMAGE.
+*/
+
+#ifndef __CDC_ACM_CORE_H
+#define __CDC_ACM_CORE_H
+
+#include "usbd_enum.h"
+#include "usb_cdc.h"
+
+#define USB_CDC_RX_LEN      64
+
+typedef struct {
+    uint8_t packet_sent;
+    uint8_t packet_receive;
+
+    uint8_t data[USB_CDC_RX_LEN];
+    uint8_t cmd[USB_CDC_CMD_PACKET_SIZE];
+
+    uint32_t receive_length;
+
+    acm_line line_coding;
+} gd32_usb_cdc_handler;
+
+extern usb_desc gd32_cdc_desc;
+extern usb_class_core gd32_cdc_class;
+
+/* function declarations */
+/* check CDC ACM is ready for data transfer */
+uint8_t gd32_cdc_acm_check_ready(usb_dev *udev);
+/* send CDC ACM data */
+void gd32_cdc_acm_data_send(usb_dev *udev, uint8_t* data, uint32_t length);
+/* receive CDC ACM data */
+void gd32_cdc_acm_data_receive(usb_dev *udev);
+
+#endif /* __CDC_ACM_CORE_H */

+ 23 - 0
lib/ZuluSCSI_platform_GD32F205/platform_hw_config.cpp

@@ -1,3 +1,25 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#ifdef ZULUSCSI_HARDWARE_CONFIG
 #include "platform_hw_config.h"
 #include "ZuluSCSI_platform.h"
 #include "ZuluSCSI_config.h"
@@ -74,3 +96,4 @@ void HardwareConfig::init_state()
     }
 }
 
+#endif // ZULUSCSI_HARDWARE_CONFIG

+ 126 - 0
lib/ZuluSCSI_platform_GD32F205/usb_conf.h

@@ -0,0 +1,126 @@
+/*!
+    \file    usb_conf.h
+    \brief   USB core driver basic configuration
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2020, GigaDevice Semiconductor Inc. 
+
+    Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this 
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice, 
+       this list of conditions and the following disclaimer in the documentation 
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors 
+       may be used to endorse or promote products derived from this software without 
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USB_CONF_H
+#define __USB_CONF_H
+
+#include "stdlib.h"
+#include "gd32f20x.h"
+
+/* USB Core and PHY interface configuration */
+
+/****************** USB FS PHY CONFIGURATION *******************************
+ *  The USB FS Core supports one on-chip Full Speed PHY.
+ *  The USE_EMBEDDED_PHY symbol is defined in the project compiler preprocessor
+ *  when FS core is used.
+*******************************************************************************/
+
+#ifdef USE_USB_FS
+    #define USB_FS_CORE
+#endif
+
+/*******************************************************************************
+ *                      FIFO Size Configuration in Device mode
+ *
+ *  (i) Receive data FIFO size = RAM for setup packets + 
+ *                   OUT endpoint control information +
+ *                   data OUT packets + miscellaneous
+ *      Space = ONE 32-bits words
+ *      --> RAM for setup packets = 10 spaces
+ *          (n is the nbr of CTRL EPs the device core supports)
+ *      --> OUT EP CTRL info = 1 space
+ *          (one space for status information written to the FIFO along with each 
+ *          received packet)
+ *      --> Data OUT packets = (Largest Packet Size / 4) + 1 spaces 
+ *          (MINIMUM to receive packets)
+ *      --> OR data OUT packets  = at least 2* (Largest Packet Size / 4) + 1 spaces 
+ *          (if high-bandwidth EP is enabled or multiple isochronous EPs)
+ *      --> Miscellaneous = 1 space per OUT EP
+ *          (one space for transfer complete status information also pushed to the 
+ *          FIFO with each endpoint's last packet)
+ *
+ *  (ii) MINIMUM RAM space required for each IN EP Tx FIFO = MAX packet size for 
+ *       that particular IN EP. More space allocated in the IN EP Tx FIFO results
+ *       in a better performance on the USB and can hide latencies on the AHB.
+ *
+ *  (iii) TXn min size = 16 words. (n:Transmit FIFO index)
+ *
+ *  (iv) When a TxFIFO is not used, the Configuration should be as follows:
+ *       case 1: n > m and Txn is not used (n,m:Transmit FIFO indexes)
+ *       --> Txm can use the space allocated for Txn.
+ *       case 2: n < m and Txn is not used (n,m:Transmit FIFO indexes)
+ *       --> Txn should be configured with the minimum space of 16 words
+ *
+ *  (v) The FIFO is used optimally when used TxFIFOs are allocated in the top 
+ *      of the FIFO.Ex: use EP1 and EP2 as IN instead of EP1 and EP3 as IN ones.
+*******************************************************************************/
+
+#ifdef USB_FS_CORE
+    #define RX_FIFO_FS_SIZE                         128U
+    #define USB_RX_FIFO_FS_SIZE                     RX_FIFO_FS_SIZE
+    #define TX0_FIFO_FS_SIZE                        64U
+    #define TX1_FIFO_FS_SIZE                        128U
+    #define TX2_FIFO_FS_SIZE                        0U
+    #define TX3_FIFO_FS_SIZE                        0U
+#endif /* USB_FS_CORE */
+
+#define USB_SOF_OUTPUT              1U
+#define USB_LOW_POWER               0U
+
+/* if uncomment it, need jump to USB JP */
+//#define VBUS_SENSING_ENABLED
+
+//#define USE_HOST_MODE
+#define USE_DEVICE_MODE
+//#define USE_OTG_MODE
+
+#ifndef USE_DEVICE_MODE
+    #ifndef USE_HOST_MODE
+        #error  "USE_DEVICE_MODE or USE_HOST_MODE should be defined!"
+    #endif
+#endif
+
+#define __ALIGN_BEGIN
+#define __ALIGN_END
+
+/* __packed keyword used to decrease the data type alignment to 1-byte */
+#if defined (__GNUC__)       /* GNU Compiler */
+    #ifndef __packed
+        #define __packed __attribute__ ((__packed__))
+    #endif
+#elif defined (__TASKING__)    /* TASKING Compiler */
+    #define __packed __unaligned
+#endif /* __CC_ARM */
+
+#endif /* __USB_CONF_H */

+ 80 - 0
lib/ZuluSCSI_platform_GD32F205/usb_serial.cpp

@@ -0,0 +1,80 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+
+#include "ZuluSCSI_platform.h"
+#include "usb_serial.h"
+
+extern "C" 
+{
+#include <gd32_cdc_acm_core.h>
+#include <drv_usb_hw.h>
+#include <usbd_core.h>
+#include <drv_usbd_int.h>
+usb_core_driver cdc_acm;
+}
+
+
+void usb_fs_init(void)
+{
+    // set USB full speed prescaler and turn on USB clock
+    rcu_usbfs_trng_clock_config(RCU_CKUSB_CKPLL_DIV2_5);
+    rcu_periph_clock_enable(RCU_USBFS);
+    usbd_init(&cdc_acm, USB_CORE_ENUM_FS, &gd32_cdc_desc, &gd32_cdc_class);
+
+    // USB full speed Interrupt config
+    nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
+    nvic_irq_enable((uint8_t)USBFS_IRQn, 2U, 0U);
+}
+
+
+bool usb_serial_ready(void)
+{
+    if (USBD_CONFIGURED == cdc_acm.dev.cur_status) 
+    {
+        if (1U == gd32_cdc_acm_check_ready(&cdc_acm)) 
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+void usb_serial_send(uint8_t *data, uint32_t length)
+{
+    gd32_cdc_acm_data_send(&cdc_acm, data, length);
+}
+
+void USBFS_IRQHandler(void)
+{
+    usbd_isr(&cdc_acm);
+}
+
+void usb_udelay (const uint32_t usec)
+{
+    delay_us(usec);
+}
+
+
+void usb_mdelay (const uint32_t msec)
+{
+    delay(msec);
+}

+ 38 - 0
lib/ZuluSCSI_platform_GD32F205/usb_serial.h

@@ -0,0 +1,38 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+
+#pragma once
+
+#include <gd32f20x.h>
+
+// init USB full speed interface
+void usb_fs_init(void);
+// check if the USB serial interface is ready to send data
+bool usb_serial_ready(void);
+// Send data of the USB serial interface
+void usb_serial_send(uint8_t *data, uint32_t length);
+
+extern "C"
+{
+    // The USB full speed IRQ handler
+    void USBFS_IRQHandler(void);
+}

+ 62 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_conf.h

@@ -0,0 +1,62 @@
+/*!
+    \file    usbd_conf.h
+    \brief   the header file of USB device configuration
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2020, GigaDevice Semiconductor Inc. 
+
+    Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this 
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice, 
+       this list of conditions and the following disclaimer in the documentation 
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors 
+       may be used to endorse or promote products derived from this software without 
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_CONF_H
+#define __USBD_CONF_H
+
+#include "usb_conf.h"
+
+#define USBD_CFG_MAX_NUM                    1U
+#define USBD_ITF_MAX_NUM                    1U
+
+#define CDC_COM_INTERFACE                   0U
+
+#define USB_STR_DESC_MAX_SIZE               255U
+
+#define CDC_DATA_IN_EP                      EP1_IN  /* EP1 for data IN */
+#define CDC_DATA_OUT_EP                     EP3_OUT /* EP3 for data OUT */
+#define CDC_CMD_EP                          EP2_IN  /* EP2 for CDC commands */
+
+#define USB_STRING_COUNT                    4U
+
+#define USB_CDC_CMD_PACKET_SIZE             8U    /* Control Endpoint Packet size */
+
+#define APP_RX_DATA_SIZE                    2048U /* Total size of IN buffer: 
+                                                    APP_RX_DATA_SIZE*8 / MAX_BAUDARATE * 1000 should be > CDC_IN_FRAME_INTERVAL*8 */
+
+/* CDC Endpoints parameters: you can fine tune these values depending on the needed baud rate and performance. */
+#define USB_CDC_DATA_PACKET_SIZE            64U   /* Endpoint IN & OUT Packet size */
+#define CDC_IN_FRAME_INTERVAL               5U   /* Number of frames between IN transfers */
+
+#endif /* __USBD_CONF_H */

+ 2 - 0
lib/ZuluSCSI_platform_GD32F205/zuluscsi_gd32f205_btldr.ld

@@ -54,12 +54,14 @@ SECTIONS
     *(*DMA1_Channel1_IRQHandler*)
     *(*EXTI3_IRQHandler*)
     *(*EXTI10_15_IRQHandler*)
+    *(*USBFS_IRQHandler*)
   }
 
   DMA1_Channel1_IRQHandler = 0;
   DMA1_Channel4_IRQHandler = 0;
   EXTI10_15_IRQHandler = 0;
   EXTI3_IRQHandler = 0;
+  USBFS_IRQHandler = 0;
 
   /* The startup code goes first into FLASH */
   .isr_vector :

+ 9 - 2
platformio.ini

@@ -1,7 +1,7 @@
 ; PlatformIO Project Configuration File https://docs.platformio.org/page/projectconf.html
 
 [platformio]
-default_envs = ZuluSCSIv1_2
+default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1, ZuluSCSI_RP2040, ZuluSCSI_RP2040_Audio, ZuluSCSI_Pico, ZuluSCSI_BS2
 
 ; Example platform to serve as a base for porting efforts
 [env:template]
@@ -39,17 +39,20 @@ lib_deps =
     ZuluSCSI_platform_GD32F205
     SCSI2SD
     CUEParser
+    GD32F20x_usbfs_library
 upload_protocol = stlink
 platform_packages = platformio/toolchain-gccarmnoneeabi@1.100301.220327
     framework-spl-gd32@https://github.com/CommunityGD32Cores/gd32-pio-spl-package.git
 extra_scripts = src/build_bootloader.py
-debug_build_flags = -Os -ggdb -g3
+debug_build_flags = 
+     -Os -Wall -Wno-sign-compare -ggdb -g3
 build_flags = 
      -Os -Wall -Wno-sign-compare -ggdb -g3 -Isrc
      -D__SYSTEM_CLOCK_120M_PLL_IRC8M=120000000
      -DSPI_DRIVER_SELECT=3
      -DSD_CHIP_SELECT_MODE=2
      -DENABLE_DEDICATED_SPI=1
+     -DPIO_USBFS_DEVICE_CDC
      -DZULUSCSI_V1_0
 
 ; ZuluSCSI V1.0 mini hardware platform with GD32F205 CPU.
@@ -61,6 +64,7 @@ build_flags =
      -DSPI_DRIVER_SELECT=3
      -DSD_CHIP_SELECT_MODE=2
      -DENABLE_DEDICATED_SPI=1
+     -DPIO_USBFS_DEVICE_CDC
      -DZULUSCSI_V1_0
      -DZULUSCSI_V1_0_mini
 
@@ -73,6 +77,7 @@ build_flags =
      -DSPI_DRIVER_SELECT=3
      -DSD_CHIP_SELECT_MODE=2
      -DENABLE_DEDICATED_SPI=1
+     -DPIO_USBFS_DEVICE_CDC
      -DHAS_SDIO_CLASS
      -DZULUSCSI_V1_1
 
@@ -85,6 +90,7 @@ build_flags =
      -DSPI_DRIVER_SELECT=3
      -DSD_CHIP_SELECT_MODE=2
      -DENABLE_DEDICATED_SPI=1
+     -DPIO_USBFS_DEVICE_CDC
      -DHAS_SDIO_CLASS
      -DZULUSCSI_V1_2
      -DZULUSCSI_HARDWARE_CONFIG
@@ -98,6 +104,7 @@ extra_scripts = src/build_bootloader.py
 platform_packages = platformio/toolchain-gccarmnoneeabi@1.100301.220327
 board_build.ldscript = lib/ZuluSCSI_platform_RP2040/rp2040.ld
 ldscript_bootloader = lib/ZuluSCSI_platform_RP2040/rp2040_btldr.ld
+lib_ignore = ZuluSCSI_platform_GD32F205
 lib_deps =
     SdFat=https://github.com/rabbitholecomputing/SdFat#2.2.0-gpt
     minIni

+ 12 - 0
src/ZuluSCSI.cpp

@@ -97,6 +97,12 @@ extern "C" void s2s_ledOff()
 
 void save_logfile(bool always = false)
 {
+#ifdef ZULUSCSI_HARDWARE_CONFIG
+  // Disable logging to the SD card when in direct mode
+  if (g_hw_config.is_active())
+    return;
+#endif
+
   static uint32_t prev_log_pos = 0;
   static uint32_t prev_log_len = 0;
   static uint32_t prev_log_save = 0;
@@ -119,6 +125,12 @@ void save_logfile(bool always = false)
 
 void init_logfile()
 {
+#ifdef ZULUSCSI_HARDWARE_CONFIG
+  // Disable logging to the SD card when in direct mode
+  if (g_hw_config.is_active())
+    return;
+#endif
+
   static bool first_open_after_boot = true;
 
   bool truncate = first_open_after_boot;

+ 5 - 1
src/ZuluSCSI_config.h

@@ -100,4 +100,8 @@
 // Use prefetch buffer in read requests
 #ifndef PREFETCH_BUFFER_SIZE
 #define PREFETCH_BUFFER_SIZE 8192
-#endif
+#endif
+
+// Masks for buttons
+#define EJECT_BTN_MASK (1|2)
+#define USER_BTN_MASK  (4)