| /* 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, 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); | |
| t_stat xu_show_throttle (FILE* st, UNIT* uptr, int32 val, void* desc); | |
| t_stat xu_set_throttle (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); | |
| void xu_dump_rxring(CTLR* xu); | |
| void xu_dump_txring(CTLR* xu); | |
| t_stat xu_show_filters (FILE* st, UNIT* uptr, int32 val, 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, 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, ð_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, 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) | |
| { | |
| 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, 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, 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; | |
| } | |
| t_stat xu_show_throttle (FILE* st, UNIT* uptr, int32 val, 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, char* cptr, void* desc) | |
| { | |
| CTLR* xu = xu_unit2ctlr(uptr); | |
| char tbuf[CBUFSIZE], gbuf[CBUFSIZE]; | |
| 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 = pack->msg[offset] | (pack->msg[offset+1] << 8); | |
| sim_debug(DBG_TRC, xu->dev, "xu_process_loopback()\n"); | |
| 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"); | |
| /* 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[21] = mac_w[0]; /* HA<15:00> */ | |
| udb[22] = mac_w[1]; /* HA<31:16> */ | |
| udb[23] = mac_w[2]; /* HA<47:32> */ | |
| udb[24] = 0x64; /* dtype */ | |
| udb[25] = (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, (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, 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], 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, 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 08-00-2B-CC-DD-EE, which should be sufficient if\n"); | |
| fprintf (st, "there is only one SIMH DEUNA/DELUA controller on your LAN. Two cards with the\n"); | |
| fprintf (st, "same MAC address will see each other's packets, resulting in a serious mess.\n\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"; | |
| } | |