// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023, Microsoft Corporation.
*
* Hypercall helper functions used by the mshv_root module.
*
* Authors: Microsoft Linux virtualization team
*/
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/export.h>
#include <asm/mshyperv.h>
#include "mshv_root.h"
/* Determined empirically */
#define HV_INIT_PARTITION_DEPOSIT_PAGES 208
#define HV_MAP_GPA_DEPOSIT_PAGES 256
#define HV_UMAP_GPA_PAGES 512
#define HV_PAGE_COUNT_2M_ALIGNED(pg_count) (!((pg_count) & (0x200 - 1)))
#define HV_WITHDRAW_BATCH_SIZE (HV_HYP_PAGE_SIZE / sizeof(u64))
#define HV_MAP_GPA_BATCH_SIZE \
((HV_HYP_PAGE_SIZE - sizeof(struct hv_input_map_gpa_pages)) \
/ sizeof(u64))
#define HV_GET_VP_STATE_BATCH_SIZE \
((HV_HYP_PAGE_SIZE - sizeof(struct hv_input_get_vp_state)) \
/ sizeof(u64))
#define HV_SET_VP_STATE_BATCH_SIZE \
((HV_HYP_PAGE_SIZE - sizeof(struct hv_input_set_vp_state)) \
/ sizeof(u64))
#define HV_GET_GPA_ACCESS_STATES_BATCH_SIZE \
((HV_HYP_PAGE_SIZE - sizeof(union hv_gpa_page_access_state)) \
/ sizeof(union hv_gpa_page_access_state))
#define HV_MODIFY_SPARSE_SPA_PAGE_HOST_ACCESS_MAX_PAGE_COUNT \
((HV_HYP_PAGE_SIZE - \
sizeof(struct hv_input_modify_sparse_spa_page_host_access)) / \
sizeof(u64))
int hv_call_withdraw_memory(u64 count, int node, u64 partition_id)
{
struct hv_input_withdraw_memory *input_page;
struct hv_output_withdraw_memory *output_page;
struct page *page;
u16 completed;
unsigned long remaining = count;
u64 status;
int i;
unsigned long flags;
page = alloc_page(GFP_KERNEL);
if (!page)
return -ENOMEM;
output_page = page_address(page);
while (remaining) {
local_irq_save(flags);
input_page = *this_cpu_ptr(hyperv_pcpu_input_arg);
memset(input_page, 0, sizeof(*input_page));
input_page->partition_id = partition_id;
status = hv_do_rep_hypercall(HVCALL_WITHDRAW_MEMORY,
min(remaining, HV_WITHDRAW_BATCH_SIZE),
0, input_page, output_page);
local_irq_restore(flags);
completed = hv_repcomp(status);
for (i = 0; i < completed; i++)
__free_page(pfn_to_page(output_page->gpa_page_list[i]));
if (!hv_result_success(status)) {
if (hv_result(status) == HV_STATUS_NO_RESOURCES)
status = HV_STATUS_SUCCESS;
break;
}
remaining -= completed;
}
free_page((unsigned long)output_page);
return hv_result_to_errno(status);
}
int hv_call_create_partition(u64 flags,
struct hv_partition_creation_properties creation_properties,
union hv_partition_isolation_properties isolation_properties,
u64 *partition_id)
{
struct hv_input_create_partition *input;
struct hv_output_create_partition *output;
u64 status;
int ret;
unsigned long irq_flags;
do {
local_irq_save(irq_flags);
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
output = *this_cpu_ptr(hyperv_pcpu_output_arg);
memset(input, 0, sizeof(*input));
input->flags = flags;
input->compatibility_version = HV_COMPATIBILITY_21_H2;
memcpy(&input->partition_creation_properties, &creation_properties,
sizeof(creation_properties));
memcpy(&input->isolation_properties, &isolation_properties,
sizeof(isolation_properties));
status = hv_do_hypercall(HVCALL_CREATE_PARTITION,
input, output);
if (hv_result(status) != HV_STATUS_INSUFFICIENT_MEMORY) {
if (hv_result_success(status))
*partition_id = output->partition_id;
local_irq_restore(irq_flags);
ret = hv_result_to_errno(status);
break;
}
local_irq_restore(irq_flags);
ret = hv_call_deposit_pages(NUMA_NO_NODE,
hv_current_partition_id, 1);
} while (!ret);
return ret;
}
int hv_call_initialize_partition(u64 partition_id)
{
struct hv_input_initialize_partition input;
u64 status;
int ret;
input.partition_id = partition_id;
ret = hv_call_deposit_pages(NUMA_NO_NODE, partition_id,
HV_INIT_PARTITION_DEPOSIT_PAGES);
if (ret)
return ret;
do {
status = hv_do_fast_hypercall8(HVCALL_INITIALIZE_PARTITION,
*(u64 *)&input);
if (hv_result(status) != HV_STATUS_INSUFFICIENT_MEMORY) {
ret = hv_result_to_errno(status);
break;
}
ret = hv_call_deposit_pages(NUMA_NO_NODE, partition_id, 1);
} while (!ret);
return ret;
}
int hv_call_finalize_partition(u64