Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions EXTENSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ struct limine_mp_framebuffer_source_response {
uint64_t revision;
uint64_t entry_count;
struct limine_mp_framebuffer_source **entries;
// Revision >= 1 only.
uint64_t *probe_masks;
};

struct limine_mp_framebuffer_source_request {
Expand All @@ -55,6 +57,40 @@ 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.
handle. When both relations are absent and the GOP handle is exactly the
system table's `ConsoleOutHandle` — the console-splitter posture, where the
aggregate handle carries no device path and no recorded PCI parent — the
loader resolves the firmware's own `ConOut` variable: every instance of that
multi-instance device path that reaches an `EFI_PCI_IO_PROTOCOL` handle must
agree on one PCI function, which is then the console framebuffer's source.
The selected PCI I/O protocol's `GetLocation()` method provides the PCI
coordinates in every proof. Failure of all three positive relationships, a
non-PCI provider, a `ConOut` naming two PCI devices, and BIOS VBE all produce
an unknown source rather than an inferred identity.

### Revision 1: probe masks

A revision >= 1 response adds `probe_masks`: `entry_count` `uint64_t` values,
index-aligned with `entries`, recording how far each source proof advanced
for that framebuffer. A serial-less machine can then name the exact firmware
shape that defeated attribution from the mask alone.

```c
#define LIMINE_MP_FRAMEBUFFER_PROBE_DP_PRESENT (1 << 0)
#define LIMINE_MP_FRAMEBUFFER_PROBE_DP_PCI_PREFIX (1 << 1)
#define LIMINE_MP_FRAMEBUFFER_PROBE_DP_LOCATED (1 << 2)
#define LIMINE_MP_FRAMEBUFFER_PROBE_CHILD_SCAN (1 << 3)
#define LIMINE_MP_FRAMEBUFFER_PROBE_CHILD_RELATION (1 << 4)
#define LIMINE_MP_FRAMEBUFFER_PROBE_CONSOLE_HANDLE (1 << 5)
#define LIMINE_MP_FRAMEBUFFER_PROBE_CONOUT_VAR (1 << 6)
#define LIMINE_MP_FRAMEBUFFER_PROBE_CONOUT_PCI (1 << 7)
#define LIMINE_MP_FRAMEBUFFER_PROBE_CONOUT_UNIQUE (1 << 8)
#define LIMINE_MP_FRAMEBUFFER_PROBE_SOURCE_PCI (1 << 9)
#define LIMINE_MP_FRAMEBUFFER_PROBE_HANDLES_SHIFT 16
#define LIMINE_MP_FRAMEBUFFER_PROBE_INDEX_SHIFT 24
```

Bits `[23:16]` carry the number of GOP handles the loader enumerated and bits
`[31:24]` this framebuffer's handle index, both saturating at `0xff`; BIOS VBE
framebuffers report a zero mask. Bit meanings are fixed for revision 1;
future proof steps append bits rather than renumbering.
208 changes: 181 additions & 27 deletions common/drivers/gop.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,11 @@ static bool mode_to_fb_info(struct fb_info *ret, EFI_GRAPHICS_OUTPUT_PROTOCOL *g

bool gop_force_16 = false;

static bool set_pci_framebuffer_source(struct fb_info *fb,
EFI_HANDLE pci_handle) {
// Resolve one EFI_PCI_IO handle to bounded PCI coordinates. The bounds keep
// every reported source inside PCI numbering; a firmware answer outside them
// is treated as no answer at all.
static bool pci_handle_location(EFI_HANDLE pci_handle,
struct fb_pci_source *out) {
EFI_GUID pci_io_guid = EFI_PCI_IO_PROTOCOL_GUID;
EFI_PCI_IO_PROTOCOL *pci_io = NULL;
EFI_STATUS status = gBS->HandleProtocol(pci_handle, &pci_io_guid,
Expand All @@ -154,15 +157,26 @@ static bool set_pci_framebuffer_source(struct fb_info *fb,
return false;
}

out->segment = segment;
out->bus = bus;
out->device = device;
out->function = function;
return true;
}

static bool set_pci_framebuffer_source(struct fb_info *fb,
EFI_HANDLE pci_handle) {
struct fb_pci_source source;
if (!pci_handle_location(pci_handle, &source)) {
return false;
}

fb->source_type = FB_SOURCE_PCI;
fb->pci_source.segment = segment;
fb->pci_source.bus = bus;
fb->pci_source.device = device;
fb->pci_source.function = function;
fb->pci_source = source;

printv("gop: Framebuffer source PCI %x:%x:%x.%x\n",
(uint32_t)segment, (uint32_t)bus, (uint32_t)device,
(uint32_t)function);
(uint32_t)source.segment, (uint32_t)source.bus,
(uint32_t)source.device, (uint32_t)source.function);
return true;
}

Expand All @@ -176,12 +190,21 @@ static bool get_framebuffer_source_from_device_path(struct fb_info *fb,
if (status != EFI_SUCCESS) {
return false;
}
fb->source_probe |= FB_PROBE_DP_PRESENT;

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);
if (status != EFI_SUCCESS) {
return false;
}
fb->source_probe |= FB_PROBE_DP_PCI_PREFIX;

if (!set_pci_framebuffer_source(fb, pci_handle)) {
return false;
}
fb->source_probe |= FB_PROBE_DP_LOCATED;
return true;
}

static bool pci_handle_owns_gop_child(EFI_HANDLE pci_handle,
Expand Down Expand Up @@ -209,25 +232,22 @@ static bool pci_handle_owns_gop_child(EFI_HANDLE pci_handle,
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.
// 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.
static bool get_framebuffer_source_from_child_relation(struct fb_info *fb,
EFI_HANDLE gop_handle) {
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;
return false;
}

UINTN handles_alloc = handles_size;
Expand All @@ -236,17 +256,151 @@ static void get_framebuffer_source(struct fb_info *fb, EFI_HANDLE gop_handle) {
&handles_size, handles);
if (status != EFI_SUCCESS) {
pmm_free(handles, handles_alloc);
return;
return false;
}
fb->source_probe |= FB_PROBE_CHILD_SCAN;

bool found = false;
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;
if (pci_handle_owns_gop_child(handles[i], &pci_io_guid, gop_handle)) {
fb->source_probe |= FB_PROBE_CHILD_RELATION;
if (set_pci_framebuffer_source(fb, handles[i])) {
found = true;
break;
}
}
}
pmm_free(handles, handles_alloc);
return found;
}

// The console-splitter posture: firmware that virtualizes console output
// installs GOP on an aggregate handle with no device path and no recorded
// PCI parent, while the handle IS the system table's ConsoleOutHandle. For
// exactly that handle, firmware's own ConOut variable names the sink device
// paths, and resolving those through EFI_PCI_IO is a firmware-asserted
// binding, not a guess. All resolved instances must agree on one function:
// a console scanning out via two PCI devices proves ownership of neither.
static bool get_framebuffer_source_from_conout(struct fb_info *fb,
EFI_HANDLE gop_handle) {
if (gST->ConsoleOutHandle == NULL
|| gop_handle != gST->ConsoleOutHandle) {
return false;
}
fb->source_probe |= FB_PROBE_CONSOLE_HANDLE;

EFI_GUID global_variable = EFI_GLOBAL_VARIABLE;
UINTN size = 0;
EFI_STATUS status = gRT->GetVariable(L"ConOut", &global_variable, NULL,
&size, NULL);
if (status != EFI_BUFFER_TOO_SMALL || size < 4) {
return false;
}
uint8_t *paths = ext_mem_alloc(size);
UINTN paths_alloc = size;
status = gRT->GetVariable(L"ConOut", &global_variable, NULL, &size, paths);
if (status != EFI_SUCCESS || size != paths_alloc) {
pmm_free(paths, paths_alloc);
return false;
}

// Walk the multi-instance device path node by node. Node lengths are
// read bytewise: instance boundaries do not keep the header alignment.
bool walked = false;
bool resolved = false;
bool conflict = false;
struct fb_pci_source agreed = {0};

UINTN at = 0;
UINTN instance_at = 0;
while (at + 4 <= size) {
uint8_t type = paths[at];
uint8_t subtype = paths[at + 1];
UINTN length = (UINTN)paths[at + 2] | ((UINTN)paths[at + 3] << 8);
if (length < 4 || length > size - at) {
break;
}
UINTN node_at = at;
at += length;
if (type != 0x7f) {
continue;
}
// 0x01 ends one instance, 0xff ends the whole list; anything else
// is malformation. Resolve the instance that just closed.
//
// LocateDevicePath stops only at an end-of-ENTIRE-path node, so an
// instance closed by 0x01 would let it read into the next instance.
// The buffer is our own copy of the variable, so promote this end
// node for the call and put it back afterwards.
if (subtype == 0x01) {
paths[node_at + 1] = 0xff;
}
EFI_GUID pci_io_guid = EFI_PCI_IO_PROTOCOL_GUID;
EFI_DEVICE_PATH *remaining =
(EFI_DEVICE_PATH *)(paths + instance_at);
EFI_HANDLE pci_handle = NULL;
instance_at = at;
if (gBS->LocateDevicePath(&pci_io_guid, &remaining, &pci_handle)
== EFI_SUCCESS) {
struct fb_pci_source candidate;
if (pci_handle_location(pci_handle, &candidate)) {
if (!resolved) {
resolved = true;
agreed = candidate;
} else if (agreed.segment != candidate.segment
|| agreed.bus != candidate.bus
|| agreed.device != candidate.device
|| agreed.function != candidate.function) {
conflict = true;
}
}
}
if (subtype == 0x01) {
paths[node_at + 1] = 0x01;
continue;
}
walked = subtype == 0xff && at == size;
break;
}
pmm_free(paths, paths_alloc);

if (!walked) {
return false;
}
fb->source_probe |= FB_PROBE_CONOUT_VAR;
if (!resolved) {
return false;
}
fb->source_probe |= FB_PROBE_CONOUT_PCI;
if (conflict) {
return false;
}
fb->source_probe |= FB_PROBE_CONOUT_UNIQUE;

fb->source_type = FB_SOURCE_PCI;
fb->pci_source = agreed;
printv("gop: Framebuffer source PCI %x:%x:%x.%x (ConOut console)\n",
(uint32_t)agreed.segment, (uint32_t)agreed.bus,
(uint32_t)agreed.device, (uint32_t)agreed.function);
return true;
}

static void get_framebuffer_source(struct fb_info *fb, EFI_HANDLE gop_handle,
size_t handles_count, size_t handle_index) {
if (!get_framebuffer_source_from_device_path(fb, gop_handle)
&& !get_framebuffer_source_from_child_relation(fb, gop_handle)
&& !get_framebuffer_source_from_conout(fb, gop_handle)) {
printv("gop: No positive framebuffer source (probe %x)\n",
fb->source_probe);
}
if (fb->source_type == FB_SOURCE_PCI) {
fb->source_probe |= FB_PROBE_SOURCE_PCI;
}
fb->source_probe |= (uint32_t)(handles_count > 0xff ? 0xff : handles_count)
<< FB_PROBE_HANDLES_SHIFT;
fb->source_probe |= (uint32_t)(handle_index > 0xff ? 0xff : handle_index)
<< FB_PROBE_INDEX_SHIFT;
}

static bool try_mode(struct fb_info *ret, EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,
Expand Down Expand Up @@ -500,7 +654,7 @@ success:;
size_t mode_count;
fb->mode_list = get_mode_list(&mode_count, gop);
fb->mode_count = mode_count;
get_framebuffer_source(fb, handles[i]);
get_framebuffer_source(fb, handles[i], handles_count, i);

fbs_count++;
}
Expand Down
21 changes: 21 additions & 0 deletions common/lib/fb.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@ struct resolution {
#define FB_SOURCE_NONE 0
#define FB_SOURCE_PCI 1

// Source-probe outcome bits, recorded per framebuffer while the source
// proofs run. Wire vocabulary: values must match the
// LIMINE_MP_FRAMEBUFFER_PROBE_* constants in protos/limine_mp.h and the
// revision-1 contract in EXTENSIONS.md.
#define FB_PROBE_DP_PRESENT (1 << 0) // GOP handle carries a device path
#define FB_PROBE_DP_PCI_PREFIX (1 << 1) // that path has an EFI_PCI_IO prefix
#define FB_PROBE_DP_LOCATED (1 << 2) // prefix handle yielded PCI coordinates
#define FB_PROBE_CHILD_SCAN (1 << 3) // child-controller relation scan ran
#define FB_PROBE_CHILD_RELATION (1 << 4) // a PCI parent names this GOP handle
#define FB_PROBE_CONSOLE_HANDLE (1 << 5) // GOP handle == gST->ConsoleOutHandle
#define FB_PROBE_CONOUT_VAR (1 << 6) // ConOut variable read and walked
#define FB_PROBE_CONOUT_PCI (1 << 7) // >=1 ConOut instance reached PCI I/O
#define FB_PROBE_CONOUT_UNIQUE (1 << 8) // every resolved instance agrees on one BDF
#define FB_PROBE_SOURCE_PCI (1 << 9) // final verdict: source_type == PCI

// [23:16] GOP handle count seen by init_gop, [31:24] this framebuffer's
// handle index, both saturating at 0xff.
#define FB_PROBE_HANDLES_SHIFT 16
#define FB_PROBE_INDEX_SHIFT 24

struct fb_pci_source {
uint16_t segment;
uint8_t bus;
Expand Down Expand Up @@ -44,6 +64,7 @@ struct fb_info {

uint8_t source_type;
struct fb_pci_source pci_source;
uint32_t source_probe;
};

extern struct fb_info *fb_fbs;
Expand Down
4 changes: 4 additions & 0 deletions common/protos/limine.c
Original file line number Diff line number Diff line change
Expand Up @@ -1649,6 +1649,7 @@ FEAT_START
struct limine_mp_framebuffer_pci_source *pci_sources =
ext_mem_alloc_counted(fbs_count, sizeof(struct limine_mp_framebuffer_pci_source));
uint64_t *source_list = ext_mem_alloc_counted(fbs_count, sizeof(uint64_t));
uint64_t *probe_masks = ext_mem_alloc_counted(fbs_count, sizeof(uint64_t));

for (size_t i = 0; i < fbs_count; i++) {
if (fbs[i].source_type == FB_SOURCE_PCI) {
Expand All @@ -1662,12 +1663,15 @@ FEAT_START
}

source_list[i] = reported_addr(&sources[i]);
probe_masks[i] = fbs[i].source_probe;
}

struct limine_mp_framebuffer_source_response *source_response =
ext_mem_alloc(sizeof(struct limine_mp_framebuffer_source_response));
source_response->revision = 1;
source_response->entry_count = fbs_count;
source_response->entries = reported_addr(source_list);
source_response->probe_masks = reported_addr(probe_masks);

source_request->response = reported_addr(source_response);
FEAT_END
Expand Down
Loading
Loading