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.

Cập nhật 8 phút đọc
Driver Types cover

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”:

  • NULL hoặ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_UNSUPPORTED ngay 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_DRIVER open trên protocol cần probe
  • TEST_PROTOCOL có 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

  1. Tại sao Supported() phải CloseProtocol dù return EFI_SUCCESS?
  2. TEST_PROTOCOLBY_DRIVER khác nhau thế nào khi dùng trong Supported()?
  3. Nếu Supported() return EFI_SUCCESS cho controller không hỗ trợ thì Start() sẽ xảy ra gì?
  4. EFI_ALREADY_STARTED có nghĩa là lỗi hay bình thường?
  5. Làm thế nào để trace open count leak từ Supported() trong UEFI Shell?

Bài liên quan

Nguồn tham khảo public

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.

Biến note thành bài viết hoàn chỉnh

Notes là nơi ghi nhanh khái niệm.