blob: f86d6380e8fd9f56c0dc17eb6f9005b0634db0af [file] [log] [blame] [raw]
/* pdp11_xu.c: DEUNA/DELUA ethernet controller simulator
------------------------------------------------------------------------------
Copyright (c) 2003-2011, David T. Hittner
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of the author shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from the author.
------------------------------------------------------------------------------
This DEUNA/DELUA simulation is based on:
Digital DELUA Users Guide, Part# EK-DELUA-UG-002
Digital DEUNA Users Guide, Part# EK-DEUNA-UG-001
These manuals can be found online at:
http://www.bitsavers.org/pdf/dec/unibus
Testing performed:
1) Receives/Transmits single packet under custom RSX driver
2) Passes RSTS 10.1 controller probe diagnostics during boot
3) VMS 7.2 on VAX780 summary:
(May/2007: WinXP x64 host; MS VC++ 2005; SIMH v3.7-0 base; WinPcap 4.0)
LAT - SET HOST/LAT in/out
DECNET - SET HOST in/out, COPY in/out
TCP/IP - PING in/out; SET HOST/TELNET in/out, COPY/FTP in/out
Clustering - Successfully clustered with AlphaVMS 8.2
4) VMS 4.7 on VAX780 summary:
(Jan/2011: Win7 x64 host; MS VC++ 2008; SIMH v3.8-2 rc1 base; WinPcap 4.1)
LAT - SET HOST/LAT in (no outbound exists)
DECNET - SET HOST in/out, DIR in/out, COPY in/out
TCP/IP - no kit available to test
Clustering - not tested
5) Runs VAX EVDWA diagnostic tests 1-10; tests 11-19 (M68000/ROM/RAM) fail
Known issues:
1) Most auxiliary commands are not implemented yet.
2) System_ID broadcast is not implemented.
3) There are residual Map_ReadB and Map_WriteB from the FvK version that
probably need to be converted to Map_ReadW and Map_WriteW calls.
4) Some jerkiness seen during interactive I/O with remote systems;
this is probably attributable to changed polling times from when
the poll duration was standardized for idling support.
------------------------------------------------------------------------------
Modification history:
25-Jan-13 RJ SELFTEST needs to report the READY state otherwise VMS 3.7 gets fatal controller error
12-Jan-11 DTH Added SHOW XU FILTERS modifier
11-Jan-11 DTH Corrected SELFTEST command, enabling use by VMS 3.7, VMS 4.7, and Ultrix 1.1
09-Dec-10 MP Added address conflict check during attach.
06-Dec-10 MP Added loopback processing support
30-Nov-10 MP Fixed the fact that no broadcast packets were received by the DEUNA
15-Aug-08 MP Fixed transmitted packets to have the correct source MAC address.
Fixed incorrect address filter setting calling eth_filter().
23-Jan-08 MP Added debugging support to display packet headers and packet data
18-Jun-07 RMS Added UNIT_IDLE flag
03-May-07 DTH Added missing FC_RMAL command; cleared multicast on write
29-Oct-06 RMS Synced poll and clock
08-Dec-05 DTH Implemented ancilliary functions 022/023/024/025
18-Nov-05 DTH Corrected time between system ID packets
07-Sep-05 DTH Corrected runt packet processing (found by Tim Chapman),
Removed unused variable
16-Aug-05 RMS Fixed C++ declaration and cast problems
10-Mar-05 RMS Fixed equality test in RCSTAT (from Mark Hittinger)
16-Jan-04 DTH Added more info to SHOW MOD commands
09-Jan-04 DTH Made XU floating address so that XUB will float correctly
08-Jan-04 DTH Added system_id message
06-Jan-04 DTH Added protection against changing mac and type if attached
05-Jan-04 DTH Moved most of xu_setmac to sim_ether
Implemented auxiliary function 12/13
Added SET/SHOW XU STATS
31-Dec-03 DTH RSTS 10.1 accepts controller during boot tests
Implemented chained buffers in transmit/receive processing
29-Dec-03 DTH Primitive RSX packet sending succeeds
23-Dec-03 DTH Implemented write function
17-Dec-03 DTH Implemented read function
05-May-03 DTH Started XU simulation -
core logic pirated from unreleased FvK PDP10 variant
------------------------------------------------------------------------------
*/
#include "pdp11_xu.h"
extern int32 tmxr_poll;
t_stat xu_rd(int32* data, int32 PA, int32 access);
t_stat xu_wr(int32 data, int32 PA, int32 access);
t_stat xu_svc(UNIT * uptr);
t_stat xu_tmrsvc(UNIT * uptr);
t_stat xu_reset (DEVICE * dptr);
t_stat xu_attach (UNIT * uptr, CONST char * cptr);
t_stat xu_detach (UNIT * uptr);
t_stat xu_showmac (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat xu_setmac (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat xu_show_stats (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat xu_set_stats (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat xu_show_type (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat xu_set_type (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat xu_show_throttle (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat xu_set_throttle (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
int32 xu_int (void);
t_stat xu_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw);
t_stat xu_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw);
void xua_read_callback(int status);
void xub_read_callback(int status);
void xua_write_callback(int status);
void xub_write_callback(int status);
void xu_setint (CTLR* xu);
void xu_clrint (CTLR* xu);
void xu_process_receive(CTLR* xu);
void xu_dump_rxring(CTLR* xu);
void xu_dump_txring(CTLR* xu);
t_stat xu_show_filters (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat xu_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
const char *xu_description (DEVICE *dptr);
#define IOLN_XU 010
DIB xua_dib = { IOBA_AUTO, IOLN_XU, &xu_rd, &xu_wr,
1, IVCL (XU), VEC_AUTO, {&xu_int}, IOLN_XU };
UNIT xua_unit[] = {
{ UDATA (&xu_svc, UNIT_IDLE|UNIT_ATTABLE, 0) }, /* receive timer */
{ UDATA (&xu_tmrsvc, UNIT_IDLE|UNIT_DIS, 0) }
};
struct xu_device xua = {
xua_read_callback, /* read callback routine */
xua_write_callback, /* write callback routine */
{0x08, 0x00, 0x2B, 0xCC, 0xDD, 0xEE}, /* mac */
XU_T_DELUA, /* type */
ETH_THROT_DEFAULT_TIME, /* ms throttle window */
ETH_THROT_DEFAULT_BURST, /* packet packet burst in throttle window */
ETH_THROT_DISABLED_DELAY /* throttle disabled */
};
MTAB xu_mod[] = {
#if defined (VM_PDP11)
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 010, "ADDRESS", "ADDRESS",
&set_addr, &show_addr, NULL },
{ MTAB_XTD|MTAB_VDV, 0, NULL, "AUTOCONFIGURE",
&set_addr_flt, NULL, NULL },
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "VECTOR", NULL,
&set_vec, &show_vec, NULL },
#else
{ MTAB_XTD|MTAB_VDV, 0, "ADDRESS", NULL,
NULL, &show_addr, NULL, "Unibus address" },
{ MTAB_XTD|MTAB_VDV, 0, "VECTOR", NULL,
NULL, &show_vec, NULL, "Interrupt vector" },
#endif
{ MTAB_XTD|MTAB_VDV|MTAB_VALR|MTAB_NC, 0, "MAC", "MAC=xx:xx:xx:xx:xx:xx",
&xu_setmac, &xu_showmac, NULL, "MAC address" },
{ MTAB_XTD |MTAB_VDV|MTAB_NMO, 0, "ETH", NULL,
NULL, &eth_show, NULL, "Display attachable devices" },
{ MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "STATS", "STATS",
&xu_set_stats, &xu_show_stats, NULL, "Display or reset statistics" },
{ MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "FILTERS", NULL,
NULL, &xu_show_filters, NULL, "Display MAC addresses which will be received" },
{ MTAB_XTD|MTAB_VDV, 0, "TYPE", "TYPE={DEUNA|DELUA}",
&xu_set_type, &xu_show_type, NULL, "Display the controller type" },
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "THROTTLE", "THROTTLE=DISABLED|TIME=n{;BURST=n{;DELAY=n}}",
&xu_set_throttle, &xu_show_throttle, NULL, "Display transmit throttle configuration" },
{ 0 },
};
REG xua_reg[] = {
{ GRDATA ( SA0, xua.mac[0], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( SA1, xua.mac[1], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( SA2, xua.mac[2], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( SA3, xua.mac[3], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( SA4, xua.mac[4], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( SA5, xua.mac[5], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( TYPE, xua.type, XU_RDX, 32, 0), REG_FIT },
{ FLDATA ( INT, xua.irq, 0) },
{ GRDATA ( IDTMR, xua.idtmr, XU_RDX, 32, 0), REG_HRO},
{ BRDATA ( SETUP, &xua.setup, XU_RDX, 8, sizeof(xua.setup)), REG_HRO},
{ BRDATA ( STATS, &xua.stats, XU_RDX, 8, sizeof(xua.stats)), REG_HRO},
{ GRDATA ( CSR0, xua.pcsr0, XU_RDX, 16, 0), REG_FIT },
{ GRDATA ( CSR1, xua.pcsr1, XU_RDX, 16, 0), REG_FIT },
{ GRDATA ( CSR2, xua.pcsr2, XU_RDX, 16, 0), REG_FIT },
{ GRDATA ( CSR3, xua.pcsr3, XU_RDX, 16, 0), REG_FIT },
{ GRDATA ( MODE, xua.mode, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( PCBB, xua.pcbb, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( STAT, xua.stat, XU_RDX, 16, 0), REG_FIT },
{ GRDATA ( TDRB, xua.tdrb, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( TELEN, xua.telen, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( TRLEN, xua.trlen, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( TXNEXT, xua.txnext, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( RDRB, xua.rdrb, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( RELEN, xua.relen, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( RRLEN, xua.rrlen, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( RXNEXT, xua.rxnext, XU_RDX, 32, 0), REG_FIT },
{ BRDATA ( PCB, xua.pcb, XU_RDX, 16, 4), REG_HRO},
{ BRDATA ( UDB, xua.udb, XU_RDX, 16, UDBSIZE), REG_HRO},
{ BRDATA ( RXHDR, xua.rxhdr, XU_RDX, 16, 4), REG_HRO},
{ BRDATA ( TXHDR, xua.txhdr, XU_RDX, 16, 4), REG_HRO},
{ GRDATA ( BA, xua_dib.ba, XU_RDX, 32, 0), REG_HRO},
{ GRDATA ( VECTOR, xua_dib.vec, XU_RDX, 32, 0), REG_HRO},
{ GRDATA ( THR_TIME, xua.throttle_time, XU_RDX, 32, 0), REG_HRO},
{ GRDATA ( THR_BURST, xua.throttle_burst, XU_RDX, 32, 0), REG_HRO},
{ GRDATA ( THR_DELAY, xua.throttle_delay, XU_RDX, 32, 0), REG_HRO},
{ NULL } };
DEBTAB xu_debug[] = {
{"TRACE", DBG_TRC, "trace routine calls"},
{"WARN", DBG_WRN, "warnings"},
{"REG", DBG_REG, "read/write registers"},
{"PACKET", DBG_PCK, "packet headers"},
{"DATA", DBG_DAT, "packet data"},
{"ETH", DBG_ETH, "ethernet device"},
{0}
};
DEVICE xu_dev = {
"XU", xua_unit, xua_reg, xu_mod,
2, XU_RDX, 8, 1, XU_RDX, 8,
&xu_ex, &xu_dep, &xu_reset,
NULL, &xu_attach, &xu_detach,
&xua_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG | DEV_ETHER,
0, xu_debug, NULL, NULL, &xu_help, NULL, NULL,
&xu_description
};
DIB xub_dib = { IOBA_AUTO, IOLN_XU, &xu_rd, &xu_wr,
1, IVCL (XU), 0, { &xu_int }, IOLN_XU };
UNIT xub_unit[] = {
{ UDATA (&xu_svc, UNIT_IDLE|UNIT_ATTABLE, 0) }, /* receive timer */
{ UDATA (&xu_tmrsvc, UNIT_IDLE|UNIT_DIS, 0) }
};
struct xu_device xub = {
xub_read_callback, /* read callback routine */
xub_write_callback, /* write callback routine */
{0x08, 0x00, 0x2B, 0xDD, 0xEE, 0xFF}, /* mac */
XU_T_DELUA, /* type */
ETH_THROT_DEFAULT_TIME, /* ms throttle window */
ETH_THROT_DEFAULT_BURST, /* packet packet burst in throttle window */
ETH_THROT_DISABLED_DELAY /* throttle disabled */
};
REG xub_reg[] = {
{ GRDATA ( SA0, xub.mac[0], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( SA1, xub.mac[1], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( SA2, xub.mac[2], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( SA3, xub.mac[3], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( SA4, xub.mac[4], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( SA5, xub.mac[5], 16, 8, 0), REG_RO|REG_FIT},
{ GRDATA ( TYPE, xub.type, XU_RDX, 32, 0), REG_FIT },
{ FLDATA ( INT, xub.irq, 0) },
{ GRDATA ( IDTMR, xub.idtmr, XU_RDX, 32, 0), REG_HRO},
{ BRDATA ( SETUP, &xub.setup, XU_RDX, 8, sizeof(xua.setup)), REG_HRO},
{ BRDATA ( STATS, &xub.stats, XU_RDX, 8, sizeof(xua.stats)), REG_HRO},
{ GRDATA ( CSR0, xub.pcsr0, XU_RDX, 16, 0), REG_FIT },
{ GRDATA ( CSR1, xub.pcsr1, XU_RDX, 16, 0), REG_FIT },
{ GRDATA ( CSR2, xub.pcsr2, XU_RDX, 16, 0), REG_FIT },
{ GRDATA ( CSR3, xub.pcsr3, XU_RDX, 16, 0), REG_FIT },
{ GRDATA ( MODE, xub.mode, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( PCBB, xub.pcbb, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( STAT, xub.stat, XU_RDX, 16, 0), REG_FIT },
{ GRDATA ( TDRB, xub.tdrb, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( TELEN, xub.telen, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( TRLEN, xub.trlen, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( TXNEXT, xub.txnext, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( RDRB, xub.rdrb, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( RELEN, xub.relen, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( RRLEN, xub.rrlen, XU_RDX, 32, 0), REG_FIT },
{ GRDATA ( RXNEXT, xub.rxnext, XU_RDX, 32, 0), REG_FIT },
{ BRDATA ( PCB, xub.pcb, XU_RDX, 16, 4), REG_HRO},
{ BRDATA ( UDB, xub.udb, XU_RDX, 16, UDBSIZE), REG_HRO},
{ BRDATA ( RXHDR, xub.rxhdr, XU_RDX, 16, 4), REG_HRO},
{ BRDATA ( TXHDR, xub.txhdr, XU_RDX, 16, 4), REG_HRO},
{ GRDATA ( BA, xub_dib.ba, XU_RDX, 32, 0), REG_HRO},
{ GRDATA ( VECTOR, xub_dib.vec, XU_RDX, 32, 0), REG_HRO},
{ GRDATA ( THR_TIME, xub.throttle_time, XU_RDX, 32, 0), REG_HRO},
{ GRDATA ( THR_BURST, xub.throttle_burst, XU_RDX, 32, 0), REG_HRO},
{ GRDATA ( THR_DELAY, xub.throttle_delay, XU_RDX, 32, 0), REG_HRO},
{ NULL } };
DEVICE xub_dev = {
"XUB", xub_unit, xub_reg, xu_mod,
2, XU_RDX, 8, 1, XU_RDX, 8,
&xu_ex, &xu_dep, &xu_reset,
NULL, &xu_attach, &xu_detach,
&xub_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG | DEV_ETHER,
0, xu_debug, NULL, NULL, NULL, NULL, NULL,
&xu_description
};
#define XU_MAX_CONTROLLERS 2
CTLR xu_ctrl[] = {
{&xu_dev, xua_unit, &xua_dib, &xua} /* XUA controller */
,{&xub_dev, xub_unit, &xub_dib, &xub} /* XUB controller */
};
/*============================================================================*/
/* Multicontroller support */
CTLR* xu_unit2ctlr(UNIT* uptr)
{
int i;
unsigned int j;
for (i=0; i<XU_MAX_CONTROLLERS; i++)
for (j=0; j<xu_ctrl[i].dev->numunits; j++)
if (&xu_ctrl[i].unit[j] == uptr)
return &xu_ctrl[i];
/* not found */
return 0;
}
CTLR* xu_dev2ctlr(DEVICE* dptr)
{
int i;
for (i=0; i<XU_MAX_CONTROLLERS; i++)
if (xu_ctrl[i].dev == dptr)
return &xu_ctrl[i];
/* not found */
return 0;
}
CTLR* xu_pa2ctlr(uint32 PA)
{
int i;
for (i=0; i<XU_MAX_CONTROLLERS; i++)
if ((PA >= xu_ctrl[i].dib->ba) && (PA < (xu_ctrl[i].dib->ba + xu_ctrl[i].dib->lnt)))
return &xu_ctrl[i];
/* not found */
return 0;
}
/*============================================================================*/
/* stop simh from reading non-existant unit data stream */
t_stat xu_ex (t_value* vptr, t_addr addr, UNIT* uptr, int32 sw)
{
return SCPE_NOFNC;
}
/* stop simh from writing non-existant unit data stream */
t_stat xu_dep (t_value val, t_addr addr, UNIT* uptr, int32 sw)
{
return SCPE_NOFNC;
}
t_stat xu_showmac (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
CTLR* xu = xu_unit2ctlr(uptr);
char buffer[20];
eth_mac_fmt((ETH_MAC*)xu->var->mac, buffer);
fprintf(st, "MAC=%s", buffer);
return SCPE_OK;
}
t_stat xu_setmac (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
t_stat status;
CTLR* xu = xu_unit2ctlr(uptr);
if (!cptr) return SCPE_IERR;
if (uptr->flags & UNIT_ATT) return SCPE_ALATT;
status = eth_mac_scan_ex(&xu->var->mac, cptr, uptr);
return status;
}
t_stat xu_set_stats (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
CTLR* xu = xu_unit2ctlr(uptr);
/* set stats to zero, regardless of passed parameter */
memset(&xu->var->stats, 0, sizeof(struct xu_stats));
return SCPE_OK;
}
t_stat xu_show_stats (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
const char* fmt = " %-26s%d\n";
CTLR* xu = xu_unit2ctlr(uptr);
struct xu_stats* stats = &xu->var->stats;
fprintf(st, "Ethernet statistics:\n");
fprintf(st, fmt, "Seconds since cleared:", stats->secs);
fprintf(st, fmt, "Recv frames:", stats->frecv);
fprintf(st, fmt, "Recv dbytes:", stats->rbytes);
fprintf(st, fmt, "Xmit frames:", stats->ftrans);
fprintf(st, fmt, "Xmit dbytes:", stats->tbytes);
fprintf(st, fmt, "Recv frames(multicast):", stats->mfrecv);
fprintf(st, fmt, "Recv dbytes(multicast):", stats->mrbytes);
fprintf(st, fmt, "Xmit frames(multicast):", stats->mftrans);
fprintf(st, fmt, "Xmit dbytes(multicast):", stats->mtbytes);
fprintf(st, fmt, "Loopback forward Frames:", stats->loopf);
return SCPE_OK;
}
t_stat xu_show_filters (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
CTLR* xu = xu_unit2ctlr(uptr);
char buffer[20];
int i;
fprintf(st, "Filters:\n");
for (i=0; i<XU_FILTER_MAX; i++) {
eth_mac_fmt((ETH_MAC*)xu->var->setup.macs[i], buffer);
fprintf(st, " [%2d]: %s\n", i, buffer);
}
if (xu->var->setup.multicast)
fprintf(st, "All Multicast Receive Mode\n");
if (xu->var->setup.promiscuous)
fprintf(st, "Promiscuous Receive Mode\n");
return SCPE_OK;
}
t_stat xu_show_type (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
CTLR* xu = xu_unit2ctlr(uptr);
fprintf(st, "type=");
switch (xu->var->type) {
case XU_T_DEUNA: fprintf(st, "DEUNA"); break;
case XU_T_DELUA: fprintf(st, "DELUA"); break;
}
return SCPE_OK;
}
t_stat xu_set_type (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
CTLR* xu = xu_unit2ctlr(uptr);
if (!cptr) return SCPE_IERR;
if (uptr->flags & UNIT_ATT) return SCPE_ALATT;
/* this assumes that the parameter has already been upcased */
if (!strcmp(cptr, "DEUNA")) xu->var->type = XU_T_DEUNA;
else if (!strcmp(cptr, "DELUA")) xu->var->type = XU_T_DELUA;
else return SCPE_ARG;
return SCPE_OK;
}
t_stat xu_show_throttle (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
CTLR* xu = xu_unit2ctlr(uptr);
if (xu->var->throttle_delay == ETH_THROT_DISABLED_DELAY)
fprintf(st, "throttle=disabled");
else
fprintf(st, "throttle=time=%d;burst=%d;delay=%d", xu->var->throttle_time, xu->var->throttle_burst, xu->var->throttle_delay);
return SCPE_OK;
}
t_stat xu_set_throttle (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
CTLR* xu = xu_unit2ctlr(uptr);
char tbuf[CBUFSIZE], gbuf[CBUFSIZE];
const char *tptr = cptr;
uint32 newval;
uint32 set_time = xu->var->throttle_time;
uint32 set_burst = xu->var->throttle_burst;
uint32 set_delay = xu->var->throttle_delay;
t_stat r = SCPE_OK;
if (!cptr) {
xu->var->throttle_delay = ETH_THROT_DEFAULT_DELAY;
eth_set_throttle (xu->var->etherface, xu->var->throttle_time, xu->var->throttle_burst, xu->var->throttle_delay);
return SCPE_OK;
}
/* this assumes that the parameter has already been upcased */
if ((!strcmp (cptr, "ON")) ||
(!strcmp (cptr, "ENABLED")))
xu->var->throttle_delay = ETH_THROT_DEFAULT_DELAY;
else
if ((!strcmp (cptr, "OFF")) ||
(!strcmp (cptr, "DISABLED")))
xu->var->throttle_delay = ETH_THROT_DISABLED_DELAY;
else {
if (set_delay == ETH_THROT_DISABLED_DELAY)
set_delay = ETH_THROT_DEFAULT_DELAY;
while (*tptr) {
tptr = get_glyph_nc (tptr, tbuf, ';');
cptr = tbuf;
cptr = get_glyph (cptr, gbuf, '=');
if ((NULL == cptr) || ('\0' == *cptr))
return SCPE_ARG;
newval = (uint32)get_uint (cptr, 10, 100, &r);
if (r != SCPE_OK)
return SCPE_ARG;
if (!MATCH_CMD(gbuf, "TIME")) {
set_time = newval;
}
else
if (!MATCH_CMD(gbuf, "BURST")) {
if (newval > 30)
return SCPE_ARG;
set_burst = newval;
}
else
if (!MATCH_CMD(gbuf, "DELAY")) {
set_delay = newval;
}
else
return SCPE_ARG;
}
xu->var->throttle_time = set_time;
xu->var->throttle_burst = set_burst;
xu->var->throttle_delay = set_delay;
}
eth_set_throttle (xu->var->etherface, xu->var->throttle_time, xu->var->throttle_burst, xu->var->throttle_delay);
return SCPE_OK;
}
/*============================================================================*/
void upd_stat16(uint16* stat, uint16 add)
{
*stat += add;
/* did stat roll over? latches at maximum */
if (*stat < add)
*stat = 0xFFFF;
}
void upd_stat32(uint32* stat, uint32 add)
{
*stat += add;
/* did stat roll over? latches at maximum */
if (*stat < add)
*stat = 0xFFFFFFFF;
}
void bit_stat16(uint16* stat, uint16 bits)
{
*stat |= bits;
}
t_stat xu_process_loopback(CTLR* xu, ETH_PACK* pack)
{
ETH_PACK response;
ETH_MAC physical_address;
t_stat status;
int offset = 16 + (pack->msg[14] | (pack->msg[15] << 8));
int function;
if (offset > ETH_MAX_PACKET - 8)
return SCPE_NOFNC;
function = pack->msg[offset] | (pack->msg[offset+1] << 8);
sim_debug(DBG_TRC, xu->dev, "xu_process_loopback(function=%d)\n", function);
if (function != 2 /*forward*/)
return SCPE_NOFNC;
/* create forward response packet */
memcpy (&response, pack, sizeof(ETH_PACK));
memcpy (physical_address, xu->var->setup.macs[0], sizeof(ETH_MAC));
/* The only packets we should be responding to are ones which
we received due to them being directed to our physical MAC address,
OR the Broadcast address OR to a Multicast address we're listening to
(we may receive others if we're in promiscuous mode, but shouldn't
respond to them) */
if ((0 == (pack->msg[0]&1)) && /* Multicast or Broadcast */
(0 != memcmp(physical_address, pack->msg, sizeof(ETH_MAC))))
return SCPE_NOFNC;
memcpy (&response.msg[0], &response.msg[offset+2], sizeof(ETH_MAC));
memcpy (&response.msg[6], physical_address, sizeof(ETH_MAC));
offset += 8 - 16; /* Account for the Ethernet Header and Offset value in this number */
response.msg[14] = offset & 0xFF;
response.msg[15] = (offset >> 8) & 0xFF;
/* send response packet */
status = eth_write(xu->var->etherface, &response, NULL);
++xu->var->stats.loopf;
if (DBG_PCK & xu->dev->dctrl)
eth_packet_trace_ex(xu->var->etherface, response.msg, response.len, ((function == 1) ? "xu-loopbackreply" : "xu-loopbackforward"), DBG_DAT & xu->dev->dctrl, DBG_PCK);
return status;
}
t_stat xu_process_local (CTLR* xu, ETH_PACK* pack)
{
/* returns SCPE_OK if local processing occurred,
otherwise returns SCPE_NOFNC or some other code */
int protocol;
sim_debug(DBG_TRC, xu->dev, "xu_process_local()\n");
protocol = pack->msg[12] | (pack->msg[13] << 8);
switch (protocol) {
case 0x0090: /* ethernet loopback */
return xu_process_loopback(xu, pack);
break;
case 0x0260: /* MOP remote console */
return SCPE_NOFNC; /* not implemented yet */
break;
}
return SCPE_NOFNC;
}
void xu_read_callback(CTLR* xu, int status)
{
if (DBG_PCK & xu->dev->dctrl)
eth_packet_trace_ex(xu->var->etherface, xu->var->read_buffer.msg, xu->var->read_buffer.len, "xu-recvd", DBG_DAT & xu->dev->dctrl, DBG_PCK);
xu->var->read_buffer.used = 0; /* none processed yet */
/* process any packets locally that can be */
status = xu_process_local (xu, &xu->var->read_buffer);
/* add packet to read queue */
if (status != SCPE_OK)
ethq_insert(&xu->var->ReadQ, ETH_ITM_NORMAL, &xu->var->read_buffer, 0);
}
void xua_read_callback(int status)
{
xu_read_callback(&xu_ctrl[0], status);
}
void xub_read_callback(int status)
{
xu_read_callback(&xu_ctrl[1], status);
}
t_stat xu_system_id (CTLR* xu, const ETH_MAC dest, uint16 receipt_id)
{
static uint16 receipt = 0;
ETH_PACK system_id;
uint8* const msg = &system_id.msg[0];
t_stat status;
sim_debug(DBG_TRC, xu->dev, "xu_system_id()\n");
memset (&system_id, 0, sizeof(system_id));
memcpy (&msg[0], dest, sizeof(ETH_MAC));
memcpy (&msg[6], xu->var->setup.macs[0], sizeof(ETH_MAC));
msg[12] = 0x60; /* type */
msg[13] = 0x02; /* type */
msg[14] = 0x1C; /* character count */
msg[15] = 0x00; /* character count */
msg[16] = 0x07; /* code */
msg[17] = 0x00; /* zero pad */
if (receipt_id) {
msg[18] = receipt_id & 0xFF; /* receipt number */
msg[19] = (receipt_id >> 8) & 0xFF; /* receipt number */
} else {
msg[18] = receipt & 0xFF; /* receipt number */
msg[19] = (receipt++ >> 8) & 0xFF; /* receipt number */
}
/* MOP VERSION */
msg[20] = 0x01; /* type */
msg[21] = 0x00; /* type */
msg[22] = 0x03; /* length */
msg[23] = 0x03; /* version */
msg[24] = 0x00; /* eco */
msg[25] = 0x00; /* user eco */
/* FUNCTION */
msg[26] = 0x02; /* type */
msg[27] = 0x00; /* type */
msg[28] = 0x02; /* length */
msg[29] = 0x05; /* value 1 */
msg[30] = 0x00; /* value 2 */
/* HARDWARE ADDRESS */
msg[31] = 0x07; /* type */
msg[32] = 0x00; /* type */
msg[33] = 0x06; /* length */
memcpy (&msg[34], xu->var->mac, sizeof(ETH_MAC)); /* ROM address */
/* DEVICE TYPE */
msg[40] = 0x64; /* type */
msg[41] = 0x00; /* type */
msg[42] = 0x01; /* length */
if (xu->var->type == XU_T_DEUNA)
msg[43] = 1; /* value (1=DEUNA) */
else
msg[43] = 11; /* value (11=DELUA) */
/* write system id */
system_id.len = 60;
status = eth_write(xu->var->etherface, &system_id, NULL);
if (DBG_PCK & xu->dev->dctrl)
eth_packet_trace_ex(xu->var->etherface, system_id.msg, system_id.len, "xu-systemid", DBG_DAT & xu->dev->dctrl, DBG_PCK);
return status;
}
t_stat xu_svc(UNIT* uptr)
{
int queue_size;
CTLR* xu = xu_unit2ctlr(uptr);
/* First pump any queued packets into the system */
if ((xu->var->ReadQ.count > 0) && ((xu->var->pcsr1 & PCSR1_STATE) == STATE_RUNNING))
xu_process_receive(xu);
/* Now read and queue packets that have arrived */
/* This is repeated as long as they are available and we have room */
do
{
queue_size = xu->var->ReadQ.count;
/* read a packet from the ethernet - processing is via the callback */
eth_read (xu->var->etherface, &xu->var->read_buffer, xu->var->rcallback);
} while (queue_size != xu->var->ReadQ.count);
/* Now pump any still queued packets into the system */
if ((xu->var->ReadQ.count > 0) && ((xu->var->pcsr1 & PCSR1_STATE) == STATE_RUNNING))
xu_process_receive(xu);
/* resubmit service timer if controller not halted */
switch (xu->var->pcsr1 & PCSR1_STATE) {
case STATE_READY:
case STATE_RUNNING:
sim_clock_coschedule (&xu->unit[0], tmxr_poll);
break;
};
return SCPE_OK;
}
t_stat xu_tmrsvc(UNIT* uptr)
{
CTLR* xu = xu_unit2ctlr(uptr);
const ETH_MAC mop_multicast = {0xAB, 0x00, 0x00, 0x02, 0x00, 0x00};
/* send identity packet when timer expires */
if (--xu->var->idtmr <= 0) {
if ((xu->var->mode & MODE_DMNT) == 0) /* if maint msg is not disabled */
xu_system_id(xu, mop_multicast, 0); /* then send ID packet */
xu->var->idtmr = XU_ID_TIMER_VAL; /* reset timer */
}
/* update stats */
upd_stat16 (&xu->var->stats.secs, 1);
/* resubmit service timer */
sim_activate_after(uptr, 1000000);
return SCPE_OK;
}
void xu_write_callback (CTLR* xu, int status)
{
xu->var->write_buffer.status = status;
}
void xua_write_callback (int status)
{
xu_write_callback(&xu_ctrl[0], status);
}
void xub_write_callback (int status)
{
xu_write_callback(&xu_ctrl[1], status);
}
void xu_setclrint(CTLR* xu, int32 bits)
{
if (xu->var->pcsr0 & 0xFF00) { /* if any interrupt bits on, */
xu->var->pcsr0 |= PCSR0_INTR; /* turn master bit on */
xu_setint(xu); /* and trigger interrupt */
} else {
xu->var->pcsr0 &= ~PCSR0_INTR; /* ... or off */
xu_clrint(xu); /* and clear interrupt if needed*/
}
}
t_stat xu_sw_reset (CTLR* xu)
{
int i;
sim_debug(DBG_TRC, xu->dev, "xu_sw_reset()\n");
/* Clear the registers. */
xu->var->pcsr0 = PCSR0_DNI | PCSR0_INTR;
xu->var->pcsr1 = STATE_READY;
switch (xu->var->type) {
case XU_T_DELUA:
xu->var->pcsr1 |= TYPE_DELUA;
break;
case XU_T_DEUNA:
xu->var->pcsr1 |= TYPE_DEUNA;
if (!xu->var->etherface) /* if not attached, set transceiver powerfail */
xu->var->pcsr1 |= PCSR1_XPWR;
break;
}
xu->var->pcsr2 = 0;
xu->var->pcsr3 = 0;
/* Clear the parameters. */
xu->var->mode = 0;
xu->var->pcbb = 0;
xu->var->stat = 0;
/* clear read queue */
ethq_clear(&xu->var->ReadQ);
/* clear setup info */
memset(&xu->var->setup, 0, sizeof(struct xu_setup));
/* clear network statistics */
memset(&xu->var->stats, 0, sizeof(struct xu_stats));
/* reset ethernet interface */
memcpy (xu->var->setup.macs[0], xu->var->mac, sizeof(ETH_MAC));
for (i=0; i<6; i++)
xu->var->setup.macs[1][i] = 0xff; /* Broadcast Address */
xu->var->setup.mac_count = 2;
if (xu->var->etherface) {
eth_filter (xu->var->etherface, xu->var->setup.mac_count,
xu->var->setup.macs, xu->var->setup.multicast,
xu->var->setup.promiscuous);
/* activate device */
sim_clock_coschedule (&xu->unit[0], tmxr_poll);
/* start service timer */
sim_activate_after (&xu->unit[1], 1000000);
}
/* clear load_server address */
memset(xu->var->load_server, 0, sizeof(ETH_MAC));
return SCPE_OK;
}
/* Reset device. */
t_stat xu_reset(DEVICE* dptr)
{
t_stat status;
CTLR* xu = xu_dev2ctlr(dptr);
sim_debug(DBG_TRC, xu->dev, "xu_reset()\n");
/* One time only initializations */
if (!xu->var->initialized) {
char uname[16];
xu->var->initialized = TRUE;
sprintf (uname, "%s-SVC", dptr->name);
sim_set_uname (&dptr->units[0], uname);
sprintf (uname, "%s-TMRSVC", dptr->name);
sim_set_uname (&dptr->units[1], uname);
/* Set an initial MAC address in the DEC range */
xu_setmac (dptr->units, 0, "08:00:2B:00:00:00/24", NULL);
}
/* init read queue (first time only) */
status = ethq_init (&xu->var->ReadQ, XU_QUE_MAX);
if (status != SCPE_OK)
return status;
/* software reset controller */
xu_sw_reset(xu);
return auto_config (0, 0); /* run autoconfig */
}
/* Perform one of the defined ancillary functions. */
int32 xu_command(CTLR* xu)
{
uint32 udbb;
int fnc, mtlen, i, j;
uint16 value, pltlen;
t_stat rstatus, wstatus, wstatus2, wstatus3;
struct xu_stats* stats = &xu->var->stats;
uint16* udb = xu->var->udb;
uint16* mac_w = (uint16*) xu->var->mac;
static const ETH_MAC zeros = {0,0,0,0,0,0};
static const ETH_MAC mcast_load_server = {0xAB, 0x00, 0x00, 0x01, 0x00, 0x00};
static const char* command[] = {
"NO-OP",
"Start Microaddress",
"Read Default Physical Address",
"NO-OP",
"Read Physical Address",
"Write Physical Address",
"Read Multicast Address List",
"Write Multicast Address List",
"Read Descriptor Ring Format",
"Write Descriptor Ring Format",
"Read Counters",
"Read/Clear Counters",
"Read Mode Register",
"Write Mode Register",
"Read Status",
"Read/Clear Status",
"Dump Internal Memory",
"Load Internal Memory",
"Read System ID",
"Write System ID",
"Read Load Server Address",
"Write Load Server Address"
};
/* Grab the PCB from the host. */
rstatus = Map_ReadW(xu->var->pcbb, 8, xu->var->pcb);
if (rstatus != 0)
return PCSR0_PCEI + 1;
/* High 8 bits are defined as MBZ. */
if (xu->var->pcb[0] & 0177400)
return PCSR0_PCEI;
/* Decode the function to be performed. */
fnc = xu->var->pcb[0] & 0377;
sim_debug(DBG_TRC, xu->dev, "xu_command(), Command: %s [0%o]\n", command[fnc], fnc);
switch (fnc) {
case FC_NOOP:
break;
case FC_RDPA: /* read default physical address */
wstatus = Map_WriteB(xu->var->pcbb + 2, 6, xu->var->mac);
if (wstatus)
return PCSR0_PCEI + 1;
break;
case FC_RPA: /* read current physical address */
wstatus = Map_WriteB(xu->var->pcbb + 2, 6, (uint8*)&xu->var->setup.macs[0]);
if (wstatus)
return PCSR0_PCEI + 1;
break;
case FC_WPA: /* write current physical address */
rstatus = Map_ReadB(xu->var->pcbb + 2, 6, (uint8*)&xu->var->setup.macs[0]);
if (xu->var->pcb[1] & 1)
return PCSR0_PCEI;
break;
case FC_RMAL: /* read multicast address list */
mtlen = (xu->var->pcb[2] & 0xFF00) >> 8;
udbb = xu->var->pcb[1] | ((xu->var->pcb[2] & 03) << 16);
wstatus = Map_WriteB(udbb, mtlen * 3, (uint8*) &xu->var->setup.macs[2]);
break;
case FC_WMAL: /* write multicast address list */
mtlen = (xu->var->pcb[2] & 0xFF00) >> 8;
sim_debug(DBG_TRC, xu->dev, "FC_WAL: mtlen=%d\n", mtlen);
if (mtlen > 10)
return PCSR0_PCEI;
udbb = xu->var->pcb[1] | ((xu->var->pcb[2] & 03) << 16);
/* clear existing multicast list */
for (i=2; i<XU_FILTER_MAX; i++) {
for (j=0; j<6; j++)
xu->var->setup.macs[i][j] = 0;
}
/* get multicast list from host */
rstatus = Map_ReadB(udbb, mtlen * 6, (uint8*) &xu->var->setup.macs[2]);
if (rstatus == 0) {
xu->var->setup.valid = 1;
xu->var->setup.mac_count = mtlen + 2;
eth_filter (xu->var->etherface, xu->var->setup.mac_count,
xu->var->setup.macs, xu->var->setup.multicast,
xu->var->setup.promiscuous);
} else {
xu->var->pcsr0 |= PCSR0_PCEI;
}
break;
case FC_RRF: /* read ring format */
if ((xu->var->pcb[1] & 1) || (xu->var->pcb[2] & 0374))
return PCSR0_PCEI;
xu->var->udb[0] = xu->var->tdrb & 0177776;
xu->var->udb[1] = (uint16)((xu->var->telen << 8) + ((xu->var->tdrb >> 16) & 3));
xu->var->udb[2] = (uint16)xu->var->trlen;
xu->var->udb[3] = xu->var->rdrb & 0177776;
xu->var->udb[4] = (uint16)((xu->var->relen << 8) + ((xu->var->rdrb >> 16) & 3));
xu->var->udb[5] = (uint16)xu->var->rrlen;
/* Write UDB to host memory. */
udbb = xu->var->pcb[1] + ((xu->var->pcb[2] & 3) << 16);
wstatus = Map_WriteW(udbb, 12, xu->var->pcb);
if (wstatus != 0)
return PCSR0_PCEI+1;
break;
case FC_WRF: /* write ring format */
if ((xu->var->pcb[1] & 1) || (xu->var->pcb[2] & 0374))
return PCSR0_PCEI;
if ((xu->var->pcsr1 & PCSR1_STATE) == STATE_RUNNING)
return PCSR0_PCEI;
/* Read UDB into local memory. */
udbb = xu->var->pcb[1] + ((xu->var->pcb[2] & 3) << 16);
rstatus = Map_ReadW(udbb, 12, xu->var->udb);
if (rstatus)
return PCSR0_PCEI+1;
if ((xu->var->udb[0] & 1) || (xu->var->udb[1] & 0374) ||
(xu->var->udb[3] & 1) || (xu->var->udb[4] & 0374) ||
(xu->var->udb[5] < 2)) {
return PCSR0_PCEI;
}
xu->var->tdrb = ((xu->var->udb[1] & 3) << 16) + (xu->var->udb[0] & 0177776);
xu->var->telen = (xu->var->udb[1] >> 8) & 0377;
xu->var->trlen = xu->var->udb[2];
xu->var->rdrb = ((xu->var->udb[4] & 3) << 16) + (xu->var->udb[3] & 0177776);
xu->var->relen = (xu->var->udb[4] >> 8) & 0377;
xu->var->rrlen = xu->var->udb[5];
xu->var->rxnext = 0;
xu->var->txnext = 0;
// xu_dump_rxring(xu);
// xu_dump_txring(xu);
break;
case FC_RDCTR: /* read counters */
case FC_RDCLCTR: /* read and clear counters */
/* prepare udb for stats transfer */
memset(xu->var->udb, 0, sizeof(xu->var->udb));
/* place stats in udb */
udb[0] = 68; /* udb length */
udb[1] = stats->secs; /* seconds since zeroed */
udb[2] = stats->frecv & 0xFFFF; /* frames received <15:00> */
udb[3] = stats->frecv >> 16; /* frames received <31:16> */
udb[4] = stats->mfrecv & 0xFFFF; /* multicast frames received <15:00> */
udb[5] = stats->mfrecv >> 16; /* multicast frames received <31:16> */
udb[6] = stats->rxerf; /* receive error status bits */
udb[7] = (uint16)stats->frecve; /* frames received with error */
udb[8] = stats->rbytes & 0xFFFF; /* data bytes received <15:00> */
udb[9] = stats->rbytes >> 16; /* data bytes received <31:16> */
udb[10] = stats->mrbytes & 0xFFFF; /* multicast data bytes received <15:00> */
udb[11] = stats->mrbytes >> 16; /* multicast data bytes received <31:16> */
udb[12] = stats->rlossi; /* received frames lost - internal buffer */
udb[13] = stats->rlossl; /* received frames lost - local buffer */
udb[14] = stats->ftrans & 0xFFFF; /* frames transmitted <15:00> */
udb[15] = stats->ftrans >> 16; /* frames transmitted <31:16> */
udb[16] = stats->mftrans & 0xFFFF; /* multicast frames transmitted <15:00> */
udb[17] = stats->mftrans >> 16; /* multicast frames transmitted <31:16> */
udb[18] = stats->ftrans3 & 0xFFFF; /* frames transmitted 3+ tries <15:00> */
udb[19] = stats->ftrans3 >> 16; /* frames transmitted 3+ tries <31:16> */
udb[20] = stats->ftrans2 & 0xFFFF; /* frames transmitted 2 tries <15:00> */
udb[21] = stats->ftrans2 >> 16; /* frames transmitted 2 tries <31:16> */
udb[22] = stats->ftransd & 0xFFFF; /* frames transmitted deferred <15:00> */
udb[23] = stats->ftransd >> 16; /* frames transmitted deferred <31:16> */
udb[24] = stats->tbytes & 0xFFFF; /* data bytes transmitted <15:00> */
udb[25] = stats->tbytes >> 16; /* data bytes transmitted <31:16> */
udb[26] = stats->mtbytes & 0xFFFF; /* multicast data bytes transmitted <15:00> */
udb[27] = stats->mtbytes >> 16; /* multicast data bytes transmitted <31:16> */
udb[28] = stats->txerf; /* transmit frame error status bits */
udb[29] = stats->ftransa; /* transmit frames aborted */
udb[30] = stats->txccf; /* transmit collision check failure */
udb[31] = 0; /* MBZ */
udb[32] = stats->porterr; /* port driver error */
udb[33] = stats->bablcnt; /* babble counter */
/* transfer udb to host */
udbb = xu->var->pcb[1] + ((xu->var->pcb[2] & 3) << 16);
wstatus = Map_WriteW(udbb, 68, xu->var->udb);
if (wstatus) {
xu->var->pcsr0 |= PCSR0_PCEI;
}
/* if clear function, clear network stats */
if (fnc == FC_RDCLCTR)
memset(stats, 0, sizeof(struct xu_stats));
break;
case FC_RMODE: /* read mode register */
value = (uint16)xu->var->mode;
wstatus = Map_WriteW(xu->var->pcbb+2, 2, &value);
if (wstatus)
return PCSR0_PCEI + 1;
break;
case FC_WMODE: /* write mode register */
value = (uint16)xu->var->mode;
xu->var->mode = xu->var->pcb[1];
sim_debug(DBG_TRC, xu->dev, "FC_WMODE: mode=%04x\n", xu->var->mode);
/* set promiscuous and multicast flags */
xu->var->setup.promiscuous = (xu->var->mode & MODE_PROM) ? 1 : 0;
xu->var->setup.multicast = (xu->var->mode & MODE_ENAL) ? 1 : 0;
/* if promiscuous or multicast flags changed, change filter */
if ((value ^ xu->var->mode) & (MODE_PROM | MODE_ENAL))
eth_filter (xu->var->etherface, xu->var->setup.mac_count,
xu->var->setup.macs, xu->var->setup.multicast,
xu->var->setup.promiscuous);
break;
case FC_RSTAT: /* read extended status */
case FC_RCSTAT: /* read and clear extended status */
value = xu->var->stat;
wstatus = Map_WriteW(xu->var->pcbb+2, 2, &value);
value = 10;
wstatus2 = Map_WriteW(xu->var->pcbb+4, 2, &value);
value = 32;
wstatus3 = Map_WriteW(xu->var->pcbb+6, 2, &value);
if (wstatus + wstatus2 + wstatus3)
return PCSR0_PCEI + 1;
if (fnc == FC_RCSTAT)
xu->var->stat &= 0377; /* clear high byte */
break;
case FC_RSID: /* read system id parameters */
/* prepare udb for transfer */
memset(xu->var->udb, 0, sizeof(xu->var->udb));
udb[11] = 0x260; /* type */
udb[12] = 28/* + parameter size */; /* ccount */
udb[13] = 7; /* code */
udb[14] = 0; /* recnum */
/* mop information */
udb[15] = 1; /* mvtype */
udb[16] = 0x0303; /* mvver + mvlen */
udb[17] = 0; /* mvueco + mveco */
/* function information */
udb[18] = 2; /* ftype */
udb[19] = 0x0502; /* fval1 + flen */
udb[20] = 0x0700; /* hatype<07:00> + fval2 */
udb[21] = 0x0600; /* halen + hatype<15:08> */
/* built-in MAC address */
udb[22] = mac_w[0]; /* HA<15:00> */
udb[23] = mac_w[1]; /* HA<31:16> */
udb[24] = mac_w[2]; /* HA<47:32> */
udb[25] = 0x64; /* dtype */
udb[26] = (11 << 8) + 1; /* dvalue + dlen */
/* transfer udb to host */
udbb = xu->var->pcb[1] + ((xu->var->pcb[2] & 3) << 16);
wstatus = Map_WriteW(udbb, 52, xu->var->udb);
if (wstatus)
xu->var->pcsr0 |= PCSR0_PCEI;
break;
case FC_WSID: /* write system id parameters */
/* get udb base */
udbb = xu->var->pcb[1] + ((xu->var->pcb[2] & 3) << 16);
/* get udb length */
pltlen = xu->var->pcb[3];
/* transfer udb from host */
rstatus = Map_ReadW(udbb, pltlen * 2, xu->var->udb);
if (rstatus)
return PCSR0_PCEI + 1;
/* decode and store system ID fields , if we ever need to.
for right now, just return "success" */
break;
case FC_RLSA: /* read load server address */
if (memcmp(xu->var->load_server, zeros, sizeof(ETH_MAC))) {
/* not set, use default multicast load address */
wstatus = Map_WriteB(xu->var->pcbb + 2, 6, (const uint8*) mcast_load_server);
} else {
/* is set, use load_server */
wstatus = Map_WriteB(xu->var->pcbb + 2, 6, xu->var->load_server);
}
if (wstatus)
return PCSR0_PCEI + 1;
break;
case FC_WLSA: /* write load server address */
rstatus = Map_ReadB(xu->var->pcbb + 2, 6, xu->var->load_server);
if (rstatus)
return PCSR0_PCEI + 1;
break;
default: /* Unknown (unimplemented) command. */
sim_printf("%s: unknown ancilliary command 0%o requested !\n", xu->dev->name, fnc);
return PCSR0_PCEI;
break;
} /* switch */
return PCSR0_DNI;
}
/* Transfer received packets into receive ring. */
void xu_process_receive(CTLR* xu)
{
uint32 segb, ba;
int slen, wlen;
t_stat rstatus, wstatus;
ETH_ITEM* item = 0;
int state = xu->var->pcsr1 & PCSR1_STATE;
int no_buffers = xu->var->pcsr0 & PCSR0_RCBI;
sim_debug(DBG_TRC, xu->dev, "xu_process_receive(), buffers: %d\n", xu->var->rrlen);
// xu_dump_rxring(xu); /* debug receive ring */
/* process only when in the running state, and host buffers are available */
if ((state != STATE_RUNNING) || no_buffers)
return;
/* check read queue for buffer loss */
if (xu->var->ReadQ.loss) {
upd_stat16(&xu->var->stats.rlossl, (uint16) xu->var->ReadQ.loss);
xu->var->ReadQ.loss = 0;
}
/* while there are still packets left to process in the queue */
while (xu->var->ReadQ.count > 0) {
/* get next receive buffer */
ba = xu->var->rdrb + (xu->var->relen * 2) * xu->var->rxnext;
rstatus = Map_ReadW (ba, 8, xu->var->rxhdr);
if (rstatus) {
/* tell host bus read failed */
xu->var->stat |= STAT_ERRS | STAT_MERR | STAT_TMOT | STAT_RRNG;
xu->var->pcsr0 |= PCSR0_SERI;
break;
}
/* if buffer not owned by controller, exit [at end of ring] */
if (!(xu->var->rxhdr[2] & RXR_OWN)) {
/* tell the host there are no more buffers */
/* xu->var->pcsr0 |= PCSR0_RCBI; */ /* I don't think this is correct 08-dec-2005 dth */
sim_debug(DBG_TRC, xu->dev, "Stopping input processing - Not Owned receive descriptor=0x%X, ", ba);
sim_debug_bits(DBG_TRC, xu->dev, xu_rdes_w2, xu->var->rxhdr[2], xu->var->rxhdr[2], 0);
sim_debug_bits(DBG_TRC, xu->dev, xu_rdes_w3, xu->var->rxhdr[3], xu->var->rxhdr[3], 1);
break;
}
/* set buffer length and address */
slen = xu->var->rxhdr[0];
segb = xu->var->rxhdr[1] + ((xu->var->rxhdr[2] & 3) << 16);
/* Initially clear status bits which are conditionally set below */
xu->var->rxhdr[2] &= ~(RXR_FRAM|RXR_OFLO|RXR_CRC|RXR_STF|RXR_ENF);
/* get first packet from receive queue */
if (!item) {
item = &xu->var->ReadQ.item[xu->var->ReadQ.head];
/*
* 2.11BSD does not seem to like small packets.
* For example.. an incoming ARP packet is:
* ETH dstaddr [6]
* ETH srcaddr [6]
* ETH type [2]
* ARP arphdr [8]
* ARP dstha [6]
* ARP dstpa [4]
* ARP srcha [6]
* ARP srcpa [4]
*
* for a total of 42 bytes. According to the 2.11BSD
* driver for DEUNA (if_de.c), this is not a legal size,
* and the packet is dropped. Therefore, we pad the
* thing to minimum size here. Stupid runts...
*/
if (item->packet.len < ETH_MIN_PACKET) {
int len = item->packet.len;
memset (&item->packet.msg[len], 0, ETH_MIN_PACKET - len);
item->packet.len = ETH_MIN_PACKET;
}
}
/* is this the start of frame? */
if (item->packet.used == 0)
xu->var->rxhdr[2] |= RXR_STF;
/* figure out chained packet size */
wlen = item->packet.crc_len - item->packet.used;
if (wlen > slen)
wlen = slen;
sim_debug(DBG_TRC, xu->dev, "Using receive descriptor=0x%X, slen=0x%04X(%d), segb=0x%04X, ", ba, slen, slen, segb);
sim_debug_bits(DBG_TRC, xu->dev, xu_rdes_w2, xu->var->rxhdr[2], xu->var->rxhdr[2], 0);
sim_debug_bits(DBG_TRC, xu->dev, xu_rdes_w3, xu->var->rxhdr[3], xu->var->rxhdr[3], 0);
sim_debug(DBG_TRC, xu->dev, ", pktlen=0x%X(%d), used=0x%X, wlen=0x%X\n", item->packet.len, item->packet.len, item->packet.used, wlen);
/* transfer chained packet to host buffer */
wstatus = Map_WriteB (segb, wlen, &item->packet.msg[item->packet.used]);
if (wstatus) {
/* error during write */
xu->var->stat |= STAT_ERRS | STAT_MERR | STAT_TMOT | STAT_RRNG;
xu->var->pcsr0 |= PCSR0_SERI;
break;
}
/* update chained counts */
item->packet.used += wlen;
/*
* Fill in the Received Message Length field.
* The documenation notes that the DEUNA actually performs
* a full CRC check on the data buffer, and adds this CRC
* value to the data, in the last 4 bytes. The question
* is: does MLEN include these 4 bytes, or not??? --FvK
*
* A quick look at the RSX Process Software driver shows
* that the CRC byte count(4) is added to MLEN, but does
* not show if the DEUNA/DELUA actually transfers the
* CRC bytes to the host buffers, since the driver never
* tries to use them. However, since the host max buffer
* size is only 1514, not 1518, I doubt the CRC is actually
* transferred in normal mode. Maybe CRC is transferred
* and used in Loopback mode.. -- DTH
*
* The VMS XEDRIVER indicates that CRC is transferred as
* part of the packet, and is included in the MLEN count. -- DTH
*/
xu->var->rxhdr[3] &= ~RXR_MLEN;
xu->var->rxhdr[3] |= (uint16)(item->packet.crc_len);
/* Is this the end-of-frame? OR is buffer chaining disabled? */
if ((item->packet.used == item->packet.crc_len) ||
(xu->var->mode & MODE_DRDC)) {
/* mark end-of-frame */
xu->var->rxhdr[2] |= RXR_ENF;
if (xu->var->mode & MODE_DRDC) /* data chaining disabled */
xu->var->rxhdr[3] |= RXR_NCHN;
/* update stats */
upd_stat32(&xu->var->stats.frecv, 1);
upd_stat32(&xu->var->stats.rbytes, item->packet.len - 14);
if (item->packet.msg[0] & 1) { /* multicast? */
upd_stat32(&xu->var->stats.mfrecv, 1);
upd_stat32(&xu->var->stats.mrbytes, item->packet.len - 14);
}
/* remove processed packet from the receive queue */
ethq_remove (&xu->var->ReadQ);
item = 0;
/* tell host we received a packet */
xu->var->pcsr0 |= PCSR0_RXI;
} /* if end-of-frame */
/* give buffer back to host */
xu->var->rxhdr[2] &= ~RXR_OWN; /* clear ownership flag */
sim_debug(DBG_TRC, xu->dev, "Updating receive descriptor=0x%X, slen=0x%04X, segb=0x%04X, ", ba, slen, segb);
sim_debug_bits(DBG_TRC, xu->dev, xu_rdes_w2, xu->var->rxhdr[2], xu->var->rxhdr[2], 0);
sim_debug_bits(DBG_TRC, xu->dev, xu_rdes_w3, xu->var->rxhdr[3], xu->var->rxhdr[3], 1);
/* update the ring entry in host memory. */
wstatus = Map_WriteW (ba, 8, xu->var->rxhdr);
if (wstatus) {
/* tell host bus write failed */
xu->var->stat |= STAT_ERRS | STAT_MERR | STAT_TMOT | STAT_RRNG;
xu->var->pcsr0 |= PCSR0_SERI;
/* if this was end-of-frame, log frame loss */
if (xu->var->rxhdr[2] & RXR_ENF)
upd_stat16(&xu->var->stats.rlossi, 1);
}
/* set to next receive ring buffer */
xu->var->rxnext += 1;
if (xu->var->rxnext == xu->var->rrlen)
xu->var->rxnext = 0;
} /* while */
/* if we failed to finish receiving the frame, flush the packet */
if (item) {
ethq_remove(&xu->var->ReadQ);
upd_stat16(&xu->var->stats.rlossl, 1);
}
/* set or clear interrupt, depending on what happened */
xu_setclrint(xu, 0);
// xu_dump_rxring(xu); /* debug receive ring */
}
void xu_process_transmit(CTLR* xu)
{
uint32 segb, ba;
int slen, wlen, i, off, giant, runt;
t_stat rstatus, wstatus;
sim_debug(DBG_TRC, xu->dev, "xu_process_transmit()\n");
/* xu_dump_txring(xu); *//* debug receive ring */
off = giant = runt = 0;
for (;;) {
/* get next transmit buffer */
ba = xu->var->tdrb + (xu->var->telen * 2) * xu->var->txnext;
rstatus = Map_ReadW (ba, 8, xu->var->txhdr);
if (rstatus) {
/* tell host bus read failed */
xu->var->stat |= STAT_ERRS | STAT_MERR | STAT_TMOT | STAT_TRNG;
xu->var->pcsr0 |= PCSR0_SERI;
break;
}
/* if buffer not owned by controller, exit [at end of ring] */
if (!(xu->var->txhdr[2] & TXR_OWN))
break;
/* set buffer length and address */
slen = xu->var->txhdr[0];
segb = xu->var->txhdr[1] + ((xu->var->txhdr[2] & 3) << 16);
wlen = slen;
/* prepare to accumulate transmit information if start of frame */
if (xu->var->txhdr[2] & TXR_STF) {
memset(&xu->var->write_buffer, 0, sizeof(ETH_PACK));
off = giant = runt = 0;
}
/* get packet data from host */
if (xu->var->write_buffer.len + slen > ETH_MAX_PACKET) {
wlen = ETH_MAX_PACKET - xu->var->write_buffer.len;
giant = 1;
}
if (wlen > 0) {
rstatus = Map_ReadB(segb, wlen, &xu->var->write_buffer.msg[off]);
if (rstatus) {
/* tell host bus read failed */
xu->var->stat |= STAT_ERRS | STAT_MERR | STAT_TMOT | STAT_TRNG;
xu->var->pcsr0 |= PCSR0_SERI;
break;
}
}
off += wlen;
xu->var->write_buffer.len += wlen;
/* transmit packet when end-of-frame is reached */
if (xu->var->txhdr[2] & TXR_ENF) {
/* make sure packet is minimum length */
if (xu->var->write_buffer.len < ETH_MIN_PACKET) {
xu->var->write_buffer.len = ETH_MIN_PACKET; /* pad packet to minimum length */
if ((xu->var->mode & MODE_TPAD) == 0) /* if pad mode is NOT on, set runt error flag */
runt = 1;
}
/* As described in the DEUNA User Guide (Section 4.7), the DEUNA is responsible
for inserting the appropriate source MAC address in the outgoing packet header,
so we do that now. */
memcpy(xu->var->write_buffer.msg+6, (uint8*)&xu->var->setup.macs[0], sizeof(ETH_MAC));
/* are we in internal loopback mode ? */
if ((xu->var->mode & MODE_LOOP) && (xu->var->mode & MODE_INTL)) {
/* just put packet in receive buffer */
ethq_insert (&xu->var->ReadQ, ETH_ITM_LOOPBACK, &xu->var->write_buffer, 0);
} else {
/* transmit packet synchronously - write callback sets status */
wstatus = eth_write(xu->var->etherface, &xu->var->write_buffer, xu->var->wcallback);
if (wstatus)
xu->var->pcsr0 |= PCSR0_PCEI;
else
if (DBG_PCK & xu->dev->dctrl)
eth_packet_trace_ex(xu->var->etherface, xu->var->write_buffer.msg, xu->var->write_buffer.len, "xu-write", DBG_DAT & xu->dev->dctrl, DBG_PCK);
}
/* update transmit status in transmit buffer */
if (xu->var->write_buffer.status != 0) {
/* failure */
const uint16 tdr = (uint16)(100 + wlen * 8); /* arbitrary value */
xu->var->txhdr[3] |= TXR_RTRY;
xu->var->txhdr[3] |= tdr & TXR_TDR;
xu->var->txhdr[2] |= TXR_ERRS;
}
/* was packet too big or too small? */
if (giant || runt) {
xu->var->txhdr[3] |= TXR_BUFL;
xu->var->txhdr[2] |= TXR_ERRS;
}
/* was packet self-addressed? */
for (i=0; i<XU_FILTER_MAX; i++)
if (memcmp(xu->var->write_buffer.msg, xu->var->setup.macs[i], sizeof(ETH_MAC)) == 0)
xu->var->txhdr[2] |= TXR_MTCH;
/* tell host we transmitted a packet */
xu->var->pcsr0 |= PCSR0_TXI;
/* update stats */
upd_stat32(&xu->var->stats.ftrans, 1);
upd_stat32(&xu->var->stats.tbytes, xu->var->write_buffer.len - 14);
if (xu->var->write_buffer.msg[0] & 1) { /* multicast? */
upd_stat32(&xu->var->stats.mftrans, 1);
upd_stat32(&xu->var->stats.mtbytes, xu->var->write_buffer.len - 14);
}
if (giant)
bit_stat16(&xu->var->stats.txerf, 0x10);
} /* if end-of-frame */
/* give buffer ownership back to host */
xu->var->txhdr[2] &= ~TXR_OWN;
/* update transmit buffer */
wstatus = Map_WriteW (ba, 8, xu->var->txhdr);
if (wstatus) {
/* tell host bus write failed */
xu->var->pcsr0 |= PCSR0_PCEI;
/* update stats */
upd_stat16(&xu->var->stats.ftransa, 1);
break;
}
/* set to next transmit ring buffer */
xu->var->txnext += 1;
if (xu->var->txnext == xu->var->trlen)
xu->var->txnext = 0;
} /* while */
}
void xu_port_command (CTLR* xu)
{
int command = xu->var->pcsr0 & PCSR0_PCMD;
int state = xu->var->pcsr1 & PCSR1_STATE;
static const char* commands[] = {
"NO-OP",
"GET PCBB",
"GET CMD",
"SELFTEST",
"START",
"BOOT",
"Reserved NO-OP",
"Reserved NO-OP",
"PDMD",
"Reserved NO-OP",
"Reserved NO-OP",
"Reserved NO-OP",
"Reserved NO-OP",
"Reserved NO-OP",
"HALT",
"STOP"
};
sim_debug(DBG_TRC, xu->dev, "xu_port_command(), Command = %s [0%o]\n", commands[command], command);
switch (command) { /* cases in order of most used to least used */
case CMD_PDMD: /* POLLING DEMAND */
/* process transmit buffers, receive buffers are done in the service timer */
xu_process_transmit(xu);
xu->var->pcsr0 |= PCSR0_DNI;
break;
case CMD_GETCMD: /* GET COMMAND */
xu_command(xu);
xu->var->pcsr0 |= PCSR0_DNI;
break;
case CMD_GETPCBB: /* GET PCB-BASE */
xu->var->pcbb = (xu->var->pcsr3 << 16) | xu->var->pcsr2;
xu->var->pcsr0 |= PCSR0_DNI;
break;
case CMD_SELFTEST: /* SELFTEST */
/*
SELFTEST is a <=15-second self diagnostic test, setting various
error flags and the DONE (DNI) flag when complete. For simulation
purposes, signal completion immediately with no errors. This
inexact behavior could be incompatible with any guest machine
diagnostics that are expecting to be able to monitor the
controller's progress through the diagnostic testing.
*/
xu->var->pcsr0 |= PCSR0_DNI;
xu->var->pcsr0 &= ~PCSR0_USCI;
xu->var->pcsr0 &= ~PCSR0_FATL;
xu->var->pcsr1 = STATE_READY;
break;
case CMD_START: /* START */
if (state == STATE_READY) {
xu->var->pcsr1 &= ~PCSR1_STATE;
xu->var->pcsr1 |= STATE_RUNNING;
xu->var->pcsr0 |= PCSR0_DNI;
/* reset ring pointers */
xu->var->rxnext = 0;
xu->var->txnext = 0;
} else
xu->var->pcsr0 |= PCSR0_PCEI;
break;
case CMD_HALT: /* HALT */
if ((state == STATE_READY) || (state == STATE_RUNNING)) {
sim_cancel (&xu->unit[0]); /* cancel service timer */
xu->var->pcsr1 &= ~PCSR1_STATE;
xu->var->pcsr1 |= STATE_HALT;
xu->var->pcsr0 |= PCSR0_DNI;
} else
xu->var->pcsr0 |= PCSR0_PCEI;
break;
case CMD_STOP: /* STOP */
if (state == STATE_RUNNING) {
xu->var->pcsr1 &= ~PCSR1_STATE;
xu->var->pcsr1 |= STATE_READY;
xu->var->pcsr0 |= PCSR0_DNI;
} else
xu->var->pcsr0 |= PCSR0_PCEI;
break;
case CMD_BOOT: /* BOOT */
/* not implemented */
sim_printf ("%s: BOOT command not implemented!\n", xu->dev->name);
xu->var->pcsr0 |= PCSR0_PCEI;
break;
case CMD_NOOP: /* NO-OP */
/* NOOP does NOT set DNI */
break;
case CMD_RSV06: /* RESERVED */
case CMD_RSV07: /* RESERVED */
case CMD_RSV11: /* RESERVED */
case CMD_RSV12: /* RESERVED */
case CMD_RSV13: /* RESERVED */
case CMD_RSV14: /* RESERVED */
case CMD_RSV15: /* RESERVED */
/* all reserved commands act as a no-op but set DNI */
xu->var->pcsr0 |= PCSR0_DNI;
break;
} /* switch */
/* set interrupt if needed */
xu_setclrint(xu, 0);
}
t_stat xu_rd(int32 *data, int32 PA, int32 access)
{
CTLR* xu = xu_pa2ctlr(PA);
int reg = (PA >> 1) & 03;
switch (reg) {
case 00:
*data = xu->var->pcsr0;
break;
case 01:
*data = xu->var->pcsr1;
break;
case 02:
*data = xu->var->pcsr2;
break;
case 03:
*data = xu->var->pcsr3;
break;
}
sim_debug(DBG_REG, xu->dev, "xu_rd(), PCSR%d, data=%04x\n", reg, *data);
if (PA & 1) {
sim_debug(DBG_WRN, xu->dev, "xu_rd(), Unexpected Odd address access of PCSR%d\n", reg);
}
return SCPE_OK;
}
t_stat xu_wr(int32 data, int32 PA, int32 access)
{
CTLR* xu = xu_pa2ctlr(PA);
int reg = (PA >> 1) & 03;
char desc[10];
switch (access) {
case WRITE :
strcpy(desc, "Word");
break;
case WRITEB:
if (PA & 1) {
strcpy(desc, "ByteHi");
} else {
strcpy(desc, "ByteLo");
}
break;
default :
strcpy(desc, "Unknown");
break;
}
sim_debug(DBG_REG, xu->dev, "xu_wr(), PCSR%d, data=%08x, PA=%08x, access=%d[%s]\n", reg, data, PA, access, desc);
switch (reg) {
case 00:
/* Clear write-one-to-clear interrupt bits */
if (access == WRITEB) {
data &= 0377;
if (PA & 1) {
/* Handle WriteOneToClear trick. */
xu->var->pcsr0 &= ~((data << 8) & 0177400);
/* set/reset interrupt */
xu_setclrint(xu, 0);
/* Bail out early to avoid PCMD crap. */
return SCPE_OK;
}
} else { /* access == WRITE [Word] */
uint16 mask = data & 0xFF00; /* only interested in high byte */
xu->var->pcsr0 &= ~mask; /* clear write-one-to-clear bits */
}
/* RESET function requested? */
if (data & PCSR0_RSET) {
xu_sw_reset(xu);
xu_setclrint(xu, 0);
return SCPE_OK; /* nothing else to do on reset */
}
/* Handle the INTE interlock; if INTE changes state, no commands can occur */
if ((xu->var->pcsr0 ^ data) & PCSR0_INTE) {
xu->var->pcsr0 ^= PCSR0_INTE;
xu->var->pcsr0 |= PCSR0_DNI;
if (xu->var->pcsr0 & PCSR0_INTE) {
sim_debug(DBG_TRC, xu->dev, "xu_wr(), Interrupts Enabled\n");
} else {
sim_debug(DBG_TRC, xu->dev, "xu_wr(), Interrupts Disabled\n");
}
} else {
/* Normal write, no interlock. */
xu->var->pcsr0 &= ~PCSR0_PCMD;
xu->var->pcsr0 |= (data & PCSR0_PCMD);
xu_port_command(xu);
}
/* We might have changed the interrupt sys. */
xu_setclrint(xu, 0);
break;
case 01:
sim_debug(DBG_WRN, xu->dev, "xu_wr(), invalid write access on PCSR1!\n");
break;
case 02:
xu->var->pcsr2 = data & 0177776; /* store word, but not MBZ LSB */
break;
case 03:
xu->var->pcsr3 = data & 0000003; /* store significant bits */
break;
}
return SCPE_OK;
}
/* attach device: */
t_stat xu_attach(UNIT* uptr, CONST char* cptr)
{
t_stat status;
char* tptr;
CTLR* xu = xu_unit2ctlr(uptr);
sim_debug(DBG_TRC, xu->dev, "xu_attach(cptr=%s)\n", cptr);
tptr = (char *) malloc(strlen(cptr) + 1);
if (tptr == NULL) return SCPE_MEM;
strcpy(tptr, cptr);
xu->var->etherface = (ETH_DEV *) malloc(sizeof(ETH_DEV));
if (!xu->var->etherface) {
free(tptr);
return SCPE_MEM;
}
status = eth_open(xu->var->etherface, cptr, xu->dev, DBG_ETH);
if (status != SCPE_OK) {
free(tptr);
free(xu->var->etherface);
xu->var->etherface = 0;
return status;
}
eth_set_throttle (xu->var->etherface, xu->var->throttle_time, xu->var->throttle_burst, xu->var->throttle_delay);
if (SCPE_OK != eth_check_address_conflict (xu->var->etherface, &xu->var->mac)) {
char buf[32];
eth_mac_fmt(&xu->var->mac, buf); /* format ethernet mac address */
sim_printf("%s: MAC Address Conflict on LAN for address %s\n", xu->dev->name, buf);
eth_close(xu->var->etherface);
free(tptr);
free(xu->var->etherface);
xu->var->etherface = NULL;
return SCPE_NOATT;
}
uptr->filename = tptr;
uptr->flags |= UNIT_ATT;
eth_setcrc(xu->var->etherface, 1); /* enable CRC */
/* init read queue (first time only) */
status = ethq_init(&xu->var->ReadQ, XU_QUE_MAX);
if (status != SCPE_OK) {
eth_close(xu->var->etherface);
free(tptr);
free(xu->var->etherface);
xu->var->etherface = NULL;
return status;
}
if (xu->var->setup.valid) {
int i, count = 0;
ETH_MAC zeros = {0, 0, 0, 0, 0, 0};
ETH_MAC filters[XU_FILTER_MAX + 1];
for (i = 0; i < XU_FILTER_MAX; i++)
if (memcmp(zeros, &xu->var->setup.macs[i], sizeof(ETH_MAC)))
memcpy (filters[count++], xu->var->setup.macs[i], sizeof(ETH_MAC));
eth_filter (xu->var->etherface, count, filters, xu->var->setup.multicast, xu->var->setup.promiscuous);
}
return SCPE_OK;
}
/* detach device: */
t_stat xu_detach(UNIT* uptr)
{
CTLR* xu = xu_unit2ctlr(uptr);
sim_debug(DBG_TRC, xu->dev, "xu_detach()\n");
if (uptr->flags & UNIT_ATT) {
eth_close (xu->var->etherface);
free(xu->var->etherface);
xu->var->etherface = NULL;
free(uptr->filename);
uptr->filename = NULL;
uptr->flags &= ~UNIT_ATT;
/* cancel service timers */
sim_cancel (uptr); /* stop the receiver */
sim_cancel (uptr+1); /* stop the timer services */
}
return SCPE_OK;
}
void xu_setint(CTLR* xu)
{
if (xu->var->pcsr0 & PCSR0_INTE) {
xu->var->irq = 1;
SET_INT(XU);
}
return;
}
void xu_clrint(CTLR* xu)
{
int i;
xu->var->irq = 0; /* set controller irq off */
/* clear master interrupt? */
for (i=0; i<XU_MAX_CONTROLLERS; i++) /* check all controllers.. */
if (xu_ctrl[i].var->irq) { /* if any irqs enabled */
SET_INT(XU); /* set master interrupt on */
return;
}
CLR_INT(XU); /* clear master interrupt */
return;
}
int32 xu_int (void)
{
int i;
for (i=0; i<XU_MAX_CONTROLLERS; i++) {
CTLR* xu = &xu_ctrl[i];
if (xu->var->irq) { /* if interrupt pending */
xu_clrint(xu); /* clear interrupt */
return xu->dib->vec; /* return vector */
}
}
return 0; /* no interrupt request active */
}
/*==============================================================================
/ debugging routines
/=============================================================================*/
void xu_dump_rxring (CTLR* xu)
{
int i;
int rrlen = xu->var->rrlen;
sim_printf ("receive ring[%s]: base address: %08x headers: %d, header size: %d, current: %d\n", xu->dev->name, xu->var->rdrb, xu->var->rrlen, xu->var->relen, xu->var->rxnext);
for (i=0; i<rrlen; i++) {
uint16 rxhdr[4] = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
uint32 ba = xu->var->rdrb + (xu->var->relen * 2) * i;
t_stat rstatus = Map_ReadW (ba, 8, rxhdr); /* get rxring entry[i] */
int own = (rxhdr[2] & RXR_OWN) >> 15;
int len = rxhdr[0];
uint32 addr = rxhdr[1] + ((rxhdr[2] & 3) << 16);
if (rstatus == 0)
sim_printf (" header[%d]: own:%d, len:%d, address:%08x data:{%04x,%04x,%04x,%04x}\n", i, own, len, addr, rxhdr[0], rxhdr[1], rxhdr[2], rxhdr[3]);
}
}
void xu_dump_txring (CTLR* xu)
{
int i;
int trlen = xu->var->trlen;
sim_printf ("transmit ring[%s]: base address: %08x headers: %d, header size: %d, current: %d\n", xu->dev->name, xu->var->tdrb, xu->var->trlen, xu->var->telen, xu->var->txnext);
for (i=0; i<trlen; i++) {
uint16 txhdr[4];
uint32 ba = xu->var->tdrb + (xu->var->telen * 2) * i;
t_stat tstatus = Map_ReadW (ba, 8, txhdr); /* get rxring entry[i] */
int own = (txhdr[2] & RXR_OWN) >> 15;
int len = txhdr[0];
uint32 addr = txhdr[1] + ((txhdr[2] & 3) << 16);
if (tstatus == 0)
sim_printf (" header[%d]: own:%d, len:%d, address:%08x data:{%04x,%04x,%04x,%04x}\n", i, own, len, addr, txhdr[0], txhdr[1], txhdr[2], txhdr[3]);
}
}
t_stat xu_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "DELUA/DEUNA Unibus Ethernet Controllers (XU, XUB)\n\n");
fprintf (st, "The simulator implements two DELUA/DEUNA Unibus Ethernet controllers (XU, XUB).\n");
fprintf (st, "Initially, both XU and XQB are disabled. Options allow control of the MAC\n");
fprintf (st, "address and the controller type.\n\n");
fprint_set_help (st, dptr);
fprintf (st, "\nConfigured options and controller state can be displayed with:\n\n");
fprint_show_help (st, dptr);
fprintf (st, "\nMAC address octets must be delimited by dashes, colons or periods.\n");
fprintf (st, "The controller defaults to a relatively unique MAC address in the range\n");
fprintf (st, "08-00-2B-00-00-00 thru 08-00-2B-FF-FF-FF, which should be sufficient\n");
fprintf (st, "for most network environments. If desired, the simulated MAC address\n");
fprintf (st, "can be directly set.\n");
fprintf (st, "To access the network, the simulated Ethernet controller must be attached to a\n");
fprintf (st, "real Ethernet interface.\n\n");
eth_attach_help(st, dptr, uptr, flag, cptr);
fprintf (st, "One final note: because of its asynchronous nature, the XU controller is not\n");
fprintf (st, "limited to the ~1.5Mbit/sec of the real DEUNA/DELUA controllers, nor the\n");
fprintf (st, "10Mbit/sec of a standard Ethernet. Attach it to a Fast or Gigabit Ethernet\n");
fprintf (st, "card, and \"Feel the Power!\" :-)\n");
return SCPE_OK;
}
const char *xu_description (DEVICE *dptr)
{
return "DEUNA/DELUA Ethernet controller";
}