xref: /haiku/src/system/boot/platform/riscv/virtio.cpp (revision 9e25244c5e9051f6cd333820d6332397361abd6c)
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 
15 enum {
16 	maxVirtioDevices = 32,
17 };
18 
19 
20 VirtioResources gVirtioDevList[maxVirtioDevices];
21 int32_t gVirtioDevListLen = 0;
22 
23 DoublyLinkedList<VirtioDevice> gVirtioDevices;
24 VirtioDevice* gKeyboardDev = NULL;
25 
26 
27 void*
28 aligned_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 
42 void
43 aligned_free(void* p)
44 {
45     free(((void**)p)[-1]);
46 }
47 
48 
49 VirtioResources*
50 ThisVirtioDev(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 
65 int32_t
66 VirtioDevice::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 
78 void
79 VirtioDevice::FreeDesc(int32_t idx)
80 {
81 	fFreeDescs[idx/32] |= (uint32_t)1 << (idx % 32);
82 }
83 
84 
85 VirtioDevice::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 
136 VirtioDevice::~VirtioDevice()
137 {
138 	gVirtioDevices.Remove(this);
139 	fRegs->status = 0; // reset
140 }
141 
142 
143 void
144 VirtioDevice::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 
177 void
178 VirtioDevice::ScheduleIO(IORequest* req)
179 {
180 	ScheduleIO(&req, 1);
181 }
182 
183 
184 IORequest*
185 VirtioDevice::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 
205 IORequest*
206 VirtioDevice::WaitIO()
207 {
208 	while (fUsed->idx == fLastUsed) {}
209 	return ConsumeIO();
210 }
211 
212 
213 //#pragma mark -
214 
215 void
216 virtio_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 
237 void
238 virtio_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 
262 void
263 virtio_fini()
264 {
265 	auto it = gVirtioDevices.GetIterator();
266 	while (VirtioDevice* dev = it.Next()) {
267 		dev->Regs()->status = 0; // reset
268 	}
269 }
270 
271 
272 int
273 virtio_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 
296 int
297 virtio_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