| 1 | /* altairz80_net.c: networking capability\r |
| 2 | \r |
| 3 | Copyright (c) 2002-2008, Peter Schorn\r |
| 4 | \r |
| 5 | Permission is hereby granted, free of charge, to any person obtaining a\r |
| 6 | copy of this software and associated documentation files (the "Software"),\r |
| 7 | to deal in the Software without restriction, including without limitation\r |
| 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense,\r |
| 9 | and/or sell copies of the Software, and to permit persons to whom the\r |
| 10 | Software is furnished to do so, subject to the following conditions:\r |
| 11 | \r |
| 12 | The above copyright notice and this permission notice shall be included in\r |
| 13 | all copies or substantial portions of the Software.\r |
| 14 | \r |
| 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r |
| 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r |
| 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\r |
| 18 | PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\r |
| 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r |
| 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r |
| 21 | \r |
| 22 | Except as contained in this notice, the name of Peter Schorn shall not\r |
| 23 | be used in advertising or otherwise to promote the sale, use or other dealings\r |
| 24 | in this Software without prior written authorization from Peter Schorn.\r |
| 25 | */\r |
| 26 | \r |
| 27 | #include "altairz80_defs.h"\r |
| 28 | #include "sim_sock.h"\r |
| 29 | extern uint32 PCX;\r |
| 30 | extern char messageBuffer[];\r |
| 31 | extern void printMessage(void);\r |
| 32 | \r |
| 33 | #define UNIT_V_SERVER (UNIT_V_UF + 0) /* define machine as a server */\r |
| 34 | #define UNIT_SERVER (1 << UNIT_V_SERVER)\r |
| 35 | #define NET_INIT_POLL_SERVER 16000\r |
| 36 | #define NET_INIT_POLL_CLIENT 15000\r |
| 37 | \r |
| 38 | static t_stat net_attach (UNIT *uptr, char *cptr);\r |
| 39 | static t_stat net_detach (UNIT *uptr);\r |
| 40 | static t_stat net_reset (DEVICE *dptr);\r |
| 41 | static t_stat net_svc (UNIT *uptr);\r |
| 42 | static t_stat set_net (UNIT *uptr, int32 value, char *cptr, void *desc);\r |
| 43 | int32 netStatus (const int32 port, const int32 io, const int32 data);\r |
| 44 | int32 netData (const int32 port, const int32 io, const int32 data);\r |
| 45 | \r |
| 46 | #define MAX_CONNECTIONS 2 /* maximal number of server connections */\r |
| 47 | #define BUFFER_LENGTH 512 /* length of input and output buffer */\r |
| 48 | #define NET_ACCEPT 1 /* bit masks for trace_level */\r |
| 49 | #define NET_DROP 2\r |
| 50 | #define NET_IN 4\r |
| 51 | #define NET_OUT 8\r |
| 52 | static int32 trace_level = 0;\r |
| 53 | \r |
| 54 | static struct {\r |
| 55 | int32 Z80StatusPort; /* Z80 status port associated with this ioSocket, read only */\r |
| 56 | int32 Z80DataPort; /* Z80 data port associated with this ioSocket, read only */\r |
| 57 | SOCKET masterSocket; /* server master socket, only defined at [1] */\r |
| 58 | SOCKET ioSocket; /* accepted server socket or connected client socket, 0 iff free */\r |
| 59 | char inputBuffer[BUFFER_LENGTH]; /* buffer for input characters read from ioSocket */\r |
| 60 | int32 inputPosRead; /* position of next character to read from buffer */\r |
| 61 | int32 inputPosWrite; /* position of next character to append to input buffer from ioSocket */\r |
| 62 | int32 inputSize; /* number of characters in circular input buffer */\r |
| 63 | char outputBuffer[BUFFER_LENGTH];/* buffer for output characters to be written to ioSocket */\r |
| 64 | int32 outputPosRead; /* position of next character to write to ioSocket */\r |
| 65 | int32 outputPosWrite; /* position of next character to append to output buffer */\r |
| 66 | int32 outputSize; /* number of characters in circular output buffer */\r |
| 67 | } serviceDescriptor[MAX_CONNECTIONS+1] = { /* serviceDescriptor[0] holds the information for a client */\r |
| 68 | /* stat dat ms ios in inPR inPW inS out outPR outPW outS */\r |
| 69 | {50, 51, 0, 0, {0}, 0, 0, 0, {0}, 0, 0, 0}, /* client Z80 port 50 and 51 */\r |
| 70 | {40, 41, 0, 0, {0}, 0, 0, 0, {0}, 0, 0, 0}, /* server Z80 port 40 and 41 */\r |
| 71 | {42, 43, 0, 0, {0}, 0, 0, 0, {0}, 0, 0, 0} /* server Z80 port 42 and 43 */\r |
| 72 | };\r |
| 73 | \r |
| 74 | static UNIT net_unit = {\r |
| 75 | UDATA (&net_svc, UNIT_ATTABLE, 0),\r |
| 76 | 0, /* wait, set in attach */\r |
| 77 | 0, /* u3 = Port */\r |
| 78 | 0, /* u4 = IP of host */\r |
| 79 | 0, /* u5, unused */\r |
| 80 | 0, /* u6, unused */\r |
| 81 | };\r |
| 82 | \r |
| 83 | static REG net_reg[] = {\r |
| 84 | { DRDATA (POLL, net_unit.wait, 32) },\r |
| 85 | { HRDATA (IPHOST, net_unit.u4, 32), REG_RO },\r |
| 86 | { DRDATA (PORT, net_unit.u3, 32), REG_RO },\r |
| 87 | { HRDATA (TRACELEVEL, trace_level, 32) },\r |
| 88 | { NULL }\r |
| 89 | };\r |
| 90 | \r |
| 91 | static MTAB net_mod[] = {\r |
| 92 | { UNIT_SERVER, 0, "CLIENT", "CLIENT", &set_net}, /* machine is a client */\r |
| 93 | { UNIT_SERVER, UNIT_SERVER, "SERVER", "SERVER", &set_net}, /* machine is a server */\r |
| 94 | { 0 }\r |
| 95 | };\r |
| 96 | \r |
| 97 | DEVICE net_dev = {\r |
| 98 | "NET", &net_unit, net_reg, net_mod,\r |
| 99 | 1, 10, 31, 1, 8, 8,\r |
| 100 | NULL, NULL, &net_reset,\r |
| 101 | NULL, &net_attach, &net_detach,\r |
| 102 | NULL, 0, 0,\r |
| 103 | NULL, NULL, NULL\r |
| 104 | };\r |
| 105 | \r |
| 106 | static t_stat set_net(UNIT *uptr, int32 value, char *cptr, void *desc) {\r |
| 107 | char temp[CBUFSIZE];\r |
| 108 | if ((net_unit.flags & UNIT_ATT) && ((net_unit.flags & UNIT_SERVER) != value)) {\r |
| 109 | strncpy(temp, net_unit.filename, CBUFSIZE); /* save name for later attach */\r |
| 110 | net_detach(&net_unit);\r |
| 111 | net_unit.flags ^= UNIT_SERVER; /* now switch from client to server and vice versa */\r |
| 112 | net_attach(uptr, temp);\r |
| 113 | return SCPE_OK;\r |
| 114 | }\r |
| 115 | return SCPE_OK;\r |
| 116 | }\r |
| 117 | \r |
| 118 | static void serviceDescriptor_reset(const uint32 i) {\r |
| 119 | serviceDescriptor[i].inputPosRead = 0;\r |
| 120 | serviceDescriptor[i].inputPosWrite = 0;\r |
| 121 | serviceDescriptor[i].inputSize = 0;\r |
| 122 | serviceDescriptor[i].outputPosRead = 0;\r |
| 123 | serviceDescriptor[i].outputPosWrite = 0;\r |
| 124 | serviceDescriptor[i].outputSize = 0;\r |
| 125 | }\r |
| 126 | \r |
| 127 | static t_stat net_reset(DEVICE *dptr) {\r |
| 128 | uint32 i;\r |
| 129 | if (net_unit.flags & UNIT_ATT)\r |
| 130 | sim_activate(&net_unit, net_unit.wait); /* start poll */\r |
| 131 | for (i = 0; i <= MAX_CONNECTIONS; i++)\r |
| 132 | serviceDescriptor_reset(i);\r |
| 133 | return SCPE_OK;\r |
| 134 | }\r |
| 135 | \r |
| 136 | static t_stat net_attach(UNIT *uptr, char *cptr) {\r |
| 137 | uint32 i, ipa, ipp;\r |
| 138 | t_stat r = get_ipaddr(cptr, &ipa, &ipp);\r |
| 139 | if (r != SCPE_OK) return SCPE_ARG;\r |
| 140 | if (ipa == 0) ipa = 0x7F000001; /* localhost = 127.0.0.1 */\r |
| 141 | if (ipp == 0) ipp = 3000;\r |
| 142 | net_unit.u3 = ipp;\r |
| 143 | net_unit.u4 = ipa;\r |
| 144 | net_reset(&net_dev);\r |
| 145 | for (i = 0; i <= MAX_CONNECTIONS; i++) serviceDescriptor[i].ioSocket = 0;\r |
| 146 | if (net_unit.flags & UNIT_SERVER) {\r |
| 147 | net_unit.wait = NET_INIT_POLL_SERVER;\r |
| 148 | serviceDescriptor[1].masterSocket = sim_master_sock(ipp);\r |
| 149 | if (serviceDescriptor[1].masterSocket == INVALID_SOCKET) return SCPE_IOERR;\r |
| 150 | }\r |
| 151 | else {\r |
| 152 | net_unit.wait = NET_INIT_POLL_CLIENT;\r |
| 153 | serviceDescriptor[0].ioSocket = sim_connect_sock(ipa, ipp);\r |
| 154 | if (serviceDescriptor[0].ioSocket == INVALID_SOCKET) return SCPE_IOERR;\r |
| 155 | }\r |
| 156 | net_unit.flags |= UNIT_ATT;\r |
| 157 | net_unit.filename = (char *) calloc(CBUFSIZE, sizeof (char)); /* alloc name buf */\r |
| 158 | if (net_unit.filename == NULL) return SCPE_MEM;\r |
| 159 | strncpy(net_unit.filename, cptr, CBUFSIZE); /* save name */\r |
| 160 | return SCPE_OK;\r |
| 161 | }\r |
| 162 | \r |
| 163 | static t_stat net_detach(UNIT *uptr) {\r |
| 164 | uint32 i;\r |
| 165 | if (!(net_unit.flags & UNIT_ATT)) return SCPE_OK; /* if not attached simply return */\r |
| 166 | if (net_unit.flags & UNIT_SERVER)\r |
| 167 | sim_close_sock(serviceDescriptor[1].masterSocket, TRUE);\r |
| 168 | for (i = 0; i <= MAX_CONNECTIONS; i++)\r |
| 169 | if (serviceDescriptor[i].ioSocket)\r |
| 170 | sim_close_sock(serviceDescriptor[i].ioSocket, FALSE);\r |
| 171 | free(net_unit.filename); /* free port string */\r |
| 172 | net_unit.filename = NULL;\r |
| 173 | net_unit.flags &= ~UNIT_ATT; /* not attached */\r |
| 174 | return SCPE_OK;\r |
| 175 | }\r |
| 176 | \r |
| 177 | /* cannot use sim_check_conn to check whether read will return an error */\r |
| 178 | static t_stat net_svc(UNIT *uptr) {\r |
| 179 | int32 i, j, k, r;\r |
| 180 | SOCKET s;\r |
| 181 | static char svcBuffer[BUFFER_LENGTH];\r |
| 182 | if (net_unit.flags & UNIT_ATT) { /* cannot remove due to following else */\r |
| 183 | sim_activate(&net_unit, net_unit.wait); /* continue poll */\r |
| 184 | if (net_unit.flags & UNIT_SERVER) {\r |
| 185 | for (i = 1; i <= MAX_CONNECTIONS; i++)\r |
| 186 | if (serviceDescriptor[i].ioSocket == 0) {\r |
| 187 | s = sim_accept_conn(serviceDescriptor[1].masterSocket, NULL);\r |
| 188 | if (s != INVALID_SOCKET) {\r |
| 189 | serviceDescriptor[i].ioSocket = s;\r |
| 190 | if (trace_level & NET_ACCEPT) {\r |
| 191 | MESSAGE_3("Accepted connection %i with socket %i.", i, s);\r |
| 192 | }\r |
| 193 | }\r |
| 194 | }\r |
| 195 | }\r |
| 196 | else if (serviceDescriptor[0].ioSocket == 0) {\r |
| 197 | serviceDescriptor[0].ioSocket = sim_connect_sock(net_unit.u4, net_unit.u3);\r |
| 198 | if (serviceDescriptor[0].ioSocket == INVALID_SOCKET) return SCPE_IOERR;\r |
| 199 | printf("\rWaiting for server ... Type g<return> (possibly twice) when ready" NLP);\r |
| 200 | return SCPE_STOP;\r |
| 201 | }\r |
| 202 | for (i = 0; i <= MAX_CONNECTIONS; i++)\r |
| 203 | if (serviceDescriptor[i].ioSocket) {\r |
| 204 | if (serviceDescriptor[i].inputSize < BUFFER_LENGTH) { /* there is space left in inputBuffer */\r |
| 205 | r = sim_read_sock(serviceDescriptor[i].ioSocket, svcBuffer,\r |
| 206 | BUFFER_LENGTH - serviceDescriptor[i].inputSize);\r |
| 207 | if (r == -1) {\r |
| 208 | if (trace_level & NET_DROP) {\r |
| 209 | MESSAGE_3("Drop connection %i with socket %i.", i, serviceDescriptor[i].ioSocket);\r |
| 210 | }\r |
| 211 | sim_close_sock(serviceDescriptor[i].ioSocket, FALSE);\r |
| 212 | serviceDescriptor[i].ioSocket = 0;\r |
| 213 | serviceDescriptor_reset(i);\r |
| 214 | continue;\r |
| 215 | }\r |
| 216 | else {\r |
| 217 | for (j = 0; j < r; j++) {\r |
| 218 | serviceDescriptor[i].inputBuffer[serviceDescriptor[i].inputPosWrite++] = svcBuffer[j];\r |
| 219 | if (serviceDescriptor[i].inputPosWrite == BUFFER_LENGTH)\r |
| 220 | serviceDescriptor[i].inputPosWrite = 0;\r |
| 221 | }\r |
| 222 | serviceDescriptor[i].inputSize += r;\r |
| 223 | }\r |
| 224 | }\r |
| 225 | if (serviceDescriptor[i].outputSize > 0) { /* there is something to write in outputBuffer */\r |
| 226 | k = serviceDescriptor[i].outputPosRead;\r |
| 227 | for (j = 0; j < serviceDescriptor[i].outputSize; j++) {\r |
| 228 | svcBuffer[j] = serviceDescriptor[i].outputBuffer[k++];\r |
| 229 | if (k == BUFFER_LENGTH) k = 0;\r |
| 230 | }\r |
| 231 | r = sim_write_sock(serviceDescriptor[i].ioSocket, svcBuffer, serviceDescriptor[i].outputSize);\r |
| 232 | if (r >= 0) {\r |
| 233 | serviceDescriptor[i].outputSize -= r;\r |
| 234 | serviceDescriptor[i].outputPosRead += r;\r |
| 235 | if (serviceDescriptor[i].outputPosRead >= BUFFER_LENGTH)\r |
| 236 | serviceDescriptor[i].outputPosRead -= BUFFER_LENGTH;\r |
| 237 | }\r |
| 238 | else printf("write %i" NLP, r);\r |
| 239 | }\r |
| 240 | }\r |
| 241 | }\r |
| 242 | return SCPE_OK;\r |
| 243 | }\r |
| 244 | \r |
| 245 | int32 netStatus(const int32 port, const int32 io, const int32 data) {\r |
| 246 | uint32 i;\r |
| 247 | if ((net_unit.flags & UNIT_ATT) == 0) return 0;\r |
| 248 | net_svc(&net_unit);\r |
| 249 | if (io == 0) /* IN */\r |
| 250 | for (i = 0; i <= MAX_CONNECTIONS; i++)\r |
| 251 | if (serviceDescriptor[i].Z80StatusPort == port)\r |
| 252 | return (serviceDescriptor[i].inputSize > 0 ? 1 : 0) |\r |
| 253 | (serviceDescriptor[i].outputSize < BUFFER_LENGTH ? 2 : 0);\r |
| 254 | return 0;\r |
| 255 | }\r |
| 256 | \r |
| 257 | int32 netData(const int32 port, const int32 io, const int32 data) {\r |
| 258 | uint32 i;\r |
| 259 | char result;\r |
| 260 | if ((net_unit.flags & UNIT_ATT) == 0) return 0;\r |
| 261 | net_svc(&net_unit);\r |
| 262 | for (i = 0; i <= MAX_CONNECTIONS; i++)\r |
| 263 | if (serviceDescriptor[i].Z80DataPort == port)\r |
| 264 | if (io == 0) { /* IN */\r |
| 265 | if (serviceDescriptor[i].inputSize == 0) {\r |
| 266 | printf("re-read from %i" NLP, port);\r |
| 267 | result = serviceDescriptor[i].inputBuffer[serviceDescriptor[i].inputPosRead > 0 ?\r |
| 268 | serviceDescriptor[i].inputPosRead - 1 : BUFFER_LENGTH - 1];\r |
| 269 | }\r |
| 270 | else {\r |
| 271 | result = serviceDescriptor[i].inputBuffer[serviceDescriptor[i].inputPosRead++];\r |
| 272 | if (serviceDescriptor[i].inputPosRead == BUFFER_LENGTH)\r |
| 273 | serviceDescriptor[i].inputPosRead = 0;\r |
| 274 | serviceDescriptor[i].inputSize--;\r |
| 275 | }\r |
| 276 | if (trace_level & NET_IN) {\r |
| 277 | MESSAGE_4(" IN(%i)=%03xh (%c)", port, (result & 0xff),\r |
| 278 | (32 <= (result & 0xff)) && ((result & 0xff) <= 127) ? (result & 0xff) : '?');\r |
| 279 | }\r |
| 280 | return result;\r |
| 281 | }\r |
| 282 | else { /* OUT */\r |
| 283 | if (serviceDescriptor[i].outputSize == BUFFER_LENGTH) {\r |
| 284 | printf("over-write %i to %i" NLP, data, port);\r |
| 285 | serviceDescriptor[i].outputBuffer[serviceDescriptor[i].outputPosWrite > 0 ?\r |
| 286 | serviceDescriptor[i].outputPosWrite - 1 : BUFFER_LENGTH - 1] = data;\r |
| 287 | }\r |
| 288 | else {\r |
| 289 | serviceDescriptor[i].outputBuffer[serviceDescriptor[i].outputPosWrite++] = data;\r |
| 290 | if (serviceDescriptor[i].outputPosWrite== BUFFER_LENGTH)\r |
| 291 | serviceDescriptor[i].outputPosWrite = 0;\r |
| 292 | serviceDescriptor[i].outputSize++;\r |
| 293 | }\r |
| 294 | if (trace_level & NET_OUT) {\r |
| 295 | MESSAGE_4("OUT(%i)=%03xh (%c)", port, data,\r |
| 296 | (32 <= data) && (data <= 127) ? data : '?');\r |
| 297 | }\r |
| 298 | return 0;\r |
| 299 | }\r |
| 300 | return 0;\r |
| 301 | }\r |