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.device fails S2_DEVICEQUERY if the structure is larger than 30 bytes, which is the size without RawMTU. A stack that wants RawMTU should be prepared to repeat the query with SizeAvailable = 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:

  1. S2_DEVICEQUERY for the MTU, address size and hardware type.
  2. S2_GETSTATIONADDRESS for the factory address (ios2_DstAddr), and let the user override it.
  3. S2_CONFIGINTERFACE with the chosen address in ios2_SrcAddr. Always try it, even though another stack may already have configured the unit: expect S2WERR_IS_CONFIGURED in that case.
  4. 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 Sana2DeviceStats has an Unused longword between Overruns and UnknownTypesReceived that 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:

  1. 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 returns S2ERR_BAD_ARGUMENT / S2WERR_MISSING_OPTION.
  2. 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 returning FALSE, after which the driver calls the disconnect hook and undoes its set-up.
  3. On success the driver sends S2EVENT_CONNECT and S2EVENT_ONLINE.
  4. The Sana2Connection and its option nodes must stay valid until S2_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), treat NULL tag data as absent, and fail affected requests gracefully.
  • [ ] Accept a plain IOStdReq for NSCMD_DEVICEQUERY. Reject every SANA-II command whose mn_Length is shorter than struct IOSana2Req with IOERR_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 report NSDEVTYPE_SANA2, 7).
  • [ ] Never touch ios2_Data directly; 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) before S2_CopyToBuff.
  • [ ] Packets with no pending read go to S2_READORPHAN requests, otherwise they are dropped and counted.
  • [ ] Reference-count multicast addresses and ranges. Implement S2_ADDMULTICASTADDRESSES natively.
  • [ ] Fill in ios2_WireError whenever an applicable code exists. Use IOERR_NOCMD versus S2ERR_NOT_SUPPORTED as described above.
  • [ ] Do not check whether a CMD_WRITE destination is local; that is the protocol's job.
  • [ ] Implement S2_ONLINE/S2_OFFLINE/S2_ONEVENT. Return all I/O with S2ERR_OUTOFSERVICE while offline. Reset global and special statistics on S2_ONLINE.
  • [ ] Support RawMTU in S2_DEVICEQUERY and honour SizeAvailable; never write past it.
  • [ ] Report safe defaults for values only known after going online, and signal S2EVENT_CONFIGCHANGED when they change.
  • [ ] If an S2_Log hook is supplied, log through it only, from task context.

See Also


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