Skip to content

Instantly share code, notes, and snippets.

@brccabral
Last active October 27, 2023 04:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brccabral/c31a8674b7c012b3dee16372ab7d3068 to your computer and use it in GitHub Desktop.
Save brccabral/c31a8674b7c012b3dee16372ab7d3068 to your computer and use it in GitHub Desktop.
Boot Windows from Linux using QEMU

Boot Windows from Linux using QEMU

Option 1: install from package repository

Installing virt-manager will install qemu as dependency.

sudo apt install virt-manager

I had to restart Ubuntu to get virtd privilegies.

Option 2: Compile QEMU

Download source code https://www.qemu.org/download/#source
Compile with just one target --target-list=x86_64-softmmu so you don'r need to compile other systems that we don't need, unless you really need them. There is an issue of using -net user that needs to be compiled with enable-slirp (install libslirp-dev). And need to set prefix as /usr, if not, it will install in /usr/local and will cause problems with libvirtd.

sudo apt install libslirp-dev
wget https://download.qemu.org/qemu-7.2.0.tar.xz
tar xvJf qemu-7.2.0.tar.xz
cd qemu-7.2.0
./configure --target-list=x86_64-softmmu --enable-slirp --prefix=/usr
make
sudo make install

Compile EDK2 - OVMF

https://github.com/tianocore/tianocore.github.io/wiki/Build-Instructions
https://github.com/tianocore/tianocore.github.io/wiki/Getting-Started-with-EDK-II

Install dependencies

sudo apt install build-essential uuid-dev iasl git nasm python-is-python3

https://github.com/tianocore/tianocore.github.io/wiki/Common-instructions

Compile BaseTools and setup project variables (it will add some variables to your ENV)

git clone https://github.com/tianocore/edk2
cd edk2
git submodule update --init
make -C BaseTools
source edksetup.sh

Edit the file edk2/Conf/target.txt with setting below. Will build OVMF module (boot system), release target (or DEBUG), for x64 and using gcc.

ACTIVE_PLATFORM       = OvmfPkg/OvmfPkgX64.dsc
TARGET                = RELEASE
TARGET_ARCH           = X64
TOOL_CHAIN_TAG        = GCC5

Run build to start building OVMF (it will build a couple dependent modules)

build

It will create our boot bin file /path/to/edk2/Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd

Create virtual RAID drive to trick QEMU in thinking we are accessing another hard drive

Find the /dev/X that matches your drive

sudo fdisk -l

In my case it is /dev/nvme0n1.
My Windows is in partition /dev/nvme0n1p3

Check if your Linux has modules loop and linear enabled (if there is no error message, you are good)

sudo modprobe loop
sudo modprobe linear

Create EFI metadata

dd if=/dev/zero of=$HOME/some/location/efi1 bs=1M count=100
dd if=/dev/zero of=$HOME/some/location/efi2 bs=1M count=1

Create loopback

sudo losetup -f $HOME/some/location/efi1
sudo losetup -f $HOME/some/location/efi2

Verify which loopX they were created

losetup -a

In my case ef1 is /dev/loop3 and ef2 is /dev/loop5
Install virtual RAID tool mdadm

sudo apt install mdadm

Put them in order = efi1 -> windows -> ef2

sudo mdadm --build --verbose /dev/md0 --chunk=512 --level=linear --raid-devices=3 /dev/loop3 /dev/nvme0n1p3 /dev/loop5

Some useful commands

sudo mdadm --stop /dev/md0
sudo mdadm --remove /dev/md0
cat /proc/mdstat

Check the sizes of ef1 and ef2

sudo fdisk -l ef*
--------------------------
Disk efi1: 100 MiB, 104857600 bytes, 204800 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk efi2: 1 MiB, 1048576 bytes, 2048 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

In this case ef1 is 204800 sectors and ef2 is 2048 sectors.

Use parted to create the partitions in the virtual RAID

sudo parted /dev/md0
(parted) unit s
(parted) mktable gpt
(parted) mkpart primary fat32 2048 204799    # depends on size of efi1 file (above with fdisk = 204800)
(parted) mkpart primary ntfs 204800 -2049    # depends on size of efi1 and efi2 files (above with fdisk = 2048)
(parted) set 1 boot on
(parted) set 1 esp on
(parted) set 2 msftdata on
(parted) name 1 EFI
(parted) name 2 Windows
(parted) quit

Format the EFI partition

sudo mkfs.msdos -F 32 -n EFI /dev/md0p1

We need to change the owner of md0

chown $USER:$USER /dev/md0

Boot QEMU using the OVMF compiled above and using a Windows ISO https://www.microsoft.com/en-us/software-download/windows11

qemu-system-x86_64 \
    -bios $HOME/path/to/edk2/Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd \
    -drive file=/dev/md0,media=disk,format=raw \
    -cpu host -enable-kvm -m 2G \
    -cdrom $HOME/Win11_22H2_English_x64v1.iso

It will start the Windows installation process, but press Shift+F10 to open a terminal.

Assign a letter to EFI volume

diskpart
DISKPART> list disk
DISKPART> select disk 0    # Select the disk
DISKPART> list volume      # Find EFI volume (partition) number
DISKPART> select volume 2  # Select EFI volume
DISKPART> assign letter=B  # Assign B: to EFI volume
DISKPART> exit

Back in the terminal, make Windows copy boot files in the EFI partition

bcdboot C:\Windows /s B: /f ALL

Update Ethernet driver

Run List Hardware to check your network adapters.

lshw -C network

In my case my ethernet was showing as generic. I had to go to Realtek website, download the driver and install.
https://www.realtek.com/en/component/zoo/category/network-interface-controllers-10-100-1000m-gigabit-ethernet-pci-express-software
https://www.realtek.com/en/component/zoo/advanced-search/72?Itemid=276

cd ~/Downloads
untar r8168-xxxx.tar.bz2
cd r8168-xxxx
sudo ./autorun.sh  # this will remove generic and install the driver
shutdown -r 0  # reboot

Create a bridge network with your ethernet

This doesn't work with WiFi because wifi protocols and cards are not designed to use bridge interfaces, this only works with Ethernet cable connections.
https://www.youtube.com/watch?v=6435eNKpyYw

But this is necessary for Linux VMs too.

# find your ethernet adapter (mine was enp7s0), and the IP it is using (192.168.x.x/y)
ip a
# disable your ethernet
ip link set enp7s0 down
# if you wait a few seconds the IP should be gone without this command
ip addr del 192.168.x.x/y dev enp7s0
# make sure systemd-networkd is stopped
systemctl status systemd-networkd
systemctl stop systemd-networkd
# create the bridge br0
ip link add name br0 type bridge
# make you ethernet bounded to the bridge
# the bridge will receive the IP from our gateway/router DHCP
ip link set enp7s0 master br0
# install some helper tools
apt install net-tools

Create systemd-networkd config files

# start the br0 device
cat <<EOF > /etc/systemd/network/br.netdev
[NetDev]
Name=br0
Kind=bridge
EOF
# bind br0 to the ethernet adapter
cat <<EOF > /etc/systemd/network/1-br0-bind.network
[Match]
Name=enp7s0

[Network]
Bridge=br0
EOF
# make the br0 get an IP from the gateway/router DHCP
cat <<EOF > /etc/systemd/network/2-br0-dhcp.network
[Match]
Name=br0

[Network]
DHCP=ipv4
EOF
# the systemd-networkd will create the bridge for us now
ip link delete br0
ip link set enp7s0 down
systemctl enable systemd-networkd
systemctl start systemd-networkd
# check if br0 got an IP and the ethernet has br0 as master
ip a

In Virt-Manager, create you VM, open the VM informations, select NIC (Network Interface Controller). In "Network source" select "Bridge device..." with name "br0".
Do this for all VMs that need external connection (Linux or Windows).

Virtio Windows drivers

But Windows need Virtio drivers to work.
Download the driver from Fedora. Download the ISO that contains the drivers.
https://docs.fedoraproject.org/en-US/quick-docs/creating-windows-virtual-machines-using-virtio-drivers/
https://github.com/virtio-win/virtio-win-pkg-scripts/blob/master/README.md

Add the ISO to a virtual CD-ROM device on the Windows VM.
https://linuxhint.com/install_virtio_drivers_kvm_qemu_windows_vm/
Now, you are ready to boot to Windows from inside Qemu.

Right click the start menu, select Device Manager. Go to the "Other devices", right click "Ethernet", select Update drivers, choose Browse local, select the D: (the CD-ROM), and search. Windows should install the correct driver.
Do the same for any other device that needs a driver. I had two "PCI device" and the "Displar adapter" that needed an update.
Restart the VM!

Old instructions

I could never make this work under WiFi, but I didn't know about the bridge or the Virtio drivers. I don't think I'll try to make it work in WiFi because now I know how to do it in Ethernet.

Get a random MAC address that needs to start with 52:54
https://wiki.archlinux.org/title/QEMU#Networking

printf -v macaddr "52:54:%02x:%02x:%02x:%02x" $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff )) $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff ))
echo $macaddr

Save it so that the DHCP gives the virtual machine the same IP every time. If you leave random, a new IP will be generated.

qemu-system-x86_64 \
    -bios $edk2/Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd \
    -drive file=/dev/md0,media=disk,format=raw \
    -cpu host \
    -enable-kvm \
    -m 6G \
    $@


#   -drive format=raw,file=/dev/nvme0n1p3 \
# sudo qemu-system-x86_64 \
    -bios $edk2/Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd \ 
    -enable-kvm \
    -cpu host \
    -smp 8 \
    -m 6G \
    -hda /dev/nvme0n1p3 \
    $@

Run QEMU using script below run_qemu

#!/bin/bash
guest=windows
bridge=${guest}br0
dir=${HOME}/QEMU
#printf -v MACADDR "52:54:%02x:%02x:%02x:%02x" $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff )) $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff ))
MACADDR=52:54:94:c6:c2:e5
partition=/dev/nvme0n1p3
HASBR0=$(ip link show | grep ${bridge} || true)
if [ -z "$HASBR0" ]; then
NIC=$(ip link show | grep wlp | grep 'state UP' | head -n 1 | cut -d":" -f 2 | xargs)
ROUTER=$(ip route show dev $NIC | grep "via" | cut -d ' ' -f3)
IPADDR=$(ip route show dev $NIC | grep "/" | cut -d ' ' -f1)
sudo ip link add name ${bridge} type bridge
sudo ip link set dev ${bridge} up
sudo ip link set $NIC master ${bridge}
sudo ip addr add $IPADDR dev ${bridge}
sudo ip route append default via $ROUTER dev ${bridge}
sudo ip link set $NIC master ${bridge}
sudo ip address del $IPADDR dev $NIC
fi
#!/bin/bash
# https://wiki.archlinux.org/title/QEMU#Networking
# https://wiki.qemu.org/Documentation/Networking
set -x -e
guest=windows
bridge=${guest}br0
# bridge=bridge0
dir=${HOME}/QEMU
#printf -v MACADDR "52:54:%02x:%02x:%02x:%02x" $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff )) $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff ))
MACADDR=52:54:94:c6:c2:e5
partition=/dev/nvme0n1p3
_nicbr0() {
sudo ip link set $1 promisc off down &> /dev/null
sudo ip link set dev $1 nomaster &> /dev/null
}
HASBR0=$(ip link show | grep ${bridge} || true)
USERID=$(whoami)
precreationg=$(ip tuntap list | cut -d: -f1 | sort)
sudo ip tuntap add user $USERID mode tap
postcreation=$(ip tuntap list | cut -d: -f1 | sort)
TAP=$(comm -13 <(echo "$precreationg") <(echo "$postcreation"))
_nicbr0 $TAP
sudo ip link set dev $TAP down &> /dev/null
sudo ip tuntap del $TAP mode tap
if [ -f "/tmp/qemu-${guest}.pid" ]; then
sudo rm /tmp/qemu-${guest}.pid
fi
if [ -z "$HASBR0" ] ; then
_nicbr0 $NIC
sudo ip addr del dev ${bridge} $IPADDR/24 &> /dev/null
sudo ip link set dev ${bridge} down
sudo ip link delete ${bridge} type bridge &> /dev/null
sudo ip route del default &> /dev/null
sudo ip link set dev $NIC up
sudo ip route add default via $ROUTER dev $NIC onlink &> /dev/null
fi
#!/bin/bash
# https://wiki.archlinux.org/title/QEMU#Networking
# https://wiki.qemu.org/Documentation/Networking
set -x -e
guest=windows
netid=${guest}nic
bridge=${guest}br0
dir=${HOME}/QEMU
#printf -v MACADDR "52:54:%02x:%02x:%02x:%02x" $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff )) $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff ))
MACADDR=52:54:94:c6:c2:e5
partition=/dev/nvme0n1p3
### Disk config ###
# setup virtual RAID disk
efi1lo=$(losetup -la | grep efi1 | awk '{ print $1 }')
if [ -z "${efi1lo}" ]; then
efi1lo=$(sudo losetup -f ${dir}/efi1 --show)
fi
efi2lo=$(losetup -la | grep efi2 | awk '{ print $1 }')
if [ -z "${efi2lo}" ]; then
efi2lo=$(sudo losetup -f ${dir}/efi2 --show)
fi
if [ -e "/dev/md0" ]; then
sudo mdadm --manage /dev/md0 --stop
fi
sudo mdadm --build --verbose /dev/md0 --chunk=512 --level=linear --raid-devices=3 $efi1lo $partition $efi2lo
sleep 1
sudo chown $USER:$USER /dev/md0
#!/bin/bash
if [ -e "/dev/md0" ]; then
sudo mdadm --manage /dev/md0 --stop
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment