1b9795fafSAugustin Cavalier/*
2b9795fafSAugustin Cavalier * Copyright 2007, Ingo Weinhold, ingo_weinhold@gmx.de.
3cbc07268SAugustin Cavalier * Copyright 2019, Haiku, Inc.
4b9795fafSAugustin Cavalier * All rights reserved. Distributed under the terms of the MIT license.
5b9795fafSAugustin Cavalier */
6cbc07268SAugustin Cavalier#include "DataContainer.h"
7cbc07268SAugustin Cavalier
8cbc07268SAugustin Cavalier#include <AutoDeleter.h>
9cbc07268SAugustin Cavalier#include <util/AutoLock.h>
10cbc07268SAugustin Cavalier
11cbc07268SAugustin Cavalier#include <vm/VMCache.h>
12cbc07268SAugustin Cavalier#include <vm/vm_page.h>
13224e7c42SIngo Weinhold
14224e7c42SIngo Weinhold#include "AllocationInfo.h"
15a41d815cSAugustin Cavalier#include "DebugSupport.h"
16224e7c42SIngo Weinhold#include "Misc.h"
17224e7c42SIngo Weinhold#include "Volume.h"
18224e7c42SIngo Weinhold
19224e7c42SIngo Weinhold// constructor
20224e7c42SIngo WeinholdDataContainer::DataContainer(Volume *volume)
21224e7c42SIngo Weinhold	: fVolume(volume),
22cbc07268SAugustin Cavalier	  fSize(0),
23cbc07268SAugustin Cavalier	  fCache(NULL)
24224e7c42SIngo Weinhold{
25224e7c42SIngo Weinhold}
26224e7c42SIngo Weinhold
27224e7c42SIngo Weinhold// destructor
28224e7c42SIngo WeinholdDataContainer::~DataContainer()
29224e7c42SIngo Weinhold{
30cbc07268SAugustin Cavalier	if (fCache != NULL) {
31cbc07268SAugustin Cavalier		fCache->Lock();
32cbc07268SAugustin Cavalier		fCache->ReleaseRefAndUnlock();
33cbc07268SAugustin Cavalier		fCache = NULL;
34cbc07268SAugustin Cavalier	}
35224e7c42SIngo Weinhold}
36224e7c42SIngo Weinhold
37224e7c42SIngo Weinhold// InitCheck
38224e7c42SIngo Weinholdstatus_t
39224e7c42SIngo WeinholdDataContainer::InitCheck() const
40224e7c42SIngo Weinhold{
41cbc07268SAugustin Cavalier	return (fVolume != NULL ? B_OK : B_ERROR);
42224e7c42SIngo Weinhold}
43224e7c42SIngo Weinhold
4458a582ffSAugustin Cavalier// GetCache
4558a582ffSAugustin CavalierVMCache*
4658a582ffSAugustin CavalierDataContainer::GetCache()
4758a582ffSAugustin Cavalier{
4858a582ffSAugustin Cavalier	if (!_IsCacheMode())
4958a582ffSAugustin Cavalier		_SwitchToCacheMode();
5058a582ffSAugustin Cavalier	return fCache;
5158a582ffSAugustin Cavalier}
5258a582ffSAugustin Cavalier
53224e7c42SIngo Weinhold// Resize
54224e7c42SIngo Weinholdstatus_t
55224e7c42SIngo WeinholdDataContainer::Resize(off_t newSize)
56224e7c42SIngo Weinhold{
57cbc07268SAugustin Cavalier//	PRINT("DataContainer::Resize(%Ld), fSize: %Ld\n", newSize, fSize);
58cbc07268SAugustin Cavalier
59224e7c42SIngo Weinhold	status_t error = B_OK;
60cbc07268SAugustin Cavalier	if (newSize < fSize) {
61cbc07268SAugustin Cavalier		// shrink
62cbc07268SAugustin Cavalier		if (_IsCacheMode()) {
63cbc07268SAugustin Cavalier			// resize the VMCache, which will automatically free pages
64cbc07268SAugustin Cavalier			AutoLocker<VMCache> _(fCache);
65cbc07268SAugustin Cavalier			error = fCache->Resize(newSize, VM_PRIORITY_SYSTEM);
66cbc07268SAugustin Cavalier			if (error != B_OK)
67cbc07268SAugustin Cavalier				return error;
68cbc07268SAugustin Cavalier		} else {
69cbc07268SAugustin Cavalier			// small buffer mode: just set the new size (done below)
70cbc07268SAugustin Cavalier		}
71cbc07268SAugustin Cavalier	} else if (newSize > fSize) {
72cbc07268SAugustin Cavalier		// grow
73cbc07268SAugustin Cavalier		if (_RequiresCacheMode(newSize)) {
74cbc07268SAugustin Cavalier			if (!_IsCacheMode())
7558a582ffSAugustin Cavalier				error = _SwitchToCacheMode();
76cbc07268SAugustin Cavalier			if (error != B_OK)
77cbc07268SAugustin Cavalier				return error;
78cbc07268SAugustin Cavalier
79cbc07268SAugustin Cavalier			AutoLocker<VMCache> _(fCache);
80cbc07268SAugustin Cavalier			fCache->Resize(newSize, VM_PRIORITY_SYSTEM);
81cbc07268SAugustin Cavalier
82cbc07268SAugustin Cavalier			// pages will be added as they are written to; so nothing else
83cbc07268SAugustin Cavalier			// needs to be done here.
84cbc07268SAugustin Cavalier		} else {
85cbc07268SAugustin Cavalier			// no need to switch to cache mode: just set the new size
86cbc07268SAugustin Cavalier			// (done below)
87cbc07268SAugustin Cavalier		}
88224e7c42SIngo Weinhold	}
89cbc07268SAugustin Cavalier
90cbc07268SAugustin Cavalier	fSize = newSize;
91cbc07268SAugustin Cavalier
92cbc07268SAugustin Cavalier//	PRINT("DataContainer::Resize() done: %lx, fSize: %Ld\n", error, fSize);
93224e7c42SIngo Weinhold	return error;
94224e7c42SIngo Weinhold}
95224e7c42SIngo Weinhold
96224e7c42SIngo Weinhold// ReadAt
97224e7c42SIngo Weinholdstatus_t
98224e7c42SIngo WeinholdDataContainer::ReadAt(off_t offset, void *_buffer, size_t size,
99cbc07268SAugustin Cavalier	size_t *bytesRead)
100224e7c42SIngo Weinhold{
101224e7c42SIngo Weinhold	uint8 *buffer = (uint8*)_buffer;
102cbc07268SAugustin Cavalier	status_t error = (buffer && offset >= 0 &&
103cbc07268SAugustin Cavalier		bytesRead ? B_OK : B_BAD_VALUE);
104cbc07268SAugustin Cavalier	if (error != B_OK)
105cbc07268SAugustin Cavalier		return error;
106cbc07268SAugustin Cavalier
107cbc07268SAugustin Cavalier	// read not more than we have to offer
108cbc07268SAugustin Cavalier	offset = min(offset, fSize);
109cbc07268SAugustin Cavalier	size = min(size, size_t(fSize - offset));
110cbc07268SAugustin Cavalier
111cbc07268SAugustin Cavalier	if (!_IsCacheMode()) {
112cbc07268SAugustin Cavalier		// in non-cache mode, we just copy the data directly
113cbc07268SAugustin Cavalier		memcpy(buffer, fSmallBuffer + offset, size);
114cbc07268SAugustin Cavalier		if (bytesRead != NULL)
115cbc07268SAugustin Cavalier			*bytesRead = size;
116cbc07268SAugustin Cavalier		return B_OK;
117224e7c42SIngo Weinhold	}
118cbc07268SAugustin Cavalier
119cbc07268SAugustin Cavalier	// cache mode
120cbc07268SAugustin Cavalier	error = _DoCacheIO(offset, buffer, size, bytesRead, false);
121cbc07268SAugustin Cavalier
122224e7c42SIngo Weinhold	return error;
123224e7c42SIngo Weinhold}
124224e7c42SIngo Weinhold
125224e7c42SIngo Weinhold// WriteAt
126224e7c42SIngo Weinholdstatus_t
127224e7c42SIngo WeinholdDataContainer::WriteAt(off_t offset, const void *_buffer, size_t size,
128cbc07268SAugustin Cavalier	size_t *bytesWritten)
129224e7c42SIngo Weinhold{
130cbc07268SAugustin Cavalier	PRINT("DataContainer::WriteAt(%Ld, %p, %lu, %p), fSize: %Ld\n", offset, _buffer, size, bytesWritten, fSize);
131cbc07268SAugustin Cavalier
132224e7c42SIngo Weinhold	const uint8 *buffer = (const uint8*)_buffer;
133224e7c42SIngo Weinhold	status_t error = (buffer && offset >= 0 && bytesWritten
134cbc07268SAugustin Cavalier		? B_OK : B_BAD_VALUE);
135cbc07268SAugustin Cavalier	if (error != B_OK)
136cbc07268SAugustin Cavalier		return error;
137cbc07268SAugustin Cavalier
138224e7c42SIngo Weinhold	// resize the container, if necessary
139cbc07268SAugustin Cavalier	if ((offset + size) > fSize)
140cbc07268SAugustin Cavalier		error = Resize(offset + size);
141cbc07268SAugustin Cavalier	if (error != B_OK)
142cbc07268SAugustin Cavalier		return error;
143224e7c42SIngo Weinhold
144cbc07268SAugustin Cavalier	if (!_IsCacheMode()) {
145cbc07268SAugustin Cavalier		// in non-cache mode, we just copy the data directly
146cbc07268SAugustin Cavalier		memcpy(fSmallBuffer + offset, buffer, size);
147cbc07268SAugustin Cavalier		if (bytesWritten != NULL)
148cbc07268SAugustin Cavalier			*bytesWritten = size;
149cbc07268SAugustin Cavalier		return B_OK;
150224e7c42SIngo Weinhold	}
151cbc07268SAugustin Cavalier
152cbc07268SAugustin Cavalier	// cache mode
153cbc07268SAugustin Cavalier	error = _DoCacheIO(offset, (uint8*)buffer, size, bytesWritten, true);
154cbc07268SAugustin Cavalier
155cbc07268SAugustin Cavalier	PRINT("DataContainer::WriteAt() done: %lx, fSize: %Ld\n", error, fSize);
156cbc07268SAugustin Cavalier	return error;
157224e7c42SIngo Weinhold}
158224e7c42SIngo Weinhold
159224e7c42SIngo Weinhold// GetAllocationInfo
160224e7c42SIngo Weinholdvoid
161224e7c42SIngo WeinholdDataContainer::GetAllocationInfo(AllocationInfo &info)
162224e7c42SIngo Weinhold{
163cbc07268SAugustin Cavalier	if (_IsCacheMode()) {
164cbc07268SAugustin Cavalier		info.AddAreaAllocation(fCache->committed_size);
165224e7c42SIngo Weinhold	} else {
166224e7c42SIngo Weinhold		// ...
167224e7c42SIngo Weinhold	}
168224e7c42SIngo Weinhold}
169224e7c42SIngo Weinhold
170cbc07268SAugustin Cavalier// _RequiresCacheMode
171cbc07268SAugustin Cavalierinline bool
172cbc07268SAugustin CavalierDataContainer::_RequiresCacheMode(size_t size)
173224e7c42SIngo Weinhold{
174cbc07268SAugustin Cavalier	// we cannot back out of cache mode after entering it,
175cbc07268SAugustin Cavalier	// as there may be other consumers of our VMCache
176cbc07268SAugustin Cavalier	return _IsCacheMode() || (size > kSmallDataContainerSize);
177224e7c42SIngo Weinhold}
178224e7c42SIngo Weinhold
179cbc07268SAugustin Cavalier// _IsCacheMode
180cbc07268SAugustin Cavalierinline bool
181cbc07268SAugustin CavalierDataContainer::_IsCacheMode() const
182224e7c42SIngo Weinhold{
183cbc07268SAugustin Cavalier	return fCache != NULL;
184224e7c42SIngo Weinhold}
185224e7c42SIngo Weinhold
186224e7c42SIngo Weinhold// _CountBlocks
187cbc07268SAugustin Cavalierinline int32
188224e7c42SIngo WeinholdDataContainer::_CountBlocks() const
189224e7c42SIngo Weinhold{
190cbc07268SAugustin Cavalier	if (_IsCacheMode())
191cbc07268SAugustin Cavalier		return fCache->page_count;
192224e7c42SIngo Weinhold	else if (fSize == 0)	// small buffer mode, empty buffer
193224e7c42SIngo Weinhold		return 0;
194224e7c42SIngo Weinhold	return 1;	// small buffer mode, non-empty buffer
195224e7c42SIngo Weinhold}
196224e7c42SIngo Weinhold
197cbc07268SAugustin Cavalier// _SwitchToCacheMode
198cbc07268SAugustin Cavalierstatus_t
19958a582ffSAugustin CavalierDataContainer::_SwitchToCacheMode()
200224e7c42SIngo Weinhold{
201cbc07268SAugustin Cavalier	status_t error = VMCacheFactory::CreateAnonymousCache(fCache, false, 0,
202cbc07268SAugustin Cavalier		0, false, VM_PRIORITY_SYSTEM);
203cbc07268SAugustin Cavalier	if (error != B_OK)
204cbc07268SAugustin Cavalier		return error;
205224e7c42SIngo Weinhold
206cbc07268SAugustin Cavalier	fCache->temporary = 1;
20758a582ffSAugustin Cavalier	fCache->virtual_end = fSize;
208cbc07268SAugustin Cavalier
20958a582ffSAugustin Cavalier	error = fCache->Commit(fSize, VM_PRIORITY_SYSTEM);
210cbc07268SAugustin Cavalier	if (error != B_OK)
211cbc07268SAugustin Cavalier		return error;
212cbc07268SAugustin Cavalier
213cbc07268SAugustin Cavalier	if (fSize != 0)
214cbc07268SAugustin Cavalier		error = _DoCacheIO(0, fSmallBuffer, fSize, NULL, true);
215cbc07268SAugustin Cavalier
216cbc07268SAugustin Cavalier	return error;
217224e7c42SIngo Weinhold}
218224e7c42SIngo Weinhold
219cbc07268SAugustin Cavalier// _DoCacheIO
220224e7c42SIngo Weinholdstatus_t
221cbc07268SAugustin CavalierDataContainer::_DoCacheIO(const off_t offset, uint8* buffer, ssize_t length,
222cbc07268SAugustin Cavalier	size_t* bytesProcessed, bool isWrite)
223224e7c42SIngo Weinhold{
224cbc07268SAugustin Cavalier	const size_t originalLength = length;
225cbc07268SAugustin Cavalier	const bool user = IS_USER_ADDRESS(buffer);
226cbc07268SAugustin Cavalier
227cbc07268SAugustin Cavalier	const off_t rounded_offset = ROUNDDOWN(offset, B_PAGE_SIZE);
228cbc07268SAugustin Cavalier	const size_t rounded_len = ROUNDUP((length) + (offset - rounded_offset),
229cbc07268SAugustin Cavalier		B_PAGE_SIZE);
230cbc07268SAugustin Cavalier	vm_page** pages = new(std::nothrow) vm_page*[rounded_len / B_PAGE_SIZE];
231cbc07268SAugustin Cavalier	if (pages == NULL)
232cbc07268SAugustin Cavalier		return B_NO_MEMORY;
233cbc07268SAugustin Cavalier	ArrayDeleter<vm_page*> pagesDeleter(pages);
234cbc07268SAugustin Cavalier
235cbc07268SAugustin Cavalier	_GetPages(rounded_offset, rounded_len, isWrite, pages);
236cbc07268SAugustin Cavalier
237cbc07268SAugustin Cavalier	status_t error = B_OK;
238cbc07268SAugustin Cavalier	size_t index = 0;
239cbc07268SAugustin Cavalier
240cbc07268SAugustin Cavalier	while (length > 0) {
241cbc07268SAugustin Cavalier		vm_page* page = pages[index];
24213249ba0SAugustin Cavalier		phys_addr_t at = (page != NULL)
24313249ba0SAugustin Cavalier			? (page->physical_page_number * B_PAGE_SIZE) : 0;
244cbc07268SAugustin Cavalier		ssize_t bytes = B_PAGE_SIZE;
245cbc07268SAugustin Cavalier		if (index == 0) {
246cbc07268SAugustin Cavalier			const uint32 pageoffset = (offset % B_PAGE_SIZE);
247cbc07268SAugustin Cavalier			at += pageoffset;
248cbc07268SAugustin Cavalier			bytes -= pageoffset;
249224e7c42SIngo Weinhold		}
250cbc07268SAugustin Cavalier		bytes = min(length, bytes);
251cbc07268SAugustin Cavalier
252cbc07268SAugustin Cavalier		if (isWrite) {
253cbc07268SAugustin Cavalier			page->modified = true;
254cbc07268SAugustin Cavalier			error = vm_memcpy_to_physical(at, buffer, bytes, user);
255cbc07268SAugustin Cavalier		} else {
256cbc07268SAugustin Cavalier			if (page != NULL) {
257cbc07268SAugustin Cavalier				error = vm_memcpy_from_physical(buffer, at, bytes, user);
258cbc07268SAugustin Cavalier			} else {
259cbc07268SAugustin Cavalier				if (user) {
260cbc07268SAugustin Cavalier					error = user_memset(buffer, 0, bytes);
261cbc07268SAugustin Cavalier				} else {
262cbc07268SAugustin Cavalier					memset(buffer, 0, bytes);
263cbc07268SAugustin Cavalier				}
264cbc07268SAugustin Cavalier			}
265cbc07268SAugustin Cavalier		}
266cbc07268SAugustin Cavalier		if (error != B_OK)
267cbc07268SAugustin Cavalier			break;
268cbc07268SAugustin Cavalier
269cbc07268SAugustin Cavalier		buffer += bytes;
270cbc07268SAugustin Cavalier		length -= bytes;
271cbc07268SAugustin Cavalier		index++;
272224e7c42SIngo Weinhold	}
273cbc07268SAugustin Cavalier
274cbc07268SAugustin Cavalier	_PutPages(rounded_offset, rounded_len, pages, error == B_OK);
275cbc07268SAugustin Cavalier
276cbc07268SAugustin Cavalier	if (bytesProcessed != NULL)
277cbc07268SAugustin Cavalier		*bytesProcessed = length > 0 ? originalLength - length : originalLength;
278cbc07268SAugustin Cavalier
279224e7c42SIngo Weinhold	return error;
280224e7c42SIngo Weinhold}
281224e7c42SIngo Weinhold
282cbc07268SAugustin Cavalier// _GetPages
283cbc07268SAugustin Cavaliervoid
284cbc07268SAugustin CavalierDataContainer::_GetPages(off_t offset, off_t length, bool isWrite,
285cbc07268SAugustin Cavalier	vm_page** pages)
286224e7c42SIngo Weinhold{
287cbc07268SAugustin Cavalier	// TODO: This method is duplicated in the ram_disk. Perhaps it
288cbc07268SAugustin Cavalier	// should be put into a common location?
289cbc07268SAugustin Cavalier
290cbc07268SAugustin Cavalier	// get the pages, we already have
291cbc07268SAugustin Cavalier	AutoLocker<VMCache> locker(fCache);
292cbc07268SAugustin Cavalier
293cbc07268SAugustin Cavalier	size_t pageCount = length / B_PAGE_SIZE;
294cbc07268SAugustin Cavalier	size_t index = 0;
295cbc07268SAugustin Cavalier	size_t missingPages = 0;
296cbc07268SAugustin Cavalier
297cbc07268SAugustin Cavalier	while (length > 0) {
298cbc07268SAugustin Cavalier		vm_page* page = fCache->LookupPage(offset);
299cbc07268SAugustin Cavalier		if (page != NULL) {
300cbc07268SAugustin Cavalier			if (page->busy) {
301cbc07268SAugustin Cavalier				fCache->WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, true);
302cbc07268SAugustin Cavalier				continue;
303cbc07268SAugustin Cavalier			}
304cbc07268SAugustin Cavalier
305cbc07268SAugustin Cavalier			DEBUG_PAGE_ACCESS_START(page);
306cbc07268SAugustin Cavalier			page->busy = true;
307cbc07268SAugustin Cavalier		} else
308cbc07268SAugustin Cavalier			missingPages++;
309cbc07268SAugustin Cavalier
310cbc07268SAugustin Cavalier		pages[index++] = page;
311cbc07268SAugustin Cavalier		offset += B_PAGE_SIZE;
312cbc07268SAugustin Cavalier		length -= B_PAGE_SIZE;
313cbc07268SAugustin Cavalier	}
314cbc07268SAugustin Cavalier
315cbc07268SAugustin Cavalier	locker.Unlock();
316cbc07268SAugustin Cavalier
317cbc07268SAugustin Cavalier	// For a write we need to reserve the missing pages.
318cbc07268SAugustin Cavalier	if (isWrite && missingPages > 0) {
319cbc07268SAugustin Cavalier		vm_page_reservation reservation;
320cbc07268SAugustin Cavalier		vm_page_reserve_pages(&reservation, missingPages,
321cbc07268SAugustin Cavalier			VM_PRIORITY_SYSTEM);
322cbc07268SAugustin Cavalier
323cbc07268SAugustin Cavalier		for (size_t i = 0; i < pageCount; i++) {
324cbc07268SAugustin Cavalier			if (pages[i] != NULL)
325cbc07268SAugustin Cavalier				continue;
326cbc07268SAugustin Cavalier
327cbc07268SAugustin Cavalier			pages[i] = vm_page_allocate_page(&reservation,
328cbc07268SAugustin Cavalier				PAGE_STATE_WIRED | VM_PAGE_ALLOC_BUSY);
329cbc07268SAugustin Cavalier
330cbc07268SAugustin Cavalier			if (--missingPages == 0)
331cbc07268SAugustin Cavalier				break;
332224e7c42SIngo Weinhold		}
333cbc07268SAugustin Cavalier
334cbc07268SAugustin Cavalier		vm_page_unreserve_pages(&reservation);
335224e7c42SIngo Weinhold	}
336224e7c42SIngo Weinhold}
337224e7c42SIngo Weinhold
338224e7c42SIngo Weinholdvoid
339cbc07268SAugustin CavalierDataContainer::_PutPages(off_t offset, off_t length, vm_page** pages,
340cbc07268SAugustin Cavalier	bool success)
341224e7c42SIngo Weinhold{
342cbc07268SAugustin Cavalier	// TODO: This method is duplicated in the ram_disk. Perhaps it
343cbc07268SAugustin Cavalier	// should be put into a common location?
344cbc07268SAugustin Cavalier
345cbc07268SAugustin Cavalier	AutoLocker<VMCache> locker(fCache);
346224e7c42SIngo Weinhold
347cbc07268SAugustin Cavalier	// Mark all pages unbusy. On error free the newly allocated pages.
348cbc07268SAugustin Cavalier	size_t index = 0;
349cbc07268SAugustin Cavalier
350cbc07268SAugustin Cavalier	while (length > 0) {
351cbc07268SAugustin Cavalier		vm_page* page = pages[index++];
352cbc07268SAugustin Cavalier		if (page != NULL) {
353cbc07268SAugustin Cavalier			if (page->CacheRef() == NULL) {
354cbc07268SAugustin Cavalier				if (success) {
355cbc07268SAugustin Cavalier					fCache->InsertPage(page, offset);
356cbc07268SAugustin Cavalier					fCache->MarkPageUnbusy(page);
357cbc07268SAugustin Cavalier					DEBUG_PAGE_ACCESS_END(page);
358cbc07268SAugustin Cavalier				} else
359cbc07268SAugustin Cavalier					vm_page_free(NULL, page);
360cbc07268SAugustin Cavalier			} else {
361cbc07268SAugustin Cavalier				fCache->MarkPageUnbusy(page);
362cbc07268SAugustin Cavalier				DEBUG_PAGE_ACCESS_END(page);
363cbc07268SAugustin Cavalier			}
364cbc07268SAugustin Cavalier		}
365cbc07268SAugustin Cavalier
366cbc07268SAugustin Cavalier		offset += B_PAGE_SIZE;
367cbc07268SAugustin Cavalier		length -= B_PAGE_SIZE;
368cbc07268SAugustin Cavalier	}
369cbc07268SAugustin Cavalier}