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
23 changes: 23 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ jobs:
test -d "$PKG_DIR"
(cd "$PKG_DIR" && zip -r "${OLDPWD}/dmdevmon-${{ github.event.release.tag_name || github.ref_name }}-$ARCH_DIR_NAME.zip" .)
ls -lh dmdevmon-*.zip

- name: Create devscan archive
run: |
set -e
# devscan (services/devscan) is packaged by the build itself from
# its devscan.dmr: module, docs and the libsystemd units and rule.
PKG_DIR=build_$ARCH_DIR_NAME/packages/devscan
test -d "$PKG_DIR"
(cd "$PKG_DIR" && zip -r "${OLDPWD}/devscan-${{ github.event.release.tag_name || github.ref_name }}-$ARCH_DIR_NAME.zip" .)
ls -lh devscan-*.zip

- name: Upload artifact
uses: actions/upload-artifact@v4
Expand All @@ -137,6 +147,7 @@ jobs:
path: |
dmdevfs-${{ github.event.release.tag_name || github.ref_name }}-*.zip
dmdevmon-${{ github.event.release.tag_name || github.ref_name }}-*.zip
devscan-${{ github.event.release.tag_name || github.ref_name }}-*.zip
retention-days: 1

generate-versions-manifest:
Expand Down Expand Up @@ -189,6 +200,18 @@ jobs:
DMDEVMON_VERSIONS="$DMDEVMON_VERSIONS $CURRENT"
fi
echo "\$version-available dmdevmon $(echo $DMDEVMON_VERSIONS | xargs)" >> versions.dmm

# devscan only exists from the release that added services/devscan
DEVSCAN_VERSIONS=""
for VERSION in $VERSIONS; do
if git cat-file -e "v$VERSION:services/devscan/CMakeLists.txt" 2>/dev/null; then
DEVSCAN_VERSIONS="$DEVSCAN_VERSIONS $VERSION"
fi
done
if ! echo " $DEVSCAN_VERSIONS " | grep -q " $CURRENT "; then
DEVSCAN_VERSIONS="$DEVSCAN_VERSIONS $CURRENT"
fi
echo "\$version-available devscan $(echo $DEVSCAN_VERSIONS | xargs)" >> versions.dmm

echo "Generated versions.dmm:"
cat versions.dmm
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ target_include_directories(${DMOD_MODULE_NAME} PRIVATE
# dmdevmon - generic monitor service for nodes implementing the dmdrvi
# monitor contract (reported to libsystemd as class "monitor", see above).
add_subdirectory(services/dmdevmon)
# devscan - asks dmdevfs (ioctl DMDEVFS_IOCTL_ADD_CONFIG_DIR) to configure the
# cfg/drivers of every mount point, started by a libsystemd rule on mount and unmount.
add_subdirectory(services/devscan)

# ======================================================================
# Tests
Expand Down
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,42 @@ DMDEVFS supports both flat and hierarchical configuration layouts:

The hierarchical structure is useful for organizing drivers by type or bus.

### Configuration Directories of Mount Points

Every mounted file system may bring driver configurations of its own, in
`<mount point>/cfg/drivers` - the same layout as the directory the mount is
configured from (`/cfg/drivers` on dmod-boot). The root of a dmdevfs mount
(`/dev` itself, not a node in it) opens a handle that answers two ioctls
(`include/dmdevfs.h`), both with the mount point as argument:

- `DMDEVFS_IOCTL_ADD_CONFIG_DIR` configures them in that dmdevfs mount, exactly
as if they were in its own directory: ordered by `driver_order` and their
dependencies, reported to libsystemd, their partitions exposed.
- `DMDEVFS_IOCTL_REMOVE_CONFIG_DIR` removes them again - every node, its
partitions and hot-plugged devices first, is reported removed (the services
started for it stop) and the driver is freed (`dmdrvi_free()`, its module
released) once the last handle open on any of its nodes is closed.

The directory of the mount's own configuration is never configured twice. Both
only queue the work for the hot-plug thread, which handles it in order with
hot-plug events, and return at once.

The [devscan](services/devscan/README.md) service makes the calls: libsystemd
reports the mounts (dmfsi's `_mount_changed` dif, which dmvfs calls) as devices
of class `mount`, a rule starts `devscan add /dev <mount point>` for a mount and
`devscan remove /dev <mount point>` before an unmount, and both end when they
are done. So a mounted card or `/ext` just brings its drivers:

```
<mount point>/cfg/drivers/ driver configurations -> dmdevfs
```

- **mounted** - the drivers are configured by the hot-plug thread. A mount
point without `cfg/drivers` is left alone.
- **about to be unmounted** - libsystemd waits for `devscan remove`, which
queues the removal of the drivers, so their nodes are reported gone (and
their services stopped) while the file system is still there.

### Example Configuration Files

#### Example 1: SPI Flash Driver
Expand Down
28 changes: 28 additions & 0 deletions include/dmdevfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,34 @@ extern "C" {
#define DMDEVFS_CLASS_AUDIO "audio"
#define DMDEVFS_CLASS_RTC "rtc"

/**
* @brief Commands of the mount itself: ioctl on its root
*
* Opening the root of a dmdevfs mount (`/dev` itself, not a node in it) gives a
* handle without a device behind it - reads report EOF, writes are discarded -
* that answers these commands. The command works on the mount that was opened,
* and only on it.
*
* - DMDEVFS_IOCTL_ADD_CONFIG_DIR: every mounted file system may bring driver
* configurations of its own, in `<mount point>/cfg/drivers` - the same
* layout as the directory the dmdevfs mount is configured from. The argument
* is the mount point (a NUL-terminated string, e.g. "/mnt/SD"); its drivers
* are configured in this mount, exactly as if they were in its own directory.
* The directory the mount is configured from is never loaded twice.
* - DMDEVFS_IOCTL_REMOVE_CONFIG_DIR: removes them again - their nodes (with
* their partitions and hot-plugged devices) are reported removed, so the
* services started for them stop, and every driver is freed once the last
* handle open on its nodes is closed.
*
* Both only queue the work for dmdevfs's hot-plug thread, which also handles
* hot-plug events, and return 0 at once (or a negative errno: -EINVAL for a
* missing or empty argument, -ENODEV when dmdevfs is not initialized, -ENOMEM).
* The devscan service (services/devscan) is what calls them, started by a
* libsystemd rule for every mount and unmount.
*/
#define DMDEVFS_IOCTL_ADD_CONFIG_DIR 0xF10
#define DMDEVFS_IOCTL_REMOVE_CONFIG_DIR 0xF11

#ifdef __cplusplus
}
#endif
Expand Down
4 changes: 4 additions & 0 deletions manifest.dmm
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ dmdevfs https://github.com/choco-technologies/dmdfs/releases/download/v<version>
# dmdevmon - monitor service Application (services/dmdevmon), built and
# released together with dmdevfs
dmdevmon https://github.com/choco-technologies/dmdfs/releases/download/v<version>/dmdevmon-v<version>-<arch_name>.zip

# devscan - driver configuration directories of mount points (services/devscan),
# built and released together with dmdevfs
devscan https://github.com/choco-technologies/dmdfs/releases/download/v<version>/devscan-v<version>-<arch_name>.zip
26 changes: 26 additions & 0 deletions services/devscan/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# =====================================================================
# devscan - the driver configuration directory of a mount point
# =====================================================================
# A one-shot service: started by a libsystemd rule (configs/devscan.rules) when a file system is mounted, it asks a
# dmdevfs mount (ioctl DMDEVFS_IOCTL_ADD_CONFIG_DIR) to configure <mount point>/cfg/drivers and ends; started again
# by the rule's "remove" key before the file system is unmounted, it asks for them to be dropped. See README.md.
set(DMOD_MODULE_NAME devscan)
set(DMOD_AUTHOR_NAME "Patryk Kubiak")
# Its own stack: it only opens a file and calls an ioctl, but the thread that mounts (automount's) is far too small
# to do even that inline
set(DMOD_STACK_SIZE 2048)
set(DMOD_PRIORITY 0)
set(DMOD_DMR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/devscan.dmr)

dmod_add_executable(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION}
src/main.c
)

# dmdevfs.h - the ioctl commands; devscan does not link dmdevfs, it talks to whichever one is mounted
target_include_directories(${DMOD_MODULE_NAME} PRIVATE
${CMAKE_SOURCE_DIR}/include
)

dmod_link_modules(${DMOD_MODULE_NAME}
dmosi
)
49 changes: 49 additions & 0 deletions services/devscan/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# devscan

A one-shot service that tells a dmdevfs mount a file system came or went. A
mounted file system - an SD card, the external flash - may bring driver
configurations of its own, in `<mount point>/cfg/drivers` (the layout of the
directory dmdevfs is configured from):

```
devscan add <dmdevfs path> <mount point> # configure <mount point>/cfg/drivers
devscan remove <dmdevfs path> <mount point> # drop them again
```

It opens the root of the dmdevfs mount (`<dmdevfs path>`, `/dev` on dmod-boot)
and does an ioctl on it - `DMDEVFS_IOCTL_ADD_CONFIG_DIR` /
`DMDEVFS_IOCTL_REMOVE_CONFIG_DIR` with the mount point as argument, see
[`include/dmdevfs.h`](../../include/dmdevfs.h) - then ends. dmdevfs queues the
work for its hot-plug thread. A mount point without `cfg/drivers` is left alone.

## How it is started

Nothing here follows mounts. libsystemd reports every mount as a device of class
`mount` (it implements dmfsi's `_mount_changed` dif, which dmvfs calls), named
after the mount point (`/mnt/SD` -> `mnt_SD`) with the mount point as its value,
and [`configs/devscan.rules`](configs/devscan.rules) says what to do:

```ini
[class=mount]
start=devscan@%name # mounted: configure its cfg/drivers
remove=devscan-remove@%name # about to be unmounted: drop them
```

[`devscan@.ini`](configs/devscan@.ini) and
[`devscan-remove@.ini`](configs/devscan-remove@.ini) are `type=oneshot` units
running `devscan add /dev %v` / `devscan remove /dev %v`. libsystemd waits for
the `remove` unit before the unmount goes on, so the nodes of the file system
are gone - and the services started for them stopped - while it is still there.
Both processes have the stack of this module (2 kB) - the thread that mounts
(automount's) has far less - and none stays running afterwards.

The path of the dmdevfs is in the unit file (`/dev`, where dmod-boot mounts
it); a system with another one, or a second one, gives its units their own path.

With dmod-boot, in a `*.dmd` file of the firmware:

```
devscan service=devscan@.ini
devscan service=devscan-remove@.ini
devscan rules=devscan.rules
```
8 changes: 8 additions & 0 deletions services/devscan/configs/devscan-remove@.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
; devscan-remove@.ini - libsystemd unit template, started by the "remove" key of the [class=mount] rule in
; devscan.rules when a mount point is about to be unmounted (%v is the mount point). The unmount waits for it.
;
; One shot: asks dmdevfs to drop what came from <mount point>/cfg/drivers and ends.
description=Drop the drivers of %v
exec=devscan
args=remove /dev %v
type=oneshot
12 changes: 12 additions & 0 deletions services/devscan/configs/devscan.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
; devscan.rules - libsystemd device rule.
;
; libsystemd reports every mount as a device of class "mount" (dmfsi's _mount_changed dif, called by dmvfs), named
; after the mount point ("/mnt/SD" -> "mnt_SD", "/ext" -> "ext"), with the mount point as its value (%v).
;
; Mounted: let dmdevfs configure its cfg/drivers. About to be unmounted: let it drop them, while the file system is
; still there - the unmount waits for the unit.
;
; Load with libsystemd_load_rules() on the directory this file lives in.
[class=mount]
start=devscan@%name
remove=devscan-remove@%name
11 changes: 11 additions & 0 deletions services/devscan/configs/devscan@.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
; devscan@.ini - libsystemd unit template, instantiated once per mount point by the [class=mount] rule in
; devscan.rules: %i is the mount point's name (e.g. "mnt_SD"), %v the mount point ("/mnt/SD").
;
; The first argument after "add" is where the dmdevfs whose drivers it is about is mounted. The only one there is
; mounts at /dev; with another (or a second) one, give the unit its own path here.
;
; One shot: asks dmdevfs to configure <mount point>/cfg/drivers and ends.
description=Scan %v for drivers
exec=devscan
args=add /dev %v
type=oneshot
25 changes: 25 additions & 0 deletions services/devscan/devscan.dmr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# DMOD Resource File for devscan
# This file specifies where resources should be installed

# === Core Module ===
dmf=./${module}.dmf => ${destination}/${module}.dmf [origin=${dmf_dir}/${module}.dmf]
dmfc=./${module}.dmfc => ${destination}/${module}.dmfc [origin=${build_dir}/dmfc/${module}.dmfc]
dmd=./${module}.dmd => ${destination}/${module}.dmd [origin=${dmf_dir}/${module}.dmd]
version=./${module}_version.txt => ${destination}/${module}_version.txt [origin=${dmf_dir}/${module}_version.txt]

# === Documentation ===
# NOTE: ${repo_dir} is the top-level repo root (devscan is built through
# add_subdirectory() from dmdevfs's CMakeLists.txt), so paths are relative to
# the repo root, not to services/devscan.
readme=./README.md => ${destination}/${module}/README.md [origin=${repo_dir}/services/devscan/README.md]

# === Configuration Files ===
# devscan@.ini / devscan-remove@.ini unit templates + devscan.rules device rule, ready to drop into
# libsystemd's units/rules directories - see README.md
configs=./configs => ${destination}/${module}/configs [origin=${repo_dir}/services/devscan/configs]

# === License ===
license=./LICENSE => ${destination}/${module}/LICENSE [origin=${repo_dir}/LICENSE]

# Adding itself
dmr=./${module}.dmr => ${destination}/${module}/${module}.dmr [origin=${repo_dir}/services/devscan/${module}.dmr]
53 changes: 53 additions & 0 deletions services/devscan/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @file main.c
* @brief devscan - tell a dmdevfs mount that a file system came or went
*
* devscan add <dmdevfs path> <mount point>
* devscan remove <dmdevfs path> <mount point>
*
* A mounted file system - an SD card, the external flash - may bring driver configurations of its own, in
* <mount point>/cfg/drivers (the layout of the directory dmdevfs is configured from). devscan opens the root of the
* dmdevfs mount (<dmdevfs path>, e.g. /dev) and asks it, with an ioctl, to configure them (DMDEVFS_IOCTL_ADD_CONFIG_DIR)
* or to drop them again (DMDEVFS_IOCTL_REMOVE_CONFIG_DIR). It is all dmdevfs-specific, so it lives here and not in
* libsystemd.
*
* libsystemd reports every mount as a device of class "mount" (dmfsi's _mount_changed dif) and the rule of
* configs/devscan.rules starts `devscan add` for it, and `devscan remove` when it is about to be unmounted (the
* rule's "remove" key - the unmount waits for it). Both end when they are done: nothing stays running. The units and
* rules that come with a mount are not its business.
*
* A mount point without cfg/drivers is left alone (dmdevfs finds nothing there).
*/
#define DMOD_ENABLE_REGISTRATION ON
#include "dmod.h"
#include "dmdevfs.h"
#include <errno.h>
#include <string.h>

int main(int argc, char* argv[])
{
bool add = argc == 4 && strcmp(argv[1], "add") == 0;
bool remove = argc == 4 && strcmp(argv[1], "remove") == 0;
if (!add && !remove)
{
Dmod_Printf("Usage: devscan <add|remove> <dmdevfs path> <mount point>\n");
Dmod_Printf("Configures/drops <mount point>/cfg/drivers in the dmdevfs mounted at <dmdevfs path>.\n");
return -EINVAL;
}
const char* devfs_path = argv[2];
const char* mount_point = argv[3];

void* devfs = Dmod_FileOpen(devfs_path, "r");
if (devfs == NULL)
{
DMOD_LOG_WARN("devscan: cannot open %s - is dmdevfs mounted there?\n", devfs_path);
return -ENODEV;
}
int ret = Dmod_Ioctl(devfs, add ? DMDEVFS_IOCTL_ADD_CONFIG_DIR : DMDEVFS_IOCTL_REMOVE_CONFIG_DIR, (void*)mount_point);
Dmod_FileClose(devfs);
if (ret != 0)
{
DMOD_LOG_WARN("devscan: %s of %s in %s failed (%d)\n", add ? "adding" : "removing", mount_point, devfs_path, ret);
}
return ret;
}
Loading
Loading