Compare commits

...

83 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
fa1b26ef10 Use correct chs order in floppy programs 2025-09-25 11:54:12 +02:00
2120df695e polmon: bp in clobber list works not 2025-09-25 11:53:47 +02:00
59def426da More aggressive make clean 2025-09-24 00:57:16 +02:00
10ae44f873 .gitignore more stuff 2025-09-24 00:54:40 +02:00
2bab877a7d Not sure why this was necessary... 2025-09-24 00:53:43 +02:00
f46b13e044 Add floppy image 2025-09-24 00:53:09 +02:00
bf16ae5646 Add polmon executable 2025-09-24 00:51:56 +02:00
3c90dd8b3d Add a fat12-enabled boot sector 2025-09-24 00:49:54 +02:00
bdb2fce4db Add fat12 lib 2025-09-24 00:46:50 +02:00
9bd84390d0 crc16: read data from the right segment 2025-09-24 00:45:56 +02:00
e4473e14f6 make crc16.s 2025-09-24 00:45:32 +02:00
7bf7e863eb kbd: now with ctrl-alt-del 2025-09-24 00:41:30 +02:00
ee97876115 Add a few toys 2025-09-24 00:40:56 +02:00
9daf8d7f97 Small cleanup in *floppy.asm 2025-09-24 00:39:18 +02:00
b5b679c4e2 Fix crc16 2025-09-24 00:38:34 +02:00
c818d185ba makefile: autobuild all nasm stuff 2025-09-24 00:37:05 +02:00
c45a312dd0 wozmon: make some room 2025-09-16 20:07:59 +02:00
becc319419 dev: remove the home to avoid .bash_history 2025-09-14 18:33:24 +02:00
716047531a Update README.md 2025-09-14 16:15:44 +02:00
84810a8037 Update README.md 2025-09-14 16:14:52 +02:00
e5aff1c7a0 Update README.md 2025-09-14 16:13:47 +02:00
5e0d6726c4 Add .gitignore 2025-09-14 16:02:02 +02:00
4d77b77daf Add arduino skecthes 2025-09-14 15:52:39 +02:00
b2eeaa2785 Fix Makefile 2025-09-14 15:36:29 +02:00
57 changed files with 3696 additions and 518 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

5
.gitignore vendored Normal file
View File

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

View File

@@ -3,13 +3,13 @@ FROM ubuntu:jammy AS deps
RUN apt-get update && apt-get install -y software-properties-common RUN apt-get update && apt-get install -y software-properties-common
RUN add-apt-repository ppa:tkchia/build-ia16 RUN add-apt-repository ppa:tkchia/build-ia16
RUN apt-get update && apt-get install -y gcc-ia16-elf RUN apt-get update && apt-get install -y gcc-ia16-elf
RUN apt-get install nasm make RUN apt-get install -y nasm make mtools
FROM deps AS dev FROM deps AS dev
WORKDIR /workspace WORKDIR /workspace
RUN adduser --home /workspace paul RUN adduser paul
USER 1000:1000 USER 1000:1000
@@ -17,7 +17,7 @@ FROM deps AS build
ARG TARGET ARG TARGET
ADD . /workspace ADD ./src /workspace
WORKDIR /workspace WORKDIR /workspace
RUN make ${TARGET} RUN make ${TARGET}

View File

@@ -1,44 +1,29 @@
dev-image = 5150-dev dev-image = 5150-dev
writefloppy.bin: writefloppy.asm
nasm writefloppy.asm -o writefloppy.bin
readfloppy.bin: readfloppy.asm
nasm readfloppy.asm -o readfloppy.bin
crc16.s: crc16.c
ia16-elf-gcc -S -Os -o crc16.s crc16.c
crc16.bin: crc16.s crt0.c 5150.ld
ia16-elf-gcc -o crc16.bin -Os -nostdlib crc16.s crt0.c
wozmon.s: wozmon.cc
ia16-elf-gcc -S -Os -o wozmon.s wozmon.cc
wozmon.bin: wozmon.s crt0.c 5150.ld
ia16-elf-gcc -o wozmon.bin -Os -nostdlib wozmon.s crt0.c
truncate -s 510 wozmon.bin
printf "\125\252" >> wozmon.bin
.PHONY: clean
clean: ## Remove generated files
rm -rf wozmon.bin crc16.bin readfloppy.bin writefloppy.bin
.PHONY: dev-image .PHONY: dev-image
dev-image: dev-image:
docker build -t $(dev-image) --target dev . docker build -t $(dev-image) --target dev .
.PHONY: dev .PHONY: dev
dev: dev-image ## Launch a dev container 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 .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=readfloppy.bin -o . --target=export .
docker build --build-arg TARGET=writefloppy.bin -o . --target=export . docker build --build-arg TARGET=writefloppy.bin -o . --target=export .
docker build --build-arg TARGET=copy.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=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=readio.bin -o . --target=export .
docker build --build-arg TARGET=writeio.bin -o . --target=export .
.PHONY: floppy
floppy: ## Make a bootable floppy image
docker build --build-arg TARGET=polos.img -o . --target=export .
.PHONY: help .PHONY: help

124
README.md
View File

@@ -1,13 +1,123 @@
5150 stuff 5150 stuff
========== ==========
The binary programs here use multiple conventions (more Fun!): ## Layout of a bootable polOS floppy disk
- .asm files use NASM syntax and are built with `nasm xx.asm`
- .s files use GAS syntax, they are built with `ia14-elf-gcc`
The reason is uh, because you see, I did things. The floppy disk is FAT12 formatted. It must contain at least the following items:
The .asm came first and was copy pasta from the internet. * _fat12boot.bin_: the boot sector. It loads _polio.com_ from a FAT12-formatted
The .s files came from the output of `ia16-elf-gcc -S` when I couldn't be bothered to write programs in assembly but still needed to make them compatible with BASIC's *CALL* instruction. drive 0 to address _0x0600_ and jumps to it.
* _polio.com_: the basic OS functions. It serves all interrupt 0x80 functions
(see below) and loads _polmon.com_ to address _0x1200_, then jumps to it.
* _polmon.com_: a barebones memory utility. See below for all it can do.
Also the wozmon binary contains special care to make it into a boot sector (0xaa55 magic). ## polOS int 0x80
* function in AH
* optional argument in AL
* other arguments on the stack
* return value in AX
| AH | AL | stack arguments | return | description |
| --- | --- | --- | --- | --- |
| _0x01_ | | _addr_, _c_, _h_, _s_ | error code in AH | __readsector__: reads 1 floppy sector at _c/h/s_ into _0000:addr_ |
| _0x02_ | | _addr_, _c_, _h_, _s_ | error code in AH | __writesector__: writes 1 floppy sector at _c/h/s_ from _0000:addr_ |
| 0x03 | | _c_, _h_ | error code in AH | __formattrack__: formats 1 track at c/h |
| 0x04 | | _fname_, _addr_ | non-zero if error | __readfile__: reads file with name at _ds:fname_ into _ds:addr_ |
| 0x05 | | _addr_, _size_ | bytes received, < 0 if error | __recvpara__: receive at most _size_ bytes from parallel port into _ds:addr_ |
| 0x06 | | _addr_, _size_ | bytes sent, < 0 if error | __sendpara__: send at most _size_ bytes to parallel port from _ds:addr_ |
| 0x07 | _until-idle_ | | 0 if idle | __runpara__: run a round of parallel comms if _AL=0_, keep going until idle if _AL!=0_ |
| 0x08 | | _fname_, _addr_, _size_ | error code in AH | __writefile__: writes _size_ bytes from _ds:addr_ into file with name at _ds:fname_ |
## polmon commands
Commands are a single letter followed by an optional space and arguments, with optional spaces between them.
A command can be prefixed with up to 4 hex digits, which will then change the current address.
* (no command): dumps 16 bytes at the current address. If no address is given, also increments the current address by 16.
* `w bytes:[u8]*`: writes bytes in hex to the current address.
* `k src:u16 size:u16`: copy _size_ bytes of memory from _cur-seg_:_src_ to _cur-seg:cur-addr_
* `s seg:u16`: changes the current segment
* `j`: calls a function at the current address. ABI calling conventions below.
* `l file:str`: loads a program with given name into the current segment, address _0x0100_.
* `r args:[u16]*`: runs the program in the current segment with arguments.
* `i ax:u16 args:[u16]*`: call an int 0x80 function with given AX and stack args. See section above.
The _r_, _j_ and _i_ commands display the value of the AX register after returning to polmon.
### ABI convention for (j)umping to functions
* AX, BX, CX, DX are caller-saved, all others are callee-saved
* first 3 arguments passed in AX, DX and CX, others are pushed in reverse order (former argument in lower memory)
* return value in AX
### ABI for programs
See example in _hello.com_
## Examples
### Launching mushroom client
```
s 200
l mushroom.com
r
```
### 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();

74
arduino/kbd/kbd.ino Normal file
View File

@@ -0,0 +1,74 @@
#include "kbd.h"
#include "paracomm.h"
#include "parallel.h"
#ifndef NOKBD
#define NOKBD 1
#endif // NOKBD
ParaComms pc;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
setupKbd();
setupParallel();
pc = ParaComms{
.mosi_cb =
[](uint8_t* data, uint8_t len) {
for (int i = 0; i < len; i++) {
Serial.printf("%c", data[i]);
}
},
};
Serial.println("kbd 0.1");
}
void loop() {
static int led_counter = 0;
static int led = HIGH;
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 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);

View File

@@ -0,0 +1,272 @@
#define LV(x) ((x) ? HIGH : LOW)
// pinout sorta like this: https://myoldcomputer.nl/Files/Datasheet/2364-Commodore.pdf
const int kDataPins[] = {
PA12,
PA11,
PB12,
PB11,
PB2,
PB1,
PB15,
PB14,
};
const int kAddressPins[] = {
D15,
D14,
D12,
D11,
D10,
D9,
D8,
D7,
D6,
D5,
D4,
D3,
D2,
};
const int kOutputEnablePin = PC9;
const int kWriteEnablePin = PC8;
void setData(int direction) {
for (int i = 0; i < 8; i++) {
pinMode(kDataPins[i], direction);
}
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(115200);
setData(INPUT);
for (int i = 0; i < 13; i++) {
pinMode(kAddressPins[i], OUTPUT);
}
pinMode(kOutputEnablePin, OUTPUT);
digitalWrite(kOutputEnablePin, HIGH);
pinMode(kWriteEnablePin, OUTPUT);
digitalWrite(kWriteEnablePin, HIGH);
}
int readData() {
int out = 0;
for (int i = 0; i < 8; i++) {
if (digitalRead(kDataPins[i]) == HIGH) {
out += (1 << i);
}
}
return out;
}
void writeData(int data) {
for (int i = 0; i < 8; i++) {
digitalWrite(kDataPins[i], LV(data & (1 << i)));
}
}
void writeAddress(int address) {
for (int i = 0; i < 13; i++) {
digitalWrite(kAddressPins[i], LV(address & (1 << i)));
}
}
int read(int address) {
writeAddress(address);
delayMicroseconds(1);
digitalWrite(kOutputEnablePin, LOW);
delayMicroseconds(1);
int out = readData();
digitalWrite(kOutputEnablePin, HIGH);
return out;
}
void write(int address, int data) {
writeAddress(address);
delayMicroseconds(1);
digitalWrite(kWriteEnablePin, LOW);
writeData(data);
delayMicroseconds(1);
digitalWrite(kWriteEnablePin, HIGH);
}
void chiperase() {
write(0x5555, 0xaa);
write(0x2aaa, 0x55);
write(0x5555, 0x80);
write(0x5555, 0xaa);
write(0x2aaa, 0x55);
write(0x5555, 0x10);
delay(20);
}
constexpr int kBufferSize = 256;
int address = 0;
int led_counter = 0;
int led = HIGH;
char buffer[kBufferSize] = {};
int buffer_size = 0;
const char* kSampleData = "this is a blarg";
int readnibble(const char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return 10 + c - 'A';
if (c >= 'a' && c <= 'f') return 10 + c - 'a';
return -1;
}
int readint8(const char* buffer) {
int n1 = readnibble(buffer[0]);
int n2 = readnibble(buffer[1]);
if (n1 < 0 || n2 < 0) return -1;
return (n1 << 4) + n2;
}
int readint16(const char* buffer) {
int i1 = readint8(buffer);
int i2 = readint8(buffer + 2);
if (i1 < 0 || i2 < 0) return -1;
return (i1 << 8) + i2;
}
int readpage(const char* buffer, char page[64]) {
for (int i = 0; i < 64; i++) {
int b = readint8(&buffer[i*2]);
if (b < 0) {
Serial.printf("error: wrong hex character %c%c\n", buffer[i*2], buffer[i*2+1]);
return -1;
}
page[i] = b;
}
return 0;
}
int writepage(int address, const char page[64]) {
int start = micros();
for (int i = 0; i < 64; i++) {
write(address + i, page[i]);
}
int end = micros();
delay(10);
return end - start;
}
void dump16(int address) {
address = address & 0x1ff0;
char buff[16];
for (int i = 0; i < 16; i++) {
buff[i] = read(address + i);
}
Serial.printf("%04x:", address);
for (int i = 0; i < 16; i++) {
Serial.printf(" %02x", buff[i]);
}
Serial.print(" ");
for (int i = 0; i < 16; i++) {
if (buff[i] >= 33 && buff[i] <= 127) {
Serial.printf("%c", buff[i]);
} else {
Serial.print(".");
}
}
Serial.print("\n");
}
void runcmd(const char* buffer, int size) {
int cmd = buffer[0];
if (size < 1) {
dump16(address);
address += 16;
return;
}
if (cmd == '0') {
address = 0;
} else if (cmd == 'a') {
if (size < 5) {
Serial.println("error: incomplete address command");
return;
}
address = readint16(buffer + 1);
} else if (cmd == 'w') {
if (size < 1 + 4 + 2) {
Serial.println("error: incomplete write command");
return;
}
int address = readint16(buffer + 1);
int data = readint8(buffer + 5);
setData(OUTPUT);
write(address, data);
delay(10);
setData(INPUT);
Serial.printf("wrote data at 0x%04x\n", address);
dump16(address & 0x1ff0);
} else if (cmd == 'p') {
if (size < 1 + 4 + 128) {
Serial.println("error: incomplete page command");
return;
}
int address = readint16(buffer + 1) & 0x1fc0;
char page[64];
if (readpage(buffer + 5, page) != 0) return;
setData(OUTPUT);
int elapsed = writepage(address, page);
setData(INPUT);
Serial.printf("wrote page at 0x%04x in %d us\n", address, elapsed);
dump16(address);
dump16(address + 16);
dump16(address + 32);
dump16(address + 48);
} else if (cmd == 's') {
int addr = address & 0x1ff0;
Serial.printf("writing sample data to 0x%04x\n", addr);
setData(OUTPUT);
for (int i = 0; i < 16; i++) {
write(addr + i, kSampleData[i]);
}
setData(INPUT);
} else if (cmd == 'e') {
setData(OUTPUT);
chiperase();
setData(INPUT);
Serial.println("chip erased");
}
}
void loop() {
if (led_counter > 200000) {
led_counter = 0;
led = (led == HIGH) ? LOW : HIGH;
digitalWrite(LED_BUILTIN, led);
}
led_counter += 1;
if (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
buffer[buffer_size] = 0;
runcmd(buffer, buffer_size);
buffer_size = 0;
buffer[0] = 0;
} else {
buffer[buffer_size] = c;
buffer_size = min(kBufferSize - 1, buffer_size + 1);
}
}
}

View File

@@ -0,0 +1,281 @@
// See RAM chip pinout here: https://www.digchip.com/datasheets/parts/datasheet/922/MK4116-pdf.php
const PinName kAddressPins[] = {
PA_7,
PC_7,
PB_6,
PB_10,
PA_8,
PA_9,
PB_4,
};
const PinName kRasPin = PA_6;
const PinName kWritePin = PB_9;
const PinName kDinPin = PB_8;
const PinName kDoutPin = PA_10;
const PinName kCasPin = PB_3;
void Timer1Isr();
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(115200);
for (int i = 0; i < 7; i++) {
pinMode(pinNametoDigitalPin(kAddressPins[i]), OUTPUT);
}
pinMode(pinNametoDigitalPin(kRasPin), OUTPUT);
pinMode(pinNametoDigitalPin(kCasPin), OUTPUT);
pinMode(pinNametoDigitalPin(kWritePin), OUTPUT);
pinMode(pinNametoDigitalPin(kDoutPin), INPUT);
pinMode(pinNametoDigitalPin(kDinPin), OUTPUT);
digitalWrite(pinNametoDigitalPin(kRasPin), HIGH);
digitalWrite(pinNametoDigitalPin(kCasPin), HIGH);
digitalWrite(pinNametoDigitalPin(kWritePin), HIGH);
// Instantiate HardwareTimer object. Thanks to 'new' instanciation, HardwareTimer is not destructed when setup() function is finished.
HardwareTimer *MyTim = new HardwareTimer(TIM1);
MyTim->setOverflow(1400, MICROSEC_FORMAT);
MyTim->attachInterrupt(Timer1Isr);
MyTim->resume();
}
#define LV(x) ((x) ? HIGH : LOW)
void writeAddress(int address) {
for (int i = 0; i < 7; i++) {
digitalWriteFast(kAddressPins[i], LV(address & (1 << i)));
}
}
int read(int address) {
int row = address >> 7;
int col = address & 0x7f;
noInterrupts();
writeAddress(row);
digitalWriteFast(kRasPin, LOW);
writeAddress(col);
digitalWriteFast(kCasPin, LOW);
delayMicroseconds(1); // tCAS ish
int out = digitalReadFast(kDoutPin);
digitalWriteFast(kCasPin, HIGH);
digitalWriteFast(kRasPin, HIGH);
interrupts();
return out;
}
void write(int address, int data) {
int row = address >> 7;
int col = address & 0x7f;
noInterrupts();
writeAddress(row);
digitalWriteFast(kRasPin, LOW);
digitalWriteFast(kWritePin, LOW);
digitalWriteFast(kDinPin, LV(data));
writeAddress(col);
digitalWriteFast(kCasPin, LOW);
for (int i = 0; i < 10; i++) asm volatile(""); // extra delay
digitalWriteFast(kWritePin, HIGH);
delayMicroseconds(1); // tCAS ish
digitalWriteFast(kCasPin, HIGH);
digitalWriteFast(kRasPin, HIGH);
interrupts();
}
void writepage(int address, const uint8_t data[16]) {
int row = address >> 7;
noInterrupts();
writeAddress(row);
digitalWriteFast(kRasPin, LOW);
for (int col = 0; col < 128; col++) {
int b = data[col >> 3] & (1 << (col % 8));
digitalWriteFast(kDinPin, LV(b));
digitalWriteFast(kWritePin, LOW);
writeAddress(col);
digitalWriteFast(kCasPin, LOW);
for (int i = 0; i < 10; i++) asm volatile(""); // extra delay
digitalWriteFast(kWritePin, HIGH);
delayMicroseconds(1); // tCAS ish
digitalWriteFast(kCasPin, HIGH);
}
digitalWriteFast(kRasPin, HIGH);
interrupts();
}
void readpage(int address, uint8_t data[16]) {
int row = address >> 7;
noInterrupts();
writeAddress(row);
digitalWriteFast(kRasPin, LOW);
for (int col = 0; col < 128; col++) {
uint8_t& out = data[col >> 3];
writeAddress(col);
digitalWriteFast(kCasPin, LOW);
delayMicroseconds(1); // tCAS ish
int b = digitalReadFast(kDoutPin);
out >>= 1;
if (b == HIGH) out |= 0x80;
digitalWriteFast(kCasPin, HIGH);
}
digitalWriteFast(kRasPin, HIGH);
interrupts();
}
int writeread(int address, int value) {
write(address, value);
return read(address);
}
void refreshrow(int row) {
writeAddress(row);
digitalWriteFast(kRasPin, LOW);
delayMicroseconds(1);
digitalWriteFast(kRasPin, HIGH);
}
void refreshall() {
for (int row = 0; row < 128; row++) {
refreshrow(row);
}
}
void Timer1Isr() {
refreshall();
}
int check01(int address) {
if (writeread(address, 0) != 0) return -1;
if (writeread(address, 1) != 1) return -2;
return 0;
}
int checkrow(int row) {
int row_address = row << 7;
for (int i = 0; i < 128; i++) {
int ret = check01(row_address + i);
if (ret != 0) {
Serial.printf("failure at 0x%04x: %d\n", row_address + i, ret);
return ret;
}
}
return 0;
}
int address = 0x007f;
int led_count = 0;
int led = 0;
char cmd = '\0';
const int kLedInterval = 1000000;
const uint8_t kSampleData[16] = {
'b', 'l', 'a', 'r', 'g', ',', ' ', 'c',
'e', 'c', 'i', ' ', 'e', 's', 't', '!',
};
void dump16(int address) {
address &= 0x3f80;
uint8_t data[16];
readpage(address, data);
Serial.printf("%04x:", address);
for (int i = 0; i < 16; i++) {
Serial.printf(" %02x", data[i]);
}
Serial.print(" ");
for (int i = 0; i < 16; i++) {
if (data[i] < 33 || data[i] > 126) {
Serial.print(".");
} else {
Serial.printf("%c", data[i]);
}
}
Serial.println("");
}
void filltest() {
uint8_t data[16];
for (int row = 0; row < 128; row++) {
snprintf(reinterpret_cast<char*>(data), 16, "testing row %03d", row);
writepage(row << 7, data);
}
for (int row = 0; row < 128; row++) {
readpage(row << 7, data);
if (atoi(reinterpret_cast<char*>(&data[12])) != row) {
Serial.printf("error in row %d\n", row);
return;
}
}
Serial.println("fill test ok.");
}
void runcmd(char cmd) {
uint8_t dat[16];
if (cmd == 'p') {
writepage(address, kSampleData);
dump16(address);
} else if (cmd == '\0') {
address += 128;
dump16(address);
} else if (cmd == '0') {
address = 0;
dump16(address);
} else if (cmd == 'f') {
filltest();
} else if (cmd == 't') {
for (int row = 0; row < 128; row++) {
Serial.printf("row %d... ", row);
int ret = checkrow(row);
if (ret != 0) {
break;
}
Serial.println("ok!");
}
}
}
void loop() {
// put your main code here, to run repeatedly:
if (led_count > kLedInterval) {
digitalWrite(LED_BUILTIN, LV(led % 2));
led += 1;
led_count = 0;
}
led_count += 1;
if (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
runcmd(cmd);
cmd = '\0';
} else {
cmd = c;
}
}
}

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

34
crc16.c
View File

@@ -1,34 +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 6(%bp), %si \n"
" push (%si) \n"
" call crc16 \n"
" mov 8(%bp), %di \n"
" mov %ax, (%di) \n"
" pop %bp \n"
" lret $4 \n"
);

19
crt0.c
View File

@@ -1,19 +0,0 @@
#include <stdint.h>
#include <string.h>
int main();
asm (
".section .init \n"
".global _start \n"
"_start: \n"
" jmp main \n"
);
void* memset(void* ptr, int val, size_t len) {
uint8_t* p = ptr;
while(len--) {
*p++ = val;
}
return ptr;
}

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

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

11
src/add.asm Normal file
View File

@@ -0,0 +1,11 @@
BITS 16
CPU 8086
_start:
mov bp, sp
mov si, [bp+6]
mov ax, [si]
mov si, [bp+4]
mov dx, [si]
sub ax, dx
retf 4

29
src/bootsect.S Normal file
View File

@@ -0,0 +1,29 @@
.arch i8086,jumps
.code16
.section .init
.global _start
_start:
#ifndef NOBPB
jmp 0f
nop
.ascii "IBM 3.1"
bpb_bytespersect: .word 512
bpb_sectspercluster: .byte 2
bpb_reservedsects: .word 1
bbp_fats: .byte 2
bpb_rootentries: .word 112
bpb_localsectors: .word 720
bpb_mediadescr: .byte 0xfd
bpb_sectsperfat: .word 2
0:
#endif // NOBPB
cli
xor %ax, %ax
mov %ax, %ds
mov %ax, %ss
mov $0x7c00, %sp
sti
jmp main

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

35
src/call.asm Normal file
View File

@@ -0,0 +1,35 @@
BITS 16
CPU 8086
org 0x7300
PARAMS equ 0x7380
; dw 0: target offset
; dw 2: target segment
; dw 4: number of params
; dw 6: param 0
; ...
_start:
push bx
push si
push di
push bp
mov si, PARAMS
mov bx, 0x6
xor cx, cx
a1:
cmp cx, [si+4]
jge a2
lea ax, [si+bx]
push ax
add bl, 2
inc cl
jmp a1
a2:
call far [si]
pop bp
pop di
pop si
pop bx
ret

22
src/copy.asm Normal file
View File

@@ -0,0 +1,22 @@
BITS 16
CPU 8086
main:
mov bp, sp
mov si, [bp+4]
mov cx, [si]
mov si, [bp+6]
mov di, [si]
mov si, [bp+8]
mov si, [si]
push ds
push es
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

66
src/debug.asm Normal file
View File

@@ -0,0 +1,66 @@
BITS 16
CPU 8086
org 0x7000
_start:
jmp main
hexdigits:
db "0123456789abcdef"
putc:
push bx
push bp
mov ah, 0x0e
xor bh, bh
int 0x10
pop bp
pop bx
ret
printnibble:
push si
push bx
mov si, hexdigits
xor bh, bh
mov bl, al
and bl, 0xf
mov al, cs:[si+bx]
call putc
pop bx
pop si
ret
printi8:
push bx
mov bl, al
mov cl, 4
shr al, cl
call printnibble
mov al, bl
call printnibble
pop bx
ret
printi16:
push bx
mov bx, ax
mov al, ah
call printi8
mov al, bl
call printi8
pop bx
ret
main:
mov bp, sp
mov si, [bp+4]
mov si, [si]
mov ax, [si]
call printi16
mov al, 0x0d
call putc
mov al, 0x0a
call putc
retf 2

20
src/doscom.ld Normal file
View File

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

25
src/dosdbt.asm Normal file
View File

@@ -0,0 +1,25 @@
BITS 16
CPU 8086
diskpointer equ 0x1e*4
dbtbase equ 0x100
_start:
jmp main
main:
push ds
mov si, diskpointer
lds si, [si]
mov di, dbtbase
mov cx, 0x0a
cld
rep movsb
pop ds
mov al, 9 ; sectors per track
mov di, dbtbase
mov [di+4], al
mov di, diskpointer
mov [di], word dbtbase
mov [di+2], ds
retf

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

15
src/fat12.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
/*
* Arguments:
* - fat_addr: 1 KiB
* - rootdir_addr: 3.5 KiB
*/
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(); }

20
src/flat0600.ld Normal file
View File

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

63
src/format.asm Normal file
View File

@@ -0,0 +1,63 @@
BITS 16
CPU 8086
buffer equ 0xe000
sectors equ 9
_start:
jmp main
formattrack:
push bp
push bx
push di
mov bp, sp
push ax ; track
push cx ; head
mov bx, buffer
xor cx, cx
l0:
cmp cl, sectors
jnl l1
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-2] ; track number
mov [di+0], al
mov al, [bp-4] ; head number
mov [di+1], al
mov al, cl ; sector number
inc al
mov [di+2], al
mov [di+3], byte 0x02 ; 512 bytes per sector
inc cl
jmp l0
l1:
mov ah, 0x05 ; format track
mov al, sectors
mov dl, 0 ; first drive
mov dh, [bp-4] ; head number
mov ch, [bp-2] ; track number
mov cl, 1 ; sector number (first sector?)
int 0x13
add sp, 4
pop di
pop bx
pop bp
ret
main:
mov bp, sp
mov si, [bp+8]
mov ax, [si]
mov si, [bp+6]
mov cx, [si]
call formattrack
mov si, [bp+4]
mov [si], ax
retf 6

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

25
src/hello.asm Normal file
View File

@@ -0,0 +1,25 @@
BITS 16
CPU 8086
org 0x7200
_start:
jmp main
hw:
db "Hello, world!", 0x0d, 0x0a, 0
main:
xor bx, bx
mov si, hw
printloop:
mov al, cs:[si]
test al, al
jz done
mov ah, 0x0e
int 0x10
inc si
jmp printloop
done:
retf

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

View File

@@ -1,9 +1,6 @@
BITS 16 BITS 16
CPU 8086
org 0xff00
; actual entry point of the program, must be present
start: start:
; sp and all segment registers need to be saved ; sp and all segment registers need to be saved
@@ -17,11 +14,11 @@ mov es, dx ; es = 0
mov bx, 0xf000 ; store the read sector there mov bx, 0xf000 ; store the read sector there
mov ax, 0x0201 ; read, 1 sector mov ax, 0x0201 ; read, 1 sector
; params: head, cylinder, sector, out ; params: cylinder, head, sector, out
mov si, [bp+14] mov si, [bp+14]
mov dh, [si]
mov si, [bp+12]
mov ch, [si] mov ch, [si]
mov si, [bp+12]
mov dh, [si]
mov si, [bp+10] mov si, [bp+10]
mov cl, [si] mov cl, [si]
@@ -32,4 +29,4 @@ mov [si], ax
pop es pop es
pop bp pop bp
retf 8 ; 6 is 2x the number of parameters on the stack retf 8 ; 2x the number of parameters on the stack

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

View File

@@ -1,9 +1,6 @@
BITS 16 BITS 16
CPU 8086
org 0xfc00
; actual entry point of the program, must be present
start: start:
; sp and all segment registers need to be saved ; sp and all segment registers need to be saved
@@ -17,11 +14,11 @@ mov es, dx ; es = 0
mov bx, 0xf000 ; store the read sector there mov bx, 0xf000 ; store the read sector there
mov ax, 0x0301 ; write, 1 sector mov ax, 0x0301 ; write, 1 sector
; params: head, cylinder, sector, out ; params: cylinder, head, sector, out
mov si, [bp+14] mov si, [bp+14]
mov dh, [si]
mov si, [bp+12]
mov ch, [si] mov ch, [si]
mov si, [bp+12]
mov dh, [si]
mov si, [bp+10] mov si, [bp+10]
mov cl, [si] mov cl, [si]
@@ -32,4 +29,4 @@ mov [si], ax
pop es pop es
pop bp pop bp
retf 8 ; 6 is 2x the number of parameters on the stack retf 8 ; 2x the number of parameters on the stack

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

151
wozmon.cc
View File

@@ -1,151 +0,0 @@
#include <cstdint>
namespace {
uint8_t getc() {
register uint8_t c asm ("al");
asm volatile (
"mov $0x00, %%ah\n"
"int $0x16\n"
: "=r" (c)
);
return c;
}
void putc(uint8_t c) {
asm volatile (
"mov %0, %%al\n"
"mov $0x0e, %%ah\n"
"int $0x10\n"
:: "r" (c)
);
}
void Jump(uint32_t addr) {
auto jump = reinterpret_cast<void (*)()>(addr);
jump();
}
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');
}
uint32_t ReadHex(const char* buf) {
uint32_t out = 0;
while (*buf == ' ') {
buf++;
}
for (int i = 0; i < 8; i++) {
uint8_t c = ReadHexNibble(*buf);
if (c > 0xf) {
break;
}
out = (out << 4) + c;
buf++;
}
return out;
}
void WriteHexNibble(uint8_t c) {
if (c > 9) {
putc('a' + c - 10);
} else {
putc('0' + c);
}
}
void WriteUint32(uint32_t a) {
for (int i = 0; i < 8; i++) {
WriteHexNibble((a >> 28) & 0xf);
a <<= 4;
}
}
void WriteUint8(uint8_t a) {
WriteHexNibble(a >> 4);
WriteHexNibble(a & 0xf);
}
void Dump(uint32_t addr, int count) {
for (int i = 0; i < count; i++) {
putc(' ');
WriteUint8(*reinterpret_cast<uint8_t*>(addr + i));
}
}
void DumpHex(uint32_t addr) {
addr &= 0xfffffffc;
WriteUint32(addr);
putc(':');
Dump(addr, 4);
putc('\r');
putc('\n');
}
int FindChar(const char* buf, uint8_t c) {
int found = 0;
while (*buf) {
if (*buf == c) {
return found;
}
found++;
buf++;
}
return -1;
}
void wozmon() {
uint32_t cur_addr = 0;
uint32_t cur_data = 0;
char inbuf[64] = {};
char* inptr = inbuf;
while (1) {
uint8_t c = getc();
putc(c); // echo
if (c == '\r') {
*inptr = 0;
if (inptr == inbuf) {
cur_addr += 4;
} else if (FindChar(inbuf, 'r') >= 0) {
Jump(cur_addr);
} else {
cur_addr = ReadHex(inbuf);
putc('\n');
}
DumpHex(cur_addr);
int assigned = FindChar(inbuf, ':');
if (assigned >= 0) {
cur_data = ReadHex(inbuf + assigned + 1);
*(reinterpret_cast<uint32_t*>(cur_addr)) = cur_data;
}
inptr = inbuf;
} else if (c == kBackspace) {
inptr--;
if (inptr < inbuf) {
inptr = inbuf;
continue;
}
putc(kOtherBackspace);
putc(' ');
putc(kOtherBackspace);
} else {
*inptr++ = c;
}
}
}
} // namespace
int main() {
wozmon();
}

261
wozmon.s
View File

@@ -1,261 +0,0 @@
.arch i8086,jumps
.code16
.att_syntax prefix
#NO_APP
.text
.type _ZN12_GLOBAL__N_14putcEh, @function
_ZN12_GLOBAL__N_14putcEh:
movw %sp, %bx
movb 2(%bx), %al
#APP
;# 21 "wozmon.cc" 1
mov $0x0e, %ah
int $0x10
;# 0 "" 2
#NO_APP
ret
.size _ZN12_GLOBAL__N_14putcEh, .-_ZN12_GLOBAL__N_14putcEh
.type _ZN12_GLOBAL__N_17ReadHexEPKc, @function
_ZN12_GLOBAL__N_17ReadHexEPKc:
pushw %si
pushw %di
pushw %bp
movw %sp, %bp
pushw %ds
pushw %ds
movw 8(%bp), %ax
movw %ax, -2(%bp)
.L5:
movw -2(%bp), %bx
cmpb $32, (%bx)
jne .L4
incw -2(%bp)
jmp .L5
.L4:
xorw %di, %di
movw %di, %ax
movw %di, %dx
movb $4, %ch
.L9:
movw %di, %bx
movw -2(%bp), %si
movb (%bx,%si), %cl
cmpb $57, %cl
ja .L6
addb $-48, %cl
.L12:
movb %cl, %bl
cmpb $15, %cl
ja .L3
movw %ax, %si
movb $12, %cl
shrw %cl, %si
movb %ch, %cl
shlw %cl, %dx
orw %si, %dx
shlw %cl, %ax
movb %bl, -4(%bp)
movb -4(%bp), %bl
xorb %bh, %bh
addw %bx, %ax
movw $0, %bx
adcw %bx, %dx
incw %di
cmpw $8, %di
jne .L9
.L3:
movw %bp, %sp
popw %bp
popw %di
popw %si
ret
.L6:
addb $-87, %cl
jmp .L12
.size _ZN12_GLOBAL__N_17ReadHexEPKc, .-_ZN12_GLOBAL__N_17ReadHexEPKc
.type _ZN12_GLOBAL__N_114WriteHexNibbleEh, @function
_ZN12_GLOBAL__N_114WriteHexNibbleEh:
pushw %bp
movw %sp, %bp
movb 4(%bp), %al
movb %al, %dl
xorb %dh, %dh
cmpw $9, %dx
jle .L14
addb $87, %al
.L16:
movb %al, 4(%bp)
popw %bp
jmp _ZN12_GLOBAL__N_14putcEh
.L14:
addb $48, %al
jmp .L16
.size _ZN12_GLOBAL__N_114WriteHexNibbleEh, .-_ZN12_GLOBAL__N_114WriteHexNibbleEh
.section .text.startup,"ax",@progbits
.global main
.type main, @function
main:
movw %sp, %bp
subw $76, %sp
leaw -64(%bp), %di
movw $64, %ax
pushw %ax
xorw %ax, %ax
movw %ax, %es
pushw %ax
pushw %di
call memset
addw $6, %sp
movw %es, %si
movw %es, -70(%bp)
.L18:
#APP
;# 11 "wozmon.cc" 1
mov $0x00, %ah
int $0x16
;# 0 "" 2
#NO_APP
pushw %ax
movb %al, -71(%bp)
call _ZN12_GLOBAL__N_14putcEh
addw $2, %sp
movb -71(%bp), %al
cmpb $13, %al
jne .L19
movb $0, (%di)
leaw -64(%bp), %bx
cmpw %bx, %di
jne .L21
addw $4, %si
adcw $0, -70(%bp)
.L22:
movw %si, %ax
andw $-4, %ax
movw %ax, %es
movw -70(%bp), %di
movw $8, %bx
.L25:
movw %ax, -76(%bp)
movw %bx, -74(%bp)
movb $12, %cl
movb %cl, -71(%bp)
pushw -68(%bp)
call _ZN12_GLOBAL__N_114WriteHexNibbleEh
movw -76(%bp), %ax
movw %ax, %dx
movb -71(%bp), %cl
shrw %cl, %dx
movb $4, %cl
shlw %cl, %di
shlw %cl, %ax
orw %dx, %di
addw $2, %sp
movw -74(%bp), %bx
decw %bx
jne .L25
movb $58, %al
pushw %ax
call _ZN12_GLOBAL__N_14putcEh
movw %es, %di
leaw 4(%di), %cx
addw $2, %sp
.L26:
movw %cx, -74(%bp)
movb $32, %al
pushw %ax
call _ZN12_GLOBAL__N_14putcEh
movb (%di), %dl
movb %dl, %al
movb %dl, -71(%bp)
xorb %ah, %ah
movb $4, %cl
sarw %cl, %ax
pushw %ax
call _ZN12_GLOBAL__N_114WriteHexNibbleEh
movb -71(%bp), %dl
andb $15, %dl
pushw %dx
call _ZN12_GLOBAL__N_114WriteHexNibbleEh
incw %di
addw $6, %sp
movw -74(%bp), %cx
cmpw %di, %cx
jne .L26
movb $13, %al
pushw %ax
call _ZN12_GLOBAL__N_14putcEh
movb $10, %al
pushw %ax
call _ZN12_GLOBAL__N_14putcEh
addw $4, %sp
xorw %bx, %bx
leaw -64(%bp), %di
.L29:
movb (%bx,%di), %al
testb %al, %al
je .L18
cmpb $58, %al
je .L28
incw %bx
jmp .L29
.L32:
xchgw %ax, %di
jmp .L18
.L21:
movb (%bx), %al
testb %al, %al
je .L23
cmpb $114, %al
je .L24
incw %bx
jmp .L21
.L19:
cmpb $127, %al
jne .L30
decw %di
leaw -64(%bp), %ax
cmpw %ax, %di
jc .L32
movb $8, %al
pushw %ax
movb %al, -71(%bp)
call _ZN12_GLOBAL__N_14putcEh
movb $32, %dl
pushw %dx
call _ZN12_GLOBAL__N_14putcEh
movb -71(%bp), %al
pushw %ax
call _ZN12_GLOBAL__N_14putcEh
addw $6, %sp
jmp .L18
.L30:
movb %al, (%di)
incw %di
jmp .L18
.L24:
call *%si
jmp .L22
.L23:
leaw -64(%bp), %ax
pushw %ax
call _ZN12_GLOBAL__N_17ReadHexEPKc
addw $2, %sp
xchgw %ax, %si
movw %dx, -70(%bp)
movb $10, %al
pushw %ax
call _ZN12_GLOBAL__N_14putcEh
addw $2, %sp
jmp .L22
.L28:
leaw 1(%bx,%di), %bx
pushw %bx
call _ZN12_GLOBAL__N_17ReadHexEPKc
addw $2, %sp
movw %ax, (%si)
movw %dx, 2(%si)
jmp .L18
.size main, .-main
.ident "GCC: (GNU) 6.3.0"