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