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