October 26, 2024

BSD TCP/IP for Kyu - the AMD "lance" chip and the BSD hp300 driver

The "lance" chip is the AM7990 (or the equivalent CMOS AM79C90 chip). The BSD code calls this the "le" device (for lance ethernet).

The relevant files are if_le.c and if_lereg.h which may be found in:

usr/src/sys/hp300/dev
/u1/BSD/4.4BSD-Lite/usr/src/sys/hp300/dev/if_le.c (on my system)
The hardware interface for the lance is two 16 bit registers, called RDP and RAP. This chip was designed in the days when address space was precious, so they set this chip up to work "through a knothole" as I like to say.

This pair of registers gives access to a 4 registers (CSR0, 1, 2, 3). A reasonable person would ask why these simply were not placed on the bus and the RAP, RDP business done away with, but it wasn't done that way.

The RAP register has only 2 active bits (to select one of the 4 CSR registers). So, to write (or read) to a CSR register, you first select it, by placing the value 0-3 into RAP, then you either read or write from the RDP address.

The four CSR are like this:

To initialize the chip, you set up a 12 word "initialization block" in ram somewhere, put the address of this into CSR1 and CSR2, then manipulate bits in CSR0 that tell the chip to read the block and initialize itself.

The chip will use DMA to read the init block, so values in CSR3 will need to be set up properly for this to work.

It will also be necessary to set up buffer rings in ram somewhere for the chip to receive and send data. I don't intend to talk about that here.

BSD driver code

First, look at if_lereg.h and take note of these:
struct lereg0 {
    u_char  ler0_pad0;
    vu_char ler0_id;    /* ID */
    u_char  ler0_pad1;
    vu_char ler0_status;    /* interrupt enable/status */
};

struct lereg1 {
    u_short ler1_rdp;   /* data port */
    u_short ler1_rap;   /* register select port */
};
The first structure (lereg0) does not seem to refer to anything described in the am7990 datasheet. We can only assume these two registers are implemented by TTL logic that is not part of the am7990 chip. The second structure (lereg1) give us RDP and RAP as per our discussion above. We then have these defines to give names to the CSR registers:
#define LE_CSR0     0
#define LE_CSR1     1
#define LE_CSR2     2
#define LE_CSR3     3
With this knowledge, lets look at if_le.c
First we search on lereg1 and find:
le->sc_r1 = (struct lereg1 *)(lestd[1] + (int)hd->hp_addr);
This sets up a pointer to the structure containing the RDP, RAP pair, as we might expect. It would be worth chasing down "lestd" and "hd->hp_addr" to see how the base address for this hardware is obtained. We will postpone that for now.

What is interesting is to search on LE_CSR1, which is used in only one place in the lereset() function as follows:

register struct lereg0 *ler0 = le->sc_r0;
register struct lereg1 *ler1 = le->sc_r1;

    LERDWR(ler0, LE_CSR1, ler1->ler1_rap);
    LERDWR(ler0, (int)&lemem->ler2_mode, ler1->ler1_rdp);
    LERDWR(ler0, LE_CSR2, ler1->ler1_rap);
    LERDWR(ler0, 0, ler1->ler1_rdp);
Here we see a macro being used to access rap and rdp --
/* access LANCE registers */
#define LERDWR(cntl, src, dst) \
    do { \
        (dst) = (src); \
    } while (((cntl)->ler0_status & LE_ACK) == 0);
This is interesting. We don't simply write to RAP or RDP. We write, then check an ACK bit in the status register in that lereg0 structure I mentioned above in passing. We loop, repeating the write until we see the ACK bit go high.

As far as writing that 12 word initialization block, it must be in "ler2_mode". This is sort of true. Take a look at if_lereg.h and you will see that this is simply the first word of the block, which is the first part of what they call "lereg2". What lereg2 is is a block of dual port RAM that they use for the init block, but it also contains the receive and transmit message descriptors

What about that base address business?

The above is a nice warm up. It would be interesting to see how Sun microsystems handled the lance in their hardware. I'll bet they don't exactly have the hardware represented by the lereg0 structure, but we shall see. Let's look at this a second time:
le->sc_r1 = (struct lereg1 *)(lestd[1] + (int)hd->hp_addr);
This is somehow generating the base address, casting it as a pointer, and storing it in sc_r1 for all future use by the driver.

The first part is easy, it comes from an array in the driver. These are just offsets from the base address.

/* offsets for:    ID,   REGS,    MEM,  NVRAM */
int lestd[] = { 0, 0x4000, 0x8000, 0xC008 };
So, the value of hp_addr is the base address we want. This comes from the hp_ctlr structure, defined in hp/dev/device.h

Martin tells me:

In my system it is 0x740000, corresponding to select code 20.
On many systems, the lab card is at select code 21 and then
the start address would be 0x750000.

This is the sort of thing that would be set up in the BSD config process. It is not even clear if those config files have survived for the hp300. I don't find them in the 4.4-Lite sources (or I don't know where to look).

Looking at some BSD sources for a sun workstation, I find a full config file at sys/sun3/conf/ARGON for a machine names "ARGON". We don't have that part of the filesystem in 4.4-Lite.

A peek at the Sun driver

This is in sys/sunif/if_lereg.h and sys/sunif/if_le.c
Indeed there is no lereg0, that is unique to the hp300, what I see is:
struct le_device {
    u_short le_rdp;     /* Register Data Port */
    u_short le_rap;     /* Register Address Port */
};
#define le_csr le_rdp

#define LE_CSR0     0
#define LE_CSR1     1
#define LE_CSR2     2
#define LE_CSR3     3
And to pass the init block to the lance, there is no weird dance with an ACK bit, we just do this:
   /* Give the init block to the chip */
    le->le_rap = LE_CSR1;   /* select the low address register */
    le->le_rdp = (long)ib & 0xffff;

    le->le_rap = LE_CSR2;   /* select the high address register */
    le->le_rdp = ((long)ib >> 16) & 0xff;
Very clean and simple.


Have any comments? Questions? Drop me a line!

Kyu / tom@mmto.org