Chapter 11
Managing UEFI Drivers Using the Shell

He has half the deed done who has made a beginning.

—Horace

Several UEFI Shell commands can be used to help debug UEFI drivers. These UEFI Shell commands are already documented in the UEFI 2.0 Shell Specification, so the full capabilities of the UEFI Shell commands are not discussed here. There is also a built-in UEFI Shell command called help that provides a detailed description of an UEFI Shell command. Figure 11.1 shows the results of issuing the “help –b” command.

Figure 11.1: The help –b Command.

Testing Specific Protocols

Table 11.1 lists UEFI Shell commands that can be used to test and debug UEFI drivers along with the protocol and/ or service exercised.

Table 11.1:. UEFI Shell Commands

Command Protocol Tested Service Tested
Load –nc DriverEntryPoint
Supported()
Load DriverEntryPoint
Driver Binding Supported()
Driver Binding Start()
Unload Loaded Image Unload()
Connect Driver Binding Supported()
Driver Binding Start()
Disconnect Driver Binding Stop()
Reconnect Driver Binding Supported()
Driver Binding Start()
Driver Binding Stop()
Drivers Component Name GetDriverName()
Devices Component Name GetDriverName()
Component Name GetControllerName()
DevTree Component Name GetControllerName()
Dh –d Component Name GetDriverName()
Component Name GetControllerName()
DrvCfg –s Driver Configuration SetOptions()
DrvCfg –f Driver Configuration ForceDefaults()
DrvCfg –v Driver Configuration OptionsValid()
DrvDiag Driver Diagnostics RunDiagnostics()

Other tests can be performed from within the UEFI Shell, as listed in Table 11.2. These are not testing a specific protocol, but are testing for other coding practices.

Table 11.2: Other Shell Testing Procedures

Shell Command sequence What it tests
Shell> Memmap Tests for incorrectly matched up DriverEntryPoint
Shell> Dh and Unload() functions.
Shell> Load DriverName.efi This will catch memory allocation that is not unallocated, protocols that are installed and not uninstalled, and so on.
Shell> Memmap
Shell> Dh
Shell> Unload DriverHandle
Shell> Memmap
Shell> Dh
Shell> Memmap Tests for incorrectly matched up DriverBinding Start() and Stop() functions. This will catch memory allocation that is not unallocated.
Shell> Connect DeviceHandle
DriverHandle
Shell> Memmap
Shell> Disconnect DeviceHandle
DriverHandle
Shell> Memmap
Shell> Reconnect DeviceHandle
Shell> Memmap
Shell> dh Tests for incorrectly matched up DriverBinding Start() Stop() and functions. This will catch protocols that are installed and not uninstalled.
Shell> Connect DeviceHandle
DriverHandle
Shell> dh
Shell> Disconnect DeviceHandle
DriverHandle
Shell> dh
Shell> Reconnect DeviceHandle
Shell> dh
Shell> OpenInfo DeviceHandle Tests for incorrectly matched up DriverBinding Start() Stop() and functions. This will catch protocols that are opened and not closed.
Shell> Connect DeviceHandle
DriverHandle
Shell> OpenInfo DeviceHandle
Shell> Disconnect DeviceHandle
DriverHandle
Shell> OpenInfo DeviceHandle
Shell> Reconnect DeviceHandle
Shell> OpenInfo DeviceHandle

Loading and Unloading UEFI Drivers

Two UEFI Shell commands are available to load and start UEFI drivers, Load and LoadPciRom. The UEFI Shell command that can be used to unload a UEFI driver if it is unloadable is Unload.

Load

The Load command loads a UEFI driver from a file. UEFI driver files typically have an extension of . efi. This command has one important option, the -nc (“No Connect”) option, for UEFI driver developers. When the Load command is used without the -nc option, the loaded driver is automatically connected to any devices in the system that it is able to manage. This means that the UEFI driver’s entry point is executed and then the EFI Boot Service ConnectController() is called. If the UEFI driver produces the Driver Binding Protocol in the driver’s entry point, then the ConnectController() call exercises the Supported() and Start() services of Driver Binding Protocol that was produced.

If the -nc option is used with the Load command, then this automatic connect operation is not performed. Instead, only the UEFI driver’s entry point is executed. When the -nc option is used, the UEFI Shell command Connect can be used to connect the UEFI driver to any devices in the system that it is able to manage. The Load command can also take wild cards, so multiple UEFI drivers can be loaded at the same time.

The following are some examples of the Load command.

Example 1 loads and does not connect the UEFI driver image EfiDriver.efi. This example exercises only the UEFI driver’s entry point:

fs0:> Load -nc EfiDriver.efi

Example 2 loads and connects the UEFI driver image called EfiDriver.efi. This example exercises the UEFI driver’s entry point and the Supported() and Start() functions of the Driver Binding Protocol:

fs0:> Load EfiDriver.efi

Example 3 loads and connects all the UEFI drivers with an .efi extension from fs0:, exercising the UEFI driver entry points and their Supported() and Start() functions of the Driver Binding Protocol:

fs0:> Load *.efi

LoadPciRom

The LoadPciRom command simulates the load of a PCI option ROM by the PCI bus driver. This command parses a ROM image that was produced with the EfiRom build utility. Details on the EfiRom build utility can be found at www.tianocore.org. The LoadPciRom command finds all the UEFI drivers in the ROM image and attempts to load and start all the UEFI drivers. This command helps test the ROM image before it is burned into a PCI adapter’s ROM. No automatic connects are performed by this command, so only the UEFI driver’s entry point is exercised by this command. The UEFI Shell command Connect must be used for the loaded UEFI drivers to start managing devices. The example below loads and calls the entry point of all the UEFI drivers in the ROM file called MyAdapter.ROM:

fs 0:> LoadPciRom MyAdapter.ROM

Unload

The Unload command unloads a UEFI driver if it is unloadable. This command takes a single argument that is the image handle number of the UEFI driver to unload. The Dh -p Image command and the Drivers command can be used to search for the image handle of the driver to unload. Once the image handle number is known, an unload operation can be attempted. The Unload command may fail for one of the following two reasons:

  1. The UEFI driver may not be unloadable, because UEFI drivers are not required to be unloadable.
  2. The UEFI driver may be unloadable, but it may not be able to be unloaded right now.

Some UEFI drivers may need to be disconnected before they are unloaded. They can be disconnected with the Disconnect command. The following example unloads the UEFI driver on handle 27. If the UEFI driver on handle 27 is unloadable, it will have registered an Unload() function in its Loaded Image Protocol. This command exercises the UEFI driver’s Unload() function.

Shell> Unload 27

Connecting UEFI Drivers

Three UEFI Shell commands can be used to test the connecting of UEFI drivers to devices: Connect, Disconnect, and Reconnect. These commands have many options. A few are described in the following sections.

Connect

The Connect command can be used to connect all UEFI drivers to all devices in the system or connect UEFI drivers to a single device. The following are some examples of the Connect command.

Example 1 connects all drivers to all devices:

fs0:> Connect -r

Example 2 connects all drivers to the device that is abstracted by handle 23:

fs0:> Connect 23

Example 3 connects the UEFI driver on handle 27 to the device that is abstracted by handle 23:

fs0:> Connect 23 27

Disconnect

The Disconnect command stops UEFI drivers from managing a device. The following are some examples of the Disconnect command.

Example 1 disconnects all drivers from all devices. However, the use of this command is not recommended, because it also disconnects all the console devices:

fs0:> Disconnect -r

Example 2 disconnects all the UEFI drivers from the device represented by handle 23:

fs0:> Disconnect 23

Example 3 disconnects the UEFI driver on handle 27 from the device represented by handle 23:

fs0:> Disconnect 23 27

Example 4 destroys the child represented by handle 32. The UEFI driver on handle 27 produced the child when it started managing the device on handle 23:

fs0:> Disconnect 23 27 32

Reconnect

The Reconnect command is the equivalent of executing the Disconnect and Connect commands back to back. The Reconnect command is the best command for testing the Driver Binding Protocol of UEFI drivers. This command tests the Supported(), Start(), and Stop() services of the Driver Binding Protocol. The Reconnect -r command tests the Driver Binding Protocol for every UEFI driver that follows the UEFI Driver Model. Use this command before an UEFI driver is loaded to verify that the current set of drivers pass the Reconnect -r test, and then load the new UEFI driver and rerun the Reconnect -r test. A UEFI driver is not complete until it passes this interoperability test with the UEFI core and the full set of UEFI drivers. The following are some examples of the Reconnect command.

Example 1 reconnects all the UEFI drivers to the device handle 23:

fs0:> Reconnect 23

Example 2 reconnects the UEFI driver on handle 27 to the device on handle 23:

fs0:> Reconnect 23 27

Example 3 reconnects all the UEFI drivers in the system:

fs0:> Reconnect -r

Driver and Device Information

Five UEFI Shell commands can be used to dump information about the UEFI drivers that follow the UEFI Driver Model. Each of these commands shows information from a slightly different perspective.

Drivers

The Drivers command lists all the UEFI drivers that follow the UEFI Driver Model. It uses the GetDriverName() service of the Component Name protocol to retrieve the human-readable name of each UEFI driver if it is available. It also shows the file path from which the UEFI driver was loaded. As UEFI drivers are loaded with the Load command, they will appear in the list of drivers produced by the Drivers command. The Drivers command can also show the name of the UEFI driver in different languages. The following are some examples of the Drivers command.

Example 1 shows the Drivers command being used to list the UEFI drivers in the default language:

fs0:> Drivers

Example 2 shows the driver names in Spanish:

fs0:> Drivers -lspa

Devices

The Devices command lists all the devices that are being managed or produced by UEFI drivers that follow the UEFI Driver Model. This command uses the GetControllerName() service of the Component Name protocol to retrieve the humanreadable name of each device that is being managed or produced by UEFI drivers. If a human-readable name is not available, then the EFI device path is used. The following are some examples of the Devices command.

Example 1 shows the Devices command being used to list the UEFI drivers in the default language:

fs0:> Devices

Example 2 shows the device names in Spanish:

fs0:> Devices -lspa

DevTree

Similar to the Devices command. the DevTree command lists all the devices being managed by UEFI drivers that follow the UEFI Driver Model. This command uses the GetControllerName() service of the Component Name Protocol to retrieve the human-readable name of each device that is being managed or produced by UEFI drivers. If the human-readable name is not available, then the EFI device path is used. This command also shows the parent/ child relationships between all of the devices visually by displaying them in a tree structure. The lower a device is in the tree of devices, the more the device name is indented. The following are some examples of the DevTree command.

Example 1 displays the device tree with the device names in the default language:

fs0:> DevTree

Example 2 displays the device tree with the device names in Spanish:

fs0:> DevTree -lspa

Example 3 displays the device tree with the device names shown as EFI device paths:

fs0:> DevTree -d

Dh –d

The Dh -d command provides a more detailed view of a single driver or a single device than the Drivers, Devices, and DevTree commands. If a driver binding handle is used with the Dh -d command, then a detailed description of that UEFI driver is provided along with the devices that the driver is managing and the child devices that the driver has produced. If a device handle is used with the Dh -d command, then a detailed description of that device is provided along with the drivers that are managing that device, that device’s parent controllers, and the device’s child controllers. If the Dh -d command is used without any parameters, then detailed information on all of the drivers and devices is displayed. The following are some examples of the Dh -d command.

Example 1 displays the details on the UEFI driver on handle 27:

fs0:> Dh -d 27

Example 2 displays the details for the device on handle 23:

fs0:> Dh -d 23

Example 3 shows details on all the drivers and devices in the system:

fs0:> Dh -d

OpenInfo

The OpenInfo command provides detailed information about a device handle that is being managed by one or more UEFI drivers that follow the UEFI Driver Model. The OpenInfo command displays each protocol interface installed on the device handle and the list of agents that have opened that protocol interface with the OpenProtocol() Boot Service. This command can be used in conjunction with the Connect, Disconnect, and Reconnect commands to verify that an UEFI driver is opening and closing protocol interfaces correctly. The following example shows the OpenInfo command being used to display the list of protocol interfaces on device handle 23 along with the list of agents that have opened those protocol interfaces:

fs0:> OpenInfo 23

Testing the Driver Configuration and Driver Diagnostics Protocols

The UEFI Shell provides a command that can be used to test the Driver Configuration Protocol, DrvCfg, and one that can be used to test the Driver Diagnostics Protocol, DrvDiag.

DrvCfg

The DrvCfg command provides the services that are required to test the Driver Configuration Protocol implementation of a UEFI driver. This command can show all the devices that are being managed by UEFI drivers that support the Driver Configuration Protocol. The Devices and Drivers commands also show the drivers that support the Driver Configuration Protocol and the devices that those drivers are managing or have produced. Once a device has been chosen, the DrvCfg command can be used to invoke the SetOptions(), ForceDefaults(), or OptionsValid() services of the Driver Configuration Protocol. The following are examples of the DrvCfg command.

Example 1 displays all the devices that are being managed by UEFI drivers that support the Driver Configuration Protocol:

fs0:> DrvCfg

Example 2 forces defaults on all the devices in the system:

fs0:> DrvCfg -f

Example 3 validates the options on all the devices in the system:

fs0:> DrvCfg -v

Example 4 invokes the SetOptions() service of the Driver Configuration Protocol for the driver on handle 27 and the device on handle 23:

fs0:> DrvCfg -s 23 27

DrvDiag

The DrvDiag command provides the ability to test all the services of the Driver Diagnostics Protocol that are produced by a UEFI driver. This command shows the devices that are being managed by UEFI drivers that support the Driver Diagnostics Protocol. The Devices and Drivers commands also show the drivers that support the Driver Diagnostics Protocol and the devices that those drivers are managing or have produced. Once a device has been chosen, the DrvDiag command can be used to invoke the RunDiagnostics() service of the Driver Diagnostics Protocol. The following are some examples of the DrvDiag command.

Example 1 displays all the devices that are being managed by UEFI drivers that support the Driver Diagnostics Protocol:

fs0:> DrvDiag

Example 2 invokes the RunDiagnostics() service of the Driver Diagnostics Protocol in standard mode for the driver on handle 27 and the device on handle 23:

fs0:> DrvDiag -s 23 27

Example 3 invokes the RunDiagnostics() service of the Driver Diagnostics Protocol in manufacturing mode for the driver on handle 27 and the device on handle 23:

fs0:> DrvDiag -m 23 27

Debugging Code Statements

Every module has a debug (check) build and a clean build. The debug build includes code for debug that will not be included in normal clean production builds. A debug build is enabled when the identifier EFI_DEBUG exists. A clean build is defined as when the EFI_DEBUG identifier does not exist.

The following debug macros can be used to insert debug code into a checked build. This debug code can greatly reduce the amount of time it takes to root cause a bug. These macros are enabled only in a debug build, so they do not take up any executable space in the production build. Table 11.3 describes the debug macros that are available.

Table 11.3: Available Debug Macros

Debug Macro Description
ASSERT (Expression) For check builds, if Expression evaluates to
FALSE, a diagnostic message is printed and the program is aborted. Aborting a program is usually done via the EFI_BREAKPOINT () macro. For clean builds, Expression does not exist in the program and no action is taken. Code that is required for normal program execution should never be placed inside an ASSERT macro, because the code will not exist in a production build.
ASSERT_EFI_ERROR (Status) For check builds, an assert is generated if Status is an error. This macro is equivalent to ASSERT (!EFI_ERROR (Status)) but is easier to read.
DEBUG ((ErrorLevel, String, …)) For check builds, String and its associated arguments will be printed if the ErrorLevel of the macro is active. See Table 11.4 for a definition of the ErrorLevel values.
DEBUG_CODE (Code) For check builds, Code is included in the build. DEBUG_CODE ( is on its own line and indented like normal code. All the debug code follows on subsequent lines and is indented an extra ) level. The is on the line following all the code and indented at the same level as DEBUG CODE (.
EFI_BREAKPOINT () On a check build, inserts a break point into the code.
DEBUG_SET_MEM (Address, Length) For a check build, initializes the memory starting at Address or Length bytes with the value BAD_POINTER. This initialization is done to enable debug of code that uses memory buffers that are not initialized.
CR (Record, TYPE, Field, Signature) The containing record macro returns a pointer to TYPE when given the structure’s Field name Record). CR and a pointer to it (The macro returns the TYPE pointer for check and production builds. For a check build, an ASSERT () is gen-Signature TYPE erated if the field of is not equal to the Signature in the CR () macro.

The ErrorLevel parameter referenced in the DEBUG() macro allows a UEFI driver to assign a different error level to each debug message. Critical errors should always be sent to the standard error device. However, informational messages that are used only to debug a driver should be sent to the standard error device only if the user wants to see those specific types of messages. The UEFI Shell supports the Err command that allows the user to set the error level. The UEFI Boot Maintenance Manager allows the user to enable and select a standard error device. It is recommended that a serial port be used as a standard error device during debug so the messages can be logged to a file with a terminal emulator. Table 11.4 contains the list of error levels that are supported in the UEFI Shell. Other levels are usable, but not defined for a specific area.

Note: DEBUG ((ErrorLevel, String, …)) is not universally supported. Some EFI-compliant systems may not print out the message.

Table 11.4: Error Levels

Mnemonic Value Description
EFI D INIT 0x00000001 Initialization messages
EFI_D_WARN 0x00000002 Warning messages
EFI D LOAD 0x00000004 Load events
EFI D FS 0x00000008 EFI file system messages
EFI_D_POOL 0x00000010 EFI pool allocation and free messages
EFI D PAGE 0x00000020 EFI page allocation and free messages
EFI D INFO 0x00000040 Informational messages
EFI_D_VARIABLE 0x00000100 EFI variable service messages
EFI D BM 0x00000400 UEFI boot manager messages
EFI D BLKIO 0x00001000 EFI Block I/O Protocol messages
EFI_D_NET 0x00004000 EFI Simple Network Protocol, PXE base code, BIS messages
EFI D UNDI 0x00010000 UNDI driver messages
EFI D LOADFILE 0x00020000 Load File Protocol messages
EFI_D_EVENT 0x00080000 EFI Event Services messages
EFI DERROR 0x80000000 Critical error messages

POST Codes

If an UEFI driver is being developed that cannot make use of the DEBUG() and ASSERT() macros, then a different mechanism must be used to help in the debugging process. Under these conditions, it is usually sufficient to send a small amount of output to a device to indicate what portions of an UEFI driver have executed and where error conditions have been detected. A few possibilities are presented below, but many others are possible depending on the devices that may be available on a specific platform. It is important to note that these mechanisms are useful during driver development and debug, but they should never be present in production versions of UEFI drivers because these types of devices are not present on all platforms.

The first possibility we will describe here is to use a POST card.

Post Card Debug

A POST card is an add-in card adapter that displays the hex value of an 8-bit I/O write cycle to address 0x80 (and sometimes 0x81). If a UEFI driver can depend on the PCI Root Bridge I/O Protocol being present, then the driver can use the services of the PCI Root Bridge I/O Protocol to send an 8-bit I/O write cycle to address 0x80. A driver can also use the services of the PCI I/O Protocol to write to address 0x80, as long as the pass-through BAR value is used. Figure 11.2 shows how the PCI Root Bridge I/O and PCI I/O Protocols can be used to send a value to a POST card.

Figure 11.2: POST Code Examples

Text-Mode VGA Frame Buffer

The next possibility is a text-mode VGA frame buffer. If a system initializes the textmode VGA display by default before the UEFI driver executes, then the UEFI driver can make use of the PCI Root Bridge I/O or PCI I/O Protocols to write text characters to the text-mode VGA display directly. Figure 11.3 shows how the PCI Root Bridge I/O and PCI I/O Protocols can be used to send the text message “ABCD” to the text-mode VGA frame buffer. Some systems do not have a VGA controller, so this solution will not work on all systems.

Figure 11.3: VGA Display Examples

Other Options

Another option that can be used if the UEFI driver being developed cannot make use of the DEBUG() and ASSERT() macros is to use some type of byte-stream-based device. This device could include a UART or an SMBus, for example. Like the POST card, the idea is to use the services of the PCI Root Bridge I/O or PCI I/O Protocols to initialize and send characters to the byte-stream device.

Many EFI-compliant implementations allow for the use of a COM cable to send debug information to another system. This allows the developer or tester to see debug code statements and other output from a separate system.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.98.0