From a74ee8182230931a4dd8f694837394a6762fba5d Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Sat, 11 Jan 2020 22:45:22 -0500 Subject: [PATCH] recipes/trs80: new recipe (WIP) --- recipes/trs80/README.md | 144 ++++++++++++++++++++++++++++++++++++++++ recipes/trs80/recv.asm | 36 ++++++++++ tools/.gitignore | 2 + tools/Makefile | 5 +- tools/pingpong.c | 56 ++++++++++++++++ tools/ttysafe.c | 26 ++++++++ 6 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 recipes/trs80/README.md create mode 100644 recipes/trs80/recv.asm create mode 100644 tools/pingpong.c create mode 100644 tools/ttysafe.c diff --git a/recipes/trs80/README.md b/recipes/trs80/README.md new file mode 100644 index 0000000..fed2e0e --- /dev/null +++ b/recipes/trs80/README.md @@ -0,0 +1,144 @@ +# TRS-80 Model 4p + +The TRS-80 (models 1, 3 and 4) are among the most popular z80 machines. They're +very nicely designed and I got my hands on a 4p with two floppy disk drives and +a RS-232 port. In this recipe, we're going to get Collapse OS running on it. + +**This is a work in progress. Collapse OS doesn't run on it yet.** + +## Floppy or RS-232? + +There are many ways to get Collapse OS to run on it. One would involve writing +it to a floppy. I bought myself old floppy drives for that purpose, but I happen +to not have any functional computer with a floppy port on it. I still have the +motherboard of my old pentium, but I don't seem to have a video card for it any +more. + +Because my 4p has a RS-232 port and because I have equipment to do serial +communication from modern machines (I didn't have a DB-9 to DB-25 adapter +though, I had to buy one), I chose that route. + +## Gathering parts + +* A TRS-80 model 4p with a RS-232 port +* A TRSDOS 6.x disk +* A means to do serial communication. In my case, that meant: + * A USB-to-serial device + * A null modem cable + * A DB-9 gender changer + * A DB-9 to DB-25 adapter + +## Overview + +We need to send sizeable binary programs through the RS-232 port and then run +it. The big challenge here is ensuring data integrity. Sure, serial +communication has parity check, but it has no helpful way of dealing with +parity errors. When parity check is enabled and that a parity error occurs, the +byte is simply dropped on the receiving side. Also, a double bit error could be +missed by those checks. + +What we'll do here is to ping back every received byte back and have the sender +do the comparison and report mismatched data. + +Another problem is ASCII control characters. When those are sent across serial +communication channels, all hell breaks lose. When sending binary data, those +characters have to be avoided. We use `tools/ttysafe` for that. + +Does TRSDOS have a way to receive this binary inside these constraints? Not to +my knowledge. As far as I know, the COMM program doesn't allow this. + +What are we going to do? We're going to punch in a binary program to handle that +kind of reception! You're gonna feel real badass about it too... + +## Testing serial communication + +The first step here is ensuring that you have bi-directional serial +communication. To do this, first prepare your TRS-80: + + set *cl to com + setcomm (word=8, parity=no) + +The first line loads the communication driver from the `COM/DRV` file on the +TRSDOS disk and binds it to `*cl`, the name generally used for serial +communication devices. The second line sets communication parameters in line +with what is generally the default on modern machine. Note that I left the +default of 300 bauds as-is. + +Then, you can run `COMM *cl` to start a serial communication console. + +Then, on the modern side, use your favorite serial communication program and set +the tty to 300 baud with option "raw". Make sure you have `-parenb`. + +If your line is good, then what you type on either side should echo on the +other side. If it does not, something's wrong. Debug. + +## Punching in the goodie + +As stated in the overview, we need a program on the TRS-80 that: + +1. Listens to `*cl` +2. Echoes each character back to `*cl` +3. Adjusts `ttysafe` escapes +4. Stores received bytes in memory + +That program has already been written, it's in `recv.asm` in this folder. You +can get the binary with `zasm < recv.asm | xxd`. + +How will you punch that in? The `debug` program! This very useful piece of +software is supplied in TRSDOS. To invoke it, first run `debug (on)` and then +press the `BREAK` key. You'll get the debug interface which allows you to punch +in any data in any memory address. Let's use `0x3000` which is the offset for +user apps. + +First, display the `0x3000-0x303f` range with the `d3000` command (I +always press Enter by mistake, but it's space you need to press). Then, you can +begin punching in with `h3000`. This will bring up a visual indicator of +the address being edited. Punch in the stuff with a space in between each byte +and end the edit session with `x`. + +But wait, it's not that easy! You see those `0xffff` addresses? They're +placeholders. You need to replace those values with your DCB handle for `*cl`. +See below. + +## Getting your DCB address + +In the previous step, you need to replace the `0xffff` placeholders in +`recv.asm` with your "DCB" address for `*cl`. That address is your driver +"handle". To get it, first get the address where the driver is loaded in +memory. You can get this by running `device (b=y)`. That address you see next +to `*cl`? that's it. But that's not our DCB. + +To get your DBC, go explore that memory area. Right after the part where there's +the `*cl` string, there's the DCB address (little endian). On my setup, the +driver was loaded in `0x0ff4` and the DCB address was 8 bytes after that, with +a value of `0x0238`. + +## Sending data through the RS-232 port + +Once you're finished punching your program in memory, you can run it with +`g3000` (not space). Because it's an infinite loop, your screen will +freeze. You can start sending your data. + +To that end, there's the `tools/pingpong` program. It takes a device and a +filename to send. As a test, send anything, but make it go through +`tools/ttysafe` first (which just takes input from stdin and spits tty-safe +content to stdout). + +On OpenBSD, the invocation can look like: + + doas ./pingpong /dev/ttyU0 mystuff.ttysafe + +You will be prompted for a key before the contents is sent. This is because on +OpenBSD, TTY configuration is lost as soon as the TTY is closed, which means +that you can't just run `stty` before running `pingpong`. So, what you'll do is, +before you press your key, run `doas stty -f /dev/ttyU0 300 raw` and then press +any key on the `pingpong` invocation. + +If everything goes well, the program will send your contents, verifying every +byte echoed back, and then send a null char to indicate to the receiving end +that it's finished sending. This will end the infinite loop on the TRS-80 side +and return. That should bring you back to a refreshed debug display and you +should see your sent content in memory, at the specified address (`0x3040` if +you didn't change it). + +**WIP: that's where we are for now...** diff --git a/recipes/trs80/recv.asm b/recipes/trs80/recv.asm new file mode 100644 index 0000000..ae12410 --- /dev/null +++ b/recipes/trs80/recv.asm @@ -0,0 +1,36 @@ + ld hl, 0x3040 ; memory address where to put contents. +loop: + ld a, 0x03 ; @GET + ld de, 0xffff ; replace with *CL's DCB addr + rst 0x28 + jr nz, maybeerror + or a + ret z ; Sending a straight NULL ends the comm. + ; @PUT that char back + ld c, a + ld a, 0x04 ; @PUT + ld de, 0xffff ; replace with *CL's DCB addr + rst 0x28 + jr nz, error + ld a, c + cp 0x20 + jr z, adjust +write: + ld (hl), a + inc hl + jr loop +adjust: + dec hl + ld a, (hl) + and 0x7f + jr write +maybeerror: + ; was it an error? + or a + jr z, loop ; not an error, just loop + ; error +error: + ld c, a ; Error code from @GET/@PUT + ld a, 0x1a ; @ERROR + rst 0x28 + ret diff --git a/tools/.gitignore b/tools/.gitignore index b64728e..4d9ca6d 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -2,3 +2,5 @@ /blkdump /upload /fontcompile +/ttysafe +/pingpong diff --git a/tools/Makefile b/tools/Makefile index e069d33..8b3cfd8 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -2,7 +2,10 @@ MEMDUMP_TGT = memdump BLKDUMP_TGT = blkdump UPLOAD_TGT = upload FONTCOMPILE_TGT = fontcompile -TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT) $(FONTCOMPILE_TGT) +TTYSAFE_TGT = ttysafe +PINGPONG_TGT = pingpong +TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT) $(FONTCOMPILE_TGT) \ + $(TTYSAFE_TGT) $(PINGPONG_TGT) OBJS = common.o all: $(TARGETS) diff --git a/tools/pingpong.c b/tools/pingpong.c new file mode 100644 index 0000000..b7b8f86 --- /dev/null +++ b/tools/pingpong.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include "common.h" + +/* Sends the contents of `fname` to `device`, expecting every sent byte to be + * echoed back verbatim. Compare every echoed byte with the one sent and bail + * out if a mismatch is detected. When the whole file is sent, push a null char + * to indicate EOF to the receiving end. + * + * It is recommended that you send contents that has gone through `ttysafe`. + */ +int main(int argc, char **argv) +{ + if (argc != 3) { + fprintf(stderr, "Usage: ./pingpong device fname\n"); + return 1; + } + FILE *fp = fopen(argv[2], "r"); + if (!fp) { + fprintf(stderr, "Can't open %s.\n", argv[2]); + return 1; + } + int fd = open(argv[1], O_RDWR|O_NOCTTY|O_NONBLOCK); + printf("Press a key...\n"); + getchar(); + unsigned char c; + // empty the recv buffer + while (read(fd, &c, 1) == 1) usleep(1000); + int returncode = 0; + while (fread(&c, 1, 1, fp)) { + putchar('.'); + fflush(stdout); + write(fd, &c, 1); + usleep(1000); // let it breathe + unsigned char c2; + while (read(fd, &c2, 1) != 1); // read echo + if (c != c2) { + // mismatch! + unsigned int pos = ftell(fp); + fprintf(stderr, "Mismatch at byte %d! %d != %d.\n", pos, c, c2); + returncode = 1; + break; + } + } + // To close the receiving loop on the other side, we send a straight null + c = 0; + write(fd, &c, 1); + printf("Done!\n"); + fclose(fp); + return returncode; +} + + diff --git a/tools/ttysafe.c b/tools/ttysafe.c new file mode 100644 index 0000000..e6ba4a0 --- /dev/null +++ b/tools/ttysafe.c @@ -0,0 +1,26 @@ +#include +#include + +/* Converts stdin to a content that is "tty safe", that is, that it doesn't + * contain ASCII control characters that can mess up serial communication. + * How it works is that it leaves any char > 0x20 intact, but any char <= 0x20 + * is replaced by two chars: char|0x80, 0x20. A 0x20 char always indicate "take + * the char you've just received and unset the 7th bit from it". + */ + +int main(void) +{ + int c = getchar(); + while (c != EOF) { + if (c <= 0x20) { + putchar(c|0x80); + putchar(0x20); + } else { + putchar(c&0xff); + } + c = getchar(); + } + return 0; +} + +