diff options
| author | Dave Wilson <[email protected]> | 2015-03-17 19:50:07 -0700 |
|---|---|---|
| committer | Dave Wilson <[email protected]> | 2015-03-17 19:50:07 -0700 |
| commit | 97cf5197cf5b882b2c689d8dc2b555f2edf8f418 (patch) | |
| tree | 46f3701832d70b420eb0fc0eb93261f9da45db3f /serial/VirtualSerial/ringbuffer.h | |
| parent | ef1905bf1e8825bb31120dfb27e0daf3154d859a (diff) | |
Initial publish
Diffstat (limited to 'serial/VirtualSerial/ringbuffer.h')
| -rw-r--r-- | serial/VirtualSerial/ringbuffer.h | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/serial/VirtualSerial/ringbuffer.h b/serial/VirtualSerial/ringbuffer.h new file mode 100644 index 00000000..130055d9 --- /dev/null +++ b/serial/VirtualSerial/ringbuffer.h @@ -0,0 +1,223 @@ +#pragma once + +#include "internal.h" + +class CRingBuffer +{ +private: + + // + // Private local variables. + // + + // + // The size in bytes of the ring buffer. + // + SIZE_T m_Size; + + // + // A pointer to the base of the ring buffer. + // + PBYTE m_Base; + + // + // A pointer to the byte beyond the end of the ring buffer. Used for + // quick comparisons when determining if we need to wrap. + // + PBYTE m_End; + + // + // A pointer to the current read point in the ring buffer. + // + // Updates to this are not protected by any lock. This is different from + // the write pointer, which is protected by the "pending read pointer" + // lock. The reason for this difference is that in this driver, we do not + // keep write requests pending. If there is not enough space to write all + // the data that was requested, we write as much as we can and drop the + // rest (lossy data transfer). + // + // If we had multiple threads modifying this pointer, then that would + // provide yet another reason for protecting updates to the pointer using a + // lock. However, in this driver, at any given time we have only one thread + // that modifies this pointer (the thread that runs the read callback). + // This is true because we use a sequential queue for read requests. If we + // were to change our read queue to be a parallel queue, this would no + // longer be true. + // + // + PBYTE m_Head; + + // + // A pointer to the current write point in the ring buffer. + // + // Updates to this pointer are protected by the "pending read pointer + // lock", because we do not want a consumer thread to mark a read request + // as pending while we are in the process of writing data to the buffer. + // The reason is that the write that we are currently performing might + // actually supply enough data to satisfy the read request, in which case + // it should not be marked pending at all. + // If the read request were to be marked pending in the situation described + // above, then we would need some trigger to later retrieve the request and + // complete it. In our driver, arrival of data is the only event that can + // trigger this. So if no more data arrives, the request will remain + // pending forever, even though there is enough data in the buffer to + // complete it. Hence we do not keep a read request pending in situations + // where the read buffer contains enough data to satisfy it. + // + // If we had multiple threads modifying this pointer, then that would + // provide yet another reason for protecting updates to the pointer using a + // lock. However, in this driver, at any given time we have only one thread + // that modifies this pointer (the thread that runs the write callback). + // This is true because we use a sequential queue for write requests. If we + // were to change our write queue to be a parallel queue, this would no + // longer be true. + // + PBYTE m_Tail; + +private: + + // + // Private Internal Methods. + // + void + GetAvailableSpace( + _Out_ SIZE_T *AvailableSpace + ) + { + WUDF_TEST_DRIVER_ASSERT(AvailableSpace); + + PBYTE headSnapshot = NULL; + PBYTE tailSnapshot = NULL; + PBYTE tailPlusOne = NULL; + + // + // 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 = m_Head; + tailSnapshot = m_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) == m_End) ? m_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 = m_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 = m_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; + } + } + + return; + } + +public: + + // + // Public Internal methods. + // + + CRingBuffer( + VOID + ); + + ~CRingBuffer( + VOID + ); + + HRESULT + Initialize( + _In_ SIZE_T BufferSize + ); + + HRESULT + Write( + _In_reads_bytes_(DataSize) PBYTE Data, + _In_ SIZE_T DataSize + ); + + HRESULT + Read( + _Out_writes_bytes_to_(DataSize, *BytesCopied) PBYTE Data, + _In_ SIZE_T DataSize, + _Out_ SIZE_T *BytesCopied + ); + + void + GetAvailableData( + _Out_ SIZE_T *AvailableData + ) + { + SIZE_T availableSpace; + + WUDF_TEST_DRIVER_ASSERT(AvailableData); + + GetAvailableSpace(&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 = m_Size - availableSpace - 1; + + return; + } +}; |
