amiga-bootcamp/06_exec_os/io_requests.md
Ilia Sharin 59929047d4 exec_os: enrich all stubs to bootcamp-quality reference articles
Complete rewrite of 14 exec_os articles from stubs to comprehensive
deep-dive technical references with architecture diagrams, pitfalls,
and best practices.

New: multitasking.md (scheduler, IPC, memory safety, real-world scenarios)
Enriched: exec_base, tasks_processes, library_system, library_vectors,
interrupts, exceptions_traps, memory_management, message_ports, signals,
semaphores, io_requests, lists_nodes, resident_modules

Updated indexes: 06_exec_os/README.md, root README.md
2026-04-23 17:55:31 -04:00

399 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[← Home](../README.md) · [Exec Kernel](README.md)
# IO Requests — IORequest, DoIO, SendIO, CheckIO, AbortIO
## Overview
AmigaOS device I/O uses a **message-based** asynchronous protocol. Every device operation is described by an `IORequest` structure sent to a device's command port. The device processes it (synchronously or in the background) and replies when done. This model unifies all hardware — disks, serial, parallel, audio, timers, network — under a single consistent API.
The IO system is built directly on top of [message ports](message_ports.md). An `IORequest` contains an embedded `Message`, and device I/O is literally message passing between your task and the device's task.
---
## Architecture
```mermaid
sequenceDiagram
participant App as Application
participant Port as Reply Port
participant Dev as Device Task
App->>Dev: OpenDevice("trackdisk.device", 0, ior, 0)
Note over Dev: Fills ior->io_Device, io_Unit
App->>Dev: SendIO(ior) [async]
Note over Dev: Device processes command
Dev->>Port: ReplyMsg (PutMsg to reply port)
App->>Port: WaitIO(ior) or Wait(portSig)
App->>App: Check ior->io_Error
App->>Dev: CloseDevice(ior)
```
### The IORequest Lifecycle
```
1. CreateIORequest / CreateStdIO → allocate request
2. OpenDevice → bind to device/unit
3. Fill io_Command, io_Data, io_Length, io_Offset
4. DoIO (sync) or SendIO (async) → submit
5. Check io_Error, io_Actual
6. Repeat 35 as needed
7. CloseDevice → unbind
8. DeleteIORequest / DeleteStdIO → free
```
---
## Structures
```c
/* exec/io.h — NDK39 */
struct IORequest {
struct Message io_Message; /* embedded Message (has MsgPort reply port) */
struct Device *io_Device; /* filled by OpenDevice */
struct Unit *io_Unit; /* filled by OpenDevice */
UWORD io_Command; /* CMD_READ, CMD_WRITE, TD_FORMAT, ... */
UBYTE io_Flags; /* IOF_QUICK = attempt synchronous fast path */
BYTE io_Error; /* result: 0 = success, negative = error code */
};
struct IOStdReq { /* extended version with data fields */
struct IORequest io_Request;
ULONG io_Actual; /* actual bytes transferred */
ULONG io_Length; /* requested byte count */
APTR io_Data; /* data buffer pointer */
ULONG io_Offset; /* byte offset (for random-access devices) */
};
```
### IORequest Field Reference
| Field | Set By | Description |
|---|---|---|
| `io_Message.mn_ReplyPort` | App | Reply port — device sends reply here when done |
| `io_Device` | `OpenDevice` | Pointer to device base — do not modify |
| `io_Unit` | `OpenDevice` | Pointer to device unit — do not modify |
| `io_Command` | App | Operation to perform (`CMD_READ`, `CMD_WRITE`, etc.) |
| `io_Flags` | App | `IOF_QUICK` for synchronous fast path attempt |
| `io_Error` | Device | 0 = success, negative = error (set after completion) |
| `io_Actual` | Device | Bytes actually transferred |
| `io_Length` | App | Bytes to transfer |
| `io_Data` | App | Buffer pointer |
| `io_Offset` | App | Device-specific offset |
---
## Standard Command Codes
```c
/* exec/io.h */
#define CMD_INVALID 0 /* not a valid command */
#define CMD_RESET 1 /* reset the device/unit to initial state */
#define CMD_READ 2 /* read io_Length bytes into io_Data from io_Offset */
#define CMD_WRITE 3 /* write io_Length bytes from io_Data at io_Offset */
#define CMD_UPDATE 4 /* flush write cache to media */
#define CMD_CLEAR 5 /* discard device read buffers */
#define CMD_STOP 6 /* suspend device operation */
#define CMD_START 7 /* resume device operation */
#define CMD_FLUSH 8 /* abort all pending requests */
#define CMD_NONSTD 9 /* first device-specific command number */
```
Device-specific commands start at `CMD_NONSTD` (9):
| Device | Command | Number | Description |
|---|---|---|---|
| trackdisk.device | `TD_FORMAT` | 10 | Low-level format a track |
| trackdisk.device | `TD_MOTOR` | 11 | Motor on/off control |
| trackdisk.device | `TD_CHANGESTATE` | 14 | Check disk insertion |
| serial.device | `SDCMD_SETPARAMS` | 10 | Set baud rate, stop bits |
| timer.device | `TR_ADDREQUEST` | 9 | Schedule a timer event |
| audio.device | `ADCMD_ALLOCATE` | 32 | Allocate audio channels |
---
## Error Codes (`io_Error`)
```c
/* exec/errors.h — NDK39 */
#define IOERR_OPENFAIL -1 /* device/unit could not be opened */
#define IOERR_ABORTED -2 /* request was aborted via AbortIO */
#define IOERR_NOCMD -3 /* unknown command */
#define IOERR_BADLENGTH -4 /* io_Length invalid for this command */
#define IOERR_BADADDRESS -5 /* io_Data not aligned or accessible */
#define IOERR_UNITBUSY -6 /* unit in use, cannot complete */
#define IOERR_SELFTEST -7 /* hardware self-test failed */
```
Device-specific error codes are positive values defined in each device's header.
---
## Opening and Closing Devices
### Opening
```c
/* Create a reply port and IO request */
struct MsgPort *replyPort = CreateMsgPort();
struct IOStdReq *ior = (struct IOStdReq *)
CreateIORequest(replyPort, sizeof(struct IOStdReq));
/* Open the device */
BYTE err = OpenDevice("trackdisk.device", 0, /* unit 0 = DF0: */
(struct IORequest *)ior, 0); /* flags = 0 */
if (err != 0)
{
Printf("OpenDevice failed: %ld\n", err);
DeleteIORequest(ior);
DeleteMsgPort(replyPort);
return RETURN_FAIL;
}
```
### Closing
```c
/* Close and free — MUST abort pending requests first */
CloseDevice((struct IORequest *)ior);
DeleteIORequest((struct IORequest *)ior);
DeleteMsgPort(replyPort);
```
---
## Synchronous I/O: DoIO
Blocks the calling task until the device completes the request:
```c
ior->io_Command = CMD_READ;
ior->io_Data = buffer;
ior->io_Length = 512;
ior->io_Offset = 0;
LONG err = DoIO((struct IORequest *)ior); /* LVO -456 */
if (err == 0)
{
/* Success — io_Actual contains bytes read */
Printf("Read %ld bytes\n", ior->io_Actual);
}
else
{
Printf("Error: %ld\n", ior->io_Error);
}
```
### How DoIO Works Internally
1. Sets `IOF_QUICK` in `io_Flags`
2. Calls the device's `BeginIO` vector
3. If the device completes synchronously (kept `IOF_QUICK` set), returns immediately
4. If the device cleared `IOF_QUICK` (will complete async), calls `WaitIO` to block
---
## Asynchronous I/O: SendIO + WaitIO
```c
/* Queue the request — returns immediately: */
ior->io_Command = CMD_READ;
ior->io_Data = buffer;
ior->io_Length = 65536;
ior->io_Offset = 0;
SendIO((struct IORequest *)ior); /* LVO -462 */
/* Do other work while device operates... */
UpdateUI();
ProcessInput();
/* Block until this specific request completes: */
WaitIO((struct IORequest *)ior); /* LVO -474 */
BYTE err = ior->io_Error;
```
### Combining Async IO with Event Loop
```c
ULONG ioSig = 1L << replyPort->mp_SigBit;
ULONG idcmpSig = 1L << win->UserPort->mp_SigBit;
SendIO((struct IORequest *)ior);
BOOL ioDone = FALSE;
while (!ioDone)
{
ULONG sigs = Wait(ioSig | idcmpSig | SIGBREAKF_CTRL_C);
if (sigs & idcmpSig) HandleGUI();
if (sigs & ioSig)
{
if (CheckIO((struct IORequest *)ior))
{
WaitIO((struct IORequest *)ior); /* Must still call to clean up */
ioDone = TRUE;
}
}
if (sigs & SIGBREAKF_CTRL_C)
{
AbortIO((struct IORequest *)ior);
WaitIO((struct IORequest *)ior);
break;
}
}
```
---
## CheckIO — Non-Blocking Status Check
```c
/* Returns non-NULL if request is complete: */
struct IORequest *result = CheckIO((struct IORequest *)ior); /* LVO -468 */
if (result)
{
WaitIO((struct IORequest *)ior); /* Must still call to dequeue reply */
/* io_Error is valid now */
}
else
{
/* Still in progress */
}
```
> **Warning**: Even after `CheckIO()` returns non-NULL, you **must** call `WaitIO()` to remove the reply message from the port. Failing to do so leaves a stale message in the queue.
---
## AbortIO — Cancel a Pending Request
```c
AbortIO((struct IORequest *)ior); /* LVO -480 — request cancellation */
WaitIO((struct IORequest *)ior); /* Wait for acknowledgment */
/* io_Error will be IOERR_ABORTED (-2) */
```
> **Important**: `AbortIO` is a **request** — the device may not abort immediately. Always follow with `WaitIO` to ensure the request is fully complete before reusing or freeing the IORequest.
---
## The IOF_QUICK Fast Path
When `IOF_QUICK` is set in `io_Flags`, the device MAY complete the request synchronously within `BeginIO()` — without queuing, signaling, or replying via message. This avoids the overhead of message passing for trivial operations.
```c
/* Fast-path attempt */
ior->io_Flags = IOF_QUICK;
BeginIO((struct IORequest *)ior);
if (ior->io_Flags & IOF_QUICK)
{
/* Completed synchronously — no WaitIO needed */
}
else
{
/* Device cleared IOF_QUICK — must wait */
WaitIO((struct IORequest *)ior);
}
```
`DoIO` uses this mechanism internally.
---
## Practical: Timer Device Usage
```c
/* Open timer.device */
struct MsgPort *timerPort = CreateMsgPort();
struct timerequest *tr = (struct timerequest *)
CreateIORequest(timerPort, sizeof(struct timerequest));
OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)tr, 0);
/* Set a 2-second timer */
tr->tr_node.io_Command = TR_ADDREQUEST;
tr->tr_time.tv_secs = 2;
tr->tr_time.tv_micro = 0;
SendIO((struct IORequest *)tr);
/* Wait for timer or user interrupt */
ULONG timerSig = 1L << timerPort->mp_SigBit;
ULONG sigs = Wait(timerSig | SIGBREAKF_CTRL_C);
if (sigs & timerSig)
{
WaitIO((struct IORequest *)tr);
Printf("Timer expired!\n");
}
else
{
AbortIO((struct IORequest *)tr);
WaitIO((struct IORequest *)tr);
}
/* Cleanup */
CloseDevice((struct IORequest *)tr);
DeleteIORequest((struct IORequest *)tr);
DeleteMsgPort(timerPort);
```
---
## Pitfalls
### 1. Reusing IORequest While In Flight
```c
/* BUG — request is still being processed by device */
SendIO(ior);
ior->io_Command = CMD_WRITE; /* WRONG — device is reading from this struct */
SendIO(ior); /* Double-send = corruption */
```
### 2. Forgetting WaitIO After CheckIO
```c
/* BUG — reply message left in port */
if (CheckIO(ior)) { /* done! */ }
/* But WaitIO was never called — stale message in reply port */
/* Next Wait() on this port returns immediately with garbage */
```
### 3. Freeing IORequest Without CloseDevice
```c
/* BUG — device still references this unit */
DeleteIORequest(ior); /* Device's internal pointers now dangle */
```
---
## Best Practices
1. **Always pair** `OpenDevice` / `CloseDevice` and `CreateIORequest` / `DeleteIORequest`
2. **Always call `WaitIO`** after `CheckIO` returns non-NULL
3. **Always call `WaitIO`** after `AbortIO`
4. **Use `SendIO`** for async operations — `DoIO` blocks your task completely
5. **Combine IO with event loop** using `Wait()` on the reply port's signal
6. **Don't reuse IORequest** until the previous operation completes
7. **Check `io_Error`** after every operation — errors are common with removable media
8. **Use `UNIT_VBLANK`** for timer.device unless you need microsecond precision (use `UNIT_MICROHZ`)
---
## References
- NDK39: `exec/io.h`, `exec/errors.h`, `exec/devices.h`
- ADCD 2.1: `OpenDevice`, `CloseDevice`, `DoIO`, `SendIO`, `WaitIO`, `CheckIO`, `AbortIO`, `CreateIORequest`, `DeleteIORequest`
- See also: [Message Ports](message_ports.md) — IORequest is built on message passing
- See also: `10_devices/` — per-device command codes and structures
- *Amiga ROM Kernel Reference Manual: Exec* — I/O requests chapter