linux-stable/arch/powerpc/kvm/guest-state-buffer.c
Jordan Niethe 19d31c5f11 KVM: PPC: Add support for nestedv2 guests
A series of hcalls have been added to the PAPR which allow a regular
guest partition to create and manage guest partitions of its own. KVM
already had an interface that allowed this on powernv platforms. This
existing interface will now be called "nestedv1". The newly added PAPR
interface will be called "nestedv2".  PHYP will support the nestedv2
interface. At this time the host side of the nestedv2 interface has not
been implemented on powernv but there is no technical reason why it
could not be added.

The nestedv1 interface is still supported.

Add support to KVM to utilize these hcalls to enable running nested
guests as a pseries guest on PHYP.

Overview of the new hcall usage:

- L1 and L0 negotiate capabilities with
  H_GUEST_{G,S}ET_CAPABILITIES()

- L1 requests the L0 create a L2 with
  H_GUEST_CREATE() and receives a handle to use in future hcalls

- L1 requests the L0 create a L2 vCPU with
  H_GUEST_CREATE_VCPU()

- L1 sets up the L2 using H_GUEST_SET and the
  H_GUEST_VCPU_RUN input buffer

- L1 requests the L0 runs the L2 vCPU using H_GUEST_VCPU_RUN()

- L2 returns to L1 with an exit reason and L1 reads the
  H_GUEST_VCPU_RUN output buffer populated by the L0

- L1 handles the exit using H_GET_STATE if necessary

- L1 reruns L2 vCPU with H_GUEST_VCPU_RUN

- L1 frees the L2 in the L0 with H_GUEST_DELETE()

Support for the new API is determined by trying
H_GUEST_GET_CAPABILITIES. On a successful return, use the nestedv2
interface.

Use the vcpu register state setters for tracking modified guest state
elements and copy the thread wide values into the H_GUEST_VCPU_RUN input
buffer immediately before running a L2. The guest wide
elements can not be added to the input buffer so send them with a
separate H_GUEST_SET call if necessary.

Make the vcpu register getter load the corresponding value from the real
host with H_GUEST_GET. To avoid unnecessarily calling H_GUEST_GET, track
which values have already been loaded between H_GUEST_VCPU_RUN calls. If
an element is present in the H_GUEST_VCPU_RUN output buffer it also does
not need to be loaded again.

Tested-by: Sachin Sant <sachinp@linux.ibm.com>
Signed-off-by: Vaibhav Jain <vaibhav@linux.ibm.com>
Signed-off-by: Gautam Menghani <gautam@linux.ibm.com>
Signed-off-by: Kautuk Consul <kconsul@linux.vnet.ibm.com>
Signed-off-by: Amit Machhiwal <amachhiw@linux.vnet.ibm.com>
Signed-off-by: Jordan Niethe <jniethe5@gmail.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://msgid.link/20230914030600.16993-11-jniethe5@gmail.com
2023-09-14 22:04:24 +10:00

621 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include "asm/hvcall.h"
#include <linux/log2.h>
#include <asm/pgalloc.h>
#include <asm/guest-state-buffer.h>
static const u16 kvmppc_gse_iden_len[__KVMPPC_GSE_TYPE_MAX] = {
[KVMPPC_GSE_BE32] = sizeof(__be32),
[KVMPPC_GSE_BE64] = sizeof(__be64),
[KVMPPC_GSE_VEC128] = sizeof(vector128),
[KVMPPC_GSE_PARTITION_TABLE] = sizeof(struct kvmppc_gs_part_table),
[KVMPPC_GSE_PROCESS_TABLE] = sizeof(struct kvmppc_gs_proc_table),
[KVMPPC_GSE_BUFFER] = sizeof(struct kvmppc_gs_buff_info),
};
/**
* kvmppc_gsb_new() - create a new guest state buffer
* @size: total size of the guest state buffer (includes header)
* @guest_id: guest_id
* @vcpu_id: vcpu_id
* @flags: GFP flags
*
* Returns a guest state buffer.
*/
struct kvmppc_gs_buff *kvmppc_gsb_new(size_t size, unsigned long guest_id,
unsigned long vcpu_id, gfp_t flags)
{
struct kvmppc_gs_buff *gsb;
gsb = kzalloc(sizeof(*gsb), flags);
if (!gsb)
return NULL;
size = roundup_pow_of_two(size);
gsb->hdr = kzalloc(size, GFP_KERNEL);
if (!gsb->hdr)
goto free;
gsb->capacity = size;
gsb->len = sizeof(struct kvmppc_gs_header);
gsb->vcpu_id = vcpu_id;
gsb->guest_id = guest_id;
gsb->hdr->nelems = cpu_to_be32(0);
return gsb;
free:
kfree(gsb);
return NULL;
}
EXPORT_SYMBOL_GPL(kvmppc_gsb_new);
/**
* kvmppc_gsb_free() - free a guest state buffer
* @gsb: guest state buffer
*/
void kvmppc_gsb_free(struct kvmppc_gs_buff *gsb)
{
kfree(gsb->hdr);
kfree(gsb);
}
EXPORT_SYMBOL_GPL(kvmppc_gsb_free);
/**
* kvmppc_gsb_put() - allocate space in a guest state buffer
* @gsb: buffer to allocate in
* @size: amount of space to allocate
*
* Returns a pointer to the amount of space requested within the buffer and
* increments the count of elements in the buffer.
*
* Does not check if there is enough space in the buffer.
*/
void *kvmppc_gsb_put(struct kvmppc_gs_buff *gsb, size_t size)
{
u32 nelems = kvmppc_gsb_nelems(gsb);
void *p;
p = (void *)kvmppc_gsb_header(gsb) + kvmppc_gsb_len(gsb);
gsb->len += size;
kvmppc_gsb_header(gsb)->nelems = cpu_to_be32(nelems + 1);
return p;
}
EXPORT_SYMBOL_GPL(kvmppc_gsb_put);
static int kvmppc_gsid_class(u16 iden)
{
if ((iden >= KVMPPC_GSE_GUESTWIDE_START) &&
(iden <= KVMPPC_GSE_GUESTWIDE_END))
return KVMPPC_GS_CLASS_GUESTWIDE;
if ((iden >= KVMPPC_GSE_META_START) && (iden <= KVMPPC_GSE_META_END))
return KVMPPC_GS_CLASS_META;
if ((iden >= KVMPPC_GSE_DW_REGS_START) &&
(iden <= KVMPPC_GSE_DW_REGS_END))
return KVMPPC_GS_CLASS_DWORD_REG;
if ((iden >= KVMPPC_GSE_W_REGS_START) &&
(iden <= KVMPPC_GSE_W_REGS_END))
return KVMPPC_GS_CLASS_WORD_REG;
if ((iden >= KVMPPC_GSE_VSRS_START) && (iden <= KVMPPC_GSE_VSRS_END))
return KVMPPC_GS_CLASS_VECTOR;
if ((iden >= KVMPPC_GSE_INTR_REGS_START) &&
(iden <= KVMPPC_GSE_INTR_REGS_END))
return KVMPPC_GS_CLASS_INTR;
return -1;
}
static int kvmppc_gsid_type(u16 iden)
{
int type = -1;
switch (kvmppc_gsid_class(iden)) {
case KVMPPC_GS_CLASS_GUESTWIDE:
switch (iden) {
case KVMPPC_GSID_HOST_STATE_SIZE:
case KVMPPC_GSID_RUN_OUTPUT_MIN_SIZE:
case KVMPPC_GSID_TB_OFFSET:
type = KVMPPC_GSE_BE64;
break;
case KVMPPC_GSID_PARTITION_TABLE:
type = KVMPPC_GSE_PARTITION_TABLE;
break;
case KVMPPC_GSID_PROCESS_TABLE:
type = KVMPPC_GSE_PROCESS_TABLE;
break;
case KVMPPC_GSID_LOGICAL_PVR:
type = KVMPPC_GSE_BE32;
break;
}
break;
case KVMPPC_GS_CLASS_META:
switch (iden) {
case KVMPPC_GSID_RUN_INPUT:
case KVMPPC_GSID_RUN_OUTPUT:
type = KVMPPC_GSE_BUFFER;
break;
case KVMPPC_GSID_VPA:
type = KVMPPC_GSE_BE64;
break;
}
break;
case KVMPPC_GS_CLASS_DWORD_REG:
type = KVMPPC_GSE_BE64;
break;
case KVMPPC_GS_CLASS_WORD_REG:
type = KVMPPC_GSE_BE32;
break;
case KVMPPC_GS_CLASS_VECTOR:
type = KVMPPC_GSE_VEC128;
break;
case KVMPPC_GS_CLASS_INTR:
switch (iden) {
case KVMPPC_GSID_HDAR:
case KVMPPC_GSID_ASDR:
case KVMPPC_GSID_HEIR:
type = KVMPPC_GSE_BE64;
break;
case KVMPPC_GSID_HDSISR:
type = KVMPPC_GSE_BE32;
break;
}
break;
}
return type;
}
/**
* kvmppc_gsid_flags() - the flags for a guest state ID
* @iden: guest state ID
*
* Returns any flags for the guest state ID.
*/
unsigned long kvmppc_gsid_flags(u16 iden)
{
unsigned long flags = 0;
switch (kvmppc_gsid_class(iden)) {
case KVMPPC_GS_CLASS_GUESTWIDE:
flags = KVMPPC_GS_FLAGS_WIDE;
break;
case KVMPPC_GS_CLASS_META:
case KVMPPC_GS_CLASS_DWORD_REG:
case KVMPPC_GS_CLASS_WORD_REG:
case KVMPPC_GS_CLASS_VECTOR:
case KVMPPC_GS_CLASS_INTR:
break;
}
return flags;
}
EXPORT_SYMBOL_GPL(kvmppc_gsid_flags);
/**
* kvmppc_gsid_size() - the size of a guest state ID
* @iden: guest state ID
*
* Returns the size of guest state ID.
*/
u16 kvmppc_gsid_size(u16 iden)
{
int type;
type = kvmppc_gsid_type(iden);
if (type == -1)
return 0;
if (type >= __KVMPPC_GSE_TYPE_MAX)
return 0;
return kvmppc_gse_iden_len[type];
}
EXPORT_SYMBOL_GPL(kvmppc_gsid_size);
/**
* kvmppc_gsid_mask() - the settable bits of a guest state ID
* @iden: guest state ID
*
* Returns a mask of settable bits for a guest state ID.
*/
u64 kvmppc_gsid_mask(u16 iden)
{
u64 mask = ~0ull;
switch (iden) {
case KVMPPC_GSID_LPCR:
mask = LPCR_DPFD | LPCR_ILE | LPCR_AIL | LPCR_LD | LPCR_MER |
LPCR_GTSE;
break;
case KVMPPC_GSID_MSR:
mask = ~(MSR_HV | MSR_S | MSR_ME);
break;
}
return mask;
}
EXPORT_SYMBOL_GPL(kvmppc_gsid_mask);
/**
* __kvmppc_gse_put() - add a guest state element to a buffer
* @gsb: buffer to the element to
* @iden: guest state ID
* @size: length of data
* @data: pointer to data
*/
int __kvmppc_gse_put(struct kvmppc_gs_buff *gsb, u16 iden, u16 size,
const void *data)
{
struct kvmppc_gs_elem *gse;
u16 total_size;
total_size = sizeof(*gse) + size;
if (total_size + kvmppc_gsb_len(gsb) > kvmppc_gsb_capacity(gsb))
return -ENOMEM;
if (kvmppc_gsid_size(iden) != size)
return -EINVAL;
gse = kvmppc_gsb_put(gsb, total_size);
gse->iden = cpu_to_be16(iden);
gse->len = cpu_to_be16(size);
memcpy(gse->data, data, size);
return 0;
}
EXPORT_SYMBOL_GPL(__kvmppc_gse_put);
/**
* kvmppc_gse_parse() - create a parse map from a guest state buffer
* @gsp: guest state parser
* @gsb: guest state buffer
*/
int kvmppc_gse_parse(struct kvmppc_gs_parser *gsp, struct kvmppc_gs_buff *gsb)
{
struct kvmppc_gs_elem *curr;
int rem, i;
kvmppc_gsb_for_each_elem(i, curr, gsb, rem) {
if (kvmppc_gse_len(curr) !=
kvmppc_gsid_size(kvmppc_gse_iden(curr)))
return -EINVAL;
kvmppc_gsp_insert(gsp, kvmppc_gse_iden(curr), curr);
}
if (kvmppc_gsb_nelems(gsb) != i)
return -EINVAL;
return 0;
}
EXPORT_SYMBOL_GPL(kvmppc_gse_parse);
static inline int kvmppc_gse_flatten_iden(u16 iden)
{
int bit = 0;
int class;
class = kvmppc_gsid_class(iden);
if (class == KVMPPC_GS_CLASS_GUESTWIDE) {
bit += iden - KVMPPC_GSE_GUESTWIDE_START;
return bit;
}
bit += KVMPPC_GSE_GUESTWIDE_COUNT;
if (class == KVMPPC_GS_CLASS_META) {
bit += iden - KVMPPC_GSE_META_START;
return bit;
}
bit += KVMPPC_GSE_META_COUNT;
if (class == KVMPPC_GS_CLASS_DWORD_REG) {
bit += iden - KVMPPC_GSE_DW_REGS_START;
return bit;
}
bit += KVMPPC_GSE_DW_REGS_COUNT;
if (class == KVMPPC_GS_CLASS_WORD_REG) {
bit += iden - KVMPPC_GSE_W_REGS_START;
return bit;
}
bit += KVMPPC_GSE_W_REGS_COUNT;
if (class == KVMPPC_GS_CLASS_VECTOR) {
bit += iden - KVMPPC_GSE_VSRS_START;
return bit;
}
bit += KVMPPC_GSE_VSRS_COUNT;
if (class == KVMPPC_GS_CLASS_INTR) {
bit += iden - KVMPPC_GSE_INTR_REGS_START;
return bit;
}
return 0;
}
static inline u16 kvmppc_gse_unflatten_iden(int bit)
{
u16 iden;
if (bit < KVMPPC_GSE_GUESTWIDE_COUNT) {
iden = KVMPPC_GSE_GUESTWIDE_START + bit;
return iden;
}
bit -= KVMPPC_GSE_GUESTWIDE_COUNT;
if (bit < KVMPPC_GSE_META_COUNT) {
iden = KVMPPC_GSE_META_START + bit;
return iden;
}
bit -= KVMPPC_GSE_META_COUNT;
if (bit < KVMPPC_GSE_DW_REGS_COUNT) {
iden = KVMPPC_GSE_DW_REGS_START + bit;
return iden;
}
bit -= KVMPPC_GSE_DW_REGS_COUNT;
if (bit < KVMPPC_GSE_W_REGS_COUNT) {
iden = KVMPPC_GSE_W_REGS_START + bit;
return iden;
}
bit -= KVMPPC_GSE_W_REGS_COUNT;
if (bit < KVMPPC_GSE_VSRS_COUNT) {
iden = KVMPPC_GSE_VSRS_START + bit;
return iden;
}
bit -= KVMPPC_GSE_VSRS_COUNT;
if (bit < KVMPPC_GSE_IDEN_COUNT) {
iden = KVMPPC_GSE_INTR_REGS_START + bit;
return iden;
}
return 0;
}
/**
* kvmppc_gsp_insert() - add a mapping from an guest state ID to an element
* @gsp: guest state parser
* @iden: guest state id (key)
* @gse: guest state element (value)
*/
void kvmppc_gsp_insert(struct kvmppc_gs_parser *gsp, u16 iden,
struct kvmppc_gs_elem *gse)
{
int i;
i = kvmppc_gse_flatten_iden(iden);
kvmppc_gsbm_set(&gsp->iterator, iden);
gsp->gses[i] = gse;
}
EXPORT_SYMBOL_GPL(kvmppc_gsp_insert);
/**
* kvmppc_gsp_lookup() - lookup an element from a guest state ID
* @gsp: guest state parser
* @iden: guest state ID (key)
*
* Returns the guest state element if present.
*/
struct kvmppc_gs_elem *kvmppc_gsp_lookup(struct kvmppc_gs_parser *gsp, u16 iden)
{
int i;
i = kvmppc_gse_flatten_iden(iden);
return gsp->gses[i];
}
EXPORT_SYMBOL_GPL(kvmppc_gsp_lookup);
/**
* kvmppc_gsbm_set() - set the guest state ID
* @gsbm: guest state bitmap
* @iden: guest state ID
*/
void kvmppc_gsbm_set(struct kvmppc_gs_bitmap *gsbm, u16 iden)
{
set_bit(kvmppc_gse_flatten_iden(iden), gsbm->bitmap);
}
EXPORT_SYMBOL_GPL(kvmppc_gsbm_set);
/**
* kvmppc_gsbm_clear() - clear the guest state ID
* @gsbm: guest state bitmap
* @iden: guest state ID
*/
void kvmppc_gsbm_clear(struct kvmppc_gs_bitmap *gsbm, u16 iden)
{
clear_bit(kvmppc_gse_flatten_iden(iden), gsbm->bitmap);
}
EXPORT_SYMBOL_GPL(kvmppc_gsbm_clear);
/**
* kvmppc_gsbm_test() - test the guest state ID
* @gsbm: guest state bitmap
* @iden: guest state ID
*/
bool kvmppc_gsbm_test(struct kvmppc_gs_bitmap *gsbm, u16 iden)
{
return test_bit(kvmppc_gse_flatten_iden(iden), gsbm->bitmap);
}
EXPORT_SYMBOL_GPL(kvmppc_gsbm_test);
/**
* kvmppc_gsbm_next() - return the next set guest state ID
* @gsbm: guest state bitmap
* @prev: last guest state ID
*/
u16 kvmppc_gsbm_next(struct kvmppc_gs_bitmap *gsbm, u16 prev)
{
int bit, pbit;
pbit = prev ? kvmppc_gse_flatten_iden(prev) + 1 : 0;
bit = find_next_bit(gsbm->bitmap, KVMPPC_GSE_IDEN_COUNT, pbit);
if (bit < KVMPPC_GSE_IDEN_COUNT)
return kvmppc_gse_unflatten_iden(bit);
return 0;
}
EXPORT_SYMBOL_GPL(kvmppc_gsbm_next);
/**
* kvmppc_gsm_init() - initialize a guest state message
* @gsm: guest state message
* @ops: callbacks
* @data: private data
* @flags: guest wide or thread wide
*/
int kvmppc_gsm_init(struct kvmppc_gs_msg *gsm, struct kvmppc_gs_msg_ops *ops,
void *data, unsigned long flags)
{
memset(gsm, 0, sizeof(*gsm));
gsm->ops = ops;
gsm->data = data;
gsm->flags = flags;
return 0;
}
EXPORT_SYMBOL_GPL(kvmppc_gsm_init);
/**
* kvmppc_gsm_new() - creates a new guest state message
* @ops: callbacks
* @data: private data
* @flags: guest wide or thread wide
* @gfp_flags: GFP allocation flags
*
* Returns an initialized guest state message.
*/
struct kvmppc_gs_msg *kvmppc_gsm_new(struct kvmppc_gs_msg_ops *ops, void *data,
unsigned long flags, gfp_t gfp_flags)
{
struct kvmppc_gs_msg *gsm;
gsm = kzalloc(sizeof(*gsm), gfp_flags);
if (!gsm)
return NULL;
kvmppc_gsm_init(gsm, ops, data, flags);
return gsm;
}
EXPORT_SYMBOL_GPL(kvmppc_gsm_new);
/**
* kvmppc_gsm_size() - creates a new guest state message
* @gsm: self
*
* Returns the size required for the message.
*/
size_t kvmppc_gsm_size(struct kvmppc_gs_msg *gsm)
{
if (gsm->ops->get_size)
return gsm->ops->get_size(gsm);
return 0;
}
EXPORT_SYMBOL_GPL(kvmppc_gsm_size);
/**
* kvmppc_gsm_free() - free guest state message
* @gsm: guest state message
*
* Returns the size required for the message.
*/
void kvmppc_gsm_free(struct kvmppc_gs_msg *gsm)
{
kfree(gsm);
}
EXPORT_SYMBOL_GPL(kvmppc_gsm_free);
/**
* kvmppc_gsm_fill_info() - serialises message to guest state buffer format
* @gsm: self
* @gsb: buffer to serialise into
*/
int kvmppc_gsm_fill_info(struct kvmppc_gs_msg *gsm, struct kvmppc_gs_buff *gsb)
{
if (!gsm->ops->fill_info)
return -EINVAL;
return gsm->ops->fill_info(gsb, gsm);
}
EXPORT_SYMBOL_GPL(kvmppc_gsm_fill_info);
/**
* kvmppc_gsm_refresh_info() - deserialises from guest state buffer
* @gsm: self
* @gsb: buffer to serialise from
*/
int kvmppc_gsm_refresh_info(struct kvmppc_gs_msg *gsm,
struct kvmppc_gs_buff *gsb)
{
if (!gsm->ops->fill_info)
return -EINVAL;
return gsm->ops->refresh_info(gsm, gsb);
}
EXPORT_SYMBOL_GPL(kvmppc_gsm_refresh_info);
/**
* kvmppc_gsb_send - send all elements in the buffer to the hypervisor.
* @gsb: guest state buffer
* @flags: guest wide or thread wide
*
* Performs the H_GUEST_SET_STATE hcall for the guest state buffer.
*/
int kvmppc_gsb_send(struct kvmppc_gs_buff *gsb, unsigned long flags)
{
unsigned long hflags = 0;
unsigned long i;
int rc;
if (kvmppc_gsb_nelems(gsb) == 0)
return 0;
if (flags & KVMPPC_GS_FLAGS_WIDE)
hflags |= H_GUEST_FLAGS_WIDE;
rc = plpar_guest_set_state(hflags, gsb->guest_id, gsb->vcpu_id,
__pa(gsb->hdr), gsb->capacity, &i);
return rc;
}
EXPORT_SYMBOL_GPL(kvmppc_gsb_send);
/**
* kvmppc_gsb_recv - request all elements in the buffer have their value
* updated.
* @gsb: guest state buffer
* @flags: guest wide or thread wide
*
* Performs the H_GUEST_GET_STATE hcall for the guest state buffer.
* After returning from the hcall the guest state elements that were
* present in the buffer will have updated values from the hypervisor.
*/
int kvmppc_gsb_recv(struct kvmppc_gs_buff *gsb, unsigned long flags)
{
unsigned long hflags = 0;
unsigned long i;
int rc;
if (flags & KVMPPC_GS_FLAGS_WIDE)
hflags |= H_GUEST_FLAGS_WIDE;
rc = plpar_guest_get_state(hflags, gsb->guest_id, gsb->vcpu_id,
__pa(gsb->hdr), gsb->capacity, &i);
return rc;
}
EXPORT_SYMBOL_GPL(kvmppc_gsb_recv);