1/*
2 * Copyright 2021, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "virtio.h"
8
9#include <new>
10#include <string.h>
11#include <malloc.h>
12#include <KernelExport.h>
13
14
15enum {
16	maxVirtioDevices = 32,
17};
18
19
20VirtioResources gVirtioDevList[maxVirtioDevices];
21int32_t gVirtioDevListLen = 0;
22
23DoublyLinkedList<VirtioDevice> gVirtioDevices;
24VirtioDevice* gKeyboardDev = NULL;
25
26
27void*
28aligned_malloc(size_t required_bytes, size_t alignment)
29{
30    void* p1; // original block
31    void** p2; // aligned block
32    int offset = alignment - 1 + sizeof(void*);
33    if ((p1 = (void*)malloc(required_bytes + offset)) == NULL) {
34       return NULL;
35    }
36    p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1));
37    p2[-1] = p1;
38    return p2;
39}
40
41
42void
43aligned_free(void* p)
44{
45    free(((void**)p)[-1]);
46}
47
48
49VirtioResources*
50ThisVirtioDev(uint32 deviceId, int n)
51{
52	for (int i = 0; i < gVirtioDevListLen; i++) {
53		VirtioRegs* volatile regs = gVirtioDevList[i].regs;
54		if (regs->signature != kVirtioSignature) continue;
55		if (regs->deviceId == deviceId) {
56			if (n == 0) return &gVirtioDevList[i]; else n--;
57		}
58	}
59	return NULL;
60}
61
62
63//#pragma mark VirtioDevice
64
65int32_t
66VirtioDevice::AllocDesc()
67{
68	for (size_t i = 0; i < fQueueLen; i++) {
69		if ((fFreeDescs[i/32] & (1 << (i % 32))) != 0) {
70			fFreeDescs[i/32] &= ~((uint32_t)1 << (i % 32));
71			return i;
72		}
73	}
74	return -1;
75}
76
77
78void
79VirtioDevice::FreeDesc(int32_t idx)
80{
81	fFreeDescs[idx/32] |= (uint32_t)1 << (idx % 32);
82}
83
84
85VirtioDevice::VirtioDevice(const VirtioResources& devRes): fRegs(devRes.regs)
86{
87	gVirtioDevices.Insert(this);
88
89	dprintf("+VirtioDevice\n");
90
91	fRegs->status = 0; // reset
92
93	fRegs->status |= kVirtioConfigSAcknowledge;
94	fRegs->status |= kVirtioConfigSDriver;
95	dprintf("features: %08x\n", fRegs->deviceFeatures);
96	fRegs->status |= kVirtioConfigSFeaturesOk;
97	fRegs->status |= kVirtioConfigSDriverOk;
98
99	fRegs->queueSel = 0;
100//	dprintf("queueNumMax: %d\n", fRegs->queueNumMax);
101	fQueueLen = fRegs->queueNumMax;
102	fRegs->queueNum = fQueueLen;
103	fLastUsed = 0;
104
105	fDescs = (VirtioDesc*)aligned_malloc(sizeof(VirtioDesc) * fQueueLen, 4096);
106	memset(fDescs, 0, sizeof(VirtioDesc) * fQueueLen);
107	fAvail = (VirtioAvail*)aligned_malloc(sizeof(VirtioAvail)
108		+ sizeof(uint16_t) * fQueueLen, 4096);
109	memset(fAvail, 0, sizeof(VirtioAvail) + sizeof(uint16_t) * fQueueLen);
110	fUsed = (VirtioUsed*)aligned_malloc(sizeof(VirtioUsed)
111		+ sizeof(VirtioUsedItem) * fQueueLen, 4096);
112	memset(fUsed, 0, sizeof(VirtioUsed) + sizeof(VirtioUsedItem) * fQueueLen);
113	fFreeDescs = new(std::nothrow) uint32_t[(fQueueLen + 31)/32];
114	memset(fFreeDescs, 0xff, sizeof(uint32_t) * ((fQueueLen + 31)/32));
115
116	fReqs = new(std::nothrow) IORequest*[fQueueLen];
117
118	fRegs->queueDescLow = (uint32_t)(uint64_t)fDescs;
119	fRegs->queueDescHi = (uint32_t)((uint64_t)fDescs >> 32);
120	fRegs->queueAvailLow = (uint32_t)(uint64_t)fAvail;
121	fRegs->queueAvailHi = (uint32_t)((uint64_t)fAvail >> 32);
122	fRegs->queueUsedLow = (uint32_t)(uint64_t)fUsed;
123	fRegs->queueUsedHi = (uint32_t)((uint64_t)fUsed >> 32);
124/*
125	dprintf("fDescs: %p\n", fDescs);
126	dprintf("fAvail: %p\n", fAvail);
127	dprintf("fUsed: %p\n", fUsed);
128*/
129	fRegs->queueReady = 1;
130
131	fRegs->config[0] = kVirtioInputCfgIdName;
132//	dprintf("name: %s\n", (const char*)(&fRegs->config[8]));
133}
134
135
136VirtioDevice::~VirtioDevice()
137{
138	gVirtioDevices.Remove(this);
139	fRegs->status = 0; // reset
140}
141
142
143void
144VirtioDevice::ScheduleIO(IORequest** reqs, uint32 cnt)
145{
146	if (cnt < 1) return;
147	int32_t firstDesc, lastDesc;
148	for (uint32 i = 0; i < cnt; i++) {
149		int32_t desc = AllocDesc();
150		if (desc < 0) {panic("virtio: no more descs"); return;}
151		if (i == 0) {
152			firstDesc = desc;
153		} else {
154			fDescs[lastDesc].flags |= kVringDescFlagsNext;
155			fDescs[lastDesc].next = desc;
156			reqs[i - 1]->next = reqs[i];
157		}
158		fDescs[desc].addr = (uint64_t)(reqs[i]->buf);
159		fDescs[desc].len = reqs[i]->len;
160		fDescs[desc].flags = 0;
161		fDescs[desc].next = 0;
162		switch (reqs[i]->op) {
163		case ioOpRead: break;
164		case ioOpWrite: fDescs[desc].flags |= kVringDescFlagsWrite; break;
165		}
166		reqs[i]->state = ioStatePending;
167		lastDesc = desc;
168	}
169	int32_t idx = fAvail->idx % fQueueLen;
170	fReqs[idx] = reqs[0];
171	fAvail->ring[idx] = firstDesc;
172	fAvail->idx++;
173	fRegs->queueNotify = 0;
174}
175
176
177void
178VirtioDevice::ScheduleIO(IORequest* req)
179{
180	ScheduleIO(&req, 1);
181}
182
183
184IORequest*
185VirtioDevice::ConsumeIO()
186{
187	if (fUsed->idx == fLastUsed)
188		return NULL;
189
190	IORequest* req = fReqs[fLastUsed % fQueueLen];
191	fReqs[fLastUsed % fQueueLen] = NULL;
192	req->state = ioStateDone;
193	int32 desc = fUsed->ring[fLastUsed % fQueueLen].id;
194	while (kVringDescFlagsNext & fDescs[desc].flags) {
195		int32 nextDesc = fDescs[desc].next;
196		FreeDesc(desc);
197		desc = nextDesc;
198	}
199	FreeDesc(desc);
200	fLastUsed++;
201	return req;
202}
203
204
205IORequest*
206VirtioDevice::WaitIO()
207{
208	while (fUsed->idx == fLastUsed) {}
209	return ConsumeIO();
210}
211
212
213//#pragma mark -
214
215void
216virtio_register(addr_t base, size_t len, uint32 irq)
217{
218	VirtioRegs* volatile regs = (VirtioRegs* volatile)base;
219
220	dprintf("virtio_register(0x%" B_PRIxADDR ", 0x%" B_PRIxSIZE ", "
221		"%" B_PRIu32 ")\n", base, len, irq);
222	dprintf("  signature: 0x%" B_PRIx32 "\n", regs->signature);
223	dprintf("  version: %" B_PRIu32 "\n", regs->version);
224	dprintf("  device id: %" B_PRIu32 "\n", regs->deviceId);
225
226	if (!(gVirtioDevListLen < maxVirtioDevices)) {
227		dprintf("too many VirtIO devices\n");
228		return;
229	}
230	gVirtioDevList[gVirtioDevListLen].regs = regs;
231	gVirtioDevList[gVirtioDevListLen].regsSize = len;
232	gVirtioDevList[gVirtioDevListLen].irq = irq;
233	gVirtioDevListLen++;
234}
235
236
237void
238virtio_init()
239{
240	dprintf("virtio_init()\n");
241
242	int i = 0;
243	for (; ; i++) {
244		VirtioResources* devRes = ThisVirtioDev(kVirtioDevInput, i);
245		if (devRes == NULL) break;
246		VirtioRegs* volatile regs = devRes->regs;
247		regs->config[0] = kVirtioInputCfgIdName;
248		dprintf("virtio_input[%d]: %s\n", i, (const char*)(&regs->config[8]));
249		if (i == 0)
250			gKeyboardDev = new(std::nothrow) VirtioDevice(*devRes);
251	}
252	dprintf("virtio_input count: %d\n", i);
253	if (gKeyboardDev != NULL) {
254		for (int i = 0; i < 4; i++) {
255			gKeyboardDev->ScheduleIO(new(std::nothrow) IORequest(ioOpWrite,
256				malloc(sizeof(VirtioInputPacket)), sizeof(VirtioInputPacket)));
257		}
258	}
259}
260
261
262void
263virtio_fini()
264{
265	auto it = gVirtioDevices.GetIterator();
266	while (VirtioDevice* dev = it.Next()) {
267		dev->Regs()->status = 0; // reset
268	}
269}
270
271
272int
273virtio_input_get_key()
274{
275	if (gKeyboardDev == NULL)
276		return 0;
277
278	IORequest* req = gKeyboardDev->ConsumeIO();
279	if (req == NULL)
280		return 0;
281
282	VirtioInputPacket &pkt = *(VirtioInputPacket*)req->buf;
283
284	int key = 0;
285	if (pkt.type == 1 && pkt.value == 1)
286		key = pkt.code;
287
288	free(req->buf); req->buf = NULL; delete req;
289	gKeyboardDev->ScheduleIO(new(std::nothrow) IORequest(ioOpWrite,
290		malloc(sizeof(VirtioInputPacket)), sizeof(VirtioInputPacket)));
291
292	return key;
293}
294
295
296int
297virtio_input_wait_for_key()
298{
299	int key = 0;
300
301	do {
302		key = virtio_input_get_key();
303	} while (key == 0);
304
305	return key;
306}
307