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:
- 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.
- 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:
The DCI interface is optimised in various ways for Ethernet device drivers, specifically:
- Physical network addresses are 48-bit quantities.
- 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.
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
- Service_ProtocolStatus
- Service_FindNetworkDriver
- Service_NetworkDriverStatus
The new service calls defined in DCI 4 are
- Service_EnumerateNetworkDrivers
- Service_DCIDriverStatus
- Service_DCIFrameTypeFree
- 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) | Name | Meaning | |
|---|---|---|---|
| 0-7 | slotid | 8 bits: Physical location of the device | |
| 8-15 | minor | 8 bits: Minor field | |
| 16-20 | pcmciaslot | 5 bits: PCMCIA virtual slot, or 0 for non-PCMIA devices | |
| 21-31 | mbz | Reserved, 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
| Value | Meaning |
|---|---|
| 0-7 | Physical expansion card slots |
| 8 | Risc PC network position |
| 16-31 | PCI slots |
| 128 | Parallel port |
| 129 | Serial port (e.g. PPP) |
| 130 | Econet socket |
| 131 | PCMCIA 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:
| Offset | Field | Contents |
|---|---|---|
| 0 | dib_swibase | The base of the device driver's allocated SWI chunk. |
| 4 | dib_name | A pointer to a short textual name unique to the driver (e.g. "en", "ppp"), and a terminating NULL. |
| 8 | dib_unit | The unit number. |
| 12 | dib_address | A pointer to a 6-byte character array which contains the hardware address of the interface. |
| 16 | dib_module | A pointer to a string containing the title of the driver module (e.g. "Ether3"). |
| 20 | dib_location | A 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.. |
| 24 | dib_slot | The slot number for this unit. |
| 28 | dib_inquire | A 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:
| Offset | Field | Contents |
|---|---|---|
| 0 | chd_next | A pointer to the next entry in the linked list. The last entry in the list contains a NULL pointer. |
| 4 | chd_dib | A 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:
- Install a handler on the unknown SWI software vector ("UKSWIV"), and check all unknown SWIs for an appropriate chunk number.
- Setup a callback handler in the initialisation routine, and then issue the service call from within this callback handler.
| R0 | = | pointer to head of linked list of device drivers |
| R1 | = | &9B (reason code) |
| R0 | = | pointer to new head of linked list |
| R1 - R9 | preserved | |
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.
| R0 | = | pointer to Driver Information Block describing this driver | ||||||
| R1 | = | &9D (reason code) | ||||||
| R2 | = | Driver status:
| ||||||
| R3 | = | DCI version supported × 100 |
| R0 - R9 | preserved | |
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.
| 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 |
| R0 | preserved | |
| R1 | = | 0 to claim the call, or preserved to pass it on. |
| R2 - R9 | preserved | |
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.
| R0 | = | Protocol module's private word pointer | ||||||
| R1 | = | &9F (reason code) | ||||||
| R2 | = | Protocol status:
| ||||||
| R3 | = | DCI version supported × 100 | ||||||
| R4 | = | Pointer to protocol module's title string |
| R0 - R9 | preserved | |
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
- send control commands
- pass data
- obtain information
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 offset | SWI name |
|---|---|
| 0 | DCIVersion |
| 1 | Inquire |
| 2 | GetNetworkMTU |
| 3 | SetNetworkMTU |
| 4 | Transmit |
| 5 | Filter |
| 6 | Stats |
| 7 | MulticastRequest |
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 number Unix error Meaning &20E16 EINVAL Incorrect flags word in R0 &20E06 ENXIO Invalid unit number supplied -
Error number Unix error Meaning &20E19 ENOTTY Illegal op for device -
Error number Unix error Meaning &20E86 Transmission is blocked &20E32 ENETDOWN Network hardware is down. &20E28 EMSGSIZE Frame length > network MTU. &20E37 ENOBUFS Not enough mbufs available. -
Error number Unix error Meaning &20E87 Frame type already claimed &20E16 EINVAL Trying to claim illegal frame type &20E16 EINVAL Trying to release a non-existent claim. &20E01 EPERM Trying to free another protocol's claim. SWI DCIDriver_MulticastRequest:
Error number Unix error Meaning &20E16 EINVAL Trying to claim illegal frame type &20E16 EINVAL Trying 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:
- 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.
Several new SWIs have been added, i.e.
- SWI NetworkMTU has been renamed to SWI DCIDriver_GetNetworkMTU, to differentiate it from the new SWI SWI DCIDriver_SetNetworkMTU.
- SWI NetworkIfSend has been renamed to SWI DCIDriver_Transmit, mainly because it is less of a mouthful.
- Some old SWIs have been deleted (see below).
- 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 name | Meaning |
|---|---|
| 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. |
| NetworkIfUp | this SWI has been removed for the same reasons as NetworkIfStart (q.v.). |
| NetworkIfDown | this SWI has been removed for the same reasons as NetworkIfStart (q.v.). |
| TxEventRequired | DCI 4 no longer uses events to communicate between device drivers and protocol modules, therefore this SWI has become redundant. |
| R0 | = | Flags (all bits must be zero) |
| R0 | preserved | |
| R1 | = | Supported DCI version number (this version = 405 decimal) |
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.
| R0 | = | Flags (all bits must be zero) |
| R1 | = | Unit number |
| R0 - R1 | preserved | |
| R2 | = | Bitmap of supported features (see below) |
| R3 - R9 | preserved | |
This SWI is used to ascertain the characteristics of a device driver. The flag bits within R2 are:
| Bit(s) | Meaning | |
|---|---|---|
| 0 | Multicast reception is supported | |
| 1 | Promiscuous reception is supported | |
| 2 | Interface receives its own transmitted packets | |
| 3 | Station number required. | |
| 4 | Interface can receive erroneous packets | |
| 5 | Interface has a hardware address | |
| 6 | Driver can alter interface's hardware address | |
| 7 | Interface is a point to point link | |
| 8 | Driver supplies standard statistics | |
| 9 | Driver supplies extended statistics | |
| 10 | This is a virtual interface | |
| 11 | This virtual interface is software based | |
| 12 | This interface can selectively receive multicast packets (ie SWI MulticastRequest is available) | |
| 13 | This interface can IP checksum frames | |
| 14-31 | Reserved, 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.
| R0 | = | Flags (all bits must be zero) |
| R1 | = | Unit number |
| R0 - R1 | preserved | |
| R2 | = | MTU |
| R3 - R9 | preserved | |
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
- There is now the standard flags word in R0.
- A unit number is now passed in R1.
- Results are returned in R2.
- A default return (R2 = 0), implying Ethernet MTU is no longer supported.
| R0 | = | Flags (all bits must be zero) |
| R1 | = | Unit number |
| R2 | = | MTU |
| R0 - R9 | preserved | |
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.
| 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 - R9 | preserved | |
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 | |
|---|---|---|
| 0 | Clear: | Use interface's own hardware address. |
| Set: | Use address given by R5 for source hardware address. | |
| 1 | Clear: | Device driver assumes ownership of memory resources |
| Set: | Protocol module retains ownership of memory resources | |
| 2-31 | Reserved, 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.
| 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 - R9 | preserved | |
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 | |
|---|---|---|
| 0 | Clear: | Claim a frame type. |
| Set: | Release a previous claim on the frame type. | |
| 1 | Clear: | 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. | |
| 2 | Clear: | The protocol module wants all multicast frames (if indicated by R3) |
| Set: | The protocol module will ask for specific multicast frames. | |
| 3 | Clear: | IP checksum not required |
| Set: | Device driver should place the IP checksum of received frames into the RxHdr. | |
| 4-31 | Reserved, 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.
| R0 | = | Flags |
| R1 | = | Unit number |
| R2 | = | Pointer to buffer for holding results |
| R0 - R9 | preserved | |
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 | |
|---|---|---|
| 0 | Clear: | Return an indication of which statistics are gathered. |
| Set: | Return the statistics themselves. | |
| 1-31 | Reserved, 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.
| 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 - R9 | preserved | |
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 | |
|---|---|---|
| 0 | Clear: | Request a multicast address |
| Set: | Release a multicast address | |
| 1 | Clear: | 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-31 | Reserved, 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:
- Support for multicast and promiscuous frames.
- Improved handling of IEEE 802.3 format frames.
- 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.
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:
| Value | Level | Meaning |
|---|---|---|
| 1 | Specific | this 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. |
| 2 | Sink | pass all Ethernet 2.0 frames that are not explicitly claimed by any protocol module. |
| 3 | Monitor | pass 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 Level | New Levels Allowed |
|---|---|
| (Nothing) | Normal, Sink, Monitor |
| Normal | Normal, Sink |
| Sink | Normal |
| 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:
| Value | Level | Meaning |
|---|---|---|
| 0 | Specific | only pass frames addressed to the interface's specific hardware address. |
| 1 | Normal | only pass frames addressed to the interface's specific hardware address, and broadcast frames. |
| 2 | Multicast | pass 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. |
| 3 | Promiscuous | pass 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:
| Value | Field | Meaning |
|---|---|---|
| 0 | ERRLVL_NO_ERRORS | only pass frames that are received error free. |
| 1 | ERRLVL_ERRORS | pass 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
- 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 .
- 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.
- 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:
| Offset | Field | Contents |
|---|---|---|
| 0 | rx_ptr | This field is for internal use by the receive handler, its value is undefined upon entry. |
| 4 | rx_tag | This field is reserved for use by the IEEE 802.2 implementor, and must be set to zero by the device driver. |
| 8 | rx_src_addr | The hardware source address of the frame. (Must be zeroed if hardware addresses not supported) |
| 14 | _spad | Space filler to align the next field (dst_addr) on a word boundary. Must be zero filled. |
| 16 | rx_dst_addr | The hardware destination address of the frame. (Must be zeroed if hardware addresses not supported) |
| 22 | _dpad | Space filler to align the next field (frame_type) on a word boundary. Must be zero filled. |
| 24 | rx_frame_type | The 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. |
| 28 | rx_error_level | This field is zero if the frame was received with no errors, otherwise it contains a driver specific error code. |
| 32 | rx_cksum | If 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
| field | Meaning |
|---|---|
| m_next | typically used to link mbufs in a chain. |
| m_list | typically 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.
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:
- It identifies which statistics the driver gathers for a particular unit.
- 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:
| Offset | Field | Contents | |||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | st_interface_type | A single byte coding the specific hardware interface type. Values so far defined are:
Note that the use of 'combination' types is deprecated in favour of returning a specific value reflecting the interface's current configuration. | |||||||||||||||||||||||||||||||||||||
| 1 | st_link_status |
A bitfield describing the current state of the interface; significant bits are:
| |||||||||||||||||||||||||||||||||||||
| 2 | st_link_polarity |
A bitfield where:
| |||||||||||||||||||||||||||||||||||||
| 3 | st_blank1 | Unused, must be set to zero. | |||||||||||||||||||||||||||||||||||||
| 4 | st_link_failures | Counts the number of times a good link went away. | |||||||||||||||||||||||||||||||||||||
| 8 | st_network_collisions | Counts the total number of collisions on the network. | |||||||||||||||||||||||||||||||||||||
| 12 | st_collisions | The number of times a collision has occured when trying to transmit a packet. | |||||||||||||||||||||||||||||||||||||
| 16 | st_excess_collisions | A count of excess transmit collisions. | |||||||||||||||||||||||||||||||||||||
| 20 | st_heartbeat_failures | The number of times the Signal Quality Error Test failed to detect a collision. | |||||||||||||||||||||||||||||||||||||
| 24 | st_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) | |||||||||||||||||||||||||||||||||||||
| 28 | st_tx_frames | The total number of frames transmitted since driver initialisation. | |||||||||||||||||||||||||||||||||||||
| 32 | st_tx_bytes | The total number of bytes transmitted since driver initialisation. | |||||||||||||||||||||||||||||||||||||
| 36 | st_tx_general_errors | A count of the number of non-specific network errors that occured during transmission. | |||||||||||||||||||||||||||||||||||||
| 40 | st_last_dest_addr | Hardware address of the last interface to which a frame was sent. | |||||||||||||||||||||||||||||||||||||
| 68 | st_jabbers | The number of times the interface was caught jabbering. | |||||||||||||||||||||||||||||||||||||
| 76 | st_unwanted_frames | The number of frames received, but not claimed by any protocol module. | |||||||||||||||||||||||||||||||||||||
| 80 | st_rx_frames | The total number of frames received since driver initialisation. | |||||||||||||||||||||||||||||||||||||
| 84 | st_rx_bytes | The total number of bytes received since driver initialisation. | |||||||||||||||||||||||||||||||||||||
| 88 | st_tx_general_errors | A count of the number of non-specific network errors that occured during frame reception. | |||||||||||||||||||||||||||||||||||||
| 92 | st_last_src_addr | Hardware address of the last interface from which a frame was received. | |||||||||||||||||||||||||||||||||||||
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:
- Correct design oversights in previous versions of the DCI.
- Provide a more modular, and upgradeable, system.
- 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):
- a1 always points at an mbctl structure for all direct entry calls
- the processor must be in supervisor mode
- a1--a4 are the only parameter registers
- a2--a4 and ip are corrupted by the call
- a1 is either the call result or corrupted
- other registers preserved by call
- the processor flags are preserved by the call
- no V set error convention (incompatible with APCS)
- in general, an error results in a1=0 on exit
- IRQ state preserved across call
- IRQs may be disabled during calls
- IRQs may be enabled during calls ONLY if specifically documented
- FIQs assumed enabled on entry
- 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
- module loads and looks for memory manager
- if memory manager is absent, module goes into a pre-active state, awaiting the arrival of the memory manager
- once the memory manager is present, a "session" is opened with it
- the device driver/protocol may now become active if it is pre-active (this might involve delaying arrival service calls until now)
- the device driver/protocol uses direct entry points to communicate with the memory manager
- the device driver/protocol is about to die --- it first closes the open session with the memory manager
- 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:
| Offset | Name | Contents |
|---|---|---|
| 48 | alloc | standard allocator. Can import data, but cannot zero the underlying storage, force single mbuf allocation or allocate unsafe data. |
| 52 | alloc_g | the allocator which can emulate the other allocator functions. |
| 56 | alloc_u | allocate unsafe mbufs |
| 60 | alloc_s | force allocation to a single mbuf |
| 64 | alloc_c | clear the underlying storage after allocation. |
| 68 | ensure_safe | Examines each mbuf and returns a modified mbuf chain if any mbufs are unsafe. |
| 72 | ensure_contig | Ensures that a given region of the described data is contiguous in memory, to permit structures to be "cast over it". |
| 76 | free | Frees a single mbuf |
| 80 | freem | Frees an mbuf chain |
| 84 | dtom_free | Performs a dtom operation and then a free operation on the result |
| 88 | dtom_freem | Performs a dtom operation and then a freem operation on the result |
| 92 | dtom | Transform a data pointer to the mbuf describing it |
| 96 | any_unsafe | scan an mbuf chain for unsafe mbufs |
| 100 | this_unsafe | determine whether an mbuf is safe or unsafe. |
| 104 | count_bytes | return the number of bytes described by an mbuf chain |
| 108 | cat | concatenate two mbuf chains together |
| 112 | trim | adjust m _len and m _off values to remove data from an mbuf chain. |
| 116 | copy | produce an mbuf chain containing a copy of the data described by an mbuf chain. |
| 120 | copy_p | produce 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. |
| 124 | copy_u | produce an unsafe copy of of the data described by an mbuf chain. |
| 128 | import | import data from raw memory into an mbuf chain |
| 132 | export | export 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.