DFU: Introduce board/app-specific override for booting in DFU mode

Using the USBDFU_OverrideEnterDFU() function, a board/application can
define extra conditions when the system should boot in DFU mode, even if
it was not explicitly switched to DFU mode from the application.

The app/dfu/main.c uses this mechanism to boot into DFU mode if the
stack + reset vector addresses are not plausible (i.e. some random junk
appears to be flashed in the application partition) or if the user
places a jumper accross the RxD+TxD lines of the debug UART.  The idea
is that the system can be recovered by placing this jumper and then
re-installing the application from DFU.
diff --git a/firmware/apps/dfu/main.c b/firmware/apps/dfu/main.c
index 07441e5..32c855c 100644
--- a/firmware/apps/dfu/main.c
+++ b/firmware/apps/dfu/main.c
@@ -103,6 +103,57 @@
 	return req_len;
 }
 
+static int uart_has_loopback_jumper(void)
+{
+	unsigned int i;
+	const Pin uart_loopback_pins[] = {
+		{PIO_PA9A_URXD0, PIOA, ID_PIOA, PIO_INPUT, PIO_DEFAULT},
+		{PIO_PA10A_UTXD0, PIOA, ID_PIOA, PIO_OUTPUT_0, PIO_DEFAULT}
+	};
+
+	/* Configure UART pins as I/O */
+	PIO_Configure(uart_loopback_pins, PIO_LISTSIZE(uart_loopback_pins));
+
+	for (i = 0; i < 10; i++) {
+		/* Set TxD high; abort if RxD doesn't go high either */
+		PIO_Set(&uart_loopback_pins[1]);
+		if (!PIO_Get(&uart_loopback_pins[0]))
+			return 0;
+		/* Set TxD low, abort if RxD doesn't go low either */
+		PIO_Clear(&uart_loopback_pins[1]);
+		if (PIO_Get(&uart_loopback_pins[0]))
+			return 0;
+	}
+	/* if we reached here, RxD always follows TxD and thus a
+	 * loopback jumper has been placed on RxD/TxD, and we will boot
+	 * into DFU unconditionally */
+	return 1;
+}
+
+/* using this function we can determine if we should enter DFU mode
+ * during boot, or if we should proceed towards the application/runtime */
+int USBDFU_OverrideEnterDFU(void)
+{
+	uint32_t *app_part = (uint32_t *)FLASH_ADDR(0);
+
+	/* If the loopback jumper is set, we enter DFU mode */
+	if (uart_has_loopback_jumper())
+		return 1;
+
+	/* if the first word of the application partition doesn't look
+	 * like a stack pointer (i.e. point to RAM), enter DFU mode */
+	if ((app_part[0] < IRAM_ADDR) ||
+	    ((uint8_t *)app_part[0] > IRAM_END))
+		return 1;
+
+	/* if the second word of the application partition doesn't look
+	 * like a function from flash (reset vector), enter DFU mode */
+	if (((uint32_t *)app_part[1] < app_part) ||
+	    ((uint8_t *)app_part[1] > IFLASH_END))
+		return 1;
+
+	return 0;
+}
 
 /* returns '1' in case we should break any endless loop */
 static void check_exec_dbg_cmd(void)
diff --git a/firmware/atmel_softpack_libraries/usb/device/dfu/dfu.h b/firmware/atmel_softpack_libraries/usb/device/dfu/dfu.h
index 4af3e3b..87ac060 100644
--- a/firmware/atmel_softpack_libraries/usb/device/dfu/dfu.h
+++ b/firmware/atmel_softpack_libraries/usb/device/dfu/dfu.h
@@ -110,6 +110,7 @@
 				uint8_t *data, unsigned int len);
 extern int USBDFU_handle_upload(uint8_t altif, unsigned int offset,
 				uint8_t *data, unsigned int req_len);
+extern int USBDFU_OverrideEnterDFU(void);
 
 /* function to be called at end of EP0 handler during runtime */
 void USBDFU_Runtime_RequestHandler(const USBGenericRequest *request);
diff --git a/firmware/atmel_softpack_libraries/usb/device/dfu/dfu_driver.c b/firmware/atmel_softpack_libraries/usb/device/dfu/dfu_driver.c
index f9e2d8e..10f015b 100644
--- a/firmware/atmel_softpack_libraries/usb/device/dfu/dfu_driver.c
+++ b/firmware/atmel_softpack_libraries/usb/device/dfu/dfu_driver.c
@@ -472,6 +472,14 @@
 	NVIC_SystemReset();
 }
 
+/* A board can provide a function overriding this, enabling a
+ * board-specific 'boot into DFU' override, like a specific GPIO that
+ * needs to be pulled a certain way. */
+WEAK int USBDFU_OverrideEnterDFU(void)
+{
+	return 0;
+}
+
 void USBDCallbacks_RequestReceived(const USBGenericRequest *request)
 {
 	USBDFU_DFU_RequestHandler(request);
diff --git a/firmware/libboard/common/source/board_cstartup_gnu.c b/firmware/libboard/common/source/board_cstartup_gnu.c
index 3d92d2f..ce60b09 100644
--- a/firmware/libboard/common/source/board_cstartup_gnu.c
+++ b/firmware/libboard/common/source/board_cstartup_gnu.c
@@ -157,7 +157,7 @@
     /* we are before the text segment has been relocated, so g_dfu is

      * not initialized yet */

     g_dfu = &_g_dfu;

-    if (g_dfu->magic != USB_DFU_MAGIC) {

+    if ((g_dfu->magic != USB_DFU_MAGIC) && !USBDFU_OverrideEnterDFU()) {

         BootIntoApp();

         /* Infinite loop */

         while ( 1 ) ;