TOC PREV NEXT INDEX

Writing Device Drivers for LynxOS


Network Device Drivers

A network driver is defined as a link-level device driver that interfaces with the LynxOS TCP/IP module. Unlike other drivers, a network driver does not interface directly with user applications. It interfaces instead with the LynxOS TCP/IP module. This interface is defined by a set of driver entry points and data structures, described in the following sections.

Kernel threads play an important role in LynxOS networking software, not only within the drivers, but also as part of the TCP/IP module.

The example code below illustrates these points for an Ethernet device. These examples can easily be adapted to other technologies.

Kernel Data Structures

A network driver must make use of a number of kernel data structures. Each of these structures is briefly described here and its use is further illustrated.

A driver must include the following header files, which define these structures and various symbolic constants used in the rest of this chapter:

#include <types.h>
#include <io.h>
#include <ioctl.h>
#include <socket.h>
#include <bsd/in.h>
#include <bsd/if.h>
#include <bsd/if_ether.h>
#include <bsd/in_var.h
#include <bsd/bsd_mbuf.h>
#include <bsd/netisr.h>

struct ether_header

The Ethernet header must be prefixed to every outgoing packet. It specifies the destination and source Ethernet addresses and a packet type. The symbolic constants ETHERTYPE_IP, ETHERTYPE_ARP and ETHERTYPE_RARP can be used for the packet type.

struct ether_header {
  u_char  ether_dhost[6]; /* dest Ethernet addr */
  u_char  ether_shost[6]; /* source Ethernet addr */
  u_short ether_type; /* Ethernet packet type */
}

struct arpcom

The arpcom structure is used for communication between the TCP/IP module and the network interface driver. It contains the ifnet structure (described below) and the interface's Ethernet and Internet addresses. This structure must be the first element in the statics structure.

struct arpcom {
  struct ifnet ac_if;/* network visible interface */
  u_char ac_enaddr[6]; /* Ethernet address */
  struct in_addr ac_ipaddr; /* Internet address */
  struct ether_multi *ac_multiaddrs;
/* list of ether multicast addrs */
  int ac_multicnt;/* length of ac_multiaddrs list */
};

struct sockaddr

This is a generic structure for specifying socket addresses, containing an address family field and up to 14 bytes of protocol-specific address data.

struct sockaddr {
  u_char sa_len; /* total length */
  u_char  sa_family; /* address family */
  char sa_data[14]; /* longer; addr value */
};

struct sockaddr_in

A structure used for specifying socket addresses for the Internet protocol family

struct sockaddr_in {
  u_char sin_len;
  u_char sin_family; /* always AF_INET */
  u_short sin_port; /* port number */
  struct in_addr sin_addr; /* host Internet addr */
  char sin_zero[8];
};

struct in_addr

Structure specifying a 32 bit host Internet address

struct in_addr {
  u_long s_addr;
};

struct ifnet

This is the principle data structure used to communicate between the driver and the TCP/IP module. struct ifnet is defined in /usr/include/bsd/if_var.h. It provides the TCP/IP software with a generic hardware-independent interface to the network drivers. It specifies a number of entry points that the TCP/IP module can call in the driver, a flag variable indicating general characteristics and current state of the interface, a queue for outgoing packets, and a number of statistics counters.

struct ifnet {
  char *if_name; /* name, e.g. "wd" or "oblan" */
  char *p; /* user defined field */
  struct ifnet *if_next;
  /* all struct ifnets are chained */
  struct ifaddr *if_addrlist;
  /* linked list of addresses */
  int if_pcount; /* number of promiscuous listeners */
  caddr_t if_bpf; /* packet filter structure */
  u_short if_index; /* numeric abbreviation for if */
  short if_unit; /* sub-unit for lower level driver */
  short if_timer; /* time `til if_-watchdog called */
  short if_flags; /* up/down, broadcast, etc. */

  struct if_data {
    /* generic interface information */
    u_char ifi_type; /* Ethernet, tokenring etc */
    u_char ifi_addrlen; /* media address length */
    u_char ifi_hdrlen; /* media header length */
    u_long ifi_mtu; /* maximum transmission unit */
    u_long ifi_metric;/* routing metric (external) */
    u_long ifi_baudrate; /* line speed */

    /* volatile statistics */
    u_long ifi_ipackets;/* packets received on i/f */
    u_long ifi_ierrors; /* input errors on i/f */
    u_long ifi_opackets; /* packets sent on i/f */
    u_long ifi_oerrors; /* ouput errors on i/f */
    u_long ifi_collisions;
    /* collisions on csma i/f */
    u_long ifi_ibytes;
    /* total number of bytes received */
    u_long ifi_obytes;
    /* total number of octets sent */
    u_long ifi_imcasts;
    /* packets received via multi-cast */
    u_long ifi_omcasts;
    /* packets sent via multi-cast */
    u_long ifi_iqdrops;
    /* dropped on input, this interface */
    u_long ifi_noproto;
    /* destined for unsupported protocol */
    struct timeval ifi_lastchange;/* last updated */
  } if_data;

  /* procedure handles */
  int (*if_init)(); /* init routine */
  int (*if_output)(); /* output routine */
  int (*if_start)(); /* initiate output routine */
  int (*if_done)(); /* output complete routine */
  int (*if_ioctl)(); /* ioctl routine */
  int (*if_reset)(); /* bus reset routine */
  int (*if_watchdog)(); /* timer routine */
  int (*if_setprio)(); /* prio tracking of kthread */

  /* output queue */
  struct ifqueue {
    struct mbuf *ifq_head;
    struct mbuf *ifq_tail;
    int  ifq_len;
    int  ifq_maxlen;
    int  ifq_drops;
  } if_snd;
  struct raweth *if_raweth;
};

The symbolic constants IFF_UP, IFF_RUNNING, and IFF_BROADCAST can be used to set bits in the if_flags field.

Looking at the arpcom structure, notice that the first member is an ifnet structure. A driver should declare a struct arpcom as part of the statics structure and use the ifnet structure within this. There is an important reason for this, explained in "ioctl Entry Point".

struct mbuf

Data packets are passed between the TCP/IP module and a network interface driver using mbuf structures. This structure is designed to allow the efficient encapsulation and decapsulation of protocol packets without copying data. A number of functions and macros are defined in mbuf.h for using mbuf structures.

/* header at beginning of each mbuf: */
struct m_hdr {
  struct   mbuf *mh_next;      /* next buffer in chain */
  struct   mbuf *mh_nextpkt;   /* next chain in queue */
  int      mh_len;             /* amount of data in this mbuf */
  caddr_t  mh_data;            /* location of data */
  short    mh_type;            /* type of data in this mbuf */
  short    mh_flags;           /* flags; see below */
};

/* record/packet header in first mbuf of chain;
* valid if M_PKTHDR set
*/

struct pkthdr {
  int len;                  /* total packet length */
  struct ifnet *rcvif;     /* rcv interface */
};

/* description of external storage mapped into mbuf,
* valid if M_EXT set
*/

struct m_ext {
  caddr_t ext_buf;           /* start of buffer */
  void (*ext_free)();        /* free routine */
  u_int ext_size;          /* size of buffer, for ext_free */
};

struct mbuf {
  struct m_hdr m_hdr;
  union {
    struct {
      struct pkthdr MH_pkthdr; /* M_PKTHDR set */
      union {
        struct m_ext MH_ext; /* M_EXT set */
        char MH_databuf[MHLEN];
      } MH_dat;
    } MH;
    char M_databuf[MLEN]; /* !M_PKTHDR, !M_EXT */
  } M_dat;
};

#define m_next m_hdr.mh_next
#define m_len m_hdr.mh_len
#define m_data m_hdr.mh_data
#define m_type m_hdr.mh_type
#define m_flags m_hdr.mh_flags
#define m_nextpkt m_hdr.mh_nextpkt
#define m_act m_nextpkt
#define m_pkthdr M_dat.MH.MH_pkthdr
#define m_ext M_dat.MH.MH_dat.MH_ext
#define m_pktdat M_dat.MH.MH_dat.MH_databuf
#define m_dat M_dat.M_databuf

Adding or Removing Data in an mbuf

The position and size of data currently in an mbuf are identified by a pointer and a length. By changing these values, data can be added or deleted at the beginning or end of the mbuf. A pointer to the start of the data in the mbuf can be obtained using the mtod macro. The pointer is cast as an arbitrary data type, specified as an argument to the macro. For example:

char *cp;
struct mbuf *mb;

cp = mtod (mb, char *);
/* get pointer to data in mbuf */

The macro dtom takes a pointer to data placed anywhere within the data portion of the mbuf and returns a pointer to the mbuf structure itself. For example, if we know that cp points within the data area of an mbuf, the sequence will be:

struct mbuf *mb;
char *cp;

mb = dtom(cp);

Data is added to the head of an mbuf by decrementing the m_data pointer, incrementing the m_len value and copying the data using a function such as bcopy. Data is added to the tail of an mbuf in a similar manner by incrementing the m_len value. The ability to add data to the tail of an mbuf is useful for implementing trailer protocols; LynxOS does not currently support such protocols.

Data is removed from the head or tail of an mbuf by simply incrementing the m_data pointer or decrementing m_len.

Allocating mbufs

The above examples did not discuss what to do when sufficient space is not available in an mbuf to add data. In this case, a new mbuf can be allocated using the function m_get. The new mbuf is linked onto the existing mbuf chain using its m_next field. m_get can be replaced with MGET, which is a macro rather than a function call. MGET produces faster code whereas m_get results in smaller code. The example to add data to the beginning of a packet now becomes:

struct mbuf *m;
caddr_t src, dst;

MGET(m, M_DONTWAIT, MT_HEADER);
if (m == NULL)
  return (ENOBUFS);
dst = mtod (m, caddr_t);
bcopy (src, dst, n);

The second argument to m_get or MGET specifies whether the function should block or return an error when no mbufs are available. A driver should use M_DONTWAIT, which causes the mbuf pointer to be set to 0 if no mbufs are free.

The third argument to m_get or MGET specifies how the mbuf will be used. This is for statistical purposes only and is used, for example, by the command netstat m. The types used by a network driver are MT_HEADER for protocol headers and MT_DATA for data packets.

mbuf Clusters

When receiving packets from a network interface, a driver must allocate mbufs to store the data. If the data packets are large enough, a structure known as an mbuf cluster can be used. A cluster can hold more data than a regular mbuf; MCLBYTES bytes as opposed to MLEN. As a rule, there is benefit to be gained from using a cluster if the packet is larger than MCLBYTES/2.

Freeing mbufs

Because there is a limited number of mbufs in the system, the driver must take care to free mbufs at appropriate points. These are listed below:

Packet Output:

Packet Input:

The sections "Packet Input" and "Packet Output" show appropriate code examples for each of the above situations. Failure to free them will eventually lead to the system running out of mbufs.

Summary of Commonly Used mbuf Macros  
Macro
Description
MCLGET
Get a cluster and set the data pointer of the mbuf to point to the cluster.
MFREE
Free the mbuf. On return, the mbuf successor (pointed to by
m->m_next) is stored in the second argument.
MGETHDR
Allocate an mbuf and initialize it as a packet header.
MH_ALIGN
Set the m_data pointer to an mbuf containing a packet header to place an object of the specified size at the end of mbuf, longword aligned.
M_PREPEND
Prepend specified bytes of data in front of the data in the mbuf.
dtom
Convert the data pointer within mbuf to mbuf pointer.
mtod
Convert mbuf pointer to data pointer of specified type.

Note: MCLGET, MFREE, MGETHDR, MH_ALIGN, and M_PREPEND are protected by network semaphore lock.

Summary of Commonly Used mbuf Functions
Function
Description
m_adj
Remove data from mbuf at start or end.
m_cat
Concatenate one mbuf chain to another.
m_copy
Version of m_copym that does not wait
m_copydata
Copy data from mbuf chain to a buffer.
m_copyback
Copy data from buffer to an mbuf chain.
m_copym
Create a new mbuf chain from an existing mbuf chain.
m_devget
Create a new mbuf chain with a packet header from data in a buffer.
m_free
A function version of MFREE macro
m_freem
Free all mbufs in a chain.
m_get
A functional version of MGET macro.
m_getclr
Get an mbuf and clear the buffer.
m_gethdr
A function version of the MGETHDR macro.
m_pullup
Pull up data so that a certain number of bytes of data are stored contiguously in the first mbuf in the chain.

Statics Structure

In keeping with the general design philosophy of LynxOS drivers, network drivers should define a statics structure for all device-specific information. However, the TCP/IP software has no knowledge of this structure, which is specific to each network interface, and does not pass it as an argument to the driver entry points.

The solution to this situation is for the ifnet structure to be contained within the statics structure. The user-defined field, p, in the ifnet structure, is initialized to contain the address of the statics structure.

Given the address of the ifnet structure passed to the entry point from the TCP/IP software, the driver can obtain the address of the statics structure as follows:

struct ifnet *ifp;
struct statics *s = (struct statics *) ifp->p;

The arpcom structure must be the first element in the statics structure. In the code examples below, the arpcom structure is named ac.

Packet Queues

A number of queues are used for transferring data between the interface and the TCP/IP software. There is an output queue for each interface, contained in the ifnet structure. There are two input queues used by all network interfaces. One for IP packets and another for ARP/RARP packets. All queues are accessed using the macros IF_ENQUEUE, IF_DEQUEUE, IF_QFULL, or IF_DROP.

Driver Entry Points

A network driver contains the following entry points:

Network Driver Entry Points  
Entry Point
Description
install/uninstall
Called by the kernel in the usual manner. The install routine must perform a number of tasks specific to network drivers.
interrupt handler
The interrupt handler is declared and called in exactly the same manner as for other drivers.
output
Called by TCP/IP software to transmit packets on the network interface
ioctl
Called by TCP/IP software to perform a number of commands on the network interface.
watchdog
Called by the TCP/IP software after a user-specified timeout period.
reset
Called by the kernel during the reboot sequence.
setprio
Called by the TCP/IP software to implement priority tracking.

By convention, the entry point names are prefixed with the driver name.

install Entry Point

In addition to the usual things done in the install routine (allocation and initialization of the statics structure, declaration of interrupt handler, etc.), the driver must also fill in the fields of the ifnet structure and make the interface known to the TCP/IP software. Note also that hardware initialization is normally done in the ioctl routine rather than in the install routine.

Finding the Interface Name

The install routine must initialize the if_name field in the ifnet structure. This is the name by which the interface is known to the TCP/IP software. It is used, for example, as an argument to the ifconfig and netstat utilities.

The interface name is a user-defined field specified in the driver's device configuration file (drvr.cfg) in the /sys/lynx.os directory. The usual technique used by the driver to find this field is to search the ucdevsw table for an entry with a matching device information structure address. ucdevsw is a kernel table containing entries for all the character devices declared in the CONFIG.TBL file. The kernel variable nucdevsw gives the size of this table.

extern int nucdevsw;
extern struct udevsw_entry ucdevsw[];
struct statics *s;
struct ifnet *ifp;

ifp->if_name = (char *) 0;
for (i = 0; i < nucdevsw; i++) {
  if (ucdevsw[i].info == (char *) info) {
    if (strlen (ucdevsw[i].name) > IFNAMSIZ) {
      sysfree (s, (long) sizeof (struct statics));
      return ((char *) SYSERR);
    }
    ifp->if_name = ucdevsw[i].name;
    break;
  }
}
if (ifp->if_name == (char*) 0) {
  sysfree (s, (long) sizeof (struct statics));
  return ((char *) SYSERR);
}

Note: This method only works for statically installed drivers. Dynamically installed drivers do not have an entry in the ucdevsw table.

Initializing the Ethernet Address

The ac_enaddr field in the arpcom structure is used to hold the interface's Ethernet address, which must be included in the Ethernet header added to all outgoing packets. The install routine should initialize this field by reading the Ethernet address from the hardware.

struct statics *s;

get_ether_addr (&s->ac.ac_enaddr);

In the above example, get_ether_addr would be a user written function that reads the Ethernet address from the hardware.

Initializing the ifnet Structure

The various fields in the ifnet structure should be initialized to appropriate values. Unused fields should be set to zero or NULL. Once the structure has been initialized, the network interface is made known to the TCP/IP module by calling the appropriate network interface attach routine, for example ether_ifattach() for Ethernet drivers.

struct statics *s;
struct ifnet *ifp;

ifp->if_timer = 0 ;
ifp->p = (char *) s;/* address of statics structure */
ifp->if_unit = 0;
ifp->if_init = NULL;
ifp->if_output = ether_output;
ifp->if_ioctl = drvr_ioctl;
ifp->if_reset = drvr_reset;
ifp->if_start = drvr_start;
ifp->if_setprio = drvr_setprio;
ifp->if_watchdog = drvr_watchdog;

ether_ifattach (ifp);

Note that the if_output handle in the ifnet structure should point to the ether_output routine in the TCP/IP module. Previously it pointed to the driver specific local routine. In BSD 4.4, most of the hardware-independent output code has been moved to the ether_output routine. After the ether_output routine has packaged the data for output, it calls a start routine specified by if_start, a member of the interface ifnet structure. For example:

ifp->if_start = lanstart;

Packet Output

The processing of outgoing packets is divided into two parts. The first part concerns the TCP/IP module, which is responsible for queueing the packet on the interface's output queue. The actual transmission of packets to the hardware is handled by the driver start routine and the kernel thread. The driver is responsible in all cases for freeing the mbufs holding the data once the packet has been transmitted or when an error is encountered.

ether_output Function

A number of tasks previously performed by the driver output routine are now done in TCP/IP module by ether_output routine. Thus the if_output field of the interface ifnet structure is initialized to the address of the ether_output routine in the driver install routine:

ifp->if_output = ether_output;

This causes ether_output routine to be called indirectly when the TCP/IP module has a packet to transmit. After enqueuing the packet to transmit, ether_output calls a device-specific function indirectly through the if_start pointer in the ifnet structure. For example, if ifp points to an ifnet structure,

(*ifp->if_start)(ifp),

the if_start field is also initialized by the driver install routine. The driver start routine starts output on the interface if resources are available. Before removing packets from the output queue for transmission, the code normally has to test whether the transmitter is idle and ready to accept a new packet. It typically dequeues a packet (which is enqueued by ether_output) and transmits it.

if (ds->xmt_pending) {
/* if already one transmission is in progress */
  return 1;
}
IF_DEQUEUE(&ifp->if_snd, m);
if (m == 0) {
  return 0;
}
ds->xmt_pending = 1;

...
...
/* Initiate transmission if using Berkeley packet filter */
if (ifp->if_bpf)
  bpf_mtup(m)
return 0;

One important point to consider is that the start routine can now be called by the TCP/IP module (by way of ether_output) and the driver stream task upon receiving an interrupt. Thus, the start routine must protect code and data in the critical area. For example, it could check a pending flag, which is set before starting to transmit, and cleared when a transmit done interrupt is received. If the transmit start routine is not reentrant, it could signal a semaphore in order to notify the driver's kernel thread that packets are now available on the output queue. The routine should then return 0 to indicate success. For example:

ssignal(&s->thread_sem);
return (0);

Also note that the total data available in an mbuf can be obtained from the mbuf packet header. For example:

/* put mbuf data into TFD data area */
length = m->m_pkthdr.len;
m_copydata(m, 0, length, p);
m_freem(m);

Kernel Thread Processing

The kernel thread must perform the following activities relating to packet output.

Starting Transmission

As explained above, the kernel thread also calls the driver start routine to start transmission. The transmit start routine dequeues a packet from the interface send queue and transmits it.

Statistics Counters

The counters relating to packet output are the if_opackets, if_oerrors, and if_collisions fields in the ifnet structure. The if_opackets counter should be incremented for each packet that is successfully transmitted by the interface without error. If the interface indicates that an error occurred during transmission, the if_oerrors counter should be incremented. The driver should also interrogate the interface to determine how many collisions, if any, occurred during transmission. A collision is not necessarily an error condition. An interface normally makes a number of attempts to send a packet before raising an error condition.

Packet Input

When packets are received by a network interface they must be copied from the device to mbufs and passed to the TCP/IP software. Because this can take a significant amount of time, the bulk of the processing of incoming packets should be done by the driver's kernel thread so that it does not impact the system's real-time performance. The interrupt handler routine should do the minimum necessary to ensure that the interface continues to function correctly.

To maintain bounded system response times, the interrupt handler should also disable further interrupts from the interface. These will be re-enabled by the driver's kernel thread. The processing of input packets involves the following activities:

Determining Packet Type

The packet type is specified in the Ethernet header and is used by the driver to determine where to send the packet received. In the following code, the ptr variable is assumed to be a pointer to the start of the received Ethernet frame. The use of the ntohs function ensures the portability of code across different CPU architectures.

ether_type = ntohs (((struct ether_header *)ptr)>ether_type);

Copying Data to mbufs

Most network devices have local RAM, which is visible to the device driver. On packet reception, the driver must allocate sufficient mbufs to hold the received packet, copy the data to the mbufs, then pass the mbuf chain to the TCP/IP software. The ifnet structure is added to the start of the packet so that the upper layers can easily identify the originating interface. The Ethernet header must be stripped from the received packet. This can be achieved simply by not copying it into the mbuf(s). If the entire packet can not be copied, any allocated mbufs must be freed. The following code outlines how a packet is copied from the hardware to mbufs using the m_devget routine. m_devget is called with the address and the size of the buffer that contains the received packet. It creates a new mbuf chain and returns the pointer to the chain.

m = m_devget(buf, len, 0, ifp, 0);

ifp is the device interface pointer. The variable buf points to the received data. This is usually an address in the interface's local RAM.

By default, m_devget uses bcopy, which copies data one byte at a time.

A driver can provide a different algorithm for more efficiency and pass its address to the m_devget routine.

Enqueueing Packet

The packet read routine finally calls a TCP/IP module called ether_input to enqueue the received packet on one of the TCP/IP software's input queues for further processing.

struct ifnet *ifnet;
struct ether_header *et;
struct mbuf *m;

ether_input(ifp, et, m);

Statistics Counters

The counters relating to packet input are the if_ipackets and if_ierrors fields in the ifnet structure. The if_ipackets counter should be incremented for each packet that is successfully transferred from the interface and enqueued on the TCP/IP input queue. Receive errors are normally indicated by the interface in a status register. In this case the if_ierrors counter should be incremented.

ioctl Entry Point

The ioctl entry point is called by the TCP/IP software in the following syntax:

drvr_ioctl (ifp, cmd, arg)
struct ifnet *ifp;
int cmd; /* ioctl command id */
caddr_t arg; /* command specific data */

The ioctl function must support the two commands SIOCSIFADDR and SIOCSIFFLAGS.

SIOCSIFADDR

This command is used to set the network interface's IP address. Currently, the only address family supported is Internet. Typically this ioctl gets called by the ifconfig utility. The driver should set the IFF_UP bit in the if_flags and call the drvr_init function to initialize the interface. The argument passed to the ioctl routine is cast to a pointer to an ifaddr structure, which is then used to initialize the interface's Internet address in the arpcom structure. The driver should also call arpwhohas to broadcast its Internet address on the network. This allows other nodes to add an entry for this interface in their ARP tables.

SIOCSIFFLAGS

This command is used to bring the interface up or down and is called, for example, by the command ifconfig name up. The TCP/IP software sets or resets the IFF_UP bit in the if_flags field before calling the driver's ioctl entry point to indicate the action to be taken. An interface that is down cannot transmit packets.

When the interface is brought up, the driver should call the drvr_init function to initialize the interface. When the interface is brought down, the interface should be reset by calling drvr_reset. In both cases, the statistics counters in the ifnet structure should be zeroed.

The driver normally defines a flag in the statics structure that it uses to keep track of the current state of the interface (s>ds_flags in the example code below).

struct statics *s;
struct ifaddr *ifa;

case SIOCSIFADDR:
  ifa = (struct ifaddr *) arg;
  ifp->if_flags |= IFF_UP;
  drvr_init (s);
  switch (ifa->ifa_addr->sa_family) {
    case AF_INET :
      ((struct arpcom*)ifp)->ac_ipaddr = IA_SIN
        (ifa)->sin_addr;
        arpwhohas ((struct arpcom*)ifp, &IA_SIN
          (ifa)->sin_addr);
        break;
    default :
      break;
  }
  break;

case SIOCSIFFLAGS:
  if ((ifp->if_flags & IFF_UP) == 0 && s->ds_flags & DSF_RUNNING) {
    drvr_reset (s); /* interface going down */
    s->ds_flags &= ~DSF_RUNNING;
  } else if ((ifp->if_flags & IFF_UP) &&
    !(s->ds_flags & DSF_RUNNING)) {
      drvr_init(s); /* interface coming up */
    }
    ifp->if_ipackets = 0 ;
    ifp->if_opackets = 0 ;
    ifp->if_ierrors = 0 ;
    ifp->if_oerrors = 0 ;
    ifp->if_collisions = 0 ;
    break;

watchdog Entry Point

The watchdog entry point can be used to implement a function that periodically monitors the operation of the interface, checking for conditions such as a hung transmitter. The function can then take corrective action if necessary. If the driver does not have a watchdog function, the corresponding field in the ifnet structure should be set to NULL before calling if_attach.

The watchdog function is used in conjunction with the if_timer field in the ifnet structure. This field specifies a timeout interval in seconds. At the expiration of this interval, the TCP/IP module calls the watchdog entry point in the driver, passing it the p field from the ifnet structure as an argument. The p field is normally used to contain the address of the statics structure.

Note that the timeout interval specified by if_timer is a one-shot function. The driver must reset it to a non-zero value to cause the watchdog function to be called again. Setting the if_timer value to 0 disables the watchdog function.

reset Entry Point

This entry point is called by the kernel during a reboot sequence, passing it the p field from the ifnet structure, which is normally the address of the statics structure. This function may also be called internally from the driver's ioctl entry point. The function should reset the hardware, putting it into an inactive state.

Kernel Thread

The kernel thread receives events from two sources, the interrupt handler (indicating completion of a packet transmission or reception) and the driver output routine (indicating the availability of packets on the if_snd queue). A single event synchronization semaphore is used for both purposes. The thread should handle interrupts first and then the packets on the output queue. The general structure of the thread looks something like:

struct statics *s;

for (;;) {
  swait (&s->threadsem, SEM_SIGIGNORE);
  handle_interrupts (s);/* handle any interrupts */
  output_packets (s);
  /* start tranmitter if necessary */
}

The precise details of the thread code depend on the hardware architecture. The function for processing interrupts contains the packet input code discussed above. It also maintains the various statistics counters. Also, receiver interrupts, if disabled by the interrupt handler, are re-enabled at this point. The output function performs the tasks discussed above in the "Packet Output" section.

Priority Tracking

Whenever the set of user tasks using the TCP/IP software changes or the priority of one of these tasks changes, the setprio entry point in the driver is invoked to allow the driver to properly implement priority tracking on its kernel thread. The entry point is passed two parameters, the address of the ifnet structure and the priority that the kernel thread should be set to. For example:

drvrsetprio (ifp, prio)
struct ifnet *ifp;
int prio;

{
int ktid; /* kernel thread id */

  ktid = ((struct statics *) (ifp->p))->kthread_id;
  stsetprio (ktid, prio);
}

Driver Configuration File

The driver configuration file drvr.cfg in the /sys/lynx.os directory needs to declare only the install (and uninstall) entry points. The other entry points are declared to the TCP/IP module dynamically using the if_attach function. A typical configuration file looks something like:

C:wd3e: \
      ::::: \
      :::wd3einstall:wd3euninstall
D:wd:wd3e0_info::
N:wd:0:

IP Multicasting Support

ether_multi Structure

For each Ethernet interface there is a list of Ethernet multicast address ranges to be received by the hardware. This list defines the multicast filtering to be implemented by the device. Each address range is stored in an ether_multi structure. For example:

struct ether_multi {
  u_char enm_addrlo[6]; /* low/only addr of range */
  u_char enm_addrhi[6]; /* high/only addr of range */
  struct arpcom *enm_ac; /* back pointer to arpcom */
  u_int enm_refcount; /* num claims to addr/range */
  struct ether_multi *enm_next;
  /* ptr to next ether_multi */
};

The entire list of ether_multi is attached to the interface's arpcom structure.

ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST;

ifp is a pointer to the interface ifnet structure.
case SIOCADDMULTI:
case SIOCDELMULTI:
  /* Update our multi-cast list */
  error = (cmd == SIOCADDMULTI) ?
  ether_addmulti((struct ifreq *)data, &s>es_ac) :
     ether_delmulti((struct ifreq *)data, &s>es_ac);

  if (error == ENETRESET) {
  /*
  * Multi-cast list has changed; set the
  * hardware filter accordingly.
  */
    lanreset(s);
  error = 0;
}
struct ifnet *ifp = &s->s_if;
register struct ether_multi *enm;
register int i, len;
struct ether_multistep step;

/*
 * Set up multi-cast address filter by passing
 * all multi-cast addresses through a crc
 * generator, and then using the high order 6
 * bits as a index into the 64 bit logical
 * address filter. The high order two bits
 * select the word, while the rest of the bits
 * select the bit within the word.
 */

bzero(s->mcast_filter, sizeof(s>mcast_filter));
ifp->if_flags &= ~IFF_ALLMULTI;
ETHER_FIRST_MULTI(step, &s->es_ac, enm);

while (enm != NULL) {
 if (bcmp((caddr_t)&enm->enm_addrlo,
    (caddr_t)&enm->enm_addrhi,
     sizeof(enm->enm_addrlo)) != 0) {
    /*
     * We must listen to a range of multi-cast
     * addresses. For now, just accept all
     * multi-casts, rather than trying to set only
     * those filter bits needed to match the
     * range.
     * (At this time, the only use of address
     * ranges is for IP multi-cast routing, for
     * which the range is big enough to require
     * all bits set.)
     */
     for (i=0; i<8; i++)
       s->mcast_filter[i] = 0xff;
     ifp->if_flags |= IFF_ALLMULTI;
     break;
     }
  getcrc((unsigned char *)&enm->enm_addrlo,
  s->mcast_filter);
  ETHER_NEXT_MULTI(step, enm);
}

char *buf;
struct ether_header *et;
u_short ether_type;
struct mbuf *m = (struct mbuf *)NULL;
int flags = 0;

/* set buf to point to start of received frame */
...
...
et = (struct ether_header *) buf;
ether_type = ntohs((u_short) et->ether_type);

if (et->ether_dhost[0] & 1)
  flags |= M_MCAST;

/* pull packet off interface */
...
...
m->m_flags |= flags;
ether_input(ifp, et, m);



LynuxWorks, Inc.
855 Branham Lane East
San Jose, CA 95138
http://www.lynuxworks.com
1.800.255.5969
TOC PREV NEXT INDEX