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