From 16c831fffaf95027aec0e84c03874304d84ae1c9 Mon Sep 17 00:00:00 2001 From: doragasu Date: Wed, 9 Mar 2016 17:18:51 +0100 Subject: [PATCH 01/44] Added sntp support and example --- examples/sntp/Makefile | 6 + examples/sntp/sntp_example.c | 108 +++++ extras/sntp/component.mk | 8 + extras/sntp/sntp.c | 748 +++++++++++++++++++++++++++++++++++ extras/sntp/sntp.h | 52 +++ extras/sntp/sntp_fun.c | 105 +++++ 6 files changed, 1027 insertions(+) create mode 100644 examples/sntp/Makefile create mode 100644 examples/sntp/sntp_example.c create mode 100644 extras/sntp/component.mk create mode 100644 extras/sntp/sntp.c create mode 100644 extras/sntp/sntp.h create mode 100644 extras/sntp/sntp_fun.c diff --git a/examples/sntp/Makefile b/examples/sntp/Makefile new file mode 100644 index 0000000..5d55b10 --- /dev/null +++ b/examples/sntp/Makefile @@ -0,0 +1,6 @@ +# Makefile for the sntp_example program + +PROGRAM=sntp_example +EXTRA_COMPONENTS = extras/sntp + +include ../../common.mk diff --git a/examples/sntp/sntp_example.c b/examples/sntp/sntp_example.c new file mode 100644 index 0000000..6083f96 --- /dev/null +++ b/examples/sntp/sntp_example.c @@ -0,0 +1,108 @@ +/* + * Test code for SNTP on esp-open-rtos. + * + * Jesus Alonso (doragasu) + */ +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +// Add extras/sntp component to makefile for this include to work +#include + +#define SNTP_SERVERS "0.pool.ntp.org", "1.pool.ntp.org", \ + "2.pool.ntp.org", "3.pool.ntp.org" + +#define vTaskDelayMs(ms) vTaskDelay((ms)/portTICK_RATE_MS) +#define UNUSED_ARG(x) (void)x + +// Use GPIO pin 2 +const int gpio_frc2 = 2; +// 1 Hz blink frequency +const int freq_frc2 = 1; + +void SntpTsk(void *pvParameters) +{ + char *servers[] = {SNTP_SERVERS}; + UNUSED_ARG(pvParameters); + + // Wait until we have joined AP and are assigned an IP + while (sdk_wifi_station_get_connect_status() != STATION_GOT_IP) { + vTaskDelayMs(100); + } + + // Start SNTP + printf("Starting SNTP... "); + sntp_set_update_delay(1*60000); + sntp_initialize(1, 0); + sntp_set_servers(servers, sizeof(servers) / sizeof(char*)); + printf("DONE!\n"); + while(1) { + vTaskDelayMs(5000); + time_t ts = sntp_get_rtc_time(NULL); + printf("TIME: %s", ctime(&ts)); + } +} + +void frc2_interrupt_handler(void) +{ + /* FRC2 needs the match register updated on each timer interrupt */ + timer_set_frequency(FRC2, freq_frc2); + gpio_toggle(gpio_frc2); +} + +// Configure FRC2 to blink the LED. I'm messing with FRC2 just to test if +// it does affect FreeRTOS. +void Frc2Config(void) { + /* configure GPIO */ + gpio_enable(gpio_frc2, GPIO_OUTPUT); + gpio_write(gpio_frc2, 1); + + /* stop timer and mask its interrupt as a precaution */ + timer_set_interrupts(FRC2, false); + timer_set_run(FRC2, false); + + /* set up ISR */ + _xt_isr_attach(INUM_TIMER_FRC2, frc2_interrupt_handler); + + /* configure timer frequency */ + timer_set_frequency(FRC2, freq_frc2); + + /* unmask interrupt and start timer */ + timer_set_interrupts(FRC2, true); + timer_set_run(FRC2, true); +} + +void user_init(void) +{ + uart_set_baud(0, 115200); + printf("SDK version:%s\n", sdk_system_get_sdk_version()); + + struct sdk_station_config config = { + .ssid = WIFI_SSID, + .password = WIFI_PASS, + }; + + /* required to call wifi_set_opmode before station_set_config */ + sdk_wifi_set_opmode(STATION_MODE); + sdk_wifi_station_set_config(&config); + + // Mess with FRC2 to test if it interferes with FreeRTOS + Frc2Config(); + + xTaskCreate(SntpTsk, (signed char *)"SNTP", 1024, NULL, 1, NULL); +} + diff --git a/extras/sntp/component.mk b/extras/sntp/component.mk new file mode 100644 index 0000000..155537a --- /dev/null +++ b/extras/sntp/component.mk @@ -0,0 +1,8 @@ +# Component makefile for extras/sntp + +INC_DIRS += $(sntp_ROOT) + +# args for passing into compile rule generation +sntp_SRC_DIR = $(sntp_ROOT) + +$(eval $(call component_compile_rules,sntp)) diff --git a/extras/sntp/sntp.c b/extras/sntp/sntp.c new file mode 100644 index 0000000..5654075 --- /dev/null +++ b/extras/sntp/sntp.c @@ -0,0 +1,748 @@ +/** + * @file + * SNTP client module + * + */ + +/* + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Simon Goldschmidt (lwIP raw API part) + * + * Modified by Jesus Alonso (doragasu) to allow setting servers dynamically. + */ + +#include "lwip/opt.h" + +#include "sntp.h" + +#include "lwip/timers.h" +#include "lwip/udp.h" +#include "lwip/dns.h" +#include "lwip/ip_addr.h" +#include "lwip/pbuf.h" + +#include +#include + +#if LWIP_UDP + +/** This is simple "SNTP" client for socket or raw API. + * It is a minimal implementation of SNTPv4 as specified in RFC 4330. + * + * For a list of some public NTP servers, see this link : + * http://support.ntp.org/bin/view/Servers/NTPPoolServers + * + * @todo: + * - set/change servers at runtime + * - complete SNTP_CHECK_RESPONSE checks 3 and 4 + * - support broadcast/multicast mode? + */ + +/** Decide whether to build SNTP for socket or raw API + * The socket API SNTP client is a very minimal implementation that does not + * fully confor to the SNTPv4 RFC, especially regarding server load and error + * procesing. */ +#ifndef SNTP_SOCKET +#define SNTP_SOCKET 0 +#endif + +#if SNTP_SOCKET +#include "lwip/sockets.h" +#endif + +/** + * SNTP_DEBUG: Enable debugging for SNTP. + */ +#ifndef SNTP_DEBUG +#define SNTP_DEBUG LWIP_DBG_OFF +#endif + +/** SNTP server port */ +#ifndef SNTP_PORT +#define SNTP_PORT 123 +#endif + +/** Set this to 1 to allow SNTP_SERVER_ADDRESS to be a DNS name */ +#ifndef SNTP_SERVER_DNS +#define SNTP_SERVER_DNS 0 +#endif + +/** Set to the number of servers supported (at least 1) */ +#ifndef SNTP_NUM_SERVERS_SUPPORTED +#define SNTP_NUM_SERVERS_SUPPORTED 1 +#endif + +/** SNTP server address: + * - as IPv4 address in "u32_t" format + * - as a DNS name if SNTP_SERVER_DNS is set to 1 + * May contain multiple server names (e.g. "pool.ntp.org","second.time.server") + */ +#ifndef SNTP_SERVER_ADDRESS +#if SNTP_SERVER_DNS +#define SNTP_SERVER_ADDRESS "pool.ntp.org" +#else +#define SNTP_SERVER_ADDRESS "213.161.194.93" /* pool.ntp.org */ +#endif +#endif + +/** Sanity check: + * Define this to + * - 0 to turn off sanity checks (default; smaller code) + * - >= 1 to check address and port of the response packet to ensure the + * response comes from the server we sent the request to. + * - >= 2 to check returned Originate Timestamp against Transmit Timestamp + * sent to the server (to ensure response to older request). + * - >= 3 @todo: discard reply if any of the LI, Stratum, or Transmit Timestamp + * fields is 0 or the Mode field is not 4 (unicast) or 5 (broadcast). + * - >= 4 @todo: to check that the Root Delay and Root Dispersion fields are each + * greater than or equal to 0 and less than infinity, where infinity is + * currently a cozy number like one second. This check avoids using a + * server whose synchronization source has expired for a very long time. + */ +#ifndef SNTP_CHECK_RESPONSE +#define SNTP_CHECK_RESPONSE 0 +#endif + +/** According to the RFC, this shall be a random delay + * between 1 and 5 minutes (in milliseconds) to prevent load peaks. + * This can be defined to a random generation function, + * which must return the delay in milliseconds as u32_t. + * Turned off by default. + */ +#ifndef SNTP_STARTUP_DELAY +#define SNTP_STARTUP_DELAY 0 +#endif + +/** SNTP receive timeout - in milliseconds + * Also used as retry timeout - this shouldn't be too low. + * Default is 3 seconds. + */ +#ifndef SNTP_RECV_TIMEOUT +#define SNTP_RECV_TIMEOUT 3000 +#endif + +/** SNTP update delay - in milliseconds + * Default is 1 hour. + */ +#ifndef SNTP_UPDATE_DELAY +#define SNTP_UPDATE_DELAY 3600000 +#endif +#if (SNTP_UPDATE_DELAY < 15000) && !SNTP_SUPPRESS_DELAY_CHECK +#error "SNTPv4 RFC 4330 enforces a minimum update time of 15 seconds!" +#endif + +/** SNTP macro to change system time and/or the update the RTC clock */ +#ifndef SNTP_SET_SYSTEM_TIME +#define SNTP_SET_SYSTEM_TIME(sec) ((void)sec) +#endif + +/** SNTP macro to change system time including microseconds */ +#ifdef SNTP_SET_SYSTEM_TIME_US +#define SNTP_CALC_TIME_US 1 +#define SNTP_RECEIVE_TIME_SIZE 2 +#else +#define SNTP_SET_SYSTEM_TIME_US(sec, us) +#define SNTP_CALC_TIME_US 0 +#define SNTP_RECEIVE_TIME_SIZE 1 +#endif + +/** SNTP macro to get system time, used with SNTP_CHECK_RESPONSE >= 2 + * to send in request and compare in response. + */ +#ifndef SNTP_GET_SYSTEM_TIME +#define SNTP_GET_SYSTEM_TIME(sec, us) do { (sec) = 0; (us) = 0; } while(0) +#endif + +/** Default retry timeout (in milliseconds) if the response + * received is invalid. + * This is doubled with each retry until SNTP_RETRY_TIMEOUT_MAX is reached. + */ +#ifndef SNTP_RETRY_TIMEOUT +#define SNTP_RETRY_TIMEOUT SNTP_RECV_TIMEOUT +#endif + +/** Maximum retry timeout (in milliseconds). */ +#ifndef SNTP_RETRY_TIMEOUT_MAX +#define SNTP_RETRY_TIMEOUT_MAX (SNTP_RETRY_TIMEOUT * 10) +#endif + +/** Increase retry timeout with every retry sent + * Default is on to conform to RFC. + */ +#ifndef SNTP_RETRY_TIMEOUT_EXP +#define SNTP_RETRY_TIMEOUT_EXP 1 +#endif + +/* the various debug levels for this file */ +#define SNTP_DEBUG_TRACE (SNTP_DEBUG | LWIP_DBG_TRACE) +#define SNTP_DEBUG_STATE (SNTP_DEBUG | LWIP_DBG_STATE) +#define SNTP_DEBUG_WARN (SNTP_DEBUG | LWIP_DBG_LEVEL_WARNING) +#define SNTP_DEBUG_WARN_STATE (SNTP_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE) +#define SNTP_DEBUG_SERIOUS (SNTP_DEBUG | LWIP_DBG_LEVEL_SERIOUS) + +#define SNTP_ERR_KOD 1 + +/* SNTP protocol defines */ +#define SNTP_MSG_LEN 48 + +#define SNTP_OFFSET_LI_VN_MODE 0 +#define SNTP_LI_MASK 0xC0 +#define SNTP_LI_NO_WARNING 0x00 +#define SNTP_LI_LAST_MINUTE_61_SEC 0x01 +#define SNTP_LI_LAST_MINUTE_59_SEC 0x02 +#define SNTP_LI_ALARM_CONDITION 0x03 /* (clock not synchronized) */ + +#define SNTP_VERSION_MASK 0x38 +#define SNTP_VERSION (4/* NTP Version 4*/<<3) + +#define SNTP_MODE_MASK 0x07 +#define SNTP_MODE_CLIENT 0x03 +#define SNTP_MODE_SERVER 0x04 +#define SNTP_MODE_BROADCAST 0x05 + +#define SNTP_OFFSET_STRATUM 1 +#define SNTP_STRATUM_KOD 0x00 + +#define SNTP_OFFSET_ORIGINATE_TIME 24 +#define SNTP_OFFSET_RECEIVE_TIME 32 +#define SNTP_OFFSET_TRANSMIT_TIME 40 + +/* number of seconds between 1900 and 1970 */ +#define DIFF_SEC_1900_1970 (2208988800UL) + +/** + * SNTP packet format (without optional fields) + * Timestamps are coded as 64 bits: + * - 32 bits seconds since Jan 01, 1970, 00:00 + * - 32 bits seconds fraction (0-padded) + * For future use, if the MSB in the seconds part is set, seconds are based + * on Feb 07, 2036, 06:28:16. + */ +#ifdef PACK_STRUCT_USE_INCLUDES +# include "arch/bpstruct.h" +#endif +PACK_STRUCT_BEGIN +struct sntp_msg { + PACK_STRUCT_FIELD(u8_t li_vn_mode); + PACK_STRUCT_FIELD(u8_t stratum); + PACK_STRUCT_FIELD(u8_t poll); + PACK_STRUCT_FIELD(u8_t precision); + PACK_STRUCT_FIELD(u32_t root_delay); + PACK_STRUCT_FIELD(u32_t root_dispersion); + PACK_STRUCT_FIELD(u32_t reference_identifier); + PACK_STRUCT_FIELD(u32_t reference_timestamp[2]); + PACK_STRUCT_FIELD(u32_t originate_timestamp[2]); + PACK_STRUCT_FIELD(u32_t receive_timestamp[2]); + PACK_STRUCT_FIELD(u32_t transmit_timestamp[2]); +} PACK_STRUCT_STRUCT; +PACK_STRUCT_END +#ifdef PACK_STRUCT_USE_INCLUDES +# include "arch/epstruct.h" +#endif + +/* function prototypes */ +static void sntp_request(void *arg); + +/** The UDP pcb used by the SNTP client */ +static struct udp_pcb* sntp_pcb; +/** Addresses of servers */ +static char* sntp_server_addresses[SNTP_NUM_SERVERS_SUPPORTED]; +/** The currently used server (initialized to 0) */ +static u8_t sntp_num_servers; +#if (SNTP_NUM_SERVERS_SUPPORTED > 1) +static u8_t sntp_current_server; +#else +#define sntp_current_server 0 +#endif /* SNTP_NUM_SERVERS_SUPPORTED */ + +static u32_t sntp_update_delay = SNTP_UPDATE_DELAY; + +#if SNTP_RETRY_TIMEOUT_EXP +#define SNTP_RESET_RETRY_TIMEOUT() sntp_retry_timeout = SNTP_RETRY_TIMEOUT +/** Retry time, initialized with SNTP_RETRY_TIMEOUT and doubled with each retry. */ +static u32_t sntp_retry_timeout; +#else /* SNTP_RETRY_TIMEOUT_EXP */ +#define SNTP_RESET_RETRY_TIMEOUT() +#define sntp_retry_timeout SNTP_RETRY_TIMEOUT +#endif /* SNTP_RETRY_TIMEOUT_EXP */ + +#if SNTP_CHECK_RESPONSE >= 1 +/** Saves the last server address to compare with response */ +static ip_addr_t sntp_last_server_address; +#endif /* SNTP_CHECK_RESPONSE >= 1 */ + +#if SNTP_CHECK_RESPONSE >= 2 +/** Saves the last timestamp sent (which is sent back by the server) + * to compare against in response */ +static u32_t sntp_last_timestamp_sent[2]; +#endif /* SNTP_CHECK_RESPONSE >= 2 */ + +/** + * SNTP processing of received timestamp + */ +static void +sntp_process(u32_t *receive_timestamp) +{ + /* convert SNTP time (1900-based) to unix GMT time (1970-based) + * @todo: if MSB is 1, SNTP time is 2036-based! + */ + time_t t = (ntohl(receive_timestamp[0]) - DIFF_SEC_1900_1970); + +#if SNTP_CALC_TIME_US + u32_t us = ntohl(receive_timestamp[1]) / 4295; + SNTP_SET_SYSTEM_TIME_US(t, us); + /* display local time from GMT time */ + LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_process: %s, %"U32_F" us", ctime(&t), us)); + +#else /* SNTP_CALC_TIME_US */ + + /* change system time and/or the update the RTC clock */ + SNTP_SET_SYSTEM_TIME(t); + /* display local time from GMT time */ + LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_process: %s", ctime(&t))); +#endif /* SNTP_CALC_TIME_US */ +} + +/** + * Initialize request struct to be sent to server. + */ +static void +sntp_initialize_request(struct sntp_msg *req) +{ + memset(req, 0, SNTP_MSG_LEN); + req->li_vn_mode = SNTP_LI_NO_WARNING | SNTP_VERSION | SNTP_MODE_CLIENT; + +#if SNTP_CHECK_RESPONSE >= 2 + { + u32_t sntp_time_sec, sntp_time_us; + /* fill in transmit timestamp and save it in 'sntp_last_timestamp_sent' */ + SNTP_GET_SYSTEM_TIME(sntp_time_sec, sntp_time_us); + sntp_last_timestamp_sent[0] = htonl(sntp_time_sec + DIFF_SEC_1900_1970); + req->transmit_timestamp[0] = sntp_last_timestamp_sent[0]; + /* we send/save us instead of fraction to be faster... */ + sntp_last_timestamp_sent[1] = htonl(sntp_time_us); + req->transmit_timestamp[1] = sntp_last_timestamp_sent[1]; + } +#endif /* SNTP_CHECK_RESPONSE >= 2 */ +} + +#if SNTP_SOCKET + +/** + * Send an SNTP request via sockets. + * This is a very minimal implementation that does not fully conform + * to the SNTPv4 RFC, especially regarding server load and error procesing. + */ +static void +sntp_request(void *arg) +{ + int sock; + struct sockaddr_in local; + struct sockaddr_in to; + int tolen; + int size; + int timeout; + struct sntp_msg sntpmsg; + ip_addr_t sntp_server_address; + + LWIP_UNUSED_ARG(arg); + + /* if we got a valid SNTP server address... */ + if (ipaddr_aton(SNTP_SERVER_ADDRESS, &sntp_server_address)) { + /* create new socket */ + sock = lwip_socket(AF_INET, SOCK_DGRAM, 0); + if (sock >= 0) { + /* prepare local address */ + memset(&local, 0, sizeof(local)); + local.sin_family = AF_INET; + local.sin_port = PP_HTONS(INADDR_ANY); + local.sin_addr.s_addr = PP_HTONL(INADDR_ANY); + + /* bind to local address */ + if (lwip_bind(sock, (struct sockaddr *)&local, sizeof(local)) == 0) { + /* set recv timeout */ + timeout = SNTP_RECV_TIMEOUT; + lwip_setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); + + /* prepare SNTP request */ + sntp_initialize_request(&sntpmsg); + + /* prepare SNTP server address */ + memset(&to, 0, sizeof(to)); + to.sin_family = AF_INET; + to.sin_port = PP_HTONS(SNTP_PORT); + inet_addr_from_ipaddr(&to.sin_addr, &sntp_server_address); + + /* send SNTP request to server */ + if (lwip_sendto(sock, &sntpmsg, SNTP_MSG_LEN, 0, (struct sockaddr *)&to, sizeof(to)) >= 0) { + /* receive SNTP server response */ + tolen = sizeof(to); + size = lwip_recvfrom(sock, &sntpmsg, SNTP_MSG_LEN, 0, (struct sockaddr *)&to, (socklen_t *)&tolen); + /* if the response size is good */ + if (size == SNTP_MSG_LEN) { + /* if this is a SNTP response... */ + if (((sntpmsg.li_vn_mode & SNTP_MODE_MASK) == SNTP_MODE_SERVER) || + ((sntpmsg.li_vn_mode & SNTP_MODE_MASK) == SNTP_MODE_BROADCAST)) { + /* do time processing */ + sntp_process(sntpmsg.receive_timestamp); + } else { + LWIP_DEBUGF( SNTP_DEBUG_WARN, ("sntp_request: not response frame code\n")); + } + } + } else { + LWIP_DEBUGF( SNTP_DEBUG_WARN, ("sntp_request: not sendto==%i\n", errno)); + } + } + /* close the socket */ + closesocket(sock); + } + } +} + +/** + * SNTP thread + */ +static void +sntp_thread(void *arg) +{ + LWIP_UNUSED_ARG(arg); + while(1) { + sntp_request(NULL); + sys_msleep(sntp_update_delay); + } +} + +/** + * Initialize this module when using sockets + */ +void +sntp_init(void) +{ + sys_thread_new("sntp_thread", sntp_thread, NULL, DEFAULT_THREAD_STACKSIZE, DEFAULT_THREAD_PRIO); +} + +#else /* SNTP_SOCKET */ + +/** + * Retry: send a new request (and increase retry timeout). + * + * @param arg is unused (only necessary to conform to sys_timeout) + */ +static void +sntp_retry(void* arg) +{ + LWIP_UNUSED_ARG(arg); + + LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_retry: Next request will be sent in %"U32_F" ms\n", + sntp_retry_timeout)); + + /* set up a timer to send a retry and increase the retry delay */ + sys_timeout(sntp_retry_timeout, sntp_request, NULL); + +#if SNTP_RETRY_TIMEOUT_EXP + { + u32_t new_retry_timeout; + /* increase the timeout for next retry */ + new_retry_timeout = sntp_retry_timeout << 1; + /* limit to maximum timeout and prevent overflow */ + if ((new_retry_timeout <= SNTP_RETRY_TIMEOUT_MAX) && + (new_retry_timeout > sntp_retry_timeout)) { + sntp_retry_timeout = new_retry_timeout; + } + } +#endif /* SNTP_RETRY_TIMEOUT_EXP */ +} + +#if (SNTP_NUM_SERVERS_SUPPORTED > 1) +/** + * If Kiss-of-Death is received (or another packet parsing error), + * try the next server or retry the current server and increase the retry + * timeout if only one server is available. + * + * @param arg is unused (only necessary to conform to sys_timeout) + */ +static void +sntp_try_next_server(void* arg) +{ + LWIP_UNUSED_ARG(arg); + + if (sntp_num_servers > 1) { + /* new server: reset retry timeout */ + SNTP_RESET_RETRY_TIMEOUT(); + sntp_current_server++; + if (sntp_current_server >= sntp_num_servers) { + sntp_current_server = 0; + } + LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_try_next_server: Sending request to server %"U16_F"\n", + (u16_t)sntp_current_server)); + /* instantly send a request to the next server */ + sntp_request(NULL); + } else { + sntp_retry(NULL); + } +} +#else /* SNTP_NUM_SERVERS_SUPPORTED > 1 */ +/* Always retry on error if only one server is supported */ +#define sntp_try_next_server sntp_retry +#endif /* SNTP_NUM_SERVERS_SUPPORTED > 1 */ + +/** UDP recv callback for the sntp pcb */ +static void +sntp_recv(void *arg, struct udp_pcb* pcb, struct pbuf *p, ip_addr_t *addr, u16_t port) +{ + u8_t mode; + u8_t stratum; + u32_t receive_timestamp[SNTP_RECEIVE_TIME_SIZE]; + err_t err; + + LWIP_UNUSED_ARG(arg); + LWIP_UNUSED_ARG(pcb); + + /* packet received: stop retry timeout */ + sys_untimeout(sntp_try_next_server, NULL); + sys_untimeout(sntp_request, NULL); + + err = ERR_ARG; +#if SNTP_CHECK_RESPONSE >= 1 + /* check server address and port */ + if (ip_addr_cmp(addr, &sntp_last_server_address) && + (port == SNTP_PORT)) +#else /* SNTP_CHECK_RESPONSE >= 1 */ + LWIP_UNUSED_ARG(addr); + LWIP_UNUSED_ARG(port); +#endif /* SNTP_CHECK_RESPONSE >= 1 */ + { + /* process the response */ + if (p->tot_len == SNTP_MSG_LEN) { + pbuf_copy_partial(p, &mode, 1, SNTP_OFFSET_LI_VN_MODE); + mode &= SNTP_MODE_MASK; + /* if this is a SNTP response... */ + if ((mode == SNTP_MODE_SERVER) || + (mode == SNTP_MODE_BROADCAST)) { + pbuf_copy_partial(p, &stratum, 1, SNTP_OFFSET_STRATUM); + if (stratum == SNTP_STRATUM_KOD) { + /* Kiss-of-death packet. Use another server or increase UPDATE_DELAY. */ + err = SNTP_ERR_KOD; + LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_recv: Received Kiss-of-Death\n")); + } else { +#if SNTP_CHECK_RESPONSE >= 2 + /* check originate_timetamp against sntp_last_timestamp_sent */ + u32_t originate_timestamp[2]; + pbuf_copy_partial(p, &originate_timestamp, 8, SNTP_OFFSET_ORIGINATE_TIME); + if ((originate_timestamp[0] != sntp_last_timestamp_sent[0]) || + (originate_timestamp[1] != sntp_last_timestamp_sent[1])) + { + LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid originate timestamp in response\n")); + } else +#endif /* SNTP_CHECK_RESPONSE >= 2 */ + /* @todo: add code for SNTP_CHECK_RESPONSE >= 3 and >= 4 here */ + { + /* correct answer */ + err = ERR_OK; + pbuf_copy_partial(p, &receive_timestamp, SNTP_RECEIVE_TIME_SIZE * 4, SNTP_OFFSET_RECEIVE_TIME); + } + } + } else { + LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid mode in response: %"U16_F"\n", (u16_t)mode)); + } + } else { + LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid packet length: %"U16_F"\n", p->tot_len)); + } + } + pbuf_free(p); + if (err == ERR_OK) { + /* Correct response, reset retry timeout */ + SNTP_RESET_RETRY_TIMEOUT(); + + sntp_process(receive_timestamp); + + /* Set up timeout for next request */ + sys_timeout(sntp_update_delay, sntp_request, NULL); + LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_recv: Scheduled next time request: %"U32_F" ms\n", + sntp_update_delay)); + } else if (err == SNTP_ERR_KOD) { + /* Kiss-of-death packet. Use another server or increase UPDATE_DELAY. */ + sntp_try_next_server(NULL); + } else { + /* another error, try the same server again */ + sntp_retry(NULL); + } +} + +/** Actually send an sntp request to a server. + * + * @param server_addr resolved IP address of the SNTP server + */ +static void +sntp_send_request(ip_addr_t *server_addr) +{ + struct pbuf* p; + p = pbuf_alloc(PBUF_TRANSPORT, SNTP_MSG_LEN, PBUF_RAM); + if (p != NULL) { + struct sntp_msg *sntpmsg = (struct sntp_msg *)p->payload; + LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_send_request: Sending request to server\n")); + /* initialize request message */ + sntp_initialize_request(sntpmsg); + /* send request */ + udp_sendto(sntp_pcb, p, server_addr, SNTP_PORT); + /* set up receive timeout: try next server or retry on timeout */ + sys_timeout((u32_t)SNTP_RECV_TIMEOUT, sntp_try_next_server, NULL); +#if SNTP_CHECK_RESPONSE >= 1 + /* save server address to verify it in sntp_recv */ + ip_addr_set(&sntp_last_server_address, server_addr); +#endif /* SNTP_CHECK_RESPONSE >= 1 */ + } else { + LWIP_DEBUGF(SNTP_DEBUG_SERIOUS, ("sntp_send_request: Out of memory, trying again in %"U32_F" ms\n", + (u32_t)SNTP_RETRY_TIMEOUT)); + /* out of memory: set up a timer to send a retry */ + sys_timeout((u32_t)SNTP_RETRY_TIMEOUT, sntp_request, NULL); + } +} + +#if SNTP_SERVER_DNS +/** + * DNS found callback when using DNS names as server address. + */ +static void +sntp_dns_found(const char* hostname, ip_addr_t *ipaddr, void *arg) +{ + LWIP_UNUSED_ARG(hostname); + LWIP_UNUSED_ARG(arg); + + if (ipaddr != NULL) { + /* Address resolved, send request */ + LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_dns_found: Server address resolved, sending request\n")); + sntp_send_request(ipaddr); + } else { + /* DNS resolving failed -> try another server */ + LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_dns_found: Failed to resolve server address resolved, trying next server\n")); + sntp_try_next_server(NULL); + } +} +#endif /* SNTP_SERVER_DNS */ + +/** + * Send out an sntp request via raw API. + * + * @param arg is unused (only necessary to conform to sys_timeout) + */ +static void +sntp_request(void *arg) +{ + ip_addr_t sntp_server_address; + err_t err; + + LWIP_UNUSED_ARG(arg); + + /* initialize SNTP server address */ +#if SNTP_SERVER_DNS + err = dns_gethostbyname(sntp_server_addresses[sntp_current_server], &sntp_server_address, + sntp_dns_found, NULL); + if (err == ERR_INPROGRESS) { + /* DNS request sent, wait for sntp_dns_found being called */ + LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_request: Waiting for server address to be resolved.\n")); + return; + } +#else /* SNTP_SERVER_DNS */ + err = ipaddr_aton(sntp_server_addresses[sntp_current_server], &sntp_server_address) + ? ERR_OK : ERR_ARG; + +#endif /* SNTP_SERVER_DNS */ + + if (err == ERR_OK) { + sntp_send_request(&sntp_server_address); + } else { + /* address conversion failed, try another server */ + LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_request: Invalid server address, trying next server.\n")); + sys_timeout((u32_t)SNTP_RETRY_TIMEOUT, sntp_try_next_server, NULL); + } +} + +/** + * Initialize this module when using raw API. + * Send out request instantly or after SNTP_STARTUP_DELAY. + */ +void +sntp_init(void) +{ + char *def_addr[] = {SNTP_SERVER_ADDRESS}; + + sntp_num_servers = 0; + sntp_set_servers(def_addr, sizeof(def_addr) / sizeof(char*)); + + SNTP_RESET_RETRY_TIMEOUT(); + sntp_pcb = udp_new(); + LWIP_ASSERT("Failed to allocate udp pcb for sntp client", sntp_pcb != NULL); + if (sntp_pcb != NULL) { + udp_recv(sntp_pcb, sntp_recv, NULL); +#if SNTP_STARTUP_DELAY + sys_timeout((u32_t)SNTP_STARTUP_DELAY, sntp_request, NULL); +#else + sntp_request(NULL); +#endif + } +} + +#endif /* SNTP_SOCKET */ + +/* Additions to allow dynamically settings servers and update delay */ + +/** + * Set the NTP servers + */ +int sntp_set_servers(char *server_url[], int num_servers) +{ + int i; + + /* Check number of servers requested */ + if (SNTP_NUM_SERVERS_SUPPORTED < num_servers) return -1; + + /* Free previously allocated buffers */ + for (i = sntp_num_servers - 1; i >= 0; i--) { + free(sntp_server_addresses[i]); + sntp_server_addresses[i] = NULL; + } + + /* Allocate memory and copy servers */ + for (i = 0; i < num_servers; i++) { + sntp_server_addresses[i] = malloc(strlen(server_url[i])); + if (sntp_server_addresses[i]) { + strcpy(sntp_server_addresses[i], server_url[i]); + } else { + sntp_num_servers = i; + return -2; + } + } + sntp_num_servers = num_servers; + return 0; +} + +void sntp_set_update_delay(uint32_t ms) +{ + sntp_update_delay = ms > 15000?ms:15000; +} + +#endif /* LWIP_UDP */ diff --git a/extras/sntp/sntp.h b/extras/sntp/sntp.h new file mode 100644 index 0000000..9b5043a --- /dev/null +++ b/extras/sntp/sntp.h @@ -0,0 +1,52 @@ +/************************************************************************//** + * RTC time keeping and synchronization using SNTP for esp-open-rtos. + * Uses SNTP lwIP contribution. + * + * 2016, Jesus Alonso (doragasu) + ****************************************************************************/ + +#ifndef _SNTP_H_ +#define _SNTP_H_ + +#include +#include + +/// Update SNTP RTC timer +void sntp_update_rtc(time_t t, uint32_t us); + +/// Function used to update the date/time, with microseconds resolution. +#define SNTP_SET_SYSTEM_TIME_US(sec, us) sntp_update_rtc(sec, us) + +/// For the lwIP implementation of SNTP to allow using names for NTP servers. +#define SNTP_SERVER_DNS 1 + +/// Number of supported NTP servers +#define SNTP_NUM_SERVERS_SUPPORTED 4 + +/// Initialize the module, and start requesting SNTP updates. This function +/// must be called only once. +void sntp_initialize(int time_zone, int day_light); + +/// Sets time zone. Allowed values are in the range [-11, 13]. +/// NOTE: Settings do not take effect until SNTP time is updated. It is +void sntp_set_timezone(int time_zone); + +/// Sets daylight. +/// NOTE: Settings do not take effect until SNTP time is updated. +void sntp_set_daylight(int day_light); + +/// Returns the time read from RTC counter, in seconds from Epoch. If +/// us is not null, it will be filled with the microseconds. +time_t sntp_get_rtc_time(int32_t *us); + +/// Set SNTP servers. Up to SNTP_NUM_SERVERS_SUPPORTED can be set. +/// Returns 0 if OK, less than 0 if error. +/// NOTE: This function must NOT be called before sntp_initialize(). +int sntp_set_servers(char *server_url[], int num_servers); + +/// Sets the update delay in ms. If requested value is less than 15s, +/// a 15s update interval will be set. +void sntp_set_update_delay(uint32_t ms); + +#endif /* _SNTP_H_ */ + diff --git a/extras/sntp/sntp_fun.c b/extras/sntp/sntp_fun.c new file mode 100644 index 0000000..83f5e6b --- /dev/null +++ b/extras/sntp/sntp_fun.c @@ -0,0 +1,105 @@ +/* + * Auxiliar functions to handle date/time along with lwIP sntp implementation. + * + * Jesus Alonso (doragasu) + */ + +#include +#include +#include +#include +#include "sntp.h" + +#define TIMER_COUNT RTC.COUNTER + +// daylight settings +// Base calculated with value obtained from NTP server (64 bits) +#define sntp_base (*((uint64_t*)RTC.SCRATCH)) +// Timer value when base was obtained +#define tim_ref (RTC.SCRATCH[2]) + +// Setters and getters for CAL, TZ and DST. +#define RTC_CAL_SET(val) (RTC.SCRATCH[3] |= (val) & 0x0000FFFF) +#define RTC_DST_SET(val) (RTC.SCRATCH[3] |= ((val)<<16) & 0x00010000) +#define RTC_TZ_SET(val) (RTC.SCRATCH[3] |= ((val)<<24) & 0xFF000000) + +#define RTC_CAL_GET() (RTC.SCRATCH[3] & 0x0000FFFF) +#define RTC_DST_GET() ((RTC.SCRATCH[3] & 0x00010000)>>16) +#define RTC_TZ_GET() ((((int)RTC.SCRATCH[3]) & ((int)0xFF000000))>>24) + +// Implemented in sntp.c +void sntp_init(void); + +// Sets time zone. Allowed values are in the range [-11, 13]. +// NOTE: Settings do not take effect until SNTP time is updated. It is +void sntp_set_timezone(int time_zone) { + //tz = time_zone; + RTC_TZ_SET(time_zone); +} + +// Sets daylight. +// NOTE: Settings do not take effect until SNTP time is updated. +void sntp_set_daylight(int day_light) { + //dst = day_light; + RTC_DST_SET(day_light); +} + + +void sntp_initialize(int time_zone, int day_light) { + sntp_base = 0; + RTC_TZ_SET(time_zone); + RTC_DST_SET(day_light); + // To avoid div by 0 exceptions if requesting time before SNTP config + RTC_CAL_SET(1); + tim_ref = TIMER_COUNT; + sntp_init(); +} + +// Check if a timer wrap has occurred. Compensate sntp_base reference +// if affirmative. +// TODO: think about multitasking and race conditions +static inline void sntp_check_timer_wrap(uint32_t current_value) { + if (current_value < tim_ref) { + // Timer wrap has occurred, compensate by subtracting 2^32 to ref. + sntp_base -= 1LLU<<32; + // DEBUG + printf("\nTIMER WRAPPED!\n"); + } +} + +// Return secs. If us is not a null pointer, fill it with usecs +time_t sntp_get_rtc_time(int32_t *us) { + time_t secs; + uint32_t tim; + uint64_t base; + + tim = TIMER_COUNT; + // Check for timer wrap + sntp_check_timer_wrap(tim); + base = sntp_base + tim - tim_ref; + secs = base * RTC_CAL_GET() / (1000000U<<12); + if (us) { + *us = base * RTC_CAL_GET() % (1000000U<<12); + } + return secs; +} + +/// Update RTC timer. Called by SNTP module each time it receives an update. +void sntp_update_rtc(time_t t, uint32_t us) { + // Apply daylight and timezone correction + t += (RTC_TZ_GET() + RTC_DST_GET()) * 3600; + // DEBUG: Compute and print drift + int64_t sntp_current = sntp_base + TIMER_COUNT - tim_ref; + int64_t sntp_correct = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / RTC_CAL_GET(); + printf("\nRTC Adjust: drift = %ld ticks, cal = %d\n", (time_t)(sntp_correct - sntp_current), RTC_CAL_GET()); + + tim_ref = TIMER_COUNT; + RTC_CAL_SET(sdk_system_rtc_clock_cali_proc()); + + sntp_base = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / RTC_CAL_GET(); + + // DEBUG: Print obtained secs and check calculated secs are the same + time_t deb = sntp_base * RTC_CAL_GET() / (1000000U<<12); + printf("\nT: %lu, %lu, %s\n", t, deb, ctime(&deb)); +} + From 0482aebf7ddfca440db04f5c9093ed7a27418bc9 Mon Sep 17 00:00:00 2001 From: doragasu Date: Fri, 11 Mar 2016 13:11:15 +0100 Subject: [PATCH 02/44] Added quick and dirty _gettimeofday_r() test implementation. --- examples/sntp/sntp_example.c | 8 ++++++++ extras/sntp/sntp_fun.c | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/examples/sntp/sntp_example.c b/examples/sntp/sntp_example.c index 6083f96..8702718 100644 --- a/examples/sntp/sntp_example.c +++ b/examples/sntp/sntp_example.c @@ -50,6 +50,14 @@ void SntpTsk(void *pvParameters) sntp_initialize(1, 0); sntp_set_servers(servers, sizeof(servers) / sizeof(char*)); printf("DONE!\n"); + + struct timespec ts; + clock_getres(CLOCK_REALTIME, &ts); + printf("Time resolution: %d secs, %d nanosecs\n", t.tv_sec, t.tv_nsec); + + clock_gettime(CLOCK_REALTIME, &t); + printf("Time: %d secs, %d nanosecs\n", t.tv_sec, t.tv_nsec); + while(1) { vTaskDelayMs(5000); time_t ts = sntp_get_rtc_time(NULL); diff --git a/extras/sntp/sntp_fun.c b/extras/sntp/sntp_fun.c index 83f5e6b..f4c02ae 100644 --- a/extras/sntp/sntp_fun.c +++ b/extras/sntp/sntp_fun.c @@ -4,6 +4,10 @@ * Jesus Alonso (doragasu) */ +#include +#include +#include +#include #include #include #include @@ -84,6 +88,16 @@ time_t sntp_get_rtc_time(int32_t *us) { return secs; } +int _gettimeofday_r(struct _reent *r, struct timeval *tp, void *tzp) { + (void)r; + (void)tzp; + + printf("DEB; gettimeofday called"); + tp->tv_sec = sntp_get_rtc_time((int32_t*)&tp->tv_usec); + return 0; +} + + /// Update RTC timer. Called by SNTP module each time it receives an update. void sntp_update_rtc(time_t t, uint32_t us) { // Apply daylight and timezone correction From f14025b1c7bf24508afb71e1ac48a7f405d615dc Mon Sep 17 00:00:00 2001 From: doragasu Date: Sun, 13 Mar 2016 17:04:03 +0100 Subject: [PATCH 03/44] Removed non working clock_* calls from example. Using vars for tz and dst. --- examples/sntp/sntp_example.c | 12 +++--- extras/sntp/sntp.h | 4 ++ extras/sntp/sntp_fun.c | 83 ++++++++++++++++++++++-------------- 3 files changed, 62 insertions(+), 37 deletions(-) diff --git a/examples/sntp/sntp_example.c b/examples/sntp/sntp_example.c index 8702718..78601dd 100644 --- a/examples/sntp/sntp_example.c +++ b/examples/sntp/sntp_example.c @@ -51,12 +51,12 @@ void SntpTsk(void *pvParameters) sntp_set_servers(servers, sizeof(servers) / sizeof(char*)); printf("DONE!\n"); - struct timespec ts; - clock_getres(CLOCK_REALTIME, &ts); - printf("Time resolution: %d secs, %d nanosecs\n", t.tv_sec, t.tv_nsec); - - clock_gettime(CLOCK_REALTIME, &t); - printf("Time: %d secs, %d nanosecs\n", t.tv_sec, t.tv_nsec); +// struct timespec ts; +// clock_getres(CLOCK_REALTIME, &ts); +// printf("Time resolution: %d secs, %d nanosecs\n", t.tv_sec, t.tv_nsec); +// +// clock_gettime(CLOCK_REALTIME, &t); +// printf("Time: %d secs, %d nanosecs\n", t.tv_sec, t.tv_nsec); while(1) { vTaskDelayMs(5000); diff --git a/extras/sntp/sntp.h b/extras/sntp/sntp.h index 9b5043a..d284d5c 100644 --- a/extras/sntp/sntp.h +++ b/extras/sntp/sntp.h @@ -39,6 +39,10 @@ void sntp_set_daylight(int day_light); /// us is not null, it will be filled with the microseconds. time_t sntp_get_rtc_time(int32_t *us); +/// Returns the time in seconds since Epoch. If tloc is not NULL, return +/// value is also stored in the memory pointed by tloc. +time_t time(time_t *tloc); + /// Set SNTP servers. Up to SNTP_NUM_SERVERS_SUPPORTED can be set. /// Returns 0 if OK, less than 0 if error. /// NOTE: This function must NOT be called before sntp_initialize(). diff --git a/extras/sntp/sntp_fun.c b/extras/sntp/sntp_fun.c index f4c02ae..efb6da8 100644 --- a/extras/sntp/sntp_fun.c +++ b/extras/sntp/sntp_fun.c @@ -1,5 +1,5 @@ /* - * Auxiliar functions to handle date/time along with lwIP sntp implementation. + * Auxiliary functions to handle date/time along with lwIP sntp implementation. * * Jesus Alonso (doragasu) */ @@ -15,46 +15,56 @@ #include "sntp.h" #define TIMER_COUNT RTC.COUNTER +#define __UNUSED(var) (void)var // daylight settings // Base calculated with value obtained from NTP server (64 bits) #define sntp_base (*((uint64_t*)RTC.SCRATCH)) // Timer value when base was obtained #define tim_ref (RTC.SCRATCH[2]) +// Calibration value +#define cal (RTC.SCRATCH[3]) +// Timezone (-11 to +13) +static int8_t tz; +// Daylight savings +static bool dst; -// Setters and getters for CAL, TZ and DST. -#define RTC_CAL_SET(val) (RTC.SCRATCH[3] |= (val) & 0x0000FFFF) -#define RTC_DST_SET(val) (RTC.SCRATCH[3] |= ((val)<<16) & 0x00010000) -#define RTC_TZ_SET(val) (RTC.SCRATCH[3] |= ((val)<<24) & 0xFF000000) - -#define RTC_CAL_GET() (RTC.SCRATCH[3] & 0x0000FFFF) -#define RTC_DST_GET() ((RTC.SCRATCH[3] & 0x00010000)>>16) -#define RTC_TZ_GET() ((((int)RTC.SCRATCH[3]) & ((int)0xFF000000))>>24) +//// Setters and getters for CAL, TZ and DST. +//#define RTC_CAL_SET(val) (RTC.SCRATCH[3] |= (val) & 0x0000FFFF) +//#define RTC_DST_SET(val) (RTC.SCRATCH[3] |= ((val)<<16) & 0x00010000) +//#define RTC_TZ_SET(val) (RTC.SCRATCH[3] |= ((val)<<24) & 0xFF000000) +// +//#define RTC_CAL_GET() (RTC.SCRATCH[3] & 0x0000FFFF) +//#define RTC_DST_GET() ((RTC.SCRATCH[3] & 0x00010000)>>16) +//#define RTC_TZ_GET() ((((int)RTC.SCRATCH[3]) & ((int)0xFF000000))>>24) // Implemented in sntp.c void sntp_init(void); // Sets time zone. Allowed values are in the range [-11, 13]. -// NOTE: Settings do not take effect until SNTP time is updated. It is +// NOTE: Settings do not take effect until SNTP time is updated. void sntp_set_timezone(int time_zone) { - //tz = time_zone; - RTC_TZ_SET(time_zone); + tz = time_zone; + //RTC_TZ_SET(time_zone); } // Sets daylight. // NOTE: Settings do not take effect until SNTP time is updated. void sntp_set_daylight(int day_light) { - //dst = day_light; - RTC_DST_SET(day_light); + dst = day_light; + //RTC_DST_SET(day_light); } void sntp_initialize(int time_zone, int day_light) { sntp_base = 0; - RTC_TZ_SET(time_zone); - RTC_DST_SET(day_light); + tz = time_zone; + //RTC_TZ_SET(time_zone); + dst = day_light; + //RTC_DST_SET(day_light); // To avoid div by 0 exceptions if requesting time before SNTP config - RTC_CAL_SET(1); + cal = 1; + //RTC_CAL_SET(1); tim_ref = TIMER_COUNT; sntp_init(); } @@ -72,7 +82,7 @@ static inline void sntp_check_timer_wrap(uint32_t current_value) { } // Return secs. If us is not a null pointer, fill it with usecs -time_t sntp_get_rtc_time(int32_t *us) { +inline time_t sntp_get_rtc_time(int32_t *us) { time_t secs; uint32_t tim; uint64_t base; @@ -81,39 +91,50 @@ time_t sntp_get_rtc_time(int32_t *us) { // Check for timer wrap sntp_check_timer_wrap(tim); base = sntp_base + tim - tim_ref; - secs = base * RTC_CAL_GET() / (1000000U<<12); +// secs = base * RTC_CAL_GET() / (1000000U<<12); + secs = base * cal / (1000000U<<12); if (us) { - *us = base * RTC_CAL_GET() % (1000000U<<12); +// *us = base * RTC_CAL_GET() % (1000000U<<12); + *us = base * cal % (1000000U<<12); } return secs; } +// Syscall implementation int _gettimeofday_r(struct _reent *r, struct timeval *tp, void *tzp) { - (void)r; - (void)tzp; + __UNUSED(r); + __UNUSED(tzp); printf("DEB; gettimeofday called"); tp->tv_sec = sntp_get_rtc_time((int32_t*)&tp->tv_usec); return 0; } +time_t time(time_t *tloc) { + time_t datetime; + + datetime = sntp_get_rtc_time(NULL); + if (tloc) *tloc = datetime; + return datetime; +} /// Update RTC timer. Called by SNTP module each time it receives an update. void sntp_update_rtc(time_t t, uint32_t us) { // Apply daylight and timezone correction - t += (RTC_TZ_GET() + RTC_DST_GET()) * 3600; +// t += (RTC_TZ_GET() + RTC_DST_GET()) * 3600; + t += (tz + dst) * 3600; // DEBUG: Compute and print drift int64_t sntp_current = sntp_base + TIMER_COUNT - tim_ref; - int64_t sntp_correct = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / RTC_CAL_GET(); - printf("\nRTC Adjust: drift = %ld ticks, cal = %d\n", (time_t)(sntp_correct - sntp_current), RTC_CAL_GET()); +// int64_t sntp_correct = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / RTC_CAL_GET(); + int64_t sntp_correct = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / cal; +// printf("\nRTC Adjust: drift = %ld ticks, cal = %d\n", (time_t)(sntp_correct - sntp_current), RTC_CAL_GET()); + printf("\nRTC Adjust: drift = %ld ticks, cal = %d\n", (time_t)(sntp_correct - sntp_current), cal); tim_ref = TIMER_COUNT; - RTC_CAL_SET(sdk_system_rtc_clock_cali_proc()); + cal = sdk_system_rtc_clock_cali_proc(); +// RTC_CAL_SET(sdk_system_rtc_clock_cali_proc()); - sntp_base = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / RTC_CAL_GET(); - - // DEBUG: Print obtained secs and check calculated secs are the same - time_t deb = sntp_base * RTC_CAL_GET() / (1000000U<<12); - printf("\nT: %lu, %lu, %s\n", t, deb, ctime(&deb)); +// sntp_base = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / RTC_CAL_GET(); + sntp_base = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / cal; } From 9651692ca23eeacf2f830deeac87efa6c5e16ecb Mon Sep 17 00:00:00 2001 From: doragasu Date: Sun, 13 Mar 2016 18:29:30 +0100 Subject: [PATCH 04/44] Cleanup and some changes to make implementation a bit more conforming to the standard. --- examples/sntp/sntp_example.c | 66 +++++++---------------------- extras/sntp/sntp.h | 80 ++++++++++++++++++++++++------------ extras/sntp/sntp_fun.c | 71 ++++++++++++-------------------- 3 files changed, 93 insertions(+), 124 deletions(-) diff --git a/examples/sntp/sntp_example.c b/examples/sntp/sntp_example.c index 78601dd..96066a2 100644 --- a/examples/sntp/sntp_example.c +++ b/examples/sntp/sntp_example.c @@ -20,7 +20,7 @@ #include -// Add extras/sntp component to makefile for this include to work +/* Add extras/sntp component to makefile for this include to work */ #include #define SNTP_SERVERS "0.pool.ntp.org", "1.pool.ntp.org", \ @@ -29,71 +29,36 @@ #define vTaskDelayMs(ms) vTaskDelay((ms)/portTICK_RATE_MS) #define UNUSED_ARG(x) (void)x -// Use GPIO pin 2 -const int gpio_frc2 = 2; -// 1 Hz blink frequency -const int freq_frc2 = 1; - -void SntpTsk(void *pvParameters) +void sntp_tsk(void *pvParameters) { char *servers[] = {SNTP_SERVERS}; UNUSED_ARG(pvParameters); - // Wait until we have joined AP and are assigned an IP + /* Wait until we have joined AP and are assigned an IP */ while (sdk_wifi_station_get_connect_status() != STATION_GOT_IP) { vTaskDelayMs(100); } - // Start SNTP + /* Start SNTP */ printf("Starting SNTP... "); - sntp_set_update_delay(1*60000); - sntp_initialize(1, 0); + /* SNTP will request an update each 5 minutes */ + sntp_set_update_delay(5*60000); + /* Set GMT+1 zone, daylight savings off */ + const struct timezone tz = {1*60, 0}; + /* SNTP initialization */ + sntp_initialize(&tz); + /* Servers must be configured right after initialization */ sntp_set_servers(servers, sizeof(servers) / sizeof(char*)); printf("DONE!\n"); -// struct timespec ts; -// clock_getres(CLOCK_REALTIME, &ts); -// printf("Time resolution: %d secs, %d nanosecs\n", t.tv_sec, t.tv_nsec); -// -// clock_gettime(CLOCK_REALTIME, &t); -// printf("Time: %d secs, %d nanosecs\n", t.tv_sec, t.tv_nsec); - + /* Print date and time each 5 seconds */ while(1) { vTaskDelayMs(5000); - time_t ts = sntp_get_rtc_time(NULL); + time_t ts = time(NULL); printf("TIME: %s", ctime(&ts)); } } -void frc2_interrupt_handler(void) -{ - /* FRC2 needs the match register updated on each timer interrupt */ - timer_set_frequency(FRC2, freq_frc2); - gpio_toggle(gpio_frc2); -} - -// Configure FRC2 to blink the LED. I'm messing with FRC2 just to test if -// it does affect FreeRTOS. -void Frc2Config(void) { - /* configure GPIO */ - gpio_enable(gpio_frc2, GPIO_OUTPUT); - gpio_write(gpio_frc2, 1); - - /* stop timer and mask its interrupt as a precaution */ - timer_set_interrupts(FRC2, false); - timer_set_run(FRC2, false); - - /* set up ISR */ - _xt_isr_attach(INUM_TIMER_FRC2, frc2_interrupt_handler); - - /* configure timer frequency */ - timer_set_frequency(FRC2, freq_frc2); - - /* unmask interrupt and start timer */ - timer_set_interrupts(FRC2, true); - timer_set_run(FRC2, true); -} - void user_init(void) { uart_set_baud(0, 115200); @@ -108,9 +73,6 @@ void user_init(void) sdk_wifi_set_opmode(STATION_MODE); sdk_wifi_station_set_config(&config); - // Mess with FRC2 to test if it interferes with FreeRTOS - Frc2Config(); - - xTaskCreate(SntpTsk, (signed char *)"SNTP", 1024, NULL, 1, NULL); + xTaskCreate(sntp_tsk, (signed char *)"SNTP", 1024, NULL, 1, NULL); } diff --git a/extras/sntp/sntp.h b/extras/sntp/sntp.h index d284d5c..dde9331 100644 --- a/extras/sntp/sntp.h +++ b/extras/sntp/sntp.h @@ -9,48 +9,74 @@ #define _SNTP_H_ #include +#include #include -/// Update SNTP RTC timer -void sntp_update_rtc(time_t t, uint32_t us); - -/// Function used to update the date/time, with microseconds resolution. +/* + * Function used by lwIP sntp module to update the date/time, + * with microseconds resolution. + */ #define SNTP_SET_SYSTEM_TIME_US(sec, us) sntp_update_rtc(sec, us) -/// For the lwIP implementation of SNTP to allow using names for NTP servers. +/* + * For the lwIP implementation of SNTP to allow using names for NTP servers. + */ #define SNTP_SERVER_DNS 1 -/// Number of supported NTP servers +/* + * Number of supported NTP servers + */ #define SNTP_NUM_SERVERS_SUPPORTED 4 -/// Initialize the module, and start requesting SNTP updates. This function -/// must be called only once. -void sntp_initialize(int time_zone, int day_light); +/* + * Initialize the module, and start requesting SNTP updates. This function + * must be called only once. + * WARNING: tz->tz_dsttime doesn't have the same meaning as the standard + * implementation. If it is set to 1, a dst hour will be applied. If set + * to zero, time will not be modified. + */ +void sntp_initialize(const struct timezone *tz); -/// Sets time zone. Allowed values are in the range [-11, 13]. -/// NOTE: Settings do not take effect until SNTP time is updated. It is -void sntp_set_timezone(int time_zone); +/* + * Sets time zone. Allowed values are in the range [-11, 13]. + * NOTE: Settings do not take effect until SNTP time is updated. It is + * recommended to set these parameters only during initialization. + * WARNING: tz->tz_dsttime doesn't have the same meaning as the standard + * implementation. If it is set to 1, a dst hour will be applied. If set + * to zero, time will not be modified. + */ +void sntp_set_timezone(const struct timezone *tz); -/// Sets daylight. -/// NOTE: Settings do not take effect until SNTP time is updated. -void sntp_set_daylight(int day_light); - -/// Returns the time read from RTC counter, in seconds from Epoch. If -/// us is not null, it will be filled with the microseconds. -time_t sntp_get_rtc_time(int32_t *us); - -/// Returns the time in seconds since Epoch. If tloc is not NULL, return -/// value is also stored in the memory pointed by tloc. +/* + * Returns the time in seconds since Epoch. If tloc is not NULL, return + * value is also stored in the memory pointed by tloc. + */ time_t time(time_t *tloc); -/// Set SNTP servers. Up to SNTP_NUM_SERVERS_SUPPORTED can be set. -/// Returns 0 if OK, less than 0 if error. -/// NOTE: This function must NOT be called before sntp_initialize(). +/* + * Set SNTP servers. Up to SNTP_NUM_SERVERS_SUPPORTED can be set. + * Returns 0 if OK, less than 0 if error. + * NOTE: This function must NOT be called before sntp_initialize(). + */ int sntp_set_servers(char *server_url[], int num_servers); -/// Sets the update delay in ms. If requested value is less than 15s, -/// a 15s update interval will be set. +/* + * Sets the update delay in ms. If requested value is less than 15s, + * a 15s update interval will be set. + */ void sntp_set_update_delay(uint32_t ms); +/* + * Returns the time read from RTC counter, in seconds from Epoch. If + * us is not null, it will be filled with the microseconds. + */ +time_t sntp_get_rtc_time(int32_t *us); + +/* + * Update RTC timer. This function is called by the SNTP module each time + * an SNTP update is received. + */ +void sntp_update_rtc(time_t t, uint32_t us); + #endif /* _SNTP_H_ */ diff --git a/extras/sntp/sntp_fun.c b/extras/sntp/sntp_fun.c index efb6da8..35c068d 100644 --- a/extras/sntp/sntp_fun.c +++ b/extras/sntp/sntp_fun.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -15,7 +14,6 @@ #include "sntp.h" #define TIMER_COUNT RTC.COUNTER -#define __UNUSED(var) (void)var // daylight settings // Base calculated with value obtained from NTP server (64 bits) @@ -24,47 +22,35 @@ #define tim_ref (RTC.SCRATCH[2]) // Calibration value #define cal (RTC.SCRATCH[3]) -// Timezone (-11 to +13) -static int8_t tz; -// Daylight savings -static bool dst; -//// Setters and getters for CAL, TZ and DST. -//#define RTC_CAL_SET(val) (RTC.SCRATCH[3] |= (val) & 0x0000FFFF) -//#define RTC_DST_SET(val) (RTC.SCRATCH[3] |= ((val)<<16) & 0x00010000) -//#define RTC_TZ_SET(val) (RTC.SCRATCH[3] |= ((val)<<24) & 0xFF000000) -// -//#define RTC_CAL_GET() (RTC.SCRATCH[3] & 0x0000FFFF) -//#define RTC_DST_GET() ((RTC.SCRATCH[3] & 0x00010000)>>16) -//#define RTC_TZ_GET() ((((int)RTC.SCRATCH[3]) & ((int)0xFF000000))>>24) +// Timezone related data. +static struct timezone stz; // Implemented in sntp.c void sntp_init(void); -// Sets time zone. Allowed values are in the range [-11, 13]. +// Sets time zone. // NOTE: Settings do not take effect until SNTP time is updated. -void sntp_set_timezone(int time_zone) { - tz = time_zone; - //RTC_TZ_SET(time_zone); +void sntp_set_timezone(const struct timezone *tz) { + if (tz) { + stz = *tz; + } else { + stz.tz_minuteswest = 0; + stz.tz_dsttime = 0; + } } -// Sets daylight. -// NOTE: Settings do not take effect until SNTP time is updated. -void sntp_set_daylight(int day_light) { - dst = day_light; - //RTC_DST_SET(day_light); -} - - -void sntp_initialize(int time_zone, int day_light) { +// Initialization +void sntp_initialize(const struct timezone *tz) { + if (tz) { + stz = *tz; + } else { + stz.tz_minuteswest = 0; + stz.tz_dsttime = 0; + } sntp_base = 0; - tz = time_zone; - //RTC_TZ_SET(time_zone); - dst = day_light; - //RTC_DST_SET(day_light); // To avoid div by 0 exceptions if requesting time before SNTP config cal = 1; - //RTC_CAL_SET(1); tim_ref = TIMER_COUNT; sntp_init(); } @@ -91,25 +77,25 @@ inline time_t sntp_get_rtc_time(int32_t *us) { // Check for timer wrap sntp_check_timer_wrap(tim); base = sntp_base + tim - tim_ref; -// secs = base * RTC_CAL_GET() / (1000000U<<12); secs = base * cal / (1000000U<<12); if (us) { -// *us = base * RTC_CAL_GET() % (1000000U<<12); *us = base * cal % (1000000U<<12); } return secs; } -// Syscall implementation +// Syscall implementation. doesn't seem to use tzp. int _gettimeofday_r(struct _reent *r, struct timeval *tp, void *tzp) { - __UNUSED(r); - __UNUSED(tzp); + (void)r; + // Syscall defined by xtensa newlib defines tzp as void* + // So it looks like it is not used. Also check tp is not NULL + if (tzp || !tp) return EINVAL; - printf("DEB; gettimeofday called"); tp->tv_sec = sntp_get_rtc_time((int32_t*)&tp->tv_usec); return 0; } +// Added te get nearer the standard way of using time functions. time_t time(time_t *tloc) { time_t datetime; @@ -118,23 +104,18 @@ time_t time(time_t *tloc) { return datetime; } -/// Update RTC timer. Called by SNTP module each time it receives an update. +// Update RTC timer. Called by SNTP module each time it receives an update. void sntp_update_rtc(time_t t, uint32_t us) { // Apply daylight and timezone correction -// t += (RTC_TZ_GET() + RTC_DST_GET()) * 3600; - t += (tz + dst) * 3600; + t += (stz.tz_minuteswest + stz.tz_dsttime * 60) * 60; // DEBUG: Compute and print drift int64_t sntp_current = sntp_base + TIMER_COUNT - tim_ref; -// int64_t sntp_correct = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / RTC_CAL_GET(); int64_t sntp_correct = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / cal; -// printf("\nRTC Adjust: drift = %ld ticks, cal = %d\n", (time_t)(sntp_correct - sntp_current), RTC_CAL_GET()); printf("\nRTC Adjust: drift = %ld ticks, cal = %d\n", (time_t)(sntp_correct - sntp_current), cal); tim_ref = TIMER_COUNT; cal = sdk_system_rtc_clock_cali_proc(); -// RTC_CAL_SET(sdk_system_rtc_clock_cali_proc()); -// sntp_base = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / RTC_CAL_GET(); sntp_base = (((uint64_t)us + (uint64_t)t * 1000000U)<<12) / cal; } From 11c9031d9b044013ceca3704360dcf5349cf3e41 Mon Sep 17 00:00:00 2001 From: doragasu Date: Thu, 31 Mar 2016 16:05:15 +0200 Subject: [PATCH 05/44] Removed my _time() implementation to use newlib provided one. --- examples/sntp/sntp_example.c | 1 + extras/sntp/sntp.h | 6 ------ extras/sntp/sntp_fun.c | 9 --------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/examples/sntp/sntp_example.c b/examples/sntp/sntp_example.c index 96066a2..242489b 100644 --- a/examples/sntp/sntp_example.c +++ b/examples/sntp/sntp_example.c @@ -22,6 +22,7 @@ /* Add extras/sntp component to makefile for this include to work */ #include +#include #define SNTP_SERVERS "0.pool.ntp.org", "1.pool.ntp.org", \ "2.pool.ntp.org", "3.pool.ntp.org" diff --git a/extras/sntp/sntp.h b/extras/sntp/sntp.h index dde9331..dd25cf0 100644 --- a/extras/sntp/sntp.h +++ b/extras/sntp/sntp.h @@ -47,12 +47,6 @@ void sntp_initialize(const struct timezone *tz); */ void sntp_set_timezone(const struct timezone *tz); -/* - * Returns the time in seconds since Epoch. If tloc is not NULL, return - * value is also stored in the memory pointed by tloc. - */ -time_t time(time_t *tloc); - /* * Set SNTP servers. Up to SNTP_NUM_SERVERS_SUPPORTED can be set. * Returns 0 if OK, less than 0 if error. diff --git a/extras/sntp/sntp_fun.c b/extras/sntp/sntp_fun.c index 35c068d..7aaca20 100644 --- a/extras/sntp/sntp_fun.c +++ b/extras/sntp/sntp_fun.c @@ -95,15 +95,6 @@ int _gettimeofday_r(struct _reent *r, struct timeval *tp, void *tzp) { return 0; } -// Added te get nearer the standard way of using time functions. -time_t time(time_t *tloc) { - time_t datetime; - - datetime = sntp_get_rtc_time(NULL); - if (tloc) *tloc = datetime; - return datetime; -} - // Update RTC timer. Called by SNTP module each time it receives an update. void sntp_update_rtc(time_t t, uint32_t us) { // Apply daylight and timezone correction From 99eba80c4dd27b2b06f19ba7e162029b414a54ae Mon Sep 17 00:00:00 2001 From: jsuiker Date: Wed, 20 Apr 2016 22:03:18 +0000 Subject: [PATCH 06/44] Added DHT library to extras and sample code to examples --- examples/dht_sensor/Makefile | 4 + examples/dht_sensor/dht_sensor.c | 46 ++++++++++ extras/dht/component.mk | 10 +++ extras/dht/dht.c | 144 +++++++++++++++++++++++++++++++ extras/dht/dht.h | 17 ++++ 5 files changed, 221 insertions(+) create mode 100644 examples/dht_sensor/Makefile create mode 100644 examples/dht_sensor/dht_sensor.c create mode 100644 extras/dht/component.mk create mode 100644 extras/dht/dht.c create mode 100644 extras/dht/dht.h diff --git a/examples/dht_sensor/Makefile b/examples/dht_sensor/Makefile new file mode 100644 index 0000000..cfdde69 --- /dev/null +++ b/examples/dht_sensor/Makefile @@ -0,0 +1,4 @@ +PROGRAM=dht_sensor +EXTRA_COMPONENTS = extras/dht +include ../../common.mk + diff --git a/examples/dht_sensor/dht_sensor.c b/examples/dht_sensor/dht_sensor.c new file mode 100644 index 0000000..11f2c43 --- /dev/null +++ b/examples/dht_sensor/dht_sensor.c @@ -0,0 +1,46 @@ +/* + * + * This sample code is in the public domain. + */ +#include "espressif/esp_common.h" +#include "esp/uart.h" +#include "FreeRTOS.h" +#include "task.h" +#include "dht.h" +#include "esp8266.h" + +/* An example using the ubiquitous DHT** humidity sensors + * to read and print a new temperature and humidity measurement + * from a sensor attached to GPIO pin 4. + */ +int const dht_gpio = 4; + +void dhtMeasurementTask(void *pvParameters) +{ + int8_t temperature = 0; + int8_t humidity = 0; + + // DHT sensors that come mounted on a PCB generally have + // pull-up resistors on the data pin. It is recommended + // to provide an external pull-up resistor otherwise... + gpio_set_pullup(dht_gpio, false, false); + + while(1) { + + if (dht_fetch_data(dht_gpio, &humidity, &temperature)) { + printf("Humidity: %i%% Temp: %iC\n", humidity, temperature); + } else { + printf("Could not read data from sensor..."); + } + + // Three second delay... + vTaskDelay(3000 / portTICK_RATE_MS); + } +} + +void user_init(void) +{ + uart_set_baud(0, 115200); + xTaskCreate(dhtMeasurementTask, (signed char *)"dhtMeasurementTask", 256, NULL, 2, NULL); +} + diff --git a/extras/dht/component.mk b/extras/dht/component.mk new file mode 100644 index 0000000..0948e4d --- /dev/null +++ b/extras/dht/component.mk @@ -0,0 +1,10 @@ +# Component makefile for extras/dht + +INC_DIRS += $(ROOT)extras/dht + +# args for passing into compile rule generation +extras/dht_INC_DIR = $(ROOT)extras/dht +extras/dht_SRC_DIR = $(ROOT)extras/dht + +$(eval $(call component_compile_rules,extras/dht)) + diff --git a/extras/dht/dht.c b/extras/dht/dht.c new file mode 100644 index 0000000..100763a --- /dev/null +++ b/extras/dht/dht.c @@ -0,0 +1,144 @@ +/* + * Part of esp-open-rtos + * Copyright (C) 2016 Jonathan Hartsuiker (https://github.com/jsuiker) + * BSD Licensed as described in the file LICENSE + * + */ + +#include "dht.h" +#include "string.h" +#include "task.h" +#include "esp/gpio.h" + +#include // sdk_os_delay_us + +#ifndef DEBUG_DHT +#define DEBUG_DHT 0 +#endif + +#if DEBUG_DHT +#define debug(fmt, ...) printf("%s" fmt "\n", "dht: ", ## __VA_ARGS__); +#else +#define debug(fmt, ...) /* (do nothing) */ +#endif + +/* + * Note: + * A suitable pull-up resistor should be connected to the selected GPIO line + * + * __ ______ _______ ___________________________ + * \ A / \ C / \ DHT duration_data_low / \ + * \_______/ B \______/ D \__________________________/ DHT duration_data_high \__ + * + * + * Initializing communications with the DHT requires four 'phases' as follows: + * + * Phase A - MCU pulls signal low for at least 18000 us + * Phase B - MCU allows signal to float back up and waits 20-40us for DHT to pull it low + * Phase C - DHT pulls signal low for ~80us + * Phase D - DHT lets signal float back up for ~80us + * + * After this, the DHT transmits its first bit by holding the signal low for 50us + * and then letting it float back high for a period of time that depends on the data bit. + * duration_data_high is shorter than 50us for a logic '0' and longer than 50us for logic '1'. + * + * There are a total of 40 data bits trasnmitted sequentially. These bits are read into a byte array + * of length 5. The first and third bytes are humidity (%) and temperature (C), respectively. Bytes 2 and 4 + * are zero-filled and the fifth is a checksum such that: + * + * byte_5 == (byte_1 + byte_2 + byte_3 + btye_4) & 0xFF + * +*/ + +/* + * @pin the selected GPIO pin + * @interval how frequently the pin state is checked in microseconds + * @timeout maximum length of time to wait for the expected pin state + * @expected_pin_state high (true) or low (false) pin state + * @counter pointer to external uint8_t for tallying the duration waited for the pin state +*/ + +bool dht_await_pin_state(uint8_t pin, uint8_t interval, uint8_t timeout, bool expected_pin_state, uint8_t * counter) { + + for (*counter = 0; *counter < timeout; *counter+=interval) { + if (gpio_read(pin) == expected_pin_state) return true; + sdk_os_delay_us(interval); + } + + return false; +} + +/* + * + * + * @pin the selected GPIO pin + * @humidity pointer to external int8_t to store resulting humidity value + * @temperature pointer to external int8_t to store resulting temperature value +*/ + +bool dht_fetch_data(int8_t pin, int8_t * humidity, int8_t * temperature) { + int8_t data[40] = {0}; + int8_t result[5] = {0}; + uint8_t i = 0; + + uint8_t init_phase_duration = 0; + uint8_t duration_data_low = 0; + uint8_t duration_data_high = 0; + + gpio_enable(pin, GPIO_OUT_OPEN_DRAIN); + + taskENTER_CRITICAL(); + + // Phase 'A' pulling signal low to initiate read sequence + gpio_write(pin, 0); + sdk_os_delay_us(20000); + gpio_write(pin, 1); + + // Step through Phase 'B' at 2us intervals, 40us max + if (dht_await_pin_state(pin, 2, 40, false, &init_phase_duration)) { + // Step through Phase 'C ' at 2us intervals, 88us max + if (dht_await_pin_state(pin, 2, 88, true, &init_phase_duration)) { + // Step through Phase 'D' at 2us intervals, 88us max + if (dht_await_pin_state(pin, 2, 88, false, &init_phase_duration)) { + + // Read in each of the 40 bits of data... + for (i = 0; i < 40; i++) { + if (dht_await_pin_state(pin, 2, 60, true, &duration_data_low)) { + if (dht_await_pin_state(pin, 2, 75, false, &duration_data_high)) { + data[i] = duration_data_high > duration_data_low; + } + } + } + + taskEXIT_CRITICAL(); + + for (i = 0; i < 40; i++) { + // Read each bit into 'result' byte array... + result[i/8] <<= 1; + result[i/8] |= data[i]; + } + + if (result[4] == ((result[0] + result[1] + result[2] + result[3]) & 0xFF)) { + // Data valid, checksum succeeded... + *humidity = result[0]; + *temperature = result[2]; + debug("Successfully retrieved sensor data..."); + return true; + } else { + debug("Checksum failed, invalid data received from sensor..."); + } + + } else { + debug("Initialization error, problem in phase 'D'..."); + } + } else { + debug("Initialization error, problem in phase 'C'..."); + } + } else { + debug("Initialization error, problem in phase 'B'..."); + } + + taskEXIT_CRITICAL(); + return false; +} + diff --git a/extras/dht/dht.h b/extras/dht/dht.h new file mode 100644 index 0000000..c8a5ed9 --- /dev/null +++ b/extras/dht/dht.h @@ -0,0 +1,17 @@ +/* + * Part of esp-open-rtos + * Copyright (C) 2016 Jonathan Hartsuiker (https://github.com/jsuiker) + * BSD Licensed as described in the file LICENSE + * + */ + +#ifndef __DTH_H__ +#define __DHT_H__ + +#include "FreeRTOS.h" + +bool dht_wait_for_pin_state(uint8_t pin, uint8_t interval, uint8_t timeout, bool expected_pin_sate, uint8_t * counter); +bool dht_fetch_data(int8_t pin, int8_t * humidity, int8_t * temperature); + +#endif + From fee987d5cf22c297fb2de53c6623e8a49653d5f9 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Sat, 7 May 2016 18:33:43 +1000 Subject: [PATCH 07/44] Startup code: Move user_start_phase2 to irom section This saves 1020 bytes from the text (IRAM) section by preventing inlining of user_start_phase2() (and dump_flash_config_sectors() as well) into the IRAM function sdk_user_start(). --- core/app_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/app_main.c b/core/app_main.c index 2f4f7f1..19bf73e 100644 --- a/core/app_main.c +++ b/core/app_main.c @@ -411,7 +411,7 @@ extern void (*__init_array_start)(void); extern void (*__init_array_end)(void); // .Lfunc009 -- .irom0.text+0x5b4 -static void user_start_phase2(void) { +static __attribute__((noinline)) void user_start_phase2(void) { uint8_t *buf; uint8_t *phy_info; @@ -483,7 +483,7 @@ static void dump_flash_sector(uint32_t start_sector, uint32_t length) { } // .Lfunc011 -- .irom0.text+0x790 -static void dump_flash_config_sectors(uint32_t start_sector) { +static __attribute__((noinline)) void dump_flash_config_sectors(uint32_t start_sector) { printf("system param error\n"); // Note: original SDK code didn't dump PHY info printf("phy_info:\n"); From 57c718d8352fe69df89e82cee76e1fe35b551f7a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 May 2016 07:34:54 +1000 Subject: [PATCH 08/44] Travis: Work around 4MB log limit when building all examples --- .travis.yml | 25 +++++------------- extras/mbedtls/mbedtls | 2 +- utils/travis_build/install_esptool2.sh | 15 +++++++++++ utils/travis_build/install_toolchain.sh | 35 +++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 20 deletions(-) create mode 100755 utils/travis_build/install_esptool2.sh create mode 100755 utils/travis_build/install_toolchain.sh diff --git a/.travis.yml b/.travis.yml index a5df0d6..ca9ff59 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ env: ESPTOOL2_COMMIT=ec0e2c7 ESPTOOL2_DIR="${HOME}/esptool2-${ESPTOOL2_COMMIT}" PATH=${PATH}:${CROSS_BINDIR}:${ESPTOOL2_DIR} + CROSS="ccache xtensa-lx106-elf-" + MAKE_CMD="make WARNINGS_AS_ERRORS=1 -C examples/ build-examples" cache: directories: - ${CROSS_ROOT} @@ -35,27 +37,12 @@ addons: - git before_install: - # Install a toolchain using esp-open-sdk (parts we need for this are the GNU toolchain and libhal) - # - # Adds hack of "{$HAS_TC} || -Buildstep-" to avoid rebuilding toolchain if it's already - # installed from the cache. If this gets any more complex it should be spun out to a standalone shell script. - - export HAS_TC="test -d ${CROSS_BINDIR}" - - unset CC # Travis sets this due to "language: c", but it confuses autotools configure when cross-building - - ${HAS_TC} || git clone --recursive https://github.com/pfalcon/esp-open-sdk.git - - ${HAS_TC} || cd esp-open-sdk - - ${HAS_TC} || git reset --hard ${OPENSDK_COMMIT} - - ${HAS_TC} || git submodule update - - ${HAS_TC} || sed -i "s/2.69/2.68/" lx106-hal/configure.ac # this is a nasty hack as Ubuntu Precise only has autoconf 2.68 not 2.69... - - ${HAS_TC} || sed -r -i 's%TOOLCHAIN ?=.*%TOOLCHAIN=${CROSS_ROOT}%' Makefile - - ${HAS_TC} || make toolchain esptool libhal STANDALONE=n - - export HAS_ET2="test -f ${ESPTOOL2_DIR}/esptool2" - - ${HAS_ET2} || git clone https://github.com/raburton/esptool2 ${ESPTOOL2_DIR} - - ${HAS_ET2} || cd ${ESPTOOL2_DIR} - - ${HAS_ET2} || git reset --hard ${ESPTOOL2_COMMIT} - - ${HAS_ET2} || make + - utils/travis_build/install_esptool2.sh + - travis_wait 30 utils/travis_build/install_toolchain.sh script: - cd ${TRAVIS_BUILD_DIR} # Remove ssid_config requirement for examples - sed -i "s%#error%//#error%" include/ssid_config.h - - make WARNINGS_AS_ERRORS=1 -C examples/ build-examples CROSS="ccache xtensa-lx106-elf-" V=1 + # Don't verbose-build all examples (too much output), only verbose-build errors + - ( ${MAKE_CMD} ) || ( ${MAKE_CMD} V=1 ) diff --git a/extras/mbedtls/mbedtls b/extras/mbedtls/mbedtls index a7ffc8f..0a0c22e 160000 --- a/extras/mbedtls/mbedtls +++ b/extras/mbedtls/mbedtls @@ -1 +1 @@ -Subproject commit a7ffc8f7396573bec401e0afcc073137522d5305 +Subproject commit 0a0c22e0efcf2f8f71d7e16712f80b8f77326f72 diff --git a/utils/travis_build/install_esptool2.sh b/utils/travis_build/install_esptool2.sh new file mode 100755 index 0000000..102fd6f --- /dev/null +++ b/utils/travis_build/install_esptool2.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -uv + +# Called by Travis to install esptool2 to generate OTA-compatible build +# images + +if test -f ${ESPTOOL2_DIR}/esptool2; then + echo "Using cached esptool2" + exit 0 +fi + +git clone https://github.com/raburton/esptool2 ${ESPTOOL2_DIR} +cd ${ESPTOOL2_DIR} +git reset --hard ${ESPTOOL2_COMMIT} +make diff --git a/utils/travis_build/install_toolchain.sh b/utils/travis_build/install_toolchain.sh new file mode 100755 index 0000000..905df90 --- /dev/null +++ b/utils/travis_build/install_toolchain.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -euv + +# Called by Travis to install a toolchain using esp-open-sdk (parts we +# ne for esp-open-rtos are the GNU toolchain, libhal, and esptool.py.) + +if test -d ${CROSS_BINDIR}; then + echo "Using cached toolchain in ${CROSS_BINDIR}" + exit 0 +fi + +# Travis sets this due to "language: c", but it confuses autotools configure when cross-building +unset CC + +git clone --recursive https://github.com/pfalcon/esp-open-sdk.git +cd esp-open-sdk +git reset --hard ${OPENSDK_COMMIT} +git submodule update --init + +# this is a nasty hack as Ubuntu Precise only has autoconf 2.68 not 2.69... +sed -i "s/2.69/2.68/" lx106-hal/configure.ac + +# build the toolchain relative to the CROSS_ROOT directory +sed -r -i 's%TOOLCHAIN ?=.*%TOOLCHAIN=${CROSS_ROOT}%' Makefile + +# will dump log on failure +echo "Building toolchain without live progress, as progress spinner fills up log..." + +if !( make toolchain esptool libhal STANDALONE=n 2>&1 > make.log ); then + cat make.log + echo "Exiting due to failed toolchain build" + exit 3 +fi + +echo "Toolchain build completed in ${CROSS_ROOT}." From b83d4a42934f9a9a4fa59e7594f2387b7b088a82 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Sun, 15 May 2016 09:21:41 +1000 Subject: [PATCH 09/44] Travis: Update esp-open-sdk commit to work around rewritten history on submodule --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ca9ff59..bb04286 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: c sudo: false env: # Target commit for https://github.com/pfalcon/esp-open-sdk/ - OPENSDK_COMMIT=e33c0ad3 + OPENSDK_COMMIT=a48b12f CROSS_ROOT="${HOME}/toolchain-${OPENSDK_COMMIT}" CROSS_BINDIR="${CROSS_ROOT}/bin" ESPTOOL2_COMMIT=ec0e2c7 From b61d06e940d48cfe277b92b10d1dee6b35c99052 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 7 Apr 2016 17:23:30 +1000 Subject: [PATCH 10/44] Wrap structure around phy_info PHY initialisation settings Add notes based on testing some of the values found there. --- core/app_main.c | 51 ++-- core/include/sdk_internal.h | 3 +- core/phy_info.c | 161 ++++++++++++ include/espressif/phy_info.h | 482 +++++++++++++++++++++++++++++++++++ 4 files changed, 661 insertions(+), 36 deletions(-) create mode 100644 core/phy_info.c create mode 100644 include/espressif/phy_info.h diff --git a/core/app_main.c b/core/app_main.c index 19bf73e..45f7e2f 100644 --- a/core/app_main.c +++ b/core/app_main.c @@ -24,6 +24,7 @@ #include "os_version.h" #include "espressif/esp_common.h" +#include "espressif/phy_info.h" #include "sdk_internal.h" /* This is not declared in any header file (but arguably should be) */ @@ -31,8 +32,6 @@ void user_init(void); #define BOOT_INFO_SIZE 28 -//TODO: phy_info should probably be a struct (no idea about its organization, though) -#define PHY_INFO_SIZE 128 // These are the offsets of these values within the RTCMEM regions. It appears // that the ROM saves them to RTCMEM before calling us, and we pull them out of @@ -45,26 +44,6 @@ void user_init(void); extern uint32_t _bss_start; extern uint32_t _bss_end; -// .Ldata003 -- .irom.text+0x0 -static const uint8_t IROM default_phy_info[PHY_INFO_SIZE] = { - 0x05, 0x00, 0x04, 0x02, 0x05, 0x05, 0x05, 0x02, - 0x05, 0x00, 0x04, 0x05, 0x05, 0x04, 0x05, 0x05, - 0x04, 0xfe, 0xfd, 0xff, 0xf0, 0xf0, 0xf0, 0xe0, - 0xe0, 0xe0, 0xe1, 0x0a, 0xff, 0xff, 0xf8, 0x00, - 0xf8, 0xf8, 0x52, 0x4e, 0x4a, 0x44, 0x40, 0x38, - 0x00, 0x00, 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xe1, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x93, 0x43, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - // user_init_flag -- .bss+0x0 uint8_t sdk_user_init_flag; @@ -82,7 +61,7 @@ xTaskHandle sdk_xWatchDogTaskHandle; static void IRAM get_otp_mac_address(uint8_t *buf); static void IRAM set_spi0_divisor(uint32_t divisor); static void zero_bss(void); -static void init_networking(uint8_t *phy_info, uint8_t *mac_addr); +static void init_networking(sdk_phy_info_t *phy_info, uint8_t *mac_addr); static void init_g_ic(void); static void dump_excinfo(void); static void user_start_phase2(void); @@ -273,7 +252,7 @@ static void zero_bss(void) { } // .Lfunc006 -- .irom0.text+0x70 -static void init_networking(uint8_t *phy_info, uint8_t *mac_addr) { +static void init_networking(sdk_phy_info_t *phy_info, uint8_t *mac_addr) { if (sdk_register_chipv6_phy(phy_info)) { printf("FATAL: sdk_register_chipv6_phy failed"); halt(); @@ -413,7 +392,7 @@ extern void (*__init_array_end)(void); // .Lfunc009 -- .irom0.text+0x5b4 static __attribute__((noinline)) void user_start_phase2(void) { uint8_t *buf; - uint8_t *phy_info; + sdk_phy_info_t phy_info, default_phy_info; sdk_system_rtc_mem_read(0, &sdk_rst_if, sizeof(sdk_rst_if)); if (sdk_rst_if.version > 3) { @@ -431,8 +410,16 @@ static __attribute__((noinline)) void user_start_phase2(void) { sdk_info._unknown1 = 0x00ffffff; sdk_info._unknown2 = 0x0104a8c0; init_g_ic(); - phy_info = malloc(PHY_INFO_SIZE); - sdk_spi_flash_read(sdk_flashchip.chip_size - sdk_flashchip.sector_size * 4, (uint32_t *)phy_info, PHY_INFO_SIZE); + + read_saved_phy_info(&phy_info); + get_default_phy_info(&default_phy_info); + + if (phy_info.version != default_phy_info.version) { + /* Versions don't match, use default for PHY info + (may be a blank config sector, or a new default version.) + */ + memcpy(&phy_info, &default_phy_info, sizeof(sdk_phy_info_t)); + } // Disable default buffering on stdout setbuf(stdout, NULL); @@ -440,13 +427,7 @@ static __attribute__((noinline)) void user_start_phase2(void) { uart_flush_txfifo(0); uart_flush_txfifo(1); - if (phy_info[0] != 5) { - // Bad version byte. Discard what we read and use default values - // instead. - memcpy(phy_info, default_phy_info, PHY_INFO_SIZE); - } - init_networking(phy_info, sdk_info.sta_mac_addr); - free(phy_info); + init_networking(&phy_info, sdk_info.sta_mac_addr); // Call gcc constructor functions void (**ctor)(void); @@ -487,7 +468,7 @@ static __attribute__((noinline)) void dump_flash_config_sectors(uint32_t start_s printf("system param error\n"); // Note: original SDK code didn't dump PHY info printf("phy_info:\n"); - dump_flash_sector(start_sector, PHY_INFO_SIZE); + dump_flash_sector(start_sector, sizeof(sdk_phy_info_t)); printf("\ng_ic saved 0:\n"); dump_flash_sector(start_sector + 1, sizeof(struct sdk_g_ic_saved_st)); printf("\ng_ic saved 1:\n"); diff --git a/core/include/sdk_internal.h b/core/include/sdk_internal.h index 5a5aee5..b8233fd 100644 --- a/core/include/sdk_internal.h +++ b/core/include/sdk_internal.h @@ -3,6 +3,7 @@ #include "espressif/esp_wifi.h" #include "espressif/spi_flash.h" +#include "espressif/phy_info.h" #include "lwip/netif.h" /////////////////////////////////////////////////////////////////////////////// @@ -217,7 +218,7 @@ void sdk_phy_enable_agc(void); void sdk_pm_attach(void); void sdk_pp_attach(void); void sdk_pp_soft_wdt_init(void); -int sdk_register_chipv6_phy(uint8_t *); +int sdk_register_chipv6_phy(sdk_phy_info_t *); void sdk_sleep_reset_analog_rtcreg_8266(void); uint32_t sdk_system_get_checksum(uint8_t *, uint32_t); void sdk_system_restart_in_nmi(void); diff --git a/core/phy_info.c b/core/phy_info.c new file mode 100644 index 0000000..8d83d1b --- /dev/null +++ b/core/phy_info.c @@ -0,0 +1,161 @@ +/* Routines to allow custom access to the Internal Espressif + SDK PHY datastructures. + + Matches espressif/phy_internal.h + + Part of esp-open-rtos. Copyright (C) 2016 Angus Gratton, + BSD Licensed as described in the file LICENSE. + */ +#include +#include +#include +#include + +static const sdk_phy_info_t IROM default_phy_info = { + ._reserved00 = { 0x05, 0x00, 0x04, 0x02, 0x05 }, + .version = 5, + ._reserved06 = { 0x05, 0x02, 0x05, 0x00, 0x04, 0x05, 0x05, 0x04, + 0x05, 0x05, 0x04,-0x02,-0x03,-0x01,-0x10,-0x10, + -0x10,-0x20,-0x20, -0x20}, + .spur_freq_primary = 225, + .spur_freq_divisor = 10, + .spur_freq_en_h = 0xFF, + .spur_freq_en_l = 0xFF, + + ._reserved1e = { 0xf8, 0, 0xf8, 0xf8 }, + + .target_power = { 82, 78, 74, 68, 64, 56 }, + .target_power_index_mcs = { 0, 0, 1, 1, 2, 3, 4, 5 }, + + .crystal_freq = CRYSTAL_FREQ_26M, + + .sdio_config = SDIO_CONFIG_AUTO, + + .bt_coexist_config = BT_COEXIST_CONFIG_NONE, + .bt_coexist_protocol = BT_COEXIST_PROTOCOL_WIFI_ONLY, + + .dual_ant_config = DUAL_ANT_CONFIG_NONE, + + ._reserved34 = 0x02, + + .crystal_sleep = CRYSTAL_SLEEP_OFF, + + .spur_freq_2_primary = 225, + .spur_freq_2_divisor = 10, + .spur_freq_2_en_h = 0x00, + .spur_freq_2_en_l = 0x00, + .spur_freq_cfg_msb = 0x00, + .spur_freq_2_cfg_msb = 0x00, + .spur_freq_3_cfg = 0x0000, + .spur_freq_4_cfg = 0x0000, + + ._reserved4a = { 0x01, 0x93, 0x43, 0x00 }, + + .low_power_en = false, + .lp_atten_stage01 = LP_ATTEN_STAGE01_23DB, + .lp_atten_bb = 0, + + .pwr_ind_11b_en = false, + .pwr_ind_11b_0 = 0, + .pwr_ind_11b_1 = 0, + + /* Nominal 3.3V VCC. NOTE: This value is 0 in the + esp-open-rtos SDK default config sector, and may be unused + by that version of the SDK? + */ + .pa_vdd = 33, + + /* Note: untested with the esp-open-rtos SDK default config sector, may be unused? */ + .freq_correct_mode = FREQ_CORRECT_DISABLE, + .force_freq_offset = 0, + + /* Note: is zero with the esp-open-rtos SDK default config sector, may be unused? */ + .rf_cal_mode = RF_CAL_MODE_SAVED, +}; + +void get_default_phy_info(sdk_phy_info_t *info) __attribute__((weak, alias("get_sdk_default_phy_info"))); + +void get_sdk_default_phy_info(sdk_phy_info_t *info) +{ + memcpy(info, &default_phy_info, sizeof(sdk_phy_info_t)); +} + +void read_saved_phy_info(sdk_phy_info_t *info) +{ + sdk_spi_flash_read(sdk_flashchip.chip_size - sdk_flashchip.sector_size * 4, (uint32_t *)info, sizeof(sdk_phy_info_t)); +} + +void write_saved_phy_info(const sdk_phy_info_t *info) +{ + sdk_spi_flash_write(sdk_flashchip.chip_size - sdk_flashchip.sector_size * 4, (uint32_t *)info, sizeof(sdk_phy_info_t)); +} + +void dump_phy_info(const sdk_phy_info_t *info, bool raw) +{ + printf("version=%d\n", info->version); + printf("spur_freq = %.3f (%d/%d)\n", + (float)info->spur_freq_primary / info->spur_freq_divisor, + info->spur_freq_primary, + info->spur_freq_divisor); + printf("spur_freq_en = 0x%02x 0x%02x\n", info->spur_freq_en_h, + info->spur_freq_en_l); + printf("target_power\n"); + for(int i = 0; i < 6; i++) { + printf(" %d: %.2fdB (raw 0x%02x)\n", i, + info->target_power[i]/4.0, + info->target_power[i]); + } + printf("target_power_index_mcs:"); + for(int i = 0; i < 8; i++) { + printf(" %d%c", info->target_power_index_mcs[i], + i == 7 ? '\n' : ','); + } + + printf("crystal_freq: %s (raw %d)\n", + (info->crystal_freq == CRYSTAL_FREQ_40M ? "40MHz" : + (info->crystal_freq == CRYSTAL_FREQ_26M ? "26MHz" : + (info->crystal_freq == CRYSTAL_FREQ_24M ? "24MHz" : "???"))), + info->crystal_freq); + + printf("sdio_config: %d\n", info->sdio_config); + printf("bt_coexist config: %d protocol: 0x%02x\n", + info->bt_coexist_config, info->bt_coexist_protocol); + printf("dual_ant_config: %d\n", info->dual_ant_config); + + printf("crystal_sleep: %d\n", info->crystal_sleep); + + printf("spur_freq_2 = %.3f (%d/%d)\n", + (float)info->spur_freq_2_primary / info->spur_freq_2_divisor, + info->spur_freq_2_primary, + info->spur_freq_2_divisor); + printf("spur_freq_2_en = 0x%02x 0x%02x\n", info->spur_freq_2_en_h, + info->spur_freq_2_en_l); + + printf("spur_freq_cfg_msb = 0x%02x\n", info->spur_freq_cfg_msb); + printf("spur_freq_2_)cfg_msb = 0x%02x\n", info->spur_freq_2_cfg_msb); + printf("spur_freq_3_cfg = 0x%04x\n", info->spur_freq_3_cfg); + printf("spur_freq_4_cfg = 0x%04x\n", info->spur_freq_4_cfg); + + printf("low_power_en = %d\n", info->low_power_en); + printf("lp_atten_stage01 = 0x%02x\n", info->lp_atten_stage01); + printf("lp_atten_bb = %.2f (raw 0x%02x)\n", info->lp_atten_bb / 4.0, + info->lp_atten_bb); + + printf("pa_vdd = %d\n", info->pa_vdd); + + printf("freq_correct_mode = 0x%02x\n", info->freq_correct_mode); + printf("force_freq_offset = %d\n", info->force_freq_offset); + printf("rf_cal_mode = 0x%02x\n", info->rf_cal_mode); + + if(raw) { + printf("Raw values:"); + uint8_t *p = (uint8_t *)info; + for(int i = 0; i < sizeof(sdk_phy_info_t); i ++) { + if(i % 8 == 0) { + printf("\n0x%02x:", i); + } + printf(" %02x", p[i]); + } + printf("\n\n"); + } +} diff --git a/include/espressif/phy_info.h b/include/espressif/phy_info.h new file mode 100644 index 0000000..bda23ea --- /dev/null +++ b/include/espressif/phy_info.h @@ -0,0 +1,482 @@ +/** Internal Espressif SDK "PHY info" data structure + + The data structure (sdk_phy_info_t) is used to configure the + ESP8266 PHY layer via the SDK. The fields here are not written + directly to hardware, the SDK code (mostly in libphy) parses this + structure and configures the hardware. + + The structure loaded at reset time from a flash configuration + sector (see read_saved_phy_info()) (Espressif's SDK sources this + from a file "esp_init_data_default.bin"). If no valid structure is + found in the flash config sector then the SDK loads default values + (see get_default_phy_info()). It is possible to implement a custom + get_default_phy_info() to change the PHY default settings (see the + 'version' field below). + + @note It is possible that the SDK will quietly write a new + configuration sector to flash itself following internal + calibration, etc. However this does not seem to happen, you need to + flash it explicitly if you want it stored there. + + @note Most of what is below is unconfirmed, except where a @note + says that it has been confirmed to work as expected. Please + consider submitting notes if you find behaviour here that works or + doesn't work as expected. + + Information on the meaning/offset of fields comes from Espressif's + flash download tool, which uses an Excel spreadsheet (in the + init_data directory of the ZIP file) to configure and a Python + script to convert an esp_init_data_custom.bin file to flash: + http://bbs.espressif.com/viewtopic.php?f=5&t=433 + + Many fields remain undocumented (& disassembly of libphy suggests + that some documented fields supported undocumented values.) + + A few additional notes about the phy_info fields can be found + in the ESP Arduino ESP8266 phy_init_data structure (however most of + that content is verbatim from Espressif's spreadsheet): + https://github.com/esp8266/Arduino/blob/master/cores/esp8266/core_esp8266_phy.c#L29 + + Part of esp-open-rtos. Copyright (C) 2016 Angus Gratton, + BSD Licensed as described in the file LICENSE. + */ +#ifndef _ESPRESSIF_PHY_INFO_H +#define _ESPRESSIF_PHY_INFO_H + +#include +#include +#include + +/* CRYSTAL_FREQ_xx values as used by sdk_phy_info_t.crystal_freq */ +#define CRYSTAL_FREQ_40M 0 +#define CRYSTAL_FREQ_26M 1 +#define CRYSTAL_FREQ_24M 2 + +/* SDIO_CONFIG_xx values as used by sdk_phy_info_t.sdio_config */ +#define SDIO_CONFIG_AUTO 0 /* Uses pin strapping to determine */ +#define SDIO_CONFIG_SDIOV1_1 /* Data output on negative edge */ +#define SDIO_CONFIG_SDIOV2_0 /* data output on positive edge */ + +/* BT_COEXIST_CONFIG_xx values as used by sdk_phy_info_t.bt_coexist */ +/* No bluetooth */ +#define BT_COEXIST_CONFIG_NONE 0 +/* Coexistence configuration A: + GPIO 0 - WLAN_ACTIVE + GPIO 14 - BT_ACTIVE + GPIO 13 - BT_PRIORITY + GPIO 3 - ANT_SEL_BT +*/ +#define BT_COEXIST_CONFIG_A 1 +/* No coexistence, but Bluetooth enabled? + Unsure how this works? + */ +#define BT_COEXIST_CONFIG_PRESENT 2 +/* Coexistence configuration B: + GPIO 0 - WLAN_ACTIVE + GPIO 14 - BT_PRIORITY + GPIO 13 - BT_ACTIVE + GPIO 3 - ANT_SEL_BT +*/ +#define BT_COEXIST_CONFIG_B 3 + +/* BT_COEXIST_PROTOCOL_xx values for coexistence protocol, + field sdk_phy_info_t.bt_coexist_protocol + */ +#define BT_COEXIST_PROTOCOL_WIFI_ONLY 0 +#define BT_COEXIST_PROTOCOL_BT_ONLY 1 + +/* Coexistence is enabled, Bluetooth has its own antenna */ +#define BT_COEXIST_PROTOCOL_FLAG_SEPARATE_ANT 2 +/* Coexistence is enabled, Bluetooth shares WiFi antenna */ +#define BT_COEXIST_PROTOCOL_FLAG_SHARE_ANT 4 + +/* Coexistence is enabled, use only BT_ACTIVE signal */ +#define BT_COEXIST_PROTOCOL_FLAG_BT_ACTIVE_ONLY 0 +/* Coexistence is enabled, use both BT_ACTIVE and BT_PRIORITY signals */ +#define BT_COEXIST_PROTOCOL_FLAG_BT_ACTIVE_PRIORITY 1 + +/* DUAL_ANT_CONFIG_xx values for dual antenna config, + field sdk_phy_info_t.dual_ant_config + + (Not really clear how this feature works, if at all.) +*/ +#define DUAL_ANT_CONFIG_NONE 0 +/* antenna diversity for WiFi, use GPIO0 + U0RXD (?) */ +#define DUAL_ANT_CONFIG_DUAL 1 +/* TX/RX switch for external PA & LNA: GPIO 0 high, GPIO 3 low during TX */ +#define DUAL_ANT_CONFIG_TX_GPIO0_HIGH_GPIO3_LOW +/* TX/RX switch for external PA & LNA: GPIO 0 low, GPIO 3 high during TX */ +#define DUAL_ANT_CONFIG_TX_GPIO0_LOW_GPIO3_HIGH + + +/* CRYSTAL_SLEEP_xx values used for sdk_phy_info_t.crystal_sleep + */ +#define CRYSTAL_SLEEP_OFF 0 +#define CRYSTAL_SLEEP_ON 1 +#define CRYSTAL_SLEEP_GPIO16 2 +#define CRYSTAL_SLEEP_GPIO2 3 + +/* RF Stage 0 & 1 attenuation constants. Use for sdk_phy_info_t.lp_atten_stage01 + + @note These values have been tested and are confirmed to work as + expected by measuring RSSI w/ rt73 USB adapter in monitor mode + (some values also checked on spectrum analyzer) - provided + low_power_en is set then the signal is attenuated as per this + setting. + + (It may look like LP_ATTEN_STAGE01_11_5DB is out of order, but + according to monitor mode captures this is the correct ordering of + these constants.) + + Setting the numeric values in between these constants appears to + also attenuate the signal, but not necessarily by the amount you'd + expect. +*/ +#define LP_ATTEN_STAGE01_0DB 0x0f /* 0dB */ +#define LP_ATTEN_STAGE01_2_5DB 0x0e /* -2.5dB */ +#define LP_ATTEN_STAGE01_6DB 0x0d /* -6dB */ +#define LP_ATTEN_STAGE01_8_5DB 0x09 /* -8.5dB */ +#define LP_ATTEN_STAGE01_11_5DB 0x0c /* -11.5dB */ +#define LP_ATTEN_STAGE01_14DB 0x08 /* -14dB */ +#define LP_ATTEN_STAGE01_17_5DB 0x04 /* -17.5dB */ +#define LP_ATTEN_STAGE01_23DB 0x00 /* -23dB */ + +/* Constant for sdk_phy_info_t.pa_vdd */ +#define PA_VDD_MEASURE_VCC 0xFF + +/* Bitmask flags for sdk_phy_info_t.freq_correct_mode */ + +/* Set this flag to disable frequency offset correction */ +#define FREQ_CORRECT_DISABLE 0 + +/* Set this flag to enable frequency offset correction */ +#define FREQ_CORRECT_ENABLE BIT(0) + +/* Set = Baseband PLL frequency is 160MHz (can only apply +ve offset) + * Unset = Baseband PLL frequency is 168MHz (can apply +ve/-ve offset */ +#define FREQ_CORRECT_BB_160M BIT(1) + +/* Set = use force_freq_offset field to correct, Unset = automatically + measure & correct offset +*/ +#define FREQ_CORRECT_FORCE BIT(2) + + +/* RF_CAL_MODE_xx fields used for sdk_phy_info_t.rf_cal_mode + */ +/* Use saved RF CAL data from flash, only. RF init takes 2ms. */ +#define RF_CAL_MODE_SAVED 0 +/* Calibrate TX power control only, use saved RF CAL data for others. + RF init takes 20ms. */ +#define RF_CAL_MODE_TXPOWER_ONLY 1 +/* Unclear if/how this mode is different to 2? */ +#define RF_CAL_MODE_SAVED_2 2 +/* Run full RF CAL routine. RF init takes approx 200ms. */ +#define RF_CAL_MODE_FULL 3 + +/* Data structure that maps to the phy_info configuration block */ +typedef struct __attribute__((packed)) { + uint8_t _reserved00[0x05]; /* 0x00 - 0x04 */ + + /* This "version" field was set to 5 in the SDK phy_info, + and the original SDK startup code checks it is 5 and then loads + default PHY configuration otherwise. + + esp-open-rtos will load phy_info from get_default_phy_info() if + the value stored in flash has a different value to the value + returned in get_default_phy_info(). This means you can + increment the version return by get_default_phy_info() (to any + value but 0xFF), and know that the new defaults will replace + any older stored values. + + @notes It's not clear whether this is actually a version field + (the other 24 bytes here have equally arbitrary numbers in + them.) Changing the "version" to other values does not seem to + effect WiFi performance at all, neither does zeroing out the + first 5 reserved bytes in _reserved00. However zeroing bytes in + the _reserved06 region will break WiFi entirely. + */ + uint8_t version; /* 0x05 */ + int8_t _reserved06[0x14]; /* 0x06 - 0x19 */ + + /* spur_freq = spur_freq_primary / spur_freq_divisor */ + uint8_t spur_freq_primary; /* 0x1a */ + uint8_t spur_freq_divisor; /* 0x1b */ + + /* Bitmask to enable spur_freq for each channel + Appears to be a big endian short word? + */ + uint8_t spur_freq_en_h; /* 0x1c */ + uint8_t spur_freq_en_l; /* 0x1d */ + + uint8_t _reserved1e[4]; /* 0x1e - 0x21 */ + + /* Each value is a target power level. + Units are 1/4 dBm ie value 64 = 16dBm. + + SDK defaults to using these transmit powers: + 20.5dBm, 19.5dBm, 18.5dBm, 17dBm, 16dBm, 14dBm + + @note Adjusting these values is confirmed to reduce + transmit power accordingly. + */ + uint8_t target_power[6]; /* 0x22 - 0x27 */ + + /* Maps 8 MCS (modulation & coding schemes) types for 802.11b, g & + * n to a target_power level index (0-5), set above. + + This mapping of MCS slot to MCS type is derived from the + spreadsheet and also a table sent by Espressif, but is untested + and may be SDK version dependendent (especially any 802.11n + rates). However the general relationship is confirmed to hold + (higher MCS index = higher bit rate). + + MCS 0: 1Mbps/2Mbps/5.5Mbps/11Mbps (802.11b) / 6Mbps/9Mbps (802.11g) + default target_power 0 (default 20.5dBm) + (see also pwr_ind_11b_en) + + MCS 1: 12Mbps (802.11g) + default target_power 0 (default 20.5dBm) + + MCS 2: 18Mbps (802.11g) + default target_power 1 (19.5dBm) + + MCS 3: 24Mbps (802.11g) + default target_power 1 (19.5dBm) + + MCS 4: 36Mbps (802.11g) + default target_power 2 (18.5dBm) + + MCS 5: 48Mbps (802.11g) + default target_power 3 (17dBm) + + MCS 6: 54Mbps (802.11g) + default target_power 4 (16dBm) + + MCS 7: 65Mbps (802.11n) - unclear if ever used? + default target_power 5 (14dBm) + */ + uint8_t target_power_index_mcs[8]; /* 0x28 - 0x2f */ + + /* One of CRYSTAL_FREQ_40M / CRYSTAL_FREQ_26M / CRYSTAL_FREQ_24M + + The crystal configured here is the input to the PLL setting + calculations which are used to derive the CPU & APB peripheral + clock frequency, and probably the WiFi PLLs (unconfirmed.) + */ + uint8_t crystal_freq; /* 0x30 */ + + uint8_t _unused31; /* 0x31: Possibly high byte of crystal freq? */ + + /* One of SDIO_CONFIG_AUTO, SDIO_CONFIG_SDIOV1_1, SDIO_CONFIG_SDIOV2_0 */ + uint8_t sdio_config; /* 0x32 */ + + /* BT coexistence pin configuration. + + One of BT_COEXIST_CONFIG_NONE, BT_COEXIST_CONFIG_A, + BT_COEXIST_CONFIG_PRESENT, BT_COEXIST_CONFIG_B + */ + uint8_t bt_coexist_config; /* 0x33 */ + + /* BT coexistence pin protocol. + + If no coexistence: + Either BT_COEXIST_PROTOCOL_WIFI_ONLY, or + BT_COEXIST_PROTOCOL_BT_ONLY. + + If coexistence: + Combine one of + BT_COEXIST_PROTOCOL_FLAG_SEPARATE_ANT or + BT_COEXIST_PROTOCOL_FLAG_SHARE_ANT + with one of + BT_COEXIST_PROTOCOL_FLAG_BT_ACTIVE_ONLY or + BT_COEXIST_PROTOCOL_FLAG_BT_ACTIVE_BT_PRIORITY + */ + uint8_t bt_coexist_protocol; /* 0x34 */ + + /* Dual antenna configuration + + One of DUAL_ANT_CONFIG_NONE, DUAL_ANT_CONFIG_DUAL, + DUAL_ANT_CONFIG_TX_GPIO0_HIGH_GPIO3_LOW, + DUAL_ANT_CONFIG_TX_GPIO0_LOW_GPIO3_HIGH + */ + uint8_t dual_ant_config; /* 0x35 */ + + uint8_t _reserved34; /* 0x36 */ + + /* For sharing crystal clock with other devices: + one of CRYSTAL_SLEEP_OFF, CRYSTAL_SLEEP_ON, + CRYSTAL_SLEEP_GPIO16, CRYSTAL_SLEEP_GPIO2 + */ + uint8_t crystal_sleep; /* 0x37 */ + + uint8_t _unused38[8]; + + /* spur_freq_2 = spur_freq_2_primary / spur_freq_2_divisor */ + uint8_t spur_freq_2_primary; /* 0x40 */ + uint8_t spur_freq_2_divisor; /* 0x41 */ + + /* Bitmask to enable spur_freq_2 for each channel? + Appears to be a big endian short word? + */ + uint8_t spur_freq_2_en_h; /* 0x42 */ + uint8_t spur_freq_2_en_l; /* 0x43 */ + + /* Not really clear what these do */ + uint8_t spur_freq_cfg_msb; /* 0x44 */ + uint8_t spur_freq_2_cfg_msb; /* 0x45 */ + uint16_t spur_freq_3_cfg; /* 0x46 - 0x47 */ + uint16_t spur_freq_4_cfg; /* 0x48 - 0x49 */ + + uint8_t _reserved4a[4]; /* 0x4a - 0x4d */ + + uint8_t _unused78[15]; /* 0x4e - 0x5c */ + + /* Flag to enable low power mode */ + uint8_t low_power_en; /* 0x5d */ + + /* Low Power attenuation of RF gain stages 0 & 1 + + Attenuates transmit power if/when low_power_en is set. + + Use one of the constants LP_ATTEN_STAGE01_0DB, + LP_ATTEN_STAGE01_2_5DB, LP_ATTEN_STAGE01_6DB, + LP_ATTEN_STAGE01_8_5DB, LP_ATTEN_STAGE01_11_5DB, + LP_ATTEN_STAGE01_14DB, LP_ATTEN_STAGE01_17_5DB, + LP_ATTEN_STAGE01_23DB. + */ + uint8_t lp_atten_stage01; /* 0x5e */ + + /* Low Power(?) attenuation of baseband gain + + Units are minus 1/4 dB, ie value 4 == -1dB. + + Maximum value is 24 (0x18) == -6dB + */ + uint8_t lp_atten_bb; /* 0x5f */ + + /* I believe this means, when pwr_ind_11b_en == 0 then the 802.11g + MCS 0 level from target_power_index_mcs are used to + determine 802.11b transmit power level. + + However, when pwr_ind_11b_en == 1 then the index values in + pwr_ind_11b_0 & pwr_ind_11b_1 are used for 802.11b instead. + + This is all unconfirmed, if you can confirm then please update + this comment. + */ + uint8_t pwr_ind_11b_en; /* 0x60 */ + + /* 802.11b low data rate power index (0~5). + Sets the power level index for operation at 1 & 2Mbps + */ + uint8_t pwr_ind_11b_0; /* 0x61 */ + + /* 802.11b high data rate power index (0~5) + Sets the power level index for operation at 5.5 & 11Mbps + */ + uint8_t pwr_ind_11b_1; /* 0x62 */ + + uint8_t _unused63[8]; /* 0x63 - 0x6a */ + + /* Set the voltage of PA_VDD, which appears to be an internal analog + reference voltage(?) + + This field is called vdd33_const in the Arduino phy fields, + and relates to usage of the TOUT pin (ADC pin). + + Set to PA_VDD_MEASURE_VCC (0xFF) and leave TOUT (ADC) pin + floating in order to use the ADC to measure the 3.3V input + voltage. + + Set to value in the range 18-36 (1.8V to 3.6V) to set a + reference voltage(?) when using TOUT pin as an ADC input. I + think this is the reference voltage used to scale the 0-1V + which is allowed on the pin, in order to get an accurate + reading. So it should be set to a value that matches system + VCC... I think! + */ + uint8_t pa_vdd; /* 0x6b */ + + /* Disable RF calibration cycle for this many times */ + uint8_t disable_rfcal_count; /* 0x6c */ + + uint8_t _unused6d[3]; + + /* Flags for frequency correction + + A bitmask combination of any of: FREQ_CORRECT_DISABLE, + FREQ_CORRECT_ENABLE, FREQ_CORRECT_BB_160M, FREQ_CORRECT_FORCE + */ + uint8_t freq_correct_mode; /* 0x70 */ + + /* Force frequency offset adjustment (instead of auto measuring) + units are 1 = 8kHz, full range +/- 1016kHz. + + Only used if FREQ_CORRECT_ENABLE and FREQ_CORRECT_FORCE are + set in freq_correct_mode. + + Unclear whether setting FREQ_CORRECT_BB_160M (which allows only positive offsets) changes the usable range. + */ + int8_t force_freq_offset; /* 0x71 */ + + /* Use stored data in flash for RF calibration. + + This field was previously called rf_cal_use_flash. + + Acceptable values one of RF_CAL_MODE_SAVED, RF_CAL_MODE_TXPOWER_ONLY, RF_CAL_MODE_SAVED_2, RF_CAL_MODE_FULL. + */ + uint8_t rf_cal_mode; /* 0x72 */ + + uint8_t _unused73[13]; +} sdk_phy_info_t; + +/* Some sanity check static assertions. These can probably be + removed after this structure has been better tested. +*/ +_Static_assert(sizeof(sdk_phy_info_t) == 128, "sdk_phy_info_t is wrong size!"); +_Static_assert(offsetof(sdk_phy_info_t, version) == 5, "version at wrong offset"); +_Static_assert(offsetof(sdk_phy_info_t, target_power) == 34, "target_power_qdb at wrong offset"); +_Static_assert(offsetof(sdk_phy_info_t, bt_coexist_protocol) == 52, "bt_coexist_protocol at wrong offset"); +_Static_assert(offsetof(sdk_phy_info_t, spur_freq_2_primary) == 64, "spur_freq_2_primary at wrong offset"); +_Static_assert(offsetof(sdk_phy_info_t, lp_atten_stage01) == 94, "lp_atten_stage01 at wrong offset"); +_Static_assert(offsetof(sdk_phy_info_t, pa_vdd) == 107, "pa_vdd aka vdd33_const at wrong offset"); +_Static_assert(offsetof(sdk_phy_info_t, rf_cal_mode) == 114, "rf_cal_use_flash at wrong offset!"); + +/* Read the default PHY info into the supplied structure. + + This function is weak-aliased to get_sdk_default_phy_info() so you + can replace it with your own if you want to vary the default values + - suggested way to do this is to call get_sdk_default_phy_info() + and then only update the fields you care about. + + The default PHY info is used at startup whenever the version field + in the default sdk_phy_info_t does not match the version field + stored in flash. So you can increment the version field to force a + reset to defaults, regardless of what values are in flash. +*/ +void get_default_phy_info(sdk_phy_info_t *info); + +/* Read the "SDK default" PHY info as used by the Espressif SDK */ +void get_sdk_default_phy_info(sdk_phy_info_t *info); + +/* Read the PHY info currently stored in the SPI flash SDK configuration sector. + + This PHY info is updated by the SDK following RF calibration, etc. + + Note that the saved data may be corrupt - read the 'version' field to verify. +*/ +void read_saved_phy_info(sdk_phy_info_t *info); + +/* Update the saved PHY info in the SPI flash. A reset is necessary to use these values. + + Note that the SDK may clobber these values, so it's recommended you reset ASAP after updating them. +*/ +void write_saved_phy_info(const sdk_phy_info_t *info); + +/* Dump known fields in the phy info structure to stdout, + if 'raw' flag is set then the raw hex values are also dumped. +*/ +void dump_phy_info(const sdk_phy_info_t *info, bool raw); + +#endif From fd20b1a53091a8da529605fca8bebfda2cc6bd56 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Apr 2016 15:43:08 +1000 Subject: [PATCH 11/44] Add PHY hardware management for Bluetooth Coexistence pin choice --- core/esp_phy.c | 32 ++++++++++++++++++++ core/include/esp/gpio.h | 8 ++--- core/include/esp/gpio_regs.h | 15 ++++++++++ core/include/esp/phy.h | 58 ++++++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 core/esp_phy.c create mode 100644 core/include/esp/phy.h diff --git a/core/esp_phy.c b/core/esp_phy.c new file mode 100644 index 0000000..36ce915 --- /dev/null +++ b/core/esp_phy.c @@ -0,0 +1,32 @@ +/** esp/phy.h + * + * PHY hardware management functions. + * + * Part of esp-open-rtos + * Copyright (C) 2016 ChefSteps, Inc + * BSD Licensed as described in the file LICENSE + */ +#include +#include + +void bt_coexist_configure(bt_coexist_mode_t mode, uint8_t bt_active_pin, uint8_t bt_priority_pin) +{ + /* Disable coexistence entirely before changing pin assignments */ + GPIO.OUT &= ~(GPIO_OUT_BT_COEXIST_MASK); + uint32_t new_mask = 0; + + new_mask = VAL2FIELD_M(GPIO_OUT_BT_ACTIVE_PIN, bt_active_pin) + + VAL2FIELD_M(GPIO_OUT_BT_PRIORITY_PIN, bt_priority_pin); + + if(mode == BT_COEXIST_USE_BT_ACTIVE || mode == BT_COEXIST_USE_BT_ACTIVE_PRIORITY) { + gpio_enable(bt_active_pin, GPIO_INPUT); + new_mask |= GPIO_OUT_BT_ACTIVE_ENABLE; + } + if(mode == BT_COEXIST_USE_BT_ACTIVE_PRIORITY) { + gpio_enable(bt_priority_pin, GPIO_INPUT); + new_mask |= GPIO_OUT_BT_PRIORITY_ENABLE; + } + GPIO.OUT |= new_mask; +} + + diff --git a/core/include/esp/gpio.h b/core/include/esp/gpio.h index c6fa42f..426d1ea 100644 --- a/core/include/esp/gpio.h +++ b/core/include/esp/gpio.h @@ -81,9 +81,9 @@ static inline void gpio_set_output_on_sleep(const uint8_t gpio_num, bool enabled static inline void gpio_write(const uint8_t gpio_num, const bool set) { if (set) - GPIO.OUT_SET = BIT(gpio_num); + GPIO.OUT_SET = BIT(gpio_num) & GPIO_OUT_PIN_MASK; else - GPIO.OUT_CLEAR = BIT(gpio_num); + GPIO.OUT_CLEAR = BIT(gpio_num) & GPIO_OUT_PIN_MASK; } /* Toggle output of a pin @@ -102,9 +102,9 @@ static inline void gpio_toggle(const uint8_t gpio_num) task's pins, without needing to disable/enable interrupts. */ if(GPIO.OUT & BIT(gpio_num)) - GPIO.OUT_CLEAR = BIT(gpio_num); + GPIO.OUT_CLEAR = BIT(gpio_num) & GPIO_OUT_PIN_MASK; else - GPIO.OUT_SET = BIT(gpio_num); + GPIO.OUT_SET = BIT(gpio_num) & GPIO_OUT_PIN_MASK; } /* Read input value of a GPIO pin. diff --git a/core/include/esp/gpio_regs.h b/core/include/esp/gpio_regs.h index 815e7c1..6ff1f92 100644 --- a/core/include/esp/gpio_regs.h +++ b/core/include/esp/gpio_regs.h @@ -61,6 +61,21 @@ struct GPIO_REGS { _Static_assert(sizeof(struct GPIO_REGS) == 0x74, "GPIO_REGS is the wrong size"); +/* Details for additional OUT register fields */ + +/* Bottom 16 bits of GPIO.OUT are for GPIOs 0-15, but upper 16 bits + are used to configure the input signalling pins for Bluetooth + Coexistence config (see esp/phy.h for a wrapper function). +*/ +#define GPIO_OUT_PIN_MASK 0x0000FFFF +#define GPIO_OUT_BT_COEXIST_MASK 0x03FF0000 +#define GPIO_OUT_BT_ACTIVE_ENABLE BIT(24) +#define GPIO_OUT_BT_PRIORITY_ENABLE BIT(25) +#define GPIO_OUT_BT_ACTIVE_PIN_M 0x0F +#define GPIO_OUT_BT_ACTIVE_PIN_S 16 +#define GPIO_OUT_BT_PRIORITY_PIN_M 0x0F +#define GPIO_OUT_BT_PRIORITY_PIN_S 20 + /* Details for CONF[i] registers */ /* GPIO.CONF[i] control the pin behavior for the corresponding GPIO in/output. diff --git a/core/include/esp/phy.h b/core/include/esp/phy.h new file mode 100644 index 0000000..a597267 --- /dev/null +++ b/core/include/esp/phy.h @@ -0,0 +1,58 @@ +/** esp/phy.h + * + * PHY hardware management functions. + * + * These are not enough to configure the ESP8266 PHY by themselves + * (yet), but they provide utilities to modify the configuration set + * up via the SDK. + * + * Functions implemented here deal directly with the hardware, not the + * SDK software layers. + * + * Part of esp-open-rtos + * Copyright (C) 2016 ChefSteps, Inc + * BSD Licensed as described in the file LICENSE + */ +#ifndef _ESP_PHY_H +#define _ESP_PHY_H + +#include +#include + +#include + +typedef enum { + BT_COEXIST_NONE, + BT_COEXIST_USE_BT_ACTIVE, + BT_COEXIST_USE_BT_ACTIVE_PRIORITY, +} bt_coexist_mode_t; + +/** Override the Bluetooth Coexistence BT_ACTIVE pin setting + taken from the phy_info structure. + + This enables other pins to be used for Bluetooth Coexistence + signals (rather than just the two provided for by phy_info). (Note + that not that not all pins are confirmed to work, GPIO 0 is + confirmed not usable as the SDK configures it as the WLAN_ACTIVE + output - even if you change the pin mode the SDK will change it + back.) + + To change pins and have coexistence work successfully the BT + coexistence feature must be enabled in the phy_info configuration + (get_default_phy_info()). You can then modify the initial + configuration by calling this function from your own user_init + function. + + (Eventually we should be able to support enough PHY registers + to enable coexistence without SDK support at all, but not yet.) + + This function will enable bt_active_pin & bt_priority_as GPIO + inputs, according to the mode parameter. + + Remember that the default Bluetooth Coexistence pins will be + configured as GPIOs by the SDK already, so you may want to + reconfigure/re-iomux them after calling this function. +*/ +void bt_coexist_configure(bt_coexist_mode_t mode, uint8_t bt_active_pin, uint8_t bt_priority_pin); + +#endif From f9fb0f212c60f2bfb1400e8b6adea74ae39bf72a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 10 Mar 2016 12:28:38 +1100 Subject: [PATCH 12/44] Add stack memory dump to fatal exception handler --- core/app_main.c | 23 ++++++++++++++++++++++- core/exception_vectors.S | 7 ++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/core/app_main.c b/core/app_main.c index 19bf73e..f3cbff4 100644 --- a/core/app_main.c +++ b/core/app_main.c @@ -85,6 +85,7 @@ static void zero_bss(void); static void init_networking(uint8_t *phy_info, uint8_t *mac_addr); static void init_g_ic(void); static void dump_excinfo(void); +static void dump_stack(uint32_t *sp); static void user_start_phase2(void); static void dump_flash_sector(uint32_t start_sector, uint32_t length); static void dump_flash_config_sectors(uint32_t start_sector); @@ -147,7 +148,7 @@ static void IRAM set_spi0_divisor(uint32_t divisor) { } // .text+0x148 -void IRAM sdk_user_fatal_exception_handler(void) { +void IRAM sdk_user_fatal_exception_handler(uint32_t *sp) { if (!sdk_NMIIrqIsOn) { vPortEnterCritical(); do { @@ -157,6 +158,8 @@ void IRAM sdk_user_fatal_exception_handler(void) { Cache_Read_Disable(); Cache_Read_Enable(0, 0, 1); dump_excinfo(); + if (sp) + dump_stack(sp); uart_flush_txfifo(0); uart_flush_txfifo(1); sdk_system_restart_in_nmi(); @@ -363,6 +366,24 @@ static void dump_excinfo(void) { sdk_system_rtc_mem_write(0, excinfo, 32); } +/* There's a lot of smart stuff we could do while dumping stack + but for now we just dump a likely looking section of stack + memory +*/ +static void dump_stack(uint32_t *sp) { + printf("\nStack: SP=%p\n", sp); + for(uint32_t *p = sp; p < sp + 32; p += 4) { + if((intptr_t)p >= 0x3fffc000) { + break; /* approximate end of RAM */ + } + printf("%p: %08x %08x %08x %08x\n", p, p[0], p[1], p[2], p[3]); + if(p[0] == 0xa5a5a5a5 && p[1] == 0xa5a5a5a5 + && p[2] == 0xa5a5a5a5 && p[3] == 0xa5a5a5a5) { + break; /* FreeRTOS uses this pattern to mark untouched stack space */ + } + } +} + // .irom0.text+0x398 void sdk_wdt_init(void) { WDT.CTRL &= ~WDT_CTRL_ENABLE; diff --git a/core/exception_vectors.S b/core/exception_vectors.S index c1f1761..1980a0a 100644 --- a/core/exception_vectors.S +++ b/core/exception_vectors.S @@ -68,6 +68,7 @@ DebugExceptionVector: .type DebugExceptionVector, @function wsr a0, excsave2 + mov a2, a1 call0 sdk_user_fatal_exception_handler rfi 2 @@ -81,6 +82,7 @@ KernelExceptionVector: .type KernelExceptionVector, @function break 1, 0 + mov a2, a1 call0 sdk_user_fatal_exception_handler rfe @@ -98,6 +100,7 @@ DoubleExceptionVector: .type DoubleExceptionVector, @function break 1, 4 + mov a2, a1 call0 sdk_user_fatal_exception_handler /* Reset vector at offset 0x80 is unused, as vecbase gets reset to mask ROM @@ -251,10 +254,11 @@ LoadStoreErrorHandler: * will have correct values */ wsr a0, sar l32i a0, sp, 0 - l32i a2, sp, 0x08 + /*l32i a2, sp, 0x08*/ l32i a3, sp, 0x0c l32i a4, sp, 0x10 rsr a1, excsave1 + mov a2, a1 call0 sdk_user_fatal_exception_handler .balign 4 @@ -515,6 +519,7 @@ UserExceptionHandler: .literal_position .LUserFailOtherExceptionCause: break 1, 1 + addi a2, a1, 0x50 /* UserExceptionHandler pushes stack down 0x50 */ call0 sdk_user_fatal_exception_handler /* _xt_user_exit is pushed onto the stack as part of the user exception handler, From b414e0b946fb874b2cee74539cc343be90bee886 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 10 Mar 2016 12:28:53 +1100 Subject: [PATCH 13/44] Add 'filteroutput.py' tool to automatically do addr2line lookups on likely hex values --- common.mk | 5 +- utils/filteroutput.py | 105 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100755 utils/filteroutput.py diff --git a/common.mk b/common.mk index 8da70cd..c8fee7a 100644 --- a/common.mk +++ b/common.mk @@ -75,6 +75,9 @@ FLAVOR ?= release # or debug # Compiler names, etc. assume gdb CROSS ?= xtensa-lx106-elf- +# Path to the filteroutput.py tool +FILTEROUTPUT ?= $(ROOT)/utils/filteroutput.py + AR = $(CROSS)ar CC = $(CROSS)gcc CPP = $(CROSS)cpp @@ -385,7 +388,7 @@ size: $(PROGRAM_OUT) $(Q) $(CROSS)size --format=sysv $(PROGRAM_OUT) test: flash - screen $(ESPPORT) 115200 + $(FILTEROUTPUT) --port $(ESPPORT) --baud 115200 --elf $(PROGRAM_OUT) # the rebuild target is written like this so it can be run in a parallel build # environment without causing weird side effects diff --git a/utils/filteroutput.py b/utils/filteroutput.py new file mode 100755 index 0000000..5e9b1cf --- /dev/null +++ b/utils/filteroutput.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# +# A thin Python wrapper around addr2line, can monitor esp-open-rtos +# output and uses gdb to convert any suitable looking hex numbers +# found in the output into function and line numbers. +# +# Works with a serial port if the --port option is supplied. +# Otherwise waits for input on stdin. +# +import serial +import argparse +import re +import os +import os.path +import subprocess +import termios +import sys +import time + +# Try looking up anything in the executable address space +RE_EXECADDR = r"(0x)?40([0-9]|[a-z]){6}" + +def find_elf_file(): + out_files = [] + for top,_,files in os.walk('.', followlinks=False): + for f in files: + if f.endswith(".out"): + out_files.append(os.path.join(top,f)) + if len(out_files) == 1: + return out_files[0] + elif len(out_files) > 1: + print("Found multiple .out files: %s. Please specify one with the --elf option." % out_files) + else: + print("No .out file found under current directory. Please specify one with the --elf option.") + sys.exit(1) + +def main(): + parser = argparse.ArgumentParser(description='esp-open-rtos output filter tool', prog='filteroutput') + parser.add_argument( + '--elf', '-e', + help="ELF file (*.out file) to load symbols from (if not supplied, will search for one)"), + parser.add_argument( + '--port', '-p', + help='Serial port to monitor (will monitor stdin if None)', + default=None) + parser.add_argument( + '--baud', '-b', + help='Baud rate for serial port', + type=int, + default=74880) + parser.add_argument( + '--reset-on-connect', '-r', + help='Reset ESP8266 (via DTR) on serial connect. (Linux resets even if not set, except when using NodeMCU-style auto-reset circuit.)', + action='store_true') + + args = parser.parse_args() + + if args.elf is None: + args.elf = find_elf_file() + elif not os.path.exists(args.elf): + print("ELF file '%s' not found" % args.elf) + sys.exit(1) + + if args.port is not None: + print("Opening %s at %dbps..." % (args.port, args.baud)) + port = serial.Serial(args.port, baudrate=args.baud) + if args.reset_on_connect: + print("Resetting...") + port.setDTR(False) + time.sleep(0.1) + port.setDTR(True) + + else: + print("Reading from stdin...") + port = sys.stdin + # disable echo + try: + old_attr = termios.tcgetattr(sys.stdin.fileno()) + attr = termios.tcgetattr(sys.stdin.fileno()) + attr[3] = attr[3] & ~termios.ECHO + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, attr) + except termios.error: + pass + + try: + while True: + line = port.readline() + if line == '': + break + print(line.strip()) + for match in re.finditer(RE_EXECADDR, line, re.IGNORECASE): + addr = match.group(0) + if not addr.startswith("0x"): + addr = "0x"+addr + # keeping addr2line and feeding it addresses on stdin didn't seem to work smoothly + addr2line = subprocess.check_output(["xtensa-lx106-elf-addr2line","-pfia","-e","%s" % args.elf, addr], cwd=".").strip() + if not addr2line.endswith(": ?? ??:0"): + print("\n%s\n" % addr2line.strip()) + finally: + if args.port is None: + # restore echo + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_attr) + +if __name__ == "__main__": + main() From 0caab973a50e483e462b65a95d6c5e8ccbec48dd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Apr 2016 14:59:23 +1000 Subject: [PATCH 14/44] Recompile libc with malloc locking enabled newlib-xtensa revision cbe80794ed0083 This fixes a crash caused by heap operations occuring inside ISRs. Particularly noticeable when sending a lot of network traffic. Probably fixes #119, maybe other crashing bugs. Configure/compile steps same as previous: ../configure --with-newlib --enable-multilib --disable-newlib-io-c99-formats --enable-newlib-supplied-syscalls --enable-target-optspace --program-transform-name="s&^&xtensa-lx106-elf-&" --disable-option-checking --with-target-subdir=xtensa-lx106-elf --target=xtensa-lx106-elf --prefix=/home/gus/dev/esp/rtos/open-rtos/libc/ --enable-newlib-nano-malloc --enable-newlib-nano-formatted-io --enable-newlib-reent-small --prefix=path_to/esp-open-rtos/libc CROSS_CFLAGS="-DSIGNAL_PROVIDED -DABORT_PROVIDED" make make install --- libc/xtensa-lx106-elf/lib/libc.a | Bin 5247078 -> 5248602 bytes libc/xtensa-lx106-elf/lib/libg.a | Bin 5247078 -> 5248602 bytes libc/xtensa-lx106-elf/lib/libm.a | Bin 2241082 -> 2241082 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/libc/xtensa-lx106-elf/lib/libc.a b/libc/xtensa-lx106-elf/lib/libc.a index 6a256cd364b4f9ccc2543bbd8b39d6f82f075433..a43b5edcb88291fd09c3cbe78c9979d511f37603 100644 GIT binary patch delta 27568 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

oDX}A3g`&vJLth8(97_JNaW*wJC|ENS^OmU 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 delta 26064 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 delta 26064 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 Date: Wed, 20 Apr 2016 15:04:29 +1000 Subject: [PATCH 15/44] lwip sys_arch: Add functional xInsideISR implementation Relies on global flags set when the user ISR is executing. Unclear if this fixes any bugs as ISR code may not have been calling into LWIP, but the previous implementation was broken. --- core/esp_interrupts.c | 6 + lwip/sys_arch.c | 1177 +++++++++++++++++++++-------------------- 2 files changed, 598 insertions(+), 585 deletions(-) diff --git a/core/esp_interrupts.c b/core/esp_interrupts.c index 4fd68b7..b3aefff 100644 --- a/core/esp_interrupts.c +++ b/core/esp_interrupts.c @@ -9,6 +9,8 @@ _xt_isr isr[16]; +bool esp_in_isr; + void IRAM _xt_isr_attach(uint8_t i, _xt_isr func) { isr[i] = func; @@ -20,6 +22,8 @@ void IRAM _xt_isr_attach(uint8_t i, _xt_isr func) */ uint16_t IRAM _xt_isr_handler(uint16_t intset) { + esp_in_isr = true; + /* WDT has highest priority (occasional WDT resets otherwise) */ if(intset & BIT(INUM_WDT)) { _xt_clear_ints(BIT(INUM_WDT)); @@ -35,5 +39,7 @@ uint16_t IRAM _xt_isr_handler(uint16_t intset) intset -= mask; } + esp_in_isr = false; + return 0; } diff --git a/lwip/sys_arch.c b/lwip/sys_arch.c index f625fc6..ad936e4 100644 --- a/lwip/sys_arch.c +++ b/lwip/sys_arch.c @@ -1,585 +1,592 @@ -/* - * Copyright (c) 2001-2003 Swedish Institute of Computer Science. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT - * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - * - * This file is part of the lwIP TCP/IP stack. - * - * Author: Adam Dunkels - * - */ - -//***************************************************************************** -// -// Include OS functionality. -// -//***************************************************************************** - -/* ------------------------ System architecture includes ----------------------------- */ -#include "arch/sys_arch.h" - -/* ------------------------ lwIP includes --------------------------------- */ -#include "lwip/opt.h" - -#include "lwip/err.h" -#include "lwip/debug.h" -#include "lwip/def.h" -#include "lwip/sys.h" -#include "lwip/mem.h" -#include "lwip/stats.h" - -/* Very crude mechanism used to determine if the critical section handling -functions are being called from an interrupt context or not. This relies on -the interrupt handler setting this variable manually. */ -portBASE_TYPE xInsideISR = pdFALSE; - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_new - *---------------------------------------------------------------------------* - * Description: - * Creates a new mailbox - * Inputs: - * int size -- Size of elements in the mailbox - * Outputs: - * sys_mbox_t -- Handle to new mailbox - *---------------------------------------------------------------------------*/ -err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize ) -{ -err_t xReturn = ERR_MEM; - - *pxMailBox = xQueueCreate( iSize, sizeof( void * ) ); - - if( *pxMailBox != NULL ) - { - xReturn = ERR_OK; - SYS_STATS_INC_USED( mbox ); - } - - return xReturn; -} - - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_free - *---------------------------------------------------------------------------* - * Description: - * Deallocates a mailbox. If there are messages still present in the - * mailbox when the mailbox is deallocated, it is an indication of a - * programming error in lwIP and the developer should be notified. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * Outputs: - * sys_mbox_t -- Handle to new mailbox - *---------------------------------------------------------------------------*/ -void sys_mbox_free( sys_mbox_t *pxMailBox ) -{ -unsigned long ulMessagesWaiting; - - ulMessagesWaiting = uxQueueMessagesWaiting( *pxMailBox ); - configASSERT( ( ulMessagesWaiting == 0 ) ); - - #if SYS_STATS - { - if( ulMessagesWaiting != 0UL ) - { - SYS_STATS_INC( mbox.err ); - } - - SYS_STATS_DEC( mbox.used ); - } - #endif /* SYS_STATS */ - - vQueueDelete( *pxMailBox ); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_post - *---------------------------------------------------------------------------* - * Description: - * Post the "msg" to the mailbox. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void *data -- Pointer to data to post - *---------------------------------------------------------------------------*/ -void sys_mbox_post( sys_mbox_t *pxMailBox, void *pxMessageToPost ) -{ - while( xQueueSendToBack( *pxMailBox, &pxMessageToPost, portMAX_DELAY ) != pdTRUE ); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_trypost - *---------------------------------------------------------------------------* - * Description: - * Try to post the "msg" to the mailbox. Returns immediately with - * error if cannot. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void *msg -- Pointer to data to post - * Outputs: - * err_t -- ERR_OK if message posted, else ERR_MEM - * if not. - *---------------------------------------------------------------------------*/ -err_t sys_mbox_trypost( sys_mbox_t *pxMailBox, void *pxMessageToPost ) -{ -err_t xReturn; -portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; - - if( xInsideISR != pdFALSE ) - { - xReturn = xQueueSendFromISR( *pxMailBox, &pxMessageToPost, &xHigherPriorityTaskWoken ); - } - else - { - xReturn = xQueueSend( *pxMailBox, &pxMessageToPost, ( portTickType ) 0 ); - } - - if( xReturn == pdPASS ) - { - xReturn = ERR_OK; - } - else - { - /* The queue was already full. */ - xReturn = ERR_MEM; - SYS_STATS_INC( mbox.err ); - } - - return xReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_mbox_fetch - *---------------------------------------------------------------------------* - * Description: - * Blocks the thread until a message arrives in the mailbox, but does - * not block the thread longer than "timeout" milliseconds (similar to - * the sys_arch_sem_wait() function). The "msg" argument is a result - * parameter that is set by the function (i.e., by doing "*msg = - * ptr"). The "msg" parameter maybe NULL to indicate that the message - * should be dropped. - * - * The return values are the same as for the sys_arch_sem_wait() function: - * Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a - * timeout. - * - * Note that a function with a similar name, sys_mbox_fetch(), is - * implemented by lwIP. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void **msg -- Pointer to pointer to msg received - * u32_t timeout -- Number of milliseconds until timeout - * Outputs: - * u32_t -- SYS_ARCH_TIMEOUT if timeout, else number - * of milliseconds until received. - *---------------------------------------------------------------------------*/ -u32_t sys_arch_mbox_fetch( sys_mbox_t *pxMailBox, void **ppvBuffer, u32_t ulTimeOut ) -{ -void *pvDummy; -portTickType xStartTime, xEndTime, xElapsed; -unsigned long ulReturn; - - xStartTime = xTaskGetTickCount(); - - if( NULL == ppvBuffer ) - { - ppvBuffer = &pvDummy; - } - - if( ulTimeOut != 0UL ) - { - configASSERT( xInsideISR == ( portBASE_TYPE ) 0 ); - - if( pdTRUE == xQueueReceive( *pxMailBox, &( *ppvBuffer ), ulTimeOut/ portTICK_RATE_MS ) ) - { - xEndTime = xTaskGetTickCount(); - xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; - - ulReturn = xElapsed; - } - else - { - /* Timed out. */ - *ppvBuffer = NULL; - ulReturn = SYS_ARCH_TIMEOUT; - } - } - else - { - while( pdTRUE != xQueueReceive( *pxMailBox, &( *ppvBuffer ), portMAX_DELAY ) ); - xEndTime = xTaskGetTickCount(); - xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; - - if( xElapsed == 0UL ) - { - xElapsed = 1UL; - } - - ulReturn = xElapsed; - } - - return ulReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_mbox_tryfetch - *---------------------------------------------------------------------------* - * Description: - * Similar to sys_arch_mbox_fetch, but if message is not ready - * immediately, we'll return with SYS_MBOX_EMPTY. On success, 0 is - * returned. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void **msg -- Pointer to pointer to msg received - * Outputs: - * u32_t -- SYS_MBOX_EMPTY if no messages. Otherwise, - * return ERR_OK. - *---------------------------------------------------------------------------*/ -u32_t sys_arch_mbox_tryfetch( sys_mbox_t *pxMailBox, void **ppvBuffer ) -{ -void *pvDummy; -unsigned long ulReturn; -long lResult; -portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; - - if( ppvBuffer== NULL ) - { - ppvBuffer = &pvDummy; - } - - if( xInsideISR != pdFALSE ) - { - lResult = xQueueReceiveFromISR( *pxMailBox, &( *ppvBuffer ), &xHigherPriorityTaskWoken ); - } - else - { - lResult = xQueueReceive( *pxMailBox, &( *ppvBuffer ), 0UL ); - } - - if( lResult == pdPASS ) - { - ulReturn = ERR_OK; - } - else - { - ulReturn = SYS_MBOX_EMPTY; - } - - return ulReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_sem_new - *---------------------------------------------------------------------------* - * Description: - * Creates and returns a new semaphore. The "ucCount" argument specifies - * the initial state of the semaphore. - * NOTE: Currently this routine only creates counts of 1 or 0 - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * u8_t ucCount -- Initial ucCount of semaphore (1 or 0) - * Outputs: - * sys_sem_t -- Created semaphore or 0 if could not create. - *---------------------------------------------------------------------------*/ -err_t sys_sem_new( sys_sem_t *pxSemaphore, u8_t ucCount ) -{ -err_t xReturn = ERR_MEM; - - vSemaphoreCreateBinary( ( *pxSemaphore ) ); - - if( *pxSemaphore != NULL ) - { - if( ucCount == 0U ) - { - xSemaphoreTake( *pxSemaphore, 1UL ); - } - - xReturn = ERR_OK; - SYS_STATS_INC_USED( sem ); - } - else - { - SYS_STATS_INC( sem.err ); - } - - return xReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_sem_wait - *---------------------------------------------------------------------------* - * Description: - * Blocks the thread while waiting for the semaphore to be - * signaled. If the "timeout" argument is non-zero, the thread should - * only be blocked for the specified time (measured in - * milliseconds). - * - * If the timeout argument is non-zero, the return value is the number of - * milliseconds spent waiting for the semaphore to be signaled. If the - * semaphore wasn't signaled within the specified time, the return value is - * SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore - * (i.e., it was already signaled), the function may return zero. - * - * Notice that lwIP implements a function with a similar name, - * sys_sem_wait(), that uses the sys_arch_sem_wait() function. - * Inputs: - * sys_sem_t sem -- Semaphore to wait on - * u32_t timeout -- Number of milliseconds until timeout - * Outputs: - * u32_t -- Time elapsed or SYS_ARCH_TIMEOUT. - *---------------------------------------------------------------------------*/ -u32_t sys_arch_sem_wait( sys_sem_t *pxSemaphore, u32_t ulTimeout ) -{ -portTickType xStartTime, xEndTime, xElapsed; -unsigned long ulReturn; - - xStartTime = xTaskGetTickCount(); - - if( ulTimeout != 0UL ) - { - if( xSemaphoreTake( *pxSemaphore, ulTimeout / portTICK_RATE_MS ) == pdTRUE ) - { - xEndTime = xTaskGetTickCount(); - xElapsed = (xEndTime - xStartTime) * portTICK_RATE_MS; - ulReturn = xElapsed; - } - else - { - ulReturn = SYS_ARCH_TIMEOUT; - } - } - else - { - while( xSemaphoreTake( *pxSemaphore, portMAX_DELAY ) != pdTRUE ); - xEndTime = xTaskGetTickCount(); - xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; - - if( xElapsed == 0UL ) - { - xElapsed = 1UL; - } - - ulReturn = xElapsed; - } - - return ulReturn; -} - -/** Create a new mutex - * @param mutex pointer to the mutex to create - * @return a new mutex */ -err_t sys_mutex_new( sys_mutex_t *pxMutex ) -{ -err_t xReturn = ERR_MEM; - - *pxMutex = xSemaphoreCreateMutex(); - - if( *pxMutex != NULL ) - { - xReturn = ERR_OK; - SYS_STATS_INC_USED( mutex ); - } - else - { - SYS_STATS_INC( mutex.err ); - } - - return xReturn; -} - -/** Lock a mutex - * @param mutex the mutex to lock */ -void sys_mutex_lock( sys_mutex_t *pxMutex ) -{ - while( xSemaphoreTake( *pxMutex, portMAX_DELAY ) != pdPASS ); -} - -/** Unlock a mutex - * @param mutex the mutex to unlock */ -void sys_mutex_unlock(sys_mutex_t *pxMutex ) -{ - xSemaphoreGive( *pxMutex ); -} - - -/** Delete a semaphore - * @param mutex the mutex to delete */ -void sys_mutex_free( sys_mutex_t *pxMutex ) -{ - SYS_STATS_DEC( mutex.used ); - vQueueDelete( *pxMutex ); -} - - -/*---------------------------------------------------------------------------* - * Routine: sys_sem_signal - *---------------------------------------------------------------------------* - * Description: - * Signals (releases) a semaphore - * Inputs: - * sys_sem_t sem -- Semaphore to signal - *---------------------------------------------------------------------------*/ -void sys_sem_signal( sys_sem_t *pxSemaphore ) -{ -portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; - - if( xInsideISR != pdFALSE ) - { - xSemaphoreGiveFromISR( *pxSemaphore, &xHigherPriorityTaskWoken ); - } - else - { - xSemaphoreGive( *pxSemaphore ); - } -} - -/*---------------------------------------------------------------------------* - * Routine: sys_sem_free - *---------------------------------------------------------------------------* - * Description: - * Deallocates a semaphore - * Inputs: - * sys_sem_t sem -- Semaphore to free - *---------------------------------------------------------------------------*/ -void sys_sem_free( sys_sem_t *pxSemaphore ) -{ - SYS_STATS_DEC(sem.used); - vQueueDelete( *pxSemaphore ); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_init - *---------------------------------------------------------------------------* - * Description: - * Initialize sys arch - *---------------------------------------------------------------------------*/ -void sys_init(void) -{ -} - -u32_t sys_now(void) -{ - return xTaskGetTickCount(); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_thread_new - *---------------------------------------------------------------------------* - * Description: - * Starts a new thread with priority "prio" that will begin its - * execution in the function "thread()". The "arg" argument will be - * passed as an argument to the thread() function. The id of the new - * thread is returned. Both the id and the priority are system - * dependent. - * Inputs: - * char *name -- Name of thread - * void (* thread)(void *arg) -- Pointer to function to run. - * void *arg -- Argument passed into function - * int stacksize -- Required stack amount in bytes - * int prio -- Thread priority - * Outputs: - * sys_thread_t -- Pointer to per-thread timeouts. - *---------------------------------------------------------------------------*/ -sys_thread_t sys_thread_new( const char *pcName, void( *pxThread )( void *pvParameters ), void *pvArg, int iStackSize, int iPriority ) -{ -xTaskHandle xCreatedTask; -portBASE_TYPE xResult; -sys_thread_t xReturn; - - xResult = xTaskCreate( pxThread, ( signed char * ) pcName, iStackSize, pvArg, iPriority, &xCreatedTask ); - - if( xResult == pdPASS ) - { - xReturn = xCreatedTask; - } - else - { - xReturn = NULL; - } - - return xReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_protect - *---------------------------------------------------------------------------* - * Description: - * This optional function does a "fast" critical region protection and - * returns the previous protection level. This function is only called - * during very short critical regions. An embedded system which supports - * ISR-based drivers might want to implement this function by disabling - * interrupts. Task-based systems might want to implement this by using - * a mutex or disabling tasking. This function should support recursive - * calls from the same task or interrupt. In other words, - * sys_arch_protect() could be called while already protected. In - * that case the return value indicates that it is already protected. - * - * sys_arch_protect() is only required if your port is supporting an - * operating system. - * Outputs: - * sys_prot_t -- Previous protection level (not used here) - *---------------------------------------------------------------------------*/ -sys_prot_t sys_arch_protect( void ) -{ - if( xInsideISR == pdFALSE ) - { - taskENTER_CRITICAL(); - } - return ( sys_prot_t ) 1; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_unprotect - *---------------------------------------------------------------------------* - * Description: - * This optional function does a "fast" set of critical region - * protection to the value specified by pval. See the documentation for - * sys_arch_protect() for more information. This function is only - * required if your port is supporting an operating system. - * Inputs: - * sys_prot_t -- Previous protection level (not used here) - *---------------------------------------------------------------------------*/ -void sys_arch_unprotect( sys_prot_t xValue ) -{ - (void) xValue; - if( xInsideISR == pdFALSE ) - { - taskEXIT_CRITICAL(); - } -} - -/* - * Prints an assertion messages and aborts execution. - */ -void sys_assert( const char *pcMessage ) -{ - (void) pcMessage; - - for (;;) - { - } -} -/*-------------------------------------------------------------------------* - * End of File: sys_arch.c - *-------------------------------------------------------------------------*/ - +/* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Adam Dunkels + * + */ + +//***************************************************************************** +// +// Include OS functionality. +// +//***************************************************************************** + +/* ------------------------ System architecture includes ----------------------------- */ +#include "arch/sys_arch.h" + +/* ------------------------ lwIP includes --------------------------------- */ +#include "lwip/opt.h" + +#include "lwip/err.h" +#include "lwip/debug.h" +#include "lwip/def.h" +#include "lwip/sys.h" +#include "lwip/mem.h" +#include "lwip/stats.h" + +extern bool esp_in_isr; + +/* Based on the default xInsideISR mechanism to determine + if an ISR is running. + + Doesn't support the possibility that LWIP functions are called from the NMI + handler (none are called from NMI when using current/SDK implementation.) +*/ +static inline bool is_inside_isr() +{ + return esp_in_isr; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_new + *---------------------------------------------------------------------------* + * Description: + * Creates a new mailbox + * Inputs: + * int size -- Size of elements in the mailbox + * Outputs: + * sys_mbox_t -- Handle to new mailbox + *---------------------------------------------------------------------------*/ +err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize ) +{ + err_t xReturn = ERR_MEM; + + *pxMailBox = xQueueCreate( iSize, sizeof( void * ) ); + + if( *pxMailBox != NULL ) + { + xReturn = ERR_OK; + SYS_STATS_INC_USED( mbox ); + } + + return xReturn; +} + + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_free + *---------------------------------------------------------------------------* + * Description: + * Deallocates a mailbox. If there are messages still present in the + * mailbox when the mailbox is deallocated, it is an indication of a + * programming error in lwIP and the developer should be notified. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * Outputs: + * sys_mbox_t -- Handle to new mailbox + *---------------------------------------------------------------------------*/ +void sys_mbox_free( sys_mbox_t *pxMailBox ) +{ +unsigned long ulMessagesWaiting; + + ulMessagesWaiting = uxQueueMessagesWaiting( *pxMailBox ); + configASSERT( ( ulMessagesWaiting == 0 ) ); + + #if SYS_STATS + { + if( ulMessagesWaiting != 0UL ) + { + SYS_STATS_INC( mbox.err ); + } + + SYS_STATS_DEC( mbox.used ); + } + #endif /* SYS_STATS */ + + vQueueDelete( *pxMailBox ); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_post + *---------------------------------------------------------------------------* + * Description: + * Post the "msg" to the mailbox. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void *data -- Pointer to data to post + *---------------------------------------------------------------------------*/ +void sys_mbox_post( sys_mbox_t *pxMailBox, void *pxMessageToPost ) +{ + while( xQueueSendToBack( *pxMailBox, &pxMessageToPost, portMAX_DELAY ) != pdTRUE ); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_trypost + *---------------------------------------------------------------------------* + * Description: + * Try to post the "msg" to the mailbox. Returns immediately with + * error if cannot. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void *msg -- Pointer to data to post + * Outputs: + * err_t -- ERR_OK if message posted, else ERR_MEM + * if not. + *---------------------------------------------------------------------------*/ +err_t sys_mbox_trypost( sys_mbox_t *pxMailBox, void *pxMessageToPost ) +{ +err_t xReturn; +portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; + + if( is_inside_isr() != pdFALSE ) + { + xReturn = xQueueSendFromISR( *pxMailBox, &pxMessageToPost, &xHigherPriorityTaskWoken ); + } + else + { + xReturn = xQueueSend( *pxMailBox, &pxMessageToPost, ( portTickType ) 0 ); + } + + if( xReturn == pdPASS ) + { + xReturn = ERR_OK; + } + else + { + /* The queue was already full. */ + xReturn = ERR_MEM; + SYS_STATS_INC( mbox.err ); + } + + return xReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_mbox_fetch + *---------------------------------------------------------------------------* + * Description: + * Blocks the thread until a message arrives in the mailbox, but does + * not block the thread longer than "timeout" milliseconds (similar to + * the sys_arch_sem_wait() function). The "msg" argument is a result + * parameter that is set by the function (i.e., by doing "*msg = + * ptr"). The "msg" parameter maybe NULL to indicate that the message + * should be dropped. + * + * The return values are the same as for the sys_arch_sem_wait() function: + * Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a + * timeout. + * + * Note that a function with a similar name, sys_mbox_fetch(), is + * implemented by lwIP. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void **msg -- Pointer to pointer to msg received + * u32_t timeout -- Number of milliseconds until timeout + * Outputs: + * u32_t -- SYS_ARCH_TIMEOUT if timeout, else number + * of milliseconds until received. + *---------------------------------------------------------------------------*/ +u32_t sys_arch_mbox_fetch( sys_mbox_t *pxMailBox, void **ppvBuffer, u32_t ulTimeOut ) +{ +void *pvDummy; +portTickType xStartTime, xEndTime, xElapsed; +unsigned long ulReturn; + + xStartTime = xTaskGetTickCount(); + + if( NULL == ppvBuffer ) + { + ppvBuffer = &pvDummy; + } + + if( ulTimeOut != 0UL ) + { + configASSERT( is_inside_isr() == ( portBASE_TYPE ) 0 ); + + if( pdTRUE == xQueueReceive( *pxMailBox, &( *ppvBuffer ), ulTimeOut/ portTICK_RATE_MS ) ) + { + xEndTime = xTaskGetTickCount(); + xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; + + ulReturn = xElapsed; + } + else + { + /* Timed out. */ + *ppvBuffer = NULL; + ulReturn = SYS_ARCH_TIMEOUT; + } + } + else + { + while( pdTRUE != xQueueReceive( *pxMailBox, &( *ppvBuffer ), portMAX_DELAY ) ); + xEndTime = xTaskGetTickCount(); + xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; + + if( xElapsed == 0UL ) + { + xElapsed = 1UL; + } + + ulReturn = xElapsed; + } + + return ulReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_mbox_tryfetch + *---------------------------------------------------------------------------* + * Description: + * Similar to sys_arch_mbox_fetch, but if message is not ready + * immediately, we'll return with SYS_MBOX_EMPTY. On success, 0 is + * returned. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void **msg -- Pointer to pointer to msg received + * Outputs: + * u32_t -- SYS_MBOX_EMPTY if no messages. Otherwise, + * return ERR_OK. + *---------------------------------------------------------------------------*/ +u32_t sys_arch_mbox_tryfetch( sys_mbox_t *pxMailBox, void **ppvBuffer ) +{ +void *pvDummy; +unsigned long ulReturn; +long lResult; +portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; + + if( ppvBuffer== NULL ) + { + ppvBuffer = &pvDummy; + } + + if( is_inside_isr() != pdFALSE ) + { + lResult = xQueueReceiveFromISR( *pxMailBox, &( *ppvBuffer ), &xHigherPriorityTaskWoken ); + } + else + { + lResult = xQueueReceive( *pxMailBox, &( *ppvBuffer ), 0UL ); + } + + if( lResult == pdPASS ) + { + ulReturn = ERR_OK; + } + else + { + ulReturn = SYS_MBOX_EMPTY; + } + + return ulReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_sem_new + *---------------------------------------------------------------------------* + * Description: + * Creates and returns a new semaphore. The "ucCount" argument specifies + * the initial state of the semaphore. + * NOTE: Currently this routine only creates counts of 1 or 0 + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * u8_t ucCount -- Initial ucCount of semaphore (1 or 0) + * Outputs: + * sys_sem_t -- Created semaphore or 0 if could not create. + *---------------------------------------------------------------------------*/ +err_t sys_sem_new( sys_sem_t *pxSemaphore, u8_t ucCount ) +{ +err_t xReturn = ERR_MEM; + + vSemaphoreCreateBinary( ( *pxSemaphore ) ); + + if( *pxSemaphore != NULL ) + { + if( ucCount == 0U ) + { + xSemaphoreTake( *pxSemaphore, 1UL ); + } + + xReturn = ERR_OK; + SYS_STATS_INC_USED( sem ); + } + else + { + SYS_STATS_INC( sem.err ); + } + + return xReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_sem_wait + *---------------------------------------------------------------------------* + * Description: + * Blocks the thread while waiting for the semaphore to be + * signaled. If the "timeout" argument is non-zero, the thread should + * only be blocked for the specified time (measured in + * milliseconds). + * + * If the timeout argument is non-zero, the return value is the number of + * milliseconds spent waiting for the semaphore to be signaled. If the + * semaphore wasn't signaled within the specified time, the return value is + * SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore + * (i.e., it was already signaled), the function may return zero. + * + * Notice that lwIP implements a function with a similar name, + * sys_sem_wait(), that uses the sys_arch_sem_wait() function. + * Inputs: + * sys_sem_t sem -- Semaphore to wait on + * u32_t timeout -- Number of milliseconds until timeout + * Outputs: + * u32_t -- Time elapsed or SYS_ARCH_TIMEOUT. + *---------------------------------------------------------------------------*/ +u32_t sys_arch_sem_wait( sys_sem_t *pxSemaphore, u32_t ulTimeout ) +{ +portTickType xStartTime, xEndTime, xElapsed; +unsigned long ulReturn; + + xStartTime = xTaskGetTickCount(); + + if( ulTimeout != 0UL ) + { + if( xSemaphoreTake( *pxSemaphore, ulTimeout / portTICK_RATE_MS ) == pdTRUE ) + { + xEndTime = xTaskGetTickCount(); + xElapsed = (xEndTime - xStartTime) * portTICK_RATE_MS; + ulReturn = xElapsed; + } + else + { + ulReturn = SYS_ARCH_TIMEOUT; + } + } + else + { + while( xSemaphoreTake( *pxSemaphore, portMAX_DELAY ) != pdTRUE ); + xEndTime = xTaskGetTickCount(); + xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; + + if( xElapsed == 0UL ) + { + xElapsed = 1UL; + } + + ulReturn = xElapsed; + } + + return ulReturn; +} + +/** Create a new mutex + * @param mutex pointer to the mutex to create + * @return a new mutex */ +err_t sys_mutex_new( sys_mutex_t *pxMutex ) +{ +err_t xReturn = ERR_MEM; + + *pxMutex = xSemaphoreCreateMutex(); + + if( *pxMutex != NULL ) + { + xReturn = ERR_OK; + SYS_STATS_INC_USED( mutex ); + } + else + { + SYS_STATS_INC( mutex.err ); + } + + return xReturn; +} + +/** Lock a mutex + * @param mutex the mutex to lock */ +void sys_mutex_lock( sys_mutex_t *pxMutex ) +{ + while( xSemaphoreTake( *pxMutex, portMAX_DELAY ) != pdPASS ); +} + +/** Unlock a mutex + * @param mutex the mutex to unlock */ +void sys_mutex_unlock(sys_mutex_t *pxMutex ) +{ + xSemaphoreGive( *pxMutex ); +} + + +/** Delete a semaphore + * @param mutex the mutex to delete */ +void sys_mutex_free( sys_mutex_t *pxMutex ) +{ + SYS_STATS_DEC( mutex.used ); + vQueueDelete( *pxMutex ); +} + + +/*---------------------------------------------------------------------------* + * Routine: sys_sem_signal + *---------------------------------------------------------------------------* + * Description: + * Signals (releases) a semaphore + * Inputs: + * sys_sem_t sem -- Semaphore to signal + *---------------------------------------------------------------------------*/ +void sys_sem_signal( sys_sem_t *pxSemaphore ) +{ +portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; + + if( is_inside_isr() != pdFALSE ) + { + xSemaphoreGiveFromISR( *pxSemaphore, &xHigherPriorityTaskWoken ); + } + else + { + xSemaphoreGive( *pxSemaphore ); + } +} + +/*---------------------------------------------------------------------------* + * Routine: sys_sem_free + *---------------------------------------------------------------------------* + * Description: + * Deallocates a semaphore + * Inputs: + * sys_sem_t sem -- Semaphore to free + *---------------------------------------------------------------------------*/ +void sys_sem_free( sys_sem_t *pxSemaphore ) +{ + SYS_STATS_DEC(sem.used); + vQueueDelete( *pxSemaphore ); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_init + *---------------------------------------------------------------------------* + * Description: + * Initialize sys arch + *---------------------------------------------------------------------------*/ +void sys_init(void) +{ +} + +u32_t sys_now(void) +{ + return xTaskGetTickCount(); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_thread_new + *---------------------------------------------------------------------------* + * Description: + * Starts a new thread with priority "prio" that will begin its + * execution in the function "thread()". The "arg" argument will be + * passed as an argument to the thread() function. The id of the new + * thread is returned. Both the id and the priority are system + * dependent. + * Inputs: + * char *name -- Name of thread + * void (* thread)(void *arg) -- Pointer to function to run. + * void *arg -- Argument passed into function + * int stacksize -- Required stack amount in bytes + * int prio -- Thread priority + * Outputs: + * sys_thread_t -- Pointer to per-thread timeouts. + *---------------------------------------------------------------------------*/ +sys_thread_t sys_thread_new( const char *pcName, void( *pxThread )( void *pvParameters ), void *pvArg, int iStackSize, int iPriority ) +{ +xTaskHandle xCreatedTask; +portBASE_TYPE xResult; +sys_thread_t xReturn; + + xResult = xTaskCreate( pxThread, ( signed char * ) pcName, iStackSize, pvArg, iPriority, &xCreatedTask ); + + if( xResult == pdPASS ) + { + xReturn = xCreatedTask; + } + else + { + xReturn = NULL; + } + + return xReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_protect + *---------------------------------------------------------------------------* + * Description: + * This optional function does a "fast" critical region protection and + * returns the previous protection level. This function is only called + * during very short critical regions. An embedded system which supports + * ISR-based drivers might want to implement this function by disabling + * interrupts. Task-based systems might want to implement this by using + * a mutex or disabling tasking. This function should support recursive + * calls from the same task or interrupt. In other words, + * sys_arch_protect() could be called while already protected. In + * that case the return value indicates that it is already protected. + * + * sys_arch_protect() is only required if your port is supporting an + * operating system. + * Outputs: + * sys_prot_t -- Previous protection level (not used here) + *---------------------------------------------------------------------------*/ +sys_prot_t sys_arch_protect( void ) +{ + if( is_inside_isr() == pdFALSE ) + { + taskENTER_CRITICAL(); + } + return ( sys_prot_t ) 1; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_unprotect + *---------------------------------------------------------------------------* + * Description: + * This optional function does a "fast" set of critical region + * protection to the value specified by pval. See the documentation for + * sys_arch_protect() for more information. This function is only + * required if your port is supporting an operating system. + * Inputs: + * sys_prot_t -- Previous protection level (not used here) + *---------------------------------------------------------------------------*/ +void sys_arch_unprotect( sys_prot_t xValue ) +{ + (void) xValue; + if( is_inside_isr() == pdFALSE ) + { + taskEXIT_CRITICAL(); + } +} + +/* + * Prints an assertion messages and aborts execution. + */ +void sys_assert( const char *pcMessage ) +{ + (void) pcMessage; + + for (;;) + { + } +} +/*-------------------------------------------------------------------------* + * End of File: sys_arch.c + *-------------------------------------------------------------------------*/ From 52f9b13faf10fee6711c73f195a3346320060304 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 May 2016 15:10:46 +1000 Subject: [PATCH 16/44] Break out debug dump functions into their own compilation unit --- core/app_main.c | 70 ------------------------------ core/debug_dumps.c | 88 ++++++++++++++++++++++++++++++++++++++ core/exception_vectors.S | 10 ++--- core/include/debug_dumps.h | 29 +++++++++++++ 4 files changed, 122 insertions(+), 75 deletions(-) create mode 100644 core/debug_dumps.c create mode 100644 core/include/debug_dumps.h diff --git a/core/app_main.c b/core/app_main.c index f3cbff4..6eb8195 100644 --- a/core/app_main.c +++ b/core/app_main.c @@ -84,8 +84,6 @@ static void IRAM set_spi0_divisor(uint32_t divisor); static void zero_bss(void); static void init_networking(uint8_t *phy_info, uint8_t *mac_addr); static void init_g_ic(void); -static void dump_excinfo(void); -static void dump_stack(uint32_t *sp); static void user_start_phase2(void); static void dump_flash_sector(uint32_t start_sector, uint32_t length); static void dump_flash_config_sectors(uint32_t start_sector); @@ -147,25 +145,6 @@ static void IRAM set_spi0_divisor(uint32_t divisor) { SPI(0).CTRL0 = SET_FIELD(SPI(0).CTRL0, SPI_CTRL0_CLOCK, clkdiv); } -// .text+0x148 -void IRAM sdk_user_fatal_exception_handler(uint32_t *sp) { - if (!sdk_NMIIrqIsOn) { - vPortEnterCritical(); - do { - DPORT.DPORT0 &= 0xffffffe0; - } while (DPORT.DPORT0 & 0x00000001); - } - Cache_Read_Disable(); - Cache_Read_Enable(0, 0, 1); - dump_excinfo(); - if (sp) - dump_stack(sp); - uart_flush_txfifo(0); - uart_flush_txfifo(1); - sdk_system_restart_in_nmi(); - halt(); -} - static void IRAM default_putc(char c) { uart_putc(0, c); @@ -335,55 +314,6 @@ static void init_g_ic(void) { } } -// .Lfunc008 -- .irom0.text+0x2a0 -static void dump_excinfo(void) { - uint32_t exccause, epc1, epc2, epc3, excvaddr, depc, excsave1; - uint32_t excinfo[8]; - - RSR(exccause, exccause); - printf("Fatal exception (%d): \n", (int)exccause); - RSR(epc1, epc1); - RSR(epc2, epc2); - RSR(epc3, epc3); - RSR(excvaddr, excvaddr); - RSR(depc, depc); - RSR(excsave1, excsave1); - printf("%s=0x%08x\n", "epc1", epc1); - printf("%s=0x%08x\n", "epc2", epc2); - printf("%s=0x%08x\n", "epc3", epc3); - printf("%s=0x%08x\n", "excvaddr", excvaddr); - printf("%s=0x%08x\n", "depc", depc); - printf("%s=0x%08x\n", "excsave1", excsave1); - sdk_system_rtc_mem_read(0, excinfo, 32); // Why? - excinfo[0] = 2; - excinfo[1] = exccause; - excinfo[2] = epc1; - excinfo[3] = epc2; - excinfo[4] = epc3; - excinfo[5] = excvaddr; - excinfo[6] = depc; - excinfo[7] = excsave1; - sdk_system_rtc_mem_write(0, excinfo, 32); -} - -/* There's a lot of smart stuff we could do while dumping stack - but for now we just dump a likely looking section of stack - memory -*/ -static void dump_stack(uint32_t *sp) { - printf("\nStack: SP=%p\n", sp); - for(uint32_t *p = sp; p < sp + 32; p += 4) { - if((intptr_t)p >= 0x3fffc000) { - break; /* approximate end of RAM */ - } - printf("%p: %08x %08x %08x %08x\n", p, p[0], p[1], p[2], p[3]); - if(p[0] == 0xa5a5a5a5 && p[1] == 0xa5a5a5a5 - && p[2] == 0xa5a5a5a5 && p[3] == 0xa5a5a5a5) { - break; /* FreeRTOS uses this pattern to mark untouched stack space */ - } - } -} - // .irom0.text+0x398 void sdk_wdt_init(void) { WDT.CTRL &= ~WDT_CTRL_ENABLE; diff --git a/core/debug_dumps.c b/core/debug_dumps.c new file mode 100644 index 0000000..6075f40 --- /dev/null +++ b/core/debug_dumps.c @@ -0,0 +1,88 @@ +/* Code for dumping status/debug output/etc, including fatal + * exception handling. + * + * Part of esp-open-rtos + * + * Partially reverse engineered from MIT licensed Espressif RTOS SDK Copyright (C) Espressif Systems. + * Additions Copyright (C) 2015 Superhouse Automation Pty Ltd + * BSD Licensed as described in the file LICENSE + */ +#include +#include +#include +#include + +#include "debug_dumps.h" +#include "common_macros.h" +#include "xtensa_ops.h" +#include "esp/rom.h" +#include "esp/uart.h" +#include "espressif/esp_common.h" +#include "sdk_internal.h" + +void dump_excinfo(void) { + uint32_t exccause, epc1, epc2, epc3, excvaddr, depc, excsave1; + uint32_t excinfo[8]; + + RSR(exccause, exccause); + printf("Fatal exception (%d): \n", (int)exccause); + RSR(epc1, epc1); + RSR(epc2, epc2); + RSR(epc3, epc3); + RSR(excvaddr, excvaddr); + RSR(depc, depc); + RSR(excsave1, excsave1); + printf("%s=0x%08x\n", "epc1", epc1); + printf("%s=0x%08x\n", "epc2", epc2); + printf("%s=0x%08x\n", "epc3", epc3); + printf("%s=0x%08x\n", "excvaddr", excvaddr); + printf("%s=0x%08x\n", "depc", depc); + printf("%s=0x%08x\n", "excsave1", excsave1); + sdk_system_rtc_mem_read(0, excinfo, 32); // Why? + excinfo[0] = 2; + excinfo[1] = exccause; + excinfo[2] = epc1; + excinfo[3] = epc2; + excinfo[4] = epc3; + excinfo[5] = excvaddr; + excinfo[6] = depc; + excinfo[7] = excsave1; + sdk_system_rtc_mem_write(0, excinfo, 32); +} + +/* There's a lot of smart stuff we could do while dumping stack + but for now we just dump a likely looking section of stack + memory +*/ +void dump_stack(uint32_t *sp) { + printf("\nStack: SP=%p\n", sp); + for(uint32_t *p = sp; p < sp + 32; p += 4) { + if((intptr_t)p >= 0x3fffc000) { + break; /* approximate end of RAM */ + } + printf("%p: %08x %08x %08x %08x\n", p, p[0], p[1], p[2], p[3]); + if(p[0] == 0xa5a5a5a5 && p[1] == 0xa5a5a5a5 + && p[2] == 0xa5a5a5a5 && p[3] == 0xa5a5a5a5) { + break; /* FreeRTOS uses this pattern to mark untouched stack space */ + } + } +} + +void IRAM fatal_exception_handler(uint32_t *sp) { + if (!sdk_NMIIrqIsOn) { + vPortEnterCritical(); + do { + DPORT.DPORT0 &= 0xffffffe0; + } while (DPORT.DPORT0 & 0x00000001); + } + Cache_Read_Disable(); + Cache_Read_Enable(0, 0, 1); + dump_excinfo(); + if (sp) + dump_stack(sp); + uart_flush_txfifo(0); + uart_flush_txfifo(1); + sdk_system_restart_in_nmi(); + while(1) {} +} + diff --git a/core/exception_vectors.S b/core/exception_vectors.S index 1980a0a..ba8fb25 100644 --- a/core/exception_vectors.S +++ b/core/exception_vectors.S @@ -69,7 +69,7 @@ DebugExceptionVector: wsr a0, excsave2 mov a2, a1 - call0 sdk_user_fatal_exception_handler + call0 fatal_exception_handler rfi 2 .org VecBase + 0x20 @@ -83,7 +83,7 @@ KernelExceptionVector: break 1, 0 mov a2, a1 - call0 sdk_user_fatal_exception_handler + call0 fatal_exception_handler rfe .org VecBase + 0x50 @@ -101,7 +101,7 @@ DoubleExceptionVector: break 1, 4 mov a2, a1 - call0 sdk_user_fatal_exception_handler + call0 fatal_exception_handler /* Reset vector at offset 0x80 is unused, as vecbase gets reset to mask ROM * vectors on chip reset. */ @@ -259,7 +259,7 @@ LoadStoreErrorHandler: l32i a4, sp, 0x10 rsr a1, excsave1 mov a2, a1 - call0 sdk_user_fatal_exception_handler + call0 fatal_exception_handler .balign 4 .LSE_assign_a1: @@ -520,7 +520,7 @@ UserExceptionHandler: .LUserFailOtherExceptionCause: break 1, 1 addi a2, a1, 0x50 /* UserExceptionHandler pushes stack down 0x50 */ - call0 sdk_user_fatal_exception_handler + call0 fatal_exception_handler /* _xt_user_exit is pushed onto the stack as part of the user exception handler, restores same set registers which were saved there and returns from exception */ diff --git a/core/include/debug_dumps.h b/core/include/debug_dumps.h new file mode 100644 index 0000000..d918043 --- /dev/null +++ b/core/include/debug_dumps.h @@ -0,0 +1,29 @@ +/* Functions for dumping status/debug output/etc, including fatal + * exception handling. + * + * Part of esp-open-rtos + * + * Copyright (C) 2015-2016 Superhouse Automation Pty Ltd + * BSD Licensed as described in the file LICENSE + */ +#ifndef _DEBUG_DUMPS_H +#define _DEBUG_DUMPS_H +#include + +/* Dump stack memory starting from stack pointer address sp. */ +void dump_stack(uint32_t *sp); + +/* Called from exception_vectors.S when a fatal exception occurs. + + Probably not useful to be called in other contexts. +*/ +void fatal_exception_handler(uint32_t *sp); + +/* Dump the current exception register state. + + Probably mostly useful when called from fatal exception handler. +*/ +void dump_excinfo(void); + + +#endif From cf350efd8aa6b7eeb9f4ffe81a4e926c7963b4d8 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 May 2016 15:37:32 +1000 Subject: [PATCH 17/44] Dump register state on fatal exception --- core/debug_dumps.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/core/debug_dumps.c b/core/debug_dumps.c index 6075f40..5878ae8 100644 --- a/core/debug_dumps.c +++ b/core/debug_dumps.c @@ -68,6 +68,22 @@ void dump_stack(uint32_t *sp) { } } +/* Dump exception status registers as stored above 'sp' + by the interrupt handler preamble +*/ +void dump_exception_registers(uint32_t *sp) { + uint32_t excsave1; + uint32_t *saved = sp - (0x50 / sizeof(uint32_t)); + printf("Registers:\n"); + RSR(excsave1, excsave1); + printf("a0 %08x ", excsave1); + printf("a1 %08x ", (intptr_t)sp); + for(int a = 2; a < 14; a++) { + printf("a%-2d %08x%c", a, saved[a+3], a == 3 || a == 7 || a == 11 ? '\n':' '); + } + printf("SAR %08x\n", saved[0x13]); +} + void IRAM fatal_exception_handler(uint32_t *sp) { if (!sdk_NMIIrqIsOn) { vPortEnterCritical(); @@ -78,11 +94,12 @@ void IRAM fatal_exception_handler(uint32_t *sp) { Cache_Read_Disable(); Cache_Read_Enable(0, 0, 1); dump_excinfo(); - if (sp) + if (sp) { + dump_exception_registers(sp); dump_stack(sp); + } uart_flush_txfifo(0); uart_flush_txfifo(1); sdk_system_restart_in_nmi(); while(1) {} } - From 36886412e627f95ec8e847f0262b617be572a772 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Sat, 7 May 2016 18:21:13 +1000 Subject: [PATCH 18/44] Add abort() implementation Also reduces the IRAM footprint of the fatal exception handler, as only the prelude (which disables interrupts & enables the flash mapping) is in IRAM now. Closes #54, relevant to #133. --- core/app_main.c | 8 +- core/debug_dumps.c | 88 ++++++++++++++++--- core/include/debug_dumps.h | 9 +- core/include/xtensa_ops.h | 14 +++ core/newlib_syscalls.c | 2 +- .../unaligned_load/unaligned_load.c | 2 +- examples/http_get_mbedtls/http_get_mbedtls.c | 6 +- examples/tls_server/tls_server.c | 8 +- lwip/include/arch/cc.h | 2 +- 9 files changed, 106 insertions(+), 33 deletions(-) diff --git a/core/app_main.c b/core/app_main.c index 6eb8195..fea9adc 100644 --- a/core/app_main.c +++ b/core/app_main.c @@ -40,8 +40,6 @@ void user_init(void); #define RTCMEM_BACKUP_PHY_VER 31 #define RTCMEM_SYSTEM_PP_VER 62 -#define halt() while (1) {} - extern uint32_t _bss_start; extern uint32_t _bss_end; @@ -100,11 +98,11 @@ static void IRAM get_otp_mac_address(uint8_t *buf) { if (!(otp_flags & 0x8000)) { //FIXME: do we really need this check? printf("Firmware ONLY supports ESP8266!!!\n"); - halt(); + abort(); } if (otp_id0 == 0 && otp_id1 == 0) { printf("empty otp\n"); - halt(); + abort(); } if (otp_flags & 0x1000) { // If bit 12 is set, it indicates that the vendor portion of the MAC @@ -258,7 +256,7 @@ static void zero_bss(void) { static void init_networking(uint8_t *phy_info, uint8_t *mac_addr) { if (sdk_register_chipv6_phy(phy_info)) { printf("FATAL: sdk_register_chipv6_phy failed"); - halt(); + abort(); } uart_set_baud(0, 74906); uart_set_baud(1, 74906); diff --git a/core/debug_dumps.c b/core/debug_dumps.c index 5878ae8..7bea87a 100644 --- a/core/debug_dumps.c +++ b/core/debug_dumps.c @@ -1,5 +1,5 @@ /* Code for dumping status/debug output/etc, including fatal - * exception handling. + * exception handling & abort implementation. * * Part of esp-open-rtos * @@ -20,7 +20,49 @@ #include "espressif/esp_common.h" #include "sdk_internal.h" -void dump_excinfo(void) { +/* Forward declarations */ +static void IRAM fatal_handler_prelude(void); +/* Inner parts of crash handlers marked noinline to ensure they don't inline into IRAM. */ +static void __attribute__((noinline)) __attribute__((noreturn)) fatal_exception_handler_inner(uint32_t *sp); +static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_inner(uint32_t *caller, uint32_t *sp); + +/* fatal_exception_handler called from any unhandled user exception + * + * (similar to a hard fault on other processor architectures) + * + * This function is run from IRAM, but the majority of the handler + * runs from flash after fatal_handler_prelude ensures it is mapped + * safely. + */ +void IRAM __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp) { + fatal_handler_prelude(); + fatal_exception_handler_inner(sp); +} + +/* Abort implementation + * + * Replaces the weak-linked abort implementation provided by newlib libc. + * + * Disable interrupts, enable flash mapping, dump stack & caller + * address, restart. + * + * This function is run from IRAM, but the majority of the abort + * handler runs from flash after fatal_handler_prelude ensures it is + * mapped safely. + * + */ +void IRAM abort(void) { + uint32_t *sp, *caller; + RETADDR(caller); + /* abort() caller is one instruction before our return address */ + caller = (uint32_t *)((intptr_t)caller - 3); + SP(sp); + fatal_handler_prelude(); + abort_handler_inner(caller, sp); +} + +/* Dump exception information from special function registers */ +static void dump_excinfo(void) { uint32_t exccause, epc1, epc2, epc3, excvaddr, depc, excsave1; uint32_t excinfo[8]; @@ -50,9 +92,13 @@ void dump_excinfo(void) { sdk_system_rtc_mem_write(0, excinfo, 32); } -/* There's a lot of smart stuff we could do while dumping stack - but for now we just dump a likely looking section of stack - memory +/* dump stack memory (frames above sp) to stdout + + There's a lot of smart stuff we could do while dumping stack + but for now we just dump what looks like our stack region. + + Probably dumps more memory than it needs to, the first instance of + 0xa5a5a5a5 probably constitutes the end of our stack. */ void dump_stack(uint32_t *sp) { printf("\nStack: SP=%p\n", sp); @@ -68,10 +114,10 @@ void dump_stack(uint32_t *sp) { } } -/* Dump exception status registers as stored above 'sp' - by the interrupt handler preamble +/* Dump normal registers that were stored above 'sp' + by the exception handler preamble */ -void dump_exception_registers(uint32_t *sp) { +void dump_registers_in_exception_handler(uint32_t *sp) { uint32_t excsave1; uint32_t *saved = sp - (0x50 / sizeof(uint32_t)); printf("Registers:\n"); @@ -84,7 +130,11 @@ void dump_exception_registers(uint32_t *sp) { printf("SAR %08x\n", saved[0x13]); } -void IRAM fatal_exception_handler(uint32_t *sp) { + +/* Prelude ensures exceptions/NMI off and flash is mapped, allowing + calls to non-IRAM functions. +*/ +static void IRAM fatal_handler_prelude(void) { if (!sdk_NMIIrqIsOn) { vPortEnterCritical(); do { @@ -93,9 +143,15 @@ void IRAM fatal_exception_handler(uint32_t *sp) { } Cache_Read_Disable(); Cache_Read_Enable(0, 0, 1); +} + +/* Main part of fatal exception handler, is run from flash to save + some IRAM. +*/ +static void fatal_exception_handler_inner(uint32_t *sp) { dump_excinfo(); if (sp) { - dump_exception_registers(sp); + dump_registers_in_exception_handler(sp); dump_stack(sp); } uart_flush_txfifo(0); @@ -103,3 +159,15 @@ void IRAM fatal_exception_handler(uint32_t *sp) { sdk_system_restart_in_nmi(); while(1) {} } + +/* Main part of abort handler, can be run from flash to save some + IRAM. +*/ +static void abort_handler_inner(uint32_t *caller, uint32_t *sp) { + printf("abort() invoked at %p.\n", caller); + dump_stack(sp); + uart_flush_txfifo(0); + uart_flush_txfifo(1); + sdk_system_restart_in_nmi(); + while(1) {} +} diff --git a/core/include/debug_dumps.h b/core/include/debug_dumps.h index d918043..1a76d26 100644 --- a/core/include/debug_dumps.h +++ b/core/include/debug_dumps.h @@ -17,13 +17,6 @@ void dump_stack(uint32_t *sp); Probably not useful to be called in other contexts. */ -void fatal_exception_handler(uint32_t *sp); - -/* Dump the current exception register state. - - Probably mostly useful when called from fatal exception handler. -*/ -void dump_excinfo(void); - +void __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp); #endif diff --git a/core/include/xtensa_ops.h b/core/include/xtensa_ops.h index 49d1fbe..1ef68e9 100644 --- a/core/include/xtensa_ops.h +++ b/core/include/xtensa_ops.h @@ -14,6 +14,20 @@ // GCC macros for reading, writing, and exchanging Xtensa processor special // registers: +/* Read stack pointer to variable. + * + * Note that the compiler will push a stack frame (minimum 16 bytes) + * in the prelude of a C function that calls any other functions. + */ +#define SP(var) asm volatile ("mov %0, a1" : "=r" (var)); + +/* Read the function return address to a variable. + * + * Depends on the containing function being simple enough that a0 is + * being used as a working register. + */ +#define RETADDR(var) asm volatile ("mov %0, a0" : "=r" (var)) + #define RSR(var, reg) asm volatile ("rsr %0, " #reg : "=r" (var)); #define WSR(var, reg) asm volatile ("wsr %0, " #reg : : "r" (var)); #define XSR(var, reg) asm volatile ("xsr %0, " #reg : "+r" (var)); diff --git a/core/newlib_syscalls.c b/core/newlib_syscalls.c index b414481..0d4b95e 100644 --- a/core/newlib_syscalls.c +++ b/core/newlib_syscalls.c @@ -25,7 +25,7 @@ IRAM caddr_t _sbrk_r (struct _reent *r, int incr) if (heap_end + incr > stack_ptr) { _write (1, "_sbrk: Heap collided with stack\n", 32); - while(1) {} + abort(); } */ heap_end += incr; diff --git a/examples/experiments/unaligned_load/unaligned_load.c b/examples/experiments/unaligned_load/unaligned_load.c index 258b4d0..4244804 100644 --- a/examples/experiments/unaligned_load/unaligned_load.c +++ b/examples/experiments/unaligned_load/unaligned_load.c @@ -305,7 +305,7 @@ static void test_system_interaction() } uint32_t ticks = xTaskGetTickCount() - start; printf("Timer interaction test PASSED after %dms.\n", ticks*portTICK_RATE_MS); - while(1) {} + abort(); } /* The following "sanity tests" are designed to try to execute every code path diff --git a/examples/http_get_mbedtls/http_get_mbedtls.c b/examples/http_get_mbedtls/http_get_mbedtls.c index 017f8fc..7aad0e3 100644 --- a/examples/http_get_mbedtls/http_get_mbedtls.c +++ b/examples/http_get_mbedtls/http_get_mbedtls.c @@ -115,7 +115,7 @@ void http_get_task(void *pvParameters) strlen(pers))) != 0) { printf(" failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok\n"); @@ -129,7 +129,7 @@ void http_get_task(void *pvParameters) if(ret < 0) { printf(" failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok (%d skipped)\n", ret); @@ -138,7 +138,7 @@ void http_get_task(void *pvParameters) if((ret = mbedtls_ssl_set_hostname(&ssl, WEB_SERVER)) != 0) { printf(" failed\n ! mbedtls_ssl_set_hostname returned %d\n\n", ret); - while(1) {} /* todo: replace with abort() */ + abort(); } /* diff --git a/examples/tls_server/tls_server.c b/examples/tls_server/tls_server.c index d253bc9..9030dc0 100644 --- a/examples/tls_server/tls_server.c +++ b/examples/tls_server/tls_server.c @@ -85,7 +85,7 @@ void tls_server_task(void *pvParameters) strlen(pers))) != 0) { printf(" failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok\n"); @@ -99,7 +99,7 @@ void tls_server_task(void *pvParameters) if(ret < 0) { printf(" failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok (%d skipped)\n", ret); @@ -109,7 +109,7 @@ void tls_server_task(void *pvParameters) if(ret != 0) { printf(" failed\n ! mbedtls_pk_parse_key returned - 0x%x\n\n", -ret); - while(1) { } /*todo: replace with abort() */ + abort(); } printf(" ok\n"); @@ -134,7 +134,7 @@ void tls_server_task(void *pvParameters) if( ( ret = mbedtls_ssl_conf_own_cert( &conf, &srvcert, &pkey ) ) != 0 ) { printf( " failed\n ! mbedtls_ssl_conf_own_cert returned %d\n\n", ret ); - while(1) { } + abort(); } mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); diff --git a/lwip/include/arch/cc.h b/lwip/include/arch/cc.h index a09ea0c..04dab26 100644 --- a/lwip/include/arch/cc.h +++ b/lwip/include/arch/cc.h @@ -94,7 +94,7 @@ typedef int sys_prot_t; _Pragma("GCC diagnostic pop") \ } while(0) #define LWIP_PLATFORM_ASSERT(x) do { printf("Assertion \"%s\" failed at line %d in %s\n", \ - x, __LINE__, __FILE__); while(1) {} } while(0) + x, __LINE__, __FILE__); abort(); } while(0) #define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \ printf("Assertion \"%s\" failed at line %d in %s\n", message, __LINE__, __FILE__); \ From efedd24624488c725dfd79620b88927bed7951f9 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Sat, 7 May 2016 19:14:48 +1000 Subject: [PATCH 19/44] fatal exception handler: Only dump "registers" from stack for fatal user exceptions --- core/debug_dumps.c | 12 +++++++----- core/exception_vectors.S | 5 +++++ core/include/debug_dumps.h | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/debug_dumps.c b/core/debug_dumps.c index 7bea87a..15396ac 100644 --- a/core/debug_dumps.c +++ b/core/debug_dumps.c @@ -23,7 +23,7 @@ /* Forward declarations */ static void IRAM fatal_handler_prelude(void); /* Inner parts of crash handlers marked noinline to ensure they don't inline into IRAM. */ -static void __attribute__((noinline)) __attribute__((noreturn)) fatal_exception_handler_inner(uint32_t *sp); +static void __attribute__((noinline)) __attribute__((noreturn)) fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack); static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_inner(uint32_t *caller, uint32_t *sp); /* fatal_exception_handler called from any unhandled user exception @@ -34,9 +34,9 @@ static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_in * runs from flash after fatal_handler_prelude ensures it is mapped * safely. */ -void IRAM __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp) { +void IRAM __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp, bool registers_saved_on_stack) { fatal_handler_prelude(); - fatal_exception_handler_inner(sp); + fatal_exception_handler_inner(sp, registers_saved_on_stack); } /* Abort implementation @@ -148,10 +148,12 @@ static void IRAM fatal_handler_prelude(void) { /* Main part of fatal exception handler, is run from flash to save some IRAM. */ -static void fatal_exception_handler_inner(uint32_t *sp) { +static void fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack) { dump_excinfo(); if (sp) { - dump_registers_in_exception_handler(sp); + if (registers_saved_on_stack) { + dump_registers_in_exception_handler(sp); + } dump_stack(sp); } uart_flush_txfifo(0); diff --git a/core/exception_vectors.S b/core/exception_vectors.S index ba8fb25..000e742 100644 --- a/core/exception_vectors.S +++ b/core/exception_vectors.S @@ -69,6 +69,7 @@ DebugExceptionVector: wsr a0, excsave2 mov a2, a1 + movi a3, 0 call0 fatal_exception_handler rfi 2 @@ -83,6 +84,7 @@ KernelExceptionVector: break 1, 0 mov a2, a1 + movi a3, 0 call0 fatal_exception_handler rfe @@ -101,6 +103,7 @@ DoubleExceptionVector: break 1, 4 mov a2, a1 + movi a3, 0 call0 fatal_exception_handler /* Reset vector at offset 0x80 is unused, as vecbase gets reset to mask ROM @@ -259,6 +262,7 @@ LoadStoreErrorHandler: l32i a4, sp, 0x10 rsr a1, excsave1 mov a2, a1 + movi a3, 0 call0 fatal_exception_handler .balign 4 @@ -520,6 +524,7 @@ UserExceptionHandler: .LUserFailOtherExceptionCause: break 1, 1 addi a2, a1, 0x50 /* UserExceptionHandler pushes stack down 0x50 */ + movi a3, 1 call0 fatal_exception_handler /* _xt_user_exit is pushed onto the stack as part of the user exception handler, diff --git a/core/include/debug_dumps.h b/core/include/debug_dumps.h index 1a76d26..809b87a 100644 --- a/core/include/debug_dumps.h +++ b/core/include/debug_dumps.h @@ -17,6 +17,6 @@ void dump_stack(uint32_t *sp); Probably not useful to be called in other contexts. */ -void __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp); +void __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp, bool registers_saved_on_stack); #endif From 981c87899b565a9def5f12fc6ac337cf93078bf3 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Sun, 15 May 2016 10:36:33 +1000 Subject: [PATCH 20/44] Add heap information to fatal exception & abort dumps --- FreeRTOS/Source/portable/esp8266/port.c | 5 ++-- core/debug_dumps.c | 39 +++++++++++++++++++++++++ core/include/debug_dumps.h | 5 +++- core/include/xtensa_ops.h | 2 +- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/FreeRTOS/Source/portable/esp8266/port.c b/FreeRTOS/Source/portable/esp8266/port.c index 21a0d55..a9dac2d 100644 --- a/FreeRTOS/Source/portable/esp8266/port.c +++ b/FreeRTOS/Source/portable/esp8266/port.c @@ -73,6 +73,7 @@ #include #include #include +#include #include "FreeRTOS.h" #include "task.h" @@ -87,7 +88,7 @@ char level1_int_disabled; After tasks start, task stacks are all allocated from the heap and FreeRTOS checks for stack overflow. */ -static uint32_t xPortSupervisorStackPointer; +uint32_t xPortSupervisorStackPointer; /* * Stack initialization @@ -220,7 +221,7 @@ size_t xPortGetFreeHeapSize( void ) uint32_t sp = xPortSupervisorStackPointer; if(sp == 0) /* scheduler not started */ - __asm__ __volatile__ ("mov %0, a1\n" : "=a"(sp)); + SP(sp); return sp - brk_val + mi.fordblks; } diff --git a/core/debug_dumps.c b/core/debug_dumps.c index 15396ac..8b20c1b 100644 --- a/core/debug_dumps.c +++ b/core/debug_dumps.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include "debug_dumps.h" #include "common_macros.h" @@ -156,18 +158,55 @@ static void fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_ } dump_stack(sp); } + dump_heapinfo(); uart_flush_txfifo(0); uart_flush_txfifo(1); sdk_system_restart_in_nmi(); while(1) {} } +void dump_heapinfo(void) +{ + extern char _heap_start; + extern uint32_t xPortSupervisorStackPointer; + struct mallinfo mi = mallinfo(); + uint32_t brk_val = (uint32_t) sbrk(0); + uint32_t sp = xPortSupervisorStackPointer; + if(sp == 0) { + SP(sp); + } + + /* Total free heap is all memory that could be allocated via + malloc (assuming fragmentation doesn't become a problem) */ + printf("\nFree Heap: %d\n", sp - brk_val + mi.fordblks); + + /* delta between brk & supervisor sp is the contiguous memory + region that is available to be put into heap space via + brk(). */ + printf("_heap_start %p brk 0x%08x supervisor sp 0x%08x sp-brk %d bytes\n", + &_heap_start, brk_val, sp, sp-brk_val); + + /* arena/fordblks/uordblks determines the amount of free space + inside the heap region already added via brk(). May be + fragmented. + + The values in parentheses are the values used internally by + nano-mallocr.c, the field names outside parentheses are the + POSIX compliant field names of the mallinfo structure. + + "arena" should be equal to brk-_heap_start ie total size available. + */ + printf("arena (total_size) %d fordblks (free_size) %d uordblocks (used_size) %d\n", + mi.arena, mi.fordblks, mi.uordblks); +} + /* Main part of abort handler, can be run from flash to save some IRAM. */ static void abort_handler_inner(uint32_t *caller, uint32_t *sp) { printf("abort() invoked at %p.\n", caller); dump_stack(sp); + dump_heapinfo(); uart_flush_txfifo(0); uart_flush_txfifo(1); sdk_system_restart_in_nmi(); diff --git a/core/include/debug_dumps.h b/core/include/debug_dumps.h index 809b87a..ab3c972 100644 --- a/core/include/debug_dumps.h +++ b/core/include/debug_dumps.h @@ -10,9 +10,12 @@ #define _DEBUG_DUMPS_H #include -/* Dump stack memory starting from stack pointer address sp. */ +/* Dump stack memory to stdout, starting from stack pointer address sp. */ void dump_stack(uint32_t *sp); +/* Dump heap statistics to stdout */ +void dump_heapinfo(void); + /* Called from exception_vectors.S when a fatal exception occurs. Probably not useful to be called in other contexts. diff --git a/core/include/xtensa_ops.h b/core/include/xtensa_ops.h index 1ef68e9..52eba2a 100644 --- a/core/include/xtensa_ops.h +++ b/core/include/xtensa_ops.h @@ -19,7 +19,7 @@ * Note that the compiler will push a stack frame (minimum 16 bytes) * in the prelude of a C function that calls any other functions. */ -#define SP(var) asm volatile ("mov %0, a1" : "=r" (var)); +#define SP(var) asm volatile ("mov %0, a1" : "=r" (var)) /* Read the function return address to a variable. * From e7607ffc2b62bb9a797c6fad0d22b0bee9c3f5fe Mon Sep 17 00:00:00 2001 From: Drasko DRASKOVIC Date: Wed, 4 May 2016 22:43:02 +0200 Subject: [PATCH 21/44] Add JSON support This commits adds JSON support by adding Jsmn (http://zserge.com/jsmn.html), a minimalistic JSON parser. Signed-off-by: Drasko DRASKOVIC --- .gitmodules | 3 + examples/json_jsmn_simple/Makefile | 4 + examples/json_jsmn_simple/json_jsmn_simple.c | 112 +++++++++++++++++++ extras/jsmn/component.mk | 10 ++ extras/jsmn/jsmn | 1 + 5 files changed, 130 insertions(+) create mode 100644 examples/json_jsmn_simple/Makefile create mode 100644 examples/json_jsmn_simple/json_jsmn_simple.c create mode 100644 extras/jsmn/component.mk create mode 160000 extras/jsmn/jsmn diff --git a/.gitmodules b/.gitmodules index 8dc737e..52708f8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "extras/mbedtls/mbedtls"] path = extras/mbedtls/mbedtls url = https://github.com/ARMmbed/mbedtls.git +[submodule "extras/jsmn/jsmn"] + path = extras/jsmn/jsmn + url = https://github.com/zserge/jsmn.git diff --git a/examples/json_jsmn_simple/Makefile b/examples/json_jsmn_simple/Makefile new file mode 100644 index 0000000..89e6ba7 --- /dev/null +++ b/examples/json_jsmn_simple/Makefile @@ -0,0 +1,4 @@ +# Simple makefile for simple example +PROGRAM=json_jsmn_simple +EXTRA_COMPONENTS = extras/jsmn +include ../../common.mk diff --git a/examples/json_jsmn_simple/json_jsmn_simple.c b/examples/json_jsmn_simple/json_jsmn_simple.c new file mode 100644 index 0000000..3a65ddf --- /dev/null +++ b/examples/json_jsmn_simple/json_jsmn_simple.c @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2010 Serge A. Zaitsev and + * 2016 Drasko DRASKOVIC + * + * 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. + */ + +#include "espressif/esp_common.h" +#include "esp/uart.h" +#include "FreeRTOS.h" +#include "task.h" +#include +#include +#include +#include "jsmn.h" + +/** + * A small example of jsmn parsing when JSON structure is known and number of + * tokens is predictable. + */ + +const char *JSON_STRING = + "{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n " + "\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}"; + +static int jsoneq(const char *json, jsmntok_t *tok, const char *s) { + if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start && + strncmp(json + tok->start, s, tok->end - tok->start) == 0) { + return 0; + } + return -1; +} + +void json_test(void *pvParameters) +{ + int i; + int r; + jsmn_parser p; + jsmntok_t t[128]; /* We expect no more than 128 tokens */ + + jsmn_init(&p); + r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t, sizeof(t)/sizeof(t[0])); + if (r < 0) { + printf("Failed to parse JSON: %d\n", r); + } + + /* Assume the top-level element is an object */ + if (r < 1 || t[0].type != JSMN_OBJECT) { + printf("Object expected\n"); + } + + /* Loop over all keys of the root object */ + for (i = 1; i < r; i++) { + if (jsoneq(JSON_STRING, &t[i], "user") == 0) { + /* We may use strndup() to fetch string value */ + printf("- User: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "admin") == 0) { + /* We may additionally check if the value is either "true" or "false" */ + printf("- Admin: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "uid") == 0) { + /* We may want to do strtol() here to get numeric value */ + printf("- UID: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "groups") == 0) { + int j; + printf("- Groups:\n"); + if (t[i+1].type != JSMN_ARRAY) { + continue; /* We expect groups to be an array of strings */ + } + for (j = 0; j < t[i+1].size; j++) { + jsmntok_t *g = &t[i+j+2]; + printf(" * %.*s\n", g->end - g->start, JSON_STRING + g->start); + } + i += t[i+1].size + 1; + } else { + printf("Unexpected key: %.*s\n", t[i].end-t[i].start, + JSON_STRING + t[i].start); + } + } + + while (1) + ; +} + + +void user_init(void) +{ + uart_set_baud(0, 115200); + printf("SDK version:%s\n", sdk_system_get_sdk_version()); + xTaskCreate(json_test, (signed char *)"jsont", 1024, NULL, 2, NULL); +} diff --git a/extras/jsmn/component.mk b/extras/jsmn/component.mk new file mode 100644 index 0000000..13beac4 --- /dev/null +++ b/extras/jsmn/component.mk @@ -0,0 +1,10 @@ +# Component makefile for extras/jsmn + +# expected anyone using jsmn json component includes it as 'jsmn/jsmn.h' +INC_DIRS += $(jsmn_ROOT)jsmn + +# args for passing into compile rule generation +jsmn_INC_DIR = +jsmn_SRC_DIR = $(jsmn_ROOT)jsmn + +$(eval $(call component_compile_rules,jsmn)) diff --git a/extras/jsmn/jsmn b/extras/jsmn/jsmn new file mode 160000 index 0000000..bbc6755 --- /dev/null +++ b/extras/jsmn/jsmn @@ -0,0 +1 @@ +Subproject commit bbc6755fce14c713f9bb4ba47c688d15efc1394b From 1e9296f60cbe5679d1c532530071c1080ad13c06 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 17 May 2016 09:08:53 +1000 Subject: [PATCH 22/44] Fatal exceptions: Cleanly deal with exceptions that occur inside fatal_exception_handler_inner() In case of heap corruption or some other major problem, dumping details in the exception handler can cause a crash loop - so fail out if we seem to be going in circles. --- core/debug_dumps.c | 41 +++++++++++++++++++++++++----------- core/include/common_macros.h | 10 ++++----- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/core/debug_dumps.c b/core/debug_dumps.c index 8b20c1b..7f1a1ca 100644 --- a/core/debug_dumps.c +++ b/core/debug_dumps.c @@ -24,10 +24,14 @@ /* Forward declarations */ static void IRAM fatal_handler_prelude(void); -/* Inner parts of crash handlers marked noinline to ensure they don't inline into IRAM. */ -static void __attribute__((noinline)) __attribute__((noreturn)) fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack); +/* Inner parts of crash handlers */ +typedef void __attribute__((noreturn)) (*fatal_exception_handler_fn)(uint32_t *sp, bool registers_saved_on_stack); +static void __attribute__((noreturn)) standard_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack); +static void __attribute__((noreturn)) second_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack); static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_inner(uint32_t *caller, uint32_t *sp); +static IRAM_DATA fatal_exception_handler_fn fatal_exception_handler_inner = standard_fatal_exception_handler_inner; + /* fatal_exception_handler called from any unhandled user exception * * (similar to a hard fault on other processor architectures) @@ -38,7 +42,8 @@ static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_in */ void IRAM __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp, bool registers_saved_on_stack) { fatal_handler_prelude(); - fatal_exception_handler_inner(sp, registers_saved_on_stack); + fatal_exception_handler_fn inner_fn = fatal_exception_handler_inner; + inner_fn(sp, registers_saved_on_stack); } /* Abort implementation @@ -132,6 +137,12 @@ void dump_registers_in_exception_handler(uint32_t *sp) { printf("SAR %08x\n", saved[0x13]); } +static void __attribute__((noreturn)) post_crash_reset(void) { + uart_flush_txfifo(0); + uart_flush_txfifo(1); + sdk_system_restart_in_nmi(); + while(1) {} +} /* Prelude ensures exceptions/NMI off and flash is mapped, allowing calls to non-IRAM functions. @@ -150,7 +161,10 @@ static void IRAM fatal_handler_prelude(void) { /* Main part of fatal exception handler, is run from flash to save some IRAM. */ -static void fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack) { +static void standard_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack) { + /* Replace the fatal exception handler 'inner' function so we + don't end up in a crash loop if this handler crashes. */ + fatal_exception_handler_inner = second_fatal_exception_handler_inner; dump_excinfo(); if (sp) { if (registers_saved_on_stack) { @@ -159,10 +173,16 @@ static void fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_ dump_stack(sp); } dump_heapinfo(); - uart_flush_txfifo(0); - uart_flush_txfifo(1); - sdk_system_restart_in_nmi(); - while(1) {} + post_crash_reset(); +} + +/* This is the exception handler that gets called if a crash occurs inside the standard handler, + so we don't end up in a crash loop. It doesn't rely on contents of stack or heap. +*/ +static void second_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack) { + dump_excinfo(); + printf("Second fatal exception occured inside fatal exception handler. Can't continue.\n"); + post_crash_reset(); } void dump_heapinfo(void) @@ -207,8 +227,5 @@ static void abort_handler_inner(uint32_t *caller, uint32_t *sp) { printf("abort() invoked at %p.\n", caller); dump_stack(sp); dump_heapinfo(); - uart_flush_txfifo(0); - uart_flush_txfifo(1); - sdk_system_restart_in_nmi(); - while(1) {} + post_crash_reset(); } diff --git a/core/include/common_macros.h b/core/include/common_macros.h index 7c6dd56..4aa6248 100644 --- a/core/include/common_macros.h +++ b/core/include/common_macros.h @@ -65,18 +65,16 @@ */ #define IRAM __attribute__((section(".iram1.text"))) -/* Use this macro to place read-only data into Instruction RAM (IRAM) +/* Use this macro to place data into Instruction RAM (IRAM) instead of loaded into rodata which resides in DRAM. + (IRAM can also be written to as necessary.) + This may be useful to free up data RAM. However all data read from the instruction space must be 32-bit aligned word reads (non-aligned reads will use an interrupt routine to "fix" them and still work, but are very slow.. */ -#ifdef __cplusplus - #define IRAM_DATA __attribute__((section(".iram1.rodata"))) -#else - #define IRAM_DATA __attribute__((section(".iram1.rodata"))) const -#endif +#define IRAM_DATA __attribute__((section(".iram1.rodata"))) #endif From d72aedf7b1ef29b3a0c99fd415231b1828ec3076 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 20 May 2016 11:01:16 +1000 Subject: [PATCH 23/44] Store .rodata in flash by default Closes #11 --- ld/common.ld | 148 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 92 insertions(+), 56 deletions(-) diff --git a/ld/common.ld b/ld/common.ld index cc8a8ce..bfc763c 100644 --- a/ld/common.ld +++ b/ld/common.ld @@ -132,43 +132,6 @@ SECTIONS _etext = .; } >iram1_0_seg :iram1_0_phdr - .irom0.text : ALIGN(4) - { - _irom0_text_start = ABSOLUTE(.); - /* esp-open-rtos compiled code goes into IROM by default - (except for libgcc which is matched above.) - */ - *(.literal .text .literal.* .text.*) - /* Anything explicitly marked as "irom" or "irom0" should go here */ - *(.irom.* .irom.*.* .irom0.*) - _irom0_text_end = ABSOLUTE(.); - - /* Temporary .rodata hacks start here, eventually all rodata will - be in irom by default */ - /* mbedtls rodata */ - *mbedtls.a:*.o(.rodata.* .rodata) - /* actual certificate in example (TEMPORARY HACK) */ - *:cert.o(.rodata.* .rodata) - /* C++ constructor and destructor tables, properly ordered: */ - __init_array_start = ABSOLUTE(.); - KEEP (*crtbegin.o(.ctors)) - KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) - KEEP (*(SORT(.ctors.*))) - KEEP (*(.ctors)) - __init_array_end = ABSOLUTE(.); - KEEP (*crtbegin.o(.dtors)) - KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) - KEEP (*(SORT(.dtors.*))) - KEEP (*(.dtors)) - /* C++ exception handlers table: */ - __XT_EXCEPTION_DESCS__ = ABSOLUTE(.); - *(.xt_except_desc) - *(.gnu.linkonce.h.*) - __XT_EXCEPTION_DESCS_END__ = ABSOLUTE(.); - *(.xt_except_desc_end) - - } >irom0_0_seg :irom0_0_phdr - .data : ALIGN(4) { _data_start = ABSOLUTE(.); @@ -186,29 +149,37 @@ SECTIONS _data_end = ABSOLUTE(.); } >dram0_0_seg :dram0_0_phdr + /* rodata in DRAM + + cherry-picked compilation units that need rodata + to be in DRAM - anything that may be run while + SPI flash is unmapped (ie IRAM functions that are + called from interrupt context or spi flash management + functions) need their compilation units listed here. + + If you have constant data that is performance-critical, + list the compilation unit(s) here as well. + */ .rodata : ALIGN(4) { _rodata_start = ABSOLUTE(.); - *(.rodata) - *(.rodata.*) - *(.gnu.linkonce.r.*) - *(.rodata1) - __XT_EXCEPTION_TABLE__ = ABSOLUTE(.); - *(.xt_except_table) - *(.gcc_except_table) - *(.gnu.linkonce.e.*) - *(.gnu.version_r) - *(.eh_frame) - . = (. + 3) & ~ 3; - *(.dynamic) - *(.gnu.version_d) - . = ALIGN(4); /* this table MUST be 4-byte aligned */ - _bss_table_start = ABSOLUTE(.); - LONG(_bss_start) - LONG(_bss_end) - _bss_table_end = ABSOLUTE(.); + + /* Store all of core, libc, freertos .rodata in RAM by default + (some parts are necessary, some parts for performance reasons.) + */ + *core.a:*(.rodata.* .rodata) *libc.a:*.o(.rodata.* .rodata) + *freertos.a:*(.rodata.* .rodata) + + /* spi flash management rodata needs to be accessed + while flash is unmapped. */ + *libmain.a:spi_flash.o(.rodata.* .rodata) + + /* libpp wdev.o has the NMI handler (sdk_wDev_ProcessFiq) + which runs at all times, flash mapped or not. */ + *libpp.a:wdev.o(.rodata.* .rodata) + _rodata_end = ABSOLUTE(.); - } >dram0_0_seg :dram0_0_phdr + } > dram0_0_seg :dram0_0_phdr .bss ALIGN(8) (NOLOAD) : ALIGN(4) { @@ -234,6 +205,71 @@ SECTIONS } >dram0_0_seg :dram0_0_bss_phdr /* __stack = 0x3ffc8000; */ + + /* All data that goes to flash (IROM) ends up in this section */ + .irom0.text : ALIGN(4) + { + /***************************** + * Actual irom0 text section * + *****************************/ + + _irom0_text_start = ABSOLUTE(.); + /* esp-open-rtos compiled code goes into IROM by default + (except for libgcc which is matched above.) + + We also link .rodata here in the hope that data is stored near + its code on the flash (in practice this doesn't quite happen. :/) + */ + *(.literal .text .literal.* .text.* .rodata .rodata.*) + /* Anything explicitly marked as "irom" or "irom0" should go here */ + *(.irom.* .irom.*.* .irom0.*) + _irom0_text_end = ABSOLUTE(.); + + /************************************************************** + C++ constructor and destructor tables, properly ordered: + **************************************************************/ + __init_array_start = ABSOLUTE(.); + KEEP (*crtbegin.o(.ctors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + __init_array_end = ABSOLUTE(.); + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + + /*********************************** + C++ exception handlers table: * + **********************************/ + __XT_EXCEPTION_DESCS__ = ABSOLUTE(.); + *(.xt_except_desc) + *(.gnu.linkonce.h.*) + __XT_EXCEPTION_DESCS_END__ = ABSOLUTE(.); + *(.xt_except_desc_end) + + /*********************************** + Additional .rodata special sections + stored in flash + ************************************/ + . = ALIGN(4); + *(.gnu.linkonce.r.*) + __XT_EXCEPTION_TABLE__ = ABSOLUTE(.); + *(.xt_except_table) + *(.gcc_except_table) + *(.gnu.linkonce.e.*) + *(.gnu.version_r) + *(.eh_frame) + . = ALIGN(4); + *(.dynamic) + *(.gnu.version_d) + . = ALIGN(4); /* this table MUST be 4-byte aligned */ + _bss_table_start = ABSOLUTE(.); + LONG(_bss_start) + LONG(_bss_end) + _bss_table_end = ABSOLUTE(.); + } > irom0_0_seg :irom0_0_phdr + .lit4 : ALIGN(4) { _lit4_start = ABSOLUTE(.); From 367c17d1cf143ae0e9ab866d137bed161aaebd04 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 27 May 2016 11:48:59 +1000 Subject: [PATCH 24/44] lwip: Fix 'errno' not being set by sockets layer --- lwip/include/arch/cc.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lwip/include/arch/cc.h b/lwip/include/arch/cc.h index 04dab26..2922a54 100644 --- a/lwip/include/arch/cc.h +++ b/lwip/include/arch/cc.h @@ -50,6 +50,8 @@ #include #include +#define ERRNO + #define BYTE_ORDER LITTLE_ENDIAN /** @todo fix some warnings: don't use #pragma if compiling with cygwin gcc */ From d5221e7efa2ca9c7ceed6f7bbb8bccd7bfaf0923 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 27 May 2016 11:50:06 +1000 Subject: [PATCH 25/44] mbedtls: Remove WIN32 #ifdef sections, use socket's SO_ERROR flag over errno when possible --- extras/mbedtls/net_lwip.c | 54 ++++++++++++--------------------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/extras/mbedtls/net_lwip.c b/extras/mbedtls/net_lwip.c index 7843c9a..e6f7183 100644 --- a/extras/mbedtls/net_lwip.c +++ b/extras/mbedtls/net_lwip.c @@ -217,18 +217,14 @@ int mbedtls_net_bind( mbedtls_net_context *ctx, const char *bind_ip, const char } -#if ( defined(_WIN32) || defined(_WIN32_WCE) ) && !defined(EFIX64) && \ - !defined(EFI32) -/* - * Check if the requested operation would be blocking on a non-blocking socket - * and thus 'failed' with a negative return value. - */ -static int net_would_block( const mbedtls_net_context *ctx ) +static int socket_errno( const mbedtls_net_context *ctx ) { - ((void) ctx); - return( WSAGetLastError() == WSAEWOULDBLOCK ); + int sock_errno = 0; + u32_t optlen = sizeof(sock_errno); + lwip_getsockopt(ctx->fd, SOL_SOCKET, SO_ERROR, &sock_errno, &optlen); + return sock_errno; } -#else + /* * Check if the requested operation would be blocking on a non-blocking socket * and thus 'failed' with a negative return value. @@ -243,7 +239,8 @@ static int net_would_block( const mbedtls_net_context *ctx ) if( ( fcntl( ctx->fd, F_GETFL, 0) & O_NONBLOCK ) != O_NONBLOCK ) return( 0 ); - switch( errno ) + + switch( socket_errno(ctx) ) { #if defined EAGAIN case EAGAIN: @@ -255,7 +252,6 @@ static int net_would_block( const mbedtls_net_context *ctx ) } return( 0 ); } -#endif /* ( _WIN32 || _WIN32_WCE ) && !EFIX64 && !EFI32 */ /* * Accept a connection from a remote client @@ -401,9 +397,7 @@ void mbedtls_net_usleep( unsigned long usec ) #endif } -/* - * Read at most 'len' characters - */ +/* Read at most 'len' characters */ int mbedtls_net_recv( void *ctx, unsigned char *buf, size_t len ) { int ret; @@ -419,17 +413,13 @@ int mbedtls_net_recv( void *ctx, unsigned char *buf, size_t len ) if( net_would_block( ctx ) != 0 ) return( MBEDTLS_ERR_SSL_WANT_READ ); -#if ( defined(_WIN32) || defined(_WIN32_WCE) ) && !defined(EFIX64) && \ - !defined(EFI32) - if( WSAGetLastError() == WSAECONNRESET ) - return( MBEDTLS_ERR_NET_CONN_RESET ); -#else - if( errno == EPIPE || errno == ECONNRESET ) + int sock_errno = socket_errno(ctx); + + if( sock_errno == EPIPE || sock_errno == ECONNRESET ) return( MBEDTLS_ERR_NET_CONN_RESET ); - if( errno == EINTR ) + if( sock_errno == EINTR ) return( MBEDTLS_ERR_SSL_WANT_READ ); -#endif return( MBEDTLS_ERR_NET_RECV_FAILED ); } @@ -465,14 +455,8 @@ int mbedtls_net_recv_timeout( void *ctx, unsigned char *buf, size_t len, if( ret < 0 ) { -#if ( defined(_WIN32) || defined(_WIN32_WCE) ) && !defined(EFIX64) && \ - !defined(EFI32) - if( WSAGetLastError() == WSAEINTR ) - return( MBEDTLS_ERR_SSL_WANT_READ ); -#else if( errno == EINTR ) return( MBEDTLS_ERR_SSL_WANT_READ ); -#endif return( MBEDTLS_ERR_NET_RECV_FAILED ); } @@ -499,17 +483,13 @@ int mbedtls_net_send( void *ctx, const unsigned char *buf, size_t len ) if( net_would_block( ctx ) != 0 ) return( MBEDTLS_ERR_SSL_WANT_WRITE ); -#if ( defined(_WIN32) || defined(_WIN32_WCE) ) && !defined(EFIX64) && \ - !defined(EFI32) - if( WSAGetLastError() == WSAECONNRESET ) - return( MBEDTLS_ERR_NET_CONN_RESET ); -#else - if( errno == EPIPE || errno == ECONNRESET ) + int sock_errno = socket_errno(ctx); + + if( sock_errno == EPIPE || sock_errno == ECONNRESET ) return( MBEDTLS_ERR_NET_CONN_RESET ); - if( errno == EINTR ) + if( sock_errno == EINTR ) return( MBEDTLS_ERR_SSL_WANT_WRITE ); -#endif return( MBEDTLS_ERR_NET_SEND_FAILED ); } From f0db26604f5ea589616481f35db6f2c46dfa2bb2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 16 Feb 2016 22:00:29 +1100 Subject: [PATCH 26/44] brk/malloc: Allow malloc to fail when out of RAM Fixes #76. --- FreeRTOS/Source/portable/esp8266/port.c | 13 +++++++------ core/newlib_syscalls.c | 21 ++++++++++++++------- ld/common.ld | 2 +- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/FreeRTOS/Source/portable/esp8266/port.c b/FreeRTOS/Source/portable/esp8266/port.c index a9dac2d..54bca65 100644 --- a/FreeRTOS/Source/portable/esp8266/port.c +++ b/FreeRTOS/Source/portable/esp8266/port.c @@ -82,13 +82,14 @@ unsigned cpu_sr; char level1_int_disabled; -/* Supervisor stack pointer entry. This is the "high water mark" of how far the - supervisor stack grew down before task started. +/* Supervisor stack pointer entry. This is the "high water mark" of + how far the supervisor stack grew down before task started. Is zero + before the scheduler starts. - After tasks start, task stacks are all allocated from the heap and - FreeRTOS checks for stack overflow. + After the scheduler starts, task stacks are all allocated from the + heap and FreeRTOS checks for stack overflow. */ -uint32_t xPortSupervisorStackPointer; +void *xPortSupervisorStackPointer; /* * Stack initialization @@ -219,7 +220,7 @@ size_t xPortGetFreeHeapSize( void ) struct mallinfo mi = mallinfo(); uint32_t brk_val = (uint32_t) sbrk(0); - uint32_t sp = xPortSupervisorStackPointer; + intptr_t sp = (intptr_t)xPortSupervisorStackPointer; if(sp == 0) /* scheduler not started */ SP(sp); return sp - brk_val + mi.fordblks; diff --git a/core/newlib_syscalls.c b/core/newlib_syscalls.c index 0d4b95e..023872c 100644 --- a/core/newlib_syscalls.c +++ b/core/newlib_syscalls.c @@ -9,9 +9,12 @@ #include #include #include +#include #include #include +extern void *xPortSupervisorStackPointer; + IRAM caddr_t _sbrk_r (struct _reent *r, int incr) { extern char _heap_start; /* linker script defined */ @@ -21,13 +24,17 @@ IRAM caddr_t _sbrk_r (struct _reent *r, int incr) if (heap_end == NULL) heap_end = &_heap_start; prev_heap_end = heap_end; - /* TODO: Check stack collision - if (heap_end + incr > stack_ptr) - { - _write (1, "_sbrk: Heap collided with stack\n", 32); - abort(); - } - */ + + intptr_t sp = (intptr_t)xPortSupervisorStackPointer; + if(sp == 0) /* scheduler not started */ + SP(sp); + + if ((intptr_t)heap_end + incr >= sp) + { + r->_errno = ENOMEM; + return (caddr_t)-1; + } + heap_end += incr; return (caddr_t) prev_heap_end; diff --git a/ld/common.ld b/ld/common.ld index cc8a8ce..b728ea0 100644 --- a/ld/common.ld +++ b/ld/common.ld @@ -232,7 +232,7 @@ SECTIONS _heap_start = ABSOLUTE(.); /* _stack_sentry = ALIGN(0x8); */ } >dram0_0_seg :dram0_0_bss_phdr -/* __stack = 0x3ffc8000; */ +/* __stack = 0x3ffc8000; <-- this value seems a bit odd, stack on sdk_user_start is ~0x3ffffce9 */ .lit4 : ALIGN(4) { From b9f8e8a648bd37a3685720f03e339a1384dc107d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 10 Mar 2016 15:59:36 +1100 Subject: [PATCH 27/44] spi_flash.h: Add note that pointers need to be word-aligned --- include/espressif/spi_flash.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/espressif/spi_flash.h b/include/espressif/spi_flash.h index df21f34..44c8677 100644 --- a/include/espressif/spi_flash.h +++ b/include/espressif/spi_flash.h @@ -31,7 +31,7 @@ sdk_SpiFlashOpResult sdk_spi_flash_erase_sector(uint16_t sec); /* Write data to flash. des_addr is byte offset to write to. Should be 4-byte aligned. - src is pointer to a buffer to read bytes from. + src is pointer to a buffer to read bytes from. Should be 4-byte aligned. size is length of buffer in bytes. Should be a multiple of 4. */ sdk_SpiFlashOpResult sdk_spi_flash_write(uint32_t des_addr, const void *src, uint32_t size); @@ -39,7 +39,7 @@ sdk_SpiFlashOpResult sdk_spi_flash_write(uint32_t des_addr, const void *src, uin /* Read data from flash. src_addr is byte offset to read from. Should be 4-byte aligned. - des is pointer to a buffer to read bytes into. + des is pointer to a buffer to read bytes into. Should be 4-byte aligned. size is number of bytes to read. Should be a multiple of 4. */ sdk_SpiFlashOpResult sdk_spi_flash_read(uint32_t src_addr, void *des, uint32_t size); From f38bb7459375f6b5c8da9cdb446482c7088307d9 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 23 Mar 2016 11:12:20 +1100 Subject: [PATCH 28/44] OTA Images: Use esptool.py elf2image --version=2 instead of requiring esptool2 --- .travis.yml | 6 +----- common.mk | 26 +++++++------------------- utils/travis_build/install_esptool2.sh | 15 --------------- 3 files changed, 8 insertions(+), 39 deletions(-) delete mode 100755 utils/travis_build/install_esptool2.sh diff --git a/.travis.yml b/.travis.yml index bb04286..0e1b62e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,15 +5,12 @@ env: OPENSDK_COMMIT=a48b12f CROSS_ROOT="${HOME}/toolchain-${OPENSDK_COMMIT}" CROSS_BINDIR="${CROSS_ROOT}/bin" - ESPTOOL2_COMMIT=ec0e2c7 - ESPTOOL2_DIR="${HOME}/esptool2-${ESPTOOL2_COMMIT}" - PATH=${PATH}:${CROSS_BINDIR}:${ESPTOOL2_DIR} CROSS="ccache xtensa-lx106-elf-" MAKE_CMD="make WARNINGS_AS_ERRORS=1 -C examples/ build-examples" + PATH=${PATH}:${CROSS_BINDIR} cache: directories: - ${CROSS_ROOT} - - ${ESPTOOL2_DIR} addons: apt: packages: @@ -37,7 +34,6 @@ addons: - git before_install: - - utils/travis_build/install_esptool2.sh - travis_wait 30 utils/travis_build/install_toolchain.sh script: diff --git a/common.mk b/common.mk index c8fee7a..0141a3a 100644 --- a/common.mk +++ b/common.mk @@ -57,15 +57,11 @@ PRINTF_SCANF_FLOAT_SUPPORT ?= 1 # Set OTA to 1 to build an image that supports rBoot OTA bootloader # -# Currently only works with 16mbit or more flash sizes, with 8mbit -# images for each "slot" +# Requires 16mbit or higher flash sizes, with 8mbit +# images for each OTA "slot" OTA ?= 0 ifeq ($(OTA),1) -# for OTA, we build a "SDK v1.2 bootloader" compatible image where everything is in -# one file (should work with the v1.2 binary bootloader, and the FOSS rBoot bootloader). -IMGTOOL ?= esptool2 - # Tell C preprocessor that we're building for OTA CPPFLAGS = -DOTA endif @@ -188,6 +184,9 @@ LIB_ARGS = $(addprefix -l,$(LIBS)) PROGRAM_OUT = $(BUILD_DIR)$(PROGRAM).out LDFLAGS += $(addprefix -T,$(LINKER_SCRIPTS)) +# firmware tool arguments +ESPTOOL_ARGS=-fs $(FLASH_SIZE)m -fm $(FLASH_MODE) -ff $(FLASH_SPEED)m + ifeq ($(OTA),0) # for non-OTA, we create two different files for uploading into the flash # these are the names and options to generate them @@ -197,20 +196,9 @@ FW_FILE_1 = $(addprefix $(FIRMWARE_DIR),$(FW_ADDR_1).bin) FW_FILE_2 = $(addprefix $(FIRMWARE_DIR),$(FW_ADDR_2).bin) else # for OTA, it's a single monolithic image -FW_FILE = $(addprefix $(FIRMWARE_DIR),$(PROGRAM).bin) +FW_FILE = $(addprefix $(FIRMWARE_DIR),$(PROGRAM)-ota.bin) endif -# firmware tool arguments -ESPTOOL_ARGS=-fs $(FLASH_SIZE)m -fm $(FLASH_MODE) -ff $(FLASH_SPEED)m - -IMGTOOL_FLASH_SIZE_2=256 -IMGTOOL_FLASH_SIZE_4=512 -IMGTOOL_FLASH_SIZE_8=1024 -IMGTOOL_FLASH_SIZE_16=2048 -IMGTOOL_FLASH_SIZE_32=4096 -IMGTOOL_FLASH_SIZE=$(value IMGTOOL_FLASH_SIZE_$(FLASH_SIZE)) -IMGTOOL_ARGS=-$(IMGTOOL_FLASH_SIZE) -$(FLASH_MODE) -$(FLASH_SPEED) - # Common include directories, shared across all "components" # components will add their include directories to this argument # @@ -373,7 +361,7 @@ $(FW_FILE_1) $(FW_FILE_2): $(PROGRAM_OUT) $(FIRMWARE_DIR) $(Q) $(ESPTOOL) elf2image $(ESPTOOL_ARGS) $< -o $(FIRMWARE_DIR) $(FW_FILE): $(PROGRAM_OUT) $(FIRMWARE_DIR) - $(Q) $(IMGTOOL) $(IMGTOOL_ARGS) -bin -boot2 $(PROGRAM_OUT) $(FW_FILE) .text .data .rodata + $(Q) $(ESPTOOL) elf2image --version=2 $(ESPTOOL_ARGS) $< -o $(FW_FILE) ifeq ($(OTA),0) flash: $(FW_FILE_1) $(FW_FILE_2) diff --git a/utils/travis_build/install_esptool2.sh b/utils/travis_build/install_esptool2.sh deleted file mode 100755 index 102fd6f..0000000 --- a/utils/travis_build/install_esptool2.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -uv - -# Called by Travis to install esptool2 to generate OTA-compatible build -# images - -if test -f ${ESPTOOL2_DIR}/esptool2; then - echo "Using cached esptool2" - exit 0 -fi - -git clone https://github.com/raburton/esptool2 ${ESPTOOL2_DIR} -cd ${ESPTOOL2_DIR} -git reset --hard ${ESPTOOL2_COMMIT} -make From a3956af4ca72685ded1142879d32c61fabb993c7 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 23 Mar 2016 14:59:19 +1100 Subject: [PATCH 29/44] Bootloader: Integrate rboot directly as 'bootloader' component Currently using unpatched upstream rboot, but modified to build without esptool2. --- .gitmodules | 4 +++ bootloader/Makefile | 71 ++++++++++++++++++++++++++++++++++++++++++++ bootloader/README.md | 11 +++++++ bootloader/rboot | 1 + common.mk | 11 +++++-- 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 bootloader/Makefile create mode 100644 bootloader/README.md create mode 160000 bootloader/rboot diff --git a/.gitmodules b/.gitmodules index 52708f8..7a3370e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,7 @@ [submodule "extras/jsmn/jsmn"] path = extras/jsmn/jsmn url = https://github.com/zserge/jsmn.git +[submodule "bootloader/rboot"] + path = bootloader/rboot + url = https://github.com/raburton/rboot.git + diff --git a/bootloader/Makefile b/bootloader/Makefile new file mode 100644 index 0000000..64a0ebc --- /dev/null +++ b/bootloader/Makefile @@ -0,0 +1,71 @@ +# This is a wrapper around the rboot makefile, which gives us the parameters +# we need to use rboot with esp-open-rtos +# +# The wrapper means we don't require esptool2 in the build process, so we can just use +# esptool.py (still need xxd, grep, sed to generate the header - see below.) + +BUILD_DIR ?= build +FIRMWARE_DIR ?= firmware + +# RBOOT configuration parameters. +# RBOOT_BIG_FLASH is required for esp-open-rtos. +export RBOOT_BIG_FLASH = 1 + +export RBOOT_BUILD_BASE=$(abspath $(BUILD_DIR)) + +# Default ESPTOOL params, all the same as when using normal esp-open-rtos makefiles +ESPTOOL ?= esptool.py +ESPPORT ?= /dev/ttyUSB0 +ESPBAUD ?= 115200 + +FLASH_SIZE ?= 16 +FLASH_MODE ?= qio +FLASH_SPEED ?= 40 + +ESPTOOL_ARGS=-fs $(FLASH_SIZE)m -fm $(FLASH_MODE) -ff $(FLASH_SPEED)m + +ifeq ("$(V)","1") +Q := +else +Q := @ +endif + +all: $(FIRMWARE_DIR)/rboot.bin + +rboot/Makefile: + $(error rboot git submodule is not checkedo out. Try running 'git submodule update --init --recursive') + +$(FIRMWARE_DIR)/rboot.bin: $(BUILD_DIR)/rboot.elf $(FIRMWARE_DIR) + @echo "FW rboot.bin" + $(Q) $(ESPTOOL) elf2image $(ESPTOOL_ARGS) $< -o $(BUILD_DIR)/ + $(Q) mv $(BUILD_DIR)/0x00000.bin $@ + +# rboot generates this header using the 'esptool2 -header' option. To try and avoid +# esptool2 as a dependency, we try it here using grep, sed, xxd (all fairly common Unix tools) +$(BUILD_DIR)/rboot-hex2a.h: $(BUILD_DIR)/rboot-stage2a.elf $(BUILD_DIR) + @echo "Extracting stub image header..." + $(Q) xtensa-lx106-elf-objcopy $< --only-section .text -Obinary $(BUILD_DIR)/rboot-hex2a.bin + $(Q) xxd -i $(BUILD_DIR)/rboot-hex2a.bin > $@.in + $(Q) sed -i "s/unsigned char .\+\[\]/const uint8 _text_data[]/" $@.in + $(Q) sed -i "s/unsigned int .\+_len/const uint32 _text_len/" $@.in + $(Q) echo "const uint32 entry_addr = $$(xtensa-lx106-elf-objdump -f $< | grep 'start address' | grep -o '0x.\+');" >> $@.in + $(Q) echo "const uint32 _text_addr = 0x$$(xtensa-lx106-elf-objdump -h -j .text $< | grep ".text" | grep -o '401.....' | head -n1);" >> $@.in + $(Q) mv $@.in $@ + +$(BUILD_DIR)/rboot-stage2a.elf: $(BUILD_DIR) + $(Q) $(MAKE) -C rboot $(RBOOT_BUILD_BASE)/rboot-stage2a.elf + +$(BUILD_DIR)/rboot.elf: $(BUILD_DIR)/rboot-hex2a.h + $(Q) $(MAKE) -C rboot $(RBOOT_BUILD_BASE)/rboot.elf + +$(BUILD_DIR) $(FIRMWARE_DIR): + $(Q) mkdir -p "$@" + +flash: $(FIRMWARE_DIR)/rboot.bin + $(Q) $(ESPTOOL) -p $(ESPPORT) -b $(ESPBAUD) write_flash $(ESPTOOL_ARGS) 0x0 $< + +clean: + $(Q) rm -rf $(BUILD_DIR) + $(Q) rm -rf $(FIRMWARE_DIR) + +.PHONY: all clean flash erase_config diff --git a/bootloader/README.md b/bootloader/README.md new file mode 100644 index 0000000..6527dba --- /dev/null +++ b/bootloader/README.md @@ -0,0 +1,11 @@ +OTA Bootloader (rboot) source module and support files. + +Can be used to build an esp-open-rtos compatible rboot bootloader, for use when OTA=1. + +It is also possible to use the upstream rboot verbatim, but *ensure that the `RBOOT_BIG_FLASH` option is enabled or images in slots other than 0 won't work correctly. + +rboot is an open source bootloader by Richard Burton: +https://github.com/raburton/rboot + +See the contents of the 'rboot' directory for more information. + diff --git a/bootloader/rboot b/bootloader/rboot new file mode 160000 index 0000000..4cf6132 --- /dev/null +++ b/bootloader/rboot @@ -0,0 +1 @@ +Subproject commit 4cf6132f6ee624318c04b2a8d9e37da64b7c283c diff --git a/common.mk b/common.mk index 0141a3a..73a998d 100644 --- a/common.mk +++ b/common.mk @@ -165,7 +165,7 @@ LINKER_SCRIPTS += $(ROOT)ld/common.ld $(ROOT)ld/rom.ld #### ifndef PROGRAM - $(error "Set the PROGRAM environment variable in your Makefile before including common.mk" +$(error "Set the PROGRAM environment variable in your Makefile before including common.mk") endif # hacky way to get a single space value @@ -223,7 +223,7 @@ Q := @ vecho := @echo endif -.PHONY: all clean debug_print +.PHONY: all clean debug_print flash flash_bootloader erase_flash all: $(PROGRAM_OUT) $(FW_FILE_1) $(FW_FILE_2) $(FW_FILE) @@ -361,6 +361,7 @@ $(FW_FILE_1) $(FW_FILE_2): $(PROGRAM_OUT) $(FIRMWARE_DIR) $(Q) $(ESPTOOL) elf2image $(ESPTOOL_ARGS) $< -o $(FIRMWARE_DIR) $(FW_FILE): $(PROGRAM_OUT) $(FIRMWARE_DIR) + $(vecho) "FW $@" $(Q) $(ESPTOOL) elf2image --version=2 $(ESPTOOL_ARGS) $< -o $(FW_FILE) ifeq ($(OTA),0) @@ -372,6 +373,12 @@ flash: $(FW_FILE) $(ESPTOOL) -p $(ESPPORT) --baud $(ESPBAUD) write_flash $(ESPTOOL_ARGS) 0x2000 $(FW_FILE) endif +flash_bootloader: + $(MAKE) -C $(ROOT)/bootloader flash + +erase_flash: + $(ESPTOOL) -p $(ESPPORT) --baud $(ESPBAUD) erase_flash + size: $(PROGRAM_OUT) $(Q) $(CROSS)size --format=sysv $(PROGRAM_OUT) From e671927bd0c9467d636d14a7297c1bc54a95e8e9 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 23 Mar 2016 17:33:08 +1100 Subject: [PATCH 30/44] OTA: Add TFTP client mode, expand ota_basic example. --- examples/ota_basic/ota_basic.c | 40 ++++++++++++ extras/rboot-ota/ota-tftp.c | 114 +++++++++++++++++++++++++++++++-- extras/rboot-ota/ota-tftp.h | 14 ++++ 3 files changed, 162 insertions(+), 6 deletions(-) diff --git a/examples/ota_basic/ota_basic.c b/examples/ota_basic/ota_basic.c index d55c03a..ea4eedd 100644 --- a/examples/ota_basic/ota_basic.c +++ b/examples/ota_basic/ota_basic.c @@ -18,6 +18,45 @@ #include "rboot.h" #include "rboot-api.h" +#define TFTP_IMAGE_SERVER "192.168.1.23" +#define TFTP_IMAGE_FILENAME1 "firmware1.bin" +#define TFTP_IMAGE_FILENAME2 "firmware2.bin" + +void tftp_client_task(void *pvParameters) +{ + printf("TFTP client task starting...\n"); + rboot_config conf; + conf = rboot_get_config(); + int slot = (conf.current_rom + 1) % conf.count; + printf("Image will be saved in OTA slot %d.\n", slot); + if(slot == conf.current_rom) { + printf("FATAL ERROR: Only one OTA slot is configured!\n"); + while(1) {} + } + + /* Alternate between trying two different filenames. Probalby want to change this if making a practical + example! + + Note: example will reboot into FILENAME1 if it is successfully downloaded, but FILENAME2 is ignored. + */ + while(1) { + printf("Downloading %s to slot %d...\n", TFTP_IMAGE_FILENAME1, slot); + int res = ota_tftp_download(TFTP_IMAGE_SERVER, TFTP_PORT, TFTP_IMAGE_FILENAME1, 1000, slot); + printf("ota_tftp_download %s result %d\n", TFTP_IMAGE_FILENAME1, res); + if(res == 0) { + printf("Rebooting into slot %d...\n", slot); + rboot_set_current_rom(slot); + sdk_system_restart(); + } + vTaskDelay(5000 / portTICK_RATE_MS); + + printf("Downloading %s to slot %d...\n", TFTP_IMAGE_FILENAME2, slot); + res = ota_tftp_download(TFTP_IMAGE_SERVER, TFTP_PORT, TFTP_IMAGE_FILENAME2, 1000, slot); + printf("ota_tftp_download %s result %d\n", TFTP_IMAGE_FILENAME2, res); + vTaskDelay(5000 / portTICK_RATE_MS); + } +} + void user_init(void) { uart_set_baud(0, 115200); @@ -39,4 +78,5 @@ void user_init(void) sdk_wifi_station_set_config(&config); ota_tftp_init_server(TFTP_PORT); + xTaskCreate(&tftp_client_task, (signed char *)"tftp_client", 1024, NULL, 2, NULL); } diff --git a/extras/rboot-ota/ota-tftp.c b/extras/rboot-ota/ota-tftp.c index d772927..0f4360a 100644 --- a/extras/rboot-ota/ota-tftp.c +++ b/extras/rboot-ota/ota-tftp.c @@ -28,6 +28,7 @@ #define TFTP_FIRMWARE_FILE "firmware.bin" #define TFTP_OCTET_MODE "octet" /* non-case-sensitive */ +#define TFTP_OP_RRQ 1 #define TFTP_OP_WRQ 2 #define TFTP_OP_DATA 3 #define TFTP_OP_ACK 4 @@ -43,8 +44,9 @@ static void tftp_task(void *port_p); static char *tftp_get_field(int field, struct netbuf *netbuf); -static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t limit_offs, size_t *received_len); +static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t limit_offs, size_t *received_len, ip_addr_t *peer_addr, int peer_port); static err_t tftp_send_ack(struct netconn *nc, int block); +static err_t tftp_send_rrq(struct netconn *nc, const char *filename); static void tftp_send_error(struct netconn *nc, int err_code, const char *err_msg); void ota_tftp_init_server(int listen_port) @@ -52,6 +54,60 @@ void ota_tftp_init_server(int listen_port) xTaskCreate(tftp_task, (signed char *)"tftpOTATask", 512, (void *)listen_port, 2, NULL); } +err_t ota_tftp_download(const char *server, int port, const char *filename, int timeout, int ota_slot) +{ + rboot_config rboot_config = rboot_get_config(); + /* Validate the OTA slot parameter */ + if(rboot_config.current_rom == ota_slot || rboot_config.count <= ota_slot) + { + return ERR_VAL; + } + /* This is all we need to know from the rboot config - where we need + to write data to. + */ + uint32_t flash_offset = rboot_config.roms[ota_slot]; + + struct netconn *nc = netconn_new (NETCONN_UDP); + err_t err; + if(!nc) { + return ERR_IF; + } + + netconn_set_recvtimeout(nc, timeout); + + /* try to bind our client port as our local port, + or keep trying the next 10 ports after it */ + int local_port = port-1; + do { + err = netconn_bind(nc, IP_ADDR_ANY, ++local_port); + } while(err == ERR_USE && local_port < port + 10); + if(err) { + netconn_delete(nc); + return err; + } + + ip_addr_t addr; + err = netconn_gethostbyname(server, &addr); + if(err) { + netconn_delete(nc); + return err; + } + + netconn_connect(nc, &addr, port); + + err = tftp_send_rrq(nc, filename); + if(err) { + netconn_delete(nc); + return err; + } + + size_t received_len; + err = tftp_receive_data(nc, flash_offset, flash_offset+MAX_IMAGE_SIZE, &received_len, &addr, port); + netconn_delete(nc); + return err; +} + + static void tftp_task(void *listen_port) { struct netconn *nc = netconn_new (NETCONN_UDP); @@ -100,7 +156,7 @@ static void tftp_task(void *listen_port) /* check mode */ char *mode = tftp_get_field(1, netbuf); - if(!mode || strcmp("octet", mode)) { + if(!mode || strcmp(TFTP_OCTET_MODE, mode)) { tftp_send_error(nc, TFTP_ERR_ILLEGAL, "Mode must be octet/binary"); free(mode); netbuf_delete(netbuf); @@ -133,7 +189,8 @@ static void tftp_task(void *listen_port) /* Finished WRQ phase, start TFTP data transfer */ size_t received_len; - int recv_err = tftp_receive_data(nc, conf.roms[slot], conf.roms[slot]+MAX_IMAGE_SIZE, &received_len); + netconn_set_recvtimeout(nc, 10000); + int recv_err = tftp_receive_data(nc, conf.roms[slot], conf.roms[slot]+MAX_IMAGE_SIZE, &received_len, NULL, 0); netconn_disconnect(nc); printf("OTA TFTP receive data result %d bytes %d\r\n", recv_err, received_len); @@ -182,20 +239,48 @@ static char *tftp_get_field(int field, struct netbuf *netbuf) return result; } -static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t limit_offs, size_t *received_len) +#define TFTP_TIMEOUT_RETRANSMITS 10 + +static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t limit_offs, size_t *received_len, ip_addr_t *peer_addr, int peer_port) { *received_len = 0; const int DATA_PACKET_SZ = 512 + 4; /*( packet size plus header */ uint32_t start_offs = write_offs; int block = 1; - struct netbuf *netbuf; + struct netbuf *netbuf = 0; + int retries = TFTP_TIMEOUT_RETRANSMITS; while(1) { - netconn_set_recvtimeout(nc, 10000); + if(peer_addr) { + netconn_disconnect(nc); + } + err_t err = netconn_recv(nc, &netbuf); + + if(peer_addr) { + if(netbuf) { + /* For TFTP server, the UDP connection is already established. But for client, + we don't know what port the server is using until we see the first data + packet - so we connect here. + */ + netconn_connect(nc, netbuf_fromaddr(netbuf), netbuf_fromport(netbuf)); + peer_addr = 0; + } else { + /* Otherwise, temporarily re-connect so we can send errors */ + netconn_connect(nc, peer_addr, peer_port); + } + } + if(err == ERR_TIMEOUT) { + if(retries-- > 0 && block > 1) { + /* Retransmit the last ACK, wait for repeat data block. + + This doesn't work for the first block, have to time out and start again. */ + tftp_send_ack(nc, block-1); + continue; + } tftp_send_error(nc, TFTP_ERR_ILLEGAL, "Timeout"); return ERR_TIMEOUT; } @@ -225,6 +310,9 @@ static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t lim } } + /* Reset retry count if we got valid data */ + retries = TFTP_TIMEOUT_RETRANSMITS; + if(write_offs % SECTOR_SIZE == 0) { sdk_spi_flash_erase_sector(write_offs / SECTOR_SIZE); } @@ -325,3 +413,17 @@ static void tftp_send_error(struct netconn *nc, int err_code, const char *err_ms netconn_send(nc, err); netbuf_delete(err); } + +static err_t tftp_send_rrq(struct netconn *nc, const char *filename) +{ + struct netbuf *rrqbuf = netbuf_new(); + uint16_t *rrqdata = (uint16_t *)netbuf_alloc(rrqbuf, 4 + strlen(filename) + strlen(TFTP_OCTET_MODE)); + rrqdata[0] = htons(TFTP_OP_RRQ); + char *rrq_filename = (char *)&rrqdata[1]; + strcpy(rrq_filename, filename); + strcpy(rrq_filename + strlen(filename) + 1, TFTP_OCTET_MODE); + + err_t err = netconn_send(nc, rrqbuf); + netbuf_delete(rrqbuf); + return err; +} diff --git a/extras/rboot-ota/ota-tftp.h b/extras/rboot-ota/ota-tftp.h index f08b28c..cccac2a 100644 --- a/extras/rboot-ota/ota-tftp.h +++ b/extras/rboot-ota/ota-tftp.h @@ -1,5 +1,8 @@ #ifndef _OTA_TFTP_H #define _OTA_TFTP_H + +#include "lwip/err.h" + /* TFTP Server OTA Support * * To use, call ota_tftp_init_server() which will start the TFTP server task @@ -30,6 +33,17 @@ */ void ota_tftp_init_server(int listen_port); +/* Attempt to make a TFTP client connection and download the specified filename. + + 'timeout' is in milliseconds, and is timeout for any UDP exchange + _not_ the entire download. + + Returns 0 on success, LWIP err.h values for errors. + + Does not change the current firmware slot, or reboot. + */ +err_t ota_tftp_download(const char *server, int port, const char *filename, int timeout, int ota_slot); + #define TFTP_PORT 69 #endif From 6eceb5843cd9b2cacec083d8554ab40a246a5cab Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 23 Mar 2016 18:01:09 +1100 Subject: [PATCH 31/44] OTA: Move OTA-aware Cache_Read_Enable to core Otherwise images built with OTA=1 are only OTA-suitable if they also link rboot-ota. --- extras/rboot-ota/rboot-cache.S => core/spiflash-cache-enable.S | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename extras/rboot-ota/rboot-cache.S => core/spiflash-cache-enable.S (100%) diff --git a/extras/rboot-ota/rboot-cache.S b/core/spiflash-cache-enable.S similarity index 100% rename from extras/rboot-ota/rboot-cache.S rename to core/spiflash-cache-enable.S From 03559de5cba418ec43bbca22ed9c0a7db663c740 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 May 2016 11:07:10 +1000 Subject: [PATCH 32/44] Move rboot_verify_image to rboot-api Removes rboot-integration.c, removes need for clients to include rboot-integration.h --- examples/ota_basic/ota_basic.c | 1 - extras/rboot-ota/rboot-api.c | 146 +++++++++++++++++++++++++++ extras/rboot-ota/rboot-api.h | 16 +++ extras/rboot-ota/rboot-integration.c | 132 ------------------------ extras/rboot-ota/rboot-integration.h | 3 - 5 files changed, 162 insertions(+), 136 deletions(-) delete mode 100644 extras/rboot-ota/rboot-integration.c diff --git a/examples/ota_basic/ota_basic.c b/examples/ota_basic/ota_basic.c index ea4eedd..1a2d826 100644 --- a/examples/ota_basic/ota_basic.c +++ b/examples/ota_basic/ota_basic.c @@ -14,7 +14,6 @@ #include "ssid_config.h" #include "ota-tftp.h" -#include "rboot-integration.h" #include "rboot.h" #include "rboot-api.h" diff --git a/extras/rboot-ota/rboot-api.c b/extras/rboot-ota/rboot-api.c index 7fe5340..9283acb 100644 --- a/extras/rboot-ota/rboot-api.c +++ b/extras/rboot-ota/rboot-api.c @@ -4,6 +4,8 @@ // richardaburton@gmail.com // See license.txt for license terms. // OTA code based on SDK sample from Espressif. +// +// esp-open-rtos additions Copyright 2016 Angus Gratton ////////////////////////////////////////////////// #include @@ -209,6 +211,150 @@ bool ICACHE_FLASH_ATTR rboot_get_last_boot_mode(uint8 *mode) { } #endif +/* NOTE: Functions below here were added for esp-open-rtos only */ + +uint32_t rboot_get_slot_offset(uint8_t slot) { + rboot_config conf; + conf = rboot_get_config(); + if (slot >= conf.count) return (uint32_t)-1; + return conf.roms[slot]; +} + +/* Structures for parsing the rboot OTA image format */ +typedef struct __attribute__((packed)) { + uint8_t magic; + uint8_t section_count; + uint8_t val[2]; /* flash size & speed when placed @ offset 0, I thik ignored otherwise */ + uint32_t entrypoint; +} image_header_t; + +typedef struct __attribute__((packed)) { + uint32_t load_addr; + uint32_t length; +} section_header_t; + +#define ROM_MAGIC_OLD 0xe9 +#define ROM_MAGIC_NEW 0xea + +bool rboot_verify_image(uint32_t initial_offset, uint32_t *image_length, const char **error_message) +{ + uint32_t offset = initial_offset; + char *error = NULL; + RBOOT_DEBUG("rboot_verify_image: verifying image at 0x%08x\n", initial_offset); + if(offset % 4) { + error = "Unaligned flash offset"; + goto fail; + } + + /* sanity limit on how far we can read */ + uint32_t end_limit = offset + 0x100000; + image_header_t image_header __attribute__((aligned(4))); + if(sdk_spi_flash_read(offset, &image_header, sizeof(image_header_t))) { + error = "Flash fail"; + goto fail; + } + + offset += sizeof(image_header_t); + + if(image_header.magic != ROM_MAGIC_OLD && image_header.magic != ROM_MAGIC_NEW) { + error = "Missing initial magic"; + goto fail; + } + + bool is_new_header = (image_header.magic == ROM_MAGIC_NEW); /* a v1.2/rboot header, so expect a v1.1 header after the initial section */ + + int remaining_sections = image_header.section_count; + + uint8_t checksum = CHKSUM_INIT; + + while(remaining_sections > 0 && offset < end_limit) + { + /* read section header */ + section_header_t header __attribute__((aligned(4))); + if(sdk_spi_flash_read(offset, &header, sizeof(section_header_t))) { + error = "Flash fail"; + goto fail; + } + + RBOOT_DEBUG("Found section @ 0x%08x (abs 0x%08x) length %d load 0x%08x\n", offset-initial_offset, offset, header.length, header.load_addr); + offset += sizeof(section_header_t); + + if(header.length+offset > end_limit) { + break; /* sanity check: will reading section take us off end of expected flashregion? */ + } + + if(header.length % 4) { + error = "Header length not modulo 4"; + goto fail; + } + + if(!is_new_header) { + /* Add individual data of the section to the checksum. */ + char chunk[16] __attribute__((aligned(4))); + for(int i = 0; i < header.length; i++) { + if(i % sizeof(chunk) == 0) + sdk_spi_flash_read(offset+i, (uint32_t *)chunk, sizeof(chunk)); + checksum ^= chunk[i % sizeof(chunk)]; + } + } + + offset += header.length; + /* pad section to 4 byte align */ + offset = (offset+3) & ~3; + + remaining_sections--; + + if(is_new_header) { + /* pad to a 16 byte offset */ + offset = (offset+15) & ~15; + + /* expect a v1.1 header here at start of "real" sections */ + sdk_spi_flash_read(offset, (uint32_t *)&image_header, sizeof(image_header_t)); + offset += sizeof(image_header_t); + if(image_header.magic != ROM_MAGIC_OLD) { + error = "Bad second magic"; + goto fail; + } + remaining_sections = image_header.section_count; + is_new_header = false; + } + } + + if(remaining_sections > 0) { + error = "Image truncated"; + goto fail; + } + + /* add a byte for the image checksum (actually comes after the padding) */ + offset++; + /* pad the image length to a 16 byte boundary */ + offset = (offset+15) & ~15; + + uint32_t read_checksum; + sdk_spi_flash_read(offset-1, &read_checksum, 1); + if((uint8_t)read_checksum != checksum) { + error = "Invalid checksum"; + goto fail; + } + + RBOOT_DEBUG("rboot_verify_image: verified expected 0x%08x bytes.\n", offset - initial_offset); + + if(image_length) + *image_length = offset - initial_offset; + + return true; + + fail: + if(error_message) + *error_message = error; + if(error) { + printf("%s: %s\n", __func__, error); + } + if(image_length) + *image_length = offset - initial_offset; + return false; +} + #ifdef __cplusplus } #endif diff --git a/extras/rboot-ota/rboot-api.h b/extras/rboot-ota/rboot-api.h index 5a39b4d..5968827 100644 --- a/extras/rboot-ota/rboot-api.h +++ b/extras/rboot-ota/rboot-api.h @@ -128,6 +128,22 @@ bool ICACHE_FLASH_ATTR rboot_get_last_boot_rom(uint8 *rom); bool ICACHE_FLASH_ATTR rboot_get_last_boot_mode(uint8 *mode); #endif +/* ADDITIONS TO RBOOT-API FOR ESP-OPEN-RTOS FOLLOW */ + +/* Returns offset of given rboot slot, or (uint32_t)-1 if slot is invalid. + */ +uint32_t rboot_get_slot_offset(uint8_t slot); + +/** @description Verify basic image parameters - headers, CRC8 checksum. + + @param Offset of image to verify. Can use rboot_get_slot_offset() to find. + @param Optional pointer will return the total valid length of the image. + @param Optional pointer to a static human-readable error message if fails. + + @return True for valid, False for invalid. +**/ +bool rboot_verify_image(uint32_t offset, uint32_t *image_length, const char **error_message); + #ifdef __cplusplus } #endif diff --git a/extras/rboot-ota/rboot-integration.c b/extras/rboot-ota/rboot-integration.c deleted file mode 100644 index 266d566..0000000 --- a/extras/rboot-ota/rboot-integration.c +++ /dev/null @@ -1,132 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include - -#define ROM_MAGIC_OLD 0xe9 -#define ROM_MAGIC_NEW 0xea - -typedef struct __attribute__((packed)) { - uint8_t magic; - uint8_t section_count; - uint8_t val[2]; /* flash size & speed when placed @ offset 0, I think ignored otherwise */ - uint32_t entrypoint; -} image_header_t; - -typedef struct __attribute__((packed)) { - uint32_t load_addr; - uint32_t length; -} section_header_t; - - -// Check that a valid-looking rboot image is found at this offset on the flash, and -// takes up 'expected_length' bytes. -bool rboot_verify_image(uint32_t offset, uint32_t expected_length, const char **error_message) -{ - char *error = NULL; - if(offset % 4) { - error = "Unaligned flash offset"; - goto fail; - } - - uint32_t end_offset = offset + expected_length; - image_header_t image_header; - sdk_spi_flash_read(offset, (uint32_t *)&image_header, sizeof(image_header_t)); - offset += sizeof(image_header_t); - - if(image_header.magic != ROM_MAGIC_OLD && image_header.magic != ROM_MAGIC_NEW) { - error = "Missing initial magic"; - goto fail; - } - - bool is_new_header = (image_header.magic == ROM_MAGIC_NEW); /* a v1.2/rboot header, so expect a v1.1 header after the initial section */ - - int remaining_sections = image_header.section_count; - - uint8_t checksum = CHKSUM_INIT; - - while(remaining_sections > 0 && offset < end_offset) - { - /* read section header */ - section_header_t header; - sdk_spi_flash_read(offset, (uint32_t *)&header, sizeof(section_header_t)); - RBOOT_DEBUG("Found section @ 0x%08lx length 0x%08lx load 0x%08lx padded 0x%08lx\r\n", offset, header.length, header.load_addr, header.length); - offset += sizeof(section_header_t); - - - if(header.length+offset > end_offset) { - break; /* sanity check: will reading section take us off end of expected flashregion? */ - } - - if(!is_new_header) { - /* Add individual data of the section to the checksum. */ - char chunk[16]; - for(int i = 0; i < header.length; i++) { - if(i % sizeof(chunk) == 0) - sdk_spi_flash_read(offset+i, (uint32_t *)chunk, sizeof(chunk)); - checksum ^= chunk[i % sizeof(chunk)]; - } - } - - offset += header.length; - /* pad section to 4 byte align */ - offset = (offset+3) & ~3; - - remaining_sections--; - - if(is_new_header) { - /* pad to a 16 byte offset */ - offset = (offset+15) & ~15; - - /* expect a v1.1 header here at start of "real" sections */ - sdk_spi_flash_read(offset, (uint32_t *)&image_header, sizeof(image_header_t)); - offset += sizeof(image_header_t); - if(image_header.magic != ROM_MAGIC_OLD) { - error = "Bad second magic"; - goto fail; - } - remaining_sections = image_header.section_count; - is_new_header = false; - } - } - - if(remaining_sections > 0) { - error = "Image truncated"; - goto fail; - } - - /* add a byte for the image checksum (actually comes after the padding) */ - offset++; - /* pad the image length to a 16 byte boundary */ - offset = (offset+15) & ~15; - - uint8_t read_checksum; - sdk_spi_flash_read(offset-1, (uint32_t *)&read_checksum, 1); /* not sure if this will work */ - if(read_checksum != checksum) { - error = "Invalid checksum"; - goto fail; - } - - if(offset != end_offset) { - error = "Wrong length"; - goto fail; - } - - RBOOT_DEBUG("rboot_verify_image: verified expected 0x%08lx bytes.\r\n", expected_length); - return true; - - fail: - if(error_message) - *error_message = error; - printf("%s: %s\r\n", __func__, error); - return false; -} diff --git a/extras/rboot-ota/rboot-integration.h b/extras/rboot-ota/rboot-integration.h index 00c1bcb..199d7a2 100644 --- a/extras/rboot-ota/rboot-integration.h +++ b/extras/rboot-ota/rboot-integration.h @@ -28,7 +28,4 @@ #define RBOOT_DEBUG(f_, ...) #endif -// Check that a valid-looking rboot image is found at this offset on the flash, and -// takes up 'expected_length' bytes. -bool rboot_verify_image(uint32_t offset, uint32_t expected_length, const char **error_message); #endif // __RBOOT_INTEGRATION_H__ From 53b2b50241ee691285d2e44295f429d3d0883614 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 23 Mar 2016 19:44:27 +1100 Subject: [PATCH 33/44] rboot: Add cryptographic digest support for OTA images & SHA256 example --- examples/ota_basic/Makefile | 2 +- examples/ota_basic/ota_basic.c | 47 +++++++++++++++++++++++++++++++--- extras/rboot-ota/ota-tftp.c | 4 ++- extras/rboot-ota/rboot-api.c | 14 ++++++++++ extras/rboot-ota/rboot-api.h | 27 +++++++++++++++++++ 5 files changed, 88 insertions(+), 6 deletions(-) diff --git a/examples/ota_basic/Makefile b/examples/ota_basic/Makefile index 84649c1..aafb05a 100644 --- a/examples/ota_basic/Makefile +++ b/examples/ota_basic/Makefile @@ -1,5 +1,5 @@ PROGRAM=ota_basic OTA=1 -EXTRA_COMPONENTS=extras/rboot-ota +EXTRA_COMPONENTS=extras/rboot-ota extras/mbedtls include ../../common.mk diff --git a/examples/ota_basic/ota_basic.c b/examples/ota_basic/ota_basic.c index 1a2d826..68e6c71 100644 --- a/examples/ota_basic/ota_basic.c +++ b/examples/ota_basic/ota_basic.c @@ -6,12 +6,14 @@ * * NOT SUITABLE TO PUT ON THE INTERNET OR INTO A PRODUCTION ENVIRONMENT!!!! */ +#include #include "espressif/esp_common.h" #include "esp/uart.h" #include "FreeRTOS.h" #include "task.h" #include "esp8266.h" #include "ssid_config.h" +#include "mbedtls/sha256.h" #include "ota-tftp.h" #include "rboot.h" @@ -21,6 +23,9 @@ #define TFTP_IMAGE_FILENAME1 "firmware1.bin" #define TFTP_IMAGE_FILENAME2 "firmware2.bin" +/* Output of the command 'sha256sum firmware1.bin' */ +static const char *FIRMWARE1_SHA256 = "88199daff8b9e76975f685ec7f95bc1df3c61bd942a33a54a40707d2a41e5488"; + void tftp_client_task(void *pvParameters) { printf("TFTP client task starting...\n"); @@ -43,9 +48,43 @@ void tftp_client_task(void *pvParameters) int res = ota_tftp_download(TFTP_IMAGE_SERVER, TFTP_PORT, TFTP_IMAGE_FILENAME1, 1000, slot); printf("ota_tftp_download %s result %d\n", TFTP_IMAGE_FILENAME1, res); if(res == 0) { - printf("Rebooting into slot %d...\n", slot); - rboot_set_current_rom(slot); - sdk_system_restart(); + printf("Looks valid, calculating SHA256...\n"); + uint32_t length; + bool valid = rboot_verify_image(conf.roms[slot], &length, NULL); + static mbedtls_sha256_context ctx; + mbedtls_sha256_init(&ctx); + mbedtls_sha256_starts(&ctx, 0); + valid = valid && rboot_digest_image(conf.roms[slot], length, (rboot_digest_update_fn)mbedtls_sha256_update, &ctx); + static uint8_t hash_result[32]; + mbedtls_sha256_finish(&ctx, hash_result); + mbedtls_sha256_free(&ctx); + + if(!valid) + { + printf("Not valid after all :(\n"); + } + else + { + printf("Image SHA256 = "); + bool valid = true; + for(int i = 0; i < sizeof(hash_result); i++) { + char hexbuf[3]; + snprintf(hexbuf, 3, "%02x", hash_result[i]); + printf(hexbuf); + if(strncmp(hexbuf, FIRMWARE1_SHA256+i*2, 2)) + valid = false; + + } + printf("\n"); + if (valid) { + printf("SHA256 Matches. Rebooting into slot %d...\n", slot); + rboot_set_current_rom(slot); + sdk_system_restart(); + } + else { + printf("Downloaded image SHA256 didn't match expected '%s'\n", FIRMWARE1_SHA256); + } + } } vTaskDelay(5000 / portTICK_RATE_MS); @@ -77,5 +116,5 @@ void user_init(void) sdk_wifi_station_set_config(&config); ota_tftp_init_server(TFTP_PORT); - xTaskCreate(&tftp_client_task, (signed char *)"tftp_client", 1024, NULL, 2, NULL); + xTaskCreate(&tftp_client_task, (signed char *)"tftp_client", 2048, NULL, 2, NULL); } diff --git a/extras/rboot-ota/ota-tftp.c b/extras/rboot-ota/ota-tftp.c index 0f4360a..1dee8cd 100644 --- a/extras/rboot-ota/ota-tftp.c +++ b/extras/rboot-ota/ota-tftp.c @@ -369,7 +369,9 @@ static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t lim it so the client gets an indication if things were successful. */ const char *err = "Unknown validation error"; - if(!rboot_verify_image(start_offs, *received_len, &err)) { + uint32_t image_length; + if(!rboot_verify_image(start_offs, &image_length, &err) + || image_length != *received_len) { tftp_send_error(nc, TFTP_ERR_ILLEGAL, err); return ERR_VAL; } diff --git a/extras/rboot-ota/rboot-api.c b/extras/rboot-ota/rboot-api.c index 9283acb..b3faac2 100644 --- a/extras/rboot-ota/rboot-api.c +++ b/extras/rboot-ota/rboot-api.c @@ -355,6 +355,20 @@ bool rboot_verify_image(uint32_t initial_offset, uint32_t *image_length, const c return false; } +bool rboot_digest_image(uint32_t offset, uint32_t image_length, rboot_digest_update_fn update_fn, void *update_ctx) +{ + uint8_t buf[32] __attribute__((aligned(4))); + for(int i = 0; i < image_length; i += sizeof(buf)) { + if(sdk_spi_flash_read(offset+i, buf, sizeof(buf))) + return false; + uint32_t digest_len = sizeof(buf); + if(i + digest_len > image_length) + digest_len = image_length - i; + update_fn(update_ctx, buf, digest_len); + } + return true; +} + #ifdef __cplusplus } #endif diff --git a/extras/rboot-ota/rboot-api.h b/extras/rboot-ota/rboot-api.h index 5968827..a74eeb2 100644 --- a/extras/rboot-ota/rboot-api.h +++ b/extras/rboot-ota/rboot-api.h @@ -144,6 +144,33 @@ uint32_t rboot_get_slot_offset(uint8_t slot); **/ bool rboot_verify_image(uint32_t offset, uint32_t *image_length, const char **error_message); + +/* @description Digest callback prototype, designed to be compatible with + mbedtls digest functions (SHA, MD5, etc.) + + See the ota_basic example to see an example of calculating the + SHA256 digest of an OTA image. +*/ +typedef void (*rboot_digest_update_fn)(void * ctx, void *data, size_t data_len); + +/** @description Calculate a digest over the image at the offset specified + + @note This function is actually a generic function that hashes SPI + flash contents, doesn't know anything about rboot image format. Use + rboot_verify_image to ensure a well-formed OTA image. + + @param offset - Starting offset of image to hash (should be 4 byte aligned.) + + @param image_length - Length of image to hash (should be 4 byte aligned.) + + @param update_fn - Function to update digest (see rboot_digest_update_fn for details) + + @param update_ctx - Context argument for digest update function. + + @return True if digest completes successfully, false if digest function failed part way through +**/ +bool rboot_digest_image(uint32_t offset, uint32_t image_length, rboot_digest_update_fn update_fn, void *update_ctx); + #ifdef __cplusplus } #endif From 1f1881a45237c0c030a89bf225c4e3396207fa12 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 24 Mar 2016 15:35:23 +1100 Subject: [PATCH 34/44] rboot-ota: Always put a checksum in rboot config Means bootloader will still work if configured to verify the checksum --- extras/rboot-ota/rboot-config.h | 14 -------------- extras/rboot-ota/rboot-integration.h | 7 +++++++ extras/rboot-ota/rboot.h | 9 +++++++-- 3 files changed, 14 insertions(+), 16 deletions(-) delete mode 100644 extras/rboot-ota/rboot-config.h diff --git a/extras/rboot-ota/rboot-config.h b/extras/rboot-ota/rboot-config.h deleted file mode 100644 index 5ca80a6..0000000 --- a/extras/rboot-ota/rboot-config.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef _RBOOT_CONFIG_H -/* rboot configuration parameters. - - Must match the config in the compiler bootloader rboot.h. Values below are the rboot.h defaults. - - Override rboot parameters by editing this file, or (much better - alternative) copy rboot-config.h to your program directory or - program/include/ directory, then edit it to override these values. -*/ - -#define RBOOT_MAX_ROMS 4 -//#define RBOOT_CONFIG_CHECKSUM - -#endif diff --git a/extras/rboot-ota/rboot-integration.h b/extras/rboot-ota/rboot-integration.h index 199d7a2..064217b 100644 --- a/extras/rboot-ota/rboot-integration.h +++ b/extras/rboot-ota/rboot-integration.h @@ -28,4 +28,11 @@ #define RBOOT_DEBUG(f_, ...) #endif +/* Enable checksumming when writing out the config, + so if the bootloader is built with checksumming then + it will still work. +*/ +#define BOOT_CONFIG_CHKSUM + + #endif // __RBOOT_INTEGRATION_H__ diff --git a/extras/rboot-ota/rboot.h b/extras/rboot-ota/rboot.h index e09aa7e..750c4d7 100644 --- a/extras/rboot-ota/rboot.h +++ b/extras/rboot-ota/rboot.h @@ -18,8 +18,13 @@ extern "C" { // if you aren't using gcc you may need to do this //#define BOOT_NO_ASM -// uncomment to have a checksum on the boot config -//#define BOOT_CONFIG_CHKSUM +// Note: enabling RBOOT_CONFIG_CHECKSUM here means the config sector +// is always written by esp-open-rtos with a checksum. +// +// This means it will work whether the bootloader has config +// checksumming enabled or not. + +#define BOOT_CONFIG_CHKSUM // uncomment to enable big flash support (>1MB) #define BOOT_BIG_FLASH From d9202af2aab2a692c32772ac7772267701a527e8 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Sat, 7 May 2016 17:25:28 +1000 Subject: [PATCH 35/44] Use latest upstream rboot, always build with OTA - use prebuilt rboot if none is compiled locally. --- bootloader/Makefile | 48 ++--- bootloader/README.md | 11 +- bootloader/firmware_prebuilt/blank_config.bin | Bin 0 -> 2048 bytes bootloader/firmware_prebuilt/rboot.bin | Bin 0 -> 3104 bytes bootloader/rboot | 2 +- bootloader/rboot.h | 23 +++ common.mk | 172 +----------------- core/spiflash-cache-enable.S | 4 - examples/ota_basic/Makefile | 1 - examples/ota_basic/ota_basic.c | 1 - extras/rboot-ota/component.mk | 5 +- extras/rboot-ota/ota-tftp.c | 1 - extras/rboot-ota/rboot-api.c | 2 +- extras/rboot-ota/rboot-api.h | 1 + extras/rboot-ota/rboot-integration.h | 4 + extras/rboot-ota/rboot.h | 116 ------------ ld/nonota.ld | 15 -- ld/ota.ld | 15 -- ld/{common.ld => program.ld} | 18 +- parameters.mk | 141 ++++++++++++++ 20 files changed, 216 insertions(+), 364 deletions(-) create mode 100644 bootloader/firmware_prebuilt/blank_config.bin create mode 100644 bootloader/firmware_prebuilt/rboot.bin create mode 100644 bootloader/rboot.h delete mode 100644 extras/rboot-ota/rboot.h delete mode 100644 ld/nonota.ld delete mode 100644 ld/ota.ld rename ld/{common.ld => program.ld} (93%) create mode 100644 parameters.mk diff --git a/bootloader/Makefile b/bootloader/Makefile index 64a0ebc..1928d01 100644 --- a/bootloader/Makefile +++ b/bootloader/Makefile @@ -1,34 +1,17 @@ # This is a wrapper around the rboot makefile, which gives us the parameters -# we need to use rboot with esp-open-rtos +# we need to use rboot with esp-open-rtos. +# +# Use 'make bootloader' to build a custom bootloader. +# +# 'make flash' for any esp-open-rtos program will use the compiled +# bootloader if it exists, or a prebuilt bootloader if no custom +# bootloader was compiled. # # The wrapper means we don't require esptool2 in the build process, so we can just use # esptool.py (still need xxd, grep, sed to generate the header - see below.) +BOOTLOADER_DIR:=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) -BUILD_DIR ?= build -FIRMWARE_DIR ?= firmware - -# RBOOT configuration parameters. -# RBOOT_BIG_FLASH is required for esp-open-rtos. -export RBOOT_BIG_FLASH = 1 - -export RBOOT_BUILD_BASE=$(abspath $(BUILD_DIR)) - -# Default ESPTOOL params, all the same as when using normal esp-open-rtos makefiles -ESPTOOL ?= esptool.py -ESPPORT ?= /dev/ttyUSB0 -ESPBAUD ?= 115200 - -FLASH_SIZE ?= 16 -FLASH_MODE ?= qio -FLASH_SPEED ?= 40 - -ESPTOOL_ARGS=-fs $(FLASH_SIZE)m -fm $(FLASH_MODE) -ff $(FLASH_SPEED)m - -ifeq ("$(V)","1") -Q := -else -Q := @ -endif +include ../parameters.mk all: $(FIRMWARE_DIR)/rboot.bin @@ -52,14 +35,19 @@ $(BUILD_DIR)/rboot-hex2a.h: $(BUILD_DIR)/rboot-stage2a.elf $(BUILD_DIR) $(Q) echo "const uint32 _text_addr = 0x$$(xtensa-lx106-elf-objdump -h -j .text $< | grep ".text" | grep -o '401.....' | head -n1);" >> $@.in $(Q) mv $@.in $@ + +RBOOT_BUILD_BASE="$(abspath $(BUILD_DIR))" +RBOOT_FW_BASE="$(abspath $(FIRMWARE_DIR))" +MAKE_VARS=RBOOT_EXTRA_INCDIR=$(BOOTLOADER_DIR) RBOOT_BUILD_BASE=$(RBOOT_BUILD_BASE) RBOOT_FW_BASE=$(RBOOT_FW_BASE) + $(BUILD_DIR)/rboot-stage2a.elf: $(BUILD_DIR) - $(Q) $(MAKE) -C rboot $(RBOOT_BUILD_BASE)/rboot-stage2a.elf + $(Q) $(MAKE) -C rboot $(RBOOT_BUILD_BASE)/rboot-stage2a.elf $(MAKE_VARS) $(BUILD_DIR)/rboot.elf: $(BUILD_DIR)/rboot-hex2a.h - $(Q) $(MAKE) -C rboot $(RBOOT_BUILD_BASE)/rboot.elf + $(Q) $(MAKE) -C rboot $(RBOOT_BUILD_BASE)/rboot.elf $(MAKE_VARS) $(BUILD_DIR) $(FIRMWARE_DIR): - $(Q) mkdir -p "$@" + mkdir -p $@ flash: $(FIRMWARE_DIR)/rboot.bin $(Q) $(ESPTOOL) -p $(ESPPORT) -b $(ESPBAUD) write_flash $(ESPTOOL_ARGS) 0x0 $< @@ -68,4 +56,4 @@ clean: $(Q) rm -rf $(BUILD_DIR) $(Q) rm -rf $(FIRMWARE_DIR) -.PHONY: all clean flash erase_config +.PHONY: all diff --git a/bootloader/README.md b/bootloader/README.md index 6527dba..710205b 100644 --- a/bootloader/README.md +++ b/bootloader/README.md @@ -1,11 +1,12 @@ OTA Bootloader (rboot) source module and support files. -Can be used to build an esp-open-rtos compatible rboot bootloader, for use when OTA=1. - -It is also possible to use the upstream rboot verbatim, but *ensure that the `RBOOT_BIG_FLASH` option is enabled or images in slots other than 0 won't work correctly. - rboot is an open source bootloader by Richard Burton: https://github.com/raburton/rboot -See the contents of the 'rboot' directory for more information. +Can be used to build an esp-open-rtos compatible rboot bootloader. Run 'make bootloader' in this directory to compile a new bootloader. +Compiling a new bootloader is optional, there's a prebuilt one in the "firmware_prebuilt" directory that will be used if no new bootloader was compiled. + +It is also possible to use rboot from upstream verbatim, but *ensure that the `RBOOT_BIG_FLASH` option is enabled or images in slots other than 0 won't work correctly. + +See the contents of the 'rboot' directory for more information on rboot. diff --git a/bootloader/firmware_prebuilt/blank_config.bin b/bootloader/firmware_prebuilt/blank_config.bin new file mode 100644 index 0000000000000000000000000000000000000000..e9784eb4c8849062d374dd2058af8814b024f3bd GIT binary patch literal 2048 ccmZQz7zLvtFd71*Aut*OqaiRF0wXO100;m80RR91 literal 0 HcmV?d00001 diff --git a/bootloader/firmware_prebuilt/rboot.bin b/bootloader/firmware_prebuilt/rboot.bin new file mode 100644 index 0000000000000000000000000000000000000000..6cc646c3bae7c7b7fb7d3930d4f9b4a36d2f1f55 GIT binary patch literal 3104 zcmb7G4Nw%<9e;1{0|Lu=cPL@NOy4eXK+Tc8ClaR_kJP9!X~tU2*lE%n*AzeI%(T>|NvEb)rkSYqgTYiK;=TUg9;T#| znND`*x9|UZ@Bjbrd%t~OmpLT;E!CPohoK_vxR*h*Z z$F-_Sty0t!e1}F5+AZ^3#rKhnkr=+5zt1KLii~mpxU6{`1~U&|m2F5obR~b^N6>V} z>Bi@xV$7P?Ad{K8(r`FlVKTOS< zml6K~xn>fF(0R+}C-8aBk>bWRe5bY?|A{$v3dFV{Lb#ur?JC0Xv!@g5T)w1t*)D-K18ZQ$_i=X3yCOQN}WUsx|B(2*FNj$gJ`K! zG$2AH^E`xj!%{)zP;Q<=rPC+M7csJ!k%@`W=@TxUFH1J&>!dnKa&+=kl4L9rL=MTn zGegqTMF$&eZ(d801vNL1kdQ2RL`^NWb2}rHpCp9@#y=OonjHUS5u@97ZuB(@QgT8y z{GF(ukll?QAY6QDPxv<%r$Q<)KE8*k8qiYgqQ8+i9I?MvElmu{Y--}62r zYEv}6*7i)xDzT|3(jkUB)<+@)RdI`!K(W#wMyH?Ldu=C=HeaO;*Qs_Dg0_i3AZS~c zBKC9?MViEL)AGn77X*~B04PiC5XIXwChQ{<4>%vc&71R&e5W;YzSJ?db$^z^-qy}= zgmg#Z-L}enZ62)f#nD9ip~qO1Vm0w{phfOP;L zpaakY=mQJ@1_5EfalpHPGk^<#IA9v!>P?g(NS)0}f=~&R>i}(lLBJ_M46xyCSKcKk z(X>_Jp$}GwnajEah&_yyL)l0{tB|r9FbwvdFR6VbE?l4wY(A@f(s0=`<%G3^|IPj# zo5ehb7c*NpPw@mZL~I}rdtzh4S&FZ+ijfE%ornB(t1wEFOJn3cT46&7`9+HmrFdNT z`ZcZKkzck5zoqz7z2+nxO+kLuD*T4xPxP7-w88*!!YPXXtXI52g?A_(W44I7#wZeg zMe&D>R#+gvofnQ%d`{uN6k1mQpMhVdjem|`@`YvKEPR8Rb;HlE`%2lBSVI_S?0Dp%^fx5is7|Vby1~!v%VlyuY^dsB!_QEE~%b$kvyrsq>B_u2MYHOT456r zIWw?PI|3JnrI$nfwYlD&BB}CZX)jC>W=c}1^5o)Pj$4*&AibQSI%&@9<%~}y&9q+5 zv{5%7J0`57Jf9<;<%;)Lix%U9&2HI}B4Sg1kyWUocnuqygCBhx!6TGYem<^aRnBTU zX-UZ`I%^m;BCV31=W_fD&7+w4>0Dh5vvzHP@#8{!Pkr z<|Z^Um3aQ5dya$0hz%p1!kqu84mD#_`Ymf&`1W3lFqyzpY)XRv8iWe+(&b`z?8Sc2 zB}<(KSz|R5kzMY- zL~m_sj{i9`TUiKJm>5=8yiA27iHS0T-_pnbdct`)p@!pM(H!wvINV_lWzD}d+D`Wj zB@Wn9H=ofyZn)&RtNsR?xVxf)<*v>3*8o3g~pRtN77aJeF2d8}cH>N7=M_9QD>xes=;80Y;_j83MJwooq_cmv`(Ma+|?Nhwkxfhv_M1ZP`G+8`##el)zDoT%@Ks5P}AH8D2iaXZq36(&B@TKtCi>Q?+fTA4>|0?=yK;eaS34IpVsG zP$1a8l&FDL;%6h%6MVhRyJk7@X(26{`^nBgDA3+Yd@X*ht1U#DnQ-fa?fyV(A@f+- zzEx`r_(-T_QwQk`ZX#P++FI6!T6}sd95mq1;_tV!>iaRX-5HGFq?!0NEU*4i z=3TJBw~z$3kY??EelOH6@O{0mHb`27L7(0oZ7cjst+}n`fBM6+EtpA?x;;_G!2ap1 GC;dN{7~$*y literal 0 HcmV?d00001 diff --git a/bootloader/rboot b/bootloader/rboot index 4cf6132..30afbaa 160000 --- a/bootloader/rboot +++ b/bootloader/rboot @@ -1 +1 @@ -Subproject commit 4cf6132f6ee624318c04b2a8d9e37da64b7c283c +Subproject commit 30afbaa777e00abf9d7d469fb3345f118c4975c1 diff --git a/bootloader/rboot.h b/bootloader/rboot.h new file mode 100644 index 0000000..9260a64 --- /dev/null +++ b/bootloader/rboot.h @@ -0,0 +1,23 @@ +/* rboot header overrides + + This "wrapper" header contains default values for building rboot + on/for esp-open-rtos. It gets included both when building the + bootloader and when building extras/rboot-ota support. It includes + the default bootloader/rboot/rboot.h header via the gcc + include_next mechanism. +*/ +#ifndef __RBOOT_H__ + +// Big flash support is required for esp-open-rtos (we use 8Mbit +// "slots" only.) +#define BOOT_BIG_FLASH + +// enable 2 way communication between +// rBoot and the user app via the esp rtc data area +#define BOOT_RTC_ENABLED + +// Call 'main' rboot.h to pick up defaults for other parameters +#include_next "rboot.h" + +#endif + diff --git a/common.mk b/common.mk index 73a998d..172fbe2 100644 --- a/common.mk +++ b/common.mk @@ -20,149 +20,7 @@ # assume the 'root' directory (ie top of the tree) is the directory common.mk is in ROOT := $(dir $(lastword $(MAKEFILE_LIST))) -# include optional local overrides at the root level, then in program directory -# -# Create either of these files for local system overrides if possible, -# instead of editing this makefile directly. --include $(ROOT)local.mk --include local.mk - -# Flash size in megabits -# Valid values are same as for esptool.py - 2,4,8,16,32 -FLASH_SIZE ?= 16 - -# Flash mode, valid values are same as for esptool.py - qio,qout,dio.dout -FLASH_MODE ?= qio - -# Flash speed in MHz, valid values are same as for esptool.py - 80, 40, 26, 20 -FLASH_SPEED ?= 40 - -# Output directories to store intermediate compiled files -# relative to the program directory -BUILD_DIR ?= $(PROGRAM_DIR)build/ -FIRMWARE_DIR ?= $(PROGRAM_DIR)firmware/ - -# esptool.py from https://github.com/themadinventor/esptool -ESPTOOL ?= esptool.py -# serial port settings for esptool.py -ESPPORT ?= /dev/ttyUSB0 -ESPBAUD ?= 115200 - -# set this to 0 if you don't need floating point support in printf/scanf -# this will save approx 14.5KB flash space and 448 bytes of statically allocated -# data RAM -# -# NB: Setting the value to 0 requires a recent esptool.py (Feb 2016 / commit ebf02c9) -PRINTF_SCANF_FLOAT_SUPPORT ?= 1 - -# Set OTA to 1 to build an image that supports rBoot OTA bootloader -# -# Requires 16mbit or higher flash sizes, with 8mbit -# images for each OTA "slot" -OTA ?= 0 - -ifeq ($(OTA),1) -# Tell C preprocessor that we're building for OTA -CPPFLAGS = -DOTA -endif - -FLAVOR ?= release # or debug - -# Compiler names, etc. assume gdb -CROSS ?= xtensa-lx106-elf- - -# Path to the filteroutput.py tool -FILTEROUTPUT ?= $(ROOT)/utils/filteroutput.py - -AR = $(CROSS)ar -CC = $(CROSS)gcc -CPP = $(CROSS)cpp -LD = $(CROSS)gcc -NM = $(CROSS)nm -C++ = $(CROSS)g++ -SIZE = $(CROSS)size -OBJCOPY = $(CROSS)objcopy -OBJDUMP = $(CROSS)objdump - -# Source components to compile and link. Each of these are subdirectories -# of the root, with a 'component.mk' file. -COMPONENTS ?= $(EXTRA_COMPONENTS) FreeRTOS lwip core - -# binary esp-iot-rtos SDK libraries to link. These are pre-processed prior to linking. -SDK_LIBS ?= main net80211 phy pp wpa - -# open source libraries linked in -LIBS ?= hal gcc c - -# set to 0 if you want to use the toolchain libc instead of esp-open-rtos newlib -OWN_LIBC ?= 1 - -# Note: you will need a recent esp -ENTRY_SYMBOL ?= call_user_start - -# Set this to zero if you don't want individual function & data sections -# (some code may be slightly slower, linking will be slighty slower, -# but compiled code size will come down a small amount.) -SPLIT_SECTIONS ?= 1 - -# Set this to 1 to have all compiler warnings treated as errors (and stop the -# build). This is recommended whenever you are working on code which will be -# submitted back to the main project, as all submitted code will be expected to -# compile without warnings to be accepted. -WARNINGS_AS_ERRORS ?= 0 - -# Common flags for both C & C++_ -C_CXX_FLAGS ?= -Wall -Wl,-EL -nostdlib $(EXTRA_C_CXX_FLAGS) -# Flags for C only -CFLAGS ?= $(C_CXX_FLAGS) -std=gnu99 $(EXTRA_CFLAGS) -# Flags for C++ only -CXXFLAGS ?= $(C_CXX_FLAGS) -fno-exceptions -fno-rtti $(EXTRA_CXXFLAGS) - -# these aren't technically preprocesor args, but used by all 3 of C, C++, assembler -CPPFLAGS += -mlongcalls -mtext-section-literals - -LDFLAGS = -nostdlib -Wl,--no-check-sections -L$(BUILD_DIR)sdklib -L$(ROOT)lib -u $(ENTRY_SYMBOL) -Wl,-static -Wl,-Map=$(BUILD_DIR)$(PROGRAM).map $(EXTRA_LDFLAGS) - -ifeq ($(WARNINGS_AS_ERRORS),1) - C_CXX_FLAGS += -Werror -endif - -ifeq ($(SPLIT_SECTIONS),1) - C_CXX_FLAGS += -ffunction-sections -fdata-sections - LDFLAGS += -Wl,-gc-sections -endif - -ifeq ($(FLAVOR),debug) - C_CXX_FLAGS += -g -O0 - LDFLAGS += -g -O0 -else ifeq ($(FLAVOR),sdklike) - # These are flags intended to produce object code as similar as possible to - # the output of the compiler used to build the SDK libs (for comparison of - # disassemblies when coding replacement routines). It is not normally - # intended to be used otherwise. - CFLAGS += -O2 -Os -fno-inline -fno-ipa-cp -fno-toplevel-reorder - LDFLAGS += -O2 -else - C_CXX_FLAGS += -g -O2 - LDFLAGS += -g -O2 -endif - -GITSHORTREV=\"$(shell cd $(ROOT); git rev-parse --short -q HEAD 2> /dev/null)\" -ifeq ($(GITSHORTREV),\"\") - GITSHORTREV="\"(nogit)\"" # (same length as a short git hash) -endif -CPPFLAGS += -DGITSHORTREV=$(GITSHORTREV) - -ifeq ($(OTA),0) -LINKER_SCRIPTS = $(ROOT)ld/nonota.ld -else -LINKER_SCRIPTS = $(ROOT)ld/ota.ld -endif -LINKER_SCRIPTS += $(ROOT)ld/common.ld $(ROOT)ld/rom.ld - -#### -#### no user configurable options below here -#### +include $(ROOT)parameters.mk ifndef PROGRAM $(error "Set the PROGRAM environment variable in your Makefile before including common.mk") @@ -184,20 +42,7 @@ LIB_ARGS = $(addprefix -l,$(LIBS)) PROGRAM_OUT = $(BUILD_DIR)$(PROGRAM).out LDFLAGS += $(addprefix -T,$(LINKER_SCRIPTS)) -# firmware tool arguments -ESPTOOL_ARGS=-fs $(FLASH_SIZE)m -fm $(FLASH_MODE) -ff $(FLASH_SPEED)m - -ifeq ($(OTA),0) -# for non-OTA, we create two different files for uploading into the flash -# these are the names and options to generate them -FW_ADDR_1 = 0x00000 -FW_ADDR_2 = 0x20000 -FW_FILE_1 = $(addprefix $(FIRMWARE_DIR),$(FW_ADDR_1).bin) -FW_FILE_2 = $(addprefix $(FIRMWARE_DIR),$(FW_ADDR_2).bin) -else -# for OTA, it's a single monolithic image -FW_FILE = $(addprefix $(FIRMWARE_DIR),$(PROGRAM)-ota.bin) -endif +FW_FILE = $(addprefix $(FIRMWARE_DIR),$(PROGRAM).bin) # Common include directories, shared across all "components" # components will add their include directories to this argument @@ -223,7 +68,7 @@ Q := @ vecho := @echo endif -.PHONY: all clean debug_print flash flash_bootloader erase_flash +.PHONY: all clean flash erase_flash all: $(PROGRAM_OUT) $(FW_FILE_1) $(FW_FILE_2) $(FW_FILE) @@ -364,17 +209,8 @@ $(FW_FILE): $(PROGRAM_OUT) $(FIRMWARE_DIR) $(vecho) "FW $@" $(Q) $(ESPTOOL) elf2image --version=2 $(ESPTOOL_ARGS) $< -o $(FW_FILE) -ifeq ($(OTA),0) -flash: $(FW_FILE_1) $(FW_FILE_2) - $(ESPTOOL) -p $(ESPPORT) --baud $(ESPBAUD) write_flash $(ESPTOOL_ARGS) $(FW_ADDR_2) $(FW_FILE_2) $(FW_ADDR_1) $(FW_FILE_1) -else flash: $(FW_FILE) - $(vecho) "Flashing OTA image slot 0 (bootloader not updated)" - $(ESPTOOL) -p $(ESPPORT) --baud $(ESPBAUD) write_flash $(ESPTOOL_ARGS) 0x2000 $(FW_FILE) -endif - -flash_bootloader: - $(MAKE) -C $(ROOT)/bootloader flash + $(ESPTOOL) -p $(ESPPORT) --baud $(ESPBAUD) write_flash $(ESPTOOL_ARGS) 0x0 $(RBOOT_BIN) 0x1000 $(RBOOT_CONF) 0x2000 $(FW_FILE) erase_flash: $(ESPTOOL) -p $(ESPPORT) --baud $(ESPBAUD) erase_flash diff --git a/core/spiflash-cache-enable.S b/core/spiflash-cache-enable.S index 2363a98..3e06f4a 100644 --- a/core/spiflash-cache-enable.S +++ b/core/spiflash-cache-enable.S @@ -12,8 +12,6 @@ * Copyright (C) 2015 Superhouse Automation Pty Ltd * BSD Licensed as described in the file LICENSE */ -#ifdef OTA - #define RBOOT_CONFIG_BASE (0x40200000 + 0x1000) #define RBOOT_ROMS_OFFS 0x8 /* offset of rboot_config_t.roms array in config */ @@ -74,5 +72,3 @@ Cache_Read_Enable: movi a0, cache_return_save /* restore a0 return address */ l32i a0, a0, 0 ret.n - -#endif diff --git a/examples/ota_basic/Makefile b/examples/ota_basic/Makefile index aafb05a..3f2ea5a 100644 --- a/examples/ota_basic/Makefile +++ b/examples/ota_basic/Makefile @@ -1,5 +1,4 @@ PROGRAM=ota_basic -OTA=1 EXTRA_COMPONENTS=extras/rboot-ota extras/mbedtls include ../../common.mk diff --git a/examples/ota_basic/ota_basic.c b/examples/ota_basic/ota_basic.c index 68e6c71..9ab3f20 100644 --- a/examples/ota_basic/ota_basic.c +++ b/examples/ota_basic/ota_basic.c @@ -16,7 +16,6 @@ #include "mbedtls/sha256.h" #include "ota-tftp.h" -#include "rboot.h" #include "rboot-api.h" #define TFTP_IMAGE_SERVER "192.168.1.23" diff --git a/extras/rboot-ota/component.mk b/extras/rboot-ota/component.mk index 690b7bc..62cf14f 100644 --- a/extras/rboot-ota/component.mk +++ b/extras/rboot-ota/component.mk @@ -1,8 +1,9 @@ # Component makefile for extras/rboot-ota -INC_DIRS += $(rboot-ota_ROOT) +# global include directories need to find rboot.h for integration, even +# when just including rboot-api.h :( +INC_DIRS += $(rboot-ota_ROOT) $(ROOT)bootloader $(ROOT)bootloader/rboot -# args for passing into compile rule generation rboot-ota_SRC_DIR = $(rboot-ota_ROOT) $(eval $(call component_compile_rules,rboot-ota)) diff --git a/extras/rboot-ota/ota-tftp.c b/extras/rboot-ota/ota-tftp.c index 1dee8cd..b6472a3 100644 --- a/extras/rboot-ota/ota-tftp.c +++ b/extras/rboot-ota/ota-tftp.c @@ -22,7 +22,6 @@ #include #include "ota-tftp.h" -#include "rboot.h" #include "rboot-api.h" #define TFTP_FIRMWARE_FILE "firmware.bin" diff --git a/extras/rboot-ota/rboot-api.c b/extras/rboot-ota/rboot-api.c index b3faac2..b9a5e57 100644 --- a/extras/rboot-ota/rboot-api.c +++ b/extras/rboot-ota/rboot-api.c @@ -8,7 +8,7 @@ // esp-open-rtos additions Copyright 2016 Angus Gratton ////////////////////////////////////////////////// -#include +#include #include //#include //#include diff --git a/extras/rboot-ota/rboot-api.h b/extras/rboot-ota/rboot-api.h index a74eeb2..29c11bd 100644 --- a/extras/rboot-ota/rboot-api.h +++ b/extras/rboot-ota/rboot-api.h @@ -12,6 +12,7 @@ * @{ */ +#include #include #ifdef __cplusplus diff --git a/extras/rboot-ota/rboot-integration.h b/extras/rboot-ota/rboot-integration.h index 064217b..005e9ea 100644 --- a/extras/rboot-ota/rboot-integration.h +++ b/extras/rboot-ota/rboot-integration.h @@ -9,6 +9,10 @@ #include #include +/*************************************************** + * Platform configuration definitions * + ***************************************************/ + #define uint8 uint8_t #define uint16 uint16_t #define uint32 uint32_t diff --git a/extras/rboot-ota/rboot.h b/extras/rboot-ota/rboot.h deleted file mode 100644 index 750c4d7..0000000 --- a/extras/rboot-ota/rboot.h +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef __RBOOT_H__ -#define __RBOOT_H__ - -////////////////////////////////////////////////// -// rBoot open source boot loader for ESP8266. -// Copyright 2015 Richard A Burton -// richardaburton@gmail.com -// See license.txt for license terms. -////////////////////////////////////////////////// - -#ifdef __cplusplus -extern "C" { -#endif - -#define RBOOT_INTEGRATION - -// uncomment to use only c code -// if you aren't using gcc you may need to do this -//#define BOOT_NO_ASM - -// Note: enabling RBOOT_CONFIG_CHECKSUM here means the config sector -// is always written by esp-open-rtos with a checksum. -// -// This means it will work whether the bootloader has config -// checksumming enabled or not. - -#define BOOT_CONFIG_CHKSUM - -// uncomment to enable big flash support (>1MB) -#define BOOT_BIG_FLASH - -// uncomment to enable 2 way communication between -// rBoot and the user app via the esp rtc data area -#define BOOT_RTC_ENABLED - -// uncomment to enable GPIO booting -//#define BOOT_GPIO_ENABLED - -// uncomment to include .irom0.text section in the checksum -// roms must be built with esptool2 using -iromchksum option -//#define BOOT_IROM_CHKSUM - -// uncomment to add a boot delay, allows you time to connect -// a terminal before rBoot starts to run and output messages -// value is in microseconds -//#define BOOT_DELAY_MICROS 2000000 - -// increase if required -#define MAX_ROMS 4 - -#define CHKSUM_INIT 0xef - -#define SECTOR_SIZE 0x1000 -#define BOOT_CONFIG_SECTOR 1 - -#define BOOT_CONFIG_MAGIC 0xe1 -#define BOOT_CONFIG_VERSION 0x01 - -#define MODE_STANDARD 0x00 -#define MODE_GPIO_ROM 0x01 -#define MODE_TEMP_ROM 0x02 - -#define RBOOT_RTC_MAGIC 0x2334ae68 -#define RBOOT_RTC_READ 1 -#define RBOOT_RTC_WRITE 0 -#define RBOOT_RTC_ADDR 64 - -#ifdef RBOOT_INTEGRATION -#include -#endif - -/** @brief Structure containing rBoot configuration - * @note ROM addresses must be multiples of 0x1000 (flash sector aligned). - * Without BOOT_BIG_FLASH only the first 8Mbit (1MB) of the chip will - * be memory mapped so ROM slots containing .irom0.text sections must - * remain below 0x100000. Slots beyond this will only be accessible via - * spi read calls, so use these for stored resources, not code. With - * BOOT_BIG_FLASH the flash will be mapped in chunks of 8MBit (1MB), so - * ROMs can be anywhere, but must not straddle two 8MBit (1MB) blocks. - * @ingroup rboot -*/ -typedef struct { - uint8 magic; ///< Our magic, identifies rBoot configuration - should be BOOT_CONFIG_MAGIC - uint8 version; ///< Version of configuration structure - should be BOOT_CONFIG_VERSION - uint8 mode; ///< Boot loader mode (MODE_STANDARD | MODE_GPIO_ROM) - uint8 current_rom; ///< Currently selected ROM (will be used for next standard boot) - uint8 gpio_rom; ///< ROM to use for GPIO boot (hardware switch) with mode set to MODE_GPIO_ROM - uint8 count; ///< Quantity of ROMs available to boot - uint8 unused[2]; ///< Padding (not used) - uint32 roms[MAX_ROMS]; ///< Flash addresses of each ROM -#ifdef BOOT_CONFIG_CHKSUM - uint8 chksum; ///< Checksum of this configuration structure (if BOOT_CONFIG_CHKSUM defined) -#endif -} rboot_config; - -#ifdef BOOT_RTC_ENABLED -/** @brief Structure containing rBoot status/control data - * @note This structure is used to, optionally, communicate between rBoot and - * the user app. It is stored in the ESP RTC data area. - * @ingroup rboot -*/ -typedef struct { - uint32 magic; ///< Magic, identifies rBoot RTC data - should be RBOOT_RTC_MAGIC - uint8 next_mode; ///< The next boot mode, defaults to MODE_STANDARD - can be set to MODE_TEMP_ROM - uint8 last_mode; ///< The last (this) boot mode - can be MODE_STANDARD, MODE_GPIO_ROM or MODE_TEMP_ROM - uint8 last_rom; ///< The last (this) boot rom number - uint8 temp_rom; ///< The next boot rom number when next_mode set to MODE_TEMP_ROM - uint8 chksum; ///< Checksum of this structure this will be updated for you passed to the API -} rboot_rtc_data; -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/ld/nonota.ld b/ld/nonota.ld deleted file mode 100644 index 97054e6..0000000 --- a/ld/nonota.ld +++ /dev/null @@ -1,15 +0,0 @@ -/* Memory layout for esp-open-rtos when not using OTA - (ie ROM bootloader only, no second stage rboot) - */ -MEMORY -{ - dport0_0_seg : org = 0x3FF00000, len = 0x10 - dram0_0_seg : org = 0x3FFE8000, len = 0x14000 - iram1_0_seg : org = 0x40100000, len = 0x08000 -/* irom0 section, mapped from SPI flash - - Origin is offset by 0x20000 to leave space for bootloader RAM sections. - - - Length is max 8Mbit of mappable flash, minus start offset -*/ - irom0_0_seg : org = 0x40220000, len = (1M - 0x20000) -} diff --git a/ld/ota.ld b/ld/ota.ld deleted file mode 100644 index da32ce1..0000000 --- a/ld/ota.ld +++ /dev/null @@ -1,15 +0,0 @@ -/* Memory layout for esp-open-rtos when using OTA second stage bootloader */ - -MEMORY -{ - dport0_0_seg : org = 0x3FF00000, len = 0x10 - dram0_0_seg : org = 0x3FFE8000, len = 0x14000 - iram1_0_seg : org = 0x40100000, len = 0x08000 -/* irom0 section, mapped from SPI flash - - Origin is offset by 0x2010 to create spacer for second stage bootloader image, - header. - - - Length is max 8Mbit of mappable flash, minus start offset -*/ - irom0_0_seg : org = 0x40202010, len = (1M - 0x2010) -} diff --git a/ld/common.ld b/ld/program.ld similarity index 93% rename from ld/common.ld rename to ld/program.ld index 444c8e7..88fa0f4 100644 --- a/ld/common.ld +++ b/ld/program.ld @@ -1,7 +1,17 @@ -/* - * Common (OTA and non-OTA) parts for the esp-open-rtos Linker Script - * - */ +/* Memory layout for esp-open-rtos when using OTA second stage bootloader */ +MEMORY +{ + dport0_0_seg : org = 0x3FF00000, len = 0x10 + dram0_0_seg : org = 0x3FFE8000, len = 0x14000 + iram1_0_seg : org = 0x40100000, len = 0x08000 +/* irom0 section, mapped from SPI flash + - Origin is offset by 0x2010 to create spacer for second stage bootloader image, + header. + + - Length is max 8Mbit of mappable flash, minus start offset +*/ + irom0_0_seg : org = 0x40202010, len = (1M - 0x2010) +} /* FreeRTOS memory management functions diff --git a/parameters.mk b/parameters.mk new file mode 100644 index 0000000..e750e83 --- /dev/null +++ b/parameters.mk @@ -0,0 +1,141 @@ +# Parameters for the esp-open-rtos make process +# +# You can edit this file to change parameters, but a better option is +# to create a local.mk file and add overrides there. The local.mk file +# can be in the root directory, or the program directory alongside the +# Makefile, or both. +# +-include $(ROOT)local.mk +-include local.mk + +# Flash size in megabits +# Valid values are same as for esptool.py - 2,4,8,16,32 +FLASH_SIZE ?= 16 + +# Flash mode, valid values are same as for esptool.py - qio,qout,dio.dout +FLASH_MODE ?= qio + +# Flash speed in MHz, valid values are same as for esptool.py - 80, 40, 26, 20 +FLASH_SPEED ?= 40 + +# Output directories to store intermediate compiled files +# relative to the program directory +BUILD_DIR ?= $(PROGRAM_DIR)build/ +FIRMWARE_DIR ?= $(PROGRAM_DIR)firmware/ + +# esptool.py from https://github.com/themadinventor/esptool +ESPTOOL ?= esptool.py +# serial port settings for esptool.py +ESPPORT ?= /dev/ttyUSB0 +ESPBAUD ?= 115200 + +# firmware tool arguments +ESPTOOL_ARGS=-fs $(FLASH_SIZE)m -fm $(FLASH_MODE) -ff $(FLASH_SPEED)m + + +# set this to 0 if you don't need floating point support in printf/scanf +# this will save approx 14.5KB flash space and 448 bytes of statically allocated +# data RAM +# +# NB: Setting the value to 0 requires a recent esptool.py (Feb 2016 / commit ebf02c9) +PRINTF_SCANF_FLOAT_SUPPORT ?= 1 + +FLAVOR ?= release # or debug + +# Compiler names, etc. assume gdb +CROSS ?= xtensa-lx106-elf- + +# Path to the filteroutput.py tool +FILTEROUTPUT ?= $(ROOT)/utils/filteroutput.py + +AR = $(CROSS)ar +CC = $(CROSS)gcc +CPP = $(CROSS)cpp +LD = $(CROSS)gcc +NM = $(CROSS)nm +C++ = $(CROSS)g++ +SIZE = $(CROSS)size +OBJCOPY = $(CROSS)objcopy +OBJDUMP = $(CROSS)objdump + +# Source components to compile and link. Each of these are subdirectories +# of the root, with a 'component.mk' file. +COMPONENTS ?= $(EXTRA_COMPONENTS) FreeRTOS lwip core + +# binary esp-iot-rtos SDK libraries to link. These are pre-processed prior to linking. +SDK_LIBS ?= main net80211 phy pp wpa + +# open source libraries linked in +LIBS ?= hal gcc c + +# set to 0 if you want to use the toolchain libc instead of esp-open-rtos newlib +OWN_LIBC ?= 1 + +# Note: you will need a recent esp +ENTRY_SYMBOL ?= call_user_start + +# Set this to zero if you don't want individual function & data sections +# (some code may be slightly slower, linking will be slighty slower, +# but compiled code size will come down a small amount.) +SPLIT_SECTIONS ?= 1 + +# Set this to 1 to have all compiler warnings treated as errors (and stop the +# build). This is recommended whenever you are working on code which will be +# submitted back to the main project, as all submitted code will be expected to +# compile without warnings to be accepted. +WARNINGS_AS_ERRORS ?= 0 + +# Common flags for both C & C++_ +C_CXX_FLAGS ?= -Wall -Wl,-EL -nostdlib $(EXTRA_C_CXX_FLAGS) +# Flags for C only +CFLAGS ?= $(C_CXX_FLAGS) -std=gnu99 $(EXTRA_CFLAGS) +# Flags for C++ only +CXXFLAGS ?= $(C_CXX_FLAGS) -fno-exceptions -fno-rtti $(EXTRA_CXXFLAGS) + +# these aren't all technically preprocesor args, but used by all 3 of C, C++, assembler +CPPFLAGS += -mlongcalls -mtext-section-literals + +LDFLAGS = -nostdlib -Wl,--no-check-sections -L$(BUILD_DIR)sdklib -L$(ROOT)lib -u $(ENTRY_SYMBOL) -Wl,-static -Wl,-Map=$(BUILD_DIR)$(PROGRAM).map $(EXTRA_LDFLAGS) + +ifeq ($(WARNINGS_AS_ERRORS),1) + C_CXX_FLAGS += -Werror +endif + +ifeq ($(SPLIT_SECTIONS),1) + C_CXX_FLAGS += -ffunction-sections -fdata-sections + LDFLAGS += -Wl,-gc-sections +endif + +ifeq ($(FLAVOR),debug) + C_CXX_FLAGS += -g -O0 + LDFLAGS += -g -O0 +else ifeq ($(FLAVOR),sdklike) + # These are flags intended to produce object code as similar as possible to + # the output of the compiler used to build the SDK libs (for comparison of + # disassemblies when coding replacement routines). It is not normally + # intended to be used otherwise. + CFLAGS += -O2 -Os -fno-inline -fno-ipa-cp -fno-toplevel-reorder + LDFLAGS += -O2 +else + C_CXX_FLAGS += -g -O2 + LDFLAGS += -g -O2 +endif + +GITSHORTREV=\"$(shell cd $(ROOT); git rev-parse --short -q HEAD 2> /dev/null)\" +ifeq ($(GITSHORTREV),\"\") + GITSHORTREV="\"(nogit)\"" # (same length as a short git hash) +endif +CPPFLAGS += -DGITSHORTREV=$(GITSHORTREV) + +LINKER_SCRIPTS += $(ROOT)ld/program.ld $(ROOT)ld/rom.ld + +# rboot firmware binary paths for flashing +RBOOT_BIN = $(ROOT)bootloader/firmware/rboot.bin +RBOOT_PREBUILT_BIN = $(ROOT)bootloader/firmware_prebuilt/rboot.bin +RBOOT_CONF = $(ROOT)bootloader/firmware_prebuilt/blank_config.bin + +# if a custom bootloader hasn't been compiled, use the +# prebuilt binary from the source tree +ifeq (,$(wildcard $(RBOOT_BIN))) +RBOOT_BIN=$(RBOOT_PREBUILT_BIN) +endif From d62fd4899a8e61fd2a27a9b1f3b5d5705b0a8e9c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Mon, 23 May 2016 12:23:04 +1000 Subject: [PATCH 36/44] ota_basic example cleanup --- examples/ota_basic/ota_basic.c | 115 ++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 46 deletions(-) diff --git a/examples/ota_basic/ota_basic.c b/examples/ota_basic/ota_basic.c index 9ab3f20..61b50b1 100644 --- a/examples/ota_basic/ota_basic.c +++ b/examples/ota_basic/ota_basic.c @@ -1,6 +1,8 @@ /* A very simple OTA example * - * Binds a TCP socket, reads an image from it over TFTP and then flashes live. + * Tries to run both a TFTP client and a TFTP server simultaneously, either will accept a TTP firmware and update it. + * + * Not a realistic OTA setup, this needs adapting (choose either client or server) before you'd want to use it. * * For more information about esp-open-rtos OTA see https://github.com/SuperHouse/esp-open-rtos/wiki/OTA-Update-Configuration * @@ -18,6 +20,7 @@ #include "ota-tftp.h" #include "rboot-api.h" +/* TFTP client will request this image filenames from this server */ #define TFTP_IMAGE_SERVER "192.168.1.23" #define TFTP_IMAGE_FILENAME1 "firmware1.bin" #define TFTP_IMAGE_FILENAME2 "firmware2.bin" @@ -25,6 +28,68 @@ /* Output of the command 'sha256sum firmware1.bin' */ static const char *FIRMWARE1_SHA256 = "88199daff8b9e76975f685ec7f95bc1df3c61bd942a33a54a40707d2a41e5488"; +/* Example function to TFTP download a firmware file and verify its SHA256 before + booting into it. +*/ +static void tftpclient_download_and_verify_file1(int slot, rboot_config *conf) +{ + printf("Downloading %s to slot %d...\n", TFTP_IMAGE_FILENAME1, slot); + int res = ota_tftp_download(TFTP_IMAGE_SERVER, TFTP_PORT+1, TFTP_IMAGE_FILENAME1, 1000, slot); + printf("ota_tftp_download %s result %d\n", TFTP_IMAGE_FILENAME1, res); + + if (res != 0) { + return; + } + + printf("Looks valid, calculating SHA256...\n"); + uint32_t length; + bool valid = rboot_verify_image(conf->roms[slot], &length, NULL); + static mbedtls_sha256_context ctx; + mbedtls_sha256_init(&ctx); + mbedtls_sha256_starts(&ctx, 0); + valid = valid && rboot_digest_image(conf->roms[slot], length, (rboot_digest_update_fn)mbedtls_sha256_update, &ctx); + static uint8_t hash_result[32]; + mbedtls_sha256_finish(&ctx, hash_result); + mbedtls_sha256_free(&ctx); + + if(!valid) + { + printf("Not valid after all :(\n"); + return; + } + + printf("Image SHA256 = "); + valid = true; + for(int i = 0; i < sizeof(hash_result); i++) { + char hexbuf[3]; + snprintf(hexbuf, 3, "%02x", hash_result[i]); + printf(hexbuf); + if(strncmp(hexbuf, FIRMWARE1_SHA256+i*2, 2)) + valid = false; + } + printf("\n"); + + if(!valid) { + printf("Downloaded image SHA256 didn't match expected '%s'\n", FIRMWARE1_SHA256); + return; + } + + printf("SHA256 Matches. Rebooting into slot %d...\n", slot); + rboot_set_current_rom(slot); + sdk_system_restart(); +} + +/* Much simpler function that just downloads a file via TFTP into an rboot slot. + + ( + */ +static void tftpclient_download_file2(int slot) +{ + printf("Downloading %s to slot %d...\n", TFTP_IMAGE_FILENAME2, slot); + int res = ota_tftp_download(TFTP_IMAGE_SERVER, TFTP_PORT+1, TFTP_IMAGE_FILENAME2, 1000, slot); + printf("ota_tftp_download %s result %d\n", TFTP_IMAGE_FILENAME2, res); +} + void tftp_client_task(void *pvParameters) { printf("TFTP client task starting...\n"); @@ -43,53 +108,10 @@ void tftp_client_task(void *pvParameters) Note: example will reboot into FILENAME1 if it is successfully downloaded, but FILENAME2 is ignored. */ while(1) { - printf("Downloading %s to slot %d...\n", TFTP_IMAGE_FILENAME1, slot); - int res = ota_tftp_download(TFTP_IMAGE_SERVER, TFTP_PORT, TFTP_IMAGE_FILENAME1, 1000, slot); - printf("ota_tftp_download %s result %d\n", TFTP_IMAGE_FILENAME1, res); - if(res == 0) { - printf("Looks valid, calculating SHA256...\n"); - uint32_t length; - bool valid = rboot_verify_image(conf.roms[slot], &length, NULL); - static mbedtls_sha256_context ctx; - mbedtls_sha256_init(&ctx); - mbedtls_sha256_starts(&ctx, 0); - valid = valid && rboot_digest_image(conf.roms[slot], length, (rboot_digest_update_fn)mbedtls_sha256_update, &ctx); - static uint8_t hash_result[32]; - mbedtls_sha256_finish(&ctx, hash_result); - mbedtls_sha256_free(&ctx); - - if(!valid) - { - printf("Not valid after all :(\n"); - } - else - { - printf("Image SHA256 = "); - bool valid = true; - for(int i = 0; i < sizeof(hash_result); i++) { - char hexbuf[3]; - snprintf(hexbuf, 3, "%02x", hash_result[i]); - printf(hexbuf); - if(strncmp(hexbuf, FIRMWARE1_SHA256+i*2, 2)) - valid = false; - - } - printf("\n"); - if (valid) { - printf("SHA256 Matches. Rebooting into slot %d...\n", slot); - rboot_set_current_rom(slot); - sdk_system_restart(); - } - else { - printf("Downloaded image SHA256 didn't match expected '%s'\n", FIRMWARE1_SHA256); - } - } - } + tftpclient_download_and_verify_file1(slot, &conf); vTaskDelay(5000 / portTICK_RATE_MS); - printf("Downloading %s to slot %d...\n", TFTP_IMAGE_FILENAME2, slot); - res = ota_tftp_download(TFTP_IMAGE_SERVER, TFTP_PORT, TFTP_IMAGE_FILENAME2, 1000, slot); - printf("ota_tftp_download %s result %d\n", TFTP_IMAGE_FILENAME2, res); + tftpclient_download_file2(slot); vTaskDelay(5000 / portTICK_RATE_MS); } } @@ -114,6 +136,7 @@ void user_init(void) sdk_wifi_set_opmode(STATION_MODE); sdk_wifi_station_set_config(&config); + printf("Starting TFTP server..."); ota_tftp_init_server(TFTP_PORT); xTaskCreate(&tftp_client_task, (signed char *)"tftp_client", 2048, NULL, 2, NULL); } From 84856f80a9c31a12814168330e7865c3ce39fd32 Mon Sep 17 00:00:00 2001 From: Kenshi Kawaguchi Date: Wed, 25 May 2016 00:07:13 -0700 Subject: [PATCH 37/44] ota_tftp_download takes an optional receive_cb that will report on the status of the TFTP transfer --- extras/rboot-ota/ota-tftp.c | 17 ++++++++++++----- extras/rboot-ota/ota-tftp.h | 8 +++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/extras/rboot-ota/ota-tftp.c b/extras/rboot-ota/ota-tftp.c index b6472a3..99ebdf7 100644 --- a/extras/rboot-ota/ota-tftp.c +++ b/extras/rboot-ota/ota-tftp.c @@ -43,7 +43,7 @@ static void tftp_task(void *port_p); static char *tftp_get_field(int field, struct netbuf *netbuf); -static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t limit_offs, size_t *received_len, ip_addr_t *peer_addr, int peer_port); +static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t limit_offs, size_t *received_len, ip_addr_t *peer_addr, int peer_port, tftp_receive_cb receive_cb); static err_t tftp_send_ack(struct netconn *nc, int block); static err_t tftp_send_rrq(struct netconn *nc, const char *filename); static void tftp_send_error(struct netconn *nc, int err_code, const char *err_msg); @@ -53,7 +53,8 @@ void ota_tftp_init_server(int listen_port) xTaskCreate(tftp_task, (signed char *)"tftpOTATask", 512, (void *)listen_port, 2, NULL); } -err_t ota_tftp_download(const char *server, int port, const char *filename, int timeout, int ota_slot) +err_t ota_tftp_download(const char *server, int port, const char *filename, + int timeout, int ota_slot, tftp_receive_cb receive_cb) { rboot_config rboot_config = rboot_get_config(); /* Validate the OTA slot parameter */ @@ -101,7 +102,8 @@ err_t ota_tftp_download(const char *server, int port, const char *filename, int } size_t received_len; - err = tftp_receive_data(nc, flash_offset, flash_offset+MAX_IMAGE_SIZE, &received_len, &addr, port); + err = tftp_receive_data(nc, flash_offset, flash_offset+MAX_IMAGE_SIZE, + &received_len, &addr, port, receive_cb); netconn_delete(nc); return err; } @@ -189,7 +191,7 @@ static void tftp_task(void *listen_port) /* Finished WRQ phase, start TFTP data transfer */ size_t received_len; netconn_set_recvtimeout(nc, 10000); - int recv_err = tftp_receive_data(nc, conf.roms[slot], conf.roms[slot]+MAX_IMAGE_SIZE, &received_len, NULL, 0); + int recv_err = tftp_receive_data(nc, conf.roms[slot], conf.roms[slot]+MAX_IMAGE_SIZE, &received_len, NULL, 0, NULL); netconn_disconnect(nc); printf("OTA TFTP receive data result %d bytes %d\r\n", recv_err, received_len); @@ -240,7 +242,7 @@ static char *tftp_get_field(int field, struct netbuf *netbuf) #define TFTP_TIMEOUT_RETRANSMITS 10 -static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t limit_offs, size_t *received_len, ip_addr_t *peer_addr, int peer_port) +static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t limit_offs, size_t *received_len, ip_addr_t *peer_addr, int peer_port, tftp_receive_cb receive_cb) { *received_len = 0; const int DATA_PACKET_SZ = 512 + 4; /*( packet size plus header */ @@ -382,6 +384,11 @@ static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t lim return ack_err; } + // Make sure ack was successful before calling callback. + if(receive_cb) { + receive_cb(*received_len); + } + if(len < DATA_PACKET_SZ) { return ERR_OK; } diff --git a/extras/rboot-ota/ota-tftp.h b/extras/rboot-ota/ota-tftp.h index cccac2a..6be63aa 100644 --- a/extras/rboot-ota/ota-tftp.h +++ b/extras/rboot-ota/ota-tftp.h @@ -3,6 +3,8 @@ #include "lwip/err.h" +typedef void (*tftp_receive_cb)(size_t bytes_received); + /* TFTP Server OTA Support * * To use, call ota_tftp_init_server() which will start the TFTP server task @@ -41,8 +43,12 @@ void ota_tftp_init_server(int listen_port); Returns 0 on success, LWIP err.h values for errors. Does not change the current firmware slot, or reboot. + + receive_cb: called repeatedly after each successful packet that + has been written to flash and ACKed. Can pass NULL to omit. */ -err_t ota_tftp_download(const char *server, int port, const char *filename, int timeout, int ota_slot); +err_t ota_tftp_download(const char *server, int port, const char *filename, + int timeout, int ota_slot, tftp_receive_cb receive_cb); #define TFTP_PORT 69 From 7fe2020785dfa360a3f49946f426ec385327dbf4 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 26 May 2016 09:19:40 +1000 Subject: [PATCH 38/44] ota_basic example: Update TFTP client calls --- examples/ota_basic/ota_basic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ota_basic/ota_basic.c b/examples/ota_basic/ota_basic.c index 61b50b1..c2050d5 100644 --- a/examples/ota_basic/ota_basic.c +++ b/examples/ota_basic/ota_basic.c @@ -34,7 +34,7 @@ static const char *FIRMWARE1_SHA256 = "88199daff8b9e76975f685ec7f95bc1df3c61bd94 static void tftpclient_download_and_verify_file1(int slot, rboot_config *conf) { printf("Downloading %s to slot %d...\n", TFTP_IMAGE_FILENAME1, slot); - int res = ota_tftp_download(TFTP_IMAGE_SERVER, TFTP_PORT+1, TFTP_IMAGE_FILENAME1, 1000, slot); + int res = ota_tftp_download(TFTP_IMAGE_SERVER, TFTP_PORT+1, TFTP_IMAGE_FILENAME1, 1000, slot, NULL); printf("ota_tftp_download %s result %d\n", TFTP_IMAGE_FILENAME1, res); if (res != 0) { @@ -86,7 +86,7 @@ static void tftpclient_download_and_verify_file1(int slot, rboot_config *conf) static void tftpclient_download_file2(int slot) { printf("Downloading %s to slot %d...\n", TFTP_IMAGE_FILENAME2, slot); - int res = ota_tftp_download(TFTP_IMAGE_SERVER, TFTP_PORT+1, TFTP_IMAGE_FILENAME2, 1000, slot); + int res = ota_tftp_download(TFTP_IMAGE_SERVER, TFTP_PORT+1, TFTP_IMAGE_FILENAME2, 1000, slot, NULL); printf("ota_tftp_download %s result %d\n", TFTP_IMAGE_FILENAME2, res); } From 34094d233c9a4497621f3cf23edfd1fcb6e1209d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Sat, 28 May 2016 11:31:29 +1000 Subject: [PATCH 39/44] Travis: build rboot bootloader as part of automated build --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0e1b62e..4bd0884 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,7 @@ addons: - python-serial - sed - git + - vim-common before_install: - travis_wait 30 utils/travis_build/install_toolchain.sh @@ -42,3 +43,5 @@ script: - sed -i "s%#error%//#error%" include/ssid_config.h # Don't verbose-build all examples (too much output), only verbose-build errors - ( ${MAKE_CMD} ) || ( ${MAKE_CMD} V=1 ) + # build bootloader + - make -C bootloader/ From 230aa9fd373967e5ca25372889169183db70811c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Sat, 28 May 2016 12:29:28 +1000 Subject: [PATCH 40/44] Add new "RAM" storage macro for putting constant data in RAM Also update comments in common_macros.h following #142 --- core/include/common_macros.h | 93 +++++++++++++------ .../unaligned_load/unaligned_load.c | 6 +- 2 files changed, 66 insertions(+), 33 deletions(-) diff --git a/core/include/common_macros.h b/core/include/common_macros.h index 4aa6248..d4afe4f 100644 --- a/core/include/common_macros.h +++ b/core/include/common_macros.h @@ -37,15 +37,71 @@ #define VAL2FIELD_M(fieldname, value) (((value) & fieldname##_M) << fieldname##_S) #define SET_FIELD_M(regbits, fieldname, value) (((regbits) & ~FIELD_MASK(fieldname)) | VAL2FIELD_M(fieldname, value)) -/* Use this macro to store constant values in IROM flash instead - of having them loaded into rodata (which resides in DRAM) +/* Use the IRAM macro to place functions into Instruction RAM (IRAM) + instead of flash (aka irom). - Unlike the ESP8266 SDK you don't need an attribute like this for - standard functions. They're stored in flash by default. But - variables need them. + (This is the opposite to the Espressif SDK, where functions default + to being placed in IRAM but the ICACHE_FLASH_ATTR attribute will + place them in flash.) - Important to note: IROM flash can only be accessed via 32-bit word - aligned reads. It's up to the user of this attribute to ensure this. + Use the IRAM attribute for functions which are called when the + flash may not be available (for example during NMI exceptions), or + for functions which are called very frequently and need high + performance. + + Usage example: + + void IRAM high_performance_function(void) + { + // do important thing here + } + + Bear in mind IRAM is limited (32KB), compared to up to 1MB of flash. +*/ +#define IRAM __attribute__((section(".iram1.text"))) + +/* Use the RAM macro to place constant data (rodata) into RAM (data + RAM) instead of the default placement in flash. This is useful for + constant data which needs high performance access. + + Usage example: + + const RAM uint8_t constants[] = { 1, 2, 3, 7 }; + + When placing string literals in RAM, they need to be declared with + the type "const char[]" not "const char *" + + Usage example: + + const RAM char hello_world[] = "Hello World"; +*/ +#define RAM __attribute__((section(".data"))) + +/* Use the IRAM_DATA macro to place data into Instruction RAM (IRAM) + instead of the default of flash (for constant data) or data RAM + (for non-constant data). + + This may be useful to free up data RAM. However all data read from + any instruction space (either IRAM or Flash) must be 32-bit aligned + word reads. Reading unaligned data stored with IRAM_DATA will be + slower than reading data stored in RAM. You can't perform unaligned + writes to IRAM. +*/ +#define IRAM_DATA __attribute__((section(".iram1.data"))) + +/* Use the IROM macro to store constant values in IROM flash. In + esp-open-rtos this is already the default location for most constant + data (rodata), so you don't need this attribute in 99% of cases. + + The exceptions are to mark data in the core & freertos libraries, + where the default for constant data storage is RAM. + + (Unlike the Espressif SDK you don't need to use an attribute like + ICACHE_FLASH_ATTR for functions, they go into flash by default.) + + Important to note: IROM flash is accessed via 32-bit word aligned + reads. esp-open-rtos does some magic to "fix" unaligned reads, but + performance is reduced. */ #ifdef __cplusplus #define IROM __attribute__((section(".irom0.literal"))) @@ -53,28 +109,5 @@ #define IROM __attribute__((section(".irom0.literal"))) const #endif -/* Use this macro to place functions into Instruction RAM (IRAM) - instead of flash memory (IROM). - - This is useful for functions which are called when the flash may - not be available (for example during NMI exceptions), or for - functions which are called very frequently and need high - performance. - - Bear in mind IRAM is limited (32KB), compared to up to 1MB of flash. -*/ -#define IRAM __attribute__((section(".iram1.text"))) - -/* Use this macro to place data into Instruction RAM (IRAM) - instead of loaded into rodata which resides in DRAM. - - (IRAM can also be written to as necessary.) - - This may be useful to free up data RAM. However all data read from - the instruction space must be 32-bit aligned word reads - (non-aligned reads will use an interrupt routine to "fix" them and - still work, but are very slow.. -*/ -#define IRAM_DATA __attribute__((section(".iram1.rodata"))) #endif diff --git a/examples/experiments/unaligned_load/unaligned_load.c b/examples/experiments/unaligned_load/unaligned_load.c index 4244804..ff6cab7 100644 --- a/examples/experiments/unaligned_load/unaligned_load.c +++ b/examples/experiments/unaligned_load/unaligned_load.c @@ -13,9 +13,9 @@ #define TESTSTRING "O hai there! %d %d %d" -const char *dramtest = TESTSTRING; -const __attribute__((section(".iram1.notrodata"))) char iramtest[] = TESTSTRING; -const __attribute__((section(".text.notrodata"))) char iromtest[] = TESTSTRING; +const RAM char dramtest[] = TESTSTRING; +const char *iromtest = TESTSTRING; +const IRAM_DATA char iramtest[] = TESTSTRING; static inline uint32_t get_ccount (void) { From 0734fa4166b653095d6538c372bed59a3a279967 Mon Sep 17 00:00:00 2001 From: Raphael Luckom Date: Wed, 27 Apr 2016 23:57:51 -0400 Subject: [PATCH 41/44] correct timebase in sys_arch.c sys_now() now returns ms. --- lwip/sys_arch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lwip/sys_arch.c b/lwip/sys_arch.c index ad936e4..dc26dfd 100644 --- a/lwip/sys_arch.c +++ b/lwip/sys_arch.c @@ -487,7 +487,7 @@ void sys_init(void) u32_t sys_now(void) { - return xTaskGetTickCount(); + return xTaskGetTickCount() * portTICK_RATE_MS; } /*---------------------------------------------------------------------------* From c63b1cfa669a80b1d35a7238396e7ca8bec5a84b Mon Sep 17 00:00:00 2001 From: Sven Date: Tue, 31 May 2016 21:10:14 +0200 Subject: [PATCH 42/44] Set correct base address for register HOST_INF_SEL --- include/espressif/esp8266/eagle_soc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/espressif/esp8266/eagle_soc.h b/include/espressif/esp8266/eagle_soc.h index cf60190..8e3855b 100644 --- a/include/espressif/esp8266/eagle_soc.h +++ b/include/espressif/esp8266/eagle_soc.h @@ -66,7 +66,7 @@ //}} //DPORT{{ -#define HOST_INF_SEL (0x28) +#define HOST_INF_SEL (PERIPHS_DPORT_BASEADDR + 0x28) #define DPORT_LINK_DEVICE_SEL 0x000000FF #define DPORT_LINK_DEVICE_SEL_S 8 #define DPORT_PERI_IO_SWAP 0x000000FF From 3c875cc4189bb2ccf927aa6950597543469a992d Mon Sep 17 00:00:00 2001 From: Johan Kanflo Date: Tue, 21 Jun 2016 20:43:02 +0200 Subject: [PATCH 43/44] Call DisconnectNetwork(...) before MQTT reconnect --- examples/mqtt_client/mqtt_client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/mqtt_client/mqtt_client.c b/examples/mqtt_client/mqtt_client.c index fdf86c3..3ec50c5 100644 --- a/examples/mqtt_client/mqtt_client.c +++ b/examples/mqtt_client/mqtt_client.c @@ -157,6 +157,7 @@ static void mqtt_task(void *pvParameters) break; } printf("Connection dropped, request restart\n\r"); + DisconnectNetwork(&network); taskYIELD(); } } From 587c867d4bac6e49155b918f3c8c46f9a62e594b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 28 Jun 2016 10:09:08 +1000 Subject: [PATCH 44/44] queue.h: Re-add the BSD Copyright notice to queue.h from Espressif's SDK. Thanks @pfalcon for the heads-up on this: https://groups.google.com/forum/#!topic/esp8266-re/I4iO3fM0mmA --- include/espressif/queue.h | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/include/espressif/queue.h b/include/espressif/queue.h index a760c8d..7725a3b 100644 --- a/include/espressif/queue.h +++ b/include/espressif/queue.h @@ -1,6 +1,61 @@ +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + #ifndef _SYS_QUEUE_H_ #define _SYS_QUEUE_H_ +/* + * Note: This header file comes to us from the Espressif + * SDK. Espressif adapted it from BSD's queue.h. The BSD copyright + * notice and the following extract from the explanatory statement + * were re-added by esp-open-rtos. + * + * FreeBSD version of this header: + * https://svnweb.freebsd.org/base/head/sys/sys/queue.h?view=markup + * + * **** + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * For details on the use of these macros, see the queue(3) manual page. + * + */ + #define QMD_SAVELINK(name, link) #define TRASHIT(x)