895 lines
23 KiB
C
895 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "gunyah_vm_mgr: " fmt
|
|
|
|
#include <asm/gunyah.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/pagemap.h>
|
|
|
|
#include "rsc_mgr.h"
|
|
#include "vm_mgr.h"
|
|
|
|
#define WRITE_TAG (1 << 0)
|
|
#define SHARE_TAG (1 << 1)
|
|
|
|
static inline struct gunyah_resource *
|
|
__first_resource(struct gunyah_vm_resource_ticket *ticket)
|
|
{
|
|
return list_first_entry_or_null(&ticket->resources,
|
|
struct gunyah_resource, list);
|
|
}
|
|
|
|
/*
|
|
* Once the parcel is converted to paged, vm_mgr only tracks
|
|
* the pages. The parcel needs to be reclaimed by the caller.
|
|
*/
|
|
int gunyah_vm_parcel_to_paged(struct gunyah_vm *ghvm,
|
|
struct gunyah_rm_mem_parcel *parcel, u64 gfn,
|
|
u64 nr)
|
|
{
|
|
struct gunyah_vm_parcel *vm_parcel;
|
|
struct gunyah_rm_mem_entry *entry;
|
|
struct gunyah_vm_binding *b;
|
|
unsigned long i, tag = 0;
|
|
struct folio *folio;
|
|
pgoff_t off = 0;
|
|
u64 size;
|
|
int ret = 0;
|
|
|
|
down_write(&ghvm->bindings_lock);
|
|
b = mtree_load(&ghvm->bindings, gfn);
|
|
if (!b) {
|
|
WARN_ON("No backing binding for the parcel being paged");
|
|
ret = -ENOENT;
|
|
goto unlock;
|
|
}
|
|
|
|
vm_parcel = b->vm_parcel;
|
|
if (!vm_parcel) {
|
|
WARN_ON("No parcel found");
|
|
ret = -ENOENT;
|
|
goto unlock;
|
|
}
|
|
|
|
if (parcel->n_acl_entries > 1)
|
|
tag |= SHARE_TAG;
|
|
if (parcel->acl_entries[0].perms & GUNYAH_RM_ACL_W)
|
|
tag |= WRITE_TAG;
|
|
|
|
for (i = 0; i < parcel->n_mem_entries; i++) {
|
|
entry = &parcel->mem_entries[i];
|
|
size = entry->size;
|
|
folio = pfn_folio(PHYS_PFN(le64_to_cpu(entry->phys_addr)));
|
|
while (size > 0) {
|
|
ret = mtree_insert_range(&ghvm->mm, gfn + off,
|
|
gfn + off + folio_nr_pages(folio) - 1,
|
|
xa_tag_pointer(folio, tag),
|
|
GFP_KERNEL);
|
|
if (ret) {
|
|
WARN_ON(ret != -ENOMEM);
|
|
gunyah_vm_mm_erase_range(ghvm, gfn, off - 1);
|
|
goto unlock;
|
|
}
|
|
off += folio_nr_pages(folio);
|
|
size -= folio_size(folio);
|
|
folio = folio_next(folio);
|
|
}
|
|
}
|
|
BUG_ON(off != nr);
|
|
vm_parcel->start = 0;
|
|
b->vm_parcel = NULL;
|
|
|
|
unlock:
|
|
up_write(&ghvm->bindings_lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gunyah_vm_mm_erase_range() - Erases a range of folios from ghvm's mm
|
|
* @ghvm: gunyah vm
|
|
* @gfn: start guest frame number
|
|
* @nr: number of pages to erase
|
|
*
|
|
* Do not use this function unless rolling back gunyah_vm_parcel_to_paged.
|
|
*/
|
|
void gunyah_vm_mm_erase_range(struct gunyah_vm *ghvm, u64 gfn, u64 nr)
|
|
{
|
|
struct folio *folio;
|
|
u64 off = gfn;
|
|
void *entry;
|
|
|
|
while (off < gfn + nr) {
|
|
entry = mtree_erase(&ghvm->mm, off);
|
|
if (!entry)
|
|
return;
|
|
folio = xa_untag_pointer(entry);
|
|
if (!folio)
|
|
return;
|
|
off += folio_nr_pages(folio);
|
|
}
|
|
}
|
|
|
|
static inline u32 donate_flags(bool share)
|
|
{
|
|
if (share)
|
|
return FIELD_PREP_CONST(GUNYAH_MEMEXTENT_OPTION_TYPE_MASK,
|
|
GUNYAH_MEMEXTENT_DONATE_TO_SIBLING);
|
|
else
|
|
return FIELD_PREP_CONST(GUNYAH_MEMEXTENT_OPTION_TYPE_MASK,
|
|
GUNYAH_MEMEXTENT_DONATE_TO_PROTECTED);
|
|
}
|
|
|
|
static inline u32 reclaim_flags(bool share, bool sync)
|
|
{
|
|
u32 flags = 0;
|
|
|
|
if (share)
|
|
flags |= FIELD_PREP_CONST(GUNYAH_MEMEXTENT_OPTION_TYPE_MASK,
|
|
GUNYAH_MEMEXTENT_DONATE_TO_SIBLING);
|
|
else
|
|
flags |= FIELD_PREP_CONST(
|
|
GUNYAH_MEMEXTENT_OPTION_TYPE_MASK,
|
|
GUNYAH_MEMEXTENT_DONATE_FROM_PROTECTED);
|
|
|
|
if (!sync)
|
|
flags |= GUNYAH_MEMEXTENT_OPTION_NOSYNC;
|
|
|
|
return flags;
|
|
}
|
|
|
|
int gunyah_vm_provide_folio(struct gunyah_vm *ghvm, struct folio *folio,
|
|
u64 gfn, bool share, bool write)
|
|
{
|
|
struct gunyah_resource *guest_extent, *host_extent, *addrspace;
|
|
u32 map_flags = BIT(GUNYAH_ADDRSPACE_MAP_FLAG_PARTIAL);
|
|
u64 extent_attrs, gpa = gunyah_gfn_to_gpa(gfn);
|
|
phys_addr_t pa = PFN_PHYS(folio_pfn(folio));
|
|
enum gunyah_pagetable_access access;
|
|
size_t size = folio_size(folio);
|
|
enum gunyah_error gunyah_error;
|
|
unsigned long tag = 0;
|
|
int ret, tmp;
|
|
|
|
/* clang-format off */
|
|
if (share) {
|
|
guest_extent = __first_resource(&ghvm->guest_shared_extent_ticket);
|
|
host_extent = __first_resource(&ghvm->host_shared_extent_ticket);
|
|
} else {
|
|
guest_extent = __first_resource(&ghvm->guest_private_extent_ticket);
|
|
host_extent = __first_resource(&ghvm->host_private_extent_ticket);
|
|
}
|
|
/* clang-format on */
|
|
addrspace = __first_resource(&ghvm->addrspace_ticket);
|
|
|
|
if (!addrspace || !guest_extent || !host_extent)
|
|
return -ENODEV;
|
|
|
|
if (share) {
|
|
map_flags |= BIT(GUNYAH_ADDRSPACE_MAP_FLAG_VMMIO);
|
|
tag |= SHARE_TAG;
|
|
} else {
|
|
map_flags |= BIT(GUNYAH_ADDRSPACE_MAP_FLAG_PRIVATE);
|
|
}
|
|
|
|
if (write)
|
|
tag |= WRITE_TAG;
|
|
|
|
ret = mtree_insert_range(&ghvm->mm, gfn,
|
|
gfn + folio_nr_pages(folio) - 1,
|
|
xa_tag_pointer(folio, tag), GFP_KERNEL);
|
|
if (ret == -EEXIST)
|
|
ret = -EAGAIN;
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (share && write)
|
|
access = GUNYAH_PAGETABLE_ACCESS_RW;
|
|
else if (share && !write)
|
|
access = GUNYAH_PAGETABLE_ACCESS_R;
|
|
else if (!share && write)
|
|
access = GUNYAH_PAGETABLE_ACCESS_RWX;
|
|
else /* !share && !write */
|
|
access = GUNYAH_PAGETABLE_ACCESS_RX;
|
|
|
|
ret = gunyah_rm_platform_pre_demand_page(ghvm->rm, ghvm->vmid, access,
|
|
folio);
|
|
if (ret)
|
|
goto reclaim_host;
|
|
|
|
gunyah_error = gunyah_hypercall_memextent_donate(donate_flags(share),
|
|
host_extent->capid,
|
|
guest_extent->capid,
|
|
pa, size);
|
|
if (gunyah_error != GUNYAH_ERROR_OK) {
|
|
pr_err("Failed to donate memory for guest address 0x%016llx: %d\n",
|
|
gpa, gunyah_error);
|
|
ret = gunyah_error_remap(gunyah_error);
|
|
goto platform_release;
|
|
}
|
|
|
|
extent_attrs =
|
|
FIELD_PREP_CONST(GUNYAH_MEMEXTENT_MAPPING_TYPE,
|
|
ARCH_GUNYAH_DEFAULT_MEMTYPE) |
|
|
FIELD_PREP(GUNYAH_MEMEXTENT_MAPPING_USER_ACCESS, access) |
|
|
FIELD_PREP(GUNYAH_MEMEXTENT_MAPPING_KERNEL_ACCESS, access);
|
|
gunyah_error = gunyah_hypercall_addrspace_map(addrspace->capid,
|
|
guest_extent->capid, gpa,
|
|
extent_attrs, map_flags,
|
|
pa, size);
|
|
if (gunyah_error != GUNYAH_ERROR_OK) {
|
|
pr_err("Failed to map guest address 0x%016llx: %d\n", gpa,
|
|
gunyah_error);
|
|
ret = gunyah_error_remap(gunyah_error);
|
|
goto memextent_reclaim;
|
|
}
|
|
|
|
return 0;
|
|
memextent_reclaim:
|
|
gunyah_error = gunyah_hypercall_memextent_donate(
|
|
reclaim_flags(share, true), guest_extent->capid,
|
|
host_extent->capid, pa, size);
|
|
if (gunyah_error != GUNYAH_ERROR_OK)
|
|
pr_err("Failed to reclaim memory donation for guest address 0x%016llx: %d\n",
|
|
gpa, gunyah_error);
|
|
platform_release:
|
|
tmp = gunyah_rm_platform_reclaim_demand_page(ghvm->rm, ghvm->vmid,
|
|
access, folio);
|
|
if (tmp) {
|
|
pr_err("Platform failed to reclaim memory for guest address 0x%016llx: %d",
|
|
gpa, tmp);
|
|
return ret;
|
|
}
|
|
reclaim_host:
|
|
gunyah_folio_host_reclaim(folio);
|
|
mtree_erase(&ghvm->mm, gfn);
|
|
return ret;
|
|
}
|
|
|
|
static int __gunyah_vm_reclaim_folio_locked(struct gunyah_vm *ghvm, void *entry,
|
|
u64 gfn, const bool sync)
|
|
{
|
|
u32 map_flags = BIT(GUNYAH_ADDRSPACE_MAP_FLAG_PARTIAL);
|
|
struct gunyah_resource *guest_extent, *host_extent, *addrspace;
|
|
enum gunyah_pagetable_access access;
|
|
enum gunyah_error gunyah_error;
|
|
struct folio *folio;
|
|
bool write, share;
|
|
phys_addr_t pa;
|
|
size_t size;
|
|
int ret;
|
|
|
|
addrspace = __first_resource(&ghvm->addrspace_ticket);
|
|
if (!addrspace)
|
|
return -ENODEV;
|
|
|
|
share = !!(xa_pointer_tag(entry) & SHARE_TAG);
|
|
write = !!(xa_pointer_tag(entry) & WRITE_TAG);
|
|
folio = xa_untag_pointer(entry);
|
|
|
|
if (!sync)
|
|
map_flags |= BIT(GUNYAH_ADDRSPACE_MAP_FLAG_NOSYNC);
|
|
|
|
/* clang-format off */
|
|
if (share) {
|
|
guest_extent = __first_resource(&ghvm->guest_shared_extent_ticket);
|
|
host_extent = __first_resource(&ghvm->host_shared_extent_ticket);
|
|
map_flags |= BIT(GUNYAH_ADDRSPACE_MAP_FLAG_VMMIO);
|
|
} else {
|
|
guest_extent = __first_resource(&ghvm->guest_private_extent_ticket);
|
|
host_extent = __first_resource(&ghvm->host_private_extent_ticket);
|
|
map_flags |= BIT(GUNYAH_ADDRSPACE_MAP_FLAG_PRIVATE);
|
|
}
|
|
/* clang-format on */
|
|
|
|
pa = PFN_PHYS(folio_pfn(folio));
|
|
size = folio_size(folio);
|
|
|
|
gunyah_error = gunyah_hypercall_addrspace_unmap(addrspace->capid,
|
|
guest_extent->capid,
|
|
gunyah_gfn_to_gpa(gfn),
|
|
map_flags, pa, size);
|
|
if (gunyah_error != GUNYAH_ERROR_OK) {
|
|
pr_err_ratelimited(
|
|
"Failed to unmap guest address 0x%016llx: %d\n",
|
|
gunyah_gfn_to_gpa(gfn), gunyah_error);
|
|
ret = gunyah_error_remap(gunyah_error);
|
|
goto err;
|
|
}
|
|
|
|
gunyah_error = gunyah_hypercall_memextent_donate(
|
|
reclaim_flags(share, sync), guest_extent->capid,
|
|
host_extent->capid, pa, size);
|
|
if (gunyah_error != GUNYAH_ERROR_OK) {
|
|
pr_err_ratelimited(
|
|
"Failed to reclaim memory donation for guest address 0x%016llx: %d\n",
|
|
gunyah_gfn_to_gpa(gfn), gunyah_error);
|
|
ret = gunyah_error_remap(gunyah_error);
|
|
goto err;
|
|
}
|
|
|
|
if (share && write)
|
|
access = GUNYAH_PAGETABLE_ACCESS_RW;
|
|
else if (share && !write)
|
|
access = GUNYAH_PAGETABLE_ACCESS_R;
|
|
else if (!share && write)
|
|
access = GUNYAH_PAGETABLE_ACCESS_RWX;
|
|
else /* !share && !write */
|
|
access = GUNYAH_PAGETABLE_ACCESS_RX;
|
|
|
|
ret = gunyah_rm_platform_reclaim_demand_page(ghvm->rm, ghvm->vmid,
|
|
access, folio);
|
|
if (ret) {
|
|
pr_err_ratelimited(
|
|
"Platform failed to reclaim memory for guest address 0x%016llx: %d",
|
|
gunyah_gfn_to_gpa(gfn), ret);
|
|
goto err;
|
|
}
|
|
|
|
BUG_ON(mtree_erase(&ghvm->mm, gfn) != entry);
|
|
|
|
unpin_user_page(folio_page(folio, 0));
|
|
account_locked_vm(current->mm, 1, false);
|
|
return 0;
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
int gunyah_vm_reclaim_folio(struct gunyah_vm *ghvm, u64 gfn, struct folio *folio)
|
|
{
|
|
void *entry;
|
|
|
|
entry = mtree_load(&ghvm->mm, gfn);
|
|
if (!entry)
|
|
return 0;
|
|
|
|
if (folio != xa_untag_pointer(entry))
|
|
return -EAGAIN;
|
|
|
|
return __gunyah_vm_reclaim_folio_locked(ghvm, entry, gfn, true);
|
|
}
|
|
|
|
int gunyah_vm_reclaim_range(struct gunyah_vm *ghvm, u64 gfn, u64 nr)
|
|
{
|
|
unsigned long next = gfn, g;
|
|
struct folio *folio;
|
|
int ret, ret2 = 0;
|
|
void *entry;
|
|
bool sync;
|
|
|
|
mt_for_each(&ghvm->mm, entry, next, gfn + nr) {
|
|
folio = xa_untag_pointer(entry);
|
|
g = next;
|
|
sync = !mt_find_after(&ghvm->mm, &g, gfn + nr);
|
|
|
|
g = next - folio_nr_pages(folio);
|
|
folio_get(folio);
|
|
folio_lock(folio);
|
|
if (mtree_load(&ghvm->mm, g) == entry)
|
|
ret = __gunyah_vm_reclaim_folio_locked(ghvm, entry, g, sync);
|
|
else
|
|
ret = -EAGAIN;
|
|
folio_unlock(folio);
|
|
folio_put(folio);
|
|
if (ret && ret2 != -EAGAIN)
|
|
ret2 = ret;
|
|
}
|
|
|
|
return ret2;
|
|
}
|
|
|
|
int gunyah_vm_binding_alloc(struct gunyah_vm *ghvm,
|
|
struct gunyah_userspace_memory_region *region,
|
|
bool lend)
|
|
{
|
|
struct gunyah_vm_binding *binding;
|
|
int ret = 0;
|
|
|
|
if (!region->memory_size || !PAGE_ALIGNED(region->memory_size) ||
|
|
!PAGE_ALIGNED(region->userspace_addr) ||
|
|
!PAGE_ALIGNED(region->guest_phys_addr))
|
|
return -EINVAL;
|
|
|
|
if (overflows_type(region->guest_phys_addr + region->memory_size, u64))
|
|
return -EOVERFLOW;
|
|
|
|
binding = kzalloc(sizeof(*binding), GFP_KERNEL_ACCOUNT);
|
|
if (!binding) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
binding->mem_type = VM_MEM_USER;
|
|
binding->userspace_addr = region->userspace_addr;
|
|
binding->vm_parcel = NULL;
|
|
binding->guest_phys_addr = region->guest_phys_addr;
|
|
binding->size = region->memory_size;
|
|
binding->flags = region->flags;
|
|
binding->label = region->label;
|
|
|
|
if (lend) {
|
|
binding->share_type = VM_MEM_LEND;
|
|
} else {
|
|
binding->share_type = VM_MEM_SHARE;
|
|
}
|
|
down_write(&ghvm->bindings_lock);
|
|
ret = mtree_insert_range(&ghvm->bindings,
|
|
gunyah_gpa_to_gfn(binding->guest_phys_addr),
|
|
gunyah_gpa_to_gfn(binding->guest_phys_addr + region->memory_size - 1),
|
|
binding, GFP_KERNEL);
|
|
|
|
if(ret != 0)
|
|
kfree(binding);
|
|
up_write(&ghvm->bindings_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gunyah_gup_demand_page(struct gunyah_vm *ghvm, struct gunyah_vm_binding *b,
|
|
u64 gpa, bool write)
|
|
{
|
|
unsigned long gfn = gunyah_gpa_to_gfn(gpa);
|
|
unsigned int gup_flags;
|
|
u64 offset;
|
|
int pinned, ret;
|
|
struct page *page;
|
|
struct folio *folio;
|
|
|
|
if (write && !(b->flags & GUNYAH_MEM_ALLOW_WRITE))
|
|
return -EPERM;
|
|
gup_flags = FOLL_LONGTERM;
|
|
if (b->flags & GUNYAH_MEM_ALLOW_WRITE)
|
|
gup_flags |= FOLL_WRITE;
|
|
|
|
offset = (gunyah_gfn_to_gpa(gfn) - b->guest_phys_addr);
|
|
|
|
ret = account_locked_vm(current->mm, 1, true);
|
|
if (ret)
|
|
return ret;
|
|
|
|
pinned = pin_user_pages_fast(b->userspace_addr + offset, 1,
|
|
gup_flags, &page);
|
|
|
|
if (pinned != 1) {
|
|
ret = pinned;
|
|
goto unlock_page;
|
|
}
|
|
|
|
folio = page_folio(page);
|
|
|
|
if (!folio_test_swapbacked(folio)) {
|
|
ret = -EIO;
|
|
goto unpin_page;
|
|
}
|
|
|
|
folio_lock(folio);
|
|
ret = gunyah_vm_provide_folio(ghvm, folio, gfn - folio_page_idx(folio, page),
|
|
!(b->share_type == VM_MEM_LEND),
|
|
!!(b->flags & GUNYAH_MEM_ALLOW_WRITE));
|
|
folio_unlock(folio);
|
|
if (ret) {
|
|
if (ret != -EAGAIN)
|
|
pr_err_ratelimited(
|
|
"Failed to provide folio for guest addr: %016llx: %d\n",
|
|
gpa, ret);
|
|
goto unpin_page;
|
|
}
|
|
return ret;
|
|
|
|
unpin_page:
|
|
unpin_user_page(page);
|
|
unlock_page:
|
|
account_locked_vm(current->mm, 1, false);
|
|
return ret;
|
|
}
|
|
|
|
int gunyah_demand_page(struct gunyah_vm *ghvm, u64 gpa, bool write)
|
|
{
|
|
unsigned long gfn = gunyah_gpa_to_gfn(gpa);
|
|
struct gunyah_vm_binding *b;
|
|
int ret;
|
|
|
|
down_read(&ghvm->bindings_lock);
|
|
b = mtree_load(&ghvm->bindings, gfn);
|
|
if (!b) {
|
|
ret = -ENOENT;
|
|
goto unlock;
|
|
}
|
|
|
|
if (b->mem_type == VM_MEM_CMA) {
|
|
dev_warn(ghvm->parent, "Demand paging of CMA mem not supported\n");
|
|
ret = -EOPNOTSUPP;
|
|
} else {
|
|
ret = gunyah_gup_demand_page(ghvm, b, gpa, write);
|
|
}
|
|
|
|
unlock:
|
|
up_read(&ghvm->bindings_lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(gunyah_demand_page);
|
|
|
|
static int gunyah_gup_share_parcel(struct gunyah_vm *ghvm,
|
|
struct gunyah_vm_parcel *vm_parcel,
|
|
struct gunyah_vm_binding *b, u64 *gfn, u64 *nr)
|
|
{
|
|
struct gunyah_rm_mem_parcel *parcel = &vm_parcel->parcel;
|
|
struct page **pages;
|
|
int pinned, ret;
|
|
struct folio *folio;
|
|
unsigned int gup_flags;
|
|
unsigned long i, offset, entries, entry_size;
|
|
|
|
offset = gunyah_gfn_to_gpa(*gfn) - b->guest_phys_addr;
|
|
pages = kcalloc(*nr, sizeof(*pages), GFP_KERNEL_ACCOUNT);
|
|
if (!pages)
|
|
return -ENOMEM;
|
|
|
|
gup_flags = FOLL_LONGTERM;
|
|
if (b->flags & GUNYAH_MEM_ALLOW_WRITE)
|
|
gup_flags |= FOLL_WRITE;
|
|
|
|
pinned = pin_user_pages_fast(b->userspace_addr + offset, *nr,
|
|
gup_flags, pages);
|
|
if (pinned < 0) {
|
|
ret = pinned;
|
|
goto free_pages;
|
|
} else if (pinned != *nr) {
|
|
ret = -EFAULT;
|
|
goto unpin_pages;
|
|
}
|
|
|
|
ret = account_locked_vm(current->mm, pinned, true);
|
|
if (ret)
|
|
goto unpin_pages;
|
|
|
|
/* overallocate & assume no large folios */
|
|
parcel->mem_entries = kcalloc(pinned, sizeof(parcel->mem_entries[0]),
|
|
GFP_KERNEL_ACCOUNT);
|
|
if (!parcel->mem_entries) {
|
|
ret = -ENOMEM;
|
|
goto unaccount_pages;
|
|
}
|
|
folio = page_folio(pages[0]);
|
|
*gfn -= folio_page_idx(folio, pages[0]);
|
|
*nr = folio_nr_pages(folio);
|
|
parcel->mem_entries[0].phys_addr = cpu_to_le64(PFN_PHYS(folio_pfn(folio)));
|
|
entry_size = cpu_to_le64(folio_size(folio));
|
|
|
|
for (i = 1, entries = 0; i < pinned; i++) {
|
|
folio = page_folio(pages[i]);
|
|
if (pages[i] == folio_page(folio, 0)) {
|
|
if (page_to_pfn(pages[i - 1]) + 1 == page_to_pfn(pages[i])) {
|
|
entry_size += cpu_to_le64(folio_size(folio));
|
|
*nr += folio_nr_pages(folio);
|
|
} else {
|
|
parcel->mem_entries[entries].size = entry_size;
|
|
entries++;
|
|
parcel->mem_entries[entries].phys_addr = cpu_to_le64(PFN_PHYS(folio_pfn(folio)));
|
|
entry_size = cpu_to_le64(folio_size(folio));
|
|
*nr += folio_nr_pages(folio);
|
|
}
|
|
} else {
|
|
unpin_user_page(pages[i]);
|
|
account_locked_vm(current->mm, 1, false);
|
|
}
|
|
}
|
|
parcel->mem_entries[entries].size = entry_size;
|
|
parcel->n_mem_entries = entries + 1;
|
|
ret = gunyah_rm_mem_share(ghvm->rm, parcel);
|
|
if (ret)
|
|
goto free_mem_entries;
|
|
|
|
vm_parcel->start = *gfn;
|
|
vm_parcel->pages = *nr;
|
|
b->vm_parcel = vm_parcel;
|
|
goto free_pages;
|
|
|
|
free_mem_entries:
|
|
kfree(parcel->mem_entries);
|
|
parcel->mem_entries = NULL;
|
|
parcel->n_mem_entries = 0;
|
|
unaccount_pages:
|
|
account_locked_vm(current->mm, pinned, false);
|
|
unpin_pages:
|
|
unpin_user_pages(pages, pinned);
|
|
free_pages:
|
|
kfree(pages);
|
|
return ret;
|
|
}
|
|
|
|
int gunyah_share_parcel(struct gunyah_vm *ghvm, struct gunyah_vm_parcel *vm_parcel,
|
|
u64 *gfn, u64 *nr)
|
|
{
|
|
struct gunyah_rm_mem_parcel *parcel = &vm_parcel->parcel;
|
|
struct gunyah_vm_binding *b;
|
|
bool lend;
|
|
u16 vmid;
|
|
int ret;
|
|
|
|
if (!*nr)
|
|
return -EINVAL;
|
|
|
|
down_write(&ghvm->bindings_lock);
|
|
b = mtree_load(&ghvm->bindings, *gfn);
|
|
if (!b) {
|
|
ret = -ENOENT;
|
|
goto unlock;
|
|
}
|
|
|
|
parcel->mem_handle = GUNYAH_MEM_HANDLE_INVAL;
|
|
if (b->share_type == VM_MEM_LEND) {
|
|
parcel->n_acl_entries = 1;
|
|
lend = true;
|
|
} else {
|
|
lend = false;
|
|
parcel->n_acl_entries = 2;
|
|
parcel->label = b->label;
|
|
}
|
|
parcel->acl_entries = kcalloc(parcel->n_acl_entries,
|
|
sizeof(*parcel->acl_entries), GFP_KERNEL);
|
|
if (!parcel->acl_entries) {
|
|
ret = -ENOMEM;
|
|
goto unlock;
|
|
}
|
|
|
|
/* acl_entries[0].vmid will be this VM's vmid. We'll fill it when the
|
|
* VM is starting and we know the VM's vmid.
|
|
*/
|
|
parcel->acl_entries[0].vmid = cpu_to_le16(ghvm->vmid);
|
|
if (b->flags & GUNYAH_MEM_ALLOW_READ)
|
|
parcel->acl_entries[0].perms |= GUNYAH_RM_ACL_R;
|
|
if (b->flags & GUNYAH_MEM_ALLOW_WRITE)
|
|
parcel->acl_entries[0].perms |= GUNYAH_RM_ACL_W;
|
|
if (b->flags & GUNYAH_MEM_ALLOW_EXEC)
|
|
parcel->acl_entries[0].perms |= GUNYAH_RM_ACL_X;
|
|
|
|
if (!lend) {
|
|
ret = gunyah_rm_get_vmid(ghvm->rm, &vmid);
|
|
if (ret)
|
|
goto free_acl;
|
|
|
|
parcel->acl_entries[1].vmid = cpu_to_le16(vmid);
|
|
/* Host assumed to have all these permissions. Gunyah will not
|
|
* grant new permissions if host actually had less than RWX
|
|
*/
|
|
parcel->acl_entries[1].perms = GUNYAH_RM_ACL_R | GUNYAH_RM_ACL_W | GUNYAH_RM_ACL_X;
|
|
}
|
|
|
|
if (b->mem_type == VM_MEM_CMA) {
|
|
ret = gunyah_cma_share_parcel(ghvm, vm_parcel, b, gfn, nr);
|
|
if (ret) {
|
|
dev_warn(ghvm->parent, "Failed to share CMA memory: %d\n", ret);
|
|
goto free_acl;
|
|
}
|
|
} else {
|
|
ret = gunyah_gup_share_parcel(ghvm, vm_parcel, b, gfn, nr);
|
|
if (ret) {
|
|
dev_warn(ghvm->parent, "Failed to share GUP memory: %d\n", ret);
|
|
goto free_acl;
|
|
}
|
|
}
|
|
goto unlock;
|
|
|
|
free_acl:
|
|
kfree(parcel->acl_entries);
|
|
parcel->acl_entries = NULL;
|
|
unlock:
|
|
up_write(&ghvm->bindings_lock);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* This function will provide the number of bindings from
|
|
* start_addr to end_addr.
|
|
* Use ULONG_MAX as the end_addr to get all the bindings of the VM.
|
|
*/
|
|
static u32 gunyah_count_bindings(struct gunyah_vm *ghvm, u64 start_addr,
|
|
u64 end_addr)
|
|
{
|
|
struct gunyah_vm_binding *b;
|
|
unsigned long addr = start_addr;
|
|
u32 count = 0;
|
|
|
|
down_read(&ghvm->bindings_lock);
|
|
mt_for_each(&ghvm->bindings, b, addr, end_addr)
|
|
count++;
|
|
up_read(&ghvm->bindings_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int gunyah_gup_reclaim_parcel(struct gunyah_vm *ghvm,
|
|
struct gunyah_vm_parcel *vm_parcel, struct gunyah_vm_binding *b)
|
|
{
|
|
struct gunyah_rm_mem_parcel *parcel = &vm_parcel->parcel;
|
|
struct gunyah_rm_mem_entry *entry;
|
|
struct folio *folio;
|
|
pgoff_t i;
|
|
int ret;
|
|
|
|
if (parcel->mem_handle == GUNYAH_MEM_HANDLE_INVAL)
|
|
return 0;
|
|
|
|
ret = gunyah_rm_mem_reclaim(ghvm->rm, parcel);
|
|
if (ret) {
|
|
dev_err(ghvm->parent, "Failed to reclaim parcel: %d\n",
|
|
ret);
|
|
/* We can't reclaim the pages -- hold onto the pages
|
|
* forever because we don't know what state the memory
|
|
* is in
|
|
*/
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < parcel->n_mem_entries; i++) {
|
|
entry = &parcel->mem_entries[i];
|
|
|
|
folio = pfn_folio(PHYS_PFN(le64_to_cpu(entry->phys_addr)));
|
|
|
|
if (folio_test_private(folio))
|
|
gunyah_folio_host_reclaim(folio);
|
|
|
|
unpin_user_page(folio_page(folio, 0));
|
|
account_locked_vm(ghvm->mm_s, 1, false);
|
|
}
|
|
|
|
parcel->mem_handle = GUNYAH_MEM_HANDLE_INVAL;
|
|
kfree(parcel->mem_entries);
|
|
kfree(parcel->acl_entries);
|
|
vm_parcel->start = 0;
|
|
vm_parcel->pages = 0;
|
|
b->vm_parcel = NULL;
|
|
return ret;
|
|
}
|
|
|
|
static int gunyah_reclaim_parcel(struct gunyah_vm *ghvm,
|
|
struct gunyah_vm_parcel *vm_parcel)
|
|
{
|
|
struct gunyah_vm_binding *b;
|
|
int ret;
|
|
|
|
down_write(&ghvm->bindings_lock);
|
|
b = mtree_load(&ghvm->bindings, vm_parcel->start);
|
|
if (!b) {
|
|
ret = -ENOENT;
|
|
goto unlock;
|
|
}
|
|
|
|
if (b->mem_type == VM_MEM_CMA)
|
|
ret = gunyah_cma_reclaim_parcel(ghvm, vm_parcel, b);
|
|
else
|
|
ret = gunyah_gup_reclaim_parcel(ghvm, vm_parcel, b);
|
|
|
|
unlock:
|
|
up_write(&ghvm->bindings_lock);
|
|
return ret;
|
|
}
|
|
|
|
int gunyah_reclaim_parcels(struct gunyah_vm *ghvm, u64 start_gfn,
|
|
u64 end_gfn)
|
|
{
|
|
unsigned long gfn = start_gfn;
|
|
struct gunyah_vm_binding *b;
|
|
int ret, ret2 = 0;
|
|
|
|
mt_for_each(&ghvm->bindings, b, gfn, end_gfn) {
|
|
if (b->vm_parcel)
|
|
ret = gunyah_reclaim_parcel(ghvm, b->vm_parcel);
|
|
if (ret)
|
|
ret2 = ret;
|
|
}
|
|
|
|
return ret2;
|
|
}
|
|
|
|
int gunyah_reclaim_fw_parcel(struct gunyah_vm *ghvm, u32 mem_handle)
|
|
{
|
|
if (ghvm->fw.parcel.parcel.mem_handle != mem_handle)
|
|
return -EINVAL;
|
|
|
|
return gunyah_reclaim_parcel(ghvm, &ghvm->fw.parcel);
|
|
}
|
|
EXPORT_SYMBOL_GPL(gunyah_reclaim_fw_parcel);
|
|
|
|
/*
|
|
* gunyah_share_range_as_parcels() - Share all bindings as parcels from start_gfn to end_gfn
|
|
* @ghvm - The gunyah vm
|
|
* @start_gfn: Start guest page number
|
|
* @end_gfn: Last guest page number
|
|
* @parcels: Array of parcels allocated.
|
|
*
|
|
* Use ULONG_MAX as the end_gfn to share all the bindings of the VM
|
|
* provided enough space for parcels is present.
|
|
* Caller is responsible to free the parcels when parcels are done
|
|
* being used.
|
|
*/
|
|
int gunyah_share_range_as_parcels(struct gunyah_vm *ghvm, u64 start_gfn,
|
|
u64 end_gfn, struct gunyah_vm_parcel **parcels)
|
|
{
|
|
struct gunyah_vm_binding *b;
|
|
unsigned long gfn = start_gfn;
|
|
u32 count = 0, n;
|
|
int ret, ret_err;
|
|
|
|
/* Find the number of parcels needed to be created within the requested range*/
|
|
n = gunyah_count_bindings(ghvm, start_gfn, end_gfn);
|
|
|
|
*parcels = kzalloc(sizeof(struct gunyah_vm_parcel) * n, GFP_KERNEL);
|
|
if (!*parcels)
|
|
return -ENOMEM;
|
|
|
|
mt_for_each(&ghvm->bindings, b, gfn, end_gfn) {
|
|
u64 parcel_start = b->guest_phys_addr >> PAGE_SHIFT;
|
|
u64 parcel_pages = b->size >> PAGE_SHIFT;
|
|
|
|
ret = gunyah_share_parcel(ghvm, &(*parcels)[count++], &parcel_start, &parcel_pages);
|
|
if (ret) {
|
|
dev_err(ghvm->parent, "Failed to share parcel of %llx: %d\n",
|
|
parcel_start, ret);
|
|
/* Let's roll back.*/
|
|
while (count--) {
|
|
if ((*parcels)[count].parcel.mem_handle !=
|
|
GUNYAH_MEM_HANDLE_INVAL) {
|
|
ret_err = gunyah_reclaim_parcel(ghvm, &(*parcels)[count]);
|
|
if (ret_err)
|
|
dev_err(ghvm->parent, "Failed to reclaim parcel: %d, memory will leak\n",
|
|
ret_err);
|
|
}
|
|
}
|
|
goto err;
|
|
}
|
|
}
|
|
return ret;
|
|
|
|
err:
|
|
kfree(*parcels);
|
|
*parcels = NULL;
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(gunyah_share_range_as_parcels);
|
|
|
|
int gunyah_setup_demand_paging(struct gunyah_vm *ghvm, u64 start_gfn,
|
|
u64 end_gfn)
|
|
{
|
|
struct gunyah_rm_mem_entry *entries;
|
|
unsigned long gfn = start_gfn;
|
|
struct gunyah_vm_binding *b;
|
|
u32 count = 0, i;
|
|
int ret = 0;
|
|
|
|
down_read(&ghvm->bindings_lock);
|
|
mt_for_each(&ghvm->bindings, b, gfn, end_gfn)
|
|
if (b->share_type == VM_MEM_LEND &&
|
|
(b->guest_phys_addr != ghvm->fw.config.guest_phys_addr))
|
|
count++;
|
|
|
|
if (!count)
|
|
goto out;
|
|
|
|
entries = kcalloc(count, sizeof(*entries), GFP_KERNEL);
|
|
if (!entries) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
gfn = start_gfn;
|
|
i = 0;
|
|
mt_for_each(&ghvm->bindings, b, gfn, end_gfn) {
|
|
if (b->share_type != VM_MEM_LEND ||
|
|
(b->guest_phys_addr == ghvm->fw.config.guest_phys_addr))
|
|
continue;
|
|
entries[i].phys_addr = cpu_to_le64(b->guest_phys_addr);
|
|
entries[i].size = cpu_to_le64(b->size);
|
|
if (++i == count)
|
|
break;
|
|
}
|
|
|
|
ret = gunyah_rm_vm_set_demand_paging(ghvm->rm, ghvm->vmid, i, entries);
|
|
kfree(entries);
|
|
out:
|
|
up_read(&ghvm->bindings_lock);
|
|
return ret;
|
|
}
|