Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
332 changes: 326 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,21 @@ See the [tests/README.md](tests/README.md) for more information about testing.

## Usage

The module can be loaded and mounted using DMVFS:
The module can be loaded and mounted using DMVFS. **Important:** DMDEVFS requires a configuration path to be specified during mounting.

```c
#include "dmvfs.h"

// Initialize DMVFS
dmvfs_init(16, 32);

// Mount the driver filesystem at /mnt
dmvfs_mount_fs("dmdevfs", "/mnt", NULL);
// Mount the driver filesystem at /mnt with configuration path
// The configuration path must point to a directory containing driver configuration files
dmvfs_mount_fs("dmdevfs", "/mnt", "/etc/dmdevfs");

// Use standard file operations
void* fp;
dmvfs_fopen(&fp, "/mnt/file.txt", DMFSI_O_RDONLY, 0, 0);
dmvfs_fopen(&fp, "/mnt/device0/file.txt", DMFSI_O_RDONLY, 0, 0);
// ... use file ...
dmvfs_fclose(fp);

Expand All @@ -108,6 +109,305 @@ dmvfs_unmount_fs("/mnt");
dmvfs_deinit();
```

**Note:** The configuration parameter cannot be NULL or empty. It must contain a valid path to a directory with driver configuration files. See the [Configuration Files](#configuration-files) section below for detailed information.

## Configuration Files

DMDEVFS uses configuration files to define and initialize device drivers. This is a critical mechanism that allows the filesystem to dynamically discover and configure hardware drivers at mount time.

### Overview

When DMDEVFS is mounted with a configuration path, it:
1. Scans the configuration directory for `.ini` files
2. Reads each configuration file to determine which driver to load
3. Initializes the driver with parameters from the configuration
4. Maps the driver to a device path within the filesystem

### Configuration File Format

Configuration files use the INI format with a `[main]` section:

```ini
[main]
driver_name = your_driver_name
# Additional driver-specific parameters
parameter1 = value1
parameter2 = value2
```

#### Required Fields

- **`driver_name`**: The name of the DMOD driver module to load (must implement dmdrvi interface)

#### Optional Fields

Any additional parameters in the configuration file are passed to the driver's initialization function. The interpretation of these parameters depends on the specific driver implementation.

### Configuration Directory Structure

DMDEVFS supports both flat and hierarchical configuration layouts:

#### Flat Structure
```
/etc/dmdevfs/
├── spi_flash.ini # Configuration for SPI flash driver
├── i2c_eeprom.ini # Configuration for I2C EEPROM driver
└── uart_storage.ini # Configuration for UART storage driver
```

#### Hierarchical Structure
```
/etc/dmdevfs/
├── flash/
│ ├── spi0.ini # SPI flash on bus 0
│ └── spi1.ini # SPI flash on bus 1
└── eeprom/
├── i2c0.ini # EEPROM on I2C bus 0
└── i2c1.ini # EEPROM on I2C bus 1
```

The hierarchical structure is useful for organizing drivers by type or bus.

### Example Configuration Files

#### Example 1: SPI Flash Driver

**File:** `/etc/dmdevfs/spi_flash.ini`

```ini
[main]
driver_name = dmspiflash
spi_bus = 0
chip_select = 1
speed_hz = 1000000
mode = 0
```

#### Example 2: I2C EEPROM Driver

**File:** `/etc/dmdevfs/eeprom.ini`

```ini
[main]
driver_name = dmi2ceeprom
i2c_bus = 0
device_address = 0x50
page_size = 64
total_size = 8192
```

#### Example 3: UART Storage Driver

**File:** `/etc/dmdevfs/uart_storage.ini`

```ini
[main]
driver_name = dmuartstorage
uart_port = 2
baud_rate = 115200
data_bits = 8
parity = none
stop_bits = 1
```

### How Configuration Files Are Interpreted

1. **File Discovery**: DMDEVFS recursively scans the configuration directory
2. **INI Parsing**: Each `.ini` file is parsed using the dmini module
3. **Driver Loading**: The `driver_name` parameter determines which driver module to load
4. **Driver Initialization**: The entire INI context is passed to the driver's `dmdrvi_create()` function
5. **Device Mapping**: The driver is registered and becomes accessible through the filesystem

### Driver Name Resolution

DMDEVFS determines which driver module to load using a priority-based resolution mechanism. The driver name can be specified in three ways, checked in the following order:

#### 1. From INI File Content (Highest Priority)

The `driver_name` field in the `[main]` section explicitly specifies which driver to load:

```ini
# File: /etc/dmdevfs/storage.ini
[main]
driver_name = dmspiflash
# ... other parameters
```

This loads the `dmspiflash` driver module, regardless of the filename or directory.

#### 2. From Configuration Filename (Fallback)

If `driver_name` is not specified in the INI file, the basename of the configuration file (without `.ini` extension) is used:

```ini
# File: /etc/dmdevfs/dmi2ceeprom.ini
[main]
# No driver_name specified
i2c_bus = 0
device_address = 0x50
```

This loads the `dmi2ceeprom` driver module based on the filename.

#### 3. From Directory Name (Hierarchical Configuration)

When configuration files are organized in subdirectories, the directory name can be passed to nested configurations:

```
/etc/dmdevfs/
└── dmspiflash/
├── device0.ini # Uses "dmspiflash" from directory name
└── device1.ini # Uses "dmspiflash" from directory name
```

Each `.ini` file in the `dmspiflash/` directory will use `dmspiflash` as the default driver name unless overridden by the `driver_name` field in the file itself.

**Example of Combined Usage:**

```
/etc/dmdevfs/
├── dmflash.ini # Uses filename: "dmflash" driver
├── dmspiflash/
│ ├── device0.ini # Uses directory: "dmspiflash" driver
│ └── device1.ini # Uses directory: "dmspiflash" driver
└── custom.ini # Contains driver_name=dmi2ceeprom in file
```

### Device Numbering and Path Generation

When a driver is initialized through its `dmdrvi_create()` function, it returns a device number structure (`dev_num`) that controls how the driver appears in the filesystem. This mechanism allows multiple instances of the same driver with different configurations.

#### Device Number Structure

The device number consists of:
- **major**: Primary device identifier (typically for device type or bus)
- **minor**: Secondary device identifier (typically for device instance)
- **flags**: Indicates which numbers are provided (`DMDRVI_NUM_MAJOR`, `DMDRVI_NUM_MINOR`)

#### Path Generation Rules

The resulting filesystem path depends on which device numbers the driver provides:

| Major | Minor | Resulting Path | Example |
|-------|-------|----------------|---------|
| ✓ | ✓ | `<driver_name><major>/<minor>` | `dmspiflash0/1` |
| ✗ | ✓ | `<driver_name>x/<minor>` | `dmspiflashx/0` |
| ✓ | ✗ | `<driver_name><major>` | `dmspiflash0` |
| ✗ | ✗ | `<driver_name>` | `dmspiflash` |

#### Examples

**Example 1: Both Major and Minor Provided**

Configuration file: `/etc/dmdevfs/spi0.ini`
```ini
[main]
driver_name = dmspiflash
spi_bus = 0 # Driver uses this to set major=0
chip_select = 1 # Driver uses this to set minor=1
```

Resulting path: `/mnt/dmspiflash0/1` (assuming mounted at `/mnt`)

**Example 2: Only Minor Provided**

Configuration file: `/etc/dmdevfs/eeprom.ini`
```ini
[main]
driver_name = dmi2ceeprom
device_address = 0x50 # Driver uses this to set minor=0
```

Resulting path: `/mnt/dmi2ceepromx/0`

**Example 3: Only Major Provided**

Configuration file: `/etc/dmdevfs/uart.ini`
```ini
[main]
driver_name = dmuartstorage
uart_port = 2 # Driver uses this to set major=2
```

Resulting path: `/mnt/dmuartstorage2`

**Example 4: No Device Numbers**

Configuration file: `/etc/dmdevfs/generic.ini`
```ini
[main]
driver_name = dmgenericdriver
```

Resulting path: `/mnt/dmgenericdriver`

#### Multiple Device Instances

You can configure multiple instances of the same driver by using separate configuration files:

```
/etc/dmdevfs/
├── spi_flash0.ini # major=0, minor=0 → /mnt/dmspiflash0/0
├── spi_flash1.ini # major=0, minor=1 → /mnt/dmspiflash0/1
└── spi_flash2.ini # major=1, minor=0 → /mnt/dmspiflash1/0
```

Or using hierarchical organization:

```
/etc/dmdevfs/dmspiflash/
├── bus0_cs0.ini # major=0, minor=0 → /mnt/dmspiflash0/0
├── bus0_cs1.ini # major=0, minor=1 → /mnt/dmspiflash0/1
└── bus1_cs0.ini # major=1, minor=0 → /mnt/dmspiflash1/0
```

#### Understanding the 'x' Notation

When only a minor number is provided (major not set), the path uses `x` as a placeholder. This is useful when the driver doesn't use a major/minor hierarchy but still wants to enumerate devices:

```
/mnt/dmspiflashx/0
/mnt/dmspiflashx/1
/mnt/dmspiflashx/2
```

This convention keeps paths consistent and prevents naming collisions.

#### Driver Implementation Notes

The device numbers are determined by the driver itself based on the configuration parameters. For example:
- An SPI flash driver might use `spi_bus` as the major number and `chip_select` as the minor number
- An I2C driver might use only the `device_address` as the minor number
- A generic driver might not use device numbers at all

Consult your specific driver's documentation to understand how it uses configuration parameters to set device numbers.

### Best Practices

1. **Use Descriptive Names**: Name configuration files to reflect their purpose (e.g., `spi_flash_boot.ini`)
2. **Document Parameters**: Add comments in INI files to explain parameter meanings
3. **Validate Configuration**: Ensure driver modules exist before deploying configuration files
4. **Organize by Function**: Use subdirectories to group related drivers
5. **Test Individually**: Test each driver configuration independently before integrating

### Troubleshooting

Common configuration issues:

- **"Config path is NULL/empty"**: Ensure you provide a valid path when mounting
- **"Failed to open config directory"**: Verify the configuration directory exists and is accessible
- **"Failed to read driver for config"**: Check INI file syntax and format
- **"Failed to configure driver"**: Verify the driver module is available and implements dmdrvi interface
- **Driver module not found**: Ensure the driver module is loaded or available in the module repository

### Dynamic Reconfiguration

Currently, DMDEVFS loads configuration at mount time. To apply new configurations:

1. Unmount the filesystem: `dmvfs_unmount_fs("/mnt")`
2. Update configuration files
3. Remount the filesystem: `dmvfs_mount_fs("dmdevfs", "/mnt", "/etc/dmdevfs")`

## API

The module implements the full DMFSI interface:
Expand Down Expand Up @@ -158,7 +458,27 @@ dmdevfs/

## Integration into Your Project

### Using CMake FetchContent
### Method 1: Using `dmod_link_modules` (Recommended)

The most convenient way to add dmdevfs to your DMOD-based project (in module mode) is to use the `dmod_link_modules` CMake macro:

```cmake
# In your CMakeLists.txt, after setting up your DMOD module

# Link DMDEVFS module to your project
dmod_link_modules(your_module_name
dmdevfs
# ... other modules
)
```

This macro automatically:
- Downloads the dmdevfs module from the repository
- Configures it for DMOD module mode
- Links it to your target
- Handles all dependencies (dmfsi, dmdrvi, dmini, dmlist)

### Method 2: Using CMake FetchContent

```cmake
include(FetchContent)
Expand All @@ -179,7 +499,7 @@ FetchContent_MakeAvailable(dmdevfs)
target_link_libraries(your_target PRIVATE dmdevfs)
```

### Manual Integration
### Method 3: Manual Integration

1. Clone the repository: `git clone https://github.com/choco-technologies/dmdfs.git`
2. Add as subdirectory: `add_subdirectory(dmdevfs)`
Expand Down