@@ -72,6 +72,37 @@ static void generate_irq_prop(void *fdt, u8 irq, enum irq_type irq_type)
_FDT(fdt_property(fdt, "interrupts", irq_prop, sizeof(irq_prop)));
}
+static void generate_nvmem_node(void *fdt, struct kvm_nv_mem *nvmem)
+{
+ char buf[22]; // 22 >= len("flash@" + 16 hex char + '\0')
+ const u64 reg_prop[] = {
+ cpu_to_fdt64(nvmem->map_addr),
+ cpu_to_fdt64(nvmem->size)
+ };
+
+ snprintf(buf, sizeof(buf), "flash@%llx", nvmem->map_addr);
+ _FDT(fdt_begin_node(fdt, buf));
+
+ _FDT(fdt_property_string(fdt, "compatible", "kvmtool,flash"));
+
+ _FDT(fdt_property(fdt, "reg", reg_prop, sizeof(reg_prop)));
+
+ _FDT(fdt_property_string(fdt, "label", nvmem->name));
+
+ _FDT(fdt_end_node(fdt));
+}
+
+static void generate_nvmem_nodes(void *fdt, struct kvm *kvm)
+{
+ struct list_head *nvmem_node;
+
+ list_for_each(nvmem_node, &kvm->cfg.arch.nvmem_list)
+ generate_nvmem_node(fdt,
+ container_of(nvmem_node,
+ struct kvm_nv_mem,
+ node));
+}
+
struct psci_fns {
u32 cpu_suspend;
u32 cpu_off;
@@ -210,6 +241,9 @@ static int setup_fdt(struct kvm *kvm)
_FDT(fdt_property_cell(fdt, "migrate", fns->migrate));
_FDT(fdt_end_node(fdt));
+ /* Non volatile memories */
+ generate_nvmem_nodes(fdt, kvm);
+
/* Finalise. */
_FDT(fdt_end_node(fdt));
_FDT(fdt_finish(fdt));
@@ -10,6 +10,7 @@
#define ARM_IOPORT_AREA _AC(0x0000000000000000, UL)
#define ARM_MMIO_AREA _AC(0x0000000000010000, UL)
#define ARM_AXI_AREA _AC(0x0000000040000000, UL)
+#define ARM_NVRAM_AREA _AC(0x000000007f000000, UL)
#define ARM_MEMORY_AREA _AC(0x0000000080000000, UL)
#define ARM_LOMAP_MAX_MEMORY ((1ULL << 32) - ARM_MEMORY_AREA)
@@ -24,9 +25,11 @@
#define ARM_IOPORT_SIZE (ARM_MMIO_AREA - ARM_IOPORT_AREA)
#define ARM_VIRTIO_MMIO_SIZE (ARM_AXI_AREA - (ARM_MMIO_AREA + ARM_GIC_SIZE))
#define ARM_PCI_CFG_SIZE (1ULL << 24)
-#define ARM_PCI_MMIO_SIZE (ARM_MEMORY_AREA - \
+#define ARM_PCI_MMIO_SIZE (ARM_NVRAM_AREA - \
(ARM_AXI_AREA + ARM_PCI_CFG_SIZE))
+#define ARM_NVRAM_SIZE (ARM_MEMORY_AREA - ARM_NVRAM_AREA)
+
#define KVM_IOPORT_AREA ARM_IOPORT_AREA
#define KVM_PCI_CFG_AREA ARM_AXI_AREA
#define KVM_PCI_MMIO_AREA (KVM_PCI_CFG_AREA + ARM_PCI_CFG_SIZE)
@@ -1,8 +1,18 @@
#ifndef ARM_COMMON__KVM_CONFIG_ARCH_H
#define ARM_COMMON__KVM_CONFIG_ARCH_H
+#include <linux/list.h>
#include "kvm/parse-options.h"
+struct kvm_nv_mem {
+ char *data_file;
+ char *name;
+ ssize_t size;
+ u64 map_addr;
+ bool write_back;
+ struct list_head node;
+};
+
struct kvm_config_arch {
const char *dump_dtb_filename;
unsigned int force_cntfrq;
@@ -12,9 +22,11 @@ struct kvm_config_arch {
u64 kaslr_seed;
enum irqchip_type irqchip;
u64 fw_addr;
+ struct list_head nvmem_list;
};
int irqchip_parser(const struct option *opt, const char *arg, int unset);
+int nvmem_parser(const struct option *opt, const char *arg, int unset);
#define OPT_ARCH_RUN(pfx, cfg) \
pfx, \
@@ -33,6 +45,10 @@ int irqchip_parser(const struct option *opt, const char *arg, int unset);
"Type of interrupt controller to emulate in the guest", \
irqchip_parser, NULL), \
OPT_U64('\0', "firmware-address", &(cfg)->fw_addr, \
- "Address where firmware should be loaded"),
+ "Address where firmware should be loaded"), \
+ OPT_CALLBACK('\0', "nvmem", NULL, \
+ "<file>,<label>[,wb]", \
+ "Load <file> as non-volatile memory", \
+ nvmem_parser, kvm),
#endif /* ARM_COMMON__KVM_CONFIG_ARCH_H */
@@ -18,6 +18,53 @@ struct kvm_ext kvm_req_ext[] = {
{ 0, 0 },
};
+int nvmem_parser(const struct option *opt, const char *arg, int unset)
+{
+ struct kvm *kvm = (struct kvm*) opt->ptr;
+ struct kvm_nv_mem *nvmem;
+ struct stat st;
+ const char *ptr;
+ uint32_t len;
+
+ nvmem = calloc(sizeof (*nvmem), 1);
+
+ if (!nvmem)
+ die("nvmem: cannot add non-volatile memory");
+
+ ptr = strstr(arg, ",");
+
+ if (!ptr)
+ die("nvmem: missing name for non-volatile memory");
+
+ len = ptr - arg + 1;
+ nvmem->data_file = malloc(len);
+ strncpy(nvmem->data_file, arg, len);
+ nvmem->data_file[len - 1] = '\0';
+
+ if (stat(nvmem->data_file, &st))
+ die("nvmem: failed to stat data file");
+ nvmem->size = st.st_size;
+
+ arg = arg + len;
+ for (ptr = arg; *ptr != '\0' && *ptr != ','; ++ptr)
+ ;
+ len = ptr - arg + 1;
+ nvmem->name = malloc(len);
+ strncpy(nvmem->name, arg, len);
+ nvmem->name[len - 1] = '\0';
+
+ if (*ptr == ',') {
+ if (!strcmp(ptr + 1, "wb"))
+ nvmem->write_back = true;
+ else
+ die("firmware-data: invalid option %s", ptr + 1);
+ }
+
+ list_add(&nvmem->node, &kvm->cfg.arch.nvmem_list);
+
+ return 0;
+}
+
bool kvm__arch_cpu_supports_vm(void)
{
/* The KVM capability check is enough. */
@@ -59,6 +106,7 @@ void kvm__arch_set_cmdline(char *cmdline, bool video)
void kvm__arch_reset(struct kvm *kvm)
{
+ INIT_LIST_HEAD(&kvm->cfg.arch.nvmem_list);
}
void kvm__arch_init(struct kvm *kvm, const char *hugetlbfs_path, u64 ram_size)
@@ -241,3 +289,89 @@ int kvm__arch_setup_firmware(struct kvm *kvm)
{
return 0;
}
+
+static int setup_nvmem(struct kvm *kvm)
+{
+ u64 map_address = ARM_NVRAM_AREA;
+ const u64 limit = ARM_NVRAM_AREA + ARM_NVRAM_SIZE;
+ struct list_head *nvmem_node;
+ const int pagesize = getpagesize();
+
+ list_for_each(nvmem_node, &kvm->cfg.arch.nvmem_list) {
+ struct kvm_nv_mem *nvmem = container_of(nvmem_node,
+ struct kvm_nv_mem,
+ node);
+ void *user_addr;
+ int fd;
+
+ if (map_address + nvmem->size > limit)
+ die("cannot map file %s in non-volatile memory, no space left",
+ nvmem->data_file);
+
+ if (nvmem->size & (pagesize - 1))
+ die("size of non-volatile memory files must be a multiple of system page size (= %d)",
+ pagesize);
+
+ user_addr = mmap(NULL, nvmem->size, PROT_RW, MAP_ANON_NORESERVE, -1, 0);
+ if (user_addr == MAP_FAILED)
+ die("cannot create mapping for file %s",
+ nvmem->data_file);
+
+ fd = open(nvmem->data_file, O_RDONLY);
+ if (fd < 0)
+ die("cannot read file %s", nvmem->data_file);
+ if (read_file(fd, user_addr, nvmem->size) < 0)
+ die("failed to map nv memory data %s",
+ nvmem->data_file);
+ close(fd);
+
+ if (kvm__register_dev_mem(kvm, map_address, nvmem->size,
+ user_addr))
+ die("failed to register nv memory mapping for guest");
+
+ nvmem->map_addr = map_address;
+ map_address += nvmem->size;
+ }
+
+ return 0;
+}
+firmware_init(setup_nvmem);
+
+static int flush_nv_mem(struct kvm *kvm)
+{
+ struct list_head *nvmem_node;
+ int err = 0;
+
+ list_for_each(nvmem_node, &kvm->cfg.arch.nvmem_list) {
+ struct kvm_nv_mem *nvmem = container_of(nvmem_node,
+ struct kvm_nv_mem,
+ node);
+ void *host_pos;
+
+ host_pos = guest_flat_to_host(kvm,
+ nvmem->map_addr);
+
+ if (nvmem->write_back) {
+ int fd;
+
+ fd = open(nvmem->data_file, O_WRONLY);
+ if (fd < 0) {
+ pr_err("failed to open firmware data file for writting");
+ err = -1;
+ continue;
+ }
+
+ if (write_in_full(fd, host_pos, nvmem->size) < 0) {
+ pr_err("failed to flush firmware data to file");
+ err = -1;
+ }
+ close(fd);
+ }
+
+ if (munmap(host_pos, nvmem->size))
+ err = -1;
+ }
+
+ return err;
+}
+firmware_exit(flush_nv_mem);
Add an option to let user load files as non-volatile memory. An additional range of addresses is reserved for nv memory. Loaded files must be a multiple of the system page size. Users can chose whether the data written by the guest modifies the original file. Signed-off-by: Julien Thierry <julien.thierry@arm.com> --- arm/fdt.c | 34 ++++++++ arm/include/arm-common/kvm-arch.h | 5 +- arm/include/arm-common/kvm-config-arch.h | 18 ++++- arm/kvm.c | 134 +++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 2 deletions(-)