diff --git a/README.md b/README.md index 7ed15b0..9a53558 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ cmake --build . ## Usage -The module can be loaded and mounted using DMVFS: +The module can be loaded and mounted using DMVFS. The module name is `dmdfs` and you must provide a path to a configuration directory containing `*.ini` files for drivers: ```c #include "dmvfs.h" @@ -80,20 +80,38 @@ The module can be loaded and mounted using DMVFS: // Initialize DMVFS dmvfs_init(16, 32); -// Mount the driver filesystem at /mnt -dmvfs_mount_fs("dmdevfs", "/mnt", NULL); +// Mount the driver filesystem at /dev +// The config parameter should point to a directory containing .ini files +dmvfs_mount_fs("dmdfs", "/dev", "/path/to/config"); -// Use standard file operations +// Access device files void* fp; -dmvfs_fopen(&fp, "/mnt/file.txt", DMFSI_O_RDONLY, 0, 0); -// ... use file ... +dmvfs_fopen(&fp, "/dev/dmclk", DMFSI_O_RDWR, 0, 0); +// ... use device ... dmvfs_fclose(fp); // Unmount when done -dmvfs_unmount_fs("/mnt"); +dmvfs_unmount_fs("/dev"); dmvfs_deinit(); ``` +### Configuration Files + +The configuration directory should contain `.ini` files for each driver. Each file can specify the driver name in the `[main]` section, or the driver name will be derived from the filename (without `.ini` extension). + +Example configuration file (`/path/to/config/dmclk.ini`): +```ini +; Clock driver configuration +[main] +driver_name=dmclk + +; Additional driver-specific configuration +[settings] +frequency=100000 +``` + +If `driver_name` is not specified, the filename will be used (e.g., `dmclk.ini` → driver name `dmclk`). + ## API The module implements the full DMFSI interface: diff --git a/src/dmdevfs.c b/src/dmdevfs.c index 02b5936..5fa0a94 100644 --- a/src/dmdevfs.c +++ b/src/dmdevfs.c @@ -16,6 +16,7 @@ #include "dmini.h" #include "dmdrvi.h" #include +#include /** * @brief Magic number for DMDEVFS context validation @@ -29,8 +30,20 @@ typedef struct dmdrvi_dev_num_t dev_num; // Device number assigned to the driver bool was_loaded; // Indicates if the driver was loaded by dmdevfs bool was_enabled; // Indicates if the driver was enabled by dmdevfs + char driver_name[DMOD_MAX_MODULE_NAME_LENGTH]; // Driver name for device file naming } driver_node_t; +/** + * @brief Directory handle for directory operations + */ +typedef struct +{ + size_t current_index; // Current position in the driver list + bool is_open; // Whether the directory is open + char path[256]; // Path being listed + driver_node_t* parent_driver; // Parent driver for subdirectory listing (NULL for root) +} dir_handle_t; + /** * @brief File system context structure */ @@ -53,6 +66,10 @@ static void read_base_name(const char* path, char* base_name, size_t name_size); static dmini_context_t read_driver_for_config(const char* config_path, char* driver_name, size_t name_size, const char* default_driver); static Dmod_Context_t* prepare_driver_module(const char* driver_name, bool* was_loaded, bool* was_enabled); static void cleanup_driver_module(const char* driver_name, bool was_loaded, bool was_enabled); +static void build_device_filename(const driver_node_t* driver_node, char* filename, size_t size); +static bool is_root_path(const char* path); +static driver_node_t* find_driver_by_subdir(dmfsi_context_t ctx, const char* path); +static bool path_is_device_subdir(dmfsi_context_t ctx, const char* path); // ============================================================================ // Module Interface Implementation @@ -95,20 +112,20 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, dmfsi_context_t, _init, (const cha { if(config == NULL) { - DMOD_LOG_ERROR("dmdevfs: Config path is NULL\n"); + DMOD_LOG_ERROR("Config path is NULL\n"); return NULL; } - if(strlen(config) == 0) + if(Dmod_StrLen(config) == 0) { - DMOD_LOG_ERROR("dmdevfs: Config path is empty\n"); + DMOD_LOG_ERROR("Config path is empty\n"); return NULL; } dmfsi_context_t ctx = Dmod_Malloc(sizeof(struct dmfsi_context)); if (ctx == NULL) { - DMOD_LOG_ERROR("dmdevfs: Failed to allocate memory for context\n"); + DMOD_LOG_ERROR("Failed to allocate memory for context\n"); return NULL; } @@ -119,7 +136,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, dmfsi_context_t, _init, (const cha int res = configure_drivers(ctx, NULL, ctx->config_path); if (res != DMFSI_OK) { - DMOD_LOG_ERROR("dmdevfs: Failed to configure drivers\n"); + DMOD_LOG_ERROR("Failed to configure drivers\n"); unconfigure_drivers(ctx); dmlist_destroy(ctx->drivers); Dmod_Free(ctx->config_path); @@ -145,7 +162,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _deinit, (dmfsi_context_t ctx { if (!dmfsi_dmdevfs_context_is_valid(ctx)) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in deinit\n"); + DMOD_LOG_ERROR("Invalid context in deinit\n"); return DMFSI_ERR_INVALID; } @@ -163,12 +180,12 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _fopen, (dmfsi_context_t ctx, { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in fopen\n"); + DMOD_LOG_ERROR("Invalid context in fopen\n"); return DMFSI_ERR_INVALID; } // TODO: Implement file opening through driver - DMOD_LOG_ERROR("dmdevfs: fopen not yet implemented\n"); + DMOD_LOG_ERROR("fopen not yet implemented\n"); return DMFSI_ERR_GENERAL; } @@ -179,12 +196,12 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _fclose, (dmfsi_context_t ctx { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in fclose\n"); + DMOD_LOG_ERROR("Invalid context in fclose\n"); return DMFSI_ERR_INVALID; } // TODO: Implement file closing - DMOD_LOG_ERROR("dmdevfs: fclose not yet implemented\n"); + DMOD_LOG_ERROR("fclose not yet implemented\n"); return DMFSI_ERR_GENERAL; } @@ -195,7 +212,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _fread, (dmfsi_context_t ctx, { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in fread\n"); + DMOD_LOG_ERROR("Invalid context in fread\n"); return DMFSI_ERR_INVALID; } @@ -211,7 +228,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _fwrite, (dmfsi_context_t ctx { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in fwrite\n"); + DMOD_LOG_ERROR("Invalid context in fwrite\n"); return DMFSI_ERR_INVALID; } @@ -227,7 +244,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _lseek, (dmfsi_context_t ctx, { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in lseek\n"); + DMOD_LOG_ERROR("Invalid context in lseek\n"); return DMFSI_ERR_INVALID; } @@ -242,7 +259,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, long, _tell, (dmfsi_context_t ctx, { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in tell\n"); + DMOD_LOG_ERROR("Invalid context in tell\n"); return -1; } @@ -257,7 +274,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _eof, (dmfsi_context_t ctx, v { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in eof\n"); + DMOD_LOG_ERROR("Invalid context in eof\n"); return 1; } @@ -272,7 +289,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, long, _size, (dmfsi_context_t ctx, { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in size\n"); + DMOD_LOG_ERROR("Invalid context in size\n"); return -1; } @@ -287,7 +304,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _getc, (dmfsi_context_t ctx, { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in getc\n"); + DMOD_LOG_ERROR("Invalid context in getc\n"); return -1; } @@ -302,7 +319,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _putc, (dmfsi_context_t ctx, { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in putc\n"); + DMOD_LOG_ERROR("Invalid context in putc\n"); return -1; } @@ -317,7 +334,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _fflush, (dmfsi_context_t ctx { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in fflush\n"); + DMOD_LOG_ERROR("Invalid context in fflush\n"); return DMFSI_ERR_INVALID; } @@ -332,7 +349,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _sync, (dmfsi_context_t ctx, { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in sync\n"); + DMOD_LOG_ERROR("Invalid context in sync\n"); return DMFSI_ERR_INVALID; } @@ -347,12 +364,50 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _opendir, (dmfsi_context_t ct { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in opendir\n"); + DMOD_LOG_ERROR("Invalid context in opendir\n"); return DMFSI_ERR_INVALID; } - // TODO: Implement directory opening - return DMFSI_ERR_GENERAL; + dir_handle_t* dir = Dmod_Malloc(sizeof(dir_handle_t)); + if (dir == NULL) + { + DMOD_LOG_ERROR("Failed to allocate directory handle\n"); + return DMFSI_ERR_GENERAL; + } + + dir->current_index = 0; + dir->is_open = true; + dir->parent_driver = NULL; + + // Store the path + if (path != NULL) + { + Dmod_StrNCpy(dir->path, path, sizeof(dir->path) - 1); + dir->path[sizeof(dir->path) - 1] = '\0'; + } + else + { + dir->path[0] = '\0'; + } + + // Check if opening root directory + if (is_root_path(path)) + { + *dp = dir; + return DMFSI_OK; + } + + // Check if opening a device subdirectory (e.g., /uart0/) + driver_node_t* parent = find_driver_by_subdir(ctx, path); + if (parent != NULL) + { + dir->parent_driver = parent; + *dp = dir; + return DMFSI_OK; + } + + Dmod_Free(dir); + return DMFSI_ERR_NOT_FOUND; } /** @@ -362,12 +417,84 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _readdir, (dmfsi_context_t ct { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in readdir\n"); + DMOD_LOG_ERROR("Invalid context in readdir\n"); return DMFSI_ERR_INVALID; } - // TODO: Implement directory reading - return DMFSI_ERR_NOT_FOUND; + if (dp == NULL || entry == NULL) + { + return DMFSI_ERR_INVALID; + } + + dir_handle_t* dir = (dir_handle_t*)dp; + if (!dir->is_open) + { + return DMFSI_ERR_INVALID; + } + + // If listing a subdirectory (parent_driver is set), return minor device entries + if (dir->parent_driver != NULL) + { + // For subdirectories, we only have one entry: the minor number + if (dir->current_index > 0) + { + return DMFSI_ERR_NOT_FOUND; // Only one entry + } + + // Return the minor number as filename + Dmod_SnPrintf(entry->name, sizeof(entry->name), "%d", dir->parent_driver->dev_num.minor); + entry->is_directory = false; + entry->size = 0; + dir->current_index++; + return DMFSI_OK; + } + + // Listing root directory - iterate through drivers + size_t driver_count = dmlist_size(ctx->drivers); + + while (dir->current_index < driver_count) + { + driver_node_t* driver_node = (driver_node_t*)dmlist_get(ctx->drivers, dir->current_index); + dir->current_index++; + + if (driver_node == NULL) + { + continue; + } + + // Build device name + if (driver_node->dev_num.flags == DMDRVI_NUM_NONE) + { + // Simple device file + Dmod_SnPrintf(entry->name, sizeof(entry->name), "%s", driver_node->driver_name); + entry->is_directory = false; + } + else if (driver_node->dev_num.flags & DMDRVI_NUM_MINOR) + { + // Directory with major number (contains minor device files) + Dmod_SnPrintf(entry->name, sizeof(entry->name), "%s%d", + driver_node->driver_name, driver_node->dev_num.major); + entry->is_directory = true; + } + else if (driver_node->dev_num.flags & DMDRVI_NUM_MAJOR) + { + // Device file with major number only + Dmod_SnPrintf(entry->name, sizeof(entry->name), "%s%d", + driver_node->driver_name, driver_node->dev_num.major); + entry->is_directory = false; + } + else + { + // Fallback + Dmod_SnPrintf(entry->name, sizeof(entry->name), "%s", driver_node->driver_name); + entry->is_directory = false; + } + + entry->size = 0; + return DMFSI_OK; + } + + return DMFSI_ERR_NOT_FOUND; // No more entries } /** @@ -377,11 +504,18 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _closedir, (dmfsi_context_t c { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in closedir\n"); + DMOD_LOG_ERROR("Invalid context in closedir\n"); return DMFSI_ERR_INVALID; } - // TODO: Implement directory closing + if (dp != NULL) + { + dir_handle_t* dir = (dir_handle_t*)dp; + dir->is_open = false; + Dmod_Free(dp); + return DMFSI_OK; + } + return DMFSI_ERR_GENERAL; } @@ -400,12 +534,18 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _direxists, (dmfsi_context_t { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in direxists\n"); + DMOD_LOG_ERROR("Invalid context in direxists\n"); return 0; } - // TODO: Implement directory existence check - return 0; + // Check if root directory + if (is_root_path(path)) + { + return 1; + } + + // Check if it's a device subdirectory + return path_is_device_subdir(ctx, path) ? 1 : 0; } /** @@ -415,7 +555,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _stat, (dmfsi_context_t ctx, { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in stat\n"); + DMOD_LOG_ERROR("Invalid context in stat\n"); return DMFSI_ERR_INVALID; } @@ -430,7 +570,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _unlink, (dmfsi_context_t ctx { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in unlink\n"); + DMOD_LOG_ERROR("Invalid context in unlink\n"); return DMFSI_ERR_INVALID; } @@ -445,7 +585,7 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _rename, (dmfsi_context_t ctx { if(dmfsi_dmdevfs_context_is_valid(ctx) == 0) { - DMOD_LOG_ERROR("dmdevfs: Invalid context in rename\n"); + DMOD_LOG_ERROR("Invalid context in rename\n"); return DMFSI_ERR_INVALID; } @@ -466,7 +606,7 @@ static int configure_drivers(dmfsi_context_t ctx, const char* driver_name, const void* dir = Dmod_OpenDir(config_path); if (dir == NULL) { - DMOD_LOG_ERROR("dmdevfs: Failed to open config directory: %s\n", ctx->config_path); + DMOD_LOG_ERROR("Failed to open config directory: %s\n", ctx->config_path); return DMFSI_ERR_NOT_FOUND; } @@ -479,7 +619,7 @@ static int configure_drivers(dmfsi_context_t ctx, const char* driver_name, const dmini_context_t config_ctx = read_driver_for_config(entry, module_name, sizeof(module_name), driver_name); if (config_ctx == NULL) { - DMOD_LOG_ERROR("dmdevfs: Failed to read driver for config: %s\n", entry); + DMOD_LOG_ERROR("Failed to read driver for config: %s\n", entry); continue; } @@ -487,12 +627,12 @@ static int configure_drivers(dmfsi_context_t ctx, const char* driver_name, const dmini_destroy(config_ctx); if (driver_node == NULL) { - DMOD_LOG_ERROR("dmdevfs: Failed to configure driver: %s\n", module_name); + DMOD_LOG_ERROR("Failed to configure driver: %s\n", module_name); continue; } if(dmlist_push_back(ctx->drivers, driver_node) != 0) { - DMOD_LOG_ERROR("dmdevfs: Failed to add driver to list: %s\n", module_name); + DMOD_LOG_ERROR("Failed to add driver to list: %s\n", module_name); Dmod_Free(driver_node); continue; } @@ -505,7 +645,7 @@ static int configure_drivers(dmfsi_context_t ctx, const char* driver_name, const int res = configure_drivers(ctx, driver_name, entry); if (res != DMFSI_OK) { - DMOD_LOG_ERROR("dmdevfs: Failed to configure drivers in directory: %s\n", entry); + DMOD_LOG_ERROR("Failed to configure drivers in directory: %s\n", entry); } } } @@ -545,6 +685,9 @@ static driver_node_t* configure_driver(const char* driver_name, dmini_context_t driver_node->was_loaded = was_loaded; driver_node->was_enabled = was_enabled; driver_node->driver = driver; + Dmod_StrNCpy(driver_node->driver_name, driver_name, DMOD_MAX_MODULE_NAME_LENGTH - 1); + driver_node->driver_name[DMOD_MAX_MODULE_NAME_LENGTH - 1] = '\0'; + driver_node->driver_context = dmdrvi_create(config_ctx, &driver_node->dev_num); if (driver_node->driver_context == NULL) { @@ -554,6 +697,11 @@ static driver_node_t* configure_driver(const char* driver_name, dmini_context_t return NULL; } + // Log device file creation + char device_name[256]; + build_device_filename(driver_node, device_name, sizeof(device_name)); + DMOD_LOG_INFO("Device created: %s\n", device_name); + return driver_node; } @@ -603,7 +751,7 @@ static void read_base_name(const char* path, char* base_name, size_t name_size) { const char* last_slash = strrchr(path, '/'); const char* name_start = (last_slash != NULL) ? last_slash + 1 : path; - strncpy(base_name, name_start, name_size); + Dmod_StrNCpy(base_name, name_start, name_size); base_name[name_size - 1] = '\0'; } @@ -615,14 +763,14 @@ static dmini_context_t read_driver_for_config(const char* config_path, char* dri dmini_context_t ctx = dmini_create(); if (ctx == NULL) { - DMOD_LOG_ERROR("dmdevfs: Failed to create INI context\n"); + DMOD_LOG_ERROR("Failed to create INI context\n"); return NULL; } int res = dmini_parse_file(ctx, config_path); if (res != DMINI_OK) { - DMOD_LOG_ERROR("dmdevfs: Failed to parse INI file: %s\n", config_path); + DMOD_LOG_ERROR("Failed to parse INI file: %s\n", config_path); dmini_destroy(ctx); return NULL; } @@ -630,7 +778,7 @@ static dmini_context_t read_driver_for_config(const char* config_path, char* dri const char* name = dmini_get_string(ctx, "main", "driver_name", default_driver); if(name != NULL) { - strncpy(driver_name, name, name_size); + Dmod_StrNCpy(driver_name, name, name_size); driver_name[name_size - 1] = '\0'; return ctx; } @@ -639,7 +787,7 @@ static dmini_context_t read_driver_for_config(const char* config_path, char* dri // cut the `.ini` extension if present char* ext = strrchr(driver_name, '.'); - if (ext != NULL && strcmp(ext, ".ini") == 0) + if (ext != NULL && Dmod_StrCmp(ext, ".ini") == 0) { *ext = '\0'; } @@ -684,4 +832,138 @@ static void cleanup_driver_module(const char* driver_name, bool was_loaded, bool { Dmod_UnloadModule(driver_name, false); } +} + +/** + * @brief Build device filename based on dev_num flags + * + * Note: For DMDRVI_NUM_MINOR, this returns a path with forward slash (e.g., "dmspi0/0"). + * This matches the dmdrvi interface specification. Full directory hierarchy support + * would require implementing nested directory operations, which is left for future + * enhancement when file I/O operations are implemented. + * + * @param driver_node Pointer to driver node (must not be NULL) + * @param filename Output buffer for filename (must not be NULL) + * @param size Size of output buffer (must be > 0) + */ +static void build_device_filename(const driver_node_t* driver_node, char* filename, size_t size) +{ + // Validate input parameters + if (driver_node == NULL || filename == NULL || size == 0) + { + return; + } + + // Build device name - start with driver name + Dmod_SnPrintf(filename, size, "%s", driver_node->driver_name); + + // Append major number if present + if (driver_node->dev_num.flags & DMDRVI_NUM_MAJOR) + { + size_t len = Dmod_StrLen(filename); + Dmod_SnPrintf(filename + len, size - len, "%d", driver_node->dev_num.major); + } + + // Append minor number if present (with directory separator) + if (driver_node->dev_num.flags & DMDRVI_NUM_MINOR) + { + size_t len = Dmod_StrLen(filename); + Dmod_SnPrintf(filename + len, size - len, "/%d", driver_node->dev_num.minor); + } +} + +/** + * @brief Check if path represents the root directory + * + * @param path Path to check (can be NULL) + * @return true if path is NULL, empty, or "/" + */ +static bool is_root_path(const char* path) +{ + if (path == NULL) + { + return true; + } + + if (path[0] == '\0' || Dmod_StrCmp(path, "/") == 0) + { + return true; + } + + return false; +} + +/** + * @brief Find a driver that has a subdirectory matching the given path + * + * @param ctx Filesystem context + * @param path Path to search for (e.g., "/uart0" or "uart0") + * @return Pointer to driver node if found, NULL otherwise + */ +static driver_node_t* find_driver_by_subdir(dmfsi_context_t ctx, const char* path) +{ + if (ctx == NULL || path == NULL) + { + return NULL; + } + + // Skip leading slash if present + const char* search_path = path; + if (path[0] == '/') + { + search_path = path + 1; + } + + size_t driver_count = dmlist_size(ctx->drivers); + for (size_t i = 0; i < driver_count; i++) + { + driver_node_t* driver_node = (driver_node_t*)dmlist_get(ctx->drivers, i); + if (driver_node == NULL) + { + continue; + } + + // Only drivers with MINOR flag create subdirectories + if (!(driver_node->dev_num.flags & DMDRVI_NUM_MINOR)) + { + continue; + } + + // Build expected directory name: drivername + major number + char dir_name[256]; + Dmod_SnPrintf(dir_name, sizeof(dir_name), "%s%d", + driver_node->driver_name, driver_node->dev_num.major); + + // Check if path matches (with or without trailing slash) + size_t dir_len = Dmod_StrLen(dir_name); + size_t path_len = Dmod_StrLen(search_path); + + if (path_len == dir_len && Dmod_StrCmp(search_path, dir_name) == 0) + { + return driver_node; + } + + // Also check with trailing slash + if (path_len == dir_len + 1 && + Dmod_StrNCmp(search_path, dir_name, dir_len) == 0 && + search_path[dir_len] == '/') + { + return driver_node; + } + } + + return NULL; +} + +/** + * @brief Check if a path corresponds to a device subdirectory + * + * @param ctx Filesystem context + * @param path Path to check + * @return true if path is a device subdirectory, false otherwise + */ +static bool path_is_device_subdir(dmfsi_context_t ctx, const char* path) +{ + return find_driver_by_subdir(ctx, path) != NULL; +} } \ No newline at end of file