Chapter 168. lwIP Direct Ethernet Device Driver

168.1. Introduction

This chapter provides a simple description of the basic requirements for a low-level, hardware specific, lwIP-direct ethernet driver.

Using a lwIP-direct driver provides benefits in performance and smaller code- and memory-footprints. It also allows for the potential for zero-copy UDP support and reduced (single) copy TCP support depending on the hardware available. The main disadvantage over the standard ethernet driver world is the lack of RedBoot network debugging support.

The high-level driver implemented by this package (which is only code, without state of its own) is used to provide a common interface for lwIP to either a lwIP-specific direct driver (as described in this chapter), or via a wrapper interface to a standard generic ethernet driver (covered by Section 167.1, “Generic Ethernet API”).

Unlike the generic ethernet (standard) device driver support the lwIP device driver interface uses a fixed namespace between the lwIP and driver layers. Normally only a single driver instance exists for a lwIP configured world, so the use of a fixed namespace is, in reality, not an issue since lwIP is designed for lightweight, low resource, deeply-embedded systems. If a target platform really does provide more than one distinct ethernet hardware implementation, requiring completely different hardware drivers, then a wrapper layer conforming to the “direct” driver interface is provided when the option CYGFUN_IO_ETH_DRIVERS_LWIP_DRIVER_DIRECT_MULTI is configured. This implements a per-driver descriptor interface between the individual low-level hardware interfaces for the platform and this common Ethernet I/O package.

Normally a direct driver implementation will also provide a driver specific header file which is referenced from the lwIP CDL option CYGBLD_LWIP_HW_DRIVER_OVERRIDE_HEADER. The CDL covering the direct driver package should explicitly set the value to the required header file name. Similarly when support is configured for multiple direct drivers, the CDL option CYGBLD_LWIP_VARIANT_OVERRIDE_HEADER can be used to reference a header providing any needed platform/variant/driver specific features.

These header files can be used to provide access to prototypes and manifests needed to support specific lwIP features as required. For example, if the hardware driver uses DMA, and requires timely support for re-using PBUFs once lwIP has finished processing them, then the ECOS_LWIP_PBUF_POOL_FREE_HOOK manifest can be defined to reference a callback function (See DRV_HDWR_pbuf_pool_free_hook()).

The following sections give an overview of the small set of functions that the driver needs to provide to be usable by this package. When the multiple direct driver support is being used then these named functions are provided by this common CYGPKG_IO_ETH_DRIVERS I/O Ethernet package, with a per-driver descriptor structure used to reference the specific driver implementations (See Section 168.3, “Multiple direct drivers”).

168.2. API reference

cyg_lwip_eth_ecos_init() — Initialize the hardware driver
cyg_lwip_eth_low_level_output() — Transmit a packet
cyg_lwip_eth_run_deliveries() — Packet buffer house-keeping
cyg_lwip_eth_ioctl() — Control interface
DRV_HDWR_pbuf_pool_free_hook() — PBUF free hook callback

The following function definitions document the namespace used by the eCos lwIP TCP/IP stack to interact with hardware drivers.

168.3. Multiple direct drivers

When support for multiple direct drivers is configured then a driver instance is contained within a cyg_lwip_eth_t structure:

typedef struct cyg_lwip_eth {
  const char *name; // NUL terminated ASCII human-readable name
  void (*init)(struct cyg_lwip_eth *drvdesc);
  void (*run_deliveries)(void *instance);
  err_t (*ll_output)(struct netif *netif,struct pbuf *p);
  int (*pbuf_free_hook)(void *instance,struct pbuf *p);
  void (*phy_event)(struct netif *netif);
  int (*ioctl)(struct netif *netif,unsigned long key,void *data,int data_length);
  void *instance;
  cyg_uint32 flags;
} CYG_HAL_TABLE_TYPE cyg_lwip_eth_t;

This CYGPKG_IO_ETH_DRIVERS package will implement the wrapper namespace to support lwIP, calling the relevant individual device driver registered functions as required.

You create an instance of cyg_lwip_eth_t using the CYG_LWIP_DRIVER macro, which sets up the structure. Using this macro ensures that if the internal design changes then existing source will fail to compile until updated to reflect the changed functionality. This is better than having definitions within the low-level drivers themselves, with the possibility of them building successfully but then failing at run-time.

The individual hardware drivers are initialised automatically via the wrapper provided cyg_lwip_eth_ecos_init() function, which iterates over the __LWIPDEVTAB__ vector containing the driver instance descriptors as required.

[Note]Note

When lwIP direct drivers are written to support CYGFUN_IO_ETH_DRIVERS_LWIP_DRIVER_DIRECT_MULTI configurations they MUST reference their cyg_lwip_eth_t descriptor via the state field of the struct netif describing the lwIP network interface. The instance field of the cyg_lwip_eth_t can be used to hold driver specific instance data.

The function pointers referenced from the cyg_lwip_eth_t descriptor closely match the raw namespace, with the exception that initialisation is passed the cyg_lwip_eth_t driver descriptor pointer, and the run_deliveries and pbuf_free_hook implementations are passed the private instance pointer. This ensures that the individual driver implementation can access the necessary state as would be the case for a single driver configuration.

[Note]Note

For the pbuf_free_hook support we should ideally pass the pbuf back to the original driver instance that allocated that specific pbuf. However, for the moment, the code just offers the pbuf to each configured driver in turn (the alternative would introduce complexity into the driver model for minimal gains).

This “do you want this pbuf” approach does not affect the behaviour, only the performance, of the driver when used in a multi-driver configuration. If the developer needs to ensure that a particular driver instance is “higher priority” than other lwIP Ethernet drivers for pbuf re-use then they should enforce a mechanism for ensuring the ordering of the __LWIPDEVTAB__device table.

168.4. lwIP MANUAL initialisation

Normally lwIP will default to DHCP for network interface address acquisition, but alternative methods can be configured (AUTOIP, STATIC or MANUAL). The relevant configuration specific interface initialisation code is actually performed in this common IO Ethernet package by the cyg_lwip_eth_drv_init_netif() function. When configured to use fixed STATIC addresses those are held in the eCos configuration file for the build. The MANUAL option, however, allows for the application code to manually supply address information and perform the interface initialisation.

When MANUAL address configuration is selected for an lwIP interface then an explicitly named function must be supplied by the application run-time, with the prototype:

char cyg_lwip_eth_init_manual(struct netif *netif, char inum, unsigned char *enaddr);

The netif parameter references the underlying lwIP network interface descriptor, with the parameter inum being the logical (indexed from 0) interface number. The enaddr references the IEEE MAC address for the interface.

It is expected that the application supplied routine will set the address configuration et al., before adding the interface, based on some per-device stored/calculated values.

It is expected that if manual application interface initialisation is being used that the developer has a reasonable understanding of lwIP and its internal requirements, and is au fait with the eCos network source base.

The following is a simple example implementation of the basic operations that need to be performed by the application to provide MANUAL interface support:

char cyg_lwip_eth_init_manual(struct netif *netif, char inum, unsigned char *enaddr)
{
    ip4_addr_t ipaddr;
    ip4_addr_t netmask;
    ip4_addr_t gw;

    application_code_to_fill_addresses_for_interface_number(inum, &ipaddr, &netmask, &gw);

    char ok = (NULL != netif_add((netif),
                                 &ipaddr,
                                 &netmask,
                                 &gw,
                                 (netif)->state,
                                 cyg_lwip_eth_netif_init,
                                 ethernet_input));

    if (ok) {
#if LWIP_CHECKSUM_CTRL_PER_NETIF // per-interface checksum offload control
        // Set following as desired for the application configuration, or the
        // target H/W driver feature support:
        (netif)->chksum_flags = NETIF_CHECKSUM_DISABLE_ALL;
#endif // LWIP_CHECKSUM_CTRL_PER_NETIF
        netif_set_up(netif);
    }

    return ok;
}