From 65993748fbb268c098353d1c37e87f4237e87de9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 13:19:33 +0000 Subject: [PATCH 1/3] Require dmgpio >= 2.0 for the chip-select ioctl dmgpio 2.0 numbers its ioctl commands from DMDRVI_IOCTL_CUSTOM_BASE (choco-technologies/dmgpio#37), so dmgpio_ioctl_cmd_set_pins_state - which drives a software chip select - has a new value. dmspi must be built against 2.0 and never loaded together with dmgpio 1.x, where the same number meant something else. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TgkwHQKqf6c4DzypSKzZT7 --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d1cea21..9b1de3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,12 +67,15 @@ dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} src/dmspi.c ) +# dmgpio >= 2.0: its ioctl commands start at DMDRVI_IOCTL_CUSTOM_BASE - +# dmgpio_ioctl_cmd_set_pins_state (chip select) has a different value than +# in 1.x, so dmspi has to be built against 2.0 and never paired with 1.x. dmod_link_modules(${DMOD_MODULE_NAME} dmdrvi dmini dmhaman dm_sw_ring - dmgpio + dmgpio@>=2.0 ) target_include_directories(${DMOD_MODULE_NAME} PRIVATE From a72b79928ae83a63cdfdb321cfadab24f61348ec Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 13:24:55 +0000 Subject: [PATCH 2/3] Number the ioctl commands from DMDRVI_IOCTL_CUSTOM_BASE, -ENOTTY for the rest dmspi numbered its commands from 1, inside the standard dmdrvi range (DMDRVI_IOCTL_NET_* are 0x01..), and rejected anything >= its max with a logged -EINVAL. dmdevfs probes every node with DMDRVI_IOCTL_BLOCK_GET_INFO (0x100) and DMDRVI_IOCTL_MONITOR_GET_POLICY (0x200), so each SPI node logged two errors, and commands below 1 were not range-checked at all. dmspi_ioctl_cmd_t now starts at DMDRVI_IOCTL_CUSTOM_BASE (dmspi_types.h includes dmdrvi_ioctl.h; dmspi_port and spitest fetch the dmdrvi headers for it), and every command outside [get_role, max) returns -ENOTTY without a log line, as dmdrvi requires - the same change dmgpio, dmdma and dmtty got. The command values change: modules that send dmspi ioctls must be rebuilt, so this goes out as dmspi 2.0 (spitest in this repository is rebuilt with it). test_dmspi calls dmspi through the dmdrvi DIF like dmdevfs does, on the x86_64 loopback port: the block/monitor/network probes, 0, -1 and dmspi_ioctl_cmd_max give -ENOTTY; dmspi_ioctl_cmd_get_role is DMDRVI_IOCTL_CUSTOM_BASE and answers. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TgkwHQKqf6c4DzypSKzZT7 --- CMakeLists.txt | 2 +- docs/api-reference.md | 7 ++++ include/dmspi_types.h | 6 ++- src/dmspi.c | 7 ++-- src/port/CMakeLists.txt | 3 ++ tests/CMakeLists.txt | 7 ++++ tests/dmspi_test.c | 72 ++++++++++++++++++++++++++++++++++++ tools/spitest/CMakeLists.txt | 4 +- 8 files changed, 102 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b1de3f..10b7924 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} # dmgpio_ioctl_cmd_set_pins_state (chip select) has a different value than # in 1.x, so dmspi has to be built against 2.0 and never paired with 1.x. dmod_link_modules(${DMOD_MODULE_NAME} - dmdrvi + dmdrvi@>=2.0 dmini dmhaman dm_sw_ring diff --git a/docs/api-reference.md b/docs/api-reference.md index 38c50ab..cd5c206 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -44,6 +44,13 @@ callable by name from another module (see [configuration.md](configuration.md)). | `dmdrvi_dmspi_read` | Receive bytes - drains the RX ring if configured, otherwise blocks clocking dummy bytes (master) or waiting for the external master (slave). | | `dmdrvi_dmspi_write` | Transmit bytes (full duplex at the wire level; received bytes are discarded). | | `dmdrvi_dmspi_ioctl` | Get/set configuration parameters, register an interrupt handler, run a full-duplex transfer, or reconfigure - see `dmspi_ioctl_cmd_t` in [include/dmspi_types.h](../include/dmspi_types.h). | + +`dmspi_ioctl_cmd_t` starts at `DMDRVI_IOCTL_CUSTOM_BASE` (0x1000, dmdrvi 2.0), +the range dmdrvi reserves for driver-specific commands. Any other command - +for example the class probes (`DMDRVI_IOCTL_BLOCK_GET_INFO`, +`DMDRVI_IOCTL_MONITOR_GET_POLICY`) dmdevfs sends to every node - returns +`-ENOTTY` without being logged. Up to dmspi 1.x the commands started at 1; +modules sending dmspi ioctls must be rebuilt. | `dmdrvi_dmspi_flush` | Block until any in-progress transfer completes. | | `dmdrvi_dmspi_stat` | Report device metadata. | diff --git a/include/dmspi_types.h b/include/dmspi_types.h index abdc5f3..2e92d47 100644 --- a/include/dmspi_types.h +++ b/include/dmspi_types.h @@ -4,6 +4,7 @@ #include #include #include +#include "dmdrvi_ioctl.h" /** * @brief SPI instance type (1-based, e.g. 1 = SPI1) @@ -79,7 +80,10 @@ typedef enum */ typedef enum { - dmspi_ioctl_cmd_get_role = 1, /**< Get role (master/slave) */ + /* Private commands start at DMDRVI_IOCTL_CUSTOM_BASE: everything below + * it is a standard dmdrvi command (network, block, monitor) that dmdevfs + * and other generic code may send to any node - dmspi answers -ENOTTY. */ + dmspi_ioctl_cmd_get_role = DMDRVI_IOCTL_CUSTOM_BASE, /**< Get role (master/slave) */ dmspi_ioctl_cmd_set_role, /**< Set role (master/slave) */ dmspi_ioctl_cmd_get_baudrate, /**< Get requested baud rate */ dmspi_ioctl_cmd_set_baudrate, /**< Set requested baud rate */ diff --git a/src/dmspi.c b/src/dmspi.c index fa6c256..9cf0d4a 100644 --- a/src/dmspi.c +++ b/src/dmspi.c @@ -682,10 +682,11 @@ dmod_dmdrvi_dif_api_declaration(2.0, dmspi, int, _ioctl, ( dmdrvi_context_t cont return -EINVAL; } - if (command >= dmspi_ioctl_cmd_max) + /* Not a dmspi command - e.g. the class probes dmdevfs sends to every + * node: not implemented, as dmdrvi requires, and not worth a log line. */ + if (command < dmspi_ioctl_cmd_get_role || command >= dmspi_ioctl_cmd_max) { - DMOD_LOG_ERROR("Invalid ioctl command %d\n", command); - return -EINVAL; + return -ENOTTY; } if (command == dmspi_ioctl_cmd_reconfigure) diff --git a/src/port/CMakeLists.txt b/src/port/CMakeLists.txt index 1dde73b..b264dc2 100644 --- a/src/port/CMakeLists.txt +++ b/src/port/CMakeLists.txt @@ -37,10 +37,13 @@ dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} ${COMMON_SOURCES} ) +# dmdrvi: headers only - dmspi_types.h numbers the ioctl commands from +# DMDRVI_IOCTL_CUSTOM_BASE. dmod_link_modules(${DMOD_MODULE_NAME} dm_sw_ring dmclk_port dmosi + dmdrvi@>=2.0 ) target_include_directories(${DMOD_MODULE_NAME} BEFORE PRIVATE diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fdbaa2e..30548c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,6 +14,13 @@ target_include_directories(${DMOD_MODULE_NAME} PRIVATE target_link_libraries(${DMOD_MODULE_NAME} dmspi_if) +# dmdrvi: the DIF the ioctl test calls dmspi through (like dmdevfs); dmini: +# its configuration. +dmod_link_modules(${DMOD_MODULE_NAME} + dmdrvi@>=2.0 + dmini +) + # Register the test module with ctest - this replays the same two steps CI # runs (see .github/templates/ci.yml.template): install the test module's # dependencies with dmf-get, then execute it through dmod_loader. The test's diff --git a/tests/dmspi_test.c b/tests/dmspi_test.c index 946afca..5ac3e07 100644 --- a/tests/dmspi_test.c +++ b/tests/dmspi_test.c @@ -1,6 +1,10 @@ #define DMOD_ENABLE_REGISTRATION ON +#define ENABLE_DIF_REGISTRATIONS ON #include "dmod_test.h" #include "dmspi.h" +#include "dmdrvi.h" +#include "dmini.h" +#include #include static dmspi_config_t g_config; @@ -45,3 +49,71 @@ DMOD_TEST_STEP(dmspi_validate_config_rejects_null) { DMOD_TEST_EXPECT_FALSE(dmspi_validate_config(NULL)); } + +/* ---- ioctl numbering, through the dmdrvi DIF the way dmdevfs calls it ---- */ + +typedef struct +{ + dmod_dmdrvi_create_t create; + dmod_dmdrvi_free_t free; + dmod_dmdrvi_open_t open; + dmod_dmdrvi_close_t close; + dmod_dmdrvi_ioctl_t ioctl; +} driver_t; + +static bool get_driver(driver_t* drv) +{ + Dmod_Context_t* module = Dmod_GetModuleContext("dmspi"); + if (module == NULL) + { + return false; + } + drv->create = Dmod_GetDifFunction(module, dmod_dmdrvi_create_sig); + drv->free = Dmod_GetDifFunction(module, dmod_dmdrvi_free_sig); + drv->open = Dmod_GetDifFunction(module, dmod_dmdrvi_open_sig); + drv->close = Dmod_GetDifFunction(module, dmod_dmdrvi_close_sig); + drv->ioctl = Dmod_GetDifFunction(module, dmod_dmdrvi_ioctl_sig); + return drv->create != NULL && drv->free != NULL && drv->open != NULL && + drv->close != NULL && drv->ioctl != NULL; +} + +DMOD_TEST_STEP(dmspi_ioctl_answers_only_its_own_command_range) +{ + driver_t drv = { 0 }; + DMOD_TEST_EXPECT_TRUE(get_driver(&drv)); + if (drv.ioctl == NULL) + { + return; + } + dmini_context_t ini = dmini_create(); + dmini_parse_string(ini, "[dmspi]\ninstance=1\nrole=master\nbaudrate=1000000\nmode=0\n"); + dmdrvi_dev_num_t dev_num = { 0 }; + dmdrvi_context_t ctx = drv.create(ini, &dev_num); + DMOD_TEST_EXPECT_NOT_NULL(ctx); + void* handle = (ctx != NULL) ? drv.open(ctx, DMDRVI_O_RDWR, &dev_num) : NULL; + DMOD_TEST_EXPECT_NOT_NULL(handle); + if (handle != NULL) + { + dmspi_role_t role = dmspi_role_slave; + uint32_t probe[16] = { 0 }; + + /* Standard dmdrvi commands - dmdevfs sends the first two to every node. */ + DMOD_TEST_EXPECT_EQ(drv.ioctl(ctx, handle, DMDRVI_IOCTL_BLOCK_GET_INFO, probe), -ENOTTY); + DMOD_TEST_EXPECT_EQ(drv.ioctl(ctx, handle, DMDRVI_IOCTL_MONITOR_GET_POLICY, probe), -ENOTTY); + DMOD_TEST_EXPECT_EQ(drv.ioctl(ctx, handle, DMDRVI_IOCTL_NET_GET_MAC_ADDR, probe), -ENOTTY); + DMOD_TEST_EXPECT_EQ(drv.ioctl(ctx, handle, 0, probe), -ENOTTY); + DMOD_TEST_EXPECT_EQ(drv.ioctl(ctx, handle, -1, probe), -ENOTTY); + DMOD_TEST_EXPECT_EQ(drv.ioctl(ctx, handle, dmspi_ioctl_cmd_max, probe), -ENOTTY); + + /* dmspi's own commands, from DMDRVI_IOCTL_CUSTOM_BASE. */ + DMOD_TEST_EXPECT_EQ((int)dmspi_ioctl_cmd_get_role, DMDRVI_IOCTL_CUSTOM_BASE); + DMOD_TEST_EXPECT_EQ(drv.ioctl(ctx, handle, dmspi_ioctl_cmd_get_role, &role), 0); + DMOD_TEST_EXPECT_EQ(role, dmspi_role_master); + drv.close(ctx, handle); + } + if (ctx != NULL) + { + drv.free(ctx); + } + dmini_destroy(ini); +} diff --git a/tools/spitest/CMakeLists.txt b/tools/spitest/CMakeLists.txt index baa6a5f..ac86316 100644 --- a/tools/spitest/CMakeLists.txt +++ b/tools/spitest/CMakeLists.txt @@ -25,9 +25,11 @@ dmod_add_executable(spitest ${DMOD_MODULE_VERSION} # dmosi is a separate module - fetch its headers for the worker thread # used by the loopback test. The real implementation is resolved at # runtime against the system's own instance, same as dmtty/tools/tty's -# dmtty_attach() - no static linkage. +# dmtty_attach() - no static linkage. dmdrvi: headers only, for +# DMDRVI_IOCTL_CUSTOM_BASE (the base of dmspi_ioctl_cmd_t). dmod_link_modules(spitest dmosi + dmdrvi@>=2.0 ) # dmspi_types.h lives in this same repo (no dmf-get fetch needed) - it only From 2925a7c209480fe213728d7afe1715728c3d6f28 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 13:54:44 +0000 Subject: [PATCH 3/3] Require dmgpio >= 1.1, the release with the new ioctl numbering choco-technologies/dmgpio#37 was released as dmgpio 1.1, not 2.0, so dmgpio@>=2.0 could not be resolved and CI had no dmgpio headers. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TgkwHQKqf6c4DzypSKzZT7 --- CMakeLists.txt | 7 ++++--- docs/api-reference.md | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 10b7924..fd2b210 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,15 +67,16 @@ dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} src/dmspi.c ) -# dmgpio >= 2.0: its ioctl commands start at DMDRVI_IOCTL_CUSTOM_BASE - +# dmgpio >= 1.1: its ioctl commands start at DMDRVI_IOCTL_CUSTOM_BASE - # dmgpio_ioctl_cmd_set_pins_state (chip select) has a different value than -# in 1.x, so dmspi has to be built against 2.0 and never paired with 1.x. +# in 1.0. Build-time only: dmspi reaches the GPIO through Dmod_Ioctl() on +# its node, so the board image must carry dmgpio >= 1.1 as well. dmod_link_modules(${DMOD_MODULE_NAME} dmdrvi@>=2.0 dmini dmhaman dm_sw_ring - dmgpio@>=2.0 + dmgpio@>=1.1 ) target_include_directories(${DMOD_MODULE_NAME} PRIVATE diff --git a/docs/api-reference.md b/docs/api-reference.md index cd5c206..d50a070 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -49,7 +49,7 @@ callable by name from another module (see [configuration.md](configuration.md)). the range dmdrvi reserves for driver-specific commands. Any other command - for example the class probes (`DMDRVI_IOCTL_BLOCK_GET_INFO`, `DMDRVI_IOCTL_MONITOR_GET_POLICY`) dmdevfs sends to every node - returns -`-ENOTTY` without being logged. Up to dmspi 1.x the commands started at 1; +`-ENOTTY` without being logged. Before this numbering the commands started at 1; modules sending dmspi ioctls must be rebuilt. | `dmdrvi_dmspi_flush` | Block until any in-progress transfer completes. | | `dmdrvi_dmspi_stat` | Report device metadata. |