xref: /haiku/src/add-ons/kernel/file_systems/ramfs/DataContainer.cpp (revision 9e54316c528c34ee76f4d0d21150ceadd031c4de)
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