SANA-II Device Driver Reference¶
This is the working reference for people who write SANA-II network drivers and for people who write protocol stacks (or monitoring tools) that call them. It covers the extended IOSana2Req, every S2_* command with its number, the buffer-management callbacks and OpenDevice() tags, events, the two-level error model, the statistics structures, the on-line/off-line state machine, and Olaf Barthel's SANA-II R4 proposal, whose definitions are already in the NDK's <devices/sana2.h>. It ends with a checklist for driver writers.
The NDK 3.2 ships the specification as SANA+RoadshowTCP-IP/doc/SANA-II.pdf ("SANA-II Network Device Driver Specification", Randell Jesup and Kenneth Dyke, revised by Heinz Wrobel, Revision 3.1, 15 December 2002), the proposal as doc/sana2r4.html ("Extending the SANA-II network driver specification", last updated 02-Mar-2003), and the headers include/devices/sana2.h and sana2specialstats.h. For the background on R2 packet filtering and multiple protocol stack support, see SANA-II Networking Standard.
Application programmers do not call SANA-II drivers. The specification is explicit: network applications must use the API of the protocol stack, which on the Amiga is
bsdsocket.library(see Roadshow — bsdsocket TCP/IP Stack). SANA-II sits between the hardware and the stack.
Driver form and installation¶
A SANA-II driver is an ordinary Exec device (see Device Command Set Rules) with an extended I/O request and extra commands. The official location for drivers is the Networks drawer in DEVS:, for example DEVS:Networks/a2065.device; installers may have to create the drawer. The Roadshow Online/Offline commands show the usual lookup: try the name as given, and if OpenDevice() fails with IOERR_OPENFAIL and the name has no path part, retry with DEVS:Networks/ prepended.
The IOSana2Req structure¶
#define SANA2_MAX_ADDR_BITS (128)
#define SANA2_MAX_ADDR_BYTES ((SANA2_MAX_ADDR_BITS+7)/8) /* 16 */
struct IOSana2Req
{
struct IORequest ios2_Req;
ULONG ios2_WireError; /* wire type specific error */
ULONG ios2_PacketType; /* packet type */
UBYTE ios2_SrcAddr[SANA2_MAX_ADDR_BYTES]; /* source address */
UBYTE ios2_DstAddr[SANA2_MAX_ADDR_BYTES]; /* dest address */
ULONG ios2_DataLength; /* length of packet data */
APTR ios2_Data; /* packet data */
APTR ios2_StatData; /* statistics data pointer */
APTR ios2_BufferManagement; /* see SANA-II OpenDevice adoc */
};
| Field | Meaning |
|---|---|
ios2_Req |
Standard IORequest. io_Error holds 0, an IOERR_* code or an S2ERR_* code. |
ios2_WireError |
More specific S2WERR_* code when io_Error is set. S2_ONEVENT reuses it as the event mask. |
ios2_PacketType |
Packet type to read/write (e.g. 2048 for IP on Ethernet). |
ios2_SrcAddr |
Filled by the driver with the source hardware address on reads; the address to configure for S2_CONFIGINTERFACE. Left-justified bytes. |
ios2_DstAddr |
Destination address for writes; filled in on reads. Other commands reuse it (see the command table). |
ios2_DataLength |
Set by the caller to the space available; set by the driver to the packet data size as on the wire, without header/trailer. |
ios2_Data |
Pointer to an abstract buffer owned by the protocol stack. The driver must never examine or modify it directly. |
ios2_StatData |
Target for statistics commands; must be longword aligned. |
ios2_BufferManagement |
Before OpenDevice(): pointer to the buffer-management tag list. After: a driver-private "magic cookie" that must be copied into every request cloned from this one. |
Because the opener must copy the request returned by OpenDevice() for every additional asynchronous request, the cookie travels with each copy. The R4 command autodocs list ios2_BufferManagement as an input of every new command for this reason.
Addresses¶
Addresses are arrays of bytes with no meaning assigned by the standard. An address whose bit count is not a multiple of eight is padded at the end (a 10-bit address is stored as 98765432 10PPPPPP). S2_DEVICEQUERY reports the number of significant bits in AddrFieldSize. For drivers such as PPP the "hardware address" is really a negotiated protocol address, returned by S2_CONFIGINTERFACE/S2_GETSTATIONADDRESS.
Flags¶
io_Flags bit |
Value | Meaning |
|---|---|---|
SANA2IOB_RAW |
7 | Raw packet I/O: the whole data-link frame. |
SANA2IOB_BCAST |
6 | Received packet was a broadcast (set by the driver). |
SANA2IOB_MCAST |
5 | Received packet was a multicast (set by the driver). |
SANA2IOB_QUICK |
0 (IOB_QUICK) |
Quick I/O requested. |
OpenDevice() flags (the flags argument): SANA2OPB_MINE (bit 0, exclusive access) and SANA2OPB_PROM (bit 1, promiscuous mode). Promiscuous mode requires exclusive opening. The SANA2IOF_* and SANA2OPF_* masks are the matching 1 << bit values.
Opening a driver¶
error = OpenDevice(name, unit, (struct IORequest *)ios2, flags);
Before the call, the opener points ios2_BufferManagement at a tag list. The driver fails the open if a required callback is missing. If the open succeeds, the driver replaces the field with its cookie. A driver may also accept an open without any buffer-management tags, so that configuration and statistics tools can open it, and must treat a tag with a NULL value as not specified. It then fails, without crashing, any request that would need the missing callbacks. Roadshow's Online and Offline commands open drivers this way, with no tag list at all.
The specification also requires a driver to reject an open with a request that is too short, such as a plain IOStdReq, by checking mn_Length in the message. The R4 proposal reverses this for the open itself: a driver should accept a plain IOStdReq so that the New Style Device NSCMD_DEVICEQUERY works, and then reject every SANA-II command whose request is shorter than a struct IOSana2Req with IOERR_BADLENGTH.
Buffer management tags¶
A SANA-II driver has no internal buffers. It reads from and writes to the stack's abstract buffers only through callbacks the stack supplies. All tags are in <devices/sana2.h>, based on S2_Dummy = TAG_USER + 0xB0000:
| Tag | Value | Origin | Purpose |
|---|---|---|---|
S2_CopyToBuff |
S2_Dummy + 1 |
R2 | Required. Copy received data into the stack's buffer. |
S2_CopyFromBuff |
S2_Dummy + 2 |
R2 | Required. Copy outgoing data out of the stack's buffer. |
S2_PacketFilter |
S2_Dummy + 3 |
R2 | Optional struct Hook, called before S2_CopyToBuff. |
S2_CopyToBuff16 |
S2_Dummy + 4 |
R3 | Optional; same interface, 16-bit aligned word transfers only. |
S2_CopyFromBuff16 |
S2_Dummy + 5 |
R3 | As above, outgoing. |
S2_CopyToBuff32 |
S2_Dummy + 6 |
R3 | Optional; 32-bit aligned longword transfers only. |
S2_CopyFromBuff32 |
S2_Dummy + 7 |
R3 | As above, outgoing. |
S2_DMACopyToBuff32 |
S2_Dummy + 8 |
R3 | Optional DMA callback (32-bit alignment). |
S2_DMACopyFromBuff32 |
S2_Dummy + 9 |
R3 | As above, outgoing. |
S2_DMACopyToBuff64 |
S2_Dummy + 10 |
R4 proposal | DMA callback with 64-bit alignment (for PCI). |
S2_DMACopyFromBuff64 |
S2_Dummy + 11 |
R4 proposal | As above, outgoing. |
S2_Log |
S2_Dummy + 12 |
R4 proposal | struct Hook the driver uses for log messages. |
CopyToBuff / CopyFromBuff¶
success = CopyToBuff(to, from, n) success = CopyFromBuff(to, from, n)
D0 A0 A1 D0 D0 A0 A1 D0
BOOL CopyToBuff(VOID *abstract_to, VOID *contiguous_from, ULONG n);
BOOL CopyFromBuff(VOID *contiguous_to, VOID *abstract_from, ULONG n);
CopyToBuff prepares the abstract structure for n bytes and copies into it. If the structure cannot be prepared (no memory, for example) it returns FALSE. Any memory it needs should already be allocated before the call. Both functions must be callable from interrupts: no direct or indirect calls to system memory functions, no stack checking, and in C, __saveds or geta4() to set up the data base register.
The 16/32-bit and DMA variants¶
The R3 enhancements are advisory. The driver chooses which callback to use for any buffer at any time, and the stack cannot know in advance which one is used. A 16- or 32-bit callback has the same interface but may only use aligned 16- or 32-bit accesses for each data word. If the length is not a multiple of the word size, the last word may carry padding garbage in either direction.
A DMA callback is called with the cookie ios2_Data in A0 and returns in D0 either NULL (do not use DMA for this buffer) or the address of the real buffer. The buffer must be contiguous, readable or writable as the direction needs, aligned to 32 bits (64 bits for the R4 tags), and a multiple of 32 bits in size and at least ios2_DataLength long. The driver falls back to the CPU callbacks whenever it cannot use DMA.
PacketFilter¶
keep = PacketFilter(hook, ios2, data)
D0 A0 A2 A1
BOOL PacketFilter(struct Hook *, struct IOSana2Req *, APTR);
The driver calls it with the CMD_READ request set up as it would be on successful return (at least ios2_DataLength, ios2_SrcAddr, ios2_DstAddr) and data pointing at the contiguous packet data (without hardware headers unless the read is raw). Return TRUE to deliver the packet and FALSE to drop it. The interrupt-safety rules above apply.
S2_Log (R4 proposal)¶
A normal hook, invoked with utility.library/CallHookPkt() as log_hook_function(hook, NULL, struct S2LogMessage *) (A0/A2/A1). If present, the driver must use it instead of its own logging.
struct S2LogMessage
{
LONG s2lm_Size; /* >= 16 */
ULONG s2lm_Priority; /* S2LOG_Emergency (0) ... S2LOG_Debug (7) */
STRPTR s2lm_Name; /* source; may be NULL */
STRPTR s2lm_Message; /* never NULL, no line feeds */
};
Priorities: S2LOG_Emergency 0, S2LOG_Alert 1, S2LOG_Critical 2, S2LOG_Error 3, S2LOG_Warning 4, S2LOG_Notice 5, S2LOG_Information 6, S2LOG_Debug 7. The hook must not be called from interrupt code, must not Wait(), must not call dos.library, and may only assume enough stack for exec and utility calls. It is installed per unit at OpenDevice() time and stays until the request that installed it closes the device. The message string is valid only during the call, and messages should use the current locale where possible.
Command reference¶
Old-style commands start at S2_START = CMD_NONSTD (9). The "new style" R4 commands use NSD-compliant numbers from 0xC000.
| Command | Value | Inputs | Outputs / notes |
|---|---|---|---|
CMD_READ 1.3+ |
2 | ios2_PacketType, ios2_Data, flags RAW/QUICK |
Src/Dst address, ios2_DataLength, flags RAW/BCAST/MCAST. Keep several pending. |
CMD_WRITE 1.3+ |
3 | ios2_PacketType, ios2_DstAddr, ios2_DataLength, ios2_Data |
With RAW, ios2_Data holds a complete frame. |
CMD_FLUSH 1.3+ |
8 | — | Aborts queued (not active) requests with IOERR_ABORTED. |
CMD_INVALID, CMD_RESET, CMD_UPDATE, CMD_CLEAR, CMD_START, CMD_STOP |
0, 1, 4, 5, 7, 6 | — | Do not apply: return IOERR_NOCMD. S2_OFFLINE/S2_ONLINE take the place of stop/start. |
S2_DEVICEQUERY |
9 | ios2_StatData → Sana2DeviceQuery, SizeAvailable set |
Fills up to SizeAvailable bytes. |
S2_GETSTATIONADDRESS |
10 | — | ios2_SrcAddr = current address, ios2_DstAddr = factory address (if any). |
S2_CONFIGINTERFACE |
11 | ios2_SrcAddr = address to use |
Only once; returns the configured address in ios2_SrcAddr. The device does not listen before this. |
S2_ADDMULTICASTADDRESS |
14 | ios2_SrcAddr = multicast address |
Global for all openers; reference counted. |
S2_DELMULTICASTADDRESS |
15 | ios2_SrcAddr |
Error if not enabled. |
S2_MULTICAST |
16 | As CMD_WRITE |
Fails with S2WERR_BAD_MULTICAST if the address fails a sanity check. |
S2_BROADCAST |
17 | As CMD_WRITE, no address |
ios2_DstAddr is trash on return. |
S2_TRACKTYPE |
18 | ios2_PacketType |
Start (and zero) per-type statistics. |
S2_UNTRACKTYPE |
19 | ios2_PacketType |
Stop; the statistics are lost. |
S2_GETTYPESTATS |
20 | ios2_PacketType, ios2_StatData → Sana2PacketTypeStats |
Only while the type is tracked. |
S2_GETSPECIALSTATS |
21 | ios2_StatData → Sana2SpecialStatHeader, RecordCountMax set |
Wire-specific records. |
S2_GETGLOBALSTATS |
22 | ios2_StatData → Sana2DeviceStats |
|
S2_ONEVENT |
23 | ios2_WireError = event mask |
Returns with ios2_WireError = events that occurred. |
S2_READORPHAN |
24 | ios2_Data, flags RAW/QUICK |
Next packet that matched no pending CMD_READ. For tools, not protocols. |
S2_ONLINE |
25 | — | Back in service; re-initialises hardware and resets global/special stats. |
S2_OFFLINE |
26 | — | Aborts pending I/O with S2ERR_OUTOFSERVICE. |
S2_ADDMULTICASTADDRESSES |
0xC000 |
ios2_SrcAddr = lowest, ios2_DstAddr = highest |
Range version (1997, Holger Kruse). |
S2_DELMULTICASTADDRESSES |
0xC001 |
Same range | Must exactly match an earlier add. |
S2_GETPEERADDRESS |
0xC002 |
ios2_BufferManagement |
R4: ios2_SrcAddr = local, ios2_DstAddr = peer. |
S2_GETDNSADDRESS |
0xC003 |
ios2_BufferManagement |
R4: ios2_SrcAddr = primary DNS, ios2_DstAddr = secondary. |
S2_GETEXTENDEDGLOBALSTATS |
0xC004 |
ios2_StatData → Sana2ExtDeviceStats |
R4: 64-bit counters plus connection times. |
S2_CONNECT |
0xC005 |
ios2_Data → Sana2Connection |
R4: connect the link layer and go online. |
S2_DISCONNECT |
0xC006 |
ios2_Data → Sana2Connection |
R4: reverse S2_CONNECT. |
S2_SAMPLE_THROUGHPUT |
0xC007 |
ios2_StatData → Sana2ThroughputStats |
R4: stays queued until AbortIO(). |
S2_END (S2_START + 18 = 27) marks the end of the old-style range. Note the jump from S2_CONFIGINTERFACE (S2_START + 2) to S2_ADDMULTICASTADDRESS (S2_START + 5).
Multicast details¶
When a driver adds a multicast address it is usually added for every user of the unit, so received multicasts satisfy any matching read. Drivers keep an enable count per address and remove it only after as many deletes as adds. For S2_ADDMULTICASTADDRESSES, an address range is every valid multicast address with ios2_SrcAddr <= address <= ios2_DstAddr, compared as big-endian binary numbers. Unicast or broadcast addresses inside the range are not enabled. The command exists for receivers such as multicast routers that need very large ranges, so a driver must not implement it by looping over S2_ADDMULTICASTADDRESS. Ranges are not merged or split: a delete must match an add exactly, and you cannot delete a single-address range with the single-address command or the reverse.
Packet types¶
Packet types are longwords whose meaning depends on the wire, so stacks should let the user configure the device name, unit and packet types. On Ethernet, a requested type in the 802.3 range (below 1500) returns the next 802.3 frame whatever its inner type, so only one opener should read 802.3 packets. ARCNET drivers use the old-style software headers. The R4 proposal suggests packet type 31 for IP over PPP, and lets a driver treat 2048 as an alias for its IP type when that is unambiguous. The AmiTCP SDK's <net/sana2tags.h> lists the values in use: ETHERTYPE_IP 0x0800, ETHERTYPE_ARP 0x0806, ARCOTYPE_IP 240 / ARCOTYPE_ARP 241 (RFC 1051), ARCNTYPE_IP 212 / ARCNTYPE_ARP 213 / ARCNTYPE_RARP 214 (RFC 1201), PPPTYPE_IP 0x21.
Device query and hardware types¶
struct Sana2DeviceQuery
{
ULONG SizeAvailable; /* bytes available (set by caller) */
ULONG SizeSupplied; /* bytes supplied (set by driver) */
ULONG DevQueryFormat; /* type 0 */
ULONG DeviceLevel; /* level 0 */
UWORD AddrFieldSize; /* address size in bits */
ULONG MTU; /* maximum packet data size */
ULONG BPS; /* line rate (bits/sec) */
ULONG HardwareType; /* what the wire is */
ULONG RawMTU; /* R4: maximum raw packet data size */
};
RawMTU is the R4 addition: the number of bytes available for SANA2IOB_RAW reads and writes. For Ethernet it is 1514 (1500 + 14-byte header; the preamble and CRC are not under driver control). Drivers that do not support raw access set it to 0, and drivers that do not know the field fill the structure only up to HardwareType.
Gotcha: the R4 document reports that
a2065.devicefailsS2_DEVICEQUERYif the structure is larger than 30 bytes, which is the size withoutRawMTU. A stack that wantsRawMTUshould be prepared to repeat the query withSizeAvailable = 30.
S2WireType_* |
Value | S2WireType_* |
Value | |
|---|---|---|---|---|
Ethernet |
1 | AmokNet (floppy port) |
200 | |
IEEE802 |
6 | Liana (Village Tronic parallel) |
202 | |
Arcnet |
7 | PPP |
253 | |
LocalTalk |
11 | SLIP |
254 | |
DyLAN |
12 | CSLIP |
255 | |
PLIP |
420 |
Values for standard hardware follow RFC 1060. New hardware types must be registered.
For Ethernet (appendix C of the specification), packets carry 64–1500 data bytes, addresses are 6 bytes in transmission order with the multicast flag in the least significant bit of the first byte, and raw frames are 6 bytes destination, 6 bytes source, 2 bytes type, then data. The 4-byte CRC is generated and checked by hardware and never appears, even in raw packets. Drivers override any attempt to set an address other than the ROM address.
Configuration sequence¶
The specification's recommended start-up sequence for a protocol stack:
S2_DEVICEQUERYfor the MTU, address size and hardware type.S2_GETSTATIONADDRESSfor the factory address (ios2_DstAddr), and let the user override it.S2_CONFIGINTERFACEwith the chosen address inios2_SrcAddr. Always try it, even though another stack may already have configured the unit: expectS2WERR_IS_CONFIGUREDin that case.- Read back
ios2_SrcAddr, because some hardware (and PPP) assigns the address dynamically.
#include <exec/io.h>
#include <devices/sana2.h>
#include <proto/exec.h>
/* ios2 was opened successfully; port is its reply port. */
static BOOL configure_unit(struct IOSana2Req *ios2)
{
struct Sana2DeviceQuery dq;
dq.SizeAvailable = sizeof(dq);
ios2->ios2_Req.io_Command = S2_DEVICEQUERY;
ios2->ios2_StatData = &dq;
if (DoIO((struct IORequest *)ios2) != 0)
{
dq.SizeAvailable = 30; /* without RawMTU */
if (DoIO((struct IORequest *)ios2) != 0)
return FALSE;
}
ios2->ios2_Req.io_Command = S2_GETSTATIONADDRESS;
if (DoIO((struct IORequest *)ios2) != 0)
return FALSE;
/* Use the factory address as the configured address. */
CopyMem(ios2->ios2_DstAddr, ios2->ios2_SrcAddr, SANA2_MAX_ADDR_BYTES);
ios2->ios2_Req.io_Command = S2_CONFIGINTERFACE;
if (DoIO((struct IORequest *)ios2) != 0 &&
ios2->ios2_WireError != S2WERR_IS_CONFIGURED)
return FALSE;
return TRUE; /* ios2_SrcAddr now holds the address in use */
}
The R4 proposal adds that a driver which cannot have its address set may silently accept S2_CONFIGINTERFACE, even more than once. A driver that must go online before it knows the real MTU or statistics should report "safe" defaults, and never zero sizes or intervals that could make a client allocate zero bytes or divide by zero.
Errors¶
Always check ios2_Req.io_Error first. Negative values are the standard <exec/errors.h> codes (IOERR_OPENFAIL −1 … IOERR_SELFTEST −7), and positive values are SANA-II codes. When io_Error is set, ios2_WireError may hold more detail, and drivers are required to fill it in when an applicable code exists.
S2ERR_* (io_Error) |
Value | Meaning |
|---|---|---|
S2ERR_NO_ERROR |
0 | Success |
S2ERR_NO_RESOURCES |
1 | Resource allocation failure |
S2ERR_BAD_ARGUMENT |
3 | Bad argument |
S2ERR_BAD_STATE |
4 | Command inappropriate for current state |
S2ERR_BAD_ADDRESS |
5 | Bad address |
S2ERR_MTU_EXCEEDED |
6 | Write data too large |
S2ERR_NOT_SUPPORTED |
8 | Valid command the hardware cannot support |
S2ERR_SOFTWARE |
9 | Software error |
S2ERR_OUTOFSERVICE |
10 | Driver is offline |
S2ERR_TX_FAILURE |
11 | Transmission attempt failed (header only) |
S2WERR_* (ios2_WireError) |
Value | Meaning |
|---|---|---|
GENERIC_ERROR |
0 | No specific info |
NOT_CONFIGURED |
1 | Unit not configured |
UNIT_ONLINE |
2 | Command requires the unit to be offline |
UNIT_OFFLINE |
3 | Command requires the unit to be online |
ALREADY_TRACKED |
4 | Protocol already tracked |
NOT_TRACKED |
5 | Protocol not tracked |
BUFF_ERROR |
6 | Buffer management callback failed |
SRC_ADDRESS |
7 | Source address problem |
DST_ADDRESS |
8 | Destination address problem |
BAD_BROADCAST |
9 | Broadcast problem |
BAD_MULTICAST |
10 | Multicast problem |
MULTICAST_FULL |
11 | Multicast address list full |
BAD_EVENT |
12 | Unsupported event class |
BAD_STATDATA |
13 | ios2_StatData failed a sanity check |
| (14) | — | There is no wire error 14 |
IS_CONFIGURED |
15 | Attempt to configure twice |
NULL_POINTER |
16 | NULL pointer argument (with S2ERR_BAD_ARGUMENT) |
TOO_MANY_RETRIES |
17 | Transmission failed, too many retries |
RCVREL_HDW_ERR |
18 | Driver-fixable hardware error |
UNIT_DISCONNECTED |
19 | R4: link layer not connected |
UNIT_CONNECTED |
20 | R4: link layer already connected |
INVALID_OPTION |
21 | R4: S2_CONNECT option rejected |
MISSING_OPTION |
22 | R4: mandatory option missing |
AUTHENTICATION_FAILED |
23 | R4: could not log in |
The header also defines S2WERR_TOO_MANY_RETIRES as an alias of S2WERR_TOO_MANY_RETRIES ("for our dyslexic friends").
IOERR_NOCMD versus S2ERR_NOT_SUPPORTED (clarified in R4): commands the driver will never handle, decided when the driver is designed, return IOERR_NOCMD. Commands it implements but cannot carry out under current run-time conditions return S2ERR_NOT_SUPPORTED. A driver for a non-broadcast medium must not implement S2_BROADCAST, and one without multicast must not implement S2_MULTICAST. Their absence from the NSCMD_DEVICEQUERY command list then tells clients what the driver can do.
Events and the on-line state¶
S2_OFFLINE exists so hardware can be tested on a live system. While a unit is offline, every pending and new request that touches the hardware returns S2ERR_OUTOFSERVICE, and packets for it are lost. Any client may take a unit offline, so every stack must handle S2ERR_OUTOFSERVICE on any request, typically by queuing S2_ONEVENT for S2EVENT_ONLINE.
S2EVENT_* |
Value | Meaning |
|---|---|---|
ERROR |
1<<0 |
Any error |
TX |
1<<1 |
Transmit error |
RX |
1<<2 |
Receive error |
ONLINE |
1<<3 |
Unit went online (returns at once if already online) |
OFFLINE |
1<<4 |
Unit went offline (returns at once if already offline) |
BUFF |
1<<5 |
Buffer-management callback error |
HARDWARE |
1<<6 |
Hardware error |
SOFTWARE |
1<<7 |
Software error |
CONFIGCHANGED |
1<<8 |
R4: client-visible configuration changed |
CONNECT |
1<<9 |
R4: session opened |
DISCONNECT |
1<<10 |
R4: session closed |
All pending requests for an event return when it happens, and every event class covering the condition fires. A buffer-callback failure during receive, for example, completes S2EVENT_ERROR, S2EVENT_RX and S2EVENT_BUFF waiters. Unsupported events return S2ERR_NOT_SUPPORTED with S2WERR_BAD_EVENT. Errors caused by a malformed request should not normally trigger events.
S2EVENT_CONFIGCHANGED fires when anything clients can query changes: AddrFieldSize, MTU or BPS from S2_DEVICEQUERY, the station address, Reconfigurations/LastStart, or the peer and DNS addresses. It does not say what changed, so clients keep copies and compare. It is not meant for counter updates.
The Roadshow Offline command is a compact example: it queues the event before issuing the command, then waits on the event, a timer and Ctrl-C.
/* NetEventRequest is a copy of the opened NetRequest. */
NetEventRequest->ios2_Req.io_Command = S2_ONEVENT;
NetEventRequest->ios2_WireError = S2EVENT_OFFLINE;
SendIO((struct IORequest *)NetEventRequest);
NetRequest->ios2_Req.io_Command = S2_OFFLINE;
error = DoIO((struct IORequest *)NetRequest);
/* ... Wait(SIGBREAKF_CTRL_C | time_mask | net_mask) ...
success when the event request returns with io_Error == 0 and
(ios2_WireError & S2EVENT_OFFLINE) != 0 */
Statistics¶
| Structure | Command | Fields |
|---|---|---|
Sana2PacketTypeStats |
S2_GETTYPESTATS |
PacketsSent, PacketsReceived, BytesSent, BytesReceived, PacketsDropped (received while no read of that type was pending) |
Sana2DeviceStats |
S2_GETGLOBALSTATS |
PacketsReceived, PacketsSent, BadData, Overruns, Unused, UnknownTypesReceived, Reconfigurations, struct timeval LastStart |
Sana2SpecialStatHeader + Sana2SpecialStatRecord[] |
S2_GETSPECIALSTATS |
Header RecordCountMax (set by caller), RecordCountSupplied; records Type, Count, String |
Sana2ExtDeviceStats |
S2_GETEXTENDEDGLOBALSTATS |
See below |
Gotcha: the C header's
Sana2DeviceStatshas anUnusedlongword betweenOverrunsandUnknownTypesReceivedthat the structure listings in the specification text omit. Always compile against<devices/sana2.h>and never copy the layout from the document.
Drivers must implement type tracking and keep every applicable global statistic. Special statistic IDs put the wire type in the upper 16 bits and the statistic number in the lower 16 bits. <devices/sana2specialstats.h> defines three for Ethernet:
| ID | Value | Counts |
|---|---|---|
S2SS_ETHERNET_BADMULTICAST |
0x00010000 |
Packets that passed the hardware multicast filter but were not in the multicast table |
S2SS_ETHERNET_RETRIES |
0x00010001 |
Transmission retries |
S2SS_ETHERNET_FIFO_UNDERRUNS |
0x00010002 |
Host did not feed the card fast enough |
R4 extended statistics¶
typedef struct { ULONG s2q_High; ULONG s2q_Low; } S2QUAD; /* 64-bit big endian */
struct Sana2ExtDeviceStats
{
ULONG s2xds_Length; /* caller: sizeof(); must be >= 8 */
ULONG s2xds_Actual; /* driver: bytes filled, <= Length */
S2QUAD s2xds_PacketsReceived;
S2QUAD s2xds_PacketsSent;
S2QUAD s2xds_BadData;
S2QUAD s2xds_Overruns;
S2QUAD s2xds_UnknownTypesReceived;
S2QUAD s2xds_Reconfigurations;
struct timeval s2xds_LastStart;
struct timeval s2xds_LastConnected;
struct timeval s2xds_LastDisconnected;
struct timeval s2xds_TimeConnected;
};
A driver must reject s2xds_Length < 8 with IOERR_BADLENGTH. s2xds_TimeConnected is zero while disconnected and never zero while connected (use tv_secs = 0, tv_micro = 1 if necessary). After a disconnect, the previous connection time is LastDisconnected − LastConnected. These fields exist for cost accounting on dial-up links.
S2_SAMPLE_THROUGHPUT installs a Sana2ThroughputStats (s2ts_Length/s2ts_Actual, optional s2ts_NotifyTask/s2ts_NotifyMask for Signal(), s2ts_StartTime, s2ts_EndTime, 64-bit s2ts_BytesSent, s2ts_BytesReceived, s2ts_Updates) that the driver updates in real time until the request is recalled with AbortIO(). It must be sent with SendIO() or BeginIO(). A driver that does not know the command returns it at once with IOERR_NOCMD. The proposal recommends periodic polling rather than notification, because a busy link would signal the task almost constantly.
The R4 connection model: S2_CONNECT / S2_DISCONNECT¶
These commands are for software drivers (PPP, PPPoE) that need a link-layer device and dial-up parameters. The client fills a Sana2Connection and passes it in ios2_Data:
struct Sana2Connection
{
ULONG s2c_Size; /* >= 84, else IOERR_BADLENGTH */
struct MinList s2c_Options; /* list of Sana2ConnectionOption */
struct Hook s2c_ErrorHook; /* (hook, NULL, STRPTR message) */
struct Hook s2c_ConnectHook; /* BOOL (hook, NULL, Sana2ConnectionMessage *) */
struct Hook s2c_DisconnectHook; /* VOID (hook, NULL, Sana2ConnectionMessage *) */
STRPTR s2c_Login; /* NULL: login and password empty */
STRPTR s2c_Password;
};
struct Sana2ConnectionOption
{
struct MinNode s2co_MinNode;
STRPTR s2co_Name; /* e.g. "ppp.async.device" */
STRPTR s2co_Value; /* numbers are text too */
};
struct Sana2ConnectionMessage
{
ULONG s2cm_Size; /* >= 20 */
struct Sana2Connection * s2cm_Connection;
struct IORequest * s2cm_Request[2]; /* two distinct link-layer requests */
LONG s2cm_RequestType; /* NSDEVTYPE_SERIAL, NSDEVTYPE_SANA2 ... */
};
How the driver processes S2_CONNECT:
- Unknown options are ignored. A malformed option makes the driver call the error hook and return
S2ERR_BAD_ARGUMENT/S2WERR_INVALID_OPTION. A missing mandatory option returnsS2ERR_BAD_ARGUMENT/S2WERR_MISSING_OPTION. - The driver opens the link layer. It may then call
s2c_ConnectHook(asynchronous PPP needs it to dial the modem; PPPoE does not). The hook gets two I/O requests for the link device. It may clone them, but nothing may be pending when it returns. On failure it must call the error hook before returningFALSE, after which the driver calls the disconnect hook and undoes its set-up. - On success the driver sends
S2EVENT_CONNECTandS2EVENT_ONLINE. - The
Sana2Connectionand its option nodes must stay valid untilS2_DISCONNECT, because the driver may need the disconnect hook if the line drops on its own.
Hooks run in a task context of unknown priority, never from interrupts, with stack only for exec and utility calls. S2_CONNECT on a connected unit fails with S2ERR_BAD_STATE / S2WERR_UNIT_CONNECTED, and S2_DISCONNECT on a disconnected unit with S2WERR_UNIT_DISCONNECTED. A driver must ignore an S2_DISCONNECT that follows a failed or missing S2_CONNECT. The two commands do not nest.
Relation to on-line/off-line: S2_CONNECT implies S2_ONLINE, and S2_DISCONNECT implies S2_OFFLINE. If the driver lists S2_CONNECT in NSCMD_DEVICEQUERY, then S2_ONLINE no longer implies connecting. After a connect, S2_OFFLINE releases the link layer but keeps the session, and S2_ONLINE takes the link back. Without a successful S2_CONNECT, S2_ONLINE/S2_OFFLINE fail with S2ERR_BAD_STATE / S2WERR_UNIT_DISCONNECTED.
Reserved option names are case-insensitive and hierarchical: ppp.async.* (device, unit, speed, buffersize, checkcarrier, rtscts, shared, nullmodem, eof, readrequests, writerequests, accm, pfc, aacfc, vjhc, ignorefcs, initialize, dial, dialtimeout, hangup), ppp.ethernet.* (device, unit, raw, bypass, readpackets, writepackets, service, ac, connecttimeout), general ppp.* (idletimeout, peeridletimeout, localaddress, remoteaddress, dns1address, dns2address, maxfail, maxterm, maxconfig, timeout, mtu, rejectpap, sendid, pap.timeout, pap.retry, dummyremoteaddress), and logfile, logoptions. Private options use an x- prefix on the last component (e.g. ppp.ethernet.x-connecttimeout).
S2_GETPEERADDRESS and S2_GETDNSADDRESS must return addresses of the size advertised by S2_DEVICEQUERY (no IPv6 address from a driver that advertised 32 bits). Unknown addresses are returned as zeroes, and two zero addresses are a legal answer.
The PPP/ sample sources in the NDK (ppp_dialer.c, ppp_connector.c, ppp_sample.c) use these extensions. Their README warns that they are prototypes and that the extensions were only suggested and may change.
Driver writer's checklist¶
- [ ] Install in
DEVS:Networks/. - [ ] Accept an
OpenDevice()with no buffer-management tags (for tools), treatNULLtag data as absent, and fail affected requests gracefully. - [ ] Accept a plain
IOStdReqforNSCMD_DEVICEQUERY. Reject every SANA-II command whosemn_Lengthis shorter thanstruct IOSana2ReqwithIOERR_BADLENGTH. - [ ] Implement
NSCMD_DEVICEQUERY(NSD 1.6 or newer) and list only the commands you really support (see New Style Device Commands; SANA-II devices reportNSDEVTYPE_SANA2, 7). - [ ] Never touch
ios2_Datadirectly; use the callbacks. Call them in a way that satisfies their interrupt-safe contract. - [ ] Multiple protocol stack support: keep a separate read queue per opener (the cookie is the natural place) and give a copy of each matching packet to every opener with a pending read. The specification says this feature "should never be left out".
- [ ] Call
S2_PacketFilter(if supplied) beforeS2_CopyToBuff. - [ ] Packets with no pending read go to
S2_READORPHANrequests, otherwise they are dropped and counted. - [ ] Reference-count multicast addresses and ranges. Implement
S2_ADDMULTICASTADDRESSESnatively. - [ ] Fill in
ios2_WireErrorwhenever an applicable code exists. UseIOERR_NOCMDversusS2ERR_NOT_SUPPORTEDas described above. - [ ] Do not check whether a
CMD_WRITEdestination is local; that is the protocol's job. - [ ] Implement
S2_ONLINE/S2_OFFLINE/S2_ONEVENT. Return all I/O withS2ERR_OUTOFSERVICEwhile offline. Reset global and special statistics onS2_ONLINE. - [ ] Support
RawMTUinS2_DEVICEQUERYand honourSizeAvailable; never write past it. - [ ] Report safe defaults for values only known after going online, and signal
S2EVENT_CONFIGCHANGEDwhen they change. - [ ] If an
S2_Loghook is supplied, log through it only, from task context.
See Also¶
- SANA-II Networking Standard
- Roadshow — bsdsocket TCP/IP Stack
- Roadshow Commands and usergroup.library
- netinfo.device and sana2.lib
- New Style Device Commands
- Device Command Set Rules
- utility.library
Sources: NDK 3.2 R4 SANA+RoadshowTCP-IP/doc/SANA-II.pdf, SANA+RoadshowTCP-IP/doc/sana2r4.html, SANA+RoadshowTCP-IP/include/devices/sana2.h, SANA+RoadshowTCP-IP/include/devices/sana2specialstats.h, SANA+RoadshowTCP-IP/source_code/Roadshow/Offline.c, SANA+RoadshowTCP-IP/source_code/PPP/README, Include_H/exec/io.h, Include_H/exec/errors.h; AmiTCP SDK 4.3 netinclude/net/sana2tags.h, netinclude/devices/sana2.h Updated: 2026-11-13