October 25, 2022

BSD TCP/IP for Kyu - hp300 "lance" driver

Chapters 3, 4, and 5 of the book talk about drivers. Chapter 3 discusses data structures common to all drivers. Chapter 4 focuses on the ethernet driver. Chapter 5 talks about the SLIP and loopback driver.

I intend to ignore the slip driver. Source for the ethernet driver is in usr/src/sys/hp300/dev in the files if_le.c and if_lereg.h

This is the AMD AM7990 chip. I still have the 1994 AMD Ethernet family databook on my shelf and it describes the AM79C90 chip which they call the "Local Area Network Controller for Ethernet", which is the origin of the name "LANCE".

peek at the lance datasheet

The lance has 4 registers (CSR's) accessed through 2 addressable ports, an address port (RAP) and a data port (RDP). So you select a register by writing to the address port, then read or write the register by an access to the data port -- a 2 step operation. These are 16 bit registers. The action takes place in system memory. You set up an initialization block, tell the lance where it is, then tell the lance to initialize.

The driver is a bit misleading. It calls the actual lance hardware registers "lereg1". The hp300 board apparently has some registers it calls lereg0, and the driver calls control structures in ram "lereg2". A fourth area of memory is NVRAM, from which it gets the ethernet address.

peek at the lance driver

This is 920 lines of code. Another 153 lines for the associated header file.

It looks like once the driver has received a packet, it calls "ether_input" to hand it up to the IP layer. This happens in the leread() routine. The routine ether_input() is in net/if_ethersubr.c Using grep to search, it looks like each driver typically makes a single call to ether_input() when it receives packets.

It looks like leput() gets called to give the driver a packet to transmit. However this is just a call internal to the driver (may as well be static). The routine leput() gets called by lestart() and this routine loops pulling packets off of the "if_snd" queue and passing them to leput().

The routine ether_output() is in net/if_ethersubr.c right alongside of ether_input(). This puts the packet on the if_snd queue and calls the start function if appropriate.

Searching for calls to ether_output is surprisingly more complex. Each driver places a pointer to ether_output into the if_output element of its if_net structure. This is used to make a call in 2 places in the routine ip_output() (in netinet/ip_output.c). It is also used in netinet/if_ether.c in the ARP handling code (3 calls).

ethernet to IP

Looking at ether_input(), the game is to decide which protocol we are dealing with. The BSD code handles a bunch of wonky protocols (OSI, LLC, NS, ...). I am only interested in TCP/IP. For us, ether_input() puts the packet on the ipintrq queue and schedules a software interrupt on NETISR_IP -- the protocol family is "INET".

Searching on ipintrq takes us to ip_input.c We could view things so far as 2 layers, maybe 3. First we have the device driver layer. Second we have the generic ethernet layer. Now we have the IP layer.

The routine ipintr() pulls just one packet off of ipintrq and processes it. It performs a lot of validation, then finally uses a table of protocol switch structures to hand it to the proper protocol input routine. it calls:

(*inetsw[ip_protox[ip->ip_p]].pr_input)(m, hlen);
This is initialized in ip_proto.c and for TCP the entry looks like:
{ SOCK_STREAM,  &inetdomain,    IPPROTO_TCP,    PR_CONNREQUIRED|PR_WANTRCVD,
  tcp_input,    0,              tcp_ctlinput,   tcp_ctloutput,
  tcp_usrreq,
  tcp_init,     tcp_fasttimo,   tcp_slowtimo,   tcp_drain,
The packet gets passed to tcp_input() which is in tcp_input.c

The TCP code totals to 4448 lines as follows:

[tom@trona netinet]$ wc tcp*
   159    605   4529 tcp_debug.c
    59    352   2305 tcp_debug.h
    85    540   3595 tcp_fsm.h
    98    541   3639 tcp.h
  1647   6745  47086 tcp_input.c
    59    375   2524 tcpip.h
   599   2636  17475 tcp_output.c
    62    409   2652 tcp_seq.h
   445   1853  12735 tcp_subr.c
   312   1317   9099 tcp_timer.c
   128    924   5734 tcp_timer.h
   517   1816  12645 tcp_usrreq.c
   278   1826  12088 tcp_var.h
  4448  19939 136106 total

More than just a peek at the driver

It is hard, for me at least, to just plow through the book without actively looking at the source code also -- and asking questions. So now we can see tcp_input() getting received packets from the IP layer. Also calls to tcp_output() winds up calling ip_output() to send packets to the IP layer.

There is a missing third player in the TCP game, and that is interaction with the socket layer. How do reads and writes take place? What sorts of things besides reads and writes do we need to consider? Calls to bind, listen, accept, and socket options come to mind.

All of that however is a topic for another page ...


Have any comments? Questions? Drop me a line!

Kyu / [email protected]