Skip to content
Closed
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
51 changes: 49 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,31 @@ jobs:
# dmsdio links against dmsdio_port_if (headers only),
# not dmsdio_port itself, so building only these two targets
# never touches src/port/*/port.c - safe to build natively here
# regardless of which CPU family the port defaults to.
# regardless of which CPU family the port defaults to. Leave
# DMOD_CPU_FAMILY at its default (stm32f7): dmod_link_modules'
# header fetch for dmgpio keys off of it too, and dmgpio has never
# published an x86_64 package - only stm32f4/stm32f7, which is
# also all its release pipeline ever builds. Overriding it to
# x86_64 here 404s that fetch, since dmgpio's headers don't come
# from a family-independent package (see dmspi's own CI for the
# same reasoning - dmsdio has the identical dmgpio dependency for
# its optional card-detect friend).
cmake .. -DDMOD_MODE=DMOD_MODULE -DDMOD_TOOLS_NAME=arch/x86_64
cmake --build . --target dmsdio test_dmsdio
cmake --build . --target dmsdio test_dmsdio dmod_generate_manifest

- name: Build x86_64 dmsdio_port mock
run: |
# Separate build dir/configure so DMOD_CPU_FAMILY=x86_64 here can't
# affect the dmgpio header fetch above. dmsdio_port itself has no
# x86_64 release (it's real SDMMC register access) - this builds
# src/port/x86_64/port.c, the mocked SD card dmsdio_test.c drives
# through mock_control.h, purely so dmod_loader has something to
# satisfy dmsdio's runtime dmsdio_port dependency with below.
mkdir -p build_dmsdio_port_x86_64
cd build_dmsdio_port_x86_64
cmake .. -DDMOD_MODE=DMOD_MODULE -DDMOD_TOOLS_NAME=arch/x86_64 -DDMOD_CPU_FAMILY=x86_64
cmake --build . --target dmsdio_port
cp dmf/dmsdio_port.dmf dmf/dmsdio_port.dmd ../build/dmf/

- name: Verify module files
run: |
Expand All @@ -37,11 +59,36 @@ jobs:
test -f build/dmf/dmsdio.dmf
test -f build/dmf/dmsdio_version.txt
test -f build/dmf/test_dmsdio.dmf
test -f build/dmf/dmsdio_port.dmf
echo "Module files present"

- name: Run tests with dmod_loader
run: |
export DMOD_DMF_DIR=$(pwd)/build/dmf
# dmod_link_modules only fetches headers at configure time;
# dmsdio's own runtime dependencies (dmini, dmgpio) still need to
# be staged into build/dmf before dmod_loader can resolve them,
# same as the ctest target defined in tests/CMakeLists.txt already
# does. dmsdio.dmd also lists dmsdio_port, which has no published
# x86_64 release (only the real stm32f4/stm32f7 hardware ports
# are) - the build step above already placed our own x86_64
# dmsdio_port.dmf (the mock) in build/dmf, so `|| true` just lets
# dmf-get's redundant remote lookup for it fail without masking a
# real failure to fetch dmini/dmgpio/dmosi.
dmf-get install -d build/dmf/dmsdio.dmd -y || true
dmf-get install -d build/dmf/test_dmsdio.dmd -y
# test_dmsdio.dmf only ever looks "dmsdio" up by name at runtime
# (Dmod_GetModuleContext("dmsdio") - see dmsdio_test.c, the same
# DIF-consumer pattern dmdevfs.c uses for every driver it mounts),
# so nothing in its compiled binary references dmsdio's own
# signature the way it does dmsdio_port's/dmini's Module API
# calls - neither dmsdio.dmd nor test_dmsdio.dmd's dependency
# scan (both installed above) can see a DIF-by-name lookup like
# that, so "dmsdio" never gets fetched as anyone's dependency.
# It's already built as its own target above, though - install
# it explicitly by name from that same local build/package tree
# (see tests/CMakeLists.txt's ctest wiring for the identical fix).
dmf-get install dmsdio -m build/packages/manifest-local.dmm -y
dmod_loader build/dmf/test_dmsdio.dmf

discover-cpu-families:
Expand Down
9 changes: 6 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,12 @@ target_include_directories(${DMOD_MODULE_NAME}_if INTERFACE
)

# To depend on another module's headers, use:
# dmod_link_modules(${DMOD_MODULE_NAME}
# other_module@1.0
# )
dmod_link_modules(${DMOD_MODULE_NAME}
dmdrvi
dmini
dmgpio
dmosi
)

add_subdirectory(src/port)
target_link_libraries(${DMOD_MODULE_NAME} ${DMOD_MODULE_NAME}_port_if)
Expand Down
53 changes: 16 additions & 37 deletions include/dmsdio.h
Original file line number Diff line number Diff line change
@@ -1,51 +1,30 @@
#ifndef DMSDIO_H
#define DMSDIO_H

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "dmod_types.h"
#include "dmsdio_defs.h"
#include "dmsdio_types.h"

/**
* Public API for the dmsdio module.
*
* Functions are declared with the dmod_dmsdio_api(...) macro - dmod's
* standard pattern for functions callable from other modules (or from this
* module's own tests/), resolved dynamically by the loader rather than
* through normal static linkage. See dm_sw_ring/include/dm_sw_ring.h for a
* fully worked real-world example of the same shape.
*
* Definitions in src/dmsdio.c use the matching
* dmod_dmsdio_api_declaration(...) macro - a plain C function
* definition here will NOT satisfy these declarations at link time.
*
* This is an example interface using the usual "opaque handle" pattern -
* replace the handle, functions, and struct definition in
* src/dmsdio.c with your module's real API.
* @brief SD host controller configuration
*/

/* Opaque handle - the real struct is defined in src/dmsdio.c */
typedef struct dmsdio* dmsdio_t;
typedef struct
{
dmsdio_instance_t instance; /**< Host controller instance number (0-based) */
dmsdio_bus_width_t max_bus_width; /**< Never negotiate wider than this, even if the card and board wiring support it */
bool allow_high_speed; /**< Attempt CMD6 High Speed switch after identification */
uint32_t command_timeout_ms;/**< Per-command response deadline */
uint32_t data_timeout_ms; /**< Per-block-transfer deadline */
uint32_t max_retries; /**< Retries for a CRC/timeout error before giving up */
} dmsdio_config_t;

/**
* Create a new dmsdio instance.
* @brief Validate a configuration structure without touching hardware.
*
* @return A valid handle on success, or NULL on allocation failure.
*/
dmod_dmsdio_api(1.0, dmsdio_t, _create, ( void ));

/**
* Destroy an instance created by dmsdio_create(). Safe to call with
* NULL.
*/
dmod_dmsdio_api(1.0, void, _destroy, ( dmsdio_t handle ));

/**
* Example accessor - replace with your module's real API.
* Does not require the dmsdio_port module to be loaded, so it is safe to
* call from any context, including off-target unit tests.
*
* @return true if handle is a valid, non-NULL instance.
* @return true if the configuration is self-consistent, false otherwise.
*/
dmod_dmsdio_api(1.0, bool, _is_valid, ( dmsdio_t handle ));
dmod_dmsdio_api(1.0, bool, _validate_config, ( const dmsdio_config_t *config ));

#endif // DMSDIO_H
97 changes: 78 additions & 19 deletions include/dmsdio_port.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,90 @@

#include "dmod_types.h"
#include "dmsdio_port_defs.h"
#include "dmsdio_types.h"

/*
* Declare architecture-independent port API functions here, following the
* dmod_dmsdio_port_api(version, return_type, _suffix, (args)) pattern,
* e.g.:
/**
* @brief Architecture-independent SD host controller port
*
* dmod_dmsdio_port_api(1.0, int, _configure, ( int some_arg ) );
* Every function here is a raw hardware primitive - no SD command sequence,
* register field, or card-state logic belongs in a port implementation
* (src/port/<arch>/port.c). dmsdio.c owns 100% of the protocol: which
* commands to send in which order, how to parse OCR/CID/CSD/SCR/card
* status, retry/timeout policy, and byte<->block address translation. A
* port only needs to know how to clock a command out, capture whatever
* response the caller says to expect, and move already-block-aligned data
* in or out - see dmsdio_response_type_t's own comment for exactly how
* little a port needs to understand about response *content*.
*
* Each declaration here must have a matching *definition* in
* src/port/<arch>/port.c (or a shared src/port/<arch>_common/ file), written
* with the dmod_dmsdio_port_api_declaration(...) macro instead:
* Whether a port uses the host controller's own internal DMA engine (e.g.
* STM32F7 SDMMC's IDMA) or leases a stream from dmdma (e.g. STM32F4 SDIO,
* which has no IDMA of its own and needs a real DMA2 stream) is entirely a
* port implementation detail, invisible from here.
*/

/* --- Lifecycle --- */

dmod_dmsdio_port_api(1.0, int, _init, ( dmsdio_instance_t instance ) );
dmod_dmsdio_port_api(1.0, int, _deinit, ( dmsdio_instance_t instance ) );

/* --- Bus configuration ---
*
* dmsdio.c always calls _set_speed_mode(dmsdio_speed_mode_identification)
* (<= 400 kHz) before the very first command of a fresh identification
* sequence, and never raises the bus width above 1-bit until after CMD7
* (card selected) - matching the mandatory power-up sequencing in the SD
* Physical Layer spec, section "Bus Protocol".
*/

dmod_dmsdio_port_api(1.0, int, _set_bus_width, ( dmsdio_instance_t instance, dmsdio_bus_width_t width ) );
dmod_dmsdio_port_api(1.0, int, _set_speed_mode, ( dmsdio_instance_t instance, dmsdio_speed_mode_t mode ) );

/* --- Commands ---
*
* dmod_dmsdio_port_api_declaration(1.0, int, _configure, ( int some_arg ) )
* {
* ...
* }
* Sends command index `cmd_index` (already including the CMD55 prefix for
* an ACMD, if any - dmsdio.c issues CMD55 as its own, separate
* _send_command() call) with `argument`, then waits for and captures
* whichever response shape `response_type` says to expect - dmsdio_response_none
* returns immediately once the command itself has been clocked out.
*
* Note this is a *different* mechanism from the dmod_init()/dmod_deinit()
* module lifecycle hooks already defined in port.c - don't declare `_init`/
* `_deinit` here too, that name collides with the lifecycle hooks and (unlike
* them) requires an explicit dmod_dmsdio_port_api_declaration(...)
* definition to avoid an undefined-reference link error.
* A response's CRC/index-check bits must be verified by the port
* (dmsdio_error_command_crc on mismatch) except for dmsdio_response_r3,
* whose CRC field is defined by the spec to be all 1s and is not checked.
*
* See dmfmc/include/dmfmc_port.h and dmfmc/src/port/stm32_common/stm32_common.c
* for a fully worked example.
* dmsdio_response_r1b is captured identically to dmsdio_response_r1 - the
* port does not need to (and must not try to) wait out the card's DAT0
* busy signal itself. Whenever dmsdio.c actually needs to know a card has
* finished an internal operation (post-erase, post-write), it polls
* CMD13/SEND_STATUS's READY_FOR_DATA bit for that, entirely at the
* protocol layer - this is deliberate: not every controller exposes a raw
* DAT0 level to software, and CMD13 polling works identically everywhere.
*/

dmod_dmsdio_port_api(1.0, dmsdio_error_t, _send_command,
( dmsdio_instance_t instance, uint8_t cmd_index, uint32_t argument,
dmsdio_response_type_t response_type, dmsdio_response_t *response ) );

/* --- Data transfer ---
*
* dmsdio.c has already issued the matching command via _send_command()
* before calling either of these - CMD17/18 for a read, CMD24/25 for a
* write, ACMD51 for an SCR read (block_size=8, block_count=1), ACMD13 for
* an SD Status read (block_size=64, block_count=1) - and, for the
* multi-block case, will issue CMD12 itself afterward; these calls only
* drive the data phase itself. block_size is *not* fixed at
* DMSDIO_BLOCK_SIZE: SCR and SD Status are transferred as a single
* narrower block each, same as on the wire. Both block_size and
* block_count must be >= 1 (validated by dmsdio.c before the call).
*
* Neither call waits for the card to finish internally programming what
* was written - only that the bytes were clocked onto the bus. dmsdio.c
* polls CMD13/SEND_STATUS itself afterward to know when a write has
* actually landed (see dmsdio_response_r1b's own comment above).
*/

dmod_dmsdio_port_api(1.0, dmsdio_error_t, _read_blocks,
( dmsdio_instance_t instance, void *buffer, uint32_t block_size, uint32_t block_count ) );
dmod_dmsdio_port_api(1.0, dmsdio_error_t, _write_blocks,
( dmsdio_instance_t instance, const void *buffer, uint32_t block_size, uint32_t block_count ) );

#endif // DMSDIO_PORT_H
Loading
Loading