Supported() trong UEFI Driver Model là gì?
Supported() là hàm probe của Driver Binding. Hiểu return status, OpenProtocol attribute đúng, anti-pattern làm driver không bao giờ bind và cách trace khi Supported fail.
Supported() là cửa đầu tiên của Driver Binding. Firmware gọi hàm này để hỏi: “Driver này có thể quản lý controller này không?” Nếu Supported() trả EFI_UNSUPPORTED, Start() không bao giờ được gọi trên controller đó.
Vì đứng trước Start(), lỗi trong Supported() thường âm thầm - không có error message, không có crash, chỉ là driver không bao giờ bind.
Probe contract - những gì Supported() được và không được làm
Supported() phải trả lời câu hỏi có/không mà không thay đổi state hệ thống:
| Mục | Giá trị | Ghi chú |
|---|---|---|
| Được làm | Probe theo ownership model | OpenProtocol BY_DRIVER để probe theo đúng cách Start() sẽ dùng. Có thể dùng TEST_PROTOCOL khi chỉ cần biết protocol tồn tại. |
| Phải làm | CloseProtocol BY_DRIVER trước khi return | Mọi protocol được open theo BY_DRIVER phải CloseProtocol() trước khi return - kể cả khi return EFI_SUCCESS. TEST_PROTOCOL không cần close vì không claim interface. |
| Không được làm | Allocate resource dài hạn | Supported() có thể bị gọi nhiều lần và có thể return EFI_UNSUPPORTED. Resource quản lý controller nên được allocate trong Start(), không phải Supported(). |
| Không được làm | Modify hardware state | Reset device, write register, init hardware trong Supported() là sai - Start() mới là nơi làm điều đó. |
| Không được làm | Install/uninstall protocol | Supported() không nên thay đổi handle database. |
Return status - mỗi giá trị có ý nghĩa khác nhau
| Mục | Giá trị | Ghi chú |
|---|---|---|
| EFI_SUCCESS | Driver hỗ trợ controller này | Firmware sẽ gọi Start() tiếp theo. Trả SUCCESS sai sẽ dẫn đến Start() fail với lỗi khó debug hơn. |
| EFI_UNSUPPORTED | Driver không hỗ trợ | Firmware thử driver tiếp theo trong list. Return này là bình thường - không phải lỗi. |
| EFI_ALREADY_STARTED | Protocol đã được cùng driver mở trên controller | Với device driver: thường báo đã bind. Với bus driver: có thể cần tiếp tục kiểm tra RemainingDevicePath vì firmware đang yêu cầu tạo child cụ thể. |
| EFI_ACCESS_DENIED | Protocol bị driver khác hoặc access mode khác giữ | OpenProtocol BY_DRIVER fail. Driver hiện tại không thể claim controller. Cần trace ai đang giữ open count. |
OpenProtocol trong Supported() - dùng attribute nào
Có hai mức probe phổ biến, tùy mức độ cần thiết:
// Mức 1: TEST_PROTOCOL - chỉ kiểm tra protocol có tồn tại không
// Không lấy interface, không claim, không cần close
Status = gBS->OpenProtocol(
ControllerHandle,
&gEfiPciIoProtocolGuid,
NULL,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_TEST_PROTOCOL
);
// Dùng khi chỉ cần biết protocol có hay không, không cần đọc thêm
// Nhược điểm: không detect được controller đang bị driver khác giữ
// Mức 2: BY_DRIVER - claim tạm theo đúng ownership model
// BẮT BUỘC CloseProtocol trước khi return
EFI_PCI_IO_PROTOCOL *PciIo;
Status = gBS->OpenProtocol(
ControllerHandle,
&gEfiPciIoProtocolGuid,
(VOID **)&PciIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER // ← cùng model mà Start() sẽ dùng
);
if (EFI_ERROR(Status)) {
return EFI_UNSUPPORTED;
}
// Đọc Vendor ID / Device ID
UINT16 VendorId;
PciIo->Pci.Read(PciIo, EfiPciIoWidthUint16, PCI_VENDOR_ID_OFFSET, 1, &VendorId);
// PHẢI close trước khi return - kể cả khi return EFI_SUCCESS
gBS->CloseProtocol(
ControllerHandle,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
return (VendorId == MY_VENDOR_ID) ? EFI_SUCCESS : EFI_UNSUPPORTED;
Nên probe theo BY_DRIVER khi driver cần đọc thêm thông tin từ interface - đây là cách probe theo đúng ownership model mà Start() sẽ dùng, giúp phát hiện controller đang bị giữ bởi driver khác. TEST_PROTOCOL phù hợp khi chỉ cần kiểm tra protocol chain tồn tại mà không cần đọc interface.
Anti-pattern thực tế
Anti-pattern 1: Trả EFI_SUCCESS quá rộng
// SAI - Supported() chỉ kiểm tra EFI_PCI_IO_PROTOCOL có hay không
// mà không check Vendor/Device ID
Status = gBS->OpenProtocol(..., EFI_OPEN_PROTOCOL_TEST_PROTOCOL);
if (!EFI_ERROR(Status)) {
return EFI_SUCCESS; // Báo support tất cả PCI device → Start() sẽ fail trên device không hợp lệ
}
Hậu quả: Start() được gọi trên controller không phù hợp, fail với lỗi khó trace hơn.
Anti-pattern 2: Allocate trong Supported()
// SAI - allocate private context trong Supported()
MY_CONTEXT *Context;
gBS->AllocatePool(EfiBootServicesData, sizeof(MY_CONTEXT), (VOID **)&Context);
// ... check device ...
// Nếu return EFI_UNSUPPORTED, Context bị leak
// ConnectController có thể gọi Supported() nhiều lần → leak mỗi lần
Anti-pattern 3: Không xử lý đúng EFI_ALREADY_STARTED
// EFI_ALREADY_STARTED có nghĩa cùng driver đã mở protocol trên controller này
// Với device driver: thường nên return EFI_ALREADY_STARTED
if (Status == EFI_ALREADY_STARTED) {
return EFI_ALREADY_STARTED;
}
// Với bus driver: KHÔNG nên luôn return ngay
// ConnectController có thể gọi Supported() với RemainingDevicePath mới
// để tạo child handle cụ thể - bus driver cần tiếp tục kiểm tra
// RemainingDevicePath thay vì return EFI_ALREADY_STARTED ngay lập tức
Anti-pattern 4: Modify hardware trong Supported()
// SAI - reset device trong Supported()
PciIo->Mem.Write(...); // Ghi register
Device->Reset(); // Hardware reset
// Supported() có thể được gọi nhiều lần bởi nhiều driver
// Reset ở đây gây side effect không kiểm soát được
RemainingDevicePath - tại sao Supported() của bus driver phức tạp hơn
Supported() có parameter thứ ba: RemainingDevicePath OPTIONAL. Device driver thường ignore nó, nhưng bus driver phải kiểm tra:
EFI_STATUS EFIAPI
MyBusDriverSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
// Bước 1: kiểm tra controller handle có protocol bus cần không
// ...
// Bước 2: kiểm tra RemainingDevicePath
if (RemainingDevicePath != NULL) {
// Nếu là End node: connect tất cả child, không cần kiểm tra thêm
if (IsDevicePathEnd(RemainingDevicePath)) {
return EFI_SUCCESS;
}
// Nếu là node cụ thể: kiểm tra node đầu có hợp lệ với bus này không
if (!IsValidChildNode(RemainingDevicePath)) {
return EFI_UNSUPPORTED;
}
}
// RemainingDevicePath == NULL: firmware muốn connect theo policy BDS/platform
return EFI_SUCCESS;
}
Đây là lý do Supported() của bus driver không chỉ là “test protocol tồn tại”:
NULLhoặc End node → driver có thể manage controller và tạo all children- Node cụ thể → driver phải xác nhận mình có thể tạo child device khớp với node đó
- Node không hợp lệ →
EFI_UNSUPPORTEDngay cả khi controller protocol đúng
Failure pattern - Supported() fail nhưng không rõ lý do
Supported() luôn trả EFI_UNSUPPORTED dù controller đúng:
- Protocol required không có trên controller handle - parent bus driver chưa enumerate
- Vendor/Device ID check sai - byte order, offset sai
- Controller handle là wrong handle type - đang check parent thay vì child
Supported() trả EFI_ACCESS_DENIED:
- Driver khác đã giữ
BY_DRIVERopen trên protocol cần probe TEST_PROTOCOLcó thể dùng như bước kiểm tra rất nhẹ khi chỉ cần biết protocol tồn tại - nhưng nếu driver cần claim controller theo Driver Binding contract, Supported() vẫn nên probe theo cùng ownership model mà Start() sẽ dùng (thường làBY_DRIVER)
Start() fail ngay sau Supported() pass:
- Supported() trả SUCCESS nhưng check không đủ chặt - Start() mới phát hiện device không hợp lệ
- Có thể tighten Supported() check để fail sớm hơn với message rõ hơn
Trace khi Supported() có vấn đề
# UEFI Shell: xem driver nào đang giữ open trên một handle
Shell> openinfo <handle_number>
# Output: list open agents, attributes (BY_DRIVER, GET, TEST...)
# Nếu thấy open count > 0 với agent không phải driver mong đợi → leak
# Xem tất cả driver và controller đã bind
Shell> drivers
Shell> dh -v
# Debug log trong EDK II - ConnectController flow
grep -r "CoreConnectController\|Supported" \
edk2/MdeModulePkg/Core/Dxe/ --include="*.c" | head -20
# Tìm hàm CoreConnectController() để xem iterate flow qua Driver Binding
Checklist Supported()
Câu hỏi tự kiểm tra
- Tại sao
Supported()phảiCloseProtocoldù returnEFI_SUCCESS? TEST_PROTOCOLvàBY_DRIVERkhác nhau thế nào khi dùng trongSupported()?- Nếu
Supported()returnEFI_SUCCESScho controller không hỗ trợ thìStart()sẽ xảy ra gì? EFI_ALREADY_STARTEDcó nghĩa là lỗi hay bình thường?- Làm thế nào để trace open count leak từ
Supported()trong UEFI Shell?
Bài liên quan
- Driver Binding Flow là gì?
- UEFI Driver Model là gì?
- Start() trong UEFI Driver Model là gì?
- Stop() trong UEFI Driver Model là gì?
- Protocol là gì?
Nguồn tham khảo public
- UEFI Specification 2.11 - EFI_DRIVER_BINDING_PROTOCOL.Supported()
- EDK II UEFI Driver Writer’s Guide - Supported()
- EDK II - DriverBinding.h Protocol definition
Thấy nội dung này hữu ích?
Lưu lại hoặc chia sẻ cho người cũng đang học firmware, BIOS/UEFI và embedded systems.
Nội dung liên quan
Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.
Start() trong UEFI Driver Model là gì?
Start() là nơi driver bind vào controller: open BY_DRIVER, install protocol, tạo child handle nếu là bus driver. Hiểu cleanup fail path và anti-pattern làm handle database bẩn.
Driver Binding Flow là gì?
Giải thích Supported, Start, Stop trong UEFI Driver Model và cách nó ảnh hưởng quá trình enumerate thiết bị.
Stop() trong UEFI Driver Model là gì?
Stop() là cleanup đối xứng của Start(). Hiểu CloseProtocol, uninstall protocol, NumberOfChildren, destroy child handle và anti-pattern làm DisconnectController fail.
Biến note thành bài viết hoàn chỉnh
Notes là nơi ghi nhanh khái niệm.