/* pdp11_xu.c: DEUNA/DELUA ethernet controller simulator | |
------------------------------------------------------------------------------ | |
Copyright (c) 2003-2004, 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.spies.com/~aek/pdf/dec/unibus | |
What this BETA version contains: | |
1) Basic Transmit/Receive packet functionality. | |
2) Promiscuous/All_Multicast/Multicast/MAC support | |
Testing performed: | |
1) Receives/Transmits single packet under custom RSX driver | |
2) Passes RSTS 10.1 controller diagnostics during boot | |
Known issues: | |
1) Transmit/Receive rings have not been thoroughly tested, | |
particularly when and where the ring pointers get reset. | |
2) Most auxiliary commands are not implemented yet. | |
3) System_ID broadcast is not implemented | |
4) Error/Interrupt signalling is still iffy from merge of FvK and sim_ether | |
------------------------------------------------------------------------------ | |
Modification history: | |
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 tmr_poll, clk_tps; | |
extern FILE *sim_log; | |
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_reset (DEVICE * dptr); | |
t_stat xu_attach (UNIT * uptr, char * cptr); | |
t_stat xu_detach (UNIT * uptr); | |
t_stat xu_showmac (FILE* st, UNIT* uptr, int32 val, void* desc); | |
t_stat xu_setmac (UNIT* uptr, int32 val, char* cptr, void* desc); | |
t_stat xu_show_stats (FILE* st, UNIT* uptr, int32 val, void* desc); | |
t_stat xu_set_stats (UNIT* uptr, int32 val, char* cptr, void* desc); | |
t_stat xu_show_type (FILE* st, UNIT* uptr, int32 val, void* desc); | |
t_stat xu_set_type (UNIT* uptr, int32 val, 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); | |
DIB xua_dib = { IOBA_XU, IOLN_XU, &xu_rd, &xu_wr, | |
1, IVCL (XU), VEC_XU, {&xu_int} }; | |
UNIT xua_unit[] = { | |
{ UDATA (&xu_svc, UNIT_ATTABLE + UNIT_DISABLE, 0) } /* receive timer */ | |
}; | |
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 */ | |
}; | |
MTAB xu_mod[] = { | |
#if defined (VM_PDP11) | |
{ MTAB_XTD|MTAB_VDV, 004, "ADDRESS", "ADDRESS", | |
&set_addr, &show_addr, NULL }, | |
#else | |
{ MTAB_XTD|MTAB_VDV, 004, "ADDRESS", NULL, | |
NULL, &show_addr, NULL }, | |
#endif | |
{ MTAB_XTD|MTAB_VDV, 0, "VECTOR", NULL, | |
NULL, &show_vec, NULL }, | |
{ MTAB_XTD | MTAB_VDV, 0, "MAC", "MAC=xx:xx:xx:xx:xx:xx", | |
&xu_setmac, &xu_showmac, NULL }, | |
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "ETH", "ETH", | |
NULL, ð_show, NULL }, | |
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATS", "STATS", | |
&xu_set_stats, &xu_show_stats, NULL }, | |
{ MTAB_XTD | MTAB_VDV, 0, "TYPE", "TYPE={DEUNA|DELUA}", | |
&xu_set_type, &xu_show_type, NULL }, | |
{ 0 }, | |
}; | |
REG xua_reg[] = { | |
{ NULL } }; | |
DEBTAB xu_debug[] = { | |
{"TRACE", DBG_TRC}, | |
{"WARN", DBG_WRN}, | |
{"REG", DBG_REG}, | |
{"ETH", DBG_ETH}, | |
{0} | |
}; | |
DEVICE xu_dev = { | |
"XU", xua_unit, xua_reg, xu_mod, | |
1, XU_RDX, 8, 1, XU_RDX, 8, | |
&xu_ex, &xu_dep, &xu_reset, | |
NULL, &xu_attach, &xu_detach, | |
&xua_dib, DEV_FLTA | DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG, | |
0, xu_debug | |
}; | |
/* XUB does not exist in the PDP10 simulation */ | |
#if defined(IOBA_XUB) | |
DIB xub_dib = { IOBA_XUB, IOLN_XUB, &xu_rd, &xu_wr, | |
1, IVCL (XU), 0, { &xu_int } }; | |
UNIT xub_unit[] = { | |
{ UDATA (&xu_svc, UNIT_ATTABLE + UNIT_DISABLE, 0) } /* receive timer */ | |
}; | |
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 */ | |
}; | |
REG xub_reg[] = { | |
{ NULL } }; | |
DEVICE xub_dev = { | |
"XUB", xub_unit, xub_reg, xu_mod, | |
1, XU_RDX, 8, 1, XU_RDX, 8, | |
&xu_ex, &xu_dep, &xu_reset, | |
NULL, &xu_attach, &xu_detach, | |
&xub_dib, DEV_FLTA | DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG, | |
0, xu_debug | |
}; | |
#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 */ | |
}; | |
#else /* IOBA_XUB */ | |
#define XU_MAX_CONTROLLERS 1 | |
CTLR xu_ctrl[] = { | |
{&xu_dev, xua_unit, &xua_dib, &xua} /* XUA controller */ | |
}; | |
#endif /* IOBA_XUB */ | |
/*============================================================================*/ | |
/* 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, 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, 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(&xu->var->mac, cptr); | |
return status; | |
} | |
t_stat xu_set_stats (UNIT* uptr, int32 val, 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, void* desc) | |
{ | |
char* fmt = " %-24s%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); | |
return SCPE_OK; | |
} | |
t_stat xu_show_type (FILE* st, UNIT* uptr, int32 val, 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, 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; | |
} | |
/*============================================================================*/ | |
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_local (CTLR* xu, ETH_PACK* pack) | |
{ | |
return SCPE_NOFNC; /* not implemented yet */ | |
} | |
void xu_read_callback(CTLR* xu, int status) | |
{ | |
/* 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, 2, &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); | |
return status; | |
} | |
t_stat xu_svc(UNIT* uptr) | |
{ | |
int queue_size; | |
t_stat status; | |
CTLR* xu = xu_unit2ctlr(uptr); | |
const int one_second = clk_tps * tmr_poll; /* recalibrate timer */ | |
const ETH_MAC mop_multicast = {0xAB, 0x00, 0x00, 0x02, 0x00, 0x00}; | |
/* 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 */ | |
status = 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); | |
/* send identity packet when timer expires */ | |
if (--xu->var->idtmr <= 0) { | |
if ((xu->var->mode & MODE_DMNT) == 0) /* if maint msg is not disabled */ | |
status = xu_system_id(xu, mop_multicast, 0); /* then send ID packet */ | |
xu->var->idtmr = XU_ID_TIMER_VAL; /* reset timer */ | |
} | |
/* has one second timer expired? if so, update stats and reset timer */ | |
if (++xu->var->sectmr >= XU_SERVICE_INTERVAL) { | |
upd_stat16 (&xu->var->stats.secs, 1); | |
xu->var->sectmr = 0; | |
} | |
/* resubmit service timer if controller not halted */ | |
switch (xu->var->pcsr1 & PCSR1_STATE) { | |
case STATE_READY: | |
case STATE_RUNNING: | |
sim_activate(&xu->unit[0], one_second/XU_SERVICE_INTERVAL); | |
break; | |
}; | |
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) | |
{ | |
t_stat status; | |
const int one_second = clk_tps * tmr_poll; /* recalibrate timer */ | |
sim_debug(DBG_TRC, xu->dev, "xu_sw_reset()\n"); | |
/* Clear the registers. */ | |
xu->var->pcsr0 = 0; | |
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; 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)); | |
xu->var->setup.mac_count = 1; | |
if (xu->var->etherface) | |
status = eth_filter (xu->var->etherface, xu->var->setup.mac_count, | |
&xu->var->mac, xu->var->setup.multicast, | |
xu->var->setup.promiscuous); | |
/* activate device if not disabled - cancel first, just in case */ | |
if ((xu->dev->flags & DEV_DIS) == 0) { | |
sim_cancel(&xu->unit[0]); | |
sim_activate(&xu->unit[0], one_second/XU_SERVICE_INTERVAL); | |
} | |
return SCPE_OK; | |
} | |
/* Reset device. */ | |
t_stat xu_reset(DEVICE* dptr) | |
{ | |
t_stat status; | |
CTLR* xu = xu_dev2ctlr(dptr); | |
/* 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 SCPE_OK; | |
} | |
/* Perform one of the defined ancillary functions. */ | |
int32 xu_command(CTLR* xu) | |
{ | |
uint32 udbb; | |
int fnc, mtlen; | |
uint16 value; | |
t_stat status, rstatus, wstatus, wstatus2, wstatus3; | |
struct xu_stats* stats = &xu->var->stats; | |
uint16* udb = xu->var->udb; | |
static 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, MAP); | |
if (rstatus != SCPE_OK) | |
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_WriteW(xu->var->pcbb + 2, 6, (uint16*)xu->var->mac, MAP); | |
if (wstatus != SCPE_OK) | |
return PCSR0_PCEI + 1; | |
break; | |
case FC_RPA: /* read current physical address */ | |
wstatus = Map_WriteW(xu->var->pcbb + 2, 6, (uint16*)&xu->var->setup.macs[0], MAP); | |
if (wstatus != SCPE_OK) | |
return PCSR0_PCEI + 1; | |
break; | |
case FC_WPA: /* write current physical address */ | |
rstatus = Map_ReadW(xu->var->pcbb + 2, 6, (uint16*)&xu->var->setup.macs[0], MAP); | |
if (xu->var->pcb[1] & 1) return PCSR0_PCEI; | |
break; | |
case FC_WMAL: /* write multicast address list */ | |
mtlen = (xu->var->pcb[2] & 0xFF00) >> 8; | |
if (mtlen > 10) return PCSR0_PCEI; | |
udbb = xu->var->pcb[1] | ((xu->var->pcb[2] & 03) << 16); | |
rstatus = Map_ReadW(udbb, mtlen * 6, (uint16*) &xu->var->setup.macs[1], MAP); | |
if (rstatus == SCPE_OK) { | |
xu->var->setup.mac_count = mtlen + 1; | |
status = 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] = (xu->var->telen << 8) + ((xu->var->tdrb >> 16) & 3); | |
xu->var->udb[2] = xu->var->trlen; | |
xu->var->udb[3] = xu->var->rdrb & 0177776; | |
xu->var->udb[4] = (xu->var->relen << 8) + ((xu->var->rdrb >> 16) & 3); | |
xu->var->udb[5] = 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, MAP); | |
if (wstatus != SCPE_OK) | |
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, MAP); | |
if (rstatus != SCPE_OK) | |
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; | |
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] = 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, MAP); | |
if (wstatus != SCPE_OK) { | |
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 = xu->var->mode; | |
wstatus = Map_WriteW(xu->var->pcbb+2, 2, &value, MAP); | |
if (wstatus != SCPE_OK) | |
return PCSR0_PCEI + 1; | |
break; | |
case FC_WMODE: /* write mode register */ | |
value = xu->var->mode; | |
xu->var->mode = xu->var->pcb[1]; | |
/* 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)) | |
status = eth_filter (xu->var->etherface, xu->var->setup.mac_count, | |
&xu->var->mac, 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, MAP); | |
value = 10; | |
wstatus2 = Map_WriteW(xu->var->pcbb+4, 2, &value, MAP); | |
value = 32; | |
wstatus3 = Map_WriteW(xu->var->pcbb+6, 2, &value, MAP); | |
if ((wstatus != SCPE_OK) || (wstatus2 != SCPE_OK) || (wstatus3 != SCPE_OK)) | |
return PCSR0_PCEI + 1; | |
if (fnc = FC_RCSTAT) | |
xu->var->stat &= 0377; /* clear high byte */ | |
break; | |
default: /* Unknown (unimplemented) command. */ | |
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, off; | |
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); | |
/* 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, MAP); | |
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; | |
break; | |
} | |
/* set buffer length and address */ | |
slen = xu->var->rxhdr[0]; | |
segb = xu->var->rxhdr[1] + ((xu->var->rxhdr[2] & 3) << 16); | |
/* 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; | |
off = 0; | |
} | |
/* figure out chained packet size */ | |
wlen = item->packet.len - item->packet.used; | |
if (wlen > slen) | |
wlen = slen; | |
/* transfer chained packet to host buffer */ | |
wstatus = Map_WriteB (segb, wlen, &item->packet.msg[off], MAP); | |
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; | |
off += wlen; | |
/* Is this the end-of-frame? */ | |
if (item->packet.used == item->packet.len) { | |
/* mark end-of-frame */ | |
xu->var->rxhdr[2] |= RXR_ENF; | |
/* | |
* 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 | |
*/ | |
xu->var->rxhdr[3] &= ~RXR_MLEN; | |
xu->var->rxhdr[3] |= (item->packet.len + 4/*CRC*/); | |
/* 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 */ | |
/* update the ring entry in host memory. */ | |
wstatus = Map_WriteW (ba, 8, xu->var->rxhdr, MAP); | |
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); | |
} | |
void xu_process_transmit(CTLR* xu) | |
{ | |
uint32 segb, ba; | |
int slen, wlen, i, off, giant, runt; | |
t_stat rstatus, wstatus; | |
const ETH_MAC zeros = {0, 0, 0, 0, 0, 0}; | |
sim_debug(DBG_TRC, xu->dev, "xu_process_transmit()\n"); | |
for (;;) { | |
/* get next transmit buffer */ | |
ba = xu->var->tdrb + (xu->var->telen * 2) * xu->var->txnext; | |
rstatus = Map_ReadW (ba, 8, xu->var->txhdr, MAP); | |
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], MAP); | |
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->mode & MODE_TPAD)) { | |
xu->var->write_buffer.len = ETH_MIN_PACKET; | |
runt = 1; | |
} | |
/* 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, 1, &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; | |
} | |
/* update transmit status in transmit buffer */ | |
if (xu->var->write_buffer.status != 0) { | |
/* failure */ | |
const uint16 tdr = 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, MAP); | |
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) | |
{ | |
char* msg; | |
int command = xu->var->pcsr0 & PCSR0_PCMD; | |
int state = xu->var->pcsr1 & PCSR1_STATE; | |
int bits; | |
static 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) { | |
case CMD_NOOP: /* NO-OP */ | |
/* NOOP does NOT set 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_GETCMD: /* GET COMMAND */ | |
bits = xu_command(xu); | |
xu->var->pcsr0 |= PCSR0_DNI; | |
break; | |
case CMD_SELFTEST: /* SELFTEST */ | |
xu_sw_reset(xu); | |
xu->var->pcsr0 |= PCSR0_DNI; | |
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_BOOT: /* BOOT */ | |
/* not implemented */ | |
msg = "%s: BOOT command not implemented!\n"; | |
printf (msg, xu->dev->name); | |
if (sim_log) fprintf(sim_log, msg, xu->dev->name); | |
xu->var->pcsr0 |= PCSR0_PCEI; | |
break; | |
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_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_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 */ | |
xu->var->pcsr0 |= PCSR0_DNI; | |
break; /* all reserved commands act as a no-op but set DNI */ | |
} /* 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; | |
sim_debug(DBG_TRC, xu->dev, "xu_rd(), PCSR%d\n", reg); | |
if (PA & 1) | |
sim_debug(DBG_WRN, xu->dev, "xu_rd(), Unexpected Odd address access of PCSR%d\n", reg); | |
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; | |
} | |
return SCPE_OK; | |
} | |
t_stat xu_wr(int32 data, int32 PA, int32 access) | |
{ | |
CTLR* xu = xu_pa2ctlr(PA); | |
int reg = (PA >> 1) & 03; | |
sim_debug(DBG_TRC, xu->dev, "xu_wr(), PCSR%d\n", reg); | |
switch (reg) { | |
case 00: | |
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. */ | |
break; | |
} | |
} | |
/* RESET function requested? */ | |
if (data & PCSR0_RSET) { | |
xu_sw_reset(xu); | |
xu->var->pcsr0 |= PCSR0_DNI; | |
xu_setclrint(xu, 0); | |
return SCPE_OK; | |
} | |
/* Nope. Handle the INTE interlock thingie. */ | |
if ((xu->var->pcsr0 ^ data) & PCSR0_INTE) { | |
xu->var->pcsr0 ^= PCSR0_INTE; | |
xu->var->pcsr0 |= PCSR0_DNI; | |
} 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, 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 = malloc(strlen(cptr) + 1); | |
if (tptr == NULL) return SCPE_MEM; | |
strcpy(tptr, cptr); | |
xu->var->etherface = malloc(sizeof(ETH_DEV)); | |
if (!xu->var->etherface) 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; | |
} | |
uptr->filename = tptr; | |
uptr->flags |= UNIT_ATT; | |
/* reset the device with the new attach info */ | |
xu_reset(xu->dev); | |
return SCPE_OK; | |
} | |
/* detach device: */ | |
t_stat xu_detach(UNIT* uptr) | |
{ | |
t_stat status; | |
CTLR* xu = xu_unit2ctlr(uptr); | |
sim_debug(DBG_TRC, xu->dev, "xu_detach()\n"); | |
if (uptr->flags & UNIT_ATT) { | |
status = eth_close (xu->var->etherface); | |
free(xu->var->etherface); | |
xu->var->etherface = 0; | |
free(uptr->filename); | |
uptr->filename = NULL; | |
uptr->flags &= ~UNIT_ATT; | |
} | |
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 */ | |
} |