xref: /haiku/src/add-ons/kernel/busses/virtio/virtio_mmio/VirtioDevice.cpp (revision 4c8e85b316c35a9161f5a1c50ad70bc91c83a76f)
1 /*
2  * Copyright 2021, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "VirtioDevice.h"
8 
9 #include <malloc.h>
10 #include <string.h>
11 #include <new>
12 
13 #include <KernelExport.h>
14 #include <kernel.h>
15 #include <debug.h>
16 
17 
18 static inline void
19 SetLowHi(vuint32 &low, vuint32 &hi, uint64 val)
20 {
21 	low = (uint32)val;
22 	hi  = (uint32)(val >> 32);
23 }
24 
25 
26 // #pragma mark - VirtioQueue
27 
28 
29 VirtioQueue::VirtioQueue(VirtioDevice *dev, int32 id)
30 	:
31 	fDev(dev),
32 	fId(id),
33 	fQueueHandler(NULL),
34 	fQueueHandlerCookie(NULL)
35 {
36 }
37 
38 
39 VirtioQueue::~VirtioQueue()
40 {
41 }
42 
43 
44 status_t
45 VirtioQueue::Init()
46 {
47 	fDev->fRegs->queueSel = fId;
48 	TRACE("queueNumMax: %d\n", fDev->fRegs->queueNumMax);
49 	fQueueLen = fDev->fRegs->queueNumMax;
50 	fDev->fRegs->queueNum = fQueueLen;
51 	fLastUsed = 0;
52 
53 	size_t queueMemSize = 0;
54 	fDescs = (VirtioDesc*)queueMemSize;
55 	queueMemSize += ROUNDUP(sizeof(VirtioDesc)
56 		* fQueueLen, B_PAGE_SIZE);
57 
58 	fAvail = (VirtioAvail*)queueMemSize;
59 	queueMemSize += ROUNDUP(sizeof(VirtioAvail)
60 		+ sizeof(uint16) * fQueueLen, B_PAGE_SIZE);
61 
62 	fUsed  = (VirtioUsed*)queueMemSize;
63 	queueMemSize += ROUNDUP(sizeof(VirtioUsed)
64 		+ sizeof(VirtioUsedItem)*fQueueLen, B_PAGE_SIZE);
65 
66 	uint8* queueMem = NULL;
67 	fArea.SetTo(create_area("VirtIO Queue", (void**)&queueMem,
68 		B_ANY_KERNEL_ADDRESS, queueMemSize, B_CONTIGUOUS,
69 		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA));
70 
71 	if (!fArea.IsSet()) {
72 		ERROR("can't create area: %08" B_PRIx32, fArea.Get());
73 		return fArea.Get();
74 	}
75 
76 	physical_entry pe;
77 	if (status_t res = get_memory_map(queueMem, queueMemSize, &pe, 1) < B_OK) {
78 		ERROR("get_memory_map failed");
79 		return res;
80 	}
81 
82 	TRACE("queueMem: %p\n", queueMem);
83 
84 	memset(queueMem, 0, queueMemSize);
85 
86 	fDescs = (VirtioDesc*)((uint8*)fDescs + (addr_t)queueMem);
87 	fAvail = (VirtioAvail*)((uint8*)fAvail + (addr_t)queueMem);
88 	fUsed  = (VirtioUsed*)((uint8*)fUsed  + (addr_t)queueMem);
89 
90 	phys_addr_t descsPhys = (addr_t)fDescs - (addr_t)queueMem + pe.address;
91 	phys_addr_t availPhys = (addr_t)fAvail - (addr_t)queueMem + pe.address;
92 	phys_addr_t usedPhys  = (addr_t)fUsed  - (addr_t)queueMem + pe.address;
93 
94 	SetLowHi(fDev->fRegs->queueDescLow,  fDev->fRegs->queueDescHi,  descsPhys);
95 	SetLowHi(fDev->fRegs->queueAvailLow, fDev->fRegs->queueAvailHi, availPhys);
96 	SetLowHi(fDev->fRegs->queueUsedLow,  fDev->fRegs->queueUsedHi,  usedPhys);
97 
98 	fFreeDescs.SetTo(new(std::nothrow) uint32[(fQueueLen + 31) / 32]);
99 	if (!fFreeDescs.IsSet())
100 		return B_NO_MEMORY;
101 
102 	memset(fFreeDescs.Get(), 0xff, sizeof(uint32) * ((fQueueLen + 31) / 32));
103 	fCookies.SetTo(new(std::nothrow) void*[fQueueLen]);
104 	if (!fCookies.IsSet())
105 		return B_NO_MEMORY;
106 
107 	fDev->fRegs->queueReady = 1;
108 
109 	return B_OK;
110 }
111 
112 
113 int32
114 VirtioQueue::AllocDesc()
115 {
116 	for (size_t i = 0; i < fQueueLen; i++) {
117 		if ((fFreeDescs[i / 32] & (1 << (i % 32))) != 0) {
118 			fFreeDescs[i / 32] &= ~((uint32)1 << (i % 32));
119 			return i;
120 		}
121 	}
122 	return -1;
123 }
124 
125 
126 void
127 VirtioQueue::FreeDesc(int32 idx)
128 {
129 	fFreeDescs[idx / 32] |= (uint32)1 << (idx % 32);
130 }
131 
132 
133 status_t
134 VirtioQueue::Enqueue(const physical_entry* vector,
135 	size_t readVectorCount, size_t writtenVectorCount,
136 	void* cookie)
137 {
138 	int32 firstDesc = -1, lastDesc = -1;
139 	size_t count = readVectorCount + writtenVectorCount;
140 
141 	if (count == 0)
142 		return B_OK;
143 
144 	for (size_t i = 0; i < count; i++) {
145 		int32 desc = AllocDesc();
146 
147 		if (desc < 0) {
148 			ERROR("no free virtio descs, queue: %p\n", this);
149 
150 			if (firstDesc >= 0) {
151 				desc = firstDesc;
152 				while (kVringDescFlagsNext & fDescs[desc].flags) {
153 					int32_t nextDesc = fDescs[desc].next;
154 					FreeDesc(desc);
155 					desc = nextDesc;
156 				}
157 				FreeDesc(desc);
158 			}
159 
160 			return B_WOULD_BLOCK;
161 		}
162 
163 		if (i == 0) {
164 			firstDesc = desc;
165 		} else {
166 			fDescs[lastDesc].flags |= kVringDescFlagsNext;
167 			fDescs[lastDesc].next = desc;
168 		}
169 		fDescs[desc].addr = vector[i].address;
170 		fDescs[desc].len = vector[i].size;
171 		fDescs[desc].flags = 0;
172 		fDescs[desc].next = 0;
173 		if (i >= readVectorCount)
174 			fDescs[desc].flags |= kVringDescFlagsWrite;
175 
176 		lastDesc = desc;
177 	}
178 
179 	int32_t idx = fAvail->idx % fQueueLen;
180 	fCookies[idx] = cookie;
181 	fAvail->ring[idx] = firstDesc;
182 	fAvail->idx++;
183 	fDev->fRegs->queueNotify = fId;
184 
185 	return B_OK;
186 }
187 
188 
189 bool
190 VirtioQueue::Dequeue(void** _cookie, uint32* _usedLength)
191 {
192 	fDev->fRegs->queueSel = fId;
193 
194 	if (fUsed->idx == fLastUsed)
195 		return false;
196 
197 	if (_cookie != NULL)
198 		*_cookie = fCookies[fLastUsed % fQueueLen];
199 	fCookies[fLastUsed % fQueueLen] = NULL;
200 
201 	if (_usedLength != NULL)
202 		*_usedLength = fUsed->ring[fLastUsed % fQueueLen].len;
203 
204 	int32_t desc = fUsed->ring[fLastUsed % fQueueLen].id;
205 	while (kVringDescFlagsNext & fDescs[desc].flags) {
206 		int32_t nextDesc = fDescs[desc].next;
207 		FreeDesc(desc);
208 		desc = nextDesc;
209 	}
210 	FreeDesc(desc);
211 	fLastUsed++;
212 
213 	return true;
214 }
215 
216 
217 // #pragma mark - VirtioIrqHandler
218 
219 
220 VirtioIrqHandler::VirtioIrqHandler(VirtioDevice* dev)
221 	:
222 	fDev(dev)
223 {
224 	fReferenceCount = 0;
225 }
226 
227 
228 void
229 VirtioIrqHandler::FirstReferenceAcquired()
230 {
231 	install_io_interrupt_handler(fDev->fIrq, Handle, fDev, 0);
232 }
233 
234 
235 void
236 VirtioIrqHandler::LastReferenceReleased()
237 {
238 	remove_io_interrupt_handler(fDev->fIrq, Handle, fDev);
239 }
240 
241 
242 int32
243 VirtioIrqHandler::Handle(void* data)
244 {
245 	// TRACE("VirtioIrqHandler::Handle(%p)\n", data);
246 	VirtioDevice* dev = (VirtioDevice*)data;
247 
248 	if ((kVirtioIntQueue & dev->fRegs->interruptStatus) != 0) {
249 		for (int32 i = 0; i < dev->fQueueCnt; i++) {
250 			VirtioQueue* queue = dev->fQueues[i].Get();
251 			if (queue->fUsed->idx != queue->fLastUsed
252 				&& queue->fQueueHandler != NULL) {
253 				queue->fQueueHandler(dev->fConfigHandlerCookie,
254 					queue->fQueueHandlerCookie);
255 				}
256 		}
257 		dev->fRegs->interruptAck = kVirtioIntQueue;
258 	}
259 
260 	if ((kVirtioIntConfig & dev->fRegs->interruptStatus) != 0) {
261 		if (dev->fConfigHandler != NULL)
262 			dev->fConfigHandler(dev->fConfigHandlerCookie);
263 
264 		dev->fRegs->interruptAck = kVirtioIntConfig;
265 	}
266 
267 	return B_HANDLED_INTERRUPT;
268 }
269 
270 
271 // #pragma mark - VirtioDevice
272 
273 
274 VirtioDevice::VirtioDevice()
275 	:
276 	fRegs(NULL),
277 	fQueueCnt(0),
278 	fIrqHandler(this),
279 	fConfigHandler(NULL),
280 	fConfigHandlerCookie(NULL)
281 {
282 }
283 
284 
285 status_t
286 VirtioDevice::Init(phys_addr_t regs, size_t regsLen, int32 irq, int32 queueCnt)
287 {
288 	fRegsArea.SetTo(map_physical_memory("Virtio MMIO",
289 		regs, regsLen, B_ANY_KERNEL_ADDRESS,
290 		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
291 		(void**)&fRegs));
292 
293 	if (!fRegsArea.IsSet())
294 		return fRegsArea.Get();
295 
296 	fIrq = irq;
297 
298 	// Reset
299 	fRegs->status = 0;
300 
301 	return B_OK;
302 }
303