| // SPDX-License-Identifier: GPL-2.0 |
| #include <linux/types.h> |
| #include <linux/vmalloc.h> |
| #include <linux/mm.h> |
| #include <linux/clockchips.h> |
| #include <linux/acpi.h> |
| #include <linux/hyperv.h> |
| #include <linux/slab.h> |
| #include <linux/cpuhotplug.h> |
| #include <linux/minmax.h> |
| #include <asm/hypervisor.h> |
| #include <asm/mshyperv.h> |
| #include <asm/apic.h> |
| |
| #include <asm/trace/hyperv.h> |
| |
| /* |
| * See struct hv_deposit_memory. The first u64 is partition ID, the rest |
| * are GPAs. |
| */ |
| #define HV_DEPOSIT_MAX (HV_HYP_PAGE_SIZE / sizeof(u64) - 1) |
| |
| /* Deposits exact number of pages. Must be called with interrupts enabled. */ |
| int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages) |
| { |
| struct page **pages, *page; |
| int *counts; |
| int num_allocations; |
| int i, j, page_count; |
| int order; |
| u64 status; |
| int ret; |
| u64 base_pfn; |
| struct hv_deposit_memory *input_page; |
| unsigned long flags; |
| |
| if (num_pages > HV_DEPOSIT_MAX) |
| return -E2BIG; |
| if (!num_pages) |
| return 0; |
| |
| /* One buffer for page pointers and counts */ |
| page = alloc_page(GFP_KERNEL); |
| if (!page) |
| return -ENOMEM; |
| pages = page_address(page); |
| |
| counts = kcalloc(HV_DEPOSIT_MAX, sizeof(int), GFP_KERNEL); |
| if (!counts) { |
| free_page((unsigned long)pages); |
| return -ENOMEM; |
| } |
| |
| /* Allocate all the pages before disabling interrupts */ |
| i = 0; |
| |
| while (num_pages) { |
| /* Find highest order we can actually allocate */ |
| order = 31 - __builtin_clz(num_pages); |
| |
| while (1) { |
| pages[i] = alloc_pages_node(node, GFP_KERNEL, order); |
| if (pages[i]) |
| break; |
| if (!order) { |
| ret = -ENOMEM; |
| num_allocations = i; |
| goto err_free_allocations; |
| } |
| --order; |
| } |
| |
| split_page(pages[i], order); |
| counts[i] = 1 << order; |
| num_pages -= counts[i]; |
| i++; |
| } |
| num_allocations = i; |
| |
| local_irq_save(flags); |
| |
| input_page = *this_cpu_ptr(hyperv_pcpu_input_arg); |
| |
| input_page->partition_id = partition_id; |
| |
| /* Populate gpa_page_list - these will fit on the input page */ |
| for (i = 0, page_count = 0; i < num_allocations; ++i) { |
| base_pfn = page_to_pfn(pages[i]); |
| for (j = 0; j < counts[i]; ++j, ++page_count) |
| input_page->gpa_page_list[page_count] = base_pfn + j; |
| } |
| status = hv_do_rep_hypercall(HVCALL_DEPOSIT_MEMORY, |
| page_count, 0, input_page, NULL); |
| local_irq_restore(flags); |
| if (!hv_result_success(status)) { |
| pr_err("Failed to deposit pages: %lld\n", status); |
| ret = hv_result(status); |
| goto err_free_allocations; |
| } |
| |
| ret = 0; |
| goto free_buf; |
| |
| err_free_allocations: |
| for (i = 0; i < num_allocations; ++i) { |
| base_pfn = page_to_pfn(pages[i]); |
| for (j = 0; j < counts[i]; ++j) |
| __free_page(pfn_to_page(base_pfn + j)); |
| } |
| |
| free_buf: |
| free_page((unsigned long)pages); |
| kfree(counts); |
| return ret; |
| } |
| |
| int hv_call_add_logical_proc(int node, u32 lp_index, u32 apic_id) |
| { |
| struct hv_add_logical_processor_in *input; |
| struct hv_add_logical_processor_out *output; |
| u64 status; |
| unsigned long flags; |
| int ret = HV_STATUS_SUCCESS; |
| int pxm = node_to_pxm(node); |
| |
| /* |
| * When adding a logical processor, the hypervisor may return |
| * HV_STATUS_INSUFFICIENT_MEMORY. When that happens, we deposit more |
| * pages and retry. |
| */ |
| do { |
| local_irq_save(flags); |
| |
| input = *this_cpu_ptr(hyperv_pcpu_input_arg); |
| /* We don't do anything with the output right now */ |
| output = *this_cpu_ptr(hyperv_pcpu_output_arg); |
| |
| input->lp_index = lp_index; |
| input->apic_id = apic_id; |
| input->flags = 0; |
| input->proximity_domain_info.domain_id = pxm; |
| input->proximity_domain_info.flags.reserved = 0; |
| input->proximity_domain_info.flags.proximity_info_valid = 1; |
| input->proximity_domain_info.flags.proximity_preferred = 1; |
| status = hv_do_hypercall(HVCALL_ADD_LOGICAL_PROCESSOR, |
| input, output); |
| local_irq_restore(flags); |
| |
| if (hv_result(status) != HV_STATUS_INSUFFICIENT_MEMORY) { |
| if (!hv_result_success(status)) { |
| pr_err("%s: cpu %u apic ID %u, %lld\n", __func__, |
| lp_index, apic_id, status); |
| ret = hv_result(status); |
| } |
| break; |
| } |
| ret = hv_call_deposit_pages(node, hv_current_partition_id, 1); |
| } while (!ret); |
| |
| return ret; |
| } |
| |
| int hv_call_create_vp(int node, u64 partition_id, u32 vp_index, u32 flags) |
| { |
| struct hv_create_vp *input; |
| u64 status; |
| unsigned long irq_flags; |
| int ret = HV_STATUS_SUCCESS; |
| int pxm = node_to_pxm(node); |
| |
| /* Root VPs don't seem to need pages deposited */ |
| if (partition_id != hv_current_partition_id) { |
| /* The value 90 is empirically determined. It may change. */ |
| ret = hv_call_deposit_pages(node, partition_id, 90); |
| if (ret) |
| return ret; |
| } |
| |
| do { |
| local_irq_save(irq_flags); |
| |
| input = *this_cpu_ptr(hyperv_pcpu_input_arg); |
| |
| input->partition_id = partition_id; |
| input->vp_index = vp_index; |
| input->flags = flags; |
| input->subnode_type = HvSubnodeAny; |
| if (node != NUMA_NO_NODE) { |
| input->proximity_domain_info.domain_id = pxm; |
| input->proximity_domain_info.flags.reserved = 0; |
| input->proximity_domain_info.flags.proximity_info_valid = 1; |
| input->proximity_domain_info.flags.proximity_preferred = 1; |
| } else { |
| input->proximity_domain_info.as_uint64 = 0; |
| } |
| status = hv_do_hypercall(HVCALL_CREATE_VP, input, NULL); |
| local_irq_restore(irq_flags); |
| |
| if (hv_result(status) != HV_STATUS_INSUFFICIENT_MEMORY) { |
| if (!hv_result_success(status)) { |
| pr_err("%s: vcpu %u, lp %u, %lld\n", __func__, |
| vp_index, flags, status); |
| ret = hv_result(status); |
| } |
| break; |
| } |
| ret = hv_call_deposit_pages(node, partition_id, 1); |
| |
| } while (!ret); |
| |
| return ret; |
| } |
| |