Page MenuHomeFreeBSD

fix UEFI VM's bootup on Hyper-V (i.e. Hyper-V Generation-2 VM)
ClosedPublic

Authored by decui_microsoft.com on Feb 20 2017, 1:37 PM.

Details

Summary

This is to fix the below bug:

Bug 211746 - [Hyper-V] UEFI VM can't boot from the iso installation disk
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=211746
(please see the discussion in the bug for more details).

The loader assumes physical memory in the [2MB, 2MB + EFI_STAGING_SIZE)
is Conventional Memory while it may not, e.g. in the case of Hyper-V
Generation-2 VM running on Windows Server 2012 R2 host, there is a
BootServiceData memory block at the address 47.449MB and the memory
is not writable. Without the patch, the loader will crash in
efi_copy_finish().

Note: EFI_STAGING_SIZE is 48MB in 10.3 and 64MB in the HEAD.

The patch verifies the end addr of the staging area, and decreases
its size if necessary. This way, the loader will not try to write into
the BootServiceData memory.

The patch also allocates the staging area in the first 1GB memory.

Thank Marcel Moolenaar for helping me on this bug!

PS, actually, this patch is only a temporary workaround for UEFI VM on Hyper-V (currently FreeBSD 10.3, 11, and the HEAD can't boot as UEFI VM on Windows Server 2012 R2 host due to this bug).

To have a thorough fix, we would need to add the "booting from arbitrary address" capability to the kernel, which would require a lot of in-depth and long term work to modify the kernel and the loader.

Diff Detail

Repository
rS FreeBSD src repository
Lint
Automatic diff as part of commit; lint not applicable.
Unit
Automatic diff as part of commit; unit tests not applicable.

Event Timeline

decui_microsoft.com retitled this revision from to fix UEFI VM's bootup on Hyper-V (i.e. Hyper-V Generation-2 VM).
decui_microsoft.com updated this object.
decui_microsoft.com edited the test plan for this revision. (Show Details)
marcel edited edge metadata.Feb 20 2017, 4:47 PM

Going for the workaround I see! :-)

sys/boot/efi/loader/copy.c
56 ↗(On Diff #25414)

It's spelled "available" instead of "aviable" :-)

79–84 ↗(On Diff #25414)

Please also handle existing allocations. If those allocations represent free/usable memory after boot services has exited, then they are for all practical purposes "conventional memory".

Also: please make sure that KERNEL_PHYSICAL_BASE is covered by usable memory to begin with.

As such, we may have multiple memory descriptors that overlap with [2MB..2MB+nr_pages*PAGE_SIZE]. As long as they are free to use after boot services has exited, we're ok. We can only reduce the staging size if [2MB..2MB+delta] is usable when [2MB+delta..2MB+nr_pages*PAGE_SIZE] isn't.
We can reduce the staging area to delta/PAGE_SIZE. But if 2MB isn't usable, we cannot reduce (or put differently we have to reduce to 0).

Going for the workaround I see! :-)

Yes, it's a workaround. Let's try to unblock Hyper-V for the usual case first: currently FreeBSD 10.3, 11, and the HEAD can't boot as UEFI VM on Hyper-V due to this bug.

sys/boot/efi/loader/copy.c
56 ↗(On Diff #25414)

Thanks! Will fix it. Not sure how I made the typo. :-)

sys/boot/efi/loader/copy.c
79–84 ↗(On Diff #25414)

Do you mean the existing allocations of the type EfiLoaderData?
IMHO, after boot services has exited, we can only write the memory of EfiLoaderData or EfiConventionalMemory. It's unsafe to write to memory of the other mem types.

In case 2MB isn't usable, IMHO the OS can't boot, since it's designed in the lds script that the kernel must begins with 2MB.

The current patch has made sure KERNEL_PHYSICAL_BASE is covered by a EfiConventionalMemory mem block.

So, IMO the new algorithm can be:

for each mem block:

if mem is not EfiConventionalMemory && mem if not EfiloaderData

continue;

if mem doesn't contain 2MB

continue;

mem1 = mem;
mem2 = mem1++; //i.e. next mem block's descriptor
while (mem2 is EfiConventionalMemory || mem2 is EfiloaderData)

if mem1.end == mem2.start {

	    ++mem2;
	    ++mem1;
  	} else {
	    break;

}

break;

//now we know [mem, mem1] contains no hole and the range's type is EfiConventionalMemory, or EfiLoaderData.
figure out available_pages
//reduce nr_pages if nr_pages is > available_pages.

This seems too complex to me and according to the screenshot in the bug,
the amount of EfiLoaderData memory is actually small and it's not adjacent to the EfiConventionalMemory block that covers 2MB.

So, if my above understanding is correct, IMHO we'd better leave the current simple algorithm as-is?

kib added inline comments.Feb 21 2017, 5:34 AM
sys/boot/efi/loader/copy.c
79–84 ↗(On Diff #25414)

Note that at the loader/earliest kernel boot phase, we must consider two different types of addresses, physical and virtual. Kernel is linked at specific virtual address, and for the text segment of the kernel, it does not matter a bit which physical addresses back the virtual mappings, as far as the virtual mapping satisfies the expectation of linker. In principle, the physical pages do not need to be contiguous even.

The physical backing is, of course, important too, but it affects different things. In particular, vm_page_array[] pages must be correctly removed from the runs, for the physical addresses that are used to map kernel. Another place which should be adapted is the creation of the initial kernel page tables, in create_pagetables() pmap function. The function makes explicit assumption that the kernel is mapped by contig physical pages.

Another question is how the kernel comprehends the used physical pages to map it. Kernel could try to inspect the initial (loader) page table it is started with, but it needs some way to access memory by a physical addresses for that. Or loader might pass some additional table to inform kernel about initial mapping, to avoid the need to inspect the page table.

sys/boot/efi/loader/copy.c
79–84 ↗(On Diff #25414)

IIRC, the UEFI firmware enables protected mode with identity-mapped paging (i.e. VA==PA) , before the execution is transferred to FreeBSD's EFI loader. So here IMO I can treat VA==PA in the patch (it looks FreeBSD UEFI loader doesn't change the VA/PA mapping(?)).

I have to admit the fact that I don't know the loader very well and actually it's my first time to have the chance to read some of the code and try to make a patch for it. :-)

Please kindly elaborate a little more about the issues that you think exist in the patch and I am happy to dig more into them.

kib added inline comments.Feb 21 2017, 7:49 AM
sys/boot/efi/loader/copy.c
79–84 ↗(On Diff #25414)

UEFI firmware is guaranteed to be executed with VA==PA indeed, but loader changes the mapping after ExitBootServices(), making each 2G VA mapped by low 2G physical.

I did not wrote anything about issues in your patch, rather, I tried to enumerate problems that must be handled to allow boot from arbitrary UEFI memory map.

sys/boot/efi/loader/copy.c
79–84 ↗(On Diff #25414)

@kib
Got it. Thanks a lot for sharing the details about the background! Yeah, I've realized the complexity of booting from arbitrary address, which would require a lot of really in-depth and long term work. So I'd like to use this patch as a temporary workaround for UEFI VM on Hyper-V. :-)

decui_microsoft.com updated this object.
decui_microsoft.com edited edge metadata.

Fixed the typo: aviable -> available.

Cc'ed more people to have more input.

IMO it would be really great to have this patch, because currently UEFI FreeBSD VMs are completely blocked on Hyper-V.

sepherosa_gmail.com edited edge metadata.

We plan to commit this near the end of this week, if no objection comes. It detects possible errors, and obviously will not make the situation worse than before.

This revision is now accepted and ready to land.Feb 27 2017, 2:46 AM
This revision was automatically updated to reflect the committed changes.