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(uint32 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