-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ESP32: add support for mounting/umounting stroage
Allow to mount and umount external storage such as SDs/MMC or internal flash using `esp:mount/4` and `esp:umount/1`. Right now only `fat` filesystem is supported. Their semantic and parameters resembles unix mount and umount syscalls. Signed-off-by: Davide Bettio <davide@uninstall.it>
- Loading branch information
Showing
8 changed files
with
406 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
299 changes: 299 additions & 0 deletions
299
src/platforms/esp32/components/avm_builtins/storage_nif.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,299 @@ | ||
/* | ||
* This file is part of AtomVM. | ||
* | ||
* Copyright 2024 Davide Bettio <davide@uninstall.it> | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later | ||
*/ | ||
|
||
#include <sdkconfig.h> | ||
#ifdef CONFIG_AVM_ENABLE_STORAGE_NIFS | ||
|
||
#include <atom.h> | ||
#include <defaultatoms.h> | ||
#include <interop.h> | ||
#include <memory.h> | ||
#include <nifs.h> | ||
#include <term.h> | ||
|
||
#include "esp32_sys.h" | ||
|
||
#include <stdlib.h> | ||
|
||
// file write test | ||
#include <fcntl.h> | ||
#include <sys/stat.h> | ||
#include <sys/types.h> | ||
#include <unistd.h> | ||
|
||
#include <driver/sdmmc_host.h> | ||
#include <driver/sdspi_host.h> | ||
#include <esp_vfs_fat.h> | ||
|
||
#include <trace.h> | ||
|
||
#include "spi_driver.h" | ||
|
||
// TODO: allow ro option | ||
enum mount_type | ||
{ | ||
FATSPIFlash, | ||
FATSDSPI, | ||
FATSDMMC | ||
}; | ||
|
||
struct MountedFS | ||
{ | ||
struct MiscEntry misc_entry; | ||
char *base_path; | ||
enum mount_type mount_type; | ||
union | ||
{ | ||
sdmmc_card_t *card; | ||
wl_handle_t wl; | ||
} handle; | ||
}; | ||
|
||
static term make_esp_error_tuple(esp_err_t err, Context *ctx) | ||
{ | ||
if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { | ||
RAISE_ERROR(OUT_OF_MEMORY_ATOM); | ||
} | ||
term result = term_alloc_tuple(2, &ctx->heap); | ||
term_put_tuple_element(result, 0, ERROR_ATOM); | ||
term_put_tuple_element(result, 1, esp_err_to_term(ctx->global, err)); | ||
return result; | ||
} | ||
|
||
static void opts_to_fatfs_mount_config(term opts_term, esp_vfs_fat_mount_config_t *mount_config) | ||
{ | ||
mount_config->format_if_mount_failed = true; | ||
mount_config->max_files = 8; | ||
mount_config->allocation_unit_size = 512; | ||
// TODO: make it configurable: disk_status_check_enable = false | ||
} | ||
|
||
static term nif_esp_mount(Context *ctx, int argc, term argv[]) | ||
{ | ||
term source_term = argv[0]; | ||
term target_term = argv[1]; | ||
term filesystem_type_term = argv[2]; | ||
term opts_term = argv[3]; | ||
|
||
int str_ok; | ||
char *source = interop_term_to_string(source_term, &str_ok); | ||
if (!str_ok) { | ||
RAISE_ERROR(BADARG_ATOM); | ||
} | ||
|
||
char *target = interop_term_to_string(target_term, &str_ok); | ||
if (!str_ok || strlen(target) > 8) { | ||
RAISE_ERROR(BADARG_ATOM); | ||
} | ||
|
||
term fat_term | ||
= globalcontext_existing_term_from_atom_string(ctx->global, ATOM_STR("\x3", "fat")); | ||
if (term_is_invalid_term(fat_term) || filesystem_type_term != fat_term) { | ||
RAISE_ERROR(BADARG_ATOM); | ||
} | ||
|
||
if (!term_is_list(opts_term) && !term_is_map(opts_term)) { | ||
RAISE_ERROR(BADARG_ATOM); | ||
} | ||
|
||
struct ESP32PlatformData *platform = ctx->global->platform_data; | ||
|
||
esp_vfs_fat_mount_config_t mount_config = {}; | ||
opts_to_fatfs_mount_config(opts_term, &mount_config); | ||
|
||
esp_err_t ret = -1; | ||
|
||
const char *part_by_name_prefix = "/dev/partition/by-name/"; | ||
int part_by_name_len = strlen(part_by_name_prefix); | ||
if (!strncmp(part_by_name_prefix, source, part_by_name_len)) { | ||
mount_config.allocation_unit_size = CONFIG_WL_SECTOR_SIZE; | ||
|
||
struct MountedFS *mount = malloc(sizeof(struct MountedFS)); | ||
mount->misc_entry.entry_type = (uintptr_t) nif_esp_mount; | ||
mount->base_path = strdup(target); | ||
mount->mount_type = FATSPIFlash; | ||
synclist_append(&platform->misc_entries, &mount->misc_entry.list_head); | ||
|
||
#if ESP_IDF_VERSION_MAJOR >= 5 | ||
ret = esp_vfs_fat_spiflash_mount_rw_wl( | ||
target, source + part_by_name_len, &mount_config, &mount->handle.wl); | ||
#else | ||
ret = esp_vfs_fat_spiflash_mount( | ||
target, source + part_by_name_len, &mount_config, &mount->handle.wl); | ||
#endif | ||
|
||
// C3 doesn't support this | ||
#ifdef SDMMC_SLOT_CONFIG_DEFAULT | ||
} else if (!strcmp(source, "sdmmc")) { | ||
mount_config.allocation_unit_size = 512; | ||
|
||
sdmmc_host_t host_config = SDMMC_HOST_DEFAULT(); | ||
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); | ||
|
||
struct MountedFS *mount = malloc(sizeof(struct MountedFS)); | ||
mount->misc_entry.entry_type = (uintptr_t) nif_esp_mount; | ||
mount->base_path = strdup(target); | ||
mount->mount_type = FATSDMMC; | ||
synclist_append(&platform->misc_entries, &mount->misc_entry.list_head); | ||
|
||
ret = esp_vfs_fat_sdmmc_mount( | ||
target, &host_config, &slot_config, &mount_config, &mount->handle.card); | ||
#endif | ||
|
||
} else if (!strcmp(source, "sdspi")) { | ||
mount_config.allocation_unit_size = 512; | ||
|
||
sdmmc_host_t host_config = SDSPI_HOST_DEFAULT(); | ||
sdspi_device_config_t spi_slot_config = SDSPI_DEVICE_CONFIG_DEFAULT(); | ||
|
||
term spi_port = interop_kv_get_value_default( | ||
opts_term, ATOM_STR("\x8", "spi_host"), term_invalid_term(), ctx->global); | ||
spi_host_device_t host_dev; | ||
bool ok = spi_driver_get_peripheral(spi_port, &host_dev, ctx->global); | ||
if (!ok) { | ||
RAISE_ERROR(BADARG_ATOM); | ||
} | ||
spi_slot_config.host_id = host_dev; | ||
|
||
term cs_term = interop_kv_get_value_default( | ||
opts_term, ATOM_STR("\x2", "cs"), term_invalid_term(), ctx->global); | ||
VALIDATE_VALUE(cs_term, term_is_integer); | ||
spi_slot_config.gpio_cs = term_to_int(cs_term); | ||
|
||
term cd_term = interop_kv_get_value_default( | ||
opts_term, ATOM_STR("\x2", "cd"), UNDEFINED_ATOM, ctx->global); | ||
if (cd_term != UNDEFINED_ATOM) { | ||
VALIDATE_VALUE(cd_term, term_is_integer); | ||
spi_slot_config.gpio_cd = term_to_int(cd_term); | ||
} | ||
|
||
struct MountedFS *mount = malloc(sizeof(struct MountedFS)); | ||
mount->misc_entry.entry_type = (uintptr_t) nif_esp_mount; | ||
mount->base_path = strdup(target); | ||
mount->mount_type = FATSPIFlash; | ||
synclist_append(&platform->misc_entries, &mount->misc_entry.list_head); | ||
|
||
ret = esp_vfs_fat_sdspi_mount( | ||
target, &host_config, &spi_slot_config, &mount_config, &mount->handle.card); | ||
} else { | ||
RAISE_ERROR(BADARG_ATOM); | ||
} | ||
|
||
term return_term = OK_ATOM; | ||
if (UNLIKELY(ret != ESP_OK)) { | ||
return_term = make_esp_error_tuple(ret, ctx); | ||
} | ||
|
||
free(source); | ||
free(target); | ||
|
||
return return_term; | ||
} | ||
|
||
static term do_umount(struct MountedFS *mount, Context *ctx) | ||
{ | ||
esp_err_t ret = ESP_OK; | ||
|
||
switch (mount->mount_type) { | ||
case FATSPIFlash: | ||
#if ESP_IDF_VERSION_MAJOR >= 5 | ||
ret = esp_vfs_fat_spiflash_unmount_rw_wl(mount->base_path, mount->handle.wl); | ||
#else | ||
ret = esp_vfs_fat_spiflash_unmount(mount->base_path, mount->handle.wl); | ||
#endif | ||
break; | ||
|
||
case FATSDSPI: | ||
case FATSDMMC: | ||
ret = esp_vfs_fat_sdcard_unmount(mount->base_path, mount->handle.card); | ||
break; | ||
} | ||
|
||
if (UNLIKELY(ret != ESP_OK)) { | ||
return make_esp_error_tuple(ret, ctx); | ||
} | ||
|
||
return OK_ATOM; | ||
} | ||
|
||
static term nif_esp_umount(Context *ctx, int argc, term argv[]) | ||
{ | ||
term source_term = argv[0]; | ||
|
||
int str_ok; | ||
char *source = interop_term_to_string(source_term, &str_ok); | ||
if (!str_ok || strlen(source) >= 8) { | ||
RAISE_ERROR(BADARG_ATOM); | ||
} | ||
|
||
struct ESP32PlatformData *platform = ctx->global->platform_data; | ||
struct ListHead *misc_entries = synclist_wrlock(&platform->misc_entries); | ||
|
||
struct ListHead *item; | ||
struct ListHead *tmp; | ||
MUTABLE_LIST_FOR_EACH (item, tmp, misc_entries) { | ||
struct MiscEntry *entry = GET_LIST_ENTRY(item, struct MiscEntry, list_head); | ||
if (entry->entry_type == (uintptr_t) nif_esp_mount) { | ||
struct MountedFS *mount = CONTAINER_OF(entry, struct MountedFS, misc_entry); | ||
if (!strcmp(source, mount->base_path)) { | ||
term ret = do_umount(mount, ctx); | ||
if (ret == OK_ATOM) { | ||
list_remove(&mount->misc_entry.list_head); | ||
free(mount); | ||
} | ||
synclist_unlock(&platform->misc_entries); | ||
return ret; | ||
} | ||
} | ||
} | ||
|
||
synclist_unlock(&platform->misc_entries); | ||
|
||
RAISE_ERROR(BADARG_ATOM); | ||
} | ||
|
||
void storage_nif_init(GlobalContext *global) {} | ||
|
||
static const struct Nif esp_mount_nif = { | ||
.base.type = NIFFunctionType, | ||
.nif_ptr = nif_esp_mount | ||
}; | ||
|
||
static const struct Nif esp_umount_nif = { | ||
.base.type = NIFFunctionType, | ||
.nif_ptr = nif_esp_umount | ||
}; | ||
|
||
const struct Nif *storage_nif_get_nif(const char *nifname) | ||
{ | ||
if (strcmp("esp:mount/4", nifname) == 0) { | ||
TRACE("Resolved platform nif %s ...\n", nifname); | ||
return &esp_mount_nif; | ||
} else if (strcmp("esp:umount/1", nifname) == 0) { | ||
TRACE("Resolved platform nif %s ...\n", nifname); | ||
return &esp_umount_nif; | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
REGISTER_NIF_COLLECTION(storage, storage_nif_init, NULL, storage_nif_get_nif) | ||
|
||
#endif |
Oops, something went wrong.