From 4861d72642160712a55c35bd9561afe8fa34f658 Mon Sep 17 00:00:00 2001 From: Nicholas Humfrey Date: Sun, 19 Aug 2018 10:38:56 +0100 Subject: [PATCH] Moved Stash and BufferFiller classes into their own files (#329) --- src/EtherCard.cpp | 383 +------------------------------------------ src/EtherCard.h | 175 +------------------- src/bufferfiller.cpp | 73 +++++++++ src/bufferfiller.h | 99 +++++++++++ src/stash.cpp | 300 +++++++++++++++++++++++++++++++++ src/stash.h | 84 ++++++++++ 6 files changed, 573 insertions(+), 541 deletions(-) create mode 100644 src/bufferfiller.cpp create mode 100644 src/bufferfiller.h create mode 100644 src/stash.cpp create mode 100644 src/stash.h diff --git a/src/EtherCard.cpp b/src/EtherCard.cpp index 02dd5a3..f752a9b 100644 --- a/src/EtherCard.cpp +++ b/src/EtherCard.cpp @@ -13,380 +13,6 @@ #include #include -#define WRITEBUF 0 -#define READBUF 1 -#define BUFCOUNT 2 - -//#define FLOATEMIT // uncomment line to enable $T in emit_P for float emitting - -byte Stash::map[SCRATCH_MAP_SIZE]; -Stash::Block Stash::bufs[BUFCOUNT]; - -uint8_t Stash::allocBlock () { - for (uint8_t i = 0; i < sizeof map; ++i) - if (map[i] != 0) - for (uint8_t j = 0; j < 8; ++j) - if (bitRead(map[i], j)) { - bitClear(map[i], j); - return (i << 3) + j; - } - return 0; -} - -void Stash::freeBlock (uint8_t block) { - bitSet(map[block>>3], block & 7); -} - -uint8_t Stash::fetchByte (uint8_t blk, uint8_t off) { - return blk == bufs[WRITEBUF].bnum ? bufs[WRITEBUF].bytes[off] : - blk == bufs[READBUF].bnum ? bufs[READBUF].bytes[off] : - ether.peekin(blk, off); -} - - -// block 0 is special since always occupied -void Stash::initMap (uint8_t last /*=SCRATCH_PAGE_NUM*/) { - last = SCRATCH_PAGE_NUM; - while (--last > 0) - freeBlock(last); -} - -// load a page/block either into the write or into the readbuffer -void Stash::load (uint8_t idx, uint8_t blk) { - if (blk != bufs[idx].bnum) { - if (idx == WRITEBUF) { - ether.copyout(bufs[idx].bnum, bufs[idx].bytes); - if (blk == bufs[READBUF].bnum) - bufs[READBUF].bnum = 255; // forget read page if same - } else if (blk == bufs[WRITEBUF].bnum) { - // special case: read page is same as write buffer - memcpy(&bufs[READBUF], &bufs[WRITEBUF], sizeof bufs[0]); - return; - } - bufs[idx].bnum = blk; - ether.copyin(bufs[idx].bnum, bufs[idx].bytes); - } -} - -uint8_t Stash::freeCount () { - uint8_t count = 0; - for (uint8_t i = 0; i < sizeof map; ++i) - for (uint8_t m = 0x80; m != 0; m >>= 1) - if (map[i] & m) - ++count; - return count; -} - -// create a new stash; make it the active stash; return the first block as a handle -uint8_t Stash::create () { - uint8_t blk = allocBlock(); - load(WRITEBUF, blk); - bufs[WRITEBUF].head.count = 0; - bufs[WRITEBUF].head.first = bufs[0].head.last = blk; - bufs[WRITEBUF].tail = sizeof (StashHeader); - bufs[WRITEBUF].next = 0; - return open(blk); // you are now the active stash -} - -// the stashheader part only contains reasonable data if we are the first block -uint8_t Stash::open (uint8_t blk) { - curr = blk; - offs = sizeof (StashHeader); // goto first byte - load(READBUF, curr); - memcpy((StashHeader*) this, bufs[READBUF].bytes, sizeof (StashHeader)); - return curr; -} - -// save the metadata of current block into the first block -void Stash::save () { - load(WRITEBUF, first); - memcpy(bufs[WRITEBUF].bytes, (StashHeader*) this, sizeof (StashHeader)); - if (bufs[READBUF].bnum == first) - load(READBUF, 0); // invalidates original in case it was the same block -} - -// follow the linked list of blocks and free every block -void Stash::release () { - while (first > 0) { - freeBlock(first); - first = ether.peekin(first, 63); - } -} - -void Stash::put (char c) { - load(WRITEBUF, last); - uint8_t t = bufs[WRITEBUF].tail; - bufs[WRITEBUF].bytes[t++] = c; - if (t <= 62) - bufs[WRITEBUF].tail = t; - else { - bufs[WRITEBUF].next = allocBlock(); - last = bufs[WRITEBUF].next; - load(WRITEBUF, last); - bufs[WRITEBUF].tail = bufs[WRITEBUF].next = 0; - ++count; - } -} - -char Stash::get () { - load(READBUF, curr); - if (curr == last && offs >= bufs[READBUF].tail) - return 0; - uint8_t b = bufs[READBUF].bytes[offs]; - if (++offs >= 63 && curr != last) { - curr = bufs[READBUF].next; - offs = 0; - } - return b; -} - -// fetchbyte(last, 62) is tail, i.e., number of characters in last block -uint16_t Stash::size () { - return 63 * count + fetchByte(last, 62) - sizeof (StashHeader); -} - -static char* wtoa (uint16_t value, char* ptr) { - if (value > 9) - ptr = wtoa(value / 10, ptr); - *ptr = '0' + value % 10; - *++ptr = 0; - return ptr; -} - -// write information about the fmt string and the arguments into special page/block 0 -// block 0 is initially marked as allocated and never returned by allocateBlock -void Stash::prepare (const char* fmt PROGMEM, ...) { - Stash::load(WRITEBUF, 0); - uint16_t* segs = Stash::bufs[WRITEBUF].words; - *segs++ = strlen_P(fmt); -#ifdef __AVR__ - *segs++ = (uint16_t) fmt; -#else - *segs++ = (uint32_t) fmt; - *segs++ = (uint32_t) fmt >> 16; -#endif - va_list ap; - va_start(ap, fmt); - for (;;) { - char c = pgm_read_byte(fmt++); - if (c == 0) - break; - if (c == '$') { -#ifdef __AVR__ - uint16_t argval = va_arg(ap, uint16_t), arglen = 0; -#else - uint32_t argval = va_arg(ap, int), arglen = 0; -#endif - switch (pgm_read_byte(fmt++)) { - case 'D': { - char buf[7]; - wtoa(argval, buf); - arglen = strlen(buf); - break; - } - case 'S': - arglen = strlen((const char*) argval); - break; - case 'F': - arglen = strlen_P((const char*) argval); - break; - case 'E': { - byte* s = (byte*) argval; - char d; - while ((d = eeprom_read_byte(s++)) != 0) - ++arglen; - break; - } - case 'H': { - Stash stash (argval); - arglen = stash.size(); - break; - } - } -#ifdef __AVR__ - *segs++ = argval; -#else - *segs++ = argval; - *segs++ = argval >> 16; -#endif - Stash::bufs[WRITEBUF].words[0] += arglen - 2; - } - } - va_end(ap); -} - -uint16_t Stash::length () { - Stash::load(WRITEBUF, 0); - return Stash::bufs[WRITEBUF].words[0]; -} - -void Stash::extract (uint16_t offset, uint16_t count, void* buf) { - Stash::load(WRITEBUF, 0); - uint16_t* segs = Stash::bufs[WRITEBUF].words; -#ifdef __AVR__ - const char* fmt PROGMEM = (const char*) *++segs; -#else - const char* fmt PROGMEM = (const char*)((segs[2] << 16) | segs[1]); - segs += 2; -#endif - Stash stash; - char mode = '@', tmp[7], *ptr = NULL, *out = (char*) buf; - for (uint16_t i = 0; i < offset + count; ) { - char c = 0; - switch (mode) { - case '@': { - c = pgm_read_byte(fmt++); - if (c == 0) - return; - if (c != '$') - break; -#ifdef __AVR__ - uint16_t arg = *++segs; -#else - uint32_t arg = *++segs; - arg |= *++segs << 16; -#endif - mode = pgm_read_byte(fmt++); - switch (mode) { - case 'D': - wtoa(arg, tmp); - ptr = tmp; - break; - case 'S': - case 'F': - case 'E': - ptr = (char*) arg; - break; - case 'H': - stash.open(arg); - ptr = (char*) &stash; - break; - } - continue; - } - case 'D': - case 'S': - c = *ptr++; - break; - case 'F': - c = pgm_read_byte(ptr++); - break; - case 'E': - c = eeprom_read_byte((byte*) ptr++); - break; - case 'H': - c = ((Stash*) ptr)->get(); - break; - } - if (c == 0) { - mode = '@'; - continue; - } - if (i >= offset) - *out++ = c; - ++i; - } -} - -void Stash::cleanup () { - Stash::load(WRITEBUF, 0); - uint16_t* segs = Stash::bufs[WRITEBUF].words; -#ifdef __AVR__ - const char* fmt PROGMEM = (const char*) *++segs; -#else - const char* fmt PROGMEM = (const char*)((segs[2] << 16) | segs[1]); - segs += 2; -#endif - for (;;) { - char c = pgm_read_byte(fmt++); - if (c == 0) - break; - if (c == '$') { -#ifdef __AVR__ - uint16_t arg = *++segs; -#else - uint32_t arg = *++segs; - arg |= *++segs << 16; -#endif - if (pgm_read_byte(fmt++) == 'H') { - Stash stash (arg); - stash.release(); - } - } - } -} - -void BufferFiller::emit_p(const char* fmt PROGMEM, ...) { - va_list ap; - va_start(ap, fmt); - for (;;) { - char c = pgm_read_byte(fmt++); - if (c == 0) - break; - if (c != '$') { - *ptr++ = c; - continue; - } - c = pgm_read_byte(fmt++); - switch (c) { - case 'D': -#ifdef __AVR__ - wtoa(va_arg(ap, uint16_t), (char*) ptr); -#else - wtoa(va_arg(ap, int), (char*) ptr); -#endif - break; -#ifdef FLOATEMIT - case 'T': - dtostrf ( va_arg(ap, double), 10, 3, (char*)ptr ); - break; -#endif - case 'H': { -#ifdef __AVR__ - char p1 = va_arg(ap, uint16_t); -#else - char p1 = va_arg(ap, int); -#endif - char p2; - p2 = (p1 >> 4) & 0x0F; - p1 = p1 & 0x0F; - if (p1 > 9) p1 += 0x07; // adjust 0x0a-0x0f to come out 'a'-'f' - p1 += 0x30; // and complete - if (p2 > 9) p2 += 0x07; // adjust 0x0a-0x0f to come out 'a'-'f' - p2 += 0x30; // and complete - *ptr++ = p2; - *ptr++ = p1; - continue; - } - case 'L': - ltoa(va_arg(ap, long), (char*) ptr, 10); - break; - case 'S': - strcpy((char*) ptr, va_arg(ap, const char*)); - break; - case 'F': { - const char* s PROGMEM = va_arg(ap, const char*); - char d; - while ((d = pgm_read_byte(s++)) != 0) - *ptr++ = d; - continue; - } - case 'E': { - byte* s = va_arg(ap, byte*); - char d; - while ((d = eeprom_read_byte(s++)) != 0) - *ptr++ = d; - continue; - } - default: - *ptr++ = c; - continue; - } - ptr += strlen((char*) ptr); - } - va_end(ap); -} - EtherCard ether; uint8_t EtherCard::mymac[ETH_LEN]; // my MAC address @@ -431,3 +57,12 @@ bool EtherCard::staticSetup (const uint8_t* my_ip, delaycnt = 0; //request gateway ARP lookup return true; } + +char* EtherCard::wtoa(uint16_t value, char* ptr) +{ + if (value > 9) + ptr = wtoa(value / 10, ptr); + *ptr = '0' + value % 10; + *++ptr = 0; + return ptr; +} diff --git a/src/EtherCard.h b/src/EtherCard.h index cc545a2..782c94c 100644 --- a/src/EtherCard.h +++ b/src/EtherCard.h @@ -37,8 +37,10 @@ #endif #include +#include "bufferfiller.h" #include "enc28j60.h" #include "net.h" +#include "stash.h" /** Enable DHCP. * Setting this to zero disables the use of DHCP; if a program uses DHCP it will @@ -99,173 +101,6 @@ typedef void (*DhcpOptionCallback)( uint8_t len); ///< Length of the DHCP option data -/** This structure describes the structure of memory used within the ENC28J60 network interface. */ -typedef struct { - uint8_t count; ///< Number of allocated pages - uint8_t first; ///< First allocated page - uint8_t last; ///< Last allocated page -} StashHeader; - -/** This class provides access to the memory within the ENC28J60 network interface. */ -class Stash : public /*Stream*/ Print, private StashHeader { - uint8_t curr; //!< Current page - uint8_t offs; //!< Current offset in page - - typedef struct { - union { - uint8_t bytes[64]; - uint16_t words[32]; - struct { - StashHeader head; // StashHeader is only stored in first block - uint8_t filler[59]; - uint8_t tail; // only meaningful if bnum==last; number of bytes in last block - uint8_t next; // pointer to next block - }; - }; - uint8_t bnum; - } Block; - - static uint8_t allocBlock (); - static void freeBlock (uint8_t block); - static uint8_t fetchByte (uint8_t blk, uint8_t off); - - static Block bufs[2]; - static uint8_t map[SCRATCH_MAP_SIZE]; - -public: - static void initMap (uint8_t last=SCRATCH_PAGE_NUM); - static void load (uint8_t idx, uint8_t blk); - static uint8_t freeCount (); - - Stash () : curr (0) { first = 0; } - Stash (uint8_t fd) { open(fd); } - - uint8_t create (); - uint8_t open (uint8_t blk); - void save (); - void release (); - - void put (char c); - char get (); - uint16_t size (); - - virtual WRITE_RESULT write(uint8_t b) { put(b); WRITE_RETURN } - - // virtual int available() { - // if (curr != last) - // return 1; - // load(1, last); - // return offs < bufs[1].tail; - // } - // virtual int read() { - // return available() ? get() : -1; - // } - // virtual int peek() { - // return available() ? bufs[1].bytes[offs] : -1; - // } - // virtual void flush() { - // curr = last; - // offs = 63; - // } - - static void prepare (const char* fmt PROGMEM, ...); - static uint16_t length (); - static void extract (uint16_t offset, uint16_t count, void* buf); - static void cleanup (); - - friend void dumpBlock (const char* msg, uint8_t idx); // optional - friend void dumpStash (const char* msg, void* ptr); // optional -}; - -/** This class populates network send and receive buffers. -* -* This class provides formatted printing into memory. Users can use it to write into send buffers. -* -* Nota: PGM_P: is a pointer to a string in program space (defined in the source code, updated to PROGMEM) -* -* # Format string -* -* | Format | Parameter | Output -* |--------|-------------|---------- -* | $D | uint16_t | Decimal representation -* | $T ¤ | double | Decimal representation with 3 digits after decimal sign ([-]d.ddd) -* | $H | uint16_t | Hexadecimal value of lsb (from 00 to ff) -* | $L | long | Decimal representation -* | $S | const char* | Copy null terminated string from main memory -* | $F | PGM_P | Copy null terminated string from program space -* | $E | byte* | Copy null terminated string from EEPROM space -* | $$ | _none_ | '$' -* -* ¤ _Available only if FLOATEMIT is defined_ -* -* # Examples -* ~~~~~~~~~~~~~{.c} -* uint16_t ddd = 123; -* double ttt = 1.23; -* uint16_t hhh = 0xa4; -* long lll = 123456789; -* char * sss; -* char fff[] PROGMEM = "MyMemory"; -* -* sss[0] = 'G'; -* sss[1] = 'P'; -* sss[2] = 'L'; -* sss[3] = 0; -* buf.emit_p( PSTR("ddd=$D\n"), ddd ); // "ddd=123\n" -* buf.emit_p( PSTR("ttt=$T\n"), ttt ); // "ttt=1.23\n" **TO CHECK** -* buf.emit_p( PSTR("hhh=$H\n"), hhh ); // "hhh=a4\n" -* buf.emit_p( PSTR("lll=$L\n"), lll ); // "lll=123456789\n" -* buf.emit_p( PSTR("sss=$S\n"), sss ); // "sss=GPL\n" -* buf.emit_p( PSTR("fff=$F\n"), fff ); // "fff=MyMemory\n" -* ~~~~~~~~~~~~~ -* -*/ -class BufferFiller : public Print { - uint8_t *start; //!< Pointer to start of buffer - uint8_t *ptr; //!< Pointer to cursor position -public: - /** @brief Empty constructor - */ - BufferFiller () {} - - /** @brief Constructor - * @param buf Pointer to the ethernet data buffer - */ - BufferFiller (uint8_t* buf) : start (buf), ptr (buf) {} - - /** @brief Add formatted text to buffer - * @param fmt Format string (see Class description) - * @param ... parameters for format string - */ - void emit_p (const char* fmt PROGMEM, ...); - - /** @brief Add data to buffer from main memory - * @param s Pointer to data - * @param n Number of characters to copy - */ - void emit_raw (const char* s, uint16_t n) { memcpy(ptr, s, n); ptr += n; } - - /** @brief Add data to buffer from program space string - * @param p Program space string pointer - * @param n Number of characters to copy - */ - void emit_raw_p (const char* p PROGMEM, uint16_t n) { memcpy_P(ptr, p, n); ptr += n; } - - /** @brief Get pointer to start of buffer - * @return uint8_t* Pointer to start of buffer - */ - uint8_t* buffer () const { return start; } - - /** @brief Get cursor position - * @return uint16_t Cursor position - */ - uint16_t position () const { return ptr - start; } - - /** @brief Write one byte to buffer - * @param v Byte to add to buffer - */ - virtual WRITE_RESULT write (uint8_t v) { *ptr++ = v; WRITE_RETURN } -}; /** This class provides the main interface to a ENC28J60 based network interface card and is the class most users will use. * @note All TCP/IP client (outgoing) connections are made from source port in range 2816-3071. Do not use these source ports for other purposes. @@ -630,6 +465,12 @@ class EtherCard : public Ethernet { static void makeNetStr(char *resultstr,uint8_t *bytestr,uint8_t len, char separator,uint8_t base); + /** @brief Convert a 16-bit integer into a string + * @param value The number to convert + * @param ptr The string location to write to + */ + char* wtoa(uint16_t value, char* ptr); + /** @brief Return the sequence number of the current TCP package */ static uint32_t getSequenceNumber(); diff --git a/src/bufferfiller.cpp b/src/bufferfiller.cpp new file mode 100644 index 0000000..1431a26 --- /dev/null +++ b/src/bufferfiller.cpp @@ -0,0 +1,73 @@ +#include "bufferfiller.h" + +void BufferFiller::emit_p(const char* fmt PROGMEM, ...) { + va_list ap; + va_start(ap, fmt); + for (;;) { + char c = pgm_read_byte(fmt++); + if (c == 0) + break; + if (c != '$') { + *ptr++ = c; + continue; + } + c = pgm_read_byte(fmt++); + switch (c) { + case 'D': +#ifdef __AVR__ + ether.wtoa(va_arg(ap, uint16_t), (char*) ptr); +#else + ether.wtoa(va_arg(ap, int), (char*) ptr); +#endif + break; +#ifdef FLOATEMIT + case 'T': + dtostrf ( va_arg(ap, double), 10, 3, (char*)ptr ); + break; +#endif + case 'H': { +#ifdef __AVR__ + char p1 = va_arg(ap, uint16_t); +#else + char p1 = va_arg(ap, int); +#endif + char p2; + p2 = (p1 >> 4) & 0x0F; + p1 = p1 & 0x0F; + if (p1 > 9) p1 += 0x07; // adjust 0x0a-0x0f to come out 'a'-'f' + p1 += 0x30; // and complete + if (p2 > 9) p2 += 0x07; // adjust 0x0a-0x0f to come out 'a'-'f' + p2 += 0x30; // and complete + *ptr++ = p2; + *ptr++ = p1; + continue; + } + case 'L': + ltoa(va_arg(ap, long), (char*) ptr, 10); + break; + case 'S': + strcpy((char*) ptr, va_arg(ap, const char*)); + break; + case 'F': { + const char* s PROGMEM = va_arg(ap, const char*); + char d; + while ((d = pgm_read_byte(s++)) != 0) + *ptr++ = d; + continue; + } + case 'E': { + byte* s = va_arg(ap, byte*); + char d; + while ((d = eeprom_read_byte(s++)) != 0) + *ptr++ = d; + continue; + } + default: + *ptr++ = c; + continue; + } + ptr += strlen((char*) ptr); + } + va_end(ap); +} + diff --git a/src/bufferfiller.h b/src/bufferfiller.h new file mode 100644 index 0000000..beef0e5 --- /dev/null +++ b/src/bufferfiller.h @@ -0,0 +1,99 @@ +/** @file */ + +#ifndef BufferFiller_h +#define BufferFiller_h + +#include "EtherCard.h" + + +/** This class populates network send and receive buffers. +* +* This class provides formatted printing into memory. Users can use it to write into send buffers. +* +* Nota: PGM_P: is a pointer to a string in program space (defined in the source code, updated to PROGMEM) +* +* # Format string +* +* | Format | Parameter | Output +* |--------|-------------|---------- +* | $D | uint16_t | Decimal representation +* | $T ¤ | double | Decimal representation with 3 digits after decimal sign ([-]d.ddd) +* | $H | uint16_t | Hexadecimal value of lsb (from 00 to ff) +* | $L | long | Decimal representation +* | $S | const char* | Copy null terminated string from main memory +* | $F | PGM_P | Copy null terminated string from program space +* | $E | byte* | Copy null terminated string from EEPROM space +* | $$ | _none_ | '$' +* +* ¤ _Available only if FLOATEMIT is defined_ +* +* # Examples +* ~~~~~~~~~~~~~{.c} +* uint16_t ddd = 123; +* double ttt = 1.23; +* uint16_t hhh = 0xa4; +* long lll = 123456789; +* char * sss; +* char fff[] PROGMEM = "MyMemory"; +* +* sss[0] = 'G'; +* sss[1] = 'P'; +* sss[2] = 'L'; +* sss[3] = 0; +* buf.emit_p( PSTR("ddd=$D\n"), ddd ); // "ddd=123\n" +* buf.emit_p( PSTR("ttt=$T\n"), ttt ); // "ttt=1.23\n" **TO CHECK** +* buf.emit_p( PSTR("hhh=$H\n"), hhh ); // "hhh=a4\n" +* buf.emit_p( PSTR("lll=$L\n"), lll ); // "lll=123456789\n" +* buf.emit_p( PSTR("sss=$S\n"), sss ); // "sss=GPL\n" +* buf.emit_p( PSTR("fff=$F\n"), fff ); // "fff=MyMemory\n" +* ~~~~~~~~~~~~~ +* +*/ +class BufferFiller : public Print { + uint8_t *start; //!< Pointer to start of buffer + uint8_t *ptr; //!< Pointer to cursor position +public: + /** @brief Empty constructor + */ + BufferFiller () {} + + /** @brief Constructor + * @param buf Pointer to the ethernet data buffer + */ + BufferFiller (uint8_t* buf) : start (buf), ptr (buf) {} + + /** @brief Add formatted text to buffer + * @param fmt Format string (see Class description) + * @param ... parameters for format string + */ + void emit_p (const char* fmt PROGMEM, ...); + + /** @brief Add data to buffer from main memory + * @param s Pointer to data + * @param n Number of characters to copy + */ + void emit_raw (const char* s, uint16_t n) { memcpy(ptr, s, n); ptr += n; } + + /** @brief Add data to buffer from program space string + * @param p Program space string pointer + * @param n Number of characters to copy + */ + void emit_raw_p (const char* p PROGMEM, uint16_t n) { memcpy_P(ptr, p, n); ptr += n; } + + /** @brief Get pointer to start of buffer + * @return uint8_t* Pointer to start of buffer + */ + uint8_t* buffer () const { return start; } + + /** @brief Get cursor position + * @return uint16_t Cursor position + */ + uint16_t position () const { return ptr - start; } + + /** @brief Write one byte to buffer + * @param v Byte to add to buffer + */ + virtual WRITE_RESULT write (uint8_t v) { *ptr++ = v; WRITE_RETURN } +}; + +#endif diff --git a/src/stash.cpp b/src/stash.cpp new file mode 100644 index 0000000..4ffb9ff --- /dev/null +++ b/src/stash.cpp @@ -0,0 +1,300 @@ +#include +#include + +#include "stash.h" + +#define WRITEBUF 0 +#define READBUF 1 +#define BUFCOUNT 2 + +//#define FLOATEMIT // uncomment line to enable $T in emit_P for float emitting + +byte Stash::map[SCRATCH_MAP_SIZE]; +Stash::Block Stash::bufs[BUFCOUNT]; + +uint8_t Stash::allocBlock () { + for (uint8_t i = 0; i < sizeof map; ++i) + if (map[i] != 0) + for (uint8_t j = 0; j < 8; ++j) + if (bitRead(map[i], j)) { + bitClear(map[i], j); + return (i << 3) + j; + } + return 0; +} + +void Stash::freeBlock (uint8_t block) { + bitSet(map[block>>3], block & 7); +} + +uint8_t Stash::fetchByte (uint8_t blk, uint8_t off) { + return blk == bufs[WRITEBUF].bnum ? bufs[WRITEBUF].bytes[off] : + blk == bufs[READBUF].bnum ? bufs[READBUF].bytes[off] : + ether.peekin(blk, off); +} + + +// block 0 is special since always occupied +void Stash::initMap (uint8_t last /*=SCRATCH_PAGE_NUM*/) { + last = SCRATCH_PAGE_NUM; + while (--last > 0) + freeBlock(last); +} + +// load a page/block either into the write or into the readbuffer +void Stash::load (uint8_t idx, uint8_t blk) { + if (blk != bufs[idx].bnum) { + if (idx == WRITEBUF) { + ether.copyout(bufs[idx].bnum, bufs[idx].bytes); + if (blk == bufs[READBUF].bnum) + bufs[READBUF].bnum = 255; // forget read page if same + } else if (blk == bufs[WRITEBUF].bnum) { + // special case: read page is same as write buffer + memcpy(&bufs[READBUF], &bufs[WRITEBUF], sizeof bufs[0]); + return; + } + bufs[idx].bnum = blk; + ether.copyin(bufs[idx].bnum, bufs[idx].bytes); + } +} + +uint8_t Stash::freeCount () { + uint8_t count = 0; + for (uint8_t i = 0; i < sizeof map; ++i) + for (uint8_t m = 0x80; m != 0; m >>= 1) + if (map[i] & m) + ++count; + return count; +} + +// create a new stash; make it the active stash; return the first block as a handle +uint8_t Stash::create () { + uint8_t blk = allocBlock(); + load(WRITEBUF, blk); + bufs[WRITEBUF].head.count = 0; + bufs[WRITEBUF].head.first = bufs[0].head.last = blk; + bufs[WRITEBUF].tail = sizeof (StashHeader); + bufs[WRITEBUF].next = 0; + return open(blk); // you are now the active stash +} + +// the stashheader part only contains reasonable data if we are the first block +uint8_t Stash::open (uint8_t blk) { + curr = blk; + offs = sizeof (StashHeader); // goto first byte + load(READBUF, curr); + memcpy((StashHeader*) this, bufs[READBUF].bytes, sizeof (StashHeader)); + return curr; +} + +// save the metadata of current block into the first block +void Stash::save () { + load(WRITEBUF, first); + memcpy(bufs[WRITEBUF].bytes, (StashHeader*) this, sizeof (StashHeader)); + if (bufs[READBUF].bnum == first) + load(READBUF, 0); // invalidates original in case it was the same block +} + +// follow the linked list of blocks and free every block +void Stash::release () { + while (first > 0) { + freeBlock(first); + first = ether.peekin(first, 63); + } +} + +void Stash::put (char c) { + load(WRITEBUF, last); + uint8_t t = bufs[WRITEBUF].tail; + bufs[WRITEBUF].bytes[t++] = c; + if (t <= 62) + bufs[WRITEBUF].tail = t; + else { + bufs[WRITEBUF].next = allocBlock(); + last = bufs[WRITEBUF].next; + load(WRITEBUF, last); + bufs[WRITEBUF].tail = bufs[WRITEBUF].next = 0; + ++count; + } +} + +char Stash::get () { + load(READBUF, curr); + if (curr == last && offs >= bufs[READBUF].tail) + return 0; + uint8_t b = bufs[READBUF].bytes[offs]; + if (++offs >= 63 && curr != last) { + curr = bufs[READBUF].next; + offs = 0; + } + return b; +} + +// fetchbyte(last, 62) is tail, i.e., number of characters in last block +uint16_t Stash::size () { + return 63 * count + fetchByte(last, 62) - sizeof (StashHeader); +} + +// write information about the fmt string and the arguments into special page/block 0 +// block 0 is initially marked as allocated and never returned by allocateBlock +void Stash::prepare (const char* fmt PROGMEM, ...) { + Stash::load(WRITEBUF, 0); + uint16_t* segs = Stash::bufs[WRITEBUF].words; + *segs++ = strlen_P(fmt); +#ifdef __AVR__ + *segs++ = (uint16_t) fmt; +#else + *segs++ = (uint32_t) fmt; + *segs++ = (uint32_t) fmt >> 16; +#endif + va_list ap; + va_start(ap, fmt); + for (;;) { + char c = pgm_read_byte(fmt++); + if (c == 0) + break; + if (c == '$') { +#ifdef __AVR__ + uint16_t argval = va_arg(ap, uint16_t), arglen = 0; +#else + uint32_t argval = va_arg(ap, int), arglen = 0; +#endif + switch (pgm_read_byte(fmt++)) { + case 'D': { + char buf[7]; + ether.wtoa(argval, buf); + arglen = strlen(buf); + break; + } + case 'S': + arglen = strlen((const char*) argval); + break; + case 'F': + arglen = strlen_P((const char*) argval); + break; + case 'E': { + byte* s = (byte*) argval; + char d; + while ((d = eeprom_read_byte(s++)) != 0) + ++arglen; + break; + } + case 'H': { + Stash stash (argval); + arglen = stash.size(); + break; + } + } +#ifdef __AVR__ + *segs++ = argval; +#else + *segs++ = argval; + *segs++ = argval >> 16; +#endif + Stash::bufs[WRITEBUF].words[0] += arglen - 2; + } + } + va_end(ap); +} + +uint16_t Stash::length () { + Stash::load(WRITEBUF, 0); + return Stash::bufs[WRITEBUF].words[0]; +} + +void Stash::extract (uint16_t offset, uint16_t count, void* buf) { + Stash::load(WRITEBUF, 0); + uint16_t* segs = Stash::bufs[WRITEBUF].words; +#ifdef __AVR__ + const char* fmt PROGMEM = (const char*) *++segs; +#else + const char* fmt PROGMEM = (const char*)((segs[2] << 16) | segs[1]); + segs += 2; +#endif + Stash stash; + char mode = '@', tmp[7], *ptr = NULL, *out = (char*) buf; + for (uint16_t i = 0; i < offset + count; ) { + char c = 0; + switch (mode) { + case '@': { + c = pgm_read_byte(fmt++); + if (c == 0) + return; + if (c != '$') + break; +#ifdef __AVR__ + uint16_t arg = *++segs; +#else + uint32_t arg = *++segs; + arg |= *++segs << 16; +#endif + mode = pgm_read_byte(fmt++); + switch (mode) { + case 'D': + ether.wtoa(arg, tmp); + ptr = tmp; + break; + case 'S': + case 'F': + case 'E': + ptr = (char*) arg; + break; + case 'H': + stash.open(arg); + ptr = (char*) &stash; + break; + } + continue; + } + case 'D': + case 'S': + c = *ptr++; + break; + case 'F': + c = pgm_read_byte(ptr++); + break; + case 'E': + c = eeprom_read_byte((byte*) ptr++); + break; + case 'H': + c = ((Stash*) ptr)->get(); + break; + } + if (c == 0) { + mode = '@'; + continue; + } + if (i >= offset) + *out++ = c; + ++i; + } +} + +void Stash::cleanup () { + Stash::load(WRITEBUF, 0); + uint16_t* segs = Stash::bufs[WRITEBUF].words; +#ifdef __AVR__ + const char* fmt PROGMEM = (const char*) *++segs; +#else + const char* fmt PROGMEM = (const char*)((segs[2] << 16) | segs[1]); + segs += 2; +#endif + for (;;) { + char c = pgm_read_byte(fmt++); + if (c == 0) + break; + if (c == '$') { +#ifdef __AVR__ + uint16_t arg = *++segs; +#else + uint32_t arg = *++segs; + arg |= *++segs << 16; +#endif + if (pgm_read_byte(fmt++) == 'H') { + Stash stash (arg); + stash.release(); + } + } + } +} + diff --git a/src/stash.h b/src/stash.h new file mode 100644 index 0000000..e751faf --- /dev/null +++ b/src/stash.h @@ -0,0 +1,84 @@ +#ifndef Stash_h +#define Stash_h + +#include "EtherCard.h" + +/** This structure describes the structure of memory used within the ENC28J60 network interface. */ +typedef struct { + uint8_t count; ///< Number of allocated pages + uint8_t first; ///< First allocated page + uint8_t last; ///< Last allocated page +} StashHeader; + +/** This class provides access to the memory within the ENC28J60 network interface. */ +class Stash : public /*Stream*/ Print, private StashHeader { + uint8_t curr; //!< Current page + uint8_t offs; //!< Current offset in page + + typedef struct { + union { + uint8_t bytes[64]; + uint16_t words[32]; + struct { + StashHeader head; // StashHeader is only stored in first block + uint8_t filler[59]; + uint8_t tail; // only meaningful if bnum==last; number of bytes in last block + uint8_t next; // pointer to next block + }; + }; + uint8_t bnum; + } Block; + + static uint8_t allocBlock (); + static void freeBlock (uint8_t block); + static uint8_t fetchByte (uint8_t blk, uint8_t off); + + static Block bufs[2]; + static uint8_t map[SCRATCH_MAP_SIZE]; + +public: + static void initMap (uint8_t last=SCRATCH_PAGE_NUM); + static void load (uint8_t idx, uint8_t blk); + static uint8_t freeCount (); + + Stash () : curr (0) { first = 0; } + Stash (uint8_t fd) { open(fd); } + + uint8_t create (); + uint8_t open (uint8_t blk); + void save (); + void release (); + + void put (char c); + char get (); + uint16_t size (); + + virtual WRITE_RESULT write(uint8_t b) { put(b); WRITE_RETURN } + + // virtual int available() { + // if (curr != last) + // return 1; + // load(1, last); + // return offs < bufs[1].tail; + // } + // virtual int read() { + // return available() ? get() : -1; + // } + // virtual int peek() { + // return available() ? bufs[1].bytes[offs] : -1; + // } + // virtual void flush() { + // curr = last; + // offs = 63; + // } + + static void prepare (const char* fmt PROGMEM, ...); + static uint16_t length (); + static void extract (uint16_t offset, uint16_t count, void* buf); + static void cleanup (); + + friend void dumpBlock (const char* msg, uint8_t idx); // optional + friend void dumpStash (const char* msg, void* ptr); // optional +}; + +#endif