PPI là gì trong UEFI?

PPI là cơ chế interface giữa các PEIM trong PEI phase, thay thế cho Protocol khi DXE chưa sẵn sàng. Hiểu PPI giúp debug dispatch order và lỗi PEIM không chạy.

Cập nhật 5 phút đọc
BIOS Terms cover

PPI (PEIM-to-PEIM Interface) là cơ chế để các PEIM tìm nhau và gọi service của nhau trong PEI phase. Khi boot còn rất sớm, DXE chưa chạy, handle database chưa tồn tại - PPI là thứ thay thế Protocol trong môi trường đó.

PPI trong PEI giống vai trò của Protocol trong DXE, nhưng nhẹ hơn và phù hợp với môi trường sớm, hạn chế hơn.

Cấu trúc một PPI

Mỗi PPI gồm hai thứ: một GUID để định danh và một interface pointer trỏ đến struct chứa function pointers.

// Interface struct - PEIM định nghĩa tùy mục đích
typedef struct {
  EFI_STATUS (EFIAPI *ReadFlash)(
    IN  UINTN   Offset,
    IN  UINTN   Length,
    OUT VOID    *Buffer
    );
} MY_FLASH_READ_PPI;

// Descriptor - đăng ký PPI với PEI Core
EFI_PEI_PPI_DESCRIPTOR mMyPpiDescriptor = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gMyFlashReadPpiGuid,   // GUID định danh
  &mMyFlashReadPpi        // con trỏ đến interface
};

EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST là flag bắt buộc phải có ở descriptor cuối cùng trong array - PEI Core dùng flag này để biết khi nào dừng đọc.

Ba operations chính

01 InstallPpi

Publish PPI

PEIM producer đăng ký PPI với PEI Core. Sau đây các PEIM khác có thể locate được.

02 LocatePpi

Tìm và dùng PPI

PEIM consumer tìm PPI theo GUID. Trả EFI_NOT_FOUND nếu producer chưa chạy.

03 NotifyPpi

Đăng ký callback

PEIM đăng ký callback để tự động chạy khi một PPI được install - không cần polling.

InstallPpi - producer publish:

PeiServicesInstallPpi(&mMyPpiDescriptor);

LocatePpi - consumer tìm và dùng:

MY_FLASH_READ_PPI *FlashPpi;
Status = PeiServicesLocatePpi(
           &gMyFlashReadPpiGuid,
           0,      // instance 0 - lấy cái đầu tiên
           NULL,   // không cần descriptor output
           (VOID **)&FlashPpi
         );
if (EFI_ERROR(Status)) {
  // Producer chưa chạy, hoặc GUID sai - không nên tiếp tục
  return Status;
}

// Gọi function qua interface pointer
Status = FlashPpi->ReadFlash(Offset, Length, Buffer);

NotifyPpi - đăng ký callback thay vì locate thủ công:

// Callback sẽ tự chạy khi gMyMemoryDiscoveredPpiGuid được install
EFI_PEI_NOTIFY_DESCRIPTOR mNotifyDescriptor = {
  (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK |
   EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiPeiMemoryDiscoveredPpiGuid,
  MyMemoryDiscoveredCallback    // hàm callback
};

PeiServicesNotifyPpi(&mNotifyDescriptor);

Notify hữu ích khi PEIM không biết trước thứ tự dispatch - thay vì locate và fail, nó đăng ký callback và để PEI Core tự gọi đúng lúc.

EDK II có hai loại notify flag cần phân biệt:

  • EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK - callback chạy ngay khi PPI được install, trong cùng dispatch pass.
  • EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH - callback được lên lịch chạy sau dispatch pass hiện tại kết thúc.

Hầu hết trường hợp dùng NOTIFY_CALLBACK. Nếu callback cần chạy sau khi một loạt PEIM đã dispatch xong, dùng NOTIFY_DISPATCH.

PPI và [Depex] trong .inf

PPI còn đóng vai trò thứ hai: kiểm soát thứ tự dispatch của PEIM. Mỗi PEIM khai báo dependency trong section [Depex] của file .inf:

[Depex]
  gEfiPeiMemoryDiscoveredPpiGuid AND
  gEfiPeiMasterBootModePpiGuid

PEI Core chỉ dispatch PEIM khi tất cả PPI trong [Depex] đã được install. Đây là cơ chế đảm bảo thứ tự init đúng mà không cần hardcode - memory PEIM phải chạy trước, chipset PEIM phải chạy trước PEIM cần access hardware.

PPI khác Protocol ở điểm nào?

Mục Giá trị Ghi chú
Phase PPI: PEI - Protocol: DXE PPI không dùng được trong DXE và ngược lại.
Database PPI: PEI Core quản lý - Protocol: handle database PPI không gắn với handle. Locate theo GUID thuần túy.
Lookup PPI: LocatePpi() - Protocol: LocateProtocol() API khác nhau, tư duy tương tự.
Notify PPI: NotifyPpi() - Protocol: RegisterProtocolNotify() Cả hai đều có callback mechanism khi interface mới xuất hiện.
Dependency PPI: [Depex] trong .inf - Protocol: [Depex] trong .inf Cả hai dùng Depex, nhưng PEI Core và DXE Dispatcher evaluate riêng.
Reinstall PPI: ReInstallPpi() - Protocol: ReinstallProtocolInterface() Cập nhật interface đã publish. Dùng khi cần thay đổi implementation sau dispatch.

Khi nào không cần quan tâm đến PPI?

Nếu bạn chỉ viết DXE driver, consume Protocol bình thường và không đụng tới PEI code, bạn chưa cần hiểu sâu PPI. Nhưng khi debug PEIM không chạy, PEIM không tìm được service cần thiết, hoặc init order sai trong PEI - PPI và [Depex] là nơi đầu tiên cần nhìn vào.

Checklist khi debug PPI

Lỗi hiểu nhầm hay gặp

PPI không phải Protocol. Hai khái niệm này tương tự về tư duy nhưng thuộc hai phase khác nhau, hai API khác nhau và hai database khác nhau. Không thể dùng gBS->LocateProtocol() để tìm PPI.

LocatePpi() trả EFI_NOT_FOUND không phải lúc nào cũng là bug. Đôi khi producer chưa chạy vì Depex của nó chưa thỏa. Cần trace ngược lên để xem PPI chain: ai publish PPI mà producer đang chờ?

[Depex] và dispatch loop. PEI Core chạy dispatch loop nhiều vòng - PEIM bị skip vì Depex chưa thỏa có thể được dispatch ở vòng sau nếu PPI dependency xuất hiện trong vòng hiện tại. Nhưng nếu PPI đó không bao giờ được install, PEIM sẽ không bao giờ chạy - không có error log, chỉ đơn giản bị bỏ qua.

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.