1/*
2 * Copyright 2018, J��r��me Duval, jerome.duval@gmail.com.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "VirtioBalloonPrivate.h"
8
9#include <new>
10#include <stdlib.h>
11#include <string.h>
12
13#include <ByteOrder.h>
14#include <util/AutoLock.h>
15
16#include "virtio_balloon.h"
17
18
19const char*
20get_feature_name(uint64 feature)
21{
22	switch (feature) {
23		case VIRTIO_BALLOON_F_MUST_TELL_HOST:
24			return "must tell host";
25		case VIRTIO_BALLOON_F_STATS_VQ:
26			return "stats vq";
27	}
28	return NULL;
29}
30
31
32VirtioBalloonDevice::VirtioBalloonDevice(device_node* node)
33	:
34	fNode(node),
35	fVirtio(NULL),
36	fVirtioDevice(NULL),
37	fStatus(B_NO_INIT),
38	fDesiredSize(0),
39	fActualSize(0)
40{
41	CALLED();
42
43	B_INITIALIZE_SPINLOCK(&fInterruptLock);
44	fQueueCondition.Init(this, "virtio balloon transfer");
45	fConfigCondition.Init(this, "virtio balloon config");
46
47	get_memory_map(fBuffer, sizeof(fBuffer), &fEntry, 1);
48
49	// get the Virtio device from our parent's parent
50	device_node* parent = gDeviceManager->get_parent_node(node);
51	device_node* virtioParent = gDeviceManager->get_parent_node(parent);
52	gDeviceManager->put_node(parent);
53
54	gDeviceManager->get_driver(virtioParent, (driver_module_info**)&fVirtio,
55		(void**)&fVirtioDevice);
56	gDeviceManager->put_node(virtioParent);
57
58	fVirtio->negotiate_features(fVirtioDevice,
59		0, &fFeatures, &get_feature_name);
60
61	fStatus = fVirtio->alloc_queues(fVirtioDevice, 2, fVirtioQueues);
62	if (fStatus != B_OK) {
63		ERROR("queue allocation failed (%s)\n", strerror(fStatus));
64		return;
65	}
66
67	fStatus = fVirtio->setup_interrupt(fVirtioDevice, _ConfigCallback, this);
68	if (fStatus != B_OK) {
69		ERROR("interrupt setup failed (%s)\n", strerror(fStatus));
70		return;
71	}
72
73	fStatus = fVirtio->queue_setup_interrupt(fVirtioQueues[0],
74		_QueueCallback, fVirtioQueues[0]);
75	if (fStatus != B_OK) {
76		ERROR("queue interrupt setup failed (%s)\n", strerror(fStatus));
77		return;
78	}
79
80	fStatus = fVirtio->queue_setup_interrupt(fVirtioQueues[1],
81		_QueueCallback, fVirtioQueues[1]);
82	if (fStatus != B_OK) {
83		ERROR("queue interrupt setup failed (%s)\n", strerror(fStatus));
84		return;
85	}
86
87	fThread = spawn_kernel_thread(&_ThreadEntry, "virtio balloon thread",
88		B_NORMAL_PRIORITY, this);
89	if (fThread < 0) {
90		fStatus = fThread;
91		return;
92	}
93	resume_thread(fThread);
94}
95
96
97VirtioBalloonDevice::~VirtioBalloonDevice()
98{
99	fRunning = false;
100	if (fThread >= 0) {
101		int32 result;
102		wait_for_thread(fThread, &result);
103		fThread = -1;
104	}
105}
106
107
108status_t
109VirtioBalloonDevice::InitCheck()
110{
111	return fStatus;
112}
113
114
115int32
116VirtioBalloonDevice::_ThreadEntry(void* data)
117{
118	VirtioBalloonDevice* device = (VirtioBalloonDevice*)data;
119	return device->_Thread();
120}
121
122
123int32
124VirtioBalloonDevice::_Thread()
125{
126	CALLED();
127
128	while (fRunning) {
129		if (fDesiredSize == fActualSize) {
130			TRACE("waiting for a config change\n");
131			ConditionVariableEntry configConditionEntry;
132			fConfigCondition.Add(&configConditionEntry);
133			status_t result = configConditionEntry.Wait(B_CAN_INTERRUPT);
134			if (result != B_OK)
135				continue;
136
137			fDesiredSize = _DesiredSize();
138			TRACE("finished waiting: requested %" B_PRIu32 " instead of %" B_PRIu32
139				"\n", fDesiredSize, fActualSize);
140		}
141		::virtio_queue queue;
142		if (fDesiredSize > fActualSize) {
143			int32 count = min_c(PAGES_COUNT, fDesiredSize - fActualSize);
144			TRACE("allocating %" B_PRIu32 " pages\n", count);
145			queue = fVirtioQueues[0];
146
147			vm_page_reservation reservation;
148			vm_page_reserve_pages(&reservation, count, VM_PRIORITY_SYSTEM);
149			for (int i = 0; i < count; i++) {
150				//TRACE("allocating page %" B_PRIu32 " pages\n", i);
151				vm_page* page = vm_page_allocate_page(&reservation,
152					PAGE_STATE_WIRED);
153				if (page == NULL) {
154					TRACE("allocating failed\n");
155					count = i;
156					break;
157				}
158				PageInfo* info = new PageInfo;
159				info->page = page;
160				fBuffer[i] = (phys_addr_t)page->physical_page_number
161					>> (PAGE_SHIFT - VIRTIO_BALLOON_PFN_SHIFT);
162				fPages.Add(info);
163			}
164			fEntry.size = count * sizeof(uint32);
165			fActualSize += count;
166		} else {
167			int32 count = min_c(PAGES_COUNT, fActualSize - fDesiredSize);
168			TRACE("freeing %" B_PRIu32 " pages\n", count);
169			queue = fVirtioQueues[1];
170
171			for (int i = 0; i < count; i++) {
172				PageInfo* info = fPages.RemoveHead();
173				if (info == NULL) {
174					TRACE("remove failed\n");
175					count = i;
176					break;
177				}
178				vm_page* page = info->page;
179				fBuffer[i] = (phys_addr_t)page->physical_page_number
180					>> (PAGE_SHIFT - VIRTIO_BALLOON_PFN_SHIFT);
181				vm_page_free(NULL, page);
182			}
183			fEntry.size = count * sizeof(uint32);
184			fActualSize -= count;
185		}
186
187		ConditionVariableEntry queueConditionEntry;
188		fQueueCondition.Add(&queueConditionEntry);
189
190		// alloc or release
191		TRACE("queue request\n");
192		status_t result = fVirtio->queue_request(queue, &fEntry, NULL, NULL);
193		if (result != B_OK) {
194			ERROR("queueing failed (%s)\n", strerror(result));
195			return result;
196		}
197
198		while (!fVirtio->queue_dequeue(queue, NULL, NULL)) {
199			TRACE("wait for response\n");
200			queueConditionEntry.Wait(B_CAN_INTERRUPT);
201		}
202
203		TRACE("update size\n");
204		_UpdateSize();
205	}
206
207	return 0;
208}
209
210
211void
212VirtioBalloonDevice::_ConfigCallback(void* driverCookie)
213{
214	CALLED();
215	VirtioBalloonDevice* device = (VirtioBalloonDevice*)driverCookie;
216
217	SpinLocker locker(device->fInterruptLock);
218	device->fConfigCondition.NotifyAll();
219}
220
221
222void
223VirtioBalloonDevice::_QueueCallback(void* driverCookie, void* cookie)
224{
225	CALLED();
226	VirtioBalloonDevice* device = (VirtioBalloonDevice*)driverCookie;
227
228	SpinLocker locker(device->fInterruptLock);
229	device->fQueueCondition.NotifyAll();
230}
231
232
233uint32
234VirtioBalloonDevice::_DesiredSize()
235{
236	CALLED();
237	uint32 desiredSize;
238
239	status_t status = fVirtio->read_device_config(fVirtioDevice,
240		offsetof(struct virtio_balloon_config, num_pages),
241		&desiredSize, sizeof(desiredSize));
242	if (status != B_OK)
243		return 0;
244
245	return B_LENDIAN_TO_HOST_INT32(desiredSize);
246}
247
248
249status_t
250VirtioBalloonDevice::_UpdateSize()
251{
252	CALLED();
253	TRACE("_UpdateSize %u\n", fActualSize);
254	uint32 actualSize = B_HOST_TO_LENDIAN_INT32(fActualSize);
255	return fVirtio->write_device_config(fVirtioDevice,
256		offsetof(struct virtio_balloon_config, actual),
257		&actualSize, sizeof(actualSize));
258}
259