/* * 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 #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, §or_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(); } } }