diff --git a/inc/SerialComms.h b/inc/SerialComms.h index 0243c35..8a1f347 100644 --- a/inc/SerialComms.h +++ b/inc/SerialComms.h @@ -3,6 +3,7 @@ #include extern class CSuperSerialCard sg_SSC; +class DataRing; enum { COMMEVT_WAIT = 0, COMMEVT_ACK, COMMEVT_TERM, COMMEVT_MAX @@ -43,9 +44,7 @@ typedef struct { class CSuperSerialCard { public: CSuperSerialCard(); - - virtual ~CSuperSerialCard() { - } + virtual ~CSuperSerialCard(); void CommInitialize(LPBYTE pCxRomPeripheral, unsigned int uSlot); @@ -129,8 +128,7 @@ class CSuperSerialCard { int m_hCommHandle; // file for communication with COM unsigned int m_dwCommInactivity; - unsigned char m_RecvBuffer[uRecvBufferSize]; // NB: More work required if >1 is used - volatile unsigned int m_vRecvBytes; + DataRing *rxbuf = nullptr; bool m_bTxIrqEnabled; bool m_bRxIrqEnabled; diff --git a/inc/file_entry.h b/inc/file_entry.h index d77ad4c..83b6665 100644 --- a/inc/file_entry.h +++ b/inc/file_entry.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include diff --git a/inc/stdafx.h b/inc/stdafx.h index 309c74d..7c322dc 100644 --- a/inc/stdafx.h +++ b/inc/stdafx.h @@ -14,7 +14,7 @@ #include #include #include -#include +#include #ifndef _WIN32 # include "wincompat.h" diff --git a/src/AY8910.cpp b/src/AY8910.cpp index b8db94b..e9f6757 100644 --- a/src/AY8910.cpp +++ b/src/AY8910.cpp @@ -29,7 +29,7 @@ // #include -#include +#include #include "wincompat.h" #include #include diff --git a/src/Riff.cpp b/src/Riff.cpp index fae3626..11edd9b 100644 --- a/src/Riff.cpp +++ b/src/Riff.cpp @@ -29,7 +29,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA /* Adaptation for SDL and POSIX (l) by beom beotiger, Nov-Dec 2007 */ #include -#include +#include #include "wincompat.h" #include "Riff.h" #include "wwrapper.h" diff --git a/src/SerialComms.cpp b/src/SerialComms.cpp index c18ceb7..981c179 100644 --- a/src/SerialComms.cpp +++ b/src/SerialComms.cpp @@ -50,6 +50,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // for read() and write() #include +#include "dataring.h" // rxbuf + char SSC_rom[] = "\x20\x9B\xC9\xA9\x16\x48\xA9\x00\x9D\xB8\x04\x9D\xB8\x03\x9D\x38" "\x04\x9D\xB8\x05\x9D\x38\x06\x9D\xB8\x06\xB9\x82\xC0\x85\x2B\x4A" "\x4A\x90\x04\x68\x29\xFE\x48\xB8\xB9\x81\xC0\x4A\xB0\x07\x4A\xB0" @@ -180,7 +182,9 @@ char SSC_rom[] = "\x20\x9B\xC9\xA9\x16\x48\xA9\x00\x9D\xB8\x04\x9D\xB8\x03\x9D\x "\x36\xE8\xE0\x04\x90\xF8\xAE\xF8\x07\x60\xC1\xD0\xD0\xCC\xC5\x08"; -pthread_mutex_t m_CriticalSection = PTHREAD_MUTEX_INITIALIZER; +// guards rxbuf. I don't think that the Linux port actually does still use threads, +// but that's how it was in mainline linapple-pie so I'll keep it for now. +static pthread_mutex_t m_CriticalSection = PTHREAD_MUTEX_INITIALIZER; // Default: 19200-8-N-1 // Maybe a better default is: 9600-7-N-1 (for HyperTrm) @@ -200,8 +204,6 @@ CSuperSerialCard::CSuperSerialCard() { GetDIPSW(); - m_vRecvBytes = 0; - m_hCommHandle = -1; m_dwCommInactivity = 0; @@ -217,8 +219,15 @@ CSuperSerialCard::CSuperSerialCard() { m_hCommEvent[i] = NULL; memset(&m_o, 0, sizeof(m_o)); + rxbuf = new DataRing(256); +} + +CSuperSerialCard::~CSuperSerialCard() { + delete rxbuf; + rxbuf = nullptr; } + // TODO: Serial Comms - UI Property Sheet Page: // . Ability to config the 2x DIPSWs - only takes affect after next Apple2 reset // . 'Default' button that resets DIPSWs to DIPSWDefaults @@ -302,6 +311,9 @@ void CSuperSerialCard::UpdateCommState() { int l_databits = CS8; tcgetattr(m_hCommHandle, &dcb); // get current attributes for m_hCommHandle in dcb + // don't bork ascii CR chars + cfmakeraw(&dcb); + // set input/output speed to m_uBaudRate cfsetispeed(&dcb, m_uBaudRate); cfsetospeed(&dcb, m_uBaudRate); @@ -370,15 +382,19 @@ bool CSuperSerialCard::CheckComm() { m_dwCommInactivity = 0; if ((m_hCommHandle == -1) && m_dwSerialPort) { - char portname[12]; // we have /dev/ttyS0..X instead of COM1..COMX+1? + char portname[32]; // we have /dev/ttyS0..X instead of COM1..COMX+1? if (m_dwSerialPort < 0 || m_dwSerialPort > 99) { m_dwSerialPort = 1; // buffer overflow check } - sprintf(portname, TEXT("/dev/ttyS%u"), (unsigned int) (m_dwSerialPort - 1)); + sprintf(portname, TEXT("/dev/ttyUSB%u"), (unsigned int) (m_dwSerialPort - 1)); m_hCommHandle = open(portname, O_RDWR | O_NOCTTY | O_NDELAY); if (m_hCommHandle != -1) { UpdateCommState(); CommThInit(); + printf("Opened serial port %s\n", portname); + } + else { + printf("ERROR: failed to open serial port %s: %s\n", portname, strerror(errno)); } } @@ -399,6 +415,10 @@ unsigned char CSuperSerialCard::SSC_IORead(unsigned short PC, unsigned short uAd unsigned int uSlot = ((uAddr & 0xff) >> 4) - 8; CSuperSerialCard *pSSC = (CSuperSerialCard *) MemGetSlotParameters(uSlot); + // hack: we need to actually read the buffer sometimes since the receive thread + // isn't carried over from AppleWin + pSSC->CheckCommEvent(0); + switch (uAddr & 0xf) { case 0x0: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); @@ -632,24 +652,45 @@ unsigned char CSuperSerialCard::CommControl(unsigned short, unsigned short, unsi return m_uControlByte; } +static const char *descbyte(uint8_t byte) { + char tmp[8] = { '\'', (char)byte, '\'', ' ', 0 }; + const char *bstr = tmp; + if (!isprint(byte)) { + switch(byte) { + case 13: bstr = " "; break; + case 10: bstr = " "; break; + case '\t': bstr = "<\\t> "; break; + case '\b': bstr = "<\\b> "; break; + default: + bstr = ""; + } + } + + static char retstr[32]; + sprintf(retstr, "%s(%d, 0x%02x)", bstr, byte, byte); + return retstr; +} + unsigned char CSuperSerialCard::CommReceive(unsigned short, unsigned short, unsigned char, unsigned char, ULONG) { if (!CheckComm()) { return 0; } - unsigned char result = 0; - if (m_vRecvBytes) { - // Don't need critical section in here as CommThread is waiting for ACK - result = m_RecvBuffer[0]; - --m_vRecvBytes; - - if (m_vbCommIRQ && !m_vRecvBytes) { - // Read last byte, so get CommThread to call WaitCommEvent() again - fprintf(stderr, "CommRecv: SetEvent - ACK\n"); - } - } - - return result; + int rdchar = rxbuf->ReadByte(); + if (rdchar >= 0) { + if (m_vbCommIRQ && rxbuf->IsEmpty()) { + // read last byte, so get CommThread to call WaitCommEvent() again + fprintf(stderr, "CommRecv: SetEvent - ACK\n"); + //SetEvent(m_hCommEvent[COMMEVT_ACK]); + } + + printf("\e[1;94m <-- A2 reads incoming byte %s\e[0m\n", descbyte(rdchar)); + return (BYTE)rdchar; + } + else { + printf("Apple II attempted to read serial w/ empty buffer\n"); + return 0; + } } unsigned char CSuperSerialCard::CommTransmit(unsigned short, unsigned short, unsigned char, unsigned char value, ULONG) { @@ -718,13 +759,13 @@ unsigned char CSuperSerialCard::CommStatus(unsigned short, unsigned short, unsig if (m_bTxIrqEnabled && m_bWrittenTx) { bIRQ = true; } - if (m_bRxIrqEnabled && m_vRecvBytes) { + if (m_bRxIrqEnabled && rxbuf->IsEmpty()) { bIRQ = true; } m_bWrittenTx = false; // Read status reg always clears IRQ - unsigned char uStatus = ST_TX_EMPTY | (m_vRecvBytes ? ST_RX_FULL : 0x00) + unsigned char uStatus = ST_TX_EMPTY | (!rxbuf->IsEmpty() ? ST_RX_FULL : 0x00) #ifdef SUPPORT_MODEM | ((modemstatus & MS_RLSD_ON) ? 0x00 : ST_DCD) // Need 0x00 to allow ZLink to start up | ((modemstatus & MS_DSR_ON) ? 0x00 : ST_DSR) @@ -805,7 +846,7 @@ void CSuperSerialCard::CommInitialize(LPBYTE pCxRomPeripheral, unsigned int uSlo void CSuperSerialCard::CommReset() { CloseComm(); GetDIPSW(); - m_vRecvBytes = 0; + rxbuf->Clear(); m_bTxIrqEnabled = false; m_bRxIrqEnabled = false; m_bWrittenTx = false; @@ -849,14 +890,27 @@ void CSuperSerialCard::CommUpdate(unsigned int totalcycles) { } void CSuperSerialCard::CheckCommEvent(unsigned int dwEvtMask) { - pthread_mutex_lock(&m_CriticalSection); - m_vRecvBytes = read(m_hCommHandle, m_RecvBuffer, 1); - pthread_mutex_unlock(&m_CriticalSection); + int maxBytes = rxbuf->BytesFree(); + if (maxBytes > 0) { + uint8_t temp[maxBytes]; + int nbytes = read(m_hCommHandle, temp, maxBytes); + if (nbytes > 0) { + printf("got SZ! %d\n", nbytes); + for(int i=0;iAppend(temp, nbytes); + printf("serial: received %d bytes; now %d bytes in buffer\n", nbytes, rxbuf->BytesAvail()); + pthread_mutex_unlock(&m_CriticalSection); + + if (m_bRxIrqEnabled) { + m_vbCommIRQ = true; + CpuIrqAssert(IS_SSC); + } + } + } } unsigned int CSuperSerialCard::CommThread(LPVOID lpParameter) { @@ -878,9 +932,12 @@ unsigned int CSuperSerialCard::CommGetSnapshot(SS_IO_Comms *pSS) pSS->comminactivity = m_dwCommInactivity; pSS->controlbyte = m_uControlByte; pSS->parity = m_uParity; - memcpy(pSS->recvbuffer, m_RecvBuffer, uRecvBufferSize); - pSS->recvbytes = m_vRecvBytes; pSS->stopbits = m_uStopBits; + + int nbytes = rxbuf->BytesAvail(); + uint8_t *peekbytes = rxbuf->Read(nbytes, false); + memcpy(pSS->recvbuffer, peekbytes, nbytes); + pSS->recvbytes = nbytes; return 0; } @@ -892,8 +949,10 @@ unsigned int CSuperSerialCard::CommSetSnapshot(SS_IO_Comms *pSS) m_dwCommInactivity = pSS->comminactivity; m_uControlByte = pSS->controlbyte; m_uParity = pSS->parity; - memcpy(m_RecvBuffer, pSS->recvbuffer, uRecvBufferSize); - m_vRecvBytes = pSS->recvbytes; m_uStopBits = pSS->stopbits; + + rxbuf->Clear(); + rxbuf->Append(pSS->recvbuffer, pSS->recvbytes); return 0; } + diff --git a/src/dataring.cpp b/src/dataring.cpp new file mode 100644 index 0000000..a92b6b5 --- /dev/null +++ b/src/dataring.cpp @@ -0,0 +1,498 @@ + +#include +#include +#include +#include +#include +#include "dataring.h" + +#define DATARING_DEBUG +//#define DATARING_DEBUG2 // requires libkaty + +#ifdef DATARING_DEBUG + #define debug(...) do { \ + printf(__VA_ARGS__); \ + printf("\n"); \ +} while(0) +#else + #define debug(...) do { } while(0) +#endif + +#define error(...) do { \ + printf(__VA_ARGS__); \ + printf("\n"); \ +} while(0) + +DataRing::DataRing(int ring_initial_size) { + fHead = fTail = 0; + fBufferBase = NULL; + fBuffer = NULL; + fPreBuffer = NULL; + fPostBuffer = NULL; + fRingSize = 0; + + if (ring_initial_size) + SetRingSize(ring_initial_size); +} + +DataRing::~DataRing() { + free(fBufferBase); + fBufferBase = NULL; + fBuffer = NULL; + fPreBuffer = fPostBuffer = NULL; +} + +void DataRing::SetRingSize(int newSize) { + // need one extra byte because we must always leave at least one byte + // unused to tell the difference between an empty and full buffer + newSize++; + + if (newSize != fRingSize || fBuffer == NULL) { + fHead = fTail = 0; + + // we'll allocate space before and after the ring for when we need to + // temporarily reassemble wrapped chunks to make them contiguous. + // worst case amount we'll need to copy is half the size of the ring + // buffer. think of it like a literal circular paper tape that you're + // cutting to extend into a solid line. the most you'll ever need to + // cut is half the circle, because any longer than that and you'd just + // cut the other end. + // + // note: factored out the +/-1's in "(((newSize - 1) + 1) & ~1) / 2" + // (newSize - 1) remove the byte we added earlier for full/empty disambiguation + // +1 & ~1 round up to nearest even integer before division / 2 + // >> 1 will at most need half that much + fReassemblySize = (newSize & ~1) >> 1; + fRingSize = newSize; + fBufTotalSize = fReassemblySize + fRingSize + fReassemblySize; + + // ensure that the main ring buffer ends up word-aligned for speed -- + // usually, malloc would probably do this for us, but it doesn't know + // about our extra reassembly padding, so let's hold it's hand a little + int wordAlignPad = 0; + int ringBufferOffset = fReassemblySize; + if (ringBufferOffset & 3) + wordAlignPad = 4 - (ringBufferOffset & 3); + + int bufAllocSize = fBufTotalSize + wordAlignPad; + + debug("setting fRingSize = %d; fReassemblySize = %d", + fRingSize, fReassemblySize); + debug("fBufTotalSize = %d(%d); wordAlignPad = %d", + fBufTotalSize, bufAllocSize, wordAlignPad); + + free(fBufferBase); + fBufferBase = (uint8_t *)malloc(bufAllocSize); + + fPreBuffer = fBufferBase + wordAlignPad; + fBuffer = fPreBuffer + fReassemblySize; + fPostBuffer = fBuffer + fRingSize; + + memset(fPostBuffer, 0xff, fReassemblySize); + memset(fBuffer, 0xee, fRingSize); + memset(fPreBuffer, 0xff, fReassemblySize); + + // debug markers at boundaries + //#ifndef CONFIG_RELEASE + //fPreBuffer[0] = 0x10; fPreBuffer[fReassemblySize - 1] = 0x19; + //fBuffer[0] = 0x20; fBuffer[fRingSize - 1] = 0x29; + //fPostBuffer[0] = 0x30; fPostBuffer[fReassemblySize - 1] = 0x39; + //#endif + } +} + +void DataRing::Clear() { + fHead = fTail; +} + +/////////////////////////////////////////////////////////////////////////////// + +int DataRing::Append(const void *datain, int dataLength) +{ + const uint8_t *data = (const uint8_t *)datain; + if (dataLength <= 0) { + error("received invalid dataLength %d", dataLength); + return -1; + } + + int bytesFree = BytesFree(); + if (dataLength > bytesFree) { + error("attempt to append %d bytes into ring " + "when only %d %s available", + dataLength, bytesFree, + (bytesFree == 1) ? "is" : "are"); + error("buffer contents looks like:"); + Dump(); + return -1; + } + + // copy bytes into the buffer, wrapping if necessary + // first get how many bytes we can push before we'd reach the end and + // have to wrap + int bytesToEnd = (fRingSize - fTail); + debug("insert %d bytes, bytesToEnd = %d at head %d, tail %d (before insertion)", + dataLength, bytesToEnd, fHead, fTail); + + if (dataLength <= bytesToEnd) + { // normal simple insertion + memcpy(fBuffer + fTail, data, dataLength); + + // even though the data isn't wrapped, the tail can still wrap around + // in some scenarios (e.g. tail = 1, dataLength = ringSize) so we have + // to check. this is because the tail points at the byte AFTER the end; + // which makes sense as (head == tail) means empty/0. + fTail += dataLength; + if (fTail >= fRingSize) fTail -= fRingSize; + } + else + { // the tail is advanced to a point where we need to put some of the + // inserted data at the tail, and the rest should "wrap around" to + // the beginning of the ring buffer + debug("performing wrapped insertion: %d bytes at tail, %d at start", + bytesToEnd, dataLength - bytesToEnd); + + memcpy(fBuffer + fTail, data, bytesToEnd); + memcpy(fBuffer, data + bytesToEnd, dataLength - bytesToEnd); + + // the formula is actually (tail + datalen) % bufferSize, but we + // already KNOW it will wrap. so we can do the clamp unconditionally. + fTail = (fTail + dataLength) - fRingSize; + } + + debug("added %d bytes; head=%d/tail=%d; now holding %d bytes", + dataLength, fHead, fTail, BytesAvail()); + + return 0; +} + +int DataRing::Prepend(const void *datain, int dataLength) { + const uint8_t *data = (const uint8_t *)datain; + + int bytesFree = BytesFree(); + if (dataLength > bytesFree) { + error("attempt to prepend %d bytes into ring " + "when only %d %s available", + dataLength, bytesFree, + (bytesFree == 1) ? "is" : "are"); + return -1; + } + + debug("Prepending %d bytes", dataLength); + + fHead -= dataLength; + if (fHead >= 0) + { // easy case, so just copy it in now that we've "made room" + memcpy(fBuffer + fHead, data, dataLength); + } + else + { + fHead += fRingSize; // clamp back in range + + // wrapped case. so we need to copy some bytes to wherever + // the NEW head is, and then the rest to the beginning. this actually + // works out just like the Append case, except for using the head + // instead of tail and the fact that we moved the head first + int bytesToEnd = (fRingSize - fHead); + debug("got bytesToEnd %d", bytesToEnd); + memcpy(fBuffer + fHead, data, bytesToEnd); + memcpy(fBuffer, data + bytesToEnd, dataLength - bytesToEnd); + } + + return 0; +} + +// moves the head/tail pointers as they should be after a Read(). +// normally done for you but broken out so you can do this separately if you +// are wanting to "peek" at the data to see if you want it before definitely +// removing it. +void DataRing::Commit(int readLen) +{ + // remove the requested # of bytes from the buffer + fHead += readLen; + if (fHead >= fRingSize) fHead -= fRingSize; + + // if the buffer is now empty, we might as well reset the head/tail; + // with a big enough buffer this should it make it way less common + // that we need to wrap under normal conditions + if (fHead == fTail) + fHead = fTail = 0; +} + +// see Read(). +// allows to drop the bounds checking when doing ReadChunk(), which will +// have already tested that there's enough bytes waiting. +uint8_t *DataRing::ReadDirect(int readLen, bool commit) +{ + debug("ReadDirect: request for %d bytes", readLen); + + // get the number of bytes from the head to the end of buffer. + // this would be ((fRingSize - 1) - fHead) + 1, but we can just + // factor out the ones. + int bytesToEnd = (fRingSize - fHead); + uint8_t *headptr = fBuffer + fHead; + //trace("bytes from head to end of buffer = %d", bytesToEnd); + + // move the head pointer + if (commit) + Commit(readLen); + + // simple most-common case: is the requested data already contigous, + // as it doesn't cross the edges of the "ring"? if so, we can just give + // them a pointer directly to the correct offset in our buffer + if (readLen <= bytesToEnd) { + debug("common case"); + return headptr; + } + + // TODO: if the previous request was a peek at or a subset of this request + // and nothing's been added since, we could re-use the already-copied + // portion. this usage pattern does occur for example in PacketDecoder. + + // else the requested # of bytes are "wrapped" around the end of the buffer; + // we'll need to reassemble it into a contiguous whole for the return. + debug("reassembling; piece sizes %d and %d", bytesToEnd, readLen - bytesToEnd); + + // there's two ways we can assemble the requested number of bytes to a + // contiguous buffer: we can either copy the part at the beginning of the + // ring to the pad space past the end, or copy the part near the end to + // before the beginning. determine which one makes more sense. + #define preWrapCopySize bytesToEnd + int postWrapCopySize = (readLen - bytesToEnd); + + if (postWrapCopySize < preWrapCopySize) + { + #define buffer_end (fPreBuffer + fBufTotalSize) + assert(fPostBuffer + (postWrapCopySize - 1) < buffer_end); + + // copy bytes from the beginning to the end, then return the head + memcpy(fPostBuffer, fBuffer, postWrapCopySize); + return headptr; + } + else + { + // copy bytes from the head to before the beginning + uint8_t *preCopyPtr = (fBuffer - preWrapCopySize); + assert(preCopyPtr >= fPreBuffer); + + memcpy(preCopyPtr, headptr, preWrapCopySize); + return preCopyPtr; + } + +#undef preWrapCopySize +#undef buffer_lastbyte +} + +// read [readLen] bytes and remove them from the buffer, returning a pointer +// to them (which will be contiguous even if they were stored wrapped). +// NOTE: you should have already checked that there are enough bytes available. +uint8_t *DataRing::Read(int readLen, bool commit) +{ + assert(readLen >= 0); + assert(readLen <= BytesAvail()); + return ReadDirect(readLen, commit); +} + +// convenience function for reading fixed-sized structures. +// if there is at least [chunk_size] bytes waiting, read it and return +// a pointer to it. else return NULL and do nothing. +uint8_t *DataRing::ReadChunk(int chunk_size, bool commit) { + if (BytesAvail() >= chunk_size) + return ReadDirect(chunk_size, commit); + else + return NULL; +} + +int DataRing::ReadByte() { + if (fHead == fTail) + return -1; + + uint8_t byte = fBuffer[fHead]; + fHead = (fHead + 1) % fRingSize; + return byte; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool DataRing::IsFull() { + // when full, the head pointer will be just after the tail (i.e. reversed), + // as the buffer will necessarily be fully wrapped in order to be full + //return ((fTail + 1) % fRingSize) == fHead; + if (fHead == 0) + return fTail == (fRingSize - 1); + else + return fHead == (fTail + 1); +} + +/////////////////////////////////////////////////////////////////////////////// + +int DataRing::isByteInBuffer(int offs) { + if (offs >= fRingSize) return false; + if (offs < 0) return false; + + //if (offs >= fHead) + //return 1; + + if (fTail >= fHead) { + // "simple" case without wrap + return (offs >= fHead && offs < fTail) ? 1 : 0; + } + else { + // wrapped + if (offs >= fHead) return 2; + if (offs < fTail) return 3; + return 0; + } +} + +#ifdef DATARING_DEBUG2 +void DataRing::Dump() { + //#define HEADCOL "\e[1;48;2;238;36;49m" + //#define TAILCOL "\e[1;48;2;80;80;238m" + #define HEADCOL "\e[1;48;2;138;16;39m" + #define TAILCOL "\e[1;48;2;70;70;198m" + #define ACTIVECOLFG "\e[3;4m" + #define ACTIVECOLBG_NOWRAP "\e[1;48;2;23;77;123m" + #define ACTIVECOLBG_WRAP1 "\e[1;48;2;132;58;179m" + #define ACTIVECOLBG_WRAP2 "\e[1;48;2;23;123;77m" + #define NORM "\e[0m" + + static const char *bib_to_col_map[] = { + NULL, ACTIVECOLBG_NOWRAP, ACTIVECOLBG_WRAP1, ACTIVECOLBG_WRAP2 + }; + + DString top(stprintf("── DataRing %p ────────────────────────────", this)); + stat("%s", top.String()); + + DString line; + line.Append(stprintf(" HheadN %d", fHead)); + while(line.Length() < 12) line.Append(' '); + line.Append("TtailN "); + int l1len = line.Length() - 4; + line.Append(stprintf("%d", fTail)); + + #define C 22 + while(line.Length() - 4 < C) line.Append(' '); + + line.Append(stprintf("bytesInBuffer %d", BytesAvail())); + if (IsFull()) { + #define FEC 42 + while(line.Length() - 4 < FEC) line.Append(' '); + line.Append("\e[1;91mFULL\e[0m"); + } + + line.ReplaceString("H", HEADCOL); + line.ReplaceString("T", TAILCOL); + line.ReplaceString("N", NORM); + + stat("%s", line.String()); + + line.Clear(); + line.Append(" bufferSize"); + int M = l1len - 1; + while(line.Length() < M) line.Append(' '); + line.Append(stprintf(" %d", fRingSize)); + while(line.Length() < C) line.Append(' '); + line.Append(stprintf("bytesFree %d", BytesFree())); + if (IsEmpty()) { + while(line.Length() < FEC) line.Append(' '); + line.Append("\e[1;92mEMPTY\e[0m"); + } + stat("%s", line.String()); + + //htstack.Dump(); + + // ---------------- + DString dump; + int linelen = 0; + + #define PRINT_ADDR(A) do { dump.Append(stprintf("%s%04x ", (A<0)?"-":" ", abs(A))); } while(0) + #define DUMP_BUFFER() do { \ + if (linelen) { \ + dump.Append(line); \ + dump.Append('\n'); \ + } \ + line.Clear(); \ + linelen = 0; \ + } while(0) + + line.Clear(); + + #define SHOW_REASSEMBLY + #ifdef SHOW_REASSEMBLY + //error("%d", fReassemblySize);exit(1); + uint8_t *ptr = fPreBuffer; + uint8_t *lastbyte = fPreBuffer + (fBufTotalSize - 1); + #else + uint8_t *ptr = fBuffer; + uint8_t *lastbyte = fBuffer + (fRingSize - 1); + #endif + + int lineCounter = 0; + const char *color = NULL; + const char *nextSpace = " "; + while(ptr <= lastbyte) + { + bool inNormalRing = (ptr >= fBuffer && ptr < fPostBuffer); + int offs = (ptr - fBuffer); + + if (lineCounter == 0) { + DUMP_BUFFER(); + PRINT_ADDR(offs); + } + if (++lineCounter >= 16) + lineCounter = 0; + + + char num[16]; + //sprintf(num, "%02x", abs(ptr - fBuffer)); + sprintf(num, "%02x", *ptr); + + #define GREY "\e[1;38;2;120;40;40m" + if (offs == fHead) nextSpace = GREY "{" NORM; + line.Append((fHead == fTail) ? " " : nextSpace); nextSpace = " "; + if (offs == fHead) line.Append(color = HEADCOL); + else if (offs == fTail) line.Append(color = TAILCOL); + + int inbuftype; + if ((inbuftype = isByteInBuffer(offs))) { + line.Append(ACTIVECOLFG); + if (!color) { + color = bib_to_col_map[inbuftype]; + if (color) line.Append(color); + } + } + + if (!inNormalRing) + line.Append(color = GREY); + + line.Append(num[0]); + if (offs == fTail) line.Append(color = TAILCOL); + + if (inNormalRing) { + if ((offs + 1) % fRingSize == fTail) + nextSpace = GREY "}" NORM; + } + + line.Append(num[1]); + if (color) { color = NULL; line.Append(NORM); } + linelen += 3; + + ptr++; + offs++; + } + + if (linelen) + DUMP_BUFFER(); + + DString btm("───────────────────────────────────────────────────────"); + stat("%s%s", dump.String(), btm.String()); +} +#else // !DATARING_DEBUG + +void DataRing::Dump() { + printf("DataRing: head=%d tail=%d free=%d avail=%d\n", + fHead, fTail, BytesFree(), BytesAvail()); +} + +#endif + diff --git a/src/dataring.h b/src/dataring.h new file mode 100644 index 0000000..84f2118 --- /dev/null +++ b/src/dataring.h @@ -0,0 +1,139 @@ + +#ifndef __RINPUT_DATARING_H +#define __RINPUT_DATARING_H + +#include +#include + +// ring buffer implementation, with ability to have a "min chunk size" when +// reading. this can also be used as a normal ring buffer by just always +// asking to read no more than "BytesAvail()". +// +// this object is designed to help out once and for all with a few common +// patterns that often can be awkward with POSIX read() operations and +// breaking any type of "stream" data into blocks such as for packet decoding +// on a TCP link. It implements a ring buffer of a configurable size, into +// which you can dump arbitrary amounts of data (such as returned by a read() +// call with a large buffer. You can then, without need to call into the +// kernel again, perform operations such as: +// +// - take data out of the buffer in particular size chunks, or only if there +// enough data to make an entire chunk +// +// - determine very quickly how much data is in the buffer +// +// - peek at data and then "put it back" if you don't want it right now +// +constexpr int DATARING_DEFAULT_SIZE = 2048; + +class DataRing +{ +public: + DataRing(int ring_initial_size = DATARING_DEFAULT_SIZE); + ~DataRing(); + + void SetRingSize(int new_size); + + // push/copy data into the ring. returns < 0 on error. + int Append(const void *data, int len); + + // push data at the beginning of the ring. returns < 0 on error. + int Prepend(const void *data, int len); + + // read a single byte from the buffer in FIFO order. + // returns -1 if called while the buffer is empty. + int ReadByte(); + + // read the given number of bytes and return a pointer to them, + // "unwrapping" the bytes if necessary (i.e. if the start and end were on + // opposite sides of the ring). to avoid copying, the pointer is located + // within the object's own ring buffer, so the data should be processed or + // copied before another read or insertion. + // + // if "commit" is false, then the head/tail pointers are not updated, so + // the data is not actually removed from the buffer. this can be used in + // combination with the Commit() function to implement a "peek". + uint8_t *Read(int len, bool commit = true); + + // remove the given number of bytes from the start of the buffer, + // "committing" a prior "peek" (a Read() with commit=false). You must call + // this before doing anything else between the Read and the Commit, and + // the "len" parameter must be equal to what was passed to Read, or + // bad things will happen. + void Commit(int len); + + // if there is at least [minSize] bytes available in the ring, read exactly + // that many bytes (and no more), and return a pointer to the start of it. + // if there is not at least chunk_size bytes yet, returns NULL. + uint8_t *ReadChunk(int minSize, bool commit = true); + + const char *ReadLine(); + + int BytesAvail(); // max number of bytes you can currently read + int BytesFree(); // max number of bytes you can currently insert + + bool IsFull(); + inline bool IsEmpty() { + return (fHead == fTail); + } + + void Dump(); + void Clear(); + +private: + uint8_t *ReadDirect(int len, bool commit); + int isByteInBuffer(int offs); + +private: + int fHead, fTail; + + int fRingSize; // size of the main ring + int fReassemblySize; // how much pad we have before and after the ring for doing reassembly + int fBufTotalSize; // pre-buffer + ring + post-buffer = (fRingSize + (fReassemblySize * 2)) + + uint8_t *fBufferBase; // actual start of the malloc(), including any word-align padding + uint8_t *fBuffer; // start of the ring (embedded between the pre and post sections) + uint8_t *fPreBuffer; // actual start of the buffer for reassembly + uint8_t *fPostBuffer; // pointer past end of buffer for reassembly + + #ifdef CONFIG_DATARING_TESTS + friend void dataring_test(); + friend bool dataring_stresstest(); + #endif +}; + +// return the number of bytes currently stored in the ring buffer +inline int DataRing::BytesAvail() +{ + if (fTail >= fHead) + return fTail - fHead; // "normal" + else + return (fRingSize - fHead) + fTail; // "wrapped" +} + +// return the maximum number of bytes that could currently be added to the +// buffer without overflowing +inline int DataRing::BytesFree() { + return (fRingSize - BytesAvail()) - 1; +} + +// simple wrapper template which saves you from needing to cast +// (use this when storing structs/packets in the ring). +template +class DataRingOf : public DataRing { +public: + DataRingOf(int ring_initial_size = DATARING_DEFAULT_SIZE) + : DataRing(ring_initial_size) + { } + + T *Read(int len) { return (T*)DataRing::Read(len); } + T *ReadChunk(int minSize, bool commit) { return (T *)DataRing::ReadChunk(minSize, commit); } + //T *PeekChunk(int minSize) { return (T *)DataRing::PeekChunk(minSize); } + + T *ReadChunk(bool commit) { + return ReadChunk(sizeof(T), commit); + } +}; + +#endif +