blob: 2f26fa3725d93a5dfcd701cd7cae7d038f0bca35 [file] [log] [blame]
#pragma once
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Memory layout for byte-oriented circular queues
#include <atomic>
#include <cstdint>
#include "common/vsoc/shm/base.h"
#include "common/vsoc/shm/lock.h"
struct iovec;
namespace vsoc {
class RegionSignalingInterface;
namespace layout {
/**
* Base classes for all spinlock protected circular queues.
* This class should be embedded in the per-region data structure that is used
* as the parameter to TypedRegion.
*/
template <uint32_t SizeLog2>
class CircularQueueBase {
CircularQueueBase() = delete;
CircularQueueBase(const CircularQueueBase&) = delete;
CircularQueueBase& operator=(const CircularQueueBase&) = delete;
protected:
/**
* Specifies a part of the queue. Note, the given indexes must be masked
* before they can be used against buffer_
*/
struct Range {
// Points to the first bytes that is part of the range
uint32_t start_idx;
// Points to the first byte that is not in the range. This is similar to
// the STL end iterator.
uint32_t end_idx;
};
static const uintptr_t BufferSize = (1 << SizeLog2);
/**
* Copy bytes from buffer_in into the part of the queue specified by Range.
*/
void CopyInRange(const char* buffer_in, const Range& t);
/**
* Copy the bytes specified by range to the given buffer. They caller must
* ensure that the buffer is large enough to hold the content of the range.
*/
void CopyOutRange(const Range& t, char* buffer_out);
/**
* Wait until data becomes available in the queue. The caller must have
* called Lock() before invoking this. The caller must call Unlock()
* after this returns.
*/
void WaitForDataLocked(RegionSignalingInterface* r);
/**
* Reserve space in the queue for writing. The caller must have called Lock()
* before invoking this. The caller must call Unlock() after this returns.
* Indexes pointing to the reserved space will be placed in range.
* On success this returns bytes.
* On failure a negative errno indicates the problem. -ENOSPC indicates that
* bytes > the queue size, -EWOULDBLOCK indicates that the call would block
* waiting for space but was requested non bloking.
*/
intptr_t WriteReserveLocked(RegionSignalingInterface* r, size_t bytes,
Range* t, bool non_blocking);
bool RecoverBase() {
return lock_.Recover();
}
// Note: Both of these fields may hold values larger than the buffer size,
// they should be interpreted modulo the buffer size. This fact along with the
// buffer size being a power of two greatly simplyfies the index calculations.
// Advances when a reader has finished with buffer space
std::atomic<uint32_t> r_released_;
// Advances when buffer space is filled and ready for a reader
std::atomic<uint32_t> w_pub_;
// Spinlock that protects the region. 0 means unlocked
SpinLock lock_;
// The actual memory in the buffer
char buffer_[BufferSize];
};
using CircularQueueBase64k = CircularQueueBase<16>;
ASSERT_SHM_COMPATIBLE(CircularQueueBase64k, multi_region);
/**
* Byte oriented circular queue. Reads will always return some data, but
* may return less data than requested. Writes will always write all of the
* data or return an error.
*/
template <uint32_t SizeLog2>
class CircularByteQueue : public CircularQueueBase<SizeLog2> {
public:
/**
* Read at most max_size bytes from the qeueue, placing them in buffer_out
*/
intptr_t Read(RegionSignalingInterface* r, char* buffer_out,
std::size_t max_size);
/**
* Write all of the given bytes into the queue. If non_blocking isn't set the
* call may block until there is enough available space in the queue. On
* success the return value will match bytes. On failure a negative errno is
* returned. -ENOSPC: If the queue size is smaller than the number of bytes to
* write. -EWOULDBLOCK: If non_blocking is true and there is not enough free
* space.
*/
intptr_t Write(RegionSignalingInterface* r, const char* buffer_in,
std::size_t bytes, bool non_blocking = false);
bool Recover() {
return this->RecoverBase();
}
protected:
using Range = typename CircularQueueBase<SizeLog2>::Range;
};
using CircularByteQueue64k = CircularByteQueue<16>;
ASSERT_SHM_COMPATIBLE(CircularByteQueue64k, multi_region);
/**
* Packet oriented circular queue. Reads will either return data or an error.
* Each return from read corresponds to a call to write and returns all of the
* data from that corresponding Write().
*/
template <uint32_t SizeLog2, uint32_t MaxPacketSize>
class CircularPacketQueue : public CircularQueueBase<SizeLog2> {
public:
/**
* Read a single packet from the queue, placing its data into buffer_out.
* If max_size indicates that buffer_out cannot hold the entire packet
* this function will return -ENOSPC.
*/
intptr_t Read(RegionSignalingInterface* r, char* buffer_out,
std::size_t max_size);
/**
* Writes [buffer_in, buffer_in + bytes) to the queue.
* If the number of bytes to be written exceeds the size of the queue
* -ENOSPC will be returned.
* If non_blocking is true and there is not enough free space on the queue to
* write all the data -EWOULDBLOCK will be returned.
*/
intptr_t Write(RegionSignalingInterface* r, const char* buffer_in,
uint32_t bytes, bool non_blocking = false);
/**
* Writes the data referenced by the given iov scatter/gather array to the
* queue.
* If the number of bytes to be written exceeds the size of the queue
* -ENOSPC will be returned.
* If non_blocking is true and there is not enough free space on the queue to
* write all the data -EWOULDBLOCK will be returned.
*/
intptr_t Writev(
RegionSignalingInterface *r,
const iovec *iov,
size_t iov_count,
bool non_blocking = false);
bool Recover() {
return this->RecoverBase();
}
protected:
static_assert(CircularQueueBase<SizeLog2>::BufferSize >= MaxPacketSize,
"Buffer is too small to hold the maximum sized packet");
using Range = typename CircularQueueBase<SizeLog2>::Range;
intptr_t CalculateBufferedSize(size_t payload);
};
using CircularPacketQueue64k = CircularPacketQueue<16, 1024>;
ASSERT_SHM_COMPATIBLE(CircularPacketQueue64k, multi_region);
} // namespace layout
} // namespace vsoc