The Uncoöperative Organization

Programming and other human stuff.

UEFI Debugging Tools

One of the many things I work on is UEFI support. It’s an interesting thing to work on, in part because there’s a lot of new development and it’s at a fairly low level, which is just the sort of thing I like.

Often during UEFI development, we’ll see a bug and need to diagnose whether it’s a problem with the hardware, the firmware, the bootloader, the OS kernel, or even a userland program. One case of this is when console graphics don’t work right.

A recent bug reported that console graphics weren’t working due to an invalid pixel format being chosen. While diagnosing this problem, there are a couple of things that could go wrong. The firmware driver could be reporting the wrong modes, the bootloader could have a bug where it’s querying the modes incorrectly or misinforming the kernel as to the mode chosen, or the kernel could have a bug where it uses the mode incorrectly. It’s also possible that the console works until a KMS driver – that is, a native driver for the card – is loaded.

In UEFI, console graphics involves every part of the stack. The system firmware provides an API known as Graphics Output Protocol, or GOP. The algorithm used by the bootloader basically looks like this somewhat simplified function:

set_mode.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
int gop_enable(struct graphics *graphics, struct kernel_params *kernel_params) {
  efi_handle gop_intf = efi_locate_protocol(&graphics_output_guid, NULL);
  efi_status status = EFI_SUCCESS;

  if (!gop_intf)
      return -1;

  graphics->max_mode = gop_intf->mode->max_mode;
  graphics->modes = calloc(graphics->max_mode, sizeof(void *));
  if (!graphics->modes)
      return -1;

  for (int i = 0; i < graphics->max_mode; i++) {
      graphics->modes[i] = calloc(1, sizeof graphics->modes[0]);
      if (!graphics->modes[i]) {
          status = EFI_NO_MEMORY;
          goto fail;
      }

      graphics->modes[i]->number = i;
      status = gop_intf->query_mode(gop_intf, i,
                      &graphics->modes[i]->size,
                      &graphics->modes[i]->info);
      if (status != EFI_SUCCESS) {
          free(graphics->modes[i]);
          graphics->modes[i] = NULL;
          goto fail;
      }
  }

  /* this sorts the modes by size, but leaves mode.number intact
  * so that we have a way to reference it to the firmware */
  sort_modes(graphics->modes);

  gop_intf->set_mode(gop_intf, graphics->modes[0]->number);
  set_kernel_params(graphics->modes[0], kernel_params);

  return 0;
fail:
  /* best effort... */
  if (graphics->modes && graphics->modes[0]) {
      gop_intf->set_mode(gop_intf, graphics->modes[0]->number);
      set_kernel_params(graphics->modes[0], kernel_params);
  }
  return -1;
}

This is a simplified version – for more details, check out efigraph.c for the excrutiating details.

set_kernel_params() in turn fills out the data structure we give to the kenrel, which then uses the efifb driver to configure a framebuffer console with the correct resolution and pixel format.

Clearly, there is plenty that can go wrong here. With that in mind, I wrote a tool called modelist.efi to debug some of these problems.

In the bug, I’m told:

With a 1280x1024 capable monitor connected to a *censored* or *censored* in
uEFI mode, the remote video redirection via BMC (*censored*) does not display
anything. The reason is that the efifb driver chooses the VGA mode
1280x1024x32bpp which the video redirection doesn’t support (the *censored*
chip doesn’t support video modes that need more than 4MB video RAM).
See also bug #XXXXXX for the same problem reported in the past for Xorg / mga
driver.

efifb: probing for efifb
efifb: framebuffer at 0x90000000, mapped to 0xffffc90011800000, using 8192k,
total 8192k
efifb: mode is 1280x1024x32, linelength=5120, pages=1
efifb: scrolling: redraw
efifb: Truecolor: size=8:8:8:8, shift=24:16:8:0
fbcon: EFI VGA (fb0) is primary device
Console: switching to colour frame buffer device 160x64
fb0: EFI VGA frame buffer device

With the knowledge of how the stack works in mind, I know that we need to find out what the firmware is actually returning to the bootloader in order to tell what layer the problem is at, so I ask:

Can you run /boot/efi/EFI/redhat/modelist.efi (from the gnu-efi package) and
show us the output?

And the response is:

fs0:\EFI\redhat> modelist.efi
GOP reports MaxMode 4
0: 640x480 BGRR pitch 640
1: 800x600 BGRR pitch 800
2: 1024x768 BGRR pitch 1024
3: 1280x1024 BGRR pitch 1280

So this tells me that the firmware is listing 4 video modes, each of which has a 32-bit pixel of the form (blue,green,red,reserved). At this point we know that the firmware is telling us about modes that the hardware cannot support, and efifb cannot correctly support this device until the firmware is fixed.

It’s important to have and use the right tools.