From: Ben Warren ben@skyportsystems.com
This patch set adds the capability to write to QEMU across the fw_cfg DMA link. It adds a higher-level command to write the BIOS-allocated address of one fw_cfg file into an arbitrary point within another, writeable fw_cfg file across the above-mentioned link.
The initial use case is for Windows VM Generation ID, where QEMU needs to change the contents of fw_cfg data at runtime, while still having BIOS allocate and manage the memory.
v2->v3: - fixed up the qemu_cfg_write* functions to allow writing to an arbitrary offset within the destination file. - Changed function name to COMMAND_WRITE_POINTER and its functionality to not patch memory at all, but write back to a specified offset.
v1->v2: - separated patch into two functional units. - changed so writes only occur over the DMA interface. - fixed coding style. - removed change to romfile struct definition (removed new write_back method).
Ben Warren (2): QEMU DMA: Add DMA write capability QEMU fw_cfg: Add command to write back address of file
src/fw/paravirt.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ src/fw/paravirt.h | 3 +++ src/fw/romfile_loader.c | 37 +++++++++++++++++++++++++++++++++++++ src/fw/romfile_loader.h | 16 ++++++++++------ 4 files changed, 99 insertions(+), 6 deletions(-)
From: Ben Warren ben@skyportsystems.com
This allows BIOS to write data back to QEMU using the DMA interface and provides a higher-level abstraction to write to a fw_cfg file
Signed-off-by: Ben Warren ben@skyportsystems.com --- src/fw/paravirt.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ src/fw/paravirt.h | 3 +++ 2 files changed, 52 insertions(+)
diff --git a/src/fw/paravirt.c b/src/fw/paravirt.c index 6de70f6..75cb992 100644 --- a/src/fw/paravirt.c +++ b/src/fw/paravirt.c @@ -253,6 +253,20 @@ qemu_cfg_read(void *buf, int len) }
static void +qemu_cfg_write(void *buf, int len) +{ + if (len == 0) { + return; + } + + if (qemu_cfg_dma_enabled()) { + qemu_cfg_dma_transfer(buf, len, QEMU_CFG_DMA_CTL_WRITE); + } else { + warn_internalerror(); + } +} + +static void qemu_cfg_skip(int len) { if (len == 0) { @@ -280,6 +294,18 @@ qemu_cfg_read_entry(void *buf, int e, int len) } }
+static void +qemu_cfg_write_entry(void *buf, int e, int len) +{ + if (qemu_cfg_dma_enabled()) { + u32 control = (e << 16) | QEMU_CFG_DMA_CTL_SELECT + | QEMU_CFG_DMA_CTL_WRITE; + qemu_cfg_dma_transfer(buf, len, control); + } else { + warn_internalerror(); + } +} + struct qemu_romfile_s { struct romfile_s file; int select, skip; @@ -303,6 +329,29 @@ qemu_cfg_read_file(struct romfile_s *file, void *dst, u32 maxlen) return file->size; }
+int +qemu_cfg_write_file(void *src, struct romfile_s *file, u32 offset, u32 len) +{ + if ((file->size + offset) < len) + return -1; + + if (!qemu_cfg_dma_enabled() || (file->copy != qemu_cfg_read_file)) { + warn_internalerror(); + return -1; + } + struct qemu_romfile_s *qfile; + qfile = container_of(file, struct qemu_romfile_s, file); + if (offset == 0) { + /* Do it in one transfer */ + qemu_cfg_write_entry(src, qfile->select, len); + } else { + qemu_cfg_select(qfile->select); + qemu_cfg_skip(offset); + qemu_cfg_write(src, len); + } + return len; +} + static void qemu_romfile_add(char *name, int select, int skip, int size) { diff --git a/src/fw/paravirt.h b/src/fw/paravirt.h index d8eb7c4..fb220d8 100644 --- a/src/fw/paravirt.h +++ b/src/fw/paravirt.h @@ -3,6 +3,7 @@
#include "config.h" // CONFIG_* #include "biosvar.h" // GET_GLOBAL +#include "romfile.h" // struct romfile_s
// Types of paravirtualized platforms. #define PF_QEMU (1<<0) @@ -43,6 +44,7 @@ static inline int runningOnKVM(void) { #define QEMU_CFG_DMA_CTL_READ 0x02 #define QEMU_CFG_DMA_CTL_SKIP 0x04 #define QEMU_CFG_DMA_CTL_SELECT 0x08 +#define QEMU_CFG_DMA_CTL_WRITE 0x10
// QEMU_CFG_DMA ID bit #define QEMU_CFG_VERSION_DMA 2 @@ -53,5 +55,6 @@ void qemu_platform_setup(void); void qemu_cfg_init(void);
u16 qemu_get_present_cpus_count(void); +int qemu_cfg_write_file(void *src, struct romfile_s *file, u32 offset, u32 len);
#endif
From: Ben Warren ben@skyportsystems.com
This command is similar to ADD_POINTER, but instead of patching memory, it writes the pointer back to QEMU over the DMA interface.
Signed-off-by: Ben Warren ben@skyportsystems.com --- src/fw/romfile_loader.c | 37 +++++++++++++++++++++++++++++++++++++ src/fw/romfile_loader.h | 16 ++++++++++------ 2 files changed, 47 insertions(+), 6 deletions(-)
diff --git a/src/fw/romfile_loader.c b/src/fw/romfile_loader.c index f4b17ff..d0ae42b 100644 --- a/src/fw/romfile_loader.c +++ b/src/fw/romfile_loader.c @@ -5,6 +5,7 @@ #include "romfile.h" // struct romfile_s #include "malloc.h" // Zone*, _malloc #include "output.h" // warn_* +#include "paravirt.h" // qemu_cfg_write_file
struct romfile_loader_file { struct romfile_s *file; @@ -98,7 +99,39 @@ static void romfile_loader_add_pointer(struct romfile_loader_entry_s *entry, pointer += (unsigned long)src_file->data; pointer = cpu_to_le64(pointer); memcpy(dest_file->data + offset, &pointer, entry->pointer_size); + return; +err: + warn_internalerror(); +} + +static void romfile_loader_write_pointer(struct romfile_loader_entry_s *entry, + struct romfile_loader_files *files) +{ + struct romfile_s *dest_file; + struct romfile_loader_file *src_file; + unsigned offset = le32_to_cpu(entry->pointer_offset); + u64 pointer = 0; + + /* Writing back to a file that may not be loaded in RAM */ + dest_file = romfile_find(entry->pointer_dest_file); + src_file = romfile_loader_find(entry->pointer_src_file, files);
+ if (!dest_file || !src_file || !src_file->data || + offset + entry->pointer_size < offset || + offset + entry->pointer_size > dest_file->size || + entry->pointer_size < 1 || entry->pointer_size > 8 || + entry->pointer_size & (entry->pointer_size - 1)) { + goto err; + } + + pointer = (unsigned long)src_file->data; + pointer = cpu_to_le64(pointer); + + /* Only supported on QEMU */ + if (qemu_cfg_write_file(&pointer, dest_file, offset, + entry->pointer_size) != entry->pointer_size) { + goto err; + } return; err: warn_internalerror(); @@ -161,6 +194,10 @@ int romfile_loader_execute(const char *name) break; case ROMFILE_LOADER_COMMAND_ADD_CHECKSUM: romfile_loader_add_checksum(entry, files); + break; + case ROMFILE_LOADER_COMMAND_WRITE_POINTER: + romfile_loader_write_pointer(entry, files); + break; default: /* Skip commands that we don't recognize. */ break; diff --git a/src/fw/romfile_loader.h b/src/fw/romfile_loader.h index 15eab2a..0c0782c 100644 --- a/src/fw/romfile_loader.h +++ b/src/fw/romfile_loader.h @@ -25,10 +25,13 @@ struct romfile_loader_entry_s { };
/* - * COMMAND_ADD_POINTER - patch the table (originating from - * @dest_file) at @pointer_offset, by adding a pointer to the table + * COMMAND_ADD_POINTER & + * COMMAND_WRITE_POINTER - patch memory (originating from + * @dest_file) at @pointer.offset, by adding a pointer to the memory * originating from @src_file. 1,2,4 or 8 byte unsigned - * addition is used depending on @pointer_size. + * addition is used depending on @pointer.size. + * Instead of patching memory, COMMAND_WRITE_POINTER writes the changes + * to @dest_file back to the host via DMA */ struct { char pointer_dest_file[ROMFILE_LOADER_FILESZ]; @@ -57,9 +60,10 @@ struct romfile_loader_entry_s { };
enum { - ROMFILE_LOADER_COMMAND_ALLOCATE = 0x1, - ROMFILE_LOADER_COMMAND_ADD_POINTER = 0x2, - ROMFILE_LOADER_COMMAND_ADD_CHECKSUM = 0x3, + ROMFILE_LOADER_COMMAND_ALLOCATE = 0x1, + ROMFILE_LOADER_COMMAND_ADD_POINTER = 0x2, + ROMFILE_LOADER_COMMAND_ADD_CHECKSUM = 0x3, + ROMFILE_LOADER_COMMAND_WRITE_POINTER = 0x4, };
enum {