initial commit
authorJohn Tsiombikas <nuclear@member.fsf.org>
Fri, 6 Oct 2023 05:08:35 +0000 (08:08 +0300)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Fri, 6 Oct 2023 05:08:35 +0000 (08:08 +0300)
62 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
com32.ld [new file with mode: 0644]
src/config.h [new file with mode: 0644]
src/game.c [new file with mode: 0644]
src/game.h [new file with mode: 0644]
src/kern/asmops.h [new file with mode: 0644]
src/kern/contty.c [new file with mode: 0644]
src/kern/contty.h [new file with mode: 0644]
src/kern/desc.h [new file with mode: 0644]
src/kern/intr.c [new file with mode: 0644]
src/kern/intr.h [new file with mode: 0644]
src/kern/intr_s.asm [new file with mode: 0644]
src/kern/intrtab.h [new file with mode: 0644]
src/kern/kbregs.h [new file with mode: 0644]
src/kern/kbscan.h [new file with mode: 0644]
src/kern/keyb.c [new file with mode: 0644]
src/kern/keyb.h [new file with mode: 0644]
src/kern/main.c [new file with mode: 0644]
src/kern/mem.c [new file with mode: 0644]
src/kern/mem.h [new file with mode: 0644]
src/kern/panic.c [new file with mode: 0644]
src/kern/panic.h [new file with mode: 0644]
src/kern/power.c [new file with mode: 0644]
src/kern/power.h [new file with mode: 0644]
src/kern/psaux.c [new file with mode: 0644]
src/kern/psaux.h [new file with mode: 0644]
src/kern/regs.asm [new file with mode: 0644]
src/kern/segm.c [new file with mode: 0644]
src/kern/segm.h [new file with mode: 0644]
src/kern/segm_s.asm [new file with mode: 0644]
src/kern/serial.c [new file with mode: 0644]
src/kern/serial.h [new file with mode: 0644]
src/kern/timer.c [new file with mode: 0644]
src/kern/timer.h [new file with mode: 0644]
src/kern/tss.h [new file with mode: 0644]
src/libc/alloca.h [new file with mode: 0644]
src/libc/assert.h [new file with mode: 0644]
src/libc/ctype.c [new file with mode: 0644]
src/libc/ctype.h [new file with mode: 0644]
src/libc/errno.h [new file with mode: 0644]
src/libc/float.h [new file with mode: 0644]
src/libc/inttypes.h [new file with mode: 0644]
src/libc/limits.h [new file with mode: 0644]
src/libc/malloc.c [new file with mode: 0644]
src/libc/math.c [new file with mode: 0644]
src/libc/math.h [new file with mode: 0644]
src/libc/rand.c [new file with mode: 0644]
src/libc/setjmp.c [new file with mode: 0644]
src/libc/setjmp.h [new file with mode: 0644]
src/libc/stdarg.h [new file with mode: 0644]
src/libc/stddef.h [new file with mode: 0644]
src/libc/stdio.c [new file with mode: 0644]
src/libc/stdio.h [new file with mode: 0644]
src/libc/stdlib.c [new file with mode: 0644]
src/libc/stdlib.h [new file with mode: 0644]
src/libc/string.c [new file with mode: 0644]
src/libc/string.h [new file with mode: 0644]
src/libc/string_s.asm [new file with mode: 0644]
src/loader.asm [new file with mode: 0644]
src/macros.inc [new file with mode: 0644]
src/startup.asm [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..cec07fe
--- /dev/null
@@ -0,0 +1,11 @@
+*.o
+*.d
+*.swp
+*.com
+*.map
+RUN
+disasm
+dosbox.conf
+*.log
+tofloppy
+*.s
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..9a8942f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+csrc = $(wildcard src/*.c) $(wildcard src/kern/*.c) $(wildcard src/libc/*.c)
+ssrc = $(wildcard src/*.asm) $(wildcard src/kern/*.asm) $(wildcard src/libc/*.asm)
+obj = $(csrc:.c=.o) $(ssrc:.asm=.o)
+dep = $(csrc:.c=.d)
+bin = game.com
+
+warn = -pedantic -Wall -Wno-unused-function
+opt = -O2
+inc = -Isrc -Isrc/kern -Isrc/libc
+
+AS = nasm
+
+ASFLAGS = -Isrc/ -Isrc/kern/
+CFLAGS = -m32 -march=i386 $(warn) $(opt) $(dbg) -fno-pic -ffreestanding \
+                -fno-stack-protector -mpreferred-stack-boundary=2 -nostdinc -ffast-math \
+                -fno-asynchronous-unwind-tables $(inc) $(def) -MMD
+LDFLAGS = -m elf_i386 -nostdlib -T com32.ld -Map game.map
+
+$(bin): $(obj)
+       $(LD) -o $@ $(obj) $(LDFLAGS)
+
+-include $(dep)
+
+%.o: %.asm
+       $(AS) -o $@ -f elf $(ASFLAGS) $<
+
+%.s: %.c
+       $(CC) $(CFLAGS) -masm=intel -S $< -o $@
+
+.PHONY: clean
+clean:
+       rm -f $(obj) $(bin)
+
+.PHONY: cleandep
+cleandep:
+       rm -f $(dep)
+
+disasm: $(bin)
+       ndisasm -o 0x100 -b 16 $< >$@
diff --git a/com32.ld b/com32.ld
new file mode 100644 (file)
index 0000000..213b4e2
--- /dev/null
+++ b/com32.ld
@@ -0,0 +1,41 @@
+OUTPUT_FORMAT(binary)
+ENTRY(_start)
+
+SECTIONS {
+       /* loader starts at <seg>:100h */
+       . = 0x100;
+       .loader : { * (.loader); }
+
+       . = ALIGN(4);
+       _ldr_main_start = .;
+       _ldr_main_end = _ldr_main_start + _main_size;
+
+       /* main program will be moved to 2MB by the loader */
+       . = 2M;
+       .main ALIGN(4): AT (_ldr_main_start) {
+               _main_start = .;
+               * (.startup);
+               * (.text*);
+               * (.rodata*);
+               * (.data*);
+               . = ALIGN(4);
+       }
+
+       .bss ALIGN(4): AT (_ldr_main_start + SIZEOF(.main)) {
+               . = ALIGN(4);
+               _bss_start = .;
+               * (.bss*);
+               * (COMMON);
+               . = ALIGN(4);
+               _bss_end = .;
+       }
+
+       . = ALIGN(4);
+       _main_size = . - _main_start;
+       _mem_start = .;
+
+       /DISCARD/ : {
+               *(.note.GNU-stack);
+               *(.comment);
+       }
+}
diff --git a/src/config.h b/src/config.h
new file mode 100644 (file)
index 0000000..ba9415d
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef PCBOOT_CONFIG_H_
+#define PCBOOT_CONFIG_H_
+
+/* frequency of generated timer ticks in hertz */
+#define TICK_FREQ_HZ           250
+
+#undef CON_TEXTMODE
+#define CON_SERIAL
+
+#endif /* PCBOOT_CONFIG_H_ */
diff --git a/src/game.c b/src/game.c
new file mode 100644 (file)
index 0000000..f04984c
--- /dev/null
@@ -0,0 +1,31 @@
+#include <string.h>
+#include "game.h"
+
+int game_init(void)
+{
+       return 0;
+}
+
+void game_shutdown(void)
+{
+}
+
+void game_draw(void)
+{
+       memset(framebuf, 2, 64000);
+
+       game_swap_buffers();
+}
+
+void game_keyboard(int key, int press)
+{
+       if(key == 27) game_quit();
+}
+
+void game_mouse(int bn, int press, int x, int y)
+{
+}
+
+void game_motion(int x, int y)
+{
+}
diff --git a/src/game.h b/src/game.h
new file mode 100644 (file)
index 0000000..9eb4cc9
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef GAME_H_
+#define GAME_H_
+
+#define FB_WIDTH       320
+#define FB_HEIGHT      200
+
+extern unsigned char *framebuf, *vmem;
+
+int game_init(void);
+void game_shutdown(void);
+
+void game_draw(void);
+
+void game_reshape(int x, int y);
+void game_keyboard(int key, int press);
+void game_mouse(int bn, int press, int x, int y);
+void game_motion(int x, int y);
+
+void game_quit(void);
+void game_swap_buffers(void);
+
+#endif /* GAME_H_ */
diff --git a/src/kern/asmops.h b/src/kern/asmops.h
new file mode 100644 (file)
index 0000000..5d8ec31
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2023  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef ASMOPS_H_
+#define ASMOPS_H_
+
+#include <inttypes.h>
+
+#define enable_intr() asm volatile("sti")
+#define disable_intr() asm volatile("cli")
+#define halt_cpu() asm volatile("hlt")
+
+#define CALLER_EIP(eip) \
+       asm volatile( \
+               "mov 4(%%ebp), %0\n\t" \
+               : "=a" ((uint32_t)eip))
+
+static inline uint8_t inp(uint16_t port)
+{
+       uint8_t res;
+       asm volatile (
+               "inb %1, %0\n\t"
+               : "=a" (res)
+               : "dN" (port));
+       return res;
+}
+
+static inline uint16_t inpw(uint16_t port)
+{
+       uint16_t res;
+       asm volatile (
+               "inw %1, %0\n\t"
+               : "=a" (res)
+               : "dN" (port));
+       return res;
+}
+
+static inline uint32_t inpd(uint16_t port)
+{
+       uint32_t res;
+       asm volatile (
+               "inl %1, %0\n\t"
+               : "=a" (res)
+               : "dN" (port));
+       return res;
+}
+
+#define outp(port, src) \
+       asm volatile( \
+               "outb %0, %1\n\t" \
+               :: "a" ((uint8_t)(src)), "dN" ((uint16_t)(port)))
+
+#define outpw(port, src) \
+       asm volatile( \
+               "outw %0, %1\n\t" \
+               :: "a" ((uint16_t)(src)), "dN" ((uint16_t)(port)))
+
+#define outpd(port, src) \
+       asm volatile( \
+               "outl %0, %1\n\t" \
+               :: "a" ((uint32_t)(src)), "dN" ((uint16_t)(port)))
+
+/* delay for about 1us */
+#define iodelay() outp(0x80, 0)
+
+#define get_ebp(ebp) \
+       asm volatile( \
+               "mov %%ebp, %0\n\t" \
+               : "=g"((uint32_t)(ebp)))
+
+#endif /* ASMOPS_H_ */
diff --git a/src/kern/contty.c b/src/kern/contty.c
new file mode 100644 (file)
index 0000000..2ed22e7
--- /dev/null
@@ -0,0 +1,310 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2023  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include "contty.h"
+#include "serial.h"
+#include "asmops.h"
+#include "config.h"
+
+#define VIRT_ROWS      200
+
+#define NCOLS          80
+#define NROWS          25
+#define TEXT_ADDR      ((char*)0xb8000)
+
+#define CRTC_ADDR      0x3d4
+#define CRTC_DATA      0x3d5
+
+#define CRTC_REG_CURSTART      0x0a
+#define CRTC_REG_CUREND                0x0b
+#define CRTC_REG_START_H       0x0c
+#define CRTC_REG_START_L       0x0d
+#define CRTC_REG_CURLOC_H      0x0e
+#define CRTC_REG_CURLOC_L      0x0f
+
+#define VMEM_CHAR(c, attr) \
+       (((uint16_t)(c) & 0xff) | ((uint16_t)(attr) << 8))
+
+static void scroll(void);
+static void crtc_cursor(int x, int y);
+static void crtc_getcursor(int *x, int *y);
+static void crtc_setstart(int y);
+static int crtc_getstart(void);
+static inline unsigned char crtc_read(int reg);
+static inline void crtc_write(int reg, unsigned char val);
+
+static int cursor_x, cursor_y;
+static unsigned char txattr = 0x07;
+static int start_line;
+static unsigned char cy0, cy1;
+static int curvis;
+static int scr_on = 1;
+
+
+int con_init(void)
+{
+#ifdef CON_SERIAL
+       ser_open(1, 9600, SER_8N1);
+#endif
+
+#ifdef CON_TEXTMODE
+       cy0 = crtc_read(CRTC_REG_CURSTART);
+       curvis = cy0 & 0x20 ? 1 : 0;
+       cy0 &= 0x1f;
+       cy1 = crtc_read(CRTC_REG_CUREND) & 0x1f;
+
+       start_line = crtc_getstart();
+       crtc_getcursor(&cursor_x, &cursor_y);
+       con_show_cursor(1);
+       scr_on = 1;
+#endif
+
+       return 0;
+}
+
+void con_scr_enable(void)
+{
+       scr_on = 1;
+}
+
+void con_scr_disable(void)
+{
+       scr_on = 0;
+}
+
+void con_show_cursor(int show)
+{
+#ifdef CON_TEXTMODE
+       unsigned char val = cy0 & 0x1f;
+       if(!show) {
+               val |= 0x20;
+       }
+       crtc_write(CRTC_REG_CURSTART, val);
+       curvis = show;
+#endif
+}
+
+void con_cursor(int x, int y)
+{
+#ifdef CON_TEXTMODE
+       cursor_x = x;
+       cursor_y = y;
+       crtc_cursor(x, y);
+#endif
+}
+
+void con_curattr(int shape, int blink)
+{
+#ifdef CON_TEXTMODE
+       unsigned char start;
+       cy0 = (shape == CON_CURSOR_LINE) ? 0xd : 0;
+       cy1 = 0xe;
+
+       start = cy0;
+       if(curvis) {
+               start |= 0x20;
+       }
+
+       crtc_write(CRTC_REG_CURSTART, start);
+       crtc_write(CRTC_REG_CUREND, cy0);
+#endif
+}
+
+void con_fgcolor(int c)
+{
+       txattr = (txattr & 0xf0) | c;
+}
+
+void con_bgcolor(int c)
+{
+       txattr = (txattr & 0x0f) | (c << 4);
+}
+
+void con_setattr(unsigned char attr)
+{
+       txattr = attr;
+}
+
+unsigned char con_getattr(void)
+{
+       return txattr;
+}
+
+void con_clear(void)
+{
+#ifdef CON_TEXTMODE
+       memset16(TEXT_ADDR, VMEM_CHAR(' ', txattr), NCOLS * NROWS);
+
+       start_line = 0;
+       crtc_setstart(0);
+
+       cursor_x = cursor_y = 0;
+       crtc_cursor(0, 0);
+#endif
+}
+
+static inline void linefeed(void)
+{
+       if(++cursor_y >= NROWS) {
+               scroll();
+               --cursor_y;
+       }
+}
+
+void con_putchar(int c)
+{
+#ifdef CON_TEXTMODE
+       if(scr_on) {
+               switch(c) {
+               case '\n':
+                       linefeed();
+               case '\r':
+                       cursor_x = 0;
+                       crtc_cursor(cursor_x, cursor_y);
+                       break;
+
+               case '\t':
+                       cursor_x = (cursor_x & 0x7) + 8;
+                       if(cursor_x >= NCOLS) {
+                               linefeed();
+                               cursor_x = 0;
+                       }
+                       crtc_cursor(cursor_x, cursor_y);
+                       break;
+
+               case '\b':
+                       if(cursor_x > 0) cursor_x--;
+                       con_putchar_scr(cursor_x, cursor_y, ' ');
+                       crtc_cursor(cursor_x, cursor_y);
+                       break;
+
+               default:
+                       con_putchar_scr(cursor_x, cursor_y, c);
+
+                       if(++cursor_x >= NCOLS) {
+                               linefeed();
+                               cursor_x = 0;
+                       }
+                       crtc_cursor(cursor_x, cursor_y);
+               }
+       }
+#endif
+
+#ifdef CON_SERIAL
+       ser_putchar(c);
+#endif
+}
+
+void con_putchar_scr(int x, int y, int c)
+{
+#ifdef CON_TEXTMODE
+       uint16_t *ptr = (uint16_t*)TEXT_ADDR;
+       ptr[(y + start_line) * NCOLS + x] = VMEM_CHAR(c, txattr);
+#endif
+}
+
+int con_printf(int x, int y, const char *fmt, ...)
+{
+#ifdef CON_TEXTMODE
+       va_list ap;
+       char buf[81];
+       char *ptr = buf;
+
+       va_start(ap, fmt);
+       vsnprintf(buf, 80, fmt, ap);
+       va_end(ap);
+
+       while(*ptr && x < 80) {
+               con_putchar_scr(x++, y, *ptr++);
+       }
+       return ptr - buf;
+#else
+       return 0;
+#endif
+}
+
+static void scroll(void)
+{
+       int new_line;
+
+       if(++start_line > VIRT_ROWS - NROWS) {
+               /* The bottom of the visible range reached the end of our text buffer.
+                * Copy the rest of the lines to the top and reset start_line.
+                */
+               memcpy(TEXT_ADDR, TEXT_ADDR + start_line * NCOLS, (NROWS - 1) * NCOLS * 2);
+               start_line = 0;
+       }
+
+       /* clear the next line that will be revealed by scrolling */
+       new_line = start_line + NROWS - 1;
+       memset16(TEXT_ADDR + new_line * NCOLS * 2, VMEM_CHAR(' ', txattr), NCOLS);
+       crtc_setstart(start_line);
+}
+
+static void crtc_cursor(int x, int y)
+{
+       unsigned int addr;
+
+       addr = (y + start_line) * NCOLS + x;
+
+       crtc_write(CRTC_REG_CURLOC_L, addr);
+       crtc_write(CRTC_REG_CURLOC_H, addr >> 8);
+}
+
+static void crtc_getcursor(int *x, int *y)
+{
+       unsigned int addr;
+
+       addr = crtc_read(CRTC_REG_CURLOC_L);
+       addr |= (unsigned int)crtc_read(CRTC_REG_CURLOC_H) << 8;
+
+       *y = addr / NCOLS - start_line;
+       *x = addr % NCOLS;
+}
+
+static void crtc_setstart(int y)
+{
+       unsigned int addr = y * NCOLS;
+
+       crtc_write(CRTC_REG_START_L, addr);
+       crtc_write(CRTC_REG_START_H, addr >> 8);
+}
+
+static int crtc_getstart(void)
+{
+       unsigned int addr;
+
+       addr = crtc_read(CRTC_REG_START_L);
+       addr |= (unsigned int)crtc_read(CRTC_REG_START_H) << 8;
+
+       return addr / NCOLS;
+}
+
+static inline unsigned char crtc_read(int reg)
+{
+       outp(CRTC_ADDR, reg);
+       return inp(CRTC_DATA);
+}
+
+static inline void crtc_write(int reg, unsigned char val)
+{
+       outp(CRTC_ADDR, reg);
+       outp(CRTC_DATA, val);
+}
diff --git a/src/kern/contty.h b/src/kern/contty.h
new file mode 100644 (file)
index 0000000..fcc6e37
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef CONTTY_H_
+#define CONTTY_H_
+
+enum {
+       CON_CURSOR_LINE,
+       CON_CURSOR_BLOCK
+};
+
+enum {
+       BLACK,
+       BLUE,
+       GREEN,
+       CYAN,
+       RED,
+       MAGENTA,
+       BROWN,
+       LTGREY,
+       GREY,
+       LTBLUE,
+       LTGREEN,
+       LTCYAN,
+       LTRED,
+       LTMAGENTA,
+       YELLOW,
+       WHITE
+};
+#define BRIGHT         8
+#define FG_BRIGHT      0x08
+#define BG_BRIGHT      0x80
+
+enum {
+       G_DIAMOND       = 0x04,
+       G_CHECKER       = 0xb1,
+       G_LR_CORNER     = 0xd9,
+       G_UR_CORNER     = 0xbf,
+       G_UL_CORNER     = 0xda,
+       G_LL_CORNER     = 0xc0,
+       G_CROSS         = 0xc5,
+       G_HLINE         = 0xc4,
+       G_L_TEE         = 0xc3,
+       G_R_TEE         = 0xb4,
+       G_B_TEE         = 0xc1,
+       G_T_TEE         = 0xc2,
+       G_VLINE         = 0xb3,
+       G_CDOT          = 0xf8,
+
+       G_HDBL          = 0xcd,
+       G_UL_HDBL       = 0xd5,
+       G_UR_HDBL       = 0xb8,
+       G_T_HDBL_TEE = 0xd1
+};
+
+
+int con_init(void);
+void con_scr_enable(void);
+void con_scr_disable(void);
+void con_show_cursor(int show);
+void con_cursor(int x, int y);
+void con_curattr(int shape, int blink);
+void con_fgcolor(int c);
+void con_bgcolor(int c);
+void con_setattr(unsigned char attr);
+unsigned char con_getattr(void);
+void con_clear(void);
+void con_putchar(int c);
+
+void con_putchar_scr(int x, int y, int c);
+int con_printf(int x, int y, const char *fmt, ...);
+
+#endif /* CONTTY_H_ */
diff --git a/src/kern/desc.h b/src/kern/desc.h
new file mode 100644 (file)
index 0000000..d581f94
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef DESC_H_
+#define DESC_H_
+
+#include <inttypes.h>
+
+typedef struct {
+       uint16_t d[4];
+} desc_t;
+
+#endif /* DESC_H_ */
diff --git a/src/kern/intr.c b/src/kern/intr.c
new file mode 100644 (file)
index 0000000..616189e
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include "config.h"
+#include "intr.h"
+#include "desc.h"
+#include "segm.h"
+#include "asmops.h"
+#include "panic.h"
+
+#define SYSCALL_INT            0x80
+
+/* IDT gate descriptor bits */
+#define GATE_TASK              (5 << 8)
+#define GATE_INTR              (6 << 8)
+#define GATE_TRAP              (7 << 8)
+#define GATE_DEFAULT   (1 << 11)
+#define GATE_PRESENT   (1 << 15)
+
+/* PIC command and data ports */
+#define PIC1_CMD       0x20
+#define PIC1_DATA      0x21
+#define PIC2_CMD       0xa0
+#define PIC2_DATA      0xa1
+
+/* PIC initialization command word 1 bits */
+#define ICW1_ICW4_NEEDED       (1 << 0)
+#define ICW1_SINGLE                    (1 << 1)
+#define ICW1_INTERVAL4         (1 << 2)
+#define ICW1_LEVEL                     (1 << 3)
+#define ICW1_INIT                      (1 << 4)
+/* PIC initialization command word 4 bits */
+#define ICW4_8086                      (1 << 0)
+#define ICW4_AUTO_EOI          (1 << 1)
+#define ICW4_BUF_SLAVE         (1 << 3) /* 1000 */
+#define ICW4_BUF_MASTER                (3 << 2) /* 1100 */
+#define ICW4_SPECIAL           (1 << 4)
+
+/* PIC operation command word 2 bits */
+#define OCW2_EOI       (1 << 5)
+
+
+static void gate_desc(desc_t *desc, uint16_t sel, uint32_t addr, int dpl, int type);
+
+/* defined in intr_asm.S */
+void set_idt(uint32_t addr, uint16_t limit);
+void intr_entry_default(void);
+void irq7_entry_check_spurious(void);
+void irq15_entry_check_spurious(void);
+
+/* the IDT (interrupt descriptor table) */
+static desc_t idt[256] __attribute__((aligned(8)));
+
+/* table of handler functions for all interrupts */
+static intr_func_t intr_func[256];
+
+static struct intr_frame *cur_intr_frame;
+static int eoi_pending;
+
+#define INTR_ENTRY_EC(n, name)         \
+       void intr_entry_##name(void);   \
+       set_intr_entry(n, intr_entry_##name);
+#define INTR_ENTRY_NOEC(n, name)       INTR_ENTRY_EC(n, name)
+
+void init_intr(void)
+{
+       int i;
+
+       set_idt((uint32_t)idt, sizeof idt - 1);
+
+       /* initialize all entry points and interrupt handlers */
+       for(i=0; i<256; i++) {
+               set_intr_entry(i, intr_entry_default);
+               interrupt(i, 0);
+       }
+
+       /* by including intrtab.h here the series of INTR_ENTRY_* macros will be
+        * expanded to a series of function prototypes for all interrupt entry
+        * points and the corresponding calls to set_intr_entry to set up the IDT
+        * slots
+        */
+#include "intrtab.h"
+
+       /* change irq7 and irq15 to special entry points which first
+        * make sure we didn't get a spurious interrupt before proceeding
+        */
+       set_intr_entry(IRQ_TO_INTR(7), irq7_entry_check_spurious);
+       set_intr_entry(IRQ_TO_INTR(15), irq15_entry_check_spurious);
+
+       /* initialize the programmable interrupt controller
+        * setting up the maping of IRQs [0, 15] to interrupts [32, 47]
+        */
+       prog_pic(IRQ_OFFSET);
+       eoi_pending = 0;
+}
+
+void cleanup_intr(void)
+{
+       disable_intr();
+       /* reprogram the PIC to the default BIOS offset (8) */
+       prog_pic(8);
+}
+
+/* retrieve the current interrupt frame.
+ * returns 0 when called during init.
+ */
+struct intr_frame *get_intr_frame(void)
+{
+       return cur_intr_frame;
+}
+
+/* set an interrupt handler function for a particular interrupt */
+void interrupt(int intr_num, intr_func_t func)
+{
+       int iflag = get_intr_flag();
+       disable_intr();
+       intr_func[intr_num] = func;
+       set_intr_flag(iflag);
+}
+
+/* this function is called from all interrupt entry points
+ * it calls the appropriate interrupt handlers if available and handles
+ * sending an end-of-interrupt command to the PICs when finished.
+ */
+void dispatch_intr(struct intr_frame frm)
+{
+       cur_intr_frame = &frm;
+
+       if(IS_IRQ(frm.inum)) {
+               eoi_pending = frm.inum;
+       }
+
+       if(intr_func[frm.inum]) {
+               intr_func[frm.inum](frm.inum);
+       } else {
+               if(frm.inum < 32) {
+                       panic("unhandled exception %u, error code: %u, at cs:eip=%x:%x\n",
+                                       frm.inum, frm.err, frm.cs, frm.eip);
+               }
+               /*printf("unhandled interrupt %d\n", frm.inum);*/
+       }
+
+       disable_intr();
+       if(eoi_pending) {
+               end_of_irq(INTR_TO_IRQ(eoi_pending));
+       }
+}
+
+void prog_pic(int offs)
+{
+       /* send ICW1 saying we'll follow with ICW4 later on */
+       outp(PIC1_CMD, ICW1_INIT | ICW1_ICW4_NEEDED);
+       outp(PIC2_CMD, ICW1_INIT | ICW1_ICW4_NEEDED);
+       /* send ICW2 with IRQ remapping */
+       outp(PIC1_DATA, offs);
+       outp(PIC2_DATA, offs + 8);
+       /* send ICW3 to setup the master/slave relationship */
+       /* ... set bit3 = 3rd interrupt input has a slave */
+       outp(PIC1_DATA, 4);
+       /* ... set slave ID to 2 */
+       outp(PIC2_DATA, 2);
+       /* send ICW4 to set 8086 mode (no calls generated) */
+       outp(PIC1_DATA, ICW4_8086);
+       outp(PIC2_DATA, ICW4_8086);
+       /* done, just reset the data port to 0 */
+       outp(PIC1_DATA, 0);
+       outp(PIC2_DATA, 0);
+}
+
+static void gate_desc(desc_t *desc, uint16_t sel, uint32_t addr, int dpl, int type)
+{
+       /* first 16bit part is the low 16bits of the entry address */
+       desc->d[0] = addr & 0xffff;
+       /* second 16bit part is the segment selector for the entry code */
+       desc->d[1] = sel;
+       /* third 16bit part has the privilege level, type, and present bit */
+       desc->d[2] = ((dpl & 3) << 13) | type | GATE_DEFAULT | GATE_PRESENT;
+       /* last 16bit part is the high 16bits of the entry address */
+       desc->d[3] = (addr & 0xffff0000) >> 16;
+}
+
+#define IS_TRAP(n)     ((n) >= 32 && !IS_IRQ(n))
+void set_intr_entry(int num, void (*handler)(void))
+{
+       int type = IS_TRAP(num) ? GATE_TRAP : GATE_INTR;
+
+       /* the syscall interrupt has to have a dpl of 3 otherwise calling it from
+        * user space will raise a general protection exception. All the rest should
+        * have a dpl of 0 to disallow user programs to execute critical interrupt
+        * handlers and possibly crashing the system.
+        */
+       int dpl = (num == SYSCALL_INT) ? 3 : 0;
+
+       gate_desc(idt + num, selector(SEGM_CODE, 0), (uint32_t)handler, dpl, type);
+}
+
+void set_pic_mask(int pic, unsigned char mask)
+{
+       outp(pic > 0 ? PIC2_DATA : PIC1_DATA, mask);
+}
+
+unsigned char get_pic_mask(int pic)
+{
+       return inp(pic > 0 ? PIC2_DATA : PIC1_DATA);
+}
+
+void mask_irq(int irq)
+{
+       int port;
+       unsigned char mask;
+
+       if(irq < 8) {
+               port = PIC1_DATA;
+       } else {
+               port = PIC2_DATA;
+               irq -= 8;
+       }
+
+       mask = inp(port) | (1 << irq);
+       outp(port, mask);
+}
+
+void unmask_irq(int irq)
+{
+       int port;
+       unsigned char mask;
+
+       if(irq < 8) {
+               port = PIC1_DATA;
+       } else {
+               port = PIC2_DATA;
+               irq -= 8;
+       }
+
+       mask = inp(port) & ~(1 << irq);
+       outp(port, mask);
+}
+
+
+void end_of_irq(int irq)
+{
+       int intr_state = get_intr_flag();
+       disable_intr();
+
+       if(!eoi_pending) {
+               set_intr_flag(intr_state);
+               return;
+       }
+       eoi_pending = 0;
+
+       if(irq > 7) {
+               outp(PIC2_CMD, OCW2_EOI);
+       }
+       outp(PIC1_CMD, OCW2_EOI);
+
+       set_intr_flag(intr_state);
+}
+
+#ifdef ENABLE_GDB_STUB
+void exceptionHandler(int id, void (*func)())
+{
+       set_intr_entry(id, func);
+}
+#endif
diff --git a/src/kern/intr.h b/src/kern/intr.h
new file mode 100644 (file)
index 0000000..3cfbe8b
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef INTR_H_
+#define INTR_H_
+
+#include <inttypes.h>
+#include "asmops.h"
+
+/* offset used to remap IRQ numbers (+32) */
+#define IRQ_OFFSET             32
+/* conversion macros between IRQ and interrupt numbers */
+#define IRQ_TO_INTR(x) ((x) + IRQ_OFFSET)
+#define INTR_TO_IRQ(x) ((x) - IRQ_OFFSET)
+/* checks whether a particular interrupt is an remapped IRQ */
+#define IS_IRQ(n)      ((n) >= IRQ_OFFSET && (n) < IRQ_OFFSET + 16)
+
+/* general purpose registers as they are pushed by pusha */
+struct registers {
+       uint32_t edi, esi, ebp, esp;
+       uint32_t ebx, edx, ecx, eax;
+} __attribute__ ((packed));
+
+/* structure used to pass the interrupt stack frame from the
+ * entry points to the C dispatch function.
+ */
+struct intr_frame {
+       /* registers pushed by pusha in intr_entry_* */
+       struct registers regs;
+       /* data segment selectors */
+       /* XXX removed: not needed unless we do dpl3 transitions
+       uint32_t ds, es, fs, gs;
+       */
+       /* interrupt number and error code pushed in intr_entry_* */
+       uint32_t inum, err;
+       /* pushed by CPU during interrupt entry */
+       uint32_t eip, cs, eflags;
+       /* pushed by CPU during interrupt entry from user space */
+       /* XXX removed: again, not doing user space currently
+       uint32_t esp, ss;
+       */
+} __attribute__ ((packed));
+
+
+
+typedef void (*intr_func_t)(int);
+
+
+void init_intr(void);
+void cleanup_intr(void);
+
+struct intr_frame *get_intr_frame(void);
+
+/* install high level interrupt callback */
+void interrupt(int intr_num, intr_func_t func);
+
+/* install low-level interrupt vector in IDT
+ * must be able to handle EOI and return with iret
+ */
+void set_intr_entry(int num, void (*handler)(void));
+
+void prog_pic(int offs);
+void set_pic_mask(int pic, unsigned char mask);
+unsigned char get_pic_mask(int pic);
+void mask_irq(int irq);
+void unmask_irq(int irq);
+
+/* defined in intr_asm.S */
+int get_intr_flag(void);
+void set_intr_flag(int onoff);
+
+void intr_ret(struct intr_frame ifrm);
+
+void end_of_irq(int irq);
+
+#endif /* INTR_H_ */
diff --git a/src/kern/intr_s.asm b/src/kern/intr_s.asm
new file mode 100644 (file)
index 0000000..58c31f6
--- /dev/null
@@ -0,0 +1,161 @@
+; pcboot - bootable PC demo/game kernel
+; Copyright (C) 2018-2023  John Tsiombikas <nuclear@member.fsf.org>
+; 
+; This program is free software: you can redistribute it and/or modify
+; it under the terms of the GNU General Public License as published by
+; the Free Software Foundation, either version 3 of the License, or
+; (at your option) any later version.
+; 
+; This program is distributed in the hope that it will be useful,
+; but WITHOUT ANY WARRANTY, without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+; GNU General Public License for more details.
+; 
+; You should have received a copy of the GNU General Public License
+; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+       section .data
+       align 4
+       dw 0
+idtr_desc:
+lim:   dw 0
+addr:  dd 0
+
+       section .text
+; void set_idt(uint32_t addr, uint16_t limit)
+       global set_idt
+set_idt:
+       mov eax, [esp + 4]
+       mov [addr], eax
+       mov ax, [esp + 8]
+       mov [lim], ax
+       lidt [idtr_desc]
+       ret
+
+; int get_intr_flag()
+       global get_intr_flag
+get_intr_flag:
+       pushf
+       pop eax
+       ; bit 9 of eflags is IF
+       shr eax, 9
+       and eax, 1
+       ret
+
+; void set_intr_flag(int onoff)
+       global set_intr_flag
+set_intr_flag:
+       cmp dword [esp + 4], 0
+       jz .off
+       sti
+       ret
+.off:  cli
+       ret
+
+; interrupt entry with error code macro
+; this macro generates an interrupt entry point for the
+; exceptions which include error codes in the stack frame
+%macro ientry_err 2
+       global intr_entry_%2
+intr_entry_%2:
+       push dword %1
+       jmp intr_entry_common
+%endmacro
+
+; interrupt entry without error code macro
+; this macro generates an interrupt entry point for the interrupts
+; and exceptions which do not include error codes in the stack frame
+; it pushes a dummy error code (0), to make the stack frame identical
+%macro ientry_noerr 2
+       global intr_entry_%2
+intr_entry_%2:
+       push dword 0
+       push dword %1
+       jmp intr_entry_common
+%endmacro
+
+; common code used by all entry points. calls dispatch_intr()
+; defined in intr.c
+       extern dispatch_intr
+intr_entry_common:
+       ; save general purpose registers
+       pusha
+       call dispatch_intr
+intr_ret_local:
+       ; restore general purpose registers
+       popa
+       ; remove error code and intr num from stack
+       add esp, 8
+       iret
+
+; special case for the timer interrupt, to avoid all the overhead of
+; going through the C interrupt dispatcher 250 times each second
+;      extern nticks
+;      global intr_entry_fast_timer
+;intr_entry_fast_timer:
+;      inc dword [nticks]
+;      ; signal end of interrupt
+;      push eax
+;      mov al, 20h
+;      out 20h, al
+;      pop eax
+;      iret
+
+; special case for IRQ 7 and IRQ 15, to catch spurious interrupts
+PIC1_CMD equ 0x20
+PIC2_CMD equ 0xa0
+OCW2_EOI equ 0x20
+OCW3_ISR equ 0x0b
+
+       global irq7_entry_check_spurious
+irq7_entry_check_spurious:
+       push eax
+       mov al, OCW3_ISR
+       out PIC1_CMD, al
+       in al, PIC1_CMD
+       and al, 80h
+       pop eax
+       jnz intr_entry_irq7
+       iret
+
+       global irq15_entry_check_spurious
+irq15_entry_check_spurious:
+       push eax
+       mov al, OCW3_ISR
+       out PIC2_CMD, al
+       in al, PIC2_CMD
+       and al, 80h
+       jnz .legit
+       ; it was spurious, send EOI to master PIC and iret
+       mov al, OCW2_EOI
+       out PIC1_CMD, al
+       pop eax
+       iret
+.legit:        pop eax
+       jmp intr_entry_irq15
+
+
+; XXX not necessary for now, just leaving it in in case it's useful
+; down the road.
+;
+; intr_ret is called by context_switch to return from the kernel
+; to userspace. The argument is a properly formed intr_frame
+; structure with the saved context of the new task.
+;
+; First thing to do is remove the return address pointing back
+; to context_switch, which then leaves us with a proper interrupt
+; stack frame, so we can jump right in the middle of the regular
+; interrupt return code above.
+       global intr_ret
+intr_ret:
+       add esp, $4
+       jmp intr_ret_local
+
+; by including interrupts.h with ASM defined, the macros above
+; are expanded to generate all required interrupt entry points
+%define INTR_ENTRY_EC(n, name)         ientry_err n, name
+%define INTR_ENTRY_NOEC(n, name)       ientry_noerr n, name
+
+%include 'intrtab.h'
+
+; vi:set ts=8 sts=8 sw=8 ft=nasm:
diff --git a/src/kern/intrtab.h b/src/kern/intrtab.h
new file mode 100644 (file)
index 0000000..34af6fa
--- /dev/null
@@ -0,0 +1,38 @@
+INTR_ENTRY_NOEC(0, div)
+INTR_ENTRY_NOEC(1, debug)
+INTR_ENTRY_NOEC(2, nmi)
+INTR_ENTRY_NOEC(3, bp)
+INTR_ENTRY_NOEC(4, overflow)
+INTR_ENTRY_NOEC(5, bound)
+INTR_ENTRY_NOEC(6, ill)
+INTR_ENTRY_NOEC(7, nodev)
+INTR_ENTRY_EC(8, dfault)
+INTR_ENTRY_NOEC(9, copseg)
+INTR_ENTRY_EC(10, tss)
+INTR_ENTRY_EC(11, segpres)
+INTR_ENTRY_EC(12, stack)
+INTR_ENTRY_EC(13, prot)
+INTR_ENTRY_EC(14, page)
+INTR_ENTRY_NOEC(15, reserved)
+INTR_ENTRY_NOEC(16, fpu)
+INTR_ENTRY_EC(17, align)
+INTR_ENTRY_NOEC(18, mce)
+INTR_ENTRY_NOEC(19, sse)
+INTR_ENTRY_NOEC(32, irq0)
+INTR_ENTRY_NOEC(33, irq1)
+INTR_ENTRY_NOEC(34, irq2)
+INTR_ENTRY_NOEC(35, irq3)
+INTR_ENTRY_NOEC(36, irq4)
+INTR_ENTRY_NOEC(37, irq5)
+INTR_ENTRY_NOEC(38, irq6)
+INTR_ENTRY_NOEC(39, irq7)
+INTR_ENTRY_NOEC(40, irq8)
+INTR_ENTRY_NOEC(41, irq9)
+INTR_ENTRY_NOEC(42, irq10)
+INTR_ENTRY_NOEC(43, irq11)
+INTR_ENTRY_NOEC(44, irq12)
+INTR_ENTRY_NOEC(45, irq13)
+INTR_ENTRY_NOEC(46, irq14)
+INTR_ENTRY_NOEC(47, irq15)
+INTR_ENTRY_NOEC(128, syscall)
+INTR_ENTRY_NOEC(255, default)
diff --git a/src/kern/kbregs.h b/src/kern/kbregs.h
new file mode 100644 (file)
index 0000000..813fa1a
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef KBREGS_H_
+#define KBREGS_H_
+
+#define KB_IRQ         1
+#define PSAUX_IRQ      12
+
+#define KB_DATA_PORT   0x60
+#define KB_CMD_PORT            0x64
+#define KB_STATUS_PORT 0x64
+
+#define KB_ACK                 0xfa
+#define KB_NACK                        0xfe
+#define KB_TEST_PASSED 0x55
+#define KB_TEST_FAILED 0xfc
+
+#define KB_STAT_OUTBUF_FULL    0x01
+#define KB_STAT_INBUF_FULL     0x02
+#define KB_STAT_SYSFLAG                0x04
+#define KB_STAT_CMD                    0x08
+#define KB_STAT_ACTIVE         0x10
+#define KB_STAT_AUX                    0x20
+#define KB_STAT_TIMEOUT                0x40
+#define KB_STAT_PAR_ERROR      0x80
+
+#define KB_CMD_TEST                            0xaa
+
+/* keyboard commands */
+#define KB_CMD_GET_CMDBYTE             0x20
+#define KB_CMD_SET_CMDBYTE             0x60
+#define KB_CMD_AUX_ENABLE              0xa8
+#define KB_CMD_READ_OUTPORT            0xd0
+#define KB_CMD_WRITE_OUTPORT   0xd1
+#define KB_CMD_PSAUX                   0xd4
+#define KB_CMD_PULSE_RESET             0xfe
+
+/* controller command byte bits */
+#define KB_CCB_KB_INTREN               0x01
+#define KB_CCB_AUX_INTREN              0x02
+#define KB_CCB_SYSFLAG                 0x04
+#define KB_CCB_KB_DISABLE              0x10
+#define KB_CCB_AUX_DISABLE             0x20
+#define KB_CCB_KB_XLAT                 0x40
+
+/* psaux commands (use with d4 prefix) */
+#define AUX_CMD_STREAM_MODE            0xea
+#define AUX_CMD_READ_MOUSE             0xeb
+#define AUX_CMD_REMOTE_MODE            0xf0
+#define AUX_CMD_ENABLE                 0xf4
+#define AUX_CMD_DEFAULTS               0xf6
+
+#define AUX_PKT0_LEFTBN                        0x01
+#define AUX_PKT0_RIGHTBN               0x02
+#define AUX_PKT0_MIDDLEBN              0x04
+#define AUX_PKT0_ALWAYS1               0x08
+#define AUX_PKT0_XSIGN                 0x10
+#define AUX_PKT0_YSIGN                 0x20
+#define AUX_PKT0_XOVF                  0x40
+#define AUX_PKT0_YOVF                  0x80
+
+#define AUX_PKT0_OVF_BITS              (AUX_PKT0_XOVF | AUX_PKT0_YOVF)
+#define AUX_PKT0_BUTTON_BITS \
+       (AUX_PKT0_LEFTBN | AUX_PKT0_RIGHTBN | AUX_PKT0_MIDDLEBN)
+
+#endif /* KBREGS_H_ */
diff --git a/src/kern/kbscan.h b/src/kern/kbscan.h
new file mode 100644 (file)
index 0000000..0c1cdee
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef KBSCAN_H_
+#define KBSCAN_H_
+
+/* table with rough translations from set 1 scancodes to ASCII-ish */
+static int scantbl_set1[] = {
+       0, KB_ESC, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',            /* 0 - e */
+       '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',                 /* f - 1c */
+       KB_LCTRL, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',                          /* 1d - 29 */
+       KB_LSHIFT, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', KB_RSHIFT,                   /* 2a - 36 */
+       KB_NUM_MUL, KB_LALT, ' ', KB_CAPSLK, KB_F1, KB_F2, KB_F3, KB_F4, KB_F5, KB_F6, KB_F7, KB_F8, KB_F9, KB_F10,                     /* 37 - 44 */
+       KB_NUMLK, KB_SCRLK, KB_NUM_7, KB_NUM_8, KB_NUM_9, KB_NUM_MINUS, KB_NUM_4, KB_NUM_5, KB_NUM_6, KB_NUM_PLUS,      /* 45 - 4e */
+       KB_NUM_1, KB_NUM_2, KB_NUM_3, KB_NUM_0, KB_NUM_DOT, KB_SYSRQ, 0, 0, KB_F11, KB_F12,                                             /* 4d - 58 */
+#if 0
+       0, 0, 0, 0, 0, 0, 0,                                                                                                                    /* 59 - 5f */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,                                                                 /* 60 - 6f */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0                                                                  /* 70 - 7f */
+#endif
+};
+
+/* extended scancodes, after the 0xe0 prefix */
+#if 0
+static int scantbl_set1_ext[] = {
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,                 /* 0 - f */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\r', KB_RCTRL, 0, 0,                       /* 10 - 1f */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,                 /* 20 - 2f */
+       0, 0, 0, 0, 0, KB_NUM_MINUS, 0, KB_SYSRQ, KB_RALT, 0, 0, 0, 0, 0, 0, 0,                 /* 30 - 3f */
+       0, 0, 0, 0, 0, 0, 0, KB_HOME, KB_UP, KB_PGUP, 0, KB_LEFT, 0, KB_RIGHT, 0, KB_END,       /* 40 - 4f */
+       KB_DOWN, KB_PGDN, KB_INSERT, KB_DEL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,                        /* 50 - 5f */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,                 /* 60 - 6f */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,                 /* 70 - 7f */
+};
+#endif
+
+
+#endif /* KBSCAN_H_ */
diff --git a/src/kern/keyb.c b/src/kern/keyb.c
new file mode 100644 (file)
index 0000000..ea32945
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include "keyb.h"
+#include "intr.h"
+#include "asmops.h"
+#include "kbregs.h"
+#include "kbscan.h"
+#include "power.h"
+#include "panic.h"
+
+#define delay7us() \
+       do { \
+               iodelay(); iodelay(); iodelay(); iodelay(); \
+               iodelay(); iodelay(); iodelay(); \
+       } while(0)
+
+static void set_ccb(unsigned char ccb);
+static unsigned char get_ccb(void);
+
+static void kbintr();
+
+#define BUFSZ  64
+#define ADVANCE(x)     ((x) = ((x) + 1) & (BUFSZ - 1))
+
+static int buffer[BUFSZ];
+static int buf_ridx, buf_widx;
+
+static unsigned int num_pressed;
+static unsigned char keystate[256];
+
+
+void kb_init(void)
+{
+       buf_ridx = buf_widx = 0;
+       num_pressed = 0;
+       memset(keystate, 0, sizeof keystate);
+
+       /* make sure set1 translation is enabled */
+       kb_set_translate(1);
+
+       interrupt(IRQ_TO_INTR(KB_IRQ), kbintr);
+       kb_intr_enable();
+       unmask_irq(KB_IRQ);
+}
+
+void kb_intr_enable(void)
+{
+       unsigned char ccb = get_ccb();
+       ccb |= KB_CCB_KB_INTREN;
+       set_ccb(ccb);
+}
+
+void kb_intr_disable(void)
+{
+       unsigned char ccb = get_ccb();
+       ccb &= ~KB_CCB_KB_INTREN;
+       set_ccb(ccb);
+}
+
+int kb_setmode(int mode)
+{
+       kb_send_data(0xf0);
+       if(!kb_wait_read() || kb_read_data() != KB_ACK) {
+               return -1;
+       }
+       kb_send_data(mode);
+       if(!kb_wait_read() || kb_read_data() != KB_ACK) {
+               return -1;
+       }
+       return 0;
+}
+
+int kb_getmode(void)
+{
+       int mode;
+
+       kb_send_data(0xf0);
+       if(!kb_wait_read() || kb_read_data() != KB_ACK) {
+               return -1;
+       }
+       kb_send_data(0);
+       if(!kb_wait_read() || kb_read_data() != KB_ACK) {
+               return -1;
+       }
+       mode = kb_read_data();
+
+       switch(mode) {
+       case 0x43: return 1;
+       case 0x41: return 2;
+       case 0x3f: return 3;
+       default:
+               break;
+       }
+       return mode;
+}
+
+void kb_set_translate(int xlat)
+{
+       unsigned char ccb = get_ccb();
+       if(xlat) {
+               ccb |= KB_CCB_KB_XLAT;
+       } else {
+               ccb &= ~KB_CCB_KB_XLAT;
+       }
+       set_ccb(ccb);
+}
+
+int kb_get_translate(void)
+{
+       return get_ccb() & KB_CCB_KB_XLAT;
+}
+
+int kb_isdown(int key)
+{
+       switch(key) {
+       case KB_ANY:
+               return num_pressed;
+
+       case KB_ALT:
+               return keystate[KB_LALT] + keystate[KB_RALT];
+
+       case KB_CTRL:
+               return keystate[KB_LCTRL] + keystate[KB_RCTRL];
+       }
+       return keystate[key];
+}
+
+void kb_wait(void)
+{
+       int key;
+       while((key = kb_getkey()) == -1) {
+               /* put the processor to sleep while waiting for keypresses, but first
+                * make sure interrupts are enabled, or we'll sleep forever
+                */
+               enable_intr();
+               halt_cpu();
+       }
+       kb_putback(key);
+}
+
+int kb_getkey(void)
+{
+       int res;
+
+       if(buf_ridx == buf_widx) {
+               return -1;
+       }
+       res = buffer[buf_ridx];
+       ADVANCE(buf_ridx);
+       return res;
+}
+
+void kb_putback(int key)
+{
+       /* go back a place */
+       if(--buf_ridx < 0) {
+               buf_ridx += BUFSZ;
+       }
+
+       /* if the write end hasn't caught up with us, go back one place
+        * and put it there, otherwise just overwrite the oldest key which
+        * is right where we were.
+        */
+       if(buf_ridx == buf_widx) {
+               ADVANCE(buf_ridx);
+       }
+
+       buffer[buf_ridx] = key;
+}
+
+int kb_wait_write(void)
+{
+       int i;
+       for(i=0; i<32768; i++) {
+               if(!(inp(KB_STATUS_PORT) & KB_STAT_INBUF_FULL)) {
+                       return 1;
+               }
+               iodelay();
+       }
+       /*printf("kb_wait_write timeout\n");*/
+       return 0;
+}
+
+int kb_wait_read(void)
+{
+       int i;
+       for(i=0; i<32768; i++) {
+               if((inp(KB_STATUS_PORT) & KB_STAT_OUTBUF_FULL)) {
+                       return 1;
+               }
+               iodelay();
+       }
+       /*printf("kb_wait_read timeout\n");*/
+       return 0;
+}
+
+void kb_send_cmd(unsigned char cmd)
+{
+       kb_wait_write();
+       outp(KB_CMD_PORT, cmd);
+}
+
+void kb_send_data(unsigned char data)
+{
+       kb_wait_write();
+       outp(KB_DATA_PORT, data);
+}
+
+unsigned char kb_read_data(void)
+{
+       kb_wait_read();
+       delay7us();
+       return inp(KB_DATA_PORT);
+}
+
+static void set_ccb(unsigned char ccb)
+{
+       kb_send_cmd(KB_CMD_SET_CMDBYTE);
+       kb_send_data(ccb);
+
+       if(kb_wait_read()) {
+               kb_read_data();
+       }
+}
+
+static unsigned char get_ccb(void)
+{
+       kb_send_cmd(KB_CMD_GET_CMDBYTE);
+       return kb_read_data();
+}
+
+static void kbintr()
+{
+       unsigned char code;
+       int key, press;
+       static int ext = 0;
+
+       code = inp(KB_DATA_PORT);
+
+       if(code == 0xe0) {
+               ext = 1;
+               return;
+       }
+
+       if(code & 0x80) {
+               press = 0;
+               code &= 0x7f;
+
+               if(num_pressed > 0) {
+                       num_pressed--;
+               }
+       } else {
+               press = 1;
+
+               num_pressed++;
+       }
+
+       if(!ext) {
+               key = code < 0x59 ? scantbl_set1[code] : 0;
+       } else {
+               switch(code) {
+               case 0x1c: key = '\r'; break;
+               case 0x1d: key = KB_RCTRL; break;
+               //case 0x35: key = KB_NUM_MINUS; break;
+               //case 0x37: key = KB_SYSRQ; break;
+               case 0x38: key = KB_RALT; break;
+               case 0x47: key = KB_HOME; break;
+               case 0x48: key = KB_UP; break;
+               case 0x49: key = KB_PGUP; break;
+               case 0x4b: key = KB_LEFT; break;
+               case 0x4d: key = KB_RIGHT; break;
+               case 0x4f: key = KB_END; break;
+               case 0x50: key = KB_DOWN; break;
+               case 0x51: key = KB_PGDN; break;
+               case 0x52: key = KB_INSERT; break;
+               case 0x53: key = KB_DEL; break;
+               default:
+                       key = 0;
+               }
+               ext = 0;
+       }
+
+       if(press) {
+               if(key == KB_DEL && (keystate[KB_LALT] || keystate[KB_RALT]) && (keystate[KB_LCTRL] || keystate[KB_RCTRL])) {
+                       reboot();
+               }
+               /* append to buffer */
+               buffer[buf_widx] = key;
+               ADVANCE(buf_widx);
+               /* if the write end overtook the read end, advance the read end
+                * too, to discard the oldest keypress from the buffer
+                */
+               if(buf_widx == buf_ridx) {
+                       ADVANCE(buf_ridx);
+               }
+       }
+
+       /* and update keystate table */
+       keystate[key] = press;
+}
diff --git a/src/kern/keyb.h b/src/kern/keyb.h
new file mode 100644 (file)
index 0000000..dc92f62
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef KEYB_H_
+#define KEYB_H_
+
+#define KB_ANY         (-1)
+#define KB_ALT         (-2)
+#define KB_CTRL                (-3)
+#define KB_SHIFT       (-4)
+
+/* special keys */
+enum {
+       KB_ESC = 27,
+       KB_BACKSP = 127,
+
+       KB_INSERT, KB_DEL, KB_HOME, KB_END, KB_PGUP, KB_PGDN,
+       KB_LEFT, KB_RIGHT, KB_UP, KB_DOWN,
+       KB_NUM_DOT, KB_NUM_ENTER, KB_NUM_PLUS, KB_NUM_MINUS, KB_NUM_MUL, KB_NUM_DIV,
+       KB_NUM_0, KB_NUM_1, KB_NUM_2, KB_NUM_3, KB_NUM_4,
+       KB_NUM_5, KB_NUM_6, KB_NUM_7, KB_NUM_8, KB_NUM_9,
+
+       KB_LALT, KB_RALT,
+       KB_LCTRL, KB_RCTRL,
+       KB_LSHIFT, KB_RSHIFT,
+       KB_F1, KB_F2, KB_F3, KB_F4, KB_F5, KB_F6,
+       KB_F7, KB_F8, KB_F9, KB_F10, KB_F11, KB_F12,
+       KB_CAPSLK, KB_NUMLK, KB_SCRLK, KB_SYSRQ
+};
+
+void kb_init(void);
+
+void kb_intr_enable(void);
+void kb_intr_disable(void);
+
+int kb_setmode(int mode);
+int kb_getmode(void);
+
+void kb_set_translate(int xlat);
+int kb_get_translate(void);
+
+/* Boolean predicate for testing the current state of a particular key.
+ * You may also pass KB_ANY to test if any key is held down.
+ */
+int kb_isdown(int key);
+
+/* waits for any keypress */
+void kb_wait(void);
+
+/* removes and returns a single key from the input buffer. */
+int kb_getkey(void);
+
+void kb_putback(int key);
+
+/* returns 1 if the keyboard controller is ready to read/write
+ * returns 0 if the wait times out
+ */
+int kb_wait_write(void);
+int kb_wait_read(void);
+
+void kb_send_cmd(unsigned char cmd);
+void kb_send_data(unsigned char data);
+unsigned char kb_read_data(void);
+
+#endif /* KEYB_H_ */
diff --git a/src/kern/main.c b/src/kern/main.c
new file mode 100644 (file)
index 0000000..6cf5de3
--- /dev/null
@@ -0,0 +1,63 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "segm.h"
+#include "intr.h"
+#include "mem.h"
+#include "contty.h"
+#include "keyb.h"
+#include "psaux.h"
+#include "timer.h"
+#include "game.h"
+
+unsigned char *framebuf, *vmem = (unsigned char*)0xa0000;
+
+static int quit;
+
+
+int main(void)
+{
+       int key;
+
+       init_segm();
+       init_intr();
+       con_init();
+       kb_init();
+       init_psaux();
+       init_mem();
+       init_timer();
+       enable_intr();
+
+       if(!(framebuf = malloc(320 * 200))) {
+               printf("failed to allocate framebuffer\n");
+               goto end;
+       }
+
+       if(game_init() == -1) {
+               goto end;
+       }
+
+       for(;;) {
+               if((key = kb_getkey()) >= 0) {
+                       game_keyboard(key, 1);
+               }
+               if(quit) break;
+               game_draw();
+       }
+       game_shutdown();
+
+end:
+       cleanup_intr(); /* also disables interrupts */
+       cleanup_timer();
+       return 0;
+}
+
+void game_quit(void)
+{
+       quit = 1;
+}
+
+void game_swap_buffers(void)
+{
+       memcpy(vmem, framebuf, 64000);
+}
diff --git a/src/kern/mem.c b/src/kern/mem.c
new file mode 100644 (file)
index 0000000..a013607
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include "config.h"
+#include "panic.h"
+#include "mem.h"
+#include "intr.h"
+
+#define FREE           0
+#define USED           1
+
+#define BM_IDX(pg)     ((pg) / 32)
+#define BM_BIT(pg)     ((pg) & 0x1f)
+
+#define IS_FREE(pg) ((bitmap[BM_IDX(pg)] & (1 << BM_BIT(pg))) == 0)
+
+#define MEM_START      ((uint32_t)&_mem_start)
+
+
+struct mem_range {
+       uint32_t start;
+       uint32_t size;
+};
+
+void move_stack(uint32_t newaddr);     /* defined in startup.s */
+
+static void mark_page(int pg, int used);
+static void add_memory(uint32_t start, size_t size);
+
+#define MAX_MAP_SIZE   16
+extern struct mem_range boot_mem_map[MAX_MAP_SIZE];
+extern int boot_mem_map_size;
+
+/* linker supplied symbol, points to the end of the kernel image */
+extern uint32_t _mem_start;
+
+
+/* A bitmap is used to track which physical memory pages are used, and which
+ * are available for allocation by alloc_ppage.
+ *
+ * last_alloc_idx keeps track of the last 32bit element in the bitmap array
+ * where a free page was found. It's guaranteed that all the elements before
+ * this have no free pages, but it doesn't imply that there will be another
+ * free page there. So it's used as a starting point for the search.
+ */
+static uint32_t *bitmap;
+static int bmsize, last_alloc_idx;
+
+
+
+void init_mem(void)
+{
+       int i, pg, max_used_pg, end_pg = 0;
+       uint32_t used_end, start, end, sz, total = 0, rem;
+       const char *suffix[] = {"bytes", "KB", "MB", "GB"};
+
+       last_alloc_idx = 0;
+
+       /* the allocation bitmap starts right at the end of the kernel image */
+       bitmap = (uint32_t*)&_mem_start;
+
+       /* start by marking all posible pages (2**20) as used. We do not "reserve"
+        * all this space. Pages beyond the end of the useful bitmap area
+        * ((char*)bitmap + bmsize), which will be determined after we traverse the
+        * memory map, are going to be marked as available for allocation.
+        */
+       memset(bitmap, 0xff, 1024 * 1024 / 8);
+
+
+       if(boot_mem_map_size <= 0 || boot_mem_map_size > MAX_MAP_SIZE) {
+               panic("invalid memory map size reported by the boot loader: %d\n", boot_mem_map_size);
+       }
+
+       printf("Memory map:\n");
+       for(i=0; i<boot_mem_map_size; i++) {
+               printf(" start: %08x - size: %08x\n", (unsigned int)boot_mem_map[i].start,
+                               (unsigned int)boot_mem_map[i].size);
+
+               start = boot_mem_map[i].start;
+               sz = boot_mem_map[i].size & 0xfffffffc;
+               end = start + sz;
+               if(end < sz) {
+                       end = 0xfffffffc;
+                       sz = end - start;
+               }
+
+               /* ignore any memory ranges which end before the end of the kernel image */
+               if(end < MEM_START) continue;
+               if(start < MEM_START) {
+                       start = MEM_START;
+               }
+
+               add_memory(start, sz);
+               total += sz;
+
+               pg = ADDR_TO_PAGE(end);
+               if(end_pg < pg) {
+                       end_pg = pg;
+               }
+       }
+
+       i = 0;
+       while(total > 1024) {
+               rem = total & 0x3ff;
+               total >>= 10;
+               ++i;
+       }
+       printf("Total usable RAM: %u.%u %s\n", total, 100 * rem / 1024, suffix[i]);
+
+       /* size of the useful part of the bitmap in bytes padded to 4-byte
+        * boundaries to allow 32bit at a time operations.
+        */
+       bmsize = (end_pg / 32) * 4;
+
+       /* mark all pages occupied by the bitmap as used */
+       used_end = (uint32_t)bitmap + bmsize - 1;
+
+       max_used_pg = ADDR_TO_PAGE(used_end);
+       printf("marking pages up to %x (page: %d) as used\n", used_end, max_used_pg);
+       for(i=0; i<=max_used_pg; i++) {
+               mark_page(i, USED);
+       }
+
+#ifdef MOVE_STACK_RAMTOP
+       /* allocate space for the stack at the top of RAM and move it there */
+       if((pg = alloc_ppages(STACK_PAGES, MEM_STACK)) != -1) {
+               printf("moving stack-top to: %x (%d pages)\n", PAGE_TO_ADDR(end_pg) - 4, STACK_PAGES);
+               move_stack(PAGE_TO_ADDR(pg + STACK_PAGES) - 4);
+       }
+#endif
+}
+
+int alloc_ppage(int area)
+{
+       return alloc_ppages(1, area);
+}
+
+/* free_ppage marks the physical page, free in the allocation bitmap.
+ *
+ * CAUTION: no checks are done that this page should actually be freed or not.
+ * If you call free_ppage with the address of some part of memory that was
+ * originally reserved due to it being in a memory hole or part of the kernel
+ * image or whatever, it will be subsequently allocatable by alloc_ppage.
+ */
+void free_ppage(int pg)
+{
+       int bmidx = BM_IDX(pg);
+
+       int intr_state = get_intr_flag();
+       disable_intr();
+
+       if(IS_FREE(pg)) {
+               panic("free_ppage(%d): I thought that was already free!\n", pg);
+       }
+
+       mark_page(pg, FREE);
+       if(bmidx < last_alloc_idx) {
+               last_alloc_idx = bmidx;
+       }
+
+       set_intr_flag(intr_state);
+}
+
+
+int alloc_ppages(int count, int area)
+{
+       int i, dir, pg, idx, max, intr_state, found_free = 0;
+
+       intr_state = get_intr_flag();
+       disable_intr();
+
+       if(area == MEM_STACK) {
+               idx = (bmsize - 1) / 4;
+               max = -1;
+               dir = -1;
+       } else {
+               idx = last_alloc_idx;
+               max = bmsize / 4;
+               dir = 1;
+       }
+
+       while(idx != max) {
+               /* if at least one bit is 0 then we have at least
+                * one free page. find it and try to allocate a range starting from there
+                */
+               if(bitmap[idx] != 0xffffffff) {
+                       pg = idx * 32;
+                       if(dir < 0) pg += 31;
+
+                       for(i=0; i<32; i++) {
+                               if(IS_FREE(pg)) {
+                                       if(!found_free && dir > 0) {
+                                               last_alloc_idx = idx;
+                                               found_free = 1;
+                                       }
+
+                                       if(alloc_ppage_range(pg, count) != -1) {
+                                               set_intr_flag(intr_state);
+                                               return pg;
+                                       }
+                               }
+                               pg += dir;
+                       }
+               }
+               idx += dir;
+       }
+
+       set_intr_flag(intr_state);
+       return -1;
+}
+
+void free_ppages(int pg0, int count)
+{
+       int i;
+
+       for(i=0; i<count; i++) {
+               free_ppage(pg0++);
+       }
+}
+
+int alloc_ppage_range(int start, int size)
+{
+       int i, pg = start;
+       int intr_state;
+
+       intr_state = get_intr_flag();
+       disable_intr();
+
+       /* first validate that no page in the requested range is allocated */
+       for(i=0; i<size; i++) {
+               if(!IS_FREE(pg)) {
+                       return -1;
+               }
+               ++pg;
+       }
+
+       /* all is well, mark them as used */
+       pg = start;
+       for(i=0; i<size; i++) {
+               mark_page(pg++, USED);
+       }
+
+       set_intr_flag(intr_state);
+       return 0;
+}
+
+int free_ppage_range(int start, int size)
+{
+       int i, pg = start;
+
+       for(i=0; i<size; i++) {
+               free_ppage(pg++);
+       }
+       return 0;
+}
+
+/* adds a range of physical memory to the available pool. used during init_mem
+ * when traversing the memory map.
+ */
+static void add_memory(uint32_t start, size_t sz)
+{
+       int i, szpg, pg;
+
+       szpg = ADDR_TO_PAGE(sz + 4095);
+       pg = ADDR_TO_PAGE(start);
+
+       for(i=0; i<szpg; i++) {
+               mark_page(pg++, FREE);
+       }
+}
+
+/* maps a page as used or free in the allocation bitmap */
+static void mark_page(int pg, int used)
+{
+       int idx = BM_IDX(pg);
+       int bit = BM_BIT(pg);
+
+       if(used) {
+               bitmap[idx] |= 1 << bit;
+       } else {
+               bitmap[idx] &= ~(1 << bit);
+       }
+}
+
+void print_page_bitmap(void)
+{
+       int i;
+
+       for(i=0; i<bmsize/4; i++) {
+               if((i & 3) == 0) {
+                       uint32_t pg = i * 32;
+                       uint32_t addr = PAGE_TO_ADDR(pg);
+                       printf("\n%5d [%08x]:", (int)pg, (unsigned long)addr);
+               }
+               printf(" %08x", bitmap[i]);
+       }
+       printf("\n");
+}
diff --git a/src/kern/mem.h b/src/kern/mem.h
new file mode 100644 (file)
index 0000000..40de366
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef MEM_H_
+#define MEM_H_
+
+#define ADDR_TO_PAGE(x)                        ((uint32_t)(x) >> 12)
+#define PAGE_TO_ADDR(x)                        ((uint32_t)(x) << 12)
+#define PAGE_TO_PTR(x)                 ((void*)PAGE_TO_ADDR(x))
+
+#define BYTES_TO_PAGES(x)              (((uint32_t)(x) + 4095) >> 12)
+
+/* for alloc_ppage/alloc_ppages */
+enum {
+       MEM_HEAP = 0,   /* start searching from the bottom */
+       MEM_STACK = 1   /* start searching from the top */
+};
+
+void init_mem(void);
+
+int alloc_ppage(int area);
+void free_ppage(int pg);
+
+/* allocate a number of consecutive pages */
+int alloc_ppages(int count, int area);
+void free_ppages(int pg0, int count);
+
+/* allocate a specific range of pages.
+ * Fails (and returns -1) if any page in the requested range is not free.
+ */
+int alloc_ppage_range(int start, int size);
+int free_ppage_range(int start, int size);
+
+#endif /* MEM_H_ */
diff --git a/src/kern/panic.c b/src/kern/panic.c
new file mode 100644 (file)
index 0000000..788900e
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2023 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include "intr.h"
+#include "asmops.h"
+
+struct all_registers {
+       uint32_t eax, ebx, ecx, edx;
+       uint32_t esp, ebp, esi, edi;
+       uint32_t eflags;
+       uint32_t cs, ss, ds, es, fs, gs;
+       uint32_t cr0, cr1, cr2, cr3;
+};
+
+/* defined in regs.S */
+void get_regs(struct all_registers *regs);
+
+void panic(const char *fmt, ...)
+{
+       va_list ap;
+       struct all_registers regs;
+       uint32_t eip;
+
+       disable_intr();
+
+       memset(&regs, 0, sizeof regs);
+       get_regs(&regs);
+
+       CALLER_EIP(eip);
+
+       printf("~~~~~ pcboot panic ~~~~~\n");
+       va_start(ap, fmt);
+       vprintf(fmt, ap);
+       va_end(ap);
+
+       printf("\nRegisters:\n");
+       printf("eax: %x, ebx: %x, ecx: %x, edx: %x\n", regs.eax, regs.ebx, regs.ecx, regs.edx);
+       printf("esp: %x, ebp: %x, esi: %x, edi: %x\n", regs.esp, regs.ebp, regs.esi, regs.edi);
+       printf("eip: %x, eflags: %x\n", eip, regs.eflags);
+       printf("cr0: %x, cr1: %x, cr2: %x, cr3: %x\n", regs.cr0, regs.cr1, regs.cr2, regs.cr3);
+       printf("cs: %x (%d|%d)\n", regs.cs, regs.cs >> 3, regs.cs & 3);
+       printf("ss: %x (%d|%d)\n", regs.ss, regs.ss >> 3, regs.ss & 3);
+       printf("ds: %x (%d|%d)\n", regs.ds, regs.ds >> 3, regs.ds & 3);
+       printf("es: %x (%d|%d)\n", regs.es, regs.es >> 3, regs.es & 3);
+       printf("fs: %x (%d|%d)\n", regs.fs, regs.fs >> 3, regs.fs & 3);
+       printf("gs: %x (%d|%d)\n", regs.gs, regs.gs >> 3, regs.gs & 3);
+
+       for(;;) halt_cpu();
+}
+
+void backtrace(void)
+{
+       int lvl = 0;
+       uint32_t *frmptr;
+       int ien = get_intr_flag();
+       disable_intr();
+       get_ebp(frmptr);
+       set_intr_flag(ien);
+
+       while(frmptr) {
+               printf("%d: %p\n", lvl++, (void*)frmptr[1]);
+               frmptr = (uint32_t*)*frmptr;
+       }
+}
diff --git a/src/kern/panic.h b/src/kern/panic.h
new file mode 100644 (file)
index 0000000..0785059
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef PANIC_H_
+#define PANIC_H_
+
+void panic(const char *fmt, ...) __attribute__((noreturn));
+void backtrace(void);
+
+#endif /* PANIC_H_ */
diff --git a/src/kern/power.c b/src/kern/power.c
new file mode 100644 (file)
index 0000000..8a677d0
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include "power.h"
+#include "keyb.h"
+#include "kbregs.h"
+#include "timer.h"
+#include "panic.h"
+#include "asmops.h"
+
+/* defined in intr_asm.S */
+void set_idt(uint32_t addr, uint16_t limit);
+
+void reboot(void)
+{
+       int i;
+
+       printf("reboot: keyboard controller\n");
+       kb_send_cmd(KB_CMD_PULSE_RESET);
+
+       for(i=0; i<32768; i++) {
+               iodelay();
+       }
+
+       printf("reboot: triple-fault\n");
+       set_idt(0, 0);
+       asm volatile("int $3");
+
+       panic("can't reboot!");
+}
diff --git a/src/kern/power.h b/src/kern/power.h
new file mode 100644 (file)
index 0000000..5e0c9fb
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef POWER_H_
+#define POWER_H_
+
+void reboot(void);
+
+#endif /* POWER_H_ */
diff --git a/src/kern/psaux.c b/src/kern/psaux.c
new file mode 100644 (file)
index 0000000..a0fb58d
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include "psaux.h"
+#include "intr.h"
+#include "asmops.h"
+#include "keyb.h"
+#include "kbregs.h"
+
+static void init_mouse(void);
+static void proc_mouse_data(unsigned char *data);
+static void psaux_intr();
+
+static int mx, my;
+static unsigned int bnstate;
+static int present;
+static int bounds[4];
+static int intr_mode;
+
+void init_psaux(void)
+{
+       interrupt(IRQ_TO_INTR(12), psaux_intr);
+       unmask_irq(12);
+
+       init_mouse();
+       set_mouse_bounds(0, 0, 319, 199);
+}
+
+static void init_mouse(void)
+{
+       unsigned char val;
+
+       kb_send_cmd(KB_CMD_AUX_ENABLE);
+       if(kb_wait_read()) {
+               val = kb_read_data();
+               printf("aux enable: %02x\n", (unsigned int)val);
+       }
+
+       /*
+       kb_send_cmd(KB_CMD_PSAUX);
+       kb_send_data(AUX_CMD_REMOTE_MODE);
+       val = kb_read_data();
+       */
+
+       kb_send_cmd(KB_CMD_GET_CMDBYTE);
+       val = kb_read_data();
+       val &= ~KB_CCB_AUX_DISABLE;
+       val |= KB_CCB_AUX_INTREN;
+       kb_send_cmd(KB_CMD_SET_CMDBYTE);
+       kb_send_data(val);
+
+       if(kb_wait_read()) {
+               val = kb_read_data();
+               printf("set cmdbyte: %02x\n", (unsigned int)val);
+       }
+       intr_mode = 1;
+
+       kb_send_cmd(KB_CMD_PSAUX);
+       kb_send_data(AUX_CMD_DEFAULTS);
+       val = kb_read_data();
+
+       kb_send_cmd(KB_CMD_PSAUX);
+       kb_send_data(AUX_CMD_ENABLE);
+       val = kb_read_data();
+
+       present = (val == KB_ACK) ? 1 : 0;
+       printf("init_mouse: %spresent\n", present ? "" : "not ");
+}
+
+int have_mouse(void)
+{
+       return present;
+}
+
+void set_mouse_bounds(int x0, int y0, int x1, int y1)
+{
+       bounds[0] = x0;
+       bounds[1] = y0;
+       bounds[2] = x1;
+       bounds[3] = y1;
+}
+
+unsigned int mouse_state(int *xp, int *yp)
+{
+       if(!intr_mode) {
+               poll_mouse();
+       }
+
+       *xp = mx;
+       *yp = my;
+       return bnstate;
+}
+
+/* TODO: poll_mouse doesn't work (enable remote mode and disable interrupt on init to test) */
+#define STAT_AUX_PENDING               (KB_STAT_OUTBUF_FULL | KB_STAT_AUX)
+static inline int aux_pending(void)
+{
+       return (inp(KB_STATUS_PORT) & STAT_AUX_PENDING) == STAT_AUX_PENDING ? 1 : 0;
+}
+
+void poll_mouse(void)
+{
+       static int poll_state;
+       static unsigned char pkt[3];
+       unsigned char rd;
+
+       ser_printf("poll_mouse(%d)\n", poll_state);
+
+       switch(poll_state) {
+       case 0: /* send read mouse command */
+               kb_send_cmd(KB_CMD_PSAUX);
+               kb_send_data(AUX_CMD_READ_MOUSE);
+               ++poll_state;
+               break;
+
+       case 1: /* wait for ACK */
+               if(kb_wait_read()) {// && aux_pending()) {
+                       if((rd = kb_read_data()) == KB_ACK) {
+                               ++poll_state;
+                       } else {
+                               ser_printf("poll_mouse state 1: expected ack: %02x\n", (unsigned int)rd);
+                       }
+               }
+               break;
+
+       case 2: /* read packet data */
+       case 3:
+       case 4:
+               if(kb_wait_read() && aux_pending()) {
+                       int i = poll_state++ - 2;
+                       pkt[i] = kb_read_data();
+               }
+               if(poll_state == 5) {
+                       ser_printf("proc_mouse_data(%02x %02x %02x)\n", (unsigned int)pkt[0],
+                                       (unsigned int)pkt[1], (unsigned int)pkt[2]);
+                       proc_mouse_data(pkt);
+                       poll_state = 0;
+               }
+               break;
+
+       default:
+               ser_printf("poll_mouse reached state: %d\n", poll_state);
+       }
+}
+
+static void proc_mouse_data(unsigned char *data)
+{
+       int dx, dy;
+
+       if(data[0] & AUX_PKT0_OVF_BITS) {
+               /* consensus seems to be that if overflow bits are set, something is
+                * fucked, and it's best to re-initialize the mouse
+                */
+               /*init_mouse();*/
+       } else {
+               bnstate = data[0] & AUX_PKT0_BUTTON_BITS;
+               dx = data[1];
+               dy = data[2];
+
+               if(data[0] & AUX_PKT0_XSIGN) {
+                       dx |= 0xffffff00;
+               }
+               if(data[0] & AUX_PKT0_YSIGN) {
+                       dy |= 0xffffff00;
+               }
+
+               mx += dx;
+               my -= dy;
+
+               if(mx < bounds[0]) mx = bounds[0];
+               if(mx > bounds[2]) mx = bounds[2];
+               if(my < bounds[1]) my = bounds[1];
+               if(my > bounds[3]) my = bounds[3];
+       }
+}
+
+static void psaux_intr()
+{
+       static unsigned char data[3];
+       static int idx;
+
+       if(!(inp(KB_STATUS_PORT) & KB_STAT_AUX)) {
+               /* no mouse data pending, ignore interrupt */
+               return;
+       }
+
+       data[idx] = kb_read_data();
+       if(++idx >= 3) {
+               idx = 0;
+
+               proc_mouse_data(data);
+       }
+}
diff --git a/src/kern/psaux.h b/src/kern/psaux.h
new file mode 100644 (file)
index 0000000..7acc3f6
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef PSAUX_H_
+#define PSAUX_H_
+
+#define MOUSE_LBN_BIT  1
+#define MOUSE_RBN_BIT  2
+#define MOUSE_MBN_BIT  4
+
+void init_psaux(void);
+
+int have_mouse(void);
+
+void set_mouse_bounds(int x0, int y0, int x1, int y1);
+unsigned int mouse_state(int *xp, int *yp);
+
+void poll_mouse(void);
+
+#endif /* PSAUX_H_ */
diff --git a/src/kern/regs.asm b/src/kern/regs.asm
new file mode 100644 (file)
index 0000000..5fb93d1
--- /dev/null
@@ -0,0 +1,57 @@
+       section .text
+       align 4
+
+       global get_regs
+get_regs:
+       push ebp
+       mov ebp, esp
+
+       push edx
+       mov edx, [ebp + 8]
+
+       mov [edx], eax
+       mov [edx + 4], ebx
+       mov [edx + 8], ecx
+       
+       ; juggle edx
+       mov eax, edx
+       pop edx
+       mov [eax + 12], edx
+       push edx
+       mov edx, eax
+       
+       ; those two are pointless in a function
+       mov [edx + 16], esp
+       mov [edx + 20], ebp
+
+       mov [edx + 24], esi
+       mov [edx + 28], edx
+
+       pushf
+       pop eax
+       mov [edx + 32], eax
+
+       mov [edx + 36], cs
+       mov [edx + 40], ss
+       mov [edx + 44], ds
+       mov [edx + 48], es
+       mov [edx + 52], fs
+       mov [edx + 56], gs
+
+       push ebx
+       mov ebx, cr0
+       mov [edx + 60], ebx
+       ;mov ebx, cr1
+       ;mov [edx + 64], ebx
+       mov ebx, cr2
+       mov [edx + 68], ebx
+       mov ebx, cr3
+       mov [edx + 72], ebx
+       pop ebx
+
+       pop edx
+       pop ebp
+       ret
+
+
+; vi:set ts=8 sts=8 sw=8 ft=nasm:
diff --git a/src/kern/segm.c b/src/kern/segm.c
new file mode 100644 (file)
index 0000000..2a461a6
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include "segm.h"
+#include "desc.h"
+#include "tss.h"
+
+/* bits for the 3rd 16bt part of the descriptor */
+#define BIT_ACCESSED   (1 << 8)
+#define BIT_WR                 (1 << 9)
+#define BIT_RD                 (1 << 9)
+#define BIT_EXP_DOWN   (1 << 10)
+#define BIT_CONFORMING (1 << 10)
+#define BIT_CODE               (1 << 11)
+#define BIT_NOSYS              (1 << 12)
+#define BIT_PRESENT            (1 << 15)
+/* TSS busy bit */
+#define BIT_BUSY               (1 << 9)
+
+/* bits for the last 16bit part of the descriptor */
+#define BIT_BIG                        (1 << 6)
+#define BIT_DEFAULT            (1 << 6)
+#define BIT_GRAN               (1 << 7)
+
+enum {TYPE_DATA, TYPE_CODE};
+
+/* we need the following bit pattern at the 8th bit excluding the busy bit: 1001 */
+#define TSS_TYPE_BITS  (9 << 8)
+
+static void segm_desc(desc_t *desc, uint32_t base, uint32_t limit, int dpl, int type);
+static void segm_desc16(desc_t *desc, uint32_t base, uint32_t limit, int dpl, int type);
+/*static void task_desc(desc_t *desc, uint32_t base, uint32_t limit, int dpl);*/
+
+/* these functions are implemented in segm-asm.S */
+void setup_selectors(uint16_t code, uint16_t data);
+void set_gdt(uint32_t addr, uint16_t limit);
+void set_task_reg(uint16_t tss_selector);
+
+/*
+static void dbg_print_gdt(void);
+static void print_desc(desc_t *desc);
+*/
+
+/* our global descriptor table */
+static desc_t gdt[NUM_SEGMENTS] __attribute__((aligned(8)));
+uint16_t orig_seg;
+
+void init_segm(void)
+{
+       asm volatile (
+               "mov %%fs, %0\n\t"
+               : "=a"(orig_seg));
+
+       memset(gdt, 0, sizeof gdt);
+       segm_desc(gdt + SEGM_CODE, 0, 0xffffffff, 0, TYPE_CODE);
+       segm_desc(gdt + SEGM_DATA, 0, 0xffffffff, 0, TYPE_DATA);
+       segm_desc(gdt + SEGM_XCODE, orig_seg << 4, 0xffffffff, 0, TYPE_CODE);
+       segm_desc(gdt + SEGM_XDATA, orig_seg << 4, 0xffffffff, 0, TYPE_DATA);
+       segm_desc16(gdt + SEGM_CODE16, orig_seg << 4, 0xffff, 0, TYPE_CODE);
+
+       set_gdt((uint32_t)gdt, sizeof gdt - 1);
+
+       setup_selectors(selector(SEGM_CODE, 0), selector(SEGM_DATA, 0));
+}
+
+/* constructs a GDT selector based on index and priviledge level */
+uint16_t selector(int idx, int rpl)
+{
+       return (idx << 3) | (rpl & 3);
+}
+
+/*
+void set_tss(uint32_t addr)
+{
+       task_desc(gdt + SEGM_TASK, addr, sizeof(struct task_state) - 1, 3);
+       set_task_reg(selector(SEGM_TASK, 0));
+}
+*/
+
+static void segm_desc(desc_t *desc, uint32_t base, uint32_t limit, int dpl, int type)
+{
+       desc->d[0] = limit & 0xffff; /* low order 16bits of limit */
+       desc->d[1] = base & 0xffff;  /* low order 16bits of base */
+
+       /* third 16bit part contains the last 8 bits of base, the 2 priviledge
+        * level bits starting on bit 13, present flag on bit 15, and type bits
+        * starting from bit 8
+        */
+       desc->d[2] = ((base >> 16) & 0xff) | ((dpl & 3) << 13) | BIT_PRESENT |
+               BIT_NOSYS | (type == TYPE_DATA ? BIT_WR : (BIT_RD | BIT_CODE));
+
+       /* last 16bit part contains the last nibble of limit, the last byte of
+        * base, and the granularity and deafult/big flags in bits 23 and 22 resp.
+        */
+       desc->d[3] = ((limit >> 16) & 0xf) | ((base >> 16) & 0xff00) | BIT_GRAN | BIT_BIG;
+}
+
+static void segm_desc16(desc_t *desc, uint32_t base, uint32_t limit, int dpl, int type)
+{
+       desc->d[0] = limit & 0xffff; /* low order 16bits of limit */
+       desc->d[1] = base & 0xffff;  /* low order 16bits of base */
+
+       /* third 16bit part contains the last 8 bits of base, the 2 priviledge
+        * level bits starting on bit 13, present flag on bit 15, and type bits
+        * starting from bit 8
+        */
+       desc->d[2] = ((base >> 16) & 0xff) | ((dpl & 3) << 13) | BIT_PRESENT |
+               BIT_NOSYS | (type == TYPE_DATA ? BIT_WR : (BIT_RD | BIT_CODE));
+
+       /* 16bit descriptors have the upper word 0 */
+       desc->d[3] = 0;
+}
+
+#if 0
+static void task_desc(desc_t *desc, uint32_t base, uint32_t limit, int dpl)
+{
+       desc->d[0] = limit & 0xffff;
+       desc->d[1] = base & 0xffff;
+
+       desc->d[2] = ((base >> 16) & 0xff) | ((dpl & 3) << 13) | BIT_PRESENT |
+               TSS_TYPE_BITS; /* XXX busy ? */
+       desc->d[3] = ((limit >> 16) & 0xf) | ((base >> 16) & 0xff00) | BIT_GRAN;
+}
+
+static void dbg_print_gdt(void)
+{
+       int i;
+
+       printf("Global Descriptor Table\n");
+       printf("-----------------------\n");
+
+       for(i=0; i<NUM_SEGMENTS; i++) {
+               printf("%d: ", i);
+               print_desc(gdt + i);
+       }
+}
+
+static void print_desc(desc_t *desc)
+{
+       uint32_t base, limit;
+       int dpl, g;
+
+       if((desc->d[0] | desc->d[1] | desc->d[2] | desc->d[3]) == 0) {
+               printf("null\n");
+       }
+
+       base = (uint32_t)desc->d[1] | ((uint32_t)(desc->d[2] & 0xff) << 16) | ((uint32_t)(desc->d[3] >> 8) << 24);
+       limit = (uint32_t)desc->d[0] | ((uint32_t)(desc->d[3] & 0xf) << 16);
+       dpl = (desc->d[2] >> 13) & 3;
+       g = (desc->d[3] >> 7) & 1;
+
+       if(g) limit = ((limit + 1) << 12) - 1;
+
+       printf("base:%x lim:%x dpl:%d type:%s %dbit\n", base, limit, dpl,
+                       desc->d[2] & BIT_CODE ? "code" : "data", desc->d[3] & BIT_BIG ? 32 : 16);
+}
+#endif
diff --git a/src/kern/segm.h b/src/kern/segm.h
new file mode 100644 (file)
index 0000000..f3b083c
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef SEGM_H_
+#define SEGM_H_
+
+#include <inttypes.h>
+
+enum {
+       SEGM_NULL,
+       SEGM_CODE,
+       SEGM_DATA,
+       SEGM_XCODE,             /* transition 32bit code with base matching the RM segment */
+       SEGM_XDATA,             /* same for data */
+       SEGM_CODE16,
+
+       NUM_SEGMENTS
+};
+
+void init_segm(void);
+
+uint16_t selector(int idx, int rpl);
+
+void set_tss(uint32_t addr);
+
+
+#endif /* SEGM_H_ */
diff --git a/src/kern/segm_s.asm b/src/kern/segm_s.asm
new file mode 100644 (file)
index 0000000..6448c04
--- /dev/null
@@ -0,0 +1,64 @@
+; pcboot - bootable PC demo/game kernel
+; Copyright (C) 2018-2023  John Tsiombikas <nuclear@member.fsf.org>
+; 
+; This program is free software: you can redistribute it and/or modify
+; it under the terms of the GNU General Public License as published by
+; the Free Software Foundation, either version 3 of the License, or
+; (at your option) any later version.
+; 
+; This program is distributed in the hope that it will be useful,
+; but WITHOUT ANY WARRANTY, without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+; GNU General Public License for more details.
+; 
+; You should have received a copy of the GNU General Public License
+; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+       section .data
+       align 4
+; memory reserved for setup_selectors
+off:   dd 0
+segm:  dw 0
+; memory reserved for set_gdt
+lim:   dw 0
+addr:  dd 0
+
+       section .text
+; setup_selectors(uint16_t code, uint16_t data)
+; loads the requested selectors to all the selector registers
+       global setup_selectors
+setup_selectors:
+       ; set data selectors directly
+       mov eax, [esp + 8]
+       mov ss, ax
+       mov es, ax
+       mov ds, ax
+       mov gs, ax
+       ;mov fs, ax     ; XXX don't touch fs, we use it to store initial seg
+       ; set cs using a long jump
+       mov eax, [esp + 4]
+       mov [segm], ax
+       mov dword [off], .ldcs
+       jmp [off]
+.ldcs: ret
+
+; set_gdt(uint32_t addr, uint16_t limit)
+; loads the GDTR with the new address and limit for the GDT
+       global set_gdt
+set_gdt:
+       mov eax, [esp + 4]
+       mov [addr], eax
+       mov ax, [esp + 8]
+       mov [lim], ax
+       lgdt [lim]
+       ret
+
+; set_task_reg(uint16_t tss_selector)
+; loads the TSS selector in the task register
+       global set_task_reg
+set_task_reg:
+       mov eax, [esp + 4]
+       ltr [esp + 4]
+       ret
+
+; vi:set ts=8 sts=8 sw=8 ft=nasm:
diff --git a/src/kern/serial.c b/src/kern/serial.c
new file mode 100644 (file)
index 0000000..84953c8
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include "config.h"
+#include "serial.h"
+#include "asmops.h"
+#include "intr.h"
+#include "panic.h"
+
+#define UART1_BASE     0x3f8
+#define UART2_BASE     0x2f8
+#define UART1_IRQ      4
+#define UART2_IRQ      3
+
+#define UART_DATA      0
+#define UART_INTR      1
+#define UART_DIVLO     0
+#define UART_DIVHI     1
+#define UART_FIFO      2
+#define UART_IID       2
+#define UART_LCTL      3
+#define UART_MCTL      4
+#define UART_LSTAT     5
+#define UART_MSTAT     6
+
+/* interrupt enable register bits */
+#define INTR_RECV      1
+#define INTR_SEND      2
+#define INTR_LSTAT     4
+#define INTR_DELTA     8
+
+/* fifo control register bits */
+#define FIFO_ENABLE            0x01
+#define FIFO_RECV_CLEAR        0x02
+#define FIFO_SEND_CLEAR        0x04
+#define FIFO_DMA               0x08
+#define FIFO_TRIG_4            0x40
+#define FIFO_TRIG_8            0x80
+#define FIFO_TRIG_14   0xc0
+
+/* interrupt id register bits */
+#define IID_PENDING            0x01
+#define IID_ID0                        0x02
+#define IID_ID1                        0x04
+#define IID_ID2                        0x08
+#define IID_FIFO_EN            0xc0
+
+#define IID_SOURCE             0xe
+
+#define IID_DELTA              0
+#define IID_SEND               0x2
+#define IID_RECV               0x4
+#define IID_FIFO               0xc
+#define IID_STATUS             0x6
+
+/* line control register bits */
+#define LCTL_BITS_8    0x03
+#define LCTL_STOP_2    0x04
+#define LCTL_DLAB      0x80
+#define LCTL_8N1       LCTL_BITS_8
+#define LCTL_8N2       (LCTL_BITS_8 | LCTL_STOP_2)
+
+/* modem control register bits */
+#define MCTL_DTR       0x01
+#define MCTL_RTS       0x02
+#define MCTL_OUT1      0x04
+#define MCTL_OUT2      0x08
+#define MCTL_LOOP      0x10
+
+/* line status register bits */
+#define LST_DRDY               0x01
+#define LST_ERR_OVER   0x02
+#define LST_ERR_PARITY 0x04
+#define LST_ERR_FRAME  0x08
+#define LST_ERR_BRK            0x10
+#define LST_TREG_EMPTY 0x20
+#define LST_TIDLE              0x40
+#define LST_ERROR              0x80
+
+/* modem status register bits */
+#define MST_DELTA_CTS  0x01
+#define MST_DELTA_DSR  0x02
+#define MST_TERI               0x04
+#define MST_DELTA_DCD  0x08
+#define MST_CTS                        0x10
+#define MST_DSR                        0x20
+#define MST_RING               0x40
+#define MST_DCD                        0x80
+
+/* interrupt controller stuff */
+#define PIC1_CMD_PORT  0x20
+#define PIC1_DATA_PORT 0x21
+#define PIC2_CMD_PORT  0xa0
+#define PIC2_DATA_PORT 0xa1
+#define OCW2_EOI               0x20
+
+#define COM_FMT_8N1            LCTL_8N1
+#define COM_FMT_8N2            LCTL_8N2
+
+#define INBUF_SIZE     16
+#define BNEXT(x)       (((x) + 1) & (INBUF_SIZE - 1))
+#define BEMPTY(b)      (b##_ridx == b##_widx)
+
+struct serial_port {
+       int base, intr;
+       int blocking;
+       int ref;
+
+       char inbuf[INBUF_SIZE];
+       int inbuf_ridx, inbuf_widx;
+};
+
+
+static int have_recv(int base);
+static void recv_intr();
+
+static struct serial_port ports[2];
+static int num_open;
+
+static int uart_base[] = {UART1_BASE, UART2_BASE};
+static int uart_irq[] = {UART1_IRQ, UART2_IRQ};
+
+int ser_open(int pidx, int baud, unsigned int mode)
+{
+       unsigned short div = 115200 / baud;
+       int i, fd, base, intr;
+       unsigned int fmt;
+
+       if(pidx < 0 || pidx > 1) {
+               printf("ser_open: invalid serial port: %d\n", pidx);
+               return -1;
+       }
+
+       for(i=0; i<num_open; i++) {
+               if(ports[i].base == uart_base[pidx]) {
+                       /* the port is already open, return the same fd and increment ref */
+                       ports[i].ref++;
+                       return i;
+               }
+       }
+
+       fd = num_open++;
+       memset(ports + fd, 0, sizeof ports[pidx]);
+
+       base = uart_base[pidx];
+       intr = uart_irq[pidx];
+
+       if(mode & SER_8N2) {
+               fmt = COM_FMT_8N2;
+       } else {
+               fmt = COM_FMT_8N1;
+       }
+
+       interrupt(IRQ_TO_INTR(uart_irq[pidx]), recv_intr);
+
+       outp(base + UART_LCTL, LCTL_DLAB);
+       outp(base + UART_DIVLO, div & 0xff);
+       outp(base + UART_DIVHI, (div >> 8) & 0xff);
+       outp(base + UART_LCTL, fmt);    /* fmt should be LCTL_8N1, LCTL_8N2 etc */
+       outp(base + UART_FIFO, FIFO_ENABLE | FIFO_SEND_CLEAR | FIFO_RECV_CLEAR);
+       outp(base + UART_MCTL, MCTL_DTR | MCTL_RTS | MCTL_OUT2);
+       outp(base + UART_INTR, INTR_RECV);
+
+       ports[fd].base = base;
+       ports[fd].intr = intr;
+       ports[fd].blocking = 1;
+       ports[fd].ref = 1;
+       return fd;
+}
+
+void ser_close(int fd)
+{
+       if(!ports[fd].ref) return;
+
+       if(--ports[fd].ref == 0) {
+               outp(ports[fd].base + UART_INTR, 0);
+               outp(ports[fd].base + UART_MCTL, 0);
+               ports[fd].base = 0;
+       }
+}
+
+int ser_block(int fd)
+{
+       ports[fd].blocking = 1;
+       return 0;
+}
+
+int ser_nonblock(int fd)
+{
+       ports[fd].blocking = 0;
+       return 0;
+}
+
+int ser_pending(int fd)
+{
+       return !BEMPTY(ports[fd].inbuf);
+}
+
+/* if msec < 0: wait for ever */
+int ser_wait(int fd, long msec)
+{
+       int res;
+       while(!(res = ser_pending(fd))) {
+               /* TODO timeout */
+       }
+       return res;
+}
+
+static int can_send(int fd)
+{
+       int base = ports[fd].base;
+       return inp(base + UART_LSTAT) & LST_TREG_EMPTY;
+}
+
+void ser_putc(int fd, char c)
+{
+       int base = ports[fd].base;
+
+       if(c == '\n') {
+               ser_putc(fd, '\r');
+       }
+
+       while(!can_send(fd));
+       /*while((inp(base + UART_MSTAT) & MST_CTS) == 0);*/
+       outp(base + UART_DATA, c);
+}
+
+int ser_getc(int fd)
+{
+       struct serial_port *p = ports + fd;
+       int have, c = -1;
+
+       if(p->blocking) {
+               while(!(have = ser_pending(fd)));
+       } else {
+               have = ser_pending(fd);
+       }
+
+       if(have) {
+               c = p->inbuf[p->inbuf_ridx];
+               p->inbuf_ridx = BNEXT(p->inbuf_ridx);
+       }
+       return c;
+}
+
+int ser_write(int fd, const char *buf, int count)
+{
+       int n = count;
+       while(n--) {
+               ser_putc(fd, *buf++);
+       }
+       return count;
+}
+
+int ser_read(int fd, char *buf, int count)
+{
+       int c, n = 0;
+       while(n < count && (c = ser_getc(fd)) != -1) {
+               *buf++ = c;
+               ++n;
+       }
+       return n;
+}
+
+char *ser_getline(int fd, char *buf, int bsz)
+{
+       static int widx;
+       char linebuf[512];
+       int i, rd, size, offs;
+
+       size = sizeof linebuf - widx - 1;
+       while(size && (rd = ser_read(fd, linebuf + widx, size)) > 0) {
+               widx += rd;
+               size -= rd;
+       }
+
+       linebuf[widx] = 0;
+
+       for(i=0; i<widx; i++) {
+               if(linebuf[i] == '\r' || linebuf[i] == '\n') {
+                       size = i >= bsz ? bsz - 1 : i;
+                       memcpy(buf, linebuf, size);
+                       buf[size] = 0;
+
+                       offs = i + 1;
+                       memmove(linebuf, linebuf + offs, widx - offs);
+                       widx -= offs;
+                       return buf;
+               }
+       }
+       return 0;
+}
+
+static int have_recv(int base)
+{
+       unsigned short stat = inp(base + UART_LSTAT);
+       if(stat & LST_ERROR) {
+               panic("serial receive error\n");
+       }
+       return stat & LST_DRDY;
+}
+
+static void recv_intr()
+{
+       int i, idreg, c;
+
+       for(i=0; i<2; i++) {
+               int base = uart_base[i];
+               struct serial_port *p = ports + i;
+
+               while(((idreg = inp(base + UART_IID)) & IID_PENDING) == 0) {
+                       while(have_recv(base)) {
+                               c = inp(base + UART_DATA);
+
+#ifdef ENABLE_GDB_STUB
+                               if(c == 3 && i == GDB_SERIAL_PORT) {
+                                       asm("int $3");
+                                       continue;
+                               }
+#endif
+
+                               p->inbuf[p->inbuf_widx] = c;
+                               p->inbuf_widx = BNEXT(p->inbuf_widx);
+
+                               if(p->inbuf_widx == p->inbuf_ridx) {
+                                       /* we overflowed, drop the oldest */
+                                       p->inbuf_ridx = BNEXT(p->inbuf_ridx);
+                               }
+                       }
+               }
+       }
+}
+
+#ifdef ENABLE_GDB_STUB
+void putDebugChar(int c)
+{
+       ser_putc(GDB_SERIAL_PORT, c);
+}
+
+int getDebugChar(void)
+{
+       return ser_getc(GDB_SERIAL_PORT);
+}
+#endif
diff --git a/src/kern/serial.h b/src/kern/serial.h
new file mode 100644 (file)
index 0000000..fb2c17e
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef SERIAL_H_
+#define SERIAL_H_
+
+#define SER_8N1                0
+#define SER_8N2                1
+#define SER_HWFLOW     2
+
+int ser_open(int pidx, int baud, unsigned int mode);
+void ser_close(int fd);
+
+int ser_block(int fd);
+int ser_nonblock(int fd);
+
+int ser_pending(int fd);
+/* if msec < 0: wait for ever */
+int ser_wait(int fd, long msec);
+
+void ser_putc(int fd, char c);
+int ser_getc(int fd);
+
+int ser_write(int fd, const char *buf, int count);
+int ser_read(int fd, char *buf, int count);
+
+#define ser_putchar(c) ser_putc(0, c)
+
+char *ser_getline(int fd, char *buf, int bsz);
+
+
+#endif /* SERIAL_H_ */
diff --git a/src/kern/timer.c b/src/kern/timer.c
new file mode 100644 (file)
index 0000000..c050b84
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2023 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include "intr.h"
+#include "asmops.h"
+#include "timer.h"
+#include "panic.h"
+#include "config.h"
+
+/* frequency of the oscillator driving the 8254 timer */
+#define OSC_FREQ_HZ            1193182
+
+/* macro to divide and round to the nearest integer */
+#define DIV_ROUND(a, b) ((a) / (b) + ((a) % (b)) / ((b) / 2))
+
+/* I/O ports connected to the 8254 */
+#define PORT_DATA0     0x40
+#define PORT_DATA1     0x41
+#define PORT_DATA2     0x42
+#define PORT_CMD       0x43
+
+/* command bits */
+#define CMD_CHAN0                      0
+#define CMD_CHAN1                      (1 << 6)
+#define CMD_CHAN2                      (2 << 6)
+#define CMD_RDBACK                     (3 << 6)
+
+#define CMD_LATCH                      0
+#define CMD_ACCESS_LOW         (1 << 4)
+#define CMD_ACCESS_HIGH                (2 << 4)
+#define CMD_ACCESS_BOTH                (3 << 4)
+
+#define CMD_OP_INT_TERM                0
+#define CMD_OP_ONESHOT         (1 << 1)
+#define CMD_OP_RATE                    (2 << 1)
+#define CMD_OP_SQWAVE          (3 << 1)
+#define CMD_OP_SOFT_STROBE     (4 << 1)
+#define CMD_OP_HW_STROBE       (5 << 1)
+
+#define CMD_MODE_BIN           0
+#define CMD_MODE_BCD           1
+
+volatile unsigned long nticks;
+
+struct timer_event {
+       int dt; /* remaining ticks delta from the previous event */
+       void (*func)(void);
+       struct timer_event *next;
+};
+
+static void timer_handler(int inum);
+
+static struct timer_event *evlist;
+
+
+void init_timer(void)
+{
+       /* calculate the reload count: round(osc / freq) */
+       int reload_count = DIV_ROUND(OSC_FREQ_HZ, TICK_FREQ_HZ);
+
+       /* set the mode to square wave for channel 0, both low
+        * and high reload count bytes will follow...
+        */
+       outp(PORT_CMD, CMD_CHAN0 | CMD_ACCESS_BOTH | CMD_OP_SQWAVE);
+
+       /* write the low and high bytes of the reload count to the
+        * port for channel 0
+        */
+       outp(PORT_DATA0, reload_count & 0xff);
+       outp(PORT_DATA0, (reload_count >> 8) & 0xff);
+
+       /* set the timer interrupt handler */
+       interrupt(IRQ_TO_INTR(0), timer_handler);
+       unmask_irq(0);
+}
+
+void cleanup_timer(void)
+{
+       /* return the timer to the original rate */
+       outp(PORT_CMD, CMD_CHAN0 | CMD_ACCESS_BOTH | CMD_OP_SQWAVE);
+       outp(PORT_DATA0, 0);
+       outp(PORT_DATA0, 0);
+}
+
+void set_alarm(unsigned long msec, void (*func)(void))
+{
+       int ticks, tsum, iflag;
+       struct timer_event *ev, *node;
+
+       if((ticks = MSEC_TO_TICKS(msec)) <= 0) {
+               return;
+       }
+
+       if(!(ev = malloc(sizeof *ev))) {
+               panic("failed to allocate timer event");
+               return;
+       }
+       ev->func = func;
+
+       iflag = get_intr_flag();
+       disable_intr();
+
+       if(!evlist || ticks < evlist->dt) {
+               /* insert at the begining */
+               ev->next = evlist;
+               evlist = ev;
+
+               ev->dt = ticks;
+               if(ev->next) {
+                       ev->next->dt -= ticks;
+               }
+       } else {
+               tsum = evlist->dt;
+               node = evlist;
+
+               while(node->next && ticks > tsum + node->next->dt) {
+                       tsum += node->next->dt;
+                       node = node->next;
+               }
+
+               ev->next = node->next;
+               node->next = ev;
+
+               /* fix the relative times */
+               ev->dt = ticks - tsum;
+               if(ev->next) {
+                       ev->next->dt -= ev->dt;
+               }
+       }
+
+       set_intr_flag(iflag);
+}
+
+void cancel_alarm(void (*func)(void))
+{
+       int iflag;
+       struct timer_event *ev, *node;
+       struct timer_event dummy;
+
+       iflag = get_intr_flag();
+       disable_intr();
+
+       dummy.next = evlist;
+       node = &dummy;
+       while(node->next) {
+               ev = node->next;
+               if(ev->func == func) {
+                       /* found it */
+                       if(ev->next) {
+                               ev->next->dt += ev->dt;
+                       }
+                       node->next = ev->next;
+                       free(ev);
+                       break;
+               }
+               node = node->next;
+       }
+
+       set_intr_flag(iflag);
+}
+
+static void timer_handler(int inum)
+{
+       nticks++;
+
+       if(evlist) {
+               evlist->dt--;
+
+               while(evlist && evlist->dt <= 0) {
+                       struct timer_event *ev = evlist;
+                       evlist = evlist->next;
+
+                       ev->func();
+                       free(ev);
+               }
+       }
+}
diff --git a/src/kern/timer.h b/src/kern/timer.h
new file mode 100644 (file)
index 0000000..c057801
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2023 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef _TIMER_H_
+#define _TIMER_H_
+
+#include "config.h"
+
+#define MSEC_TO_TICKS(ms)      ((ms) * TICK_FREQ_HZ / 1000)
+#define TICKS_TO_MSEC(tk)      ((tk) * 1000 / TICK_FREQ_HZ)
+
+extern volatile unsigned long nticks;
+
+void init_timer(void);
+void cleanup_timer(void);
+
+/*
+int sys_sleep(int sec);
+void sleep(unsigned long msec);
+*/
+
+/* schedule a function to be called 'msec' milliseconds into the future
+ * warning: this function will be called directly from the timer interrupt, and
+ * must be extremely quick.
+ */
+void set_alarm(unsigned long msec, void (*func)(void));
+void cancel_alarm(void (*func)(void));
+
+#endif /* _TIMER_H_ */
diff --git a/src/kern/tss.h b/src/kern/tss.h
new file mode 100644 (file)
index 0000000..e9c6943
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef TSS_H_
+#define TSS_H_
+
+#include <inttypes.h>
+
+struct task_state {
+       uint32_t prev_task;
+       uint32_t esp0, ss0;     /* we only ever set these two values */
+       uint32_t esp1, ss1;
+       uint32_t esp2, ss2;
+       uint32_t cr3;
+       uint32_t eip;
+       uint32_t eflags;
+       uint32_t eax, ecx, edx, ebx;
+       uint32_t esp, ebp, esi, edi;
+       uint32_t es, cs, ss, ds, fs, gs;
+       uint32_t ldt_sel;
+       uint16_t trap, iomap_addr;
+} __attribute__((packed));
+
+#endif /* TSS_H_ */
diff --git a/src/libc/alloca.h b/src/libc/alloca.h
new file mode 100644 (file)
index 0000000..ae27a71
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef ALLOCA_H_
+#define ALLOCA_H_
+
+#define alloca(x)      __builtin_alloca(x)
+
+#endif /* ALLOCA_H_ */
diff --git a/src/libc/assert.h b/src/libc/assert.h
new file mode 100644 (file)
index 0000000..7180c97
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef ASSERT_H_
+#define ASSERT_H_
+
+#include "panic.h"
+
+#define assert(x) \
+       if(!(x)) { \
+               panic("Kernel assertion failed at " __FILE__ ":%d: " #x "\n", __LINE__); \
+       }
+
+#endif /* ASSERT_H_ */
diff --git a/src/libc/ctype.c b/src/libc/ctype.c
new file mode 100644 (file)
index 0000000..89e7b93
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "ctype.h"
+
+int isalnum(int c)
+{
+       return isalpha(c) || isdigit(c);
+}
+
+int isalpha(int c)
+{
+       return isupper(c) || islower(c);
+}
+
+int isblank(int c)
+{
+       return c == ' ' || c == '\t';
+}
+
+int isdigit(int c)
+{
+       return c >= '0' && c <= '9';
+}
+
+int isupper(int c)
+{
+       return c >= 'A' && c <= 'Z';
+}
+
+int islower(int c)
+{
+       return c >= 'a' && c <= 'z';
+}
+
+int isgraph(int c)
+{
+       return c > ' ' && c <= '~';
+}
+
+int isprint(int c)
+{
+       return isgraph(c) || c == ' ';
+}
+
+int isspace(int c)
+{
+       return isblank(c) || c == '\f' || c == '\n' || c == '\r' || c == '\v';
+}
+
+int toupper(int c)
+{
+       return islower(c) ? (c + ('A' - 'a')) : c;
+}
+
+int tolower(int c)
+{
+       return isupper(c) ? (c - ('A' - 'a')) : c;
+}
diff --git a/src/libc/ctype.h b/src/libc/ctype.h
new file mode 100644 (file)
index 0000000..79595c8
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef CTYPE_H_
+#define CTYPE_H_
+
+int isalnum(int c);
+int isalpha(int c);
+#define isascii(c)     ((c) < 128)
+int isblank(int c);
+int isdigit(int c);
+int isupper(int c);
+int islower(int c);
+int isprint(int c);
+int isspace(int c);
+
+int toupper(int c);
+int tolower(int c);
+
+#endif /* CTYPE_H_ */
diff --git a/src/libc/errno.h b/src/libc/errno.h
new file mode 100644 (file)
index 0000000..8c152de
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef ERRNO_H_
+#define ERRNO_H_
+
+#define EFOO                   1
+#define EAGAIN                 2
+#define EINVAL                 3
+#define ECHILD                 4
+#define EBUSY                  5
+#define ENOMEM                 6
+#define EIO                            7
+#define ENOENT                 8
+#define ENAMETOOLONG   9
+#define ENOSPC                 10
+#define EPERM                  11
+#define ENOTDIR                        12
+#define EISDIR                 13
+#define EEXIST                 14
+#define ERANGE                 34
+
+#define EBUG                   127     /* for missing features and known bugs */
+
+extern int errno;
+
+#endif /* ERRNO_H_ */
diff --git a/src/libc/float.h b/src/libc/float.h
new file mode 100644 (file)
index 0000000..073bf6d
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef FLOAT_H_
+#define FLOAT_H_
+
+#define FLT_MIN        __FLT_MIN__
+#define FLT_MAX        __FLT_MAX__
+
+#endif /* FLOAT_H_ */
diff --git a/src/libc/inttypes.h b/src/libc/inttypes.h
new file mode 100644 (file)
index 0000000..39d948a
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef INTTYPES_H_
+#define INTTYPES_H_
+
+typedef char int8_t;
+typedef short int16_t;
+typedef int int32_t;
+typedef long long int64_t;
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+typedef unsigned long long uint64_t;
+
+#endif /* INTTYPES_H_ */
diff --git a/src/libc/limits.h b/src/libc/limits.h
new file mode 100644 (file)
index 0000000..b3abdad
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef LIMITS_H_
+#define LIMITS_H_
+
+#define CHAR_BIT       8
+
+#define SHRT_MIN       (-32768)
+#define SHRT_MAX       32767
+#define INT_MIN                (-2147483648)
+#define INT_MAX                2147483647
+#define LONG_MIN       (-2147483648)
+#define LONG_MAX       2147483647
+
+#define USHRT_MAX      65535
+#define UINT_MAX       0xffffffff
+#define ULONG_MAX      0xffffffff
+
+#define PATH_MAX       256
+
+#endif /* LIMITS_H_ */
diff --git a/src/libc/malloc.c b/src/libc/malloc.c
new file mode 100644 (file)
index 0000000..c064915
--- /dev/null
@@ -0,0 +1,429 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include "mem.h"
+#include "panic.h"
+
+#define SINGLE_POOL
+
+struct mem_desc {
+       size_t size;
+       uint32_t magic;
+#ifdef MALLOC_DEBUG
+       uint32_t dbg;
+#endif
+       struct mem_desc *next;
+};
+
+#ifdef MALLOC_DEBUG
+static void check_cycles(struct mem_desc *mem);
+static void print_pool(void);
+#endif
+
+#define MAGIC_USED     0xdeadf00d
+#define MAGIC_FREE     0x1ee7d00d
+
+#define DESC_PTR(b)    ((void*)((struct mem_desc*)(b) + 1))
+#define PTR_DESC(p)    ((struct mem_desc*)(p) - 1)
+
+#ifdef SINGLE_POOL
+static struct mem_desc *pool;
+#else
+
+#define NUM_POOLS      16
+#define FIRST_POOL_POW2        5
+/* 2**(x+5) size pools: 0->32, 1->64, 2->128 .. 15->1048576 */
+#define POOL_SIZE(x)   (1 << ((x) + FIRST_POOL_POW2))
+#define MAX_POOL_SIZE  (1 << ((NUM_POOLS - 1) + FIRST_POOL_POW2))
+#define MAX_POOL_PAGES BYTES_TO_PAGES(MAX_POOL_SIZE)
+static struct mem_desc *pools[NUM_POOLS];
+
+static int add_to_pool(struct mem_desc *b);
+
+static int pool_index(int sz)
+{
+       int x = 0;
+       --sz;
+       while(sz) {
+               sz >>= 1;
+               ++x;
+       }
+       return x - FIRST_POOL_POW2;
+}
+#endif /* !SINGLE_POOL */
+
+
+#ifdef SINGLE_POOL
+#define MIN_BLOCK_SIZE         (sizeof(struct mem_desc) * 2)
+
+void *malloc(size_t sz)
+{
+       int pg0, npages;
+       size_t total_sz, rest_sz;
+       struct mem_desc *mem, *rest, *prev, dummy;
+       int found = 0;
+
+       total_sz = (sz + sizeof(struct mem_desc) + 3) & 0xfffffffc;
+
+       dummy.next = pool;
+       prev = &dummy;
+       while(prev->next) {
+               mem = prev->next;
+               /* give the whole block to the allocation if mem->size == total_sz or
+                * if it's larger, but not large enough to fit another mem_desc in there
+                * for the new block that we're going to split off + some reasonable
+                * amount of memory for the new block.
+                */
+               if(mem->size >= total_sz && mem->size < total_sz + MIN_BLOCK_SIZE) {
+                       prev->next = mem->next;
+                       found = 1;
+                       break;
+               }
+               /* if we have enough space, split the block and give the upper part
+                * to the allocation
+                */
+               if(mem->size > total_sz) {
+                       void *ptr = (char*)mem + mem->size - total_sz;
+                       mem->size -= total_sz;
+                       mem = ptr;
+                       found = 1;
+                       break;
+               }
+               prev = prev->next;
+       }
+       pool = dummy.next;
+
+       if(found) {
+               mem->size = total_sz;
+               mem->magic = MAGIC_USED;
+               mem->next = 0;
+               return DESC_PTR(mem);
+       }
+
+       /* did not find a free block, grab a new one */
+       npages = BYTES_TO_PAGES(total_sz);
+       if((pg0 = alloc_ppages(npages, 0)) == -1) {
+               errno = ENOMEM;
+               return 0;
+       }
+       mem = PAGE_TO_PTR(pg0);
+       mem->size = total_sz;
+       mem->next = 0;
+       mem->magic = MAGIC_USED;
+
+       /* add the rest of the block to pool */
+       rest_sz = npages * 4096 - total_sz;
+       if(rest_sz > 0) {
+               rest = (struct mem_desc*)((char*)mem + total_sz);
+               rest->size = rest_sz;
+               rest->next = 0;
+               rest->magic = MAGIC_USED;
+               free(DESC_PTR(rest));
+       }
+
+       return DESC_PTR(mem);
+}
+
+void free(void *p)
+{
+       struct mem_desc *mem, *prev, *cur, dummy;
+       char *end;
+
+       if(!p) return;
+       mem = PTR_DESC(p);
+
+       if(mem->magic != MAGIC_USED) {
+               if(mem->magic == MAGIC_FREE) {
+                       panic("free(%p): double-free\n", p);
+               } else {
+                       panic("free(%p): corrupted magic (%x)!\n", p, mem->magic);
+               }
+       }
+       mem->magic = MAGIC_FREE;
+       mem->next = 0;
+
+       /* nothing in the pool, just add this one */
+       if(!pool) {
+               pool = mem;
+               return;
+       }
+
+       end = (char*)mem + mem->size;
+
+       dummy.next = pool;
+       prev = &dummy;
+
+       while(prev->next) {
+               cur = prev->next;
+
+               /* block starts right at the end of mem: coalesce */
+               if((char*)cur == end) {
+                       mem->size += cur->size;
+                       mem->next = cur->next;
+                       cur->magic = 0;
+                       prev->next = mem;
+                       goto done;
+               }
+
+               /* block starts *after* the end of mem: add in front */
+               if((char*)cur > end) {
+                       mem->next = cur;
+                       prev->next = mem;
+                       goto done;
+               }
+
+               prev = prev->next;
+       }
+
+       /* our block starts at the end of the last block in the pool: coalesce */
+       if((char*)prev + prev->size == (char*)mem) {
+               mem->magic = 0;
+               prev->size += mem->size;
+               goto done;
+       }
+
+       /* so our block starts after the end of the last block: append */
+       prev->next = mem;
+
+done:
+       pool = dummy.next;
+
+#ifdef MALLOC_DEBUG
+       print_pool();
+#endif
+}
+
+#else  /* !SINGLE_POOL */
+
+void *malloc(size_t sz)
+{
+       int pidx, pg0;
+       size_t total_sz, halfsz;
+       struct mem_desc *mem, *other;
+
+       total_sz = sz + sizeof(struct mem_desc);
+
+       if(total_sz > MAX_POOL_SIZE) {
+               /*printf("  allocation too big, hitting sys_alloc directly\n");*/
+               if((pg0 = alloc_ppages(BYTES_TO_PAGES(total_sz), 0)) == -1) {
+                       errno = ENOMEM;
+                       return 0;
+               }
+               mem = PAGE_TO_PTR(pg0);
+               mem->magic = MAGIC_USED;
+               mem->size = total_sz;
+               mem->next = 0;
+               return DESC_PTR(mem);
+       }
+
+       pidx = pool_index(total_sz);
+       while(pidx < NUM_POOLS) {
+               if(pools[pidx]) {
+                       /* found a pool with a free block that fits */
+                       mem = pools[pidx];
+                       pools[pidx] = mem->next;
+                       mem->next = 0;
+
+                       /*printf("  using pool %d (size %ld)\n", pidx, (unsigned long)mem->size);*/
+
+                       /* while the mem size is larger than twice the allocation, split it */
+                       while(pidx-- > 0 && total_sz <= (halfsz = mem->size / 2)) {
+                               /*printf("  splitting %ld block in half and ", (unsigned long)mem->size);*/
+                               other = (struct mem_desc*)((char*)mem + halfsz);
+                               mem->size = other->size = halfsz;
+
+                               add_to_pool(other);
+                       }
+
+                       if(mem->magic != MAGIC_FREE) {
+                               panic("Trying to allocate range surrounded by an aura of wrong MAGIC\n");
+                       }
+                       mem->magic = MAGIC_USED;
+                       return DESC_PTR(mem);
+               }
+
+               ++pidx;
+       }
+
+       /* did not find a free block, add a new one */
+       pidx = NUM_POOLS - 1;
+       if((pg0 = alloc_ppages(MAX_POOL_PAGES, 0)) == -1) {
+               errno = ENOMEM;
+               return 0;
+       }
+       mem = PAGE_TO_PTR(pg0);
+       mem->size = MAX_POOL_SIZE;
+       mem->next = pools[pidx];
+       mem->magic = MAGIC_FREE;
+       pools[pidx] = mem;
+
+       /* try again now that there is a free block */
+       return malloc(sz);
+}
+
+
+void free(void *p)
+{
+       int pg0;
+       struct mem_desc *mem;
+
+       if(!p) return;
+
+       mem = PTR_DESC(p);
+       if(mem->magic != MAGIC_USED) {
+               if(mem->magic == MAGIC_FREE) {
+                       panic("free(%p): double-free\n", p);
+               } else {
+                       panic("free(%p): corrupted magic (%x)!\n", p, mem->magic);
+               }
+       }
+
+       if(mem->size > MAX_POOL_SIZE) {
+               /*printf("foo_free(%p): %ld bytes. block too large for pools, returning to system\n",
+                               (void*)p, (unsigned long)mem->size);*/
+               pg0 = ADDR_TO_PAGE(mem);
+               free_ppages(pg0, BYTES_TO_PAGES(mem->size));
+               return;
+       }
+
+       /*printf("foo_free(%p): block of %ld bytes and ", (void*)p, (unsigned long)mem->size);*/
+       add_to_pool(mem);
+}
+
+#endif /* !def SINGLE_POOL */
+
+
+void *calloc(size_t num, size_t size)
+{
+       void *ptr = malloc(num * size);
+       if(ptr) {
+               memset(ptr, 0, num * size);
+       }
+       return ptr;
+}
+
+void *realloc(void *ptr, size_t size)
+{
+       struct mem_desc *mem;
+       void *newp;
+
+       if(!ptr) {
+               return malloc(size);
+       }
+
+       mem = PTR_DESC(ptr);
+       if(mem->size >= size) {
+               return ptr;     /* TODO: shrink */
+       }
+
+       if(!(newp = malloc(size))) {
+               return 0;
+       }
+       memcpy(newp, ptr, mem->size);
+       free(ptr);
+       return newp;
+}
+
+#ifdef MALLOC_DEBUG
+static void check_cycles(struct mem_desc *mem)
+{
+       static uint32_t dbg = 42;
+       uint32_t cur_dbg = dbg++;
+
+       while(mem) {
+               if(mem->magic != MAGIC_FREE) {
+                       panic("check_cycles: NON-FREE MAGIC!\n");
+               }
+               if(mem->dbg == cur_dbg) {
+                       panic("CYCLE DETECTED\n");
+               }
+               mem->dbg = cur_dbg;
+               mem = mem->next;
+       }
+}
+
+static void print_pool(void)
+{
+       struct mem_desc *mem = pool;
+
+       printf("DBG: malloc pool:\n");
+       while(mem) {
+               printf(" %p (%ld) [%x]\n", mem, mem->size, mem->magic);
+               mem = mem->next;
+
+               assert((uint32_t)mem != MAGIC_USED);
+       }
+}
+#endif /* MALLOC_DEBUG */
+
+#ifndef SINGLE_POOL
+static int add_to_pool(struct mem_desc *mem)
+{
+       int pidx;
+       struct mem_desc head;
+       struct mem_desc *iter, *pnode;
+
+       pidx = pool_index(mem->size);
+
+       /*printf("adding %ld block to pool %d\n", (unsigned long)mem->size, pidx);*/
+
+       iter = &head;
+       head.next = pools[pidx];
+
+       while(iter->next) {
+               pnode = iter->next;
+               if(mem->size == pnode->size) {  /* only coalesce same-sized blocks */
+                       size_t size = mem->size;
+
+                       if((char*)mem == (char*)pnode - size) {
+                               iter->next = pnode->next;       /* unlink pnode */
+                               pools[pidx] = head.next;
+                               mem->next = 0;
+                               mem->size += size;
+
+                               /*printf("  coalescing %p with %p and ", (void*)mem, (void*)pnode);*/
+                               return add_to_pool(mem);
+                       }
+                       if((char*)mem == (char*)pnode + size) {
+                               iter->next = pnode->next;       /* unlink pnode */
+                               pools[pidx] = head.next;
+                               pnode->next = 0;
+                               pnode->size += size;
+
+                               /*printf("  coalescing %p with %p and ", (void*)mem, (void*)pnode);*/
+                               return add_to_pool(pnode);
+                       }
+               }
+               iter = iter->next;
+       }
+
+       /* otherwise just add it to the pool */
+       mem->magic = MAGIC_FREE;
+       mem->next = pools[pidx];
+       pools[pidx] = mem;
+
+#ifdef MALLOC_DEBUG
+       check_cycles(pools[pidx]);
+#endif
+       return 0;
+}
+#endif /* !def SINGLE_POOL */
diff --git a/src/libc/math.c b/src/libc/math.c
new file mode 100644 (file)
index 0000000..3f3e5de
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "math.h"
+
+static double calc_pow(double x, double y, double precision);
+
+double pow(double x, double y)
+{
+       if(y == 0.0 || y == -0.0) {
+               return 1.0;
+       }
+       if(y == 1.0) {
+               return x;
+       }
+       if(y == -INFINITY) {
+               return fabs(x) < 1.0 ? INFINITY : 0.0;
+       }
+       if(y == INFINITY) {
+               return fabs(x) < 1.0 ? 0.0 : INFINITY;
+       }
+       return calc_pow(x, y, 1e-6);
+}
+
+static double calc_pow(double x, double y, double precision)
+{
+       if(y < 0.0) {
+               return 1.0 / calc_pow(x, -y, precision);
+       }
+       if(y >= 10.0) {
+               double p = calc_pow(x, y / 2.0, precision / 2.0);
+               return p * p;
+       }
+       if(y >= 1.0) {
+               return x * calc_pow(x, y - 1.0, precision);
+       }
+       if(precision >= 1) {
+               return __builtin_sqrt(x);
+       }
+       return __builtin_sqrt(calc_pow(x, y * 2.0, precision * 2.0));
+}
diff --git a/src/libc/math.h b/src/libc/math.h
new file mode 100644 (file)
index 0000000..e3b0f23
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef MATH_H_
+#define MATH_H_
+
+#define INFINITY       __builtin_inff()
+#define NAN                    __builtin_nanf
+
+#define M_PI           3.141592653589793
+
+#define sin(x)         __builtin_sin(x)
+#define cos(x)         __builtin_cos(x)
+#define tan(x)         __builtin_tan(x)
+#define fabs(x)                __builtin_fabs(x)
+#define fmod(x, y)     __builtin_fmod(x, y)
+#define sqrt(x)                __builtin_sqrt(x)
+#define atan2(y, x)    __builtin_atan2(y, x)
+
+double pow(double x, double y);
+
+#endif /* MATH_H_ */
diff --git a/src/libc/rand.c b/src/libc/rand.c
new file mode 100644 (file)
index 0000000..de159c2
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+/* random number generator, based on this description of the algorithm
+ * used by the GNU libc: https://www.mathstat.dal.ca/~selinger/random
+ */
+#include <stdlib.h>
+#include <inttypes.h>
+
+static int init_done;
+static int32_t rng[34];
+static int32_t *ptr0, *ptr1;
+
+int rand(void)
+{
+       int res;
+
+       if(!init_done) {
+               srand(1);
+       }
+
+       *ptr1 += *ptr0;
+       res = (uint32_t)*ptr1 >> 1;
+       if(++ptr0 >= rng + 34) ptr0 = rng;
+       if(++ptr1 >= rng + 34) ptr1 = rng;
+
+       return res;
+}
+
+void srand(unsigned int seed)
+{
+       int i;
+
+       init_done = 1;
+       if(seed == 0) seed = 1;
+
+       rng[0] = seed;
+       for(i=1; i<31; i++) {
+               rng[i] = (16807 * rng[i - 1]) % RAND_MAX;
+               if(rng[i] < 0) rng[i] += RAND_MAX;
+       }
+       for(i=31; i<34; i++) {
+               rng[i] = rng[i - 31];
+       }
+       ptr0 = rng + 3;
+       ptr1 = rng + 31;
+
+       for(i=34; i<344; i++) {
+               rand();
+       }
+}
diff --git a/src/libc/setjmp.c b/src/libc/setjmp.c
new file mode 100644 (file)
index 0000000..517563b
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+256boss - bootable launcher for 256byte intros
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <setjmp.h>
+
+int setjmp(jmp_buf buf)
+{
+       return __builtin_setjmp(buf);
+}
+
+void longjmp(jmp_buf buf, int val)
+{
+       __builtin_longjmp(buf, 1);
+}
diff --git a/src/libc/setjmp.h b/src/libc/setjmp.h
new file mode 100644 (file)
index 0000000..c92e93d
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+256boss - bootable launcher for 256byte intros
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef SETJMP_H_
+#define SETJMP_H_
+
+typedef unsigned long jmp_buf[5];
+
+int setjmp(jmp_buf buf);
+void longjmp(jmp_buf buf, int val);
+
+#endif /* SETJMP_H_ */
diff --git a/src/libc/stdarg.h b/src/libc/stdarg.h
new file mode 100644 (file)
index 0000000..2d0bb92
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef STDARG_H_
+#define STDARG_H_
+
+/* Assumes that arguments are passed on the stack 4-byte aligned */
+
+typedef int* va_list;
+
+#define va_start(ap, last)     ((ap) = (int*)&(last) + 1)
+#define va_arg(ap, type)       (*(type*)(ap)++)
+#define va_end(ap)
+
+#endif /* STDARG_H_ */
diff --git a/src/libc/stddef.h b/src/libc/stddef.h
new file mode 100644 (file)
index 0000000..9ec6bda
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef STDDEF_H_
+#define STDDEF_H_
+
+#include <inttypes.h>
+
+typedef int32_t ssize_t;
+typedef uint32_t size_t;
+typedef int wchar_t;
+
+typedef int32_t ptrdiff_t;
+typedef uint32_t intptr_t;
+
+#define NULL   0
+
+#endif /* STDDEF_H_ */
diff --git a/src/libc/stdio.c b/src/libc/stdio.c
new file mode 100644 (file)
index 0000000..a0e39b1
--- /dev/null
@@ -0,0 +1,409 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include "contty.h"
+#include "serial.h"
+#include "panic.h"
+
+enum {
+       OUT_DEF,
+       OUT_BUF,
+       OUT_SCR,
+       OUT_SER
+};
+
+extern void pcboot_putchar(int c);
+
+static int intern_printf(int out, char *buf, size_t sz, const char *fmt, va_list ap);
+static int intern_scanf(const char *instr, FILE *infile, const char *fmt, va_list ap);
+static void bwrite(int out, char *buf, size_t buf_sz, char *str, int sz);
+/*static int readchar(const char *str, FILE *fp);*/
+
+int putchar(int c)
+{
+       con_putchar(c);
+       return c;
+}
+
+int puts(const char *s)
+{
+       while(*s) {
+               putchar(*s++);
+       }
+       putchar('\n');
+       return 0;
+}
+
+/* -- printf and friends -- */
+
+int printf(const char *fmt, ...)
+{
+       int res;
+       va_list ap;
+
+       va_start(ap, fmt);
+       res = intern_printf(OUT_DEF, 0, 0, fmt, ap);
+       va_end(ap);
+       return res;
+}
+
+int vprintf(const char *fmt, va_list ap)
+{
+       return intern_printf(OUT_DEF, 0, 0, fmt, ap);
+}
+
+int sprintf(char *buf, const char *fmt, ...)
+{
+       int res;
+       va_list ap;
+
+       va_start(ap, fmt);
+       res = intern_printf(OUT_BUF, buf, 0, fmt, ap);
+       va_end(ap);
+       return res;
+}
+
+int vsprintf(char *buf, const char *fmt, va_list ap)
+{
+       return intern_printf(OUT_BUF, buf, 0, fmt, ap);
+}
+
+int snprintf(char *buf, size_t sz, const char *fmt, ...)
+{
+       int res;
+       va_list ap;
+
+       va_start(ap, fmt);
+       res = intern_printf(OUT_BUF, buf, sz, fmt, ap);
+       va_end(ap);
+       return res;
+}
+
+int vsnprintf(char *buf, size_t sz, const char *fmt, va_list ap)
+{
+       return intern_printf(OUT_BUF, buf, sz, fmt, ap);
+}
+
+int fprintf(FILE *fp, const char *fmt, ...)
+{
+       int res;
+       va_list ap;
+
+       va_start(ap, fmt);
+       res = vfprintf(fp, fmt, ap);
+       va_end(ap);
+       return res;
+}
+
+int vfprintf(FILE *fp, const char *fmt, va_list ap)
+{
+       if(fp == stdout || fp == stderr) {
+               return vprintf(fmt, ap);
+       }
+
+       panic("*fprintf for anything other than stdout/stderr, not implemented yet\n");
+       return 0;
+}
+
+int ser_printf(const char *fmt, ...)
+{
+       int res;
+       va_list ap;
+
+       va_start(ap, fmt);
+       res = intern_printf(OUT_SER, 0, 0, fmt, ap);
+       va_end(ap);
+       return res;
+}
+
+int ser_vprintf(const char *fmt, va_list ap)
+{
+       return intern_printf(OUT_SER, 0, 0, fmt, ap);
+}
+
+/*
+void perror(const char *s)
+{
+       printf("%s: %s\n", s, strerror(errno));
+}
+*/
+
+/* intern_printf provides all the functionality needed by all the printf
+ * variants.
+ * - buf: optional buffer onto which the formatted results are written. If null
+ *   then the output goes to the terminal through putchar calls. This is used
+ *   by the (v)sprintf variants which write to an array of char.
+ * - sz: optional maximum size of the output, 0 means unlimited. This is used
+ *   by the (v)snprintf variants to avoid buffer overflows.
+ * The rest are obvious, format string and variable argument list.
+ */
+static char *convc = "dioxXucsfeEgGpn%";
+
+#define IS_CONV(c)     strchr(convc, c)
+
+#define BUF(x) ((x) ? (x) + cnum : (x))
+#define SZ(x)  ((x) ? (x) - cnum : (x))
+
+static int intern_printf(int out, char *buf, size_t sz, const char *fmt, va_list ap)
+{
+       char conv_buf[32];
+       char *str;
+       int i, slen;
+       const char *fstart = 0;
+
+       /* state */
+       int cnum = 0;
+       int base = 10;
+       int alt = 0;
+       int fwidth = 0;
+       int padc = ' ';
+       int sign = 0;
+       int left_align = 0;
+       int hex_caps = 0;
+       int unsig = 0;
+       int num, unum;
+
+       while(*fmt) {
+               if(*fmt == '%') {
+                       fstart = fmt++;
+                       continue;
+               }
+
+               if(fstart) {
+                       if(IS_CONV(*fmt)) {
+                               switch(*fmt) {
+                               case 'X':
+                                       hex_caps = 1;
+
+                               case 'x':
+                               case 'p':
+                                       base = 16;
+
+                                       if(alt) {
+                                               bwrite(out, BUF(buf), SZ(sz), "0x", 2);
+                                               cnum += 2;
+                                       }
+
+                               case 'u':
+                                       unsig = 1;
+
+                                       if(0) {
+                               case 'o':
+                                               base = 8;
+
+                                               if(alt) {
+                                                       bwrite(out, BUF(buf), SZ(sz), "0", 1);
+                                                       cnum++;
+                                               }
+                                       }
+
+                               case 'd':
+                               case 'i':
+                                       if(unsig) {
+                                               unum = va_arg(ap, unsigned int);
+                                               utoa(unum, conv_buf, base);
+                                       } else {
+                                               num = va_arg(ap, int);
+                                               itoa(num, conv_buf, base);
+                                       }
+                                       if(hex_caps) {
+                                               for(i=0; conv_buf[i]; i++) {
+                                                       conv_buf[i] = toupper(conv_buf[i]);
+                                               }
+                                       }
+
+                                       slen = strlen(conv_buf);
+
+                                       if(left_align) {
+                                               if(!unsig && sign && num >= 0) {
+                                                       bwrite(out, BUF(buf), SZ(sz), "+", 1);
+                                                       cnum++;
+                                               }
+                                               bwrite(out, BUF(buf), SZ(sz), conv_buf, slen);
+                                               cnum += slen;
+                                               padc = ' ';
+                                       }
+                                       for(i=slen; i<fwidth; i++) {
+                                               bwrite(out, BUF(buf), SZ(sz), (char*)&padc, 1);
+                                               cnum++;
+                                       }
+                                       if(!left_align) {
+                                               if(!unsig && sign && num >= 0) {
+                                                       bwrite(out, BUF(buf), SZ(sz), "+", 1);
+                                                       cnum++;
+                                               }
+                                               bwrite(out, BUF(buf), SZ(sz), conv_buf, slen);
+                                               cnum += slen;
+                                       }
+                                       break;
+
+                               case 'c':
+                                       {
+                                               char c = va_arg(ap, int);
+                                               bwrite(out, BUF(buf), SZ(sz), &c, 1);
+                                               cnum++;
+                                       }
+                                       break;
+
+                               case 's':
+                                       str = va_arg(ap, char*);
+                                       slen = strlen(str);
+
+                                       if(left_align) {
+                                               bwrite(out, BUF(buf), SZ(sz), str, slen);
+                                               cnum += slen;
+                                               padc = ' ';
+                                       }
+                                       for(i=slen; i<fwidth; i++) {
+                                               bwrite(out, BUF(buf), SZ(sz), (char*)&padc, 1);
+                                               cnum++;
+                                       }
+                                       if(!left_align) {
+                                               bwrite(out, BUF(buf), SZ(sz), str, slen);
+                                               cnum += slen;
+                                       }
+                                       break;
+
+                               case 'n':
+                                       *va_arg(ap, int*) = cnum;
+                                       break;
+
+                               default:
+                                       break;
+                               }
+
+                               /* restore default conversion state */
+                               base = 10;
+                               alt = 0;
+                               fwidth = 0;
+                               padc = ' ';
+                               hex_caps = 0;
+
+                               fstart = 0;
+                               fmt++;
+                       } else {
+                               switch(*fmt) {
+                               case '#':
+                                       alt = 1;
+                                       break;
+
+                               case '+':
+                                       sign = 1;
+                                       break;
+
+                               case '-':
+                                       left_align = 1;
+                                       break;
+
+                               case 'l':
+                               case 'L':
+                                       break;
+
+                               case '0':
+                                       padc = '0';
+                                       break;
+
+                               default:
+                                       if(isdigit(*fmt)) {
+                                               const char *fw = fmt;
+                                               while(*fmt && isdigit(*fmt)) fmt++;
+
+                                               fwidth = atoi(fw);
+                                               continue;
+                                       }
+                               }
+                               fmt++;
+                       }
+               } else {
+                       bwrite(out, BUF(buf), SZ(sz), (char*)fmt++, 1);
+                       cnum++;
+               }
+       }
+
+       return cnum;
+}
+
+
+#if 0
+static char *sconvc = "diouxcsefg%";
+
+#define IS_SCONV(c)    strchr(sconvc, c)
+
+static int intern_scanf(const char *instr, FILE *infile, const char *fmt, va_list ap)
+{
+       return -1;      /* TODO */
+}
+#endif
+
+
+/* bwrite is called by intern_printf to transparently handle writing into a
+ * buffer or to the terminal
+ */
+static void bwrite(int out, char *buf, size_t buf_sz, char *str, int sz)
+{
+       int i;
+
+       if(out == OUT_BUF) {
+               if(buf_sz && buf_sz <= sz) sz = buf_sz;
+               buf[sz] = 0;
+               memcpy(buf, str, sz);
+       } else {
+               switch(out) {
+               case OUT_DEF:
+                       for(i=0; i<sz; i++) {
+                               putchar(*str++);
+                       }
+                       break;
+
+               case OUT_SER:
+                       for(i=0; i<sz; i++) {
+                               ser_putchar(*str++);
+                       }
+                       break;
+
+               default:
+                       /* TODO: OUT_SCR */
+                       break;
+               }
+       }
+}
+
+/*
+static int readchar(const char *str, FILE *fp)
+{
+       static const char *orig_str;
+       static const char *sptr;
+
+       if(str) {
+               if(str == orig_str) {
+                       if(!*sptr) return -1;
+                       return *sptr++;
+               } else {
+                       orig_str = sptr = str;
+                       return readchar(str, fp);
+               }
+       } else {
+               return fgetc(fp);
+       }
+
+       return -1;
+}
+*/
diff --git a/src/libc/stdio.h b/src/libc/stdio.h
new file mode 100644 (file)
index 0000000..19abc10
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef STDIO_H_
+#define STDIO_H_
+
+#include <stdlib.h>
+#include <stdarg.h>
+
+typedef struct FILE FILE;
+
+#define SEEK_SET       0
+#define SEEK_CUR       1
+#define SEEK_END       2
+
+#define EOF    (-1)
+
+#define stdin  ((FILE*)0)
+#define stdout ((FILE*)1)
+#define stderr ((FILE*)2)
+
+int putchar(int c);
+int puts(const char *s);
+
+int printf(const char *fmt, ...);
+int vprintf(const char *fmt, va_list ap);
+
+int sprintf(char *buf, const char *fmt, ...);
+int vsprintf(char *buf, const char *fmt, va_list ap);
+
+int snprintf(char *buf, size_t sz, const char *fmt, ...);
+int vsnprintf(char *buf, size_t sz, const char *fmt, va_list ap);
+
+/* TODO */
+int fprintf(FILE *fp, const char *fmt, ...);
+int vfprintf(FILE *fp, const char *fmt, va_list ap);
+
+/* TODO
+int fscanf(FILE *fp, const char *fmt, ...);
+int vfscanf(FILE *fp, const char *fmt, va_list ap);
+
+int sscanf(const char *str, const char *fmt, ...);
+int vsscanf(const char *ptr, const char *fmt, va_list ap);
+*/
+
+/* printf to the serial port */
+int ser_printf(const char *fmt, ...);
+int ser_vprintf(const char *fmt, va_list ap);
+
+void perror(const char *s);
+
+
+/* FILE I/O */
+FILE *fopen(const char *path, const char *mode);
+int fclose(FILE *fp);
+
+long filesize(FILE *fp);
+int fseek(FILE *fp, long offset, int from);
+void rewind(FILE *fp);
+long ftell(FILE *fp);
+
+size_t fread(void *buf, size_t size, size_t count, FILE *fp);
+size_t fwrite(const void *buf, size_t size, size_t count, FILE *fp);
+
+int fgetc(FILE *fp);
+char *fgets(char *buf, int size, FILE *fp);
+
+int fputc(int c, FILE *fp);
+
+int fflush(FILE *fp);
+
+int feof(FILE *fp);
+int ferror(FILE *fp);
+void clearerr(FILE *fp);
+
+#endif /* STDIO_H_ */
diff --git a/src/libc/stdlib.c b/src/libc/stdlib.c
new file mode 100644 (file)
index 0000000..6b130ed
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <assert.h>
+#include <alloca.h>
+
+int atoi(const char *str)
+{
+       return strtol(str, 0, 10);
+}
+
+long atol(const char *str)
+{
+       return strtol(str, 0, 10);
+}
+
+long strtol(const char *str, char **endp, int base)
+{
+       long acc = 0;
+       int sign = 1;
+       int valid = 0;
+       const char *start = str;
+
+       while(isspace(*str)) str++;
+
+       if(base == 0) {
+               if(str[0] == '0') {
+                       if(str[1] == 'x' || str[1] == 'X') {
+                               base = 16;
+                       } else {
+                               base = 8;
+                       }
+               } else {
+                       base = 10;
+               }
+       }
+
+       if(*str == '+') {
+               str++;
+       } else if(*str == '-') {
+               sign = -1;
+               str++;
+       }
+
+       while(*str) {
+               long val = LONG_MAX;
+               char c = tolower(*str);
+
+               if(isdigit(c)) {
+                       val = *str - '0';
+               } else if(c >= 'a' && c <= 'f') {
+                       val = 10 + c - 'a';
+               } else {
+                       break;
+               }
+               if(val >= base) {
+                       break;
+               }
+               valid = 1;
+
+               acc = acc * base + val;
+               str++;
+       }
+
+       if(endp) {
+               *endp = (char*)(valid ? str : start);
+       }
+
+       return sign > 0 ? acc : -acc;
+}
+
+void itoa(int val, char *buf, int base)
+{
+       static char rbuf[16];
+       char *ptr = rbuf;
+       int neg = 0;
+
+       if(val < 0) {
+               neg = 1;
+               val = -val;
+       }
+
+       if(val == 0) {
+               *ptr++ = '0';
+       }
+
+       while(val) {
+               int digit = val % base;
+               *ptr++ = digit < 10 ? (digit + '0') : (digit - 10 + 'a');
+               val /= base;
+       }
+
+       if(neg) {
+               *ptr++ = '-';
+       }
+
+       ptr--;
+
+       while(ptr >= rbuf) {
+               *buf++ = *ptr--;
+       }
+       *buf = 0;
+}
+
+void utoa(unsigned int val, char *buf, int base)
+{
+       static char rbuf[16];
+       char *ptr = rbuf;
+
+       if(val == 0) {
+               *ptr++ = '0';
+       }
+
+       while(val) {
+               unsigned int digit = val % base;
+               *ptr++ = digit < 10 ? (digit + '0') : (digit - 10 + 'a');
+               val /= base;
+       }
+
+       ptr--;
+
+       while(ptr >= rbuf) {
+               *buf++ = *ptr--;
+       }
+       *buf = 0;
+}
+
+double atof(const char *str)
+{
+       return strtod(str, 0);
+}
+
+
+double strtod(const char *str, char **endp)
+{
+       char *ep;
+       const char *start = str;
+       int valid = 0;
+       long ival = 0, dval = 0;
+       int ddig = 0;
+       double res;
+
+       /* integer part */
+       ival = strtol(str, &ep, 10);
+       if(ep == str && *str != '.') {
+               if(endp) *endp = (char*)str;
+               return 0.0;
+       }
+       if(ep != str) valid = 1;
+       str = *ep == '.' ? ep + 1 : ep;
+       if(!isdigit(*str)) {
+               goto done;
+       }
+       valid = 1;
+
+       dval = strtol(str, &ep, 10);
+       assert(dval >= 0);
+       ddig = ep - str;
+       str = ep;
+
+done:
+       if(*endp) {
+               *endp = (char*)(valid ? str : start);
+       }
+
+       res = (double)ival;
+       if(ddig) {
+               double d = (double)dval;
+               while(ddig-- > 0) {
+                       d /= 10.0;
+               }
+               res += d;
+       }
+       return res;
+}
+
+int atexit(void (*func)(void))
+{
+       /* there's no concept of exiting at the moment, so this does nothing */
+       return 0;
+}
+
+void abort(void)
+{
+       panic("Aborted\n");
+}
+
+#define QSORT_THRESHOLD        4
+#define ITEM(idx)      ((char*)arr + (idx) * itemsz)
+
+#define SWAP(p, q) \
+       do { \
+               int nn = itemsz; \
+               char *pp = (p); \
+               char *qq = (q); \
+               do { \
+                       char tmp = *pp; \
+                       *pp++ = *qq; \
+                       *qq++ = tmp; \
+               } while(--nn > 0); \
+       } while(0)
+
+static void ins_sort(void *arr, size_t count, size_t itemsz, int (*cmp)(const void*, const void*))
+{
+       int i;
+       char *it, *a, *b;
+
+       if(count <= 1) return;
+
+       it = (char*)arr + itemsz;
+       for(i=1; i<count; i++) {
+               a = it;
+               it += itemsz;
+               while(a > (char*)arr && cmp(a, (b = a - itemsz)) < 0) {
+                       SWAP(a, b);
+                       a -= itemsz;
+               }
+       }
+}
+
+void qsort(void *arr, size_t count, size_t itemsz, int (*cmp)(const void*, const void*))
+{
+       char *ma, *mb, *mc, *left, *right;
+       size_t sepidx, nleft, nright;
+
+       if(count <= 1) return;
+
+       if(count < QSORT_THRESHOLD) {
+               ins_sort(arr, count, itemsz, cmp);
+               return;
+       }
+
+       ma = arr;
+       mb = ITEM(count / 2);
+       mc = ITEM(count - 1);
+       if(cmp(ma, mb) < 0) SWAP(ma, mb);
+       if(cmp(mc, ma) < 0) SWAP(mc, ma);
+
+       left = ma + itemsz;
+       right = mc - itemsz;
+       for(;;) {
+               while(cmp(left, ma) < 0) left += itemsz;
+               while(cmp(ma, right) < 0) right -= itemsz;
+               if(left >= right) break;
+               SWAP(left, right);
+       }
+       SWAP(ma, right);
+       sepidx = (right - (char*)arr) / itemsz;
+       nleft = sepidx;
+       nright = count - nleft - 1;
+
+       qsort(ma, nleft, itemsz, cmp);
+       qsort(right + itemsz, nright, itemsz, cmp);
+}
diff --git a/src/libc/stdlib.h b/src/libc/stdlib.h
new file mode 100644 (file)
index 0000000..981cf69
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef STDLIB_H_
+#define STDLIB_H_
+
+#include <stddef.h>
+
+#define RAND_MAX       2147483647
+
+#define abs(x) __builtin_abs(x)
+
+int atoi(const char *str);
+long atol(const char *str);
+long strtol(const char *str, char **endp, int base);
+
+void itoa(int val, char *buf, int base);
+void utoa(unsigned int val, char *buf, int base);
+
+double atof(const char *str);
+double strtod(const char *str, char **endp);
+
+int atexit(void (*func)(void));
+
+void abort(void);
+
+void qsort(void *arr, size_t count, size_t size, int (*cmp)(const void*, const void*));
+
+int rand(void);
+int rand_r(unsigned int *seedp);
+void srand(unsigned int seed);
+
+/* defined in malloc.c */
+void *malloc(size_t sz);
+void *calloc(size_t num, size_t sz);
+void *realloc(void *ptr, size_t sz);
+void free(void *ptr);
+
+#endif /* STDLIB_H_ */
diff --git a/src/libc/string.c b/src/libc/string.c
new file mode 100644 (file)
index 0000000..06d8f87
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <string.h>
+#include <ctype.h>
+#include <stddef.h>
+
+int errno;
+
+void *memmove(void *dest, const void *src, size_t n)
+{
+       int i;
+       char *dptr;
+       const char *sptr;
+
+       if(dest <= src) {
+               /* forward copy */
+               dptr = dest;
+               sptr = src;
+               for(i=0; i<n; i++) {
+                       *dptr++ = *sptr++;
+               }
+       } else {
+               /* backwards copy */
+               dptr = (char*)dest + n - 1;
+               sptr = (const char*)src + n - 1;
+               for(i=0; i<n; i++) {
+                       *dptr-- = *sptr--;
+               }
+       }
+
+       return dest;
+}
+
+int memcmp(void *aptr, void *bptr, size_t n)
+{
+       int i, startoffs, diff;
+       uint32_t *a32, *b32;
+       unsigned char *a = aptr;
+       unsigned char *b = bptr;
+
+       a32 = (uint32_t*)((intptr_t)(a + 3) & 0xfffffffc);
+       b32 = (uint32_t*)((intptr_t)(b + 3) & 0xfffffffc);
+
+       /* if both are aligned the same way... */
+       if((startoffs = (unsigned char*)a32 - a) == (unsigned char*)b32 - b) {
+               /* catch-up to the 32bit alignment */
+               for(i=0; i<startoffs; i++) {
+                       if((diff = *a++ - *b++) != 0 || --n <= 0) {
+                               return diff;
+                       }
+               }
+
+               /* compare 32bit at once */
+               while(n >= 4) {
+                       if(*a32 != *b32) break;
+                       a32++;
+                       b32++;
+                       n -= 4;
+               }
+
+               /* update byte pointers to contine with the tail */
+               a = (unsigned char*)a32;
+               b = (unsigned char*)b32;
+       }
+
+       /* we're here both for the tail-end of same-alignment buffers, or for the
+        * whole length of mis-aligned buffers.
+        */
+       while(n-- > 0) {
+               if((diff = *a++ - *b++) != 0) {
+                       return diff;
+               }
+       }
+       return 0;
+}
+
+size_t strlen(const char *s)
+{
+       size_t len = 0;
+       while(*s++) len++;
+       return len;
+}
+
+char *strchr(const char *s, int c)
+{
+       while(*s) {
+               if(*s == c) {
+                       return (char*)s;
+               }
+               s++;
+       }
+       return 0;
+}
+
+char *strrchr(const char *s, int c)
+{
+       const char *ptr = s;
+
+       /* find the end */
+       while(*ptr) ptr++;
+
+       /* go back checking for c */
+       while(--ptr >= s) {
+               if(*ptr == c) {
+                       return (char*)ptr;
+               }
+       }
+       return 0;
+}
+
+char *strstr(const char *str, const char *substr)
+{
+       while(*str) {
+               const char *s1 = str;
+               const char *s2 = substr;
+
+               while(*s1 && *s1 == *s2) {
+                       s1++;
+                       s2++;
+               }
+               if(!*s2) {
+                       return (char*)str;
+               }
+               str++;
+       }
+       return 0;
+}
+
+char *strcasestr(const char *str, const char *substr)
+{
+       while(*str) {
+               const char *s1 = str;
+               const char *s2 = substr;
+
+               while(*s1 && tolower(*s1) == tolower(*s2)) {
+                       s1++;
+                       s2++;
+               }
+               if(!*s2) {
+                       return (char*)str;
+               }
+               str++;
+       }
+       return 0;
+}
+
+int strcmp(const char *s1, const char *s2)
+{
+       while(*s1 && *s1 == *s2) {
+               s1++;
+               s2++;
+       }
+       return *s1 - *s2;
+}
+
+int strcasecmp(const char *s1, const char *s2)
+{
+       while(*s1 && tolower(*s1) == tolower(*s2)) {
+               s1++;
+               s2++;
+       }
+       return tolower(*s1) - tolower(*s2);
+}
+
+int strncmp(const char *s1, const char *s2, int n)
+{
+       if(n <= 0) return 0;
+
+       while(n-- > 0 && *s1 && *s2 && *s1 == *s2) {
+               s1++;
+               s2++;
+       }
+
+       if(n <= 0) return 0;
+       return *s1 - *s2;
+}
+
+int strncasecmp(const char *s1, const char *s2, int n)
+{
+       if(n <= 0) return 0;
+
+       while(n-- > 0 && *s1 && *s2 && tolower(*s1) == tolower(*s2)) {
+               s1++;
+               s2++;
+       }
+
+       if(n <= 0) return 0;
+       return tolower(*s1) - tolower(*s2);
+}
+
+char *strcpy(char *dest, const char *src)
+{
+       char *dptr = dest;
+       while((*dptr++ = *src++));
+       return dest;
+}
+
+char *strcat(char *dest, const char *src)
+{
+       strcpy(dest + strlen(dest), src);
+       return dest;
+}
+
+char *strncpy(char *dest, const char *src, int n)
+{
+       char *dptr = dest;
+       while(n-- > 0 && (*dptr++ = *src++));
+       return dest;
+}
+
+/*
+static const char *errstr[] = {
+       "Success",
+       "Foo",
+       "Interrupted",
+       "Invalid",
+       "Child",
+       "Timeout",
+       "Out of memory",
+       "I/O error",
+       "Not found",
+       "Name too long",
+       "No space left on device",
+       "Permission denied",
+       "Not a directory",
+       "Is a directory",
+       "Does not exist",
+       0,0,0,0,0,
+       0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,
+       "Bug"
+};
+
+char *strerror(int err)
+{
+       if(err < 0 || err > sizeof errstr / sizeof *errstr || !errstr[err]) {
+               return "Unknown";
+       }
+       return (char*)errstr[err];
+}
+*/
diff --git a/src/libc/string.h b/src/libc/string.h
new file mode 100644 (file)
index 0000000..85490b2
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef STRING_H_
+#define STRING_H_
+
+#include <stdlib.h>
+
+void *memset(void *s, int c, size_t n);
+void *memset16(void *s, int c, size_t n);
+
+void *memcpy(void *dest, const void *src, size_t n);
+void *memmove(void *dest, const void *src, size_t n);
+
+int memcmp(void *aptr, void *bptr, size_t n);
+
+size_t strlen(const char *s);
+
+char *strchr(const char *s, int c);
+char *strrchr(const char *s, int c);
+
+char *strstr(const char *str, const char *substr);
+char *strcasestr(const char *str, const char *substr);
+
+int strcmp(const char *s1, const char *s2);
+int strcasecmp(const char *s1, const char *s2);
+
+int strncmp(const char *s1, const char *s2, int n);
+int strncasecmp(const char *s1, const char *s2, int n);
+
+char *strcpy(char *dest, const char *src);
+char *strcat(char *dest, const char *src);
+
+char *strncpy(char *dest, const char *src, int n);
+
+char *strerror(int err);
+
+#endif /* STRING_H_ */
diff --git a/src/libc/string_s.asm b/src/libc/string_s.asm
new file mode 100644 (file)
index 0000000..4545d16
--- /dev/null
@@ -0,0 +1,174 @@
+; pcboot - bootable PC demo/game kernel
+; Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+; 
+; This program is free software: you can redistribute it and/or modify
+; it under the terms of the GNU General Public License as published by
+; the Free Software Foundation, either version 3 of the License, or
+; (at your option) any later version.
+; 
+; This program is distributed in the hope that it will be useful,
+; but WITHOUT ANY WARRANTY, without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+; GNU General Public License for more details.
+; 
+; You should have received a copy of the GNU General Public License
+; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+       ; standard C memset
+       global memset
+memset:
+       push ebp
+       mov ebp, esp
+       push edi
+
+       mov edi, [ebp + 8]
+       push edi
+       mov al, [ebp + 12]
+       mov ah, al
+       mov cx, ax
+       shl eax, 16
+       mov ax, cx
+       mov ecx, [ebp + 16]
+
+       test ecx, ecx
+       jz .done
+
+       ; write 1, 2, or 3 times until we reache a 32bit-aligned dest address
+       mov edx, edi
+       and edx, 3
+       jz .main
+       jmp [.pre_tab + ecx * 4]
+
+.pre_tab dd .main, .pre1, .pre2, .pre3
+.pre3: stosb
+       dec ecx
+.pre2: stosb
+       dec ecx
+.pre1: stosb
+       dec ecx
+       jz .done
+
+       ; edi is 32bit-aligned here, write ecx>>2 32bit values
+.main:
+       push ecx
+       shr ecx, 2
+       rep stosd
+       pop ecx
+
+       ; write any trailing bytes
+       and ecx, 3
+       jmp [.post_tab + ecx * 4]
+
+.post_tab dd .done, .post1, .post2, .post3
+.post3:        stosb
+.post2:        stosb
+.post1:        stosb
+
+.done: pop eax
+       pop edi
+       pop ebp
+       ret
+
+
+       ; same as memset but copies 16bit values, and the count is the number
+       ; of 16bit values to copy, not bytes.
+       global memset16
+memset16:
+       push ebp
+       mov ebp, esp
+       push edi
+
+       mov edi, [ebp + 8]
+       push edi
+       mov ax, [ebp + 12]
+       shl eax, 16
+       mov ax, [ebp + 12]
+       mov ecx, [ebp + 16]
+
+       test ecx, ecx
+       jz .done
+
+       mov edx, edi
+       and edx, 3
+       jz .main
+       jmp [.pre_tab + edx * 4]
+
+.pre_tab dd .main, .pre1, .pre2, .pre3
+.pre3:
+       ; unaligned by +3:
+       ; same as next one, but jump to ms16main instead of falling through
+       mov [edi], al
+       mov [edi + ecx * 2 - 1], ah
+       rol eax, 8
+       inc edi
+       dec ecx
+       jz .done
+       jmp .main
+.pre1:
+       ; unaligned by +1:
+       ; - write low byte at edi
+       ; - write high byte at the end
+       ; - rotate by 8 for the rest
+       ; - decrement ecx
+       mov [edi], al
+       mov [edi + ecx * 2 - 1], ah
+       rol eax, 8
+       inc edi
+       dec ecx
+       jz .done
+.pre2:
+       ; unaligned by +2
+       stosw
+       dec ecx
+       jz .done
+
+.main:
+       push ecx
+       shr ecx, 1
+       rep stosd
+       pop ecx
+
+       and ecx, 1
+       jz .done
+       stosw
+.done:
+       pop eax
+       pop edi
+       pop ebp
+       ret
+
+
+       ; standard C memcpy
+       global memcpy
+memcpy:
+       push ebp
+       mov ebp, esp
+       push edi
+       push esi
+
+       mov edi, [ebp + 8]
+       mov esi, [ebp + 12]
+       mov ecx, [ebp + 16]
+
+       test ecx, ecx
+       jz .done
+
+       mov edx, ecx
+       shr ecx, 2
+       rep movsd
+
+       and edx, 3
+       jmp [.post_tab + edx * 4]
+
+.post_tab dd .done, .post1, .post2, .post3
+.post3:        movsb
+.post2:        movsb
+.post1:        movsb
+
+.done:
+       pop esi
+       pop edi
+       pop ebp
+       ret
+
+; vi:set ts=8 sts=8 sw=8 ft=nasm:
diff --git a/src/loader.asm b/src/loader.asm
new file mode 100644 (file)
index 0000000..41d9452
--- /dev/null
@@ -0,0 +1,570 @@
+       section .loader
+
+       extern startup
+       extern _ldr_main_start
+       extern _main_start
+       extern _main_size
+       extern boot_mem_map
+       extern boot_mem_map_size
+
+%include 'macros.inc'
+
+       [bits 16]
+       global _start
+_start:
+       cli
+       mov ax, cs
+       mov ds, ax
+       mov es, ax
+       mov fs, ax      ; this will store the original real mode segment
+       mov ss, ax
+       ; modify the return to real mode jump segment
+       mov [.jmpcs16 + 3], ax
+
+       xor ax, ax
+       mov sp, ax
+
+       ; check for VM86 and abort
+       mov eax, cr0
+       test ax, 1
+       jz .notvm86
+
+       mov si, str_errvm86
+       call printstr
+       jmp exit
+
+.notvm86:
+%ifdef BUG_WARNING
+       call warning    ; issue a warning and allow the user to abort
+%endif
+
+%ifdef CON_SERIAL
+       call setup_serial
+%endif
+       call enable_a20
+       call detect_memory
+
+       ; calculate GDT linear address
+       xor eax, eax
+       mov ax, cs
+       shl eax, 4
+       add eax, gdt
+       mov [gdt_base], eax
+
+       ; set tmp segment bases to match the linear address of our current seg
+       xor eax, eax
+       mov ax, cs
+       shl eax, 4
+       mov word [gdt + 18h + 2], ax    ; tmp pm code base
+       mov word [gdt + 20h + 2], ax    ; tmp pm data base
+       mov word [gdt + 28h + 2], ax    ; ret-to-realmode code
+       shr eax, 16
+       mov byte [gdt + 18h + 4], al
+       mov byte [gdt + 20h + 4], al
+       mov byte [gdt + 28h + 4], al
+
+       mov si, str_enterpm
+       call printstr
+
+       ; change video mode now, to avoid having to bring in all the int86 code
+       mov ax, 13h
+       int 10h
+
+       lgdt [gdt_lim]
+
+       mov eax, cr0
+       or eax, 1
+       mov cr0, eax
+
+       jmp 18h:.pm
+
+       [bits 32]
+.pm:   mov ax, 20h     ; tmp data selector
+       mov ds, ax
+       mov ax, 10h     ; dest data selector
+       mov es, ax
+
+       ; copy main program high
+       cld
+       mov esi, _ldr_main_start
+       mov edi, _main_start
+       mov ecx, _main_size + 3
+       shr ecx, 2
+       rep movsd
+
+       ; copy memory map
+       mov eax, [mem_map_size]
+       mov [es:boot_mem_map_size], eax
+       mov esi, mem_map
+       mov edi, boot_mem_map
+       mov ecx, 32     ; 128 bytes
+       rep movsd
+
+       mov ax, 10h
+       mov ds, ax
+       mov ss, ax
+       mov esp, _main_start
+
+       call 8:startup
+       cli
+
+       ; return to real mode
+       jmp 28h:.rm
+
+       [bits 16]
+.rm:   mov eax, cr0
+       and ax, 0xfffe
+       mov cr0, eax
+.jmpcs16:
+       jmp 42h:.loadcs16       ; 42 seg is modifed at the start
+.loadcs16:
+       mov ax, fs
+       mov ds, ax
+       mov es, ax
+       mov ss, ax
+
+       ; restore real-mode IVT
+       lidt [rmidt]
+       sti
+
+       mov ax, 3
+       int 10h
+exit:  mov ax, 4c00h
+       int 21h
+
+str_errvm86 db 'Error: memory manager detected! Stop it and try again (e.g. emm386 off)',10,0
+str_enterpm db 'Entering 32bit protected mode ...',10,0
+
+
+%ifdef BUG_WARNING
+       ; warns the user about the experimental and buggy state of this
+       ; protected mode system, and allows aborting if having to reboot later
+       ; is not acceptable.
+warning:
+       mov si, str_warnmsg
+       call printstr
+       WAITKEY
+       dec al
+       jz exit
+       ret
+
+str_warnmsg:
+       db 'WARNING: this program uses an experimental protected mode kernel, which is ',10
+       db 'still in a very early stage of development, and will probably not be able to ',10
+       db 'return cleanly to DOS on exit. You might be forced to reboot after quitting!',10
+       db 'If this is not acceptable, press ESC now to abort.',10,10
+       db 'Press ESC to abort and return to DOS, any other key to continue...',10,0
+%endif ; BUG_WARNING
+
+
+; ---------------------- A20 address line enable -----------------------
+
+enable_a20:
+       call test_a20
+       jnc .ret
+
+       mov si, .infomsg
+       call printstr           ; print "Enable A20 line ... "
+
+       call enable_a20_kbd
+       call test_a20
+       jnc .done
+       call enable_a20_fast
+       call test_a20
+       jnc .done
+       mov si, .failstr
+       call printstr
+       mov ax, 4c00h
+       int 21h
+.done: mov si, .okstr
+       call printstr
+.ret:  ret
+
+.infomsg db 'Enable A20 line:',0
+.failstr db ' failed.',10,0
+.okstr db ' success.',10,0
+
+       ; CF = 1 if A20 test fails (not enabled)
+test_a20:
+       push ds
+       push es
+       xor ax, ax
+       mov ds, ax
+       not ax
+       mov es, ax
+       mov si, 420h
+       mov di, 430h
+       mov dl, [ds:si]
+       mov dh, [es:di]
+       mov [ds:si], al
+       not ax
+       mov [es:di], al
+       cmp al, [ds:si]
+       clc
+       jnz .done
+       stc
+.done: mov [ds:si], dl
+       mov [es:di], dh
+       pop es
+       pop ds
+       ret
+
+enable_a20_fast:
+       mov si, .info
+       call printstr
+       in al, 92h
+       or al, 2
+       out 92h, al
+       ret
+.info db ' fast ...',0
+
+KBC_DATA_PORT equ 0x60
+KBC_CMD_PORT equ 0x64
+KBC_STATUS_PORT equ 0x64
+KBC_CMD_WR_OUTPORT equ 0xd1
+
+KBC_STAT_IN_FULL equ 2
+
+enable_a20_kbd:
+       mov si, .info
+       call printstr
+       call kbc_wait_write
+       mov al, KBC_CMD_WR_OUTPORT
+       out KBC_CMD_PORT, al
+       call kbc_wait_write
+       mov al, 0xdf
+       out KBC_DATA_PORT, al
+       ret
+.info db ' kbd ...',0
+
+kbc_wait_write:
+       in al, KBC_STATUS_PORT
+       and al, KBC_STAT_IN_FULL
+       jnz kbc_wait_write
+       ret
+
+
+; ---------------------- memory detection -----------------------
+
+detect_memory:
+       mov si, memdet_detram
+       call printstr
+       mov si, memdet_e820_msg
+       call printstr
+       call detect_mem_e820
+       jnc .done
+       mov si, str_fail
+       call printstr
+
+       mov si, memdet_detram
+       call printstr
+       mov si, memdet_e801_msg
+       call printstr
+       call detect_mem_e801
+       jnc .done
+       mov si, str_fail
+       call printstr
+
+       mov si, memdet_detram
+       call printstr
+       mov esi, memdet_88_msg
+       call printstr
+       call detect_mem_88
+       jnc .done
+       mov esi, str_fail
+       call printstr
+
+       mov si, memdet_detram
+       call printstr
+       mov esi, memdet_cmos_msg
+       call printstr
+       call detect_mem_cmos
+       jnc .done
+       mov esi, str_fail
+       call printstr
+
+       mov si, memdet_fail_msg
+       call printstr
+       jmp exit
+.done:
+       mov si, str_ok
+       call printstr
+       ret
+
+str_ok db 'OK',10,0
+str_fail db 'failed',10,0
+memdet_fail_msg db 'Failed to detect available memory!',10,0
+memdet_detram  db 'Detecting RAM '
+memdet_e820_msg db '(BIOS 15h/0xe820)... ',0
+memdet_e801_msg db '(BIOS 15h/0xe801)... ',0
+memdet_88_msg  db '(BIOS 15h/0x88, max 64mb)... ',0
+memdet_cmos_msg db '(CMOS)...',0
+
+       ; detect extended memory using BIOS call 15h/e820
+detect_mem_e820:
+       mov dword [mem_map_size], 0
+
+       mov edi, .buffer
+       xor ebx, ebx
+       mov edx, 534d4150h
+
+.looptop:
+       mov eax, 0xe820
+       mov ecx, 24
+       int 15h
+       jc .fail
+       cmp eax, 534d4150h
+       jnz .fail
+
+       ; skip areas starting above 4GB as we won't be able to use them
+       cmp dword [di + 4], 0
+       jnz .skip
+
+       ; only care for type 1 (usable ram), otherwise ignore
+       cmp dword [di + 16], 1
+       jnz .skip
+
+       mov eax, [.buffer]
+       mov esi, mem_map
+       mov ebp, [mem_map_size]
+       mov [ebp * 8 + esi], eax
+
+       ; skip areas with 0 size (also clamp size to 4gb)
+       ; test high 32bits
+       cmp dword [edi + 12], 0
+       jz .highzero
+       ; high part is non-zero, make low part ffffffff
+       xor eax, eax
+       not eax
+       jmp .skiph0
+
+.highzero:
+       ; if both high and low parts are zero, ignore
+       mov eax, [di + 8]
+       test eax, eax
+       jz .skip
+
+.skiph0:mov [ebp * 8 + esi + 4], eax
+       inc dword [mem_map_size]
+
+.skip:
+       ; terminate the loop if ebx was reset to 0
+       test ebx, ebx
+       jz .done
+       jmp .looptop
+.done:
+       clc
+       ret
+
+.fail: ; if size > 0, then it's not a failure, just the end
+       cmp dword [mem_map_size], 0
+       jnz .done
+       stc
+       ret
+
+.buffer times 32 db 0
+
+       ; detect extended memory using BIOS call 15h/e801
+detect_mem_e801:
+       mov si, mem_map
+       mov ebp, [mem_map_size]
+       mov dword [ebp], 0
+
+       xor cx, cx
+       xor dx, dx
+       mov ax, 0xe801
+       int 15h
+       jc .fail
+
+       test cx, cx
+       jnz .foo1
+       test ax, ax
+       jz .fail
+       mov cx, ax
+       mov dx, bx
+
+.foo1: mov dword [si], 100000h
+       movzx eax, cx
+       ; first size is in KB, convert to bytes
+       shl eax, 10
+       jnc .foo2
+       ; overflow means it's >4GB, clamp to 4GB
+       mov eax, 0xffffffff
+.foo2: mov [si + 4], eax
+       inc dword [mem_map_size]
+       test dx, dx
+       jz .done
+       mov dword [si + 8], 1000000h
+       movzx eax, dx
+       ; second size is in 64kb blocks, convert to bytes
+       shl eax, 16
+       jnc .foo3
+       ; overflow means it's >4GB, clamp to 4GB
+       mov eax, 0xffffffff
+.foo3: mov [si + 12], eax
+       inc dword [mem_map_size]
+.done:
+       clc
+       ret
+.fail:
+       stc
+       ret
+
+detect_mem_88:
+       ; reportedly some BIOS implementations fail to clear CF on success
+       clc
+       mov ah, 88h
+       int 15h
+       jc .fail
+
+       test ax, ax
+       jz .fail
+
+       ; ax has size in KB, convert to bytes in eax
+       and eax, 0xffff
+       shl eax, 10
+
+       mov esi, mem_map
+       mov dword [si], 100000h
+       mov [si + 4], eax
+
+       mov dword [mem_map_size], 1
+       clc
+       ret
+.fail: stc
+       ret
+
+detect_mem_cmos:
+       mov al, 31h
+       out 70h, al
+       in al, 71h
+       mov ah, al
+       mov al, 30h
+       out 70h, al
+       in al, 71h
+
+       test ax, ax
+       jz .fail
+
+       ; ax has size in KB, convert to bytes in eax
+       and eax, 0xffff
+       shl eax, 10
+
+       mov esi, mem_map
+       mov dword [si], 100000h
+       mov [si + 4], eax
+       mov dword [mem_map_size], 1
+       clc
+       ret
+.fail: stc
+       ret
+
+
+       align 4
+mem_map_size dd 0
+mem_map times 128 db 0
+
+
+; ----------------------- serial console ------------------------
+
+%ifdef CON_SERIAL
+setup_serial:
+       ; set clock divisor
+       mov dx, UART_BASE + 3   ; LCTL
+       mov al, 80h             ; DLAB
+       out dx, al
+       mov dx, UART_BASE       ; DIVLO
+       mov al, UART_DIVISOR & 0xff
+       out dx, al
+       inc dx                  ; DIVHI
+       mov al, UART_DIVISOR >> 8
+       out dx, al
+       mov dx, UART_BASE + 3   ; LCTL
+       mov al, 3               ; 8n1
+       out dx, al
+       inc dx                  ; MCTL
+       mov al, 0xb             ; DTR/RTS/OUT2
+       out dx, al
+       ret
+
+ser_putchar:
+       SER_PUTCHAR
+       ret
+
+ser_printstr:
+       lodsb
+       test al, al
+       jz .end
+       cmp al, 10
+       jnz .nolf
+       push ax
+       mov al, 13
+       call ser_putchar
+       pop ax
+.nolf: call ser_putchar
+       jmp ser_printstr
+.end:  ret
+
+%endif ; def CON_SERIAL
+
+
+printstr:
+       lodsb
+       test al, al
+       jz .end
+       cmp al, 10      ; check for line-feed and insert CR before it
+       jnz .nolf
+       push ax
+%ifdef CON_SERIAL
+       mov al, 13
+       call ser_putchar
+%endif
+       mov dl, 13
+       mov ah, 2
+       int 21h
+       pop ax
+.nolf:
+%ifdef CON_SERIAL
+       call ser_putchar
+%endif
+       mov ah, 2
+       mov dl, al
+       int 21h
+       jmp printstr
+.end:  ret
+
+
+       align 4
+enterpm dd 0xbad00d    ; space for linear address for far jump to pmode
+enterpm_sel dw 8       ; selector for far jump to protected mode
+       align 4
+gdt_lim dw 47          ; GDT limit
+gdt_base dd 0xbadf00d  ; space for GDT linear address
+
+       align 8
+gdt:   ; 0: null segment
+       dd 0
+       dd 0
+       ; 1: code - 0/lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:code/non-conf/rd (sel: 8)
+       dd 0000ffffh
+       dd 00cf9a00h
+       ; 2: data - 0/lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:data/rw (sel: 10h)
+       dd 0000ffffh
+       dd 00cf9200h
+       ; 3: tmp code (will set base before entering pmode) (sel: 18h)
+       dd 0000ffffh
+       dd 00cf9a00h
+       ; 4: tmp data (will set base before entering pmode) (sel: 20h)
+       dd 0000ffffh
+       dd 00cf9200h
+       ; 5: return to real-mode 16bit code segment (sel: 28h)
+       dd 0000ffffh
+       dd 00009a00h
+
+       ; pseudo IDTR descriptor for real-mode IVT at address 0
+       align 4
+       dw 0
+rmidt: dw 3ffh         ; IVT limit (1kb / 256 entries)
+       dd 0            ; IVT base 0
+
+; vi:set ts=8 sts=8 sw=8 ft=nasm:
diff --git a/src/macros.inc b/src/macros.inc
new file mode 100644 (file)
index 0000000..ce5250a
--- /dev/null
@@ -0,0 +1,33 @@
+%define BUG_WARNING
+%define CON_SERIAL
+
+UART_BASE      equ 2f8h                ; COM1: 3f8, COM2: 2f8
+UART_DIVISOR   equ 115200 / 9600       ; 9600 baud
+
+%macro WAITKEY 0
+%%waitkey:
+       in al, 64h
+       test al, 1
+       jz %%waitkey
+       in al, 60h
+%endmacro
+
+%macro SER_PUTCHAR 0
+       push dx
+       push ax
+       mov dx, UART_BASE + 5   ; LSTAT
+%%wait:        in al, dx
+       test al, 20h            ; TREG_EMPTY
+       jz %%wait
+       mov dx, UART_BASE
+       pop ax
+       out dx, al
+       pop dx
+%endmacro
+
+%macro PMPRINT 1
+       mov al, %1
+       SER_PUTCHAR
+%endmacro
+
+; vi:set ts=8 sts=8 sw=8 ft=nasm:
diff --git a/src/startup.asm b/src/startup.asm
new file mode 100644 (file)
index 0000000..c1d85e5
--- /dev/null
@@ -0,0 +1,47 @@
+       bits 32
+       section .text
+
+%include 'macros.inc'
+
+       extern main
+       extern _bss_start
+       extern _bss_end
+
+       global startup
+startup:
+       ; clear .bss
+       mov eax, _bss_end
+       sub eax, _bss_start
+       test eax, eax
+       jz .nobss
+       mov ecx, eax
+       mov edi, _bss_start
+       xor eax, eax
+       shr ecx, 2
+       rep stosd
+.nobss:
+
+       xor ebp, ebp    ; terminate backtraces
+       call main
+       retf
+
+;      global putchar
+;putchar:
+;      mov eax, [esp + 4]
+;      cmp al, 10
+;      jnz .nonl
+;      push eax
+;      mov al, 13
+;      SER_PUTCHAR
+;      pop eax
+;.nonl:        SER_PUTCHAR
+;      ret
+
+       section .data
+       global boot_mem_map_size
+       global boot_mem_map
+       align 4
+boot_mem_map_size dd 0
+boot_mem_map times 128 db 0
+
+; vi:set ts=8 sts=8 sw=8 ft=nasm: