From 9b0fd54f4b48fb42fe5e4afc23280ea6d952055e Mon Sep 17 00:00:00 2001
From: swym <0xfd000000@gmail.com>
Date: Sat, 24 Aug 2019 16:17:39 +0000
Subject: [PATCH] feat(epicardium): Add ESB module

usb/epc_usb
====
Contains device-independent USB implementation, services MAXUSB stack
and is given the actual USB descriptors & callbacks on initialization.
Handles USB events and configures/deconfigures the device accordingly
during the USB setup procedure.

usb/mass_storage & cdcacm
====
Contain device-specific implementations, service MAXUSB's device class
APIs for cdcacm and mass storage.
Here, mass storage is independent of the underlying storage device.

modules/usb
====
Public apic_usb API, configuration and storage device management.
Defines the actual USB device descriptors and services the upc_usb API.

If we want to add SD card functionality, this would be the place to add
it.
---
 epicardium/FreeRTOSConfig.h     |   2 +-
 epicardium/cdcacm.c             | 382 --------------------------
 epicardium/cdcacm.h             |  10 -
 epicardium/descriptors.h        | 227 ----------------
 epicardium/epicardium.h         |  19 ++
 epicardium/fs/filesystem_fat.c  |  56 ++--
 epicardium/meson.build          |   4 +-
 epicardium/modules/filesystem.h |  10 +
 epicardium/modules/hardware.c   |   6 +-
 epicardium/modules/meson.build  |   1 +
 epicardium/modules/serial.c     |   2 +-
 epicardium/modules/usb.c        | 322 ++++++++++++++++++++++
 epicardium/usb/cdcacm.c         | 126 +++++++++
 epicardium/usb/cdcacm.h         |  15 ++
 epicardium/usb/descriptors.h    |  83 ++++++
 epicardium/usb/epc_usb.c        | 460 ++++++++++++++++++++++++++++++++
 epicardium/usb/epc_usb.h        |  32 +++
 epicardium/usb/mass_storage.c   |  55 ++++
 epicardium/usb/mass_storage.h   |  10 +
 19 files changed, 1179 insertions(+), 643 deletions(-)
 delete mode 100644 epicardium/cdcacm.c
 delete mode 100644 epicardium/cdcacm.h
 delete mode 100644 epicardium/descriptors.h
 create mode 100644 epicardium/modules/usb.c
 create mode 100644 epicardium/usb/cdcacm.c
 create mode 100644 epicardium/usb/cdcacm.h
 create mode 100644 epicardium/usb/descriptors.h
 create mode 100644 epicardium/usb/epc_usb.c
 create mode 100644 epicardium/usb/epc_usb.h
 create mode 100644 epicardium/usb/mass_storage.c
 create mode 100644 epicardium/usb/mass_storage.h

diff --git a/epicardium/FreeRTOSConfig.h b/epicardium/FreeRTOSConfig.h
index b92b12b9..f1d3aa46 100644
--- a/epicardium/FreeRTOSConfig.h
+++ b/epicardium/FreeRTOSConfig.h
@@ -52,7 +52,7 @@
 #define INCLUDE_vTaskSuspend        1
 #define INCLUDE_vTaskDelay          1
 #define INCLUDE_uxTaskGetStackHighWaterMark 1
-
+#define INCLUDE_xTimerPendFunctionCall 1
 /* Allow static allocation of data structures */
 #define configSUPPORT_STATIC_ALLOCATION 1
 
diff --git a/epicardium/cdcacm.c b/epicardium/cdcacm.c
deleted file mode 100644
index 1cbbb154..00000000
--- a/epicardium/cdcacm.c
+++ /dev/null
@@ -1,382 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
- * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
- * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- *
- * Except as contained in this notice, the name of Maxim Integrated
- * Products, Inc. shall not be used except as stated in the Maxim Integrated
- * Products, Inc. Branding Policy.
- *
- * The mere transfer of this software does not imply any licenses
- * of trade secrets, proprietary technology, copyrights, patents,
- * trademarks, maskwork rights, or any other form of intellectual
- * property whatsoever. Maxim Integrated Products, Inc. retains all
- * ownership rights.
- *
- * $Id: main.c 32120 2017-11-28 23:51:11Z lorne.smith $
- *
- *******************************************************************************
- */
-
-/**
- * @file    main.c
- * @brief   USB CDC-ACM example
- * @details This project creates a virtual COM port, which loops back data sent to it.
- *          Load the project, connect a cable from the PC to the USB connector
- *          on the Evaluation Kit, and observe that the PC now recognizes a new COM port.
- *          A driver for the COM port, if needed, is located in the Driver/ subdirectory.
- *
- */
-
-#include <stdio.h>
-#include <stddef.h>
-#include "mxc_config.h"
-#include "mxc_sys.h"
-#include "mxc_delay.h"
-#include "board.h"
-#include "led.h"
-#include "usb.h"
-#include "usb_event.h"
-#include "enumerate.h"
-#include "cdc_acm.h"
-#include "descriptors.h"
-
-#include "modules/modules.h"
-#include "modules/log.h"
-#include <errno.h>
-
-/***** Definitions *****/
-#define EVENT_ENUM_COMP MAXUSB_NUM_EVENTS
-
-#define BUFFER_SIZE 64
-
-#define STRINGIFY(x) #x
-#define TOSTRING(x) STRINGIFY(x)
-
-/***** Global Data *****/
-//SWYM: rename to CDC_xy or put into struct CDC_state
-volatile int configured; //SWYM: actually unused...
-volatile int suspended;
-volatile unsigned int event_flags;
-int remote_wake_en;
-
-/***** Function Prototypes *****/
-static int setconfig_callback(usb_setup_pkt *sud, void *cbdata);
-static int setfeature_callback(usb_setup_pkt *sud, void *cbdata);
-static int clrfeature_callback(usb_setup_pkt *sud, void *cbdata);
-static int event_callback(maxusb_event_t evt, void *data);
-static void usb_app_sleep(void);
-static void usb_app_wakeup(void);
-static int usb_read_callback(void);
-//static void echo_usb(void);
-
-/***** File Scope Variables *****/
-
-/* This EP assignment must match the Configuration Descriptor */
-static const acm_cfg_t acm_cfg = {
-	1,                    /* EP OUT */
-	MXC_USBHS_MAX_PACKET, /* OUT max packet size */
-	2,                    /* EP IN */
-	MXC_USBHS_MAX_PACKET, /* IN max packet size */
-	3,                    /* EP Notify */
-	MXC_USBHS_MAX_PACKET, /* Notify max packet size */
-};
-
-static volatile int usb_read_complete;
-
-int usb_startup_cb()
-{
-	const sys_cfg_usbhs_t sys_usbhs_cfg = NULL;
-	return SYS_USBHS_Init(&sys_usbhs_cfg);
-}
-
-int usb_shutdown_cb()
-{
-	return SYS_USBHS_Shutdown();
-}
-
-/* User-supplied function to delay usec micro-seconds */
-void delay_us(unsigned int usec)
-{
-	/* mxc_delay() takes unsigned long, so can't use it directly */
-	mxc_delay(usec);
-}
-
-/******************************************************************************/
-int cdcacm_init(void)
-{
-	maxusb_cfg_options_t usb_opts;
-
-	/* Initialize state */
-	configured     = 0;
-	suspended      = 0;
-	event_flags    = 0;
-	remote_wake_en = 0;
-
-	/* Start out in full speed */
-	usb_opts.enable_hs = 0;
-	usb_opts.delay_us =
-		delay_us; /* Function which will be used for delays */
-	usb_opts.init_callback     = usb_startup_cb;
-	usb_opts.shutdown_callback = usb_shutdown_cb;
-
-	/* Initialize the usb module */
-	if (usb_init(&usb_opts) != 0) {
-		LOG_ERR("cdcacm", "usb_init() failed");
-		return -EIO;
-	}
-
-	/* Initialize the enumeration module */
-	if (enum_init() != 0) {
-		LOG_ERR("cdcacm", "enum_init() failed");
-		return -EIO;
-	}
-
-	/* Register enumeration data */
-	enum_register_descriptor(
-		ENUM_DESC_DEVICE, (uint8_t *)&device_descriptor, 0
-	);
-	enum_register_descriptor(
-		ENUM_DESC_CONFIG, (uint8_t *)&config_descriptor, 0
-	);
-	enum_register_descriptor(ENUM_DESC_STRING, lang_id_desc, 0);
-	enum_register_descriptor(ENUM_DESC_STRING, mfg_id_desc, 1);
-	enum_register_descriptor(ENUM_DESC_STRING, prod_id_desc, 2);
-
-	/* Handle configuration */
-	enum_register_callback(ENUM_SETCONFIG, setconfig_callback, NULL);
-
-	/* Handle feature set/clear */
-	enum_register_callback(ENUM_SETFEATURE, setfeature_callback, NULL);
-	enum_register_callback(ENUM_CLRFEATURE, clrfeature_callback, NULL);
-
-	/* Initialize the class driver */
-	if (acm_init(&config_descriptor.comm_interface_descriptor) != 0) {
-		LOG_ERR("cdcacm", "acm_init() failed");
-		return -EIO;
-	}
-
-	/* Register callbacks */
-	usb_event_enable(MAXUSB_EVENT_NOVBUS, event_callback, NULL);
-	usb_event_enable(MAXUSB_EVENT_VBUS, event_callback, NULL);
-	acm_register_callback(ACM_CB_READ_READY, usb_read_callback);
-	usb_read_complete = 0;
-
-	/* Start with USB in low power mode */
-	usb_app_sleep();
-	/* TODO: Fix priority */
-	NVIC_SetPriority(USB_IRQn, 6);
-	NVIC_EnableIRQ(USB_IRQn);
-
-	return 0;
-}
-
-int cdcacm_num_read_avail(void)
-{
-	return acm_canread();
-}
-
-uint8_t cdcacm_read(void)
-{
-	while (acm_canread() <= 0) {
-	}
-
-	uint8_t buf;
-	acm_read(&buf, 1);
-	return buf;
-}
-
-void cdcacm_write(uint8_t *data, int len)
-{
-	static int lockup_disable = 0;
-	if (acm_present() && !lockup_disable) {
-		int ret = acm_write(data, len);
-		if (ret < 0) {
-			lockup_disable = 1;
-			LOG_ERR("cdcacm", "fifo lockup detected");
-		} else if (ret != len) {
-			LOG_WARN(
-				"cdcacm", "write length mismatch, got %d", ret
-			);
-		}
-	}
-}
-
-/******************************************************************************/
-#if 0
-static void echo_usb(void)
-{
-  int chars;
-  uint8_t buffer[BUFFER_SIZE];
-  
-  if ((chars = acm_canread()) > 0) {
-
-    if (chars > BUFFER_SIZE) {
-      chars = BUFFER_SIZE;
-    }
-
-    // Read the data from USB
-    if (acm_read(buffer, chars) != chars) {
-      printf("acm_read() failed\n");
-      return;
-    }
-
-    // Echo it back
-    if (acm_present()) {
-      if (acm_write(buffer, chars) != chars) {
-        printf("acm_write() failed\n");
-      }
-    }
-  }
-}
-#endif
-/******************************************************************************/
-static int setconfig_callback(usb_setup_pkt *sud, void *cbdata)
-{
-	/* Confirm the configuration value */
-	if (sud->wValue ==
-	    config_descriptor.config_descriptor.bConfigurationValue) {
-		configured = 1;
-		MXC_SETBIT(&event_flags, EVENT_ENUM_COMP);
-		return acm_configure(&acm_cfg); /* Configure the device class */
-	} else if (sud->wValue == 0) {
-		configured = 0;
-		return acm_deconfigure();
-	}
-
-	return -1;
-}
-
-/******************************************************************************/
-static int setfeature_callback(usb_setup_pkt *sud, void *cbdata)
-{
-	if (sud->wValue == FEAT_REMOTE_WAKE) {
-		remote_wake_en = 1;
-	} else {
-		// Unknown callback
-		return -1;
-	}
-
-	return 0;
-}
-
-/******************************************************************************/
-static int clrfeature_callback(usb_setup_pkt *sud, void *cbdata)
-{
-	if (sud->wValue == FEAT_REMOTE_WAKE) {
-		remote_wake_en = 0;
-	} else {
-		// Unknown callback
-		return -1;
-	}
-
-	return 0;
-}
-
-/******************************************************************************/
-static void usb_app_sleep(void)
-{
-	/* TODO: Place low-power code here */
-	suspended = 1;
-}
-
-/******************************************************************************/
-static void usb_app_wakeup(void)
-{
-	/* TODO: Place low-power code here */
-	suspended = 0;
-}
-
-/******************************************************************************/
-static int event_callback(maxusb_event_t evt, void *data)
-{
-	/* Set event flag */
-	MXC_SETBIT(&event_flags, evt);
-
-	switch (evt) {
-	case MAXUSB_EVENT_NOVBUS:
-		usb_event_disable(MAXUSB_EVENT_BRST);
-		usb_event_disable(MAXUSB_EVENT_SUSP);
-		usb_event_disable(MAXUSB_EVENT_DPACT);
-		usb_disconnect();
-		configured = 0;
-		enum_clearconfig();
-		acm_deconfigure();
-		usb_app_sleep();
-		break;
-	case MAXUSB_EVENT_VBUS:
-		usb_event_clear(MAXUSB_EVENT_BRST);
-		usb_event_enable(MAXUSB_EVENT_BRST, event_callback, NULL);
-		usb_event_clear(MAXUSB_EVENT_SUSP);
-		usb_event_enable(MAXUSB_EVENT_SUSP, event_callback, NULL);
-		usb_connect();
-		usb_app_sleep();
-		break;
-	case MAXUSB_EVENT_BRST:
-		usb_app_wakeup();
-		enum_clearconfig();
-		acm_deconfigure();
-		configured = 0;
-		suspended  = 0;
-		break;
-	case MAXUSB_EVENT_SUSP:
-		usb_app_sleep();
-		break;
-	case MAXUSB_EVENT_DPACT:
-		usb_app_wakeup();
-		break;
-	default:
-		break;
-	}
-
-	return 0;
-}
-
-/******************************************************************************/
-static int usb_read_callback(void)
-{
-	usb_read_complete = 1;
-	return 0;
-}
-
-/******************************************************************************/
-#include "FreeRTOS.h"
-#include "task.h"
-
-void USB_IRQHandler(void)
-{
-	usb_event_handler();
-
-	if (serial_task_id != NULL) {
-		BaseType_t xHigherPriorityTaskWoken = pdFALSE;
-		vTaskNotifyGiveFromISR(
-			serial_task_id, &xHigherPriorityTaskWoken
-		);
-		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
-	}
-}
-
-/******************************************************************************/
-/* TODO: We probably need to fix something related to this */
-#if 0
-void SysTick_Handler(void)
-{
-    mxc_delay_handler();
-}
-#endif /* 0 */
diff --git a/epicardium/cdcacm.h b/epicardium/cdcacm.h
deleted file mode 100644
index 1c25d750..00000000
--- a/epicardium/cdcacm.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef CDCACM_H
-#define CDCACM_H
-#include <stdint.h>
-
-int cdcacm_init(void);
-int cdcacm_num_read_avail(void);
-uint8_t cdcacm_read(void);
-void cdcacm_write(uint8_t *data, int len);
-
-#endif
diff --git a/epicardium/descriptors.h b/epicardium/descriptors.h
deleted file mode 100644
index c081c401..00000000
--- a/epicardium/descriptors.h
+++ /dev/null
@@ -1,227 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
- * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
- * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- *
- * Except as contained in this notice, the name of Maxim Integrated
- * Products, Inc. shall not be used except as stated in the Maxim Integrated
- * Products, Inc. Branding Policy.
- *
- * The mere transfer of this software does not imply any licenses
- * of trade secrets, proprietary technology, copyrights, patents,
- * trademarks, maskwork rights, or any other form of intellectual
- * property whatsoever. Maxim Integrated Products, Inc. retains all
- * ownership rights.
- *
- * Description: Communications Device Class ACM (Serial Port) over USB
- * $Id: descriptors.h 36682 2018-08-06 21:14:03Z michael.bayern $
- *
- *******************************************************************************
- */
-
-#ifndef _DESCRIPTORS_H_
-#define _DESCRIPTORS_H_
-
-#include <stdint.h>
-#include "usb.h"
-#include "hid_kbd.h"
-
-usb_device_descriptor_t __attribute__((aligned(4))) device_descriptor = {
-    0x12,         /* bLength = 18                     */
-    0x01,         /* bDescriptorType = Device         */
-    0x0110,       /* bcdUSB USB spec rev (BCD)        */
-    0x02,         /* bDeviceClass = comm class (2)    */
-    0x00,         /* bDeviceSubClass                  */
-    0x00,         /* bDeviceProtocol                  */
-    0x40,         /* bMaxPacketSize0 is 64 bytes      */
-    0x0B6A,       /* idVendor (Maxim Integrated)      */
-    0x003C,       /* idProduct                        */
-    0x0100,       /* bcdDevice                        */
-    0x01,         /* iManufacturer Descriptor ID      */
-    0x02,         /* iProduct Descriptor ID           */
-    0x00,         /* iSerialNumber = (0) No string    */
-    0x01          /* bNumConfigurations               */
-};
-
-__attribute__((aligned(4)))
-struct __attribute__((packed)) {
-    usb_configuration_descriptor_t  config_descriptor;
-    usb_interface_descriptor_t      comm_interface_descriptor;
-    uint8_t                         header_functional_descriptor[5];
-    uint8_t                         call_management_descriptor[5];
-    uint8_t                         acm_functional_descriptor[4];
-    uint8_t                         union_functional_descriptor[5];
-    usb_endpoint_descriptor_t       endpoint_descriptor_3;
-    usb_interface_descriptor_t      data_interface_descriptor;
-    usb_endpoint_descriptor_t       endpoint_descriptor_1;
-    usb_endpoint_descriptor_t       endpoint_descriptor_2;
-} config_descriptor =
-{
-    {
-        0x09,       /*  bLength = 9                     */
-        0x02,       /*  bDescriptorType = Config (2)    */
-        0x0043,     /*  wTotalLength(L/H)               */
-        0x02,       /*  bNumInterfaces                  */
-        0x01,       /*  bConfigValue                    */
-        0x00,       /*  iConfiguration                  */
-        0xE0,       /*  bmAttributes (self-powered, remote wakeup) */
-        0x01,       /*  MaxPower is 2ma (units are 2ma/bit) */
-    },
-    { /*  First Interface Descriptor For Comm Class Interface */
-        0x09,       /*  bLength = 9                     */
-        0x04,       /*  bDescriptorType = Interface (4) */
-        0x00,       /*  bInterfaceNumber                */
-        0x00,       /*  bAlternateSetting               */
-        0x01,       /*  bNumEndpoints (one for OUT)     */
-        0x02,       /*  bInterfaceClass = Communications Interface Class (2) */
-        0x02,       /*  bInterfaceSubClass = Abstract Control Model (2) */
-        0x01,       /*  bInterfaceProtocol = Common "AT" commands (1), no class specific protocol (0) */
-        0x00,       /*  iInterface                      */
-    },
-    { /*  Header Functional Descriptor */
-        0x05,         /*  bFunctionalLength = 5           */
-        0x24,         /*  bDescriptorType                 */
-        0x00,         /*  bDescriptorSubtype              */
-        0x10, 0x01,   /*  bcdCDC                          */
-    },
-    { /*  Call Management Descriptor */
-        0x05,         /*  bFunctionalLength = 5           */
-        0x24,         /*  bDescriptorType                 */
-        0x01,         /*  bDescriptorSubtype              */
-        0x03,         /*  bmCapabilities = Device handles call management itself (0x01), management over data class (0x02) */
-        0x01,         /*  bmDataInterface                 */
-    },
-    { /*  Abstract Control Management Functional Descriptor */
-        0x04,         /*  bFunctionalLength = 4           */
-        0x24,         /*  bDescriptorType                 */
-        0x02,         /*  bDescriptorSubtype              */
-        0x02,         /*  bmCapabilities                  */
-    },
-    { /*  Union Functional Descriptor */
-        0x05,         /*  bFunctionalLength = 5           */
-        0x24,         /*  bDescriptorType                 */
-        0x06,         /*  bDescriptorSubtype              */
-        0x00,         /*  bmMasterInterface               */
-        0x01,         /*  bmSlaveInterface0               */
-    },
-    { /*  IN Endpoint 3 (Descriptor #1) */
-        0x07,         /*  bLength                          */
-        0x05,         /*  bDescriptorType (Endpoint)       */
-        0x83,         /*  bEndpointAddress (EP3-IN)        */
-        0x03,         /*  bmAttributes (interrupt)         */
-        0x0040,       /*  wMaxPacketSize                   */
-        0xff,         /*  bInterval (milliseconds)         */
-    },
-    { /*  Second Interface Descriptor For Data Interface */
-        0x09,         /*  bLength                          */
-        0x04,         /*  bDescriptorType (Interface)      */
-        0x01,         /*  bInterfaceNumber                 */
-        0x00,         /*  bAlternateSetting                */
-        0x02,         /*  bNumEndpoints                    */
-        0x0a,         /*  bInterfaceClass = Data Interface (10) */
-        0x00,         /*  bInterfaceSubClass = none (0)    */
-        0x00,         /*  bInterfaceProtocol = No class specific protocol (0) */
-        0x00,         /*  biInterface = No Text String (0) */
-    },
-    { /*  OUT Endpoint 1 (Descriptor #2) */
-        0x07,         /*  bLength                          */
-        0x05,         /*  bDescriptorType (Endpoint)       */
-        0x01,         /*  bEndpointAddress (EP1-OUT)       */
-        0x02,         /*  bmAttributes (bulk)              */
-        0x0040,       /*  wMaxPacketSize                   */
-        0x00,         /*  bInterval (N/A)                  */
-    },
-    { /*  IN Endpoint 2 (Descriptor #3) */
-        0x07,         /*  bLength                          */
-        0x05,         /*  bDescriptorType (Endpoint)       */
-        0x82,         /*  bEndpointAddress (EP2-IN)        */
-        0x02,         /*  bmAttributes (bulk)              */
-        0x0040,       /*  wMaxPacketSize                   */
-        0x00          /*  bInterval (N/A)                  */
-    }
-};
-
-__attribute__((aligned(4)))
-uint8_t lang_id_desc[] = {
-    0x04,         /* bLength */
-    0x03,         /* bDescriptorType */
-    0x09, 0x04    /* bString = wLANGID (see usb_20.pdf 9.6.7 String) */
-};
-
-__attribute__((aligned(4)))
-uint8_t mfg_id_desc[] = {
-    0x22,         /* bLength */
-    0x03,         /* bDescriptorType */
-    'M', 0,
-    'a', 0,
-    'x', 0,
-    'i', 0,
-    'm', 0,
-    ' ', 0,
-    'I', 0,
-    'n', 0,
-    't', 0,
-    'e', 0,
-    'g', 0,
-    'r', 0,
-    'a', 0,
-    't', 0,
-    'e', 0,
-    'd', 0,
-};
-
-__attribute__((aligned(4)))
-uint8_t prod_id_desc[] = {
-    0x22,         /* bLength */
-    0x03,         /* bDescriptorType */
-    'M', 0,
-    'A', 0,
-    'X', 0,
-    '3', 0,
-    '2', 0,
-    '6', 0,
-    '6', 0,
-    '5', 0,
-    ' ', 0,
-    'C', 0,
-    'D', 0,
-    'C', 0,
-    '-', 0,
-    'A', 0,
-    'C', 0,
-    'M', 0,
-};
-
-/* Not currently used (see device descriptor), but could be enabled if desired */
-__attribute__((aligned(4)))
-uint8_t serial_id_desc[] = {
-    0x14,         /* bLength */
-    0x03,         /* bDescriptorType */
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '1', 0
-};
-
-#endif /* _DESCRIPTORS_H_ */
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index eb34e17b..74b0c814 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -131,6 +131,10 @@ typedef _Bool bool;
 #define API_MAX86150_GET_DATA		0x0101
 #define API_MAX86150_SET_LED_AMPLITUDE	0x0102
 
+#define API_USB_SHUTDOWN           0x110
+#define API_USB_STORAGE            0x111
+#define API_USB_CDCACM             0x112
+
 /* clang-format on */
 
 typedef uint32_t api_int_id_t;
@@ -1700,5 +1704,20 @@ API(API_MAX30001_DISABLE, int epic_max30001_disable_sensor(
 void
 ));
 
+/**
+ * De-initialize the currently configured USB device (if any)
+ *
+ */
+API(API_USB_SHUTDOWN, int epic_usb_shutdown(void));
+/**
+ * Configure the USB peripheral to export the internal FLASH
+ * as a Mass Storage device
+ */
+API(API_USB_STORAGE, int epic_usb_storage(void));
+/**
+ * Configure the USB peripheral to provide card10's stdin/stdout
+ * on a USB CDC-ACM device
+ */
+API(API_USB_CDCACM, int epic_usb_cdcacm(void));
 
 #endif /* _EPICARDIUM_H */
diff --git a/epicardium/fs/filesystem_fat.c b/epicardium/fs/filesystem_fat.c
index 7d955103..ccaec621 100644
--- a/epicardium/fs/filesystem_fat.c
+++ b/epicardium/fs/filesystem_fat.c
@@ -16,6 +16,7 @@
 
 #include <FreeRTOS.h>
 #include <semphr.h>
+#include <timers.h>
 
 #include "fs/internal.h"
 #include "modules/filesystem.h"
@@ -57,7 +58,7 @@ struct FatObject {
 struct EpicFileSystem {
 	struct FatObject pool[EPIC_FAT_MAX_OPENED];
 	uint32_t generationCount;
-	bool initialized;
+	bool attached;
 	FATFS FatFs;
 	int lockCoreMask;
 };
@@ -102,6 +103,11 @@ static StaticSemaphore_t s_globalLockBuffer;
 
 static SemaphoreHandle_t s_globalLock = NULL;
 
+static void cb_attachTimer(void *a, uint32_t b)
+{
+	fatfs_attach();
+}
+
 void fatfs_init()
 {
 	static volatile bool s_initCalled = false;
@@ -117,6 +123,7 @@ void fatfs_init()
 #else
 	s_globalLock = xSemaphoreCreateMutex();
 #endif
+
 	s_globalFileSystem.generationCount = 1;
 	fatfs_attach();
 }
@@ -137,11 +144,11 @@ int fatfs_attach()
 	int rc = 0;
 	if (globalLockAccquire()) {
 		EpicFileSystem *fs = &s_globalFileSystem;
-		if (!fs->initialized) {
+		if (!fs->attached) {
 			ff_res = f_mount(&fs->FatFs, "/", 0);
 			if (ff_res == FR_OK) {
-				fs->initialized = true;
-				SSLOG_DEBUG("FatFs mounted\n");
+				fs->attached = true;
+				SSLOG_INFO("attached\n");
 			} else {
 				SSLOG_ERR(
 					"f_mount error %s\n",
@@ -158,25 +165,38 @@ int fatfs_attach()
 	return rc;
 }
 
+void fatfs_schedule_attach(void)
+{
+	//if we're running in thread context, cont't call the *FromISR version
+	if (xPortIsInsideInterrupt()) {
+		xTimerPendFunctionCallFromISR(cb_attachTimer, NULL, 0, NULL);
+	} else {
+		xTimerPendFunctionCall(
+			cb_attachTimer, NULL, 0, 1); //wait 1 tick
+	}
+}
+
 void fatfs_detach()
 {
 	FRESULT ff_res;
 	EpicFileSystem *fs;
 	if (efs_lock_global(&fs) == 0) {
-		efs_close_all(fs, EPICARDIUM_COREMASK_BOTH);
-
-		//unmount by passing NULL as fs object, will destroy our sync object via ff_del_syncobj
-		ff_res = f_mount(NULL, "/", 0);
-		if (ff_res != FR_OK) {
-			SSLOG_ERR(
-				"f_mount (unmount) error %s\n",
-				f_get_rc_string(ff_res)
-			);
-		}
+		if (fs->attached) {
+			efs_close_all(fs, EPICARDIUM_COREMASK_BOTH);
+
+			//unmount by passing NULL as fs object, will destroy our sync object via ff_del_syncobj
+			ff_res = f_mount(NULL, "/", 0);
+			if (ff_res != FR_OK) {
+				SSLOG_ERR(
+					"f_mount (unmount) error %s\n",
+					f_get_rc_string(ff_res)
+				);
+			}
 
-		fs->initialized = false;
-		disk_deinitialize();
-		SSLOG_INFO("detached\n");
+			fs->attached = false;
+			disk_deinitialize();
+			SSLOG_INFO("detached\n");
+		}
 		efs_unlock_global(fs);
 	}
 }
@@ -223,7 +243,7 @@ int efs_lock_global(EpicFileSystem **fs)
 	if (!globalLockAccquire()) {
 		return -EBUSY;
 	}
-	if (!s_globalFileSystem.initialized) {
+	if (!s_globalFileSystem.attached) {
 		globalLockRelease();
 		return -ENODEV;
 	}
diff --git a/epicardium/meson.build b/epicardium/meson.build
index 1aa55560..cf67023b 100644
--- a/epicardium/meson.build
+++ b/epicardium/meson.build
@@ -80,7 +80,9 @@ endif
 
 elf = executable(
   name + '.elf',
-  'cdcacm.c',
+  'usb/epc_usb.c',
+  'usb/cdcacm.c',
+  'usb/mass_storage.c',
   'main.c',
   'support.c',
   'fs/filesystem_fat.c',
diff --git a/epicardium/modules/filesystem.h b/epicardium/modules/filesystem.h
index 720147ec..db54a289 100644
--- a/epicardium/modules/filesystem.h
+++ b/epicardium/modules/filesystem.h
@@ -16,9 +16,19 @@ void fatfs_init(void);
 
 /**
  * initialize and mount the FLASH storage
+ * 
+ * NOTE: not safe to be called from an ISR
  */
 int fatfs_attach(void);
 
+
+/**
+ * asynchronously attach the FLASH storage
+ * 
+ * safe to be called from an ISR
+ */
+void fatfs_schedule_attach(void);
+
 /** close all opened FDs, sync and deinitialize FLASH layer */
 void fatfs_detach(void);
 
diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c
index cb983dae..c944c6b9 100644
--- a/epicardium/modules/hardware.c
+++ b/epicardium/modules/hardware.c
@@ -2,7 +2,7 @@
 
 #include "api/dispatcher.h"
 #include "api/interrupt-sender.h"
-#include "cdcacm.h"
+#include "usb/epc_usb.h"
 #include "modules/filesystem.h"
 #include "modules/log.h"
 #include "modules/modules.h"
@@ -152,8 +152,8 @@ int hardware_early_init(void)
 	/*
 	 * USB-Serial
 	 */
-	if (cdcacm_init() < 0) {
-		LOG_ERR("init", "USB-Serial unavailable");
+	if (epic_usb_cdcacm() < 0) {
+		LOG_ERR("startup", "USB-Serial unavailable");
 	}
 
 	/*
diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build
index 1aa7494e..693ca9a8 100644
--- a/epicardium/modules/meson.build
+++ b/epicardium/modules/meson.build
@@ -21,4 +21,5 @@ module_sources = files(
   'trng.c',
   'vibra.c',
   'watchdog.c',
+  'usb.c'
 )
diff --git a/epicardium/modules/serial.c b/epicardium/modules/serial.c
index b7be0594..c5350b11 100644
--- a/epicardium/modules/serial.c
+++ b/epicardium/modules/serial.c
@@ -4,7 +4,7 @@
 #include "modules/modules.h"
 
 #include "max32665.h"
-#include "cdcacm.h"
+#include "usb/cdcacm.h"
 #include "uart.h"
 
 #include "FreeRTOS.h"
diff --git a/epicardium/modules/usb.c b/epicardium/modules/usb.c
new file mode 100644
index 00000000..e14032e4
--- /dev/null
+++ b/epicardium/modules/usb.c
@@ -0,0 +1,322 @@
+/**
+ * 
+ * Implementation of public epic_usb_ interface
+ * 
+ * Configuration and FLASH-specific implementation of USB mass storage device
+ * Configuration of USB CDCACM device
+ * 
+ * USB descriptors for both are defined here.
+ * 
+ */
+
+#include "epicardium.h"
+
+#include "modules/filesystem.h"
+
+#include "usb/cdcacm.h"
+#include "usb/mass_storage.h"
+#include "usb/descriptors.h"
+#include "usb/epc_usb.h"
+
+#include "modules/log.h"
+
+#include "mx25lba.h"
+#include "msc.h"
+
+/* memory access callbacks for the mass storage (FLASH) device */
+static int mscmem_init();
+static uint32_t mscmem_size(void);
+static int mscmem_read(uint32_t lba, uint8_t *buffer);
+static int mscmem_write(uint32_t lba, uint8_t *buffer);
+static int mscmem_start();
+static int mscmem_stop();
+static int mscmem_ready();
+
+/* USB descriptors, definition at the end of this file */
+static usb_device_descriptor_t device_descriptor_cdcacm
+	__attribute__((aligned(4)));
+static struct config_descriptor_cdcacm config_descriptor_cdcacm
+	__attribute__((aligned(4)));
+static usb_device_descriptor_t device_descriptor_msc
+	__attribute__((aligned(4)));
+static struct config_descriptor_msc config_descriptor_msc
+	__attribute__((aligned(4)));
+
+/* definitions of config structs */
+static const msc_mem_t s_mscCallbacks = {
+	mscmem_init, mscmem_start, mscmem_stop,  mscmem_ready,
+	mscmem_size, mscmem_read,  mscmem_write,
+};
+
+static struct esb_device_descriptors s_dd_cdcacm = {
+	.device = &device_descriptor_cdcacm, .cdcacm = &config_descriptor_cdcacm
+};
+
+static struct esb_device_descriptors s_dd_msc = {
+	.device = &device_descriptor_msc, .msc = &config_descriptor_msc
+};
+
+static struct esb_config s_cfg_cdcacm = {
+	.name        = "cdcacm",
+	.init        = esb_cdc_init,
+	.configure   = esb_cdc_configure,
+	.deconfigure = esb_cdc_deconfigure,
+	.deinit      = NULL,
+
+	.descriptors = &s_dd_cdcacm,
+	.deviceData  = NULL,
+};
+
+static struct esb_config s_cfg_msc = {
+	.name        = "msc FLASH",
+	.init        = esb_msc_init,
+	.configure   = esb_msc_configure,
+	.deconfigure = esb_msc_deconfigure,
+	.deinit      = NULL,
+
+	.descriptors = &s_dd_msc,
+	.deviceData  = (void *)&s_mscCallbacks,
+};
+
+/* function implementations */
+
+static int dirty = 0;
+static int mscmem_init()
+{
+	LOG_DEBUG("msc", "mem.init");
+	fatfs_detach();
+	return mx25_init();
+}
+
+static uint32_t mscmem_size(void)
+{
+	return mx25_size();
+}
+
+static int mscmem_read(uint32_t lba, uint8_t *buffer)
+{
+	return mx25_read(lba, buffer);
+}
+
+static int mscmem_write(uint32_t lba, uint8_t *buffer)
+{
+	if (dirty == 0) {
+		//bootloader_dirty();
+	}
+	dirty = 2;
+	return mx25_write(lba, buffer);
+}
+
+static int mscmem_start()
+{
+	return mx25_start();
+}
+
+static int mscmem_stop()
+{
+	LOG_DEBUG("msc", "mem.stop");
+	int ret = mx25_stop();
+	fatfs_schedule_attach();
+	return ret;
+}
+
+static int mscmem_ready()
+{
+	if (dirty) {
+		dirty--;
+		if (dirty == 0) {
+			LOG_DEBUG("msc", "sync");
+			mx25_sync();
+			//bootloader_clean();
+		}
+	}
+	return mx25_ready();
+}
+
+static bool s_fsDetached = false;
+int epic_usb_shutdown(void)
+{
+	esb_deinit();
+	if (s_fsDetached) {
+		fatfs_attach();
+	}
+	return 0;
+}
+
+int epic_usb_storage(void)
+{
+	esb_deinit();
+	s_fsDetached = true;
+	return esb_init(&s_cfg_msc);
+}
+
+int epic_usb_cdcacm(void)
+{
+	esb_deinit();
+	if (s_fsDetached) {
+		fatfs_attach();
+	}
+	return esb_init(&s_cfg_cdcacm);
+}
+
+/* clang-format off */
+static usb_device_descriptor_t device_descriptor_cdcacm = {
+	.bLength            = 0x12,
+	.bDescriptorType    = DT_DEVICE,
+	.bcdUSB             = 0x0110,
+	.bDeviceClass       = CLS_COMM,
+	.bDeviceSubClass    = 0x00,
+	.bDeviceProtocol    = 0x00,
+	.bMaxPacketSize     = 0x40,
+	.idVendor           = VNDR_MAXIM,
+	.idProduct          = 0x003C,
+	.bcdDevice          = 0x0100,
+	.iManufacturer      = 0x01,
+	.iProduct           = 0x02,
+	.iSerialNumber      = 0x00,
+	.bNumConfigurations = 0x01
+};
+
+static struct config_descriptor_cdcacm config_descriptor_cdcacm = {
+    .config = {
+        .bLength            = 0x09,
+        .bDescriptorType    = DT_CONFIG,
+        .wTotalLength       = 0x0043,     /*  sizeof(struct config_descriptor_cdcacm) */
+        .bNumInterfaces     = 0x02,
+        .bConfigurationValue= 0x01,
+        .iConfiguration     = 0x00,
+        .bmAttributes       = 0xE0,       /*  (self-powered, remote wakeup) */
+        .bMaxPower          = 0x01,       /*  MaxPower is 2ma (units are 2ma/bit) */
+    },
+    .comm_interface = { /*  First Interface Descriptor For Comm Class Interface */
+        .bLength            = 0x09,
+        .bDescriptorType    = DT_INTERFACE,
+        .bInterfaceNumber   = 0x00,
+        .bAlternateSetting  = 0x00,
+        .bNumEndpoints      = 0x01,
+        .bInterfaceClass    = CLS_COMM,
+        .bInterfaceSubClass = SCLS_ACM,
+        .bInterfaceProtocol = PROT_AT_CMDS,
+        .iInterface         = 0x00,
+    },
+    .header_functional = {
+        0x05,         /*  bFunctionalLength = 5           */
+        DT_FUNCTIONAL,/*  bDescriptorType                 */
+        0x00,         /*  bDescriptorSubtype              */
+        0x10, 0x01,   /*  bcdCDC                          */
+    },
+    .call_management = {
+        0x05,         /*  bFunctionalLength = 5           */
+        DT_FUNCTIONAL,/*  bDescriptorType                 */
+        0x01,         /*  bDescriptorSubtype              */
+        0x03,         /*  bmCapabilities = Device handles call management itself (0x01), management over data class (0x02) */
+        0x01,         /*  bmDataInterface                 */
+    },
+    .acm_functional = {
+        0x04,         /*  bFunctionalLength = 4           */
+        DT_FUNCTIONAL,/*  bDescriptorType                 */
+        0x02,         /*  bDescriptorSubtype              */
+        0x02,         /*  bmCapabilities                  */
+    },
+    .union_functional = {
+        0x05,         /*  bFunctionalLength = 5           */
+        DT_FUNCTIONAL,/*  bDescriptorType                 */
+        0x06,         /*  bDescriptorSubtype              */
+        0x00,         /*  bmMasterInterface               */
+        0x01,         /*  bmSlaveInterface0               */
+    },
+    .endpoint_notify = {
+        .bLength            = 0x07,
+        .bDescriptorType    = DT_ENDPOINT,
+        .bEndpointAddress   = 0x80 | ESB_ENDPOINT_CDCACM_NOTIFY,
+        .bmAttributes       = ATTR_INTERRUPT,
+        .wMaxPacketSize     = 0x0040,
+        .bInterval          = 0xff,
+    },
+    .data_interface = {
+        .bLength            = 0x09,
+        .bDescriptorType    = DT_INTERFACE,
+        .bInterfaceNumber   = 0x01,
+        .bAlternateSetting  = 0x00,
+        .bNumEndpoints      = 0x02,
+        .bInterfaceClass    = CLS_DATA,
+        .bInterfaceSubClass = 0x00,
+        .bInterfaceProtocol = 0x00,
+        .iInterface         = 0x00, //not 1?
+    },
+    .endpoint_out = {
+        .bLength            = 0x07,
+        .bDescriptorType    = DT_ENDPOINT,
+        .bEndpointAddress   = ESB_ENDPOINT_CDCACM_OUT,
+        .bmAttributes       = ATTR_BULK,
+        .wMaxPacketSize     = 0x0040,
+        .bInterval          = 0x00,
+    },
+    .endpoint_in = {
+        .bLength            = 0x07,
+        .bDescriptorType    = DT_ENDPOINT,
+        .bEndpointAddress   = 0x80 | ESB_ENDPOINT_CDCACM_IN,
+        .bmAttributes       = ATTR_BULK,
+        .wMaxPacketSize     = 0x0040,
+        .bInterval          = 0x00
+    }
+};
+
+static usb_device_descriptor_t device_descriptor_msc = {
+	.bLength            = 0x12,
+	.bDescriptorType    = DT_DEVICE,
+	.bcdUSB             = 0x0200,
+	.bDeviceClass       = CLS_UNSPECIFIED,
+	.bDeviceSubClass    = 0x00,
+	.bDeviceProtocol    = 0x00,
+	.bMaxPacketSize     = 0x40,
+	.idVendor           = VNDR_MAXIM,
+	.idProduct          = 0x4402,
+	.bcdDevice          = 0x0100,
+	.iManufacturer      = 0x01,
+	.iProduct           = 0x02,
+	.iSerialNumber      = 0x03,
+	.bNumConfigurations = 0x01
+};
+
+static struct config_descriptor_msc config_descriptor_msc =
+{
+    .config = {
+        .bLength            = 0x09,
+        .bDescriptorType    = DT_CONFIG,
+        .wTotalLength       = 0x0020,
+        .bNumInterfaces     = 0x01,
+        .bConfigurationValue= 0x01,
+        .iConfiguration     = 0x00,
+        .bmAttributes       = 0xC0,       /* (self-powered, no remote wakeup) */
+        .bMaxPower          = 0x32,
+    },
+    .msc_interface = {
+        .bLength                = 0x09,
+        .bDescriptorType        = DT_INTERFACE,
+        .bInterfaceNumber       = 0x00,
+        .bAlternateSetting      = 0x00,
+        .bNumEndpoints          = 0x02,
+        .bInterfaceClass        = CLS_MASS_STOR,
+        .bInterfaceSubClass     = SCLS_SCSI_CMDS,
+        .bInterfaceProtocol     = PROT_BULK_TRANS,
+        .iInterface             = 0x00,
+    },
+    .endpoint_out = {
+        .bLength            = 0x07,
+        .bDescriptorType    = DT_ENDPOINT,
+        .bEndpointAddress   = ESB_ENDPOINT_MSC_OUT,
+        .bmAttributes       = ATTR_BULK,
+        .wMaxPacketSize     = 0x0040,
+        .bInterval          = 0x00,
+    },
+    .endpoint_in = {
+        .bLength            = 0x07,
+        .bDescriptorType    = DT_ENDPOINT,
+        .bEndpointAddress   = 0x80 | ESB_ENDPOINT_MSC_IN,
+        .bmAttributes       = ATTR_BULK,
+        .wMaxPacketSize     = 0x0040,
+        .bInterval          = 0x00
+    }
+};
+/* clang-format on */
\ No newline at end of file
diff --git a/epicardium/usb/cdcacm.c b/epicardium/usb/cdcacm.c
new file mode 100644
index 00000000..679a3a05
--- /dev/null
+++ b/epicardium/usb/cdcacm.c
@@ -0,0 +1,126 @@
+/**
+ * 
+ * epicardium-specific implemnetation of cdcacm device
+ * services CDCACM API
+ */
+
+#include "usb/epc_usb.h"
+#include "usb/cdcacm.h"
+#include "usb/descriptors.h"
+
+#include <stdio.h>
+#include <stddef.h>
+#include <errno.h>
+
+#include "usb.h"
+#include "usb_event.h"
+#include "cdc_acm.h"
+
+#include "modules/log.h"
+
+#include "FreeRTOS.h"
+#include "task.h"
+
+static inline struct config_descriptor_cdcacm *
+descriptors(struct esb_config *self)
+{
+	return self->descriptors->cdcacm;
+}
+
+/******************************************************************************/
+static volatile int usb_read_complete; // SWYM: only ever written to
+static int cb_acm_read_ready(void)
+{
+	usb_read_complete = 1;
+	return 0;
+}
+
+int esb_cdc_init(struct esb_config *self)
+{
+	LOG_DEBUG("cdcacm", "init");
+	struct config_descriptor_cdcacm *dsc = descriptors(self);
+	usb_read_complete                    = 0;
+	acm_register_callback(
+		ACM_CB_READ_READY,
+		cb_acm_read_ready); //SWYM: actually not needed
+	return acm_init(&dsc->comm_interface);
+}
+
+int esb_cdc_configure(struct esb_config *self)
+{
+	struct config_descriptor_cdcacm *dsc = descriptors(self);
+
+	//acm_configure does not keep the amc_cfg_t pointer around
+	//so stack-local lifetime is fine
+	acm_cfg_t acm_cfg = {
+		.in_ep         = dsc->endpoint_in.bEndpointAddress & 0x0f,
+		.in_maxpacket  = MXC_USBHS_MAX_PACKET,
+		.out_ep        = dsc->endpoint_out.bEndpointAddress,
+		.out_maxpacket = MXC_USBHS_MAX_PACKET,
+		.notify_ep     = dsc->endpoint_notify.bEndpointAddress & 0x0f,
+		.notify_maxpacket = MXC_USBHS_MAX_PACKET,
+	};
+
+	LOG_DEBUG(
+		"cdcacm",
+		"configure, endpoints %d,%d,%d",
+		acm_cfg.in_ep,
+		acm_cfg.out_ep,
+		acm_cfg.notify_ep
+	);
+
+	return acm_configure(&acm_cfg);
+}
+
+int esb_cdc_deconfigure(struct esb_config *self)
+{
+	LOG_DEBUG("cdcacm", "deconfigure");
+	return acm_deconfigure();
+}
+
+int cdcacm_num_read_avail(void)
+{
+	return acm_canread();
+}
+
+uint8_t cdcacm_read(void)
+{
+	while (acm_canread() <= 0) {
+	}
+
+	uint8_t buf;
+	acm_read(&buf, 1);
+	return buf;
+}
+
+void cdcacm_write(uint8_t *data, int len)
+{
+	static int lockup_disable = 0;
+	if (acm_present() && !lockup_disable) {
+		int ret = acm_write(data, len);
+		if (ret < 0) {
+			LOG_ERR("cdcacm", "fifo lockup detected");
+			lockup_disable = 1;
+		} else if (ret != len) {
+			LOG_WARN(
+				"cdcacm", "write length mismatch, got %d", ret
+			);
+		}
+	}
+}
+
+/******************************************************************************/
+
+extern TaskHandle_t serial_task_id;
+void USB_IRQHandler(void)
+{
+	usb_event_handler();
+
+	if (serial_task_id != NULL) {
+		BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+		vTaskNotifyGiveFromISR(
+			serial_task_id, &xHigherPriorityTaskWoken
+		);
+		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+	}
+}
diff --git a/epicardium/usb/cdcacm.h b/epicardium/usb/cdcacm.h
new file mode 100644
index 00000000..6683e055
--- /dev/null
+++ b/epicardium/usb/cdcacm.h
@@ -0,0 +1,15 @@
+#ifndef CDCACM_H
+#define CDCACM_H
+
+#include "usb/epc_usb.h"
+
+// callbacks for esb_config
+int esb_cdc_init(struct esb_config* self);
+int esb_cdc_configure(struct esb_config* self);
+int esb_cdc_deconfigure(struct esb_config* self);
+
+int cdcacm_num_read_avail(void);
+uint8_t cdcacm_read(void);
+void cdcacm_write(uint8_t *data, int len);
+
+#endif
diff --git a/epicardium/usb/descriptors.h b/epicardium/usb/descriptors.h
new file mode 100644
index 00000000..2fb2cc45
--- /dev/null
+++ b/epicardium/usb/descriptors.h
@@ -0,0 +1,83 @@
+#ifndef _DESCRIPTORS_H_
+#define _DESCRIPTORS_H_
+
+#include <stdint.h>
+#include "usb.h"
+
+#define ESB_ENDPOINT_MSC_IN         2
+#define ESB_ENDPOINT_MSC_OUT        1
+#define ESB_ENDPOINT_CDCACM_NOTIFY  3
+#define ESB_ENDPOINT_CDCACM_IN      2
+#define ESB_ENDPOINT_CDCACM_OUT     1
+
+/* device types */
+#define DT_DEVICE       0x01
+#define DT_CONFIG       0x02
+#define DT_INTERFACE    0x04
+#define DT_ENDPOINT     0x05
+#define DT_FUNCTIONAL   0x24
+
+/* interface classes */
+#define CLS_UNSPECIFIED 0x00
+#define CLS_COMM        0x02
+#define CLS_MASS_STOR   0x08
+#define CLS_DATA        0x0a
+
+/* sub-classes */
+#define SCLS_NONE       0x00
+#define SCLS_ACM        0x02
+#define SCLS_SCSI_CMDS  0x06
+
+/* interface protocols */
+#define PROT_AT_CMDS    0x01
+#define PROT_BULK_TRANS 0x50
+
+/* endpoint attributes */
+#define ATTR_BULK       0x02
+#define ATTR_INTERRUPT  0x03
+
+
+#define VNDR_MAXIM  0x0B6A
+
+#if defined(__GNUC__)
+#define PACKED_STRUCT struct __attribute__((packed))
+#else
+#define PACKED_STRUCT __packed struct
+#endif
+
+
+
+
+PACKED_STRUCT
+config_descriptor_cdcacm {
+    usb_configuration_descriptor_t  config;
+    usb_interface_descriptor_t      comm_interface;
+    uint8_t                         header_functional[5];
+    uint8_t                         call_management[5];
+    uint8_t                         acm_functional[4];
+    uint8_t                         union_functional[5];
+    usb_endpoint_descriptor_t       endpoint_notify;
+    usb_interface_descriptor_t      data_interface;
+    usb_endpoint_descriptor_t       endpoint_out;
+    usb_endpoint_descriptor_t       endpoint_in;
+};
+
+PACKED_STRUCT
+config_descriptor_msc {
+    usb_configuration_descriptor_t  config;
+    usb_interface_descriptor_t      msc_interface;
+    usb_endpoint_descriptor_t       endpoint_out;
+    usb_endpoint_descriptor_t       endpoint_in;
+};
+
+struct esb_device_descriptors {
+    usb_device_descriptor_t* device;
+    union {
+        usb_configuration_descriptor_t* config;
+        struct config_descriptor_cdcacm* cdcacm;
+        struct config_descriptor_msc* msc;
+    };
+};
+
+
+#endif /* _DESCRIPTORS_H_ */
diff --git a/epicardium/usb/epc_usb.c b/epicardium/usb/epc_usb.c
new file mode 100644
index 00000000..9c24ea14
--- /dev/null
+++ b/epicardium/usb/epc_usb.c
@@ -0,0 +1,460 @@
+/**
+ * 
+ * 
+ * Core of the USB implementation:
+ * 
+ * - device independent
+ * - handles setup of MAXUSB stack
+ * - handles events and state of the MAXUSB stack
+ * 
+ * Also contains definitions of device-independent String descriptors
+ * 
+ * 
+ * 
+ * 
+ * 
+ * swym's USB ramblings:
+ * 
+ * setting up USB basically consists of two parts:
+ * 
+ * - set up descriptors
+ * - initialize the corresponding MAXUSB stacks - acm, msc...
+ * 
+ * at the moment, the descriptors are statically configured in
+ * descriptors.h several descriptors point to enumeration descriptors
+ * via iFooBar indices. Those enumeration descriptors are registered
+ * via enum_register_descriptor(type, desc, idx), where the idx passed
+ * corresponds to the iFooBar index mentioned above.
+ * 
+ * There are several callbacks, some of which do not perform anything meaningful
+ * at the moment. For example usb_write_callback sets a global flag that is never
+ * read from. These will be removed later on.
+ * 
+ * The  initialization routines of acm & msc refer to the descriptors from which,
+ * among other things, they get their enpoint IDs. These are hard-coded right now
+ * but it would make sense to allocate these IDs at runtime. There is, AFAICT, no
+ * reason for these endpoint IDs to be 3, 1, 2 in the CDCACM case for example.
+ * 
+ * Allocating those at runtime would also make the setup less rigid: we can have all
+ * combinations of {cdcacm, storage} interfaces without the awkward descriptor hackery
+ * in epc_usb_init.
+ * 
+ * To generalize even further, the descriptors could be malloced. The total length of
+ * the descriptor structure can easily be derived from the number of interfaces to be
+ * initialized. The memory allocated will then be split up (with alignment of the single
+ * descriptor structs taken care of) in a simple bump-allocator strategy on demand.
+ * 
+ * 
+ */
+#include "usb/epc_usb.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#include <FreeRTOS.h>
+#include <task.h>
+#include <timers.h>
+
+#include "mxc_config.h"
+#include "mxc_sys.h"
+#include "mxc_delay.h"
+
+// MAXUSB includes
+#include "usb.h"
+#include "usb_event.h"
+#include "enumerate.h"
+#include "msc.h"
+#include "cdc_acm.h"
+
+#include "usb/descriptors.h"
+#include "modules/log.h"
+#include "modules/filesystem.h"
+
+//#define USE_REMOTE_WAKE_ENABLE
+
+/***** Function Prototypes *****/
+static int cb_usb_setconfig(usb_setup_pkt *sud, void *cbdata);
+#ifdef USE_REMOTE_WAKE_ENABLE
+static int cb_usb_setfeature(usb_setup_pkt *sud, void *cbdata);
+static int cb_usb_clrfeature(usb_setup_pkt *sud, void *cbdata);
+#endif
+static int cb_usb_event(maxusb_event_t evt, void *data);
+static int cb_usb_shutdown();
+static int cb_usb_init();
+static void usb_app_sleep(void);
+static void usb_app_wakeup(void);
+/* needed for usb_opts. mxc_delay() takes unsigned long, so can't use it directly */
+static void delay_us(unsigned int usec);
+static void device_deinit();
+static bool device_deconfigure();
+static int device_configure();
+
+volatile int suspended;
+#ifdef USE_REMOTE_WAKE_ENABLE
+int remote_wake_en; //SWYM: unused, only written to!
+#endif
+
+static StaticTimer_t x;
+static TimerHandle_t timerWakeup = NULL;
+static void cb_timerReset(TimerHandle_t t);
+
+static struct esb_config s_device = { 0 };
+
+__attribute__((aligned(4))) uint8_t lang_id_desc[] = {
+	0x04, /* bLength */
+	0x03, /* bDescriptorType */
+	0x09,
+	0x04 /* bString = wLANGID (see usb_20.pdf 9.6.7 String) */
+};
+
+__attribute__((aligned(4))) uint8_t mfg_id_desc[] = {
+	0x22, /* bLength */
+	0x03, /* bDescriptorType */
+	'M',  0, 'a', 0, 'x', 0, 'i', 0, 'm', 0, ' ', 0, 'I', 0, 'n', 0,
+	't',  0, 'e', 0, 'g', 0, 'r', 0, 'a', 0, 't', 0, 'e', 0, 'd', 0,
+};
+
+__attribute__((aligned(4))) uint8_t prod_id_desc[] = {
+	0x24, /* bLength */
+	0x03, /* bDescriptorType */
+	'C',  0, 'A', 0, 'R', 0, 'D', 0, '1', 0, '0', 0, ' ', 0, 'U', 0, 'S', 0,
+	'B',  0, ' ', 0, 'G', 0, 'A', 0, 'D', 0, 'G', 0, 'E', 0, 'T', 0,
+};
+
+/* Not currently used (see device descriptor), but could be enabled if desired */
+__attribute__((aligned(4)))
+uint8_t serial_id_desc[] = { 0x14, /* bLength */
+			     0x03, /* bDescriptorType */
+			     'c',  0,   'a', 0,   'a', 0,   'a', 0,   'a',
+			     0,    'd', 0,   '1', 0,   '0', 0,   '0', 0 };
+
+int cb_usb_init()
+{
+	const sys_cfg_usbhs_t sys_usbhs_cfg = NULL;
+	LOG_DEBUG("usb", "init");
+	return SYS_USBHS_Init(&sys_usbhs_cfg);
+}
+
+volatile bool shutDownCompleted;
+void do_usb_shutdown()
+{
+	LOG_DEBUG("usb", "shutting down...");
+	shutDownCompleted = false;
+	usb_shutdown();
+	for (int i = 0; i < 10000 && !shutDownCompleted; ++i) {
+	}
+}
+int cb_usb_shutdown()
+{
+	SYS_USBHS_Shutdown();
+	SYS_Reset_Periph(SYS_RESET_USB);
+	device_deinit();
+	LOG_DEBUG("usb", "shutdown complete");
+	shutDownCompleted = true;
+	return 0;
+}
+
+/* User-supplied function to delay usec micro-seconds */
+static void delay_us(unsigned int usec)
+{
+	/* mxc_delay() takes unsigned long, so can't use it directly */
+	mxc_delay(usec);
+}
+
+static void device_deinit()
+{
+	esb_cfg_handler deinit = s_device.deinit;
+	s_device.deinit        = NULL;
+	s_device.configure     = NULL;
+	s_device.deconfigure   = NULL;
+	if (deinit) {
+		LOG_DEBUG("usb", "deinit");
+		deinit(&s_device);
+	}
+}
+
+//de-configure last device, if any
+static volatile bool s_configured = false;
+static bool device_deconfigure()
+{
+	if (s_configured) {
+		s_configured = false;
+		if (s_device.deconfigure) {
+			LOG_DEBUG("usb", "deconfigure");
+			enum_clearconfig();
+			s_device.deconfigure(&s_device);
+			return true;
+		}
+	}
+	return false;
+}
+
+static int device_configure()
+{
+	if (!s_configured && s_device.configure) {
+		s_configured = true;
+		LOG_DEBUG("usb", "configure");
+		return s_device.configure(&s_device);
+	}
+	return 0;
+}
+
+static volatile bool s_connected = false;
+static void disconnect()
+{
+	if (s_connected) {
+		LOG_DEBUG("usb", "disconnect");
+		s_connected = false;
+		usb_disconnect();
+		//		SYS_Reset_Periph(SYS_RESET_USB);
+		mxc_delay(10000);
+	}
+}
+static void connect()
+{
+	s_connected = true;
+	usb_connect();
+}
+
+static volatile bool s_initialized = false;
+void esb_deinit(void)
+{
+	if (s_initialized) {
+		s_initialized = false;
+		disconnect();
+		device_deconfigure();
+		do_usb_shutdown();
+		device_deinit();
+	}
+}
+int esb_init(struct esb_config *cfg)
+{
+	esb_deinit();
+	if (cfg == NULL) {
+		return 0;
+	}
+	if (timerWakeup == NULL) {
+		timerWakeup = xTimerCreateStatic(
+			"timerWakeup",     /* name */
+			pdMS_TO_TICKS(50), /* period/time */
+			pdFALSE,           /* auto reload */
+			NULL,              /* timer ID */
+			cb_timerReset,
+			&x);
+	}
+
+	maxusb_cfg_options_t usb_opts;
+
+	/* Initialize state */
+	suspended = 0;
+#ifdef USE_REMOTE_WAKE_ENABLE
+	remote_wake_en = 0;
+#endif
+
+	/* Start out in full speed */
+	usb_opts.enable_hs         = 0;
+	usb_opts.delay_us          = delay_us;
+	usb_opts.init_callback     = cb_usb_init;
+	usb_opts.shutdown_callback = cb_usb_shutdown;
+
+	/* Initialize the usb module */
+	if (usb_init(&usb_opts) != 0) {
+		LOG_ERR("usb", "usb_init() failed");
+		return -EIO;
+	}
+
+	/* Initialize the enumeration module */
+	if (enum_init() != 0) {
+		do_usb_shutdown();
+		LOG_ERR("usb", "enum_init() failed");
+		return -EIO;
+	}
+
+	LOG_INFO("usb", "initializing device %s", cfg->name);
+	if (cfg->init(cfg)) {
+		enum_clearconfig();
+		do_usb_shutdown();
+		LOG_ERR("usb", "device init failed");
+		return -EIO;
+	}
+
+	s_initialized = true;
+	s_device      = *cfg;
+
+	enum_register_descriptor(
+		ENUM_DESC_DEVICE, (uint8_t *)cfg->descriptors->device, 0
+	);
+	enum_register_descriptor(
+		ENUM_DESC_CONFIG, (uint8_t *)cfg->descriptors->config, 0
+	);
+	enum_register_descriptor(ENUM_DESC_STRING, lang_id_desc, 0);
+	enum_register_descriptor(ENUM_DESC_STRING, mfg_id_desc, 1);
+	enum_register_descriptor(ENUM_DESC_STRING, prod_id_desc, 2);
+	enum_register_descriptor(ENUM_DESC_STRING, serial_id_desc, 3);
+	//enum_register_descriptor(ENUM_DESC_STRING, cdcacm_func_desc, 4);
+	//enum_register_descriptor(ENUM_DESC_STRING, msc_func_desc, 5);
+
+	/* Handle configuration */
+	enum_register_callback(ENUM_SETCONFIG, cb_usb_setconfig, NULL);
+#ifdef USE_REMOTE_WAKE_ENABLE
+	/* Handle feature set/clear */
+	enum_register_callback(ENUM_SETFEATURE, cb_usb_setfeature, NULL);
+	enum_register_callback(ENUM_CLRFEATURE, cb_usb_clrfeature, NULL);
+#endif
+
+	/* Register callbacks */
+	usb_event_enable(MAXUSB_EVENT_NOVBUS, cb_usb_event, NULL);
+	usb_event_enable(MAXUSB_EVENT_VBUS, cb_usb_event, NULL);
+
+	/* Start with USB in low power mode */
+	usb_app_sleep();
+	/* TODO: Fix priority */
+	NVIC_SetPriority(USB_IRQn, 6);
+	NVIC_EnableIRQ(USB_IRQn);
+
+	return 0;
+}
+
+static int cb_usb_setconfig(usb_setup_pkt *sud, void *cbdata)
+{
+	LOG_DEBUG("usb", "setconfig %d", sud->wValue);
+	if (sud->wValue == s_device.descriptors->config->bConfigurationValue) {
+		return device_configure();
+	} else if (sud->wValue == 0) {
+		device_deconfigure();
+		return 0;
+	}
+	return -1;
+}
+
+#ifdef USE_REMOTE_WAKE_ENABLE
+static int cb_usb_setfeature(usb_setup_pkt *sud, void *cbdata)
+{
+	if (sud->wValue == FEAT_REMOTE_WAKE) {
+		remote_wake_en = 1;
+		return 0;
+	}
+	return -1;
+}
+
+static int cb_usb_clrfeature(usb_setup_pkt *sud, void *cbdata)
+{
+	if (sud->wValue == FEAT_REMOTE_WAKE) {
+		remote_wake_en = 0;
+		return 0;
+	}
+	return -1;
+}
+#endif
+
+/******************************************************************************/
+static void usb_app_sleep(void)
+{
+	/* TODO: Place low-power code here */
+	suspended = 1;
+}
+
+/******************************************************************************/
+static void usb_app_wakeup(void)
+{
+	/* TODO: Place low-power code here */
+	suspended = 0;
+}
+
+static void cb_timerReset(TimerHandle_t t)
+{
+	(void)t;
+	int s = usb_get_status();
+	LOG_DEBUG("usb", "cb_timerReset %08x", s);
+
+	LOG_DEBUG("usb", "SYS_USBHS_Shutdown");
+	SYS_USBHS_Shutdown();
+	LOG_DEBUG("usb", "SYS_Reset_Periph");
+	SYS_Reset_Periph(SYS_RESET_USB);
+
+	//copy-paste from esb_init(), need to refactor
+	maxusb_cfg_options_t usb_opts;
+	usb_opts.enable_hs         = 0;
+	usb_opts.delay_us          = delay_us;
+	usb_opts.init_callback     = cb_usb_init;
+	usb_opts.shutdown_callback = cb_usb_shutdown;
+
+	/* Initialize the usb module */
+	if (usb_init(&usb_opts) != 0) {
+		LOG_ERR("usb", "usb_init() failed");
+		return;
+	}
+	usb_event_enable(MAXUSB_EVENT_NOVBUS, cb_usb_event, NULL);
+	usb_event_enable(MAXUSB_EVENT_VBUS, cb_usb_event, NULL);
+}
+
+static void scheduleReset()
+{
+	LOG_DEBUG("usb", "scheduleReset");
+	xTimerChangePeriodFromISR(timerWakeup, pdMS_TO_TICKS(500), NULL);
+}
+
+#if LOG_ENABLE_DEBUG
+static const char *maxusb_event_string(maxusb_event_t evt)
+{
+	static const char *names[MAXUSB_NUM_EVENTS] = {
+		[MAXUSB_EVENT_DPACT]  = "DPACT",
+		[MAXUSB_EVENT_RWUDN]  = "RWUDN",
+		[MAXUSB_EVENT_BACT]   = "BACT",
+		[MAXUSB_EVENT_BRST]   = "BRST",
+		[MAXUSB_EVENT_SUSP]   = "SUSP",
+		[MAXUSB_EVENT_NOVBUS] = "NOVBUS",
+		[MAXUSB_EVENT_VBUS]   = "VBUS",
+		[MAXUSB_EVENT_BRSTDN] = "BRSTDN",
+		[MAXUSB_EVENT_SUDAV]  = "SUDAV",
+	};
+	return evt < MAXUSB_NUM_EVENTS ? names[evt] : "<INVALID>";
+}
+#endif
+
+/******************************************************************************/
+static int cb_usb_event(maxusb_event_t evt, void *data)
+{
+	static int s_VBUS_SUSP_sequence = 0;
+	LOG_DEBUG("usb", "event %s (%d)\n", maxusb_event_string(evt), evt);
+
+	switch (evt) {
+	case MAXUSB_EVENT_NOVBUS:
+		usb_event_disable(MAXUSB_EVENT_BRST);
+		usb_event_disable(MAXUSB_EVENT_SUSP);
+		usb_event_disable(MAXUSB_EVENT_DPACT);
+		usb_disconnect();
+		device_deconfigure();
+		usb_app_sleep();
+		break;
+	case MAXUSB_EVENT_VBUS:
+		s_VBUS_SUSP_sequence = 1;
+		usb_event_clear(MAXUSB_EVENT_BRST);
+		usb_event_enable(MAXUSB_EVENT_BRST, cb_usb_event, NULL);
+		usb_event_clear(MAXUSB_EVENT_SUSP);
+		usb_event_enable(MAXUSB_EVENT_SUSP, cb_usb_event, NULL);
+		connect();
+		usb_app_sleep();
+		break;
+	case MAXUSB_EVENT_BRST:
+		usb_app_wakeup();
+		device_deconfigure();
+		suspended = 0;
+		break;
+	case MAXUSB_EVENT_SUSP:
+		if (!s_VBUS_SUSP_sequence) {
+			scheduleReset();
+		}
+		s_VBUS_SUSP_sequence = 0;
+		//usb_app_sleep();
+		break;
+	case MAXUSB_EVENT_DPACT:
+		usb_app_wakeup();
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
diff --git a/epicardium/usb/epc_usb.h b/epicardium/usb/epc_usb.h
new file mode 100644
index 00000000..c6917bd3
--- /dev/null
+++ b/epicardium/usb/epc_usb.h
@@ -0,0 +1,32 @@
+#ifndef EPICARDIUM_USB_EPC_USB_H_INCLUDED
+#define EPICARDIUM_USB_EPC_USB_H_INCLUDED
+
+/**
+ * 
+ * EPC - it's not just a Universal Serial Bus,
+ * it's an Epic Serial Bus!
+ */
+
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct esb_config;
+typedef int (*esb_cfg_handler)(struct esb_config* self);
+
+struct esb_config {
+    const char* name;
+    esb_cfg_handler init;
+    esb_cfg_handler configure;
+    esb_cfg_handler deconfigure;
+    esb_cfg_handler deinit;
+
+    struct esb_device_descriptors* descriptors;
+    void* deviceData;
+};
+
+int esb_init(struct esb_config* cfg);
+void esb_deinit(void);
+
+#endif//EPICARDIUM_USB_EPC_USB_H_INCLUDED
diff --git a/epicardium/usb/mass_storage.c b/epicardium/usb/mass_storage.c
new file mode 100644
index 00000000..b3c6c0f6
--- /dev/null
+++ b/epicardium/usb/mass_storage.c
@@ -0,0 +1,55 @@
+/**
+ * backend-independent implementation of the mass storage device
+ * services MSC API and esb_cfg API
+ */
+
+#include "usb/mass_storage.h"
+#include "usb/epc_usb.h"
+#include "usb/descriptors.h"
+
+#include "modules/log.h"
+
+#include "msc.h"
+#include "usb.h"
+#include "max32665.h"
+
+#define VENDOR_STRING "CCC"
+#define PRODUCT_STRING "card10"
+#define VERSION_STRING "1.0"
+
+static inline struct config_descriptor_msc *descriptors(struct esb_config *self)
+{
+	return self->descriptors->msc;
+}
+
+int esb_msc_configure(struct esb_config *self)
+{
+	LOG_DEBUG("msc", "configure");
+	struct config_descriptor_msc *dsc = descriptors(self);
+	const msc_cfg_t msc_cfg           = {
+                dsc->endpoint_out.bEndpointAddress,
+                MXC_USBHS_MAX_PACKET, /* OUT max packet size */
+                dsc->endpoint_in.bEndpointAddress & 0x0f,
+                MXC_USBHS_MAX_PACKET, /* IN max packet size */
+	};
+	return msc_configure(&msc_cfg);
+}
+
+int esb_msc_deconfigure(struct esb_config *self)
+{
+	LOG_DEBUG("msc", "deconfigure");
+	(void)self;
+	return msc_deconfigure();
+}
+
+int esb_msc_init(struct esb_config *self)
+{
+	LOG_DEBUG("msc", "init");
+	static const msc_idstrings_t ids = { VENDOR_STRING,
+					     PRODUCT_STRING,
+					     VERSION_STRING };
+
+	msc_mem_t *mem                    = self->deviceData;
+	struct config_descriptor_msc *dsc = descriptors(self);
+	return msc_init(&dsc->msc_interface, &ids, mem);
+}
diff --git a/epicardium/usb/mass_storage.h b/epicardium/usb/mass_storage.h
new file mode 100644
index 00000000..77c36bce
--- /dev/null
+++ b/epicardium/usb/mass_storage.h
@@ -0,0 +1,10 @@
+#ifndef EPICARDIUM_USB_MSC_H_INCLUDED
+#define EPICARDIUM_USB_MSC_H_INCLUDED
+
+#include "usb/epc_usb.h"
+
+int esb_msc_configure(struct esb_config* self);
+int esb_msc_deconfigure(struct esb_config* self);
+int esb_msc_init(struct esb_config* self);
+
+#endif//EPICARDIUM_USB_MSC_H_INCLUDED
-- 
GitLab