DCI 4 networkingNetwork Device Drivers

Overview

1 History

See 'Document information' at the end of this document.

2 Unresolved Questions

There are no unresolved questions (at the moment).

3 Introduction

This document describes version 4 of the Device Control Interface ("DCI"), an interface between protocol modules and device driver modules in the RISC OS networking system.

3.1 Objectives

This new version is needed to overcome deficiencies/errors in the existing interface. The specific points it aims to address are:

  • Full support for multiple protocol modules in a single system: older versions of the DCI notionally provided this support, but there were implicit features of the design which made support for more than one protocol module at any one time difficult.

  • Support for multicast and promiscuous frame reception. Promiscuous reception is when an Ethernet interface receives all frames, regardless of their destination address; multicasting is a method used to transmit a single frame to multiple hosts simultaneously, it can be viewed as a form of limited broadcast: individual hosts on a network can choose whether or not they wish to receive multicast frames {The glossary of Internet terms in "Internetworking with TCP/IP" by Douglas Comer defines multicasting as "A technique that allows copies of a single [frame] to be passed to a selected subset of all possible destinations . . . . broadcast is a special form of multicast in which the subset of machines to receive a copy of a [frame] consists of the entire set."

  • The need for improved data transfer rates compared to previous DCI versions, and the formal adoption of techniques already used to improve data throughput.

Backwards compatabilty

The radical changes and new features being introduced with this new version of the DCI, along with the inbuilt lack of flexibility in previous versions, combine to make any attempt at backwards compatibility impossible. With this lack of compatibility, care has been taken to ensure that there is no overlap between DCI 4 compliant modules, and other modules loaded on the same machine that implement earlier versions of the DCI, specifically:

  1. In both old and new DCI versions, it is the responsibility of the protocol module to initialise the interface between itself and a device driver after either actively or passively learning of the device driver's presence; DCI 4 has replaced the active (Service_FindNetworkDriver) and passive (Service_NetworkDriverStatus) service calls used by protocol modules with Service_EnumerateNetworkDrivers and Service_DCIDriverStatus respectively.
  2. To prevent old device drivers getting confused by Service_ProtocolStatus, which no longer provides a sensible (as far as old DCI versions are concerned) value in R2, this service call has been made obsolete, and has been replaced by Service_DCIProtocolStatus.

3.2 Principles of operation

The principle behind the interface is that protocol modules register a list of desirable frame types with network device drivers. When a device driver receives a frame, it passes it along to the protocol module that expressed an interest in the frame's type. Transmission is much simpler --- the protocol module passes the frame to be transmitted to the appropriate device driver. In both cases it is generally the recipient of the frame that assumes responsibility for the memory containing the frame (i.e. the protocol module for received packets, the device driver for transmitted packets).

Identifying Device Drivers

Device drivers are always identified by their "Driver Information Block", described on page 4. These Driver Information Blocks are used in a number of service calls, and are also given to protocol modules along with received frames.

There are fields within a Driver Information Block which uniquely identify each interface, but to prevent protocol modules having to make laborious (i.e. strcmp()) comparisons of these fields, device drivers should maintain a single, static, Driver Information Block for each interface it controls. In this way, protocol modules need only compare the address of Driver Information Blocks to identify an interface.

This scheme means that any use of the *RMTidy RISC OS command will kill any network stack on the machine --- this is not a great problem, since anyone who uses rmtidy in a modern RISC OS system is asking for all the trouble that they are about to receive. However, if a device driver module is re-initialised (via rmreinit), then the address of its Driver Information Block will change, therefore any protocol module's handler for the Service_DCIDriverStatus service call (page 6) cannot compare addresses, but must fall back to comparing those fields which uniquely identify an interface, i.e. dib_name & dib_unit.

3.3 Device Driver considerations

Important points for device driver writers to note are:

  1. The DCI interface is optimised in various ways for Ethernet device drivers, specifically:

    1. Physical network addresses are 48-bit quantities.
    2. Protocol modules identify the physical network frames they wish to receive by the type of the frame.

    (For example, the Internet module claims frame types 0x800 (IP), 0x806 (ARP), and 0x8035 (RevARP). This is a 16-bit value transmitted as part of the Ethernet header.)

    Drivers for other types of network hardware will need to emulate an Ethernet driver at this interface by mapping "virtual Ethernet" values onto the real values meaningful to the network hardware.

  2. At startup, driver modules must set the variable Inet$EtherType to the textual name of the controlled physical interface (e.g. "en", "ea"), with a suffix of `0'. This is for backwards compatibility with versions of Acorn's TCP/ IP Protocol Suite software already in the field. The textual name is the same string as the field dib_name in the Driver Information Block (see page 4 for a description of Driver Information Blocks). Note that this field variable reflects the last driver initialised, i.e. a driver will always set this field, regardless of whether or not it has been set previously.

4 Service Calls

As explained in the section on backwards compatibility ( Section 3), the service calls defined in earlier versions of the DCI are all now obsolete. To summarise, these calls are

  1. Service_ProtocolStatus
  2. Service_FindNetworkDriver
  3. Service_NetworkDriverStatus

The new service calls defined in DCI 4 are

  1. Service_EnumerateNetworkDrivers
  2. Service_DCIDriverStatus
  3. Service_DCIFrameTypeFree
  4. Service_DCIProtocolStatus

Note that the old, unnamed, service call 0x41200, which was never part of any formal DCI specification, but which used to be issued during finalisation of the Internet module, has now been officially replaced by Service_DCIProtocolStatus.

4.1 Data Structures

Some user applications need to associate device drivers with the physical location of the network hardware, i.e. with which "slot" the hardware occupies. In order to support complex networking cards (e.g. one card with multiple, independent, interfaces), the concept of a slot is overloaded with a minor device number; the interpretation of this minor device number is device driver dependent.

Using C, a slot can be expressed as

struct slot
{
    unsigned int slotid:8,
    minor:8,
    pcmciaslot:5, /* must be zero if not a PCMCIA virtual slot */
    mbz:11; /* must be zero */
}

In this, and all other C code fragments, the standard Norcroft RISC OS compiler is assumed - with this example, this means that bitfields start at the least significant end of a word, i.e. slotid is bits 0-7, minor bits 8-15, and so on.

Bit(s)NameMeaning
0-7slotid

8 bits: Physical location of the device

8-15minor

8 bits: Minor field

16-20pcmciaslot

5 bits: PCMCIA virtual slot, or 0 for non-PCMIA devices

21-31mbzReserved, must be zero

Device drivers are free to interpret the minor field as they wish, but a typical use would be to discriminate multiple units on a single physical card. The pcmciaslot field is used to differentiate between cards in different PCMCIA slots (unfortunately, PCMCIA also uses the word "slot" to refer to the physical connection to a card). this field only has any significance when slotid is a PCMCIA virtual slot (see immediately below for a description of virtual slots).

As well as the physical expansion card slots, which now number from 0-8 with the introduction of the latest Acorn machines, there are also a number of "virtual" slots, i.e. network interfaces which don't use hardware in an expansion card slot. The list of physical and virtual slots can be summarised as

ValueMeaning
0-7Physical expansion card slots
8Risc PC network position
16-31PCI slots
128Parallel port
129Serial port (e.g. PPP)
130Econet socket
131PCMCIA cards

Note:- there is only one PCMCIA virtual slot, this one virtual slot refers to PCMCIA hardware which may contain more than one physical PCMCIA slot; the pcmciaslot field within the slot number can be used by the device driver to differentiate between physical PCMCIA slots.

Driver Information Blocks

Device drivers identify themselves via a Driver Information Block, which can be expressed in C syntax as

struct dib
{
    unsigned int dib_swibase;
    char *dib_name;
    unsigned int dib_unit;
    unsigned char *dib_address;
    char *dib_module;
    char *dib_location;
    struct slot dib_slot;
    unsigned int dib_inquire;
};

The fields within this structure are:

OffsetFieldContents
0dib_swibaseThe base of the device driver's allocated SWI chunk.
4dib_nameA pointer to a short textual name unique to the driver (e.g. "en", "ppp"), and a terminating NULL.
8dib_unitThe unit number.
12dib_addressA pointer to a 6-byte character array which contains the hardware address of the interface.
16dib_moduleA pointer to a string containing the title of the driver module (e.g. "Ether3").
20dib_locationA pointer to a string which attempts to describe the physical location of the interface. A typical string would be somewhere between 8 and 40 characters long and would be of the form "Network Expansion Slot", or "Expansion Slot 0, port #1" etc..
24dib_slotThe slot number for this unit.
28dib_inquireA copy of the flags returned from the Inquire SWI (section 5.3).

Note that there is a subtle, but important, distinction between this definition of a Driver Information Block and its definition in previous versions of the DCI: the new definition has one Driver Information Block per unit, rather than one per device driver; if a device driver controls several units, then it must provide one struct dib per unit.

Units: a single device driver may control more than one physical network interface, either by driving multiple network cards, or by driving multiple interfaces on a single card. The driver is responsible for allocating a number to each interface under its control --- the first interface being unit 0, the second interface being unit 1, and so on. Any particular network connection can then be uniquely identified by its driver name and unit number, e.g. en0, ea2. If an interface is found to be faulty during any hardware check its driver may perform, the interface must still be assigned a unit number, and must still appear in the enumerated list of device drivers.

Chained Driver Information blocks

The results from the Service_EnumerateNetworkDrivers service call are chained together into a linked list of Driver Information Blocks. The C structure used for this linked list is

struct chaindib
{
    struct chaindib *chd_next;
    struct dib *chd_dib;
};

Just in case the fields within this structure are not self-evident, they are:

OffsetFieldContents
0chd_nextA pointer to the next entry in the linked list. The last entry in the list contains a NULL pointer.
4chd_dibA pointer to the Driver Information Block for this entry in the linked list.
Protocol Information Blocks

In much the same way that device drivers are identified by their Driver Information Block, older versions of the DCI used to contain a Protocol Information Block which identified individual protocol modules. This Protocol Information Block is not needed in DCI 4, and has been removed.

4.2 Obsolete services

As already mentioned in section 4, the old, unnamed, service call 0x41200 will not be issued by DCI 4 compliant versions of the Internet module when it is terminating.

4.3 Device Driver Initialisation

When a device driver module initialises, it is expected to issue a Service_DCIDriverStatus service call to announce that it is starting; at the time this startup call is issued, the module must also be capable of handling SWIs raised by any protocol module interested in the device driver. Unfortunately, the RISC OS kernel does not recognise a module's SWIs until after its initialisation routine has returned, which means that driver modules must take explicit steps to allow SWIs to be caught before issuing the service call, i.e. they must either:

  1. Install a handler on the unknown SWI software vector ("UKSWIV"), and check all unknown SWIs for an appropriate chunk number.
  2. Setup a callback handler in the initialisation routine, and then issue the service call from within this callback handler.

Service_EnumerateNetworkDriversService Call &9B
List all active network drivers in the system
R0=pointer to head of linked list of device drivers
R1=&9B (reason code)
R0=pointer to new head of linked list
R1 - R9preserved

This service call is used to obtain a list of all active network device drivers in the system. When the service call is issued, R0 is a NULL pointer; upon receipt of this call, a network device driver should chain Driver Information Blocks to the head of the list, one for each logical interface the driver controls. Section 4.1 describes the struct chaindib used to hold the linked list of Driver Information Blocks.

This service call should never be claimed.

Note: Struct chaindibs are transient objects: they should be allocated from RMA by the device drivers, and freed back into the RMA by the protocol module which issued the service call. The Driver Information Blocks referenced by the struct chaindibs must be static data, as explained in section 3.2.

Service_DCIDriverStatusService Call &9D
Announce initialisation and finalisation of driver
R0=pointer to Driver Information Block describing this driver
R1=&9D (reason code)
R2=Driver status:
ValueMeaning
0Starting
1Terminating
R3=DCI version supported × 100
R0 - R9preserved

Service_DCIDriverStatus is issued by a network driver module during its initialisation (R2 = 0), and finalisation (R2 = 1) calls. If a network device driver controls multiple logical interfaces, then a separate service call must be issued for each interface the driver is responsible for.

Upon receipt of this service call from a driver that is starting up (i.e. R2 = 0), a protocol module should add the driver

to its list of known device drivers. If a service call is received from a driver that is terminating, the protocol module should scan its list of known device drivers for a Driver Information Block matching the one addressed by R0, removing it from the list if a match is found. A Driver Information Block is uniquely identified by its dib_name and dib_unit fields, therefore a comparison of these two fields is sufficient to prove a match.

The supported DCI version passed in R3 is in the same format as described for the DCIVersion SWI (Section 5.3), i.e. 405 decimal for this version.

When this call is issued while starting, device drivers should be able to receive SWIs raised by protocol modules; this means that the service call cannot be directly issued from a driver's initialisation routine --- see section 4.3 for an explanation of this feature, along with ways to overcome it.

When this call is issued during finalisation, protocol modules should not expect to be able to issue SWIs, therefore no special action to allow this is required of the driver module issuing the service call.

This service call should never be claimed.

Service_DCIFrameTypeFreeService Call &9E
Announce that a frame type has been freed by a driver
R0=pointer to Driver Information Block describing this driver
R1=&9E (reason code)
R2=frame type being released
R3=address level of former claim
R4=error level of former claim
R0preserved
R1=0 to claim the call, or preserved to pass it on.
R2 - R9preserved

This service call is issued by a device driver when a protocol module releases a claim it formerly had on a frame type (i.e. when the frame type becomes free for claiming by a different protocol module). This release may have been either explicit (the protocol module called the SWI DCIDriver_Filter SWI), or implicit (the protocol module issued a Service_DCIProtocolStatus service call).

If a protocol module wishes to claim the newly relinquished frame type for itself, it should use the Filter SWI to do so, and then claim the service call by setting R1 to 0.

Note: the concepts of frame types and the various filtering levels available are explained fully in section 6.2.

Service_DCIProtocolStatusService Call &9F
Announce initialisation and finalisation of protocol
R0=Protocol module's private word pointer
R1=&9F (reason code)
R2=Protocol status:
ValueMeaning
0Starting
1Terminating
R3=DCI version supported × 100
R4=Pointer to protocol module's title string
R0 - R9preserved

Service_DCIProtocolStatus is issued by a protocol module during its initialisation (R2 = 0), and finalisation (R2 = 1) calls. The private word pointer in R0 is the same as that supplied by the protocol module in the SWI DCIDriver_Filter SWI (see section 5.3).

The supported DCI version passed in R3 is in the same format as described for the SWI DCIDriver_DCIVersion SWI, i.e. 405 decimal for this version.

The title string pointed to by R4 should be identical to the title string in the protocol module's header. This string is not used anywhere else in the DCI --- it is intended for use by modules that rely on the protocol module, but which do not communicate with it via the DCI; these modules need to have the name of significant protocol modules built into them.

As with device drivers issuing Service_DCIDriverStatus, protocol modules should already be capable of handling any SWIs at the time they issue this service call to announce that they are starting; the techniques described in section 4.3 are as equally valid for protocol modules as they are for device drivers.

When terminating, the protocol module which issued this service call must be prepared to handle receive events for all frame types it has not explicitly relinquished until the service call returns; once the call has returned, device drivers should have deleted all references to the protocol module which issued the service call. If necessary, device drivers may enable interrupts while processing the service call, but they should return with the interrupt state preserved.

Device drivers must never claim this service call.

5 Device Driver SWIs

All network device drivers must provide a SWI call interface which protocol modules can use to

to/from a device driver. Obviously, each device driver will have its own, unique, SWI chunk, so a protocol module must use the dib_swibase field from the Driver Information Block (see section 4.1) to determine the base of a driver's SWI chunk.

The SWI calls that a device driver must supply (and their offsets) are

SWI offsetSWI name
0DCIVersion
1Inquire
2GetNetworkMTU
3SetNetworkMTU
4Transmit
5Filter
6Stats
7MulticastRequest
Re-entrancy

All device driver SWIs are potentially re-entrant, i.e. the protocol modules are not expected to take any explicit action to prevent re-entrance. If re-entrancy is undesirable during the processing of a SWI, then the device driver should take explicit steps to prevent this, i.e. by disabling interrupts.

In order to minimise interrupt latency within the machine as a whole, device drivers should take steps to minimise the length of time during which interrupts are disabled.

Byte Sex

All data passed between the protocol modules and device drivers use the host byte sex, i.e. little-endian, whereas all data transmitted over the wire are (obviously) in network, or big-endian, byte sex. Device drivers are responsible for converting data to the appropriate sex.

5.1 Errors

All the SWI descriptions given later in this section ignore what will happen if the SWI needs to return an error. Obviously, there will be circumstances in which it is necessary to return an error from a SWI call, and the standard RISC OS mechanism is used, i.e. the device driver will set the V flag, and return with R0 pointing to an error block. Apart from the unavoidable corruption of R0, all other registers which are declared as being preserved by the SWI are still preserved, even when an error is returned.

Error numbers usually equate to Unix error numbers, as defined in the standard header file "errno.h"; these numbers are always less than 128, and are converted into offsets within the standard error block that has been defined for DCI 4 and Internet. This error block starts at &20E00, for example, the error EINVAL (invalid argument, defined as 22, &16) would be returned as error number &20E16. There are certain circumstances (e.g., the SWI DCIDriver_Transmit SWI indicating that transmission is blocked) where an appropriate Unix error number does not exist --- in this situation, a custom error number is defined specifically for this one error condition.

Standardised Errors

In an attempt to force some consistency, this sub-section defines some of the errors which various SWIs, and the circumstances in which these errors may be returned. This is not meant to be an exhaustive list, merely to cover all the errors explicitly mentioned in this document, plus some other common faults.

  • Any SWI:

    Error numberUnix errorMeaning
    &20E16EINVALIncorrect flags word in R0
    &20E06ENXIOInvalid unit number supplied
  • SWI DCIDriver_SetNetworkMTU:

    Error numberUnix errorMeaning
    &20E19ENOTTYIllegal op for device
  • SWI DCIDriver_Transmit:

    Error numberUnix errorMeaning
    &20E86Transmission is blocked
    &20E32ENETDOWNNetwork hardware is down.
    &20E28EMSGSIZEFrame length > network MTU.
    &20E37ENOBUFSNot enough mbufs available.
  • SWI DCIDriver_Filter:

    Error numberUnix errorMeaning
    &20E87Frame type already claimed
    &20E16EINVALTrying to claim illegal frame type
    &20E16EINVALTrying to release a non-existent claim.
    &20E01EPERMTrying to free another protocol's claim.
  • SWI DCIDriver_MulticastRequest:

    Error numberUnix errorMeaning
    &20E16EINVALTrying to claim illegal frame type
    &20E16EINVALTrying to release a non-existent claim.

5.2 Changes in DCI 4

The device driver SWI interface has been given an extensive overhaul for DCI 4: the complete break between this, and older versions of the DCI (as explained in the section about backwards compatibility 3.1) mean that all SWIs, even SWI DCIDriver_DCIVersion can be altered without worrying about the impact on non-DCI 4 modules within a machine.

The major changes made for DCI 4 are:

  1. All SWIs now use R0 as a flag word: this provides an easy route to alter SWI functionality in any future versions of the DCI that prove to be necessary. All bits of this flag word should be set to zero, except where explicitly stated otherwise.
  2. Several new SWIs have been added, i.e.

  3. SWI NetworkMTU has been renamed to SWI DCIDriver_GetNetworkMTU, to differentiate it from the new SWI SWI DCIDriver_SetNetworkMTU.
  4. SWI NetworkIfSend has been renamed to SWI DCIDriver_Transmit, mainly because it is less of a mouthful.
  5. Some old SWIs have been deleted (see below).
  6. Offsets of SWIs within a driver's SWI chunk have been changed to fill in the gaps left by the deleted SWIs; for example, SWI DCIDriver_DCIVersion has been moved from offset 4 to the more logical offset of 0.

Deleted SWI Details

As mentioned above, some of the SWIs from earlier versions of the DCI have been removed from DCI 4. These SWIs are:

SWI nameMeaning
NetworkIfStart

The decision on whether network hardware should be enabled lies with the device driver, not with a protocol module (consider the situation where one protocol module believes that the hardware should be enabled, while a different module is of the opinion that it should be disabled).

As far as protocol modules are concerned, they can only reasonably expect the hardware to be enabled when they have declared an interest in one or more frame types; if they have no declared interests, then whether the hardware is enabled or not is of no significance to them.

NetworkIfUpthis SWI has been removed for the same reasons as NetworkIfStart (q.v.).
NetworkIfDownthis SWI has been removed for the same reasons as NetworkIfStart (q.v.).
TxEventRequiredDCI 4 no longer uses events to communicate between device drivers and protocol modules, therefore this SWI has become redundant.
DCIDriver_DCIVersionSWI DCIDriver+&00
Returns DCI version numbers supported by the device driver
R0=Flags (all bits must be zero)
R0preserved
R1=Supported DCI version number (this version = 405 decimal)
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

Returns DCI major and minor version numbers supported by the device driver. The supported DCI version number is calculated as (major version × 100) + minor version.

Note: earlier versions of the DCI only returned the major version, i.e. 1 or 2 (as opposed to 100 or 200).

There was no formal version 3 of the DCI.

DCIDriver_InquireSWI DCIDriver+&01
Read the characteristics for the device driver
R0=Flags (all bits must be zero)
R1=Unit number
R0 - R1preserved
R2=Bitmap of supported features (see below)
R3 - R9preserved
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

This SWI is used to ascertain the characteristics of a device driver. The flag bits within R2 are:

Bit(s)Meaning
0Multicast reception is supported
1Promiscuous reception is supported
2Interface receives its own transmitted packets
3Station number required.
4Interface can receive erroneous packets
5Interface has a hardware address
6Driver can alter interface's hardware address
7Interface is a point to point link
8Driver supplies standard statistics
9Driver supplies extended statistics
10This is a virtual interface
11This virtual interface is software based
12This interface can selectively receive multicast packets (ie SWI MulticastRequest is available)
13This interface can IP checksum frames
14-31Reserved, must be zero

Most of these flags are self-explanatory; "Station number required" (bit 3) is used by AUN software to find out whether the underlying network requires a fixed "pseudo-Econet" station number (i.e. set in CMOS RAM), or whether a dynamic station number allocation mechanism can be employed. For example, physical Econet requires a fixed station number and its driver should set bit 3 of the flags, but Ethernet does not, and any such driver should leave bit 3 clear.

The concept of virtual interfaces (bits 10 and 11) is explained in section 9.2.

The driver should only set bit 13 if it can checksum frames efficiently, either in hardware or while copying the data into mbufs. If it would involve an extra pass over memory, it should leave the checksum up to the protocol module. See section 6.3 for more details.

Some of these characteristisc are inter-related, specifically:

  • If bit 5 (interface has a hardware address) is not set, then bit 6 (driver can alter hardware address) is ignored.
  • A driver cannot supply extended statistics (bit 9), without also supplying standard statistics (bit 8).
  • If bit 11 (virtual interface is software based) is set, then bit 10 (this is a virtual interface) should always be set as well.

DCIDriver_GetNetworkMTUSWI DCIDriver+&02
Return the MTU for the unit
R0=Flags (all bits must be zero)
R1=Unit number
R0 - R1preserved
R2=MTU
R3 - R9preserved
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

This SWI returns the MTU (Maximum Transmission Unit) for the unit specified in R0. Ethernet has a fixed MTU of 1500 bytes, other hardware layers (e.g. PPP) may have a variable MTU.

Note: this SWI has changed with respect to earlier versions of the DCI, in as much as

  1. There is now the standard flags word in R0.
  2. A unit number is now passed in R1.
  3. Results are returned in R2.
  4. A default return (R2 = 0), implying Ethernet MTU is no longer supported.

DCIDriver_SetNetworkMTUSWI DCIDriver+&03
Set the MTU for the unit
R0=Flags (all bits must be zero)
R1=Unit number
R2=MTU
R0 - R9preserved
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

For those device drivers that allow it (e.g. PPP), this SWI sets the Maximum Transmission Unit for the unit given in R1.

If the device driver has an immutable MTU, then it must still support this SWI, but return an error indicating an illegal

operation.

Note: protocol modules can only ever consider this MTU as a guideline - other protocol modules may set a

different MTU for the same logical unit.

DCIDriver_TransmitSWI DCIDriver+&04
Request the driver send frames on the network
R0=Flags
R1=Unit number
R2=Frame type
R3=Pointer to mbuf chains containing data to transmit
R4=(byte aligned) pointer to destination hardware address
R5=(byte aligned) pointer to source hardware address (if applicable)
R0 - R9preserved
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

This SWI is a request from the protocol module for the device driver to send the packet addressed by R3 to the hardware address specified in R4. The "frame type" passed in R2 is something of a misnomer: the value given is copied into the last 2 bytes of an Ethernet frame header, i.e. it is the length field according to the IEEE 802.3 spec., and the frame type as far as Ethernet 2.0 is concerned.

If a previous frame is still being transmitted, the driver should queue the new request if possible, otherwise return an error indicating that transmission is blocked.

The flag bits within R0 are:

Bit(s)Meaning
0Clear:Use interface's own hardware address.
Set:Use address given by R5 for source hardware address.
1Clear:Device driver assumes ownership of memory resources
Set:Protocol module retains ownership of memory resources
2-31Reserved, must be zero

Regardless of who initially allocated the memory resources (i.e. mbuf chains) passed in R3, it is the new owner of these resources (i.e. the device driver if R0, bit1 = 0; the protocol module if R0, bit 1 = 1) that is responsible for returning these resources to the free pool when they are no longer needed.

This SWI uses the scheme, described in section 6.3 for linking several received mbuf chains, to pass multiple output chains to the device driver via asingle call to this SWI. Care must be taken to ensure that the flag bits in R0 are applicable to all , mbuf chains passed to the driver.

The data passed to the driver can be either "safe", or "unsafe" (section 8.3 explains the concept of unsafe data) --- if the device driver is given ownership of memory resources, and needs to keep these resources after the Transmit SWI has finished, then it must use the ensure_safe function of the memory manager (section 8.2) to obtain a safe copy of the data.

Note: the register numbers for this call have changed from earlier versions of the DCI, this change being made in an attempt to standardise register usage as far as possible.

DCIDriver_FilterSWI DCIDriver+&05
Register a request for network frames
R0=Flags
R1=Unit number
R2=Frame type
R3=Address level (for write)
R4=Error level (for write)
R5=Private word pointer
R6=Address of handler routine for received frames
R0 - R9preserved
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

This SWI is the mechanism by which protocol modules inform device drivers which Ethernet frame types they would like to be passed. A full description of this interface is provided in section 6.2.

The flag bits within R0 are:

Bit(s)Meaning
0Clear:Claim a frame type.
Set:Release a previous claim on the frame type.
1Clear:Device drivers can pass unsafe mbuf chains to the receive handler
Set:Device drivers should ensure_safe mbuf chains before passing them to the receive handler.
2Clear:The protocol module wants all multicast frames (if indicated by R3)
Set:The protocol module will ask for specific multicast frames.
3Clear:IP checksum not required
Set:Device driver should place the IP checksum of received frames into the RxHdr.
4-31Reserved, must be zero

The private word pointer passed in R5 is the address of the protcol module's private word, which itself contains the address of the module's workspace.

This pointer is passed round in R0 by the protocol module in the Service_DCIProtocolStatus service call.

When a device driver receives a network frame of a type claimed by a protocol module, it will call the routine given in R6. Section 6.3 describes the parameters which must be passed to this received frame handler.

The concept of safe and unsafe data, as used in bit 1 of the flags is explained in section 8.3.

This SWI should return an error when:

  • An illegal frame type is claimed.
  • A frame type is already claimed. If a protocol module receives this error from a device driver, it can use Service_DCIFrameTypeFree to learn when the frame type is again free for claiming.
  • An attempt is made to free a frame type which has not been previously claimed by the protocol module.

DCIDriver_StatsSWI DCIDriver+&06
Register a request for network frames
R0=Flags
R1=Unit number
R2=Pointer to buffer for holding results
R0 - R9preserved
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

This SWI is the mechanism by which device drivers return statistics they have gathered while running. A full description of these statistics, including the structure copied into the the addressed by R2 is provided in section 7.

The flag bits within R0 are:

Bit(s)Meaning
0Clear:Return an indication of which statistics are gathered.
Set:Return the statistics themselves.
1-31Reserved, must be zero

The buffer addressed by R2 must be large enough to hold the full statistics structure, i.e. at least 100 bytes long; the driver is free to copy that many bytes into the buffer without thought for the consequences if the buffer is too small.

DCIDriver_MulticastRequestSWI DCIDriver+&07
Manage multicast addresses received by the driver
R0=Flags
R1=Unit number
R2=Frame type
R3=(byte aligned) pointer to multicast hardware (MAC) address
R4=(word aligned) pointer to multicast logical address (eg pointer to IP address for frame type 0x800)
R5=private word pointer
R6=address of handler routine for received frames
R0 - R9preserved
Interrupts are undefined
Fast interrupts are undefined
Processor is in undefined mode
Not defined

This SWI is the mechanism by which protocol modules specify which destination multicast addresses they wish to receive.

The flag bits within R0 are:

Bit(s)Meaning
0Clear:Request a multicast address
Set:Release a multicast address
1Clear:Requesting/releasing specific multicast address (as specified by R3,R4)
Set:Requesting/releasing all multicast addresses (R3 and R4 irrelevant) - these operations supersede specific multicast address operations (see state diagram below)
2-31Reserved, must be zero

If a protocol module calls the SWI DCIDriver_Filter SWI with bit 2 of R0 clear, then it will receive all multicast frames (if the address level is multicast or promiscuous). If, however, it sets bit 2 of R0, and the address level is multicast, then it will initially receive no frames (usually -- see below); to start to receive certain multicasts, it should call this SWI. R3 will point to a MAC address -- Ethernet drivers will use only this. Non-Ethernet drivers will probably need to know what logical address is being requested, as there may not be a one-to-one mapping between the logical and hardware multicast addresses for the specified frame type (as indeed there isn't for IP/Ethernet).

Contact Acorn for details of what to pass in R4 for specific frame types. FIXME: Contact who now?

R1, R2, R5, and R6 must match the values passed into the Filter SWI so that the device driver can tell which filter this call is intended for.

It is not expected that the device driver will do software filtering of multicasts (beyond ensuring that specific and broadcast filters don't receive any multicasts); this is up to the protocol modules. The intention of this SWI is that it should be used to set up hardware filtering where possible; protocol modules may receive more multicasts than they requested. For example, if one protocol module is using selective multicasts, while another, older protocol module isn't, the selective module will probably end up receiving all multicasts because the hardware filtering will have had to be switched off for the unselective protocol module.

This actually aids compatibility with DCI 4.03 driver modules -- a new protocol module need only set bit 2 of R0 when calling SWI DCIDriver_Filter, then ignore any "SWI not known" errors from the MulticastRequest SWI. It will then work fine with older drivers.

Device drivers will need to track which filters are requesting which multicast addresses, so that when a filter is released or a protocol module dies all its multicast claims can be automatically removed. However, as specified above, there is no need to check whether a multicast filter has requested a specific multicast address before passing a received frame to it.

This SWI provides no function for filters with an address level other than ADDRLVL_MULTICAST, and if called for such a filter should return EINVAL.

6 Received Frames

One of the major changes between DCI 4 and earlier DCI versions is the scheme used for handling received frames. The main changes introduced with this version are:

  1. Support for multicast and promiscuous frames.
  2. Improved handling of IEEE 802.3 format frames.
  3. Protocol modules are informed of received frames with a direct call into a handler routine, rather than via an event.

The principle of operation is that protocol modules register an interest in one or more frame types with a device driver, defining various filtering parameters in the process. When a device driver receives a network frame, it uses the frame type and filtering parameters to decide which (if any) protocol module should be passed the frame. Any one received frame can be passed to either one or no protocol modules, it is not possible for a single frame to be given to multiple protocol modules.

6.1 Frame Class --- Ethernet 2 and IEEE 802.3

All Ethernet frames have a 14 byte MAC header: 6 bytes of destination hardware address, 6 bytes of source hardware address, and 2 more bytes. Unfortunately, there are two competing "standards" which place a different interpretation on these last 2 bytes: Ethernet 2.0, which considers them as 16 bits of frame type, and IEEE 802.3 which treats them as 16 bits of frame length.

It is obviously not possible to refer to Ethernet 2.0 and IEEE 802.3 as different types of frame, so this document uses the term "class" to refer to the property of being either an Ethernet 2.0, or an IEEE 802.3 frame.

Since all Ethernet frames must be no more than 1500 bytes long, a device driver should assume that any received frame with an Ethernet 2.0 "frame type" of 0--1500 is an IEEE 802.3 frame, and that everything else is an Ethernet 2.0 frame. (Note that although Ethernet frames should be padded to a minimum length of 46 bytes, frame lengths < 46 are still legal values).

All IEEE 802.3 class frames should also conform to the IEEE 802.2 standard for Logical Link Control --- this latter standard defines a set of services to be supported, and provides a method to identify the type of an 802.3 class frame; the implementation of this IEEE 802.2 Logical Link Control layer cannot be the responsibility of any specific protocol module, and it would be inefficient to make each device driver responsible for the implementation, so DCI 4 caters for the scheme shown in figure 1.

Note: It cannot be protocol modules for two reasons:

  • Protocol modules are frame type specific, whereas the standard services which an IEEE 802.2 implementor must provide are frame type independent.
  • The software that implements the IEEE 802.2 layer will be expected to filter frame types, and pass them along to protocol modules; therefore, obviously, this software cannot be a standard protocol module itself.

Figure 1: Filtering Ethernet 2.0 and IEEE 802.3 class frames
Figure 1: Filtering Ethernet 2.0 and IEEE 802.3 class frames

In this scheme, device drivers can differentiate between the two frame classes, and, furthermore, can distinguish Ethernet 2.0 frame types. However, no effort is made to ascertain frame types for IEEE 802.3 frames, and all frames of this class are passed to a pseudo-protocol module which implements the IEEE 802.2 Logical Link Control layer, and which provides a similar interface to DCI 4, allowing IEEE 802.3 protocol modules to claim specific frame types.

6.3 Frame Filtering

A protocol module uses SWI DCIDriver_Filter to identify a number of criteria which a received frame must match before being passed by the device driver to the protocol module; these criteria are

  • Frame type
  • Address level
  • Error level

Only one protocol module is allowed to claim any given frame type, and when claimed, that frame type is never passed to any other protocol module. For example, if one protocol module has claimed a frame type with an address filter of specifically addressed packets only, then a second protocol module:

  • cannot claim the same frame type with an address level of promiscuous.
  • can claim all frame types not specifically registered, with (e.g.) an address level of multicast, but will not be passed any broadcast frames of the type claimed by the first protocol module (which will not receive the frame either, because the address level will filter out broadcast packets).

Frame Type

DCI 4 splits the 32-bit frame type into two 16-bit subfields --- the hi-order 16 bits specify the frame class and level, while the lo-order 16 bits provide the exact frame type (where significant).

Expressed in C format, the class/level subfield can take the following values:

#define FRMLVL_E2SPECIFIC 0x0001
#define FRMLVL_E2SINK 0x0002
#define FRMLVL_E2MONITOR 0x0003
#define FRMLVL_IEEE 0x0004

All other values for this subfield are illegal --- any attempt to use them in a SWI DCIDriver_Filter SWI should generate an error; similarly, if the hi-order subfield of the frame type is FRMLVL_E2SPECIFIC, then the lo-order subfield can take any value from 0x0000 -- 0xffff, otherwise it must be set to 0x0000, and any other value passed to Filter should be treated as an error.

The precise meanings of the class/level subfield values are:

ValueLevelMeaning
1Specificthis is the standard frame level filter --- the protocol module is only passed Ethernet 2.0 frames whose type match that given in the lo-order, frame type subfield.
2Sinkpass all Ethernet 2.0 frames that are not explicitly claimed by any protocol module.
3Monitorpass all Ethernet 2.0 frames to the protocol module.

For Ethernet 2.0 frames, the table below gives a summary of what frame levels are allowed on new claims, given the highest level of filtering currently active (monitor is considered higher than sink, and both of these levels are considered higher than normal)

Highest Current LevelNew Levels Allowed
(Nothing)Normal, Sink, Monitor
NormalNormal, Sink
SinkNormal
Monitor(Nothing)
Address Level

The four levels of address level filtering can be expressed in C as

#define ADDRLVL_SPECIFIC 0
#define ADDRLVL_NORMAL 1
#define ADDRLVL_MULTICAST 2
#define ADDRLVL_PROMISCUOUS 3

These levels are:

ValueLevelMeaning
0Specificonly pass frames addressed to the interface's specific hardware address.
1Normalonly pass frames addressed to the interface's specific hardware address, and broadcast frames.
2Multicastpass all specifically addressed, broadcast and multicast frames. If bit 2 of R0 was set on entry to the SWI DCIDriver_Filter SWI, and the SWI DCIDriver_Inquire SWI returns with bit 12 set, then the driver should attempt to filter multicast frames -- see the SWI DCIDriver_MulticastRequest SWI for details. Otherwise, all multicast frames will be passed.
3Promiscuouspass all frames of the appropriate frame type, with no address matching at all.

Most Ethernet controllers can perform this address filtering at a hardware level, but, obviously, the hardware needs to be configured to the loosest level of filtering requested by any protocol module. In the situation where two protocol modules have specified two different levels of address filtering, the device driver must still filter out unwanted frames; protocol modules are only responsible for filtering out any unwanted subset of multicast frames.

Error Level

A device driver should provide two levels of error filtering, in C these are

#define ERRLVL_NO_ERRORS 0
#define ERRLVL_ERRORS 1

These levels are:

ValueFieldMeaning
0ERRLVL_NO_ERRORSonly pass frames that are received error free.
1ERRLVL_ERRORSpass all frames, regardless of error state.

6.3 Received Frame Handlers

A major difference between DCI 4 and earlier versions of the DCI is the method used to notify protocol modules of the arrival of frames in which they have registered an interest. The main features of these receive handlers are

  1. Device drivers call a direct entry point within the protocol module (earlier DCI versions used a receive event). The address of this direct entry point is passed to the device driver at the same time the frame type is claimed via the SWI DCIDriver_Filter SWI .
  2. A device driver can pass several received frames to the protocol module with one call to the receive handler, rather than having to call the protocol module once per frame.
  3. The protocol module becomes the new owner of all mbufs passed to its receive handler by device drivers: it is the protocol module that is responsible for freeing all resources once they are no longer needed.

Handler Details

On entry:

R0 = pointer to Driver Information Block describing the source interface

R1 = pointer to head of mbuf list of received frames

R12 = protocol module's private word pointer, i.e. value passed in R5 to SWI DCIDriver_Filter SWI

On exit:

All registers preserved.

Interrupt Status: Both interrupts and fast interrupts are enabled by the received frame handler.

Details of the exact structure of the mbuf list of received frames are given in the section below.

Each received frame has a header which can be described in terms of the following C structure:

struct rx_hdr
{
    void *rx_ptr;
    unsigned int rx_tag;
    unsigned char rx_src_addr[6], _spad[2];
    unsigned char rx_dst_addr[6], _dpad[2];
    unsigned int rx_frame_type;
    unsigned int rx_error_level;
    unsigned int rx_cksum;
}

The fields in this structure are:

OffsetFieldContents
0rx_ptrThis field is for internal use by the receive handler, its value is undefined upon entry.
4rx_tagThis field is reserved for use by the IEEE 802.2 implementor, and must be set to zero by the device driver.
8rx_src_addrThe hardware source address of the frame. (Must be zeroed if hardware addresses not supported)
14_spadSpace filler to align the next field (dst_addr) on a word boundary. Must be zero filled.
16rx_dst_addrThe hardware destination address of the frame. (Must be zeroed if hardware addresses not supported)
22_dpadSpace filler to align the next field (frame_type) on a word boundary. Must be zero filled.
24rx_frame_typeThe length (for IEEE 802.3), or the type (for Ethernet 2.0) of the received frame, i.e. the last 2 bytes of the frame's MAC header.
28rx_error_levelThis field is zero if the frame was received with no errors, otherwise it contains a driver specific error code.
32rx_cksumIf bit 3 was set in the Filter call, this field must be filled in with the checksum of the complete frame. The checksum algorithm is that used by IP, ie the one's complement of the one's complement sum of all 16 bit words in the frame (padded with a zero-byte at the end if necessary to make a multiple of two bytes). Note that no knowledge of the IP packet format is required; in particular the complete frame should be checksummed, not just the length specified in the IP header. Furthermore, the frame need not even be an IP packet - other protocols may use the same checksum. The rx_cksum field is 32 bits wide only for ease of access - the most significant two bytes must be zero.

This frame header is passed in the first mbuf of each frame, the first byte of the frame data is in the second mbuf in the chain.

Note that DCI versions before 4.05 did not have the rx_cksum field; similarly later versions may have extra fields. Hence protocol modules must not fault unexpectedly long header mbufs; nor should they fault short headers, unless they were relying on the extra fields.

Mbuf Chaining

An mbuf contains two fields which point to the next mbuf in a linked list, specifically

fieldMeaning
m_nexttypically used to link mbufs in a chain.
m_listtypically used to link separate mbuf chains together.

When a device driver calls a protocol module's receive handler, it uses a single mbuf chain to hold each received frame, and can link several frames together for passing via the single call. Figure 2 shows how m_next and m_list are used to link chains of mbufs together into a list.

Note that, although this structure allows different frame types to be passed to the protocol module (because the first mbuf+ in each chain contains a struct rx_hdr which includes the frame_type field), the receive handler is only given a single Driver Information Block, and therefore all the frames passed in any one call to the handler must come from a single unit.

Figure 2: Linking mbuf chains
Figure 2: Linking mbuf chains

7 Statistics

7.1 Introduction

The SWI DCIDriver_Inquire SWI makes mention of device drivers supporting both standard, and extended statistics interfaces. This version of the DCI does not define an extended statistics interface, but it does define a standard stats. interface, and that is what this section is all about.

This document defines what it considers to be the definitive list of network parameters, and the driver maintains a subset of these (remembering that the whole set is a valid subset). An independent set of statistics is maintained for each unit that the driver controls.

The SWI DCIDriver_Stats serves two purposes:

  1. It identifies which statistics the driver gathers for a particular unit.
  2. It allows reading of the gathered statistics.

7.2 Data Structures

The statistics structure is written in C code as:

struct stats
{
    /* general information */
    unsigned char st_interface_type;
    unsigned char st_link_status;
    unsigned char st_link_polarity;
    unsigned char st_blank1;
    unsigned long st_link_failures;
    unsigned long st_network_collisions;

    /* transmit statistics */
    unsigned long st_collisions;
    unsigned long st_excess_collisions;
    unsigned long st_heartbeat_failures;
    unsigned long st_not_listening;
    /* unsigned long st_net_error; */ /* CJF: This is no longer part of the statistics structure */
    unsigned long st_tx_frames;
    unsigned long st_tx_bytes;
    unsigned long st_tx_general_errors;
    unsigned char st_last_dest_addr[8];

    /* receive statistics */
    unsigned long st_crc_failures;
    unsigned long st_frame_alignment_errors;
    unsigned long st_dropped_frames;
    unsigned long st_runt_frames;
    unsigned long st_overlong_frames;
    unsigned long st_jabbers;
    unsigned long st_late_events;
    unsigned long st_unwanted_frames;
    unsigned long st_rx_frames;
    unsigned long st_rx_bytes;
    unsigned long st_rx_general_errors;
    unsigned char st_last_src_addr[8];
};

The fields within this structure are:

OffsetFieldContents
0st_interface_type

A single byte coding the specific hardware interface type. Values so far defined are:

CodeInterface type
110-base5
210-base2
310-baseT
4Combination 10-base5 / 10-base2
5Combination 10-base2 / 10-baseT
6Reduced Squelch 10-baseT
7Acorn Econet
8Serial line
9Parallel port
10Combination 10-base5 / 10-base2 / 10-baseT
1110-baseFX
12100-baseTX
13100-baseVG
14100-baseT4
15100-baseFX
16ATM 25.6
17ATM 155

Note that the use of 'combination' types is deprecated in favour of returning a specific value reflecting the interface's current configuration.

1st_link_status

A bitfield describing the current state of the interface; significant bits are:

Bit(s)Meaning
0Clear:

Interface bad (i.e. self-test failed).

Set:

Interface OK.

1Clear:

Interface is inactive.

Set:

Interface is active.

2-3

Describe the currently configured receive level as follows:

Binary valueMeaning
00Accept directly addressed frames only.
01Accept directly addressed and broadcast frames only.
10Accept direct, broadcast, and multicast frames.
11Promiscuous mode, accept all frames.
4Clear:

Link is half duplex.

Set:

Link is full duplex.

5-7Reserved, must be zero
2st_link_polarity

A bitfield where:

Bit(s)Meaning
0Set:Link polarity correct
Clear:Link polarity incorrect
1-7Reserved, must be zero
3st_blank1

Unused, must be set to zero.

4st_link_failures

Counts the number of times a good link went away.

8st_network_collisionsCounts the total number of collisions on the network.
12st_collisionsThe number of times a collision has occured when trying to transmit a packet.
16st_excess_collisionsA count of excess transmit collisions.
20st_heartbeat_failuresThe number of times the Signal Quality Error Test failed to detect a collision.
24st_not_listening

A count of the number of times when the remote station was not listening.

(This statistic will usually be specific to Acorn Econet)

28st_tx_framesThe total number of frames transmitted since driver initialisation.
32st_tx_bytesThe total number of bytes transmitted since driver initialisation.
36st_tx_general_errorsA count of the number of non-specific network errors that occured during transmission.
40st_last_dest_addrHardware address of the last interface to which a frame was sent.
68st_jabbersThe number of times the interface was caught jabbering.
76st_unwanted_framesThe number of frames received, but not claimed by any protocol module.
80st_rx_framesThe total number of frames received since driver initialisation.
84st_rx_bytesThe total number of bytes received since driver initialisation.
88st_tx_general_errorsA count of the number of non-specific network errors that occured during frame reception.
92st_last_src_addrHardware address of the last interface from which a frame was received.
FIXME: The specification is missing a number of these descriptions - 44-64, 72.

Note that statistics are gathered for all frames received --- even if a driver subsequently decides that no protocol wants a given frame, that frame still appears in the relevant receive statistics (i.e. st_rx_bytes, st_last_src_addr etc.).

7.3 Statistics

The basic interface for reading statistics from a driver is the SWI DCIDriver_Stats SWI, outlined in section 5.3 There are two different forms of this SWI, selected by bit 0 of R0 --- the first form is used to determine which statistics are supported by the driver, while the second form is used to read the statistics.

To indicate which statistics it supports, a device driver returns a statistics structure with all bits in those fields it does support set to 1, and all bits in those fields it doesn't support set to 0. Those fields which are a variable length (i.e. st_last_dest_addr & st_last_src_addr) use the same mechanism to indicate which parts of the field are valid. For example, a standard Ethernet interface which uses 6-byte hardware addresses would return st_last_src_addr set to

0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,

whereas a PPP driver which does not use hardware addresses, and therefore would not support this field would return it set to

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00.

When returning the statistics, all multi-byte fields are returned with host byte ordering.

8 Memory Management

8.1 Introduction

In all versions of the DCI, data pass across the interface between protocol modules and device drivers in "mbufs". These are based upon the data structures originally developed for handling network data within BSD Unix kernels.

Mbufs within DCI 4 are noticeably different from their brethren, both those from BSD, and those from earlier versions of the DCI, the main distinctions being:

  • Mbufs and the data they describe no longer occupy a contiguous piece of memory.
  • It is no longer the responsibility of protocol modules to allocate and maintain pools of free memory --- DCI 4 introduces a single, centralised, memory manager module which all protocol and device driver modules claim memory from in the form of mbufs.
  • The set of function calls and macros for manipulating mbufs (i.e. those operations defined in mbuf.c and mbuf.h) provided by the new memory manager module are completely changed from those used in earlier versions of the DCI. Any module being upgraded to DCI 4 will have to have all these calls changed to the new versions.

8.2 Memory Manager Module

Overview

Memory management for packet storage in earlier versions of the DCI is performed with mbufs. DCI 4 also uses mbuf based packet storage, but there are some differences. These differences are for the following reasons:

  1. Correct design oversights in previous versions of the DCI.
  2. Provide a more modular, and upgradeable, system.
  3. Offer single mbuf arbiter with optimised routines available to all DCI4 components.

The memory manager, the arbiter module, is central to the DCI4 mbuf scheme. It performs most of the low level work associated with mbufs, as well as relieving both protocol and client modules of some tedium.

A complete specification of the memory manager is available separately; this document is designed to guide the reader conversant with "traditional" mbufs through using DCI4 mbufs.

Communication with the memory manager is centred around an mbctl structure. This is stored in the client's memory, and is mainly initialised by the memory manager to contain useful information, including the addresses of a number of routines within the memory manager for the client to call directly.

Direct entry points are designed to permit the easy inter-operation of assembler and APCS code (such as that generated by the NorCroft C compiler), and roughly obey APCS. A list of entry/exit characteristics follows (using APCS register naming convention):

  1. a1 always points at an mbctl structure for all direct entry calls
  2. the processor must be in supervisor mode
  3. a1--a4 are the only parameter registers
  4. a2--a4 and ip are corrupted by the call
  5. a1 is either the call result or corrupted
  6. other registers preserved by call
  7. the processor flags are preserved by the call
  8. no V set error convention (incompatible with APCS)
  9. in general, an error results in a1=0 on exit
  10. IRQ state preserved across call
  11. IRQs may be disabled during calls
  12. IRQs may be enabled during calls ONLY if specifically documented
  13. FIQs assumed enabled on entry
  14. FIQs preserved across calls

Currently, no direct entry point routine will enable interrupts if they are disabled on entry.

The header file mbuf.h provides some macros to manage interfacing with the memory manager routines.

These direct entry points provide access to allocator and free routines, along with a whole host of support routines. Rather than each protocol implementing it's own mbuf scheme, and each device driver having to choose the correct mbuf pool to allocate from, all protocols and all device drivers perform their allocations and frees via these direct entry points.

Structures

The new memory manager module uses two main data structures: the mbuf structure, each one of which describes a piece of atomically allocated memory, and struct mbctl, the control struture which describes the exact interface between the memory manager and one of its clients.

Note that, although a struct mbuf is recognisably similar to the structure used in "traditional" memory management schemes, there are enough differences in the new structure to render it incompatible with the macros defined in the traditional versions of mbuf.h.

The new definition of an mbuf is shown below:

typedef struct mbuf
{
    struct mbuf *m_next; /* next mbuf in chain */
    struct mbuf *m_list; /* next mbuf in list (clients only) */
    ptrdiff_t m_off; /* current offset to data from mbuf itself */
    size_t m_len; /* current byte count */
    const ptrdiff_t m_inioff; /* original offset to data from mbuf itself */
    const size_t m_inilen; /* original byte count (for underlying data) */
    unsigned char m_type; /* client use only */
    const unsigned char m_sys1; /* MBufManager use only */
    const unsigned char m_sys2; /* MBufManager use only */
    unsigned char m_flags /* client use only */
    struct pkthdr m_pkthdr; /* client use only */
} dci4_mbuf;

The MLEN macro value is no longer directly applicable --- each mbuf must have its maximum size checked individually. Likewise, reseting m_off now requires examining the m_inioff field of the mbuf - just setting it to zero is no longer good enough.

m_act has been renamed m_list.

m_inilen and m_inioff are provided to replace the MMINOFF and MMAXLEN macros, as the values are now dependent upon the particular mbuf in question.

m_indir has disappeared --- specific routines exist for determining if an mbuf chain contains unsafe data.

The mbuf structure has been separated from the underlying storage it describes. The underlying storage blocks may now be different sizes (128 and 1536 byte blocks are currently used).

Earlier versions of the DCI had big mbufs, but they were weakly defined and dtom did not work with them. DCI 4 corrects both these points --- indirect mbufs, which at times were not distinguishable from large mbufs, have been formalised into unsafe mbufs.

As an mbuf chain passes around the system, ownership of that chain is also transferred. Ownership brings with it the responsibility to free the mbuf chain (unless it is transferred to another component, although this is unlikely).

The other crucial structure in DCI 4 memory management is mbctl:

typedef struct mbctl
{
    /* reserved for MBufManager use in establishing context */
    int opaque; /* MBufManager use only */

    /* Client initialises before session is established */
    size_t mbcsize; /* size of mbctl structure from client */
    unsigned int mbcvers; /* client version of MBufManager* spec */
    unsigned long flags; /* */
    size_t advminubs; /* Advisory desired minimum underlying block size */
    size_t advmaxubs; /* Advisory desired maximum underlying block size */
    size_t mincontig; /* client required min ensure_contig value */
    unsigned long spare1; /* Must be set to zero on initialisation */

    /* MBufManager initialises during session establishment */
    size_t minubs; /* Minimum underlying block size */
    size_t maxubs; /* Maximum underlying block size */
    size_t maxcontig; /* Maximum contiguify block size */
    unsigned long spare2; /* Reserved for future use */

    /* Allocation routines */
    struct mbuf * /* MBC_DEFAULT */
    (* alloc)
    (struct mbctl *, size_t bytes, void *ptr);

    struct mbuf * /* Parameter driven */
    (* alloc_g)
    (struct mbctl *, size_t bytes, void *ptr, unsigned long flags);

    struct mbuf * /* MBC_UNSAFE */
    (* alloc_u)
    (struct mbctl *, size_t bytes, void *ptr);

    struct mbuf * /* MBC_SINGLE */
    (* alloc_s)
    (struct mbctl *, size_t bytes, void *ptr);

    struct mbuf * /* MBC_CLEAR */
    (* alloc_c)
    (struct mbctl *, size_t bytes, void *ptr);

    /* Ensuring routines */
    struct mbuf *
    (* ensure_safe)
    (struct mbctl *, struct mbuf *mp);

    struct mbuf *
    (* ensure_contig)
    (struct mbctl *, struct mbuf *mp, size_t bytes);

    /* Freeing routines */
    void
    (* free)
    (struct mbctl *, struct mbuf *mp);

    void
    (* freem)
    (struct mbctl *, struct mbuf *mp);

    void
    (* dtom_free)
    (struct mbctl *, struct mbuf *mp);

    void
    (* dtom_freem)
    (struct mbctl *, struct mbuf *mp);

    /* Support routines */
    struct mbuf * /* No ownership transfer though */
    (* dtom)
    (struct mbctl *, void *ptr);

    int /* Client retains mp ownership */
    (* any_unsafe)
    (struct mbctl *, struct mbuf *mp);

    int /* Client retains mp ownership */
    (* this_unsafe)
    (struct mbctl *, struct mbuf *mp);

    size_t /* Client retains mp ownership */
    (* count_bytes)
    (struct mbctl *, struct mbuf *mp);

    struct mbuf * /* Client retains old, new ownership */
    (* cat)
    (struct mbctl *, struct mbuf *old, struct mbuf *new);

    struct mbuf * /* Client retains mp ownership */
    (* trim)
    (struct mbctl *, struct mbuf *mp, int bytes, void *ptr);

    struct mbuf * /* Client retains mp ownership */
    (* copy)
    (struct mbctl *, struct mbuf *mp, size_t off, size_t len);

    struct mbuf * /* Client retains mp ownership */
    (* copy_p)
    (struct mbctl *, struct mbuf *mp, size_t off, size_t len);

    struct mbuf * /* Client retains mp ownership */
    (* copy_u)
    (struct mbctl *, struct mbuf *mp, size_t off, size_t len);

    struct mbuf * /* Client retains mp ownership */
    (* import)
    (struct mbctl *, struct mbuf *mp, size_t bytes, void *ptr);

    struct mbuf * /* Client retains mp ownership */
    (* export)
    (struct mbctl *, struct mbuf *mp, size_t bytes, void *ptr);
} dci4_mbctl;

Some of the fields the client initialises are present to permit future versions of the memory manager to tune themselves as tightly as possible to the setup they are asked to support.

Note that dtom is no longer a macro. Don't worry --- it's efficient assembler, and it works with all sizes of mbuf the memory manager cares to use.

Using the memory manager module

Basic use of the memory manager is performed as follows

  1. module loads and looks for memory manager
  2. if memory manager is absent, module goes into a pre-active state, awaiting the arrival of the memory manager
  3. once the memory manager is present, a "session" is opened with it
  4. the device driver/protocol may now become active if it is pre-active (this might involve delaying arrival service calls until now)
  5. the device driver/protocol uses direct entry points to communicate with the memory manager
  6. the device driver/protocol is about to die --- it first closes the open session with the memory manager
  7. the device driver/protocol can now die

Initialisation with the memory manager is performed with code something like:

static _kernel_oserror *open_mbuf_manager_session(void)
{
    _kernel_swi_regs r;
    memset(&mbctl, 0, sizeof(struct mbctl));
    mbctl.mbcsize = sizeof(struct mbctl);
    mbctl.mbcvers = MBUF_MANAGER_VERSION;
    mbctl.flags = 0;
    mbctl.advminubs = 0;
    mbctl.advmaxubs = 0;
    mbctl.mincontig = 0;
    mbctl.spare1 = 0;
    r.r[0] = (int) &mbctl;
    return(_kernel_swi(Mbuf_OpenSession, &r, &r));
}

Finalisation is performed with code something like:

static _kernel_oserror *close_mbuf_manager_session(void)
{
    _kernel_swi_regs r;
    r.r[0] = (int) &mbctl;
    return(_kernel_swi(Mbuf_CloseSession, &r, &r));
}

A quick summary of the available direct entry point routines:

OffsetNameContents
48allocstandard allocator. Can import data, but cannot zero the underlying storage, force single mbuf allocation or allocate unsafe data.
52alloc_gthe allocator which can emulate the other allocator functions.
56alloc_uallocate unsafe mbufs
60alloc_sforce allocation to a single mbuf
64alloc_cclear the underlying storage after allocation.
68ensure_safeExamines each mbuf and returns a modified mbuf chain if any mbufs are unsafe.
72ensure_contigEnsures that a given region of the described data is contiguous in memory, to permit structures to be "cast over it".
76freeFrees a single mbuf
80freemFrees an mbuf chain
84dtom_freePerforms a dtom operation and then a free operation on the result
88dtom_freemPerforms a dtom operation and then a freem operation on the result
92dtomTransform a data pointer to the mbuf describing it
96any_unsafescan an mbuf chain for unsafe mbufs
100this_unsafedetermine whether an mbuf is safe or unsafe.
104count_bytesreturn the number of bytes described by an mbuf chain
108catconcatenate two mbuf chains together
112trimadjust m _len and m _off values to remove data from an mbuf chain.
116copyproduce an mbuf chain containing a copy of the data described by an mbuf chain.
120copy_pproduce an mbuf chain containing a copy of the data described by an mbuf chain. The only difference between this routine and copy is that this routine assumes that the m_type, m_flags and m_pkthdr fields contain important data which should be preserved during the copy.
124copy_uproduce an unsafe copy of of the data described by an mbuf chain.
128importimport data from raw memory into an mbuf chain
132exportexport from from an mbuf chain into raw memory

So, a device driver might use the allocator as follows:

struct mbuf *mp = mbctl.alloc(&mbctl, packlen, NULL);

which allocates an mbuf chain of "packlen" bytes.

The entire chain might later be freed thus:

mbctl.freem(&mbctl, mp);

The reason all the direct entry point calls take a (struct mbctl *) value as there first parameter is to permit the MBufManager to establish a context within which it is operating (ie find its workspace!).

Finally, the memory manager supports the DCI4 statistics interface. This can be useful in fine tuning your DCI4 component.

8.3 Unsafe Data

The concept of indirect data mbufs was introduced in earlier versions of the DCI to eliminate the need for data to be copied where this is possible; this results in a significant improvement in frame rates. Typically, an mbuf is used to indirect to user data, rather than making a private copy of that data.

An important implication of this is that a protocol module cannot rely on the indirect pointers after any system call which uses them has returned to the user: if they need to keep the data after this time, then a private copy must be made of the data. Protocol modules should always know whether an mbuf chain is unsafe or not (since they create the chain in the first place); device drivers are informed via the flags in the SWI DCIDriver_Transmit SWI whether or not the passed mbuf chain is safe or not --- if they need to use any data from an unsafe mbuf chain after the SWI has returned, then they must make a copy of that chain.

9 Miscellanea

9.1 Network Card Self-Tests

All network cards should support at least one *-command, used to initiate a hardware self-test. This * -command should be of the form nametest, where name is the driver name as supplied in the dib_name field of the driver information block

When invoked, the self-test command should, to the best of its ability, ascertain whether the network hardware is still functioning correctly, and print a short success/failure message. As part of this self-test, the drivers should, where possible, perform a live network test (this is because many network faults are due to cabling problems rather than hardware failures, and a live network test may be able to detect these problems).

9.2 Virtual Interfaces

When running some form of PC emulator under RISC OS, it is frequently desirable to run a second protocol stack within the emulator that is independant of a similar protocol stack running on the native OS (the classic example being a TCP/IP stack running with one Internet address under RISC OS, and a PC based TCP/IP stack running under the emulator with a different Internet address).

One hardware-based solution to this problem would be to simply have two Ethernet cards in the same machine, each dedicated to one of the competing protocol stacks; the obvious downside to this solution would be the expense --- any software-based solution that allowed one card to support two interfaces would be much cheaper.

The optional software solution supported by DCI 4 is the concept of 'virtual interfaces'. A virtual interface is where a driver creates a second unit for a physical interface, this unit having an Ethernet hardware address that is different from the hardware address for the first, "real" unit for the interface.

Exactly how this virtual interface is implemented is highly dependent upon the Ethernet controller chip used by the interface. Some controllers allow more than one hardware address to be specified for the one interface; this obviously makes the implemention of a virtual interface a relatively easy task. For controllers that do not allow more than one hardware address, the device driver will need to put the interface into promiscuous mode, and discard frames with unwanted hardware addresses under software control.

For any unit which is a virtual interface, bit 10 of the Inquire flags for that unit should be set; if the virtual interface uses software filtering of Ethernet hardware addresses, then bit 11 of these flags should also be set.