Home > Linux, Server stuff > How to boot Windows partition virtually under KVM with UEFI firmware

How to boot Windows partition virtually under KVM with UEFI firmware

It is actually quite easy to boot Windows virtualized using KVM. But to properly use the UEFI bootloader, suitable QEMU arguments are required. Here is a lightly commented QEMU command I use to boot virtual Windows 10 I have on a separate partition.

sudo qemu-system-x86_64 --enable-kvm -cpu host -m 2048 \
-smp 4,sockets=1,cores=2,threads=2 -mem-path /dev/hugepages \
-vga qxl -display none -serial mon:stdio \
-rtc clock=host,base=localtime \
-device qemu-xhci,id=xhci \
-device virtio-tablet,wheel-axis=true \
-soundhw ac97 \
-netdev user,id=vmnic,smb=/ \
-device virtio-net,netdev=vmnic \
-drive file=/usr/share/ovmf/x64/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on \
-drive file=$HOME/.config/qemu-windows.nvram,if=pflash,format=raw,unit=1 \
-drive file=/dev/sdb,index=0,media=disk,driver=raw \
-cdrom /opt/UefiShell.iso

For now I use sudo, because QEMU needs to access raw partitions from /dev/sdb. The other, maybe better way would be assigning a group to /dev/sdb, setting up proper group permissions and adding me to that group.

-m 2048 -smp 4,sockets=1,cores=2,threads=2 says to allocate 2GB of RAM for the guest and use CPU threads (1 CPU socket, with 2 cores, each core having 2 threads).

-mem-path /dev/hugepages is better described in Arch wiki.

-display sdl -vga qxl Use SDL for rendering and window management in the host and QXL GPU device in the guest (there are QXL drivers for Windows).

-device qemu-xhci,id=xhci Enable USB3 support by emulating an XHCI controller

-device virtio-tablet,wheel-axis=true Emulate a tablet pointing device with mouse scroll support

-soundhw ac97 Emulate ac97. You can then use Realtek driver. It worked better in QEMU than Intel hda for me.

netdev stuff Is for setting up network interface

-drive file=/usr/share/ovmf/x64/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on This is a very important part. It loads OVMF UEFI firmware read-only as the first Flash device. This firmware implements a UEFI bios and allows running UEFI Shell or booting .efi bootloader for Windows (bootmgfw.efi). This OVMF can be downloaded directly from the OVMF project repo or if you are using Arch Linux, just install ovmf package.

-drive file=$HOME/.config/qemu-windows.nvram,if=pflash,format=raw,unit=1 this loads a read-write NVRam flash image as the second virtual flash chip. OVMF firmware uses this to store UEFI variables, .efi boot order, etc. The default image can be copied from the OVMF setup (at /usr/share/ovmf/x64/OVMF_VARS.fd in ovmf Arch linux package). It must be a writable copy.

-drive file=/dev/sdb,index=0,media=disk,driver=raw Attaches my raw sdb block device to the virtual machine. That is used as a HDD for the guest, it has Windows pre-installed there together with EFI partition.

-cdrom /opt/UefiShell.iso UEFI shell iso as a CDROM. Before OVMF nvram is properly configured to boot Windows by default, this will result in booting into the EFI shell which allows to run .efi executables manually. Windows can be run by just navigating into the EFI partition and running the Windows efi loader – blkX:\EFI\Microsoft\Boot\bootmgfw.efi.

I don’t know how to force Windows to write UEFI boot order. There doesn’t seem to be a tool like efibootmgr on Windows. It would set the UEFI boot order up randomly during Windows Updates (mostly when you don’t want it to touch your EFI setup). If TianoCore fails to find your Windows installation, you can try pressing ESC during TianoCore EFI boot to get to boot menu. Or you can always boot Linux using the -cdrom command and use efibootmgr to force the OVMF to boot the Windows loader entry for this virtual machine by default. Usage of the efibootmgr command is out of scope of this article and can be found in many online resources elsewhere.

You can even use SPICE to connect to the booted VM remotely. To do that, add these options:

-spice port=5900,addr=,disable-ticketing -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 -chardev spicevmc,id=spicechannel0,name=vdagent \ -chardev spicevmc,name=usbredir,id=usbredirchardev1 -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1 \ -chardev spicevmc,name=usbredir,id=usbredirchardev2 -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2 \ -chardev spicevmc,name=usbredir,id=usbredirchardev3 -device usb-redir,chardev=usbredirchardev3,id=usbredirdev3 \

It says to listen on port 5900 (localhost-only), adds SPICE channel between host and guest (to allow remote copy-paste) and 4 remotelly-attachable USB devices.

You can then use SPICE client like remote-viewer to connect to the VM. If the original local VM graphical output is not needed, add -display none parameter.

Have fun with virtualized Windows and remember – Windows is good for games only and for stuff like government PDFs which use proprietary Adobe XFA forms and are therefore supported in official Adobe Reader which has the best support in this platform only. Windows definitely shouldn’t be used on servers or anything serious! By default it is very limited (maximum number or RAM, maximum number of CPU cores, maximum number of listening socket connections, etc) until you pay huge amounts of money to Microsoft. Wise people use Linux and lazy people who don’t care about privacy or freedom use Mac OS X.

Categories: Linux, Server stuff Tags:
  1. defb
    August 1st, 2018 at 03:49 | #1

    I’m attempting to run this with debian testing (at the moment deb10), but unfortunately, the ovmf package in the deb repos don’t have /usr/share/ovmf/x64/OVMF_CODE.fd. Also, by OVMF_CODE.fd, do you mean copy it over to .config and renaming it qemu-windows.nvram? Other than that, I’m impressed at how you can virtualize a physical install of windows when virtual box has trouble doing it.

    • k3a
      August 1st, 2018 at 16:23 | #2

      For Debian (deb/apt) you can list the content of a package with
      # dpkg-query -L ovmf

      So just update the path.

      In my case qemu-windows.nvram is a “user” copy of OVMF_VARS.fd. It is mapped by QEMU as a second persistent flash (first being the actual UEFI bios binary which won’t change). This second flash (represented also as a file here) stores the bios settings – mainly boot order (the EFI boot order manages the OS like Windows or utility efibootmgr on Linux and BIOS stores it in this config flash). The original OVMF_VARS.fd contains the ‘default’ settings.

  2. Björn Carlsson
    August 6th, 2018 at 08:04 | #3

    Where did you find UefiShell.iso

  3. Ivan Avery Frey
    November 3rd, 2018 at 21:19 | #5

    Thank you for these instructions. My Linux install is on the same disk as my Windows install. Is there a way to boot Windows virtually when Linux and Windows are on the same physical device?

    • k3a
      November 3rd, 2018 at 21:32 | #6

      Hey. Definitely. Partitions are on different parts of the physical drive. And this is exactly what I am doing here. I mounted /dev/sdb with index 0 (which is equivalent to /dev/sdb1 where my EFI partition is and this /dev/sdb drive is where I have both OS installed. Note that QEMU counts from 0, Linux creates /dev/sdbX starting 1).

      My abbreviated output of fdisk -l /dev/sdb:

      /dev/sdb1 EFI System
      /dev/sdb2 AFS
      /dev/sdb3 Microsoft basic data
      /dev/sdb4 Microsoft reserved
      /dev/sdb5 Linux filesystem root
      /dev/sdb6 Linux filesystem swap

      The EFI partition (/dev/sdb1 in my case) is where the windows bootloader is (/EFI/Microsoft/Boot/bootmgfw.efi). EFI implementation (or EFI console) can then run this bootloader which then loads registry-like /EFI/Microsoft/Boot/BCD file where the partition UUID of the Windows installation is. That Windows bootloader would then load that partition and continue normal windows booting. So you will use EFI partition initially, run Windows bootloader on it (you can use EFI console mounted as cdrom at the beginning or use boot options of the TianoCore EFI firmware – hit ESC during boot), which then finds and loads the the big windows partition, mounts it and boots from it.

  4. rp
    December 14th, 2018 at 22:31 | #7

    Thank you very much for these instructions. I was able to boot up my windows partition. However I only get a 800×600 resolution. I suspect it must be something to do with the fact that I don’t have virtio drivers installed in my windows guest. I am not clear how to go about doing this and more specifically what drivers to install from the fedora repository. Could you please give some additional details on virtio driver installation in the windows guest?

  5. k3a
    December 15th, 2018 at 08:55 | #8

    You can extract them from Fedora distribution. Fedora ships these drivers signed so they will install without any hacks. I don’t remember exact place but I think I extracted them from rpm – here is a list of fedora stable rpms https://fedorapeople.org/groups/virt/virtio-win/repo/stable/ . Ubuntu also ships the drivers but I don’t know if they are signed. Their debs are here https://launchpad.net/kvm-guest-drivers-windows/+download.

  6. January 20th, 2019 at 22:08 | #9

    Thanks for this. Saved me a ton of time. Used it to boot a copy of the Win10-Edge MS developer ova after converting it to qemu.

    If you do have to do any boot diagnostics, then the refind cdrom iso https://sourceforge.net/projects/refind/ can be used as a ready replacement in place of the EFI Shell iso.

  7. Muhammed Unais P
    March 5th, 2019 at 15:44 | #10


    any update on resolution ?

  8. k3a
    March 8th, 2019 at 13:30 | #11

    @Muhammed Unais P
    Install virtio and QSL drivers from ISO/vfd/rpm from Fedora project https://docs.fedoraproject.org/en-US/quick-docs/creating-windows-virtual-machines-using-virtio-drivers/index.html (iso at the bottom of the page). Those drivers are libre and open-source (https://github.com/virtio-win/kvm-guest-drivers-windows) but Windows requires digitally signed drivers and signing it cost money which Fedora paid. Alternatively you can compile those drivers from the source code and disable driver signature verification in the Windows guest.

  9. Necktwi
    November 13th, 2019 at 16:02 | #12

    I’m using Gentoo, I did mount -t hugetlbfs hugetlbfs /dev/hugepages/ and then executed

    $ sudo qemu-system-x86_64 –enable-kvm -cpu host -m 2048 -smp 3 -mem-path /dev/hugepages -device qemu-xhci,id=xhci -device virtio-tablet,wheel-axis=true -soundhw hda -netdev user,id=vmnic,smb=/temp -device virtio-net,netdev=vmnic -drive file=/usr/share/edk2-ovmf/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on -drive file=$HOME/.config/qemu-windows.nvram,if=pflash,format=raw,unit=1 -drive file=/dev/sda,index=0,media=disk,driver=raw -cdrom /usr/share/edk2-ovmf/UefiShell.iso

    qemu-system-x86_64: unable to map backing store for guest RAM: Cannot allocate memory
    qemu-system-x86_64: falling back to regular RAM allocation.
    audio: Failed to create voice `adc’

    and the window displays guest has not initiated the display (yet).

  10. k3a
    November 15th, 2019 at 14:20 | #13

    It is not enough to just do the mount. Please read the relevant Arch wiki article for querying Hugepagesize and setting vm.nr_hugepages to /etc/sysctl.conf ( https://wiki.archlinux.org/index.php/KVM#Enabling_huge_pages ). For the beginning though, you can try running QEMU without hugepages. They are just a way to improve performance, not strictly necessary.

    You are missing -vga qxl -display sdl. Parameter vga says you want a graphics implementation emulated (important!), display specifies how to present the graphics in the host (in a SDL window; display is optional, not specifying it will use a default). Also better change smp to 4 or other miltiply of 2 (I’ve updated it in the article to -smp 4,sockets=1,cores=2,threads=2).

    As for network parameters, you can remove smb=/temp or change it to smb=/. I use it to forward /temp directory from my host (linux) to windows guest (as a network drive). It uses SAMBA server to create a network drive. I didn’t want to share the whole tree, so I forwarded just /temp.

  11. Ben
    April 30th, 2020 at 15:48 | #14

    Thanks for this article, had to tweak a bit but this helped point me in the right direction. Was considering whether I wanted to dual boot or simply run Windows in a VM and this gives me the flexibility to do both. For those curious, here is my modification that got it working while testing if possible from a Kubuntu 20.04 live disk (where bios.bin is OVMF.fd, reference flags in this readme https://github.com/tianocore/edk2/blob/master/OvmfPkg/README )

    sudo qemu-system-x86_64 –enable-kvm -cpu host -m 4096 \
    -smp 4,sockets=1,cores=2,threads=2 -mem-path /dev/hugepages \
    -vga qxl -serial mon:stdio \
    -rtc clock=host,base=localtime \
    -device virtio-tablet,wheel-axis=true \
    -drive file=/usr/share/OVMF/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on \
    -pflash /home/kubuntu/vm/bios.bin
    -drive file=/dev/nvme0n1,index=0,media=disk,driver=raw

    • k3a
      April 30th, 2020 at 16:05 | #15

      Glad it helped you the right direction. FYI: You may also find interesting that QEMU supports SPICE protocol. What that means is that you can run qemu VM on one PC, let it listen for SPICE connections and then on another PC use virt-viewer to connect to it. Such connection not only transfers video output and mouse/keyboard events but also the sound and USB over the network. You can attach a USB device on the second PC (where VM is not running) and then attach that USB remotely to the VM running on the first machine. And even hear that windows “device plug-in sound” on the second PC. How cool is that?! If that interests you more, see also SPICE user manual.

  12. chmedly
    April 4th, 2022 at 16:08 | #16

    Any way to set this up from the GUI (Virtual Machine Manager)? As much as I love really long terminal commands, it seems like this would be a bit easier in a Desktop style of operation. Especially when I only need to boot windows every few months…

    • k3a
      April 4th, 2022 at 17:34 | #17

      I don’t know. You can try something from here but GUI tools very often lack more advanced options. I also run Windows very rarely but I made windows.sh script and put the long command there. So I can now just write windows.sh [enter] and get it booting.

  1. No trackbacks yet.

deadly laser