agentleFS
gerphgerph/riscos-agent-skills/skills/writing-cmodules/SKILL.md

writing-cmodules

Skill4 starsChanged 7 months agoMITView on GitHub

Development notes and procedures for working with RISC OS modules. Describes the layout of the project, how to build and tests, common patterns with SWIs and Vectors. Use when creating RISC OS modules, creating SWI implementations, claiming vectors, or writing service handlers.

We split this skill into the rules it gives an agent

32 rules. About 0 are common, 14 are distinctive, and 18 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈14

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 18

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Always use the writing-c skill when using this skill, as it includes fundamentals which are not stated here.

    line 13
  2. Distinctive:

    When the user or the existing code structure points at a specific layer for the work, start there and exhaust that path before changing …

    line 16
  3. Distinctive:

    Do not let a runtime symptom in a test environment pull you into unrelated module scaffolding too early.

    line 23
  4. Distinctive:

    If the user requests that a module be built and does not provide a definition of the interfaces, ask if they would like you design the …

    line 32
  5. Very specific:

    When asked to design a new module, use the designing-modules skill to aid in getting the interfaces correct, *before* beginning the …

    line 33
  6. Distinctive:

    If the service handler or SWI handler is not required, it can be omitted, and the CMHG entry commented out.

    line 61
  7. Distinctive:

    When porting an assembler module, preserve the original service-call register contract explicitly.

    line 70
  8. Distinctive:

    When editing the CMHG file or Makefiles, retain the commented lines to help guide future authors.

    line 77
  9. Distinctive:

    If finalisation returns an error, the kernel may call it again, so it must be repeatable and safe after partial cleanup.

    line 84
  10. Very specific:

    Read references/finalisation-and-errors.md for detailed finaliser patterns, error-block examples, and the o.modhead linker pitfall.

    line 87
  11. Very specific:

    Read the generated h/modhead header before changing a handler signature.

    line 93
  12. Distinctive:

    Prefer dedicated SWI handlers over one central dispatcher unless every SWI genuinely shares the same register contract.

    line 96
  13. Distinctive:

    Use XSWIs internally so errors are returned explicitly instead of raising.

    line 99
  14. Very specific:

    Read references/swi-interface.md for swi-decoding-table syntax, naming pitfalls, handler signatures, and the XSWI convention.

    line 101
  15. Very specific:

    Use ordinary C globals and heap allocation for module state.

    line 112
  16. Very specific:

    If the original source used conditional assembly switches, preserve them as named #if or #ifdef options instead of collapsing the behaviour …

    line 116
  17. Very specific:

    Read references/finalisation-and-errors.md for the private-word trap, CMHG-generated error blocks, and time-format notes.

    line 120
  18. Very specific:

    If a new header is added, the makefile should be updated to add a dependency in the form h.header.

    line 138
  19. Very specific:

    If a new header file like h/os is created, it would be included with #include "os.h" and the dependency in the makefile is h.os:

    line 146
  20. Distinctive:

    No number suffix for 26-bit (obsolescent).

    line 168
  21. Very specific:

    Use a small BASIC smoke test first, then make it repeatable through a project test target.

    line 177
  22. Very specific:

    Read references/testing.md for example SYS calls, riscos-build-run invocations, 64-bit limitations, and the leaf-name collision pitfall.

    line 181
  23. Distinctive:

    Use conditional compilation for debug output:

    line 188
  24. Very specific:

    When debug is no longer required, comment out the #define DEBUG line.

    line 204
  25. Very specific:

    Only clean the build if there are problems with the invocation that are not expected (riscos-amu clean).

    line 215
  26. Very specific:

    Use riscos-project create-ci to create these files for the current project.

    line 227
  27. Distinctive:

    Implementation: Use the addresses of CMHG-generated generic veneers, not the raw C functions.

    line 243
  28. Distinctive:

    Key Tip: Always pass explicit 0s for intermediate registers (R0-R6) to ensure your target register (R7) is correctly populated.

    line 278
  29. Distinctive:

    If your SWI returns a pointer to a block of data:

    line 281
  30. Very specific:

    Missing COMPONENT: If COMPONENT is not defined in the Makefile, the build may fail or produce oddly named binaries.

    line 293
  31. Very specific:

    RAM vs ROM: Ensure you are building the ram target for testing.

    line 294
  32. Very specific:

    File Suffixes: Always ensure your Makefile handles dependencies on generated headers: ${OZDIR}.module: h.modhead

    line 295

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

C modules

Agent guidance

Always use the writing-c skill when using this skill, as it includes fundamentals which are not stated here.

When the user or the existing code structure points at a specific layer for the work, start there and exhaust that path before changing outer plumbing. For example, if the request is to implement logic in a helper such as c/conversion, do that first and keep c/module, CMHG veneers, SWI claiming, and registration code unchanged unless the target layer clearly cannot satisfy the request.

Do not let a runtime symptom in a test environment pull you into unrelated module scaffolding too early. A missing feature result from a test should first be checked against the intended implementation layer, not assumed to require changes to SWI registration, claiming, or veneer wiring.

Module Structure

New modules

  • If the user requests that a module be built and does not provide a definition of the interfaces, ask if they would like you design the module before launching into implementation.
  • When asked to design a new module, use the designing-modules skill to aid in getting the interfaces correct, before beginning the implementation.

Creating a template project:

  • To create the base files, use riscos-project create --name <project> --type cmodule --skeleton This will create the files in the current directory.
  • Then build the project to confirm that it works before making changes.

Core Files

A RISC OS module requires three essential components:

  1. CMHG file (cmhg/*) - Module definition describing:
    • Module title and help string (which should start with a capital letter, using Pascal Case).
    • SWI chunk base number
    • Entry points (initialisation, finalisation, SWI handler, veneers and others)
    • SWI decoding table (names of exported SWIs)
    • Error blocks
    • Command definitions
  2. Module interface (c/module) - C code implementing:
    • Mod_Init() - Called when module is loaded
    • Mod_Final() - Called when module is unloaded
    • Mod_SWI() - Dispatches SWI calls to appropriate functions
    • Mod_Service() - Dispatches Services to appropriate functions

If the service handler or SWI handler is not required, it can be omitted, and the CMHG entry commented out.

CMHG service handlers have this C signature:

void Mod_Service(int service, _kernel_swi_regs *r, void *pw);

Declare the service list in cmhg/modhead with service-call-handler:. For cache invalidation based on display or palette state, common services include Service_ModeChanging, Service_SwitchingOutputToSprite, and Service_DisplayChanged. When porting an assembler module, preserve the original service-call register contract explicitly. If the original service path consumes values from R0-R7, thread those values through the C interface deliberately instead of discarding them in a simplified wrapper.

  1. Makefile (Makefile,fe1) - Build configuration using AMU (RISC OS make)
    • See the using-makefiles skill if you need more information on this.

For more information about the CMHG file format, a blank template can be generated for reference with riscos-cmunge -blank blank. Do not commit the template file read in this way - always change the module header. See the using-cmhg skill for more information.

When editing the CMHG file or Makefiles, retain the commented lines to help guide future authors.

Module finalisation

Mod_Final() must release resources in reverse order of creation, clear global state, and only report an error when shutdown genuinely has to be prevented. If finalisation returns an error, the kernel may call it again, so it must be repeatable and safe after partial cleanup.

Read references/finalisation-and-errors.md for detailed finaliser patterns, error-block examples, and the o.modhead linker pitfall.

SWI interface

  • SWIs are numbered relative to the chunk base declared in cmhg/modhead.
  • Read the generated h/modhead header before changing a handler signature.
  • Decode register values into typed locals in c/module; do not pass raw _kernel_swi_regs deeper into the implementation.
  • Prefer dedicated SWI handlers over one central dispatcher unless every SWI genuinely shares the same register contract.
  • Unknown SWIs returned from Mod_SWI() must return error_BAD_SWI.
  • Use XSWIs internally so errors are returned explicitly instead of raising.

Read references/swi-interface.md for swi-decoding-table syntax, naming pitfalls, handler signatures, and the XSWI convention.

Module restrictions

  • Floating point should not be used in modules - if it is required, then refer to the using-libasm skill for details of the fpsvc functions.
  • exit(), assert() and about() must never be used in modules.

Memory and private word

Use ordinary C globals and heap allocation for module state. Pass pw back to interfaces that require a private word, but never dereference it or overwrite it when using the Shared C Library.

If the original source used conditional assembly switches, preserve them as named #if or #ifdef options instead of collapsing the behaviour into one path.

Read references/finalisation-and-errors.md for the private-word trap, CMHG-generated error blocks, and time-format notes.

Build System

AMU Makefiles

The build uses AMU (RISC OS make) with ,fe1 filetype:

OBJS = o.modhead \
       o.module \
       o.hardware

include CModule

If a new header is added, the makefile should be updated to add a dependency in the form h.header. For example, if the file c/hardware was created and included the module header with #include "modhead.h", a line should be added beside the additional dependencies like:

${OZDIR}.hardware: h.modhead

If a new header file like h/os is created, it would be included with #include "os.h" and the dependency in the makefile is h.os:

${OZDIR}.hardware: h.os

More information about the use of makefiles can be obtained with the using-makefiles

Build Targets

  • riscos-amu or riscos-amu ram - Build absolutes or RAM module
  • riscos-amu rom - Build ROM module
  • riscos-amu BUILD26=1 - Build 26-bit compatible version
  • riscos-amu BUILD64=1 - Build 64-bit version
  • riscos-amu export - Export headers/libraries
  • riscos-amu clean - Remove build artifacts

Output Directories

Suffix z means 'for modules'; no z means 'for application'. Suffix 32 for 32-bit. Suffix 64 for 64-bit. No number suffix for 26-bit (obsolescent).

  • o - object directory.
  • aif - AIF, application executable.
  • rm - modules (RAM).
  • aof - AOF files (ROM).

Testing

Use a small BASIC smoke test first, then make it repeatable through a project test target. Keep the host filename and the guest leaf name distinct so the module and the BASIC file cannot collide inside riscos-build-run.

Read references/testing.md for example SYS calls, riscos-build-run invocations, 64-bit limitations, and the leaf-name collision pitfall.

Debugging

Debug Output

Use conditional compilation for debug output:

#define DEBUG

#ifdef DEBUG
#define dprintf if (1) printf
#else
#define dprintf if (0) printf
#endif

dprintf("Debug: value=%d\n", value);

Note: printf output goes to the debug stream, visible in build logs.

When debug is no longer required, comment out the #define DEBUG line. Uncomment if it is needed again. NEVER remove the dprintf(...) function calls.

Build Verification

Always verify builds:

riscos-amu
riscos-build-run rm32 --command "*RMLoad rm32.<module>"

Only clean the build if there are problems with the invocation that are not expected (riscos-amu clean).

64-bit builds can be tested with a load, but cannot use BBC BASIC:

riscos-amu BUILD64=1
riscos-build-run --arch aarch64 rm64 --command "*RMLoad rm64.<module>"

CI/CD

CI files can be created automatically and will generate .robuild.yaml which contains RISC OS commands to run. Use riscos-project create-ci to create these files for the current project.

Common features

Claiming vectors

Vector claims are usually done in the module initialisation code. They must be released on module finalisation. See the skill using-cmhg for details on claiming vectors.

Generic veneers

For non-claimable entry points, like driver registrations, the CMHG generic-veneer should be used. Many modules (CDFSDriver, SCSI, etc.) require passing entry point addresses to the OS or other drivers via a Driver Information Block. Other interfaces, like timed events (OS_CallAfter, OS_CallEvery) have a similar entry point.

Implementation: Use the addresses of CMHG-generated generic veneers, not the raw C functions.

/* In Mod_Register */
CDFS_DIB *dib = (CDFS_DIB *)r->r[0];
dib->msf_to_lba_fn = (int)Entry_MSFToLBA_Veneer; // Address of the veneer

or

_swix(OS_CallAfter, _INR(0,2), 10, Entry_Tick_Veneer, pw); // Veneer address.

This ensures the C environment (relocation, static base) is correctly initialized when the function is called from external assembly or the OS. More information on the patterns used for generic geneers can be found in the skill using-cmhg.

Module Testing Strategies

Verifying SWIs in BASIC

A simple BASIC script is the most effective way to test a new module. Use the skill using-bbcbasic if you need to work with BASIC.

Example: Testing a complex SWI (CD_DriveStatus)

REM Passing a pointer in R7
DIM control_block% 20
control_block%!0 = device%
control_block%!4 = card%
...
SYS "CD_DriveStatus", 0, 0, 0, 0, 0, 0, 0, control_block% TO status%
PRINT "Status is: "; status%

Key Tip: Always pass explicit 0s for intermediate registers (R0-R6) to ensure your target register (R7) is correctly populated.

Verifying Version Blocks

If your SWI returns a pointer to a block of data:

SYS "CD_Version" TO v%
PRINT "Version word: "; v%!0
PRINT "Version string: "; FNstring0(v%+4)

DEFFNstring0(p%):LOCAL s$:SYS "OS_IntOn",p% TO s$:=s$

Build and Linker Pitfalls

  • Missing COMPONENT: If COMPONENT is not defined in the Makefile, the build may fail or produce oddly named binaries.
  • RAM vs ROM: Ensure you are building the ram target for testing.
  • File Suffixes: Always ensure your Makefile handles dependencies on generated headers: ${OZDIR}.module: h.modhead

Resources

  • RISC OS PRM - Introduction to SWIs
  • CMHG Documentation
  • Manager modules may need to announce that they are starting up and shutting down. Usually services are issued for these events. The module announcements pattern can be found here references/module-announcements.md.
  • Licenses and authorship may be announced with the module acknowledgements service. Adding this service can be achieved by following the module acknowledgements pattern.
  • Modules which need to replace OS functionality may claim those OS_* SWI calls through OS_ClaimOSSWI. More information on this can be found at claiming-os-swis.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.