freebsd-dev/sys/dev/ed/if_ed_hpp.c
Warner Losh 969eaf2179 Break out obscure ISA cards into their own files, as well as ne2000
and wd80x3 support.  Make the obscure ISA cards optional, and add
those options to NOTES on i386 (note: the ifdef around the whole code
is for module building).  Tweak pc98 ed support to include wd80x3 too.
Add goo for alpha too.

The affected cards are the 3Com 3C503, HP LAN+ and SIC (whatever that
is).  I couldn't find any of these for sale on ebay, so they are
untested.  If you have one of these cards, and send it to me, I'll
ensure that you have no future problems with it...

Minor cleanups as well by using functions rather than cut and paste
code for some probing operations (where the function call overhead is
lost in the noise).

Remove use of kvtop, since they aren't required anymore.  This driver
needs to get its memory mapped act together, however, and use bus
space.  It doesn't right now.

This reduces the size of if_ed.ko from about 51k to 33k on my laptop.
2005-02-09 20:03:40 +00:00

664 lines
16 KiB
C

/*-
* Copyright (c) 2005, M. Warner Losh
* All rights reserved.
* Copyright (c) 1995, David Greenman
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice unmodified, this list of conditions, and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "opt_ed.h"
#ifdef ED_HPP
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/bus.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <machine/resource.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_mib.h>
#include <net/if_media.h>
#include <net/bpf.h>
#include <dev/ed/if_edreg.h>
#include <dev/ed/if_edvar.h>
static void ed_hpp_writemem(struct ed_softc *, uint8_t *, uint16_t,
uint16_t);
/*
* Interrupt conversion table for the HP PC LAN+
*/
static uint16_t ed_hpp_intr_val[] = {
0, /* 0 */
0, /* 1 */
0, /* 2 */
3, /* 3 */
4, /* 4 */
5, /* 5 */
6, /* 6 */
7, /* 7 */
0, /* 8 */
9, /* 9 */
10, /* 10 */
11, /* 11 */
12, /* 12 */
0, /* 13 */
0, /* 14 */
15 /* 15 */
};
#define ED_HPP_TEST_SIZE 16
/*
* Probe and vendor specific initialization for the HP PC Lan+ Cards.
* (HP Part nos: 27247B and 27252A).
*
* The card has an asic wrapper around a DS8390 core. The asic handles
* host accesses and offers both standard register IO and memory mapped
* IO. Memory mapped I/O allows better performance at the expense of greater
* chance of an incompatibility with existing ISA cards.
*
* The card has a few caveats: it isn't tolerant of byte wide accesses, only
* short (16 bit) or word (32 bit) accesses are allowed. Some card revisions
* don't allow 32 bit accesses; these are indicated by a bit in the software
* ID register (see if_edreg.h).
*
* Other caveats are: we should read the MAC address only when the card
* is inactive.
*
* For more information; please consult the CRYNWR packet driver.
*
* The AUI port is turned on using the "link2" option on the ifconfig
* command line.
*/
int
ed_probe_HP_pclanp(device_t dev, int port_rid, int flags)
{
struct ed_softc *sc = device_get_softc(dev);
int error;
int n; /* temp var */
int memsize; /* mem on board */
u_char checksum; /* checksum of board address */
u_char irq; /* board configured IRQ */
uint8_t test_pattern[ED_HPP_TEST_SIZE]; /* read/write areas for */
uint8_t test_buffer[ED_HPP_TEST_SIZE]; /* probing card */
u_long conf_maddr, conf_msize, conf_irq, junk;
error = ed_alloc_port(dev, 0, ED_HPP_IO_PORTS);
if (error)
return (error);
/* Fill in basic information */
sc->asic_offset = ED_HPP_ASIC_OFFSET;
sc->nic_offset = ED_HPP_NIC_OFFSET;
sc->chip_type = ED_CHIP_TYPE_DP8390;
sc->isa16bit = 0; /* the 8390 core needs to be in byte mode */
/*
* Look for the HP PCLAN+ signature: "0x50,0x48,0x00,0x53"
*/
if ((ed_asic_inb(sc, ED_HPP_ID) != 0x50) ||
(ed_asic_inb(sc, ED_HPP_ID + 1) != 0x48) ||
((ed_asic_inb(sc, ED_HPP_ID + 2) & 0xF0) != 0) ||
(ed_asic_inb(sc, ED_HPP_ID + 3) != 0x53))
return ENXIO;
/*
* Read the MAC address and verify checksum on the address.
*/
ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_MAC);
for (n = 0, checksum = 0; n < ETHER_ADDR_LEN; n++)
checksum += (sc->arpcom.ac_enaddr[n] =
ed_asic_inb(sc, ED_HPP_MAC_ADDR + n));
checksum += ed_asic_inb(sc, ED_HPP_MAC_ADDR + ETHER_ADDR_LEN);
if (checksum != 0xFF)
return ENXIO;
/*
* Verify that the software model number is 0.
*/
ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_ID);
if (((sc->hpp_id = ed_asic_inw(sc, ED_HPP_PAGE_4)) &
ED_HPP_ID_SOFT_MODEL_MASK) != 0x0000)
return ENXIO;
/*
* Read in and save the current options configured on card.
*/
sc->hpp_options = ed_asic_inw(sc, ED_HPP_OPTION);
sc->hpp_options |= (ED_HPP_OPTION_NIC_RESET |
ED_HPP_OPTION_CHIP_RESET | ED_HPP_OPTION_ENABLE_IRQ);
/*
* Reset the chip. This requires writing to the option register
* so take care to preserve the other bits.
*/
ed_asic_outw(sc, ED_HPP_OPTION,
(sc->hpp_options & ~(ED_HPP_OPTION_NIC_RESET |
ED_HPP_OPTION_CHIP_RESET)));
DELAY(5000); /* wait for chip reset to complete */
ed_asic_outw(sc, ED_HPP_OPTION,
(sc->hpp_options | (ED_HPP_OPTION_NIC_RESET |
ED_HPP_OPTION_CHIP_RESET |
ED_HPP_OPTION_ENABLE_IRQ)));
DELAY(5000);
if (!(ed_nic_inb(sc, ED_P0_ISR) & ED_ISR_RST))
return ENXIO; /* reset did not complete */
/*
* Read out configuration information.
*/
ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW);
irq = ed_asic_inb(sc, ED_HPP_HW_IRQ);
/*
* Check for impossible IRQ.
*/
if (irq >= (sizeof(ed_hpp_intr_val) / sizeof(ed_hpp_intr_val[0])))
return ENXIO;
/*
* If the kernel IRQ was specified with a '?' use the cards idea
* of the IRQ. If the kernel IRQ was explicitly specified, it
* should match that of the hardware.
*/
error = bus_get_resource(dev, SYS_RES_IRQ, 0, &conf_irq, &junk);
if (error)
bus_set_resource(dev, SYS_RES_IRQ, 0, ed_hpp_intr_val[irq], 1);
else {
if (conf_irq != ed_hpp_intr_val[irq])
return (ENXIO);
}
/*
* Fill in softconfig info.
*/
sc->vendor = ED_VENDOR_HP;
sc->type = ED_TYPE_HP_PCLANPLUS;
sc->type_str = "HP-PCLAN+";
sc->mem_shared = 0; /* we DON'T have dual ported RAM */
sc->mem_start = 0; /* we use offsets inside the card RAM */
sc->hpp_mem_start = NULL;/* no memory mapped I/O by default */
/*
* The board has 32KB of memory. Is there a way to determine
* this programmatically?
*/
memsize = 32768;
/*
* Check if memory mapping of the I/O registers possible.
*/
if (sc->hpp_options & ED_HPP_OPTION_MEM_ENABLE) {
u_long mem_addr;
/*
* determine the memory address from the board.
*/
ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW);
mem_addr = (ed_asic_inw(sc, ED_HPP_HW_MEM_MAP) << 8);
/*
* Check that the kernel specified start of memory and
* hardware's idea of it match.
*/
error = bus_get_resource(dev, SYS_RES_MEMORY, 0,
&conf_maddr, &conf_msize);
if (error)
return (error);
if (mem_addr != conf_maddr)
return ENXIO;
error = ed_alloc_memory(dev, 0, memsize);
if (error)
return (error);
sc->hpp_mem_start = rman_get_virtual(sc->mem_res);
}
/*
* Fill in the rest of the soft config structure.
*/
/*
* The transmit page index.
*/
sc->tx_page_start = ED_HPP_TX_PAGE_OFFSET;
if (device_get_flags(dev) & ED_FLAGS_NO_MULTI_BUFFERING)
sc->txb_cnt = 1;
else
sc->txb_cnt = 2;
/*
* Memory description
*/
sc->mem_size = memsize;
sc->mem_ring = sc->mem_start +
(sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE);
sc->mem_end = sc->mem_start + sc->mem_size;
/*
* Receive area starts after the transmit area and
* continues till the end of memory.
*/
sc->rec_page_start = sc->tx_page_start +
(sc->txb_cnt * ED_TXBUF_SIZE);
sc->rec_page_stop = (sc->mem_size / ED_PAGE_SIZE);
sc->cr_proto = 0; /* value works */
/*
* Set the wrap registers for string I/O reads.
*/
ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW);
ed_asic_outw(sc, ED_HPP_HW_WRAP,
((sc->rec_page_start / ED_PAGE_SIZE) |
(((sc->rec_page_stop / ED_PAGE_SIZE) - 1) << 8)));
/*
* Reset the register page to normal operation.
*/
ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_PERF);
/*
* Verify that we can read/write from adapter memory.
* Create test pattern.
*/
for (n = 0; n < ED_HPP_TEST_SIZE; n++)
test_pattern[n] = (n*n) ^ ~n;
#undef ED_HPP_TEST_SIZE
/*
* Check that the memory is accessible thru the I/O ports.
* Write out the contents of "test_pattern", read back
* into "test_buffer" and compare the two for any
* mismatch.
*/
for (n = 0; n < (32768 / ED_PAGE_SIZE); n ++) {
ed_hpp_writemem(sc, test_pattern, (n * ED_PAGE_SIZE),
sizeof(test_pattern));
ed_hpp_readmem(sc, (n * ED_PAGE_SIZE),
test_buffer, sizeof(test_pattern));
if (bcmp(test_pattern, test_buffer,
sizeof(test_pattern)))
return ENXIO;
}
return (0);
}
/*
* HP PC Lan+ : Set the physical link to use AUI or TP/TL.
*/
void
ed_hpp_set_physical_link(struct ed_softc *sc)
{
struct ifnet *ifp = &sc->arpcom.ac_if;
int lan_page;
ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN);
lan_page = ed_asic_inw(sc, ED_HPP_PAGE_0);
if (ifp->if_flags & IFF_ALTPHYS) {
/*
* Use the AUI port.
*/
lan_page |= ED_HPP_LAN_AUI;
ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN);
ed_asic_outw(sc, ED_HPP_PAGE_0, lan_page);
} else {
/*
* Use the ThinLan interface
*/
lan_page &= ~ED_HPP_LAN_AUI;
ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN);
ed_asic_outw(sc, ED_HPP_PAGE_0, lan_page);
}
/*
* Wait for the lan card to re-initialize itself
*/
DELAY(150000); /* wait 150 ms */
/*
* Restore normal pages.
*/
ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_PERF);
}
/*
* Support routines to handle the HP PC Lan+ card.
*/
/*
* HP PC Lan+: Read from NIC memory, using either PIO or memory mapped
* IO.
*/
void
ed_hpp_readmem(struct ed_softc *sc, long src, uint8_t *dst, uint16_t amount)
{
int use_32bit_access = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS);
/* Program the source address in RAM */
ed_asic_outw(sc, ED_HPP_PAGE_2, src);
/*
* The HP PC Lan+ card supports word reads as well as
* a memory mapped i/o port that is aliased to every
* even address on the board.
*/
if (sc->hpp_mem_start) {
/* Enable memory mapped access. */
ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options &
~(ED_HPP_OPTION_MEM_DISABLE |
ED_HPP_OPTION_BOOT_ROM_ENB));
if (use_32bit_access && (amount > 3)) {
uint32_t *dl = (uint32_t *) dst;
volatile uint32_t *const sl =
(uint32_t *) sc->hpp_mem_start;
uint32_t *const fence = dl + (amount >> 2);
/* Copy out NIC data. We could probably write this
as a `movsl'. The currently generated code is lousy.
*/
while (dl < fence)
*dl++ = *sl;
dst += (amount & ~3);
amount &= 3;
}
/* Finish off any words left, as a series of short reads */
if (amount > 1) {
u_short *d = (u_short *) dst;
volatile u_short *const s =
(u_short *) sc->hpp_mem_start;
u_short *const fence = d + (amount >> 1);
/* Copy out NIC data. */
while (d < fence)
*d++ = *s;
dst += (amount & ~1);
amount &= 1;
}
/*
* read in a byte; however we need to always read 16 bits
* at a time or the hardware gets into a funny state
*/
if (amount == 1) {
/* need to read in a short and copy LSB */
volatile u_short *const s =
(volatile u_short *) sc->hpp_mem_start;
*dst = (*s) & 0xFF;
}
/* Restore Boot ROM access. */
ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options);
} else {
/* Read in data using the I/O port */
if (use_32bit_access && (amount > 3)) {
ed_asic_insl(sc, ED_HPP_PAGE_4, dst, amount >> 2);
dst += (amount & ~3);
amount &= 3;
}
if (amount > 1) {
ed_asic_insw(sc, ED_HPP_PAGE_4, dst, amount >> 1);
dst += (amount & ~1);
amount &= 1;
}
if (amount == 1) { /* read in a short and keep the LSB */
*dst = ed_asic_inw(sc, ED_HPP_PAGE_4) & 0xFF;
}
}
}
/*
* HP PC Lan+: Write to NIC memory, using either PIO or memory mapped
* IO.
* Only used in the probe routine to test the memory. 'len' must
* be even.
*/
static void
ed_hpp_writemem(struct ed_softc *sc, uint8_t *src, uint16_t dst, uint16_t len)
{
/* reset remote DMA complete flag */
ed_nic_outb(sc, ED_P0_ISR, ED_ISR_RDC);
/* program the write address in RAM */
ed_asic_outw(sc, ED_HPP_PAGE_0, dst);
if (sc->hpp_mem_start) {
u_short *s = (u_short *) src;
volatile u_short *d = (u_short *) sc->hpp_mem_start;
u_short *const fence = s + (len >> 1);
/*
* Enable memory mapped access.
*/
ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options &
~(ED_HPP_OPTION_MEM_DISABLE |
ED_HPP_OPTION_BOOT_ROM_ENB));
/*
* Copy to NIC memory.
*/
while (s < fence)
*d = *s++;
/*
* Restore Boot ROM access.
*/
ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options);
} else {
/* write data using I/O writes */
ed_asic_outsw(sc, ED_HPP_PAGE_4, src, len / 2);
}
}
/*
* Write to HP PC Lan+ NIC memory. Access to the NIC can be by using
* outsw() or via the memory mapped interface to the same register.
* Writes have to be in word units; byte accesses won't work and may cause
* the NIC to behave weirdly. Long word accesses are permitted if the ASIC
* allows it.
*/
u_short
ed_hpp_write_mbufs(struct ed_softc *sc, struct mbuf *m, int dst)
{
int len, wantbyte;
unsigned short total_len;
unsigned char savebyte[2];
volatile u_short * const d =
(volatile u_short *) sc->hpp_mem_start;
int use_32bit_accesses = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS);
/* select page 0 registers */
ed_nic_outb(sc, ED_P0_CR, sc->cr_proto | ED_CR_STA);
/* reset remote DMA complete flag */
ed_nic_outb(sc, ED_P0_ISR, ED_ISR_RDC);
/* program the write address in RAM */
ed_asic_outw(sc, ED_HPP_PAGE_0, dst);
if (sc->hpp_mem_start) /* enable memory mapped I/O */
ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options &
~(ED_HPP_OPTION_MEM_DISABLE |
ED_HPP_OPTION_BOOT_ROM_ENB));
wantbyte = 0;
total_len = 0;
if (sc->hpp_mem_start) { /* Memory mapped I/O port */
while (m) {
total_len += (len = m->m_len);
if (len) {
caddr_t data = mtod(m, caddr_t);
/* finish the last word of the previous mbuf */
if (wantbyte) {
savebyte[1] = *data;
*d = *((u_short *) savebyte);
data++; len--; wantbyte = 0;
}
/* output contiguous words */
if ((len > 3) && (use_32bit_accesses)) {
volatile uint32_t *const dl =
(volatile uint32_t *) d;
uint32_t *sl = (uint32_t *) data;
uint32_t *fence = sl + (len >> 2);
while (sl < fence)
*dl = *sl++;
data += (len & ~3);
len &= 3;
}
/* finish off remain 16 bit writes */
if (len > 1) {
u_short *s = (u_short *) data;
u_short *fence = s + (len >> 1);
while (s < fence)
*d = *s++;
data += (len & ~1);
len &= 1;
}
/* save last byte if needed */
if ((wantbyte = (len == 1)) != 0)
savebyte[0] = *data;
}
m = m->m_next; /* to next mbuf */
}
if (wantbyte) /* write last byte */
*d = *((u_short *) savebyte);
} else {
/* use programmed I/O */
while (m) {
total_len += (len = m->m_len);
if (len) {
caddr_t data = mtod(m, caddr_t);
/* finish the last word of the previous mbuf */
if (wantbyte) {
savebyte[1] = *data;
ed_asic_outw(sc, ED_HPP_PAGE_4,
*((u_short *)savebyte));
data++;
len--;
wantbyte = 0;
}
/* output contiguous words */
if ((len > 3) && use_32bit_accesses) {
ed_asic_outsl(sc, ED_HPP_PAGE_4,
data, len >> 2);
data += (len & ~3);
len &= 3;
}
/* finish off remaining 16 bit accesses */
if (len > 1) {
ed_asic_outsw(sc, ED_HPP_PAGE_4,
data, len >> 1);
data += (len & ~1);
len &= 1;
}
if ((wantbyte = (len == 1)) != 0)
savebyte[0] = *data;
} /* if len != 0 */
m = m->m_next;
}
if (wantbyte) /* spit last byte */
ed_asic_outw(sc, ED_HPP_PAGE_4, *(u_short *)savebyte);
}
if (sc->hpp_mem_start) /* turn off memory mapped i/o */
ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options);
return (total_len);
}
#endif /* ED_HPP */