From 7690b13953162dcf398619bbfa6809d3d4e7dc67 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 27 Dec 2014 20:20:51 +0000 Subject: [PATCH] stmhal: Add ability to mount custom block device. --- docs/library/pyb.rst | 31 +++++++++++ stmhal/Makefile | 1 + stmhal/diskio.c | 58 ++++++++++++++++++++ stmhal/ffconf.c | 56 ++++++++++++-------- stmhal/ffconf.h | 2 +- stmhal/fsusermount.c | 119 ++++++++++++++++++++++++++++++++++++++++++ stmhal/fsusermount.h | 39 ++++++++++++++ stmhal/modpyb.c | 4 ++ stmhal/moduos.c | 6 +++ stmhal/qstrdefsport.h | 9 ++++ 10 files changed, 302 insertions(+), 23 deletions(-) create mode 100644 stmhal/fsusermount.c create mode 100644 stmhal/fsusermount.h diff --git a/docs/library/pyb.rst b/docs/library/pyb.rst index 8af0090c14..3d05a40a74 100644 --- a/docs/library/pyb.rst +++ b/docs/library/pyb.rst @@ -154,6 +154,37 @@ Miscellaneous functions Print out lots of information about the board. +.. function:: mount(device, mountpoint, \*, readonly=False, mkfs=False) + + Mount a block device and make it available as part of the filesystem. + ``device`` must be an object that provides the block protocol: + + - ``readblocks(self, blocknum, buf)`` + - ``writeblocks(self, blocknum, buf)`` (optional) + - ``count(self)`` + - ``sync(self)`` (optional) + + ``readblocks`` and ``writeblocks`` should copy data between ``buf`` and + the block device, starting from block number ``blocknum`` on the device. + ``buf`` will be a bytearray with length a multiple of 512. If + ``writeblocks`` is not defined then the device is mounted read-only. + The return value of these two functions is ignored. + + ``count`` should return the number of blocks available on the device. + ``sync``, if implemented, should sync the data on the device. + + The parameter ``mountpoint`` is the location in the root of the filesystem + to mount the device. It must begin with a forward-slash. + + If ``readonly`` is ``True``, then the device is mounted read-only, + otherwise it is mounted read-write. + + If ``mkfs`` is ``True``, then a new filesystem is created if one does not + already exist. + + To unmount a device, pass ``None`` as the device and the mount location + as ``mountpoint``. + .. function:: repl_uart(uart) Get or set the UART object that the REPL is repeated on. diff --git a/stmhal/Makefile b/stmhal/Makefile index d9d8548773..422b31dfa3 100644 --- a/stmhal/Makefile +++ b/stmhal/Makefile @@ -133,6 +133,7 @@ SRC_C = \ storage.c \ file.c \ sdcard.c \ + fsusermount.c \ diskio.c \ ffconf.c \ lcd.c \ diff --git a/stmhal/diskio.c b/stmhal/diskio.c index 44059b04d5..baebd27929 100644 --- a/stmhal/diskio.c +++ b/stmhal/diskio.c @@ -36,16 +36,19 @@ #include "misc.h" #include "qstr.h" #include "obj.h" +#include "runtime.h" #include "systick.h" #include "rtc.h" #include "storage.h" #include "sdcard.h" #include "ff.h" /* FatFs lower layer API */ #include "diskio.h" /* FatFs lower layer API */ +#include "fsusermount.h" const PARTITION VolToPart[] = { {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) /* {0, 2}, // Logical drive 2 ==> Physical drive 0, 2nd partition {0, 3}, // Logical drive 3 ==> Physical drive 0, 3rd partition @@ -55,6 +58,7 @@ const PARTITION VolToPart[] = { /* Definitions of physical drive number for each media */ #define PD_FLASH (0) #define PD_SDCARD (1) +#define PD_USER (2) /*-----------------------------------------------------------------------*/ /* Initialize a Drive */ @@ -77,6 +81,15 @@ DSTATUS disk_initialize ( // TODO return STA_PROTECT if SD card is read only return 0; #endif + + case PD_USER: + if (fs_user_mount == NULL) { + return STA_NODISK; + } + if (fs_user_mount->writeblocks[0] == MP_OBJ_NULL) { + return STA_PROTECT; + } + return 0; } return STA_NOINIT; @@ -100,6 +113,15 @@ DSTATUS disk_status ( // TODO return STA_PROTECT if SD card is read only return 0; #endif + + case PD_USER: + if (fs_user_mount == NULL) { + return STA_NODISK; + } + if (fs_user_mount->writeblocks[0] == MP_OBJ_NULL) { + return STA_PROTECT; + } + return 0; } return STA_NOINIT; @@ -132,6 +154,12 @@ DRESULT disk_read ( } return RES_OK; #endif + + case PD_USER: + fs_user_mount->readblocks[2] = MP_OBJ_NEW_SMALL_INT(sector); + fs_user_mount->readblocks[3] = mp_obj_new_bytearray_by_ref(count * 512, buff); + mp_call_method_n_kw(2, 0, fs_user_mount->readblocks); + return RES_OK; } return RES_PARERR; @@ -165,6 +193,16 @@ DRESULT disk_write ( } return RES_OK; #endif + + case PD_USER: + if (fs_user_mount->writeblocks[0] == MP_OBJ_NULL) { + // read-only block device + return RES_ERROR; + } + fs_user_mount->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(sector); + fs_user_mount->writeblocks[3] = mp_obj_new_bytearray_by_ref(count * 512, (void*)buff); + mp_call_method_n_kw(2, 0, fs_user_mount->writeblocks); + return RES_OK; } return RES_PARERR; @@ -208,6 +246,26 @@ DRESULT disk_ioctl ( } break; #endif + + case PD_USER: + switch (cmd) { + case CTRL_SYNC: + if (fs_user_mount->sync[0] != MP_OBJ_NULL) { + mp_call_method_n_kw(0, 0, fs_user_mount->sync); + } + return RES_OK; + + case GET_BLOCK_SIZE: + *((DWORD*)buff) = 1; // high-level sector erase size in units of the small (512) bl + return RES_OK; + + case GET_SECTOR_COUNT: { + mp_obj_t ret = mp_call_method_n_kw(0, 0, fs_user_mount->count); + *((DWORD*)buff) = mp_obj_get_int(ret); + return RES_OK; + } + } + break; } return RES_PARERR; diff --git a/stmhal/ffconf.c b/stmhal/ffconf.c index e19acad32d..8ed7543433 100644 --- a/stmhal/ffconf.c +++ b/stmhal/ffconf.c @@ -26,43 +26,52 @@ #include -#include "mpconfigport.h" +#include "mpconfig.h" +#include "misc.h" +#include "qstr.h" +#include "obj.h" #include "ff.h" #include "ffconf.h" +#include "fsusermount.h" +#if _FS_RPATH extern BYTE ff_CurrVol; +#endif + +STATIC bool check_path(const TCHAR **path, const char *mount_point_str, mp_uint_t mount_point_len) { + if (strncmp(*path, mount_point_str, mount_point_len) == 0) { + if ((*path)[mount_point_len] == '/') { + *path += mount_point_len; + return true; + } else if ((*path)[mount_point_len] == '\0') { + *path = "/"; + return true; + } + } + return false; +} // "path" is the path to lookup; will advance this pointer beyond the volume name. // Returns logical drive number (-1 means invalid path). -int ff_get_ldnumber (const TCHAR** path) { +int ff_get_ldnumber (const TCHAR **path) { if (!(*path)) { return -1; } if (**path != '/') { -#if _FS_RPATH + #if _FS_RPATH return ff_CurrVol; -#else + #else return -1; -#endif - } else if (strncmp(*path, "/flash", 6) == 0) { - if ((*path)[6] == '/') { - *path += 6; - } else if ((*path)[6] == '\0') { - *path = "/"; - } else { - return -1; - } + #endif + } + + if (check_path(path, "/flash", 6)) { return 0; - } else if (strncmp(*path, "/sd", 3) == 0) { - if ((*path)[3] == '/') { - *path += 3; - } else if ((*path)[3] == '\0') { - *path = "/"; - } else { - return -1; - } + } else if (check_path(path, "/sd", 3)) { return 1; + } else if (fs_user_mount != NULL && check_path(path, fs_user_mount->str, fs_user_mount->len)) { + return 2; } else { return -1; } @@ -72,8 +81,11 @@ void ff_get_volname(BYTE vol, TCHAR **dest) { if (vol == 0) { memcpy(*dest, "/flash", 6); *dest += 6; - } else { + } else if (vol == 1) { memcpy(*dest, "/sd", 3); *dest += 3; + } else { + memcpy(*dest, fs_user_mount->str, fs_user_mount->len); + *dest += fs_user_mount->len; } } diff --git a/stmhal/ffconf.h b/stmhal/ffconf.h index 868121118a..d0cd8bce98 100644 --- a/stmhal/ffconf.h +++ b/stmhal/ffconf.h @@ -176,7 +176,7 @@ / Drive/Volume Configurations /---------------------------------------------------------------------------*/ -#define _VOLUMES 2 +#define _VOLUMES 3 /* Number of volumes (logical drives) to be used. */ diff --git a/stmhal/fsusermount.c b/stmhal/fsusermount.c new file mode 100644 index 0000000000..e6b915382a --- /dev/null +++ b/stmhal/fsusermount.c @@ -0,0 +1,119 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * 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 "mpconfig.h" +#include "misc.h" +#include "nlr.h" +#include "qstr.h" +#include "obj.h" +#include "runtime.h" +#include "ff.h" +#include "fsusermount.h" + +// for user-mountable block device +fs_user_mount_t *fs_user_mount; + +STATIC mp_obj_t pyb_mount(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_readonly, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_mkfs, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + }; + + // parse args + mp_obj_t device = pos_args[0]; + mp_obj_t mount_point = pos_args[1]; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // get the mount point + mp_uint_t mnt_len; + const char *mnt_str = mp_obj_str_get_data(mount_point, &mnt_len); + + if (device == mp_const_none) { + // umount + FRESULT res = FR_NO_FILESYSTEM; + if (fs_user_mount != NULL) { + res = f_mount(NULL, fs_user_mount->str, 0); + m_del_obj(fs_user_mount_t, fs_user_mount); + fs_user_mount = NULL; + } + if (res != FR_OK) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "can't umount")); + } + } else { + // mount + if (fs_user_mount != NULL) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "device already mounted")); + } + + // create new object + fs_user_mount = m_new_obj(fs_user_mount_t); + fs_user_mount->str = mnt_str; + fs_user_mount->len = mnt_len; + + // load block protocol methods + mp_load_method(device, MP_QSTR_readblocks, fs_user_mount->readblocks); + mp_load_method_maybe(device, MP_QSTR_writeblocks, fs_user_mount->writeblocks); + mp_load_method_maybe(device, MP_QSTR_sync, fs_user_mount->sync); + mp_load_method(device, MP_QSTR_count, fs_user_mount->count); + + // Read-only device indicated by writeblocks[0] == MP_OBJ_NULL. + // User can specify read-only device by: + // 1. readonly=True keyword argument + // 2. nonexistent writeblocks method (then writeblocks[0] == MP_OBJ_NULL already) + if (args[0].u_bool) { + fs_user_mount->writeblocks[0] = MP_OBJ_NULL; + } + + // mount the block device + FRESULT res = f_mount(&fs_user_mount->fatfs, fs_user_mount->str, 1); + + // check the result + if (res == FR_OK) { + } else if (res == FR_NO_FILESYSTEM && args[1].u_bool) { + res = f_mkfs(fs_user_mount->str, 1, 0); + if (res != FR_OK) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "can't mkfs")); + } + } else { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "can't mount")); + } + + /* + if (fs_user_mount->writeblocks[0] == MP_OBJ_NULL) { + printf("mounted read-only"); + } else { + printf("mounted read-write"); + } + DWORD nclst; + FATFS *fatfs; + f_getfree(fs_user_mount.str, &nclst, &fatfs); + printf(" on %s with %u bytes free\n", fs_user_mount.str, (uint)(nclst * fatfs->csize * 512)); + */ + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(pyb_mount_obj, 2, pyb_mount); diff --git a/stmhal/fsusermount.h b/stmhal/fsusermount.h new file mode 100644 index 0000000000..5a983464ee --- /dev/null +++ b/stmhal/fsusermount.h @@ -0,0 +1,39 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * 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. + */ + +typedef struct _fs_user_mount_t { + const char *str; + mp_uint_t len; + mp_obj_t readblocks[4]; + mp_obj_t writeblocks[4]; + mp_obj_t sync[2]; + mp_obj_t count[2]; + FATFS fatfs; +} fs_user_mount_t; + +extern fs_user_mount_t *fs_user_mount; + +MP_DECLARE_CONST_FUN_OBJ(pyb_mount_obj); diff --git a/stmhal/modpyb.c b/stmhal/modpyb.c index 98fa62ac2c..03e65f1be0 100644 --- a/stmhal/modpyb.c +++ b/stmhal/modpyb.c @@ -60,6 +60,8 @@ #include "usb.h" #include "pybstdio.h" #include "ff.h" +#include "diskio.h" +#include "fsusermount.h" #include "portmodules.h" /// \module pyb - functions related to the pyboard @@ -344,6 +346,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_freq_obj, 0, 4, pyb_freq); /// Sync all file systems. STATIC mp_obj_t pyb_sync(void) { storage_flush(); + disk_ioctl(2, CTRL_SYNC, NULL); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_0(pyb_sync_obj, pyb_sync); @@ -543,6 +546,7 @@ STATIC const mp_map_elem_t pyb_module_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_delay), (mp_obj_t)&pyb_delay_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_udelay), (mp_obj_t)&pyb_udelay_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_sync), (mp_obj_t)&pyb_sync_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_mount), (mp_obj_t)&pyb_mount_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_Timer), (mp_obj_t)&pyb_timer_type }, diff --git a/stmhal/moduos.c b/stmhal/moduos.c index ad0913fae4..b16891fec8 100644 --- a/stmhal/moduos.c +++ b/stmhal/moduos.c @@ -37,8 +37,10 @@ #include "rng.h" #include "storage.h" #include "ff.h" +#include "diskio.h" #include "file.h" #include "sdcard.h" +#include "fsusermount.h" #include "portmodules.h" /// \module os - basic "operating system" services @@ -123,6 +125,9 @@ STATIC mp_obj_t os_listdir(mp_uint_t n_args, const mp_obj_t *args) { if (sd_in_root()) { mp_obj_list_append(dir_list, MP_OBJ_NEW_QSTR(MP_QSTR_sd)); } + if (fs_user_mount != NULL) { + mp_obj_list_append(dir_list, mp_obj_new_str(fs_user_mount->str + 1, fs_user_mount->len - 1, false)); + } return dir_list; } @@ -309,6 +314,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_stat_obj, os_stat); /// Sync all filesystems. STATIC mp_obj_t os_sync(void) { storage_flush(); + disk_ioctl(2, CTRL_SYNC, NULL); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_0(os_sync_obj, os_sync); diff --git a/stmhal/qstrdefsport.h b/stmhal/qstrdefsport.h index 8348fdcceb..a6de1557ea 100644 --- a/stmhal/qstrdefsport.h +++ b/stmhal/qstrdefsport.h @@ -72,6 +72,15 @@ Q(micros) Q(elapsed_millis) Q(elapsed_micros) +// for user-mountable block devices +Q(mount) +Q(readonly) +Q(mkfs) +Q(readblocks) +Q(writeblocks) +Q(sync) +Q(count) + // for module weak links Q(binascii) Q(re)