Skip to content

Boot ROM and SDK Notes

M Hightower edited this page Sep 1, 2019 · 67 revisions

WIP - some information has not been verified.

Printing

There are two UARTs: 0 and 1. They are not equal. UART0 has both a TX and RX pin available. UART1 only has the TX pin available. UART1's RX pin was reassigned so that an external FLASH chip could be supported. Device programming happens with UART0. UART0 is also the default serial device when the ESP8266 boots. It always reports boot status messages at startup. UART1 is considered an optional debug output/printer port.

putc1 and putc2 are character print drivers, they are called to print a character, which may result in writing a character to the UART TX register or some other defined destination, like a memory buffer. The ESP8266 has internal/builtin drivers, They have no symbol names so I refer to them as default_putc1 and default_putc2. There is an allowance for external drivers being installed/registered, using ets_install_putc1 or ets_install_putc2. Now things get confusing. Some printf functions use the builtin putc drivers only, some use the registered drivers only. And in some, not all cases, the builtin driver will be used if one is not registered.

There are many printf like functions int the Boot ROM and SDK most if not all use ets_vprintf() to complete the task. ets_vprintf() argument list is not modeled after the POSIX vprintf() that you may be familiar with. This printf differs by having a function, character print driver, as the 1st argument. The ESP8266 RTOS SDK maps its Boot ROM function address to ets_io_vprintf() which I think is a more appropriate name.

It should be noted, that the SDK does not document much in the way of Boot ROM or SDK printf functions. There are os_printf, os_printf_plus, ets_install_putc1, system_set_os_print, uart_init, uart0_tx_buffer, uart0_rx_intr_handler, uart_div_modify, system_uart_swap, system_uart_de_swap, ...

Summary of printf functions:

ets_install_uart_printf - Registers internal default_putc1 (a UART driver) driver for putc1

ets_uart_printf - Uses internal default_putc1, ignores registered putc1.

ets_install_external_printf - Registers pre and post printf callbacks as well as optional putc2 driver.

ets_external_printf - Uses internal default_putc2, ignores registered putc2. Also calls pre and post printf callbacks.

ets_printf - Uses ets_write_char to write to the registered putc1 and/or putc2 drivers. Also uses the registered pre and post printf callbacks.

ets_vprintf

ets_install_putc1

Installs a character print driver function to print a character. This function is similar to int putchar(int c) found in Linux. To ensure that printing can be handled from an ISR, the driver must be in IRAM or ROM.

Syntax

void ets_install_putc1( void (* pfn)(char c) );

Parameter

Pointer to print driver function.

void pfn(char c);

Return Value

Type: void

Example

void nullPrint(char c) { (void)c; }
...
ets_install_putc1(nullPrint);

Remarks

  • In includes for NONOS SDK, include/osapi.h
  • Boot ROM function at 0x4000242c
  • putc1 driver stored at 0x3fffdd48, 0x3fffdd3c + 12
  • called by ets_install_uart_printf()
  • Boot ROM default putc1 driver function is at 0x40001dcc.
  • Proper handling of line ending characters '\n' and/or '\r' must be performed by the putc1 driver.
    • eg. filter out '\r' occurances and print '\n' as '\r','\n'
  • No stack frame

See Also

ets_install_putc2

Installs a character print driver function to print a character. The purpose of this driver is not clear. It looks like it is intended for non-standard device printing, maybe to a buffer. In fact, that is what the default_putc2 does. This function is similar to int putchar(int c) found in Linux. To ensure that printing can be handled from an ISR, the driver must be in IRAM or ROM.

Syntax

void ets_install_putc2( void (* pfn)(char c) )

Parameter

Pointer to print driver function.

void pfn(char c);

Return Value

Type: void

Example

struc _SNPRINTF_BUF {
  unsigned short length;
  unsigned short space; 
  char          *next_char;
  char           buffer[0];
} *snprintf_buf;

size_t constexpr buffer_size = 128;
...
snprintf_buf = (struc _SNPRINTF_BUF *)malloc(buffer_size + sizeof(struc _SNPRINTF_BUF));
if (snprintf_buf) {
  snprintf_buf->next_char = &snprintf_buf->buffer[0];
  snprintf_buf->length = snprintf_buf->space = buffer_size;
  buffered_print.space -= 1;
}

void my_putc2(char c) {
  if (snprintf_buf->space && snprintf_buf->next_char) {
    *snprintf_buf->next_char = c;
     snprintf_buf->next_char++;
     snprintf_buf->space -= 1;
  }
}
...
ets_install_putc2(my_putc2);

Remarks

  • Not included in SDK header files.
  • Boot ROM function at 0x4000248c
  • putc2 driver stored at 0x3fffdd4c, 0x3fffdd3c + 16
  • called by ets_install_external_printf()
  • Boot ROM default_putc2 driver function address is 0x400024a8.
  • No stack frame

default_putc1

Default character print driver function to print a character via UART.

Syntax

void default_putc1(char c)`

Remarks

  • Boot ROM default_putc1 driver function address 0x40001dcc.
  • pointer to registered putc1 print driver. *((void **)0x3fffdd48) 3fffdd3c + 12
  • Internal Boot ROM function, no LD mappings.
  • no stack frame

See Also

ets_install_uart_printf

Installs a UART based character print driver function, default_putc1. Print characters to UART0 or UART1.

Syntax

void *ets_install_uart_printf(void)

Remarks

  • Not included in SDK header files.
  • Boot ROM function at 0x40002438
  • Default putc1 driver 0x40001dcc
  • Calls ets_install_putc1()
  • Happens to return the address of the default_putc1 driver.
  • Stack frame

See Also

GetUartDevice

UartDevice structure is used by default_putc1 driver and updated by uart_buff_switch.

Syntax

UartDevice *GetUartDevice(void);

Return Value

Type: UartDevice * Pointer to UartDevice structure.

typedef struct {
    UartBautRate         baut_rate;     //  +0
    UartBitsNum4Char     data_bits;     //  +4
    UartExistParity      exist_parity;  //  +8
    UartParityMode       parity;        // +12
    UartStopBitsNum      stop_bits;     // +16
    UartFlowCtrl         flow_ctrl;     // +20
    typedef struct {
        uint32_t     RcvBuffSize;       // +24
        uint8_t     *pRcvMsgBuff;       // +28
        uint8_t     *pWritePos;         // +32
        uint8_t     *pReadPos;          // +38
        uint8_t      TrigLvl;           // +40 //JLU: may need to pad
        RcvMsgBuffState  BuffState;     // +44
    } RcvMsgBuff         rcv_buff;
    typedef struct {
        uint32_t     TrxBuffSize;       // +48
        uint8_t     *pTrxBuff;          // +52
    } TrxMsgBuff         trx_buff;
    RcvMsgState          rcv_state;     // +56
    int32_t              received;      // +60
    int32_t              buff_uart_no;  // +64 - indicate which uart use tx/rx buffer
} UartDevice;

Remarks

  • Not included in SDK header files; however, structure UartDevice appears in driver_lib/include/driver/uart.h and examples/esp_mqtt_proj/include/driver/uart.h
  • Boot ROM function address 0x40003f4c
  • Returns UartDevice address 0x3fffde10
  • Issue, The data presented may not be true. The hardware can be reconfigured without UartDev being updated. eg. baud rate, bits, etc. If one UART is configured differently from the other, then even a call to uart_buff_switch can result in a mismatch of parameters, aside from the one or two that it sets.
  • Not included in SDK header files.
  • no stack frame

See also

ets_putc

A wrapper for uart_tx_one_char(). Prints character ch on the selected UART. At boot, the selected UART defaults to UART0. This selection can be changed through calls to uart_buff_switch.

Syntax

void ets_putc(char ch);

Remarks

  • Not included in SDK header files
  • Boot ROM function at 0x40002be8
  • If the function is redefined to have prototype int ets_putc(int ch) the return value will be the character ch as an unsigned char upgraded to int.
  • defines a stack frame

uart_buff_switch

This API updates entries in UartDev, (0x3fffde10), which in turn are referenced by the internal default_putc1 driver and in turn uart_tx_one_char.

Use to select from UART0 or UART1 for printing.

  • 0 - Selects UART0, and clears RX FIFO.
  • 1 - selects UART1

Syntax

void uart_buff_switch(uint8_t uart_no);

Remarks

  • Not included in SDK header files.
  • Boot ROM function at 0x400038a4
  • There is only one UartDev structure so its values represent the current UART selected; however, this API only changes two entries. Most of the entries are not changed and may be stale. putc1 only uses the buff_uart_no entry, which this API sets.
  • Sets *((uint32_t *)(0x40003224 + 44)) = 0; only if (uart_no == 0)
  • Sets *((uint32_t *)(0x40003224 + 64)) = uart_no;
  • Stack frame

See Also

ets_uart_printf

Uses Boot ROM default putc1 driver. Use uart_buff_switch to select UART for printing.

Syntax

int ets_uart_printf(const char *fmt, ...);

Remarks

  • Not included in SDK header files.
  • Boot ROM function at 0x40002438
  • Default putc1 driver 0x40001dcc
  • Uses ets_vprintf()
  • Stack frame

system_set_os_print

Debug logging control. When enabled the SDK will print cryptic strings through the installed UART drivers. 1 - enables SDK debug logging 0 - disable printing debug logging

Syntax

void system_set_os_print(uint_t onoff);

Remarks

  • SDK function
  • onoff is evaluaded as 0 or not 0.
  • may not have stack frame

system_get_os_print

Returns a 1 when SDK debug printing is enabled otherwise it returns 0.

Syntax

uint8_t system_get_os_print(void);

Remarks

  • SDK function
  • No stack frame

os_printf_plus

Debug printf function. Output can be muted or enabled by calls to system_set_print(). If fmt is in flash a check is made to see if there is enough heap space to handle copying from flash to DRAM. If not up to 64 bytes of fmt are copied to a stack buffer.

Syntax

int os_printf_plus(const char *fmt, ...);

Returns

Type: int

Bug - Always returns 176.

Remarks

  • SDK IRAM function
  • SDK include file osapi.h
  • Supports fmt in flash, flash string must be align(4).
  • Alias os_printf
  • uses 176 bytes of stack space
  • Does not appear to have a return value. Always returns 176 which happens to be the stack size.
  • May call: ets_strlen, ets_write_char, ets_vprintf, ets_memcpy, pvPortMalloc, vPortFree, xPortWantedSizeAlign, system_get_free_heap_size
  • May call malloc if fmt is in flash.
  • Only handles simple print formats: %d, %i, %p, %X, %u, %c, %s according to this site; however, I don't think "%i% is really there.
  • Stack frame.

ets_write_char

A simple function that calls the registered print drive callback functions, putc1, then putc2. If their registry entry is NULL, they are skipped.

Syntax

void ets_write_char(int c);

Parameter

c - character to be printed

Return Value

Type: void

See also: ets_install_putc1, ets_install_putc2, default_putc1, default_putc2

Used by: ets_printf

Remarks

  • Boot ROM function at 0x40001da0
  • Registered driver addresses for putc1 0x3fffdd48 and putc2 0x3fffdd4c are checked for null and skipped.
  • Assumes character driver functions have void returns.
  • If the character driver functions return the character they were given then ets_write_char could be declared type int and the printed character would be returned.
  • Stack frame

ets_printf

Logic flow:

  1. Check if a putc1 or putc2 print function is installed. If not return 0.
  2. Check if a pre_printf callback is registered. If so call it.
  3. Call ets_vprintf with ets_write_char to print results.
    • ets_write_char will in turn call putc1 and putc2 if they are registered.
  4. Check if a post_printf callback is registered. If so call it.
  5. Return the return value given by ets_vprintf.

Curious comment in ESP8266_RTOS_SDK/components/esp8266/source/ets_printf.c:

Re-write ets_printf in SDK side, since ets_printf in ROM will use a global variable which address is in heap region of SDK side. If use ets_printf in ROM, this variable may be re-write when heap alloc and modification.

What I think this means: Many Boot ROM functions have a single thread perspective. There are often ROM functions that require a data area. These areas are statically defined and cannot maintain a thread-specific context. The default_putc2 function is a good example. In the Boot ROM data area, it stores a buffer pointer and a length value. The actual buffer pointer and length are supplied by the pre_printf callback function. It uses these when adding print data to the buffer. After each character is printed, the buffer pointer is advanced and the length is decremented. I see two things that can go wrong.

  1. A context switch while printing could corrupt the content of the buffer if a 2nd thread tried to print.
  2. Once finished the owning thread frees the memory; however, does not zero the length then any later printing will be writing to the buffer which is on the heap or allocated to a new thread.

I think similar issues exist for the single-threaded case where printing happens from an ISR, which in effect turns it into a two-thread case.

A lot is going on with this print option. If pre-printf and post-printf callbacks are registered they will be called. Only the registered putc1 and putc2 drivers are called on; however, if you supply NULL for the putc2 driver with ets_install_external_printf you will get the default_putc2 driver registered.

As far as I can tell ets_printf should be safe as long as you never call ets_install_external_printf. Or call on something that calls it. And, that is a good question, is anyone calling? None that I have found so far.

Syntax

int ets_printf(const char *fmt, ...);

ets_install_external_printf

void ets_install_external_printf(
   void pre_printtf_callback(char **buffer, uint16_t *length, void **context),
   void _putc2(int c), 
   void post_printf_callback(void **context));

void pre_printf_callback(char **buffer, uint16_t *length, void **context);

  • char **buffer is a pointer to the character buffer for storing the printf result.
  • uint16_t *length should be set to the length of the buffer.
  • void **context, optional, is supplied to the post-printf callback function.

void _putc2(int c); Optional - a default putc2 driver will be supplied if this is NULL.

void post_printf_callback(void **context); For a reliable system this option is required!

  • Required if pre_printf_callback is supplied, but not inforced.
  • This function MUST reset buffer pointer and length to ensure no more writing to the buffer address occurs after this function exits! Failure to do so may result in heap corruption and other disasters.

Examples

size_t constexpr print_buf_sz = 128;
char *print_buf = NULL;

void pre_printf_cb(char **pC, uint16_t *len, void **pContext) {
  if (print_buf) {
    *pC = print_buf;
  } else {
    print_buf = *pC = (char *)zalloc(print_buf_sz);
  }
  if (print_buf) {
    *pContext = (void *)pC;
    *len = print_buf_sz - 1;
  } else {
    *len = 0;         // Make sure there are no attempts to write
    *pContext = NULL;
  }
}

void post_printf_cb(void **pContext) {
  if (print_buf) {
    print_buf[print_buf_sz - est_get_printf_buf_remain_len() - 1] = '\0';
    est_reset_printf_buf_len();
  }
  if (*pContext)
    *pContext = NULL;   // Zero the buffer pointer that putc2 has access to.
}

ets_install_external_printf(pre_printf_cb, NULL, post_printf_cb);
ets_printf("this is fun? :/\n");
ets_install_external_printf(NULL, NULL, NULL); // clear external prinf

... // Do something with print_buf, 
    // eg. copy to RTC_memory so you have the last error message 
    // generated before a crash.

free(print_buf);
print_buf = NULL:

Remarks

  • calls to ets_install_external_printf must be done with IRQ disabled!
  • ets_install_external_printf(NULL, NULL, NULL); will not completely remove the effects of a previous install. The default_putc2 driver gets installed for putc2 with this attempt to uninstall. To complete the operation you should finish with ets_install_putc2(NULL);.
  • at completion, the post-printf callback, must reset the buffer length to ensure no further use is made of the buffer pointer.
  • Not included in SDK header files.

ets_external_printf

Do Not Use!

A printf function that only prints using the results from ets_install_external_printf; however, it ignores the installed putc2 driver and use the internal default_putc2 driver. It will call the registered pre and post printf callbacks.

Syntax

int ets_external_printf(...);

default_putc2

Default putc2 driver. Special driver captures output to a buffer until the buffer is full. Buffer pointer setup and length gets initialized by the pre_printf_callback function registered by a previous call to ets_install_external_printf`.

This functions is weird and does not appear to be used by the SDK. I suspect it has been grossly misunderstood :)

Syntax

int default_putc2(int c);

See Also

void est_reset_printf_buf_len est_get_printf_buf_remain_len ets_install_putc2 ets_printf

Remarks

  • Boot ROM default putc2 driver function address 0x400024a8.
  • putc1 *((void *)0x3fffdd48) 3fffdd3c + 12 registered putc1 function
  • putc2 *((void *)0x3fffdd4c) 3fffdd3c + 16 registered putc2 function
  • pre CB *((uint32_t *)0x3fffdd50) 3fffdd3c + 20 1st argument of ets_install_external_printf call
  • post CB *((uint32_t *)0x3fffdd54) 3fffdd3c + 24 3rd argument of ets_install_external_printf call
  • buffer_len *((uint16_t *)0x3fffdd58) 3fffdd3c + 28
  • buffer_ptr *((uint32_t *)0x3fffdd5c) 3fffdd3c + 32
  • post CB data *((uint32_t *)0x3fffdd60) 3fffdd3c + 36
  • no stack frame

est_reset_printf_buf_len

Reset available for write buffer length. No further writing will occur.

void est_reset_printf_buf_len(void);

Remarks

  • *((uint16_t *)0x3fffdd58) = 0; // buffer_len @ 3fffdd3c + 28
  • The misspelling with "est" vs "ets" is from the .ld file.
  • Not included in SDK header files.
  • no stack frame

est_get_printf_buf_remain_len

Get free space in buffer for putc2 to write.

Syntax

uint16_t est_get_printf_buf_remain_len(void);

Return Value

Type: unsigned short

number of bytes free in buffer

Remarks

  • return *((uint16_t *)0x3fffdd58); // buffer_len @ 3fffdd3c + 28
  • The misspelling with "est" vs "ets" is from the .ld file.
  • Not included in SDK header files.
  • no stack frame
Clone this wiki locally