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*
get_feature_name(uint64 feature)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
VirtioBalloonDevice(device_node * node)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, NULL);
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
~VirtioBalloonDevice()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
InitCheck()109 VirtioBalloonDevice::InitCheck()
110 {
111 return fStatus;
112 }
113
114
115 int32
_ThreadEntry(void * data)116 VirtioBalloonDevice::_ThreadEntry(void* data)
117 {
118 VirtioBalloonDevice* device = (VirtioBalloonDevice*)data;
119 return device->_Thread();
120 }
121
122
123 int32
_Thread()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
_ConfigCallback(void * driverCookie)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
_QueueCallback(void * driverCookie,void * cookie)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
_DesiredSize()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
_UpdateSize()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