feat(plat/st): add STM32CubeProgrammer support on USB

Add a file to support over USB the STMicroelectronics tool
STM32CubeProgrammer in BL2 for STM32MP15x platform.

This tools is based on DFU stack.

Change-Id: I48a8f772cb0e9b8be24c06847f724f0470c0f917
Signed-off-by: Patrick Delaunay <patrick.delaunay@foss.st.com>
diff --git a/plat/st/common/include/stm32cubeprogrammer.h b/plat/st/common/include/stm32cubeprogrammer.h
new file mode 100644
index 0000000..503d919
--- /dev/null
+++ b/plat/st/common/include/stm32cubeprogrammer.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2021, STMicroelectronics - All Rights Reserved
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef STM32CUBEPROGRAMMER_H
+#define STM32CUBEPROGRAMMER_H
+
+#include <stdint.h>
+
+#include <usb_dfu.h>
+
+/* Phase definition */
+#define PHASE_FLASHLAYOUT	0U
+#define PHASE_SSBL		3U
+#define PHASE_CMD		0xF1U
+#define PHASE_RESET		0xFFU
+
+/* Functions provided by plat */
+uint8_t usb_dfu_get_phase(uint8_t alt);
+
+int stm32cubeprog_usb_load(struct usb_handle *usb_core_handle,
+			   uintptr_t ssbl_base,
+			   size_t ssbl_len);
+
+#endif /* STM32CUBEPROGRAMMER_H */
diff --git a/plat/st/common/stm32cubeprogrammer_usb.c b/plat/st/common/stm32cubeprogrammer_usb.c
new file mode 100644
index 0000000..4cd210e
--- /dev/null
+++ b/plat/st/common/stm32cubeprogrammer_usb.c
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2021, STMicroelectronics - All Rights Reserved
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <errno.h>
+
+#include <tools_share/firmware_image_package.h>
+
+#include <stm32cubeprogrammer.h>
+#include <usb_dfu.h>
+
+/* Undefined download address */
+#define UNDEFINED_DOWN_ADDR	0xFFFFFFFF
+
+struct dfu_state {
+	uint8_t phase;
+	uintptr_t base;
+	size_t len;
+	uintptr_t address;
+	/* working buffer */
+	uint8_t buffer[UCHAR_MAX];
+};
+
+static struct dfu_state dfu_state;
+
+/* minimal size of Get Pḧase = offset for additionnl information */
+#define	GET_PHASE_LEN	9
+
+#define DFU_ERROR(...) \
+	{ \
+		ERROR(__VA_ARGS__); \
+		if (dfu->phase != PHASE_RESET) { \
+			snprintf((char *)&dfu->buffer[GET_PHASE_LEN], \
+				 sizeof(dfu->buffer) - GET_PHASE_LEN, \
+				 __VA_ARGS__); \
+			dfu->phase = PHASE_RESET; \
+			dfu->address = UNDEFINED_DOWN_ADDR; \
+			dfu->len = 0; \
+		} \
+	}
+
+static bool is_valid_header(fip_toc_header_t *header)
+{
+	if ((header->name == TOC_HEADER_NAME) && (header->serial_number != 0U)) {
+		return true;
+	}
+
+	return false;
+}
+
+static int dfu_callback_upload(uint8_t alt, uintptr_t *buffer, uint32_t *len,
+			       void *user_data)
+{
+	int result = 0;
+	uint32_t length = 0;
+	struct dfu_state *dfu = (struct dfu_state *)user_data;
+
+	switch (usb_dfu_get_phase(alt)) {
+	case PHASE_CMD:
+		/* Get Pḧase */
+		dfu->buffer[0] = dfu->phase;
+		dfu->buffer[1] = (uint8_t)(dfu->address);
+		dfu->buffer[2] = (uint8_t)(dfu->address >> 8);
+		dfu->buffer[3] = (uint8_t)(dfu->address >> 16);
+		dfu->buffer[4] = (uint8_t)(dfu->address >> 24);
+		dfu->buffer[5] = 0x00;
+		dfu->buffer[6] = 0x00;
+		dfu->buffer[7] = 0x00;
+		dfu->buffer[8] = 0x00;
+		length = GET_PHASE_LEN;
+		if (dfu->phase == PHASE_FLASHLAYOUT &&
+		    dfu->address == UNDEFINED_DOWN_ADDR) {
+			INFO("Send detach request\n");
+			dfu->buffer[length++] = 0x01;
+		}
+		if (dfu->phase == PHASE_RESET) {
+			/* error information is added by DFU_ERROR macro */
+			length += strnlen((char *)&dfu->buffer[GET_PHASE_LEN],
+					  sizeof(dfu->buffer) - GET_PHASE_LEN)
+				  - 1;
+		}
+		break;
+
+	default:
+		DFU_ERROR("phase ID :%i, alternate %i for phase %i\n",
+			  dfu->phase, alt, usb_dfu_get_phase(alt));
+		result = -EIO;
+		break;
+	}
+
+	if (result == 0) {
+		*len = length;
+		*buffer = (uintptr_t)dfu->buffer;
+	}
+
+	return result;
+}
+
+static int dfu_callback_download(uint8_t alt, uintptr_t *buffer, uint32_t *len,
+				 void *user_data)
+{
+	struct dfu_state *dfu = (struct dfu_state *)user_data;
+
+	if ((dfu->phase != usb_dfu_get_phase(alt)) ||
+	    (dfu->address == UNDEFINED_DOWN_ADDR)) {
+		DFU_ERROR("phase ID :%i, alternate %i, address %x\n",
+			  dfu->phase, alt, (uint32_t)dfu->address);
+		return -EIO;
+	}
+
+	VERBOSE("Download %d %lx %x\n", alt, dfu->address, *len);
+	*buffer = dfu->address;
+	dfu->address += *len;
+
+	if (dfu->address - dfu->base > dfu->len) {
+		return  -EIO;
+	}
+
+	return 0;
+}
+
+static int dfu_callback_manifestation(uint8_t alt, void *user_data)
+{
+	struct dfu_state *dfu = (struct dfu_state *)user_data;
+
+	if (dfu->phase != usb_dfu_get_phase(alt)) {
+		ERROR("Manifestation phase ID :%i, alternate %i, address %lx\n",
+		      dfu->phase, alt, dfu->address);
+		return -EIO;
+	}
+
+	INFO("phase ID :%i, Manifestation %d at %lx\n",
+	     dfu->phase, alt, dfu->address);
+
+	switch (dfu->phase) {
+	case PHASE_SSBL:
+		if (!is_valid_header((fip_toc_header_t *)dfu->base)) {
+			DFU_ERROR("FIP Header check failed for phase %d\n", alt);
+			return -EIO;
+		}
+		VERBOSE("FIP header looks OK.\n");
+
+		/* Configure End with request detach */
+		dfu->phase = PHASE_FLASHLAYOUT;
+		dfu->address = UNDEFINED_DOWN_ADDR;
+		dfu->len = 0;
+		break;
+	default:
+		DFU_ERROR("Unknown phase\n");
+	}
+
+	return 0;
+}
+
+/* Open a connection to the USB device */
+static const struct usb_dfu_media usb_dfu_fops = {
+	.upload = dfu_callback_upload,
+	.download = dfu_callback_download,
+	.manifestation = dfu_callback_manifestation,
+};
+
+int stm32cubeprog_usb_load(struct usb_handle *usb_core_handle,
+			   uintptr_t base,
+			   size_t len)
+{
+	int ret;
+
+	usb_core_handle->user_data = (void *)&dfu_state;
+
+	INFO("DFU USB START...\n");
+	ret = usb_core_start(usb_core_handle);
+	if (ret != USBD_OK) {
+		return -EIO;
+	}
+
+	dfu_state.phase = PHASE_SSBL;
+	dfu_state.address = base;
+	dfu_state.base = base;
+	dfu_state.len = len;
+
+	ret = usb_dfu_loop(usb_core_handle, &usb_dfu_fops);
+	if (ret != USBD_OK) {
+		return -EIO;
+	}
+
+	INFO("DFU USB STOP...\n");
+	ret = usb_core_stop(usb_core_handle);
+	if (ret != USBD_OK) {
+		return -EIO;
+	}
+
+	return 0;
+}