I try to avoid working with Windows as much as possible, but let’s face it —sometimes there are no other options, mainly because some tools are only available on Windows. For instance, in the automotive realm, there are several tools that don't give us any other choice. However, I refuse to go fully Windows, and working with a dual boot setup is just too uncomfortable. Yes, I know we can use WSL, but it has its limitations, and I don't want to compromise my workflow in Linux. My solution is virtualization, and for good virtualization, there's no better option than KVM and QEMU on Linux.

💡
I’m assuming you have a fresh installation of Arch Linux or any other distro, it is not a requisite, just saying because maybe you already have some packages installed

Install QEMU and Virt Manager

We need to verify if our CPU supports virtualization or if it's enabled. You can use the following command. Here, you can see that virtualization is enabled on my machine, and I’m using an AMD processor. For Intel processors, you should see 'VT-x' displayed. If nothing appears, then you may need to enable virtualization in the BIOS, or perhaps your CPU doesn't support virtualization

$ lscpu | grep "Virtualization"                              
Virtualization:                       AMD-V

Virtualization magic in Linux is supported by KVM, which stands for Kernel-based Virtual Machine. It is already included in the Linux kernel (version 2.6.0 and later), so you don’t need to install anything. However, we need to install a program to emulate the desired hardware, and that program is QEMU, along with some additional utilities, which I will describe below:

  • dnsmasq: A lightweight DNS and DHCP server with support for DHCPv6, PXE, and a TFTP server.
  • vde2: A set of programs to provide virtual, software-defined Ethernet network interface controllers (NICs) across multiple devices.
  • bridge-utils: Utilities needed to create and manage bridge devices for setting up networks for hosted virtual machines.
  • ebtables and iptables: Utilities used to set up tables of rules (inside the Linux kernel) that inspect Ethernet frames.
  • libguestfs: A set of tools for accessing and modifying virtual machine (VM) disk images.
  • ovmf: Enables UEFI support for virtual machines.
  • swtpm: Software emulation of cryptographic keys. This is essential for running Windows 11.
$ sudo pacman -S qemu-base virt-viewer vde2 ebtables iptables-nft nftables dnsmasq  bridge-utils ovmf swtpm libguestfs-tools

In case you need or prefer a graphical user interface you should also install Virt Manager. As usual I’m personally prefer command line options for later automation

$ sudo pacman -S virt-manager

Now, we verify that the corresponding kernel modules are loaded with the lsmod command

$ lsmod | grep kvm              
kvm_amd               208896  0
kvm                  1368064  1 kvm_amd
ccp                   180224  1 kvm_amd

After the Installation time to do some configuration, open the file libvirtd.conf with your favorite editor with root privileges

$ sudo nano /etc/libvirt/libvirtd.conf

Inside the file you need to uncomment the following two lines, to make libvirt group available for your user, and avoid typing sudo

...
# This is restricted to 'root' by default.
unix_sock_group = "libvirt"
...

# If not using PolicyKit and setting group ownership for access
# control, then you may want to relax this too.
unix_sock_rw_perms = "0770"

Then add your local user to group libvirt and kvm

$ sudo usermod -aG kvm,libvirt $USER

Make libvirt to start when you boot your computer and initiate the service daemon, the last command is just to verify if the service is active

$ sudo systemctl enable libvirtd
$ sudo systemctl start libvirtd
$ sudo systemctl status libvirtd
○ libvirtd.service - libvirt legacy monolithic daemon
     Loaded: loaded (/usr/lib/systemd/system/libvirtd.service; enabled; preset:>
     Active: inactive (dead) since Fri 2024-1
     ...

Open the file /etc/libvirt/qemu.conf with your preferred editor using root privileges. Locate the user and group lines, uncomment both, and replace <username> with your Linux username. This will eliminate the need to launch your virtual machines using sudo.

# Some examples of valid values are:
# 
#       user = "qemu"   # A user named "qemu"
#       user = "+0"     # Super user (uid=0)
#       user = "100"    # A user named "100" or a user with uid=100
# 
user = "<useranme>"

# The group for QEMU processes run by the system instance. It can be
# specified in a similar way to user.
group = "<username>"

Restart the libvirt daemon to apply the changes we made to qmeu.conf file

$ sudo systemctl restart libvirtd

Verify the default network is enable, persistent and auto start is also active we need the network to be active when the computer start to avoid start manually every time

$ virsh -c qemu:///system net-list --all
 Name      State    Autostart   Persistent
--------------------------------------------
 default   active   yes         yes

I add the -c qemu:///system option to manage all my VM resources at the system level rather than the user level. This means that all my network configurations and disk images are stored in the /var/lib/libvirt directory, while the XML configuration files are located in /etc/libvirt/qemu. Alternatively, if you don't use this option, the resources would be stored under ~/.config/libvirt.

In case none of the last option are active use virsh net-autostart command

$ virsh -c qemu:///system net-autostart default 
Network default marked as autostarted

Execute the following command to verify if the nested virtualization is active by default. If you get the output like "Y" or "1", it means the feature nested virtualization is enabled. Otherwise, you will see the error message as "No such file or directory".

$ cat /sys/module/kvm_amd/parameters/nested
1

In case not, enable virtualization inside our virtual machines in some kind of inception mode, this is useful in case you want to run WSL or docker inside you virtual windows machine. Note use kvm_intel instead of kvm_amd on the line below in case you are not using an AMD processor

$ sudo modprobe -r kvm_amd
$ sudo modprobe kvm_amd nested=1

Installing Windows 11

Got to the official windows portal and download the latest Windows 11 ISO and then go to https://github.com/virtio-win/virtio-win-pkg-scripts and download the Latest virtio-win ISO. Copy both images we download to a more suitable directory

$ sudo mkdir /var/lib/libvirt/images/iso
$ sudo mv Downloads/Win11_24H2_English_x64.iso /var/lib/libvirt/images/iso
$ sudo mv Downloads/virtio-win-0.1.266.iso /var/lib/libvirt/images/iso

Then proceed to the installation process with the command below. keeping in mid you have to set the memory and cores according to your own host machine resources. The most important options for you to congit are name, ram and vcpus, there is another you should pay attention and is the diks size for you virtual machine, we are setting 80Gigs with disk size option. The last line --enable-kvm --cpu enable the enlightenment options to improve virtualization inside the Windows virtual machine, if you are using and Intel processor append also hv_evmcsat the end

virt-install \
--connect=qemu:///system \
--name=Windows11 \
--ram=12288 \
--vcpus=8 \
--os-variant=win11 \
--disk size=80,cache=none,bus=virtio \
--disk path=/var/lib/libvirt/images/iso/virtio-win-0.1.266.iso,device=cdrom \
--cdrom /var/lib/libvirt/images/iso/Win11_24H2_English_x64.iso \
--video qxl \
--sound default \
--graphics spice,listen=0.0.0.0 \
--tpm backend.type=emulator,backend.version=2.0,model=tpm-crb \
--network network=default,model=virtio \
--cpu host \
--features kvm_hidden=on,smm=on hv_relaxed,hv_vapic,hv_spinlocks="8191",hv_vpindex,hv_runtime,hv_synic \
--features hv_time,hv_stimer,hv_reset,hv_vendor_id="KVM Hv",hv_frequencies,hv-reenlightenment,hv-tlbflush,hv-ipi

From here, simply follow the usual Windows installation steps. Watch the following video (since I'm not in the mood to make my own) from minute 6:21 to 7:10 to learn how to mount the VirtIO drivers: https://www.youtube.com/watch?v=WmFpwpW6Xko&t=659s . At some point during the installation process, you'll be prompted to install the network drivers. You can find them in the CD drive (E:) under the path virtio-win-0.1.266 → NetKVM → w11 → amd64.

After the installation, it is strongly recommended to debloat your Windows installation using the following script: https://github.com/Raphire/Win11Debloat , and disable automatic updates.

Additionally, you need to install some guest tools that are included with the virtio-win-0.1.266.iso image, which should still be mounted in your virtual machine. To install the guest additions:

  1. Navigate to the CD drive (E:) virtio-win-0.1.266.
  2. Double-click virtio-win-guest-tools.exe to install the guest additions.

This will enable copy-and-paste functionality between the host and guest, as well as automatic window resizing for your virtual machine.

If you want to run your Windows virtual machine again, you need to use the virsh start command first and then use virt-manager or virt-viewer to view your running virtual machine.

$ virsh -c qemu:///system start Windows11         
Domain 'Windows11-2' started
$ virt-viewer Windows11 &

Sharing drives

To make this a real hybrid workflow, we need to make a few improvements, such as sharing a folder where all projects are located. In my case, I always use a folder called Workspace in my user root directory.

Using virt-xml, we are going to add some new parameters to our recent virtual machine. First, we’ll enable the shared memory option.

$ virt-xml Windows11 --edit --memorybacking source.type=memfd,access.mode=shared
Domain 'Windows11' defined successfully.

Next, we set the directory to be shared with the option source.dir=/home/<username>/Workspace. Just replace <username> with your actual Linux username.

The target.dir option is where you specify the name that Windows will recognize for the shared folder. In my case, I decided to name it linux. You can choose any name you like for the shared folder in Windows, but ensure it's easy to remember and access.

$ virt-xml Windows11 --add-device --filesystem driver.type=virtiofs,source.dir=/home/diego/Workspace,target.dir=linux  
Domain 'Windows11' defined successfully.

The previous instructions can also be applied when creating the virtual machine with virt-install by simply adding the --filesystem and --memorybacking options to the virt-install command.

--filesystem driver.type=virtiofs,source.dir=/home/diego/Workspace,target.dir=linux \
--memorybacking source.type=memfd,access.mode=shared

On your Windows machine, we need to set up a few things. First, download the latest version of WinFSP from here, and then configure some options in Windows Services. While I could write detailed instructions and share images, it’s easier to follow the next video from the "My Linux For Work" YouTube channel (I recommend his channel if you’re into Arch and Hyperland). You can find the relevant information from minute 5:28 to 6:00 in the video: https://www.youtube.com/watch?v=oVHkvx9ZLJc&t=284s

The VirtIO disk is no longer required for our installation. To remove the entire unit containing the VirtIO image from your virtual machine, simply type the following command

$ virt-xml Windows11 --remove-device --disk 2
Domain 'Windows11' defined successfully.

To remove the Windows ISO image from our cd-rom unit you can type

$ virt-xml Windows11 --edit target=sdb --disk path=

If you wanna insert a cd-rom content

$ virt-xml Windows11 --edit target=sdb --disk path=/path/to/your/cdrom/image

USB passthrough

As embedded engineers, we need USB passthrough, as some Windows tools require communication with external hardware. To passthrough a USB device, type lsusb to list the connected USB devices. You can see that I have attached a Nucleo board with an onboard ST-Link V2.

$ lsusb
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 002: ID 3434:0213 Keychron Keychron K1 Pro
Bus 003 Device 003: ID 8087:0029 Intel Corp. AX200 Bluetooth
Bus 003 Device 004: ID 0483:374b STMicroelectronics ST-LINK/V2.1

Using the information from lsub we use virt-xml to add our device with --host 003.004

$ virt-xml Windows11 --add-device --update --hostdev 003.004                 
Device hotplug successful.
Domain 'Windows11' defined successfully.

Go to Device manager on your windows and verify out device has been passthrough

If we want to disconnect the same device form our virtual machine , just type

$ virt-xml Windows11 --remove-device --update --hostdev 003.004   
Device hotunplug successful.
Domain 'Windows11' defined successfully.

Windows as a linux app

This is the ultimate integration, we are going to make our windows to be super integrated as if it was one linux app using a remote desktop protocol called XRDP. In linux we can use freeRDP to do XRDP connections with our guest machine with our guest machine

$ sudo pacman -S freerdp

Get the Windows 11 IP address using the command , virsh net-dhcp-leases <network>, remember we are using a network called default

$ virsh -c qemu:///system net-dhcp-leases default
 Expiry Time           MAC address         Protocol   IP address          Hostname   Client ID or DUID
-----------------------------------------------------------------------------------------------------------
 2024-11-29 13:29:18   52:54:00:b7:85:05   ipv4       192.168.122.19/24   Windows    01:52:54:00:b7:85:05

or you can also use domifaddr <vm machine>

$ virsh -c qemu:///system domifaddr Windows11 
 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------
 vnet1      52:54:00:b7:85:05    ipv4         192.168.122.19/24

Find what kind of display server are you using

$ echo $XDG_SESSION_TYPE
wayland

It's time to connect to your virtual machine. If your display server is Xorg, use the command xfreerdp, but if you're using Wayland, use wlfreerdp. The line below shows the options you should use, replacing <ip_address> with the IP address of your Windows virtual machine, <username> with your Windows username, and <password> with your Windows password:

$ wlfreerdp -grab-keyboard /v:<ip address> /u:<username> /p:<password> /size:100% /d: /dynamic-resolution /gfx-h264:avc444 +gfx-progressive 

That was a long line, but you can write in your .bashrc file a function to launch you windows machine with a single command, we decide type pass the password as a parameter to avoid hard code our super secret password

windows()
{
    virsh -c qemu:///system start Windows11
    sleep 30
    xfreerdp -grab-keyboard /v:192.168.122.19 /u:diego /p:$1 /size:100% /d: /dynamic-resolution /gfx-h264:avc444 +gfx-progressive 
}

The you can use you newly function as, where ***** will be your password

$ windows ***** 

Extend the windows disk size

In case you want to modify your virtual machine disk space because you are running out of space. Identify Windows machine disk image and its path

$ virsh -c qemu:///system domblklist Windows11 
 Target   Source
------------------------------------------------------------------
 sda      /var/lib/libvirt/images/iso/Win11_24H2_English_x64.iso
 sdb      /var/lib/libvirt/images/iso/virtio-win-0.1.266.iso
 vda      /var/lib/libvirt/images/Windows11.qcow2

Indicate how many Gigabytes you would like to increase using qemu-img, in this example I’m adding just ten more gigs

$ sudo qemu-img resize /var/lib/libvirt/images/Windows11.qcow2 +10G
Image resized

On your Windows machine, go to the Disk Management application. You will see the newly added disk space listed as Unallocated. Now, proceed to assign this empty space to your C: drive. If you're unsure how to do this, you can watch this video for a step-by-step guide: https://www.youtube.com/watch?v=5jvpSv-WSc0

Delete the virtual machine

If for some reason you don’t want the virtual machine anymore, the remove a virtual machine and its storage drive

$ virsh undefine Windows11 --nvram --remove-all-storage
Domain 'Windows11' has been undefined
Volume 'vda'(/var/lib/libvirt/images/Windows11.qcow2) removed.

If you are really interested in deep on Virtual Machines here is a couple of Links

Virtualization Guide
Learn all about virtualization and VM management
Ubuntu Manpage: virt-install - provision new virtual machines