friendev EtherDune TCP/IP library
|
Implements a "shared" circular buffer using spare ENC28J60 memory. More...
#include <SharedBuffer.h>
Public Member Functions | |
uint16_t | write (uint16_t len, const byte *data) |
Writes a fragment to the shared buffer More... | |
uint16_t | release () |
Releases one fragment of data More... | |
SharedBuffer () | |
~SharedBuffer () | |
void | flush () |
Releases all data in this buffer, freeing up space. More... | |
bool | isEmpty () |
Determines whether this buffer is empty More... | |
uint16_t | fillTxBuffer (uint16_t dstOffset, uint16_t &checksum, uint16_t count=0xFFFF) |
Copies up to count fragments to the transmit buffer via DMA, concatenating them starting at given offset. More... | |
Implements a "shared" circular buffer using spare ENC28J60 memory.
This class takes the memory that is not used for the receive/transmit buffer and turns it into a circular buffer shared by all sockets or any network service that needs to assemble packets before they are put on the network, or in the case of TCP, buffer data in case a retransmit is necessary.
The concept of "shared circular buffer" is an attempt to provide each network service with its own circular buffer but without having to statically assign a chunk of dedicated memory to each possible service. Instead, all services share the same memory chunk, which is about 4kB in length by default. See the ENC28J60 Hardware configuration section to see how the ENC28J60 8kB memory is distributed among TX buffer, RX buffer and this shared buffer.
This "shared circular buffer" indeed creates a virtual private circular buffer for each client. To do this, the implementation keeps track of the head of the circular buffer and all tails. Every time a SharedBuffer writes, writes to the head. Every time a SharedBuffer releases data it does not need any more, data is only actually freed if that last release happened to be the last tail.
Consider the following memory area we want to share for 3 "virtual" circular buffers A, B and C. In the example, tail pointers A, B, C point each to the first byte to be read.
Now, let's say A writes a fragment of data to the buffer:
And then B and C write to the buffer
Then B writes again:
At this point, lets say B decides to read out (release) the first fragment. Since this is a circular buffer, only the first fragment (the one pointed out by tail pointer B) may be released:
Fragment B0 is released and B's tail pointer is updated to point to B1
This has fragmented our memory space. The "shared" circular buffer implementation does not keep track of free fragments that exist between used chunks of memory. The implementation makes an optimistic assumption that free chunks will eventually coalesce since the buffer is supposed to be used to keep data for a short amount of time. This assumption saves a lot of overhead in keeping track of these fragments and maximizes usage of the limited memory available. The caveat here is that if a service does not free a fragment in a timely manner it will eventually block all services. This is mitigated by 1) not running many services simultaneously (at the end, we're in a microcontroller!) and 2) careful service design: making sure that services release data frequently or time out and release in case of errors.
Back to our example, when A releases its only fragment, we end up with this:
The global tail
now points to C and all the free space is consolidated in one chunk (remember this is a circular buffer and therefore it wraps around).
You can think of this shared circular buffer as a regular circular buffer in which the head is shared for all and the tail is the one that is most behind.
This mechanism is implemented in the SharedBuffer class. Each instance of SharedBuffer represents an independent buffer (like A, B or C in the example) in the shared memory area. SharedBuffer also takes care of writing a small header for each fragment that contains a pointer to the next fragment, the length of the fragment and a checksum of the data in the fragment that can be combined to calculate the checksum of the concatenation.
The class includes a method, fillTxBuffer() that reads out fragments and concatenates them into the transmit buffer, calculating the resulting checksum.
Definition at line 158 of file SharedBuffer.h.
SharedBuffer::SharedBuffer | ( | ) |
Definition at line 40 of file SharedBuffer.cpp.
SharedBuffer::~SharedBuffer | ( | ) |
Definition at line 45 of file SharedBuffer.cpp.
uint16_t SharedBuffer::fillTxBuffer | ( | uint16_t | dstOffset, |
uint16_t & | checksum, | ||
uint16_t | count = 0xFFFF |
||
) |
Copies up to count
fragments to the transmit buffer via DMA, concatenating them starting at given offset.
This function may copy fewer fragments than count
to make sure not to overflow the TX buffer
dstOffset | Destination ENC28J60 memory address, encoded as an offset within the tx buffer. |
checksum | [out] Resulting checksum of copied data |
count | Maximum number of fragments to concatenate |
Definition at line 212 of file SharedBuffer.cpp.
void SharedBuffer::flush | ( | ) |
Releases all data in this buffer, freeing up space.
Definition at line 198 of file SharedBuffer.cpp.
bool SharedBuffer::isEmpty | ( | ) |
Determines whether this buffer is empty
true
if the buffer is empty. false
otherwiseDefinition at line 263 of file SharedBuffer.cpp.
uint16_t SharedBuffer::release | ( | ) |
Releases one fragment of data
Definition at line 162 of file SharedBuffer.cpp.
uint16_t SharedBuffer::write | ( | uint16_t | len, |
const byte * | data | ||
) |
Writes a fragment to the shared buffer
len | length of the data pointed by data |
data | pointer to the data to write |
Definition at line 126 of file SharedBuffer.cpp.