| // SPDX-License-Identifier: GPL-2.0-only | |
| #include <linux/efi.h> | |
| #include <linux/memblock.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/crash_dump.h> | |
| #include <linux/nmi.h> | |
| #include <asm/unaccepted_memory.h> | |
| /* Protects unaccepted memory bitmap and accepting_list */ | |
| static DEFINE_SPINLOCK(unaccepted_memory_lock); | |
| struct accept_range { | |
| struct list_head list; | |
| unsigned long start; | |
| unsigned long end; | |
| }; | |
| static LIST_HEAD(accepting_list); | |
| /* | |
| * accept_memory() -- Consult bitmap and accept the memory if needed. | |
| * | |
| * Only memory that is explicitly marked as unaccepted in the bitmap requires | |
| * an action. All the remaining memory is implicitly accepted and doesn't need | |
| * acceptance. | |
| * | |
| * No need to accept: | |
| * - anything if the system has no unaccepted table; | |
| * - memory that is below phys_base; | |
| * - memory that is above the memory that addressable by the bitmap; | |
| */ | |
| void accept_memory(phys_addr_t start, unsigned long size) | |
| { | |
| struct efi_unaccepted_memory *unaccepted; | |
| unsigned long range_start, range_end; | |
| struct accept_range range, *entry; | |
| unsigned long flags; | |
| phys_addr_t end; | |
| u64 unit_size; | |
| unaccepted = efi_get_unaccepted_table(); | |
| if (!unaccepted) | |
| return; | |
| end = PAGE_ALIGN(start + size); | |
| start = PAGE_ALIGN_DOWN(start); | |
| unit_size = unaccepted->unit_size; | |
| /* | |
| * Only care for the part of the range that is represented | |
| * in the bitmap. | |
| */ | |
| if (start < unaccepted->phys_base) | |
| start = unaccepted->phys_base; | |
| if (end < unaccepted->phys_base) | |
| return; | |
| /* Translate to offsets from the beginning of the bitmap */ | |
| start -= unaccepted->phys_base; | |
| end -= unaccepted->phys_base; | |
| /* | |
| * load_unaligned_zeropad() can lead to unwanted loads across page | |
| * boundaries. The unwanted loads are typically harmless. But, they | |
| * might be made to totally unrelated or even unmapped memory. | |
| * load_unaligned_zeropad() relies on exception fixup (#PF, #GP and now | |
| * #VE) to recover from these unwanted loads. | |
| * | |
| * But, this approach does not work for unaccepted memory. For TDX, a | |
| * load from unaccepted memory will not lead to a recoverable exception | |
| * within the guest. The guest will exit to the VMM where the only | |
| * recourse is to terminate the guest. | |
| * | |
| * There are two parts to fix this issue and comprehensively avoid | |
| * access to unaccepted memory. Together these ensure that an extra | |
| * "guard" page is accepted in addition to the memory that needs to be | |
| * used: | |
| * | |
| * 1. Implicitly extend the range_contains_unaccepted_memory(start, size) | |
| * checks up to the next unit_size if 'start+size' is aligned on a | |
| * unit_size boundary. | |
| * | |
| * 2. Implicitly extend accept_memory(start, size) to the next unit_size | |
| * if 'size+end' is aligned on a unit_size boundary. (immediately | |
| * following this comment) | |
| */ | |
| if (!(end % unit_size)) | |
| end += unit_size; | |
| /* Make sure not to overrun the bitmap */ | |
| if (end > unaccepted->size * unit_size * BITS_PER_BYTE) | |
| end = unaccepted->size * unit_size * BITS_PER_BYTE; | |
| range.start = start / unit_size; | |
| range.end = DIV_ROUND_UP(end, unit_size); | |
| retry: | |
| spin_lock_irqsave(&unaccepted_memory_lock, flags); | |
| /* | |
| * Check if anybody works on accepting the same range of the memory. | |
| * | |
| * The check is done with unit_size granularity. It is crucial to catch | |
| * all accept requests to the same unit_size block, even if they don't | |
| * overlap on physical address level. | |
| */ | |
| list_for_each_entry(entry, &accepting_list, list) { | |
| if (entry->end <= range.start) | |
| continue; | |
| if (entry->start >= range.end) | |
| continue; | |
| /* | |
| * Somebody else accepting the range. Or at least part of it. | |
| * | |
| * Drop the lock and retry until it is complete. | |
| */ | |
| spin_unlock_irqrestore(&unaccepted_memory_lock, flags); | |
| goto retry; | |
| } | |
| /* | |
| * Register that the range is about to be accepted. | |
| * Make sure nobody else will accept it. | |
| */ | |
| list_add(&range.list, &accepting_list); | |
| range_start = range.start; | |
| for_each_set_bitrange_from(range_start, range_end, unaccepted->bitmap, | |
| range.end) { | |
| unsigned long phys_start, phys_end; | |
| unsigned long len = range_end - range_start; | |
| phys_start = range_start * unit_size + unaccepted->phys_base; | |
| phys_end = range_end * unit_size + unaccepted->phys_base; | |
| /* | |
| * Keep interrupts disabled until the accept operation is | |
| * complete in order to prevent deadlocks. | |
| * | |
| * Enabling interrupts before calling arch_accept_memory() | |
| * creates an opportunity for an interrupt handler to request | |
| * acceptance for the same memory. The handler will continuously | |
| * spin with interrupts disabled, preventing other task from | |
| * making progress with the acceptance process. | |
| */ | |
| spin_unlock(&unaccepted_memory_lock); | |
| arch_accept_memory(phys_start, phys_end); | |
| spin_lock(&unaccepted_memory_lock); | |
| bitmap_clear(unaccepted->bitmap, range_start, len); | |
| } | |
| list_del(&range.list); | |
| touch_softlockup_watchdog(); | |
| spin_unlock_irqrestore(&unaccepted_memory_lock, flags); | |
| } | |
| bool range_contains_unaccepted_memory(phys_addr_t start, unsigned long size) | |
| { | |
| struct efi_unaccepted_memory *unaccepted; | |
| unsigned long flags; | |
| bool ret = false; | |
| phys_addr_t end; | |
| u64 unit_size; | |
| unaccepted = efi_get_unaccepted_table(); | |
| if (!unaccepted) | |
| return false; | |
| end = PAGE_ALIGN(start + size); | |
| start = PAGE_ALIGN_DOWN(start); | |
| unit_size = unaccepted->unit_size; | |
| /* | |
| * Only care for the part of the range that is represented | |
| * in the bitmap. | |
| */ | |
| if (start < unaccepted->phys_base) | |
| start = unaccepted->phys_base; | |
| if (end < unaccepted->phys_base) | |
| return false; | |
| /* Translate to offsets from the beginning of the bitmap */ | |
| start -= unaccepted->phys_base; | |
| end -= unaccepted->phys_base; | |
| /* | |
| * Also consider the unaccepted state of the *next* page. See fix #1 in | |
| * the comment on load_unaligned_zeropad() in accept_memory(). | |
| */ | |
| if (!(end % unit_size)) | |
| end += unit_size; | |
| /* Make sure not to overrun the bitmap */ | |
| if (end > unaccepted->size * unit_size * BITS_PER_BYTE) | |
| end = unaccepted->size * unit_size * BITS_PER_BYTE; | |
| spin_lock_irqsave(&unaccepted_memory_lock, flags); | |
| while (start < end) { | |
| if (test_bit(start / unit_size, unaccepted->bitmap)) { | |
| ret = true; | |
| break; | |
| } | |
| start += unit_size; | |
| } | |
| spin_unlock_irqrestore(&unaccepted_memory_lock, flags); | |
| return ret; | |
| } | |
| #ifdef CONFIG_PROC_VMCORE | |
| static bool unaccepted_memory_vmcore_pfn_is_ram(struct vmcore_cb *cb, | |
| unsigned long pfn) | |
| { | |
| return !pfn_is_unaccepted_memory(pfn); | |
| } | |
| static struct vmcore_cb vmcore_cb = { | |
| .pfn_is_ram = unaccepted_memory_vmcore_pfn_is_ram, | |
| }; | |
| static int __init unaccepted_memory_init_kdump(void) | |
| { | |
| register_vmcore_cb(&vmcore_cb); | |
| return 0; | |
| } | |
| core_initcall(unaccepted_memory_init_kdump); | |
| #endif /* CONFIG_PROC_VMCORE */ |