// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2009, Microsoft Corporation.
*
* Authors:
* Haiyang Zhang <haiyangz@microsoft.com>
* Hank Janssen <hjanssen@microsoft.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/hyperv.h>
#include <linux/uio.h>
#include <linux/interrupt.h>
#include <linux/set_memory.h>
#include <asm/page.h>
#include <asm/mshyperv.h>
#include "hyperv_vmbus.h"
/*
* hv_gpadl_size - Return the real size of a gpadl, the size that Hyper-V uses
*
* For BUFFER gpadl, Hyper-V uses the exact same size as the guest does.
*
* For RING gpadl, in each ring, the guest uses one PAGE_SIZE as the header
* (because of the alignment requirement), however, the hypervisor only
* uses the first HV_HYP_PAGE_SIZE as the header, therefore leaving a
* (PAGE_SIZE - HV_HYP_PAGE_SIZE) gap. And since there are two rings in a
* ringbuffer, the total size for a RING gpadl that Hyper-V uses is the
* total size that the guest uses minus twice of the gap size.
*/
static inline u32 hv_gpadl_size(enum hv_gpadl_type type, u32 size)
{
switch (type) {
case HV_GPADL_BUFFER:
return size;
case HV_GPADL_RING:
/* The size of a ringbuffer must be page-aligned */
BUG_ON(size % PAGE_SIZE);
/*
* Two things to notice here:
* 1) We're processing two ring buffers as a unit
* 2) We're skipping any space larger than HV_HYP_PAGE_SIZE in
* the first guest-size page of each of the two ring buffers.
* So we effectively subtract out two guest-size pages, and add
* back two Hyper-V size pages.
*/
return size - 2 * (PAGE_SIZE - HV_HYP_PAGE_SIZE);
}
BUG();
return 0;
}
/*
* hv_ring_gpadl_send_hvpgoffset - Calculate the send offset (in unit of
* HV_HYP_PAGE) in a ring gpadl based on the
* offset in the guest
*
* @offset: the offset (in bytes) where the send ringbuffer starts in the
* virtual address space of the guest
*/
static inline u32 hv_ring_gpadl_send_hvpgoffset(u32 offset)
{
/*
* For RING gpadl, in each ring, the guest uses one PAGE_SIZE as the
* header (because of the alignment requirement), however, the
* hypervisor only uses the first HV_HYP_PAGE_SIZE as the header,
* therefore leaving a (PAGE_SIZE - HV_HYP_PAGE_SIZE) gap.
*
* And to calculate the effective send offset in gpadl, we need to
* substract this gap.
*/
return (offset - (PAGE_SIZE - HV_HYP_PAGE_SIZE)) >> HV_HYP_PAGE_SHIFT;
}
/*
* hv_gpadl_hvpfn - Return the Hyper-V page PFN of the @i th Hyper-V page in
* the gpadl
*
* @type: the type of the gpadl
* @kbuffer: the pointer to the gpadl in the guest
* @size: the total size (in bytes) of the gpadl
* @send_offset: the offset (in bytes) where the send ringbuffer starts in the
* virtual address space of the guest
* @i: the index
*/
static inline u64 hv_gpadl_hvpfn(enum hv_gpadl_type type, void *kbuffer,
u32 size, u32 send_offset, int i)
{
int send_idx = hv_ring_gpadl_send_hvpgoffset(send_offset);
unsigned long delta = 0UL;
switch (type) {
case HV_GPADL_BUFFER:
break;
case HV_GPADL_RING:
if (i == 0)
delta = 0;
else if (i <= send_idx)
delta = PAGE_SIZE - HV_HYP_PAGE_SIZE;
else
delta = 2 * (PAGE_SIZE - HV_HYP_PAGE_SIZE);
break;
default:
BUG();