Page 1 of 1

Reading EDID fails on OVMF

Posted: Fri Aug 13, 2021 10:41 am
by nexos
Hello,
I am currently working on a display manager for my UEFI bootloader. The only problem is, HandleProtocol() for EDID keeps returning EFI_UNSUPPORTED! Here is the code for

Code: Select all

// Initializes the boot graphics system
void screen_init()
{
    EFI_GUID gopguid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
    UINTN bufsize = 0;
    EFI_HANDLE* handles = NULL;
    // Obtain the size of the GOP handle list
    EFI_STATUS status = gBS->LocateHandle(ByProtocol, &gopguid, NULL, &bufsize, handles);
    // Status must equal EFI_BUFFER_TOO_SMALL
    if(status != EFI_BUFFER_TOO_SMALL)
        efi_panicearly(L"unable to locate GOP handle list\r\n");
    // Allocate the buffer
    handles = (EFI_HANDLE*)efi_alloc(bufsize);
    if(!handles)
        efi_panicearly(L"unable to allocate GOP handle list\r\n");
    // Read in the handle list
    status = gBS->LocateHandle(ByProtocol, &gopguid, NULL, &bufsize, handles);
    if(EFI_ERROR(status))
        efi_panicearly(L"unable to locate GOP handles list\r\n");
    // Loop through every handle, attempting to open up GOP and EDID until we succeed
    UINTN numhandles = bufsize / sizeof(EFI_HANDLE);
    EFI_EDID_ACTIVE_PROTOCOL* edid = NULL;
    for(int i = 0; i < numhandles; ++i)
    {
        // Obtain the current handle
        EFI_HANDLE handle = handles[i];
        // Attempt to open up the GOP on it. At the moment, we only use the first display
        status = gBS->HandleProtocol(handle, &gopguid, (VOID**)&gop);
        // Check if it worked
        if(EFI_ERROR(status))
        {
            // No display is supported
            if(i + 1 == numhandles)
                break;
            else
                continue;
        }
        // Open up EDID now
        EFI_GUID edidguid = EFI_EDID_ACTIVE_PROTOCOL_GUID;
        status = gBS->HandleProtocol(handle, &edidguid, (VOID**)&edid);
        if(EFI_ERROR(status))
        {
            if(i + 1 == numhandles)
                efi_panicearly(L"could not find EDID\r\n");
        }
    }
}
Also, EDK2 is being used to build this
Thanks,
nexos

- Edit - I thought I knew C... The problem was that a continue statement should have been a break #-o . I also cleaned up that loop, it had some very unnecessary stuff in it

Re: Reading EDID fails on OVMF

Posted: Fri Aug 13, 2021 11:12 am
by kzinti
You can't get EDID information on QEMU. At least I've never been able to get any. Given that there is no actual monitor connected, this seems to make sense.

The way I went about debugging EDID is to capture the data on a real HW / monitor and write it to a file. Then you can hardcode that data in your uefi app for work with QEMU.

Also consider that some GOP handles are pointing to virtual devices and there won't be an actual monitor attached to them, even on real hardware.

Finally you should check for the "active edid protocol" and if that fails check for the "discovered edid protocol". It's possible for the former to fail and the second to return something usable. Note that both protocols have the same interface, so it's safe to cast one to the other.

Here is how I do it:

Code: Select all

void EfiBoot::InitDisplays()
{
    UINTN size = 0;
    std::vector<EFI_HANDLE> handles;
    EFI_STATUS status;

    while ((status = g_efiBootServices->LocateHandle(ByProtocol, &g_efiGraphicsOutputProtocolGuid, nullptr, &size, handles.data())) == EFI_BUFFER_TOO_SMALL)
    {
        handles.resize(size / sizeof(EFI_HANDLE));
    }

    if (EFI_ERROR(status))
    {
        // Likely EFI_NOT_FOUND, but any error should be handled as "no display available"
        return;
    }

    for (auto handle: handles)
    {
        EFI_DEVICE_PATH_PROTOCOL* dpp = nullptr;
        g_efiBootServices->HandleProtocol(handle, &g_efiDevicePathProtocolGuid, (void**)&dpp);
        // If dpp is NULL, this is the "Console Splitter" driver. It is used to draw on all
        // screens at the same time and doesn't represent a real hardware device.
        if (!dpp) continue;

        EFI_GRAPHICS_OUTPUT_PROTOCOL* gop = nullptr;
        g_efiBootServices->HandleProtocol(handle, &g_efiGraphicsOutputProtocolGuid, (void**)&gop);
        // gop is not expected to be null, but let's play safe.
        if (!gop) continue;

        EFI_EDID_ACTIVE_PROTOCOL* edid = nullptr;
        if (EFI_ERROR(g_efiBootServices->HandleProtocol(handle, &g_efiEdidActiveProtocolGuid, (void**)&edid)) || !edid)
        {
            g_efiBootServices->HandleProtocol(handle, &g_efiEdidDiscoveredProtocolGuid, (void**)&edid);
        }
 
        // edid can be null, don't rely on it to exist

        m_displays.push_back(std::move(EfiDisplay(gop, edid)));
    }
}

Re: Reading EDID fails on OVMF

Posted: Fri Aug 13, 2021 11:32 am
by nexos
kiznit wrote:You can't get EDID information on QEMU. At least I've never been able to get any. Given that there is no actual monitor connected, this seems to make sense.
Huh, that's interesting. Back when my OS worked on BIOS, I remember I was able to use EDID to give me the resolution. I assume QEMU probably creates a virtual EDID block.

- Edit - Huh, OVMF's EDID has a size of 0, meaning that OVMF must not support EDID on QEMU. Quite interesting, I'm gonna reboot and test on my host to make sure its not a bug in my code

Re: Reading EDID fails on OVMF

Posted: Fri Aug 13, 2021 12:27 pm
by Ethin
If your using EDK II, use gEfiEdidActiveProtocolGuid, gEfiEdidDiscoveredProtocolGuid, or gEfiEdidOverrideProtocolGuid when passing GUIDs to LocateProtocol or HandleProtocol. (For GOP, its gEfiGraphicsOutputProtocolGuid.) You shouldn't need to use the EDID protocols directly -- you should be able to directly call EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE.

Re: Reading EDID fails on OVMF

Posted: Fri Aug 13, 2021 12:36 pm
by nexos
Ethin wrote:If your using EDK II, use gEfiEdidActiveProtocolGuid, gEfiEdidDiscoveredProtocolGuid, or gEfiEdidOverrideProtocolGuid when passing GUIDs to LocateProtocol or HandleProtocol. (For GOP, its gEfiGraphicsOutputProtocolGuid.) You shouldn't need to use the EDID protocols directly -- you should be able to directly call EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE.
Yeah, I managed to get the EDID, but according to the spec, a SizeOfEdid equal to 0 means that there is no EDID. Like I said above, I'm going to test on real hardware, and we'll see :)