mirror of
https://github.com/AsahiLinux/m1n1
synced 2024-11-24 23:53:04 +00:00
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:
parent
586157b8bf
commit
6661ab14d0
9 changed files with 511 additions and 1 deletions
2
Makefile
2
Makefile
|
@ -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 \
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ class HV_EVENT(IntEnum):
|
|||
USER_INTERRUPT = 3
|
||||
WDT_BARK = 4
|
||||
CPU_SWITCH = 5
|
||||
VIRTIO = 6
|
||||
|
||||
VMProxyHookData = Struct(
|
||||
"flags" / RegAdapter(MMIOTraceFlags),
|
||||
|
|
133
proxyclient/m1n1/hv/virtio.py
Normal file
133
proxyclient/m1n1/hv/virtio.py
Normal 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
|
|
@ -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)
|
||||
|
|
4
src/hv.h
4
src/hv.h
|
@ -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
308
src/hv_virtio.c
Normal 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);
|
||||
}
|
|
@ -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]);
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue