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.

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

Start() là nơi driver thực sự chiếm quyền quản lý controller. Khác với Supported() chỉ probe, Start() phải:

  • Claim controller bằng OpenProtocol BY_DRIVER
  • Allocate private context và resource
  • Install protocol mới lên handle
  • Cleanup hoàn toàn nếu bất kỳ bước nào fail

Lỗi phổ biến nhất trong Start() không phải là “không làm đúng” mà là “làm đúng khi thành công nhưng bỏ qua cleanup khi fail” - để lại handle database ở trạng thái nửa vời.

Start() contract - thứ tự bắt buộc

Start() phải theo thứ tự:

1. OpenProtocol BY_DRIVER trên controller
   → Fail: return status phù hợp (EFI_UNSUPPORTED, EFI_ACCESS_DENIED, EFI_ALREADY_STARTED tùy tình huống) - chưa có gì để cleanup

2. Allocate private context
   → Fail: CloseProtocol bước 1, return EFI_OUT_OF_RESOURCES

3. Init hardware / đọc config
   → Fail: FreePool context + CloseProtocol bước 1, return lỗi

4. Install protocol mới lên handle
   → Fail: FreePool + CloseProtocol, return lỗi

5. Tạo child handle nếu là bus driver
   → Fail: UninstallProtocol + FreePool + CloseProtocol, return lỗi

Return EFI_SUCCESS - driver đang quản lý controller

Mỗi bước fail phải cleanup ngược lại tất cả bước đã thành công trước đó.

OpenProtocol BY_DRIVER - exclusive ownership

BY_DRIVER là tuyên bố “driver này đang quản lý controller này”:

EFI_STATUS Status;
MY_DRIVER_CONTEXT   *Context = NULL;
EFI_PCI_IO_PROTOCOL *PciIo   = NULL;

// Bước 1: Claim controller
Status = gBS->OpenProtocol(
    ControllerHandle,
    &gEfiPciIoProtocolGuid,
    (VOID **)&PciIo,
    This->DriverBindingHandle,
    ControllerHandle,
    EFI_OPEN_PROTOCOL_BY_DRIVER  // thể hiện driver đang quản lý protocol/controller này
                               // driver khác thường không thể mở cùng protocol bằng BY_DRIVER
                               // cho đến khi driver hiện tại CloseProtocol()
);
if (EFI_ERROR(Status)) {
    // Chưa open được → không có gì để cleanup
    return Status;
}

// Từ đây: PciIo hợp lệ, driver đang claim controller

Sau khi OpenProtocol BY_DRIVER thành công, driver khác thường không thể mở cùng protocol trên cùng controller bằng BY_DRIVER cho đến khi driver hiện tại CloseProtocol() - đây là cơ chế ownership của Driver Model.

Allocate và init - fail path phải clean

// Bước 2: Allocate private context
Status = gBS->AllocatePool(
    EfiBootServicesData,
    sizeof(MY_DRIVER_CONTEXT),
    (VOID **)&Context
);
if (EFI_ERROR(Status)) {
    goto CleanupPciIo;  // cleanup bước 1
}

Context->Signature = MY_DRIVER_SIGNATURE;
Context->PciIo     = PciIo;
Context->Handle    = ControllerHandle;

// Bước 3: Init hardware
Status = InitMyHardware(PciIo);
if (EFI_ERROR(Status)) {
    goto CleanupContext;  // cleanup bước 1 + 2
}

// Bước 4: Install protocol
Status = gBS->InstallProtocolInterface(
    &ControllerHandle,
    &gMyProtocolGuid,
    EFI_NATIVE_INTERFACE,
    &Context->MyProtocol
);
if (EFI_ERROR(Status)) {
    goto CleanupContext;
}

return EFI_SUCCESS;

// Cleanup labels - thứ tự ngược với bước thực hiện
CleanupContext:
    gBS->FreePool(Context);
    Context = NULL;

CleanupPciIo:
    gBS->CloseProtocol(
        ControllerHandle,
        &gEfiPciIoProtocolGuid,
        This->DriverBindingHandle,
        ControllerHandle
    );
    return Status;

Install protocol - lên handle nào

Start() có thể install protocol lên hai loại handle khác nhau tùy driver type:

Mục Giá trị Ghi chú
Install lên ControllerHandle Device driver pattern Driver cung cấp abstraction trực tiếp cho controller. Ví dụ: NIC driver install SimpleNetwork Protocol lên PCI handle.
Install lên child handle mới Bus driver pattern Driver tạo EFI_HANDLE mới cho mỗi child device. Child handle có Device Path riêng và protocol riêng.
Install cả hai Hybrid pattern Một số driver install management protocol lên ControllerHandle và install abstraction protocol lên child handle.

Tạo child handle - bus driver pattern

Bus driver phải tạo child handle cho từng device phía sau bus. RemainingDevicePath quyết định tạo tất cả hay chỉ tạo child cụ thể:

// RemainingDevicePath == NULL hoặc End node:
// → enumerate/connect toàn bộ child theo policy
// RemainingDevicePath có node cụ thể:
// → chỉ tạo child khớp node đó
// Node không hợp lệ với bus này:
// → return EFI_UNSUPPORTED hoặc lỗi phù hợp

Đây là cặp logic với RemainingDevicePath trong Supported() - cả hai hàm phải xử lý nhất quán.

// Tạo child handle cho bus driver
EFI_HANDLE ChildHandle = NULL;

// Bước 5a: Install Device Path lên child handle mới
// Tạo Device Path cho child dựa trên parent + bus-specific node
EFI_DEVICE_PATH_PROTOCOL *ChildDevicePath = BuildChildDevicePath(ParentDevicePath, BusAddress);
// Nếu ChildDevicePath được allocate riêng và install fail,
// phải FreePool(ChildDevicePath) theo ownership thực tế

Status = gBS->InstallProtocolInterface(
    &ChildHandle,          // NULL → firmware tạo handle mới
    &gEfiDevicePathProtocolGuid,
    EFI_NATIVE_INTERFACE,
    ChildDevicePath
);
if (EFI_ERROR(Status)) goto CleanupContext;

// Bước 5b: Install bus-specific protocol lên child handle
Status = gBS->InstallProtocolInterface(
    &ChildHandle,
    &gMyBusChildProtocolGuid,
    EFI_NATIVE_INTERFACE,
    &Context->ChildProtocol
);
if (EFI_ERROR(Status)) {
    // Phải uninstall Device Path vừa install
    gBS->UninstallProtocolInterface(ChildHandle, &gEfiDevicePathProtocolGuid, ChildDevicePath);
    goto CleanupContext;
}

// Bước 5c: Mở child protocol BY_CHILD_CONTROLLER trên parent
// Để parent biết child handle nào thuộc về nó
gBS->OpenProtocol(
    ControllerHandle,
    &gEfiPciIoProtocolGuid,
    (VOID **)&PciIo,
    This->DriverBindingHandle,             // AgentHandle: driver binding handle
    ChildHandle,                           // ControllerHandle: child controller handle
    EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER  // parent-child relationship
    // Stop() có thể dùng OpenProtocolInformation() để biết child nào đang mở parent protocol
);

BY_CHILD_CONTROLLER là cách firmware biết child handle nào thuộc controller nào - quan trọng khi Stop() cần destroy child.

Anti-pattern thực tế

Anti-pattern 1: Partial install rồi return lỗi không cleanup

// SAI - install protocol thành công nhưng sau đó fail và không uninstall
Status = gBS->InstallProtocolInterface(&Handle, &gMyGuid, ...);
// ... làm thêm gì đó ...
Status = SomeOtherInit();
if (EFI_ERROR(Status)) {
    return Status;  // LEAK - protocol vẫn còn install trên handle
}

Hậu quả: handle database có protocol “orphan” - không có driver quản lý, gây conflict khi driver khác muốn install cùng protocol.

Anti-pattern 2: Allocate context nhưng không lưu vào protocol

// Context được allocate nhưng không ai giữ reference
gBS->AllocatePool(..., &Context);
gBS->InstallProtocolInterface(&Handle, &gMyGuid, EFI_NATIVE_INTERFACE, &SomeStaticStruct);
// Context bị leak, không ai free được khi Stop() chạy
// Trong EDK II pattern chuẩn, protocol interface thường nằm trong private context,
// và dùng CR() macro với signature để recover context từ protocol pointer trong Stop()

Anti-pattern 3: Không lưu ControllerHandle trong context

// Start() thành công nhưng không lưu ControllerHandle
// Stop() sau đó không biết CloseProtocol trên handle nào
Context->PciIo = PciIo;
// Context->ControllerHandle = ControllerHandle;  ← bị quên

Anti-pattern 4: Install protocol lên NULL handle mà không lưu handle mới

EFI_HANDLE NewHandle = NULL;
gBS->InstallProtocolInterface(&NewHandle, &gMyGuid, ...);
// NewHandle bây giờ có giá trị - firmware đã tạo handle mới
// Nếu không lưu NewHandle, Stop() không thể UninstallProtocolInterface

Failure pattern - Start() fail nhưng không rõ lý do

OpenProtocol BY_DRIVER fail:

  • Supported() trả SUCCESS nhưng Start() fail ngay bước 1 - Supported() check không đủ chặt hoặc race condition với driver khác
  • EFI_ALREADY_STARTED: cùng driver đã open trên controller - Start() được gọi lần hai không cần thiết

InstallProtocolInterface fail:

  • GUID đã được install trên handle bởi driver khác
  • Handle không hợp lệ
  • Memory không đủ

Handle database bẩn sau Start() fail:

  • Dùng Shell> dh -v hoặc openinfo để xem open count và protocol list bất thường
  • Protocol còn trên handle nhưng không có driver quản lý → leak từ partial init

Trace khi Start() có vấn đề

# UEFI Shell: xem protocol đã install trên handle
Shell> dh -v <handle>
# Nếu thấy protocol orphan (không có driver claim) → partial init leak

# Xem open count và agent
Shell> openinfo <handle>
# Trên protocol mà driver phải quản lý: nếu không thấy agent mở BY_DRIVER
# sau Start() thành công → driver chưa claim đúng hoặc claim protocol khác

# Xem device tree - child handle có được tạo không
Shell> devtree

# EDK II trace
grep -r "CoreInstallProtocolInterface\|InstallProtocolInterface" \
    edk2/MdeModulePkg/Core/Dxe/ --include="*.c" | head -10
# CoreInstallProtocolInterface() xử lý install và notify các watcher

Checklist Start()

Câu hỏi tự kiểm tra

  1. Tại sao OpenProtocol BY_DRIVER trong Start() lại là “exclusive claim”?
  2. Nếu Start() fail ở bước 3 (init hardware) thì phải cleanup những gì?
  3. BY_CHILD_CONTROLLER khác BY_DRIVER thế nào - tại sao bus driver cần cả hai?
  4. Protocol “orphan” trên handle là gì và tại sao nguy hiểm?
  5. Tại sao goto cleanup là pattern phổ biến và được chấp nhận trong UEFI driver?

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.