The makefile now works for Linux and most Unix's. Howevr, for Solaris and MacOS, you must first export the OSTYPE environment variable: > export OSTYPE > make Otherwise, you will get build errors. 1. New Features 1.1 3.8-0 1.1.1 SCP and Libraries - BREAK, NOBREAK, and SHOW BREAK with no argument will set, clear, and show (respectively) a breakpoint at the current PC. 1.2 GRI - Added support for the GRI-99 processor. 1.3 HP2100 - Added support for the BACI terminal interface. - Added support for RTE OS/VMA/EMA, SIGNAL, VIS firmware extensions. 1.4 Nova - Added support for 64KW memory (implemented in third-party CPU's). 1.5 PDP-11 - Added support for DC11, RC11, KE11A, KG11A. - Added modem control support for DL11. - Added ASCII character support for all 8b devices. 2. Bugs Fixed Please see the revision history on http://simh.trailing-edge.com or in the source module sim_rev.h.
301 lines
14 KiB
C
301 lines
14 KiB
C
/* altairz80_net.c: networking capability
|
|
|
|
Copyright (c) 2002-2008, 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"
|
|
extern uint32 PCX;
|
|
extern char messageBuffer[];
|
|
extern void printMessage(void);
|
|
|
|
#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
|
|
|
|
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 */
|
|
#define NET_ACCEPT 1 /* bit masks for trace_level */
|
|
#define NET_DROP 2
|
|
#define NET_IN 4
|
|
#define NET_OUT 8
|
|
static int32 trace_level = 0;
|
|
|
|
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 },
|
|
{ HRDATA (TRACELEVEL, trace_level, 32) },
|
|
{ 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) { /* cannot remove due to following else */
|
|
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;
|
|
if (trace_level & NET_ACCEPT) {
|
|
MESSAGE_3("Accepted connection %i with socket %i.", i, s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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" NLP);
|
|
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) {
|
|
if (trace_level & NET_DROP) {
|
|
MESSAGE_3("Drop connection %i with socket %i.", i, serviceDescriptor[i].ioSocket);
|
|
}
|
|
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" NLP, r);
|
|
}
|
|
}
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
int32 netStatus(const int32 port, const int32 io, const int32 data) {
|
|
uint32 i;
|
|
if ((net_unit.flags & UNIT_ATT) == 0) return 0;
|
|
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;
|
|
if ((net_unit.flags & UNIT_ATT) == 0) return 0;
|
|
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" NLP, 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--;
|
|
}
|
|
if (trace_level & NET_IN) {
|
|
MESSAGE_4(" IN(%i)=%03xh (%c)", port, (result & 0xff),
|
|
(32 <= (result & 0xff)) && ((result & 0xff) <= 127) ? (result & 0xff) : '?');
|
|
}
|
|
return result;
|
|
}
|
|
else { /* OUT */
|
|
if (serviceDescriptor[i].outputSize == BUFFER_LENGTH) {
|
|
printf("over-write %i to %i" NLP, 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++;
|
|
}
|
|
if (trace_level & NET_OUT) {
|
|
MESSAGE_4("OUT(%i)=%03xh (%c)", port, data,
|
|
(32 <= data) && (data <= 127) ? data : '?');
|
|
}
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|