| /* pdp11_dmc.c: DMC11 Emulation | |
| ------------------------------------------------------------------------------ | |
| Copyright (c) 2011, Robert M. A. Jarratt | |
| 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. | |
| ------------------------------------------------------------------------------ | |
| I/O is done through sockets so that the remote system can be on the same host machine. The device starts polling for | |
| incoming connections when it receives its first read buffer. The device opens the connection for writing when it receives | |
| the first write buffer. | |
| Transmit and receive buffers are added to their respective queues and the polling method in dmc_svc() checks for input | |
| and sends any output. | |
| On the wire the format is a 2-byte block length followed by that number of bytes. Some of the diagnostics expect to receive | |
| the same number of bytes in a buffer as were sent by the other end. Using sockets without a block length can cause the | |
| buffers to coalesce and then the buffer lengths in the diagnostics fail. The block length is always sent in network byte | |
| order. | |
| Tested with two diagnostics. To run the diagnostics set the default directory to SYS$MAINTENANCE, run ESSAA and then | |
| configure it for the DMC-11 with the following commands: | |
| The above commands can be put into a COM file in SYS$MAINTENANCE (works on VMS 3.0 but not 4.6, not sure why). | |
| ATT DW780 SBI DW0 3 4 | |
| ATT DMC11 DW0 XMA0 760070 300 5 | |
| SELECT XMA0 | |
| (if putting these into a COM file to be executed by ESSAA add a "DS> " prefix) | |
| The first is EVDCA which takes no parameters. Invoke it with the command R EVDCA. This diagnostic uses the DMC-11 loopback | |
| functionality and the transmit port is not used when LU LOOP is enabled. Seems to work only under later versions of VMS | |
| such as 4.6, does not work on 3.0. | |
| The second is EVDMC, invoke this with the command R EVDMC. For this I used the following commands inside the diagnostic: | |
| RUN MODE=TRAN on one machine | |
| RUN MODE=REC on the other (unless the one instance is configured with the ports looping back). | |
| You can add /PASS=n to the above commands to get the diagnostic to send and receive more buffers. | |
| The other test was to configure DECnet on VMS 4.6 and do SET HOST. | |
| */ | |
| // TODO: Avoid need for manifests and newest runtime, compile with 2003 | |
| // TODO: Investigate line number and set parameters at the unit level (?) | |
| // TODO: Multipoint. In this case perhaps don't need transmit port, allow all lines to connect to port on control node. | |
| // TODO: Show active connections like DZ does, for multipoint. | |
| // TODO: Test MOP. | |
| // TODO: Implement actual DDCMP protocol and run over UDP. | |
| // TODO: Allow NCP SHOW COUNTERS to work (think this is the base address thing). Since fixing how I get the addresses this should work now. | |
| #include <time.h> | |
| #include <ctype.h> | |
| #include "pdp11_dmc.h" | |
| #define POLL 1000 | |
| #define TRACE_BYTES_PER_LINE 16 | |
| struct csrs { | |
| uint16 sel0; | |
| uint16 sel2; | |
| uint16 sel4; | |
| uint16 sel6; | |
| }; | |
| typedef struct csrs CSRS; | |
| typedef enum | |
| { | |
| Initialised, /* after MASTER CLEAR */ | |
| Running /* after any transmit or receive buffer has been supplied */ | |
| } ControllerState; | |
| typedef enum | |
| { | |
| Idle, | |
| InputTransfer, | |
| OutputTransfer | |
| } TransferState; | |
| typedef enum | |
| { | |
| Available, /* when empty or partially filled on read */ | |
| ContainsData, | |
| TransferInProgress | |
| } BufferState; | |
| typedef struct | |
| { | |
| int isPrimary; | |
| SOCKET socket; // socket used bidirectionally | |
| int receive_readable; | |
| char *receive_port; | |
| int transmit_writeable; | |
| char transmit_host[CBUFSIZE]; | |
| int transmit_is_loopback; /* if true the transmit socket is the loopback to the receive */ | |
| int speed; /* bits per second in each direction, 0 for no limit */ | |
| int last_second; | |
| int bytes_sent_in_last_second; | |
| int bytes_received_in_last_second; | |
| time_t last_connect_attempt; | |
| } LINE; | |
| /* A partially filled buffer (during a read from the socket) will have block_len_bytes_read = 1 or actual_bytes_transferred < actual_block_len */ | |
| typedef struct buffer | |
| { | |
| uint32 address; /* unibus address of the buffer */ | |
| uint16 count; /* size of the buffer passed to the device by the driver */ | |
| uint16 actual_block_len; /* actual length of the received block */ | |
| uint8 *transfer_buffer; /* the buffer into which data is received or from which it is transmitted*/ | |
| int block_len_bytes_read; /* the number of bytes read so far for the block length */ | |
| int actual_bytes_transferred; /* the number of bytes from the actual block that have been read or written so far*/ | |
| struct buffer *next; /* next buffer in the queue */ | |
| BufferState state; /* state of this buffer */ | |
| int is_loopback; /* loopback was requested when this buffer was queued */ | |
| } BUFFER; | |
| typedef struct | |
| { | |
| char * name; | |
| BUFFER queue[BUFFER_QUEUE_SIZE]; | |
| int head; | |
| int tail; | |
| int count; | |
| struct dmc_controller *controller; /* back pointer to the containing controller */ | |
| } BUFFER_QUEUE; | |
| typedef struct | |
| { | |
| int started; | |
| clock_t start_time; | |
| clock_t cumulative_time; | |
| } TIMER; | |
| typedef struct | |
| { | |
| TIMER between_polls_timer; | |
| TIMER poll_timer; | |
| uint32 poll_count; | |
| } UNIT_STATS; | |
| typedef enum | |
| { | |
| DMC, | |
| DMR, | |
| DMP | |
| } DEVTYPE; | |
| struct dmc_controller { | |
| CSRS *csrs; | |
| DEVICE *device; | |
| ControllerState state; | |
| TransferState transfer_state; /* current transfer state (type of transfer) */ | |
| int transfer_type; | |
| int transfer_in_io; // remembers IN I/O setting at start of input transfer as host changes it during transfer! | |
| LINE *line; | |
| BUFFER_QUEUE *receive_queue; | |
| BUFFER_QUEUE *transmit_queue; | |
| SOCKET master_socket; | |
| int32 connect_poll_interval; | |
| DEVTYPE dev_type; | |
| uint32 rxi; | |
| uint32 txi; | |
| uint32 buffers_received_from_net; | |
| uint32 buffers_transmitted_to_net; | |
| uint32 receive_buffer_output_transfers_completed; | |
| uint32 transmit_buffer_output_transfers_completed; | |
| uint32 receive_buffer_input_transfers_completed; | |
| uint32 transmit_buffer_input_transfers_completed; | |
| }; | |
| typedef struct dmc_controller CTLR; | |
| t_stat dmc_rd(int32* data, int32 PA, int32 access); | |
| t_stat dmc_wr(int32 data, int32 PA, int32 access); | |
| t_stat dmc_svc(UNIT * uptr); | |
| t_stat dmc_reset (DEVICE * dptr); | |
| t_stat dmc_attach (UNIT * uptr, char * cptr); | |
| int dmc_isattached(CTLR *controller); | |
| t_stat dmc_detach (UNIT * uptr); | |
| int32 dmc_rxint (void); | |
| int32 dmc_txint (void); | |
| t_stat dmc_setpeer (UNIT* uptr, int32 val, char* cptr, void* desc); | |
| t_stat dmc_showpeer (FILE* st, UNIT* uptr, int32 val, void* desc); | |
| t_stat dmc_setspeed (UNIT* uptr, int32 val, char* cptr, void* desc); | |
| t_stat dmc_showspeed (FILE* st, UNIT* uptr, int32 val, void* desc); | |
| t_stat dmc_settype (UNIT* uptr, int32 val, char* cptr, void* desc); | |
| t_stat dmc_showtype (FILE* st, UNIT* uptr, int32 val, void* desc); | |
| t_stat dmc_setstats (UNIT* uptr, int32 val, char* cptr, void* desc); | |
| t_stat dmc_showstats (FILE* st, UNIT* uptr, int32 val, void* desc); | |
| t_stat dmc_setconnectpoll (UNIT* uptr, int32 val, char* cptr, void* desc); | |
| t_stat dmc_showconnectpoll (FILE* st, UNIT* uptr, int32 val, void* desc); | |
| t_stat dmc_setlinemode (UNIT* uptr, int32 val, char* cptr, void* desc); | |
| t_stat dmc_showlinemode (FILE* st, UNIT* uptr, int32 val, void* desc); | |
| int dmc_is_dmc(CTLR *controller); | |
| int dmc_is_rqi_set(CTLR *controller); | |
| int dmc_is_rdyi_set(CTLR *controller); | |
| int dmc_is_iei_set(CTLR *controller); | |
| int dmc_is_ieo_set(CTLR *controller); | |
| void dmc_process_command(CTLR *controller); | |
| int dmc_buffer_fill_receive_buffers(CTLR *controller); | |
| void dmc_start_transfer_receive_buffer(CTLR *controller); | |
| int dmc_buffer_send_transmit_buffers(CTLR *controller); | |
| void dmc_buffer_queue_init(CTLR *controller, BUFFER_QUEUE *q, char *name); | |
| BUFFER *dmc_buffer_queue_head(BUFFER_QUEUE *q); | |
| int dmc_buffer_queue_full(BUFFER_QUEUE *q); | |
| void dmc_buffer_queue_get_stats(BUFFER_QUEUE *q, int *available, int *contains_data, int *transfer_in_progress); | |
| void dmc_start_transfer_transmit_buffer(CTLR *controller); | |
| void dmc_error_and_close_receive(CTLR *controller, char *format); | |
| void dmc_close_receive(CTLR *controller, char *reason, char *from); | |
| void dmc_close_transmit(CTLR *controller, char *reason); | |
| int dmc_get_socket(CTLR *controller, int forRead); | |
| int dmc_get_receive_socket(CTLR *controller, int forRead); | |
| int dmc_get_transmit_socket(CTLR *controller, int is_loopback, int forRead); | |
| void dmc_line_update_speed_stats(LINE *line); | |
| UNIT_STATS *dmc_get_unit_stats(UNIT *uptr); | |
| void dmc_set_unit_stats(UNIT *uptr, UNIT_STATS *stats); | |
| DEBTAB dmc_debug[] = { | |
| {"TRACE", DBG_TRC}, | |
| {"WARN", DBG_WRN}, | |
| {"REG", DBG_REG}, | |
| {"INFO", DBG_INF}, | |
| {"DATA", DBG_DAT}, | |
| {"DATASUM",DBG_DTS}, | |
| {"SOCKET", DBG_SOK}, | |
| {"CONNECT", DBG_CON}, | |
| {0} | |
| }; | |
| UNIT dmc_unit[] = { | |
| { UDATA (&dmc_svc, UNIT_IDLE|UNIT_ATTABLE|UNIT_DISABLE, 0) }, | |
| { UDATA (&dmc_svc, UNIT_IDLE|UNIT_ATTABLE|UNIT_DISABLE, 0) }, | |
| { UDATA (&dmc_svc, UNIT_IDLE|UNIT_ATTABLE|UNIT_DISABLE, 0) }, | |
| { UDATA (&dmc_svc, UNIT_IDLE|UNIT_ATTABLE|UNIT_DISABLE, 0) } | |
| }; | |
| UNIT dmp_unit[] = { | |
| { UDATA (&dmc_svc, UNIT_IDLE|UNIT_ATTABLE|UNIT_DISABLE, 0) } | |
| }; | |
| CSRS dmc_csrs[DMC_NUMDEVICE]; | |
| CSRS dmp_csrs[DMP_NUMDEVICE]; | |
| REG dmca_reg[] = { | |
| { HRDATA (SEL0, dmc_csrs[0].sel0, 16) }, | |
| { HRDATA (SEL2, dmc_csrs[0].sel2, 16) }, | |
| { HRDATA (SEL4, dmc_csrs[0].sel4, 16) }, | |
| { HRDATA (SEL6, dmc_csrs[0].sel6, 16) }, | |
| { GRDATA (BSEL0, dmc_csrs[0].sel0, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL1, dmc_csrs[0].sel0, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL2, dmc_csrs[0].sel2, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL3, dmc_csrs[0].sel2, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL4, dmc_csrs[0].sel4, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL5, dmc_csrs[0].sel4, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL6, dmc_csrs[0].sel6, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL7, dmc_csrs[0].sel6, DEV_RDX, 8, 8) }, | |
| { NULL } }; | |
| REG dmcb_reg[] = { | |
| { HRDATA (SEL0, dmc_csrs[1].sel0, 16) }, | |
| { HRDATA (SEL2, dmc_csrs[1].sel2, 16) }, | |
| { HRDATA (SEL4, dmc_csrs[1].sel4, 16) }, | |
| { HRDATA (SEL6, dmc_csrs[1].sel6, 16) }, | |
| { GRDATA (BSEL0, dmc_csrs[1].sel0, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL1, dmc_csrs[1].sel0, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL2, dmc_csrs[1].sel2, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL3, dmc_csrs[1].sel2, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL4, dmc_csrs[1].sel4, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL5, dmc_csrs[1].sel4, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL6, dmc_csrs[1].sel6, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL7, dmc_csrs[1].sel6, DEV_RDX, 8, 8) }, | |
| { NULL } }; | |
| REG dmcc_reg[] = { | |
| { HRDATA (SEL0, dmc_csrs[2].sel0, 16) }, | |
| { HRDATA (SEL2, dmc_csrs[2].sel2, 16) }, | |
| { HRDATA (SEL4, dmc_csrs[2].sel4, 16) }, | |
| { HRDATA (SEL6, dmc_csrs[2].sel6, 16) }, | |
| { GRDATA (BSEL0, dmc_csrs[2].sel0, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL1, dmc_csrs[2].sel0, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL2, dmc_csrs[2].sel2, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL3, dmc_csrs[2].sel2, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL4, dmc_csrs[2].sel4, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL5, dmc_csrs[2].sel4, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL6, dmc_csrs[2].sel6, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL7, dmc_csrs[2].sel6, DEV_RDX, 8, 8) }, | |
| { NULL } }; | |
| REG dmcd_reg[] = { | |
| { HRDATA (SEL0, dmc_csrs[3].sel0, 16) }, | |
| { HRDATA (SEL2, dmc_csrs[3].sel2, 16) }, | |
| { HRDATA (SEL4, dmc_csrs[3].sel4, 16) }, | |
| { HRDATA (SEL6, dmc_csrs[3].sel6, 16) }, | |
| { GRDATA (BSEL0, dmc_csrs[3].sel0, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL1, dmc_csrs[3].sel0, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL2, dmc_csrs[3].sel2, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL3, dmc_csrs[3].sel2, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL4, dmc_csrs[3].sel4, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL5, dmc_csrs[3].sel4, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL6, dmc_csrs[3].sel6, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL7, dmc_csrs[3].sel6, DEV_RDX, 8, 8) }, | |
| { NULL } }; | |
| REG dmp_reg[] = { | |
| { HRDATA (SEL0, dmc_csrs[3].sel0, 16) }, | |
| { HRDATA (SEL2, dmc_csrs[3].sel2, 16) }, | |
| { HRDATA (SEL4, dmc_csrs[3].sel4, 16) }, | |
| { HRDATA (SEL6, dmc_csrs[3].sel6, 16) }, | |
| { GRDATA (BSEL0, dmc_csrs[3].sel0, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL1, dmc_csrs[3].sel0, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL2, dmc_csrs[3].sel2, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL3, dmc_csrs[3].sel2, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL4, dmc_csrs[3].sel4, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL5, dmc_csrs[3].sel4, DEV_RDX, 8, 8) }, | |
| { GRDATA (BSEL6, dmc_csrs[3].sel6, DEV_RDX, 8, 0) }, | |
| { GRDATA (BSEL7, dmc_csrs[3].sel6, DEV_RDX, 8, 8) }, | |
| { NULL } }; | |
| MTAB dmc_mod[] = { | |
| { MTAB_XTD | MTAB_VDV, 0, "PEER", "PEER=address:port" ,&dmc_setpeer, &dmc_showpeer, NULL }, | |
| { MTAB_XTD | MTAB_VDV, 0, "SPEED", "SPEED" ,&dmc_setspeed, &dmc_showspeed, NULL }, | |
| #ifdef DMP | |
| { MTAB_XTD | MTAB_VDV, 0, "TYPE", "TYPE" ,&dmc_settype, &dmc_showtype, NULL }, | |
| #endif | |
| { MTAB_XTD | MTAB_VDV, 0, "LINEMODE", "LINEMODE" ,&dmc_setlinemode, &dmc_showlinemode, NULL }, | |
| { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATS", "STATS" ,&dmc_setstats, &dmc_showstats, NULL }, | |
| { MTAB_XTD | MTAB_VDV, 0, "CONNECTPOLL", "CONNECTPOLL" ,&dmc_setconnectpoll, &dmc_showconnectpoll, NULL }, | |
| { MTAB_XTD | MTAB_VDV, 006, "ADDRESS", "ADDRESS", &set_addr, &show_addr, NULL }, | |
| { MTAB_XTD |MTAB_VDV, 0, "VECTOR", "VECTOR", &set_vec, &show_vec, NULL }, | |
| { 0 }, | |
| }; | |
| #define IOLN_DMC 010 | |
| #ifndef IOBA_FLOAT | |
| #define IOBA_FLOAT 0 | |
| #define VEC_FLOAT 0 | |
| #endif | |
| DIB dmc_dib[] = | |
| { | |
| { IOBA_FLOAT, IOLN_DMC, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_FLOAT, {&dmc_rxint, &dmc_txint} }, | |
| { IOBA_FLOAT, IOLN_DMC, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_FLOAT, {&dmc_rxint, &dmc_txint} }, | |
| { IOBA_FLOAT, IOLN_DMC, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_FLOAT, {&dmc_rxint, &dmc_txint} }, | |
| { IOBA_FLOAT, IOLN_DMC, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_FLOAT, {&dmc_rxint, &dmc_txint} } | |
| }; | |
| #define IOLN_DMP 010 | |
| DIB dmp_dib[] = | |
| { | |
| { IOBA_FLOAT, IOLN_DMP, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_FLOAT, {&dmc_rxint, &dmc_txint} } | |
| }; | |
| DEVICE dmc_dev[] = | |
| { | |
| { "DMA", &dmc_unit[0], dmca_reg, dmc_mod, DMC_UNITSPERDEVICE, DMC_RDX, 8, 1, DMC_RDX, 8, | |
| NULL,NULL,&dmc_reset,NULL,&dmc_attach,&dmc_detach, | |
| &dmc_dib[0], DEV_FLTA | DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_NET | DEV_DEBUG, 0, dmc_debug }, | |
| { "DMB", &dmc_unit[1], dmcb_reg, dmc_mod, DMC_UNITSPERDEVICE, DMC_RDX, 8, 1, DMC_RDX, 8, | |
| NULL,NULL,&dmc_reset,NULL,&dmc_attach,&dmc_detach, | |
| &dmc_dib[1], DEV_FLTA | DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_NET | DEV_DEBUG, 0, dmc_debug }, | |
| { "DMC", &dmc_unit[2], dmcc_reg, dmc_mod, DMC_UNITSPERDEVICE, DMC_RDX, 8, 1, DMC_RDX, 8, | |
| NULL,NULL,&dmc_reset,NULL,&dmc_attach,&dmc_detach, | |
| &dmc_dib[2], DEV_FLTA | DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_NET | DEV_DEBUG, 0, dmc_debug }, | |
| { "DMD", &dmc_unit[3], dmcd_reg, dmc_mod, DMC_UNITSPERDEVICE, DMC_RDX, 8, 1, DMC_RDX, 8, | |
| NULL,NULL,&dmc_reset,NULL,&dmc_attach,&dmc_detach, | |
| &dmc_dib[3], DEV_FLTA | DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_NET | DEV_DEBUG, 0, dmc_debug } | |
| }; | |
| #ifdef DMP | |
| DEVICE dmp_dev[] = | |
| { | |
| { "DMP", &dmp_unit[0], dmp_reg, dmc_mod, DMP_UNITSPERDEVICE, DMC_RDX, 8, 1, DMC_RDX, 8, | |
| NULL,NULL,&dmc_reset,NULL,&dmc_attach,&dmc_detach, | |
| &dmp_dib[0], DEV_FLTA | DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_NET | DEV_DEBUG, 0, dmc_debug } | |
| }; | |
| #endif | |
| LINE dmc_line[DMC_NUMDEVICE] = | |
| { | |
| { 0, INVALID_SOCKET }, | |
| { 0, INVALID_SOCKET }, | |
| { 0, INVALID_SOCKET }, | |
| { 0, INVALID_SOCKET } | |
| }; | |
| BUFFER_QUEUE dmc_receive_queues[DMC_NUMDEVICE]; | |
| BUFFER_QUEUE dmc_transmit_queues[DMC_NUMDEVICE]; | |
| LINE dmp_line[DMP_NUMDEVICE] = | |
| { | |
| { 0, INVALID_SOCKET } | |
| }; | |
| BUFFER_QUEUE dmp_receive_queues[DMP_NUMDEVICE]; | |
| BUFFER_QUEUE dmp_transmit_queues[DMP_NUMDEVICE]; | |
| CTLR dmc_ctrls[] = | |
| { | |
| { &dmc_csrs[0], &dmc_dev[0], Initialised, Idle, 0, 0, &dmc_line[0], &dmc_receive_queues[0], &dmc_transmit_queues[0], INVALID_SOCKET, -1, 30, DMC }, | |
| { &dmc_csrs[1], &dmc_dev[1], Initialised, Idle, 0, 0, &dmc_line[1], &dmc_receive_queues[1], &dmc_transmit_queues[1], INVALID_SOCKET, -1, 30, DMC }, | |
| { &dmc_csrs[2], &dmc_dev[2], Initialised, Idle, 0, 0, &dmc_line[2], &dmc_receive_queues[2], &dmc_transmit_queues[2], INVALID_SOCKET, -1, 30, DMC }, | |
| { &dmc_csrs[3], &dmc_dev[3], Initialised, Idle, 0, 0, &dmc_line[3], &dmc_receive_queues[3], &dmc_transmit_queues[3], INVALID_SOCKET, -1, 30, DMC }, | |
| #ifdef DMP | |
| { &dmp_csrs[0], &dmp_dev[0], Initialised, Idle, 0, 0, &dmp_line[0], &dmp_receive_queues[0], &dmp_transmit_queues[0], INVALID_SOCKET, -1, 30, DMP } | |
| #endif | |
| }; | |
| extern int32 tmxr_poll; /* calibrated delay */ | |
| UNIT_STATS *dmc_get_unit_stats(UNIT *uptr) | |
| { | |
| UNIT_STATS *ans = NULL; | |
| #ifdef USE_ADDR64 | |
| ans = (UNIT_STATS *)(((t_uint64)uptr->u3 << 32) | uptr->u4); | |
| #else | |
| ans = (UNIT_STATS *)(uptr->u3); | |
| #endif | |
| return ans; | |
| } | |
| void dmc_set_unit_stats(UNIT *uptr, UNIT_STATS *stats) | |
| { | |
| #ifdef USE_ADDR64 | |
| uptr->u3 = (int32)((t_uint64)stats >> 32); | |
| uptr->u4 = (int32)((t_uint64)stats & 0xFFFFFFFF); | |
| #else | |
| uptr->u3 = (int32)stats; | |
| #endif | |
| } | |
| void dmc_reset_unit_stats(UNIT_STATS *s) | |
| { | |
| s->between_polls_timer.started = FALSE; | |
| s->poll_timer.started = FALSE; | |
| s->poll_count = 0; | |
| } | |
| int dmc_timer_started(TIMER *t) | |
| { | |
| return t->started; | |
| } | |
| void dmc_timer_start(TIMER *t) | |
| { | |
| t->start_time = clock(); | |
| t->cumulative_time = 0; | |
| t->started = TRUE; | |
| } | |
| void dmc_timer_stop(TIMER *t) | |
| { | |
| clock_t end_time = clock(); | |
| t->cumulative_time += end_time - t->start_time; | |
| } | |
| void dmc_timer_resume(TIMER *t) | |
| { | |
| t->start_time = clock(); | |
| } | |
| double dmc_timer_cumulative_seconds(TIMER *t) | |
| { | |
| return (double)t->cumulative_time/CLOCKS_PER_SEC; | |
| } | |
| int dmc_is_dmc(CTLR *controller) | |
| { | |
| return controller->dev_type != DMP; | |
| } | |
| CTLR *dmc_get_controller_from_unit(UNIT *unit) | |
| { | |
| int i; | |
| CTLR *ans = NULL; | |
| for (i = 0; i < DMC_NUMDEVICE + DMP_NUMDEVICE; i++) | |
| { | |
| if (dmc_ctrls[i].device->units == unit) | |
| { | |
| ans = &dmc_ctrls[i]; | |
| break; | |
| } | |
| } | |
| return ans; | |
| } | |
| CTLR* dmc_get_controller_from_address(uint32 address) | |
| { | |
| int i; | |
| for (i=0; i<DMC_NUMDEVICE + DMP_NUMDEVICE; i++) | |
| { | |
| DIB *dib = (DIB *)dmc_ctrls[i].device->ctxt; | |
| if ((address >= dib->ba) && (address < (dib->ba + dib->lnt))) | |
| return &dmc_ctrls[i]; | |
| } | |
| /* not found */ | |
| return 0; | |
| } | |
| CTLR *dmc_get_controller_from_device(DEVICE *device) | |
| { | |
| int i; | |
| CTLR *ans = NULL; | |
| for (i = 0; i < DMC_NUMDEVICE + DMP_NUMDEVICE; i++) | |
| { | |
| if (dmc_ctrls[i].device == device) | |
| { | |
| ans = &dmc_ctrls[i]; | |
| break; | |
| } | |
| } | |
| return ans; | |
| } | |
| t_stat dmc_showpeer (FILE* st, UNIT* uptr, int32 val, void* desc) | |
| { | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| if (controller->line->transmit_host[0]) | |
| { | |
| fprintf(st, "PEER=%s", controller->line->transmit_host); | |
| } | |
| else | |
| { | |
| fprintf(st, "PEER Unspecified"); | |
| } | |
| return SCPE_OK; | |
| } | |
| t_stat dmc_setpeer (UNIT* uptr, int32 val, char* cptr, void* desc) | |
| { | |
| t_stat status = SCPE_OK; | |
| char host[CBUFSIZE], port[CBUFSIZE]; | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| if (!cptr) return SCPE_IERR; | |
| if (uptr->flags & UNIT_ATT) return SCPE_ALATT; | |
| status = sim_parse_addr (cptr, host, sizeof(host), NULL, port, sizeof(port), NULL, NULL); | |
| if (status != SCPE_OK) | |
| return status; | |
| if (host[0] == '\0') | |
| return SCPE_ARG; | |
| strncpy(controller->line->transmit_host, cptr, sizeof(controller->line->transmit_host)-1); | |
| return status; | |
| } | |
| t_stat dmc_showspeed (FILE* st, UNIT* uptr, int32 val, void* desc) | |
| { | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| fprintf(st, "SPEED=%d", controller->line->speed); | |
| return SCPE_OK; | |
| } | |
| t_stat dmc_setspeed (UNIT* uptr, int32 val, char* cptr, void* desc) | |
| { | |
| t_stat status = SCPE_OK; | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| if (!cptr) return SCPE_IERR; | |
| if (uptr->flags & UNIT_ATT) return SCPE_ALATT; | |
| if (sscanf(cptr, "%d", &controller->line->speed) != 1) | |
| { | |
| status = SCPE_ARG; | |
| } | |
| return status; | |
| } | |
| t_stat dmc_showtype (FILE* st, UNIT* uptr, int32 val, void* desc) | |
| { | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| switch (controller->dev_type) | |
| { | |
| case DMC: | |
| { | |
| fprintf(st, "TYPE=DMC"); | |
| break; | |
| } | |
| case DMR: | |
| { | |
| fprintf(st, "TYPE=DMR"); | |
| break; | |
| } | |
| case DMP: | |
| { | |
| fprintf(st, "TYPE=DMP"); | |
| break; | |
| } | |
| default: | |
| { | |
| fprintf(st, "TYPE=???"); | |
| break; | |
| } | |
| } | |
| return SCPE_OK; | |
| } | |
| t_stat dmc_settype (UNIT* uptr, int32 val, char* cptr, void* desc) | |
| { | |
| char buf[80]; | |
| t_stat status = SCPE_OK; | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| if (!cptr) return SCPE_IERR; | |
| if (uptr->flags & UNIT_ATT) return SCPE_ALATT; | |
| if (sscanf(cptr, "%s", buf) != 1) | |
| { | |
| status = SCPE_ARG; | |
| } | |
| else | |
| { | |
| if (strcmp(buf,"DMC")==0) | |
| { | |
| controller->dev_type = DMC; | |
| } | |
| else if (strcmp(buf,"DMR")==0) | |
| { | |
| controller->dev_type = DMR; | |
| } | |
| else if (strcmp(buf,"DMP")==0) | |
| { | |
| controller->dev_type = DMP; | |
| } | |
| else | |
| { | |
| status = SCPE_ARG; | |
| } | |
| } | |
| return status; | |
| } | |
| t_stat dmc_showstats (FILE* st, UNIT* uptr, int32 val, void* desc) | |
| { | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| TIMER *poll_timer = &dmc_get_unit_stats(uptr)->poll_timer; | |
| TIMER *between_polls_timer = &dmc_get_unit_stats(uptr)->between_polls_timer; | |
| uint32 poll_count = dmc_get_unit_stats(uptr)->poll_count; | |
| if (dmc_timer_started(between_polls_timer) && poll_count > 0) | |
| { | |
| fprintf(st, "Average time between polls=%f (sec)\n", dmc_timer_cumulative_seconds(between_polls_timer)/poll_count); | |
| } | |
| else | |
| { | |
| fprintf(st, "Average time between polls=n/a\n"); | |
| } | |
| if (dmc_timer_started(poll_timer) && poll_count > 0) | |
| { | |
| fprintf(st, "Average time within poll=%f (sec)\n", dmc_timer_cumulative_seconds(poll_timer)/poll_count); | |
| } | |
| else | |
| { | |
| fprintf(st, "Average time within poll=n/a\n"); | |
| } | |
| fprintf(st, "Buffers received from the network=%d\n", controller->buffers_received_from_net); | |
| fprintf(st, "Buffers sent to the network=%d\n", controller->buffers_transmitted_to_net); | |
| fprintf(st, "Output transfers completed for receive buffers=%d\n", controller->receive_buffer_output_transfers_completed); | |
| fprintf(st, "Output transfers completed for transmit buffers=%d\n", controller->transmit_buffer_output_transfers_completed); | |
| fprintf(st, "Input transfers completed for receive buffers=%d\n", controller->receive_buffer_input_transfers_completed); | |
| fprintf(st, "Input transfers completed for transmit buffers=%d\n", controller->transmit_buffer_input_transfers_completed); | |
| return SCPE_OK; | |
| } | |
| t_stat dmc_setstats (UNIT* uptr, int32 val, char* cptr, void* desc) | |
| { | |
| t_stat status = SCPE_OK; | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| dmc_reset_unit_stats(dmc_get_unit_stats(uptr)); | |
| controller->receive_buffer_output_transfers_completed = 0; | |
| controller->transmit_buffer_output_transfers_completed = 0; | |
| controller->receive_buffer_input_transfers_completed = 0; | |
| controller->transmit_buffer_input_transfers_completed = 0; | |
| printf("Statistics reset\n" ); | |
| return status; | |
| } | |
| t_stat dmc_showconnectpoll (FILE* st, UNIT* uptr, int32 val, void* desc) | |
| { | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| fprintf(st, "CONNECTPOLL=%d", controller->connect_poll_interval); | |
| return SCPE_OK; | |
| } | |
| t_stat dmc_setconnectpoll (UNIT* uptr, int32 val, char* cptr, void* desc) | |
| { | |
| t_stat status = SCPE_OK; | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| if (!cptr) return SCPE_IERR; | |
| if (sscanf(cptr, "%d", &controller->connect_poll_interval) != 1) | |
| { | |
| status = SCPE_ARG; | |
| } | |
| return status; | |
| } | |
| t_stat dmc_showlinemode (FILE* st, UNIT* uptr, int32 val, void* desc) | |
| { | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| fprintf(st, "LINEMODE=%s", controller->line->isPrimary? "PRIMARY" : "SECONDARY"); | |
| return SCPE_OK; | |
| } | |
| t_stat dmc_setlinemode (UNIT* uptr, int32 val, char* cptr, void* desc) | |
| { | |
| t_stat status = SCPE_OK; | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| if (!cptr) return SCPE_IERR; | |
| if (uptr->flags & UNIT_ATT) return SCPE_ALATT; | |
| if (MATCH_CMD(cptr, "PRIMARY") == 0) | |
| { | |
| controller->line->isPrimary = 1; | |
| } | |
| else if (MATCH_CMD(cptr, "SECONDARY") == 0) | |
| { | |
| controller->line->isPrimary = 0; | |
| } | |
| else | |
| { | |
| status = SCPE_ARG; | |
| } | |
| return status; | |
| } | |
| void dmc_setrxint(CTLR *controller) | |
| { | |
| controller->rxi = 1; | |
| SET_INT(DMCRX); | |
| } | |
| void dmc_clrrxint(CTLR *controller) | |
| { | |
| controller->rxi = 0; | |
| CLR_INT(DMCRX); | |
| } | |
| void dmc_settxint(CTLR *controller) | |
| { | |
| controller->txi = 1; | |
| SET_INT(DMCTX); | |
| } | |
| void dmc_clrtxint(CTLR *controller) | |
| { | |
| controller->txi = 0; | |
| CLR_INT(DMCTX); | |
| } | |
| int dmc_getsel(int addr) | |
| { | |
| return (addr >> 1) & 03; | |
| } | |
| uint16 dmc_bitfld(int data, int start_bit, int length) | |
| { | |
| uint16 ans = data >> start_bit; | |
| uint32 mask = (1 << (length))-1; | |
| ans &= mask; | |
| return ans; | |
| } | |
| void dmc_dumpregsel0(CTLR *controller, int trace_level, char * prefix, uint16 data) | |
| { | |
| char *type_str = ""; | |
| uint16 type = dmc_bitfld(data, SEL0_TYPEI_BIT, 2); | |
| if (dmc_is_dmc(controller)) | |
| { | |
| if (dmc_is_rqi_set(controller)) | |
| { | |
| if (type==TYPE_BACCI) | |
| type_str = "BA/CC I"; | |
| else if (type==TYPE_CNTLI) | |
| type_str = "CNTL I"; | |
| else if (type==TYPE_BASEI) | |
| type_str = "BASE I"; | |
| else | |
| type_str = "?????"; | |
| } | |
| sim_debug( | |
| trace_level, | |
| controller->device, | |
| "%s SEL0 (0x%04x) %s%s%s%s%s%s%s%s\n", | |
| prefix, | |
| data, | |
| dmc_bitfld(data, SEL0_RUN_BIT, 1) ? "RUN " : "", | |
| dmc_bitfld(data, SEL0_MCLR_BIT, 1) ? "MCLR " : "", | |
| dmc_bitfld(data, SEL0_LU_LOOP_BIT, 1) ? "LU LOOP " : "", | |
| dmc_bitfld(data, SEL0_RDI_BIT, 1) ? "RDI " : "", | |
| dmc_bitfld(data, SEL0_DMC_IEI_BIT, 1) ? "IEI " : "", | |
| dmc_bitfld(data, SEL0_DMC_RQI_BIT, 1) ? "RQI " : "", | |
| dmc_bitfld(data, SEL0_IN_IO_BIT, 1) ? "IN I/O " : "", | |
| type_str | |
| ); | |
| } | |
| else | |
| { | |
| sim_debug( | |
| trace_level, | |
| controller->device, | |
| "%s SEL0 (0x%04x) %s%s%s%s%s%s\n", | |
| prefix, | |
| data, | |
| dmc_bitfld(data, SEL0_RUN_BIT, 1) ? "RUN " : "", | |
| dmc_bitfld(data, SEL0_MCLR_BIT, 1) ? "MCLR " : "", | |
| dmc_bitfld(data, SEL0_LU_LOOP_BIT, 1) ? "LU LOOP " : "", | |
| dmc_bitfld(data, SEL0_DMP_RQI_BIT, 1) ? "RQI " : "", | |
| dmc_bitfld(data, SEL0_DMP_IEO_BIT, 1) ? "IEO " : "", | |
| dmc_bitfld(data, SEL0_DMP_IEI_BIT, 1) ? "IEI " : "" | |
| ); | |
| } | |
| } | |
| void dmc_dumpregsel2(CTLR *controller, int trace_level, char *prefix, uint16 data) | |
| { | |
| char *type_str = ""; | |
| uint16 type = dmc_bitfld(data, SEL2_TYPEO_BIT, 2); | |
| if (type==TYPE_BACCO) | |
| type_str = "BA/CC O"; | |
| else if (type==TYPE_CNTLO) | |
| type_str = "CNTL O"; | |
| else | |
| type_str = "?????"; | |
| sim_debug( | |
| trace_level, | |
| controller->device, | |
| "%s SEL2 (0x%04x) PRIO=%d LINE=%d %s%s%s%s\n", | |
| prefix, | |
| data, | |
| dmc_bitfld(data, SEL2_PRIO_BIT, SEL2_PRIO_BIT_LENGTH), | |
| dmc_bitfld(data, SEL2_LINE_BIT, SEL2_LINE_BIT_LENGTH), | |
| dmc_bitfld(data, SEL2_RDO_BIT, 1) ? "RDO " : "", | |
| dmc_bitfld(data, SEL2_IEO_BIT, 1) ? "IEO " : "", | |
| dmc_bitfld(data, SEL2_OUT_IO_BIT, 1) ? "OUT I/O " : "", | |
| type_str | |
| ); | |
| } | |
| void dmc_dumpregsel4(CTLR *controller, int trace_level, char *prefix, uint16 data) | |
| { | |
| sim_debug(trace_level, controller->device, "%s SEL4 (0x%04x)\n", prefix, data); | |
| } | |
| void dmc_dumpregsel6(CTLR *controller, int trace_level, char *prefix, uint16 data) | |
| { | |
| sim_debug( | |
| trace_level, | |
| controller->device, | |
| "%s SEL6 (0x%04x) %s\n", | |
| prefix, | |
| data, | |
| dmc_bitfld(data, SEL6_LOST_DATA_BIT, 1) ? "LOST_DATA " : ""); | |
| } | |
| uint16 dmc_getreg(CTLR *controller, int reg, int ext) | |
| { | |
| uint16 ans = 0; | |
| switch (dmc_getsel(reg)) | |
| { | |
| case 00: | |
| ans = controller->csrs->sel0; | |
| if (ext) dmc_dumpregsel0(controller, DBG_REG, "Getting", ans); | |
| break; | |
| case 01: | |
| ans = controller->csrs->sel2; | |
| if (ext) dmc_dumpregsel2(controller, DBG_REG, "Getting", ans); | |
| break; | |
| case 02: | |
| ans = controller->csrs->sel4; | |
| if (ext) dmc_dumpregsel4(controller, DBG_REG, "Getting", ans); | |
| break; | |
| case 03: | |
| ans = controller->csrs->sel6; | |
| if (ext) dmc_dumpregsel6(controller, DBG_REG, "Getting", ans); | |
| break; | |
| default: | |
| { | |
| sim_debug(DBG_WRN, controller->device, "dmc_getreg(). Invalid register %d", reg); | |
| } | |
| } | |
| return ans; | |
| } | |
| void dmc_setreg(CTLR *controller, int reg, uint16 data, int ext) | |
| { | |
| char *trace = (ext) ? "Writing" : "Setting"; | |
| switch (dmc_getsel(reg)) | |
| { | |
| case 00: | |
| dmc_dumpregsel0(controller, DBG_REG, trace, data); | |
| controller->csrs->sel0 = data; | |
| break; | |
| case 01: | |
| dmc_dumpregsel2(controller, DBG_REG, trace, data); | |
| controller->csrs->sel2 = data; | |
| break; | |
| case 02: | |
| dmc_dumpregsel4(controller, DBG_REG, trace, data); | |
| controller->csrs->sel4 = data; | |
| break; | |
| case 03: | |
| dmc_dumpregsel6(controller, DBG_REG, trace, data); | |
| controller->csrs->sel6 = data; | |
| break; | |
| default: | |
| { | |
| sim_debug(DBG_WRN, controller->device, "dmc_setreg(). Invalid register %d", reg); | |
| } | |
| } | |
| } | |
| int dmc_is_master_clear_set(CTLR *controller) | |
| { | |
| return controller->csrs->sel0 & MASTER_CLEAR_MASK; | |
| } | |
| int dmc_is_lu_loop_set(CTLR *controller) | |
| { | |
| return controller->csrs->sel0 & LU_LOOP_MASK; | |
| } | |
| int dmc_is_rqi_set(CTLR *controller) | |
| { | |
| int ans = 0; | |
| if (dmc_is_dmc(controller)) | |
| { | |
| ans = controller->csrs->sel0 & DMC_RQI_MASK; | |
| } | |
| else | |
| { | |
| ans = controller->csrs->sel0 & DMP_RQI_MASK; | |
| } | |
| return ans; | |
| } | |
| int dmc_is_rdyi_set(CTLR *controller) | |
| { | |
| int ans = 0; | |
| if (dmc_is_dmc(controller)) | |
| { | |
| ans = controller->csrs->sel0 & DMC_RDYI_MASK; | |
| } | |
| else | |
| { | |
| ans = controller->csrs->sel2 & DMP_RDYI_MASK; | |
| } | |
| return ans; | |
| } | |
| int dmc_is_iei_set(CTLR *controller) | |
| { | |
| int ans = 0; | |
| if (dmc_is_dmc(controller)) | |
| { | |
| ans = controller->csrs->sel0 & DMC_IEI_MASK; | |
| } | |
| else | |
| { | |
| ans = controller->csrs->sel0 & DMP_IEI_MASK; | |
| } | |
| return ans; | |
| } | |
| int dmc_is_ieo_set(CTLR *controller) | |
| { | |
| int ans = 0; | |
| if (dmc_is_dmc(controller)) | |
| { | |
| ans = controller->csrs->sel2 & DMC_IEO_MASK; | |
| } | |
| else | |
| { | |
| ans = controller->csrs->sel0 & DMP_IEO_MASK; | |
| } | |
| return ans; | |
| } | |
| int dmc_is_in_io_set(CTLR *controller) | |
| { | |
| int ans = 0; | |
| if (dmc_is_dmc(controller)) | |
| { | |
| ans = controller->csrs->sel0 & DMC_IN_IO_MASK; | |
| } | |
| else | |
| { | |
| ans = !controller->csrs->sel2 & DMP_IN_IO_MASK; | |
| } | |
| return ans; | |
| } | |
| int dmc_is_out_io_set(CTLR *controller) | |
| { | |
| int ans = controller->csrs->sel2 & OUT_IO_MASK; | |
| return ans; | |
| } | |
| int dmc_is_rdyo_set(CTLR *controller) | |
| { | |
| return controller->csrs->sel2 & DMC_RDYO_MASK; | |
| } | |
| void dmc_set_rdyi(CTLR *controller) | |
| { | |
| if (dmc_is_dmc(controller)) | |
| { | |
| dmc_setreg(controller, 0, controller->csrs->sel0 | DMC_RDYI_MASK, 0); | |
| } | |
| else | |
| { | |
| dmc_setreg(controller, 2, controller->csrs->sel2 | DMP_RDYI_MASK, 0); | |
| } | |
| if (dmc_is_iei_set(controller)) | |
| { | |
| dmc_setrxint(controller); | |
| } | |
| } | |
| void dmc_clear_rdyi(CTLR *controller) | |
| { | |
| if (dmc_is_dmc(controller)) | |
| { | |
| dmc_setreg(controller, 0, controller->csrs->sel0 & ~DMC_RDYI_MASK, 0); | |
| } | |
| else | |
| { | |
| dmc_setreg(controller, 2, controller->csrs->sel2 & ~DMP_RDYI_MASK, 0); | |
| } | |
| } | |
| void dmc_set_rdyo(CTLR *controller) | |
| { | |
| dmc_setreg(controller, 2, controller->csrs->sel2 | DMC_RDYO_MASK, 0); | |
| if (dmc_is_ieo_set(controller)) | |
| { | |
| dmc_settxint(controller); | |
| } | |
| } | |
| void dmc_set_lost_data(CTLR *controller) | |
| { | |
| dmc_setreg(controller, 6, controller->csrs->sel6 | LOST_DATA_MASK, 0); | |
| } | |
| void dmc_clear_master_clear(CTLR *controller) | |
| { | |
| dmc_setreg(controller, 0, controller->csrs->sel0 & ~MASTER_CLEAR_MASK, 0); | |
| } | |
| void dmc_set_run(CTLR *controller) | |
| { | |
| dmc_setreg(controller, 0, controller->csrs->sel0 | RUN_MASK, 0); | |
| } | |
| int dmc_get_input_transfer_type(CTLR *controller) | |
| { | |
| int ans = 0; | |
| if (dmc_is_dmc(controller)) | |
| { | |
| ans = controller->csrs->sel0 & DMC_TYPE_INPUT_MASK; | |
| } | |
| else | |
| { | |
| ans = controller->csrs->sel2 & DMP_TYPE_INPUT_MASK; | |
| } | |
| return ans; | |
| } | |
| int dmc_get_output_transfer_type(CTLR *controller) | |
| { | |
| return controller->csrs->sel2 & TYPE_OUTPUT_MASK; | |
| } | |
| void dmc_set_type_output(CTLR *controller, int type) | |
| { | |
| dmc_setreg(controller, 2, controller->csrs->sel2 | (type & TYPE_OUTPUT_MASK), 0); | |
| } | |
| void dmc_set_out_io(CTLR *controller) | |
| { | |
| dmc_setreg(controller, 2, controller->csrs->sel2 | OUT_IO_MASK, 0); | |
| } | |
| void dmc_clear_out_io(CTLR *controller) | |
| { | |
| dmc_setreg(controller, 2, controller->csrs->sel2 & ~OUT_IO_MASK, 0); | |
| } | |
| void dmc_process_master_clear(CTLR *controller) | |
| { | |
| sim_debug(DBG_INF, controller->device, "Master clear\n"); | |
| dmc_clear_master_clear(controller); | |
| controller->state = Initialised; | |
| dmc_setreg(controller, 0, 0, 0); | |
| if (controller->dev_type == DMR) | |
| { | |
| /* DMR-11 indicates microdiagnostics complete when this is set */ | |
| dmc_setreg(controller, 2, 0x8000, 0); | |
| } | |
| else | |
| { | |
| /* preserve contents of BSEL3 if DMC-11 */ | |
| dmc_setreg(controller, 2, controller->csrs->sel2 & 0xFF00, 0); | |
| } | |
| if (controller->dev_type == DMP) | |
| { | |
| dmc_setreg(controller, 4, 077, 0); | |
| } | |
| else | |
| { | |
| dmc_setreg(controller, 4, 0, 0); | |
| } | |
| if (controller->dev_type == DMP) | |
| { | |
| dmc_setreg(controller, 6, 0305, 0); | |
| } | |
| else | |
| { | |
| dmc_setreg(controller, 6, 0, 0); | |
| } | |
| dmc_buffer_queue_init(controller, controller->receive_queue, "receive"); | |
| dmc_buffer_queue_init(controller, controller->transmit_queue, "transmit"); | |
| controller->transfer_state = Idle; | |
| dmc_set_run(controller); | |
| sim_cancel (controller->device->units); /* stop poll */ | |
| sim_activate_abs(controller->device->units, clk_cosched (tmxr_poll)); | |
| } | |
| void dmc_start_input_transfer(CTLR *controller) | |
| { | |
| int ok = 1; | |
| int type = dmc_get_input_transfer_type(controller); | |
| /* if this is a BA/CC I then check that the relevant queue has room first */ | |
| if (type == TYPE_BACCI) | |
| { | |
| ok = (dmc_is_in_io_set(controller) && !dmc_buffer_queue_full(controller->receive_queue)) | |
| || | |
| (!dmc_is_in_io_set(controller) && !dmc_buffer_queue_full(controller->transmit_queue)); | |
| } | |
| if (ok) | |
| { | |
| sim_debug(DBG_INF, controller->device, "Starting input transfer\n"); | |
| controller->transfer_state = InputTransfer; | |
| controller->transfer_type = type; | |
| controller->transfer_in_io = dmc_is_in_io_set(controller); | |
| dmc_set_rdyi(controller); | |
| } | |
| else | |
| { | |
| sim_debug(DBG_WRN, controller->device, "Input transfer request not granted as queue is full\n"); | |
| } | |
| } | |
| void dmc_start_data_output_transfer(CTLR *controller, uint32 addr, int16 count, int is_receive) | |
| { | |
| if (is_receive) | |
| { | |
| sim_debug(DBG_INF, controller->device, "Starting data output transfer for receive, address=0x%08x, count=%d\n", addr, count); | |
| dmc_set_out_io(controller); | |
| } | |
| else | |
| { | |
| sim_debug(DBG_INF, controller->device, "Starting data output transfer for transmit, address=0x%08x, count=%d\n", addr, count); | |
| dmc_clear_out_io(controller); | |
| } | |
| dmc_setreg(controller, 4, addr & 0xFFFF, 0); | |
| dmc_setreg(controller, 6, (((addr & 0x30000)) >> 2) | count, 0); | |
| controller->transfer_state = OutputTransfer; | |
| dmc_set_type_output(controller, TYPE_BACCO); | |
| dmc_set_rdyo(controller); | |
| } | |
| void dmc_start_control_output_transfer(CTLR *controller) | |
| { | |
| sim_debug(DBG_INF, controller->device, "Starting control output transfer\n"); | |
| controller->transfer_state = OutputTransfer; | |
| dmc_set_type_output(controller, TYPE_CNTLO); | |
| dmc_set_rdyo(controller); | |
| } | |
| t_stat dmc_svc(UNIT* uptr) | |
| { | |
| CTLR *controller; | |
| int32 poll; | |
| TIMER *poll_timer = &dmc_get_unit_stats(uptr)->poll_timer; | |
| TIMER *between_polls_timer = &dmc_get_unit_stats(uptr)->between_polls_timer; | |
| poll = clk_cosched (tmxr_poll); | |
| if (dmc_timer_started(between_polls_timer)) | |
| { | |
| dmc_timer_stop(between_polls_timer); | |
| } | |
| if (dmc_timer_started(poll_timer)) | |
| { | |
| dmc_timer_resume(poll_timer); | |
| } | |
| else | |
| { | |
| dmc_timer_start(poll_timer); | |
| } | |
| controller = dmc_get_controller_from_unit(uptr); | |
| if (dmc_isattached(controller)) | |
| { | |
| dmc_line_update_speed_stats(controller->line); | |
| dmc_buffer_fill_receive_buffers(controller); | |
| if (controller->transfer_state == Idle) dmc_start_transfer_receive_buffer(controller); | |
| dmc_buffer_send_transmit_buffers(controller); | |
| if (controller->transfer_state == Idle) dmc_start_transfer_transmit_buffer(controller); | |
| } | |
| /* resubmit service timer */ | |
| sim_activate(controller->device->units, poll); | |
| dmc_timer_stop(poll_timer); | |
| if (dmc_timer_started(between_polls_timer)) | |
| { | |
| dmc_timer_resume(between_polls_timer); | |
| } | |
| else | |
| { | |
| dmc_timer_start(between_polls_timer); | |
| } | |
| dmc_get_unit_stats(uptr)->poll_count++; | |
| return SCPE_OK; | |
| } | |
| void dmc_line_update_speed_stats(LINE *line) | |
| { | |
| clock_t current = clock(); | |
| int current_second = current / CLOCKS_PER_SEC; | |
| if (current_second != line->last_second) | |
| { | |
| line->bytes_received_in_last_second = 0; | |
| line->bytes_sent_in_last_second = 0; | |
| line->last_second = current_second; | |
| } | |
| } | |
| /* given the number of bytes sent/received in the last second, the number of bytes to send or receive and the line speed, calculate how many bytes can be sent/received now */ | |
| int dmc_line_speed_calculate_byte_length(int bytes_in_last_second, int num_bytes, int speed) | |
| { | |
| int ans; | |
| if (speed == 0) | |
| { | |
| ans = num_bytes; | |
| } | |
| else | |
| { | |
| int clocks_this_second = clock() % CLOCKS_PER_SEC; | |
| int allowable_bytes_to_date = ((speed/8) * clocks_this_second)/CLOCKS_PER_SEC; | |
| int allowed_bytes = allowable_bytes_to_date - bytes_in_last_second; | |
| if (allowed_bytes < 0) | |
| { | |
| allowed_bytes = 0; | |
| } | |
| if (num_bytes > allowed_bytes) | |
| { | |
| ans = allowed_bytes; | |
| } | |
| else | |
| { | |
| ans = num_bytes; | |
| } | |
| //sim_debug(DBG_WRN, dmc_ctrls[0].device, "Bytes in last second %4d, clocks this sec %3d allowable bytes %4d, requested %4d allowed %4d\n", bytes_in_last_second, clocks_this_second, allowable_bytes_to_date, num_bytes, ans); | |
| } | |
| return ans; | |
| } | |
| void dmc_buffer_trace_line(int tracelevel, CTLR *controller, uint8 *buf, int length, char *prefix) | |
| { | |
| char hex[TRACE_BYTES_PER_LINE*3+1]; | |
| char ascii[TRACE_BYTES_PER_LINE+1]; | |
| int i; | |
| hex[0] = 0; | |
| ascii[TRACE_BYTES_PER_LINE] = 0; | |
| for (i = 0; i<TRACE_BYTES_PER_LINE; i++) | |
| { | |
| if (i>=length) | |
| { | |
| strcat(hex, " "); | |
| ascii[i] = ' '; | |
| } | |
| else | |
| { | |
| char hexByte[4]; | |
| sprintf(hexByte, "%02X ", buf[i]); | |
| strcat(hex, hexByte); | |
| if (isprint(buf[i])) | |
| { | |
| ascii[i] = (char)buf[i]; | |
| } | |
| else | |
| { | |
| ascii[i] = '.'; | |
| } | |
| } | |
| } | |
| sim_debug(tracelevel, controller->device, "%s %s %s\n", prefix, hex, ascii); | |
| } | |
| void dmc_buffer_trace(CTLR *controller, uint8 *buf, int length, char *prefix, uint32 address) | |
| { | |
| int i; | |
| if (length >= 0 && controller->device->dctrl & DBG_DAT) | |
| { | |
| sim_debug(DBG_DAT, controller->device, "%s Buffer address 0x%08x (%d bytes)\n", prefix, address, length); | |
| for(i = 0; i < length / TRACE_BYTES_PER_LINE; i++) | |
| { | |
| dmc_buffer_trace_line(DBG_DAT, controller, buf + i*TRACE_BYTES_PER_LINE, TRACE_BYTES_PER_LINE, prefix); | |
| } | |
| if (length %TRACE_BYTES_PER_LINE > 0) | |
| { | |
| dmc_buffer_trace_line(DBG_DAT, controller, buf + length/TRACE_BYTES_PER_LINE, length % TRACE_BYTES_PER_LINE, prefix); | |
| } | |
| } | |
| else if (length >= 0 && controller->device->dctrl & DBG_DTS) | |
| { | |
| char prefix2[80]; | |
| sprintf(prefix2, "%s (len=%d)", prefix, length); | |
| dmc_buffer_trace_line(DBG_DTS, controller, buf, (length > TRACE_BYTES_PER_LINE)? TRACE_BYTES_PER_LINE : length, prefix2); | |
| } | |
| } | |
| void dmc_buffer_queue_init(CTLR *controller, BUFFER_QUEUE *q, char *name) | |
| { | |
| q->name = name; | |
| q->head = 0; | |
| q->tail = 0; | |
| q->count = 0; | |
| q->controller = controller; | |
| } | |
| int dmc_buffer_queue_full(BUFFER_QUEUE *q) | |
| { | |
| return q->count > BUFFER_QUEUE_SIZE; | |
| } | |
| void dmc_buffer_queue_add(BUFFER_QUEUE *q, uint32 address, uint16 count) | |
| { | |
| if (!dmc_buffer_queue_full(q)) | |
| { | |
| int new_buffer = 0; | |
| if (q->count > 0) | |
| { | |
| int last_buffer = q->tail; | |
| new_buffer = (q->tail +1) % BUFFER_QUEUE_SIZE; | |
| /* Link last buffer to the new buffer */ | |
| q->queue[last_buffer].next = &q->queue[new_buffer]; | |
| } | |
| else | |
| { | |
| q->head = 0; | |
| new_buffer = 0; | |
| } | |
| q->tail = new_buffer; | |
| q->queue[new_buffer].address = address; | |
| q->queue[new_buffer].count = count; | |
| q->queue[new_buffer].actual_block_len = 0; | |
| q->queue[new_buffer].transfer_buffer = NULL; | |
| q->queue[new_buffer].block_len_bytes_read = 0; | |
| q->queue[new_buffer].actual_bytes_transferred = 0; | |
| q->queue[new_buffer].next = NULL; | |
| q->queue[new_buffer].state = Available; | |
| q->queue[new_buffer].is_loopback = dmc_is_lu_loop_set(q->controller); | |
| q->count++; | |
| sim_debug(DBG_INF, q->controller->device, "Queued %s buffer address=0x%08x count=%d\n", q->name, address, count); | |
| } | |
| else | |
| { | |
| sim_debug(DBG_WRN, q->controller->device, "Failed to queue %s buffer address=0x%08x, queue full\n", q->name, address); | |
| // TODO: Report error here. | |
| } | |
| } | |
| void dmc_buffer_queue_release_head(BUFFER_QUEUE *q) | |
| { | |
| if (q->count > 0) | |
| { | |
| q->head = (q->head + 1) % BUFFER_QUEUE_SIZE; | |
| q->count--; | |
| } | |
| else | |
| { | |
| sim_debug(DBG_INF, q->controller->device, "Failed to release %s buffer, queue already empty\n", q->name); | |
| } | |
| } | |
| BUFFER *dmc_buffer_queue_head(BUFFER_QUEUE *q) | |
| { | |
| BUFFER *ans = NULL; | |
| if (q->count >0) | |
| { | |
| ans = &q->queue[q->head]; | |
| } | |
| return ans; | |
| } | |
| BUFFER *dmc_buffer_queue_find_first_available(BUFFER_QUEUE *q) | |
| { | |
| BUFFER *ans = dmc_buffer_queue_head(q); | |
| while (ans != NULL) | |
| { | |
| if (ans->state == Available) | |
| { | |
| break; | |
| } | |
| ans = ans->next; | |
| } | |
| return ans; | |
| } | |
| BUFFER *dmc_buffer_queue_find_first_contains_data(BUFFER_QUEUE *q) | |
| { | |
| BUFFER *ans = dmc_buffer_queue_head(q); | |
| while (ans != NULL) | |
| { | |
| if (ans->state == ContainsData) | |
| { | |
| break; | |
| } | |
| ans = ans->next; | |
| } | |
| return ans; | |
| } | |
| void dmc_buffer_queue_get_stats(BUFFER_QUEUE *q, int *available, int *contains_data, int *transfer_in_progress) | |
| { | |
| BUFFER *buf = dmc_buffer_queue_head(q); | |
| *available = 0; | |
| *contains_data = 0; | |
| *transfer_in_progress = 0; | |
| while (buf != NULL) | |
| { | |
| switch (buf->state) | |
| { | |
| case Available: | |
| { | |
| (*available)++; | |
| break; | |
| } | |
| case ContainsData: | |
| { | |
| (*contains_data)++; | |
| break; | |
| } | |
| case TransferInProgress: | |
| { | |
| (*transfer_in_progress)++; | |
| break; | |
| } | |
| } | |
| buf = buf->next; | |
| } | |
| } | |
| t_stat dmc_open_master_socket(CTLR *controller, char *port) | |
| { | |
| t_stat ans; | |
| ans = SCPE_OK; | |
| if (controller->master_socket == INVALID_SOCKET) | |
| { | |
| controller->master_socket = sim_master_sock(port, &ans); | |
| if (controller->master_socket == INVALID_SOCKET) | |
| { | |
| sim_debug(DBG_WRN, controller->device, "Failed to open master socket on port %s\n", port); | |
| ans = SCPE_OPENERR; | |
| } | |
| else | |
| { | |
| printf ("DMC-11 %s listening on port %s\n", controller->device->name, port); | |
| } | |
| } | |
| return ans; | |
| } | |
| t_stat dmc_close_master_socket(CTLR *controller) | |
| { | |
| sim_close_sock (controller->master_socket, TRUE); | |
| controller->master_socket = INVALID_SOCKET; | |
| return SCPE_OK; | |
| } | |
| // Gets the bidirectional socket and handles arbitration of determining which socket to use. | |
| int dmc_get_socket(CTLR *controller, int forRead) | |
| { | |
| int ans = 0; | |
| if (controller->line->isPrimary) | |
| { | |
| ans = dmc_get_transmit_socket(controller, 0, forRead); // TODO: After change to single socket, loopback may not work. | |
| } | |
| else | |
| { | |
| ans = dmc_get_receive_socket(controller, forRead); // TODO: After change to single socket, loopback may not work. | |
| } | |
| return ans; | |
| } | |
| int dmc_get_receive_socket(CTLR *controller, int forRead) | |
| { | |
| int ans = 0; | |
| if (controller->line->socket == INVALID_SOCKET) | |
| { | |
| char *ipaddr; | |
| //sim_debug(DBG_SOK, controller->device, "Trying to open receive socket\n"); | |
| controller->line->socket = sim_accept_conn (controller->master_socket, &ipaddr); /* poll connect */ | |
| if (controller->line->socket != INVALID_SOCKET) | |
| { | |
| char host[sizeof(controller->line->transmit_host)]; | |
| if (sim_parse_addr (controller->line->transmit_host, host, sizeof(host), NULL, NULL, 0, NULL, ipaddr)) | |
| { | |
| sim_debug(DBG_WRN, controller->device, "Received connection from unexpected source IP %s. Closing the connection.\n", ipaddr); | |
| dmc_close_receive(controller, "Unathorized connection", ipaddr); | |
| } | |
| else | |
| { | |
| sim_debug(DBG_SOK, controller->device, "Opened receive socket %d\n", controller->line->socket); | |
| controller->line->receive_readable = FALSE; | |
| } | |
| free(ipaddr); | |
| } | |
| } | |
| if (controller->line->socket != INVALID_SOCKET) | |
| { | |
| int readable = sim_check_conn(controller->line->socket, forRead); | |
| if (readable == 0) /* Still opening */ | |
| { | |
| // Socket is still being opened, or is open but there is no data ready to be read. | |
| ans = 0; | |
| } | |
| else if (readable == -1) /* Failed to open */ | |
| { | |
| dmc_close_receive(controller, "failed to connect", NULL); | |
| ans = 0; | |
| } | |
| else /* connected */ | |
| { | |
| if (!controller->line->receive_readable) | |
| { | |
| sim_debug(DBG_CON, controller->device, "Receive socket is now readable\n"); | |
| } | |
| controller->line->receive_readable = TRUE; | |
| ans = 1; | |
| } | |
| } | |
| return ans; | |
| } | |
| int dmc_get_transmit_socket(CTLR *controller, int is_loopback, int forRead) | |
| { | |
| int ans = 0; | |
| /* close transmit socket if there is a change in the loopback setting */ | |
| if (is_loopback ^ controller->line->transmit_is_loopback) | |
| { | |
| dmc_close_transmit(controller, "loopback change"); | |
| } | |
| if (controller->line->socket == INVALID_SOCKET && ((int32)(time(NULL) - controller->line->last_connect_attempt)) > controller->connect_poll_interval) | |
| { | |
| char host_port_buf[CBUFSIZE]; | |
| char *host_port = host_port_buf; | |
| controller->line->transmit_is_loopback = is_loopback; | |
| controller->line->last_connect_attempt = time(NULL); | |
| if (is_loopback) | |
| { | |
| if (strrchr(controller->line->receive_port, ':')) | |
| { | |
| host_port = controller->line->receive_port; | |
| } | |
| else | |
| { | |
| sprintf(host_port_buf, "localhost:%s", controller->line->receive_port); | |
| } | |
| } | |
| else | |
| { | |
| host_port = controller->line->transmit_host; | |
| } | |
| sim_debug(DBG_SOK, controller->device, "Trying to open transmit socket to address:port %s\n", host_port); | |
| controller->line->last_connect_attempt = time(NULL); | |
| controller->line->socket = sim_connect_sock(host_port, NULL, NULL); | |
| if (controller->line->socket != INVALID_SOCKET) | |
| { | |
| sim_debug(DBG_SOK, controller->device, "Opened transmit socket to port %s\n", host_port); | |
| controller->line->transmit_writeable = FALSE; | |
| } | |
| } | |
| if (controller->line->socket != INVALID_SOCKET) | |
| { | |
| int writeable = sim_check_conn(controller->line->socket, forRead); | |
| if (writeable == 0) /* Still opening */ | |
| { | |
| //sim_debug(DBG_SOK, controller->device, "Waiting for transmit socket to become writeable\n"); | |
| ans = 0; | |
| } | |
| else if (writeable == -1) /* Failed to open */ | |
| { | |
| dmc_close_transmit(controller, "failed to connect"); | |
| ans = 0; | |
| } | |
| else /* connected */ | |
| { | |
| if (!controller->line->transmit_writeable) | |
| { | |
| sim_debug(DBG_CON, controller->device, "Transmit socket is now writeable\n"); | |
| } | |
| controller->line->transmit_writeable = TRUE; | |
| ans = 1; | |
| } | |
| } | |
| return ans; | |
| } | |
| void dmc_close_receive(CTLR *controller, char *reason, char *from) | |
| { | |
| sim_debug(DBG_SOK, controller->device, "Closing receive socket on port %s, reason: %s%s%s\n", controller->line->receive_port, reason, from ? " from " : "", from ? from : ""); | |
| sim_close_sock(controller->line->socket, FALSE); | |
| controller->line->socket = INVALID_SOCKET; | |
| if (controller->line->receive_readable) | |
| { | |
| sim_debug(DBG_CON, controller->device, "Readable receive socket closed, reason: %s\n", reason); | |
| } | |
| controller->line->receive_readable = FALSE; | |
| } | |
| void dmc_error_and_close_receive(CTLR *controller, char *format) | |
| { | |
| int err = WSAGetLastError(); | |
| char errmsg[80]; | |
| sprintf(errmsg, format, err); | |
| if (controller->line->isPrimary) | |
| { | |
| dmc_close_transmit(controller, errmsg); | |
| } | |
| else | |
| { | |
| dmc_close_receive(controller, errmsg, NULL); | |
| } | |
| } | |
| void dmc_close_transmit(CTLR *controller, char *reason) | |
| { | |
| sim_debug(DBG_SOK, controller->device, "Closing transmit socket to port %s, socket %d, reason: %s\n", controller->line->transmit_host, controller->line->socket, reason); | |
| sim_close_sock(controller->line->socket, FALSE); | |
| controller->line->socket = INVALID_SOCKET; | |
| if (controller->line->transmit_writeable) | |
| { | |
| sim_debug(DBG_CON, controller->device, "Writeable transmit socket closed, reason: %s\n", reason); | |
| } | |
| controller->line->transmit_writeable = FALSE; | |
| } | |
| /* returns true if some data was received */ | |
| int dmc_buffer_fill_receive_buffers(CTLR *controller) | |
| { | |
| int ans = FALSE; | |
| SOCKET socket; | |
| if (controller->state == Initialised) | |
| { | |
| /* accept any inbound connection but just throw away any data */ | |
| if (dmc_get_socket(controller, TRUE)) | |
| { | |
| char buffer[1000]; | |
| int bytes_read = 0; | |
| socket = controller->line->socket; | |
| bytes_read = sim_read_sock(socket, buffer, sizeof(buffer)); | |
| if (bytes_read < 0) | |
| { | |
| dmc_error_and_close_receive(controller, "read error, code=%d"); | |
| } | |
| else if (bytes_read > 0) | |
| { | |
| sim_debug(DBG_SOK, controller->device, "Discarding received data while controller is not running\n"); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| BUFFER *buffer = dmc_buffer_queue_find_first_available(controller->receive_queue); | |
| while (buffer != NULL && buffer->state == Available) | |
| { | |
| if (dmc_get_socket(controller, TRUE)) | |
| { | |
| int bytes_read = 0; | |
| int lost_data = 0; | |
| socket = controller->line->socket; | |
| /* read block length and allocate buffer */ | |
| if ((size_t)buffer->block_len_bytes_read < sizeof(buffer->actual_block_len)) | |
| { | |
| char *start_addr = ((char *)&buffer->actual_block_len) + buffer->block_len_bytes_read; | |
| bytes_read = sim_read_sock(socket, start_addr, sizeof(buffer->actual_block_len) - buffer->block_len_bytes_read); | |
| if (bytes_read >= 0) | |
| { | |
| buffer->block_len_bytes_read += bytes_read; | |
| if (buffer->block_len_bytes_read == sizeof(buffer->actual_block_len)) | |
| { | |
| buffer->actual_block_len = ntohs(buffer->actual_block_len); | |
| if (buffer->actual_block_len > buffer->count) | |
| { | |
| sim_debug(DBG_WRN, controller->device, "LOST DATA, buffer available has %d bytes, but the block is %d bytes\n", buffer->count, buffer->actual_block_len); | |
| dmc_setreg(controller, 4, 0, 0); | |
| dmc_setreg(controller, 6, 0, 0); | |
| dmc_set_lost_data(controller); | |
| dmc_start_control_output_transfer(controller); | |
| lost_data = 1; | |
| } | |
| if (buffer->actual_block_len > 0) | |
| { | |
| buffer->transfer_buffer = (uint8 *)malloc(buffer->actual_block_len); /* read full buffer regardless, so bad buffer is flushed */ | |
| } | |
| } | |
| } | |
| } | |
| else | |
| { | |
| lost_data = buffer->actual_block_len > buffer->count; /* need to preserve this variable if need more than one attempt to read the buffer */ | |
| } | |
| /* read the actual block */ | |
| if (buffer->block_len_bytes_read == sizeof(buffer->actual_block_len)) | |
| { | |
| bytes_read = 0; | |
| if (buffer->actual_block_len > 0) | |
| { | |
| int bytes_to_read = dmc_line_speed_calculate_byte_length(controller->line->bytes_received_in_last_second, buffer->actual_block_len - buffer->actual_bytes_transferred, controller->line->speed); | |
| if (bytes_to_read > 0) | |
| { | |
| bytes_read = sim_read_sock(controller->line->socket, (char *)(buffer->transfer_buffer + buffer->actual_bytes_transferred), bytes_to_read); | |
| } | |
| } | |
| if (bytes_read >= 0) | |
| { | |
| buffer->actual_bytes_transferred += bytes_read; | |
| controller->line->bytes_received_in_last_second += bytes_read; | |
| if (buffer->actual_bytes_transferred >= buffer->actual_block_len) | |
| { | |
| dmc_buffer_trace(controller, buffer->transfer_buffer, buffer->actual_bytes_transferred, "REC ", buffer->address); | |
| controller->buffers_received_from_net++; | |
| buffer->state = ContainsData; | |
| if (!lost_data) | |
| { | |
| Map_WriteB(buffer->address, buffer->actual_bytes_transferred, buffer->transfer_buffer); | |
| } | |
| else | |
| { | |
| buffer->actual_block_len = 0; /* so an empty buffer is returned to the driver */ | |
| } | |
| if (buffer->actual_block_len > 0) | |
| { | |
| free(buffer->transfer_buffer); | |
| buffer->transfer_buffer = NULL; | |
| } | |
| ans = TRUE; | |
| } | |
| } | |
| } | |
| /* Only close the socket if there was an error or no more data */ | |
| if (bytes_read < 0) | |
| { | |
| dmc_error_and_close_receive(controller, "read error, code=%d"); | |
| break; | |
| } | |
| /* if buffer is incomplete do not try to read any more buffers and continue filling this one later */ | |
| if (buffer->state == Available) | |
| { | |
| break; /* leave buffer available and continue filling it later */ | |
| } | |
| } | |
| else | |
| { | |
| break; | |
| } | |
| buffer = buffer ->next; | |
| } | |
| } | |
| return ans; | |
| } | |
| /* returns true if some data was actually sent */ | |
| int dmc_buffer_send_transmit_buffers(CTLR *controller) | |
| { | |
| int ans = FALSE; | |
| /* when transmit buffer is queued it is marked as available, not as ContainsData */ | |
| BUFFER *buffer = dmc_buffer_queue_find_first_available(controller->transmit_queue); | |
| while (buffer != NULL) | |
| { | |
| if (dmc_get_socket(controller, FALSE)) // TODO: , buffer->is_loopback); | |
| { | |
| int bytes = 0; | |
| int bytes_to_send; | |
| uint16 block_len; | |
| int total_buffer_len = (buffer->count > 0) ? buffer->count + sizeof(block_len) : 0; | |
| /* only send the buffer if it actually has some data, sometimes get zero length buffers - don't send these */ | |
| if (total_buffer_len > 0) | |
| { | |
| if (buffer->transfer_buffer == NULL) | |
| { | |
| int n; | |
| /* construct buffer and include block length bytes */ | |
| buffer->transfer_buffer = (uint8 *)malloc(total_buffer_len); | |
| block_len = htons(buffer->count); | |
| memcpy(buffer->transfer_buffer, (char *)&block_len, sizeof(block_len)); | |
| n = Map_ReadB(buffer->address, buffer->count, buffer->transfer_buffer + sizeof(block_len)); | |
| if (n > 0) | |
| { | |
| sim_debug(DBG_WRN, controller->device, "DMA error\n"); | |
| } | |
| } | |
| bytes_to_send = dmc_line_speed_calculate_byte_length(controller->line->bytes_sent_in_last_second, buffer->count + sizeof(block_len) - buffer->actual_bytes_transferred, controller->line->speed); | |
| if (bytes_to_send > 0) | |
| { | |
| bytes = sim_write_sock (controller->line->socket, (char *)(buffer->transfer_buffer + buffer->actual_bytes_transferred), bytes_to_send); | |
| if (bytes >= 0) | |
| { | |
| buffer->actual_bytes_transferred += bytes; | |
| controller->line->bytes_sent_in_last_second += bytes; | |
| } | |
| if (buffer->actual_bytes_transferred >= total_buffer_len || bytes < 0) | |
| { | |
| dmc_buffer_trace(controller, buffer->transfer_buffer+sizeof(block_len), buffer->count, "TRAN", buffer->address); | |
| free(buffer->transfer_buffer); | |
| } | |
| } | |
| } | |
| if (buffer->actual_bytes_transferred >= total_buffer_len) | |
| { | |
| controller->buffers_transmitted_to_net++; | |
| buffer->state = ContainsData; // so won't try to transmit again | |
| ans = TRUE; | |
| } | |
| else if (bytes < 0) | |
| { | |
| int err = WSAGetLastError (); | |
| char errmsg[80]; | |
| sprintf(errmsg, "write failure, code=%d", err); | |
| dmc_close_transmit(controller, errmsg); | |
| break; | |
| } | |
| else | |
| { | |
| break; /* poll again later to send more bytes */ | |
| } | |
| } | |
| else | |
| { | |
| break; | |
| } | |
| buffer = buffer ->next; | |
| } | |
| return ans; | |
| } | |
| void dmc_start_transfer_receive_buffer(CTLR *controller) | |
| { | |
| BUFFER *head = dmc_buffer_queue_head(controller->receive_queue); | |
| if (head != NULL) | |
| { | |
| if (head->state == ContainsData) | |
| { | |
| head->state = TransferInProgress; | |
| dmc_start_data_output_transfer(controller, head->address, head->actual_block_len, TRUE); | |
| } | |
| } | |
| } | |
| void dmc_start_transfer_transmit_buffer(CTLR *controller) | |
| { | |
| BUFFER *head = dmc_buffer_queue_head(controller->transmit_queue); | |
| if (head != NULL) | |
| { | |
| if (head->state == ContainsData) | |
| { | |
| head->state = TransferInProgress; | |
| dmc_start_data_output_transfer(controller, head->address, head->count, FALSE); | |
| } | |
| } | |
| } | |
| void dmc_check_for_output_transfer_completion(CTLR *controller) | |
| { | |
| if (!dmc_is_rdyo_set(controller)) | |
| { | |
| sim_debug(DBG_INF, controller->device, "Output transfer completed\n"); | |
| controller->transfer_state = Idle; | |
| if (dmc_get_output_transfer_type(controller) == TYPE_BACCO) | |
| { | |
| if (dmc_is_out_io_set(controller)) | |
| { | |
| dmc_buffer_queue_release_head(controller->receive_queue); | |
| controller->receive_buffer_output_transfers_completed++; | |
| } | |
| else | |
| { | |
| dmc_buffer_queue_release_head(controller->transmit_queue); | |
| controller->transmit_buffer_output_transfers_completed++; | |
| } | |
| } | |
| dmc_process_command(controller); // check for any input transfers | |
| } | |
| } | |
| void dmc_process_input_transfer_completion(CTLR *controller) | |
| { | |
| if (dmc_is_dmc(controller)) | |
| { | |
| if (!dmc_is_rqi_set(controller)) | |
| { | |
| uint16 sel4 = controller->csrs->sel4; | |
| uint16 sel6 = controller->csrs->sel6; | |
| dmc_clear_rdyi(controller); | |
| if (controller->transfer_type == TYPE_BASEI) | |
| { | |
| uint32 baseaddr = ((sel6 >> 14) << 16) | sel4; | |
| uint16 count = sel6 & 0x3FFF; | |
| sim_debug(DBG_INF, controller->device, "Completing Base In input transfer, base address=0x%08x count=%d\n", baseaddr, count); | |
| } | |
| else if (controller->transfer_type == TYPE_BACCI) | |
| { | |
| uint32 addr = ((sel6 >> 14) << 16) | sel4; | |
| uint16 count = sel6 & 0x3FFF; | |
| if (controller->transfer_in_io != dmc_is_in_io_set(controller)) | |
| { | |
| sim_debug(DBG_TRC, controller->device, "IN IO MISMATCH\n"); | |
| } | |
| controller->transfer_in_io = dmc_is_in_io_set(controller); // using evdmc the flag is set when the transfer completes - not when it starts, evdca seems to set in only at the start of the transfer - clearing it when it completes | |
| controller->state = Running; | |
| if (controller->transfer_in_io) | |
| { | |
| dmc_buffer_queue_add(controller->receive_queue, addr, count); | |
| dmc_buffer_fill_receive_buffers(controller); | |
| controller->receive_buffer_input_transfers_completed++; | |
| } | |
| else | |
| { | |
| dmc_buffer_queue_add(controller->transmit_queue, addr, count); | |
| dmc_buffer_send_transmit_buffers(controller); | |
| controller->transmit_buffer_input_transfers_completed++; | |
| } | |
| } | |
| controller->transfer_state = Idle; | |
| } | |
| } | |
| else | |
| { | |
| if (!dmc_is_rdyi_set(controller)) | |
| { | |
| uint16 sel6 = controller->csrs->sel6; | |
| if (controller->transfer_type == TYPE_DMP_MODE) | |
| { | |
| uint16 mode = sel6 & DMP_TYPE_INPUT_MASK; | |
| char * duplex = (mode & 1) ? "Full-Duplex" : "Half-Duplex"; | |
| char * config; | |
| if (mode & 4) | |
| { | |
| config = "Point-to-point"; | |
| } | |
| else | |
| { | |
| config = (mode & 2) ? "Tributary station" : "Control Station"; | |
| } | |
| sim_debug(DBG_INF, controller->device, "Completing Mode input transfer, %s %s\n", duplex, config); | |
| } | |
| else if (controller->transfer_type == TYPE_DMP_CONTROL) | |
| { | |
| sim_debug(DBG_WRN, controller->device, "Control command (not processed yet)\n"); | |
| } | |
| else if (controller->transfer_type == TYPE_DMP_RECEIVE) | |
| { | |
| sim_debug(DBG_WRN, controller->device, "Receive Buffer command (not processed yet)\n"); | |
| } | |
| else if (controller->transfer_type == TYPE_DMP_TRANSMIT) | |
| { | |
| sim_debug(DBG_WRN, controller->device, "Transmit Buffer command (not processed yet)\n"); | |
| } | |
| else | |
| { | |
| sim_debug(DBG_WRN, controller->device, "Unrecognised command code %hu\n", controller->transfer_type); | |
| } | |
| controller->transfer_state = Idle; | |
| } | |
| } | |
| } | |
| void dmc_process_command(CTLR *controller) | |
| { | |
| if (dmc_is_master_clear_set(controller)) | |
| { | |
| dmc_process_master_clear(controller); | |
| } | |
| else | |
| { | |
| if (controller->transfer_state == InputTransfer) | |
| { | |
| dmc_process_input_transfer_completion(controller); | |
| } | |
| else if (controller->transfer_state == OutputTransfer) | |
| { | |
| dmc_check_for_output_transfer_completion(controller); | |
| } | |
| else if (dmc_is_rqi_set(controller)) | |
| { | |
| dmc_start_input_transfer(controller); | |
| } | |
| } | |
| } | |
| t_stat dmc_rd(int32 *data, int32 PA, int32 access) | |
| { | |
| CTLR *controller = dmc_get_controller_from_address(PA); | |
| sim_debug(DBG_TRC, controller->device, "dmc_rd(), addr=0x%x access=%d\n", PA, access); | |
| *data = dmc_getreg(controller, PA, 1); | |
| return SCPE_OK; | |
| } | |
| t_stat dmc_wr(int32 data, int32 PA, int32 access) | |
| { | |
| CTLR *controller = dmc_get_controller_from_address(PA); | |
| int reg = PA & 07; | |
| uint16 oldValue = dmc_getreg(controller, PA, 0); | |
| if (access == WRITE) | |
| { | |
| sim_debug(DBG_TRC, controller->device, "dmc_wr(), addr=0x%08x, SEL%d, data=0x%04x\n", PA, reg, data); | |
| } | |
| else | |
| { | |
| sim_debug(DBG_TRC, controller->device, "dmc_wr(), addr=0x%08x, BSEL%d, data=%04x\n", PA, reg, data); | |
| } | |
| if (access == WRITE) | |
| { | |
| if (PA & 1) | |
| sim_debug(DBG_WRN, controller->device, "dmc_wr(), Unexpected non-16-bit write access to SEL%d\n", reg); | |
| dmc_setreg(controller, PA, data, 1); | |
| } | |
| else | |
| { | |
| uint16 mask; | |
| if (PA & 1) | |
| { | |
| mask = 0xFF00; | |
| data = data << 8; | |
| } | |
| else | |
| { | |
| mask = 0x00FF; | |
| } | |
| dmc_setreg(controller, PA, (oldValue & ~mask) | (data & mask), 1); | |
| } | |
| if (dmc_getsel(reg) == 0 || dmc_getsel(reg) == 1) | |
| { | |
| dmc_process_command(controller); | |
| } | |
| return SCPE_OK; | |
| } | |
| int32 dmc_rxint (void) | |
| { | |
| int i; | |
| int32 ans = 0; /* no interrupt request active */ | |
| for (i=0; i<DMC_NUMDEVICE; i++) | |
| { | |
| CTLR *controller = &dmc_ctrls[i]; | |
| if (controller->rxi != 0) | |
| { | |
| DIB *dib = (DIB *)controller->device->ctxt; | |
| ans = dib->vec; | |
| dmc_clrrxint(controller); | |
| break; | |
| } | |
| } | |
| return ans; | |
| } | |
| int32 dmc_txint (void) | |
| { | |
| int i; | |
| int32 ans = 0; /* no interrupt request active */ | |
| for (i=0; i<DMC_NUMDEVICE; i++) | |
| { | |
| CTLR *controller = &dmc_ctrls[i]; | |
| if (controller->txi != 0) | |
| { | |
| DIB *dib = (DIB *)controller->device->ctxt; | |
| ans = dib->vec + 4; | |
| dmc_clrtxint(controller); | |
| break; | |
| } | |
| } | |
| return ans; | |
| } | |
| t_stat dmc_reset (DEVICE *dptr) | |
| { | |
| t_stat ans = SCPE_OK; | |
| CTLR *controller = dmc_get_controller_from_device(dptr); | |
| sim_debug(DBG_TRC, dptr, "dmc_reset()\n"); | |
| if (dmc_get_unit_stats(dptr->units) == NULL) | |
| { | |
| dmc_set_unit_stats(dptr->units, (UNIT_STATS *)malloc(sizeof(UNIT_STATS))); | |
| dmc_reset_unit_stats(dmc_get_unit_stats(dptr->units)); | |
| } | |
| dmc_clrrxint(controller); | |
| dmc_clrtxint(controller); | |
| sim_cancel (controller->device->units); /* stop poll */ | |
| if (!(dptr->flags & DEV_DIS)) | |
| { | |
| ans = auto_config (dptr->name, DMC_UNITSPERDEVICE); | |
| } | |
| return ans; | |
| } | |
| t_stat dmc_attach (UNIT *uptr, char *cptr) | |
| { | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| t_stat ans = SCPE_OK; | |
| ans = dmc_open_master_socket(controller, cptr); | |
| if (ans == SCPE_OK) | |
| { | |
| controller->line->socket = INVALID_SOCKET; | |
| uptr->flags = uptr->flags | UNIT_ATT; /* set unit attached flag */ | |
| uptr->filename = (char *)malloc(strlen(cptr)+1); | |
| strcpy(uptr->filename, cptr); | |
| controller->line->receive_port = uptr->filename; | |
| //sim_activate_abs(controller->device->units, clk_cosched (tmxr_poll)); | |
| } | |
| return ans; | |
| } | |
| int dmc_isattached(CTLR *controller) | |
| { | |
| return controller->master_socket != INVALID_SOCKET; | |
| } | |
| t_stat dmc_detach (UNIT *uptr) | |
| { | |
| CTLR *controller = dmc_get_controller_from_unit(uptr); | |
| dmc_error_and_close_receive(controller, "Detach"); | |
| dmc_close_master_socket(controller); | |
| uptr->flags = uptr->flags & ~UNIT_ATT; /* clear unit attached flag */ | |
| free(uptr->filename); | |
| uptr->filename = NULL; | |
| sim_cancel(uptr); | |
| return SCPE_OK; | |
| } |