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