Run MacOS in Linux with Qemu on secondary GPU

all for operating system
Post Reply
User avatar
david
Site Admin
Posts: 394
Joined: Sat May 21, 2016 7:50 pm

Run MacOS in Linux with Qemu on secondary GPU

Post by david »

How to run MacOS in Linux on secondary GPU using Qemu as emulator and native GPU passed to emulated VM:

Image

Join our telegram group if you wana chat or have specific questions:
https://t.me/+h2K5CX5jEZA0MWJk


PC test hardware is:
CPU - Ryzen 5950 16 core 32T
Ram - 128GB Ram Kingston 4x32GB 3400Mhz CL16
MB - Asus PRIME X570-PRO
PSU - 850W Seasonic

Radeon 560 connected to native linux OS and Radeon 7950 gpu`s passed to the MacOS.
Image

Asus PRIME X570-PRO support 2 x PCIe 4.0 x16 (x16 or dual x8) runing native Devuan (Debian with no SystemD) linux!
You can install qemu on almost any linux distro here in this example i use Devuan linux and qemu 7.2.
Image


Here how lspci -nnk command looks on my PC make sure you put your secondary GPU into vfio mode:

Code: Select all

09:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Tahiti PRO [Radeon HD 7950/8950 OEM / R9 280] [1002:679a]
	Subsystem: Gigabyte Technology Co., Ltd Tahiti PRO [Radeon HD 7950/8950 OEM / R9 280] [1458:254c]
	Kernel driver in use: vfio-pci
	Kernel modules: radeon, amdgpu
09:00.1 Audio device [0403]: Advanced Micro Devices, Inc. [AMD/ATI] Tahiti HDMI Audio [Radeon HD 7870 XT / 7950/7970] [1002:aaa0]
	Subsystem: Gigabyte Technology Co., Ltd Tahiti HDMI Audio [Radeon HD 7870 XT / 7950/7970] [1458:aaa0]
	Kernel driver in use: vfio-pci
	Kernel modules: snd_hda_intel
0a:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Baffin [Radeon RX 550 640SP / RX 560/560X] [1002:67ff] (rev cf)
	Subsystem: Micro-Star International Co., Ltd. [MSI] Baffin [Radeon RX 550 640SP / RX 560/560X] [1462:8a91]
	Kernel driver in use: amdgpu
	Kernel modules: amdgpu
0a:00.1 Audio device [0403]: Advanced Micro Devices, Inc. [AMD/ATI] Baffin HDMI/DP Audio [Radeon RX 550 640SP / RX 560/560X] [1002:aae0]
	Subsystem: Micro-Star International Co., Ltd. [MSI] Baffin HDMI/DP Audio [Radeon RX 550 640SP / RX 560/560X] [1462:aae0]
	Kernel driver in use: snd_hda_intel
	Kernel modules: snd_hda_intel
Qemu conig:

Code: Select all

qemu-system-x86_64 \
  -enable-kvm \
  -machine q35,accel=kvm \
  -cpu Haswell,kvm=on,vendor=GenuineIntel,+kvm_pv_unhalt,+kvm_pv_eoi,+hypervisor,+invtsc,+ssse3,+sse4.2,+popcnt,+avx,+avx2,+aes,+fma,+bmi1,+bmi2,+xsave,+xsaveopt,check \
  -smp sockets=1,cores=4 \
  -m 16380 \
  -smbios type=2 \
  -drive if=pflash,format=raw,readonly=on,file=OVMF_CODE.fd \
  -drive if=pflash,format=raw,file=OVMF_VARS-1920x1080.fd \
  -device isa-applesmc,osk="ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc" \
  -global ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off \
  -device ich9-ahci,id=sata \
  -drive id=MacHDD,if=none,file=mac_hdd.img,format=qcow2 \
  -device ide-hd,bus=sata.1,drive=MacHDD \
  -vga none \
  -netdev user,id=net0,hostfwd=tcp::2222-:22 -device virtio-net-pci,netdev=net0,id=net0,mac=52:54:00:c9:18:27 \
  -device nec-usb-xhci,id=xhci \
  -device usb-host,vendorid=0x248a,productid=0x00da,bus=xhci.0,port=1 \
  -device usb-host,vendorid=0x046d,productid=0xc52f,bus=xhci.0,port=2 \
  -device usb-host,vendorid=0x04ca,productid=0x007d,bus=xhci.0,port=3 \
  -device usb-host,vendorid=0x1058,productid=0x0730,bus=xhci.0,port=4 \
  -device nec-usb-xhci,id=xhci1 \
  -device usb-host,vendorid=0x0b05,productid=0x17cb,bus=xhci1.0,port=1 \
  -device pcie-root-port,id=pcie1,slot=1,chassis=1 \
  -device vfio-pci,host=0c:00.0,bus=pcie1,addr=0x0,multifunction=on,x-no-kvm-intx=on,x-vga=on \
  -device vfio-pci,host=0c:00.1,bus=pcie1,addr=0x1
  

Radeon 7950 GPU is 2012 GPU can work in MACOS monterey very good.

To download ready to use Qemu virtual hdd file "mac_hdd_ng.img" enter our telegram group:
https://t.me/+h2K5CX5jEZA0MWJk


Video demonstration of MacOS Monterey running in Qemu.


youtu.be/QVDMa7MuiGg


User avatar
david
Site Admin
Posts: 394
Joined: Sat May 21, 2016 7:50 pm

Re: Run MacOS in Linux with Qemu on secondary GPU

Post by david »

Before run the scripts make sure you have:

Code: Select all

sudo apt update
sudo apt install qemu-system-x86 qemu-kvm ovmf bridge-utils cpu-checker

Code: Select all

sudo nano /etc/modules

vfio
vfio_pci
vfio_iommu_type1
vfio_virqfd
kvm
kvm_intel    # or kvm_amd depending on your CPU

and 

sudo nano /etc/initramfs-tools/modules

vfio
vfio_pci
vfio_iommu_type1
vfio_virqfd
kvm
kvm_intel    # If Intel CPU
# kvm_amd     # If AMD CPU (uncomment this and comment out the Intel one if needed)


and then

Code: Select all

sudo update-initramfs -u

Code: Select all

sudo nano /etc/default/grub

Code: Select all

GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt"

Code: Select all

update-grub2

After that you reboot and run first script to check is all is ok if ok then continue till steap 3.

Python Scripts for GPU pass (Devuan linux 5.0) easy way :

1.gpu_pass_check_step1.py

Code: Select all

#!/usr/bin/env python3

import os
import subprocess
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

class GPUPassApp(Gtk.Window):
    def __init__(self):
        super().__init__(title="Devuan GPU Passthrough Checker")

        self.set_border_width(15)
        self.set_default_size(500, 300)

        self.grid = Gtk.Grid(column_spacing=10, row_spacing=10)
        self.add(self.grid)

        self.status_label = Gtk.Label(label="Running checks...\n")
        self.grid.attach(self.status_label, 0, 0, 1, 1)

        self.refresh_button = Gtk.Button(label="Re-run Checks")
        self.refresh_button.connect("clicked", self.run_all_checks)
        self.grid.attach(self.refresh_button, 0, 1, 1, 1)

        self.run_all_checks()

    def run_all_checks(self, widget=None):
        results = []

        # Root check
        if os.geteuid() != 0:
            self.status_label.set_text("This app must be run as root.\n")
            return

        # Check QEMU
        qemu_check = shutil.which("qemu-system-x86_64")
        results.append(f"QEMU: {'✅ Found' if qemu_check else '❌ Not found'}")

        # Check if vfio-pci is loaded
        vfio_loaded = subprocess.getoutput("lsmod | grep '^vfio_pci'")
        results.append(f"VFIO-PCI module: {'✅ Loaded' if vfio_loaded else '❌ Not loaded'}")

        # Check GRUB cmdline
        try:
            with open("/etc/default/grub", "r") as f:
                grub = f.read()
            if "iommu=pt" in grub or "amd_iommu=on" in grub or "intel_iommu=on" in grub:
                results.append("IOMMU GRUB param: ✅ Present")
            else:
                results.append("IOMMU GRUB param: ❌ Missing")
        except Exception as e:
            results.append(f"GRUB check: ❌ Error - {e}")

        # Check initramfs modules file
        try:
            with open("/etc/initramfs-tools/modules", "r") as f:
                modules = f.read()
            needed = ["vfio", "vfio_pci", "vfio_iommu_type1"]
            for mod in needed:
                status = "✅" if mod in modules else "❌"
                results.append(f"/etc/initramfs-tools/modules: {status} {mod}")
        except Exception as e:
            results.append(f"Initramfs modules check: ❌ Error - {e}")

        self.status_label.set_text("\n".join(results))

if __name__ == "__main__":
    import shutil
    win = GPUPassApp()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()
2.gpu_pass_check_step2.py

Code: Select all

#!/usr/bin/env python3

import os
import subprocess
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

class GPUSelector(Gtk.Window):
    def __init__(self):
        super().__init__(title="GPU Passthrough - Select GPU")
        self.set_border_width(15)
        self.set_default_size(700, 300)

        self.grid = Gtk.Grid(column_spacing=10, row_spacing=10)
        self.add(self.grid)

        self.label = Gtk.Label(label="Select a GPU for passthrough (audio will auto-select):")
        self.label.set_xalign(0)
        self.grid.attach(self.label, 0, 0, 2, 1)

        self.gpu_liststore = Gtk.ListStore(str, str)
        self.combo = Gtk.ComboBox.new_with_model_and_entry(self.gpu_liststore)
        self.combo.set_entry_text_column(1)
        self.grid.attach(self.combo, 0, 1, 2, 1)

        self.populate_gpus()

        self.apply_button = Gtk.Button(label="Save Selection")
        self.apply_button.connect("clicked", self.save_selection)
        self.grid.attach(self.apply_button, 0, 2, 1, 1)

        self.status_label = Gtk.Label()
        self.status_label.set_xalign(0)
        self.grid.attach(self.status_label, 0, 3, 2, 1)

    def populate_gpus(self):
        lspci_output = subprocess.check_output("lspci -nnk", shell=True, text=True)
        lines = lspci_output.strip().splitlines()

        current_device = {}
        devices = []

        for line in lines:
            if line and not line.startswith("\t") and not line.startswith(" "):
                # New device block
                if current_device:
                    devices.append(current_device)
                current_device = {"header": line, "details": []}
            else:
                current_device.setdefault("details", []).append(line.strip())

        if current_device:
            devices.append(current_device)

        for dev in devices:
            header = dev.get("header", "")
            details = dev.get("details", [])

            if not any(x in header for x in ["VGA compatible controller", "3D controller"]):
                continue

            if any("Kernel driver in use: vfio-pci" in d for d in details):
                continue  # already passed through

            pci_addr = header.split()[0]
            description = header

            self.gpu_liststore.append([pci_addr, description])

    def save_selection(self, widget):
        tree_iter = self.combo.get_active_iter()
        if not tree_iter:
            self.status_label.set_text("❌ No GPU selected.")
            return

        gpu_pci = self.gpu_liststore[tree_iter][0]
        gpu_slot = gpu_pci[:-1] + "0"
        audio_slot = gpu_pci[:-1] + "1"

        def get_pci_id(dev):
            path = f"/sys/bus/pci/devices/0000:{dev}"
            try:
                with open(f"{path}/vendor") as v, open(f"{path}/device") as d:
                    return f"{v.read().strip()[2:]}:{d.read().strip()[2:]}"
            except Exception:
                return None

        ids = []
        for dev in [gpu_slot, audio_slot]:
            pci_id = get_pci_id(dev)
            if pci_id:
                ids.append(pci_id)

        if not ids:
            self.status_label.set_text("❌ Could not detect device IDs.")
            return

        with open("/tmp/vfio-gpu-select.txt", "w") as f:
            for line in ids:
                f.write(line + "\n")

        self.status_label.set_text(f"✅ Saved IDs: {', '.join(ids)}")

if __name__ == "__main__":
    win = GPUSelector()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()
3.gpu_pass_check_step3.py

Code: Select all

#!/usr/bin/env python3

import os
import sys
import subprocess

VFIO_CONF = "/etc/modprobe.d/vfio.conf"
BLACKLIST_NOUVEAU = "/etc/modprobe.d/blacklist-nouveau.conf"
BLACKLIST_SND = "/etc/modprobe.d/blacklist-nvidia-hda.conf"
GRUB_CONFIG = "/etc/default/grub"
SELECTED_IDS_FILE = "/tmp/vfio-gpu-select.txt"

def read_selected_ids():
    if not os.path.isfile(SELECTED_IDS_FILE):
        print("No selected PCI IDs found. Please run GPU selection first.")
        sys.exit(1)
    with open(SELECTED_IDS_FILE) as f:
        ids = [line.strip() for line in f if line.strip()]
    return ids

def update_vfio_conf(ids):
    line = "options vfio-pci ids=" + ",".join(ids) + "\n"
    with open(VFIO_CONF, "w") as f:
        f.write("# Auto-generated vfio config for GPU passthrough\n")
        f.write(line)
    print(f"Updated {VFIO_CONF}")

def update_blacklists(ids):
    # Blacklist nouveau for GPU PCI IDs
    with open(BLACKLIST_NOUVEAU, "w") as f:
        f.write("# Auto-generated blacklist for nouveau on selected GPUs\n")
        for pci_id in ids:
            f.write(f"blacklist pci:v0000{pci_id[0:4]}d0000{pci_id[5:9]}*\n")

    # Blacklist snd_hda_intel only for audio PCI IDs (usually second ID)
    with open(BLACKLIST_SND, "w") as f:
        f.write("# Auto-generated blacklist for snd_hda_intel on NVIDIA HDMI audio\n")
        # Assume last id is audio device (works for typical GPU+audio pairs)
        for pci_id in ids[1:]:
            f.write(f"blacklist pci:v0000{pci_id[0:4]}d0000{pci_id[5:9]}*\n")

    print(f"Updated blacklist files: {BLACKLIST_NOUVEAU}, {BLACKLIST_SND}")

def update_grub(ids):
    # Read current GRUB config
    with open(GRUB_CONFIG, "r") as f:
        lines = f.readlines()

    new_lines = []
    for line in lines:
        if line.startswith("GRUB_CMDLINE_LINUX="):
            # Remove existing vfio-pci.ids param if any
            line = line.strip()
            if "vfio-pci.ids=" in line:
                # Remove previous vfio-pci.ids=... segment
                import re
                line = re.sub(r'vfio-pci.ids=[^"\s]*', '', line)
                # Remove any doubled spaces left behind
                line = line.replace("  ", " ")
            # Insert new vfio-pci.ids param before the closing quote
            line = line.rstrip('"') + f" vfio-pci.ids={','.join(ids)}\""
            new_lines.append(line + "\n")
        else:
            new_lines.append(line)

    with open(GRUB_CONFIG, "w") as f:
        f.writelines(new_lines)

    # Update grub
    subprocess.run(["update-grub"], check=True)
    print("Updated GRUB configuration and ran update-grub.")

def main():
    ids = read_selected_ids()
    print(f"Selected PCI IDs: {ids}")

    update_vfio_conf(ids)
    update_blacklists(ids)
    update_grub(ids)

    print("\n✅ Configuration applied! Please reboot your system to enable GPU passthrough.")

if __name__ == "__main__":
    if os.geteuid() != 0:
        print("This script must be run as root.")
        sys.exit(1)
    main()
4.gpu_pass_cleanup.sh

Code: Select all

#!/bin/bash
# gpu-pass cleanup script - Part 4
# Must be run as root

set -e

VFIO_CONF="/etc/modprobe.d/vfio.conf"
BLACKLIST_NOUVEAU="/etc/modprobe.d/blacklist-nouveau.conf"
BLACKLIST_SND="/etc/modprobe.d/blacklist-nvidia-hda.conf"
GRUB_CONFIG="/etc/default/grub"

echo "[*] Cleaning GPU passthrough configuration..."

# Remove vfio.conf file
if [ -f "$VFIO_CONF" ]; then
    rm -f "$VFIO_CONF"
    echo "Removed $VFIO_CONF"
else
    echo "$VFIO_CONF not found, skipping"
fi

# Remove blacklist files
for file in "$BLACKLIST_NOUVEAU" "$BLACKLIST_SND"; do
    if [ -f "$file" ]; then
        rm -f "$file"
        echo "Removed $file"
    else
        echo "$file not found, skipping"
    fi
done

# Remove vfio-pci.ids=... from GRUB_CMDLINE_LINUX in /etc/default/grub
if grep -q "vfio-pci.ids=" "$GRUB_CONFIG"; then
    sed -i 's/vfio-pci.ids=[^" ]*//g' "$GRUB_CONFIG"
    # Clean double spaces that might occur after removal
    sed -i 's/  / /g' "$GRUB_CONFIG"
    echo "Removed vfio-pci.ids from $GRUB_CONFIG"
else
    echo "No vfio-pci.ids found in $GRUB_CONFIG, skipping"
fi

# Update grub
echo "[*] Updating grub configuration..."
update-grub

echo "[*] Cleanup done. Rebooting system in 5 seconds..."
sleep 5
reboot


User avatar
david
Site Admin
Posts: 394
Joined: Sat May 21, 2016 7:50 pm

Re: Run MacOS in Linux with Qemu on secondary GPU

Post by david »

Head Less Qemu miniaml configuration for autostart MacOS monterey with Linux boot.

Code: Select all

#!/bin/bash

exec qemu-system-x86_64 \
  -enable-kvm \
  -machine q35,accel=kvm \
  -cpu Penryn,kvm=on,vendor=GenuineIntel,+kvm_pv_unhalt,+kvm_pv_eoi,+hypervisor,+invtsc,+ssse3,+sse4.2,+popcnt,+aes,+xsave,+xsaveopt,chec>
  -smp sockets=1,cores=2 \
  -m 8096 \
  -smbios type=2 \
  -drive if=pflash,format=raw,readonly=on,file=/home/../OVMF_CODE.fd \
  -drive if=pflash,format=raw,file=/home/../OVMF_VARS-1920x1080.fd \
  -device isa-applesmc,osk="ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc" \
  -global ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off \
  -device ich9-ahci,id=sata \
  -drive id=inst,if=none,file=/home/.../mac_hdd_ng.img,format=qcow2 \
  -device ide-hd,bus=sata.1,drive=inst \
  -vga none \
  -nographic -display none \
  -netdev user,id=net0,hostfwd=tcp::2222-:22 \
  -device virtio-net-pci,netdev=net0,id=net0,mac=52:54:00:c9:18:27 \
  -device nec-usb-xhci,id=xhci \
  -device usb-host,vendorid=0x046d,productid=0xc534,bus=xhci.0,port=1 \
  -device pcie-root-port,id=pcie1,slot=1,chassis=1 \
  -device vfio-pci,host=01:00.0,bus=pcie1,addr=0x0,multifunction=on,x-no-kvm-intx=on,x-vga=on \
  -device vfio-pci,host=01:00.1,bus=pcie1,addr=0x1

Add autostart in /etc/rc.local in Devuan linux.

Code: Select all


#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.

if test -d /etc/boot.d ; then
        run-parts /etc/boot.d
fi
nohup /home/xxx/run_macvm.sh > /tmp/qemu_mac.log 2>&1 &


exit 0



Post Reply