Compare commits

..

6 Commits

Author SHA1 Message Date
6b9646681e readme: add a few links 2025-10-24 11:09:22 -04:00
0e18958341 Update README.md 2025-10-13 15:48:26 +02:00
aeee8131e7 Update README.md 2025-10-13 14:47:53 +02:00
8cd6652b45 Update README.md 2025-10-13 14:41:34 +02:00
3e471f1390 Update README.md 2025-10-13 14:36:49 +02:00
d7e0f7b759 Polish mrbridge and add ftpserve 2025-10-13 13:57:16 +02:00
3 changed files with 210 additions and 33 deletions

120
README.md
View File

@@ -1,37 +1,123 @@
5150 stuff 5150 stuff
========== ==========
The binary programs here use multiple conventions (more Fun!): ## Layout of a bootable polOS floppy disk
- .asm files use NASM syntax and are built with `nasm xx.asm`
- .s files use GAS syntax, they are built with `ia16-elf-gcc`
The reason is uh, because you see, I did things. The floppy disk is FAT12 formatted. It must contain at least the following items:
The .asm came first and was copy pasta from the internet. * _fat12boot.bin_: the boot sector. It loads _polio.com_ from a FAT12-formatted
The .s files came from the output of `ia16-elf-gcc -S` when I couldn't be bothered to write programs in assembly but still needed to make them compatible with BASIC's *CALL* instruction. drive 0 to address _0x0600_ and jumps to it.
* _polio.com_: the basic OS functions. It serves all interrupt 0x80 functions
(see below) and loads _polmon.com_ to address _0x1200_, then jumps to it.
* _polmon.com_: a barebones memory utility. See below for all it can do.
Also the wozmon binary contains special care to make it into a boot sector (0xaa55 magic). ## polOS int 0x80
* function in AH
* optional argument in AL
* other arguments on the stack
* return value in AX
| AH | AL | stack arguments | return | description |
| --- | --- | --- | --- | --- |
| _0x01_ | | _addr_, _c_, _h_, _s_ | error code in AH | __readsector__: reads 1 floppy sector at _c/h/s_ into _0000:addr_ |
| _0x02_ | | _addr_, _c_, _h_, _s_ | error code in AH | __writesector__: writes 1 floppy sector at _c/h/s_ from _0000:addr_ |
| 0x03 | | _c_, _h_ | error code in AH | __formattrack__: formats 1 track at c/h |
| 0x04 | | _fname_, _addr_ | non-zero if error | __readfile__: reads file with name at _ds:fname_ into _ds:addr_ |
| 0x05 | | _addr_, _size_ | bytes received, < 0 if error | __recvpara__: receive at most _size_ bytes from parallel port into _ds:addr_ |
| 0x06 | | _addr_, _size_ | bytes sent, < 0 if error | __sendpara__: send at most _size_ bytes to parallel port from _ds:addr_ |
| 0x07 | _until-idle_ | | 0 if idle | __runpara__: run a round of parallel comms if _AL=0_, keep going until idle if _AL!=0_ |
| 0x08 | | _fname_, _addr_, _size_ | error code in AH | __writefile__: writes _size_ bytes from _ds:addr_ into file with name at _ds:fname_ |
## polmon commands
Commands are a single letter followed by an optional space and arguments, with optional spaces between them.
A command can be prefixed with up to 4 hex digits, which will then change the current address.
* (no command): dumps 16 bytes at the current address. If no address is given, also increments the current address by 16.
* `w bytes:[u8]*`: writes bytes in hex to the current address.
* `k src:u16 size:u16`: copy _size_ bytes of memory from _cur-seg_:_src_ to _cur-seg:cur-addr_
* `s seg:u16`: changes the current segment
* `j`: calls a function at the current address. ABI calling conventions below.
* `l file:str`: loads a program with given name into the current segment, address _0x0100_.
* `r args:[u16]*`: runs the program in the current segment with arguments.
* `i ax:u16 args:[u16]*`: call an int 0x80 function with given AX and stack args. See section above.
The _r_, _j_ and _i_ commands display the value of the AX register after returning to polmon.
### ABI convention for (j)umping to functions
* AX, BX, CX, DX are caller-saved, all others are callee-saved
* first 3 arguments passed in AX, DX and CX, others are pushed in reverse order (former argument in lower memory)
* return value in AX
### ABI for programs
See example in _hello.com_
## Examples
### Launching mushroom client
Anyway, just type
``` ```
make binaries s 200
l mushroom.com
r
``` ```
and stop whining. ### FTP a file and store it to the floppy disk
You can also type
``` ```
make floppy s 200
l ftpget.com
``` ```
then * switch to segment _0x200_
``` * load file _ftpget.com_ to _cur-seg:0100_
bochs
```
to launch the simulator. You'll need to `c` it in the console for it to start.
Here you need to run this on the host:
```
python ftpserve.py src/hello.com
```
Then on polOS:
```
r f000 200
s 0
6000
w 48454c4c4f202020434f4d
i 0800 6000 f000 200
```
* run previously loaded `ftpget.com`: store up to _0x200_ (512) bytes to address _0000:f000_
* set _cur-seg_ to _0x0000_
* set _cur-add_ to _0x6000_
* write file name in ascii: `HELLO COM` to _0000:6000_
* call int 0x80 0x08 (writefile) with args: _fname_, _addr_, _size_
We can now test that the program got transferred correctly:
```
s 300
l hello.com
r
```
### But where is my `ls` command?
You can just inspect memory at _0000:1c00_, that's the root directory. Each entry is _0x20_ (32) bytes long and starts with 11 characters that constitute the file name.
## Useful stuff ## Useful stuff
* [Colab](https://colab.research.google.com/drive/1xGKYQJLKyabcSNYOiPumf9_-bbCbXke1?usp=sharing) * [Colab](https://colab.research.google.com/drive/1xGKYQJLKyabcSNYOiPumf9_-bbCbXke1?usp=sharing)
* [Arduino pinout](https://docs.google.com/spreadsheets/d/1jgKhr-0MFtY_bFZL9xYwVsxFPt4u_5ItV8Rj7FQ7Kj4/edit) * [Arduino pinout](https://docs.google.com/spreadsheets/d/1jgKhr-0MFtY_bFZL9xYwVsxFPt4u_5ItV8Rj7FQ7Kj4/edit)
* [DOS 3.1 disk images](https://dl.winworldpc.com/Abandonware%20Operating%20Systems/PC/DOS/IBM/IBM%20PC-DOS%203.1%20%285.25%29.7z)
* [DOS 3.1 manual](https://dl.winworldpc.com/IBM%20PC-DOS%203.10%20Manuals%20Feb85.7z)
* [IBM 5150 BIOS ROM](https://www.minuszerodegrees.net/bios/BIOS_IBM5150_19OCT81_5700671_U33.BIN)
* [IBM Cassette BASIC C1.00](https://www.minuszerodegrees.net/rom/bin/IBM/IBM%205150%20-%20Cassette%20BASIC%20version%20C1.00.zip)
* [IBM 5150 technical ref](https://www.minuszerodegrees.net/manuals/IBM/IBM_5150_Technical_Reference_6025005_AUG81.pdf)
* [Ruud's diagnostic ROM](https://minuszerodegrees.net/ruuds_diagnostic_rom/bin/ruuds_diagnostic_rom_v5.4_8kb.zip)
* [Supersoft diagnostic ROM](https://www.minuszerodegrees.net/supersoft_landmark/Supersoft_PCXT_8KB.bin)

79
ftpserve.py Normal file
View File

@@ -0,0 +1,79 @@
import argparse
import logging
import os
import serial
kCrc16Polynomial = 0x1021
kCrc16Preset = 0xFFFF
kDefaultSerialPort = None
kDefaultBaudrate = 115200
kDefaultChunkSize = 0x20
logger = logging.getLogger(__name__)
def crc16(data):
crc = kCrc16Preset
for c in data:
crc ^= c << 8
for j in range(8):
mix = crc & 0x8000
crc <<= 1
if mix:
crc ^= kCrc16Polynomial
crc &= 0xFFFF
return crc
def getserial(port=None, baudrate=None, **kwargs):
if port is not None:
return serial.Serial(port, baudrate)
# return first /dev/ttyACMx found
for i in range(10):
try:
return serial.Serial(f"/dev/ttyACM{i}", baudrate)
except serial.SerialException:
pass
raise RuntimeError("No serial device available.")
def ftpserve(file, device, chunksize=0x20):
with open(file, "rb") as f:
data = f.read()
device.reset_input_buffer()
logger.info(f"Serving file {file}")
for i in range(0, len(data), chunksize):
device.write(data[i : i + chunksize])
r = device.read()
if r != b"\x42":
raise RuntimeError(f"wrong response {r}")
logger.info(f"ack for chunk @{i}")
logger.info(f"Done serving file {file}")
crc = crc16(data)
logger.info(f"File length: {len(data):04x}, crc16: {crc:04x}")
def parse_args():
parser = argparse.ArgumentParser(description="Serve a file to ftpget.")
parser.add_argument("file", help="file to serve")
parser.add_argument("--port", help="path to the serial port")
parser.add_argument("--baudrate", default=kDefaultBaudrate)
parser.add_argument("--chunksize", default=kDefaultChunkSize)
return parser.parse_args()
def main():
logging.basicConfig(level=logging.INFO)
args = parse_args()
device = getserial(**vars(args))
ftpserve(args.file, device, args.chunksize)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass

View File

@@ -4,12 +4,29 @@ import threading
import time import time
import websocket import websocket
kDefaultSerialPort = None
kDefaultBaudrate = 115200
def getserial(port=None, baudrate=None, **kwargs):
if port is not None:
return serial.Serial(port, baudrate)
# return first /dev/ttyACMx found
for i in range(10):
try:
return serial.Serial(f"/dev/ttyACM{i}", baudrate)
except serial.SerialException:
pass
raise RuntimeError("No serial device available.")
def parse_args(): def parse_args():
parser = argparse.ArgumentParser(description="Bridge a serial port to a websocket") parser = argparse.ArgumentParser(description="Bridge a serial port to a websocket")
parser.add_argument("port", help="path to the serial port") parser.add_argument(
parser.add_argument("--baudrate", default=115200) "--port", help="path to the serial port", default=kDefaultSerialPort
parser.add_argument("--escape", action='store_true') )
parser.add_argument("--baudrate", default=kDefaultBaudrate)
parser.add_argument("ws", help="URL of the websocket") parser.add_argument("ws", help="URL of the websocket")
return parser.parse_args() return parser.parse_args()
@@ -28,28 +45,23 @@ def slowwrite(device, data):
def ws_thread(device, ws): def ws_thread(device, ws):
while True: while True:
data = ws.recv(); data = ws.recv()
slowwrite(device, data.replace(b'\n', b'\r\n')) slowwrite(device, data.replace(b"\n", b"\r\n"))
def main(): def main():
args = parse_args() args = parse_args()
device = serial.Serial(args.port, baudrate=args.baudrate) device = getserial(**vars(args))
ws = websocket.create_connection(args.ws) ws = websocket.create_connection(args.ws)
wst = threading.Thread(target=ws_thread, args=(device, ws), daemon=True) wst = threading.Thread(target=ws_thread, args=(device, ws), daemon=True)
wst.start() wst.start()
if args.escape:
device.write([0x03])
try:
device_thread(device, ws) device_thread(device, ws)
except KeyboardInterrupt:
pass
finally:
if args.escape:
device.write([0x02])
if __name__ == "__main__": if __name__ == "__main__":
try:
main() main()
except KeyboardInterrupt:
pass