Skip to content

Commit 3d0b5f8

Browse files
committed
pkg/pillar: add Intel iGPU passthrough support to KVM hypervisor
When an Intel iGPU (vendor 0x8086, class 0x03xx) is in the vfio-pci passthrough list: - Place the iGPU at guest BDF 00:02.0 (required for VBIOS/GOP ROM) - Enable x-igd-opregion=on for OpRegion/VBT passthrough via fw_cfg - Load igd.rom as the Option ROM (IgdAssignmentDxe sets ASLS/BDSM) - Move USB root port from slot 0x2 to 0x1b to free the iGPU slot - Support optional proprietary GOP ROMs for UEFI framebuffer Update eve-uefi reference to pick up edk2-stable202508.01 and igd.rom. Signed-off-by: Mikhail Malyshev <mike.malyshev@gmail.com>
1 parent 2111f1d commit 3d0b5f8

File tree

2 files changed

+107
-10
lines changed

2 files changed

+107
-10
lines changed

pkg/pillar/Dockerfile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ ARG BUILD_PKGS_BASE="git gcc linux-headers libc-dev make linux-pam-dev m4 findut
1010
# we use the same image in several places
1111
ARG EVE_ALPINE_IMAGE=lfedge/eve-alpine:745ae9066273c73b0fd879c4ba4ff626a8392d04
1212

13-
FROM lfedge/eve-uefi:b3ef9ca37c99439c776673aeba83c52ea8b84626 AS uefi-build
13+
FROM lfedge/eve-uefi:417cf0c3acf434fb0f775e8a588ae7b6e3523d6d AS uefi-build
1414
FROM lfedge/eve-dom0-ztools:d19fc83dfbd01562eba914a83d571f7315429052 AS zfs
1515
RUN mkdir /out
1616
# copy zfs-related files from dom0-ztools using prepared list of files
@@ -167,7 +167,6 @@ WORKDIR /
167167

168168
RUN mkdir -p /out/usr/lib/xen/boot
169169
COPY --from=uefi-build /OVMF_VARS.fd /out/usr/lib/xen/boot/OVMF_VARS.fd
170-
171170
COPY --from=zfs /out /out
172171
COPY --from=fscrypt /opt/zededa/bin /out/opt/zededa/bin
173172
COPY --from=gpttools / /out

pkg/pillar/hypervisor/kvm.go

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -274,12 +274,21 @@ const qemuGlobalConfTemplate = `# This file is automatically generated by domain
274274
driver = "virtio-gpu-pci"
275275
{{- end }}
276276
{{- end }}
277+
{{- if .HasIntelIGPU }}
278+
[device "pci.2"]
279+
driver = "pcie-root-port"
280+
port = "12"
281+
chassis = "2"
282+
bus = "pcie.0"
283+
addr = "0x1b"
284+
{{- else }}
277285
[device "pci.2"]
278286
driver = "pcie-root-port"
279287
port = "12"
280288
chassis = "2"
281289
bus = "pcie.0"
282290
addr = "0x2"
291+
{{- end }}
283292
284293
[device "usb"]
285294
driver = "qemu-xhci"
@@ -434,6 +443,9 @@ const qemuPCIPassthruTemplate = `
434443
{{- if .Xopregion }}
435444
x-igd-opregion = "on"
436445
{{- end}}
446+
{{- if .Romfile }}
447+
romfile = "{{.Romfile}}"
448+
{{- end}}
437449
`
438450

439451
const qemuSerialTemplate = `
@@ -492,12 +504,13 @@ const qemuSwtpmTemplate = `
492504
tpmdev = "tpm0"
493505
`
494506

495-
// Context for qemuGlobalConfTemplate.
507+
// Context for qemuGlobalConfContext.
496508
type tQemuGlobalConfContext struct {
497509
Machine string
498510
VirtualizationMode string
499511
BootLoaderSettingsFile string
500512
BootOrder string // Boot order: "usb" (prioritize USB), "nousb" (deprioritize USB), or "" (default)
513+
HasIntelIGPU bool // true when Intel iGPU is in the passthrough list; moves USB root port to slot 0x1b
501514
types.DomainConfig
502515
types.DomainStatus
503516
}
@@ -513,6 +526,7 @@ type tQemuPCIPassthruContext struct {
513526
PciShortAddr string
514527
Xvga bool
515528
Xopregion bool
529+
Romfile string // non-empty path to GOP ROM (e.g. /persist/gop/3ea0.rom); omitted if empty
516530
Bus string
517531
Addr string
518532
}
@@ -1420,6 +1434,7 @@ func multifunctionDevGroup(pcis []pciDevice) multifunctionDevs {
14201434
type pciAssignmentsTemplateFiller struct {
14211435
multifunctionDevices multifunctionDevs
14221436
file io.Writer
1437+
domainUUID string
14231438
}
14241439

14251440
func (f *pciAssignmentsTemplateFiller) pciEBridge(pciID int, pciWOFunction string) error {
@@ -1443,17 +1458,48 @@ func (f *pciAssignmentsTemplateFiller) do(pciAssignments []pciDevice) error {
14431458
Xvga: pa.isVGA(),
14441459
Xopregion: false,
14451460
}
1446-
if vendor, err := pa.vid(); err == nil {
1447-
// check for Intel vendor
1448-
if vendor == "0x8086" {
1449-
if pciPTContext.Xvga {
1450-
// we set opregion for Intel vga
1451-
// https://github.com/qemu/qemu/blob/stable-5.0/docs/igd-assign.txt#L91-L96
1452-
pciPTContext.Xopregion = true
1461+
1462+
isIntelIGPU := false
1463+
if vendor, err := pa.vid(); err == nil && vendor == "0x8086" {
1464+
if pciPTContext.Xvga {
1465+
// Enable OpRegion passthrough for Intel iGPU — writes etc/igd-opregion to fw_cfg.
1466+
// https://github.com/qemu/qemu/blob/stable-5.0/docs/igd-assign.txt#L91-L96
1467+
pciPTContext.Xopregion = true
1468+
isIntelIGPU = true
1469+
logrus.Infof("iGPU passthrough: detected Intel GPU at host PCI %s for domain %s",
1470+
pa.ioBundle.PciLong, f.domainUUID)
1471+
if devid, err := pa.devid(); err == nil {
1472+
pciPTContext.Romfile = gopRomPath(devid)
1473+
if pciPTContext.Romfile == igdRomPath {
1474+
logrus.Infof("iGPU passthrough: no proprietary GOP ROM in %s for device %s, "+
1475+
"using bundled VfioIgdPkg ROM (OS display works; no pre-OS UEFI framebuffer) for domain %s",
1476+
gopRomDir, devid, f.domainUUID)
1477+
} else {
1478+
logrus.Infof("iGPU passthrough: using proprietary GOP ROM %s for domain %s",
1479+
pciPTContext.Romfile, f.domainUUID)
1480+
}
1481+
} else {
1482+
logrus.Warnf("iGPU passthrough: could not read device ID for %s (domain %s): %v",
1483+
pa.ioBundle.PciLong, f.domainUUID, err)
1484+
pciPTContext.Romfile = igdRomPath
14531485
}
14541486
}
14551487
}
14561488

1489+
if isIntelIGPU {
1490+
// Intel iGPU must appear at guest BDF 00:02.0 for VBIOS/GOP ROM initialization.
1491+
// Place directly on the root bus — no pcie-root-port wrapper.
1492+
// The USB root port was moved from 0x2 to 0x1b in qemuGlobalConfTemplate.
1493+
logrus.Infof("iGPU passthrough: placing device %s at guest BDF 00:02.0 for domain %s",
1494+
pa.ioBundle.PciLong, f.domainUUID)
1495+
pciPTContext.Bus = "pcie.0"
1496+
pciPTContext.Addr = "0x2"
1497+
if err := tQemuPCIPassthru.Execute(f.file, pciPTContext); err != nil {
1498+
return logError("can't write iGPU passthrough to config file (%v)", err)
1499+
}
1500+
continue
1501+
}
1502+
14571503
pciLongWoFunc, err := pa.pciLongWOFunction()
14581504
if err != nil {
14591505
logrus.Warnf("retrieving pci address without function failed: %v", err)
@@ -1524,6 +1570,55 @@ func (f *virtNetworkTemplateFiller) do(virtualNetworks []virtualNetwork,
15241570
return nil
15251571
}
15261572

1573+
// gopRomDir is where proprietary Intel GOP Option ROMs can be placed for display output.
1574+
// ROMs are named by PCI device ID, e.g. /persist/gop/3ea0.rom for device 0x3ea0.
1575+
// The 0x-prefixed form (0x3ea0.rom) is also accepted as a fallback.
1576+
const gopRomDir = "/persist/gop"
1577+
1578+
// igdRomPath is the bundled open-source IGD Option ROM built from VfioIgdPkg.
1579+
// It provides IgdAssignmentDxe which sets ASLS and BDSM (including Gen11+ 64-bit
1580+
// BDSM at PCI config 0xC0) without requiring a proprietary Intel GOP driver.
1581+
const igdRomPath = "/usr/lib/xen/boot/igd.rom"
1582+
1583+
// gopRomPath returns the path to a proprietary GOP ROM for the given PCI device ID
1584+
// (as read from sysfs, e.g. "0x3ea0") if one exists in gopRomDir, otherwise returns
1585+
// igdRomPath. A proprietary ROM provides the Intel GOP driver which enables the UEFI
1586+
// framebuffer (pre-OS display: GRUB, bootloader, UEFI shell). The bundled ROM provides
1587+
// only IgdAssignmentDxe: ASLS/BDSM are set correctly so the guest OS display driver
1588+
// works, but there is no display output before the OS driver loads.
1589+
func gopRomPath(devid string) string {
1590+
name := strings.TrimPrefix(strings.ToLower(strings.TrimSpace(devid)), "0x")
1591+
for _, filename := range []string{name + ".rom", "0x" + name + ".rom"} {
1592+
path := filepath.Join(gopRomDir, filename)
1593+
if _, err := os.Stat(path); err == nil {
1594+
return path
1595+
}
1596+
}
1597+
return igdRomPath
1598+
}
1599+
1600+
// detectIntelIGPU returns true if any adapter in the passthrough list is an Intel VGA device.
1601+
func detectIntelIGPU(adapters []types.IoAdapter, aa *types.AssignableAdapters) bool {
1602+
if aa == nil {
1603+
return false
1604+
}
1605+
for _, adapter := range adapters {
1606+
for _, ib := range aa.LookupIoBundleAny(adapter.Name) {
1607+
if ib == nil || ib.PciLong == "" || ib.UsbAddr != "" {
1608+
continue
1609+
}
1610+
dev := pciDevice{ioBundle: *ib}
1611+
if !dev.isVGA() {
1612+
continue
1613+
}
1614+
if vendor, err := dev.vid(); err == nil && vendor == "0x8086" {
1615+
return true
1616+
}
1617+
}
1618+
}
1619+
return false
1620+
}
1621+
15271622
// CreateDomConfig creates a domain config (a qemu config file,
15281623
// typically named something like xen-%d.cfg)
15291624
func (ctx KvmContext) CreateDomConfig(domainName string,
@@ -1548,12 +1643,14 @@ func (ctx KvmContext) CreateDomConfig(domainName string,
15481643
// 4. Default (BOOT_ORDER_UNSPECIFIED) - no boot order modification
15491644
// By the time we get here, config.BootOrder has the final resolved value.
15501645
bootOrder := bootOrderToFwCfgString(config.BootOrder)
1646+
hasIntelIGPU := detectIntelIGPU(config.IoAdapterList, aa)
15511647

15521648
qemuConfContext := tQemuGlobalConfContext{
15531649
Machine: ctx.devicemodel,
15541650
VirtualizationMode: virtualizationMode,
15551651
BootLoaderSettingsFile: bootLoaderSettingsFile,
15561652
BootOrder: bootOrder,
1653+
HasIntelIGPU: hasIntelIGPU,
15571654
DomainConfig: config,
15581655
DomainStatus: status,
15591656
}
@@ -1702,6 +1799,7 @@ func (ctx KvmContext) CreateDomConfig(domainName string,
17021799
pciAssignmentsFiller := pciAssignmentsTemplateFiller{
17031800
multifunctionDevices: multifunctionDevices,
17041801
file: file,
1802+
domainUUID: config.UUIDandVersion.UUID.String(),
17051803
}
17061804
err = pciAssignmentsFiller.do(pciAssignments)
17071805
if err != nil {

0 commit comments

Comments
 (0)