DEV Community

MachineHunter
MachineHunter

Posted on

How to make custom UEFI Protocol

DXE Driver's main task is to create and install Protocols. In this post, I will explain how to create Protocol in EDK2 and how to use that protocol from other UEFI module.

UEFI Protocol Explanation

DXE Driver abstract the access to the device by creating Protocols. For example, you can use Tcg2Protocol to access TPM2.0 functionality rather than accessing it via things like MMIO or Port IO. Protocols are data structures containing function pointers and the actual function's contents are in the DXE Driver which installed that Protocol. The Protocol (data structure) itself is also in that DXE Driver (usually in .data section).
Image description
(Image from EDK2 UEFI Driver Writer's Guide 3.6.2)

Each Protocol has its own GUID and this is used for other UEFI modules to locate this Protocol. Several Protocols can be grouped and this group is identified by Handle. Therefore, you can use gBS->LocateHandle first to locate Handle and then locate the Protocol inside. However, it might be more simpler to use gBS->LocateProtocol to locate Protocol by GUID directly.

Protocols are stored into a single database because it has to be accessible via any UEFI modules. In order to store the protocol to this database, the DXE Driver which create the protocol has to use gBS->InstallMultipleProtocolInterfaces inside.

Implementation

MyDxe2.inf

Let's start with the inf file. The main point is to write gEfiDummyProtocol to the [Protocols]. We have to write the Protocols this DXE Driver produce and also consume.



[Defines]
INF_VERSION = 0x00010006
BASE_NAME = MyDxe2
FILE_GUID = 9d46dccd-ea11-4808-93e9-9b7021889b2d
MODULE_TYPE = DXE_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = MyDxeEntry

[Sources]
MyDxe2.h
MyDxe2.c

[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec

[LibraryClasses]
UefiDriverEntryPoint
UefiRuntimeServicesTableLib
UefiBootServicesTableLib
UefiLib
PrintLib

[Protocols]
gEfiDummyProtocolGuid ## PRODUCES

[Depex]
TRUE

Enter fullscreen mode Exit fullscreen mode




MyDxe2.h

This is the header file. This mainly defines types like EFI_DUMMY_PROTOCOL or DUMMY_FUNC1, so that you can use it in MyDxe2.c. Definition of gEfiDummyProtocolGuid will be defined in the separate place.



#include <Uefi.h>

include <Library/UefiLib.h>

include <Library/PrintLib.h>

include <Library/UefiDriverEntryPoint.h>

include <Library/UefiRuntimeServicesTableLib.h>

include <Library/UefiBootServicesTableLib.h>


typedef EFI_STATUS (EFIAPI DUMMY_FUNC1)();
typedef EFI_STATUS (EFIAPI DUMMY_FUNC2)();

// e9811f61-14bd-4637-8d17-aec540d8f508
#define EFI_DUMMY_PROTOCOL_GUID \
{ 0xe9811f61, 0x14bd, 0x4637, { 0x8d, 0x17, 0xae, 0xc5, 0x40, 0xd8, 0xf5, 0x08 } }

extern EFI_GUID gEfiDummyProtocolGuid;

typedef struct _EFI_DUMMY_PROTOCOL {
DUMMY_FUNC1 DummyFunc1;
DUMMY_FUNC2 DummyFunc2;
} EFI_DUMMY_PROTOCOL;

Enter fullscreen mode Exit fullscreen mode




MyDxe2.c

This is the DXE Driver's contents. mDummy is the actual data structure of this Protocol and this Protocol is installed by gBS->InstallMultipleProtocolInterfaces. The content of DummyFunc1 and DummyFunc2 can be any, but in this example, I'm storing the message "DummyFuncN called" in UEFI variable called MyDxeStatus, so that, you can check if the Protocols is really running correctly.



#include "MyDxe2.h"

UINT32 myvarSize = 30;
CHAR8 myvarValue[30] = {0};
CHAR16 myvarName[30] = L"MyDxeStatus";

// eefbd379-9f5c-4a92-a157-ae4079eb1448
EFI_GUID myvarGUID = { 0xeefbd379, 0x9f5c, 0x4a92, { 0xa1, 0x57, 0xae, 0x40, 0x79, 0xeb, 0x14, 0x48 }};

EFI_HANDLE mDummyHandle = NULL;

EFI_STATUS EFIAPI DummyFunc1() {
AsciiSPrint(myvarValue, 18, "DummyFunc1 called");
gRT->SetVariable(
myvarName,
&myvarGUID,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
myvarSize,
myvarValue);
return EFI_SUCCESS;
}

EFI_STATUS EFIAPI DummyFunc2() {
AsciiSPrint(myvarValue, 18, "DummyFunc2 called");
gRT->SetVariable(
myvarName,
&myvarGUID,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
myvarSize,
myvarValue);
return EFI_SUCCESS;
}

EFI_DUMMY_PROTOCOL mDummy = {
DummyFunc1,
DummyFunc2
};

EFI_STATUS EFIAPI MyDxeEntry(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) {
gBS->InstallMultipleProtocolInterfaces(&mDummyHandle, &gEfiDummyProtocolGuid, &mDummy, NULL);

<span class="n">EFI_TIME</span> <span class="n">time</span><span class="p">;</span>
<span class="n">gRT</span><span class="o">-&gt;</span><span class="n">GetTime</span><span class="p">(</span><span class="o">&amp;</span><span class="n">time</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">AsciiSPrint</span><span class="p">(</span><span class="n">myvarValue</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="s">"%2d/%2d %2d:%2d"</span><span class="p">,</span> <span class="n">time</span><span class="p">.</span><span class="n">Month</span><span class="p">,</span> <span class="n">time</span><span class="p">.</span><span class="n">Day</span><span class="p">,</span> <span class="n">time</span><span class="p">.</span><span class="n">Hour</span><span class="p">,</span> <span class="n">time</span><span class="p">.</span><span class="n">Minute</span><span class="p">);</span>

<span class="n">gRT</span><span class="o">-&gt;</span><span class="n">SetVariable</span><span class="p">(</span>
        <span class="n">myvarName</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">myvarGUID</span><span class="p">,</span>
        <span class="n">EFI_VARIABLE_NON_VOLATILE</span> <span class="o">|</span> <span class="n">EFI_VARIABLE_BOOTSERVICE_ACCESS</span> <span class="o">|</span> <span class="n">EFI_VARIABLE_RUNTIME_ACCESS</span><span class="p">,</span>
        <span class="n">myvarSize</span><span class="p">,</span>
        <span class="n">myvarValue</span><span class="p">);</span>

<span class="k">return</span> <span class="n">EFI_SUCCESS</span><span class="p">;</span>
Enter fullscreen mode Exit fullscreen mode

}

Enter fullscreen mode Exit fullscreen mode




MdeModulePkg.dec

We should define gEfiDummyProtocolGuid to MdeModulePkg.dec in order to use gEfiDummyProtocolGuid in the other UEFI module. Inside MdeModulePkg.dec, add the definition like below.



[Protocols]
...
## MyDxe/MyDxe2.h
gEfiDummyProtocolGuid = { 0xe9811f61, 0x14bd, 0x4637, { 0x8d, 0x17, 0xae, 0xc5, 0x40, 0xd8, 0xf5, 0x08 } }

Enter fullscreen mode Exit fullscreen mode




MdeModulePkg.dsc




[Components]
MdeModulePkg/MyDxe/MyDxe2.inf
...

Enter fullscreen mode Exit fullscreen mode




Build it

Make sure you have ACTIVE_PLATFORM = MdeModulePkg/MdeModulePkg.dsc in Conf/target.txt and let's build it. Then, construct FFS file like I explained in the previous post, add the FFS file to the BIOS image, and flash the SPI flash chip with that image.

Using DummyProtocol

Next, let's write a module that uses this Protocol. I will be writing a UEFI module that runs from a USB memory in BDS phase, so that I can print the output to the screen easily.
Create a folder named AppPkg/ under edk2/ and will be creating these files.



AppPkg/
├── AppPkg.dec
├── AppPkg.dsc
└── Applications/
└── UseDummyProtocol/
├── UseDummyProtocol.c
└── UseDummyProtocol.inf

Enter fullscreen mode Exit fullscreen mode




AppPkg.dec

FirstPkg can be any name.



[Defines]
DEC_SPECIFICATION = 0x00010005
PACKAGE_NAME = FirstPkg
PACKAGE_GUID = 75281f80-8657-4cf2-837f-04d73179c590
PACKAGE_VERSION = 0.1

Enter fullscreen mode Exit fullscreen mode




AppPkg.dsc




[Defines]
PLATFORM_NAME = FirstPkg
PLATFORM_GUID = 7b86cb02-9fbb-4f77-8d93-35c8c90f2488
PLATFORM_VERSION = 0.1
DSC_SPECIFICATION = 0x00010005
OUTPUT_DIRECTORY = Build/FirstPkg$(ARCH)
SUPPORTED_ARCHITECTURES = IA32|X64
BUILD_TARGETS = DEBUG|RELEASE|NOOPT
DEFINE DEBUG_ENABLE_OUTPUT = TRUE

[LibraryClasses]
# Entry point
UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
ShellCEntryLib|ShellPkg/Library/UefiShellCEntryLib/UefiShellCEntryLib.inf

# Common Libraries
BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
!if $(DEBUG_ENABLE_OUTPUT)
DebugLib|MdePkg/Library/UefiDebugLibConOut/UefiDebugLibConOut.inf
DebugPrintErrorLevelLib|MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.inf
!else ## DEBUG_ENABLE_OUTPUT
DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
!endif ## DEBUG_ENABLE_OUTPUT

UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
RegisterFilterLib|MdePkg/Library/RegisterFilterLibNull/RegisterFilterLibNull.inf

[Components]
AppPkg/Applications/UseDummyProtocol/UseDummyProtocol.inf

Enter fullscreen mode Exit fullscreen mode




UseDummyProtocol.inf




[Defines]
INF_VERSION = 0x00010006
BASE_NAME = UseDummyProtocol
FILE_GUID = 5765e8dd-c97a-4ce2-891e-9e24b94d654b
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 0.1
ENTRY_POINT = UefiMain

[Sources]
UseDummyProtocol.c

[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec

[LibraryClasses]
UefiLib
ShellCEntryLib
UefiBootServicesTableLib
UefiRuntimeServicesTableLib

[Protocols]
gEfiDummyProtocolGuid ## CONSUME

Enter fullscreen mode Exit fullscreen mode




UseDummyProtocol.c

This is checking UEFI variable MyDxeStatus by GetVariable before and after calling DummyFunc1 (the DummyProtocol's function). DummyFunc1 will change the value of MyDxeStatus to a DummyFunc1 called which was originally the current time set when this DXE Driver was executed.



#include <Uefi.h>

include <Library/UefiLib.h>

include <Library/UefiBootServicesTableLib.h>

include <Library/UefiRuntimeServicesTableLib.h>

include <MyDxe/MyDxe2.h>

include <Library/BaseMemoryLib.h>


EFI_STATUS EFIAPI UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE SystemTable) {
EFI_DUMMY_PROTOCOL DummyProtocol;
gBS->LocateProtocol(&gEfiDummyProtocolGuid, NULL, (VOID**)&DummyProtocol);

<span class="n">CHAR8</span> <span class="n">myvarValue</span><span class="p">[</span><span class="mi">30</span><span class="p">];</span>
<span class="n">UINT64</span> <span class="n">myvarSize</span> <span class="o">=</span> <span class="mi">30</span><span class="p">;</span>
<span class="n">UINT32</span> <span class="n">Attributes</span> <span class="o">=</span> <span class="n">EFI_VARIABLE_NON_VOLATILE</span> <span class="o">|</span> <span class="n">EFI_VARIABLE_BOOTSERVICE_ACCESS</span> <span class="o">|</span> <span class="n">EFI_VARIABLE_RUNTIME_ACCESS</span><span class="p">;</span>
<span class="n">EFI_GUID</span> <span class="n">myvarGUID</span> <span class="o">=</span> <span class="p">{</span> <span class="mh">0xeefbd379</span><span class="p">,</span> <span class="mh">0x9f5c</span><span class="p">,</span> <span class="mh">0x4a92</span><span class="p">,</span> <span class="p">{</span> <span class="mh">0xa1</span><span class="p">,</span> <span class="mh">0x57</span><span class="p">,</span> <span class="mh">0xae</span><span class="p">,</span> <span class="mh">0x40</span><span class="p">,</span> <span class="mh">0x79</span><span class="p">,</span> <span class="mh">0xeb</span><span class="p">,</span> <span class="mh">0x14</span><span class="p">,</span> <span class="mh">0x48</span> <span class="p">}};</span>

<span class="n">Print</span><span class="p">(</span><span class="s">L"[Before Calling Protocol] MyDxeStatus:</span><span class="se">\r\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">gRT</span><span class="o">-&gt;</span><span class="n">GetVariable</span><span class="p">(</span>
        <span class="s">L"MyDxeStatus"</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">myvarGUID</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">Attributes</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">myvarSize</span><span class="p">,</span>
        <span class="n">myvarValue</span><span class="p">);</span>

<span class="n">AsciiPrint</span><span class="p">(</span><span class="s">"%a</span><span class="se">\n\n</span><span class="s">"</span><span class="p">,</span> <span class="n">myvarValue</span><span class="p">);</span>

<span class="n">DummyProtocol</span><span class="o">-&gt;</span><span class="n">DummyFunc1</span><span class="p">();</span>

<span class="n">Print</span><span class="p">(</span><span class="s">L"[After Calling Protocol] MyDxeStatus:</span><span class="se">\r\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">gRT</span><span class="o">-&gt;</span><span class="n">GetVariable</span><span class="p">(</span>
        <span class="s">L"MyDxeStatus"</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">myvarGUID</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">Attributes</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">myvarSize</span><span class="p">,</span>
        <span class="n">myvarValue</span><span class="p">);</span>

<span class="n">AsciiPrint</span><span class="p">(</span><span class="s">"%a</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">myvarValue</span><span class="p">);</span>

<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
Enter fullscreen mode Exit fullscreen mode

}

Enter fullscreen mode Exit fullscreen mode




Build and Execute it

Before build, change to ACTIVE_PLATFORM = AppPkg/AppPkg.dsc in the Conf/target.txt. Then, build it.
This time, I will store the generated UseDummyProtocol.efi to the USB memory. In USB memory, make a directory EFI/BOOT/ and rename UseDummyProtocol.efi into bootx64.efi and put that into the folder.

Plug the USB into the PC you flashed the BIOS before, and boot that machine. If you see below output, then it's a success.
Image description
(I missed specifing the string length...)

Top comments (0)