1 /* 2 * Copyright 2007, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Copyright 2019, Haiku, Inc. 4 * All rights reserved. Distributed under the terms of the MIT license. 5 */ 6 #include "DataContainer.h" 7 8 #include <AutoDeleter.h> 9 #include <util/AutoLock.h> 10 11 #include <vm/VMCache.h> 12 #include <vm/vm_page.h> 13 14 #include "AllocationInfo.h" 15 #include "DebugSupport.h" 16 #include "Misc.h" 17 #include "Volume.h" 18 19 // constructor 20 DataContainer::DataContainer(Volume *volume) 21 : fVolume(volume), 22 fSize(0), 23 fCache(NULL) 24 { 25 } 26 27 // destructor 28 DataContainer::~DataContainer() 29 { 30 if (fCache != NULL) { 31 fCache->Lock(); 32 fCache->ReleaseRefAndUnlock(); 33 fCache = NULL; 34 } 35 } 36 37 // InitCheck 38 status_t 39 DataContainer::InitCheck() const 40 { 41 return (fVolume != NULL ? B_OK : B_ERROR); 42 } 43 44 // GetCache 45 VMCache* 46 DataContainer::GetCache() 47 { 48 if (!_IsCacheMode()) 49 _SwitchToCacheMode(); 50 return fCache; 51 } 52 53 // Resize 54 status_t 55 DataContainer::Resize(off_t newSize) 56 { 57 // PRINT("DataContainer::Resize(%Ld), fSize: %Ld\n", newSize, fSize); 58 59 status_t error = B_OK; 60 if (newSize < fSize) { 61 // shrink 62 if (_IsCacheMode()) { 63 // resize the VMCache, which will automatically free pages 64 AutoLocker<VMCache> _(fCache); 65 error = fCache->Resize(newSize, VM_PRIORITY_SYSTEM); 66 if (error != B_OK) 67 return error; 68 } else { 69 // small buffer mode: just set the new size (done below) 70 } 71 } else if (newSize > fSize) { 72 // grow 73 if (_RequiresCacheMode(newSize)) { 74 if (!_IsCacheMode()) 75 error = _SwitchToCacheMode(); 76 if (error != B_OK) 77 return error; 78 79 AutoLocker<VMCache> _(fCache); 80 fCache->Resize(newSize, VM_PRIORITY_SYSTEM); 81 82 // pages will be added as they are written to; so nothing else 83 // needs to be done here. 84 } else { 85 // no need to switch to cache mode: just set the new size 86 // (done below) 87 } 88 } 89 90 fSize = newSize; 91 92 // PRINT("DataContainer::Resize() done: %lx, fSize: %Ld\n", error, fSize); 93 return error; 94 } 95 96 // ReadAt 97 status_t 98 DataContainer::ReadAt(off_t offset, void *_buffer, size_t size, 99 size_t *bytesRead) 100 { 101 uint8 *buffer = (uint8*)_buffer; 102 status_t error = (buffer && offset >= 0 && 103 bytesRead ? B_OK : B_BAD_VALUE); 104 if (error != B_OK) 105 return error; 106 107 // read not more than we have to offer 108 offset = min(offset, fSize); 109 size = min(size, size_t(fSize - offset)); 110 111 if (!_IsCacheMode()) { 112 // in non-cache mode, we just copy the data directly 113 memcpy(buffer, fSmallBuffer + offset, size); 114 if (bytesRead != NULL) 115 *bytesRead = size; 116 return B_OK; 117 } 118 119 // cache mode 120 error = _DoCacheIO(offset, buffer, size, bytesRead, false); 121 122 return error; 123 } 124 125 // WriteAt 126 status_t 127 DataContainer::WriteAt(off_t offset, const void *_buffer, size_t size, 128 size_t *bytesWritten) 129 { 130 PRINT("DataContainer::WriteAt(%Ld, %p, %lu, %p), fSize: %Ld\n", offset, _buffer, size, bytesWritten, fSize); 131 132 const uint8 *buffer = (const uint8*)_buffer; 133 status_t error = (buffer && offset >= 0 && bytesWritten 134 ? B_OK : B_BAD_VALUE); 135 if (error != B_OK) 136 return error; 137 138 // resize the container, if necessary 139 if ((offset + size) > fSize) 140 error = Resize(offset + size); 141 if (error != B_OK) 142 return error; 143 144 if (!_IsCacheMode()) { 145 // in non-cache mode, we just copy the data directly 146 memcpy(fSmallBuffer + offset, buffer, size); 147 if (bytesWritten != NULL) 148 *bytesWritten = size; 149 return B_OK; 150 } 151 152 // cache mode 153 error = _DoCacheIO(offset, (uint8*)buffer, size, bytesWritten, true); 154 155 PRINT("DataContainer::WriteAt() done: %lx, fSize: %Ld\n", error, fSize); 156 return error; 157 } 158 159 // GetAllocationInfo 160 void 161 DataContainer::GetAllocationInfo(AllocationInfo &info) 162 { 163 if (_IsCacheMode()) { 164 info.AddAreaAllocation(fCache->committed_size); 165 } else { 166 // ... 167 } 168 } 169 170 // _RequiresCacheMode 171 inline bool 172 DataContainer::_RequiresCacheMode(size_t size) 173 { 174 // we cannot back out of cache mode after entering it, 175 // as there may be other consumers of our VMCache 176 return _IsCacheMode() || (size > kSmallDataContainerSize); 177 } 178 179 // _IsCacheMode 180 inline bool 181 DataContainer::_IsCacheMode() const 182 { 183 return fCache != NULL; 184 } 185 186 // _CountBlocks 187 inline int32 188 DataContainer::_CountBlocks() const 189 { 190 if (_IsCacheMode()) 191 return fCache->page_count; 192 else if (fSize == 0) // small buffer mode, empty buffer 193 return 0; 194 return 1; // small buffer mode, non-empty buffer 195 } 196 197 // _SwitchToCacheMode 198 status_t 199 DataContainer::_SwitchToCacheMode() 200 { 201 status_t error = VMCacheFactory::CreateAnonymousCache(fCache, false, 0, 202 0, false, VM_PRIORITY_SYSTEM); 203 if (error != B_OK) 204 return error; 205 206 fCache->temporary = 1; 207 fCache->virtual_end = fSize; 208 209 error = fCache->Commit(fSize, VM_PRIORITY_SYSTEM); 210 if (error != B_OK) 211 return error; 212 213 if (fSize != 0) 214 error = _DoCacheIO(0, fSmallBuffer, fSize, NULL, true); 215 216 return error; 217 } 218 219 // _DoCacheIO 220 status_t 221 DataContainer::_DoCacheIO(const off_t offset, uint8* buffer, ssize_t length, 222 size_t* bytesProcessed, bool isWrite) 223 { 224 const size_t originalLength = length; 225 const bool user = IS_USER_ADDRESS(buffer); 226 227 const off_t rounded_offset = ROUNDDOWN(offset, B_PAGE_SIZE); 228 const size_t rounded_len = ROUNDUP((length) + (offset - rounded_offset), 229 B_PAGE_SIZE); 230 vm_page** pages = new(std::nothrow) vm_page*[rounded_len / B_PAGE_SIZE]; 231 if (pages == NULL) 232 return B_NO_MEMORY; 233 ArrayDeleter<vm_page*> pagesDeleter(pages); 234 235 _GetPages(rounded_offset, rounded_len, isWrite, pages); 236 237 status_t error = B_OK; 238 size_t index = 0; 239 240 while (length > 0) { 241 vm_page* page = pages[index]; 242 phys_addr_t at = (page->physical_page_number * B_PAGE_SIZE); 243 ssize_t bytes = B_PAGE_SIZE; 244 if (index == 0) { 245 const uint32 pageoffset = (offset % B_PAGE_SIZE); 246 at += pageoffset; 247 bytes -= pageoffset; 248 } 249 bytes = min(length, bytes); 250 251 if (isWrite) { 252 page->modified = true; 253 error = vm_memcpy_to_physical(at, buffer, bytes, user); 254 } else { 255 if (page != NULL) { 256 error = vm_memcpy_from_physical(buffer, at, bytes, user); 257 } else { 258 if (user) { 259 error = user_memset(buffer, 0, bytes); 260 } else { 261 memset(buffer, 0, bytes); 262 } 263 } 264 } 265 if (error != B_OK) 266 break; 267 268 buffer += bytes; 269 length -= bytes; 270 index++; 271 } 272 273 _PutPages(rounded_offset, rounded_len, pages, error == B_OK); 274 275 if (bytesProcessed != NULL) 276 *bytesProcessed = length > 0 ? originalLength - length : originalLength; 277 278 return error; 279 } 280 281 // _GetPages 282 void 283 DataContainer::_GetPages(off_t offset, off_t length, bool isWrite, 284 vm_page** pages) 285 { 286 // TODO: This method is duplicated in the ram_disk. Perhaps it 287 // should be put into a common location? 288 289 // get the pages, we already have 290 AutoLocker<VMCache> locker(fCache); 291 292 size_t pageCount = length / B_PAGE_SIZE; 293 size_t index = 0; 294 size_t missingPages = 0; 295 296 while (length > 0) { 297 vm_page* page = fCache->LookupPage(offset); 298 if (page != NULL) { 299 if (page->busy) { 300 fCache->WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, true); 301 continue; 302 } 303 304 DEBUG_PAGE_ACCESS_START(page); 305 page->busy = true; 306 } else 307 missingPages++; 308 309 pages[index++] = page; 310 offset += B_PAGE_SIZE; 311 length -= B_PAGE_SIZE; 312 } 313 314 locker.Unlock(); 315 316 // For a write we need to reserve the missing pages. 317 if (isWrite && missingPages > 0) { 318 vm_page_reservation reservation; 319 vm_page_reserve_pages(&reservation, missingPages, 320 VM_PRIORITY_SYSTEM); 321 322 for (size_t i = 0; i < pageCount; i++) { 323 if (pages[i] != NULL) 324 continue; 325 326 pages[i] = vm_page_allocate_page(&reservation, 327 PAGE_STATE_WIRED | VM_PAGE_ALLOC_BUSY); 328 329 if (--missingPages == 0) 330 break; 331 } 332 333 vm_page_unreserve_pages(&reservation); 334 } 335 } 336 337 void 338 DataContainer::_PutPages(off_t offset, off_t length, vm_page** pages, 339 bool success) 340 { 341 // TODO: This method is duplicated in the ram_disk. Perhaps it 342 // should be put into a common location? 343 344 AutoLocker<VMCache> locker(fCache); 345 346 // Mark all pages unbusy. On error free the newly allocated pages. 347 size_t index = 0; 348 349 while (length > 0) { 350 vm_page* page = pages[index++]; 351 if (page != NULL) { 352 if (page->CacheRef() == NULL) { 353 if (success) { 354 fCache->InsertPage(page, offset); 355 fCache->MarkPageUnbusy(page); 356 DEBUG_PAGE_ACCESS_END(page); 357 } else 358 vm_page_free(NULL, page); 359 } else { 360 fCache->MarkPageUnbusy(page); 361 DEBUG_PAGE_ACCESS_END(page); 362 } 363 } 364 365 offset += B_PAGE_SIZE; 366 length -= B_PAGE_SIZE; 367 } 368 } 369