/* $OpenBSD: virtio.c,v 1.97 2021/08/29 18:01:32 dv Exp $ */ /* * Copyright (c) 2015 Mike Larkin * * 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 /* PAGE_SIZE */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "atomicio.h" #include "pci.h" #include "vioscsi.h" #include "virtio.h" #include "vmd.h" #include "vmm.h" extern char *__progname; struct viornd_dev viornd; struct vioblk_dev *vioblk; struct vionet_dev *vionet; struct vioscsi_dev *vioscsi; struct vmmci_dev vmmci; int nr_vionet; int nr_vioblk; #define MAXPHYS (64 * 1024) /* max raw I/O transfer size */ #define VIRTIO_NET_F_MAC (1<<5) #define VMMCI_F_TIMESYNC (1<<0) #define VMMCI_F_ACK (1<<1) #define VMMCI_F_SYNCRTC (1<<2) #define RXQ 0 #define TXQ 1 const char * vioblk_cmd_name(uint32_t type) { switch (type) { case VIRTIO_BLK_T_IN: return "read"; case VIRTIO_BLK_T_OUT: return "write"; case VIRTIO_BLK_T_SCSI_CMD: return "scsi read"; case VIRTIO_BLK_T_SCSI_CMD_OUT: return "scsi write"; case VIRTIO_BLK_T_FLUSH: return "flush"; case VIRTIO_BLK_T_FLUSH_OUT: return "flush out"; case VIRTIO_BLK_T_GET_ID: return "get id"; default: return "unknown"; } } static const char * virtio_reg_name(uint8_t reg) { switch (reg) { case VIRTIO_CONFIG_DEVICE_FEATURES: return "device feature"; case VIRTIO_CONFIG_GUEST_FEATURES: return "guest feature"; case VIRTIO_CONFIG_QUEUE_ADDRESS: return "queue address"; case VIRTIO_CONFIG_QUEUE_SIZE: return "queue size"; case VIRTIO_CONFIG_QUEUE_SELECT: return "queue select"; case VIRTIO_CONFIG_QUEUE_NOTIFY: return "queue notify"; case VIRTIO_CONFIG_DEVICE_STATUS: return "device status"; case VIRTIO_CONFIG_ISR_STATUS: return "isr status"; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI: return "device config 0"; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 4: return "device config 1"; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 8: return "device config 2"; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 12: return "device config 3"; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 16: return "device config 4"; default: return "unknown"; } } uint32_t vring_size(uint32_t vq_size) { uint32_t allocsize1, allocsize2; /* allocsize1: descriptor table + avail ring + pad */ allocsize1 = VIRTQUEUE_ALIGN(sizeof(struct vring_desc) * vq_size + sizeof(uint16_t) * (2 + vq_size)); /* allocsize2: used ring + pad */ allocsize2 = VIRTQUEUE_ALIGN(sizeof(uint16_t) * 2 + sizeof(struct vring_used_elem) * vq_size); return allocsize1 + allocsize2; } /* Update queue select */ void viornd_update_qs(void) { /* Invalid queue? */ if (viornd.cfg.queue_select > 0) { viornd.cfg.queue_size = 0; return; } /* Update queue address/size based on queue select */ viornd.cfg.queue_address = viornd.vq[viornd.cfg.queue_select].qa; viornd.cfg.queue_size = viornd.vq[viornd.cfg.queue_select].qs; } /* Update queue address */ void viornd_update_qa(void) { /* Invalid queue? */ if (viornd.cfg.queue_select > 0) return; viornd.vq[viornd.cfg.queue_select].qa = viornd.cfg.queue_address; } int viornd_notifyq(void) { uint64_t q_gpa; uint32_t vr_sz; size_t sz; int dxx, ret; uint16_t aidx, uidx; char *buf, *rnd_data; struct vring_desc *desc; struct vring_avail *avail; struct vring_used *used; ret = 0; /* Invalid queue? */ if (viornd.cfg.queue_notify > 0) return (0); vr_sz = vring_size(VIORND_QUEUE_SIZE); q_gpa = viornd.vq[viornd.cfg.queue_notify].qa; q_gpa = q_gpa * VIRTIO_PAGE_SIZE; buf = calloc(1, vr_sz); if (buf == NULL) { log_warn("calloc error getting viornd ring"); return (0); } if (read_mem(q_gpa, buf, vr_sz)) { free(buf); return (0); } desc = (struct vring_desc *)(buf); avail = (struct vring_avail *)(buf + viornd.vq[viornd.cfg.queue_notify].vq_availoffset); used = (struct vring_used *)(buf + viornd.vq[viornd.cfg.queue_notify].vq_usedoffset); aidx = avail->idx & VIORND_QUEUE_MASK; uidx = used->idx & VIORND_QUEUE_MASK; dxx = avail->ring[aidx] & VIORND_QUEUE_MASK; sz = desc[dxx].len; if (sz > MAXPHYS) fatalx("viornd descriptor size too large (%zu)", sz); rnd_data = malloc(sz); if (rnd_data != NULL) { arc4random_buf(rnd_data, sz); if (write_mem(desc[dxx].addr, rnd_data, sz)) { log_warnx("viornd: can't write random data @ " "0x%llx", desc[dxx].addr); } else { /* ret == 1 -> interrupt needed */ /* XXX check VIRTIO_F_NO_INTR */ ret = 1; viornd.cfg.isr_status = 1; used->ring[uidx].id = dxx; used->ring[uidx].len = sz; used->idx++; if (write_mem(q_gpa, buf, vr_sz)) { log_warnx("viornd: error writing vio ring"); } } free(rnd_data); } else fatal("memory allocation error for viornd data"); free(buf); return (ret); } int virtio_rnd_io(int dir, uint16_t reg, uint32_t *data, uint8_t *intr, void *unused, uint8_t sz) { *intr = 0xFF; if (dir == 0) { switch (reg) { case VIRTIO_CONFIG_DEVICE_FEATURES: case VIRTIO_CONFIG_QUEUE_SIZE: case VIRTIO_CONFIG_ISR_STATUS: log_warnx("%s: illegal write %x to %s", __progname, *data, virtio_reg_name(reg)); break; case VIRTIO_CONFIG_GUEST_FEATURES: viornd.cfg.guest_feature = *data; break; case VIRTIO_CONFIG_QUEUE_ADDRESS: viornd.cfg.queue_address = *data; viornd_update_qa(); break; case VIRTIO_CONFIG_QUEUE_SELECT: viornd.cfg.queue_select = *data; viornd_update_qs(); break; case VIRTIO_CONFIG_QUEUE_NOTIFY: viornd.cfg.queue_notify = *data; if (viornd_notifyq()) *intr = 1; break; case VIRTIO_CONFIG_DEVICE_STATUS: viornd.cfg.device_status = *data; break; } } else { switch (reg) { case VIRTIO_CONFIG_DEVICE_FEATURES: *data = viornd.cfg.device_feature; break; case VIRTIO_CONFIG_GUEST_FEATURES: *data = viornd.cfg.guest_feature; break; case VIRTIO_CONFIG_QUEUE_ADDRESS: *data = viornd.cfg.queue_address; break; case VIRTIO_CONFIG_QUEUE_SIZE: *data = viornd.cfg.queue_size; break; case VIRTIO_CONFIG_QUEUE_SELECT: *data = viornd.cfg.queue_select; break; case VIRTIO_CONFIG_QUEUE_NOTIFY: *data = viornd.cfg.queue_notify; break; case VIRTIO_CONFIG_DEVICE_STATUS: *data = viornd.cfg.device_status; break; case VIRTIO_CONFIG_ISR_STATUS: *data = viornd.cfg.isr_status; viornd.cfg.isr_status = 0; vcpu_deassert_pic_irq(viornd.vm_id, 0, viornd.irq); break; } } return (0); } void vioblk_update_qa(struct vioblk_dev *dev) { /* Invalid queue? */ if (dev->cfg.queue_select > 0) return; dev->vq[dev->cfg.queue_select].qa = dev->cfg.queue_address; } void vioblk_update_qs(struct vioblk_dev *dev) { /* Invalid queue? */ if (dev->cfg.queue_select > 0) { dev->cfg.queue_size = 0; return; } /* Update queue address/size based on queue select */ dev->cfg.queue_address = dev->vq[dev->cfg.queue_select].qa; dev->cfg.queue_size = dev->vq[dev->cfg.queue_select].qs; } static void vioblk_free_info(struct ioinfo *info) { if (!info) return; free(info->buf); free(info); } static struct ioinfo * vioblk_start_read(struct vioblk_dev *dev, off_t sector, size_t sz) { struct ioinfo *info; /* Limit to 64M for now */ if (sz > (1 << 26)) { log_warnx("%s: read size exceeded 64M", __func__); return (NULL); } info = calloc(1, sizeof(*info)); if (!info) goto nomem; info->buf = malloc(sz); if (info->buf == NULL) goto nomem; info->len = sz; info->offset = sector * VIRTIO_BLK_SECTOR_SIZE; info->file = &dev->file; return info; nomem: free(info); log_warn("malloc error vioblk read"); return (NULL); } static const uint8_t * vioblk_finish_read(struct ioinfo *info) { struct virtio_backing *file; file = info->file; if (file->pread(file->p, info->buf, info->len, info->offset) != info->len) { info->error = errno; log_warn("vioblk read error"); return NULL; } return info->buf; } static struct ioinfo * vioblk_start_write(struct vioblk_dev *dev, off_t sector, paddr_t addr, size_t len) { struct ioinfo *info; /* Limit to 64M for now */ if (len > (1 << 26)) { log_warnx("%s: write size exceeded 64M", __func__); return (NULL); } info = calloc(1, sizeof(*info)); if (!info) goto nomem; info->buf = malloc(len); if (info->buf == NULL) goto nomem; info->len = len; info->offset = sector * VIRTIO_BLK_SECTOR_SIZE; info->file = &dev->file; if (read_mem(addr, info->buf, info->len)) { vioblk_free_info(info); return NULL; } return info; nomem: free(info); log_warn("malloc error vioblk write"); return (NULL); } static int vioblk_finish_write(struct ioinfo *info) { struct virtio_backing *file; file = info->file; if (file->pwrite(file->p, info->buf, info->len, info->offset) != info->len) { log_warn("vioblk write error"); return EIO; } return 0; } /* * XXX in various cases, ds should be set to VIRTIO_BLK_S_IOERR, if we can */ int vioblk_notifyq(struct vioblk_dev *dev) { uint64_t q_gpa; uint32_t vr_sz; uint16_t idx, cmd_desc_idx, secdata_desc_idx, ds_desc_idx; uint8_t ds; int cnt, ret; off_t secbias; char *vr; struct vring_desc *desc, *cmd_desc, *secdata_desc, *ds_desc; struct vring_avail *avail; struct vring_used *used; struct virtio_blk_req_hdr cmd; ret = 0; /* Invalid queue? */ if (dev->cfg.queue_notify > 0) return (0); vr_sz = vring_size(VIOBLK_QUEUE_SIZE); q_gpa = dev->vq[dev->cfg.queue_notify].qa; q_gpa = q_gpa * VIRTIO_PAGE_SIZE; vr = calloc(1, vr_sz); if (vr == NULL) { log_warn("calloc error getting vioblk ring"); return (0); } if (read_mem(q_gpa, vr, vr_sz)) { log_warnx("error reading gpa 0x%llx", q_gpa); goto out; } /* Compute offsets in ring of descriptors, avail ring, and used ring */ desc = (struct vring_desc *)(vr); avail = (struct vring_avail *)(vr + dev->vq[dev->cfg.queue_notify].vq_availoffset); used = (struct vring_used *)(vr + dev->vq[dev->cfg.queue_notify].vq_usedoffset); idx = dev->vq[dev->cfg.queue_notify].last_avail & VIOBLK_QUEUE_MASK; if ((avail->idx & VIOBLK_QUEUE_MASK) == idx) { log_warnx("vioblk queue notify - nothing to do?"); goto out; } while (idx != (avail->idx & VIOBLK_QUEUE_MASK)) { cmd_desc_idx = avail->ring[idx] & VIOBLK_QUEUE_MASK; cmd_desc = &desc[cmd_desc_idx]; if ((cmd_desc->flags & VRING_DESC_F_NEXT) == 0) { log_warnx("unchained vioblk cmd descriptor received " "(idx %d)", cmd_desc_idx); goto out; } /* Read command from descriptor ring */ if (cmd_desc->flags & VRING_DESC_F_WRITE) { log_warnx("vioblk: unexpected writable cmd descriptor " "%d", cmd_desc_idx); goto out; } if (read_mem(cmd_desc->addr, &cmd, sizeof(cmd))) { log_warnx("vioblk: command read_mem error @ 0x%llx", cmd_desc->addr); goto out; } switch (cmd.type) { case VIRTIO_BLK_T_IN: /* first descriptor */ secdata_desc_idx = cmd_desc->next & VIOBLK_QUEUE_MASK; secdata_desc = &desc[secdata_desc_idx]; if ((secdata_desc->flags & VRING_DESC_F_NEXT) == 0) { log_warnx("unchained vioblk data descriptor " "received (idx %d)", cmd_desc_idx); goto out; } cnt = 0; secbias = 0; do { struct ioinfo *info; const uint8_t *secdata; if ((secdata_desc->flags & VRING_DESC_F_WRITE) == 0) { log_warnx("vioblk: unwritable data " "descriptor %d", secdata_desc_idx); goto out; } info = vioblk_start_read(dev, cmd.sector + secbias, secdata_desc->len); if (info == NULL) { log_warnx("vioblk: can't start read"); goto out; } /* read the data, use current data descriptor */ secdata = vioblk_finish_read(info); if (secdata == NULL) { vioblk_free_info(info); log_warnx("vioblk: block read error, " "sector %lld", cmd.sector); goto out; } if (write_mem(secdata_desc->addr, secdata, secdata_desc->len)) { log_warnx("can't write sector " "data to gpa @ 0x%llx", secdata_desc->addr); vioblk_free_info(info); goto out; } vioblk_free_info(info); secbias += (secdata_desc->len / VIRTIO_BLK_SECTOR_SIZE); secdata_desc_idx = secdata_desc->next & VIOBLK_QUEUE_MASK; secdata_desc = &desc[secdata_desc_idx]; /* Guard against infinite chains */ if (++cnt >= VIOBLK_QUEUE_SIZE) { log_warnx("%s: descriptor table " "invalid", __func__); goto out; } } while (secdata_desc->flags & VRING_DESC_F_NEXT); ds_desc_idx = secdata_desc_idx; ds_desc = secdata_desc; ds = VIRTIO_BLK_S_OK; break; case VIRTIO_BLK_T_OUT: secdata_desc_idx = cmd_desc->next & VIOBLK_QUEUE_MASK; secdata_desc = &desc[secdata_desc_idx]; if ((secdata_desc->flags & VRING_DESC_F_NEXT) == 0) { log_warnx("wr vioblk: unchained vioblk data " "descriptor received (idx %d)", cmd_desc_idx); goto out; } if (secdata_desc->len > dev->max_xfer) { log_warnx("%s: invalid read size %d requested", __func__, secdata_desc->len); goto out; } cnt = 0; secbias = 0; do { struct ioinfo *info; if (secdata_desc->flags & VRING_DESC_F_WRITE) { log_warnx("wr vioblk: unexpected " "writable data descriptor %d", secdata_desc_idx); goto out; } info = vioblk_start_write(dev, cmd.sector + secbias, secdata_desc->addr, secdata_desc->len); if (info == NULL) { log_warnx("wr vioblk: can't read " "sector data @ 0x%llx", secdata_desc->addr); goto out; } if (vioblk_finish_write(info)) { log_warnx("wr vioblk: disk write " "error"); vioblk_free_info(info); goto out; } vioblk_free_info(info); secbias += secdata_desc->len / VIRTIO_BLK_SECTOR_SIZE; secdata_desc_idx = secdata_desc->next & VIOBLK_QUEUE_MASK; secdata_desc = &desc[secdata_desc_idx]; /* Guard against infinite chains */ if (++cnt >= VIOBLK_QUEUE_SIZE) { log_warnx("%s: descriptor table " "invalid", __func__); goto out; } } while (secdata_desc->flags & VRING_DESC_F_NEXT); ds_desc_idx = secdata_desc_idx; ds_desc = secdata_desc; ds = VIRTIO_BLK_S_OK; break; case VIRTIO_BLK_T_FLUSH: case VIRTIO_BLK_T_FLUSH_OUT: ds_desc_idx = cmd_desc->next & VIOBLK_QUEUE_MASK; ds_desc = &desc[ds_desc_idx]; ds = VIRTIO_BLK_S_UNSUPP; break; case VIRTIO_BLK_T_GET_ID: secdata_desc_idx = cmd_desc->next & VIOBLK_QUEUE_MASK; secdata_desc = &desc[secdata_desc_idx]; /* * We don't support this command yet. While it's not * officially part of the virtio spec (will be in v1.2) * there's no feature to negotiate. Linux drivers will * often send this command regardless. * * When the command is received, it should appear as a * chain of 3 descriptors, similar to the IN/OUT * commands. The middle descriptor should have have a * length of VIRTIO_BLK_ID_BYTES bytes. */ if ((secdata_desc->flags & VRING_DESC_F_NEXT) == 0) { log_warnx("id vioblk: unchained vioblk data " "descriptor received (idx %d)", cmd_desc_idx); goto out; } /* Skip the data descriptor. */ ds_desc_idx = secdata_desc->next & VIOBLK_QUEUE_MASK; ds_desc = &desc[ds_desc_idx]; ds = VIRTIO_BLK_S_UNSUPP; break; default: log_warnx("%s: unsupported command 0x%x", __func__, cmd.type); ds_desc_idx = cmd_desc->next & VIOBLK_QUEUE_MASK; ds_desc = &desc[ds_desc_idx]; ds = VIRTIO_BLK_S_UNSUPP; break; } if ((ds_desc->flags & VRING_DESC_F_WRITE) == 0) { log_warnx("%s: ds descriptor %d unwritable", __func__, ds_desc_idx); goto out; } if (write_mem(ds_desc->addr, &ds, sizeof(ds))) { log_warnx("%s: can't write device status data @ 0x%llx", __func__, ds_desc->addr); goto out; } ret = 1; dev->cfg.isr_status = 1; used->ring[used->idx & VIOBLK_QUEUE_MASK].id = cmd_desc_idx; used->ring[used->idx & VIOBLK_QUEUE_MASK].len = cmd_desc->len; used->idx++; dev->vq[dev->cfg.queue_notify].last_avail = avail->idx & VIOBLK_QUEUE_MASK; if (write_mem(q_gpa, vr, vr_sz)) log_warnx("%s: error writing vio ring", __func__); idx = (idx + 1) & VIOBLK_QUEUE_MASK; } out: free(vr); return (ret); } int virtio_blk_io(int dir, uint16_t reg, uint32_t *data, uint8_t *intr, void *cookie, uint8_t sz) { struct vioblk_dev *dev = (struct vioblk_dev *)cookie; *intr = 0xFF; if (dir == 0) { switch (reg) { case VIRTIO_CONFIG_DEVICE_FEATURES: case VIRTIO_CONFIG_QUEUE_SIZE: case VIRTIO_CONFIG_ISR_STATUS: log_warnx("%s: illegal write %x to %s", __progname, *data, virtio_reg_name(reg)); break; case VIRTIO_CONFIG_GUEST_FEATURES: dev->cfg.guest_feature = *data; break; case VIRTIO_CONFIG_QUEUE_ADDRESS: dev->cfg.queue_address = *data; vioblk_update_qa(dev); break; case VIRTIO_CONFIG_QUEUE_SELECT: dev->cfg.queue_select = *data; vioblk_update_qs(dev); break; case VIRTIO_CONFIG_QUEUE_NOTIFY: dev->cfg.queue_notify = *data; if (vioblk_notifyq(dev)) *intr = 1; break; case VIRTIO_CONFIG_DEVICE_STATUS: dev->cfg.device_status = *data; if (dev->cfg.device_status == 0) { log_debug("%s: device reset", __func__); dev->cfg.guest_feature = 0; dev->cfg.queue_address = 0; vioblk_update_qa(dev); dev->cfg.queue_size = 0; vioblk_update_qs(dev); dev->cfg.queue_select = 0; dev->cfg.queue_notify = 0; dev->cfg.isr_status = 0; dev->vq[0].last_avail = 0; vcpu_deassert_pic_irq(dev->vm_id, 0, dev->irq); } break; default: break; } } else { switch (reg) { case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI: switch (sz) { case 4: *data = (uint32_t)(dev->sz); break; case 2: *data &= 0xFFFF0000; *data |= (uint32_t)(dev->sz) & 0xFFFF; break; case 1: *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->sz) & 0xFF; break; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 1: if (sz == 1) { *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->sz >> 8) & 0xFF; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 2: if (sz == 1) { *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->sz >> 16) & 0xFF; } else if (sz == 2) { *data &= 0xFFFF0000; *data |= (uint32_t)(dev->sz >> 16) & 0xFFFF; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 3: if (sz == 1) { *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->sz >> 24) & 0xFF; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 4: switch (sz) { case 4: *data = (uint32_t)(dev->sz >> 32); break; case 2: *data &= 0xFFFF0000; *data |= (uint32_t)(dev->sz >> 32) & 0xFFFF; break; case 1: *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->sz >> 32) & 0xFF; break; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 5: if (sz == 1) { *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->sz >> 40) & 0xFF; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 6: if (sz == 1) { *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->sz >> 48) & 0xFF; } else if (sz == 2) { *data &= 0xFFFF0000; *data |= (uint32_t)(dev->sz >> 48) & 0xFFFF; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 7: if (sz == 1) { *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->sz >> 56) & 0xFF; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 8: switch (sz) { case 4: *data = (uint32_t)(dev->max_xfer); break; case 2: *data &= 0xFFFF0000; *data |= (uint32_t)(dev->max_xfer) & 0xFFFF; break; case 1: *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->max_xfer) & 0xFF; break; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 9: if (sz == 1) { *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->max_xfer >> 8) & 0xFF; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 10: if (sz == 1) { *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->max_xfer >> 16) & 0xFF; } else if (sz == 2) { *data &= 0xFFFF0000; *data |= (uint32_t)(dev->max_xfer >> 16) & 0xFFFF; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 11: if (sz == 1) { *data &= 0xFFFFFF00; *data |= (uint32_t)(dev->max_xfer >> 24) & 0xFF; } /* XXX handle invalid sz */ break; case VIRTIO_CONFIG_DEVICE_FEATURES: *data = dev->cfg.device_feature; break; case VIRTIO_CONFIG_GUEST_FEATURES: *data = dev->cfg.guest_feature; break; case VIRTIO_CONFIG_QUEUE_ADDRESS: *data = dev->cfg.queue_address; break; case VIRTIO_CONFIG_QUEUE_SIZE: if (sz == 4) *data = dev->cfg.queue_size; else if (sz == 2) { *data &= 0xFFFF0000; *data |= (uint16_t)dev->cfg.queue_size; } else if (sz == 1) { *data &= 0xFFFFFF00; *data |= (uint8_t)dev->cfg.queue_size; } break; case VIRTIO_CONFIG_QUEUE_SELECT: *data = dev->cfg.queue_select; break; case VIRTIO_CONFIG_QUEUE_NOTIFY: *data = dev->cfg.queue_notify; break; case VIRTIO_CONFIG_DEVICE_STATUS: if (sz == 4) *data = dev->cfg.device_status; else if (sz == 2) { *data &= 0xFFFF0000; *data |= (uint16_t)dev->cfg.device_status; } else if (sz == 1) { *data &= 0xFFFFFF00; *data |= (uint8_t)dev->cfg.device_status; } break; case VIRTIO_CONFIG_ISR_STATUS: *data = dev->cfg.isr_status; dev->cfg.isr_status = 0; vcpu_deassert_pic_irq(dev->vm_id, 0, dev->irq); break; } } return (0); } int virtio_net_io(int dir, uint16_t reg, uint32_t *data, uint8_t *intr, void *cookie, uint8_t sz) { struct vionet_dev *dev = (struct vionet_dev *)cookie; *intr = 0xFF; mutex_lock(&dev->mutex); if (dir == 0) { switch (reg) { case VIRTIO_CONFIG_DEVICE_FEATURES: case VIRTIO_CONFIG_QUEUE_SIZE: case VIRTIO_CONFIG_ISR_STATUS: log_warnx("%s: illegal write %x to %s", __progname, *data, virtio_reg_name(reg)); break; case VIRTIO_CONFIG_GUEST_FEATURES: dev->cfg.guest_feature = *data; break; case VIRTIO_CONFIG_QUEUE_ADDRESS: dev->cfg.queue_address = *data; vionet_update_qa(dev); break; case VIRTIO_CONFIG_QUEUE_SELECT: dev->cfg.queue_select = *data; vionet_update_qs(dev); break; case VIRTIO_CONFIG_QUEUE_NOTIFY: dev->cfg.queue_notify = *data; if (vionet_notifyq(dev)) *intr = 1; break; case VIRTIO_CONFIG_DEVICE_STATUS: dev->cfg.device_status = *data; if (dev->cfg.device_status == 0) { log_debug("%s: device reset", __func__); dev->cfg.guest_feature = 0; dev->cfg.queue_address = 0; vionet_update_qa(dev); dev->cfg.queue_size = 0; vionet_update_qs(dev); dev->cfg.queue_select = 0; dev->cfg.queue_notify = 0; dev->cfg.isr_status = 0; dev->vq[RXQ].last_avail = 0; dev->vq[RXQ].notified_avail = 0; dev->vq[TXQ].last_avail = 0; dev->vq[TXQ].notified_avail = 0; vcpu_deassert_pic_irq(dev->vm_id, 0, dev->irq); } break; default: break; } } else { switch (reg) { case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI: case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 1: case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 2: case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 3: case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 4: case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 5: *data = dev->mac[reg - VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI]; break; case VIRTIO_CONFIG_DEVICE_FEATURES: *data = dev->cfg.device_feature; break; case VIRTIO_CONFIG_GUEST_FEATURES: *data = dev->cfg.guest_feature; break; case VIRTIO_CONFIG_QUEUE_ADDRESS: *data = dev->cfg.queue_address; break; case VIRTIO_CONFIG_QUEUE_SIZE: *data = dev->cfg.queue_size; break; case VIRTIO_CONFIG_QUEUE_SELECT: *data = dev->cfg.queue_select; break; case VIRTIO_CONFIG_QUEUE_NOTIFY: *data = dev->cfg.queue_notify; break; case VIRTIO_CONFIG_DEVICE_STATUS: *data = dev->cfg.device_status; break; case VIRTIO_CONFIG_ISR_STATUS: *data = dev->cfg.isr_status; dev->cfg.isr_status = 0; vcpu_deassert_pic_irq(dev->vm_id, 0, dev->irq); break; } } mutex_unlock(&dev->mutex); return (0); } /* * Must be called with dev->mutex acquired. */ void vionet_update_qa(struct vionet_dev *dev) { /* Invalid queue? */ if (dev->cfg.queue_select > 1) return; dev->vq[dev->cfg.queue_select].qa = dev->cfg.queue_address; } /* * Must be called with dev->mutex acquired. */ void vionet_update_qs(struct vionet_dev *dev) { /* Invalid queue? */ if (dev->cfg.queue_select > 1) { dev->cfg.queue_size = 0; return; } /* Update queue address/size based on queue select */ dev->cfg.queue_address = dev->vq[dev->cfg.queue_select].qa; dev->cfg.queue_size = dev->vq[dev->cfg.queue_select].qs; } /* * vionet_enq_rx * * Take a given packet from the host-side tap and copy it into the guest's * buffers utilizing the rx virtio ring. If the packet length is invalid * (too small or too large) or if there are not enough buffers available, * the packet is dropped. * * Must be called with dev->mutex acquired. */ int vionet_enq_rx(struct vionet_dev *dev, char *pkt, size_t sz, int *spc) { uint64_t q_gpa; uint32_t vr_sz; uint16_t dxx, idx, hdr_desc_idx, chain_hdr_idx; int ret = 0; char *vr = NULL; size_t bufsz = 0, off = 0, pkt_offset = 0, chunk_size = 0; size_t chain_len = 0; struct vring_desc *desc, *pkt_desc, *hdr_desc; struct vring_avail *avail; struct vring_used *used; struct vring_used_elem *ue; struct virtio_net_hdr hdr; size_t hdr_sz; if (sz < VIONET_MIN_TXLEN || sz > VIONET_MAX_TXLEN) { log_warn("%s: invalid packet size", __func__); return (0); } hdr_sz = sizeof(hdr); if (!(dev->cfg.device_status & VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK)) return ret; vr_sz = vring_size(VIONET_QUEUE_SIZE); q_gpa = dev->vq[RXQ].qa; q_gpa = q_gpa * VIRTIO_PAGE_SIZE; vr = calloc(1, vr_sz); if (vr == NULL) { log_warn("rx enq: calloc error getting vionet ring"); return (0); } if (read_mem(q_gpa, vr, vr_sz)) { log_warnx("rx enq: error reading gpa 0x%llx", q_gpa); goto out; } /* Compute offsets in ring of descriptors, avail ring, and used ring */ desc = (struct vring_desc *)(vr); avail = (struct vring_avail *)(vr + dev->vq[RXQ].vq_availoffset); used = (struct vring_used *)(vr + dev->vq[RXQ].vq_usedoffset); idx = dev->vq[RXQ].last_avail & VIONET_QUEUE_MASK; if ((dev->vq[RXQ].notified_avail & VIONET_QUEUE_MASK) == idx) { log_debug("%s: insufficient available buffer capacity, " "dropping packet.", __func__); goto out; } hdr_desc_idx = avail->ring[idx] & VIONET_QUEUE_MASK; hdr_desc = &desc[hdr_desc_idx]; dxx = hdr_desc_idx; chain_hdr_idx = dxx; chain_len = 0; /* Process the descriptor and walk any potential chain. */ do { off = 0; pkt_desc = &desc[dxx]; if (!(pkt_desc->flags & VRING_DESC_F_WRITE)) { log_warnx("%s: invalid descriptor, not writable", __func__); goto out; } /* How much data do we get to write? */ if (sz - bufsz > pkt_desc->len) chunk_size = pkt_desc->len; else chunk_size = sz - bufsz; if (chain_len == 0) { off = hdr_sz; if (chunk_size == pkt_desc->len) chunk_size -= off; } /* Write a chunk of data if we need to */ if (chunk_size && write_mem(pkt_desc->addr + off, pkt + pkt_offset, chunk_size)) { log_warnx("%s: failed to write to buffer 0x%llx", __func__, pkt_desc->addr); goto out; } chain_len += chunk_size + off; bufsz += chunk_size; pkt_offset += chunk_size; dxx = pkt_desc->next & VIONET_QUEUE_MASK; } while (bufsz < sz && pkt_desc->flags & VRING_DESC_F_NEXT); /* Update the list of used buffers. */ ue = &used->ring[(used->idx) & VIONET_QUEUE_MASK]; ue->id = chain_hdr_idx; ue->len = chain_len; off = ((char *)ue - vr); if (write_mem(q_gpa + off, ue, sizeof(*ue))) { log_warnx("%s: error updating rx used ring", __func__); goto out; } /* Move our marker in the ring...*/ used->idx++; dev->vq[RXQ].last_avail = (dev->vq[RXQ].last_avail + 1) & VIONET_QUEUE_MASK; /* Prepend the virtio net header in the first buffer. */ memset(&hdr, 0, sizeof(hdr)); hdr.hdr_len = hdr_sz; if (write_mem(hdr_desc->addr, &hdr, hdr_sz)) { log_warnx("vionet: rx enq header write_mem error @ 0x%llx", hdr_desc->addr); goto out; } /* Update the index field in the used ring. This must be done last. */ dev->cfg.isr_status = 1; off = (char *)&used->idx - vr; *spc = (dev->vq[RXQ].notified_avail - dev->vq[RXQ].last_avail) & VIONET_QUEUE_MASK; if (write_mem(q_gpa + off, &used->idx, sizeof(used->idx))) log_warnx("vionet: error writing vio ring"); ret = 1; out: free(vr); return (ret); } /* * vionet_rx * * Enqueue data that was received on a tap file descriptor * to the vionet device queue. * * Must be called with dev->mutex acquired. */ static int vionet_rx(struct vionet_dev *dev) { char buf[PAGE_SIZE]; int num_enq = 0, spc = 0; struct ether_header *eh; ssize_t sz; do { sz = read(dev->fd, buf, sizeof(buf)); if (sz == -1) { /* * If we get EAGAIN, No data is currently available. * Do not treat this as an error. */ if (errno != EAGAIN) log_warn("unexpected read error on vionet " "device"); } else if (sz > 0) { eh = (struct ether_header *)buf; if (!dev->lockedmac || ETHER_IS_MULTICAST(eh->ether_dhost) || memcmp(eh->ether_dhost, dev->mac, sizeof(eh->ether_dhost)) == 0) num_enq += vionet_enq_rx(dev, buf, sz, &spc); } else if (sz == 0) { log_debug("process_rx: no data"); break; } } while (spc > 0 && sz > 0); return (num_enq); } /* * vionet_rx_event * * Called from the event handling thread when new data can be * received on the tap fd of a vionet device. */ static void vionet_rx_event(int fd, short kind, void *arg) { struct vionet_dev *dev = arg; mutex_lock(&dev->mutex); if (vionet_rx(dev) > 0) { /* XXX: vcpu_id */ vcpu_assert_pic_irq(dev->vm_id, 0, dev->irq); } mutex_unlock(&dev->mutex); } /* * Must be called with dev->mutex acquired. */ void vionet_notify_rx(struct vionet_dev *dev) { uint64_t q_gpa; uint32_t vr_sz; char *vr; struct vring_avail *avail; vr_sz = vring_size(VIONET_QUEUE_SIZE); q_gpa = dev->vq[RXQ].qa; q_gpa = q_gpa * VIRTIO_PAGE_SIZE; vr = malloc(vr_sz); if (vr == NULL) { log_warn("malloc error getting vionet ring"); return; } if (read_mem(q_gpa, vr, vr_sz)) { log_warnx("error reading gpa 0x%llx", q_gpa); free(vr); return; } /* Compute offset into avail ring */ avail = (struct vring_avail *)(vr + dev->vq[RXQ].vq_availoffset); dev->vq[RXQ].notified_avail = avail->idx - 1; free(vr); } /* * Must be called with dev->mutex acquired. */ int vionet_notifyq(struct vionet_dev *dev) { int ret; switch (dev->cfg.queue_notify) { case RXQ: vionet_notify_rx(dev); ret = 0; break; case TXQ: ret = vionet_notify_tx(dev); break; default: /* * Catch the unimplemented queue ID 2 (control queue) as * well as any bogus queue IDs. */ log_debug("%s: notify for unimplemented queue ID %d", __func__, dev->cfg.queue_notify); ret = 0; break; } return (ret); } /* * Must be called with dev->mutex acquired. */ int vionet_notify_tx(struct vionet_dev *dev) { uint64_t q_gpa; uint32_t vr_sz; uint16_t idx, pkt_desc_idx, hdr_desc_idx, dxx, cnt; size_t pktsz, chunk_size = 0; ssize_t dhcpsz; int ret, num_enq, ofs, spc; char *vr, *pkt, *dhcppkt; struct vring_desc *desc, *pkt_desc, *hdr_desc; struct vring_avail *avail; struct vring_used *used; struct ether_header *eh; dhcpsz = 0; vr = pkt = dhcppkt = NULL; ret = spc = 0; vr_sz = vring_size(VIONET_QUEUE_SIZE); q_gpa = dev->vq[TXQ].qa; q_gpa = q_gpa * VIRTIO_PAGE_SIZE; vr = calloc(1, vr_sz); if (vr == NULL) { log_warn("calloc error getting vionet ring"); goto out; } if (read_mem(q_gpa, vr, vr_sz)) { log_warnx("error reading gpa 0x%llx", q_gpa); goto out; } /* Compute offsets in ring of descriptors, avail ring, and used ring */ desc = (struct vring_desc *)(vr); avail = (struct vring_avail *)(vr + dev->vq[TXQ].vq_availoffset); used = (struct vring_used *)(vr + dev->vq[TXQ].vq_usedoffset); num_enq = 0; idx = dev->vq[TXQ].last_avail & VIONET_QUEUE_MASK; if ((avail->idx & VIONET_QUEUE_MASK) == idx) { log_warnx("vionet tx queue notify - nothing to do?"); goto out; } while ((avail->idx & VIONET_QUEUE_MASK) != idx) { hdr_desc_idx = avail->ring[idx] & VIONET_QUEUE_MASK; hdr_desc = &desc[hdr_desc_idx]; pktsz = 0; cnt = 0; dxx = hdr_desc_idx; do { pktsz += desc[dxx].len; dxx = desc[dxx].next & VIONET_QUEUE_MASK; /* * Virtio 1.0, cs04, section 2.4.5: * "The number of descriptors in the table is defined * by the queue size for this virtqueue: this is the * maximum possible descriptor chain length." */ if (++cnt >= VIONET_QUEUE_SIZE) { log_warnx("%s: descriptor table invalid", __func__); goto out; } } while (desc[dxx].flags & VRING_DESC_F_NEXT); pktsz += desc[dxx].len; /* Remove virtio header descriptor len */ pktsz -= hdr_desc->len; /* Drop packets violating device MTU-based limits */ if (pktsz < VIONET_MIN_TXLEN || pktsz > VIONET_MAX_TXLEN) { log_warnx("%s: invalid packet size %lu", __func__, pktsz); goto drop_packet; } pkt = malloc(pktsz); if (pkt == NULL) { log_warn("malloc error alloc packet buf"); goto out; } ofs = 0; pkt_desc_idx = hdr_desc->next & VIONET_QUEUE_MASK; pkt_desc = &desc[pkt_desc_idx]; while (pkt_desc->flags & VRING_DESC_F_NEXT) { /* must be not writable */ if (pkt_desc->flags & VRING_DESC_F_WRITE) { log_warnx("unexpected writable tx desc " "%d", pkt_desc_idx); goto out; } /* Check we don't read beyond allocated pktsz */ if (pkt_desc->len > pktsz - ofs) { log_warnx("%s: descriptor len past pkt len", __func__); chunk_size = pktsz - ofs; } else chunk_size = pkt_desc->len; /* Read packet from descriptor ring */ if (read_mem(pkt_desc->addr, pkt + ofs, chunk_size)) { log_warnx("vionet: packet read_mem error " "@ 0x%llx", pkt_desc->addr); goto out; } ofs += pkt_desc->len; pkt_desc_idx = pkt_desc->next & VIONET_QUEUE_MASK; pkt_desc = &desc[pkt_desc_idx]; } /* Now handle tail descriptor - must be not writable */ if (pkt_desc->flags & VRING_DESC_F_WRITE) { log_warnx("unexpected writable tx descriptor %d", pkt_desc_idx); goto out; } /* Check we don't read beyond allocated pktsz */ if (pkt_desc->len > pktsz - ofs) { log_warnx("%s: descriptor len past pkt len", __func__); chunk_size = pktsz - ofs - pkt_desc->len; } else chunk_size = pkt_desc->len; /* Read packet from descriptor ring */ if (read_mem(pkt_desc->addr, pkt + ofs, chunk_size)) { log_warnx("vionet: packet read_mem error @ " "0x%llx", pkt_desc->addr); goto out; } /* reject other source addresses */ if (dev->lockedmac && pktsz >= ETHER_HDR_LEN && (eh = (struct ether_header *)pkt) && memcmp(eh->ether_shost, dev->mac, sizeof(eh->ether_shost)) != 0) log_debug("vionet: wrong source address %s for vm %d", ether_ntoa((struct ether_addr *) eh->ether_shost), dev->vm_id); else if (dev->local && (dhcpsz = dhcp_request(dev, pkt, pktsz, &dhcppkt)) != -1) { log_debug("vionet: dhcp request," " local response size %zd", dhcpsz); /* XXX signed vs unsigned here, funky cast */ } else if (write(dev->fd, pkt, pktsz) != (int)pktsz) { log_warnx("vionet: tx failed writing to tap: " "%d", errno); goto out; } drop_packet: ret = 1; dev->cfg.isr_status = 1; used->ring[used->idx & VIONET_QUEUE_MASK].id = hdr_desc_idx; used->ring[used->idx & VIONET_QUEUE_MASK].len = hdr_desc->len; used->idx++; dev->vq[TXQ].last_avail++; num_enq++; idx = dev->vq[TXQ].last_avail & VIONET_QUEUE_MASK; free(pkt); pkt = NULL; } if (write_mem(q_gpa, vr, vr_sz)) { log_warnx("vionet: tx error writing vio ring"); } if (dhcpsz > 0) { if (vionet_enq_rx(dev, dhcppkt, dhcpsz, &spc)) ret = 1; } out: free(vr); free(pkt); free(dhcppkt); return (ret); } int vmmci_ctl(unsigned int cmd) { struct timeval tv = { 0, 0 }; if ((vmmci.cfg.device_status & VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK) == 0) return (-1); if (cmd == vmmci.cmd) return (0); switch (cmd) { case VMMCI_NONE: break; case VMMCI_SHUTDOWN: case VMMCI_REBOOT: /* Update command */ vmmci.cmd = cmd; /* * vmm VMs do not support powerdown, send a reboot request * instead and turn it off after the triple fault. */ if (cmd == VMMCI_SHUTDOWN) cmd = VMMCI_REBOOT; /* Trigger interrupt */ vmmci.cfg.isr_status = VIRTIO_CONFIG_ISR_CONFIG_CHANGE; vcpu_assert_pic_irq(vmmci.vm_id, 0, vmmci.irq); /* Add ACK timeout */ tv.tv_sec = VMMCI_TIMEOUT; evtimer_add(&vmmci.timeout, &tv); break; case VMMCI_SYNCRTC: if (vmmci.cfg.guest_feature & VMMCI_F_SYNCRTC) { /* RTC updated, request guest VM resync of its RTC */ vmmci.cmd = cmd; vmmci.cfg.isr_status = VIRTIO_CONFIG_ISR_CONFIG_CHANGE; vcpu_assert_pic_irq(vmmci.vm_id, 0, vmmci.irq); } else { log_debug("%s: RTC sync skipped (guest does not " "support RTC sync)\n", __func__); } break; default: fatalx("invalid vmmci command: %d", cmd); } return (0); } void vmmci_ack(unsigned int cmd) { struct timeval tv = { 0, 0 }; switch (cmd) { case VMMCI_NONE: break; case VMMCI_SHUTDOWN: /* * The shutdown was requested by the VM if we don't have * a pending shutdown request. In this case add a short * timeout to give the VM a chance to reboot before the * timer is expired. */ if (vmmci.cmd == 0) { log_debug("%s: vm %u requested shutdown", __func__, vmmci.vm_id); tv.tv_sec = VMMCI_TIMEOUT; evtimer_add(&vmmci.timeout, &tv); return; } /* FALLTHROUGH */ case VMMCI_REBOOT: /* * If the VM acknowleged our shutdown request, give it * enough time to shutdown or reboot gracefully. This * might take a considerable amount of time (running * rc.shutdown on the VM), so increase the timeout before * killing it forcefully. */ if (cmd == vmmci.cmd && evtimer_pending(&vmmci.timeout, NULL)) { log_debug("%s: vm %u acknowledged shutdown request", __func__, vmmci.vm_id); tv.tv_sec = VMMCI_SHUTDOWN_TIMEOUT; evtimer_add(&vmmci.timeout, &tv); } break; case VMMCI_SYNCRTC: log_debug("%s: vm %u acknowledged RTC sync request", __func__, vmmci.vm_id); vmmci.cmd = VMMCI_NONE; break; default: log_warnx("%s: illegal request %u", __func__, cmd); break; } } void vmmci_timeout(int fd, short type, void *arg) { log_debug("%s: vm %u shutdown", __progname, vmmci.vm_id); vm_shutdown(vmmci.cmd == VMMCI_REBOOT ? VMMCI_REBOOT : VMMCI_SHUTDOWN); } int vmmci_io(int dir, uint16_t reg, uint32_t *data, uint8_t *intr, void *unused, uint8_t sz) { *intr = 0xFF; if (dir == 0) { switch (reg) { case VIRTIO_CONFIG_DEVICE_FEATURES: case VIRTIO_CONFIG_QUEUE_SIZE: case VIRTIO_CONFIG_ISR_STATUS: log_warnx("%s: illegal write %x to %s", __progname, *data, virtio_reg_name(reg)); break; case VIRTIO_CONFIG_GUEST_FEATURES: vmmci.cfg.guest_feature = *data; break; case VIRTIO_CONFIG_QUEUE_ADDRESS: vmmci.cfg.queue_address = *data; break; case VIRTIO_CONFIG_QUEUE_SELECT: vmmci.cfg.queue_select = *data; break; case VIRTIO_CONFIG_QUEUE_NOTIFY: vmmci.cfg.queue_notify = *data; break; case VIRTIO_CONFIG_DEVICE_STATUS: vmmci.cfg.device_status = *data; break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI: vmmci_ack(*data); break; } } else { switch (reg) { case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI: *data = vmmci.cmd; break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 4: /* Update time once when reading the first register */ gettimeofday(&vmmci.time, NULL); *data = (uint64_t)vmmci.time.tv_sec; break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 8: *data = (uint64_t)vmmci.time.tv_sec << 32; break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 12: *data = (uint64_t)vmmci.time.tv_usec; break; case VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI + 16: *data = (uint64_t)vmmci.time.tv_usec << 32; break; case VIRTIO_CONFIG_DEVICE_FEATURES: *data = vmmci.cfg.device_feature; break; case VIRTIO_CONFIG_GUEST_FEATURES: *data = vmmci.cfg.guest_feature; break; case VIRTIO_CONFIG_QUEUE_ADDRESS: *data = vmmci.cfg.queue_address; break; case VIRTIO_CONFIG_QUEUE_SIZE: *data = vmmci.cfg.queue_size; break; case VIRTIO_CONFIG_QUEUE_SELECT: *data = vmmci.cfg.queue_select; break; case VIRTIO_CONFIG_QUEUE_NOTIFY: *data = vmmci.cfg.queue_notify; break; case VIRTIO_CONFIG_DEVICE_STATUS: *data = vmmci.cfg.device_status; break; case VIRTIO_CONFIG_ISR_STATUS: *data = vmmci.cfg.isr_status; vmmci.cfg.isr_status = 0; vcpu_deassert_pic_irq(vmmci.vm_id, 0, vmmci.irq); break; } } return (0); } int virtio_get_base(int fd, char *path, size_t npath, int type, const char *dpath) { switch (type) { case VMDF_RAW: return 0; case VMDF_QCOW2: return virtio_qcow2_get_base(fd, path, npath, dpath); } log_warnx("%s: invalid disk format", __func__); return -1; } /* * Initializes a struct virtio_backing using the list of fds. */ static int virtio_init_disk(struct virtio_backing *file, off_t *sz, int *fd, size_t nfd, int type) { /* * probe disk types in order of preference, first one to work wins. * TODO: provide a way of specifying the type and options. */ switch (type) { case VMDF_RAW: return virtio_raw_init(file, sz, fd, nfd); case VMDF_QCOW2: return virtio_qcow2_init(file, sz, fd, nfd); } log_warnx("%s: invalid disk format", __func__); return -1; } void virtio_init(struct vmd_vm *vm, int child_cdrom, int child_disks[][VM_MAX_BASE_PER_DISK], int *child_taps) { struct vmop_create_params *vmc = &vm->vm_params; struct vm_create_params *vcp = &vmc->vmc_params; uint8_t id; uint8_t i; int ret; /* Virtio entropy device */ if (pci_add_device(&id, PCI_VENDOR_QUMRANET, PCI_PRODUCT_QUMRANET_VIO_RNG, PCI_CLASS_SYSTEM, PCI_SUBCLASS_SYSTEM_MISC, PCI_VENDOR_OPENBSD, PCI_PRODUCT_VIRTIO_ENTROPY, 1, NULL)) { log_warnx("%s: can't add PCI virtio rng device", __progname); return; } if (pci_add_bar(id, PCI_MAPREG_TYPE_IO, virtio_rnd_io, NULL)) { log_warnx("%s: can't add bar for virtio rng device", __progname); return; } memset(&viornd, 0, sizeof(viornd)); viornd.vq[0].qs = VIORND_QUEUE_SIZE; viornd.vq[0].vq_availoffset = sizeof(struct vring_desc) * VIORND_QUEUE_SIZE; viornd.vq[0].vq_usedoffset = VIRTQUEUE_ALIGN( sizeof(struct vring_desc) * VIORND_QUEUE_SIZE + sizeof(uint16_t) * (2 + VIORND_QUEUE_SIZE)); viornd.pci_id = id; viornd.irq = pci_get_dev_irq(id); viornd.vm_id = vcp->vcp_id; if (vcp->vcp_nnics > 0) { vionet = calloc(vcp->vcp_nnics, sizeof(struct vionet_dev)); if (vionet == NULL) { log_warn("%s: calloc failure allocating vionets", __progname); return; } nr_vionet = vcp->vcp_nnics; /* Virtio network */ for (i = 0; i < vcp->vcp_nnics; i++) { if (pci_add_device(&id, PCI_VENDOR_QUMRANET, PCI_PRODUCT_QUMRANET_VIO_NET, PCI_CLASS_SYSTEM, PCI_SUBCLASS_SYSTEM_MISC, PCI_VENDOR_OPENBSD, PCI_PRODUCT_VIRTIO_NETWORK, 1, NULL)) { log_warnx("%s: can't add PCI virtio net device", __progname); return; } if (pci_add_bar(id, PCI_MAPREG_TYPE_IO, virtio_net_io, &vionet[i])) { log_warnx("%s: can't add bar for virtio net " "device", __progname); return; } ret = pthread_mutex_init(&vionet[i].mutex, NULL); if (ret) { errno = ret; log_warn("%s: could not initialize mutex " "for vionet device", __progname); return; } vionet[i].vq[RXQ].qs = VIONET_QUEUE_SIZE; vionet[i].vq[RXQ].vq_availoffset = sizeof(struct vring_desc) * VIONET_QUEUE_SIZE; vionet[i].vq[RXQ].vq_usedoffset = VIRTQUEUE_ALIGN( sizeof(struct vring_desc) * VIONET_QUEUE_SIZE + sizeof(uint16_t) * (2 + VIONET_QUEUE_SIZE)); vionet[i].vq[RXQ].last_avail = 0; vionet[i].vq[RXQ].notified_avail = 0; vionet[i].vq[TXQ].qs = VIONET_QUEUE_SIZE; vionet[i].vq[TXQ].vq_availoffset = sizeof(struct vring_desc) * VIONET_QUEUE_SIZE; vionet[i].vq[TXQ].vq_usedoffset = VIRTQUEUE_ALIGN( sizeof(struct vring_desc) * VIONET_QUEUE_SIZE + sizeof(uint16_t) * (2 + VIONET_QUEUE_SIZE)); vionet[i].vq[TXQ].last_avail = 0; vionet[i].vq[TXQ].notified_avail = 0; vionet[i].fd = child_taps[i]; vionet[i].vm_id = vcp->vcp_id; vionet[i].vm_vmid = vm->vm_vmid; vionet[i].irq = pci_get_dev_irq(id); event_set(&vionet[i].event, vionet[i].fd, EV_READ | EV_PERSIST, vionet_rx_event, &vionet[i]); if (event_add(&vionet[i].event, NULL)) { log_warn("could not initialize vionet event " "handler"); return; } /* MAC address has been assigned by the parent */ memcpy(&vionet[i].mac, &vcp->vcp_macs[i], 6); vionet[i].cfg.device_feature = VIRTIO_NET_F_MAC; vionet[i].lockedmac = vmc->vmc_ifflags[i] & VMIFF_LOCKED ? 1 : 0; vionet[i].local = vmc->vmc_ifflags[i] & VMIFF_LOCAL ? 1 : 0; if (i == 0 && vmc->vmc_bootdevice & VMBOOTDEV_NET) vionet[i].pxeboot = 1; vionet[i].idx = i; vionet[i].pci_id = id; log_debug("%s: vm \"%s\" vio%u lladdr %s%s%s%s", __func__, vcp->vcp_name, i, ether_ntoa((void *)vionet[i].mac), vionet[i].lockedmac ? ", locked" : "", vionet[i].local ? ", local" : "", vionet[i].pxeboot ? ", pxeboot" : ""); } } if (vcp->vcp_ndisks > 0) { nr_vioblk = vcp->vcp_ndisks; vioblk = calloc(vcp->vcp_ndisks, sizeof(struct vioblk_dev)); if (vioblk == NULL) { log_warn("%s: calloc failure allocating vioblks", __progname); return; } /* One virtio block device for each disk defined in vcp */ for (i = 0; i < vcp->vcp_ndisks; i++) { if (pci_add_device(&id, PCI_VENDOR_QUMRANET, PCI_PRODUCT_QUMRANET_VIO_BLOCK, PCI_CLASS_MASS_STORAGE, PCI_SUBCLASS_MASS_STORAGE_SCSI, PCI_VENDOR_OPENBSD, PCI_PRODUCT_VIRTIO_BLOCK, 1, NULL)) { log_warnx("%s: can't add PCI virtio block " "device", __progname); return; } if (pci_add_bar(id, PCI_MAPREG_TYPE_IO, virtio_blk_io, &vioblk[i])) { log_warnx("%s: can't add bar for virtio block " "device", __progname); return; } vioblk[i].vq[0].qs = VIOBLK_QUEUE_SIZE; vioblk[i].vq[0].vq_availoffset = sizeof(struct vring_desc) * VIOBLK_QUEUE_SIZE; vioblk[i].vq[0].vq_usedoffset = VIRTQUEUE_ALIGN( sizeof(struct vring_desc) * VIOBLK_QUEUE_SIZE + sizeof(uint16_t) * (2 + VIOBLK_QUEUE_SIZE)); vioblk[i].vq[0].last_avail = 0; vioblk[i].cfg.device_feature = VIRTIO_BLK_F_SIZE_MAX; vioblk[i].max_xfer = 1048576; vioblk[i].pci_id = id; vioblk[i].vm_id = vcp->vcp_id; vioblk[i].irq = pci_get_dev_irq(id); if (virtio_init_disk(&vioblk[i].file, &vioblk[i].sz, child_disks[i], vmc->vmc_diskbases[i], vmc->vmc_disktypes[i]) == -1) { log_warnx("%s: unable to determine disk format", __func__); return; } vioblk[i].sz /= 512; } } /* vioscsi cdrom */ if (strlen(vcp->vcp_cdrom)) { vioscsi = calloc(1, sizeof(struct vioscsi_dev)); if (vioscsi == NULL) { log_warn("%s: calloc failure allocating vioscsi", __progname); return; } if (pci_add_device(&id, PCI_VENDOR_QUMRANET, PCI_PRODUCT_QUMRANET_VIO_SCSI, PCI_CLASS_MASS_STORAGE, PCI_SUBCLASS_MASS_STORAGE_SCSI, PCI_VENDOR_OPENBSD, PCI_PRODUCT_VIRTIO_SCSI, 1, NULL)) { log_warnx("%s: can't add PCI vioscsi device", __progname); return; } if (pci_add_bar(id, PCI_MAPREG_TYPE_IO, vioscsi_io, vioscsi)) { log_warnx("%s: can't add bar for vioscsi device", __progname); return; } for ( i = 0; i < VIRTIO_MAX_QUEUES; i++) { vioscsi->vq[i].qs = VIOSCSI_QUEUE_SIZE; vioscsi->vq[i].vq_availoffset = sizeof(struct vring_desc) * VIOSCSI_QUEUE_SIZE; vioscsi->vq[i].vq_usedoffset = VIRTQUEUE_ALIGN( sizeof(struct vring_desc) * VIOSCSI_QUEUE_SIZE + sizeof(uint16_t) * (2 + VIOSCSI_QUEUE_SIZE)); vioscsi->vq[i].last_avail = 0; } if (virtio_init_disk(&vioscsi->file, &vioscsi->sz, &child_cdrom, 1, VMDF_RAW) == -1) { log_warnx("%s: unable to determine iso format", __func__); return; } vioscsi->locked = 0; vioscsi->lba = 0; vioscsi->n_blocks = vioscsi->sz >> 11; /* num of 2048 blocks in file */ vioscsi->max_xfer = VIOSCSI_BLOCK_SIZE_CDROM; vioscsi->pci_id = id; vioscsi->vm_id = vcp->vcp_id; vioscsi->irq = pci_get_dev_irq(id); } /* virtio control device */ if (pci_add_device(&id, PCI_VENDOR_OPENBSD, PCI_PRODUCT_OPENBSD_CONTROL, PCI_CLASS_COMMUNICATIONS, PCI_SUBCLASS_COMMUNICATIONS_MISC, PCI_VENDOR_OPENBSD, PCI_PRODUCT_VIRTIO_VMMCI, 1, NULL)) { log_warnx("%s: can't add PCI vmm control device", __progname); return; } if (pci_add_bar(id, PCI_MAPREG_TYPE_IO, vmmci_io, NULL)) { log_warnx("%s: can't add bar for vmm control device", __progname); return; } memset(&vmmci, 0, sizeof(vmmci)); vmmci.cfg.device_feature = VMMCI_F_TIMESYNC | VMMCI_F_ACK | VMMCI_F_SYNCRTC; vmmci.vm_id = vcp->vcp_id; vmmci.irq = pci_get_dev_irq(id); vmmci.pci_id = id; evtimer_set(&vmmci.timeout, vmmci_timeout, NULL); } /* * vionet_set_hostmac * * Sets the hardware address for the host-side tap(4) on a vionet_dev. * * This should only be called from the event-loop thread * * vm: pointer to the current vmd_vm instance * idx: index into the array of vionet_dev's for the target vionet_dev * addr: ethernet address to set */ void vionet_set_hostmac(struct vmd_vm *vm, unsigned int idx, uint8_t *addr) { struct vmop_create_params *vmc = &vm->vm_params; struct vm_create_params *vcp = &vmc->vmc_params; struct vionet_dev *dev; if (idx > vcp->vcp_nnics) fatalx("vionet_set_hostmac"); dev = &vionet[idx]; memcpy(dev->hostmac, addr, sizeof(dev->hostmac)); } void virtio_shutdown(struct vmd_vm *vm) { int i; /* ensure that our disks are synced */ if (vioscsi != NULL) vioscsi->file.close(vioscsi->file.p, 0); for (i = 0; i < nr_vioblk; i++) vioblk[i].file.close(vioblk[i].file.p, 0); } int vmmci_restore(int fd, uint32_t vm_id) { log_debug("%s: receiving vmmci", __func__); if (atomicio(read, fd, &vmmci, sizeof(vmmci)) != sizeof(vmmci)) { log_warnx("%s: error reading vmmci from fd", __func__); return (-1); } if (pci_set_bar_fn(vmmci.pci_id, 0, vmmci_io, NULL)) { log_warnx("%s: can't set bar fn for vmm control device", __progname); return (-1); } vmmci.vm_id = vm_id; vmmci.irq = pci_get_dev_irq(vmmci.pci_id); memset(&vmmci.timeout, 0, sizeof(struct event)); evtimer_set(&vmmci.timeout, vmmci_timeout, NULL); return (0); } int viornd_restore(int fd, struct vm_create_params *vcp) { log_debug("%s: receiving viornd", __func__); if (atomicio(read, fd, &viornd, sizeof(viornd)) != sizeof(viornd)) { log_warnx("%s: error reading viornd from fd", __func__); return (-1); } if (pci_set_bar_fn(viornd.pci_id, 0, virtio_rnd_io, NULL)) { log_warnx("%s: can't set bar fn for virtio rng device", __progname); return (-1); } viornd.vm_id = vcp->vcp_id; viornd.irq = pci_get_dev_irq(viornd.pci_id); return (0); } int vionet_restore(int fd, struct vmd_vm *vm, int *child_taps) { struct vmop_create_params *vmc = &vm->vm_params; struct vm_create_params *vcp = &vmc->vmc_params; uint8_t i; int ret; nr_vionet = vcp->vcp_nnics; if (vcp->vcp_nnics > 0) { vionet = calloc(vcp->vcp_nnics, sizeof(struct vionet_dev)); if (vionet == NULL) { log_warn("%s: calloc failure allocating vionets", __progname); return (-1); } log_debug("%s: receiving vionet", __func__); if (atomicio(read, fd, vionet, vcp->vcp_nnics * sizeof(struct vionet_dev)) != vcp->vcp_nnics * sizeof(struct vionet_dev)) { log_warnx("%s: error reading vionet from fd", __func__); return (-1); } /* Virtio network */ for (i = 0; i < vcp->vcp_nnics; i++) { if (pci_set_bar_fn(vionet[i].pci_id, 0, virtio_net_io, &vionet[i])) { log_warnx("%s: can't set bar fn for virtio net " "device", __progname); return (-1); } memset(&vionet[i].mutex, 0, sizeof(pthread_mutex_t)); ret = pthread_mutex_init(&vionet[i].mutex, NULL); if (ret) { errno = ret; log_warn("%s: could not initialize mutex " "for vionet device", __progname); return (-1); } vionet[i].fd = child_taps[i]; vionet[i].vm_id = vcp->vcp_id; vionet[i].vm_vmid = vm->vm_vmid; vionet[i].irq = pci_get_dev_irq(vionet[i].pci_id); memset(&vionet[i].event, 0, sizeof(struct event)); event_set(&vionet[i].event, vionet[i].fd, EV_READ | EV_PERSIST, vionet_rx_event, &vionet[i]); } } return (0); } int vioblk_restore(int fd, struct vmop_create_params *vmc, int child_disks[][VM_MAX_BASE_PER_DISK]) { struct vm_create_params *vcp = &vmc->vmc_params; uint8_t i; nr_vioblk = vcp->vcp_ndisks; vioblk = calloc(vcp->vcp_ndisks, sizeof(struct vioblk_dev)); if (vioblk == NULL) { log_warn("%s: calloc failure allocating vioblks", __progname); return (-1); } log_debug("%s: receiving vioblk", __func__); if (atomicio(read, fd, vioblk, nr_vioblk * sizeof(struct vioblk_dev)) != nr_vioblk * sizeof(struct vioblk_dev)) { log_warnx("%s: error reading vioblk from fd", __func__); return (-1); } for (i = 0; i < vcp->vcp_ndisks; i++) { if (pci_set_bar_fn(vioblk[i].pci_id, 0, virtio_blk_io, &vioblk[i])) { log_warnx("%s: can't set bar fn for virtio block " "device", __progname); return (-1); } if (virtio_init_disk(&vioblk[i].file, &vioblk[i].sz, child_disks[i], vmc->vmc_diskbases[i], vmc->vmc_disktypes[i]) == -1) { log_warnx("%s: unable to determine disk format", __func__); return (-1); } vioblk[i].vm_id = vcp->vcp_id; vioblk[i].irq = pci_get_dev_irq(vioblk[i].pci_id); } return (0); } int vioscsi_restore(int fd, struct vm_create_params *vcp, int child_cdrom) { if (!strlen(vcp->vcp_cdrom)) return (0); vioscsi = calloc(1, sizeof(struct vioscsi_dev)); if (vioscsi == NULL) { log_warn("%s: calloc failure allocating vioscsi", __progname); return (-1); } log_debug("%s: receiving vioscsi", __func__); if (atomicio(read, fd, vioscsi, sizeof(struct vioscsi_dev)) != sizeof(struct vioscsi_dev)) { log_warnx("%s: error reading vioscsi from fd", __func__); return (-1); } if (pci_set_bar_fn(vioscsi->pci_id, 0, vioscsi_io, vioscsi)) { log_warnx("%s: can't set bar fn for vmm control device", __progname); return (-1); } if (virtio_init_disk(&vioscsi->file, &vioscsi->sz, &child_cdrom, 1, VMDF_RAW) == -1) { log_warnx("%s: unable to determine iso format", __func__); return (-1); } vioscsi->vm_id = vcp->vcp_id; vioscsi->irq = pci_get_dev_irq(vioscsi->pci_id); return (0); } int virtio_restore(int fd, struct vmd_vm *vm, int child_cdrom, int child_disks[][VM_MAX_BASE_PER_DISK], int *child_taps) { struct vmop_create_params *vmc = &vm->vm_params; struct vm_create_params *vcp = &vmc->vmc_params; int ret; if ((ret = viornd_restore(fd, vcp)) == -1) return ret; if ((ret = vioblk_restore(fd, vmc, child_disks)) == -1) return ret; if ((ret = vioscsi_restore(fd, vcp, child_cdrom)) == -1) return ret; if ((ret = vionet_restore(fd, vm, child_taps)) == -1) return ret; if ((ret = vmmci_restore(fd, vcp->vcp_id)) == -1) return ret; return (0); } int viornd_dump(int fd) { log_debug("%s: sending viornd", __func__); if (atomicio(vwrite, fd, &viornd, sizeof(viornd)) != sizeof(viornd)) { log_warnx("%s: error writing viornd to fd", __func__); return (-1); } return (0); } int vmmci_dump(int fd) { log_debug("%s: sending vmmci", __func__); if (atomicio(vwrite, fd, &vmmci, sizeof(vmmci)) != sizeof(vmmci)) { log_warnx("%s: error writing vmmci to fd", __func__); return (-1); } return (0); } int vionet_dump(int fd) { log_debug("%s: sending vionet", __func__); if (atomicio(vwrite, fd, vionet, nr_vionet * sizeof(struct vionet_dev)) != nr_vionet * sizeof(struct vionet_dev)) { log_warnx("%s: error writing vionet to fd", __func__); return (-1); } return (0); } int vioblk_dump(int fd) { log_debug("%s: sending vioblk", __func__); if (atomicio(vwrite, fd, vioblk, nr_vioblk * sizeof(struct vioblk_dev)) != nr_vioblk * sizeof(struct vioblk_dev)) { log_warnx("%s: error writing vioblk to fd", __func__); return (-1); } return (0); } int vioscsi_dump(int fd) { if (vioscsi == NULL) return (0); log_debug("%s: sending vioscsi", __func__); if (atomicio(vwrite, fd, vioscsi, sizeof(struct vioscsi_dev)) != sizeof(struct vioscsi_dev)) { log_warnx("%s: error writing vioscsi to fd", __func__); return (-1); } return (0); } int virtio_dump(int fd) { int ret; if ((ret = viornd_dump(fd)) == -1) return ret; if ((ret = vioblk_dump(fd)) == -1) return ret; if ((ret = vioscsi_dump(fd)) == -1) return ret; if ((ret = vionet_dump(fd)) == -1) return ret; if ((ret = vmmci_dump(fd)) == -1) return ret; return (0); } void virtio_stop(struct vm_create_params *vcp) { uint8_t i; for (i = 0; i < vcp->vcp_nnics; i++) { if (event_del(&vionet[i].event)) { log_warn("could not initialize vionet event " "handler"); return; } } } void virtio_start(struct vm_create_params *vcp) { uint8_t i; for (i = 0; i < vcp->vcp_nnics; i++) { if (event_add(&vionet[i].event, NULL)) { log_warn("could not initialize vionet event " "handler"); return; } } }