1/*
2 * Copyright 2022 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Leorize, leorize+oss@disroot.org
7 */
8
9
10#include <MemoryRingIO.h>
11
12#include <AutoLocker.h>
13
14#include <algorithm>
15
16#include <stdlib.h>
17#include <string.h>
18
19
20class PThreadLocking {
21public:
22	inline bool Lock(pthread_mutex_t* mutex)
23	{
24		return pthread_mutex_lock(mutex) == 0;
25	}
26
27	inline void Unlock(pthread_mutex_t* mutex)
28	{
29		pthread_mutex_unlock(mutex);
30	}
31};
32
33
34typedef AutoLocker<pthread_mutex_t, PThreadLocking> PThreadAutoLocker;
35
36
37struct ReadCondition {
38	inline bool operator()(BMemoryRingIO &ring) {
39		return ring.BytesAvailable() != 0;
40	}
41};
42
43
44struct WriteCondition {
45	inline bool operator()(BMemoryRingIO &ring) {
46		return ring.SpaceAvailable() != 0;
47	}
48};
49
50
51#define RING_MASK(x) ((x) & (fBufferSize - 1))
52
53
54static size_t
55next_power_of_two(size_t value)
56{
57	value--;
58	value |= value >> 1;
59	value |= value >> 2;
60	value |= value >> 4;
61	value |= value >> 8;
62	value |= value >> 16;
63#if SIZE_MAX >= UINT64_MAX
64	value |= value >> 32;
65#endif
66	value++;
67
68	return value;
69}
70
71
72BMemoryRingIO::BMemoryRingIO(size_t size)
73	:
74	fBuffer(NULL),
75	fBufferSize(0),
76	fWriteAtNext(0),
77	fReadAtNext(0),
78	fBufferFull(false),
79	fWriteDisabled(false)
80{
81	// We avoid the use of pthread_mutexattr as it can possibly fail.
82	//
83	// The only Haiku-specific behavior that we depend on is that
84	// PTHREAD_MUTEX_DEFAULT mutexes check for double-locks.
85	pthread_mutex_init(&fLock, NULL);
86	pthread_cond_init(&fEvent, NULL);
87
88	SetSize(size);
89}
90
91
92BMemoryRingIO::~BMemoryRingIO()
93{
94	SetSize(0);
95
96	pthread_mutex_destroy(&fLock);
97	pthread_cond_destroy(&fEvent);
98}
99
100
101status_t
102BMemoryRingIO::InitCheck() const
103{
104	if (fBufferSize == 0)
105		return B_NO_INIT;
106
107	return B_OK;
108}
109
110
111ssize_t
112BMemoryRingIO::Read(void* _buffer, size_t size)
113{
114	if (_buffer == NULL)
115		return B_BAD_VALUE;
116	if (size == 0)
117		return 0;
118
119	PThreadAutoLocker _(fLock);
120
121	if (!fWriteDisabled)
122		WaitForRead();
123
124	size = std::min(size, BytesAvailable());
125	uint8* buffer = reinterpret_cast<uint8*>(_buffer);
126	if (fReadAtNext + size < fBufferSize)
127		memcpy(buffer, fBuffer + fReadAtNext, size);
128	else {
129		const size_t upper = fBufferSize - fReadAtNext;
130		const size_t lower = size - upper;
131		memcpy(buffer, fBuffer + fReadAtNext, upper);
132		memcpy(buffer + upper, fBuffer, lower);
133	}
134	fReadAtNext = RING_MASK(fReadAtNext + size);
135	fBufferFull = false;
136
137	pthread_cond_signal(&fEvent);
138
139	return size;
140}
141
142
143ssize_t
144BMemoryRingIO::Write(const void* _buffer, size_t size)
145{
146	if (_buffer == NULL)
147		return B_BAD_VALUE;
148	if (size == 0)
149		return 0;
150
151	PThreadAutoLocker locker(fLock);
152
153	if (!fWriteDisabled)
154		WaitForWrite();
155
156	// We separate this check from WaitForWrite() as the boolean
157	// might have been toggled during our wait on the conditional.
158	if (fWriteDisabled)
159		return B_READ_ONLY_DEVICE;
160
161	const uint8* buffer = reinterpret_cast<const uint8*>(_buffer);
162	size = std::min(size, SpaceAvailable());
163	if (fWriteAtNext + size < fBufferSize)
164		memcpy(fBuffer + fWriteAtNext, buffer, size);
165	else {
166		const size_t upper = fBufferSize - fWriteAtNext;
167		const size_t lower = size - upper;
168		memcpy(fBuffer + fWriteAtNext, buffer, size);
169		memcpy(fBuffer, buffer + upper, lower);
170	}
171	fWriteAtNext = RING_MASK(fWriteAtNext + size);
172	fBufferFull = fReadAtNext == fWriteAtNext;
173
174	pthread_cond_signal(&fEvent);
175
176	return size;
177}
178
179
180status_t
181BMemoryRingIO::SetSize(size_t _size)
182{
183	PThreadAutoLocker locker(fLock);
184
185	const size_t size = next_power_of_two(_size);
186
187	const size_t availableBytes = BytesAvailable();
188	if (size < availableBytes)
189		return B_BAD_VALUE;
190
191	if (size == 0) {
192		free(fBuffer);
193		fBuffer = NULL;
194		fBufferSize = 0;
195		Clear(); // resets other internal counters
196		return B_OK;
197	}
198
199	uint8* newBuffer = reinterpret_cast<uint8*>(malloc(size));
200	if (newBuffer == NULL)
201		return B_NO_MEMORY;
202
203	Read(newBuffer, availableBytes);
204	free(fBuffer);
205
206	fBuffer = newBuffer;
207	fBufferSize = size;
208	fReadAtNext = 0;
209	fWriteAtNext = RING_MASK(availableBytes);
210	fBufferFull = fBufferSize == availableBytes;
211
212	pthread_cond_signal(&fEvent);
213
214	return B_OK;
215}
216
217
218void
219BMemoryRingIO::Clear()
220{
221	PThreadAutoLocker locker(fLock);
222
223	fReadAtNext = 0;
224	fWriteAtNext = 0;
225	fBufferFull = false;
226}
227
228
229size_t
230BMemoryRingIO::BytesAvailable()
231{
232	PThreadAutoLocker locker(fLock);
233
234	if (fWriteAtNext == fReadAtNext) {
235		if (fBufferFull)
236			return fBufferSize;
237		return 0;
238	}
239	return RING_MASK(fWriteAtNext - fReadAtNext);
240}
241
242
243size_t
244BMemoryRingIO::SpaceAvailable()
245{
246	PThreadAutoLocker locker(fLock);
247
248	return fBufferSize - BytesAvailable();
249}
250
251
252size_t
253BMemoryRingIO::BufferSize()
254{
255	PThreadAutoLocker locker(fLock);
256
257	return fBufferSize;
258}
259
260
261template<typename Condition>
262status_t
263BMemoryRingIO::_WaitForCondition(bigtime_t timeout)
264{
265	PThreadAutoLocker autoLocker;
266
267	struct timespec absTimeout;
268	if (timeout == B_INFINITE_TIMEOUT) {
269		autoLocker.SetTo(fLock, false);
270	} else {
271		memset(&absTimeout, 0, sizeof(absTimeout));
272		bigtime_t target = system_time() + timeout;
273		absTimeout.tv_sec = target / 100000;
274		absTimeout.tv_nsec = (target % 100000) * 1000L;
275		int err = pthread_mutex_timedlock(&fLock, &absTimeout);
276		if (err == ETIMEDOUT)
277			return B_TIMED_OUT;
278		if (err != EDEADLK)
279			autoLocker.SetTo(fLock, true);
280	}
281
282	Condition cond;
283	while (!cond(*this)) {
284		if (fWriteDisabled)
285			return B_READ_ONLY_DEVICE;
286
287		int err = 0;
288
289		if (timeout == B_INFINITE_TIMEOUT)
290			err = pthread_cond_wait(&fEvent, &fLock);
291		else
292			err = pthread_cond_timedwait(&fEvent, &fLock, &absTimeout);
293
294		if (err != 0)
295			return err;
296	}
297
298	return B_OK;
299}
300
301
302status_t
303BMemoryRingIO::WaitForRead(bigtime_t timeout)
304{
305	return _WaitForCondition<ReadCondition>(timeout);
306}
307
308
309status_t
310BMemoryRingIO::WaitForWrite(bigtime_t timeout)
311{
312	return _WaitForCondition<WriteCondition>(timeout);
313}
314
315
316void
317BMemoryRingIO::SetWriteDisabled(bool disabled)
318{
319	PThreadAutoLocker autoLocker(fLock);
320
321	fWriteDisabled = disabled;
322
323	pthread_cond_broadcast(&fEvent);
324}
325
326
327bool
328BMemoryRingIO::WriteDisabled()
329{
330	PThreadAutoLocker autoLocker(fLock);
331
332	return fWriteDisabled;
333}
334