├── .gitignore ├── LICENSE ├── README.md ├── include ├── BIOS │ ├── BIOSBlockDevice.h │ ├── BIOSTextMode.h │ ├── BiosDisk.h │ └── MemoryMap.h ├── Memory.h ├── Stage2Header.h ├── StringUtil.h ├── cxxabi.h ├── device │ ├── IBlockDevice.h │ ├── PS2Controller.h │ ├── SysCtrl.h │ ├── TextScreen.h │ ├── VideoMemory.h │ └── io.h ├── fs │ ├── FatDirent.h │ ├── FatDirentLong.h │ ├── FatFs.h │ ├── FatFsInfo.h │ ├── FatName.h │ └── FatSuper.h ├── host │ └── File.h ├── kernel │ ├── MultiBootHeader.h │ ├── MultiBootInfo.h │ └── MultiBootMmap.h ├── part │ ├── CHSPacked.h │ ├── MBREntry.h │ └── MBRTable.h ├── pm86.h └── types │ ├── ByteBlob.h │ ├── FixedLengthString.h │ ├── FlagField.h │ ├── UnalignedInt.h │ └── UniquePtr.h ├── kernel ├── abi.S ├── kernel.cpp ├── kernel.ld └── meson.build ├── lib ├── BIOS │ ├── e820.S │ └── meson.build ├── cxxabi │ ├── cxxabi.cpp │ └── meson.build └── pm86 │ ├── a20.cpp │ ├── copy.cpp │ ├── meson.build │ └── pmcall.S ├── mbr ├── abi.S ├── mbr.cpp ├── mbr.ld └── meson.build ├── meson.build ├── stage2 ├── abi.S ├── heap.cpp ├── meson.build ├── stage2.cpp └── stage2.ld ├── test ├── bochsrc.txt ├── boot.cfg ├── meson.build ├── mkdiskimage.sh └── mkfatimage.sh ├── tools ├── fatedit.cpp ├── installfat.cpp ├── meson.build └── util.h └── vbr ├── abi.S ├── meson.build ├── vbr.cpp └── vbr.ld /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | *~ 4 | *.img 5 | *.bin 6 | *.SYS 7 | tools/installfat 8 | tools/fatedit 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 David Oberhollenzer 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | "I wrote a bootloader in C++, [I AM A CHAMPION!](https://www.youtube.com/watch?v=j4miM_sZU-k)" 4 | 5 | Here is a pre-compiled version: https://goliath32.com/hausboot/hausboot.tar.xz 6 | 7 | It includes a disk image, scripts to run it and the individual components 8 | as separate binaries. 9 | 10 | The abomination in this repository consists of: 11 | 12 | - An MBR boot sector that chain loads a sector form a bootable partition. 13 | - A VBR (partition boot sector) designed to fit into a FAT32 super block, 14 | chain loads a second stage from hidden sectors. 15 | - A second stage that loads a kernel binary from a FAT32 file system into 16 | high memory, enters protected mode with A20 enabled and runs the kernel. 17 | - A tiny [multiboot compliant](https://en.wikipedia.org/wiki/Multiboot_specification) 18 | kernel that prints out some info and then [HALTs](https://www.youtube.com/watch?v=tRgwr_4l6BE). 19 | 20 | The MBR & VBR were initially written as a [proof-of-concept](https://www.youtube.com/watch?v=vq7NFBm2oRw) 21 | and "told you so" to show off that a boot sector could be written in 22 | what's mostly high-level C++. 23 | 24 | The second stage FAT32 kernel loader is the result of [unhealthy obsession 25 | to see this through](https://www.youtube.com/watch?v=QFKzaXW8_Ek), no matter 26 | how stupid the whole thing is. 27 | 28 | This is *not* intended, nor in any way, shape or form [fit for any real world use](https://www.youtube.com/watch?v=QfN1GRqKXpM). 29 | 30 | ## Background 31 | 32 | TODO: make the weird ramblings less incoherent and turn it into a blog post? 33 | 34 | I learned to program with QBASIC and 8086 assembly under MS-DOS. I later 35 | learned how a CPU works from the gate level up, before I learned C. Over the 36 | years, I used C++ on-again-off-again, having to re-learn it every time 37 | since 2011, because the standards commedy keeps changing the darn thing. 38 | 39 | On your typical beginner forums, there were many who ambitiously wanted to 40 | write a computer game, or an OS. For both there are many dedicated websites, 41 | with tutorials. If you go down the OS route, you end up being told that OS 42 | development is *all about* the nookies and crannies of the x86 architecture. 43 | Instead of learning about scheduling, memory management or filesystems, you 44 | learn about the intricacies of the protected mode and the A20 gate. 45 | 46 | Among other things, you are told that boot loaders are hand written in assembly. 47 | You cannot write a boot sector in C, C is too big and FAT. Booting 48 | happens [below C level](https://source.denx.de/u-boot/u-boot). 49 | 50 | Even after many years of C programming, a real eye-opener for me was attending 51 | a (now sadly discontinued) masters course on compiler construction. Turns out, 52 | compilers are *really good*. Even my toy optimizing compiler I wrote that 53 | semester could churn out surprisingly efficient assembly. And I'm pretty sure 54 | the gcc people are a lot smarter than me, in addition to a 30 year head start. 55 | 56 | To a degree, it changed my way of thinking about programming. Source code should 57 | first and foremost be written *for people to read*. Source code should 58 | convey *high level meaning*, both to people, as well as to the compiler. Bit 59 | twiddling hacks are an utter waste of time and make the job harder for the 60 | compiler as well. Keeping down asymptotic complexity is your job, optimizing the 61 | coefficient is the compilers job (but you should really be thinking in terms of 62 | data structures anyway, rather than algorithms). 63 | 64 | Programming languages really are a *formal language* based *calculus* that 65 | describes *meaning*. A compiler is essentially a term-rewriting system and you 66 | are operating with the semantics of that *calculus*. Adages like "C is just an 67 | assembly pre-processor" are not just nonsense, but outright dangerous. The chasm 68 | between C semantics and machine semantics are what gives rise to fear and 69 | misunderstanding around the dreaded undefined-behavior-boogy-man that HN loves 70 | to gossip about so much. 71 | 72 | Pondering further on that, I concluded that an expressive, strong type system 73 | is really key to expressing such high level meaning and semantics. A modern, 74 | high-level language should IMO not have bit manipulation *at all*. Flag fields 75 | are really just composite data types in disguise, model them as such and gain 76 | type safety! I'd go as far as claim that memory-safety issues are themselves 77 | really just a *symptom* of flaws in the type system. 78 | 79 | **Anyway**, revisiting C++ once again, it made me realize that you can model 80 | all of those high level type semantics really well with classes. Thinking about 81 | classes as user defined *types* is an immensely powerful concept, and C++ even 82 | allows you to micro manage memory layout if need be. If leveraged, C++ 83 | effectively hands you tools of Ada like proportions, you just need to use them. 84 | 85 | Thinking about C++ and compiler construction gave me the shower-thought 86 | realization that it should absolutely be possible to write a boot sector 87 | in C++. And after all, C++ is a "systems programming language", right? :-P 88 | 89 | I found the idea especially funny, because much of the criticism I heard/read 90 | about C++ over the years pertained to C++ programs being big, bloated resource 91 | hogs. Also I found the idea to be an interesting challenge/exercise. Which is 92 | what brings us here. 93 | 94 | ## How to Build This 95 | 96 | You need a *very* recent version of g++, as well as the 32 bit version 97 | of `libstdc++` if you are on x86_64 (if you are on "Apple Silicon": sorry...). 98 | 99 | The project uses the [Meson](https://mesonbuild.com/) build system, to compile 100 | the sources, simply run: 101 | 102 | ```sh 103 | meson setup ./build 104 | cd build 105 | meson compile 106 | ``` 107 | 108 | A sample disk image, along with a [Bochs](https://en.wikipedia.org/wiki/Bochs) 109 | config file are generated in `test/` in the build directory. 110 | 111 | To run it in bochs simply run: 112 | 113 | ```sh 114 | bochs -q -f /path/to/bochsrc.txt 115 | ``` 116 | 117 | Alternatively, you can try running it in Qemu: 118 | 119 | ```sh 120 | qemu-system-i386 -drive format=raw,file=/path/to/disk.img 121 | ``` 122 | 123 | With any luck, it might work on your machine as well :-). I have only tested it 124 | with Bochs and Qemu on two Fedora installations and an OpenSuSE machine so far. 125 | 126 | # Boot Strapping Process 127 | 128 | ## Master Boot Record (MBR) 129 | 130 | When you turn on an (legacy BIOS) PC, we are basically transported back to 131 | the 1980s again. The CPU pretends to be a 16 bit mode 8086 that can address 132 | up to 1M of memory using segmentation. 133 | 134 | After reset, the 8086 starts execution at FFFF:0000, or some such, i.e. at 135 | linear address 0xFFFF0. The IBM PC hardware has a ROM chip mapped at that region 136 | that contains the BIOS. The lower 640k of address space are left to the 137 | operating system, including stuff like memory mapped hardware (e.g. video RAM). 138 | This is called "conventional memory" for some reason (as opposed to 139 | *unconventional* memory?). 140 | 141 | The boot strap code in the BIOS simply looks for a floppy or hard drive that 142 | has the magic signature 0xAA55 at the end of the first sector. This magic value 143 | indicates a bootable drive. The BIOS simply copies the first sector to the 144 | magic address 0x7C00 and jumps into it. 145 | 146 | This would typically contain an OS boot loader in hand written assembly that 147 | fetches a bigger executable from the same disk. This would possibly be a 148 | second stage loader that can read the file system and load the OS kernel. 149 | 150 | Eventually somebody figured, it would be a jolly idea to have partitioned 151 | hard disks on the IBM too! This was implemented by storing a partition table 152 | at the end of the first sector. Instead of the original boot strap program, 153 | the first sector now contains the "Master Boot Record" that is 154 | loaded & executed by the BIOS. It relocates itself (typically to `0x0600`; 155 | that's what Microsoft went with), inspects the partition table and chain 156 | loads the actual boot loader from the first sector of a partition marked 157 | as bootable. 158 | 159 | The BIOS code was left untouched, and doesn't need to know about partitions. 160 | It's completely an OS feature, but the partition boot loader needs to be aware 161 | of it, hence we started calling those "Volume Boot Record" (VBR). 162 | 163 | The "standard" layout of the partition table is simply what DOS et al settled 164 | on, using 4 entries, each 16 byte. Later schemes like GPT with UUID partition 165 | labels and additional, *extended* partitions are built on top of that in a 166 | backwards compatible way, which is why you can still only have up to 4 regular, 167 | non-extended partitions there. 168 | 169 | But with a typical, old DOS MBR, this leaves us with 446 bytes of payload 170 | area for the MBR program. For comparison: if we used 80-column IBM cards, 171 | that would be roughly 6 or 7 punched cards (depending on the format). 172 | 173 | In the directory `mbr`, there is C++ source for a program that fits in this 174 | space and does the task described above. The bulk of the implementation is 175 | actually in C++ classes defined in headers in the `include` directory. 176 | 177 | ## Volume Boot Record (VBR) 178 | 179 | The volume boot record sits in the first sector of the partition and is 180 | chain-loaded by the MBR. 181 | 182 | It typically shares the sector with the super block of a filesystem. For 183 | instance, FAT32 uses the first 90 bytes for filesystem information. The 184 | first 3 bytes hold a jump instruction, so we don't accidentally try to 185 | execute the filesystem information. 186 | 187 | Subtracting the super block and 2 byte boot signature, that leaves a FAT32 VBR 188 | with 420 bytes of payload for a program (6 punched cards). 189 | 190 | So what the FAT32 VBR does, is chain-load a much larger program, stored in 191 | reserved sectors between the super block and the first FAT. We are guaranteed 192 | at least 32 reserved sectors, but this count includes the first sector itself, 193 | the special FS information sector, and a backup copy of the first sector (can 194 | be disabled). 195 | 196 | If we disable the backup copy and relocate the FS information sector, we can 197 | max that out to 30 sectors, or a *whopping 15k* for our second stage 198 | boot loader. 199 | 200 | In the `vbr` directory, there is a C++ program that should fit into 201 | that 420 byte region, and chain loads the second stage. The `installfat` 202 | program from the `tools` directory contains C++ source for a program that 203 | modifies a FAT image as described and installs the VBR, as well as the 204 | second stage binaries. 205 | 206 | ## Second Stage Boot Loader 207 | 208 | The second stage boot loader has enough breathing space to do some more hardware 209 | initialization and information gathering (e.g. getting the memory layout and 210 | enabling the [A20 line](https://en.wikipedia.org/wiki/A20_line)). 211 | 212 | It implements an actual FAT32 reader, reads a config file from disk that tells 213 | it from where to load the kernel. 214 | 215 | The kernel is copied to high memory, the BSS is zeroed out, and a multiboot 216 | information structure is setup up, before finally switching into protected 217 | mode and calling the kernel. 218 | 219 | The code for this sits in `stage2`, but once again, most of the magic happens 220 | in the headers included from `include`. In the `tools` directory, there is a 221 | program called `fatedit` that can fumble files into a FAT32 image, so we don't 222 | have to mount the silly thing. 223 | 224 | The FAT32 parser is currently limited to short names only. 225 | 226 | The multiboot code is also fairly limited. It does not support parsing ELF files 227 | and insists that the kernel provides memory layout information instead. 228 | 229 | ## The FAT filesystem 230 | 231 | The FAT (**f**latulent, **a**rchaic **t**rash) filesystem was the original, 232 | native filesystem for DOS (**d**ead **o**perating **s**ystem) and has over the 233 | years become the lingua franca of filesystems, supported by practically every 234 | relevant OS, typically used on external storage media that can be exchanged 235 | between them. At it's core it is idiotically simple, but full of quirks and 236 | semi backwards compatible extensions. 237 | 238 | For those interested: [Microsoft published a white paper on it](https://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/fatgen103.doc) 239 | 240 | It mainly consists of a super block and a "file allocation table" that manages 241 | the data blocks on the disk. Because power can fail during writes, there are 242 | typically 2 of those. 243 | 244 | The data blocks are called *clusters*, indexing starts after the allocation 245 | table (the first one has index 2 for some reason). 246 | 247 | Data belonging to a file is stored using a linked list of clusters. The links 248 | are stored in the allocation table, which is simply an array of cluster indices. 249 | Basically if you know the cluster index that you are currently looking at, you 250 | use that as an index into the array and the value stored there is the index of 251 | the next cluster where you should continue reading. 252 | 253 | There are special sentinel values to indicate that there is no next cluster (the 254 | one you are holding is the last one). Unused clusters are marked with special 255 | value 0 in the table. 256 | 257 | A directory is basically a file that contains a sequence of entry records, 258 | storing a name, the first cluster where the entry data begins, how big it is, 259 | and some flags (e.g. this is not a file but actually a directory). The super 260 | block tells us the cluster index of the root directory, so we can recursively 261 | scan our way through the tree. 262 | 263 | The main difference between FAT12, FAT16 and FAT32 is the number of bits used for 264 | the cluster indices in the table, i.e. 12, 16 and "28 out of 32" respectively. 265 | Also, FAT12 and FAT16 had some special case handling for the root directory. 266 | This probably has boot loader related reasons, FAT32 instead simply has a 267 | reserved region between the VBR and the first FART, where we can park a second 268 | stage boot loader. 269 | 270 | You should note, that you **cannot choose** which FAT type you are using! It 271 | depends *entirely* on **the number of clusters**. If your disk has up to 4085 272 | clusters, you are using FAT12, for up to 65525 clusters, you are using FAT16, 273 | and for higher numbers, FAT32. 274 | 275 | You cannot format a floppy with FAT16 or a 10M hard disk with FAT32. What you 276 | can do, is dial up the number of sectors per cluster to fudge the cluster count 277 | below a threshold and use a *smaller* FAT for a *bigger* disk. 278 | 279 | A disk must have *at least* some circa 32M to be FAT32 formatted. With 1 sector 280 | per cluster (giving us 65536 clusters). For 4k clusters it would be somewhere 281 | around 256M. In both cases, you should bump it up a little further tough. The 282 | Microsoft spec says, you should stay away from the threshold numbers, because 283 | different implementations are subtly broken in different ways. 284 | 285 | ## Multiboot 286 | 287 | The Multiboot specificiation is something the GNU people came up with. It 288 | describes a bootloader-to-kernel binary interface. 289 | 290 | The kernel binary contains a header with a magic signature and some flags in 291 | the first few kilobytes of the file. The boot loader reads it to find out where 292 | to load the kernel binary to and what extra information it wants. The kernel 293 | is a 32 bit executable, it *can be* an ELF file, or manually specify the file 294 | layout in the multiboot header. 295 | 296 | The boot loader sets up 32 bit protected mode for the kernel, enables the A20 297 | line and passes a big information structure to the kernel, containing e.g. the 298 | memory layout, kernel command line or the initrd location in memory. 299 | 300 | To get the memory layout, the boot loader would typically use BIOS 301 | [interrupt 15h, AX=E820](https://en.wikipedia.org/wiki/E820). In fact, the 302 | multiboot memory map structure is pretty much identical to what this returns, 303 | except that it tacks on an additional size field. 304 | 305 | By the way: The Linux kernel **does not** use multiboot. It has a real-mode 306 | setup stub that does (among other things) the protected mode and A20 setup. 307 | It also reads the `E820` memory map on its own. 308 | 309 | There is a information structure in the `bzImage` at fixed offset `0x01F1`. 310 | The boot loader would load the first `N` sectors (whatever the info struct says) 311 | to low memory and the rest of the kernel into high-memory. 312 | 313 | # Minor Things I Learned Along the Way 314 | 315 | ## Building 16 bit code with gcc 316 | 317 | gcc supports 16 bit code in a really passive aggressive way. Turns out, you can 318 | use 32 bit registers from 16 bit code by using the `0x66` op-code prefix. Once 319 | you are in 32 bit mode, the meaning of the prefix is inverted (i.e. use it to 320 | access only the lower 16 bit instead of the full register). 321 | 322 | When told to generate 16 bit code, gcc will basically generate 32 bit code with 323 | instructions prefixed to run in 16 bit real mode. You need to take this into 324 | account, when calling from assembly into gcc generated code or vice versa. 325 | 326 | This also means that current gcc theoretically cannot target anything pre i386. 327 | 328 | Also, gcc will happily poop out XMM/SSE instructions with a `0x66` prefix when 329 | it needs to move data around. Set the proper `-march=i386` flag, to make sure 330 | it doesn't do that. 331 | 332 | Furthermore, accessing memory past 64k is a PITA. There is an `0x67` opcode 333 | prefix that allows you to load/store using a 32 bit register. On Qemu, this 334 | works just fine, but on real hardware, this this will trigger a protection 335 | fault if you try to access memory past the 64k current segment boundary. 336 | 337 | ## Computed Goto 338 | 339 | gcc allows you to do this: 340 | 341 | ```C++ 342 | somelabel: 343 | auto *wtf = &&somelabel; 344 | 345 | goto *wtf; 346 | ``` 347 | 348 | the intended use is to hack together jump tables for state machines by hand. 349 | But it also allows pointer arithmetic on the, um "label pointer" and even 350 | funkier stuff: 351 | 352 | ```C++ 353 | goto *((void *)0x7c00); 354 | ``` 355 | 356 | I initially had the MBR written in C++ entirely, including the self relocating, 357 | but ultimately replace the entry/exit code with an assembly stub instead. It 358 | was very fidgety and relied on the compiler generating *specific* code, e.g. 359 | not turning an absolute jump to a relative one. This is also, why the relocating 360 | code couldn't just *call* main either. The compiler would prefer a more 361 | compact, relative call, or if main was static, just inline it. 362 | 363 | ## Disassembling a raw Binary 364 | 365 | I had to resort to this a few times to double check: 366 | 367 | ```sh 368 | objdump -D -m i8086 -b binary ./mbr/mbr.bin 369 | ``` 370 | 371 | ## Debugging Low Level Code with gdb and Qemu 372 | 373 | Qemu has built in support for the gdbserver protocol: 374 | 375 | ```sh 376 | qemu-system-i386 -drive format=raw,file=./disk.img -S -s 377 | ``` 378 | 379 | You can then connect to it from within gdb: 380 | 381 | ```sh 382 | $ gdb 383 | ...Dr. Stallman, of MIT, has pointed out that bobbadah bobbadah hoe daddy 384 | yanga langa furjeezama bing jingle oh yeah... 385 | (gdb) target remote localhost:1234 386 | ...bla bla bla...What machine is this? I don't have debug symbols, lol... 387 | (gdb) set architecture i8086 388 | The target architecture is set to "i8086". 389 | (gdb) break *0x7c00 390 | Breakpoint 1 at 0x7c00 391 | (gdb) c 392 | Continuing. 393 | 394 | Breakpoint 1, 0x00007c00 in ?? () 395 | ``` 396 | 397 | You can dump registers and disassemble memory ranges, and so on. 398 | 399 | ## Debugging Low Level Code with Bochs 400 | 401 | Bochs needs to be compiled with debugger mode enabled for this. Typical Linux 402 | Distros have a separate package for that, with a separate binary that can 403 | launch you into a debug shell: 404 | 405 | ```sh 406 | $ bochs-debugger -q -f bochsrc.txt 407 | ... bla bla bla ... 408 | (0) [0x0000fffffff0] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b ; ea5be000f0 409 | lbreak 0x7c00 410 | c 411 | ... bla bla bla ... 412 | (0) Breakpoint 1, 0x0000000000007c00 in ?? () 413 | Next at t=16300062 414 | (0) [0x000000007c00] 0000:7c00 (unk. ctxt): xor ax, ax ; 31c0 415 | 416 | ``` 417 | 418 | You can single step with `s`, an optional number argument steps over several 419 | instructions, disassembly and address are shown. 420 | 421 | For break points, `break` expects a `segment:offset` address, while `lbreak` 422 | expects a linear address, `c` continues until the next break point. 423 | 424 | You can dump registers with `r`: 425 | 426 | ```sh 427 | r 428 | CPU0: 429 | rax: 00000000_0000aa55 430 | rbx: 00000000_00000000 431 | rcx: 00000000_00090000 432 | rdx: 00000000_00000080 433 | rsp: 00000000_0000ffd6 434 | rbp: 00000000_00000000 435 | rsi: 00000000_000e0000 436 | rdi: 00000000_0000ffac 437 | r8 : 00000000_00000000 438 | r9 : 00000000_00000000 439 | r10: 00000000_00000000 440 | r11: 00000000_00000000 441 | r12: 00000000_00000000 442 | r13: 00000000_00000000 443 | r14: 00000000_00000000 444 | r15: 00000000_00000000 445 | rip: 00000000_00007c00 446 | eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf 447 | ``` 448 | 449 | or inspect/hexdump memory regions with `x /
`: 450 | 451 | ```sh 452 | x /16b 0x7df0 453 | [bochs]: 454 | 0x0000000000007df0 : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 455 | 0x0000000000007df8 : 0x00 0x00 0x00 0x00 0x00 0x00 0x55 0xaa 456 | ``` 457 | -------------------------------------------------------------------------------- /include/BIOS/BIOSBlockDevice.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * BIOSBlockDevice.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef BIOS_BLOCK_DEVICE_H 8 | #define BIOS_BLOCK_DEVICE_H 9 | 10 | #include "BIOS/BiosDisk.h" 11 | #include "device/IBlockDevice.h" 12 | 13 | #include 14 | 15 | class BIOSBlockDevice : public IBlockDevice { 16 | public: 17 | BIOSBlockDevice() = delete; 18 | 19 | BIOSBlockDevice(BiosDisk disk, uint32_t offset) { 20 | if (!disk.ReadDriveParameters(_geometry)) { 21 | _isInitialized = false; 22 | return; 23 | } 24 | 25 | _disk = disk; 26 | _partStart = offset; 27 | _isInitialized = true; 28 | } 29 | 30 | virtual bool LoadSector(uint32_t index, void *buffer) override final { 31 | auto chs = _geometry.LBA2CHS(_partStart + index); 32 | 33 | return _disk.LoadSectors(chs, buffer, 1); 34 | } 35 | 36 | virtual uint16_t SectorSize() const override final { 37 | return 512; 38 | } 39 | 40 | const auto &DriveGeometry() const { 41 | return _geometry; 42 | } 43 | 44 | bool IsInitialized() const { 45 | return _isInitialized; 46 | } 47 | private: 48 | bool _isInitialized; 49 | BiosDisk::DriveGeometry _geometry; 50 | uint32_t _partStart; 51 | BiosDisk _disk{0}; 52 | }; 53 | 54 | #endif /* BIOS_BLOCK_DEVICE_H */ 55 | -------------------------------------------------------------------------------- /include/BIOS/BIOSTextMode.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * BIOSTextMode.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef BIOS_TEXT_MODE_H 8 | #define BIOS_TEXT_MODE_H 9 | 10 | #include 11 | #include 12 | 13 | class BIOSTextMode { 14 | public: 15 | void Reset() { 16 | __asm__ __volatile__("int $0x10" : : "a"(0x0003)); 17 | } 18 | 19 | void PutChar(uint8_t c) { 20 | __asm__ __volatile__("int $0x10" : : "a"(0x0e00 | c), "b"(0)); 21 | } 22 | }; 23 | 24 | [[noreturn, maybe_unused]] static void DumpMessageAndHang(const char *msg) 25 | { 26 | BIOSTextMode screen; 27 | 28 | screen.Reset(); 29 | while (*msg != '\0') 30 | screen.PutChar(*(msg++)); 31 | 32 | for (;;) { 33 | __asm__ __volatile__ ("hlt"); 34 | } 35 | } 36 | 37 | #endif /* BIOS_TEXT_MODE_H */ 38 | -------------------------------------------------------------------------------- /include/BIOS/BiosDisk.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * BiosDisk.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef BIOS_H 8 | #define BIOS_H 9 | 10 | #include 11 | #include "part/CHSPacked.h" 12 | 13 | class BiosDisk { 14 | public: 15 | BiosDisk() = delete; 16 | 17 | BiosDisk(uint8_t driveNum) : _driveNum(driveNum) { 18 | } 19 | 20 | inline bool Reset() { 21 | int error; 22 | __asm__ __volatile__ ("int $0x13\r\n" 23 | "sbb %0,%0" 24 | : "=r"(error) 25 | : "a"(0), "d"(_driveNum)); 26 | return error == 0; 27 | } 28 | 29 | inline bool LoadSectors(CHSPacked source, 30 | void *out, uint8_t count) const { 31 | uint16_t dx = (static_cast(source.Head()) << 8) | 32 | _driveNum; 33 | uint16_t cx = (source.Cylinder() << 8) | 34 | ((source.Cylinder() & 0x0300) >> 2) | source.Sector(); 35 | 36 | int error; 37 | 38 | __asm__ __volatile__ ("int $0x13\r\n" 39 | "sbb %0,%0" 40 | : "=r"(error) 41 | : "a"(0x0200 | count), "c"(cx), 42 | "b"(out), "d"(dx)); 43 | return error == 0; 44 | } 45 | 46 | struct DriveGeometry { 47 | uint16_t cylinders; 48 | uint16_t headsPerCylinder; 49 | uint16_t sectorsPerTrack; 50 | 51 | CHSPacked LBA2CHS(uint32_t lba) const { 52 | CHSPacked out; 53 | 54 | out.SetCylinder(lba / ((uint32_t)headsPerCylinder * 55 | sectorsPerTrack)); 56 | out.SetHead((lba / sectorsPerTrack) % headsPerCylinder); 57 | out.SetSector((lba % sectorsPerTrack) + 1); 58 | 59 | return out; 60 | } 61 | }; 62 | 63 | bool ReadDriveParameters(DriveGeometry &out) const { 64 | uint16_t cxOut, dxOut; 65 | int error; 66 | __asm__ __volatile__ ("int $0x13\r\n" 67 | "sbb %0,%0" 68 | : "=r"(error), "=c"(cxOut), "=d"(dxOut) 69 | : "a"(0x0800), "d"(_driveNum), 70 | "e"(0), "D"(0) 71 | : "bx"); 72 | if (error != 0) 73 | return false; 74 | 75 | out.cylinders = (((cxOut & 0x0C0) << 2) | ((cxOut >> 8) & 0x0FF)) + 1; 76 | out.sectorsPerTrack = cxOut & 0x3F; 77 | out.headsPerCylinder = ((dxOut >> 8) & 0x0FF) + 1; 78 | return true; 79 | } 80 | private: 81 | uint8_t _driveNum; 82 | }; 83 | 84 | static_assert(sizeof(BiosDisk) == sizeof(uint8_t)); 85 | 86 | #endif /* BIOS_H */ 87 | -------------------------------------------------------------------------------- /include/BIOS/MemoryMap.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * MemoryMap.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef BIOS_MEMORY_MAP_H 8 | #define BIOS_MEMORY_MAP_H 9 | 10 | #include "types/UnalignedInt.h" 11 | 12 | #include 13 | 14 | extern "C" { 15 | int IntCallE820(uint32_t *ebxInOut, uint8_t dst[20]); 16 | }; 17 | 18 | class MemoryMapEntry { 19 | public: 20 | enum class MemType : uint32_t { 21 | Usable = 1, 22 | Reserved = 2, 23 | ACPI = 3, 24 | Preserve = 4, 25 | Broken = 5, 26 | }; 27 | 28 | int Load(uint32_t &ebxInOut) { 29 | static MemoryMapEntry temp; 30 | 31 | /* 32 | XXX: Linux memory.c says that some BIOSes assume we always 33 | use the same buffer, so we use a static scratch buffer for 34 | the call and copy the result afterwards. 35 | */ 36 | auto ret = IntCallE820(&ebxInOut, (uint8_t *)&temp); 37 | *this = temp; 38 | 39 | return ret; 40 | } 41 | 42 | auto BaseAddress() const { 43 | return _base.Read(); 44 | } 45 | 46 | auto Size() const { 47 | return _size.Read(); 48 | } 49 | 50 | MemType Type() const { 51 | auto value = _type.Read(); 52 | 53 | return value < 1 || value > 5 ? 54 | MemType::Broken : (MemType)value; 55 | } 56 | 57 | const char *TypeAsString() const { 58 | switch (Type()) { 59 | case MemType::Usable: return "free"; 60 | case MemType::Reserved: return "reserved"; 61 | case MemType::ACPI: return "ACPI"; 62 | case MemType::Preserve: return "preserve"; 63 | default: 64 | return "unknown"; 65 | } 66 | } 67 | private: 68 | UnalignedInt _base; 69 | UnalignedInt _size; 70 | UnalignedInt _type; 71 | }; 72 | 73 | static_assert(sizeof(MemoryMapEntry) == 20); 74 | 75 | template 76 | class MemoryMap { 77 | public: 78 | bool Load() { 79 | uint32_t ebx = 0; 80 | 81 | _count = 0; 82 | 83 | do { 84 | int ret = _ent[_count].Load(ebx); 85 | if (ret < 0) { 86 | _count = 0; 87 | return false; 88 | } 89 | if (ret > 0) 90 | break; 91 | ++_count; 92 | } while (_count < MAX_COUNT && ebx != 0); 93 | 94 | return true; 95 | } 96 | 97 | const auto *begin() const { 98 | return _ent; 99 | } 100 | 101 | const auto *end() const { 102 | return _ent + _count; 103 | } 104 | 105 | const auto &At(size_t i) const { 106 | return i < _count ? _ent[i] : _ent[_count - 1]; 107 | } 108 | 109 | auto Count() const { 110 | return _count; 111 | } 112 | private: 113 | MemoryMapEntry _ent[MAX_COUNT]; 114 | size_t _count = 0; 115 | }; 116 | 117 | #endif /* BIOS_MEMORY_MAP_H */ 118 | -------------------------------------------------------------------------------- /include/Memory.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * Memory.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef MEMORY_H 8 | #define MEMORY_H 9 | 10 | #include 11 | 12 | extern "C" { 13 | void HeapInit(void *basePtr, size_t maxSize); 14 | void *malloc(size_t count); 15 | void free(void *ptr); 16 | } 17 | 18 | inline void *operator new(size_t size) { return malloc(size); } 19 | inline void *operator new[](size_t size) { return malloc(size); } 20 | inline void operator delete(void *p) { free(p); } 21 | inline void operator delete(void *p, size_t) { free(p); } 22 | inline void operator delete[](void *p) { free(p); } 23 | inline void operator delete[](void *p, size_t) { free(p); } 24 | 25 | #endif /* MEMORY_H */ 26 | 27 | -------------------------------------------------------------------------------- /include/Stage2Header.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * Stage2Header.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef STAGE2HEADER_H 8 | #define STAGE2HEADER_H 9 | 10 | #include 11 | #include 12 | 13 | #include "part/MBREntry.h" 14 | #include "BIOS/BiosDisk.h" 15 | 16 | constexpr uint32_t Stage2Magic = 0xD0D0CACA; 17 | constexpr uint16_t Stage2Location = 0x1000; 18 | 19 | class Stage2Header { 20 | public: 21 | void SetSectorCount(uint32_t size) { 22 | _sectorCount = size / 512; 23 | 24 | if (size % 512) 25 | _sectorCount += 1; 26 | } 27 | 28 | size_t SectorCount() const { 29 | return _sectorCount; 30 | } 31 | 32 | void UpdateChecksum() { 33 | _checksum = 0; 34 | _checksum = ~(ComputeChecksum()) + 1; 35 | } 36 | 37 | uint32_t ComputeChecksum() const { 38 | auto *ptr = (uint32_t *)this; 39 | auto count = _sectorCount * 128; 40 | uint32_t acc = 0; 41 | 42 | while (count--) 43 | acc += *(ptr++); 44 | 45 | return acc; 46 | } 47 | 48 | bool Verify(uint32_t maxSectors) const { 49 | if (_magic != Stage2Magic) 50 | return false; 51 | 52 | if (_sectorCount == 0 || _sectorCount > maxSectors) 53 | return false; 54 | 55 | return (_checksum != 0) && (ComputeChecksum() == 0); 56 | } 57 | 58 | void SetBiosBootDrive(BiosDisk drive) { 59 | _biosBootDrive = drive; 60 | } 61 | 62 | BiosDisk BiosBootDrive() const { 63 | return _biosBootDrive; 64 | } 65 | 66 | void SetBootMBREntry(const MBREntry &ent) { 67 | _bootMbrEntry = ent; 68 | } 69 | 70 | const MBREntry &BootMBREntry() const { 71 | return _bootMbrEntry; 72 | } 73 | private: 74 | const uint32_t _magic = Stage2Magic; 75 | uint32_t _checksum = 0; 76 | uint16_t _sectorCount = 0; 77 | BiosDisk _biosBootDrive{0}; 78 | const uint8_t _pad0 = 0; 79 | MBREntry _bootMbrEntry{}; 80 | }; 81 | 82 | static_assert(sizeof(Stage2Header) == 28); 83 | 84 | #endif /* STAGE2HEADER_H */ 85 | -------------------------------------------------------------------------------- /include/StringUtil.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * StringUtil.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef STRING_UTIL_H 8 | #define STRING_UTIL_H 9 | 10 | static bool StrEqual(const char *a, const char *b) 11 | { 12 | for (;;) { 13 | if (*a != *b) 14 | return false; 15 | if (*a == '\0') 16 | break; 17 | ++a; 18 | ++b; 19 | } 20 | return true; 21 | } 22 | 23 | static bool IsSpace(int x) 24 | { 25 | return x == ' ' || x == '\t'; 26 | } 27 | 28 | static bool IsAlnum(int x) 29 | { 30 | return (x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z') || 31 | (x >= '0' && x <= '9'); 32 | } 33 | 34 | #endif /* STRING_UTIL_H */ 35 | -------------------------------------------------------------------------------- /include/cxxabi.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * cxxabi.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef CXXABI_H 8 | #define CXXABI_H 9 | 10 | extern "C" { 11 | extern void *__dso_handle; 12 | 13 | int __cxa_atexit(void (*f)(void *), void *objptr, void *dso); 14 | 15 | void __cxa_finalize(void *f); 16 | }; 17 | 18 | #endif /* CXXABI_H */ 19 | -------------------------------------------------------------------------------- /include/device/IBlockDevice.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * IBlockDevice.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef IBLOCK_DEVICE_H 8 | #define IBLOCK_DEVICE_H 9 | 10 | #include 11 | 12 | class IBlockDevice { 13 | public: 14 | virtual ~IBlockDevice() { 15 | } 16 | 17 | virtual bool LoadSector(uint32_t index, void *buffer) = 0; 18 | 19 | virtual uint16_t SectorSize() const = 0; 20 | }; 21 | 22 | #endif /* IBLOCK_DEVICE_H */ 23 | -------------------------------------------------------------------------------- /include/device/PS2Controller.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * PS2Controller.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef PS2CONTROLLER_H 8 | #define PS2CONTROLLER_H 9 | 10 | #include "types/FlagField.h" 11 | #include "device/io.h" 12 | 13 | class PS2Controller { 14 | public: 15 | enum class PS2Status : uint8_t { 16 | OutputBufferFull = 0x01, 17 | InputBufferFull = 0x02, 18 | SystemFlag = 0x04, 19 | CommandFlag = 0x08, 20 | Vendor1 = 0x10, 21 | Vendor2 = 0x20, 22 | Timeout = 0x40, 23 | ParityError = 0x80 24 | }; 25 | 26 | auto Status() { 27 | IoWait(); 28 | return FlagField(IoReadByte(0x64)); 29 | } 30 | 31 | bool EnableA20Gate() { 32 | if (!DrainBuffers()) 33 | return false; 34 | 35 | // Command write 36 | SendCommand(0xd1); 37 | DrainBuffers(); 38 | 39 | // Turn A20 on 40 | WriteData(0xdf); 41 | DrainBuffers(); 42 | 43 | // Null command, required by UHCI 44 | SendCommand(0xff); 45 | DrainBuffers(); 46 | return true; 47 | } 48 | private: 49 | auto ReadData() { 50 | IoWait(); 51 | return IoReadByte(0x60); 52 | } 53 | 54 | void SendCommand(uint8_t cmd) { 55 | IoWriteByte(0x64, cmd); 56 | } 57 | 58 | void WriteData(uint8_t cmd) { 59 | IoWriteByte(0x60, cmd); 60 | } 61 | 62 | bool DrainBuffers() { 63 | int ffCount = 0; 64 | 65 | for (int i = 0; i < 100000; ++i) { 66 | auto status = Status(); 67 | 68 | if (status.RawValue() == 0xFF) { 69 | // probably no controller present? 70 | if (ffCount++ > 32) 71 | return false; 72 | continue; 73 | } 74 | 75 | ffCount = 0; 76 | 77 | if (status.IsSet(PS2Status::OutputBufferFull)) { 78 | (void)ReadData(); 79 | continue; 80 | } 81 | 82 | if (!status.IsSet(PS2Status::InputBufferFull)) 83 | return true; 84 | } 85 | 86 | return false; 87 | } 88 | }; 89 | 90 | #endif /* PS2CONTROLLER_H */ 91 | -------------------------------------------------------------------------------- /include/device/SysCtrl.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * SysCtrl.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef SYSCTRL_H 8 | #define SYSCTRL_H 9 | 10 | #include "types/FlagField.h" 11 | #include "device/io.h" 12 | 13 | class SystemCtrlPort { 14 | public: 15 | enum class PortA : uint8_t { 16 | FastReset = 0x01, 17 | EnableA20 = 0x02, 18 | SecurityLock = 0x08, 19 | WatchdogStatus = 0x10, 20 | HDD2DriveActive = 0x40, 21 | HDD1DriveActive = 0x80, 22 | }; 23 | 24 | enum class PortB : uint8_t { 25 | ConnectTimer2ToSPKR = 0x01, 26 | SpeakerEnable = 0x02, 27 | ParityCeckEnable = 0x04, 28 | ChannelCheckEnable = 0x08, 29 | RequestRefresh = 0x10, 30 | Timer2Output = 0x20, 31 | ChannelCheck = 0x40, 32 | ParityCheck = 0x80, 33 | }; 34 | 35 | void EnableFastA20() { 36 | auto ctrlA = ReadPortA(); 37 | ctrlA.Set(PortA::EnableA20); 38 | ctrlA.Clear(PortA::FastReset); 39 | SetPortA(ctrlA); 40 | } 41 | private: 42 | FlagField ReadPortA() { 43 | return FlagField(IoReadByte(0x92)); 44 | } 45 | 46 | FlagField ReadPortB() { 47 | return FlagField(IoReadByte(0x61)); 48 | } 49 | 50 | void SetPortA(FlagField flags) { 51 | IoWriteByte(0x92, flags.RawValue()); 52 | } 53 | 54 | void SetPortB(FlagField flags) { 55 | IoWriteByte(0x61, flags.RawValue()); 56 | } 57 | }; 58 | 59 | #endif /* SYSCTRL_H */ 60 | -------------------------------------------------------------------------------- /include/device/TextScreen.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * TextScreen.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef TEXT_SCREEN_H 8 | #define TEXT_SCREEN_H 9 | 10 | #include 11 | #include 12 | 13 | template 14 | class TextScreen { 15 | public: 16 | void Reset() { _driver.Reset(); } 17 | void PutChar(uint8_t c) { _driver.PutChar(c); } 18 | auto &Driver() { return _driver; } 19 | 20 | void WriteString(const char *str) { 21 | while (*str != '\0') 22 | _driver.PutChar(*(str++)); 23 | } 24 | 25 | void WriteString(const char *str, size_t count) { 26 | while (count--) { 27 | _driver.PutChar(*(str++)); 28 | } 29 | } 30 | 31 | void WriteHex(uint64_t x) { 32 | char buffer[sizeof(x) * 2 + 1]; 33 | char *ptr = buffer + sizeof(buffer) - 1; 34 | 35 | *(ptr--) = '\0'; 36 | 37 | for (size_t i = 0; i < (sizeof(buffer) - 1); ++i) { 38 | auto digit = x & 0x0F; 39 | *(ptr--) = digit < 10 ? (digit + '0') : 40 | (digit - 10 + 'A'); 41 | x >>= 4; 42 | } 43 | 44 | (*this) << (ptr + 1); 45 | } 46 | 47 | void WriteDecimal(uint32_t x) { 48 | if (x > 0) { 49 | char buffer[10]; 50 | char *ptr = buffer + sizeof(buffer) - 1; 51 | 52 | *(ptr--) = '\0'; 53 | 54 | while (x > 0) { 55 | *(ptr--) = (x % 10) + '0'; 56 | x /= 10; 57 | } 58 | 59 | (*this) << (ptr + 1); 60 | } else { 61 | PutChar('0'); 62 | } 63 | } 64 | 65 | auto &operator<< (const char *str) { 66 | while (*str != '\0') 67 | PutChar(*(str++)); 68 | 69 | return *this; 70 | } 71 | 72 | auto &operator<< (uint32_t x) { 73 | WriteDecimal(x); 74 | return *this; 75 | } 76 | 77 | auto &operator<< (int32_t x) { 78 | if (x < 0) { 79 | PutChar('-'); 80 | x = -x; 81 | } 82 | 83 | WriteDecimal(x); 84 | return *this; 85 | } 86 | 87 | auto &operator<< (void *ptr) { 88 | WriteHex((uint32_t)ptr); 89 | return *this; 90 | } 91 | private: 92 | DRIVER _driver; 93 | }; 94 | 95 | #endif /* TEXT_SCREEN_H */ 96 | -------------------------------------------------------------------------------- /include/device/VideoMemory.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * VideoMemory.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef VIDEO_MEMORY_H 8 | #define VIDEO_MEMORY_H 9 | 10 | #include 11 | 12 | #include "device/io.h" 13 | 14 | class VideoMemory { 15 | public: 16 | enum class Color : uint8_t { 17 | Black = 0, 18 | Blue = 1, 19 | Green = 2, 20 | Cyan = 3, 21 | Red = 4, 22 | Magenta = 5, 23 | Brown = 6, 24 | LightGray = 7, 25 | DarkGray = 8, 26 | LightBlue = 9, 27 | LightGreen = 10, 28 | LightCyan = 11, 29 | LightRed = 12, 30 | LightMagenta = 13, 31 | Yellow = 14, 32 | White = 15 33 | }; 34 | 35 | void Reset() { 36 | for (size_t i = 0; i < (80 * 25); ++i) { 37 | _vidmem[i].SetColor(_fg, _bg); 38 | _vidmem[i].SetChar(' '); 39 | } 40 | 41 | _x = 0; 42 | _y = 0; 43 | } 44 | 45 | void PutChar(uint8_t c) { 46 | if (c == '\r') { 47 | _x = 0; 48 | SyncCursor(); 49 | return; 50 | } 51 | 52 | if (c == '\n') { 53 | _y += 1; 54 | if (_y >= _height) { 55 | ScrollUp(); 56 | } else { 57 | SyncCursor(); 58 | } 59 | return; 60 | } 61 | 62 | if (c == '\t') { 63 | auto diff = 8 - (_x % 8); 64 | while (diff--) 65 | PutChar(' '); 66 | return; 67 | } 68 | 69 | if (c >= 0x20 && c <= 0x7E) { 70 | _vidmem[_y * _width + _x].SetChar(c); 71 | _x += 1; 72 | 73 | if (_x >= _width) { 74 | _x = 0; 75 | _y += 1; 76 | 77 | if (_y >= _height) { 78 | ScrollUp(); 79 | return; 80 | } 81 | } 82 | 83 | SyncCursor(); 84 | } 85 | } 86 | 87 | void SetColor(Color foreground, Color background) { 88 | _fg = foreground; 89 | _bg = background; 90 | } 91 | 92 | void ScrollUp() { 93 | auto *dst = _vidmem; 94 | auto *src = _vidmem + _width; 95 | auto *end = _vidmem + _width * _height; 96 | 97 | while (src != end) 98 | *(dst++) = *(src++); 99 | 100 | while (dst != end) { 101 | dst->SetChar(' '); 102 | dst->SetColor(_fg, _bg); 103 | } 104 | 105 | if (_y > 0) 106 | _y -= 1; 107 | 108 | SyncCursor(); 109 | } 110 | private: 111 | struct VidMemCell { 112 | public: 113 | void SetColor(Color fg, Color bg) { 114 | uint16_t attrib = (uint8_t)bg << 4 | (uint8_t)fg; 115 | 116 | _raw = (_raw & 0x00FF) | (attrib << 8); 117 | } 118 | 119 | void SetChar(uint8_t c) { 120 | _raw = (_raw & 0xFF00) | c; 121 | } 122 | private: 123 | uint16_t _raw; 124 | }; 125 | 126 | void SyncCursor() { 127 | uint16_t pos = _y * _width + _x; 128 | 129 | IoWriteByte(0x3D4, 14); 130 | IoWriteByte(0x3D5, (pos >> 8) & 0xFF); 131 | IoWriteByte(0x3D4, 15); 132 | IoWriteByte(0x3D5, pos & 0xFF); 133 | } 134 | 135 | VidMemCell *_vidmem = (VidMemCell *)0x0B8000; 136 | uint8_t _width = 80; 137 | uint8_t _height = 25; 138 | uint8_t _x = 0; 139 | uint8_t _y = 0; 140 | Color _fg = Color::LightGray; 141 | Color _bg = Color::Black; 142 | }; 143 | 144 | #endif /* VIDEO_MEMORY_H */ 145 | -------------------------------------------------------------------------------- /include/device/io.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * io.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef IO_H 8 | #define IO_H 9 | 10 | #include 11 | 12 | static inline void IoWriteByte(uint16_t port, uint8_t val) 13 | { 14 | __asm__ __volatile__("outb %0, %1" 15 | : : "a"(val), "Nd"(port)); 16 | } 17 | 18 | static inline uint8_t IoReadByte(uint16_t port) 19 | { 20 | uint8_t ret; 21 | __asm__ __volatile__("inb %1, %0" 22 | : "=a"(ret) : "Nd"(port)); 23 | return ret; 24 | } 25 | 26 | static inline void IoWait() 27 | { 28 | IoWriteByte(0x80, 0); 29 | } 30 | 31 | static inline void SetFs(uint16_t seg) 32 | { 33 | __asm__ __volatile__ ("movw %0, %%fs" : : "rm"(seg)); 34 | } 35 | 36 | static inline uint8_t Peek(uint16_t seg, uint16_t off) 37 | { 38 | uint8_t *ptr = (uint8_t *)off; 39 | uint8_t v; 40 | SetFs(seg); 41 | __asm__ __volatile__ ("movb %%fs:%1, %0" : "=q"(v) : "m"(*ptr)); 42 | return v; 43 | } 44 | 45 | static inline void Poke(uint16_t seg, uint16_t off, uint8_t v) 46 | { 47 | uint8_t *ptr = (uint8_t *)off; 48 | SetFs(seg); 49 | __asm__ __volatile__ ("movb %1, %%fs:%0" : "+m"(*ptr) : "qi"(v)); 50 | } 51 | 52 | #endif /* IO_H */ 53 | -------------------------------------------------------------------------------- /include/fs/FatDirent.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * FatDirent.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef FAT_DIRENT_H 8 | #define FAT_DIRENT_H 9 | 10 | #include "types/FixedLengthString.h" 11 | #include "types/UnalignedInt.h" 12 | #include "types/FlagField.h" 13 | #include "types/ByteBlob.h" 14 | 15 | class FatDirent { 16 | public: 17 | enum class Flags : uint8_t { 18 | ReadOnly = 0x01, 19 | Hidden = 0x02, 20 | System = 0x04, 21 | VolumeID = 0x08, 22 | Directory = 0x10, 23 | Archive = 0x20, 24 | LongFileName = 0x0F, 25 | }; 26 | 27 | uint32_t ClusterIndex() const { 28 | uint32_t out = _clusterIndexHigh.Read(); 29 | out <<= 16; 30 | out |= _clusterIndexLow.Read(); 31 | return out; 32 | } 33 | 34 | void SetClusterIndex(uint32_t index) { 35 | _clusterIndexLow = index & 0x0FFFF; 36 | _clusterIndexHigh = (index >> 16) & 0x0FFFF; 37 | } 38 | 39 | uint32_t Size() const { 40 | return _size.Read(); 41 | } 42 | 43 | void SetSize(uint32_t size) { 44 | _size = size; 45 | } 46 | 47 | auto EntryFlags() const { 48 | return _flags; 49 | } 50 | 51 | void SetEntryFlags(FlagField flags) { 52 | _flags = flags; 53 | } 54 | 55 | uint8_t Checksum() const { 56 | uint8_t sum = 0; 57 | 58 | for (int i = 0; i < 8; ++i) 59 | sum = ((sum & 1) << 7) + (sum >> 1) + _name.At(i); 60 | 61 | for (int i = 0; i < 3; ++i) 62 | sum = ((sum & 1) << 7) + (sum >> 1) + _extension.At(i); 63 | 64 | return sum; 65 | } 66 | 67 | bool IsDummiedOut() const { 68 | return _name.At(0) == 0xE5; 69 | } 70 | 71 | bool IsLastInList() const { 72 | return _name.At(0) == 0x00; 73 | } 74 | 75 | void SetName(const char *name) { 76 | _name.Set(name); 77 | } 78 | 79 | void SetExtension(const char *ext) { 80 | _extension.Set(ext); 81 | } 82 | 83 | bool NameToString(char buffer[13]) const { 84 | if (_name.At(0) == ' ') 85 | return false; 86 | 87 | size_t idx = 0; 88 | 89 | for (size_t i = 0; i < 8; ++i) 90 | buffer[idx++] = _name.At(i); 91 | 92 | while (idx > 0 && buffer[idx - 1] == ' ') 93 | --idx; 94 | 95 | if (_extension.At(0) != ' ') { 96 | buffer[idx++] = '.'; 97 | 98 | for (size_t i = 0; i < 3; ++i) 99 | buffer[idx++] = _extension.At(i); 100 | 101 | while (idx > 0 && buffer[idx - 1] == ' ') 102 | --idx; 103 | } 104 | 105 | buffer[idx] = '\0'; 106 | return true; 107 | } 108 | private: 109 | FixedLengthString<8, ' '> _name; 110 | FixedLengthString<3, ' '> _extension; 111 | FlagField _flags{}; 112 | ByteBlob<2> _unused; 113 | UnalignedInt _ctimeHMS = 0; 114 | UnalignedInt _ctimeYMD = 0; 115 | UnalignedInt _atimeYMD = 0; 116 | UnalignedInt _clusterIndexHigh = 0; 117 | UnalignedInt _mtimeHMS = 0; 118 | UnalignedInt _mtimeYMD = 0; 119 | UnalignedInt _clusterIndexLow = 0; 120 | UnalignedInt _size = 0; 121 | }; 122 | 123 | static_assert(sizeof(FatDirent) == 32); 124 | 125 | #endif /* FAT_DIRENT_H */ 126 | -------------------------------------------------------------------------------- /include/fs/FatDirentLong.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * FatDirentLong.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef FAT_DIRENT_LONG_H 8 | #define FAT_DIRENT_LONG_H 9 | 10 | #include "fs/FatDirent.h" 11 | 12 | class FatDirentLong { 13 | public: 14 | bool IsLast() const { 15 | return _seqNumber & 0x40; 16 | } 17 | 18 | uint8_t SequenceNumber() const { 19 | return _seqNumber & 0x3F; 20 | } 21 | 22 | auto EntryFlags() const { 23 | return _attrib; 24 | } 25 | 26 | auto ShortNameChecksum() const { 27 | return _shortChecksum; 28 | } 29 | 30 | void NameToString(char buffer[14]) const { 31 | char *ptr = Stringify(_namePart, 5, buffer); 32 | ptr = Stringify(_namePart2, 6, ptr); 33 | ptr = Stringify(_namePart3, 2, ptr); 34 | *ptr = '\0'; 35 | } 36 | private: 37 | char *Stringify(const UnalignedInt *in, size_t count, char *out) const { 38 | for (size_t i = 0; i < count; ++i) { 39 | auto x = in[i].Read(); 40 | 41 | if ((x > 0 && x < 0x20) || x >= 0x7F) { 42 | *(out++) = '?'; 43 | } else { 44 | *(out++) = x; 45 | } 46 | } 47 | return out; 48 | } 49 | 50 | uint8_t _seqNumber; 51 | UnalignedInt _namePart[5]; 52 | FlagField _attrib; 53 | uint8_t _entryType; 54 | uint8_t _shortChecksum; 55 | UnalignedInt _namePart2[6]; 56 | UnalignedInt _unused; 57 | UnalignedInt _namePart3[2]; 58 | }; 59 | 60 | static_assert(sizeof(FatDirentLong) == 32); 61 | static_assert(sizeof(FatDirentLong) == sizeof(FatDirent)); 62 | 63 | #endif /* FAT_DIRENT_LONG_H */ 64 | -------------------------------------------------------------------------------- /include/fs/FatFs.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * FatFs.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef FAT_FS_H 8 | #define FAT_FS_H 9 | 10 | #include "device/IBlockDevice.h" 11 | #include "types/FlagField.h" 12 | #include "types/UniquePtr.h" 13 | #include "fs/FatSuper.h" 14 | #include "fs/FatName.h" 15 | #include "StringUtil.h" 16 | #include "Memory.h" 17 | 18 | #include 19 | 20 | struct FatFile { 21 | uint32_t cluster; 22 | uint32_t size; 23 | FlagField flags; 24 | }; 25 | 26 | class FatFs { 27 | public: 28 | FatFs() = delete; 29 | 30 | FatFs(UniquePtr blk, const FatSuper &fsSuper) : 31 | _blk(std::move(blk)), super(fsSuper) { 32 | currentFatSector = 0xFFFFFFFF; 33 | currentDataCluster = 0xFFFFFFFF; 34 | 35 | fatWindow = (uint8_t *)malloc(_blk->SectorSize()); 36 | dataWindow = (uint8_t *)malloc(BytesPerCluster()); 37 | } 38 | 39 | ~FatFs() { 40 | free(fatWindow); 41 | free(dataWindow); 42 | fatWindow = nullptr; 43 | dataWindow = nullptr; 44 | } 45 | 46 | size_t BytesPerCluster() const { 47 | return super.SectorsPerCluster() * _blk->SectorSize(); 48 | } 49 | 50 | auto RootDir() const { 51 | FatFile out; 52 | out.cluster = super.RootDirIndex(); 53 | out.size = 0; 54 | out.flags.Clear(); 55 | out.flags.Set(FatDirent::Flags::Directory); 56 | return out; 57 | } 58 | 59 | int32_t ReadAt(const FatFile &finfo, uint8_t *buffer, 60 | uint32_t offset, uint32_t size) { 61 | if (finfo.size > 0) { 62 | if (offset >= finfo.size) 63 | return 0; 64 | 65 | if (size > (finfo.size - offset)) 66 | size = (finfo.size - offset); 67 | } 68 | 69 | auto cluster = finfo.cluster; 70 | int32_t ret = 0; 71 | 72 | while (size > 0 && cluster < 0x0FFFFFF0) { 73 | if (offset < BytesPerCluster()) { 74 | if (!LoadDataCluster(cluster)) 75 | return -1; 76 | 77 | uint32_t diff = BytesPerCluster() - offset; 78 | if (diff > size) 79 | diff = size; 80 | 81 | for (uint32_t i = 0; i < diff; ++i) 82 | *(buffer++) = dataWindow[offset + i]; 83 | 84 | offset = 0; 85 | size -= diff; 86 | ret += diff; 87 | } else { 88 | offset -= BytesPerCluster(); 89 | } 90 | 91 | if (size > 0) { 92 | uint32_t next; 93 | if (!ReadFatIndex(cluster, next)) 94 | return -1; 95 | cluster = next; 96 | } 97 | } 98 | 99 | return ret; 100 | } 101 | 102 | enum class FindResult { 103 | Ok = 0, 104 | NameInvalid = -1, 105 | IOError = -2, 106 | NoEntry = -3, 107 | NotDir = -4, 108 | }; 109 | 110 | FindResult FindInDirectory(const FatFile &in, const char *name, FatFile &out) { 111 | if (!IsShortName(name)) 112 | return FindResult::NameInvalid; 113 | 114 | if (!in.flags.IsSet(FatDirent::Flags::Directory)) 115 | return FindResult::NotDir; 116 | 117 | auto index = in.cluster; 118 | 119 | while (index < 0x0FFFFFF0) { 120 | uint32_t next; 121 | 122 | if (!LoadDataCluster(index) || !ReadFatIndex(index, next)) 123 | return FindResult::IOError; 124 | 125 | index = next; 126 | 127 | auto *entS = (FatDirent *)dataWindow; 128 | auto max = BytesPerCluster() / sizeof(*entS); 129 | 130 | for (decltype(max) i = 0; i < max; ++i) { 131 | if (entS[i].IsLastInList()) 132 | return FindResult::NoEntry; 133 | if (entS[i].EntryFlags().IsSet(FatDirent::Flags::LongFileName)) 134 | continue; 135 | if (entS[i].IsDummiedOut()) 136 | continue; 137 | 138 | char buffer[8 + 1 + 3 + 1]; 139 | entS[i].NameToString(buffer); 140 | 141 | if (StrEqual(buffer, name)) { 142 | out.cluster = entS[i].ClusterIndex(); 143 | out.size = entS[i].Size(); 144 | out.flags = entS[i].EntryFlags(); 145 | return FindResult::Ok; 146 | } 147 | } 148 | } 149 | 150 | return FindResult::NoEntry; 151 | } 152 | 153 | FindResult FindByPath(const char *name, FatFile &out) { 154 | out = RootDir(); 155 | 156 | while (*name != '\0') { 157 | if (*name == '/' || *name == '\\') { 158 | while (*name == '/' || *name == '\\') 159 | ++name; 160 | continue; 161 | } 162 | 163 | char name8_3[8 + 1 + 3 + 1]; 164 | int count = 0; 165 | 166 | while (*name != '\0' && *name != '/' && *name != '\\') { 167 | if (count >= (8 + 1 + 3)) 168 | return FindResult::NameInvalid; 169 | name8_3[count++] = *(name++); 170 | } 171 | 172 | name8_3[count] = '\0'; 173 | 174 | auto ret = FindInDirectory(out, name8_3, out); 175 | if (ret != FindResult::Ok) 176 | return ret; 177 | } 178 | 179 | return FindResult::Ok; 180 | } 181 | 182 | const IBlockDevice &BlockDevice() const { 183 | return *_blk; 184 | } 185 | private: 186 | bool LoadDataCluster(uint32_t index) { 187 | if (index < 2) 188 | return false; 189 | 190 | if (index == currentDataCluster) 191 | return true; 192 | 193 | auto lba = super.ClusterIndex2Sector(index); 194 | 195 | for (uint32_t i = 0; i < super.SectorsPerCluster(); ++i) { 196 | auto *ptr = dataWindow + i * _blk->SectorSize(); 197 | 198 | if (!_blk->LoadSector(lba + i, ptr)) 199 | return false; 200 | } 201 | 202 | currentDataCluster = index; 203 | return true; 204 | } 205 | 206 | bool LoadFatSector(uint32_t index) { 207 | if (index >= super.SectorsPerFat()) 208 | return false; 209 | 210 | if (index != currentFatSector) { 211 | auto lba = super.ReservedSectors() + index; 212 | 213 | if (!_blk->LoadSector(lba, fatWindow)) 214 | return false; 215 | 216 | currentFatSector = index; 217 | } 218 | return true; 219 | } 220 | 221 | bool ReadFatIndex(uint32_t index, uint32_t &out) { 222 | auto sector = (index * 4) / _blk->SectorSize(); 223 | auto offset = (index * 4) % _blk->SectorSize(); 224 | 225 | if (!LoadFatSector(sector)) 226 | return false; 227 | 228 | out = *((uint32_t *)(fatWindow + offset)); 229 | return true; 230 | } 231 | 232 | UniquePtr _blk; 233 | uint8_t *fatWindow; 234 | uint8_t *dataWindow; 235 | const FatSuper &super; 236 | uint32_t currentFatSector; 237 | uint32_t currentDataCluster; 238 | }; 239 | 240 | #endif /* FAT_FS_H */ 241 | -------------------------------------------------------------------------------- /include/fs/FatFsInfo.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * FatFsInfo.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef FAT_FS_INFO_H 8 | #define FAT_FS_INFO_H 9 | 10 | #include "types/ByteBlob.h" 11 | 12 | constexpr uint32_t FatFsInfoMagic1 = 0x41615252; 13 | constexpr uint32_t FatFsInfoMagic2 = 0x61417272; 14 | constexpr uint32_t FatFsInfoMagic3 = 0xAA550000; 15 | 16 | class FatFsInfo { 17 | public: 18 | bool IsValid() const { 19 | return _magic1 == FatFsInfoMagic1 && 20 | _magic2 == FatFsInfoMagic2 && 21 | _magic3 == FatFsInfoMagic3; 22 | } 23 | 24 | void SetNextFreeCluster(uint32_t index) { 25 | _nextFreeCluster = index; 26 | } 27 | 28 | void SetNumFreeCluster(uint32_t count) { 29 | _freeClusters = count; 30 | } 31 | private: 32 | uint32_t _magic1 = FatFsInfoMagic1; 33 | ByteBlob<480> _reserved0; 34 | uint32_t _magic2 = FatFsInfoMagic2; 35 | uint32_t _freeClusters; 36 | uint32_t _nextFreeCluster; 37 | ByteBlob<12> _reserved1; 38 | uint32_t _magic3 = FatFsInfoMagic3; 39 | }; 40 | 41 | static_assert(sizeof(FatFsInfo) == 512); 42 | 43 | #endif /* FAT_FS_INFO_H */ 44 | -------------------------------------------------------------------------------- /include/fs/FatName.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * FatName.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef FAT_CHARSET_H 8 | #define FAT_CHARSET_H 9 | 10 | [[maybe_unused]] static bool IsValidFatChar(int c) 11 | { 12 | // Must be printable ASCII 13 | if (c < 0x20 || c > 0x7E) 14 | return false; 15 | 16 | // Must be shouty case 17 | if (c >= 0x61 && c <= 0x7A) 18 | return false; 19 | 20 | // Must not be :;<=>? 21 | if (c >= 0x3A && c <= 0x3F) 22 | return false; 23 | 24 | // more special chars to exclude 25 | if (c == '|' || c == '\\' || c == '/' || c == '"' || c == '*') 26 | return false; 27 | 28 | if (c == '+' || c == ',' || c == '[' || c == ']') 29 | return false; 30 | 31 | return true; 32 | } 33 | 34 | [[maybe_unused]] static bool IsShortName(const char *entry) 35 | { 36 | size_t len = 0; 37 | while (entry[len] != '\0') 38 | ++len; 39 | 40 | if (len == 0 || len > (8 + 1 + 3)) 41 | return false; 42 | 43 | int dotPos = -1; 44 | 45 | for (size_t i = 0; i < len; ++i) { 46 | if (!IsValidFatChar(entry[i])) 47 | return false; 48 | 49 | if (entry[i] == '.') { 50 | if (dotPos >= 0) 51 | return false; 52 | if (dotPos < 0) 53 | dotPos = i; 54 | } 55 | } 56 | 57 | if (dotPos < 0) 58 | return len <= 8; 59 | 60 | if (dotPos == 0 || dotPos == (int)(len - 1)) 61 | return false; 62 | 63 | if (dotPos > 8 || (len - dotPos) > 4) 64 | return false; 65 | 66 | return true; 67 | } 68 | 69 | #endif /* FAT_CHARSET_H */ 70 | -------------------------------------------------------------------------------- /include/fs/FatSuper.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * FatSuper.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef FAT_SUPER_H 8 | #define FAT_SUPER_H 9 | 10 | #include "types/FixedLengthString.h" 11 | #include "types/UnalignedInt.h" 12 | #include "types/ByteBlob.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | class FatSuper { 20 | public: 21 | FatSuper() = default; 22 | 23 | void SetTotalSectorCount(uint32_t count) { 24 | _totalSectorCount = count; 25 | } 26 | 27 | void SetSectorsPerFat(uint32_t count) { 28 | _sectorsPerFat = count; 29 | } 30 | 31 | void SetBootCode(const uint8_t *data, size_t size, 32 | size_t entryPoint = 0) { 33 | size = std::min(size, sizeof(_bootCode)); 34 | 35 | memcpy(&_bootCode, data, size); 36 | 37 | _introJump.SetEntryPoint(entryPoint); 38 | } 39 | 40 | void SetOEMName(const char *name) { 41 | _oemName.Set(name); 42 | } 43 | 44 | uint16_t BytesPerSector() const { 45 | return _bytesPerSector.Read(); 46 | } 47 | 48 | uint16_t ReservedSectors() const { 49 | return _numReservedSectors.Read(); 50 | } 51 | 52 | uint16_t FsInfoIndex() const { 53 | return _fsInfoIndex.Read(); 54 | } 55 | 56 | void SetFsInfoIndex(uint16_t value) { 57 | _fsInfoIndex.Set(value); 58 | } 59 | 60 | uint16_t BackupIndex() const { 61 | return _bootSecCopyIndex.Read(); 62 | } 63 | 64 | void SetBackupIndex(uint16_t value) { 65 | _bootSecCopyIndex.Set(value); 66 | } 67 | 68 | unsigned int SectorsPerCluster() const { 69 | return _sectorsPerCluster; 70 | } 71 | 72 | unsigned int NumFats() const { 73 | return _numFats; 74 | } 75 | 76 | unsigned int SectorCount() const { 77 | return _totalSectorCount.Read(); 78 | } 79 | 80 | unsigned int SectorsPerFat() const { 81 | return _sectorsPerFat.Read(); 82 | } 83 | 84 | unsigned int RootDirIndex() const { 85 | return _rootDirIndex.Read(); 86 | } 87 | 88 | uint32_t ClusterIndex2Sector(uint32_t N) const { 89 | auto first = ReservedSectors() + NumFats() * SectorsPerFat(); 90 | 91 | if (N <= 2) 92 | return first; 93 | 94 | return ((N - 2) * SectorsPerCluster()) + first; 95 | } 96 | private: 97 | struct { 98 | public: 99 | void SetEntryPoint(size_t entry) { 100 | entry = std::min(entry, sizeof(_bootCode)); 101 | 102 | _jump = 0xEB; 103 | _codeOffset = offsetof(FatSuper, _bootCode) - 2 + entry; 104 | _nop = 0x90; 105 | } 106 | private: 107 | uint8_t _jump; 108 | uint8_t _codeOffset; 109 | uint8_t _nop; 110 | } _introJump; 111 | 112 | FixedLengthString<8, ' '> _oemName; 113 | 114 | /* DOS 2.0 BPB */ 115 | UnalignedInt _bytesPerSector; 116 | uint8_t _sectorsPerCluster; 117 | UnalignedInt _numReservedSectors; 118 | uint8_t _numFats; 119 | ByteBlob<4> _unused0; 120 | uint8_t _mediaDescriptor; 121 | ByteBlob<2> _unused1; 122 | 123 | /* DOS 3.31 BPB */ 124 | UnalignedInt _sectorsPerTrack; 125 | UnalignedInt _headsPerDisk; 126 | ByteBlob<4> _unused2; 127 | UnalignedInt _totalSectorCount; 128 | 129 | /* FAT32 BPB */ 130 | UnalignedInt _sectorsPerFat; 131 | UnalignedInt _mirrorFlags; 132 | UnalignedInt _version; 133 | UnalignedInt _rootDirIndex; 134 | UnalignedInt _fsInfoIndex; 135 | UnalignedInt _bootSecCopyIndex; 136 | ByteBlob<12> _unused3; 137 | uint8_t _physDriveNum; 138 | ByteBlob<1> _wtf1; 139 | uint8_t _extBootSignature; 140 | UnalignedInt _volumeId; 141 | FixedLengthString<11, ' '> _label; 142 | FixedLengthString<8, ' '> _fsName; 143 | 144 | /* boot sector code */ 145 | ByteBlob<420> _bootCode; 146 | 147 | /* magic signature */ 148 | UnalignedInt _bootSignature; 149 | }; 150 | 151 | static_assert(sizeof(FatSuper) == 512); 152 | 153 | #endif /* FAT_SUPER_H */ 154 | -------------------------------------------------------------------------------- /include/host/File.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_H 2 | #define FILE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | class File { 14 | public: 15 | File() : _fd(-1) { 16 | } 17 | 18 | File(const char *path, bool readonly) { 19 | _fd = open(path, readonly ? O_RDONLY : O_RDWR); 20 | if (_fd < 0) 21 | HandleError(-1, errno); 22 | } 23 | 24 | ~File() { 25 | if (_fd >= 0) 26 | close(_fd); 27 | } 28 | 29 | File(const File &) = delete; 30 | File &operator= (const File &) = delete; 31 | 32 | File(File &&o) { 33 | _fd = o._fd; 34 | o._fd = -1; 35 | } 36 | 37 | File &operator= (File &&o) { 38 | std::swap(o._fd, _fd); 39 | return *this; 40 | } 41 | 42 | void ReadAt(uint64_t offset, void *data, size_t size) const { 43 | while (size > 0) { 44 | auto ret = pread(_fd, data, size, offset); 45 | if (ret < 0 && errno == EINTR) 46 | continue; 47 | 48 | if (ret <= 0) 49 | HandleError(ret, errno); 50 | 51 | data = (char *)data + ret; 52 | size -= ret; 53 | offset += ret; 54 | } 55 | } 56 | 57 | void WriteAt(uint64_t offset, const void *data, size_t size) { 58 | while (size > 0) { 59 | auto ret = pwrite(_fd, data, size, offset); 60 | if (ret < 0 && errno == EINTR) 61 | continue; 62 | 63 | if (ret <= 0) 64 | HandleError(ret, errno); 65 | 66 | data = (const char *)data + ret; 67 | size -= ret; 68 | offset += ret; 69 | } 70 | } 71 | 72 | ssize_t Read(void *data, size_t size) { 73 | ssize_t total = 0; 74 | 75 | while (size > 0) { 76 | auto ret = read(_fd, data, size); 77 | if (ret == 0) 78 | break; 79 | if (ret < 0) { 80 | if (errno == EINTR) 81 | continue; 82 | HandleError(ret, errno); 83 | } 84 | 85 | data = (char *)data + ret; 86 | size -= ret; 87 | total += ret; 88 | } 89 | 90 | return total; 91 | } 92 | private: 93 | void HandleError(ssize_t retCode, int errorCode) const { 94 | if (retCode < 0) { 95 | throw std::system_error(errorCode, 96 | std::system_category(), 97 | _filename); 98 | } else { 99 | std::ostringstream str; 100 | 101 | str << _filename << ": unexpected end-of-file"; 102 | 103 | throw std::runtime_error(str.str()); 104 | } 105 | } 106 | 107 | int _fd; 108 | std::string _filename; 109 | }; 110 | 111 | #endif /* FILE_H */ 112 | -------------------------------------------------------------------------------- /include/kernel/MultiBootHeader.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * MultiBootHeader.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef MULTIBOOT_HEADER_H 8 | #define MULTIBOOT_HEADER_H 9 | 10 | #include 11 | 12 | #include "types/FlagField.h" 13 | 14 | class MultiBootHeader { 15 | public: 16 | enum class KernelFlags : uint32_t { 17 | Force4KAlign = 0x00000001, 18 | WantMemoryMap = 0x00000002, 19 | WantVidmode = 0x00000004, 20 | HaveLayoutInfo = 0x00010000, 21 | }; 22 | 23 | static constexpr uint32_t Magic = 0x1BADB002; 24 | 25 | bool IsValid() const { 26 | return _magic == Magic && 27 | _checksum == (~(_magic + _flags.RawValue()) + 1); 28 | } 29 | 30 | auto Flags() const { 31 | return _flags; 32 | } 33 | 34 | bool ExtractMemLayout(uint32_t hdrFileOffset, uint32_t filesize, 35 | uint32_t &fileStart, uint32_t &memStart, 36 | uint32_t &count) const { 37 | // end address must be after start address 38 | if (_loadEndAddr <= _loadAddr) 39 | return false; 40 | 41 | fileStart = 0; 42 | memStart = _loadAddr; 43 | count = _loadEndAddr - _loadAddr; 44 | 45 | // BSS must be after the text & data blob 46 | if (_bssEndAddr < _loadAddr) 47 | return false; 48 | 49 | // header & entry point must be inside the text & data blob 50 | if (_hdrAddr < _loadAddr || _hdrAddr >= _loadEndAddr) 51 | return false; 52 | 53 | if (_entryAddr < _loadAddr || _entryAddr >= _loadEndAddr) 54 | return false; 55 | 56 | // distance of header from load address cannot be more than 57 | // there is in the file 58 | auto hdrMemOffset = _hdrAddr - _loadAddr; 59 | 60 | if (hdrMemOffset > hdrFileOffset) 61 | return false; 62 | 63 | // but there can be extra data in front of the text segment 64 | if (hdrFileOffset > hdrMemOffset) 65 | fileStart = hdrFileOffset - hdrMemOffset; 66 | 67 | // must fit in file 68 | return count <= (filesize - fileStart); 69 | } 70 | 71 | void *EntryPoint() const { 72 | return (void *)_entryAddr; 73 | } 74 | 75 | size_t BSSSize() const { 76 | return _bssEndAddr > _loadEndAddr ? 77 | (_bssEndAddr - _loadEndAddr) : 0; 78 | } 79 | private: 80 | uint32_t _magic; 81 | FlagField _flags; 82 | uint32_t _checksum; 83 | uint32_t _hdrAddr; 84 | uint32_t _loadAddr; 85 | uint32_t _loadEndAddr; 86 | uint32_t _bssEndAddr; 87 | uint32_t _entryAddr; 88 | }; 89 | 90 | static_assert(sizeof(MultiBootHeader) == 32); 91 | 92 | #endif /* MULTIBOOT_HEADER_H */ 93 | -------------------------------------------------------------------------------- /include/kernel/MultiBootInfo.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * MultiBootInfo.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef MULTIBOOT_INFO_H 8 | #define MULTIBOOT_INFO_H 9 | 10 | #include 11 | 12 | #include "kernel/MultiBootMmap.h" 13 | #include "types/FlagField.h" 14 | 15 | class MultiBootInfo { 16 | public: 17 | enum class InfoFlag : uint32_t { 18 | MemInfo = 0x00000001, 19 | BootDev = 0x00000002, 20 | CmdLine = 0x00000004, 21 | Mods = 0x00000008, 22 | AoutSyms = 0x00000010, 23 | ELFShdr = 0x00000020, 24 | MemMap = 0x00000040, 25 | DeviceInfo = 0x00000080, 26 | ConfigTable = 0x00000100, 27 | BootLoaderName = 0x00000200, 28 | APMTable = 0x00000400, 29 | VBEInfo = 0x00000800, 30 | FrameBufferInfo = 0x00001000, 31 | }; 32 | 33 | const char *BootLoaderName() const { 34 | if (!_flags.IsSet(InfoFlag::BootLoaderName)) 35 | return nullptr; 36 | 37 | return _bootLoaderName; 38 | } 39 | 40 | void SetLoadName(const char *name) { 41 | _bootLoaderName = name; 42 | _flags.Set(InfoFlag::BootLoaderName); 43 | } 44 | 45 | const char *CommandLine() const { 46 | if (!_flags.IsSet(InfoFlag::CmdLine)) 47 | return nullptr; 48 | 49 | return _cmdline; 50 | } 51 | 52 | const MultiBootMmap *MemoryMapBegin() const { 53 | if (!_flags.IsSet(InfoFlag::MemMap)) 54 | return nullptr; 55 | 56 | return _mmap.base; 57 | } 58 | 59 | const MultiBootMmap *MemoryMapEnd() const { 60 | if (!_flags.IsSet(InfoFlag::MemMap)) 61 | return nullptr; 62 | 63 | auto *end = (const char *)_mmap.base + _mmap.length; 64 | 65 | return (const MultiBootMmap *)end; 66 | } 67 | 68 | void SetMemoryMap(MultiBootMmap *array, size_t count) { 69 | _mmap.base = array; 70 | _mmap.length = count * sizeof(array[0]); 71 | _flags.Set(InfoFlag::MemMap); 72 | 73 | for (size_t i = 0; i < count; ++i) { 74 | if (array[i].Type() != MemoryMapEntry::MemType::Usable) 75 | continue; 76 | if (array[i].BaseAddress() > 0x0'FFFF'FFFFUL) 77 | continue; 78 | 79 | if (array[i].BaseAddress() < 0x0010'0000) { 80 | _memLower = array[i].Size() / 1024; 81 | } else { 82 | _memUpper = array[i].Size() / 1024; 83 | } 84 | } 85 | } 86 | 87 | auto LowMemoryCount() const { 88 | return _memLower; 89 | } 90 | 91 | auto HighMemoryCount() const { 92 | return _memUpper; 93 | } 94 | private: 95 | FlagField _flags{}; 96 | 97 | uint32_t _memLower = 0; 98 | uint32_t _memUpper = 0; 99 | 100 | uint32_t _bootDevice = 0; 101 | char *_cmdline = nullptr; 102 | 103 | uint32_t _modsCount = 0; 104 | uint32_t _modsAddr = 0; 105 | 106 | uint32_t _syms[4]; 107 | 108 | struct { 109 | uint32_t length = 0; 110 | const MultiBootMmap *base = nullptr; 111 | } _mmap; 112 | 113 | uint32_t _drivesLength = 0; 114 | uint32_t _drivesAddr = 0; 115 | 116 | uint32_t _configTable = 0; 117 | const char *_bootLoaderName = nullptr; 118 | uint32_t _apmTable = 0; 119 | 120 | struct { 121 | uint32_t controlInfo = 0; 122 | uint32_t modeInfo = 0; 123 | uint32_t mode = 0; 124 | struct { 125 | uint32_t segment = 0; 126 | uint32_t offset = 0; 127 | uint32_t length = 0; 128 | } interface; 129 | } _vbe; 130 | 131 | struct { 132 | uint32_t addr = 0; 133 | uint32_t pitch = 0; 134 | uint32_t width = 0; 135 | uint32_t height = 0; 136 | uint32_t bpp = 0; 137 | uint32_t type = 0; 138 | } _framebuffer; 139 | 140 | uint8_t color_info[6]; 141 | }; 142 | 143 | static_assert(sizeof(void *) == sizeof(uint32_t)); 144 | static_assert(sizeof(MultiBootInfo) == 128); 145 | 146 | #endif /* MULTIBOOT_INFO_H */ 147 | -------------------------------------------------------------------------------- /include/kernel/MultiBootMmap.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * MultiBootMmap.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef MULTIBOOT_MMAP_H 8 | #define MULTIBOOT_MMAP_H 9 | 10 | #include 11 | 12 | #include "types/FlagField.h" 13 | #include "types/UnalignedInt.h" 14 | #include "BIOS/MemoryMap.h" 15 | 16 | class MultiBootMmap { 17 | public: 18 | auto Type() const { 19 | return _wrapped.Type(); 20 | } 21 | 22 | auto BaseAddress() const { 23 | return _wrapped.BaseAddress(); 24 | } 25 | 26 | auto Size() const { 27 | return _wrapped.Size(); 28 | } 29 | 30 | const auto *TypeAsString() const { 31 | return _wrapped.TypeAsString(); 32 | } 33 | 34 | const auto *Next() const { 35 | auto next = (uint32_t)this + _size.Read() + sizeof(_size); 36 | 37 | return (const MultiBootMmap *)next; 38 | } 39 | 40 | MultiBootMmap &operator= (const MemoryMapEntry &e820) { 41 | _wrapped = e820; 42 | return *this; 43 | } 44 | 45 | void SetNext(MultiBootMmap *next) { 46 | _size = (char *)next - (char *)this - sizeof(_size); 47 | } 48 | private: 49 | UnalignedInt _size; 50 | MemoryMapEntry _wrapped; 51 | }; 52 | 53 | static_assert(sizeof(void *) == sizeof(uint32_t)); 54 | static_assert(sizeof(MultiBootMmap) == 24); 55 | 56 | #endif /* MULTIBOOT_MMAP_H */ 57 | -------------------------------------------------------------------------------- /include/part/CHSPacked.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * CHSPacked.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef CHS_PACKED_H 8 | #define CHS_PACKED_H 9 | 10 | #include 11 | 12 | class CHSPacked { 13 | public: 14 | uint8_t Head() const { 15 | return _lo; 16 | } 17 | 18 | void SetHead(uint8_t value) { 19 | _lo = value; 20 | } 21 | 22 | uint8_t Sector() const { 23 | return _mid & 0x3F; 24 | } 25 | 26 | void SetSector(uint8_t value) { 27 | _mid = (_mid & 0xC0) | (value & 0x3F); 28 | } 29 | 30 | uint16_t Cylinder() const { 31 | return (static_cast(_mid & 0xC0) << 2) | _hi; 32 | } 33 | 34 | void SetCylinder(uint16_t value) { 35 | _mid = (_mid & 0x3F) | ((value >> 2) & 0xC0); 36 | _hi = value & 0xFF; 37 | } 38 | private: 39 | uint8_t _lo = 0; 40 | uint8_t _mid = 0; 41 | uint8_t _hi = 0; 42 | }; 43 | 44 | static_assert(sizeof(CHSPacked) == 3); 45 | 46 | #endif /* CHS_PACKED_H */ 47 | -------------------------------------------------------------------------------- /include/part/MBREntry.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * MBREntry.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef MBR_ENTRY_H 8 | #define MBR_ENTRY_H 9 | 10 | #include 11 | #include "part/CHSPacked.h" 12 | #include "types/UnalignedInt.h" 13 | #include "types/FlagField.h" 14 | 15 | class MBREntry { 16 | public: 17 | enum class Attribute : uint8_t { 18 | Bootable = 0x80 19 | }; 20 | 21 | bool IsBootable() const { 22 | return _attrib.IsSet(Attribute::Bootable); 23 | } 24 | 25 | const CHSPacked &StartAddressCHS() const { 26 | return _chsStart; 27 | } 28 | 29 | uint32_t StartAddressLBA() const { 30 | return _lbaStart.Read(); 31 | } 32 | private: 33 | FlagField _attrib; 34 | CHSPacked _chsStart; 35 | uint8_t _type; 36 | CHSPacked _chsEnd; 37 | UnalignedInt _lbaStart; 38 | UnalignedInt _sectorCount; 39 | }; 40 | 41 | static_assert(sizeof(MBREntry) == 16); 42 | 43 | #endif /* MBR_ENTRY_H */ 44 | -------------------------------------------------------------------------------- /include/part/MBRTable.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * MBRTable.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef MBR_TABLE_H 8 | #define MBR_TABLE_H 9 | 10 | #include "part/MBREntry.h" 11 | 12 | struct MBRTable { 13 | MBREntry entries[4]; 14 | uint16_t magic; 15 | }; 16 | 17 | static_assert(sizeof(MBRTable) == (4 * 16 + 2)); 18 | 19 | #endif /* MBR_TABLE_H */ 20 | -------------------------------------------------------------------------------- /include/pm86.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * pm86.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef PM86_H 8 | #define PM86_H 9 | 10 | #include 11 | #include 12 | 13 | // Try to enable the A20 line by various means and test if it worked 14 | extern bool EnableA20(); 15 | 16 | extern "C" { 17 | /* 18 | Switch to protected mode, call a 32 bit function with possible 19 | arguments and switch back into 16 bit real-mode before returning. 20 | */ 21 | void ProtectedModeCall(...); 22 | } 23 | 24 | void CopyMemory32(void *dst, const void *src, size_t count); 25 | 26 | void ClearMemory32(void *dst, size_t size); 27 | 28 | #endif /* PM86_H */ 29 | -------------------------------------------------------------------------------- /include/types/ByteBlob.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * ByteBlob.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef BYTE_BLOB_H 8 | #define BYTE_BLOB_H 9 | 10 | #include 11 | #include 12 | 13 | template 14 | class ByteBlob { 15 | public: 16 | ByteBlob() { 17 | for (auto &x : _raw) 18 | x = INIT; 19 | } 20 | private: 21 | uint8_t _raw[COUNT]; 22 | }; 23 | 24 | #endif /* BYTE_BLOB_H */ 25 | -------------------------------------------------------------------------------- /include/types/FixedLengthString.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * FixedLengthString.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef FIXED_LENGTH_STRING_H 8 | #define FIXED_LENGTH_STRING_H 9 | 10 | #include 11 | #include 12 | 13 | template 14 | class FixedLengthString { 15 | public: 16 | FixedLengthString() { 17 | for (auto &it : _raw) 18 | it = FILL; 19 | } 20 | 21 | FixedLengthString(const char *value) { 22 | Set(value); 23 | } 24 | 25 | void Set(const char *value) { 26 | size_t i = 0; 27 | 28 | for (; i < COUNT && value[i] != '\0'; ++i) 29 | _raw[i] = value[i]; 30 | 31 | for (; i < COUNT; ++i) 32 | _raw[i] = FILL; 33 | } 34 | 35 | uint8_t At(size_t i) const { 36 | return i < COUNT ? _raw[i] : FILL; 37 | } 38 | private: 39 | uint8_t _raw[COUNT]; 40 | }; 41 | 42 | 43 | #endif /* FIXED_LENGTH_STRING_H */ 44 | -------------------------------------------------------------------------------- /include/types/FlagField.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * FlagField.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef FLAG_FIELD_H 8 | #define FLAG_FIELD_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | template 15 | class FlagField { 16 | private: 17 | class FlagRef { 18 | public: 19 | FlagRef(FlagField &ref, T flag) : _ref(ref), _flag(flag) { 20 | } 21 | 22 | FlagRef() = delete; 23 | FlagRef(const FlagRef &) = delete; 24 | FlagRef(FlagRef &&) = delete; 25 | 26 | constexpr FlagRef &operator= (FlagRef &&o) { 27 | _ref.Set(_flag, (bool)o); 28 | return *this; 29 | } 30 | 31 | constexpr FlagRef &operator= (const FlagRef &o) { 32 | _ref.Set(_flag, (bool)o); 33 | return *this; 34 | } 35 | 36 | constexpr bool operator= (bool set) { 37 | _ref.Set(_flag, set); 38 | return set; 39 | } 40 | 41 | constexpr operator bool() const { 42 | return _ref.IsSet(_flag); 43 | } 44 | private: 45 | FlagField &_ref; 46 | T _flag; 47 | }; 48 | 49 | U _value; 50 | static_assert(std::is_enum::value); 51 | static_assert(std::is_integral::value); 52 | static_assert(!std::numeric_limits::is_signed); 53 | static_assert(sizeof(U) >= sizeof(T)); 54 | public: 55 | FlagField() : _value(0) { 56 | } 57 | 58 | explicit constexpr FlagField(U raw) : _value(raw) { 59 | } 60 | 61 | constexpr FlagField(const std::initializer_list &init) : _value(0) { 62 | for (auto &flag : init) { 63 | _value |= static_cast(flag); 64 | } 65 | } 66 | 67 | constexpr void Set(T flag) { 68 | _value |= static_cast(flag); 69 | } 70 | 71 | constexpr void Clear(T flag) { 72 | _value &= ~static_cast(flag); 73 | } 74 | 75 | constexpr void Clear() { 76 | _value = 0; 77 | } 78 | 79 | constexpr void Set(T flag, bool setval) { 80 | if (setval) { 81 | Set(flag); 82 | } else { 83 | Clear(flag); 84 | } 85 | } 86 | 87 | constexpr bool IsSet(T flag) const { 88 | return (_value & static_cast(flag)) == static_cast(flag); 89 | } 90 | 91 | constexpr const FlagRef operator[] (T flag) const && { 92 | return FlagRef(*this, flag); 93 | } 94 | 95 | constexpr FlagRef operator[] (T flag) & { 96 | return FlagRef(*this, flag); 97 | } 98 | 99 | constexpr U RawValue() const { 100 | return _value; 101 | } 102 | }; 103 | 104 | #endif /* FLAG_FIELD_H */ 105 | -------------------------------------------------------------------------------- /include/types/UnalignedInt.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * UnalignedInt.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef UNALIGNED_INT_H 8 | #define UNALIGNED_INT_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | template 16 | class UnalignedInt { 17 | public: 18 | UnalignedInt() { 19 | Set(0); 20 | } 21 | 22 | UnalignedInt(T value) { 23 | Set(value); 24 | } 25 | 26 | UnalignedInt &operator= (T value) { 27 | Set(value); 28 | return *this; 29 | } 30 | 31 | T Read() const { 32 | size_t i = 0; 33 | T out = 0; 34 | 35 | for (auto x : _raw) 36 | out |= x << (8 * (i++)); 37 | 38 | return out; 39 | } 40 | 41 | void Set(T value) { 42 | for (size_t i = 0; i < sizeof(T); ++i) { 43 | _raw[i] = value & 0x0FF; 44 | value >>= 8; 45 | } 46 | } 47 | private: 48 | uint8_t _raw[sizeof(T)]; 49 | 50 | static_assert(std::is_integral::value); 51 | static_assert(!std::numeric_limits::is_signed); 52 | }; 53 | 54 | #endif /* UNALIGNED_INT_H */ 55 | -------------------------------------------------------------------------------- /include/types/UniquePtr.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * UniquePtr.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef UNIQUE_PTR_H 8 | #define UNIQUE_PTR_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | template 15 | class UniquePtr { 16 | public: 17 | constexpr UniquePtr() : _raw(nullptr) {} 18 | constexpr UniquePtr(std::nullptr_t null) : _raw(null) {} 19 | explicit UniquePtr(T *ptr) : _raw(ptr) {} 20 | 21 | UniquePtr(UniquePtr &&o) : _raw(o._raw) { 22 | o._raw = nullptr; 23 | } 24 | 25 | template 26 | UniquePtr(UniquePtr &&o) : _raw(o.Release()) { 27 | static_assert(std::is_base_of::value); 28 | } 29 | 30 | UniquePtr &operator= (UniquePtr &&o) { 31 | T *temp = _raw; 32 | _raw = o._raw; 33 | o._raw = temp; 34 | return *this; 35 | } 36 | 37 | ~UniquePtr() { 38 | delete _raw; 39 | } 40 | 41 | UniquePtr(const UniquePtr &o) = delete; 42 | UniquePtr &operator= (const UniquePtr &o) = delete; 43 | 44 | UniquePtr &operator= (std::nullptr_t null) { 45 | delete _raw; 46 | _raw = null; 47 | return *this; 48 | } 49 | 50 | bool operator== (std::nullptr_t null) const { return _raw == null; } 51 | bool operator!= (std::nullptr_t null) const { return _raw == null; } 52 | 53 | T *operator->() const { return _raw; } 54 | T &operator*() const { return *_raw; } 55 | 56 | T *Release() { 57 | auto *temp = _raw; 58 | _raw = nullptr; 59 | return temp; 60 | } 61 | private: 62 | T *_raw; 63 | }; 64 | 65 | template 66 | UniquePtr MakeUnique(Args&&... args) 67 | { 68 | T *obj = new T(std::forward(args)...); 69 | return UniquePtr(obj); 70 | } 71 | 72 | #endif /* UNIQUE_PTR_H */ 73 | -------------------------------------------------------------------------------- /kernel/abi.S: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * abi.S 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | .global _start 8 | .extern multiboot_main 9 | .extern inital_stack_ptr 10 | 11 | /* 12 | We use the linker script to set up the following order of sections: 13 | - text 14 | - data 15 | - bss 16 | 17 | Each with 4k alignment. We instruct the linker to export the following 18 | pseudo symbols for use in the code to get the addresses of the segments. 19 | */ 20 | .extern __start_text 21 | .extern __stop_text 22 | 23 | .extern __start_data 24 | .extern __stop_data 25 | 26 | .extern __start_bss 27 | .extern __stop_bss 28 | 29 | /* 30 | Multiboot header. Because we are using a flat binary instead of ELF, we 31 | manually specify load address and section layout for the boot loader. 32 | */ 33 | .section .mbheader 34 | .align 4 35 | .long 0x1BADB002 36 | .long 0x00010003 37 | .long -(0x1BADB002 + 0x00010003) 38 | .long .mbheader 39 | .long __start_text 40 | .long __stop_data 41 | .long __stop_bss 42 | .long _start 43 | 44 | /* 45 | Entry point that the boot loader jumps to. 46 | */ 47 | .section .text 48 | _start: 49 | movl %eax, %ecx 50 | 51 | movl $gdt_desc, %eax 52 | lgdt (%eax) 53 | 54 | movl $0x10, %eax 55 | movl %eax, %ds 56 | movl %eax, %es 57 | movl %eax, %fs 58 | movl %eax, %gs 59 | movl %eax, %ss 60 | 61 | ljmp $0x08,$.flush 62 | .flush: 63 | movl $_init_stack_ptr, %esp 64 | push %ecx 65 | push %ebx 66 | call multiboot_main 67 | 68 | /* 69 | A dummy GDT with identity mapping, replacing the one from the boot loader. 70 | */ 71 | gdt: 72 | .quad 0x0000000000000000 73 | .quad 0x00CF9A000000FFFF 74 | .quad 0x00CF92000000FFFF 75 | gdt_end: 76 | 77 | gdt_desc: 78 | .word gdt_end - gdt - 1 79 | .long gdt 80 | 81 | /* 82 | Initial kernel stack 83 | */ 84 | .section .bss 85 | _init_stack: 86 | .space 1024 87 | _init_stack_ptr: 88 | -------------------------------------------------------------------------------- /kernel/kernel.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * kernel.cpp 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #include "kernel/MultiBootInfo.h" 8 | #include "device/VideoMemory.h" 9 | #include "device/TextScreen.h" 10 | 11 | extern "C" { 12 | void multiboot_main(const MultiBootInfo *info, uint32_t signature); 13 | }; 14 | 15 | static void PrintMemoryMap(TextScreen& s, 16 | const MultiBootInfo *info) 17 | { 18 | const auto *mmap = info->MemoryMapBegin(); 19 | const auto *mmapEnd = info->MemoryMapEnd(); 20 | 21 | if (mmap == nullptr || mmapEnd == nullptr) { 22 | s << "Boot loader did not provide a memory map!" << "\r\n"; 23 | return; 24 | } 25 | 26 | s << "Multiboot memory map:" << "\r\n"; 27 | 28 | for (; mmap < mmapEnd; mmap = mmap->Next()) { 29 | auto start = mmap->BaseAddress(); 30 | auto end = start; 31 | 32 | if (mmap->Size() > 0) { 33 | end += mmap->Size(); 34 | 35 | if (end < start) { 36 | end = 0xFFFF'FFFF'FFFF'FFFF; 37 | } else { 38 | end -= 1; 39 | } 40 | } 41 | 42 | s.WriteHex(start); 43 | s << " | "; 44 | s.WriteHex(end); 45 | s << " | " << mmap->TypeAsString(); 46 | s << "\r\n"; 47 | } 48 | } 49 | 50 | void multiboot_main(const MultiBootInfo *info, uint32_t signature) 51 | { 52 | TextScreen s; 53 | 54 | s.Driver().SetColor(VideoMemory::Color::White, 55 | VideoMemory::Color::Blue); 56 | s.Reset(); 57 | 58 | s << "Hello 32 bit world!" << "\r\n"; 59 | 60 | if (signature != 0x2BADB002) { 61 | s << "Multi boot signature is broken!" << "\r\n"; 62 | goto fail; 63 | } 64 | 65 | s << "Boot loader name: " << info->BootLoaderName() << "\r\n"; 66 | s << "Low memory: " << info->LowMemoryCount() << "k" << "\r\n"; 67 | s << "High memory: " << info->HighMemoryCount() << "k" << "\r\n"; 68 | 69 | PrintMemoryMap(s, info); 70 | fail: 71 | for (;;) 72 | __asm__ ("hlt"); 73 | } 74 | -------------------------------------------------------------------------------- /kernel/kernel.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("binary") 2 | OUTPUT_ARCH(i386) 3 | 4 | SECTIONS 5 | { 6 | . = 1M; 7 | 8 | .text : ALIGN(4K) { 9 | __start_text = .; 10 | KEEP(*(.mbheader)) 11 | *(.text) 12 | *(.text.*) 13 | *(.rodata) 14 | *(.rodata.*) 15 | . = ALIGN(4K); 16 | __stop_text = .; 17 | } 18 | 19 | .data : ALIGN(4K) { 20 | __start_data = .; 21 | *(.data) 22 | *(.data.*) 23 | . = ALIGN(4K); 24 | __stop_data = .; 25 | } 26 | 27 | .bss : ALIGN(4K) { 28 | __start_bss = .; 29 | *(COMMON) 30 | *(.bss) 31 | *(.bss.*) 32 | . = ALIGN(4K); 33 | __stop_bss = .; 34 | } 35 | 36 | /DISCARD/ : { *(*) } 37 | } 38 | -------------------------------------------------------------------------------- /kernel/meson.build: -------------------------------------------------------------------------------- 1 | kernel = executable( 2 | 'KRNL386', 3 | name_suffix: 'SYS', 4 | sources: [ 5 | 'abi.S', 6 | 'kernel.cpp', 7 | ], 8 | link_args: [ 9 | '-Wl,-T' + join_paths(meson.current_source_dir(), 'kernel.ld'), 10 | '-nostdlib', 11 | ], 12 | link_depends: [ 13 | 'kernel.ld', 14 | ], 15 | cpp_args: pm32_cpp_args, 16 | install: false, 17 | implicit_include_directories: false, 18 | include_directories: incs, 19 | pie: false, 20 | ) 21 | -------------------------------------------------------------------------------- /lib/BIOS/e820.S: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * e820.S 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | .code16 8 | .section ".text" 9 | .globl IntCallE820 10 | .type IntCallE820, @function 11 | IntCallE820: 12 | pushl %ebp 13 | movl %esp, %ebp 14 | pushal 15 | 16 | movl 8(%ebp), %ebx 17 | movl (%ebx), %ebx 18 | 19 | movl $0x0000e820, %eax 20 | movl $20, %ecx 21 | movl $0x534d4150, %edx 22 | movl 12(%ebp), %edi 23 | int $0x15 24 | 25 | /* 26 | Check carry first, some BIOSes don't set the signature and terminate 27 | the list with cary flag. If neither is set, something is fishy. 28 | */ 29 | jc _last 30 | cmpl $0x534d4150, %eax 31 | jne _fail 32 | 33 | /* result write back */ 34 | movl 8(%ebp), %eax 35 | movl %ebx, (%eax) 36 | 37 | /* restore state, leave function */ 38 | popal 39 | xor %eax, %eax 40 | _out: 41 | mov %ebp, %esp 42 | popl %ebp 43 | retl 44 | _last: 45 | popal 46 | movl $1, %eax 47 | jmp _out 48 | _fail: 49 | popal 50 | movl $-1, %eax 51 | jmp _out 52 | .size IntCallE820, .-IntCallE820 53 | -------------------------------------------------------------------------------- /lib/BIOS/meson.build: -------------------------------------------------------------------------------- 1 | libBIOS = static_library( 2 | 'BIOS', 3 | sources: [ 4 | 'e820.S', 5 | ], 6 | cpp_args: realmode_cpp_args, 7 | install: false, 8 | implicit_include_directories: false, 9 | include_directories: incs, 10 | pic: false, 11 | ) 12 | -------------------------------------------------------------------------------- /lib/cxxabi/cxxabi.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * cxxabi.cpp 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #include "cxxabi.h" 8 | 9 | #define ATEXIT_MAX_FUNCS (32) 10 | 11 | struct atexit_func_entry_t { 12 | void (*destructor)(void *); 13 | void *obj; 14 | }; 15 | 16 | static_assert(sizeof(atexit_func_entry_t) == 8); 17 | 18 | static atexit_func_entry_t atexitFuncs[ATEXIT_MAX_FUNCS]; 19 | static unsigned int atexitCount = 0; 20 | 21 | void *__dso_handle = nullptr; 22 | 23 | int __cxa_atexit(void (*f)(void *), void *objptr, void *dso) 24 | { 25 | (void)dso; 26 | 27 | if (atexitCount >= ATEXIT_MAX_FUNCS) 28 | return -1; 29 | 30 | atexitFuncs[atexitCount].destructor = f; 31 | atexitFuncs[atexitCount].obj = objptr; 32 | atexitCount += 1; 33 | return 0; 34 | } 35 | 36 | void __cxa_finalize(void *f) 37 | { 38 | auto i = atexitCount; 39 | 40 | if (f == nullptr) { 41 | while (i--) { 42 | if (atexitFuncs[i].destructor != nullptr) 43 | atexitFuncs[i].destructor(atexitFuncs[i].obj); 44 | } 45 | } else { 46 | while (i--) { 47 | if (atexitFuncs[i].destructor == f) { 48 | atexitFuncs[i].destructor(atexitFuncs[i].obj); 49 | atexitFuncs[i].destructor = nullptr; 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/cxxabi/meson.build: -------------------------------------------------------------------------------- 1 | libcxxabi = static_library( 2 | 'CXXABI', 3 | sources: [ 4 | 'cxxabi.cpp', 5 | ], 6 | cpp_args: realmode_cpp_args, 7 | install: false, 8 | implicit_include_directories: false, 9 | include_directories: incs, 10 | pic: false, 11 | ) 12 | -------------------------------------------------------------------------------- /lib/pm86/a20.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * a20.cpp 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #include "device/PS2Controller.h" 8 | #include "device/SysCtrl.h" 9 | 10 | static bool TestA20() 11 | { 12 | auto old = Peek(0x0000, 0x0500); 13 | 14 | for (int i = 0; i < 100; ++i) { 15 | Poke(0x0000, 0x0500, 0xAA ^ i); 16 | Poke(0xffff, 0x0510, 0x55 ^ i); 17 | 18 | IoWait(); 19 | 20 | bool success = (Peek(0x0000, 0x0500) == (0xAA ^ i) && 21 | Peek(0xffff, 0x0510) == (0x55 ^ i)); 22 | 23 | if (success) { 24 | Poke(0x0000, 0x0500, old); 25 | return true; 26 | } 27 | } 28 | 29 | Poke(0x0000, 0x0500, old); 30 | return false; 31 | } 32 | 33 | bool EnableA20() 34 | { 35 | for (int i = 0; i < 256; ++i) { 36 | if (TestA20()) 37 | return true; 38 | 39 | // Try using INT 15 service 40 | __asm__ __volatile__("pushfl\r\n" 41 | "int $0x15\r\n" 42 | "popfl" 43 | : : "a"(0x2401)); 44 | if (TestA20()) 45 | return true; 46 | 47 | // Try via the keyboard controller 48 | PS2Controller ps2; 49 | 50 | if (ps2.EnableA20Gate()) { 51 | if (TestA20()) 52 | return true; 53 | } 54 | 55 | // Try fast A20 enable via system CTRL port A 56 | SystemCtrlPort ctrl; 57 | 58 | ctrl.EnableFastA20(); 59 | 60 | if (TestA20()) 61 | return true; 62 | } 63 | 64 | return false; 65 | } 66 | -------------------------------------------------------------------------------- /lib/pm86/copy.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * copy.cpp 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #include "pm86.h" 8 | 9 | __asm__ (".code32"); 10 | 11 | void CopyMemory32(void *dst, const void *src, size_t count) 12 | { 13 | if (dst == src || count == 0) 14 | return; 15 | 16 | if (dst < src) { 17 | auto *d = (char *)dst; 18 | auto *s = (char *)src; 19 | 20 | while (count--) 21 | *(d++) = *(s++); 22 | } else { 23 | auto *d = (char *)dst + count - 1; 24 | auto *s = (char *)src + count - 1; 25 | 26 | while (count--) 27 | *(d--) = *(s--); 28 | } 29 | } 30 | 31 | void ClearMemory32(void *dst, size_t size) 32 | { 33 | auto *ptr = (char *)dst; 34 | 35 | while (size--) 36 | *(ptr++) = 0x00; 37 | } 38 | -------------------------------------------------------------------------------- /lib/pm86/meson.build: -------------------------------------------------------------------------------- 1 | libpm86 = static_library( 2 | 'pm86', 3 | sources: [ 4 | 'a20.cpp', 5 | 'copy.cpp', 6 | 'pmcall.S', 7 | ], 8 | cpp_args: realmode_cpp_args, 9 | install: false, 10 | implicit_include_directories: false, 11 | include_directories: incs, 12 | pic: false, 13 | ) 14 | -------------------------------------------------------------------------------- /lib/pm86/pmcall.S: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * pmcall.S 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | .code16 8 | .section ".text" 9 | .globl ProtectedModeCall 10 | .type ProtectedModeCall, @function 11 | ProtectedModeCall: 12 | /* save away the 16 bit context */ 13 | popl %eax 14 | movl %eax, (_scratch) 15 | movl %ebp, (_scratch + 4) 16 | movw %ss, %ax 17 | movw %ax, (_scratch + 8) 18 | movw %sp, (_scratch + 10) 19 | movw %ds, (_scratch + 12) 20 | movw %es, (_scratch + 14) 21 | 22 | /* calculate abolute, 32 bit stack pointer */ 23 | movzx %sp, %ebp 24 | shl $4, %eax 25 | addl %eax, %ebp 26 | movl %ebp, %esp 27 | 28 | /* enter protected mode */ 29 | cli 30 | cld 31 | 32 | xor %eax, %eax 33 | movw %ax, %ds 34 | movl $gdt_desc, %eax 35 | lgdt (%eax) 36 | 37 | movl %cr0, %eax 38 | or 0x01, %al 39 | movl %eax, %cr0 40 | ljmp $0x08,$_enterpm 41 | 42 | .code32 43 | _enterpm: 44 | /* call into the desired 32 bit function */ 45 | mov $0x10, %eax 46 | mov %eax, %ss 47 | mov %eax, %ds 48 | 49 | pop %eax 50 | call *%eax 51 | 52 | /* jump into a 16 bit segment */ 53 | cli 54 | cld 55 | ljmp $0x18,$_seg16 56 | .code16 57 | _seg16: 58 | movw $0x20, %ax 59 | movw %ax, %es 60 | movw %ax, %ds 61 | movw %ax, %ss 62 | 63 | /* leave protected mode */ 64 | movl %cr0, %eax 65 | and ~0x01, %al 66 | movl %eax, %cr0 67 | ljmp $0,$_leavepm 68 | _leavepm: 69 | /* restore old 16 bit environment */ 70 | movl (_scratch + 4), %ebp 71 | movw (_scratch + 8), %ss 72 | movw (_scratch + 10), %ax 73 | movzx %ax, %esp 74 | movw (_scratch + 12), %ds 75 | movw (_scratch + 14), %es 76 | 77 | /* repair stack and return */ 78 | movl (_scratch), %eax 79 | pushl %eax 80 | retl 81 | gdt: 82 | .quad 0x0000000000000000 /* 0x00: null segment */ 83 | .quad 0x00CF9A000000FFFF /* 0x08: 32 bit code segment */ 84 | .quad 0x00CF92000000FFFF /* 0x10: 32 bit data segment */ 85 | .quad 0x008F9A000000FFFF /* 0x18: 16 bit code segment */ 86 | .quad 0x008F92000000FFFF /* 0x18: 16 bit data segment */ 87 | gdt_end: 88 | 89 | gdt_desc: 90 | .word gdt_end - gdt - 1 91 | .long gdt 92 | _scratch: 93 | .quad 0x0000000000000000 94 | .quad 0x0000000000000000 95 | .size ProtectedModeCall, .-ProtectedModeCall 96 | -------------------------------------------------------------------------------- /mbr/abi.S: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * abi.S 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | .code16 8 | .global main 9 | .global CallVbr 10 | .section ".entry" 11 | /* 12 | Main entry point. The BIOS loads the MBR to 0000:7c00 and then jump here. 13 | 14 | We relocate the MBR to 0000:0600 (the linker "thinks" we are there already, 15 | so the addresses are fixed up if we use the symbols, but we need to be 16 | carefull about relative addressing!) 17 | 18 | We also setup a stack and call into the main() function, passing along the 19 | original EDX register value, containing the boot device ID (for use 20 | with INT 13) in the lower 8 bit. 21 | */ 22 | _start: 23 | xor %ax, %ax 24 | mov %ax, %ds 25 | mov %ax, %es 26 | mov %ax, %ss 27 | 28 | mov $0x0a00, %sp 29 | pushl %edx 30 | 31 | mov $0x0600, %di 32 | mov $0x7c00, %si 33 | mov $512, %cx 34 | 35 | rep 36 | movsb 37 | 38 | ljmp $00,$_relocated 39 | 40 | _relocated: 41 | calll main 42 | 43 | /* 44 | void CallVbr(uint32_t edx, const MBREntry *ent); 45 | 46 | Jumps into the volume boot record loaded to 0000:7c00, setting up the 47 | binary interface as expected: 48 | - DL containing the boot device number for INT 13 49 | - DS:SI pointing to the partition table entry that was selected 50 | 51 | Note: edx is popped twice intentionally. First time is to get rid of the 52 | return address that is also on the stack. 53 | */ 54 | CallVbr: 55 | pop %edx 56 | pop %edx 57 | pop %esi 58 | ljmp $00,$0x7c00 59 | -------------------------------------------------------------------------------- /mbr/mbr.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * mbr.cpp 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #include "BIOS/BIOSTextMode.h" 8 | #include "BIOS/BiosDisk.h" 9 | #include "part/MBRTable.h" 10 | 11 | #include 12 | 13 | static const char *msgResetDrv = "Drive reset failed!"; 14 | static const char *msgNoBootPart = "No boot partition found!"; 15 | static const char *msgFailLoad = "Failed to load VBR boot sector!"; 16 | static const char *msgNoMagic = "VBR boot sector has no magic number!"; 17 | 18 | static auto *bootSector = (uint16_t *)0x7C00; 19 | static auto *partTable = (MBRTable *)(0x0600 + 446); 20 | 21 | extern "C" { 22 | [[noreturn]] void CallVbr(BiosDisk disk, const MBREntry *ent); 23 | 24 | void main(BiosDisk disk); 25 | } 26 | 27 | void main(BiosDisk disk) 28 | { 29 | for (const auto &it : partTable->entries) { 30 | if (!it.IsBootable()) 31 | continue; 32 | 33 | if (!disk.Reset()) 34 | DumpMessageAndHang(msgResetDrv); 35 | 36 | if (!disk.LoadSectors(it.StartAddressCHS(), bootSector, 1)) 37 | DumpMessageAndHang(msgFailLoad); 38 | 39 | if (bootSector[255] != 0xAA55) 40 | DumpMessageAndHang(msgNoMagic); 41 | 42 | CallVbr(disk, &it); 43 | } 44 | 45 | DumpMessageAndHang(msgNoBootPart); 46 | } 47 | -------------------------------------------------------------------------------- /mbr/mbr.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("binary") 2 | OUTPUT_ARCH(i386) 3 | 4 | SECTIONS 5 | { 6 | . = 0x0600; 7 | 8 | .text : { 9 | KEEP(*(.entry)) 10 | *(.text) 11 | *(.text.*) 12 | *(.rodata) 13 | *(.rodata.*) 14 | *(.data) 15 | *(.data.*) 16 | *(.bss) 17 | *(.bss.*) 18 | FILL(0x00) 19 | . = 446; 20 | } 21 | 22 | /DISCARD/ : { *(*) } 23 | } 24 | -------------------------------------------------------------------------------- /mbr/meson.build: -------------------------------------------------------------------------------- 1 | mbr = executable( 2 | 'mbr', 3 | name_suffix: 'bin', 4 | sources: [ 5 | 'abi.S', 6 | 'mbr.cpp', 7 | ], 8 | link_args: [ 9 | '-Wl,-T' + join_paths(meson.current_source_dir(), 'mbr.ld'), 10 | '-nostdlib', 11 | ], 12 | link_depends: [ 13 | 'mbr.ld', 14 | ], 15 | cpp_args: realmode_cpp_args, 16 | install: false, 17 | implicit_include_directories: false, 18 | include_directories: incs, 19 | pie: false, 20 | ) 21 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('Hausboot', 'cpp', meson_version: '>= 0.54.2') 2 | 3 | compiler = meson.get_compiler('cpp') 4 | compiler_native = meson.get_compiler('cpp', native: true) 5 | 6 | generic_cpp_args = [ 7 | '-O2', 8 | '-Os', 9 | '-Wall', 10 | '-Wextra', 11 | ] 12 | 13 | bare_cpp_args = generic_cpp_args 14 | bare_cpp_args += [ 15 | '-Wno-main', 16 | '-ffreestanding', 17 | '-fno-exceptions', 18 | '-fno-rtti', 19 | '-momit-leaf-frame-pointer', 20 | '-fno-threadsafe-statics', 21 | ] 22 | 23 | realmode_cpp_args = bare_cpp_args 24 | realmode_cpp_args += [ 25 | '-m16', 26 | '-march=i386', 27 | ] 28 | 29 | pm32_cpp_args = bare_cpp_args 30 | pm32_cpp_args += [ 31 | '-m32', 32 | '-march=i386', 33 | ] 34 | 35 | incs = include_directories('include') 36 | 37 | subdir('lib/BIOS') 38 | subdir('lib/cxxabi') 39 | subdir('lib/pm86') 40 | subdir('mbr') 41 | subdir('vbr') 42 | subdir('stage2') 43 | subdir('kernel') 44 | subdir('tools') 45 | subdir('test') 46 | -------------------------------------------------------------------------------- /stage2/abi.S: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * abi.S 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | .code16 8 | .global main 9 | .section ".entry" 10 | .extern __start_stage2 11 | .extern __stop_stage2 12 | _start: 13 | xor %ax, %ax 14 | mov %ax, %ds 15 | mov %ax, %es 16 | mov %ax, %ss 17 | mov $__stop_stage2, %esp 18 | add $1024, %esp 19 | pushl %esp 20 | calll main 21 | 22 | 23 | .code32 24 | .section ".text" 25 | .globl MBTrampoline 26 | .type MBTrampoline, @function 27 | MBTrampoline: 28 | /* first pop the return address, then the kernel entry point */ 29 | popl %ecx 30 | popl %ecx 31 | 32 | /* get the info pointer */ 33 | popl %ebx 34 | 35 | movl $0x2BADB002, %eax 36 | jmp *%ecx 37 | .size MBTrampoline, .-MBTrampoline 38 | -------------------------------------------------------------------------------- /stage2/heap.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * heap.cpp 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #include "Memory.h" 8 | #include "types/FlagField.h" 9 | 10 | #include 11 | 12 | struct HeapEntry { 13 | enum class HeapFlags { 14 | Reserved = 0x01, 15 | }; 16 | 17 | HeapEntry() = delete; 18 | ~HeapEntry() = delete; 19 | HeapEntry(HeapEntry &&) = delete; 20 | HeapEntry(const HeapEntry &) = delete; 21 | HeapEntry &operator= (const HeapEntry &) = delete; 22 | HeapEntry &operator= (HeapEntry &&) = delete; 23 | 24 | bool IsFree() const { 25 | return !_flags.IsSet(HeapFlags::Reserved); 26 | } 27 | 28 | size_t Size() const { 29 | return ((char *)_next - (char *)this) - sizeof(*this); 30 | } 31 | 32 | void *Reserve(size_t count) { 33 | auto sz = Size(); 34 | 35 | if (!IsFree() || (count > sz)) 36 | return nullptr; 37 | 38 | if ((sz - count) >= (sizeof(*this) + 4)) { 39 | HeapEntry *ent = (HeapEntry *)((char *)DataPtr() + count); 40 | ent->Init(_next); 41 | _next = ent; 42 | } 43 | 44 | _flags.Set(HeapFlags::Reserved); 45 | return DataPtr(); 46 | } 47 | 48 | void Release() { 49 | _flags.Clear(HeapFlags::Reserved); 50 | } 51 | 52 | void Init(HeapEntry *next) { 53 | _next = next; 54 | _flags.Clear(); 55 | } 56 | 57 | HeapEntry *Next() { 58 | return _next; 59 | } 60 | 61 | bool TryConsolidate() { 62 | if (IsFree() && _next->IsFree()) { 63 | _next = _next->_next; 64 | return true; 65 | } 66 | return false; 67 | } 68 | private: 69 | void *DataPtr() { 70 | return (char *)this + sizeof(*this); 71 | } 72 | 73 | HeapEntry *_next; 74 | FlagField _flags; 75 | }; 76 | 77 | static_assert(sizeof(HeapEntry) == 8); 78 | 79 | static HeapEntry *heap; 80 | static HeapEntry *heapEnd; 81 | 82 | void HeapInit(void *basePtr, size_t maxSize) 83 | { 84 | if (maxSize % 4) 85 | maxSize -= maxSize % 4; 86 | 87 | heap = (HeapEntry *)basePtr; 88 | heapEnd = (HeapEntry *)((char *)heap + maxSize); 89 | 90 | heap->Init(heapEnd); 91 | } 92 | 93 | void *malloc(size_t count) 94 | { 95 | if (count % 4) 96 | count += 4 - (count % 4); 97 | 98 | HeapEntry *found = nullptr; 99 | 100 | for (auto *it = heap; it != heapEnd; it = it->Next()) { 101 | if (it->IsFree() && it->Size() >= count) { 102 | if (found == nullptr || it->Size() < found->Size()) 103 | found = it; 104 | } 105 | } 106 | 107 | return (found != nullptr) ? found->Reserve(count) : nullptr; 108 | } 109 | 110 | void free(void *ptr) 111 | { 112 | if (ptr == nullptr) 113 | return; 114 | 115 | auto *ent = (HeapEntry *)((char *)ptr - sizeof(HeapEntry)); 116 | 117 | ent->Release(); 118 | 119 | for (;;) { 120 | bool merged = false; 121 | 122 | for (ent = heap; ent->Next() != heapEnd; ent = ent->Next()) { 123 | if (ent->TryConsolidate()) { 124 | merged = true; 125 | break; 126 | } 127 | } 128 | 129 | if (!merged) 130 | break; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /stage2/meson.build: -------------------------------------------------------------------------------- 1 | stage2 = executable( 2 | 'stage2', 3 | name_suffix: 'bin', 4 | sources: [ 5 | 'abi.S', 6 | 'heap.cpp', 7 | 'stage2.cpp', 8 | ], 9 | link_args: [ 10 | '-Wl,-T' + join_paths(meson.current_source_dir(), 'stage2.ld'), 11 | '-nostdlib', 12 | ], 13 | link_depends: [ 14 | 'stage2.ld', 15 | ], 16 | link_with: [ 17 | libBIOS, 18 | libpm86, 19 | libcxxabi, 20 | ], 21 | cpp_args: realmode_cpp_args, 22 | install: false, 23 | implicit_include_directories: false, 24 | include_directories: incs, 25 | pie: false, 26 | ) 27 | -------------------------------------------------------------------------------- /stage2/stage2.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * stage2.cpp 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #include "BIOS/BIOSTextMode.h" 8 | #include "BIOS/MemoryMap.h" 9 | #include "BIOS/BIOSBlockDevice.h" 10 | #include "kernel/MultiBootHeader.h" 11 | #include "kernel/MultiBootInfo.h" 12 | #include "device/IBlockDevice.h" 13 | #include "device/TextScreen.h" 14 | #include "types/UniquePtr.h" 15 | #include "fs/FatDirentLong.h" 16 | #include "fs/FatDirent.h" 17 | #include "fs/FatSuper.h" 18 | #include "fs/FatName.h" 19 | #include "fs/FatFs.h" 20 | #include "Stage2Header.h" 21 | #include "StringUtil.h" 22 | #include "pm86.h" 23 | 24 | __attribute__ ((section(".header"))) 25 | uint8_t headerBlob[sizeof(Stage2Header)]; 26 | static auto *stage2header = (Stage2Header *)headerBlob; 27 | 28 | static const char *bootConfigName = "BOOT.CFG"; 29 | static constexpr size_t bootConfigMaxSize = 4096; 30 | static constexpr size_t multiBootMaxSearch = 8192; 31 | static constexpr size_t heapMaxSize = 8192; 32 | 33 | static TextScreen screen; 34 | static MemoryMap<32> mmap; 35 | static UniquePtr fs; 36 | 37 | static bool haveKernel = false; 38 | static void *kernelEntry = nullptr; 39 | 40 | template 41 | static TextScreen &operator<< (TextScreen &s, FatFs::FindResult type) 42 | { 43 | const char *str = "no such file or directory"; 44 | 45 | switch (type) { 46 | case FatFs::FindResult::Ok: str = "ok"; break; 47 | case FatFs::FindResult::NameInvalid: str = "name invalid"; break; 48 | case FatFs::FindResult::IOError: str = "I/O error"; break; 49 | case FatFs::FindResult::NotDir: 50 | str = "component is not a directory"; 51 | break; 52 | default: 53 | break; 54 | } 55 | 56 | s << str; 57 | return s; 58 | } 59 | 60 | template 61 | static TextScreen &operator<< (TextScreen &s, CHSPacked chs) 62 | { 63 | s << chs.Cylinder() << "/" << chs.Head() << "/" << chs.Sector(); 64 | return s; 65 | } 66 | 67 | template 68 | static TextScreen &operator<< (TextScreen &s, 69 | const BiosDisk::DriveGeometry &geom) 70 | { 71 | s << geom.cylinders << "/" << geom.headsPerCylinder << "/" 72 | << geom.sectorsPerTrack; 73 | return s; 74 | } 75 | 76 | /*****************************************************************************/ 77 | 78 | static bool MBFindHeader(const FatFile &finfo, uint32_t &offset, 79 | MultiBootHeader &hdr) 80 | { 81 | auto scanSize = finfo.size > multiBootMaxSearch ? 82 | multiBootMaxSearch : finfo.size; 83 | 84 | auto *buffer = (uint32_t *)malloc(1024); 85 | if (buffer == nullptr) { 86 | screen << "out of memory" << "\r\n"; 87 | return false; 88 | } 89 | 90 | for (offset = 0; offset < scanSize; ) { 91 | auto ret = fs->ReadAt(finfo, (uint8_t *)buffer, offset, 1024); 92 | if (ret <= 0) 93 | goto fail; 94 | 95 | for (decltype(ret) i = 0; i < (ret / 4); ++i) { 96 | if (((uint32_t *)buffer)[i] != MultiBootHeader::Magic) 97 | continue; 98 | 99 | ret = fs->ReadAt(finfo, (uint8_t *)&hdr, 100 | offset + i * 4, sizeof(hdr)); 101 | if (ret <= 0) 102 | goto fail; 103 | 104 | if (hdr.IsValid()) { 105 | offset += i * 4; 106 | free(buffer); 107 | return true; 108 | } 109 | } 110 | 111 | offset += ret; 112 | } 113 | fail: 114 | free(buffer); 115 | return false; 116 | } 117 | 118 | static bool MBLoadKernel(const FatFile &finfo, const MultiBootHeader &hdr, 119 | uint32_t fileOffset) 120 | { 121 | uint32_t fileStart, memStart, count; 122 | 123 | if (!hdr.ExtractMemLayout(fileOffset, finfo.size, fileStart, 124 | memStart, count)) { 125 | screen << "Error: " << "Memory layout is broken!" << "\r\n"; 126 | return false; 127 | } 128 | 129 | // TODO: check if target actually is in high-mem 130 | // TODO: check if we have enough memory available there 131 | 132 | // load it into memory 133 | screen << "Loading " << count << " bytes to #"; 134 | screen.WriteHex(memStart); 135 | screen << "\r\n"; 136 | 137 | uint8_t *buffer = (uint8_t *)malloc(1024); 138 | if (buffer == nullptr) { 139 | screen << "out of memory" << "\r\n"; 140 | return false; 141 | } 142 | 143 | for (auto offset = fileStart; offset < (count - fileStart); ) { 144 | auto ret = fs->ReadAt(finfo, buffer, offset, 1024); 145 | 146 | if (ret == 0) 147 | break; 148 | 149 | if (ret < 0) 150 | return false; 151 | 152 | ProtectedModeCall(CopyMemory32, (void *)memStart, buffer, ret); 153 | 154 | memStart += ret; 155 | offset += ret; 156 | } 157 | 158 | free(buffer); 159 | 160 | ProtectedModeCall(ClearMemory32, (void *)memStart, hdr.BSSSize()); 161 | return true; 162 | } 163 | 164 | static MultiBootInfo *MBGenInfo() 165 | { 166 | auto *info = new MultiBootInfo(); 167 | 168 | info->SetLoadName("hausboot"); 169 | 170 | size_t count = mmap.Count(); 171 | auto *mbMmap = new MultiBootMmap[count]; 172 | 173 | for (size_t i = 0; i < count; ++i) { 174 | mbMmap[i] = mmap.At(i); 175 | mbMmap[i].SetNext(mbMmap + i + 1); 176 | } 177 | 178 | info->SetMemoryMap(mbMmap, count); 179 | return info; 180 | } 181 | 182 | extern "C" { 183 | void MBTrampoline(void *address, const MultiBootInfo *info); 184 | } 185 | 186 | /*****************************************************************************/ 187 | 188 | static bool CmdEcho(const char *line) 189 | { 190 | screen << line << "\r\n"; 191 | return true; 192 | } 193 | 194 | static bool CmdInfo(const char *what) 195 | { 196 | if (StrEqual(what, "disk")) { 197 | auto geom = ((const BIOSBlockDevice &)fs->BlockDevice()).DriveGeometry(); 198 | auto lba = stage2header->BootMBREntry().StartAddressLBA(); 199 | auto chs = geom.LBA2CHS(lba); 200 | 201 | screen << "Boot disk: " << "\r\n" 202 | << " geometry (C/H/S): " << geom << "\r\n" 203 | << "Boot partition: " << "\r\n" 204 | << " LBA: " << lba << "\r\n" 205 | << " CHS: " << chs << "\r\n"; 206 | 207 | return true; 208 | } 209 | 210 | if (StrEqual(what, "memory")) { 211 | screen << "Memory:" << "\r\n"; 212 | 213 | for (const auto &it : mmap) { 214 | screen << " base: "; 215 | screen.WriteHex(it.BaseAddress()); 216 | screen << ", size: "; 217 | screen.WriteHex(it.Size()); 218 | screen << ", type: " << it.TypeAsString() << "\r\n"; 219 | } 220 | 221 | return true; 222 | } 223 | 224 | screen << "Unknown info type: " << what << "\r\n"; 225 | return false; 226 | } 227 | 228 | static bool CmdMultiboot(const char *path) 229 | { 230 | FatFile finfo; 231 | 232 | auto ret = fs->FindByPath(path, finfo); 233 | if (ret != FatFs::FindResult::Ok) { 234 | screen << path << ": " << ret << "\r\n"; 235 | return false; 236 | } 237 | 238 | uint32_t offset; 239 | MultiBootHeader hdr; 240 | 241 | if (!MBFindHeader(finfo, offset, hdr)) { 242 | screen << "Error: " << "No multiboot header found!" << "\r\n"; 243 | return false; 244 | } 245 | 246 | if (hdr.Flags().IsSet(MultiBootHeader::KernelFlags::WantVidmode)) { 247 | screen << "Error: " 248 | << "video mode info required (unsupported)!" << "\r\n"; 249 | return false; 250 | } 251 | 252 | if (!hdr.Flags().IsSet(MultiBootHeader::KernelFlags::HaveLayoutInfo)) { 253 | screen << "Error: " 254 | << "No memory layout provided (unsupported)!" << "\r\n"; 255 | return false; 256 | } 257 | 258 | if (!MBLoadKernel(finfo, hdr, offset)) 259 | return false; 260 | 261 | haveKernel = true; 262 | kernelEntry = hdr.EntryPoint(); 263 | return true; 264 | } 265 | 266 | static const struct { 267 | const char *name; 268 | bool (*callback)(const char *arg); 269 | } commands[] = { 270 | { "echo", CmdEcho }, 271 | { "info", CmdInfo }, 272 | { "multiboot", CmdMultiboot }, 273 | }; 274 | 275 | static void RunScript(char *ptr) 276 | { 277 | while (*ptr != '\0') { 278 | // isolate the current line 279 | char *line = ptr; 280 | 281 | while (*ptr != '\0' && *ptr != '\n') 282 | ++ptr; 283 | 284 | if (*ptr == '\n') 285 | *(ptr++) = '\0'; 286 | 287 | // isolate command string 288 | while (*line == ' ' || *line == '\t') 289 | ++line; 290 | 291 | if (!IsAlnum(*line)) 292 | continue; 293 | 294 | const char *cmd = line; 295 | 296 | while (IsAlnum(*line)) 297 | ++line; 298 | 299 | if (!IsSpace(*line) || *cmd == '\0' || *cmd == '#') 300 | continue; 301 | 302 | *(line++) = '\0'; 303 | 304 | while (IsSpace(*line)) 305 | ++line; 306 | 307 | const char *arg = line; 308 | 309 | // dispatch 310 | bool found = false; 311 | 312 | for (const auto &it : commands) { 313 | if (StrEqual(it.name, cmd)) { 314 | if (!it.callback(arg)) 315 | return; 316 | found = true; 317 | break; 318 | } 319 | } 320 | 321 | if (!found) { 322 | screen << "Error, unknown command: " << cmd << "\r\n"; 323 | return; 324 | } 325 | } 326 | } 327 | 328 | /*****************************************************************************/ 329 | 330 | extern "C" { 331 | void main(void *heapPtr); 332 | } 333 | 334 | void main(void *heapPtr) 335 | { 336 | FatFs::FindResult ret; 337 | char *fileBuffer; 338 | FatFile finfo; 339 | int32_t rdRet; 340 | 341 | // initialization 342 | HeapInit(heapPtr, heapMaxSize); 343 | 344 | screen.Reset(); 345 | 346 | auto part = MakeUnique(stage2header->BiosBootDrive(), 347 | stage2header->BootMBREntry().StartAddressLBA()); 348 | 349 | if (part == nullptr || !part->IsInitialized()) { 350 | screen << "Error initializing FAT partition wrapper!" << "\r\n"; 351 | goto fail; 352 | } 353 | 354 | if (!EnableA20()) { 355 | screen << "Error enabling A20 line!" << "\r\n"; 356 | goto fail; 357 | } 358 | 359 | if (!mmap.Load()) { 360 | screen << "Error loading BIOS memory map!" << "\r\n"; 361 | goto fail; 362 | } 363 | 364 | fs = MakeUnique(std::move(part), *((FatSuper *)0x7C00)); 365 | if (fs == nullptr) { 366 | screen << "Error initializing FAT FS wrapper!" << "\r\n"; 367 | goto fail; 368 | } 369 | 370 | // find the boot loader config file 371 | ret = fs->FindByPath(bootConfigName, finfo); 372 | if (ret != FatFs::FindResult::Ok) { 373 | screen << bootConfigName << ": " << ret << "\r\n"; 374 | goto fail; 375 | } 376 | 377 | if (finfo.size > bootConfigMaxSize) { 378 | screen << bootConfigName << ": too big (max size: " 379 | << bootConfigMaxSize << ")" << "\r\n"; 380 | goto fail; 381 | } 382 | 383 | // load it into memory 384 | fileBuffer = (char *)malloc(finfo.size + 1); 385 | 386 | rdRet = fs->ReadAt(finfo, (uint8_t *)fileBuffer, 0, finfo.size); 387 | if (rdRet < 0) { 388 | screen << "Error loading config file " << "\r\n"; 389 | goto fail; 390 | } 391 | 392 | fileBuffer[rdRet] = '\0'; 393 | 394 | // interpret it 395 | RunScript(fileBuffer); 396 | free(fileBuffer); 397 | 398 | // run the kernel 399 | if (haveKernel) { 400 | auto *info = MBGenInfo(); 401 | 402 | ProtectedModeCall(MBTrampoline, kernelEntry, info); 403 | } else { 404 | screen << "No kernel loaded!" << "\r\n"; 405 | goto fail; 406 | } 407 | fail: 408 | for (;;) { 409 | __asm__ volatile("hlt"); 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /stage2/stage2.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("binary") 2 | OUTPUT_ARCH(i386) 3 | 4 | SECTIONS 5 | { 6 | . = 0x1000; 7 | 8 | .text : { 9 | __start_stage2 = .; 10 | KEEP(*(.header)) 11 | KEEP(*(.entry)) 12 | *(.text) 13 | *(.text.*) 14 | *(.rodata) 15 | *(.rodata.*) 16 | *(.data) 17 | *(.data.*) 18 | *(.bss) 19 | *(.bss.*) 20 | FILL(0) 21 | . = ALIGN(512); 22 | __stop_stage2 = .; 23 | } 24 | 25 | /DISCARD/ : { *(*) } 26 | } 27 | -------------------------------------------------------------------------------- /test/bochsrc.txt: -------------------------------------------------------------------------------- 1 | # configuration file generated by Bochs 2 | plugin_ctrl: unmapped=true, biosdev=true, speaker=true, extfpuirq=true, parallel=true, serial=true, e1000=false, ne2k=false 3 | config_interface: textconfig 4 | display_library: x 5 | memory: host=32, guest=32 6 | romimage: file="/usr/share/bochs/BIOS-bochs-latest", address=0x00000000, options=none 7 | vgaromimage: file="/usr/share/bochs/VGABIOS-lgpl-latest" 8 | boot: disk 9 | floppy_bootsig_check: disabled=0 10 | floppya: type=1_44 11 | # no floppyb 12 | ata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 13 | ata0-master: type=disk, path="@diskimg@", mode=flat, cylinders=800, heads=4, spt=32, sect_size=512, model="Generic 1234", biosdetect=auto 14 | ata0-slave: type=none 15 | ata1: enabled=true, ioaddr1=0x170, ioaddr2=0x370, irq=15 16 | ata1-master: type=none 17 | ata1-slave: type=none 18 | ata2: enabled=false 19 | ata3: enabled=false 20 | optromimage1: file=none 21 | optromimage2: file=none 22 | optromimage3: file=none 23 | optromimage4: file=none 24 | optramimage1: file=none 25 | optramimage2: file=none 26 | optramimage3: file=none 27 | optramimage4: file=none 28 | pci: enabled=1, chipset=i440fx, slot1=none, slot2=none, slot3=none, slot4=none, slot5=none 29 | vga: extension=vbe, update_freq=5, realtime=1, ddc=builtin 30 | cpu: count=1:1:1, ips=4000000, quantum=16, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 31 | cpuid: level=6, stepping=3, model=3, family=6, vendor_string="AuthenticAMD", brand_string="AMD Athlon(tm) processor" 32 | cpuid: mmx=true, apic=xapic, simd=sse2, sse4a=false, misaligned_sse=false, sep=true 33 | cpuid: movbe=false, adx=false, aes=false, sha=false, xsave=false, xsaveopt=false, x86_64=true 34 | cpuid: 1g_pages=false, pcid=false, fsgsbase=false, smep=false, smap=false, mwait=true 35 | print_timestamps: enabled=0 36 | port_e9_hack: enabled=0 37 | private_colormap: enabled=0 38 | clock: sync=none, time0=local, rtc_sync=0 39 | # no cmosimage 40 | log: - 41 | logprefix: %t%e%d 42 | debug: action=ignore 43 | info: action=report 44 | error: action=report 45 | panic: action=ask 46 | keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none 47 | mouse: type=ps2, enabled=false, toggle=ctrl+mbutton 48 | speaker: enabled=true, mode=system 49 | parport1: enabled=true, file=none 50 | parport2: enabled=false 51 | com1: enabled=true, mode=null 52 | com2: enabled=false 53 | com3: enabled=false 54 | com4: enabled=false 55 | -------------------------------------------------------------------------------- /test/boot.cfg: -------------------------------------------------------------------------------- 1 | echo ********** Second Stage FAT32 boot loader ********** 2 | info disk 3 | info memory 4 | echo **************************************************** 5 | 6 | multiboot BOOT/KRNL386.SYS 7 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | mkfatimg_path = join_paths(meson.current_source_dir(), 'mkfatimage.sh') 2 | mkdiskimg_path = join_paths(meson.current_source_dir(), 'mkdiskimage.sh') 3 | bootcfg_path = join_paths(meson.current_source_dir(), 'boot.cfg') 4 | 5 | fatpart = custom_target( 6 | 'fatpart', 7 | depends: [ 8 | vbr, 9 | stage2, 10 | kernel, 11 | fatedit, 12 | installfat 13 | ], 14 | depend_files: [ 15 | mkfatimg_path, 16 | bootcfg_path, 17 | ], 18 | output: 'fatpart.img', 19 | command: [ 20 | mkfatimg_path, 21 | vbr, 22 | stage2, 23 | kernel, 24 | bootcfg_path, 25 | fatedit, 26 | installfat, 27 | '@OUTPUT0@' 28 | ], 29 | install: false, 30 | build_by_default: true, 31 | ) 32 | 33 | diskimg = custom_target( 34 | 'diskimage', 35 | depends: [ 36 | mbr, 37 | fatpart, 38 | ], 39 | depend_files: [ 40 | mkdiskimg_path, 41 | ], 42 | output: 'disk.img', 43 | command: [ 44 | mkdiskimg_path, 45 | fatpart, 46 | mbr, 47 | '@OUTPUT0@' 48 | ], 49 | install: false, 50 | build_by_default: true, 51 | ) 52 | 53 | conf_data = configuration_data() 54 | conf_data.set('diskimg', diskimg.full_path()) 55 | 56 | configure_file( 57 | input: join_paths(meson.current_source_dir(), 'bochsrc.txt'), 58 | output: 'bochsrc.txt', 59 | configuration: conf_data 60 | ) 61 | -------------------------------------------------------------------------------- /test/mkdiskimage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | FATPART="$1" 4 | MBRFILE="$2" 5 | OUTFILE="$3" 6 | 7 | dd if=/dev/zero of="$OUTFILE" bs=1M count=50 8 | 9 | parted --script "$OUTFILE" \ 10 | "mklabel msdos" \ 11 | "mkpart primary fat32 1M 40M" \ 12 | "set 1 boot on" 13 | 14 | dd if="$MBRFILE" of="$OUTFILE" conv=notrunc bs=1 count=446 15 | dd if="$FATPART" of="$OUTFILE" conv=notrunc bs=512 seek=2048 16 | 17 | -------------------------------------------------------------------------------- /test/mkfatimage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | VBRFILE="$1" 4 | STAGE2FILE="$2" 5 | KERNELFILE="$3" 6 | CFGFILE="$4" 7 | FATEDIT="$5" 8 | INSTALLFAT="$6" 9 | IMGFILE="$7" 10 | 11 | dd if=/dev/zero of="$IMGFILE" bs=1M count=40 12 | mkfs.fat -F 32 "$IMGFILE" 13 | 14 | "$INSTALLFAT" -v "$VBRFILE" -o "$IMGFILE" --stage2 "$STAGE2FILE" 15 | 16 | echo "mkdir BOOT" | "$FATEDIT" "$IMGFILE" 17 | echo "pack $KERNELFILE BOOT/KRNL386.SYS" | "$FATEDIT" "$IMGFILE" 18 | echo "pack $CFGFILE BOOT.CFG" | "$FATEDIT" "$IMGFILE" 19 | -------------------------------------------------------------------------------- /tools/fatedit.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * fatedit.cpp 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #include "fs/FatSuper.h" 8 | #include "fs/FatFsInfo.h" 9 | #include "fs/FatDirent.h" 10 | #include "fs/FatDirentLong.h" 11 | #include "fs/FatName.h" 12 | #include "host/File.h" 13 | #include "util.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | struct DirEntry { 23 | std::string shortName; 24 | std::string longName; 25 | uint32_t size; 26 | uint32_t firstCluster; 27 | bool isDirectory; 28 | }; 29 | 30 | static FatSuper super; 31 | static File file; 32 | 33 | class FatReader { 34 | public: 35 | uint32_t NextClusterInFile(uint32_t N) const { 36 | auto idx = N * 4; 37 | 38 | // XXX: std::vector::at() does bounds checking, this 39 | // should throw an exception if N is bonkers. 40 | uint32_t out = _fatRaw.at(idx++); 41 | out |= static_cast(_fatRaw.at(idx++)) << 8; 42 | out |= static_cast(_fatRaw.at(idx++)) << 16; 43 | out |= static_cast(_fatRaw.at(idx++)) << 24; 44 | 45 | return out & 0x0FFFFFFF; 46 | } 47 | 48 | void LoadFromImage() { 49 | if (super.FsInfoIndex() >= super.ReservedSectors()) { 50 | std::ostringstream ss; 51 | ss << "FS Info index is " << super.ReservedSectors() 52 | << ", which is past reserved sector count (" 53 | << super.ReservedSectors() << ")"; 54 | throw std::runtime_error(ss.str()); 55 | } 56 | 57 | file.ReadAt(super.FsInfoIndex() * super.BytesPerSector(), 58 | &_fsinfo, sizeof(_fsinfo)); 59 | 60 | if (!_fsinfo.IsValid()) 61 | throw std::runtime_error("FS info sector is broken!"); 62 | 63 | auto offset = super.ReservedSectors() * super.BytesPerSector(); 64 | auto size = super.SectorsPerFat() * super.BytesPerSector(); 65 | 66 | _fatRaw.resize(size); 67 | 68 | file.ReadAt(offset, _fatRaw.data(), _fatRaw.size()); 69 | } 70 | 71 | void WriteToImage() { 72 | auto offset = super.ReservedSectors() * super.BytesPerSector(); 73 | 74 | for (size_t i = 0; i < super.NumFats(); ++i) { 75 | file.WriteAt(offset, _fatRaw.data(), _fatRaw.size()); 76 | 77 | offset += _fatRaw.size(); 78 | } 79 | 80 | _fsinfo.SetNextFreeCluster(FindFreeCluster()); 81 | _fsinfo.SetNumFreeCluster(NumFreeClusters()); 82 | 83 | file.WriteAt(super.FsInfoIndex() * super.BytesPerSector(), 84 | &_fsinfo, sizeof(_fsinfo)); 85 | } 86 | 87 | uint32_t AllocateCluster() { 88 | auto out = FindFreeCluster(); 89 | if (out >= 0x0FFFFFF8) 90 | throw std::runtime_error("No free cluster available"); 91 | Set(out, 0x0FFFFFF8); 92 | return out; 93 | } 94 | 95 | uint32_t AllocateCluster(uint32_t lastInFile) { 96 | auto out = AllocateCluster(); 97 | Set(lastInFile, out); 98 | return out; 99 | } 100 | 101 | uint64_t ClusterFileOffset(uint32_t index) const { 102 | return super.ClusterIndex2Sector(index) * 103 | super.BytesPerSector(); 104 | } 105 | 106 | size_t BytesPerCluster() const { 107 | return super.SectorsPerCluster() * super.BytesPerSector(); 108 | } 109 | private: 110 | void Set(size_t N, uint32_t value) { 111 | auto idx = N * 4; 112 | 113 | if (_fatRaw.size() < 4 || idx >= (_fatRaw.size() - 3)) 114 | return; 115 | 116 | _fatRaw[idx++] = value & 0xFF; 117 | _fatRaw[idx++] = (value >> 8) & 0xFF; 118 | _fatRaw[idx++] = (value >> 16) & 0xFF; 119 | _fatRaw[idx++] = (value >> 24) & 0x0F; 120 | } 121 | 122 | uint32_t NumFreeClusters() const { 123 | uint32_t count = 0; 124 | 125 | for (size_t i = 0; i < (_fatRaw.size() / 4); ++i) { 126 | if (NextClusterInFile(i) == 0) 127 | ++count; 128 | } 129 | 130 | return count; 131 | } 132 | 133 | uint32_t FindFreeCluster() { 134 | auto fatSectors = super.SectorsPerFat() * super.NumFats(); 135 | auto total = super.SectorCount(); 136 | 137 | if (total <= fatSectors) 138 | return 0x0FFFFFFF8; 139 | total -= fatSectors; 140 | 141 | if (total <= super.ReservedSectors()) 142 | return 0x0FFFFFFF8; 143 | total -= super.ReservedSectors(); 144 | 145 | total /= super.SectorsPerCluster(); 146 | if (total < 1) 147 | return 0x0FFFFFFF8; 148 | 149 | for (size_t i = 0; i < total; ++i) { 150 | if (NextClusterInFile(i) == 0) 151 | return i; 152 | } 153 | 154 | return 0x0FFFFFFF8; 155 | } 156 | 157 | std::vector _fatRaw; 158 | FatFsInfo _fsinfo; 159 | }; 160 | 161 | static FatReader fat; 162 | 163 | class FileReader { 164 | public: 165 | FileReader(uint32_t idx, uint32_t size) : _cluster(idx), _offset(0), 166 | _size(size), _totalRead(0) { 167 | } 168 | 169 | ssize_t Read(void *data, size_t size) { 170 | ssize_t total = 0; 171 | 172 | while (size > 0) { 173 | auto diff = DataLeftInCluster(); 174 | 175 | if (diff == 0) { 176 | NextCluster(); 177 | 178 | diff = DataLeftInCluster(); 179 | if (diff == 0) 180 | break; 181 | } 182 | 183 | uint64_t offset = fat.ClusterFileOffset(_cluster) + _offset; 184 | 185 | if (diff > size) 186 | diff = size; 187 | 188 | file.ReadAt(offset, data, diff); 189 | 190 | _offset += diff; 191 | _size -= diff; 192 | 193 | data = (char *)data + diff; 194 | total += diff; 195 | size -= diff; 196 | } 197 | 198 | _totalRead += total; 199 | return total; 200 | } 201 | 202 | size_t TotalRead() const { 203 | return _totalRead; 204 | } 205 | private: 206 | size_t DataLeftInCluster() { 207 | size_t max = fat.BytesPerCluster(); 208 | 209 | size_t avail = _offset < max ? (max - _offset) : 0; 210 | 211 | return avail < _size ? avail : _size; 212 | } 213 | 214 | void NextCluster() { 215 | _offset = 0; 216 | 217 | if (_size == 0) { 218 | _cluster = 0; 219 | return; 220 | } 221 | 222 | auto ent = fat.NextClusterInFile(_cluster); 223 | 224 | if (ent < 2 || ent >= 0x0FFFFFF8) { 225 | _size = 0; 226 | _cluster = 0; 227 | } else { 228 | _cluster = ent; 229 | } 230 | } 231 | 232 | uint32_t _cluster; 233 | uint32_t _offset; 234 | uint32_t _size; 235 | size_t _totalRead; 236 | }; 237 | 238 | class FileWriter { 239 | public: 240 | FileWriter() { 241 | _cluster = fat.AllocateCluster(); 242 | _firstCluster = _cluster; 243 | _offset = 0; 244 | _totalSize = 0; 245 | } 246 | 247 | FileWriter(uint32_t idx, uint32_t size) : _cluster(idx), _offset(size), 248 | _firstCluster(idx), _totalSize(size) { 249 | auto clusterSize = fat.BytesPerCluster(); 250 | 251 | while (size > clusterSize) { 252 | auto ent = fat.NextClusterInFile(_cluster); 253 | 254 | if (ent < 2 || ent >= 0x0FFFFFF8) 255 | throw std::runtime_error("Error following cluster chain"); 256 | 257 | _cluster = ent; 258 | size -= clusterSize; 259 | } 260 | 261 | auto ent = fat.NextClusterInFile(_cluster); 262 | if (ent < 0x0FFFFFF8) 263 | throw std::runtime_error("Cluster chain not properly termminated"); 264 | 265 | _offset = size; 266 | } 267 | 268 | void Append(const void *data, size_t size) { 269 | while (size > 0) { 270 | auto diff = SpaceAvailable(); 271 | if (diff > size) 272 | diff = size; 273 | 274 | if (diff == 0) { 275 | _cluster = fat.AllocateCluster(_cluster); 276 | _offset = 0; 277 | continue; 278 | } 279 | 280 | uint64_t offset = fat.ClusterFileOffset(_cluster) + _offset; 281 | 282 | file.WriteAt(offset, data, diff); 283 | 284 | _offset += diff; 285 | _totalSize += diff; 286 | data = (const char *)data + diff; 287 | size -= diff; 288 | } 289 | } 290 | 291 | auto FirstCluster() const { 292 | return _firstCluster; 293 | } 294 | 295 | auto BytesWritten() const { 296 | return _totalSize; 297 | } 298 | private: 299 | size_t SpaceAvailable() const { 300 | return fat.BytesPerCluster() - _offset; 301 | } 302 | 303 | uint32_t _cluster; 304 | uint32_t _offset; 305 | 306 | uint32_t _firstCluster; 307 | uint32_t _totalSize; 308 | }; 309 | 310 | static bool ScanDirectory(FileReader &rd, std::vector &out, 311 | size_t &totalRead) 312 | { 313 | size_t overread = 0; 314 | 315 | out.clear(); 316 | 317 | for (;;) { 318 | std::vector lEntList; 319 | FatDirent sEnt; 320 | 321 | auto ret = rd.Read(&sEnt, sizeof(sEnt)); 322 | if (ret < 0) 323 | return false; 324 | if (ret < sizeof(sEnt)) { 325 | overread = ret; 326 | break; 327 | } 328 | 329 | if (sEnt.IsDummiedOut()) 330 | continue; 331 | 332 | if (sEnt.IsLastInList()) { 333 | overread = ret; 334 | break; 335 | } 336 | 337 | if (sEnt.EntryFlags().IsSet(FatDirent::Flags::LongFileName)) { 338 | // re-encode the entry, get count and checksum 339 | FatDirentLong lEnt; 340 | memcpy(&lEnt, &sEnt, sizeof(sEnt)); 341 | 342 | auto count = lEnt.SequenceNumber(); 343 | auto chksum = lEnt.ShortNameChecksum(); 344 | 345 | if (!lEnt.IsLast() || count < 1) { 346 | std::cerr << "Long name list needs to begin with last entry!" << std::endl; 347 | return false; 348 | } 349 | 350 | lEntList.push_back(lEnt); 351 | 352 | // read all subsequence entries belonging to this one 353 | for (decltype(count) i = 1; i < count; ++i) { 354 | ret = rd.Read(&lEnt, sizeof(lEnt)); 355 | if (ret < 0) 356 | return false; 357 | 358 | if (ret < sizeof(lEnt)) { 359 | std::cerr << "Not enouth space for long name list!" << std::endl; 360 | return false; 361 | } 362 | 363 | if (!lEnt.EntryFlags().IsSet(FatDirent::Flags::LongFileName) || 364 | lEnt.IsLast()) { 365 | std::cerr << "Broken long name entry!" << std::endl; 366 | return false; 367 | } 368 | 369 | lEntList.push_back(lEnt); 370 | } 371 | 372 | // Sanitize the list 373 | std::reverse(lEntList.begin(), lEntList.end()); 374 | 375 | for (size_t i = 0; i < lEntList.size(); ++i) { 376 | if (lEntList.at(i).SequenceNumber() != (i + 1) || 377 | lEntList.at(i).ShortNameChecksum() != chksum) { 378 | std::cerr << "Broken long filename sequence!" << std::endl; 379 | return false; 380 | } 381 | } 382 | 383 | // Get the corresponding short name entry 384 | ret = rd.Read(&sEnt, sizeof(sEnt)); 385 | if (ret < 0) 386 | return false; 387 | if (ret < sizeof(sEnt)) { 388 | std::cerr << "Long name entry not followed by short name!" << std::endl; 389 | return false; 390 | } 391 | 392 | if (sEnt.EntryFlags().IsSet(FatDirent::Flags::LongFileName)) { 393 | std::cerr << "Short name entry is broken!" << std::endl; 394 | return false; 395 | } 396 | 397 | if (sEnt.Checksum() != chksum) { 398 | std::cerr << "Short name checksum does not match!" << std::endl; 399 | return false; 400 | } 401 | } 402 | 403 | char shortName[12]; 404 | if (!sEnt.NameToString(shortName)) { 405 | std::cerr << "File with empty short name found!" << std::endl; 406 | return false; 407 | } 408 | 409 | DirEntry ent; 410 | ent.size = sEnt.Size(); 411 | ent.firstCluster = sEnt.ClusterIndex(); 412 | ent.shortName = shortName; 413 | ent.isDirectory = sEnt.EntryFlags().IsSet(FatDirent::Flags::Directory); 414 | 415 | if (lEntList.empty()) 416 | ent.longName = shortName; 417 | 418 | for (const auto &it : lEntList) { 419 | char buffer[14]; 420 | 421 | it.NameToString(buffer); 422 | 423 | if (strlen(buffer) == 0) { 424 | std::cerr << "File with empty long name found!" << std::endl; 425 | return false; 426 | } 427 | 428 | ent.longName += buffer; 429 | } 430 | 431 | out.push_back(ent); 432 | } 433 | 434 | totalRead = rd.TotalRead() - overread; 435 | return true; 436 | } 437 | 438 | static bool FindFile(std::string args, DirEntry &out) 439 | { 440 | DirEntry ent; 441 | 442 | ent.size = 0; 443 | ent.firstCluster = super.RootDirIndex(); 444 | ent.isDirectory = true; 445 | 446 | auto path = SplitPath(args); 447 | 448 | while (!path.empty()) { 449 | if (!ent.isDirectory) { 450 | std::cerr << args << ": component is not a directory" << std::endl; 451 | return false; 452 | } 453 | 454 | FileReader rd(ent.firstCluster, 0xFFFFFFFF); 455 | std::vector list; 456 | size_t total; 457 | 458 | if (!ScanDirectory(rd, list, total)) 459 | return false; 460 | 461 | auto name = path.front(); 462 | path.pop_front(); 463 | 464 | bool found = false; 465 | 466 | for (const auto &it : list) { 467 | if (it.longName == name || it.shortName == name) { 468 | found = true; 469 | ent = it; 470 | break; 471 | } 472 | } 473 | 474 | if (!found) { 475 | std::cerr << name << ": no such file or directory" << std::endl; 476 | return false; 477 | } 478 | } 479 | 480 | out = ent; 481 | return true; 482 | } 483 | 484 | static bool MatchCommand(std::string &str, const char *cmd) 485 | { 486 | auto len = strlen(cmd); 487 | 488 | if (str.size() < len || strncmp(str.c_str(), cmd, len) != 0) 489 | return false; 490 | 491 | if (str.size() > len && !isspace(str.at(len))) 492 | return false; 493 | 494 | while (len < str.size() && isspace(str.at(len))) 495 | ++len; 496 | 497 | str.erase(0, len); 498 | return true; 499 | } 500 | 501 | static void ListDirectory(std::string args) 502 | { 503 | DirEntry ent; 504 | 505 | if (!FindFile(args, ent)) 506 | return; 507 | 508 | if (!ent.isDirectory) { 509 | std::cerr << args << ": not a directory" << std::endl; 510 | return; 511 | } 512 | 513 | FileReader rd(ent.firstCluster, 0xFFFFFFFF); 514 | std::vector list; 515 | size_t total; 516 | 517 | if (!ScanDirectory(rd, list, total)) 518 | return; 519 | 520 | for (const auto &it : list) { 521 | std::cout << it.longName << " (" << it.shortName << "), " 522 | << it.size; 523 | 524 | if (it.isDirectory) 525 | std::cout << ", directory"; 526 | 527 | std::cout << std::endl; 528 | } 529 | } 530 | 531 | static void DumpFile(std::string args) 532 | { 533 | DirEntry ent; 534 | 535 | if (!FindFile(args, ent)) 536 | return; 537 | 538 | if (ent.isDirectory) { 539 | std::cerr << args << ": is a directory" << std::endl; 540 | return; 541 | } 542 | 543 | FileReader rd(ent.firstCluster, ent.size); 544 | 545 | for (;;) { 546 | char buffer[512]; 547 | 548 | auto ret = rd.Read(buffer, sizeof(buffer)); 549 | if (ret <= 0) 550 | return; 551 | 552 | std::cout.write(buffer, ret); 553 | } 554 | } 555 | 556 | static void InitializeEmptyDirectory(uint32_t parentIdx, uint32_t &indexOut) 557 | { 558 | FlagField flags; 559 | flags.Set(FatDirent::Flags::Directory); 560 | 561 | FileWriter data; 562 | 563 | FatDirent dot; 564 | dot.SetName("."); 565 | dot.SetEntryFlags(flags); 566 | dot.SetClusterIndex(data.FirstCluster()); 567 | 568 | FatDirent dotdot; 569 | dotdot.SetName(".."); 570 | dotdot.SetEntryFlags(flags); 571 | dotdot.SetClusterIndex(parentIdx); 572 | 573 | FatDirent zero; 574 | memset(&zero, 0, sizeof(zero)); 575 | 576 | indexOut = data.FirstCluster(); 577 | 578 | data.Append(&dot, sizeof(dot)); 579 | data.Append(&dotdot, sizeof(dotdot)); 580 | data.Append(&zero, sizeof(zero)); 581 | } 582 | 583 | static bool FindParent(const std::list &path, DirEntry &parent) 584 | { 585 | if (path.size() > 1) { 586 | std::ostringstream ss; 587 | size_t idx = 0; 588 | 589 | for (const auto &it : path) { 590 | if (idx < path.size() - 1) { 591 | if (idx > 0) 592 | ss << '/'; 593 | ss << it; 594 | ++idx; 595 | } 596 | } 597 | 598 | auto prefix = ss.str(); 599 | 600 | if (!FindFile(prefix, parent)) 601 | return false; 602 | 603 | if (!parent.isDirectory) { 604 | std::cerr << prefix << ": not a directory" << std::endl; 605 | return false; 606 | } 607 | } else { 608 | parent.size = 0; 609 | parent.firstCluster = super.RootDirIndex(); 610 | parent.isDirectory = true; 611 | } 612 | 613 | return true; 614 | } 615 | 616 | static void AppendDirectoryEntry(uint32_t cluster, size_t size, bool isDir, 617 | const std::string &name, 618 | uint32_t fileCluster, size_t fileSize) 619 | { 620 | FatDirent ent; 621 | 622 | auto pos = name.find('.'); 623 | if (pos == std::string::npos) { 624 | ent.SetName(name.c_str()); 625 | } else { 626 | auto pre = name.substr(0, pos); 627 | auto post = name.substr(pos + 1, std::string::npos); 628 | 629 | ent.SetName(pre.c_str()); 630 | ent.SetExtension(post.c_str()); 631 | } 632 | 633 | ent.SetClusterIndex(fileCluster); 634 | 635 | if (isDir) { 636 | FlagField flags; 637 | flags.Set(FatDirent::Flags::Directory); 638 | ent.SetEntryFlags(flags); 639 | } else { 640 | ent.SetSize(fileSize); 641 | } 642 | 643 | FileWriter parentDir(cluster, size); 644 | parentDir.Append(&ent, sizeof(ent)); 645 | 646 | memset(&ent, 0, sizeof(ent)); 647 | parentDir.Append(&ent, sizeof(ent)); 648 | } 649 | 650 | static bool CheckEntryNotInDir(uint32_t cluster, size_t &actualSize, 651 | const std::string &name) 652 | { 653 | FileReader rd(cluster, 0xFFFFFFFF); 654 | std::vector entries; 655 | 656 | if (!ScanDirectory(rd, entries, actualSize)) 657 | return false; 658 | 659 | for (const auto &it : entries) { 660 | if (it.shortName == name || it.longName == name) { 661 | std::cerr << name << ": already exists" << std::endl; 662 | return false; 663 | } 664 | } 665 | 666 | return true; 667 | } 668 | 669 | static void CreateDirectory(std::string args) 670 | { 671 | auto path = SplitPath(args); 672 | if (path.empty()) 673 | return; 674 | 675 | auto name = path.back(); 676 | if (!IsShortName(name.c_str())) { 677 | std::cerr << name << ": is not a short name (sorry)" << std::endl; 678 | return; 679 | } 680 | 681 | if (name.find('.') != std::string::npos) { 682 | std::cerr << name << ": directory name must not contain `.`" << std::endl; 683 | return; 684 | } 685 | 686 | DirEntry parent; 687 | size_t actualSize; 688 | 689 | if (!FindParent(path, parent)) 690 | return; 691 | 692 | if (!CheckEntryNotInDir(parent.firstCluster, actualSize, name)) 693 | return; 694 | 695 | uint32_t index; 696 | 697 | InitializeEmptyDirectory(parent.firstCluster, index); 698 | AppendDirectoryEntry(parent.firstCluster, actualSize, true, name, index, 0); 699 | } 700 | 701 | static void PackDirectory(std::string args) 702 | { 703 | // isolate the input filename 704 | size_t count = 0; 705 | while (count < args.size() && !isspace(args.at(count))) 706 | ++count; 707 | 708 | if (count >= args.size()) 709 | return; 710 | 711 | auto input = args.substr(0, count); 712 | 713 | while (isspace(args.at(count))) 714 | ++count; 715 | 716 | args.erase(0, count); 717 | 718 | // get the target path 719 | auto path = SplitPath(args); 720 | if (path.empty()) 721 | return; 722 | 723 | auto name = path.back(); 724 | if (!IsShortName(name.c_str())) { 725 | std::cerr << name << ": is not a short name (sorry)" << std::endl; 726 | return; 727 | } 728 | 729 | // locate the parent directory 730 | DirEntry parent; 731 | size_t parentDirSize; 732 | 733 | if (!FindParent(path, parent)) 734 | return; 735 | 736 | if (!CheckEntryNotInDir(parent.firstCluster, parentDirSize, name)) 737 | return; 738 | 739 | // pack the input file 740 | File infile(input.c_str(), true); 741 | FileWriter wr; 742 | 743 | for (;;) { 744 | char buffer[512]; 745 | 746 | auto diff = infile.Read(buffer, sizeof(buffer)); 747 | if (diff == 0) 748 | break; 749 | 750 | wr.Append(buffer, diff); 751 | } 752 | 753 | std::cout << "Packed `" << input << "`" << std::endl; 754 | 755 | // update the directory 756 | AppendDirectoryEntry(parent.firstCluster, parentDirSize, false, 757 | name, wr.FirstCluster(), wr.BytesWritten()); 758 | } 759 | 760 | static struct { 761 | const char *name; 762 | void (*callback)(std::string); 763 | } commands[] = { 764 | { "dir", ListDirectory }, 765 | { "type", DumpFile }, 766 | { "mkdir", CreateDirectory }, 767 | { "pack", PackDirectory }, 768 | }; 769 | 770 | int main(int argc, char **argv) 771 | { 772 | if (argc != 2 || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) { 773 | std::cerr << "Usage: fatedit " << std::endl << std::endl 774 | << "edit commands are read from STDIN" << std::endl; 775 | return EXIT_FAILURE; 776 | } 777 | 778 | try { 779 | file = File(argv[1], false); 780 | } catch (std::exception &e) { 781 | std::cerr << e.what() << std::endl; 782 | return EXIT_FAILURE; 783 | } 784 | 785 | // read the super block 786 | file.ReadAt(0, &super, sizeof(super)); 787 | 788 | // read the entire FAT into memory 789 | fat.LoadFromImage(); 790 | 791 | // do the thing 792 | for (;;) { 793 | if (isatty(STDIN_FILENO)) { 794 | std::cout << "C:\\> "; 795 | std::cout.flush(); 796 | } 797 | 798 | std::string line; 799 | if (!std::getline(std::cin, line)) 800 | break; 801 | trim(line); 802 | 803 | if (line.empty() || MatchCommand(line, "rem")) 804 | continue; 805 | 806 | bool found = false; 807 | 808 | for (const auto &it : commands) { 809 | if (MatchCommand(line, it.name)) { 810 | it.callback(line); 811 | found = true; 812 | break; 813 | } 814 | } 815 | 816 | if (!found) { 817 | std::cerr << "Unknown command `" << line << "`" << std::endl; 818 | } 819 | } 820 | 821 | // Update all the FATs 822 | fat.WriteToImage(); 823 | 824 | // Write back super block 825 | file.WriteAt(0, &super, sizeof(super)); 826 | 827 | if (super.BackupIndex() > 0 && 828 | super.BackupIndex() < super.ReservedSectors()) { 829 | file.WriteAt(super.BackupIndex() * super.BytesPerSector(), 830 | &super, sizeof(super)); 831 | } 832 | 833 | return EXIT_SUCCESS; 834 | } 835 | -------------------------------------------------------------------------------- /tools/installfat.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * installfat.cpp 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #include "Stage2Header.h" 8 | #include "fs/FatFsInfo.h" 9 | #include "fs/FatSuper.h" 10 | #include "host/File.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | static bool ReadAll(const char *filename, std::vector &out, 18 | size_t max) 19 | { 20 | File fd(filename, true); 21 | 22 | out.clear(); 23 | 24 | for (;;) { 25 | auto used = out.size(); 26 | out.resize(out.size() + 1024); 27 | 28 | auto ret = fd.Read(out.data() + used, out.size() - used); 29 | if (ret == 0) { 30 | out.resize(used); 31 | break; 32 | } 33 | 34 | out.resize(used + ret); 35 | 36 | if (out.size() > max) { 37 | std::cerr << filename << " is too big (max: " 38 | << max << ")" << std::endl; 39 | return false; 40 | } 41 | } 42 | 43 | return true; 44 | } 45 | 46 | int main(int argc, char **argv) 47 | { 48 | const char *vbrFile = nullptr; 49 | const char *stage2File = nullptr; 50 | const char *outFile = nullptr; 51 | 52 | for (int i = 1; i < argc; ++i) { 53 | if (!strcmp(argv[i], "--stage2")) { 54 | if ((i + 1) >= argc) { 55 | std::cerr << "Missing argument for `" << argv[i] 56 | << "`" << std::endl; 57 | return EXIT_FAILURE; 58 | } 59 | 60 | stage2File = argv[++i]; 61 | continue; 62 | } 63 | 64 | if (!strcmp(argv[i], "-v")) { 65 | if (argv[i][2] != '\0') { 66 | vbrFile = argv[i] + 2; 67 | continue; 68 | } 69 | 70 | if ((i + 1) >= argc) { 71 | std::cerr << "Missing argument for `" << argv[i] 72 | << "`" << std::endl; 73 | return EXIT_FAILURE; 74 | } 75 | 76 | vbrFile = argv[++i]; 77 | continue; 78 | } 79 | 80 | if (!strcmp(argv[i], "-o")) { 81 | if (argv[i][2] != '\0') { 82 | outFile = argv[i] + 2; 83 | continue; 84 | } 85 | 86 | if ((i + 1) >= argc) { 87 | std::cerr << "Missing argument for `" << argv[i] 88 | << "`" << std::endl; 89 | return EXIT_FAILURE; 90 | } 91 | 92 | outFile = argv[++i]; 93 | continue; 94 | } 95 | } 96 | 97 | if (outFile == nullptr) { 98 | std::cerr << "no output file specified" << std::endl; 99 | return EXIT_FAILURE; 100 | } 101 | 102 | if (vbrFile == nullptr) { 103 | std::cerr << "no VBR file specified" << std::endl; 104 | return EXIT_FAILURE; 105 | } 106 | 107 | if (stage2File == nullptr) { 108 | std::cerr << "no stag2 file specified" << std::endl; 109 | return EXIT_FAILURE; 110 | } 111 | 112 | try { 113 | std::vector stage2; 114 | std::vector vbr; 115 | File file(outFile, false); 116 | FatFsInfo fsinfo; 117 | FatSuper super; 118 | 119 | file.ReadAt(0, &super, sizeof(super)); 120 | file.ReadAt(super.FsInfoIndex() * super.BytesPerSector(), 121 | &fsinfo, sizeof(fsinfo)); 122 | 123 | if (!ReadAll(vbrFile, vbr, 420)) 124 | return EXIT_FAILURE; 125 | 126 | super.SetBackupIndex(0); 127 | super.SetFsInfoIndex(1); 128 | super.SetOEMName("Goliath"); 129 | super.SetBootCode(vbr.data(), vbr.size()); 130 | 131 | file.WriteAt(0, &super, sizeof(super)); 132 | file.WriteAt(super.BytesPerSector(), &fsinfo, sizeof(fsinfo)); 133 | 134 | auto max = (super.ReservedSectors() - 2) * 135 | super.BytesPerSector(); 136 | 137 | if (!ReadAll(stage2File, stage2, max)) 138 | return EXIT_FAILURE; 139 | 140 | auto *hdr = new (stage2.data()) Stage2Header(); 141 | hdr->SetSectorCount(stage2.size()); 142 | hdr->UpdateChecksum(); 143 | 144 | if (!hdr->Verify(hdr->SectorCount())) { 145 | std::cerr << "Stage 2 header verification failed" 146 | << std::endl; 147 | return EXIT_FAILURE; 148 | } 149 | 150 | file.WriteAt(2 * super.BytesPerSector(), 151 | stage2.data(), stage2.size()); 152 | } catch (std::exception &e) { 153 | std::cerr << e.what() << std::endl; 154 | } 155 | 156 | return EXIT_SUCCESS; 157 | } 158 | -------------------------------------------------------------------------------- /tools/meson.build: -------------------------------------------------------------------------------- 1 | installfat = executable( 2 | 'installfat', 3 | sources: [ 4 | 'installfat.cpp', 5 | ], 6 | install: false, 7 | native: true, 8 | implicit_include_directories: true, 9 | include_directories: incs, 10 | ) 11 | 12 | fatedit = executable( 13 | 'fatedit', 14 | sources: [ 15 | 'fatedit.cpp', 16 | ], 17 | install: false, 18 | native: true, 19 | implicit_include_directories: true, 20 | include_directories: incs, 21 | ) 22 | -------------------------------------------------------------------------------- /tools/util.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * util.h 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #ifndef UTIL_H 8 | #define UTIL_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | static void trim(std::string &line) 15 | { 16 | while (!line.empty() && isspace(line.back())) 17 | line.pop_back(); 18 | 19 | size_t count = 0; 20 | while (count < line.size() && isspace(line.at(count))) 21 | ++count; 22 | 23 | if (count > 0) 24 | line.erase(0, count); 25 | } 26 | 27 | static std::list SplitPath(std::string str) 28 | { 29 | std::list out; 30 | 31 | while (!str.empty()) { 32 | // drop leadings slashes 33 | size_t i = 0; 34 | while (i < str.size() && (str.at(i) == '/' || str.at(i) == '\\')) 35 | ++i; 36 | if (i > 0) { 37 | str.erase(0, i); 38 | continue; 39 | } 40 | 41 | // find end of current component 42 | while (i < str.size() && str.at(i) != '/' && str.at(i) != '\\') 43 | ++i; 44 | 45 | if (i == str.size()) { 46 | out.push_back(str); 47 | str.clear(); 48 | } else { 49 | out.push_back(str.substr(0, i)); 50 | str.erase(0, i + 1); 51 | } 52 | } 53 | 54 | return out; 55 | } 56 | 57 | #endif /* UTIL_H */ 58 | -------------------------------------------------------------------------------- /vbr/abi.S: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * abi.S 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | .code16 8 | .global main 9 | .section ".entry" 10 | _start: 11 | xor %ax, %ax 12 | mov %ax, %ds 13 | mov %ax, %es 14 | mov %ax, %ss 15 | mov $0x7c00, %sp 16 | pushl %esi 17 | pushl %edx 18 | calll main 19 | jmp *%eax 20 | -------------------------------------------------------------------------------- /vbr/meson.build: -------------------------------------------------------------------------------- 1 | vbr = executable( 2 | 'vbr', 3 | name_suffix: 'bin', 4 | sources: [ 5 | 'abi.S', 6 | 'vbr.cpp', 7 | ], 8 | link_args: [ 9 | '-Wl,-T' + join_paths(meson.current_source_dir(), 'vbr.ld'), 10 | '-nostdlib', 11 | ], 12 | link_depends: [ 13 | 'vbr.ld', 14 | ], 15 | cpp_args: realmode_cpp_args, 16 | install: false, 17 | implicit_include_directories: false, 18 | include_directories: incs, 19 | pie: false, 20 | ) 21 | -------------------------------------------------------------------------------- /vbr/vbr.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ISC */ 2 | /* 3 | * vbr.cpp 4 | * 5 | * Copyright (C) 2023 David Oberhollenzer 6 | */ 7 | #include "BIOS/BiosDisk.h" 8 | #include "BIOS/BIOSTextMode.h" 9 | #include "part/MBREntry.h" 10 | #include "fs/FatSuper.h" 11 | #include "Stage2Header.h" 12 | 13 | static const char *msgErrLoad = "Error loading stage 2!"; 14 | static const char *msgErrBroken = "Stage 2 corrupted!"; 15 | 16 | extern "C" { 17 | void *main(BiosDisk disk, const MBREntry *ent); 18 | } 19 | 20 | void *main(BiosDisk disk, const MBREntry *ent) 21 | { 22 | auto *super = (FatSuper *)0x7c00; 23 | MBREntry part = *ent; 24 | 25 | // Get and sanitze number of reserved sectors 26 | auto count = super->ReservedSectors(); 27 | if (count < 3) 28 | DumpMessageAndHang(msgErrLoad); 29 | 30 | count -= 2; 31 | 32 | if (count > 30) 33 | count = 30; 34 | 35 | // XXX: we boldly assume the partition to be cylinder 36 | // aligned, so the stupid CHS arithmetic won't overflow. 37 | CHSPacked src = part.StartAddressCHS(); 38 | 39 | src.SetSector(src.Sector() + 2); 40 | 41 | auto *dst = (uint8_t *)Stage2Location; 42 | 43 | if (!disk.LoadSectors(src, dst, count)) 44 | DumpMessageAndHang(msgErrLoad); 45 | 46 | auto *hdr = (Stage2Header *)dst; 47 | 48 | if (!hdr->Verify(count)) 49 | DumpMessageAndHang(msgErrBroken); 50 | 51 | // Enter stage 2 52 | hdr->SetBiosBootDrive(disk); 53 | hdr->SetBootMBREntry(part); 54 | 55 | return dst + sizeof(*hdr); 56 | } 57 | -------------------------------------------------------------------------------- /vbr/vbr.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("binary") 2 | OUTPUT_ARCH(i386) 3 | 4 | SECTIONS 5 | { 6 | . = 0x7C5A; 7 | 8 | .text : { 9 | KEEP(*(.entry)) 10 | *(.text) 11 | *(.text.*) 12 | *(.rodata) 13 | *(.rodata.*) 14 | } 15 | 16 | /DISCARD/ : { *(*) } 17 | } 18 | --------------------------------------------------------------------------------