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
5 changes: 3 additions & 2 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, block, display, input and audio 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, input, audio and RTC 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`, `display`, `input`, `audio` 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`, `audio`, `rtc` 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 @@ -216,6 +216,7 @@ driver and asks what it is:
| `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) |
| `DMDRVI_IOCTL_AUDIO_GET_INFO` | `audio` | `[class=audio] start=...@%name` - an audio device (any driver implementing the standard audio ioctls, e.g. a codec such as dmwm8994) |
| `DMDRVI_IOCTL_RTC_GET_INFO` | `rtc` | `[class=rtc] start=dmutc@%name` - a clock, including an unset calendar; the library service receives the reported path as `%v` |

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)`:
Expand Down
10 changes: 10 additions & 0 deletions docs/open-mode-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,13 @@ Validation on 2026-10-16:
- The regression fails against the exact pre-fix dmdevfs source: driver free
and unmount complete prematurely (0/1 steps, exit 1).
- STM32F7 build passed.

## RTC device reporting

RTC nodes are probed with GET_INFO, not GET_TIME, so an unset calendar is still
reported to libsystemd. The new rtc report mask participates in the existing
all/none/single-class filtering and device removal notifies the matching rule.
Native ctest passes 10/10 targets, including 11/11 node-reporting steps covering
RTC startup, unset calendars, report filtering and service stop on unmount.
STM32F7 compilation passed; the integrated board image starts dmutc@rtc as a
library unit through the class=rtc rule.
5 changes: 4 additions & 1 deletion include/dmdevfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ extern "C" {
* - "input": DMDRVI_IOCTL_INPUT_GET_INFO - an input device (touch panel,
* mouse, buttons);
* - "audio": DMDRVI_IOCTL_AUDIO_GET_INFO - an audio codec or other audio
* device, e.g. for a player or mixer service.
* device, e.g. for a player or mixer service;
* - "rtc": DMDRVI_IOCTL_RTC_GET_INFO - an RTC, including unset calendars,
* e.g. for the dmutc library service.
*
* The device name is the node path relative to the mount with '/' replaced
* by '_' (unit names cannot contain '/'): "/dmsdio0/0" -> "dmsdio0_0". The
Expand All @@ -46,6 +48,7 @@ extern "C" {
#define DMDEVFS_CLASS_DISPLAY "display"
#define DMDEVFS_CLASS_INPUT "input"
#define DMDEVFS_CLASS_AUDIO "audio"
#define DMDEVFS_CLASS_RTC "rtc"

#ifdef __cplusplus
}
Expand Down
23 changes: 22 additions & 1 deletion src/dmdevfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@
#define DMDEVFS_REPORT_DISPLAY 0x04u
#define DMDEVFS_REPORT_INPUT 0x08u
#define DMDEVFS_REPORT_AUDIO 0x10u
#define DMDEVFS_REPORT_RTC 0x20u
#define DMDEVFS_REPORT_ALL (DMDEVFS_REPORT_MONITOR | DMDEVFS_REPORT_BLOCK | \
DMDEVFS_REPORT_DISPLAY | DMDEVFS_REPORT_INPUT | \
DMDEVFS_REPORT_AUDIO)
DMDEVFS_REPORT_AUDIO | DMDEVFS_REPORT_RTC)

/**
* @brief Configuration key limiting what a device's nodes are reported as
Expand Down Expand Up @@ -3042,6 +3043,10 @@ static uint8_t read_report_mask( dmini_context_t config_ctx, const char* section
{
return DMDEVFS_REPORT_AUDIO;
}
if (strcmp(value, DMDEVFS_CLASS_RTC) == 0)
{
return DMDEVFS_REPORT_RTC;
}

DMOD_LOG_WARN("Unknown %s=%s for driver %s, reporting all classes\n", DMDEVFS_REPORT_KEY, value, driver_name);
return DMDEVFS_REPORT_ALL;
Expand Down Expand Up @@ -3124,6 +3129,14 @@ static uint8_t probe_node_classes( driver_node_t* node, uint8_t wanted )
classes |= DMDEVFS_REPORT_AUDIO;
}

// GET_INFO works even on an unset clock: the UTC service must still bind.
dmdrvi_rtc_info_t rtc;
if ((wanted & DMDEVFS_REPORT_RTC) != 0 &&
dmdrvi_ioctl(node->driver_context, handle, DMDRVI_IOCTL_RTC_GET_INFO, &rtc) == 0)
{
classes |= DMDEVFS_REPORT_RTC;
}

dmdrvi_close(node->driver_context, handle);
return classes;
}
Expand Down Expand Up @@ -3203,6 +3216,10 @@ static void report_node_added( const char* abs_path, driver_node_t* node )
{
report_node_class(DMDEVFS_CLASS_AUDIO, name, abs_path, true);
}
if ((classes & DMDEVFS_REPORT_RTC) != 0)
{
report_node_class(DMDEVFS_CLASS_RTC, name, abs_path, true);
}
}

/**
Expand All @@ -3224,6 +3241,10 @@ static void report_node_removed( driver_node_t* node )

path_t name;
build_report_name(node->path, name, sizeof(name));
if ((classes & DMDEVFS_REPORT_RTC) != 0)
{
report_node_class(DMDEVFS_CLASS_RTC, name, NULL, false);
}
if ((classes & DMDEVFS_REPORT_AUDIO) != 0)
{
report_node_class(DMDEVFS_CLASS_AUDIO, name, NULL, false);
Expand Down
12 changes: 12 additions & 0 deletions tests/fixtures/config/mock.ini
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,15 @@ driver_name=dmdevfs_mockdrv
major=7
audio=true
report=block

[mock8]
driver_name=dmdevfs_mockdrv
major=8
rtc=true
report=rtc

[mock9]
driver_name=dmdevfs_mockdrv
major=9
rtc=true
report=audio
3 changes: 3 additions & 0 deletions tests/fixtures/rules/devices.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ start=inp@%name

[class=audio]
start=aud@%name

[class=rtc]
start=rtc@%name
2 changes: 2 additions & 0 deletions tests/fixtures/units/rtc@.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
exec=dmdevfs_testsvc
args=%v
12 changes: 12 additions & 0 deletions tests/mockdrv/mockdrv.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct dmdrvi_context
bool display;
bool input;
bool audio;
bool rtc;
bool plugged;
dmdrvi_monitor_policy_t policy;
volatile uint32_t events; /* MONITOR_EVENT calls */
Expand Down Expand Up @@ -222,6 +223,7 @@ dmod_dmdrvi_dif_api_declaration(2.0, dmdevfs_mockdrv, dmdrvi_context_t, _create,
ctx->display = (strcmp(dmini_get_string(config, NULL, "display", "false"), "true") == 0);
ctx->input = (strcmp(dmini_get_string(config, NULL, "input", "false"), "true") == 0);
ctx->audio = (strcmp(dmini_get_string(config, NULL, "audio", "false"), "true") == 0);
ctx->rtc = (strcmp(dmini_get_string(config, NULL, "rtc", "false"), "true") == 0);
ctx->plugged = false;
ctx->events = 0;
ctx->refreshes = 0;
Expand Down Expand Up @@ -393,6 +395,16 @@ static int host_ioctl(dmdrvi_context_t context, int command, void* arg)
((dmdrvi_input_info_t*)arg)->width = 480;
((dmdrvi_input_info_t*)arg)->height = 272;
return 0;
case DMDRVI_IOCTL_RTC_GET_INFO:
if (!context->rtc || arg == NULL) return -ENOTTY;
*(dmdrvi_rtc_info_t*)arg = (dmdrvi_rtc_info_t){1000000000u, 0, 2000, 2099, 0};
return 0;
case DMDRVI_IOCTL_RTC_GET_STATUS:
if (!context->rtc || arg == NULL) return -ENOTTY;
*(dmdrvi_rtc_status_t*)arg = (dmdrvi_rtc_status_t){DMDRVI_RTC_STATUS_RUNNING, 0};
return 0;
case DMDRVI_IOCTL_RTC_GET_TIME:
return context->rtc ? -ENODATA : -ENOTTY;
case DMDRVI_IOCTL_AUDIO_GET_INFO:
if (!context->audio || arg == NULL)
{
Expand Down
11 changes: 11 additions & 0 deletions tests/test_dmdevfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ void dmod_test_setup(void)
wait_ready("dsp@dmdevfs_mockdrv3", "/dev/dmdevfs_mockdrv3");
wait_ready("inp@dmdevfs_mockdrv4", "/dev/dmdevfs_mockdrv4");
wait_ready("aud@dmdevfs_mockdrv6", "/dev/dmdevfs_mockdrv6");
wait_ready("rtc@dmdevfs_mockdrv8", "/dev/dmdevfs_mockdrv8");
}
}

Expand Down Expand Up @@ -216,6 +217,16 @@ DMOD_TEST_STEP(dmdevfs_reports_audio_nodes)
DMOD_TEST_EXPECT_TRUE(wait_state("aud@dmdevfs_mockdrv6", DMOSI_PROCESS_STATE_TERMINATED));
}

DMOD_TEST_STEP(dmdevfs_reports_unset_rtc_and_stops_its_service)
{
DMOD_TEST_EXPECT_NOT_NULL(g_mount);
DMOD_TEST_EXPECT_TRUE(wait_ready("rtc@dmdevfs_mockdrv8", "/dev/dmdevfs_mockdrv8"));
DMOD_TEST_EXPECT_EQ(unit_state("rtc@dmdevfs_mockdrv9"), -1); /* filtered by report=audio */
DMOD_TEST_EXPECT_EQ(unit_state("rtc@dmdevfs_mockdrv2"), -1); /* not an RTC */
g_fs.deinit(g_mount); g_mount = NULL;
DMOD_TEST_EXPECT_TRUE(wait_state("rtc@dmdevfs_mockdrv8", DMOSI_PROCESS_STATE_TERMINATED));
}

/** DMDRVI_IOCTL_DEVFS_GET_FRIEND on @p node through dmdevfs. */
static int get_friend(const char* node, dmdrvi_devfs_friend_t* f)
{
Expand Down
Loading