diff --git a/Makefile b/Makefile index c38435d..703d453 100644 --- a/Makefile +++ b/Makefile @@ -50,10 +50,11 @@ DRIVERLIB = $(SDK_PATH)/driverlib OSLIB = $(SDK_PATH)/oslib FREERTOS = $(SDK_PATH)/third_party/FreeRTOS COMMON = $(SDK_PATH)/example/common +STR = external/str CPPFLAGS += $(DEFINES) $(INC) -CFLAGS += -ffunction-sections -fdata-sections -Wall -std=c11 -CXXFLAGS += -ffunction-sections -fdata-sections -Wall +CFLAGS += -Os -ffunction-sections -fdata-sections -Wall -std=c11 +CXXFLAGS += -Os -ffunction-sections -fdata-sections -Wall INC += -I$(SDK_PATH) INC += -I$(SDK_PATH)/inc @@ -63,6 +64,7 @@ INC += -I$(FREERTOS)/source INC += -I$(FREERTOS)/source/include INC += -I$(FREERTOS)/source/portable/GCC/ARM_CM4 INC += -I$(COMMON) +INC += -I$(STR) LIBS = @@ -70,6 +72,7 @@ OBJDIR ?= obj OBJ := $(addprefix $(OBJDIR)/src/, \ ApplicationHooks.o \ + FormattedIO.o \ IPCQueue.o \ main.o \ pinmux.o \ @@ -123,6 +126,10 @@ OBJ += $(addprefix $(OBJDIR)/$(DRIVERLIB)/, \ wdt.o \ ) +OBJ += $(addprefix $(OBJDIR)/$(STR)/, \ + StrPrintf.o \ + ) + .PHONY: all all: $(TARGET) diff --git a/external/str/StrPrintf.c b/external/str/StrPrintf.c new file mode 100644 index 0000000..15c05b0 --- /dev/null +++ b/external/str/StrPrintf.c @@ -0,0 +1,721 @@ +/**************************************************************************** +* +* Since this code originated from code which is public domain, I +* hereby declare this code to be public domain as well. +* +* Dave Hylands - dhylands@gmail.com +* +****************************************************************************/ +/** +* +* @file StrPrintf.cpp +* +* @brief Implementation of a re-entrant printf function. +* +* Implements a reentrant version of the printf function. Also allows a +* function pointer to be provided to perform the actual output. +* +* This version of printf was taken from +* +* http://www.efgh.com/software/gprintf.htm +* +* This software was posted by the author as being in the "public" domain. +* I've taken the original gprintf.txt and made some minor revisions. +* +****************************************************************************/ + +/** +* @defgroup StrPrintf String Formatting +* @ingroup Str +*/ +/** +* @defgroup StrPrintfInternal String Formatting Internals +* @ingroup StrPrintf +*/ + +/* ---- Include Files ---------------------------------------------------- */ + +#include "StrPrintf.h" +#include +#include + +#if defined( AVR ) + +#undef StrPrintf +#undef vStrPrintf + +#undef StrXPrintf +#undef vStrXPrintf + +#define StrPrintf StrPrintf_P +#define vStrPrintf vStrPrintf_P + +#define StrXPrintf StrXPrintf_P +#define vStrXPrintf vStrXPrintf_P + +#else + +#define pgm_read_byte( addr ) *addr + +#endif + +/* ---- Public Variables ------------------------------------------------- */ +/* ---- Private Constants and Types -------------------------------------- */ + +/** + * @addtogroup StrPrintfInternal + * @{ + */ + +/** + * Controls a variety of output options. + */ + +typedef enum +{ + NO_OPTION = 0x00, /**< No options specified. */ + MINUS_SIGN = 0x01, /**< Should we print a minus sign? */ + RIGHT_JUSTIFY = 0x02, /**< Should field be right justified? */ + ZERO_PAD = 0x04, /**< Should field be zero padded? */ + CAPITAL_HEX = 0x08 /**< Did we encounter %X? */ + +} FmtOption; + +/** @def IsOptionSet( p, x ) Determines if an option has been set. */ +/** @def IsOptionClear( p, x ) Determines if an option is not set. */ +/** @def SetOption( p, x ) Sets an option. */ +/** @def ClearOption( p, x ) Unsets an option. */ + +#define IsOptionSet( p, x ) (( (p)->options & (x)) != 0 ) +#define IsOptionClear( p, x ) (( (p)->options & (x)) == 0 ) +#define SetOption( p, x ) (p)->options = (FmtOption)((p)->options | (x)) +#define ClearOption( p, x ) (p)->options = (FmtOption)((p)->options & ~(x)) + +/** + * Internal structure which is used to allow vStrXPrintf() to be reentrant. + */ + +typedef struct +{ + /** Number of characters output so far. */ + int numOutputChars; + + /** Options determined from parsing format specification. */ + FmtOption options; + + /** Minimum number of characters to output. */ + short minFieldWidth; + + /** The exact number of characters to output. */ + short editedStringLen; + + /** The number of leading zeros to output. */ + short leadingZeros; + + /** The function to call to perform the actual output. */ + StrXPrintfFunc outFunc; + + /** Parameter to pass to the output function. */ + void *outParm; + +} Parameters; + +/** + * Internal structure used by vStrPrintf() . + */ +typedef struct +{ + char *str; /**< Buffer to store results into. */ + int maxLen; /**< Maximum number of characters which can be stored. */ + +} StrPrintfParms; + +/* ---- Private Variables ------------------------------------------------ */ +/* ---- Private Function Prototypes -------------------------------------- */ + +static void OutputChar( Parameters *p, int c ); +static void OutputField( Parameters *p, char *s ); +static int StrPrintfFunc( void *outParm, int ch ); + +/** @} */ + +/* ---- Functions -------------------------------------------------------- */ + +/** + * @addtogroup StrPrintf + * @{ + */ + +/***************************************************************************/ +/** +* Writes formatted data into a user supplied buffer. +* +* @param outStr (out) Place to store the formatted string. +* @param maxLen (in) Max number of characters to write into @a outStr. +* @param fmt (in) Format string (see vStrXPrintf() for sull details). +*/ + +int StrPrintf( char *outStr, int maxLen, const char *fmt, ... ) +{ + int rc; + va_list args; + + va_start( args, fmt ); + rc = vStrPrintf( outStr, maxLen, fmt, args ); + va_end( args ); + + return rc; + +} // StrPrintf + +/***************************************************************************/ +/** +* Generic printf function which writes formatted data by calling a user +* supplied function. +* +* @a outFunc will be called to output each character. If @a outFunc returns +* a number >= 0, then StrXPrintf will continue to call @a outFunc with +* additional characters. +* +* If @a outFunc returns a negative number, then StrXPrintf will stop +* calling @a outFunc and will return the non-negative return value. +* +* @param outFunc (in) Pointer to function to call to do the actual output. +* @param outParm (in) Passed to @a outFunc. +* @param fmt (in) Format string (see vStrXPrintf() for sull details). +* +*/ + +int StrXPrintf( StrXPrintfFunc outFunc, void *outParm, const char *fmt, ... ) +{ + int rc; + va_list args; + + va_start( args, fmt ); + rc = vStrXPrintf( outFunc, outParm, fmt, args ); + va_end( args ); + + return rc; + +} // StrxPrintf + +/***************************************************************************/ +/** +* Writes formatted data into a user supplied buffer. +* +* @param outStr (out) Place to store the formatted string. +* @param maxLen (in) Max number of characters to write into @a outStr. +* @param fmt (in) Format string (see vStrXPrintf() for sull details). +* @param args (in) Arguments in a format compatible with va_arg(). +*/ + +int vStrPrintf( char *outStr, int maxLen, const char *fmt, va_list args ) +{ + StrPrintfParms strParm; + + strParm.str = outStr; + strParm.maxLen = maxLen - 1; /* Leave space for temrinating null char */ + + return vStrXPrintf( StrPrintfFunc, &strParm, fmt, args ); + +} // vStrPrintf + +/***************************************************************************/ +/** +* Generic, reentrant printf function. This is the workhorse of the StrPrintf +* functions. +* +* @a outFunc will be called to output each character. If @a outFunc returns +* a number >= 0, then vStrXPrintf will continue to call @a outFunc with +* additional characters. +* +* If @a outFunc returns a negative number, then vStrXPrintf will stop calling +* @a outFunc and will return the non-negative return value. +* +* The format string @a fmt consists of ordinary characters, escape +* sequences, and format specifications. The ordinary characters and escape +* sequences are output in their order of appearance. Format specifications +* start with a percent sign (%) and are read from left to right. When +* the first format specification (if any) is encountered, it converts the +* value of the first argument after @a fmt and outputs it accordingly. +* The second format specification causes the second argument to be +* converted and output, and so on. If there are more arguments than there +* are format specifications, the extra arguments are ignored. The +* results are undefined if there are not enough arguments for all the +* format specifications. +* +* A format specification has optional, and required fields, in the following +* form: +* +* %[flags][width][.precision][l]type +* +* Each field of the format specification is a single character or a number +* specifying a particular format option. The simplest format specification +* contains only the percent sign and a @b type character (for example %s). +* If a percent sign is followed by a character that has no meaning as a +* format field, the character is sent to the output function. For example, +* to print a percent-sign character, use %%. +* +* The optional fields, which appear before the type character, control +* other aspects of the formatting, as follows: +* +* @b flags may be one of the following: +* +* - - (minus sign) left align the result within the given field width. +* - 0 (zero) Zeros are added until the minimum width is reached. +* +* @b width may be one of the following: +* - a number specifying the minimum width of the field +* - * (asterick) means that an integer taken from the argument list will +* be used to provide the width. The @a width argument must precede the +* value being formatted in the argument list. +* +* @b precision may be one of the following: +* - a number +* - * (asterick) means that an integer taken from the argument list will +* be used to provide the precision. The @a precision argument must +* precede the value being formatted in the argument list. +* +* The interpretation of @a precision depends on the type of field being +* formatted: +* - For b, d, o, u, x, X, the precision specifies the minimum number of +* digits that will be printed. If the number of digits in the argument +* is less than @a precision, the output value is padded on the left with +* zeros. The value is not truncated when the number of digits exceeds +* @a prcision. +* - For s, the precision specifies the maximum number of characters to be +* printed. +* +* The optional type modifier l (lowercase ell), may be used to specify +* that the argument is a long argument. This makes a difference on +* architectures where the sizeof an int is different from the sizeof a long. +* +* @b type causes the output to be formatted as follows: +* - b Unsigned binary integer. +* - c Character. +* - d Signed decimal integer. +* - o Unsigned octal integer. +* - s Null terminated character string. +* - u Unsigned Decimal integer. +* - x Unsigned hexadecimal integer, using "abcdef". +* - X Unsigned hexadecimal integer, using "ABCDEF". +* +* @param outFunc (in) Pointer to function to call to output a character. +* @param outParm (in) Passed to @a outFunc. +* @param fmt (in) Format string (ala printf, descrtibed above). +* @param args (in) Variable length list of arguments. +* +* @return The number of characters successfully output, or a negative number +* if an error occurred. +*/ + +int vStrXPrintf +( + StrXPrintfFunc outFunc, + void *outParm, + const char *fmt, + va_list args +) +{ + Parameters p; + char controlChar; + + p.numOutputChars = 0; + p.outFunc = outFunc; + p.outParm = outParm; + + controlChar = pgm_read_byte( fmt++ ); + + while ( controlChar != '\0' ) + { + if ( controlChar == '%' ) + { + short precision = -1; + short longArg = 0; + short base = 0; + + controlChar = pgm_read_byte( fmt++ ); + p.minFieldWidth = 0; + p.leadingZeros = 0; + p.options = NO_OPTION; + + SetOption( &p, RIGHT_JUSTIFY ); + + /* + * Process [flags] + */ + + if ( controlChar == '-' ) + { + ClearOption( &p, RIGHT_JUSTIFY ); + controlChar = pgm_read_byte( fmt++ ); + } + + if ( controlChar == '0' ) + { + SetOption( &p, ZERO_PAD ); + controlChar = pgm_read_byte( fmt++ ); + } + + /* + * Process [width] + */ + + if ( controlChar == '*' ) + { + p.minFieldWidth = (short)va_arg( args, int ); + controlChar = pgm_read_byte( fmt++ ); + } + else + { + while (( '0' <= controlChar ) && ( controlChar <= '9' )) + { + p.minFieldWidth = + p.minFieldWidth * 10 + controlChar - '0'; + controlChar = pgm_read_byte( fmt++ ); + } + } + + /* + * Process [.precision] + */ + + if ( controlChar == '.' ) + { + controlChar = pgm_read_byte( fmt++ ); + if ( controlChar == '*' ) + { + precision = (short)va_arg( args, int ); + controlChar = pgm_read_byte( fmt++ ); + } + else + { + precision = 0; + while (( '0' <= controlChar ) && ( controlChar <= '9' )) + { + precision = precision * 10 + controlChar - '0'; + controlChar = pgm_read_byte( fmt++ ); + } + } + } + + /* + * Process [l] + */ + + if ( controlChar == 'l' ) + { + longArg = 1; + controlChar = pgm_read_byte( fmt++ ); + } + + /* + * Process type. + */ + + if ( controlChar == 'd' ) + { + base = 10; + } + else + if ( controlChar == 'x' ) + { + base = 16; + } + else + if ( controlChar == 'X' ) + { + base = 16; + SetOption( &p, CAPITAL_HEX ); + } + else + if ( controlChar == 'u' ) + { + base = 10; + } + else + if ( controlChar == 'o' ) + { + base = 8; + } + else + if ( controlChar == 'b' ) + { + base = 2; + } + else + if ( controlChar == 'c' ) + { + base = -1; + ClearOption( &p, ZERO_PAD ); + } + else + if ( controlChar == 's' ) + { + base = -2; + ClearOption( &p, ZERO_PAD ); + } + + if ( base == 0 ) /* invalid conversion type */ + { + if ( controlChar != '\0' ) + { + OutputChar( &p, controlChar ); + controlChar = pgm_read_byte( fmt++ ); + } + } + else + { + if ( base == -1 ) /* conversion type c */ + { + char c = (char)va_arg( args, int ); + p.editedStringLen = 1; + OutputField( &p, &c ); + } + else if ( base == -2 ) /* conversion type s */ + { + char *string = va_arg( args, char * ); + + p.editedStringLen = 0; + while ( string[ p.editedStringLen ] != '\0' ) + { + if (( precision >= 0 ) && ( p.editedStringLen >= precision )) + { + /* + * We don't require the string to be null terminated + * if a precision is specified. + */ + + break; + } + p.editedStringLen++; + } + OutputField( &p, string ); + } + else /* conversion type d, b, o or x */ + { + unsigned long x; + + /* + * Worst case buffer allocation is required for binary output, + * which requires one character per bit of a long. + */ + + char buffer[ CHAR_BIT * sizeof( unsigned long ) + 1 ]; + + p.editedStringLen = 0; + if ( longArg ) + { + x = va_arg( args, unsigned long ); + } + else + if ( controlChar == 'd' ) + { + x = va_arg( args, int ); + } + else + { + x = va_arg( args, unsigned ); + } + + if (( controlChar == 'd' ) && ((long) x < 0 )) + { + SetOption( &p, MINUS_SIGN ); + x = - (long) x; + } + + do + { + int c; + c = x % base + '0'; + if ( c > '9' ) + { + if ( IsOptionSet( &p, CAPITAL_HEX )) + { + c += 'A'-'9'-1; + } + else + { + c += 'a'-'9'-1; + } + } + buffer[ sizeof( buffer ) - 1 - p.editedStringLen++ ] = (char)c; + } + while (( x /= base ) != 0 ); + + if (( precision >= 0 ) && ( precision > p.editedStringLen )) + { + p.leadingZeros = precision - p.editedStringLen; + } + OutputField( &p, buffer + sizeof(buffer) - p.editedStringLen ); + } + controlChar = pgm_read_byte( fmt++ ); + } + } + else + { + /* + * We're not processing a % output. Just output the character that + * was encountered. + */ + + OutputChar( &p, controlChar ); + controlChar = pgm_read_byte( fmt++ ); + } + } + return p.numOutputChars; + +} // vStrXPrintf + +/** @} */ + +/** + * @addtogroup StrPrintfInternal + * @{ + */ + +/***************************************************************************/ +/** +* Outputs a single character, keeping track of how many characters have +* been output. +* +* @param p (mod) State information. +* @param c (in) Character to output. +*/ + +static void OutputChar( Parameters *p, int c ) +{ + if ( p->numOutputChars >= 0 ) + { + int n = (*p->outFunc)(p->outParm, c); + + if ( n >= 0 ) + { + p->numOutputChars++; + } + else + { + p->numOutputChars = n; + } + } + +} // OutputChar + +/***************************************************************************/ +/** +* Outputs a formatted field. This routine assumes that the field has been +* converted to a string, and this routine takes care of the width +* options, leading zeros, and any leading minus sign. +* +* @param p (mod) State information. +* @param s (in) String to output. +*/ + +static void OutputField( Parameters *p, char *s ) +{ + short padLen = p->minFieldWidth - p->leadingZeros - p->editedStringLen; + + if ( IsOptionSet( p, MINUS_SIGN )) + { + if ( IsOptionSet( p, ZERO_PAD )) + { + /* + * Since we're zero padding, output the minus sign now. If we're space + * padding, we wait until we've output the spaces. + */ + + OutputChar( p, '-' ); + } + + /* + * Account for the minus sign now, even if we are going to output it + * later. Otherwise we'll output too much space padding. + */ + + padLen--; + } + + if ( IsOptionSet( p, RIGHT_JUSTIFY )) + { + /* + * Right justified: Output the spaces then the field. + */ + + while ( --padLen >= 0 ) + { + OutputChar( p, p->options & ZERO_PAD ? '0' : ' ' ); + } + } + if ( IsOptionSet( p, MINUS_SIGN ) && IsOptionClear( p, ZERO_PAD )) + { + /* + * We're not zero padding, which means we haven't output the minus + * sign yet. Do it now. + */ + + OutputChar( p, '-' ); + } + + /* + * Output any leading zeros. + */ + + while ( --p->leadingZeros >= 0 ) + { + OutputChar( p, '0' ); + } + + /* + * Output the field itself. + */ + + while ( --p->editedStringLen >= 0 ) + { + OutputChar( p, *s++ ); + } + + /* + * Output any trailing space padding. Note that if we output leading + * padding, then padLen will already have been decremented to zero. + */ + + while ( --padLen >= 0 ) + { + OutputChar( p, ' ' ); + } + +} // OutputField + +/***************************************************************************/ +/** +* Helper function, used by vStrPrintf() (and indirectly by StrPrintf()) +* for outputting characters into a user supplied buffer. +* +* @param outParm (mod) Pointer to StrPrintfParms structure. +* @param ch (in) Character to output. +* +* @return 1 if the character was stored successfully, -1 if the buffer +* was overflowed. +*/ + +static int StrPrintfFunc( void *outParm, int ch ) +{ + StrPrintfParms *strParm = (StrPrintfParms *)outParm; + + if ( strParm->maxLen > 0 ) + { + *strParm->str++ = (char)ch; + *strParm->str = '\0'; + strParm->maxLen--; + + return 1; + } + + /* + * Whoops. We ran out of space. + */ + + return -1; + +} // StrPrintfFunc + diff --git a/external/str/StrPrintf.h b/external/str/StrPrintf.h new file mode 100644 index 0000000..0d48d96 --- /dev/null +++ b/external/str/StrPrintf.h @@ -0,0 +1,21 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +#pragma once + +#include + +typedef int (*StrXPrintfFunc)(void *outParm, int c); + +int +StrPrintf(char* outStr, int maxLen, const char* fmt, ...); + +int +StrXPrintf(StrXPrintfFunc outFunc, void* outParm, const char* fmt, ...); + +int +vStrPrintf(char* outStr, int maxLen, const char* fmt, va_list args); + +int +vStrXPrintf(StrXPrintfFunc outFunc,void* outParm, const char* fmt, + va_list args); diff --git a/src/FormattedIO.c b/src/FormattedIO.c new file mode 100644 index 0000000..258b508 --- /dev/null +++ b/src/FormattedIO.c @@ -0,0 +1,147 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FormattedIO.h" +#include +#include +#include "Serial.h" + +static int +PrintIPC(uint32_t aLength, void* aBuffer) +{ + IPCMessageQueue* queue = GetSerialOutQueue(); + if (!queue) { + return -1; + } + IPCMessage msg; + int res = IPCMessageInit(&msg); + if (res < 0) { + return -1; + } + res = IPCMessageProduce(&msg, aLength, aBuffer); + if (res < 0) { + goto err; + } + res = IPCMessageQueueConsume(queue, &msg); + if (res < 0) { + goto err; + } + res = IPCMessageWaitForConsumption(&msg); + if (res < 0) { + goto err; + } + IPCMessageUninit(&msg); + return res; + +err: + IPCMessageUninit(&msg); + return -1; +} + +/* + * Libc-like interfaces for easy usage. + */ + +int +Print(const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + int res = VPrint(fmt, ap); + va_end(ap); + + return res; +} + +typedef struct +{ + char* mBuf; + size_t mLen; +} StrBuf; + +static int +PutStrBuf(void* aParam, int aChar) +{ + StrBuf* buf = aParam; + + if (!buf->mLen) { + return -1; + } else if (buf->mLen == 1) { + aChar = '\0'; /* always terminate string buffer */ + } + + *buf->mBuf = aChar; + ++buf->mBuf; + --buf->mLen; + + return 1; +} + +int +VPrint(const char* fmt, va_list ap) +{ + char buf[128]; + StrBuf strBuf = { + .mBuf = buf, + .mLen = sizeof(buf) + }; + int res = vStrXPrintf(PutStrBuf, &strBuf, fmt, ap); + if (res < 0) { + return -1; + } + uint32_t len = sizeof(buf) - strBuf.mLen; + res = PrintIPC(len, buf); + if (res < 0) { + return -1; + } + return len; +} + +static int +PutSerial(void* aParam, int aChar) +{ + SerialPutChar(aChar); + return 0; +} + +int +_Print(const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + int res = _VPrint(fmt, ap); + va_end(ap); + + return res; +} + +int +_VPrint(const char* fmt, va_list ap) +{ + int res = vStrXPrintf(PutSerial, NULL, fmt, ap); + if (res < 0) { + return -1; + } + return res; +} + +int +PrintFromISR(const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + int res = VPrintFromISR(fmt, ap); + va_end(ap); + + return res; +} + +int +VPrintFromISR(const char* fmt, va_list ap) +{ + return _VPrint(fmt, ap); +} diff --git a/src/FormattedIO.h b/src/FormattedIO.h new file mode 100644 index 0000000..c9f9f9a --- /dev/null +++ b/src/FormattedIO.h @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include + +int +Print(const char* fmt, ...); + +int +VPrint(const char* fmt, va_list ap); + +int +PrintFromISR(const char* fmt, ...); + +int +VPrintFromISR(const char* fmt, va_list ap); + +/* Internal functions for debugging; don't use in production code. */ + +int +_Print(const char* fmt, ...); + +int +_VPrint(const char* fmt, va_list ap); diff --git a/src/IPCQueue.c b/src/IPCQueue.c index 6ec20cd..03df28f 100644 --- a/src/IPCQueue.c +++ b/src/IPCQueue.c @@ -4,6 +4,14 @@ #include "IPCQueue.h" +typedef struct +{ + uint32_t mDWord0; + uint32_t mDWord1; + uint32_t mStatus; + void* mBuffer; +} IPCMessageReply; + /* * IPCMessage */ @@ -11,33 +19,155 @@ int IPCMessageInit(IPCMessage* aMsg) { + aMsg->mMonitor = xQueueCreate(1, sizeof(IPCMessageReply)); + if (!aMsg->mMonitor) { + return -1; + } + aMsg->mDWord0 = 0; aMsg->mDWord1 = 0; - aMsg->mStatus = 0; + aMsg->mStatus = IPC_MESSAGE_STATE_CLEAR; aMsg->mBuffer = NULL; return 0; } +void +IPCMessageUninit(IPCMessage* aMsg) +{ + vQueueDelete(aMsg->mMonitor); +} + +uint32_t +IPCMessageGetBufferLength(const IPCMessage* aMsg) +{ + return aMsg->mStatus & 0x00ffffff; +} + int -IPCMessageProduce(IPCMessage* aMsg) +IPCMessageProduce(IPCMessage* aMsg, uint32_t aLength, void* aBuffer) +{ + switch (aMsg->mStatus & 0xf0000000) { + case IPC_MESSAGE_STATE_CLEAR: /* fall through */ + case IPC_MESSAGE_STATE_PRODUCED: /* fall through */ + case IPC_MESSAGE_STATE_ERROR: + /* We're good if the message is currently not in transit. */ + break; + case IPC_MESSAGE_STATE_PENDING: /* fall through */ + default: + /* If the message is currently in transit or the status is + * unknown, we don't produce a new one. Better abort here. */ + return -1; + } + + aMsg->mDWord0 = 0; + aMsg->mDWord1 = 0; + aMsg->mStatus = 0; + aMsg->mStatus |= IPC_MESSAGE_STATE_PRODUCED; + aMsg->mStatus |= aLength; + aMsg->mBuffer = aBuffer; + + return 0; +} + +static int +WaitForConsumption(IPCMessage* aMsg, IPCMessageReply* aReply) { - /* TODO: At some point we have to implement efficient IPC with - * large buffers. IPCMessageProduce() will signal the end of the - * message constrcution **on the producer tast.** A produced - * message can be send over over an IPC queue to a consumer task. - * The consumer calls IPCMessageConsume() after it processed the - * buffer. The producer can then release the buffer. */ + uint32_t state = aMsg->mStatus & 0xf0000000; + if (state != IPC_MESSAGE_STATE_PENDING) { + return -1; + } + BaseType_t ret = xQueueReceive(aMsg->mMonitor, aReply, portMAX_DELAY); + if (ret != pdPASS) { + return -1; + }; return 0; } int -IPCMessageConsume(IPCMessage* aMsg) +IPCMessageWaitForReply(IPCMessage* aMsg) { - /* TODO: See IPCMessageProduce() */ + if (aMsg->mStatus & IPC_MESSAGE_FLAG_NOWAIT) { + /* The consumer won't reply to us, so we're + * returning an error here. */ + return -1; + } + IPCMessageReply reply; + int res = WaitForConsumption(aMsg, &reply); + if (res < 0) { + return -1; + }; + aMsg->mDWord0 = reply.mDWord0; + aMsg->mDWord1 = reply.mDWord1; + aMsg->mStatus = reply.mStatus; + aMsg->mBuffer = reply.mBuffer; return 0; } +int +IPCMessageWaitForConsumption(IPCMessage* aMsg) +{ + if (aMsg->mStatus & IPC_MESSAGE_FLAG_NOWAIT) { + /* The consumer won't send a reply. We simply reset + * the message state and succeed silently. */ + aMsg->mStatus &= 0x0fffffff; + aMsg->mStatus |= IPC_MESSAGE_STATE_CLEAR; + return 0; + } + IPCMessageReply reply; + int res = WaitForConsumption(aMsg, &reply); + if (res < 0) { + return -1; + }; + aMsg->mStatus &= 0x0fffffff; /* clear pending status */ + return 0; +} + +static int +ConsumeAndReply(IPCMessage* aMsg, const IPCMessageReply* aReply) +{ + BaseType_t res = xQueueSend(aMsg->mMonitor, &aReply, 0); + if (res != pdPASS) { + return -1; + } + return 0; +} + +int +IPCMessageConsumeAndReply(IPCMessage* aMsg, + uint32_t aDWord0, uint32_t aDWord1, + uint32_t aFlags, uint32_t aLength, + void* aBuffer) +{ + if (aMsg->mStatus & IPC_MESSAGE_FLAG_NOWAIT) { + /* We cannot reply because the producer doesn't + * wait for the consumption of the message. */ + return -1; + } + IPCMessageReply reply = { + .mDWord0 = aDWord0, + .mDWord1 = aDWord1, + .mStatus = aFlags | aLength, + .mBuffer = aBuffer + }; + return ConsumeAndReply(aMsg, &reply); +} + +int +IPCMessageConsume(IPCMessage* aMsg) +{ + if (aMsg->mStatus & IPC_MESSAGE_FLAG_NOWAIT) { + return 0; /* Silently succeed */ + } + static const IPCMessageReply sReply = { + .mDWord0 = 0, + .mDWord1 = 0, + .mStatus = IPC_MESSAGE_STATE_CLEAR, + .mBuffer = NULL, + }; + return ConsumeAndReply(aMsg, &sReply); +} + /* * IPCMessageQueue */ @@ -52,14 +182,49 @@ IPCMessageQueueInit(IPCMessageQueue* aMsgQueue) return 0; } +void +IPCMessageQueueUninit(IPCMessageQueue* aMsgQueue) +{ + vQueueDelete(aMsgQueue->mWaitQueue); +} + int IPCMessageQueueConsume(IPCMessageQueue* aMsgQueue, IPCMessage* aMsg) { - BaseType_t res = xQueueSend(aMsgQueue->mWaitQueue, aMsg, 0); - if (res != pdPASS){ - return -1; - } - return 0; + uint32_t status = aMsg->mStatus; + + switch (status & 0xf0000000) { + case IPC_MESSAGE_STATE_PRODUCED: /* fall through */ + /* We're good if the message has been produced correctly. */ + break; + case IPC_MESSAGE_STATE_CLEAR: /* fall through */ + case IPC_MESSAGE_STATE_PENDING: /* fall through */ + case IPC_MESSAGE_STATE_ERROR: /* fall through */ + default: + /* In any other case, the message is probably not ready for + * consumption. Better abort here. */ + return -1; + } + + /* Usually the consumer will send a reply after the message has + * been processed. Except if we set the NOWAIT flag. In this case + * we reset the message state to CLEAR. */ + aMsg->mStatus &= 0x0fffffff; + if (aMsg->mStatus & IPC_MESSAGE_FLAG_NOWAIT) { + aMsg->mStatus |= IPC_MESSAGE_STATE_CLEAR; + } else { + aMsg->mStatus |= IPC_MESSAGE_STATE_PENDING; + } + + BaseType_t res = xQueueSend(aMsgQueue->mWaitQueue, aMsg, 0); + if (res != pdPASS){ + goto err_xQueueSend; + } + return 0; + +err_xQueueSend: + aMsg->mStatus = status; + return -1; } int diff --git a/src/IPCQueue.h b/src/IPCQueue.h index 7e4253c..83fa4f3 100644 --- a/src/IPCQueue.h +++ b/src/IPCQueue.h @@ -4,6 +4,139 @@ #pragma once +/* + * Inter-Process Communication is one of the building blocks of the + * firmware. + * + * IPC is performed between tasks (not processes) by exchanging IPC + * messages. A task can either send messages, receive messages, or + * both. + * + * IPC messages + * ------------ + * + * An IPC message is represented by the data structure IPCMessage. Each + * message can transfer two 32-bit values and optionally an external + * buffer. + * + * Initialize the message structure by calling IPCMessageInit(). To + * release an initialized message's internal resource, call + * IPCMessageUninit() + * + * IPCMessage msg; + * IPCMessageInit(&msg); + * // do IPC + * IPCMessageUninit(&msg); + * + * The init function returns a negative value on errors, or 0 on + * success. In the examples, we leave out error checking, but don't do + * so in production code. + * + * After you initialized the message, you have to 'produce' it, give + * it to a consumer, and wait for consumption. The produce step is + * performed by IPCMessageProduce(), the waiting step is performed by + * IPCMessageWaitForConsumption(). + * + * const char buf[] = "Hello world"; + * IPCMessage msg; + * IPCMessageInit(&msg); + * IPCMessageProduce(&msg, sizeof(buf), (void*)buf); + * // do message setup and actual IPC with the consumer + * IPCMessageWaitForConsumption(&msg) + * IPCMessageUninit(&msg); + * + * IPCMessageProduce() takes the message as its argument, and an optional + * buffer plus length. The buffer's address is attached to the message and + * received by the consumer. Pass 0 and NULL if you don't want to transfer + * a buffer. IPC messages do not transfer ownership of the message or the + * attached buffer! The message, the buffer and the buffer's content must + * be valid until the consumer has finished processing the message. + * + * Two additional values can be transfered in the IPC message. + * + * msg.mDWord0 = (uint32_t)1ul; + * msg.mDWord1 = (uint32_t)-1l; + * + * After the IPC message has been given to the consumer, which is described + * in the next section, IPCMessageWaitForConsumption() allows to wait for the + * completion of the consumer's side. Instead of only consuming a message, + * producers have the option of returning a reply. Replace the call to + * IPCMessageWaitForConsumption() with IPCMessageWaitForReply() to receive + * the reply. The reply data can contain two 32-bit values and optionally a + * buffer. Again, ownership of the buffer is not transfered. + * + * An initialized IPC message can be used throughout multiple produce- + * consume cyles. There's no requirement to uninitialize and re-initialize + * after consumption. + * + * IPC is performed asynchronously. Both, producer and consumer, continue + * independently. Only calling IPCMessageWaitForConsumption() or + * IPCMessageWaitForReply() will synchronize them. Once these calls return, + * it's safe to release the message, buffer, and buffer content. + * + * There's one shortcut through the produce-consume cycle. If you only want + * to send a message to a consumer and don't have to care about a reply or + * buffer lifetime, you can set NOWAIT on the produced message. + * + * msg.mStatus |= IPC_MESSAGE_FLAG_NOWAIT + * + * NOWAIT is an optimization for these single-shot use cases. The producer + * will not reply after consuming the message, and producers will not wait + * for it. Calling the related functions is safe, but there's no reqirement + * to do so. + * + * Sending a message + * ----------------- + * + * Each consumer task waits for messages on a queue of type IPCMessageQueue. + * To insert an IPC message into the queue, call IPCMessageQueueConsume(). This + * will wake up the waiting consumer. + * + * extern IPCMessageQueue msgQueue; + * + * const char buf[] = "Hello world"; + * IPCMessage msg; + * IPCMessageInit(&msg); + * IPCMessageProduce(&msg, sizeof(buf), (void*)buf); + * // do message setup + * IPCMessageQueueConsume(&msgQueue, &msg); + * IPCMessageWaitForConsumption(&msg) + * IPCMessageUninit(&msg); + * + * Calls to IPCMessageQuueConsume() are meant to complete quickly. But + * if there's lots of contention on the queue, or the consumer is slow, + * the function might block until there's space available at the end of + * the message queue. + * + * Receiving a message + * ------------------- + * + * The consumer task waits for incomming messages on an IPCMessageQueue + * until a message arrives. Message queues are initialized with a call to + * IPCMessageQueueInit(). + * + * IPCMessageQueue msgQueue; + * IPCMessageQueueInit(&msgQueue); + * + * Waiting if performed by IPCMessageQueueWait(). The function's message + * argument returns the received message. After processing the message, call + * IPCMessageConsume() to signal the producer that you're done. + * + * IPCMessageQueue msgQueue; + * IPCMessageQueueInit(&msgQueue); + * + * while (1) { + * IPCMessage msg; + * IPCMessageQueueWait(&msgQueue, &msg); + * // process message + * IPCMessageConsume(&msg); + * } + * + * To send a reply to the producer, replace the call to IPCMessageConsume() + * with IPCMessageReply(). This function allows to transfer two 32-bit values, + * and a buffer to the producer. Again, buffer ownership is not transfered. + */ + #include #include @@ -13,22 +146,52 @@ * IPCMessage */ +enum IPCMessageStatus { + /* Don't wait for consumer and don't signal consumption to producer. */ + IPC_MESSAGE_FLAG_NOWAIT = 0x01000000, + IPC_MESSAGE_STATE_CLEAR = 0x00000000, + IPC_MESSAGE_STATE_PRODUCED = 0x10000000, + IPC_MESSAGE_STATE_PENDING = 0x20000000, + IPC_MESSAGE_STATE_ERROR = 0x30000000 +}; + typedef struct { + /* Internal monitor for signalling */ + QueueHandle_t mMonitor; + /* Two dword for data transfers. */ uint32_t mDWord0; uint32_t mDWord1; - /* Message flags [31:24] and buffer length [23:0] */ + /* Message state [31:28], flags [27:24], and buffer length [23:0] */ uint32_t mStatus; /* Message buffer */ - const void* mBuffer; + void* mBuffer; } IPCMessage; int IPCMessageInit(IPCMessage* aMsg); +void +IPCMessageUninit(IPCMessage* aMsg); + +uint32_t +IPCMessageGetBufferLength(const IPCMessage* aMsg); + int -IPCMessageProduce(IPCMessage* aMsg); +IPCMessageProduce(IPCMessage* aMsg, uint32_t aLength, void* aBuffer); + +int +IPCMessageWaitForReply(IPCMessage* aMsg); + +int +IPCMessageWaitForConsumption(IPCMessage* aMsg); + +int +IPCMessageConsumeAndReply(IPCMessage* aMsg, + uint32_t aDWord0, uint32_t aDWord1, + uint32_t aFlags, uint32_t aLength, + void* aBuffer); int IPCMessageConsume(IPCMessage* aMsg); @@ -40,8 +203,6 @@ IPCMessageConsume(IPCMessage* aMsg); typedef struct { QueueHandle_t mWaitQueue; - - unsigned char mBuffer[1024]; } IPCMessageQueue; int diff --git a/src/Producer.c b/src/Producer.c index 42b1742..a880aef 100644 --- a/src/Producer.c +++ b/src/Producer.c @@ -3,12 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Producer.h" - -#include -#include - -#include "IPCQueue.h" #include "Ptr.h" +#include "FormattedIO.h" #include "Task.h" #include "Ticks.h" @@ -20,20 +16,7 @@ Run(ProducerTask* aProducer) }; for (unsigned long i = 0;; i = (i + 1) % ArrayLength(sMessage)) { - IPCMessage msg; - int res = IPCMessageInit(&msg); - if (res < 0) { - return; - } - msg.mBuffer = sMessage[i]; - msg.mStatus = strlen(msg.mBuffer) + 1; - - IPCMessageProduce(&msg); - - res = IPCMessageQueueConsume(aProducer->mSendQueue, &msg); - if (res < 0) { - return; - } + Print("%s", sMessage[i]); vTaskDelay(TicksOfMSecs(200)); } } @@ -52,9 +35,8 @@ TaskEntryPoint(void* aParam) } int -ProducerTaskInit(ProducerTask* aProducer, IPCMessageQueue* aSendQueue) +ProducerTaskInit(ProducerTask* aProducer) { - aProducer->mSendQueue = aSendQueue; aProducer->mTask = NULL; return 0; diff --git a/src/Producer.h b/src/Producer.h index d909d3e..b588a0e 100644 --- a/src/Producer.h +++ b/src/Producer.h @@ -5,20 +5,15 @@ #pragma once #include -#include #include -#include "IPCQueue.h" - typedef struct { - IPCMessageQueue* mSendQueue; - TaskHandle_t mTask; } ProducerTask; int -ProducerTaskInit(ProducerTask* aProducer, IPCMessageQueue* aSendQueue); +ProducerTaskInit(ProducerTask* aProducer); int ProducerTaskSpawn(ProducerTask* aProducer); diff --git a/src/Serial.c b/src/Serial.c index c1bb989..4b41ab5 100644 --- a/src/Serial.c +++ b/src/Serial.c @@ -4,14 +4,46 @@ #include "Serial.h" +#include +#include +#include +#include +#include #include +#include +#include + #include "Task.h" +/* + * Serial output + */ + +typedef struct +{ + IPCMessageQueue mRecvQueue; + + TaskHandle_t mTask; +} SerialOutTask; + +void +SerialPutChar(int c) +{ + MAP_UARTCharPut(CONSOLE, c); +} + +void +SerialPutString(size_t aLength, const char* aString) +{ + for (const char* end = aString + aLength; aString < end; ++aString) { + SerialPutChar(*aString); + } +} + static void Run(SerialOutTask* aSerialOut) { - InitTerm(); ClearTerm(); for (;;) { @@ -20,7 +52,8 @@ Run(SerialOutTask* aSerialOut) if (res < 0) { return; } - Report(msg.mBuffer); + + SerialPutString(IPCMessageGetBufferLength(&msg), msg.mBuffer); IPCMessageConsume(&msg); } @@ -39,7 +72,7 @@ TaskEntryPoint(void* aParam) vTaskSuspend(serialOut->mTask); } -int +static int SerialOutTaskInit(SerialOutTask* aSerialOut) { int res = IPCMessageQueueInit(&aSerialOut->mRecvQueue); @@ -51,7 +84,7 @@ SerialOutTaskInit(SerialOutTask* aSerialOut) return 0; } -int +static int SerialOutTaskSpawn(SerialOutTask* aSerialOut) { BaseType_t res = xTaskCreate(TaskEntryPoint, "serial-out", @@ -62,3 +95,32 @@ SerialOutTaskSpawn(SerialOutTask* aSerialOut) } return 0; } + +/* + * Public interfaces + */ + +static SerialOutTask sSerialOutTask; + +int +SerialInit() +{ + InitTerm(); + + /* + * Create the output task + */ + if (SerialOutTaskInit(&sSerialOutTask) < 0) { + return -1; + } + if (SerialOutTaskSpawn(&sSerialOutTask) < 0) { + return -1; + } + return 0; +} + +IPCMessageQueue* +GetSerialOutQueue() +{ + return &sSerialOutTask.mRecvQueue; +} diff --git a/src/Serial.h b/src/Serial.h index 90aa9cd..3d661e1 100644 --- a/src/Serial.h +++ b/src/Serial.h @@ -4,21 +4,19 @@ #pragma once -#include -#include -#include - #include "IPCQueue.h" -typedef struct -{ - IPCMessageQueue mRecvQueue; +int +SerialInit(void); - TaskHandle_t mTask; -} SerialOutTask; +void +SerialPutChar(int c); -int -SerialOutTaskInit(SerialOutTask* aSerialOut); +void +SerialPutString(size_t aLength, const char* aString); -int -SerialOutTaskSpawn(SerialOutTask* aSerialOut); +/* Returns the message queue for output of over the serial line. This + * is a singleton. + */ +IPCMessageQueue* +GetSerialOutQueue(void); diff --git a/src/main.c b/src/main.c index 5a7d562..1dc2c2a 100644 --- a/src/main.c +++ b/src/main.c @@ -37,12 +37,7 @@ main(void) * Create the output task */ - static SerialOutTask serialOutTask; - - if (SerialOutTaskInit(&serialOutTask) < 0) { - return EXIT_FAILURE; - } - if (SerialOutTaskSpawn(&serialOutTask) < 0) { + if (SerialInit() < 0) { return EXIT_FAILURE; } @@ -52,7 +47,7 @@ main(void) static ProducerTask producerTask; - if (ProducerTaskInit(&producerTask, &serialOutTask.mRecvQueue) < 0) { + if (ProducerTaskInit(&producerTask) < 0) { return EXIT_FAILURE; } if (ProducerTaskSpawn(&producerTask) < 0) {