m-stack updated. assume it works but will test later.
author"Darron Broad" <darron@kewl.org>
Sat, 18 Feb 2017 16:25:38 +0000
changeset 18 f73c91611afb
parent 17 96d6863a1180
child 19 55aab162d711
m-stack updated. assume it works but will test later.
cdcacm.c
cdcacm.h
usb/include/usb.h
usb/include/usb_cdc.h
usb/include/usb_hid.h
usb/include/usb_msc.h
usb/src/usb.c
usb/src/usb_cdc.c
usb/src/usb_hid.c
usb/src/usb_msc.c
usb/src/usb_priv.h
usb/usb_config.h
--- a/cdcacm.c	Sat Feb 18 16:14:32 2017 +0000
+++ b/cdcacm.c	Sat Feb 18 16:25:38 2017 +0000
@@ -118,7 +118,7 @@
 }
 #endif
 
-void
+int8_t
 app_set_line_coding_callback(uint8_t interface, const struct cdc_line_coding *coding)
 {
 }
--- a/cdcacm.h	Sat Feb 18 16:14:32 2017 +0000
+++ b/cdcacm.h	Sat Feb 18 16:25:38 2017 +0000
@@ -44,7 +44,7 @@
 void app_set_comm_feature_callback(uint8_t, bool, bool);
 void app_clear_comm_feature_callback(uint8_t, bool, bool);
 int8_t app_get_comm_feature_callback(uint8_t, bool *, bool *);
-void app_set_line_coding_callback(uint8_t, const struct cdc_line_coding *);
+int8_t app_set_line_coding_callback(uint8_t, const struct cdc_line_coding *);
 int8_t app_get_line_coding_callback(uint8_t, struct cdc_line_coding *);
 int8_t app_set_control_line_state_callback(uint8_t, bool, bool);
 int8_t app_send_break_callback(uint8_t, uint16_t);
--- a/usb/include/usb.h	Sat Feb 18 16:14:32 2017 +0000
+++ b/usb/include/usb.h	Sat Feb 18 16:25:38 2017 +0000
@@ -522,14 +522,38 @@
  *
  * This is the callback function type expected to be passed to @p
  * usb_start_receive_ep0_data_stage() and @p usb_send_data_stage().
- * Callback functions will be called by the stack when the event for which
- * they are registered occurs.
  *
- * @param transfer_ok   @a true if transaction completed successfully, or
- *                      @a false if there was an error
+ * For OUT transfers with data, the callback function will be called by the
+ * stack when the data stage of the transfer completes. For this type of
+ * transfer, the callback can return -1 or 0:
+ * -1: The application has rejected the data. This will cause the stack to
+ *     send a STALL to the host.
+ *  0: The application has accepted and processed the data. This will cause
+ *     the stack to send a zero-length packet (indicating success) to be sent
+ *     as the status stage.
+ *
+ * For OUT transfers without data (which do not have a data stage), the
+ * callback function will be called when the status stage of the transfer
+ * completes; the return value is ignored.
+ *
+ * For IN transfers, the callback function will be called by the stack when
+ * the status stage of the transfer completes; the return value is ignored.
+ *
+ * Note that the functionality is different for different types of transfers.
+ * The callback gets called at the place which which has the most meaning for
+ * each type of transfer.
+ *
+ * @param data_ok       True if transaction(s) completed successfully, or
+ *                      false if there was an error
  * @param context       A pointer to application-provided context data
+ *
+ * @returns
+ *   For OUT transfers with data, -1 or 0 can be returned as described.
+ *
+ *   For IN transfers and for OUT transfers without data, the return value
+ *   is ignored.
  */
-typedef void (*usb_ep0_data_stage_callback)(bool transfer_ok, void *context);
+typedef int8_t (*usb_ep0_data_stage_callback)(bool data_ok, void *context);
 
 /** @brief Start the data stage of an OUT control transfer
  *
--- a/usb/include/usb_cdc.h	Sat Feb 18 16:14:32 2017 +0000
+++ b/usb/include/usb_cdc.h	Sat Feb 18 16:25:38 2017 +0000
@@ -356,8 +356,12 @@
  *                       the idle setting.
  * @param data_multiplexed_state  Whether to set the data multiplexed
  *                                state. True = clear the multiplexed state.
+ *
+ * @returns
+ *   Return 0 if the request can be handled or -1 if it cannot. Returning -1
+ *   will cause STALL to be returned to the host.
  */
-extern void CDC_SET_COMM_FEATURE_CALLBACK(uint8_t interface,
+extern int8_t CDC_SET_COMM_FEATURE_CALLBACK(uint8_t interface,
                                             bool idle_setting,
                                             bool data_multiplexed_state);
 #endif
@@ -376,10 +380,14 @@
  *                       the idle setting.
  * @param data_multiplexed_state  Whether to clear the data multiplexed
  *                                state. True = clear the multiplexed state.
+ *
+ * @returns
+ *   Return 0 if the request can be handled or -1 if it cannot. Returning -1
+ *   will cause STALL to be returned to the host.
  */
-extern void CDC_CLEAR_COMM_FEATURE_CALLBACK(uint8_t interface,
-                                            bool idle_setting,
-                                            bool data_multiplexed_state);
+extern int8_t CDC_CLEAR_COMM_FEATURE_CALLBACK(uint8_t interface,
+                                              bool idle_setting,
+                                              bool data_multiplexed_state);
 #endif
 
 #ifdef CDC_GET_COMM_FEATURE_CALLBACK
@@ -416,8 +424,11 @@
  * @param interface      The interface for which the command is intended
  * @param coding         The new line coding set by the host
  *
+ * @returns
+ *   Return 0 if the request can be handled or -1 if it cannot. Returning -1
+ *   will cause STALL to be returned to the host.
  */
-extern void CDC_SET_LINE_CODING_CALLBACK(uint8_t interface,
+extern int8_t CDC_SET_LINE_CODING_CALLBACK(uint8_t interface,
                                          const struct cdc_line_coding *coding);
 #endif
 
--- a/usb/include/usb_hid.h	Sat Feb 18 16:14:32 2017 +0000
+++ b/usb/include/usb_hid.h	Sat Feb 18 16:25:38 2017 +0000
@@ -186,8 +186,8 @@
  *                       does not dereference this pointer.
  *
  * @returns
- *   Return 0 if the request can be handled or -1 if it cannot. Returning -1
- *   will cause STALL to be returned to the host.
+ *   Return the length of the report or -1 if the request is invalid.
+ *   Returning -1 will cause STALL to be returned to the host.
  */
 extern int16_t HID_GET_REPORT_CALLBACK(uint8_t interface, uint8_t report_type,
                                        uint8_t report_id, const void **report,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usb/include/usb_msc.h	Sat Feb 18 16:25:38 2017 +0000
@@ -0,0 +1,798 @@
+/*
+ *  M-Stack USB Mass Storage Device Class Structures
+ *  Copyright (C) 2014 Alan Ott <alan@signal11.us>
+ *  Copyright (C) 2014 Signal 11 Software
+ *
+ *  2014-06-04
+ *
+ *  M-Stack is free software: you can redistribute it and/or modify it under
+ *  the terms of the GNU Lesser General Public License as published by the
+ *  Free Software Foundation, version 3; or the Apache License, version 2.0
+ *  as published by the Apache Software Foundation.  If you have purchased a
+ *  commercial license for this software from Signal 11 Software, your
+ *  commerical license superceeds the information in this header.
+ *
+ *  M-Stack is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ *  License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this software.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  You should have received a copy of the Apache License, verion 2.0 along
+ *  with this software.  If not, see <http://www.apache.org/licenses/>.
+ */
+
+#ifndef USB_MSC_H__
+#define USB_MSC_H__
+
+/** @file usb_msc.h
+ *  @brief USB Mass Storage Class Enumerations and Structures
+ *  @defgroup public_api Public API
+ */
+
+/** @addtogroup public_api
+ *  @{
+ */
+
+#include <stdint.h>
+#include "usb_config.h"
+
+#if defined(__XC16__) || defined(__XC32__)
+#pragma pack(push, 1)
+#elif __XC8
+#else
+#error "Compiler not supported"
+#endif
+
+/** @defgroup msc_items USB MSC Class Enumerations and Descriptors
+ *  @brief Packet structs, constants, and callback functions implementing
+ *  the "Universal Serial Bus Mass Storage Class", revision 1.4, and the
+ *  "Universal Serial Bus Mass Storage Class Bulk-Only Transport", revision
+ *  1.0.
+ *
+ *  An indespensible reference is Jan Axelson's "USB Mass Storage" book.
+ *  The major value in this book is the real-life perspective regarding
+ *  which parts of the SCSI standards are actually used in USB devices, what
+ *  they are used for, and how the SCSI commands and terminology map onto USB
+ *  devices.
+ *
+ *  Document sections listed in the comments are prefixed with the document
+ *  they reference as follows:\n
+ *  \b MSCO: USB Mass Storage Class, revision 1.4\n
+ *  \b BOT:  USB Mass Storage Class Bulk-Only Transport, revision 1.0\n
+ *  \b Axelson: Jan Axelson's "USB Mass Storage" book\n
+ *
+ *  For more information, see the above referenced document, available from
+ *  http://www.usb.org .
+ *  @addtogroup msc_items
+ *  @{
+ */
+
+#define MSC_DEVICE_CLASS 0x08 /* http://www.usb.org/developers/defined_class */
+#define MSC_SCSI_TRANSPARENT_COMMAND_SET_SUBCLASS 0x06
+/* Many of the subclass codes (MSCO: sec 2) are omitted here. Get in
+ * contact with Signal 11 if you need something specific.  */
+
+#define MSC_PROTOCOL_CODE_BBB 0x50 /* Bulk-Only */
+/* Many of the protocol codes (MSCO: sec 3) are omitted here. Get in
+ * contact with Signal 11 if you need something specific.  */
+
+#if MSC_MAX_LUNS_PER_INTERFACE <= 8
+	typedef uint8_t msc_lun_mask_t;
+#else
+	typedef uint16_t msc_lun_mask_t;
+#endif
+
+
+/** MSC Class Requests
+ *
+ * These are the class requests needed for MSC Bulk-Only Transport (MSCO:
+ * sec 4, table 3).  Others are omitted.  Get in contact with Signal 11 if
+ * you need something specific.
+ */
+enum MSCRequests {
+	MSC_GET_MAX_LUN = 0xfe,
+	MSC_BULK_ONLY_MASS_STORAGE_RESET = 0xff,
+};
+
+/** MSC Command Block Status Values
+ *
+ * See BOT, 5.2
+ */
+enum MSCStatus {
+	MSC_STATUS_PASSED = 0, /* Success */
+	MSC_STATUS_FAILED = 1,
+	MSC_STATUS_PHASE_ERROR = 2,
+};
+
+/** MSC Command Block Flags (Direction is the only used bit)
+ *
+ * See BOT, 5.1
+ */
+enum MSCDirection {
+	MSC_DIRECTION_OUT = 0x0,
+	MSC_DIRECTION_IN_BIT = 0x80,
+};
+
+/** MSC Bulk-Only Data Command Block Wrapper
+ *
+ * See BOT, 5.1
+ */
+struct msc_command_block_wrapper {
+	uint32_t dCBWSignature; /* Set to 0x43425355 */
+	uint32_t dCBWTag;
+	uint32_t dCBWDataTransferLength; /** Data to be transfered */
+	uint8_t bmCBWFlags; /** bit 0x80=data-in, 0x00=data-out */
+	uint8_t bCBWLUN; /**< Lower 4 bits only */
+	uint8_t bCBWCBLength ; /**< Lower 4 bits only; length of CBWCB */
+	uint8_t CBWCB[16];
+};
+
+/** MSC Bulk-Only Data Command Block Wrapper
+ *
+ * See BOT, 5.2
+ */
+struct msc_command_status_wrapper {
+	uint32_t dCSWSignature; /**< Set to 0x53425355 */
+	uint32_t dCSWTag;
+	uint32_t dCSWDataResidue;
+	uint8_t  bCSWStatus; /**< @see enum MSCStatus */
+};
+
+/* SCSI Definitions and Structures */
+
+enum MSCSCSICommands {
+	MSC_SCSI_FORMAT_UNIT = 0x04,
+	MSC_SCSI_INQUIRY = 0x12,
+	MSC_SCSI_MODE_SELECT_6 = 0x15,
+	MSC_SCSI_MODE_SELECT_10 = 0x55,
+	MSC_SCSI_MODE_SENSE_6 = 0x1a,
+	MSC_SCSI_MODE_SENSE_10 = 0x5a,
+	MSC_SCSI_START_STOP_UNIT = 0x1b,
+	MSC_SCSI_READ_6 = 0x08,
+	MSC_SCSI_READ_10 = 0x28,
+	MSC_SCSI_READ_CAPACITY_10 = 0x25,
+	MSC_SCSI_REPORT_LUNS = 0xa0,
+	MSC_SCSI_REQUEST_SENSE = 0x03,
+	MSC_SCSI_SEND_DIAGNOSTIC = 0x1d,
+	MSC_SCSI_TEST_UNIT_READY = 0x00,
+	MSC_SCSI_VERIFY = 0x2f,
+	MSC_SCSI_WRITE_6 = 0x0a,
+	MSC_SCSI_WRITE_10 = 0x2a,
+};
+
+struct msc_scsi_inquiry_command {
+	uint8_t operation_code; /* 0x12 */
+	uint8_t evpd; /* bit 0 only */
+	uint8_t page_code;
+	uint16_t allocation_length;
+	uint8_t control;
+};
+
+struct msc_scsi_request_sense_command {
+	uint8_t operation_code; /* 0x3 */
+	uint8_t desc; /* bit 0 only */
+	uint8_t reserved[2];
+	uint8_t allocation_length;
+	uint8_t control;
+};
+
+struct msc_scsi_mode_sense_6_command {
+	uint8_t operation_code; /* 0x1a */
+	uint8_t dbd_reserved;
+	uint8_t pc_page_code; /* bits 6-7: PC, bits 0-5: Page Code */
+	uint8_t subpage_code;
+	uint8_t allocation_length;
+	uint8_t control;
+};
+
+struct msc_scsi_start_stop_unit {
+	uint8_t operation_code;
+	uint8_t immed; /* Bit 0 only */
+	uint8_t reserved[2];
+	uint8_t command; /* bit 0: start, bit 1: LOEJ, bits 4-7: power cond */
+	uint8_t control;
+};
+
+struct msc_scsi_read_10_command {
+	uint8_t opcode;
+	uint8_t unused_flags;
+	uint32_t logical_block_address;
+	uint8_t group_number;
+	uint16_t transfer_length;
+	uint8_t control_flags;
+};
+
+struct msc_scsi_write_10_command {
+	uint8_t operation_code;
+	uint8_t wrprotect_flags;
+	uint32_t logical_block_address;
+	uint8_t group_number;
+	uint16_t transfer_length;
+	uint8_t control;
+};
+
+enum MSCSCSIVersion {
+	MSC_SCSI_SPC_VERSION_2 = 4,
+	MSC_SCSI_SPC_VERSION_3 = 5,
+};
+
+
+struct scsi_inquiry_response {
+	uint8_t peripheral; /**< Set to 0x0 */
+	uint8_t rmb; /**< 0x80 for removable media */
+	uint8_t version; /**< enum MSCSCSIVersion */
+	uint8_t response_data_format; /**< Set to 0x2 */
+	uint8_t additional_length; /**< 4 less than sizeof(inquiry_response) */
+	uint8_t unused[3]; /**< Not used in our implementation */
+	char vendor[8]; /**< ASCII, no zero-termination */
+	char product[16]; /**< ASCII, no zero-termination */
+	char revision[4]; /**< ASCII, no zero-termination */
+};
+
+struct scsi_capacity_response {
+	uint32_t last_block;
+	uint32_t block_length;
+};
+
+enum SCSISenseResponseCode {
+	SCSI_SENSE_CURRENT_ERRORS = 0x70,
+	SCSI_SENSE_DEFERRED_ERRORS = 0x71,
+	SCSI_SENSE_INFORMATION_VALID = 0x80,
+};
+
+enum SCSISenseFlags {
+	SCSI_SENSE_FILEMARK = 0x80,
+	SCSI_SENSE_EOM = 0x40,
+	SCSI_SENSE_ILI = 0x20,
+	SCSI_SENSE_KEY_MASK = 0x0f,
+};
+
+struct scsi_mode_sense_response {
+	uint8_t mode_data_length; /**< sizeof(scsi_mode_sense_response) - 1 */
+	uint8_t medium_type; /**< Set to 0x00 for SBC devices */
+	uint8_t device_specific_parameter; /**< set to 0x80 for write-protect, else 0x0 */
+	uint8_t block_descriptor_length; /**< Set to 0x0 */
+};
+
+enum SCSISenseKeys {
+	SCSI_SENSE_KEY_NOT_READY = 0x2,
+	SCSI_SENSE_KEY_MEDIUM_ERROR = 0x3,
+	SCSI_SENSE_KEY_ILLEGAL_REQUEST = 0x5,
+	SCSI_SENSE_KEY_UNIT_ATTENTION = 0x6,
+	SCSI_SENSE_KEY_DATA_PROTECT = 0x7,
+};
+
+enum SCSIAdditionalSenseCodes {
+	/* ILLEGAL_REQUEST */
+	SCSI_ASC_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE = 0x21,
+	SCSI_ASC_INVALID_COMMAND_OPERATION_CODE = 0x20,
+	SCSI_ASC_INVALID_FIELD_IN_COMMAND_PACKET = 0x24,
+	SCSI_ASC_LOGICAL_UNIT_NOT_SUPPORTED = 0x25,
+
+	/* MEDIUM_ERROR */
+	SCSI_ASC_PERIPHERAL_DEVICE_WRITE_FAULT = 0x03,
+	SCSI_ASC_UNRECOVERED_READ_ERROR = 0x11,
+
+	/* UNIT_ATTENTION */
+	SCSI_ASC_WRITE_ERROR = 0x0c,
+	SCSI_ASC_MEDIUM_NOT_PRESENT = 0x3a,
+
+	/* WRITE_DATA_PROTECT */
+	SCSI_ASC_WRITE_PROTECTED = 0x27,
+};
+
+struct scsi_sense_response {
+	uint8_t response_code; /**< 0x70=current_errors, 0x71=deferred_errors
+	                         *  0x80 bit = information_valid */
+	uint8_t obsolete;
+	uint8_t flags; /**< Bitmask of SCSISenseFlags OR'd with one from SCSISenseKeys */
+	uint32_t information;
+	uint8_t additional_sense_length;
+	uint32_t command_specific_information;
+	uint8_t additional_sense_code; /**< SCSIAdditionalSenseCodes */
+	uint8_t additional_sense_code_qualifier;
+	uint8_t field_replaceable_unit_code;
+	uint8_t sense_key_specific[3];
+	/* Additional, vendor-specific sense data goes here. */
+};
+
+#if defined(__XC16__) || defined(__XC32__)
+#pragma pack(pop)
+#elif __XC8
+#else
+#error "Compiler not supported"
+#endif
+
+
+/** MSC Application States
+ *
+ * The states the transport goes through.
+ */
+enum MSCApplicationStates {
+	MSC_IDLE,
+	MSC_DATA_TRANSPORT_IN,  /**< Next transactions will be data sent */
+	MSC_DATA_TRANSPORT_OUT, /**< Next transactions will be data received */
+	MSC_STALL,              /**< Next transaction needs to stall */
+	MSC_CSW,                /**< Next transaction will contain the CSW */
+	MSC_NEEDS_RESET_RECOVERY, /**< Reset recovery is required */
+};
+
+/** Return codes used by application callbacks.
+ *
+ * Success is defined to be 0 and all failures are < 0.
+ */
+enum MSCReturnCodes {
+	MSC_SUCCESS                  =  0,
+	MSC_ERROR_MEDIUM_NOT_PRESENT = -1, /**< The medium is not physically present */
+	MSC_ERROR_INVALID_LUN        = -2, /**< The LUN is out of range */
+	MSC_ERROR_INVALID_ADDRESS    = -3, /**< The LBA address is out of range */
+	MSC_ERROR_WRITE_PROTECTED    = -4, /**< The medium is write protected */
+	MSC_ERROR_READ               = -5, /**< Error while reading the medium */
+	MSC_ERROR_WRITE              = -6, /**< Error while writing the medium */
+	MSC_ERROR_MEDIUM             = -7, /**< Unspecified medium error */
+};
+
+/* Forward declare struct msc_application_data to enable the following:
+ * 1. The struct be used by the callback,
+ * 2. The callback can be used by the struct. */
+struct msc_application_data;
+
+/** @brief MSC Transmission Complete Callback
+ *
+ * This is the callback function type expected to be passed to @p
+ * msc_start_send_to_host(). Callback functions will be called by the stack
+ * when the transmission of a block of data has completed.
+ *
+ * @param app_data      Pointer to application data for this interface.
+ * @param transfer_ok   @a true if transaction completed successfully, or
+ *                      @a false if there was an error
+ */
+typedef void (*msc_completion_callback) (struct msc_application_data *app_data,
+                                         bool transfer_ok);
+
+/** MSC Applicaiton Data
+ *
+ * The application shall provide one of these structures for each interface
+ * when the MSC class is initialized and keep it as a persistent object for
+ * the lifetime of the application. Global variables work well for this.
+ *
+ * The Application shall initialize the variables in the first section. The
+ * variables in the second section are used by the MSC class to keep track of
+ * the state of the connection.
+ *
+ * The application will pass this structure to the MSC class whenever it needs
+ * the MSC class to process data for an interface.
+ */
+struct msc_application_data {
+	/* Application should initialize the following: */
+	uint8_t interface;
+	uint8_t max_lun; /**< The maximally numbered LUN. One-less than the number of LUNs. */
+	uint8_t in_endpoint;
+	uint8_t out_endpoint;
+	uint8_t in_endpoint_size; /**< Size in bytes for IN endpoint */
+	msc_lun_mask_t media_is_removable_mask; /**< bitmask, one bit for each LUN */
+	const char *vendor; /**< SCSI-assigned vendor. Pointer to global or constant. */
+	const char *product; /**< Pointer to global or constant. */
+	const char *revision; /**< Pointer to global or constant. */
+
+	/* MSC Class handler will initialize and use the following. The
+	 * applicaiton should ignore these: */
+	uint8_t state; /**< enum MSCApplicationStates */
+	uint32_t current_tag;
+	/* Error-reporting codes */
+	uint8_t sense_key;
+	uint8_t additional_sense_code;
+	/* CSW fields */
+	uint32_t residue;
+	uint8_t status; /**< enum MSCStatus */
+	/* READ command handling fields */
+	uint32_t requested_bytes; /* Bytes requested by SCSI */
+	uint32_t requested_bytes_cbw; /* Bytes requested by the MSC (CBW) */
+	uint32_t transferred_bytes;
+	/* Block size for each LUN */
+	uint32_t block_size[MSC_MAX_LUNS_PER_INTERFACE];
+
+	/* Asynchronous transmit/receive data */
+	union {
+		const uint8_t *tx_buf; /**< Data to be sent to the host. */
+		uint8_t *rx_buf;       /**< Data received from the host. */
+	};
+	uint16_t tx_len_remaining; /**< TX data remaining in the current block */
+#ifdef MSC_WRITE_SUPPORT
+	uint8_t *rx_buf_cur; /**< Current position in the RX buffer */
+	size_t rx_buf_len;   /**< Length of the application's block RX buffer */
+	/* Endpoint buffer management. */
+	uint8_t out_ep_missed_transactions; /**< Number of out transactions not processed */
+#endif
+	msc_completion_callback operation_complete_callback;
+};
+
+/** Initialize the MSC class for all interfaces
+ *
+ * Initialize all instances of the MSC class. Call this function with an
+ * array containing an @class msc_application_data for each
+ * MSC interface. The @p app_data pointer should point to an array of valid
+ * @class msc_application_data structures which have been filled out properly.
+ *
+ * The array should be valid for the lifetime of the application. Global
+ * variables work well for this.
+ *
+ * @param app_data  Pointer to an array of application data structures
+ * @param count     Number of structures (size of array) in app_data
+ *
+ * @returns
+ *   Returns 0 on success, or -1 if some of the data is invalid. On a -1
+ *   return, assume that none of the interfaces have been initialized
+ *   properly and fail.
+ */
+uint8_t msc_init(struct msc_application_data *app_data, uint8_t count);
+
+/** Process MSC Setup Request
+ *
+ * Process a setup request which has been unhandled as if it is potentially
+ * an MSC setup request. This function will then call appropriate callbacks
+ * into the appliction if the setup packet is one recognized by the MSC
+ * specification.
+ *
+ * @param setup          A setup packet to handle
+ *
+ * @returns
+ *   Returns 0 if the setup packet could be processed or -1 if it could not.
+ */
+int8_t process_msc_setup_request(const struct setup_packet *setup);
+
+/** Clear Halt on MSC Endpoint
+ *
+ * Notify the MSC class that a CLEAR_FEATURE ENDPOINT_HALT has been
+ * received for an MSC endpoint.
+ *
+ * @param endpoint_num    The endpoint number affected
+ * @param direction       The endpoint direction affected. 0=out, 1=in
+ *
+ */
+void msc_clear_halt(uint8_t endpoint_num, uint8_t direction);
+
+/** Notify of a transaction completing on the Data-IN endpoint
+ *
+ * Notify the MSC class that an IN transaction has completed on the Data-IN
+ * endpoint.  If using interrupts, call this function from the
+ * @p IN_TRANSACTION_COMPLETE_CALLBACK.
+ *
+ * This function will not block.
+ *
+ * @param endpoint_num    The endpoint number affected
+ */
+void msc_in_transaction_complete(uint8_t endpoint_num);
+
+/** Notify of a transaction completing on the Data-OUT endpoint
+ *
+ * Notify the MSC class that an OUT transaction has completed on the
+ * Data-OUT endpoint.  This function will process the data which is
+ * available on endpoint @p endpoint_num and will re-arm the endpoint when
+ * appropriate.
+ *
+ * If using interrupts, call this function from the
+ * @p OUT_TRANSACTION_CALLBACK.
+ *
+ * This function will not block.
+ *
+ * @param endpoint_num    The endpoint number affected
+ */
+void msc_out_transaction_complete(uint8_t endpoint_num);
+
+/** Send data read from the medium to the host
+ *
+ * Start transmission of data that has been read from the storage medium to
+ * the host. This function simply starts the transmission and returns control
+ * immediately to the caller without blocking. When the transmission has
+ * completed, the @p completion_callback will be called. The application
+ * should call this function as soon as data is available from the storage
+ * medium, and should not call it again until the @p completion_callback has
+ * been called. For example, if 8 blocks are requested by the host, the
+ * application could read a single block, call @p msc_start_send_to_host(),
+ * wait for the @p completion_callback to be called, then call
+ * @p msc_start_send_to_host() with the second block, and so on..
+ *
+ * This function does not block.
+ *
+ * @p completion_callback will be called from interrupt context and must
+ * not block.
+ *
+ * @p len needs to be multiple of the IN endpoint size for all calls to
+ * this function except the last in response to an @p MSC_READ() callback.
+ *
+ * @param app_data             Pointer to application data for this interface.
+ * @param data                 Pointer to the data to send.
+ * @param len                  Data length in bytes. Must be a multiple of
+ *                             the IN endpoint size.
+ * @param completion_callback  Pointer to a function which is called when
+ *                             the transmission has completed.
+ *
+ * @returns
+ *   Returns 0 if the transmission could be started or -1 if it could not.
+ */
+uint8_t msc_start_send_to_host(struct msc_application_data *app_data,
+                               const uint8_t *data, uint16_t len,
+                               msc_completion_callback completion_callback);
+
+/** Notify the MSC class that a Read Operation has Completed
+ *
+ * Tell the MSC class that a read operation has been completed with either
+ * success or failure.
+ *
+ * In the case of success, pass true to @p passed, indicating that the data
+ * requested by an @p MSC_READ() callback has all been passed to the MSC
+ * class using @p msc_start_send_to_host().  This will cause the transfer to
+ * complete successfully.
+ *
+ * If the read failed, pass false to @p passed.  This will cause a SCSI
+ * MEDIUM_ERROR to be returned to the host.
+ *
+ * @param app_data       Pointer to application data for this interface.
+ * @param passed         Whether the read operation completed successfully
+ */
+void msc_notify_read_operation_complete(
+                                    struct msc_application_data *app_data,
+                                    bool passed);
+
+#ifdef MSC_WRITE_SUPPORT
+/** Notify Write Data Handled
+ *
+ * Tell the MSC class that a block of data provided to a write callback
+ * has been handled, and that the buffer is no longer in use by the
+ * applicaiton. The MSC stack will now provide the next block to be written
+ * into the same buffer and call the callback provided to @p
+ * MSC_START_WRITE().
+ *
+ * Note that calling this function does not necessarily indicate that the
+ * data was successfully written to the medium. It only indicates that the
+ * application has received the buffer and dealt with it, and requests that
+ * the MSC stack put the next block of data into the buffer. It is possible
+ * the application does not know that a write operation has failed (or will
+ * fail) until the end of the write operation.
+ *
+ * Note that if there are OUT transfers pending (and waiting because the
+ * application is using the buffer), and if the application's buffer is
+ * sufficiently small, then the MSC stack could potentially call the @p
+ * msc_completion_callback() from this function. With small buffers, this is
+ * a typical case.
+ *
+ * If there was an error in handling the data, call @p
+ * msc_notify_write_operation_complete() instead to cancel the transport.
+ *
+ * @param app_data       Pointer to application data for this interface.
+ */
+void msc_notify_write_data_handled(struct msc_application_data *app_data);
+
+/** Notify Write Operation Complete
+ *
+ * Tell the MSC class that a write operation has completed, either with
+ * full success, with partial success, or with failure. The MSC stack can now
+ * complete the data transport and report the status to the host.
+ *
+ * In the case of a complete success, set @p passed to true, and set @p
+ * bytes_processed to the number of bytes which were asked for and written.
+ *
+ * In the case of a failure or a partial success, set @p passed to false
+ * and set @p bytes_processed to the number of bytes which were acutally
+ * written successfully.
+ *
+ * Pass true as @p passed if the write succeeded or false if it failed.
+ *
+ * @param app_data         Pointer to application data for this interface.
+ * @param passed           Whether the write operation fully completed
+ *                         successfully, writing all desired data.
+ * @param bytes_processed  The number of bytes successfully written. This may
+ *                         be fewer than the number of bytes asked for by the
+ *                         host.
+ */
+void msc_notify_write_operation_complete(struct msc_application_data *app_data,
+                                         bool passed,
+                                         uint32_t bytes_processed);
+#endif
+
+/** MSC Bulk-Only Mass Storage Reset callback
+ *
+ * The MSC class will call this function when a Bulk-Only Mass Storage Reset
+ * request has been received from the host.  This function should perform
+ * a reset of the device indicated by @p interfce and not return until this
+ * has been completed.
+ *
+ * @param interface      The interface for which the command is intended
+ *
+ * @returns
+ *   Return 0 if the request can be handled or -1 if it cannot. Returning -1
+ *   will cause STALL to be returned to the host.
+ */
+extern int8_t MSC_BULK_ONLY_MASS_STORAGE_RESET_CALLBACK(uint8_t interface);
+
+#ifdef MSC_GET_STORAGE_INFORMATION
+/** MSC Get Storage Information Callback
+ *
+ * The USB Stack will call this function when the host requests information
+ * about the storage device.
+ *
+ * SCSI (and therefore USB) drives are required to support LBA (Logical Block
+ * Addressing) which means every block on the drive can be addressed as an
+ * array of fixed-sized blocks.
+ *
+ * This function may be called more than once as data is requested by the
+ * host.
+ *
+ * @param app_data       Pointer to application data for this interface.
+ * @param lun            The Logical Unit Number (LUN) of the medium requested.
+ * @param block_size     The block size for this medium. This block size is
+ *                       used for both reading and writing. It should be a
+ *                       multiple of both the IN and OUT endpoint sizes, but
+ *                       does not strictly have to be. If it is not, the
+ *                       handling of reads and writes on the application side
+ *                       will likely be more complex. The block_size must be
+ *                       below 2^24.
+ * @param num_blocks     The number of blocks the medium contains.
+ * @param write_protect  Whether write-protection is enabled.
+ *
+ *
+ * @returns
+ *   Return a code from @p MSCReturnCodes. Returning non-success will cause
+ *   an error to be returned to the host.
+ */
+extern int8_t MSC_GET_STORAGE_INFORMATION(
+			const struct msc_application_data *app_data,
+			uint8_t lun,
+			uint32_t *block_size,
+			uint32_t *num_blocks,
+			bool *write_protect);
+#else
+#error "You must define MSC_GET_STORAGE_INFORMATION in your usb_config.h"
+#endif
+
+#ifdef MSC_UNIT_READY
+/** MSC Check if Medium is Ready Callback
+ *
+ * The USB Stack will call this function when the host requests information
+ * regarding whether the medium is ready or not.
+ *
+ * This function may be called more than once as data is requested by the
+ * host.
+ *
+ * @param app_data       Pointer to application data for this interface.
+ * @param lun            The Logical Unit Number (LUN) of the medium requested.
+ *
+ * @returns
+ *   Return a code from @p MSCReturnCodes. Returning non-success will cause
+ *   an error to be returned to the host.
+ */
+extern int8_t MSC_UNIT_READY(
+			const struct msc_application_data *app_data,
+			uint8_t lun);
+#else
+#error "You must define MSC_UNIT_READY in your usb_config.h"
+#endif
+
+#ifdef MSC_START_STOP_UNIT
+/** Start or Stop an MSC Unit
+ *
+ * The USB Stack will call this function when the host requests that a
+ * logical unit start or stop. This is mostly used for stopping and ejecting
+ * the medium.
+ *
+ * @param app_data       Pointer to application data for this interface.
+ * @param lun            The Logical Unit Number (LUN) of the medium requested.
+ * @param start          Whether to start or stop the medium. true means to
+ *                       start, and false means to stop the medium.
+ * @param load_eject     Whether to load or eject the medium. The desired
+ *                       behavior depends on the value of @p start. When
+ *                       load_eject is true, then the application should load
+ *                       media if start is true, or eject media if start is
+ *                       false.
+ *
+ * The four cases for @p start and @p load_eject:
+ * if (start  && load_eject):   load the medium
+ * if (start  && !load_eject):  neither load nor eject
+ * if (!start && load_eject):   eject the medium
+ * if (!start && !load_eject):  neither load nor eject
+ *
+ * @returns
+ *   Return a code from @p MSCReturnCodes. Returning non-success will cause
+ *   an error to be returned to the host.
+ */
+extern int8_t MSC_START_STOP_UNIT(
+			const struct msc_application_data *app_data,
+			uint8_t lun,
+			bool start,
+			bool load_eject);
+#else
+#error "You must define MSC_START_STOP_UNIT in your usb_config.h"
+#endif
+
+#ifdef MSC_START_READ
+/** MSC Read Callback
+ *
+ * The USB Stack will call this function when the host requests a read
+ * operation from the device. This callback will initiate reading data from
+ * the medium. The application will then call @p msc_start_send_to_host()
+ * repeatedly until all data has been given to the MSC class. At that point,
+ * the application must call @p msc_notify_read_operation_complete() to
+ * indicate that the Data-Transport is complete.
+ *
+ * See the documentation for @p msc_start_send_to_host() for additional
+ * restrictions.
+ *
+ * Note that this funciton must simply kick-off the reading/sending of the
+ * data and return quickly. In other words, this function must not block.
+ *
+ * @param app_data       Pointer to application data for this interface.
+ * @param lun            The Logical Unit Number (LUN) of the medium requested.
+ * @param lba_address    Logical Block Address to start reading from.
+ * @param num_blocks     Number of blocks to read.
+ *
+ * @returns
+ *   Return a code from @p MSCReturnCodes. Returning non-success will cause
+ *   an error to be returned to the host.
+ */
+extern int8_t MSC_START_READ(
+		struct msc_application_data *app_data,
+		uint8_t lun,
+		uint32_t lba_address,
+		uint16_t num_blocks);
+#else
+#error "You must define MSC_START_READ in your usb_config.h"
+#endif
+
+#ifdef MSC_WRITE_SUPPORT
+#ifdef MSC_START_WRITE
+/** MSC Write Callback
+ *
+ * The USB Stack will call this function when the host requests to write
+ * data to the device.  The application's implementation of this function
+ * will provide a buffer (@p buffer) where the USB stack will place the data
+ * as it is received.  Once the buffer is full, the USB stack will call the
+ * provided callback function (@p callback), notifying the application that
+ * it can begin processing the data (ie: writing it to the medium).  Once
+ * the buffer has been processed (and the application is done with the
+ * buffer), the application must then call @p
+ * msc_notify_block_write_complete() to notify the USB stack that it is
+ * ready for the next buffer-full of data.
+ *
+ * Note that this funciton must simply set any necessary state on the
+ * appliction side and return the requested data quickly. In other words,
+ * this function must not block.
+ *
+ * @param app_data       Pointer to application data for this interface.
+ * @param lun            The Logical Unit Number (LUN) of the medium requested.
+ * @param lba_address    Logical Block Address the data is intended for.
+ * @param num_blocks     Number of blocks which will eventually be written.
+ * @param buffer         The place to put the data from the USB bus
+ * @param buffer_len     The size of the buffer in bytes. It must be a
+ *                       multiple of the OUT endpoint size.
+ * @param callback       A function to be called when the data has been
+ *                       received from the host. It will be called from
+ *                       interrupt context and must not block.
+ *
+ * @returns
+ *   Return a code from @p MSCReturnCodes. Returning non-success will cause
+ *   an error to be returned to the host.
+ */
+extern int8_t MSC_START_WRITE(
+		struct msc_application_data *app_data,
+		uint8_t lun,
+		uint32_t lba_address,
+		uint16_t num_blocks,
+		uint8_t **buffer,
+		size_t *buffer_len,
+		msc_completion_callback *callback);
+#else
+#error "You must either define MSC_START_WRITE in your usb_config.h or make this MSC class read-only."
+#endif /* MSC_START_WRITE */
+#endif /* MSC_WRITE_SUPPORT */
+
+/* Doxygen end-of-group for msc_items */
+/** @}*/
+
+/* Doxygen end-of-group for public_api */
+/** @}*/
+
+#endif /* USB_MSC_H__ */
--- a/usb/src/usb.c	Sat Feb 18 16:14:32 2017 +0000
+++ b/usb/src/usb.c	Sat Feb 18 16:25:38 2017 +0000
@@ -750,6 +750,10 @@
 #endif
 }
 
+/* This function is only called in either:
+ *   1. a direct response to a SETUP packet, or
+ *   2. as a STATUS stage,
+ * hence the hard-coding of DTS to 1, which is appropriate in both cases. */
 static void send_zero_length_packet_ep0()
 {
 #ifdef PPB_EP0_IN
@@ -839,9 +843,12 @@
 	uint8_t bytes_to_send = MIN(len, EP_0_IN_LEN);
 	bytes_to_send = MIN(bytes_to_send, bytes_asked_for);
 	returning_short = len < bytes_asked_for;
-	copy_to_ep0_in_buf(ptr, bytes_to_send);
+	if (bytes_to_send > 0)
+		copy_to_ep0_in_buf(ptr, bytes_to_send);
 	ep0_data_stage_in_buffer = ((char*)ptr) + bytes_to_send;
 	ep0_data_stage_buf_remaining = MIN(bytes_asked_for, len) - bytes_to_send;
+	if (ep0_data_stage_buf_remaining == 0 && returning_short)
+		control_need_zlp = 1;
 
 	/* Send back the first transaction */
 	ep0_buf.flags |= EP_TX_DTS;
@@ -1171,7 +1178,7 @@
 	 * in progress and thus invalidates any IN transactions which were
 	 * pending for a previous control transfer. Cancel any of these IN
 	 * transactions which were pending. */
-#ifdef PPB_EP0_OUT
+#ifdef PPB_EP0_IN
 	/* For ping-pong mode on EP 0, note below that ppbi is the next
 	 * ping-pong buffer which would be written to, meaning that !ppbi is
 	 * the buffer which would have an IN transaction pending (if any).
@@ -1313,8 +1320,29 @@
 					reset_ep0_data_stage();
 				}
 				else {
-					/* The data stage has completed. Set up the status stage. */
-					send_zero_length_packet_ep0();
+					/* The data stage has completed. Notify the application
+					 * and set up the status stage accordingly as success
+					 * (zero-length packet) or failure (STALL). */
+					int8_t res = 0;
+
+					if (ep0_data_stage_callback)
+						res = ep0_data_stage_callback(1/*true*/, ep0_data_stage_context);
+
+					if (res < 0) {
+						/* The application has indicated failure of
+						 * some kind, so stall the transfer */
+						stall_ep0();
+					}
+					else {
+						/* Set up the successful status
+						 * stage. */
+						send_zero_length_packet_ep0();
+					}
+
+					/* Remove the callback pointer so it
+					 * is not called again at the end of
+					 * the status stage */
+					ep0_data_stage_callback = NULL;
 				}
 			}
 		}
@@ -1355,8 +1383,8 @@
 		if (ep0_data_stage_direc == 0/*OUT*/) {
 			/* An IN on the control endpoint with no data pending
 			 * and during an OUT transfer means the STATUS stage
-			 * of the control transfer has completed. Notify the
-			 * application, if applicable. */
+			 * of the control transfer has completed. If there
+			 * is still a callback, call it. */
 			if (ep0_data_stage_callback)
 				ep0_data_stage_callback(1/*true*/, ep0_data_stage_context);
 			reset_ep0_data_stage();
--- a/usb/src/usb_cdc.c	Sat Feb 18 16:14:32 2017 +0000
+++ b/usb/src/usb_cdc.c	Sat Feb 18 16:25:38 2017 +0000
@@ -81,37 +81,38 @@
 
 #if defined(CDC_SET_COMM_FEATURE_CALLBACK) || defined(CDC_CLEAR_COMM_FEATURE_CALLBACK)
 static uint8_t set_or_clear_request;
-static void set_or_clear_comm_feature_callback(bool transfer_ok, void *context)
+static int8_t set_or_clear_comm_feature_callback(bool transfer_ok, void *context)
 {
 	/* Only ABSTRACT_STATE is supported here. */
 
 	if (!transfer_ok)
-		return;
+		return -1;
 
 	bool idle_setting = (transfer_data.comm_feature & 1) != 0;
 	bool data_multiplexed_state = (transfer_data.comm_feature & 2) != 0;
 
 	if (set_or_clear_request == CDC_SET_COMM_FEATURE) {
-		CDC_SET_COMM_FEATURE_CALLBACK(transfer_interface,
-		                              idle_setting,
-		                              data_multiplexed_state);
+		return CDC_SET_COMM_FEATURE_CALLBACK(transfer_interface,
+		                                     idle_setting,
+		                                     data_multiplexed_state);
 	}
 	else {
 		/* request == CDC_CLEAR_COMM_FEATURE */
-		CDC_CLEAR_COMM_FEATURE_CALLBACK(transfer_interface,
-		                                idle_setting,
-		                                data_multiplexed_state);
+		return CDC_CLEAR_COMM_FEATURE_CALLBACK(transfer_interface,
+		                                       idle_setting,
+		                                       data_multiplexed_state);
 	}
+	return 0;
 }
 #endif
 
 #if defined(CDC_SET_LINE_CODING_CALLBACK)
-static void set_line_coding(bool transfer_ok, void *context) {
+static int8_t set_line_coding(bool transfer_ok, void *context) {
 	if (!transfer_ok)
-		return;
+		return -1;
 
-	CDC_SET_LINE_CODING_CALLBACK(transfer_interface,
-	                             &transfer_data.line_coding);
+	return CDC_SET_LINE_CODING_CALLBACK(transfer_interface,
+	                                    &transfer_data.line_coding);
 }
 #endif
 
@@ -159,7 +160,7 @@
 			return -1;
 
 		usb_send_data_stage((void*)response,
-		                    MIN(len, setup->wLength),
+		                    min(len, setup->wLength),
 		                    callback, context);
 		return 0;
 	}
@@ -227,7 +228,7 @@
 				(uint16_t) data_multiplexed_state << 1;
 
 		usb_send_data_stage((char*)&transfer_data.comm_feature,
-		                    MIN(setup->wLength,
+		                    min(setup->wLength,
 		                        sizeof(transfer_data.comm_feature)),
 		                    NULL/*callback*/, NULL);
 		return 0;
@@ -241,7 +242,7 @@
 		transfer_interface = interface;
 		usb_start_receive_ep0_data_stage(
 		                      (char*)&transfer_data.line_coding,
-		                      MIN(setup->wLength,
+		                      min(setup->wLength,
 		                          sizeof(transfer_data.line_coding)),
 		                      set_line_coding, NULL);
 		return 0;
@@ -260,7 +261,7 @@
 			return -1;
 
 		usb_send_data_stage((char*)&transfer_data.line_coding,
-		                    MIN(setup->wLength,
+		                    min(setup->wLength,
 		                        sizeof(transfer_data.line_coding)),
 		                    /*callback*/NULL, NULL);
 		return 0;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usb/src/usb_hid.c	Sat Feb 18 16:25:38 2017 +0000
@@ -0,0 +1,176 @@
+/*
+ *  M-Stack USB Device Stack Implementation
+ *  Copyright (C) 2013 Alan Ott <alan@signal11.us>
+ *  Copyright (C) 2013 Signal 11 Software
+ *
+ *  Initial version for PIC18, 2008-02-24
+ *  PIC24 port, 2013-08-13
+ *
+ *  M-Stack is free software: you can redistribute it and/or modify it under
+ *  the terms of the GNU Lesser General Public License as published by the
+ *  Free Software Foundation, version 3; or the Apache License, version 2.0
+ *  as published by the Apache Software Foundation.  If you have purchased a
+ *  commercial license for this software from Signal 11 Software, your
+ *  commerical license superceeds the information in this header.
+ *
+ *  M-Stack is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ *  License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this software.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  You should have received a copy of the Apache License, verion 2.0 along
+ *  with this software.  If not, see <http://www.apache.org/licenses/>.
+ */
+
+#include <usb_config.h>
+
+#include <usb_ch9.h>
+#include <usb.h>
+#include <usb_hid.h>
+
+#define MIN(x,y) (((x)<(y))?(x):(y))
+
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct hid_descriptor), 9);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct hid_optional_descriptor), 3);
+
+#ifdef MULTI_CLASS_DEVICE
+static uint8_t *hid_interfaces;
+static uint8_t num_hid_interfaces;
+
+void hid_set_interface_list(uint8_t *interfaces, uint8_t num_interfaces)
+{
+	hid_interfaces = interfaces;
+	num_hid_interfaces = num_interfaces;
+}
+#endif
+
+uint8_t process_hid_setup_request(const struct setup_packet *setup)
+{
+	/* The following comes from the HID spec 1.11, section 7.1.1 */
+
+	uint8_t interface = setup->wIndex;
+
+#ifdef MULTI_CLASS_DEVICE
+	/* Check the interface first to make sure the destination is a
+	 * HID interface. Composite devices will need to call
+	 * hid_set_interface_list() first.
+	 */
+	uint8_t i;
+	for (i = 0; i < num_hid_interfaces; i++) {
+		if (interface == hid_interfaces[i])
+			break;
+	}
+
+	/* Return if interface is not in the list of HID interfaces. */
+	if (i == num_hid_interfaces)
+		return -1;
+#endif
+
+	if (setup->bRequest == GET_DESCRIPTOR &&
+	    setup->REQUEST.bmRequestType == 0x81) {
+		uint8_t descriptor = ((setup->wValue >> 8) & 0x00ff);
+
+		const void *desc;
+		int16_t len = -1;
+
+		if (descriptor == DESC_HID) {
+			len = USB_HID_DESCRIPTOR_FUNC(interface, &desc);
+		}
+		else if (descriptor == DESC_REPORT) {
+			len = USB_HID_REPORT_DESCRIPTOR_FUNC(interface, &desc);
+		}
+#ifdef USB_HID_PHYSICAL_DESCRIPTOR_FUNC
+		else if (descriptor == DESC_PHYSICAL) {
+			uint8_t descriptor_index = setup->wValue & 0x00ff;
+			len = USB_HID_PHYSICAL_DESCRIPTOR_FUNC(interface, descriptor_index, &desc);
+		}
+#endif
+		if (len < 0)
+			return -1;
+
+		usb_send_data_stage((void*) desc, min(len, setup->wLength), NULL, NULL);
+		return 0;
+	}
+
+	/* No support for Set_Descriptor */
+
+#ifdef HID_GET_REPORT_CALLBACK
+	const void *desc;
+	int16_t len = -1;
+	usb_ep0_data_stage_callback callback;
+	void *context;
+	if (setup->bRequest == HID_GET_REPORT &&
+	    setup->REQUEST.bmRequestType == 0xa1) {
+		uint8_t report_type = (setup->wValue >> 8) & 0x00ff;
+		uint8_t report_id = setup->wValue & 0x00ff;
+		len = HID_GET_REPORT_CALLBACK(interface/*interface*/,
+		                              report_type, report_id,
+		                              &desc, &callback, &context);
+		if (len < 0)
+			return -1;
+
+		usb_send_data_stage((void*)desc, min(len, setup->wLength), callback, context);
+		return 0;
+	}
+#endif
+
+#ifdef HID_SET_REPORT_CALLBACK
+	if (setup->bRequest == HID_SET_REPORT &&
+	    setup->REQUEST.bmRequestType == 0x21) {
+		uint8_t report_type = (setup->wValue >> 8) & 0x00ff;
+		uint8_t report_id = setup->wValue & 0x00ff;
+		int8_t res = HID_SET_REPORT_CALLBACK(interface,
+		                                     report_type, report_id);
+		return res;
+	}
+#endif
+
+#ifdef HID_GET_IDLE_CALLBACK
+	if (setup->bRequest == HID_GET_IDLE &&
+	    setup->REQUEST.bmRequestType == 0xa1) {
+		uint8_t report_id = setup->wValue & 0x00ff;
+		uint8_t res = HID_GET_IDLE_CALLBACK(interface, report_id);
+
+		usb_send_data_stage((char*)&res, 1, NULL, NULL);
+		return 0;
+	}
+#endif
+
+#ifdef HID_SET_IDLE_CALLBACK
+	if (setup->bRequest == HID_SET_IDLE &&
+	    setup->REQUEST.bmRequestType == 0x21) {
+		uint8_t duration = (setup->wValue >> 8) & 0x00ff;
+		uint8_t report_id = setup->wValue & 0x00ff;
+		uint8_t res = HID_SET_IDLE_CALLBACK(interface, report_id,
+		                                    duration);
+
+		return res;
+	}
+#endif
+
+#ifdef HID_GET_PROTOCOL_CALLBACK
+	if (setup->bRequest == HID_GET_PROTOCOL &&
+	    setup->REQUEST.bmRequestType == 0xa1) {
+		int8_t res = HID_GET_PROTOCOL_CALLBACK(interface);
+		if (res < 0)
+			return -1;
+
+		usb_send_data_stage((char*)&res, 1, NULL, NULL);
+		return 0;
+	}
+#endif
+
+#ifdef HID_SET_PROTOCOL_CALLBACK
+	if (setup->bRequest == HID_SET_PROTOCOL &&
+	    setup->REQUEST.bmRequestType == 0x21) {
+		int8_t res = HID_SET_PROTOCOL_CALLBACK(interface,
+		                                       setup->wValue);
+		return res;
+	}
+#endif
+
+	return -1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usb/src/usb_msc.c	Sat Feb 18 16:25:38 2017 +0000
@@ -0,0 +1,1357 @@
+/*
+ *  M-Stack USB Device Stack Implementation
+ *  Copyright (C) 2014 Alan Ott <alan@signal11.us>
+ *  Copyright (C) 2014 Signal 11 Software
+ *
+ *  2014-06-04
+ *
+ *  M-Stack is free software: you can redistribute it and/or modify it under
+ *  the terms of the GNU Lesser General Public License as published by the
+ *  Free Software Foundation, version 3; or the Apache License, version 2.0
+ *  as published by the Apache Software Foundation.  If you have purchased a
+ *  commercial license for this software from Signal 11 Software, your
+ *  commerical license superceeds the information in this header.
+ *
+ *  M-Stack is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ *  License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this software.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  You should have received a copy of the Apache License, verion 2.0 along
+ *  with this software.  If not, see <http://www.apache.org/licenses/>.
+ */
+
+#include <usb_config.h>
+
+#include <usb_ch9.h>
+#include <usb.h>
+#include <usb_msc.h>
+#include "usb_priv.h"
+
+#include <string.h>
+
+#define MIN(x,y) (((x)<(y))?(x):(y))
+#define MAX(x,y) (((x)>(y))?(x):(y))
+
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct msc_command_block_wrapper), 31);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct msc_command_status_wrapper), 13);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct msc_scsi_inquiry_command), 6);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct msc_scsi_request_sense_command), 6);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct msc_scsi_mode_sense_6_command), 6);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct msc_scsi_start_stop_unit), 6);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct msc_scsi_read_10_command), 10);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct msc_scsi_write_10_command), 10);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct scsi_inquiry_response), 36);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct scsi_capacity_response), 8);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct scsi_mode_sense_response), 4);
+STATIC_SIZE_CHECK_EQUAL(sizeof(struct scsi_sense_response), 18);
+
+
+#if MSC_MAX_LUNS_PER_INTERFACE > 0x10
+	#error Too many LUNs per interface. Max is 16 LUNs per interface
+#endif
+
+#if MSC_MAX_LUNS_PER_INTERFACE == 0
+	#error At least one LUN must be supported.
+#endif
+
+static struct msc_application_data *g_application_data;
+#ifdef MSC_SUPPORT_MULTIPLE_MSC_INTERFACES
+static uint8_t g_application_data_count;
+#endif
+
+static inline void swap(uint8_t *v1, uint8_t *v2)
+{
+	uint8_t tmp;
+	tmp = *v1;
+	*v1 = *v2;
+	*v2 = tmp;
+}
+
+static void swap4(uint32_t *val)
+{
+	union swapper {
+		uint32_t val;
+		uint8_t v_bytes[4];
+	};
+
+	union swapper *sw = (union swapper *) val;
+	swap(&sw->v_bytes[0], &sw->v_bytes[3]);
+	swap(&sw->v_bytes[1], &sw->v_bytes[2]);
+}
+
+static void swap2(uint16_t *val)
+{
+	union swapper {
+		uint16_t val;
+		uint8_t v_bytes[2];
+	};
+
+	union swapper *sw = (union swapper *) val;
+	swap(&sw->v_bytes[0], &sw->v_bytes[1]);
+}
+
+static bool direction_is_in(uint8_t flags)
+{
+	return flags & MSC_DIRECTION_IN_BIT;
+}
+
+static bool direction_is_out(uint8_t flags)
+{
+	return ~flags & MSC_DIRECTION_IN_BIT;
+}
+
+#ifdef MSC_SUPPORT_MULTIPLE_MSC_INTERFACES
+/* Lookup applicaiton data by interface number. */
+static struct msc_application_data *get_app_data(uint8_t interface)
+{
+	uint8_t i;
+
+	for (i = 0; i < g_application_data_count; i++) {
+		struct msc_application_data *d = &g_application_data[i];
+		if (d->interface == interface) {
+			return d;
+		}
+	}
+
+	return NULL;
+}
+
+/* Lookup application data by endpoint. */
+static struct msc_application_data *get_app_data_by_endpoint(
+					uint8_t endpoint_num, uint8_t direction)
+{
+	uint8_t i;
+
+	for (i = 0; i < g_application_data_count; i++) {
+		struct msc_application_data *d = &g_application_data[i];
+		/* The if-else inside the loop is slower than doing it
+		 * outside the loop, but it's less code, and since this
+		 * doesn't happen very often (just in the clear_halt case
+		 * which only happens on error), this is an acceptable
+		 * tradeoff. */
+		if (direction && d->in_endpoint == endpoint_num) {
+			return d;
+		}
+		else if (d->out_endpoint == endpoint_num) {
+			return d;
+		}
+	}
+
+	return NULL;
+}
+#else
+/* Lookup applicaiton data by interface number. */
+static inline struct msc_application_data *get_app_data(uint8_t interface)
+{
+	if (interface == g_application_data[0].interface)
+		return g_application_data;
+
+	return NULL;
+}
+
+/* Lookup application data by endpoint. */
+static inline struct msc_application_data *get_app_data_by_endpoint(
+					uint8_t endpoint_num, uint8_t direction)
+{
+	if (direction && g_application_data[0].in_endpoint == endpoint_num) {
+		return g_application_data;
+	}
+	else if (g_application_data[0].out_endpoint == endpoint_num) {
+		return g_application_data;
+	}
+
+	return NULL;
+}
+#endif
+
+/* Stall the IN endpoint and set the status which will be returned by the
+ * next CSW. */
+static void stall_in_and_set_status(struct msc_application_data *msc,
+                                    uint32_t residue, uint8_t status)
+{
+	msc->residue = residue;
+	msc->status = status;
+	usb_halt_ep_in(msc->in_endpoint);
+	msc->state = MSC_CSW;
+}
+
+/* Stall the OUT endpoint and set the status which will be returned by the
+ * next CSW. */
+static void stall_out_and_set_status(struct msc_application_data *msc,
+                                     uint32_t residue, uint8_t status)
+{
+	msc->residue = residue;
+	msc->status = status;
+	usb_halt_ep_out(msc->out_endpoint);
+	msc->state = MSC_CSW;
+}
+
+/* Send a Command Status Word (CSW) */
+static int8_t send_csw(struct msc_application_data *msc,
+                       uint32_t residue, uint8_t status)
+{
+	struct msc_command_status_wrapper *csw;
+
+	/* Make sure endpoint is free */
+	if (usb_in_endpoint_busy(msc->in_endpoint))
+		return -1;
+
+	csw = (struct msc_command_status_wrapper *)
+			usb_get_in_buffer(msc->in_endpoint);
+	csw->dCSWSignature = 0x53425355;
+	csw->dCSWTag = msc->current_tag;
+	csw->dCSWDataResidue = residue;
+	csw->bCSWStatus = status;
+
+	usb_send_in_buffer(msc->in_endpoint, sizeof(*csw));
+
+	/* Reset states and status */
+	msc->state = MSC_IDLE;
+	msc->status = MSC_STATUS_PASSED;
+	msc->residue = 0;
+
+	return 0;
+}
+
+static void set_scsi_sense(struct msc_application_data *msc,
+                           enum MSCReturnCodes code)
+{
+	if (code == MSC_ERROR_MEDIUM_NOT_PRESENT) {
+		msc->sense_key = SCSI_SENSE_KEY_NOT_READY;
+		msc->additional_sense_code = SCSI_ASC_MEDIUM_NOT_PRESENT;
+	}
+	else if (code == MSC_ERROR_INVALID_LUN) {
+		msc->sense_key = SCSI_SENSE_KEY_ILLEGAL_REQUEST;
+		msc->additional_sense_code =
+			SCSI_ASC_LOGICAL_UNIT_NOT_SUPPORTED;
+	}
+	else if (code == MSC_ERROR_INVALID_ADDRESS) {
+		msc->sense_key = SCSI_SENSE_KEY_ILLEGAL_REQUEST;
+		msc->additional_sense_code =
+			SCSI_ASC_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+	}
+	else if (code == MSC_ERROR_WRITE_PROTECTED) {
+		msc->sense_key = SCSI_SENSE_KEY_DATA_PROTECT;
+		msc->additional_sense_code = SCSI_ASC_WRITE_PROTECTED;
+	}
+	else if (code == MSC_ERROR_READ) {
+		msc->sense_key = SCSI_SENSE_KEY_MEDIUM_ERROR;
+		msc->additional_sense_code = SCSI_ASC_UNRECOVERED_READ_ERROR;
+	}
+	else if (code == MSC_ERROR_WRITE) {
+		msc->sense_key = SCSI_SENSE_KEY_MEDIUM_ERROR;
+		msc->additional_sense_code =
+			SCSI_ASC_PERIPHERAL_DEVICE_WRITE_FAULT;
+	}
+	else if (code == MSC_ERROR_MEDIUM) {
+		msc->sense_key = SCSI_SENSE_KEY_MEDIUM_ERROR;
+		msc->additional_sense_code = 0;
+	}
+}
+
+/* Whether the CBW is valid and meaningful, per the MSC BOT spec */
+static bool msc_cbw_valid_and_meaningful(struct msc_application_data *msc,
+                                         const uint8_t *data, uint16_t len)
+{
+	const struct msc_command_block_wrapper *cbw = (const void*) data;
+
+	/* Test whether the CBW isvalid. Conforms to BOT: 6.2.1 */
+
+	if (len != sizeof(struct msc_command_block_wrapper))
+		return false;
+
+	if (cbw->dCBWSignature != 0x43425355)
+		return false;
+
+	/* Test whether the CBW is meaningful. Conforms to BOT: 6.2.2 */
+
+	if (msc->state != MSC_IDLE)
+		return false;
+
+	if (cbw->bCBWCBLength < 1 || cbw->bCBWCBLength > 16)
+		return false;
+
+	if (cbw->bmCBWFlags & 0x7F)
+		return false;
+
+	if (cbw->bCBWLUN > msc->max_lun ||
+	    cbw->bCBWLUN >= MSC_MAX_LUNS_PER_INTERFACE)
+		return false;
+
+	return true;
+}
+
+static void stall_in_and_phase_error(struct msc_application_data *msc)
+{
+	stall_in_and_set_status(msc, 0, MSC_STATUS_PHASE_ERROR);
+}
+
+static void stall_out_and_phase_error(struct msc_application_data *msc)
+{
+	stall_out_and_set_status(msc, 0, MSC_STATUS_PHASE_ERROR);
+}
+
+static void phase_error(struct msc_application_data *msc)
+{
+	send_csw(msc, 0, MSC_STATUS_PHASE_ERROR);
+}
+
+/** Set the state of the Data-IN endpoint after sending data to the host
+ *
+ * Set the state of the Data-In endpoint based on the amount of data requested
+ * by the host in the CBW packet (cbw_length) and the amount of data actually
+ * sent (sent_length).
+ *
+ * If the cbw_length is more than the sent_length, the endpoint must be
+ * stalled (BOT: 6.7.2, Device Requirements, case 5).
+ *
+ * If the requested length and the number of bytes sent match (the most common
+ * case, Case 6), the EP does not have to be stalled and a CSW can be sent.
+ *
+ * This function should be called after data has been sent to the host for
+ * short transports where the device expects to send data (Di) (such as
+ * INQUIRY, REQUEST_SENSE, etc.). It is not used for READ, which has a
+ * different mechanism for handling status.
+ */
+static void set_data_in_endpoint_state(struct msc_application_data *msc,
+                              uint32_t cbw_length,
+                              size_t sent_length)
+{
+	msc->status = MSC_STATUS_PASSED;
+
+	if (cbw_length > sent_length) {
+		/* Case 5 (Hi > Di): Stall the IN EP and set residue */
+		msc->residue = cbw_length - sent_length;
+		msc->state = MSC_STALL;
+	}
+	else {
+		/* Case 6 (Hi = Di): OK. Host will want the CSW next */
+		msc->residue = 0;
+		msc->state = MSC_CSW;
+	}
+}
+
+/* Check the Di cases: 2, 5, 6, 7, and 10 (BOT section 6.7). Case 6 is
+ * correct, and 5 is acceptable with residue. 2, 7, and 10 result in a phase
+ * error with an endpoint (IN or OUT) potentially stalled.
+ *
+ * This function returns 0 if case 5 or 6, or -1 if case 2, 7, or 10.
+ *
+ * The calling function should:
+ *   for return value 0:
+ *     proceed with returning data to the host, properly handling residue,
+ *   for return value -1:
+ *     do nothing further in response to the received command.
+ */
+static int8_t check_di_cases(struct msc_application_data *msc,
+                             const struct msc_command_block_wrapper *cbw,
+                             uint32_t intended_length)
+{
+	const uint32_t cbw_length = cbw->dCBWDataTransferLength;
+	const bool direc_is_out = direction_is_out(cbw->bmCBWFlags);
+
+	/* Case 2 (Hn < Di): set phase error (no stall) */
+	if (cbw_length == 0) {
+		phase_error(msc);
+		return -1;
+	}
+
+	/* Case 7 (Hi < Di): stall IN and phase error */
+	if (cbw_length < intended_length) {
+		stall_in_and_phase_error(msc);
+		return -1;
+	}
+
+	/* Case 10 (Ho <> Di): stall OUT and phase error */
+	if (direc_is_out) {
+		stall_out_and_phase_error(msc);
+		return -1;
+	}
+
+	/* Case 5 (Hi > Di): OK, with residue.
+	 * Case 6 (Hi = Di): OK */
+	return 0;
+}
+
+/* Check the Dn cases: 1, 4, and 9 (BOT section 6.7). Case 1 is correct, and
+ * cases 4 and 9 will result in a stall of an appropriate endpoint.
+ *
+ * This function returns 0 if case 1 or returns -1 for cases 4 and 9.
+ *
+ * The calling function should:
+ *   for return value 0:
+ *     proceed to the CSW state (no data needs to be returned or handled),
+ *   for return value -1:
+ *     do nothing further in response to the received command.
+ *
+ */
+static int8_t check_dn_cases(struct msc_application_data *msc,
+                             const struct msc_command_block_wrapper *cbw)
+{
+	const uint32_t cbw_length = cbw->dCBWDataTransferLength;
+	const bool direc_is_out = direction_is_out(cbw->bmCBWFlags);
+
+	/* Case 9 (Ho > Dn): stall OUT and set status FAILED */
+	if (direc_is_out && cbw_length > 0) {
+		stall_out_and_set_status(msc, cbw_length, MSC_STATUS_FAILED);
+		return -1;
+	}
+
+	/* Case 4 (Hi > Dn): Stall IN and set status FAILED. */
+	if (cbw_length > 0) {
+		stall_in_and_set_status(msc, cbw_length, MSC_STATUS_FAILED);
+		return -1;
+	}
+
+	/* Case 1 (Hn = Dn): OK */
+	return 0;
+}
+
+#ifdef MSC_WRITE_SUPPORT
+/* Check the Do cases: 3, 8, 11, 12, and 13 (BOT section 6.7). Case 12 is
+ * correct. Other cases stall appropriate endpoints.
+ *
+ * This function returns 0 for case 12 or returns -1 for all other cases.
+ *
+ * The calling function should:
+ *   for return value 0:
+ *     proceed with receiving data from the host,
+ *   for return value -1:
+ *     do nothing further in response to the received command.
+ */
+static int8_t check_do_cases(struct msc_application_data *msc,
+                             const struct msc_command_block_wrapper *cbw,
+                             uint32_t intended_length)
+{
+	const uint32_t cbw_length = cbw->dCBWDataTransferLength;
+	const bool direc_is_in = direction_is_in(cbw->bmCBWFlags);
+
+	/* Case 3 (Hn < Do): set phase error (no stall) */
+	if (cbw_length == 0) {
+		phase_error(msc);
+		return -1;
+	}
+
+	/* Case 8 (Hi <> Do): stall IN and phase error */
+	if (direc_is_in) {
+		stall_in_and_phase_error(msc);
+		return -1;
+	}
+
+	/* Case 13 (Ho < Do): stall OUT and phase error */
+	if (cbw_length < intended_length) {
+		stall_out_and_phase_error(msc);
+		return -1;
+	}
+
+	/* Case 11 (Ho > Do): Stall OUT and set status to FAILED
+	 *
+	 * Note that BOT 6.7.3:device:case_11 says that the device shall
+	 * receive the intended data and then stall the bulk-OUT endpoint.
+	 * BOT does not define "intended data," but appears to use it
+	 * to mean the amount of data that is specified (in our case) in the
+	 * SCSI packet. This is the definition used most often in this
+	 * implementation of the Mass Storage class, but in this case, for
+	 * simplicity, the device will "intend" to receve 0 bytes if the CBW
+	 * length and the SCSI length don't match.
+	 *
+	 * This means instead of setting up to receive intended_length bytes
+	 * and then stalling once that amount of data has been received and
+	 * then dealing with returning residue, the OUT endpoint will simply
+	 * be stalled here.
+	 *
+	 * This passes the USBCV Case 11 test. */
+	if (cbw_length > intended_length) {
+		stall_out_and_set_status(msc, cbw_length, MSC_STATUS_FAILED);
+		return -1;
+	}
+
+	/* Case 12 (Ho = Do): OK */
+	return 0;
+}
+#endif
+
+#ifdef MULTI_CLASS_DEVICE
+static uint8_t *msc_interfaces;
+static uint8_t num_msc_interfaces;
+
+void msc_set_interface_list(uint8_t *interfaces, uint8_t num_interfaces)
+{
+	msc_interfaces = interfaces;
+	num_msc_interfaces = num_interfaces;
+}
+
+static bool interface_is_msc(uint8_t interface)
+{
+	uint8_t i;
+	for (i = 0; i < num_msc_interfaces; i++) {
+		if (interface == msc_interfaces[i])
+			break;
+	}
+
+	/* Return if interface is not in the list of CDC interfaces. */
+	if (i == num_msc_interfaces)
+		return false;
+
+	return true;
+}
+#endif
+
+uint8_t msc_init(struct msc_application_data *app_data, uint8_t count)
+{
+	uint8_t i;
+
+#ifndef MSC_SUPPORT_MULTIPLE_MSC_INTERFACES
+	if (count > 1)
+		return -1;
+#endif
+
+	for (i = 0; i < count; i++) {
+		struct msc_application_data *d = &app_data[i];
+
+		/* Validate struct members. */
+		if (d->max_lun > 0x0f)
+			return -1;
+		if (d->max_lun >= MSC_MAX_LUNS_PER_INTERFACE)
+			return -1;
+		if (d->in_endpoint_size != 64)
+			return -1;
+		if (d->in_endpoint > 15)
+			return -1;
+		if (d->out_endpoint > 15)
+			return -1;
+
+		/* Initialize the MSC-Class-Controlled members.*/
+		d->state = MSC_IDLE;
+		d->sense_key = 0;
+		d->additional_sense_code = 0;
+		d->residue = 0;
+		d->status = 0;
+		d->requested_bytes = 0;
+		d->transferred_bytes = 0;
+		d->tx_buf = NULL;
+		d->tx_len_remaining = 0;
+#ifdef MSC_WRITE_SUPPORT
+		d->rx_buf_cur = NULL;
+		d->rx_buf_len = 0;
+		d->out_ep_missed_transactions = 0;
+#endif
+		d->operation_complete_callback = NULL;
+		memset(d->block_size, 0, sizeof(d->block_size));
+	}
+
+	g_application_data = app_data;
+#ifdef MSC_SUPPORT_MULTIPLE_MSC_INTERFACES
+	g_application_data_count = count;
+#endif
+
+	return 0;
+}
+
+int8_t process_msc_setup_request(const struct setup_packet *setup)
+{
+	uint8_t interface = setup->wIndex;
+	struct msc_application_data *msc;
+
+
+#ifdef MULTI_CLASS_DEVICE
+	/* Check the interface first to make sure the destination is an
+	 * MSC interface. Multi-class devices will need to call
+	 * msc_set_interface_list() first.
+	 */
+	if (!interface_is_msc(interface))
+		return -1;
+#endif
+
+	/* Get application data for this interface */
+	msc = get_app_data(interface);
+	if (!msc)
+		return -1;
+
+	/* BOT 3.2 says the GET_MAX_LUN request is optional if there's only one
+	 * LUN, but stalling this request will cause Windows 7 to hang for 18
+	 * seconds when a device is first connected. */
+	if (setup->bRequest == MSC_GET_MAX_LUN &&
+	    setup->REQUEST.bmRequestType == 0xa1) {
+
+		/* Stall invalid value/length */
+		if (setup->wValue != 0 ||
+		    setup->wLength != 1)
+			return -1;
+
+		usb_send_data_stage((void*)&msc->max_lun,
+		                    1, NULL, 0);
+		return 0;
+	}
+
+	if (setup->bRequest == MSC_BULK_ONLY_MASS_STORAGE_RESET &&
+	    setup->REQUEST.bmRequestType == 0x21) {
+		struct msc_application_data *d = get_app_data(interface);
+
+		/* Stall invalid value/length */
+		if (setup->wValue != 0 ||
+		    setup->wLength != 0)
+			return -1;
+
+		if (d)
+			d->state = MSC_IDLE;
+		else
+			return -1;
+
+#ifdef MSC_BULK_ONLY_MASS_STORAGE_RESET_CALLBACK
+		int8_t res = 0;
+
+		res = MSC_BULK_ONLY_MASS_STORAGE_RESET_CALLBACK(interface);
+		if (res < 0)
+			return -1;
+#endif
+		/* Clear the NEEDS_RESET_RECOVERY state */
+		msc->state = MSC_IDLE;
+
+		/* Return zero-length packet. No data stage. */
+		usb_send_data_stage(NULL, 0, NULL, NULL);
+
+		return 0;
+	}
+	return -1;
+}
+
+#ifdef MSC_WRITE_SUPPORT
+/* Copy data to the application's buffer. If the application's buffer
+ * is full, return -1.
+ *
+ * It will be common for this function to be called when the buffer is full,
+ * since writing to the medium is slower than USB, and since the host relies
+ * on the device to throttle the connection as appropriate.
+ *
+ * If -1 is returned from this function, it will be up to the caller to make
+ * sure that this function is re-called later, when there is buffer space
+ * available. */
+static inline uint8_t receive_data(struct msc_application_data *msc,
+                                       const uint8_t *data, uint16_t len)
+{
+	/* Make sure this doesn't take us off the end of the
+	 * application's buffer. */
+	if (msc->rx_buf_cur + len > msc->rx_buf + msc->rx_buf_len)
+		return -1;
+
+	/* Ignore this data if it is more than was expected. This indicates
+	 * an error on the host side, but it can be handled here by just
+	 * ignoring the extra data. The difference will be reflected in the
+	 * residue in the CSW. */
+	if (msc->transferred_bytes >= msc->requested_bytes)
+		return 0;
+
+	/* Copy to the application's buffer. */
+	memcpy(msc->rx_buf_cur, data, len);
+	msc->rx_buf_cur += len;
+
+	/* If this is the last piece of the data block, notify the
+	 * application that it's buffer is now full and that it can start
+	 * writing to the medium.  */
+	if (msc->rx_buf_cur >= msc->rx_buf + msc->rx_buf_len) {
+		msc->operation_complete_callback(msc, true);
+	}
+
+	return 0;
+}
+#endif
+
+/* Send the next transaction containing data from the medium to the host. */
+static int8_t send_next_data_transaction(struct msc_application_data *msc)
+{
+	const uint8_t *cur = msc->tx_buf;
+	const uint16_t len = msc->tx_len_remaining;
+
+	if (!usb_is_configured() || usb_in_endpoint_busy(msc->in_endpoint))
+		return -1;
+
+	if (len > 0) {
+		/* There is data to send; send one packet worth. */
+		uint8_t *buf;
+		uint16_t to_copy;
+
+		buf = usb_get_in_buffer(msc->in_endpoint);
+		to_copy = MIN(len, msc->in_endpoint_size);
+		memcpy(buf, cur, to_copy);
+
+		usb_send_in_buffer(msc->in_endpoint, to_copy);
+
+		msc->transferred_bytes += to_copy;
+		msc->tx_buf += to_copy;
+		msc->tx_len_remaining -= to_copy;
+	}
+	else {
+		/* Transfer of block has completed */
+		msc->operation_complete_callback(msc, true);
+		msc->operation_complete_callback = NULL;
+		msc->tx_buf = NULL;
+	}
+
+	return 0;
+}
+
+uint8_t msc_start_send_to_host(struct msc_application_data *msc,
+                               const uint8_t *data, uint16_t len,
+                               msc_completion_callback completion_callback)
+{
+	int8_t res;
+
+	usb_disable_transaction_interrupt();
+
+	if (msc->state != MSC_DATA_TRANSPORT_IN || len == 0) {
+		res = -1;
+		goto out;
+	}
+
+	msc->tx_buf = data;
+	msc->tx_len_remaining = len;
+	msc->operation_complete_callback = completion_callback;
+
+	/* Kick off the transmission. */
+	res = send_next_data_transaction(msc);
+
+out:
+	usb_enable_transaction_interrupt();
+	return res;
+}
+
+void msc_notify_read_operation_complete(
+                                   struct msc_application_data *app_data,
+                                   bool passed)
+{
+	usb_disable_transaction_interrupt();
+
+	if (app_data->state != MSC_DATA_TRANSPORT_IN) {
+		goto out;
+	}
+
+	uint32_t residue = app_data->requested_bytes_cbw -
+	                           app_data->transferred_bytes;
+
+	if (!passed) {
+		/* Save off the error codes. These will be read by the host
+		 * with a REQUEST_SENSE SCSI command. */
+		set_scsi_sense(app_data, MSC_ERROR_READ);
+	}
+
+	uint8_t status = passed? MSC_STATUS_PASSED: MSC_STATUS_FAILED;
+
+	if (residue > 0) {
+		stall_in_and_set_status(app_data, residue, status);
+	}
+	else {
+		send_csw(app_data, residue, status);
+		app_data->state = MSC_IDLE;
+	}
+
+out:
+	usb_enable_transaction_interrupt();
+}
+
+#ifdef MSC_WRITE_SUPPORT
+/* Must be called with the transaction interrupt disabled */
+static void handle_missed_out_transactions(struct msc_application_data *msc)
+{
+	/* Handle any pending data.  There's a good chance the USB
+	 * peripheral received data while the write was occurring. That data
+	 * was held in the endpoint buffer(s) by
+	 * msc_out_transaction_complete(). Process that data now. Make sure to
+	 * _only_ call msc_out_transaction_complete() the exact number of
+	 * times a transaction completed in order to avoid a race condition
+	 * window (more below).
+	 *
+	 * usb_endpoint_has_data() can't be used to determine how many
+	 * transactions are pending because since this function runs
+	 * with interrupts disabled. Doing so would open up a race condition
+	 * window where the endpoint would be read too many times if a
+	 * transaction were to complete during this function. For this reason
+	 * msc->out_ep_missed_transactions is used to keep track of how many
+	 * OUT transactions are pending (with data on the endpoint buffers).
+	 *
+	 * Make a new variable for count below because
+	 * msc->out_ep_missed_transactions is also manipulated by
+	 * msc_out_transaction_complete(), and if the application buffer is
+	 * short (eg: smaller the length of two transactions), there's a
+	 * possibility that one call to msc_out_transaction_complete() could
+	 * fail (if the application buffer is full), in which case
+	 * msc_out_transaction_complete() would re-increment
+	 * msc->out_ep_missed_transactions.  */
+
+	uint8_t i, count;
+
+	count = msc->out_ep_missed_transactions;
+	for (i = 0; i < count; i++) {
+		msc_out_transaction_complete(msc->out_endpoint);
+		msc->out_ep_missed_transactions--;
+	}
+}
+
+void msc_notify_write_data_handled(struct msc_application_data *msc)
+{
+	usb_disable_transaction_interrupt();
+
+	if (msc->state != MSC_DATA_TRANSPORT_OUT) {
+		goto out;
+	}
+
+	/* Ignore if this function has been called too many times. */
+	if (msc->transferred_bytes >= msc->requested_bytes)
+		goto out;
+
+	msc->transferred_bytes += msc->rx_buf_len;
+
+	if (msc->transferred_bytes < msc->requested_bytes) {
+		/* Still more data left to transfer and write. Reset the
+		 * buffer pointer to the beginning of the buffer to prepare
+		 * to receive more data from the host. */
+		msc->rx_buf_cur = msc->rx_buf;
+	}
+
+out:
+	handle_missed_out_transactions(msc);
+
+	usb_enable_transaction_interrupt();
+}
+
+void msc_notify_write_operation_complete(struct msc_application_data *msc,
+                                         bool passed,
+                                         uint32_t bytes_processed)
+{
+	uint32_t residue;
+
+	usb_disable_transaction_interrupt();
+
+	if (msc->state != MSC_DATA_TRANSPORT_OUT) {
+		goto out;
+	}
+
+	residue = msc->requested_bytes_cbw - bytes_processed;
+
+	if (!passed) {
+		/* Save off the error codes. These will be read by the host
+		 * with a REQUEST_SENSE SCSI command. */
+		set_scsi_sense(msc, MSC_ERROR_WRITE);
+
+		stall_out_and_set_status(msc, residue, MSC_STATUS_FAILED);
+		msc->out_ep_missed_transactions = 0;
+		goto fail;
+	}
+
+	if (msc->transferred_bytes < msc->requested_bytes) {
+		/* The application is ending the Data Transport before the
+		 * host has sent all the data it expects to send. This
+		 * becomes case 11 (Ho > Do). Stall the OUT endpoint, but set
+		 * the status to PASSED, because the device processed all the
+		 * data it intended to process. */
+		stall_out_and_set_status(msc, residue, MSC_STATUS_PASSED);
+		msc->out_ep_missed_transactions = 0;
+		goto fail;
+	}
+	else {
+		/* No more data left to transfer */
+		send_csw(msc, residue, MSC_STATUS_PASSED);
+		msc->state = MSC_IDLE;
+	}
+
+out:
+	handle_missed_out_transactions(msc);
+fail:
+	usb_enable_transaction_interrupt();
+}
+#endif /* MSC_WRITE_SUPPORT */
+
+static void process_msc_command(struct msc_application_data *msc,
+                                const uint8_t *data, uint16_t len)
+{
+	const struct msc_command_block_wrapper *cbw = (const void *) data;
+	const uint8_t command = cbw->CBWCB[0];
+	const uint8_t lun = cbw->bCBWLUN;
+	const uint32_t cbw_length = cbw->dCBWDataTransferLength;
+	int8_t res;
+
+	/* Check the Command Block Wrapper (CBW) */
+	if (!msc_cbw_valid_and_meaningful(msc, data,len))
+		goto bad_cbw;
+
+	msc->current_tag = cbw->dCBWTag;
+
+	if (command == MSC_SCSI_INQUIRY) {
+		uint32_t scsi_request_len;
+		struct msc_scsi_inquiry_command *cmd =
+			(struct msc_scsi_inquiry_command *) cbw->CBWCB;
+		struct scsi_inquiry_response *resp =
+			(struct scsi_inquiry_response *)
+				usb_get_in_buffer(msc->in_endpoint);
+
+		swap2(&cmd->allocation_length);
+
+		/* The host may request just the first part of the inquiry
+		 * response structure. */
+		scsi_request_len = MIN(cmd->allocation_length, sizeof(*resp));
+
+		/* INQUIRY: Device indends to send data to the host (Di). */
+		res = check_di_cases(msc, cbw, scsi_request_len);
+		if (res < 0)
+			goto fail;
+
+		if (usb_in_endpoint_busy(msc->in_endpoint))
+			goto fail;
+
+		/* Send INQUIRY response */
+		memset(resp, 0, sizeof(*resp));
+		resp->peripheral = 0x0;
+		resp->rmb = (msc->media_is_removable_mask & (1<<lun))? 0x80: 0;
+		resp->version = MSC_SCSI_SPC_VERSION_2;
+		resp->response_data_format = 0x2;
+		resp->additional_length = sizeof(*resp) - 4;
+		strncpy(resp->vendor, msc->vendor, sizeof(resp->vendor));
+		strncpy(resp->product, msc->product, sizeof(resp->product));
+		strncpy(resp->revision, msc->revision, sizeof(resp->revision));
+
+		usb_send_in_buffer(msc->in_endpoint, scsi_request_len);
+
+		set_data_in_endpoint_state(msc, cbw_length, scsi_request_len);
+	}
+	else if (command == MSC_SCSI_TEST_UNIT_READY) {
+		/* TEST_UNIT_READY: Device intends to transfer no data (Dn). */
+		res = check_dn_cases(msc, cbw);
+		if (res < 0)
+			goto fail;
+
+		if (usb_in_endpoint_busy(msc->in_endpoint))
+			goto fail;
+
+		res = MSC_UNIT_READY(msc, lun);
+		if (res < 0) {
+			/* Set error */
+			set_scsi_sense(msc, res);
+			send_csw(msc, cbw_length, MSC_STATUS_FAILED);
+			goto fail;
+		}
+
+		send_csw(msc, cbw_length, MSC_STATUS_PASSED);
+	}
+	else if (command == MSC_SCSI_READ_CAPACITY_10) {
+		struct scsi_capacity_response *resp =
+			(struct scsi_capacity_response *)
+				usb_get_in_buffer(msc->in_endpoint);
+		uint32_t block_size, num_blocks;
+		bool write_protect;
+
+		/* Read Capacity 10: Device intends to send data
+		 *                   to the host (Di) */
+		res = check_di_cases(msc, cbw, sizeof(*resp));
+		if (res < 0)
+			goto fail;
+
+		if (usb_in_endpoint_busy(msc->in_endpoint))
+			goto fail;
+
+		res = MSC_GET_STORAGE_INFORMATION(
+				msc, lun,
+				&block_size, &num_blocks, &write_protect);
+		if (res < 0) {
+			/* Stall and set error */
+			set_scsi_sense(msc, res);
+			stall_in_and_set_status(
+			                 msc, cbw_length, MSC_STATUS_FAILED);
+			goto fail;
+		}
+
+		/* Pack and send the response buffer */
+		resp->last_block = num_blocks - 1;
+		resp->block_length = block_size;
+		swap4(&resp->last_block);
+		swap4(&resp->block_length);
+		usb_send_in_buffer(msc->in_endpoint, sizeof(*resp));
+
+		/* Save off block_size */
+		msc->block_size[lun] = block_size;
+
+		set_data_in_endpoint_state(msc, cbw_length, sizeof(*resp));
+	}
+	else if (command == MSC_SCSI_REQUEST_SENSE) {
+		uint32_t scsi_request_len;
+		struct msc_scsi_request_sense_command *cmd =
+			(struct msc_scsi_request_sense_command *) cbw->CBWCB;
+		struct scsi_sense_response *resp =
+			(struct scsi_sense_response *)
+				usb_get_in_buffer(msc->in_endpoint);
+
+		scsi_request_len = MIN(cmd->allocation_length, sizeof(*resp));
+
+		/* REQUEST_SENSE: Device intends to send data
+		 *                to the host (Di) */
+		res = check_di_cases(msc, cbw, scsi_request_len);
+		if (res < 0)
+			goto fail;
+
+		if (usb_in_endpoint_busy(msc->in_endpoint))
+			goto fail;
+
+		memset(resp, 0, sizeof(*resp));
+		resp->response_code = SCSI_SENSE_CURRENT_ERRORS;
+		resp->flags = msc->sense_key;
+		resp->additional_sense_length = 0xa;
+		resp->additional_sense_code = msc->additional_sense_code;
+
+		usb_send_in_buffer(msc->in_endpoint, scsi_request_len);
+
+		set_data_in_endpoint_state(msc, cbw_length, scsi_request_len);
+	}
+	else if (command == MSC_SCSI_MODE_SENSE_6) {
+		uint32_t block_size, num_blocks;
+		int8_t res;
+		bool write_protect;
+
+		struct msc_scsi_mode_sense_6_command *cmd =
+			(struct msc_scsi_mode_sense_6_command *) cbw->CBWCB;
+		struct scsi_mode_sense_response *resp =
+			(struct scsi_mode_sense_response *)
+				usb_get_in_buffer(msc->in_endpoint);
+
+		/* MODE_SENSE(6): Device intends to send data
+		 *                to the host (Di) */
+		res = check_di_cases(msc, cbw, sizeof(*resp));
+		if (res < 0)
+			goto fail;
+
+		if (usb_in_endpoint_busy(msc->in_endpoint))
+			goto fail;
+
+		/* Look for page code 0x3f, subpage code 0x0. */
+		if (cmd->pc_page_code != 0x3f || cmd->subpage_code != 0) {
+			msc->sense_key = SCSI_SENSE_KEY_ILLEGAL_REQUEST;
+			msc->additional_sense_code =
+			     SCSI_ASC_INVALID_FIELD_IN_COMMAND_PACKET;
+
+			/* Stall and send the status after the stall. */
+			stall_in_and_set_status(msc,
+			                        cbw_length,
+			                        MSC_STATUS_FAILED);
+			goto fail;
+		}
+
+		res = MSC_GET_STORAGE_INFORMATION(msc, lun, &block_size,
+		                                  &num_blocks, &write_protect);
+		if (res < 0) {
+			/* Stall and set error */
+			set_scsi_sense(msc, res);
+			stall_in_and_set_status(
+			                msc, cbw_length, MSC_STATUS_FAILED);
+			goto fail;
+		}
+
+#ifndef MSC_WRITE_SUPPORT
+		/* Force write-protect on if write is not supported */
+		write_protect = true;
+#endif
+		resp->mode_data_length =
+			sizeof(struct scsi_mode_sense_response) - 1;
+		resp->medium_type = 0x0; /* 0 = SBC */
+		resp->device_specific_parameter = (write_protect)? 0x80: 0;
+		resp->block_descriptor_length = 0;
+
+		usb_send_in_buffer(msc->in_endpoint, sizeof(*resp));
+		set_data_in_endpoint_state(msc, cbw_length, sizeof(*resp));
+	}
+	else if (command == MSC_SCSI_START_STOP_UNIT) {
+		int8_t res;
+		bool start, load_eject;
+
+		struct msc_scsi_start_stop_unit *cmd =
+			(struct msc_scsi_start_stop_unit *) cbw->CBWCB;
+
+		/* START STOP UNIT: Device intends to not send or receive
+		 *                  any data (Dn). */
+		res = check_dn_cases(msc, cbw);
+		if (res < 0)
+			goto fail;
+
+		if (usb_in_endpoint_busy(msc->in_endpoint))
+			goto fail;
+
+		/* Only accept power condition 0x0, START_VALID */
+		if ((cmd->command & 0xf0) != 0)
+			goto fail;
+
+		start      = ((cmd->command & 0x1) != 0);
+		load_eject = ((cmd->command & 0x2) != 0);
+
+		res = MSC_START_STOP_UNIT(msc, lun, start, load_eject);
+		if (res < 0) {
+			set_scsi_sense(msc, res);
+			send_csw(msc, cbw_length, MSC_STATUS_FAILED);
+			goto fail;
+		}
+
+		send_csw(msc, 0, MSC_STATUS_PASSED);
+	}
+	else if (command == MSC_SCSI_READ_10) {
+		uint32_t scsi_request_len;
+		struct msc_scsi_read_10_command *cmd =
+			(struct msc_scsi_read_10_command *) cbw->CBWCB;
+
+		swap4(&cmd->logical_block_address);
+		swap2(&cmd->transfer_length); /* length in blocks */
+
+		if (usb_in_endpoint_busy(msc->in_endpoint))
+			goto fail;
+
+		scsi_request_len = cmd->transfer_length * msc->block_size[lun];
+
+		/* Handle the nonsensical, but possible case of the host
+		 * asking to read 0 bytes in the SCSI. That actually makes
+		 * this a Dn case rather than a Di case. */
+		if (scsi_request_len == 0) {
+			res = check_dn_cases(msc, cbw);
+			if (res < 0)
+				goto fail;
+
+			/* If check_dn_cases() succeeded, then the host is
+			 * not expecting any data, so send the CSW*/
+			send_csw(msc, 0, MSC_STATUS_PASSED);
+			goto fail; /* Not a failure, but handled the same */
+		}
+
+		/* READ(10): Device intends to send data to the host (Di) */
+		res = check_di_cases(msc, cbw, scsi_request_len);
+		if (res < 0)
+			goto fail;
+
+		/* Set up the transport state. It's important that this is
+		 * done before the call to the MSC_START_READ() callback
+		 * below, because the application could concievably start
+		 * calling msc_send_to_host() from the callback. */
+		msc->requested_bytes = MIN(cbw_length, scsi_request_len);
+		msc->requested_bytes_cbw = cbw_length;
+		msc->transferred_bytes = 0;
+		msc->state = MSC_DATA_TRANSPORT_IN;
+
+		/* Start the Data-Transport. After receiving the call to
+		 * MSC_START_READ() the application will repeatedly call
+		 * msc_send_to_host() with data read from the medium
+		 * and then call msc_data_complete() when finished. */
+		res = MSC_START_READ(msc, lun,
+			             cmd->logical_block_address,
+			             cmd->transfer_length);
+		if (res < 0) {
+			set_scsi_sense(msc, res);
+			stall_in_and_set_status(msc,
+			                        cbw_length,
+			                        MSC_STATUS_FAILED);
+
+			/* Reset the state */
+			msc->requested_bytes = 0;
+			msc->requested_bytes_cbw = 0;
+			msc->state = MSC_IDLE;
+			goto fail;
+		}
+	}
+#ifdef MSC_WRITE_SUPPORT
+	else if (command == MSC_SCSI_WRITE_10) {
+		uint32_t scsi_request_len;
+		int8_t res;
+		struct msc_scsi_write_10_command *cmd =
+			(struct msc_scsi_write_10_command *) cbw->CBWCB;
+
+		swap4(&cmd->logical_block_address);
+		swap2(&cmd->transfer_length); /* length in blocks */
+
+		scsi_request_len = cmd->transfer_length * msc->block_size[lun];
+
+		/* Handle the nonsensical, but possible case of the host
+		 * asking to write 0 bytes in the SCSI command. That actually
+		 * makes this a Dn case rather than a Do case, and is
+		 * required by the USBCV test. */
+		if (scsi_request_len == 0) {
+			res = check_dn_cases(msc, cbw);
+			if (res < 0)
+				goto fail;
+
+			/* If check_dn_cases() succeeded, then the host is
+			 * not expecting to write any data, so send the CSW */
+			send_csw(msc, 0, MSC_STATUS_PASSED);
+			goto fail; /* Not a failure, but handled the same */
+		}
+
+		/* Write(10): Device intends to receive data
+		 *            from the host (Do) */
+		res = check_do_cases(msc, cbw, scsi_request_len);
+		if (res < 0)
+			goto fail;
+
+		/* Start the Data-Transport. The application will give
+		 * a buffer to put the data into. */
+		res = MSC_START_WRITE(msc,
+		               lun,
+		               cmd->logical_block_address,
+		               cmd->transfer_length,
+		               &msc->rx_buf,
+		               &msc->rx_buf_len,
+		               &msc->operation_complete_callback);
+
+		if (res < 0) {
+			set_scsi_sense(msc, res);
+			stall_out_and_set_status(msc,
+			                         cbw_length,
+			                         MSC_STATUS_FAILED);
+			goto fail;
+		}
+
+		/* Initialize the data transport */
+		msc->requested_bytes = scsi_request_len;
+		msc->requested_bytes_cbw = cbw_length;
+		msc->transferred_bytes = 0;
+		msc->rx_buf_cur = msc->rx_buf;
+		msc->state = MSC_DATA_TRANSPORT_OUT;
+	}
+#endif /* MSC_WRITE_SUPPORT */
+	else {
+		/* Unsupported command. See Axelson, page 69. */
+		const bool direc_is_in = direction_is_in(cbw->bmCBWFlags);
+
+		/* Set error codes which will be requested with REQUEST_SENSE
+		 * by the host later. */
+		msc->sense_key = SCSI_SENSE_KEY_ILLEGAL_REQUEST;
+		msc->additional_sense_code =
+			SCSI_ASC_INVALID_COMMAND_OPERATION_CODE;
+
+		/* Stall appropriate endpoint and send FAILED for the CSW. */
+		if (direc_is_in || cbw_length == 0)
+			stall_in_and_set_status(msc,
+			                 cbw_length, MSC_STATUS_FAILED);
+		else
+			stall_out_and_set_status(msc,
+			                 cbw_length, MSC_STATUS_FAILED);
+
+		goto fail;
+	}
+
+	return;
+
+bad_cbw:
+	/* If the CBW is not valid or is not meaningful, then stall both
+	 * endpoints until a Reset Recovery (BOT 5.3.4) procedure is
+	 * completed by the host (BOT 5.3, Figure 2). */
+	usb_halt_ep_in(msc->in_endpoint);
+	usb_halt_ep_out(msc->out_endpoint);
+	msc->state = MSC_NEEDS_RESET_RECOVERY;
+fail:
+	return;
+}
+
+void msc_clear_halt(uint8_t endpoint, uint8_t direction)
+{
+	struct msc_application_data *msc;
+
+	msc = get_app_data_by_endpoint(endpoint, direction);
+	if (!msc)
+		return;
+
+	if (msc->state == MSC_CSW) {
+		send_csw(msc, msc->residue, msc->status);
+		msc->state = MSC_IDLE;
+	}
+	else if (msc->state == MSC_NEEDS_RESET_RECOVERY) {
+		/* The device needs a Reset Recovery (BOT 5.3.4) but the
+		 * host has not performed a Bulk-Only Mass Storage Reset
+		 * (BOT 3.1) control transfer yet, so the endpoint must
+		 * remain stalled. (BOT 5.3, Figure 2) */
+		if (direction)
+			usb_halt_ep_in(endpoint);
+		else
+			usb_halt_ep_out(endpoint);
+	}
+}
+
+void msc_in_transaction_complete(uint8_t endpoint)
+{
+	struct msc_application_data *msc;
+
+	msc = get_app_data_by_endpoint(endpoint, 1/*IN*/);
+	if (!msc)
+		return;
+
+	if (msc->state == MSC_DATA_TRANSPORT_IN) {
+		send_next_data_transaction(msc);
+	}
+	else if (msc->state == MSC_STALL) {
+		usb_halt_ep_in(msc->in_endpoint);
+		msc->state = MSC_CSW;
+	}
+	else if (msc->state == MSC_CSW) {
+		send_csw(msc, msc->residue, msc->status);
+		msc->state = MSC_IDLE;
+	}
+}
+
+void msc_out_transaction_complete(uint8_t endpoint)
+{
+	struct msc_application_data *msc;
+	const unsigned char *out_buf;
+	uint16_t out_buf_len;
+#ifdef MSC_WRITE_SUPPORT
+	uint8_t res;
+#endif
+
+	msc = get_app_data_by_endpoint(endpoint, 0/*OUT*/);
+	if (!msc)
+		return;
+
+	/* If the OUT endpoint is halted because of a failed write (from
+	 * msc_notify_write_operation_complete(), while interrupts are
+	 * disabled), it's possible that a transaction completed before the
+	 * endpoint was halted, and that interrupt would cause this
+	 * function to be called on a halted endpoint.  Since in that case
+	 * this is a stray interrupt, there's no need to re-arm the
+	 * endpoint.  */
+	if (usb_out_endpoint_halted(endpoint))
+		return;
+
+	out_buf_len = usb_get_out_buffer(endpoint, &out_buf);
+
+#ifdef MSC_WRITE_SUPPORT
+	if (msc->state == MSC_DATA_TRANSPORT_OUT) {
+		/* In the DATA_TRANSPORT_OUT state, treat this transaction
+		 * as data which is to be written to the medium. This call
+		 * may fail if the application's buffer is full, in which
+		 * case the endpoint will not be re-armed, and the data
+		 * will remain in the endpoint buffer until there is space
+		 * in the application's buffer. */
+		res = receive_data(msc, out_buf, out_buf_len);
+	}
+	else if (msc->state == MSC_IDLE) {
+		process_msc_command(msc, out_buf, out_buf_len);
+		res = 0;
+	}
+	else {
+		/* OUT transaction completed when the device is not in a
+		 * state that can handle it. Ignore the data. */
+		res = 0;
+	}
+
+	/* If the data could not be handled by the application yet (because
+	 * the application's buffer is full), don't re-arm the endpoint now.
+	 * Keep the received data in the endpoint's buffer until the
+	 * application has reset its own buffer (and has signaled such by
+	 * calling msc_write_complete()).  At that point, the endpoint's
+	 * data will be handled and the endpoint re-armed.  */
+	if (res == 0)
+		usb_arm_out_endpoint(endpoint);
+	else
+		msc->out_ep_missed_transactions++;
+#else
+	/* If read-only, then OUT transactions are always processed
+	 * and fully handled, leaving no reason to have missed
+	 * transactions, as above. */
+	if (msc->state == MSC_IDLE)
+		process_msc_command(msc, out_buf, out_buf_len);
+	usb_arm_out_endpoint(endpoint);
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usb/src/usb_priv.h	Sat Feb 18 16:25:38 2017 +0000
@@ -0,0 +1,44 @@
+/*
+ *  M-Stack Private Header File - Don't use outside of this directory
+ *  Copyright (C) 2013 Alan Ott <alan@signal11.us>
+ *  Copyright (C) 2013 Signal 11 Software
+ *
+ *  2015-04-04
+ *
+ *  M-Stack is free software: you can redistribute it and/or modify it under
+ *  the terms of the GNU Lesser General Public License as published by the
+ *  Free Software Foundation, version 3; or the Apache License, version 2.0
+ *  as published by the Apache Software Foundation.  If you have purchased a
+ *  commercial license for this software from Signal 11 Software, your
+ *  commerical license superceeds the information in this header.
+ *
+ *  M-Stack is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ *  License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this software.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  You should have received a copy of the Apache License, verion 2.0 along
+ *  with this software.  If not, see <http://www.apache.org/licenses/>.
+ */
+
+#ifndef USB_PRIV_H__
+#define USB_PRIV_H__
+
+#include <usb_config.h>
+
+/* Private M-Stack functions */
+#ifdef USB_USE_INTERRUPTS
+/* Manipulate the transaction (token) interrupt.  There is no stack used
+ * here, so care must be used to ensure that calls to these functions are
+ * not nested.  */
+void usb_disable_transaction_interrupt();
+void usb_enable_transaction_interrupt();
+#else
+#define usb_disable_transaction_interrupt
+#define usb_enable_transaction_interrupt
+#endif
+
+#endif /* USB_PRIV_H__ */
--- a/usb/usb_config.h	Sat Feb 18 16:14:32 2017 +0000
+++ b/usb/usb_config.h	Sat Feb 18 16:25:38 2017 +0000
@@ -110,4 +110,12 @@
 #define CDC_SEND_BREAK_CALLBACK app_send_break_callback
 #endif
 
+/* Define min/max for usb_cdc.c */
+#ifndef min
+#define min(X,Y) (((X) < (Y)) ? (X) : (Y))
+#endif
+#ifndef max
+#define max(X,Y) (((X) > (Y)) ? (X) : (Y))
+#endif
+
 #endif /* USB_CONFIG_H__ */