/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is based on:
 *     USB API for Teensy USB Development Board
 *     http://www.pjrc.com/teensy/teensyduino.html
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2011 PJRC.COM, LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


#include "USBDriver.h"


#ifdef ECXX_AVR_HAVE_HARDWARE_USB


namespace eCxx {


//
// Helper assembly macros
//
#define readWordLSBFirst(VAL, REG) \
    __asm__ __volatile__ (         \
        "lds %A0, %1 \n\t"         \
        "lds %B0, %1 \n\t"         \
        : "=r"(VAL)                \
        : "M"(&REG)                \
    )


#define pgmReadByte(VAL, ADDR) \
    __asm__ __volatile__ (     \
        "lpm %0, Z \n\t"       \
        : "=r"(VAL)            \
        : "z"(ADDR)            \
    )


#define pgmReadBytePostInc(VAL, ADDR) \
    __asm__ __volatile__ (            \
        "lpm %0, Z+ \n\t"             \
        : "=r"(VAL), "+z"(ADDR)       \
    )


#define pgmReadWordPostInc(VAL, ADDR) \
    __asm__ __volatile__ (            \
        "lpm %A0, Z+ \n\t"            \
        "lpm %B0, Z+ \n\t"            \
        : "=r"(VAL), "+z"(ADDR)       \
    )


//
// Helper functions
//
static constexpr uint8_t LSB(uint16_t v)
{ return v & 0xFF; }


static constexpr uint8_t MSB(uint16_t v)
{ return (v >> 8) & 0xFF; }


static constexpr uint8_t EP_SIZE(uint8_t v)
{
    return (v == 64) ? 0x30
                     : ( (v == 32) ? 0x20
                                   : ( (v == 16) ? 0x10
                                                 : 0x00
                                     )
                       );
}


static inline void usbWaitUntilBankIsReadyToAcceptNewInPacket()
{
    // Wait until TXINI is set (the bank is ready to accept a new IN packet)
    while( !( UEINTX & _BV(TXINI) ) );
}


static inline void usbWaitUntilNewOutDataReceived()
{
    // Wait until RXOUTI is set (a new OUT data is received)
    while( !( UEINTX & _BV(RXOUTI) ) );
}


static inline void usbSendPacketAndClearBank()
{
    // Clear TXINI to send the packet and to clear the endpoint bank
    UEINTX = ~_BV(TXINI);
}


static inline void usbAckPacketAndClearBank()
{
    // Clear RXOUTI to acknowledge the packet and to clear the endpoint bank
    UEINTX = ~_BV(RXOUTI);
}


//
// Restart helper functions
//
static constexpr uint16_t MAGIC_KEY_RESTART_LOCK = 0x8888;
static constexpr uint16_t MAGIC_KEY_BOOTLOADER   = 0x7777;

static volatile uint16_t* const magicKeyPos_RestartLock = reinterpret_cast<uint16_t*>(RAMEND - 1);

#if defined(__AVR_AT90USB1286__)
static volatile uint16_t* const magicKeyPos_Bootloader  = reinterpret_cast<uint16_t*>(0x1E00    );
#elif defined(__AVR_AT90USB646__)
static volatile uint16_t* const magicKeyPos_Bootloader  = reinterpret_cast<uint16_t*>(0x0F80    );
#elif defined(__AVR_ATmega32U4__)
static volatile uint16_t* const magicKeyPos_Bootloader  = reinterpret_cast<uint16_t*>(0x0800    );
#elif defined(__AVR_ATmega16U4__)
static volatile uint16_t* const magicKeyPos_Bootloader  = reinterpret_cast<uint16_t*>(0x0480    );
#else
#error "unsupported AVR MCU"
#endif


static void resetToBootloader(volatile uint8_t* _acmLineRtsDtr)
{
    // Wait for a while
    Application::delayMS(150);

    // Disable interrupt
    cli();

    // To avoid spurious resets, check if the DTR has really gone low
    if(*_acmLineRtsDtr & 0x01) {
        // DTR has gone back to high, abort
        *magicKeyPos_RestartLock = 0;
        return;
    }

    // Stop USB
    usbStop();

    // Configure the watchdog timer to the smallest possible timeout
    wdt_reset();
    wdt_enable(WDTO_15MS);

    // Set the bootloader magic key
    *magicKeyPos_Bootloader = MAGIC_KEY_BOOTLOADER;

    // Loop forever until watchdog restart
    for(;;);
}


static void jumpToApplicationStart(volatile uint8_t* _acmLineRtsDtr)
{
    // Wait for a while
    Application::delayMS(150);

    // Disable interrupt
    cli();

    // To avoid spurious resets, check if the DTR has really gone low
    if(*_acmLineRtsDtr & 0x01) {
        // DTR has gone back to high, abort
        *magicKeyPos_RestartLock = 0;
        return;
    }

    // Disable watchdog
    wdt_reset();
    wdt_disable();
    wdt_reset();

    // Jump to beginning of application space
    __asm__ __volatile__ ( "jmp 0x0000 \n\t" );
}


//
// Free shared USB VID/PID pair for CDC devices
// https://github.com/obdev/v-usb/blob/master/usbdrv/USB-IDs-for-free.txt
//
#if !defined(ECXX_AVR_USB_VID) || !defined(ECXX_AVR_USB_PID)
    #define ECXX_AVR_USB_VID 0x16C0
    #define ECXX_AVR_USB_PID 0x05E1
#endif


//
// Constants
//

// Vendor ID and product ID
static constexpr uint16_t VID                       = ECXX_AVR_USB_VID;
static constexpr uint16_t PID                       = ECXX_AVR_USB_PID;

// Endpoint types
static constexpr uint8_t  MAX_ENDPOINT_NUMBER       = 6;

static constexpr uint8_t  ENDPOINT0_SIZE            = 32;

static constexpr uint8_t  EP_TYPE_CONTROL           = 0x00; // 0b00000000
static constexpr uint8_t  EP_TYPE_BULK_IN           = 0x81; // 0b10000001
static constexpr uint8_t  EP_TYPE_BULK_OUT          = 0x80; // 0b10000000
static constexpr uint8_t  EP_TYPE_INTERRUPT_IN      = 0xC1; // 0b11000001
static constexpr uint8_t  EP_TYPE_INTERRUPT_OUT     = 0xC0; // 0b11000000

static constexpr uint8_t  EP_SINGLE_BUFFER          = 0x02; // 0b00000010
static constexpr uint8_t  EP_DOUBLE_BUFFER          = 0x06; // 0b00000110

// Standard request types (USB 2.0 specification, section 9.3, table 9-2)
static constexpr uint8_t REQUEST_HOSTTODEVICE       = 0x00; // 0b0-------
static constexpr uint8_t REQUEST_DEVICETOHOST       = 0x80; // 0b1-------

static constexpr uint8_t REQUEST_STANDARD           = 0x00; // 0b-00-----
static constexpr uint8_t REQUEST_CLASS              = 0x20; // 0b-01-----
static constexpr uint8_t REQUEST_VENDOR             = 0x40; // 0b-10-----
static constexpr uint8_t REQUEST_RESERVED           = 0x60; // 0b-11-----

static constexpr uint8_t REQUEST_DEVICE             = 0x00; // 0b---00000
static constexpr uint8_t REQUEST_INTERFACE          = 0x01; // 0b---00001
static constexpr uint8_t REQUEST_ENDPOINT           = 0x02; // 0b---00010
static constexpr uint8_t REQUEST_OTHER              = 0x03; // 0b---00011

// Standard device requests (USB 2.0 specification, section 9.4, table 9-4)
static constexpr uint8_t GET_STATUS                 = 0x00;
static constexpr uint8_t CLR_FEATURE                = 0x01;
static constexpr uint8_t SET_FEATURE                = 0x03;
static constexpr uint8_t SET_ADDRESS                = 0x05;
static constexpr uint8_t GET_DESCRIPTOR             = 0x06;
static constexpr uint8_t SET_DESCRIPTOR             = 0x07;
static constexpr uint8_t GET_CONFIGURATION          = 0x08;
static constexpr uint8_t SET_CONFIGURATION          = 0x09;
static constexpr uint8_t GET_INTERFACE              = 0x0A;
static constexpr uint8_t SET_INTERFACE              = 0x0B;

// CDC (communication class device) requests (USB CDC 1.1 specification, section 3.6.2.1, table 4)
static constexpr uint8_t CDC_SET_LINE_CODING        = 0x20;
static constexpr uint8_t CDC_GET_LINE_CODING        = 0x21;
static constexpr uint8_t CDC_SET_CONTROL_LINE_STATE = 0x22;
static constexpr uint8_t CDC_SEND_BREAK             = 0x23;


//
// Endpoint configuration
//
static const uint8_t PROGMEM usbEndpointConfigurationTable[] = {
    1, EP_TYPE_INTERRUPT_IN,  EP_SIZE(CDC_ACM_INTRSIZE) | EP_SINGLE_BUFFER, // ACM0
    1, EP_TYPE_BULK_OUT,      EP_SIZE(CDC_ACM_BULKSIZE) | EP_DOUBLE_BUFFER, // RXD0
    1, EP_TYPE_BULK_IN,       EP_SIZE(CDC_ACM_BULKSIZE) | EP_DOUBLE_BUFFER, // TXD0

    1, EP_TYPE_INTERRUPT_IN,  EP_SIZE(CDC_ACM_INTRSIZE) | EP_SINGLE_BUFFER, // ACM1
    1, EP_TYPE_BULK_OUT,      EP_SIZE(CDC_ACM_BULKSIZE) | EP_DOUBLE_BUFFER, // RXD1
    1, EP_TYPE_BULK_IN,       EP_SIZE(CDC_ACM_BULKSIZE) | EP_DOUBLE_BUFFER  // TXD1
};

static constexpr uint8_t NUM_ENDPOINTS = 6;


//
// Descriptor data
//
#if defined(__AVR_AT90USB1286__)
#define BCD_DEVICE 0x11, 0x01
#elif defined(__AVR_AT90USB646__)
#define BCD_DEVICE 0x10, 0x01
#elif defined(__AVR_ATmega32U4__)
#define BCD_DEVICE 0x01, 0x01
#elif defined(__AVR_ATmega16U4__)
#define BCD_DEVICE 0x00, 0x01
#else
#error "unsupported AVR MCU"
#endif

// Without using IAD (interface association descriptor), the virtual UART(s) would
// not be properly enumerated under Windows
#define USE_IAD

static constexpr uint8_t STR_IDX_MFRE = 0x01;
static constexpr uint8_t STR_IDX_PROD = 0x02;
static constexpr uint8_t STR_IDX_SNUM = 0x03;

static const uint8_t PROGMEM usbDeviceDescriptor[] = {
    // Device descriptor (USB 2.0 specification, section 9.6.1, table 9-8)
    18,                 // bLength            - descriptor size in bytes
    0x01,               // bDescriptorType    - DEVICE descriptor type
    0x00, 0x02,         // bcdUSB             - USB version 2.0
#ifndef USE_IAD
    0x02,               // bDeviceClass       - class    : CDC
    0x00,               // bDeviceSubClass    - subclass : none
    0x00,               // bDeviceProtocol    - protocol : none
#else
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/usb-interface-association-descriptor
    0xEF,               // bDeviceClass       - class    : miscellaneous
    0x02,               // bDeviceSubClass    - subclass : common class
    0x01,               // bDeviceProtocol    - protocol : interface association descriptor
#endif
    ENDPOINT0_SIZE,     // bMaxPacketSize0    - maximum packet size for endpoint 0
    LSB(VID), MSB(VID), // idVendor           - vendor ID
    LSB(PID), MSB(PID), // idProduct          - product ID
    BCD_DEVICE,         // bcdDevice          - device release number
    STR_IDX_MFRE,       // iManufacturer      - manufacturer string index
    STR_IDX_PROD,       // iProduct           - product string index
    STR_IDX_SNUM,       // iSerialNumber      - serial number string index
    0x01                // bNumConfigurations - number of configurations
};


#ifndef USE_IAD
static constexpr uint8_t USB_CONFIG_DESC_SIZE = 9 + (    9 + 5 + 5 + 4 + 5 + 7 + 9 + 7 + 7) * 2;
#else
static constexpr uint8_t USB_CONFIG_DESC_SIZE = 9 + (8 + 9 + 5 + 5 + 4 + 5 + 7 + 9 + 7 + 7) * 2;
#endif

static constexpr uint8_t STR_IDX_CONF     = 0x04;

static constexpr uint8_t STR_IDX_IAD0     = 0x05;
static constexpr uint8_t STR_IDX_CTL0     = 0x06;
static constexpr uint8_t STR_IDX_DTA0     = 0x07;

static constexpr uint8_t STR_IDX_IAD1     = 0x08;
static constexpr uint8_t STR_IDX_CTL1     = 0x09;
static constexpr uint8_t STR_IDX_DTA1     = 0x0A;

static constexpr uint8_t CDC_INVALIDX_IFN = 0xFF;

static constexpr uint8_t CDC_ACM0_CTL_IFN = 0x00;
static constexpr uint8_t CDC_ACM0_DTA_IFN = 0x01;

static constexpr uint8_t CDC_ACM1_CTL_IFN = 0x02;
static constexpr uint8_t CDC_ACM1_DTA_IFN = 0x03;

static const uint8_t PROGMEM usbConfigurationDescriptor[USB_CONFIG_DESC_SIZE] = {
    // ==============================
    // ===== Primary descriptor =====
    // ==============================
    // Configuration descriptor (USB 2.0 specification, section 9.6.3, table 9-10)
    9,                                                    // bLength             - descriptor size in bytes
    0x02,                                                 // bDescriptorType     - CONFIGURATION descriptor type
    LSB(USB_CONFIG_DESC_SIZE), MSB(USB_CONFIG_DESC_SIZE), // wTotalLength        - total length of data returned for this configuration
    0x04,                                                 // bNumInterfaces      - number of interfaces supported by this configuration
    0x01,                                                 // bConfigurationValue - value to use as an argument to the SET_CONFIGURATION request
    STR_IDX_CONF,                                         // iConfiguration      - index of string descriptor describing this configuration
    0xC0,                                                 // bmAttributes        - 0b11000000 self-powered
    250,                                                  // bMaxPower           - maximum power consumption 500 mA
    // ====================================
    // ===== First CDC ACM descriptor =====
    // ====================================
#ifdef USE_IAD
    // Interface association descriptor (USB 2.0 IAD specification, section 9.X.Y, table 9-Z)
    8,                                                    // bLength             - descriptor size in bytes
    0x0B,                                                 // bDescriptorType     - INTERFACE_ASSOCIATION descriptor type
    CDC_ACM0_CTL_IFN,                                     // bFirstInterface     - first interface number
    0x02,                                                 // bInterfaceCount     - number of interfaces
    0x02,                                                 // bFunctionClass      - class    : CDC communication
    0x02,                                                 // bFunctionSubClass   - subclass : abstract control model
    0x00,                                                 // bFunctionProtocol   - protocol : none
    STR_IDX_IAD0,                                         // iFunction           - index of string descriptor describing this descriptor
#endif
    // Interface descriptor (USB 2.0 specification, section 9.6.5, table 9-12)
    9,                                                    // bLength             - descriptor size in bytes
    0x04,                                                 // bDescriptorType     - INTERFACE descriptor type
    CDC_ACM0_CTL_IFN,                                     // bInterfaceNumber    - interface number
    0x00,                                                 // bAlternateSetting   - alternate setting number
    0x01,                                                 // bNumEndpoints       - number of endpoints
    0x02,                                                 // bInterfaceClass     - class    : CDC communication
    0x02,                                                 // bInterfaceSubClass  - subclass : abstract control model
    0x01,                                                 // bInterfaceProtocol  - protocol : AT Commands V.250 etc.
    STR_IDX_CTL0,                                         // iInterface          - index of string descriptor describing this descriptor
    // CDC header functional descriptor (USB CDC 1.1 specification, section 5.2.3.1, table 26)
    5,                                                    // bFunctionLength     - descriptor size in bytes
    0x24,                                                 // bDescriptorType     - CS_INTERFACE descriptor type
    0x00,                                                 // bDescriptorSubtype  - header functional descriptor
    0x10, 0x01,                                           // bcdCDC              - CDC version 1.1
    // Abstract control management functional descriptor (USB CDC 1.1 specification, section 5.2.3.3, table 28)
    4,                                                    // bFunctionLength     - descriptor size in bytes
    0x24,                                                 // bDescriptorType     - CS_INTERFACE descriptor type
    0x02,                                                 // bDescriptorSubtype  - abstract control management functional descriptor
    0x06,                                                 // bmCapabilities      - device supports the SEND_BREAK, GET_LINE_CODING, SET_LINE_CODING, SET_CONTROL_LINE_STATE requests
    // Union functional descriptor (USB CDC 1.1 specification, section 5.2.3.8, table 33)
    5,                                                    // bFunctionLength     - descriptor size in bytes
    0x24,                                                 // bDescriptorType     - CS_INTERFACE descriptor type
    0x06,                                                 // bDescriptorSubtype  - union functional descriptor
    CDC_ACM0_CTL_IFN,                                     // bMasterInterface    - interface number of the control interface
    CDC_ACM0_DTA_IFN,                                     // bSlaveInterface0    - interface number of the subordinate interface
    // Call management functional descriptor (USB CDC 1.1 specification, section 5.2.3.2, table 27)
    5,                                                    // bFunctionLength     - descriptor size in bytes
    0x24,                                                 // bDescriptorType     - CS_INTERFACE descriptor type
    0x01,                                                 // bDescriptorSubtype  - call management functional descriptor
    0x00,                                                 // bmCapabilities      - device does not handle call management itself
    0x01,                                                 // bDataInterface      - multiplexed commands are handled via data interface 0x01
    // Endpoint Descriptor (USB 2.0 specification, section 9.6.6, table 9-13)
    7,                                                    // bLength             - descriptor size in bytes
    0x05,                                                 // bDescriptorType     - ENDPOINT descriptor Type
    CDC_ACM_ENDPOINT[0] | 0x80,                           // bEndpointAddress    - input endpoint
    0x03,                                                 // bmAttributes        - interrupt endpoint
    CDC_ACM_INTRSIZE, 0,                                  // wMaxPacketSize      - maximum packet size in bytes
    64,                                                   // bInterval           - endpoint polling interval
    // Interface descriptor (USB 2.0 specification, section 9.6.5, table 9-12)
    0x09,                                                 // bLength             - descriptor size in bytes
    0x04,                                                 // bDescriptorType     - INTERFACE descriptor type
    CDC_ACM0_DTA_IFN,                                     // bInterfaceNumber    - interface number
    0x00,                                                 // bAlternateSetting   - alternate setting number
    0x02,                                                 // bNumEndpoints       - number of endpoints
    0x0A,                                                 // bInterfaceClass     - class    : CDC data
    0x00,                                                 // bInterfaceSubClass  - subclass : none
    0x00,                                                 // bInterfaceProtocol  - protocol : none
    STR_IDX_DTA0,                                         // iInterface          - index of string descriptor describing this descriptor
    // Endpoint descriptor (USB 2.0 specification, section 9.6.6, table 9-13)
    7,                                                    // bLength             - descriptor size in bytes
    0x05,                                                 // bDescriptorType     - ENDPOINT descriptor Type
    CDC_RXD_ENDPOINT[0],                                  // bEndpointAddress    - output endpoint
    0x02,                                                 // bmAttributes        - bulk endpoint
    CDC_ACM_BULKSIZE, 0,                                  // wMaxPacketSize      - maximum packet size in bytes
    0,                                                    // bInterval           - endpoint polling interval
    // Endpoint descriptor, (USB 2.0 specification, section 9.6.6, table 9-13)
    7,                                                    // bLength             - descriptor size in bytes
    0x05,                                                 // bDescriptorType     - ENDPOINT descriptor Type
    CDC_TXD_ENDPOINT[0] | 0x80,                           // bEndpointAddress    - input endpoint
    0x02,                                                 // bmAttributes        - bulk endpoint
    CDC_ACM_BULKSIZE, 0,                                  // wMaxPacketSize      - maximum packet size in bytes
    0,                                                    // bInterval           - endpoint polling interval
    // =====================================
    // ===== Second CDC ACM descriptor =====
    // =====================================
#ifdef USE_IAD
    // Interface association descriptor (USB 2.0 IAD specification, section 9.X.Y, table 9-Z)
    8,                                                    // bLength             - descriptor size in bytes
    0x0B,                                                 // bDescriptorType     - INTERFACE_ASSOCIATION descriptor type
    CDC_ACM1_CTL_IFN,                                     // bFirstInterface     - first interface number
    0x02,                                                 // bInterfaceCount     - number of interfaces
    0x02,                                                 // bFunctionClass      - class    : CDC communication
    0x02,                                                 // bFunctionSubClass   - subclass : abstract control model
    0x00,                                                 // bFunctionProtocol   - protocol : none
    STR_IDX_IAD1,                                         // iFunction           - index of string descriptor describing this descriptor
#endif
    // Interface descriptor (USB 2.0 specification, section 9.6.5, table 9-12)
    9,                                                    // bLength             - descriptor size in bytes
    0x04,                                                 // bDescriptorType     - INTERFACE descriptor type
    CDC_ACM1_CTL_IFN,                                     // bInterfaceNumber    - interface number
    0x00,                                                 // bAlternateSetting   - alternate setting number
    0x01,                                                 // bNumEndpoints       - number of endpoints
    0x02,                                                 // bInterfaceClass     - class    : CDC communication
    0x02,                                                 // bInterfaceSubClass  - subclass : abstract control model
    0x01,                                                 // bInterfaceProtocol  - protocol : AT Commands V.250 etc.
    STR_IDX_CTL1,                                         // iInterface          - index of string descriptor describing this descriptor
    // CDC header functional descriptor (USB CDC 1.1 specification, section 5.2.3.1, table 26)
    5,                                                    // bFunctionLength     - descriptor size in bytes
    0x24,                                                 // bDescriptorType     - CS_INTERFACE descriptor type
    0x00,                                                 // bDescriptorSubtype  - header functional descriptor
    0x10, 0x01,                                           // bcdCDC              - CDC version 1.1
    // Abstract control management functional descriptor (USB CDC 1.1 specification, section 5.2.3.3, table 28)
    4,                                                    // bFunctionLength     - descriptor size in bytes
    0x24,                                                 // bDescriptorType     - CS_INTERFACE descriptor type
    0x02,                                                 // bDescriptorSubtype  - abstract control management functional descriptor
    0x06,                                                 // bmCapabilities      - device supports the SEND_BREAK, GET_LINE_CODING, SET_LINE_CODING, SET_CONTROL_LINE_STATE requests
    // Union functional descriptor (USB CDC 1.1 specification, section 5.2.3.8, table 33)
    5,                                                    // bFunctionLength     - descriptor size in bytes
    0x24,                                                 // bDescriptorType     - CS_INTERFACE descriptor type
    0x06,                                                 // bDescriptorSubtype  - union functional descriptor
    CDC_ACM1_CTL_IFN,                                     // bMasterInterface    - interface number of the control interface
    CDC_ACM1_DTA_IFN,                                     // bSlaveInterface0    - interface number of the subordinate interface
    // Call management functional descriptor (USB CDC 1.1 specification, section 5.2.3.2, table 27)
    5,                                                    // bFunctionLength     - descriptor size in bytes
    0x24,                                                 // bDescriptorType     - CS_INTERFACE descriptor type
    0x01,                                                 // bDescriptorSubtype  - call management functional descriptor
    0x00,                                                 // bmCapabilities      - device does not handle call management itself
    0x03,                                                 // bDataInterface      - multiplexed commands are handled via data interface 0x03
    // Endpoint Descriptor (USB 2.0 specification, section 9.6.6, table 9-13)
    7,                                                    // bLength             - descriptor size in bytes
    0x05,                                                 // bDescriptorType     - ENDPOINT descriptor Type
    CDC_ACM_ENDPOINT[1] | 0x80,                           // bEndpointAddress    - input endpoint
    0x03,                                                 // bmAttributes        - interrupt endpoint
    CDC_ACM_INTRSIZE, 0,                                  // wMaxPacketSize      - maximum packet size in bytes
    64,                                                   // bInterval           - endpoint polling interval
    // Interface descriptor (USB 2.0 specification, section 9.6.5, table 9-12)
    9,                                                    // bLength             - descriptor size in bytes
    0x04,                                                 // bDescriptorType     - INTERFACE descriptor type
    CDC_ACM1_DTA_IFN,                                     // bInterfaceNumber    - interface number
    0x00,                                                 // bAlternateSetting   - alternate setting number
    0x02,                                                 // bNumEndpoints       - number of endpoints
    0x0A,                                                 // bInterfaceClass     - class    : CDC data
    0x00,                                                 // bInterfaceSubClass  - subclass : none
    0x00,                                                 // bInterfaceProtocol  - protocol : none
    STR_IDX_DTA1,                                         // iInterface          - index of string descriptor describing this descriptor
    // Endpoint descriptor (USB 2.0 specification, section 9.6.6, table 9-13)
    7,                                                    // bLength             - descriptor size in bytes
    0x05,                                                 // bDescriptorType     - ENDPOINT descriptor Type
    CDC_RXD_ENDPOINT[1],                                  // bEndpointAddress    - output endpoint
    0x02,                                                 // bmAttributes        - bulk endpoint
    CDC_ACM_BULKSIZE, 0,                                  // wMaxPacketSize      - maximum packet size in bytes
    0,                                                    // bInterval           - endpoint polling interval
    // Endpoint descriptor, (USB 2.0 specification, section 9.6.6, table 9-13)
    7,                                                    // bLength             - descriptor size in bytes
    0x05,                                                 // bDescriptorType     - ENDPOINT descriptor Type
    CDC_TXD_ENDPOINT[1] | 0x80,                           // bEndpointAddress    - input endpoint
    0x02,                                                 // bmAttributes        - bulk endpoint
    CDC_ACM_BULKSIZE, 0,                                  // wMaxPacketSize      - maximum packet size in bytes
    0                                                     // bInterval           - endpoint polling interval
};


#if !defined(ECXX_AVR_STR_MANUFACTURER) || !defined(ECXX_AVR_STR_PRODUCT) || !defined(ECXX_AVR_STR_SERIAL_NUMBER)
    #define ECXX_AVR_STR_MANUFACTURER  L"eCxx.SourceForge.io"
    #define ECXX_AVR_STR_PRODUCT       L"eCxx CDC-ACM Dual Virtual UART"
    #define ECXX_AVR_STR_SERIAL_NUMBER L"eCxxVU:2"
#endif

static constexpr uint16_t LANGID_EN_US = 0x0409;

template <size_t N>
struct USBStringDescriptor {
    // String descriptor (USB 2.0 specification, section 9.6.7, table 9-15)
    uint8_t bLength;         // bLength         - descriptor size in bytes
    uint8_t bDescriptorType; // bDescriptorType - CONFIGURATION descriptor type (0x03)
    wchar_t wString[N];      // wLandID[1]      - LANGID code zero       (1 character  ; 2 bytes per character)
                             // wString         - unicode-encoded string (N characters ; 2 bytes per character)
};

static const USBStringDescriptor< 1> PROGMEM usbStrDesc0 = { 4,                                  0x03, { LANGID_EN_US }           };
static const USBStringDescriptor<20> PROGMEM usbStrDesc1 = { sizeof(ECXX_AVR_STR_MANUFACTURER ), 0x03, ECXX_AVR_STR_MANUFACTURER  };
static const USBStringDescriptor<31> PROGMEM usbStrDesc2 = { sizeof(ECXX_AVR_STR_PRODUCT      ), 0x03, ECXX_AVR_STR_PRODUCT       };
static const USBStringDescriptor< 9> PROGMEM usbStrDesc3 = { sizeof(ECXX_AVR_STR_SERIAL_NUMBER), 0x03, ECXX_AVR_STR_SERIAL_NUMBER };

#define STR_CONF L"CONF"

#define STR_IAD0 L"IAD0"
#define STR_CTL0 L"CTL0"
#define STR_DTA0 L"DTA0"

#define STR_IAD1 L"IAD1"
#define STR_CTL1 L"CTL1"
#define STR_DTA1 L"DTA1"

static const USBStringDescriptor<5> PROGMEM usbStrDesc4 = { sizeof(STR_CONF), 0x03, STR_CONF };
static const USBStringDescriptor<5> PROGMEM usbStrDesc5 = { sizeof(STR_IAD0), 0x03, STR_IAD0 };
static const USBStringDescriptor<5> PROGMEM usbStrDesc6 = { sizeof(STR_CTL0), 0x03, STR_CTL0 };
static const USBStringDescriptor<5> PROGMEM usbStrDesc7 = { sizeof(STR_DTA0), 0x03, STR_DTA0 };
static const USBStringDescriptor<5> PROGMEM usbStrDesc8 = { sizeof(STR_IAD1), 0x03, STR_IAD1 };
static const USBStringDescriptor<5> PROGMEM usbStrDesc9 = { sizeof(STR_CTL1), 0x03, STR_CTL1 };
static const USBStringDescriptor<5> PROGMEM usbStrDescA = { sizeof(STR_DTA1), 0x03, STR_DTA1 };


struct USBDescriptorList {
          uint16_t wValue;
          uint16_t wIndex;
    const uint8_t* addr;
          uint8_t  length;
};

static const USBDescriptorList PROGMEM descriptorList[] = {
    { 0x0100,                0x0000,       usbDeviceDescriptor,                            sizeof(usbDeviceDescriptor       ) },
    { 0x0200,                0x0000,       usbConfigurationDescriptor,                     sizeof(usbConfigurationDescriptor) },
    { 0x0300,                0x0000,       reinterpret_cast<const uint8_t*>(&usbStrDesc0), 4                                  },
    { 0x0300 | STR_IDX_MFRE, LANGID_EN_US, reinterpret_cast<const uint8_t*>(&usbStrDesc1), sizeof(ECXX_AVR_STR_MANUFACTURER ) },
    { 0x0300 | STR_IDX_PROD, LANGID_EN_US, reinterpret_cast<const uint8_t*>(&usbStrDesc2), sizeof(ECXX_AVR_STR_PRODUCT      ) },
    { 0x0300 | STR_IDX_SNUM, LANGID_EN_US, reinterpret_cast<const uint8_t*>(&usbStrDesc3), sizeof(ECXX_AVR_STR_SERIAL_NUMBER) },
    { 0x0300 | STR_IDX_CONF, LANGID_EN_US, reinterpret_cast<const uint8_t*>(&usbStrDesc4), sizeof(STR_CONF                  ) },
    { 0x0300 | STR_IDX_IAD0, LANGID_EN_US, reinterpret_cast<const uint8_t*>(&usbStrDesc5), sizeof(STR_IAD0                  ) },
    { 0x0300 | STR_IDX_CTL0, LANGID_EN_US, reinterpret_cast<const uint8_t*>(&usbStrDesc6), sizeof(STR_CTL0                  ) },
    { 0x0300 | STR_IDX_DTA0, LANGID_EN_US, reinterpret_cast<const uint8_t*>(&usbStrDesc7), sizeof(STR_DTA0                  ) },
    { 0x0300 | STR_IDX_IAD1, LANGID_EN_US, reinterpret_cast<const uint8_t*>(&usbStrDesc8), sizeof(STR_IAD1                  ) },
    { 0x0300 | STR_IDX_CTL1, LANGID_EN_US, reinterpret_cast<const uint8_t*>(&usbStrDesc9), sizeof(STR_CTL1                  ) },
    { 0x0300 | STR_IDX_DTA1, LANGID_EN_US, reinterpret_cast<const uint8_t*>(&usbStrDescA), sizeof(STR_DTA1                  ) }
};

static constexpr uint8_t NUM_DESC_LIST_ITEMS = eCxx::arraySize(descriptorList);


//
// A simple AVR USB driver
//
static volatile uint8_t usbConfiguration __ECXX_ATTR_SEC_NOINIT;
static volatile uint8_t usbSuspended     __ECXX_ATTR_SEC_NOINIT;

// The time remaining before any partially full packet (or a zero length packet) will be transmitted
volatile uint8_t txdFlushTimer[2] __ECXX_ATTR_SEC_NOINIT;

// Serial port settings
//     # These are ignored, but kept in RAM because the CDC spec requires a read that returns the current settings
//     # Baudrate  : default 0x0000E100 = 57600 bps
//     # Stop bits : 0 = 1 stop bit ; 1 = 1.5 stop bits ; 2 = 2 stop bits
//     # Parity    : 0 = none       ; 1 = odd           ; 2 = even        ; 3 = mark ; 4 = space
//     # Data bits : 5, 6, 7, 8, or 16
static volatile uint8_t acmLineCoding[2][7] = {
//    Baudrate (UInt32 Little Endian)   Stop Bits   Parity   Data Bits
    { 0x00, 0xE1, 0x00, 0x00,           0x00,       0x00,    0x08 },
    { 0x00, 0xE1, 0x00, 0x00,           0x00,       0x00,    0x08 }
};

// D1 = RTS ; D0 = DTR
static volatile uint8_t acmLineRtsDtr[2] __ECXX_ATTR_SEC_NOINIT;

#ifdef ECXX_AVR_VUART_DUMP_SETTINGS
static volatile uint32_t wIndexes[2] __ECXX_ATTR_SEC_NOINIT;
#endif

// Callback function to be called if the USB host opens virtual UART #0 using the magic baudrate
static VoidCallback magicBaudrateCB = nullptr;

// Callback function to be called if the USB host sets the DTR line of virtual UART #0 to low
static VoidCallback dtrLowCB = nullptr;

// Callback function to be called on USB CDC SET_LINE_CODING request for virtual UARTs #0 and #1
static VoidCallback setLineCodingCB[2] = { nullptr, nullptr };


////////////////////////////////////////////////////////////////////////////////////////////////////


extern void usbStart()
{
    // Get and check the CPU frequency
    const uint8_t cpuFreq = Application::getCPUFrequency() / 1000000UL;
    if(cpuFreq != 8 && cpuFreq != 16) return;

    // No need to continue if already enabled
    const uint8_t u = USBCON;
    if( ( u & _BV(USBE) ) && !( u & _BV(FRZCLK) ) ) return;

    // Ensure the restart lock is not set
    *magicKeyPos_RestartLock = 0;

#if defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB646__)
    // USB device mode enable
    UHWCON |= _BV(UIMOD);
#endif

    // USB pad regulator enable
    UHWCON |= _BV(UVREGE);

    // Enable USB with clock disabled
    USBCON |= _BV(USBE) | _BV(FRZCLK);

    // Configure PLL and wait for PLL lock
#if defined(__AVR_AT90USB1286__)
    if(cpuFreq == 8) PLLCSR = 0b00001110;
    else             PLLCSR = 0b00010110;
#elif defined(__AVR_AT90USB646__)
    if(cpuFreq == 8) PLLCSR = 0b00001110;
    else             PLLCSR = 0b00011010;
#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)
    if(cpuFreq == 8) PLLCSR = 0b00000010;
    else             PLLCSR = 0b00010010;
#else
    #error "unsupported AVR MCU"
#endif

    while( !( PLLCSR & _BV(PLOCK) ) );

    // Start USB clock
    USBCON &= ~_BV(FRZCLK);
    USBCON |=  _BV(OTGPADE);

    // Enable pull-up resistors
    UDCON &= ~_BV(DETACH);

    // Set variables
    usbConfiguration = 0;
    usbSuspended     = 0;
    txdFlushTimer[0] = 0;
    txdFlushTimer[1] = 0;
    acmLineRtsDtr[0] = 0;
    acmLineRtsDtr[1] = 0;

    // Clear all interrupt flahs
    UDINT = 0;

    // Enable end-of-reset, start-of-frame, and suspend interrupts
    UDIEN = _BV(EORSTE) | _BV(SOFE) | _BV(SUSPE);
}


extern void usbStop()
{
    // Disable all interrupts
    UDIEN = 0;

    // Disable pull-up resistors
    UDCON |= _BV(DETACH);

    // Disable USB
    USBCON &= ~_BV(USBE) & ~_BV(OTGPADE);

    // Disable PLL
    PLLCSR &= ~_BV(PLLE);

    // Set variables
    usbConfiguration = 0;
    usbSuspended     = 1;
}


extern bool usbReady()
{ return !!usbConfiguration & !usbSuspended; }


////////////////////////////////////////////////////////////////////////////////////////////////////


static inline uint32_t _getVUARTBaudrate(uint8_t idx)
{
    return   ( static_cast<uint32_t>(acmLineCoding[idx][0])       )
           | ( static_cast<uint32_t>(acmLineCoding[idx][1]) <<  8 )
           | ( static_cast<uint32_t>(acmLineCoding[idx][2]) << 16 )
           | ( static_cast<uint32_t>(acmLineCoding[idx][3]) << 24 );
}


extern void setVUART0MagicBaudrateCallback(VoidCallback cb)
{ magicBaudrateCB = cb; }


extern void setVUART0DtrLowCallback(VoidCallback cb)
{ dtrLowCB = cb; }


extern bool setVUARTChangeSettingsCallback(uint8_t idx, VoidCallback cb)
{
    if(idx > 1) return false;

    setLineCodingCB[idx] = cb;

    return true;
}


extern bool getVUARTSerialSettings(uint8_t idx, uint32_t& baudrate, uint8_t& charSize, uint8_t& parity, uint8_t& stopBits)
{
    if(idx > 1) return false;

    baudrate = _getVUARTBaudrate(idx);
    charSize = acmLineCoding[idx][6];
    parity   = acmLineCoding[idx][5];
    stopBits = acmLineCoding[idx][4];

    return true;
}


extern uint32_t getVUARTBaudrate(uint8_t idx)
{ return (idx > 1) ? 0 : _getVUARTBaudrate(idx); }


extern uint8_t getVUARTRtsDtrState(uint8_t idx)
{ return (idx > 1) ? 255 : acmLineRtsDtr[idx]; }


#ifdef ECXX_AVR_VUART_DUMP_SETTINGS

extern uint32_t getVUARTWIndexes(uint8_t idx)
{ return wIndexes[idx]; }

#endif


//
// USB interrupt vectors
//

// USB device Interrupt - handle all device-level events;
// the transmit buffer flushing is triggered by the start of frame
ISR(USB_GEN_vect)
{
    const uint8_t intBits = UDINT;
    UDINT = 0;

    if( intBits & _BV(EORSTI) ) {
        UENUM   = 0;
        UECONX  = 1;
        UECFG0X = EP_TYPE_CONTROL;
        UECFG1X = EP_SIZE(ENDPOINT0_SIZE) | EP_SINGLE_BUFFER;
        UEIENX  = _BV(RXSTPE);
        // Set variable
        usbConfiguration = 0;
        acmLineRtsDtr[0] = 0;
        acmLineRtsDtr[1] = 0;
    }

    if( ( intBits & _BV(SOFI) ) && usbConfiguration ) {
        if(txdFlushTimer[0]) {
            --txdFlushTimer[0];
            if(!txdFlushTimer[0]) {
                UENUM  = CDC_TXD_ENDPOINT[0];
                UEINTX = 0b00111010;
            }
        }
        if(txdFlushTimer[1]) {
            --txdFlushTimer[1];
            if(!txdFlushTimer[1]) {
                UENUM  = CDC_TXD_ENDPOINT[1];
                UEINTX = 0b00111010;
            }
        }
    }

    if( intBits & _BV(SUSPI) ) {
        // Enable wake-up interrupt
        UDIEN = _BV(WAKEUPE);
        // Shutdown USB
        USBCON |=  _BV(USBE) | _BV(FRZCLK);
        PLLCSR &= ~_BV(PLLE);
        // Set variables
        usbConfiguration = 0;
        usbSuspended     = 1;
    }

    if( usbSuspended && ( intBits & _BV(WAKEUPI) ) ) {
        // Get and check the CPU frequency
        const uint8_t cpuFreq = Application::getCPUFrequency() / 1000000UL;
        if(cpuFreq != 8 && cpuFreq != 16) return;
        // Reconfigure PLL and wait for PLL lock
#if defined(__AVR_AT90USB1286__)
        if(cpuFreq == 8) PLLCSR = 0b00001110;
        else             PLLCSR = 0b00010110;
#elif defined(__AVR_AT90USB646__)
        if(cpuFreq == 8) PLLCSR = 0b00001110;
        else             PLLCSR = 0b00011010;
#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)
        if(cpuFreq == 8) PLLCSR = 0b00000010;
        else             PLLCSR = 0b00010010;
#else
        #error "unsupported AVR MCU"
#endif
        while( !( PLLCSR & _BV(PLOCK) ) );
        // Restart USB clock
        USBCON &= ~_BV(FRZCLK);
        USBCON |=  _BV(OTGPADE);
        // Enable end-of-reset, start-of-frame, and suspend interrupts
        UDIEN = _BV(EORSTE) | _BV(SOFE) | _BV(SUSPE);
        // Set variable
        usbSuspended = 0;
    }
}


// USB endpoint interrupt - endpoint 0 is handled here;
// other endpoints are manipulated by the user-callable functions and the start-of-frame interrupt
ISR(USB_COM_vect)
{
    // Select endpoint 0 and get the interrupt flags
    UENUM = 0;
    const uint8_t intBits = UEINTX;

    // Check if that the current bank contains a new valid SETUP packet
    if( intBits & _BV(RXSTPI) ) {

        // Get the request type and specific
        uint8_t  bmRequestType = UEDATX;
        uint8_t  bRequest      = UEDATX;
        // Get the value, index, and length
        uint16_t wValue;
        uint16_t wIndex;
        uint16_t wLength;
        readWordLSBFirst(wValue,  UEDATX);
        readWordLSBFirst(wIndex,  UEDATX);
        readWordLSBFirst(wLength, UEDATX);
        // Clear the interrupt flags
        UEINTX = ~( _BV(RXSTPI) | _BV(RXOUTI) | _BV(TXINI) );

        //
        // Standard device requests (USB 2.0 specification, section 9.4)
        //

        if(bRequest == GET_DESCRIPTOR) {
            const uint8_t* list = reinterpret_cast<const uint8_t*>(descriptorList);
                  uint16_t descVal;
            const uint8_t* descAddr;
                  uint8_t  descLength;
            for(uint8_t i = 0; ; ++i) {
                if(i >= NUM_DESC_LIST_ITEMS) {
                    // Stall
                    UECONX = _BV(STALLRQ) | _BV(EPEN);
                    return;
                }
                pgmReadWordPostInc(descVal, list);
                if(descVal != wValue) {
                    list += sizeof(USBDescriptorList) - 2;
                    continue;
                }
                pgmReadWordPostInc(descVal, list);
                if(descVal != wIndex) {
                    list += sizeof(USBDescriptorList) - 4;
                    continue;
                }
                pgmReadWordPostInc(descAddr, list);
                pgmReadByte(descLength, list);
                break;
            }
            uint8_t n;
            uint8_t len = (wLength < 256) ? wLength : 255;
            if(len > descLength) len = descLength;
            list = descAddr;
            do {
                // Wait for host ready for IN packet
                uint8_t i;
                do {
                    i = UEINTX;
                } while( !( i & ( _BV(TXINI) | _BV(RXOUTI) ) ) );
                // Abort as needed
                if( i & _BV(RXOUTI) ) return;
                // Send IN packet
                n = (len < ENDPOINT0_SIZE) ? len : ENDPOINT0_SIZE;
                for(i = n; i; --i) {
                    pgmReadBytePostInc(UEDATX, list);
                }
                len -= n;
                usbSendPacketAndClearBank();
            } while(len || n == ENDPOINT0_SIZE);
            return;
        }

        if(bRequest == SET_ADDRESS && bmRequestType == REQUEST_DEVICE) {
            usbSendPacketAndClearBank();
            usbWaitUntilBankIsReadyToAcceptNewInPacket();
            UDADDR = wValue | _BV(ADDEN);
            return;
        }

        if(bRequest == SET_CONFIGURATION && bmRequestType == REQUEST_DEVICE) {
            usbConfiguration = wValue;
            txdFlushTimer[0] = 0;
            txdFlushTimer[1] = 0;
            acmLineRtsDtr[0] = 0;
            acmLineRtsDtr[1] = 0;
            usbSendPacketAndClearBank();
            const uint8_t* cfg = usbEndpointConfigurationTable;
            for(uint8_t i = 1; i <= NUM_ENDPOINTS; ++i) {
                uint8_t en;
                UENUM = i;
                pgmReadBytePostInc(en, cfg);
                UECONX = en;
                if(en) {
                    pgmReadBytePostInc(UECFG0X, cfg);
                    pgmReadBytePostInc(UECFG1X, cfg);
                }
            }
            UERST = 0x1E;
            UERST = 0;
            return;
        }

        if(bRequest == GET_CONFIGURATION && bmRequestType == REQUEST_DEVICETOHOST) {
            usbWaitUntilBankIsReadyToAcceptNewInPacket();
            UEDATX = usbConfiguration;
            usbSendPacketAndClearBank();
            return;
        }

        if(bRequest == GET_STATUS) {
            usbWaitUntilBankIsReadyToAcceptNewInPacket();
            uint8_t r = 0;
            if( bmRequestType == (REQUEST_DEVICETOHOST | REQUEST_STANDARD | REQUEST_ENDPOINT) ) {
                UENUM = wIndex & 0x07;
                if( UECONX & _BV(STALLRQ) ) r = 1; // Halt
                UENUM = 0;
            }
            UEDATX = r;
            UEDATX = 0;
            usbSendPacketAndClearBank();
            return;
        }

        if( ( bRequest == CLR_FEATURE || bRequest == SET_FEATURE )
            && bmRequestType == (REQUEST_HOSTTODEVICE | REQUEST_STANDARD | REQUEST_ENDPOINT)
            && wValue == 0
        ) {
            const uint8_t i = wIndex & 0x07;
            if(i >= 1 && i <= MAX_ENDPOINT_NUMBER) {
                usbSendPacketAndClearBank();
                UENUM = i;
                if(bRequest == SET_FEATURE) {
                    UECONX = _BV(STALLRQ) | _BV(EPEN);
                }
                else {
                    UECONX = _BV(STALLRQC) | _BV(RSTDT) | _BV(EPEN);
                    UERST  = _BV(i);
                    UERST  = 0;
                }
                return;
            }
        }

        //
        // CDC (communication class device) requests (USB CDC 1.1 specification, section 6.2)
        //

        if( bRequest == CDC_SET_LINE_CODING && bmRequestType == (REQUEST_HOSTTODEVICE | REQUEST_CLASS | REQUEST_INTERFACE) ) {
            // Wait until new data is received
            usbWaitUntilNewOutDataReceived();
            // Get the interface index
            const uint8_t wix = wIndex & 0x07;
            const uint8_t ifn = (wix == CDC_ACM0_CTL_IFN) ? 0
                                                          : ( (wix == CDC_ACM1_CTL_IFN) ? 1
                                                                                        : CDC_INVALIDX_IFN
                                                            );
            // Got invalid index, discard the data
            if(ifn == CDC_INVALIDX_IFN) {
                uint8_t dummy;
                for(uint8_t i = 0; i < 7; ++i) dummy = UEDATX;
                eCxx::ignoreUnused(dummy);
            }
            // Got valid index, store the data
            else {
                volatile uint8_t* p = acmLineCoding[ifn];
                for(uint8_t i = 0; i < 7; ++i) *p++ = UEDATX;
            }
            // Send the response
            usbAckPacketAndClearBank();
            usbSendPacketAndClearBank();
            // Do not proceed if the interface index is invalid
            if(ifn == CDC_INVALIDX_IFN) return;
#ifdef ECXX_AVR_VUART_DUMP_SETTINGS
            // Dump the wIndex to the lower part of the variable
            wIndexes[ifn] &= 0x0000FFFF;
            wIndexes[ifn] |= ( static_cast<uint32_t>(wIndex) << 16 );
#endif
            // Call the user callback as needed
            if( setLineCodingCB[ifn] ) (*setLineCodingCB[ifn])();
            return;
        }

        if( bRequest == CDC_GET_LINE_CODING && bmRequestType == (REQUEST_DEVICETOHOST | REQUEST_CLASS | REQUEST_INTERFACE) ) {
            // Wait until new data can be sent
            usbWaitUntilBankIsReadyToAcceptNewInPacket();
            // Get the interface index
            const uint8_t wix = wIndex & 0x07;
            const uint8_t ifn = (wix == CDC_ACM0_CTL_IFN) ? 0
                                                          : ( (wix == CDC_ACM1_CTL_IFN) ? 1
                                                                                        : CDC_INVALIDX_IFN
                                                            );
            // Got invalid index, send all zeroes
            if(ifn == CDC_INVALIDX_IFN) {
                for(uint8_t i = 0; i < 7; ++i) UEDATX = 0;
            }
            // Got valid index, send the data
            else {
                volatile uint8_t* p = acmLineCoding[ifn];
                for(uint8_t i = 0; i < 7; ++i) UEDATX = *p++;
            }
            // Send the response
            usbSendPacketAndClearBank();
            return;
        }

        if( bRequest == CDC_SET_CONTROL_LINE_STATE && bmRequestType == (REQUEST_HOSTTODEVICE | REQUEST_CLASS | REQUEST_INTERFACE) ) {
            // Wait until new data can be sent and send the response
            usbWaitUntilBankIsReadyToAcceptNewInPacket();
            usbSendPacketAndClearBank();
            // Get the interface index
            const uint8_t wix = wIndex & 0x07;
            const uint8_t ifn = (wix == CDC_ACM0_CTL_IFN) ? 0
                                                          : ( (wix == CDC_ACM1_CTL_IFN) ? 1
                                                                                        : CDC_INVALIDX_IFN
                                                            );
            // Do not proceed if the interface index is invalid
            if(ifn == CDC_INVALIDX_IFN) return;
            // Store the data
            acmLineRtsDtr[ifn] = wValue & 0xFF;
#ifdef ECXX_AVR_VUART_DUMP_SETTINGS
            // Dump the wIndex to the upper part of the variable
            wIndexes[ifn] &= 0xFFFF0000;
            wIndexes[ifn] |= static_cast<uint32_t>(wIndex);
#endif
            // Stop and return if the restart lock has been set
            if(*magicKeyPos_RestartLock == MAGIC_KEY_RESTART_LOCK) return;
            // Check if the USB host opens the virtual UART #0 and then sets the DTR line to low
            if( !ifn && !(acmLineRtsDtr[ifn] & 0x01) ) {
                // Check if the virtual UART #0 was opened using the magic baudrate of 1200 bps
                if( _getVUARTBaudrate(0) == 1200UL) {
                    // If the user callback is set, call it
                    if(magicBaudrateCB) {
                        (*magicBaudrateCB)();
                    }
                    // No user callback, proceed with soft-reset to bootloader
                    else {
                        // Wait until there is no more busy banks
                        UENUM = CDC_TXD_ENDPOINT[0];
                        while(UESTA0X & 0x03) {
                            wdt_reset();
                            UEINTX = 0xFF;
                            while(UEINTX & 0x04);
                        }
                        UENUM = CDC_TXD_ENDPOINT[1];
                        while(UESTA0X & 0x03) {
                            wdt_reset();
                            UEINTX = 0xFF;
                            while(UEINTX & 0x04);
                        }
                        // Set the restart lock
                        *magicKeyPos_RestartLock = MAGIC_KEY_RESTART_LOCK;
                        // Enable interrupt so USB can run again
                        sei();
                        // Soft-reset to bootloader
                        resetToBootloader( &acmLineRtsDtr[ifn] );
                    }
                }
                // Other baudrate
                else {
                    // If the user callback is set, call it
                    if(dtrLowCB) {
                        (*dtrLowCB)();
                    }
                    // No user callback, proceed with jump to application start
                    else {
                        // Set the restart lock
                        *magicKeyPos_RestartLock = MAGIC_KEY_RESTART_LOCK;
                        // Enable interrupt so USB can run again
                        sei();
                        // Jump to application start
                        jumpToApplicationStart( &acmLineRtsDtr[ifn] );
                    }
                }
            }
            return;
        }

        if( bRequest == CDC_SEND_BREAK
            && bmRequestType == (REQUEST_HOSTTODEVICE | REQUEST_CLASS | REQUEST_INTERFACE)
        ) {
            usbWaitUntilBankIsReadyToAcceptNewInPacket();
            usbSendPacketAndClearBank();
            return;
        }

    } // if intBits & _BV(RXSTPI)

    // Stall
    UECONX = _BV(STALLRQ) | _BV(EPEN);
}


} // namespace eCxx


#endif
