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