This repository has been archived on 2025-02-01. You can view files and clone it, but cannot push or open issues or pull requests.
reprapfirmware-dc42/OutputMemory.cpp
David Crocker f5c4bdc770 Version 1.09r
Added timeout to output buffers destined for USB
Fixed bugs in thermocouple code
Reallocated thermocouple pin numbers
Made Roland mill and inkjet support conditional and normally disabled
Fixed issue with thermocouple code messing up the timekeeping system,
which was suspected of causing the network to be unreliable
Added support for M577 (thanks chrishamm)
2016-01-16 23:10:36 +00:00

643 lines
14 KiB
C++

/*
* OutputMemory.cpp
*
* Created on: 10 Jan 2016
* Authors: David and Christian
*/
#include "OutputMemory.h"
#include "RepRapFirmware.h"
#include <cstdarg>
/*static*/ OutputBuffer * volatile OutputBuffer::freeOutputBuffers = nullptr; // Messages may also be sent by ISRs,
/*static*/ volatile size_t OutputBuffer::usedOutputBuffers = 0; // so make these volatile.
/*static*/ volatile size_t OutputBuffer::maxUsedOutputBuffers = 0;
//*************************************************************************************************
// OutputBuffer class implementation
void OutputBuffer::Append(OutputBuffer *other)
{
if (other != nullptr)
{
last->next = other;
last = other->last;
for(OutputBuffer *item = Next(); item != other; item = item->Next())
{
item->last = last;
}
}
}
void OutputBuffer::IncreaseReferences(size_t refs)
{
if (refs > 0)
{
for(OutputBuffer *item = this; item != nullptr; item = item->Next())
{
item->references += refs;
item->isReferenced = true;
}
}
}
size_t OutputBuffer::Length() const
{
size_t totalLength = 0;
for(const OutputBuffer *current = this; current != nullptr; current = current->Next())
{
totalLength += current->DataLength();
}
return totalLength;
}
char &OutputBuffer::operator[](size_t index)
{
// Get the right buffer to access
OutputBuffer *itemToIndex = this;
while (index > itemToIndex->DataLength())
{
index -= itemToIndex->DataLength();
itemToIndex = itemToIndex->Next();
}
// Return the char reference
return itemToIndex->data[index];
}
char OutputBuffer::operator[](size_t index) const
{
// Get the right buffer to access
const OutputBuffer *itemToIndex = this;
while (index > itemToIndex->DataLength())
{
index -= itemToIndex->DataLength();
itemToIndex = itemToIndex->Next();
}
// Return the char reference
return itemToIndex->data[index];
}
const char *OutputBuffer::Read(size_t len)
{
size_t offset = bytesRead;
bytesRead += len;
return data + offset;
}
int OutputBuffer::printf(const char *fmt, ...)
{
char formatBuffer[FORMAT_STRING_LENGTH];
va_list vargs;
va_start(vargs, fmt);
int ret = vsnprintf(formatBuffer, ARRAY_SIZE(formatBuffer), fmt, vargs);
va_end(vargs);
copy(formatBuffer);
return ret;
}
int OutputBuffer::vprintf(const char *fmt, va_list vargs)
{
char formatBuffer[FORMAT_STRING_LENGTH];
int res = vsnprintf(formatBuffer, ARRAY_SIZE(formatBuffer), fmt, vargs);
cat(formatBuffer);
return res;
}
int OutputBuffer::catf(const char *fmt, ...)
{
char formatBuffer[FORMAT_STRING_LENGTH];
va_list vargs;
va_start(vargs, fmt);
int ret = vsnprintf(formatBuffer, ARRAY_SIZE(formatBuffer), fmt, vargs);
va_end(vargs);
cat(formatBuffer);
return ret;
}
size_t OutputBuffer::copy(const char c)
{
// Unlink existing entries before starting the copy process
if (next != nullptr)
{
ReleaseAll(next);
next = nullptr;
last = this;
}
// Set the date
data[0] = c;
dataLength = 1;
return 1;
}
size_t OutputBuffer::copy(const char *src)
{
return copy(src, strlen(src));
}
size_t OutputBuffer::copy(const char *src, size_t len)
{
// Unlink existing entries before starting the copy process
if (next != nullptr)
{
ReleaseAll(next);
next = nullptr;
last = this;
}
// Does the whole string fit into this instance?
if (len > OUTPUT_BUFFER_SIZE)
{
// No - copy what we can't write into a new chain
OutputBuffer *currentBuffer;
size_t bytesCopied = OUTPUT_BUFFER_SIZE;
do {
if (!Allocate(currentBuffer, true))
{
// We cannot store the whole string, stop here
break;
}
currentBuffer->references = references;
// Fill up the next instance
const size_t copyLength = min<size_t>(OUTPUT_BUFFER_SIZE, len - bytesCopied);
memcpy(currentBuffer->data, src + bytesCopied, copyLength);
currentBuffer->dataLength = copyLength;
bytesCopied += copyLength;
// Link it to the chain
if (next == nullptr)
{
next = last = currentBuffer;
}
else
{
last->next = currentBuffer;
last = currentBuffer;
}
} while (bytesCopied < len);
// Update references to the last entry for all items
for(OutputBuffer *item = Next(); item != last; item = item->Next())
{
item->last = last;
}
// Then copy the rest into this instance
memcpy(data, src, OUTPUT_BUFFER_SIZE);
dataLength = OUTPUT_BUFFER_SIZE;
return bytesCopied;
}
// Yes, the whole string fits into this instance. No need to allocate a new item
memcpy(data, src, len);
dataLength = len;
return len;
}
size_t OutputBuffer::cat(const char c)
{
// See if we can append a char
if (last->dataLength == OUTPUT_BUFFER_SIZE)
{
// No - allocate a new item and copy the data
OutputBuffer *nextBuffer;
if (!Allocate(nextBuffer, true))
{
// We cannot store any more data. Should never happen
return 0;
}
nextBuffer->references = references;
nextBuffer->copy(c);
// Link the new item to this list
last->next = nextBuffer;
for(OutputBuffer *item = this; item != nextBuffer; item = item->Next())
{
item->last = nextBuffer;
}
}
else
{
// Yes - we have enough space left
last->data[last->dataLength++] = c;
}
return 1;
}
size_t OutputBuffer::cat(const char *src)
{
return cat(src, strlen(src));
}
size_t OutputBuffer::cat(const char *src, size_t len)
{
// Copy what we can into the last buffer
size_t copyLength = min<size_t>(len, OUTPUT_BUFFER_SIZE - last->dataLength);
memcpy(last->data + last->dataLength, src, copyLength);
last->dataLength += copyLength;
// Is there any more data left?
if (len > copyLength)
{
// Yes - copy what we couldn't write into a new chain
OutputBuffer *nextBuffer;
if (!Allocate(nextBuffer, true))
{
// We cannot store any more data, stop here
return copyLength;
}
nextBuffer->references = references;
const size_t bytesCopied = copyLength + nextBuffer->copy(src + copyLength, len - copyLength);
// Done - now append the new entry to the chain
last->next = nextBuffer;
last = nextBuffer->last;
for(OutputBuffer *item = Next(); item != nextBuffer; item = item->Next())
{
item->last = last;
}
return bytesCopied;
}
// No more data had to be written, we could store everything
return len;
}
size_t OutputBuffer::cat(StringRef &str)
{
return cat(str.Pointer(), str.Length());
}
// Encode a string in JSON format and append it to a string buffer and return the number of bytes written
size_t OutputBuffer::EncodeString(const char *src, size_t srcLength, bool allowControlChars, bool encapsulateString)
{
size_t bytesWritten = 0;
if (encapsulateString)
{
bytesWritten += cat('"');
}
size_t srcPointer = 1;
char c = *src++;
while (srcPointer <= srcLength && c != 0 && (c >= ' ' || allowControlChars))
{
char esc;
switch (c)
{
case '\r':
esc = 'r';
break;
case '\n':
esc = 'n';
break;
case '\t':
esc = 't';
break;
case '"':
esc = '"';
break;
case '\\':
esc = '\\';
break;
default:
esc = 0;
break;
}
if (esc != 0)
{
bytesWritten += cat('\\');
bytesWritten += cat(esc);
}
else
{
bytesWritten += cat(c);
}
c = *src++;
srcPointer++;
}
if (encapsulateString)
{
bytesWritten += cat('"');
}
return bytesWritten;
}
size_t OutputBuffer::EncodeReply(OutputBuffer *src, bool allowControlChars)
{
size_t bytesWritten = cat('"');
while (src != nullptr)
{
bytesWritten += EncodeString(src->Data(), src->DataLength(), allowControlChars, false);
src = Release(src);
}
bytesWritten += cat('"');
return bytesWritten;
}
// Initialise the output buffers manager
/*static*/ void OutputBuffer::Init()
{
freeOutputBuffers = nullptr;
for(size_t i = 0; i < OUTPUT_BUFFER_COUNT; i++)
{
freeOutputBuffers = new OutputBuffer(freeOutputBuffers);
}
}
// Allocates an output buffer instance which can be used for (large) string outputs
/*static*/ bool OutputBuffer::Allocate(OutputBuffer *&buf, bool isAppending)
{
const irqflags_t flags = cpu_irq_save();
if (freeOutputBuffers == nullptr)
{
reprap.GetPlatform()->RecordError(ErrorCode::OutputStarvation);
cpu_irq_restore(flags);
buf = nullptr;
return false;
}
else if (isAppending)
{
// It's a good idea to leave at least one OutputBuffer available if we're
// writing a large chunk of data...
if (freeOutputBuffers->next == nullptr)
{
cpu_irq_restore(flags);
buf = nullptr;
return false;
}
}
buf = freeOutputBuffers;
freeOutputBuffers = buf->next;
usedOutputBuffers++;
if (usedOutputBuffers > maxUsedOutputBuffers)
{
maxUsedOutputBuffers = usedOutputBuffers;
}
buf->next = nullptr;
buf->last = buf;
buf->dataLength = buf->bytesRead = 0;
buf->references = 1; // Assume it's only used once by default
buf->isReferenced = false;
cpu_irq_restore(flags);
return true;
}
// Get the number of bytes left for continuous writing
/*static*/ size_t OutputBuffer::GetBytesLeft(const OutputBuffer *writingBuffer)
{
// If writingBuffer is NULL, just return how much space there is left for continuous writing
if (writingBuffer == nullptr)
{
if (usedOutputBuffers == OUTPUT_BUFFER_COUNT)
{
// No more instances can be allocated
return 0;
}
return (OUTPUT_BUFFER_COUNT - usedOutputBuffers - 1) * OUTPUT_BUFFER_SIZE;
}
// Do we have any more buffers left for writing?
if (usedOutputBuffers == OUTPUT_BUFFER_COUNT)
{
// No - refer to this one only
return OUTPUT_BUFFER_SIZE - writingBuffer->last->DataLength();
}
// Yes - we know how many buffers are in use, so there is no need to work through the free list
return (OUTPUT_BUFFER_SIZE - writingBuffer->last->DataLength() + (OUTPUT_BUFFER_COUNT - usedOutputBuffers - 1) * OUTPUT_BUFFER_SIZE);
}
// Truncate an output buffer to free up more memory. Returns the number of released bytes.
/*static */ size_t OutputBuffer::Truncate(OutputBuffer *buffer, size_t bytesNeeded)
{
// Can we free up space from this chain?
if (buffer == nullptr || buffer->Next() == nullptr)
{
// No
return 0;
}
// Yes - free up the last entries
size_t releasedBytes = 0;
OutputBuffer *previousItem;
do {
// Get two the last entries from the chain
previousItem = buffer;
OutputBuffer *lastItem = previousItem->Next();
while (lastItem->Next() != nullptr)
{
previousItem = lastItem;
lastItem = lastItem->Next();
}
// Unlink and free the last entry
previousItem->next = nullptr;
Release(lastItem);
releasedBytes += OUTPUT_BUFFER_SIZE;
} while (previousItem != buffer && releasedBytes < bytesNeeded);
// Update all the references to the last item
for(OutputBuffer *item = buffer; item != nullptr; item = item->Next())
{
item->last = previousItem;
}
return releasedBytes;
}
// Releases an output buffer instance and returns the next entry from the chain
/*static */ OutputBuffer *OutputBuffer::Release(OutputBuffer *buf)
{
const irqflags_t flags = cpu_irq_save();
OutputBuffer *nextBuffer = buf->next;
// If this one is reused by another piece of code, don't free it up
if (buf->references > 1)
{
buf->references--;
buf->bytesRead = 0;
cpu_irq_restore(flags);
return nextBuffer;
}
// Otherwise prepend it to the list of free output buffers again
buf->next = freeOutputBuffers;
freeOutputBuffers = buf;
usedOutputBuffers--;
cpu_irq_restore(flags);
return nextBuffer;
}
/*static */ void OutputBuffer::ReleaseAll(OutputBuffer *buf)
{
while (buf != nullptr)
{
buf = Release(buf);
}
}
/*static*/ void OutputBuffer::Diagnostics()
{
reprap.GetPlatform()->MessageF(GENERIC_MESSAGE, "Used output buffers: %d of %d (%d max)\n",
usedOutputBuffers, OUTPUT_BUFFER_COUNT, maxUsedOutputBuffers);
}
//*************************************************************************************************
// OutputStack class implementation
// Push an OutputBuffer chain to the stack
void OutputStack::Push(OutputBuffer *buffer)
{
if (count == OUTPUT_STACK_DEPTH)
{
OutputBuffer::ReleaseAll(buffer);
reprap.GetPlatform()->RecordError(ErrorCode::OutputStackOverflow);
return;
}
if (buffer != nullptr)
{
buffer->whenQueued = millis();
const irqflags_t flags = cpu_irq_save();
items[count++] = buffer;
cpu_irq_restore(flags);
}
}
// Pop an OutputBuffer chain or return NULL if none is available
OutputBuffer *OutputStack::Pop()
{
if (count == 0)
{
return nullptr;
}
const irqflags_t flags = cpu_irq_save();
OutputBuffer *item = items[0];
for(size_t i = 1; i < count; i++)
{
items[i - 1] = items[i];
}
count--;
cpu_irq_restore(flags);
return item;
}
// Returns the first item from the stack or NULL if none is available
OutputBuffer *OutputStack::GetFirstItem() const
{
if (count == 0)
{
return nullptr;
}
return items[0];
}
// Set the first item of the stack. If it's NULL, then the first item will be removed
void OutputStack::SetFirstItem(OutputBuffer *buffer)
{
const irqflags_t flags = cpu_irq_save();
if (buffer == nullptr)
{
// If buffer is NULL, then the first item is removed from the stack
for(size_t i = 1; i < count; i++)
{
items[i - 1] = items[i];
}
count--;
}
else
{
// Else only the first item is updated
items[0] = buffer;
buffer->whenQueued = millis();
}
cpu_irq_restore(flags);
}
// Returns the last item from the stack or NULL if none is available
OutputBuffer *OutputStack::GetLastItem() const
{
if (count == 0)
{
return nullptr;
}
return items[count - 1];
}
// Get the total length of all queued buffers
size_t OutputStack::DataLength() const
{
size_t totalLength = 0;
const irqflags_t flags = cpu_irq_save();
for(size_t i = 0; i < count; i++)
{
totalLength += items[i]->Length();
}
cpu_irq_restore(flags);
return totalLength;
}
// Append another OutputStack to this instance. If no more space is available,
// all OutputBuffers that can't be added are automatically released
void OutputStack::Append(OutputStack *stack)
{
for(size_t i = 0; i < stack->count; i++)
{
if (count < OUTPUT_STACK_DEPTH)
{
items[count++] = stack->items[i];
}
else
{
reprap.GetPlatform()->RecordError(ErrorCode::OutputStackOverflow);
OutputBuffer::ReleaseAll(stack->items[i]);
}
}
}
// Increase the number of references for each OutputBuffer on the stack
void OutputStack::IncreaseReferences(size_t num)
{
const irqflags_t flags = cpu_irq_save();
for(size_t i = 0; i < count; i++)
{
items[i]->IncreaseReferences(num);
}
cpu_irq_restore(flags);
}
// Release all buffers and clean up
void OutputStack::ReleaseAll()
{
for(size_t i = 0; i < count; i++)
{
OutputBuffer::ReleaseAll(items[i]);
}
count = 0;
}
// vim: ts=4:sw=4