diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9261d60d5..fb1a74616 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -62,7 +62,7 @@ repos: hooks: - id: commit message scopes name: "commit message must be scoped with: mdns, modem, websocket, asio, mqtt_cxx, console, common" - entry: '\A(?!(feat|fix|ci|bump|test|docs)\((mdns|modem|common|console|websocket|asio|mqtt_cxx|examples)\)\:)' + entry: '\A(?!(feat|fix|ci|bump|test|docs)\((mdns|modem|common|console|websocket|asio|mqtt_cxx|examples|eppp)\)\:)' language: pygrep args: [--multiline] stages: [commit-msg] diff --git a/components/eppp_link/.cz.yaml b/components/eppp_link/.cz.yaml new file mode 100644 index 000000000..abdb70718 --- /dev/null +++ b/components/eppp_link/.cz.yaml @@ -0,0 +1,8 @@ +--- +commitizen: + bump_message: 'bump(eppp_link): $current_version -> $new_version' + pre_bump_hooks: python ../../ci/changelog.py eppp_link + tag_format: epp_link-v$version + version: 0.0.1 + version_files: + - idf_component.yml diff --git a/components/eppp_link/CMakeLists.txt b/components/eppp_link/CMakeLists.txt new file mode 100644 index 000000000..bd6123139 --- /dev/null +++ b/components/eppp_link/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "eppp_link.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES esp_netif esp_driver_spi esp_driver_gpio esp_timer) diff --git a/components/eppp_link/Kconfig b/components/eppp_link/Kconfig new file mode 100644 index 000000000..44c9e8836 --- /dev/null +++ b/components/eppp_link/Kconfig @@ -0,0 +1,50 @@ +menu "eppp_link" + + choice EPPP_LINK_DEVICE + prompt "Choose PPP device" + default EPPP_LINK_DEVICE_UART + help + Select which peripheral to use for PPP link + + config EPPP_LINK_DEVICE_UART + bool "UART" + help + Use UART. + + config EPPP_LINK_DEVICE_SPI + bool "SPI" + help + Use SPI. + endchoice + + config EPPP_LINK_CONN_MAX_RETRY + int "Maximum retry" + default 6 + help + Set the Maximum retry to infinitely avoid reconnecting + This is used only with the simplified API (eppp_connect() + and eppp_listen()) + + config EPPP_LINK_PACKET_QUEUE_SIZE + int "Packet queue size" + default 64 + help + Size of the Tx packet queue. + You can decrease the number for slower bit rates. + + config EPPP_LINK_SERVER_IP + hex "Server IP address" + range 0 0xFFFFFFFF + default 0xc0a80b01 + help + Preferred IP address of the server side. + + config EPPP_LINK_CLIENT_IP + hex "Client IP address" + range 0 0xFFFFFFFF + default 0xc0a80b02 + help + Preferred IP address of the client side. + + +endmenu diff --git a/components/eppp_link/LICENSE b/components/eppp_link/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/components/eppp_link/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/eppp_link/README.md b/components/eppp_link/README.md new file mode 100644 index 000000000..c47253140 --- /dev/null +++ b/components/eppp_link/README.md @@ -0,0 +1,43 @@ +# ESP PPP Link component (eppp_link) + +The component provides a general purpose connectivity engine between two micro-controllers, one acting as PPP server (slave), the other one as PPP client (host). Typical application is a WiFi connectivity provider for chips that do not have WiFi: + +``` + SLAVE micro HOST micro + \|/ +----------------+ +----------------+ + | | | serial line | | + +---+ WiFi NAT PPPoS |======== UART / SPI =======| PPPoS client | + | (server)| | | + +----------------+ +----------------+ +``` + +## API + +### Client + +* `eppp_connect()` -- Simplified API. Provides the initialization, starts the task and blocks until we're connected + +* `eppp_client_init()` -- Initialization of the client. Need to run only once. +* `eppp_client_start()` -- Starts the connection, could be called after startup or whenever a connection is lost +* `eppp_client_perform()` -- Perform one iteration of the PPP task (need to be called regularly in task-less configuration) + +### Server + +* `eppp_listen()` -- Simplified API. Provides the initialization, starts the task and blocks until the client connects +* `eppp_server_init()` -- Initialization of the server. Need to run only once. +* `eppp_server_start()` -- (Re)starts the connection, should be called after startup or whenever a connection is lost +* `eppp_server_perform()` -- Perform one iteration of the PPP task (need to be called regularly in task-less configuration) + +## Throughput + +Tested with WiFi-NAPT example, no IRAM optimizations + +### UART @ 3Mbauds + +* TCP - 2Mbits +* UDP - 2Mbits + +### SPI @ 40MHz + +* TCP - 6Mbits +* UDP - 10Mbits diff --git a/components/eppp_link/eppp_link.c b/components/eppp_link/eppp_link.c new file mode 100644 index 000000000..b7253e478 --- /dev/null +++ b/components/eppp_link/eppp_link.c @@ -0,0 +1,527 @@ +/* + * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "esp_netif_ppp.h" +#include "eppp_link_types.h" + +#if CONFIG_EPPP_LINK_DEVICE_SPI +#include "driver/spi_master.h" +#include "driver/spi_slave.h" +#include "driver/gpio.h" +#include "esp_timer.h" +#endif + +static const int GOT_IPV4 = BIT0; +static const int CONNECTION_FAILED = BIT1; +#define CONNECT_BITS (GOT_IPV4|CONNECTION_FAILED) + +static EventGroupHandle_t s_event_group = NULL; +static const char *TAG = "eppp_link"; +static int s_retry_num = 0; + +#if CONFIG_EPPP_LINK_DEVICE_SPI +static spi_device_handle_t s_spi_device; +#define SPI_HOST SPI2_HOST +#define GPIO_MOSI 11 +#define GPIO_MISO 13 +#define GPIO_SCLK 12 +#define GPIO_CS 10 +#define GPIO_INTR 2 +#endif // CONFIG_EPPP_LINK_DEVICE_SPI + +enum eppp_type { + EPPP_SERVER, + EPPP_CLIENT, +}; + +struct eppp_handle { + QueueHandle_t out_queue; + QueueHandle_t ready_semaphore; + esp_netif_t *netif; + enum eppp_type role; +}; + +struct packet { + size_t len; + uint8_t *data; +}; + + +static esp_err_t transmit(void *h, void *buffer, size_t len) +{ +#if CONFIG_EXAMPLE_CONNECT_PPP_DEVICE_SPI +#define MAX_PAYLOAD 1600 + struct eppp_handle *handle = h; + struct packet buf = { }; + + uint8_t *current_buffer = buffer; + size_t remaining = len; + do { + size_t batch = remaining > MAX_PAYLOAD ? MAX_PAYLOAD : remaining; + buf.data = malloc(batch); + buf.len = batch; + remaining -= batch; + memcpy(buf.data, current_buffer, batch); + current_buffer += batch; + BaseType_t ret = xQueueSend(handle->out_queue, &buf, pdMS_TO_TICKS(10)); + if (ret != pdTRUE) { + ESP_LOGE(TAG, "Failed to queue packet to slave!"); + } + } while (remaining > 0); +#endif + return ESP_OK; +} + +static esp_netif_t *netif_init(enum eppp_type role) +{ + static int s_eppp_netif_count = 0; // used as a suffix for the netif key + if (s_eppp_netif_count > 9) { + ESP_LOGE(TAG, "Cannot create more than 10 instances"); + return NULL; + } + + // Create the object first + struct eppp_handle *h = calloc(1, sizeof(struct eppp_handle)); + if (!h) { + ESP_LOGE(TAG, "Failed to allocate eppp_handle"); + return NULL; + } + h->out_queue = xQueueCreate(CONFIG_EPPP_LINK_PACKET_QUEUE_SIZE, sizeof(struct packet)); + if (!h->out_queue) { + ESP_LOGE(TAG, "Failed to create the packet queue"); + free(h); + return NULL; + } + h->role = role; + if (role == EPPP_CLIENT) { + h->ready_semaphore = xSemaphoreCreateBinary(); + if (!h->ready_semaphore) { + ESP_LOGE(TAG, "Failed to create the packet queue"); + vQueueDelete(h->out_queue); + free(h); + return NULL; + } + } + + esp_netif_driver_ifconfig_t driver_cfg = { + .handle = h, + .transmit = transmit, + }; + const esp_netif_driver_ifconfig_t *ppp_driver_cfg = &driver_cfg; + + esp_netif_inherent_config_t base_netif_cfg = ESP_NETIF_INHERENT_DEFAULT_PPP(); + char if_key[] = "EPPP0"; // netif key needs to be unique + if_key[sizeof(if_key) - 1] += s_eppp_netif_count++; + base_netif_cfg.if_key = if_key; + if (role == EPPP_CLIENT) { + base_netif_cfg.if_desc = "pppos_client"; + } else { + base_netif_cfg.if_desc = "pppos_server"; + } + esp_netif_config_t netif_ppp_config = { .base = &base_netif_cfg, + .driver = ppp_driver_cfg, + .stack = ESP_NETIF_NETSTACK_DEFAULT_PPP + }; + + esp_netif_t *netif = esp_netif_new(&netif_ppp_config); + if (!netif) { + ESP_LOGE(TAG, "Failed to create esp_netif"); + vQueueDelete(h->out_queue); + free(h); + return NULL; + } + return netif; + +} + +static esp_err_t netif_start(esp_netif_t *netif) +{ + esp_netif_action_start(netif, 0, 0, 0); + esp_netif_action_connected(netif, 0, 0, 0); + return ESP_OK; +} + +static void on_ip_event(void *arg, esp_event_base_t base, int32_t event_id, void *data) +{ + ip_event_got_ip_t *event = (ip_event_got_ip_t *)data; + esp_netif_t *netif = event->esp_netif; + if (event_id == IP_EVENT_PPP_GOT_IP) { + ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip)); + xEventGroupSetBits(s_event_group, GOT_IPV4); + } else if (event_id == IP_EVENT_PPP_LOST_IP) { + ESP_LOGI(TAG, "Disconnect from PPP Server"); + s_retry_num++; + if (s_retry_num > CONFIG_EPPP_LINK_CONN_MAX_RETRY) { + ESP_LOGE(TAG, "PPP Connection failed %d times, stop reconnecting.", s_retry_num); + xEventGroupSetBits(s_event_group, CONNECTION_FAILED); + } else { + ESP_LOGI(TAG, "PPP Connection failed %d times, try to reconnect.", s_retry_num); + netif_start(netif); + } + } +} + +#if CONFIG_EPPP_LINK_DEVICE_SPI + +#define TRANSFER_SIZE (MAX_PAYLOAD + 4) +#define SHORT_PAYLOAD (48) +#define CONTROL_SIZE (SHORT_PAYLOAD + 4) + +#define CONTROL_MASTER 0xA5 +#define CONTROL_MASTER_WITH_DATA 0xA6 +#define CONTROL_SLAVE 0x5A +#define CONTROL_SLAVE_WITH_DATA 0x5B +#define DATA_MASTER 0xAF +#define DATA_SLAVE 0xFA + +#define MAX(a,b) (((a)>(b))?(a):(b)) + +struct header { + union { + uint16_t size; + struct { + uint8_t short_size; + uint8_t long_size; + } __attribute__((packed)); + }; + uint8_t magic; + uint8_t checksum; +} __attribute__((packed)); + +static void IRAM_ATTR gpio_isr_handler(void *arg) +{ + static uint32_t s_last_time; + uint32_t now = esp_timer_get_time(); + uint32_t diff = now - s_last_time; + if (diff < 5) { // debounce + return; + } + s_last_time = now; + + BaseType_t yield = false; + struct eppp_handle *h = arg; + xSemaphoreGiveFromISR(h->ready_semaphore, &yield); + if (yield) { + portYIELD_FROM_ISR(); + } +} + +static esp_err_t init_master(spi_device_handle_t *spi, esp_netif_t *netif) +{ + spi_bus_config_t bus_cfg = {}; + bus_cfg.mosi_io_num = GPIO_MOSI; + bus_cfg.miso_io_num = GPIO_MISO; + bus_cfg.sclk_io_num = GPIO_SCLK; + bus_cfg.quadwp_io_num = -1; + bus_cfg.quadhd_io_num = -1; + bus_cfg.max_transfer_sz = 14000; + bus_cfg.flags = 0; + bus_cfg.intr_flags = 0; + + if (spi_bus_initialize(SPI_HOST, &bus_cfg, SPI_DMA_CH_AUTO) != ESP_OK) { + return ESP_FAIL; + } + + spi_device_interface_config_t dev_cfg = {}; + dev_cfg.clock_speed_hz = 40 * 1000 * 1000; + dev_cfg.mode = 0; + dev_cfg.spics_io_num = GPIO_CS; + dev_cfg.cs_ena_pretrans = 0; + dev_cfg.cs_ena_posttrans = 0; + dev_cfg.duty_cycle_pos = 128; + dev_cfg.input_delay_ns = 0; + dev_cfg.pre_cb = NULL; + dev_cfg.post_cb = NULL; + dev_cfg.cs_ena_posttrans = 3; + dev_cfg.queue_size = 3; + + if (spi_bus_add_device(SPI_HOST, &dev_cfg, spi) != ESP_OK) { + return ESP_FAIL; + } + + //GPIO config for the handshake line. + gpio_config_t io_conf = { + .intr_type = GPIO_INTR_POSEDGE, + .mode = GPIO_MODE_INPUT, + .pull_up_en = 1, + .pin_bit_mask = BIT64(GPIO_INTR), + }; + + gpio_config(&io_conf); + gpio_install_isr_service(0); + gpio_set_intr_type(GPIO_INTR, GPIO_INTR_POSEDGE); + gpio_isr_handler_add(GPIO_INTR, gpio_isr_handler, esp_netif_get_io_driver(netif)); + return ESP_OK; +} + +static void post_setup(spi_slave_transaction_t *trans) +{ + gpio_set_level(GPIO_INTR, 1); +} + +static void post_trans(spi_slave_transaction_t *trans) +{ + gpio_set_level(GPIO_INTR, 0); +} + +static esp_err_t init_slave(spi_device_handle_t *spi, esp_netif_t *netif) +{ + spi_bus_config_t bus_cfg = {}; + bus_cfg.mosi_io_num = GPIO_MOSI; + bus_cfg.miso_io_num = GPIO_MISO; + bus_cfg.sclk_io_num = GPIO_SCLK; + bus_cfg.quadwp_io_num = -1; + bus_cfg.quadhd_io_num = -1; + bus_cfg.flags = 0; + bus_cfg.intr_flags = 0; + + //Configuration for the SPI slave interface + spi_slave_interface_config_t slvcfg = { + .mode = 0, + .spics_io_num = GPIO_CS, + .queue_size = 3, + .flags = 0, + .post_setup_cb = post_setup, + .post_trans_cb = post_trans + }; + + //Configuration for the handshake line + gpio_config_t io_conf = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = BIT64(GPIO_INTR), + }; + + gpio_config(&io_conf); + gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY); + + //Initialize SPI slave interface + if (spi_slave_initialize(SPI_HOST, &bus_cfg, &slvcfg, SPI_DMA_CH_AUTO) != ESP_OK) { + return ESP_FAIL; + } + return ESP_OK; +} + +union transaction { + spi_transaction_t master; + spi_slave_transaction_t slave; +}; + +typedef void (*set_transaction_t)(union transaction *t, size_t len, const void *tx_buffer, void *rx_buffer); +typedef esp_err_t (*perform_transaction_t)(union transaction *t, struct eppp_handle *h); + +static void set_transaction_master(union transaction *t, size_t len, const void *tx_buffer, void *rx_buffer) +{ + t->master.length = len * 8; + t->master.tx_buffer = tx_buffer; + t->master.rx_buffer = rx_buffer; +} + +static void set_transaction_slave(union transaction *t, size_t len, const void *tx_buffer, void *rx_buffer) +{ + t->slave.length = len * 8; + t->slave.tx_buffer = tx_buffer; + t->slave.rx_buffer = rx_buffer; +} + +static esp_err_t perform_transaction_master(union transaction *t, struct eppp_handle *h) +{ + xSemaphoreTake(h->ready_semaphore, portMAX_DELAY); // Wait until slave is ready + return spi_device_transmit(s_spi_device, &t->master); +} + +static esp_err_t perform_transaction_slave(union transaction *t, struct eppp_handle *h) +{ + return spi_slave_transmit(SPI_HOST, &t->slave, portMAX_DELAY); +} + +_Noreturn static void ppp_task(void *args) +{ + static WORD_ALIGNED_ATTR uint8_t out_buf[TRANSFER_SIZE] = {}; + static WORD_ALIGNED_ATTR uint8_t in_buf[TRANSFER_SIZE] = {}; + + esp_netif_t *netif = args; + struct eppp_handle *h = esp_netif_get_io_driver(netif); + union transaction t; + + const uint8_t FRAME_OUT_CTRL = h->role == EPPP_CLIENT ? CONTROL_MASTER : CONTROL_SLAVE; + const uint8_t FRAME_OUT_CTRL_EX = h->role == EPPP_CLIENT ? CONTROL_MASTER_WITH_DATA : CONTROL_SLAVE_WITH_DATA; + const uint8_t FRAME_OUT_DATA = h->role == EPPP_CLIENT ? DATA_MASTER : DATA_SLAVE; + const uint8_t FRAME_IN_CTRL = h->role == EPPP_SERVER ? CONTROL_MASTER : CONTROL_SLAVE; + const uint8_t FRAME_IN_CTRL_EX = h->role == EPPP_SERVER ? CONTROL_MASTER_WITH_DATA : CONTROL_SLAVE_WITH_DATA; + const uint8_t FRAME_IN_DATA = h->role == EPPP_SERVER ? DATA_MASTER : DATA_SLAVE; + const set_transaction_t set_transaction = h->role == EPPP_CLIENT ? set_transaction_master : set_transaction_slave; + const perform_transaction_t perform_transaction = h->role == EPPP_CLIENT ? perform_transaction_master : perform_transaction_slave; + + if (h->role == EPPP_CLIENT) { + // as a client, try to actively connect (not waiting for server's interrupt) + xSemaphoreGive(h->ready_semaphore); + } + while (1) { + struct packet buf = { .len = 0 }; + struct header *head = (void *)out_buf; + bool need_data_frame = false; + size_t out_long_payload = 0; + head->magic = FRAME_OUT_CTRL_EX; + head->size = 0; + head->checksum = 0; + BaseType_t tx_queue_stat = xQueueReceive(h->out_queue, &buf, 0); + if (tx_queue_stat == pdTRUE && buf.data) { + if (buf.len > SHORT_PAYLOAD) { + head->magic = FRAME_OUT_CTRL; + head->size = buf.len; + out_long_payload = buf.len; + need_data_frame = true; +// printf("need_data_frame %d\n", buf.len); + } else { + head->magic = FRAME_OUT_CTRL_EX; + head->long_size = 0; + head->short_size = buf.len; + memcpy(out_buf + sizeof(struct header), buf.data, buf.len); + free(buf.data); + } + } + memset(&t, 0, sizeof(t)); + set_transaction(&t, CONTROL_SIZE, out_buf, in_buf); + for (int i = 0; i < sizeof(struct header) - 1; ++i) { + head->checksum += out_buf[i]; + } + esp_err_t ret = perform_transaction(&t, h); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "spi_device_transmit failed"); + continue; + } + head = (void *)in_buf; + uint8_t checksum = 0; + for (int i = 0; i < sizeof(struct header) - 1; ++i) { + checksum += in_buf[i]; + } + if (checksum != head->checksum) { + ESP_LOGE(TAG, "Wrong checksum"); + continue; + } +// printf("MAGIC: %x\n", head->magic); + if (head->magic != FRAME_IN_CTRL && head->magic != FRAME_IN_CTRL_EX) { + ESP_LOGE(TAG, "Wrong magic"); + continue; + } + if (head->magic == FRAME_IN_CTRL_EX && head->short_size > 0) { + esp_netif_receive(netif, in_buf + sizeof(struct header), head->short_size, NULL); + } + size_t in_long_payload = 0; + if (head->magic == FRAME_IN_CTRL) { + need_data_frame = true; + in_long_payload = head->size; + } + if (!need_data_frame) { + continue; + } + // now, we need data frame +// printf("performing data frame %d %d\n", out_long_payload, buf.len); + head = (void *)out_buf; + head->magic = FRAME_OUT_DATA; + head->size = out_long_payload; + head->checksum = 0; + for (int i = 0; i < sizeof(struct header) - 1; ++i) { + head->checksum += out_buf[i]; + } + if (head->size > 0) { + memcpy(out_buf + sizeof(struct header), buf.data, buf.len); +// ESP_LOG_BUFFER_HEXDUMP(TAG, out_buf + sizeof(struct header), head->size, ESP_LOG_INFO); + free(buf.data); + } + + memset(&t, 0, sizeof(t)); + set_transaction(&t, MAX(in_long_payload, out_long_payload) + sizeof(struct header), out_buf, in_buf); + + ret = perform_transaction(&t, h); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "spi_device_transmit failed"); + continue; + } + head = (void *)in_buf; + checksum = 0; + for (int i = 0; i < sizeof(struct header) - 1; ++i) { + checksum += in_buf[i]; + } + if (checksum != head->checksum) { + ESP_LOGE(TAG, "Wrong checksum"); + continue; + } + if (head->magic != FRAME_IN_DATA) { + ESP_LOGE(TAG, "Wrong magic"); + continue; + } +// printf("got size %d\n", head->size); + + if (head->size > 0) { +// ESP_LOG_BUFFER_HEXDUMP(TAG, in_buf + sizeof(struct header), head->size, ESP_LOG_INFO); + esp_netif_receive(netif, in_buf + sizeof(struct header), head->size, NULL); + } + } +} +#endif // CONFIG_EPPP_LINK_DEVICE_SPI + + +static esp_netif_t *default_setup(enum eppp_type role) +{ + s_event_group = xEventGroupCreate(); + if (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event, NULL) != ESP_OK) { + ESP_LOGE(TAG, "Failed to register IP event handler"); + return NULL; + } + esp_netif_t *netif = netif_init(role); + if (!netif) { + ESP_LOGE(TAG, "Failed to initialize PPP netif"); + vEventGroupDelete(s_event_group); + return NULL; + } + esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected, netif); + esp_netif_ppp_config_t netif_params; + ESP_ERROR_CHECK(esp_netif_ppp_get_params(netif, &netif_params)); + netif_params.ppp_our_ip4_addr = esp_netif_htonl(role == EPPP_SERVER ? CONFIG_EPPP_LINK_SERVER_IP : CONFIG_EPPP_LINK_CLIENT_IP); + netif_params.ppp_their_ip4_addr = esp_netif_htonl(role == EPPP_SERVER ? CONFIG_EPPP_LINK_CLIENT_IP : CONFIG_EPPP_LINK_SERVER_IP); + ESP_ERROR_CHECK(esp_netif_ppp_set_params(netif, &netif_params)); +#if CONFIG_EPPP_LINK_DEVICE_SPI + if (role == EPPP_CLIENT) { + init_master(&s_spi_device, netif); + } else { + init_slave(&s_spi_device, netif); + } +#endif + + netif_start(netif); + + if (xTaskCreate(ppp_task, "ppp connect", 4096, netif, 18, NULL) != pdTRUE) { + ESP_LOGE(TAG, "Failed to create a ppp connection task"); + return NULL; + } + ESP_LOGI(TAG, "Waiting for IP address"); + EventBits_t bits = xEventGroupWaitBits(s_event_group, CONNECT_BITS, pdFALSE, pdFALSE, portMAX_DELAY); + if (bits & CONNECTION_FAILED) { + ESP_LOGE(TAG, "Connection failed!"); + return NULL; + } + ESP_LOGI(TAG, "Connected!"); + return netif; +} + +esp_netif_t *eppp_connect(void) +{ + return default_setup(EPPP_CLIENT); +} + +esp_netif_t *eppp_listen(void) +{ + return default_setup(EPPP_SERVER); +} diff --git a/components/eppp_link/eppp_link_types.h b/components/eppp_link/eppp_link_types.h new file mode 100644 index 000000000..e69de29bb diff --git a/components/eppp_link/idf_component.yml b/components/eppp_link/idf_component.yml new file mode 100644 index 000000000..fbab49cb8 --- /dev/null +++ b/components/eppp_link/idf_component.yml @@ -0,0 +1,6 @@ +version: 0.0.9 +url: https://github.com/espressif/esp-protocols/tree/master/components/eppp_link +description: The component provides a general purpose PPP connectivity, typically used as WiFi-PPP router +dependencies: + idf: + version: '>=5.2' diff --git a/components/eppp_link/include/eppp_link.h b/components/eppp_link/include/eppp_link.h new file mode 100644 index 000000000..4d8e820bf --- /dev/null +++ b/components/eppp_link/include/eppp_link.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +esp_netif_t *eppp_connect(void); + +esp_netif_t *eppp_listen(void);