# CSMWrap: running legacy BIOS software on UEFI-only hardware

> CSMWrap packages a Compatibility Support Module build of SeaBIOS as an EFI application, which lets a machine with UEFI only firmware boot a legacy BIOS operating system, and it does so by putting a BIOS emulator in the UEFI boot path rather than by changing firmware settings. The deployment is a single .efi file on a FAT partition, and the configuration is a small INI file, but the firmware prerequisites and the video card caveats are where most of the work is.

**CSMWrap/CSMWrap** — Get PC BIOS back on UEFI only systems.

- Repository: https://github.com/CSMWrap/CSMWrap
- Stars: 968 · Forks: 54
- Language: C
- License: LGPL-2.1
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/csmwrap-csmwrap

## A BIOS emulator placed in the UEFI boot path

The problem CSMWrap addresses is the slow disappearance of the legacy BIOS. Most modern desktop firmware is UEFI only, which the README calls class 3, and the Compatibility Support Module that used to emulate a BIOS on those machines has been removed by the vendors. A machine that has to run a legacy operating system, an old Windows install, a DOS-era utility or a piece of hardware with a legacy driver, has no way to do it out of the box.

The approach is to put the BIOS back in the firmware. CSMWrap wraps a Compatibility Support Module build of the SeaBIOS project as an out-of-firmware EFI application, and the effect is a compatibility layer for traditional PC BIOS operation. The legacy BIOS is not in the firmware, it is a program that the firmware runs.

The architectural consequence is that the UEFI firmware itself does the booting. It finds the EFI application on the boot medium, runs it, and the application then provides the legacy environment: it emulates the BIOS interfaces that the legacy operating system expects, and the disk access goes through the EFI block services underneath. A legacy operating system believes it is talking to a real BIOS, and the UEFI firmware is what is really there.

The repository is C, and its top level shows what it is made of. The seabios directory is the SeaBIOS source as a submodule, alongside flanterm for the text output, nanoprintf for formatted printing, picoefi for the EFI protocol bindings, uACPI for the ACPI tables, freestnd-c-hdrs for a freestanding C runtime, and cc-runtime for the compiler runtime. The src directory holds CSMWrap's own code, and the GNUmakefile at the root is the build. So the SeaBIOS part is not a reimplementation, it is the upstream project wrapped.

## Deploying the file: FAT, MBR and a hardcoded path

The deployment is designed to be as close to drop-in as the situation allows, and the executive summary in the README lays it out in three steps.

First, download the latest csmwrapARCH.efi from the releases page, where ARCH is x86_64 or ia32 depending on the hardware, and the README notes that dropping both also works. Second, copy that file to a FAT-formatted partition, typically as /EFI/BOOT/BOOTX64.EFI for the 64-bit build or /EFI/BOOT/BOOTIA32.EFI for the 32-bit one. The hardcoded path is the reason: it is what the firmware looks for when it falls back to the removable media path, and the README is explicit that the file has to be named that way for the firmware to pick it up automatically. Third, select the UEFI boot entry for the drive you deployed to.

The medium needs to be FAT, and the README specifies 12, 16 or 32. It also recommends MBR as the partition table, with two reasons: UEFI firmware is perfectly capable of booting from that format, and it is the most compatible with the legacy operating systems you are likely trying to boot, several of which predate GPT entirely.

The point of the fallback path is that it needs no firmware change at all. If the machine has no boot order entry you can create, the removable media path still works, which means the same stick that boots the legacy system can be prepared without touching the setup. The README is careful to say that ideally that is all that would be needed.

For a machine you can reconfigure, the alternative is to install the application as a normal boot entry in the EFI system partition rather than relying on the fallback, and to drop both the 64-bit and 32-bit builds so that the firmware can pick whichever it needs.

## Firmware settings: Secure Boot, X2APIC and the two to try

CSMWrap is designed to work without firmware changes for settings that might not even be exposed, depending on the firmware, or that might conflict with other UEFI operating systems on the same machine. That is a real design constraint, since a multi-boot machine may have an operating system that needs X2APIC while the legacy one cannot run with it.

The one requirement is Secure Boot, and it should be disabled unless you intend to sign the CSMWrap EFI application manually, which the README says is possible but is beyond the scope of that document. An unsigned EFI application will not run with Secure Boot enforced, and the message the firmware gives is not always obvious about the cause, so this is the first thing to check when the stick is not recognised.

Beyond that, the README recommends disabling one setting if at all possible, and trying two more if things do not work. The first is X2APIC, the extended interrupt controller mode, which the README lists first among the settings worth disabling for best legacy OS compatibility. The other two are Above 4G Decoding, which changes how the firmware describes the memory map, and Resizable BAR, also called Smart Access Memory, which changes how the PCI express memory BARs are sized. The README is careful to say that these two should not be necessary and asks for an issue report if you find you need them, which is a good way of distinguishing a real dependency from a guess.

The ordering of these recommendations reflects how much the author expects them to matter. Secure Boot is a hard requirement unless you sign the binary. X2APIC is the setting most likely to be the cause of a legacy system that boots and then hangs. The other two are mentioned as a fallback, and the request for a report suggests they are rarely the answer.

## The video card problem, and why SeaVGABIOS is not enough

The second half of CSMWrap wraps the SeaVGABIOS module of SeaBIOS, and the README is unusually blunt about the limits of that.

SeaVGABIOS is a bare bones implementation of a legacy video BIOS. The consequences are that many things needing direct access to legacy video modes will not work properly, and the examples given are pretty much all MS-DOS games and MS-DOS Editor. More modern operating systems that use the VESA BIOS Extensions standard, which means more modern Windows NT and Linux, should still work fine.

So the split is between a legacy operating system that wants real mode video modes and one that wants a linear framebuffer through VBE. The first category is exactly the category of software people most often need a legacy BIOS for, which makes the limitation awkward. There is no way around it in software, because a bare bones VGA BIOS is what a bare bones VGA BIOS does.

The recommended fix is hardware. The README says it is highly recommended, if possible, to install a legacy-capable video card, and explains the mechanism: if a legacy capable card is present, its own Video BIOS is used instead of SeaVGABIOS, which provides a much better, close to native experience. The configuration reflects that priority. The vgabios option takes a path to a custom VBIOS file on the EFI system partition, for example \EFI\CSMWrap\vgabios.bin, and when it is empty the card's built-in OpROM is used and, failing that, SeaVGABIOS. There is also a special value, cbfs, which loads vgabios.bin from coreboot's cbfs, and a cbfs:filename form for a custom cbfs filename.

That default order is worth noting, because it means CSMWrap prefers real hardware over its own emulation, and only falls back to the emulator when the machine has nothing better. On a modern desktop with a UEFI-native graphics card, that fallback is what you get.

## Configuration: an INI file, an NVRAM variable and the efivarfs trick

CSMWrap takes an optional INI style configuration file named csmwrap.ini, placed in the same directory as the EFI executable, for example /EFI/BOOT/csmwrap.ini. If that file is absent, CSMWrap falls back to an NVRAM variable, and if both are absent, sensible defaults are used.

The NVRAM variable holds the same content as the ini file, parsed identically, newline separated with one key=value per line. Writing it has a wrinkle that the README explains carefully. Authoring it with the UEFI Shell setvar command is not supported, because its quoted string argument cannot carry a literal newline. The workaround is to write the variable from Linux through efivarfs, reusing the exact ini content, with the requirement that it is a single printf because efivarfs performs a complete variable set on every write:

```sh
printf '\x07\x00\x00\x00%s' "$(cat csmwrap.ini)" > /sys/firmware/efi/efivars/CSMWrapConfig-2910aadb-f7ca-42b5-8ce2-d62d6a69b5ee
```

The four byte prefix is the NV|BS|RT attribute mask efivarfs expects, and the double quoted argument is emitted verbatim so that the newline separators and the backslash paths in the file survive unmangled. The double quotes are required, the README points out, because without them the shell would word split the configuration.

This is a small piece of engineering that tells you something about the project. The NVRAM route exists so that a configuration can be set once and survive reinstalling the boot medium, and the choice of efivarfs over the UEFI shell is a deliberate one that trades a nicer interface for a working one.

The options themselves are what most users will actually touch. The serial, serial_port and serial_baud settings enable serial debug output and set the port address, with the four standard COM port addresses listed, and the default baud rate is 115200. The verbose option shows debug output on the screen through Flanterm, which is the terminal renderer in the repository. The iommu_disable option defaults to true and disables the IOMMUs, Intel VT-d and AMD-Vi, before the legacy boot, which is necessary for many legacy operating systems since they have no idea what an IOMMU is.

## CPU visibility, the system thread and the MP tables

The hardest options in the table are the CPU ones, and they exist because a legacy operating system has a very specific idea of how many processors exist and where they are in the APIC topology.

The problem is the system thread. SeaBIOS needs a particular application processor to run, and the option system_thread takes the APIC ID of the logical CPU to reserve for it, with a default that auto-picks the highest ID application processor below 0xFF. The constraints are stated: the CPU must be an enabled application processor, not the bootstrap processor, and its APIC ID must be below 255. The selected CPU is hidden from the operating system in both the MADT and the MP table, which is how a processor can be used and yet appear not to exist to the software that is booting.

The allow and block lists are the general form of that idea. cpu_allowlist is a comma separated list of APIC IDs of logical CPUs that should be exposed to the operating system, where each entry is a single ID or an inclusive range such as 0,2-4,7. cpu_blocklist is the same shape for the CPUs to hide. The two are mutually exclusive, and the README explains the edge cases carefully.

An empty value is itself a setting. cpu_allowlist with nothing after the equals sign means hide every application processor and leave only the bootstrap processor visible. An empty cpu_blocklist is a no-op for visibility but still claims the slot, which is why the allowlist cannot then be set as well. The bootstrap processor is always exposed, and the system thread is always hidden, whatever else you configure.

That last rule is the one to remember. Whatever you do with the lists, one processor is always going to be invisible, and an operating system that counts processors in a particular way may notice.

## Building it, the licence and when to reach for something else

Building from source needs either x86-64 or IA32 architecture, and the dependencies are a base development package including make, gnu-efi, xxd, which some distributions call tinyxxd, and nasm. Then it is:

```bash
make
```

The resulting.efi file lands in a bin-ARCH directory, bin-x86_64 or bin-ia32. The prebuilt binaries on the releases page are the path most users take, and the build is mostly for people who need a custom build, which given the option to sign the application yourself, is a legitimate reason.

The licence is LGPL-2.1, which is worth understanding in this context. The project embeds SeaBIOS and vendors several other components with their own terms, so a derived and distributed binary carries obligations. For most users, who download a release rather than distributing a modified build, this is not the deciding factor.

On maintenance, the repository is not archived and the last push was on 2026-10-16. The release history shows three releases in quick succession in May 2026, with 3.1.2 on 2026-05-09, 3.1.1 on 2026-05-06 and 3.1.0 on 2026-05-04, which suggests a burst of work on the configuration options and the CPU handling rather than a slow trickle.

Where this is the wrong tool is worth stating plainly. If the legacy software needs real legacy video modes, the SeaVGABIOS limitation is not a bug you can configure around, and the answer is a legacy graphics card or a different machine. If Secure Boot is mandatory in your environment, the application is unsigned and you have to sign it. If the machine has no UEFI at all, the whole premise does not apply. And if you are choosing a new operating system for new hardware, a BIOS emulator in the boot path is a workaround for a category of machine that is shrinking rather than growing, and it adds a layer between the firmware and your software that can only make debugging harder.

## Conclusion

Adopt CSMWrap when you have a UEFI only machine that must run a legacy BIOS operating system, such as an old installation for which no UEFI driver exists, and when you can boot the machine from a USB stick and change firmware settings. It is the wrong tool for a machine you cannot reconfigure in firmware, for anything that needs real legacy video modes such as most MS-DOS games, and for a system where Secure Boot is mandatory, since the application is unsigned unless you sign it yourself. Before deploying, read the option table and decide about the video card and the CPU visibility settings, since the defaults hide a core from the operating system and disable the IOMMUs, and both are visible to whatever you boot.

## FAQ

### What is CSMWrap and what does it do?

It is an EFI application that wraps a Compatibility Support Module build of SeaBIOS as an out-of-firmware program, creating a compatibility layer for traditional PC BIOS operation. That lets a legacy BIOS operating system boot on a UEFI only class 3 machine without the vendor's Compatibility Support Module.

### How do I install CSMWrap?

Download the csmwrapARCH.efi file from the releases page, copy it to a FAT formatted partition as /EFI/BOOT/BOOTX64.EFI for the 64-bit build or /EFI/BOOT/BOOTIA32.EFI for the 32-bit one, because the path is hardcoded for the firmware to find, and then select the UEFI boot entry for that drive. The README recommends an MBR partition table.

### Does CSMWrap work with Secure Boot?

Not by default. Secure Boot should be disabled unless you want to sign the CSMWrap EFI application manually, which the README says is possible but is out of its scope.

### Why do some legacy games not work with CSMWrap?

CSMWrap wraps SeaVGABIOS, a bare bones implementation of a legacy video BIOS, so software that needs direct access to legacy video modes, such as most MS-DOS games and MS-DOS Editor, will not work properly. Operating systems using VESA BIOS Extensions, such as modern Windows NT and Linux, should work, and installing a legacy capable video card makes the real Video BIOS of that card be used instead.

### How do I configure CSMWrap?

Place an INI style file named csmwrap.ini next to the EFI executable, or use the NVRAM variable, which holds the same content. The UEFI Shell setvar command cannot be used because its quoted string argument cannot carry a newline, so the variable has to be written from Linux through efivarfs with a single printf.

## Sources

- [CSMWrap/CSMWrap on GitHub](https://github.com/CSMWrap/CSMWrap)
- [Issues](https://github.com/CSMWrap/CSMWrap/issues)
- [License: LGPL-2.1](https://github.com/CSMWrap/CSMWrap/blob/main/LICENSE)
- [README](https://github.com/CSMWrap/CSMWrap/blob/main/README.md)
- [Releases](https://github.com/CSMWrap/CSMWrap/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/csmwrap-csmwrap
