From 0b19d918d91275bfdd4adf452b850c6295651490 Mon Sep 17 00:00:00 2001 From: Martin Wimpress Date: Sat, 4 Apr 2020 12:54:30 +0100 Subject: [PATCH] Add macOS support --- README.md | 72 ++++++++++++++++++++++++ quickemu | 162 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 194 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 3e1db4e..4a1dc39 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,78 @@ Starting /media/martin/Quickemu/windows10.conf * Complete the installation as you normally would. * Post-install you should run the VirtIO installer from the CD-ROM: drive. +### macOS + +There are some considerations when running macOS via Quickemu. + + * `quickemu` will automatically download the required [Clover EFI bootloader](https://sourceforge.net/projects/cloverefiboot/) and OVMF firmware from [the macOS-Simple-KVM project](https://github.com/foxlet/macOS-Simple-KVM). + * **macOS 10.14.3 or newer is supported**: + * [VirtIO block devices QEMU standard VGA are supported](https://www.kraxel.org/blog/2019/06/macos-qemu-guest/) since macOS 10.14.3 (Mohave). + * [VirtIO `usb-tablet` devices are supported](http://philjordan.eu/osx-virt/) since macOS 10.11 (El Capitan). + * [vmxnet3 network devices are supported](https://github.com/foxlet/macOS-Simple-KVM/blob/master/docs/guide-networking.md) since macOS 10.11 (El Capitan). + * Running macOS on QEMU required the guest CPU is set to `Penryn`. + * This is a very old architecture, [so to unlock higher CPU performance; AVX, AES-NI, SSE et al are enabled](https://www.nicksherlock.com/2017/10/passthrough-of-advanced-cpu-features-for-macos-high-sierra-guests/). + * UHCI USB (USB 2.0) is the fastest supported. + * USB pass-through has not been tested. + +You can use `quickemu` to run a macOS virtual machine. + + * Download macOS using `fetch-macos.py` + +``` +wget https://raw.githubusercontent.com/foxlet/macOS-Simple-KVM/master/tools/FetchMacOS/fetch-macos.py -O fetch-macos.py +chmod +x fetch-macos.py +./fetch-macos.py +qemu-virgil.qemu-img convert BaseSystem/BaseSystem.dmg -O raw BaseSystem.img +``` + + * Create a VM configuration file; for example `macos.conf` + * The `guest_os="macos"` line instructs `quickemu` to use optimise for macOS. + * The `img=` sets the boot disk that you downloaded with `fetch-macos.py` + +``` +guest_os="macos" +img="/media/$USER/Quickemu/macos/BaseSystem.img" +disk_img="/media/$USER/Quickemu/macos/macos.qcow2" +disk=128G +usb_devices=("046d:082d" "046d:085e") +``` + + * Use `quickemu` to start the virtual machine: + +``` +./quickemu --vm macos.conf +``` + +Which will output something like this: + +``` +Starting macos.conf + - QEMU: /snap/bin/qemu-virgil v4.2.0 + - BOOT: EFI + - Guest: Macos optimised + - Disk: /media/martin/Quickemu/macos/macos.qcow2 (64G) + Just created, booting from /media/martin/Quickemu/macos/BaseSystem.img + - CPU: 4 Core(s) + - RAM: 4G + - Screen: 1664x936 + - Video: VGA + - GL: ON + - Virgil3D: OFF + - Display: SDL + - smbd: /home/martin will be exported to the guest via smb://10.0.2.4/qemu + - ssh: 22223/tcp is connected. Login via 'ssh user@localhost -p 22223' +``` + + * Boot from the BaseSystem + * Click **Disk Utility** and **Continue** + * Select `Apple Inc. VirtIO Block Media` that is ~68GB from the list and click **Erase**. + * Enter a `Name:` for the disk and click **Erase**. + * Click **Done**. + * Close Disk Utility + * Click **Reinstall macOS** and **Continue** + * Complete the installation as you normally would. + ### All the options Here are the full usage instructions: diff --git a/quickemu b/quickemu index 9c6872f..5c37159 100755 --- a/quickemu +++ b/quickemu @@ -1,6 +1,18 @@ #!/usr/bin/env bash export LC_ALL=C +function web_get() { + local URL="${1}" + local FILE=$(echo "${URL##*/}") + if [ ! -e "${VMDIR}/${FILE}" ]; then + wget -q -c "${URL}" -O "${VMDIR}/${FILE}" + if [ $? -ne 0 ]; then + echo "ERROR! Failed to download ${URL}" + exit 1 + fi + fi +} + function disk_delete() { if [ -e "${disk_img}" ]; then rm "${disk_img}" @@ -113,8 +125,7 @@ enable_usb_passthrough() { USB_DEV=$(lsusb -d ${VENDOR_ID}:${PRODUCT_ID} | cut -d' ' -f4 | cut -d':' -f1) USB_NAME=$(lsusb -d ${VENDOR_ID}:${PRODUCT_ID} | cut -d' ' -f7-) echo " - ${USB_NAME}" - USB_PASSTHROUGH="${USB_PASSTHROUGH} -usb -device usb-host,vendorid=0x${VENDOR_ID},productid=0x${PRODUCT_ID}" - + USB_PASSTHROUGH="${USB_PASSTHROUGH} -device usb-host,vendorid=0x${VENDOR_ID},productid=0x${PRODUCT_ID},bus=usb.0" if [ ! -w /dev/bus/usb/${USB_BUS}/${USB_DEV} ]; then local EXEC_SCRIPT=1 echo "chown root:${USER} /dev/bus/usb/${USB_BUS}/${USB_DEV}" >> "${TEMP_SCRIPT}" @@ -156,12 +167,26 @@ function vm_boot() { # Force to lowercase. boot=$(echo ${boot,,}) + + # A;ways Boot macOS using EFI + if [ "${guest_os}" == "macos" ]; then + boot="efi" + fi + if [ "${boot}" == "efi" ] || [ "${boot}" == "uefi" ]; then if [ -e "${VIRGIL_PATH}/usr/share/qemu/edk2-x86_64-code.fd" ] ; then - local EFI_CODE="${VIRGIL_PATH}/usr/share/qemu/edk2-x86_64-code.fd" - local EFI_VARS="${VMDIR}/${VMNAME}-vars.fd" - if [ ! -e "${EFI_VARS}" ]; then - cp "${VIRGIL_PATH}/usr/share/qemu/edk2-i386-vars.fd" "${EFI_VARS}" + if [ "${guest_os}" == "macos" ]; then + web_get "https://github.com/foxlet/macOS-Simple-KVM/raw/master/ESP.qcow2" + web_get "https://github.com/foxlet/macOS-Simple-KVM/raw/master/firmware/OVMF_CODE.fd" + web_get "https://github.com/foxlet/macOS-Simple-KVM/raw/master/firmware/OVMF_VARS-1024x768.fd" + local EFI_CODE="${VMDIR}/OVMF_CODE.fd" + local EFI_VARS="${VMDIR}/OVMF_VARS-1024x768.fd" + else + local EFI_CODE="${VIRGIL_PATH}/usr/share/qemu/edk2-x86_64-code.fd" + local EFI_VARS="${VMDIR}/${VMNAME}-vars.fd" + if [ ! -e "${EFI_VARS}" ]; then + cp "${VIRGIL_PATH}/usr/share/qemu/edk2-i386-vars.fd" "${EFI_VARS}" + fi fi echo " - BOOT: EFI" else @@ -179,6 +204,14 @@ function vm_boot() { linux) DISPLAY_DEVICE="virtio-vga" ;; + macos) + CPU="-cpu Penryn,vendor=GenuineIntel,kvm=on,+aes,+avx,+avx2,+bmi1,+bmi2,+fma,+invtsc,+movbe,+pcid,+smep,+sse3,+sse4.2,+xgetbv1,+xsave,+xsavec,+xsaveopt" + readonly ROT_OSK="bheuneqjbexolgurfrjbeqfthneqrqcyrnfrqbagfgrny(p)NccyrPbzchgreVap" + readonly OSK=$(echo ${ROT_OSK} | rot13) + GUEST_TWEAKS="-device isa-applesmc,osk=${OSK}" + DISPLAY_DEVICE="VGA" + VIRGL="off" + ;; windows) CPU="${CPU},hv_time" GUEST_TWEAKS="-no-hpet" @@ -200,11 +233,11 @@ function vm_boot() { echo "ERROR! Failed to create ${disk_img}" exit 1 fi - if [ -z "${iso}" ]; then - echo "ERROR! You haven't specified a .iso image to boot from." + if [ -z "${iso}" ] && [ -z "${img}" ]; then + echo "ERROR! You haven't specified a .iso or .img image to boot from." exit 1 fi - echo " Just created, booting from ${iso}" + echo " Just created, booting from ${iso}${img}" if [ $? -ne 0 ]; then echo "ERROR! Failed to create ${disk_img} of ${disk}. Stopping here." exit 1 @@ -218,15 +251,16 @@ function vm_boot() { else DISK_CURR_SIZE=$(stat -c%s "${disk_img}") if [ ${DISK_CURR_SIZE} -le ${DISK_MIN_SIZE} ]; then - echo " Looks unused, booting from ${iso}" - if [ -z "${iso}" ]; then - echo "ERROR! You haven't specified a .iso image to boot from." + echo " Looks unused, booting from ${iso}${img}" + if [ -z "${iso}" ] && [ -z "${img}" ]; then + echo "ERROR! You haven't specified a .iso or .img image to boot from." exit 1 fi else # If there is a disk image, that appears to have an install - # then do not boot from the iso + # then do not boot from the iso/img iso="" + img="" fi fi fi @@ -251,6 +285,7 @@ function vm_boot() { elif [ ${CORES_HOST} -ge 4 ]; then CORES_VM="2" fi + local SMP="-smp ${CORES_VM},sockets=1,cores=${CORES_VM},threads=1" echo " - CPU: ${CORES_VM} Core(s)" local RAM_VM="2G" @@ -331,43 +366,89 @@ function vm_boot() { # Boot the iso image if [ "${boot}" == "efi" ] || [ "${boot}" == "uefi" ]; then - ${QEMU} \ - -name ${VMNAME},process=${VMNAME} \ - -enable-kvm -machine q35 ${GUEST_TWEAKS} \ - ${CPU} -smp ${CORES_VM} \ - -m ${RAM_VM} -device virtio-balloon \ - -drive if=pflash,format=raw,readonly,file="${EFI_CODE}" \ - -drive if=pflash,format=raw,file="${EFI_VARS}" \ - -drive media=cdrom,index=0,file="${iso}" \ - -drive media=cdrom,index=1,file="${driver_iso}" \ - -drive if=none,id=drive0,cache=directsync,aio=native,format=qcow2,file="${disk_img}" \ - -device virtio-blk-pci,drive=drive0,scsi=off ${STATUS_QUO} \ - ${VIDEO} -display ${OUTPUT},gl=${GL}${OUTPUT_EXTRA} \ - -device qemu-xhci,id=xhci,p2=8,p3=8 -device usb-kbd,bus=xhci.0 -device usb-tablet,bus=xhci.0 ${USB_PASSTHROUGH} \ - -device virtio-net,netdev=nic -netdev ${NET},id=nic \ - -audiodev pa,id=pa,server=unix:${XDG_RUNTIME_DIR}/pulse/native,out.stream-name=${LAUNCHER}-${VMNAME},in.stream-name=${LAUNCHER}-${VMNAME} \ - -device intel-hda -device hda-duplex,audiodev=pa,mixer=off \ - -rtc base=localtime,clock=host \ - -object rng-random,id=rng0,filename=/dev/urandom \ - -device virtio-rng-pci,rng=rng0 \ - -spice port=5930,disable-ticketing \ - -device virtio-serial-pci \ - -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 \ - -chardev spicevmc,id=spicechannel0,name=vdagent \ - -serial mon:stdio \ - "${@}" + if [ "${guest_os}" == "macos" ]; then + if [ -z "${img}" ]; then + ${QEMU} \ + -name ${VMNAME},process=${VMNAME} \ + -enable-kvm -machine q35 ${GUEST_TWEAKS} \ + ${CPU} ${SMP} \ + -m ${RAM_VM} -device virtio-balloon \ + -drive if=pflash,format=raw,readonly,file="${EFI_CODE}" \ + -drive if=pflash,format=raw,file="${EFI_VARS}" \ + -drive id=ESP,cache=directsync,aio=native,if=none,format=qcow2,file="${VMDIR}/ESP.qcow2" \ + -device virtio-blk-pci,drive=ESP,scsi=off \ + -drive id=SystemDisk,cache=directsync,aio=native,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO} \ + -device virtio-blk-pci,drive=SystemDisk,scsi=off \ + ${VIDEO} -display ${OUTPUT},gl=${GL}${OUTPUT_EXTRA} \ + -device usb-ehci,id=usb -device usb-kbd,bus=usb.0 -device usb-tablet,bus=usb.0 ${USB_PASSTHROUGH} \ + -device vmxnet3,netdev=nic -netdev ${NET},id=nic \ + -audiodev pa,id=pa,server=unix:${XDG_RUNTIME_DIR}/pulse/native,out.stream-name=${LAUNCHER}-${VMNAME},in.stream-name=${LAUNCHER}-${VMNAME} \ + -device intel-hda -device hda-duplex,audiodev=pa,mixer=off \ + -rtc base=localtime,clock=host \ + -serial mon:stdio \ + "${@}" + else + ${QEMU} \ + -name ${VMNAME},process=${VMNAME} \ + -enable-kvm -machine q35 ${GUEST_TWEAKS} \ + ${CPU} ${SMP} \ + -m ${RAM_VM} -device virtio-balloon \ + -drive if=pflash,format=raw,readonly,file="${EFI_CODE}" \ + -drive if=pflash,format=raw,file="${EFI_VARS}" \ + -drive id=ESP,cache=directsync,aio=native,if=none,format=qcow2,file="${VMDIR}/ESP.qcow2" \ + -device virtio-blk-pci,drive=ESP,scsi=off \ + -drive id=InstallMedia,cache=directsync,aio=native,if=none,format=raw,readonly,file="${img}" \ + -device virtio-blk-pci,drive=InstallMedia,scsi=off \ + -drive id=SystemDisk,cache=directsync,aio=native,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO} \ + -device virtio-blk-pci,drive=SystemDisk,scsi=off \ + ${VIDEO} -display ${OUTPUT},gl=${GL}${OUTPUT_EXTRA} \ + -device usb-ehci,id=usb -device usb-kbd,bus=usb.0 -device usb-tablet,bus=usb.0 ${USB_PASSTHROUGH} \ + -device vmxnet3,netdev=nic -netdev ${NET},id=nic \ + -audiodev pa,id=pa,server=unix:${XDG_RUNTIME_DIR}/pulse/native,out.stream-name=${LAUNCHER}-${VMNAME},in.stream-name=${LAUNCHER}-${VMNAME} \ + -device intel-hda -device hda-duplex,audiodev=pa,mixer=off \ + -rtc base=localtime,clock=host \ + -serial mon:stdio \ + "${@}" + fi + else + ${QEMU} \ + -name ${VMNAME},process=${VMNAME} \ + -enable-kvm -machine q35 ${GUEST_TWEAKS} \ + ${CPU} ${SMP} \ + -m ${RAM_VM} -device virtio-balloon \ + -drive if=pflash,format=raw,readonly,file="${EFI_CODE}" \ + -drive if=pflash,format=raw,file="${EFI_VARS}" \ + -drive media=cdrom,index=0,file="${iso}" \ + -drive media=cdrom,index=1,file="${driver_iso}" \ + -drive if=none,id=drive0,cache=directsync,aio=native,format=qcow2,file="${disk_img}" \ + -device virtio-blk-pci,drive=drive0,scsi=off ${STATUS_QUO} \ + ${VIDEO} -display ${OUTPUT},gl=${GL}${OUTPUT_EXTRA} \ + -device qemu-xhci,id=usb,p2=8,p3=8 -device usb-kbd,bus=usb.0 -device usb-tablet,bus=usb.0 ${USB_PASSTHROUGH} \ + -device virtio-net,netdev=nic -netdev ${NET},id=nic \ + -audiodev pa,id=pa,server=unix:${XDG_RUNTIME_DIR}/pulse/native,out.stream-name=${LAUNCHER}-${VMNAME},in.stream-name=${LAUNCHER}-${VMNAME} \ + -device intel-hda -device hda-duplex,audiodev=pa,mixer=off \ + -rtc base=localtime,clock=host \ + -object rng-random,id=rng0,filename=/dev/urandom \ + -device virtio-rng-pci,rng=rng0 \ + -spice port=5930,disable-ticketing \ + -device virtio-serial-pci \ + -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 \ + -chardev spicevmc,id=spicechannel0,name=vdagent \ + -serial mon:stdio \ + "${@}" + fi else ${QEMU} \ -name ${VMNAME},process=${VMNAME} \ -enable-kvm -machine q35 ${GUEST_TWEAKS} \ - ${CPU} -smp ${CORES_VM} \ + ${CPU} ${SMP} \ -m ${RAM_VM} -device virtio-balloon \ -drive media=cdrom,index=0,file="${iso}" \ -drive media=cdrom,index=1,file="${driver_iso}" \ -drive if=none,id=drive0,cache=directsync,aio=native,format=qcow2,file="${disk_img}" \ -device virtio-blk-pci,drive=drive0,scsi=off ${STATUS_QUO} \ ${VIDEO} -display ${OUTPUT},gl=${GL}${OUTPUT_EXTRA} \ - -device qemu-xhci,id=xhci,p2=8,p3=8 -device usb-kbd,bus=xhci.0 -device usb-tablet,bus=xhci.0 ${USB_PASSTHROUGH} \ + -device qemu-xhci,id=usb,p2=8,p3=8 -device usb-kbd,bus=usb.0 -device usb-tablet,bus=usb.0 ${USB_PASSTHROUGH} \ -device virtio-net,netdev=nic -netdev ${NET},id=nic \ -audiodev pa,id=pa,server=unix:${XDG_RUNTIME_DIR}/pulse/native,out.stream-name=${LAUNCHER}-${VMNAME},in.stream-name=${LAUNCHER}-${VMNAME} \ -device intel-hda -device hda-duplex,audiodev=pa,mixer=off \ @@ -418,6 +499,7 @@ function usage() { # Lowercase variables are used in the VM config file only boot="legacy" guest_os="linux" +img="" iso="" driver_iso="" disk_img=""