1. New Features 1.1 3.7-0 1.1.1 SCP - Added SET THROTTLE and SET NOTHROTTLE commands to regulate simulator execution rate and host resource utilization. - Added idle support (based on work by Mark Pizzolato). - Added -e to control error processing in nested DO commands (from Dave Bryan). 1.1.2 HP2100 - Added Double Integer instructions, 1000-F CPU, and Floating Point Processor (from Dave Bryan). - Added 2114 and 2115 CPUs, 12607B and 12578A DMA controllers, and 21xx binary loader protection (from Dave Bryan). 1.1.3 Interdata - Added SET IDLE and SET NOIDLE commands to idle the simulator in wait state. 1.1.4 PDP-11 - Added SET IDLE and SET NOIDLE commands to idle the simulator in wait state (WAIT instruction executed). - Added TA11/TU60 cassette support. 1.1.5 PDP-8 - Added SET IDLE and SET NOIDLE commands to idle the simulator in wait state (keyboard poll loop or jump-to-self). - Added TA8E/TU60 cassette support. 1.1.6 PDP-1 - Added support for 16-channel sequence break system. - Added support for PDP-1D extended features and timesharing clock. - Added support for Type 630 data communications subsystem. 1.1.6 PDP-4/7/9/15 - Added SET IDLE and SET NOIDLE commands to idle the simulator in wait state (keyboard poll loop or jump-to-self). 1.1.7 VAX, VAX780 - Added SET IDLE and SET NOIDLE commands to idle the simulator in wait state (more than 200 cycles at IPL's 0, 1, or 3 in kernel mode). 1.1.8 PDP-10 - Added SET IDLE and SET NOIDLE commands to idle the simulator in wait state (operating system dependent). - Added CD20 (CD11) support. 2. Bugs Fixed Please see the revision history on http://simh.trailing-edge.com or in the source module sim_rev.h.
292 lines
13 KiB
C
292 lines
13 KiB
C
/* altairz80_net.c: networking capability
|
|
|
|
Copyright (c) 2002-2007, Peter Schorn
|
|
|
|
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
|
|
PETER SCHORN 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 Peter Schorn shall not
|
|
be used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from Peter Schorn.
|
|
*/
|
|
|
|
#include "altairz80_defs.h"
|
|
#include "sim_sock.h"
|
|
|
|
#define UNIT_V_SERVER (UNIT_V_UF + 0) /* define machine as a server */
|
|
#define UNIT_SERVER (1 << UNIT_V_SERVER)
|
|
#define NET_INIT_POLL_SERVER 16000
|
|
#define NET_INIT_POLL_CLIENT 15000
|
|
|
|
/*#define DEBUG_NETWORK TRUE*/
|
|
|
|
static t_stat net_attach (UNIT *uptr, char *cptr);
|
|
static t_stat net_detach (UNIT *uptr);
|
|
static t_stat net_reset (DEVICE *dptr);
|
|
static t_stat net_svc (UNIT *uptr);
|
|
static t_stat set_net (UNIT *uptr, int32 value, char *cptr, void *desc);
|
|
int32 netStatus (const int32 port, const int32 io, const int32 data);
|
|
int32 netData (const int32 port, const int32 io, const int32 data);
|
|
|
|
#define MAX_CONNECTIONS 2 /* maximal number of server connections */
|
|
#define BUFFER_LENGTH 512 /* length of input and output buffer */
|
|
|
|
static struct {
|
|
int32 Z80StatusPort; /* Z80 status port associated with this ioSocket, read only */
|
|
int32 Z80DataPort; /* Z80 data port associated with this ioSocket, read only */
|
|
SOCKET masterSocket; /* server master socket, only defined at [1] */
|
|
SOCKET ioSocket; /* accepted server socket or connected client socket, 0 iff free */
|
|
char inputBuffer[BUFFER_LENGTH]; /* buffer for input characters read from ioSocket */
|
|
int32 inputPosRead; /* position of next character to read from buffer */
|
|
int32 inputPosWrite; /* position of next character to append to input buffer from ioSocket */
|
|
int32 inputSize; /* number of characters in circular input buffer */
|
|
char outputBuffer[BUFFER_LENGTH];/* buffer for output characters to be written to ioSocket */
|
|
int32 outputPosRead; /* position of next character to write to ioSocket */
|
|
int32 outputPosWrite; /* position of next character to append to output buffer */
|
|
int32 outputSize; /* number of characters in circular output buffer */
|
|
} serviceDescriptor[MAX_CONNECTIONS+1] = { /* serviceDescriptor[0] holds the information for a client */
|
|
/* stat dat ms ios in inPR inPW inS out outPR outPW outS */
|
|
{50, 51, 0, 0, {0}, 0, 0, 0, {0}, 0, 0, 0}, /* client Z80 port 50 and 51 */
|
|
{40, 41, 0, 0, {0}, 0, 0, 0, {0}, 0, 0, 0}, /* server Z80 port 40 and 41 */
|
|
{42, 43, 0, 0, {0}, 0, 0, 0, {0}, 0, 0, 0} /* server Z80 port 42 and 43 */
|
|
};
|
|
|
|
static UNIT net_unit = {
|
|
UDATA (&net_svc, UNIT_ATTABLE, 0),
|
|
0, /* wait, set in attach */
|
|
0, /* u3 = Port */
|
|
0, /* u4 = IP of host */
|
|
0, /* u5, unused */
|
|
0, /* u6, unused */
|
|
};
|
|
|
|
static REG net_reg[] = {
|
|
{ DRDATA (POLL, net_unit.wait, 32) },
|
|
{ HRDATA (IPHOST, net_unit.u4, 32), REG_RO },
|
|
{ DRDATA (PORT, net_unit.u3, 32), REG_RO },
|
|
{ NULL }
|
|
};
|
|
|
|
static MTAB net_mod[] = {
|
|
{ UNIT_SERVER, 0, "CLIENT", "CLIENT", &set_net}, /* machine is a client */
|
|
{ UNIT_SERVER, UNIT_SERVER, "SERVER", "SERVER", &set_net}, /* machine is a server */
|
|
{ 0 }
|
|
};
|
|
|
|
DEVICE net_dev = {
|
|
"NET", &net_unit, net_reg, net_mod,
|
|
1, 10, 31, 1, 8, 8,
|
|
NULL, NULL, &net_reset,
|
|
NULL, &net_attach, &net_detach,
|
|
NULL, 0, 0,
|
|
NULL, NULL, NULL
|
|
};
|
|
|
|
static t_stat set_net(UNIT *uptr, int32 value, char *cptr, void *desc) {
|
|
char temp[CBUFSIZE];
|
|
if ((net_unit.flags & UNIT_ATT) && ((net_unit.flags & UNIT_SERVER) != value)) {
|
|
strncpy(temp, net_unit.filename, CBUFSIZE); /* save name for later attach */
|
|
net_detach(&net_unit);
|
|
net_unit.flags ^= UNIT_SERVER; /* now switch from client to server and vice versa */
|
|
net_attach(uptr, temp);
|
|
return SCPE_OK;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static void serviceDescriptor_reset(const uint32 i) {
|
|
serviceDescriptor[i].inputPosRead = 0;
|
|
serviceDescriptor[i].inputPosWrite = 0;
|
|
serviceDescriptor[i].inputSize = 0;
|
|
serviceDescriptor[i].outputPosRead = 0;
|
|
serviceDescriptor[i].outputPosWrite = 0;
|
|
serviceDescriptor[i].outputSize = 0;
|
|
}
|
|
|
|
static t_stat net_reset(DEVICE *dptr) {
|
|
uint32 i;
|
|
if (net_unit.flags & UNIT_ATT)
|
|
sim_activate(&net_unit, net_unit.wait); /* start poll */
|
|
for (i = 0; i <= MAX_CONNECTIONS; i++)
|
|
serviceDescriptor_reset(i);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat net_attach(UNIT *uptr, char *cptr) {
|
|
uint32 i, ipa, ipp;
|
|
t_stat r = get_ipaddr(cptr, &ipa, &ipp);
|
|
if (r != SCPE_OK) return SCPE_ARG;
|
|
if (ipa == 0) ipa = 0x7F000001; /* localhost = 127.0.0.1 */
|
|
if (ipp == 0) ipp = 3000;
|
|
net_unit.u3 = ipp;
|
|
net_unit.u4 = ipa;
|
|
net_reset(&net_dev);
|
|
for (i = 0; i <= MAX_CONNECTIONS; i++) serviceDescriptor[i].ioSocket = 0;
|
|
if (net_unit.flags & UNIT_SERVER) {
|
|
net_unit.wait = NET_INIT_POLL_SERVER;
|
|
serviceDescriptor[1].masterSocket = sim_master_sock(ipp);
|
|
if (serviceDescriptor[1].masterSocket == INVALID_SOCKET) return SCPE_IOERR;
|
|
}
|
|
else {
|
|
net_unit.wait = NET_INIT_POLL_CLIENT;
|
|
serviceDescriptor[0].ioSocket = sim_connect_sock(ipa, ipp);
|
|
if (serviceDescriptor[0].ioSocket == INVALID_SOCKET) return SCPE_IOERR;
|
|
}
|
|
net_unit.flags |= UNIT_ATT;
|
|
net_unit.filename = (char *) calloc(CBUFSIZE, sizeof (char)); /* alloc name buf */
|
|
if (net_unit.filename == NULL) return SCPE_MEM;
|
|
strncpy(net_unit.filename, cptr, CBUFSIZE); /* save name */
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat net_detach(UNIT *uptr) {
|
|
uint32 i;
|
|
if (!(net_unit.flags & UNIT_ATT)) return SCPE_OK; /* if not attached simply return */
|
|
if (net_unit.flags & UNIT_SERVER)
|
|
sim_close_sock(serviceDescriptor[1].masterSocket, TRUE);
|
|
for (i = 0; i <= MAX_CONNECTIONS; i++)
|
|
if (serviceDescriptor[i].ioSocket)
|
|
sim_close_sock(serviceDescriptor[i].ioSocket, FALSE);
|
|
free(net_unit.filename); /* free port string */
|
|
net_unit.filename = NULL;
|
|
net_unit.flags &= ~UNIT_ATT; /* not attached */
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* cannot use sim_check_conn to check whether read will return an error */
|
|
static t_stat net_svc(UNIT *uptr) {
|
|
int32 i, j, k, r;
|
|
SOCKET s;
|
|
static char svcBuffer[BUFFER_LENGTH];
|
|
if (net_unit.flags & UNIT_ATT) {
|
|
sim_activate(&net_unit, net_unit.wait); /* continue poll */
|
|
if (net_unit.flags & UNIT_SERVER) {
|
|
for (i = 1; i <= MAX_CONNECTIONS; i++)
|
|
if (serviceDescriptor[i].ioSocket == 0) {
|
|
s = sim_accept_conn(serviceDescriptor[1].masterSocket, NULL);
|
|
if (s != INVALID_SOCKET) {
|
|
serviceDescriptor[i].ioSocket = s;
|
|
#ifdef DEBUG_NETWORK
|
|
printf("Accepted connection %i with socket %i.\n\r", i, s);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
else if (serviceDescriptor[0].ioSocket == 0) {
|
|
serviceDescriptor[0].ioSocket = sim_connect_sock(net_unit.u4, net_unit.u3);
|
|
if (serviceDescriptor[0].ioSocket == INVALID_SOCKET) return SCPE_IOERR;
|
|
printf("\rWaiting for server ... Type g<return> (possibly twice) when ready\n\r");
|
|
return SCPE_STOP;
|
|
}
|
|
for (i = 0; i <= MAX_CONNECTIONS; i++)
|
|
if (serviceDescriptor[i].ioSocket) {
|
|
if (serviceDescriptor[i].inputSize < BUFFER_LENGTH) { /* there is space left in inputBuffer */
|
|
r = sim_read_sock(serviceDescriptor[i].ioSocket, svcBuffer,
|
|
BUFFER_LENGTH - serviceDescriptor[i].inputSize);
|
|
if (r == -1) {
|
|
#ifdef DEBUG_NETWORK
|
|
printf("Drop connection %i with socket %i.\n\r", i, serviceDescriptor[i].ioSocket);
|
|
#endif
|
|
sim_close_sock(serviceDescriptor[i].ioSocket, FALSE);
|
|
serviceDescriptor[i].ioSocket = 0;
|
|
serviceDescriptor_reset(i);
|
|
continue;
|
|
}
|
|
else {
|
|
for (j = 0; j < r; j++) {
|
|
serviceDescriptor[i].inputBuffer[serviceDescriptor[i].inputPosWrite++] = svcBuffer[j];
|
|
if (serviceDescriptor[i].inputPosWrite == BUFFER_LENGTH)
|
|
serviceDescriptor[i].inputPosWrite = 0;
|
|
}
|
|
serviceDescriptor[i].inputSize += r;
|
|
}
|
|
}
|
|
if (serviceDescriptor[i].outputSize > 0) { /* there is something to write in outputBuffer */
|
|
k = serviceDescriptor[i].outputPosRead;
|
|
for (j = 0; j < serviceDescriptor[i].outputSize; j++) {
|
|
svcBuffer[j] = serviceDescriptor[i].outputBuffer[k++];
|
|
if (k == BUFFER_LENGTH) k = 0;
|
|
}
|
|
r = sim_write_sock(serviceDescriptor[i].ioSocket, svcBuffer, serviceDescriptor[i].outputSize);
|
|
if (r >= 0) {
|
|
serviceDescriptor[i].outputSize -= r;
|
|
serviceDescriptor[i].outputPosRead += r;
|
|
if (serviceDescriptor[i].outputPosRead >= BUFFER_LENGTH)
|
|
serviceDescriptor[i].outputPosRead -= BUFFER_LENGTH;
|
|
}
|
|
else printf("write %i\r\n", r);
|
|
}
|
|
}
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
int32 netStatus(const int32 port, const int32 io, const int32 data) {
|
|
uint32 i;
|
|
net_svc(&net_unit);
|
|
if (io == 0) { /* IN */
|
|
for (i = 0; i <= MAX_CONNECTIONS; i++)
|
|
if (serviceDescriptor[i].Z80StatusPort == port)
|
|
return (serviceDescriptor[i].inputSize > 0 ? 1 : 0) |
|
|
(serviceDescriptor[i].outputSize < BUFFER_LENGTH ? 2 : 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int32 netData(const int32 port, const int32 io, const int32 data) {
|
|
uint32 i;
|
|
char result;
|
|
net_svc(&net_unit);
|
|
for (i = 0; i <= MAX_CONNECTIONS; i++)
|
|
if (serviceDescriptor[i].Z80DataPort == port)
|
|
if (io == 0) { /* IN */
|
|
if (serviceDescriptor[i].inputSize == 0) {
|
|
printf("re-read from %i\r\n", port);
|
|
result = serviceDescriptor[i].inputBuffer[serviceDescriptor[i].inputPosRead > 0 ?
|
|
serviceDescriptor[i].inputPosRead - 1 : BUFFER_LENGTH - 1];
|
|
}
|
|
else {
|
|
result = serviceDescriptor[i].inputBuffer[serviceDescriptor[i].inputPosRead++];
|
|
if (serviceDescriptor[i].inputPosRead == BUFFER_LENGTH)
|
|
serviceDescriptor[i].inputPosRead = 0;
|
|
serviceDescriptor[i].inputSize--;
|
|
}
|
|
#ifdef DEBUG_NETWORK
|
|
printf(" IN(%i)=%03xh (%c)\r\n", port, (result & 0xff),
|
|
(32 <= (result & 0xff)) && ((result & 0xff) <= 127) ? (result & 0xff) : '?');
|
|
#endif
|
|
return result;
|
|
}
|
|
else { /* OUT */
|
|
if (serviceDescriptor[i].outputSize == BUFFER_LENGTH) {
|
|
printf("over-write %i to %i\r\n", data, port);
|
|
serviceDescriptor[i].outputBuffer[serviceDescriptor[i].outputPosWrite > 0 ?
|
|
serviceDescriptor[i].outputPosWrite - 1 : BUFFER_LENGTH - 1] = data;
|
|
}
|
|
else {
|
|
serviceDescriptor[i].outputBuffer[serviceDescriptor[i].outputPosWrite++] = data;
|
|
if (serviceDescriptor[i].outputPosWrite== BUFFER_LENGTH)
|
|
serviceDescriptor[i].outputPosWrite = 0;
|
|
serviceDescriptor[i].outputSize++;
|
|
}
|
|
#ifdef DEBUG_NETWORK
|
|
printf("OUT(%i)=%03xh (%c)\r\n", port, data, (32 <= data) && (data <= 127) ? data : '?');
|
|
#endif
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|