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.
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
Publish PPI
PEIM producer đăng ký PPI với PEI Core. Sau đây các PEIM khác có thể locate được.
Tìm và dùng PPI
PEIM consumer tìm PPI theo GUID. Trả EFI_NOT_FOUND nếu producer chưa chạy.
Đă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
- UEFI PI Specification 1.9 - Volume 1: PEI Core Interface
- EDK II MdePkg - PeiServicesLib.h
- EDK II MdePkg - PiPei.h
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.
PEI là gì trong UEFI?
PEI là phase chuẩn bị nền tảng trước DXE trong UEFI, chịu trách nhiệm memory init, PPI và HOB để hệ thống có thể tiếp tục boot.
PEI Fail Checklist
Quicknote checklist debug lỗi trong PEI phase.
DEPEX là gì?
Quicknote giải thích Dependency Expression trong EDK II/UEFI.
Biến note thành bài viết hoàn chỉnh
Notes là nơi ghi nhanh khái niệm.