2017-10-10 14:03:12 -04:00
/*
* 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.
2017-11-08 13:47:22 -05:00
COMPILER_ALIGNED ( 4 ) static uint8_t inquiry_info [ 2 ] [ 36 ] ;
2017-10-10 14:03:12 -04:00
/* Capacities of Disk */
2017-11-08 13:47:22 -05:00
COMPILER_ALIGNED ( 4 ) static uint8_t format_capa [ 2 ] [ 8 ] ;
2017-10-10 14:03:12 -04:00
/**
* \ 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 ;
}
2017-10-25 20:41:44 -04:00
fs_user_mount_t * current_mount = get_vfs ( lun ) ;
// Return ERR_NOT_READY if not ready, otherwise ERR_NONE.
if ( current_mount = = NULL ) {
2017-10-26 09:08:29 -04:00
return ERR_NOT_FOUND ;
2017-10-25 20:41:44 -04:00
}
2017-10-10 14:03:12 -04:00
// TODO(tannewt): Should we flush here?
return ERR_NONE ;
}
2017-10-26 19:53:25 -04:00
/**
* \ 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 ;
}
2017-10-10 14:03:12 -04:00
/**
* \ 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 ) ;
2017-10-25 20:41:44 -04:00
if ( current_mount = = NULL ) {
2017-10-26 09:08:29 -04:00
return ERR_NOT_FOUND ;
2017-10-10 14:03:12 -04:00
}
2017-10-25 20:41:44 -04:00
return ERR_NONE ;
2017-10-10 14:03:12 -04:00
}
/**
* \ 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 ;
}
2017-10-25 20:41:44 -04:00
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
2017-10-10 14:03:12 -04:00
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 ) {
2017-10-26 19:53:25 -04:00
return ERR_NOT_FOUND ;
2017-10-10 14:03:12 -04:00
}
// 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 ) {
2017-10-26 19:53:25 -04:00
return ERR_NOT_FOUND ;
2017-10-10 14:03:12 -04:00
}
fs_user_mount_t * vfs = get_vfs ( lun ) ;
// This is used to determine the writeability of the disk from USB.
2017-10-26 19:53:25 -04:00
if ( vfs = = NULL ) {
return ERR_NOT_FOUND ;
}
if ( vfs - > writeblocks [ 0 ] = = MP_OBJ_NULL | |
( vfs - > flags & FSUSER_USB_WRITABLE ) = = 0 ) {
2017-10-10 14:03:12 -04:00
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 ;
}
2017-11-08 15:42:50 -05:00
CRITICAL_SECTION_ENTER ( ) ;
2017-10-10 14:03:12 -04:00
if ( active_read ) {
active_addr + = 1 ;
active_nblocks - - ;
2017-11-06 19:29:28 -05:00
if ( active_nblocks = = 0 ) {
active_read = false ;
}
2017-10-10 14:03:12 -04:00
}
if ( active_write ) {
sector_loaded = true ;
}
usb_busy = false ;
2017-11-08 15:42:50 -05:00
CRITICAL_SECTION_LEAVE ( ) ;
2017-10-10 14:03:12 -04:00
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 ) {
fs_user_mount_t * vfs = get_vfs ( active_lun ) ;
disk_read ( vfs , sector_buffer , active_addr , 1 ) ;
2017-11-08 15:42:50 -05:00
CRITICAL_SECTION_ENTER ( ) ;
int32_t result = mscdf_xfer_blocks ( true , sector_buffer , 1 ) ;
usb_busy = result = = ERR_NONE ;
CRITICAL_SECTION_LEAVE ( ) ;
2017-10-10 14:03:12 -04:00
}
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 ) {
2017-11-08 15:42:50 -05:00
// Turn off interrupts because with them on,
// usb_msc_xfer_done could be called before we update
// usb_busy. If that happened, we'd overwrite the fact that
// the transfer actually already finished.
CRITICAL_SECTION_ENTER ( ) ;
2017-10-10 14:03:12 -04:00
int32_t result = mscdf_xfer_blocks ( false , sector_buffer , 1 ) ;
2017-11-08 15:42:50 -05:00
usb_busy = result = = ERR_NONE ;
CRITICAL_SECTION_LEAVE ( ) ;
2017-10-10 14:03:12 -04:00
} else {
mscdf_xfer_blocks ( false , NULL , 0 ) ;
active_write = false ;
// This write is complete, start the autoreload clock.
autoreload_start ( ) ;
}
}
}