*/
#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/units.h"
+#include "system/address-spaces.h"
#include "system/runstate.h"
#include "hw/ppc/pnv.h"
#include "hw/ppc/pnv_mpipl.h"
+#include <math.h>
+
+#define MDST_TABLE_RELOCATED \
+ (pnv->mpipl_state.skiboot_base + MDST_TABLE_OFF)
+#define MDDT_TABLE_RELOCATED \
+ (pnv->mpipl_state.skiboot_base + MDDT_TABLE_OFF)
+
+/*
+ * Preserve the memory regions as pointed by MDST table
+ *
+ * During this, the memory region pointed by entries in MDST, are 'copied'
+ * as it is to the memory region pointed by corresponding entry in MDDT
+ *
+ * Notes: All reads should consider data coming from skiboot as big-endian,
+ * and data written should also be in big-endian
+ */
+static bool pnv_mpipl_preserve_mem(PnvMachineState *pnv)
+{
+ g_autofree MdstTableEntry *mdst = g_malloc(MDST_TABLE_SIZE);
+ g_autofree MddtTableEntry *mddt = g_malloc(MDDT_TABLE_SIZE);
+ g_autofree MdrtTableEntry *mdrt = g_malloc0(MDRT_TABLE_SIZE);
+ AddressSpace *default_as = &address_space_memory;
+ MemTxResult io_result;
+ MemTxAttrs attrs;
+ uint64_t src_addr, dest_addr;
+ uint32_t data_len;
+ uint64_t num_chunks, chunk_id = 0;
+ int mdrt_idx = 0;
+
+ /* Mark the memory transactions as privileged memory access */
+ attrs.user = 0;
+ attrs.memory = 1;
+
+ if (pnv->mpipl_state.mdrt_table) {
+ /*
+ * MDRT table allocated from some past crash, free the memory to
+ * prevent memory leak
+ */
+ g_free(pnv->mpipl_state.mdrt_table);
+ pnv->mpipl_state.num_mdrt_entries = 0;
+ }
+
+ io_result = address_space_read(default_as, MDST_TABLE_RELOCATED, attrs,
+ mdst, MDST_TABLE_SIZE);
+ if (io_result != MEMTX_OK) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MPIPL: Failed to read MDST table at: 0x" TARGET_FMT_lx "\n",
+ MDST_TABLE_RELOCATED);
+
+ return false;
+ }
+
+ io_result = address_space_read(default_as, MDDT_TABLE_RELOCATED, attrs,
+ mddt, MDDT_TABLE_SIZE);
+ if (io_result != MEMTX_OK) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MPIPL: Failed to read MDDT table at: 0x" TARGET_FMT_lx "\n",
+ MDDT_TABLE_RELOCATED);
+
+ return false;
+ }
+
+ /* Try to read all entries */
+ for (int i = 0; i < MDST_MAX_ENTRIES; ++i) {
+ g_autofree uint8_t *copy_buffer = NULL;
+ bool is_copy_failed = false;
+
+ /* Considering entry with address and size as 0, as end of table */
+ if ((mdst[i].addr == 0) && (mdst[i].size == 0)) {
+ break;
+ }
+
+ if (mdst[i].size != mddt[i].size) {
+ qemu_log_mask(LOG_TRACE,
+ "Warning: Invalid entry, size mismatch in MDST & MDDT\n");
+ continue;
+ }
+
+ if (mdst[i].data_region != mddt[i].data_region) {
+ qemu_log_mask(LOG_TRACE,
+ "Warning: Invalid entry, region mismatch in MDST & MDDT\n");
+ continue;
+ }
+
+ src_addr = be64_to_cpu(mdst[i].addr) & ~HRMOR_BIT;
+ dest_addr = be64_to_cpu(mddt[i].addr) & ~HRMOR_BIT;
+ data_len = be32_to_cpu(mddt[i].size);
+
+#define COPY_CHUNK_SIZE ((size_t)(32 * MiB))
+ copy_buffer = g_try_malloc(COPY_CHUNK_SIZE);
+ if (copy_buffer == NULL) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MPIPL: Failed allocating memory (size: %zu) for copying"
+ " reserved memory regions\n", COPY_CHUNK_SIZE);
+ is_copy_failed = true;
+ continue;
+ }
+
+ chunk_id = 0;
+ num_chunks = ceil((data_len * 1.0f) / COPY_CHUNK_SIZE);
+ while (chunk_id < num_chunks) {
+ /* Take minimum of bytes left to copy, and chunk size */
+ uint64_t copy_len = MIN(
+ data_len - (chunk_id * COPY_CHUNK_SIZE),
+ COPY_CHUNK_SIZE
+ );
+
+ /* Copy the source region to destination */
+ io_result = address_space_read(default_as, src_addr, attrs,
+ copy_buffer, copy_len);
+ if (io_result != MEMTX_OK) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MPIPL: Failed to read region at: 0x%" PRIx64 "\n",
+ src_addr);
+ is_copy_failed = true;
+ break;
+ }
+
+ io_result = address_space_write(default_as, dest_addr, attrs,
+ copy_buffer, copy_len);
+ if (io_result != MEMTX_OK) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MPIPL: Failed to write region at: 0x%" PRIx64 "\n",
+ dest_addr);
+ is_copy_failed = true;
+ break;
+ }
+
+ src_addr += COPY_CHUNK_SIZE;
+ dest_addr += COPY_CHUNK_SIZE;
+ ++chunk_id;
+ }
+#undef COPY_CHUNK_SIZE
+
+ if (is_copy_failed) {
+ /*
+ * HDAT doesn't specify an error code in MDRT for failed copy,
+ * and doesn't specify how this is to be handled
+ * Hence just skip adding an entry in MDRT, as done for size
+ * mismatch or other inconsistency between MDST/MDDT
+ */
+ continue;
+ }
+
+ /* Populate entry in MDRT table if preserving successful */
+ mdrt[mdrt_idx].src_addr = cpu_to_be64(src_addr);
+ mdrt[mdrt_idx].dest_addr = cpu_to_be64(dest_addr);
+ mdrt[mdrt_idx].size = cpu_to_be32(data_len);
+ mdrt[mdrt_idx].data_region = mdst[i].data_region;
+ ++mdrt_idx;
+ }
+
+ pnv->mpipl_state.mdrt_table = g_steal_pointer(&mdrt);
+ pnv->mpipl_state.num_mdrt_entries = mdrt_idx;
+
+ return true;
+}
void do_mpipl_preserve(PnvMachineState *pnv)
{
+ pnv_mpipl_preserve_mem(pnv);
+
/* Mark next boot as Memory-preserving boot */
pnv->mpipl_state.is_next_boot_mpipl = true;
#ifndef PNV_MPIPL_H
#define PNV_MPIPL_H
+#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "exec/hwaddr.h"
+#include "qemu/compiler.h"
+typedef struct MdstTableEntry MdstTableEntry;
+typedef struct MdrtTableEntry MdrtTableEntry;
typedef struct MpiplPreservedState MpiplPreservedState;
+/*
+ * Following offsets are copied from skiboot source code.
+ * These need to be updated if this changes in a future skiboot version
+ */
+/* Use 768 bytes for SPIRAH */
+#define SPIRAH_OFF 0x00010000
+#define SPIRAH_SIZE 0x300
+
+/* Use 256 bytes for processor dump area */
+#define PROC_DUMP_AREA_OFF (SPIRAH_OFF + SPIRAH_SIZE)
+#define PROC_DUMP_AREA_SIZE 0x100
+
+#define PROCIN_OFF (PROC_DUMP_AREA_OFF + PROC_DUMP_AREA_SIZE)
+#define PROCIN_SIZE 0x800
+
+/* Offsets of MDST and MDDT tables from skiboot base */
+#define MDST_TABLE_OFF (PROCIN_OFF + PROCIN_SIZE)
+#define MDST_TABLE_SIZE 0x400
+
+#define MDDT_TABLE_OFF (MDST_TABLE_OFF + MDST_TABLE_SIZE)
+#define MDDT_TABLE_SIZE 0x400
+/*
+ * Offset of the dump result table MDRT. Hostboot will write to this
+ * memory after moving memory content from source to destination memory.
+ */
+#define MDRT_TABLE_OFF 0x01c00000
+#define MDRT_TABLE_SIZE 0x00008000
+
+/* HRMOR_BIT copied from skiboot */
+#define HRMOR_BIT (1ull << 63)
+
+/*
+ * Memory Dump Source Table (MDST)
+ *
+ * Format of this table is same as Memory Dump Source Table defined in HDAT
+ */
+struct MdstTableEntry {
+ uint64_t addr;
+ uint8_t data_region;
+ uint8_t dump_type;
+ uint16_t reserved;
+ uint32_t size;
+} QEMU_PACKED;
+
+/* Memory dump destination table (MDDT) has same structure as MDST */
+typedef MdstTableEntry MddtTableEntry;
+
+/*
+ * Memory dump result table (MDRT)
+ *
+ * List of the memory ranges that have been included in the dump. This table is
+ * filled by hostboot and passed to OPAL on second boot. OPAL/payload will use
+ * this table to extract the dump.
+ *
+ * Note: This structure differs from HDAT, but matches the structure
+ * skiboot uses
+ */
+struct MdrtTableEntry {
+ uint64_t src_addr;
+ uint64_t dest_addr;
+ uint8_t data_region;
+ uint8_t dump_type; /* unused */
+ uint16_t reserved; /* unused */
+ uint32_t size;
+ uint64_t padding; /* unused */
+} QEMU_PACKED;
+
+/* Maximum length of mdst/mddt/mdrt tables */
+#define MDST_MAX_ENTRIES (MDST_TABLE_SIZE / sizeof(MdstTableEntry))
+#define MDDT_MAX_ENTRIES (MDDT_TABLE_SIZE / sizeof(MddtTableEntry))
+#define MDRT_MAX_ENTRIES (MDRT_TABLE_SIZE / sizeof(MdrtTableEntry))
+
+static_assert(MDST_MAX_ENTRIES == MDDT_MAX_ENTRIES,
+ "Maximum entries in MDDT must match MDST");
+static_assert(MDRT_MAX_ENTRIES >= MDST_MAX_ENTRIES,
+ "MDRT should support atleast having number of entries as in MDST");
+
/* Preserved state to be saved in PnvMachineState */
struct MpiplPreservedState {
/* skiboot_base will be valid only after OPAL sends relocated base to SBE */
hwaddr skiboot_base;
bool is_next_boot_mpipl;
+
+ MdrtTableEntry *mdrt_table;
+ uint32_t num_mdrt_entries;
};
#endif