/
persci.c
493 lines (414 loc) · 9.56 KB
/
persci.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "persci.h"
/* ASCII C0 CONTROLS */
#define SOH 1
#define EOT 4
#define ENQ 5
#define ACK 6
#define NAK 21
/* DRIVES */
static FILE *files[4] = { 0, 0, 0, 0 };
static uint8_t *discs[4] = { 0, 0, 0, 0 };
/* flag used by e.g. Raspberry Pi Pico to start using external disc */
uint8_t rf_persci_ejected = 0;
/* STATE */
#define RF_PERSCI_STATE_IDLE 0
#define RF_PERSCI_STATE_INPUT 1
#define RF_PERSCI_STATE_OUTPUT 2
#define RF_PERSCI_STATE_WRITING 3
#define RF_PERSCI_STATE_WRITTEN 4
#define RF_PERSCI_STATE_ERROR 5
static char rf_persci_state = RF_PERSCI_STATE_IDLE;
static int validate_drive_no(int drive)
{
if (drive < 0 || drive > 3) {
fprintf(stderr, "persci: invalid drive number %d\n", drive);
return 1;
}
return 0;
}
int rf_persci_insert(int drive, char *filename)
{
FILE *ptr;
int ret;
/* drive 0-3 only */
if ((ret = validate_drive_no(drive))) {
return ret;
}
/* disc must be not already inserted */
if (files[drive]) {
fprintf(stderr, "persci: file already open: drive %d\n", drive);
return 1;
}
/* open disc file */
if (!(ptr = fopen(filename, "r+b"))) {
ret = errno;
fprintf(stderr, "persci: fopen failed: %s filename=%s\n", strerror(ret), filename);
return ret;
}
/* allocate memory */
if (!(discs[drive] = malloc(256256))) {
ret = errno;
perror("persci: malloc failed");
fclose(ptr);
return ret;
}
/* read file into memory */
fread(discs[drive], 1, 256256, ptr);
if (ferror(ptr)) {
ret = errno;
fprintf(stderr, "persci: fread failed: %s drive=%d\n", strerror(ret), drive);
fclose(ptr);
return ret;
}
/* ok */
files[drive] = ptr;
return 0;
}
int rf_persci_insert_bytes(int drive, const uint8_t *bytes)
{
int ret;
/* drive 0-3 only */
if ((ret = validate_drive_no(drive))) {
return ret;
}
/* point at byte array */
discs[drive] = (uint8_t *) bytes;
return 0;
}
int rf_persci_eject(int drive)
{
int ret;
/* drive 0-3 only */
if ((ret = validate_drive_no(drive))) {
return ret;
}
/* close the file */
if (files[drive]) {
fclose(files[drive]);
files[drive] = 0;
/* assume malloc was used */
if (discs[drive]) {
free(discs[drive]);
}
}
/* if no file, data was static and malloc was not used */
discs[drive] = 0;
/* mark ejected */
rf_persci_ejected = 1;
return 0;
}
/* BUFFER */
static char rf_persci_buf[131];
static unsigned int rf_persci_idx = 0;
static unsigned int rf_persci_len = 0;
/* empty buffer */
static void rf_persci_reset(void)
{
rf_persci_idx = 0;
rf_persci_len = 0;
}
/* write a char to buffer */
static void rf_persci_w(char c)
{
/* validate not full */
if (rf_persci_len >= 131) {
fprintf(stderr, "persci: buffer full\n");
exit(1);
}
rf_persci_buf[rf_persci_len++] = c;
}
/* write a string to buffer */
static void rf_persci_ws(const char *s)
{
const char *i;
for (i = s; *i; i++) {
rf_persci_w(*i);
}
}
/* ERROR HANDLING */
/* write error message */
static void rf_persci_error(const char *message)
{
/* send error */
rf_persci_reset();
rf_persci_w(NAK);
rf_persci_ws(message);
rf_persci_ws(" ERROR\r\n\004");
/* set state */
rf_persci_state = RF_PERSCI_STATE_ERROR;
}
/* write error message with drive number */
static void rf_persci_error_on_drive(const char *message, uint8_t drive)
{
/* send error */
rf_persci_reset();
rf_persci_w(NAK);
rf_persci_ws(message);
rf_persci_ws(" ERROR ON DRIVE #");
rf_persci_w(48 + drive);
rf_persci_ws("\r\n\004");
/* set state */
rf_persci_state = RF_PERSCI_STATE_ERROR;
}
/* READING AND WRITING */
/* validate drive track and sector are within ranges */
static char rf_persci_validate(uint8_t track, uint8_t sector, uint8_t drive)
{
/* validate drive number */
if (drive > 3) {
rf_persci_error("COMMAND");
return 1;
}
/* check disc is present */
if (!discs[drive]) {
rf_persci_error_on_drive("READY", drive);
return 1;
}
/* validate track and sector numbers */
if (track > 76 || sector < 1 || sector > 26) {
rf_persci_error_on_drive("COMMAND", drive);
return 1;
}
return 0;
}
/* track and sector offset */
static long offset(uint8_t track, uint8_t sector)
{
return ((track * 26) + (sector - 1)) * 128;
}
/* I (Input) */
static void rf_persci_input(uint8_t track, uint8_t sector, uint8_t drive)
{
uint8_t i;
uint8_t *p;
/* start response */
rf_persci_reset();
rf_persci_w(SOH);
/* read from memory */
p = discs[drive] + offset(track, sector);
for (i = 128; i; --i) {
rf_persci_w(*(p++));
}
/* ACK EOT */
rf_persci_w(ACK);
rf_persci_w(EOT);
/* set state */
rf_persci_state = RF_PERSCI_STATE_INPUT;
}
static uint8_t rf_persci_drive = 0;
static uint8_t rf_persci_track = 0;
static uint8_t rf_persci_sector = 0;
/* O (Output) */
static void rf_persci_output(uint8_t track, uint8_t sector, uint8_t drive)
{
/* set desired track/sector/drive */
rf_persci_track = track;
rf_persci_sector = sector;
rf_persci_drive = drive;
/* ENQ EOT */
rf_persci_reset();
rf_persci_w(ENQ);
rf_persci_w(EOT);
/* set state */
rf_persci_state = RF_PERSCI_STATE_OUTPUT;
}
/* READ */
/* read next char from buffer */
static char rf_persci_peek(void)
{
/* validate buffer */
if (rf_persci_idx >= rf_persci_len) {
fprintf(stderr, "persci: buffer empty\n");
exit(1);
}
/* read char from buffer */
return rf_persci_buf[rf_persci_idx];
}
/* read next char from buffer and advance */
static char rf_persci_r(void)
{
char c = rf_persci_peek();
/* advance and reset if empty */
rf_persci_idx++;
if (rf_persci_idx >= rf_persci_len) {
rf_persci_idx = 0;
rf_persci_len = 0;
}
return c;
}
/* write data after O */
static void rf_persci_write(void)
{
FILE *ptr;
size_t s;
long off;
size_t len;
/* get size of data */
for (len = 0; rf_persci_r() != EOT; ++len) {
}
/* get location */
off = offset(rf_persci_track, rf_persci_sector);
/* write data to file (if present) */
if ((ptr = files[rf_persci_drive])) {
/* move to track and sector */
if (fseek(ptr, off, SEEK_SET)) {
perror("persci: fseek failed");
rf_persci_error_on_drive("HARD DISK", rf_persci_drive);
return;
}
/* write sector */
s = fwrite(rf_persci_buf, 1, len, ptr);
if (s != len) {
perror("persci: fwrite failed");
rf_persci_error_on_drive("HARD DISK", rf_persci_drive);
return;
}
if (fflush(ptr)) {
perror("persci: fflush failed");
rf_persci_error_on_drive("HARD DISK", rf_persci_drive);
}
}
/* write data to memory */
memcpy(
discs[rf_persci_drive] + off,
rf_persci_buf,
len);
/* ACK EOT */
rf_persci_reset();
rf_persci_w(ACK);
rf_persci_w(EOT);
/* update state */
rf_persci_state = RF_PERSCI_STATE_WRITTEN;
}
/* COMMAND PARSING */
/* skip whitespace in buffer */
static void rf_persci_read_ws(void)
{
while (rf_persci_peek() == ' ') {
rf_persci_r();
}
}
/* read decimal int from buffer */
static char rf_persci_read_int(void)
{
char i, c;
rf_persci_read_ws();
i = 0;
while ((c = rf_persci_peek()) >= '0' && c <= '9') {
rf_persci_r();
i *= 10;
i += (c - 48);
}
return i;
}
/* read char and check it is as expected */
static char rf_persci_expect(char c)
{
rf_persci_read_ws();
if (rf_persci_peek() == c) {
rf_persci_r();
return 1;
}
return 0;
}
/* read command and execute it */
static void rf_persci_command(void)
{
char ch = rf_persci_r();
switch (ch) {
case 'I':
case 'O':
{
int track, sector, drive;
/* track */
track = rf_persci_read_int();
/* sector */
sector = rf_persci_read_int();
/* drive */
if (!rf_persci_expect('/')) {
break;
}
drive = rf_persci_read_int();
/* EOT */
if (!rf_persci_expect(EOT)) {
break;
}
/* validate args */
if (rf_persci_validate(track, sector, drive)) {
return;
}
/* handle command */
switch (ch) {
case 'I':
rf_persci_input(track, sector, drive);
return;
case 'O':
rf_persci_output(track, sector, drive);
return;
}
/* should not get here */
break;
}
}
/* parse failed */
rf_persci_error("COMMAND");
}
/* HANDLE COMMS */
/* handle next operation */
static void rf_persci_serve(void)
{
switch (rf_persci_state) {
case RF_PERSCI_STATE_IDLE:
rf_persci_command();
break;
case RF_PERSCI_STATE_WRITING:
rf_persci_write();
break;
default:
fprintf(stderr, "persci: invalid state\n");
exit(1);
}
}
/* read next char from buffer */
int rf_persci_getc(void)
{
int c;
/* validate state */
if (rf_persci_state != RF_PERSCI_STATE_INPUT &&
rf_persci_state != RF_PERSCI_STATE_OUTPUT &&
rf_persci_state != RF_PERSCI_STATE_WRITTEN &&
rf_persci_state != RF_PERSCI_STATE_ERROR) {
return -1;
}
/* test not empty */
if (rf_persci_idx >= rf_persci_len) {
return -1;
}
/* get char */
c = rf_persci_r();
/* update state */
if (rf_persci_idx == 0) {
rf_persci_state = (rf_persci_state == RF_PERSCI_STATE_OUTPUT) ? RF_PERSCI_STATE_WRITING : RF_PERSCI_STATE_IDLE;
}
return c;
}
/* write next char to buffer and handle if EOT */
int rf_persci_putc(char c)
{
/* validate state */
if (rf_persci_state != RF_PERSCI_STATE_IDLE && rf_persci_state != RF_PERSCI_STATE_WRITING) {
return -1;
}
/* write to buffer */
rf_persci_w(c);
/* on EOT, operate */
if (c == EOT) {
rf_persci_serve();
}
return c;
}