Chapter 167. Writing Ethernet Device Drivers
Table of Contents
167.1. Generic Ethernet API
This section provides a simple description of how to write a low-level, hardware dependent ethernet driver. In eCos this is known as a “standard” driver.
There is a high-level driver (which is only code — with no state of its own) that is part of the stack. There will be one or more low-level drivers tied to the actual network hardware. Each of these drivers contains one or more driver instances. The intent is that the low-level drivers know nothing of the details of the stack that will be using them. Thus, the same driver can be used by the eCos supported TCP/IP stack, RedBoot, or any other, with no changes.
A driver instance is contained within a struct eth_drv_sc:
struct eth_hwr_funs { // Initialize hardware (including startup) void (*start)(struct eth_drv_sc *sc, unsigned char *enaddr, int flags); // Shut down hardware void (*stop)(struct eth_drv_sc *sc); // Device control (ioctl pass-thru) int (*control)(struct eth_drv_sc *sc, unsigned long key, void *data, int data_length); // Query - can a packet be sent? int (*can_send)(struct eth_drv_sc *sc); // Send a packet of data void (*send)(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len, int total_len, unsigned long key); // Receive [unload] a packet of data void (*recv)(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len); // Deliver data to/from device from/to stack memory space // (moves lots of memcpy()s out of DSRs into thread) void (*deliver)(struct eth_drv_sc *sc); // Poll for interrupts/device service void (*poll)(struct eth_drv_sc *sc); // Get interrupt information from hardware driver int (*int_vector)(struct eth_drv_sc *sc); // Logical driver interface struct eth_drv_funs *eth_drv, *eth_drv_old; }; struct eth_drv_sc { struct eth_hwr_funs *funs; void *driver_private; const char *dev_name; int state; struct arpcom sc_arpcom; /* ethernet common */ };
Note | |
---|---|
If you have two instances of the same hardware, you only need one struct eth_hwr_funs shared between them. |
There is another structure which is used to communicate with the rest of the stack:
struct eth_drv_funs { // Logical driver - initialization void (*init)(struct eth_drv_sc *sc, unsigned char *enaddr); // Logical driver - incoming packet notifier void (*recv)(struct eth_drv_sc *sc, int total_len); // Logical driver - outgoing packet notifier void (*tx_done)(struct eth_drv_sc *sc, CYG_ADDRESS key, int status); };
Your driver does not create an instance of this structure. It is provided for driver code to use in the eth_drv member of the function record. Its usage is described below in Section 167.3, “Upper Layer Functions”
One more function completes the API with which your driver communicates with the rest of the stack:
extern void eth_drv_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data);
This function is designed so that it can be registered as the DSR for your interrupt handler. It will awaken the “Network Delivery Thread” to call your deliver routine. See Section 167.2.7, “Deliver function”.
You create an instance of struct eth_drv_sc
using the
ETH_DRV_SC()
macro which
sets up the structure, including the prototypes for the functions, etc.
By doing things this way, if the internal design of the ethernet drivers
changes (e.g. we need to add a new low-level implementation function),
existing drivers will no longer compile until updated. This is much
better than to have all of the definitions in the low-level drivers
themselves and have them be (quietly) broken if the interfaces change.
The “magic”
which gets the drivers started (and indeed, linked) is
similar to what is used for the I/O subsystem.
This is done using the
NETDEVTAB_ENTRY()
macro, which defines an initialization function
and the basic data structures for the low-level driver.
typedef struct cyg_netdevtab_entry { const char *name; bool (*init)(struct cyg_netdevtab_entry *tab); void *device_instance; unsigned long status; } cyg_netdevtab_entry_t;
The device_instance
entry here would point to the struct eth_drv_sc
entry previously defined. This allows the network driver
setup to work with any class of driver, not just ethernet drivers. In
the future, there will surely be serial PPP
drivers, etc. These will
use the NETDEVTAB_ENTRY()
setup to create the basic driver, but they will
most likely be built on top of other high-level device driver layers.
To instantiate itself, and connect it to the system, a hardware driver will have a template (boilerplate) which looks something like this:
#include <cyg/infra/cyg_type.h> #include <cyg/hal/hal_arch.h> #include <cyg/infra/diag.h> #include <cyg/hal/drv_api.h> #include <cyg/io/eth/netdev.h> #include <cyg/io/eth/eth_drv.h> ETH_DRV_SC(DRV
_sc, 0, // No driver specific data needed "eth0", // Name for this interfaceHRDWR
_start,HRDWR
_stop,HRDWR
_control,HRDWR
_can_sendHRDWR
_send,HRDWR
_recv,HRDWR
_deliver,HRDWR
_poll,HRDWR
_int_vector ); NETDEVTAB_ENTRY(DRV
_netdev, "DRV
",DRV_HRDWR
_init, &DRV
_sc);
This, along with the referenced functions, completely define the driver.
Note | |
---|---|
If one needed the same low-level driver to handle
multiple similar hardware interfaces, you would need multiple invocations
of the
0, // No driver specific data is currently. |
167.2. Review of the functions
Now a brief review of the functions. This discussion will use generic names for the functions — your driver should use hardware-specific names to maintain uniqueness against any other drivers.
167.2.1. Init function
static bool DRV_HDWR
_init(struct cyg_netdevtab_entry *tab)
This function is called as part of system initialization. Its primary function is to decide if the hardware (as indicated via tab->device_instance) is working and if the interface needs to be made available in the system. If this is the case, this function needs to finish with a call to the ethernet driver function:
struct eth_drv_sc *sc = (struct eth_drv_sc *)tab->device_instance;
….initialization code….
// Initialize upper level driver
(sc->funs->eth_drv->init)( sc, unsigned char *enaddr );
where enaddr
is a pointer to the ethernet station address for this unit, to inform
the stack of this device's readiness and availability.
Note | |
---|---|
The ethernet station address (ESA) is supposed to be a world-unique, 48 bit address for this particular ethernet interface. Typically it is provided by the board/hardware manufacturer in ROM.
In many packages it is possible for the
ESA
to be set from RedBoot,
(perhaps from 'fconfig' data), hard-coded from
CDL, or from an EPROM.
A driver should choose a run-time specified
ESA
(e.g. from RedBoot)
preferentially, otherwise (in order) it should use a CDL specified
ESA
if one has been set, otherwise an EPROM set
ESA, or otherwise
fail. See the |
167.2.2. Start function
static void
HRDWR
_start(struct eth_drv_sc *sc, unsigned char *enaddr, int flags)
This function is called, perhaps much later than system initialization time, when the system (an application) is ready for the interface to become active. The purpose of this function is to set up the hardware interface to start accepting packets from the network and be able to send packets out. The receiver hardware should not be enabled prior to this call.
Notes: | |
---|---|
|
167.2.3. Stop function
static void HRDWR
_stop(struct eth_drv_sc *sc)
This function is the inverse of “start.” It should shut down the hardware, disable the receiver, and keep it from interacting with the physical network.
167.2.4. Control function
static int
HRDWR
_control(
struct eth_drv_sc *sc, unsigned long key,
void *data, int len)
This function is used to perform low-level “control”
operations on the
interface. These operations would typically be initiated via
ioctl()
calls in the BSD
stack, and would be anything that might require the hardware setup to
change (i.e. cannot be performed totally by the
platform-independent layers).
The key
parameter selects the operation, and the
data
and len
params point describe,
as required, some data for the operation in question.
Warning | |
---|---|
Debugging of applications or execution of tests that use low-level filtering is strongly discouraged when connecting over an ethernet connection to RedBoot. In such instances the ethernet device is shared between eCos and RedBoot. Low-level “control” operations instructing the device to filter ethernet packets by IP address, port or VLAN can filter ethernet packets destined to or from RedBoot. Where these is no alternative, the developer must ensure that their application does not filter away ethernet packets to or from RedBoot by adjusting the filters accordingly. e.g. Ensure that the RedBoot TCP port (default 9000) and address are never filtered out.
Certain network tests (e.g. |
Available Operations:
- ETH_DRV_SET_MAC_ADDRESS
- This operation sets the ethernet station address (ESA or MAC) for the device. Normally this address is kept in non-volatile memory and is unique in the world. This function must at least set the interface to use the new address. It may also update the NVM as appropriate.
- ETH_DRV_GET_IF_STATS_UD, ETH_DRV_GET_IF_STATS
These acquire a set of statistical counters from the interface, and write the information into the memory pointed to by
data
. The “UD” variant explicitly instructs the driver to acquire up-to-date values. This is a separate option because doing so may take some time, depending on the hardware.The definition of the data structure is in
cyg/io/eth/eth_drv_stats.h
.This call is typically made by SNMP.
- ETH_DRV_SET_MC_LIST
This entry instructs the device to set up multicast packet filtering to receive only packets addressed to the multicast ESAs in the list pointed to by
data
.The format of the data is a 32-bit count of the ESAs in the list, followed by packed bytes which are the ESAs themselves, thus:
struct eth_drv_mc_list { int len; unsigned char addrs[CYGNUM_IO_ETH_DRIVERS_FILTER_LIST_SIZE][ETHER_ADDR_LEN]; };
Pass an empty list (
len
=0) to clear any existing multicast filters.Some driver/hardware combinations can support a large number of ESAs, which can lead to a very large
struct eth_drv_mc_list
object if all the available address slots are supported. TheCYGNUM_IO_ETH_DRIVERS_FILTER_LIST_SIZE
CDL option can be tuned to reflect the upper limit required by an application configuration to minimise the overhead of passing unnecessarily largestruct eth_drv_mc_list
objects around.- ETH_DRV_SET_MC_ALL
- This entry instructs the device to receive all multicast packets, and delete any explicit filtering which had been set up.
- ETH_DRV_SET_DA_LIST
This entry allows a list of unicast-DA (Destination Address) values to be supplied, and any perfect filtering supported by the underlying driver to be configured appropriately.
The
eth_drv_filter_list_t
structure is used to provide the unicast-DA addresses to replace any existing DA filtering in place.Pass an empty list (
len
=0) to clear any existing unicast-DA filters.- ETH_DRV_SET_SA_LIST
This entry allows a
eth_drv_filter_list_t
supplied list of SA (Source Address) filters to be specified.Pass an empty list (
len
=0) to clear any existing SA filters.- ETH_DRV_FILTER_OPTIONS
This entry is provided as a single API for get/set of multiple filtering options (minimising the number of calls and the code required). It uses a standard AND/EOR approach to provide a single get/set interface.
For example, assuming the variable declaration:
struct eth_drv_options fo;
then the following will perform a GET without changing any flag state:
fo.u.mand = 0xFFFFFFFF; fo.eor = 0x00000000;
To set an explicit value then the corresponding flag bit can be 0 for the AND. e.g.
fo.u.mand = 0x00000000; fo.eor = 0x12345678;
To set an explicit value then the corresponding flag bit can be 0 for the AND. e.g. to set flag bit-0 to regardless of the current state:
fo.u.mand = 0xFFFFFFFE; fo.eor = 0x00000001;
Toggling bits can also be supported. e.g. to toggle bit-2 and bit-4:
fo.u.mand = 0xFFFFFFFF; fo.eor = 0x00000014;
After a successful request the
eth_drv_options
fieldu.val
is updated to reflect the current driver option flag state after any changes that may have been requested.Currently the following flags are defined, but not all drivers will necessarily support all the features:
The
ETH_DRV_FILTER_OPT_PROMISC
flag is provided as an alternative to the existingETH_DRV_SET_PROMISC
key
option, just so that the control of the feature can be managed along with the other flags. It controls promiscuous mode.The
ETH_DRV_FILTER_OPT_BLOCK_BCAST
flag controls whether all broadcast frames are dropped.The
ETH_DRV_FILTER_OPT_INVERSE_DA
controls whether any enabled unicast DA (Destination Address) or multicast filtering (as set viaETH_DRV_SET_DA_LIST
orETH_DRV_SET_MC_LIST
) operates in inverse filtering mode where matches are dropped, and non-matching frames are allowed through.The
ETH_DRV_FILTER_OPT_INVERSE_SA
controls whether any enabled SA (Source Address) filtering (set byETH_DRV_SET_SA_LIST
) operates in inverse filtering mode where SA matches are dropped and non-SA matches allowed.The
ETH_DRV_FILTER_OPT_L4_TCPUDP_ONLY
flag controls whether any enabled L4 filtering will drop all non-TCP and non-UDP packets. e.g. ICMP.- ETH_DRV_SET_FILTER_L3L4
This entry allows a L3 and/or L4 filter to be added. The
struct eth_drv_filter_l3l4
descriptor provides the filter configuration settings. The port numbers, IPv4 and IPv6 addresses must all be provided in network byte order.The
flags
field is a combination of binary (boolean) flags describing the filter to be applied:ETH_DRV_L3L4_L3SRC
is set when the supplied L3 source address (SA) should be used for the match.ETH_DRV_L3L4_L3SRC_IPV6
is used to distinguish the type of SA supplied: unset (0) for IPv4, and set (1) for IPv6.ETH_DRV_L3L4_L3SRC_INV
is set when an inverted SA match should be configured.ETH_DRV_L3L4_L3SRC_MASK
is set when thel3src_mb
SA bitmask should be applied.ETH_DRV_L3L4_L3DST
is set when the supplied L3 destination address (DA) should be used for the match.ETH_DRV_L3L4_L3DST_IPV6
is used to distinguish the type of DA supplied: unset (0) for IPv4, and set (1) for IPv6.ETH_DRV_L3L4_L3DST_INV
is set when an inverted DA match should be configured.ETH_DRV_L3L4_L3DST_MASK
is set when thel3dst_mb
DA bitmask should be applied.ETH_DRV_L3L4_L4SRC
is set when a L4 source port filter should be applied as supplied inl4_src
.ETH_DRV_L3L4_L4SRC_UDP
is set for L4 source UDP match, and unset for TCP.ETH_DRV_L3L4_L4SRC_INV
is set when an inverted L4 source match should be applied.ETH_DRV_L3L4_L4DST
is set when a L4 destination port filter should be applied as supplied inl4_dst
.ETH_DRV_L3L4_L4DST_UDP
is set for L4 destination UDP match, and unset for TCP.ETH_DRV_L3L4_L4DST_INV
is set when an inverted L4 destination match should be applied.Note Not all drivers may support all of the L3/L4 filtering options available in this API. The developer should be aware of the features and limitations of the underlying Ethernet hardware MAC interface (and driver) in use.
- ETH_DRV_CLR_FILTER_L3L4
-
This entry allows a L3/L4 filter to be removed.
The
eth_drv_filter_l3l4
structure should be populated as per the originalETH_DRV_SET_FILTER_L3L4
call. - ETH_DRV_SET_VLANTAG
This operation instructs the device to configure a single, perfect, VLAN Tag filter.
The passed
eth_drv_vlantag
structure defines the VLAN Tag to be set for the filter, along with control flags that can affect the operation. The following values can be ORed into theflags
field to control the filter:ETH_DRV_VLANTAG_FLG_INVERSE
if set configures the filter as an inverted match; where only packets matching the VLAN Tag are dropped.ETH_DRV_VLANTAG_FLG_12BIT
configures the driver to only match against the least-significant 12-bits of the suppliedvt
field.ETH_DRV_VLANTAG_FLG_SVLAN
configures the driver to also accept the S-VLAN Tag (0x88A8
) as a valid match.The special flag
ETH_DRV_VLANTAG_FLG_DISABLE
is used to disable the VLAN Tag driver feature. The otherflags
settings are ignored, as-is thevt
value.- ETH_DRV_GET_VLANTAG
-
This call will return the current VLAN Tag filter setting
in the supplied
eth_drv_vlantag
structure. The extra flagETH_DRV_VLANTAG_FLG_VALID
in the returnedflags
field indicates whether a valid VLAN Tag filter has been set, and whether the contents of the structure can be interpreted. - ETH_DRV_OPTIONS
This entry allows control of the operation of the underlying device driver. It is provided as a single API for get/set of multiple options using a standard AND/EOR approach, though currently only the RX interrupt-vs-polled option is provided.
See
ETH_DRV_FILTER_OPTIONS
for more detail regarding using AND/EOR for get and set operations.The RX operation mode is controlled by multiple bits covered by the
ETH_DRV_OPTION_RX_MODE_MASK
.The
ETH_DRV_OPTION_RX_INT
mode selects interrupt driven RX mode and is the default driver mode.The
ETH_DRV_OPTION_RX_POLL
mode, where supported by the underlying driver, selects a RX polled mode of operation. Normally this would not be desirable, but for some applications the (undefined) interrupt overhead of a high rate of RX activity may adversely affect the performance of other subsystems; such that limiting RX reception (at the cost of increased missed packets) is desired.Note The polled operation is less efficient with CPU bandwidth than the normal interrupt driven driver mode so throughput will be lower when polled mode is selected.
Care should be taken with the driver specific poll-period selected since high-frequency polling when a high-priority networking stack control thread is in use can be just as "bad" as an interrupt storm in denying other threads CPU time.
The use of the
ETH_DRV_OPTION_RX_POLL
mode should be viewed as an option in extremis. Suitable selection of the network thread priority levels, in conjunction with the driver and network stack buffering options, should allow for correct application operation in a well constructed application when present on a RX saturated network. If supported by the driver then H/W filtering options can further reduce the S/W load of the system hopefully avoiding the need to switch to polled RX.The
ETH_DRV_OPTION_RX_AUTO
mode, where supported by the underlying driver, selects a mode of operation where (under driver specific configuration) the driver will switch between interrupt and polled modes depending on the RX activity. This can be used to ensure that if the driver and network stack are receiving high volumes of data that the RX interrupt load of the system can be reduced by throttling RX reception using the polled mode.
This function should return zero if the specified operation was completed successfully. It should return non-zero if the operation could not be performed, for any reason.
167.2.5. Can-send function
static int HRDWR
_can_send(struct eth_drv_sc *sc)
This function is called to determine if it is possible to start the transmission of a packet on the interface. Some interfaces will allow multiple packets to be "queued" and this function allows for the highest possible utilization of that mode.
Return the number of packets which could be accepted at this time, zero implies that the interface is saturated/busy.
167.2.6. Send function
struct eth_drv_sg {
CYG_ADDRESS buf;
CYG_ADDRWORD len;
};
static void
HRDWR
_send(
struct eth_drv_sc *sc,
struct eth_drv_sg *sg_list, int sg_len,
int total_len, unsigned long key)
This function is used to send a packet of data to the network. It is the responsibility of this function to somehow hand the data over to the hardware interface. This will most likely require copying, but just the address/length values could be used by smart hardware.
Note | |
---|---|
All data in/out of the driver is specified via a
“scatter-gather”
list. This is just an array of address/length pairs which describe
sections of data to move (in the order given by the array), as in the
struct eth_drv_sg defined above and pointed to by
|
Once the data has been successfully sent by the interface (or if an
error occurs), the driver should call
(sc->funs->eth_drv->tx_done)()
(see Section 167.3.2, “Callback Tx-Done function”)
using the specified key
.
Only then will the upper layers release the resources
for that packet and start another transmission.
Note | |
---|---|
In future, this function may be extended so that the data need not be copied by having the function return a “disposition” code (done, send pending, etc). At this point, you should move the data to some “safe” location before returning. |
167.2.7. Deliver function
static void
HRDWR
_deliver(struct eth_drv_sc *sc)
This function is called from the “Network Delivery Thread” in order to let the device driver do the time-consuming work associated with receiving a packet — usually copying the entire packet from the hardware or a special memory location into the network stack's memory.
After handling any outstanding incoming packets or pending transmission status, it can unmask the device's interrupts, and free any relevant resources so it can process further packets.
It will be called when the interrupt handler for the network device has called
eth_drv_dsr( vector, count, (cyg_addrword_t)sc );
to alert the system that “something requires attention.”
This eth_drv_dsr()
call must occur from within the
interrupt handler's DSR (not the ISR) or actually be
the DSR, whenever it is determined that
the device needs attention from the foreground. The third parameter
(data
in the prototype of
eth_drv_dsr()
must
be a valid struct eth_drv_sc pointer sc
.
The reason for this slightly convoluted train of events is to keep the DSR (and ISR) execution time as short as possible, so that other activities of higher priority than network servicing are not denied the CPU by network traffic.
To deliver a newly-received packet into the network stack, the deliver routine must call the following which will in turn call the receive function, which we talk about next.
(sc->funs->eth_drv->recv)(sc, len);
See also Section 167.3.3, “Callback Receive function” below.
167.2.8. Receive function
static void
HRDWR
_recv(
struct eth_drv_sc *sc,
struct eth_drv_sg *sg_list, int sg_len)
This function is a call back, only invoked after the upper-level function
(sc->funs->eth_drv->recv)(struct eth_drv_sc *sc, int total_len)
has been called itself from your deliver function when it knows that a
packet of data is available on the
interface. The (sc->funs->eth_drv->recv)()
function then arranges network buffers
and structures for the data and then calls
to actually
move the data from the interface.
HRDWR
_recv()
A scatter-gather list (struct eth_drv_sg) is used once more, just like in the send case.
167.2.9. Poll function
static void
HRDWR
_poll(struct eth_drv_sc *sc)
This function is used when in a non-interrupt driven system, e.g. when interrupts are completely disabled. This allows the driver time to check whether anything needs doing either for transmission, or to check if anything has been received, or if any other processing needs doing.
It is perfectly correct and acceptable for the poll function to look like this:
static voidHRDWR
_poll(struct eth_drv_sc *sc) {my_interrupt_ISR
(sc);HRDWR
_deliver(struct eth_drv_sc *sc); }
provided that both the ISR and the deliver functions are idempotent and harmless if called when there is no attention needed by the hardware. Some devices might not need a call to the ISR here if the deliver function contains all the “intelligence.”
167.2.10. Interrupt-vector function
static int
HRDWR
_int_vector(struct eth_drv_sc *sc)
This function returns the interrupt vector number used for receive interrupts. This is so that the common GDB stubs can detect when to check for incoming “CTRL-C” packets (used to asynchronously halt the application) when debugging over ethernet. The GDB stubs need to know which interrupt the ethernet device uses so that they can mask or unmask that interrupt as required.
167.3. Upper Layer Functions
Upper layer functions are called by drivers to deliver received packets or transmission completion status back up into the network stack.
These functions are defined by the hardware independent upper layers of the networking driver support. They are present to hide the interfaces to the actual networking stack so that the hardware drivers may be used by different network stack implementations without change.
These functions require a pointer to a struct eth_drv_sc which describes the interface at a logical level. It is assumed that the low level hardware driver will keep track of this pointer so it may be passed “up” as appropriate.
167.3.1. Callback Init function
void (sc->funs->eth_drv->init)( struct eth_drv_sc *sc, unsigned char *enaddr)
This function establishes the device at initialization time. It should be called once per device instance only, from the initialization function, if all is well (see Section 167.2.1, “Init function”). The hardware should be totally initialized (not “started”) when this function is called.
167.3.2. Callback Tx-Done function
void (sc->funs->eth_drv->tx_done)( struct eth_drv_sc *sc, unsigned long key, int status)
This function is called when a packet completes transmission on the
interface. The key
value must be one of the keys provided to
above. The value HRDWR
_send()status
should be non-zero
(details currently undefined) to indicate that an error occurred during the
transmission, and zero if all was well.
It should be called from the deliver function (see Section 167.2.7, “Deliver function”) or poll function (see Section 167.2.9, “Poll function”).
167.3.3. Callback Receive function
void (sc->funs->eth_drv->recv)(struct eth_drv_sc *sc, int len)
This function is called to indicate that a packet of length
len
has
arrived at the interface.
The callback
function
described above will be used to actually unload the data from the
interface into buffers used by the device independent layers.
HRDWR
_recv()
It should be called from the deliver function (see Section 167.2.7, “Deliver function”) or poll function (see Section 167.2.9, “Poll function”).
167.4. Calling graph for Transmission and Reception
It may be worth clarifying further the flow of control in the transmit and receive cases, where the hardware driver does use interrupts and so DSRs to tell the “foreground” when something asynchronous has occurred.
167.4.1. Transmission
- Some foreground task such as the application, SNMP “daemon”, DHCP management thread or whatever, calls into network stack to send a packet, or the stack decides to send a packet in response to incoming traffic such as a “ping” or ARP request.
-
The driver calls the
function in the hardware driver.HRDWR
_can_send() -
returns the number of available "slots" in which it can store a pending transmit packet. If it cannot send at this time, the packet is queued outside the hardware driver for later; in this case, the hardware is already busy transmitting, so expect an interrupt as described below for completion of the packet currently outgoing.HRDWR
_can_send() -
If it can send right now,
HRDWR
_send() is called.
copies the data into special hardware buffers, or instructs the hardware to “send that.” It also remembers the key that is associated with this tx request.HRDWR
_send() - These calls return … time passes …
- Asynchronously, the hardware makes an interrupt to say “transmit is done.” The ISR quietens the interrupt source in the hardware and requests that the associated DSR be run.
-
The DSR calls (or is) the
eth_drv_dsr()
function in the generic driver. -
eth_drv_dsr()
in the generic driver awakens the “Network Delivery Thread” which calls the deliver functionHRDWR
_deliver() in the driver. -
The deliver function realizes that a transmit request has completed,
and calls the callback tx-done function
(sc->funs->eth_drv->tx_done)()
with the same key that it remembered for this tx. - The callback tx-done function uses the key to find the resources associated with this transmit request; thus the stack knows that the transmit has completed and its resources can be freed.
-
The callback tx-done function
also enquires whether
HRDWR
_can_send() now says “yes, we can send” and if so, dequeues a further transmit request which may have been queued as described above. If so, thenHRDWR
_send() copies the data into the hardware buffers, or instructs the hardware to "send that" and remembers the new key, as above. These calls then all return to the “Network Delivery Thread” which then sleeps, awaiting the next asynchronous event. - All done …
167.4.2. Receive
- Asynchronously, the hardware makes an interrupt to say “there is ready data in a receive buffer.” The ISR quietens the interrupt source in the hardware and requests that the associated DSR be run.
-
The DSR calls (or is) the
eth_drv_dsr()
function in the generic driver. -
eth_drv_dsr()
in the generic driver awakens the “Network Delivery Thread” which calls the deliver functionHRDWR
_deliver() in the driver. -
The deliver function realizes that there is data ready and calls
the callback receive function
(sc->funs->eth_drv->recv)()
to tell it how many bytes to prepare for. - The callback receive function allocates memory within the stack (eg. MBUFs in BSD/Unix style stacks) and prepares a set of scatter-gather buffers that can accommodate the packet.
-
It then calls back into the hardware driver routine
HRDWR
_recv().HRDWR
_recv() must copy the data from the hardware's buffers into the scatter-gather buffers provided, and return. - The network stack now has the data in-hand, and does with it what it will. This might include recursive calls to transmit a response packet. When this all is done, these calls return, and the “Network Delivery Thread” sleeps once more, awaiting the next asynchronous event.
2024-03-18 | Open Publication License |