/*
 * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <string.h>
#include <stdlib.h>
#include "usb/usb_types_ch9.h"
#include "dev_msc.h"

/*
Some tests where the ESP (acting as host) will require that a particular test
device acting as a MSC SCSI flash drive be connected. That test device's
information and descriptors are defined in this file.

If you are connecting a different MSC SCSI flash drive, please update
the descriptor and getter functions accordingly.

------------------------------ Device Descriptor -------------------------------
bLength                     : 0x12      (18 bytes)
bDescriptorType             : 0x01      (Device Descriptor)
bcdUSB                      : 0x0210    (2.10)
bDeviceClass                : 0x00
bDeviceSubClass             : 0x00
bDeviceProtocol             : 0x00
bMaxPacketSize0             : 0x40      (64 bytes)
idVendor                    : 0x0781    (SanDisk Corp)
idProduct                   : 0x5595
bcdDevice                   : 0x0100    (1.00)
iManufacturer               : 1
iProduct                    : 2
iSerial                     : 3
bNumConfigurations          : 1

--------------------------- Configuration Descriptor ---------------------------
bLength                     : 0x09      (9 bytes)
bDescriptorType             : 0x02      (Configuration Descriptor)
wTotalLength                : 0x0020    (32 bytes)
bNumInterfaces              : 0x01      (1 Interface)
bConfigurationValue         : 0x01      (Configuration 1)
iConfiguration              : 0x00      (No String Descriptor)
bmAttributes                : 0x80
 D7: Reserved, set 1            : 0x01
 D6: Self Powered               : 0x00  (no)
 D5: Remote Wakeup              : 0x00  (no)
 D4..0: Reserved, set 0         : 0x00
MaxPower                    : 0x70      (224 mA)

Data (HexDump)              : 09 02 20 00 01 01 00 80 70 09 04 00 00 02 08 06
                              50 00 07 05 81 02 00 02 00 07 05 02 02 00 02 00

----------------------------- Interface Descriptor -----------------------------
bLength                     : 0x09      (9 bytes)
bDescriptorType             : 0x04      (Interface Descriptor)
bInterfaceNumber            : 0x00
bAlternateSetting           : 0x00
bNumEndpoints               : 0x02      (2 Endpoints)
bInterfaceClass             : 0x08      (Mass Storage)
bInterfaceSubClass          : 0x06      (SCSI transparent command set)
bInterfaceProtocol          : 0x50      (Bulk-Only Transport)
iInterface                  : 0x00      (No String Descriptor)

------------------------------ Endpoint Descriptor -----------------------------
bLength                     : 0x07      (7 bytes)
bDescriptorType             : 0x05      (Endpoint Descriptor)
bEndpointAddress            : 0x81      (Direction=IN EndpointID=1)
bmAttributes                : 0x02      (TransferType=Bulk)
wMaxPacketSize              : 0x0040    (max 64 bytes for FS, 512 bytes for HS)
bInterval                   : 0x00      (never NAKs)

------------------------------ Endpoint Descriptor -----------------------------
bLength                     : 0x07      (7 bytes)
bDescriptorType             : 0x05      (Endpoint Descriptor)
bEndpointAddress            : 0x02      (Direction=OUT EndpointID=2)
bmAttributes                : 0x02      (TransferType=Bulk)
wMaxPacketSize              : 0x0040    (max 64 bytes for FS, 512 bytes for HS)
bInterval                   : 0x00      (never NAKs)

---------------------------- String Descriptor Manu ----------------------------
bLength                     : 0x0A      (10 bytes)
bDescriptorType             : 0x03      (String Descriptor)
wData                       : " USB"

---------------------------- String Descriptor Prod ----------------------------
bLength                     : 0x22      (34 bytes)
bDescriptorType             : 0x03      (String Descriptor)
wData                       : " SanDisk 3.2Gen1"

----------------------------- String Descriptor Ser ----------------------------
bLength                     : 0xF2      (242 bytes)
bDescriptorType             : 0x03      (String Descriptor)
wData                       : "0101cdd1e856b427bbb796f870561a4b2b817af9da9872c8d75217cccdd5d5eccb3a0000000000000000000096abe1a3ff83610095558107aea948b4"
*/

// --------------------------- Device Information ------------------------------

static const dev_msc_info_t dev_info = {
    .bInterfaceNumber = 0x00,
    .bAlternateSetting = 0x00,
    .in_ep_addr = 0x81,
    .out_up_addr = 0x02,
    .scsi_sector_size = 512,
};

// ------------------------------- Descriptors ---------------------------------

static const usb_device_desc_t dev_desc = {
    .bLength = USB_DEVICE_DESC_SIZE,
    .bDescriptorType = USB_B_DESCRIPTOR_TYPE_DEVICE,
    .bcdUSB = 0x0210,       // 2.10
    .bDeviceClass = USB_CLASS_PER_INTERFACE,
    .bDeviceSubClass = 0,
    .bDeviceProtocol = 0,
    .bMaxPacketSize0 = 64,
    .idVendor = 0x0781,     // SanDisk Corp
    .idProduct = 0x5595,
    .bcdDevice = 0x0100,    // 1.00
    .iManufacturer = 1,
    .iProduct = 2,
    .iSerialNumber = 3,
    .bNumConfigurations = 1,
};

static const usb_config_desc_t config_desc = {
    .bLength = USB_CONFIG_DESC_SIZE,
    .bDescriptorType = USB_B_DESCRIPTOR_TYPE_CONFIGURATION,
    .wTotalLength = 0x0020,     // 32 bytes
    .bNumInterfaces = 1,
    .bConfigurationValue = 1,
    .iConfiguration = 0,
    .bmAttributes = 0x80,
    .bMaxPower = 0x70,          // 224 mA
};

static const usb_intf_desc_t intf_desc = {
    .bLength = USB_INTF_DESC_SIZE,
    .bDescriptorType = USB_B_DESCRIPTOR_TYPE_INTERFACE,
    .bInterfaceNumber = 0,
    .bAlternateSetting = 0,
    .bNumEndpoints = 2,
    .bInterfaceClass = USB_CLASS_MASS_STORAGE,
    .bInterfaceSubClass = 0x06, //SCSI
    .bInterfaceProtocol = 0x50, //Bulk only
    .iInterface = 0,
};

static const usb_ep_desc_t in_ep_desc_fs = {
    .bLength = USB_EP_DESC_SIZE,
    .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT,
    .bEndpointAddress = 0x81,       // EP 1 IN
    .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK,
    .wMaxPacketSize = 64,
    .bInterval = 0,
};

static const usb_ep_desc_t in_ep_desc_hs = {
    .bLength = USB_EP_DESC_SIZE,
    .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT,
    .bEndpointAddress = 0x81,       // EP 1 IN
    .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK,
    .wMaxPacketSize = 512,
    .bInterval = 0,
};

static const usb_ep_desc_t out_ep_desc_fs = {
    .bLength = USB_EP_DESC_SIZE,
    .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT,
    .bEndpointAddress = 0x02,       // EP 2 OUT
    .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK,
    .wMaxPacketSize = 64,
    .bInterval = 0,
};

static const usb_ep_desc_t out_ep_desc_hs = {
    .bLength = USB_EP_DESC_SIZE,
    .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT,
    .bEndpointAddress = 0x02,       // EP 2 OUT
    .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK,
    .wMaxPacketSize = 512,
    .bInterval = 0,
};

/*
String descriptors are dynamically initialized due to issues with static
initialization of variable length array members. See IDF-9886.
*/

static const usb_str_desc_t str_desc_manu_base = {
    .bLength = sizeof(usb_str_desc_t) + (4 * sizeof(uint16_t)),
    .bDescriptorType = USB_B_DESCRIPTOR_TYPE_STRING,
};
static const uint16_t str_desc_manu_data[] = {
    0x0020,         // ' '
    0x0055,         // 'U'
    0x0053,         // 'S'
    0x0042,         // 'B'
};
static uint8_t *str_desc_manu[sizeof(str_desc_manu_base) + sizeof(str_desc_manu_data)];

static const usb_str_desc_t str_desc_prod_base = {
    .bLength = sizeof(usb_str_desc_t) + (16 * sizeof(uint16_t)),
    .bDescriptorType = USB_B_DESCRIPTOR_TYPE_STRING,
};
static const uint16_t str_desc_prod_data[] = {
    0x0020,         // ' '
    0x0053,         // 'S'
    0x0061,         // 'a'
    0x006e,         // 'n'
    0x0044,         // 'D'
    0x0069,         // 'i'
    0x0073,         // 's'
    0x006b,         // 'k'
    0x0020,         // ' '
    0x0033,         // '3'
    0x002e,         // '.'
    0x0032,         // '2'
    0x0047,         // 'G'
    0x0065,         // 'e'
    0x006e,         // 'n'
    0x0031,         // '1'
};
static uint8_t *str_desc_prod[sizeof(str_desc_prod_base) + sizeof(str_desc_prod_data)];

static const usb_str_desc_t str_desc_ser_base = {
    .bLength = sizeof(usb_str_desc_t) + (120 * sizeof(uint16_t)),
    .bDescriptorType = USB_B_DESCRIPTOR_TYPE_STRING,
};
static const uint16_t str_desc_ser_data[] = {
    /*
    The following string encoded in UTF-16LE

    "0101cdd1e856b427bbb796f870561a4b2b817af9da9872c8d75217cccdd5d5eccb3a0000000
    000000000000096abe1a3ff83610095558107aea948b4"
    */
    0x0030, 0x0031, 0x0030, 0x0031, 0x0063, 0x0064, 0x0064, 0x0031, 0x0065,
    0x0038, 0x0035, 0x0036, 0x0062, 0x0034, 0x0032, 0x0037, 0x0062, 0x0062,
    0x0062, 0x0037, 0x0039, 0x0036, 0x0066, 0x0038, 0x0037, 0x0030, 0x0035,
    0x0036, 0x0031, 0x0061, 0x0034, 0x0062, 0x0032, 0x0062, 0x0038, 0x0031,
    0x0037, 0x0061, 0x0066, 0x0039, 0x0064, 0x0061, 0x0039, 0x0038, 0x0037,
    0x0032, 0x0063, 0x0038, 0x0064, 0x0037, 0x0035, 0x0032, 0x0031, 0x0037,
    0x0063, 0x0063, 0x0063, 0x0064, 0x0064, 0x0035, 0x0064, 0x0035, 0x0065,
    0x0063, 0x0063, 0x0062, 0x0033, 0x0061, 0x0030, 0x0030, 0x0030, 0x0030,
    0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
    0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0039, 0x0036,
    0x0061, 0x0062, 0x0065, 0x0031, 0x0061, 0x0033, 0x0066, 0x0066, 0x0038,
    0x0033, 0x0036, 0x0031, 0x0030, 0x0030, 0x0039, 0x0035, 0x0035, 0x0035,
    0x0038, 0x0031, 0x0030, 0x0037, 0x0061, 0x0065, 0x0061, 0x0039, 0x0034,
    0x0038, 0x0062, 0x0034,
};
static uint8_t *str_desc_ser[sizeof(str_desc_ser_base) + sizeof(str_desc_ser_data)];

// -------------------------------- Functions ----------------------------------

void dev_msc_init(void)
{
    // Dynamically initialize string descriptors due to compiler limitations (see IDF-9886)
    uint8_t *ptr;

    // Initialize manufacturer string descriptor
    ptr = (uint8_t *)str_desc_manu;
    memcpy(ptr, &str_desc_manu_base, sizeof(str_desc_manu_base));
    ptr += sizeof(str_desc_manu_base);
    memcpy(ptr, &str_desc_manu_data, sizeof(str_desc_manu_data));

    // Initialize product string descriptor
    ptr = (uint8_t *)str_desc_prod;
    memcpy(ptr, &str_desc_prod_base, sizeof(str_desc_prod_base));
    ptr += sizeof(str_desc_prod_base);
    memcpy(ptr, &str_desc_prod_data, sizeof(str_desc_prod_data));

    // Initialize serial string descriptor
    ptr = (uint8_t *)str_desc_ser;
    memcpy(ptr, &str_desc_ser_base, sizeof(str_desc_ser_base));
    ptr += sizeof(str_desc_ser_base);
    memcpy(ptr, &str_desc_ser_data, sizeof(str_desc_ser_data));
}

const dev_msc_info_t *dev_msc_get_info(void)
{
    return &dev_info;
}

const usb_device_desc_t *dev_msc_get_dev_desc(usb_speed_t speed)
{
    return &dev_desc;
}

const usb_config_desc_t *dev_msc_get_config_desc(usb_speed_t speed)
{
    return &config_desc;
}

const usb_intf_desc_t *dev_msc_get_intf_desc(usb_speed_t speed)
{
    return &intf_desc;
}

const usb_ep_desc_t *dev_msc_get_in_ep_desc(usb_speed_t speed)
{
    const usb_ep_desc_t *ret;

    // EP descriptor differs by speed due to MPS
    switch (speed) {
    case USB_SPEED_FULL:
        ret = &in_ep_desc_fs;
        break;
    case USB_SPEED_HIGH:
        ret = &in_ep_desc_hs;
        break;
    default:
        ret = NULL;
        abort();    // Should never occur
        break;
    }

    return ret;
}

const usb_ep_desc_t *dev_msc_get_out_ep_desc(usb_speed_t speed)
{
    const usb_ep_desc_t *ret;

    // EP descriptor differs by speed due to MPS
    switch (speed) {
    case USB_SPEED_FULL:
        ret = &out_ep_desc_fs;
        break;
    case USB_SPEED_HIGH:
        ret = &out_ep_desc_hs;
        break;
    default:
        ret = NULL;
        abort();    // Should never occur
        break;
    }

    return ret;
}

const usb_str_desc_t *dev_msc_get_str_desc_manu(void)
{
    return (const usb_str_desc_t *)str_desc_manu;
}

const usb_str_desc_t *dev_msc_get_str_desc_prod(void)
{
    return (const usb_str_desc_t *)str_desc_prod;
}

const usb_str_desc_t *dev_msc_get_str_desc_ser(void)
{
    return (const usb_str_desc_t *)str_desc_ser;
}