circuitpython/ports/atmel-samd/usb_mass_storage.c
Scott Shawcroft 4140b3b944 atmel-samd: Stop double CSW after MSC reads
This explicit zero length xfer leads to a second CSW packet. If
another read was started between the two CSWs then the host gets
confused and resets the device.

On reads, the CSW is automatically sent when we reply with the
total length. Writes must do it manually so they can wait for the
disk to flush the data.
2017-11-03 15:08:55 -07:00

320 lines
10 KiB
C

/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
*
* 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 THE
* AUTHORS OR COPYRIGHT HOLDERS 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.
*/
#include <string.h>
#include "usb_mass_storage.h"
#include "supervisor/shared/autoreload.h"
#include "hal/utils/include/err_codes.h"
#include "hal/utils/include/utils.h"
#include "usb/class/msc/device/mscdf.h"
#include "extmod/vfs.h"
#include "extmod/vfs_fat.h"
#include "lib/oofatfs/ff.h"
#include "lib/oofatfs/diskio.h"
#include "lib/oofatfs/ffconf.h"
#include "py/mpconfig.h"
#include "py/mphal.h"
#include "py/mpstate.h"
#include "py/misc.h"
// The root FS is always at the end of the list.
static fs_user_mount_t* get_vfs(int lun) {
// TODO(tannewt): Return the mount which matches the lun where 0 is the end
// and is counted in reverse.
if (lun > 0) {
return NULL;
}
mp_vfs_mount_t* current_mount = MP_STATE_VM(vfs_mount_table);
if (current_mount == NULL) {
return NULL;
}
while (current_mount->next != NULL) {
current_mount = current_mount->next;
}
return current_mount->obj;
}
/* Inquiry Information */
// This is designed to handle the common case where we have an internal file
// system and an optional SD card.
static uint8_t inquiry_info[2][36];
/* Capacities of Disk */
static uint8_t format_capa[2][8];
/**
* \brief Eject Disk
* \param[in] lun logic unit number
* \return Operation status.
*/
int32_t usb_msc_disk_eject(uint8_t lun) {
if (lun > 1) {
return ERR_NOT_FOUND;
}
fs_user_mount_t* current_mount = get_vfs(lun);
// Return ERR_NOT_READY if not ready, otherwise ERR_NONE.
if (current_mount == NULL) {
return ERR_NOT_FOUND;
}
// TODO(tannewt): Should we flush here?
return ERR_NONE;
}
/**
* \brief Inquiry whether Disk is writable. ERR_DENIED if it is not writable.
* ERR_NONE if it is. ERR_NOT_FOUND if its missing.
* \param[in] lun logic unit number
* \return Operation status.
*/
int32_t usb_msc_disk_is_writable(uint8_t lun) {
if (lun > 1) {
return ERR_NOT_FOUND;
}
fs_user_mount_t* vfs = get_vfs(lun);
if (vfs == NULL) {
return ERR_NOT_FOUND;
}
if (vfs->writeblocks[0] == MP_OBJ_NULL ||
(vfs->flags & FSUSER_USB_WRITABLE) == 0) {
return ERR_DENIED;
}
return ERR_NONE;
}
/**
* \brief Inquiry whether Disk is ready
* \param[in] lun logic unit number
* \return Operation status.
*/
int32_t usb_msc_disk_is_ready(uint8_t lun) {
if (lun > 1) {
return ERR_NOT_FOUND;
}
fs_user_mount_t* current_mount = get_vfs(lun);
if (current_mount == NULL) {
return ERR_NOT_FOUND;
}
return ERR_NONE;
}
/**
* \brief Callback invoked when inquiry data command received
* \param[in] lun logic unit number
* \return Operation status.
*/
uint8_t *usb_msc_inquiry_info(uint8_t lun) {
if (lun > 1) {
return NULL;
} else {
for (uint8_t i = 0; i < 36; i++) {
inquiry_info[lun][i] = 0;
}
inquiry_info[lun][0] = SCSI_INQ_PQ_CONNECTED | SCSI_INQ_DT_DIR_ACCESS;
// connected, direct access
inquiry_info[lun][1] = SCSI_INQ_RMB; // removable medium
inquiry_info[lun][2] = SCSI_INQ_VER_SPC; // SBC version of SCSI primary commands
inquiry_info[lun][3] = SCSI_INQ_RSP_SPC2;// SPC-2 response format
inquiry_info[lun][4] = 31; // 31 bytes following
return &inquiry_info[lun][0];
}
}
/**
* \brief Callback invoked when read format capacities command received
* \param[in] lun logic unit number
*/
uint8_t *usb_msc_get_capacity(uint8_t lun) {
if (lun > 1) {
return NULL;
} else {
fs_user_mount_t * vfs = get_vfs(lun);
uint32_t last_valid_sector = 0;
uint32_t sector_size = 0;
if (vfs == NULL ||
disk_ioctl(vfs, GET_SECTOR_COUNT, &last_valid_sector) != RES_OK ||
disk_ioctl(vfs, GET_SECTOR_SIZE, &sector_size) != RES_OK) {
return NULL;
}
// Subtract one from the sector count to get the last valid sector.
last_valid_sector--;
format_capa[lun][0] = (uint8_t)(last_valid_sector >> 24);
format_capa[lun][1] = (uint8_t)(last_valid_sector >> 16);
format_capa[lun][2] = (uint8_t)(last_valid_sector >> 8);
format_capa[lun][3] = (uint8_t)(last_valid_sector >> 0);
format_capa[lun][4] = (uint8_t)(sector_size >> 24);
format_capa[lun][5] = (uint8_t)(sector_size >> 16);
format_capa[lun][6] = (uint8_t)(sector_size >> 8);
format_capa[lun][7] = (uint8_t)(sector_size >> 0);
// 8 byte response. First 4 bytes are last block address. Second 4
// bytes are sector size.
return &format_capa[lun][0];
}
}
// USB transfer state.
volatile bool usb_busy;
volatile bool active_read;
volatile bool active_write;
volatile uint8_t active_lun;
volatile uint32_t active_addr;
volatile uint32_t active_nblocks;
volatile bool sector_loaded;
COMPILER_ALIGNED(4) uint8_t sector_buffer[512];
/**
* \brief Callback invoked when a new read blocks command received
* \param[in] lun logic unit number
* \param[in] addr start address of disk to be read
* \param[in] nblocks block amount to be read
* \return Operation status.
*/
int32_t usb_msc_new_read(uint8_t lun, uint32_t addr, uint32_t nblocks) {
if (lun > 1) {
return ERR_NOT_FOUND;
}
// Store transfer info so we can service it in the "background".
active_lun = lun;
active_addr = addr;
active_nblocks = nblocks;
active_read = true;
return ERR_NONE;
}
/**
* \brief Callback invoked when a new write blocks command received
* \param[in] lun logic unit number
* \param[in] addr start address of disk to be written
* \param[in] nblocks block amount to be written
* \return Operation status.
*/
int32_t usb_msc_new_write(uint8_t lun, uint32_t addr, uint32_t nblocks) {
if (lun > 1) {
return ERR_NOT_FOUND;
}
fs_user_mount_t * vfs = get_vfs(lun);
// This is used to determine the writeability of the disk from USB.
if (vfs == NULL) {
return ERR_NOT_FOUND;
}
if (vfs->writeblocks[0] == MP_OBJ_NULL ||
(vfs->flags & FSUSER_USB_WRITABLE) == 0) {
return ERR_DENIED;
}
// Store transfer info so we can service it in the "background".
active_lun = lun;
active_addr = addr;
active_nblocks = nblocks;
active_write = true;
sector_loaded = false;
// Return ERR_DENIED when the file system is read-only to the USB host.
return ERR_NONE;
}
/**
* \brief Callback invoked when a blocks transfer is done
* \param[in] lun logic unit number
* \return Operation status.
*/
int32_t usb_msc_xfer_done(uint8_t lun) {
if (lun > 1) {
return ERR_DENIED;
}
if (active_read) {
active_addr += 1;
active_nblocks--;
}
if (active_write) {
sector_loaded = true;
}
usb_busy = false;
return ERR_NONE;
}
// The start_read callback begins a read transaction which we accept but delay our response until the "main thread" calls usb_msc_background. Once it does, we read immediately from the drive into our cache and trigger the USB DMA to output the sector. Once the sector is transmitted, xfer_done will be called.
void usb_msc_background(void) {
if (active_read && !usb_busy) {
if (active_nblocks == 0) {
active_read = false;
return;
}
fs_user_mount_t * vfs = get_vfs(active_lun);
disk_read(vfs, sector_buffer, active_addr, 1);
// TODO(tannewt): Check the read result.
mscdf_xfer_blocks(true, sector_buffer, 1);
usb_busy = true;
}
if (active_write && !usb_busy) {
if (sector_loaded) {
fs_user_mount_t * vfs = get_vfs(active_lun);
disk_write(vfs, sector_buffer, active_addr, 1);
// Since by getting here we assume the mount is read-only to
// MicroPython lets update the cached FatFs sector if its the one
// we just wrote.
#if _MAX_SS != _MIN_SS
if (vfs->ssize == FILESYSTEM_BLOCK_SIZE) {
#else
// The compiler can optimize this away.
if (_MAX_SS == FILESYSTEM_BLOCK_SIZE) {
#endif
if (active_addr == vfs->fatfs.winsect && active_addr > 0) {
memcpy(vfs->fatfs.win,
sector_buffer,
FILESYSTEM_BLOCK_SIZE);
}
}
sector_loaded = false;
active_addr += 1;
active_nblocks--;
}
// Load more blocks from USB if they are needed.
if (active_nblocks > 0) {
int32_t result = mscdf_xfer_blocks(false, sector_buffer, 1);
usb_busy = result != ERR_NONE;
} else {
mscdf_xfer_blocks(false, NULL, 0);
active_write = false;
// This write is complete, start the autoreload clock.
autoreload_start();
}
}
}