diff --git a/.vscode/settings.json b/.vscode/settings.json index 92e78ab..1f3ec26 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -65,6 +65,7 @@ "C_Cpp_Runner.useLinkTimeOptimization": false, "C_Cpp_Runner.msvcSecureNoWarnings": false, "clangd.fallbackFlags": [ - "-I${workspaceRoot}" + "-I${workspaceRoot}", + "-DDCC_TESTING=1" ] } \ No newline at end of file diff --git a/crt.s b/crt.s index ebfa2cf..1886120 100644 --- a/crt.s +++ b/crt.s @@ -290,6 +290,18 @@ DN_Packet_DCC_Send: bx lr #endif +/* libc functions */ +.global strlen +strlen: + mov r2, r0 +strlen.loop: + ldrb r1, [r0], #1 + tst r1, r1 + bne strlen.loop + sub r0, r0, r2 + sub r0, r0, #1 + bx lr + /* End */ .weak ExitFunction .weak UndefHandler, PAbortHandler, DAbortHandler diff --git a/dcc/dn_dcc_proto.c b/dcc/dn_dcc_proto.c index 9928c2c..bc86b11 100644 --- a/dcc/dn_dcc_proto.c +++ b/dcc/dn_dcc_proto.c @@ -1,4 +1,5 @@ #include "dn_dcc_proto.h" +#include #if HAVE_MINILZO #include "../minilzo/minilzo.h" #endif @@ -89,8 +90,6 @@ uint32_t DN_RLE_Matching(uint8_t *src, uint32_t size) { uint32_t offset = 0; uint32_t count = 1; - if (size < 2) return count; - while ((offset < (size - 1)) && (count < 0x7fff) && (src[offset] == src[offset + 1])) { wdog_reset(); count++; @@ -140,7 +139,6 @@ uint32_t DN_Packet_Compress(uint8_t *src, uint32_t size, uint8_t *dest) dest[outOffset + 2] = src[inOffset]; inOffset += RLE_Count & 0x7fff; - outOffset += 3; } } @@ -340,7 +338,205 @@ void DN_Packet_Read(uint8_t *dest, uint32_t size) { } } -/* 04 - Utilities */ +/* 04 - DCC Buffer */ +#if USE_DCC_WBUF +static uint32_t temp_buf; +static uint8_t temp_buf_offset; +static uint32_t checksum; + +void DN_Packet_DCC_Send_Buffer_Flush(void) { + if (!temp_buf_offset) return; + checksum = DN_Calculate_CRC32(checksum, (uint8_t *)(&temp_buf), 4); + DN_Packet_DCC_Send(temp_buf); + + temp_buf = 0; + temp_buf_offset = 0; +} + +void DN_Packet_DCC_Send_Buffer_Reset(void) { + temp_buf = 0; + temp_buf_offset = 0; + checksum = 0xffffffff; +} + +static inline void DN_Packet_DCC_Send_Buffer8(uint8_t data) { + temp_buf |= data << (temp_buf_offset << 3); + temp_buf_offset++; + + if (temp_buf_offset == 4) DN_Packet_DCC_Send_Buffer_Flush(); +} + +static inline void DN_Packet_DCC_Send_Buffer8_Multi(uint8_t *data, uint32_t size) { + for (uint32_t i = 0; i < size; i++) { + DN_Packet_DCC_Send_Buffer8(data[i]); + } +} + +static inline void DN_Packet_DCC_Send_Buffer16(uint16_t data) { + DN_Packet_DCC_Send_Buffer8(data & 0xff); + DN_Packet_DCC_Send_Buffer8(data >> 8); +} + +static inline void DN_Packet_DCC_Send_Buffer32(uint32_t data) { + DN_Packet_DCC_Send_Buffer16(data & 0xffff); + DN_Packet_DCC_Send_Buffer16(data >> 16); +} + +void DN_Packet_WriteDirectCompressed(uint8_t *src, uint32_t size) { + uint32_t MAGIC = CMD_WRITE_COMP_RLE; + uint32_t SIZE; + uint32_t inOffset = 0; + uint32_t outOffset = 8; + uint16_t RLE_Count; + uint16_t RAW_Count; + uint32_t rawInOffset; + + DN_Packet_DCC_Send_Buffer_Reset(); + + /* 01 - Compute output size */ + while (inOffset < size) { + wdog_reset(); + RLE_Count = DN_RLE_Matching(src + inOffset, size - inOffset); + + if (RLE_Count == 1) { + RAW_Count = 0; + + while (RLE_Count == 1 && inOffset < size) { + wdog_reset(); + RAW_Count++; + inOffset++; + + if (RAW_Count >= 0x7fff) break; + RLE_Count = DN_RLE_Matching(src + inOffset, size - inOffset); + } + + outOffset += 2 + RAW_Count; + } + + if (RLE_Count > 1) { + inOffset += RLE_Count; + outOffset += 3; + } + } + + SIZE = outOffset - 4; + DN_Packet_DCC_Send(outOffset >> 2); + DN_Packet_DCC_Send_Buffer32(MAGIC); + DN_Packet_DCC_Send_Buffer32(SIZE); + + /* 02 - Actually compress */ + inOffset = 0; + + while (inOffset < size) { + wdog_reset(); + RLE_Count = DN_RLE_Matching(src + inOffset, size - inOffset); + + if (RLE_Count == 1) { + RAW_Count = 0; + rawInOffset = inOffset; + + while (RLE_Count == 1 && inOffset < size) { + wdog_reset(); + RAW_Count++; + inOffset++; + + if (RAW_Count >= 0x7fff) break; + RLE_Count = DN_RLE_Matching(src + inOffset, size - inOffset); + } + + DN_Packet_DCC_Send_Buffer16(RAW_Count); + DN_Packet_DCC_Send_Buffer8_Multi(src + rawInOffset, RAW_Count); + } + + if (RLE_Count > 1) { + DN_Packet_DCC_Send_Buffer16(RLE_Count | 0x8000); + DN_Packet_DCC_Send_Buffer8(src[inOffset]); + + inOffset += RLE_Count; + } + } + + DN_Packet_DCC_Send_Buffer_Flush(); + DN_Packet_DCC_Send(checksum); +} + +void DN_Packet_WriteDirect(uint8_t *src, uint32_t size) { + uint32_t MAGIC = CMD_WRITE_COMP_NONE; + + DN_Packet_DCC_Send_Buffer_Reset(); + + DN_Packet_DCC_Send((size + 4) >> 2); + DN_Packet_DCC_Send_Buffer32(MAGIC); + + DN_Packet_DCC_Send_Buffer8_Multi(src, size); + + DN_Packet_DCC_Send_Buffer_Flush(); + DN_Packet_DCC_Send(checksum); +} +#endif +static uint32_t temp_read_buf; +static uint8_t temp_read_buf_offset; + +void DN_Packet_DCC_Read_Buffer_Reset(void) { + temp_read_buf = 0; + temp_read_buf_offset = 0; +} + +static inline uint8_t DN_Packet_DCC_Read_Buffer8(void) { + if (!temp_read_buf_offset) temp_read_buf = DN_Packet_DCC_Read(); + uint8_t temp = temp_read_buf & 0xff; + + temp_read_buf >>= 8; + temp_read_buf_offset = (temp_read_buf_offset + 1) & 3; + return temp; +} + +static inline uint16_t DN_Packet_DCC_Read_Buffer16(void) { + return DN_Packet_DCC_Read_Buffer8() | DN_Packet_DCC_Read_Buffer8() << 8; +} + +static inline uint32_t DN_Packet_DCC_Read_Buffer32(void) { + return DN_Packet_DCC_Read_Buffer16() | DN_Packet_DCC_Read_Buffer16() << 16; +} + +void DN_Packet_DCC_ReadDirectCompressed(uint8_t *dest, uint32_t size) { + uint32_t inOffset = 0; + uint32_t outOffset = 0; + + DN_Packet_DCC_Read_Buffer_Reset(); + + while (inOffset < size) { + uint16_t flag = DN_Packet_DCC_Read_Buffer16(); + uint16_t count = flag & 0x7fff; + + if (flag & 0x8000) { + inOffset += 3; + uint8_t data = DN_Packet_DCC_Read_Buffer8(); + + do { + dest[outOffset++] = data; + } while (count--); + } else { + inOffset += 2 + count; + + do { + dest[outOffset++] = DN_Packet_DCC_Read_Buffer8(); + } while (count--); + } + } +} + +void DN_Packet_DCC_ReadDirect(uint8_t *dest, uint32_t size) { + uint32_t inOffset = 0; + uint32_t outOffset = 0; + + while (inOffset < size) { + dest[outOffset++] = DN_Packet_DCC_Read(); + inOffset += 4; + } +} + +/* 05 - Utilities */ uint32_t DN_Log2(uint32_t value) { uint32_t m = 0; diff --git a/dcc/dn_dcc_proto.h b/dcc/dn_dcc_proto.h index a0f5c2a..59a06cc 100644 --- a/dcc/dn_dcc_proto.h +++ b/dcc/dn_dcc_proto.h @@ -27,6 +27,7 @@ typedef struct { uint32_t nor_cmd_set; uint32_t base_offset; MemTypes type; + char name[255]; } DCCMemory; typedef struct { @@ -102,14 +103,14 @@ typedef struct { #define DCC_ERASE_ERROR 0x24 // Erase error #define DCC_PROGRAM_ERROR 0x25 // Write error #define DCC_PROBE_ERROR 0x26 // Device probe failed -#define DCC_ASSERT_ERROR 0x27 // Ready flag timeout +#define DCC_R_ASSERT_ERROR 0x27 // Ready flag timeout during read #define DCC_READ_ERROR 0x28 // Read error #define DCC_W_ASSERT_ERROR 0x2A // Ready flag timeout during write #define DCC_E_ASSERT_ERROR 0x2B // Ready flag timeout during erase #define DCC_ROFS_ERROR 0x2D // Cannot write to read-only memory #define DCC_E_UNK_ERROR 0x2E // Unknown erase error, Please file a bug report #define DCC_WUPROT_TIMEOUT 0x2F // Write unprotect timeout -#define DCC_WUPROT_ERROR 0x30 // Write unprotect failed +#define DCC_WUPROT_ERROR 0x30 // Write unprotect failed, Attempted to write unprotect the block but still write protected afterwards. #define DCC_W_UNK_ERROR 0x31 // Unknown write error, Please file a bug report #define DCC_UNK_ERROR 0x32 // Unknown error, may happen if the flash is not completely initialized. #define DCC_FLASH_NOENT 0x37 // Flash with this ID is not probed/not found @@ -132,6 +133,12 @@ void DN_Packet_Send(uint8_t *src, uint32_t size); void DN_Packet_Send_One(uint32_t data); void DN_Packet_Read(uint8_t *dest, uint32_t size); uint32_t DN_Log2(uint32_t value); +#if USE_DCC_WBUF +void DN_Packet_WriteDirectCompressed(uint8_t *src, uint32_t size); +void DN_Packet_WriteDirect(uint8_t *src, uint32_t size); +#endif +void DN_Packet_DCC_ReadDirectCompressed(uint8_t *dest, uint32_t size); +void DN_Packet_DCC_ReadDirect(uint8_t *dest, uint32_t size); // Watchdog extern void wdog_reset(void); \ No newline at end of file diff --git a/dcc/lwprintf.c b/dcc/lwprintf.c new file mode 100644 index 0000000..39d19bb --- /dev/null +++ b/dcc/lwprintf.c @@ -0,0 +1,1191 @@ +/** + * \file lwprintf.c + * \brief Lightweight stdio manager + */ + +/* + * Copyright (c) 2024 Tilen MAJERLE + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This file is part of LwPRINTF - Lightweight stdio manager library. + * + * Author: Tilen MAJERLE + * Version: v1.0.6 + */ +#include "lwprintf.h" +#include +#include +#include + +#if LWPRINTF_CFG_OS +#include "system/lwprintf_sys.h" +#endif /* LWPRINTF_CFG_OS */ + +/* Static checks */ +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING && !LWPRINTF_CFG_SUPPORT_TYPE_FLOAT +#error "Cannot use engineering type without float!" +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING && !LWPRINTF_CFG_SUPPORT_TYPE_FLOAT */ +#if !LWPRINTF_CFG_OS && LWPRINTF_CFG_OS_MANUAL_PROTECT +#error "LWPRINTF_CFG_OS_MANUAL_PROTECT can only be used if LWPRINTF_CFG_OS is enabled" +#endif /* !LWPRINTF_CFG_OS && LWPRINTF_CFG_OS_MANUAL_PROTECT */ + +#define CHARISNUM(x) ((x) >= '0' && (x) <= '9') +#define CHARTONUM(x) ((x) - '0') +#define IS_PRINT_MODE(p) ((p)->out_fn == prv_out_fn_print) + +/* Define custom types */ +#if LWPRINTF_CFG_SUPPORT_LONG_LONG +typedef long long int float_long_t; +typedef unsigned long long int uint_maxtype_t; +typedef long long int int_maxtype_t; +#else +typedef long int float_long_t; +typedef unsigned long int uint_maxtype_t; +typedef long int int_maxtype_t; +#endif /* LWPRINTF_CFG_SUPPORT_LONG_LONG */ + +/** + * \brief Float number splitted by parts + */ +typedef struct { + float_long_t integer_part; /*!< Integer type of double number */ + double decimal_part_dbl; /*!< Decimal part of double number multiplied by 10^precision */ + float_long_t decimal_part; /*!< Decimal part of double number in integer format */ + double diff; /*!< Difference between decimal parts (double - int) */ + + short digits_cnt_integer_part; /*!< Number of digits for integer part */ + short digits_cnt_decimal_part; /*!< Number of digits for decimal part */ + short digits_cnt_decimal_part_useful; /*!< Number of useful digits to print */ +} float_num_t; + +#if LWPRINTF_CFG_SUPPORT_TYPE_FLOAT +/* Powers of 10 from beginning up to precision level */ +static const float_long_t powers_of_10[] = { + (float_long_t)1E00, (float_long_t)1E01, (float_long_t)1E02, (float_long_t)1E03, (float_long_t)1E04, + (float_long_t)1E05, (float_long_t)1E06, (float_long_t)1E07, (float_long_t)1E08, (float_long_t)1E09, +#if LWPRINTF_CFG_SUPPORT_LONG_LONG + (float_long_t)1E10, (float_long_t)1E11, (float_long_t)1E12, (float_long_t)1E13, (float_long_t)1E14, + (float_long_t)1E15, (float_long_t)1E16, (float_long_t)1E17, (float_long_t)1E18, +#endif /* LWPRINTF_CFG_SUPPORT_LONG_LONG */ +}; +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_FLOAT */ +#define FLOAT_MAX_B_ENG (powers_of_10[LWPRINTF_ARRAYSIZE(powers_of_10) - 1]) + +/** + * \brief Check for negative input number before outputting signed integers + * \param[in] pp: Parsing object + * \param[in] nnum: Number to check + */ +#define SIGNED_CHECK_NEGATIVE(pp, nnum) \ + { \ + if ((nnum) < 0) { \ + (pp)->m.flags.is_negative = 1; \ + (nnum) = -(nnum); \ + } \ + } + +/** + * \brief Forward declaration + */ +struct lwprintf_int; + +/** + * \brief Private output function declaration + * \param[in] lwi: Internal working structure + * \param[in] chr: Character to print + */ +typedef int (*prv_output_fn)(struct lwprintf_int* lwi, const char chr); + +/** + * \brief Internal structure + */ +typedef struct lwprintf_int { + lwprintf_t* lwobj; /*!< Instance handle */ + const char* fmt; /*!< Format string */ + char* const buff; /*!< Pointer to buffer when not using print option */ + const size_t buff_size; /*!< Buffer size of input buffer (when used) */ + size_t n_len; /*!< Full length of formatted text */ + prv_output_fn out_fn; /*!< Output internal function */ + uint8_t is_print_cancelled; /*!< Status if print should be cancelled */ + + /* This must all be reset every time new % is detected */ + struct { + struct { + uint8_t left_align : 1; /*!< Minus for left alignment */ + uint8_t plus : 1; /*!< Prepend + for positive numbers on the output */ + uint8_t space : 1; /*!< Prepend spaces. Not used with plus modifier */ + uint8_t zero : 1; /*!< Zero pad flag detection, add zeros if number length is less than width modifier */ + uint8_t thousands : 1; /*!< Thousands has grouping applied */ + uint8_t alt : 1; /*!< Alternate form with hash */ + uint8_t precision : 1; /*!< Precision flag has been used */ + + /* Length modified flags */ + uint8_t longlong : 2; /*!< Flag indicatin long-long number, used with 'l' (1) or 'll' (2) mode */ + uint8_t char_short : 2; /*!< Used for 'h' (1 = short) or 'hh' (2 = char) length modifier */ + uint8_t sz_t : 1; /*!< Status for size_t length integer type */ + uint8_t umax_t : 1; /*!< Status for uintmax_z length integer type */ + + uint8_t uc : 1; /*!< Uppercase flag */ + uint8_t is_negative : 1; /*!< Status if number is negative */ + uint8_t is_num_zero : 1; /*!< Status if input number is zero */ + } flags; /*!< List of flags */ + + int precision; /*!< Selected precision */ + int width; /*!< Text width indicator */ + uint8_t base; /*!< Base for number format output */ + char type; /*!< Format type */ + } m; /*!< Block that is reset on every start of format */ +} lwprintf_int_t; + +/** + * \brief Get LwPRINTF instance based on user input + * \param[in] lwi: LwPRINTF instance. + * Set to `NULL` for default instance + */ +#define LWPRINTF_GET_LWOBJ(ptr) ((ptr) != NULL ? (ptr) : (&lwprintf_default)) + +/** + * \brief LwPRINTF default structure used by application + */ +static lwprintf_t lwprintf_default; + +/** + * \brief Output function to print data + * \param[in] ptr: LwPRINTF internal instance + * \param[in] chr: Character to print + * \return `1` on success, `0` otherwise + */ +static int +prv_out_fn_print(lwprintf_int_t* lwi, const char chr) { + if (lwi->is_print_cancelled) { + return 0; + } + /*!< Send character to output */ + if (!lwi->lwobj->out_fn(chr, lwi->lwobj)) { + lwi->is_print_cancelled = 1; + } + if (chr != '\0' && !lwi->is_print_cancelled) { + ++lwi->n_len; + } + return 1; +} + +/** + * \brief Output function to generate buffer data + * \param[in] lwi: LwPRINTF internal instance + * \param[in] chr: Character to write + * \return `1` on success, `0` otherwise + */ +static int +prv_out_fn_write_buff(lwprintf_int_t* lwi, const char chr) { + if (lwi->buff_size > 0 && lwi->n_len < (lwi->buff_size - 1) && lwi->buff != NULL) { + lwi->buff[lwi->n_len] = chr; + if (chr != '\0') { + lwi->buff[lwi->n_len + 1] = '\0'; + } + } + if (chr != '\0') { + ++lwi->n_len; + } + return 1; +} + +/** + * \brief Parse number from input string + * \param[in,out] format: Input text to process + * \return Parsed number + */ +static int +prv_parse_num(const char** format) { + const char* fmt = *format; + int num = 0; + + for (; CHARISNUM(*fmt); ++fmt) { + num = (int)10 * num + CHARTONUM(*fmt); + } + *format = fmt; + return num; +} + +/** + * \brief Format data that are printed before actual value + * \param[in,out] lwi: LwPRINTF internal instance + * \param[in] buff_size: Expected buffer size of output string + * \return `1` on success, `0` otherwise + */ +static int +prv_out_str_before(lwprintf_int_t* lwi, size_t buff_size) { + /* Check for width */ + if (lwi->m.width > 0 + /* If number is negative, add negative sign or if positive and has plus sign forced */ + && (lwi->m.flags.is_negative || lwi->m.flags.plus)) { + --lwi->m.width; + } + + /* Check for alternate mode */ + if (lwi->m.flags.alt && !lwi->m.flags.is_num_zero) { + if (lwi->m.base == 8) { + if (lwi->m.width > 0) { + --lwi->m.width; + } + } else if (lwi->m.base == 16 || lwi->m.base == 2) { + if (lwi->m.width >= 2) { + lwi->m.width -= 2; + } else { + lwi->m.width = 0; + } + } + } + + /* Add negative sign (or positive in case of + flag or space in case of space flag) before when zeros are used to fill width */ + if (lwi->m.flags.zero) { + if (lwi->m.flags.is_negative) { + lwi->out_fn(lwi, '-'); + } else if (lwi->m.flags.plus) { + lwi->out_fn(lwi, '+'); + } else if (lwi->m.flags.space) { + lwi->out_fn(lwi, ' '); + } + } + + /* Check for flags output */ + if (lwi->m.flags.alt && !lwi->m.flags.is_num_zero) { + if (lwi->m.base == 8) { + lwi->out_fn(lwi, '0'); + } else if (lwi->m.base == 16) { + lwi->out_fn(lwi, '0'); + lwi->out_fn(lwi, lwi->m.flags.uc ? 'X' : 'x'); + } else if (lwi->m.base == 2) { + lwi->out_fn(lwi, '0'); + lwi->out_fn(lwi, lwi->m.flags.uc ? 'B' : 'b'); + } + } + + /* Right alignment, spaces or zeros */ + if (!lwi->m.flags.left_align && lwi->m.width > 0) { + for (size_t idx = buff_size; !lwi->m.flags.left_align && idx < (size_t)lwi->m.width; ++idx) { + lwi->out_fn(lwi, lwi->m.flags.zero ? '0' : ' '); + } + } + + /* Add negative sign here when spaces are used for width */ + if (!lwi->m.flags.zero) { + if (lwi->m.flags.is_negative) { + lwi->out_fn(lwi, '-'); + } else if (lwi->m.flags.plus) { + lwi->out_fn(lwi, '+'); + } else if (lwi->m.flags.space && buff_size >= (size_t)lwi->m.width) { + lwi->out_fn(lwi, ' '); + } + } + + return 1; +} + +/** + * \brief Format data that are printed after actual value + * \param[in,out] lwi: LwPRINTF internal instance + * \param[in] buff_size: Expected buffer size of output string + * \return `1` on success, `0` otherwise + */ +static int +prv_out_str_after(lwprintf_int_t* lwi, size_t buff_size) { + /* Left alignment, but only with spaces */ + if (lwi->m.flags.left_align) { + for (size_t idx = buff_size; idx < (size_t)lwi->m.width; ++idx) { + lwi->out_fn(lwi, ' '); + } + } + return 1; +} + +/** + * \brief Output raw string without any formatting + * \param[in,out] lwi: LwPRINTF internal instance + * \param[in] buff: Buffer string + * \param[in] buff_size: Length of buffer to output + * \return `1` on success, `0` otherwise + */ +static int +prv_out_str_raw(lwprintf_int_t* lwi, const char* buff, size_t buff_size) { + for (size_t idx = 0; idx < buff_size; ++idx) { + lwi->out_fn(lwi, buff[idx]); + } + return 1; +} + +/** + * \brief Output generated string from numbers/digits + * Paddings before and after are applied at this stage + * + * \param[in,out] lwi: LwPRINTF internal instance + * \param[in] buff: Buffer string + * \param[in] buff_size: Length of buffer to output + * \return `1` on success, `0` otherwise + */ +static int +prv_out_str(lwprintf_int_t* lwi, const char* buff, size_t buff_size) { + prv_out_str_before(lwi, buff_size); /* Implement pre-format */ + prv_out_str_raw(lwi, buff, buff_size); /* Print actual string */ + prv_out_str_after(lwi, buff_size); /* Implement post-format */ + + return 1; +} + +/** + * \brief Convert `unsigned int` to string + * \param[in,out] lwi: LwPRINTF internal instance + * \param[in] num: Number to convert to string + * \return `1` on success, `0` otherwise + */ +static int +prv_longest_unsigned_int_to_str(lwprintf_int_t* lwi, uint_maxtype_t num) { + uint_maxtype_t den, digit; + uint8_t digits_cnt; + char chr; + + /* Check if number is zero */ + lwi->m.flags.is_num_zero = (num) == 0; + if ((num) == 0) { + prv_out_str_before(lwi, 1); + lwi->out_fn(lwi, '0'); + prv_out_str_after(lwi, 1); + } else { /* Start with digits length */ + for (digits_cnt = 0, den = (num); den > 0; ++digits_cnt, den /= lwi->m.base) {} + for (den = 1; ((num) / den) >= lwi->m.base; den *= lwi->m.base) {} + + prv_out_str_before(lwi, digits_cnt); + for (; den > 0;) { + digit = (num) / den; + (num) = (num) % den; + den = den / lwi->m.base; + chr = (char)digit + (char)(digit >= 10 ? ((lwi->m.flags.uc ? 'A' : 'a') - 10) : '0'); + lwi->out_fn(lwi, chr); + } + prv_out_str_after(lwi, digits_cnt); + } + return 1; +} + +/** + * \brief Convert signed long int to string + * \param[in,out] lwi: LwPRINTF instance + * \param[in] num: Number to convert to string + * \return `1` on success, `0` otherwise + */ +static int +prv_longest_signed_int_to_str(lwprintf_int_t* lwi, signed long int num) { + SIGNED_CHECK_NEGATIVE(lwi, num); + return prv_longest_unsigned_int_to_str(lwi, num); +} + +/** + * \brief Calculate string length, limited to the maximum value. + * + * \note Use custom implementation to support potential `< C11` versions + * + * \param str: String to calculate + * \param max_n: Max number of bytes at which length is cut + * \return String length in bytes + */ +size_t +prv_strnlen(const char* str, size_t max_n) { + size_t length = 0; + + for (; *str != '\0' && length < max_n; ++length, ++str) {} + return length; +} + +#if LWPRINTF_CFG_SUPPORT_TYPE_FLOAT + +/** + * \brief Calculate necessary parameters for input number + * \param[in,out] lwi: LwPRINTF internal instance + * \param[in] n: Float number instance + * \param[in] num: Input number + * \param[in] type: Format type + */ +static void +prv_calculate_dbl_num_data(lwprintf_int_t* lwi, float_num_t* n, double num, const char type) { + memset(n, 0x00, sizeof(*n)); + + if (lwi->m.precision >= (int)LWPRINTF_ARRAYSIZE(powers_of_10)) { + lwi->m.precision = (int)LWPRINTF_ARRAYSIZE(powers_of_10) - 1; + } + + /* + * Get integer and decimal parts, both in integer formats + * + * As an example, with input number of 12.345678 and precision digits set as 4, then result is the following: + * + * integer_part = 12 -> Actual integer part of the double number + * decimal_part_dbl = 3456.78 -> Decimal part multiplied by 10^precision, keeping it in double format + * decimal_part = 3456 -> Integer part of decimal number + * diff = 0.78 -> Difference between actual decimal and integer part of decimal + * This is used for rounding of last digit (if necessary) + */ + num += 0.000000000000005; + n->integer_part = (float_long_t)num; + n->decimal_part_dbl = (num - (double)n->integer_part) * (double)powers_of_10[lwi->m.precision]; + n->decimal_part = (float_long_t)n->decimal_part_dbl; + n->diff = n->decimal_part_dbl - (double)((float_long_t)n->decimal_part); + + /* Rounding check of last digit */ + if (n->diff > 0.5) { + ++n->decimal_part; + if (n->decimal_part >= powers_of_10[lwi->m.precision]) { + n->decimal_part = 0; + ++n->integer_part; + } + } else if (n->diff < 0.5) { + /* Used in separate if, since comparing float to == will certainly result to false */ + } else { + /* Difference is exactly 0.5 */ + if (n->decimal_part == 0) { + ++n->integer_part; + } else { + ++n->decimal_part; + } + } + + /* Calculate number of digits for integer and decimal parts */ + if (n->integer_part == 0) { + n->digits_cnt_integer_part = 1; + } else { + float_long_t tmp; + for (n->digits_cnt_integer_part = 0, tmp = n->integer_part; tmp > 0; ++n->digits_cnt_integer_part, tmp /= 10) {} + } + n->digits_cnt_decimal_part = (short)lwi->m.precision; + +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + /* Calculate minimum useful digits for decimal (excl last useless zeros) */ + if (type == 'g') { + float_long_t tmp = n->decimal_part; + short adder, i; + + for (adder = 0, i = 0; tmp > 0 || i < (short)lwi->m.precision; + tmp /= 10, n->digits_cnt_decimal_part_useful += adder, ++i) { + if (adder == 0 && (tmp % 10) > 0) { + adder = 1; + } + } + } else +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + { + n->digits_cnt_decimal_part_useful = (short)lwi->m.precision; + } +} + +/** + * \brief Convert double number to string + * \param[in,out] lwi: LwPRINTF internal instance + * \param[in] num: Number to convert to string + * \return `1` on success, `0` otherwise + */ +static int +prv_double_to_str(lwprintf_int_t* lwi, double in_num) { + float_num_t dblnum; + double orig_num = in_num; + int digits_cnt, chosen_precision, i; +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + int exp_cnt = 0; +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + char def_type = lwi->m.type; + char str[LWPRINTF_CFG_SUPPORT_LONG_LONG ? 22 : 11]; + + /* + * Check for corner cases + * + * - Print "nan" if number is not valid + * - Print negative infinity if number is less than absolute minimum + * - Print negative infinity if number is less than -FLOAT_MAX_B_ENG and engineering mode is disabled + * - Print positive infinity if number is greater than absolute minimum + * - Print positive infinity if number is greater than FLOAT_MAX_B_ENG and engineering mode is disabled + * - Go to engineering mode if it is enabled and `in_num < -FLOAT_MAX_B_ENG` or `in_num > FLOAT_MAX_B_ENG` + */ + if (in_num != in_num) { + return prv_out_str(lwi, lwi->m.flags.uc ? "NAN" : "nan", 3); + } else if (in_num < -DBL_MAX +#if !LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + || in_num < -FLOAT_MAX_B_ENG +#endif /* !LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + ) { + return prv_out_str(lwi, lwi->m.flags.uc ? "-INF" : "-inf", 4); + } else if (in_num > DBL_MAX +#if !LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + || in_num > FLOAT_MAX_B_ENG +#endif /* !LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + ) { + char str[5], *s_ptr = str; + if (lwi->m.flags.plus) { + *s_ptr++ = '+'; + } + strcpy(s_ptr, lwi->m.flags.uc ? "INF" : "inf"); + return prv_out_str(lwi, str, lwi->m.flags.plus ? 4 : 3); +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + } else if ((in_num < -FLOAT_MAX_B_ENG || in_num > FLOAT_MAX_B_ENG) && def_type != 'g') { + lwi->m.type = def_type = 'e'; /* Go to engineering mode */ +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + } + + /* Check sign of the number */ + SIGNED_CHECK_NEGATIVE(lwi, in_num); + orig_num = in_num; + +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + /* Engineering mode check for number of exponents */ + if (def_type == 'e' || def_type == 'g' + || in_num > (double)(powers_of_10[LWPRINTF_ARRAYSIZE(powers_of_10) - 1])) { /* More vs what float can hold */ + if (lwi->m.type != 'g') { + lwi->m.type = 'e'; + } + + /* Normalize number to be between 0 and 1 and count decimals for exponent */ + if (in_num < 1) { + for (exp_cnt = 0; in_num < 1 && in_num > 0; in_num *= 10, --exp_cnt) {} + } else { + for (exp_cnt = 0; in_num >= 10; in_num /= 10, ++exp_cnt) {} + } + } +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + + /* Check precision data */ + chosen_precision = lwi->m.precision; /* This is default value coming from app */ + if (lwi->m.precision >= (int)LWPRINTF_ARRAYSIZE(powers_of_10)) { + lwi->m.precision = (int)LWPRINTF_ARRAYSIZE(powers_of_10) - 1; /* Limit to maximum precision */ + /* + * Precision is lower than the one selected by app (or user). + * It means that we have to append ending zeros for precision when printing data + */ + } else if (!lwi->m.flags.precision) { + lwi->m.precision = LWPRINTF_CFG_FLOAT_DEFAULT_PRECISION; /* Default precision when not used */ + chosen_precision = lwi->m.precision; /* There was no precision, update chosen precision */ + } else if (lwi->m.flags.precision && lwi->m.precision == 0) { +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + /* Precision must be set to 1 if set to 0 by default */ + if (def_type == 'g') { + lwi->m.precision = 1; + } +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + } + + /* Check if type is g and decide if final output should be 'f' or 'e' */ + /* + * For 'g/G' specifier + * + * A double argument representing a floating-point number is converted + * in style 'f' or 'e' (or in style 'F' or 'E' in the case of a 'G' conversion specifier), + * depending on the value converted and the precision. + * Let 'P' equal the precision if nonzero, '6' if the precision is omitted, or '1' if the precision is zero. + * Then, if a conversion with style 'E' would have an exponent of 'X': + * + * if 'P > X >= -4', the conversion is with style 'f' (or 'F') and precision 'P - (X + 1)'. + * otherwise, the conversion is with style 'e' (or 'E') and precision 'P - 1'. + * + * Finally, unless the '#' flag is used, + * any trailing zeros are removed from the fractional portion of the result + * and the decimal-point character is removed if there is no fractional portion remaining. + * + * A double argument representing an infinity or 'NaN' is converted in the style of an 'f' or 'F' conversion specifier. + */ + + /* Calculate data for number */ + prv_calculate_dbl_num_data(lwi, &dblnum, def_type == 'e' ? in_num : orig_num, def_type); + +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + /* Set type G */ + if (def_type == 'g') { + /* As per standard to decide level of precision */ + if (exp_cnt >= -4 && exp_cnt < lwi->m.precision) { + if (lwi->m.precision > exp_cnt) { + lwi->m.precision -= exp_cnt + 1; + chosen_precision -= exp_cnt + 1; + } else { + lwi->m.precision = 0; + chosen_precision = 0; + } + lwi->m.type = 'f'; + in_num = orig_num; + } else { + lwi->m.type = 'e'; + if (lwi->m.precision > 0) { + --lwi->m.precision; + --chosen_precision; + } + } + prv_calculate_dbl_num_data(lwi, &dblnum, in_num, def_type); + } +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + + /* Set number of digits to display */ + digits_cnt = dblnum.digits_cnt_integer_part; + if (0) { +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + } else if (def_type == 'g' && lwi->m.precision > 0) { + digits_cnt += dblnum.digits_cnt_decimal_part_useful; + if (dblnum.digits_cnt_decimal_part_useful > 0) { + ++digits_cnt; + } +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + } else { + if (chosen_precision > 0 && lwi->m.flags.precision) { + /* Add precision digits + dot separator */ + digits_cnt += chosen_precision + 1; + } + } + +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + /* Increase number of digits to display */ + if (lwi->m.type == 'e') { + /* Format is +Exxx, so add 4 or 5 characters (max is 307, min is 00 for exponent) */ + digits_cnt += 4 + (exp_cnt >= 100 || exp_cnt <= -100); + } +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + + /* Output strings */ + prv_out_str_before(lwi, digits_cnt); + + /* Output integer part of number */ + if (dblnum.integer_part == 0) { + lwi->out_fn(lwi, '0'); + } else { + for (i = 0; dblnum.integer_part > 0; dblnum.integer_part /= 10, ++i) { + str[i] = (char)'0' + (char)(dblnum.integer_part % 10); + } + for (; i > 0; --i) { + lwi->out_fn(lwi, str[i - 1]); + } + } + + /* Output decimal part */ + if (lwi->m.precision > 0) { + int x; + if (dblnum.digits_cnt_decimal_part_useful > 0) { + lwi->out_fn(lwi, '.'); + } + for (i = 0; dblnum.decimal_part > 0; dblnum.decimal_part /= 10, ++i) { + str[i] = (char)'0' + (char)(dblnum.decimal_part % 10); + } + + /* Output relevant zeros first, string to print is opposite way */ +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + if (def_type == 'g') { + /* TODO: This is to be checked */ + for (x = 0; x < (lwi->m.precision - i) && dblnum.digits_cnt_decimal_part_useful > 0; + ++x, --dblnum.digits_cnt_decimal_part_useful) { + lwi->out_fn(lwi, '0'); + } + } else +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + { + for (x = i; x < lwi->m.precision; ++x) { + lwi->out_fn(lwi, '0'); + } + } + + /* Now print string itself */ + for (; i > 0; --i) { + lwi->out_fn(lwi, str[i - 1]); +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + if (def_type == 'g' && --dblnum.digits_cnt_decimal_part_useful == 0) { + break; + } +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + } + + /* Print ending zeros if selected precision is bigger than maximum supported */ + if (def_type != 'g') { + for (; x < chosen_precision; ++x) { + lwi->out_fn(lwi, '0'); + } + } + } + +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + /* Engineering mode output, add exponent part */ + if (lwi->m.type == 'e') { + lwi->out_fn(lwi, lwi->m.flags.uc ? 'E' : 'e'); + lwi->out_fn(lwi, exp_cnt >= 0 ? '+' : '-'); + if (exp_cnt < 0) { + exp_cnt = -exp_cnt; + } + if (exp_cnt >= 100) { + lwi->out_fn(lwi, (char)'0' + (char)(exp_cnt / 100)); + exp_cnt /= 100; + } + lwi->out_fn(lwi, (char)'0' + (char)(exp_cnt / 10)); + lwi->out_fn(lwi, (char)'0' + (char)(exp_cnt % 10)); + } +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + prv_out_str_after(lwi, digits_cnt); + + return 1; +} + +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_FLOAT */ + +/** + * \brief Process format string and parse variable parameters + * \param[in,out] lwi: LwPRINTF internal instance + * \param[in] arg: Variable parameters list + * \return `1` on success, `0` otherwise + */ +static uint8_t +prv_format(lwprintf_int_t* lwi, va_list arg) { + uint8_t detected = 0; + const char* fmt = lwi->fmt; + +#if LWPRINTF_CFG_OS && !LWPRINTF_CFG_OS_MANUAL_PROTECT + if (IS_PRINT_MODE(lwi) && /* OS protection only for print */ + (!lwprintf_sys_mutex_isvalid(&lwi->lwobj->mutex) /* Invalid mutex handle */ + || !lwprintf_sys_mutex_wait(&lwi->lwobj->mutex))) { /* Cannot acquire mutex */ + return 0; + } +#endif /* LWPRINTF_CFG_OS && !LWPRINTF_CFG_OS_MANUAL_PROTECT */ + + while (fmt != NULL && *fmt != '\0') { + /* Check if we should stop processing */ + if (lwi->is_print_cancelled) { + break; + } + + /* Detect beginning */ + if (*fmt != '%') { + lwi->out_fn(lwi, *fmt); /* Output character */ + ++fmt; + continue; + } + ++fmt; + memset(&lwi->m, 0x00, sizeof(lwi->m)); /* Reset structure */ + + /* Parse format */ + /* %[flags][width][.precision][length]type */ + /* Go to https://docs.majerle.eu for more info about supported features */ + + /* Check [flags] */ + /* It can have multiple flags in any order */ + detected = 1; + do { + switch (*fmt) { + case '-': lwi->m.flags.left_align = 1; break; + case '+': lwi->m.flags.plus = 1; break; + case ' ': lwi->m.flags.space = 1; break; + case '0': lwi->m.flags.zero = 1; break; + case '\'': lwi->m.flags.thousands = 1; break; + case '#': lwi->m.flags.alt = 1; break; + default: detected = 0; break; + } + if (detected) { + ++fmt; + } + } while (detected); + + /* Check [width] */ + lwi->m.width = 0; + if (CHARISNUM(*fmt)) { /* Fixed width check */ + /* If number is negative, it has been captured from previous step (left align) */ + lwi->m.width = prv_parse_num(&fmt); /* Number from string directly */ + } else if (*fmt == '*') { /* Or variable check */ + const int w = (int)va_arg(arg, int); + if (w < 0) { + lwi->m.flags.left_align = 1; /* Negative width means left aligned */ + lwi->m.width = -w; + } else { + lwi->m.width = w; + } + ++fmt; + } + + /* Check [.precision] */ + lwi->m.precision = 0; + if (*fmt == '.') { /* Precision flag is detected */ + lwi->m.flags.precision = 1; + if (*++fmt == '*') { /* Variable check */ + const int pr = (int)va_arg(arg, int); + lwi->m.precision = pr > 0 ? pr : 0; + ++fmt; + } else if (CHARISNUM(*fmt)) { /* Directly in the string */ + lwi->m.precision = prv_parse_num(&fmt); + } + } + + /* Check [length] */ + detected = 1; + switch (*fmt) { + case 'h': + lwi->m.flags.char_short = 1; /* Single h detected */ + if (*++fmt == 'h') { /* Does it follow by another h? */ + lwi->m.flags.char_short = 2; /* Second h detected */ + ++fmt; + } + break; + case 'l': + lwi->m.flags.longlong = 1; /* Single l detected */ + if (*++fmt == 'l') { /* Does it follow by another l? */ + lwi->m.flags.longlong = 2; /* Second l detected */ + ++fmt; + } + break; + case 'L': break; + case 'z': + lwi->m.flags.sz_t = 1; /* Size T flag */ + ++fmt; + break; + case 'j': + lwi->m.flags.umax_t = 1; /* uintmax_t flag */ + ++fmt; + break; + case 't': break; + default: detected = 0; + } + + /* Check type */ + lwi->m.type = *fmt + (char)((*fmt >= 'A' && *fmt <= 'Z') ? 0x20 : 0x00); + if (*fmt >= 'A' && *fmt <= 'Z') { + lwi->m.flags.uc = 1; + } + switch (*fmt) { + case 'a': + case 'A': + /* Double in hexadecimal notation */ + (void)va_arg(arg, double); /* Read argument to ignore it and move to next one */ + prv_out_str_raw(lwi, "NaN", 3); /* Print string */ + break; + case 'c': lwi->out_fn(lwi, (char)va_arg(arg, int)); break; +#if LWPRINTF_CFG_SUPPORT_TYPE_INT + case 'd': + case 'i': { + /* Check for different length parameters */ + lwi->m.base = 10; + if (lwi->m.flags.longlong == 0) { + prv_longest_signed_int_to_str(lwi, (int_maxtype_t)va_arg(arg, signed int)); + } else if (lwi->m.flags.longlong == 1) { + prv_longest_signed_int_to_str(lwi, (int_maxtype_t)va_arg(arg, signed long int)); +#if LWPRINTF_CFG_SUPPORT_LONG_LONG + } else if (lwi->m.flags.longlong == 2) { + prv_longest_signed_int_to_str(lwi, (int_maxtype_t)va_arg(arg, signed long long int)); +#endif /* LWPRINTF_CFG_SUPPORT_LONG_LONG */ + } + break; + } + case 'b': + case 'B': + case 'o': + case 'u': + case 'x': + case 'X': + if (*fmt == 'b' || *fmt == 'B') { + lwi->m.base = 2; + } else if (*fmt == 'o') { + lwi->m.base = 8; + } else if (*fmt == 'u') { + lwi->m.base = 10; + } else if (*fmt == 'x' || *fmt == 'X') { + lwi->m.base = 16; + } + lwi->m.flags.space = 0; /* Space flag has no meaning here */ + + /* Check for different length parameters */ + if (0) { + + } else if (lwi->m.flags.sz_t) { + prv_longest_unsigned_int_to_str(lwi, (uint_maxtype_t)va_arg(arg, size_t)); + } else if (lwi->m.flags.umax_t) { + prv_longest_unsigned_int_to_str(lwi, (uint_maxtype_t)va_arg(arg, uintmax_t)); + } else if (lwi->m.flags.longlong == 0 || lwi->m.base == 2) { + uint_maxtype_t v; + switch (lwi->m.flags.char_short) { + case 2: v = (uint_maxtype_t)va_arg(arg, unsigned int); break; + case 1: v = (uint_maxtype_t)va_arg(arg, unsigned int); break; + default: v = (uint_maxtype_t)va_arg(arg, unsigned int); break; + } + prv_longest_unsigned_int_to_str(lwi, v); + } else if (lwi->m.flags.longlong == 1) { + prv_longest_unsigned_int_to_str(lwi, (uint_maxtype_t)va_arg(arg, unsigned long int)); +#if LWPRINTF_CFG_SUPPORT_LONG_LONG + } else if (lwi->m.flags.longlong == 2) { + prv_longest_unsigned_int_to_str(lwi, (uint_maxtype_t)va_arg(arg, unsigned long long int)); +#endif /* LWPRINTF_CFG_SUPPORT_LONG_LONG */ + } + break; +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_INT */ +#if LWPRINTF_CFG_SUPPORT_TYPE_STRING + case 's': { + const char* b = va_arg(arg, const char*); + if (b == NULL) { + b = "(null)"; + } + + /* Output string up to maximum buffer. If user provides lower buffer size, write will not write to it + but it will still calculate "would be" length */ + prv_out_str(lwi, b, prv_strnlen(b, lwi->m.flags.precision ? (size_t)lwi->m.precision : (SIZE_MAX))); + break; + } +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_STRING */ +#if LWPRINTF_CFG_SUPPORT_TYPE_POINTER + case 'p': { + lwi->m.base = 16; /* Go to hex format */ + lwi->m.flags.uc = 0; /* Uppercase characters */ + lwi->m.flags.zero = 1; /* Zero padding */ + lwi->m.width = + sizeof(uintptr_t) * 2; /* Number is in hex format and byte is represented with 2 letters */ + + prv_longest_unsigned_int_to_str(lwi, (uint_maxtype_t)va_arg(arg, uintptr_t)); + break; + } +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_POINTER */ +#if LWPRINTF_CFG_SUPPORT_TYPE_FLOAT + case 'f': + case 'F': +#if LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING + case 'e': + case 'E': + case 'g': + case 'G': +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING */ + /* Double number in different format. Final output depends on type of format */ + prv_double_to_str(lwi, (double)va_arg(arg, double)); + break; +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_FLOAT */ + case 'n': { + int* ptr = (void*)va_arg(arg, int*); + *ptr = (int)lwi->n_len; /* Write current length */ + + break; + } + case '%': lwi->out_fn(lwi, '%'); break; +#if LWPRINTF_CFG_SUPPORT_TYPE_BYTE_ARRAY + /* + * This is to print unsigned-char formatted pointer in hex string + * + * char arr[] = {0, 1, 2, 3, 255}; + * "%5K" would produce 00010203FF + */ + case 'k': + case 'K': { + unsigned char* ptr = + (void*)va_arg(arg, unsigned char*); /* Get input parameter as unsigned char pointer */ + int len = lwi->m.width, full_width; + uint8_t is_space = lwi->m.flags.space == 1; + + if (ptr == NULL || len == 0) { + break; + } + + lwi->m.flags.zero = 1; /* Prepend with zeros if necessary */ + lwi->m.width = 0; /* No width parameter */ + lwi->m.base = 16; /* Hex format */ + lwi->m.flags.space = 0; /* Delete any flag for space */ + + /* Full width of digits to print */ + full_width = len * (2 + (int)is_space); + if (is_space && full_width > 0) { + --full_width; /* Remove space after last number */ + } + + /* Output byte by byte w/o hex prefix */ + prv_out_str_before(lwi, full_width); + for (int i = 0; i < len; ++i, ++ptr) { + uint8_t d; + + d = (*ptr >> 0x04) & 0x0F; /* Print MSB */ + lwi->out_fn(lwi, (char)(d) + (char)(d >= 10 ? ((lwi->m.flags.uc ? 'A' : 'a') - 10) : '0')); + d = *ptr & 0x0F; /* Print LSB */ + lwi->out_fn(lwi, (char)(d) + (char)(d >= 10 ? ((lwi->m.flags.uc ? 'A' : 'a') - 10) : '0')); + + if (is_space && i < (len - 1)) { + lwi->out_fn(lwi, ' '); /* Generate space between numbers */ + } + } + prv_out_str_after(lwi, full_width); + break; + } +#endif /* LWPRINTF_CFG_SUPPORT_TYPE_BYTE_ARRAY */ + default: lwi->out_fn(lwi, *fmt); + } + ++fmt; + } + lwi->out_fn(lwi, '\0'); /* Output last zero number */ +#if LWPRINTF_CFG_OS && !LWPRINTF_CFG_OS_MANUAL_PROTECT + if (IS_PRINT_MODE(lwi)) { /* Mutex only for print operation */ + lwprintf_sys_mutex_release(&lwi->lwobj->mutex); + } +#endif /* LWPRINTF_CFG_OS && !LWPRINTF_CFG_OS_MANUAL_PROTECT */ + return 1; +} + +/** + * \brief Initialize LwPRINTF instance + * \param[in,out] lwobj: LwPRINTF working instance + * \param[in] out_fn: Output function used for print operation. + * When set to `NULL`, direct print to stream functions won't work + * and will return error if called by the application. + * Also, system mutex for this specific instance won't be called + * as system mutex isn't needed. All formatting functions (with print being an exception) + * are thread safe. Library utilizes stack-based variables + * \return `1` on success, `0` otherwise + */ +uint8_t +lwprintf_init_ex(lwprintf_t* lwobj, lwprintf_output_fn out_fn) { + LWPRINTF_GET_LWOBJ(lwobj)->out_fn = out_fn; + +#if LWPRINTF_CFG_OS + /* Create system mutex, but only if user selected to ever use print mode */ + if (out_fn != NULL + && (lwprintf_sys_mutex_isvalid(&LWPRINTF_GET_LWOBJ(lwobj)->mutex) + || !lwprintf_sys_mutex_create(&LWPRINTF_GET_LWOBJ(lwobj)->mutex))) { + return 0; + } +#endif /* LWPRINTF_CFG_OS */ + return 1; +} + +/** + * \brief Print formatted data from variable argument list to the output + * \param[in,out] lwobj: LwPRINTF instance. Set to `NULL` to use default instance + * \param[in] format: C string that contains the text to be written to output + * \param[in] arg: A value identifying a variable arguments list initialized with `va_start`. + * `va_list` is a special type defined in ``. + * \return The number of characters that would have been written if `n` had been sufficiently large, + * not counting the terminating null character. + */ +int +lwprintf_vprintf_ex(lwprintf_t* const lwobj, const char* format, va_list arg) { + lwprintf_int_t fobj = { + .lwobj = LWPRINTF_GET_LWOBJ(lwobj), + .out_fn = prv_out_fn_print, + .fmt = format, + .buff = NULL, + .buff_size = 0, + }; + /* For direct print, output function must be set by user */ + if (fobj.lwobj->out_fn == NULL) { + return 0; + } + if (prv_format(&fobj, arg)) { + return (int)fobj.n_len; + } + return 0; +} + +/** + * \brief Print formatted data to the output + * \param[in,out] lwobj: LwPRINTF instance. Set to `NULL` to use default instance + * \param[in] format: C string that contains the text to be written to output + * \param[in] ...: Optional arguments for format string + * \return The number of characters that would have been written if `n` had been sufficiently large, + * not counting the terminating null character. + */ +int +lwprintf_printf_ex(lwprintf_t* const lwobj, const char* format, ...) { + va_list valist; + int n_len; + + va_start(valist, format); + n_len = lwprintf_vprintf_ex(lwobj, format, valist); + va_end(valist); + + return n_len; +} + +/** + * \brief Write formatted data from variable argument list to sized buffer + * \param[in,out] lwobj: LwPRINTF instance. Set to `NULL` to use default instance + * \param[in] s_out: Pointer to a buffer where the resulting C-string is stored. + * The buffer should have a size of at least `n` characters + * \param[in] n_maxlen: Maximum number of bytes to be used in the buffer. + * The generated string has a length of at most `n - 1`, + * leaving space for the additional terminating null character + * \param[in] format: C string that contains a format string that follows the same specifications as format in printf + * \param[in] arg: A value identifying a variable arguments list initialized with `va_start`. + * `va_list` is a special type defined in ``. + * \return The number of characters that would have been written if `n` had been sufficiently large, + * not counting the terminating null character. + */ +int +lwprintf_vsnprintf_ex(lwprintf_t* const lwobj, char* s_out, size_t n_maxlen, const char* format, va_list arg) { + lwprintf_int_t fobj = { + .lwobj = LWPRINTF_GET_LWOBJ(lwobj), + .out_fn = prv_out_fn_write_buff, + .fmt = format, + .buff = s_out, + .buff_size = n_maxlen, + }; + if (s_out != NULL && n_maxlen > 0) { + *s_out = '\0'; + } + if (prv_format(&fobj, arg)) { + return (int)fobj.n_len; + } + return 0; +} + +/** + * \brief Write formatted data from variable argument list to sized buffer + * \param[in,out] lwobj: LwPRINTF instance. Set to `NULL` to use default instance + * \param[in] s_out: Pointer to a buffer where the resulting C-string is stored. + * The buffer should have a size of at least `n` characters + * \param[in] n_maxlen: Maximum number of bytes to be used in the buffer. + * The generated string has a length of at most `n - 1`, + * leaving space for the additional terminating null character + * \param[in] format: C string that contains a format string that follows the same specifications as format in printf + * \param[in] ...: Optional arguments for format string + * \return The number of characters that would have been written if `n` had been sufficiently large, + * not counting the terminating null character. + */ +int +lwprintf_snprintf_ex(lwprintf_t* const lwobj, char* s_out, size_t n_maxlen, const char* format, ...) { + va_list valist; + int len; + + va_start(valist, format); + len = lwprintf_vsnprintf_ex(lwobj, s_out, n_maxlen, format, valist); + va_end(valist); + + return len; +} + +#if LWPRINTF_CFG_OS_MANUAL_PROTECT || __DOXYGEN__ + +/** + * \brief Manually enable mutual exclusion + * \param[in,out] lwobj: LwPRINTF instance. Set to `NULL` to use default instance + * \return `1` if protected, `0` otherwise + */ +uint8_t +lwprintf_protect_ex(lwprintf_t* const lwobj) { + lwprintf_t* obj = LWPRINTF_GET_LWOBJ(lwobj); + return obj->out_fn != NULL && lwprintf_sys_mutex_isvalid(&obj->mutex) && lwprintf_sys_mutex_wait(&obj->mutex); +} + +/** + * \brief Manually disable mutual exclusion + * \param[in,out] lwobj: LwPRINTF instance. Set to `NULL` to use default instance + * \return `1` if protection disabled, `0` otherwise + */ +uint8_t +lwprintf_unprotect_ex(lwprintf_t* const lwobj) { + lwprintf_t* obj = LWPRINTF_GET_LWOBJ(lwobj); + return obj->out_fn != NULL && lwprintf_sys_mutex_release(&obj->mutex); +} + +#endif /* LWPRINTF_CFG_OS_MANUAL_PROTECT || __DOXYGEN__ */ diff --git a/dcc/lwprintf.h b/dcc/lwprintf.h new file mode 100644 index 0000000..75b0699 --- /dev/null +++ b/dcc/lwprintf.h @@ -0,0 +1,313 @@ +/** + * \file lwprintf.h + * \brief Lightweight stdio manager + */ + +/* + * Copyright (c) 2024 Tilen MAJERLE + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This file is part of LwPRINTF - Lightweight stdio manager library. + * + * Author: Tilen MAJERLE + * Version: v1.0.6 + */ +#ifndef LWPRINTF_HDR_H +#define LWPRINTF_HDR_H + +#include +#include +#include +#include +#include "lwprintf_opt.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * \defgroup LWPRINTF Lightweight stdio manager + * \brief Lightweight stdio manager + * \{ + */ + +/** + * \brief Unused variable macro + * \param[in] x: Unused variable + */ +#define LWPRINTF_UNUSED(x) ((void)(x)) + +/** + * \brief Calculate size of statically allocated array + * \param[in] x: Input array + * \return Number of array elements + */ +#define LWPRINTF_ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0])) + +/** + * \brief Forward declaration for LwPRINTF instance + */ +struct lwprintf; + +/** + * \brief Callback function for character output + * \param[in] ch: Character to print + * \param[in] lwobj: LwPRINTF instance + * \return `ch` on success, `0` to terminate further string processing + */ +typedef int (*lwprintf_output_fn)(int ch, struct lwprintf* lwobj); + +/** + * \brief LwPRINTF instance + */ +typedef struct lwprintf { + lwprintf_output_fn out_fn; /*!< Output function for direct print operations */ +#if LWPRINTF_CFG_OS || __DOXYGEN__ + LWPRINTF_CFG_OS_MUTEX_HANDLE mutex; /*!< OS mutex handle */ +#endif /* LWPRINTF_CFG_OS || __DOXYGEN__ */ +} lwprintf_t; + +uint8_t lwprintf_init_ex(lwprintf_t* lwobj, lwprintf_output_fn out_fn); +int lwprintf_vprintf_ex(lwprintf_t* const lwobj, const char* format, va_list arg); +int lwprintf_printf_ex(lwprintf_t* const lwobj, const char* format, ...); +int lwprintf_vsnprintf_ex(lwprintf_t* const lwobj, char* s, size_t n, const char* format, va_list arg); +int lwprintf_snprintf_ex(lwprintf_t* const lwobj, char* s, size_t n, const char* format, ...); +uint8_t lwprintf_protect_ex(lwprintf_t* const lwobj); +uint8_t lwprintf_unprotect_ex(lwprintf_t* const lwobj); + +/** + * \brief Write formatted data from variable argument list to sized buffer + * \param[in,out] lwobj: LwPRINTF instance. Set to `NULL` to use default instance + * \param[in] s: Pointer to a buffer where the resulting C-string is stored. + * The buffer should have a size of at least `n` characters + * \param[in] format: C string that contains a format string that follows the same specifications as format in printf + * \param[in] ...: Optional arguments for format string + * \return The number of characters that would have been written, + * not counting the terminating null character. + */ +#define lwprintf_sprintf_ex(lwobj, s, format, ...) lwprintf_snprintf_ex((lwobj), (s), SIZE_MAX, (format), ##__VA_ARGS__) + +/** + * \brief Initialize default LwPRINTF instance + * \param[in] out_fn: Output function used for print operation + * \return `1` on success, `0` otherwise + * \sa lwprintf_init_ex + */ +#define lwprintf_init(out_fn) lwprintf_init_ex(NULL, (out_fn)) + +/** + * \brief Print formatted data from variable argument list to the output with default LwPRINTF instance + * \param[in] format: C string that contains the text to be written to output + * \param[in] arg: A value identifying a variable arguments list initialized with `va_start`. + * `va_list` is a special type defined in ``. + * \return The number of characters that would have been written if `n` had been sufficiently large, + * not counting the terminating null character. + */ +#define lwprintf_vprintf(format, arg) lwprintf_vprintf_ex(NULL, (format), (arg)) + +/** + * \brief Print formatted data to the output with default LwPRINTF instance + * \param[in] format: C string that contains the text to be written to output + * \param[in] ...: Optional arguments for format string + * \return The number of characters that would have been written if `n` had been sufficiently large, + * not counting the terminating null character. + */ +#define lwprintf_printf(format, ...) lwprintf_printf_ex(NULL, (format), ##__VA_ARGS__) + +/** + * \brief Write formatted data from variable argument list to sized buffer with default LwPRINTF instance + * \param[in] s: Pointer to a buffer where the resulting C-string is stored. + * The buffer should have a size of at least `n` characters + * \param[in] n: Maximum number of bytes to be used in the buffer. + * The generated string has a length of at most `n - 1`, + * leaving space for the additional terminating null character + * \param[in] format: C string that contains a format string that follows the same specifications as format in printf + * \param[in] arg: A value identifying a variable arguments list initialized with `va_start`. + * `va_list` is a special type defined in ``. + * \return The number of characters that would have been written if `n` had been sufficiently large, + * not counting the terminating null character. + */ +#define lwprintf_vsnprintf(s, n, format, arg) lwprintf_vsnprintf_ex(NULL, (s), (n), (format), (arg)) + +/** + * \brief Write formatted data from variable argument list to sized buffer with default LwPRINTF instance + * \param[in] s: Pointer to a buffer where the resulting C-string is stored. + * The buffer should have a size of at least `n` characters + * \param[in] n: Maximum number of bytes to be used in the buffer. + * The generated string has a length of at most `n - 1`, + * leaving space for the additional terminating null character + * \param[in] format: C string that contains a format string that follows the same specifications as format in printf + * \param[in] ...: Optional arguments for format string + * \return The number of characters that would have been written if `n` had been sufficiently large, + * not counting the terminating null character. + */ +#define lwprintf_snprintf(s, n, format, ...) lwprintf_snprintf_ex(NULL, (s), (n), (format), ##__VA_ARGS__) + +/** + * \brief Write formatted data from variable argument list to sized buffer with default LwPRINTF instance + * \param[in] s: Pointer to a buffer where the resulting C-string is stored. + * The buffer should have a size of at least `n` characters + * \param[in] format: C string that contains a format string that follows the same specifications as format in printf + * \param[in] ...: Optional arguments for format string + * \return The number of characters that would have been written, + * not counting the terminating null character. + */ +#define lwprintf_sprintf(s, format, ...) lwprintf_sprintf_ex(NULL, (s), (format), ##__VA_ARGS__) + +/** + * \brief Manually enable mutual exclusion + * \return `1` if protected, `0` otherwise + */ +#define lwprintf_protect() lwprintf_protect_ex(NULL) + +/** + * \brief Manually disable mutual exclusion + * \return `1` if protected, `0` otherwise + */ +#define lwprintf_unprotect() lwprintf_unprotect_ex(NULL) + +#if LWPRINTF_CFG_ENABLE_SHORTNAMES || __DOXYGEN__ + +/** + * \copydoc lwprintf_printf + * \note This function is equivalent to \ref lwprintf_printf + * and available only if \ref LWPRINTF_CFG_ENABLE_SHORTNAMES is enabled + */ +#define lwprintf lwprintf_printf + +/** + * \copydoc lwprintf_vprintf + * \note This function is equivalent to \ref lwprintf_vprintf + * and available only if \ref LWPRINTF_CFG_ENABLE_SHORTNAMES is enabled + */ +#define lwvprintf lwprintf_vprintf + +/** + * \copydoc lwprintf_vsnprintf + * \note This function is equivalent to \ref lwprintf_vsnprintf + * and available only if \ref LWPRINTF_CFG_ENABLE_SHORTNAMES is enabled + */ +#define lwvsnprintf lwprintf_vsnprintf + +/** + * \copydoc lwprintf_snprintf + * \note This function is equivalent to \ref lwprintf_snprintf + * and available only if \ref LWPRINTF_CFG_ENABLE_SHORTNAMES is enabled + */ +#define lwsnprintf lwprintf_snprintf + +/** + * \copydoc lwprintf_sprintf + * \note This function is equivalent to \ref lwprintf_sprintf + * and available only if \ref LWPRINTF_CFG_ENABLE_SHORTNAMES is enabled + */ +#define lwsprintf lwprintf_sprintf + +#endif /* LWPRINTF_CFG_ENABLE_SHORTNAMES || __DOXYGEN__ */ + +#if LWPRINTF_CFG_ENABLE_STD_NAMES || __DOXYGEN__ + +/** + * \copydoc lwprintf_printf + * \note This function is equivalent to \ref lwprintf_printf + * and available only if \ref LWPRINTF_CFG_ENABLE_STD_NAMES is enabled + */ +#define printf lwprintf_printf + +/** + * \copydoc lwprintf_vprintf + * \note This function is equivalent to \ref lwprintf_vprintf + * and available only if \ref LWPRINTF_CFG_ENABLE_STD_NAMES is enabled + */ +#define vprintf lwprintf_vprintf + +/** + * \copydoc lwprintf_vsnprintf + * \note This function is equivalent to \ref lwprintf_vsnprintf + * and available only if \ref LWPRINTF_CFG_ENABLE_STD_NAMES is enabled + */ +#define vsnprintf lwprintf_vsnprintf + +/** + * \copydoc lwprintf_snprintf + * \note This function is equivalent to \ref lwprintf_snprintf + * and available only if \ref LWPRINTF_CFG_ENABLE_STD_NAMES is enabled + */ +#define snprintf lwprintf_snprintf + +/** + * \copydoc lwprintf_sprintf + * \note This function is equivalent to \ref lwprintf_sprintf + * and available only if \ref LWPRINTF_CFG_ENABLE_STD_NAMES is enabled + */ +#define sprintf lwprintf_sprintf + +#endif /* LWPRINTF_CFG_ENABLE_STD_NAMES || __DOXYGEN__ */ + +/* Debug module */ +#if !defined(NDEBUG) +/** + * \brief Debug output function + * + * Its purpose is to have a debug printout to the defined output, + * which will get disabled for the release build (when NDEBUG is defined). + * + * \note It calls \ref lwprintf_printf to execute the print + * \note Defined as empty when \ref NDEBUG is enabled + * \param[in] fmt: Format text + * \param[in] ...: Optional formatting parameters + */ +#define lwprintf_debug(fmt, ...) lwprintf_printf((fmt), ##__VA_ARGS__) +/** + * \brief Conditional debug output + * + * It prints the formatted text only if condition is true + * + * Its purpose is to have a debug printout to the defined output, + * which will get disabled for the release build (when NDEBUG is defined). + * + * \note It calls \ref lwprintf_debug to execute the print + * \note Defined as empty when \ref NDEBUG is enabled + * \param[in] cond: Condition to check before outputing the message + * \param[in] fmt: Format text + * \param[in] ...: Optional formatting parameters + */ +#define lwprintf_debug_cond(cond, fmt, ...) \ + do { \ + if ((cond)) { \ + lwprintf_debug((fmt), ##__VA_ARGS__) \ + } \ + } while (0) +#else +#define lwprintf_debug(fmt, ...) ((void)0) +#define lwprintf_debug_cond(cond, fmt, ...) ((void)0) +#endif + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LWPRINTF_HDR_H */ \ No newline at end of file diff --git a/dcc/lwprintf_opt.h b/dcc/lwprintf_opt.h new file mode 100644 index 0000000..0f27dfa --- /dev/null +++ b/dcc/lwprintf_opt.h @@ -0,0 +1,191 @@ +/** + * \file lwprintf_opt.h + * \brief LwPRINTF options + */ + +/* + * Copyright (c) 2024 Tilen MAJERLE + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This file is part of LwPRINTF - Lightweight stdio manager library. + * + * Author: Tilen MAJERLE + * Version: v1.0.6 + */ +#ifndef LWPRINTF_OPT_HDR_H +#define LWPRINTF_OPT_HDR_H + +/* Uncomment to ignore user options (or set macro in compiler flags) */ +/* #define LWPRINTF_IGNORE_USER_OPTS */ + +/* Include application options */ +#ifndef LWPRINTF_IGNORE_USER_OPTS +#include "lwprintf_opts.h" +#endif /* LWPRINTF_IGNORE_USER_OPTS */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * \defgroup LWPRINTF_OPT Configuration + * \brief LwPRINTF options + * \{ + */ + +/** + * \brief Enables `1` or disables `0` operating system support in the library + * + * \note When `LWPRINTF_CFG_OS` is enabled, user must implement functions in \ref LWPRINTF_SYS group. + */ +#ifndef LWPRINTF_CFG_OS +#define LWPRINTF_CFG_OS 0 +#endif + +/** + * \brief Mutex handle type + * + * \note This value must be set in case \ref LWPRINTF_CFG_OS is set to `1`. + * If data type is not known to compiler, include header file with + * definition before you define handle type + */ +#ifndef LWPRINTF_CFG_OS_MUTEX_HANDLE +#define LWPRINTF_CFG_OS_MUTEX_HANDLE void* +#endif + +/** + * \brief Enables `1` or disables `0` manual mutex lock. + * + * When this feature is enabled, together with \ref LWPRINTF_CFG_OS, behavior is as following: + * - System mutex is kept created during init phase + * - Calls to direct printing functions are not thread-safe by default anymore + * - Calls to sprintf (buffer functions) are kept thread-safe + * - User must manually call \ref lwprintf_protect or \ref lwprintf_protect_ex functions to protect direct printing operation + * - User must manually call \ref lwprintf_unprotect or \ref lwprintf_unprotect_ex functions to exit protected area + * + * \note If you prefer to completely disable locking mechanism with this library, + * turn off \ref LWPRINTF_CFG_OS and fully manually handle mutual exclusion for non-reentrant functions + */ +#ifndef LWPRINTF_CFG_OS_MANUAL_PROTECT +#define LWPRINTF_CFG_OS_MANUAL_PROTECT 0 +#endif + +/** + * \brief Enables `1` or disables `0` support for `long long int` type, signed or unsigned. + * + */ +#ifndef LWPRINTF_CFG_SUPPORT_LONG_LONG +#define LWPRINTF_CFG_SUPPORT_LONG_LONG 1 +#endif + +/** + * \brief Enables `1` or disables `0` support for any specifier accepting any kind of integer types. + * This is enabling `%d, %b, %u, %o, %i, %x` specifiers + * + */ +#ifndef LWPRINTF_CFG_SUPPORT_TYPE_INT +#define LWPRINTF_CFG_SUPPORT_TYPE_INT 1 +#endif + +/** + * \brief Enables `1` or disables `0` support `%p` pointer print type + * + * When enabled, architecture must support `uintptr_t` type, normally available with C11 standard + */ +#ifndef LWPRINTF_CFG_SUPPORT_TYPE_POINTER +#define LWPRINTF_CFG_SUPPORT_TYPE_POINTER 1 +#endif + +/** + * \brief Enables `1` or disables `0` support `%f` float type + * + */ +#ifndef LWPRINTF_CFG_SUPPORT_TYPE_FLOAT +#define LWPRINTF_CFG_SUPPORT_TYPE_FLOAT 1 +#endif + +/** + * \brief Enables `1` or disables `0` support for `%e` engineering output type for float numbers + * + * \note \ref LWPRINTF_CFG_SUPPORT_TYPE_FLOAT has to be enabled to use this feature + * + */ +#ifndef LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING +#define LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING 1 +#endif + +/** + * \brief Enables `1` or disables `0` support for `%s` for string output + * + */ +#ifndef LWPRINTF_CFG_SUPPORT_TYPE_STRING +#define LWPRINTF_CFG_SUPPORT_TYPE_STRING 1 +#endif + +/** + * \brief Enables `1` or disables `0` support for `%k` for hex byte array output + * + */ +#ifndef LWPRINTF_CFG_SUPPORT_TYPE_BYTE_ARRAY +#define LWPRINTF_CFG_SUPPORT_TYPE_BYTE_ARRAY 1 +#endif + +/** + * \brief Specifies default number of precision for floating number + * + * Represents number of digits to be used after comma if no precision + * is set with specifier itself + * + */ +#ifndef LWPRINTF_CFG_FLOAT_DEFAULT_PRECISION +#define LWPRINTF_CFG_FLOAT_DEFAULT_PRECISION 6 +#endif + +/** + * \brief Enables `1` or disables `0` optional short names for LwPRINTF API functions. + * + * It adds functions for default instance: `lwprintf`, `lwsnprintf` and others + */ +#ifndef LWPRINTF_CFG_ENABLE_SHORTNAMES +#define LWPRINTF_CFG_ENABLE_SHORTNAMES 1 +#endif /* LWPRINTF_CFG_ENABLE_SHORTNAMES */ + +/** + * \brief Enables `1` or disables `0` C standard API names + * + * Disabled by default not to interfere with compiler implementation. + * Application may need to remove standard C STDIO library from linkage + * to be able to properly compile LwPRINTF with this option enabled + */ +#ifndef LWPRINTF_CFG_ENABLE_STD_NAMES +#define LWPRINTF_CFG_ENABLE_STD_NAMES 0 +#endif /* LWPRINTF_CFG_ENABLE_SHORTNAMES */ + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LWPRINTF_OPT_HDR_H */ diff --git a/dcc/lwprintf_opts.h b/dcc/lwprintf_opts.h new file mode 100644 index 0000000..116a569 --- /dev/null +++ b/dcc/lwprintf_opts.h @@ -0,0 +1,46 @@ +/** + * \file lwprintf_opts_template.h + * \brief LwPRINTF configuration file + */ + +/* + * Copyright (c) 2024 Tilen MAJERLE + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This file is part of LwPRINTF - Lightweight stdio manager library. + * + * Author: Tilen MAJERLE + * Version: v1.0.6 + */ +#ifndef LWPRINTF_OPTS_HDR_H +#define LWPRINTF_OPTS_HDR_H + +/* Rename this file to "lwprintf_opts.h" for your application */ + +/* + * Open "include/lwprintf/lwprintf_opt.h" and + * copy & replace here settings you want to change values + */ +#define LWPRINTF_CFG_SUPPORT_TYPE_FLOAT 0 +#define LWPRINTF_CFG_SUPPORT_TYPE_ENGINEERING 0 + +#endif /* LWPRINTF_OPTS_HDR_H */ diff --git a/dcc/plat.h b/dcc/plat.h index 6a0fb0c..294f866 100644 --- a/dcc/plat.h +++ b/dcc/plat.h @@ -1,8 +1,11 @@ #pragma once #include #include +#include #ifdef DCC_TESTING +#include + extern void WRITE_U8(uint32_t reg, uint8_t val); extern void WRITE_U16(uint32_t reg, uint16_t val); extern void WRITE_U32(uint32_t reg, uint32_t val); @@ -12,8 +15,10 @@ extern uint16_t READ_U16(uint32_t reg); extern uint32_t READ_U32(uint32_t reg); extern void *PLAT_MEMCPY(void *dest, const void *src, size_t n); +#define INT_MEMCPY memcpy +#define PLAT_SNPRINTF snprintf #else -#include +#include "dcc/lwprintf.h" #define WRITE_U8(_reg, _val) (*((volatile uint8_t *)(_reg)) = (_val)) #define WRITE_U16(_reg, _val) (*((volatile uint16_t *)(_reg)) = (_val)) @@ -24,4 +29,6 @@ extern void *PLAT_MEMCPY(void *dest, const void *src, size_t n); #define READ_U32(_reg) (*((volatile uint32_t *)(_reg))) #define PLAT_MEMCPY memcpy +#define INT_MEMCPY memcpy +#define PLAT_SNPRINTF lwprintf_snprintf #endif \ No newline at end of file diff --git a/dcc_emu.py b/dcc_emu.py index 3eb2c12..c5f4f56 100644 --- a/dcc_emu.py +++ b/dcc_emu.py @@ -99,6 +99,7 @@ def test_arm(): mu.ctl_set_exits([0]) mu.mem_map(0x00000000, 32 * 1024 * 1024) + mu.mem_map(0x12000000, 32 * 1024 * 1024) mu.mem_map(0x03000000, 2 * 1024 * 1024) # map 2MB memory for this emulation @@ -131,14 +132,17 @@ def test_arm(): def on_write(mu, access, address, size, value, data): if DEBUG: if address <= 0x14000000: - if address == 0xaaa and value == 0x98: + if (address & 0x1ffff) == 0xaaa and value == 0x98: mu.mem_write(0x00000000, open("cfi_32mb.bin", "rb").read()) + mu.mem_write(0x12000000, open("cfi_32mb.bin", "rb").read()) - elif address == 0xaaa and value == 0x90: + elif (address & 0x1ffff) == 0xaaa and value == 0x90: mu.mem_write(0x00000000, b"\x01\x00\x7e\x22") + mu.mem_write(0x12000000, b"\x01\x00\x7e\x22") - elif address == 0x0 and value == 0xf0: + elif (address & 0x1ffff) == 0x0 and value == 0xf0: mu.mem_write(0x00000000, open("build/dumpnow.bin", "rb").read()) + mu.mem_write(0x12000000, open("build/dumpnow.bin", "rb").read()) # mu.reg_write(0x) print("Write at", hex(address), size, hex(value)) # if value == 0x98: @@ -211,9 +215,11 @@ if __name__ == '__main__': print("H:", hex(_dcc_read_host())) print("RUN") - _dcc_write_host(0x152 | 0x00000000) - _dcc_write_host(0x00120000) - _dcc_write_host(0x00000080) + + if False: + _dcc_write_host(0x152 | 0x00000000) + _dcc_write_host(0x00120000) + _dcc_write_host(0x00000080) while True: while (_dcc_read_status_host() & 2) == 0: time.sleep(0.1) diff --git a/dcc_parser.py b/dcc_parser.py new file mode 100644 index 0000000..a1fae33 --- /dev/null +++ b/dcc_parser.py @@ -0,0 +1,12 @@ +import re +import sys + +if __name__ == "__main__": + o = open(sys.argv[2], "wb") + for l in open(sys.argv[1], encoding="latin-1"): + m = re.match(r'DCC OCD -> HOST 0x([0-9a-f]*)', l.rstrip()) + if m is None: continue + print(m) + s = int(m[1], 16) + print(s) + o.write(s.to_bytes(4, "little")) \ No newline at end of file diff --git a/devices.h b/devices.h index f99da22..c248f3c 100644 --- a/devices.h +++ b/devices.h @@ -8,6 +8,7 @@ static Device devices[] = { {&nor_cfi_controller, 0x0}, + {&nor_cfi_controller, 0x12000000}, // {&nand_controller, 0x0}, {0x0, 0x0} }; \ No newline at end of file diff --git a/flash/cfi/cfi.c b/flash/cfi/cfi.c index aac7809..48551c6 100644 --- a/flash/cfi/cfi.c +++ b/flash/cfi/cfi.c @@ -2,6 +2,7 @@ #include "cfi.h" #include "dcc/dn_dcc_proto.h" +#include "dcc/plat.h" #define CFI_READ(o, x) READ_U16(o + ((x) * 2)) #define CFI_WRITE(o, x, y) WRITE_U16(o + ((x) * 2), y) @@ -87,7 +88,7 @@ DCC_RETURN CFI_Probe(DCCMemory *mem, uint32_t offset) { mem->manufacturer = (uint8_t)CFI_READ(offset, 0x00); mem->device_id = CFI_READ(offset, 0x01); uint16_t spansion_id2 = CFI_READ(offset, 0x0e); - // uint16_t spansion_id3 = CFI_READ(offset, 0x0f); + uint16_t spansion_id3 = CFI_READ(offset, 0x0f); mem->bit_width = qry.bit_width; mem->size = qry.size; @@ -131,6 +132,7 @@ DCC_RETURN CFI_Probe(DCCMemory *mem, uint32_t offset) { } } else if (mem->manufacturer == 0x01) { // Spansion if ((mem->device_id & 0xff) == 0x7e && spansion_id2 == 0x2221 && mem->size == 0x01000000) mem->size = 0x800000; + PLAT_SNPRINTF(mem->name, 255, "0x%04x/0x%04x", spansion_id2, spansion_id3); } return DCC_OK; diff --git a/flash/onenand/onenand.c b/flash/onenand/onenand.c index ec7b72e..d8261d2 100644 --- a/flash/onenand/onenand.c +++ b/flash/onenand/onenand.c @@ -2,11 +2,17 @@ #include "dcc/dn_dcc_proto.h" #include "controller/controller.h" -void OneNAND_Ctrl_Wait_Ready(DCCMemory *mem, uint16_t flag) { +int OneNAND_Ctrl_Wait_Ready(DCCMemory *mem, uint16_t flag) { // Busy assert routines + int timeout = 0x10000; + do { wdog_reset(); + if (timeout == 0) return 0; + timeout--; } while ((OneNAND_Ctrl_Reg_Read(mem, O1N_REG_INTERRUPT) & flag) != flag); + + return 1; } uint32_t OneNAND_Probe(DCCMemory *mem, uint32_t offset) { @@ -21,7 +27,7 @@ uint32_t OneNAND_Probe(DCCMemory *mem, uint32_t offset) { OneNAND_Ctrl_Reg_Write(mem, O1N_REG_INTERRUPT, 0x0); OneNAND_Ctrl_Reg_Write(mem, O1N_REG_COMMAND, O1N_CMD_HOT_RESET); - OneNAND_Ctrl_Wait_Ready(mem, 0x8000); + if (!OneNAND_Ctrl_Wait_Ready(mem, 0x8000)) return DCC_PROBE_ERROR; uint16_t mfr_id = OneNAND_Ctrl_Reg_Read(mem, O1N_REG_MANUFACTURER_ID); uint16_t dev_id = OneNAND_Ctrl_Reg_Read(mem, O1N_REG_DEVICE_ID); @@ -34,7 +40,7 @@ uint32_t OneNAND_Probe(DCCMemory *mem, uint32_t offset) { uint32_t density = 2 << ((mem->page_size == 4096 ? 4 : 3) + ((dev_id >> 4) & 0xf)); mem->size = density << 20; - mem->block_size = mem->page_size * 0x40; + mem->block_size = mem->page_size << 6; return DCC_OK; } @@ -55,7 +61,7 @@ uint32_t OneNAND_Read_Upper(DCCMemory *mem, uint8_t *page_buf, uint8_t *spare_bu OneNAND_Ctrl_Reg_Write(mem, O1N_REG_START_ADDRESS8, (page & 63) << 2); OneNAND_Ctrl_Reg_Write(mem, O1N_REG_COMMAND, O1N_CMD_READ); - OneNAND_Ctrl_Wait_Ready(mem, 0x8080); + if (!OneNAND_Ctrl_Wait_Ready(mem, 0x8080)) return DCC_R_ASSERT_ERROR; OneNAND_Ctrl_Get_Data(mem, page_buf, spare_buf, mem->page_size, mem->page_size >> 5); return DCC_OK; diff --git a/has/msm6050_8mb.has b/has/msm6050_8mb.has new file mode 100644 index 0000000..afa7ee5 Binary files /dev/null and b/has/msm6050_8mb.has differ diff --git a/main.c b/main.c index 9ebeb82..93948d6 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,5 @@ #include "dcc/dn_dcc_proto.h" -#include "flash/cfi/cfi.h" +#include "dcc/plat.h" #include "devices.h" typedef DCC_RETURN DCC_INIT_PTR(DCCMemory *mem, uint32_t offset); @@ -14,6 +14,8 @@ void *absolute_to_relative(void* ptr) { return ptr; }; extern void *absolute_to_relative(void *ptr); #endif +size_t strlen(const char *str); + // dcc code void dcc_main(uint32_t StartAddress, uint32_t PageSize) { DCCMemory mem[16] = { 0 }; @@ -36,12 +38,33 @@ void dcc_main(uint32_t StartAddress, uint32_t PageSize) { ext_mem = DCC_MEM_EXTENDED(1, mem[i].page_size, mem[i].block_size, mem[i].size >> 20); mem_has_spare[i] = 0; WRITE_EXTMEM: + if (strlen(mem[i].name)) ext_mem |= 0x80; + BUF_INIT[dcc_init_offset++] = DCC_MEM_OK | (ext_mem << 16); BUF_INIT[dcc_init_offset++] = mem[i].manufacturer | (mem[i].device_id << 16); + + if (strlen(mem[i].name)) { + int sLen = strlen(mem[i].name); + uint8_t *bufCast = (uint8_t *)BUF_INIT; + // int sPos = 0; + // uint8_t *sData = (uint8_t *)(BUF_INIT + dcc_init_offset); + // BUF_INIT[dcc_init_offset++] = sLen; + + // for (int j = 0; j < sLen; j++) { + // BUF_INIT[dcc_init_offset++] = mem[i].name[j]; + // } + + BUF_INIT[dcc_init_offset] = sLen; + INT_MEMCPY((bufCast + (dcc_init_offset << 2) + 1), mem[i].name, sLen); + + dcc_init_offset += ALIGN4(1 + sLen) >> 2; + } + BUF_INIT[dcc_init_offset++] = ext_mem; break; case MEMTYPE_NAND: + if (strlen(mem[i].name)) goto NAND_EXTMEM; BUF_INIT[dcc_init_offset++] = DCC_MEM_OK | (mem[i].page_size << 16); BUF_INIT[dcc_init_offset++] = mem[i].manufacturer | (mem[i].device_id << 16); mem_has_spare[i] = 1; @@ -50,6 +73,7 @@ void dcc_main(uint32_t StartAddress, uint32_t PageSize) { case MEMTYPE_ONENAND: case MEMTYPE_AND: case MEMTYPE_AG_AND: + NAND_EXTMEM: ext_mem = DCC_MEM_EXTENDED(0, mem[i].page_size, mem[i].block_size, mem[i].size >> 20); mem_has_spare[i] = 1; goto WRITE_EXTMEM; diff --git a/make_test.bat b/make_test.bat index a05d02e..bb593b6 100644 --- a/make_test.bat +++ b/make_test.bat @@ -1,4 +1,4 @@ @echo off clang -I . -DDCC_TESTING -DHAVE_MINILZO=1 -DHAVE_LZ4=1 -D_CRT_SECURE_NO_WARNINGS=1 test/test_rle_compress.c dcc/dn_dcc_proto.c minilzo/minilzo.c lz4/lz4_fs.c plat/default.c -o dcc_test_rle.exe clang -I . -DDCC_TESTING -DHAVE_MINILZO=1 -DHAVE_LZ4=1 -D_CRT_SECURE_NO_WARNINGS=1 test/test_dcc_writing_reading.c dcc/dn_dcc_proto.c minilzo/minilzo.c lz4/lz4_fs.c plat/default.c -o dcc_test_wr.exe -clang -I . -DDCC_TESTING -DHAVE_MINILZO=1 -DHAVE_LZ4=1 -D_CRT_SECURE_NO_WARNINGS=1 test/test_dcc_emulate.c test/test_dcc_platform.c main.c dcc/dn_dcc_proto.c dcc/bitutils.c minilzo/minilzo.c lz4/lz4_fs.c plat/default.c flash/cfi/cfi.c flash/mmap/mmap.c -o dcc_test_emu.exe \ No newline at end of file +clang -I . -DDCC_TESTING -DHAVE_MINILZO=1 -DHAVE_LZ4=1 -D_CRT_SECURE_NO_WARNINGS=1 test/test_dcc_emulate.c test/test_dcc_platform.c main.c dcc/dn_dcc_proto.c dcc/bitutils.c dcc/lwprintf.c minilzo/minilzo.c lz4/lz4_fs.c plat/default.c flash/cfi/cfi.c flash/mmap/mmap.c -o dcc_test_emu.exe \ No newline at end of file diff --git a/make_test.sh b/make_test.sh index 47f9dbe..2fea0c4 100644 --- a/make_test.sh +++ b/make_test.sh @@ -1,4 +1,4 @@ #!/bin/bash clang -I . -DDCC_TESTING -DHAVE_MINILZO=1 -DHAVE_LZ4=1 -D_CRT_SECURE_NO_WARNINGS=1 test/test_rle_compress.c dcc/dn_dcc_proto.c minilzo/minilzo.c lz4/lz4_fs.c plat/default.c -o dcc_test_rle clang -I . -DDCC_TESTING -DHAVE_MINILZO=1 -DHAVE_LZ4=1 -D_CRT_SECURE_NO_WARNINGS=1 test/test_dcc_writing_reading.c dcc/dn_dcc_proto.c minilzo/minilzo.c lz4/lz4_fs.c plat/default.c -o dcc_test_wr -clang -I . -DDCC_TESTING -DHAVE_MINILZO=1 -DHAVE_LZ4=1 -D_CRT_SECURE_NO_WARNINGS=1 test/test_dcc_emulate.c test/test_dcc_platform.c main.c dcc/dn_dcc_proto.c dcc/bitutils.c minilzo/minilzo.c lz4/lz4_fs.c plat/default.c flash/cfi/cfi.c flash/mmap/mmap.c -o dcc_test_emu \ No newline at end of file +clang -I . -DDCC_TESTING -DHAVE_MINILZO=1 -DHAVE_LZ4=1 -D_CRT_SECURE_NO_WARNINGS=1 test/test_dcc_emulate.c test/test_dcc_platform.c main.c dcc/dn_dcc_proto.c dcc/bitutils.c dcc/lwprintf.c minilzo/minilzo.c lz4/lz4_fs.c plat/default.c flash/cfi/cfi.c flash/mmap/mmap.c -o dcc_test_emu \ No newline at end of file diff --git a/makefile b/makefile index e275655..d1cce75 100644 --- a/makefile +++ b/makefile @@ -117,7 +117,7 @@ DADEFS += -DUSE_BREAKPOINTS=0 DDEFS += -DUSE_BREAKPOINTS=0 endif -SRC = main.c dcc/memory.c dcc/dn_dcc_proto.c dcc/bitutils.c plat/$(PLATFORM).c $(DEVICES) $(CONTROLLERS) $(ADD_DEPS) +SRC = main.c dcc/memory.c dcc/dn_dcc_proto.c dcc/bitutils.c dcc/lwprintf.c plat/$(PLATFORM).c $(DEVICES) $(CONTROLLERS) $(ADD_DEPS) # List ASM source files here ASRC = crt.s @@ -149,7 +149,7 @@ MCFLAGS = -mcpu=$(MCU) ASFLAGS = $(MCFLAGS) -g -gdwarf-2 -Wa,-amhls=$(<:.s=.lst) $(ADEFS) -c CPFLAGS = $(MCFLAGS) -fPIC -fPIE -I . $(OPT) -gdwarf-2 -mthumb-interwork -fomit-frame-pointer -Wall -Wstrict-prototypes -fverbose-asm -Wa,-ahlms=$(<:.c=.lst) $(DEFS) -c -LDFLAGS = $(MCFLAGS) -fPIC -fPIE -nostartfiles -nostdlib -T$(LDSCRIPT) -Wl,-Map=build/$(PROJECT).map,--cref,--no-warn-mismatch $(LIBDIR) +LDFLAGS = $(MCFLAGS) -fPIC -fPIE -nostartfiles -T$(LDSCRIPT) -Wl,-Map=build/$(PROJECT).map,--cref,--no-warn-mismatch $(LIBDIR) # Generate dependency information #CPFLAGS += -MD -MP -MF .dep/$(@F).d diff --git a/test/test_dcc_platform.c b/test/test_dcc_platform.c index a49c581..1103beb 100644 --- a/test/test_dcc_platform.c +++ b/test/test_dcc_platform.c @@ -4,7 +4,7 @@ #include #define MEMORY_BASE_OFFSET 0x0 -#define CFI_TYPE 4 +#define CFI_TYPE 2 #if CFI_TYPE == 2 // S71WS256P #define CFI_MFR_ID 0x01