From 54090e25abc39a5fa8b3aaff0aab8563979776d8 Mon Sep 17 00:00:00 2001 From: Cass Sheng Date: Wed, 26 Aug 2026 15:30:23 -0500 Subject: [PATCH] drivers/gop: Resolve GOP child controller ownership Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Cass Sheng --- EXTENSIONS.md | 11 +++-- common/drivers/gop.c | 109 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 97 insertions(+), 23 deletions(-) diff --git a/EXTENSIONS.md b/EXTENSIONS.md index 7aa89d5b..bfa2e441 100644 --- a/EXTENSIONS.md +++ b/EXTENSIONS.md @@ -51,7 +51,10 @@ For `LIMINE_MP_FRAMEBUFFER_SOURCE_UNKNOWN`, `data` is `NULL`. For segment-group numbering. Values outside the PCI ranges are never reported as a PCI source. -UEFI GOP handles are resolved through their device paths to the closest handle -supporting `EFI_PCI_IO_PROTOCOL`, whose `GetLocation()` method provides the PCI -coordinates. Resolution failure, a non-PCI provider, and BIOS VBE all produce -an unknown source rather than an inferred identity. +UEFI GOP handles are first resolved through their device paths to the closest +handle supporting `EFI_PCI_IO_PROTOCOL`. If firmware installs GOP on a child +whose device path cannot be resolved to PCI I/O, the loader accepts only an +explicit `EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER` relationship naming that GOP +handle. The selected PCI I/O protocol's `GetLocation()` method provides the PCI +coordinates. Failure of both positive relationships, a non-PCI provider, and +BIOS VBE all produce an unknown source rather than an inferred identity. diff --git a/common/drivers/gop.c b/common/drivers/gop.c index a8a8f3dd..31d5e349 100644 --- a/common/drivers/gop.c +++ b/common/drivers/gop.c @@ -131,27 +131,14 @@ static bool mode_to_fb_info(struct fb_info *ret, EFI_GRAPHICS_OUTPUT_PROTOCOL *g bool gop_force_16 = false; -static void get_framebuffer_source(struct fb_info *fb, EFI_HANDLE gop_handle) { - EFI_GUID device_path_guid = EFI_DEVICE_PATH_PROTOCOL_GUID; - EFI_DEVICE_PATH *device_path = NULL; - - EFI_STATUS status = gBS->HandleProtocol(gop_handle, &device_path_guid, - (void **)&device_path); - if (status != EFI_SUCCESS) { - return; - } - +static bool set_pci_framebuffer_source(struct fb_info *fb, + EFI_HANDLE pci_handle) { EFI_GUID pci_io_guid = EFI_PCI_IO_PROTOCOL_GUID; - EFI_HANDLE pci_handle = NULL; - status = gBS->LocateDevicePath(&pci_io_guid, &device_path, &pci_handle); - if (status != EFI_SUCCESS) { - return; - } - EFI_PCI_IO_PROTOCOL *pci_io = NULL; - status = gBS->HandleProtocol(pci_handle, &pci_io_guid, (void **)&pci_io); + EFI_STATUS status = gBS->HandleProtocol(pci_handle, &pci_io_guid, + (void **)&pci_io); if (status != EFI_SUCCESS) { - return; + return false; } UINTN segment; @@ -164,7 +151,7 @@ static void get_framebuffer_source(struct fb_info *fb, EFI_HANDLE gop_handle) { || bus > UINT8_MAX || device > 31 || function > 7) { - return; + return false; } fb->source_type = FB_SOURCE_PCI; @@ -176,6 +163,90 @@ static void get_framebuffer_source(struct fb_info *fb, EFI_HANDLE gop_handle) { printv("gop: Framebuffer source PCI %x:%x:%x.%x\n", (uint32_t)segment, (uint32_t)bus, (uint32_t)device, (uint32_t)function); + return true; +} + +static bool get_framebuffer_source_from_device_path(struct fb_info *fb, + EFI_HANDLE gop_handle) { + EFI_GUID device_path_guid = EFI_DEVICE_PATH_PROTOCOL_GUID; + EFI_DEVICE_PATH *device_path = NULL; + + EFI_STATUS status = gBS->HandleProtocol(gop_handle, &device_path_guid, + (void **)&device_path); + if (status != EFI_SUCCESS) { + return false; + } + + EFI_GUID pci_io_guid = EFI_PCI_IO_PROTOCOL_GUID; + EFI_HANDLE pci_handle = NULL; + status = gBS->LocateDevicePath(&pci_io_guid, &device_path, &pci_handle); + return status == EFI_SUCCESS + && set_pci_framebuffer_source(fb, pci_handle); +} + +static bool pci_handle_owns_gop_child(EFI_HANDLE pci_handle, + EFI_GUID *pci_io_guid, + EFI_HANDLE gop_handle) { + EFI_OPEN_PROTOCOL_INFORMATION_ENTRY *entries = NULL; + UINTN entries_count = 0; + EFI_STATUS status = gBS->OpenProtocolInformation(pci_handle, pci_io_guid, + &entries, &entries_count); + if (status != EFI_SUCCESS) { + return false; + } + + bool owns = false; + for (UINTN i = 0; i < entries_count; i++) { + if ((entries[i].Attributes & EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) + && entries[i].ControllerHandle == gop_handle) { + owns = true; + break; + } + } + if (entries != NULL) { + gBS->FreePool(entries); + } + return owns; +} + +static void get_framebuffer_source(struct fb_info *fb, EFI_HANDLE gop_handle) { + if (get_framebuffer_source_from_device_path(fb, gop_handle)) { + return; + } + + // Some firmware installs GOP on a child handle whose device path cannot be + // resolved back to EFI_PCI_IO_PROTOCOL. UEFI bus drivers still record the + // controller relationship by opening the parent PCI I/O protocol with + // EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER and the GOP handle as + // ControllerHandle. Walk only those explicit relationships: guessing from + // framebuffer addresses or display class would not prove boot ownership. + EFI_GUID pci_io_guid = EFI_PCI_IO_PROTOCOL_GUID; + EFI_HANDLE tmp_handles[1]; + EFI_HANDLE *handles = tmp_handles; + UINTN handles_size = sizeof(tmp_handles); + EFI_STATUS status = gBS->LocateHandle(ByProtocol, &pci_io_guid, NULL, + &handles_size, handles); + if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) { + return; + } + + UINTN handles_alloc = handles_size; + handles = ext_mem_alloc(handles_alloc); + status = gBS->LocateHandle(ByProtocol, &pci_io_guid, NULL, + &handles_size, handles); + if (status != EFI_SUCCESS) { + pmm_free(handles, handles_alloc); + return; + } + + size_t handles_count = handles_size / sizeof(EFI_HANDLE); + for (size_t i = 0; i < handles_count; i++) { + if (pci_handle_owns_gop_child(handles[i], &pci_io_guid, gop_handle) + && set_pci_framebuffer_source(fb, handles[i])) { + break; + } + } + pmm_free(handles, handles_alloc); } static bool try_mode(struct fb_info *ret, EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,