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
20DataContainer::DataContainer(Volume *volume)
21	: fVolume(volume),
22	  fSize(0),
23	  fCache(NULL)
24{
25}
26
27// destructor
28DataContainer::~DataContainer()
29{
30	if (fCache != NULL) {
31		fCache->Lock();
32		fCache->ReleaseRefAndUnlock();
33		fCache = NULL;
34	}
35}
36
37// InitCheck
38status_t
39DataContainer::InitCheck() const
40{
41	return (fVolume != NULL ? B_OK : B_ERROR);
42}
43
44// GetCache
45VMCache*
46DataContainer::GetCache()
47{
48	if (!_IsCacheMode())
49		_SwitchToCacheMode();
50	return fCache;
51}
52
53// Resize
54status_t
55DataContainer::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
97status_t
98DataContainer::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
126status_t
127DataContainer::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
160void
161DataContainer::GetAllocationInfo(AllocationInfo &info)
162{
163	if (_IsCacheMode()) {
164		info.AddAreaAllocation(fCache->committed_size);
165	} else {
166		// ...
167	}
168}
169
170// _RequiresCacheMode
171inline bool
172DataContainer::_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
180inline bool
181DataContainer::_IsCacheMode() const
182{
183	return fCache != NULL;
184}
185
186// _CountBlocks
187inline int32
188DataContainer::_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
198status_t
199DataContainer::_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
220status_t
221DataContainer::_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
283void
284DataContainer::_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
338void
339DataContainer::_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