Merge pull request #19 from HikariKnight/dev

Merge dev into main for release of 2.0.0
This commit is contained in:
HikariKnight 2024-01-01 13:04:27 +01:00 committed by GitHub
commit 164a440024
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 2630 additions and 1125 deletions

7
.gitignore vendored
View file

@ -1,4 +1,9 @@
config/
backup/
utils/
utils.old/
utils.old/
bin/
dist/
main
quickpassthrough
debug.log

View file

@ -1,60 +1,81 @@
# QuickPassthrough
A project to simplify setting up GPU passthrough on your Linux host for [QuickEMU](https://github.com/quickemu-project/quickemu)(vfio support not developed yet) and libvirt/virt-manager
![Quickpassthrough Preview GIF](https://github.com/HikariKnight/quickpassthrough/blob/dev/quickpassthrough_preview.gif?raw=true)
You can use it by simply running
You can use it by simply downloading the latest release and run it inside a terminal/shell or by downloading and compiling it yourself with the commands below.
## Build with current dependencies
```bash
git clone https://github.com/HikariKnight/quickpassthrough.git
cd quickpassthrough
./vfio-setup
go mod download
CGO_ENABLED=0 go build -o quickpassthrough cmd/main.go
```
## Features
## Build with newest dependencies (may break)
```bash
git clone https://github.com/HikariKnight/quickpassthrough.git
cd quickpassthrough
go mod download
go get -u ./cmd
CGO_ENABLED=0 go build -o quickpassthrough cmd/main.go
```
* General warning and info about what you will be needing
* Enable and configure vfio modules
* Configure 2nd GPU for GPU Passthrough
* Dump the selected GPU rom (as some cards require a romfile for passthrough to work), however no rom patching support planned.
* Enable and configure the correct kernel modules and load them early (initramfs-tools, dracut and mkinitcpio)
* Configure kernel arguments for systemd-boot (using kernelstub)
* Configure kernel arguments for grub2
## Does this work on immutable systems?
Currently no, there might be support for ostree (fedora silverblue, kinoite, etc) at a later time.
## How do I undo the changes?
There is a "backup/" folder generated on the first run that will have a copy of all your files (and their paths) from before we edited anything.
Copy the files back to your system (blank files inside .d/ folders will be used to "undo" any new config files we wrote) and rebuild your initramfs then remove the kernel arguments listed in config/kernel_args from your bootloader (if your system use kernelstub, grubby or you had to manually add them).
## How do I just disable vfio for 1 boot?
Remove the vfio kernel arguments from your bootloader by pressing E on the boot menu. The kernel arguments added to the bootloader can be found in the config/kernel_args file
NOTE: You can also just remove them from your bootloader permanently and update your bootloader if you want to keep the config files on your system.
## What this project does NOT do
* Setup or configure your Virtual Machine (that is your job)
* Optimize your Virtual Machine for Passthrough (again this is your job)
* Optimize your host machine for Passthrough or Virtualization (out of this projects scope)
* Setup and configure GPU Passthrough on systems with 1 graphic card (iGPU counts as 1 Graphic Card by itself, so iGPU with another GPU will work)
* Does not configure passthrough of 3D controllers, as it will not work (this is most gaming laptops so do not even think about it). If you try run this on a laptop with a 3D controller, the "2nd GPU" will not show up.
NOTE: This project is aimed at desktops and headless servers.
## Features
* Show general warning to user and inform about making a backup and general expectations
* Detect if user has an amd or intel CPU and provide the correct IOMMU kernel args based on that
* Configure your 2nd GPU for GPU Passthrough (1 for host, 1 for VM)
* Use [ls-iommu](https://github.com/HikariKnight/ls-iommu) to find PCI devices like graphic cards, usb controllers, etc and see what IOMMU group they are in
* Enable and configure vfio modules (initramfs-tools, dracut, modprobe and mkinitcpio)
* Generate the correct kernel arguments for grub and systemd-boot
* Generate a script to use for dumping the vbios rom (as some cards require a romfile for passthrough to work), however no rom patching support planned.
* Configure kernel arguments for systemd-boot (using kernelstub or grubby)
* Configure kernel arguments for grub2 (editing /etc/default/grub or using grubby)
* A menu system you can navigate through, built using [Bubble Tea](https://github.com/charmbracelet/bubbletea) (Help appreciated to make this menu better!)
* Make sure [vendor-reset](https://github.com/gnif/vendor-reset) module is loaded before vfio, check the repository for the list of cards that require it!
* Provides you with the correct kernel arguments to add to your bootloader entry if a supported bootloader is not found
## Contributing
## Features now handled by [ls-iommu](https://github.com/HikariKnight/ls-iommu)
* Automatically handle GPUs where parts of it might be in separate IOMMU groups (ex: RX6600XT)
* Fetch the ID and PCI Address of devices
* Locate the vbios rom path on the system
* Tell the user to enable IOMMU (VT-d/AMD-v) on their motherboard and bootloader
* Get a list of devices, their IOMMU groups and various other information
## Contributing
<img src="https://user-images.githubusercontent.com/2557889/156038229-4e70352f-9182-4474-8e32-d14d3ad67566.png" width="250px">
I know my bash skills are not great, so help is always welcome! And help is wanted here.
If you know bash well, you will be able to help! Just make a pull request to the [dev branch](https://github.com/HikariKnight/quickpassthrough/tree/dev) with your changes!
Just remember to add comments to document the work and explain it for people who are less familiar with the bash syntax or anything else you use. 😄
This project originally started out as a bash only project, upon completing the proof of concept it became very clear that bash would become very messy with all the weird quirks and regex and inline editing of files. <br>
So the project moved over to golang, this lets us utilize TUI toolkits like to build a proper menu system for the project. <br>
If you know golang, passthrough or qemu, you are welcome to help! Just make a pull request to the [dev branch](https://github.com/HikariKnight/quickpassthrough/tree/dev) with your changes!<br>
Just remember to add comments to document the work and explain it for people who are less familiar with the golang syntax or anything else you use. 😄
Also if you know English, you can help by just proof reading. English is not my native language, plus I have dyslexia so I often make spelling mistakes.
Proof reading is still contribution!
## TODO
* ~~Everything~~
* ~~Show general warning to user and inform about making a backup and general expectations~~
* ~~Detect if user has an amd or intel CPU and provide the correct IOMMU kernel args based on that~~
* ~~Tell user to enable IOMMU (VT-d/AMD-v) on their motherboard and bootloader~~
* ~~Integrate ls-iommu and locate graphic cards and see what IOMMU group they are in~~
* ~~Enable and configure vfio modules~~
* ~~Fetch the ID for the GPUs and generate the correct kernel arguments for grub and systemd-boot~~
* ~~Dump the GPU rom, just in case it will be needed for passthrough~~ (no rom patching planned due to complexity)
* ~~Get help to actually make the scripts better~~
* Automatically handle GPUs where parts of it might be in separate IOMMU groups (ex: RX5600XT)
* A non-hacky menu system? (I will need help by some bash wizards for this)
* Coloured highlight/text for important information?
* Install vendor\_reset kernel module? (maybe far future, need to detect if user is using a card that needs it, can however mention where to get it)
* Suggest looking-glass if they are going to run Windows
----
### Why bash?
### Why GO?
I wanted the dependencies to be minimal without the need for compilation and to avoid potential breaking changes in the future (like with the transition from python2 to python3).
I know enough bash to make things work, but I am in no way a professional in writing bash scripts as I usually write python and golang.
There is also quite a lot of perl usage as I am quite familiar with the perl regex format over something like sed
I wanted to learn it, while also using a language that would potentially not create system dependencies. GO fits this criteria when you compile using CGO_ENABLED=0 as this will statically link the libraries and still produce a fairly small (when compressed with upx) binary.

29
cmd/.goreleaser.yaml Normal file
View file

@ -0,0 +1,29 @@
before:
hooks:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
hooks:
post:
- upx "{{ .Path }}"
archives:
- replacements:
linux: Linux
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
# modelines, feel free to remove those if you don't want/use them:
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj

17
cmd/main.go Normal file
View file

@ -0,0 +1,17 @@
package main
import (
internal "github.com/HikariKnight/quickpassthrough/internal"
downloader "github.com/HikariKnight/quickpassthrough/internal/ls_iommu_downloader"
"github.com/HikariKnight/quickpassthrough/internal/params"
)
func main() {
// Get all our arguments in 1 neat struct
pArg := params.NewParams()
if !pArg.Flag["gui"] {
downloader.CheckLsIOMMU()
internal.Tui()
}
}

38
go.mod Normal file
View file

@ -0,0 +1,38 @@
module github.com/HikariKnight/quickpassthrough
go 1.20
require (
github.com/HikariKnight/ls-iommu v0.0.0-20230912061539-899ed0ca3fd5
github.com/akamensky/argparse v1.4.0
github.com/cavaliergopher/grab/v3 v3.0.1
github.com/charmbracelet/bubbles v0.17.1
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/lipgloss v0.9.1
github.com/gookit/color v1.5.4
github.com/klauspost/cpuid/v2 v2.2.6
github.com/nexidian/gocliselect v1.0.0
golang.org/x/term v0.15.0
)
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/buger/goterm v1.0.4 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/pkg/term v1.1.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
)

91
go.sum Normal file
View file

@ -0,0 +1,91 @@
github.com/HikariKnight/ls-iommu v0.0.0-20230912061539-899ed0ca3fd5 h1:IEH+I+phCvL0fBzQKoWQxK147ZrsqPhLjzx1dETBT9Y=
github.com/HikariKnight/ls-iommu v0.0.0-20230912061539-899ed0ca3fd5/go.mod h1:+yX6+uXNeERvwFtP/gH/dOW/MA+K10Wi6asYcRtDXd8=
github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/buger/goterm v1.0.3 h1:7V/HeAQHrzPk/U4BvyH2g9u+xbUW9nr4yRPyG59W4fM=
github.com/buger/goterm v1.0.3/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4=
github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4=
github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o=
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/nexidian/gocliselect v1.0.0 h1:BTxqUqqhwc/O3jJrPuvpF359FjQag7EYgwdEF9cYY+w=
github.com/nexidian/gocliselect v1.0.0/go.mod h1:xyHtRO0Au/S+4tsEooDEj5+VZtkk+RU6RRs7q4o5TmI=
github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -0,0 +1,247 @@
package configs
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/HikariKnight/quickpassthrough/internal/logger"
"github.com/HikariKnight/quickpassthrough/pkg/command"
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
"github.com/klauspost/cpuid/v2"
)
// This function just adds what bootloader the system has to our config.bootloader value
// Preference is given to kernelstub because it is WAY easier to safely edit compared to grub2
func getBootloader(config *Config) {
// Check what bootloader handler we are using
// Check for grub2-mkconfig
_, err := command.Run("which", "grub2-mkconfig")
if err == nil {
// Mark bootloader as grub2
config.Bootloader = "grub2"
}
// Check for grub2-mkconfig
_, err = command.Run("which", "grub-mkconfig")
if err == nil {
// Mark bootloader as grub2
config.Bootloader = "grub2"
}
// Check for grubby (used by fedora)
_, err = command.Run("which", "grubby")
if err == nil {
// Mark it as unknown as i do not support it yet
config.Bootloader = "grubby"
}
// Check for kernelstub (used by pop os)
_, err = command.Run("which", "kernelstub")
if err == nil {
config.Bootloader = "kernelstub"
}
}
// This function adds the default kernel arguments we want to the config/cmdline file
// This gives us a file we can read all the kernel arguments this system needs
// in case of an unknown bootloader
func Set_Cmdline(gpu_IDs []string) {
// Get the system info
cpuinfo := cpuid.CPU
// Get the configs
config := GetConfig()
// Write the file containing our kernel arguments to feed the bootloader
fileio.AppendContent("iommu=pt", config.Path.CMDLINE)
// Write the argument based on which cpu the user got
switch cpuinfo.VendorString {
case "AuthenticAMD":
fileio.AppendContent(" amd_iommu=on", config.Path.CMDLINE)
case "GenuineIntel":
fileio.AppendContent(" intel_iommu=on", config.Path.CMDLINE)
}
// Add the GPU ids for vfio to the kernel arguments
fileio.AppendContent(fmt.Sprintf(" vfio_pci.ids=%s", strings.Join(gpu_IDs, ",")), config.Path.CMDLINE)
}
// Configures systemd-boot using kernelstub
func Set_KernelStub() string {
// Get the config
config := GetConfig()
// Get the kernel args
kernel_args := fileio.ReadFile(config.Path.CMDLINE)
// Write to logger
logger.Printf("Running command:\nsudo kernelstub -a \"%s\"\n", kernel_args)
// Run the command
_, err := command.Run("sudo", "kernelstub", "-a", kernel_args)
errorcheck.ErrorCheck(err, "Error, kernelstub command returned exit code 1")
// Return what we did
return fmt.Sprintf("Executed: sudo kernelstub -a \"%s\"", kernel_args)
}
// Configures grub2 and/or systemd-boot using grubby
func Set_Grubby() string {
// Get the config
config := GetConfig()
// Get the kernel args
kernel_args := fileio.ReadFile(config.Path.CMDLINE)
// Write to logger
logger.Printf("Running command:\nsudo grubby --update-kernel=ALL --args=\"%s\"\n", kernel_args)
// Run the command
_, err := command.Run("sudo", "grubby", "--update-kernel=ALL", fmt.Sprintf("--args=%s", kernel_args))
errorcheck.ErrorCheck(err, "Error, grubby command returned exit code 1")
// Return what we did
return fmt.Sprintf("Executed: sudo grubby --update-kernel=ALL --args=\"%s\"", kernel_args)
}
func Configure_Grub2() {
// Get the config struct
config := GetConfig()
// Make the config file path
conffile := fmt.Sprintf("%s/grub", config.Path.DEFAULT)
// Make sure we start from scratch by deleting any old file
if fileio.FileExist(conffile) {
os.Remove(conffile)
}
// Make a regex to get the system path instead of the config path
syspath_re := regexp.MustCompile(`^config`)
sysfile := syspath_re.ReplaceAllString(conffile, "")
// Make a regex to find the LINUX lines
cmdline_default_re := regexp.MustCompile(`^GRUB_CMDLINE_LINUX_DEFAULT=\"(.+)\"$`)
currentargs_re := regexp.MustCompile(`^GRUB_CMDLINE_LINUX(_DEFAULT|)=\"(.?|.+)\"$`)
// Make a bool so we know if we edited the default line if both are in the template
default_edited := false
// Read the mkinitcpio file
grub_content := fileio.ReadLines(sysfile)
// Write to logger
logger.Printf("Read %s:\n%s\n", sysfile, strings.Join(grub_content, "\n"))
for _, line := range grub_content {
if currentargs_re.MatchString(line) {
// Get the current modules
old_kernel_args := strings.Split(currentargs_re.ReplaceAllString(line, "${2}"), " ")
// Clean up the old arguments by removing vfio related kernel arguments
new_kernel_args := clean_Grub2_Args(old_kernel_args)
// Get the kernel args from our config
kernel_args := fileio.ReadFile(config.Path.CMDLINE)
// Add our kernel args to the list
new_kernel_args = append(new_kernel_args, kernel_args)
// If we are at the line starting with MODULES=
if cmdline_default_re.MatchString(line) {
// Write to logger
logger.Printf("Replacing line in %s:\n%s\nWith:\nGRUB_CMDLINE_LINUX_DEFAULT=\"%s\"\n", conffile, line, strings.Join(new_kernel_args, " "))
// Write the modules line we generated
fileio.AppendContent(fmt.Sprintf("GRUB_CMDLINE_LINUX_DEFAULT=\"%s\"\n", strings.Join(new_kernel_args, " ")), conffile)
// Mark the default line as edited so we can skip the non default line
default_edited = true
} else {
// If we have not edited the GRUB_CMDLINE_LINUX_DEFAULT line
if !default_edited {
// Write to logger
logger.Printf("Replacing line in %s:\n%s\nWith:\nGRUB_CMDLINE_LINUX=\"%s\"\n", conffile, line, strings.Join(new_kernel_args, " "))
// Write the modules line we generated
fileio.AppendContent(fmt.Sprintf("GRUB_CMDLINE_LINUX=\"%s\"\n", strings.Join(new_kernel_args, " ")), conffile)
}
}
} else {
// Write the line to the file since it does not match our regex
fileio.AppendContent(fmt.Sprintf("%s\n", line), conffile)
}
}
}
func clean_Grub2_Args(old_kernel_args []string) []string {
// Make a regex to get the VFIO related kernel arguments removed, if they already existed
vfio_args_re := regexp.MustCompile(`(amd|intel)_iommu=(on|1)|iommu=(pt|on)|vfio_pci.ids=.+|vfio_pci.disable_vga=\d{1}|rd.driver.pre=vfio_pci`)
// Make a stringlist to keep our new arguments
var clean_kernel_args []string
// Loop through current kernel_args and add anything that isnt vfio or vendor-reset related
for _, v := range old_kernel_args {
// If what we find is not a vfio module or vendor-reset module
if !vfio_args_re.MatchString(v) {
// Add module to module list
clean_kernel_args = append(clean_kernel_args, v)
}
}
// Return cleaned up arguments
return clean_kernel_args
}
// This function copies our config to /etc/default/grub and updates grub
func Set_Grub2() ([]string, error) {
// Get the config
config := GetConfig()
// Get the conf file
conffile := fmt.Sprintf("%s/grub", config.Path.DEFAULT)
// Get the sysfile
sysfile_re := regexp.MustCompile(`^config`)
sysfile := sysfile_re.ReplaceAllString(conffile, "")
// Write to logger
logger.Printf("Executing command:\nsudo cp -v \"%s\" %s", conffile, sysfile)
// Make our output slice
var output []string
// Copy files to system
output = append(output, CopyToSystem(conffile, sysfile))
// Set a variable for the mkconfig command
mkconfig := "grub-mkconfig"
// Check for grub-mkconfig
_, err := command.Run("which", "grub-mkconfig")
if err == nil {
// Set binary as grub-mkconfig
mkconfig = "grub-mkconfig"
} else {
mkconfig = "grub2-mkconfig"
}
// Update grub.cfg
if fileio.FileExist("/boot/grub/grub.cfg") {
output = append(output, fmt.Sprintf("Executed: sudo %s -o /boot/grub/grub.cfg\nSee debug.log for more detailed output", mkconfig))
_, mklog, err := command.RunErr("sudo", mkconfig, "-o", "/boot/grub/grub.cfg")
logger.Printf(strings.Join(mklog, "\n"))
errorcheck.ErrorCheck(err, "Failed to update /boot/grub/grub.cfg")
} else {
output = append(output, fmt.Sprintf("Executed: sudo %s -o /boot/grub/grub.cfg\nSee debug.log for more detailed output", mkconfig))
_, mklog, err := command.RunErr("sudo", mkconfig, "-o", "/boot/grub2/grub.cfg")
logger.Printf(strings.Join(mklog, "\n"))
errorcheck.ErrorCheck(err, "Failed to update /boot/grub/grub.cfg")
}
return output, err
}

View file

@ -0,0 +1,41 @@
package configs
import (
"fmt"
"os"
"strings"
"github.com/HikariKnight/quickpassthrough/internal/logger"
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
)
// This function writes a dracut configuration file for /etc/dracut.conf.d/
func Set_Dracut() {
config := GetConfig()
// Set the dracut config file
dracutConf := fmt.Sprintf("%s/vfio.conf", config.Path.DRACUT)
// If the file already exists then delete it
if fileio.FileExist(dracutConf) {
os.Remove(dracutConf)
}
// Write to logger
logger.Printf("Writing to %s:\nadd_drivers+=\" %s \"\n", dracutConf, strings.Join(vfio_modules(), " "))
// Write the dracut config file
fileio.AppendContent(fmt.Sprintf("add_drivers+=\" %s \"\n", strings.Join(vfio_modules(), " ")), dracutConf)
// Get the current kernel arguments we have generated
kernel_args := fileio.ReadFile(config.Path.CMDLINE)
// If the kernel argument is not already in the file
if !strings.Contains(kernel_args, "rd.driver.pre=vfio_pci") {
// Add to our kernel arguments file that vfio_pci should load early (dracut does this using kernel arguments)
fileio.AppendContent(" rd.driver.pre=vfio_pci", config.Path.CMDLINE)
}
// Make a backup of dracutConf if there is one there
backupFile(strings.Replace(dracutConf, "config", "", 1))
}

View file

@ -0,0 +1,84 @@
package configs
import (
"bufio"
"fmt"
"os"
"regexp"
"strings"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
)
// Special function to read the header of a file (reads the first N lines)
func initramfs_readHeader(lines int, fileName string) string {
// Open the file
f, err := os.Open(fileName)
errorcheck.ErrorCheck(err, fmt.Sprintf("Error opening %s", fileName))
defer f.Close()
header_re := regexp.MustCompile(`^#`)
var header []string
// Make a new scanner
scanner := bufio.NewScanner(f)
// Read the first N lines
for i := 0; i < lines; i++ {
scanner.Scan()
if header_re.MatchString(scanner.Text()) {
header = append(header, scanner.Text())
}
}
// Return the header
return fmt.Sprintf("%s\n", strings.Join(header, "\n"))
}
// Reads the system file and copies over the content while inserting the vfio modules
// Takes the config file as argument
func initramfs_addModules(conffile string) {
// Make a regex to get the system path instead of the config path
syspath_re := regexp.MustCompile(`^config`)
// Make a regex to skip specific modules and comments
skipmodules_re := regexp.MustCompile(`(^#|vendor-reset|vfio|vfio_pci|vfio_iommu_type1|vfio_virqfd)`)
// Get the syspath
syspath := syspath_re.ReplaceAllString(conffile, "")
// Open the system file for reading
sysfile, err := os.Open(syspath)
errorcheck.ErrorCheck(err, fmt.Sprintf("Error opening file for reading %s", syspath))
defer sysfile.Close()
// Check if user has vendor-reset installed/enabled and make sure that is first
content := fileio.ReadFile(syspath)
if strings.Contains(content, "vendor-reset") {
fileio.AppendContent("vendor-reset\n", conffile)
}
// Write the vfio modules
fileio.AppendContent(
fmt.Sprint(
"# Added by quickpassthrough #\n",
fmt.Sprintf(
"%s\n",
strings.Join(vfio_modules(), "\n"),
),
"#############################\n",
),
conffile,
)
// Scan the system file line by line
scanner := bufio.NewScanner(sysfile)
for scanner.Scan() {
// If this is not a line we skip then
if !skipmodules_re.MatchString(scanner.Text()) {
// Add the module to our config
fileio.AppendContent(fmt.Sprintf("%s\n", scanner.Text()), conffile)
}
}
}

View file

@ -0,0 +1,75 @@
package configs
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/HikariKnight/quickpassthrough/internal/logger"
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
)
// This function copies the content of /etc/mkinitcpio.conf to the config folder and does an inline replace/insert on the MODULES=() line
func Set_Mkinitcpio() {
// Get the config struct
config := GetConfig()
// Make sure we start from scratch by deleting any old file
if fileio.FileExist(config.Path.MKINITCPIO) {
os.Remove(config.Path.MKINITCPIO)
}
// Make a regex to get the system path instead of the config path
syspath_re := regexp.MustCompile(`^config`)
sysfile := syspath_re.ReplaceAllString(config.Path.MKINITCPIO, "")
// Make a regex to find the modules line
module_line_re := regexp.MustCompile(`^MODULES=`)
modules_re := regexp.MustCompile(`MODULES=\((.+?)\)`)
vfio_modules_re := regexp.MustCompile(`(vfio_iommu_type1|vfio_pci|vfio_virqfd|vfio|vendor-reset)`)
// Read the mkinitcpio file
mkinitcpio_content := fileio.ReadLines(sysfile)
// Write to logger
logger.Printf("Read %s:\n%s\n", sysfile, strings.Join(mkinitcpio_content, "\n"))
for _, line := range mkinitcpio_content {
// If we are at the line starting with MODULES=
if module_line_re.MatchString(line) {
// Get the current modules
currentmodules := strings.Split(modules_re.ReplaceAllString(line, "${1}"), " ")
// Get the vfio modules we need to use
modules := vfio_modules()
// If vendor-reset is in the current modules
if strings.Contains(line, "vendor-reset") {
// Write to logger
logger.Printf("vendor-reset module detected in %s\nMaking sure it will be loaded before vfio\n", sysfile)
// Add vendor-reset first
modules = append([]string{"vendor-reset"}, modules...)
}
// Loop through current modules and add anything that isnt vfio or vendor-reset related
for _, v := range currentmodules {
// If what we find is not a vfio module or vendor-reset module
if !vfio_modules_re.MatchString(v) {
// Add module to module list
modules = append(modules, v)
}
}
// Write to logger
logger.Printf("Replacing line in %s:\n%s\nWith:\nMODULES=(%s)\n", config.Path.MKINITCPIO, line, strings.Join(modules, " "))
// Write the modules line we generated
fileio.AppendContent(fmt.Sprintf("MODULES=(%s)\n", strings.Join(modules, " ")), config.Path.MKINITCPIO)
} else {
// Else just write the line to the config
fileio.AppendContent(fmt.Sprintf("%s\n", line), config.Path.MKINITCPIO)
}
}
}

View file

@ -0,0 +1,66 @@
package configs
import (
"fmt"
"os"
"strings"
"github.com/HikariKnight/quickpassthrough/internal/logger"
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
)
// This function generates a modprobe file for /etc/modprobe.d/
func Set_Modprobe(gpu_IDs []string) {
// Get the config
config := GetConfig()
// Read our current kernel arguments
kernel_args := fileio.ReadFile(config.Path.CMDLINE)
var vfio_pci_options []string
vfio_pci_options = append(vfio_pci_options, strings.Join(gpu_IDs, ","))
if strings.Contains(kernel_args, "vfio_pci.disable_vga=1") {
// Write to logger
logger.Printf("User has disabled vfio video output on host, adding disable_vga=1 to the optional hardcoded vfio_pci options\n")
vfio_pci_options = append(vfio_pci_options, "disable_vga=1")
}
// Put our config file path into a string
conffile := fmt.Sprintf("%s/vfio.conf", config.Path.MODPROBE)
// If the file exists
if fileio.FileExist(conffile) {
// Delete the old file
os.Remove(conffile)
}
content := fmt.Sprint(
"## This is an autogenerated file that stubs your graphic card for use with vfio\n",
"## This file should be placed inside /etc/modprobe.d/\n",
"# Uncomment the line below to \"hardcode\" your graphic card to be bound to the vfio-pci driver.\n",
"# In most cases this should not be neccessary, it will also prevent you from turning off vfio in the bootloader.\n",
fmt.Sprintf(
"#options vfio_pci ids=%s\n",
strings.Join(vfio_pci_options, " "),
),
"\n",
"# Make sure vfio_pci is loaded before these modules: nvidia, nouveau, amdgpu and radeon\n",
"softdep nvidia pre: vfio vfio_pci\n",
"softdep nouveau pre: vfio vfio_pci\n",
"softdep amdgpu pre: vfio vfio_pci\n",
"softdep radeon pre: vfio vfio_pci\n",
)
// Write to logger
logger.Printf("Writing %s:\n%s", conffile, content)
// Write the vfio.conf file to our modprobe config
fileio.AppendContent(
content,
conffile,
)
// Make a backup of dracutConf if there is one there
backupFile(strings.Replace(conffile, "config", "", 1))
}

View file

@ -0,0 +1,70 @@
package configs
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/HikariKnight/quickpassthrough/internal/logger"
)
// Generates a script file named dump_vbios.sh and places it inside the utils folder.
// This script has to be run without a display manager or display server running
func GenerateVBIOSDumper(vbios_path string) {
// Get the config directories
config := GetConfig()
// Get the program directory
exe, _ := os.Executable()
scriptdir := filepath.Dir(exe)
// If we are using go run use the working directory instead
if strings.Contains(scriptdir, "/tmp/go-build") {
scriptdir, _ = os.Getwd()
}
vbios_script_template := fmt.Sprint(
"#!/bin/bash\n",
"# THIS FILE IS AUTO GENERATED!\n",
"# IF YOU HAVE CHANGED GPU, PLEASE RE-RUN QUICKPASSTHROUGH!\n",
"mkdir -p \"%s\"\n",
"echo Attempting to enable reading from rom\n",
"echo 1 | sudo tee %s\n",
"echo\n",
"echo Attempting to dump VBIOS\n",
"sudo bash -c \"cat %s\" > %s/%s/vfio_card.rom || echo \"\nFailed to dump the VBIOS, in most cases a reboot can fix this.\nOr you have to bind the gpu to the vfio-pci driver, reboot the machine and try dumping again.\nIf that still fails, you might find your VBIOS at: https://www.techpowerup.com/vgabios/\n\"\n",
"file \"%s/%s/vfio_card.rom\"\n",
"echo\n",
"echo Attempting to disable reading from rom \\(cleanup\\)\n",
"echo 0 | sudo tee %s\n",
)
vbios_script := fmt.Sprintf(
vbios_script_template,
config.Path.QUICKEMU,
vbios_path,
vbios_path,
scriptdir,
config.Path.QUICKEMU,
scriptdir,
config.Path.QUICKEMU,
vbios_path,
)
// Make the script file
scriptfile, err := os.Create("utils/dump_vbios.sh")
errorcheck.ErrorCheck(err, "Cannot create file \"utils/dump_vbios.sh\"")
defer scriptfile.Close()
// Make the script executable
scriptfile.Chmod(0775)
errorcheck.ErrorCheck(err, "Could not change permissions of \"utils/dump_vbios.sh\"")
// Write to logger
logger.Printf("Writing utils/dump_vbios.sh\n")
// Write the script
scriptfile.WriteString(vbios_script)
}

View file

@ -0,0 +1,50 @@
package configs
import (
"fmt"
"os"
"strings"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/HikariKnight/quickpassthrough/internal/logger"
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
)
// This function adds the disable vfio video output on host option to the config
// The function will use the given int as the value for the option
func DisableVFIOVideo(i int) {
// Get the config
config := GetConfig()
// Write to logger
logger.Printf("Adding vfio_pci.disable_vga=%v to %s\n", i, config.Path.CMDLINE)
// Get the current kernel arguments we have generated
kernel_args := fileio.ReadFile(config.Path.CMDLINE)
// If the kernel argument is already in the file
if strings.Contains(kernel_args, "vfio_pci.disable_vga") {
// Remove the old file
err := os.Remove(config.Path.CMDLINE)
errorcheck.ErrorCheck(err, fmt.Sprintf("Could not rewrite %s", config.Path.CMDLINE))
// Enable or disable the VGA based on our given value
if i == 0 {
kernel_args = strings.Replace(kernel_args, "vfio_pci.disable_vga=1", "vfio_pci.disable_vga=0", 1)
} else {
kernel_args = strings.Replace(kernel_args, "vfio_pci.disable_vga=0", "vfio_pci.disable_vga=1", 1)
}
// Rewrite the kernel_args file
fileio.AppendContent(kernel_args, config.Path.CMDLINE)
} else {
// Add to the kernel arguments that we want to disable VFIO video output on the host
fileio.AppendContent(
fmt.Sprintf(
" vfio_pci.disable_vga=%v", i,
),
config.Path.CMDLINE,
)
}
}

251
internal/configs/configs.go Normal file
View file

@ -0,0 +1,251 @@
package configs
import (
"fmt"
"os"
"regexp"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/HikariKnight/quickpassthrough/internal/logger"
"github.com/HikariKnight/quickpassthrough/pkg/command"
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
"github.com/HikariKnight/quickpassthrough/pkg/uname"
"github.com/klauspost/cpuid/v2"
)
type Path struct {
CMDLINE string
MODPROBE string
INITRAMFS string
ETCMODULES string
DEFAULT string
QUICKEMU string
DRACUT string
MKINITCPIO string
}
type Config struct {
Bootloader string
Cpuvendor string
Path *Path
Gpu_Group string
Gpu_IDs []string
}
// Gets the path to all the config files
func GetConfigPaths() *Path {
Paths := &Path{
CMDLINE: "config/kernel_args",
MODPROBE: "config/etc/modprobe.d",
INITRAMFS: "config/etc/initramfs-tools",
ETCMODULES: "config/etc/modules",
DEFAULT: "config/etc/default",
QUICKEMU: "config/quickemu",
DRACUT: "config/etc/dracut.conf.d",
MKINITCPIO: "config/etc/mkinitcpio.conf",
}
return Paths
}
// Gets all the configs and returns the struct
func GetConfig() *Config {
config := &Config{
Bootloader: "unknown",
Cpuvendor: cpuid.CPU.VendorString,
Path: GetConfigPaths(),
Gpu_Group: "",
Gpu_IDs: []string{},
}
// Detect the bootloader we are using
getBootloader(config)
return config
}
// Constructs the empty config files and folders based on what exists on the system
func InitConfigs() {
config := GetConfig()
// Add all directories we need into a stringlist
dirs := []string{
config.Path.MODPROBE,
config.Path.INITRAMFS,
config.Path.DEFAULT,
config.Path.DRACUT,
}
// Remove old config
os.RemoveAll("config")
// Make the config folder
os.Mkdir("config", os.ModePerm)
// Make a regex to get the system path instead of the config path
syspath_re := regexp.MustCompile(`^config`)
// For each directory
for _, confpath := range dirs {
// Get the system path
syspath := syspath_re.ReplaceAllString(confpath, "")
// If the path exists
if fileio.FileExist(syspath) {
// Write to log
logger.Printf(
"%s found on the system\n"+
"Creating %s\n",
syspath,
confpath,
)
// Make a backup directory
makeBackupDir(syspath)
// Create the directories for our configs
err := os.MkdirAll(confpath, os.ModePerm)
errorcheck.ErrorCheck(err)
}
}
// Add all files we need to a stringlist
files := []string{
config.Path.ETCMODULES,
config.Path.MKINITCPIO,
fmt.Sprintf("%s/modules", config.Path.INITRAMFS),
fmt.Sprintf("%s/grub", config.Path.DEFAULT),
}
// If we are using grubby
if config.Bootloader == "grubby" {
// Do not create an empty /etc/default/grub file
files = files[:len(files)-1]
}
for _, conffile := range files {
// Get the system file path
sysfile := syspath_re.ReplaceAllString(conffile, "")
// If the file exists
if fileio.FileExist(sysfile) {
// Write to log
logger.Printf(
"%s found on the system\n"+
"Creating %s\n",
sysfile,
conffile,
)
// Create the directories for our configs
file, err := os.Create(conffile)
errorcheck.ErrorCheck(err)
// Close the file so we can edit it
file.Close()
// Backup the sysfile if we do not have a backup
backupFile(sysfile)
}
// If we now have a config that exists
if fileio.FileExist(conffile) {
switch conffile {
case config.Path.ETCMODULES:
// Write to logger
logger.Printf("Getting the header (if it is there) from %s\n", conffile)
// Read the header
header := initramfs_readHeader(4, sysfile)
fileio.AppendContent(header, conffile)
// Add the modules to the config file
initramfs_addModules(conffile)
case fmt.Sprintf("%s/modules", config.Path.INITRAMFS):
// Write to logger
logger.Printf("Getting the header (if it is there) from %s\n", conffile)
// Read the header
header := initramfs_readHeader(11, sysfile)
fileio.AppendContent(header, conffile)
// Add the modules to the config file
initramfs_addModules(conffile)
}
}
}
}
// Returns a list of modules used for vfio based on the systems kernel version
func vfio_modules() []string {
// Make the list of modules
modules := []string{
"vfio_pci",
"vfio",
"vfio_iommu_type1",
}
// If we are on a kernel older than 6.2
sysinfo := uname.New()
kernel_re := regexp.MustCompile(`^(6\.1|6\.0|[1-5]\.)`)
if kernel_re.MatchString(sysinfo.Kernel) {
// Write to the debug log
logger.Printf("Linux kernel version %s detected!\nIncluding vfio_virqfd module\n")
// Include the vfio_virqfd module
// NOTE: this driver was merged into the vfio module in 6.2
modules = append(modules, "vfio_virqfd")
}
// Return the modules
return modules
}
func backupFile(source string) {
// Make a destination path
dest := fmt.Sprintf("backup%s", source)
// If the file exists in the config but not on the system it is a file we make
if fileio.FileExist(fmt.Sprintf("config%s", source)) && !fileio.FileExist(source) {
// Create the blank file so that a copy of the backup folder to /etc
file, err := os.Create(dest)
errorcheck.ErrorCheck(err, "Error creating file %s\n", dest)
file.Close()
} else if !fileio.FileExist(dest) {
// If a backup of the file does not exist
// Write to the logger
logger.Printf("No first time backup of %s detected.\nCreating a backup at %s\n", source, dest)
// Copy the file
fileio.FileCopy(source, dest)
}
}
func makeBackupDir(dest string) {
// If a backup directory does not exist
if !fileio.FileExist("backup/") {
// Write to the logger
logger.Printf("Backup directory does not exist!\nCreating backup directory for first run backup")
}
// Make the empty directories
err := os.MkdirAll(fmt.Sprintf("backup/%s", dest), os.ModePerm)
errorcheck.ErrorCheck(err, "Error making backup/ folder")
}
// Copy a file to the system, make sure you have run command.Elevate() recently
func CopyToSystem(conffile, sysfile string) string {
// Since we should be elevated with our sudo token we will copy with cp
// (using built in functions will not work as we are running as the normal user)
output, _ := command.Run("sudo", "cp", "-v", conffile, sysfile)
// Clean the output
clean_re := regexp.MustCompile(`\n`)
clean_output := clean_re.ReplaceAllString(output[0], "")
// Write output to logger
logger.Printf(clean_output)
// Return the output
return fmt.Sprintf("Copying: %s", clean_output)
}

12
internal/logger/logger.go Normal file
View file

@ -0,0 +1,12 @@
package logger
import (
"fmt"
"log"
)
// Formats our log output to \n%s\n\n for readability
func Printf(content string, v ...any) {
content = fmt.Sprintf("\n%s\n", content)
log.Printf(content, v...)
}

View file

@ -0,0 +1,192 @@
package ls_iommu_downloader
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
"github.com/HikariKnight/quickpassthrough/pkg/untar"
"github.com/cavaliergopher/grab/v3"
)
// Generated from github API response using https://mholt.github.io/json-to-go/
type Response struct {
URL string `json:"url"`
AssetsURL string `json:"assets_url"`
UploadURL string `json:"upload_url"`
HTMLURL string `json:"html_url"`
ID int `json:"id"`
Author struct {
Login string `json:"login"`
ID int `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
} `json:"author"`
NodeID string `json:"node_id"`
TagName string `json:"tag_name"`
TargetCommitish string `json:"target_commitish"`
Name string `json:"name"`
Draft bool `json:"draft"`
Prerelease bool `json:"prerelease"`
CreatedAt time.Time `json:"created_at"`
PublishedAt time.Time `json:"published_at"`
Assets []struct {
URL string `json:"url"`
ID int `json:"id"`
NodeID string `json:"node_id"`
Name string `json:"name"`
Label string `json:"label"`
Uploader struct {
Login string `json:"login"`
ID int `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
} `json:"uploader"`
ContentType string `json:"content_type"`
State string `json:"state"`
Size int `json:"size"`
DownloadCount int `json:"download_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
BrowserDownloadURL string `json:"browser_download_url"`
} `json:"assets"`
TarballURL string `json:"tarball_url"`
ZipballURL string `json:"zipball_url"`
Body string `json:"body"`
}
func CheckLsIOMMU() {
// Check the API for releases
resp, err := http.Get("https://api.github.com/repos/hikariknight/ls-iommu/releases/latest")
errorcheck.ErrorCheck(err)
// Close the response when function ends
defer resp.Body.Close()
// Get the response body
body, err := io.ReadAll(resp.Body)
errorcheck.ErrorCheck(err)
var result Response
if err := json.Unmarshal(body, &result); err != nil {
fmt.Println("Cant decode JSON")
}
// Make the directory for ls-iommu if it does not exist
path := "utils"
if !fileio.FileExist(path) {
err := os.Mkdir(path, os.ModePerm)
errorcheck.ErrorCheck(err)
}
// Generate the download url
downloadUrl := fmt.Sprintf(
"https://github.com/HikariKnight/ls-iommu/releases/download/%s/ls-iommu_Linux_x86_64.tar.gz",
result.TagName,
)
// Generate checksums.txt url
checkSumsUrl := fmt.Sprintf(
"https://github.com/HikariKnight/ls-iommu/releases/download/%s/checksums.txt",
result.TagName,
)
fileName := fmt.Sprintf("%s/ls-iommu_Linux_x86_64.tar.gz", path)
// Get the checksum data
checksums, err := http.Get(checkSumsUrl)
errorcheck.ErrorCheck(err)
defer checksums.Body.Close()
checksums_txt, err := io.ReadAll(checksums.Body)
errorcheck.ErrorCheck(err)
// Check if the tar.gz exists
if !fileio.FileExist(fileName) {
downloadNewVersion(path, fileName, downloadUrl)
if checkSum(string(checksums_txt), fileName) {
err = untar.Untar(fmt.Sprintf("%s/", path), fileName)
errorcheck.ErrorCheck(err)
}
} else {
if !checkSum(string(checksums_txt), fileName) {
downloadNewVersion(path, fileName, downloadUrl)
err = untar.Untar(fmt.Sprintf("%s/", path), fileName)
errorcheck.ErrorCheck(err)
}
}
}
func checkSum(checksums string, fileName string) bool {
r, err := os.Open(fileName)
errorcheck.ErrorCheck(err)
defer r.Close()
hasher := sha256.New()
if _, err := io.Copy(hasher, r); err != nil {
log.Fatal(err)
}
value := hex.EncodeToString(hasher.Sum(nil))
return strings.Contains(checksums, value)
}
func downloadNewVersion(path, fileName, downloadUrl string) {
// Create a request
grabClient := grab.NewClient()
req, _ := grab.NewRequest(fileName, downloadUrl)
// Remove old archive
os.Remove(fileName)
// Download ls-iommu
download := grabClient.Do(req)
// check for errors
if err := download.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Download failed: %v\n", err)
if !fileio.FileExist("utils/ls-iommu") {
log.Fatal("If the above error is 404, then we could not communicate with the GitHub API\n Please manually download and extract ls-iommu to: utils/\nYou can download it from: https://github.com/HikariKnight/ls-iommu/releases")
} else {
fmt.Println("Existing ls-iommu binary detected in \"utils/\", will use that instead as the GitHub API did not respond.")
}
} else {
fmt.Printf("Download saved to ./%v \n", download.Filename)
}
}

View file

@ -0,0 +1,71 @@
package lsiommu
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"strings"
"github.com/HikariKnight/quickpassthrough/internal/logger"
"github.com/klauspost/cpuid/v2"
)
func GetIOMMU(args ...string) []string {
var stdout, stderr bytes.Buffer
// Write to logger
logger.Printf("Executing: utils/ls-iommu %s\n", strings.Join(args, " "))
// Configure the ls-iommu command
cmd := exec.Command("utils/ls-iommu", args...)
cmd.Stderr = &stderr
cmd.Stdout = &stdout
// Execute the command
err := cmd.Run()
// Generate the correct iommu string for the system
var iommu_args string
cpuinfo := cpuid.CPU
// Write the argument based on which cpu the user got
switch cpuinfo.VendorString {
case "AuthenticAMD":
iommu_args = "iommu=pt amd_iommu=on"
case "GenuineIntel":
iommu_args = "iommu=pt intel_iommu=on"
}
// If ls-iommu returns an error then IOMMU is disabled
if err != nil {
fmt.Printf(
"IOMMU disabled in either UEFI/BIOS or in bootloader, or run inside container!\n"+
"For your bootloader, make sure you have added the kernel arguments:\n"+
"%s\n",
iommu_args,
)
os.Exit(1)
}
// Read the output
var items []string
output, _ := io.ReadAll(&stdout)
// Write to logger
logger.Printf("ls-iommu query returned\n%s", string(output))
// Make regex to shorten vendor names
shortenVendor := regexp.MustCompile(` Corporation:| Technology Inc.:| Electronics Co Ltd:|Advanced Micro Devices, Inc\. \[(AMD)(|\/ATI)\]:`)
// Parse the output line by line
scanner := bufio.NewScanner(strings.NewReader(string(output)))
for scanner.Scan() {
// Write the objects into the list
items = append(items, shortenVendor.ReplaceAllString(scanner.Text(), "${1}"))
}
// Return our list of items
return items
}

View file

@ -0,0 +1,50 @@
package pages
import (
"fmt"
"os"
"github.com/HikariKnight/quickpassthrough/internal/configs"
"github.com/HikariKnight/quickpassthrough/pkg/command"
"github.com/HikariKnight/quickpassthrough/pkg/menu"
"github.com/gookit/color"
)
// Welcome page
func Welcome() {
// Clear screen
command.Clear()
// Write title
title := color.New(color.BgHiBlue, color.White, color.Bold)
title.Println("Welcome to Quickpassthrough!")
// Write welcome message
color.Print(
"This script is meant to make it easier to setup GPU passthrough for\n",
"Qemu based systems. WITH DIFFERENT 2 GPUS ON THE HOST SYSTEM\n",
"However due to the complexity of GPU passthrough\n",
"This script assumes you know how to do (and have done) the following.\n\n",
"* You have already enabled IOMMU, VT-d, SVM and/or AMD-v\n inside your UEFI/BIOS advanced settings.\n",
"* Know how to edit your bootloader\n",
"* Have a bootloader timeout of at least 3 seconds to access the menu\n",
"* Enable & Configure kernel modules\n",
"* Have a backup/snapshot of your system in case the script causes your\n system to be unbootable\n\n",
"By continuing you accept that I am not liable if your system\n",
"becomes unbootable, as you will be asked to verify the files generated\n\n",
"You can press ESC to exit the program at any time.\n\n",
)
// Make user accept responsibility
choice := menu.YesNo("Are you sure you want to continue?")
// If yes, go to next page
if choice == "y" {
configs.InitConfigs()
config := configs.GetConfig()
SelectGPU(config)
} else {
fmt.Println("")
os.Exit(0)
}
}

View file

@ -0,0 +1,129 @@
package pages
import (
"fmt"
"os"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/HikariKnight/quickpassthrough/internal/configs"
lsiommu "github.com/HikariKnight/quickpassthrough/internal/lsiommu"
"github.com/HikariKnight/quickpassthrough/pkg/command"
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
"github.com/HikariKnight/quickpassthrough/pkg/menu"
"github.com/gookit/color"
)
func SelectGPU(config *configs.Config) {
// Clear the screen
command.Clear()
// Get the users GPUs
gpus := lsiommu.GetIOMMU("-g", "-F", "vendor:,prod_name,optional_revision:,device_id")
// Generate a list of choices based on the GPUs and get the users selection
choice := menu.GenIOMMUMenu("Select a GPU to view the IOMMU groups of", gpus)
// Parse the choice
switch choice {
case "back":
Welcome()
case "":
// If ESC is pressed
fmt.Println("")
os.Exit(0)
default:
config.Gpu_Group = choice
viewGPU(config)
}
}
func viewGPU(config *configs.Config, ext ...int) {
// Clear the screen
command.Clear()
// Set mode to relative
mode := "-r"
// Set mode to relative extended
if len(ext) > 0 {
mode = "-rr"
}
// Get the IOMMU listings for GPUs
group := lsiommu.GetIOMMU("-g", mode, "-i", config.Gpu_Group, "-F", "vendor:,prod_name,optional_revision:,device_id")
// Write a title
title := color.New(color.BgHiBlue, color.White, color.Bold)
title.Println("This list should only show devices related to your GPU (usually 1 video, 1 audio device)")
// Print all the gpus
for _, v := range group {
fmt.Println(v)
}
// Add a new line for tidyness
fmt.Println("")
// Make an empty string
var choice string
// Change choices depending on if we have done an extended search or not
if len(ext) > 0 {
choice = menu.YesNoManual("Use this GPU (any extra devices listed may or may not be linked to it) for passthrough?")
} else {
choice = menu.YesNoEXT("Use this GPU (and related devices) for passthrough?")
}
// Parse the choice
switch choice {
case "":
// If ESC is pressed
fmt.Println("")
os.Exit(0)
case "ext":
// Run an extended relative search
viewGPU(config, 1)
case "n":
// Go back to selecting a gpu
SelectGPU(config)
case "y":
// Get the device ids for the selected gpu using ls-iommu
config.Gpu_IDs = lsiommu.GetIOMMU("-g", mode, "-i", config.Gpu_Group, "--id")
// If the kernel_args file already exists
if fileio.FileExist(config.Path.CMDLINE) {
// Delete it as we will have to make a new one anyway
err := os.Remove(config.Path.CMDLINE)
errorcheck.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE))
}
// Write initial kernel_arg file
configs.Set_Cmdline(config.Gpu_IDs)
// Go to the vbios dumper page
genVBIOS_dumper(config)
case "manual":
config.Gpu_IDs = menu.ManualInput(
"Please manually enter the vendorID:deviceID for every device to use except PCI Express Switches\n"+
"NOTE: All devices sharing the same IOMMU group will still get pulled into the VM!",
"xxxx:yyyy,xxxx:yyyy,xxxx:yyyy",
)
// If the kernel_args file already exists
if fileio.FileExist(config.Path.CMDLINE) {
// Delete it as we will have to make a new one anyway
err := os.Remove(config.Path.CMDLINE)
errorcheck.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE))
}
// Write initial kernel_arg file
configs.Set_Cmdline(config.Gpu_IDs)
// Go to the vbios dumper page
genVBIOS_dumper(config)
}
}

View file

@ -0,0 +1,63 @@
package pages
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/HikariKnight/quickpassthrough/internal/configs"
lsiommu "github.com/HikariKnight/quickpassthrough/internal/lsiommu"
"github.com/HikariKnight/quickpassthrough/pkg/command"
"github.com/HikariKnight/quickpassthrough/pkg/menu"
"github.com/gookit/color"
)
func genVBIOS_dumper(config *configs.Config) {
// Clear the scren
command.Clear()
// Get the program directory
exe, _ := os.Executable()
scriptdir := filepath.Dir(exe)
// If we are using go run use the working directory instead
if strings.Contains(scriptdir, "/tmp/go-build") {
scriptdir, _ = os.Getwd()
}
// Get the vbios path and generate the vbios dumping script
vbios_path := lsiommu.GetIOMMU("-g", "-i", config.Gpu_Group, "--rom")[0]
configs.GenerateVBIOSDumper(vbios_path)
// Write a title
title := color.New(color.BgHiBlue, color.White, color.Bold)
title.Println("Generated \"dump VBIOS\" script")
// Tell users about the VBIOS dumper script
fmt.Print(
"For some GPUs, you will need to dump the VBIOS and pass the\n",
"rom to the VM along with the card in order to get a functional passthrough.\n",
"In many cases you can find your vbios at https://www.techpowerup.com/vgabios/\n",
"\n",
"You can also attempt to dump your own vbios using the script in\n",
fmt.Sprintf("%s/utils/dump_vbios.sh\n", scriptdir),
"\n",
)
// Get the OK press
choice := menu.OkBack("Make sure you run the script with the display-manager stopped using ssh or tty!")
// Parse choice
switch choice {
case "next":
disableVideo(config)
case "back":
SelectGPU(config)
case "":
fmt.Println("")
os.Exit(0)
}
}

View file

@ -0,0 +1,51 @@
package pages
import (
"fmt"
"os"
"github.com/HikariKnight/quickpassthrough/internal/configs"
"github.com/HikariKnight/quickpassthrough/pkg/command"
"github.com/HikariKnight/quickpassthrough/pkg/menu"
"github.com/gookit/color"
)
func disableVideo(config *configs.Config) {
// Clear the screen
command.Clear()
// Write a title
title := color.New(color.BgHiBlue, color.White, color.Bold)
title.Println("Do you want to disable video output on the VFIO card in Linux?")
fmt.Print(
"Disabling video output in Linux for the card you want to use in a VM\n",
"will make it easier to successfully do the passthrough without issues.\n",
"\n",
)
// Make the yesno menu
choice := menu.YesNoBack("Do you want to force disable video output in linux on this card?")
switch choice {
case "y":
// Add disable VFIO video to the config
configs.DisableVFIOVideo(1)
//selectUSB(config)
prepModules(config)
case "n":
// Do not disable VFIO Video
configs.DisableVFIOVideo(0)
//selectUSB(config)
prepModules(config)
case "back":
genVBIOS_dumper(config)
case "":
// If ESC is pressed
fmt.Println("")
os.Exit(0)
}
}

View file

@ -0,0 +1,91 @@
package pages
import (
"fmt"
"os"
"github.com/HikariKnight/quickpassthrough/internal/configs"
lsiommu "github.com/HikariKnight/quickpassthrough/internal/lsiommu"
"github.com/HikariKnight/quickpassthrough/pkg/command"
"github.com/HikariKnight/quickpassthrough/pkg/menu"
"github.com/gookit/color"
)
func selectUSB(config *configs.Config) {
// Clear the screen
command.Clear()
// Get the users GPUs
usbs := lsiommu.GetIOMMU("-u", "-F", "vendor:,prod_name,optional_revision:,device_id")
// Generate a list of choices based on the GPUs and get the users selection
choice := menu.GenIOMMUMenu("Select a USB to view the IOMMU groups of", usbs, 1)
// Parse the choice
switch choice {
case "back":
disableVideo(config)
case "":
// If ESC is pressed
fmt.Println("")
os.Exit(0)
default:
// View the selected GPU
viewUSB(choice, config)
}
}
func viewUSB(id string, config *configs.Config, ext ...int) {
// Clear the screen
command.Clear()
// Set mode to relative
mode := "-r"
// Set mode to relative extended
if len(ext) > 0 {
mode = "-rr"
}
// Get the IOMMU listings for USB controllers
group := lsiommu.GetIOMMU("-u", mode, "-i", id, "-F", "vendor:,prod_name,optional_revision:,device_id")
// Write a title
title := color.New(color.BgHiBlue, color.White, color.Bold)
title.Println("This list should only show the USB controller")
// Print all the usb controllers
for _, v := range group {
fmt.Println(v)
}
// Add a new line for tidyness
fmt.Println("")
// Make an empty string
var choice string
// Ask if we shall use the devices for passthrough
if len(ext) == 0 {
choice = menu.YesNo("Use all listed devices for passthrough?")
} else {
choice = menu.YesNoEXT("Use all listed devices for passthrough?")
}
// Parse the choice
switch choice {
case "":
// If ESC is pressed
fmt.Println("")
os.Exit(0)
case "n":
// Go back to selecting a gpu
selectUSB(config)
case "y":
// Go to the select a usb controller
}
}

View file

@ -0,0 +1,214 @@
package pages
import (
"encoding/base64"
"fmt"
"log"
"os"
"os/user"
"strings"
"syscall"
"github.com/HikariKnight/quickpassthrough/internal/configs"
"github.com/HikariKnight/quickpassthrough/internal/logger"
"github.com/HikariKnight/quickpassthrough/pkg/command"
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
"github.com/HikariKnight/quickpassthrough/pkg/menu"
"github.com/HikariKnight/quickpassthrough/pkg/uname"
"github.com/gookit/color"
"golang.org/x/term"
)
func prepModules(config *configs.Config) {
// If we have files for modprobe
if fileio.FileExist(config.Path.MODPROBE) {
// Configure modprobe
configs.Set_Modprobe(config.Gpu_IDs)
}
// If we have a folder for dracut
if fileio.FileExist(config.Path.DRACUT) {
// Configure dracut
configs.Set_Dracut()
}
// If we have a mkinitcpio.conf file
if fileio.FileExist(config.Path.MKINITCPIO) {
configs.Set_Mkinitcpio()
}
// Configure grub2 here as we can make the config without sudo
if config.Bootloader == "grub2" {
// Write to logger
logger.Printf("Configuring grub2 manually")
configs.Configure_Grub2()
}
// Finalize changes
finalize(config)
}
func finalize(config *configs.Config) {
// Clear the screen
command.Clear()
// Write a title
title := color.New(color.BgHiBlue, color.White, color.Bold)
title.Println("Finalizing configuration")
color.Print(
"The configuration files have been generated and are\n",
"located inside the \"config\" folder\n",
"\n",
"* The \"kernel_args\" file contains kernel arguments that your bootloader needs\n",
//"* The \"quickemu\" folder contains files that might be\n useable for quickemu in the future\n",
"* The files inside the \"etc\" folder must be copied to your system.\n",
" NOTE: Verify that these files are correctly formated/edited!\n",
"* Once all files have been copied, you need to update your bootloader and rebuild\n",
" your initramfs using the tools to do so by your system.\n",
"\n",
"This program can do this for you, however the program will have to\n",
"type your password to sudo using STDIN, to avoid using STDIN press CTRL+C\n",
"and copy the files, update your bootloader and rebuild your initramfs manually.\n",
"If you want to go back and change something, choose Back\n",
"\nNOTE: A backup of the original files from the first run can be found in the backup folder\n",
)
// Make a choice of going next or back
choice := menu.Next("Press Next to continue with sudo using STDIN, ESC to exit or Back to go back.")
// Parse the choice
switch choice {
case "next":
installPassthrough(config)
case "back":
// Go back
disableVideo(config)
}
}
func installPassthrough(config *configs.Config) {
// Get the user data
user, err := user.Current()
if err != nil {
log.Fatalf(err.Error())
}
// Provide a password prompt
fmt.Printf("[sudo] password for %s: ", user.Username)
bytep, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
os.Exit(1)
}
fmt.Print("\n")
// Elevate with sudo
command.Elevate(
base64.StdEncoding.EncodeToString(
bytep,
),
)
// Make an output string
var output string
// Based on the bootloader, setup the configuration
if config.Bootloader == "kernelstub" {
// Write to logger
logger.Printf("Configuring systemd-boot using kernelstub")
// Configure kernelstub
output = configs.Set_KernelStub()
fmt.Printf("%s\n", output)
} else if config.Bootloader == "grubby" {
// Write to logger
logger.Printf("Configuring bootloader using grubby")
// Configure kernelstub
output = configs.Set_Grubby()
fmt.Printf("%s\n", output)
} else if config.Bootloader == "grub2" {
// Write to logger
logger.Printf("Applying grub2 changes")
grub_output, _ := configs.Set_Grub2()
fmt.Printf("%s\n", strings.Join(grub_output, "\n"))
} else {
kernel_args := fileio.ReadFile(config.Path.CMDLINE)
logger.Printf("Unsupported bootloader, please add the below line to your bootloaders kernel arguments\n%s", kernel_args)
}
// A lot of linux systems support modprobe along with their own module system
// So copy the modprobe files if we have them
modprobeFile := fmt.Sprintf("%s/vfio.conf", config.Path.MODPROBE)
if fileio.FileExist(modprobeFile) {
// Copy initramfs-tools module to system
output = configs.CopyToSystem(modprobeFile, "/etc/modprobe.d/vfio.conf")
fmt.Printf("%s\n", output)
}
// Copy the config files for the system we have
initramfsFile := fmt.Sprintf("%s/modules", config.Path.INITRAMFS)
dracutFile := fmt.Sprintf("%s/vfio.conf", config.Path.DRACUT)
if fileio.FileExist(initramfsFile) {
// Copy initramfs-tools module to system
output = configs.CopyToSystem(initramfsFile, "/etc/initramfs-tools/modules")
fmt.Printf("%s\n", output)
// Copy the modules file to /etc/modules
output = configs.CopyToSystem(config.Path.ETCMODULES, "/etc/modules")
fmt.Printf("%s\n", output)
// Write to logger
logger.Printf("Executing: sudo update-initramfs -u")
// Update initramfs
fmt.Println("Executed: sudo update-initramfs -u\nSee debug.log for detailed output")
cmd_out, cmd_err, _ := command.RunErr("sudo", "update-initramfs", "-u")
cmd_out = append(cmd_out, cmd_err...)
// Write to logger
logger.Printf(strings.Join(cmd_out, "\n"))
} else if fileio.FileExist(dracutFile) {
// Copy dracut config to /etc/dracut.conf.d/vfio
output = configs.CopyToSystem(dracutFile, "/etc/dracut.conf.d/vfio")
fmt.Printf("%s\n", output)
// Get systeminfo
sysinfo := uname.New()
// Write to logger
logger.Printf("Executing: sudo dracut -f -v --kver %s", sysinfo.Release)
// Update initramfs
fmt.Printf("Executed: sudo dracut -f -v --kver %s\nSee debug.log for detailed output", sysinfo.Release)
_, cmd_err, _ := command.RunErr("sudo", "dracut", "-f", "-v", "--kver", sysinfo.Release)
// Write to logger
logger.Printf(strings.Join(cmd_err, "\n"))
} else if fileio.FileExist(config.Path.MKINITCPIO) {
// Copy dracut config to /etc/dracut.conf.d/vfio
output = configs.CopyToSystem(config.Path.MKINITCPIO, "/etc/mkinitcpio.conf")
fmt.Printf("%s\n", output)
// Write to logger
logger.Printf("Executing: sudo mkinitcpio -P")
// Update initramfs
fmt.Println("Executed: sudo mkinitcpio -P\nSee debug.log for detailed output")
cmd_out, cmd_err, _ := command.RunErr("sudo", "mkinitcpio", "-P")
cmd_out = append(cmd_out, cmd_err...)
// Write to logger
logger.Printf(strings.Join(cmd_out, "\n"))
}
// Make sure prompt end up on next line
fmt.Print("\n")
}

91
internal/params/params.go Normal file
View file

@ -0,0 +1,91 @@
package params
import (
"fmt"
"os"
"github.com/akamensky/argparse"
)
/*
The whole purpose of this module is to make a struct
to just carry all our parsed arguments around between functions
Create a Params struct with all the argparse arguments
pArg := params.NewParams()
*/
type Params struct {
Flag map[string]bool
FlagCounter map[string]int
IntList map[string][]int
StringList map[string][]string
String map[string]string
}
func (p *Params) addFlag(name string, flag bool) {
p.Flag[name] = flag
}
func (p *Params) addFlagCounter(name string, flag int) {
p.FlagCounter[name] = flag
}
func (p *Params) addIntList(name string, flag []int) {
p.IntList[name] = flag
}
func (p *Params) addStringList(name string, flag []string) {
p.StringList[name] = flag
}
func (p *Params) addString(name string, flag string) {
p.String[name] = flag
}
func NewParams() *Params {
// Setup the parser for arguments
parser := argparse.NewParser("quickpassthrough", "A utility to help you configure your host for GPU Passthrough")
// Configure arguments
gui := parser.Flag("g", "gui", &argparse.Options{
Required: false,
Help: "Launch GUI (placeholder for now)",
})
// Parse arguments
err := parser.Parse(os.Args)
if err != nil {
// In case of error print error and print usage
// This can also be done by passing -h or --help flags
fmt.Print(parser.Usage(err))
os.Exit(4)
}
// Make our struct
pArg := &Params{
Flag: make(map[string]bool),
FlagCounter: make(map[string]int),
IntList: make(map[string][]int),
StringList: make(map[string][]string),
String: make(map[string]string),
}
// Add all parsed arguments to a struct for portability since we will use them all over the program
pArg.addFlag("gui", *gui)
/*pArg.addFlag("gpu", *gpu)
pArg.addFlag("usb", *usb)
pArg.addFlag("nic", *nic)
pArg.addFlag("sata", *sata)
pArg.addFlagCounter("related", *related)
pArg.addStringList("ignore", *ignore)
pArg.addIntList("iommu_group", *iommu_group)
pArg.addFlag("kernelmodules", *kernelmodules)
pArg.addFlag("legacyoutput", *legacyoutput)
pArg.addFlag("id", *id)
pArg.addFlag("pciaddr", *pciaddr)
pArg.addFlag("rom", *rom)
pArg.addString("format", *format)*/
return pArg
}

24
internal/ui_main.go Normal file
View file

@ -0,0 +1,24 @@
package internal
// A simple example demonstrating the use of multiple text input components
// from the Bubbles component library.
import (
"os"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/HikariKnight/quickpassthrough/internal/pages"
tea "github.com/charmbracelet/bubbletea"
)
// This is where we build everything
func Tui() {
// Log all errors to a new logfile (super useful feature of BubbleTea!)
os.Remove("debug.log")
logfile, err := tea.LogToFile("debug.log", "")
errorcheck.ErrorCheck(err, "Error creating log file")
defer logfile.Close()
// New WIP Tui
pages.Welcome()
}

View file

@ -1,152 +0,0 @@
#!/bin/bash
# shellcheck disable=SC1091
function make_BACKUP () {
local BACKUPDIR
BACKUPDIR="$SCRIPTDIR/backup"
if [ ! -d "$BACKUPDIR" ];
then
# Make the backup directories and backup the files
if [ -d "/etc/initramfs-tools" ];
then
mkdir -p "$BACKUPDIR/etc/initramfs-tools"
cp -v "/etc/initramfs-tools/modules" "$BACKUPDIR/etc/initramfs-tools/modules"
cp -v "/etc/modules" "$BACKUPDIR/etc/modules"
elif [ -d "/etc/dracut.conf" ];
then
mkdir -p "$BACKUPDIR/etc/dracut.conf.d"
if [ -f "/etc/dracut.conf.d/10-vfio.conf" ];
then
cp -v "/etc/dracut.conf.d/10-vfio.conf" "$BACKUPDIR/etc/dracut.conf.d/10-vfio.conf"
fi
elif [ -f "/etc/mkinitcpio.conf" ];
then
mkdir -p "$BACKUPDIR/etc"
cp -v "/etc/mkinitcpio.conf" "$BACKUPDIR/etc/mkinitcpio.conf"
fi
if [ -f "/etc/default/grub" ];
then
mkdir -p "$BACKUPDIR/etc/default"
cp -v "/etc/default/grub" "$BACKUPDIR/etc/default/grub"
fi
if [ -d "/etc/modprobe.d" ];
then
mkdir -p "$BACKUPDIR/etc/modprobe.d"
# If a vfio.conf file exists, backup that too
if [ -f "/etc/modprobe.d/vfio.conf" ];
then
cp -v "/etc/modprobe.d/vfio.conf" "$BACKUPDIR/etc/modprobe.d/vfio.conf"
fi
fi
printf "Backup completed!\n"
else
echo "
A backup already exists!
backup skipped.
"
fi
}
function copy_FILES () {
echo "Starting copying files to the system!"
if [ -d "/etc/modprobe.d" ];
then
sudo cp -v "$SCRIPTDIR/$MODPROBE/vfio.conf" "/etc/modprobe.d/vfio.conf"
fi
if [ -d "/etc/initramfs-tools" ];
then
sudo cp -v "$SCRIPTDIR/$ETCMODULES" "/etc/modules"
sudo cp -v "$SCRIPTDIR/$INITRAMFS/modules" "/etc/initramfs-tools/modules"
echo "
Rebuilding initramfs"
sudo update-initramfs -u
elif [ -f "/etc/dracut.conf" ];
then
cp -v "$SCRIPTDIR/$DRACUT/10-vfio.conf" "/etc/dracut.conf.d/10-vfio.conf"
echo "
Rebuilding initramfs"
sudo dracut -f -v --kver "$(uname -r)"
elif [ -f "/etc/mkinitcpio.conf" ];
then
cp -v "$SCRIPTDIR/$MKINITCPIO" "/etc/mkinitcpio.conf"
echo "
Rebuilding initramfs"
sudo mkinitcpio -P
else
echo "
Unsupported initramfs infrastructure
In order to make vfio work, please add these modules to your
initramfs and make them load early, then rebuild initramfs.
vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd
Press ENTER to continue once you have done the above."
read -r
fi
}
function apply_CHANGES () {
clear
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
echo "Configuration is now complete and these files have been generated for your system:
$SCRIPTDIR/$ETCMODULES
$SCRIPTDIR/$INITRAMFS/modules
$SCRIPTDIR/$MODPROBE/vfio.conf
By proceeding, a backup of your system's version of these files will be placed in
$SCRIPTDIR/backup
unless a backup already exist.
Then the files above will be copied to your system followed by running followed by updating your
initramfs and then attempt adding new kernel arguments to your bootloader."
read -r -p "Do you want to proceed with the installation of the files? (no=quit) [Y/n]: " YESNO
case "${YESNO}" in
[Nn]*)
exit 1
;;
*)
make_BACKUP
copy_FILES
exec "$SCRIPTDIR/lib/set_CMDLINE.sh"
;;
esac
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
apply_CHANGES
}
main

View file

@ -1,41 +0,0 @@
#!/bin/bash
function get_GPU () {
clear
printf "These are your graphic cards, they have to be in separate groups.
The graphic card you want to passthrough cannot be in a group with other devices that
does not belong to itself. Both cards must also have unique hardware ids [xxxx:yyyy]!:
"
echo "#------------------------------------------#"
"$SCRIPTDIR/utils/ls-iommu" -g -F name,device_id,optional_revision
echo "#------------------------------------------#"
printf "
Press q to quit
"
read -r -p "Which group number do you want to check?: " IOMMU_GROUP
case "${IOMMU_GROUP}" in
[1-9]*)
exec "$SCRIPTDIR/lib/get_GPU_GROUP.sh" "$IOMMU_GROUP"
;;
[Qq]*)
echo "Aborted, your setup is incomplete!
DO NOT use any of the files from $SCRIPTDIR/config !
"
;;
*)
exec "$SCRIPTDIR/lib/get_GPU.sh"
;;
esac
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
get_GPU
}
main

View file

@ -1,84 +0,0 @@
#!/bin/bash
# shellcheck disable=SC1091
function get_GPU_GROUP () {
clear
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
printf "For this card to be passthrough-able, it must contain only:
* The GPU/Graphic card
* The GPU Audio Controller
Optionally it may also include:
* GPU USB Host Controller
* GPU Serial Port
* GPU USB Type-C UCSI Controller
* PCI Bridge (if they are in their own IOMMU groups)
NOTE: If the audio controller is missing, it might be in a separate IOMMU group, please run
\"%s/utils/ls-iommu\" -i %s -rr -F \"pciaddr,subclass_name:,name,device_id,optional_revision\"
and add the PCI address and deviceID to the config manually (this is not done automatically because the -rr flag is unreliable if the system has 2 cards from the same vendor)
" "$SCRIPTDIR" "$1"
echo "#------------------------------------------#"
"$SCRIPTDIR/utils/ls-iommu" -i "$1" -r -F subclass_name:,name,device_id,optional_revision # | cut -d " " -f 1-5,6- | perl -pe "s/\[[0-9a-f]{4}\]: //"
echo "#------------------------------------------#"
printf "
To use any of these devices for passthrough ALL of them (except PCI bridges in their own IOMMU groups) has to be passed through to the VMs\
To return to the previous page just press ENTER without typing in anything.
"
read -r -p "Do you want to use these devices for passthrough? [y/N]: " YESNO
case "${YESNO}" in
[Yy]*)
# Get the hardware ids from the selected group
local GPU_DEVID
GPU_DEVID=$("$SCRIPTDIR/utils/ls-iommu" -i "$1" -r --id | perl -pe "s/\n/,/" | perl -pe "s/,$/\n/")
# Get the PCI ids
local PCI_ID
PCI_ID=$("$SCRIPTDIR/utils/ls-iommu" -i "$1" -r --pciaddr | perl -pe "s/([0-9a-f]{2}:[0-9a-f]{2}.[0-9a-f]{1})\n/\"\1\" /" | perl -pe "s/\s$//")
# Write the GPU_PCI_IDs to the config that quickemu might make use of in the future
echo "GPU_PCI_ID=($PCI_ID)
USB_CTL_ID=()" > "$SCRIPTDIR/$QUICKEMU/qemu-vfio_vars.conf"
# Get the GPU ROM
"$SCRIPTDIR/lib/get_GPU_ROM.sh" "$1"
# Start setting up modules
if [ -d "/etc/initramfs-tools" ];
then
exec "$SCRIPTDIR/lib/set_INITRAMFSTOOLS.sh" "$GPU_DEVID"
elif [ -d "/etc/dracut.conf" ];
then
exec "$SCRIPTDIR/lib/set_DRACUT.sh" "$GPU_DEVID"
elif [ -f "/etc/mkinitcpio.conf" ];
then
exec "$SCRIPTDIR/lib/set_MKINITCPIO.sh" "$GPU_DEVID"
else
# Bind GPU to VFIO
"$SCRIPTDIR/lib/set_VFIO.sh" "$GPU_DEVID"
# Configure modprobe
"$SCRIPTDIR/lib/set_MODPROBE.sh" "$GPU_DEVID"
fi
;;
*)
exec "$SCRIPTDIR/lib/get_GPU.sh"
;;
esac
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
get_GPU_GROUP "$1"
}
main "$1"

View file

@ -1,70 +0,0 @@
#!/bin/bash
# shellcheck disable=SC1091,SC2024
function get_GPU_ROM () {
clear
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
VBIOS_PATH=$("$SCRIPTDIR/utils/ls-iommu" -g -i "$1" --rom)
echo "We will now attempt to dump the vbios of your selected GPU.
Passing a VBIOS rom to the card used for passthrough is required for some cards, but not all.
Some cards also requires you to patch your VBIOS romfile, check online if this is neccessary for your card.
The VBIOS will be read from $VBIOS_PATH
This process will require the use of sudo and will run the following commands:
echo 1 | sudo tee $VBIOS_PATH
sudo cat $VBIOS_PATH > $SCRIPTDIR/$QUICKEMU/vfio_card.rom
echo 0 | sudo tee $VBIOS_PATH
"
read -r -p "Do you want to dump the VBIOS, choosing N will skip this step [y/N]: " YESNO
case "${YESNO}" in
[Yy]*)
echo 1 | sudo tee "$VBIOS_PATH"
sudo cat "$VBIOS_PATH" > "$SCRIPTDIR/$QUICKEMU/vfio_card.rom"
sudo md5sum "$VBIOS_PATH" | cut -d " " -f 1 > "$SCRIPTDIR/$QUICKEMU/vfio_card.rom.md5"
local ROM_MD5
ROM_MD5=$(sudo md5sum "$VBIOS_PATH" | cut -d " " -f 1)
echo 0 | sudo tee "$VBIOS_PATH"
local ROMFILE_MD5
ROMFILE_MD5=$(md5sum "$SCRIPTDIR/$QUICKEMU/vfio_card.rom" | cut -d " " -f 1)
if [ -f "$SCRIPTDIR/$QUICKEMU/vfio_card.rom" ];
then
if [ "$ROM_MD5" == "$ROMFILE_MD5" ];
then
echo "Checksums match!"
echo "Dumping of VBIOS successful!"
echo 'GPU_ROMFILE="vfio_card.rom"' >> "$SCRIPTDIR/$QUICKEMU/qemu-vfio_vars.conf"
read -r -p "Press ENTER to continue."
else
echo "Checksums does not match!"
echo "Dumping of VBIOS failed, skipping romfile"
mv "$SCRIPTDIR/$QUICKEMU/vfio_card.rom" "$SCRIPTDIR/$QUICKEMU/vfio_card.rom.fail"
echo 'GPU_ROMFILE=""' >> "$SCRIPTDIR/$QUICKEMU/qemu-vfio_vars.conf"
read -r -p "Press ENTER to continue."
fi
else
echo 'GPU_ROMFILE=""' >> "$SCRIPTDIR/$QUICKEMU/qemu-vfio_vars.conf"
fi
;;
[Nn]*)
echo 'GPU_ROMFILE=""' >> "$SCRIPTDIR/$QUICKEMU/qemu-vfio_vars.conf"
;;
*)
echo 'GPU_ROMFILE=""' >> "$SCRIPTDIR/$QUICKEMU/qemu-vfio_vars.conf"
;;
esac
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
get_GPU_ROM "$1"
}
main "$1"

View file

@ -1,39 +0,0 @@
#!/bin/bash
function get_USB_CTL () {
clear
printf "THIS STEP IS OPTIONAL IF YOU DO NOT PLAN TO USE ANYTHING OTHER THAN MOUSE AND KEYBOARD!
The USB Controller you want to passthrough cannot be in a group with other devices.
Passing through a whole USB Controller (a set of hardwired 1-4 usb ports on the motherboard)
is only needed if you intend to use other devices than just mouse and keyboard with the VFIO enabled VM.
"
echo "#------------------------------------------#"
"$SCRIPTDIR/utils/ls-iommu" -u -F name,device_id,optional_revision
echo "#------------------------------------------#"
printf "
Press q to quit
"
read -r -p "Which group number do you want to check?: " IOMMU_GROUP
case "${IOMMU_GROUP}" in
[1-9]*)
exec "$SCRIPTDIR/lib/get_USB_CTL_GROUP.sh" "$IOMMU_GROUP"
;;
[Qq]*)
exec "$SCRIPTDIR/lib/apply_CHANGES.sh"
;;
*)
exec "$SCRIPTDIR/lib/apply_CHANGES.sh"
;;
esac
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
get_USB_CTL
}
main

View file

@ -1,48 +0,0 @@
#!/bin/bash
# shellcheck disable=SC1091
function get_USB_CTL_GROUP () {
clear
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
printf "
For this USB controller device to be passthrough-able, it must be the ONLY device in this group!
Passing through more than just the USB controller can in some cases cause system issues
if you do not know what you are doing.
"
echo "#------------------------------------------#"
"$SCRIPTDIR/utils/ls-iommu" -i "$1" -F subclass_name:,name,device_id,optional_revision
echo "#------------------------------------------#"
printf "
To use any of the devices shown for passthrough, all of them have to be passed through
To return to the previous page just press ENTER.
"
read -r -p "Do you want to use the displayed devices for passthrough? [y/N]: " YESNO
case "${YESNO}" in
[Yy]*)
# Get the PCI ids
local PCI_ID
PCI_ID=$("$SCRIPTDIR/utils/ls-iommu" -i "$1" --pciaddr | perl -pe "s/([0-9a-f]{4}:[0-9a-f]{2}:[0-9a-f]{2}.[0-9a-f]{1})\n/\"\1\" /" | perl -pe "s/\s$//")
# Replace the blank USB_CTL_ID with the PCI_ID for the usb controller the user wants to pass through
perl -pi -e "s/USB_CTL_ID=\(\)/USB_CTL_ID=\($PCI_ID\)/" "$SCRIPTDIR/$QUICKEMU/qemu-vfio_vars.conf"
exec "$SCRIPTDIR/lib/apply_CHANGES.sh"
;;
*)
exec "$SCRIPTDIR/lib/get_USB_CTL.sh"
;;
esac
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
get_USB_CTL_GROUP "$1"
}
main "$1"

View file

@ -1,18 +0,0 @@
#!/bin/bash
# shellcheck disable=SC2034
MODPROBE="config/etc/modprobe.d"
INITRAMFS="config/etc/initramfs-tools"
ETCMODULES="config/etc/modules"
DEFAULT="config/etc/default"
QUICKEMU="config/quickemu"
DRACUT="config/etc/dracut.conf.d"
MKINITCPIO="config/etc/mkinitcpio.conf"
READAPI="wget -O-"
DOWNLOAD="wget -0 \"$SCRIPTDIR/utils/ls-iommu.tar.gz\""
# Get the tool to use for downloading
if [ -f "/usr/bin/curl" ];
then
READAPI="curl"
DOWNLOAD="curl -JLo ls-iommu.tar.gz"
fi

View file

@ -1,188 +0,0 @@
#!/bin/bash
# shellcheck disable=SC1091
# Function to configure systemd-boot using kernelstub
function set_KERNELSTUB () {
# Separator
printf "
############################################################
"
# Tell what we are going to do
echo "Adding vfio kernel arguments to systemd-boot using kernelstub"
# Get the config paths
source "${SCRIPTDIR}/lib/paths.sh"
# Check if systemd-boot already has vfio parameters from before
KERNELSTUB_TEST=$(sudo kernelstub -p 2>&1 | grep "Kernel Boot Options" | perl -pe "s/.+Kernel Boot Options:\..+(vfio_pci.ids=.+ ).+/\1/")
# If there are already vfio_pci parameters in kernelstub
if [[ "$KERNELSTUB_TEST" =~ vfio_pci.ids ]] ;
then
# Remove the old parameters
sudo kernelstub -d "$KERNELSTUB_TEST"
sudo kernelstub -d "vfio_pci.disable_vga=1"
sudo kernelstub -d "vfio_pci.disable_vga=0"
fi
# Apply new parameters
CMDLINE=$(cat "${SCRIPTDIR}/config/kernel_args")
sudo kernelstub -a "$CMDLINE"
}
# Function to configure grub
function set_GRUB () {
# Separator
printf "
############################################################
"
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
local CMDLINE
CMDLINE=$(cat "$SCRIPTDIR/config/kernel_args")
# HIGHLY EXPERIMENTAL!
local GRUB_CMDLINE
local GRUB_CMDLINE_LINUX
# Check if there is a GRUB_CMDLINE_LINUX_DEFAULT line in grub config
if grep -q "GRUB_CMDLINE_LINUX_DEFAULT=" "$SCRIPTDIR/$DEFAULT/grub" ;
then
# Update the GRUB_CMDLINE_LINUX_DEFAULT line
GRUB_CMDLINE=$(grep -P "^GRUB_CMDLINE_LINUX_DEFAULT" "/etc/default/grub" | perl -pe "s/GRUB_CMDLINE_LINUX_DEFAULT=\"(.+)\"/\1/" | perl -pe "s/(amd|intel)_iommu=(on|1)|iommu=(pt|on)|vfio_pci.ids=.+|vfio_pci.disable_vga=\d{1}//g" | perl -pe "s/(^\s+|\s+$)//g")
GRUB_CMDLINE_LINUX=$(grep -P "^GRUB_CMDLINE_LINUX_DEFAULT" "/etc/default/grub")
perl -pi -e "s/${GRUB_CMDLINE_LINUX}/GRUB_CMDLINE_LINUX_DEFAULT=\"${GRUB_CMDLINE} ${CMDLINE}\"/" "${SCRIPTDIR}/$DEFAULT/grub"
else
# Update the GRUB_CMDLINE_LINUX line
GRUB_CMDLINE=$(grep -P "^GRUB_CMDLINE_LINUX" "/etc/default/grub" | perl -pe "s/GRUB_CMDLINE_LINUX=\"(.+)\"/\1/" | perl -pe "s/(amd|intel)_iommu=(on|1)|iommu=(pt|on)|vfio_pci.ids=.+|vfio_pci.disable_vga=\d{1}//g" | perl -pe "s/(^\s+|\s+$)//g")
GRUB_CMDLINE_LINUX=$(grep -P "^GRUB_CMDLINE_LINUX" "/etc/default/grub")
perl -pi -e "s/${GRUB_CMDLINE_LINUX}/GRUB_CMDLINE_LINUX=\"${GRUB_CMDLINE} ${CMDLINE}\"/" "${SCRIPTDIR}/$DEFAULT/grub"
fi
echo "The script will now replace your default grub file with a new one.
Then attempt to update grub and generate a new grub.cfg.
If generating the grub.cfg file fails, you can find a backup of your grub default file here:
$SCRIPTDIR/backup/etc/default/grub
"
read -r -p "Press ENTER to continue"
sudo cp -v "$SCRIPTDIR/$DEFAULT/grub" "/etc/default/grub"
# Generate grub.cfg
if [ -d "/boot/grub" ];
then
sudo grub-mkconfig -o "/boot/grub/grub.cfg"
else
sudo grub-mkconfig -o "/boot/grub2/grub.cfg"
fi
echo ""
read -r -p "Please verify there were no errors generating the grub.cfg file, then press ENTER"
}
function show_FINISH () {
# Separator
printf "
############################################################
"
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
local CMDLINE
CMDLINE=$(cat "$SCRIPTDIR/config/kernel_args")
echo "Configuration is now complete!"
if [ "$1" == 0 ];
then
printf "For VFIO to work properly you need to make sure these kernel parameters are in your bootloader entry:
#-----------------------------------------------#
%s
#-----------------------------------------------#
" "$CMDLINE"
fi
echo "Restart your system and run
\"$SCRIPTDIR/vfio-verify\"
to check if your GPU is properly set up.
If the graphic card is bound to vfio-pci then you can
proceed to add it to your virtual machines.
A backup the files we replaced on your system can be found inside
$SCRIPTDIR/backup/
In order to restore these files just copy them back to your system and
rebuild your initramfs image.
You can remove the the vfio_pci kernel arguments from the linux line in your bootloader
to disable/unbind the graphic card from the vfio driver on boot.
The files inside \"$SCRIPTDIR/$QUICKEMU\" are currently unused files, however they provide
the required information that the QuickEMU project can hook into and use to add support for VFIO enabled VMs.
The PCI Devices with these IDs are what you should add to your VMs using Virt Manager:
NOTE: Some AMD GPUs will require the vendor-reset kernel module from https://github.com/gnif/vendor-reset to be installed!"
source "${SCRIPTDIR}/config/quickemu/qemu-vfio_vars.conf"
for dev in "${GPU_PCI_ID[@]}"
do
echo "* $dev"
done
for dev in "${USB_CTL_ID[@]}"
do
echo "* $dev"
done
echo "
To add the graphic card to your VM using qemu directly, use the following arguments:"
for dev in "${GPU_PCI_ID[@]}"
do
echo -n "-device vfio-pci,host=$dev "
done
printf "\n"
echo "
For performance tuning and advanced configuration look at:
https://github.com/HikariKnight/vfio-setup-docs/wiki"
}
function set_CMDLINE () {
# Make a variable to tell if
local BOOTLOADER_AUTOCONFIG
BOOTLOADER_AUTOCONFIG=0
# If kernelstub is detected (program to manage systemd-boot)
if which kernelstub > /dev/null 2>&1 ;
then
# Configure kernelstub
set_KERNELSTUB
BOOTLOADER_AUTOCONFIG=1
fi
# If grub exists
if which grub-mkconfig > /dev/null 2>&1 ;
then
# Configure grub
set_GRUB
BOOTLOADER_AUTOCONFIG=1
fi
show_FINISH $BOOTLOADER_AUTOCONFIG
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
set_CMDLINE
}
main

View file

@ -1,30 +0,0 @@
#!/bin/bash
# shellcheck disable=SC1091
function set_DRACUT () {
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
# Write the dracut config
echo "add_drivers+=\" vfio_pci vfio vfio_iommu_type1 vfio_virqfd \"" > "$SCRIPTDIR/$DRACUT/10-vfio.conf"
# Get the kernel_args file content
CMDLINE=$(cat "$SCRIPTDIR/config/kernel_args")
# Update kernel_args to load the vfio_pci module early in dracut (as dracut uses kernel arguments for early loading)
echo "$CMDLINE rd.driver.pre=vfio_pci" > "$SCRIPTDIR/config/kernel_args"
# Bind GPU to VFIO
"$SCRIPTDIR/lib/set_VFIO.sh" "$1"
# Configure modprobe
exec "$SCRIPTDIR/lib/set_MODPROBE.sh" "$1"
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
set_DRACUT "$1"
}
main "$1"

View file

@ -1,66 +0,0 @@
#!/bin/bash
# shellcheck disable=SC1091
function insert_INITRAMFSTOOLS() {
# Get the header and enabled modules separately from the /etc/modules file
local MODULES_HEADER
local MODULES_ENABLED
local VENDOR_RESET
MODULES_HEADER=$(head -n "$1" "$2" | grep -P "^#" | grep -v "# Added by quickpassthrough")
MODULES_ENABLED=$(grep -vP "^#" "$2" | grep -v "vendor-reset" | perl -pe "s/^\n//")
VENDOR_RESET=0
# If vendor-reset is present
if grep -q "vendor-reset" "$2" ;
then
VENDOR_RESET=1
fi
# Write header
echo "$MODULES_HEADER" > "$2"
# If vendor-reset existed from before
if [ $VENDOR_RESET == 1 ];
then
# Write vendor-reset as the first module!
echo "vendor-reset" >> "$2"
fi
# Append vfio
printf "
# Added by quickpassthrough #
vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd
#############################
" >> "$2"
# Write the previously enabled modules under vfio in the load order
echo "$MODULES_ENABLED" >> "$2"
}
function set_INITRAMFSTOOLS () {
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
# Insert modules in the correct locations as early as possible without
# conflicting with vendor-reset module if it is enabled
insert_INITRAMFSTOOLS 4 "$SCRIPTDIR/$ETCMODULES"
insert_INITRAMFSTOOLS 11 "$SCRIPTDIR/$INITRAMFS/modules"
# Bind GPU to VFIO
"$SCRIPTDIR/lib/set_VFIO.sh" "$1"
# Configure modprobe
exec "$SCRIPTDIR/lib/set_MODPROBE.sh" "$1"
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
set_INITRAMFSTOOLS "$1"
}
main "$1"

View file

@ -1,35 +0,0 @@
#!/bin/bash
# shellcheck disable=SC1091
function set_MKINITCPIO () {
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
# Grab the current modules but exclude vfio and vendor-reset
CURRENTMODULES=$(grep -P "^MODULES" "$SCRIPTDIR/$MKINITCPIO" | perl -pe "s/MODULES=\((.+)\)/\1/")
MODULES="$(grep -P "^MODULES" "$SCRIPTDIR/$MKINITCPIO" | perl -pe "s/MODULES=\((.+)\)/\1/" | perl -pe "s/\s?(vfio_iommu_type1|vfio_pci|vfio_virqfd|vfio|vendor-reset)\s?//g")"
# Check if vendor-reset is present
if [[ $CURRENTMODULES =~ "vendor-reset" ]];
then
# Inject vfio modules with vendor-reset
perl -pi -e "s/MODULES=\(${CURRENTMODULES}\)/MODULES=\(vendor-reset vfio vfio_iommu_type1 vfio_pci vfio_virqfd ${MODULES}\)/" "$SCRIPTDIR/$MKINITCPIO"
else
# Inject vfio modules
perl -pi -e "s/MODULES=\(${CURRENTMODULES}\)/MODULES=\(vfio vfio_iommu_type1 vfio_pci vfio_virqfd ${MODULES}\)/" "$SCRIPTDIR/$MKINITCPIO"
fi
# Bind GPU to VFIO
"$SCRIPTDIR/lib/set_VFIO.sh" "$1"
# Configure modprobe
exec "$SCRIPTDIR/lib/set_MODPROBE.sh" "$1"
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
set_MKINITCPIO "$1"
}
main "$1"

View file

@ -1,41 +0,0 @@
#!/bin/bash
# shellcheck disable=SC1091
function set_MODPROBE () {
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
# Assign the GPU device ids to a variable
GPU_DEVID="$1"
# If VGA is disabled
if grep -q "vfio_pci.disable_vga=1" "$SCRIPTDIR/config/kernel_args" ;
then
# Modify our GPU_DEVID line to containe disable_vga=1
GPU_DEVID="${GPU_DEVID} disable_vga=1"
fi
# Write the vfio modprobe config
printf "## This is an autogenerated file that stubs your graphic card for use with vfio
## This file should be placed inside /etc/modprobe.d/
# Uncomment the line below to \"hardcode\" your graphic card to be bound to the vfio-pci driver.
# In most cases this should not be neccessary, it will also prevent you from turning off vfio in the bootloader.
#options vfio_pci ids=%s
# Make sure vfio_pci is loaded before these modules: nvidia, nouveau, amdgpu and radeon
softdep nvidia pre: vfio vfio_pci
softdep nouveau pre: vfio vfio_pci
softdep amdgpu pre: vfio vfio_pci
softdep radeon pre: vfio vfio_pci
" "${GPU_DEVID}" > "$SCRIPTDIR/$MODPROBE/vfio.conf"
exec "$SCRIPTDIR/lib/get_USB_CTL.sh"
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
set_MODPROBE "$1"
}
main "$1"

View file

@ -1,47 +0,0 @@
#!/bin/bash
# shellcheck disable=SC1091
function set_VFIO () {
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
# Assign the GPU device ids to a variable
GPU_DEVID="$1"
# Get the kernel_args file content
CMDLINE=$(cat "$SCRIPTDIR/config/kernel_args")
# Ask if we shall disable video output on this card
echo "
Disabling video output in Linux for the card you want to use in a VM
will make it easier to successfully do the passthrough without issues."
read -r -p "Do you want to force disable video output in linux on this card? [Y/n]: " DISABLE_VGA
case "${DISABLE_VGA}" in
[Yy]*)
# Update kernel_args file
echo "${CMDLINE} vfio_pci.ids=${GPU_DEVID} vfio_pci.disable_vga=1" > "$SCRIPTDIR/config/kernel_args"
# Update GPU_DEVID
GPU_DEVID="$GPU_DEVID disable_vga=1"
;;
[Nn]*)
# Update kernel_args file
echo "${CMDLINE} vfio_pci.ids=${GPU_DEVID}" > "$SCRIPTDIR/config/kernel_args"
;;
*)
# Update kernel_args file
echo "${CMDLINE} vfio_pci.ids=${GPU_DEVID} vfio_pci.disable_vga=1" > "$SCRIPTDIR/config/kernel_args"
# Update GPU_DEVID
GPU_DEVID="$GPU_DEVID disable_vga=1"
;;
esac
}
function main () {
SCRIPTDIR=$(dirname "$(realpath "$0")" | perl -pe "s/\/\.\.\/lib//" | perl -pe "s/\/lib$//")
set_VFIO "$1"
}
main "$1"

98
pkg/command/command.go Normal file
View file

@ -0,0 +1,98 @@
package command
import (
"bytes"
"encoding/base64"
"io"
"os"
"os/exec"
"time"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
)
func Run(binary string, args ...string) ([]string, error) {
var stdout, stderr bytes.Buffer
// Configure the ls-iommu c--ommand
cmd := exec.Command(binary, args...)
cmd.Stderr = &stderr
cmd.Stdout = &stdout
// Execute the command
err := cmd.Run()
// Read the output
output, _ := io.ReadAll(&stdout)
// Get the output
outputs := []string{}
outputs = append(outputs, string(output))
// Return our list of items
return outputs, err
}
// This function is just like command.Run() but also returns STDERR
func RunErr(binary string, args ...string) ([]string, []string, error) {
var stdout, stderr bytes.Buffer
// Configure the ls-iommu c--ommand
cmd := exec.Command(binary, args...)
cmd.Stderr = &stderr
cmd.Stdout = &stdout
// Execute the command
err := cmd.Run()
// Read the output
output, _ := io.ReadAll(&stdout)
outerr, _ := io.ReadAll(&stderr)
// Get the output
var outputs, outerrs []string
outputs = append(outputs, string(output))
outerrs = append(outerrs, string(outerr))
// Return our list of items
return outputs, outerrs, err
}
// This functions runs the command "sudo -Sk -- echo", this forces sudo
// to re-authenticate and lets us enter the password to STDIN
// giving us the ability to run sudo commands
func Elevate(password string) {
// Do a simple sudo command to just authenticate with sudo
cmd := exec.Command("sudo", "-S", "--", "echo")
// Wait for 500ms, if the password is correct, sudo will return immediately
cmd.WaitDelay = 1000 * time.Millisecond
// Open STDIN
stdin, err := cmd.StdinPipe()
errorcheck.ErrorCheck(err, "\nFailed to get sudo STDIN")
// Start the authentication
cmd.Start()
// Get the passed password
pw, _ := base64.StdEncoding.DecodeString(password)
_, err = stdin.Write([]byte(string(pw) + "\n"))
errorcheck.ErrorCheck(err, "\nFailed at typing to STDIN")
// Clear the password
pw = nil
password = ""
stdin.Close()
// Wait for the sudo prompt (If the correct password was given, it will not stay behind)
err = cmd.Wait()
errorcheck.ErrorCheck(err, "\nError, password given was wrong")
}
// Function to just clear the terminal
func Clear() {
c := exec.Command("clear")
c.Stdout = os.Stdout
c.Run()
}

99
pkg/fileio/fileio.go Normal file
View file

@ -0,0 +1,99 @@
package fileio
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
)
/*
* This just implements repetetive tasks I have to do with files
*/
// Creates a file and appends the content to the file (ending newline must be supplied with content string)
func AppendContent(content string, fileName string) {
// Open the file
f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm)
errorcheck.ErrorCheck(err, fmt.Sprintf("Error opening \"%s\" for writing", fileName))
defer f.Close()
// Write the content
_, err = f.WriteString(content)
errorcheck.ErrorCheck(err, fmt.Sprintf("Error writing to %s", fileName))
}
// Reads the file and returns a stringlist with each line
func ReadLines(fileName string) []string {
content, err := os.Open(fileName)
errorcheck.ErrorCheck(err, fmt.Sprintf("Error reading file %s", fileName))
defer content.Close()
// Make a list of lines
var lines []string
// Read the file line by line
scanner := bufio.NewScanner(content)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
// Return all the lines
return lines
}
// Reads a file and returns all the content as a string
func ReadFile(fileName string) string {
// Read the whole file
content, err := os.ReadFile(fileName)
errorcheck.ErrorCheck(err, fmt.Sprintf("Failed to ReadFile on %s", fileName))
// Return all the lines as one string
return string(content)
}
// Checks if a file exists and returns a bool
func FileExist(fileName string) bool {
var exist bool
// Check if the file exists
if _, err := os.Stat(fileName); !errors.Is(err, os.ErrNotExist) {
// Set the value to true
exist = true
} else {
// Set the value to false
exist = false
}
// Return if the file exists
return exist
}
// Copies a FILE from source to dest
func FileCopy(sourceFile, destFile string) {
// Get the file info
filestat, err := os.Stat(sourceFile)
errorcheck.ErrorCheck(err, "Error getting fileinfo of: %s", sourceFile)
// If the file is a regular file
if filestat.Mode().IsRegular() {
// Open the source file for reading
source, err := os.Open(sourceFile)
errorcheck.ErrorCheck(err, "Error opening %s for copying", sourceFile)
defer source.Close()
// Create the destination file
dest, err := os.Create(destFile)
errorcheck.ErrorCheck(err, "Error creating %s", destFile)
defer dest.Close()
// Copy the contents of source to dest using io
_, err = io.Copy(dest, source)
errorcheck.ErrorCheck(err, "Failed to copy \"%s\" to \"%s\"", sourceFile, destFile)
}
}

40
pkg/menu/genmenu.go Normal file
View file

@ -0,0 +1,40 @@
package menu
import (
"regexp"
"github.com/gookit/color"
"github.com/nexidian/gocliselect"
)
func GenIOMMUMenu(msg string, choices []string, none_option ...int) string {
// Make a regex to get the iommu group
iommu_group_regex := regexp.MustCompile(`(\d{1,3})`)
// Make the menu
menu := gocliselect.NewMenu(msg)
// For each choice passed
for _, choice := range choices {
// Get the iommu group
iommuGroup := iommu_group_regex.FindString(choice)
// Add the choice with shortened vendor name and the iommu group as the return value
menu.AddItem(choice, iommuGroup)
}
// If none_option is higher than 0
if len(none_option) > 0 {
// Add a skip option
menu.AddItem(color.Bold.Sprint("Skip/None"), "skip")
}
// Add a go back option
menu.AddItem(color.Bold.Sprint("Go Back"), "back")
// Display the menu
choice := menu.Display()
// Return the value selected
return choice
}

27
pkg/menu/manual.go Normal file
View file

@ -0,0 +1,27 @@
package menu
import (
"fmt"
"strings"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/gookit/color"
)
func ManualInput(msg string, format string) []string {
// Print the title
color.Bold.Println(msg)
// Tell user the format to use
color.Bold.Printf("The format is %s\n", format)
// Get the user input
var input string
_, err := fmt.Scan(&input)
errorcheck.ErrorCheck(err)
input_list := strings.Split(input, ",")
// Return the input
return input_list
}

20
pkg/menu/next.go Normal file
View file

@ -0,0 +1,20 @@
package menu
import (
"github.com/gookit/color"
"github.com/nexidian/gocliselect"
)
// Make a Next menu
func Next(msg string) string {
// Make the menu
menu := gocliselect.NewMenu(msg)
menu.AddItem(color.Bold.Sprint("Next"), "next")
menu.AddItem(color.Bold.Sprint("Go Back"), "back")
// Display the menu
choice := menu.Display()
// Return the value selected
return choice
}

33
pkg/menu/ok.go Normal file
View file

@ -0,0 +1,33 @@
package menu
import (
"github.com/gookit/color"
"github.com/nexidian/gocliselect"
)
// Make an OK menu
func Ok(msg string) string {
// Make the menu
menu := gocliselect.NewMenu(msg)
menu.AddItem("OK", "next")
// Display the menu
choice := menu.Display()
// Return the value selected
return choice
}
// Make an OK & Go Back menu
func OkBack(msg string) string {
// Make the menu
menu := gocliselect.NewMenu(msg)
menu.AddItem(color.Bold.Sprint("OK"), "next")
menu.AddItem(color.Bold.Sprint("Go Back"), "back")
// Display the menu
choice := menu.Display()
// Return the value selected
return choice
}

63
pkg/menu/yesno.go Normal file
View file

@ -0,0 +1,63 @@
package menu
import (
"github.com/gookit/color"
"github.com/nexidian/gocliselect"
)
// Make a YesNo menu
func YesNo(msg string) string {
// Make the menu
menu := gocliselect.NewMenu(msg)
menu.AddItem("Yes", "y")
menu.AddItem("No", "n")
// Display the menu
choice := menu.Display()
// Return the value selected
return choice
}
func YesNoBack(msg string) string {
// Make the menu
menu := gocliselect.NewMenu(msg)
menu.AddItem("Yes", "y")
menu.AddItem("No", "n")
menu.AddItem(color.Bold.Sprint("Go Back"), "back")
// Display the menu
choice := menu.Display()
// Return the value selected
return choice
}
func YesNoEXT(msg string) string {
// Make the menu
menu := gocliselect.NewMenu(msg)
menu.AddItem("Yes", "y")
menu.AddItem("No", "n")
menu.AddItem("ADVANCED: View with extended related search by vendor ID, devices listed might not be related", "ext")
// Display the menu
choice := menu.Display()
// Return the value selected
return choice
}
// Make a YesNo menu
func YesNoManual(msg string) string {
// Make the menu
menu := gocliselect.NewMenu(msg)
menu.AddItem("Yes", "y")
menu.AddItem("No", "n")
menu.AddItem("Manual Entry", "manual")
// Display the menu
choice := menu.Display()
// Return the value selected
return choice
}

56
pkg/uname/uname.go Normal file
View file

@ -0,0 +1,56 @@
package uname
import "syscall"
type Uname struct {
Sysname string
Nodename string
Hostname string
Release string
Kernel string
Version string
Machine string
Arch string
Domainname string
}
// A utility to convert int8 values to proper strings.
func int8ToStr(arr []int8) string {
b := make([]byte, 0, len(arr))
for _, v := range arr {
if v == 0x00 {
break
}
b = append(b, byte(v))
}
return string(b)
}
func New() *Uname {
var system syscall.Utsname
uname := &Uname{}
if err := syscall.Uname(&system); err == nil {
// extract members:
// type Utsname struct {
// Sysname [65]int8
// Nodename [65]int8
// Release [65]int8
// Version [65]int8
// Machine [65]int8
// Domainname [65]int8
// }
// Add to the uname struct for humans
uname.Sysname = int8ToStr(system.Sysname[:])
uname.Nodename = int8ToStr(system.Nodename[:])
uname.Hostname = uname.Nodename
uname.Release = int8ToStr(system.Release[:])
uname.Kernel = uname.Release
uname.Version = int8ToStr(system.Version[:])
uname.Machine = int8ToStr(system.Machine[:])
uname.Arch = uname.Machine
uname.Domainname = int8ToStr(system.Domainname[:])
}
return uname
}

83
pkg/untar/untar.go Normal file
View file

@ -0,0 +1,83 @@
package untar
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
)
// Slightly modified from source: https://medium.com/@skdomino/taring-untaring-files-in-go-6b07cf56bc07
// Untar takes a destination path and a path to a file; a tar reader loops over the tarfile
// creating the file structure at 'dst' along the way, and writing any files
func Untar(dst string, fileName string) error {
r, err := os.Open(fileName)
errorcheck.ErrorCheck(err, fmt.Sprintf("Failed to open: %s", fileName))
defer r.Close()
gzr, err := gzip.NewReader(r)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
switch {
// if no more files are found return
case err == io.EOF:
return nil
// return any other error
case err != nil:
return err
// if the header is nil, just skip it (not sure how this happens)
case header == nil:
continue
}
// the target location where the dir/file should be created
target := filepath.Join(dst, header.Name)
// the following switch could also be done using fi.Mode(), not sure if there
// a benefit of using one vs. the other.
// fi := header.FileInfo()
// check the file type
switch header.Typeflag {
// if its a dir and it doesn't exist create it
case tar.TypeDir:
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(target, 0755); err != nil {
return err
}
}
// if it's a file create it
case tar.TypeReg:
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return err
}
// copy over contents
if _, err := io.Copy(f, tr); err != nil {
return err
}
// manually close here after each file operation; defering would cause each file close to wait until all operations have completed.
f.Close()
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View file

@ -1,196 +0,0 @@
#!/bin/bash
# shellcheck disable=SC1091
# Get the scripts directory
SCRIPTDIR=$(dirname "$(realpath "$0")")
cd "$SCRIPTDIR" || exit
# Get the config paths
source "$SCRIPTDIR/lib/paths.sh"
# Get the CPU Vendor
CPU_VENDOR=$(grep "vendor_id" /proc/cpuinfo | head -1 | cut -f 2 | cut -d " " -f 2)
CMDLINE="iommu=pt"
# Adjust our kernel_args based on cpu vendor
if [ "$CPU_VENDOR" == "GenuineIntel" ];
then
CMDLINE="$CMDLINE intel_iommu=on"
elif [ "$CPU_VENDOR" == "AuthenticAMD" ];
then
CMDLINE="$CMDLINE amd_iommu=on"
fi
# Clear the screen
clear
# Show the user a warning before we start
printf "Welcome to QuickPassthrough!
Written for systems using initramfs.
The setup done by this script is quite complex and is prone to human error or hardware incompatibilities.
It is HIGHLY RECOMMENDED to make a backup/snapshot of your system using something like timeshift or snapper before starting.
Once everything is configured, your 2nd graphic card will hopefully be dedicated for use inside a virtual machine.
Even though that this script is intended to make this setup easier, it is recommended that you
read through a guide and learn what is actually being done so that you can get familiar the process.
A full documentation for debian/ubuntu systems can be found here: https://github.com/hikariknight/vfio-setup-docs/wiki
Press ENTER to continue once you have made a backup of your system.
"
read -r
# Separator
printf "
############################################################
"
echo "This script assumes a few things:
* You have already enabled IOMMU, VT-d and/or AMD-v inside your UEFI/BIOS advanced settings.
* You have already added \"$CMDLINE\" to your
kernel boot arguments and booted your system with these kernel arguments active.
* You are comfortable with navigating and changing settings in your UEFI/BIOS.
* You know how edit your bootloader configuration and kernel arguments.
* Your Linux distribution is an EFI installation (important to get VFIO working).
NOTE: If your computer no longer fully shut down after enabling IOMMU, then there is possibly a bug
with your motherboard and a piece of hardware in your system, it only prevents you from using
the system in a headless mode with working shutdown and is otherwise just an annoying
quirk with IOMMU on some boards.
This is a list of prerequisites you will be needing before starting with VFIO:
* 2 very different GPUs (iGPU/APU included), the easiest combination is to have 2 from different vendors (amd/intel/nvidia)
if both cards share the same device id (ex: both are identified as 1022:145c), then passthrough will
not be possible unless you swap out one of the cards.
* A \"ghost display\" dummy plug for your second graphic card (or having it hooked to a separate input on your monitor).
* If you are planning to use the integrated GPU on your CPU, make sure your monitor is connected to it and working/enabled before continuing.
* Preferably a motherboard verified to work with IOMMU and with good IOMMU groups.
https://reddit.com/r/vfio is a good resource for this info.
(If you are unsure, you will find out while using this script)
* It is also highly recommended to have access to your VM through VNC (RDP will not work) as once you pass through
a graphic card, it will most likely not possible to interact with it through spice and the normal qemu display window!
Press ENTER to start creating your config from scratch.
NOTE: continuing will delete the contents of \"$SCRIPTDIR/config\"
"
read -r
# Separator
printf "
############################################################
"
if [ ! -d "$SCRIPTDIR/utils" ];
then
mkdir "$SCRIPTDIR/utils"
fi
if [ ! -f "$SCRIPTDIR/utils/ls-iommu.tar.gz" ];
then
printf "Checking for newest version of ls-iommu\n"
LS_IOMMU_VERSION=$($READAPI https://api.github.com/repos/hikariknight/ls-iommu/releases/latest | grep "tag_name" | perl -pe "s/.+\"tag_name\"\:\s+\"(\d+\.\d+\.\d+)\",/\1/")
printf "Downloading required tool ls-iommu from https://github.com/hikariknight/ls-iommu\n"
cd "$SCRIPTDIR/utils" || exit
$DOWNLOAD https://github.com/HikariKnight/ls-iommu/releases/download/${LS_IOMMU_VERSION}/ls-iommu_${LS_IOMMU_VERSION}_Linux_x86_64.tar.gz
tar -zxf "$SCRIPTDIR/utils/ls-iommu.tar.gz" --directory "$SCRIPTDIR/utils/"
else
if [ ! -f "$SCRIPTDIR/utils/ls-iommu" ];
then
tar -zxf "$SCRIPTDIR/utils/ls-iommu.tar.gz" --directory "$SCRIPTDIR/utils/"
fi
fi
if [ -d "$SCRIPTDIR/config" ];
then
rm -r "$SCRIPTDIR/config"
fi
# Make the directories
mkdir -p "$SCRIPTDIR/$MODPROBE"
mkdir -p "$SCRIPTDIR/$QUICKEMU"
# Write the cmdline file
echo "$CMDLINE" > "$SCRIPTDIR/config/kernel_args"
# Copy system configs that exists into our config folder so we can safely edit them
if [ -f "/etc/modules" ];
then
# This copies /etc/modules without the vfio module lines
grep -v "vfio" "/etc/modules" > "$SCRIPTDIR/$ETCMODULES"
fi
if [ -f "/etc/default/grub" ];
then
# Create the default folder
mkdir -p "$SCRIPTDIR/$DEFAULT"
# Copy grub config
cp "/etc/default/grub" "$SCRIPTDIR/$DEFAULT/grub"
fi
if [ -f "/etc/initramfs-tools/modules" ];
then
# Create the initramfs folder
mkdir -p "$SCRIPTDIR/$INITRAMFS"
# This copies /etc/initramfs-tools/modules without the vfio modules
grep -v "vfio" "/etc/initramfs-tools/modules" > "$SCRIPTDIR/$INITRAMFS/modules"
fi
if [ -f "/etc/mkinitcpio.conf" ];
then
# Copy mkinitcpio.conf to our config folder
cp "/etc/mkinitcpio.conf" "$SCRIPTDIR/$MKINITCPIO"
fi
if [ -f "/etc/dracut.conf" ];
then
# Create the dracut folder
mkdir -p "$SCRIPTDIR/$DRACUT"
# Create a dracut.conf.d file
touch "$SCRIPTDIR/$DRACUT/10-vfio.conf"
fi
# Run ls-iommu so we can verify that IOMMU properly working
LS_IOMMU=$($SCRIPTDIR/utils/ls-iommu)
# Detect if IOMMU is disabled (output will be empty)
if [[ "$LS_IOMMU" == "" ]];
then
# Tell user to enable IOMMU then try again
echo "IOMMU IS NOT ENABLED!
Please enable IOMMU, VT-d or AMD-v inside your UEFI/BIOS and add \"$CMDLINE\"
to your kernel boot arguments and reboot your system, then re-run this script!
"
exit 1
else
# Show the output of ls-iommu for manual verification
echo "$LS_IOMMU"
fi
echo ""
# Have user visually verify that IOMMU is working
read -r -p "Is there more than 1 group in the output above? [y/N]: " YESNO
case "${YESNO}" in
[Yy]*)
echo ""
;;
[Nn]*)
echo "Please enable IOMMU, VT-d or AMD-v inside your UEFI/BIOS and add \"$CMDLINE\"
to your kernel boot arguments and reboot your system, then re-run this script!
"
exit 1
;;
*)
echo "Please enable IOMMU, VT-d or AMD-v inside your UEFI/BIOS and add \"$CMDLINE\"
to your kernel boot arguments and reboot your system, then re-run this script!
"
exit 1
;;
esac
exec "$SCRIPTDIR/lib/get_GPU.sh"

View file

@ -1,32 +0,0 @@
#!/bin/bash
# shellcheck disable=SC2002,SC2164
# Get the scripts directory
SCRIPTDIR=$(dirname "$(which "$0")")
cd "$SCRIPTDIR"
# If there is a config generated, then $SCRIPTDIR/config/kernel_args
# should exist, which contains all the info we need
if [ -f "$SCRIPTDIR/config/kernel_args" ];
then
echo "#------------------------------------------#"
# List info about the vfio gpu and what kernel driver is being used
"$SCRIPTDIR/utils/ls-iommu" -g -r -k
echo "#------------------------------------------#"
printf "
If the \"Kernel driver in use\" for the passed through devices are \"vfio-pci\", then VFIO has been successfully enabled!
GPU Devices using \"pcieport\" can be ignored.
NOTE: If your system freezes when starting a VM that uses your VFIO enabled card..
consider adding the below line to your bootloaders kernel arguments:
vfio_pci.disable_vga=1
That will disable video output from the card while it is connected to the Linux host.
"
else
# Tell user to run the setup first if the kernel_args file is not found
echo "Please run \"$SCRIPTDIR/vfio-setup\" first!"
fi