File: [local] / src / usr.sbin / vmd / fw_cfg.c (download)
Revision 1.8, Sun Feb 4 14:53:12 2024 UTC (4 months ago) by dv
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD Changes since 1.7: +2 -2 lines
Reverse calloc args.
Found by smatch "double check that we're allocating correct size"
warning. Reported by and ok jsg@.
|
/* $OpenBSD: fw_cfg.c,v 1.8 2024/02/04 14:53:12 dv Exp $ */
/*
* Copyright (c) 2018 Claudio Jeker <claudio@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/uio.h>
#include <machine/biosvar.h> /* bios_memmap_t */
#include <machine/vmmvar.h>
#include <dev/pv/virtioreg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "atomicio.h"
#include "pci.h"
#include "vmd.h"
#include "vmm.h"
#include "fw_cfg.h"
#define FW_CFG_SIGNATURE 0x0000
#define FW_CFG_ID 0x0001
#define FW_CFG_NOGRAPHIC 0x0004
#define FW_CFG_FILE_DIR 0x0019
#define FW_CFG_FILE_FIRST 0x0020
#define FW_CFG_DMA_SIGNATURE 0x51454d5520434647ULL /* QEMU CFG */
struct fw_cfg_dma_access {
uint32_t control;
#define FW_CFG_DMA_ERROR 0x0001
#define FW_CFG_DMA_READ 0x0002
#define FW_CFG_DMA_SKIP 0x0004
#define FW_CFG_DMA_SELECT 0x0008
#define FW_CFG_DMA_WRITE 0x0010 /* not implemented */
uint32_t length;
uint64_t address;
};
struct fw_cfg_file {
uint32_t size;
uint16_t selector;
uint16_t reserved;
char name[56];
};
extern char *__progname;
static struct fw_cfg_state {
size_t offset;
size_t size;
uint8_t *data;
} fw_cfg_state;
static uint64_t fw_cfg_dma_addr;
static bios_memmap_t e820[VMM_MAX_MEM_RANGES];
static int fw_cfg_select_file(uint16_t);
static void fw_cfg_file_dir(void);
void
fw_cfg_init(struct vmop_create_params *vmc)
{
unsigned int sd = 0;
size_t i, e820_len = 0;
char bootorder[64];
const char *bootfmt;
int bootidx = -1;
/* Define e820 memory ranges. */
memset(&e820, 0, sizeof(e820));
for (i = 0; i < vmc->vmc_params.vcp_nmemranges; i++) {
struct vm_mem_range *range = &vmc->vmc_params.vcp_memranges[i];
bios_memmap_t *entry = &e820[i];
entry->addr = range->vmr_gpa;
entry->size = range->vmr_size;
if (range->vmr_type == VM_MEM_RAM)
entry->type = BIOS_MAP_FREE;
else
entry->type = BIOS_MAP_RES;
e820_len += sizeof(bios_memmap_t);
}
fw_cfg_add_file("etc/e820", &e820, e820_len);
/* do not double print chars on serial port */
fw_cfg_add_file("etc/screen-and-debug", &sd, sizeof(sd));
switch (vmc->vmc_bootdevice) {
case VMBOOTDEV_DISK:
bootidx = pci_find_first_device(PCI_PRODUCT_VIRTIO_BLOCK);
bootfmt = "/pci@i0cf8/*@%d\nHALT";
break;
case VMBOOTDEV_CDROM:
bootidx = pci_find_first_device(PCI_PRODUCT_VIRTIO_SCSI);
bootfmt = "/pci@i0cf8/*@%d/*@0/*@0,40000100\nHALT";
break;
case VMBOOTDEV_NET:
/* XXX not yet */
bootidx = pci_find_first_device(PCI_PRODUCT_VIRTIO_NETWORK);
bootfmt = "HALT";
break;
}
if (bootidx > -1) {
snprintf(bootorder, sizeof(bootorder), bootfmt, bootidx);
log_debug("%s: bootorder: %s", __func__, bootorder);
fw_cfg_add_file("bootorder", bootorder, strlen(bootorder) + 1);
}
}
int
fw_cfg_dump(int fd)
{
log_debug("%s: sending fw_cfg state", __func__);
if (atomicio(vwrite, fd, &fw_cfg_dma_addr,
sizeof(fw_cfg_dma_addr)) != sizeof(fw_cfg_dma_addr)) {
log_warnx("%s: error writing fw_cfg to fd", __func__);
return -1;
}
if (atomicio(vwrite, fd, &fw_cfg_state.offset,
sizeof(fw_cfg_state.offset)) != sizeof(fw_cfg_state.offset)) {
log_warnx("%s: error writing fw_cfg to fd", __func__);
return -1;
}
if (atomicio(vwrite, fd, &fw_cfg_state.size,
sizeof(fw_cfg_state.size)) != sizeof(fw_cfg_state.size)) {
log_warnx("%s: error writing fw_cfg to fd", __func__);
return -1;
}
if (fw_cfg_state.size != 0)
if (atomicio(vwrite, fd, fw_cfg_state.data,
fw_cfg_state.size) != fw_cfg_state.size) {
log_warnx("%s: error writing fw_cfg to fd", __func__);
return (-1);
}
return 0;
}
int
fw_cfg_restore(int fd)
{
log_debug("%s: receiving fw_cfg state", __func__);
if (atomicio(read, fd, &fw_cfg_dma_addr,
sizeof(fw_cfg_dma_addr)) != sizeof(fw_cfg_dma_addr)) {
log_warnx("%s: error reading fw_cfg from fd", __func__);
return -1;
}
if (atomicio(read, fd, &fw_cfg_state.offset,
sizeof(fw_cfg_state.offset)) != sizeof(fw_cfg_state.offset)) {
log_warnx("%s: error reading fw_cfg from fd", __func__);
return -1;
}
if (atomicio(read, fd, &fw_cfg_state.size,
sizeof(fw_cfg_state.size)) != sizeof(fw_cfg_state.size)) {
log_warnx("%s: error reading fw_cfg from fd", __func__);
return -1;
}
fw_cfg_state.data = NULL;
if (fw_cfg_state.size != 0) {
if ((fw_cfg_state.data = malloc(fw_cfg_state.size)) == NULL)
fatal("%s", __func__);
if (atomicio(read, fd, fw_cfg_state.data,
fw_cfg_state.size) != fw_cfg_state.size) {
log_warnx("%s: error reading fw_cfg from fd", __func__);
return -1;
}
}
return 0;
}
static void
fw_cfg_reset_state(void)
{
free(fw_cfg_state.data);
fw_cfg_state.offset = 0;
fw_cfg_state.size = 0;
fw_cfg_state.data = NULL;
}
static void
fw_cfg_set_state(void *data, size_t len)
{
if ((fw_cfg_state.data = malloc(len)) == NULL) {
log_warn("%s", __func__);
return;
}
memcpy(fw_cfg_state.data, data, len);
fw_cfg_state.size = len;
fw_cfg_state.offset = 0;
}
static void
fw_cfg_select(uint16_t selector)
{
uint16_t one = 1;
uint32_t id = htole32(0x3);
fw_cfg_reset_state();
switch (selector) {
case FW_CFG_SIGNATURE:
fw_cfg_set_state("QEMU", 4);
break;
case FW_CFG_ID:
fw_cfg_set_state(&id, sizeof(id));
break;
case FW_CFG_NOGRAPHIC:
fw_cfg_set_state(&one, sizeof(one));
break;
case FW_CFG_FILE_DIR:
fw_cfg_file_dir();
break;
default:
if (!fw_cfg_select_file(selector))
log_debug("%s: unhandled selector %x",
__func__, selector);
break;
}
}
static void
fw_cfg_handle_dma(struct fw_cfg_dma_access *fw)
{
uint32_t len = 0, control = fw->control;
fw->control = 0;
if (control & FW_CFG_DMA_SELECT) {
uint16_t selector = control >> 16;
log_debug("%s: selector 0x%04x", __func__, selector);
fw_cfg_select(selector);
}
/* calculate correct length of operation */
if (fw_cfg_state.offset < fw_cfg_state.size)
len = fw_cfg_state.size - fw_cfg_state.offset;
if (len > fw->length)
len = fw->length;
if (control & FW_CFG_DMA_WRITE) {
fw->control |= FW_CFG_DMA_ERROR;
} else if (control & FW_CFG_DMA_READ) {
if (write_mem(fw->address,
fw_cfg_state.data + fw_cfg_state.offset, len)) {
log_warnx("%s: write_mem error", __func__);
fw->control |= FW_CFG_DMA_ERROR;
}
/* clear rest of buffer */
if (len < fw->length)
if (write_mem(fw->address + len, NULL,
fw->length - len)) {
log_warnx("%s: write_mem error", __func__);
fw->control |= FW_CFG_DMA_ERROR;
}
}
fw_cfg_state.offset += len;
if (fw_cfg_state.offset == fw_cfg_state.size)
fw_cfg_reset_state();
}
uint8_t
vcpu_exit_fw_cfg(struct vm_run_params *vrp)
{
uint32_t data = 0;
struct vm_exit *vei = vrp->vrp_exit;
get_input_data(vei, &data);
switch (vei->vei.vei_port) {
case FW_CFG_IO_SELECT:
if (vei->vei.vei_dir == VEI_DIR_IN) {
log_warnx("%s: fw_cfg: read from selector port "
"unsupported", __progname);
set_return_data(vei, 0);
break;
}
log_debug("%s: selector 0x%04x", __func__, data);
fw_cfg_select(data);
break;
case FW_CFG_IO_DATA:
if (vei->vei.vei_dir == VEI_DIR_OUT) {
log_debug("%s: fw_cfg: discarding data written to "
"data port", __progname);
break;
}
/* fw_cfg only defines 1-byte reads via IO port */
if (fw_cfg_state.offset < fw_cfg_state.size) {
set_return_data(vei,
fw_cfg_state.data[fw_cfg_state.offset++]);
if (fw_cfg_state.offset == fw_cfg_state.size)
fw_cfg_reset_state();
} else
set_return_data(vei, 0);
break;
}
return 0xFF;
}
uint8_t
vcpu_exit_fw_cfg_dma(struct vm_run_params *vrp)
{
struct fw_cfg_dma_access fw_dma;
uint32_t data = 0;
struct vm_exit *vei = vrp->vrp_exit;
if (vei->vei.vei_size != 4) {
log_debug("%s: fw_cfg_dma: discarding data written to "
"dma addr", __progname);
if (vei->vei.vei_dir == VEI_DIR_OUT)
fw_cfg_dma_addr = 0;
return 0xFF;
}
if (vei->vei.vei_dir == VEI_DIR_OUT) {
get_input_data(vei, &data);
switch (vei->vei.vei_port) {
case FW_CFG_IO_DMA_ADDR_HIGH:
fw_cfg_dma_addr = (uint64_t)be32toh(data) << 32;
break;
case FW_CFG_IO_DMA_ADDR_LOW:
fw_cfg_dma_addr |= be32toh(data);
/* writing least significant half triggers operation */
if (read_mem(fw_cfg_dma_addr, &fw_dma, sizeof(fw_dma)))
break;
/* adjust byteorder */
fw_dma.control = be32toh(fw_dma.control);
fw_dma.length = be32toh(fw_dma.length);
fw_dma.address = be64toh(fw_dma.address);
fw_cfg_handle_dma(&fw_dma);
/* just write control byte back */
data = be32toh(fw_dma.control);
if (write_mem(fw_cfg_dma_addr, &data, sizeof(data)))
break;
/* done, reset base address */
fw_cfg_dma_addr = 0;
break;
}
} else {
uint64_t sig = htobe64(FW_CFG_DMA_SIGNATURE);
switch (vei->vei.vei_port) {
case FW_CFG_IO_DMA_ADDR_HIGH:
set_return_data(vei, sig >> 32);
break;
case FW_CFG_IO_DMA_ADDR_LOW:
set_return_data(vei, sig & 0xffffffff);
break;
}
}
return 0xFF;
}
static uint16_t file_id = FW_CFG_FILE_FIRST;
struct fw_cfg_file_entry {
TAILQ_ENTRY(fw_cfg_file_entry) entry;
struct fw_cfg_file file;
void *data;
};
TAILQ_HEAD(, fw_cfg_file_entry) fw_cfg_files =
TAILQ_HEAD_INITIALIZER(fw_cfg_files);
static struct fw_cfg_file_entry *
fw_cfg_lookup_file(const char *name)
{
struct fw_cfg_file_entry *f;
TAILQ_FOREACH(f, &fw_cfg_files, entry) {
if (strcmp(name, f->file.name) == 0)
return f;
}
return NULL;
}
void
fw_cfg_add_file(const char *name, const void *data, size_t len)
{
struct fw_cfg_file_entry *f;
if (fw_cfg_lookup_file(name))
fatalx("%s: fw_cfg: file %s exists", __progname, name);
if ((f = calloc(1, sizeof(*f))) == NULL)
fatal("%s", __func__);
if ((f->data = malloc(len)) == NULL)
fatal("%s", __func__);
if (strlcpy(f->file.name, name, sizeof(f->file.name)) >=
sizeof(f->file.name))
fatalx("%s: fw_cfg: file name too long", __progname);
f->file.size = htobe32(len);
f->file.selector = htobe16(file_id++);
memcpy(f->data, data, len);
TAILQ_INSERT_TAIL(&fw_cfg_files, f, entry);
}
static int
fw_cfg_select_file(uint16_t id)
{
struct fw_cfg_file_entry *f;
id = htobe16(id);
TAILQ_FOREACH(f, &fw_cfg_files, entry)
if (f->file.selector == id) {
size_t size = be32toh(f->file.size);
fw_cfg_set_state(f->data, size);
log_debug("%s: accessing file %s", __func__,
f->file.name);
return 1;
}
return 0;
}
static void
fw_cfg_file_dir(void)
{
struct fw_cfg_file_entry *f;
struct fw_cfg_file *fp;
uint32_t count = 0;
uint32_t *data;
size_t size;
TAILQ_FOREACH(f, &fw_cfg_files, entry)
count++;
size = sizeof(count) + count * sizeof(struct fw_cfg_file);
if ((data = malloc(size)) == NULL)
fatal("%s", __func__);
*data = htobe32(count);
fp = (struct fw_cfg_file *)(data + 1);
log_debug("%s: file directory with %d files", __func__, count);
TAILQ_FOREACH(f, &fw_cfg_files, entry) {
log_debug(" %6dB %04x %s", be32toh(f->file.size),
be16toh(f->file.selector), f->file.name);
memcpy(fp, &f->file, sizeof(f->file));
fp++;
}
/* XXX should sort by name but SeaBIOS does not care */
fw_cfg_set_state(data, size);
}