amiga-bootcamp/16_driver_development/device_driver_basics.md
2026-04-26 14:46:18 -04:00

174 lines
4.2 KiB
Markdown

[← Home](../README.md) · [Driver Development](README.md)
# Exec Device Driver Framework
## Overview
An Amiga **device** is a shared library with additional I/O semantics. Devices handle hardware or protocol communication via `IORequest` messages. Understanding this framework is essential before writing any specific driver (network, graphics, audio).
---
## Device vs Library
| Feature | Library | Device |
|---|---|---|
| Base structure | `struct Library` | `struct Device` (extends Library) |
| Entry points | `Open`, `Close`, `Expunge`, LVO functions | `Open`, `Close`, `Expunge` + `BeginIO`, `AbortIO` |
| Communication | Direct function calls | `IORequest` messages via `DoIO`/`SendIO` |
| Concurrency | Caller's task context | Can have own task/interrupt context |
---
## Minimal Device Structure
```c
/* mydevice.h */
struct MyDevBase {
struct Device md_Device; /* MUST be first */
struct Library *md_SysBase;
struct Library *md_DOSBase;
/* ... device-specific state ... */
};
```
---
## Device Function Table
Every device must provide exactly these entry points in its function vector:
```c
/* Function table (same as library, plus BeginIO/AbortIO): */
static const APTR funcTable[] = {
(APTR) DevOpen, /* -6 (standard library Open) */
(APTR) DevClose, /* -12 (standard library Close) */
(APTR) DevExpunge, /* -18 (standard library Expunge) */
(APTR) DevReserved, /* -24 (reserved, must return 0) */
(APTR) DevBeginIO, /* -30 (start I/O operation) */
(APTR) DevAbortIO, /* -36 (abort pending I/O) */
(APTR) -1 /* end marker */
};
```
---
## DevOpen
```c
LONG DevOpen(struct IORequest *ioreq, ULONG unit, ULONG flags,
struct MyDevBase *base)
{
/* Initialize per-unit state if needed */
struct MyUnit *u = &base->md_Units[unit];
ioreq->io_Device = (struct Device *)base;
ioreq->io_Unit = (struct Unit *)u;
ioreq->io_Error = 0;
base->md_Device.dd_Library.lib_OpenCnt++;
u->mu_OpenCnt++;
return 0; /* success */
}
```
---
## DevBeginIO — The Core
```c
void DevBeginIO(struct IORequest *ioreq, struct MyDevBase *base)
{
struct IOStdReq *ios = (struct IOStdReq *)ioreq;
ios->io_Error = 0;
switch (ios->io_Command) {
case CMD_READ:
/* Handle synchronously or queue for async */
if (!(ios->io_Flags & IOF_QUICK)) {
/* Queue request — reply later via ReplyMsg */
AddTail(&unit->mu_ReadQueue, &ios->io_Message.mn_Node);
ios->io_Flags &= ~IOF_QUICK;
return; /* do NOT ReplyMsg yet */
}
/* ... do sync read ... */
break;
case CMD_WRITE:
/* ... */
break;
default:
ios->io_Error = IOERR_NOCMD;
break;
}
/* Complete the request: */
if (!(ios->io_Flags & IOF_QUICK)) {
ReplyMsg(&ios->io_Message);
}
}
```
---
## DevAbortIO
```c
LONG DevAbortIO(struct IORequest *ioreq, struct MyDevBase *base)
{
/* Remove from pending queue if found */
Forbid();
Remove(&ioreq->io_Message.mn_Node);
Permit();
ioreq->io_Error = IOERR_ABORTED;
ReplyMsg(&ioreq->io_Message);
return 0;
}
```
---
## IOF_QUICK — Synchronous Fast Path
The `IOF_QUICK` flag is critical:
- Caller sets `IOF_QUICK` in `ioreq->io_Flags`
- If device completes immediately, it leaves `IOF_QUICK` set → caller knows it's done (no `WaitIO` needed)
- If device queues the request, it **clears** `IOF_QUICK` → caller must `WaitIO`
```c
/* Caller pattern (DoIO does this internally): */
ioreq->io_Flags |= IOF_QUICK;
BeginIO(ioreq);
if (!(ioreq->io_Flags & IOF_QUICK))
WaitIO(ioreq);
```
---
## RomTag for Device
```c
static struct Resident romtag = {
RTC_MATCHWORD,
&romtag,
&endskip,
RTF_AUTOINIT,
1, /* version */
NT_DEVICE, /* ← not NT_LIBRARY */
0, /* priority */
"mydevice.device",
"mydevice 1.0",
&initTable
};
```
---
## References
- NDK39: `exec/devices.h`, `exec/io.h`
- ADCD 2.1: `DoIO`, `SendIO`, `WaitIO`, `AbortIO`
- RKRM: *Amiga ROM Kernel Reference Manual: Devices* — device driver chapter