|
|
GNU Mach 1.0
/*
Written 1994 by Donald Becker.
This driver is for the Hewlett Packard PC LAN (27***) plus ethercards.
These cards are sold under several model numbers, usually 2724*.
This software may be used and distributed according to the terms
of the GNU Public License, incorporated herein by reference.
The author may be reached as [email protected], or C/O
Center of Excellence in Space Data and Information Sciences
Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771
As is often the case, a great deal of credit is owed to Russ Nelson.
The Crynwr packet driver was my primary source of HP-specific
programming information.
*/
/*
* Ported to mach by Stephen Clawson, [email protected]
* University of Utah CSL.
*
* Derived from the Linux driver by Donald Becker.
*
* Also uses code Shantanu Goel adapted from Donald Becker
* for ns8930 support.
*
*/
#include <hpp.h>
#if NHPP > 0
#include <sys/types.h>
#include "vm_param.h"
#include <kern/time_out.h>
#include <device/device_types.h>
#include <device/errno.h>
#include <device/io_req.h>
#include <device/if_hdr.h>
#include <device/if_ether.h>
#include <device/net_status.h>
#include <device/net_io.h>
#include <chips/busses.h>
#include <i386/ipl.h>
#include <i386/pio.h>
#include <i386at/gpl/if_nsreg.h>
/*
* XXX - This is some gross glue garbage. The io instructions really
* should be integrated into pio.h...
*/
#define IO_DELAY __asm__ __volatile__("outb %al,$0x80")
#define outb_p(p, v) { outb(p, v); IO_DELAY; }
#define inb_p(p) ({ unsigned char _v; _v = inb(p); IO_DELAY; _v; })
static __inline void
insw(u_short port, void *addr, int cnt)
{
__asm __volatile("cld\n\trepne\n\tinsw" :
: "d" (port), "D" (addr), "c" (cnt) : "%edi", "%ecx");
}
static __inline void
outsw(u_short port, void *addr, int cnt)
{
__asm __volatile("cld\n\trepne\n\toutsw" :
: "d" (port), "S" (addr), "c" (cnt) : "%esi", "%ecx");
}
/*
The HP EtherTwist chip implementation is a fairly routine DP8390
implementation. It allows both shared memory and programmed-I/O buffer
access, using a custom interface for both. The programmed-I/O mode is
entirely implemented in the HP EtherTwist chip, bypassing the problem
ridden built-in 8390 facilities used on NE2000 designs. The shared
memory mode is likewise special, with an offset register used to make
packets appear at the shared memory base. Both modes use a base and bounds
page register to hide the Rx ring buffer wrap -- a packet that spans the
end of physical buffer memory appears continuous to the driver. (c.f. the
3c503 and Cabletron E2100)
A special note: the internal buffer of the board is only 8 bits wide.
This lays several nasty traps for the unaware:
- the 8390 must be programmed for byte-wide operations
- all I/O and memory operations must work on whole words (the access
latches are serially preloaded and have no byte-swapping ability).
This board is laid out in I/O space much like the earlier HP boards:
the first 16 locations are for the board registers, and the second 16 are
for the 8390. The board is easy to identify, with both a dedicated 16 bit
ID register and a constant 0x530* value in the upper bits of the paging
register.
*/
#define HP_ID 0x00 /* ID register, always 0x4850. */
#define HP_PAGING 0x02 /* Registers visible @ 8-f, see PageName. */
#define HPP_OPTION 0x04 /* Bitmapped options, see HP_Option.*/
#define HPP_OUT_ADDR 0x08 /* I/O output location in Perf_Page.*/
#define HPP_IN_ADDR 0x0A /* I/O input location in Perf_Page.*/
#define HP_DATAPORT 0x0c /* I/O data transfer in Perf_Page.*/
#define HPP_NIC_OFFSET 0x10 /* Offset to the 8390 registers.*/
#define HP_IO_EXTENT 32
#define HP_START_PG 0x00 /* First page of TX buffer */
#define HP_STOP_PG 0x80 /* Last page +1 of RX ring */
/*#define HP_STOP_PG 0x1f
/* The register set selected in HP_PAGING. */
enum PageName {
Perf_Page = 0, /* Normal operation. */
MAC_Page = 1, /* The ethernet address (+checksum). */
HW_Page = 2, /* EEPROM-loaded hw parameters. */
LAN_Page = 4, /* Transciever type, testing, etc. */
ID_Page = 6 };
/* The bit definitions for the HPP_OPTION register. */
enum HP_Option {
NICReset = 1, /* Active low, really UNreset. */
ChipReset = 2,
EnableIRQ = 4,
FakeIntr = 8,
BootROMEnb = 0x10,
IOEnb = 0x20,
MemEnable = 0x40,
ZeroWait = 0x80,
MemDisable = 0x1000, };
void hpp_reset_8390(struct nssoftc *ns);
void hpp_mem_block_input(struct nssoftc *ns, int, char *, int);
int hpp_mem_block_output(struct nssoftc *ns, int, char *, int);
void hpp_io_block_input(struct nssoftc *ns, int, char *, int);
int hpp_io_block_output(struct nssoftc *ns, int,char *, int);
/*
* Watchdog timer.
*/
int hppwstart = 0;
void hppwatch(void);
/*
* Autoconfig structures.
*/
int hpp_std[] = { 0x200, 0x240, 0x280, 0x2C0, 0x300, 0x320, 0x340, 0 };
struct bus_device *hpp_info[NHPP];
int hpp_probe();
void hpp_attach();
struct bus_driver hppdriver = {
hpp_probe, 0, hpp_attach, 0, hpp_std, "hpp", hpp_info, 0, 0, 0
};
/*
* ns8390 state.
*/
struct nssoftc hppnssoftc[NHPP];
/*
* hpp state.
*/
struct hppsoftc {
unsigned long rmem_start; /* shmem "recv" start */
unsigned long rmem_end; /* shmem "recv" end */
unsigned long mem_start; /* shared mem start */
unsigned long mem_end; /* shared mem end */
} hppsoftc[NHPP];
/*
* Probe a list of addresses for the card.
*
*/
int hpp_probe(port, dev)
int port;
struct bus_device *dev;
{
int unit = dev->unit;
char *str = "hp-plus ethernet board %d out of range.\n";
caddr_t base = (caddr_t) (dev ? dev->address : 0);
int i;
if ((unit < 0) || (unit >= NHPP)) {
printf(str, unit);
return(0);
}
/* Check a single specified location. */
if (base > (caddr_t) 0x1ff)
return hpp_probe1(dev, base);
else if (base != 0) /* Don't probe at all. */
return 0;
for (i = 0; hpp_std[i]; i++) {
int ioaddr = hpp_std[i];
if ( ioaddr > 0 && hpp_probe1(dev, ioaddr) ) {
dev->address = ioaddr;
hpp_std[i] = -1; /* Mark address used */
return(1);
}
}
return 0;
}
/*
* Do the interesting part of the probe at a single address.
*
*/
int hpp_probe1(dev, ioaddr)
struct bus_device *dev;
int ioaddr;
{
int i;
u_char checksum = 0;
int mem_start;
struct hppsoftc *hpp = &hppsoftc[dev->unit];
struct nssoftc *ns = &hppnssoftc[dev->unit];
struct ifnet *ifp = &ns->sc_if;
/* Check for the HP+ signature, 50 48 0x 53. */
if (inw(ioaddr + HP_ID) != 0x4850
|| (inw(ioaddr + HP_PAGING) & 0xfff0) != 0x5300)
return 0;
printf("%s%d: HP PClan plus at %#3x,", dev->name, dev->unit, ioaddr);
/* Retrieve and checksum the station address. */
outw(ioaddr + HP_PAGING, MAC_Page);
printf("MAC_Page = %d, ioaddr = %x\n", MAC_Page, ioaddr);
for(i = 0; i < ETHER_ADDR_LEN; i++) {
u_char inval = inb(ioaddr + 8 + i);
ns->sc_addr[i] = inval;
checksum += inval;
printf(" %2.2x", inval);
}
checksum += inb(ioaddr + 14);
if (checksum != 0xff) {
printf(" bad checksum %2.2x.\n", checksum);
return 0;
} else {
/* Point at the Software Configuration Flags. */
outw(ioaddr + HP_PAGING, ID_Page);
printf(" ID %4.4x", inw(ioaddr + 12));
}
/* Read the IRQ line. */
outw(ioaddr + HP_PAGING, HW_Page);
{
int irq = inb(ioaddr + 13) & 0x0f;
int option = inw(ioaddr + HPP_OPTION);
dev->sysdep1 = irq;
take_dev_irq(dev);
if (option & MemEnable) {
mem_start = inw(ioaddr + 9) << 8;
printf(", IRQ %d, memory address %#x.\n", irq, mem_start);
} else {
mem_start = 0;
printf(", IRQ %d, programmed-I/O mode.\n", irq);
}
}
/* Set the wrap registers for string I/O reads. */
outw( ioaddr + 14, (HP_START_PG + TX_2X_PAGES) | ((HP_STOP_PG - 1) << 8));
/* Set the base address to point to the NIC, not the "real" base! */
ns->sc_port = ioaddr + HPP_NIC_OFFSET;
ns->sc_name = dev->name;
ns->sc_unit = dev->unit;
ns->sc_pingpong = 0; /* turn off pingpong mode */
ns->sc_word16 = 0; /* Agggghhhhh! Debug time: 2 days! */
ns->sc_txstrtpg = HP_START_PG;
ns->sc_rxstrtpg = HP_START_PG + TX_2X_PAGES;
ns->sc_stoppg = HP_STOP_PG;
ns->sc_reset = hpp_reset_8390;
ns->sc_input = hpp_io_block_input;
ns->sc_output = hpp_io_block_output;
/* Check if the memory_enable flag is set in the option register. */
if (mem_start) {
ns->sc_input = hpp_mem_block_input;
ns->sc_output = hpp_mem_block_output;
hpp->mem_start = mem_start;
hpp->rmem_start = hpp->mem_start + TX_2X_PAGES * 256;
hpp->mem_end = hpp->rmem_end
= hpp->mem_start + (HP_STOP_PG - HP_START_PG) * 256;
}
outw(ioaddr + HP_PAGING, Perf_Page);
/* Leave the 8390 and HP chip reset. */
outw( ioaddr + HPP_OPTION, inw(ioaddr + HPP_OPTION) & ~EnableIRQ );
/*
* Initialize interface header.
*/
ifp->if_unit = dev->unit;
ifp->if_mtu = ETHERMTU;
ifp->if_flags = IFF_BROADCAST;
ifp->if_header_size = sizeof(struct ether_header);
ifp->if_header_format = HDR_ETHERNET;
ifp->if_address_size = ETHER_ADDR_LEN;
ifp->if_address = ns->sc_addr;
if_init_queues(ifp);
return (1);
}
/*
* XXX
*
* this routine really should do the invasive part of the setup.
*/
void
hpp_attach(dev)
struct bus_device *dev;
{
/* NULL */
}
int
hppopen(dev, flag)
dev_t dev;
int flag;
{
int s, unit = minor(dev);
struct bus_device *bd;
struct hppsoftc *hpp;
struct nssoftc *ns = &hppnssoftc[unit];
int ioaddr = ns->sc_port - HPP_NIC_OFFSET;
int option_reg;
if (unit < 0 || unit >= NHPP ||
(bd = hpp_info[unit]) == 0 || !(bd->alive))
return ENXIO;
/*
* Start watchdog.
*/
if (!hppwstart) {
hppwstart++;
timeout(hppwatch, 0, hz);
}
hpp = &hppsoftc[unit];
ns->sc_if.if_flags |= IFF_UP;
s = splimp();
/* Reset the 8390 and HP chip. */
option_reg = inw(ioaddr + HPP_OPTION);
outw( ioaddr + HPP_OPTION, option_reg & ~(NICReset + ChipReset) );
IO_DELAY; IO_DELAY;
/* Unreset the board and enable interrupts. */
outw( ioaddr + HPP_OPTION, option_reg | (EnableIRQ + NICReset + ChipReset));
/* Set the wrap registers for programmed-I/O operation. */
outw( ioaddr + HP_PAGING, HW_Page );
outw( ioaddr + 14, (HP_START_PG + TX_2X_PAGES) | ((HP_STOP_PG - 1) << 8) );
/* Select the operational page. */
outw( ioaddr + HP_PAGING, Perf_Page );
nsinit(ns);
splx(s);
return (0);
}
/*
* needs to be called at splimp()?
*
*/
void
hpp_reset_8390(ns)
struct nssoftc *ns;
{
int ioaddr = ns->sc_port - HPP_NIC_OFFSET;
int option_reg = inw(ioaddr + HPP_OPTION);
outw( ioaddr + HPP_OPTION, option_reg & ~(NICReset + ChipReset) );
/* Pause a few cycles for the hardware reset to take place. */
IO_DELAY;
IO_DELAY;
ns->sc_txing = 0;
outw( ioaddr + HPP_OPTION, option_reg | (EnableIRQ + NICReset + ChipReset) );
/*
* XXX - I'm not sure there needs to be this many IO_DELAY's...
*/
IO_DELAY; IO_DELAY;
IO_DELAY; IO_DELAY;
if ((inb_p(ioaddr + HPP_NIC_OFFSET + EN0_ISR) & ENISR_RESET) == 0)
printf("%s: hp_reset_8390() did not complete.\n", ns->sc_name);
return;
}
/*
* Block input and output, similar to the Crynwr packet driver.
* Note that transfer with the EtherTwist+ must be on word boundaries.
*/
void
hpp_io_block_input(ns, count, buf, ring_offset)
struct nssoftc *ns;
int count;
char *buf;
int ring_offset;
{
int ioaddr = ns->sc_port - HPP_NIC_OFFSET;
outw(ioaddr + HPP_IN_ADDR, ring_offset);
insw(ioaddr + HP_DATAPORT, buf, count >> 1 );
if (count & 0x01)
buf[count-1] = (char) inw(ioaddr + HP_DATAPORT);
}
void
hpp_mem_block_input(ns, count, buf, ring_offset)
struct nssoftc *ns;
int count;
char *buf;
int ring_offset;
{
int ioaddr = ns->sc_port - HPP_NIC_OFFSET;
int option_reg = inw(ioaddr + HPP_OPTION);
char *mem_start = (char *)phystokv(hppsoftc[ns->sc_unit].mem_start);
outw(ioaddr + HPP_IN_ADDR, ring_offset);
outw(ioaddr + HPP_OPTION, option_reg & ~(MemDisable + BootROMEnb));
/* copy as much as we can straight through */
bcopy16(mem_start, buf, count & ~1);
/* Now we copy that last byte. */
if (count & 0x01) {
u_short savebyte[2];
bcopy16(mem_start + (count & ~1), savebyte, 2);
buf[count-1] = savebyte[0];
}
outw(ioaddr + HPP_OPTION, option_reg);
}
/*
* output data into NIC buffers.
*
* NOTE: All transfers must be on word boundaries.
*/
int
hpp_io_block_output(ns, count, buf, start_page)
struct nssoftc *ns;
int count;
char *buf;
int start_page;
{
int ioaddr = ns->sc_port - HPP_NIC_OFFSET;
outw(ioaddr + HPP_OUT_ADDR, start_page << 8) ;
if (count > 1) {
outsw(ioaddr + HP_DATAPORT, buf, count >> 1);
}
if ( (count & 1) == 1 ) {
u_char savebyte[2];
savebyte[1] = 0;
savebyte[0] = buf[count - 1];
outw(ioaddr + HP_DATAPORT, *(u_short *)savebyte);
}
if (count < (ETHERMIN + sizeof( struct ether_header )))
count = ETHERMIN + sizeof( struct ether_header );
return (count) ;
}
/* XXX
*
* I take great pains to not try and bcopy past the end of the buffer,
* does this matter? Are the io request buffers the exact byte size?
*/
int
hpp_mem_block_output(ns, count, buf, start_page )
struct nssoftc *ns;
int count;
char *buf;
int start_page;
{
int ioaddr = ns->sc_port - HPP_NIC_OFFSET;
int option_reg = inw(ioaddr + HPP_OPTION);
struct hppsoftc *hpp = &hppsoftc[ns->sc_unit];
char *shmem;
outw(ioaddr + HPP_OUT_ADDR, start_page << 8);
outw(ioaddr + HPP_OPTION, option_reg & ~(MemDisable + BootROMEnb));
shmem = (char *)phystokv(hpp->mem_start);
bcopy16(buf, shmem, count & ~1);
if ( (count & 1) == 1 ) {
u_char savebyte[2];
savebyte[1] = 0;
savebyte[0] = buf[count - 1];
bcopy16(savebyte, shmem + (count & ~1), 2);
}
while (count < ETHERMIN + sizeof(struct ether_header)) {
*(shmem + count) = 0;
count++;
}
outw(ioaddr + HPP_OPTION, option_reg);
return count;
}
int
hppintr(unit)
int unit;
{
nsintr(&hppnssoftc[unit]);
return(0);
}
void
hppstart(unit)
int unit;
{
nsstart(&hppnssoftc[unit]);
}
int hppoutput();
int
hppoutput(dev, ior)
dev_t dev;
io_req_t ior;
{
int unit = minor(dev);
struct bus_device *ui;
if (unit >= NHPP || (ui = hpp_info[unit]) == 0 || ui->alive == 0)
return (ENXIO);
return (net_write(&hppnssoftc[unit].sc_if, hppstart, ior));
}
int
hppsetinput(dev, receive_port, priority, filter, filter_count)
dev_t dev;
mach_port_t receive_port;
int priority;
filter_t *filter;
unsigned filter_count;
{
int unit = minor(dev);
struct bus_device *ui;
if (unit >= NHPP || (ui = hpp_info[unit]) == 0 || ui->alive == 0)
return (ENXIO);
return (net_set_filter(&hppnssoftc[unit].sc_if, receive_port,
priority, filter, filter_count));
}
int
hppgetstat(dev, flavor, status, count)
dev_t dev;
int flavor;
dev_status_t status;
unsigned *count;
{
int unit = minor(dev);
struct bus_device *ui;
if (unit >= NHPP || (ui = hpp_info[unit]) == 0 || ui->alive == 0)
return (ENXIO);
return (net_getstat(&hppnssoftc[unit].sc_if, flavor, status, count));
}
int
hppsetstat(dev, flavor, status, count)
dev_t dev;
int flavor;
dev_status_t status;
unsigned count;
{
int unit = minor(dev), oflags, s;
struct bus_device *ui;
struct ifnet *ifp;
struct net_status *ns;
if (unit >= NHPP || (ui = hpp_info[unit]) == 0 || ui->alive == 0)
return (ENXIO);
ifp = &hppnssoftc[unit].sc_if;
switch (flavor) {
case NET_STATUS:
if (count < NET_STATUS_COUNT)
return (D_INVALID_SIZE);
ns = (struct net_status *)status;
oflags = ifp->if_flags & (IFF_ALLMULTI|IFF_PROMISC);
ifp->if_flags &= ~(IFF_ALLMULTI|IFF_PROMISC);
ifp->if_flags |= ns->flags & (IFF_ALLMULTI|IFF_PROMISC);
if ((ifp->if_flags & (IFF_ALLMULTI|IFF_PROMISC)) != oflags) {
s = splimp();
nsinit(&hppnssoftc[unit]);
splx(s);
}
break;
default:
return (D_INVALID_OPERATION);
}
return (D_SUCCESS);
}
/*
* Watchdog.
* Check for hung transmissions.
*/
void
hppwatch()
{
int unit, s;
struct nssoftc *ns;
timeout(hppwatch, 0, hz);
s = splimp();
for (unit = 0; unit < NHPP; unit++) {
if (hpp_info[unit] == 0 || hpp_info[unit]->alive == 0)
continue;
ns = &hppnssoftc[unit];
if (ns->sc_timer && --ns->sc_timer == 0) {
printf("hpp%d: transmission timeout\n", unit);
(*ns->sc_reset)(ns);
nsinit(ns);
}
}
splx(s);
}
#endif /* NHPP > 0 */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.