mirror of
https://github.com/AsahiLinux/m1n1
synced 2024-12-19 02:03:06 +00:00
6661ab14d0
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>
308 lines
7 KiB
C
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);
|
|
}
|