/* 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. | |
------------------------------------------------------------------------------ | |
Modification history: | |
23-Jan-13 RJ Clock co-scheduling move to generic framework (from Mark Pizzolato) | |
21-Jan-13 RJ Added help. | |
15-Jan-13 RJ Contribution from Paul Koning: | |
Support for RSTS using the ROM INPUT (ROM I) command to get | |
the DMC11 to report DSR status. | |
Don't accept any data from the peer until a buffer has been | |
made available. | |
Also added shadow CSRs. The code was using the CSRs to check | |
the command being executed, but the driver could end up | |
changing the bits, so a shadow set is used to do this. | |
------------------------------------------------------------------------------ | |
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 | |
{ | |
int32 isPrimary; | |
SOCKET socket; // socket used bidirectionally | |
int receive_readable; | |
char *receive_port; | |
int transmit_writeable; | |
char peer[CBUFSIZE]; | |
int transmit_is_loopback; /* if true the transmit socket is the loopback to the receive */ | |
int32 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; | |
CSRS *shadow_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; | |
UNIT_STATS *stats; | |
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); | |
t_stat dmc_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); | |
t_stat dmc_help_attach (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); | |
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); | |
void dmc_buffer_queue_init_all(CTLR *controller); | |
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_socket(CTLR *controller, char *format); | |
void dmc_close_socket(CTLR *controller, char *reason); | |
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); | |
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 dmc0_unit = { UDATA (&dmc_svc, UNIT_IDLE|UNIT_ATTABLE|UNIT_DISABLE, 0) }; | |
UNIT dmc1_unit = { UDATA (&dmc_svc, UNIT_IDLE|UNIT_ATTABLE|UNIT_DISABLE, 0) }; | |
UNIT dmc2_unit = { UDATA (&dmc_svc, UNIT_IDLE|UNIT_ATTABLE|UNIT_DISABLE, 0) }; | |
UNIT dmc3_unit = { UDATA (&dmc_svc, UNIT_IDLE|UNIT_ATTABLE|UNIT_DISABLE, 0) }; | |
UNIT dmpa_unit = { UDATA (&dmc_svc, UNIT_IDLE|UNIT_ATTABLE|UNIT_DISABLE, 0) }; | |
CSRS dmc_csrs[DMC_NUMDEVICE]; | |
CSRS dmc_shadow_csrs[DMC_NUMDEVICE]; | |
CSRS dmp_csrs[DMP_NUMDEVICE]; | |
CSRS dmp_shadow_csrs[DMP_NUMDEVICE]; | |
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]; | |
UNIT_STATS dmc_stats[DMC_NUMDEVICE]; | |
UNIT_STATS dmp_stats[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) }, | |
{ HRDATA (SHADOWSEL0, dmc_shadow_csrs[0].sel0, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL2, dmc_shadow_csrs[0].sel2, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL4, dmc_shadow_csrs[0].sel4, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL6, dmc_shadow_csrs[0].sel6, 16), REG_HRO }, | |
{ BRDATA (PEER, dmc_line[0].peer, 16, 8, sizeof(dmc_line[0].peer)), REG_HRO}, | |
{ HRDATA (MODE, dmc_line[0].isPrimary, 32), REG_HRO }, | |
{ HRDATA (SPEED, dmc_line[0].speed, 32), REG_HRO }, | |
{ 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) }, | |
{ HRDATA (SHADOWSEL0, dmc_shadow_csrs[1].sel0, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL2, dmc_shadow_csrs[1].sel2, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL4, dmc_shadow_csrs[1].sel4, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL6, dmc_shadow_csrs[1].sel6, 16), REG_HRO }, | |
{ BRDATA (PEER, dmc_line[1].peer, 16, 8, sizeof(dmc_line[1].peer)), REG_HRO}, | |
{ HRDATA (MODE, dmc_line[1].isPrimary, 32), REG_HRO }, | |
{ HRDATA (SPEED, dmc_line[1].speed, 32), REG_HRO }, | |
{ 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) }, | |
{ HRDATA (SHADOWSEL0, dmc_shadow_csrs[2].sel0, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL2, dmc_shadow_csrs[2].sel2, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL4, dmc_shadow_csrs[2].sel4, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL6, dmc_shadow_csrs[2].sel6, 16), REG_HRO }, | |
{ BRDATA (PEER, dmc_line[2].peer, 16, 8, sizeof(dmc_line[2].peer)), REG_HRO}, | |
{ HRDATA (MODE, dmc_line[2].isPrimary, 32), REG_HRO }, | |
{ HRDATA (SPEED, dmc_line[2].speed, 32), REG_HRO }, | |
{ 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) }, | |
{ HRDATA (SHADOWSEL0, dmc_shadow_csrs[3].sel0, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL2, dmc_shadow_csrs[3].sel2, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL4, dmc_shadow_csrs[3].sel4, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL6, dmc_shadow_csrs[3].sel6, 16), REG_HRO }, | |
{ BRDATA (PEER, dmc_line[3].peer, 16, 8, sizeof(dmc_line[3].peer)), REG_HRO}, | |
{ HRDATA (MODE, dmc_line[3].isPrimary, 32), REG_HRO }, | |
{ HRDATA (SPEED, dmc_line[3].speed, 32), REG_HRO }, | |
{ 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) }, | |
{ HRDATA (SHADOWSEL0, dmp_shadow_csrs[0].sel0, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL2, dmp_shadow_csrs[0].sel2, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL4, dmp_shadow_csrs[0].sel4, 16), REG_HRO }, | |
{ HRDATA (SHADOWSEL6, dmp_shadow_csrs[0].sel6, 16), REG_HRO }, | |
{ BRDATA (PEER, dmp_line[0].peer, 16, 8, sizeof(dmp_line[0].peer)), REG_HRO}, | |
{ HRDATA (MODE, dmp_line[0].isPrimary, 32), REG_HRO }, | |
{ HRDATA (SPEED, dmp_line[0].speed, 32), REG_HRO }, | |
{ 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=bits/sec (0=unrestricted)" ,&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={PRIMARY|SECONDARY}" ,&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=seconds" ,&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 | |
DIB dmc0_dib = { IOBA_AUTO, IOLN_DMC, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_AUTO, {&dmc_rxint, &dmc_txint} }; | |
DIB dmc1_dib = { IOBA_AUTO, IOLN_DMC, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_AUTO, {&dmc_rxint, &dmc_txint} }; | |
DIB dmc2_dib = { IOBA_AUTO, IOLN_DMC, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_AUTO, {&dmc_rxint, &dmc_txint} }; | |
DIB dmc3_dib = { IOBA_AUTO, IOLN_DMC, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_AUTO, {&dmc_rxint, &dmc_txint} }; | |
#define IOLN_DMP 010 | |
DIB dmp_dib = { IOBA_AUTO, IOLN_DMP, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_AUTO, {&dmc_rxint, &dmc_txint }}; | |
DEVICE dmc_dev[] = | |
{ | |
{ "DMC0", &dmc0_unit, dmca_reg, dmc_mod, DMC_UNITSPERDEVICE, DMC_RDX, 8, 1, DMC_RDX, 8, | |
NULL,NULL,&dmc_reset,NULL,&dmc_attach,&dmc_detach, | |
&dmc0_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG, 0, dmc_debug, | |
NULL, NULL, &dmc_help, &dmc_help_attach, NULL }, | |
{ "DMC1", &dmc1_unit, dmcb_reg, dmc_mod, DMC_UNITSPERDEVICE, DMC_RDX, 8, 1, DMC_RDX, 8, | |
NULL,NULL,&dmc_reset,NULL,&dmc_attach,&dmc_detach, | |
&dmc1_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG, 0, dmc_debug, | |
NULL, NULL, &dmc_help, &dmc_help_attach, NULL }, | |
{ "DMC2", &dmc2_unit, dmcc_reg, dmc_mod, DMC_UNITSPERDEVICE, DMC_RDX, 8, 1, DMC_RDX, 8, | |
NULL,NULL,&dmc_reset,NULL,&dmc_attach,&dmc_detach, | |
&dmc2_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG, 0, dmc_debug, | |
NULL, NULL, &dmc_help, &dmc_help_attach, NULL }, | |
{ "DMC3", &dmc3_unit, dmcd_reg, dmc_mod, DMC_UNITSPERDEVICE, DMC_RDX, 8, 1, DMC_RDX, 8, | |
NULL,NULL,&dmc_reset,NULL,&dmc_attach,&dmc_detach, | |
&dmc3_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG, 0, dmc_debug, | |
NULL, NULL, &dmc_help, &dmc_help_attach, NULL } | |
}; | |
#ifdef DMP | |
DEVICE dmp_dev[] = | |
{ | |
{ "DMP", &dmp_unit, dmp_reg, dmc_mod, DMP_UNITSPERDEVICE, DMC_RDX, 8, 1, DMC_RDX, 8, | |
NULL,NULL,&dmc_reset,NULL,&dmc_attach,&dmc_detach, | |
&dmp_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG, 0, dmc_debug, | |
NULL, NULL, &dmc_help, &dmc_help_attach, NULL } | |
}; | |
#endif | |
CTLR dmc_ctrls[] = | |
{ | |
{ &dmc_csrs[0], &dmc_shadow_csrs[0], &dmc_dev[0], Initialised, Idle, 0, 0, &dmc_line[0], &dmc_receive_queues[0], &dmc_transmit_queues[0], &dmc_stats[0], INVALID_SOCKET, 30, DMC }, | |
{ &dmc_csrs[1], &dmc_shadow_csrs[1], &dmc_dev[1], Initialised, Idle, 0, 0, &dmc_line[1], &dmc_receive_queues[1], &dmc_transmit_queues[1], &dmc_stats[1], INVALID_SOCKET, 30, DMC }, | |
{ &dmc_csrs[2], &dmc_shadow_csrs[2], &dmc_dev[2], Initialised, Idle, 0, 0, &dmc_line[2], &dmc_receive_queues[2], &dmc_transmit_queues[2], &dmc_stats[2], INVALID_SOCKET, 30, DMC }, | |
{ &dmc_csrs[3], &dmc_shadow_csrs[3], &dmc_dev[3], Initialised, Idle, 0, 0, &dmc_line[3], &dmc_receive_queues[3], &dmc_transmit_queues[3], &dmc_stats[3], INVALID_SOCKET, 30, DMC }, | |
#ifdef DMP | |
{ &dmp_csrs[0], &dmp_shadow_csrs[0], &dmp_dev[0], Initialised, Idle, 0, 0, &dmp_line[0], &dmp_receive_queues[0], &dmp_transmit_queues[0], &dmp_stats[0], INVALID_SOCKET, -1, 30, DMP } | |
#endif | |
}; | |
extern int32 tmxr_poll; /* calibrated delay */ | |
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->peer[0]) | |
{ | |
fprintf(st, "peer=%s", controller->line->peer); | |
} | |
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->peer, cptr, sizeof(controller->line->peer)-1); | |
return status; | |
} | |
t_stat dmc_showspeed (FILE* st, UNIT* uptr, int32 val, void* desc) | |
{ | |
CTLR *controller = dmc_get_controller_from_unit(uptr); | |
if (controller->line->speed > 0) | |
{ | |
fprintf(st, "speed=%d bits/sec", controller->line->speed); | |
} | |
else | |
{ | |
fprintf(st, "speed=0 (unrestricted)"); | |
} | |
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 = &controller->stats->poll_timer; | |
TIMER *between_polls_timer = &controller->stats->between_polls_timer; | |
uint32 poll_count = controller->stats->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(controller->stats); | |
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; | |
} | |
t_stat dmc_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) | |
{ | |
fprintf(st, "The DMC11 is a synchronous serial point-to-point communications device."); | |
fprintf(st, "A real DMC11 transports data using DDCMP, the emulated device makes a"); | |
fprintf(st, "TCP/IP connection to another emulated device and sends length-prefixed"); | |
fprintf(st, "messages across the connection, each message representing a single buffer"); | |
fprintf(st, "passed to the DMC11. The DMC11 can be used for point-to-point DDCMP"); | |
fprintf(st, "connections carrying DECnet and other types of networking, e.g. from ULTRIX or DSM.\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "The line mode of the two ends of the link must be set. One end must always"); | |
fprintf(st, "be primary and one end always secondary, setting both to primary or both"); | |
fprintf(st, "to secondary will not work. If there are firewall problems at one side, set"); | |
fprintf(st, "that side to be primary as the primary always initiates the TCP/IP connection.\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "SET DMC0 LINEMODE= {PRIMARY|SECONDARY}\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "To set the host and port to which data is to be transmitted use the following"); | |
fprintf(st, "command (required for PRIMARY and SECONDARY, secondary will check it is"); | |
fprintf(st, "receiving from the configured primary):\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "SET DMC0 PEER=host:port\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "The device must be attached to a receive port, use the ATTACH command specifying"); | |
fprintf(st, "the receive port number, even if the line mode is primary.\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "The minimum interval between attempts to connect to the other side is set using"); | |
fprintf(st, "the following command:\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "SET DMC0 CONNECTPOLL=n\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "Where n is the number of seconds. The default is 30 seconds.\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "If you want to experience the actual data rates of the physical hardware you can"); | |
fprintf(st, "set the bit rate of the simulated line can be set using the following command:\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "SET DMC0 SPEED=n\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "Where n is the number of data bits per second that the simulated line runs at."); | |
fprintf(st, "In practice this is implemented as a delay in reading the bytes from the socket."); | |
fprintf(st, "Use a value of zero to run at full speed with no artificial throttling.\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "To configure two simulators to talk to each other use the following example:\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "Machine 1\n"); | |
fprintf(st, "SET DMC0 ENABLE\n"); | |
fprintf(st, "SET DMC0 PRIMARY\n"); | |
fprintf(st, "SET DMC0 PEER=LOCALHOST:2222\n"); | |
fprintf(st, "ATTACH DMC0 1111\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "Machine 2\n"); | |
fprintf(st, "SET DMC0 ENABLE\n"); | |
fprintf(st, "SET DMC0 SECONDARY\n"); | |
fprintf(st, "SET DMC0 PEER= LOCALHOST:1111\n"); | |
fprintf(st, "ATTACH DMC0 2222\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "Debugging\n"); | |
fprintf(st, "=========\n"); | |
fprintf(st, "The simulator has a number of debug options, these are:\n"); | |
fprintf(st, "• REG. Shows whenever a CSR is read or written and the current value.\n"); | |
fprintf(st, "• INFO. Shows higher-level tracing only.\n"); | |
fprintf(st, "• WARN. Shows any warnings.\n"); | |
fprintf(st, "• TRACE. Shows more detailed trace information.\n"); | |
fprintf(st, "• DATA. Shows the actual data sent and received.\n"); | |
fprintf(st, "• DATASUM. Brief summary of each received and transmitted buffer. Ignored if DATA is set.\n"); | |
fprintf(st, "• SOCKET. Shows socket opens and closes.\n"); | |
fprintf(st, "• CONNECT. Shows sockets actually connecting.\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "To get a full trace use\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "SET DMC0 DEBUG\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "However it is recommended to use the following when sending traces:\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "SET DMC0 DEBUG=REG;INFO;WARN\n"); | |
fprintf(st, "\n"); | |
fprintf(st, "\n"); | |
return SCPE_OK; | |
} | |
t_stat dmc_help_attach (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) | |
{ | |
//tmxr_attach_help (st, dptr, uptr, flag, cptr); | |
fprintf (st, "The communication line performs input and output through a TCP session\n"); | |
fprintf (st, "connected to a user-specified port. The ATTACH command specifies the"); | |
fprintf (st, "port to be used:\n\n"); | |
fprintf (st, " sim> ATTACH %s {interface:}port set up listening port\n\n", dptr->name); | |
fprintf (st, "where port is a decimal number between 1 and 65535 that is not being used for\n"); | |
fprintf (st, "other TCP/IP activities. An ATTACH is required even if in PRIMARY mode. \n\n"); | |
return SCPE_OK; | |
} | |
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%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_ROMI_BIT, 1) ? "ROMI " : "", | |
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; | |
if (!ext) | |
{ | |
controller->shadow_csrs->sel0 = data; | |
} | |
break; | |
case 01: | |
dmc_dumpregsel2(controller, DBG_REG, trace, data); | |
controller->csrs->sel2 = data; | |
if (!ext) | |
{ | |
controller->shadow_csrs->sel2 = data; | |
} | |
break; | |
case 02: | |
dmc_dumpregsel4(controller, DBG_REG, trace, data); | |
controller->csrs->sel4 = data; | |
if (!ext) | |
{ | |
controller->shadow_csrs->sel4 = data; | |
} | |
break; | |
case 03: | |
dmc_dumpregsel6(controller, DBG_REG, trace, data); | |
controller->csrs->sel6 = data; | |
if (!ext) | |
{ | |
controller->shadow_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->shadow_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->shadow_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); | |
dmc_close_socket(controller, "Master clear"); /* to resynch both ends */ | |
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_all(controller); | |
controller->transfer_state = Idle; | |
dmc_set_run(controller); | |
sim_cancel (controller->device->units); /* stop poll */ | |
sim_clock_coschedule (controller->device->units, tmxr_poll); /* reactivate */ | |
} | |
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; | |
TIMER *poll_timer; | |
TIMER *between_polls_timer; | |
controller = dmc_get_controller_from_unit(uptr); | |
poll_timer = &controller->stats->poll_timer; | |
between_polls_timer = &controller->stats->between_polls_timer; | |
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); | |
} | |
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_clock_coschedule (controller->device->units, tmxr_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); | |
} | |
controller->stats->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; | |
} | |
void dmc_buffer_queue_init_all(CTLR *controller) | |
{ | |
dmc_buffer_queue_init(controller, controller->receive_queue, "receive"); | |
dmc_buffer_queue_init(controller, controller->transmit_queue, "transmit"); | |
} | |
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->peer)]; | |
if (sim_parse_addr (controller->line->peer, 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->peer; | |
} | |
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_error_and_close_socket(CTLR *controller, char *format) | |
{ | |
int err = WSAGetLastError(); | |
char errmsg[80]; | |
sprintf(errmsg, format, err); | |
dmc_close_socket(controller, errmsg); | |
} | |
void dmc_close_socket(CTLR *controller, char *reason) | |
{ | |
if (controller->line->isPrimary) | |
{ | |
dmc_close_transmit(controller, reason); | |
} | |
else | |
{ | |
dmc_close_receive(controller, reason, NULL); | |
} | |
} | |
void dmc_close_receive(CTLR *controller, char *reason, char *from) | |
{ | |
if (controller->line->socket != INVALID_SOCKET) | |
{ | |
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_close_transmit(CTLR *controller, char *reason) | |
{ | |
if (controller->line->socket != INVALID_SOCKET) | |
{ | |
sim_debug(DBG_SOK, controller->device, "Closing transmit socket to port %s, socket %d, reason: %s\n", controller->line->peer, 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 == Running) | |
{ | |
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; | |
dmc_error_and_close_socket(controller, "oversized packet"); | |
} | |
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_socket(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); | |
} | |
else if (dmc_is_dmc (controller) && | |
controller->csrs->sel0 & ROMI_MASK && | |
controller->csrs->sel6 == DSPDSR) | |
/* DMC-11 or DMR-11, see if ROMI bit is set. If so, if SEL6 is | |
0x22b3 (read line status instruction), set the DTR bit in SEL2. */ | |
{ | |
dmc_setreg (controller, 2, 0x800, 0); | |
} | |
} | |
} | |
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"); | |
dmc_buffer_queue_init_all(controller); | |
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; | |
dmc_reset_unit_stats(controller->stats); | |
} | |
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_socket(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; | |
} |