Skip to content

Commit

Permalink
nvdimm acpi: introduce _FIT
Browse files Browse the repository at this point in the history
_FIT is required for hotplug support, guest will inquire the updated
device info from it if a hotplug event is received

As FIT buffer is not completely mapped into guest address space, so a
new function, Read FIT whose UUID is UUID
648B9CF2-CDA1-4312-8AD9-49C4AF32BD62, handle 0x10000, function index
is 0x1, is reserved by QEMU to read the piece of FIT buffer. The buffer
is concatenated before _FIT return

Refer to docs/specs/acpi-nvdimm.txt for detailed design

Signed-off-by: Xiao Guangrong <guangrong.xiao@linux.intel.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
  • Loading branch information
Xiao Guangrong authored and mstsirkin committed Nov 1, 2016
1 parent 75b0713 commit 806864d
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 5 deletions.
58 changes: 55 additions & 3 deletions docs/specs/acpi_nvdimm.txt
Expand Up @@ -127,6 +127,58 @@ _DSM process diagram:
| result from the page | | |
+--------------------------+ +--------------+

_FIT implementation
-------------------
TODO (will fill it when nvdimm hotplug is introduced)
Device Handle Reservation
-------------------------
As we mentioned above, byte 0 ~ byte 3 in the DSM memory save NVDIMM device
handle. The handle is completely QEMU internal thing, the values in range
[0, 0xFFFF] indicate nvdimm device (O means nvdimm root device named NVDR),
other values are reserved by other purpose.

Current reserved handle:
0x10000 is reserved for QEMU internal DSM function called on the root
device.

QEMU internal use only _DSM function
------------------------------------
UUID, 648B9CF2-CDA1-4312-8AD9-49C4AF32BD62, is reserved for QEMU internal
DSM function.

There is the function introduced by QEMU and only used by QEMU internal.

1) Read FIT
As we only reserved one page for NVDIMM ACPI it is impossible to map the
whole FIT data to guest's address space. This function is used by _FIT
method to read a piece of FIT data from QEMU.

Input parameters:
Arg0 – UUID {set to 648B9CF2-CDA1-4312-8AD9-49C4AF32BD62}
Arg1 – Revision ID (set to 1)
Arg2 - Function Index, 0x1
Arg3 - A package containing a buffer whose layout is as follows:

+----------+-------------+-------------+-----------------------------------+
| Filed | Byte Length | Byte Offset | Description |
+----------+-------------+-------------+-----------------------------------+
| offset | 4 | 0 | the offset of FIT buffer |
+----------+-------------+-------------+-----------------------------------+

Output:
+----------+-------------+-------------+-----------------------------------+
| Filed | Byte Length | Byte Offset | Description |
+----------+-------------+-------------+-----------------------------------+
| | | | return status codes |
| | | | 0x100 indicates fit has been |
| status | 4 | 0 | updated |
| | | | other follows Chapter 3 in DSM |
| | | | Spec Rev1 |
+----------+-------------+-------------+-----------------------------------+
| fit data | Varies | 4 | FIT data |
| | | | |
+----------+-------------+-------------+-----------------------------------+

The FIT offset is maintained by the caller itself, current offset plugs
the length returned by the function is the next offset we should read.
When all the FIT data has been read out, zero length is returned.

If it returns 0x100, OSPM should restart to read FIT (read from offset 0
again).
204 changes: 202 additions & 2 deletions hw/acpi/nvdimm.c
Expand Up @@ -496,6 +496,22 @@ typedef struct NvdimmFuncSetLabelDataIn NvdimmFuncSetLabelDataIn;
QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncSetLabelDataIn) +
offsetof(NvdimmDsmIn, arg3) > 4096);

struct NvdimmFuncReadFITIn {
uint32_t offset; /* the offset of FIT buffer. */
} QEMU_PACKED;
typedef struct NvdimmFuncReadFITIn NvdimmFuncReadFITIn;
QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncReadFITIn) +
offsetof(NvdimmDsmIn, arg3) > 4096);

struct NvdimmFuncReadFITOut {
/* the size of buffer filled by QEMU. */
uint32_t len;
uint32_t func_ret_status; /* return status code. */
uint8_t fit[0]; /* the FIT data. */
} QEMU_PACKED;
typedef struct NvdimmFuncReadFITOut NvdimmFuncReadFITOut;
QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncReadFITOut) > 4096);

static void
nvdimm_dsm_function0(uint32_t supported_func, hwaddr dsm_mem_addr)
{
Expand All @@ -516,6 +532,74 @@ nvdimm_dsm_no_payload(uint32_t func_ret_status, hwaddr dsm_mem_addr)
cpu_physical_memory_write(dsm_mem_addr, &out, sizeof(out));
}

#define NVDIMM_QEMU_RSVD_HANDLE_ROOT 0x10000

/* Read FIT data, defined in docs/specs/acpi_nvdimm.txt. */
static void nvdimm_dsm_func_read_fit(AcpiNVDIMMState *state, NvdimmDsmIn *in,
hwaddr dsm_mem_addr)
{
NvdimmFitBuffer *fit_buf = &state->fit_buf;
NvdimmFuncReadFITIn *read_fit;
NvdimmFuncReadFITOut *read_fit_out;
GArray *fit;
uint32_t read_len = 0, func_ret_status;
int size;

read_fit = (NvdimmFuncReadFITIn *)in->arg3;
le32_to_cpus(&read_fit->offset);

qemu_mutex_lock(&fit_buf->lock);
fit = fit_buf->fit;

nvdimm_debug("Read FIT: offset %#x FIT size %#x Dirty %s.\n",
read_fit->offset, fit->len, fit_buf->dirty ? "Yes" : "No");

if (read_fit->offset > fit->len) {
func_ret_status = 3 /* Invalid Input Parameters */;
goto exit;
}

/* It is the first time to read FIT. */
if (!read_fit->offset) {
fit_buf->dirty = false;
} else if (fit_buf->dirty) { /* FIT has been changed during RFIT. */
func_ret_status = 0x100 /* fit changed */;
goto exit;
}

func_ret_status = 0 /* Success */;
read_len = MIN(fit->len - read_fit->offset,
4096 - sizeof(NvdimmFuncReadFITOut));

exit:
size = sizeof(NvdimmFuncReadFITOut) + read_len;
read_fit_out = g_malloc(size);

read_fit_out->len = cpu_to_le32(size);
read_fit_out->func_ret_status = cpu_to_le32(func_ret_status);
memcpy(read_fit_out->fit, fit->data + read_fit->offset, read_len);

cpu_physical_memory_write(dsm_mem_addr, read_fit_out, size);

g_free(read_fit_out);
qemu_mutex_unlock(&fit_buf->lock);
}

static void nvdimm_dsm_reserved_root(AcpiNVDIMMState *state, NvdimmDsmIn *in,
hwaddr dsm_mem_addr)
{
switch (in->function) {
case 0x0:
nvdimm_dsm_function0(0x1 | 1 << 1 /* Read FIT */, dsm_mem_addr);
return;
case 0x1 /*Read FIT */:
nvdimm_dsm_func_read_fit(state, in, dsm_mem_addr);
return;
}

nvdimm_dsm_no_payload(1 /* Not Supported */, dsm_mem_addr);
}

static void nvdimm_dsm_root(NvdimmDsmIn *in, hwaddr dsm_mem_addr)
{
/*
Expand Down Expand Up @@ -742,6 +826,7 @@ nvdimm_dsm_read(void *opaque, hwaddr addr, unsigned size)
static void
nvdimm_dsm_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
AcpiNVDIMMState *state = opaque;
NvdimmDsmIn *in;
hwaddr dsm_mem_addr = val;

Expand Down Expand Up @@ -769,6 +854,11 @@ nvdimm_dsm_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
goto exit;
}

if (in->handle == NVDIMM_QEMU_RSVD_HANDLE_ROOT) {
nvdimm_dsm_reserved_root(state, in, dsm_mem_addr);
goto exit;
}

/* Handle 0 is reserved for NVDIMM Root Device. */
if (!in->handle) {
nvdimm_dsm_root(in, dsm_mem_addr);
Expand Down Expand Up @@ -821,9 +911,13 @@ void nvdimm_init_acpi_state(AcpiNVDIMMState *state, MemoryRegion *io,
#define NVDIMM_DSM_OUT_BUF_SIZE "RLEN"
#define NVDIMM_DSM_OUT_BUF "ODAT"

#define NVDIMM_DSM_RFIT_STATUS "RSTA"

#define NVDIMM_QEMU_RSVD_UUID "648B9CF2-CDA1-4312-8AD9-49C4AF32BD62"

static void nvdimm_build_common_dsm(Aml *dev)
{
Aml *method, *ifctx, *function, *handle, *uuid, *dsm_mem;
Aml *method, *ifctx, *function, *handle, *uuid, *dsm_mem, *elsectx2;
Aml *elsectx, *unsupport, *unpatched, *expected_uuid, *uuid_invalid;
Aml *pckg, *pckg_index, *pckg_buf, *field, *dsm_out_buf, *dsm_out_buf_size;
uint8_t byte_list[1];
Expand Down Expand Up @@ -912,9 +1006,15 @@ static void nvdimm_build_common_dsm(Aml *dev)
/* UUID for NVDIMM Root Device */, expected_uuid));
aml_append(method, ifctx);
elsectx = aml_else();
aml_append(elsectx, aml_store(
ifctx = aml_if(aml_equal(handle, aml_int(NVDIMM_QEMU_RSVD_HANDLE_ROOT)));
aml_append(ifctx, aml_store(aml_touuid(NVDIMM_QEMU_RSVD_UUID
/* UUID for QEMU internal use */), expected_uuid));
aml_append(elsectx, ifctx);
elsectx2 = aml_else();
aml_append(elsectx2, aml_store(
aml_touuid("4309AC30-0D11-11E4-9191-0800200C9A66")
/* UUID for NVDIMM Devices */, expected_uuid));
aml_append(elsectx, elsectx2);
aml_append(method, elsectx);

uuid_invalid = aml_lnot(aml_equal(uuid, expected_uuid));
Expand Down Expand Up @@ -994,6 +1094,105 @@ static void nvdimm_build_device_dsm(Aml *dev, uint32_t handle)
aml_append(dev, method);
}

static void nvdimm_build_fit(Aml *dev)
{
Aml *method, *pkg, *buf, *buf_size, *offset, *call_result;
Aml *whilectx, *ifcond, *ifctx, *elsectx, *fit;

buf = aml_local(0);
buf_size = aml_local(1);
fit = aml_local(2);

aml_append(dev, aml_create_dword_field(aml_buffer(4, NULL),
aml_int(0), NVDIMM_DSM_RFIT_STATUS));

/* build helper function, RFIT. */
method = aml_method("RFIT", 1, AML_SERIALIZED);
aml_append(method, aml_create_dword_field(aml_buffer(4, NULL),
aml_int(0), "OFST"));

/* prepare input package. */
pkg = aml_package(1);
aml_append(method, aml_store(aml_arg(0), aml_name("OFST")));
aml_append(pkg, aml_name("OFST"));

/* call Read_FIT function. */
call_result = aml_call5(NVDIMM_COMMON_DSM,
aml_touuid(NVDIMM_QEMU_RSVD_UUID),
aml_int(1) /* Revision 1 */,
aml_int(0x1) /* Read FIT */,
pkg, aml_int(NVDIMM_QEMU_RSVD_HANDLE_ROOT));
aml_append(method, aml_store(call_result, buf));

/* handle _DSM result. */
aml_append(method, aml_create_dword_field(buf,
aml_int(0) /* offset at byte 0 */, "STAU"));

aml_append(method, aml_store(aml_name("STAU"),
aml_name(NVDIMM_DSM_RFIT_STATUS)));

/* if something is wrong during _DSM. */
ifcond = aml_equal(aml_int(0 /* Success */), aml_name("STAU"));
ifctx = aml_if(aml_lnot(ifcond));
aml_append(ifctx, aml_return(aml_buffer(0, NULL)));
aml_append(method, ifctx);

aml_append(method, aml_store(aml_sizeof(buf), buf_size));
aml_append(method, aml_subtract(buf_size,
aml_int(4) /* the size of "STAU" */,
buf_size));

/* if we read the end of fit. */
ifctx = aml_if(aml_equal(buf_size, aml_int(0)));
aml_append(ifctx, aml_return(aml_buffer(0, NULL)));
aml_append(method, ifctx);

aml_append(method, aml_store(aml_shiftleft(buf_size, aml_int(3)),
buf_size));
aml_append(method, aml_create_field(buf,
aml_int(4 * BITS_PER_BYTE), /* offset at byte 4.*/
buf_size, "BUFF"));
aml_append(method, aml_return(aml_name("BUFF")));
aml_append(dev, method);

/* build _FIT. */
method = aml_method("_FIT", 0, AML_SERIALIZED);
offset = aml_local(3);

aml_append(method, aml_store(aml_buffer(0, NULL), fit));
aml_append(method, aml_store(aml_int(0), offset));

whilectx = aml_while(aml_int(1));
aml_append(whilectx, aml_store(aml_call1("RFIT", offset), buf));
aml_append(whilectx, aml_store(aml_sizeof(buf), buf_size));

/*
* if fit buffer was changed during RFIT, read from the beginning
* again.
*/
ifctx = aml_if(aml_equal(aml_name(NVDIMM_DSM_RFIT_STATUS),
aml_int(0x100 /* fit changed */)));
aml_append(ifctx, aml_store(aml_buffer(0, NULL), fit));
aml_append(ifctx, aml_store(aml_int(0), offset));
aml_append(whilectx, ifctx);

elsectx = aml_else();

/* finish fit read if no data is read out. */
ifctx = aml_if(aml_equal(buf_size, aml_int(0)));
aml_append(ifctx, aml_return(fit));
aml_append(elsectx, ifctx);

/* update the offset. */
aml_append(elsectx, aml_add(offset, buf_size, offset));
/* append the data we read out to the fit buffer. */
aml_append(elsectx, aml_concatenate(fit, buf, fit));
aml_append(whilectx, elsectx);
aml_append(method, whilectx);

aml_append(dev, method);
}

static void nvdimm_build_nvdimm_devices(Aml *root_dev, uint32_t ram_slots)
{
uint32_t slot;
Expand Down Expand Up @@ -1052,6 +1251,7 @@ static void nvdimm_build_ssdt(GArray *table_offsets, GArray *table_data,

/* 0 is reserved for root device. */
nvdimm_build_device_dsm(dev, 0);
nvdimm_build_fit(dev);

nvdimm_build_nvdimm_devices(dev, ram_slots);

Expand Down

0 comments on commit 806864d

Please sign in to comment.