Pinecore x86 + Freedos Alpha Distro
The DOS-MODE Kernel
This is the latest kernel running on a Pentium 3 machine that uses ISOLINUX to boot into the FREEDOS kernel and then launches the PINE.COM stub to then load the Pinecore-x86 DOS MODE kernel in a boot chain manner. This allows users to have a fallback mode to pure 16-bit DOS (We strongly Recommend FreeDOS) to run any other applications in real mode dos.
Typing quit at the pinecore shell drops you back to the FreeDOS prompt, and running PINE.COM again reloads the kernel from scratch and re-initializes every driver, so a bad enumeration or a swapped USB stick is a round trip rather than a reboot. Be aware the exit path is not tidy yet — the PIT is left at 100hz so DOS's clock runs fast, and the USB controllers are left running, so treat the fallback as a development convenience and not something to lean on in the middle of real work.
Why This Exists
Pinecore was not started as an OS project. We were building DeadSeas, a DOS game, and we wanted to play it over WiFi on the machines it was written for — Pentium 2 through Pentium 4 era hardware sitting on the bench.
The problem is that nothing supports that hardware from DOS. There is no DOS packet driver for a USB WiFi dongle, no DOS driver for a CardBus socket the BIOS refuses to program, no way to get a modern-ish NIC talking to a 16-bit real mode application. The drivers were never written because by the time the hardware shipped, everybody had moved to Windows. So if we wanted DeadSeas running over WiFi on a P3, somebody had to write that layer.
That layer became the kernel. Pinecore is a 32-bit DOS — it runs your DOS binaries in V86 mode with a DPMI host underneath them, and it brings up the hardware DOS never learned about, then hands it back to those programs through the interfaces they already know: INT 13h for disks, INT 60h for a packet driver, INT 21h for files. A DOS game does not need to know that its packets are going out over an Atheros USB dongle that was made in 2008 and never had a DOS driver in its life.
The game is still the point. The kernel is the tool we had to build to get there, and we are releasing it because the tool turned out to be more generally useful than the reason we built it.
Pinecore DOS mode Features
Most of these features are still under construction and have many bugs at this stage so please bear in mind that this is a very early alpha, please use at your own risk.
- Preemption 8192hz irq8
- OHCI, ECHI with support for mounting fat 16/32 drives ( Still buggy but working and needs hot-plugging finished).
- USB support for Keyboards, Mouse, HID HUB, WIFI devices AR9170 (netgear), IWI pro series 2200BG/2915ABG, Ralink rt73
- Serial debugging (Very useful for developing drivers to peek and poke etc)
- Network: intel E100, RDC R6040
- Storage: ATA-PIO (needs dma completion to get higher speeds) Fat16, fat32, CF-Cards (PCI Delock cardbus pci bridge)
- PCMCIA Support, Aironet 250, Atheros (airport but not working)
- Sound: Soundblaster driver is still in development
Writing Your Own Drivers
This is the part we want other people using. Drivers are .kmd modules — ordinary ELF32 relocatable objects, built with the same i686-elf-gcc that builds the kernel, and loaded at runtime. You do not need to rebuild or reflash the kernel to add hardware support, and you do not need to ask us to merge anything.
The shortest possible driver is `src/modules/hello.c` — read that one first, it is about 30 lines and it does nothing except prove the whole path works.
How a module is structured
- `module_init(your_init_fn)` — runs when the module loads, return 0 for success or an error code to be rejected
- `module_exit(your_exit_fn)` — your cleanup (see the warning below before you trust this)
- `MODULE_NAME`, `MODULE_AUTHOR`, `MODULE_DESCRIPTION`, `MODULE_LICENSE` — the loader reads these, and the license gates which symbols you can bind to
- `EXPORT_SYMBOL(name)` / `EXPORT_SYMBOL_GPL(name)` — publish a function for other modules to use, GPL-family only for the second one
How modules find each other
The loader walks every loaded module's export table (`module_resolve()` in `kernel/module.c`), so a module can bind straight to another module's symbols — this is how `aironet.kmd` gets its I/O base from `pcmcia.kmd`, and how the USB class drivers bind to `usbcore.kmd`. Load order does not matter much because the autoloader does repeated passes and retries anything whose symbols were not resolvable yet, so a driver that depends on three other modules will come up once they have.
How to build and install one
- Drop your `.c` in `src/modules/`, add it to `MODULE_SRCS` in `src/Makefile`, run make modules
- Copy the resulting `.kmd` into `\DRIVERS\` on the boot medium — or on any FAT volume the kernel can see, including a USB stick
- At boot the kernel scans `\DRIVERS\*.KMD` and loads what it finds, with the serial log telling you exactly what happened to each one
If you want a hardened setup, `PCORE.CFG` has a kmd_allow allowlist so the kernel will only trust modules you named, which matters because a .kmd runs at Ring 0 with no supervision at all. There is also usb_enable = no for when the USB stack fights the BIOS on your particular board.
Publishing your hardware to DOS programs
This is the bit that makes a driver useful rather than just present. You register with the subsystem that matches your device and the kernel does the DOS-facing translation for you:
- Storage — `int13h_register_disk` and your device becomes a drive letter, visible to `dir` and to any DOS program that uses INT 13h or INT 21h. `cf.c` does this in about 80 lines
- Network — implement the layer-2 `netdev_*` contract and the INT 60h packet driver shim can carry mTCP, Watt-32 and anything else expecting a Crynwr driver. `aironet.c` was the first to do it
- USB host controllers — `usbcore_register_hcd` with a vtable of transfer ops
- USB devices — `usbcore_register_class_driver` and you get probe/disconnect callbacks per interface
- Audio — `snd_register_driver`, and this one is wide open, the HAL exists and no backend has ever been written, so the first person to make a sound on this OS could be you
- `shell_register_command` gives your driver its own shell command for diagnostics, which every one of ours has and you will want on day one
How you actually debug this
Serial. Get a null modem cable or a USB serial adapter on COM1 before you write a line of driver code — it is the only thing that survives a lockup, and every driver in the tree is littered with `serial_puts` breadcrumbs because that is how all of them got working. The shell also has peek, poke, pci, pcr, pcw, io and mmio for reading and writing config space, ports and MMIO by hand from the prompt, which means you can prove a register does what the datasheet claims before committing it to code.
One thing to know before you start
There is currently no unload path. Your `module_exit` only runs if your `module_init` fails — nothing else calls it, so a module cannot be unloaded and reloaded without rebooting the kernel. Write the exit function anyway (all of ours have one) because it will be wired up, but do not design around being able to unload today.