diff --git a/CMakeLists.txt b/CMakeLists.txt index 9dade19..bbf152a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,15 +82,16 @@ dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} ) # Link to other DMOD modules (will be downloaded on build time) -# dmdrvi 2.4: DMDRVI_IOCTL_MONITOR_* / _GFX_* / _INPUT_* (node reporting) and +# dmdrvi 2.6: DMDRVI_IOCTL_MONITOR_* / _GFX_* / _INPUT_* / _AUDIO_* (node +# reporting; _AUDIO_* since 2.6) and # DMDRVI_IOCTL_DEVFS_GET_FRIEND (answered by dmdevfs itself), see # src/dmdevfs.c. -# libsystemd: nodes are reported as "monitor"/"block"/"display"/"input" +# libsystemd: nodes are reported as "monitor"/"block"/"display"/"input"/"audio" # devices so device rules can start services (dmdevmon, automount, dmview) # for them. dmod_link_modules(${DMOD_MODULE_NAME} dmfsi - dmdrvi@>=2.4 + dmdrvi@>=2.6 dmini dmlist dmosi diff --git a/README.md b/README.md index db99196..13b905d 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 and input 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 and audio 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` 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` 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. @@ -215,6 +215,7 @@ driver and asks what it is: | `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) | +| `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) | 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/include/dmdevfs.h b/include/dmdevfs.h index cc044e3..9965261 100644 --- a/include/dmdevfs.h +++ b/include/dmdevfs.h @@ -28,7 +28,9 @@ extern "C" { * 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). + * mouse, buttons); + * - "audio": DMDRVI_IOCTL_AUDIO_GET_INFO - an audio codec or other audio + * device, e.g. for a player or mixer service. * * The device name is the node path relative to the mount with '/' replaced * by '_' (unit names cannot contain '/'): "/dmsdio0/0" -> "dmsdio0_0". The @@ -43,6 +45,7 @@ extern "C" { #define DMDEVFS_CLASS_BLOCK "block" #define DMDEVFS_CLASS_DISPLAY "display" #define DMDEVFS_CLASS_INPUT "input" +#define DMDEVFS_CLASS_AUDIO "audio" #ifdef __cplusplus } diff --git a/src/dmdevfs.c b/src/dmdevfs.c index a5df040..0b3e427 100644 --- a/src/dmdevfs.c +++ b/src/dmdevfs.c @@ -52,13 +52,15 @@ #define DMDEVFS_REPORT_BLOCK 0x02u #define DMDEVFS_REPORT_DISPLAY 0x04u #define DMDEVFS_REPORT_INPUT 0x08u +#define DMDEVFS_REPORT_AUDIO 0x10u #define DMDEVFS_REPORT_ALL (DMDEVFS_REPORT_MONITOR | DMDEVFS_REPORT_BLOCK | \ - DMDEVFS_REPORT_DISPLAY | DMDEVFS_REPORT_INPUT) + DMDEVFS_REPORT_DISPLAY | DMDEVFS_REPORT_INPUT | \ + DMDEVFS_REPORT_AUDIO) /** * @brief Configuration key limiting what a device's nodes are reported as * - * "all" (default), "monitor", "block", "display", "input" or "none" - e.g. + * "all" (default), "monitor", "block", "display", "input", "audio" 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. @@ -2935,6 +2937,10 @@ static uint8_t read_report_mask( dmini_context_t config_ctx, const char* section { return DMDEVFS_REPORT_INPUT; } + if (strcmp(value, DMDEVFS_CLASS_AUDIO) == 0) + { + return DMDEVFS_REPORT_AUDIO; + } DMOD_LOG_WARN("Unknown %s=%s for driver %s, reporting all classes\n", DMDEVFS_REPORT_KEY, value, driver_name); return DMDEVFS_REPORT_ALL; @@ -2962,7 +2968,8 @@ static void build_report_name( const char* node_path, char* out, size_t out_size * * Opens the node read-only through its driver, issues * DMDRVI_IOCTL_MONITOR_GET_POLICY, DMDRVI_IOCTL_BLOCK_GET_INFO, - * DMDRVI_IOCTL_GFX_GET_INFO and/or DMDRVI_IOCTL_INPUT_GET_INFO (only those + * DMDRVI_IOCTL_GFX_GET_INFO, DMDRVI_IOCTL_INPUT_GET_INFO and/or + * DMDRVI_IOCTL_AUDIO_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. * @@ -3009,6 +3016,12 @@ static uint8_t probe_node_classes( driver_node_t* node, uint8_t wanted ) { classes |= DMDEVFS_REPORT_INPUT; } + dmdrvi_audio_info_t audio; + if ((wanted & DMDEVFS_REPORT_AUDIO) != 0 && + dmdrvi_ioctl(node->driver_context, handle, DMDRVI_IOCTL_AUDIO_GET_INFO, &audio) == 0) + { + classes |= DMDEVFS_REPORT_AUDIO; + } dmdrvi_close(node->driver_context, handle); return classes; @@ -3085,6 +3098,10 @@ static void report_node_added( const char* abs_path, driver_node_t* node ) { report_node_class(DMDEVFS_CLASS_INPUT, name, abs_path, true); } + if ((classes & DMDEVFS_REPORT_AUDIO) != 0) + { + report_node_class(DMDEVFS_CLASS_AUDIO, name, abs_path, true); + } } /** @@ -3106,6 +3123,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_AUDIO) != 0) + { + report_node_class(DMDEVFS_CLASS_AUDIO, name, NULL, false); + } if ((classes & DMDEVFS_REPORT_INPUT) != 0) { report_node_class(DMDEVFS_CLASS_INPUT, name, NULL, false); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8d3c367..02fce28 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -96,9 +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) +# dmdrvi: DMDRVI_IOCTL_DEVFS_GET_FRIEND (2.4), DMDRVI_IOCTL_AUDIO_* (2.6) dmod_link_modules(${DMOD_MODULE_NAME} - dmdrvi@>=2.4 + dmdrvi@>=2.6 dmfsi dmhaman dmosi @@ -124,7 +124,7 @@ target_compile_definitions(${DMOD_MODULE_NAME} PRIVATE ) dmod_link_modules(${DMOD_MODULE_NAME} dmfsi - dmdrvi@>=2.3 + dmdrvi@>=2.6 dmhaman dmosi ) @@ -143,7 +143,7 @@ target_compile_definitions(${DMOD_MODULE_NAME} PRIVATE ) dmod_link_modules(${DMOD_MODULE_NAME} dmfsi - dmdrvi@>=2.3 + dmdrvi@>=2.6 dmhaman dmosi libsystemd @@ -163,7 +163,7 @@ target_compile_definitions(${DMOD_MODULE_NAME} PRIVATE ) dmod_link_modules(${DMOD_MODULE_NAME} dmfsi - dmdrvi@>=2.3 + dmdrvi@>=2.6 dmhaman dmosi libsystemd diff --git a/tests/fixtures/config/mock.ini b/tests/fixtures/config/mock.ini index 6ea2e00..9c0cea1 100644 --- a/tests/fixtures/config/mock.ini +++ b/tests/fixtures/config/mock.ini @@ -5,6 +5,8 @@ ; /dev/dmdevfs_mockdrv3 display -> reported as "display" ; /dev/dmdevfs_mockdrv4 input device -> reported as "input" ; /dev/dmdevfs_mockdrv5 display, report=input -> not reported +; /dev/dmdevfs_mockdrv6 audio codec -> reported as "audio" +; /dev/dmdevfs_mockdrv7 audio, report=block -> not reported ; mock3 and mock4 are one panel (friends_group=panel): a display and its ; touch input (friend_role=touch). ; Each can hot-plug a block child /dev/dmdevfs_mockdrv/0. @@ -41,3 +43,14 @@ driver_name=dmdevfs_mockdrv major=5 display=true report=input + +[mock6] +driver_name=dmdevfs_mockdrv +major=6 +audio=true + +[mock7] +driver_name=dmdevfs_mockdrv +major=7 +audio=true +report=block diff --git a/tests/fixtures/rules/devices.ini b/tests/fixtures/rules/devices.ini index 67220c9..935632c 100644 --- a/tests/fixtures/rules/devices.ini +++ b/tests/fixtures/rules/devices.ini @@ -9,3 +9,6 @@ start=dsp@%name [class=input] start=inp@%name + +[class=audio] +start=aud@%name diff --git a/tests/fixtures/units/aud@.ini b/tests/fixtures/units/aud@.ini new file mode 100644 index 0000000..c26dad3 --- /dev/null +++ b/tests/fixtures/units/aud@.ini @@ -0,0 +1,3 @@ +; Started for every node dmdevfs reports as "audio" (rules/devices.ini) +exec=dmdevfs_testsvc +args=%v diff --git a/tests/mockdrv/CMakeLists.txt b/tests/mockdrv/CMakeLists.txt index cecd790..f1d7dd8 100644 --- a/tests/mockdrv/CMakeLists.txt +++ b/tests/mockdrv/CMakeLists.txt @@ -18,7 +18,7 @@ target_include_directories(${DMOD_MODULE_NAME}_if INTERFACE ) dmod_link_modules(${DMOD_MODULE_NAME} - dmdrvi@>=2.3 + dmdrvi@>=2.6 dmini dmlist ) diff --git a/tests/mockdrv/mockdrv.c b/tests/mockdrv/mockdrv.c index 7dba130..b27a65d 100644 --- a/tests/mockdrv/mockdrv.c +++ b/tests/mockdrv/mockdrv.c @@ -11,9 +11,10 @@ * Test driver for the libsystemd node reporting of dmdevfs. * * Configuration keys: `major` (device number), `monitor` ("true": the host - * node implements the monitor contract), `display` / `input` ("true": the - * host node answers DMDRVI_IOCTL_GFX_GET_INFO / DMDRVI_IOCTL_INPUT_GET_INFO, - * as a display or an input device would). Its policy comes from + * node implements the monitor contract), `display` / `input` / `audio` + * ("true": the host node answers DMDRVI_IOCTL_GFX_GET_INFO / + * DMDRVI_IOCTL_INPUT_GET_INFO / DMDRVI_IOCTL_AUDIO_GET_INFO, as a display, + * an input device or an audio codec would). Its policy comes from * `event_handler`, `settle_ms` and `poll_interval_ms`; MONITOR_EVENT and * MONITOR_REFRESH calls are only counted (DMDEVFS_MOCKDRV_IOCTL_GET_STATS), * as are the opens of the host node and of its child. @@ -37,6 +38,7 @@ struct dmdrvi_context bool monitor; bool display; bool input; + bool audio; bool plugged; dmdrvi_monitor_policy_t policy; volatile uint32_t events; /* MONITOR_EVENT calls */ @@ -215,6 +217,7 @@ dmod_dmdrvi_dif_api_declaration(2.0, dmdevfs_mockdrv, dmdrvi_context_t, _create, ctx->monitor = (strcmp(monitor, "true") == 0); 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->plugged = false; ctx->events = 0; ctx->refreshes = 0; @@ -370,6 +373,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_AUDIO_GET_INFO: + if (!context->audio || arg == NULL) + { + return -ENOTTY; + } + memset(arg, 0, sizeof(dmdrvi_audio_info_t)); + ((dmdrvi_audio_info_t*)arg)->config.sample_rate_hz = 48000; + ((dmdrvi_audio_info_t*)arg)->config.channels = 2; + ((dmdrvi_audio_info_t*)arg)->config.sample_bits = 16; + return 0; case DMDEVFS_MOCKDRV_IOCTL_SET_MEDIA: if (context->plugged || arg == NULL) { diff --git a/tests/test_dmdevfs.c b/tests/test_dmdevfs.c index 8b994dd..a70a0a4 100644 --- a/tests/test_dmdevfs.c +++ b/tests/test_dmdevfs.c @@ -17,7 +17,8 @@ * dmdevfs_mockdrv (mockdrv/). libsystemd has the units from fixtures/units * and the rules from fixtures/rules loaded, so every report dmdevfs makes * starts a real process (dmdevfs_testsvc) as mon@ ("monitor"), - * blk@ ("block"), dsp@ ("display") or inp@ ("input"), and + * blk@ ("block"), dsp@ ("display"), inp@ ("input") or + * aud@ ("audio"), and * every removal stops it again - both observable * through libsystemd_status(). A unit that was never reported does not exist * at all (-ENOENT). @@ -77,12 +78,13 @@ void dmod_test_setup(void) if (g_mount != NULL) { g_fs.mounted(g_mount, "/dev"); - /* Every mount starts mon@dmdevfs_mockdrv0, dsp@dmdevfs_mockdrv3 and - * inp@dmdevfs_mockdrv4 - let them come up before any step (or the - * teardown) may stop them. */ + /* Every mount starts mon@dmdevfs_mockdrv0, dsp@dmdevfs_mockdrv3, + * inp@dmdevfs_mockdrv4 and aud@dmdevfs_mockdrv6 - let them come up + * before any step (or the teardown) may stop them. */ wait_ready("mon@dmdevfs_mockdrv0", "/dev/dmdevfs_mockdrv0"); wait_ready("dsp@dmdevfs_mockdrv3", "/dev/dmdevfs_mockdrv3"); wait_ready("inp@dmdevfs_mockdrv4", "/dev/dmdevfs_mockdrv4"); + wait_ready("aud@dmdevfs_mockdrv6", "/dev/dmdevfs_mockdrv6"); } } @@ -199,6 +201,21 @@ DMOD_TEST_STEP(dmdevfs_reports_display_and_input_nodes) DMOD_TEST_EXPECT_TRUE(wait_state("inp@dmdevfs_mockdrv4", DMOSI_PROCESS_STATE_TERMINATED)); } +DMOD_TEST_STEP(dmdevfs_reports_audio_nodes) +{ + DMOD_TEST_EXPECT_NOT_NULL(g_mount); + DMOD_TEST_EXPECT_TRUE(wait_ready("aud@dmdevfs_mockdrv6", "/dev/dmdevfs_mockdrv6")); + DMOD_TEST_EXPECT_EQ(unit_state("dsp@dmdevfs_mockdrv6"), -1); /* audio only */ + DMOD_TEST_EXPECT_EQ(unit_state("mon@dmdevfs_mockdrv6"), -1); + DMOD_TEST_EXPECT_EQ(unit_state("aud@dmdevfs_mockdrv7"), -1); /* report=block */ + DMOD_TEST_EXPECT_EQ(unit_state("aud@dmdevfs_mockdrv3"), -1); /* display, no audio */ + DMOD_TEST_EXPECT_EQ(unit_state("aud@dmdevfs_mockdrv2"), -1); /* plain node */ + + g_fs.deinit(g_mount); + g_mount = NULL; + DMOD_TEST_EXPECT_TRUE(wait_state("aud@dmdevfs_mockdrv6", 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) {