xref: /haiku/src/add-ons/kernel/bus_managers/virtio/VirtioBalloonDevice.cpp (revision 01dc1ea4cfb70a5499b7d90f91fede60e50c468f)
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 
19 const char*
20 get_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 
32 VirtioBalloonDevice::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 
97 VirtioBalloonDevice::~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 
108 status_t
109 VirtioBalloonDevice::InitCheck()
110 {
111 	return fStatus;
112 }
113 
114 
115 int32
116 VirtioBalloonDevice::_ThreadEntry(void* data)
117 {
118 	VirtioBalloonDevice* device = (VirtioBalloonDevice*)data;
119 	return device->_Thread();
120 }
121 
122 
123 int32
124 VirtioBalloonDevice::_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 
211 void
212 VirtioBalloonDevice::_ConfigCallback(void* driverCookie)
213 {
214 	CALLED();
215 	VirtioBalloonDevice* device = (VirtioBalloonDevice*)driverCookie;
216 
217 	SpinLocker locker(device->fInterruptLock);
218 	device->fConfigCondition.NotifyAll();
219 }
220 
221 
222 void
223 VirtioBalloonDevice::_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 
233 uint32
234 VirtioBalloonDevice::_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 
249 status_t
250 VirtioBalloonDevice::_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