diff --git a/README.md b/README.md index 13b905d..7eabf0f 100644 --- a/README.md +++ b/README.md @@ -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 (`p`), 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)) @@ -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. @@ -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)`: diff --git a/docs/open-mode-validation.md b/docs/open-mode-validation.md index b04c10e..d646ba0 100644 --- a/docs/open-mode-validation.md +++ b/docs/open-mode-validation.md @@ -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. diff --git a/include/dmdevfs.h b/include/dmdevfs.h index 9965261..d9114bc 100644 --- a/include/dmdevfs.h +++ b/include/dmdevfs.h @@ -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 @@ -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 } diff --git a/src/dmdevfs.c b/src/dmdevfs.c index ca8e2a1..fbc30ae 100644 --- a/src/dmdevfs.c +++ b/src/dmdevfs.c @@ -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 @@ -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; @@ -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; } @@ -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); + } } /** @@ -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); diff --git a/tests/fixtures/config/mock.ini b/tests/fixtures/config/mock.ini index 9c0cea1..ffdeed2 100644 --- a/tests/fixtures/config/mock.ini +++ b/tests/fixtures/config/mock.ini @@ -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 diff --git a/tests/fixtures/rules/devices.ini b/tests/fixtures/rules/devices.ini index 935632c..735fde0 100644 --- a/tests/fixtures/rules/devices.ini +++ b/tests/fixtures/rules/devices.ini @@ -12,3 +12,6 @@ start=inp@%name [class=audio] start=aud@%name + +[class=rtc] +start=rtc@%name diff --git a/tests/fixtures/units/rtc@.ini b/tests/fixtures/units/rtc@.ini new file mode 100644 index 0000000..a1528f9 --- /dev/null +++ b/tests/fixtures/units/rtc@.ini @@ -0,0 +1,2 @@ +exec=dmdevfs_testsvc +args=%v diff --git a/tests/mockdrv/mockdrv.c b/tests/mockdrv/mockdrv.c index 55f6f0b..2272d12 100644 --- a/tests/mockdrv/mockdrv.c +++ b/tests/mockdrv/mockdrv.c @@ -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 */ @@ -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; @@ -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) { diff --git a/tests/test_dmdevfs.c b/tests/test_dmdevfs.c index c319805..d4127dd 100644 --- a/tests/test_dmdevfs.c +++ b/tests/test_dmdevfs.c @@ -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"); } } @@ -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) {