├── .gitignore ├── Makefile ├── README.md ├── app.c ├── bochsrc.txt ├── boot.s ├── intr.s ├── linker.ld ├── screenshot.png ├── util.c └── util.h /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | tmp/ 3 | build/* 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | AS = as 2 | CC = gcc 3 | LD = ld 4 | OBJCOPY = objcopy 5 | 6 | # On Mac, install i386-elf-gcc and i386-elf-binutils packages from MacPorts 7 | # 8 | # AS = i386-elf-as 9 | # CC = i386-elf-gcc 10 | # LD = i386-elf-ld 11 | # OBJCOPY = i386-elf-objcopy 12 | 13 | ASFLAGS = -march=i386 --32 14 | CFLAGS = -ffreestanding -m16 -march=i386 -mpreferred-stack-boundary=2 -Wall -Wextra -pedantic 15 | LDFLAGS = -Tlinker.ld -nostdlib -m elf_i386 16 | 17 | OBJDIR = build 18 | 19 | COMMON_OBJS = $(addprefix $(OBJDIR)/, intr.o util.o) 20 | BINS = $(addprefix $(OBJDIR)/,boot.bin app.bin) 21 | APP = app 22 | DISK = $(OBJDIR)/disk.img 23 | 24 | .PHONY: clean disk 25 | 26 | # don't remove intermediate files 27 | .SECONDARY: 28 | 29 | all: $(OBJDIR) $(BINS) 30 | 31 | $(OBJDIR): 32 | mkdir -p $(OBJDIR) 33 | 34 | $(OBJDIR)/%.bin: $(OBJDIR)/%.elf 35 | $(OBJCOPY) -O binary $< $@ 36 | 37 | $(OBJDIR)/boot.elf: boot.s 38 | $(AS) $(ASFLAGS) $< -o $@ 39 | 40 | $(OBJDIR)/%.elf: $(OBJDIR)/%.o $(COMMON_OBJS) 41 | $(LD) $(LDFLAGS) $^ -o $@ 42 | 43 | $(OBJDIR)/%.o: %.s 44 | $(AS) $(ASFLAGS) $< -o $@ 45 | 46 | $(OBJDIR)/%.o: %.c *.h 47 | $(CC) $(CFLAGS) -c $< -o $@ 48 | 49 | disk: all 50 | dd if=/dev/zero of=$(DISK) bs=512 count=2880 51 | dd if=$(OBJDIR)/boot.bin of=$(DISK) bs=512 conv=notrunc 52 | dd if=$(OBJDIR)/boot.bin of=$(DISK) bs=512 conv=notrunc seek=1 53 | dd if=$(OBJDIR)/$(APP).bin of=$(DISK) bs=512 conv=notrunc seek=2 54 | 55 | qemu: all disk 56 | qemu-system-i386 -drive format=raw,file=$(DISK) 57 | 58 | bochs: all disk 59 | bochs -f bochsrc.txt 60 | 61 | clean: 62 | rm -rf build 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | This repo demonstrates how to create C programs for the x86 platform, which: 4 | 5 | - boot directly from a USB drive / SD card 6 | - don't require any operating system code 7 | - don't require writing any custom drivers 8 | - only require small and fixed amount of assembly code 9 | - can access the BIOS API directly from C 10 | 11 | To achieve this, they have several limitations: 12 | 13 | - they don't have access to the standard C library 14 | - they only work in the [real-address mode](http://www.logix.cz/michal/doc/i386/chp14-00.htm) 15 | - the final binary is limited to ~64KB 16 | - the available RAM is limited to ~640KB 17 | - the boot-loader is not guaranteed to work on every PC 18 | 19 | 20 | 21 | ## Prerequisites 22 | 23 | - GCC with support for i386 targets 24 | - GNU binutils 25 | - QEMU / Bochs (for testing and debugging) 26 | - A PC supporting USB boot in BIOS ("legacy") mode 27 | - A spare USB disk / SD card 28 | 29 | On Mac I recommend installing `i386-elf-gcc` & `i386-elf-binutils` packages from MacPorts, 30 | and updating Makefile accordingly 31 | 32 | ## Compiling: 33 | ``` 34 | $ make 35 | ``` 36 | 37 | ## Testing in emulators: 38 | ``` 39 | $ make qemu 40 | $ make bochs 41 | ``` 42 | 43 | ## Installing on a USB disk / SD card: 44 | 45 | Be careful to pick the right device, this will overwrite your data! 46 | 47 | ``` 48 | $ make disk 49 | $ sudo dd if=build/disk.img of=/dev/ 50 | ``` 51 | 52 | ## Code overview 53 | 54 | #### Overall structure 55 | 56 | The final app consists of two binaries: a boot loader and the actual 57 | program. Both are created by compiling the source code with gcc 58 | and GNU assembler, linking the program with GNU ld, and converting 59 | the resulting ELF files to flat binaries using objcopy. 60 | 61 | To ensure that our `main()` is always the first code in the program 62 | binary, we move it to a separate `.start` section using the 63 | [`ENTRY_POINT`](https://github.com/luke8086/boot2c/blob/master/util.h) 64 | macro, and emit it at the top of the file 65 | using a custom 66 | [linker script](https://github.com/luke8086/boot2c/blob/master/linker.ld) 67 | 68 | #### 16 vs 32 bit 69 | 70 | It is [possible](http://www.logix.cz/michal/doc/i386/chp16-00.htm) 71 | to use 32-bit instructions in the real-address mode, they just need 72 | to be marked with address-size and operand-size prefixes. The 73 | `-m16` option for gcc, and `.code16` directive in GNU assembler, do exactly 74 | that. The resulting code is 32-bit, only marked everywhere with those 75 | prefixes. It's not compatible with actual 16-bit CPUs. 76 | 77 | Unfortunately the 32-bit addresses still cannot exceed the boundary 78 | of the [segment](http://www.logix.cz/michal/doc/i386/chp14-01.htm#14-01) 79 | (65535), otherwise they'll trigger an exception. QEMU 80 | doesn't emulate this behaviour, so it's useful to occasionally test 81 | with Bochs. 82 | 83 | #### Bootloader 84 | 85 | The boot loader 86 | ([boot.s](https://github.com/luke8086/boot2c/blob/master/boot.s)) 87 | loads the main program from the 88 | startup disk to the memory segment at 0x10000, and jumps to the 89 | starting point at offset 0. 90 | It assumes that BIOS will emulate the USB disk either as 91 | a HDD or a floppy. To make it more likely, it includes a basic MBR 92 | partition table. Just in case, we install it both to the main boot 93 | sector, and the boot sector of the first active partition. 94 | 95 | Since USB booting 96 | [is not](https://wiki.osdev.org/Problems_Booting_From_USB_Flash) 97 | a standardized process, it may not work on every PC. I only really tested 98 | on mine, and it still behaved in two different ways for a USB stick 99 | and an SD card. 100 | In case it doesn't work for you, you can try an 101 | [alternative](#installing-the-syslinux-boot-loader) loader. 102 | 103 | #### Calling BIOS services 104 | 105 | The services provided by BIOS are primarily accessed by saving their 106 | method number and arguments to CPU registers, and triggering a software 107 | interrupt. Some of them store return values back to the registers. 108 | 109 | To avoid writing separate assembly code for every service, we define a generic 110 | function 111 | ([intr.h](https://github.com/luke8086/boot2c/blob/master/intr.h), 112 | [intr.s](https://github.com/luke8086/boot2c/blob/master/intr.s)), 113 | taking the interrupt 114 | number and a pointer to a struct holding register values. 115 | 116 | #### Memory segmentation 117 | 118 | Modern compilers don't have a concept of far pointers, so we can't 119 | seamlessly access memory outside of the current code / data segments. 120 | This is the main factor limiting our binary to 64K. 121 | 122 | Fortunately, gcc has support for ["address spaces"](https://gcc.gnu.org/onlinedocs/gcc-6.1.0/gcc/Named-Address-Spaces.html#index-x86-named-address-spaces-3132) 123 | relative to FS and GS. We include `set_fs` and `get_fs` functions 124 | to set values of these registers, for example to access the text-mode video memory 125 | at `b800:0000` (see [bios.h](https://github.com/luke8086/boot2c/blob/master/bios.h)) 126 | 127 | #### Standard library 128 | 129 | The standard C library depends on the operating system, so we can't 130 | use it in standalone programs (hence the `-ffreestanding` and `-nostdlib` 131 | flags). However, for certain operations, like initialising a struct on 132 | the stack, the compiler may still generate implicit calls to standard 133 | functions, like `memcpy`. In such cases, we just need to provide 134 | our own versions (see 135 | [util.h](https://github.com/luke8086/boot2c/blob/master/util.h) and 136 | [util.c](https://github.com/luke8086/boot2c/blob/master/util.c)). 137 | 138 | ## Troubleshooting 139 | 140 | More likely than not, working on standalone programs will require some 141 | tinkering. Below are some hints: 142 | 143 | #### Installing the syslinux boot loader 144 | 145 | In case the provided boot loader doesn't work, you may experiment with the 146 | one of syslinux: 147 | 148 | ``` 149 | $ wget https://mirrors.edge.kernel.org/pub/linux/utils/boot/syslinux/6.xx/syslinux-6.03.tar.gz 150 | $ tar zxf syslinux-6.03.tar.gz 151 | $ make disk 152 | $ dd if=syslinux-6.03/bios/mbr/mbr.bin of=build/disk.img conv=notrunc 153 | ``` 154 | 155 | #### Disassembling files 156 | 157 | By default, objdump will disassemble our binaries with no complaints, 158 | showing a completely incorrect output. Since the code is compiled to run 159 | in 16-bit mode, we need to add `-m i8086`: 160 | 161 | ```bash 162 | $ objdump -D -m i8086 build/app.o 163 | $ objdump -D -m i8086 build/app.elf 164 | $ objdump -D -b binary -m i8086 build/app.bin 165 | ``` 166 | 167 | #### Debugging with Bochs 168 | 169 | Bochs is one of the slowest emulators, but often more accurate than others. 170 | Its debugger seems to handle 16-bit code slightly better than GDB with QEMU. 171 | The `xchg %bx, bx` instruction can be used to set a breakpoint, in C it's 172 | available using `BOCHS_BREAKPOINT` macro. The system clock is completely 173 | inaccurate, so it's not that useful for testing games / animations. 174 | 175 | #### Running in VirtualBox 176 | 177 | The easiest way to test in VirtualBox is by attaching `disk.img` as a raw 178 | image of a floppy. However, it imposes a limit on the amount of sectors 179 | that can be read (with a single BIOS call) to 0x48. So you'll need to replace 180 | `mov $0x027f, %ax` with `mov $0x0248, %ax` in `boot.s` 181 | 182 | #### Writing assembly functions 183 | 184 | In case you want to write any custom assembly function, be sure to use 185 | 32-bit `ret` (i.e. `retl` in GNU as), otherwise it'll leave the stack 186 | shifted by 2 bytes. 187 | 188 | ## References 189 | 190 | Lists of available BIOS services: 191 | 192 | - [HelpPC Reference Library](http://stanislavs.org/helppc/) - A well-organized 193 | quick reference of interrupts and other relevant topics 194 | - [Ralf Brown's Interrupt List](http://www.ctyme.com/rbrown.htm) - The ultimate list 195 | 196 | If you'd like to learn more: 197 | 198 | - [OSDev Wiki](https://wiki.osdev.org) - Tons of knowledge about low-level development, primarily focused on writing operating systems 199 | - [Intel 80386 Programmer's Reference Manual](http://www.logix.cz/michal/doc/i386/) 200 | - [80286 and 80287 Programmer's Reference Manual](https://duckduckgo.com/?q="80286+and+80287+Programmers+Reference+Manual") 201 | - [System BIOS for IBM PC/XT/AT computers and compatibles](https://duckduckgo.com/?q="System+BIOS+for+IBM+PC%2FXT%2FAT+computers+and+compatibles") - Another comprehensive reference of BIOS 202 | - [IBM 5150 Technical Reference](https://duckduckgo.com/?q=ibm+5150+technical+reference) 203 | (includes the source code of the original BIOS) 204 | - [IBM PC BIOS source code reconstruction](https://sites.google.com/site/pcdosretro/ibmpcbios) 205 | -------------------------------------------------------------------------------- /app.c: -------------------------------------------------------------------------------- 1 | /* 2 | * app.c - Sample standalone C app for x86 3 | * 4 | * author: luke8086 5 | * license: GPL-2 6 | */ 7 | 8 | #include "util.h" 9 | 10 | /* Use 2 columns per 1 row, to achieve square blocks */ 11 | #define WIDTH (TEXT_WIDTH / 2) 12 | #define HEIGHT (TEXT_HEIGHT) 13 | 14 | /* Duration of a single frame in ticks of the system timer */ 15 | #define FRAME_TICKS 3 16 | 17 | struct coords { 18 | int x, y; 19 | }; 20 | 21 | struct body { 22 | struct coords coords[WIDTH * HEIGHT]; 23 | struct coords *head; 24 | struct coords *tail; 25 | int grow; 26 | }; 27 | 28 | enum dir { 29 | DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT 30 | }; 31 | 32 | /* Block types correpond to their foreground colors */ 33 | enum block { 34 | PT_FLOOR = CL_BLACK, 35 | PT_WALL = CL_LIGHT_CYAN, 36 | PT_SNAKE = CL_YELLOW, 37 | PT_FRUIT = CL_LIGHT_MAGENTA 38 | }; 39 | 40 | static void 41 | set_block(int x, int y, enum block block) 42 | { 43 | struct attr_char ch = { .ascii = 0xdb, .attr = block }; 44 | CHAR_AT(x * 2, y) = CHAR_AT(x * 2 + 1, y) = ch; 45 | } 46 | 47 | static enum block 48 | check_block(struct coords c) 49 | { 50 | return CHAR_AT(c.x * 2, c.y).attr; 51 | } 52 | 53 | static void 54 | fill_rect(int x, int y, int w, int h, enum block block) 55 | { 56 | for (int j = 0; j < h; ++j) { 57 | for (int i = 0; i < w; ++i) { 58 | set_block(x + i, y + j, block); 59 | } 60 | } 61 | } 62 | 63 | static void 64 | draw_board(void) 65 | { 66 | fill_rect(0, 0, WIDTH, HEIGHT, PT_WALL); 67 | fill_rect(1, 1, WIDTH - 2, HEIGHT - 2, PT_FLOOR); 68 | fill_rect((WIDTH - 6) / 2, 0, 6, 1, PT_FLOOR); 69 | fill_rect((WIDTH - 6) / 2, HEIGHT - 1, 6, 1, PT_FLOOR); 70 | fill_rect(0, (HEIGHT - 5) / 2, 1, 5, PT_FLOOR); 71 | fill_rect(WIDTH - 1, (HEIGHT - 5) / 2, 1, 5, PT_FLOOR); 72 | } 73 | 74 | static void 75 | add_fruit(void) { 76 | struct coords c; 77 | 78 | do { 79 | c.x = rand() % WIDTH; 80 | c.y = rand() % HEIGHT; 81 | } while(check_block(c) != PT_FLOOR); 82 | 83 | set_block(c.x, c.y, PT_FRUIT); 84 | } 85 | 86 | static void 87 | handle_kbd(enum dir prev_dir, enum dir *next_dir) { 88 | char sc = check_keystroke() ? (get_keystroke().scancode) : 0; 89 | 90 | if (sc == SC_UP && prev_dir != DIR_DOWN) *next_dir = DIR_UP; 91 | else if (sc == SC_DOWN && prev_dir != DIR_UP) *next_dir = DIR_DOWN; 92 | else if (sc == SC_LEFT && prev_dir != DIR_RIGHT) *next_dir = DIR_LEFT; 93 | else if (sc == SC_RIGHT && prev_dir != DIR_LEFT) *next_dir = DIR_RIGHT; 94 | } 95 | 96 | static void 97 | init_body(struct body *body) 98 | { 99 | body->coords[0].x = 16; 100 | body->coords[0].y = 12; 101 | body->head = body->tail = body->coords; 102 | body->grow = 8; 103 | } 104 | 105 | static struct coords 106 | move_head(struct coords head, enum dir dir) 107 | { 108 | switch (dir) { 109 | case DIR_UP: head.y--; break; 110 | case DIR_DOWN: head.y++; break; 111 | case DIR_LEFT: head.x--; break; 112 | case DIR_RIGHT: head.x++; break; 113 | } 114 | 115 | head.x = (head.x + WIDTH) % WIDTH; 116 | head.y = (head.y + HEIGHT) % HEIGHT; 117 | 118 | return head; 119 | } 120 | 121 | static void 122 | move_snake(struct body *s, struct coords next_head) 123 | { 124 | if (s->grow) { 125 | ++s->tail; 126 | --s->grow; 127 | } else { 128 | set_block(s->tail->x, s->tail->y, PT_FLOOR); 129 | } 130 | 131 | for (struct coords *c = s->tail; c != s->head; --c) { 132 | *c = *(c - 1); 133 | } 134 | 135 | *(s->head) = next_head; 136 | 137 | set_block(s->head->x, s->head->y, PT_SNAKE); 138 | } 139 | 140 | void ENTRY_POINT 141 | main(void) 142 | { 143 | set_fs(TEXT_MEM_SEG); 144 | 145 | put_string("\r\nHello World! This is a standalone x86 app written in C\r\n", CL_LIGHT_CYAN); 146 | put_string("\r\nPress any key to play a game...\r\n", CL_LIGHT_GREEN); 147 | 148 | (void)get_keystroke(); 149 | 150 | for (;;) { 151 | struct body body; 152 | enum dir prev_dir = DIR_RIGHT; 153 | enum dir next_dir = DIR_RIGHT; 154 | 155 | init_body(&body); 156 | draw_board(); 157 | add_fruit(); 158 | 159 | for (;;) { 160 | /* TODO: Handle midnight overflow */ 161 | uint32_t frame_end = get_time() + FRAME_TICKS; 162 | 163 | struct coords next_head = move_head(*body.head, next_dir); 164 | enum block next_block = check_block(next_head); 165 | 166 | if (next_block == PT_FRUIT) 167 | body.grow += 2; 168 | else if (next_block != PT_FLOOR) 169 | break; 170 | 171 | move_snake(&body, next_head); 172 | 173 | if (next_block == PT_FRUIT) 174 | add_fruit(); 175 | 176 | prev_dir = next_dir; 177 | 178 | do handle_kbd(prev_dir, &next_dir); 179 | while (get_time() < frame_end); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /bochsrc.txt: -------------------------------------------------------------------------------- 1 | ata0-master: type=disk, path="build/disk.img" 2 | boot: disk 3 | magic_break: enabled=1 4 | clock: sync=realtime 5 | 6 | # display_library: wx 7 | # config_interface: wx 8 | -------------------------------------------------------------------------------- /boot.s: -------------------------------------------------------------------------------- 1 | /* 2 | * boot.s - Minimal bootloader for USB drives 3 | * 4 | * author: luke8086 5 | * license: GPL-2 6 | */ 7 | 8 | .code16 9 | 10 | /* Setup code, data and stack segments at 0x10000 - 0x1ffff */ 11 | cli 12 | mov $0x1000, %ax 13 | mov %ax, %ds 14 | mov %ax, %es 15 | mov %ax, %ss 16 | mov $0xfff0, %sp 17 | sti 18 | 19 | /* Load our program from disk to RAM using INT 13h, AH=02h */ 20 | mov $0x027f, %ax 21 | mov $0x0000, %bx 22 | mov $0x0003, %cx 23 | mov $0x00, %dh 24 | int $0x13 25 | 26 | /* Jump to the program */ 27 | ljmp $0x1000, $0x0 28 | 29 | /* MBR partition table with a single bootable partition */ 30 | .org 0x01be 31 | .byte 0x80, 0x00, 0x02, 0x00 32 | .byte 0x01, 0x00, 0x3f, 0x00 33 | .long 0x01, 0x7f 34 | 35 | /* Magic number designating a boot loader */ 36 | .org 0x01fe 37 | .byte 0x55, 0xaa 38 | -------------------------------------------------------------------------------- /intr.s: -------------------------------------------------------------------------------- 1 | /* 2 | * intr.s - A generic function for triggering software interrupts 3 | * 4 | * author: luke8086 5 | * license: GPL-2 6 | */ 7 | 8 | .code16 9 | 10 | .global intr 11 | 12 | intr: 13 | push %ebp 14 | mov %esp, %ebp 15 | 16 | /* Preserve general-purpose registers */ 17 | pushal 18 | 19 | /* Inject the interrupt number */ 20 | mov 0x8(%ebp), %eax 21 | mov %al, 2f + 0x01 22 | 23 | /* Just in case, invalidate the pre-fetch queue */ 24 | jmp 1f 25 | 1: 26 | 27 | /* Save ebp, since it's also used as an input for BIOS */ 28 | push %ebp 29 | 30 | /* Load values from struct regs to the registers */ 31 | mov 0xc(%ebp), %esi 32 | mov 0x00(%esi), %eax 33 | mov 0x04(%esi), %ebx 34 | mov 0x08(%esi), %ecx 35 | mov 0x0c(%esi), %edx 36 | mov 0x10(%esi), %ebp 37 | mov 0x14(%esi), %edi 38 | mov 0x18(%esi), %esi 39 | 40 | /* Trigger the interrupt */ 41 | 2: int $0x0 42 | 43 | /* Restore ebp of our stack frame */ 44 | pop %ebp 45 | 46 | /* Save registers back to struct regs */ 47 | mov 0xc(%ebp), %esi 48 | mov %eax, 0x00(%esi) 49 | mov %ebx, 0x04(%esi) 50 | mov %ecx, 0x08(%esi) 51 | mov %edx, 0x0c(%esi) 52 | pushfl 53 | popl 0x1c(%esi) 54 | 55 | /* Restore general-purpose registers */ 56 | popal 57 | 58 | /* Just in case, clear the direction flag */ 59 | cld 60 | 61 | pop %ebp 62 | retl 63 | -------------------------------------------------------------------------------- /linker.ld: -------------------------------------------------------------------------------- 1 | SECTIONS 2 | { 3 | .text : 4 | { 5 | *(.start); 6 | *(.text); 7 | } 8 | .data : 9 | { 10 | *(.data); 11 | *(.rodata*); 12 | *(.bss); 13 | } 14 | /DISCARD/ : 15 | { 16 | *(.comment); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luke8086/boot2c/19992ac7ecf4b0c7c100c9de32ddcff0cdc403b0/screenshot.png -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * util.c - BIOS interface and other basic helpers 3 | * 4 | * author: luke8086 5 | * license: GPL-2 6 | */ 7 | 8 | #include "util.h" 9 | 10 | void 11 | set_fs(uint16_t segment) 12 | { 13 | __asm__ volatile ("mov %0, %%fs" :: "r"(segment) : "memory"); 14 | } 15 | 16 | void 17 | set_gs(uint16_t segment) 18 | { 19 | __asm__ volatile ("mov %0, %%gs" :: "r"(segment) : "memory"); 20 | } 21 | 22 | void * 23 | memcpy(void *dest, const void *src, size_t n) 24 | { 25 | unsigned char *srcb = (unsigned char *)src; 26 | unsigned char *destb = (unsigned char *)dest; 27 | 28 | while (n--) { 29 | *(destb++) = *(srcb++); 30 | } 31 | 32 | return dest; 33 | } 34 | 35 | size_t 36 | strlen(const char *s) 37 | { 38 | size_t n = 0; 39 | 40 | while (*s++) 41 | ++n; 42 | 43 | return n; 44 | } 45 | 46 | uint16_t 47 | rand(void) 48 | { 49 | /* See https://en.wikipedia.org/wiki/Xorshift */ 50 | 51 | static uint16_t seed = 0; 52 | 53 | if (seed == 0) 54 | seed = get_time(); 55 | 56 | seed ^= (seed << 13); 57 | seed ^= (seed >> 9); 58 | seed ^= (seed << 7); 59 | 60 | return seed - 1; 61 | } 62 | 63 | void 64 | toggle_cursor(int visible) 65 | { 66 | struct regs regs = { 67 | .ah = 0x01, 68 | .ch = (visible ? 0x06 : 0x20 ), 69 | .cl = (visible ? 0x07 : 0x00 ), 70 | }; 71 | 72 | intr(0x10, ®s); 73 | } 74 | 75 | uint16_t 76 | get_cursor(void) 77 | { 78 | struct regs regs = { .ah = 0x03, .bh = 0x00 }; 79 | 80 | intr(0x10, ®s); 81 | 82 | return regs.dx; 83 | } 84 | 85 | void 86 | move_cursor(uint16_t pos) 87 | { 88 | struct regs regs = { .ah = 0x02, .bh = 0x00 }; 89 | 90 | regs.dx = pos; 91 | 92 | intr(0x10, ®s); 93 | } 94 | 95 | void 96 | put_char(char ascii) 97 | { 98 | struct regs regs = { 99 | .ah = 0x0e, 100 | .al = ascii, 101 | .bh = 0x00, 102 | }; 103 | 104 | intr(0x10, ®s); 105 | } 106 | 107 | void 108 | put_string(char *s, uint8_t attr) 109 | { 110 | uint16_t pos = get_cursor(); 111 | uint16_t len = strlen(s); 112 | 113 | struct regs regs = { 114 | .ah = 0x13, 115 | .al = 0x01, 116 | .bh = 0x00, 117 | .bl = attr, 118 | }; 119 | 120 | regs.cx = len; 121 | regs.dx = pos; 122 | regs.bp = (uint16_t)(uint32_t)s; 123 | 124 | intr(0x10, ®s); 125 | } 126 | 127 | int 128 | check_keystroke(void) 129 | { 130 | struct regs regs = { .ah = 0x01 }; 131 | 132 | intr(0x16, ®s); 133 | 134 | return !(regs.eflags & 0x0040); 135 | } 136 | 137 | struct keystroke 138 | get_keystroke(void) 139 | { 140 | struct regs regs = { .ah = 0x00 }; 141 | 142 | intr(0x16, ®s); 143 | 144 | struct keystroke ret = { 145 | .scancode = regs.ah, 146 | .ascii = regs.al, 147 | }; 148 | 149 | return ret; 150 | } 151 | 152 | uint32_t 153 | get_time(void) 154 | { 155 | struct regs regs = { .ah = 0x00 }; 156 | 157 | intr(0x1a, ®s); 158 | 159 | return ((regs.cx << 16) | regs.dx); 160 | } 161 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * util.h - BIOS interface and other basic helpers 3 | * 4 | * author: luke8086 5 | * license: GPL-2 6 | */ 7 | 8 | /* 9 | * Note: this library only covers a tiny subset of available functionality 10 | * of BIOS. To learn more, see http://stanislavs.org/helppc/idx_interrupt.html 11 | * and http://www.ctyme.com/rbrown.htm 12 | */ 13 | 14 | #ifndef _UTIL_H_ 15 | #define _UTIL_H_ 16 | 17 | #include 18 | 19 | /* ------------------------------------------------------------------------- * 20 | * Misc declarations and basic helper functions 21 | * ------------------------------------------------------------------------- */ 22 | 23 | typedef __SIZE_TYPE__ size_t; 24 | 25 | /* Function attribute moving it to the .start section */ 26 | #define ENTRY_POINT __attribute__((section(".start"))) 27 | 28 | /* Breakpoint trigger for Bochs */ 29 | #define BOCHS_BREAKPOINT __asm__ volatile ("xchg %bx, %bx") 30 | 31 | /* Set values of the FS / GS registers */ 32 | void set_fs(uint16_t); 33 | void set_gs(uint16_t); 34 | 35 | /* Standard library functions (may be called implicitly by the compiler) */ 36 | void *memcpy(void *, const void *, size_t); 37 | size_t strlen(const char *s); 38 | 39 | /* Return a pseudo-random number between 0 - 65534 */ 40 | uint16_t rand(void); 41 | 42 | 43 | /* ------------------------------------------------------------------------- * 44 | * Code for triggering software interrupts, in particular to call BIOS services 45 | * ------------------------------------------------------------------------- */ 46 | 47 | /* 48 | * Container for values of the CPU registers 49 | * 50 | * Note: initializing fields of different structs at once, 51 | * e.g. struct regs r = { .ah = 1, .bx = 2 }), doesn't seem to work 52 | */ 53 | struct regs { 54 | union { 55 | struct { 56 | int32_t eax, ebx, ecx, edx; 57 | uint32_t ebp, edi, esi; 58 | }; 59 | 60 | struct { 61 | int16_t ax, _eax_fill; 62 | int16_t bx, _ebx_fill; 63 | int16_t cx, _ecx_fill; 64 | int16_t dx, _edx_fill; 65 | uint16_t bp, _ebp_fill; 66 | uint16_t di, _edi_fill; 67 | uint16_t si, _esi_fill; 68 | }; 69 | 70 | struct { 71 | int8_t al, ah; int16_t _eax_fill_; 72 | int8_t bl, bh; int16_t _ebx_fill_; 73 | int8_t cl, ch; int16_t _ecx_fill_; 74 | int8_t dl, dh; int16_t _edx_fill_; 75 | }; 76 | }; 77 | 78 | uint32_t eflags; 79 | }; 80 | 81 | /* Trigger software interrupt */ 82 | void intr(int int_no, struct regs *); 83 | 84 | 85 | /* ------------------------------------------------------------------------- * 86 | * Video services 87 | * ------------------------------------------------------------------------- */ 88 | 89 | /* Text attributes */ 90 | enum { 91 | CL_BLACK, CL_BLUE, CL_GREEN, CL_CYAN, 92 | CL_RED, CL_MAGENTA, CL_BROWN, CL_LIGHT_GRAY, 93 | 94 | /* Bright colors require additional setup to be used for bg */ 95 | CL_DARK_GRAY, CL_LIGHT_BLUE, CL_LIGHT_GREEN, CL_LIGHT_CYAN, 96 | CL_LIGHT_RED, CL_LIGHT_MAGENTA, CL_YELLOW, CL_WHITE 97 | }; 98 | 99 | /* Toggle visibility of the cursor */ 100 | void toggle_cursor(int visible); 101 | 102 | /* Get cursor position (high byte = row, low byte = column) */ 103 | uint16_t get_cursor(void); 104 | 105 | /* Move text cursor to the given position */ 106 | void move_cursor(uint16_t pos); 107 | 108 | /* Write a single character at the cursor position */ 109 | void put_char(char); 110 | 111 | /* Write a null-terminated string with attribute at the cursor position */ 112 | void put_string(char *, uint8_t); 113 | 114 | /* 115 | * Direct access to the text-mode video memory. 116 | * Requires calling set_fs(TEXT_MEM_SEG); 117 | */ 118 | 119 | /* Default text-mode dimensions */ 120 | #define TEXT_WIDTH 80 121 | #define TEXT_HEIGHT 25 122 | 123 | /* Attributed character, a single item of the text-mode memory */ 124 | struct attr_char { 125 | uint8_t ascii; 126 | uint8_t attr; 127 | }; 128 | 129 | /* Text-mode memory */ 130 | #define TEXT_MEM_SEG 0xb800 131 | #define TEXT_MEM ((struct attr_char __seg_fs volatile *) 0x00) 132 | 133 | /* Access a single character, assuming default screen dimensions */ 134 | #define CHAR_AT(x, y) (TEXT_MEM[(y) * TEXT_WIDTH + (x)]) 135 | 136 | 137 | /* ------------------------------------------------------------------------- * 138 | * Keyboard services 139 | * ------------------------------------------------------------------------- */ 140 | 141 | /* Single keystroke in the keyboard buffer */ 142 | struct keystroke { 143 | char scancode; 144 | char ascii; 145 | }; 146 | 147 | /* Keyboard scan codes */ 148 | enum { 149 | SC_ESC = 0x01, 150 | SC_UP = 0x48, 151 | SC_DOWN = 0x50, 152 | SC_LEFT = 0x4b, 153 | SC_RIGHT = 0x4d, 154 | SC_PGUP = 0x49, 155 | SC_PGDN = 0x51, 156 | }; 157 | 158 | /* Check if a keystroke is ready in the keyboard buffer */ 159 | int check_keystroke(void); 160 | 161 | /* Wait for, and return the next keystroke from the keyboard buffer */ 162 | struct keystroke get_keystroke(void); 163 | 164 | 165 | /* ------------------------------------------------------------------------- * 166 | * Clock services 167 | * ------------------------------------------------------------------------- */ 168 | 169 | /* Get system time in 18.2Hz ticks since midnight */ 170 | uint32_t get_time(void); 171 | 172 | #endif /* _UTIL_H_ */ 173 | --------------------------------------------------------------------------------