| /* |
| * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <string.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| |
| #include "pico.h" |
| #if LIB_PICO_PRINTF_PICO |
| #include "pico/printf.h" |
| #endif |
| #include "pico/stdio.h" |
| #include "pico/stdio/driver.h" |
| #include "pico/time.h" |
| #if PICO_STDOUT_MUTEX |
| #include "pico/mutex.h" |
| #endif |
| |
| #if LIB_PICO_STDIO_UART |
| #include "pico/stdio_uart.h" |
| #endif |
| |
| #if LIB_PICO_STDIO_USB |
| #include "pico/stdio_usb.h" |
| #endif |
| |
| #if LIB_PICO_STDIO_SEMIHOSTING |
| #include "pico/stdio_semihosting.h" |
| #endif |
| |
| #if LIB_PICO_STDIO_RTT |
| #include "pico/stdio_rtt.h" |
| #endif |
| |
| static stdio_driver_t *drivers; |
| static stdio_driver_t *filter; |
| |
| #if PICO_STDOUT_MUTEX |
| auto_init_mutex(print_mutex); |
| |
| bool stdout_serialize_begin(void) { |
| return mutex_try_enter_block_until(&print_mutex, make_timeout_time_ms(PICO_STDIO_DEADLOCK_TIMEOUT_MS)); |
| } |
| |
| void stdout_serialize_end(void) { |
| mutex_exit(&print_mutex); |
| } |
| |
| #else |
| static bool stdout_serialize_begin(void) { |
| return true; |
| } |
| static void stdout_serialize_end(void) { |
| } |
| #endif |
| static void stdio_out_chars_no_crlf(stdio_driver_t *driver, const char *s, int len) { |
| driver->out_chars(s, len); |
| } |
| |
| static void stdio_out_chars_crlf(stdio_driver_t *driver, const char *s, int len) { |
| #if PICO_STDIO_ENABLE_CRLF_SUPPORT |
| if (!driver->crlf_enabled) { |
| driver->out_chars(s, len); |
| return; |
| } |
| int first_of_chunk = 0; |
| static const char crlf_str[] = {'\r', '\n'}; |
| for (int i = 0; i < len; i++) { |
| bool prev_char_was_cr = i > 0 ? s[i - 1] == '\r' : driver->last_ended_with_cr; |
| if (s[i] == '\n' && !prev_char_was_cr) { |
| if (i > first_of_chunk) { |
| driver->out_chars(&s[first_of_chunk], i - first_of_chunk); |
| } |
| driver->out_chars(crlf_str, 2); |
| first_of_chunk = i + 1; |
| } |
| } |
| if (first_of_chunk < len) { |
| driver->out_chars(&s[first_of_chunk], len - first_of_chunk); |
| } |
| if (len > 0) { |
| driver->last_ended_with_cr = s[len - 1] == '\r'; |
| } |
| #else |
| driver->out_chars(s, len); |
| #endif |
| } |
| |
| int stdio_put_string(const char *s, int len, bool newline, bool cr_translation) { |
| bool serialized = stdout_serialize_begin(); |
| if (!serialized) { |
| #if PICO_STDIO_IGNORE_NESTED_STDOUT |
| return 0; |
| #endif |
| } |
| if (len == -1) len = (int)strlen(s); |
| void (*out_func)(stdio_driver_t *, const char *, int) = cr_translation ? stdio_out_chars_crlf : stdio_out_chars_no_crlf; |
| for (stdio_driver_t *driver = drivers; driver; driver = driver->next) { |
| if (!driver->out_chars) continue; |
| if (filter && filter != driver) continue; |
| out_func(driver, s, len); |
| if (newline) { |
| const char c = '\n'; |
| out_func(driver, &c, 1); |
| } |
| } |
| if (serialized) { |
| stdout_serialize_end(); |
| } |
| return len; |
| } |
| |
| int stdio_get_until(char *buf, int len, absolute_time_t until) { |
| do { |
| // todo round robin might be nice on each call, but then again hopefully |
| // no source will starve the others |
| for (stdio_driver_t *driver = drivers; driver; driver = driver->next) { |
| if (filter && filter != driver) continue; |
| if (driver->in_chars) { |
| int read = driver->in_chars(buf, len); |
| if (read > 0) { |
| return read; |
| } |
| } |
| } |
| if (time_reached(until)) { |
| return PICO_ERROR_TIMEOUT; |
| } |
| // we sleep here in case the in_chars methods acquire mutexes or disable IRQs and |
| // potentially starve out what they are waiting on (have seen this with USB) |
| busy_wait_us(1); |
| } while (true); |
| } |
| |
| int stdio_putchar_raw(int c) { |
| char cc = (char)c; |
| stdio_put_string(&cc, 1, false, false); |
| return c; |
| } |
| |
| int stdio_puts_raw(const char *s) { |
| int len = (int)strlen(s); |
| stdio_put_string(s, len, true, false); |
| stdio_flush(); |
| return len; |
| } |
| |
| void stdio_set_driver_enabled(stdio_driver_t *driver, bool enable) { |
| stdio_driver_t **prev = &drivers; |
| while (*prev) { |
| if (*prev == driver) { |
| if (!enable) { |
| *prev = driver->next; |
| driver->next = NULL; |
| } |
| return; |
| } |
| prev = &(*prev)->next; |
| } |
| if (enable) { |
| *prev = driver; |
| } |
| } |
| |
| void stdio_flush(void) { |
| for (stdio_driver_t *d = drivers; d; d = d->next) { |
| if (d->out_flush) d->out_flush(); |
| } |
| } |
| |
| #if LIB_PICO_PRINTF_PICO |
| typedef struct stdio_stack_buffer { |
| int used; |
| char buf[PICO_STDIO_STACK_BUFFER_SIZE]; |
| } stdio_stack_buffer_t; |
| |
| static void stdio_stack_buffer_flush(stdio_stack_buffer_t *buffer) { |
| if (buffer->used) { |
| for (stdio_driver_t *d = drivers; d; d = d->next) { |
| if (!d->out_chars) continue; |
| if (filter && filter != d) continue; |
| stdio_out_chars_crlf(d, buffer->buf, buffer->used); |
| } |
| buffer->used = 0; |
| } |
| } |
| |
| static void stdio_buffered_printer(char c, void *arg) { |
| stdio_stack_buffer_t *buffer = (stdio_stack_buffer_t *)arg; |
| if (buffer->used == PICO_STDIO_STACK_BUFFER_SIZE) { |
| stdio_stack_buffer_flush(buffer); |
| } |
| buffer->buf[buffer->used++] = c; |
| } |
| #endif |
| |
| bool stdio_init_all(void) { |
| // todo add explicit custom, or registered although you can call stdio_enable_driver explicitly anyway |
| // These are well known ones |
| |
| bool rc = false; |
| #if LIB_PICO_STDIO_UART |
| stdio_uart_init(); |
| rc = true; |
| #endif |
| |
| #if LIB_PICO_STDIO_SEMIHOSTING |
| stdio_semihosting_init(); |
| rc = true; |
| #endif |
| |
| #if LIB_PICO_STDIO_RTT |
| stdio_rtt_init(); |
| rc = true; |
| #endif |
| |
| #if LIB_PICO_STDIO_USB |
| rc |= stdio_usb_init(); |
| #endif |
| return rc; |
| } |
| |
| bool stdio_deinit_all(void) { |
| // todo add explicit custom, or registered although you can call stdio_enable_driver explicitly anyway |
| // These are well known ones |
| |
| // First flush, to make sure everything is printed |
| stdio_flush(); |
| |
| bool rc = false; |
| #if LIB_PICO_STDIO_UART |
| stdio_uart_deinit(); |
| rc = true; |
| #endif |
| |
| #if LIB_PICO_STDIO_SEMIHOSTING |
| stdio_semihosting_deinit(); |
| rc = true; |
| #endif |
| |
| #if LIB_PICO_STDIO_RTT |
| stdio_rtt_deinit(); |
| rc = true; |
| #endif |
| |
| #if LIB_PICO_STDIO_USB |
| rc = stdio_usb_deinit(); |
| #endif |
| return rc; |
| } |
| |
| int stdio_getchar_timeout_us(uint32_t timeout_us) { |
| char buf[1]; |
| int rc = stdio_get_until(buf, sizeof(buf), make_timeout_time_us(timeout_us)); |
| if (rc < 0) return rc; |
| assert(rc); |
| return (uint8_t)buf[0]; |
| } |
| |
| void stdio_filter_driver(stdio_driver_t *driver) { |
| filter = driver; |
| } |
| |
| void stdio_set_translate_crlf(stdio_driver_t *driver, bool enabled) { |
| #if PICO_STDIO_ENABLE_CRLF_SUPPORT |
| if (enabled && !driver->crlf_enabled) { |
| driver->last_ended_with_cr = false; |
| } |
| driver->crlf_enabled = enabled; |
| #else |
| // Suppress -Wunused-parameter |
| (void)driver; |
| (void)enabled; |
| |
| panic_unsupported(); |
| #endif |
| } |
| |
| void stdio_set_chars_available_callback(void (*fn)(void*), void *param) { |
| for (stdio_driver_t *s = drivers; s; s = s->next) { |
| if (s->set_chars_available_callback) s->set_chars_available_callback(fn, param); |
| } |
| } |
| |
| #if PICO_STDIO_SHORT_CIRCUIT_CLIB_FUNCS |
| #define PRIMARY_STDIO_FUNC(x) WRAPPER_FUNC(x) |
| #else |
| #define PRIMARY_STDIO_FUNC(x) stdio_ ## x |
| #endif |
| |
| int PRIMARY_STDIO_FUNC(getchar)(void) { |
| char buf[1]; |
| int len = stdio_get_until(buf, 1, at_the_end_of_time); |
| if (len < 0) return len; |
| assert(len == 1); |
| return (uint8_t)buf[0]; |
| } |
| |
| int PRIMARY_STDIO_FUNC(putchar)(int c) { |
| char cc = (char)c; |
| stdio_put_string(&cc, 1, false, true); |
| return c; |
| } |
| |
| int PRIMARY_STDIO_FUNC(puts)(const char *s) { |
| int len = (int)strlen(s); |
| stdio_put_string(s, len, true, true); |
| stdio_flush(); |
| return len; |
| } |
| |
| int REAL_FUNC(vprintf)(const char *format, va_list va); |
| |
| int PRIMARY_STDIO_FUNC(vprintf)(const char *format, va_list va) { |
| bool serialzed = stdout_serialize_begin(); |
| if (!serialzed) { |
| #if PICO_STDIO_IGNORE_NESTED_STDOUT |
| return 0; |
| #endif |
| } |
| int ret; |
| #if LIB_PICO_PRINTF_PICO |
| struct stdio_stack_buffer buffer; |
| buffer.used = 0; |
| ret = vfctprintf(stdio_buffered_printer, &buffer, format, va); |
| stdio_stack_buffer_flush(&buffer); |
| stdio_flush(); |
| #elif LIB_PICO_PRINTF_NONE |
| ((void)format); |
| ((void)va); |
| extern void printf_none_assert(void); |
| printf_none_assert(); |
| ret = 0; |
| #else |
| ret = REAL_FUNC(vprintf)(format, va); |
| #endif |
| if (serialzed) { |
| stdout_serialize_end(); |
| } |
| return ret; |
| } |
| |
| int __printflike(1, 0) PRIMARY_STDIO_FUNC(printf)(const char* format, ...) |
| { |
| va_list va; |
| va_start(va, format); |
| int ret = vprintf(format, va); |
| va_end(va); |
| return ret; |
| } |
| |
| #if PICO_STDIO_SHORT_CIRCUIT_CLIB_FUNCS |
| // define the stdio_ versions to be the same as our wrappers |
| int stdio_getchar(void) __attribute__((alias(__XSTRING(WRAPPER_FUNC(getchar))))); |
| int stdio_putchar(int) __attribute__((alias(__XSTRING(WRAPPER_FUNC(putchar))))); |
| int stdio_puts(const char *s) __attribute__((alias(__XSTRING(WRAPPER_FUNC(puts))))); |
| int stdio_vprintf(const char *format, va_list va) __attribute__((alias(__XSTRING(WRAPPER_FUNC(vprintf))))); |
| int __printflike(1, 0) stdio_printf(const char* format, ...) __attribute__((alias(__XSTRING(WRAPPER_FUNC(printf))))); |
| #else |
| // todo there is no easy way to avoid the wrapper functions since they are in the CMake, so lets just forward for now |
| |
| int REAL_FUNC(getchar)(void); |
| int REAL_FUNC(putchar)(int); |
| int REAL_FUNC(puts)(const char *s); |
| int __printflike(1, 0) REAL_FUNC(printf)(const char* format, ...); |
| |
| int WRAPPER_FUNC(getchar)(void) { |
| return REAL_FUNC(getchar)(); |
| } |
| int WRAPPER_FUNC(putchar)(int c) { |
| return REAL_FUNC(putchar)(c); |
| } |
| int WRAPPER_FUNC(puts)(const char *s) { |
| return REAL_FUNC(puts)(s); |
| } |
| int WRAPPER_FUNC(vprintf)(const char *format, va_list va) { |
| return REAL_FUNC(vprintf)(format, va); |
| } |
| int __printflike(1, 0) WRAPPER_FUNC(printf)(const char* format, ...) { |
| va_list va; |
| va_start(va, format); |
| int ret = REAL_FUNC(vprintf)(format, va); |
| va_end(va); |
| return ret; |
| } |
| #endif |
| |
| |