m1n1/src/hv_virtio.c
Martin Povišer 6661ab14d0 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>
2023-01-13 17:25:25 +09:00

308 lines
7 KiB
C

/* 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);
}