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
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=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=call.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=writeio.bin -o . --target=export .

120
README.md
View File

@@ -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)

View File

@@ -2,6 +2,10 @@
#include "paracomm.h"
#include "parallel.h"
#ifndef NOKBD
#define NOKBD 1
#endif // NOKBD
ParaComms pc;
void setup() {
@@ -37,7 +41,10 @@ void loop() {
}
led_counter += 1;
// checkKbdReset();
#if NOKBD
#else // NOKBD
checkKbdReset();
#endif // NOKBD
uint8_t mosib;
if (strobeOccurred(mosib)) {
@@ -48,9 +55,9 @@ void loop() {
if (Serial.available() > 0) {
int c = Serial.read();
#if 1
#if NOKBD
pc.SendByte(c);
#else
#else // NOKBD
if (c == 2) {
mode = 0;
} else if (c == 3) {
@@ -62,6 +69,6 @@ void loop() {
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
CXX = ia16-elf-gcc
LD = ia16-elf-gcc
OBJCOPY = ia16-elf-objcopy
CXXFLAGS = -mregparmcall -ffunction-sections -Os -flto
CFLAGS = -mregparmcall -ffunction-sections -Os -flto
LDFLAGS = -mregparmcall -Wl,--gc-sections -Os -nostdlib -flto
%.bin: %.asm
nasm $< -o $@
%.elf:
$(LD) $(LDFLAGS) $(CPPFLAGS) -o $@ $^
%.com: %.elf
ia16-elf-objcopy -O binary $< $@
$(OBJCOPY) -O binary $< $@
bootsectors = fat12boot.bin wozmon.bin
$(bootsectors):
ia16-elf-objcopy -O binary $< $@
fat12boot.bin:
$(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.bin: fat12boot.elf
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
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
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: paracli.s paracomm.o
dos-programs = mushroom.elf hello.elf
$(dos-programs): LDFLAGS += -T doscom.ld
dos-programs = mushroom.com hello.com ftpget.com crc16.com
$(dos-programs:.com=.elf): LDFLAGS += -T doscom.ld
mushroom.elf: mushroom.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
mformat -i $@ -t 40 -h 2 -s 9
mcopy -i $@ polio.com ::/
mcopy -i $@ polmon.com ::/
mcopy -i $@ mushroom.com ::/
mcopy -i $@ hello.com ::/
mcopy -i $@ $(dos-programs) ::/
dd if=fat12boot.bin of=$@ conv=notrunc
.PHONY: clean

View File

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

View File

@@ -19,15 +19,15 @@ _start:
mov bx, 0x6
xor cx, cx
a1:
cmp cx, [es:si+4]
cmp cx, [si+4]
jge a2
lea ax, [es:si+bx]
lea ax, [si+bx]
push ax
add bl, 2
inc cl
jmp a1
a2:
call far [es:si]
call far [si]
pop bp
pop di
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>
#define kDataAddr ((uint8_t*)0x0000)
#include "polos.h"
uint16_t crc16(int data_len) {
const uint8_t* data = kDataAddr;
#define kDefaultSegment 0
uint16_t crc16(uint16_t data, const uint16_t seg, int data_len) {
uint16_t crc = 0xFFFF;
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;
for (unsigned char j = 0; j < 8; ++j) {
uint16_t mix = crc & 0x8000;
@@ -19,15 +19,13 @@ uint16_t crc16(int data_len) {
return crc;
}
asm(".global main \n"
"main: \n"
" push %bp \n"
" mov %sp, %bp \n"
" mov 8(%bp), %si \n"
" push (%si) \n"
" call crc16 \n"
" add $0x2, %sp \n"
" mov 6(%bp), %di \n"
" mov %ax, (%di) \n"
" pop %bp \n"
" lret $4 \n");
int main(int argc, int argv[]) {
if (argc < 2) {
return -1;
}
uint16_t seg = kDefaultSegment;
if (argc > 2) {
seg = argv[2];
}
return crc16(argv[0], seg, argv[1]);
}

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 <stdlib.h>
#include <string.h>
#include "polos.h"
#define kSectorsPerCluster 2
#define kSectorsPerTrack 9
#define kHeads 2
#define kTracks 40
#define kFatSizeSectors 2
#define kRootDirSizeSectors 7
#define kBytesPerCluster 1024
#define kDataRegionStartSector (1 + kFatSizeSectors * 2 + kRootDirSizeSectors)
#define kNumClusters ((kSectorsPerTrack * kHeads * kTracks - kDataRegionStartSector) / kSectorsPerCluster + 2)
typedef struct {
char name[8];
@@ -22,23 +28,12 @@ typedef struct {
static uint8_t* gFat;
static direntry* gRootdir;
static int readsector(int c, int h, int s, uint8_t* addr) {
register uint8_t* dest asm("bx") = addr;
register uint8_t nsects asm("al") = 1;
register uint8_t func asm("ah") = 0x02;
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");
static uint8_t rwsector(uint8_t w, int c, int h, int s, void* addr) {
uint16_t ret;
uint16_t cmd = w ? 0x0301 : 0x0201;
// retry for a total of up to 3 attempts if drive not ready
for (int i = 0; i < 3; i++) {
asm volatile("int $0x13"
: "=r"(ret)
: "r"(dest), "r"(nsects), "r"(func), "r"(sect), "r"(cyl),
"r"(head), "r"(drive), "r"(seg));
ret = __readwritesector__(cmd, addr, c, h, s) >> 8;
if (ret != 0x80) {
break;
}
@@ -47,6 +42,14 @@ static int readsector(int c, int h, int s, uint8_t* addr) {
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) {
int offs = cluster * 3 / 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) {
int logicalsector =
kDataRegionStartSector + (cluster - 2) * kSectorsPerCluster;
@@ -65,19 +90,9 @@ static void cluster2chs(int cluster, int* c, int* h, int* s) {
*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) {
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;
cluster2chs(cluster, &c, &h, &s);
if (readsector(c, h, s, addr + i)) {
@@ -88,10 +103,10 @@ static int loadfile(direntry* entry, void* addr) {
s = 1;
h++;
}
if (h > 1) {
h = 0;
c++;
}
// if (h > 1) { // this will never happen
// h = 0;
// c++;
// }
if (readsector(c, h, s, addr + i + 512)) {
return -5;
};
@@ -100,6 +115,94 @@ static int loadfile(direntry* entry, void* addr) {
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) {
if (readsector(0, 0, 2, fat_addr)) {
return -2;
@@ -115,21 +218,22 @@ int fat12_init(void* fat_addr, void* rootdir_addr) {
}
int fat12_readfile(const char* name, void* addr) {
direntry* file = 0;
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;
}
}
direntry* file = findfile(name);
if (!file) {
return -4;
}
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>
int main() {
int main(int argc, int argv[]) {
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 {
. = 0x1000;
. = 0x1200;
.text : {
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:
call sendbuffer # send input buffer
1:
mov ah, 0x07 # do parallel comms
mov ax, 0x0700 # do parallel comms
int 0x80
mov ax, kRecvBufSize
push ax

View File

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

View File

@@ -2,19 +2,16 @@
#include <stdint.h>
typedef void (*recv_cb)(const uint8_t* buf, uint8_t size);
/** Must call first **/
void paracomm_init();
void paracomm_init(recv_cb cb);
/** Sends a single byte.
* Returns: 0 if no error, non-0 otherwise.
*/
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 */
void paracomm_feed(uint8_t n);

View File

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

View File

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

View File

@@ -2,94 +2,26 @@
#include <cstdio>
#include <cstring>
#ifndef WOZMON
#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
#include "polos.h"
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;
// arguments on the stack in reverse order
extern "C" uint16_t Int80h(uint8_t fun, int nargs);
extern "C" uint16_t Int80h(uint16_t fun, uint16_t nargs, uint16_t args[]);
asm(".section .text.Int80h \n"
"Int80h: \n"
" push %si \n"
" push %bp \n"
" mov %al, %ah \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"
" test %dx, %dx \n"
" jz 1f \n"
" push (%si) \n"
" dec %dx \n"
" add $2, %si \n"
" cmp %cx, %si \n"
" je 1f \n"
" push -2(%si) \n"
" sub $2, %si \n"
" jmp 0b \n"
"1: \n"
" int $0x80 \n"
@@ -111,21 +43,22 @@ uint8_t ReadHexNibble(uint8_t c) {
return 10 + (c - 'a');
}
uint16_t ReadUintN(int n, const char* buf) {
uint16_t ReadUintN(int n, const char*& buf) {
uint16_t out = 0;
for (int i = 0; i < n; i++) {
if (buf[i] == 0) {
if (*buf < '0') {
break;
}
out <<= 4;
out += ReadHexNibble(buf[i]);
out += ReadHexNibble(*buf);
buf += 1;
}
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) {
if (c > 9) {
@@ -145,19 +78,9 @@ void WriteUint16(uint16_t a) {
WriteUint8(a & 0xff);
}
uint8_t __get_far_u8__(uint16_t addr, uint16_t seg) {
register uint16_t ad asm("si") = addr;
register uint16_t sg asm("ds") = seg;
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");
void WriteUint16ln(uint16_t a) {
WriteUint16(a);
puts("\r\n");
}
__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);
WriteUint8(d);
}
#if ASCIIDUMP
putchar(' ');
putchar(' ');
for (int i = 0; i < count; i++) {
@@ -184,7 +106,6 @@ void Dump(uint16_t addr, uint16_t seg, int count) {
putchar('.');
}
}
#endif
}
void DumpHex(uint16_t addr, uint16_t seg) {
@@ -197,8 +118,7 @@ void DumpHex(uint16_t addr, uint16_t seg) {
putchar(']');
putchar(':');
Dump(addr, seg, kDumpSize);
putchar('\r');
putchar('\n');
puts("\r\n");
}
void ClearScreen() {
@@ -207,23 +127,6 @@ void ClearScreen() {
: "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) {
if (c >= 'a' && c <= 'z') {
return c - ('a' - 'A');
@@ -231,62 +134,103 @@ char toupper(char 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++) {
if (*name == 0 || *name == '.') {
if (j == len || name[j] == '.') {
fatname[i] = ' ';
} else {
fatname[i] = toupper(*name++);
fatname[i] = toupper(name[j]);
j++;
}
if (i == 7 && *name == '.') {
name++;
if (i == 7 && name[j] == '.') {
j++;
}
}
}
void LaunchFile(const char* name) {
constexpr uint16_t kSegment = 0x200;
constexpr uint16_t kAddress = 0x100;
constexpr uint16_t kHeaderAddress = (kSegment << 4);
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"
uint16_t __readfile__(const char* name, uint16_t addr) {
register uint16_t ret asm("ax");
asm volatile("push %2 \n\t"
"push %1 \n\t"
"mov $04, %%ah \n\t"
"int $0x80 \n\t"
"add $4, %%sp \n\t"
"mov %2, %%ax \n\t"
"mov %%ax, %%ds \n\t"
"mov %%ax, %%es \n\t"
"cli \n\t"
"mov %%ax, %%ss \n\t"
"mov %3, %%ax \n\t"
"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");
"pop %%cx \n\t"
"pop %%cx \n\t"
: "=r"(ret)
: "r"(name), "r"(addr)
: "bx", "cx", "dx", "memory", "cc");
return ret;
}
#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);
@@ -294,9 +238,9 @@ extern "C" void cbreak();
asm(" .section .text.cbreak \n"
"cbreak: \n"
" xor %ax, %ax \n"
" mov %ds, %ax \n"
" mov %ax, %ds \n"
" cli \n"
" mov %ss, %ax \n"
" mov %ax, %ss \n"
" mov $0x2000, %sp \n"
" sti \n"
" pushf \n"
@@ -313,15 +257,43 @@ void InstallCtrlBreak() {
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 dump = true;
for (const char* ptr = buf; *ptr;) {
if (*ptr == 's') {
ptr += 1;
cur_addr = 0;
cur_seg = ReadUint16(ptr + 1);
ptr += 5;
ReadArgs(ptr, &cur_seg, 1);
} else if (*ptr == '$') {
__basic__();
} else if (*ptr == 'j') {
@@ -329,52 +301,31 @@ bool ParseCommand(const char* buf, uint16_t& cur_addr, uint16_t& cur_seg) {
ptr++;
auto jump = reinterpret_cast<uint16_t (*)()>(cur_addr);
uint16_t ret = jump();
WriteUint16(ret);
putchar('\r');
putchar('\n');
#if INT80H
WriteUint16ln(ret);
} else if (*ptr == 'i') {
dump = false;
int nargs = 0;
ptr++;
int fun = ReadUint8(ptr);
ptr += 2;
for (; *ptr;) {
if (*ptr == ' ') {
ptr++;
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
uint16_t args[8]; // ought to be enough for everybody
int nargs = ReadArgs(ptr, args, 8);
uint16_t fun = args[0];
uint16_t ret = Int80h(fun, nargs - 1, args + 1);
WriteUint16ln(ret);
} else if (*ptr == 'l') {
dump = false;
ClearScreen();
ptr++;
#endif // CLEARSCREENCMD
#if LAUNCH
const char* fname;
int l = ReadString(ptr, fname);
uint16_t ret = LoadFile(fname, l, cur_seg);
if (ret) {
WriteUint16ln(ret);
}
} else if (*ptr == 'r') {
dump = false;
ptr++;
while (*ptr == ' ') {
ptr++;
}
int l = 0;
for (; ptr[l]; l++) {
}
LaunchFile(ptr);
ptr += l;
#endif // LAUNCH
uint16_t args[8]; // ought to be enough for everybody
int nargs = ReadArgs(ptr, args, 8);
uint16_t ret = Exec(cur_seg, nargs, args);
WriteUint16ln(ret);
} else if (*ptr == 'w') {
dump = false;
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);
__set_far_u8__(addr, cur_seg, d);
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 {
cur_addr = ReadUint16(ptr);
ptr += 4;
}
}
return dump;
}
void DisplayPrompt() {
putchar('>');
putchar(' ');
}
void DisplayPrompt() { puts("> "); }
[[noreturn]]
void polmon() {
uint16_t cur_addr = 0;
uint16_t cur_seg = 0;
@@ -410,9 +368,7 @@ void polmon() {
char* inptr = inbuf;
ClearScreen();
#if SHOWTITLE
puts("PolMon for Workgroups 3.1\r\n");
#endif // SHOWTITLE
DisplayPrompt();
while (1) {
@@ -434,7 +390,6 @@ void polmon() {
first = false;
inptr = inbuf;
DisplayPrompt();
#if BACKSPACE
} else if (c == kBackspace || c == kOtherBackspace) {
inptr--;
if (inptr < inbuf) {
@@ -444,26 +399,19 @@ void polmon() {
}
putchar(' ');
putchar(kOtherBackspace);
#endif
} else {
*inptr = c;
inptr++;
}
}
__builtin_unreachable();
}
} // namespace
int main() {
#if CTRLBREAK
InstallCtrlBreak();
#endif // CTRLBREAK
polmon();
}
#if BOOTSTRAP
__attribute__((section(".init"), noreturn, used)) void _start() {
main();
__builtin_unreachable();
}
#endif // BOOTSTRAP
__attribute__((section(".init"), used)) void _start() { main(); }

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 <stdlib.h>
#include "polos.h"
int getchar() {
register char c asm("al");
asm volatile("movb $0x00, %%ah\n\t"
@@ -43,3 +45,56 @@ void* memset(void* ptr, int val, size_t len) {
}
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);
}