So maybe you can answer me this, how is memory management done in this environment? Are you free (heh) to do malloc and free or did you have to provide your own implementation? Is GRUB2 already in protected mode or there were some hacks needed on that area?
In the BIOS version, GRUB2 is entered in 16-bit real mode, and it transitions to 32-bit protected mode; it then has its own memory map. In the 32-bit and 64-bit EFI versions, the firmware transitions to 32-bit or 64-bit protected mode before calling GRUB2. (In all cases, there's no actual protection and the segment bases are all 0, though 64-bit mode does actually require a page table.)
However, GRUB2 normally only runs on the boot CPU ("BSP", bootstrap processor). We have to initialize the non-boot CPUs ("APs", application processors) ourselves. To do that, we allocate an aligned region of low memory, copy some bootstrap code and data into that memory, and send each processor an INIT (to reset them) and a startup inter-processor interrupt (SIPI) that forces them to jump to that address in low memory. We then transition them from 16-bit real mode to either 32-bit or 64-bit protected mode ourselves. (In the case of 64-bit protected mode, we set up paging by using the same page-table address that the firmware set up on the BSP, so that they all have the same memory map.) With that done, the AP can then run C code. Then we put the AP to sleep using mwait, waiting for work to do. When we want them to do something, we hand them a function pointer and a pointer-sized parameter.
We have a very tiny Python pci module, which contains a brute-force bus-walk to find devices with a specific class; we're using that to find PCI-attached USB host controllers so that we can force them to "hand off" to the OS, which then causes the BIOS to stop polling them, which often causes the BIOS to stop doing frequent SMIs. (That allows us to tell if a BIOS's out-of-spec use of long-running SMI handlers is caused by USB handling.)
Simple PCI enumeration, though, is effectively just "walk every possible device, read the first four bytes of configuration space from function 0, if they're not 0xffffffff then they're vendor and product IDs, and then read the same from functions 1-7".