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, 192 queue); 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