LittleFS on nRF5340 + MX25L51245G (64MB QSPI NOR): -14 (EFAULT) after ~1600 files, filesystem wiped on reboot

Environment

  • Board: nRF5340 custom PCB
  • SDK: nRF Connect SDK 3.0.2
  • External flash: MX25L51245GZ2I-08G (64MB NOR, QSPI interface)
  • Filesystem: LittleFS via Zephyr fstab
  • IDE: VS Code with nRF Connect extension

What I am doing

I am writing 2500 to 3000 files into a single LittleFS directory on external QSPI flash. Each file is approximately 1KB in size and stores pin code data. Files are named by their pin code value for example /external/pin_codes/12345678.

For each pin received over BLE I follow this exact sequence:

  1. Write the pin file using fs_write_pin_code_data_to_file()
  2. Verify the written size using fs_read_pin_code_data_file_size()
  3. Write a 12-byte GUID/transaction record into a separate session file using fs_write_current_ble_session_command_guids_data_on_file()

Total data written is roughly 2500 x 1KB = ~2.5MB. This is less than 4% of the 64MB flash. Storage space is not the issue.


Problem

After approximately 1600 file writes the following errors start occurring:

  • fs_open() or fs_write() returns -14 (EFAULT)
  • Occasionally -2 (ENOENT) on file open even though the directory exists
  • On the next reboot all files are completely wiped — all 1600+ pin files are gone with no warning

Key observation: if I comment out the call fs_write_current_ble_session_command_guids_data_on_file() at step 3, I can write 2500 to 3000 pin files without any error. The moment that call is active errors begin at around 1600 writes. The function fs_write_current_ble_session_command_guids_data_on_file() does internally on every single call — it opens the session file, seeks to the position, writes 12 bytes of GUID/transaction data, then closes the file. This means across 1600 pin writes the same session file is being opened, seeked, written and closed 1600 times. 

Also observed: if I batch the session file writes — record every 20 records in a buffer and then write — the error does not appear for 3000 pins but it may appear after that.

The major concern is that the filesystem behaves very unpredictably — I am not confident about when it will randomly wipe out all files and data

DTS configuration

dts
&qspi {
    pinctrl-0 = <&qspi_default>;
    pinctrl-1 = <&qspi_sleep>;
    pinctrl-names = "default", "sleep";

    mx25l512: mx25l51245g@0 {
        compatible = "nordic,qspi-nor";
        reg = <0>;
        writeoc = "pp4io";
        readoc = "read4io";
        sck-frequency = <8000000>;
        jedec-id = [c2 20 1A];
        sfdp-bfp = [
            e5 20 fb ff  1f ff ff ff  44 eb 08 6b  08 3b 04 bb
            fe ff ff ff  ff ff 00 ff  ff ff 44 eb  0c 20 0f 52
            10 d8 00 ff  d6 49 c5 00  81 df 04 e3  44 03 67 38
            30 b0 30 b0  f7 bd d5 5c  4a 9e 29 ff  f0 50 f9 85
        ];
        size = <0x20000000>;
        has-dpd;
        t-enter-dpd = <10000>;
        t-exit-dpd = <30000>;
        external_partition: partition {
            label = "external";
        };
    };
};

fstab {
    compatible = "zephyr,fstab";
    lfs1: lfs1 {
        compatible = "zephyr,fstab,littlefs";
        mount-point = "/external";
        partition = <&external_partition>;
        read-size = <16>;
        prog-size = <256>;
        cache-size = <4096>;
        lookahead-size = <512>;
        block-cycles = <500>;
    };
};

Functions being used

Below is the outer flow that runs once per BLE pin command. It ties all three steps together:

c
// Called once per pin received over BLE.
// Writes pin file, verifies it, then writes the transaction GUID.
void process_single_pin(uint32_t pin_code, uint8_t pin_length,
                        const uint8_t *data, uint16_t start,
                        uint16_t length, int32_t command_guid,
                        int32_t time_stamp)
{
    // Step 1 — write the pin data file (~1KB) to flash
    int write_res = fs_write_pin_code_data_to_file(
                        pin_code, pin_length, data, start, length);
    sb_print_log(LOG_DEBUG, "Pin write result: %d", write_res);

    // Step 2 — verify written size matches expected
    int pin_size = fs_read_pin_code_data_file_size(pin_code);
    if (pin_size != length)
    {
        sb_print_log(LOG_ERR, "Size mismatch. Expected: %d Found: %d",
                     length, pin_size);
        fs_delete_pin_code_data(pin_code);
        return;
    }

    // Step 3 — write 12-byte GUID record into separate session file.
    // Internally this opens the session file, seeks to the current
    // counter offset, writes 12 bytes, then closes the file.
    // This open/seek/write/close happens once per pin — so across
    // 1600 pins this function opens and closes the same file 1600 times.
    fs_write_current_ble_session_command_guids_data_on_file(command_guid, len);
                                    
}

Step 1 — Write pin file

c
int fs_write_pin_code_data_to_file(uint32_t pin_code, uint8_t pin_length,
                                    const uint8_t *data, uint16_t start,
                                    uint16_t length)
{
    int write_count = 0;
    char pin_code_file_name[15];
    char full_file_name[MAX_PATH_LEN];

    snprintf(pin_code_file_name, sizeof(pin_code_file_name), "%" PRIu32, pin_code);
    snprintf(full_file_name, sizeof(full_file_name), "%s/%s",
             PIN_CODES_DIRECTORY, pin_code_file_name);

    for (int i = 0; i < 3; i++)
    {
        int result = __fs_write_bytes_to_file(fs_external_mount_point,
                                              full_file_name,
                                              data + start, length,
                                              &write_count);
        if (result < 0)
        {
            k_sleep(K_MSEC(100));
            result = __fs_write_bytes_to_file(fs_external_mount_point,
                                              full_file_name,
                                              data + start, length,
                                              &write_count);
        }
        if (result == APP_SUCCESS)
            break;
    }
    return write_count;
}

int __fs_write_bytes_to_file(struct fs_mount_t *fs_mount_point,
                              const char *abs_path,
                              const char *data, int len, int *write_count)
{
    int rc;
    struct fs_file_t file;
    char fname[MAX_PATH_LEN];

    fs_file_t_init(&file);
    snprintf(fname, sizeof(fname), "%s/%s",
             fs_mount_point->mnt_point, abs_path);

    rc = fs_open(&file, fname, FS_O_CREATE | FS_O_WRITE);
    if (rc < 0)
    {
        sb_print_log(LOG_DEBUG, "File Open Err: %d", rc);
        return rc;
    }

    rc = fs_write(&file, data, len);
    fs_sync(&file);
    fs_close(&file);

    *write_count = rc;
    return rc < 0;
}

Step 2 — Verify written file size

c
int fs_read_pin_code_data_file_size(uint32_t pin_code)
{
    char pin_code_file_name[45];
    char file_name[MAX_PATH_LEN];

    snprintf(pin_code_file_name, sizeof(pin_code_file_name), "%u", pin_code);
    snprintf(file_name, sizeof(file_name), "%s/%s/%s",
             fs_external_mount_point->mnt_point,
             PIN_CODES_DIRECTORY,
             pin_code_file_name);

    int rc;
    int size = 0;

    for (int i = 0; i < 3; i++)
    {
        struct fs_dirent dirent;
        dirent.type = FS_DIR_ENTRY_FILE;
        rc = fs_stat(file_name, &dirent);
        if (rc == 0)
        {
            size = dirent.size;
            break;
        }
    }

    if (rc < 0)
    {
        sb_print_log(LOG_DEBUG, "Error %d for Fs Stat (%s)", rc, file_name);
        return rc;
    }

    return size;
}


Step 3 — Write the 12 byte GUID data in session file

int8_t fs_write_current_ble_session_command_guids_data_on_file(uint8_t *data_to_write, uint8_t len)
{
     int8_t rc;
     struct fs_file_t file;
     fs_file_t_init(&file);
     char fname[MAX_PATH_LEN];
     snprintf(fname, sizeof(fname), "%s/%s",
     fs_external_mount_point->mnt_point,
     TRANSACTIONS_BLE_SESSION_DATA_FILE);
     rc = fs_open(&file, fname, FS_O_CREATE | FS_O_WRITE);
     if (rc < 0)
     {
        return -1;
     }

   /**
   * Each record is of 12 bytes.
   * Seek to the correct position for this record.
   */
    int32_t location_index = g_current_ble_session_transaction_counter * len;
    rc = fs_seek(&file, location_index, FS_SEEK_SET);
    if (rc != 0)
    {
       sb_print_log(LOG_DEBUG, "File Seek Err: %d", rc);
       return rc;
     }

   /**
   * Write the record data.
   */
   rc = fs_write(&file, data_to_write, len);
   if (rc != len)
   {
      sb_print_log(LOG_DEBUG, "Failed to Write Current Session GUIDs in file: %d", rc);
   }

   fs_close(&file);

   /**
   * Update the counter to enable circular usage of the file.
   */
    g_current_ble_session_transaction_counter++;
    if (g_current_ble_session_transaction_counter >= MAX_CURRENT_SESSION_TRANSACTIONS_COUNT)
    {
       g_current_ble_session_transaction_counter = 0;
    }

    return rc;
}






Parents Reply Children
No Data
Related