Hello, I want to figure out how to save data to non-volatile memory in the UEFI such that data is still there after I reopen QEMU.
So I used to run my bootx64.efi by producing it and then just putting it to app/efi/boot/bootx64.efi and then I did
UEFI variables and volumes on secondary storage. do not put into UEFI variables everything, only something small, what is really needed to be there - because they are stored in a very scarce NOR flash devices, your users won't be happy if you thrash those. in most cases, its use is for your installation BootXXXX Load Option variable. for OVMF, that file OVMG_VARS.fd is a representation of such a flash device, you provide it for qemu on a per VM basis, just like Octoconrabas pointed. in other words, when starting your VM, provide its own copy of this file.
accessing volumes on secondary storages allows you to store there what you need. generally, it goes to ESP, but not necessarily, it could be any FAT volume, if you don't want to parse FS structures in your loader. then you just create a FAT volume and put your stuff there \efi\yourorgname\. btw, use this approach for storing your loader as well, and then start it from the shell or Boot Manager. or from a load option even - the latter is the most natural way for a "production ready" phase. don't use the "default" \efi\boot\bootx64.efi approach - it's meant as a last chance and is OPTIONAL for the firmware to check on persistent storage devices. it may clash with other things already sitting there with this name and it may just not be started.
for accessing FAT drives, you first open EFI_SIMPLE_FILE_SYSTEM_PROTOCOL on a handle, representing that device (in case of using the volume from where your loader got started, this handle is the DeviceHandle member of the EFI_LOADED_IMAGE_PROTOCOL instance of your loader, but also, you can enumerate all the devices, supporting SFSP). then you OpenVolume() on it and get an EFI_FILE_PROTOCOL instance of the root directory of that volume. then you can Open() any file or directory on that instance, getting an instance of EFI_FILE_PROTOCOL of that file or directory. then, finally, you can Read() or Write() that file. storing on the volume what you need.
for FS, other, than FAT family (except exFAT), you have to use BLOCK_IO_PROTOCOL and implement FS reading/writing yourself. as you guess, it's way better to use a FAT volume. but don't worry, you will still need to implement the aforementioned non-FAT volume reading (at least) for getting your kernel files. and if, like in Windows, there is journaling capabilities for the configuration database (registry) in your OS, you will need to implement it (for replaying unfinished transactions and making the registry clean). this implies being able to do write on a non-FAT volume, if your OS uses such. but for loader specific data, please, use \efi\yourorgname\ directory on a FAT volume. then at least at the beginning, firmware will save you from the burden of messing around a pretty hard work of getting the data from/to a non-FAT FS.
also, some firmwares materialize SFSP on a non-FAT volumes. like uboot on ext4 for example. UEFI allows that (but not how apple does it - ESP is always a FAT32 volume on a compliant system). it's a nice feature (if wouldn't have nasty bugs) but it's just a non-guaranteed addition.
In a short summary:
1) you use flash based storage only for your variables, and they are small. BootXXXX for your installation mostly. access is through FW services.
2) your loader specific data with the loader itself go into a FAT volume (most naturally ESP, but when developing, just put it into a normal FAT volume, would be easier), you put your data into \efi\yourorgname directory and access it, using an easy to use firmware services. you put here most your stuff. but again, let's not participate in bloating ESP to 1GB, ok?
3) your OS resides on your own badass FS and the firmware has no idea about it. you implement your own FS reading code in the loader, that does the job. using EFI_BLOCK_IO_PROTOCOL underneath for accessing the drive. you mostly need a readonly capabilities, but if your OS requires features, like logging for e.g. configuration database, you would need to implement write access to your FS as well.