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
11 changes: 7 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,15 @@ dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION}
)

# Link to other DMOD modules (will be downloaded on build time)
# dmdrvi 2.1: DMDRVI_IOCTL_MONITOR_* (node reporting, see src/dmdevfs.c).
# libsystemd: nodes are reported as "monitor"/"block" devices so device rules
# can start services (dmdevmon, automount) for them.
# dmdrvi 2.4: DMDRVI_IOCTL_MONITOR_* / _GFX_* / _INPUT_* (node reporting) and
# DMDRVI_IOCTL_DEVFS_GET_FRIEND (answered by dmdevfs itself), see
# src/dmdevfs.c.
# libsystemd: nodes are reported as "monitor"/"block"/"display"/"input"
# devices so device rules can start services (dmdevmon, automount, dmview)
# for them.
dmod_link_modules(${DMOD_MODULE_NAME}
dmfsi
dmdrvi@>=2.1
dmdrvi@>=2.4
dmini
dmlist
dmosi
Expand Down
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ DMOD Driver File System - A driver-based file system module for embedded systems
- **DMFSI Compatible**: Implements the standard DMOD file system interface
- **DMVFS Integration**: Can be mounted as a file system in DMVFS
- **Modular Design**: Built on DMOD framework for easy integration
- **libsystemd Device Reporting**: Reports monitored and block device nodes to libsystemd, so device rules can start services for them (see [libsystemd Device Reporting](#libsystemd-device-reporting))
- **libsystemd Device Reporting**: Reports monitored, block, display and input device nodes to libsystemd, so device rules can start services for them (see [libsystemd Device Reporting](#libsystemd-device-reporting))
- **Partition Nodes**: Exposes the MBR (incl. extended/logical) and GPT partitions of block devices as their own nodes (`<node>p<N>`), with offsets translated by DMDEVFS (see [Partition Nodes](#partition-nodes))
- **dmdevmon Service**: Generic monitor service that drives every node implementing the dmdrvi monitor contract - presence detection, hot-plug and polling without driver threads (see [services/dmdevmon](services/dmdevmon/README.md))

Expand Down Expand Up @@ -151,7 +151,7 @@ parameter2 = value2
- **`driver_order`**: An integer that controls when the driver is configured relative to other drivers. All drivers with `driver_order = 0` (the default when the field is omitted) are configured before any driver with `driver_order = 1`, which in turn is configured before `driver_order = 2`, and so on. Use this to force a driver to be configured after another one when there is no dmod dependency between them to infer the order from automatically. Negative values are allowed and are configured before the default group: `driver_order = -1` runs before `0`, and `-2` before `-1`. Use a negative value to put a driver ahead of everything left at the default, without having to raise `driver_order` in every other configuration file.
- **`friends_group`**: Groups configurations that form one logical peripheral. Once device paths are available, DMDEVFS reports every other member of the group through the optional driver's `dmdrvi_friend_changed()` callback.
- **`friend_role`**: Optional application-defined role of this member inside its friends group, such as `chip_select`. It lets consumers distinguish several devices of the same type without relying on section names or generated paths.
- **`report`**: What this device's nodes may be reported to libsystemd as: `all` (default), `monitor`, `block` or `none`. Hot-plugged nodes inherit it. See [libsystemd Device Reporting](#libsystemd-device-reporting).
- **`report`**: What this device's nodes may be reported to libsystemd as: `all` (default), `monitor`, `block`, `display`, `input` or `none`. Hot-plugged nodes inherit it. See [libsystemd Device Reporting](#libsystemd-device-reporting).

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.

Expand Down Expand Up @@ -195,6 +195,13 @@ owner's group and role and generate a `ready` notification. Removing such a
device with `dmdrvi_device_unavailable()` generates a `dead` notification.
Both `friends_group` and the callback are optional.

Modules that are not drivers - e.g. a display service looking for the touch
panel of its display - get the same information with
`DMDRVI_IOCTL_DEVFS_GET_FRIEND` on an open node: DMDEVFS answers it itself
(the driver never sees it) with the `index`-th other member of the node's
group, its absolute path and its `friend_role`, and `-ENOENT` when there is
none.

### libsystemd Device Reporting

Once a node's absolute path is known (after `dmdrvi_path_ready()`, both for
Expand All @@ -206,9 +213,11 @@ driver and asks what it is:
|----------------|-------------------|--------------|
| `DMDRVI_IOCTL_MONITOR_GET_POLICY` | `monitor` | `[class=monitor] start=dmdevmon@%name` - the node needs a monitor service (presence detection, hot-plug, polling); [dmdevmon](services/dmdevmon/README.md) ships with this repository |
| `DMDRVI_IOCTL_BLOCK_GET_INFO` | `block` | `[class=block] start=automount@%name` - a block device |
| `DMDRVI_IOCTL_GFX_GET_INFO` | `display` | `[class=display] start=dmview@%name` - a display (any driver implementing the standard graphics ioctls) |
| `DMDRVI_IOCTL_INPUT_GET_INFO` | `input` | `[class=input] start=...@%name` - an input device (touch panel, mouse, buttons) |

A node can be reported under both classes, or under none (then nothing
happens). Each report is `libsystemd_notify_device_added(class, name, path)`:
A node can be reported under several classes, or under none (then nothing
happens). The class names are defined in `dmdevfs.h` (`DMDEVFS_CLASS_*`). Each report is `libsystemd_notify_device_added(class, name, path)`:

- **name** - the node path relative to the mount with `/` replaced by `_`,
because unit names cannot contain `/`: `/dmsdio0` -> `dmsdio0`,
Expand Down
29 changes: 29 additions & 0 deletions include/dmdevfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,35 @@ extern "C" {
#define DMDEVFS_VERSION_MAJOR 0
#define DMDEVFS_VERSION_MINOR 2

/**
* @brief Device classes dmdevfs reports nodes under to libsystemd
*
* Once a node's absolute path is known, dmdevfs asks its driver what the
* node is and reports it under every class it answers to, so device rules
* (`[class=<name>]`) can start services for it:
*
* - "monitor": DMDRVI_IOCTL_MONITOR_GET_POLICY - a monitor service
* (dmdevmon) has to drive it;
* - "block": DMDRVI_IOCTL_BLOCK_GET_INFO - a block device, e.g. for
* automount;
* - "display": DMDRVI_IOCTL_GFX_GET_INFO - a display, e.g. for dmview;
* - "input": DMDRVI_IOCTL_INPUT_GET_INFO - an input device (touch panel,
* mouse, buttons).
*
* The device name is the node path relative to the mount with '/' replaced
* by '_' (unit names cannot contain '/'): "/dmsdio0/0" -> "dmsdio0_0". The
* user value is the node's absolute path. Removal is reported when the node
* goes away, which makes libsystemd stop the matching units. These are the
* values of the `report=` configuration key, too (plus "all" and "none").
*
* The members of a node's friends_group are available to every module
* through DMDRVI_IOCTL_DEVFS_GET_FRIEND on the node (dmdrvi_ioctl.h).
*/
#define DMDEVFS_CLASS_MONITOR "monitor"
#define DMDEVFS_CLASS_BLOCK "block"
#define DMDEVFS_CLASS_DISPLAY "display"
#define DMDEVFS_CLASS_INPUT "input"

#ifdef __cplusplus
}
#endif
Expand Down
181 changes: 155 additions & 26 deletions src/dmdevfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,40 +40,28 @@
*/
#define DMDEVFS_NULL_DEVICE_NAME "null"

/**
* @brief Device classes dmdevfs reports nodes under to libsystemd
*
* Once a node's absolute path is known, dmdevfs asks its driver what the node
* is and reports it to libsystemd, so device rules can start services for it
* (see report_node_added()):
*
* - "monitor": the driver answers DMDRVI_IOCTL_MONITOR_GET_POLICY - a
* monitor service (dmdevmon) has to drive it;
* - "block": the driver answers DMDRVI_IOCTL_BLOCK_GET_INFO - a block
* device, e.g. for automount.
*
* The device name is the node path relative to the mount with '/' replaced
* by '_' (unit names cannot contain '/'): "/dmsdio0/0" -> "dmsdio0_0". The
* user value is the node's absolute path. Removal is reported when the node
* goes away (dmdrvi_device_unavailable() or teardown), which makes libsystemd
* stop the matching units.
/*
* Device classes dmdevfs reports nodes under to libsystemd: DMDEVFS_CLASS_*
* in dmdevfs.h (see report_node_added()).
*/
#define DMDEVFS_CLASS_MONITOR "monitor"
#define DMDEVFS_CLASS_BLOCK "block"

/**
* @brief Bits of driver_node_t::report_mask / driver_node_t::reported
*/
#define DMDEVFS_REPORT_MONITOR 0x01u
#define DMDEVFS_REPORT_BLOCK 0x02u
#define DMDEVFS_REPORT_ALL (DMDEVFS_REPORT_MONITOR | DMDEVFS_REPORT_BLOCK)
#define DMDEVFS_REPORT_DISPLAY 0x04u
#define DMDEVFS_REPORT_INPUT 0x08u
#define DMDEVFS_REPORT_ALL (DMDEVFS_REPORT_MONITOR | DMDEVFS_REPORT_BLOCK | \
DMDEVFS_REPORT_DISPLAY | DMDEVFS_REPORT_INPUT)

/**
* @brief Configuration key limiting what a device's nodes are reported as
*
* "all" (default), "monitor", "block" or "none" - e.g. "none" for raw flash
* that must never be picked up by automount. Read from the same place as
* friends_group (see configure_driver()); hot-plugged nodes inherit it.
* "all" (default), "monitor", "block", "display", "input" or "none" - e.g.
* "none" for raw flash that must never be picked up by automount. Read from
* the same place as friends_group (see configure_driver()); hot-plugged nodes
* inherit it.
*/
#define DMDEVFS_REPORT_KEY "report"

Expand Down Expand Up @@ -334,6 +322,7 @@ static int compare_mount_context( const void* data, const void* user_data );
static int compare_mount_owns_context( const void* data, const void* user_data );
static int compare_mount_owns_dynamic_device( const void* data, const void* user_data );
static int build_absolute_path( const char* mount_path, const char* node_path, char* out, size_t out_size );
static int get_friend( dmfsi_context_t mount, driver_node_t* node, dmdrvi_devfs_friend_t* arg );
static void notify_driver_path_ready( const char* mount_path, driver_node_t* node );
static uint8_t read_report_mask( dmini_context_t config_ctx, const char* section_name, const char* driver_name );
static void report_node_added( const char* abs_path, driver_node_t* node );
Expand Down Expand Up @@ -837,6 +826,12 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmdevfs, int, _ioctl, (dmfsi_context_t ctx,

file_handle_t* handle = (file_handle_t*)fp;

// Device file system commands are answered here, never by the driver
if(request == DMDRVI_IOCTL_DEVFS_GET_FRIEND)
{
return get_friend(ctx, handle->driver, (dmdrvi_devfs_friend_t*)arg);
}

if(handle->driver->is_builtin)
{
// No driver module behind a built-in device to carry out an ioctl
Expand Down Expand Up @@ -2932,6 +2927,14 @@ static uint8_t read_report_mask( dmini_context_t config_ctx, const char* section
{
return DMDEVFS_REPORT_BLOCK;
}
if (strcmp(value, DMDEVFS_CLASS_DISPLAY) == 0)
{
return DMDEVFS_REPORT_DISPLAY;
}
if (strcmp(value, DMDEVFS_CLASS_INPUT) == 0)
{
return DMDEVFS_REPORT_INPUT;
}

DMOD_LOG_WARN("Unknown %s=%s for driver %s, reporting all classes\n", DMDEVFS_REPORT_KEY, value, driver_name);
return DMDEVFS_REPORT_ALL;
Expand All @@ -2958,9 +2961,10 @@ static void build_report_name( const char* node_path, char* out, size_t out_size
* @brief Ask a node's driver which classes the node belongs to
*
* Opens the node read-only through its driver, issues
* DMDRVI_IOCTL_MONITOR_GET_POLICY and/or DMDRVI_IOCTL_BLOCK_GET_INFO (only
* those allowed by @p wanted) and closes it again. A driver that cannot be
* opened, or answers neither, yields 0 - nothing is reported for it.
* DMDRVI_IOCTL_MONITOR_GET_POLICY, DMDRVI_IOCTL_BLOCK_GET_INFO,
* DMDRVI_IOCTL_GFX_GET_INFO and/or DMDRVI_IOCTL_INPUT_GET_INFO (only those
* allowed by @p wanted) and closes it again. A driver that cannot be opened,
* or answers none of them, yields 0 - nothing is reported for it.
*
* Must be called without g_devfs_mutex held (driver code).
*/
Expand Down Expand Up @@ -2993,6 +2997,18 @@ static uint8_t probe_node_classes( driver_node_t* node, uint8_t wanted )
{
classes |= DMDEVFS_REPORT_BLOCK;
}
dmdrvi_gfx_info_t gfx;
if ((wanted & DMDEVFS_REPORT_DISPLAY) != 0 &&
dmdrvi_ioctl(node->driver_context, handle, DMDRVI_IOCTL_GFX_GET_INFO, &gfx) == 0)
{
classes |= DMDEVFS_REPORT_DISPLAY;
}
dmdrvi_input_info_t input;
if ((wanted & DMDEVFS_REPORT_INPUT) != 0 &&
dmdrvi_ioctl(node->driver_context, handle, DMDRVI_IOCTL_INPUT_GET_INFO, &input) == 0)
{
classes |= DMDEVFS_REPORT_INPUT;
}

dmdrvi_close(node->driver_context, handle);
return classes;
Expand Down Expand Up @@ -3061,6 +3077,14 @@ static void report_node_added( const char* abs_path, driver_node_t* node )
{
report_node_class(DMDEVFS_CLASS_BLOCK, name, abs_path, true);
}
if ((classes & DMDEVFS_REPORT_DISPLAY) != 0)
{
report_node_class(DMDEVFS_CLASS_DISPLAY, name, abs_path, true);
}
if ((classes & DMDEVFS_REPORT_INPUT) != 0)
{
report_node_class(DMDEVFS_CLASS_INPUT, name, abs_path, true);
}
}

/**
Expand All @@ -3082,6 +3106,14 @@ static void report_node_removed( driver_node_t* node )

path_t name;
build_report_name(node->path, name, sizeof(name));
if ((classes & DMDEVFS_REPORT_INPUT) != 0)
{
report_node_class(DMDEVFS_CLASS_INPUT, name, NULL, false);
}
if ((classes & DMDEVFS_REPORT_DISPLAY) != 0)
{
report_node_class(DMDEVFS_CLASS_DISPLAY, name, NULL, false);
}
if ((classes & DMDEVFS_REPORT_BLOCK) != 0)
{
report_node_class(DMDEVFS_CLASS_BLOCK, name, NULL, false);
Expand All @@ -3092,6 +3124,103 @@ static void report_node_removed( driver_node_t* node )
}
}

// ============================================================================
// Friends of a node (DMDRVI_IOCTL_DEVFS_GET_FRIEND)
// ============================================================================

typedef struct
{
driver_node_t* self;
uint32_t index; // Members still to skip
driver_node_t* found;
} friend_lookup_t;

/**
* @brief dmlist_foreach() callback: the index-th other member of a friends group
*/
static bool find_friend( void* data, void* user_data )
{
driver_node_t* node = (driver_node_t*)data;
friend_lookup_t* lookup = (friend_lookup_t*)user_data;
if (node == lookup->self || node->removed || node->is_builtin || node->friends_group == NULL ||
strcmp(node->friends_group, lookup->self->friends_group) != 0)
{
return true;
}
if (lookup->index > 0)
{
lookup->index--;
return true;
}
lookup->found = node;
return false;
}

/**
* @brief Copy @p len bytes of @p src and a terminator into @p dst, if it fits
*/
static bool copy_out( char* dst, size_t dst_size, const char* src, size_t len )
{
if (dst == NULL || dst_size <= len)
{
return false;
}
memcpy(dst, src, len);
dst[len] = '\0';
return true;
}

/**
* @brief Answer DMDRVI_IOCTL_DEVFS_GET_FRIEND for @p node - never passed to its driver
*
* Writes straight into the caller's buffers - no length limit of its own.
* Everything is done under g_devfs_mutex; no driver code and no logging is
* called while it is held.
*
* @return 0, -ENOENT when there is no member arg->index (or no group),
* -ERANGE when a buffer is too small (lengths filled in), -EINVAL
* for a NULL arg
*/
static int get_friend( dmfsi_context_t mount, driver_node_t* node, dmdrvi_devfs_friend_t* arg )
{
if (arg == NULL)
{
return -EINVAL;
}
arg->path_length = 0;
arg->role_length = 0;

friend_lookup_t lookup = { .self = node, .index = arg->index, .found = NULL };
int ret = -ENOENT;
dmosi_mutex_lock(g_devfs_mutex);
if (mount->ready && node->friends_group != NULL)
{
dmlist_foreach(mount->drivers, find_friend, &lookup);
}
if (lookup.found != NULL)
{
/* Absolute path: the mount path (nothing for "/") followed by the node path */
const char* prefix = (strcmp(mount->mount_path, ROOT_DIRECTORY_NAME) == 0) ? "" : mount->mount_path;
const char* role = (lookup.found->friend_role != NULL) ? lookup.found->friend_role : "";
size_t prefix_len = strlen(prefix);
size_t node_len = strlen(lookup.found->path);
arg->path_length = prefix_len + node_len;
arg->role_length = strlen(role);

bool fits = arg->path != NULL && arg->path_size > arg->path_length &&
arg->role != NULL && arg->role_size > arg->role_length;
if (fits)
{
memcpy(arg->path, prefix, prefix_len);
(void)copy_out(arg->path + prefix_len, arg->path_size - prefix_len, lookup.found->path, node_len);
(void)copy_out(arg->role, arg->role_size, role, arg->role_length);
}
ret = fits ? 0 : -ERANGE;
}
dmosi_mutex_unlock(g_devfs_mutex);
return ret;
}

// ============================================================================
// Partitions
// ============================================================================
Expand Down
8 changes: 5 additions & 3 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ target_include_directories(${DMOD_MODULE_NAME} PRIVATE
target_compile_definitions(${DMOD_MODULE_NAME} PRIVATE
DMDEVFS_TEST_FIXTURES_DIR="fixtures"
)
# dmdrvi: DMDRVI_IOCTL_DEVFS_GET_FRIEND (2.4)
dmod_link_modules(${DMOD_MODULE_NAME}
dmdrvi@>=2.4
dmfsi
dmhaman
dmosi
Expand All @@ -122,7 +124,7 @@ target_compile_definitions(${DMOD_MODULE_NAME} PRIVATE
)
dmod_link_modules(${DMOD_MODULE_NAME}
dmfsi
dmdrvi@>=2.1
dmdrvi@>=2.3
dmhaman
dmosi
)
Expand All @@ -141,7 +143,7 @@ target_compile_definitions(${DMOD_MODULE_NAME} PRIVATE
)
dmod_link_modules(${DMOD_MODULE_NAME}
dmfsi
dmdrvi@>=2.1
dmdrvi@>=2.3
dmhaman
dmosi
libsystemd
Expand All @@ -161,7 +163,7 @@ target_compile_definitions(${DMOD_MODULE_NAME} PRIVATE
)
dmod_link_modules(${DMOD_MODULE_NAME}
dmfsi
dmdrvi@>=2.1
dmdrvi@>=2.3
dmhaman
dmosi
libsystemd
Expand Down
Loading
Loading