* Copyright 2021, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#include "virtio.h"
#include <new>
#include <string.h>
#include <malloc.h>
#include <KernelExport.h>
enum {
maxVirtioDevices = 32,
};
VirtioResources gVirtioDevList[maxVirtioDevices];
int32_t gVirtioDevListLen = 0;
DoublyLinkedList<VirtioDevice> gVirtioDevices;
VirtioDevice* gKeyboardDev = NULL;
void*
aligned_malloc(size_t required_bytes, size_t alignment)
{
void* p1;
void** p2;
int offset = alignment - 1 + sizeof(void*);
if ((p1 = (void*)malloc(required_bytes + offset)) == NULL) {
return NULL;
}
p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1));
p2[-1] = p1;
return p2;
}
void
aligned_free(void* p)
{
free(((void**)p)[-1]);
}
VirtioResources*
ThisVirtioDev(uint32 deviceId, int n)
{
for (int i = 0; i < gVirtioDevListLen; i++) {
VirtioRegs* volatile regs = gVirtioDevList[i].regs;
if (regs->signature != kVirtioSignature) continue;
if (regs->deviceId == deviceId) {
if (n == 0) return &gVirtioDevList[i]; else n--;
}
}
return NULL;
}
int32_t
VirtioDevice::AllocDesc()
{
for (size_t i = 0; i < fQueueLen; i++) {
if ((fFreeDescs[i/32] & (1 << (i % 32))) != 0) {
fFreeDescs[i/32] &= ~((uint32_t)1 << (i % 32));
return i;
}
}
return -1;
}
void
VirtioDevice::FreeDesc(int32_t idx)
{
fFreeDescs[idx/32] |= (uint32_t)1 << (idx % 32);
}
VirtioDevice::VirtioDevice(const VirtioResources& devRes): fRegs(devRes.regs)
{
gVirtioDevices.Insert(this);
dprintf("+VirtioDevice\n");
fRegs->status = 0;
fRegs->status |= kVirtioConfigSAcknowledge;
fRegs->status |= kVirtioConfigSDriver;
dprintf("features: %08x\n", fRegs->deviceFeatures);
fRegs->status |= kVirtioConfigSFeaturesOk;
fRegs->status |= kVirtioConfigSDriverOk;
fRegs->queueSel = 0;
fQueueLen = fRegs->queueNumMax;
fRegs->queueNum = fQueueLen;
fLastUsed = 0;
fDescs = (VirtioDesc*)aligned_malloc(sizeof(VirtioDesc) * fQueueLen, 4096);
memset(fDescs, 0, sizeof(VirtioDesc) * fQueueLen);
fAvail = (VirtioAvail*)aligned_malloc(sizeof(VirtioAvail)
+ sizeof(uint16_t) * fQueueLen, 4096);
memset(fAvail, 0, sizeof(VirtioAvail) + sizeof(uint16_t) * fQueueLen);
fUsed = (VirtioUsed*)aligned_malloc(sizeof(VirtioUsed)
+ sizeof(VirtioUsedItem) * fQueueLen, 4096);
memset(fUsed, 0, sizeof(VirtioUsed) + sizeof(VirtioUsedItem) * fQueueLen);
fFreeDescs = new(std::nothrow) uint32_t[(fQueueLen + 31)/32];
memset(fFreeDescs, 0xff, sizeof(uint32_t) * ((fQueueLen + 31)/32));
fReqs = new(std::nothrow) IORequest*[fQueueLen];
fRegs->queueDescLow = (uint32_t)(uint64_t)fDescs;
fRegs->queueDescHi = (uint32_t)((uint64_t)fDescs >> 32);
fRegs->queueAvailLow = (uint32_t)(uint64_t)fAvail;
fRegs->queueAvailHi = (uint32_t)((uint64_t)fAvail >> 32);
fRegs->queueUsedLow = (uint32_t)(uint64_t)fUsed;
fRegs->queueUsedHi = (uint32_t)((uint64_t)fUsed >> 32);
dprintf("fDescs: %p\n", fDescs);
dprintf("fAvail: %p\n", fAvail);
dprintf("fUsed: %p\n", fUsed);
*/
fRegs->queueReady = 1;
fRegs->config[0] = kVirtioInputCfgIdName;
}
VirtioDevice::~VirtioDevice()
{
gVirtioDevices.Remove(this);
fRegs->status = 0;
}
void
VirtioDevice::ScheduleIO(IORequest** reqs, uint32 cnt)
{
if (cnt < 1) return;
int32_t firstDesc, lastDesc;
for (uint32 i = 0; i < cnt; i++) {
int32_t desc = AllocDesc();
if (desc < 0) {panic("virtio: no more descs"); return;}
if (i == 0) {
firstDesc = desc;
} else {
fDescs[lastDesc].flags |= kVringDescFlagsNext;
fDescs[lastDesc].next = desc;
reqs[i - 1]->next = reqs[i];
}
fDescs[desc].addr = (uint64_t)(reqs[i]->buf);
fDescs[desc].len = reqs[i]->len;
fDescs[desc].flags = 0;
fDescs[desc].next = 0;
switch (reqs[i]->op) {
case ioOpRead: break;
case ioOpWrite: fDescs[desc].flags |= kVringDescFlagsWrite; break;
}
reqs[i]->state = ioStatePending;
lastDesc = desc;
}
int32_t idx = fAvail->idx % fQueueLen;
fReqs[idx] = reqs[0];
fAvail->ring[idx] = firstDesc;
fAvail->idx++;
fRegs->queueNotify = 0;
}
void
VirtioDevice::ScheduleIO(IORequest* req)
{
ScheduleIO(&req, 1);
}
IORequest*
VirtioDevice::ConsumeIO()
{
if (fUsed->idx == fLastUsed)
return NULL;
IORequest* req = fReqs[fLastUsed % fQueueLen];
fReqs[fLastUsed % fQueueLen] = NULL;
req->state = ioStateDone;
int32 desc = fUsed->ring[fLastUsed % fQueueLen].id;
while (kVringDescFlagsNext & fDescs[desc].flags) {
int32 nextDesc = fDescs[desc].next;
FreeDesc(desc);
desc = nextDesc;
}
FreeDesc(desc);
fLastUsed++;
return req;
}
IORequest*
VirtioDevice::WaitIO()
{
while (fUsed->idx == fLastUsed) {}
return ConsumeIO();
}
void
virtio_register(addr_t base, size_t len, uint32 irq)
{
VirtioRegs* volatile regs = (VirtioRegs* volatile)base;
dprintf("virtio_register(0x%" B_PRIxADDR ", 0x%" B_PRIxSIZE ", "
"%" B_PRIu32 ")\n", base, len, irq);
dprintf(" signature: 0x%" B_PRIx32 "\n", regs->signature);
dprintf(" version: %" B_PRIu32 "\n", regs->version);
dprintf(" device id: %" B_PRIu32 "\n", regs->deviceId);
if (!(gVirtioDevListLen < maxVirtioDevices)) {
dprintf("too many VirtIO devices\n");
return;
}
gVirtioDevList[gVirtioDevListLen].regs = regs;
gVirtioDevList[gVirtioDevListLen].regsSize = len;
gVirtioDevList[gVirtioDevListLen].irq = irq;
gVirtioDevListLen++;
}
void
virtio_init()
{
dprintf("virtio_init()\n");
int i = 0;
for (; ; i++) {
VirtioResources* devRes = ThisVirtioDev(kVirtioDevInput, i);
if (devRes == NULL) break;
VirtioRegs* volatile regs = devRes->regs;
regs->config[0] = kVirtioInputCfgIdName;
dprintf("virtio_input[%d]: %s\n", i, (const char*)(®s->config[8]));
if (i == 0)
gKeyboardDev = new(std::nothrow) VirtioDevice(*devRes);
}
dprintf("virtio_input count: %d\n", i);
if (gKeyboardDev != NULL) {
for (int i = 0; i < 4; i++) {
gKeyboardDev->ScheduleIO(new(std::nothrow) IORequest(ioOpWrite,
malloc(sizeof(VirtioInputPacket)), sizeof(VirtioInputPacket)));
}
}
}
void
virtio_fini()
{
auto it = gVirtioDevices.GetIterator();
while (VirtioDevice* dev = it.Next()) {
dev->Regs()->status = 0;
}
}
int
virtio_input_get_key()
{
if (gKeyboardDev == NULL)
return 0;
IORequest* req = gKeyboardDev->ConsumeIO();
if (req == NULL)
return 0;
VirtioInputPacket &pkt = *(VirtioInputPacket*)req->buf;
int key = 0;
if (pkt.type == 1 && pkt.value == 1)
key = pkt.code;
free(req->buf); req->buf = NULL; delete req;
gKeyboardDev->ScheduleIO(new(std::nothrow) IORequest(ioOpWrite,
malloc(sizeof(VirtioInputPacket)), sizeof(VirtioInputPacket)));
return key;
}
int
virtio_input_wait_for_key()
{
int key = 0;
do {
key = virtio_input_get_key();
} while (key == 0);
return key;
}