Compare commits

..

29 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
aaefcc864d polio is getting bigger 2025-10-06 22:52:28 +02:00
a1275671b3 polio: exit copy, add writefile 2025-10-06 22:51:59 +02:00
84ea4d5cae polmon: add kopy and split launch into load+run 2025-10-06 22:51:32 +02:00
8818606867 polmon: fix restoring ss in cbreak 2025-10-06 22:50:34 +02:00
3d90cbadf8 polmon: cleanup 2025-10-06 22:49:52 +02:00
1208f1f66e fat12: can now write files 2025-10-06 22:49:17 +02:00
91cf76c251 fix ftpget 2025-10-06 22:46:40 +02:00
540f42b21b Update ake binaries set 2025-10-06 22:43:37 +02:00
5e08ee648b polio: fix read/writesector 2025-10-06 22:42:21 +02:00
798b3e8e6b call: remove useless segment override 2025-10-06 22:41:12 +02:00
05b733b296 bootsect: minor tweak 2025-10-06 22:40:28 +02:00
ec3fe0e07d Old crc16 is now a .asm 2025-10-06 22:40:10 +02:00
0917aa1280 Ok, last changes tonight for real 2025-10-04 01:27:59 +02:00
d7df8501d6 Last fixes tonight 2025-10-04 00:27:06 +02:00
f419f72649 Small improvements 2025-10-04 00:00:41 +02:00
775295804e Add ftpget 2025-10-03 23:48:11 +02:00
d1bea93d07 hello: now with args 2025-10-03 23:47:31 +02:00
2f507a2f71 clang-format -i 2025-10-03 23:47:12 +02:00
da77e08b80 polmon: al int80, run args, ret code 2025-10-03 23:34:27 +02:00
cad4f5081a Exit wozmon 2025-10-03 23:31:43 +02:00
5bb49da246 polio: fix comms 2025-10-03 23:27:27 +02:00
a55c7c47e0 kbd: use macros 2025-10-03 23:22:21 +02:00
cbe304cabf Move stuff around 2025-10-03 23:21:54 +02:00
24 changed files with 907 additions and 526 deletions

View File

@@ -11,14 +11,13 @@ dev: dev-image ## Launch a dev container
.PHONY: binaries .PHONY: binaries
binaries: ## Build all small binaries binaries: ## Build all small binaries
docker build --build-arg TARGET=call.bin -o . --target=export .
docker build --build-arg TARGET=readfloppy.bin -o . --target=export . docker build --build-arg TARGET=readfloppy.bin -o . --target=export .
docker build --build-arg TARGET=writefloppy.bin -o . --target=export . docker build --build-arg TARGET=writefloppy.bin -o . --target=export .
docker build --build-arg TARGET=crc16.bin -o . --target=export .
docker build --build-arg TARGET=wozmon.bin -o . --target=export .
docker build --build-arg TARGET=hello.bin -o . --target=export .
docker build --build-arg TARGET=copy.bin -o . --target=export . docker build --build-arg TARGET=copy.bin -o . --target=export .
docker build --build-arg TARGET=call.bin -o . --target=export .
docker build --build-arg TARGET=format.bin -o . --target=export . docker build --build-arg TARGET=format.bin -o . --target=export .
docker build --build-arg TARGET=crc16.bin -o . --target=export .
docker build --build-arg TARGET=hello.bin -o . --target=export .
docker build --build-arg TARGET=readio.bin -o . --target=export . docker build --build-arg TARGET=readio.bin -o . --target=export .
docker build --build-arg TARGET=writeio.bin -o . --target=export . docker build --build-arg TARGET=writeio.bin -o . --target=export .

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)

View File

@@ -2,6 +2,10 @@
#include "paracomm.h" #include "paracomm.h"
#include "parallel.h" #include "parallel.h"
#ifndef NOKBD
#define NOKBD 1
#endif // NOKBD
ParaComms pc; ParaComms pc;
void setup() { void setup() {
@@ -37,7 +41,10 @@ void loop() {
} }
led_counter += 1; led_counter += 1;
// checkKbdReset(); #if NOKBD
#else // NOKBD
checkKbdReset();
#endif // NOKBD
uint8_t mosib; uint8_t mosib;
if (strobeOccurred(mosib)) { if (strobeOccurred(mosib)) {
@@ -48,9 +55,9 @@ void loop() {
if (Serial.available() > 0) { if (Serial.available() > 0) {
int c = Serial.read(); int c = Serial.read();
#if 1 #if NOKBD
pc.SendByte(c); pc.SendByte(c);
#else #else // NOKBD
if (c == 2) { if (c == 2) {
mode = 0; mode = 0;
} else if (c == 3) { } else if (c == 3) {
@@ -62,6 +69,6 @@ void loop() {
pc.SendByte(c); pc.SendByte(c);
} }
} }
#endif #endif // NOKBD
} }
} }

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

67
mrbridge.py Normal file
View File

@@ -0,0 +1,67 @@
import argparse
import serial
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", default=kDefaultSerialPort
)
parser.add_argument("--baudrate", default=kDefaultBaudrate)
parser.add_argument("ws", help="URL of the websocket")
return parser.parse_args()
def device_thread(device, ws):
while True:
line = device.readline()
ws.send(line)
def slowwrite(device, data):
for b in data:
device.write([b])
time.sleep(0.005)
def ws_thread(device, ws):
while True:
data = ws.recv()
slowwrite(device, data.replace(b"\n", b"\r\n"))
def main():
args = parse_args()
device = getserial(**vars(args))
ws = websocket.create_connection(args.ws)
wst = threading.Thread(target=ws_thread, args=(device, ws), daemon=True)
wst.start()
device_thread(device, ws)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass

View File

@@ -1,69 +1,53 @@
%.bin: %.asm
nasm $< -o $@
crc16.s: crc16.c
ia16-elf-gcc -S -Os -o crc16.s crc16.c
crc16.bin: crc16.s
ia16-elf-gcc -o crc16.bin -Os -nostdlib crc16.s
CC = ia16-elf-gcc CC = ia16-elf-gcc
CXX = ia16-elf-gcc CXX = ia16-elf-gcc
LD = ia16-elf-gcc LD = ia16-elf-gcc
OBJCOPY = ia16-elf-objcopy
CXXFLAGS = -mregparmcall -ffunction-sections -Os -flto CXXFLAGS = -mregparmcall -ffunction-sections -Os -flto
CFLAGS = -mregparmcall -ffunction-sections -Os -flto CFLAGS = -mregparmcall -ffunction-sections -Os -flto
LDFLAGS = -mregparmcall -Wl,--gc-sections -Os -nostdlib -flto LDFLAGS = -mregparmcall -Wl,--gc-sections -Os -nostdlib -flto
%.bin: %.asm
nasm $< -o $@
%.elf: %.elf:
$(LD) $(LDFLAGS) $(CPPFLAGS) -o $@ $^ $(LD) $(LDFLAGS) $(CPPFLAGS) -o $@ $^
%.com: %.elf %.com: %.elf
ia16-elf-objcopy -O binary $< $@ $(OBJCOPY) -O binary $< $@
bootsectors = fat12boot.bin wozmon.bin fat12boot.bin:
$(bootsectors): $(OBJCOPY) -O binary $< $@
ia16-elf-objcopy -O binary $< $@
fat12boot.elf: fat12boot.o fat12.o bootsect.S fat12boot.elf: fat12boot.o fat12.o bootsect.S stdlib.o
fat12boot.elf: LDFLAGS += -T bootsect.ld fat12boot.elf: LDFLAGS += -T bootsect.ld
fat12boot.bin: fat12boot.elf fat12boot.bin: fat12boot.elf
stdlib.o: CFLAGS := $(filter-out -flto, $(CFLAGS)) stdlib.o: CFLAGS := $(filter-out -flto, $(CFLAGS))
polmon.elf: LDFLAGS += -T flat1000.ld polmon.elf: LDFLAGS += -T init.ld
polmon.elf: polmon.o stdlib.o polmon.elf: polmon.o stdlib.o
polio.elf: LDFLAGS += -T flat0600.ld polio.elf: LDFLAGS += -T flat0600.ld -mprotected-mode
polio.elf: polio-main.o polio.s fat12.o paracomm.o stdlib.o polio.elf: polio-main.o polio.s fat12.o paracomm.o stdlib.o
wozmon.o: polmon.cc
wozmon.o: CPPFLAGS = -DWOZMON=1
wozmon.o:
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $<
wozmon.elf: wozmon.o bootsect.S
wozmon.elf: LDFLAGS += -T bootsect.ld
wozmon.elf: CPPFLAGS += -DNOBPB
wozmon.bin: wozmon.elf
paracli.elf: LDFLAGS += -T doscom.ld paracli.elf: LDFLAGS += -T doscom.ld
paracli.elf: paracli.s paracomm.o paracli.elf: paracli.s paracomm.o
dos-programs = mushroom.elf hello.elf dos-programs = mushroom.com hello.com ftpget.com crc16.com
$(dos-programs): LDFLAGS += -T doscom.ld $(dos-programs:.com=.elf): LDFLAGS += -T doscom.ld
mushroom.elf: mushroom.s mushroom.elf: mushroom.s
hello.elf: hello.o stdlib.o crt0.s hello.elf: hello.o stdlib.o crt0.s
ftpget.elf: ftpget.o stdlib.o crt0.s
crc16.elf: crc16.o stdlib.o crt0.s
polos.img: fat12boot.bin polmon.com polio.com mushroom.com hello.com polos.img: fat12boot.bin polmon.com polio.com $(dos-programs)
dd if=/dev/zero of=$@ bs=512 count=720 dd if=/dev/zero of=$@ bs=512 count=720
mformat -i $@ -t 40 -h 2 -s 9 mformat -i $@ -t 40 -h 2 -s 9
mcopy -i $@ polio.com ::/ mcopy -i $@ polio.com ::/
mcopy -i $@ polmon.com ::/ mcopy -i $@ polmon.com ::/
mcopy -i $@ mushroom.com ::/ mcopy -i $@ $(dos-programs) ::/
mcopy -i $@ hello.com ::/
dd if=fat12boot.bin of=$@ conv=notrunc dd if=fat12boot.bin of=$@ conv=notrunc
.PHONY: clean .PHONY: clean

View File

@@ -5,7 +5,7 @@
.global _start .global _start
_start: _start:
#ifndef NOBPB #ifndef NOBPB
jmp l0 jmp 0f
nop nop
.ascii "IBM 3.1" .ascii "IBM 3.1"
@@ -18,7 +18,7 @@ bpb_localsectors: .word 720
bpb_mediadescr: .byte 0xfd bpb_mediadescr: .byte 0xfd
bpb_sectsperfat: .word 2 bpb_sectsperfat: .word 2
l0: 0:
#endif // NOBPB #endif // NOBPB
cli cli
xor %ax, %ax xor %ax, %ax

View File

@@ -19,15 +19,15 @@ _start:
mov bx, 0x6 mov bx, 0x6
xor cx, cx xor cx, cx
a1: a1:
cmp cx, [es:si+4] cmp cx, [si+4]
jge a2 jge a2
lea ax, [es:si+bx] lea ax, [si+bx]
push ax push ax
add bl, 2 add bl, 2
inc cl inc cl
jmp a1 jmp a1
a2: a2:
call far [es:si] call far [si]
pop bp pop bp
pop di pop di
pop si pop si

45
src/crc16.asm Normal file
View File

@@ -0,0 +1,45 @@
CPU 8086
_start:
push bp
mov bp, sp
mov si, [bp+8]
push word [si]
mov si, [bp+10]
push word [si]
call crc16
mov di, [bp+6]
mov [di], ax
mov sp, bp
pop bp
retf 6
crc16:
push bp
mov bp, sp
mov bx, [bp+4]
add [bp+6], bx
mov ax, -1
.L2:
cmp bx, [bp+6]
jne .L5
pop bp
ret
.L5:
mov dl, [bx]
mov cl, 0x8
shl dx, cl
xor ax, dx
mov dl, cl
.L4:
mov cx, ax
shl cx, 1
test ax, ax
xchg cx, ax
jge .L3
xor ax, 4129
.L3:
dec dl
jne .L4
inc bx
jmp .L2

View File

@@ -1,13 +1,13 @@
#include <stdint.h> #include <stdint.h>
#define kDataAddr ((uint8_t*)0x0000) #include "polos.h"
uint16_t crc16(int data_len) { #define kDefaultSegment 0
const uint8_t* data = kDataAddr;
uint16_t crc16(uint16_t data, const uint16_t seg, int data_len) {
uint16_t crc = 0xFFFF; uint16_t crc = 0xFFFF;
for (unsigned int i = 0; i < data_len; ++i) { for (unsigned int i = 0; i < data_len; ++i) {
uint16_t dbyte = data[i]; uint16_t dbyte = __get_far_u8__(data + i, seg);
crc ^= dbyte << 8; crc ^= dbyte << 8;
for (unsigned char j = 0; j < 8; ++j) { for (unsigned char j = 0; j < 8; ++j) {
uint16_t mix = crc & 0x8000; uint16_t mix = crc & 0x8000;
@@ -19,15 +19,13 @@ uint16_t crc16(int data_len) {
return crc; return crc;
} }
asm(".global main \n" int main(int argc, int argv[]) {
"main: \n" if (argc < 2) {
" push %bp \n" return -1;
" mov %sp, %bp \n" }
" mov 8(%bp), %si \n" uint16_t seg = kDefaultSegment;
" push (%si) \n" if (argc > 2) {
" call crc16 \n" seg = argv[2];
" add $0x2, %sp \n" }
" mov 6(%bp), %di \n" return crc16(argv[0], seg, argv[1]);
" mov %ax, (%di) \n" }
" pop %bp \n"
" lret $4 \n");

View File

@@ -1,57 +0,0 @@
.arch i8086,jumps
.code16
.att_syntax prefix
#NO_APP
#APP
.section .text.init
.global _start
_start:
.section .text.main
.global main
main:
push %bp
mov %sp, %bp
mov 8(%bp), %si
push (%si)
call crc16
add $0x2, %sp
mov 6(%bp), %di
mov %ax, (%di)
pop %bp
lret $4
#NO_APP
.text
.global crc16
.type crc16, @function
crc16:
pushw %bp
movw %sp, %bp
xorw %bx, %bx
movw $-1, %ax
.L2:
cmpw 4(%bp), %bx
jne .L5
popw %bp
ret
.L5:
movb %cs:(%bx), %dl
movb $8, %cl
shlw %cl, %dx
xorw %dx, %ax
movb %cl, %dl
.L4:
movw %ax, %cx
shlw $1, %cx
testw %ax, %ax
xchgw %ax, %cx
jge .L3
xorw $4129, %ax
.L3:
decb %dl
jne .L4
incw %bx
jmp .L2
.size crc16, .-crc16
.ident "GCC: (GNU) 6.3.0"

View File

@@ -1,12 +1,18 @@
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include "polos.h"
#define kSectorsPerCluster 2 #define kSectorsPerCluster 2
#define kSectorsPerTrack 9 #define kSectorsPerTrack 9
#define kHeads 2 #define kHeads 2
#define kTracks 40
#define kFatSizeSectors 2 #define kFatSizeSectors 2
#define kRootDirSizeSectors 7 #define kRootDirSizeSectors 7
#define kBytesPerCluster 1024
#define kDataRegionStartSector (1 + kFatSizeSectors * 2 + kRootDirSizeSectors) #define kDataRegionStartSector (1 + kFatSizeSectors * 2 + kRootDirSizeSectors)
#define kNumClusters ((kSectorsPerTrack * kHeads * kTracks - kDataRegionStartSector) / kSectorsPerCluster + 2)
typedef struct { typedef struct {
char name[8]; char name[8];
@@ -22,23 +28,12 @@ typedef struct {
static uint8_t* gFat; static uint8_t* gFat;
static direntry* gRootdir; static direntry* gRootdir;
static int readsector(int c, int h, int s, uint8_t* addr) { static uint8_t rwsector(uint8_t w, int c, int h, int s, void* addr) {
register uint8_t* dest asm("bx") = addr; uint16_t ret;
register uint8_t nsects asm("al") = 1; uint16_t cmd = w ? 0x0301 : 0x0201;
register uint8_t func asm("ah") = 0x02; // retry for a total of up to 3 attempts if drive not ready
register uint8_t sect asm("cl") = s;
register uint8_t cyl asm("ch") = c;
register uint8_t head asm("dh") = h;
register uint8_t drive asm("dl") = 0;
register uint16_t seg asm("es") = 0;
register uint8_t ret asm("ah");
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
asm volatile("int $0x13" ret = __readwritesector__(cmd, addr, c, h, s) >> 8;
: "=r"(ret)
: "r"(dest), "r"(nsects), "r"(func), "r"(sect), "r"(cyl),
"r"(head), "r"(drive), "r"(seg));
if (ret != 0x80) { if (ret != 0x80) {
break; break;
} }
@@ -47,6 +42,14 @@ static int readsector(int c, int h, int s, uint8_t* addr) {
return ret; return ret;
} }
static uint8_t readsector(int c, int h, int s, void* addr) {
return rwsector(0, c, h, s, addr);
}
static uint8_t writesector(int c, int h, int s, void* addr) {
return rwsector(1, c, h, s, addr);
}
static int readcluster(int cluster) { static int readcluster(int cluster) {
int offs = cluster * 3 / 2; int offs = cluster * 3 / 2;
if (cluster % 2) { if (cluster % 2) {
@@ -57,6 +60,28 @@ static int readcluster(int cluster) {
} }
} }
static int writecluster(int cluster, int value) {
int offs = cluster * 3 / 2;
if (cluster % 2) {
gFat[offs] = (gFat[offs] & 0x0f) + ((value & 0x0f) << 4);
gFat[offs+1] = value >> 4;
// high nibble is lsb + whole byte
} else {
gFat[offs] = value & 0xff;
gFat[offs+1] = (gFat[offs+1] & 0xf0) + (value >> 8);
}
return value;
}
static int firstfreecluster() {
for (int i = 2; i < kNumClusters; i++) {
if (readcluster(i) == 0) {
return i;
}
}
return -1;
}
static void cluster2chs(int cluster, int* c, int* h, int* s) { static void cluster2chs(int cluster, int* c, int* h, int* s) {
int logicalsector = int logicalsector =
kDataRegionStartSector + (cluster - 2) * kSectorsPerCluster; kDataRegionStartSector + (cluster - 2) * kSectorsPerCluster;
@@ -65,19 +90,9 @@ static void cluster2chs(int cluster, int* c, int* h, int* s) {
*c = logicalsector / (kHeads * kSectorsPerTrack); *c = logicalsector / (kHeads * kSectorsPerTrack);
} }
static int strncmp(const char* s1, const char* s2, size_t len) {
for (int i = 0; i < len && s1[i]; i++) {
if (s1[i] != s2[i]) {
return 1;
}
}
// XXX: really we should return 0 only if *s1 == *s2 here too
return 0;
}
static int loadfile(direntry* entry, void* addr) { static int loadfile(direntry* entry, void* addr) {
int cluster = entry->cluster; int cluster = entry->cluster;
for (int i = 0; i < entry->size; i += 1024) { for (int i = 0; i < entry->size; i += kBytesPerCluster) {
int c, h, s; int c, h, s;
cluster2chs(cluster, &c, &h, &s); cluster2chs(cluster, &c, &h, &s);
if (readsector(c, h, s, addr + i)) { if (readsector(c, h, s, addr + i)) {
@@ -88,10 +103,10 @@ static int loadfile(direntry* entry, void* addr) {
s = 1; s = 1;
h++; h++;
} }
if (h > 1) { // if (h > 1) { // this will never happen
h = 0; // h = 0;
c++; // c++;
} // }
if (readsector(c, h, s, addr + i + 512)) { if (readsector(c, h, s, addr + i + 512)) {
return -5; return -5;
}; };
@@ -100,6 +115,94 @@ static int loadfile(direntry* entry, void* addr) {
return 0; return 0;
} }
static direntry* findfile(const char* name) {
direntry* file = 0;
for (int i = 0; i < 16; i++) {
direntry* entry = &gRootdir[i];
if (!strncmp(entry->name, name, 11)) {
file = entry;
break;
}
}
return file;
}
static direntry* newfile(const char* name) {
direntry* file = 0;
for (int i = 0; i < 16; i++) {
direntry* entry = &gRootdir[i];
if (entry->name[0] == 0) {
file = entry;
break;
}
}
if (!file) {
return file;
}
memcpy(file->name, name, 11);
file->size = 0;
file->cluster = 0;
return file;
}
static int allocatecluster(int previous) {
int cluster = firstfreecluster();
writecluster(cluster, 0xfff);
if (previous != 0) {
writecluster(previous, cluster);
}
return cluster;
}
static void freeclusterchain(int cluster) {
if (cluster < 2 || cluster >= 0xf00) {
return;
}
freeclusterchain(readcluster(cluster));
writecluster(cluster, 0);
}
static int savefile(direntry* file, void* addr, int size) {
freeclusterchain(file->cluster);
file->cluster = 0;
int cluster = 0;
for (int i = 0; i < size; i += kBytesPerCluster) {
cluster = allocatecluster(cluster);
if (file->cluster == 0) {
file->cluster = cluster;
}
int c, h, s;
cluster2chs(cluster, &c, &h, &s);
if (writesector(c, h, s, addr + i)) {
return -7;
}
s++;
if (s > 9) {
s = 1;
h++;
}
// if (h > 1) { // no cluster will cross a cylinder boundary
// h = 0;
// c++;
// }
if (writesector(c, h, s, addr + i + 512)) {
return -7;
};
}
file->size = size;
if (writesector(0, 0, 2, gFat)) {
return -8;
}
if (writesector(0, 0, 6, gRootdir)) {
return -9;
}
return 0;
}
int fat12_init(void* fat_addr, void* rootdir_addr) { int fat12_init(void* fat_addr, void* rootdir_addr) {
if (readsector(0, 0, 2, fat_addr)) { if (readsector(0, 0, 2, fat_addr)) {
return -2; return -2;
@@ -115,21 +218,22 @@ int fat12_init(void* fat_addr, void* rootdir_addr) {
} }
int fat12_readfile(const char* name, void* addr) { int fat12_readfile(const char* name, void* addr) {
direntry* file = 0; direntry* file = findfile(name);
for (int i = 0; i < 16; i++) {
direntry* entry = &gRootdir[i];
if (entry->name[0] == 0) {
break;
}
if (!strncmp(entry->name, name, 11)) {
file = entry;
break;
}
}
if (!file) { if (!file) {
return -4; return -4;
} }
return loadfile(file, addr); return loadfile(file, addr);
} }
int fat12_writefile(const char* name, void* addr, int size) {
direntry* file = findfile(name);
if (!file) {
file = newfile(name);
}
if (!file) {
// no moar space
return -6;
}
return savefile(file, addr, size);
}

52
src/ftpget.c Normal file
View File

@@ -0,0 +1,52 @@
#include <stdint.h>
#include <unistd.h>
#include "polos.h"
#define kDefaultChunkSize 0x20
static int recv(uint8_t* buf, uint8_t size) {
uint16_t ds = 0;
asm volatile ("push %%ds \n\t"
"mov %0, %%ds \n\t"
"pop %0 \n" : "+r"(ds));
int len = read(kLpt1, buf, size);
asm volatile ("mov %0, %%ds" :: "r"(ds));
return len;
}
int main(int argc, uint16_t argv[]) {
if (argc < 2) {
return -1;
}
uint8_t* dest = (uint8_t*)argv[0];
uint16_t size = argv[1];
uint8_t chunksize = kDefaultChunkSize;
if (argc > 2) {
chunksize = argv[2];
}
uint8_t ack = 0x42;
int i;
for (i = 0; i < size;) {
// delay?
int len;
for (int j = 0; j < 3; j++) {
int r = runcomms(kUntilIdle);
len = recv(dest + i, chunksize);
if (len > 0) {
break;
}
}
if (len <= 0) {
break;
}
write(kLpt1, &ack, 1);
i += len;
}
return i;
}

View File

@@ -1,5 +1,12 @@
#include <stdio.h> #include <stdio.h>
int main() { int main(int argc, int argv[]) {
puts("Hello, world!\r\n"); puts("Hello, world!\r\n");
if (argc > 1) {
return argv[0] + argv[1];
} else if (argc > 0) {
return argv[0];
}
return 0x42;
} }

View File

@@ -1,5 +1,5 @@
SECTIONS { SECTIONS {
. = 0x1000; . = 0x1200;
.text : { .text : {
KEEP(*(.init)) KEEP(*(.init))

View File

@@ -1,55 +0,0 @@
import argparse
import serial
import threading
import time
import websocket
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("ws", help="URL of the websocket")
return parser.parse_args()
def device_thread(device, ws):
while True:
line = device.readline()
ws.send(line)
def slowwrite(device, data):
for b in data:
device.write([b])
time.sleep(0.01)
def ws_thread(device, ws):
while True:
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)
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])
if __name__ == "__main__":
main()

View File

@@ -244,7 +244,7 @@ main:
4: 4:
call sendbuffer # send input buffer call sendbuffer # send input buffer
1: 1:
mov ah, 0x07 # do parallel comms mov ax, 0x0700 # do parallel comms
int 0x80 int 0x80
mov ax, kRecvBufSize mov ax, kRecvBufSize
push ax push ax

View File

@@ -22,6 +22,7 @@ static uint8_t miso_recvbuf[256];
static uint8_t miso_size; static uint8_t miso_size;
static uint8_t miso_received_nibbles; static uint8_t miso_received_nibbles;
static uint8_t miso_state; static uint8_t miso_state;
recv_cb miso_recv_cb;
static void swapbuffers() { static void swapbuffers() {
mosi_sendbuf = mosi_workbuf[mosi_workbuf_idx]; mosi_sendbuf = mosi_workbuf[mosi_workbuf_idx];
@@ -92,19 +93,21 @@ void paracomm_feed(uint8_t n) {
miso_received_nibbles += 1; miso_received_nibbles += 1;
if (miso_received_nibbles == 2 * miso_size) { if (miso_received_nibbles == 2 * miso_size) {
miso_recv_cb(miso_recvbuf, miso_size);
miso_state = 0; miso_state = 0;
} }
} break; } break;
} }
} }
void paracomm_init() { void paracomm_init(recv_cb miso_cb) {
mosi_size = 0; mosi_size = 0;
mosi_workbuf_idx = 0; mosi_workbuf_idx = 0;
mosi_workbuf_size = 0; mosi_workbuf_size = 0;
mosi_state = 0; mosi_state = 0;
miso_state = 0; miso_state = 0;
miso_recv_cb = miso_cb;
} }
int paracomm_send(uint8_t b) { int paracomm_send(uint8_t b) {
@@ -119,20 +122,4 @@ int paracomm_send(uint8_t b) {
return 0; return 0;
} }
uint8_t paracomm_recv(uint8_t* buf, uint8_t size) { uint8_t paracomm_busy() { return mosi_state != 0 || miso_state != 0; }
if (miso_state != 0) {
return 0;
}
if (size > miso_size) {
size = miso_size;
}
memcpy(buf, miso_recvbuf, size);
memcpy(miso_recvbuf, miso_recvbuf+size, miso_size - size);
miso_size -= size;
return size;
}
uint8_t paracomm_busy() {
return mosi_state != 0 || miso_state != 0;
}

View File

@@ -2,19 +2,16 @@
#include <stdint.h> #include <stdint.h>
typedef void (*recv_cb)(const uint8_t* buf, uint8_t size);
/** Must call first **/ /** Must call first **/
void paracomm_init(); void paracomm_init(recv_cb cb);
/** Sends a single byte. /** Sends a single byte.
* Returns: 0 if no error, non-0 otherwise. * Returns: 0 if no error, non-0 otherwise.
*/ */
int paracomm_send(uint8_t b); int paracomm_send(uint8_t b);
/** Receive some bytes.
* Returns: number of bytes copied
*/
uint8_t paracomm_recv(uint8_t* buf, uint8_t size);
/** Call after reading a nibble from the port */ /** Call after reading a nibble from the port */
void paracomm_feed(uint8_t n); void paracomm_feed(uint8_t n);

View File

@@ -7,24 +7,26 @@
#define kFatAddr ((void*)0x1a00) #define kFatAddr ((void*)0x1a00)
#define kRootDirAddr ((void*)0x1c00) #define kRootDirAddr ((void*)0x1c00)
#define kPolmonAddr ((void*)0x1000) #define kPolmonAddr ((void*)0x1200)
#define kInt80Vector ((uint16_t*)(0x80 * 4)) #define kInt80Vector ((uint16_t*)(0x80 * 4))
#define kParallelDataPort 0x3bc #define kParallelDataPort 0x3bc
#define kParallelStatusPort 0x3bd #define kParallelStatusPort 0x3bd
#define kParallelControlPort 0x3be #define kParallelControlPort 0x3be
#define kParaDelay 400 #define kParaDelay 100
#define kRecvBufferSize 256 #define kRecvBufferSize 256
void dosdbt(); void dosdbt();
void int80h(); void int80h();
uint8_t parabuf[kRecvBufferSize]; static uint8_t parabuf[kRecvBufferSize];
uint8_t parasize; static uint8_t parasize;
static void paracb(const uint8_t* buf, uint8_t size) { static void paracb(const uint8_t* buf, uint8_t size) {
// we'll mercilessly erase old data with new data if (parasize + size > sizeof(parabuf)) {
memcpy(parabuf, buf, size); size = sizeof(parabuf) - parasize;
parasize = size; }
memcpy(parabuf + parasize, buf, size);
parasize += size;
} }
static void die() { static void die() {
@@ -51,20 +53,21 @@ static void __delay__(int n) {
} }
} }
uint8_t xferpara() { uint16_t xferpara(uint8_t until_idle) {
do {
uint8_t mosib = paracomm_nextbyte(); uint8_t mosib = paracomm_nextbyte();
__outb__(kParallelDataPort, mosib); __outb__(kParallelDataPort, mosib);
__outb__(kParallelControlPort, 1); __outb__(kParallelControlPort, 1);
__delay__(kParaDelay); __delay__(kParaDelay);
__outb__(kParallelControlPort, 0); __outb__(kParallelControlPort, 0);
__delay__(kParaDelay);
uint8_t mison = __inb__(kParallelStatusPort) >> 4; uint8_t mison = __inb__(kParallelStatusPort) >> 4;
paracomm_feed(mison); paracomm_feed(mison);
} while (until_idle && paracomm_busy());
return paracomm_busy(); return paracomm_busy();
} }
uint8_t parasend(uint8_t* addr, uint8_t size) { uint16_t parasend(uint8_t* addr, uint8_t size) {
uint8_t i; uint8_t i;
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
if (paracomm_send(addr[i])) { if (paracomm_send(addr[i])) {
@@ -74,6 +77,22 @@ uint8_t parasend(uint8_t* addr, uint8_t size) {
return i; return i;
} }
uint16_t pararecv(uint8_t* buf, uint8_t size) {
if (parasize == 0) {
return 0;
}
if (size > parasize) {
size = parasize;
}
memcpy(buf, parabuf, size);
if (size < parasize) {
memcpy(parabuf, parabuf + size, parasize - size);
}
parasize -= size;
return size;
}
int main() { int main() {
dosdbt(); dosdbt();
parasize = 0; parasize = 0;

View File

@@ -6,7 +6,7 @@
.global _start .global _start
_start: _start:
cli cli
mov ax, 0x2000 # stack address mov ax, 0x2000 # stack address, shared with polmon
mov sp, ax # ss should already be 0 mov sp, ax # ss should already be 0
sti sti
jmp main jmp main
@@ -40,20 +40,6 @@ dosdbt: # assumes es=0
.section .text.int80stuff .section .text.int80stuff
# near copy [param 2] bytes, ds:[param 0] -> ds:[param 1]
copy:
mov ax, [bp-14] # ds
mov es, ax
mov ds, ax
mov cl, 4
shl si, cl
mov si, [bp+0] # source
mov di, [bp+2] # destination
mov cx, [bp+4] # length
cld
rep movsb
ret
## floppy stuff ## floppy stuff
# same as read but with a different int13h:ah value # same as read but with a different int13h:ah value
@@ -70,6 +56,7 @@ readsector:
mov ch, [bp+2] # c mov ch, [bp+2] # c
mov dh, [bp+4] # h mov dh, [bp+4] # h
mov cl, [bp+6] # s mov cl, [bp+6] # s
mov dl, 0 # drive
int 0x13 int 0x13
ret ret
@@ -121,8 +108,19 @@ readfile:
add ax, si add ax, si
mov dx, [bp+2] mov dx, [bp+2]
add dx, si add dx, si
call fat12_readfile jmp fat12_readfile
ret
# params: ds:fname, ds:src, size
writefile:
mov si, [bp-14] # ds
mov cl, 4
shl si, cl
mov ax, [bp+0]
add ax, si
mov dx, [bp+2]
add dx, si
mov cx, [bp+4]
jmp fat12_writefile
# params: ds:addr, size # params: ds:addr, size
sendpara: sendpara:
@@ -132,8 +130,7 @@ sendpara:
mov ax, [bp+0] mov ax, [bp+0]
add ax, si add ax, si
mov dx, [bp+2] mov dx, [bp+2]
call parasend jmp parasend
ret
# params: ds:addr, size # params: ds:addr, size
recvpara: recvpara:
@@ -143,7 +140,10 @@ recvpara:
mov ax, [bp+0] mov ax, [bp+0]
add ax, si add ax, si
mov dx, [bp+2] mov dx, [bp+2]
call paracomm_recv jmp pararecv
dummy:
mov ax, -1
ret ret
.section .text.int80h .section .text.int80h
@@ -154,17 +154,16 @@ int80h:
push bp push bp
push ds push ds
push es push es
mov bp, sp
xor cx, cx xor cx, cx
mov es, cx mov es, cx
mov ds, cx mov ds, cx
shl ah xchg ah, cl
cmp ah, offset int80h_entries shl cl
cmp cl, offset int80h_entries
jge 0f jge 0f
mov al, ah mov si, cx
xor ah, ah add bp, 16 # 10 for us, 6 for the interrupt
mov si, ax
mov bp, sp
lea bp, [bp + 16] # 10 for us, 6 for the interrupt
call cs:[int80h_table+si] call cs:[int80h_table+si]
jmp 1f jmp 1f
0: 0:
@@ -179,7 +178,7 @@ int80h:
.section .rodata.int80h .section .rodata.int80h
int80h_table: int80h_table:
.word offset copy # 0x00 .word offset dummy # 0x00
.word offset readsector # 0x01 .word offset readsector # 0x01
.word offset writesector # 0x02 .word offset writesector # 0x02
.word offset formattrack # 0x03 .word offset formattrack # 0x03
@@ -187,5 +186,6 @@ int80h_table:
.word offset recvpara # 0x05 .word offset recvpara # 0x05
.word offset sendpara # 0x06 .word offset sendpara # 0x06
.word offset xferpara # 0x07 .word offset xferpara # 0x07
.word offset writefile # 0x08
int80h_entries = . - int80h_table int80h_entries = . - int80h_table

View File

@@ -2,94 +2,26 @@
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#ifndef WOZMON #include "polos.h"
#define WOZMON 0
#endif
#if WOZMON
#define BACKSPACE 0
#define ASCIIDUMP 0
#define CLEARSCREENCMD 0
#define INT80H 0
#define SHOWTITLE 0
#define BOOTSTRAP 0
#define LAUNCH 0
#define CTRLBREAK 0
#endif // WOZMON
#ifndef BACKSPACE
#define BACKSPACE 1
#endif
#ifndef ASCIIDUMP
#define ASCIIDUMP 1
#endif
#ifndef CLEARSCREENCMD
#define CLEARSCREENCMD 1
#endif
#ifndef INT80H
#define INT80H 1
#endif
#ifndef SHOWTITLE
#define SHOWTITLE 1
#endif
#ifndef BOOTSTRAP
#define BOOTSTRAP 1
#endif
#ifndef LAUNCH
#define LAUNCH 1
#endif
#ifndef CTRLBREAK
#define CTRLBREAK 1
#endif
namespace { namespace {
#if WOZMON
int getchar() {
register char c asm("al");
asm volatile("movb $0x00, %%ah\n\t"
"int $0x16"
: "=r"(c)::"ah", "cc");
return c;
}
void putchar(int c) {
asm volatile("push %%bp \n\t"
"mov %0, %%ax \n\t"
"movb $0x0e, %%ah \n\t"
"movb $0, %%bh \n\t"
"int $0x10 \n\t"
"pop %%bp \n\t" ::"r"(c)
: "ax", "bh", "cc");
}
#endif // WOZMON
constexpr int kDumpSize = 16; constexpr int kDumpSize = 16;
// arguments on the stack in reverse order extern "C" uint16_t Int80h(uint16_t fun, uint16_t nargs, uint16_t args[]);
extern "C" uint16_t Int80h(uint8_t fun, int nargs);
asm(".section .text.Int80h \n" asm(".section .text.Int80h \n"
"Int80h: \n" "Int80h: \n"
" push %si \n" " push %si \n"
" push %bp \n" " push %bp \n"
" mov %al, %ah \n"
" mov %sp, %bp \n" " mov %sp, %bp \n"
" lea 6(%bp), %si \n" // 4 for pushed stuff, 2 for return addr " mov %cx, %si \n"
" shl $1, %dx \n"
" add %dx, %si \n"
"0: \n" "0: \n"
" test %dx, %dx \n" " cmp %cx, %si \n"
" jz 1f \n" " je 1f \n"
" push (%si) \n" " push -2(%si) \n"
" dec %dx \n" " sub $2, %si \n"
" add $2, %si \n"
" jmp 0b \n" " jmp 0b \n"
"1: \n" "1: \n"
" int $0x80 \n" " int $0x80 \n"
@@ -111,21 +43,22 @@ uint8_t ReadHexNibble(uint8_t c) {
return 10 + (c - 'a'); return 10 + (c - 'a');
} }
uint16_t ReadUintN(int n, const char* buf) { uint16_t ReadUintN(int n, const char*& buf) {
uint16_t out = 0; uint16_t out = 0;
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
if (buf[i] == 0) { if (*buf < '0') {
break; break;
} }
out <<= 4; out <<= 4;
out += ReadHexNibble(buf[i]); out += ReadHexNibble(*buf);
buf += 1;
} }
return out; return out;
} }
uint8_t ReadUint8(const char* buf) { return ReadUintN(2, buf); } uint8_t ReadUint8(const char*& buf) { return ReadUintN(2, buf); }
uint16_t ReadUint16(const char* buf) { return ReadUintN(4, buf); } uint16_t ReadUint16(const char*& buf) { return ReadUintN(4, buf); }
void WriteHexNibble(uint8_t c) { void WriteHexNibble(uint8_t c) {
if (c > 9) { if (c > 9) {
@@ -145,19 +78,9 @@ void WriteUint16(uint16_t a) {
WriteUint8(a & 0xff); WriteUint8(a & 0xff);
} }
uint8_t __get_far_u8__(uint16_t addr, uint16_t seg) { void WriteUint16ln(uint16_t a) {
register uint16_t ad asm("si") = addr; WriteUint16(a);
register uint16_t sg asm("ds") = seg; puts("\r\n");
register uint8_t ret asm("al");
asm("lodsb \n\t" : "=r"(ret) : "r"(ad), "r"(sg));
return ret;
}
void __set_far_u8__(uint16_t addr, uint16_t seg, uint8_t val) {
register uint16_t ad asm("di") = addr;
register uint16_t sg asm("es") = seg;
register uint8_t v asm("al") = val;
asm("stosb \n\t" ::"r"(sg), "r"(ad), "r"(v) : "memory");
} }
__attribute__((noreturn)) inline static void __basic__() { __attribute__((noreturn)) inline static void __basic__() {
@@ -173,7 +96,6 @@ void Dump(uint16_t addr, uint16_t seg, int count) {
uint8_t d = __get_far_u8__(addr + i, seg); uint8_t d = __get_far_u8__(addr + i, seg);
WriteUint8(d); WriteUint8(d);
} }
#if ASCIIDUMP
putchar(' '); putchar(' ');
putchar(' '); putchar(' ');
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
@@ -184,7 +106,6 @@ void Dump(uint16_t addr, uint16_t seg, int count) {
putchar('.'); putchar('.');
} }
} }
#endif
} }
void DumpHex(uint16_t addr, uint16_t seg) { void DumpHex(uint16_t addr, uint16_t seg) {
@@ -197,8 +118,7 @@ void DumpHex(uint16_t addr, uint16_t seg) {
putchar(']'); putchar(']');
putchar(':'); putchar(':');
Dump(addr, seg, kDumpSize); Dump(addr, seg, kDumpSize);
putchar('\r'); puts("\r\n");
putchar('\n');
} }
void ClearScreen() { void ClearScreen() {
@@ -207,23 +127,6 @@ void ClearScreen() {
: "ax", "cc"); : "ax", "cc");
} }
void Status(uint8_t status) {
asm volatile("xorb %%bh, %%bh \n\t"
"movb $0x03, %%ah \n\t"
"int $0x10 \n\t"
"mov %%dx, %%di \n\t"
"movb $0x02, %%ah \n\t"
"movw $77, %%dx \n\t"
"int $0x10 \n\t"
"movb %0, %%al \n\t"
"call %1 \n\t"
"movb $0x02, %%ah \n\t"
"movw %%di, %%dx \n\t"
"int $0x10" ::"rm"(status),
"l"(WriteUint8)
: "ax", "bh", "dx", "cx", "di", "cc");
}
char toupper(char c) { char toupper(char c) {
if (c >= 'a' && c <= 'z') { if (c >= 'a' && c <= 'z') {
return c - ('a' - 'A'); return c - ('a' - 'A');
@@ -231,62 +134,103 @@ char toupper(char c) {
return c; return c;
} }
void tofatname(const char* name, char* fatname) { void tofatname(const char* name, uint8_t len, char* fatname) {
int j = 0;
for (int i = 0; i < 11; i++) { for (int i = 0; i < 11; i++) {
if (*name == 0 || *name == '.') { if (j == len || name[j] == '.') {
fatname[i] = ' '; fatname[i] = ' ';
} else { } else {
fatname[i] = toupper(*name++); fatname[i] = toupper(name[j]);
j++;
} }
if (i == 7 && *name == '.') { if (i == 7 && name[j] == '.') {
name++; j++;
} }
} }
} }
void LaunchFile(const char* name) { uint16_t __readfile__(const char* name, uint16_t addr) {
constexpr uint16_t kSegment = 0x200; register uint16_t ret asm("ax");
constexpr uint16_t kAddress = 0x100; asm volatile("push %2 \n\t"
constexpr uint16_t kHeaderAddress = (kSegment << 4); "push %1 \n\t"
constexpr uint16_t kFlatAddress = (kSegment << 4) + kAddress;
constexpr uint8_t kTrampoline[] = { 0xe8, 0xfd, 0x00, 0xcb };
char fatname[11];
tofatname(name, fatname);
auto* ptr = reinterpret_cast<uint8_t*>(kHeaderAddress);
memcpy(ptr, kTrampoline, sizeof(kTrampoline));
asm volatile("mov %1, %%ax \n\t"
"push %%ax \n\t"
"mov %0, %%ax \n\t"
"push %%ax \n\t"
"mov $04, %%ah \n\t" "mov $04, %%ah \n\t"
"int $0x80 \n\t" "int $0x80 \n\t"
"add $4, %%sp \n\t" "pop %%cx \n\t"
"mov %2, %%ax \n\t" "pop %%cx \n\t"
"mov %%ax, %%ds \n\t" : "=r"(ret)
"mov %%ax, %%es \n\t" : "r"(name), "r"(addr)
"cli \n\t" : "bx", "cx", "dx", "memory", "cc");
"mov %%ax, %%ss \n\t"
"mov %3, %%ax \n\t" return ret;
"xchg %%ax, %%sp \n\t"
"sti \n\t"
"push %%ax \n\t"
"lcall %2,$0 \n\t"
"xor %%ax, %%ax \n\t"
"mov %%ax, %%ds \n\t"
"mov %%ax, %%es \n\t"
"cli \n\t"
"pop %%sp \n\t"
"mov %%ax, %%ss \n\t"
"sti"
:: "rmi"(fatname),
"rmi"(kFlatAddress), "i"(kSegment), "i"(kHeaderAddress)
: "ax", "bx", "cx", "dx", "memory");
} }
#if CTRLBREAK uint16_t LoadFile(const char* name, uint8_t len, uint16_t seg) {
constexpr uint16_t kAddress = 0x100;
// trampoline:
// call 0x100
// retf
// XXX: if you change kAddress, the trampoline needs to be updated
constexpr uint8_t kTrampoline[] = {0xe8, 0xfd, 0x00, 0xcb};
uint16_t headeraddr = (seg << 4);
uint16_t flataddr = headeraddr + kAddress;
char fatname[11];
tofatname(name, len, fatname);
uint16_t r = __readfile__(fatname, flataddr);
if (r) {
memcpy(fatname + 8, "COM", 3);
r = __readfile__(fatname, flataddr);
}
if (r) {
return r;
}
auto* ptr = reinterpret_cast<uint8_t*>(headeraddr);
memcpy(ptr, kTrampoline, sizeof(kTrampoline));
return 0;
}
uint16_t Exec(uint16_t seg, uint8_t argc, uint16_t* argv) {
constexpr uint16_t kAddress = 0x100;
constexpr uint16_t kArgvAddress = 0x0004;
uint16_t headeraddr = (seg << 4);
auto* argp = reinterpret_cast<uint16_t*>(headeraddr + kArgvAddress);
memcpy(argp, argv, argc * 2);
register uint16_t ac asm("ax") = argc;
register uint16_t av asm("dx") = kArgvAddress;
asm volatile("mov %2, %%cx \n\t"
"mov %%cx, %%ds \n\t"
"mov %%cx, %%es \n\t"
"cli \n\t"
"mov %%cx, %%ss \n\t"
"mov %3, %%cx \n\t"
"xchg %%cx, %%sp \n\t"
"sti \n\t"
"push %%cx \n\t"
"push %2 \n\t"
"xor %%cx, %%cx \n\t"
"push %%cx \n\t"
"mov %%sp, %%bx \n\t"
"lcall *(%%bx) \n\t"
"pop %%cx \n\t"
"pop %%dx \n\t"
"mov %%cx, %%ds \n\t"
"mov %%cx, %%es \n\t"
"cli \n\t"
"pop %%sp \n\t"
"mov %%cx, %%ss \n\t"
"sti"
: "+r"(ac), "+r"(av)
: "r"(seg), "i"(kAddress)
: "bx", "cx", "memory", "cc");
return ac;
}
constexpr uint16_t kCtrlBreakVector = (0x1b * 4); constexpr uint16_t kCtrlBreakVector = (0x1b * 4);
@@ -294,9 +238,9 @@ extern "C" void cbreak();
asm(" .section .text.cbreak \n" asm(" .section .text.cbreak \n"
"cbreak: \n" "cbreak: \n"
" xor %ax, %ax \n" " xor %ax, %ax \n"
" mov %ds, %ax \n" " mov %ax, %ds \n"
" cli \n" " cli \n"
" mov %ss, %ax \n" " mov %ax, %ss \n"
" mov $0x2000, %sp \n" " mov $0x2000, %sp \n"
" sti \n" " sti \n"
" pushf \n" " pushf \n"
@@ -313,15 +257,43 @@ void InstallCtrlBreak() {
vec[1] = 0; vec[1] = 0;
} }
#endif // CTRLBREAK // j
// w [u8]*
// i [u16] [u16]*
// r str [u16]*
// k u16 u16 u16
int ReadArgs(const char*& ptr, uint16_t* args, int size) {
int nargs = 0;
while (*ptr && nargs < size) {
if (*ptr == ' ') {
ptr += 1;
continue;
}
args[nargs] = ReadUint16(ptr);
nargs += 1;
}
return nargs;
}
int ReadString(const char*& ptr, const char*& str) {
while (*ptr == ' ') {
ptr++;
}
str = ptr;
int l;
for (l = 0; *ptr && *ptr != ' '; l++, ptr++) {
}
return l;
}
bool ParseCommand(const char* buf, uint16_t& cur_addr, uint16_t& cur_seg) { bool ParseCommand(const char* buf, uint16_t& cur_addr, uint16_t& cur_seg) {
bool dump = true; bool dump = true;
for (const char* ptr = buf; *ptr;) { for (const char* ptr = buf; *ptr;) {
if (*ptr == 's') { if (*ptr == 's') {
ptr += 1;
cur_addr = 0; cur_addr = 0;
cur_seg = ReadUint16(ptr + 1); ReadArgs(ptr, &cur_seg, 1);
ptr += 5;
} else if (*ptr == '$') { } else if (*ptr == '$') {
__basic__(); __basic__();
} else if (*ptr == 'j') { } else if (*ptr == 'j') {
@@ -329,52 +301,31 @@ bool ParseCommand(const char* buf, uint16_t& cur_addr, uint16_t& cur_seg) {
ptr++; ptr++;
auto jump = reinterpret_cast<uint16_t (*)()>(cur_addr); auto jump = reinterpret_cast<uint16_t (*)()>(cur_addr);
uint16_t ret = jump(); uint16_t ret = jump();
WriteUint16(ret); WriteUint16ln(ret);
putchar('\r');
putchar('\n');
#if INT80H
} else if (*ptr == 'i') { } else if (*ptr == 'i') {
dump = false; dump = false;
int nargs = 0;
ptr++; ptr++;
int fun = ReadUint8(ptr); uint16_t args[8]; // ought to be enough for everybody
ptr += 2; int nargs = ReadArgs(ptr, args, 8);
for (; *ptr;) { uint16_t fun = args[0];
if (*ptr == ' ') { uint16_t ret = Int80h(fun, nargs - 1, args + 1);
ptr++; WriteUint16ln(ret);
continue;
}
uint16_t d = ReadUint16(ptr);
asm volatile("push %0" ::"r"(d));
nargs++;
ptr += 4;
}
uint16_t ret = Int80h(fun, nargs);
asm volatile("shl %0 \n\t"
"add %0, %%sp" ::"r"(nargs));
WriteUint16(ret);
putchar('\r');
putchar('\n');
#endif // INT80H
#if CLEARSCREENCMD
} else if (*ptr == 'l') { } else if (*ptr == 'l') {
dump = false; dump = false;
ClearScreen();
ptr++; ptr++;
#endif // CLEARSCREENCMD const char* fname;
#if LAUNCH int l = ReadString(ptr, fname);
uint16_t ret = LoadFile(fname, l, cur_seg);
if (ret) {
WriteUint16ln(ret);
}
} else if (*ptr == 'r') { } else if (*ptr == 'r') {
dump = false; dump = false;
ptr++; ptr++;
while (*ptr == ' ') { uint16_t args[8]; // ought to be enough for everybody
ptr++; int nargs = ReadArgs(ptr, args, 8);
} uint16_t ret = Exec(cur_seg, nargs, args);
int l = 0; WriteUint16ln(ret);
for (; ptr[l]; l++) {
}
LaunchFile(ptr);
ptr += l;
#endif // LAUNCH
} else if (*ptr == 'w') { } else if (*ptr == 'w') {
dump = false; dump = false;
uint16_t addr = cur_addr; uint16_t addr = cur_addr;
@@ -387,21 +338,28 @@ bool ParseCommand(const char* buf, uint16_t& cur_addr, uint16_t& cur_seg) {
uint8_t d = ReadUint8(ptr); uint8_t d = ReadUint8(ptr);
__set_far_u8__(addr, cur_seg, d); __set_far_u8__(addr, cur_seg, d);
addr++; addr++;
ptr += 2; }
} else if (*ptr == 'k') {
dump = false;
ptr++;
uint16_t args[2];
// TODO: implement copy on non-zero segments
if (cur_seg == 0 && ReadArgs(ptr, args, 2) == 2) {
auto* src = reinterpret_cast<uint8_t*>(args[0]);
uint16_t size = args[1];
auto* dest = reinterpret_cast<uint8_t*>(cur_addr);
memcpy(dest, src, size);
} }
} else { } else {
cur_addr = ReadUint16(ptr); cur_addr = ReadUint16(ptr);
ptr += 4;
} }
} }
return dump; return dump;
} }
void DisplayPrompt() { void DisplayPrompt() { puts("> "); }
putchar('>');
putchar(' ');
}
[[noreturn]]
void polmon() { void polmon() {
uint16_t cur_addr = 0; uint16_t cur_addr = 0;
uint16_t cur_seg = 0; uint16_t cur_seg = 0;
@@ -410,9 +368,7 @@ void polmon() {
char* inptr = inbuf; char* inptr = inbuf;
ClearScreen(); ClearScreen();
#if SHOWTITLE
puts("PolMon for Workgroups 3.1\r\n"); puts("PolMon for Workgroups 3.1\r\n");
#endif // SHOWTITLE
DisplayPrompt(); DisplayPrompt();
while (1) { while (1) {
@@ -434,7 +390,6 @@ void polmon() {
first = false; first = false;
inptr = inbuf; inptr = inbuf;
DisplayPrompt(); DisplayPrompt();
#if BACKSPACE
} else if (c == kBackspace || c == kOtherBackspace) { } else if (c == kBackspace || c == kOtherBackspace) {
inptr--; inptr--;
if (inptr < inbuf) { if (inptr < inbuf) {
@@ -444,26 +399,19 @@ void polmon() {
} }
putchar(' '); putchar(' ');
putchar(kOtherBackspace); putchar(kOtherBackspace);
#endif
} else { } else {
*inptr = c; *inptr = c;
inptr++; inptr++;
} }
} }
__builtin_unreachable();
} }
} // namespace } // namespace
int main() { int main() {
#if CTRLBREAK
InstallCtrlBreak(); InstallCtrlBreak();
#endif // CTRLBREAK
polmon(); polmon();
} }
#if BOOTSTRAP __attribute__((section(".init"), used)) void _start() { main(); }
__attribute__((section(".init"), noreturn, used)) void _start() {
main();
__builtin_unreachable();
}
#endif // BOOTSTRAP

59
src/polos.h Normal file
View File

@@ -0,0 +1,59 @@
#pragma once
#include <stdint.h>
#define kLpt1 0x10
#define kUntilIdle 1
#define kOnce 0
int runcomms(char until_idle);
int putchar(int);
int puts(const char*);
inline static uint8_t __get_far_u8__(uint16_t addr, uint16_t seg) {
register uint16_t ad asm("di") = addr;
register uint8_t ret asm("al");
asm("mov %2, %%es \t\n"
"movb %%es:(%%di), %%al \t\n"
: "=r"(ret)
: "r"(ad), "r"(seg)
: "es");
return ret;
}
inline static void __set_far_u8__(uint16_t addr, uint16_t seg, uint8_t val) {
register uint16_t ad asm("di") = addr;
register uint8_t v asm("al") = val;
asm("mov %0, %%es \t\n"
"stosb \n\t" ::"r"(seg), "r"(ad), "r"(v) : "es", "memory");
}
inline static uint16_t __readwritesector__(uint16_t cmd, uint8_t* addr, uint8_t c, uint8_t h, uint8_t s) {
register uint8_t* dest asm("bx") = addr;
register uint8_t sect asm("cl") = s;
register uint8_t cyl asm("ch") = c;
register uint8_t head asm("dh") = h;
register uint8_t drive asm("dl") = 0;
register uint16_t ret asm("ax");
asm volatile("xor %%ax, %%ax \n\t"
"mov %%ax, %%es \n\t"
"mov %1, %%ax \n\t"
"int $0x13"
: "=r"(ret)
: "ir"(cmd), "r"(dest), "r"(sect), "r"(cyl),
"r"(head), "r"(drive)
);
return ret;
}
inline static uint16_t __readsector__(uint8_t* addr, uint8_t c, uint8_t h, uint8_t s) {
return __readwritesector__(0x0201, addr, c, h, s);
}
inline static uint16_t __writesector__(uint8_t* addr, uint8_t c, uint8_t h, uint8_t s) {
return __readwritesector__(0x0301, addr, c, h, s);
}

View File

@@ -1,6 +1,8 @@
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include "polos.h"
int getchar() { int getchar() {
register char c asm("al"); register char c asm("al");
asm volatile("movb $0x00, %%ah\n\t" asm volatile("movb $0x00, %%ah\n\t"
@@ -43,3 +45,56 @@ void* memset(void* ptr, int val, size_t len) {
} }
return ptr; return ptr;
} }
int strncmp(const char* s1, const char* s2, size_t len) {
for (int i = 0; i < len && s1[i]; i++) {
if (s1[i] != s2[i]) {
return 1;
}
}
// XXX: really we should return 0 only if *s1 == *s2 here too
return 0;
}
static int int80h_2(uint8_t fun, uint16_t arg1, uint16_t arg2) {
register uint16_t a1 asm("dx") = arg1;
register uint16_t a2 asm("cx") = arg2;
register uint8_t ah asm("ah") = fun;
register uint16_t ret asm("ax");
asm volatile("push %3 \n\t"
"push %2 \n\t"
"int $0x80 \n\t"
"pop %%cx \n\t"
"pop %%cx \n\t"
: "=r"(ret)
: "r"(ah), "r"(a1), "r"(a2)
: "bx", "cc", "memory");
return ret;
}
int runcomms(char until_idle) {
register int ret asm("ax");
register char ui asm("al") = until_idle;
asm volatile("mov $0x07, %%ah \n\t"
"int $0x80 \n\t"
: "=r"(ret)
: "r"(ui)
: "bx", "cx", "dx", "cc", "memory");
return ret;
}
int read(int fd, void* buf, size_t size) {
if (fd != kLpt1) {
return -1;
}
return int80h_2(0x05, (uint16_t)buf, size);
}
int write(int fd, void* buf, size_t size) {
if (fd != kLpt1) {
return -1;
}
return int80h_2(0x06, (uint16_t)buf, size);
}