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 + (off_t)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 != NULL) 243 ? (page->physical_page_number * B_PAGE_SIZE) : 0; 244 ssize_t bytes = B_PAGE_SIZE; 245 if (index == 0) { 246 const uint32 pageoffset = (offset % B_PAGE_SIZE); 247 at += pageoffset; 248 bytes -= pageoffset; 249 } 250 bytes = min(length, bytes); 251 252 if (isWrite) { 253 page->modified = true; 254 error = vm_memcpy_to_physical(at, buffer, bytes, user); 255 } else { 256 if (page != NULL) { 257 error = vm_memcpy_from_physical(buffer, at, bytes, user); 258 } else { 259 if (user) { 260 error = user_memset(buffer, 0, bytes); 261 } else { 262 memset(buffer, 0, bytes); 263 } 264 } 265 } 266 if (error != B_OK) 267 break; 268 269 buffer += bytes; 270 length -= bytes; 271 index++; 272 } 273 274 _PutPages(rounded_offset, rounded_len, pages, error == B_OK); 275 276 if (bytesProcessed != NULL) 277 *bytesProcessed = length > 0 ? originalLength - length : originalLength; 278 279 return error; 280 } 281 282 // _GetPages 283 void 284 DataContainer::_GetPages(off_t offset, off_t length, bool isWrite, 285 vm_page** pages) 286 { 287 // TODO: This method is duplicated in the ram_disk. Perhaps it 288 // should be put into a common location? 289 290 // get the pages, we already have 291 AutoLocker<VMCache> locker(fCache); 292 293 size_t pageCount = length / B_PAGE_SIZE; 294 size_t index = 0; 295 size_t missingPages = 0; 296 297 while (length > 0) { 298 vm_page* page = fCache->LookupPage(offset); 299 if (page != NULL) { 300 if (page->busy) { 301 fCache->WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, true); 302 continue; 303 } 304 305 DEBUG_PAGE_ACCESS_START(page); 306 page->busy = true; 307 } else 308 missingPages++; 309 310 pages[index++] = page; 311 offset += B_PAGE_SIZE; 312 length -= B_PAGE_SIZE; 313 } 314 315 locker.Unlock(); 316 317 // For a write we need to reserve the missing pages. 318 if (isWrite && missingPages > 0) { 319 vm_page_reservation reservation; 320 vm_page_reserve_pages(&reservation, missingPages, 321 VM_PRIORITY_SYSTEM); 322 323 for (size_t i = 0; i < pageCount; i++) { 324 if (pages[i] != NULL) 325 continue; 326 327 pages[i] = vm_page_allocate_page(&reservation, 328 PAGE_STATE_WIRED | VM_PAGE_ALLOC_BUSY); 329 330 if (--missingPages == 0) 331 break; 332 } 333 334 vm_page_unreserve_pages(&reservation); 335 } 336 } 337 338 void 339 DataContainer::_PutPages(off_t offset, off_t length, vm_page** pages, 340 bool success) 341 { 342 // TODO: This method is duplicated in the ram_disk. Perhaps it 343 // should be put into a common location? 344 345 AutoLocker<VMCache> locker(fCache); 346 347 // Mark all pages unbusy. On error free the newly allocated pages. 348 size_t index = 0; 349 350 while (length > 0) { 351 vm_page* page = pages[index++]; 352 if (page != NULL) { 353 if (page->CacheRef() == NULL) { 354 if (success) { 355 fCache->InsertPage(page, offset); 356 fCache->MarkPageUnbusy(page); 357 DEBUG_PAGE_ACCESS_END(page); 358 } else 359 vm_page_free(NULL, page); 360 } else { 361 fCache->MarkPageUnbusy(page); 362 DEBUG_PAGE_ACCESS_END(page); 363 } 364 } 365 366 offset += B_PAGE_SIZE; 367 length -= B_PAGE_SIZE; 368 } 369 } 370