From f79bfddcad7ec5891f6e13cb919a712cf42ebdc3 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Thu, 23 Nov 2017 17:44:33 +0200 Subject: [PATCH] Make stdin/stdout/stderr be FileHandles Rework so that everything is a FileHandle, including stdin/stdout/stderr. Provide legacy functionality of calling serial_getc and serial_putc as an internal "DirectSerial" FileHandle. Add a JSON option to use UARTSerial instead. Add hooks for target and application to provide custom FileHandles. Allow for CRLF conversion to work on any FileHandle that isatty(), as stdin/stdout or any other FILE. Optimise the conversion so it doesn't force all write calls to be 1 byte. Limit the conversion to the stdio layer, so that read() and write() work the same as the FileHandle methods - this seems less confusing. --- platform/mbed_lib.json | 12 +- platform/mbed_retarget.cpp | 428 +++++++++++++++++++++++++------------ platform/mbed_retarget.h | 44 ++++ 3 files changed, 352 insertions(+), 132 deletions(-) diff --git a/platform/mbed_lib.json b/platform/mbed_lib.json index 479b76cb90..6dd1601cf3 100644 --- a/platform/mbed_lib.json +++ b/platform/mbed_lib.json @@ -2,7 +2,17 @@ "name": "platform", "config": { "stdio-convert-newlines": { - "help": "Enable conversion to standard newlines on stdin/stdout", + "help": "Enable conversion to standard newlines on stdin/stdout/stderr", + "value": false + }, + + "stdio-convert-tty-newlines": { + "help": "Enable conversion to standard newlines on any tty FILE stream", + "value": false + }, + + "stdio-buffered-serial": { + "help": "Use UARTSerial driver to obtain buffered serial I/O on stdin/stdout/stderr. If false, unbuffered serial_getc and serial_putc are used directly.", "value": false }, diff --git a/platform/mbed_retarget.cpp b/platform/mbed_retarget.cpp index 1247efacf4..9d798f9384 100644 --- a/platform/mbed_retarget.cpp +++ b/platform/mbed_retarget.cpp @@ -28,11 +28,15 @@ #include "platform/mbed_critical.h" #include "platform/mbed_poll.h" #include "platform/PlatformMutex.h" +#include "drivers/UARTSerial.h" #include "us_ticker_api.h" #include "lp_ticker_api.h" #include #include #include +#ifndef SSIZE_MAX +#define SSIZE_MAX INT_MAX +#endif #include #include #include "platform/mbed_retarget.h" @@ -66,7 +70,7 @@ static SingletonPtr _mutex; # define PREFIX(x) x #endif -#define FILE_HANDLE_RESERVED 0xFFFFFFFF +#define FILE_HANDLE_RESERVED ((FileHandle*)0xFFFFFFFF) using namespace mbed; @@ -89,7 +93,9 @@ uint32_t mbed_heap_size = 0; * we can't just return a Filehandle* from _open and instead have to * put it in a filehandles array and return the index into that array */ -static FileHandle *filehandles[OPEN_MAX]; +static FileHandle *filehandles[OPEN_MAX] = { FILE_HANDLE_RESERVED, FILE_HANDLE_RESERVED, FILE_HANDLE_RESERVED }; +static char stdio_in_prev[OPEN_MAX]; +static char stdio_out_prev[OPEN_MAX]; static SingletonPtr filehandle_mutex; namespace mbed { @@ -110,20 +116,138 @@ void remove_filehandle(FileHandle *file) { #if DEVICE_SERIAL extern int stdio_uart_inited; extern serial_t stdio_uart; -#if MBED_CONF_PLATFORM_STDIO_CONVERT_NEWLINES -static char stdio_in_prev; -static char stdio_out_prev; -#endif #endif -static void init_serial() { -#if DEVICE_SERIAL +/* Private FileHandle to implement backwards-compatible functionality of + * direct HAL serial access for default stdin/stdout/stderr. + * This is not a particularly well-behaved FileHandle for a stream, which + * is why it's not public. People should be using UARTSerial. + */ +class DirectSerial : public FileHandle { +public: + DirectSerial(PinName tx, PinName rx, int baud); + virtual ssize_t write(const void *buffer, size_t size); + virtual ssize_t read(void *buffer, size_t size); + virtual off_t seek(off_t offset, int whence = SEEK_SET) { + return -ESPIPE; + } + virtual off_t size() { + return -EINVAL; + } + virtual int isatty() { + return true; + } + virtual int close() { + return 0; + } + virtual short poll(short events) const; +}; + +DirectSerial::DirectSerial(PinName tx, PinName rx, int baud) { if (stdio_uart_inited) return; - serial_init(&stdio_uart, STDIO_UART_TX, STDIO_UART_RX); -#if MBED_CONF_PLATFORM_STDIO_BAUD_RATE - serial_baud(&stdio_uart, MBED_CONF_PLATFORM_STDIO_BAUD_RATE); -#endif + serial_init(&stdio_uart, tx, rx); + serial_baud(&stdio_uart, baud); +} + +ssize_t DirectSerial::write(const void *buffer, size_t size) { + const unsigned char *buf = static_cast(buffer); + for (size_t i = 0; i < size; i++) { + serial_putc(&stdio_uart, buf[i]); + } + return size; +} + +ssize_t DirectSerial::read(void *buffer, size_t size) { + unsigned char *buf = static_cast(buffer); + if (size == 0) { + return 0; + } + buf[0] = serial_getc(&stdio_uart); + return 1; +} + +short DirectSerial::poll(short events) const { + short revents = 0; + if ((events & POLLIN) && serial_readable(&stdio_uart)) { + revents |= POLLIN; + } + if ((events & POLLOUT) && serial_writable(&stdio_uart)) { + revents |= POLLOUT; + } + return revents; +} + +class Sink : public FileHandle { +public: + virtual ssize_t write(const void *buffer, size_t size); + virtual ssize_t read(void *buffer, size_t size); + virtual off_t seek(off_t offset, int whence = SEEK_SET) { return ESPIPE; } + virtual off_t size() { return -EINVAL; } + virtual int isatty() { return true; } + virtual int close() { return 0; } +}; + +ssize_t Sink::write(const void *buffer, size_t size) { + // Just swallow the data - this is historical non-DEVICE_SERIAL behaviour + return size; +} + +ssize_t Sink::read(void *buffer, size_t size) { + // Produce 1 zero byte - historical behaviour returned 1 without touching + // the buffer + unsigned char *buf = static_cast(buffer); + buf[0] = 0; + return 1; +} + + +MBED_WEAK FileHandle* mbed::mbed_target_override_console(int fd) +{ + return NULL; +} + +MBED_WEAK FileHandle* mbed::mbed_override_console(int fd) +{ + return NULL; +} + +static FileHandle* default_console() +{ +#if DEVICE_SERIAL +# if MBED_CONF_PLATFORM_STDIO_BUFFERED_SERIAL + static UARTSerial console(STDIO_UART_TX, STDIO_UART_RX, MBED_CONF_PLATFORM_STDIO_BAUD_RATE); +# else + static DirectSerial console(STDIO_UART_TX, STDIO_UART_RX, MBED_CONF_PLATFORM_STDIO_BAUD_RATE); +# endif +#else // DEVICE_SERIAL + static Sink console; #endif + return &console; +} + +/* Locate the default console for stdout, stdin, stderr */ +static FileHandle* get_console(int fd) { + FileHandle *fh = mbed_override_console(fd); + if (fh) { + return fh; + } + fh = mbed_target_override_console(fd); + if (fh) { + return fh; + } + return default_console(); +} + +/* Deal with the fact C library may not _open descriptors 0, 1, 2 - auto bind */ +static FileHandle* get_fhc(int fd) { + if (fd >= OPEN_MAX) { + return NULL; + } + FileHandle *fh = filehandles[fd]; + if (fh == FILE_HANDLE_RESERVED && fd < 3) { + filehandles[fd] = fh = get_console(fd); + } + return fh; } /** @@ -189,7 +313,7 @@ static int reserve_filehandle() { filehandle_mutex->unlock(); return -1; } - filehandles[fh_i] = (FileHandle*)FILE_HANDLE_RESERVED; + filehandles[fh_i] = FILE_HANDLE_RESERVED; filehandle_mutex->unlock(); return fh_i; @@ -202,6 +326,8 @@ int mbed::bind_to_fd(FileHandle *fh) { } filehandles[fh_i] = fh; + stdio_in_prev[fh_i] = 0; + stdio_out_prev[fh_i] = 0; return fh_i; } @@ -285,19 +411,21 @@ extern "C" FILEHANDLE PREFIX(_open)(const char *name, int openflags) { // Before version 5.03, we were using a patched version of microlib with proper names // This is the workaround that the microlib author suggested us static int n = 0; - if (!std::strcmp(name, ":tt")) return n++; + if (std::strcmp(name, ":tt") == 0 && n < 3) { + return n++; + } #else /* Use the posix convention that stdin,out,err are filehandles 0,1,2. */ if (std::strcmp(name, __stdin_name) == 0) { - init_serial(); - return 0; + get_fhc(STDIN_FILENO); + return STDIN_FILENO; } else if (std::strcmp(name, __stdout_name) == 0) { - init_serial(); - return 1; + get_fhc(STDOUT_FILENO); + return STDOUT_FILENO; } else if (std::strcmp(name, __stderr_name) == 0) { - init_serial(); - return 2; + get_fhc(STDERR_FILENO); + return STDERR_FILENO; } #endif #ifndef __IAR_SYSTEMS_ICC__ @@ -341,6 +469,8 @@ extern "C" int open(const char *name, int oflag, ...) { } filehandles[fh_i] = res; + stdio_in_prev[fh_i] = 0; + stdio_out_prev[fh_i] = 0; return fh_i; } @@ -350,7 +480,7 @@ extern "C" int PREFIX(_close)(FILEHANDLE fh) { } extern "C" int close(int fh) { - FileHandle* fhc = filehandles[fh]; + FileHandle* fhc = get_fhc(fh); filehandles[fh] = NULL; if (fhc == NULL) { errno = EBADF; @@ -366,15 +496,88 @@ extern "C" int close(int fh) { } } +static bool convert_crlf(int fd) { +#if MBED_CONF_PLATFORM_STDIO_CONVERT_TTY_NEWLINES + return isatty(fd); +#elif MBED_CONF_PLATFORM_STDIO_CONVERT_NEWLINES + return fd < 3 && isatty(fd); +#else + return false; +#endif +} + #if defined(__ICCARM__) extern "C" size_t __write (int fh, const unsigned char *buffer, size_t length) { #else extern "C" int PREFIX(_write)(FILEHANDLE fh, const unsigned char *buffer, unsigned int length, int mode) { #endif - ssize_t written = write(fh, buffer, length); + +#if defined(MBED_TRAP_ERRORS_ENABLED) && MBED_TRAP_ERRORS_ENABLED && defined(MBED_CONF_RTOS_PRESENT) + if (core_util_is_isr_active() || !core_util_are_interrupts_enabled()) { + error("Error - writing to a file in an ISR or critical section\r\n"); + } +#endif + + if (length > SSIZE_MAX) { + errno = EINVAL; + return -1; + } + + ssize_t slength = length; + ssize_t written = 0; + + if (convert_crlf(fh)) { + // local prev is previous in buffer during seek + // stdio_out_prev[fh] is last thing actually written + char prev = stdio_out_prev[fh]; + // Seek for '\n' without preceding '\r'; if found flush + // preceding and insert '\r'. Continue until end of input. + for (ssize_t cur = 0; cur < slength; cur++) { + if (buffer[cur] == '\n' && prev != '\r') { + ssize_t r; + // flush stuff preceding the \n + if (cur > written) { + r = write(fh, buffer + written, cur - written); + if (r < 0) { + return -1; + } + written += r; + if (written < cur) { + // For some reason, didn't write all - give up now + goto finish; + } + stdio_out_prev[fh] = prev; + } + // insert a \r now, leaving the \n still to be written + r = write(fh, "\r", 1); + if (r < 0) { + return -1; + } + if (r < 1) { + goto finish; + } + stdio_out_prev[fh] = '\r'; + } + prev = buffer[cur]; + } + } + + // Flush remaining from conversion, or the whole thing if no conversion + if (written < slength) { + ssize_t r = write(fh, buffer + written, slength - written); + if (r < 0) { + return -1; + } + written += r; + if (written > 0) { + stdio_out_prev[fh] = buffer[written - 1]; + } + } + +finish: #ifdef __ARMCC_VERSION if (written >= 0) { - return (ssize_t)length - written; + return slength - written; } else { return written; } @@ -384,46 +587,20 @@ extern "C" int PREFIX(_write)(FILEHANDLE fh, const unsigned char *buffer, unsign } extern "C" ssize_t write(int fh, const void *buf, size_t length) { - const unsigned char *buffer = static_cast(buf); - int n; // n is the number of bytes written -#if defined(MBED_TRAP_ERRORS_ENABLED) && MBED_TRAP_ERRORS_ENABLED && defined(MBED_CONF_RTOS_PRESENT) - if (core_util_is_isr_active() || !core_util_are_interrupts_enabled()) { - error("Error - writing to a file in an ISR or critical section\r\n"); + FileHandle* fhc = get_fhc(fh); + if (fhc == NULL) { + errno = EBADF; + return -1; } -#endif - FileHandle* fhc = filehandles[fh]; - if (fhc == NULL && fh < 3) { -#if DEVICE_SERIAL - if (!stdio_uart_inited) init_serial(); -#if MBED_CONF_PLATFORM_STDIO_CONVERT_NEWLINES - for (unsigned int i = 0; i < length; i++) { - if (buffer[i] == '\n' && stdio_out_prev != '\r') { - serial_putc(&stdio_uart, '\r'); - } - serial_putc(&stdio_uart, buffer[i]); - stdio_out_prev = buffer[i]; - } -#else - for (unsigned int i = 0; i < length; i++) { - serial_putc(&stdio_uart, buffer[i]); - } -#endif -#endif - n = length; + ssize_t ret = fhc->write(buf, length); + if (ret < 0) { + errno = -ret; + return -1; } else { - if (fhc == NULL) { - errno = EBADF; - return -1; - } - - n = fhc->write(buffer, length); - if (n < 0) { - errno = -n; - } + return ret; } - return n; } #if defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) @@ -432,9 +609,8 @@ extern "C" void PREFIX(_exit)(int return_code) { } extern "C" void _ttywrch(int ch) { -#if DEVICE_SERIAL - serial_putc(&stdio_uart, ch); -#endif + char c = ch; + write(STDOUT_FILENO, &c, 1); } #endif @@ -443,7 +619,50 @@ extern "C" size_t __read (int fh, unsigned char *buffer, size_t #else extern "C" int PREFIX(_read)(FILEHANDLE fh, unsigned char *buffer, unsigned int length, int mode) { #endif - ssize_t bytes_read = read(fh, buffer, length); + +#if defined(MBED_TRAP_ERRORS_ENABLED) && MBED_TRAP_ERRORS_ENABLED && defined(MBED_CONF_RTOS_PRESENT) + if (core_util_is_isr_active() || !core_util_are_interrupts_enabled()) { + error("Error - reading from a file in an ISR or critical section\r\n"); + } +#endif + + if (length > SSIZE_MAX) { + errno = EINVAL; + return -1; + } + + ssize_t bytes_read = 0; + + if (convert_crlf(fh)) { + while (true) { + char c; + ssize_t r = read(fh, &c, 1); + if (r < 0) { + return -1; + } + if (r == 0) { + return bytes_read; + } + if ((c == '\r' && stdio_in_prev[fh] != '\n') || + (c == '\n' && stdio_in_prev[fh] != '\r')) { + stdio_in_prev[fh] = c; + *buffer = '\n'; + break; + } else if ((c == '\r' && stdio_in_prev[fh] == '\n') || + (c == '\n' && stdio_in_prev[fh] == '\r')) { + stdio_in_prev[fh] = c; + continue; + } else { + stdio_in_prev[fh] = c; + *buffer = c; + break; + } + } + bytes_read = 1; + } else { + bytes_read = read(fh, buffer, length); + } + #ifdef __ARMCC_VERSION if (bytes_read < 0) { return -1; @@ -458,58 +677,23 @@ extern "C" int PREFIX(_read)(FILEHANDLE fh, unsigned char *buffer, unsigned int } extern "C" ssize_t read(int fh, void *buf, size_t length) { - unsigned char *buffer = static_cast(buf); - int n; // n is the number of bytes read -#if defined(MBED_TRAP_ERRORS_ENABLED) && MBED_TRAP_ERRORS_ENABLED && defined(MBED_CONF_RTOS_PRESENT) - if (core_util_is_isr_active() || !core_util_are_interrupts_enabled()) { - error("Error - reading from a file in an ISR or critical section\r\n"); + FileHandle* fhc = get_fhc(fh); + if (fhc == NULL) { + errno = EBADF; + return -1; } -#endif - FileHandle* fhc = filehandles[fh]; - if (fhc == NULL && fh < 3) { - // only read a character at a time from stdin -#if DEVICE_SERIAL - if (!stdio_uart_inited) init_serial(); -#if MBED_CONF_PLATFORM_STDIO_CONVERT_NEWLINES - while (true) { - char c = serial_getc(&stdio_uart); - if ((c == '\r' && stdio_in_prev != '\n') || - (c == '\n' && stdio_in_prev != '\r')) { - stdio_in_prev = c; - *buffer = '\n'; - break; - } else if ((c == '\r' && stdio_in_prev == '\n') || - (c == '\n' && stdio_in_prev == '\r')) { - stdio_in_prev = c; - // onto next character - continue; - } else { - stdio_in_prev = c; - *buffer = c; - break; - } - } -#else - *buffer = serial_getc(&stdio_uart); -#endif -#endif - n = 1; + ssize_t ret = fhc->read(buf, length); + if (ret < 0) { + errno = -ret; + return -1; } else { - if (fhc == NULL) { - errno = EBADF; - return -1; - } - - n = fhc->read(buffer, length); - if (n < 0) { - errno = -n; - } + return ret; } - return n; } + #ifdef __ARMCC_VERSION extern "C" int PREFIX(_istty)(FILEHANDLE fh) #else @@ -520,11 +704,8 @@ extern "C" int _isatty(FILEHANDLE fh) } extern "C" int isatty(int fh) { - FileHandle* fhc = filehandles[fh]; + FileHandle* fhc = get_fhc(fh); if (fhc == NULL) { - /* stdin, stdout and stderr should be tty */ - if (fh < 3) return 1; - errno = EBADF; return 0; } @@ -561,12 +742,7 @@ int _lseek(FILEHANDLE fh, int offset, int whence) } extern "C" off_t lseek(int fh, off_t offset, int whence) { - FileHandle* fhc = filehandles[fh]; - if (fhc == NULL && fh < 3) { - errno = ESPIPE; - return -1; - } - + FileHandle* fhc = get_fhc(fh); if (fhc == NULL) { errno = EBADF; return -1; @@ -587,7 +763,7 @@ extern "C" int PREFIX(_ensure)(FILEHANDLE fh) { #endif extern "C" int fsync(int fh) { - FileHandle* fhc = filehandles[fh]; + FileHandle* fhc = get_fhc(fh); if (fhc == NULL) { errno = EBADF; return -1; @@ -604,12 +780,7 @@ extern "C" int fsync(int fh) { #ifdef __ARMCC_VERSION extern "C" long PREFIX(_flen)(FILEHANDLE fh) { - FileHandle* fhc = filehandles[fh]; - if (fhc == NULL && fh < 3) { - errno = EINVAL; - return -1; - } - + FileHandle* fhc = get_fhc(fh); if (fhc == NULL) { errno = EBADF; return -1; @@ -656,12 +827,7 @@ extern "C" int _fstat(int fh, struct stat *st) { #endif extern "C" int fstat(int fh, struct stat *st) { - FileHandle* fhc = filehandles[fh]; - if (fhc == NULL && fh < 3) { - st->st_mode = S_IFCHR; - return 0; - } - + FileHandle* fhc = get_fhc(fh); if (fhc == NULL) { errno = EBADF; return -1; @@ -683,7 +849,7 @@ extern "C" int poll(struct pollfd fds[], nfds_t nfds, int timeout) for (nfds_t n = 0; n < nfds; n++) { // Underlying FileHandle poll returns POLLNVAL if given NULL, so // we don't need to take special action. - fhs[n].fh = filehandles[fds[n].fd]; + fhs[n].fh = get_fhc(fds[n].fd); fhs[n].events = fds[n].events; } int ret = poll(fhs, nfds, timeout); diff --git a/platform/mbed_retarget.h b/platform/mbed_retarget.h index f089ef37bb..e4837a7bfb 100644 --- a/platform/mbed_retarget.h +++ b/platform/mbed_retarget.h @@ -54,6 +54,10 @@ typedef unsigned int gid_t; ///< Group ID #define NAME_MAX 255 ///< Maximum size of a name in a file path +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + #include /** \addtogroup platform */ @@ -70,6 +74,46 @@ namespace mbed { class FileHandle; class DirHandle; +/** Targets may implement this to change stdin, stdout, stderr. + * + * If the application hasn't provided mbed_override_console, this is called + * to give the target a chance to specify a FileHandle for the console. + * + * If this is not provided or returns NULL, the console will be: + * - UARTSerial if configuration option "platform.stdio-buffered-serial" is + * true and the target has DEVICE_SERIAL; + * - Raw HAL serial via serial_getc and serial_putc if + * "platform.stdio-buffered-serial" is false and the target has DEVICE_SERIAL; + * - stdout/stderr will be a sink and stdin will input a stream of 0s if the + * target does not have DEVICE_SERIAL. + * + * @param fd file descriptor - STDIN_FILENO, STDOUT_FILENO or STDERR_FILENO + * @return pointer to FileHandle to override normal stream otherwise NULL + */ +FileHandle* mbed_target_override_console(int fd); + +/** Applications may implement this to change stdin, stdout, stderr. + * + * This hook gives the application a chance to specify a custom FileHandle + * for the console. + * + * If this is not provided or returns NULL, the console will be specified + * by mbed_target_override_console, else will default to serial - see + * mbed_target_override_console for more details. + * + * Example: + * @code + * FileHandle* mbed::mbed_override_console(int) { + * static UARTSerial my_serial(D0, D1); + * return &my_serial; + * } + * @endcode + + * @param fd file descriptor - STDIN_FILENO, STDOUT_FILENO or STDERR_FILENO + * @return pointer to FileHandle to override normal stream otherwise NULL + */ +FileHandle* mbed_override_console(int fd); + } typedef mbed::DirHandle DIR;