diff --git a/common.mk b/common.mk index fcfaeee..4e8ee3a 100644 --- a/common.mk +++ b/common.mk @@ -48,6 +48,13 @@ ESPTOOL ?= 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 # # Currently only works with 16mbit or more flash sizes, with 8mbit @@ -109,7 +116,7 @@ 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) +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 ($(SPLIT_SECTIONS),1) C_CXX_FLAGS += -ffunction-sections -fdata-sections @@ -202,6 +209,9 @@ INC_DIRS = $(PROGRAM_DIR) $(PROGRAM_DIR)include $(ROOT)include ifeq ($(OWN_LIBC),1) INC_DIRS += $(ROOT)libc/xtensa-lx106-elf/include LDFLAGS += -L$(ROOT)libc/xtensa-lx106-elf/lib + ifeq ($(PRINTF_SCANF_FLOAT_SUPPORT),1) + LDFLAGS += -u _printf_float -u _scanf_float + endif endif ifeq ("$(V)","1") diff --git a/core/app_main.c b/core/app_main.c index a710c77..7a9bf8d 100644 --- a/core/app_main.c +++ b/core/app_main.c @@ -250,7 +250,7 @@ void IRAM sdk_user_start(void) { // .text+0x3a8 void IRAM vApplicationStackOverflowHook(xTaskHandle task, char *task_name) { - printf("\"%s\"(stack_size = %lu) overflow the heap_size.\n", task_name, uxTaskGetStackHighWaterMark(task)); + printf("Task stack overflow (high water mark=%lu name=\"%s\")\n", uxTaskGetStackHighWaterMark(task), task_name); } // .text+0x3d8 diff --git a/core/esp_gpio.c b/core/esp_gpio.c index 23ddf22..8d12be1 100644 --- a/core/esp_gpio.c +++ b/core/esp_gpio.c @@ -8,32 +8,34 @@ void gpio_enable(const uint8_t gpio_num, const gpio_direction_t direction) { - uint32_t iomux_flags; - - switch(direction) { + switch (direction) { case GPIO_INPUT: - iomux_flags = 0; + GPIO.ENABLE_OUT_CLEAR = BIT(gpio_num); + iomux_set_gpio_function(gpio_num, false); break; case GPIO_OUTPUT: - iomux_flags = IOMUX_PIN_OUTPUT_ENABLE; + GPIO.CONF[gpio_num] &= ~GPIO_CONF_OPEN_DRAIN; + GPIO.ENABLE_OUT_SET = BIT(gpio_num); + iomux_set_gpio_function(gpio_num, true); break; case GPIO_OUT_OPEN_DRAIN: - iomux_flags = IOMUX_PIN_OUTPUT_ENABLE; - break; - case GPIO_INPUT_PULLUP: - iomux_flags = IOMUX_PIN_PULLUP; - break; - default: - return; /* Invalid direction flag */ - } - iomux_set_gpio_function(gpio_num, iomux_flags); - if(direction == GPIO_OUT_OPEN_DRAIN) GPIO.CONF[gpio_num] |= GPIO_CONF_OPEN_DRAIN; - else - GPIO.CONF[gpio_num] &= ~GPIO_CONF_OPEN_DRAIN; - if (iomux_flags & IOMUX_PIN_OUTPUT_ENABLE) GPIO.ENABLE_OUT_SET = BIT(gpio_num); - else - GPIO.ENABLE_OUT_CLEAR = BIT(gpio_num); + iomux_set_gpio_function(gpio_num, true); + break; + } +} + +void gpio_set_pullup(uint8_t gpio_num, bool enabled, bool enabled_during_sleep) +{ + uint32_t flags = 0; + + if (enabled) { + flags |= IOMUX_PIN_PULLUP; + } + if (enabled_during_sleep) { + flags |= IOMUX_PIN_PULLUP_SLEEP; + } + iomux_set_pullup_flags(gpio_to_iomux(gpio_num), flags); } diff --git a/core/include/esp/gpio.h b/core/include/esp/gpio.h index b5efcaf..01fe144 100644 --- a/core/include/esp/gpio.h +++ b/core/include/esp/gpio.h @@ -17,14 +17,24 @@ typedef enum { GPIO_INPUT, GPIO_OUTPUT, /* "Standard" push-pull output */ GPIO_OUT_OPEN_DRAIN, /* Open drain output */ - GPIO_INPUT_PULLUP, } gpio_direction_t; -/* Enable GPIO on the specified pin, and set it to input/output/ with - * pullup as needed +/* Enable GPIO on the specified pin, and set it to input or output mode */ void gpio_enable(const uint8_t gpio_num, const gpio_direction_t direction); +/* Enable/disable internal pullup resistor for a particular GPIO + * + * Note: According to Espressif, pullup resistor values are between 30K and + * 100K ohms (see http://bbs.espressif.com/viewtopic.php?t=1079#p4097) + * However, measured values suggest that the actual value is likely to be close + * to 47K in reality. + * + * NOTE: The enabled_during_sleep setting is currently untested (please send + * feedback if you give it a try) + */ +void gpio_set_pullup(uint8_t gpio_num, bool enabled, bool enabled_during_sleep); + /* Disable GPIO on the specified pin, and set it Hi-Z. * * If later muxing this pin to a different function, make sure to set @@ -36,13 +46,37 @@ static inline void gpio_disable(const uint8_t gpio_num) *gpio_iomux_reg(gpio_num) &= ~IOMUX_PIN_OUTPUT_ENABLE; } +/* Set whether the specified pin continues to drive its output when the ESP8266 + * goes into sleep mode. Note that this setting is reset to off whenever + * gpio_enable is called, so this must be called after calling that function. + * + * NOTE: This functionality is currently untested (please send feedback if you + * give it a try) + */ +static inline void gpio_set_output_on_sleep(const uint8_t gpio_num, bool enabled) +{ + if (enabled) { + IOMUX.PIN[gpio_to_iomux(gpio_num)] |= IOMUX_PIN_OUTPUT_ENABLE_SLEEP; + } else { + IOMUX.PIN[gpio_to_iomux(gpio_num)] &= ~IOMUX_PIN_OUTPUT_ENABLE_SLEEP; + } +} + /* Set output of a pin high or low. * - * Only works if pin has been set to GPIO_OUTPUT via gpio_enable() + * Only works if pin has been set to GPIO_OUTPUT or GPIO_OUT_OPEN_DRAIN via + * gpio_enable() + * + * If the mode is GPIO_OUT_OPEN_DRAIN, setting it low (false) will pull the pin + * down to ground, but setting it high (true) will allow it to float. Note + * that even in GPIO_OUT_OPEN_DRAIN mode, the input gates are still physically + * connected to the pin, and can be damaged if the voltage is not in either the + * "low" or "high" range. Make sure there is some sort of pull-up resistor on + * the line to avoid floating logic lines! */ static inline void gpio_write(const uint8_t gpio_num, const bool set) { - if(set) + if (set) GPIO.OUT_SET = BIT(gpio_num); else GPIO.OUT_CLEAR = BIT(gpio_num); @@ -50,7 +84,10 @@ static inline void gpio_write(const uint8_t gpio_num, const bool set) /* Toggle output of a pin * - * Only works if pin has been set to GPIO_OUTPUT via gpio_enable() + * Only works if pin has been set to GPIO_OUTPUT or GPIO_OUT_OPEN_DRAIN via + * gpio_enable() + * + * See notes in gpio_write() about GPIO_OUT_OPEN_DRAIN mode. */ static inline void gpio_toggle(const uint8_t gpio_num) { @@ -68,8 +105,12 @@ static inline void gpio_toggle(const uint8_t gpio_num) /* Read input value of a GPIO pin. * - * If pin is set as an input, this reads the value on the pin. - * If pin is set as an output, this reads the last value written to the pin. + * If pin is set GPIO_INPUT, this reads the level on the pin. + * If pin is set GPIO_OUTPUT, this reads the level at which the pin is + * currently being driven (i.e. the last value written). + * If pin is set GPIO_OUT_OPEN_DRAIN, when the pin is written low, this will + * return low (false), when the pin is written high, this will behave like + * GPIO_INPUT. */ static inline bool gpio_read(const uint8_t gpio_num) { @@ -80,7 +121,8 @@ extern void gpio_interrupt_handler(void); /* Set the interrupt type for a given pin * - * If int_type is not GPIO_INTTYPE_NONE, the gpio_interrupt_handler will be attached and unmasked. + * If int_type is not GPIO_INTTYPE_NONE, the gpio_interrupt_handler will be + * attached and unmasked. */ static inline void gpio_set_interrupt(const uint8_t gpio_num, const gpio_inttype_t int_type) { diff --git a/core/include/esp/iomux.h b/core/include/esp/iomux.h index 214c9e8..9948784 100644 --- a/core/include/esp/iomux.h +++ b/core/include/esp/iomux.h @@ -43,25 +43,41 @@ inline static esp_reg_t gpio_iomux_reg(const uint8_t gpio_number) return &(IOMUX.PIN[gpio_to_iomux(gpio_number)]); } +inline static void iomux_set_function(uint8_t iomux_num, uint32_t func) +{ + uint32_t prev = IOMUX.PIN[iomux_num] & ~IOMUX_PIN_FUNC_MASK; + IOMUX.PIN[iomux_num] = IOMUX_FUNC(func) | prev; +} + +inline static void iomux_set_direction_flags(uint8_t iomux_num, uint32_t dir_flags) +{ + uint32_t mask = IOMUX_PIN_OUTPUT_ENABLE | IOMUX_PIN_OUTPUT_ENABLE_SLEEP; + uint32_t prev = IOMUX.PIN[iomux_num] & ~mask; + IOMUX.PIN[iomux_num] = dir_flags | prev; +} + +inline static void iomux_set_pullup_flags(uint8_t iomux_num, uint32_t pullup_flags) +{ + uint32_t mask = IOMUX_PIN_PULLUP | IOMUX_PIN_PULLDOWN | IOMUX_PIN_PULLUP_SLEEP | IOMUX_PIN_PULLDOWN_SLEEP; + uint32_t prev = IOMUX.PIN[iomux_num] & ~mask; + IOMUX.PIN[iomux_num] = pullup_flags | prev; +} + /** * Set a pin to the GPIO function. * * This allows you to set pins to GPIO without knowing in advance the * exact register masks to use. * - * flags can be any of IOMUX_PIN_OUTPUT_ENABLE, IOMUX_PIN_PULLUP, IOMUX_PIN_PULLDOWN, etc. Any other flags will be cleared. - * - * Equivalent to a direct register operation if gpio_number is known at compile time. - * ie the following are equivalent: - * - * iomux_set_gpio_function(12, IOMUX_PIN_OUTPUT_ENABLE); - * IOMUX_GPIO12 = IOMUX_GPIO12_FUNC_GPIO | IOMUX_PIN_OUTPUT_ENABLE; + * Sets the function and direction, but leaves the pullup configuration the + * same as before. */ -inline static void iomux_set_gpio_function(const uint8_t gpio_number, const uint32_t flags) +inline static void iomux_set_gpio_function(uint8_t gpio_number, bool output_enable) { - const uint8_t reg_idx = gpio_to_iomux(gpio_number); - const uint32_t func = (reg_idx > 11 ? IOMUX_FUNC(0) : IOMUX_FUNC(3)) | flags; - IOMUX.PIN[reg_idx] = func | flags; + const uint8_t iomux_num = gpio_to_iomux(gpio_number); + const uint32_t func = iomux_num > 11 ? 0 : 3; + iomux_set_function(iomux_num, func); + iomux_set_direction_flags(iomux_num, output_enable ? IOMUX_PIN_OUTPUT_ENABLE : 0); } #ifdef __cplusplus diff --git a/core/include/esp/types.h b/core/include/esp/types.h index 3f0560a..cb816da 100644 --- a/core/include/esp/types.h +++ b/core/include/esp/types.h @@ -2,6 +2,7 @@ #define _ESP_TYPES_H #include +#include typedef volatile uint32_t *esp_reg_t; diff --git a/examples/access_point/Makefile b/examples/access_point/Makefile new file mode 100644 index 0000000..5a6bc6b --- /dev/null +++ b/examples/access_point/Makefile @@ -0,0 +1,5 @@ +# Makefile for access_point example +PROGRAM=access_point +EXTRA_COMPONENTS=extras/dhcpserver + +include ../../common.mk diff --git a/examples/access_point/access_point.c b/examples/access_point/access_point.c new file mode 100644 index 0000000..5a41156 --- /dev/null +++ b/examples/access_point/access_point.c @@ -0,0 +1,97 @@ +/* Very basic example showing usage of access point mode and the DHCP server. + + The ESP in the example runs a telnet server on 172.16.0.1 (port 23) that + outputs some status information if you connect to it, then closes + the connection. + + This example code is in the public domain. +*/ +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define AP_SSID "esp-open-rtos AP" +#define AP_PSK "esp-open-rtos" + +#define TELNET_PORT 23 + +static void telnetTask(void *pvParameters); + +void user_init(void) +{ + uart_set_baud(0, 115200); + printf("SDK version:%s\n", sdk_system_get_sdk_version()); + + sdk_wifi_set_opmode(SOFTAP_MODE); + struct ip_info ap_ip; + IP4_ADDR(&ap_ip.ip, 172, 16, 0, 1); + IP4_ADDR(&ap_ip.gw, 0, 0, 0, 0); + IP4_ADDR(&ap_ip.netmask, 255, 255, 0, 0); + sdk_wifi_set_ip_info(1, &ap_ip); + + struct sdk_softap_config ap_config = { + .ssid = AP_SSID, + .ssid_hidden = 0, + .channel = 3, + .ssid_len = strlen(AP_SSID), + .authmode = AUTH_WPA_WPA2_PSK, + .password = AP_PSK, + .max_connection = 3, + .beacon_interval = 100, + }; + sdk_wifi_softap_set_config(&ap_config); + + ip_addr_t first_client_ip; + IP4_ADDR(&first_client_ip, 172, 16, 0, 2); + dhcpserver_start(&first_client_ip, 4); + + xTaskCreate(telnetTask, (signed char *)"telnetTask", 512, NULL, 2, NULL); +} + +/* Telnet task listens on port 23, returns some status information and then closes + the connection if you connect to it. +*/ +static void telnetTask(void *pvParameters) +{ + struct netconn *nc = netconn_new (NETCONN_TCP); + if(!nc) { + printf("Status monitor: Failed to allocate socket.\r\n"); + return; + } + netconn_bind(nc, IP_ADDR_ANY, TELNET_PORT); + netconn_listen(nc); + + while(1) { + struct netconn *client = NULL; + err_t err = netconn_accept(nc, &client); + + if ( err != ERR_OK ) { + if(client) + netconn_delete(client); + continue; + } + + ip_addr_t client_addr; + uint16_t port_ignore; + netconn_peer(client, &client_addr, &port_ignore); + + char buf[80]; + snprintf(buf, sizeof(buf), "Uptime %d seconds\r\n", + xTaskGetTickCount()*portTICK_RATE_MS/1000); + netconn_write(client, buf, strlen(buf), NETCONN_COPY); + snprintf(buf, sizeof(buf), "Free heap %d bytes\r\n", (int)xPortGetFreeHeapSize()); + netconn_write(client, buf, strlen(buf), NETCONN_COPY); + snprintf(buf, sizeof(buf), "Your address is %d.%d.%d.%d\r\n\r\n", + ip4_addr1(&client_addr), ip4_addr2(&client_addr), + ip4_addr3(&client_addr), ip4_addr4(&client_addr)); + netconn_write(client, buf, strlen(buf), NETCONN_COPY); + netconn_delete(client); + } +} diff --git a/extras/dhcpserver/component.mk b/extras/dhcpserver/component.mk new file mode 100644 index 0000000..07da648 --- /dev/null +++ b/extras/dhcpserver/component.mk @@ -0,0 +1,9 @@ +# Component makefile for extras/dhcpserver + +INC_DIRS += $(dhcpserver_ROOT)include + +# args for passing into compile rule generation +dhcpserver_INC_DIR = $(dhcpserver_ROOT) +dhcpserver_SRC_DIR = $(dhcpserver_ROOT) + +$(eval $(call component_compile_rules,dhcpserver)) diff --git a/extras/dhcpserver/dhcpserver.c b/extras/dhcpserver/dhcpserver.c new file mode 100644 index 0000000..269b297 --- /dev/null +++ b/extras/dhcpserver/dhcpserver.c @@ -0,0 +1,370 @@ +/* Very basic LWIP & FreeRTOS-based DHCP server + * + * Based on RFC2131 http://www.ietf.org/rfc/rfc2131.txt + * ... although not fully RFC compliant yet. + * + * TODO + * * Allow binding on a single interface only (for mixed AP/client mode), lwip seems to make it hard to + * listen for or send broadcasts on a specific interface only. + * + * * Probably allocates more memory than it should, it should be possible to reuse netbufs in most cases. + * + * Part of esp-open-rtos + * Copyright (C) 2015 Superhouse Automation Pty Ltd + * BSD Licensed as described in the file LICENSE + */ +#include + +#include +#include +#include +#include + +/* Grow the size of the lwip dhcp_msg struct's options field, as LWIP + defaults to a 68 octet options field for its DHCP client, and most + full-sized clients send us more than this. */ +#define DHCP_OPTIONS_LEN 312 + +#include + +_Static_assert(sizeof(struct dhcp_msg) == offsetof(struct dhcp_msg, options) + 312, "dhcp_msg_t should have extended options size"); + +#include + +#include "dhcpserver.h" + +typedef struct { + uint8_t hwaddr[NETIF_MAX_HWADDR_LEN]; + uint32_t expires; +} dhcp_lease_t; + +typedef struct { + struct netconn *nc; + uint8_t max_leases; + ip_addr_t first_client_addr; + struct netif *server_if; + dhcp_lease_t *leases; /* length max_leases */ +} server_state_t; + +/* Only one DHCP server task can run at once, so we have global state + for it. +*/ +static xTaskHandle dhcpserver_task_handle; +static server_state_t *state; + +/* Handlers for various kinds of incoming DHCP messages */ +static void handle_dhcp_discover(struct dhcp_msg *received); +static void handle_dhcp_request(struct dhcp_msg *dhcpmsg); +static void handle_dhcp_release(struct dhcp_msg *dhcpmsg); + +static void send_dhcp_nak(struct dhcp_msg *dhcpmsg); + +static void dhcpserver_task(void *pxParameter); + +/* Utility functions */ +static uint8_t *find_dhcp_option(struct dhcp_msg *msg, uint8_t option_num, uint8_t min_length, uint8_t *length); +static uint8_t *add_dhcp_option_byte(uint8_t *opt, uint8_t type, uint8_t value); +static uint8_t *add_dhcp_option_bytes(uint8_t *opt, uint8_t type, void *value, uint8_t len); +static dhcp_lease_t *find_lease_slot(uint8_t *hwaddr); + +/* Copy IP address as dotted decimal to 'dest', must be at least 16 bytes long */ +inline static void sprintf_ipaddr(const ip_addr_t *addr, char *dest) +{ + if(addr == NULL) + sprintf(dest, "NULL"); + else + sprintf(dest, "%d.%d.%d.%d", ip4_addr1(addr), + ip4_addr2(addr), ip4_addr3(addr), ip4_addr4(addr)); +} + +void dhcpserver_start(const ip_addr_t *first_client_addr, uint8_t max_leases) +{ + /* Stop any existing running dhcpserver */ + if(dhcpserver_task_handle) + dhcpserver_stop(); + + state = malloc(sizeof(server_state_t)); + state->max_leases = max_leases; + state->leases = calloc(max_leases, sizeof(dhcp_lease_t)); + // state->server_if is assigned once the task is running - see comment in dhcpserver_task() + ip_addr_copy(state->first_client_addr, *first_client_addr); + + xTaskCreate(dhcpserver_task, (signed char *)"DHCPServer", 768, NULL, 8, &dhcpserver_task_handle); +} + +void dhcpserver_stop(void) +{ + if(dhcpserver_task_handle) { + vTaskDelete(dhcpserver_task_handle); + free(state); + dhcpserver_task_handle = NULL; + } +} + +static void dhcpserver_task(void *pxParameter) +{ + /* netif_list isn't assigned until after user_init completes, which is why we do it inside the task */ + state->server_if = netif_list; /* TODO: Make this configurable */ + + state->nc = netconn_new (NETCONN_UDP); + if(!state->nc) { + printf("OTA TFTP: Failed to allocate socket.\r\n"); + return; + } + + netconn_bind(state->nc, IP_ADDR_ANY, DHCP_SERVER_PORT); + + while(1) + { + struct netbuf *netbuf; + struct dhcp_msg received = { 0 }; + + /* Receive a DHCP packet */ + err_t err = netconn_recv(state->nc, &netbuf); + if(err != ERR_OK) { + printf("DHCP Server Error: Failed to receive DHCP packet. err=%d\r\n", err); + continue; + } + + /* expire any leases that have passed */ + uint32_t now = xTaskGetTickCount(); + for(int i = 0; i < state->max_leases; i++) { + uint32_t expires = state->leases[i].expires; + if(expires && expires < now) + state->leases[i].expires = 0; + } + + ip_addr_t received_ip; + u16_t port; + netconn_addr(state->nc, &received_ip, &port); + + if(netbuf_len(netbuf) < offsetof(struct dhcp_msg, options)) { + /* too short to be a valid DHCP client message */ + netbuf_delete(netbuf); + continue; + } + if(netbuf_len(netbuf) >= sizeof(struct dhcp_msg)) { + printf("DHCP Server Warning: Client sent more options than we know how to parse. len=%d\r\n", netbuf_len(netbuf)); + } + + netbuf_copy(netbuf, &received, sizeof(struct dhcp_msg)); + netbuf_delete(netbuf); + + uint8_t *message_type = find_dhcp_option(&received, DHCP_OPTION_MESSAGE_TYPE, + DHCP_OPTION_MESSAGE_TYPE_LEN, NULL); + if(!message_type) { + printf("DHCP Server Error: No message type field found"); + continue; + } + + + printf("State dump. Message type %d\n", *message_type); + for(int i = 0; i < state->max_leases; i++) { + dhcp_lease_t *lease = &state->leases[i]; + printf("lease slot %d expiry %d hwaddr %02x:%02x:%02x:%02x:%02x:%02x\r\n", i, lease->expires, lease->hwaddr[0], + lease->hwaddr[1], lease->hwaddr[2], lease->hwaddr[3], lease->hwaddr[4], + lease->hwaddr[5]); + } + + switch(*message_type) { + case DHCP_DISCOVER: + handle_dhcp_discover(&received); + break; + case DHCP_REQUEST: + handle_dhcp_request(&received); + break; + case DHCP_RELEASE: + handle_dhcp_release(&received); + default: + printf("DHCP Server Error: Unsupported message type %d\r\n", *message_type); + break; + } + } +} + +static void handle_dhcp_discover(struct dhcp_msg *dhcpmsg) +{ + if(dhcpmsg->htype != DHCP_HTYPE_ETH) + return; + if(dhcpmsg->hlen > NETIF_MAX_HWADDR_LEN) + return; + + dhcp_lease_t *freelease = find_lease_slot(dhcpmsg->chaddr); + if(!freelease) { + printf("DHCP Server: All leases taken.\r\n"); + return; /* Nothing available, so do nothing */ + } + + /* Reuse the DISCOVER buffer for the OFFER response */ + dhcpmsg->op = DHCP_BOOTREPLY; + bzero(dhcpmsg->options, DHCP_OPTIONS_LEN); + + ip_addr_copy(dhcpmsg->yiaddr, state->first_client_addr); + ip4_addr4(&(dhcpmsg->yiaddr)) += (freelease - state->leases); + + uint8_t *opt = (uint8_t *)&dhcpmsg->options; + opt = add_dhcp_option_byte(opt, DHCP_OPTION_MESSAGE_TYPE, DHCP_OFFER); + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SERVER_ID, &state->server_if->ip_addr, 4); + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SUBNET_MASK, &state->server_if->netmask, 4); + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_END, NULL, 0); + + struct netbuf *netbuf = netbuf_new(); + netbuf_alloc(netbuf, sizeof(struct dhcp_msg)); + netbuf_take(netbuf, dhcpmsg, sizeof(struct dhcp_msg)); + netconn_sendto(state->nc, netbuf, IP_ADDR_BROADCAST, 68); + netbuf_delete(netbuf); +} + +static void handle_dhcp_request(struct dhcp_msg *dhcpmsg) +{ + static char ipbuf[16]; + if(dhcpmsg->htype != DHCP_HTYPE_ETH) + return; + if(dhcpmsg->hlen > NETIF_MAX_HWADDR_LEN) + return; + + ip_addr_t requested_ip; + uint8_t *requested_ip_opt = find_dhcp_option(dhcpmsg, DHCP_OPTION_REQUESTED_IP, 4, NULL); + if(requested_ip_opt) { + memcpy(&requested_ip.addr, requested_ip_opt, 4); + } else if(ip_addr_cmp(&requested_ip, IP_ADDR_ANY)) { + ip_addr_copy(requested_ip, dhcpmsg->ciaddr); + } else { + printf("DHCP Server Error: No requested IP\r\n"); + send_dhcp_nak(dhcpmsg); + return; + } + + /* Test the first 4 octets match */ + if(ip4_addr1(&requested_ip) != ip4_addr1(&state->first_client_addr) + || ip4_addr2(&requested_ip) != ip4_addr2(&state->first_client_addr) + || ip4_addr3(&requested_ip) != ip4_addr3(&state->first_client_addr)) { + sprintf_ipaddr(&requested_ip, ipbuf); + printf("DHCP Server Error: %s not an allowed IP\r\n", ipbuf); + send_dhcp_nak(dhcpmsg); + return; + } + /* Test the last octet is in the MAXCLIENTS range */ + int16_t octet_offs = ip4_addr4(&requested_ip) - ip4_addr4(&state->first_client_addr); + if(octet_offs < 0 || octet_offs >= state->max_leases) { + printf("DHCP Server Error: Address out of range\r\n"); + send_dhcp_nak(dhcpmsg); + return; + } + + dhcp_lease_t *requested_lease = state->leases + octet_offs; + if(requested_lease->expires != 0 && memcmp(requested_lease->hwaddr, dhcpmsg->chaddr,dhcpmsg->hlen)) + { + printf("DHCP Server Error: Lease for address already taken\r\n"); + send_dhcp_nak(dhcpmsg); + return; + } + + memcpy(requested_lease->hwaddr, dhcpmsg->chaddr, dhcpmsg->hlen); + sprintf_ipaddr(&requested_ip, ipbuf); + printf("DHCP lease addr %s assigned to MAC %02x:%02x:%02x:%02x:%02x:%02x\r\n", ipbuf, requested_lease->hwaddr[0], + requested_lease->hwaddr[1], requested_lease->hwaddr[2], requested_lease->hwaddr[3], requested_lease->hwaddr[4], + requested_lease->hwaddr[5]); + requested_lease->expires = DHCPSERVER_LEASE_TIME * configTICK_RATE_HZ; + + /* Reuse the REQUEST message as the ACK message */ + dhcpmsg->op = DHCP_BOOTREPLY; + bzero(dhcpmsg->options, DHCP_OPTIONS_LEN); + + ip_addr_copy(dhcpmsg->yiaddr, requested_ip); + + uint8_t *opt = (uint8_t *)&dhcpmsg->options; + opt = add_dhcp_option_byte(opt, DHCP_OPTION_MESSAGE_TYPE, DHCP_ACK); + uint32_t expiry = htonl(DHCPSERVER_LEASE_TIME); + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_LEASE_TIME, &expiry, 4); + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SERVER_ID, &state->server_if->ip_addr, 4); + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SUBNET_MASK, &state->server_if->netmask, 4); + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_END, NULL, 0); + + struct netbuf *netbuf = netbuf_new(); + netbuf_alloc(netbuf, sizeof(struct dhcp_msg)); + netbuf_take(netbuf, dhcpmsg, sizeof(struct dhcp_msg)); + netconn_sendto(state->nc, netbuf, IP_ADDR_BROADCAST, 68); + netbuf_delete(netbuf); +} + +static void handle_dhcp_release(struct dhcp_msg *dhcpmsg) +{ + dhcp_lease_t *lease = find_lease_slot(dhcpmsg->chaddr); + if(lease) { + lease->expires = 0; + } +} + +static void send_dhcp_nak(struct dhcp_msg *dhcpmsg) +{ + /* Reuse 'dhcpmsg' for the NAK */ + dhcpmsg->op = DHCP_BOOTREPLY; + bzero(dhcpmsg->options, DHCP_OPTIONS_LEN); + + uint8_t *opt = (uint8_t *)&dhcpmsg->options; + opt = add_dhcp_option_byte(opt, DHCP_OPTION_MESSAGE_TYPE, DHCP_NAK); + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SERVER_ID, &state->server_if->ip_addr, 4); + opt = add_dhcp_option_bytes(opt, DHCP_OPTION_END, NULL, 0); + + struct netbuf *netbuf = netbuf_new(); + netbuf_alloc(netbuf, sizeof(struct dhcp_msg)); + netbuf_take(netbuf, dhcpmsg, sizeof(struct dhcp_msg)); + netconn_sendto(state->nc, netbuf, IP_ADDR_BROADCAST, 68); + netbuf_delete(netbuf); +} + +static uint8_t *find_dhcp_option(struct dhcp_msg *msg, uint8_t option_num, uint8_t min_length, uint8_t *length) +{ + uint8_t *start = (uint8_t *)&msg->options; + uint8_t *msg_end = (uint8_t *)msg + sizeof(struct dhcp_msg); + + for(uint8_t *p = start; p < msg_end-2;) { + uint8_t type = *p++; + uint8_t len = *p++; + if(type == DHCP_OPTION_END) + return NULL; + if(p+len >= msg_end) + break; /* We've overrun our valid DHCP message size, or this isn't a valid option */ + if(type == option_num) { + if(len < min_length) + break; + if(length) + *length = len; + return p; /* start of actual option data */ + } + p += len; + } + return NULL; /* Not found */ +} + +static uint8_t *add_dhcp_option_byte(uint8_t *opt, uint8_t type, uint8_t value) +{ + *opt++ = type; + *opt++ = 1; + *opt++ = value; + return opt; +} + +static uint8_t *add_dhcp_option_bytes(uint8_t *opt, uint8_t type, void *value, uint8_t len) +{ + *opt++ = type; + if(len) { + *opt++ = len; + memcpy(opt, value, len); + } + return opt+len; +} + +/* Find a free DHCP lease, or a lease already assigned to 'hwaddr' */ +static dhcp_lease_t *find_lease_slot(uint8_t *hwaddr) +{ + dhcp_lease_t *empty_lease = NULL; + for(int i = 0; i < state->max_leases; i++) { + if(state->leases[i].expires == 0 && !empty_lease) + empty_lease = &state->leases[i]; + else if (memcmp(hwaddr, state->leases[i].hwaddr, 6) == 0) + return &state->leases[i]; + } + return empty_lease; +} diff --git a/extras/dhcpserver/include/dhcpserver.h b/extras/dhcpserver/include/dhcpserver.h new file mode 100644 index 0000000..5548c19 --- /dev/null +++ b/extras/dhcpserver/include/dhcpserver.h @@ -0,0 +1,33 @@ +/* Very basic LWIP & FreeRTOS-based DHCP server + * + * Header file contains default configuration for the DHCP server. + * + * + * Part of esp-open-rtos + * Copyright (C) 2015 Superhouse Automation Pty Ltd + * BSD Licensed as described in the file LICENSE + */ +#ifndef _DHCPSERVER_H +#define _DHCPSERVER_H + +#ifndef DHCPSERVER_LEASE_TIME +#define DHCPSERVER_LEASE_TIME 3600 +#endif + +/* Start DHCP server. + + Static IP of server should already be set and network interface enabled. + + first_client_addr is the IP address of the first lease to be handed + to a client. Subsequent lease addresses are calculated by + incrementing the final octet of the IPv4 address, up to max_leases. +*/ +void dhcpserver_start(const ip_addr_t *first_client_addr, uint8_t max_leases); + +void dhcpserver_get_lease(const ip_addr_t *first_client_addr, uint8_t max_leases); + +/* Stop DHCP server. + */ +void dhcpserver_stop(void); + +#endif diff --git a/libc/xtensa-lx106-elf/include/config.h b/libc/xtensa-lx106-elf/include/config.h new file mode 100644 index 0000000..6159c6f --- /dev/null +++ b/libc/xtensa-lx106-elf/include/config.h @@ -0,0 +1,43 @@ +#ifndef __SYS_CONFIG_H__ +#define __SYS_CONFIG_H__ + +#include /* floating point macros */ +#include /* POSIX defs */ + +/************************************************************************* + esp8266-specific xtensa stuff + *************************************************************************/ + +#ifndef _REENT_SMALL +#define _REENT_SMALL +#endif + +/* esp8266 hardware FIFO buffers are 128 bytes */ +#define __BUFSIZ__ 128 + +/************************************************************************* + end of esp8266-specific stuff + *************************************************************************/ + +#ifndef __EXPORT +#define __EXPORT +#endif + +#ifndef __IMPORT +#define __IMPORT +#endif + +/* Define return type of read/write routines. In POSIX, the return type + for read()/write() is "ssize_t" but legacy newlib code has been using + "int" for some time. If not specified, "int" is defaulted. */ +#ifndef _READ_WRITE_RETURN_TYPE +#define _READ_WRITE_RETURN_TYPE int +#endif +/* Define `count' parameter of read/write routines. In POSIX, the `count' + parameter is "size_t" but legacy newlib code has been using "int" for some + time. If not specified, "int" is defaulted. */ +#ifndef _READ_WRITE_BUFSIZE_TYPE +#define _READ_WRITE_BUFSIZE_TYPE int +#endif + +#endif /* __SYS_CONFIG_H__ */ diff --git a/libc/xtensa-lx106-elf/lib/libc.a b/libc/xtensa-lx106-elf/lib/libc.a index a166b9e..6a256cd 100644 Binary files a/libc/xtensa-lx106-elf/lib/libc.a and b/libc/xtensa-lx106-elf/lib/libc.a differ diff --git a/libc/xtensa-lx106-elf/lib/libg.a b/libc/xtensa-lx106-elf/lib/libg.a index a166b9e..6a256cd 100644 Binary files a/libc/xtensa-lx106-elf/lib/libg.a and b/libc/xtensa-lx106-elf/lib/libg.a differ diff --git a/libc/xtensa-lx106-elf/lib/libm.a b/libc/xtensa-lx106-elf/lib/libm.a index be41436..7d96963 100644 Binary files a/libc/xtensa-lx106-elf/lib/libm.a and b/libc/xtensa-lx106-elf/lib/libm.a differ