//*****************************************************************************
//
// usbddfu-rt.c - USB Device Firmware Update runtime device class driver.
//
// Copyright (c) 2010-2020 Texas Instruments Incorporated.  All rights reserved.
// Software License Agreement
// 
// Texas Instruments (TI) is supplying this software for use solely and
// exclusively on TI's microcontroller products. The software is owned by
// TI and/or its suppliers, and is protected under applicable copyright
// laws. You may not combine this software with "viral" open-source
// software in order to form a larger program.
// 
// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
// DAMAGES, FOR ANY REASON WHATSOEVER.
// 
// This is part of revision 2.2.0.295 of the Tiva USB Library.
//
//*****************************************************************************

#include <stdbool.h>
#include <stdint.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_nvic.h"
#include "driverlib/debug.h"
#include "driverlib/usb.h"
#include "driverlib/sysctl.h"
#include "driverlib/systick.h"
#include "driverlib/interrupt.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "usblib/usblib.h"
#include "usblib/usblibpriv.h"
#include "usblib/usbdfu.h"
#include "usblib/usb-ids.h"
#include "usblib/device/usbdevice.h"
#include "usblib/device/usbddfu-rt.h"
#include "usblib/usblibpriv.h"

//*****************************************************************************
//
//! \addtogroup dfu_device_class_api
//! @{
//
//*****************************************************************************

//*****************************************************************************
//
// DFU Device Descriptor.  This is a dummy structure since runtime DFU must be
// a part of a composite device and cannot be instantiated on its own.
//
//*****************************************************************************
const uint8_t g_pui8DFUDeviceDescriptor[] =
{
    18,                             // Size of this structure.
    USB_DTYPE_DEVICE,               // Type of this structure.
    USBShort(0x110),                // USB version 1.1 (if we say 2.0, hosts
                                    // assume
                                    // high-speed - see USB 2.0 spec 9.2.6.6)
    USB_CLASS_VEND_SPECIFIC,        // USB Device Class
    0,                              // USB Device Sub-class
    0,                              // USB Device protocol
    64,                             // Maximum packet size for default pipe.
    USBShort(0),                    // Vendor ID (VID).
    USBShort(0),                    // Product ID (PID).
    USBShort(0),                    // Device Release Number BCD.
    0,                              // Manufacturer string identifier.
    0,                              // Product string identifier.
    0,                              // Product serial number.
    1                               // Number of configurations.
};

//*****************************************************************************
//
// DFU device runtime configuration descriptor.  This is also a dummy structure
// since the primary device class configuration will be used when DFU is added
// to the composite device.
//
//*****************************************************************************
uint8_t g_pui8DFUConfigDescriptor[] =
{
    //
    // Configuration descriptor header.
    //
    9,                              // Size of the configuration descriptor.
    USB_DTYPE_CONFIGURATION,        // Type of this descriptor.
    USBShort(27),                   // The total size of this full structure.
    1,                              // The number of interfaces in this
                                    // configuration.
    1,                              // The unique value for this configuration.
    0,                              // The string identifier that describes
                                    // this configuration.
    USB_CONF_ATTR_SELF_PWR,         // Bus Powered, Self Powered, remote wake
                                    // up.
    250,                            // The maximum power in 2mA increments.
};

//*****************************************************************************
//
// The DFU runtime interface descriptor.
//
//*****************************************************************************
uint8_t g_pui8DFUInterface[DFUINTERFACE_SIZE] =
{
    //
    // Interface descriptor for runtime DFU operation.
    //
    9,                              // Length of this descriptor.
    USB_DTYPE_INTERFACE,            // This is an interface descriptor.
    0,                              // Interface number .
    0,                              // Alternate setting number.
    0,                              // Number of endpoints (only endpoint 0
                                    // used)
    USB_CLASS_APP_SPECIFIC,         // Application specific interface class
    USB_DFU_SUBCLASS,               // Device Firmware Upgrade subclass
    USB_DFU_RUNTIME_PROTOCOL,       // DFU runtime protocol
    0,                              // No string descriptor for this interface.
};

//*****************************************************************************
//
// The DFU functional descriptor.
//
//*****************************************************************************
uint8_t g_pui8DFUFunctionalDesc[DFUFUNCTIONALDESC_SIZE] =
{
    //
    // Device Firmware Upgrade functional descriptor.
    //
    9,                              // Length of this descriptor.
    USB_DFU_FUNC_DESCRIPTOR_TYPE,   // DFU Functional descriptor type
    (DFU_ATTR_CAN_DOWNLOAD |        // DFU attributes.
     DFU_ATTR_CAN_UPLOAD |
     DFU_ATTR_WILL_DETACH |
     DFU_ATTR_MANIFEST_TOLERANT),
    USBShort(0xFFFF),               // Detach timeout (set to maximum).
    USBShort(DFU_TRANSFER_SIZE),    // Transfer size 1KB.
    USBShort(0x0110)                // DFU Version 1.1
};

//*****************************************************************************
//
// The DFU runtime configuration descriptor is defined as two sections.
// These sections are:
//
// 1.  The 9 byte configuration descriptor.
// 2.  The interface descriptor + DFU functional descriptor.
//
//*****************************************************************************
const tConfigSection g_sDFUConfigSection =
{
    sizeof(g_pui8DFUConfigDescriptor),
    g_pui8DFUConfigDescriptor
};

const tConfigSection g_sDFUInterfaceSection =
{
    sizeof(g_pui8DFUInterface),
    g_pui8DFUInterface
};

const tConfigSection g_sDFUFunctionalDescSection =
{
    sizeof(g_pui8DFUFunctionalDesc),
    g_pui8DFUFunctionalDesc
};

//*****************************************************************************
//
// This array lists all the sections that must be concatenated to make a
// single, complete DFU runtime configuration descriptor.
//
//*****************************************************************************
const tConfigSection *g_psDFUSections[] =
{
    &g_sDFUConfigSection,
    &g_sDFUInterfaceSection,
    &g_sDFUFunctionalDescSection
};

#define NUM_DFU_SECTIONS        (sizeof(g_psDFUSections) /                    \
                                 sizeof(g_psDFUSections[0]))

//*****************************************************************************
//
// The header for the single configuration we support.  This is the root of
// the data structure that defines all the bits and pieces that are pulled
// together to generate the configuration descriptor.
//
//*****************************************************************************
tConfigHeader g_sDFUConfigHeader =
{
    NUM_DFU_SECTIONS,
    g_psDFUSections
};

//*****************************************************************************
//
// Configuration Descriptor.
//
//*****************************************************************************
const tConfigHeader * const g_ppsDFUConfigDescriptors[] =
{
    &g_sDFUConfigHeader
};

//*****************************************************************************
//
// Forward references for device handler callbacks
//
//*****************************************************************************
static void HandleGetDescriptor(void *pvDFUInstance, tUSBRequest *psUSBRequest);
static void HandleRequest(void *pvDFUInstance, tUSBRequest *psUSBRequest);
static void HandleDevice(void *pvDFUInstance, uint32_t ui32Request,
                         void *pvRequestData);

//*****************************************************************************
//
// The device information structure for the USB DFU devices.
//
//*****************************************************************************
static const tCustomHandlers g_sDFUHandlers =
{
    //
    // GetDescriptor
    //
    HandleGetDescriptor,

    //
    // RequestHandler
    //
    HandleRequest,

    //
    // InterfaceChange
    //
    0,

    //
    // ConfigChange
    //
    0,

    //
    // DataReceived
    //
    0,

    //
    // DataSentCallback
    //
    0,

    //
    // ResetHandler
    //
    0,

    //
    // SuspendHandler
    //
    0,

    //
    //

    //
    // ResumeHandler
    //
    0,

    //
    // DisconnectHandler
    //
    0,

    //
    // EndpointHandler
    //
    0,

    //
    // Device handler.
    //
    HandleDevice,
};

//*****************************************************************************
//
// Device instance specific handler.  This callback received notifications of
// events related to handling interface, endpoint and string identifiers when
// a device is part of a composite device.  In this case, the only resource we
// need which may be renumbered is the DFU runtime interface.
//
//*****************************************************************************
static void
HandleDevice(void *pvDFUInstance, uint32_t ui32Request, void *pvRequestData)
{
    tDFUInstance *psInst;
    uint8_t *pui8Data;

    //
    // Get a pointer to the DFU device instance data pointer
    //
    psInst = &((tUSBDDFUDevice *)pvDFUInstance)->sPrivateData;

    //
    // Get a byte pointer to the data.
    //
    pui8Data = (uint8_t *)pvRequestData;

    //
    // Which request event have we been passed?
    //
    switch(ui32Request)
    {
        //
        // This was an interface change event.
        //
        case USB_EVENT_COMP_IFACE_CHANGE:
        {
            //
            // Save the change to the interface number.
            //
            psInst->ui8Interface = pui8Data[1];
            break;
        }

        //
        // We are not interested in any other event.
        //
        default:
        {
            break;
        }
    }
}

//*****************************************************************************
//
// This function is called by the USB device stack whenever a request for a
// non-standard descriptor is received.
//
// \param pvDFUInstance is the instance data for this request.
// \param psUSBRequest points to the request received.
//
// This call parses the provided request structure and determines which
// descriptor is being requested.  Assuming the descriptor can be found, it is
// scheduled for transmission via endpoint zero.  If the descriptor cannot be
// found, the endpoint is stalled to indicate an error to the host.
//
//*****************************************************************************
static void
HandleGetDescriptor(void *pvDFUInstance, tUSBRequest *psUSBRequest)
{
    uint32_t ui32Size;

    ASSERT(pvDFUInstance != 0);

    //
    // Which type of class descriptor are we being asked for?  We only support
    // 1 type - the DFU functional descriptor.
    //
    if(((psUSBRequest->wValue >> 8) == USB_DFU_FUNC_DESCRIPTOR_TYPE) &&
       ((psUSBRequest->wValue & 0xFF) == 0))
    {
        //
        // If there is more data to send than the host requested then just
        // send the requested amount of data.
        //
        if((uint16_t)g_pui8DFUFunctionalDesc[0] > psUSBRequest->wLength)
        {
            ui32Size = (uint32_t)psUSBRequest->wLength;
        }
        else
        {
            ui32Size = (uint32_t)g_pui8DFUFunctionalDesc[0];
        }

        //
        // Send the data via endpoint 0.
        //
        USBDCDSendDataEP0(0, g_pui8DFUFunctionalDesc, ui32Size);
    }
    else
    {
        //
        // This was an unknown or invalid request so stall.
        //
        USBDCDStallEP0(0);
    }
}

//*****************************************************************************
//
// This function is called by the USB device stack whenever a non-standard
// request is received.
//
// \param pvDFUInstance is the instance data for this HID device.
// \param psUSBRequest points to the request received.
//
// This call parses the provided request structure.  Assuming the request is
// understood, it is handled and any required response generated.  If the
// request cannot be handled by this device class, endpoint zero is stalled to
// indicate an error to the host.
//
//*****************************************************************************
static void
HandleRequest(void *pvDFUInstance, tUSBRequest *psUSBRequest)
{
    tDFUInstance *psInst;
    tUSBDDFUDevice *psDevice;

    ASSERT(pvDFUInstance != 0);

    //
    // Get a pointer to the DFU device structure
    //
    psDevice = pvDFUInstance;

    //
    // Get a pointer to the DFU device instance data pointer
    //
    psInst = &psDevice->sPrivateData;

    //
    // Make sure the request was for this interface.
    //
    if(psUSBRequest->wIndex != psInst->ui8Interface)
    {
        return;
    }

    //
    // Determine the type of request.
    //
    switch(psUSBRequest->bRequest)
    {
        //
        // We have been asked to detach.  In this case, we call back to the
        // application telling it to tidy up and re-enter the boot loader.  We
        // rely upon it doing this on our behalf since this must be done from a
        // non-interrupt context and this call is most likely in interrupt
        // context.
        //
        case USBD_DFU_REQUEST_DETACH:
        {
            //
            // Tell the application it's time to reenter the boot loader.
            //
            psDevice->pfnCallback(psDevice->pvCBData, USBD_DFU_EVENT_DETACH,
                                  0, (void *)0);
            break;
        }

        //
        // This request was not recognized so stall.
        //
        default:
        {
            USBDCDStallEP0(0);
            break;
        }
    }
}

//*****************************************************************************
//
//! Initializes DFU device operation for a given USB controller.
//!
//! \param ui32Index is the index of the USB controller which is to be
//! initialized for DFU runtime device operation.
//! \param psDFUDevice points to a structure containing parameters customizing
//! the operation of the DFU device.
//! \param psCompEntry is the composite device entry to initialize when
//! creating a composite device.
//!
//! The \e psCompEntry should point to the composite device entry to
//! initialize. This is part of the array that is passed to the
//! USBDCompositeInit() function.
//!
//! \return Returns zero on failure or a non-zero instance value that should be
//! used with the remaining USB DFU APIs.
//
//*****************************************************************************
void *
USBDDFUCompositeInit(uint32_t ui32Index, tUSBDDFUDevice *psDFUDevice,
                     tCompositeEntry *psCompEntry)
{
    tDFUInstance *psInst;

    //
    // Check parameter validity.
    //
    ASSERT(ui32Index == 0);
    ASSERT(psDFUDevice);
    ASSERT(psCompEntry != 0);

    //
    // Get a pointer to the DFU device instance data pointer
    //
    psInst = &psDFUDevice->sPrivateData;

    //
    // Initialize the composite entry that is used by the composite device
    // class.
    //
    if(psCompEntry != 0)
    {
        psCompEntry->psDevInfo = &psInst->sDevInfo;
        psCompEntry->pvInstance = (void *)psDFUDevice;
    }

    //
    // Initialize the device information structure.
    //
    psInst->sDevInfo.psCallbacks = &g_sDFUHandlers;
    psInst->sDevInfo.pui8DeviceDescriptor = g_pui8DFUDeviceDescriptor;
    psInst->sDevInfo.ppsConfigDescriptors = g_ppsDFUConfigDescriptors;
    psInst->sDevInfo.ppui8StringDescriptors = 0;
    psInst->sDevInfo.ui32NumStringDescriptors = 0;

    psInst->ui32USBBase = USB0_BASE;
    psInst->bConnected = false;
    psInst->ui8Interface = 0;

    //
    // Initialize the device info structure for the DFU device.
    //
    USBDCDDeviceInfoInit(0, &psInst->sDevInfo);

    //
    // Return the pointer to the instance indicating that everything went well.
    //
    return((void *)psDFUDevice);
}

//*****************************************************************************
//
//! Shuts down the DFU device.
//!
//! \param pvDFUInstance is the pointer to the device instance structure as
//! returned by USBDDFUCompositeInit().
//!
//! This function terminates DFU operation for the instance supplied and
//! removes the device from the USB bus.
//!
//! Following this call, the \e pvDFUInstance instance should not me used in
//! any other calls.
//!
//! \return None.
//
//*****************************************************************************
void
USBDDFUCompositeTerm(void *pvDFUInstance)
{
    tDFUInstance *psInst;

    ASSERT(pvDFUInstance);

    //
    // Get a pointer to our instance data.
    //
    psInst = &((tUSBDDFUDevice *)pvDFUInstance)->sPrivateData;

    //
    // Terminate the requested instance.
    //
    USBDCDTerm(0);

    psInst->ui32USBBase = 0;
}

//*****************************************************************************
//
//! Removes the current USB device from the bus and transfers control to the
//! DFU boot loader.
//!
//! This function should be called from the application's main loop (i.e. not
//! in interrupt context) following a callback to the USB DFU callback function
//! notifying the application of a DETACH request from the host.  The function
//! will prepare the system to switch to DFU mode and transfer control to the
//! boot loader in preparation for a firmware upgrade from the host.
//!
//! The application must ensure that it has completed all necessary shutdown
//! activities (saved any required data, etc.) before making this call since
//! the function will not return.
//!
//! \return This function does not return.
//
//*****************************************************************************
void
USBDDFUUpdateBegin(void)
{
    //
    // Terminate the USB device and take us off the bus.
    //
    USBDCDTerm(0);

    //
    // Disable all interrupts.
    //
    MAP_IntMasterDisable();

    //
    // We must make sure we turn off SysTick and its interrupt
    // before entering the boot loader!
    //
    MAP_SysTickIntDisable();
    MAP_SysTickDisable();

    //
    // Disable all processor interrupts.  Instead of disabling them
    // one at a time, a direct write to NVIC is done to disable all
    // peripheral interrupts.
    //
    HWREG(NVIC_DIS0) = 0xffffffff;
    HWREG(NVIC_DIS1) = 0xffffffff;

    //
    // Reset the USB peripheral
    //
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0);
    MAP_SysCtlPeripheralReset(SYSCTL_PERIPH_USB0);
    MAP_SysCtlPeripheralDisable(SYSCTL_PERIPH_USB0);

    //
    // Wait for about a second.
    //
    MAP_SysCtlDelay(MAP_SysCtlClockGet() / 3);

    //
    // Re-enable interrupts at the NVIC level.
    //
    MAP_IntMasterEnable();

    //
    // Return control to the boot loader.  This is a call to the SVC
    // handler in the boot loader.
    //
    (*((void (*)(void))(*(uint32_t *)0x2c)))();

    //
    // Should never get here, but just in case.
    //
    while(1)
    {
    }
}

//*****************************************************************************
//
// Close the Doxygen group.
//! @}
//
//*****************************************************************************
