OXYGENHUB
◆ main x16-emulator / src / netcard.c
8.9 KiB · C · last changed chore(): initial commit 15 days ago source blame history raw
1
// Commander X16 Emulator — a virtual serial/network card. See netcard.h.
2
//
3
// THE MODEM'S SIDE OF THE CONVERSATION, as the X16 floor of C*'s CLM expects
4
// it, because that floor is what this exists to test:
5
//
6
//   +++            in data mode, back to command mode with the call kept
7
//   ATH0<CR>       hang up; OK if there was a call, ERROR if not
8
//   ATD"host:port"<CR>   dial; CONNECT, or NO CARRIER when nothing answers
9
//   ATI<CR>        a Zimodem-shaped report: CONNECTED TO <ssid> (<address>)
10
//   ATE0/ATE1      echo off/on (on, as Zimodem ships)
11
//   AT<CR>         OK
12
//
13
// Everything else is answered ERROR, which is also what Zimodem does.
14
// Commands are echoed while echo is on, as the card's firmware does, so a
15
// program that has to skip its own echo gets to practise here.
16
17
#ifndef _WIN32
18
#define _POSIX_C_SOURCE 200809L   // getaddrinfo under -std=c11
19
#define _DEFAULT_SOURCE
20
#endif
21
22
#include "netcard.h"
23
24
#include <ctype.h>
25
#include <stdio.h>
26
#include <stdlib.h>
27
#include <string.h>
28
29
#ifndef _WIN32
30
#include <errno.h>
31
#include <fcntl.h>
32
#include <netdb.h>
33
#include <netinet/in.h>
34
#include <netinet/tcp.h>
35
#include <sys/select.h>
36
#include <sys/socket.h>
37
#include <unistd.h>
38
#define NETCARD_SOCKETS 1
39
#else
40
#define NETCARD_SOCKETS 0
41
#endif
42
43
bool has_netcard = false;
44
uint16_t netcard_addr = 0x9f60;
45
46
// 16450 registers
47
#define LCR_DLAB   0x80
48
#define LSR_DR     0x01
49
#define LSR_OE     0x02
50
#define LSR_THRE   0x20
51
#define LSR_TEMT   0x40
52
#define MSR_CTS    0x10
53
#define MSR_DSR    0x20
54
#define MSR_DCD    0x80
55
56
#define RX_CAP 4096
57
#define LINE_CAP 256
58
59
static struct {
60
	uint8_t dll, dlm, ier, lcr, mcr, scr, fcr;
61
	uint8_t rx[RX_CAP];
62
	int rx_head, rx_tail;
63
	bool overrun;
64
	// the modem
65
	bool echo;
66
	bool online;            // a call is up (the socket is open)
67
	bool data_mode;         // bytes go to the wire rather than the parser
68
	char line[LINE_CAP];
69
	int line_len;
70
	int plus_count;         // +++ seen in data mode
71
	int fd;
72
	bool connecting;
73
	char host[128];
74
	int port;
75
} card;
76
77
static void rx_put(uint8_t b)
78
{
79
	int next = (card.rx_tail + 1) % RX_CAP;
80
	if (next == card.rx_head) {
81
		card.overrun = true;
82
		return;
83
	}
84
	card.rx[card.rx_tail] = b;
85
	card.rx_tail = next;
86
}
87
88
static void rx_str(const char *s)
89
{
90
	while (*s) {
91
		rx_put((uint8_t)*s++);
92
	}
93
}
94
95
static void reply(const char *s)
96
{
97
	rx_str("\r\n");
98
	rx_str(s);
99
	rx_str("\r\n");
100
}
101
102
static void hangup(void)
103
{
104
#if NETCARD_SOCKETS
105
	if (card.fd >= 0) {
106
		close(card.fd);
107
	}
108
#endif
109
	card.fd = -1;
110
	card.online = false;
111
	card.connecting = false;
112
	card.data_mode = false;
113
}
114
115
void netcard_init(void)
116
{
117
	memset(&card, 0, sizeof card);
118
	card.fd = -1;
119
	card.echo = true;
120
	card.lcr = 0x03;
121
}
122
123
// ATD"host:port" — begin a connection; the answer comes from netcard_step.
124
static void dial(const char *arg)
125
{
126
	char buf[160];
127
	char *colon;
128
	size_t n;
129
130
	while (*arg == '"' || *arg == 'T' || *arg == 'P' || *arg == ' ') {
131
		arg++;
132
	}
133
	n = strlen(arg);
134
	if (n >= sizeof buf) {
135
		reply("ERROR");
136
		return;
137
	}
138
	memcpy(buf, arg, n + 1);
139
	while (n > 0 && (buf[n - 1] == '"' || buf[n - 1] == ' ')) {
140
		buf[--n] = '\0';
141
	}
142
	colon = strrchr(buf, ':');
143
	if (!colon || colon == buf) {
144
		reply("ERROR");
145
		return;
146
	}
147
	*colon = '\0';
148
	snprintf(card.host, sizeof card.host, "%s", buf);
149
	card.port = atoi(colon + 1);
150
	if (card.port <= 0 || card.port > 65535) {
151
		reply("ERROR");
152
		return;
153
	}
154
155
#if NETCARD_SOCKETS
156
	{
157
		struct addrinfo hints, *res = NULL;
158
		char portstr[8];
159
		snprintf(portstr, sizeof portstr, "%d", card.port);
160
		memset(&hints, 0, sizeof hints);
161
		hints.ai_family = AF_UNSPEC;
162
		hints.ai_socktype = SOCK_STREAM;
163
		if (getaddrinfo(card.host, portstr, &hints, &res) != 0 || !res) {
164
			reply("NO CARRIER");
165
			return;
166
		}
167
		card.fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
168
		if (card.fd < 0) {
169
			freeaddrinfo(res);
170
			reply("NO CARRIER");
171
			return;
172
		}
173
		fcntl(card.fd, F_SETFL, fcntl(card.fd, F_GETFL, 0) | O_NONBLOCK);
174
		{
175
			int one = 1;
176
			(void)setsockopt(card.fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one);
177
		}
178
		if (connect(card.fd, res->ai_addr, res->ai_addrlen) < 0 && errno != EINPROGRESS) {
179
			freeaddrinfo(res);
180
			hangup();
181
			reply("NO CARRIER");
182
			return;
183
		}
184
		freeaddrinfo(res);
185
		card.connecting = true;
186
	}
187
#else
188
	reply("NO CARRIER");
189
#endif
190
}
191
192
// One AT command line, without its CR.
193
static void command(char *cmd)
194
{
195
	char *p = cmd;
196
	while (*p == '+') {           // a +++ that arrived in command mode is nothing
197
		p++;
198
	}
199
	while (*p == ' ') {
200
		p++;
201
	}
202
	if (*p == '\0') {
203
		return;
204
	}
205
	if (toupper((unsigned char)p[0]) != 'A' || toupper((unsigned char)p[1]) != 'T') {
206
		reply("ERROR");
207
		return;
208
	}
209
	p += 2;
210
	if (*p == '\0') {
211
		reply("OK");
212
		return;
213
	}
214
	switch (toupper((unsigned char)*p)) {
215
		case 'H':
216
			if (card.online || card.connecting) {
217
				hangup();
218
				reply("OK");
219
			} else {
220
				reply("ERROR");
221
			}
222
			return;
223
		case 'D':
224
			if (card.online || card.connecting) {
225
				reply("ERROR");
226
				return;
227
			}
228
			dial(p + 1);
229
			return;
230
		case 'E':
231
			card.echo = (p[1] != '0');
232
			reply("OK");
233
			return;
234
		case 'I':
235
			rx_str("\r\nZimodem-ish 1.0 (x16emu virtual card)\r\n");
236
			rx_str("CONNECTED TO x16emu (127.0.0.1)\r\n");
237
			reply("OK");
238
			return;
239
		case 'O':
240
			if (card.online) {
241
				card.data_mode = true;
242
				reply("CONNECT");
243
			} else {
244
				reply("ERROR");
245
			}
246
			return;
247
		case 'Z':
248
			hangup();
249
			card.echo = true;
250
			reply("OK");
251
			return;
252
		default:
253
			reply("ERROR");
254
			return;
255
	}
256
}
257
258
static void tx_byte(uint8_t b)
259
{
260
	if (card.data_mode) {
261
		// +++ with nothing else around it escapes to command mode. The guard
262
		// time is not modelled; three plusses in a row are enough here.
263
		if (b == '+') {
264
			if (++card.plus_count == 3) {
265
				card.plus_count = 0;
266
				card.data_mode = false;
267
				reply("OK");
268
			}
269
			return;
270
		}
271
		if (card.plus_count) {
272
			// they were data after all
273
#if NETCARD_SOCKETS
274
			if (card.fd >= 0) {
275
				const char plus[3] = {'+', '+', '+'};
276
				(void)!write(card.fd, plus, card.plus_count);
277
			}
278
#endif
279
			card.plus_count = 0;
280
		}
281
#if NETCARD_SOCKETS
282
		if (card.fd >= 0) {
283
			(void)!write(card.fd, &b, 1);
284
		}
285
#endif
286
		return;
287
	}
288
	if (card.echo) {
289
		rx_put(b);
290
	}
291
	if (b == '\r' || b == '\n') {
292
		if (card.line_len > 0) {
293
			card.line[card.line_len] = '\0';
294
			card.line_len = 0;
295
			command(card.line);
296
		}
297
		return;
298
	}
299
	if (b == 8 || b == 127) {
300
		if (card.line_len > 0) {
301
			card.line_len--;
302
		}
303
		return;
304
	}
305
	if (card.line_len < LINE_CAP - 1) {
306
		card.line[card.line_len++] = (char)b;
307
	}
308
}
309
310
// Called once a frame from the main loop: finish a connection in progress,
311
// and move bytes from the wire into the receive FIFO.
312
void netcard_step(void)
313
{
314
#if NETCARD_SOCKETS
315
	if (card.connecting) {
316
		fd_set w;
317
		struct timeval tv = {0, 0};
318
		FD_ZERO(&w);
319
		FD_SET(card.fd, &w);
320
		if (select(card.fd + 1, NULL, &w, NULL, &tv) > 0) {
321
			int err = 0;
322
			socklen_t len = sizeof err;
323
			(void)getsockopt(card.fd, SOL_SOCKET, SO_ERROR, &err, &len);
324
			card.connecting = false;
325
			if (err != 0) {
326
				hangup();
327
				reply("NO CARRIER");
328
			} else {
329
				card.online = true;
330
				card.data_mode = true;
331
				card.plus_count = 0;
332
				reply("CONNECT");
333
			}
334
		}
335
		return;
336
	}
337
	if (card.online && card.fd >= 0) {
338
		uint8_t buf[512];
339
		for (;;) {
340
			ssize_t n = read(card.fd, buf, sizeof buf);
341
			if (n > 0) {
342
				for (ssize_t i = 0; i < n; i++) {
343
					rx_put(buf[i]);
344
				}
345
				if (n < (ssize_t)sizeof buf) {
346
					break;
347
				}
348
			} else if (n == 0) {
349
				hangup();
350
				reply("NO CARRIER");
351
				break;
352
			} else {
353
				break;    // EAGAIN: nothing waiting
354
			}
355
		}
356
	}
357
#endif
358
}
359
360
uint8_t netcard_read(uint8_t reg, bool debugOn)
361
{
362
	if (reg >= 8) {
363
		return 0x9f;    // the DE-9 half: not here
364
	}
365
	switch (reg) {
366
		case 0:
367
			if (card.lcr & LCR_DLAB) {
368
				return card.dll;
369
			}
370
			if (card.rx_head == card.rx_tail) {
371
				return 0;
372
			}
373
			{
374
				uint8_t b = card.rx[card.rx_head];
375
				if (!debugOn) {
376
					card.rx_head = (card.rx_head + 1) % RX_CAP;
377
				}
378
				return b;
379
			}
380
		case 1:
381
			return (card.lcr & LCR_DLAB) ? card.dlm : card.ier;
382
		case 2:
383
			return 0x01 | (card.fcr & 0x01 ? 0xc0 : 0);   // no interrupt pending
384
		case 3:
385
			return card.lcr;
386
		case 4:
387
			return card.mcr & 0x3f;
388
		case 5: {
389
			uint8_t lsr = LSR_THRE | LSR_TEMT;
390
			if (card.rx_head != card.rx_tail) {
391
				lsr |= LSR_DR;
392
			}
393
			if (card.overrun) {
394
				lsr |= LSR_OE;
395
				if (!debugOn) {
396
					card.overrun = false;
397
				}
398
			}
399
			return lsr;
400
		}
401
		case 6: {
402
			uint8_t msr = MSR_CTS | MSR_DSR;
403
			if (card.online) {
404
				msr |= MSR_DCD;
405
			}
406
			return msr;
407
		}
408
		case 7:
409
			return card.scr;
410
	}
411
	return 0x9f;
412
}
413
414
void netcard_write(uint8_t reg, uint8_t val)
415
{
416
	if (reg >= 8) {
417
		return;
418
	}
419
	switch (reg) {
420
		case 0:
421
			if (card.lcr & LCR_DLAB) {
422
				card.dll = val;
423
			} else {
424
				tx_byte(val);
425
			}
426
			break;
427
		case 1:
428
			if (card.lcr & LCR_DLAB) {
429
				card.dlm = val;
430
			} else {
431
				card.ier = val & 0x0f;
432
			}
433
			break;
434
		case 2:
435
			card.fcr = val;
436
			if (val & 0x02) {    // clear the receive FIFO
437
				card.rx_head = card.rx_tail = 0;
438
			}
439
			break;
440
		case 3:
441
			card.lcr = val;
442
			break;
443
		case 4:
444
			card.mcr = val;
445
			break;
446
		case 7:
447
			card.scr = val;
448
			break;
449
		default:
450
			break;
451
	}
452
}