Examples opening and reading/writing a file with EDK2


alejandro.estay@...
 

Hi, I'm making a little UEFI app, just for check basic functionality of the firmware. inside this app I want to load, read and write a file, binary or text. However I can't find a "complete explanation" or examples about the use of the procedures (EFI_FILE_PROTOCOL.Open(), EFI_FILE_PROTOCOL.Read()) from the UEFI API (steps, what to check). The only thing I found was some little Uefi Shell apps doing this using the shell API. However I would like to do it using the "bare firmware" instead of loading the shell. For me, the most confusing part, is when the program has to check the handle database to find the particular handle of the file that is being opened. Also I have some doubts about how to check, without the shell, what volume or partition would have the exact file I'm looking for (i.e. what if 2 volumes have simmilar, or even identical root directories).

Thanks in advance


Laszlo Ersek
 

On 11/22/19 02:54, alejandro.estay@gmail.com wrote:
Hi, I'm making a little UEFI app, just for check basic functionality
of the firmware. inside this app I want to load, read and write a
file, binary or text. However I can't find a "complete explanation" or
examples about the use of the procedures (EFI_FILE_PROTOCOL.Open(),
EFI_FILE_PROTOCOL.Read()) from the UEFI API (steps, what to check).
The only thing I found was some little Uefi Shell apps doing this
using the shell API. However I would like to do it using the "bare
firmware" instead of loading the shell. For me, the most confusing
part, is when the program has to check the handle database to find
the particular handle of the file that is being opened. Also I have
some doubts about how to check, without the shell, what volume or
partition would have the exact file I'm looking for (i.e. what if 2
volumes have simmilar, or even identical root directories).
First, you need to find the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL instance in
the protocol database that is right for your purposes. You could locate
this protocol instance for example with the LocateDevicePath() boot
service. There could be other ways for you to locate the right handle,
and then open the Simple File System protocol interface on that handle.

This really depends on your use case. It's your application that has to
know on what device (such as, what PCI(e) controller, what SCSI disk,
what ATAPI disk, what partition, etc) to look for the interesting file.

For example, if you simply check every EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
in the protocol database, and among those, you cannot distinguish two
from each other (because both look suitable), then you'll have to
investigate the device path protocol installed on each handle. You might
be able to make a decision based on the structure / semantics of the
device paths themselves. Alternatively, you might have to traverse the
device paths node by node, and open further protocol interfaces on the
corresponding handles, to ultimately pick the right
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.


Once you have the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL interface open, you
need to call its OpenVolume() member function. See the UEFI spec for
details please. It will give you the EFI_FILE_PROTOCOL for the root
directory of that file system.

Once you got the EFI_FILE_PROTOCOL interface for the root directory, you
can call the Open() member function for opening files or directories
relative to the root directory. Either way, you'll get a new
EFI_FILE_PROTOCOL interface for the opened object (file or directory).
If you've opened a directory previously, then you can issue further
Open() calls for opening files or directories relative to *that*
(sub)directory.

In case you start with an EFI_DEVICE_PATH_PROTOCOL instance that
identifies a particular file in a particular filesystem, then the device
path protocol will contain *at least one* File Path Media Device Path
node. It is important that there may be more than one such device path
node, and the full pathname (within the filesystem), from the root
directory to the particular file, may be split over a number of device
path nodes.

For example, you could have just one File Path node containing
"\dir1\dir2\hello.txt". Or you could have three File Path nodes
containing "dir1", "dir2", "hello.txt", respectively. Or you could have
two File Path nodes containing "dir1\dir2\" and "hello.txt",
respectively.

In these cases, you'd need one, three, or two, EFI_FILE_PROTOCOL.Open()
calls, accordingly.

Alternatively, you'd need to concatenate the pathname fragments into a
whole pathname, making sure that there be precisely one backslash
separator between each pair of pathname components, and then issue a
single EFI_FILE_PROTOCOL.Open() call in the end.


You can find a helper function called EfiOpenFileByDevicePath() in
"MdePkg/Library/UefiLib/UefiLib.c".

A somewhat similar function is GetFileBufferByFilePath(), in
"MdePkg/Library/DxeServicesLib/DxeServicesLib.c".

Thanks,
Laszlo


Gao, Zhichao
 

Hi Alejadro,

I am trying to answer your questions. If anything missed, please feel free to let me know.

1. First I think CapsuleApp is a good example to consume the file protocol.
2. File handle like the standard C's file pointer, it has multi required protocol install on it, such as device path protocol, simple file protocol and so on.
We can do some operation on the file thru the file handle.
3. We can use DevicePathFromHandle to get the file's path from the file handle. And use ConvertDevicePathToText to convert the path to text mode to view.

All the above can be found in the CapsuleApp.

Thanks,
Zhichao

-----Original Message-----
From: discuss@edk2.groups.io [mailto:discuss@edk2.groups.io] On Behalf Of
alejandro.estay@gmail.com
Sent: Friday, November 22, 2019 9:54 AM
To: discuss@edk2.groups.io
Subject: [edk2-discuss] Examples opening and reading/writing a file with EDK2

Hi, I'm making a little UEFI app, just for check basic functionality of the firmware.
inside this app I want to load, read and write a file, binary or text. However I
can't find a "complete explanation" or examples about the use of the
procedures (EFI_FILE_PROTOCOL.Open(), EFI_FILE_PROTOCOL.Read()) from the
UEFI API (steps, what to check). The only thing I found was some little Uefi Shell
apps doing this using the shell API. However I would like to do it using the "bare
firmware" instead of loading the shell. For me, the most confusing part, is when
the program has to check the handle database to find the particular handle of
the file that is being opened. Also I have some doubts about how to check,
without the shell, what volume or partition would have the exact file I'm looking
for (i.e. what if 2 volumes have simmilar, or even identical root directories).

Thanks in advance


alejandro.estay@...
 

Hi I've wrote another little program trying to focus on this (it was added into MdeModulePkg)
------------------------------------------------------------------------------------------------------------------------------
#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Guid/FileInfo.h>
#include <Protocol/LoadedImage.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/IoLib.h>



// Piezo speaker related
#define EFI_SPEAKER_CONTROL_PORT 0x61
#define EFI_SPEAKER_OFF_MASK 0xFC
#define EFI_BEEP_ON_TIME_INTERVAL 0x50000
#define EFI_BEEP_OFF_TIME_INTERVAL 0x50000

#define EFI_TIMER_CONTROL_PORT 0x43
#define EFI_TIMER_CONTROL2_PORT 0x42

#define EFI_DEFAULT_BEEP_NUMBER 1
#define EFI_DEFAULT_BEEP_ON_TIME 0x50000
#define EFI_DEFAULT_BEEP_OFF_TIME 0x50000
#define EFI_DEFAULT_BEEP_FREQUENCY 0x500
#define EFI_DEFAULT_BEEP_ALTFREQUENCY 0x1500

#define I8254_CTRL_SEL_CTR(x) ((x) << 6)
#define I8254_CTRL_RW(x) (((x) & 0x3) << 4)
#define I8254_CTRL_LATCH I8254_CTRL_RW(0)
#define I8254_CTRL_REG 0x03
#define I8254_CTRL_LSB_MSB I8254_CTRL_RW(3)



/**
as the real entry point for the application.

@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.

@retval EFI_SUCCESS The entry point is executed successfully.
@retval other Some error occurs when executing this entry point.

**/
EFI_STATUS call_status;
//EFI_GUID gEfiSimpleFileSystemProtocolGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
EFI_GUID gEfiFileInfoGuid = EFI_FILE_INFO_ID;
EFI_GUID gEfiLoadedImageProtocolGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;

EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem;



EFI_FILE *Root;
EFI_FILE *text_file;
//CHAR16 *image_text={L"oe\r\n"};
CHAR16 image_text[21]={};
UINTN buffer_siz=21;

int index;




VOID turn_spkr_on( VOID )
{
UINT8 Data;

Data = IoRead8( EFI_SPEAKER_CONTROL_PORT );
Data |= 0x03;
IoWrite8( EFI_SPEAKER_CONTROL_PORT, Data) ;
}


VOID turn_spkr_off( VOID )
{
UINT8 Data;

Data = IoRead8( EFI_SPEAKER_CONTROL_PORT );
Data &= EFI_SPEAKER_OFF_MASK;
IoWrite8( EFI_SPEAKER_CONTROL_PORT, Data );
}


VOID GenerateBeep( VOID )
{

turn_spkr_on();
gBS->Stall( EFI_BEEP_ON_TIME_INTERVAL );
turn_spkr_off();
gBS->Stall( EFI_BEEP_OFF_TIME_INTERVAL );

}


EFI_STATUS ChangeFrequency( UINT16 Frequency )
{
UINT8 Data;

Data = 0xB6;
IoWrite8(EFI_TIMER_CONTROL_PORT, Data);

Data = (UINT8)(Frequency & 0x00FF);
IoWrite8( EFI_TIMER_CONTROL2_PORT, Data );
Data = (UINT8)((Frequency & 0xFF00) >> 8);
IoWrite8( EFI_TIMER_CONTROL2_PORT, Data );

return EFI_SUCCESS;
}


EFI_STATUS EFIAPI UefiMain (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
{
//DEBUG ((EFI_D_INFO, "My Entry point: 0x%08x\r\n", (CHAR16*)UefiMainMySampleApp ) );
Print(L"abriendo archivo...\n");
EFI_HANDLE_PROTOCOL HandleProtocol = SystemTable->BootServices->HandleProtocol;
call_status = HandleProtocol(ImageHandle, &gEfiLoadedImageProtocolGuid, (void**)&LoadedImage);
call_status = HandleProtocol(LoadedImage->DeviceHandle, &gEfiSimpleFileSystemProtocolGuid, (void**)&FileSystem);
FileSystem->OpenVolume(FileSystem, &Root);
call_status = Root->Open(Root, &text_file, L"text_img", EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY);
if (call_status != EFI_SUCCESS)
{
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"imagen no encontrada\r\n");
return call_status;
}
else
{
call_status=ChangeFrequency(EFI_DEFAULT_BEEP_ALTFREQUENCY);

GenerateBeep();

SystemTable->ConOut->OutputString(SystemTable->ConOut, L"leyendo\r\n");


call_status=Root->Read(text_file, &buffer_siz, &image_text);


SystemTable->ConOut->OutputString(SystemTable->ConOut, image_text);
}

return EFI_SUCCESS;
}
---------------------------------------
When I pass over read, GDB reports that image_text is 0. If I understood this, I believe that Read() must place the address of the first of the read elements from the file (20 elements). The file appears to be properly opened (i have the QEMU OVMF log also), but I'm failing reading it and the pointer variable is not being filled (or I'm confusing it).

I'm kinda rookie with C and pointer notation still confuses me sometimes.
Thanks for your answers

Here's the QEMU OVMF log tail
FSOpen: Open '\boot2.efi' Success
[Security] 3rd party image[0] can be loaded after EndOfDxe: PciRoot(0x0)/Pci(0x1,0x1)/Ata(Primary,Master,0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\boot2.efi.
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 6F328A8
Loading driver at 0x00006AF5000 EntryPoint=0x00006AF626A boot2.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 6F66E90
ProtectUefiImageCommon - 0x6F328A8
- 0x0000000006AF5000 - 0x0000000000002000
InstallProtocolInterface: 752F3136-4E16-4FDC-A22A-E5F46812F4CA 7EA9AA0
FSOpen: Open 'text_img' Success
FSOpen: Open '\' Success


Laszlo Ersek
 

On 12/31/19 21:38, alejandro.estay@gmail.com wrote:

EFI_STATUS EFIAPI UefiMain (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
{
//DEBUG ((EFI_D_INFO, "My Entry point: 0x%08x\r\n", (CHAR16*)UefiMainMySampleApp ) );
Print(L"abriendo archivo...\n");
EFI_HANDLE_PROTOCOL HandleProtocol = SystemTable->BootServices->HandleProtocol;
call_status = HandleProtocol(ImageHandle, &gEfiLoadedImageProtocolGuid, (void**)&LoadedImage);
call_status = HandleProtocol(LoadedImage->DeviceHandle, &gEfiSimpleFileSystemProtocolGuid, (void**)&FileSystem);
FileSystem->OpenVolume(FileSystem, &Root);
(Side comment: please always check the returns status.)

call_status = Root->Open(Root, &text_file, L"text_img", EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY);
if (call_status != EFI_SUCCESS)
{
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"imagen no encontrada\r\n");
return call_status;
}
else
{
call_status=ChangeFrequency(EFI_DEFAULT_BEEP_ALTFREQUENCY);

GenerateBeep();

SystemTable->ConOut->OutputString(SystemTable->ConOut, L"leyendo\r\n");


call_status=Root->Read(text_file, &buffer_siz, &image_text);
This call looks invalid (undefined behavior), likely due to a copy/paste
error. It should be:

text_file->Read (text_file, ...)
^^^^^^^^^ ^^^^^^^^^

and not

Root->Read (text_file, ...)
^^^^ ^^^^^^^^^

HTH,
Laszlo



SystemTable->ConOut->OutputString(SystemTable->ConOut, image_text);
}

return EFI_SUCCESS;
}


alejandro.estay@...
 

Thank you very much Laszlo, and Happy New Year.

There's still some problems. Apparently the optimizer is stripping out the fixed array that I'm using to save and print the text file. I know that this is not "best practice" (Untyped allocation is the standard here, I guess), however is working printing the file successfully using the NOOPT target, so I think that there's something in the DEBUG target that I don't know.
The suspicious behaviour occurs when I ask charptr variable after saving the fixed array address, it remains in 0

charptr=&buffer_char[0];

as initialized. What are the differences between DEBUG and NOOPT towards generated debug information?

Added some screenshots. Text output with debug case prints just spaces.