2417 lines
57 KiB
C
2417 lines
57 KiB
C
/*
|
|
* fxp.c
|
|
*
|
|
* This file contains an ethernet device driver for Intel 82557, 82558,
|
|
* 82559, 82550, and 82562 fast ethernet controllers.
|
|
*
|
|
* Created: Nov 2004 by Philip Homburg <philip@f-mnx.phicoh.com>
|
|
*/
|
|
|
|
#include <minix/drivers.h>
|
|
#include <minix/netdriver.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <net/hton.h>
|
|
#include <net/gen/ether.h>
|
|
#include <net/gen/eth_io.h>
|
|
#include <machine/pci.h>
|
|
#include <minix/ds.h>
|
|
#include <minix/endpoint.h>
|
|
|
|
#include <timers.h>
|
|
|
|
#define tmra_ut timer_t
|
|
#define tmra_inittimer(tp) tmr_inittimer(tp)
|
|
#define debug 0
|
|
#define RAND_UPDATE /**/
|
|
#define printW() ((void)0)
|
|
|
|
#include "assert.h"
|
|
#include "fxp.h"
|
|
#include "mii.h"
|
|
|
|
/* Number of receive buffers */
|
|
#define N_RX_BUF 40
|
|
|
|
/* Number of transmit buffers */
|
|
#define N_TX_BUF 4
|
|
|
|
/* I/O vectors are handled IOVEC_NR entries at a time. */
|
|
#define IOVEC_NR 16
|
|
|
|
/* Configuration */
|
|
#define FXP_ENVVAR "FXPETH"
|
|
|
|
struct pcitab
|
|
{
|
|
u16_t vid;
|
|
u16_t did;
|
|
int checkclass;
|
|
};
|
|
|
|
PRIVATE struct pcitab pcitab_fxp[]=
|
|
{
|
|
{ 0x8086, 0x1229, 0 }, /* Intel 82557, etc. */
|
|
{ 0x8086, 0x2449, 0 }, /* Intel 82801BA/BAM/CA/CAM */
|
|
{ 0x8086, 0x103d, 0 }, /* Intel 82801DB */
|
|
{ 0x8086, 0x1064, 0 }, /* Intel 82562 */
|
|
|
|
{ 0x0000, 0x0000, 0 }
|
|
};
|
|
|
|
typedef int irq_hook_t;
|
|
|
|
static timer_t *fxp_timers= NULL;
|
|
static clock_t fxp_next_timeout= 0;
|
|
|
|
/* ignore interrupt for the moment */
|
|
#define interrupt(x) 0
|
|
|
|
PRIVATE union tmpbuf
|
|
{
|
|
char pad[4096];
|
|
struct cbl_conf cc;
|
|
struct ias ias;
|
|
} *tmpbufp;
|
|
|
|
typedef struct fxp
|
|
{
|
|
port_t fxp_base_port;
|
|
int fxp_mode;
|
|
int fxp_got_int;
|
|
int fxp_send_int;
|
|
int fxp_flags;
|
|
int fxp_client;
|
|
int fxp_features; /* Needed? */
|
|
int fxp_irq;
|
|
int fxp_type; /* What kind of hardware */
|
|
int fxp_ee_addrlen; /* #EEPROM address bits */
|
|
int fxp_tx_alive;
|
|
int fxp_need_reset;
|
|
|
|
/* Rx */
|
|
vir_bytes fxp_read_s;
|
|
int fxp_rx_nbuf;
|
|
int fxp_rx_bufsize;
|
|
struct rfd *fxp_rx_buf;
|
|
phys_bytes fxp_rx_busaddr;
|
|
int fxp_rx_head;
|
|
int fxp_rx_need_restart;
|
|
int fxp_need_conf; /* Re-configure after draining send
|
|
* queue
|
|
*/
|
|
|
|
/* Tx */
|
|
int fxp_tx_nbuf;
|
|
int fxp_tx_bufsize;
|
|
struct tx *fxp_tx_buf;
|
|
phys_bytes fxp_tx_busaddr;
|
|
int fxp_tx_idle;
|
|
int fxp_tx_head;
|
|
int fxp_tx_tail;
|
|
int fxp_tx_threshold;
|
|
|
|
/* Link status */
|
|
int fxp_report_link;
|
|
int fxp_link_up;
|
|
int fxp_mii_busy;
|
|
u16_t fxp_mii_scr;
|
|
|
|
/* PCI related */
|
|
int fxp_seen; /* TRUE iff device available */
|
|
u8_t fxp_pcibus;
|
|
u8_t fxp_pcidev;
|
|
u8_t fxp_pcifunc;
|
|
|
|
/* 'large' items */
|
|
irq_hook_t fxp_hook;
|
|
ether_addr_t fxp_address;
|
|
message fxp_rx_mess;
|
|
message fxp_tx_mess;
|
|
struct sc fxp_stat;
|
|
u8_t fxp_conf_bytes[CC_BYTES_NR];
|
|
char fxp_name[sizeof("fxp#n")];
|
|
iovec_t fxp_iovec[IOVEC_NR];
|
|
iovec_s_t fxp_iovec_s[IOVEC_NR];
|
|
}
|
|
fxp_t;
|
|
|
|
/* fxp_mode */
|
|
#define FM_DISABLED 0x0
|
|
#define FM_ENABLED 0x1
|
|
|
|
/* fxp_flags */
|
|
#define FF_EMPTY 0x000
|
|
#define FF_PACK_SENT 0x001
|
|
#define FF_PACK_RECV 0x002
|
|
#define FF_SEND_AVAIL 0x004
|
|
#define FF_READING 0x010
|
|
#define FF_PROMISC 0x040
|
|
#define FF_MULTI 0x080
|
|
#define FF_BROAD 0x100
|
|
#define FF_ENABLED 0x200
|
|
|
|
/* fxp_features */
|
|
#define FFE_NONE 0x0
|
|
|
|
/* fxp_type */
|
|
#define FT_UNKNOWN 0x0
|
|
#define FT_82557 0x1
|
|
#define FT_82558A 0x2
|
|
#define FT_82559 0x4
|
|
#define FT_82801 0x8
|
|
|
|
PRIVATE int fxp_instance;
|
|
|
|
PRIVATE fxp_t *fxp_state;
|
|
|
|
PRIVATE tmra_ut fxp_watchdog;
|
|
|
|
PRIVATE u32_t system_hz;
|
|
|
|
#define fxp_inb(port, offset) (do_inb((port) + (offset)))
|
|
#define fxp_inl(port, offset) (do_inl((port) + (offset)))
|
|
#define fxp_outb(port, offset, value) (do_outb((port) + (offset), (value)))
|
|
#define fxp_outl(port, offset, value) (do_outl((port) + (offset), (value)))
|
|
|
|
_PROTOTYPE( static void fxp_init, (message *mp) );
|
|
_PROTOTYPE( static void fxp_pci_conf, (void) );
|
|
_PROTOTYPE( static int fxp_probe, (fxp_t *fp, int skip) );
|
|
_PROTOTYPE( static void fxp_conf_hw, (fxp_t *fp) );
|
|
_PROTOTYPE( static void fxp_init_hw, (fxp_t *fp) );
|
|
_PROTOTYPE( static void fxp_init_buf, (fxp_t *fp) );
|
|
_PROTOTYPE( static void fxp_reset_hw, (fxp_t *fp) );
|
|
_PROTOTYPE( static void fxp_confaddr, (fxp_t *fp) );
|
|
_PROTOTYPE( static void fxp_rec_mode, (fxp_t *fp) );
|
|
_PROTOTYPE( static void fxp_writev_s, (const message *mp, int from_int) );
|
|
_PROTOTYPE( static void fxp_readv_s, (message *mp, int from_int) );
|
|
_PROTOTYPE( static void fxp_do_conf, (fxp_t *fp) );
|
|
_PROTOTYPE( static void fxp_cu_ptr_cmd, (fxp_t *fp, int cmd,
|
|
phys_bytes bus_addr, int check_idle) );
|
|
_PROTOTYPE( static void fxp_ru_ptr_cmd, (fxp_t *fp, int cmd,
|
|
phys_bytes bus_addr, int check_idle) );
|
|
_PROTOTYPE( static void fxp_restart_ru, (fxp_t *fp) );
|
|
_PROTOTYPE( static void fxp_getstat_s, (message *mp) );
|
|
_PROTOTYPE( static void fxp_handler, (fxp_t *fp) );
|
|
_PROTOTYPE( static void fxp_check_ints, (fxp_t *fp) );
|
|
_PROTOTYPE( static void fxp_watchdog_f, (timer_t *tp) );
|
|
_PROTOTYPE( static int fxp_link_changed, (fxp_t *fp) );
|
|
_PROTOTYPE( static void fxp_report_link, (fxp_t *fp) );
|
|
_PROTOTYPE( static void reply, (fxp_t *fp) );
|
|
_PROTOTYPE( static void mess_reply, (message *req, message *reply) );
|
|
_PROTOTYPE( static u16_t eeprom_read, (fxp_t *fp, int reg) );
|
|
_PROTOTYPE( static void eeprom_addrsize, (fxp_t *fp) );
|
|
_PROTOTYPE( static u16_t mii_read, (fxp_t *fp, int reg) );
|
|
_PROTOTYPE( static void fxp_set_timer,(timer_t *tp, clock_t delta,
|
|
tmr_func_t watchdog) );
|
|
_PROTOTYPE( static void fxp_expire_timers,(void) );
|
|
_PROTOTYPE( static u8_t do_inb, (port_t port) );
|
|
_PROTOTYPE( static u32_t do_inl, (port_t port) );
|
|
_PROTOTYPE( static void do_outb, (port_t port, u8_t v) );
|
|
_PROTOTYPE( static void do_outl, (port_t port, u32_t v) );
|
|
_PROTOTYPE( static void tell_dev, (vir_bytes start, size_t size,
|
|
int pci_bus, int pci_dev, int pci_func) );
|
|
|
|
PRIVATE void handle_hw_intr(void)
|
|
{
|
|
int r;
|
|
fxp_t *fp;
|
|
|
|
fp= fxp_state;
|
|
|
|
if (fp->fxp_mode != FM_ENABLED)
|
|
return;
|
|
fxp_handler(fp);
|
|
|
|
r= sys_irqenable(&fp->fxp_hook);
|
|
if (r != OK) {
|
|
panic("unable enable interrupts: %d", r);
|
|
}
|
|
|
|
if (!fp->fxp_got_int)
|
|
return;
|
|
fp->fxp_got_int= 0;
|
|
assert(fp->fxp_flags & FF_ENABLED);
|
|
fxp_check_ints(fp);
|
|
}
|
|
|
|
/* SEF functions and variables. */
|
|
FORWARD _PROTOTYPE( void sef_local_startup, (void) );
|
|
FORWARD _PROTOTYPE( int sef_cb_init_fresh, (int type, sef_init_info_t *info) );
|
|
FORWARD _PROTOTYPE( void sef_cb_signal_handler, (int signo) );
|
|
EXTERN int env_argc;
|
|
EXTERN char **env_argv;
|
|
|
|
/*===========================================================================*
|
|
* main *
|
|
*===========================================================================*/
|
|
int main(int argc, char *argv[])
|
|
{
|
|
message m;
|
|
int ipc_status;
|
|
int r;
|
|
|
|
/* SEF local startup. */
|
|
env_setargs(argc, argv);
|
|
sef_local_startup();
|
|
|
|
while (TRUE)
|
|
{
|
|
if ((r= netdriver_receive(ANY, &m, &ipc_status)) != OK)
|
|
panic("netdriver_receive failed: %d", r);
|
|
|
|
if (is_ipc_notify(ipc_status)) {
|
|
switch (_ENDPOINT_P(m.m_source)) {
|
|
case HARDWARE:
|
|
handle_hw_intr();
|
|
break;
|
|
case CLOCK:
|
|
fxp_expire_timers();
|
|
break;
|
|
default:
|
|
panic(" illegal notify from: %d", m.m_source);
|
|
}
|
|
|
|
/* get new message */
|
|
continue;
|
|
}
|
|
|
|
switch (m.m_type)
|
|
{
|
|
case DL_WRITEV_S: fxp_writev_s(&m, FALSE); break;
|
|
case DL_READV_S: fxp_readv_s(&m, FALSE); break;
|
|
case DL_CONF: fxp_init(&m); break;
|
|
case DL_GETSTAT_S: fxp_getstat_s(&m); break;
|
|
default:
|
|
panic(" illegal message: %d", m.m_type);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* sef_local_startup *
|
|
*===========================================================================*/
|
|
PRIVATE void sef_local_startup()
|
|
{
|
|
/* Register init callbacks. */
|
|
sef_setcb_init_fresh(sef_cb_init_fresh);
|
|
sef_setcb_init_lu(sef_cb_init_fresh);
|
|
sef_setcb_init_restart(sef_cb_init_fresh);
|
|
|
|
/* Register live update callbacks. */
|
|
sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready);
|
|
sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_workfree);
|
|
|
|
/* Register signal callbacks. */
|
|
sef_setcb_signal_handler(sef_cb_signal_handler);
|
|
|
|
/* Let SEF perform startup. */
|
|
sef_startup();
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* sef_cb_init_fresh *
|
|
*===========================================================================*/
|
|
PRIVATE int sef_cb_init_fresh(int UNUSED(type), sef_init_info_t *UNUSED(info))
|
|
{
|
|
/* Initialize the fxp driver. */
|
|
long v;
|
|
int r;
|
|
vir_bytes ft;
|
|
|
|
system_hz = sys_hz();
|
|
|
|
v = 0;
|
|
(void) env_parse("instance", "d", 0, &v, 0, 255);
|
|
fxp_instance = (int) v;
|
|
|
|
ft = sizeof(*fxp_state);
|
|
|
|
if(!(fxp_state = alloc_contig(ft, 0, NULL)))
|
|
panic("couldn't allocate table: %d", ENOMEM);
|
|
|
|
memset(fxp_state, 0, ft);
|
|
|
|
if((r=tsc_calibrate()) != OK)
|
|
panic("tsc_calibrate failed: %d", r);
|
|
|
|
/* Announce we are up! */
|
|
netdriver_announce();
|
|
|
|
return(OK);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* sef_cb_signal_handler *
|
|
*===========================================================================*/
|
|
PRIVATE void sef_cb_signal_handler(int signo)
|
|
{
|
|
port_t port;
|
|
fxp_t *fp;
|
|
|
|
/* Only check for termination signal, ignore anything else. */
|
|
if (signo != SIGTERM) return;
|
|
|
|
fp= fxp_state;
|
|
|
|
if (fp->fxp_mode == FM_ENABLED && (fp->fxp_flags & FF_ENABLED)) {
|
|
port= fp->fxp_base_port;
|
|
|
|
/* Reset device */
|
|
if (debug)
|
|
printf("%s: resetting device\n", fp->fxp_name);
|
|
fxp_outl(port, CSR_PORT, CP_CMD_SOFT_RESET);
|
|
}
|
|
|
|
exit(0);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_init *
|
|
*===========================================================================*/
|
|
static void fxp_init(mp)
|
|
message *mp;
|
|
{
|
|
static int first_time= 1;
|
|
|
|
fxp_t *fp;
|
|
message reply_mess;
|
|
|
|
if (first_time)
|
|
{
|
|
first_time= 0;
|
|
fxp_pci_conf(); /* Configure PCI devices. */
|
|
|
|
tmra_inittimer(&fxp_watchdog);
|
|
tmr_arg(&fxp_watchdog)->ta_int= 0;
|
|
fxp_set_timer(&fxp_watchdog, system_hz, fxp_watchdog_f);
|
|
}
|
|
|
|
fp= fxp_state;
|
|
if (fp->fxp_mode == FM_DISABLED)
|
|
{
|
|
/* This is the default, try to (re)locate the device. */
|
|
fxp_conf_hw(fp);
|
|
if (fp->fxp_mode == FM_DISABLED)
|
|
{
|
|
/* Probe failed, or the device is configured off. */
|
|
reply_mess.m_type= DL_CONF_REPLY;
|
|
reply_mess.DL_STAT= ENXIO;
|
|
mess_reply(mp, &reply_mess);
|
|
return;
|
|
}
|
|
if (fp->fxp_mode == FM_ENABLED)
|
|
fxp_init_hw(fp);
|
|
fxp_report_link(fp);
|
|
}
|
|
|
|
assert(fp->fxp_mode == FM_ENABLED);
|
|
assert(fp->fxp_flags & FF_ENABLED);
|
|
|
|
fp->fxp_flags &= ~(FF_PROMISC | FF_MULTI | FF_BROAD);
|
|
|
|
if (mp->DL_MODE & DL_PROMISC_REQ)
|
|
fp->fxp_flags |= FF_PROMISC;
|
|
if (mp->DL_MODE & DL_MULTI_REQ)
|
|
fp->fxp_flags |= FF_MULTI;
|
|
if (mp->DL_MODE & DL_BROAD_REQ)
|
|
fp->fxp_flags |= FF_BROAD;
|
|
|
|
fxp_rec_mode(fp);
|
|
|
|
reply_mess.m_type = DL_CONF_REPLY;
|
|
reply_mess.DL_STAT = OK;
|
|
*(ether_addr_t *) reply_mess.DL_HWADDR = fp->fxp_address;
|
|
|
|
mess_reply(mp, &reply_mess);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_pci_conf *
|
|
*===========================================================================*/
|
|
static void fxp_pci_conf()
|
|
{
|
|
static char envvar[] = FXP_ENVVAR "#";
|
|
static char envfmt[] = "*:d.d.d";
|
|
|
|
fxp_t *fp;
|
|
long v;
|
|
|
|
fp= fxp_state;
|
|
|
|
strcpy(fp->fxp_name, "fxp#0");
|
|
fp->fxp_name[4] += fxp_instance;
|
|
fp->fxp_seen= FALSE;
|
|
fp->fxp_features= FFE_NONE;
|
|
envvar[sizeof(FXP_ENVVAR)-1]= '0'+fxp_instance;
|
|
#if 0
|
|
if (getenv(envvar) != NULL)
|
|
{
|
|
if (strcmp(getenv(envvar), "off") == 0)
|
|
{
|
|
fp->fxp_pcibus= 255;
|
|
}
|
|
if (!env_prefix(envvar, "pci"))
|
|
env_panic(envvar);
|
|
}
|
|
#endif
|
|
|
|
pci_init();
|
|
|
|
if (fp->fxp_pcibus == 255)
|
|
return;
|
|
|
|
v= 0;
|
|
#if 0
|
|
(void) env_parse(envvar, envfmt, 1, &v, 0, 255);
|
|
#endif
|
|
fp->fxp_pcibus= v;
|
|
v= 0;
|
|
#if 0
|
|
(void) env_parse(envvar, envfmt, 2, &v, 0, 255);
|
|
#endif
|
|
fp->fxp_pcidev= v;
|
|
v= 0;
|
|
#if 0
|
|
(void) env_parse(envvar, envfmt, 3, &v, 0, 255);
|
|
#endif
|
|
fp->fxp_pcifunc= v;
|
|
|
|
if (fxp_probe(fp, fxp_instance))
|
|
fp->fxp_seen= TRUE;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_probe *
|
|
*===========================================================================*/
|
|
static int fxp_probe(fxp_t *fp, int skip)
|
|
{
|
|
int i, r, devind, just_one;
|
|
u16_t vid, did;
|
|
u32_t bar;
|
|
u8_t ilr, rev;
|
|
char *dname, *str;
|
|
|
|
if ((fp->fxp_pcibus | fp->fxp_pcidev | fp->fxp_pcifunc) != 0)
|
|
{
|
|
/* Look for specific PCI device */
|
|
r= pci_find_dev(fp->fxp_pcibus, fp->fxp_pcidev,
|
|
fp->fxp_pcifunc, &devind);
|
|
if (r == 0)
|
|
{
|
|
printf("%s: no PCI device found at %d.%d.%d\n",
|
|
fp->fxp_name, fp->fxp_pcibus,
|
|
fp->fxp_pcidev, fp->fxp_pcifunc);
|
|
return FALSE;
|
|
}
|
|
pci_ids(devind, &vid, &did);
|
|
just_one= TRUE;
|
|
}
|
|
else
|
|
{
|
|
r= pci_first_dev(&devind, &vid, &did);
|
|
if (r == 0)
|
|
return FALSE;
|
|
just_one= FALSE;
|
|
}
|
|
|
|
for(;;)
|
|
{
|
|
for (i= 0; pcitab_fxp[i].vid != 0; i++)
|
|
{
|
|
if (pcitab_fxp[i].vid != vid)
|
|
continue;
|
|
if (pcitab_fxp[i].did != did)
|
|
continue;
|
|
if (pcitab_fxp[i].checkclass) {
|
|
panic("fxp_probe: class check not implemented");
|
|
}
|
|
break;
|
|
}
|
|
if (pcitab_fxp[i].vid != 0)
|
|
{
|
|
if (just_one || !skip)
|
|
break;
|
|
skip--;
|
|
}
|
|
|
|
if (just_one)
|
|
{
|
|
printf(
|
|
"%s: wrong PCI device (%04x/%04x) found at %d.%d.%d\n",
|
|
fp->fxp_name, vid, did,
|
|
fp->fxp_pcibus,
|
|
fp->fxp_pcidev, fp->fxp_pcifunc);
|
|
return FALSE;
|
|
}
|
|
|
|
r= pci_next_dev(&devind, &vid, &did);
|
|
if (!r)
|
|
return FALSE;
|
|
}
|
|
|
|
dname= pci_dev_name(vid, did);
|
|
#if VERBOSE
|
|
if (!dname)
|
|
dname= "unknown device";
|
|
printf("%s: %s (%04x/%04x) at %s\n",
|
|
fp->fxp_name, dname, vid, did, pci_slot_name(devind));
|
|
#endif
|
|
pci_reserve(devind);
|
|
|
|
bar= pci_attr_r32(devind, PCI_BAR_2) & 0xffffffe0;
|
|
if (bar < 0x400) {
|
|
panic("fxp_probe: base address is not properly configured");
|
|
}
|
|
fp->fxp_base_port= bar;
|
|
|
|
ilr= pci_attr_r8(devind, PCI_ILR);
|
|
fp->fxp_irq= ilr;
|
|
if (debug)
|
|
{
|
|
printf("%s: using I/O address 0x%lx, IRQ %d\n",
|
|
fp->fxp_name, (unsigned long)bar, ilr);
|
|
}
|
|
|
|
rev= pci_attr_r8(devind, PCI_REV);
|
|
str= NULL;
|
|
fp->fxp_type= FT_UNKNOWN;
|
|
switch(rev)
|
|
{
|
|
case FXP_REV_82557A: str= "82557A"; /* 0x01 */
|
|
fp->fxp_type= FT_82557;
|
|
break;
|
|
case FXP_REV_82557B: str= "82557B"; break; /* 0x02 */
|
|
case FXP_REV_82557C: str= "82557C"; break; /* 0x03 */
|
|
case FXP_REV_82558A: str= "82558A"; /* 0x04 */
|
|
fp->fxp_type= FT_82558A;
|
|
break;
|
|
case FXP_REV_82558B: str= "82558B"; /* 0x05 */
|
|
fp->fxp_type= FT_82559;
|
|
break;
|
|
case FXP_REV_82559A: str= "82559A"; break; /* 0x06 */
|
|
case FXP_REV_82559B: str= "82559B"; break; /* 0x07 */
|
|
case FXP_REV_82559C: str= "82559C"; /* 0x08 */
|
|
fp->fxp_type= FT_82559;
|
|
break;
|
|
case FXP_REV_82559ERA: str= "82559ER-A"; /* 0x09 */
|
|
fp->fxp_type= FT_82559;
|
|
break;
|
|
case FXP_REV_82550_1: str= "82550(1)"; /* 0x0C */
|
|
fp->fxp_type= FT_82559;
|
|
break;
|
|
case FXP_REV_82550_2: str= "82550(2)"; /* 0x0D */
|
|
fp->fxp_type= FT_82559;
|
|
break;
|
|
case FXP_REV_82550_3: str= "82550(3)"; /* 0x0E */
|
|
fp->fxp_type= FT_82559;
|
|
break;
|
|
case FXP_REV_82551_1: str= "82551(1)"; /* 0x0F */
|
|
fp->fxp_type= FT_82559;
|
|
break;
|
|
case FXP_REV_82551_2: str= "82551(2)"; /* 0x10 */
|
|
fp->fxp_type= FT_82559;
|
|
break;
|
|
case FXP_REV_82801DB: str= "82801DB"; /* 0x81 */
|
|
fp->fxp_type= FT_82801;
|
|
break;
|
|
}
|
|
|
|
#if VERBOSE
|
|
if (str)
|
|
printf("%s: device revision: %s\n", fp->fxp_name, str);
|
|
else
|
|
printf("%s: unknown revision: 0x%x\n", fp->fxp_name, rev);
|
|
#endif
|
|
|
|
if (fp->fxp_type == FT_UNKNOWN)
|
|
{
|
|
printf("fxp_probe: device is not supported by this driver\n");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_conf_hw *
|
|
*===========================================================================*/
|
|
static void fxp_conf_hw(fxp_t *fp)
|
|
{
|
|
int i;
|
|
int mwi, ext_stat1, ext_stat2, lim_fifo, i82503, fc;
|
|
|
|
fp->fxp_mode= FM_DISABLED; /* Superfluous */
|
|
|
|
if (!fp->fxp_seen)
|
|
return;
|
|
|
|
/* PCI device is present */
|
|
fp->fxp_mode= FM_ENABLED;
|
|
|
|
fp->fxp_flags= FF_EMPTY;
|
|
fp->fxp_got_int= 0;
|
|
fp->fxp_send_int= 0;
|
|
fp->fxp_ee_addrlen= 0; /* Unknown */
|
|
fp->fxp_need_reset= 0;
|
|
fp->fxp_report_link= 0;
|
|
fp->fxp_link_up= -1; /* Unknown */
|
|
fp->fxp_mii_busy= 0;
|
|
fp->fxp_read_s= 0;
|
|
fp->fxp_rx_need_restart= 0;
|
|
fp->fxp_need_conf= 0;
|
|
fp->fxp_tx_head= 0;
|
|
fp->fxp_tx_tail= 0;
|
|
fp->fxp_tx_alive= 0;
|
|
fp->fxp_tx_threshold= TXTT_MIN;
|
|
|
|
/* Try to come up with a sensible configuration for the current
|
|
* device. Unfortunately every device is different, defaults are
|
|
* not always zero, and some fields are re-used with a completely
|
|
* different interpretation. We start out with a sensible default
|
|
* for all devices and then add device specific changes.
|
|
*/
|
|
fp->fxp_conf_bytes[0]= CC_BYTES_NR;
|
|
fp->fxp_conf_bytes[1]= CTL_DEFAULT | CRL_DEFAULT;
|
|
fp->fxp_conf_bytes[2]= CAI_DEFAULT;
|
|
fp->fxp_conf_bytes[3]= 0;
|
|
fp->fxp_conf_bytes[4]= 0;
|
|
fp->fxp_conf_bytes[5]= 0;
|
|
fp->fxp_conf_bytes[6]= CCB6_ESC | CCB6_ETCB | CCB6_RES;
|
|
fp->fxp_conf_bytes[7]= CUR_1;
|
|
fp->fxp_conf_bytes[8]= CCB8_503_MII;
|
|
fp->fxp_conf_bytes[9]= 0;
|
|
fp->fxp_conf_bytes[10]= CLB_NORMAL | CPAL_DEFAULT | CCB10_NSAI |
|
|
CCB10_RES1;
|
|
fp->fxp_conf_bytes[11]= 0;
|
|
fp->fxp_conf_bytes[12]= CIS_DEFAULT;
|
|
fp->fxp_conf_bytes[13]= CCB13_DEFAULT;
|
|
fp->fxp_conf_bytes[14]= CCB14_DEFAULT;
|
|
fp->fxp_conf_bytes[15]= CCB15_RES1 | CCB15_RES2;
|
|
fp->fxp_conf_bytes[16]= CCB16_DEFAULT;
|
|
fp->fxp_conf_bytes[17]= CCB17_DEFAULT;
|
|
fp->fxp_conf_bytes[18]= CCB18_RES1 | CCB18_PFCT | CCB18_PE;
|
|
fp->fxp_conf_bytes[19]= CCB19_FDPE;
|
|
fp->fxp_conf_bytes[20]= CCB20_PFCL | CCB20_RES1;
|
|
fp->fxp_conf_bytes[21]= CCB21_RES21;
|
|
|
|
#if VERBOSE
|
|
for (i= 0; i<CC_BYTES_NR; i++)
|
|
printf("%d: %0x, ", i, fp->fxp_conf_bytes[i]);
|
|
printf("\n");
|
|
#endif
|
|
|
|
mwi= 0; /* Do we want "Memory Write and Invalidate"? */
|
|
ext_stat1= 0; /* Do we want extended statistical counters? */
|
|
ext_stat2= 0; /* Do we want even more statistical counters? */
|
|
lim_fifo= 0; /* Limit number of frame in TX FIFO */
|
|
i82503= 0; /* Older 10 Mbps interface on the 82557 */
|
|
fc= 0; /* Flow control */
|
|
|
|
switch(fp->fxp_type)
|
|
{
|
|
case FT_82557:
|
|
if (i82503)
|
|
{
|
|
fp->fxp_conf_bytes[8] &= ~CCB8_503_MII;
|
|
fp->fxp_conf_bytes[15] |= CCB15_CRSCDT;
|
|
}
|
|
break;
|
|
case FT_82558A:
|
|
case FT_82559:
|
|
case FT_82801:
|
|
if (mwi)
|
|
fp->fxp_conf_bytes[3] |= CCB3_MWIE;
|
|
if (ext_stat1)
|
|
fp->fxp_conf_bytes[6] &= ~CCB6_ESC;
|
|
if (ext_stat2)
|
|
fp->fxp_conf_bytes[6] &= ~CCB6_TCOSC;
|
|
if (lim_fifo)
|
|
fp->fxp_conf_bytes[7] |= CCB7_2FFIFO;
|
|
if (fc)
|
|
{
|
|
/* From FreeBSD driver */
|
|
fp->fxp_conf_bytes[16]= 0x1f;
|
|
fp->fxp_conf_bytes[17]= 0x01;
|
|
|
|
fp->fxp_conf_bytes[19] |= CCB19_FDRSTAFC |
|
|
CCB19_FDRSTOFC;
|
|
}
|
|
|
|
fp->fxp_conf_bytes[18] |= CCB18_LROK;
|
|
|
|
if (fp->fxp_type == FT_82801)
|
|
{
|
|
fp->fxp_conf_bytes[6] = 0xba; /* ctrl 1 */
|
|
fp->fxp_conf_bytes[15] = 0x48; /* promiscuous */
|
|
fp->fxp_conf_bytes[21] = 0x05; /* mc_all */
|
|
}
|
|
break;
|
|
default:
|
|
panic("fxp_conf_hw: bad device type: %d", fp->fxp_type);
|
|
}
|
|
|
|
#if VERBOSE
|
|
for (i= 0; i<CC_BYTES_NR; i++)
|
|
printf("%d: %0x, ", i, fp->fxp_conf_bytes[i]);
|
|
printf("\n");
|
|
#endif
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_init_hw *
|
|
*===========================================================================*/
|
|
static void fxp_init_hw(fp)
|
|
fxp_t *fp;
|
|
{
|
|
int i, r, isr;
|
|
port_t port;
|
|
u32_t bus_addr;
|
|
|
|
port= fp->fxp_base_port;
|
|
|
|
fxp_init_buf(fp);
|
|
|
|
fp->fxp_flags = FF_EMPTY;
|
|
fp->fxp_flags |= FF_ENABLED;
|
|
|
|
/* Set the interrupt handler and policy. Do not automatically
|
|
* reenable interrupts. Return the IRQ line number on interrupts.
|
|
*/
|
|
fp->fxp_hook = fp->fxp_irq;
|
|
r= sys_irqsetpolicy(fp->fxp_irq, 0, &fp->fxp_hook);
|
|
if (r != OK)
|
|
panic("sys_irqsetpolicy failed: %d", r);
|
|
|
|
fxp_reset_hw(fp);
|
|
|
|
r= sys_irqenable(&fp->fxp_hook);
|
|
if (r != OK)
|
|
panic("sys_irqenable failed: %d", r);
|
|
|
|
/* Reset PHY? */
|
|
|
|
fxp_do_conf(fp);
|
|
|
|
/* Set pointer to statistical counters */
|
|
r= sys_umap(SELF, VM_D, (vir_bytes)&fp->fxp_stat, sizeof(fp->fxp_stat),
|
|
&bus_addr);
|
|
if (r != OK)
|
|
panic("sys_umap failed: %d", r);
|
|
fxp_cu_ptr_cmd(fp, SC_CU_LOAD_DCA, bus_addr, TRUE /* check idle */);
|
|
|
|
/* Ack previous interrupts */
|
|
isr= fxp_inb(port, SCB_INT_STAT);
|
|
fxp_outb(port, SCB_INT_STAT, isr);
|
|
|
|
/* Enable interrupts */
|
|
fxp_outb(port, SCB_INT_MASK, 0);
|
|
|
|
fxp_ru_ptr_cmd(fp, SC_RU_START, fp->fxp_rx_busaddr,
|
|
TRUE /* check idle */);
|
|
|
|
fxp_confaddr(fp);
|
|
if (debug)
|
|
{
|
|
printf("%s: Ethernet address ", fp->fxp_name);
|
|
for (i= 0; i < 6; i++)
|
|
{
|
|
printf("%x%c", fp->fxp_address.ea_addr[i],
|
|
i < 5 ? ':' : '\n');
|
|
}
|
|
}
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_init_buf *
|
|
*===========================================================================*/
|
|
static void fxp_init_buf(fp)
|
|
fxp_t *fp;
|
|
{
|
|
size_t rx_totbufsize, tx_totbufsize, tot_bufsize, alloc_bufsize;
|
|
char *alloc_buf;
|
|
phys_bytes buf;
|
|
int i, r;
|
|
struct rfd *rfdp;
|
|
struct tx *txp;
|
|
phys_bytes ph;
|
|
|
|
fp->fxp_rx_nbuf= N_RX_BUF;
|
|
rx_totbufsize= fp->fxp_rx_nbuf * sizeof(struct rfd);
|
|
fp->fxp_rx_bufsize= rx_totbufsize;
|
|
|
|
fp->fxp_tx_nbuf= N_TX_BUF;
|
|
tx_totbufsize= fp->fxp_tx_nbuf * sizeof(struct tx);
|
|
fp->fxp_tx_bufsize= tx_totbufsize;
|
|
|
|
tot_bufsize= sizeof(*tmpbufp) + tx_totbufsize + rx_totbufsize;
|
|
if (tot_bufsize % 4096)
|
|
tot_bufsize += 4096 - (tot_bufsize % 4096);
|
|
alloc_bufsize= tot_bufsize;
|
|
alloc_buf= alloc_contig(alloc_bufsize, AC_ALIGN4K, &ph);
|
|
if (alloc_buf == NULL) {
|
|
panic("fxp_init_buf: unable to alloc_contig size: %d", alloc_bufsize);
|
|
}
|
|
|
|
buf= (phys_bytes)alloc_buf;
|
|
|
|
tell_dev((vir_bytes)buf, tot_bufsize, 0, 0, 0);
|
|
|
|
tmpbufp= (union tmpbuf *)buf;
|
|
|
|
fp->fxp_rx_buf= (struct rfd *)&tmpbufp[1];
|
|
r= sys_umap(SELF, VM_D, (vir_bytes)fp->fxp_rx_buf, rx_totbufsize,
|
|
&fp->fxp_rx_busaddr);
|
|
if (r != OK)
|
|
panic("sys_umap failed: %d", r);
|
|
|
|
#if 0
|
|
printf("fxp_init_buf: got phys 0x%x for vir 0x%x\n",
|
|
fp->fxp_rx_busaddr, fp->fxp_rx_buf);
|
|
#endif
|
|
|
|
for (i= 0, rfdp= fp->fxp_rx_buf; i<fp->fxp_rx_nbuf; i++, rfdp++)
|
|
{
|
|
rfdp->rfd_status= 0;
|
|
rfdp->rfd_command= 0;
|
|
if (i != fp->fxp_rx_nbuf-1)
|
|
{
|
|
r= sys_umap(SELF, VM_D, (vir_bytes)&rfdp[1],
|
|
sizeof(rfdp[1]), &rfdp->rfd_linkaddr);
|
|
if (r != OK)
|
|
panic("sys_umap failed: %d", r);
|
|
}
|
|
else
|
|
{
|
|
rfdp->rfd_linkaddr= fp->fxp_rx_busaddr;
|
|
rfdp->rfd_command |= RFDC_EL;
|
|
}
|
|
rfdp->rfd_reserved= 0;
|
|
rfdp->rfd_res= 0;
|
|
rfdp->rfd_size= sizeof(rfdp->rfd_buf);
|
|
|
|
}
|
|
fp->fxp_rx_head= 0;
|
|
|
|
fp->fxp_tx_buf= (struct tx *)((char *)fp->fxp_rx_buf+rx_totbufsize);
|
|
r= sys_umap(SELF, VM_D, (vir_bytes)fp->fxp_tx_buf,
|
|
(phys_bytes)tx_totbufsize, &fp->fxp_tx_busaddr);
|
|
if (r != OK)
|
|
panic("sys_umap failed: %d", r);
|
|
|
|
for (i= 0, txp= fp->fxp_tx_buf; i<fp->fxp_tx_nbuf; i++, txp++)
|
|
{
|
|
txp->tx_status= 0;
|
|
txp->tx_command= TXC_EL | CBL_NOP; /* Just in case */
|
|
if (i != fp->fxp_tx_nbuf-1)
|
|
{
|
|
r= sys_umap(SELF, VM_D, (vir_bytes)&txp[1],
|
|
(phys_bytes)sizeof(txp[1]),
|
|
&txp->tx_linkaddr);
|
|
if (r != OK)
|
|
panic("sys_umap failed: %d", r);
|
|
}
|
|
else
|
|
{
|
|
txp->tx_linkaddr= fp->fxp_tx_busaddr;
|
|
}
|
|
txp->tx_tbda= TX_TBDA_NIL;
|
|
txp->tx_size= 0;
|
|
txp->tx_tthresh= fp->fxp_tx_threshold;
|
|
txp->tx_ntbd= 0;
|
|
}
|
|
fp->fxp_tx_idle= 1;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_reset_hw *
|
|
*===========================================================================*/
|
|
static void fxp_reset_hw(fp)
|
|
fxp_t *fp;
|
|
{
|
|
/* Inline the function in init? */
|
|
port_t port;
|
|
|
|
port= fp->fxp_base_port;
|
|
|
|
/* Reset device */
|
|
fxp_outl(port, CSR_PORT, CP_CMD_SOFT_RESET);
|
|
tickdelay(micros_to_ticks(CSR_PORT_RESET_DELAY));
|
|
|
|
/* Disable interrupts */
|
|
fxp_outb(port, SCB_INT_MASK, SIM_M);
|
|
|
|
/* Set CU base to zero */
|
|
fxp_cu_ptr_cmd(fp, SC_CU_LOAD_BASE, 0, TRUE /* check idle */);
|
|
|
|
/* Set RU base to zero */
|
|
fxp_ru_ptr_cmd(fp, SC_RU_LOAD_BASE, 0, TRUE /* check idle */);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_confaddr *
|
|
*===========================================================================*/
|
|
static void fxp_confaddr(fxp_t *fp)
|
|
{
|
|
static char eakey[]= FXP_ENVVAR "#_EA";
|
|
static char eafmt[]= "x:x:x:x:x:x";
|
|
clock_t t0,t1;
|
|
int i, r;
|
|
u32_t bus_addr;
|
|
long v;
|
|
|
|
/* User defined ethernet address? */
|
|
eakey[sizeof(FXP_ENVVAR)-1]= '0' + fxp_instance;
|
|
|
|
for (i= 0; i < 6; i++)
|
|
{
|
|
if (env_parse(eakey, eafmt, i, &v, 0x00L, 0xFFL) != EP_SET)
|
|
break;
|
|
fp->fxp_address.ea_addr[i]= v;
|
|
}
|
|
|
|
if (i != 0 && i != 6) env_panic(eakey); /* It's all or nothing */
|
|
|
|
if (i == 0)
|
|
{
|
|
/* Get ethernet address from EEPROM */
|
|
for (i= 0; i<3; i++)
|
|
{
|
|
v= eeprom_read(fp, i);
|
|
fp->fxp_address.ea_addr[i*2]= (v & 0xff);
|
|
fp->fxp_address.ea_addr[i*2+1]= ((v >> 8) & 0xff);
|
|
}
|
|
}
|
|
|
|
/* Tell NIC about ethernet address */
|
|
tmpbufp->ias.ias_status= 0;
|
|
tmpbufp->ias.ias_command= CBL_C_EL | CBL_AIS;
|
|
tmpbufp->ias.ias_linkaddr= 0;
|
|
memcpy(tmpbufp->ias.ias_ethaddr, fp->fxp_address.ea_addr,
|
|
sizeof(tmpbufp->ias.ias_ethaddr));
|
|
r= sys_umap(SELF, VM_D, (vir_bytes)&tmpbufp->ias,
|
|
(phys_bytes)sizeof(tmpbufp->ias), &bus_addr);
|
|
if (r != OK)
|
|
panic("sys_umap failed: %d", r);
|
|
|
|
fxp_cu_ptr_cmd(fp, SC_CU_START, bus_addr, TRUE /* check idle */);
|
|
|
|
getuptime(&t0);
|
|
do {
|
|
/* Wait for CU command to complete */
|
|
if (tmpbufp->ias.ias_status & CBL_F_C)
|
|
break;
|
|
} while (getuptime(&t1)==OK && (t1-t0) < micros_to_ticks(1000));
|
|
|
|
if (!(tmpbufp->ias.ias_status & CBL_F_C))
|
|
panic("fxp_confaddr: CU command failed to complete");
|
|
if (!(tmpbufp->ias.ias_status & CBL_F_OK))
|
|
panic("fxp_confaddr: CU command failed");
|
|
|
|
#if VERBOSE
|
|
printf("%s: hardware ethernet address: ", fp->fxp_name);
|
|
for (i= 0; i<6; i++)
|
|
{
|
|
printf("%02x%s", fp->fxp_address.ea_addr[i],
|
|
i < 5 ? ":" : "");
|
|
}
|
|
printf("\n");
|
|
#endif
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_rec_mode *
|
|
*===========================================================================*/
|
|
static void fxp_rec_mode(fp)
|
|
fxp_t *fp;
|
|
{
|
|
fp->fxp_conf_bytes[0]= CC_BYTES_NR; /* Just to be sure */
|
|
fp->fxp_conf_bytes[15] &= ~(CCB15_BD|CCB15_PM);
|
|
fp->fxp_conf_bytes[21] &= ~CCB21_MA;
|
|
|
|
if (fp->fxp_flags & FF_PROMISC)
|
|
fp->fxp_conf_bytes[15] |= CCB15_PM;
|
|
if (fp->fxp_flags & FF_MULTI)
|
|
fp->fxp_conf_bytes[21] |= CCB21_MA;
|
|
|
|
if (!(fp->fxp_flags & (FF_BROAD|FF_MULTI|FF_PROMISC)))
|
|
fp->fxp_conf_bytes[15] |= CCB15_BD;
|
|
|
|
/* Queue request if not idle */
|
|
if (fp->fxp_tx_idle)
|
|
{
|
|
fxp_do_conf(fp);
|
|
}
|
|
else
|
|
{
|
|
printf("fxp_rec_mode: setting fxp_need_conf\n");
|
|
fp->fxp_need_conf= TRUE;
|
|
}
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_writev_s *
|
|
*===========================================================================*/
|
|
static void fxp_writev_s(const message *mp, int from_int)
|
|
{
|
|
endpoint_t iov_endpt;
|
|
cp_grant_id_t iov_grant;
|
|
vir_bytes iov_offset;
|
|
int i, j, n, o, r, s, count, size, prev_head;
|
|
int fxp_tx_nbuf, fxp_tx_head;
|
|
u16_t tx_command;
|
|
fxp_t *fp;
|
|
iovec_s_t *iovp;
|
|
struct tx *txp, *prev_txp;
|
|
|
|
fp= fxp_state;
|
|
|
|
count = mp->DL_COUNT;
|
|
fp->fxp_client= mp->m_source;
|
|
|
|
assert(fp->fxp_mode == FM_ENABLED);
|
|
assert(fp->fxp_flags & FF_ENABLED);
|
|
|
|
if (from_int)
|
|
{
|
|
assert(fp->fxp_flags & FF_SEND_AVAIL);
|
|
fp->fxp_flags &= ~FF_SEND_AVAIL;
|
|
fp->fxp_tx_alive= TRUE;
|
|
}
|
|
|
|
if (fp->fxp_tx_idle)
|
|
{
|
|
txp= fp->fxp_tx_buf;
|
|
fxp_tx_head= 0; /* lint */
|
|
prev_txp= NULL; /* lint */
|
|
}
|
|
else
|
|
{
|
|
fxp_tx_nbuf= fp->fxp_tx_nbuf;
|
|
prev_head= fp->fxp_tx_head;
|
|
fxp_tx_head= prev_head+1;
|
|
if (fxp_tx_head == fxp_tx_nbuf)
|
|
fxp_tx_head= 0;
|
|
assert(fxp_tx_head < fxp_tx_nbuf);
|
|
|
|
if (fxp_tx_head == fp->fxp_tx_tail)
|
|
{
|
|
/* Send queue is full */
|
|
assert(!(fp->fxp_flags & FF_SEND_AVAIL));
|
|
fp->fxp_flags |= FF_SEND_AVAIL;
|
|
goto suspend;
|
|
}
|
|
|
|
prev_txp= &fp->fxp_tx_buf[prev_head];
|
|
txp= &fp->fxp_tx_buf[fxp_tx_head];
|
|
}
|
|
|
|
assert(!(fp->fxp_flags & FF_SEND_AVAIL));
|
|
assert(!(fp->fxp_flags & FF_PACK_SENT));
|
|
|
|
iov_endpt= mp->DL_ENDPT;
|
|
iov_grant= mp->DL_GRANT;
|
|
|
|
size= 0;
|
|
o= 0;
|
|
iov_offset= 0;
|
|
for (i= 0; i<count; i += IOVEC_NR,
|
|
iov_offset += IOVEC_NR * sizeof(fp->fxp_iovec_s[0]))
|
|
{
|
|
n= IOVEC_NR;
|
|
if (i+n > count)
|
|
n= count-i;
|
|
r= sys_safecopyfrom(iov_endpt, iov_grant, iov_offset,
|
|
(vir_bytes)fp->fxp_iovec_s,
|
|
n * sizeof(fp->fxp_iovec_s[0]), D);
|
|
if (r != OK)
|
|
panic("fxp_writev: sys_safecopyfrom failed: %d", r);
|
|
|
|
for (j= 0, iovp= fp->fxp_iovec_s; j<n; j++, iovp++)
|
|
{
|
|
s= iovp->iov_size;
|
|
if (size + s > ETH_MAX_PACK_SIZE_TAGGED) {
|
|
panic("fxp_writev: invalid packet size: %d", size + s);
|
|
}
|
|
|
|
r= sys_safecopyfrom(iov_endpt, iovp->iov_grant,
|
|
0, (vir_bytes)(txp->tx_buf+o), s, D);
|
|
if (r != OK) {
|
|
panic("fxp_writev_s: sys_safecopyfrom failed: %d", r);
|
|
}
|
|
size += s;
|
|
o += s;
|
|
}
|
|
}
|
|
if (size < ETH_MIN_PACK_SIZE)
|
|
panic("fxp_writev: invalid packet size: %d", size);
|
|
|
|
txp->tx_status= 0;
|
|
txp->tx_command= TXC_EL | CBL_XMIT;
|
|
txp->tx_tbda= TX_TBDA_NIL;
|
|
txp->tx_size= TXSZ_EOF | size;
|
|
txp->tx_tthresh= fp->fxp_tx_threshold;
|
|
txp->tx_ntbd= 0;
|
|
if (fp->fxp_tx_idle)
|
|
{
|
|
fp->fxp_tx_idle= 0;
|
|
fp->fxp_tx_head= fp->fxp_tx_tail= 0;
|
|
|
|
fxp_cu_ptr_cmd(fp, SC_CU_START, fp->fxp_tx_busaddr,
|
|
TRUE /* check idle */);
|
|
}
|
|
else
|
|
{
|
|
/* Link new request in transmit list */
|
|
tx_command= prev_txp->tx_command;
|
|
assert(tx_command == (TXC_EL | CBL_XMIT));
|
|
prev_txp->tx_command= CBL_XMIT;
|
|
fp->fxp_tx_head= fxp_tx_head;
|
|
}
|
|
|
|
fp->fxp_flags |= FF_PACK_SENT;
|
|
|
|
/* If the interrupt handler called, don't send a reply. The reply
|
|
* will be sent after all interrupts are handled.
|
|
*/
|
|
if (from_int)
|
|
return;
|
|
reply(fp);
|
|
return;
|
|
|
|
suspend:
|
|
if (from_int)
|
|
panic("fxp: should not be sending");
|
|
|
|
fp->fxp_tx_mess= *mp;
|
|
reply(fp);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_readv_s *
|
|
*===========================================================================*/
|
|
static void fxp_readv_s(mp, from_int)
|
|
message *mp;
|
|
int from_int;
|
|
{
|
|
int i, j, n, o, r, s, count, size, fxp_rx_head, fxp_rx_nbuf;
|
|
endpoint_t iov_endpt;
|
|
cp_grant_id_t iov_grant;
|
|
port_t port;
|
|
unsigned packlen;
|
|
vir_bytes iov_offset;
|
|
u16_t rfd_status;
|
|
u16_t rfd_res;
|
|
u8_t scb_status;
|
|
fxp_t *fp;
|
|
iovec_s_t *iovp;
|
|
struct rfd *rfdp, *prev_rfdp;
|
|
|
|
fp= fxp_state;
|
|
|
|
count = mp->DL_COUNT;
|
|
fp->fxp_client= mp->m_source;
|
|
|
|
assert(fp->fxp_mode == FM_ENABLED);
|
|
assert(fp->fxp_flags & FF_ENABLED);
|
|
|
|
port= fp->fxp_base_port;
|
|
|
|
fxp_rx_head= fp->fxp_rx_head;
|
|
rfdp= &fp->fxp_rx_buf[fxp_rx_head];
|
|
|
|
rfd_status= rfdp->rfd_status;
|
|
if (!(rfd_status & RFDS_C))
|
|
{
|
|
/* Receive buffer is empty, suspend */
|
|
goto suspend;
|
|
}
|
|
|
|
if (!(rfd_status & RFDS_OK))
|
|
{
|
|
/* Not OK? What happened? */
|
|
assert(0);
|
|
}
|
|
else
|
|
{
|
|
assert(!(rfd_status & (RFDS_CRCERR | RFDS_ALIGNERR |
|
|
RFDS_OUTOFBUF | RFDS_DMAOVR | RFDS_TOOSHORT |
|
|
RFDS_RXERR)));
|
|
}
|
|
rfd_res= rfdp->rfd_res;
|
|
assert(rfd_res & RFDR_EOF);
|
|
assert(rfd_res & RFDR_F);
|
|
|
|
packlen= rfd_res & RFDSZ_SIZE;
|
|
|
|
iov_endpt = mp->DL_ENDPT;
|
|
iov_grant = mp->DL_GRANT;
|
|
|
|
size= 0;
|
|
o= 0;
|
|
iov_offset= 0;
|
|
for (i= 0; i<count; i += IOVEC_NR,
|
|
iov_offset += IOVEC_NR * sizeof(fp->fxp_iovec_s[0]))
|
|
{
|
|
n= IOVEC_NR;
|
|
if (i+n > count)
|
|
n= count-i;
|
|
r= sys_safecopyfrom(iov_endpt, iov_grant, iov_offset,
|
|
(vir_bytes)fp->fxp_iovec_s,
|
|
n * sizeof(fp->fxp_iovec_s[0]), D);
|
|
if (r != OK)
|
|
panic("fxp_readv_s: sys_safecopyfrom failed: %d", r);
|
|
|
|
for (j= 0, iovp= fp->fxp_iovec_s; j<n; j++, iovp++)
|
|
{
|
|
s= iovp->iov_size;
|
|
if (size + s > packlen)
|
|
{
|
|
assert(packlen > size);
|
|
s= packlen-size;
|
|
}
|
|
|
|
r= sys_safecopyto(iov_endpt, iovp->iov_grant,
|
|
0, (vir_bytes)(rfdp->rfd_buf+o), s, D);
|
|
if (r != OK)
|
|
{
|
|
panic("fxp_readv: sys_safecopyto failed: %d", r);
|
|
}
|
|
|
|
size += s;
|
|
if (size == packlen)
|
|
break;
|
|
o += s;
|
|
}
|
|
if (size == packlen)
|
|
break;
|
|
}
|
|
if (size < packlen)
|
|
{
|
|
assert(0);
|
|
}
|
|
|
|
fp->fxp_read_s= packlen;
|
|
fp->fxp_flags= (fp->fxp_flags & ~FF_READING) | FF_PACK_RECV;
|
|
|
|
/* Re-init the current buffer */
|
|
rfdp->rfd_status= 0;
|
|
rfdp->rfd_command= RFDC_EL;
|
|
rfdp->rfd_reserved= 0;
|
|
rfdp->rfd_res= 0;
|
|
rfdp->rfd_size= sizeof(rfdp->rfd_buf);
|
|
|
|
fxp_rx_nbuf= fp->fxp_rx_nbuf;
|
|
if (fxp_rx_head == 0)
|
|
{
|
|
prev_rfdp= &fp->fxp_rx_buf[fxp_rx_nbuf-1];
|
|
}
|
|
else
|
|
prev_rfdp= &rfdp[-1];
|
|
|
|
assert(prev_rfdp->rfd_command & RFDC_EL);
|
|
prev_rfdp->rfd_command &= ~RFDC_EL;
|
|
|
|
fxp_rx_head++;
|
|
if (fxp_rx_head == fxp_rx_nbuf)
|
|
fxp_rx_head= 0;
|
|
assert(fxp_rx_head < fxp_rx_nbuf);
|
|
fp->fxp_rx_head= fxp_rx_head;
|
|
|
|
if (!from_int)
|
|
reply(fp);
|
|
|
|
return;
|
|
|
|
suspend:
|
|
if (fp->fxp_rx_need_restart)
|
|
{
|
|
fp->fxp_rx_need_restart= 0;
|
|
|
|
/* Check the status of the RU */
|
|
scb_status= fxp_inb(port, SCB_STATUS);
|
|
if ((scb_status & SS_RUS_MASK) != SS_RU_NORES)
|
|
{
|
|
/* Race condition? */
|
|
printf("fxp_readv: restart race: 0x%x\n",
|
|
scb_status);
|
|
assert((scb_status & SS_RUS_MASK) == SS_RU_READY);
|
|
}
|
|
else
|
|
{
|
|
fxp_restart_ru(fp);
|
|
}
|
|
}
|
|
if (from_int)
|
|
{
|
|
assert(fp->fxp_flags & FF_READING);
|
|
|
|
/* No need to store any state */
|
|
return;
|
|
}
|
|
|
|
fp->fxp_rx_mess= *mp;
|
|
assert(!(fp->fxp_flags & FF_READING));
|
|
fp->fxp_flags |= FF_READING;
|
|
|
|
reply(fp);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_do_conf *
|
|
*===========================================================================*/
|
|
static void fxp_do_conf(fp)
|
|
fxp_t *fp;
|
|
{
|
|
int r;
|
|
u32_t bus_addr;
|
|
clock_t t0,t1;
|
|
|
|
/* Configure device */
|
|
tmpbufp->cc.cc_status= 0;
|
|
tmpbufp->cc.cc_command= CBL_C_EL | CBL_CONF;
|
|
tmpbufp->cc.cc_linkaddr= 0;
|
|
memcpy(tmpbufp->cc.cc_bytes, fp->fxp_conf_bytes,
|
|
sizeof(tmpbufp->cc.cc_bytes));
|
|
|
|
r= sys_umap(SELF, VM_D, (vir_bytes)&tmpbufp->cc,
|
|
(phys_bytes)sizeof(tmpbufp->cc), &bus_addr);
|
|
if (r != OK)
|
|
panic("sys_umap failed: %d", r);
|
|
|
|
fxp_cu_ptr_cmd(fp, SC_CU_START, bus_addr, TRUE /* check idle */);
|
|
|
|
getuptime(&t0);
|
|
do {
|
|
/* Wait for CU command to complete */
|
|
if (tmpbufp->cc.cc_status & CBL_F_C)
|
|
break;
|
|
} while (getuptime(&t1)==OK && (t1-t0) < micros_to_ticks(100000));
|
|
|
|
if (!(tmpbufp->cc.cc_status & CBL_F_C))
|
|
panic("fxp_do_conf: CU command failed to complete");
|
|
if (!(tmpbufp->cc.cc_status & CBL_F_OK))
|
|
panic("fxp_do_conf: CU command failed");
|
|
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_cu_ptr_cmd *
|
|
*===========================================================================*/
|
|
static void fxp_cu_ptr_cmd(fp, cmd, bus_addr, check_idle)
|
|
fxp_t *fp;
|
|
int cmd;
|
|
phys_bytes bus_addr;
|
|
int check_idle;
|
|
{
|
|
clock_t t0,t1;
|
|
port_t port;
|
|
u8_t scb_cmd;
|
|
|
|
port= fp->fxp_base_port;
|
|
|
|
if (check_idle)
|
|
{
|
|
/* Consistency check. Make sure that CU is idle */
|
|
if ((fxp_inb(port, SCB_STATUS) & SS_CUS_MASK) != SS_CU_IDLE)
|
|
panic("fxp_cu_ptr_cmd: CU is not idle");
|
|
}
|
|
|
|
fxp_outl(port, SCB_POINTER, bus_addr);
|
|
fxp_outb(port, SCB_CMD, cmd);
|
|
|
|
/* What is a reasonable time-out? There is nothing in the
|
|
* documentation. 1 ms should be enough.
|
|
*/
|
|
getuptime(&t0);
|
|
do {
|
|
/* Wait for CU command to be accepted */
|
|
scb_cmd= fxp_inb(port, SCB_CMD);
|
|
if ((scb_cmd & SC_CUC_MASK) == SC_CU_NOP)
|
|
break;
|
|
} while (getuptime(&t1)==OK && (t1-t0) < micros_to_ticks(100000));
|
|
|
|
if ((scb_cmd & SC_CUC_MASK) != SC_CU_NOP)
|
|
panic("fxp_cu_ptr_cmd: CU does not accept command");
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_ru_ptr_cmd *
|
|
*===========================================================================*/
|
|
static void fxp_ru_ptr_cmd(fp, cmd, bus_addr, check_idle)
|
|
fxp_t *fp;
|
|
int cmd;
|
|
phys_bytes bus_addr;
|
|
int check_idle;
|
|
{
|
|
clock_t t0,t1;
|
|
port_t port;
|
|
u8_t scb_cmd;
|
|
|
|
port= fp->fxp_base_port;
|
|
|
|
if (check_idle)
|
|
{
|
|
/* Consistency check, make sure that RU is idle */
|
|
if ((fxp_inb(port, SCB_STATUS) & SS_RUS_MASK) != SS_RU_IDLE)
|
|
panic("fxp_ru_ptr_cmd: RU is not idle");
|
|
}
|
|
|
|
fxp_outl(port, SCB_POINTER, bus_addr);
|
|
fxp_outb(port, SCB_CMD, cmd);
|
|
|
|
getuptime(&t0);
|
|
do {
|
|
/* Wait for RU command to be accepted */
|
|
scb_cmd= fxp_inb(port, SCB_CMD);
|
|
if ((scb_cmd & SC_RUC_MASK) == SC_RU_NOP)
|
|
break;
|
|
} while (getuptime(&t1)==OK && (t1-t0) < micros_to_ticks(1000));
|
|
|
|
if ((scb_cmd & SC_RUC_MASK) != SC_RU_NOP)
|
|
panic("fxp_ru_ptr_cmd: RU does not accept command");
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_restart_ru *
|
|
*===========================================================================*/
|
|
static void fxp_restart_ru(fp)
|
|
fxp_t *fp;
|
|
{
|
|
int i, fxp_rx_nbuf;
|
|
port_t port;
|
|
struct rfd *rfdp;
|
|
|
|
port= fp->fxp_base_port;
|
|
|
|
fxp_rx_nbuf= fp->fxp_rx_nbuf;
|
|
for (i= 0, rfdp= fp->fxp_rx_buf; i<fxp_rx_nbuf; i++, rfdp++)
|
|
{
|
|
rfdp->rfd_status= 0;
|
|
rfdp->rfd_command= 0;
|
|
if (i == fp->fxp_rx_nbuf-1)
|
|
rfdp->rfd_command= RFDC_EL;
|
|
rfdp->rfd_reserved= 0;
|
|
rfdp->rfd_res= 0;
|
|
rfdp->rfd_size= sizeof(rfdp->rfd_buf);
|
|
}
|
|
fp->fxp_rx_head= 0;
|
|
|
|
/* Make sure that RU is in the 'No resources' state */
|
|
if ((fxp_inb(port, SCB_STATUS) & SS_RUS_MASK) != SS_RU_NORES)
|
|
panic("fxp_restart_ru: RU is in an unexpected state");
|
|
|
|
fxp_ru_ptr_cmd(fp, SC_RU_START, fp->fxp_rx_busaddr,
|
|
FALSE /* do not check idle */);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_getstat_s *
|
|
*===========================================================================*/
|
|
static void fxp_getstat_s(message *mp)
|
|
{
|
|
clock_t t0,t1;
|
|
int r;
|
|
fxp_t *fp;
|
|
u32_t *p;
|
|
eth_stat_t stats;
|
|
|
|
fp= fxp_state;
|
|
|
|
assert(fp->fxp_mode == FM_ENABLED);
|
|
assert(fp->fxp_flags & FF_ENABLED);
|
|
|
|
p= &fp->fxp_stat.sc_tx_fcp;
|
|
*p= 0;
|
|
|
|
/* The dump commmand doesn't take a pointer. Setting a pointer
|
|
* doesn't hurt though.
|
|
*/
|
|
fxp_cu_ptr_cmd(fp, SC_CU_DUMP_SC, 0, FALSE /* do not check idle */);
|
|
|
|
getuptime(&t0);
|
|
do {
|
|
/* Wait for CU command to complete */
|
|
if (*p != 0)
|
|
break;
|
|
} while (getuptime(&t1)==OK && (t1-t0) < micros_to_ticks(1000));
|
|
|
|
if (*p == 0)
|
|
panic("fxp_getstat: CU command failed to complete");
|
|
if (*p != SCM_DSC)
|
|
panic("fxp_getstat: bad magic");
|
|
|
|
stats.ets_recvErr=
|
|
fp->fxp_stat.sc_rx_crc +
|
|
fp->fxp_stat.sc_rx_align +
|
|
fp->fxp_stat.sc_rx_resource +
|
|
fp->fxp_stat.sc_rx_overrun +
|
|
fp->fxp_stat.sc_rx_cd +
|
|
fp->fxp_stat.sc_rx_short;
|
|
stats.ets_sendErr=
|
|
fp->fxp_stat.sc_tx_maxcol +
|
|
fp->fxp_stat.sc_tx_latecol +
|
|
fp->fxp_stat.sc_tx_crs;
|
|
stats.ets_OVW= fp->fxp_stat.sc_rx_overrun;
|
|
stats.ets_CRCerr= fp->fxp_stat.sc_rx_crc;
|
|
stats.ets_frameAll= fp->fxp_stat.sc_rx_align;
|
|
stats.ets_missedP= fp->fxp_stat.sc_rx_resource;
|
|
stats.ets_packetR= fp->fxp_stat.sc_rx_good;
|
|
stats.ets_packetT= fp->fxp_stat.sc_tx_good;
|
|
stats.ets_transDef= fp->fxp_stat.sc_tx_defered;
|
|
stats.ets_collision= fp->fxp_stat.sc_tx_totcol;
|
|
stats.ets_transAb= fp->fxp_stat.sc_tx_maxcol;
|
|
stats.ets_carrSense= fp->fxp_stat.sc_tx_crs;
|
|
stats.ets_fifoUnder= fp->fxp_stat.sc_tx_underrun;
|
|
stats.ets_fifoOver= fp->fxp_stat.sc_rx_overrun;
|
|
stats.ets_CDheartbeat= 0;
|
|
stats.ets_OWC= fp->fxp_stat.sc_tx_latecol;
|
|
|
|
r= sys_safecopyto(mp->DL_ENDPT, mp->DL_GRANT, 0, (vir_bytes)&stats,
|
|
sizeof(stats), D);
|
|
if (r != OK)
|
|
panic("fxp_getstat_s: sys_safecopyto failed: %d", r);
|
|
|
|
mp->m_type= DL_STAT_REPLY;
|
|
r= send(mp->m_source, mp);
|
|
if (r != OK)
|
|
panic("fxp_getstat_s: send failed: %d", r);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_handler *
|
|
*===========================================================================*/
|
|
static void fxp_handler(fxp_t *fp)
|
|
{
|
|
int port;
|
|
u16_t isr;
|
|
|
|
RAND_UPDATE
|
|
|
|
port= fp->fxp_base_port;
|
|
|
|
/* Ack interrupt */
|
|
isr= fxp_inb(port, SCB_INT_STAT);
|
|
fxp_outb(port, SCB_INT_STAT, isr);
|
|
|
|
if (isr & SIS_FR)
|
|
{
|
|
isr &= ~SIS_FR;
|
|
|
|
if (!fp->fxp_got_int && (fp->fxp_flags & FF_READING))
|
|
{
|
|
fp->fxp_got_int= TRUE;
|
|
interrupt(fxp_tasknr);
|
|
}
|
|
}
|
|
if (isr & SIS_CNA)
|
|
{
|
|
isr &= ~SIS_CNA;
|
|
if (!fp->fxp_tx_idle)
|
|
{
|
|
fp->fxp_send_int= TRUE;
|
|
if (!fp->fxp_got_int)
|
|
{
|
|
fp->fxp_got_int= TRUE;
|
|
interrupt(fxp_tasknr);
|
|
}
|
|
}
|
|
}
|
|
if (isr & SIS_RNR)
|
|
{
|
|
isr &= ~SIS_RNR;
|
|
|
|
/* Assume that receive buffer is full of packets. fxp_readv
|
|
* will restart the RU.
|
|
*/
|
|
fp->fxp_rx_need_restart= 1;
|
|
}
|
|
if (isr)
|
|
{
|
|
printf("fxp_handler: unhandled interrupt: isr = 0x%02x\n",
|
|
isr);
|
|
}
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_check_ints *
|
|
*===========================================================================*/
|
|
static void fxp_check_ints(fxp_t *fp)
|
|
{
|
|
int n, fxp_flags, prev_tail;
|
|
int fxp_tx_tail, fxp_tx_nbuf, fxp_tx_threshold;
|
|
port_t port;
|
|
u32_t busaddr;
|
|
u16_t tx_status;
|
|
u8_t scb_status;
|
|
struct tx *txp;
|
|
|
|
fxp_flags= fp->fxp_flags;
|
|
|
|
if (fxp_flags & FF_READING)
|
|
{
|
|
if (!(fp->fxp_rx_buf[fp->fxp_rx_head].rfd_status & RFDS_C))
|
|
; /* Nothing */
|
|
else
|
|
{
|
|
fxp_readv_s(&fp->fxp_rx_mess, TRUE /* from int */);
|
|
}
|
|
}
|
|
if (fp->fxp_tx_idle)
|
|
; /* Nothing to do */
|
|
else if (fp->fxp_send_int)
|
|
{
|
|
fp->fxp_send_int= FALSE;
|
|
fxp_tx_tail= fp->fxp_tx_tail;
|
|
fxp_tx_nbuf= fp->fxp_tx_nbuf;
|
|
n= 0;
|
|
for (;;)
|
|
{
|
|
txp= &fp->fxp_tx_buf[fxp_tx_tail];
|
|
tx_status= txp->tx_status;
|
|
if (!(tx_status & TXS_C))
|
|
break;
|
|
|
|
n++;
|
|
|
|
assert(tx_status & TXS_OK);
|
|
if (tx_status & TXS_U)
|
|
{
|
|
fxp_tx_threshold= fp->fxp_tx_threshold;
|
|
if (fxp_tx_threshold < TXTT_MAX)
|
|
{
|
|
fxp_tx_threshold++;
|
|
fp->fxp_tx_threshold= fxp_tx_threshold;
|
|
}
|
|
printf(
|
|
"fxp_check_ints: fxp_tx_threshold = 0x%x\n",
|
|
fxp_tx_threshold);
|
|
}
|
|
|
|
if (txp->tx_command & TXC_EL)
|
|
{
|
|
fp->fxp_tx_idle= 1;
|
|
break;
|
|
}
|
|
|
|
fxp_tx_tail++;
|
|
if (fxp_tx_tail == fxp_tx_nbuf)
|
|
fxp_tx_tail= 0;
|
|
assert(fxp_tx_tail < fxp_tx_nbuf);
|
|
}
|
|
|
|
if (fp->fxp_need_conf)
|
|
{
|
|
/* Check the status of the CU */
|
|
port= fp->fxp_base_port;
|
|
scb_status= fxp_inb(port, SCB_STATUS);
|
|
if ((scb_status & SS_CUS_MASK) != SS_CU_IDLE)
|
|
{
|
|
/* Nothing to do */
|
|
printf("scb_status = 0x%x\n", scb_status);
|
|
}
|
|
else
|
|
{
|
|
printf("fxp_check_ints: fxp_need_conf\n");
|
|
fp->fxp_need_conf= FALSE;
|
|
fxp_do_conf(fp);
|
|
}
|
|
}
|
|
|
|
if (n)
|
|
{
|
|
if (!fp->fxp_tx_idle)
|
|
{
|
|
fp->fxp_tx_tail= fxp_tx_tail;
|
|
|
|
/* Check the status of the CU */
|
|
port= fp->fxp_base_port;
|
|
scb_status= fxp_inb(port, SCB_STATUS);
|
|
if ((scb_status & SS_CUS_MASK) != SS_CU_IDLE)
|
|
{
|
|
/* Nothing to do */
|
|
printf("scb_status = 0x%x\n",
|
|
scb_status);
|
|
|
|
}
|
|
else
|
|
{
|
|
if (fxp_tx_tail == 0)
|
|
prev_tail= fxp_tx_nbuf-1;
|
|
else
|
|
prev_tail= fxp_tx_tail-1;
|
|
busaddr= fp->fxp_tx_buf[prev_tail].
|
|
tx_linkaddr;
|
|
|
|
fxp_cu_ptr_cmd(fp, SC_CU_START,
|
|
busaddr, 1 /* check idle */);
|
|
}
|
|
}
|
|
|
|
if (fp->fxp_flags & FF_SEND_AVAIL)
|
|
{
|
|
fxp_writev_s(&fp->fxp_tx_mess,
|
|
TRUE /* from int */);
|
|
}
|
|
}
|
|
|
|
}
|
|
if (fp->fxp_report_link)
|
|
fxp_report_link(fp);
|
|
|
|
if (fp->fxp_flags & (FF_PACK_SENT | FF_PACK_RECV))
|
|
reply(fp);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_watchdog_f *
|
|
*===========================================================================*/
|
|
static void fxp_watchdog_f(tp)
|
|
timer_t *tp;
|
|
{
|
|
fxp_t *fp;
|
|
|
|
tmr_arg(&fxp_watchdog)->ta_int= 0;
|
|
fxp_set_timer(&fxp_watchdog, system_hz, fxp_watchdog_f);
|
|
|
|
fp= fxp_state;
|
|
if (fp->fxp_mode != FM_ENABLED)
|
|
return;
|
|
|
|
/* Handle race condition, MII interface might be busy */
|
|
if(!fp->fxp_mii_busy)
|
|
{
|
|
/* Check the link status. */
|
|
if (fxp_link_changed(fp))
|
|
{
|
|
#if VERBOSE
|
|
printf("fxp_watchdog_f: link changed\n");
|
|
#endif
|
|
fp->fxp_report_link= TRUE;
|
|
fp->fxp_got_int= TRUE;
|
|
interrupt(fxp_tasknr);
|
|
}
|
|
}
|
|
|
|
if (!(fp->fxp_flags & FF_SEND_AVAIL))
|
|
{
|
|
/* Assume that an idle system is alive */
|
|
fp->fxp_tx_alive= TRUE;
|
|
return;
|
|
}
|
|
if (fp->fxp_tx_alive)
|
|
{
|
|
fp->fxp_tx_alive= FALSE;
|
|
return;
|
|
}
|
|
|
|
fp->fxp_need_reset= TRUE;
|
|
fp->fxp_got_int= TRUE;
|
|
interrupt(fxp_tasknr);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_link_changed *
|
|
*===========================================================================*/
|
|
static int fxp_link_changed(fp)
|
|
fxp_t *fp;
|
|
{
|
|
u16_t scr;
|
|
|
|
scr= mii_read(fp, MII_SCR);
|
|
scr &= ~(MII_SCR_RES|MII_SCR_RES_1);
|
|
|
|
return (fp->fxp_mii_scr != scr);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_report_link *
|
|
*===========================================================================*/
|
|
static void fxp_report_link(fxp_t *fp)
|
|
{
|
|
u16_t mii_ctrl, mii_status, mii_id1, mii_id2,
|
|
mii_ana, mii_anlpa, mii_ane, mii_extstat,
|
|
mii_ms_ctrl, mii_ms_status, scr;
|
|
u32_t oui;
|
|
int model, rev;
|
|
int f, link_up, ms_regs;
|
|
|
|
/* Assume an 82555 (compatible) PHY. The should be changed for
|
|
* 82557 NICs with different PHYs
|
|
*/
|
|
ms_regs= 0; /* No master/slave registers. */
|
|
|
|
fp->fxp_report_link= FALSE;
|
|
|
|
scr= mii_read(fp, MII_SCR);
|
|
scr &= ~(MII_SCR_RES|MII_SCR_RES_1);
|
|
fp->fxp_mii_scr= scr;
|
|
|
|
mii_ctrl= mii_read(fp, MII_CTRL);
|
|
mii_read(fp, MII_STATUS); /* The status reg is latched, read twice */
|
|
mii_status= mii_read(fp, MII_STATUS);
|
|
mii_id1= mii_read(fp, MII_PHYID_H);
|
|
mii_id2= mii_read(fp, MII_PHYID_L);
|
|
mii_ana= mii_read(fp, MII_ANA);
|
|
mii_anlpa= mii_read(fp, MII_ANLPA);
|
|
mii_ane= mii_read(fp, MII_ANE);
|
|
if (mii_status & MII_STATUS_EXT_STAT)
|
|
mii_extstat= mii_read(fp, MII_EXT_STATUS);
|
|
else
|
|
mii_extstat= 0;
|
|
if (ms_regs)
|
|
{
|
|
mii_ms_ctrl= mii_read(fp, MII_MS_CTRL);
|
|
mii_ms_status= mii_read(fp, MII_MS_STATUS);
|
|
}
|
|
else
|
|
{
|
|
mii_ms_ctrl= 0;
|
|
mii_ms_status= 0;
|
|
}
|
|
|
|
/* How do we know about the link status? */
|
|
link_up= !!(mii_status & MII_STATUS_LS);
|
|
|
|
fp->fxp_link_up= link_up;
|
|
if (!link_up)
|
|
{
|
|
#if VERBOSE
|
|
printf("%s: link down\n", fp->fxp_name);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
oui= (mii_id1 << MII_PH_OUI_H_C_SHIFT) |
|
|
((mii_id2 & MII_PL_OUI_L_MASK) >> MII_PL_OUI_L_SHIFT);
|
|
model= ((mii_id2 & MII_PL_MODEL_MASK) >> MII_PL_MODEL_SHIFT);
|
|
rev= (mii_id2 & MII_PL_REV_MASK);
|
|
|
|
#if VERBOSE
|
|
printf("OUI 0x%06lx, Model 0x%02x, Revision 0x%x\n", oui, model, rev);
|
|
#endif
|
|
|
|
if (mii_ctrl & (MII_CTRL_LB|MII_CTRL_PD|MII_CTRL_ISO))
|
|
{
|
|
printf("%s: PHY: ", fp->fxp_name);
|
|
f= 1;
|
|
if (mii_ctrl & MII_CTRL_LB)
|
|
{
|
|
printf("loopback mode");
|
|
f= 0;
|
|
}
|
|
if (mii_ctrl & MII_CTRL_PD)
|
|
{
|
|
if (!f) printf(", ");
|
|
f= 0;
|
|
printf("powered down");
|
|
}
|
|
if (mii_ctrl & MII_CTRL_ISO)
|
|
{
|
|
if (!f) printf(", ");
|
|
f= 0;
|
|
printf("isolated");
|
|
}
|
|
printf("\n");
|
|
return;
|
|
}
|
|
if (!(mii_ctrl & MII_CTRL_ANE))
|
|
{
|
|
printf("%s: manual config: ", fp->fxp_name);
|
|
switch(mii_ctrl & (MII_CTRL_SP_LSB|MII_CTRL_SP_MSB))
|
|
{
|
|
case MII_CTRL_SP_10: printf("10 Mbps"); break;
|
|
case MII_CTRL_SP_100: printf("100 Mbps"); break;
|
|
case MII_CTRL_SP_1000: printf("1000 Mbps"); break;
|
|
case MII_CTRL_SP_RES: printf("reserved speed"); break;
|
|
}
|
|
if (mii_ctrl & MII_CTRL_DM)
|
|
printf(", full duplex");
|
|
else
|
|
printf(", half duplex");
|
|
printf("\n");
|
|
return;
|
|
}
|
|
|
|
if (!debug) goto resspeed;
|
|
|
|
printf("%s: ", fp->fxp_name);
|
|
mii_print_stat_speed(mii_status, mii_extstat);
|
|
printf("\n");
|
|
|
|
if (!(mii_status & MII_STATUS_ANC))
|
|
printf("%s: auto-negotiation not complete\n", fp->fxp_name);
|
|
if (mii_status & MII_STATUS_RF)
|
|
printf("%s: remote fault detected\n", fp->fxp_name);
|
|
if (!(mii_status & MII_STATUS_ANA))
|
|
{
|
|
printf("%s: local PHY has no auto-negotiation ability\n",
|
|
fp->fxp_name);
|
|
}
|
|
if (!(mii_status & MII_STATUS_LS))
|
|
printf("%s: link down\n", fp->fxp_name);
|
|
if (mii_status & MII_STATUS_JD)
|
|
printf("%s: jabber condition detected\n", fp->fxp_name);
|
|
if (!(mii_status & MII_STATUS_EC))
|
|
{
|
|
printf("%s: no extended register set\n", fp->fxp_name);
|
|
goto resspeed;
|
|
}
|
|
if (!(mii_status & MII_STATUS_ANC))
|
|
goto resspeed;
|
|
|
|
printf("%s: local cap.: ", fp->fxp_name);
|
|
if (mii_ms_ctrl & (MII_MSC_1000T_FD | MII_MSC_1000T_HD))
|
|
{
|
|
printf("1000 Mbps: T-");
|
|
switch(mii_ms_ctrl & (MII_MSC_1000T_FD | MII_MSC_1000T_HD))
|
|
{
|
|
case MII_MSC_1000T_FD: printf("FD"); break;
|
|
case MII_MSC_1000T_HD: printf("HD"); break;
|
|
default: printf("FD/HD"); break;
|
|
}
|
|
if (mii_ana)
|
|
printf(", ");
|
|
}
|
|
mii_print_techab(mii_ana);
|
|
printf("\n");
|
|
|
|
if (mii_ane & MII_ANE_PDF)
|
|
printf("%s: parallel detection fault\n", fp->fxp_name);
|
|
if (!(mii_ane & MII_ANE_LPANA))
|
|
{
|
|
printf("%s: link-partner does not support auto-negotiation\n",
|
|
fp->fxp_name);
|
|
goto resspeed;
|
|
}
|
|
|
|
printf("%s: remote cap.: ", fp->fxp_name);
|
|
if (mii_ms_ctrl & (MII_MSC_1000T_FD | MII_MSC_1000T_HD))
|
|
if (mii_ms_status & (MII_MSS_LP1000T_FD | MII_MSS_LP1000T_HD))
|
|
{
|
|
printf("1000 Mbps: T-");
|
|
switch(mii_ms_status &
|
|
(MII_MSS_LP1000T_FD | MII_MSS_LP1000T_HD))
|
|
{
|
|
case MII_MSS_LP1000T_FD: printf("FD"); break;
|
|
case MII_MSS_LP1000T_HD: printf("HD"); break;
|
|
default: printf("FD/HD"); break;
|
|
}
|
|
if (mii_anlpa)
|
|
printf(", ");
|
|
}
|
|
mii_print_techab(mii_anlpa);
|
|
printf("\n");
|
|
|
|
if (ms_regs)
|
|
{
|
|
printf("%s: ", fp->fxp_name);
|
|
if (mii_ms_ctrl & MII_MSC_MS_MANUAL)
|
|
{
|
|
printf("manual %s",
|
|
(mii_ms_ctrl & MII_MSC_MS_VAL) ?
|
|
"MASTER" : "SLAVE");
|
|
}
|
|
else
|
|
{
|
|
printf("%s device",
|
|
(mii_ms_ctrl & MII_MSC_MULTIPORT) ?
|
|
"multiport" : "single-port");
|
|
}
|
|
if (mii_ms_ctrl & MII_MSC_RES)
|
|
printf(" reserved<0x%x>", mii_ms_ctrl & MII_MSC_RES);
|
|
printf(": ");
|
|
if (mii_ms_status & MII_MSS_FAULT)
|
|
printf("M/S config fault");
|
|
else if (mii_ms_status & MII_MSS_MASTER)
|
|
printf("MASTER");
|
|
else
|
|
printf("SLAVE");
|
|
printf("\n");
|
|
}
|
|
|
|
if (mii_ms_status & (MII_MSS_LP1000T_FD|MII_MSS_LP1000T_HD))
|
|
{
|
|
if (!(mii_ms_status & MII_MSS_LOCREC))
|
|
{
|
|
printf("%s: local receiver not OK\n",
|
|
fp->fxp_name);
|
|
}
|
|
if (!(mii_ms_status & MII_MSS_REMREC))
|
|
{
|
|
printf("%s: remote receiver not OK\n",
|
|
fp->fxp_name);
|
|
}
|
|
}
|
|
if (mii_ms_status & (MII_MSS_RES|MII_MSS_IDLE_ERR))
|
|
{
|
|
printf("%s", fp->fxp_name);
|
|
if (mii_ms_status & MII_MSS_RES)
|
|
printf(" reserved<0x%x>", mii_ms_status & MII_MSS_RES);
|
|
if (mii_ms_status & MII_MSS_IDLE_ERR)
|
|
{
|
|
printf(" idle error %d",
|
|
mii_ms_status & MII_MSS_IDLE_ERR);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
resspeed:
|
|
#if VERBOSE
|
|
printf("%s: link up, %d Mbps, %s duplex\n",
|
|
fp->fxp_name, (scr & MII_SCR_100) ? 100 : 10,
|
|
(scr & MII_SCR_FD) ? "full" : "half");
|
|
#endif
|
|
;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* reply *
|
|
*===========================================================================*/
|
|
static void reply(fp)
|
|
fxp_t *fp;
|
|
{
|
|
message reply;
|
|
int flags;
|
|
int r;
|
|
|
|
flags = DL_NOFLAGS;
|
|
if (fp->fxp_flags & FF_PACK_SENT)
|
|
flags |= DL_PACK_SEND;
|
|
if (fp->fxp_flags & FF_PACK_RECV)
|
|
flags |= DL_PACK_RECV;
|
|
|
|
reply.m_type = DL_TASK_REPLY;
|
|
reply.DL_FLAGS = flags;
|
|
reply.DL_COUNT = fp->fxp_read_s;
|
|
|
|
r= send(fp->fxp_client, &reply);
|
|
|
|
if (r < 0)
|
|
panic("fxp: send failed: %d", r);
|
|
|
|
fp->fxp_read_s = 0;
|
|
fp->fxp_flags &= ~(FF_PACK_SENT | FF_PACK_RECV);
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* mess_reply *
|
|
*===========================================================================*/
|
|
static void mess_reply(req, reply_mess)
|
|
message *req;
|
|
message *reply_mess;
|
|
{
|
|
if (send(req->m_source, reply_mess) != OK)
|
|
panic("fxp: unable to mess_reply");
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* eeprom_read *
|
|
*===========================================================================*/
|
|
PRIVATE u16_t eeprom_read(fp, reg)
|
|
fxp_t *fp;
|
|
int reg;
|
|
{
|
|
port_t port;
|
|
u16_t v;
|
|
int b, i, alen;
|
|
|
|
alen= fp->fxp_ee_addrlen;
|
|
if (!alen)
|
|
{
|
|
eeprom_addrsize(fp);
|
|
alen= fp->fxp_ee_addrlen;
|
|
assert(alen == 6 || alen == 8);
|
|
}
|
|
|
|
port= fp->fxp_base_port;
|
|
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS); /* Enable EEPROM */
|
|
v= EEPROM_READ_PREFIX;
|
|
for (i= EEPROM_PREFIX_LEN-1; i >= 0; i--)
|
|
{
|
|
b= ((v & (1 << i)) ? CE_EEDI : 0);
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b); /* bit */
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b | CE_EESK); /* Clock */
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b);
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
}
|
|
|
|
v= reg;
|
|
for (i= alen-1; i >= 0; i--)
|
|
{
|
|
b= ((v & (1 << i)) ? CE_EEDI : 0);
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b); /* bit */
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b | CE_EESK); /* Clock */
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b);
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
}
|
|
|
|
v= 0;
|
|
for (i= 0; i<16; i++)
|
|
{
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | CE_EESK); /* Clock */
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
b= !!(fxp_inb(port, CSR_EEPROM) & CE_EEDO);
|
|
v= (v << 1) | b;
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS );
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
}
|
|
fxp_outb(port, CSR_EEPROM, 0); /* Disable EEPROM */
|
|
micro_delay(EECS_DELAY);
|
|
|
|
return v;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* eeprom_addrsize *
|
|
*===========================================================================*/
|
|
PRIVATE void eeprom_addrsize(fp)
|
|
fxp_t *fp;
|
|
{
|
|
port_t port;
|
|
u16_t v;
|
|
int b, i;
|
|
|
|
port= fp->fxp_base_port;
|
|
|
|
/* Try to find out the size of the EEPROM */
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS); /* Enable EEPROM */
|
|
v= EEPROM_READ_PREFIX;
|
|
for (i= EEPROM_PREFIX_LEN-1; i >= 0; i--)
|
|
{
|
|
b= ((v & (1 << i)) ? CE_EEDI : 0);
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b); /* bit */
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b | CE_EESK); /* Clock */
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b);
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
}
|
|
|
|
for (i= 0; i<32; i++)
|
|
{
|
|
b= 0;
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b); /* bit */
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b | CE_EESK); /* Clock */
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | b);
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
v= fxp_inb(port, CSR_EEPROM);
|
|
if (!(v & CE_EEDO))
|
|
break;
|
|
}
|
|
if (i >= 32)
|
|
panic("eeprom_addrsize: failed");
|
|
fp->fxp_ee_addrlen= i+1;
|
|
|
|
/* Discard 16 data bits */
|
|
for (i= 0; i<16; i++)
|
|
{
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS | CE_EESK); /* Clock */
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
fxp_outb(port, CSR_EEPROM, CE_EECS );
|
|
micro_delay(EESK_PERIOD/2+1);
|
|
}
|
|
fxp_outb(port, CSR_EEPROM, 0); /* Disable EEPROM */
|
|
micro_delay(EECS_DELAY);
|
|
|
|
#if VERBOSE
|
|
printf("%s EEPROM address length: %d\n",
|
|
fp->fxp_name, fp->fxp_ee_addrlen);
|
|
#endif
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* mii_read *
|
|
*===========================================================================*/
|
|
PRIVATE u16_t mii_read(fp, reg)
|
|
fxp_t *fp;
|
|
int reg;
|
|
{
|
|
clock_t t0,t1;
|
|
port_t port;
|
|
u32_t v;
|
|
|
|
port= fp->fxp_base_port;
|
|
|
|
assert(!fp->fxp_mii_busy);
|
|
fp->fxp_mii_busy++;
|
|
|
|
if (!(fxp_inl(port, CSR_MDI_CTL) & CM_READY))
|
|
panic("mii_read: MDI not ready");
|
|
fxp_outl(port, CSR_MDI_CTL, CM_READ | (1 << CM_PHYADDR_SHIFT) |
|
|
(reg << CM_REG_SHIFT));
|
|
|
|
getuptime(&t0);
|
|
do {
|
|
v= fxp_inl(port, CSR_MDI_CTL);
|
|
if (v & CM_READY)
|
|
break;
|
|
} while (getuptime(&t1)==OK && (t1-t0) < micros_to_ticks(100000));
|
|
|
|
if (!(v & CM_READY))
|
|
panic("mii_read: MDI not ready after command");
|
|
|
|
fp->fxp_mii_busy--;
|
|
assert(!fp->fxp_mii_busy);
|
|
|
|
return v & CM_DATA_MASK;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_set_timer *
|
|
*===========================================================================*/
|
|
PRIVATE void fxp_set_timer(tp, delta, watchdog)
|
|
timer_t *tp; /* timer to be set */
|
|
clock_t delta; /* in how many ticks */
|
|
tmr_func_t watchdog; /* watchdog function to be called */
|
|
{
|
|
clock_t now; /* current time */
|
|
int r;
|
|
|
|
/* Get the current time. */
|
|
r= getuptime(&now);
|
|
if (r != OK)
|
|
panic("unable to get uptime from clock: %d", r);
|
|
|
|
/* Add the timer to the local timer queue. */
|
|
tmrs_settimer(&fxp_timers, tp, now + delta, watchdog, NULL);
|
|
|
|
/* Possibly reschedule an alarm call. This happens when a new timer
|
|
* is added in front.
|
|
*/
|
|
if (fxp_next_timeout == 0 ||
|
|
fxp_timers->tmr_exp_time < fxp_next_timeout)
|
|
{
|
|
fxp_next_timeout= fxp_timers->tmr_exp_time;
|
|
#if VERBOSE
|
|
printf("fxp_set_timer: calling sys_setalarm for %d (now+%d)\n",
|
|
fxp_next_timeout, fxp_next_timeout-now);
|
|
#endif
|
|
r= sys_setalarm(fxp_next_timeout, 1);
|
|
if (r != OK)
|
|
panic("unable to set synchronous alarm: %d", r);
|
|
}
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* fxp_expire_tmrs *
|
|
*===========================================================================*/
|
|
PRIVATE void fxp_expire_timers()
|
|
{
|
|
/* A synchronous alarm message was received. Check if there are any expired
|
|
* timers. Possibly reschedule the next alarm.
|
|
*/
|
|
clock_t now; /* current time */
|
|
int r;
|
|
|
|
/* Get the current time to compare the timers against. */
|
|
r= getuptime(&now);
|
|
if (r != OK)
|
|
panic("Unable to get uptime from clock: %d", r);
|
|
|
|
/* Scan the timers queue for expired timers. Dispatch the watchdog function
|
|
* for each expired timers. Possibly a new alarm call must be scheduled.
|
|
*/
|
|
tmrs_exptimers(&fxp_timers, now, NULL);
|
|
if (fxp_timers == NULL)
|
|
fxp_next_timeout= TMR_NEVER;
|
|
else
|
|
{ /* set new alarm */
|
|
fxp_next_timeout = fxp_timers->tmr_exp_time;
|
|
r= sys_setalarm(fxp_next_timeout, 1);
|
|
if (r != OK)
|
|
panic("Unable to set synchronous alarm: %d", r);
|
|
}
|
|
}
|
|
|
|
static u8_t do_inb(port_t port)
|
|
{
|
|
int r;
|
|
u32_t value;
|
|
|
|
r= sys_inb(port, &value);
|
|
if (r != OK)
|
|
panic("sys_inb failed: %d", r);
|
|
return value;
|
|
}
|
|
|
|
static u32_t do_inl(port_t port)
|
|
{
|
|
int r;
|
|
u32_t value;
|
|
|
|
r= sys_inl(port, &value);
|
|
if (r != OK)
|
|
panic("sys_inl failed: %d", r);
|
|
return value;
|
|
}
|
|
|
|
static void do_outb(port_t port, u8_t value)
|
|
{
|
|
int r;
|
|
|
|
r= sys_outb(port, value);
|
|
if (r != OK)
|
|
panic("sys_outb failed: %d", r);
|
|
}
|
|
|
|
static void do_outl(port_t port, u32_t value)
|
|
{
|
|
int r;
|
|
|
|
r= sys_outl(port, value);
|
|
if (r != OK)
|
|
panic("sys_outl failed: %d", r);
|
|
}
|
|
|
|
PRIVATE void tell_dev(buf, size, pci_bus, pci_dev, pci_func)
|
|
vir_bytes buf;
|
|
size_t size;
|
|
int pci_bus;
|
|
int pci_dev;
|
|
int pci_func;
|
|
{
|
|
int r;
|
|
endpoint_t dev_e;
|
|
message m;
|
|
|
|
r= ds_retrieve_label_endpt("amddev", &dev_e);
|
|
if (r != OK)
|
|
{
|
|
#if 0
|
|
printf(
|
|
"fxp`tell_dev: ds_retrieve_label_endpt failed for 'amddev': %d\n",
|
|
r);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
m.m_type= IOMMU_MAP;
|
|
m.m2_i1= pci_bus;
|
|
m.m2_i2= pci_dev;
|
|
m.m2_i3= pci_func;
|
|
m.m2_l1= buf;
|
|
m.m2_l2= size;
|
|
|
|
r= sendrec(dev_e, &m);
|
|
if (r != OK)
|
|
{
|
|
printf("fxp`tell_dev: sendrec to %d failed: %d\n",
|
|
dev_e, r);
|
|
return;
|
|
}
|
|
if (m.m_type != OK)
|
|
{
|
|
printf("fxp`tell_dev: dma map request failed: %d\n",
|
|
m.m_type);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* $PchId: fxp.c,v 1.4 2005/01/31 22:10:37 philip Exp $
|
|
*/
|
|
|