Commit b33a7705 authored by Damien George's avatar Damien George
Browse files

extmod/fsusermount: Support mounting of multiple block devices.

This patch adds support to fsusermount for multiple block devices
(instead of just one).  The maximum allowed is fixed at compile time by
the size of the fs_user_mount array accessed via MP_STATE_PORT, which
in turn is set by MICROPY_FATFS_VOLUMES.

With this patch, stmhal (which is still tightly coupled to fsusermount)
is also modified to support mounting multiple devices   And the flash and
SD card are now just two block devices that are mounted at start up if
they exist (and they have special native code to make them more
efficient).
parent 34023eb6
......@@ -53,25 +53,38 @@ STATIC mp_obj_t fatfs_mount_mkfs(mp_uint_t n_args, const mp_obj_t *pos_args, mp_
if (device == mp_const_none) {
// umount
FRESULT res = FR_NO_FILESYSTEM;
if (MP_STATE_PORT(fs_user_mount) != NULL) {
res = f_mount(NULL, MP_STATE_PORT(fs_user_mount)->str, 0);
m_del_obj(fs_user_mount_t, MP_STATE_PORT(fs_user_mount));
MP_STATE_PORT(fs_user_mount) = NULL;
for (size_t i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount)); ++i) {
fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[i];
if (vfs != NULL && !memcmp(mnt_str, vfs->str, mnt_len + 1)) {
res = f_mount(NULL, vfs->str, 0);
if (vfs->flags & FSUSER_FREE_OBJ) {
m_del_obj(fs_user_mount_t, vfs);
}
MP_STATE_PORT(fs_user_mount)[i] = NULL;
break;
}
}
if (res != FR_OK) {
nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "can't umount"));
}
} else {
// mount
if (MP_STATE_PORT(fs_user_mount) != NULL) {
nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "device already mounted"));
size_t i = 0;
for (; i < MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount)); ++i) {
if (MP_STATE_PORT(fs_user_mount)[i] == NULL) {
break;
}
}
if (i == MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount))) {
nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "too many devices mounted"));
}
// create new object
fs_user_mount_t *vfs;
MP_STATE_PORT(fs_user_mount) = vfs = m_new_obj(fs_user_mount_t);
MP_STATE_PORT(fs_user_mount)[i] = vfs = m_new_obj(fs_user_mount_t);
vfs->str = mnt_str;
vfs->len = mnt_len;
vfs->flags = FSUSER_FREE_OBJ;
// load block protocol methods
mp_load_method(device, MP_QSTR_readblocks, vfs->readblocks);
......@@ -79,7 +92,7 @@ STATIC mp_obj_t fatfs_mount_mkfs(mp_uint_t n_args, const mp_obj_t *pos_args, mp_
mp_load_method_maybe(device, MP_QSTR_ioctl, vfs->u.ioctl);
if (vfs->u.ioctl[0] != MP_OBJ_NULL) {
// device supports new block protocol, so indicate it
vfs->u.old.count[1] = MP_OBJ_SENTINEL;
vfs->flags |= FSUSER_HAVE_IOCTL;
} else {
// no ioctl method, so assume the device uses the old block protocol
mp_load_method_maybe(device, MP_QSTR_sync, vfs->u.old.sync);
......@@ -114,7 +127,7 @@ mkfs_error:
if (res != FR_OK) {
goto mkfs_error;
}
MP_STATE_PORT(fs_user_mount) = NULL;
MP_STATE_PORT(fs_user_mount)[i] = NULL;
}
} else {
nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "can't mount"));
......@@ -141,30 +154,39 @@ STATIC mp_obj_t fatfs_mount(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t
MP_DEFINE_CONST_FUN_OBJ_KW(fsuser_mount_obj, 2, fatfs_mount);
STATIC mp_obj_t fatfs_umount(mp_obj_t bdev_or_path_in) {
if (MP_STATE_PORT(fs_user_mount) == NULL) {
goto einval;
}
size_t i = 0;
if (MP_OBJ_IS_STR(bdev_or_path_in)) {
mp_uint_t mnt_len;
const char *mnt_str = mp_obj_str_get_data(bdev_or_path_in, &mnt_len);
if (memcmp(mnt_str, MP_STATE_PORT(fs_user_mount)->str, mnt_len + 1)) {
goto einval;
for (; i < MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount)); ++i) {
fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[i];
if (!memcmp(mnt_str, vfs->str, mnt_len + 1)) {
break;
}
}
} else if (bdev_or_path_in != MP_STATE_PORT(fs_user_mount)->readblocks[1]) {
goto einval;
} else {
for (; i < MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount)); ++i) {
fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[i];
if (bdev_or_path_in == vfs->readblocks[1]) {
break;
}
}
}
if (i == MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount))) {
nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(EINVAL)));
}
FRESULT res = f_mount(NULL, MP_STATE_PORT(fs_user_mount)->str, 0);
m_del_obj(fs_user_mount_t, MP_STATE_PORT(fs_user_mount));
MP_STATE_PORT(fs_user_mount) = NULL;
fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[i];
FRESULT res = f_mount(NULL, vfs->str, 0);
if (vfs->flags & FSUSER_FREE_OBJ) {
m_del_obj(fs_user_mount_t, vfs);
}
MP_STATE_PORT(fs_user_mount)[i] = NULL;
if (res != FR_OK) {
nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "can't umount"));
}
return mp_const_none;
einval:
nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(EINVAL)));
}
MP_DEFINE_CONST_FUN_OBJ_1(fsuser_umount_obj, fatfs_umount);
......
......@@ -24,13 +24,18 @@
* THE SOFTWARE.
*/
// these are the values for fs_user_mount_t.flags
#define FSUSER_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func
#define FSUSER_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount
#define FSUSER_HAVE_IOCTL (0x0004) // new protocol with ioctl
typedef struct _fs_user_mount_t {
const char *str;
mp_uint_t len;
uint16_t len; // length of str
uint16_t flags;
mp_obj_t readblocks[4];
mp_obj_t writeblocks[4];
// new protocol uses just ioctl, old uses sync (optional) and count
// if ioctl[3]=count[1]=MP_OBJ_SENTINEL then we have the new protocol, else old
union {
mp_obj_t ioctl[4];
struct {
......
......@@ -35,17 +35,23 @@
#include "py/runtime.h"
#include "lib/fatfs/ff.h" /* FatFs lower layer API */
#include "lib/fatfs/diskio.h" /* FatFs lower layer API */
#include "storage.h"
#include "sdcard.h"
#include "extmod/fsusermount.h"
// constants for block protocol ioctl
//#define BP_IOCTL_INIT (1) // unused
#define BP_IOCTL_INIT (1)
//#define BP_IOCTL_DEINIT (2) // unused
#define BP_IOCTL_SYNC (3)
#define BP_IOCTL_SEC_COUNT (4)
#define BP_IOCTL_SEC_SIZE (5)
STATIC fs_user_mount_t *disk_get_device(uint id) {
if (id < MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount))) {
return MP_STATE_PORT(fs_user_mount)[id];
} else {
return NULL;
}
}
/*-----------------------------------------------------------------------*/
/* Initialize a Drive */
/*-----------------------------------------------------------------------*/
......@@ -54,33 +60,27 @@ DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber (0..) */
)
{
switch (pdrv) {
#if MICROPY_HW_HAS_FLASH
case PD_FLASH:
storage_init();
return 0;
#endif
#if MICROPY_HW_HAS_SDCARD
case PD_SDCARD:
if (!sdcard_power_on()) {
return STA_NODISK;
}
// TODO return STA_PROTECT if SD card is read only
return 0;
#endif
fs_user_mount_t *vfs = disk_get_device(pdrv);
if (vfs == NULL) {
return STA_NOINIT;
}
case PD_USER:
if (MP_STATE_PORT(fs_user_mount) == NULL) {
return STA_NODISK;
}
if (MP_STATE_PORT(fs_user_mount)->writeblocks[0] == MP_OBJ_NULL) {
return STA_PROTECT;
}
return 0;
if (vfs->flags & FSUSER_HAVE_IOCTL) {
// new protocol with ioctl; call ioctl(INIT, 0)
vfs->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(BP_IOCTL_INIT);
vfs->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(0); // unused
mp_obj_t ret = mp_call_method_n_kw(2, 0, vfs->u.ioctl);
if (MP_OBJ_SMALL_INT_VALUE(ret) != 0) {
// error initialising
return STA_NOINIT;
}
}
return STA_NOINIT;
if (vfs->writeblocks[0] == MP_OBJ_NULL) {
return STA_PROTECT;
} else {
return 0;
}
}
/*-----------------------------------------------------------------------*/
......@@ -91,28 +91,16 @@ DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber (0..) */
)
{
switch (pdrv) {
case PD_FLASH :
// flash is ready
return 0;
#if MICROPY_HW_HAS_SDCARD
case PD_SDCARD:
// TODO return STA_PROTECT if SD card is read only
return 0;
#endif
case PD_USER:
if (MP_STATE_PORT(fs_user_mount) == NULL) {
return STA_NODISK;
}
if (MP_STATE_PORT(fs_user_mount)->writeblocks[0] == MP_OBJ_NULL) {
return STA_PROTECT;
}
return 0;
fs_user_mount_t *vfs = disk_get_device(pdrv);
if (vfs == NULL) {
return STA_NOINIT;
}
return STA_NOINIT;
if (vfs->writeblocks[0] == MP_OBJ_NULL) {
return STA_PROTECT;
} else {
return 0;
}
}
/*-----------------------------------------------------------------------*/
......@@ -126,37 +114,24 @@ DRESULT disk_read (
UINT count /* Number of sectors to read (1..128) */
)
{
switch (pdrv) {
#if MICROPY_HW_HAS_FLASH
case PD_FLASH:
for (int i = 0; i < count; i++) {
if (!storage_read_block(buff + i * FLASH_BLOCK_SIZE, sector + i)) {
return RES_ERROR;
}
}
return RES_OK;
#endif
#if MICROPY_HW_HAS_SDCARD
case PD_SDCARD:
if (sdcard_read_blocks(buff, sector, count) != 0) {
return RES_ERROR;
}
return RES_OK;
#endif
fs_user_mount_t *vfs = disk_get_device(pdrv);
if (vfs == NULL) {
return RES_PARERR;
}
case PD_USER:
if (MP_STATE_PORT(fs_user_mount) == NULL) {
// nothing mounted
return RES_ERROR;
}
MP_STATE_PORT(fs_user_mount)->readblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
MP_STATE_PORT(fs_user_mount)->readblocks[3] = mp_obj_new_bytearray_by_ref(count * 512, buff);
mp_call_method_n_kw(2, 0, MP_STATE_PORT(fs_user_mount)->readblocks);
return RES_OK;
if (vfs->flags & FSUSER_NATIVE) {
mp_uint_t (*f)(uint8_t*, uint32_t, uint32_t) = (void*)vfs->readblocks[2];
if (f(buff, sector, count) != 0) {
return RES_ERROR;
}
} else {
vfs->readblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
vfs->readblocks[3] = mp_obj_new_bytearray_by_ref(count * 512, buff);
mp_call_method_n_kw(2, 0, vfs->readblocks);
// TODO handle error return
}
return RES_PARERR;
return RES_OK;
}
/*-----------------------------------------------------------------------*/
......@@ -171,41 +146,29 @@ DRESULT disk_write (
UINT count /* Number of sectors to write (1..128) */
)
{
switch (pdrv) {
#if MICROPY_HW_HAS_FLASH
case PD_FLASH:
for (int i = 0; i < count; i++) {
if (!storage_write_block(buff + i * FLASH_BLOCK_SIZE, sector + i)) {
return RES_ERROR;
}
}
return RES_OK;
#endif
fs_user_mount_t *vfs = disk_get_device(pdrv);
if (vfs == NULL) {
return RES_PARERR;
}
#if MICROPY_HW_HAS_SDCARD
case PD_SDCARD:
if (sdcard_write_blocks(buff, sector, count) != 0) {
return RES_ERROR;
}
return RES_OK;
#endif
if (vfs->writeblocks[0] == MP_OBJ_NULL) {
// read-only block device
return RES_WRPRT;
}
case PD_USER:
if (MP_STATE_PORT(fs_user_mount) == NULL) {
// nothing mounted
return RES_ERROR;
}
if (MP_STATE_PORT(fs_user_mount)->writeblocks[0] == MP_OBJ_NULL) {
// read-only block device
return RES_ERROR;
}
MP_STATE_PORT(fs_user_mount)->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
MP_STATE_PORT(fs_user_mount)->writeblocks[3] = mp_obj_new_bytearray_by_ref(count * 512, (void*)buff);
mp_call_method_n_kw(2, 0, MP_STATE_PORT(fs_user_mount)->writeblocks);
return RES_OK;
if (vfs->flags & FSUSER_NATIVE) {
mp_uint_t (*f)(const uint8_t*, uint32_t, uint32_t) = (void*)vfs->writeblocks[2];
if (f(buff, sector, count) != 0) {
return RES_ERROR;
}
} else {
vfs->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
vfs->writeblocks[3] = mp_obj_new_bytearray_by_ref(count * 512, (void*)buff);
mp_call_method_n_kw(2, 0, vfs->writeblocks);
// TODO handle error return
}
return RES_PARERR;
return RES_OK;
}
#endif
......@@ -221,100 +184,69 @@ DRESULT disk_ioctl (
void *buff /* Buffer to send/receive control data */
)
{
switch (pdrv) {
#if MICROPY_HW_HAS_FLASH
case PD_FLASH:
switch (cmd) {
case CTRL_SYNC:
storage_flush();
return RES_OK;
case GET_BLOCK_SIZE:
*((DWORD*)buff) = 1; // high-level sector erase size in units of the small (512) block size
return RES_OK;
}
break;
#endif
fs_user_mount_t *vfs = disk_get_device(pdrv);
if (vfs == NULL) {
return RES_PARERR;
}
#if MICROPY_HW_HAS_SDCARD
case PD_SDCARD:
switch (cmd) {
case CTRL_SYNC:
return RES_OK;
if (vfs->flags & FSUSER_HAVE_IOCTL) {
// new protocol with ioctl
switch (cmd) {
case CTRL_SYNC:
vfs->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(BP_IOCTL_SYNC);
vfs->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(0); // unused
mp_call_method_n_kw(2, 0, vfs->u.ioctl);
return RES_OK;
case GET_SECTOR_COUNT: {
vfs->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(BP_IOCTL_SEC_COUNT);
vfs->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(0); // unused
mp_obj_t ret = mp_call_method_n_kw(2, 0, vfs->u.ioctl);
*((DWORD*)buff) = mp_obj_get_int(ret);
return RES_OK;
}
case GET_SECTOR_SIZE: {
vfs->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(BP_IOCTL_SEC_SIZE);
vfs->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(0); // unused
mp_obj_t ret = mp_call_method_n_kw(2, 0, vfs->u.ioctl);
*((WORD*)buff) = mp_obj_get_int(ret);
return RES_OK;
}
case GET_BLOCK_SIZE:
*((DWORD*)buff) = 1; // erase block size in units of sector size
return RES_OK;
default:
return RES_PARERR;
}
} else {
// old protocol with sync and count
switch (cmd) {
case CTRL_SYNC:
if (vfs->u.old.sync[0] != MP_OBJ_NULL) {
mp_call_method_n_kw(0, 0, vfs->u.old.sync);
}
return RES_OK;
case GET_BLOCK_SIZE:
*((DWORD*)buff) = 1; // high-level sector erase size in units of the small (512) block size
return RES_OK;
case GET_SECTOR_COUNT: {
mp_obj_t ret = mp_call_method_n_kw(0, 0, vfs->u.old.count);
*((DWORD*)buff) = mp_obj_get_int(ret);
return RES_OK;
}
break;
#endif
case PD_USER: {
fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount);
if (vfs == NULL) {
// nothing mounted
return RES_ERROR;
}
if (vfs->u.old.count[1] == MP_OBJ_SENTINEL) {
// new protocol with ioctl
switch (cmd) {
case CTRL_SYNC:
vfs->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(BP_IOCTL_SYNC);
vfs->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(0); // unused
mp_call_method_n_kw(2, 0, vfs->u.ioctl);
vfs->u.ioctl[3] = MP_OBJ_SENTINEL; // indicate new protocol
return RES_OK;
case GET_SECTOR_COUNT: {
vfs->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(BP_IOCTL_SEC_COUNT);
vfs->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(0); // unused
mp_obj_t ret = mp_call_method_n_kw(2, 0, vfs->u.ioctl);
*((DWORD*)buff) = mp_obj_get_int(ret);
vfs->u.ioctl[3] = MP_OBJ_SENTINEL; // indicate new protocol
return RES_OK;
}
case GET_SECTOR_SIZE: {
vfs->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(BP_IOCTL_SEC_SIZE);
vfs->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(0); // unused
mp_obj_t ret = mp_call_method_n_kw(2, 0, vfs->u.ioctl);
*((WORD*)buff) = mp_obj_get_int(ret);
vfs->u.ioctl[3] = MP_OBJ_SENTINEL; // indicate new protocol
return RES_OK;
}
case GET_BLOCK_SIZE:
*((DWORD*)buff) = 1; // erase block size in units of sector size
return RES_OK;
}
} else {
// old protocol with sync and count
switch (cmd) {
case CTRL_SYNC:
if (vfs->u.old.sync[0] != MP_OBJ_NULL) {
mp_call_method_n_kw(0, 0, vfs->u.old.sync);
}
return RES_OK;
case GET_SECTOR_COUNT: {
mp_obj_t ret = mp_call_method_n_kw(0, 0, vfs->u.old.count);
*((DWORD*)buff) = mp_obj_get_int(ret);
return RES_OK;
}
case GET_SECTOR_SIZE:
*((WORD*)buff) = 512; // old protocol had fixed sector size
return RES_OK;
case GET_BLOCK_SIZE:
*((DWORD*)buff) = 1; // erase block size in units of sector size
return RES_OK;
}
}
break;
case GET_SECTOR_SIZE:
*((WORD*)buff) = 512; // old protocol had fixed sector size
return RES_OK;
case GET_BLOCK_SIZE:
*((DWORD*)buff) = 1; // erase block size in units of sector size
return RES_OK;
default:
return RES_PARERR;
}
}
return RES_PARERR;
}
#endif
......@@ -30,10 +30,11 @@
#include "lib/fatfs/diskio.h" /* FatFs lower layer API */
#include "rtc.h"
const PARTITION VolToPart[] = {
const PARTITION VolToPart[MICROPY_FATFS_VOLUMES] = {
{0, 1}, // Logical drive 0 ==> Physical drive 0, 1st partition
{1, 0}, // Logical drive 1 ==> Physical drive 1 (auto detection)
{2, 0}, // Logical drive 2 ==> Physical drive 2 (auto detection)
{3, 0}, // Logical drive 3 ==> Physical drive 3 (auto detection)
/*
{0, 2}, // Logical drive 2 ==> Physical drive 0, 2nd partition
{0, 3}, // Logical drive 3 ==> Physical drive 0, 3rd partition
......
......@@ -60,26 +60,18 @@ int ff_get_ldnumber (const TCHAR **path) {
#endif
}
if (check_path(path, "/flash", 6)) {
return PD_FLASH;
} else if (check_path(path, "/sd", 3)) {
return PD_SDCARD;
} else if (MP_STATE_PORT(fs_user_mount) != NULL && check_path(path, MP_STATE_PORT(fs_user_mount)->str, MP_STATE_PORT(fs_user_mount)->len)) {
return PD_USER;
} else {
return -1;
for (size_t i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount)); ++i) {
fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[i];
if (vfs != NULL && check_path(path, vfs->str, vfs->len)) {
return i;
}
}
return -1;
}
void ff_get_volname(BYTE vol, TCHAR **dest) {
if (vol == PD_FLASH) {
memcpy(*dest, "/flash", 6);
*dest += 6;
} else if (vol == PD_SDCARD) {
memcpy(*dest, "/sd", 3);
*dest += 3;
} else {
memcpy(*dest, MP_STATE_PORT(fs_user_mount)->str, MP_STATE_PORT(fs_user_mount)->len);
*dest += MP_STATE_PORT(fs_user_mount)->len;
}
fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[vol];
memcpy(*dest, vfs->str, vfs->len);
*dest += vfs->len;
}
......@@ -38,6 +38,7 @@
#include "lib/utils/pyexec.h"
#include "lib/fatfs/ff.h"
#include "extmod/fsusermount.h"
#include "systick.h"
#include "pendsv.h"
......@@ -64,10 +65,7 @@
void SystemClock_Config(void);
static FATFS fatfs0;
#if MICROPY_HW_HAS_SDCARD
static FATFS fatfs1;
#endif
fs_user_mount_t fs_user_mount_flash;
void flash_error(int n) {
for (int i = 0; i < n; i++) {
......@@ -169,8 +167,18 @@ static const char fresh_readme_txt[] =
// we don't make this function static because it needs a lot of stack and we
// want it to be executed without using stack within main() function
void init_flash_fs(uint reset_mode) {
// init the vfs object
fs_user_mount_t *vfs = &fs_user_mount_flash;
vfs->str = "/flash";
vfs->len = 6;
vfs->flags = 0;
pyb_flash_init_vfs(vfs);
// put the flash device in slot 0 (it will be unused at this point)
MP_STATE_PORT(fs_user_mount)[0] = vfs;
// try to mount the flash
FRESULT res = f_mount(&fatfs0, "/flash", 1);
FRESULT res = f_mount(&vfs->fatfs, vfs->str, 1);
if (reset_mode == 3 || res == FR_NO_FILESYSTEM) {
// no filesystem, or asked to reset it, so create a fresh one
......@@ -183,7 +191,9 @@ void init_flash_fs(uint reset_mode) {