This article focuses on the standard UEFI Driver framework and explains how a driver uses the Driver Binding Protocol to identify, bind, initialize, and unload devices. It also clarifies the relationship between Handles, Protocols, and controllers—an area that often confuses beginners. Keywords: UEFI Driver, EDK II, Protocol.
Technical Specification Snapshot
| Parameter | Value |
|---|---|
| Primary Language | C / UEFI C |
| Execution Phase | UEFI DXE |
| Core Protocols | EFI_DRIVER_BINDING_PROTOCOL, EFI_PCI_IO_PROTOCOL, EFI_USB_IO_PROTOCOL |
| Key Interfaces | Supported / Start / Stop / EntryPoint |
| Development Framework | EDK II |
| Dependent Libraries | UefiBootServicesTableLib, MemoryAllocationLib, DebugLib |
| Intended Audience | Firmware development, driver development, embedded engineers |
| GitHub Stars | Not provided in the original |
UEFI Drivers and UEFI Applications Have Clearly Separated Responsibilities
A UEFI Application typically runs in the Shell and exits when execution completes. Its main job is to locate and invoke existing Protocols. A UEFI Driver is different: its goal is to identify devices, take control of them, and install new Protocols that higher-level programs can continue to use.
This design is similar to the Linux model where the application layer calls into the driver layer. The difference is that, in UEFI, services are not exposed as traditional device files. Instead, they are exposed as Protocols installed on Handles.
The Main Relationship Between Drivers and Applications
// The driver installs services, and the application consumes them
Driver: driver load -> detect device -> bind device -> install Protocol
Application: locate Protocol -> call service
This flow captures the smallest complete loop in the UEFI driver model.
The Driver Binding Protocol Is the Core Entry Point of the UEFI Driver Model
Every standard UEFI Driver must implement EFI_DRIVER_BINDING_PROTOCOL. It defines the three core interfaces for determining whether a driver supports a device, how it starts that device, and how it stops it. This protocol serves as the unified entry point the firmware uses to dispatch drivers.
typedef struct _EFI_DRIVER_BINDING_PROTOCOL {
EFI_DRIVER_BINDING_SUPPORTED Supported; // Determine whether the controller is supported
EFI_DRIVER_BINDING_START Start; // Bind to and initialize the device
EFI_DRIVER_BINDING_STOP Stop; // Unload and release resources
UINT32 Version; // Driver version
EFI_HANDLE ImageHandle;
EFI_HANDLE DriverBindingHandle;
} EFI_DRIVER_BINDING_PROTOCOL;
This structure determines whether the system can automatically enumerate and invoke the driver.
A Handle Is Not Always a Device, and That Mental Model Matters
Beginners often interpret a Handle as a physical device handle, but that is incomplete. In UEFI, driver images, applications, service objects, controllers, and even child controllers can all have Handles. In driver development, you most often interact with controller Handles, but the two concepts are not fully interchangeable.
The Supported Function Declares Whether the Driver Supports a Controller
The responsibility of Supported() is not to initialize the device. It performs capability detection. The usual pattern is to try opening a foundational protocol on the controller, such as PCI I/O or USB I/O, and then inspect the configuration space or descriptors to determine whether the hardware matches the target.
EFI_STATUS
EFIAPI
MyDriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
EFI_STATUS Status;
EFI_PCI_IO_PROTOCOL *PciIo;
// Try to open the PCI I/O protocol as a driver to probe device capability
Status = gBS->OpenProtocol(
Controller,
&gEfiPciIoProtocolGuid,
(VOID **)&PciIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR(Status)) {
return EFI_UNSUPPORTED; // The target protocol is missing, so the controller is not supported
}
// Only perform checks here; release the protocol immediately after probing
gBS->CloseProtocol(
Controller,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
Controller
);
return EFI_SUCCESS;
}
This code demonstrates the core principle of Supported(): probe only, and do not retain resources.
The Firmware Dispatches Supported and Start in a Fixed Order
The system call chain is usually: ConnectController() triggers controller connection, the firmware traverses all installed Driver Binding Protocols, calls Supported() according to version priority, and the first driver that returns success gets the chance to bind. The flow then proceeds to Start().
The Start Function Takes Control of the Device and Installs Custom Protocols
Start() is where the driver begins doing real work. It opens the device’s foundational protocol for long-term use, allocates private context, initializes the hardware, and installs the new Protocols provided by the driver onto the controller Handle or a child Handle.
EFI_STATUS
EFIAPI
MyDriverBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
EFI_STATUS Status;
EFI_PCI_IO_PROTOCOL *PciIo;
MY_DEVICE_PRIVATE_DATA *Private;
// Open the protocol and formally acquire access to the hardware
Status = gBS->OpenProtocol(
Controller,
&gEfiPciIoProtocolGuid,
(VOID **)&PciIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR(Status)) {
return Status;
}
// Allocate driver-private context to store runtime state
Private = AllocateZeroPool(sizeof(MY_DEVICE_PRIVATE_DATA));
if (Private == NULL) {
gBS->CloseProtocol(Controller, &gEfiPciIoProtocolGuid, This->DriverBindingHandle, Controller);
return EFI_OUT_OF_RESOURCES;
}
// Initialize the hardware and save the context
Private->PciIo = PciIo;
Status = InitializeHardware(PciIo); // Core logic: complete device initialization
if (EFI_ERROR(Status)) {
FreePool(Private);
gBS->CloseProtocol(Controller, &gEfiPciIoProtocolGuid, This->DriverBindingHandle, Controller);
return Status;
}
return EFI_SUCCESS;
}
This code shows the essence of Start(): acquire access, establish context, and initialize the hardware.
The Stop Function Must Mirror Start With Strictly Symmetrical Cleanup
The goal of Stop() is not simply to exit. It must undo every side effect introduced during Start(). Every opened protocol, allocated memory block, created event, installed protocol, and child controller must be cleaned up in reverse.
EFI_STATUS
EFIAPI
MyDriverBindingStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
)
{
// Close the protocol opened in Start() and release hardware access
gBS->CloseProtocol(
Controller,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
Controller
);
// If private data was allocated, release it here as well
// FreePool(DevicePrivate); retrieve the private structure based on the actual protocol design
return EFI_SUCCESS;
}
This code reflects the first principle of Stop(): the resource lifecycle must form a closed loop.
You Should Memorize the Resource Cleanup Mapping
// Symmetry between Start and Stop
OpenProtocol() -> CloseProtocol()
AllocatePool() -> FreePool()
CreateEvent() -> CloseEvent()
InstallProtocol() -> UninstallProtocol()
CreateChildHandle() -> DestroyChildHandle()
Memorizing this table can significantly reduce resource leaks in UEFI drivers.
The Driver Entry Point Registers the Driver Binding Protocol With the Firmware
The entry point does not initialize devices. Instead, it registers the driver’s capability description with the firmware. Only after the firmware detects that gEfiDriverBindingProtocolGuid has been installed will it treat the current image as a driver eligible to participate in device binding.
EFI_DRIVER_BINDING_PROTOCOL gMyDriverBinding = {
MyDriverBindingSupported,
MyDriverBindingStart,
MyDriverBindingStop,
0x10,
NULL,
NULL
};
EFI_STATUS
EFIAPI
MyDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
gMyDriverBinding.ImageHandle = ImageHandle;
gMyDriverBinding.DriverBindingHandle = ImageHandle;
// Install the Driver Binding Protocol on the image handle so the firmware can enumerate this driver
return gBS->InstallMultipleProtocolInterfaces(
&gMyDriverBinding.DriverBindingHandle,
&gEfiDriverBindingProtocolGuid,
&gMyDriverBinding,
NULL
);
}
This code performs driver registration, not device startup.
The USB DXE Example Demonstrates the Smallest Runnable UEFI Driver Skeleton
The original example implements a simplified USB driver: Supported() uses EFI_USB_IO_PROTOCOL to determine whether the target is a USB device, Start() reads the device descriptor and stores the private context, Stop() closes the protocol and releases resources, and the entry point installs the Driver Binding Protocol.
The USB Driver Example Demonstrates the Protocol-Driven Driver Model
Status = UsbIo->UsbGetDeviceDescriptor(UsbIo, &UsbDeviceDesc);
if (!EFI_ERROR(Status)) {
DEBUG((DEBUG_INFO, "Vendor ID : 0x%04X\n", UsbDeviceDesc.IdVendor)); // Print the vendor ID
DEBUG((DEBUG_INFO, "Product ID: 0x%04X\n", UsbDeviceDesc.IdProduct)); // Print the product ID
DEBUG((DEBUG_INFO, "Class : 0x%02X\n", UsbDeviceDesc.DeviceClass)); // Print the device class
}
This code shows that, during the Start() phase, the driver can already read hardware descriptor information through a standard protocol.
The Image Assets Are Site Elements and Do Not Carry Driver-Specific Technical Details
AI Visual Insight: This image shows a WeChat sharing prompt from the Cnblogs page. It is part of the site’s UI guidance and does not involve the UEFI driver model, protocol binding flow, or device initialization details. Therefore, it does not constitute technical evidence at the firmware development level.
Understanding UEFI Drivers Starts With Seeing Them as Protocol Installers
If a UEFI Application is a protocol consumer, then a UEFI Driver is a protocol producer. It connects to firmware dispatch through Driver Binding, accesses hardware through foundational protocols, and exposes capabilities upward by installing custom protocols.
In EDK II, the real challenge is not writing the function signatures. The hard part is keeping device matching, protocol ownership, private data management, and cleanup logic consistent at all times. Treating Supported(), Start(), and Stop() as a state machine is closer to real development than seeing them as three isolated functions.
FAQ
1. Why does Supported() open a protocol and then immediately close it?
Because Supported() only checks device compatibility and should not hold resources for the long term. If it does not close the protocol, it may block the real Start() phase or interfere with other drivers competing for the same controller.
2. Does Start() always need to install a custom Protocol?
Not necessarily, but it is usually recommended. Installing a custom Protocol lets you abstract driver capabilities into a standard service interface that other UEFI Applications or Drivers can reuse.
3. Why must Stop() be strictly symmetrical with Start()?
The UEFI firmware environment is highly sensitive to resource leaks. Any protocol left open, memory left allocated, or protocol interface left installed can prevent a controller from reconnecting, stop a driver from unloading, or even affect subsequent firmware flows.
[AI Readability Summary]
This article systematically reconstructs the core UEFI Driver development framework. It explains the responsibilities, call chain, and symmetrical cleanup principles of Supported, Start, Stop, and the driver entry point in the Driver Binding Protocol. It also uses a simplified USB DXE driver example to illustrate protocol installation, device binding, and driver registration in EDK II.