spapr: nested: Introduce H_GUEST_[GET|SET]_STATE hcalls.

Introduce the nested PAPR hcalls:
    - H_GUEST_GET_STATE which is used to get state of a nested guest or
      a guest VCPU. The value field for each element in the request is
      destination to be updated to reflect current state on success.
    - H_GUEST_SET_STATE which is used to modify the state of a guest or
      a guest VCPU. On success, guest (or its VCPU) state shall be
      updated as per the value field for the requested element(s).

Reviewed-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Michael Neuling <mikey@neuling.org>
Signed-off-by: Harsh Prateek Bora <harshpb@linux.ibm.com>
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
This commit is contained in:
Harsh Prateek Bora 2024-03-08 16:49:37 +05:30 committed by Nicholas Piggin
parent 4a575f9a05
commit 64c43909b2
3 changed files with 294 additions and 0 deletions

View File

@ -1029,6 +1029,140 @@ void spapr_nested_gsb_init(void)
}
}
static struct guest_state_element *guest_state_element_next(
struct guest_state_element *element,
int64_t *len,
int64_t *num_elements)
{
uint16_t size;
/* size is of element->value[] only. Not whole guest_state_element */
size = be16_to_cpu(element->size);
if (len) {
*len -= size + offsetof(struct guest_state_element, value);
}
if (num_elements) {
*num_elements -= 1;
}
return (struct guest_state_element *)(element->value + size);
}
static
struct guest_state_element_type *guest_state_element_type_find(uint16_t id)
{
int i;
for (i = 0; i < ARRAY_SIZE(guest_state_element_types); i++)
if (id == guest_state_element_types[i].id) {
return &guest_state_element_types[i];
}
return NULL;
}
static void log_element(struct guest_state_element *element,
struct guest_state_request *gsr)
{
qemu_log_mask(LOG_GUEST_ERROR, "h_guest_%s_state id:0x%04x size:0x%04x",
gsr->flags & GUEST_STATE_REQUEST_SET ? "set" : "get",
be16_to_cpu(element->id), be16_to_cpu(element->size));
qemu_log_mask(LOG_GUEST_ERROR, "buf:0x%016"PRIx64" ...\n",
be64_to_cpu(*(uint64_t *)element->value));
}
static bool guest_state_request_check(struct guest_state_request *gsr)
{
int64_t num_elements, len = gsr->len;
struct guest_state_buffer *gsb = gsr->gsb;
struct guest_state_element *element;
struct guest_state_element_type *type;
uint16_t id, size;
/* gsb->num_elements = 0 == 32 bits long */
assert(len >= 4);
num_elements = be32_to_cpu(gsb->num_elements);
element = gsb->elements;
len -= sizeof(gsb->num_elements);
/* Walk the buffer to validate the length */
while (num_elements) {
id = be16_to_cpu(element->id);
size = be16_to_cpu(element->size);
if (false) {
log_element(element, gsr);
}
/* buffer size too small */
if (len < 0) {
return false;
}
type = guest_state_element_type_find(id);
if (!type) {
qemu_log_mask(LOG_GUEST_ERROR, "Element ID %04x unknown\n", id);
log_element(element, gsr);
return false;
}
if (id == GSB_HV_VCPU_IGNORED_ID) {
goto next_element;
}
if (size != type->size) {
qemu_log_mask(LOG_GUEST_ERROR, "Size mismatch. Element ID:%04x."
"Size Exp:%i Got:%i\n", id, type->size, size);
log_element(element, gsr);
return false;
}
if ((type->flags & GUEST_STATE_ELEMENT_TYPE_FLAG_READ_ONLY) &&
(gsr->flags & GUEST_STATE_REQUEST_SET)) {
qemu_log_mask(LOG_GUEST_ERROR, "Trying to set a read-only Element "
"ID:%04x.\n", id);
return false;
}
if (type->flags & GUEST_STATE_ELEMENT_TYPE_FLAG_GUEST_WIDE) {
/* guest wide element type */
if (!(gsr->flags & GUEST_STATE_REQUEST_GUEST_WIDE)) {
qemu_log_mask(LOG_GUEST_ERROR, "trying to set a guest wide "
"Element ID:%04x.\n", id);
return false;
}
} else {
/* thread wide element type */
if (gsr->flags & GUEST_STATE_REQUEST_GUEST_WIDE) {
qemu_log_mask(LOG_GUEST_ERROR, "trying to set a thread wide "
"Element ID:%04x.\n", id);
return false;
}
}
next_element:
element = guest_state_element_next(element, &len, &num_elements);
}
return true;
}
static bool is_gsr_invalid(struct guest_state_request *gsr,
struct guest_state_element *element,
struct guest_state_element_type *type)
{
if ((gsr->flags & GUEST_STATE_REQUEST_SET) &&
(*(uint64_t *)(element->value) & ~(type->mask))) {
log_element(element, gsr);
qemu_log_mask(LOG_GUEST_ERROR, "L1 can't set reserved bits "
"(allowed mask: 0x%08"PRIx64")\n", type->mask);
return true;
}
return false;
}
static target_ulong h_guest_get_capabilities(PowerPCCPU *cpu,
SpaprMachineState *spapr,
target_ulong opcode,
@ -1264,6 +1398,136 @@ static target_ulong h_guest_create_vcpu(PowerPCCPU *cpu,
return H_SUCCESS;
}
static target_ulong getset_state(SpaprMachineStateNestedGuest *guest,
uint64_t vcpuid,
struct guest_state_request *gsr)
{
void *ptr;
uint16_t id;
struct guest_state_element *element;
struct guest_state_element_type *type;
int64_t lenleft, num_elements;
lenleft = gsr->len;
if (!guest_state_request_check(gsr)) {
return H_P3;
}
num_elements = be32_to_cpu(gsr->gsb->num_elements);
element = gsr->gsb->elements;
/* Process the elements */
while (num_elements) {
type = NULL;
/* log_element(element, gsr); */
id = be16_to_cpu(element->id);
if (id == GSB_HV_VCPU_IGNORED_ID) {
goto next_element;
}
type = guest_state_element_type_find(id);
assert(type);
/* Get pointer to guest data to get/set */
if (type->location && type->copy) {
ptr = type->location(guest, vcpuid);
assert(ptr);
if (!~(type->mask) && is_gsr_invalid(gsr, element, type)) {
return H_INVALID_ELEMENT_VALUE;
}
type->copy(ptr + type->offset, element->value,
gsr->flags & GUEST_STATE_REQUEST_SET ? true : false);
}
next_element:
element = guest_state_element_next(element, &lenleft, &num_elements);
}
return H_SUCCESS;
}
static target_ulong map_and_getset_state(PowerPCCPU *cpu,
SpaprMachineStateNestedGuest *guest,
uint64_t vcpuid,
struct guest_state_request *gsr)
{
target_ulong rc;
int64_t len;
bool is_write;
len = gsr->len;
/* only get_state would require write access to the provided buffer */
is_write = (gsr->flags & GUEST_STATE_REQUEST_SET) ? false : true;
gsr->gsb = address_space_map(CPU(cpu)->as, gsr->buf, (uint64_t *)&len,
is_write, MEMTXATTRS_UNSPECIFIED);
if (!gsr->gsb) {
rc = H_P3;
goto out1;
}
if (len != gsr->len) {
rc = H_P3;
goto out1;
}
rc = getset_state(guest, vcpuid, gsr);
out1:
address_space_unmap(CPU(cpu)->as, gsr->gsb, len, is_write, len);
return rc;
}
static target_ulong h_guest_getset_state(PowerPCCPU *cpu,
SpaprMachineState *spapr,
target_ulong *args,
bool set)
{
target_ulong flags = args[0];
target_ulong lpid = args[1];
target_ulong vcpuid = args[2];
target_ulong buf = args[3];
target_ulong buflen = args[4];
struct guest_state_request gsr;
SpaprMachineStateNestedGuest *guest;
guest = spapr_get_nested_guest(spapr, lpid);
if (!guest) {
return H_P2;
}
gsr.buf = buf;
assert(buflen <= GSB_MAX_BUF_SIZE);
gsr.len = buflen;
gsr.flags = 0;
if (flags & H_GUEST_GETSET_STATE_FLAG_GUEST_WIDE) {
gsr.flags |= GUEST_STATE_REQUEST_GUEST_WIDE;
}
if (flags & !H_GUEST_GETSET_STATE_FLAG_GUEST_WIDE) {
return H_PARAMETER; /* flag not supported yet */
}
if (set) {
gsr.flags |= GUEST_STATE_REQUEST_SET;
}
return map_and_getset_state(cpu, guest, vcpuid, &gsr);
}
static target_ulong h_guest_set_state(PowerPCCPU *cpu,
SpaprMachineState *spapr,
target_ulong opcode,
target_ulong *args)
{
return h_guest_getset_state(cpu, spapr, args, true);
}
static target_ulong h_guest_get_state(PowerPCCPU *cpu,
SpaprMachineState *spapr,
target_ulong opcode,
target_ulong *args)
{
return h_guest_getset_state(cpu, spapr, args, false);
}
void spapr_register_nested_hv(void)
{
spapr_register_hypercall(KVMPPC_H_SET_PARTITION_TABLE, h_set_ptbl);
@ -1289,6 +1553,8 @@ void spapr_register_nested_papr(void)
spapr_register_hypercall(H_GUEST_CREATE, h_guest_create);
spapr_register_hypercall(H_GUEST_DELETE, h_guest_delete);
spapr_register_hypercall(H_GUEST_CREATE_VCPU, h_guest_create_vcpu);
spapr_register_hypercall(H_GUEST_SET_STATE, h_guest_set_state);
spapr_register_hypercall(H_GUEST_GET_STATE, h_guest_get_state);
}
void spapr_unregister_nested_papr(void)
@ -1298,6 +1564,8 @@ void spapr_unregister_nested_papr(void)
spapr_unregister_hypercall(H_GUEST_CREATE);
spapr_unregister_hypercall(H_GUEST_DELETE);
spapr_unregister_hypercall(H_GUEST_CREATE_VCPU);
spapr_unregister_hypercall(H_GUEST_SET_STATE);
spapr_unregister_hypercall(H_GUEST_GET_STATE);
}
#else

View File

@ -366,6 +366,7 @@ struct SpaprMachineState {
#define H_OVERLAP -68
#define H_STATE -75
#define H_IN_USE -77
#define H_INVALID_ELEMENT_VALUE -81
#define H_UNSUPPORTED_FLAG -256
#define H_MULTI_THREADS_ACTIVE -9005
@ -589,6 +590,8 @@ struct SpaprMachineState {
#define H_GUEST_SET_CAPABILITIES 0x464
#define H_GUEST_CREATE 0x470
#define H_GUEST_CREATE_VCPU 0x474
#define H_GUEST_GET_STATE 0x478
#define H_GUEST_SET_STATE 0x47C
#define H_GUEST_DELETE 0x488
#define MAX_HCALL_OPCODE H_GUEST_DELETE

View File

@ -224,6 +224,10 @@ typedef struct SpaprMachineStateNestedGuest {
#define HVMASK_MSR 0xEBFFFFFFFFBFEFFF
#define HVMASK_HDEXCR 0x00000000FFFFFFFF
#define HVMASK_TB_OFFSET 0x000000FFFFFFFFFF
#define GSB_MAX_BUF_SIZE (1024 * 1024)
#define H_GUEST_GETSET_STATE_FLAG_GUEST_WIDE 0x8000000000000000
#define GUEST_STATE_REQUEST_GUEST_WIDE 0x1
#define GUEST_STATE_REQUEST_SET 0x2
/*
* As per ISA v3.1B, following bits are reserved:
@ -322,6 +326,25 @@ typedef struct SpaprMachineStateNestedGuest {
#define GSE_ENV_DWM(i, f, m) \
GUEST_STATE_ELEMENT_MSK(i, 8, f, copy_state_8to8, m)
struct guest_state_element {
uint16_t id;
uint16_t size;
uint8_t value[];
} QEMU_PACKED;
struct guest_state_buffer {
uint32_t num_elements;
struct guest_state_element elements[];
} QEMU_PACKED;
/* Actual buffer plus some metadata about the request */
struct guest_state_request {
struct guest_state_buffer *gsb;
int64_t buf;
int64_t len;
uint16_t flags;
};
/*
* Register state for entering a nested guest with H_ENTER_NESTED.
* New member must be added at the end.