├── .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 |
--------------------------------------------------------------------------------