diff --git a/applications/nrf5340_audio/Kconfig.defaults b/applications/nrf5340_audio/Kconfig.defaults index 3216e06f1bcd..df1b661291b1 100644 --- a/applications/nrf5340_audio/Kconfig.defaults +++ b/applications/nrf5340_audio/Kconfig.defaults @@ -124,10 +124,6 @@ endchoice config FS_FATFS_EXFAT default y -# Set the maximum file name length to 255 -config FS_FATFS_MAX_LFN - default 255 - config WATCHDOG default y diff --git a/applications/nrf5340_audio/src/modules/lc3_streamer.c b/applications/nrf5340_audio/src/modules/lc3_streamer.c index 32784e099d06..87351fbe784d 100644 --- a/applications/nrf5340_audio/src/modules/lc3_streamer.c +++ b/applications/nrf5340_audio/src/modules/lc3_streamer.c @@ -163,7 +163,7 @@ static int stream_loop(struct lc3_stream *stream) ret = lc3_file_open(&stream->file, stream->filename); if (ret) { - LOG_ERR("Failed to open file %d", ret); + LOG_ERR("Failed to open file %s: %d", stream->filename, ret); return ret; } @@ -369,8 +369,7 @@ int lc3_streamer_stream_register(const char *const filename, uint8_t *const stre return ret; } - strncpy(streams[*streamer_idx].filename, filename, - ARRAY_SIZE(streams[*streamer_idx].filename)); + strncpy(streams[*streamer_idx].filename, filename, strlen(filename)); ret = data_fifo_init(&streams[*streamer_idx].fifo); if (ret) { diff --git a/applications/nrf5340_audio/src/modules/sd_card.c b/applications/nrf5340_audio/src/modules/sd_card.c index 5e973449c388..91f116b5a6eb 100644 --- a/applications/nrf5340_audio/src/modules/sd_card.c +++ b/applications/nrf5340_audio/src/modules/sd_card.c @@ -14,13 +14,20 @@ #include #include #include +#include #include LOG_MODULE_REGISTER(sd_card, CONFIG_MODULE_SD_CARD_LOG_LEVEL); -#define SD_ROOT_PATH "/SD:/" -/* Maximum length for path support by Windows file system */ -#define PATH_MAX_LEN 260 +#define SD_ROOT_PATH "/SD:/" +/* Round down to closest 4-byte boundary */ +#define PATH_MAX_LEN ROUND_DOWN(CONFIG_FS_FATFS_MAX_LFN, 4) +#define SD_CARD_LEVELS_MAX 8 +#define SD_CARD_BUF_SIZE 700 + +static uint32_t num_files_added; +static struct k_mem_slab slab_A; +static struct k_mem_slab slab_B; static const char *sd_root_path = "/SD:"; static FATFS fat_fs; @@ -31,7 +38,165 @@ static struct fs_mount_t mnt_pt = { .fs_data = &fat_fs, }; -int sd_card_list_files(char const *const path, char *buf, size_t *buf_size) +/** + * @brief Replaces first carriage return or line feed with null terminator. + */ +static void cr_lf_remove(char *str) +{ + char *p = str; + + while (*p != '\0') { + if (*p == '\r' || *p == '\n') { + *p = '\0'; + break; + } + p++; + } +} + +/** + * @brief Recursively traverse the SD card tree. + */ +static int traverse_down(char const *const path, uint8_t curr_depth, uint16_t result_file_num_max, + uint16_t result_path_len_max, char result[][result_path_len_max], + char const *const search_pattern) +{ + int ret = 0; + + if (curr_depth > SD_CARD_LEVELS_MAX) { + LOG_WRN("At tree curr_depth %u, greater than %u", curr_depth, SD_CARD_LEVELS_MAX); + return 0; + } + + char *slab_A_ptr; + + ret = k_mem_slab_alloc(&slab_A, (void **)&slab_A_ptr, K_NO_WAIT); + if (ret) { + LOG_ERR("Failed to alloc slab A: %d", ret); + return ret; + } + + char *slab_A_ptr_origin = slab_A_ptr; + char *slab_B_ptr; + + ret = k_mem_slab_alloc(&slab_B, (void **)&slab_B_ptr, K_NO_WAIT); + if (ret) { + k_mem_slab_free(&slab_A, (void *)slab_A_ptr_origin); + LOG_ERR("Failed to alloc slab B: %d", ret); + return ret; + } + + char *slab_B_ptr_origin = slab_B_ptr; + size_t slab_A_ptr_size = SD_CARD_BUF_SIZE; + + /* Search for folders */ + ret = sd_card_list_files(path, slab_A_ptr, &slab_A_ptr_size, false); + if (ret == -ENOENT) { + /* Not able to open, hence likely not a folder */ + ret = 0; + goto cleanup; + } else if (ret) { + goto cleanup; + } + + LOG_DBG("At curr_depth %d tmp_buf is: %s", curr_depth, slab_A_ptr); + + char *token = strtok_r(slab_A_ptr, "\r\n", &slab_A_ptr); + + while (token != NULL) { + if (strstr(token, "System Volume Information") != NULL) { + /* Skipping System Volume Information */ + token = strtok_r(NULL, "\n", &slab_A_ptr); + continue; + } + + cr_lf_remove(token); + memset(slab_B_ptr, '\0', PATH_MAX_LEN); + + if (path != NULL) { + strcat(slab_B_ptr, path); + cr_lf_remove(slab_B_ptr); + strcat(slab_B_ptr, "/"); + } + + strcat(slab_B_ptr, token); + + if (strstr(token, search_pattern) != NULL) { + if (num_files_added >= result_file_num_max) { + LOG_WRN("Max file count reached %u", result_file_num_max); + ret = -ENOMEM; + goto cleanup; + } + strcpy(result[num_files_added], slab_B_ptr); + num_files_added++; + LOG_DBG("Added file num: %d at: %s", num_files_added, slab_B_ptr); + + } else { + ret = traverse_down(slab_B_ptr, curr_depth + 1, result_file_num_max, + result_path_len_max, result, search_pattern); + if (ret) { + LOG_ERR("Failed to traverse down: %d", ret); + } + } + + token = strtok_r(NULL, "\n", &slab_A_ptr); + } + +cleanup: + k_mem_slab_free(&slab_A, (void *)slab_A_ptr_origin); + k_mem_slab_free(&slab_B, (void *)slab_B_ptr_origin); + return ret; +} + +int sd_card_list_files_match(uint16_t result_file_num_max, uint16_t result_path_len_max, + char result[][result_path_len_max], char *path, + char const *const search_pattern) +{ + int ret; + + num_files_added = 0; + + if (result == NULL) { + return -EINVAL; + } + + if (result_file_num_max == 0) { + return -EINVAL; + } + + if (result_path_len_max == 0 || (result_path_len_max > PATH_MAX_LEN)) { + return -EINVAL; + } + + if (search_pattern == NULL) { + return -EINVAL; + } + + char __aligned(4) buf_A[SD_CARD_BUF_SIZE * SD_CARD_LEVELS_MAX] = {'\0'}; + char __aligned(4) buf_B[PATH_MAX_LEN * SD_CARD_LEVELS_MAX] = {'\0'}; + + ret = k_mem_slab_init(&slab_A, buf_A, SD_CARD_BUF_SIZE, SD_CARD_LEVELS_MAX); + if (ret) { + LOG_ERR("Failed to init slab: %d", ret); + return ret; + } + + ret = k_mem_slab_init(&slab_B, buf_B, PATH_MAX_LEN, SD_CARD_LEVELS_MAX); + if (ret) { + LOG_ERR("Failed to init slab: %d", ret); + return ret; + } + + ret = traverse_down(path, 0, result_file_num_max, result_path_len_max, result, + search_pattern); + if (ret) { + return ret; + } + + return num_files_added; +} + +int sd_card_list_files(char const *const path, char *buf, size_t *buf_size, bool extra_info) { int ret; struct fs_dir_t dirp; @@ -44,7 +209,6 @@ int sd_card_list_files(char const *const path, char *buf, size_t *buf_size) } fs_dir_t_init(&dirp); - if (path == NULL) { ret = fs_opendir(&dirp, sd_root_path); if (ret) { @@ -59,9 +223,14 @@ int sd_card_list_files(char const *const path, char *buf, size_t *buf_size) strcat(abs_path_name, path); + if (strchr(abs_path_name, '.')) { + /* Path contains a dot. Regarded as not a folder*/ + return -ENOENT; + } + ret = fs_opendir(&dirp, abs_path_name); if (ret) { - LOG_ERR("Open assigned path failed"); + LOG_ERR("Open assigned path failed %d. %s", ret, abs_path_name); return ret; } } @@ -78,9 +247,17 @@ int sd_card_list_files(char const *const path, char *buf, size_t *buf_size) if (buf != NULL) { size_t remaining_buf_size = *buf_size - used_buf_size; - ssize_t len = snprintk( - &buf[used_buf_size], remaining_buf_size, "[%s]\t%s\n", - entry.type == FS_DIR_ENTRY_DIR ? "DIR " : "FILE", entry.name); + ssize_t len; + + if (extra_info) { + len = snprintk(&buf[used_buf_size], remaining_buf_size, + "[%s]\t%s\r\n", + entry.type == FS_DIR_ENTRY_DIR ? "DIR " : "FILE", + entry.name); + } else { + len = snprintk(&buf[used_buf_size], remaining_buf_size, "%s\r\n", + entry.name); + } if (len >= remaining_buf_size) { LOG_ERR("Failed to append to buffer, error: %d", len); @@ -290,7 +467,7 @@ int sd_card_init(void) sd_card_size_bytes = (uint64_t)sector_count * sector_size; - LOG_INF("SD card volume size: %d MB", (uint32_t)(sd_card_size_bytes >> 20)); + LOG_INF("SD card volume size: %lld B", sd_card_size_bytes); mnt_pt.mnt_point = sd_root_path; diff --git a/applications/nrf5340_audio/src/modules/sd_card.h b/applications/nrf5340_audio/src/modules/sd_card.h index a51bd37fcee2..989f122352de 100644 --- a/applications/nrf5340_audio/src/modules/sd_card.h +++ b/applications/nrf5340_audio/src/modules/sd_card.h @@ -10,16 +10,40 @@ #include #include +/** + * @brief Finds all files on SD card that match the given pattern. + * + * @note The function uses a recursive approach with internal buffers. Memory intensive. + * + * @param[in] result_file_num_max Maximum number of files to be found. + * @param[in] result_file_len_max Maximum length of each file name including total path length + * @param[out] result Pointer to the result array of dimension result_file_num_max + * * result_file_len_max. + * @param[in] path NULL, search from root, otherwise search from the given path. + * Note not to add an ending "/" + * @param[in] pattern Null terminated pattern to find, e.g. *.lc3 or *.wav + * @retval Number of files found. + * @retval -EINVAL invalid parameters. + * @retval -ENOMEM out of memory. + * @retval -ENODEV SD init failed. SD likely not inserted. + * @retval -EPERM SD card operation is ongoing somewhere else. + * @retval -Other, error from underlying drivers. + */ +int sd_card_list_files_match(uint16_t result_file_num_max, uint16_t result_file_len_max, + char result[][result_file_len_max], char *path, + char const *const pattern); + /** * @brief Print out the contents under SD card root path and write the content to buffer. * * @param[in] path Path of the folder which is going to be listed. - * If assigned path is null, then listing the contents under + * If assigned path is NULL, then listing the contents under * root. If assigned path doesn't exist, an error will be * returned. * @param[out] buf Buffer where data is written. If set to NULL, it will be * ignored. * @param[in, out] buf_size Buffer size. + * @param[in] extra_info Will append DIR/FILE info to string. * * @retval 0 on success. * @retval -EPERM SD card operation is ongoing somewhere else. @@ -28,7 +52,7 @@ * @retval -FR_INVALID_NAME Path is too long. * @retval Otherwise, error from underlying drivers. */ -int sd_card_list_files(char const *const path, char *buf, size_t *buf_size); +int sd_card_list_files(char const *const path, char *buf, size_t *buf_size, bool extra_info); /** * @brief Write data from buffer into the file. diff --git a/applications/nrf5340_audio/src/modules/sd_card_playback.c b/applications/nrf5340_audio/src/modules/sd_card_playback.c index f3725c218699..406c7cb7347e 100644 --- a/applications/nrf5340_audio/src/modules/sd_card_playback.c +++ b/applications/nrf5340_audio/src/modules/sd_card_playback.c @@ -564,7 +564,7 @@ static int cmd_list_files(const struct shell *shell, size_t argc, char **argv) char buf[LIST_FILES_BUF_SIZE]; size_t buf_size = LIST_FILES_BUF_SIZE; - ret = sd_card_list_files(playback_file_path, buf, &buf_size); + ret = sd_card_list_files(playback_file_path, buf, &buf_size, true); if (ret) { shell_error(shell, "List files err: %d", ret); return ret; diff --git a/samples/bluetooth/broadcast_config_tool/prj.conf b/samples/bluetooth/broadcast_config_tool/prj.conf index cac9c4490691..77e38762ead3 100644 --- a/samples/bluetooth/broadcast_config_tool/prj.conf +++ b/samples/bluetooth/broadcast_config_tool/prj.conf @@ -15,7 +15,7 @@ CONFIG_THREAD_RUNTIME_STATS=y CONFIG_MAIN_THREAD_PRIORITY=10 CONFIG_STACK_SENTINEL=y CONFIG_INIT_STACKS=y -CONFIG_MAIN_STACK_SIZE=1800 +CONFIG_MAIN_STACK_SIZE=12000 CONFIG_THREAD_NAME=y CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=1200 @@ -27,9 +27,11 @@ CONFIG_LOG=y CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y CONFIG_LOG_TAG_MAX_LEN=2 CONFIG_LOG_TAG_DEFAULT="--" -CONFIG_USE_SEGGER_RTT=y -CONFIG_LOG_BACKEND_RTT=y + CONFIG_LOG_BUFFER_SIZE=4096 +CONFIG_USE_SEGGER_RTT=n +CONFIG_LOG_BACKEND_RTT=n + CONFIG_SOC_NRF53_CPUNET_ENABLE=y CONFIG_ZBUS=y @@ -94,7 +96,7 @@ CONFIG_KERNEL_SHELL=y CONFIG_SHELL_BACKEND_SERIAL=y CONFIG_SHELL_VT100_COMMANDS=y CONFIG_SHELL_VT100_COLORS=y -CONFIG_SHELL_STACK_SIZE=4096 +CONFIG_SHELL_STACK_SIZE=8096 CONFIG_SHELL_CMD_BUFF_SIZE=128 ## Reduce shell memory usage CONFIG_SHELL_WILDCARD=n @@ -148,6 +150,7 @@ CONFIG_SW_CODEC_LC3=n CONFIG_NRF5340_AUDIO_SD_CARD_MODULE=y CONFIG_NRF5340_AUDIO_SD_CARD_LC3_FILE=y CONFIG_NRF5340_AUDIO_SD_CARD_LC3_STREAMER=y -CONFIG_SD_CARD_LC3_STREAMER_STACK_SIZE=3000 + +CONFIG_SD_CARD_LC3_STREAMER_STACK_SIZE=8000 CONFIG_MODULE_SD_CARD_LOG_LEVEL_WRN=y diff --git a/samples/bluetooth/broadcast_config_tool/src/main.c b/samples/bluetooth/broadcast_config_tool/src/main.c index 0634a7e2cc53..c6f1f587da89 100644 --- a/samples/bluetooth/broadcast_config_tool/src/main.c +++ b/samples/bluetooth/broadcast_config_tool/src/main.c @@ -317,9 +317,6 @@ static void le_audio_msg_sub_thread(void) break; case LE_AUDIO_EVT_STREAMING: - LOG_DBG("LE audio evt streaming for stream big %d sub: %d bis: %d", - msg.idx.lvl1, msg.idx.lvl2, msg.idx.lvl3); - stream_frame_get_and_send(msg.idx); break; @@ -583,6 +580,29 @@ static void broadcast_config_clear(void) } } +#define SD_FILECOUNT_MAX 420 +#define SD_PATHLEN_MAX 190 +static char sd_paths_and_files[SD_FILECOUNT_MAX][SD_PATHLEN_MAX]; +static uint32_t num_files_added; + +static int sd_card_toc_gen(void) +{ + /* Traverse SD tree */ + int ret; + + ret = sd_card_list_files_match(SD_FILECOUNT_MAX, SD_PATHLEN_MAX, sd_paths_and_files, NULL, + ".lc3"); + if (ret < 0) { + return ret; + } + + num_files_added = ret; + + LOG_INF("Number of *.lc3 files on SD card: %d", num_files_added); + + return 0; +} + int main(void) { int ret; @@ -621,6 +641,8 @@ int main(void) ret = zbus_subscribers_create(); ERR_CHK_MSG(ret, "Failed to create zbus subscriber threads"); + ret = sd_card_toc_gen(); + ERR_CHK_MSG(ret, "Failed to generate SD card table"); return 0; } @@ -1606,7 +1628,7 @@ static int cmd_file_list(const struct shell *shell, size_t argc, char **argv) dir_path = argv[1]; } - ret = sd_card_list_files(dir_path, buf, &buf_size); + ret = sd_card_list_files(dir_path, buf, &buf_size, true); if (ret) { shell_error(shell, "List files err: %d", ret); return ret; @@ -2068,6 +2090,10 @@ static void lecture_set(const struct shell *shell) char *num_bis_argv[4] = {"num_bises", "1", "0", "0"}; + char *fileselect_argv[5] = {"file select", + "24000hz/48_kbps/auditorium-english_24kHz_left_48kbps.lc3", "0", + "0", "0"}; + cmd_preset(shell, 3, preset_argv); cmd_adv_name(shell, 3, adv_name_argv); cmd_broadcast_name(shell, 3, name_argv); @@ -2083,6 +2109,8 @@ static void lecture_set(const struct shell *shell) cmd_num_bises(shell, 4, num_bis_argv); + cmd_file_select(shell, 5, fileselect_argv); + cmd_show(shell, 0, NULL); } @@ -2105,6 +2133,12 @@ static void silent_tv_1_set(const struct shell *shell) char *location_FL_argv[5] = {"location", "FL", "0", "0", "0"}; char *location_FR_argv[5] = {"location", "FR", "0", "0", "1"}; + char *fileselect0_argv[5] = { + "file select", "24000hz/48_kbps/left-channel_24kHz_left_48kbps.lc3", "0", "0", "0"}; + char *fileselect1_argv[5] = {"file select", + "24000hz/48_kbps/right-channel_24kHz_left_48kbps.lc3", "0", + "0", "1"}; + cmd_preset(shell, 3, preset_argv); cmd_adv_name(shell, 3, adv_name_argv); cmd_broadcast_name(shell, 3, name_argv); @@ -2122,6 +2156,9 @@ static void silent_tv_1_set(const struct shell *shell) cmd_location(shell, 5, location_FL_argv); cmd_location(shell, 5, location_FR_argv); + cmd_file_select(shell, 5, fileselect0_argv); + cmd_file_select(shell, 5, fileselect1_argv); + cmd_show(shell, 0, NULL); } @@ -2145,6 +2182,12 @@ static void silent_tv_2_set(const struct shell *shell) char *location_FL0_argv[5] = {"location", "FL", "0", "0", "0"}; char *location_FR0_argv[5] = {"location", "FR", "0", "0", "1"}; + char *fileselect00_argv[5] = { + "file select", "24000hz/48_kbps/left-channel_24kHz_left_48kbps.lc3", "0", "0", "0"}; + char *fileselect01_argv[5] = {"file select", + "24000hz/48_kbps/right-channel_24kHz_left_48kbps.lc3", "0", + "0", "1"}; + cmd_preset(shell, 3, preset0_argv); cmd_adv_name(shell, 3, adv_name0_argv); cmd_broadcast_name(shell, 3, name0_argv); @@ -2162,6 +2205,9 @@ static void silent_tv_2_set(const struct shell *shell) cmd_location(shell, 5, location_FL0_argv); cmd_location(shell, 5, location_FR0_argv); + cmd_file_select(shell, 5, fileselect00_argv); + cmd_file_select(shell, 5, fileselect01_argv); + /* BIG1 */ char *preset1_argv[3] = {"preset", "48_2_1", "1"}; char *adv_name1_argv[3] = {"adv_name", "Silent_TV2_high", "1"}; @@ -2180,6 +2226,13 @@ static void silent_tv_2_set(const struct shell *shell) char *location_FL1_argv[5] = {"location", "FL", "1", "0", "0"}; char *location_FR1_argv[5] = {"location", "FR", "1", "0", "1"}; + char *fileselect10_argv[5] = { + "file select", "48000hz/80_kbps/left-channel_48kHz_left_80kbps.lc3", "1", "0", "0"}; + char *fileselect11_argv[5] = {"file select", + "48000hz/80_kbps/right-channel_48kHz_left_80kbps.lc3", "1", + "0", "1"}; + char *num_rtn_argv[4] = {"rtn", "2", "1", "0"}; + cmd_preset(shell, 3, preset1_argv); cmd_adv_name(shell, 3, adv_name1_argv); cmd_broadcast_name(shell, 3, name1_argv); @@ -2197,6 +2250,11 @@ static void silent_tv_2_set(const struct shell *shell) cmd_location(shell, 5, location_FL1_argv); cmd_location(shell, 5, location_FR1_argv); + cmd_file_select(shell, 5, fileselect10_argv); + cmd_file_select(shell, 5, fileselect11_argv); + + cmd_rtn(shell, 4, num_rtn_argv); + cmd_show(shell, 0, NULL); } @@ -2209,7 +2267,7 @@ static void multi_language_set(const struct shell *shell) char *packing_argv[3] = {"packing", "int", "0"}; char *lang0_argv[4] = {"lang_set", "eng", "0", "0"}; - char *lang1_argv[4] = {"lang_set", "spa", "0", "1"}; + char *lang1_argv[4] = {"lang_set", "chi", "0", "1"}; char *lang2_argv[4] = {"lang_set", "nor", "0", "2"}; char *context0_argv[4] = {"context", "unspecified", "0", "0"}; @@ -2220,6 +2278,21 @@ static void multi_language_set(const struct shell *shell) char *num_bis1_argv[4] = {"num_bises", "1", "0", "1"}; char *num_bis2_argv[4] = {"num_bises", "1", "0", "2"}; + char *num_rtn0_argv[4] = {"rtn", "2", "0", "0"}; + char *num_rtn1_argv[4] = {"rtn", "2", "0", "1"}; + char *num_rtn2_argv[4] = {"rtn", "2", "0", "2"}; + + char *fileselect0_argv[5] = {"file select", + "24000hz/48_kbps/gate-b23-english_24kHz_left_48kbps.lc3", "0", + "0", "0"}; + char *fileselect1_argv[5] = {"file select", + "24000hz/48_kbps/gate-b23-mandarin_24kHz_left_48kbps.lc3", "0", + "1", "0"}; + char *fileselect2_argv[5] = { + "file select", + "24000hz/48_kbps/adventuresherlockholmes_01_doyle_24kHz_left_48kbps.lc3", "0", "2", + "0"}; + cmd_num_subgroups(shell, 3, num_subs_argv); cmd_preset(shell, 3, preset_argv); cmd_adv_name(shell, 3, adv_name_argv); @@ -2238,6 +2311,14 @@ static void multi_language_set(const struct shell *shell) cmd_num_bises(shell, 4, num_bis1_argv); cmd_num_bises(shell, 4, num_bis2_argv); + cmd_rtn(shell, 4, num_rtn0_argv); + cmd_rtn(shell, 4, num_rtn1_argv); + cmd_rtn(shell, 4, num_rtn2_argv); + + cmd_file_select(shell, 5, fileselect0_argv); + cmd_file_select(shell, 5, fileselect1_argv); + cmd_file_select(shell, 5, fileselect2_argv); + cmd_show(shell, 0, NULL); } @@ -2249,13 +2330,20 @@ static void personal_sharing_set(const struct shell *shell) char *packing_argv[3] = {"packing", "int", "0"}; char *encrypt_argv[4] = {"encrypt", "1", "0", "Auratest"}; - char *context_argv[4] = {"context", "conversational", "0", "0"}; + char *context_argv[4] = {"context", "media", "0", "0"}; char *num_bis_argv[4] = {"num_bises", "2", "0", "0"}; char *location_FL_argv[5] = {"location", "FL", "0", "0", "0"}; char *location_FR_argv[5] = {"location", "FR", "0", "0", "1"}; + char *fileselect0_argv[5] = { + "file select", "48000hz/80_kbps/groovy-ambient-funk-201745_48kHz_left_80kbps.lc3", + "0", "0", "0"}; + char *fileselect1_argv[5] = { + "file select", "48000hz/80_kbps/groovy-ambient-funk-201745_48kHz_right_80kbps.lc3", + "0", "0", "1"}; + cmd_preset(shell, 3, preset_argv); cmd_adv_name(shell, 3, adv_name_argv); cmd_broadcast_name(shell, 3, name_argv); @@ -2269,6 +2357,9 @@ static void personal_sharing_set(const struct shell *shell) cmd_location(shell, 5, location_FL_argv); cmd_location(shell, 5, location_FR_argv); + cmd_file_select(shell, 5, fileselect0_argv); + cmd_file_select(shell, 5, fileselect1_argv); + cmd_show(shell, 0, NULL); } @@ -2282,7 +2373,7 @@ static void personal_multi_language_set(const struct shell *shell) char *encrypt_argv[4] = {"encrypt", "1", "0", "Auratest"}; char *lang0_argv[4] = {"lang_set", "eng", "0", "0"}; - char *lang1_argv[4] = {"lang_set", "spa", "0", "1"}; + char *lang1_argv[4] = {"lang_set", "chi", "0", "1"}; char *context0_argv[4] = {"context", "media", "0", "0"}; char *context1_argv[4] = {"context", "media", "0", "1"}; @@ -2295,6 +2386,22 @@ static void personal_multi_language_set(const struct shell *shell) char *location_FL1_argv[5] = {"location", "FL", "0", "1", "0"}; char *location_FR1_argv[5] = {"location", "FR", "0", "1", "1"}; + char *fileselect000_argv[5] = {"file select", + "24000hz/48_kbps/auditorium-english_24kHz_left_48kbps.lc3", + "0", "0", "0"}; + char *fileselect001_argv[5] = {"file select", + "24000hz/48_kbps/auditorium-english_24kHz_right_48kbps.lc3", + "0", "0", "1"}; + char *fileselect010_argv[5] = {"file select", + "24000hz/48_kbps/auditorium-mandarin_24kHz_left_48kbps.lc3", + "0", "1", "0"}; + char *fileselect011_argv[5] = {"file select", + "24000hz/48_kbps/auditorium-mandarin_24kHz_right_48kbps.lc3", + "0", "1", "1"}; + + char *num_rtn000_argv[4] = {"rtn", "2", "0", "0"}; + char *num_rtn010_argv[4] = {"rtn", "2", "0", "1"}; + cmd_num_subgroups(shell, 3, num_subs_argv); cmd_preset(shell, 3, preset_argv); cmd_adv_name(shell, 3, adv_name_argv); @@ -2316,6 +2423,14 @@ static void personal_multi_language_set(const struct shell *shell) cmd_location(shell, 5, location_FL1_argv); cmd_location(shell, 5, location_FR1_argv); + cmd_file_select(shell, 5, fileselect000_argv); + cmd_file_select(shell, 5, fileselect001_argv); + cmd_file_select(shell, 5, fileselect010_argv); + cmd_file_select(shell, 5, fileselect011_argv); + + cmd_rtn(shell, 4, num_rtn000_argv); + cmd_rtn(shell, 4, num_rtn010_argv); + cmd_show(shell, 0, NULL); } @@ -2383,11 +2498,29 @@ static int cmd_clear(const struct shell *shell, size_t argc, char **argv) return 0; } +static void file_paths_get(size_t idx, struct shell_static_entry *entry); + +SHELL_DYNAMIC_CMD_CREATE(folder_names, file_paths_get); + +static void file_paths_get(size_t idx, struct shell_static_entry *entry) +{ + if (idx < num_files_added) { + entry->syntax = sd_paths_and_files[idx]; + } else { + entry->syntax = NULL; + } + + entry->handler = NULL; + entry->help = NULL; + entry->subcmd = &folder_names; +} + SHELL_STATIC_SUBCMD_SET_CREATE(sub_file_cmd, SHELL_COND_CMD(CONFIG_SHELL, list, NULL, "List files on SD card", cmd_file_list), - SHELL_COND_CMD(CONFIG_SHELL, select, NULL, "Select file on SD card", - cmd_file_select), + /* 5 required arguments */ + SHELL_CMD_ARG(select, &folder_names, "Select file on SD card", + cmd_file_select, 5, 0), SHELL_SUBCMD_SET_END); SHELL_STATIC_SUBCMD_SET_CREATE( diff --git a/tests/nrf5340_audio/sd_card/CMakeLists.txt b/tests/nrf5340_audio/sd_card/CMakeLists.txt new file mode 100644 index 000000000000..eeb511926ce0 --- /dev/null +++ b/tests/nrf5340_audio/sd_card/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(test_sd_card) + +target_sources(app + PRIVATE + ${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/modules/sd_card.c + src/main.c + ) + +target_include_directories(app PRIVATE + ${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/ + ${ZEPHYR_NRF_MODULE_DIR}/modules/fs/fatfs/include/ +) diff --git a/tests/nrf5340_audio/sd_card/Kconfig b/tests/nrf5340_audio/sd_card/Kconfig new file mode 100644 index 000000000000..7b3bf485b7b7 --- /dev/null +++ b/tests/nrf5340_audio/sd_card/Kconfig @@ -0,0 +1,7 @@ +# Temporary Kconfig file for SD card module + +module = MODULE_SD_CARD +module-str = module-sd-card +source "subsys/logging/Kconfig.template.log_config" + +source "Kconfig.zephyr" diff --git a/tests/nrf5340_audio/sd_card/prj.conf b/tests/nrf5340_audio/sd_card/prj.conf new file mode 100644 index 000000000000..4ffff2e47584 --- /dev/null +++ b/tests/nrf5340_audio/sd_card/prj.conf @@ -0,0 +1,16 @@ +CONFIG_ZTEST=y +CONFIG_TEST_EXTRA_STACK_SIZE=8000 +CONFIG_IRQ_OFFLOAD=y +CONFIG_DISK_DRIVERS=y +CONFIG_DISK_ACCESS=y +CONFIG_POSIX_API=y + +CONFIG_FILE_SYSTEM=y +CONFIG_FAT_FILESYSTEM_ELM=y +CONFIG_FS_FATFS_LFN=y +CONFIG_FS_FATFS_LFN_MODE_STACK=y +CONFIG_FS_FATFS_EXFAT=y +CONFIG_FILE_SYSTEM_MKFS=y +CONFIG_FS_FATFS_MKFS=y + +CONFIG_MODULE_SD_CARD_LOG_LEVEL_WRN=y diff --git a/tests/nrf5340_audio/sd_card/ramdisk.overlay b/tests/nrf5340_audio/sd_card/ramdisk.overlay new file mode 100644 index 000000000000..4f8151d57a63 --- /dev/null +++ b/tests/nrf5340_audio/sd_card/ramdisk.overlay @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + ramdisk0 { + compatible = "zephyr,ram-disk"; + disk-name = "SD"; + sector-size = <512>; + sector-count = <10000>; + }; +}; diff --git a/tests/nrf5340_audio/sd_card/src/main.c b/tests/nrf5340_audio/sd_card/src/main.c new file mode 100644 index 000000000000..c6eaa176c929 --- /dev/null +++ b/tests/nrf5340_audio/sd_card/src/main.c @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MKFS_DEV_ID "SD:" + +static int test_create_file(const char *path) +{ + int ret; + struct fs_file_t file; + + fs_file_t_init(&file); + ret = fs_open(&file, path, FS_O_CREATE | FS_O_WRITE); + if (ret == 0) { + fs_write(&file, "Testfile", 8); + fs_close(&file); + } else { + TC_PRINT("Failed to open file\n"); + return ret; + } + + return 0; +} + +static int dir_name_get(char *name, size_t name_size, uint32_t curr_level, uint32_t curr_dir_id, + uint8_t subdirs) +{ + int ret; + + name[0] = '\0'; + strcat(name, "/SD:"); + + struct dir_tree { + uint32_t peer_id; + uint32_t numeric_name; + }; + + struct dir_tree f_tree[5]; + + uint32_t on_level = curr_level; + + f_tree[on_level].peer_id = curr_dir_id; + f_tree[on_level].numeric_name = curr_dir_id % subdirs; + + while (on_level > 0) { + f_tree[on_level - 1].peer_id = f_tree[on_level].peer_id / subdirs; + f_tree[on_level - 1].numeric_name = f_tree[on_level - 1].peer_id % subdirs; + on_level--; + } + + for (int i = 0; i < curr_level + 1; i++) { + char tmp[100] = {'\0'}; + + ret = sprintf(tmp, "/dir_%d", f_tree[i].numeric_name); + strcat(name, tmp); + } + + return 0; +} + +static bool directory_exists(const char *path) +{ + struct fs_dirent entry; + int ret = fs_stat(path, &entry); + + return (ret == 0 && entry.type == FS_DIR_ENTRY_DIR); +} + +static int test_disk_populate(uint8_t num_levels, uint8_t files_per_dir, uint8_t subdirs, + char const *const extension) +{ + TC_PRINT("Writing files. Num levels %u, files_per_dir %u, dirs_pr_level %u\n", num_levels, + files_per_dir, subdirs); + + int ret; + int files_added = 0; + char current_path[200] = {'\0'}; + + for (int level = 0; level < num_levels; level++) { + uint32_t dirs_on_level = pow(subdirs, (level + 1)); + + for (int i = 0; i < dirs_on_level; i++) { + ret = dir_name_get(current_path, 200, level, i, subdirs); + zassert_equal(0, ret, "dir_name_get should return 0, %u", ret); + if (!directory_exists(current_path)) { + ret = fs_mkdir(current_path); + zassert_equal(0, ret, "fs_mkdir should return 0, %u", ret); + } + + for (int j = 0; j < files_per_dir; j++) { + char filepath[1000] = {'\0'}; + char filename[100] = {'\0'}; + + strcat(filepath, current_path); + sprintf(filename, "/file_%d%s", j, extension); + strcat(filepath, filename); + ret = test_create_file(filepath); + zassert_equal(0, ret, "test_create_file should return 0, %u", ret); + files_added++; + } + } + } + + TC_PRINT("--------Done writing %d files--------\n", files_added); + return files_added; +} + +static void *setup_fn(void) +{ + int ret; + + ret = sd_card_init(); + zassert_equal(0, ret, "sd_card_init() should return 0, %u", ret); + + return NULL; +} + +static void before_fn(void *fixture) +{ + int ret; + + ret = fs_mkfs(FS_FATFS, (uintptr_t)MKFS_DEV_ID, NULL, 0); + + if (ret < 0) { + TC_PRINT("Format failed %d\n", ret); + zassert_equal(0, ret, "fs_mkfs should return 0, %u", ret); + } +} + +#define SD_FILECOUNT_MAX 1500 +/* Align at 4-byte boundary */ +#define SD_PATHLEN_MAX ROUND_DOWN(CONFIG_FS_FATFS_MAX_LFN, 4) + +ZTEST(sd_card, test_basic_files) +{ + int ret; + + static char sd_paths_and_files[SD_FILECOUNT_MAX][SD_PATHLEN_MAX] = {'\0'}; + + ret = test_disk_populate(1, 2, 1, ".lc3"); + zassert_true(ret >= 0, "test_disk_populate returned error, %u", ret); + + ret = sd_card_list_files_match(SD_FILECOUNT_MAX, SD_PATHLEN_MAX, sd_paths_and_files, NULL, + ".lc3"); + + zassert_true(ret >= 0, "sd_card_list_files_match returned error, %u", ret); + zassert_equal(2, ret, "Num files found mismatch, %u", ret); + zassert_str_equal("dir_0/file_0.lc3", sd_paths_and_files[0], "File mismatch: %s", + sd_paths_and_files[0]); + zassert_str_equal("dir_0/file_1.lc3", sd_paths_and_files[1], "File mismatch: %s", + sd_paths_and_files[1]); +} + +/* Same as test_basic_files. Checks that functions are reentrant */ +ZTEST(sd_card, test_basic_files_again) +{ + int ret; + + static char sd_paths_and_files[SD_FILECOUNT_MAX][SD_PATHLEN_MAX] = {'\0'}; + + ret = test_disk_populate(1, 3, 1, ".lc3"); + zassert_true(ret >= 0, "test_disk_populate returned error, %u", ret); + + ret = sd_card_list_files_match(SD_FILECOUNT_MAX, SD_PATHLEN_MAX, sd_paths_and_files, NULL, + ".lc3"); + + zassert_true(ret >= 0, "sd_card_list_files_match returned error, %u", ret); + zassert_equal(3, ret, "Num files found mismatch, %u", ret); +} + +ZTEST(sd_card, test_valid_files_and_dummy_files) +{ + int ret; + + static char sd_paths_and_files[SD_FILECOUNT_MAX][SD_PATHLEN_MAX] = {'\0'}; + + ret = test_disk_populate(1, 2, 1, ".lc3"); + zassert_true(ret >= 0, "test_disk_populate returned error, %u", ret); + + ret = test_disk_populate(1, 2, 1, ".dum"); + zassert_true(ret >= 0, "test_disk_populate returned error, %u", ret); + + ret = sd_card_list_files_match(SD_FILECOUNT_MAX, SD_PATHLEN_MAX, sd_paths_and_files, NULL, + ".lc3"); + + zassert_true(ret >= 0, "sd_card_list_files_match returned error, %u", ret); + zassert_equal(2, ret, "Num files found mismatch, %u", ret); + + ret = sd_card_list_files_match(SD_FILECOUNT_MAX, SD_PATHLEN_MAX, sd_paths_and_files, NULL, + ".dum"); + + zassert_true(ret >= 0, "sd_card_list_files_match returned error, %u", ret); + zassert_equal(2, ret, "Num files found mismatch, %u", ret); + + ret = sd_card_list_files_match(SD_FILECOUNT_MAX, SD_PATHLEN_MAX, sd_paths_and_files, NULL, + ".nonexist"); + + zassert_true(ret >= 0, "sd_card_list_files_match returned error, %u", ret); + zassert_equal(0, ret, "Num files found mismatch, %u", ret); +} + +ZTEST(sd_card, test_deep_structure) +{ + int ret; + const uint8_t NUM_LEVELS = 7; + const uint8_t FILES_PER_DIR = 1; + const uint8_t SUBDIRS = 1; + + static char sd_paths_and_files[10][SD_PATHLEN_MAX] = {'\0'}; + + ret = test_disk_populate(NUM_LEVELS, FILES_PER_DIR, SUBDIRS, ".lc3"); + zassert_true(ret >= 0, "test_disk_populate returned error, %u", ret); + uint32_t files_created = ret; + + ret = sd_card_list_files_match(10, SD_PATHLEN_MAX, sd_paths_and_files, NULL, ".lc3"); + + zassert_true(ret >= 0, "sd_card_list_files_match returned error, %u", ret); + zassert_equal(files_created, ret, "Num files found mismatch, %u", ret); +} + +ZTEST(sd_card, test_many_files) +{ + int ret; + const uint8_t NUM_LEVELS = 2; + const uint8_t FILES_PER_DIR = 50; + const uint8_t SUBDIRS = 4; + + static char sd_paths_and_files[SD_FILECOUNT_MAX][SD_PATHLEN_MAX] = {'\0'}; + + ret = test_disk_populate(NUM_LEVELS, FILES_PER_DIR, SUBDIRS, ".lc3"); + zassert_true(ret >= 0, "test_disk_populate returned error, %u", ret); + uint32_t files_created = ret; + + ret = sd_card_list_files_match(SD_FILECOUNT_MAX, SD_PATHLEN_MAX, sd_paths_and_files, NULL, + ".lc3"); + + zassert_true(ret >= 0, "sd_card_list_files_match returned error, %u", ret); + zassert_equal(files_created, ret, "Num files found mismatch, %u", ret); +} + +ZTEST(sd_card, test_with_start_path) +{ + int ret; + + static char sd_paths_and_files[SD_FILECOUNT_MAX][SD_PATHLEN_MAX] = {'\0'}; + + ret = test_disk_populate(2, 1, 2, ".lc3"); + zassert_true(ret >= 0, "test_disk_populate returned error, %u", ret); + + ret = sd_card_list_files_match(SD_FILECOUNT_MAX, SD_PATHLEN_MAX, sd_paths_and_files, + "dir_1", ".lc3"); + + zassert_true(ret >= 0, "sd_card_list_files_match returned error, %u", ret); + zassert_equal(3, ret, "Num files found mismatch, %u", ret); + zassert_str_equal("dir_1/file_0.lc3", sd_paths_and_files[0], "File mismatch: %s", + sd_paths_and_files[0]); + zassert_str_equal("dir_1/dir_0/file_0.lc3", sd_paths_and_files[1], "File mismatch: %s", + sd_paths_and_files[1]); + zassert_str_equal("dir_1/dir_1/file_0.lc3", sd_paths_and_files[2], "File mismatch: %s", + sd_paths_and_files[0]); + + ret = sd_card_list_files_match(SD_FILECOUNT_MAX, SD_PATHLEN_MAX, sd_paths_and_files, + "dir_1/dir_0", ".lc3"); + + zassert_true(ret >= 0, "sd_card_list_files_match returned error, %u", ret); + zassert_equal(1, ret, "Num files found mismatch, %u", ret); + + zassert_str_equal("dir_1/dir_0/file_0.lc3", sd_paths_and_files[0], "File mismatch: %s", + sd_paths_and_files[0]); +} + +ZTEST_SUITE(sd_card, NULL, setup_fn, before_fn, NULL, NULL); diff --git a/tests/nrf5340_audio/sd_card/testcase.yaml b/tests/nrf5340_audio/sd_card/testcase.yaml new file mode 100644 index 000000000000..00b714552230 --- /dev/null +++ b/tests/nrf5340_audio/sd_card/testcase.yaml @@ -0,0 +1,9 @@ +tests: + nrf5340_audio.sd_card_test: + sysbuild: true + platform_allow: native_sim + integration_platforms: + - native_sim + tags: macros nrf5340_audio_unit_tests sysbuild ci_tests_nrf5340_audio + extra_args: + - EXTRA_DTC_OVERLAY_FILE="ramdisk.overlay"