hv: Add 9P virtio peripheral

Add some minimal implementation of virtio peripherals. At the level
of on-target hypervisor code we implement the MMIO layout and
maintain virtqueues. Once a buffer is available, we break into the
host proxyclient to deal with it.

Specific device-classes of the virtio spec ought to be implemented in
the proxyclient. Here the one device implemented is 9P transport,
exporting the m1n1 source directory.

Signed-off-by: Martin Povišer <povik@cutebit.org>
This commit is contained in:
Martin Povišer 2022-06-27 16:38:32 +02:00 committed by Hector Martin
parent 586157b8bf
commit 6661ab14d0
9 changed files with 511 additions and 1 deletions

View file

@ -99,7 +99,7 @@ OBJECTS := \
firmware.o \
gxf.o gxf_asm.o \
heapblock.o \
hv.o hv_vm.o hv_exc.o hv_vuart.o hv_wdt.o hv_asm.o hv_aic.o \
hv.o hv_vm.o hv_exc.o hv_vuart.o hv_wdt.o hv_asm.o hv_aic.o hv_virtio.o \
i2c.o \
iodev.o \
iova.o \

View file

@ -13,6 +13,7 @@ from .. import xnutools, shell
from .gdbserver import *
from .types import *
from .virtio import *
__all__ = ["HV"]
@ -111,6 +112,7 @@ class HV(Reloadable):
self.hvcall_handlers = {}
self.switching_context = False
self.show_timestamps = False
self.virtio_devs = {}
def _reloadme(self):
super()._reloadme()
@ -999,6 +1001,52 @@ class HV(Reloadable):
self.p.exit(0)
def attach_virtio(self, dev, base=None, irq=None, verbose=False):
assert base is not None and irq is not None
# TODO: ^-- allocate those if not chosen by caller
data = dev.config_data
data_base = self.u.heap.malloc(len(data))
self.iface.writemem(data_base, data)
config = VirtioConfig.build({
"irq": irq,
"devid": dev.devid,
"feats": dev.feats,
"num_qus": dev.num_qus,
"data": data_base,
"data_len": len(data),
"verbose": verbose,
})
config_base = self.u.heap.malloc(len(config))
self.iface.writemem(config_base, config)
self.p.hv_map_virtio(base, config_base)
self.add_tracer(irange(base, 0x1000), "VIRTIO", TraceMode.RESERVED)
dev.base = base
dev.hv = self
self.virtio_devs[base] = dev
def handle_virtio(self, reason, code, info):
ctx = self.iface.readstruct(info, ExcInfo)
self.virtio_ctx = info = self.iface.readstruct(ctx.data, VirtioExcInfo)
try:
handled = self.virtio_devs[info.devbase].handle_exc(info)
except:
self.log(f"Python exception from within virtio handler")
traceback.print_exc()
handled = False
if not handled:
signal.signal(signal.SIGINT, self.default_sigint)
self.run_shell("Entering hypervisor shell", "Returning")
signal.signal(signal.SIGINT, self._handle_sigint)
self.p.exit(EXC_RET.HANDLED)
def skip(self):
self.ctx.elr += 4
self.cont()
@ -1299,6 +1347,7 @@ class HV(Reloadable):
self.iface.set_handler(START.HV, HV_EVENT.VTIMER, self.handle_exception)
self.iface.set_handler(START.HV, HV_EVENT.WDT_BARK, self.handle_bark)
self.iface.set_handler(START.HV, HV_EVENT.CPU_SWITCH, self.handle_exception)
self.iface.set_handler(START.HV, HV_EVENT.VIRTIO, self.handle_virtio)
self.iface.set_event_handler(EVENT.MMIOTRACE, self.handle_mmiotrace)
self.iface.set_event_handler(EVENT.IRQTRACE, self.handle_irqtrace)

View file

@ -37,6 +37,7 @@ class HV_EVENT(IntEnum):
USER_INTERRUPT = 3
WDT_BARK = 4
CPU_SWITCH = 5
VIRTIO = 6
VMProxyHookData = Struct(
"flags" / RegAdapter(MMIOTraceFlags),

View file

@ -0,0 +1,133 @@
# SPDX-License-Identifier: MIT
from construct import Struct, Int8ul, Int16ul, Int32sl, Int32ul, Int64ul
from subprocess import Popen, PIPE
import pathlib
import struct
import os
import sys
from ..utils import *
VirtioConfig = Struct(
"irq" / Int32sl,
"devid" / Int32ul,
"feats" / Int64ul,
"num_qus" / Int32ul,
"data" / Int64ul,
"data_len" / Int64ul,
"verbose" / Int8ul,
)
class VirtioDescFlags(Register16):
WRITE = 1
NEXT = 0
VirtioDesc = Struct(
"addr" / Int64ul,
"len" / Int32ul,
"flags" / RegAdapter(VirtioDescFlags),
"next" / Int16ul,
)
VirtioExcInfo = Struct(
"devbase" / Int64ul,
"qu" / Int16ul,
"idx" / Int16ul,
"pad" / Int32ul,
"descbase" / Int64ul,
)
class VirtioDev:
def __init__(self):
self.base, self.hv = None, None # assigned by HV object
def read_buf(self, desc):
return self.hv.iface.readmem(desc.addr, desc.len)
def read_desc(self, ctx, idx):
off = VirtioDesc.sizeof() * idx
return self.hv.iface.readstruct(ctx.descbase + off, VirtioDesc)
@property
def config_data(self):
return b""
@property
def devid(self):
return 0
@property
def num_qus(self):
return 1
@property
def feats(self):
return 0
class Virtio9PTransport(VirtioDev):
def __init__(self, tag="m1n1", root=None):
p_stdin, self.fin = os.pipe()
self.fout, p_stdout = os.pipe()
if root is None:
root = str(pathlib.Path(__file__).resolve().parents[3])
if type(tag) is str:
self.tag = tag.encode("ascii")
else:
self.tag = tag
self.p = Popen([
"u9fs",
"-a", "none", # no auth
"-n", # not a network conn
"-u", os.getlogin(), # single user
root,
], stdin=p_stdin, stdout=p_stdout, stderr=sys.stderr)
@property
def config_data(self):
return struct.pack("=H", len(self.tag)) + self.tag
@property
def devid(self):
return 9
@property
def num_qus(self):
return 1
@property
def feats(self):
return 1
def call(self, req):
os.write(self.fin, req)
resp = os.read(self.fout, 4)
length = int.from_bytes(resp, byteorder="little")
resp += os.read(self.fout, length - 4)
return resp
def handle_exc(self, ctx):
head = self.read_desc(ctx, ctx.idx)
assert not head.flags.WRITE
req = bytearray()
while not head.flags.WRITE:
req += self.read_buf(head)
if not head.flags.NEXT:
break
head = self.read_desc(ctx, head.next)
resp = self.call(bytes(req))
resplen = len(resp)
while len(resp):
self.hv.iface.writemem(head.addr, resp[:head.len])
resp = resp[head.len:]
if not head.flags.NEXT:
break
head = self.read_desc(ctx, head.next)
self.hv.p.virtio_put_buffer(ctx.devbase, ctx.qu, ctx.idx, resplen)
return True

View file

@ -594,6 +594,8 @@ class M1N1Proxy(Reloadable):
P_HV_SET_TIME_STEALING = 0xc0a
P_HV_PIN_CPU = 0xc0b
P_HV_WRITE_HCR = 0xc0c
P_HV_MAP_VIRTIO = 0xc0d
P_VIRTIO_PUT_BUFFER = 0xc0e
P_FB_INIT = 0xd00
P_FB_SHUTDOWN = 0xd01
@ -1025,6 +1027,10 @@ class M1N1Proxy(Reloadable):
return self.request(self.P_HV_PIN_CPU, cpu)
def hv_write_hcr(self, hcr):
return self.request(self.P_HV_WRITE_HCR, hcr)
def hv_map_virtio(self, base, config):
return self.request(self.P_HV_MAP_VIRTIO, base, config)
def virtio_put_buffer(self, base, qu, idx, length):
return self.request(self.P_VIRTIO_PUT_BUFFER, base, qu, idx, length)
def fb_init(self):
return self.request(self.P_FB_INIT)

View file

@ -47,6 +47,7 @@ typedef enum _hv_entry_type {
HV_USER_INTERRUPT,
HV_WDT_BARK,
HV_CPU_SWITCH,
HV_VIRTIO,
} hv_entry_type;
/* VM */
@ -69,6 +70,9 @@ bool hv_trace_irq(u32 type, u32 num, u32 count, u32 flags);
/* Virtual peripherals */
void hv_vuart_poll(void);
void hv_map_vuart(u64 base, int irq, iodev_id_t iodev);
struct virtio_conf;
void hv_map_virtio(u64 base, struct virtio_conf *conf);
void virtio_put_buffer(u64 base, int qu, u32 id, u32 len);
/* Exceptions */
void hv_exc_proxy(struct exc_info *ctx, uartproxy_boot_reason_t reason, u32 type, void *extra);

308
src/hv_virtio.c Normal file
View file

@ -0,0 +1,308 @@
/* SPDX-License-Identifier: MIT */
#include "hv.h"
#include "aic.h"
#include "iodev.h"
#include "malloc.h"
#define MAGIC 0x000
#define VERSION 0x004
#define DEVID 0x008
#define VENDID 0x00c
#define FEAT_HOST 0x010
#define FEAT_HOST_SEL 0x014
#define FEAT_GUEST 0x020
#define FEAT_GUEST_SEL 0x024
#define QSEL 0x030
#define QMAX 0x034
#define QSIZE 0x038
#define QREADY 0x044
#define QNOTIFY 0x050
#define QDESC 0x080
#define QGUESTAREA 0x090
#define QHOSTAREA 0x0a0
#define IRQ_STATUS 0x060
#define USED_BUFFER BIT(0)
#define CFG_CHANGE BIT(1)
#define IRQ_ACK 0x064
#define DEV_STATUS 0x070
#define DESC_NEXT BIT(0)
#define DESC_WRITE BIT(1)
struct availring {
u16 flags;
u16 idx;
u16 ring[];
};
struct usedring {
u16 flags;
u16 idx;
struct {
u32 id;
u32 len;
} ring[];
};
struct desc {
u64 addr;
u32 len;
u16 flags;
u16 id;
};
struct virtio_q {
struct virtio_dev *host;
int idx;
u32 max;
u32 size;
bool ready;
struct desc *desc;
u16 avail_seen;
struct availring *avail;
struct usedring *used;
u64 area_regs[(QHOSTAREA + 8 - QDESC) / 4];
};
struct virtio_conf {
s32 irq;
u32 devid;
u64 feats;
u32 num_qus;
void *config;
u64 config_len;
u8 verbose;
} PACKED;
struct virtio_dev {
struct virtio_dev *next;
u64 base;
int irq;
int num_qus;
u32 devid;
u64 feats;
uint8_t *config;
size_t config_len;
bool verbose;
u32 feat_host_sel;
u32 status;
u32 irqstatus;
struct virtio_q *currq;
struct virtio_q qs[];
};
static struct virtio_dev *devlist;
static void notify_avail(struct exc_info *ctx, struct virtio_q *q, int idx)
{
struct desc *d = &q->desc[idx];
struct {
u64 devbase;
u16 qu;
u16 idx;
u32 pad;
u64 descbase;
} PACKED info = {
q->host->base, q->idx, idx, 0, (u64)q->desc,
};
if (q->host->verbose)
printf("virtio @ %lx: available %s buffer at %lx, size %x, flags %x\n", q->host->base,
(d->flags & DESC_WRITE) ? "device" : "driver", d->addr, d->len, d->flags);
hv_exc_proxy(ctx, START_HV, HV_VIRTIO, &info);
}
static void notify_buffers(struct exc_info *ctx, struct virtio_dev *dev, u32 qidx)
{
struct virtio_q *q = &dev->qs[qidx];
struct availring *avail = q->avail;
if (qidx >= (u32)dev->num_qus)
return;
for (; avail->idx != q->avail_seen; q->avail_seen++)
notify_avail(ctx, q, avail->ring[q->avail_seen % q->size]);
}
static struct virtio_dev *dev_by_base(u64 base)
{
struct virtio_dev *dev;
for (dev = devlist; dev; dev = dev->next)
if (dev->base == base)
break;
return dev;
}
void virtio_put_buffer(u64 base, int qu, u32 id, u32 len)
{
struct virtio_dev *dev = dev_by_base(base);
struct virtio_q *q;
struct usedring *used;
if (!dev) {
printf("virtio_put_buffer: no device at %lx\n", base);
return;
}
q = &dev->qs[qu];
used = q->used;
used->ring[used->idx % q->size].id = id;
used->ring[used->idx % q->size].len = len;
used->idx++;
dev->irqstatus |= USED_BUFFER;
aic_set_sw(dev->irq, true);
}
static bool handle_virtio(struct exc_info *ctx, u64 addr, u64 *val, bool write, int width)
{
struct virtio_dev *dev;
struct virtio_q *q;
UNUSED(ctx);
UNUSED(width);
dev = dev_by_base(addr & ~0xfff);
if (!dev)
return false;
addr &= 0xfff;
if (write) {
if (dev->verbose)
printf("virtio @ %lx: W 0x%lx <- 0x%lx (%d)\n", dev->base, addr, *val, width);
switch (addr) {
case DEV_STATUS:
dev->status = *val;
break;
case QSEL:
if (((int)*val) <= dev->num_qus)
dev->currq = &dev->qs[*val];
else
dev->currq = NULL;
break;
case QNOTIFY:
notify_buffers(ctx, dev, *val);
break;
case FEAT_HOST_SEL:
dev->feat_host_sel = *val;
break;
case IRQ_ACK:
dev->irqstatus &= ~(*val);
if (!dev->irqstatus)
aic_set_sw(dev->irq, false);
break;
}
q = dev->currq;
if (!q)
return true;
switch (addr) {
case QSIZE:
q->size = *val;
break;
case QREADY:
q->ready = *val & 1;
break;
case QDESC ... QHOSTAREA + 4:
addr -= QDESC;
addr /= 4;
q->area_regs[addr] = *val;
q->desc = (void *)(q->area_regs[1] << 32 | q->area_regs[0]);
q->avail = (void *)(q->area_regs[5] << 32 | q->area_regs[4]);
q->used = (void *)(q->area_regs[9] << 32 | q->area_regs[8]);
break;
}
} else {
switch (addr) {
case MAGIC:
*val = 0x74726976;
break;
case VERSION:
*val = 2;
break;
case DEVID:
*val = dev->devid;
break;
case DEV_STATUS:
*val = dev->status;
break;
case FEAT_HOST:
*val = dev->feats >> (dev->feat_host_sel * 32);
break;
case IRQ_STATUS:
*val = dev->irqstatus;
break;
case 0x100 ... 0x1000:
if (addr - 0x100 < dev->config_len)
*val = dev->config[addr - 0x100];
else
*val = 0;
break;
default:
q = dev->currq;
if (!q) {
*val = 0;
goto rdone;
}
}
switch (addr) {
case QMAX:
*val = q->max;
break;
case QREADY:
*val = q->ready;
break;
}
rdone:
if (dev->verbose)
printf("virtio @ %lx: R 0x%lx -> 0x%lx (%d)\n", dev->base, addr, *val, width);
};
return true;
}
void hv_map_virtio(u64 base, struct virtio_conf *conf)
{
struct virtio_dev *dev;
int i;
dev = malloc(sizeof(*dev) + sizeof(struct virtio_q) * conf->num_qus);
dev->num_qus = conf->num_qus;
dev->base = base;
dev->irq = conf->irq;
dev->devid = conf->devid;
dev->currq = NULL;
dev->feats = conf->feats | BIT(32); /* always set: VIRTIO_F_VERSION_1 */
dev->config = conf->config;
dev->config_len = conf->config_len;
dev->verbose = conf->verbose;
for (i = 0; i < dev->num_qus; i++) {
dev->qs[i].host = dev;
dev->qs[i].idx = i;
dev->qs[i].max = 256;
dev->qs[i].avail_seen = 0;
dev->qs[i].ready = 0;
}
if (devlist)
dev->next = devlist;
devlist = dev;
hv_map_hook(base, handle_virtio, 0x1000);
}

View file

@ -455,6 +455,13 @@ int proxy_process(ProxyRequest *request, ProxyReply *reply)
case P_HV_MAP_VUART:
hv_map_vuart(request->args[0], request->args[1], request->args[2]);
break;
case P_HV_MAP_VIRTIO:
hv_map_virtio(request->args[0], (void *)request->args[1]);
break;
case P_VIRTIO_PUT_BUFFER:
virtio_put_buffer(request->args[0], request->args[1], request->args[2],
request->args[3]);
break;
case P_HV_TRACE_IRQ:
reply->retval = hv_trace_irq(request->args[0], request->args[1], request->args[2],
request->args[3]);

View file

@ -130,6 +130,8 @@ typedef enum {
P_HV_SET_TIME_STEALING,
P_HV_PIN_CPU,
P_HV_WRITE_HCR,
P_HV_MAP_VIRTIO,
P_VIRTIO_PUT_BUFFER,
P_FB_INIT = 0xd00,
P_FB_SHUTDOWN,