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