Compare commits
6 Commits
aaefcc864d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b9646681e | |||
| 0e18958341 | |||
| aeee8131e7 | |||
| 8cd6652b45 | |||
| 3e471f1390 | |||
| d7e0f7b759 |
120
README.md
120
README.md
@@ -1,37 +1,123 @@
|
||||
5150 stuff
|
||||
==========
|
||||
|
||||
The binary programs here use multiple conventions (more Fun!):
|
||||
- .asm files use NASM syntax and are built with `nasm xx.asm`
|
||||
- .s files use GAS syntax, they are built with `ia16-elf-gcc`
|
||||
## Layout of a bootable polOS floppy disk
|
||||
|
||||
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.
|
||||
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.
|
||||
* _fat12boot.bin_: the boot sector. It loads _polio.com_ from a FAT12-formatted
|
||||
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
|
||||
```
|
||||
bochs
|
||||
```
|
||||
to launch the simulator. You'll need to `c` it in the console for it to start.
|
||||
* switch to segment _0x200_
|
||||
* load file _ftpget.com_ to _cur-seg:0100_
|
||||
|
||||
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
|
||||
|
||||
* [Colab](https://colab.research.google.com/drive/1xGKYQJLKyabcSNYOiPumf9_-bbCbXke1?usp=sharing)
|
||||
* [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
79
ftpserve.py
Normal 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
|
||||
44
mrbridge.py
44
mrbridge.py
@@ -4,12 +4,29 @@ import threading
|
||||
import time
|
||||
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():
|
||||
parser = argparse.ArgumentParser(description="Bridge a serial port to a websocket")
|
||||
parser.add_argument("port", help="path to the serial port")
|
||||
parser.add_argument("--baudrate", default=115200)
|
||||
parser.add_argument("--escape", action='store_true')
|
||||
parser.add_argument(
|
||||
"--port", help="path to the serial port", default=kDefaultSerialPort
|
||||
)
|
||||
parser.add_argument("--baudrate", default=kDefaultBaudrate)
|
||||
parser.add_argument("ws", help="URL of the websocket")
|
||||
return parser.parse_args()
|
||||
|
||||
@@ -28,28 +45,23 @@ def slowwrite(device, data):
|
||||
|
||||
def ws_thread(device, ws):
|
||||
while True:
|
||||
data = ws.recv();
|
||||
slowwrite(device, data.replace(b'\n', b'\r\n'))
|
||||
data = ws.recv()
|
||||
slowwrite(device, data.replace(b"\n", b"\r\n"))
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
device = serial.Serial(args.port, baudrate=args.baudrate)
|
||||
device = getserial(**vars(args))
|
||||
ws = websocket.create_connection(args.ws)
|
||||
|
||||
wst = threading.Thread(target=ws_thread, args=(device, ws), daemon=True)
|
||||
wst.start()
|
||||
|
||||
if args.escape:
|
||||
device.write([0x03])
|
||||
try:
|
||||
device_thread(device, ws)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
if args.escape:
|
||||
device.write([0x02])
|
||||
device_thread(device, ws)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user