Compare commits

..

59 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
1b2237f2f7 Dir cleanup 2025-10-01 23:44:19 +02:00
bf9a88805f First program to return to polmon 2025-10-01 23:34:25 +02:00
188256b0ac polmon: simplified r command 2025-10-01 23:33:47 +02:00
b1e7fde1a3 polmon: now with ctrl+break! 2025-10-01 23:33:18 +02:00
2ba1546dec Refactor parallel comms a bit 2025-10-01 23:26:54 +02:00
3df5b28e4e Fix int 0x80 from different segment 2025-10-01 23:24:38 +02:00
b0d57c1c96 Add a couple useful tools 2025-10-01 23:22:56 +02:00
806d87e0f1 Get rid of crt0.c 2025-10-01 23:22:08 +02:00
a4959782a8 Add simple ftp tool 2025-10-01 14:48:28 +02:00
f707c394f0 Add mushroom client, at last 2025-10-01 14:47:52 +02:00
0c0133b3c1 polio: add paracomm i05 recv and i06 send 2025-10-01 14:47:34 +02:00
6a69215c90 polmon: add r command 2025-10-01 14:45:45 +02:00
59f49a232e linker: .data is NOLOAD 2025-10-01 14:45:03 +02:00
ce3ad9fa86 kbd: now not a kbd anymore 2025-10-01 14:44:14 +02:00
06bc908b33 Add bochs config file 2025-09-29 18:57:23 +02:00
f1b2902640 clang-format -i *.cc *.c *.h 2025-09-29 18:54:48 +02:00
f3057e92be Add .clang-format 2025-09-29 18:54:16 +02:00
e20b09f130 Add parallel port comms to arduino 2025-09-29 18:48:11 +02:00
8b434f4434 Add paracli, a parallel port comm utility 2025-09-29 18:00:55 +02:00
83e267fa36 Add a couple more small binaries 2025-09-29 18:00:00 +02:00
dd5d329dff Refactor boot with polio.com first, then polmon 2025-09-29 17:57:42 +02:00
044e58ef12 Update .gitignore 2025-09-29 17:52:43 +02:00
de41328677 polmon: refactor jump & stdlib 2025-09-29 17:52:21 +02:00
8a06693456 boot: prompt retry to load polio.com 2025-09-29 17:47:34 +02:00
05d5e5413f fat12: slightly simpler logic? 2025-09-29 17:46:31 +02:00
50e3017c5c Use linker script to add boot sector magic 2025-09-29 17:45:21 +02:00
7cbd423ef9 copy: fix call with weird es 2025-09-29 17:44:14 +02:00
c49d00422a polmon: no moar es 2025-09-26 16:51:05 +02:00
bbaefabf8a Small fixes in Makefile 2025-09-26 16:50:36 +02:00
202bae362a polmon: stop using es and praying 2025-09-25 14:15:27 +02:00
57 changed files with 2782 additions and 986 deletions

9
.clang-format Normal file
View File

@@ -0,0 +1,9 @@
---
Language: Cpp
BasedOnStyle: LLVM
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
IndentWidth: 4
InsertNewlineAtEOF: true
PointerAlignment: Left
SpacesBeforeTrailingComments: 2

4
.gitignore vendored
View File

@@ -1,3 +1,5 @@
*.bin
*.o
*.elf
*.com
polos.img
polmon.com

View File

@@ -17,7 +17,7 @@ FROM deps AS build
ARG TARGET
ADD . /workspace
ADD ./src /workspace
WORKDIR /workspace
RUN make ${TARGET}

View File

@@ -1,86 +1,25 @@
dev-image = 5150-dev
%.bin: %.asm
nasm $? -o $@
crc16.s: crc16.c
ia16-elf-gcc -S -Os -o crc16.s crc16.c
crc16.bin: crc16.s crt0.c
ia16-elf-gcc -o crc16.bin -Os -nostdlib crc16.s crt0.c
CC = ia16-elf-gcc
CXX = ia16-elf-gcc
LD = ia16-elf-gcc
CXXFLAGS = -mregparmcall -ffunction-sections -Os -flto
CFLAGS = -mregparmcall -ffunction-sections -Os -flto
LDFLAGS = -mregparmcall -Wl,--gc-sections -Os -nostdlib -flto
%.elf:
$(LD) $(LDFLAGS) $(CPPFLAGS) -o $@ $^
bootsectors = fat12boot.bin wozmon.bin
$(bootsectors):
ia16-elf-objcopy -O binary $? $@
truncate -s 510 $@
printf "\125\252" >> $@
fat12boot.elf: fat12boot.o fat12.o bootsect.S
fat12boot.elf: LDFLAGS += -T bootsect.ld
fat12boot.bin: fat12boot.elf
polmon.elf: LDFLAGS += -T flat0600.ld
polmon.elf: polmon.o
polmon.com: polmon.elf
ia16-elf-objcopy -O binary $? $@
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
polos.img: fat12boot.bin polmon.com
dd if=/dev/zero of=$@ bs=512 count=720
mformat -i $@ -t 40 -h 2 -s 9
mcopy -i $@ polmon.com ::/polmon.com
dd if=fat12boot.bin of=$@ conv=notrunc
.PHONY: clean
clean: ## Remove generated files
rm -rf *.bin polmon.com polos.img
.PHONY: dev-image
dev-image:
docker build -t $(dev-image) --target dev .
.PHONY: dev
dev: dev-image ## Launch a dev container
docker run -it --rm -v $(CURDIR):/workspace $(dev-image)
docker run -it --rm -v $(CURDIR)/src:/workspace $(dev-image)
.PHONY: binaries
binaries: ## Build all 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=debug.bin -o . --target=export .
docker build --build-arg TARGET=dosdbt.bin -o . --target=export .
docker build --build-arg TARGET=format.bin -o . --target=export .
docker build --build-arg TARGET=polmon.com -o . --target=export .
docker build --build-arg TARGET=fat12boot.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 .
.PHONY: floppy
floppy: ## Make a bootable floppy image

117
README.md
View File

@@ -1,26 +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
```
s 200
l ftpget.com
```
* 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)

193
arduino/kbd/kbd.cc Normal file
View File

@@ -0,0 +1,193 @@
// keyboard wiring diagram:
// https://www.minuszerodegrees.net/5150/misc/5150_keyboard_reset.jpg
#include "Arduino.h"
constexpr int kClockPin = PC6;
constexpr int kDataPin = PC4;
constexpr int kBitDelayMicros = 100;
constexpr int kCodeDelayMicros = 200;
constexpr int kKeyDelayMicros = 100;
namespace {
void sendCode(int code) {
// preconditions: clock and data pins are INPUT
while (digitalRead(kDataPin) == LOW || digitalRead(kClockPin) == LOW) {
// wait. we're not allowed to send
}
digitalWrite(kDataPin, LOW);
digitalWrite(kClockPin, LOW);
pinMode(kClockPin, OUTPUT);
delayMicroseconds(kBitDelayMicros);
pinMode(kClockPin, INPUT);
// delayMicroseconds(kDelayMicros);
for (int i = 0; i < 8; i++) {
if ((code & (1 << i)) == 0) {
// send a 0
pinMode(kDataPin, OUTPUT);
} // else do nothing, it's already a 1
delayMicroseconds(kBitDelayMicros);
pinMode(kClockPin, OUTPUT);
delayMicroseconds(kBitDelayMicros);
pinMode(kDataPin, INPUT);
pinMode(kClockPin, INPUT);
}
delayMicroseconds(kCodeDelayMicros);
}
enum State {
kReady,
kMaybeReset,
};
constexpr int kResetDelay = 19;
constexpr int kLetterCodes[] = {
0x1E, // 'a'
0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, 0x23, 0x17, 0x24,
0x25, 0x26, 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1F,
0x14, 0x16, 0x2F, 0x11, 0x2D, 0x15, 0x2c,
};
constexpr int kNumberCodes[] = {
0x0B, // '0'
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
};
void sendNormalCode(int code) {
sendCode(code);
sendCode(code | 0x80);
}
void sendShiftCode(int code) {
sendCode(42); // left shift
sendNormalCode(code);
sendCode(42 | 0x80);
}
void sendCtrlAltDel() {
sendCode(0x1d); // ctrl
sendCode(0x38); // alt
sendCode(0x53); // del
sendCode(0x53 | 0x80);
sendCode(0x38 | 0x80);
sendCode(0x1d | 0x80);
}
void sendCtrlBreak() {
sendCode(0x1d); // ctrl
sendCode(0x46); // break
sendCode(0x46 | 0x80);
sendCode(0x1d | 0x80);
}
} // namespace
void sendAsciiChar(int c) {
if (c >= 'a' && c <= 'z') {
return sendNormalCode(kLetterCodes[c - 'a']);
} else if (c >= '0' && c <= '9') {
return sendNormalCode(kNumberCodes[c - '0']);
} else if (c >= 'A' && c <= 'Z') {
return sendShiftCode(kLetterCodes[c - 'A']);
}
switch (c) {
case '-':
return sendNormalCode(12);
case '=':
return sendNormalCode(13);
case '[':
return sendNormalCode(26);
case ']':
return sendNormalCode(27);
case ';':
return sendNormalCode(39);
case '\'':
return sendNormalCode(40);
case ',':
return sendNormalCode(51);
case '.':
return sendNormalCode(52);
case '/':
return sendNormalCode(53);
case ' ':
return sendNormalCode(57);
case '\n':
return sendNormalCode(28);
case '!':
return sendShiftCode(2);
case '@':
return sendShiftCode(3);
case '#':
return sendShiftCode(4);
case '$':
return sendShiftCode(5);
case '%':
return sendShiftCode(6);
case '^':
return sendShiftCode(7);
case '&':
return sendShiftCode(8);
case '*':
return sendShiftCode(9);
case '(':
return sendShiftCode(10);
case ')':
return sendShiftCode(11);
case '_':
return sendShiftCode(12);
case '+':
return sendShiftCode(13);
case '{':
return sendShiftCode(26);
case '}':
return sendShiftCode(27);
case ':':
return sendShiftCode(39);
case '"':
return sendShiftCode(40);
case '<':
return sendShiftCode(51);
case '>':
return sendShiftCode(52);
case '?':
return sendShiftCode(53);
case 0:
return sendCtrlAltDel();
case 27:
return sendCtrlBreak(); // escape
}
}
void setupKbd() {
pinMode(kClockPin, INPUT);
pinMode(kDataPin, INPUT);
}
void checkKbdReset() {
static State state = kReady;
static int lastclocklow = 0;
int clockp = digitalRead(kClockPin);
if (state == State::kReady && clockp == LOW) {
state = State::kMaybeReset;
lastclocklow = millis();
} else if (state == State::kMaybeReset) {
if (clockp == HIGH && millis() - lastclocklow > kResetDelay) {
delay(1);
state = State::kReady;
sendCode(0xaa);
Serial.println("Reset!");
}
}
}

5
arduino/kbd/kbd.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
void sendAsciiChar(int c);
void setupKbd();
void checkKbdReset();

View File

@@ -1,213 +1,74 @@
// keyboard wiring diagram: https://www.minuszerodegrees.net/5150/misc/5150_keyboard_reset.jpg
#include "kbd.h"
#include "paracomm.h"
#include "parallel.h"
constexpr int kClockPin = PC6;
constexpr int kDataPin = PC4;
#ifndef NOKBD
#define NOKBD 1
#endif // NOKBD
constexpr int kBitDelayMicros = 100;
constexpr int kCodeDelayMicros = 200;
constexpr int kKeyDelayMicros = 100;
ParaComms pc;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
// put your setup code here, to run once:
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(kClockPin, INPUT);
pinMode(kDataPin, INPUT);
setupKbd();
setupParallel();
Serial.println("kbd 0.1");
}
pc = ParaComms{
.mosi_cb =
[](uint8_t* data, uint8_t len) {
for (int i = 0; i < len; i++) {
Serial.printf("%c", data[i]);
}
},
};
// 0. reset
// 0.0. clock line will be held low externally for 20 ms
// when back up, keyboard needs to clock 0xAA as a response
//
// 1. operation
// 1.0. just directly clock out scan codes of pressed keys
void sendCode(int code) {
Serial.printf("sending code 0x%02x\n", code);
// preconditions: clock and data pins are INPUT
while (digitalRead(kDataPin) == LOW || digitalRead(kClockPin) == LOW) {
// wait. we're not allowed to send
}
digitalWrite(kDataPin, LOW);
digitalWrite(kClockPin, LOW);
pinMode(kClockPin, OUTPUT);
delayMicroseconds(kBitDelayMicros);
pinMode(kClockPin, INPUT);
// delayMicroseconds(kDelayMicros);
for (int i = 0; i < 8; i++) {
if ((code & (1 << i)) == 0) {
// send a 0
pinMode(kDataPin, OUTPUT);
} // else do nothing, it's already a 1
delayMicroseconds(kBitDelayMicros);
pinMode(kClockPin, OUTPUT);
delayMicroseconds(kBitDelayMicros);
pinMode(kDataPin, INPUT);
pinMode(kClockPin, INPUT);
}
delayMicroseconds(kCodeDelayMicros);
}
enum State {
kReady,
kMaybeReset,
};
constexpr int kResetDelay = 19;
State state = State::kReady;
int lastclocklow = 0;
constexpr int kLetterCodes[] = {
0x1E, // 'a'
0x30,
0x2E,
0x20,
0x12,
0x21,
0x22,
0x23,
0x17,
0x24,
0x25,
0x26,
0x32,
0x31,
0x18,
0x19,
0x10,
0x13,
0x1F,
0x14,
0x16,
0x2F,
0x11,
0x2D,
0x15,
0x2c,
};
constexpr int kNumberCodes[] = {
0x0B, // '0'
0x02,
0x03,
0x04,
0x05,
0x06,
0x07,
0x08,
0x09,
0x0A,
};
void sendNormalCode(int code) {
sendCode(code);
sendCode(code | 0x80);
}
void sendShiftCode(int code) {
sendCode(42); // left shift
sendNormalCode(code);
sendCode(42 | 0x80);
}
void sendCtrlAltDel() {
sendCode(0x1d); // ctrl
sendCode(0x38); // alt
sendCode(0x53); // del
sendCode(0x53 | 0x80);
sendCode(0x38 | 0x80);
sendCode(0x1d | 0x80);
}
void sendCtrlBreak() {
sendCode(0x1d); // ctrl
sendCode(0x46); // break
sendCode(0x46 | 0x80);
sendCode(0x1d | 0x80);
}
void sendAsciiChar(int c) {
if (c >= 'a' && c <= 'z') {
return sendNormalCode(kLetterCodes[c - 'a']);
} else if (c >= '0' && c <= '9') {
return sendNormalCode(kNumberCodes[c - '0']);
} else if (c >= 'A' && c <= 'Z') {
return sendShiftCode(kLetterCodes[c - 'A']);
}
switch (c) {
case '-': return sendNormalCode(12);
case '=': return sendNormalCode(13);
case '[': return sendNormalCode(26);
case ']': return sendNormalCode(27);
case ';': return sendNormalCode(39);
case '\'': return sendNormalCode(40);
case ',': return sendNormalCode(51);
case '.': return sendNormalCode(52);
case '/': return sendNormalCode(53);
case ' ': return sendNormalCode(57);
case '\n': return sendNormalCode(28);
case '!': return sendShiftCode(2);
case '@': return sendShiftCode(3);
case '#': return sendShiftCode(4);
case '$': return sendShiftCode(5);
case '%': return sendShiftCode(6);
case '^': return sendShiftCode(7);
case '&': return sendShiftCode(8);
case '*': return sendShiftCode(9);
case '(': return sendShiftCode(10);
case ')': return sendShiftCode(11);
case '_': return sendShiftCode(12);
case '+': return sendShiftCode(13);
case '{': return sendShiftCode(26);
case '}': return sendShiftCode(27);
case ':': return sendShiftCode(39);
case '"': return sendShiftCode(40);
case '<': return sendShiftCode(51);
case '>': return sendShiftCode(52);
case '?': return sendShiftCode(53);
case 0: return sendCtrlAltDel();
case 27: return sendCtrlBreak(); // escape
}
Serial.println("kbd 0.1");
}
void loop() {
static int led_counter = 0;
static int led = HIGH;
if (led_counter > 400000) {
led_counter = 0;
led = (led == HIGH) ? LOW : HIGH;
digitalWrite(LED_BUILTIN, led);
}
led_counter += 1;
static int led_counter = 0;
static int led = HIGH;
int clockp = digitalRead(kClockPin);
if (state == State::kReady && clockp == LOW) {
state = State::kMaybeReset;
lastclocklow = millis();
} else if (state == State::kMaybeReset) {
if (clockp == HIGH && millis() - lastclocklow > kResetDelay) {
delay(1);
state = State::kReady;
sendCode(0xaa);
Serial.println("Reset!");
static int mode = 0; // 0 = keyboard, 1 = parallel
if (led_counter > 400000) {
led_counter = 0;
led = (led == HIGH) ? LOW : HIGH;
digitalWrite(LED_BUILTIN, led);
}
}
led_counter += 1;
if (Serial.available() > 0) {
int c = Serial.read();
sendAsciiChar(c);
//delayMicroseconds(kKeyDelayMicros);
}
#if NOKBD
#else // NOKBD
checkKbdReset();
#endif // NOKBD
uint8_t mosib;
if (strobeOccurred(mosib)) {
pc.FeedMosiData(mosib);
uint8_t misob = pc.NextMisoNibble();
writeParallel(misob);
}
if (Serial.available() > 0) {
int c = Serial.read();
#if NOKBD
pc.SendByte(c);
#else // NOKBD
if (c == 2) {
mode = 0;
} else if (c == 3) {
mode = 1;
} else {
if (mode == 0) {
sendAsciiChar(c);
} else {
pc.SendByte(c);
}
}
#endif // NOKBD
}
}

124
arduino/kbd/paracomm.h Normal file
View File

@@ -0,0 +1,124 @@
#pragma once
#include <stdint.h>
struct ParaComms {
using MessageCallback = void (*)(uint8_t *, uint8_t);
enum State {
kIdle,
kLength,
kLength2, // for miso
kData,
// kCrc, // ?
};
static constexpr uint8_t kMosiStartByte = 0x42;
static constexpr uint8_t kIdleByte = 0x00;
static constexpr uint8_t kMisoStartNibble = 0xa;
static constexpr uint8_t kMisoIdleNibble = 0x0;
MessageCallback mosi_cb = nullptr;
uint8_t *miso_buffer = nullptr;
uint8_t miso_size = 0;
uint8_t miso_nibbles_sent = 0;
State miso_state = kIdle;
// double work buffer. one being sent, one being written to
uint8_t miso_workbuf[2][256];
uint8_t miso_workbuf_idx = 0;
uint8_t miso_workbuf_size = 0;
uint8_t mosi_buffer[256];
uint8_t mosi_size = 0;
uint8_t mosi_received = 0;
State mosi_state = kIdle;
void SwapBuffers() {
miso_buffer = miso_workbuf[miso_workbuf_idx];
miso_size = miso_workbuf_size;
miso_workbuf_idx = (miso_workbuf_idx + 1) % 2;
miso_workbuf_size = 0;
}
int SendByte(uint8_t b) {
if (miso_workbuf_size == 256) {
return -1; // sorry we're full
}
uint8_t *buff = miso_workbuf[miso_workbuf_idx];
buff[miso_workbuf_size] = b;
miso_workbuf_size += 1;
return 0;
}
void FeedMosiData(uint8_t b) {
switch (mosi_state) {
case kIdle:
// check start byte
if (b == kMosiStartByte) {
mosi_state = kLength;
} else if (b == kIdleByte) {
// just twiddling our thumb drives
} else {
// Serial.printf("sp: %02x\n", b);
}
break;
case kLength:
// assert(b > 0)
mosi_size = b;
mosi_received = 0;
mosi_state = kData;
break;
case kData:
mosi_buffer[mosi_received] = b;
mosi_received += 1;
if (mosi_received == mosi_size) {
if (mosi_cb != nullptr) {
mosi_cb(mosi_buffer, mosi_received);
}
mosi_state = kIdle;
}
case kLength2: // error
return;
}
}
uint8_t NextMisoNibble() {
switch (miso_state) {
case kIdle:
if (miso_size == 0) {
SwapBuffers();
if (miso_size == 0) {
return kMisoIdleNibble;
}
}
miso_state = kLength;
return kMisoStartNibble;
case kLength:
// assert(miso_size > 0);
miso_state = kLength2;
return miso_size & 0xf;
case kLength2:
miso_nibbles_sent = 0;
miso_state = kData;
return miso_size >> 4;
case kData: {
uint8_t b = miso_buffer[miso_nibbles_sent / 2];
if (miso_nibbles_sent % 2 == 0) {
b &= 0xf;
} else {
b >>= 4;
}
miso_nibbles_sent += 1;
if (miso_nibbles_sent == miso_size * 2) {
SwapBuffers();
miso_state = kIdle;
}
return b;
}
}
__builtin_unreachable();
}
};

95
arduino/kbd/parallel.cc Normal file
View File

@@ -0,0 +1,95 @@
#include "parallel.h"
#include <stdint.h>
#include "Arduino.h"
namespace {
constexpr int kParallelDataPins[] = {
D9, D8, D7, D6, D5, D4, D3, D2,
};
constexpr int kParallelBusyPin = D14;
constexpr int kParallelAckPin = D15;
constexpr int kParallelPaperoutPin = D12;
constexpr int kParallelSelectPin = D11;
constexpr int kParallelNibblePins[] = {
kParallelSelectPin,
kParallelPaperoutPin,
kParallelAckPin,
kParallelBusyPin,
};
constexpr bool kParallelNibbleInverted[] = {
false,
false,
false,
true,
};
constexpr int kParallelStrobePin = D10;
volatile bool strobe_pending = false;
volatile uint8_t strobe_byte = 0;
void strobeIsr() {
strobe_pending = true;
strobe_byte = readParallel();
}
} // namespace
uint8_t readParallel() {
uint8_t out = 0;
for (int i = 0; i < 8; i++) {
if (digitalReadFast(digitalPinToPinName(kParallelDataPins[i])) ==
HIGH) {
out |= (1 << i);
}
}
if (digitalReadFast(digitalPinToPinName(kParallelStrobePin)) == HIGH) {
out |= (1 << 8);
}
return out;
}
#define BIT(x, n) ((x & (1 << n)) ? HIGH : LOW)
void writeParallel(uint8_t nibble) {
for (int i = 0; i < 4; i++) {
int lvl = BIT(nibble, i);
if (kParallelNibbleInverted[i]) {
lvl = lvl == HIGH ? LOW : HIGH;
}
digitalWriteFast(digitalPinToPinName(kParallelNibblePins[i]), lvl);
}
}
bool strobeOccurred(uint8_t &byte) {
bool pending;
{
noInterrupts();
pending = strobe_pending;
byte = strobe_byte;
strobe_pending = false;
interrupts();
}
return pending;
}
void setupParallel() {
for (int i = 0; i < sizeof(kParallelDataPins) / sizeof(int); i++) {
pinMode(kParallelDataPins[i], INPUT);
}
for (int i = 0; i < sizeof(kParallelNibblePins) / sizeof(int); i++) {
pinMode(kParallelNibblePins[i], OUTPUT);
digitalWrite(kParallelNibblePins[i], HIGH);
}
pinMode(kParallelStrobePin, INPUT);
attachInterrupt(digitalPinToInterrupt(kParallelStrobePin), strobeIsr,
FALLING);
}

14
arduino/kbd/parallel.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <stdint.h>
/** Returns the byte present on D0~D7 */
uint8_t readParallel();
/** Writes a nibble on status pins */
void writeParallel(uint8_t nibble);
void setupParallel();
/** Returns true and sets the byte if a strobe was received */
bool strobeOccurred(uint8_t &byte);

5
bochsrc.txt Normal file
View File

@@ -0,0 +1,5 @@
romimage: file=$BXSHARE/BIOS-bochs-legacy
boot:floppy
floppya: 360k=polos.img, status=inserted
display_library: sdl2
keyboard: keymap=$BXSHARE/keymaps/sdl2-pc-us.map

35
crc16.c
View File

@@ -1,35 +0,0 @@
#include <stdint.h>
#define kDataAddr ((uint8_t*) 0x0000)
uint16_t crc16(int data_len) {
const uint8_t* data = kDataAddr;
uint16_t crc = 0xFFFF;
for (unsigned int i = 0; i < data_len; ++i) {
uint16_t dbyte = data[i];
crc ^= dbyte << 8;
for (unsigned char j = 0; j < 8; ++j) {
uint16_t mix = crc & 0x8000;
crc = (crc << 1);
if (mix)
crc = crc ^ 0x1021;
}
}
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"
);

52
crc16.s
View File

@@ -1,52 +0,0 @@
.arch i8086,jumps
.code16
.att_syntax prefix
#NO_APP
#APP
.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"

27
crt0.c
View File

@@ -1,27 +0,0 @@
#include <stdint.h>
#include <string.h>
int main();
asm (
".section .init \n"
".global _start \n"
"_start: \n\t"
"mov %cs, %ax \n\t"
"mov %ax, %ds \n\t"
"mov %ax, %es \n\t"
"cli \n\t"
"mov %ax, %ss \n\t"
"mov $0x1000, %ax \n\t"
"mov %ax, %sp \n\t"
"sti \n\t"
"jmp main"
);
void* memset(void* ptr, int val, size_t len) {
uint8_t* p = ptr;
while(len--) {
*p++ = val;
}
return ptr;
}

138
fat12.c
View File

@@ -1,138 +0,0 @@
#include <stdint.h>
#include <stdlib.h>
#define kSectorsPerCluster 2
#define kSectorsPerTrack 9
#define kHeads 2
#define kFatSizeSectors 2
#define kRootDirSizeSectors 7
#define kDataRegionStartSector (1 + kFatSizeSectors*2 + kRootDirSizeSectors)
typedef struct {
char name[8];
char ext[3];
uint8_t attr;
uint8_t dontcare[14];
uint16_t cluster;
uint32_t size;
} direntry;
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");
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));
if (ret == 0x80) {
continue;
} else {
return ret;
}
}
return ret;
}
static int readcluster(int cluster) {
int offs = cluster * 3 / 2;
if (cluster % 2) {
// high nibble is lsb + whole byte
return ((gFat[offs] & 0xf0) >> 4) + (gFat[offs+1] << 4);
} else {
return gFat[offs] + ((gFat[offs+1] & 0x0f) << 8);
}
}
static void cluster2chs(int cluster, int* c, int* h, int* s) {
int logicalsector = kDataRegionStartSector + (cluster - 2) * kSectorsPerCluster;
*s = (logicalsector % kSectorsPerTrack) + 1;
*h = (logicalsector / kSectorsPerTrack) % kHeads;
*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) {
int c, h, s;
cluster2chs(cluster, &c, &h, &s);
if (readsector(c, h, s, addr + i)) {
return -5;
}
s++;
if (s > 9) {
s = 1;
h++;
}
if (h > 1) {
h = 0;
c++;
}
if (readsector(c, h, s, addr + i + 512)) {
return -5;
};
cluster = readcluster(cluster);
}
return 0;
}
int fat12_init(void* fat_addr, void* rootdir_addr) {
if (readsector(0, 0, 2, fat_addr)) {
return -2;
}
if (readsector(0, 0, 6, rootdir_addr)) {
return -3;
}
gFat = fat_addr;
gRootdir = rootdir_addr;
return 0;
}
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;
}
}
if (!file) {
return -4;
}
return loadfile(file, addr);
}

View File

@@ -1,59 +0,0 @@
#include <stdint.h>
#include <stdlib.h>
#include "fat12.h"
#define kPolmonAddress ((void*)0x0600)
#define kFatAddress ((void*)0x1000)
#define kRootDirAddress ((void*)0x1200)
static int putchar(int c) {
register uint8_t khar asm ("al") = c;
register uint8_t func asm ("ah") = 0x0e;
register uint8_t page asm ("bh") = 0;
asm volatile ("int $0x10"
:: "r" (khar), "r" (func), "r" (page)
: "bp");
return c;
}
static int puts(const char* msg) {
while (*msg) {
putchar(*msg++);
}
return 0;
}
__attribute__((noreturn))
static void die(const char* msg) {
puts(msg);
while (1) {
}
__builtin_unreachable();
}
__attribute__((noreturn))
static void jump(void* addr) {
asm volatile ("ljmp $0,%0" :: "i"(addr));
__builtin_unreachable();
}
__attribute__((noreturn))
static void loadpolmon() {
if (fat12_init(kFatAddress, kRootDirAddress)) {
die("fi");
}
if (fat12_readfile("POLMON COM", kPolmonAddress)) {
die("pnf");
}
jump(kPolmonAddress);
}
int main() {
loadpolmon();
}

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

386
polmon.cc
View File

@@ -1,386 +0,0 @@
#include <cstdint>
#ifndef WOZMON
#define WOZMON 0
#endif
#if WOZMON
#define BACKSPACE 0
#define ASCIIDUMP 0
#define CLEARSCREENCMD 0
#define FARCALL 0
#define SHOWTITLE 0
#define BOOTSTRAP 0
#endif // WOZMON
#ifndef BACKSPACE
#define BACKSPACE 1
#endif
#ifndef ASCIIDUMP
#define ASCIIDUMP 1
#endif
#ifndef CLEARSCREENCMD
#define CLEARSCREENCMD 1
#endif
#ifndef FARCALL
#define FARCALL 1
#endif
#ifndef SHOWTITLE
#define SHOWTITLE 1
#endif
#ifndef BOOTSTRAP
#define BOOTSTRAP 1
#endif
namespace {
constexpr int kDumpSize = 16;
uint8_t getc() {
register uint8_t c asm ("al");
asm volatile (
"movb $0x00, %%ah\n\t"
"int $0x16"
: "=r" (c)
:: "ah", "cc"
);
return c;
}
void putc(uint8_t c) {
asm volatile (
"push %%bp \n\t"
"movb %0, %%al \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"
);
}
void puts(const char* s) {
while (*s) {
putc(*s++);
}
}
// arguments on the stack in reverse order
extern "C" uint16_t Jump(uint16_t addr, int nargs);
asm (
".section .text \n"
"Jump: \n"
" push %bx \n"
" push %si \n"
" push %di \n"
" push %bp \n"
" push %es \n"
" push %ax \n" // addr
" movw %sp, %bp \n"
" movw %dx, %si \n" // nargs
" add %si, %si \n"
"j0: \n"
" test %si, %si \n"
" jz j1 \n"
" lea 12(%bp,%si), %di\n"
" push %di \n"
" sub $2, %si \n"
" jmp j0 \n"
"j1: \n"
" lcall *(%bp) \n"
" add $4, %sp \n"
" pop %bp \n"
" pop %di \n"
" pop %si \n"
" pop %bx \n"
" ret"
// return value in ax
);
constexpr uint8_t kBackspace = 0x7f;
constexpr uint8_t kOtherBackspace = 0x08;
uint8_t ReadHexNibble(uint8_t c) {
// lowercase only
if (c <= '9') {
return c - '0';
}
return 10 + (c - 'a');
}
uint16_t ReadUintN(int n, const char* buf) {
uint16_t out = 0;
for (int i = 0; i < n; i++) {
if (buf[i] == 0) {
break;
}
out <<= 4;
out += ReadHexNibble(buf[i]);
}
return out;
}
uint8_t ReadUint8(const char* buf) {
return ReadUintN(2, buf);
}
uint16_t ReadUint16(const char* buf) {
return ReadUintN(4, buf);
}
void WriteHexNibble(uint8_t c) {
if (c > 9) {
putc('a' + c - 10);
} else {
putc('0' + c);
}
}
void WriteUint8(uint8_t a) {
WriteHexNibble(a >> 4);
WriteHexNibble(a & 0xf);
}
void WriteUint16(uint16_t a) {
WriteUint8(a >> 8);
WriteUint8(a & 0xff);
}
void __set_es__(uint16_t es) {
asm volatile (
"mov %0, %%es"
:: "r" (es)
: "es"
);
}
uint8_t __get_es_u8__(uint16_t addr) {
register uint16_t ad asm ("di") = addr;
uint8_t ret;
asm volatile (
"movb %%es:(%1), %0"
: "=r" (ret)
: "r" (ad)
);
return ret;
}
void __set_es_u8__(uint16_t addr, uint8_t val) {
register uint16_t ad asm ("di") = addr;
asm volatile (
"movb %0, %%es:(%1)"
:: "ri" (val), "rmi" (ad)
: "memory"
);
}
__attribute__((noreturn))
inline static void __basic__() {
asm volatile (
"movw $0x40, %ax \n\t"
"mov %ax, %ds \n\t"
"int $0x18"
);
__builtin_unreachable();
}
void Dump(uint16_t addr, int count) {
for (int i = 0; i < count; i++) {
putc(' ');
uint8_t d = __get_es_u8__(addr + i);
WriteUint8(d);
}
#if ASCIIDUMP
putc(' ');
putc(' ');
for (int i = 0; i < count; i++) {
uint8_t d = __get_es_u8__(addr + i);
if (d > 31 && d < 127) {
putc(d);
} else {
putc('.');
}
}
#endif
}
void DumpHex(uint16_t addr, uint16_t seg) {
addr &= -kDumpSize;
putc('[');
WriteUint16(seg);
putc(':');
__set_es__(seg);
WriteUint16(addr);
putc(']');
putc(':');
Dump(addr, kDumpSize);
putc('\r');
putc('\n');
}
void ClearScreen() {
asm volatile (
"movw $0x0002, %%ax \n\t"
"int $0x10"
::: "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 _ZN12_GLOBAL__N_110WriteUint8Eh \n\t"
"movb $0x02, %%ah \n\t"
"movw %%di, %%dx \n\t"
"int $0x10"
:: "rm" (status)
: "ax", "bh", "dx", "cx", "di", "cc"
);
}
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') {
cur_addr = 0;
cur_seg = ReadUint16(ptr+1);
ptr += 5;
} else if (*ptr == '$') {
__basic__();
} else if (*ptr == 'r') {
dump = false;
#if FARCALL
int nargs = 0;
ptr++;
for (; *ptr;) {
if (*ptr == ' ') {
ptr++;
continue;
}
uint16_t d = ReadUint16(ptr);
asm volatile ("push %0" :: "r" (d));
nargs++;
ptr += 4;
}
__set_es__(cur_seg);
uint16_t ret = Jump(cur_addr, nargs);
asm volatile (
"shl %0 \n\t"
"add %0, %%sp"
:: "r"(nargs)
);
WriteUint16(ret);
putc('\r'); putc('\n');
#else // FARCALL
auto jump = reinterpret_cast<void(*)()>(cur_addr);
jump();
#endif // FARCALL
ptr++;
#if CLEARSCREENCMD
} else if (*ptr == 'l') {
dump = false;
ClearScreen();
ptr++;
#endif
} else if (*ptr == 'w') {
dump = false;
uint16_t addr = cur_addr;
ptr += 1;
for (; *ptr;) {
if (*ptr == ' ') {
ptr++;
continue;
}
uint8_t d = ReadUint8(ptr);
__set_es__(cur_seg);
__set_es_u8__(addr, d);
addr++;
ptr += 2;
}
} else {
cur_addr = ReadUint16(ptr);
ptr += 4;
}
}
return dump;
}
const char* prompt = "> ";
void polmon() {
uint16_t cur_addr = 0;
uint16_t cur_seg = 0;
char inbuf[64];
bool first = true;
char* inptr = inbuf;
ClearScreen();
#if SHOWTITLE
puts("PolMon 0.2\r\n");
#endif // SHOWTITLE
puts(prompt);
while (1) {
uint8_t c = getc();
putc(c); // echo
if (c == '\r') {
*inptr = 0;
if (inbuf[0] == 0) {
if (!first) {
cur_addr += kDumpSize;
}
} else {
putc('\n');
}
if (ParseCommand(inbuf, cur_addr, cur_seg)) {
DumpHex(cur_addr, cur_seg);
};
first = false;
inptr = inbuf;
puts(prompt);
#if BACKSPACE
} else if (c == kBackspace || c == kOtherBackspace) {
inptr--;
if (inptr < inbuf) {
inptr = inbuf;
putc(' ');
continue;
}
putc(' ');
putc(kOtherBackspace);
#endif
} else {
*inptr = c;
inptr++;
}
}
}
} // namespace
int main() {
polmon();
}
#if BOOTSTRAP
__attribute__((section(".init"), noreturn, used))
void _start() {
main();
__builtin_unreachable();
}
#endif // BOOTSTRAP

55
src/Makefile Normal file
View File

@@ -0,0 +1,55 @@
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
$(OBJCOPY) -O binary $< $@
fat12boot.bin:
$(OBJCOPY) -O binary $< $@
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 init.ld
polmon.elf: polmon.o stdlib.o
polio.elf: LDFLAGS += -T flat0600.ld -mprotected-mode
polio.elf: polio-main.o polio.s fat12.o paracomm.o stdlib.o
paracli.elf: LDFLAGS += -T doscom.ld
paracli.elf: paracli.s paracomm.o
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 $(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 $@ $(dos-programs) ::/
dd if=fat12boot.bin of=$@ conv=notrunc
.PHONY: clean
clean: ## Remove generated files
rm -rf *.bin *.elf *.o *.com polos.img

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

25
src/bootsect.ld Normal file
View File

@@ -0,0 +1,25 @@
SECTIONS {
. = 0x7c00;
.text : {
KEEP(*(.init))
*(.text*)
}
.rodata : {
*(.rodata*)
}
.bss (NOLOAD) : {
* (.bss)
}
.data (NOLOAD) : {
*(.data)
}
. = 0x7dfe;
.magic : {
SHORT(0xAA55);
}
}

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

View File

@@ -11,9 +11,12 @@ main:
mov si, [si]
push ds
push es
pop ds
mov ax, cs
mov ds, ax
mov es, ax
cld
rep movsw
pop es
pop ds
retf 6

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

31
src/crc16.c Normal file
View File

@@ -0,0 +1,31 @@
#include <stdint.h>
#include "polos.h"
#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 = __get_far_u8__(data + i, seg);
crc ^= dbyte << 8;
for (unsigned char j = 0; j < 8; ++j) {
uint16_t mix = crc & 0x8000;
crc = (crc << 1);
if (mix)
crc = crc ^ 0x1021;
}
}
return crc;
}
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]);
}

8
src/crt0.s Normal file
View File

@@ -0,0 +1,8 @@
.arch i8086,jumps
.code16
.intel_syntax noprefix
.section .init
.global _start
_start:
jmp main

View File

@@ -1,5 +1,5 @@
SECTIONS {
. = 0x7c00;
. = 0x100;
.text : {
KEEP(*(.init))
@@ -14,7 +14,7 @@ SECTIONS {
* (.bss)
}
.data : {
.data (NOLOAD) : {
*(.data)
}
}

239
src/fat12.c Normal file
View File

@@ -0,0 +1,239 @@
#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];
char ext[3];
uint8_t attr;
uint8_t dontcare[14];
uint16_t cluster;
uint32_t size;
} direntry;
static uint8_t* gFat;
static direntry* gRootdir;
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++) {
ret = __readwritesector__(cmd, addr, c, h, s) >> 8;
if (ret != 0x80) {
break;
}
}
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) {
// high nibble is lsb + whole byte
return ((gFat[offs] & 0xf0) >> 4) + (gFat[offs + 1] << 4);
} else {
return gFat[offs] + ((gFat[offs + 1] & 0x0f) << 8);
}
}
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;
*s = (logicalsector % kSectorsPerTrack) + 1;
*h = (logicalsector / kSectorsPerTrack) % kHeads;
*c = logicalsector / (kHeads * kSectorsPerTrack);
}
static int loadfile(direntry* entry, void* addr) {
int cluster = entry->cluster;
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)) {
return -5;
}
s++;
if (s > 9) {
s = 1;
h++;
}
// if (h > 1) { // this will never happen
// h = 0;
// c++;
// }
if (readsector(c, h, s, addr + i + 512)) {
return -5;
};
cluster = readcluster(cluster);
}
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;
}
if (readsector(0, 0, 6, rootdir_addr)) {
return -3;
}
gFat = fat_addr;
gRootdir = rootdir_addr;
return 0;
}
int fat12_readfile(const char* name, void* addr) {
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);
}

View File

@@ -11,5 +11,5 @@ int fat12_init(void* fat_addr, void* rootdir_addr);
* Returns:
* -4 if file is not found
* -5 if there is a disk error
*/
*/
int fat12_readfile(const char* name, void* addr);

53
src/fat12boot.c Normal file
View File

@@ -0,0 +1,53 @@
#include <stdint.h>
#include <stdlib.h>
#include "fat12.h"
#define kPolmonAddress ((void*)0x0600)
#define kFatAddress ((void*)0x1000)
#define kRootDirAddress ((void*)0x1200)
static int putchar(int c) {
register uint8_t khar asm("al") = c;
register uint8_t func asm("ah") = 0x0e;
register uint8_t page asm("bh") = 0;
asm volatile("int $0x10" ::"r"(khar), "r"(func), "r"(page) : "bp");
return c;
}
static int puts(const char* msg) {
while (*msg) {
putchar(*msg++);
}
return 0;
}
__attribute__((noreturn)) static void die(const char* msg) {
puts(msg);
while (1) {
}
__builtin_unreachable();
}
__attribute__((noreturn)) static void jump(void* addr) {
asm volatile("ljmp $0,%0" ::"i"(addr));
__builtin_unreachable();
}
__attribute__((noreturn)) static void loadpolmon() {
if (fat12_init(kFatAddress, kRootDirAddress)) {
die("fi");
}
while (fat12_readfile("POLIO COM", kPolmonAddress)) {
asm volatile("mov $00, %%ah \n\t"
"int $0x16 \n\t" ::
: "ax");
}
jump(kPolmonAddress);
}
int main() { loadpolmon(); }

View File

@@ -14,7 +14,7 @@ SECTIONS {
* (.bss)
}
.data : {
.data (NOLOAD) : {
*(.data)
}
}

48
src/ftp.asm Normal file
View File

@@ -0,0 +1,48 @@
CPU 8086
chunksize equ 0x20
devicedelay equ 0x08
_start:
push bp
mov bp, sp
sub sp, 8
mov [bp-2], word chunksize ; recv size
mov [bp-4], word 0xf000 ; recv addr
mov [bp-6], word 0x0001 ; send size
mov [bp-8], word 0x7000 ; send addr
mov bx, [bp-8]
mov [bx], byte 0x42
l0:
cmp word [bp-4], 0xf600
jb l1
l2:
mov sp, bp
pop bp
ret
l1:
mov ah, 0x07
int 0x80
test al, al
jnz l1
push word [bp-2]
push word [bp-4]
mov ah, 0x05
int 0x80
test al, al
jz l2
mov ah, 0
add [bp-4], ax
push word [bp-6]
push word [bp-8]
mov ah, 0x06
int 0x80
add sp, 8
mov cx, devicedelay
l3:
mov ah, 0x07
push cx
int 0x80
pop cx
loop l3 ; give the device some time to answer
jmp l0

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;
}

12
src/hello.c Normal file
View File

@@ -0,0 +1,12 @@
#include <stdio.h>
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;
}

20
src/init.ld Normal file
View File

@@ -0,0 +1,20 @@
SECTIONS {
. = 0x1200;
.text : {
KEEP(*(.init))
*(.text*)
}
.rodata : {
*(.rodata*)
}
.bss (NOLOAD) : {
* (.bss)
}
.data (NOLOAD) : {
*(.data)
}
}

261
src/mushroom.s Normal file
View File

@@ -0,0 +1,261 @@
.arch i8086,jumps
.code16
.intel_syntax noprefix
.section .init
.global _start
_start:
mov ax, cs
mov ds, ax
mov es, ax
cli
mov ss, ax
mov sp, 0x100
sti
jmp main
.section .bss
kBufSize = 77
kDividerRow = 23
kRecvBufSize = 128
curpos: .word 0
inputsize: .byte 0
escaped: .byte 0
inputbuf: .zero kBufSize
recvbuf: .zero kRecvBufSize
# XXX: funky call
# inputs:
# si: string offset
# cl: string length
# clobbers: ax, bh, dx
.section .text.printn
printn:
push bx
push bp
mov bh, 0
1:
test cl, cl
jz 0f
mov al, [si]
cmpb escaped, 0
je 3f
cmp al, 'm'
jne 5f
movb escaped, 0
jmp 5f
3:
cmp al, 0x1b # ESC
jne 4f
movb escaped, 1
jmp 5f
4:
mov ah, 0x0e # write tty
int 0x10
push cx
mov ah, 0x03 # get cursor
int 0x10
cmp dh, kDividerRow
jb 2f
push dx
mov ah, 0x08 # read color
int 0x10
mov bh, ah
xor cx, cx
mov dx, 0x164f
mov ax, 0x0601 # scroll up, 1 line
int 0x10
pop dx
dec dh
mov bh, 0
mov ah, 0x02 # set cursor
int 0x10
2:
pop cx
5:
inc si
dec cl
jmp 1b
0:
pop bp
pop bx
ret
# XXX: funky call
# inputs:
# si: string offset
# cl: string length
# clobbers: ax, bh, dx
.section .text.addtext
addtext:
push di
push cx
mov bh, 0
mov ah, 0x03 # get cursor pos
int 0x10
pop cx
push dx
mov di, offset curpos
mov dx, [di]
mov ah, 0x02 # set cursor
int 0x10
call printn
mov ah, 0x03 # get cursor pos
int 0x10
mov [di], dx
pop dx
mov ah, 0x02 # set cursor
int 0x10
pop di
ret
.section .text.sendbuffer
sendbuffer:
push bp
mov bp, sp
sub sp, 2
mov al, inputsize
mov [bp-2], al
movb inputsize, 0
mov si, offset .l0
mov cl, 2
call addtext
mov si, offset inputbuf
mov cl, [bp-2]
call addtext
mov si, offset .l1
mov cl, 2
call addtext
mov si, offset inputbuf
mov cl, [bp-2]
call parasend
# XXX: should check cx in case not everything was sent
mov al, ' '
mov dx, 0x1802 # [24; 2]
call drawchar
mov ah, 0x02
int 0x10 # mov cursor back to start of line
add sp, 2
pop bp
ret
# draw until end of line
# al: char
# dh: row
# dl: start col
.section .text.drawchar
drawchar:
push ax
mov bh, 0
mov ah, 0x02 # set cursor
int 0x10
mov cx, 80
sub cl, dl
pop ax
mov ah, 0x0a
int 0x10
ret
.section .text.parasend
parasend:
mov ah, 0x06 # send parallel
push cx
push si
int 0x80
mov cx, 1
push cx
mov cx, offset .l1 + 1
push cx
mov ah, 0x06 # send parallel
int 0x80
add sp, 8
ret
prompt:
mov bh, 0
mov dx, 0x1800
mov ah, 0x02
int 0x10
mov ah, 0x0e
mov al, '>'
int 0x10
mov ah, 0x0e
mov al, ' '
int 0x10
ret
.section .rodata
.l0: .ascii "> "
.l1: .byte 0x0d, 0x0a
.l2: .ascii "MUSHRoom 8086 client 0.1\r\n"
l2len = . - offset .l2
.section .text.main
.global main
main:
movb inputsize, 0 # inputsize = 0
movw curpos, 0 # mov buffer cursor to [0;0]
movb escaped, 0
mov ax, 0x0002
int 0x10
mov si, offset .l2
mov cl, l2len
call addtext
mov dx, 0x1700 # [23; 0]
mov al, 0xc4 # horizontal box line
call drawchar
call prompt
0:
mov ah, 0x01
int 0x16 # check buffer
jz 1f # if empty, do a round of comms
mov ah, 0x00
int 0x16 # read 1 key
cmp al, 0x08 # backspace
je 5f
cmp al, 0x0d # CR
je 4f
mov di, offset inputbuf
mov si, offset inputsize
mov bh, 0
mov bl, [si]
cmp bl, kBufSize # stop accepting input at end of screen
jge 1f # do a round of comms
mov [di+bx], al # store character in buffer
incb [si] # increment buffer size
mov ah, 0x0e
mov bh, 0
int 0x10 # local echo
jmp 0b
5:
mov bl, inputsize
test bl, bl
jz 0b
decb inputsize
mov bh, 0
mov ah, 0x0e
int 0x10 # backspace
mov ax, 0x0e20
int 0x10
mov ax, 0x0e08
int 0x10
jmp 0b
4:
call sendbuffer # send input buffer
1:
mov ax, 0x0700 # do parallel comms
int 0x80
mov ax, kRecvBufSize
push ax
mov ax, offset recvbuf
push ax
mov ah, 0x05 # recv
int 0x80
test al, al
mov cl, al
pop si
pop bx
jz 0b
call addtext
jmp 0b

80
src/paracli.s Normal file
View File

@@ -0,0 +1,80 @@
.arch i8086,jumps
.code16
.intel_syntax noprefix
.section .init
.global _start
_start:
mov ax, cs
mov ds, ax
mov es, ax
cli
mov ss, ax
mov sp, 0x100
sti
jmp main
.section .text.parcb
parcb:
push bx
push si
push bp
mov si, ax
1:
test dx, dx
jz 0f
mov al, [si]
mov ah, 0x0e
mov bh, 0
int 0x10
dec dx
inc si
jmp 1b
0:
pop bp
pop si
pop bx
ret
.section .rodata.l0
.l0: .ascii "> "
.l1: .ascii "."
delay = 500
.section .text.main
.global main
main:
mov ax, 0x0002
int 0x10
mov ax, offset parcb
call paracomm_init
0:
mov ah, 0x01
int 0x16
jz 1f
mov ah, 0x00
int 0x16
mov ah, 0
call paracomm_send
test ax, ax
jz 0b
1:
call paracomm_nextbyte
mov dx, 0x3bc
out dx, al
add dl, 2
mov al, 1
out dx, al
mov cx, delay
2: loop 2b
mov al, 0
out dx, al
mov cx, delay
3: loop 3b
dec dl
in al, dx
mov cl, 4
shr al, cl
call paracomm_feed
jmp 0b

125
src/paracomm.c Normal file
View File

@@ -0,0 +1,125 @@
#include "paracomm.h"
#include <stdlib.h>
#include <string.h>
#define kMosiIdleByte 0x00
#define kMosiStartByte 0x42
#define kMisoIdleNibble 0x0
#define kMisoStartNibble 0xa
static uint8_t mosi_workbuf[2][256];
static uint8_t mosi_workbuf_idx;
static uint8_t mosi_workbuf_size;
static uint8_t* mosi_sendbuf;
static uint8_t mosi_size;
static uint8_t mosi_sent;
static uint8_t mosi_state;
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];
mosi_size = mosi_workbuf_size;
mosi_workbuf_size = 0;
mosi_workbuf_idx = (mosi_workbuf_idx + 1) % 2;
}
uint8_t paracomm_nextbyte() {
switch (mosi_state) {
case 0:
if (mosi_size == 0) {
swapbuffers();
if (mosi_size == 0) {
return kMosiIdleByte;
}
}
mosi_state = 1;
return kMosiStartByte;
case 1:
// assert(mosi_size > 0)
mosi_sent = 0;
mosi_state = 2;
return mosi_size;
case 2: {
uint8_t b = mosi_sendbuf[mosi_sent];
mosi_sent += 1;
if (mosi_sent == mosi_size) {
swapbuffers();
mosi_state = 0;
}
return b;
}
}
__builtin_unreachable();
}
void paracomm_feed(uint8_t n) {
switch (miso_state) {
case 0:
if (n == kMisoStartNibble) {
miso_state = 1;
} else if (n == kMisoIdleNibble) {
} else {
// error: spurious nibble
}
break;
case 1:
miso_size = n;
miso_state = 2;
break;
case 2:
miso_size += n << 4;
miso_received_nibbles = 0;
miso_state = 3;
break;
case 3: {
uint8_t idx = miso_received_nibbles / 2;
if (miso_received_nibbles % 2 == 0) {
miso_recvbuf[idx] = n;
} else {
miso_recvbuf[idx] += n << 4;
}
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(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) {
if (mosi_workbuf_size == 256) {
return -1;
}
uint8_t* buff = mosi_workbuf[mosi_workbuf_idx];
buff[mosi_workbuf_size] = b;
mosi_workbuf_size += 1;
return 0;
}
uint8_t paracomm_busy() { return mosi_state != 0 || miso_state != 0; }

22
src/paracomm.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
typedef void (*recv_cb)(const uint8_t* buf, uint8_t size);
/** Must call first **/
void paracomm_init(recv_cb cb);
/** Sends a single byte.
* Returns: 0 if no error, non-0 otherwise.
*/
int paracomm_send(uint8_t b);
/** Call after reading a nibble from the port */
void paracomm_feed(uint8_t n);
/** Yields the next byte to send out the port */
uint8_t paracomm_nextbyte();
/** Returns non-zero if busy */
uint8_t paracomm_busy();

112
src/polio-main.c Normal file
View File

@@ -0,0 +1,112 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "fat12.h"
#include "paracomm.h"
#define kFatAddr ((void*)0x1a00)
#define kRootDirAddr ((void*)0x1c00)
#define kPolmonAddr ((void*)0x1200)
#define kInt80Vector ((uint16_t*)(0x80 * 4))
#define kParallelDataPort 0x3bc
#define kParallelStatusPort 0x3bd
#define kParallelControlPort 0x3be
#define kParaDelay 100
#define kRecvBufferSize 256
void dosdbt();
void int80h();
static uint8_t parabuf[kRecvBufferSize];
static uint8_t parasize;
static void paracb(const uint8_t* buf, uint8_t size) {
if (parasize + size > sizeof(parabuf)) {
size = sizeof(parabuf) - parasize;
}
memcpy(parabuf + parasize, buf, size);
parasize += size;
}
static void die() {
while (1) {
}
}
static void __outb__(uint16_t port, uint8_t data) {
register uint16_t p asm("dx") = port;
register uint8_t d asm("al") = data;
asm volatile("outb %0, %1" ::"r"(d), "r"(p));
}
static uint8_t __inb__(uint16_t port) {
register uint16_t p asm("dx") = port;
register uint8_t d asm("al");
asm volatile("inb %1, %0" : "=r"(d) : "r"(p));
return d;
}
static void __delay__(int n) {
for (int i = 0; i < n; i++) {
asm volatile("");
}
}
uint16_t xferpara(uint8_t until_idle) {
do {
uint8_t mosib = paracomm_nextbyte();
__outb__(kParallelDataPort, mosib);
__outb__(kParallelControlPort, 1);
__delay__(kParaDelay);
__outb__(kParallelControlPort, 0);
uint8_t mison = __inb__(kParallelStatusPort) >> 4;
paracomm_feed(mison);
} while (until_idle && paracomm_busy());
return paracomm_busy();
}
uint16_t parasend(uint8_t* addr, uint8_t size) {
uint8_t i;
for (i = 0; i < size; i++) {
if (paracomm_send(addr[i])) {
break;
}
}
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;
paracomm_init(paracb);
fat12_init(kFatAddr, kRootDirAddr);
uint16_t* int80v = kInt80Vector;
int80v[0] = (uint16_t)int80h;
int80v[1] = 0;
if (fat12_readfile("POLMON COM", kPolmonAddr)) {
die();
}
asm volatile("jmp *%0" ::"r"(kPolmonAddr));
}

191
src/polio.s Normal file
View File

@@ -0,0 +1,191 @@
.arch i8086,jumps
.code16
.intel_syntax noprefix
.section .init
.global _start
_start:
cli
mov ax, 0x2000 # stack address, shared with polmon
mov sp, ax # ss should already be 0
sti
jmp main
diskpointer = 0x1e*4
dbtbase = 0x0500
.section .text.dosdbt
.global dosdbt
dosdbt: # assumes es=0
push si
push di
push ds
mov si, diskpointer
lds si, [si]
mov di, dbtbase
mov cx, 0x0a # size of the dbt
cld
rep movsb # move dbt to dbtbase
pop ds
mov al, 9 # sectors per track
mov si, dbtbase
mov [si+4], al # set number of sectors
mov di, diskpointer
mov [di], si # new int1eh offset
mov [di+2], ds # and segment
pop di
pop si
ret
.section .text.int80stuff
## floppy stuff
# same as read but with a different int13h:ah value
writesector:
mov ax, 0x0301 # write command, 1 sector
jmp 0f
# params: addr, c, h, s
# return status in ah, sectors read (1 or 0) in al
readsector:
mov ax, 0x0201 # read command, 1 sector
0:
mov bx, [bp+0] # addr
mov ch, [bp+2] # c
mov dh, [bp+4] # h
mov cl, [bp+6] # s
mov dl, 0 # drive
int 0x13
ret
# params: c, h
# returns: status in ah, 0 in al?
buffer = 0xe000 # buffer needs to be big enough for a whole track
# (for some reason), so 9 * 512 bytes = 0x1200 bytes
sectors = 9
formattrack:
mov bx, buffer
xor cx, cx
0:
cmp cl, sectors
jnl 1f
mov di, cx
and di, 0x0f # max 15 sectors
shl di, 1
shl di, 1 # di = cl*4
lea di, [bx+di]
mov al, [bp+0] # track number
mov [di+0], al
mov al, [bp+2] # head number
mov [di+1], al
mov al, cl # sector number
inc al
mov [di+2], al
movb [di+3], 0x02 # 512 bytes per sector
inc cl
jmp 0b
1:
mov ah, 0x05 # format track
mov al, sectors
mov dl, 0 # first drive
mov ch, [bp+0] # track number
mov dh, [bp+2] # head number
mov cl, 1 # sector number (first sector?)
int 0x13
ret
# params: ds:fname, ds:dest
readfile:
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
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:
mov si, [bp-14] # ds
mov cl, 4
shl si, cl
mov ax, [bp+0]
add ax, si
mov dx, [bp+2]
jmp parasend
# params: ds:addr, size
recvpara:
mov si, [bp-14] # ds
mov cl, 4
shl si, cl
mov ax, [bp+0]
add ax, si
mov dx, [bp+2]
jmp pararecv
dummy:
mov ax, -1
ret
.section .text.int80h
.global int80h
int80h:
push si
push di
push bp
push ds
push es
mov bp, sp
xor cx, cx
mov es, cx
mov ds, cx
xchg ah, cl
shl cl
cmp cl, offset int80h_entries
jge 0f
mov si, cx
add bp, 16 # 10 for us, 6 for the interrupt
call cs:[int80h_table+si]
jmp 1f
0:
mov ax, -1
1:
pop es
pop ds
pop bp
pop di
pop si
iret
.section .rodata.int80h
int80h_table:
.word offset dummy # 0x00
.word offset readsector # 0x01
.word offset writesector # 0x02
.word offset formattrack # 0x03
.word offset readfile # 0x04
.word offset recvpara # 0x05
.word offset sendpara # 0x06
.word offset xferpara # 0x07
.word offset writefile # 0x08
int80h_entries = . - int80h_table

417
src/polmon.cc Normal file
View File

@@ -0,0 +1,417 @@
#include <cstdint>
#include <cstdio>
#include <cstring>
#include "polos.h"
namespace {
constexpr int kDumpSize = 16;
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 %sp, %bp \n"
" mov %cx, %si \n"
" shl $1, %dx \n"
" add %dx, %si \n"
"0: \n"
" cmp %cx, %si \n"
" je 1f \n"
" push -2(%si) \n"
" sub $2, %si \n"
" jmp 0b \n"
"1: \n"
" int $0x80 \n"
" mov %bp, %sp \n"
" pop %bp \n"
" pop %si \n"
" ret"
// return value in ax
);
constexpr uint8_t kBackspace = 0x7f;
constexpr uint8_t kOtherBackspace = 0x08;
uint8_t ReadHexNibble(uint8_t c) {
// lowercase only
if (c <= '9') {
return c - '0';
}
return 10 + (c - 'a');
}
uint16_t ReadUintN(int n, const char*& buf) {
uint16_t out = 0;
for (int i = 0; i < n; i++) {
if (*buf < '0') {
break;
}
out <<= 4;
out += ReadHexNibble(*buf);
buf += 1;
}
return out;
}
uint8_t ReadUint8(const char*& buf) { return ReadUintN(2, buf); }
uint16_t ReadUint16(const char*& buf) { return ReadUintN(4, buf); }
void WriteHexNibble(uint8_t c) {
if (c > 9) {
putchar('a' + c - 10);
} else {
putchar('0' + c);
}
}
void WriteUint8(uint8_t a) {
WriteHexNibble(a >> 4);
WriteHexNibble(a & 0xf);
}
void WriteUint16(uint16_t a) {
WriteUint8(a >> 8);
WriteUint8(a & 0xff);
}
void WriteUint16ln(uint16_t a) {
WriteUint16(a);
puts("\r\n");
}
__attribute__((noreturn)) inline static void __basic__() {
asm volatile("movw $0x40, %ax \n\t"
"mov %ax, %ds \n\t"
"int $0x18");
__builtin_unreachable();
}
void Dump(uint16_t addr, uint16_t seg, int count) {
for (int i = 0; i < count; i++) {
putchar(' ');
uint8_t d = __get_far_u8__(addr + i, seg);
WriteUint8(d);
}
putchar(' ');
putchar(' ');
for (int i = 0; i < count; i++) {
uint8_t d = __get_far_u8__(addr + i, seg);
if (d > 31 && d < 127) {
putchar(d);
} else {
putchar('.');
}
}
}
void DumpHex(uint16_t addr, uint16_t seg) {
addr &= -kDumpSize;
putchar('[');
WriteUint16(seg);
putchar(':');
WriteUint16(addr);
putchar(']');
putchar(':');
Dump(addr, seg, kDumpSize);
puts("\r\n");
}
void ClearScreen() {
asm volatile("movw $0x0002, %%ax \n\t"
"int $0x10" ::
: "ax", "cc");
}
char toupper(char c) {
if (c >= 'a' && c <= 'z') {
return c - ('a' - 'A');
}
return c;
}
void tofatname(const char* name, uint8_t len, char* fatname) {
int j = 0;
for (int i = 0; i < 11; i++) {
if (j == len || name[j] == '.') {
fatname[i] = ' ';
} else {
fatname[i] = toupper(name[j]);
j++;
}
if (i == 7 && name[j] == '.') {
j++;
}
}
}
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"
"pop %%cx \n\t"
"pop %%cx \n\t"
: "=r"(ret)
: "r"(name), "r"(addr)
: "bx", "cx", "dx", "memory", "cc");
return ret;
}
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);
extern "C" void cbreak();
asm(" .section .text.cbreak \n"
"cbreak: \n"
" xor %ax, %ax \n"
" mov %ax, %ds \n"
" cli \n"
" mov %ax, %ss \n"
" mov $0x2000, %sp \n"
" sti \n"
" pushf \n"
" push %ax \n"
" mov $0x1000, %ax \n"
" push %ax \n"
" mov $0x20, %al \n"
" out %al, $0x20 \n"
" iret \n");
void InstallCtrlBreak() {
auto vec = reinterpret_cast<void (**)()>(kCtrlBreakVector);
vec[0] = cbreak;
vec[1] = 0;
}
// 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;
ReadArgs(ptr, &cur_seg, 1);
} else if (*ptr == '$') {
__basic__();
} else if (*ptr == 'j') {
dump = false;
ptr++;
auto jump = reinterpret_cast<uint16_t (*)()>(cur_addr);
uint16_t ret = jump();
WriteUint16ln(ret);
} else if (*ptr == 'i') {
dump = false;
ptr++;
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;
ptr++;
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++;
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;
ptr += 1;
for (; *ptr;) {
if (*ptr == ' ') {
ptr++;
continue;
}
uint8_t d = ReadUint8(ptr);
__set_far_u8__(addr, cur_seg, d);
addr++;
}
} 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);
}
}
return dump;
}
void DisplayPrompt() { puts("> "); }
[[noreturn]]
void polmon() {
uint16_t cur_addr = 0;
uint16_t cur_seg = 0;
char inbuf[64];
bool first = true;
char* inptr = inbuf;
ClearScreen();
puts("PolMon for Workgroups 3.1\r\n");
DisplayPrompt();
while (1) {
uint8_t c = getchar();
putchar(c); // echo
if (c == '\r') {
*inptr = 0;
if (inbuf[0] == 0) {
if (!first) {
cur_addr += kDumpSize;
}
} else {
putchar('\n');
}
if (ParseCommand(inbuf, cur_addr, cur_seg)) {
DumpHex(cur_addr, cur_seg);
};
first = false;
inptr = inbuf;
DisplayPrompt();
} else if (c == kBackspace || c == kOtherBackspace) {
inptr--;
if (inptr < inbuf) {
inptr = inbuf;
putchar(' ');
continue;
}
putchar(' ');
putchar(kOtherBackspace);
} else {
*inptr = c;
inptr++;
}
}
__builtin_unreachable();
}
} // namespace
int main() {
InstallCtrlBreak();
polmon();
}
__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);
}

10
src/readio.asm Normal file
View File

@@ -0,0 +1,10 @@
BITS 16
CPU 8086
_start:
mov bp, sp
mov si, [bp+4]
mov dx, [si]
xor ax, ax
in al, dx
retf 2

10
src/runcomm.asm Normal file
View File

@@ -0,0 +1,10 @@
CPU 8086
mov cx, 0x100
a:
mov ah, 0x07
int 0x80
test al, al
loopnz a
b:
ret

100
src/stdlib.c Normal file
View File

@@ -0,0 +1,100 @@
#include <stdint.h>
#include <stdlib.h>
#include "polos.h"
int getchar() {
register char c asm("al");
asm volatile("movb $0x00, %%ah\n\t"
"int $0x16"
: "=r"(c)::"ah", "cc");
return c;
}
int 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");
return 0;
}
int puts(const char* s) {
while (*s) {
putchar(*s++);
}
return 0;
}
void* memcpy(void* dest, const void* src, size_t n) {
uint8_t* d = dest;
const uint8_t* s = src;
for (int i = 0; i < n; i++) {
d[i] = s[i];
}
return dest;
}
void* memset(void* ptr, int val, size_t len) {
uint8_t* p = ptr;
while (len--) {
*p++ = val;
}
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);
}

13
src/trampoline.asm Normal file
View File

@@ -0,0 +1,13 @@
CPU 8086
src equ 0xf000
dest equ 0x0600
size equ 0x0600
_start:
mov cx, size
mov si, src
mov di, dest
rep movsb
mov ax, dest
jmp ax

12
src/writeio.asm Normal file
View File

@@ -0,0 +1,12 @@
BITS 16
CPU 8086
_start:
mov bp, sp
mov si, [bp+6]
mov dx, [si]
xor ax, ax
mov si, [bp+4]
mov al, [si]
out dx, al
retf 4