SharpCAT/VirtualSerial2/ringbuffer.c

339 lines
9.1 KiB
C
Raw Normal View History

/*++
Copyright (c) Microsoft Corporation, All Rights Reserved
Module Name:
RingBuffer.c
Abstract:
This file implements the Ring Buffer
Environment:
--*/
#include "internal.h"
VOID
RingBufferInitialize(
_In_ PRING_BUFFER Self,
_In_reads_bytes_(BufferSize)
BYTE* Buffer,
_In_ size_t BufferSize
)
{
Self->Size = BufferSize;
Self->Base = Buffer;
Self->End = Buffer + BufferSize;
Self->Head = Buffer;
Self->Tail = Buffer;
}
VOID
RingBufferGetAvailableSpace(
_In_ PRING_BUFFER Self,
_Out_ size_t *AvailableSpace
)
{
BYTE* headSnapshot = NULL;
BYTE* tailSnapshot = NULL;
BYTE* tailPlusOne = NULL;
ASSERT(AvailableSpace);
//
// Take a snapshot of the head and tail pointers. We will compute the
// available space based on this snapshot. This is safe to do in a
// single-producer, single-consumer model, because -
// * A producer will call GetAvailableSpace() to determine whether
// there is enough space to write the data it is trying to write.
// The only other thread that could modify the amount of space
// available is the consumer thread, which can only increase the
// amount of space available. Hence it is safe for the producer
// to write based on this snapshot.
// * A consumer thread will call GetAvailableSpace() to determine
// whether there is enough data in the buffer for it to read.
// (Available data = Buffer size - Available space). The only
// other thread that could modify the amount of space available
// is the producer thread, which can only decrease the amount of
// space available (thereby increasing the amount of data
// available. Hence it is safe for the consumer to read based on
// this snapshot.
//
headSnapshot = Self->Head;
tailSnapshot = Self->Tail;
//
// In order to distinguish between a full buffer and an empty buffer,
// we always leave the last byte of the buffer unused. So, an empty
// buffer is denoted by -
// tail == head
// ... and a full buffer is denoted by -
// (tail+1) == head
//
tailPlusOne = ((tailSnapshot+1) == Self->End) ? Self->Base : (tailSnapshot+1);
if (tailPlusOne == headSnapshot)
{
//
// Buffer full
//
*AvailableSpace = 0;
}
else if (tailSnapshot == headSnapshot)
{
//
// Buffer empty
// The -1 in the computation below is to account for the fact that
// we always leave the last byte of the ring buffer unused in order
// to distinguish between an empty buffer and a full buffer.
//
*AvailableSpace = Self->Size - 1;
}
else
{
if (tailSnapshot > headSnapshot)
{
//
// Data has not wrapped around the end of the buffer
// The -1 in the computation below is to account for the fact
// that we always leave the last byte of the ring buffer unused
// in order to distinguish between an empty buffer and a full
// buffer.
//
*AvailableSpace = Self->Size - (tailSnapshot - headSnapshot) - 1;
}
else
{
//
// Data has wrapped around the end of the buffer
// The -1 in the computation below is to account for the fact
// that we always leave the last byte of the ring buffer unused
// in order to distinguish between an empty buffer and a full
// buffer.
//
*AvailableSpace = (headSnapshot - tailSnapshot) - 1;
}
}
}
VOID
RingBufferGetAvailableData(
_In_ PRING_BUFFER Self,
_Out_ size_t *AvailableData
)
{
size_t availableSpace;
ASSERT(AvailableData);
RingBufferGetAvailableSpace(Self, &availableSpace);
//
// The -1 in the arithmetic below accounts for the fact that we always
// keep 1 byte of the ring buffer unused in order to distinguish
// between a full buffer and an empty buffer.
//
*AvailableData = Self->Size - availableSpace - 1;
}
NTSTATUS
RingBufferWrite(
_In_ PRING_BUFFER Self,
_In_reads_bytes_(DataSize)
BYTE* Data,
_In_ size_t DataSize
)
{
size_t availableSpace;
size_t bytesToCopy;
size_t spaceFromCurrToEnd;
ASSERT(Data && (0 != DataSize));
if (Self->Tail >= Self->End)
{
return STATUS_INTERNAL_ERROR;
}
//
// Get the amount of space available in the buffer
//
RingBufferGetAvailableSpace(Self, &availableSpace);
//
// If there is not enough space to fit in all the data passed in by the
// caller then copy as much as possible and throw away the rest
//
if (availableSpace < DataSize)
{
bytesToCopy = availableSpace;
}
else
{
bytesToCopy = DataSize;
}
if (bytesToCopy)
{
//
// The buffer has some space at least
//
if ((Self->Tail + bytesToCopy) > Self->End)
{
//
// The data being written will wrap around the end of the buffer.
// So the copy has to be done in two steps -
// * X bytes from current position to end of the buffer
// * the remaining (bytesToCopy - X) from the start of the buffer
//
//
// The first step of the copy ...
//
spaceFromCurrToEnd = Self->End - Self->Tail;
RtlCopyMemory(Self->Tail, Data, spaceFromCurrToEnd);
Data += spaceFromCurrToEnd;
bytesToCopy -= spaceFromCurrToEnd;
//
// The second step of the copy ...
//
RtlCopyMemory(Self->Base, Data, bytesToCopy);
//
// Advance the tail pointer
//
Self->Tail = Self->Base + bytesToCopy;
}
else
{
//
// Data does NOT wrap around the end of the buffer. Just copy it
// over in a single step
//
RtlCopyMemory(Self->Tail, Data, bytesToCopy);
//
// Advance the tail pointer
//
Self->Tail += bytesToCopy;
if (Self->Tail == Self->End)
{
//
// We have exactly reached the end of the buffer. The next
// write should wrap around and start from the beginning.
//
Self->Tail = Self->Base;
}
}
ASSERT(Self->Tail < Self->End);
}
return STATUS_SUCCESS;
}
NTSTATUS
RingBufferRead(
_In_ PRING_BUFFER Self,
_Out_writes_bytes_to_(DataSize, *BytesCopied)
BYTE* Data,
_In_ size_t DataSize,
_Out_ size_t *BytesCopied
)
{
size_t availableData;
size_t dataFromCurrToEnd;
ASSERT(Data && (DataSize != 0));
if (Self->Head >= Self->End)
{
return STATUS_INTERNAL_ERROR;
}
//
// Get the amount of data available in the buffer
//
RingBufferGetAvailableData(Self, &availableData);
if (availableData == 0)
{
*BytesCopied = 0;
return STATUS_SUCCESS;
}
if (DataSize > availableData)
{
DataSize = availableData;
}
*BytesCopied = DataSize;
if ((Self->Head + DataSize) > Self->End)
{
//
// The data requested by the caller is wrapped around the end of the
// buffer. So we'll do the copy in two steps -
// * Copy X bytes from the current position to the end buffer into
// the caller's buffer
// * Copy (DataSize - X) bytes from the beginning to the buffer into
// the caller's buffer
//
//
// The first step of the copy ...
//
dataFromCurrToEnd = Self->End - Self->Head;
RtlCopyMemory(Data, Self->Head, dataFromCurrToEnd);
Data += dataFromCurrToEnd;
DataSize -= dataFromCurrToEnd;
//
// The second step of the copy ...
//
RtlCopyMemory(Data, Self->Base, DataSize);
//
// Advance the head pointer
//
Self->Head = Self->Base + DataSize;
}
else
{
//
// The data in the buffer is NOT wrapped around the end of the buffer.
// Simply copy the data over to the caller's buffer in a single step.
//
RtlCopyMemory(Data, Self->Head, DataSize);
//
// Advance the head pointer
//
Self->Head += DataSize;
if (Self->Head == Self->End)
{
//
// We have exactly reached the end of the buffer. The next
// read should wrap around and start from the beginning.
//
Self->Head = Self->Base;
}
}
ASSERT(Self->Head < Self->End);
return STATUS_SUCCESS;
}