Please wait until the page is fully downloaded and then press the "Expand" button or the blue line numbers.

0010001 /*
0010002 inet/mnx_eth.c
0010003 
0010004 Created:       Jan 2, 1992 by Philip Homburg
0010005 
0010006 Copyright 1995 Philip Homburg
0010007 */
mnx_eth.c contains the Minix-specific code for the ethernet layer; eth.c contains the OS-independent code for the ethernet layer. Most of the Minix-specific code deals with the Minix messaging service and, for this reason, a solid understanding of the Minix messaging service is necessary to understand the functions within this file.


0010008 
0010009 #include "inet.h"
0010010 #include "proto.h"
0010011 #include "osdep_eth.h"
0010012 #include "generic/type.h"
0010013 
0010014 #include "generic/assert.h"
0010015 #include "generic/buf.h"
0010016 #include "generic/clock.h"
0010017 #include "generic/eth.h"
0010018 #include "generic/eth_int.h"
0010019 #include "generic/sr.h"
0010020 
0010021 THIS_FILE
0010022 
0010023 FORWARD _PROTOTYPE( void setup_read, (eth_port_t *eth_port) );
0010024 FORWARD _PROTOTYPE( void read_int, (eth_port_t *eth_port, int count) );
0010025 FORWARD _PROTOTYPE( void write_int, (eth_port_t *eth_port) );
0010026 FORWARD _PROTOTYPE( void eth_recvev, (event_t *ev, ev_arg_t ev_arg) );
0010027 FORWARD _PROTOTYPE( void eth_sendev, (event_t *ev, ev_arg_t ev_arg) );
0010028 FORWARD _PROTOTYPE( eth_port_t *find_port, (message *m) );
0010029 
0010030 PUBLIC void osdep_eth_init()
osdep_eth_init()

osdep_eth_init() ("osdep" stands for "OS DEPendent") is called by eth_init() during the initialization of the network service. osdep_eth_init() calls several Minix-specific functions to initialize several Minix-independent and Minix-dependent ethernet port fields (for example, the ethernet address is obtained by querying from the ethernet driver).

After each ethernet port is configured, sr_add_minor() and setup_read() are called for the port.



0010031 {
0010032          int i, r, tasknr;
0010033          struct eth_conf *ecp;
0010034          eth_port_t *eth_port;
Since osdep_eth_init() initializes several fields in an ethernet port, it is useful to study the fields of an ethernet port.


eth_port_t / osdep_eth_port_t


Each ethernet card on a system will have one eth_port_t associated with it. In other words, if there is only a single ethernet card on a system, eth_port_table[] will have only a single element. eth_port_t minus the osdep_eth_port_t field is the OS independent portion of the ethernet configuration.

typedef struct eth_port

{
int etp_flags;
ether_addr_t etp_ethaddr;
acc_t *etp_wr_pack, *etp_rd_pack;
struct eth_fd *etp_type_any;
struct eth_fd *etp_type[ETH_TYPE_HASH_NR];
event_t etp_sendev;

osdep_eth_port_t etp_osdep;
} eth_port_t;
The OS dependent (= osdep) part of the ethernet port configuration is osdep_eth_port_t. In other words, if you were to port this network service to another OS, you'd need to create an osdep_eth_port_t that matched your OS.

typedef struct osdep_eth_port

{
int etp_task;
int etp_port;
int etp_recvconf;
iovec_t etp_wr_iovec[IOVEC_NR];
iovec_t etp_rd_iovec[RD_IOVEC];
event_t etp_recvev;
message etp_sendrepl;
message etp_recvrepl;

} osdep_eth_port_t;
int etp_flags:

Initialized to EPF_ENABLED in osdep_eth_init(). In setup_read(), if data has not been received by the ethernet task, etp_flags is set to EPF_READ_IP (although I don't know why yet. If the ethernet task has not delivered a packet yet and another packet is also waiting to be delivered, the EPF_MORE2WRITE flag is set.


ether_addr_t etp_ethaddr:

The ethernet address of the port. This will be a 6 octet number like 00-E0-81-00-29-F6 (the ethernet address of my system). The address of the ethernet port is obtained by querying the ethernet task and disassembling its response. This ethernet address is generally used as the source address for outgoing packets. However, if an ethernet file descriptor is in promiscuous mode, the file descriptor not only accepts any packet regardless of destination ethernet address but can also send out packets with any source ethernet address (not just the ethernet card's address).


typedef struct ether_addr 

{
u8_t ea_addr[6];
} ether_addr_t;
acc_t *etp_wr_pack:

The queueing for ethernet packets being sent out by the ethernet task is somewhat convoluted. If no ethernet packets are waiting to be sent out by the ethernet task (driver),
eth_write_port() stores an ethernet packet in the etp_wr_pack field until the packet is sent off by the ethernet task. After the ethernet task successfully sends the packet off, this field is set to NULL (either by eth_write_port() or write_int(). If the ethernet task cannot immediately send the ethernet packet off, the packet remains in etp_wr_pack. If another packet arrives for an ip port to send off to the ethernet port, the ip port encapsulates the ip packet and the resulting ethernet packet is placed in the dl_eth.de_frame field of the ip port. If an ip port then has additional packets that it wishes to send out, the packets are placed in the dl_eth.de_q_head/dl_eth.de_q_tail queue until the ethernet packets in etp_wr_pack and dl_eth.de_frame are sent out.

It's important to note that neither etp_wr_pack nor dl_eth.de_frame are linked lists (i.e., queues). They each hold only a single ethernet packet.


acc_t *etp_rd_pack:

The read queue for the ethernet port. If a read request message was sent to the ethernet driver and an ethernet packet was not already received, this is an empty buffer waiting to be filled by a packet received later.

struct eth_fd *etp_type_any:
struct eth_fd *etp_type[ETH_TYPE_HASH_NR]:


When an ethernet file descriptor is configured, either the NWEO_TYPESPEC flag or the NWEO_TYPEANY flag is set for the ef_flags field of the ethernet file descriptor (
eth_fd). If NWEO_TYPEANY is set, any type of packet is accepted by the file descriptor. If NWEO_TYPESPEC if set, nweo_type is set to one of the following:

#define ETH_RARP_PROTO 0x8035
#define ETH_ARP_PROTO 0x806
#define ETH_IP_PROTO 0x800

After this configuration is done, hash_fd() is called to place the file descriptor in either the etp_type_any linked list (if the NWEO_TYPEANY flag is set) or in one of the linked lists in etp_type[] (there is a single linked list for each of the types shown above). The figure below shows an example of an etp_type[] linked list with three ethernet file descriptors.




event_t etp_sendev:

After sending out an ethernet packet for an ethernet port, the ethernet driver sends a message back to the ethernet port indicating completion. etp_sendev is the event queue that contains the notification event in case the message could not be immediately delivered.


int etp_task:

etp_task is determined by calling sys_findproc() and passing in the name of the task. In the following same configuration:

eth0 DP8390 0 { default; };
psip1;

the task would be "DP8390" for the ethernet device.


int etp_port:

The port number of the ethernet device as determined by the configuration file. For the following inet.conf file for a system with two DP8390 ethernet cards:

eth0 DP8390 0 { default; };
psip1;
eth2 DP8390 1;


the ethernet port associated with eth0 will have an etp_port field equal to 0 and the ethernet port associated with eth2 will have an etp_port field equal to 1.


int etp_recvconf:

etp_recvconf is the "receive configuration" and is set by eth_set_rec_conf(). The receive configuration reflects whether an ethernet port is receiving broadcast and multicast packets and whether the port is in promiscuous mode.


iovec_t etp_wr_iovec[IOVEC_NR]:
iovec_t etp_rd_iovec[RD_IOVEC]:


The buffers that the ethernet code uses to write/read data to/from the ethernet device. For example, on lines 10419-10438 of setup_read(), the message to the ethernet task requesting any data read by the task is set up and sent.




event_t etp_recvev:

etp_recvev is the event queue for the ethernet port.


message etp_sendrepl, message etp_recvrepl:

When a message arrives from the ethernet driver indicating that a packet has been sent or received and the message cannot be immediately processed, the message is placed here. An event (with function eth_recvev() or eth_sendev()) is also added to the event queue. When an event for a received packet is processed, eth_recvev() determines from the DL_COUNT field how many bytes have been received. If the event is for a completed write operation, the fields in the message are not of any significance.


0010035          message mess, repl_mess;
0010036 
0010037          for (i= 0, eth_port= eth_port_table, ecp= eth_conf;
0010038                   i<eth_conf_nr; i++, eth_port++, ecp++)
Initialize each of the ethernet ports. For the following inet.conf file:

eth0 DP8390 0 { default; };
psip1;

there will be a single ethernet port.


eth_conf[] / psip_conf[] / ip_conf[] / iftype[]


read_conf(), called by nw_init(), reads the configuration file /etc/inet.conf and populates the arrays eth_conf[], psip_conf[], ip_conf[] and iftype[].

An example inet.conf file and the resulting arrays are shown in the following figure:



Note that "psip" stands for "pseudo ip", which is generally used over serial lines (as opposed to ethernet).

A fourth array, iftype[], correlates the interface type with the interface number (the index of the array). Since the same information in if_type[] can be found in ip_conf[], the code could have been rewritten using ip_conf[] where if_type[] was used. However, this would have limited the flexibility of the code since a network protocol other than ip could be added later.


iftype[]
0th entry1st entry2nd entry3rd entry
interface typeNETTYPE_ETH NETTYPE_PSIP0 0


In addition to populating the four arrays, read_conf() also creates the necessary device files. For example, for the inet.conf file above, the following files (and their minor device numbers in parentheses) are created:

/dev/eth0 (1), /dev/ip0 (2), /dev/tcp0 (3), /dev/udp0 (4)
/dev/psip1 (17), /dev/ip1 (18), /dev/tcp1 (19), /dev/udp1 (20)

Note that the major device number for all of the network device files is set to the major device number of the /dev/ip file, which must be created before the network service can be started. The value of this major device number is 7 for the default configuration.

Links are also created for the default entry. For example, for the inet.conf file above, the following links are created:

/dev/eth -> /dev/eth0
/dev/ip -> /dev/ip0
/dev/tcp -> /dev/tcp0
/dev/udp -> /dev/udp0


From inet(8):


Inet starts as a normal process, reads the configuration file
/etc/inet.conf to see what it should do, and uses a few special low level
system calls to turn itself into a server. The format of the
configuration file is as follows:

Configuration
The inet configuration file is fairly simple, here is an example:

eth0 DP8390 0 { default; };
psip1;

It tells that network 0 (the one containing devices eth0, ip0, tcp0 and
udp0) uses the ethernet device driver handled by task "DP8390" at port 0.
This network is marked as the default network, so most programs use it
through the unnumbered devices like /dev/tcp or /dev/udp. Network 1 is a
Pseudo IP network that can be used for a serial IP over a modem for
instance.

ethN task port {options};
This sets up an ethernet with device name /dev/ethN, built on the
given ethernet device driver at the given port at that driver. (If
a network driver manages two network cards then they are at port 0
and 1.)


psipN {options};
Creates pseudo IP network /dev/psipN, usable for IP over serial
lines, tunnels and whatnot.



0010039          {
0010040                   r= sys_findproc(ecp->ec_task, &tasknr, 0);
sys_findproc() is a system call that is defined in src/lib/syslib/sys_findproc.c. sys_findproc has the following signature:

int sys_findproc(char *name, int *tasknr, int flags)

Given the name of the ethernet driver (sys_findproc()'s first parameter - this name is acquired from the inet.conf file), sys_findproc() returns the ethernet driver's task number in tasknr (sys_findproc()'s second parameter).


0010041                   if (r != OK)
0010042                   {
0010043                            ip_panic(( "unable to find task %s: %d\n",
0010044                                     ecp->ec_task, r ));
0010045                   }
0010046 
0010047                   eth_port->etp_osdep.etp_port= ecp->ec_port;
eth_conf[] is populated with the values from inet.conf (see comment for line 10038).


0010048                   eth_port->etp_osdep.etp_task= tasknr;
tasknr was obtained by sys_findproc() on line 10040.


0010049                   ev_init(&eth_port->etp_osdep.etp_recvev);
event_t / ev_enqueue() / ev_process() / ev_init() / ev_in_queue()

The event_t typedef is declared in inet/generic/event.h:

typedef struct event

{
ev_func_t ev_func;
ev_arg_t ev_arg;
struct event *ev_next;
} event_t;
If an event needs to be scheduled, ev_enqueue() is called to place the event in the system-wide event queue whose head is ev_head. ev_process() is eventually called from the main loop in inet.c to process the events. ev_in_queue(ev) simply returns TRUE if the event ev, ev_in_queue()'s only parameter, has a non-null value for func (see below) and FALSE if func is null. In this way, ev_in_queue() determines whether the event has been configured.

ev_init(ev) simply zeroes out the ev_func and ev_next fields of the event ev, ev_init()'s only parameter.

ev_func: A function (e.g., ip_process_loopb()) that performs some task.

ev_arg:

typedef union ev_arg

{
int ev_int;
void *ev_ptr;
} ev_arg_t;
ev_arg is ev_func's argument. In the case of a packet destined for the loopback address (127.0.0.1), the argument will be the ip port associated with the ip file descriptor that is sending out the packet. In the case of a message from the ethernet task that caused a deadlock, ev_arg is a pointer to the message's destination ethernet port.

ev_next: The next event in the system-wide event queue.


0010050 
A DL_INIT (Data Link INITialize) message, used to announce the existence of an ethernet port and to obtain information from the ethernet driver, has the format as shown below. Lines 10051-10054 set up the message that is sent on line 10056.

Note that "DL" stands for "Data Link."

From dp8390.c:


* |------------|----------|---------|----------|---------|---------|
* | DL_INIT | port nr | proc nr | mode | | address |
* |------------|----------|---------|----------|---------|---------|



0010051                   mess.m_type= DL_INIT;
0010052                   mess.DL_PORT= eth_port->etp_osdep.etp_port;
etp_port is the port number of the ethernet device as determined by the configuration file. For example, for the following /etc/inet.conf file:

eth0 DP8390 0 { default; };
psip1;

the first ethernet device would have a port number of 0 (due to the "0" between "DP8390" and the left curly brace).


0010053                   mess.DL_PROC= this_proc;
this_proc, the process number of the network service, was obtained during the initialization of the network service.


0010054                   mess.DL_MODE= DL_NOMODE;
Possible modes are as follows:

/* Bits in 'DL_MODE' field of DL requests. */
#define DL_NOMODE 0x0 /* unicast */
#define DL_PROMISC_REQ 0x2 /* promiscuous */
#define DL_MULTI_REQ 0x4 /* multicast */
#define DL_BROAD_REQ 0x8 /* broadcast */


At this point, DL_NOMODE will be passed in. Later, however, if any ethernet file descriptors are configured to accept broadcast or multicast packets or if any ethernet file descriptors are configured to be in promiscuous mode, another message will be sent to the ethernet task.


0010055 
0010056                   r= send(eth_port->etp_osdep.etp_task, &mess);
The message just built is sent to the ethernet task (driver). The response to this message is handled on line 10067.

Click here for a detailed description of the Minix message system.


0010057                   if (r<0)
0010058                   {
0010059 #if !CRAMPED
0010060                            printf(
0010061                   "osdep_eth_init: unable to send to ethernet task, error= %d\n",
0010062                                     r);
0010063 #endif
0010064                            continue;
0010065                   }
0010066 
0010067                   if (receive(eth_port->etp_osdep.etp_task, &mess)<0)
0010068                            ip_panic(("unable to receive"));
Receive the reply back from the ethernet task.


0010069 
Verify that the ethernet task (i.e., driver) exists (lines 10070-10079) and that the port number received is the same as the port number sent (see line 10052).


0010070                   if (mess.m3_i1 == ENXIO)
ENXIO is #define'd in include/errno.h:

#define ENXIO (_SIGN 6) /* no such device or address */


0010071                   {
0010072 #if !CRAMPED
0010073                            printf(
0010074                   "osdep_eth_init: no ethernet device at task=%d,port=%d\n",
0010075                                     eth_port->etp_osdep.etp_task,
0010076                                     eth_port->etp_osdep.etp_port);
0010077 #endif
0010078                            continue;
0010079                   }
0010080                   if (mess.m3_i1 != eth_port->etp_osdep.etp_port)
0010081                            ip_panic(("osdep_eth_init: DL_INIT error or wrong port: %d\n",
0010082                                     mess.m3_i1));
0010083 
The message was received and was successful. Extract the 6-octet ethernet address from the response.


0010084                   eth_port->etp_ethaddr= *(ether_addr_t *)mess.m3_ca1;
0010085 
0010086                   sr_add_minor(if2minor(ecp->ec_ifno, ETH_DEV_OFF),
0010087                            i, eth_open, eth_close, eth_read,
0010088                            eth_write, eth_ioctl, eth_cancel);
sr_fd / sr_fd_table[] / sr_add_minor()

One of the most important data arrays in the network service is sr_fd_table[], an array of 64 struct sr_fd's. Each sr_fd element in sr_fd_table[] corresponds to either a device or an opened file descriptor to a device (i.e., a "channel"):

typedef struct sr_fd

{
int srf_flags;
int srf_fd;
int srf_port;
sr_open_t srf_open;
sr_close_t srf_close;
sr_write_t srf_write;
sr_read_t srf_read;
sr_ioctl_t srf_ioctl;
sr_cancel_t srf_cancel;
mq_t *srf_ioctl_q, *srf_ioctl_q_tail;
mq_t *srf_read_q, *srf_read_q_tail;
mq_t *srf_write_q, *srf_write_q_tail;
} sr_fd_t;
For each device (e.g., /dev/udp0), an element in sr_fd_table[] is configured by sr_add_minor(). For example, for the following inet.conf file:

eth0 DP8390 0 { default; };
psip1;

an element (i.e., a struct sr_fd) is configured for each of the following devices:

/dev/eth0 sr_fd_table[1]
/dev/ip0 sr_fd_table[2]
/dev/tcp0 sr_fd_table[3]
/dev/udp0 sr_fd_table[4]

/dev/psip1 sr_fd_table[17]
/dev/ip1 sr_fd_table[18]
/dev/tcp1 sr_fd_table[19]
/dev/udp1 sr_fd_table[20]




sr_add_minor() is called in the initialization routines for the various protocols: mnx_eth.c (osdep_eth_init()), psip.c (psip_enable()), ip.c (ip_init()), tcp.c (tcp_init()), and udp.c (udp_init()).



When a device file (e.g., /dev/udp0) is opened by a process, the element that corresponds to the device is copied to an element that is currently unoccupied (see sr_open()). In this way, a "channel" is opened. Using this technique, a channel can be opened, closed, and manipulated without affecting the elements of the descriptors initially set by sr_add_minor().


int srf_flags:

srf_flags is a combination of the following:

#define SFF_FREE 0x00
#define SFF_MINOR 0x01
#define SFF_INUSE 0x02
#define SFF_BUSY 0x3C
#define SFF_IOCTL_IP 0x04
#define SFF_READ_IP 0x08
#define SFF_WRITE_IP 0x10
#define SFF_PENDING_REQ 0x30
#define SFF_SUSPENDED 0x1C0
#define SFF_IOCTL_SUSP 0x40
#define SFF_READ_SUSP 0x80
#define SFF_WRITE_SUSP

srf_flags is initialized to SFF_FREE for each element in sr_fd_table[]. If the channel corresponds to a device file, srf_flags is set to SFF_INUSE | SFF_MINOR. If the channel does not correspond to a device file, srf_flags is set simply to SFF_INUSE.

When a request comes in for a read, write, or ioctl operation and the network service is not already processing another request for the same operation, srf_flags is set to SFF_READ_IP, SFF_WRITE_IP, or SFF_IOCTL_IP. However, if an operation is attempted but the underlying protocol is still processing a previous request of the same nature (e.g., udp_write()), the appropriate flag (SFF_IOCTL_SUSP, SFF_READ_SUSP, or SFF_WRITE_SUSP) in srf_flags is set.


int srf_fd, srf_port:

srf_fd and srf_port are both set by sr_add_minor(). For the channels in srf_fd_table[] that correspond to the device files (e.g., /dev/udp0), srf_fd is set to the minor device number of the device. For example, if /dev/udp0 is added to sr_fd_table[] and the interface number of the device file is 0 (see comments for ip_conf[]), then the minor device number is:

if2minor(ifno, dev) = ((0)*16 + UDP_DEV = 0 + 4 = 4

For the channels in srf_fd_table[] that do not correspond to a device file, srf_fd is the file descriptor for the appropriate protocol. For example, if the file system requests that a udp channel be opened, srf_open is dereferenced and udp_open() is called. udp_open() opens a udp file descriptor and returns the index of the corresponding element in udp_fd_table[]. srf_fd is set to the index of this element.

Later, when the file system requests a read or a write on the open channel, srf_fd is passed into the protocol-specific read or write function (e.g., udp_read()), allowing the protocol-specific function to locate the appropriate file descriptor (e.g., udp file descriptor).

srf_port is more straight-forward. srf_port is the index in the protocol's port table. For example, if a system has two udp device files (/dev/udp0 and /dev/udp1), udp_port_table[] will have two entries, 0 and 1. Therefore, srf_port for the entry in sr_fd_table[] that corresponds to /dev/udp0 will be 0 and srf_port for the entry that corresponds to /dev/udp1 will be 1.


sr_open_t srf_open:
sr_close_t srf_close:
sr_write_t srf_write:
sr_read_t srf_read:
sr_ioctl_t srf_ioctl:
sr_cancel_t srf_cancel:


The fields above are all protocol-specific functions and and are all set by sr_add_minor(). For example, when sr_add_minor() is called by udp_init(), srf_open, srf_close, srf_write, srf_read, srf_ioctl, and srf_cancel are set to the pointers of the functions udp_open(), udp_close(), udp_write(), udp_read(), udp_ioctl(), and udp_cancel(). Later, when the file system makes a request to the network service, these functions will be called. For example, if the file system requests that data is written to a channel, srf_write is dereferenced and, if the channel is a udp channel, udp_write() is called.

mq_t *srf_ioctl_q, *srf_ioctl_q_tail:
mq_t *srf_read_q, *srf_read_q_tail:
mq_t *srf_write_q, *srf_write_q_tail:


The fields above are linked lists of ioctl, read, and write messages waiting to be processed. When a message requesting an ioctl, read, or write operation is received, the message is placed at the end of the linked list (unless there are no previous messages of this type that have not already been processed).


After the initialization of the network service, sr_rec() is called upon receipt of messages from the file system in the endless loop within main(). sr_rec() then calls a function to handle the specific request. For open requests, sr_rec() calls sr_open(); for read, write, and io requests, sr_rec() calls sr_rwio(); for close requests, sr_rec() calls sr_close(); for cancel requests, sr_rec() calls sr_cancel().


0010089 
0010090                   eth_port->etp_flags |= EPF_ENABLED;
0010091                   eth_port->etp_wr_pack= 0;
0010092                   eth_port->etp_rd_pack= 0;
0010093                   setup_read (eth_port);
setup_read() / mnx_eth

setup_read(eth_port) sends a message to the ethernet task inquiring whether an ethernet packet has arrived that is destined for the ethernet port eth_port, setup_read()'s only parameter. If a datagram has arrived, the datagram is copied to a buffer within the network service and the datagram is handed off to eth_arrive().

After processing all the packets that the ethernet task has received and copied over to the network service, setup_read() leaves a buffer in the ethernet port's etp_rd_pack field to which the ethernet task can later copy another ethernet packet after the packet arrives.


0010094                   eth_port++;
0010095          }
0010096 }
0010097 
0010098 PUBLIC void eth_write_port(eth_port, pack)
0010099 eth_port_t *eth_port;
0010100 acc_t *pack;
eth_write_port()

eth_write_port(eth_port, pack) sends a message to the ethernet task (driver) requesting that the packet pack, eth_write_port()'s second parameter, be sent.

eth_write_port() is called only by eth_send(). By the time that eth_write_port() has been called, all layers (e.g., ethernet, ip, udp) have verified that the outgoing packet is valid and that the packet is destined for a remote system (i.e., is not destined for the local loopback or the address of the local port).


0010101 {
0010102          eth_port_t *loc_port;
0010103          message mess1, block_msg;
0010104          int i, pack_size;
0010105          acc_t *pack_ptr;
0010106          iovec_t *iovec;
0010107          u8_t *eth_dst_ptr;
0010108          int multicast, r;
0010109          ev_arg_t ev_arg;
0010110 
0010111          assert(eth_port->etp_wr_pack == NULL);
0010112          eth_port->etp_wr_pack= pack;
pack, the second parameter to eth_write_port(), is placed in the ethernet port's etp_wr_pack field. The packet is freed after the ethernet driver reports that the packet has been successfully sent off.


0010113 
0010114          iovec= eth_port->etp_osdep.etp_wr_iovec;
etp_wr_iovec[IOVEC_NR], a field within an ethernet port is a buffer that is used to write data to the ethernet port's associated ethernet device. etp_wr_iovec, of course, is a pointer to the first element.

This array is populated (lines 10115-10121) and then sent to the ethernet device (line 10158).

iovec_t is declared in include/minix/type.h:

typedef struct {

vir_bytes iov_addr; /* address of an I/O buffer */
vir_bytes iov_size; /* sizeof an I/O buffer */
} iovec_t;
IOVEC_NR is the number of buffers that can be sent to the ethernet device at one time. It is #define'd in osdep_eth.h:

#define IOVEC_NR 16


0010115          pack_size= 0;
0010116          for (i=0, pack_ptr= pack; i<IOVEC_NR && pack_ptr; i++,
0010117                   pack_ptr= pack_ptr->acc_next)
A packet is made up of accessors, which are linked together by the acc_next field.


0010118          {
0010119                   iovec[i].iov_addr= (vir_bytes)ptr2acc_data(pack_ptr);
ptr2acc_data()

The macro ptr2acc_data is #define'd in inet/generic/buf.h as:

#define ptr2acc_data(/* acc_t * */ a) (bf_temporary_acc=(a), \
(&bf_temporary_acc->acc_buffer->buf_data_p[bf_temporary_acc-> \
acc_offset]))

ptr2acc_data() simply returns a pointer to the actual data within an accessor.

ptr2acc_data() is usually called so that the fields of a header (e.g., ip header) can be analyzed.


0010120                   pack_size += iovec[i].iov_size= pack_ptr->acc_length;
0010121          }
0010122          if (i>= IOVEC_NR)
The packet is too fragmented. Pack the packet (line 10124) and then fill in the values for iovec[] again (lines 10126-10132).


0010123          {
0010124                   pack= bf_pack(pack);              /* packet is too fragmented */
bf_pack()

bf_pack(old_acc) creates a new linked list of accessors of old_acc's equivalent length and copies the data from old_acc to it. In this way, the data is compressed (or packed).

For example, bf_pack(old_acc) will return new_acc:



(Note that 300+200+300=800=512+288.)


0010125                   eth_port->etp_wr_pack= pack;
The "packed" packet pack (silliness not intentional) is placed in the ethernet port's write queue.

Lines 10126-10132 are equivalent to 10115-10121.


0010126                   pack_size= 0;
0010127                   for (i=0, pack_ptr= pack; i<IOVEC_NR && pack_ptr;
0010128                            i++, pack_ptr= pack_ptr->acc_next)
0010129                   {
0010130                            iovec[i].iov_addr= (vir_bytes)ptr2acc_data(pack_ptr);
0010131                            pack_size += iovec[i].iov_size= pack_ptr->acc_length;
0010132                   }
0010133          }
0010134          assert (i< IOVEC_NR);
0010135          assert (pack_size >= ETH_MIN_PACK_SIZE);
0010136 
Lines 10137-10154 set up a message that is sent on line 10158. The 2 possible message layouts are below. If iovec[] is more than one element long, messages must be of type DL_WRITEV.

From src/kernel/dp8390.c:

* The valid messages and their parameters are:
*
* m_type DL_PORT DL_PROC DL_COUNT DL_MODE DL_ADDR
* |------------|----------|---------|----------|---------|---------|
* | DL_WRITE | port nr | proc nr | count | mode | address |
* |------------|----------|---------|----------|---------|---------|
* | DL_WRITEV | port nr | proc nr | count | mode | address |
* |------------|----------|---------|----------|---------|---------|





0010137          if (i == 1)
0010138          {
0010139                   /* simple packets can be sent using DL_WRITE instead of
0010140                    * DL_WRITEV.
0010141                    */
0010142                   mess1.DL_COUNT= iovec[0].iov_size;
0010143                   mess1.DL_ADDR= (char *)iovec[0].iov_addr;
0010144                   mess1.m_type= DL_WRITE;
0010145          }
0010146          else
0010147          {
0010148                   mess1.DL_COUNT= i;
0010149                   mess1.DL_ADDR= (char *)iovec;
0010150                   mess1.m_type= DL_WRITEV;
0010151          }
0010152          mess1.DL_PORT= eth_port->etp_osdep.etp_port;
0010153          mess1.DL_PROC= this_proc;
this_proc, the process number of the network server, was set during the initialization of the network server.


0010154          mess1.DL_MODE= DL_NOMODE;
There are four possible modes for this operation (as #define'd in include/minix/com.h):

/* Bits in 'DL_MODE' field of DL requests. */
#define DL_NOMODE 0x0 /* unicast */
#define DL_PROMISC_REQ 0x2 /* promiscuous */
#define DL_MULTI_REQ 0x4 /* multicast */
#define DL_BROAD_REQ 0x8 /* broadcast */

Since this is a write operation, DL_MODE is irrelevant and, therefore, DL_MODE is set to DL_NOMODE.


0010155 
0010156          for (;;)
0010157          {
0010158                   r= send (eth_port->etp_osdep.etp_task, &mess1);
The message just built is sent to the ethernet task (driver). The response to this message is handled on line 10067.

Click here for a detailed description of the Minix message system.


0010159                   if (r != ELOCKED)
0010160                            break;
0010161 
If this point in the code is reached, the message was blocked by another message sent by the ethernet task to the network service. This second message - the blocking message - is either the result of a previous write request from another ethernet port or a message indicating that the ethernet task has received an ethernet packet (in which case, the message may be destined for the same ethernet port).

In both of these cases, determine the destination ethernet port and place the blocking message in the port's event queue.

If the message was not a blocking message, the code continues on line 10189.


0010162                   /* ethernet task is sending to this task, I hope */
0010163                   r= receive(eth_port->etp_osdep.etp_task, &block_msg);
Receive the message that caused the deadlock.

Click here for a detailed description of the Minix message system.


0010164                   if (r < 0)
0010165                            ip_panic(("unable to receive"));
0010166 
0010167                   loc_port= eth_port;
0010168                   if (loc_port->etp_osdep.etp_port != block_msg.DL_PORT)
0010169                   {
0010170                            loc_port= find_port(&block_msg);
find_port()

find_port(m) returns the ethernet port whose etp_port field is equal to DL_PORT field of the message m, find_port()'s only parameter.

For the following inet.conf file for a system with two DP8390 ethernet cards:

eth0 DP8390 0 { default; };
psip1;
eth2 DP8390 1;


the ethernet port associated with eth0 will have an etp_port field equal to 0 and the ethernet port associated with eth2 will have an etp_port field equal to 1.

find_port() is called by setup_read() and eth_write_port(). Both functions call find_port() after an attempt to send a message to the ethernet task is blocked by a message sent by the ethernet task to the network service.


0010171                   }
0010172                   assert(block_msg.DL_STAT & (DL_PACK_SEND|DL_PACK_RECV));
0010173                   if (block_msg.DL_STAT & DL_PACK_SEND)
The blocking message is the result of a previous write request from another ethernet port. Note that the blocking message cannot be for the same port as the blocked message since an ethernet port will send out a single ethernet packet at a time.


0010174                   {
0010175                            assert(loc_port != eth_port);
0010176                            loc_port->etp_osdep.etp_sendrepl= block_msg;
0010177                            ev_arg.ev_ptr= loc_port;
0010178                            ev_enqueue(&loc_port->etp_sendev, eth_sendev, ev_arg);
event_t / ev_enqueue() / ev_process() / ev_init() / ev_in_queue()

The event_t typedef is declared in inet/generic/event.h:

typedef struct event

{
ev_func_t ev_func;
ev_arg_t ev_arg;
struct event *ev_next;
} event_t;
If an event needs to be scheduled, ev_enqueue() is called to place the event in the system-wide event queue whose head is ev_head. ev_process() is eventually called from the main loop in inet.c to process the events. ev_in_queue(ev) simply returns TRUE if the event ev, ev_in_queue()'s only parameter, has a non-null value for func (see below) and FALSE if func is null. In this way, ev_in_queue() determines whether the event has been configured.

ev_init(ev) simply zeroes out the ev_func and ev_next fields of the event ev, ev_init()'s only parameter.

ev_func: A function (e.g., ip_process_loopb()) that performs some task.

ev_arg:

typedef union ev_arg

{
int ev_int;
void *ev_ptr;
} ev_arg_t;
ev_arg is ev_func's argument. In the case of a packet destined for the loopback address (127.0.0.1), the argument will be the ip port associated with the ip file descriptor that is sending out the packet. In the case of a message from the ethernet task that caused a deadlock, ev_arg is a pointer to the message's destination ethernet port.

ev_next: The next event in the system-wide event queue.eth_sendev()

When a message indicating a packet has been written by the ethernet task is received by the network service but the network service must complete another task first before handling this message, an event is placed in the event queue. When the network service eventually processes the event, eth_sendev() is called and it, in turn, calls write_int() to process any additional ethernet packets waiting to be sent out.


0010179                   }
0010180                   if (block_msg.DL_STAT & DL_PACK_RECV)
The blocking message is a message indicating that the ethernet task has received an ethernet packet (in which case, the message may be destined for the same ethernet port).


0010181                   {
0010182                            loc_port->etp_osdep.etp_recvrepl= block_msg;
0010183                            ev_arg.ev_ptr= loc_port;
0010184                            ev_enqueue(&loc_port->etp_osdep.etp_recvev,
0010185                                     eth_recvev, ev_arg);
event_t / ev_enqueue() / ev_process() / ev_init() / ev_in_queue()

The event_t typedef is declared in inet/generic/event.h:

typedef struct event

{
ev_func_t ev_func;
ev_arg_t ev_arg;
struct event *ev_next;
} event_t;
If an event needs to be scheduled, ev_enqueue() is called to place the event in the system-wide event queue whose head is ev_head. ev_process() is eventually called from the main loop in inet.c to process the events. ev_in_queue(ev) simply returns TRUE if the event ev, ev_in_queue()'s only parameter, has a non-null value for func (see below) and FALSE if func is null. In this way, ev_in_queue() determines whether the event has been configured.

ev_init(ev) simply zeroes out the ev_func and ev_next fields of the event ev, ev_init()'s only parameter.

ev_func: A function (e.g., ip_process_loopb()) that performs some task.

ev_arg:

typedef union ev_arg

{
int ev_int;
void *ev_ptr;
} ev_arg_t;
ev_arg is ev_func's argument. In the case of a packet destined for the loopback address (127.0.0.1), the argument will be the ip port associated with the ip file descriptor that is sending out the packet. In the case of a message from the ethernet task that caused a deadlock, ev_arg is a pointer to the message's destination ethernet port.

ev_next: The next event in the system-wide event queue.eth_recvev()

When a message from the ethernet task indicating a received packet is received by the network service but the network service must complete another task first, an event is placed in the event queue. When the network service eventually processes the event, eth_recvev() is called and it, in turn, calls read_int() to process the packet.


0010186                   }
0010187          }
0010188 
0010189          if (r < 0)
0010190                   ip_panic(("unable to send"));
0010191 
0010192          r= receive(eth_port->etp_osdep.etp_task, &mess1);
Receive the response message from the send() on line 10158. This message will, of course, be sent by the ethernet task to the network service. Since this message is the response message, the message will be for the same ethernet port as the message sent by the network service to the ethernet task. Note that this message is not necessarily the message indicating that the requested write (see line 10158) has been completed. This message will indicate either that a packet has been sent/received or that neither has occurred. Specifically, the dp8390 ethernet driver will send either a message indicating that a write has been completed or it will send a message indicating that neither a write nor a read has been completed. However, another ethernet driver could choose to send a message indicating that a read has been completed (in which case, the message would be processed by the block that begins on line 10201).

Click here for a detailed description of the Minix message system.


0010193          if (r < 0)
0010194                   ip_panic(("unable to receive"));
0010195 
0010196          assert(mess1.m_type == DL_TASK_REPLY &&
0010197                   mess1.DL_PORT == mess1.DL_PORT &&
0010198                   mess1.DL_PROC == this_proc);
0010199          assert((mess1.DL_STAT >> 16) == OK);
0010200 
0010201          if (mess1.DL_STAT & DL_PACK_RECV)
The message may be reporting a received ethernet packet. Place the message in the event queue for the ethernet port.

Note that if the dp8390 ethernet driver receives a request to send a packet, the driver will never report that it has received a packet. Note, however, that another ethernet driver could choose to send a message indicating that a read has been completed.


0010202          {
0010203                   eth_port->etp_osdep.etp_recvrepl= mess1;
0010204                   ev_arg.ev_ptr= eth_port;
0010205                   ev_enqueue(&eth_port->etp_osdep.etp_recvev, eth_recvev,
0010206                            ev_arg);
eth_recvev()

When a message from the ethernet task indicating a received packet is received by the network service but the network service must complete another task first, an event is placed in the event queue. When the network service eventually processes the event, eth_recvev() is called and it, in turn, calls read_int() to process the packet.event_t / ev_enqueue() / ev_process() / ev_init() / ev_in_queue()

The event_t typedef is declared in inet/generic/event.h:

typedef struct event

{
ev_func_t ev_func;
ev_arg_t ev_arg;
struct event *ev_next;
} event_t;
If an event needs to be scheduled, ev_enqueue() is called to place the event in the system-wide event queue whose head is ev_head. ev_process() is eventually called from the main loop in inet.c to process the events. ev_in_queue(ev) simply returns TRUE if the event ev, ev_in_queue()'s only parameter, has a non-null value for func (see below) and FALSE if func is null. In this way, ev_in_queue() determines whether the event has been configured.

ev_init(ev) simply zeroes out the ev_func and ev_next fields of the event ev, ev_init()'s only parameter.

ev_func: A function (e.g., ip_process_loopb()) that performs some task.

ev_arg:

typedef union ev_arg

{
int ev_int;
void *ev_ptr;
} ev_arg_t;
ev_arg is ev_func's argument. In the case of a packet destined for the loopback address (127.0.0.1), the argument will be the ip port associated with the ip file descriptor that is sending out the packet. In the case of a message from the ethernet task that caused a deadlock, ev_arg is a pointer to the message's destination ethernet port.

ev_next: The next event in the system-wide event queue.


0010207          }
0010208          if (!(mess1.DL_STAT & DL_PACK_SEND))
Neither a write nor a read was completed for this ethernet port.


0010209          {
0010210                   /* Packet is not yet sent. */
0010211                   return;
0010212          }
0010213 
The message is the response from the write request from line 10158. If the packet is a broadcast packet or the ethernet port is in promiscuous mode, the packet will need to be delivered to ethernet file descriptors on the system.


0010214          /* If the port is in promiscuous mode or the packet is
0010215           * broadcasted/multicasted, enqueue the reply packet.
If the packet is a multicast packet, the packet must likely be delivered to various ethernet file descriptors. If the ethernet port is in promiscuous mode, there will be at least one ethernet file descriptor that is in promiscuous mode that will be delivered the packet. Place an event in the event queue for this to be handled later.


0010216           */
0010217          eth_dst_ptr= (u8_t *)ptr2acc_data(pack);
0010218          multicast= (*eth_dst_ptr & 1);       /* low order bit indicates multicast */
Determine if the address is a multicast or broadcast address.

A multicast address has the low-order bit of the high-order byte turned on. In hexadecimal representation, this bit looks like 01:00:00:00:00:00. (The ethernet broadcast address ff:ff:ff:ff:ff:ff can be considered a special case of the Ethernet multicast address.)


0010219          if (multicast || (eth_port->etp_osdep.etp_recvconf & NWEO_EN_PROMISC))
0010220          {
0010221                   eth_port->etp_osdep.etp_sendrepl= mess1;
0010222                   ev_arg.ev_ptr= eth_port;
0010223                   ev_enqueue(&eth_port->etp_sendev, eth_sendev, ev_arg);
event_t / ev_enqueue() / ev_process() / ev_init() / ev_in_queue()

The event_t typedef is declared in inet/generic/event.h:

typedef struct event

{
ev_func_t ev_func;
ev_arg_t ev_arg;
struct event *ev_next;
} event_t;
If an event needs to be scheduled, ev_enqueue() is called to place the event in the system-wide event queue whose head is ev_head. ev_process() is eventually called from the main loop in inet.c to process the events. ev_in_queue(ev) simply returns TRUE if the event ev, ev_in_queue()'s only parameter, has a non-null value for func (see below) and FALSE if func is null. In this way, ev_in_queue() determines whether the event has been configured.

ev_init(ev) simply zeroes out the ev_func and ev_next fields of the event ev, ev_init()'s only parameter.

ev_func: A function (e.g., ip_process_loopb()) that performs some task.

ev_arg:

typedef union ev_arg

{
int ev_int;
void *ev_ptr;
} ev_arg_t;
ev_arg is ev_func's argument. In the case of a packet destined for the loopback address (127.0.0.1), the argument will be the ip port associated with the ip file descriptor that is sending out the packet. In the case of a message from the ethernet task that caused a deadlock, ev_arg is a pointer to the message's destination ethernet port.

ev_next: The next event in the system-wide event queue.eth_sendev()

When a message indicating a packet has been written by the ethernet task is received by the network service but the network service must complete another task first before handling this message, an event is placed in the event queue. When the network service eventually processes the event, eth_sendev() is called and it, in turn, calls write_int() to process any additional ethernet packets waiting to be sent out.


0010224 
0010225                   /* Pretend that we didn't get a reply. */
The packet will be freed later after the packet has been handed off to all the interested ethernet file descriptors.


0010226                   return;
0010227          }
0010228 
0010229          /* packet is sent */
The packet was a unicast packet and was sent off by the ethernet task. Free the packet.


0010230          bf_afree(eth_port->etp_wr_pack);
bf_afree()

After a chain of accessors is no longer needed, the chain (and not simply the single accessor passed as the parameter) can be freed by calling bf_free(). However, if either acc_linkC or buf_linkC of one of the accessors in the linked list is not equal to one (1), the entire chain will not be freed. For example, if buf_afree(acc1) is called for the following chain:



Then the resulting chain will be:



bf_afree() returns acc1 (accessors[63]) to acc_freelist (recall that acc_freelist is the linked list of acc_t's without an associated buffer). However, buffers512[127] cannot be freed because acc2 (accessors[64]) still references it.

bf_afree() is called after an accessor's associated data is no longer needed (for example, after a packet has been sent off by the ethernet driver).


0010231          eth_port->etp_wr_pack= NULL;
0010232 }
0010233 
0010234 PUBLIC void eth_rec(m)
0010235 message *m;
eth_rec()

eth_rec() is called from the main loop or by eth_get_stat() or by eth_set_rec_conf() upon receipt of a message from the ethernet task. If the message is valid, the message will either indicate that a write operation has successfully ended (in which case, write_int() is called) or that the ethernet task has received a packet (in which case, read_int() is called).

The message (which is of type mess_2) has the following structure:
From dp8390.c:


*
* m-type DL_PORT DL_PROC DL_COUNT DL_STAT DL_CLCK
* |------------|----------|---------|----------|---------|---------|
* |DL_TASK_REPL| port nr | proc nr | rd-count | err|stat| clock |
* |------------|----------|---------|----------|---------|---------|
*

From include/minix/type.h:

typedef struct {
int m_source; /* who sent the message */
int m_type; /* what kind of message is it */
union {
mess_1 m_m1;
mess_2 m_m2;
mess_3 m_m3;
mess_4 m_m4;
mess_5 m_m5;
mess_6 m_m6;
} m_u;
} message;

typedef struct {int m2i1, m2i2, m2i3; long m2l1, m2l2; char *m2p1;} mess_2;

From include/minix/com.h:

# define DL_PORT m2_i1
# define DL_PROC m2_i2
# define DL_COUNT m2_i3
# define DL_CLCK m2_l2
# define DL_STAT m2_l1



0010236 {
0010237          int i;
0010238          eth_port_t *loc_port;
0010239          int stat;
0010240 
0010241          assert(m->m_type == DL_TASK_REPLY);
0010242 
0010243          set_time (m->DL_CLCK);
set_time()

set_time() is called only from eth_rec(). eth_rec() takes advantage of the fact that the current time is included in messages from the ethernet task. (Specifically, the current time is in the field DL_CLCK of the message.)

Do not confuse set_time() with set_timer(), which does something completely different.


0010244 
Find the ethernet port to which this message is destined and verify that the message is coming from the ethernet task.


0010245          for (i=0, loc_port= eth_port_table; i<eth_conf_nr; i++, loc_port++)
0010246          {
0010247                   if (loc_port->etp_osdep.etp_port == m->DL_PORT &&
0010248                            loc_port->etp_osdep.etp_task == m->m_source)
0010249                            break;
0010250          }
0010251          if (i == eth_conf_nr)
If there are two ethernet ports (i.e., eth_conf_nr = 2), there will exist an ethernet port 0 and an ethernet port 1. Therefore, if the message was destined for neither the 0th nor the 1st ethernet port (i.e., i reached 2 above), there's a problem.


0010252          {
0010253                   ip_panic(("message from unknown source: %d:%d",
0010254                            m->m_source, m->DL_PORT));
0010255          }
0010256 
0010257          stat= m->DL_STAT & 0xffff;
As can be seen in the general comment on line 10235, DL_STAT is composed of stat (the lower 16 bits) and err (the upper 16 bits).


0010258 
0010259          assert(stat & (DL_PACK_SEND|DL_PACK_RECV));
0010260          if (stat & DL_PACK_SEND)
0010261                   write_int(loc_port);
The ethernet task has successfully sent off a packet.


write_int()


After the ethernet task sends off an ethernet packet, the task sends a message to the network service indicating success and write_int() is subsequently called. write_int() sets the etp_wr_pack field of the ethernet port to null and then calls eth_restart_write() to process any additional ethernet packets waiting to be sent out. If the ethernet packet just sent out is a multicast/broadcast packet or if the ethernet port is in promiscuous mode, eth_arrive() is called to place the packet in the read queue of the ethernet port.


0010262          if (stat & DL_PACK_RECV)
0010263                   read_int(loc_port, m->DL_COUNT);
The ethernet task has received an ethernet packet. The packet's length is DL_COUNT.


read_int()


After processing all of the ethernet packets that have been received by the ethernet task, setup_read() leaves behind a buffer in the ethernet port's etp_rd_pack field. When the ethernet task eventually receives another ethernet packet, the ethernet task copies the packet to this buffer and sends a message to the network service. This message prompts eth_rec() to be called, which then calls read_int().

read_int() simply passes the packet off to eth_arrive() to be processed and then calls setup_read() to either handle additional ethernet packets that have arrived or to set up another buffer in etp_rd_pack.


0010264 }
0010265 
0010266 #ifndef notdef
0010267 PUBLIC int eth_get_stat(eth_port, eth_stat)
0010268 eth_port_t *eth_port;
0010269 eth_stat_t *eth_stat;
eth_get_stat()

eth_get_stat(eth_port, eth_stat) sends a message to the ethernet task requesting the statistics for the ethernet port eth_port, eth_get_stat()'s first parameter. If successful, the ethernet task fills in the fields of the struct eth_stat_t eth_stat, eth_get_stat()'s second parameter.

eth_get_stat() is called only from eth_ioctl() in response to an NWIOGETHSTAT request.


0010270 {
0010271          acc_t *acc;
0010272          int result;
0010273          message mess, mlocked;
0010274 
The format of the message that eth_get_stat() sends to the ethernet task is as follows (from src/kernel/dp8390.c):


* m_type DL_PORT DL_PROC DL_COUNT DL_MODE DL_ADDR

* |------------|----------|---------|----------|---------|---------|
* | DL_GETSTAT | port nr | proc nr | | | address |
* |------------|----------|---------|----------|---------|---------|






0010275          mess.m_type= DL_GETSTAT;
0010276          mess.DL_PORT= eth_port->etp_osdep.etp_port;
0010277          mess.DL_PROC= this_proc;
0010278          mess.DL_ADDR= (char *)eth_stat;
nwio_ethstat_t

The nwio_ethstat struct is used by the ethernet driver to report statistics.

typedef struct nwio_ethstat

{
ether_addr_t nwes_addr;
eth_stat_t nwes_stat;
} nwio_ethstat_t;
ether_addr_t nwes_addr:

The ethernet address of an ethernet port.

eth_stat_t nwes_stat:

typedef struct eth_stat

{
unsigned long ets_recvErr, /* # receive errors */
ets_sendErr, /* # send error */
ets_OVW, /* # buffer overwrite warnings,
(packets arrive faster than
can be processed) */
ets_CRCerr, /* # crc errors of read */
ets_frameAll, /* # frames not alligned (# bits
not a mutiple of 8) */
ets_missedP, /* # packets missed due to too
slow packet processing */
ets_packetR, /* # packets received */
ets_packetT, /* # packets transmitted */
ets_transDef, /* # transmission defered (there
was a transmission of an
other station in progress */
ets_collision, /* # collissions */
ets_transAb, /* # transmissions aborted due
to accesive collisions */
ets_carrSense, /* # carrier sense lost */
ets_fifoUnder, /* # fifo underruns (processor
is too busy) */
ets_fifoOver, /* # fifo overruns (processor is
too busy) */
ets_CDheartbeat, /* # times unable to transmit
collision signal */
ets_OWC; /* # times out of window
collision */
} eth_stat_t;



0010279 
0010280          for (;;)
The first attempt to send a message may not be successful due to deadlock. Continue to attempt to send the message until deadlock does not occur.


0010281          {
0010282                   result= send(eth_port->etp_osdep.etp_task, &mess);
The message just built is sent to the ethernet task (driver). The response to this message is handled on line 10293.

Click here for a detailed description of the Minix message system.


0010283                   if (result != ELOCKED)
0010284                            break;
If this point in the code is reached, the message was blocked by a message sent by the ethernet task to the network service. Call eth_rec() to handle the message.


0010285                   result= receive(eth_port->etp_osdep.etp_task, &mlocked);
Receive the message that caused the deadlock.

Click here for a detailed description of the Minix message system.


0010286                   assert(result == OK);
0010287 
0010288                   compare(mlocked.m_type, ==, DL_TASK_REPLY);
If the message is not of type DL_TASK_REPLY, there's a problem. The ethernet task sends only DL_TASK_REPLY and DL_INIT_REPL messages and the only time that the ethernet task sends DL_INIT_REPL messages is in response to a DL_INIT message.


compare()


compare is #define'd in inet/generic/assert.h:

#define compare(a,t,b) (!((a) t (b)) ? bad_compare(this_file, __LINE__, \
(a), #a " " #t " " #b, (b)) : (void) 0)

and bad_compare() is defined in inet/inet.c.

If the relationship between the 3 arguments in compare() does not hold, some debugging output is emitted and then Minix is terminated.

For example, if compare(result, >=, 0) is called and result (the first argument) is -1, Minix will be terminated.


0010289                   eth_rec(&mlocked);
Any deadlocked messages are handled first. If the message in response to the send() on line 10282 is meaningful (i.e., a DL_PACK_SEND or DL_PACK_RECV message), the response message will also be handled by eth_rec().


eth_rec()


eth_rec() is called from the main loop or by eth_get_stat() or by eth_set_rec_conf() upon receipt of a message from the ethernet task. If the message is valid, the message will either indicate that a write operation has successfully ended (in which case, write_int() is called) or that the ethernet task has received a packet (in which case, read_int() is called).

The message (which is of type mess_2) has the following structure:
From dp8390.c:


*
* m-type DL_PORT DL_PROC DL_COUNT DL_STAT DL_CLCK
* |------------|----------|---------|----------|---------|---------|
* |DL_TASK_REPL| port nr | proc nr | rd-count | err|stat| clock |
* |------------|----------|---------|----------|---------|---------|
*

From include/minix/type.h:

typedef struct {
int m_source; /* who sent the message */
int m_type; /* what kind of message is it */
union {
mess_1 m_m1;
mess_2 m_m2;
mess_3 m_m3;
mess_4 m_m4;
mess_5 m_m5;
mess_6 m_m6;
} m_u;
} message;

typedef struct {int m2i1, m2i2, m2i3; long m2l1, m2l2; char *m2p1;} mess_2;

From include/minix/com.h:

# define DL_PORT m2_i1
# define DL_PROC m2_i2
# define DL_COUNT m2_i3
# define DL_CLCK m2_l2
# define DL_STAT m2_l1



0010290          }
0010291          assert(result == OK);
0010292 
0010293          result= receive(eth_port->etp_osdep.etp_task, &mess);
Receive the response message from the send() on line 10282. This message is, of course, sent by the ethernet task to the network service. This message will be for the same ethernet port as the message sent by the network service to the ethernet task. If the message came from the dp8390 ethernet task, the ethernet task copied the statistics to the network service.

Click here for a detailed description of the Minix message system.


0010294          assert(result == OK);
0010295          assert(mess.m_type == DL_TASK_REPLY);
0010296 
0010297          result= mess.DL_STAT >> 16;
0010298 assert (result == 0);
0010299 
0010300          if (mess.DL_STAT)
The DL_STAT field of the message includes the DL_PACK_SEND and DL_PACK_RECV flags. If one of these flags is set, pass the message off to eth_rec() to be processed.

From include/minix/com.h:

/* Bits in 'DL_STAT' field of DL replies. */
# define DL_PACK_SEND 0x01
# define DL_PACK_RECV 0x02


0010301          {
0010302 #if DEBUG
0010303  { where(); printf("calling eth_rec()\n"); }
0010304 #endif
0010305                   eth_rec(&mess);
eth_rec()

eth_rec() is called from the main loop or by eth_get_stat() or by eth_set_rec_conf() upon receipt of a message from the ethernet task. If the message is valid, the message will either indicate that a write operation has successfully ended (in which case, write_int() is called) or that the ethernet task has received a packet (in which case, read_int() is called).

The message (which is of type mess_2) has the following structure:
From dp8390.c:


*
* m-type DL_PORT DL_PROC DL_COUNT DL_STAT DL_CLCK
* |------------|----------|---------|----------|---------|---------|
* |DL_TASK_REPL| port nr | proc nr | rd-count | err|stat| clock |
* |------------|----------|---------|----------|---------|---------|
*

From include/minix/type.h:

typedef struct {
int m_source; /* who sent the message */
int m_type; /* what kind of message is it */
union {
mess_1 m_m1;
mess_2 m_m2;
mess_3 m_m3;
mess_4 m_m4;
mess_5 m_m5;
mess_6 m_m6;
} m_u;
} message;

typedef struct {int m2i1, m2i2, m2i3; long m2l1, m2l2; char *m2p1;} mess_2;

From include/minix/com.h:

# define DL_PORT m2_i1
# define DL_PROC m2_i2
# define DL_COUNT m2_i3
# define DL_CLCK m2_l2
# define DL_STAT m2_l1



0010306          }
0010307          return OK;
0010308 }
0010309 #endif
0010310 
0010311 #ifndef notdef
0010312 PUBLIC void eth_set_rec_conf (eth_port, flags)
0010313 eth_port_t *eth_port;
0010314 u32_t flags;
eth_set_rec_conf ()

eth_set_rec_conf() sends a message requesting the ethernet task to change the receiving capabilities for an ethernet port. For example, if an ethernet file descriptor is being configured to receive broadcasts, eth_set_rec_conf() will send a message to the ethernet task requesting that broadcast packets for the port be accepted.

Note that eth_set_rec_conf() is called by eth_ioctl() for a NWIOSETHOPT (NetWork IO Set ETHernet OPTions) request. Since only root can open and configure the device file /dev/eth, only root can configure an ethernet file descriptor and, therefore, only root is able to place the ethernet card in promiscuous mode.


0010315 {
0010316          int result;
0010317          unsigned dl_flags;
0010318          message mess, repl_mess;
0010319 
The DL_MODE field (see line 10331) of the message sent to the ethernet task determines whether the ethernet port receives broadcast and multicast packets and whether the ethernet port receives all packets seen by the ethernet task (i.e., promiscuous mode).


0010320          dl_flags= DL_NOMODE;
0010321          if (flags & NWEO_EN_BROAD)
0010322                   dl_flags |= DL_BROAD_REQ;
0010323          if (flags & NWEO_EN_MULTI)
0010324                   dl_flags |= DL_MULTI_REQ;
0010325          if (flags & NWEO_EN_PROMISC)
0010326                   dl_flags |= DL_PROMISC_REQ;
0010327 
0010328          mess.m_type= DL_INIT;
0010329          mess.DL_PORT= eth_port->etp_osdep.etp_port;
0010330          mess.DL_PROC= this_proc;
0010331          mess.DL_MODE= dl_flags;
A DL_INIT message has the following format (from dp8390.c):

* |------------|----------|---------|----------|---------|---------|
* | DL_INIT | port nr | proc nr | mode | | address |
* |------------|----------|---------|----------|---------|---------|



0010332 
0010333          do
The first attempt to send a message may not be successful due to deadlock. Continue to attempt to send the message until a deadlock does not occur.


0010334          {
0010335                   result= send (eth_port->etp_osdep.etp_task, &mess);
The message just built is sent to the ethernet task (driver). The response to this message is handled on line 10353.

Click here for a detailed description of the Minix message system.


0010336                   if (result == ELOCKED)
0010337                   /* Ethernet task is sending to this task, I hope */
0010338                   {
If this point in the code is reached, the message was blocked by a message sent by the ethernet task to the network service. Call eth_rec() to handle the message.


0010339                            if (receive (eth_port->etp_osdep.etp_task,
0010340                                     &repl_mess)< 0)
Receive the message that caused the deadlock.

Click here for a detailed description of the Minix message system.


0010341                            {
0010342                                     ip_panic(("unable to receive"));
0010343                            }
0010344 
0010345                            compare(repl_mess.m_type, ==, DL_TASK_REPLY);
If the message is not of type DL_TASK_REPLY, there's a problem.


compare()


compare is #define'd in inet/generic/assert.h:

#define compare(a,t,b) (!((a) t (b)) ? bad_compare(this_file, __LINE__, \
(a), #a " " #t " " #b, (b)) : (void) 0)

and bad_compare() is defined in inet/inet.c.

If the relationship between the 3 arguments in compare() does not hold, some debugging output is emitted and then Minix is terminated.

For example, if compare(result, >=, 0) is called and result (the first argument) is -1, Minix will be terminated.


0010346                            eth_rec(&repl_mess);
eth_rec()

eth_rec() is called from the main loop or by eth_get_stat() or by eth_set_rec_conf() upon receipt of a message from the ethernet task. If the message is valid, the message will either indicate that a write operation has successfully ended (in which case, write_int() is called) or that the ethernet task has received a packet (in which case, read_int() is called).

The message (which is of type mess_2) has the following structure:
From dp8390.c:


*
* m-type DL_PORT DL_PROC DL_COUNT DL_STAT DL_CLCK
* |------------|----------|---------|----------|---------|---------|
* |DL_TASK_REPL| port nr | proc nr | rd-count | err|stat| clock |
* |------------|----------|---------|----------|---------|---------|
*

From include/minix/type.h:

typedef struct {
int m_source; /* who sent the message */
int m_type; /* what kind of message is it */
union {
mess_1 m_m1;
mess_2 m_m2;
mess_3 m_m3;
mess_4 m_m4;
mess_5 m_m5;
mess_6 m_m6;
} m_u;
} message;

typedef struct {int m2i1, m2i2, m2i3; long m2l1, m2l2; char *m2p1;} mess_2;

From include/minix/com.h:

# define DL_PORT m2_i1
# define DL_PROC m2_i2
# define DL_COUNT m2_i3
# define DL_CLCK m2_l2
# define DL_STAT m2_l1



0010347                   }
0010348          } while (result == ELOCKED);
0010349          
0010350          if (result < 0)
0010351                   ip_panic(("unable to send(%d)", result));
0010352 
0010353          if (receive (eth_port->etp_osdep.etp_task, &repl_mess) < 0)
0010354                   ip_panic(("unable to receive"));
Receive the next message sent by the ethernet task to the network service.

The ethernet task sends only DL_TASK_REPLY and DL_INIT_REPL messages and the only time that the ethernet task sends DL_INIT_REPL messages is in response to a DL_INIT message.

Click here for a detailed description of the Minix message system.


0010355 
0010356          assert (repl_mess.m_type == DL_INIT_REPLY);
0010357          if (repl_mess.m3_i1 != eth_port->etp_osdep.etp_port)
0010358          {
0010359                   ip_panic(("got reply for wrong port"));
0010360          }
0010361          eth_port->etp_osdep.etp_recvconf= flags;
The ethernet task has accepted the configuration request. Set the ethernet port's flags to reflect this change.


0010362 }
0010363 #endif
0010364 
0010365 PRIVATE void write_int(eth_port)
0010366 eth_port_t *eth_port;
write_int()

After the ethernet task sends off an ethernet packet, the task sends a message to the network service indicating success and write_int() is subsequently called. write_int() sets the etp_wr_pack field of the ethernet port to null and then calls eth_restart_write() to process any additional ethernet packets waiting to be sent out. If the ethernet packet just sent out is a multicast/broadcast packet or if the ethernet port is in promiscuous mode, eth_arrive() is called to place the packet in the read queue of the ethernet port.


0010367 {
0010368          acc_t *pack;
0010369          int multicast;
0010370          u8_t *eth_dst_ptr;
0010371 
0010372          pack= eth_port->etp_wr_pack;
0010373          eth_port->etp_wr_pack= NULL;
A packet is stored in the etp_wr_pack until it is sent out by the ethernet task. Click here for a description of etp_wr_pack, dl_eth.de_frame and the associated queue.


0010374 
0010375          eth_dst_ptr= (u8_t *)ptr2acc_data(pack);
ptr2acc_data()

The macro ptr2acc_data is #define'd in inet/generic/buf.h as:

#define ptr2acc_data(/* acc_t * */ a) (bf_temporary_acc=(a), \
(&bf_temporary_acc->acc_buffer->buf_data_p[bf_temporary_acc-> \
acc_offset]))

ptr2acc_data() simply returns a pointer to the actual data within an accessor.

ptr2acc_data() is usually called so that the fields of a header (e.g., ip header) can be analyzed.


0010376          multicast= (*eth_dst_ptr & 1);       /* low order bit indicates multicast */
0010377          if (multicast || (eth_port->etp_osdep.etp_recvconf & NWEO_EN_PROMISC))
0010378                   eth_arrive(eth_port, pack, bf_bufsize(pack));
if multicast or promiscuous the other file descriptors of the current port probably should be informed about, therefore eth_arrive() is called to receive the outward packet


eth_arrive()


eth_arrive() is called when either the ethernet task receives an ethernet packet, when an ethernet multicast/broadcast packet is sent out of an ethernet port, or when an ethernet packet is destined for a local ethernet port. For a given packet, eth_arrive() finds the ethernet file descriptors that are interested in the packet. eth_arrive() then hands the packet off to the ip layer by calling either packet2user() or ip_eth_arrived() for these ethernet file descriptors.


0010379          else
0010380                   bf_afree(pack);
no multicast or promiscuous mode. The reference to the packet that was sent, pack, can freed now


bf_afree()


After a chain of accessors is no longer needed, the chain (and not simply the single accessor passed as the parameter) can be freed by calling bf_free(). However, if either acc_linkC or buf_linkC of one of the accessors in the linked list is not equal to one (1), the entire chain will not be freed. For example, if buf_afree(acc1) is called for the following chain:



Then the resulting chain will be:



bf_afree() returns acc1 (accessors[63]) to acc_freelist (recall that acc_freelist is the linked list of acc_t's without an associated buffer). However, buffers512[127] cannot be freed because acc2 (accessors[64]) still references it.

bf_afree() is called after an accessor's associated data is no longer needed (for example, after a packet has been sent off by the ethernet driver).


0010381 
0010382          eth_restart_write(eth_port);
eth_restart_write()

eth_restart_write(eth_port) is called after the ethernet task successfully sends out an ethernet packet (if the destination of the ethernet packet is remote) or after the ethernet packet is handed off to its destination ethernet port (if the destination of the ethernet packet is local). If a second ethernet packet arrived from the ip layer while the previous packet was being sent out by the ethernet task, the packet will be placed in the ip port's dl_eth.de_frame field. eth_restart_write()'s task is to pass the ethernet packet on to eth_write() so that eth_write() can send the frame out.


0010383 }
0010384 
0010385 PRIVATE void read_int(eth_port, count)
0010386 eth_port_t *eth_port;
0010387 int count;
read_int()

After processing all of the ethernet packets that have been received by the ethernet task, setup_read() leaves behind a buffer in the ethernet port's etp_rd_pack field. When the ethernet task eventually receives another ethernet packet, the ethernet task copies the packet to this buffer and sends a message to the network service. This message prompts eth_rec() to be called, which then calls read_int().

read_int() simply passes the packet off to eth_arrive() to be processed and then calls setup_read() to either handle additional ethernet packets that have arrived or to set up another buffer in etp_rd_pack.


0010388 {
0010389          acc_t *pack, *cut_pack;
0010390 
0010391          pack= eth_port->etp_rd_pack;
As described above, setup_read() leaves behind a buffer in etp_rd_pack to which the ethernet task can copy a newly arrived ethernet packet.


0010392          eth_port->etp_rd_pack= NULL;
setup_read() on line 10400 provides another buffer to which the ethernet task can write.


0010393 
The ethernet task passed off an ethernet packet that may be smaller than the buffer. Cut the packet to size.


0010394          cut_pack= bf_cut(pack, 0, count);
bf_cut()

If a section of a linked list needs to be duplicated, bf_cut(data, offset, length) is called. For example, if a section of length 50 starting at an offset of 75 of the linked list below needs to be duplicated, bf_cut(data, 75, 50) is called:



Note that the original linked list remains unchanged and that acc_linkC for all the accessors in the new linked list is one.

If length (the second parameter) is zero, simply duplicate the first accessor in the linked list but set acc_length=0 and acc_next=null. In other words, create a linked list of length one accessor whose acc_length is 0.

bf_cut() is used in a number of scenarios, including cutting a received ethernet packet to size.

For a full description of the network service's buffer management, click here.



0010395          bf_afree(pack);
bf_afree()

After a chain of accessors is no longer needed, the chain (and not simply the single accessor passed as the parameter) can be freed by calling bf_free(). However, if either acc_linkC or buf_linkC of one of the accessors in the linked list is not equal to one (1), the entire chain will not be freed. For example, if buf_afree(acc1) is called for the following chain:



Then the resulting chain will be:



bf_afree() returns acc1 (accessors[63]) to acc_freelist (recall that acc_freelist is the linked list of acc_t's without an associated buffer). However, buffers512[127] cannot be freed because acc2 (accessors[64]) still references it.

bf_afree() is called after an accessor's associated data is no longer needed (for example, after a packet has been sent off by the ethernet driver).


0010396 
0010397          eth_arrive(eth_port, cut_pack, count);
eth_arrive()

eth_arrive() is called when either the ethernet task receives an ethernet packet, when an ethernet multicast/broadcast packet is sent out of an ethernet port, or when an ethernet packet is destined for a local ethernet port. For a given packet, eth_arrive() finds the ethernet file descriptors that are interested in the packet. eth_arrive() then hands the packet off to the ip layer by calling either packet2user() or ip_eth_arrived() for these ethernet file descriptors.


0010398          
0010399          eth_port->etp_flags &= ~(EPF_READ_IP|EPF_READ_SP);
After setup_read() returns (see next line), the EPF_READ_IP and EPF_READ_SP flags will be set again (in other words, a read in progress has been suspended).


0010400          setup_read(eth_port);
setup_read() / mnx_eth

setup_read(eth_port) sends a message to the ethernet task inquiring whether an ethernet packet has arrived that is destined for the ethernet port eth_port, setup_read()'s only parameter. If a datagram has arrived, the datagram is copied to a buffer within the network service and the datagram is handed off to eth_arrive().

After processing all the packets that the ethernet task has received and copied over to the network service, setup_read() leaves a buffer in the ethernet port's etp_rd_pack field to which the ethernet task can later copy another ethernet packet after the packet arrives.


0010401 }
0010402 
0010403 PRIVATE void setup_read(eth_port)
0010404 eth_port_t *eth_port;
setup_read() / mnx_eth

setup_read(eth_port) sends a message to the ethernet task inquiring whether an ethernet packet has arrived that is destined for the ethernet port eth_port, setup_read()'s only parameter. If a datagram has arrived, the datagram is copied to a buffer within the network service and the datagram is handed off to eth_arrive().

After processing all the packets that the ethernet task has received and copied over to the network service, setup_read() leaves a buffer in the ethernet port's etp_rd_pack field to which the ethernet task can later copy another ethernet packet after the packet arrives.


0010405 {
0010406          eth_port_t *loc_port;
0010407          acc_t *pack, *pack_ptr;
0010408          message mess1, block_msg;
0010409          iovec_t *iovec;
0010410          ev_arg_t ev_arg;
0010411          int i, r;
0010412 
0010413          assert(!(eth_port->etp_flags & (EPF_READ_IP|EPF_READ_SP)));
0010414 
0010415          do
0010416          {
0010417                   assert (!eth_port->etp_rd_pack);
0010418 
0010419                   iovec= eth_port->etp_osdep.etp_rd_iovec;
0010420                   pack= bf_memreq (ETH_MAX_PACK_SIZE);
Acquire a buffer that can hold the largest possible ethernet packet.

ETH_MAX_PACK_SIZE is #define'd in include/net/gen/ether.h:

#define ETH_MAX_PACK_SIZE 1514


bf_memreq()


After the buffers have been initialized, accessors[] looks like the following:



bf_memreq() allocates accessors to the caller. For example, if 1514 bytes of buffer space are requested immediately after the network process starts and each buffer is 512 bytes (the default), then accessors[] will look like the following:



Note that three elements of accessors[] have been removed from buf512_freelist and that the head of the chain of the 3 accessors is returned by bf_memreq(). Also note that the acc_linkC and buf_linkC fields have been set to one and acc_length and acc_offset have been set to their appropriate values.

So what happens if there are not enough buffers on the buf512_freelist to satisfy a request? On lines 2280-2290 of buf.c, functions that free buffers for the specific clients (e.g., eth_buffree()) are called until there are enough buffers on buf512_freelist.

For a complete description of the network service's buffer management, click here.


0010421 
0010422                   for (i=0, pack_ptr= pack; i<RD_IOVEC && pack_ptr;
0010423                            i++, pack_ptr= pack_ptr->acc_next)
Associate each accessor with an element of iovec[]. One of the fields of the message that will be sent to the ethernet driver is a pointer to iovec[]. The ethernet driver in return will fill this buffer with the packet that was received.

Lines 10422-10434 build a DL_READV message ("DL" stands for "Data Link", "V" stands for "Vector read") that will be sent on line 10438. The message has the following format (from src/kernel/dp8390.c):

* |------------|----------|---------|----------|---------|---------|
* | DL_READV | port nr | proc nr | count | | address |
* |------------|----------|---------|----------|---------|---------|



This message requests a "vector read" (i.e., do_vread()) from the ethernet driver.


0010424                   {
0010425                            iovec[i].iov_addr= (vir_bytes)ptr2acc_data(pack_ptr);
ptr2acc_data()

The macro ptr2acc_data is #define'd in inet/generic/buf.h as:

#define ptr2acc_data(/* acc_t * */ a) (bf_temporary_acc=(a), \
(&bf_temporary_acc->acc_buffer->buf_data_p[bf_temporary_acc-> \
acc_offset]))

ptr2acc_data() simply returns a pointer to the actual data within an accessor.

ptr2acc_data() is usually called so that the fields of a header (e.g., ip header) can be analyzed.


0010426                            iovec[i].iov_size= (vir_bytes)pack_ptr->acc_length;
0010427                   }
0010428                   assert (!pack_ptr);
0010429 
0010430                   mess1.m_type= DL_READV;
0010431                   mess1.DL_PORT= eth_port->etp_osdep.etp_port;
0010432                   mess1.DL_PROC= this_proc;
0010433                   mess1.DL_COUNT= i;
0010434                   mess1.DL_ADDR= (char *)iovec;
0010435 
0010436                   for (;;)
The first attempt to send a message may not be successful due to deadlock. Continue to attempt to send the message until deadlock does not occur.


0010437                   {
0010438                            r= send (eth_port->etp_osdep.etp_task, &mess1);
The message just built is sent to the ethernet task (driver). The response to this message is handled on line 10474.

Click here for a detailed description of the Minix message system.




0010439                            if (r != ELOCKED)
0010440                                     break;
0010441 
If this point in the code is reached, the message was blocked by another message sent by the ethernet task to the network service. This second message - the blocking message - is either the result of a previous read request from another ethernet port or a message indicating that the ethernet task has sent an ethernet packet (in which case, the message may be destined for the same ethernet port).

In both of these cases, determine the destination ethernet port and place the blocking message in the port's event queue.

If the message was not a blocking message, the code continues on line 10189.




0010442                            /* ethernet task is sending to this task, I hope */
0010443                            r= receive(eth_port->etp_osdep.etp_task, &block_msg);
Receive the message that caused the deadlock.

Click here for a detailed description of the Minix message system.


0010444                            if (r < 0)
0010445                                     ip_panic(("unable to receive"));
0010446 
0010447                            loc_port= eth_port;
0010448                            if (loc_port->etp_osdep.etp_port != block_msg.DL_PORT)
The message from the ethernet task is for a different ethernet port. Find the port.


0010449                            {
0010450                                     loc_port= find_port(&block_msg);
find_port()

find_port(m) returns the ethernet port whose etp_port field is equal to DL_PORT field of the message m, find_port()'s only parameter.

For the following inet.conf file for a system with two DP8390 ethernet cards:

eth0 DP8390 0 { default; };
psip1;
eth2 DP8390 1;


the ethernet port associated with eth0 will have an etp_port field equal to 0 and the ethernet port associated with eth2 will have an etp_port field equal to 1.

find_port() is called by setup_read() and eth_write_port(). Both functions call find_port() after an attempt to send a message to the ethernet task is blocked by a message sent by the ethernet task to the network service.


0010451                            }
0010452                            assert(block_msg.DL_STAT &
0010453                                     (DL_PACK_SEND|DL_PACK_RECV));
0010454                            if (block_msg.DL_STAT & DL_PACK_SEND)
The message indicates that a packet was sent. Place an event in the event queue of the appropriate ethernet port.


0010455                            {
0010456                                     loc_port->etp_osdep.etp_sendrepl= block_msg;
0010457                                     ev_arg.ev_ptr= loc_port;
0010458                                     ev_enqueue(&loc_port->etp_sendev, eth_sendev,
0010459                                              ev_arg);
event_t / ev_enqueue() / ev_process() / ev_init() / ev_in_queue()

The event_t typedef is declared in inet/generic/event.h:

typedef struct event

{
ev_func_t ev_func;
ev_arg_t ev_arg;
struct event *ev_next;
} event_t;
If an event needs to be scheduled, ev_enqueue() is called to place the event in the system-wide event queue whose head is ev_head. ev_process() is eventually called from the main loop in inet.c to process the events. ev_in_queue(ev) simply returns TRUE if the event ev, ev_in_queue()'s only parameter, has a non-null value for func (see below) and FALSE if func is null. In this way, ev_in_queue() determines whether the event has been configured.

ev_init(ev) simply zeroes out the ev_func and ev_next fields of the event ev, ev_init()'s only parameter.

ev_func: A function (e.g., ip_process_loopb()) that performs some task.

ev_arg:

typedef union ev_arg

{
int ev_int;
void *ev_ptr;
} ev_arg_t;
ev_arg is ev_func's argument. In the case of a packet destined for the loopback address (127.0.0.1), the argument will be the ip port associated with the ip file descriptor that is sending out the packet. In the case of a message from the ethernet task that caused a deadlock, ev_arg is a pointer to the message's destination ethernet port.

ev_next: The next event in the system-wide event queue.eth_sendev()

When a message indicating a packet has been written by the ethernet task is received by the network service but the network service must complete another task first before handling this message, an event is placed in the event queue. When the network service eventually processes the event, eth_sendev() is called and it, in turn, calls write_int() to process any additional ethernet packets waiting to be sent out.


0010460                            }
0010461                            if (block_msg.DL_STAT & DL_PACK_RECV)
The message indicates that a packet was received. Place an event in the event queue of the appropriate ethernet port.


0010462                            {
0010463                                     assert(loc_port != eth_port);
0010464                                     loc_port->etp_osdep.etp_recvrepl= block_msg;
0010465                                     ev_arg.ev_ptr= loc_port;
0010466                                     ev_enqueue(&loc_port->etp_osdep.etp_recvev,
0010467                                              eth_recvev, ev_arg);
event_t / ev_enqueue() / ev_process() / ev_init() / ev_in_queue()

The event_t typedef is declared in inet/generic/event.h:

typedef struct event

{
ev_func_t ev_func;
ev_arg_t ev_arg;
struct event *ev_next;
} event_t;
If an event needs to be scheduled, ev_enqueue() is called to place the event in the system-wide event queue whose head is ev_head. ev_process() is eventually called from the main loop in inet.c to process the events. ev_in_queue(ev) simply returns TRUE if the event ev, ev_in_queue()'s only parameter, has a non-null value for func (see below) and FALSE if func is null. In this way, ev_in_queue() determines whether the event has been configured.

ev_init(ev) simply zeroes out the ev_func and ev_next fields of the event ev, ev_init()'s only parameter.

ev_func: A function (e.g., ip_process_loopb()) that performs some task.

ev_arg:

typedef union ev_arg

{
int ev_int;
void *ev_ptr;
} ev_arg_t;
ev_arg is ev_func's argument. In the case of a packet destined for the loopback address (127.0.0.1), the argument will be the ip port associated with the ip file descriptor that is sending out the packet. In the case of a message from the ethernet task that caused a deadlock, ev_arg is a pointer to the message's destination ethernet port.

ev_next: The next event in the system-wide event queue.eth_recvev()

When a message from the ethernet task indicating a received packet is received by the network service but the network service must complete another task first, an event is placed in the event queue. When the network service eventually processes the event, eth_recvev() is called and it, in turn, calls read_int() to process the packet.


0010468                            }
0010469                   }
0010470 
0010471                   if (r < 0)
All errors for send() other than ELOCKED (which was handled on lines 10443-10469) are unforgivable.


0010472                            ip_panic(("unable to send"));
0010473 
0010474                   r= receive (eth_port->etp_osdep.etp_task, &mess1);
0010475                   if (r < 0)
0010476                            ip_panic(("unable to receive"));
0010477 
0010478                   assert (mess1.m_type == DL_TASK_REPLY &&
0010479                            mess1.DL_PORT == mess1.DL_PORT &&
0010480                            mess1.DL_PROC == this_proc);
0010481                   compare((mess1.DL_STAT >> 16), ==, OK);
compare()

compare is #define'd in inet/generic/assert.h:

#define compare(a,t,b) (!((a) t (b)) ? bad_compare(this_file, __LINE__, \
(a), #a " " #t " " #b, (b)) : (void) 0)

and bad_compare() is defined in inet/inet.c.

If the relationship between the 3 arguments in compare() does not hold, some debugging output is emitted and then Minix is terminated.

For example, if compare(result, >=, 0) is called and result (the first argument) is -1, Minix will be terminated.


0010482 
0010483                   if (mess1.DL_STAT & DL_PACK_RECV)
A packet of size mess1.DL_COUNT destined to this ethernet port was received by the ethernet driver and was copied to the buffer. Trim the buffer to the size of the packet with bf_cut().


0010484                   {
0010485                            /* packet received */
0010486                            pack_ptr= bf_cut(pack, 0, mess1.DL_COUNT);
bf_cut()

If a section of a linked list needs to be duplicated, bf_cut(data, offset, length) is called. For example, if a section of length 50 starting at an offset of 75 of the linked list below needs to be duplicated, bf_cut(data, 75, 50) is called:



Note that the original linked list remains unchanged and that acc_linkC for all the accessors in the new linked list is one.

If length (the second parameter) is zero, simply duplicate the first accessor in the linked list but set acc_length=0 and acc_next=null. In other words, create a linked list of length one accessor whose acc_length is 0.

bf_cut() is used in a number of scenarios, including cutting a received ethernet packet to size.

For a full description of the network service's buffer management, click here.



0010487                            bf_afree(pack);
bf_afree()

After a chain of accessors is no longer needed, the chain (and not simply the single accessor passed as the parameter) can be freed by calling bf_free(). However, if either acc_linkC or buf_linkC of one of the accessors in the linked list is not equal to one (1), the entire chain will not be freed. For example, if buf_afree(acc1) is called for the following chain:



Then the resulting chain will be:



bf_afree() returns acc1 (accessors[63]) to acc_freelist (recall that acc_freelist is the linked list of acc_t's without an associated buffer). However, buffers512[127] cannot be freed because acc2 (accessors[64]) still references it.

bf_afree() is called after an accessor's associated data is no longer needed (for example, after a packet has been sent off by the ethernet driver).


0010488 
0010489                            eth_arrive(eth_port, pack_ptr, mess1.DL_COUNT);
eth_arrive()

eth_arrive() is called when either the ethernet task receives an ethernet packet, when an ethernet multicast/broadcast packet is sent out of an ethernet port, or when an ethernet packet is destined for a local ethernet port. For a given packet, eth_arrive() finds the ethernet file descriptors that are interested in the packet. eth_arrive() then hands the packet off to the ip layer by calling either packet2user() or ip_eth_arrived() for these ethernet file descriptors.


0010490                   }
0010491                   else
0010492                   {
0010493                            /* no packet received */
0010494                            eth_port->etp_rd_pack= pack;
0010495                            eth_port->etp_flags |= EPF_READ_IP;
The ethernet driver will place the next received packet into pack.


0010496                   }
0010497 
0010498                   if (mess1.DL_STAT & DL_PACK_SEND)
A confirmation reply arrived for a packet that was previously sent. An event is queued to handle this at a later time.


0010499                   {
0010500                            eth_port->etp_osdep.etp_sendrepl= mess1;
0010501                            ev_arg.ev_ptr= eth_port;
0010502                            ev_enqueue(&eth_port->etp_sendev, eth_sendev, ev_arg);
event_t / ev_enqueue() / ev_process() / ev_init() / ev_in_queue()

The event_t typedef is declared in inet/generic/event.h:

typedef struct event

{
ev_func_t ev_func;
ev_arg_t ev_arg;
struct event *ev_next;
} event_t;
If an event needs to be scheduled, ev_enqueue() is called to place the event in the system-wide event queue whose head is ev_head. ev_process() is eventually called from the main loop in inet.c to process the events. ev_in_queue(ev) simply returns TRUE if the event ev, ev_in_queue()'s only parameter, has a non-null value for func (see below) and FALSE if func is null. In this way, ev_in_queue() determines whether the event has been configured.

ev_init(ev) simply zeroes out the ev_func and ev_next fields of the event ev, ev_init()'s only parameter.

ev_func: A function (e.g., ip_process_loopb()) that performs some task.

ev_arg:

typedef union ev_arg

{
int ev_int;
void *ev_ptr;
} ev_arg_t;
ev_arg is ev_func's argument. In the case of a packet destined for the loopback address (127.0.0.1), the argument will be the ip port associated with the ip file descriptor that is sending out the packet. In the case of a message from the ethernet task that caused a deadlock, ev_arg is a pointer to the message's destination ethernet port.

ev_next: The next event in the system-wide event queue.eth_sendev()

When a message indicating a packet has been written by the ethernet task is received by the network service but the network service must complete another task first before handling this message, an event is placed in the event queue. When the network service eventually processes the event, eth_sendev() is called and it, in turn, calls write_int() to process any additional ethernet packets waiting to be sent out.


0010503                   }
0010504          } while (!(eth_port->etp_flags & EPF_READ_IP));
If this condition is false, the ethernet driver has received no more packets destined for the ethernet port (see line 10495). An important point to note is that when setup_read() returns, it leaves a buffer waiting in etp_rd_pack for the ethernet task to fill (see line 10494). Upon writing a packet to this buffer, the task sends a message to the network service, which in turn causes read_int(), beginning the packet's path to the destination process(es).


0010505          eth_port->etp_flags |= EPF_READ_SP;
0010506 }
0010507 
0010508 PRIVATE void eth_recvev(ev, ev_arg)
0010509 event_t *ev;
0010510 ev_arg_t ev_arg;
eth_recvev()

When a message from the ethernet task indicating a received packet is received by the network service but the network service must complete another task first, an event is placed in the event queue. When the network service eventually processes the event, eth_recvev() is called and it, in turn, calls read_int() to process the packet.


0010511 {
0010512          eth_port_t *eth_port;
0010513          message *m_ptr;
0010514 
0010515          eth_port= ev_arg.ev_ptr;
A pointer to the ethernet port was stored in the ev_ptr field of ev_arg.



0010516          assert(ev == &eth_port->etp_osdep.etp_recvev);
0010517          m_ptr= &eth_port->etp_osdep.etp_recvrepl;
The message requesting the read was placed in the etp_recvrepl field of the ethernet port. The message must be analyzed to determine the number of bytes in the packet (see line 10525).


0010518 
0010519          assert(m_ptr->m_type == DL_TASK_REPLY);
0010520          assert(eth_port->etp_osdep.etp_port == m_ptr->DL_PORT);
0010521 
0010522          assert(m_ptr->DL_STAT & DL_PACK_RECV);
0010523          m_ptr->DL_STAT &= ~DL_PACK_RECV;
This is a (probably unnecessary) precaution to avoid handling reply packets twice.


0010524 
0010525          read_int(eth_port, m_ptr->DL_COUNT);
read_int()

After processing all of the ethernet packets that have been received by the ethernet task, setup_read() leaves behind a buffer in the ethernet port's etp_rd_pack field. When the ethernet task eventually receives another ethernet packet, the ethernet task copies the packet to this buffer and sends a message to the network service. This message prompts eth_rec() to be called, which then calls read_int().

read_int() simply passes the packet off to eth_arrive() to be processed and then calls setup_read() to either handle additional ethernet packets that have arrived or to set up another buffer in etp_rd_pack.


0010526 }
0010527 
0010528 PRIVATE void eth_sendev(ev, ev_arg)
0010529 event_t *ev;
0010530 ev_arg_t ev_arg;
eth_sendev()

When a message indicating a packet has been written by the ethernet task is received by the network service but the network service must complete another task first before handling this message, an event is placed in the event queue. When the network service eventually processes the event, eth_sendev() is called and it, in turn, calls write_int() to process any additional ethernet packets waiting to be sent out.


0010531 {
0010532          eth_port_t *eth_port;
0010533          message *m_ptr;
0010534 
0010535          eth_port= ev_arg.ev_ptr;
A pointer to the ethernet port was stored in the ev_ptr field of ev_arg.


0010536          assert(ev == &eth_port->etp_sendev);
0010537          m_ptr= &eth_port->etp_osdep.etp_sendrepl;
The message requesting the write was placed in the etp_recvrepl field of the ethernet port.


0010538 
0010539          assert (m_ptr->m_type == DL_TASK_REPLY);
0010540          assert(eth_port->etp_osdep.etp_port == m_ptr->DL_PORT);
0010541 
0010542          assert(m_ptr->DL_STAT & DL_PACK_SEND);
0010543          m_ptr->DL_STAT &= ~DL_PACK_SEND;
This is a (probably unnecessary) precaution to avoid handling reply packets twice.


0010544 
0010545          /* packet is sent */
0010546          write_int(eth_port);
write_int()

After the ethernet task sends off an ethernet packet, the task sends a message to the network service indicating success and write_int() is subsequently called. write_int() sets the etp_wr_pack field of the ethernet port to null and then calls eth_restart_write() to process any additional ethernet packets waiting to be sent out. If the ethernet packet just sent out is a multicast/broadcast packet or if the ethernet port is in promiscuous mode, eth_arrive() is called to place the packet in the read queue of the ethernet port.


0010547 }
0010548 
0010549 PRIVATE eth_port_t *find_port(m)
0010550 message *m;
find_port()

find_port(m) returns the ethernet port whose etp_port field is equal to DL_PORT field of the message m, find_port()'s only parameter.

For the following inet.conf file for a system with two DP8390 ethernet cards:

eth0 DP8390 0 { default; };
psip1;
eth2 DP8390 1;


the ethernet port associated with eth0 will have an etp_port field equal to 0 and the ethernet port associated with eth2 will have an etp_port field equal to 1.

find_port() is called by setup_read() and eth_write_port(). Both functions call find_port() after an attempt to send a message to the ethernet task is blocked by a message sent by the ethernet task to the network service.


0010551 {
0010552          eth_port_t *loc_port;
0010553          int i;
0010554 
0010555          for (i=0, loc_port= eth_port_table; i<eth_conf_nr; i++, loc_port++)
Find the message's destination ethernet port.


0010556          {
0010557                   if (loc_port->etp_osdep.etp_port == m->DL_PORT)
0010558                            break;
0010559          }
0010560          assert (i<eth_conf_nr);
0010561          return loc_port;
0010562 }
0010563 
0010564 /*
0010565  * $PchId: mnx_eth.c,v 1.8 1995/11/21 06:41:57 philip Exp $
0010566  */