RUN
disasm
dosbox.conf
+*.log
+tofloppy
warn = -pedantic -Wall
inc = -Isrc -Isrc/libc
+AS = nasm
+
+ASFLAGS = -Isrc/
CFLAGS = -m32 -march=i386 $(warn) $(opt) $(dbg) -fno-pic -ffreestanding -nostdinc $(inc) $(def)
LDFLAGS = -m elf_i386 -nostdlib -T com32.ld -Map test.map
-include $(dep)
%.o: %.asm
- nasm -o $@ -f elf $<
+ $(AS) -o $@ -f elf $(ASFLAGS) $<
%.s: %.c
$(CC) $(CFLAGS) -S $< -o $@
--- /dev/null
+/*
+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 (%%esp), %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)
+
+
+#endif /* ASMOPS_H_ */
--- /dev/null
+/*
+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 PCBOOT_CONFIG_H_
+#define PCBOOT_CONFIG_H_
+
+/* frequency of generated timer ticks in hertz */
+#define TICK_FREQ_HZ 250
+
+#define CON_TEXTMODE
+#define CON_SERIAL
+
+#endif /* PCBOOT_CONFIG_H_ */
--- /dev/null
+/*
+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 <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_setstart(int y);
+static inline unsigned char crtc_read(int reg);
+static inline void crtc_write(int reg, unsigned char val);
+
+extern 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;
+
+ con_show_cursor(1);
+ crtc_setstart(0);
+ crtc_cursor(cursor_x, cursor_y);
+ 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_setstart(int y)
+{
+ unsigned int addr = y * NCOLS;
+
+ crtc_write(CRTC_REG_START_L, addr);
+ crtc_write(CRTC_REG_START_H, addr >> 8);
+}
+
+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);
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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)
+
+
+void init_pic(void);
+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]
+ */
+ init_pic();
+ eoi_pending = 0;
+}
+
+/* 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 %d, error code: %d\n", frm.inum, frm.err);
+ }
+ printf("unhandled interrupt %d\n", frm.inum);
+ }
+
+ disable_intr();
+ if(eoi_pending) {
+ end_of_irq(INTR_TO_IRQ(eoi_pending));
+ }
+}
+
+void init_pic(void)
+{
+ prog_pic(IRQ_OFFSET);
+}
+
+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_KCODE, 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
--- /dev/null
+/*
+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);
+
+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_ */
--- /dev/null
+; 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:
--- /dev/null
+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)
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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;
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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 */
--- /dev/null
+/*
+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));
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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();
+ }
+}
--- /dev/null
+/*
+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);
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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;
+}
+*/
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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);
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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];
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+; 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:
section .loader
extern startup
-
extern _ldr_main_start
extern _main_start
extern _main_size
+%include 'macros.inc'
+
[bits 16]
global _start
_start:
jmp exit
.notvm86:
+ call setup_serial
call enable_a20
; calculate GDT linear address
call 8:startup
+ PMPRINT 'X'
+ PMPRINT 'Y'
+ PMPRINT 'Z'
+ PMPRINT 'Z'
+ PMPRINT 'Y'
+ PMPRINT 13
+ PMPRINT 10
+
; return to real mode
jmp 28h:.rm
exit: mov ax, 4c00h
int 21h
+dbg_inpm db 'DBG: in pmode',10,0
+dbg_retrm db 'DBG: returning to real mode...',10,0
+
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
jnz kbc_wait_write
ret
+UART_BASE equ 2f8h ; COM1: 3f8, COM2: 2f8
+UART_DIVISOR equ 115200 / 9600 ; 9600 baud
+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
+
printstr:
lodsb
test al, al
cmp al, 10 ; check for line-feed and insert CR before it
jnz .nolf
push ax
+ mov al, 13
+ call ser_putchar
mov dl, 13
mov ah, 2
int 21h
pop ax
-.nolf: mov ah, 2
+.nolf: call ser_putchar
+ mov ah, 2
mov dl, al
int 21h
jmp printstr
.end: ret
+ [bits 32]
+ser_putchar_pmode:
+ SER_PUTCHAR
+ ret
+
+ser_putstr_pmode:
+ lodsb
+ test al, al
+ jz .end
+ cmp al, 10
+ jnz .nolf
+ push ax
+ mov al, 13
+ call ser_putchar_pmode
+ pop ax
+.nolf: call ser_putchar_pmode
+ jmp ser_putstr_pmode
+.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
--- /dev/null
+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 ; TRIG_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:
--- /dev/null
+void putchar(int c); /* in startup.asm */
+
+static void printstr(const char *s)
+{
+ while(*s) {
+ putchar(*s++);
+ }
+}
+
+int main(void)
+{
+ printstr("hello from C\n");
+ return 0;
+}
--- /dev/null
+/*
+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");
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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 <stdarg.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(®s, 0, sizeof regs);
+ get_regs(®s);
+
+ 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();
+}
--- /dev/null
+/*
+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));
+
+#endif /* PANIC_H_ */
--- /dev/null
+ 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:
--- /dev/null
+/*
+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 <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);
+
+
+/* our global descriptor table */
+static desc_t gdt[NUM_SEGMENTS] __attribute__((aligned(8)));
+
+
+void init_segm(void)
+{
+ memset(gdt, 0, sizeof gdt);
+ segm_desc(gdt + SEGM_KCODE, 0, 0xffffffff, 0, TYPE_CODE);
+ segm_desc(gdt + SEGM_KDATA, 0, 0xffffffff, 0, TYPE_DATA);
+ segm_desc(gdt + SEGM_UCODE, 0, 0xffffffff, 3, TYPE_CODE);
+ segm_desc(gdt + SEGM_UDATA, 0, 0xffffffff, 3, TYPE_DATA);
+ segm_desc16(gdt + SEGM_CODE16, 0, 0xffff, 0, TYPE_CODE);
+
+ set_gdt((uint32_t)gdt, sizeof gdt - 1);
+
+ setup_selectors(selector(SEGM_KCODE, 0), selector(SEGM_KDATA, 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;
+}
+
+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<6; i++) {
+ print_desc(gdt + i);
+ }
+}
+
+static void print_desc(desc_t *desc)
+{
+ uint32_t base, limit;
+ int dpl, g, db, l, avl, p, s, type;
+ char *type_str;
+
+ 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;
+ type = (desc->d[2] >> 8) & 0xf;
+ g = (desc->d[3] >> 23) & 1;
+ db = (desc->d[3] >> 22) & 1;
+ l = (desc->d[3] >> 21) & 1;
+ avl = (desc->d[3] >> 20) & 1;
+
+ p = (desc->d[2] >> 15) & 1;
+ s = (desc->d[2] >> 12) & 1;
+}
+*/
--- /dev/null
+/*
+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_
+
+enum {
+ SEGM_KCODE = 1,
+ SEGM_KDATA = 2,
+ SEGM_UCODE,
+ SEGM_UDATA,
+ SEGM_TASK,
+ SEGM_CODE16,
+
+ NUM_SEGMENTS
+};
+
+void init_segm(void);
+
+uint16_t selector(int idx, int rpl);
+
+void set_tss(uint32_t addr);
+
+
+#endif /* SEGM_H_ */
--- /dev/null
+; 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:
--- /dev/null
+/*
+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 "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
+
+struct serial_port {
+ int base, intr;
+ int blocking;
+
+ char inbuf[256];
+ int inbuf_ridx, inbuf_widx;
+};
+
+#define BNEXT(x) (((x) + 1) & 0xff)
+#define BEMPTY(b) (b##_ridx == b##_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 base, intr;
+ unsigned int fmt;
+
+ if(pidx < 0 || pidx > 1) {
+ printf("ser_open: invalid serial port: %d\n", pidx);
+ return -1;
+ }
+
+ if(ports[pidx].base) {
+ printf("ser_open: port %d already open!\n", pidx);
+ return -1;
+ }
+ memset(ports + pidx, 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[pidx].base = base;
+ ports[pidx].intr = intr;
+ ports[pidx].blocking = 1;
+ ++num_open;
+ return pidx;
+}
+
+void ser_close(int fd)
+{
+ if(--num_open == 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 char linebuf[512];
+ static int widx;
+ int i, rd, size, offs;
+
+ size = sizeof linebuf - widx;
+ 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
--- /dev/null
+/*
+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_ */
bits 32
section .text
+%include 'macros.inc'
+
+ extern main
+
global startup
startup:
- mov ebx, 0xb8000
+ mov ebx, 0xb8000 + 156
mov byte [ebx], '@'
+ mov byte [ebx + 2], '@'
+ mov byte [ebx + 160], '@'
+ mov byte [ebx + 162], '@'
+
+ call main
+
.waitkey:
in al, 64h
test al, 1
in al, 60h
ret
+ 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
+
; vi:set ts=8 sts=8 sw=8 ft=nasm:
--- /dev/null
+/*
+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_ */