openmv/src/omv/common/file_utils.c
2024-10-04 20:55:40 +02:00

434 lines
12 KiB
C

/*
* SPDX-License-Identifier: MIT
*
* Copyright (C) 2013-2024 OpenMV, LLC.
*
* 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.
*
* Filesystem helper functions.
*/
#include "imlib_config.h"
#if defined(IMLIB_ENABLE_IMAGE_FILE_IO)
#include <string.h>
#include "py/runtime.h"
#include "extmod/vfs.h"
#include "extmod/vfs_fat.h"
#include "omv_common.h"
#include "fb_alloc.h"
#include "file_utils.h"
#define FF_MIN(x, y) (((x) < (y))?(x):(y))
NORETURN static void ff_read_fail(FIL *fp) {
if (fp) {
f_close(fp);
}
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to read requested bytes!"));
}
NORETURN static void ff_write_fail(FIL *fp) {
if (fp) {
f_close(fp);
}
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to write requested bytes!"));
}
NORETURN static void ff_expect_fail(FIL *fp) {
if (fp) {
f_close(fp);
}
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unexpected value read!"));
}
NORETURN void file_raise_format(FIL *fp) {
if (fp) {
f_close(fp);
}
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported format!"));
}
NORETURN void file_raise_corrupted(FIL *fp) {
if (fp) {
f_close(fp);
}
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File corrupted!"));
}
NORETURN void file_raise_error(FIL *fp, FRESULT res) {
if (fp) {
f_close(fp);
}
mp_raise_msg(&mp_type_OSError, (mp_rom_error_text_t) ffs_strerror(res));
}
static FATFS *lookup_path(const TCHAR **path) {
mp_vfs_mount_t *fs = mp_vfs_lookup_path(*path, path);
if (fs == MP_VFS_NONE || fs == MP_VFS_ROOT) {
return NULL;
}
// here we assume that the mounted device is FATFS
return &((fs_user_mount_t *) MP_OBJ_TO_PTR(fs->obj))->fatfs;
}
FRESULT file_ll_open(FIL *fp, const TCHAR *path, BYTE mode) {
FATFS *fs = lookup_path(&path);
if (fs == NULL) {
return FR_NO_PATH;
}
return f_open(fs, fp, path, mode);
}
FRESULT file_ll_close(FIL *fp) {
return f_close(fp);
}
FRESULT file_ll_read(FIL *fp, void *buff, UINT btr, UINT *br) {
return f_read(fp, buff, btr, br);
}
FRESULT file_ll_write(FIL *fp, const void *buff, UINT btw, UINT *bw) {
return f_write(fp, buff, btw, bw);
}
FRESULT file_ll_opendir(FF_DIR *dp, const TCHAR *path) {
FATFS *fs = lookup_path(&path);
if (fs == NULL) {
return FR_NO_PATH;
}
return f_opendir(fs, dp, path);
}
FRESULT file_ll_stat(const TCHAR *path, FILINFO *fno) {
FATFS *fs = lookup_path(&path);
if (fs == NULL) {
return FR_NO_PATH;
}
return f_stat(fs, path, fno);
}
FRESULT file_ll_mkdir(const TCHAR *path) {
FATFS *fs = lookup_path(&path);
if (fs == NULL) {
return FR_NO_PATH;
}
return f_mkdir(fs, path);
}
FRESULT file_ll_unlink(const TCHAR *path) {
FATFS *fs = lookup_path(&path);
if (fs == NULL) {
return FR_NO_PATH;
}
return f_unlink(fs, path);
}
FRESULT file_ll_rename(const TCHAR *path_old, const TCHAR *path_new) {
FATFS *fs_old = lookup_path(&path_old);
if (fs_old == NULL) {
return FR_NO_PATH;
}
FATFS *fs_new = lookup_path(&path_new);
if (fs_new == NULL) {
return FR_NO_PATH;
}
if (fs_old != fs_new) {
return FR_NO_PATH;
}
return f_rename(fs_new, path_old, path_new);
}
FRESULT file_ll_touch(const TCHAR *path) {
FIL fp;
FATFS *fs = lookup_path(&path);
if (fs == NULL) {
return FR_NO_PATH;
}
if (f_stat(fs, path, NULL) != FR_OK) {
f_open(fs, &fp, path, FA_WRITE | FA_CREATE_ALWAYS);
f_close(&fp);
}
return FR_OK;
}
// When a sector boundary is encountered while writing a file and there are
// more than 512 bytes left to write FatFs will detect that it can bypass
// its internal write buffer and pass the data buffer passed to it directly
// to the disk write function. However, the disk write function needs the
// buffer to be aligned to a 4-byte boundary. FatFs doesn't know this and
// will pass an unaligned buffer if we don't fix the issue. To fix this problem
// we use a temporary buffer to fix the alignment and to speed everything up.
// We use this temporary buffer for both reads and writes. The buffer allows us
// to do multi-block reads and writes which significantly speed things up.
static uint32_t file_buffer_offset = 0;
static uint8_t *file_buffer_pointer = 0;
static uint32_t file_buffer_size = 0;
static uint32_t file_buffer_index = 0;
void file_buffer_init0() {
file_buffer_offset = 0;
file_buffer_pointer = 0;
file_buffer_size = 0;
file_buffer_index = 0;
}
OMV_ATTR_ALWAYS_INLINE static void file_fill(FIL *fp) {
if (file_buffer_index == file_buffer_size) {
file_buffer_pointer -= file_buffer_offset;
file_buffer_size += file_buffer_offset;
file_buffer_offset = 0;
file_buffer_index = 0;
uint32_t file_remaining = f_size(fp) - f_tell(fp);
uint32_t can_do = FF_MIN(file_buffer_size, file_remaining);
UINT bytes;
FRESULT res = f_read(fp, file_buffer_pointer, can_do, &bytes);
if (res != FR_OK) {
file_raise_error(fp, res);
}
if (bytes != can_do) {
ff_read_fail(fp);
}
}
}
OMV_ATTR_ALWAYS_INLINE static void file_flush(FIL *fp) {
if (file_buffer_index == file_buffer_size) {
UINT bytes;
FRESULT res = f_write(fp, file_buffer_pointer, file_buffer_index, &bytes);
if (res != FR_OK) {
file_raise_error(fp, res);
}
if (bytes != file_buffer_index) {
ff_write_fail(fp);
}
file_buffer_pointer -= file_buffer_offset;
file_buffer_size += file_buffer_offset;
file_buffer_offset = 0;
file_buffer_index = 0;
}
}
void file_buffer_on(FIL *fp) {
file_buffer_offset = f_tell(fp) % 4;
file_buffer_pointer = fb_alloc_all(&file_buffer_size, FB_ALLOC_PREFER_SIZE) + file_buffer_offset;
if (!file_buffer_size) {
mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("No memory!"));
}
file_buffer_size -= file_buffer_offset;
file_buffer_index = 0;
if (fp->flag & FA_READ) {
uint32_t file_remaining = f_size(fp) - f_tell(fp);
uint32_t can_do = FF_MIN(file_buffer_size, file_remaining);
UINT bytes;
FRESULT res = f_read(fp, file_buffer_pointer, can_do, &bytes);
if (res != FR_OK) {
file_raise_error(fp, res);
}
if (bytes != can_do) {
ff_read_fail(fp);
}
}
}
void file_buffer_off(FIL *fp) {
if ((fp->flag & FA_WRITE) && file_buffer_index) {
UINT bytes;
FRESULT res = f_write(fp, file_buffer_pointer, file_buffer_index, &bytes);
if (res != FR_OK) {
file_raise_error(fp, res);
}
if (bytes != file_buffer_index) {
ff_write_fail(fp);
}
}
file_buffer_pointer = 0;
fb_free();
}
void file_open(FIL *fp, const char *path, bool buffered, uint32_t flags) {
FRESULT res = file_ll_open(fp, path, flags);
if (res != FR_OK) {
file_raise_error(fp, res);
}
if (buffered) {
file_buffer_on(fp);
}
}
void file_close(FIL *fp) {
if (file_buffer_pointer) {
file_buffer_off(fp);
}
FRESULT res = f_close(fp);
if (res != FR_OK) {
file_raise_error(fp, res);
}
}
void file_seek(FIL *fp, UINT offset) {
FRESULT res = f_lseek(fp, offset);
if (res != FR_OK) {
file_raise_error(fp, res);
}
}
void file_truncate(FIL *fp) {
FRESULT res = f_truncate(fp);
if (res != FR_OK) {
file_raise_error(fp, res);
}
}
void file_sync(FIL *fp) {
FRESULT res = f_sync(fp);
if (res != FR_OK) {
file_raise_error(fp, res);
}
}
uint32_t file_tell(FIL *fp) {
if (file_buffer_pointer) {
if (fp->flag & FA_READ) {
return f_tell(fp) - file_buffer_size + file_buffer_index;
} else {
return f_tell(fp) + file_buffer_index;
}
}
return f_tell(fp);
}
uint32_t file_size(FIL *fp) {
if (file_buffer_pointer) {
if (fp->flag & FA_READ) {
return f_size(fp);
} else {
return f_size(fp) + file_buffer_index;
}
}
return f_size(fp);
}
void file_read(FIL *fp, void *data, size_t size) {
if (data == NULL) {
uint8_t byte;
if (file_buffer_pointer) {
for (size_t i = 0; i < size; i++) {
file_fill(fp);
byte = file_buffer_pointer[file_buffer_index++];
}
} else {
for (size_t i = 0; i < size; i++) {
UINT bytes;
FRESULT res = f_read(fp, &byte, 1, &bytes);
if (res != FR_OK) {
file_raise_error(fp, res);
}
if (bytes != 1) {
ff_read_fail(fp);
}
}
}
return;
}
if (file_buffer_pointer) {
if (size <= 4) {
for (size_t i = 0; i < size; i++) {
file_fill(fp);
((uint8_t *) data)[i] = file_buffer_pointer[file_buffer_index++];
}
} else {
while (size) {
file_fill(fp);
uint32_t file_buffer_space_left = file_buffer_size - file_buffer_index;
uint32_t can_do = FF_MIN(size, file_buffer_space_left);
memcpy(data, file_buffer_pointer + file_buffer_index, can_do);
file_buffer_index += can_do;
data += can_do;
size -= can_do;
}
}
} else {
UINT bytes;
FRESULT res = f_read(fp, data, size, &bytes);
if (res != FR_OK) {
file_raise_error(fp, res);
}
if (bytes != size) {
ff_read_fail(fp);
}
}
}
void file_write(FIL *fp, const void *data, size_t size) {
if (file_buffer_pointer) {
// We get a massive speed boost by buffering up as much data as possible
// before a write to the SD card. So much so that the time wasted by
// all these operations does not cost us.
while (size) {
uint32_t file_buffer_space_left = file_buffer_size - file_buffer_index;
uint32_t can_do = FF_MIN(size, file_buffer_space_left);
memcpy(file_buffer_pointer + file_buffer_index, data, can_do);
file_buffer_index += can_do;
data += can_do;
size -= can_do;
file_flush(fp);
}
} else {
UINT bytes;
FRESULT res = f_write(fp, data, size, &bytes);
if (res != FR_OK) {
file_raise_error(fp, res);
}
if (bytes != size) {
ff_write_fail(fp);
}
}
}
void file_write_byte(FIL *fp, uint8_t value) {
file_write(fp, &value, 1);
}
void file_write_short(FIL *fp, uint16_t value) {
file_write(fp, &value, 2);
}
void file_write_long(FIL *fp, uint32_t value) {
file_write(fp, &value, 4);
}
void file_read_check(FIL *fp, const void *data, size_t size) {
uint8_t buf[16];
while (size) {
size_t len = OMV_MIN(sizeof(buf), size);
file_read(fp, buf, len);
if (memcmp(data, buf, len)) {
ff_expect_fail(fp);
}
size -= len;
data = ((uint8_t *) data) + len;
}
}
#endif //IMLIB_ENABLE_IMAGE_FILE_IO