# LLFS (Linked List File System)

## Introduction
The llfs filesystem can be generated using the [mkllfs](mkllfs.md) utility.
The resulting C file encapsulates the filesystem data within a linked list which can be used by the llfs library.

As a flat filesystem, llfs lacks support for directories. 
Using the llfs API, information about the files in the filesystem can be retrieved,
and the files can be read using direct memory access.
Alternatively, the POSIX file functions can be used. 
But this is more resource intensive, as data must be copied before using it.

It's essential to note that the llfs filesystem operates in a read-only mode,
restricting operations solely to read functions.

## Table of contents
 - [Introduction](#introduction)
 - [Table of contents](#table-of-contents)
 - [Initialization](#initialization)
 - [Usage of the llfs API](#usage-of-the-llfs-api)
   - [The `llfs_file_t` struct](#the-llfs_file_t-struct)
   - [Getting a list of files](#getting-a-list-of-files)
     - [Iterator function (not recommended)](#iterator-function-not-recommended)
   - [Reading a file](#reading-a-file)
   - [Getting the number of files](#getting-the-number-of-files)
 - [Using the POSIX file functions](#using-the-posix-file-functions)
 - [Enabling the external loader in STM32CubeIDE](#enabling-the-external-loader-in-stm32cubeide)

## Initialization
Before using the llfs API, or the file related POSIX (fopen, fgetc, ...) functions, the filesystem must be initialized by calling `llfs_init()`.

## Usage of the llfs API
### The `llfs_file_t` struct
The `llfs_file_t` struct contains information about a file in the filesystem.
```c
typedef struct {
    const uint8_t* data; // Pointer to the file data (len bytes)
    const char* name;    // Null-terminated string with the filename
    size_t len;          // Length of the file data
} llfs_file_t;
```
The data pointer points to the data of the file in the filesystem, and can be used to read the file.

### Getting a list of files
```c
#include "llfs.h"

void main(void) {
    llfs_init();
    
    // Allocate space for 10 files
    llfs_file_t file_list[10];
    
    // Get the file list
    size_t file_count = llfs_file_list(file_list, 10, NULL);
    
    // Loop through the files and print their names and sizes
    for (int i = 0; i < file_count; i++) {
        LOG_INFO(TAG, "File: %s, size: %d", file_list[i].name, file_list[i].len);
    }
}
```
Result:
```
[Info]      (2009) [main]: File: image.bmp, size: 9270
[Info]      (2013) [main]: File: python_file.py, size: 645
[Info]      (2019) [main]: File: image2.bmp, size: 7738
[Info]      (2024) [main]: File: filename with a space.txt, size: 61
[Info]      (2031) [main]: File: file1.txt, size: 77
```

It is also possible to use a file extension filter (e.g. `*.bmp`, `*.txt`, `*.py`).
```c
    // ...
    size_t file_count = llfs_file_list(file_list, 10, "*.bmp");
    // ...
```
This will only return files with the `.bmp` extension.
````
[Info]      (2009) [main]: File: image.bmp, size: 9270
[Info]      (2019) [main]: File: image2.bmp, size: 7738
````

#### Iterator function (not recommended)
It is also possible to iterate through the files without allocating an array.
When the memory pointer is `NULL`, the iterator will start at the beginning of the file list. 
Each call to `llfs_next_file()` will return the next file in the list,
if a filter is specified files that don't match the filter will be skipped.
```c
#include "llfs.h"

void main(void) {
    llfs_init();
    
    // Get the file list
    void* mem = NULL;   // Pointer for internal use by the llfs library
    llfs_file_t* file;
    while ((file = llfs_next_file(&mem, ".bmp")) != NULL) {
        LOG_INFO(TAG, "File: %s", file->name);
    }
}
```
While this method doesn't require allocating memory for the file list,
it is slower than the previous method due to the overhead calling the function for each file.
Additionally, the required memory for the filelist is very small, so it's recommended to use the first method. 

### Reading a file
```c
#include "llfs.h"

void main(void) {
    llfs_init();
    
    // Get a file by name
    llfs_file_t* file = llfs_file_open("filename with a space.txt");
    
    if (file != NULL) {
        // Print the file name, size and data
        LOG_INFO(TAG, "File found: %s, size: %d", file->name, file->len);
        LOG_INFO(TAG, "File data: %s", file->data);
    } else {
        LOG_WARN(TAG, "File not found");
    }
}
```
Result:
```
[Info]      (2040) [main]: File found: filename with a space.txt, size: 61
[Info]      (2047) [main]: File data: This is a file with a space in it's filename.
```

### Getting the number of files
```c
#include "llfs.h"

void main(void) {
    llfs_init();
    
    // Get the number of files
    size_t file_count = llfs_file_count();
    
    // Print the number of files
    LOG_INFO(TAG, "File count: %d", file_count);
}
```

## Using the POSIX file functions
The llfs library also supports the POSIX file functions.
As the file system is read-only, write functions are not implemented.
There is also a limit on the number of files that can be open concurrently,
this is set by the `POSIX_MAX_FILES` macro in `llfs.c`. 
The default value is 10, but there are already 3 files in use (stdin, stdout, stderr),
so the maximum number of files that can be open is 7.

The following functions are tested and working, but other functions might also work:
 - `fopen`
 - `fclose`
 - `fgetc`
 - `fread`
 - `fseek`
 - `ftell`
 - `rewind`
 - `fstat`
 - `fileno`

## Enabling the external loader in STM32CubeIDE
In order to write the file system data to the QSPI flash, the external loader must be enabled in STM32CubeIDE.
This can be done by opening the debug configuration:

![screenshot of step 1](img/ext_loader_step_1.png)

Then, in the `Debugger` tab:
3. Enable the `External Loader`
4. Click the `Scan` button
5. Select the correct loader: `N25Q128A_STM32F746G-DISCO, 0x90000000 ...`

![screenshot of step 2](img/ext_loader_step_2.png)
