Skip to content
choco-technologiesPublic

About

DMOD Driver Interface

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

51 Commits

Folders and files

Repository files navigation

dmdrvi - DMOD Driver Interface Module

A DMOD module library that provides a standardized interface for device drivers, enabling uniform access to hardware devices in embedded systems.

Overview

dmdrvi is a driver interface module designed for embedded systems using the DMOD framework. It provides a consistent API for device driver operations including open, close, read, write, ioctl, and other standard device operations. The module uses device numbers (major/minor) to identify and manage devices.

Features

  • Standardized Device Interface: Uniform API for all device drivers
  • Device Identification: Major/minor device number system
  • Flexible Access Modes: Read-only, write-only, and read-write support
  • Standard Operations: open, close, read, write, ioctl, flush, stat
  • 64-bit Addressing: Explicit offset, size, and signed I/O-result types for devices larger than 4 GiB
  • Block Device Controls: Standard geometry, erase, and discard ioctls
  • Input Controls: Standard info / state / wait-for-event ioctls for touch panels, mice and buttons, so higher layers use every input device the same way
  • Monitor Controls: Class-independent GET_POLICY / EVENT / REFRESH ioctls that let a monitor service handle presence detection, hot-plug and polling for a driver, so drivers never create threads for it
  • Configuration Support: Integration with dmini for device configuration
  • Friends Groups: Group related driver configurations and let drivers discover one another's device paths
  • Dynamic Device Notifications: Drivers can inform dmdevfs when a device becomes available or unavailable at runtime within an existing context
  • Network Driver Ioctls: Built-in ioctl commands for MAC address configuration, link status, and interface start/stop (see dmdrvi_ioctl.h)
  • Audio Ioctls: Codec controls (identity, format and output, volume, mute) and one stream contract (layout, start/stop, status, I/O timeout, drain) shared by codecs and bare serial audio interfaces (see dmdrvi_ioctl.h)
  • SAL-Compatible: Uses only DMOD SAL functions
  • Lightweight: Minimal memory footprint suitable for embedded systems

API

Context Management

  • dmdrvi_create(config, dev_num) - Create driver context with configuration
  • dmdrvi_free(context) - Free driver context

Device Operations

  • dmdrvi_open(context, flags, dev_num) - Open device with specified flags
  • dmdrvi_close(context, handle) - Close device handle
  • dmdrvi_read(context, handle, buffer, size, offset) - Read at a 64-bit byte offset; returns bytes, EOF, or a negative error
  • dmdrvi_write(context, handle, buffer, size, offset) - Write at a 64-bit byte offset; returns bytes or a negative error
  • dmdrvi_ioctl(context, handle, command, arg) - Device control operations
  • dmdrvi_flush(context, handle) - Flush device buffers
  • dmdrvi_stat(context, path, stat) - Get device status

Dynamic Device Notifications (MAL interface, driver -> dmdevfs)

  • dmdrvi_device_available(context, dev_num) - Driver informs dmdevfs that a new device is available within an existing context
  • dmdrvi_device_unavailable(context, dev_num) - Driver informs dmdevfs that a device is no longer available

Friends Groups

  • dmdrvi_friend_changed(context, info) - Notify a driver about a device belonging to the same friends group (optional driver callback)

Open Flags

  • DMDRVI_O_RDONLY - Open for read only
  • DMDRVI_O_WRONLY - Open for write only
  • DMDRVI_O_RDWR - Open for read and write

Usage Example

#include "dmdrvi.h"

// Create driver context - driver assigns device numbers
dmdrvi_dev_num_t dev_num;  // Output parameter
dmdrvi_context_t ctx = dmdrvi_create(NULL, &dev_num);

// Check what numbering scheme the driver uses
if (dev_num.flags == DMDRVI_NUM_NONE) {
    // Device file: /dev/dmclk
    Dmod_Printf("Device: /dev/dmclk\n");
} else if (dev_num.flags & DMDRVI_NUM_MINOR) {
    // Device file: /dev/dmspi0/0, /dev/dmspi0/1, etc.
    // Driver uses both major and minor (directory structure)
    Dmod_Printf("Device: /dev/dmspi%d/%d\n", dev_num.major, dev_num.minor);
} else if (dev_num.flags & DMDRVI_NUM_MAJOR) {
    // Device file: /dev/dmuart0, /dev/dmuart1, etc.
    // Driver uses major number only
    Dmod_Printf("Device: /dev/dmuart%d\n", dev_num.major);
}

// Check if driver provides a human-friendly alternative file name
if (dev_num.flags & DMDRVI_NUM_ALT_NAME) {
    Dmod_Printf("Alternative name: /dev/%s\n", dev_num.alt_name);
}

// Open device for reading and writing
void* handle = dmdrvi_open(ctx, DMDRVI_O_RDWR, &dev_num);

// Write data to device
char write_buffer[] = "Hello Device";
dmdrvi_ssize_t written = dmdrvi_write(ctx, handle, write_buffer, sizeof(write_buffer), 0);

// Read data from device
char read_buffer[256];
dmdrvi_ssize_t read = dmdrvi_read(ctx, handle, read_buffer, sizeof(read_buffer), 0);

// Get device status (does not require opening the device)
dmdrvi_stat_t stat;
int result = dmdrvi_stat(ctx, "/dev/dmuart0", &stat);
Dmod_Printf("Device size: %llu bytes\n", (unsigned long long)stat.size);

// Flush buffers
dmdrvi_flush(ctx, handle);

// Close device
dmdrvi_close(ctx, handle);

// Free context
dmdrvi_free(ctx);

Network Driver Ioctl Commands

dmdrvi_ioctl.h defines a set of ioctl commands intended for network (e.g. Ethernet) drivers implemented on top of dmdrvi. Packet data itself is transferred with the regular dmdrvi_read/dmdrvi_write calls - these ioctls only cover control operations that don't fit the read/write model:

  • DMDRVI_IOCTL_NET_SET_MAC_ADDR - set the device MAC address (const dmdrvi_net_mac_addr_t* in)
  • DMDRVI_IOCTL_NET_GET_MAC_ADDR - read the device MAC address (dmdrvi_net_mac_addr_t* out)
  • DMDRVI_IOCTL_NET_GET_LINK_STATUS - read the current link state (dmdrvi_net_link_status_t* out)
  • DMDRVI_IOCTL_NET_START - start the interface (begin packet reception/transmission)
  • DMDRVI_IOCTL_NET_STOP - stop the interface

A typical bring-up sequence is: set the MAC address, start the interface, then poll (or wait for) the link status before sending/receiving packets:

#include "dmdrvi.h"
#include "dmdrvi_ioctl.h"

void* handle = dmdrvi_open(ctx, DMDRVI_O_RDWR, &dev_num);

// Configure the MAC address before starting the interface
dmdrvi_net_mac_addr_t mac = { .addr = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x01 } };
dmdrvi_ioctl(ctx, handle, DMDRVI_IOCTL_NET_SET_MAC_ADDR, &mac);

// Start the interface
dmdrvi_ioctl(ctx, handle, DMDRVI_IOCTL_NET_START, NULL);

// Check link status
dmdrvi_net_link_status_t link;
dmdrvi_ioctl(ctx, handle, DMDRVI_IOCTL_NET_GET_LINK_STATUS, &link);
if (link == DMDRVI_NET_LINK_UP) {
    // Send a packet
    uint8_t frame[64] = { /* ... */ };
    dmdrvi_write(ctx, handle, frame, sizeof(frame), 0);

    // Receive a packet
    uint8_t rx_buffer[1518];
    dmdrvi_ssize_t received = dmdrvi_read(ctx, handle, rx_buffer, sizeof(rx_buffer), 0);
}

// Stop the interface when done
dmdrvi_ioctl(ctx, handle, DMDRVI_IOCTL_NET_STOP, NULL);
dmdrvi_close(ctx, handle);

For guidance on implementing these commands inside a network driver's dmdrvi_ioctl() DIF, see docs/dmdrvi.md.

Block Driver Ioctl Commands

  • DMDRVI_IOCTL_BLOCK_GET_INFO returns dmdrvi_block_info_t geometry and capability flags.
  • DMDRVI_IOCTL_BLOCK_ERASE performs a physical erase over a dmdrvi_block_range_t.
  • DMDRVI_IOCTL_BLOCK_DISCARD marks a dmdrvi_block_range_t unused without promising its read-back contents.

All block ranges use 64-bit byte offsets and lengths. See the API reference for alignment and capability rules.

Graphics Ioctl Commands

Any device exposing a framebuffer (LCD-TFT controller, SPI display, ...) implements the standard DMDRVI_IOCTL_GFX_* commands (0x300 range), so a generic graphics library only needs the device path: GET_INFO returns a dmdrvi_gfx_info_t (resolution, dmdrvi_gfx_pixel_format_t, bytes per pixel, stride, buffer count). The others are GET_FRAMEBUFFER, SWAP_BUFFERS, WAIT_VSYNC, FILL_RECT (0xAARRGGBB, converted by the driver), SET/GET_DISPLAY_ENABLED, SET/GET_BACKLIGHT and PRESENT. PRESENT makes the drawing visible whatever the buffer count: double buffered, it shows the drawing buffer at the next frame and copies the area drawn into the other buffer, so a client that redraws only what changed draws without tearing and without redrawing the rest; single buffered, it writes the area back from the data cache. Reading/writing the node accesses the drawing buffer at the given byte offset. Commands a driver cannot honour return -ENOTSUP; driver-specific ones use DMDRVI_IOCTL_CUSTOM_BASE.

Input Ioctl Commands

Any device a user touches, moves or presses (touch panel, mouse, buttons) implements the standard DMDRVI_IOCTL_INPUT_* commands (0x400 range), so a GUI library or a test tool only needs the device path, whatever the driver:

  • DMDRVI_IOCTL_INPUT_GET_INFO - returns a dmdrvi_input_info_t: name, dmdrvi_input_type_t, DMDRVI_INPUT_CAP_* capability bits, coordinate range, maximum number of contacts and buttons.
  • DMDRVI_IOCTL_INPUT_GET_STATE - returns the current dmdrvi_input_state_t (buttons, relative motion, wheel, contacts in screen coordinates). read() of the node returns the same.
  • DMDRVI_IOCTL_INPUT_WAIT_EVENT - blocks until the state changes (const uint32_t* timeout in ms, NULL = forever), -ETIMEDOUT on timeout.

See docs/dmdrvi.md for the state rules, the recommended ini keys and an example.

Audio Ioctl Commands

Audio nodes use DMDRVI_IOCTL_AUDIO_* (0x500 range). GET_INFO, CONFIGURE, SET_VOLUME and SET_MUTE are a codec's controls: hardware ID, format and output, a 0..100 volume the driver maps to its gain, mute.

The stream commands - GET_STREAM, START, STOP, GET_STATUS, SET_TIMEOUT / GET_TIMEOUT and DRAIN - are implemented by every node that carries samples through read/write: a codec (samples in channels, e.g. stereo) and a bare serial audio interface such as SAI, I2S or TDM (samples in its wire slots). An application plays the same way on both - CONFIGURE (optional), START, write, DRAIN, STOP - and learns the buffer layout from GET_STREAM. A serial interface accepts CONFIGURE only for its fixed format and answers the analog controls with -ENOTSUP. See docs/dmdrvi.md.

Device File System Ioctl Commands

DMDRVI_IOCTL_DEVFS_GET_FRIEND (0xF00 range, answered by dmdevfs, never by the driver) returns the members of a node's friends_group to any module - e.g. a display service finding the touch panel of its display. See docs/dmdrvi.md.

Monitor Ioctl Commands

Drivers whose devices need work done over time (card insertion/removal, USB port changes, media or link polling) implement three class-independent commands instead of creating threads. A monitor service calls them:

  • DMDRVI_IOCTL_MONITOR_GET_POLICY - returns a dmdrvi_monitor_policy_t: the dmhaman handler whose calls are the node's events, the settle time after events and the poll interval. Called once when the monitor starts.
  • DMDRVI_IOCTL_MONITOR_EVENT - called immediately after every event. Fast and non-blocking; must not wait for in-flight I/O. It only records state or sets flags.
  • DMDRVI_IOCTL_MONITOR_REFRESH - called at monitor start, after events once they settled, and every poll interval. May block; announces or withdraws nodes through dmdrvi_device_available() / dmdrvi_device_unavailable().

Drivers without monitoring needs answer -ENOTTY. See docs/dmdrvi.md for the exact calling rules, examples and the recommended ini keys.

Building

mkdir build
cd build
cmake .. -DDMOD_MODE=DMOD_MODULE
cmake --build .

This generates:

  • dmf/dmdrvi.dmf - The driver interface library module
  • dmf/dmdrvi_version.txt - Version information

Integration with Configuration

dmdrvi can work with dmini module for device configuration:

#include "dmini.h"
#include "dmdrvi.h"

// Parse device configuration
dmini_context_t config = dmini_create();
dmini_parse_file(config, "device.ini");

// Create driver context - driver assigns device numbers
dmdrvi_dev_num_t dev_num;  // Output parameter
dmdrvi_context_t driver = dmdrvi_create(config, &dev_num);

// The driver has now assigned major/minor numbers and set the flags
// The filesystem layer can use dev_num to create appropriate device files

// Use driver...

dmdrvi_free(driver);
dmini_destroy(config);

Friends Groups

Friends groups connect device configurations that together implement one logical peripheral. This is useful when one driver depends on devices exposed by other drivers. For example, an SPI controller can use GPIO devices for its SCK, MISO, MOSI, and software-controlled chip-select pins.

Add the same friends_group value to every related INI section. dmdevfs then calls the optional dmdrvi_friend_changed() callback when information about a member of that group changes. The callback receives a dmdrvi_friend_info_t containing:

  • group_name - the value of friends_group
  • friend_role - an optional application-defined role from the member's friend_role setting
  • alt_name - the member's alternative device name, when present
  • node_path - the full path of the device node; a dependent driver can save this path and open the device when needed
  • state - the current device state (unknown, ready, sleeping, or dead)
  • dev_num - the member's device-number information

friend_role is useful when a group contains several devices of the same type. Instead of depending on a section name or a generated path, the consuming driver can find a member by its logical role, such as chip_select.

The following configuration groups an SPI2 controller and its GPIO pins under arduino_spi. The SPI driver can identify the CS GPIO by its chip_select role and obtain its path (on this board, /dev/dmgpio8/arduino_spi_cs) from info->node_path:

; STM32F746G-DISCO SPI2 on the Arduino Uno V3 connector:
; D13/SCK=PI1, D12/MISO=PB14, D11/MOSI=PB15, D10/CS=PI0.

[arduino_spi_sck]
driver_name=dmgpio
friends_group=arduino_spi
pin=PI1
mode=alternate
alternate_function=5
speed=maximum
output_circuit=push_pull
pull=none

[arduino_spi_miso]
driver_name=dmgpio
friends_group=arduino_spi
pin=PB14
mode=alternate
alternate_function=5
pull=none

[arduino_spi_mosi]
driver_name=dmgpio
friends_group=arduino_spi
pin=PB15
mode=alternate
alternate_function=5
speed=maximum
output_circuit=push_pull
pull=none

[arduino_spi_cs]
driver_name=dmgpio
friends_group=arduino_spi
friend_role=chip_select
pin=PI0
mode=output
pull=up
speed=maximum
output_circuit=push_pull

[arduino_spi]
driver_name=dmspi
friends_group=arduino_spi
instance=2
role=master
baudrate=1000000
mode=0
bit_order=msb_first
nss_mode=soft
cs_active_level=low

A driver that consumes a friend can implement the callback as follows:

void dmdrvi_friend_changed(dmdrvi_context_t context,
                           const dmdrvi_friend_info_t* info)
{
    struct dmspi_context* spi = (struct dmspi_context*)context;

    if (info->group_name != NULL &&
        info->friend_role != NULL &&
        strcmp(info->friend_role, "chip_select") == 0) {
        if (info->state == dmdrvi_dev_state_ready && info->node_path != NULL) {
            /* Copy node_path into storage owned by the driver. */
            save_cs_path(spi, info->node_path);
        } else {
            clear_cs_path(spi);
        }
    }
}

The callback is optional. Drivers that do not need to discover or track other members of their group do not have to implement it.

Device Number System

The driver manages device numbering within its own namespace. Each driver decides whether it uses numbering and which numbering scheme to employ:

Numbering Schemes

  1. No Numbering (DMDRVI_NUM_NONE):

    • Driver doesn't use device numbers
    • Device file uses driver name only
    • Example: /dev/dmclk
  2. Major Number Only (DMDRVI_NUM_MAJOR):

    • Driver uses major number to identify channels
    • Device files named with major number suffix
    • Example: /dev/dmuart0, /dev/dmuart1
  3. Major and Minor Numbers (DMDRVI_NUM_MAJOR | DMDRVI_NUM_MINOR):

    • Driver uses both major and minor numbers
    • Creates directory for major number, files for each minor number
    • Example: /dev/dmspi0/0, /dev/dmspi0/1, /dev/dmspi1/0
  4. Alternative File Name (DMDRVI_NUM_ALT_NAME):

    • Driver provides a human-friendly alternative name for the device file (max 32 characters)
    • The alt_name field in dmdrvi_dev_num_t contains the alternative name
    • Can be combined with other flags
    • Example: /dev/my_sensor

Device Number Assignment

  • Major number: Identifies the device channel within a driver (e.g., UART0, UART1, SPI0)
  • Minor number: Identifies specific configuration for the same channel (e.g., different SPI speeds for different CS lines)
  • Each driver has its own namespace: Different drivers can use the same major/minor numbers independently

The driver assigns device numbers when creating a context. The filesystem layer receives the assigned numbers and creates appropriate device files.

Example device numbers:

Driver  Flags                    Major   Minor   Device Path
-------------------------------------------------------------
CLK     NUM_NONE                 -       -       /dev/dmclk
UART    NUM_MAJOR                0       -       /dev/dmuart0
UART    NUM_MAJOR                1       -       /dev/dmuart1
SPI     NUM_MAJOR|NUM_MINOR      0       0       /dev/dmspi0/0
SPI     NUM_MAJOR|NUM_MINOR      0       1       /dev/dmspi0/1
SPI     NUM_MAJOR|NUM_MINOR      1       0       /dev/dmspi1/0

Error Handling

Most functions return error codes compatible with standard errno values:

  • 0 - Success
  • Negative values - Error codes (implementation-specific)

For read/write operations:

  • Returns number of bytes transferred on success
  • Returns 0 or negative value on error

Memory Footprint

The driver interface module has a minimal footprint suitable for embedded systems. Actual memory usage depends on the underlying driver implementation.

License

MIT License - see LICENSE file for details

About

DMOD Driver Interface

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages