Boot ROM and SDK Notes - mhightower83/Arduino-ESP8266-misc GitHub Wiki

WIP - some information has not been verified.

Booting

  • Boot ROM Espressif/Xtensa code
  • main(); eboot.bin Arduino ESP8266 Boot loader
  • app_entry(); in Arduino ESP8266 Core, handle preliminary setup before calling the SDK Start entry point.
    • app_entry_custom;
    • app_entry_redefinable(); Used to create option to reclaim 4K of Heap/RAM
  • call_user_start() NONOS SDK entry point
  • Somehow the "C" library init code that zeros things gets called.
  • user_init(); Arduino ESP8266 Core, this is the start of the Primary Arduino ESP8266 Core initialization.
    • By this time the Boot ROM flashchip structure has been updated by the SDK with the boot image header flash size.
      • If boot image is configured for greater than 4MByte, calls to Boot ROM functions that write/erase flash addresses above 4MByte will fail, without this update.
    • More initialization
    • Additional calls finish the "C"/"C++" Initialization.

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_install_external_printf - Registers pre and post printf callbacks as well as an optional putc2 driver, otherwise the internal default_putc2 (print to buffer) driver is installed. dangerous

ets_vprintf - The foundation for all the Boot ROM printf functions.

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_uart_printf - Uses internal default_putc1, ignores registered putc1.

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

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

  • Prototype in 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. Has special handling for \n and \r. When called with '\r` it is not printed; however, when called with '\n' a '\r' and '\n' are printed.

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

int ets_putc(char ch);

Return Value

Type: int Always 0

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.
  • Return value was that returned by uart_tx_one_char
  • defines a stack frame

uart_buff_switch

Use to select a port for printing: UART0 or UART1

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

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

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

uart_tx_one_char

An internal Boot ROM function that handles printing to one of the two UARTs. uart_tx_one_char uses data stored at UartDevice.buff_uart_no to select the UART. UartDevice.buff_uart_no is set by a previous call to uart_buff_switch.

This function is called by many higher level functions for printing.

Return value

Type: int, Always 0

Remarks

  • At boot UART0 is selected.

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(). When fmt is in flash, os_printf_plus will work best with fmt at 64 characters or less. 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, fmt is truncated to 64 characters and 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, %D, %u, %U %c, %C, %s, %S, %x, %X, %p. No support for float, %f. And %S does not support PROGMEM.
  • 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(char c);

Parameter

c

Type: char

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

Misc

rtc_get_reset_reason

Returns, the reason for the reset as reported by ROM code. Note, the reason values are different from those returned by the NONOS SDK. Also, the reset reason will not change after a software WDT reset or software reset. For example, if the first reset was caused by a power-on boot, the reset reason is 1. After a software reset, the reset reason will still be 1.

Syntax

extern "C" uint32_t rtc_get_reset_reason(void);

Return Value

Type: uint32_t

typedef enum {
    NO_MEAN                =  0,    /* Undefined */
    POWERON_RESET          =  1,    /* Power on boot */ /*<1, Vbat power on reset*/
    EXT_RESET              =  2,    /* External reset or wake-up from Deep-sleep */ /**<2, external system reset*/
    SW_RESET               =  3,    /**/ /**<3, Software reset digital core*/
    OWDT_RESET             =  4,    /* Hardware WDT reset *//**<4, Legacy watch dog reset digital core*/
    DEEPSLEEP_RESET        =  5,    /**//**<5, Deep Sleep reset digital core*/
    SDIO_RESET             =  6,    /**//**<6, Reset by SLC module, reset digital core*/
} RTC_RESET_REASON;

See Also

  • "ESP8266 Reset Causes and Common Fatal Exception Causes" - 1.1. Identifying Reset Cause in ROM Code

Remarks

  • Boot ROM function.
  • Prototype from ESP8266_RTOS_SDK
  • Gets the value from RTC memory. *(0x60000714) & 0x0F

ets_delay_us

This function delays for a specified period in microseconds. For a 160MHz CPU clock rate, the maximum delay value is 26,843,545, around 26 seconds. The delay value is converted to CPU cycles. Then the function spins in a tight loop, referencing the ESP8266's CCOUNT register, waiting for it to equal or exceed the difference.

Syntax

void ets_delay_us(uint32_t usec);

Parameter

Type: uint32_t

Return Value

Type: void

Example

ets_delay_us(1000);

Remarks

  • none

See Also

  • none