diff options
Diffstat (limited to 'hw')
| -rw-r--r-- | hw/core/qdev-properties-system.c | 5 | ||||
| -rw-r--r-- | hw/display/Makefile.objs | 2 | ||||
| -rw-r--r-- | hw/display/vga-pci.c | 97 | ||||
| -rw-r--r-- | hw/display/virtio-gpu.c | 918 | ||||
| -rw-r--r-- | hw/net/pcnet.c | 8 | ||||
| -rw-r--r-- | hw/nvram/fw_cfg.c | 55 | ||||
| -rw-r--r-- | hw/ppc/mac_newworld.c | 2 | ||||
| -rw-r--r-- | hw/ppc/mac_oldworld.c | 2 | ||||
| -rw-r--r-- | hw/s390x/virtio-ccw.c | 60 | ||||
| -rw-r--r-- | hw/s390x/virtio-ccw.h | 18 | ||||
| -rw-r--r-- | hw/sparc/sun4m.c | 2 | ||||
| -rw-r--r-- | hw/sparc64/sun4u.c | 2 | ||||
| -rw-r--r-- | hw/vfio/Makefile.objs | 2 | ||||
| -rw-r--r-- | hw/vfio/calxeda-xgmac.c | 55 | ||||
| -rw-r--r-- | hw/vfio/platform.c | 615 |
15 files changed, 1750 insertions, 93 deletions
diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c index c413226a97..0309fe5767 100644 --- a/hw/core/qdev-properties-system.c +++ b/hw/core/qdev-properties-system.c @@ -389,7 +389,7 @@ void qdev_set_nic_properties(DeviceState *dev, NICInfo *nd) nd->instantiated = 1; } -static int qdev_add_one_global(QemuOpts *opts, void *opaque) +static int qdev_add_one_global(void *opaque, QemuOpts *opts, Error **errp) { GlobalProperty *g; @@ -404,5 +404,6 @@ static int qdev_add_one_global(QemuOpts *opts, void *opaque) void qemu_add_globals(void) { - qemu_opts_foreach(qemu_find_opts("global"), qdev_add_one_global, NULL, 0); + qemu_opts_foreach(qemu_find_opts("global"), + qdev_add_one_global, NULL, NULL); } diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs index 3ea106d9f3..61c80f319d 100644 --- a/hw/display/Makefile.objs +++ b/hw/display/Makefile.objs @@ -34,3 +34,5 @@ obj-$(CONFIG_CG3) += cg3.o obj-$(CONFIG_VGA) += vga.o common-obj-$(CONFIG_QXL) += qxl.o qxl-logger.o qxl-render.o + +obj-$(CONFIG_VIRTIO) += virtio-gpu.o diff --git a/hw/display/vga-pci.c b/hw/display/vga-pci.c index ff5dfb2c23..0ed44c7831 100644 --- a/hw/display/vga-pci.c +++ b/hw/display/vga-pci.c @@ -54,9 +54,7 @@ typedef struct PCIVGAState { VGACommonState vga; uint32_t flags; MemoryRegion mmio; - MemoryRegion ioport; - MemoryRegion bochs; - MemoryRegion qext; + MemoryRegion mrs[3]; } PCIVGAState; #define TYPE_PCI_VGA "pci-vga" @@ -76,16 +74,16 @@ static const VMStateDescription vmstate_vga_pci = { static uint64_t pci_vga_ioport_read(void *ptr, hwaddr addr, unsigned size) { - PCIVGAState *d = ptr; + VGACommonState *s = ptr; uint64_t ret = 0; switch (size) { case 1: - ret = vga_ioport_read(&d->vga, addr); + ret = vga_ioport_read(s, addr + 0x3c0); break; case 2: - ret = vga_ioport_read(&d->vga, addr); - ret |= vga_ioport_read(&d->vga, addr+1) << 8; + ret = vga_ioport_read(s, addr + 0x3c0); + ret |= vga_ioport_read(s, addr + 0x3c1) << 8; break; } return ret; @@ -94,11 +92,11 @@ static uint64_t pci_vga_ioport_read(void *ptr, hwaddr addr, static void pci_vga_ioport_write(void *ptr, hwaddr addr, uint64_t val, unsigned size) { - PCIVGAState *d = ptr; + VGACommonState *s = ptr; switch (size) { case 1: - vga_ioport_write(&d->vga, addr + 0x3c0, val); + vga_ioport_write(s, addr + 0x3c0, val); break; case 2: /* @@ -106,8 +104,8 @@ static void pci_vga_ioport_write(void *ptr, hwaddr addr, * indexed registers with a single word write because the * index byte is updated first. */ - vga_ioport_write(&d->vga, addr + 0x3c0, val & 0xff); - vga_ioport_write(&d->vga, addr + 0x3c1, (val >> 8) & 0xff); + vga_ioport_write(s, addr + 0x3c0, val & 0xff); + vga_ioport_write(s, addr + 0x3c1, (val >> 8) & 0xff); break; } } @@ -125,21 +123,21 @@ static const MemoryRegionOps pci_vga_ioport_ops = { static uint64_t pci_vga_bochs_read(void *ptr, hwaddr addr, unsigned size) { - PCIVGAState *d = ptr; + VGACommonState *s = ptr; int index = addr >> 1; - vbe_ioport_write_index(&d->vga, 0, index); - return vbe_ioport_read_data(&d->vga, 0); + vbe_ioport_write_index(s, 0, index); + return vbe_ioport_read_data(s, 0); } static void pci_vga_bochs_write(void *ptr, hwaddr addr, uint64_t val, unsigned size) { - PCIVGAState *d = ptr; + VGACommonState *s = ptr; int index = addr >> 1; - vbe_ioport_write_index(&d->vga, 0, index); - vbe_ioport_write_data(&d->vga, 0, val); + vbe_ioport_write_index(s, 0, index); + vbe_ioport_write_data(s, 0, val); } static const MemoryRegionOps pci_vga_bochs_ops = { @@ -154,13 +152,13 @@ static const MemoryRegionOps pci_vga_bochs_ops = { static uint64_t pci_vga_qext_read(void *ptr, hwaddr addr, unsigned size) { - PCIVGAState *d = ptr; + VGACommonState *s = ptr; switch (addr) { case PCI_VGA_QEXT_REG_SIZE: return PCI_VGA_QEXT_SIZE; case PCI_VGA_QEXT_REG_BYTEORDER: - return d->vga.big_endian_fb ? + return s->big_endian_fb ? PCI_VGA_QEXT_BIG_ENDIAN : PCI_VGA_QEXT_LITTLE_ENDIAN; default: return 0; @@ -170,15 +168,15 @@ static uint64_t pci_vga_qext_read(void *ptr, hwaddr addr, unsigned size) static void pci_vga_qext_write(void *ptr, hwaddr addr, uint64_t val, unsigned size) { - PCIVGAState *d = ptr; + VGACommonState *s = ptr; switch (addr) { case PCI_VGA_QEXT_REG_BYTEORDER: if (val == PCI_VGA_QEXT_BIG_ENDIAN) { - d->vga.big_endian_fb = true; + s->big_endian_fb = true; } if (val == PCI_VGA_QEXT_LITTLE_ENDIAN) { - d->vga.big_endian_fb = false; + s->big_endian_fb = false; } break; } @@ -206,10 +204,34 @@ static const MemoryRegionOps pci_vga_qext_ops = { .endianness = DEVICE_LITTLE_ENDIAN, }; +static void pci_std_vga_mmio_region_init(VGACommonState *s, + MemoryRegion *parent, + MemoryRegion *subs, + bool qext) +{ + memory_region_init_io(&subs[0], NULL, &pci_vga_ioport_ops, s, + "vga ioports remapped", PCI_VGA_IOPORT_SIZE); + memory_region_add_subregion(parent, PCI_VGA_IOPORT_OFFSET, + &subs[0]); + + memory_region_init_io(&subs[1], NULL, &pci_vga_bochs_ops, s, + "bochs dispi interface", PCI_VGA_BOCHS_SIZE); + memory_region_add_subregion(parent, PCI_VGA_BOCHS_OFFSET, + &subs[1]); + + if (qext) { + memory_region_init_io(&subs[2], NULL, &pci_vga_qext_ops, s, + "qemu extended regs", PCI_VGA_QEXT_SIZE); + memory_region_add_subregion(parent, PCI_VGA_QEXT_OFFSET, + &subs[2]); + } +} + static void pci_std_vga_realize(PCIDevice *dev, Error **errp) { PCIVGAState *d = PCI_VGA(dev); VGACommonState *s = &d->vga; + bool qext = false; /* vga + console init */ vga_common_init(s, OBJECT(dev), true); @@ -224,23 +246,12 @@ static void pci_std_vga_realize(PCIDevice *dev, Error **errp) /* mmio bar for vga register access */ if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_MMIO)) { memory_region_init(&d->mmio, NULL, "vga.mmio", 4096); - memory_region_init_io(&d->ioport, NULL, &pci_vga_ioport_ops, d, - "vga ioports remapped", PCI_VGA_IOPORT_SIZE); - memory_region_init_io(&d->bochs, NULL, &pci_vga_bochs_ops, d, - "bochs dispi interface", PCI_VGA_BOCHS_SIZE); - - memory_region_add_subregion(&d->mmio, PCI_VGA_IOPORT_OFFSET, - &d->ioport); - memory_region_add_subregion(&d->mmio, PCI_VGA_BOCHS_OFFSET, - &d->bochs); if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_QEXT)) { - memory_region_init_io(&d->qext, NULL, &pci_vga_qext_ops, d, - "qemu extended regs", PCI_VGA_QEXT_SIZE); - memory_region_add_subregion(&d->mmio, PCI_VGA_QEXT_OFFSET, - &d->qext); + qext = true; pci_set_byte(&d->dev.config[PCI_REVISION_ID], 2); } + pci_std_vga_mmio_region_init(s, &d->mmio, d->mrs, qext); pci_register_bar(&d->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio); } @@ -262,6 +273,7 @@ static void pci_secondary_vga_realize(PCIDevice *dev, Error **errp) { PCIVGAState *d = PCI_VGA(dev); VGACommonState *s = &d->vga; + bool qext = false; /* vga + console init */ vga_common_init(s, OBJECT(dev), false); @@ -269,23 +281,12 @@ static void pci_secondary_vga_realize(PCIDevice *dev, Error **errp) /* mmio bar */ memory_region_init(&d->mmio, OBJECT(dev), "vga.mmio", 4096); - memory_region_init_io(&d->ioport, OBJECT(dev), &pci_vga_ioport_ops, d, - "vga ioports remapped", PCI_VGA_IOPORT_SIZE); - memory_region_init_io(&d->bochs, OBJECT(dev), &pci_vga_bochs_ops, d, - "bochs dispi interface", PCI_VGA_BOCHS_SIZE); - - memory_region_add_subregion(&d->mmio, PCI_VGA_IOPORT_OFFSET, - &d->ioport); - memory_region_add_subregion(&d->mmio, PCI_VGA_BOCHS_OFFSET, - &d->bochs); if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_QEXT)) { - memory_region_init_io(&d->qext, NULL, &pci_vga_qext_ops, d, - "qemu extended regs", PCI_VGA_QEXT_SIZE); - memory_region_add_subregion(&d->mmio, PCI_VGA_QEXT_OFFSET, - &d->qext); + qext = true; pci_set_byte(&d->dev.config[PCI_REVISION_ID], 2); } + pci_std_vga_mmio_region_init(s, &d->mmio, d->mrs, qext); pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram); pci_register_bar(&d->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio); diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c new file mode 100644 index 0000000000..4b10ca1ebb --- /dev/null +++ b/hw/display/virtio-gpu.c @@ -0,0 +1,918 @@ +/* + * Virtio GPU Device + * + * Copyright Red Hat, Inc. 2013-2014 + * + * Authors: + * Dave Airlie <airlied@redhat.com> + * Gerd Hoffmann <kraxel@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. + * See the COPYING file in the top-level directory. + */ + +#include "qemu-common.h" +#include "qemu/iov.h" +#include "ui/console.h" +#include "trace.h" +#include "hw/virtio/virtio.h" +#include "hw/virtio/virtio-gpu.h" +#include "hw/virtio/virtio-bus.h" + +static struct virtio_gpu_simple_resource* +virtio_gpu_find_resource(VirtIOGPU *g, uint32_t resource_id); + +static void update_cursor_data_simple(VirtIOGPU *g, + struct virtio_gpu_scanout *s, + uint32_t resource_id) +{ + struct virtio_gpu_simple_resource *res; + uint32_t pixels; + + res = virtio_gpu_find_resource(g, resource_id); + if (!res) { + return; + } + + if (pixman_image_get_width(res->image) != s->current_cursor->width || + pixman_image_get_height(res->image) != s->current_cursor->height) { + return; + } + + pixels = s->current_cursor->width * s->current_cursor->height; + memcpy(s->current_cursor->data, + pixman_image_get_data(res->image), + pixels * sizeof(uint32_t)); +} + +static void update_cursor(VirtIOGPU *g, struct virtio_gpu_update_cursor *cursor) +{ + struct virtio_gpu_scanout *s; + + if (cursor->pos.scanout_id >= g->conf.max_outputs) { + return; + } + s = &g->scanout[cursor->pos.scanout_id]; + + if (cursor->hdr.type != VIRTIO_GPU_CMD_MOVE_CURSOR) { + if (!s->current_cursor) { + s->current_cursor = cursor_alloc(64, 64); + } + + s->current_cursor->hot_x = cursor->hot_x; + s->current_cursor->hot_y = cursor->hot_y; + + if (cursor->resource_id > 0) { + update_cursor_data_simple(g, s, cursor->resource_id); + } + dpy_cursor_define(s->con, s->current_cursor); + } + dpy_mouse_set(s->con, cursor->pos.x, cursor->pos.y, + cursor->resource_id ? 1 : 0); +} + +static void virtio_gpu_get_config(VirtIODevice *vdev, uint8_t *config) +{ + VirtIOGPU *g = VIRTIO_GPU(vdev); + memcpy(config, &g->virtio_config, sizeof(g->virtio_config)); +} + +static void virtio_gpu_set_config(VirtIODevice *vdev, const uint8_t *config) +{ + VirtIOGPU *g = VIRTIO_GPU(vdev); + struct virtio_gpu_config vgconfig; + + memcpy(&vgconfig, config, sizeof(g->virtio_config)); + + if (vgconfig.events_clear) { + g->virtio_config.events_read &= ~vgconfig.events_clear; + } +} + +static uint64_t virtio_gpu_get_features(VirtIODevice *vdev, uint64_t features) +{ + return features; +} + +static void virtio_gpu_notify_event(VirtIOGPU *g, uint32_t event_type) +{ + g->virtio_config.events_read |= event_type; + virtio_notify_config(&g->parent_obj); +} + +static struct virtio_gpu_simple_resource * +virtio_gpu_find_resource(VirtIOGPU *g, uint32_t resource_id) +{ + struct virtio_gpu_simple_resource *res; + + QTAILQ_FOREACH(res, &g->reslist, next) { + if (res->resource_id == resource_id) { + return res; + } + } + return NULL; +} + +void virtio_gpu_ctrl_response(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd, + struct virtio_gpu_ctrl_hdr *resp, + size_t resp_len) +{ + size_t s; + + if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_FENCE) { + resp->flags |= VIRTIO_GPU_FLAG_FENCE; + resp->fence_id = cmd->cmd_hdr.fence_id; + resp->ctx_id = cmd->cmd_hdr.ctx_id; + } + s = iov_from_buf(cmd->elem.in_sg, cmd->elem.in_num, 0, resp, resp_len); + if (s != resp_len) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: response size incorrect %zu vs %zu\n", + __func__, s, resp_len); + } + virtqueue_push(cmd->vq, &cmd->elem, s); + virtio_notify(VIRTIO_DEVICE(g), cmd->vq); + cmd->finished = true; +} + +void virtio_gpu_ctrl_response_nodata(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd, + enum virtio_gpu_ctrl_type type) +{ + struct virtio_gpu_ctrl_hdr resp; + + memset(&resp, 0, sizeof(resp)); + resp.type = type; + virtio_gpu_ctrl_response(g, cmd, &resp, sizeof(resp)); +} + +static void +virtio_gpu_fill_display_info(VirtIOGPU *g, + struct virtio_gpu_resp_display_info *dpy_info) +{ + int i; + + for (i = 0; i < g->conf.max_outputs; i++) { + if (g->enabled_output_bitmask & (1 << i)) { + dpy_info->pmodes[i].enabled = 1; + dpy_info->pmodes[i].r.width = g->req_state[i].width; + dpy_info->pmodes[i].r.height = g->req_state[i].height; + } + } +} + +void virtio_gpu_get_display_info(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) +{ + struct virtio_gpu_resp_display_info display_info; + + trace_virtio_gpu_cmd_get_display_info(); + memset(&display_info, 0, sizeof(display_info)); + display_info.hdr.type = VIRTIO_GPU_RESP_OK_DISPLAY_INFO; + virtio_gpu_fill_display_info(g, &display_info); + virtio_gpu_ctrl_response(g, cmd, &display_info.hdr, + sizeof(display_info)); +} + +static pixman_format_code_t get_pixman_format(uint32_t virtio_gpu_format) +{ + switch (virtio_gpu_format) { +#ifdef HOST_WORDS_BIGENDIAN + case VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM: + return PIXMAN_b8g8r8x8; + case VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: + return PIXMAN_b8g8r8a8; + case VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM: + return PIXMAN_x8r8g8b8; + case VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM: + return PIXMAN_a8r8g8b8; + case VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM: + return PIXMAN_r8g8b8x8; + case VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM: + return PIXMAN_r8g8b8a8; + case VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM: + return PIXMAN_x8b8g8r8; + case VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM: + return PIXMAN_a8b8g8r8; +#else + case VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM: + return PIXMAN_x8r8g8b8; + case VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: + return PIXMAN_a8r8g8b8; + case VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM: + return PIXMAN_b8g8r8x8; + case VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM: + return PIXMAN_b8g8r8a8; + case VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM: + return PIXMAN_x8b8g8r8; + case VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM: + return PIXMAN_a8b8g8r8; + case VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM: + return PIXMAN_r8g8b8x8; + case VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM: + return PIXMAN_r8g8b8a8; +#endif + default: + return 0; + } +} + +static void virtio_gpu_resource_create_2d(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) +{ + pixman_format_code_t pformat; + struct virtio_gpu_simple_resource *res; + struct virtio_gpu_resource_create_2d c2d; + + VIRTIO_GPU_FILL_CMD(c2d); + trace_virtio_gpu_cmd_res_create_2d(c2d.resource_id, c2d.format, + c2d.width, c2d.height); + + if (c2d.resource_id == 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: resource id 0 is not allowed\n", + __func__); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + return; + } + + res = virtio_gpu_find_resource(g, c2d.resource_id); + if (res) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: resource already exists %d\n", + __func__, c2d.resource_id); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + return; + } + + res = g_new0(struct virtio_gpu_simple_resource, 1); + + res->width = c2d.width; + res->height = c2d.height; + res->format = c2d.format; + res->resource_id = c2d.resource_id; + + pformat = get_pixman_format(c2d.format); + if (!pformat) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: host couldn't handle guest format %d\n", + __func__, c2d.format); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; + } + res->image = pixman_image_create_bits(pformat, + c2d.width, + c2d.height, + NULL, 0); + + if (!res->image) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: resource creation failed %d %d %d\n", + __func__, c2d.resource_id, c2d.width, c2d.height); + g_free(res); + cmd->error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY; + return; + } + + QTAILQ_INSERT_HEAD(&g->reslist, res, next); +} + +static void virtio_gpu_resource_destroy(VirtIOGPU *g, + struct virtio_gpu_simple_resource *res) +{ + pixman_image_unref(res->image); + QTAILQ_REMOVE(&g->reslist, res, next); + g_free(res); +} + +static void virtio_gpu_resource_unref(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) +{ + struct virtio_gpu_simple_resource *res; + struct virtio_gpu_resource_unref unref; + + VIRTIO_GPU_FILL_CMD(unref); + trace_virtio_gpu_cmd_res_unref(unref.resource_id); + + res = virtio_gpu_find_resource(g, unref.resource_id); + if (!res) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n", + __func__, unref.resource_id); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + return; + } + virtio_gpu_resource_destroy(g, res); +} + +static void virtio_gpu_transfer_to_host_2d(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) +{ + struct virtio_gpu_simple_resource *res; + int h; + uint32_t src_offset, dst_offset, stride; + int bpp; + pixman_format_code_t format; + struct virtio_gpu_transfer_to_host_2d t2d; + + VIRTIO_GPU_FILL_CMD(t2d); + trace_virtio_gpu_cmd_res_xfer_toh_2d(t2d.resource_id); + + res = virtio_gpu_find_resource(g, t2d.resource_id); + if (!res || !res->iov) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n", + __func__, t2d.resource_id); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + return; + } + + if (t2d.r.x > res->width || + t2d.r.y > res->height || + t2d.r.width > res->width || + t2d.r.height > res->height || + t2d.r.x + t2d.r.width > res->width || + t2d.r.y + t2d.r.height > res->height) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: transfer bounds outside resource" + " bounds for resource %d: %d %d %d %d vs %d %d\n", + __func__, t2d.resource_id, t2d.r.x, t2d.r.y, + t2d.r.width, t2d.r.height, res->width, res->height); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; + } + + format = pixman_image_get_format(res->image); + bpp = (PIXMAN_FORMAT_BPP(format) + 7) / 8; + stride = pixman_image_get_stride(res->image); + + if (t2d.offset || t2d.r.x || t2d.r.y || + t2d.r.width != pixman_image_get_width(res->image)) { + void *img_data = pixman_image_get_data(res->image); + for (h = 0; h < t2d.r.height; h++) { + src_offset = t2d.offset + stride * h; + dst_offset = (t2d.r.y + h) * stride + (t2d.r.x * bpp); + + iov_to_buf(res->iov, res->iov_cnt, src_offset, + (uint8_t *)img_data + + dst_offset, t2d.r.width * bpp); + } + } else { + iov_to_buf(res->iov, res->iov_cnt, 0, + pixman_image_get_data(res->image), + pixman_image_get_stride(res->image) + * pixman_image_get_height(res->image)); + } +} + +static void virtio_gpu_resource_flush(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) +{ + struct virtio_gpu_simple_resource *res; + struct virtio_gpu_resource_flush rf; + pixman_region16_t flush_region; + int i; + + VIRTIO_GPU_FILL_CMD(rf); + trace_virtio_gpu_cmd_res_flush(rf.resource_id, + rf.r.width, rf.r.height, rf.r.x, rf.r.y); + + res = virtio_gpu_find_resource(g, rf.resource_id); + if (!res) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n", + __func__, rf.resource_id); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + return; + } + + if (rf.r.x > res->width || + rf.r.y > res->height || + rf.r.width > res->width || + rf.r.height > res->height || + rf.r.x + rf.r.width > res->width || + rf.r.y + rf.r.height > res->height) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: flush bounds outside resource" + " bounds for resource %d: %d %d %d %d vs %d %d\n", + __func__, rf.resource_id, rf.r.x, rf.r.y, + rf.r.width, rf.r.height, res->width, res->height); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; + } + + pixman_region_init_rect(&flush_region, + rf.r.x, rf.r.y, rf.r.width, rf.r.height); + for (i = 0; i < VIRTIO_GPU_MAX_SCANOUT; i++) { + struct virtio_gpu_scanout *scanout; + pixman_region16_t region, finalregion; + pixman_box16_t *extents; + + if (!(res->scanout_bitmask & (1 << i))) { + continue; + } + scanout = &g->scanout[i]; + + pixman_region_init(&finalregion); + pixman_region_init_rect(®ion, scanout->x, scanout->y, + scanout->width, scanout->height); + + pixman_region_intersect(&finalregion, &flush_region, ®ion); + pixman_region_translate(&finalregion, -scanout->x, -scanout->y); + extents = pixman_region_extents(&finalregion); + /* work out the area we need to update for each console */ + dpy_gfx_update(g->scanout[i].con, + extents->x1, extents->y1, + extents->x2 - extents->x1, + extents->y2 - extents->y1); + + pixman_region_fini(®ion); + pixman_region_fini(&finalregion); + } + pixman_region_fini(&flush_region); +} + +static void virtio_gpu_set_scanout(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) +{ + struct virtio_gpu_simple_resource *res; + struct virtio_gpu_scanout *scanout; + pixman_format_code_t format; + uint32_t offset; + int bpp; + struct virtio_gpu_set_scanout ss; + + VIRTIO_GPU_FILL_CMD(ss); + trace_virtio_gpu_cmd_set_scanout(ss.scanout_id, ss.resource_id, + ss.r.width, ss.r.height, ss.r.x, ss.r.y); + + g->enable = 1; + if (ss.resource_id == 0) { + scanout = &g->scanout[ss.scanout_id]; + if (scanout->resource_id) { + res = virtio_gpu_find_resource(g, scanout->resource_id); + if (res) { + res->scanout_bitmask &= ~(1 << ss.scanout_id); + } + } + if (ss.scanout_id == 0 || + ss.scanout_id >= g->conf.max_outputs) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: illegal scanout id specified %d", + __func__, ss.scanout_id); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID; + return; + } + dpy_gfx_replace_surface(g->scanout[ss.scanout_id].con, NULL); + scanout->ds = NULL; + scanout->width = 0; + scanout->height = 0; + return; + } + + /* create a surface for this scanout */ + if (ss.scanout_id >= VIRTIO_GPU_MAX_SCANOUT || + ss.scanout_id >= g->conf.max_outputs) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout id specified %d", + __func__, ss.scanout_id); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID; + return; + } + + res = virtio_gpu_find_resource(g, ss.resource_id); + if (!res) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n", + __func__, ss.resource_id); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + return; + } + + if (ss.r.x > res->width || + ss.r.y > res->height || + ss.r.width > res->width || + ss.r.height > res->height || + ss.r.x + ss.r.width > res->width || + ss.r.y + ss.r.height > res->height) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout %d bounds for" + " resource %d, (%d,%d)+%d,%d vs %d %d\n", + __func__, ss.scanout_id, ss.resource_id, ss.r.x, ss.r.y, + ss.r.width, ss.r.height, res->width, res->height); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; + } + + scanout = &g->scanout[ss.scanout_id]; + + format = pixman_image_get_format(res->image); + bpp = (PIXMAN_FORMAT_BPP(format) + 7) / 8; + offset = (ss.r.x * bpp) + ss.r.y * pixman_image_get_stride(res->image); + if (!scanout->ds || surface_data(scanout->ds) + != ((uint8_t *)pixman_image_get_data(res->image) + offset) || + scanout->width != ss.r.width || + scanout->height != ss.r.height) { + /* realloc the surface ptr */ + scanout->ds = qemu_create_displaysurface_from + (ss.r.width, ss.r.height, format, + pixman_image_get_stride(res->image), + (uint8_t *)pixman_image_get_data(res->image) + offset); + if (!scanout->ds) { + cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; + return; + } + dpy_gfx_replace_surface(g->scanout[ss.scanout_id].con, scanout->ds); + } + + res->scanout_bitmask |= (1 << ss.scanout_id); + scanout->resource_id = ss.resource_id; + scanout->x = ss.r.x; + scanout->y = ss.r.y; + scanout->width = ss.r.width; + scanout->height = ss.r.height; +} + +int virtio_gpu_create_mapping_iov(struct virtio_gpu_resource_attach_backing *ab, + struct virtio_gpu_ctrl_command *cmd, + struct iovec **iov) +{ + struct virtio_gpu_mem_entry *ents; + size_t esize, s; + int i; + + if (ab->nr_entries > 16384) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: nr_entries is too big (%d > 1024)\n", + __func__, ab->nr_entries); + return -1; + } + + esize = sizeof(*ents) * ab->nr_entries; + ents = g_malloc(esize); + s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, + sizeof(*ab), ents, esize); + if (s != esize) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: command data size incorrect %zu vs %zu\n", + __func__, s, esize); + g_free(ents); + return -1; + } + + *iov = g_malloc0(sizeof(struct iovec) * ab->nr_entries); + for (i = 0; i < ab->nr_entries; i++) { + hwaddr len = ents[i].length; + (*iov)[i].iov_len = ents[i].length; + (*iov)[i].iov_base = cpu_physical_memory_map(ents[i].addr, &len, 1); + if (!(*iov)[i].iov_base || len != ents[i].length) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: failed to map MMIO memory for" + " resource %d element %d\n", + __func__, ab->resource_id, i); + virtio_gpu_cleanup_mapping_iov(*iov, i); + g_free(ents); + g_free(*iov); + *iov = NULL; + return -1; + } + } + g_free(ents); + return 0; +} + +void virtio_gpu_cleanup_mapping_iov(struct iovec *iov, uint32_t count) +{ + int i; + + for (i = 0; i < count; i++) { + cpu_physical_memory_unmap(iov[i].iov_base, iov[i].iov_len, 1, + iov[i].iov_len); + } +} + +static void virtio_gpu_cleanup_mapping(struct virtio_gpu_simple_resource *res) +{ + virtio_gpu_cleanup_mapping_iov(res->iov, res->iov_cnt); + g_free(res->iov); + res->iov = NULL; + res->iov_cnt = 0; +} + +static void +virtio_gpu_resource_attach_backing(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) +{ + struct virtio_gpu_simple_resource *res; + struct virtio_gpu_resource_attach_backing ab; + int ret; + + VIRTIO_GPU_FILL_CMD(ab); + trace_virtio_gpu_cmd_res_back_attach(ab.resource_id); + + res = virtio_gpu_find_resource(g, ab.resource_id); + if (!res) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n", + __func__, ab.resource_id); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + return; + } + + ret = virtio_gpu_create_mapping_iov(&ab, cmd, &res->iov); + if (ret != 0) { + cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; + return; + } + + res->iov_cnt = ab.nr_entries; +} + +static void +virtio_gpu_resource_detach_backing(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) +{ + struct virtio_gpu_simple_resource *res; + struct virtio_gpu_resource_detach_backing detach; + + VIRTIO_GPU_FILL_CMD(detach); + trace_virtio_gpu_cmd_res_back_detach(detach.resource_id); + + res = virtio_gpu_find_resource(g, detach.resource_id); + if (!res || !res->iov) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n", + __func__, detach.resource_id); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + return; + } + virtio_gpu_cleanup_mapping(res); +} + +static void virtio_gpu_simple_process_cmd(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) +{ + VIRTIO_GPU_FILL_CMD(cmd->cmd_hdr); + + switch (cmd->cmd_hdr.type) { + case VIRTIO_GPU_CMD_GET_DISPLAY_INFO: + virtio_gpu_get_display_info(g, cmd); + break; + case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: + virtio_gpu_resource_create_2d(g, cmd); + break; + case VIRTIO_GPU_CMD_RESOURCE_UNREF: + virtio_gpu_resource_unref(g, cmd); + break; + case VIRTIO_GPU_CMD_RESOURCE_FLUSH: + virtio_gpu_resource_flush(g, cmd); + break; + case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: + virtio_gpu_transfer_to_host_2d(g, cmd); + break; + case VIRTIO_GPU_CMD_SET_SCANOUT: + virtio_gpu_set_scanout(g, cmd); + break; + case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING: + virtio_gpu_resource_attach_backing(g, cmd); + break; + case VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING: + virtio_gpu_resource_detach_backing(g, cmd); + break; + default: + cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; + break; + } + if (!cmd->finished) { + virtio_gpu_ctrl_response_nodata(g, cmd, cmd->error ? cmd->error : + VIRTIO_GPU_RESP_OK_NODATA); + } +} + +static void virtio_gpu_handle_ctrl_cb(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOGPU *g = VIRTIO_GPU(vdev); + qemu_bh_schedule(g->ctrl_bh); +} + +static void virtio_gpu_handle_cursor_cb(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOGPU *g = VIRTIO_GPU(vdev); + qemu_bh_schedule(g->cursor_bh); +} + +static void virtio_gpu_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOGPU *g = VIRTIO_GPU(vdev); + struct virtio_gpu_ctrl_command *cmd; + + if (!virtio_queue_ready(vq)) { + return; + } + + cmd = g_new(struct virtio_gpu_ctrl_command, 1); + while (virtqueue_pop(vq, &cmd->elem)) { + cmd->vq = vq; + cmd->error = 0; + cmd->finished = false; + g->stats.requests++; + + virtio_gpu_simple_process_cmd(g, cmd); + if (!cmd->finished) { + QTAILQ_INSERT_TAIL(&g->fenceq, cmd, next); + g->stats.inflight++; + if (g->stats.max_inflight < g->stats.inflight) { + g->stats.max_inflight = g->stats.inflight; + } + fprintf(stderr, "inflight: %3d (+)\r", g->stats.inflight); + cmd = g_new(struct virtio_gpu_ctrl_command, 1); + } + } + g_free(cmd); +} + +static void virtio_gpu_ctrl_bh(void *opaque) +{ + VirtIOGPU *g = opaque; + virtio_gpu_handle_ctrl(&g->parent_obj, g->ctrl_vq); +} + +static void virtio_gpu_handle_cursor(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOGPU *g = VIRTIO_GPU(vdev); + VirtQueueElement elem; + size_t s; + struct virtio_gpu_update_cursor cursor_info; + + if (!virtio_queue_ready(vq)) { + return; + } + while (virtqueue_pop(vq, &elem)) { + s = iov_to_buf(elem.out_sg, elem.out_num, 0, + &cursor_info, sizeof(cursor_info)); + if (s != sizeof(cursor_info)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: cursor size incorrect %zu vs %zu\n", + __func__, s, sizeof(cursor_info)); + } else { + update_cursor(g, &cursor_info); + } + virtqueue_push(vq, &elem, 0); + virtio_notify(vdev, vq); + } +} + +static void virtio_gpu_cursor_bh(void *opaque) +{ + VirtIOGPU *g = opaque; + virtio_gpu_handle_cursor(&g->parent_obj, g->cursor_vq); +} + +static void virtio_gpu_invalidate_display(void *opaque) +{ +} + +static void virtio_gpu_update_display(void *opaque) +{ +} + +static void virtio_gpu_text_update(void *opaque, console_ch_t *chardata) +{ +} + +static int virtio_gpu_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info) +{ + VirtIOGPU *g = opaque; + + if (idx > g->conf.max_outputs) { + return -1; + } + + g->req_state[idx].x = info->xoff; + g->req_state[idx].y = info->yoff; + g->req_state[idx].width = info->width; + g->req_state[idx].height = info->height; + + if (info->width && info->height) { + g->enabled_output_bitmask |= (1 << idx); + } else { + g->enabled_output_bitmask &= ~(1 << idx); + } + + /* send event to guest */ + virtio_gpu_notify_event(g, VIRTIO_GPU_EVENT_DISPLAY); + return 0; +} + +const GraphicHwOps virtio_gpu_ops = { + .invalidate = virtio_gpu_invalidate_display, + .gfx_update = virtio_gpu_update_display, + .text_update = virtio_gpu_text_update, + .ui_info = virtio_gpu_ui_info, +}; + +static void virtio_gpu_device_realize(DeviceState *qdev, Error **errp) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(qdev); + VirtIOGPU *g = VIRTIO_GPU(qdev); + int i; + + g->config_size = sizeof(struct virtio_gpu_config); + g->virtio_config.num_scanouts = g->conf.max_outputs; + virtio_init(VIRTIO_DEVICE(g), "virtio-gpu", VIRTIO_ID_GPU, + g->config_size); + + g->req_state[0].width = 1024; + g->req_state[0].height = 768; + + g->ctrl_vq = virtio_add_queue(vdev, 64, virtio_gpu_handle_ctrl_cb); + g->cursor_vq = virtio_add_queue(vdev, 16, virtio_gpu_handle_cursor_cb); + + g->ctrl_bh = qemu_bh_new(virtio_gpu_ctrl_bh, g); + g->cursor_bh = qemu_bh_new(virtio_gpu_cursor_bh, g); + QTAILQ_INIT(&g->reslist); + QTAILQ_INIT(&g->fenceq); + + g->enabled_output_bitmask = 1; + g->qdev = qdev; + + for (i = 0; i < g->conf.max_outputs; i++) { + g->scanout[i].con = + graphic_console_init(DEVICE(g), i, &virtio_gpu_ops, g); + if (i > 0) { + dpy_gfx_replace_surface(g->scanout[i].con, NULL); + } + } +} + +static void virtio_gpu_instance_init(Object *obj) +{ +} + +static void virtio_gpu_reset(VirtIODevice *vdev) +{ + VirtIOGPU *g = VIRTIO_GPU(vdev); + struct virtio_gpu_simple_resource *res, *tmp; + int i; + + g->enable = 0; + + QTAILQ_FOREACH_SAFE(res, &g->reslist, next, tmp) { + virtio_gpu_resource_destroy(g, res); + } + for (i = 0; i < g->conf.max_outputs; i++) { +#if 0 + g->req_state[i].x = 0; + g->req_state[i].y = 0; + if (i == 0) { + g->req_state[0].width = 1024; + g->req_state[0].height = 768; + } else { + g->req_state[i].width = 0; + g->req_state[i].height = 0; + } +#endif + g->scanout[i].resource_id = 0; + g->scanout[i].width = 0; + g->scanout[i].height = 0; + g->scanout[i].x = 0; + g->scanout[i].y = 0; + g->scanout[i].ds = NULL; + } + g->enabled_output_bitmask = 1; +} + +static Property virtio_gpu_properties[] = { + DEFINE_VIRTIO_GPU_PROPERTIES(VirtIOGPU, conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtio_gpu_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + + vdc->realize = virtio_gpu_device_realize; + vdc->get_config = virtio_gpu_get_config; + vdc->set_config = virtio_gpu_set_config; + vdc->get_features = virtio_gpu_get_features; + + vdc->reset = virtio_gpu_reset; + + dc->props = virtio_gpu_properties; +} + +static const TypeInfo virtio_gpu_info = { + .name = TYPE_VIRTIO_GPU, + .parent = TYPE_VIRTIO_DEVICE, + .instance_size = sizeof(VirtIOGPU), + .instance_init = virtio_gpu_instance_init, + .class_init = virtio_gpu_class_init, +}; + +static void virtio_register_types(void) +{ + type_register_static(&virtio_gpu_info); +} + +type_init(virtio_register_types) + +QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_ctrl_hdr) != 24); +QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_update_cursor) != 56); +QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_unref) != 32); +QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_create_2d) != 40); +QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_set_scanout) != 48); +QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_flush) != 48); +QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_transfer_to_host_2d) != 56); +QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_mem_entry) != 16); +QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_attach_backing) != 32); +QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_detach_backing) != 32); +QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resp_display_info) != 408); diff --git a/hw/net/pcnet.c b/hw/net/pcnet.c index bdfd38f4ca..68b9981983 100644 --- a/hw/net/pcnet.c +++ b/hw/net/pcnet.c @@ -1241,6 +1241,14 @@ static void pcnet_transmit(PCNetState *s) } bcnt = 4096 - GET_FIELD(tmd.length, TMDL, BCNT); + + /* if multi-tmd packet outsizes s->buffer then skip it silently. + Note: this is not what real hw does */ + if (s->xmit_pos + bcnt > sizeof(s->buffer)) { + s->xmit_pos = -1; + goto txdone; + } + s->phys_mem_read(s->dma_opaque, PHYSADDR(s, tmd.tbadr), s->buffer + s->xmit_pos, bcnt, CSR_BSWP(s)); s->xmit_pos += bcnt; diff --git a/hw/nvram/fw_cfg.c b/hw/nvram/fw_cfg.c index 68eff77983..88481b78c4 100644 --- a/hw/nvram/fw_cfg.c +++ b/hw/nvram/fw_cfg.c @@ -46,7 +46,6 @@ typedef struct FWCfgEntry { uint32_t len; uint8_t *data; void *callback_opaque; - FWCfgCallback callback; FWCfgReadCallback read_callback; } FWCfgEntry; @@ -232,19 +231,7 @@ static void fw_cfg_reboot(FWCfgState *s) static void fw_cfg_write(FWCfgState *s, uint8_t value) { - int arch = !!(s->cur_entry & FW_CFG_ARCH_LOCAL); - FWCfgEntry *e = &s->entries[arch][s->cur_entry & FW_CFG_ENTRY_MASK]; - - trace_fw_cfg_write(s, value); - - if (s->cur_entry & FW_CFG_WRITE_CHANNEL && e->callback && - s->cur_offset < e->len) { - e->data[s->cur_offset++] = value; - if (s->cur_offset == e->len) { - e->callback(e->callback_opaque, e->data); - s->cur_offset = 0; - } - } + /* nothing, write support removed in QEMU v2.4+ */ } static int fw_cfg_select(FWCfgState *s, uint16_t key) @@ -436,6 +423,7 @@ static void fw_cfg_add_bytes_read_callback(FWCfgState *s, uint16_t key, key &= FW_CFG_ENTRY_MASK; assert(key < FW_CFG_MAX_ENTRY && len < UINT32_MAX); + assert(s->entries[arch][key].data == NULL); /* avoid key conflict */ s->entries[arch][key].data = data; s->entries[arch][key].len = (uint32_t)len; @@ -458,7 +446,6 @@ static void *fw_cfg_modify_bytes_read(FWCfgState *s, uint16_t key, s->entries[arch][key].data = data; s->entries[arch][key].len = len; s->entries[arch][key].callback_opaque = NULL; - s->entries[arch][key].callback = NULL; return ptr; } @@ -484,6 +471,16 @@ void fw_cfg_add_i16(FWCfgState *s, uint16_t key, uint16_t value) fw_cfg_add_bytes(s, key, copy, sizeof(value)); } +void fw_cfg_modify_i16(FWCfgState *s, uint16_t key, uint16_t value) +{ + uint16_t *copy, *old; + + copy = g_malloc(sizeof(value)); + *copy = cpu_to_le16(value); + old = fw_cfg_modify_bytes_read(s, key, copy, sizeof(value)); + g_free(old); +} + void fw_cfg_add_i32(FWCfgState *s, uint16_t key, uint32_t value) { uint32_t *copy; @@ -502,23 +499,6 @@ void fw_cfg_add_i64(FWCfgState *s, uint16_t key, uint64_t value) fw_cfg_add_bytes(s, key, copy, sizeof(value)); } -void fw_cfg_add_callback(FWCfgState *s, uint16_t key, FWCfgCallback callback, - void *callback_opaque, void *data, size_t len) -{ - int arch = !!(key & FW_CFG_ARCH_LOCAL); - - assert(key & FW_CFG_WRITE_CHANNEL); - - key &= FW_CFG_ENTRY_MASK; - - assert(key < FW_CFG_MAX_ENTRY && len <= UINT32_MAX); - - s->entries[arch][key].data = data; - s->entries[arch][key].len = (uint32_t)len; - s->entries[arch][key].callback_opaque = callback_opaque; - s->entries[arch][key].callback = callback; -} - void fw_cfg_add_file_callback(FWCfgState *s, const char *filename, FWCfgReadCallback callback, void *callback_opaque, void *data, size_t len) @@ -535,18 +515,19 @@ void fw_cfg_add_file_callback(FWCfgState *s, const char *filename, index = be32_to_cpu(s->files->count); assert(index < FW_CFG_FILE_SLOTS); - fw_cfg_add_bytes_read_callback(s, FW_CFG_FILE_FIRST + index, - callback, callback_opaque, data, len); - pstrcpy(s->files->f[index].name, sizeof(s->files->f[index].name), filename); for (i = 0; i < index; i++) { if (strcmp(s->files->f[index].name, s->files->f[i].name) == 0) { - trace_fw_cfg_add_file_dupe(s, s->files->f[index].name); - return; + error_report("duplicate fw_cfg file name: %s", + s->files->f[index].name); + exit(1); } } + fw_cfg_add_bytes_read_callback(s, FW_CFG_FILE_FIRST + index, + callback, callback_opaque, data, len); + s->files->f[index].size = cpu_to_be32(len); s->files->f[index].select = cpu_to_be16(FW_CFG_FILE_FIRST + index); trace_fw_cfg_add_file(s, index, s->files->f[index].name, len); diff --git a/hw/ppc/mac_newworld.c b/hw/ppc/mac_newworld.c index a365bf9223..0f3e34122a 100644 --- a/hw/ppc/mac_newworld.c +++ b/hw/ppc/mac_newworld.c @@ -119,7 +119,7 @@ static const MemoryRegionOps unin_ops = { static void fw_cfg_boot_set(void *opaque, const char *boot_device, Error **errp) { - fw_cfg_add_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); + fw_cfg_modify_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); } static uint64_t translate_kernel_address(void *opaque, uint64_t addr) diff --git a/hw/ppc/mac_oldworld.c b/hw/ppc/mac_oldworld.c index f26133dedd..99879dd2d5 100644 --- a/hw/ppc/mac_oldworld.c +++ b/hw/ppc/mac_oldworld.c @@ -52,7 +52,7 @@ static void fw_cfg_boot_set(void *opaque, const char *boot_device, Error **errp) { - fw_cfg_add_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); + fw_cfg_modify_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); } static uint64_t translate_kernel_address(void *opaque, uint64_t addr) diff --git a/hw/s390x/virtio-ccw.c b/hw/s390x/virtio-ccw.c index ef90feddea..ab5fc7f40e 100644 --- a/hw/s390x/virtio-ccw.c +++ b/hw/s390x/virtio-ccw.c @@ -1,8 +1,9 @@ /* * virtio ccw target implementation * - * Copyright 2012,2014 IBM Corp. + * Copyright 2012,2015 IBM Corp. * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com> + * Pierre Morel <pmorel@linux.vnet.ibm.com> * * This work is licensed under the terms of the GNU GPL, version 2 or (at * your option) any later version. See the COPYING file in the top-level @@ -1310,6 +1311,7 @@ static void virtio_ccw_save_config(DeviceState *d, QEMUFile *f) { VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d); SubchDev *s = dev->sch; + VirtIODevice *vdev = virtio_ccw_get_vdev(s); subch_device_save(s, f); if (dev->indicators != NULL) { @@ -1333,6 +1335,7 @@ static void virtio_ccw_save_config(DeviceState *d, QEMUFile *f) qemu_put_be32(f, 0); qemu_put_be64(f, 0UL); } + qemu_put_be16(f, vdev->config_vector); qemu_put_be64(f, dev->routes.adapter.ind_offset); qemu_put_byte(f, dev->thinint_isc); } @@ -1341,6 +1344,7 @@ static int virtio_ccw_load_config(DeviceState *d, QEMUFile *f) { VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d); SubchDev *s = dev->sch; + VirtIODevice *vdev = virtio_ccw_get_vdev(s); int len; s->driver_data = dev; @@ -1366,6 +1370,7 @@ static int virtio_ccw_load_config(DeviceState *d, QEMUFile *f) qemu_get_be64(f); dev->summary_indicator = NULL; } + qemu_get_be16s(f, &vdev->config_vector); dev->routes.adapter.ind_offset = qemu_get_be64(f); dev->thinint_isc = qemu_get_byte(f); if (s->thinint_active) { @@ -1730,6 +1735,56 @@ static const TypeInfo virtio_ccw_bus_info = { .class_init = virtio_ccw_bus_class_init, }; +#ifdef CONFIG_VIRTFS +static Property virtio_ccw_9p_properties[] = { + DEFINE_PROP_STRING("devno", VirtioCcwDevice, bus_id), + DEFINE_PROP_BIT("ioeventfd", VirtioCcwDevice, flags, + VIRTIO_CCW_FLAG_USE_IOEVENTFD_BIT, true), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtio_ccw_9p_realize(VirtioCcwDevice *ccw_dev, Error **errp) +{ + V9fsCCWState *dev = VIRTIO_9P_CCW(ccw_dev); + DeviceState *vdev = DEVICE(&dev->vdev); + Error *err = NULL; + + qdev_set_parent_bus(vdev, BUS(&ccw_dev->bus)); + object_property_set_bool(OBJECT(vdev), true, "realized", &err); + if (err) { + error_propagate(errp, err); + } +} + +static void virtio_ccw_9p_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtIOCCWDeviceClass *k = VIRTIO_CCW_DEVICE_CLASS(klass); + + k->exit = virtio_ccw_exit; + k->realize = virtio_ccw_9p_realize; + dc->reset = virtio_ccw_reset; + dc->props = virtio_ccw_9p_properties; + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); +} + +static void virtio_ccw_9p_instance_init(Object *obj) +{ + V9fsCCWState *dev = VIRTIO_9P_CCW(obj); + + virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev), + TYPE_VIRTIO_9P); +} + +static const TypeInfo virtio_ccw_9p_info = { + .name = TYPE_VIRTIO_9P_CCW, + .parent = TYPE_VIRTIO_CCW_DEVICE, + .instance_size = sizeof(V9fsCCWState), + .instance_init = virtio_ccw_9p_instance_init, + .class_init = virtio_ccw_9p_class_init, +}; +#endif + static void virtio_ccw_register(void) { type_register_static(&virtio_ccw_bus_info); @@ -1745,6 +1800,9 @@ static void virtio_ccw_register(void) #endif type_register_static(&virtio_ccw_rng); type_register_static(&virtual_css_bridge_info); +#ifdef CONFIG_VIRTFS + type_register_static(&virtio_ccw_9p_info); +#endif } type_init(virtio_ccw_register) diff --git a/hw/s390x/virtio-ccw.h b/hw/s390x/virtio-ccw.h index ad3af7626a..d729263960 100644 --- a/hw/s390x/virtio-ccw.h +++ b/hw/s390x/virtio-ccw.h @@ -1,8 +1,9 @@ /* * virtio ccw target definitions * - * Copyright 2012 IBM Corp. + * Copyright 2012,2015 IBM Corp. * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com> + * Pierre Morel <pmorel@linux.vnet.ibm.com> * * This work is licensed under the terms of the GNU GPL, version 2 or (at * your option) any later version. See the COPYING file in the top-level @@ -189,4 +190,19 @@ typedef struct VirtIORNGCcw { VirtualCssBus *virtual_css_bus_init(void); void virtio_ccw_device_update_status(SubchDev *sch); VirtIODevice *virtio_ccw_get_vdev(SubchDev *sch); + +#ifdef CONFIG_VIRTFS +#include "hw/9pfs/virtio-9p.h" + +#define TYPE_VIRTIO_9P_CCW "virtio-9p-ccw" +#define VIRTIO_9P_CCW(obj) \ + OBJECT_CHECK(V9fsCCWState, (obj), TYPE_VIRTIO_9P_CCW) + +typedef struct V9fsCCWState { + VirtioCcwDevice parent_obj; + V9fsState vdev; +} V9fsCCWState; + +#endif /* CONFIG_VIRTFS */ + #endif diff --git a/hw/sparc/sun4m.c b/hw/sparc/sun4m.c index 8a3599c403..68ac4d8bba 100644 --- a/hw/sparc/sun4m.c +++ b/hw/sparc/sun4m.c @@ -124,7 +124,7 @@ void DMA_register_channel (int nchan, static void fw_cfg_boot_set(void *opaque, const char *boot_device, Error **errp) { - fw_cfg_add_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); + fw_cfg_modify_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); } static void nvram_init(Nvram *nvram, uint8_t *macaddr, diff --git a/hw/sparc64/sun4u.c b/hw/sparc64/sun4u.c index 6f34e87935..30cfa0e0a0 100644 --- a/hw/sparc64/sun4u.c +++ b/hw/sparc64/sun4u.c @@ -127,7 +127,7 @@ void DMA_register_channel (int nchan, static void fw_cfg_boot_set(void *opaque, const char *boot_device, Error **errp) { - fw_cfg_add_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); + fw_cfg_modify_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); } static int sun4u_NVRAM_set_params(Nvram *nvram, uint16_t NVRAM_size, diff --git a/hw/vfio/Makefile.objs b/hw/vfio/Makefile.objs index e31f30ec09..d540c9d140 100644 --- a/hw/vfio/Makefile.objs +++ b/hw/vfio/Makefile.objs @@ -1,4 +1,6 @@ ifeq ($(CONFIG_LINUX), y) obj-$(CONFIG_SOFTMMU) += common.o obj-$(CONFIG_PCI) += pci.o +obj-$(CONFIG_SOFTMMU) += platform.o +obj-$(CONFIG_SOFTMMU) += calxeda-xgmac.o endif diff --git a/hw/vfio/calxeda-xgmac.c b/hw/vfio/calxeda-xgmac.c new file mode 100644 index 0000000000..eb914f0d0b --- /dev/null +++ b/hw/vfio/calxeda-xgmac.c @@ -0,0 +1,55 @@ +/* + * calxeda xgmac VFIO device + * + * Copyright Linaro Limited, 2014 + * + * Authors: + * Eric Auger <eric.auger@linaro.org> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "hw/vfio/vfio-calxeda-xgmac.h" + +static void calxeda_xgmac_realize(DeviceState *dev, Error **errp) +{ + VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(dev); + VFIOCalxedaXgmacDeviceClass *k = VFIO_CALXEDA_XGMAC_DEVICE_GET_CLASS(dev); + + vdev->compat = g_strdup("calxeda,hb-xgmac"); + + k->parent_realize(dev, errp); +} + +static const VMStateDescription vfio_platform_calxeda_xgmac_vmstate = { + .name = TYPE_VFIO_CALXEDA_XGMAC, + .unmigratable = 1, +}; + +static void vfio_calxeda_xgmac_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VFIOCalxedaXgmacDeviceClass *vcxc = + VFIO_CALXEDA_XGMAC_DEVICE_CLASS(klass); + vcxc->parent_realize = dc->realize; + dc->realize = calxeda_xgmac_realize; + dc->desc = "VFIO Calxeda XGMAC"; + dc->vmsd = &vfio_platform_calxeda_xgmac_vmstate; +} + +static const TypeInfo vfio_calxeda_xgmac_dev_info = { + .name = TYPE_VFIO_CALXEDA_XGMAC, + .parent = TYPE_VFIO_PLATFORM, + .instance_size = sizeof(VFIOCalxedaXgmacDevice), + .class_init = vfio_calxeda_xgmac_class_init, + .class_size = sizeof(VFIOCalxedaXgmacDeviceClass), +}; + +static void register_calxeda_xgmac_dev_type(void) +{ + type_register_static(&vfio_calxeda_xgmac_dev_info); +} + +type_init(register_calxeda_xgmac_dev_type) diff --git a/hw/vfio/platform.c b/hw/vfio/platform.c new file mode 100644 index 0000000000..9382bb7f36 --- /dev/null +++ b/hw/vfio/platform.c @@ -0,0 +1,615 @@ +/* + * vfio based device assignment support - platform devices + * + * Copyright Linaro Limited, 2014 + * + * Authors: + * Kim Phillips <kim.phillips@linaro.org> + * Eric Auger <eric.auger@linaro.org> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * Based on vfio based PCI device assignment support: + * Copyright Red Hat, Inc. 2012 + */ + +#include <linux/vfio.h> +#include <sys/ioctl.h> + +#include "hw/vfio/vfio-platform.h" +#include "qemu/error-report.h" +#include "qemu/range.h" +#include "sysemu/sysemu.h" +#include "exec/memory.h" +#include "qemu/queue.h" +#include "hw/sysbus.h" +#include "trace.h" +#include "hw/platform-bus.h" + +/* + * Functions used whatever the injection method + */ + +/** + * vfio_init_intp - allocate, initialize the IRQ struct pointer + * and add it into the list of IRQs + * @vbasedev: the VFIO device handle + * @info: irq info struct retrieved from VFIO driver + */ +static VFIOINTp *vfio_init_intp(VFIODevice *vbasedev, + struct vfio_irq_info info) +{ + int ret; + VFIOPlatformDevice *vdev = + container_of(vbasedev, VFIOPlatformDevice, vbasedev); + SysBusDevice *sbdev = SYS_BUS_DEVICE(vdev); + VFIOINTp *intp; + + intp = g_malloc0(sizeof(*intp)); + intp->vdev = vdev; + intp->pin = info.index; + intp->flags = info.flags; + intp->state = VFIO_IRQ_INACTIVE; + + sysbus_init_irq(sbdev, &intp->qemuirq); + + /* Get an eventfd for trigger */ + ret = event_notifier_init(&intp->interrupt, 0); + if (ret) { + g_free(intp); + error_report("vfio: Error: trigger event_notifier_init failed "); + return NULL; + } + + QLIST_INSERT_HEAD(&vdev->intp_list, intp, next); + return intp; +} + +/** + * vfio_set_trigger_eventfd - set VFIO eventfd handling + * + * @intp: IRQ struct handle + * @handler: handler to be called on eventfd signaling + * + * Setup VFIO signaling and attach an optional user-side handler + * to the eventfd + */ +static int vfio_set_trigger_eventfd(VFIOINTp *intp, + eventfd_user_side_handler_t handler) +{ + VFIODevice *vbasedev = &intp->vdev->vbasedev; + struct vfio_irq_set *irq_set; + int argsz, ret; + int32_t *pfd; + + argsz = sizeof(*irq_set) + sizeof(*pfd); + irq_set = g_malloc0(argsz); + irq_set->argsz = argsz; + irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; + irq_set->index = intp->pin; + irq_set->start = 0; + irq_set->count = 1; + pfd = (int32_t *)&irq_set->data; + *pfd = event_notifier_get_fd(&intp->interrupt); + qemu_set_fd_handler(*pfd, (IOHandler *)handler, NULL, intp); + ret = ioctl(vbasedev->fd, VFIO_DEVICE_SET_IRQS, irq_set); + g_free(irq_set); + if (ret < 0) { + error_report("vfio: Failed to set trigger eventfd: %m"); + qemu_set_fd_handler(*pfd, NULL, NULL, NULL); + } + return ret; +} + +/* + * Functions only used when eventfds are handled on user-side + * ie. without irqfd + */ + +/** + * vfio_mmap_set_enabled - enable/disable the fast path mode + * @vdev: the VFIO platform device + * @enabled: the target mmap state + * + * enabled = true ~ fast path = MMIO region is mmaped (no KVM TRAP); + * enabled = false ~ slow path = MMIO region is trapped and region callbacks + * are called; slow path enables to trap the device IRQ status register reset +*/ + +static void vfio_mmap_set_enabled(VFIOPlatformDevice *vdev, bool enabled) +{ + int i; + + trace_vfio_platform_mmap_set_enabled(enabled); + + for (i = 0; i < vdev->vbasedev.num_regions; i++) { + VFIORegion *region = vdev->regions[i]; + + memory_region_set_enabled(®ion->mmap_mem, enabled); + } +} + +/** + * vfio_intp_mmap_enable - timer function, restores the fast path + * if there is no more active IRQ + * @opaque: actually points to the VFIO platform device + * + * Called on mmap timer timout, this function checks whether the + * IRQ is still active and if not, restores the fast path. + * by construction a single eventfd is handled at a time. + * if the IRQ is still active, the timer is re-programmed. + */ +static void vfio_intp_mmap_enable(void *opaque) +{ + VFIOINTp *tmp; + VFIOPlatformDevice *vdev = (VFIOPlatformDevice *)opaque; + + qemu_mutex_lock(&vdev->intp_mutex); + QLIST_FOREACH(tmp, &vdev->intp_list, next) { + if (tmp->state == VFIO_IRQ_ACTIVE) { + trace_vfio_platform_intp_mmap_enable(tmp->pin); + /* re-program the timer to check active status later */ + timer_mod(vdev->mmap_timer, + qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + vdev->mmap_timeout); + qemu_mutex_unlock(&vdev->intp_mutex); + return; + } + } + vfio_mmap_set_enabled(vdev, true); + qemu_mutex_unlock(&vdev->intp_mutex); +} + +/** + * vfio_intp_inject_pending_lockheld - Injects a pending IRQ + * @opaque: opaque pointer, in practice the VFIOINTp handle + * + * The function is called on a previous IRQ completion, from + * vfio_platform_eoi, while the intp_mutex is locked. + * Also in such situation, the slow path already is set and + * the mmap timer was already programmed. + */ +static void vfio_intp_inject_pending_lockheld(VFIOINTp *intp) +{ + trace_vfio_platform_intp_inject_pending_lockheld(intp->pin, + event_notifier_get_fd(&intp->interrupt)); + + intp->state = VFIO_IRQ_ACTIVE; + + /* trigger the virtual IRQ */ + qemu_set_irq(intp->qemuirq, 1); +} + +/** + * vfio_intp_interrupt - The user-side eventfd handler + * @opaque: opaque pointer which in practice is the VFIOINTp handle + * + * the function is entered in event handler context: + * the vIRQ is injected into the guest if there is no other active + * or pending IRQ. + */ +static void vfio_intp_interrupt(VFIOINTp *intp) +{ + int ret; + VFIOINTp *tmp; + VFIOPlatformDevice *vdev = intp->vdev; + bool delay_handling = false; + + qemu_mutex_lock(&vdev->intp_mutex); + if (intp->state == VFIO_IRQ_INACTIVE) { + QLIST_FOREACH(tmp, &vdev->intp_list, next) { + if (tmp->state == VFIO_IRQ_ACTIVE || + tmp->state == VFIO_IRQ_PENDING) { + delay_handling = true; + break; + } + } + } + if (delay_handling) { + /* + * the new IRQ gets a pending status and is pushed in + * the pending queue + */ + intp->state = VFIO_IRQ_PENDING; + trace_vfio_intp_interrupt_set_pending(intp->pin); + QSIMPLEQ_INSERT_TAIL(&vdev->pending_intp_queue, + intp, pqnext); + ret = event_notifier_test_and_clear(&intp->interrupt); + qemu_mutex_unlock(&vdev->intp_mutex); + return; + } + + trace_vfio_platform_intp_interrupt(intp->pin, + event_notifier_get_fd(&intp->interrupt)); + + ret = event_notifier_test_and_clear(&intp->interrupt); + if (!ret) { + error_report("Error when clearing fd=%d (ret = %d)\n", + event_notifier_get_fd(&intp->interrupt), ret); + } + + intp->state = VFIO_IRQ_ACTIVE; + + /* sets slow path */ + vfio_mmap_set_enabled(vdev, false); + + /* trigger the virtual IRQ */ + qemu_set_irq(intp->qemuirq, 1); + + /* + * Schedule the mmap timer which will restore fastpath when no IRQ + * is active anymore + */ + if (vdev->mmap_timeout) { + timer_mod(vdev->mmap_timer, + qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + vdev->mmap_timeout); + } + qemu_mutex_unlock(&vdev->intp_mutex); +} + +/** + * vfio_platform_eoi - IRQ completion routine + * @vbasedev: the VFIO device handle + * + * De-asserts the active virtual IRQ and unmasks the physical IRQ + * (effective for level sensitive IRQ auto-masked by the VFIO driver). + * Then it handles next pending IRQ if any. + * eoi function is called on the first access to any MMIO region + * after an IRQ was triggered, trapped since slow path was set. + * It is assumed this access corresponds to the IRQ status + * register reset. With such a mechanism, a single IRQ can be + * handled at a time since there is no way to know which IRQ + * was completed by the guest (we would need additional details + * about the IRQ status register mask). + */ +static void vfio_platform_eoi(VFIODevice *vbasedev) +{ + VFIOINTp *intp; + VFIOPlatformDevice *vdev = + container_of(vbasedev, VFIOPlatformDevice, vbasedev); + + qemu_mutex_lock(&vdev->intp_mutex); + QLIST_FOREACH(intp, &vdev->intp_list, next) { + if (intp->state == VFIO_IRQ_ACTIVE) { + trace_vfio_platform_eoi(intp->pin, + event_notifier_get_fd(&intp->interrupt)); + intp->state = VFIO_IRQ_INACTIVE; + + /* deassert the virtual IRQ */ + qemu_set_irq(intp->qemuirq, 0); + + if (intp->flags & VFIO_IRQ_INFO_AUTOMASKED) { + /* unmasks the physical level-sensitive IRQ */ + vfio_unmask_single_irqindex(vbasedev, intp->pin); + } + + /* a single IRQ can be active at a time */ + break; + } + } + /* in case there are pending IRQs, handle the first one */ + if (!QSIMPLEQ_EMPTY(&vdev->pending_intp_queue)) { + intp = QSIMPLEQ_FIRST(&vdev->pending_intp_queue); + vfio_intp_inject_pending_lockheld(intp); + QSIMPLEQ_REMOVE_HEAD(&vdev->pending_intp_queue, pqnext); + } + qemu_mutex_unlock(&vdev->intp_mutex); +} + +/** + * vfio_start_eventfd_injection - starts the virtual IRQ injection using + * user-side handled eventfds + * @intp: the IRQ struct pointer + */ + +static int vfio_start_eventfd_injection(VFIOINTp *intp) +{ + int ret; + + ret = vfio_set_trigger_eventfd(intp, vfio_intp_interrupt); + if (ret) { + error_report("vfio: Error: Failed to pass IRQ fd to the driver: %m"); + } + return ret; +} + +/* VFIO skeleton */ + +static void vfio_platform_compute_needs_reset(VFIODevice *vbasedev) +{ + vbasedev->needs_reset = true; +} + +/* not implemented yet */ +static int vfio_platform_hot_reset_multi(VFIODevice *vbasedev) +{ + return -1; +} + +/** + * vfio_populate_device - Allocate and populate MMIO region + * and IRQ structs according to driver returned information + * @vbasedev: the VFIO device handle + * + */ +static int vfio_populate_device(VFIODevice *vbasedev) +{ + VFIOINTp *intp, *tmp; + int i, ret = -1; + VFIOPlatformDevice *vdev = + container_of(vbasedev, VFIOPlatformDevice, vbasedev); + + if (!(vbasedev->flags & VFIO_DEVICE_FLAGS_PLATFORM)) { + error_report("vfio: Um, this isn't a platform device"); + return ret; + } + + vdev->regions = g_new0(VFIORegion *, vbasedev->num_regions); + + for (i = 0; i < vbasedev->num_regions; i++) { + struct vfio_region_info reg_info = { .argsz = sizeof(reg_info) }; + VFIORegion *ptr; + + vdev->regions[i] = g_malloc0(sizeof(VFIORegion)); + ptr = vdev->regions[i]; + reg_info.index = i; + ret = ioctl(vbasedev->fd, VFIO_DEVICE_GET_REGION_INFO, ®_info); + if (ret) { + error_report("vfio: Error getting region %d info: %m", i); + goto reg_error; + } + ptr->flags = reg_info.flags; + ptr->size = reg_info.size; + ptr->fd_offset = reg_info.offset; + ptr->nr = i; + ptr->vbasedev = vbasedev; + + trace_vfio_platform_populate_regions(ptr->nr, + (unsigned long)ptr->flags, + (unsigned long)ptr->size, + ptr->vbasedev->fd, + (unsigned long)ptr->fd_offset); + } + + vdev->mmap_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, + vfio_intp_mmap_enable, vdev); + + QSIMPLEQ_INIT(&vdev->pending_intp_queue); + + for (i = 0; i < vbasedev->num_irqs; i++) { + struct vfio_irq_info irq = { .argsz = sizeof(irq) }; + + irq.index = i; + ret = ioctl(vbasedev->fd, VFIO_DEVICE_GET_IRQ_INFO, &irq); + if (ret) { + error_printf("vfio: error getting device %s irq info", + vbasedev->name); + goto irq_err; + } else { + trace_vfio_platform_populate_interrupts(irq.index, + irq.count, + irq.flags); + intp = vfio_init_intp(vbasedev, irq); + if (!intp) { + error_report("vfio: Error installing IRQ %d up", i); + goto irq_err; + } + } + } + return 0; +irq_err: + timer_del(vdev->mmap_timer); + QLIST_FOREACH_SAFE(intp, &vdev->intp_list, next, tmp) { + QLIST_REMOVE(intp, next); + g_free(intp); + } +reg_error: + for (i = 0; i < vbasedev->num_regions; i++) { + g_free(vdev->regions[i]); + } + g_free(vdev->regions); + return ret; +} + +/* specialized functions for VFIO Platform devices */ +static VFIODeviceOps vfio_platform_ops = { + .vfio_compute_needs_reset = vfio_platform_compute_needs_reset, + .vfio_hot_reset_multi = vfio_platform_hot_reset_multi, + .vfio_eoi = vfio_platform_eoi, +}; + +/** + * vfio_base_device_init - perform preliminary VFIO setup + * @vbasedev: the VFIO device handle + * + * Implement the VFIO command sequence that allows to discover + * assigned device resources: group extraction, device + * fd retrieval, resource query. + * Precondition: the device name must be initialized + */ +static int vfio_base_device_init(VFIODevice *vbasedev) +{ + VFIOGroup *group; + VFIODevice *vbasedev_iter; + char path[PATH_MAX], iommu_group_path[PATH_MAX], *group_name; + ssize_t len; + struct stat st; + int groupid; + int ret; + + /* name must be set prior to the call */ + if (!vbasedev->name || strchr(vbasedev->name, '/')) { + return -EINVAL; + } + + /* Check that the host device exists */ + g_snprintf(path, sizeof(path), "/sys/bus/platform/devices/%s/", + vbasedev->name); + + if (stat(path, &st) < 0) { + error_report("vfio: error: no such host device: %s", path); + return -errno; + } + + g_strlcat(path, "iommu_group", sizeof(path)); + len = readlink(path, iommu_group_path, sizeof(iommu_group_path)); + if (len < 0 || len >= sizeof(iommu_group_path)) { + error_report("vfio: error no iommu_group for device"); + return len < 0 ? -errno : -ENAMETOOLONG; + } + + iommu_group_path[len] = 0; + group_name = basename(iommu_group_path); + + if (sscanf(group_name, "%d", &groupid) != 1) { + error_report("vfio: error reading %s: %m", path); + return -errno; + } + + trace_vfio_platform_base_device_init(vbasedev->name, groupid); + + group = vfio_get_group(groupid, &address_space_memory); + if (!group) { + error_report("vfio: failed to get group %d", groupid); + return -ENOENT; + } + + g_snprintf(path, sizeof(path), "%s", vbasedev->name); + + QLIST_FOREACH(vbasedev_iter, &group->device_list, next) { + if (strcmp(vbasedev_iter->name, vbasedev->name) == 0) { + error_report("vfio: error: device %s is already attached", path); + vfio_put_group(group); + return -EBUSY; + } + } + ret = vfio_get_device(group, path, vbasedev); + if (ret) { + error_report("vfio: failed to get device %s", path); + vfio_put_group(group); + return ret; + } + + ret = vfio_populate_device(vbasedev); + if (ret) { + error_report("vfio: failed to populate device %s", path); + vfio_put_group(group); + } + + return ret; +} + +/** + * vfio_map_region - initialize the 2 memory regions for a given + * MMIO region index + * @vdev: the VFIO platform device handle + * @nr: the index of the region + * + * Init the top memory region and the mmapped memory region beneath + * VFIOPlatformDevice is used since VFIODevice is not a QOM Object + * and could not be passed to memory region functions +*/ +static void vfio_map_region(VFIOPlatformDevice *vdev, int nr) +{ + VFIORegion *region = vdev->regions[nr]; + uint64_t size = region->size; + char name[64]; + + if (!size) { + return; + } + + g_snprintf(name, sizeof(name), "VFIO %s region %d", + vdev->vbasedev.name, nr); + + /* A "slow" read/write mapping underlies all regions */ + memory_region_init_io(®ion->mem, OBJECT(vdev), &vfio_region_ops, + region, name, size); + + g_strlcat(name, " mmap", sizeof(name)); + + if (vfio_mmap_region(OBJECT(vdev), region, ®ion->mem, + ®ion->mmap_mem, ®ion->mmap, size, 0, name)) { + error_report("%s unsupported. Performance may be slow", name); + } +} + +/** + * vfio_platform_realize - the device realize function + * @dev: device state pointer + * @errp: error + * + * initialize the device, its memory regions and IRQ structures + * IRQ are started separately + */ +static void vfio_platform_realize(DeviceState *dev, Error **errp) +{ + VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(dev); + SysBusDevice *sbdev = SYS_BUS_DEVICE(dev); + VFIODevice *vbasedev = &vdev->vbasedev; + VFIOINTp *intp; + int i, ret; + + vbasedev->type = VFIO_DEVICE_TYPE_PLATFORM; + vbasedev->ops = &vfio_platform_ops; + + trace_vfio_platform_realize(vbasedev->name, vdev->compat); + + ret = vfio_base_device_init(vbasedev); + if (ret) { + error_setg(errp, "vfio: vfio_base_device_init failed for %s", + vbasedev->name); + return; + } + + for (i = 0; i < vbasedev->num_regions; i++) { + vfio_map_region(vdev, i); + sysbus_init_mmio(sbdev, &vdev->regions[i]->mem); + } + + QLIST_FOREACH(intp, &vdev->intp_list, next) { + vfio_start_eventfd_injection(intp); + } +} + +static const VMStateDescription vfio_platform_vmstate = { + .name = TYPE_VFIO_PLATFORM, + .unmigratable = 1, +}; + +static Property vfio_platform_dev_properties[] = { + DEFINE_PROP_STRING("host", VFIOPlatformDevice, vbasedev.name), + DEFINE_PROP_BOOL("x-mmap", VFIOPlatformDevice, vbasedev.allow_mmap, true), + DEFINE_PROP_UINT32("mmap-timeout-ms", VFIOPlatformDevice, + mmap_timeout, 1100), + DEFINE_PROP_END_OF_LIST(), +}; + +static void vfio_platform_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = vfio_platform_realize; + dc->props = vfio_platform_dev_properties; + dc->vmsd = &vfio_platform_vmstate; + dc->desc = "VFIO-based platform device assignment"; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo vfio_platform_dev_info = { + .name = TYPE_VFIO_PLATFORM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(VFIOPlatformDevice), + .class_init = vfio_platform_class_init, + .class_size = sizeof(VFIOPlatformDeviceClass), + .abstract = true, +}; + +static void register_vfio_platform_dev_type(void) +{ + type_register_static(&vfio_platform_dev_info); +} + +type_init(register_vfio_platform_dev_type) |