Skip to content
Merged
Show file tree
Hide file tree
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
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,40 @@ When a driver is initialized through its `dmdrvi_create()` function, it returns
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`)
- **flags**: Indicates which numbers are provided (`DMDRVI_NUM_MAJOR`, `DMDRVI_NUM_MINOR`, `DMDRVI_NUM_ALT_NAME`)
- **alt_name**: Optional alternative (human-friendly) file name, valid when `DMDRVI_NUM_ALT_NAME` flag is set

#### 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` |

#### Alternative Names

When a driver sets the `DMDRVI_NUM_ALT_NAME` flag and provides an `alt_name` in
`dmdrvi_dev_num_t`, DMDEVFS creates an additional mapping from `/<alt_name>` at
the root of the filesystem to the same driver node.

- The alternative name appears as a separate entry when listing the root directory.
- Opening, reading, writing, or stat-ing the alternative path works identically to
using the primary path.
- The `alt_name` flag can be combined with `DMDRVI_NUM_MAJOR` and/or
`DMDRVI_NUM_MINOR` — the primary path is still generated normally and both
paths remain accessible simultaneously.

**Example 5: Driver with Alternative Name**

A GPIO driver configured as a temperature-sensor pin might set `alt_name = "temp_sensor"`:

Accessible paths:
- Primary: `/mnt/dmgpio3` (major=3, no minor)
- Alternative: `/mnt/temp_sensor`

#### Path Generation Rules

Expand Down
180 changes: 145 additions & 35 deletions src/dmdevfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@ typedef struct
bool was_loaded; // Indicates if the driver was loaded by dmdevfs
bool was_enabled; // Indicates if the driver was enabled by dmdevfs
path_t path; // Path associated with the driver
path_t alt_path; // Alternative path (empty string if not set)
} driver_node_t;

typedef struct
{
driver_node_t* driver; // Last driver
char* directory_path; // Directory path
driver_node_t* driver; // Current driver (primary phase)
char* directory_path; // Directory path
bool in_alt_phase; // Whether we are iterating alternative names
driver_node_t* alt_driver; // Current driver in the alternative names phase
} directory_node_t;

/**
Expand Down Expand Up @@ -90,9 +93,12 @@ static int read_driver_node_path( const driver_node_t* node, char* path_buffer,
static int compare_paths_ignore_trailing_slash( const char* path1, const char* path2 );
static int compare_driver_directory( const void* data, const void* user_data );
static int compare_driver_node_path( const void* data, const void* user_data );
static int compare_driver_alt_path( const void* data, const void* user_data );
static int compare_driver_alt_directory( const void* data, const void* user_data );
static int compare_driver(const void* data, const void* user_data );
static bool is_directory( dmfsi_context_t ctx, const char* path );
static driver_node_t* get_next_driver_node( dmfsi_context_t ctx, driver_node_t* current, const char* dir_path );
static driver_node_t* get_next_alt_driver_node( dmfsi_context_t ctx, driver_node_t* current, const char* dir_path );

static driver_node_t* find_driver_node( dmfsi_context_t ctx, const char* path );
static int driver_stat( driver_node_t* context, const char* path, dmdrvi_stat_t* stat );
Expand Down Expand Up @@ -669,6 +675,8 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _opendir, (dmfsi_context_t ct
}
dir_node->driver = get_next_driver_node(ctx, NULL, path);
dir_node->directory_path = Dmod_StrDup(path);
dir_node->in_alt_phase = false;
dir_node->alt_driver = NULL;

*dp = dir_node;

Expand All @@ -688,47 +696,83 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _readdir, (dmfsi_context_t ct
}

directory_node_t* dir_node = (directory_node_t*)dp;
if (dir_node->driver == NULL)

if (!dir_node->in_alt_phase)
{
return DMFSI_ERR_NOT_FOUND; // No more entries
// Primary phase: iterate drivers by their primary paths
if (dir_node->driver == NULL)
{
// No more primary entries; switch to alternative names phase
dir_node->in_alt_phase = true;
dir_node->alt_driver = get_next_alt_driver_node(ctx, NULL, dir_node->directory_path);
}
else
{
driver_node_t* driver = dir_node->driver;

path_t parent_dir;
if (read_driver_parent_directory(dir_node->driver, parent_dir, sizeof(parent_dir)) != 0)
{
DMOD_LOG_ERROR("Failed to read parent directory for driver\n");
return DMFSI_ERR_GENERAL;
}

bool file_should_be_listed = compare_paths_ignore_trailing_slash(dir_node->directory_path, parent_dir) == 0;
if(file_should_be_listed)
{
// Extract basename from the full path for the directory entry
read_base_name(driver->path, entry->name, sizeof(entry->name));

dmdrvi_stat_t stat;
int res = driver_stat(driver, driver->path, &stat);
if (res != 0)
{
DMOD_LOG_ERROR("Failed to get file stats for: %s\n", driver->path);
return DMFSI_ERR_GENERAL;
}

entry->size = stat.size;
entry->attr = stat.mode;
}
else
{
// Extract the immediate subdirectory name relative to the listing directory.
// E.g. listing "/" with a driver whose parent is "dmgpio8/" yields "dmgpio8".
read_next_subdir_name(dir_node->directory_path, parent_dir, entry->name, sizeof(entry->name));
entry->size = 0;
entry->attr = DMFSI_ATTR_DIRECTORY;
}

// Move to next driver for subsequent call
dir_node->driver = get_next_driver_node(ctx, driver, dir_node->directory_path);
return DMFSI_OK;
}
}
driver_node_t* driver = dir_node->driver;

path_t parent_dir;
if (read_driver_parent_directory(dir_node->driver, parent_dir, sizeof(parent_dir)) != 0)
// Alternative names phase: iterate drivers that expose an alt_path in this directory
if (dir_node->alt_driver == NULL)
{
DMOD_LOG_ERROR("Failed to read parent directory for driver\n");
return DMFSI_ERR_GENERAL;
return DMFSI_ERR_NOT_FOUND; // No more entries
}

bool file_should_be_listed = compare_paths_ignore_trailing_slash(dir_node->directory_path, parent_dir) == 0;
if(file_should_be_listed)
{
// Extract basename from the full path for the directory entry
read_base_name(driver->path, entry->name, sizeof(entry->name));

dmdrvi_stat_t stat;
int res = driver_stat(driver, driver->path, &stat);
if (res != 0)
{
DMOD_LOG_ERROR("Failed to get file stats for: %s\n", driver->path);
return DMFSI_ERR_GENERAL;
}
driver_node_t* alt_driver = dir_node->alt_driver;

entry->size = stat.size;
entry->attr = stat.mode;
}
else
strncpy(entry->name, alt_driver->dev_num.alt_name, sizeof(entry->name));
entry->name[sizeof(entry->name) - 1] = '\0';

dmdrvi_stat_t stat;
int res = driver_stat(alt_driver, alt_driver->path, &stat);
if (res != 0)
{
// Extract the immediate subdirectory name relative to the listing directory.
// E.g. listing "/" with a driver whose parent is "dmgpio8/" yields "dmgpio8".
read_next_subdir_name(dir_node->directory_path, parent_dir, entry->name, sizeof(entry->name));
entry->size = 0;
entry->attr = DMFSI_ATTR_DIRECTORY;
DMOD_LOG_ERROR("Failed to get file stats for alt path: %s\n", alt_driver->alt_path);
return DMFSI_ERR_GENERAL;
}

// Move to next driver for subsequent call
dir_node->driver = get_next_driver_node(ctx, driver, dir_node->directory_path);
entry->size = stat.size;
entry->attr = stat.mode;

// Advance to the next driver with an alt_path in this directory
dir_node->alt_driver = get_next_alt_driver_node(ctx, alt_driver, dir_node->directory_path);
return DMFSI_OK;
}

Expand Down Expand Up @@ -1003,6 +1047,14 @@ static driver_node_t* configure_driver(const char* driver_name, dmini_context_t
return NULL;
}

driver_node->alt_path[0] = '\0';
if (driver_node->dev_num.flags & DMDRVI_NUM_ALT_NAME)
{
Dmod_SnPrintf(driver_node->alt_path, sizeof(driver_node->alt_path),
"/%s", driver_node->dev_num.alt_name);
DMOD_LOG_INFO("Driver %s has alternative path: %s\n", driver_name, driver_node->alt_path);
}

DMOD_LOG_STEP(0, "Configured driver: %s (path: %s)\n", driver_name, driver_node->path);

return driver_node;
Expand Down Expand Up @@ -1555,6 +1607,51 @@ static int compare_driver_node_path( const void* data, const void* user_data )
return strcmp(node->path, path);
}

/**
* @brief Compare the alternative path of a driver node with a given path
*/
static int compare_driver_alt_path( const void* data, const void* user_data )
{
const driver_node_t* node = (const driver_node_t*)data;
const char* path = (const char*)user_data;
if (node == NULL || path == NULL)
{
return 1;
}

if (node->alt_path[0] == '\0')
{
return 1; // No alternative path set
}

return strcmp(node->alt_path, path);
}

/**
* @brief Check whether a driver node has an alternative path reachable from
* the given directory.
*
* Alternative paths are always placed at the root level (e.g. "/my_sensor"),
* so a driver is only reachable in this way when listing the root directory.
*/
static int compare_driver_alt_directory( const void* data, const void* user_data )
{
const driver_node_t* node = (const driver_node_t*)data;
const char* dir_path = (const char*)user_data;
if (node == NULL || dir_path == NULL)
{
return 1;
}

if (node->alt_path[0] == '\0')
{
return 1; // No alternative path set
}

// Alternative paths are always at the root level
return compare_paths_ignore_trailing_slash(dir_path, ROOT_DIRECTORY_NAME);
}

/**
* @brief Compare a driver node with a given driver node
*/
Expand Down Expand Up @@ -1587,11 +1684,24 @@ static driver_node_t* get_next_driver_node( dmfsi_context_t ctx, driver_node_t*
}

/**
* @brief Find a driver node by its path
* @brief Get the next driver node that has an alternative path inside the given directory
*/
static driver_node_t* get_next_alt_driver_node( dmfsi_context_t ctx, driver_node_t* current, const char* dir_path )
{
return dmlist_find_next(ctx->drivers, current, dir_path, compare_driver_alt_directory);
}

/**
* @brief Find a driver node by its path (primary or alternative)
*/
static driver_node_t* find_driver_node( dmfsi_context_t ctx, const char* path )
{
return dmlist_find(ctx->drivers, path, compare_driver_node_path);
driver_node_t* node = dmlist_find(ctx->drivers, path, compare_driver_node_path);
if (node == NULL)
{
node = dmlist_find(ctx->drivers, path, compare_driver_alt_path);
}
return node;
}

/**
Expand Down
Loading