Documentation: Update copyrights to 2019.
[pub/lufa.git] / Bootloaders / Printer / BootloaderPrinter.c
index 9a6a8c2..deb3b96 100644 (file)
@@ -1,13 +1,13 @@
 /*
              LUFA Library
-     Copyright (C) Dean Camera, 2013.
+     Copyright (C) Dean Camera, 2019.
 
   dean [at] fourwalledcubicle [dot] com
            www.lufa-lib.org
 */
 
 /*
-  Copyright 2013  Dean Camera (dean [at] fourwalledcubicle [dot] com)
+  Copyright 2019  Dean Camera (dean [at] fourwalledcubicle [dot] com)
 
   Permission to use, copy, modify, distribute, and sell this
   software and its documentation for any purpose is hereby granted
 
 #include "BootloaderPrinter.h"
 
+/** LUFA Printer Class driver interface configuration and state information. This structure is
+ *  passed to all Printer Class driver functions, so that multiple instances of the same class
+ *  within a device can be differentiated from one another.
+ */
+USB_ClassInfo_PRNT_Device_t TextOnly_Printer_Interface =
+       {
+               .Config =
+                       {
+                               .InterfaceNumber          = INTERFACE_ID_Printer,
+                               .DataINEndpoint           =
+                                       {
+                                               .Address          = PRINTER_IN_EPADDR,
+                                               .Size             = PRINTER_IO_EPSIZE,
+                                               .Banks            = 1,
+                                       },
+                               .DataOUTEndpoint =
+                                       {
+                                               .Address          = PRINTER_OUT_EPADDR,
+                                               .Size             = PRINTER_IO_EPSIZE,
+                                               .Banks            = 1,
+                                       },
+                               .IEEE1284String =
+                                       "MFG:Generic;"
+                                       "MDL:Generic_/_Text_Only;"
+                                       "CMD:1284.4;"
+                                       "CLS:PRINTER",
+                       },
+       };
+
 /** Intel HEX parser state machine state information, to track the contents of
  *  a HEX file streamed in as a sequence of arbitrary bytes.
  */
-struct
+static struct
 {
        /** Current HEX parser state machine state. */
        uint8_t  ParserState;
@@ -58,34 +87,102 @@ struct
        /** Checksum of the current record received so far. */
        uint8_t  Checksum;
        /** Starting address of the last addressed FLASH page. */
-       uint32_t PageStartAddress;
+       flashaddr_t PageStartAddress;
        /** Current 32-bit byte extended base address in FLASH being targeted. */
-       uint32_t CurrBaseAddress;
+       flashaddr_t CurrBaseAddress;
        /** Current 32-bit byte address in FLASH being targeted. */
-       uint32_t CurrAddress;
-} HEXParser =
-       {
-               .ParserState = HEX_PARSE_STATE_WAIT_LINE
-       };
+       flashaddr_t CurrAddress;
+} HEXParser;
 
 /** Indicates if there is data waiting to be written to a physical page of
  *  memory in FLASH.
  */
 static bool PageDirty = false;
 
-/**
- * Determines if a given input byte of data is an ASCII encoded HEX value.
- *
- * \note Input HEX bytes are expected to be in uppercase only.
- *
- * \param[in] Byte  ASCII byte of data to check
- *
- * \return Boolean \c true if the input data is ASCII encoded HEX, \c false otherwise.
+/** Flag to indicate if the bootloader should be running, or should exit and allow the application code to run
+ *  via a soft reset. When cleared, the bootloader will abort, the USB interface will shut down and the application
+ *  started via a forced watchdog reset.
+ */
+static bool RunBootloader = true;
+
+/** Magic lock for forced application start. If the HWBE fuse is programmed and BOOTRST is unprogrammed, the bootloader
+ *  will start if the /HWB line of the AVR is held low and the system is reset. However, if the /HWB line is still held
+ *  low when the application attempts to start via a watchdog reset, the bootloader will re-start. If set to the value
+ *  \ref MAGIC_BOOT_KEY the special init function \ref Application_Jump_Check() will force the application to start.
+ */
+uint16_t MagicBootKey ATTR_NO_INIT;
+
+
+/** Special startup routine to check if the bootloader was started via a watchdog reset, and if the magic application
+ *  start key has been loaded into \ref MagicBootKey. If the bootloader started via the watchdog and the key is valid,
+ *  this will force the user application to start via a software jump.
  */
-static bool IsHex(const char Byte)
+void Application_Jump_Check(void)
 {
-       return ((Byte >= 'A') && (Byte <= 'F')) ||
-              ((Byte >= '0') && (Byte <= '9'));
+       bool JumpToApplication = false;
+
+       #if (BOARD == BOARD_LEONARDO)
+               /* Enable pull-up on the IO13 pin so we can use it to select the mode */
+               PORTC |= (1 << 7);
+               Delay_MS(10);
+
+               /* If IO13 is not jumpered to ground, start the user application instead */
+               JumpToApplication = ((PINC & (1 << 7)) != 0);
+
+               /* Disable pull-up after the check has completed */
+               PORTC &= ~(1 << 7);
+       #elif ((BOARD == BOARD_XPLAIN) || (BOARD == BOARD_XPLAIN_REV1))
+               /* Disable JTAG debugging */
+               JTAG_DISABLE();
+
+               /* Enable pull-up on the JTAG TCK pin so we can use it to select the mode */
+               PORTF |= (1 << 4);
+               Delay_MS(10);
+
+               /* If the TCK pin is not jumpered to ground, start the user application instead */
+               JumpToApplication = ((PINF & (1 << 4)) != 0);
+
+               /* Re-enable JTAG debugging */
+               JTAG_ENABLE();
+       #else
+               /* Check if the device's BOOTRST fuse is set */
+               if (!(BootloaderAPI_ReadFuse(GET_HIGH_FUSE_BITS) & ~FUSE_BOOTRST))
+               {
+                       /* If the reset source was not an external reset or the key is correct, clear it and jump to the application */
+                       if (!(MCUSR & (1 << EXTRF)) || (MagicBootKey == MAGIC_BOOT_KEY))
+                         JumpToApplication = true;
+
+                       /* Clear reset source */
+                       MCUSR &= ~(1 << EXTRF);
+               }
+               else
+               {
+                       /* If the reset source was the bootloader and the key is correct, clear it and jump to the application;
+                        * this can happen in the HWBE fuse is set, and the HBE pin is low during the watchdog reset */
+                       if ((MCUSR & (1 << WDRF)) && (MagicBootKey == MAGIC_BOOT_KEY))
+                               JumpToApplication = true;
+
+                       /* Clear reset source */
+                       MCUSR &= ~(1 << WDRF);
+               }
+       #endif
+
+       /* Don't run the user application if the reset vector is blank (no app loaded) */
+       bool ApplicationValid = (pgm_read_word_near(0) != 0xFFFF);
+
+       /* If a request has been made to jump to the user application, honor it */
+       if (JumpToApplication && ApplicationValid)
+       {
+               /* Turn off the watchdog */
+               MCUSR &= ~(1 << WDRF);
+               wdt_disable();
+
+               /* Clear the boot key and jump to the user application */
+               MagicBootKey = 0;
+
+               // cppcheck-suppress constStatement
+               ((void (*)(void))0x0000)();
+       }
 }
 
 /**
@@ -95,16 +192,41 @@ static bool IsHex(const char Byte)
  *
  * \param[in] Byte  ASCII byte of data to convert
  *
- * \return Integer converted value of the input ASCII encoded HEX byte of data.
+ * \return Integer converted value of the input ASCII encoded HEX byte of data, or -1 if the
+ *         input is not valid ASCII encoded HEX.
  */
-static uint8_t HexToDecimal(const char Byte)
+static int8_t HexToDecimal(const char Byte)
 {
        if ((Byte >= 'A') && (Byte <= 'F'))
          return (10 + (Byte - 'A'));
        else if ((Byte >= '0') && (Byte <= '9'))
          return (Byte - '0');
 
-       return 0;
+       return -1;
+}
+
+/**
+ * Flushes a partially written page of data to physical FLASH, if a page
+ * boundary has been crossed.
+ *
+ * \note If a page flush occurs the global HEX parser state is updated.
+ */
+static void FlushPageIfRequired(void)
+{
+       /* Abort if no data has been buffered for writing to the current page */
+       if (!PageDirty)
+         return;
+
+       /* Flush the FLASH page to physical memory if we are crossing a page boundary */
+       uint32_t NewPageStartAddress = (HEXParser.CurrAddress & ~(SPM_PAGESIZE - 1));
+       if (HEXParser.PageStartAddress != NewPageStartAddress)
+       {
+               BootloaderAPI_WritePage(HEXParser.PageStartAddress);
+
+               HEXParser.PageStartAddress = NewPageStartAddress;
+
+               PageDirty = false;
+       }
 }
 
 /**
@@ -120,7 +242,6 @@ static void ParseIntelHEXByte(const char ReadCharacter)
        {
                HEXParser.Checksum     = 0;
                HEXParser.CurrAddress  = HEXParser.CurrBaseAddress;
-               HEXParser.ParserState  = HEX_PARSE_STATE_WAIT_LINE;
                HEXParser.ReadMSB      = false;
 
                /* ASCII ':' indicates the start of a new HEX record */
@@ -131,11 +252,12 @@ static void ParseIntelHEXByte(const char ReadCharacter)
        }
 
        /* Only allow ASCII HEX encoded digits, ignore all other characters */
-       if (!IsHex(ReadCharacter))
+       int8_t ReadCharacterDec = HexToDecimal(ReadCharacter);
+       if (ReadCharacterDec < 0)
          return;
 
        /* Read and convert the next nibble of data from the current character */
-       HEXParser.Data    = (HEXParser.Data << 4) | HexToDecimal(ReadCharacter);
+       HEXParser.Data    = (HEXParser.Data << 4) | ReadCharacterDec;
        HEXParser.ReadMSB = !HEXParser.ReadMSB;
 
        /* Only process further when a full byte (two nibbles) have been read */
@@ -195,32 +317,20 @@ static void ParseIntelHEXByte(const char ReadCharacter)
                        switch (HEXParser.RecordType)
                        {
                                case HEX_RECORD_TYPE_Data:
-                                       /* If we are writing to a new page, we need to erase it
-                                        * first
-                                        */
+                                       /* If we are writing to a new page, we need to erase it first */
                                        if (!(PageDirty))
                                        {
-                                               boot_page_erase(HEXParser.PageStartAddress);
-                                               boot_spm_busy_wait();
+                                               BootloaderAPI_ErasePage(HEXParser.PageStartAddress);
 
                                                PageDirty = true;
                                        }
 
                                        /* Fill the FLASH memory buffer with the new word of data */
-                                       boot_page_fill(HEXParser.CurrAddress, NewDataWord);
+                                       BootloaderAPI_FillWord(HEXParser.CurrAddress, NewDataWord);
                                        HEXParser.CurrAddress += 2;
 
                                        /* Flush the FLASH page to physical memory if we are crossing a page boundary */
-                                       uint32_t NewPageStartAddress = (HEXParser.CurrAddress & ~(SPM_PAGESIZE - 1));
-                                       if (PageDirty && (HEXParser.PageStartAddress != NewPageStartAddress))
-                                       {
-                                               boot_page_write(HEXParser.PageStartAddress);
-                                               boot_spm_busy_wait();
-
-                                               HEXParser.PageStartAddress = NewPageStartAddress;
-
-                                               PageDirty = false;
-                                       }
+                                       FlushPageIfRequired();
                                        break;
 
                                case HEX_RECORD_TYPE_ExtendedSegmentAddress:
@@ -244,16 +354,11 @@ static void ParseIntelHEXByte(const char ReadCharacter)
                          break;
 
                        /* Flush the FLASH page to physical memory if we are crossing a page boundary */
-                       uint32_t NewPageStartAddress = (HEXParser.CurrAddress & ~(SPM_PAGESIZE - 1));
-                       if (PageDirty && (HEXParser.PageStartAddress != NewPageStartAddress))
-                       {
-                               boot_page_write(HEXParser.PageStartAddress);
-                               boot_spm_busy_wait();
-
-                               HEXParser.PageStartAddress = NewPageStartAddress;
+                       FlushPageIfRequired();
 
-                               PageDirty = false;
-                       }
+                       /* If end of the HEX file reached, the bootloader should exit at next opportunity */
+                       if (HEXParser.RecordType == HEX_RECORD_TYPE_EndOfFile)
+                         RunBootloader = false;
 
                        break;
 
@@ -273,29 +378,42 @@ int main(void)
        LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);
        GlobalInterruptEnable();
 
-       for (;;)
+       while (RunBootloader)
        {
-               USB_USBTask();
-
-               Endpoint_SelectEndpoint(PRINTER_OUT_EPADDR);
+               uint8_t BytesReceived = PRNT_Device_BytesReceived(&TextOnly_Printer_Interface);
 
-               /* Check if we have received new printer data from the host */
-               if (Endpoint_IsOUTReceived()) {
-                       LEDs_ToggleLEDs(LEDMASK_USB_BUSY);
+               if (BytesReceived)
+               {
+                       LEDs_SetAllLEDs(LEDMASK_USB_BUSY);
 
-                       /* Read all bytes of data from the host and parse them */
-                       while (Endpoint_IsReadWriteAllowed())
+                       while (BytesReceived--)
                        {
+                               int16_t ReceivedByte = PRNT_Device_ReceiveByte(&TextOnly_Printer_Interface);
+
                                /* Feed the next byte of data to the HEX parser */
-                               ParseIntelHEXByte(Endpoint_Read_8());
+                               ParseIntelHEXByte(ReceivedByte);
                        }
 
-                       /* Send an ACK to the host, ready for the next data packet */
-                       Endpoint_ClearOUT();
-
                        LEDs_SetAllLEDs(LEDMASK_USB_READY);
                }
+
+               PRNT_Device_USBTask(&TextOnly_Printer_Interface);
+               USB_USBTask();
        }
+
+       /* Wait a short time to end all USB transactions and then disconnect */
+       _delay_us(1000);
+
+       /* Disconnect from the host - USB interface will be reset later along with the AVR */
+       USB_Detach();
+
+       /* Unlock the forced application start mode of the bootloader if it is restarted */
+       MagicBootKey = MAGIC_BOOT_KEY;
+
+       /* Enable the watchdog and force a timeout to reset the AVR */
+       wdt_enable(WDTO_250MS);
+
+       for (;;);
 }
 
 /** Configures the board hardware and chip peripherals for the demo's functionality. */
@@ -351,8 +469,10 @@ void EVENT_USB_Device_ConfigurationChanged(void)
        bool ConfigSuccess = true;
 
        /* Setup Printer Data Endpoints */
-       ConfigSuccess &= Endpoint_ConfigureEndpoint(PRINTER_IN_EPADDR,  EP_TYPE_BULK, PRINTER_IO_EPSIZE, 1);
-       ConfigSuccess &= Endpoint_ConfigureEndpoint(PRINTER_OUT_EPADDR, EP_TYPE_BULK, PRINTER_IO_EPSIZE, 1);
+       ConfigSuccess &= PRNT_Device_ConfigureEndpoints(&TextOnly_Printer_Interface);
+
+       /* Reset the HEX parser upon successful connection to a host */
+       HEXParser.ParserState = HEX_PARSE_STATE_WAIT_LINE;
 
        /* Indicate endpoint configuration success or failure */
        LEDs_SetAllLEDs(ConfigSuccess ? LEDMASK_USB_READY : LEDMASK_USB_ERROR);
@@ -364,44 +484,5 @@ void EVENT_USB_Device_ConfigurationChanged(void)
  */
 void EVENT_USB_Device_ControlRequest(void)
 {
-       /* Process Printer specific control requests */
-       switch (USB_ControlRequest.bRequest)
-       {
-               case PRNT_REQ_GetDeviceID:
-                       if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
-                       {
-                               /* Generic printer IEEE 1284 identification string, will bind to an in-built driver on
-                                * Windows systems, and will fall-back to a text-only printer driver on *nix.
-                                */
-                               const char PrinterIDString[] =
-                                       "MFG:Generic;"
-                                       "MDL:Generic_/_Text_Only;"
-                                       "CMD:1284.4;"
-                                       "CLS:PRINTER";
-
-                               Endpoint_ClearSETUP();
-                               Endpoint_Write_16_BE(sizeof(PrinterIDString));
-                               Endpoint_Write_Control_Stream_LE(PrinterIDString, strlen(PrinterIDString));
-                               Endpoint_ClearStatusStage();
-                       }
-
-                       break;
-               case PRNT_REQ_GetPortStatus:
-                       if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
-                       {
-                               Endpoint_ClearSETUP();
-                               Endpoint_Write_8(PRNT_PORTSTATUS_NOTERROR | PRNT_PORTSTATUS_SELECT);
-                               Endpoint_ClearStatusStage();
-                       }
-
-                       break;
-               case PRNT_REQ_SoftReset:
-                       if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
-                       {
-                               Endpoint_ClearSETUP();
-                               Endpoint_ClearStatusStage();
-                       }
-
-                       break;
-       }
+       PRNT_Device_ProcessControlRequest(&TextOnly_Printer_Interface);
 }