Skip to content

Commit

Permalink
feat(spi_nand_flash): Add linux target support
Browse files Browse the repository at this point in the history
  • Loading branch information
RathiSonika committed Feb 3, 2025
1 parent 52be198 commit 05e804f
Show file tree
Hide file tree
Showing 17 changed files with 508 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .build-test-rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ spi_nand_flash/test_app:
- if: IDF_VERSION_MAJOR < 5
reason: The spi_nand_flash component is compatible with IDF version v5.0 and above, due to a change in the f_mkfs API in versions above v5.0, which is not supported in older IDF versions.

spi_nand_flash/host_test:
disable:
- if: IDF_VERSION_MAJOR < 5
reason: The spi_nand_flash component is compatible with IDF version v5.0 and above, due to a change in the f_mkfs API in versions above v5.0, which is not supported in older IDF versions.

thorvg/examples/thorvg-example:
enable:
- if: (IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 3) and (IDF_TARGET in ["esp32p4"])
Expand Down
42 changes: 29 additions & 13 deletions spi_nand_flash/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,28 +1,44 @@
idf_build_get_property(target IDF_TARGET)

set(reqs fatfs)
set(inc diskio include)
set(priv_inc priv_include)
set(srcs "src/nand.c"
"src/nand_winbond.c"
"src/nand_gigadevice.c"
"src/nand_alliance.c"
"src/nand_micron.c"
"src/nand_impl.c"
"src/nand_impl_wrap.c"
"src/nand_diag_api.c"
"src/spi_nand_oper.c"
"src/dhara_glue.c"
"vfs/vfs_fat_spinandflash.c"
"src/nand_impl_wrap.c"
"diskio/diskio_nand.c")

set(reqs fatfs)
if(${target} STREQUAL "linux")

list(APPEND reqs esp_partition)
list(APPEND srcs "src/nand_impl_linux.c")

else()

list(APPEND srcs "src/nand_winbond.c"
"src/nand_gigadevice.c"
"src/nand_alliance.c"
"src/nand_micron.c"
"src/nand_impl.c"
"src/nand_impl_wrap.c"
"src/nand_diag_api.c"
"src/spi_nand_oper.c"
"vfs/vfs_fat_spinandflash.c")

set(priv_reqs vfs)
list(APPEND inc vfs)

if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.3")
list(APPEND reqs esp_driver_spi)
else()
list(APPEND reqs driver)
endif()

set(priv_reqs vfs)
endif()


idf_component_register(SRCS ${srcs}
INCLUDE_DIRS include vfs diskio
PRIV_INCLUDE_DIRS "priv_include"
INCLUDE_DIRS ${inc}
PRIV_INCLUDE_DIRS ${priv_inc}
REQUIRES ${reqs}
PRIV_REQUIRES ${priv_reqs})
3 changes: 2 additions & 1 deletion spi_nand_flash/diskio/diskio_nand.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,12 @@ DRESULT ff_nand_ioctl(BYTE pdrv, BYTE cmd, void *buff)
break;
}
#if FF_USE_TRIM
case CTRL_TRIM:
case CTRL_TRIM: {
DWORD start_sector = *((DWORD *)buff);
DWORD end_sector = *((DWORD *)buff + 1) + 1;
DWORD sector_count = end_sector - start_sector;
return ff_nand_trim(pdrv, start_sector, sector_count);
}
#endif //FF_USE_TRIM
default:
return RES_ERROR;
Expand Down
6 changes: 6 additions & 0 deletions spi_nand_flash/host_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)

project(nand_flash_host_test)
2 changes: 2 additions & 0 deletions spi_nand_flash/host_test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
| Supported Targets | Linux |
| ----------------- | ----- |
8 changes: 8 additions & 0 deletions spi_nand_flash/host_test/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
idf_component_register(SRCS "test_nand_flash.cpp" "test_app_main.cpp"
REQUIRES fatfs
WHOLE_ARCHIVE
)

# Currently 'main' for IDF_TARGET=linux is defined in freertos component.
# Since we are using a freertos mock here, need to let Catch2 provide 'main'.
target_link_libraries(${COMPONENT_LIB} PRIVATE Catch2WithMain)
5 changes: 5 additions & 0 deletions spi_nand_flash/host_test/main/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dependencies:
espressif/catch2: "^3.4.0"
espressif/spi_nand_flash:
version: '*'
override_path: '../../'
28 changes: 28 additions & 0 deletions spi_nand_flash/host_test/main/test_app_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdio.h>
#include <catch2/catch_session.hpp>
#include <catch2/catch_test_macros.hpp>


extern "C" void app_main(void)
{
int argc = 1;
const char *argv[2] = {
"target_test_main",
NULL
};

auto result = Catch::Session().run(argc, argv);
if (result != 0) {
printf("Test failed with result %d\n", result);
} else {
printf("Test passed.\n");
}
fflush(stdout);
exit(result);
}
180 changes: 180 additions & 0 deletions spi_nand_flash/host_test/main/test_nand_flash.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdio.h>
#include <string.h>

#include "ff.h"
#include "esp_partition.h"
#include "diskio_impl.h"
#include "diskio_nand.h"
#include "spi_nand_flash.h"
#include "nand_private/nand_impl_wrap.h"

#include <catch2/catch_test_macros.hpp>

#define PATTERN_SEED 0x12345678

TEST_CASE("Create volume, open file, write and read back data", "[fatfs, spi_nand_flash]")
{
FRESULT fr_result;
BYTE pdrv;
FATFS fs;
FIL file;
UINT bw;

esp_err_t esp_result;
spi_nand_flash_config_t nand_flash_config;
spi_nand_flash_device_t *device_handle;
REQUIRE(spi_nand_flash_init_device(&nand_flash_config, &device_handle) == ESP_OK);

// Get a physical drive
esp_result = ff_diskio_get_drive(&pdrv);
REQUIRE(esp_result == ESP_OK);

// Register physical drive as wear-levelled partition
esp_result = ff_diskio_register_nand(pdrv, device_handle);

// Create FAT volume on the entire disk
LBA_t part_list[] = {100, 0, 0, 0};
BYTE work_area[FF_MAX_SS];

fr_result = f_fdisk(pdrv, part_list, work_area);
REQUIRE(fr_result == FR_OK);

char drv[3] = {(char)('0' + pdrv), ':', 0};
const MKFS_PARM opt = {(BYTE)(FM_ANY), 0, 0, 0, 0};
fr_result = f_mkfs(drv, &opt, work_area, sizeof(work_area)); // Use default volume
REQUIRE(fr_result == FR_OK);

// Mount the volume
fr_result = f_mount(&fs, drv, 0);
REQUIRE(fr_result == FR_OK);

// Open, write and read data
fr_result = f_open(&file, "0:/test.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE);
REQUIRE(fr_result == FR_OK);

// Generate data
uint32_t data_size = 1000;

char *data = (char *) malloc(data_size);
char *read = (char *) malloc(data_size);

for (uint32_t i = 0; i < data_size; i += sizeof(i)) {
*((uint32_t *)(data + i)) = i;
}

// Write generated data
fr_result = f_write(&file, data, data_size, &bw);
REQUIRE(fr_result == FR_OK);
REQUIRE(bw == data_size);

// Move to beginning of file
fr_result = f_lseek(&file, 0);
REQUIRE(fr_result == FR_OK);

// Read written data
fr_result = f_read(&file, read, data_size, &bw);
REQUIRE(fr_result == FR_OK);
REQUIRE(bw == data_size);

REQUIRE(memcmp(data, read, data_size) == 0);

// Close file
fr_result = f_close(&file);
REQUIRE(fr_result == FR_OK);

// Unmount default volume
fr_result = f_mount(0, drv, 0);
REQUIRE(fr_result == FR_OK);

// Clear
free(read);
free(data);
ff_diskio_unregister(pdrv);
ff_diskio_clear_pdrv_nand(device_handle);
spi_nand_flash_deinit_device(device_handle);
esp_partition_unload_all();
}

TEST_CASE("verify mark_bad_block works", "[spi_nand_flash]")
{
spi_nand_flash_config_t nand_flash_config;
spi_nand_flash_device_t *device_handle;
REQUIRE(spi_nand_flash_init_device(&nand_flash_config, &device_handle) == ESP_OK);

uint32_t sector_num, sector_size;
REQUIRE(spi_nand_flash_get_capacity(device_handle, &sector_num) == 0);
REQUIRE(spi_nand_flash_get_sector_size(device_handle, &sector_size) == 0);

uint32_t test_block = 15;
if (test_block < sector_num) {
bool is_bad_status = false;
// Verify if test_block is not bad block
REQUIRE(nand_wrap_is_bad(device_handle, test_block, &is_bad_status) == 0);
REQUIRE(is_bad_status == false);
// mark test_block as a bad block
REQUIRE(nand_wrap_mark_bad(device_handle, test_block) == 0);
// Verify if test_block is marked as bad block
REQUIRE(nand_wrap_is_bad(device_handle, test_block, &is_bad_status) == 0);
REQUIRE(is_bad_status == true);
}

spi_nand_flash_deinit_device(device_handle);
esp_partition_unload_all();
}

static void fill_buffer(uint32_t seed, uint8_t *dst, size_t count)
{
srand(seed);
for (size_t i = 0; i < count; ++i) {
uint32_t val = rand();
memcpy(dst + i * sizeof(uint32_t), &val, sizeof(val));
}
}

TEST_CASE("verify nand_prog, nand_read, nand_copy, nand_is_free works", "[spi_nand_flash]")
{
spi_nand_flash_config_t nand_flash_config;
spi_nand_flash_device_t *device_handle;
REQUIRE(spi_nand_flash_init_device(&nand_flash_config, &device_handle) == ESP_OK);

uint32_t sector_num, sector_size, block_size;
REQUIRE(spi_nand_flash_get_capacity(device_handle, &sector_num) == 0);
REQUIRE(spi_nand_flash_get_sector_size(device_handle, &sector_size) == 0);
REQUIRE(spi_nand_flash_get_block_size(device_handle, &block_size) == 0);

uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(sector_size, MALLOC_CAP_DEFAULT);
REQUIRE(pattern_buf != NULL);
uint8_t *temp_buf = (uint8_t *)heap_caps_malloc(sector_size, MALLOC_CAP_DEFAULT);
REQUIRE(temp_buf != NULL);

fill_buffer(PATTERN_SEED, pattern_buf, sector_size / sizeof(uint32_t));

bool is_page_free = true;
uint32_t test_block = 20;
uint32_t test_page = test_block * (block_size / sector_size); //(block_num * pages_per_block)
uint32_t dst_page = test_page + 1;
if (test_page < sector_num) {
// Verify if test_page is free
REQUIRE(nand_wrap_is_free(device_handle, test_page, &is_page_free) == 0);
REQUIRE(is_page_free == true);
// Write/program test_page
REQUIRE(nand_wrap_prog(device_handle, test_page, pattern_buf) == 0);
// Verify if test_page is used/programmed
REQUIRE(nand_wrap_is_free(device_handle, test_page, &is_page_free) == 0);
REQUIRE(is_page_free == false);

REQUIRE(nand_wrap_read(device_handle, test_page, 0, sector_size, temp_buf) == 0);
REQUIRE(nand_wrap_copy(device_handle, test_page, dst_page) == 0);
REQUIRE(nand_wrap_read(device_handle, dst_page, 0, sector_size, temp_buf) == 0);
}
free(pattern_buf);
free(temp_buf);
spi_nand_flash_deinit_device(device_handle);
esp_partition_unload_all();
}
6 changes: 6 additions & 0 deletions spi_nand_flash/host_test/partition_table.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
storage, data, fat, , 8M,
10 changes: 10 additions & 0 deletions spi_nand_flash/host_test/pytest_nand_flash_linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut


@pytest.mark.linux
@pytest.mark.host_test
def test_nand_flash_linux(dut: Dut) -> None:
dut.expect_exact('All tests passed', timeout=120)
8 changes: 8 additions & 0 deletions spi_nand_flash/host_test/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CONFIG_IDF_TARGET="linux"
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table.csv"
CONFIG_MMU_PAGE_SIZE=0X10000
CONFIG_ESP_PARTITION_ENABLE_STATS=y
4 changes: 4 additions & 0 deletions spi_nand_flash/include/spi_nand_flash.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@

#include <stdint.h>
#include "esp_err.h"
#ifndef CONFIG_IDF_TARGET_LINUX
#include "driver/spi_common.h"
#include "driver/spi_master.h"
#endif

#ifdef __cplusplus
extern "C" {
Expand All @@ -21,7 +23,9 @@ extern "C" {
@note The spi_device_handle_t must be initialized with the flag SPI_DEVICE_HALFDUPLEX
*/
struct spi_nand_flash_config_t {
#ifndef CONFIG_IDF_TARGET_LINUX
spi_device_handle_t device_handle; ///< SPI Device for this nand chip.
#endif
uint8_t gc_factor; ///< The gc factor controls the number of blocks to spare block ratio.
///< Lower values will reduce the available space but increase performance
};
Expand Down
11 changes: 11 additions & 0 deletions spi_nand_flash/priv_include/nand.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

#include <stdint.h>
#include "spi_nand_flash.h"
#ifdef CONFIG_IDF_TARGET_LINUX
#include "freertos/FreeRTOS.h"
#include "esp_partition.h"
#endif
#include "freertos/semphr.h"

#ifdef __cplusplus
Expand Down Expand Up @@ -40,6 +44,10 @@ typedef struct {
uint8_t log2_ppb; //is power of 2, log2_ppb shift ((1<<log2_ppb) * page_size) will be stored in block size
uint32_t block_size;
uint32_t page_size;
#ifdef CONFIG_IDF_TARGET_LINUX
uint32_t emulated_page_size;
uint32_t emulated_page_oob;
#endif
uint32_t num_blocks;
uint32_t read_page_delay_us;
uint32_t erase_block_delay_us;
Expand Down Expand Up @@ -68,6 +76,9 @@ struct spi_nand_flash_device_t {
uint8_t *work_buffer;
uint8_t *read_buffer;
SemaphoreHandle_t mutex;
#ifdef CONFIG_IDF_TARGET_LINUX
const esp_partition_t *partition;
#endif
};

esp_err_t nand_register_dev(spi_nand_flash_device_t *handle);
Expand Down
Loading

0 comments on commit 05e804f

Please sign in to comment.