From 31dac92e5f0daac98190fd603df213a0a25a3807 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 1 Oct 2019 18:50:34 +0200 Subject: [PATCH] ethernet: support OpenCores ethernet MAC OpenCores Ethernet MAC has a relatively simple interface, and is already supported in QEMU. This makes it a good candidate for enabling network support when running IDF apps in QEMU, compared to the relatively more complex task of writing a QEMU model of ESP32 EMAC. This driver is written with QEMU in mind: it does not implement or handle things that aren't implemented or handled in the QEMU model: error flags, error interrupts. The transmit part of the driver also assumes that the TX operation is done immediately when the TX descriptor is written (which is the case with QEMU), hence waiting for the TX operation to complete is not necessary. For simplicity, the driver assumes that the peripheral register occupy the same memory range as the ESP32 EMAC registers, and the same interrupt source number is used. --- components/esp_eth/CMakeLists.txt | 4 + components/esp_eth/Kconfig | 24 + components/esp_eth/component.mk | 4 + components/esp_eth/include/esp_eth_mac.h | 6 + components/esp_eth/src/esp_eth_mac_openeth.c | 410 ++++++++++++++++++ components/esp_eth/src/openeth.h | 216 +++++++++ .../Kconfig.projbuild | 11 + .../protocol_examples_common/connect.c | 5 + 8 files changed, 680 insertions(+) create mode 100644 components/esp_eth/src/esp_eth_mac_openeth.c create mode 100644 components/esp_eth/src/openeth.h diff --git a/components/esp_eth/CMakeLists.txt b/components/esp_eth/CMakeLists.txt index 3829df188..8547a49e3 100644 --- a/components/esp_eth/CMakeLists.txt +++ b/components/esp_eth/CMakeLists.txt @@ -13,6 +13,10 @@ if(CONFIG_ETH_SPI_ETHERNET_DM9051) "src/esp_eth_phy_dm9051.c") endif() +if(CONFIG_ETH_USE_OPENETH) + list(APPEND esp_eth_srcs "src/esp_eth_mac_openeth.c") +endif() + idf_component_register(SRCS "${esp_eth_srcs}" INCLUDE_DIRS "include" LDFRAGMENTS "linker.lf" diff --git a/components/esp_eth/Kconfig b/components/esp_eth/Kconfig index 8e894d397..63ee86936 100644 --- a/components/esp_eth/Kconfig +++ b/components/esp_eth/Kconfig @@ -153,4 +153,28 @@ menu "Ethernet" Set the GPIO number used by DM9051's Interrupt pin. endif endif + + menuconfig ETH_USE_OPENETH + bool "Support OpenCores Ethernet MAC (for use with QEMU)" + default n + help + OpenCores Ethernet MAC driver can be used when an ESP-IDF application + is executed in QEMU. This driver is not supported when running on a + real chip. + + if ETH_USE_OPENETH + config ETH_OPENETH_DMA_RX_BUFFER_NUM + int "Number of Ethernet DMA Rx buffers" + range 1 64 + default 4 + help + Number of DMA receive buffers, each buffer is 1600 bytes. + + config ETH_OPENETH_DMA_TX_BUFFER_NUM + int "Number of Ethernet DMA Tx buffers" + range 1 64 + default 1 + help + Number of DMA transmit buffers, each buffer is 1600 bytes. + endif endmenu diff --git a/components/esp_eth/component.mk b/components/esp_eth/component.mk index 3a768fdf8..252aafccf 100644 --- a/components/esp_eth/component.mk +++ b/components/esp_eth/component.mk @@ -12,3 +12,7 @@ endif ifndef CONFIG_ETH_SPI_ETHERNET_DM9051 COMPONENT_OBJEXCLUDE += src/esp_eth_mac_dm9051.o src/esp_eth_phy_dm9051.o endif + +ifndef CONFIG_ETH_USE_OPENETH + COMPONENT_OBJEXCLUDE += src/esp_eth_mac_openeth.o +endif diff --git a/components/esp_eth/include/esp_eth_mac.h b/components/esp_eth/include/esp_eth_mac.h index efcbe98d0..8ef030d8a 100644 --- a/components/esp_eth/include/esp_eth_mac.h +++ b/components/esp_eth/include/esp_eth_mac.h @@ -307,6 +307,12 @@ typedef struct { */ esp_eth_mac_t *esp_eth_mac_new_dm9051(const eth_dm9051_config_t *dm9051_config, const eth_mac_config_t *mac_config); #endif + + +#if CONFIG_ETH_USE_OPENETH +esp_eth_mac_t *esp_eth_mac_new_openeth(const eth_mac_config_t *config); +#endif // CONFIG_ETH_USE_OPENETH + #ifdef __cplusplus } #endif diff --git a/components/esp_eth/src/esp_eth_mac_openeth.c b/components/esp_eth/src/esp_eth_mac_openeth.c new file mode 100644 index 000000000..e110e405b --- /dev/null +++ b/components/esp_eth/src/esp_eth_mac_openeth.c @@ -0,0 +1,410 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +// This is a driver for OpenCores Ethernet MAC (https://opencores.org/projects/ethmac). +// Espressif chips do not use this MAC, but it is supported in QEMU +// (see hw/net/opencores_eth.c). Since the interface of this MAC is a relatively +// simple one, it is used for the purpose of running IDF apps in QEMU. +// The QEMU driver also emulates the DP83848C PHY, which is supported in IDF. +// Note that this driver is written with QEMU in mind. For example, it doesn't +// handle errors which QEMU will not report, and doesn't wait for TX to be +// finished, since QEMU does this instantly. + +#include +#include +#include +#include +#include "esp_log.h" +#include "esp_eth.h" +#include "esp_intr_alloc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "openeth.h" + +static const char *TAG = "emac_opencores"; + +#define MAC_CHECK(a, str, goto_tag, ret_value, ...) \ + do \ + { \ + if (!(a)) \ + { \ + ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + ret = ret_value; \ + goto goto_tag; \ + } \ + } while (0) + +// Driver state structure +typedef struct { + esp_eth_mac_t parent; + esp_eth_mediator_t *eth; + intr_handle_t intr_hdl; + TaskHandle_t rx_task_hdl; + int cur_rx_desc; + int cur_tx_desc; + uint8_t addr[6]; + uint8_t *rx_buf[RX_BUF_COUNT]; + uint8_t *tx_buf[TX_BUF_COUNT]; +} emac_opencores_t; + + +// Interrupt handler and the receive task + +static esp_err_t emac_opencores_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length); + +static IRAM_ATTR void emac_opencores_isr_handler(void *args) +{ + emac_opencores_t *emac = (emac_opencores_t*) args; + BaseType_t high_task_wakeup; + + uint32_t status = REG_READ(OPENETH_INT_SOURCE_REG); + + if (status & OPENETH_INT_RXB) { + // Notify receive task + vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup); + if (high_task_wakeup) { + portYIELD_FROM_ISR(); + } + } + + if (status & OPENETH_INT_BUSY) { + ESP_EARLY_LOGW(TAG, "%s: RX frame dropped (0x%x)", __func__, status); + } + + // Clear interrupt + REG_WRITE(OPENETH_INT_SOURCE_REG, status); +} + +static void emac_opencores_rx_task(void *arg) +{ + emac_opencores_t *emac = (emac_opencores_t *)arg; + uint8_t *buffer = NULL; + uint32_t length = 0; + while (1) { + if (ulTaskNotifyTake(pdFALSE, portMAX_DELAY)) { + while(true) { + buffer = (uint8_t *)malloc(ETH_MAX_PACKET_SIZE); + length = ETH_MAX_PACKET_SIZE; + if (emac_opencores_receive(&emac->parent, buffer, &length) == ESP_OK) { + // pass the buffer to the upper layer + if (length) { + emac->eth->stack_input(emac->eth, buffer, length); + } else { + free(buffer); + } + } else { + free(buffer); + break; + } + } + } + } + vTaskDelete(NULL); +} + + +// Below functions implement the driver interface + +static esp_err_t emac_opencores_set_mediator(esp_eth_mac_t *mac, esp_eth_mediator_t *eth) +{ + esp_err_t ret = ESP_OK; + MAC_CHECK(eth, "can't set mac's mediator to null", err, ESP_ERR_INVALID_ARG); + emac_opencores_t *emac = __containerof(mac, emac_opencores_t, parent); + emac->eth = eth; + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_opencores_write_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, uint32_t phy_reg, uint32_t reg_value) +{ + ESP_LOGV(TAG, "%s: addr=%d reg=0x%x val=0x%04x", __func__, phy_addr, phy_reg, reg_value); + REG_SET_FIELD(OPENETH_MIIADDRESS_REG, OPENETH_FIAD, phy_addr); + REG_SET_FIELD(OPENETH_MIIADDRESS_REG, OPENETH_RGAD, phy_reg); + REG_WRITE(OPENETH_MIITX_DATA_REG, reg_value & OPENETH_MII_DATA_MASK); + REG_SET_BIT(OPENETH_MIICOMMAND_REG, OPENETH_WCTRLDATA); + return ESP_OK; +} + +static esp_err_t emac_opencores_read_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, uint32_t phy_reg, uint32_t *reg_value) +{ + esp_err_t ret = ESP_OK; + MAC_CHECK(reg_value, "can't set reg_value to null", err, ESP_ERR_INVALID_ARG); + REG_SET_FIELD(OPENETH_MIIADDRESS_REG, OPENETH_FIAD, phy_addr); + REG_SET_FIELD(OPENETH_MIIADDRESS_REG, OPENETH_RGAD, phy_reg); + REG_SET_BIT(OPENETH_MIICOMMAND_REG, OPENETH_RSTAT); + *reg_value = (REG_READ(OPENETH_MIIRX_DATA_REG) & OPENETH_MII_DATA_MASK); + ESP_LOGV(TAG, "%s: addr=%d reg=0x%x val=0x%04x", __func__, phy_addr, phy_reg, *reg_value); + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_opencores_set_addr(esp_eth_mac_t *mac, uint8_t *addr) +{ + ESP_LOGV(TAG, "%s: " MACSTR, __func__, MAC2STR(addr)); + esp_err_t ret = ESP_OK; + MAC_CHECK(addr, "can't set mac addr to null", err, ESP_ERR_INVALID_ARG); + emac_opencores_t *emac = __containerof(mac, emac_opencores_t, parent); + memcpy(emac->addr, addr, 6); + const uint8_t mac0[4] = {addr[5], addr[4], addr[3], addr[2]}; + const uint8_t mac1[4] = {addr[1], addr[0]}; + uint32_t mac0_u32, mac1_u32; + memcpy(&mac0_u32, &mac0, 4); + memcpy(&mac1_u32, &mac1, 4); + REG_WRITE(OPENETH_MAC_ADDR0_REG, mac0_u32); + REG_WRITE(OPENETH_MAC_ADDR1_REG, mac1_u32); + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_opencores_get_addr(esp_eth_mac_t *mac, uint8_t *addr) +{ + ESP_LOGV(TAG, "%s: " MACSTR, __func__, MAC2STR(addr)); + esp_err_t ret = ESP_OK; + MAC_CHECK(addr, "can't set mac addr to null", err, ESP_ERR_INVALID_ARG); + emac_opencores_t *emac = __containerof(mac, emac_opencores_t, parent); + memcpy(addr, emac->addr, 6); + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_opencores_set_link(esp_eth_mac_t *mac, eth_link_t link) +{ + ESP_LOGV(TAG, "%s: %s", __func__, link == ETH_LINK_UP ? "up" : "down"); + esp_err_t ret = ESP_OK; + emac_opencores_t *emac = __containerof(mac, emac_opencores_t, parent); + switch (link) { + case ETH_LINK_UP: + MAC_CHECK(esp_intr_enable(emac->intr_hdl) == ESP_OK, "enable interrupt failed", err, ESP_FAIL); + openeth_enable(); + break; + case ETH_LINK_DOWN: + MAC_CHECK(esp_intr_disable(emac->intr_hdl) == ESP_OK, "disable interrupt failed", err, ESP_FAIL); + openeth_disable(); + break; + default: + MAC_CHECK(false, "unknown link status", err, ESP_ERR_INVALID_ARG); + break; + } + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_opencores_set_speed(esp_eth_mac_t *mac, eth_speed_t speed) +{ + /* QEMU doesn't emulate PHY speed, so accept any value */ + return ESP_OK; +} + +static esp_err_t emac_opencores_set_duplex(esp_eth_mac_t *mac, eth_duplex_t duplex) +{ + /* QEMU doesn't emulate full/half duplex, so accept any value */ + return ESP_OK; +} + +static esp_err_t emac_opencores_set_promiscuous(esp_eth_mac_t *mac, bool enable) +{ + if (enable) { + REG_SET_BIT(OPENETH_MODER_REG, OPENETH_PRO); + } else { + REG_CLR_BIT(OPENETH_MODER_REG, OPENETH_PRO); + } + return ESP_OK; +} + +static esp_err_t emac_opencores_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t length) +{ + esp_err_t ret = ESP_OK; + emac_opencores_t *emac = __containerof(mac, emac_opencores_t, parent); + MAC_CHECK(buf, "can't set buf to null", err, ESP_ERR_INVALID_ARG); + MAC_CHECK(length, "buf length can't be zero", err, ESP_ERR_INVALID_ARG); + MAC_CHECK(length < DMA_BUF_SIZE * TX_BUF_COUNT, "insufficient TX buffer size", err, ESP_ERR_INVALID_SIZE); + + uint32_t bytes_remaining = length; + // In QEMU, there never is a TX operation in progress, so start with descriptor 0. + + ESP_LOGV(TAG, "%s: len=%d", __func__, length); + while (bytes_remaining > 0) { + uint32_t will_write = MIN(bytes_remaining, DMA_BUF_SIZE); + memcpy(emac->tx_buf[emac->cur_tx_desc], buf, will_write); + openeth_tx_desc_t* desc_ptr = openeth_tx_desc(emac->cur_tx_desc); + openeth_tx_desc_t desc_val = *desc_ptr; + desc_val.wr = (emac->cur_tx_desc == TX_BUF_COUNT - 1); + desc_val.len = will_write; + desc_val.rd = 1; + // TXEN is already set, and this triggers a TX operation for the descriptor + ESP_LOGV(TAG, "%s: desc %d (%p) len=%d wr=%d", __func__, emac->cur_tx_desc, desc_ptr, will_write, desc_val.wr); + *desc_ptr = desc_val; + bytes_remaining -= will_write; + buf += will_write; + emac->cur_tx_desc = (emac->cur_tx_desc + 1) % TX_BUF_COUNT; + } + + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_opencores_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length) +{ + esp_err_t ret = ESP_OK; + emac_opencores_t *emac = __containerof(mac, emac_opencores_t, parent); + MAC_CHECK(buf && length, "can't set buf and length to null", err, ESP_ERR_INVALID_ARG); + + openeth_rx_desc_t *desc_ptr = openeth_rx_desc(emac->cur_rx_desc); + openeth_rx_desc_t desc_val = *desc_ptr; + ESP_LOGV(TAG, "%s: desc %d (%p) e=%d len=%d wr=%d", __func__, emac->cur_rx_desc, desc_ptr, desc_val.e, desc_val.len, desc_val.wr); + if (desc_val.e) { + ret = ESP_ERR_INVALID_STATE; + goto err; + } + size_t rx_length = desc_val.len; + MAC_CHECK(*length >= rx_length, "RX length too large", err, ESP_ERR_INVALID_SIZE); + *length = rx_length; + memcpy(buf, desc_val.rxpnt, *length); + desc_val.e = 1; + *desc_ptr = desc_val; + + emac->cur_rx_desc = (emac->cur_rx_desc + 1) % RX_BUF_COUNT; + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_opencores_init(esp_eth_mac_t *mac) +{ + esp_err_t ret = ESP_OK; + emac_opencores_t *emac = __containerof(mac, emac_opencores_t, parent); + esp_eth_mediator_t *eth = emac->eth; + MAC_CHECK(eth->on_state_changed(eth, ETH_STATE_LLINIT, NULL) == ESP_OK, "lowlevel init failed", err, ESP_FAIL); + MAC_CHECK(esp_read_mac(emac->addr, ESP_MAC_ETH) == ESP_OK, "fetch ethernet mac address failed", err, ESP_FAIL); + + // Sanity check + if (REG_READ(OPENETH_MODER_REG) != OPENETH_MODER_DEFAULT) { + ESP_LOGE(TAG, "CONFIG_ETH_USE_OPENETH should only be used when running in QEMU."); + ESP_LOGE(TAG, "When running the app on the ESP32, use CONFIG_ETH_USE_ESP32_EMAC instead."); + abort(); + } + // Initialize the MAC + openeth_reset(); + openeth_set_tx_desc_cnt(TX_BUF_COUNT); + emac_opencores_set_addr(mac, emac->addr); + + return ESP_OK; +err: + eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); + return ret; +} + +static esp_err_t emac_opencores_deinit(esp_eth_mac_t *mac) +{ + emac_opencores_t *emac = __containerof(mac, emac_opencores_t, parent); + esp_eth_mediator_t *eth = emac->eth; + eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); + return ESP_OK; +} + +static esp_err_t emac_opencores_del(esp_eth_mac_t *mac) +{ + emac_opencores_t *emac = __containerof(mac, emac_opencores_t, parent); + esp_intr_free(emac->intr_hdl); + vTaskDelete(emac->rx_task_hdl); + for (int i = 0; i < RX_BUF_COUNT; i++) { + free(emac->rx_buf[i]); + } + for (int i = 0; i < TX_BUF_COUNT; i++) { + free(emac->tx_buf[i]); + } + free(emac); + return ESP_OK; +} + +esp_eth_mac_t *esp_eth_mac_new_openeth(const eth_mac_config_t *config) +{ + esp_eth_mac_t *ret = NULL; + emac_opencores_t *emac = NULL; + MAC_CHECK(config, "can't set mac config to null", out, NULL); + emac = calloc(1, sizeof(emac_opencores_t)); + MAC_CHECK(emac, "calloc emac failed", out, NULL); + + // Allocate DMA buffers + for (int i = 0; i < RX_BUF_COUNT; i++) { + emac->rx_buf[i] = heap_caps_calloc(1, DMA_BUF_SIZE, MALLOC_CAP_DMA); + if (!(emac->rx_buf[i])) { + goto out; + } + openeth_init_rx_desc(openeth_rx_desc(i), emac->rx_buf[i]); + } + openeth_rx_desc(RX_BUF_COUNT - 1)->wr = 1; + emac->cur_rx_desc = 0; + + for (int i = 0; i < TX_BUF_COUNT; i++) { + emac->tx_buf[i] = heap_caps_calloc(1, DMA_BUF_SIZE, MALLOC_CAP_DMA); + if (!(emac->tx_buf[i])) { + goto out; + } + openeth_init_tx_desc(openeth_tx_desc(i), emac->tx_buf[i]); + } + openeth_tx_desc(TX_BUF_COUNT - 1)->wr = 1; + emac->cur_tx_desc = 0; + + emac->parent.set_mediator = emac_opencores_set_mediator; + emac->parent.init = emac_opencores_init; + emac->parent.deinit = emac_opencores_deinit; + emac->parent.del = emac_opencores_del; + emac->parent.write_phy_reg = emac_opencores_write_phy_reg; + emac->parent.read_phy_reg = emac_opencores_read_phy_reg; + emac->parent.set_addr = emac_opencores_set_addr; + emac->parent.get_addr = emac_opencores_get_addr; + emac->parent.set_speed = emac_opencores_set_speed; + emac->parent.set_duplex = emac_opencores_set_duplex; + emac->parent.set_link = emac_opencores_set_link; + emac->parent.set_promiscuous = emac_opencores_set_promiscuous; + emac->parent.transmit = emac_opencores_transmit; + emac->parent.receive = emac_opencores_receive; + + // Initialize the interrupt + MAC_CHECK(esp_intr_alloc(OPENETH_INTR_SOURCE, ESP_INTR_FLAG_IRAM, emac_opencores_isr_handler, + emac, &(emac->intr_hdl)) == ESP_OK, + "alloc emac interrupt failed", out, NULL); + + // Create the RX task + BaseType_t xReturned = xTaskCreate(emac_opencores_rx_task, "emac_rx", config->rx_task_stack_size, emac, + config->rx_task_prio, &emac->rx_task_hdl); + MAC_CHECK(xReturned == pdPASS, "create emac_rx task failed", out, NULL); + return &(emac->parent); + +out: + if (emac) { + if (emac->rx_task_hdl) { + vTaskDelete(emac->rx_task_hdl); + } + if (emac->intr_hdl) { + esp_intr_free(emac->intr_hdl); + } + for (int i = 0; i < TX_BUF_COUNT; i++) { + free(emac->tx_buf[i]); + } + for (int i = 0; i < RX_BUF_COUNT; i++) { + free(emac->rx_buf[i]); + } + free(emac); + } + return ret; +} diff --git a/components/esp_eth/src/openeth.h b/components/esp_eth/src/openeth.h new file mode 100644 index 000000000..ea4f48b24 --- /dev/null +++ b/components/esp_eth/src/openeth.h @@ -0,0 +1,216 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#pragma once +#include +#include +#include +#include "sdkconfig.h" +#include "soc/soc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// These are the register definitions for the OpenCores Ethernet MAC. +// See comments in esp_eth_mac_openeth.c for more details about this driver. + +// DMA buffers configuration +#define DMA_BUF_SIZE 1600 +#define RX_BUF_COUNT CONFIG_ETH_OPENETH_DMA_RX_BUFFER_NUM +#define TX_BUF_COUNT CONFIG_ETH_OPENETH_DMA_TX_BUFFER_NUM + +// This driver uses the interrupt source number of the internal EMAC of the ESP32 chip, +// and uses the same register address base. This of course only works in QEMU, where +// the OpenCores MAC is mapped to the same register base and to the same interrupt +// source. This driver does a sanity check that it is not running on the real ESP32 +// chip, using the EMAC date register. +#define OPENETH_INTR_SOURCE ETS_ETH_MAC_INTR_SOURCE +#define OPENETH_BASE DR_REG_EMAC_BASE + +// OpenCores ethmac registers +#define OPENETH_MODER_REG (OPENETH_BASE + 0x00) +#define OPENETH_MODER_DEFAULT 0xa000 +// OPENETH_RST: reset the MAC +#define OPENETH_RST BIT(11) +// OPENETH_PRO: enable promiscuous mode +#define OPENETH_PRO BIT(5) +// OPENETH_TXEN: enable transmit +#define OPENETH_TXEN BIT(1) +// OPENETH_RXEN: enable receive +#define OPENETH_RXEN BIT(0) + +#define OPENETH_INT_SOURCE_REG (OPENETH_BASE + 0x04) +#define OPENETH_INT_MASK_REG (OPENETH_BASE + 0x08) +// These bits apply to INT_SOURCE and INT_MASK registers: +// OPENETH_INT_BUSY: Buffer was received and discarded due to lack of buffers +#define OPENETH_INT_BUSY BIT(4) +// OPENETH_INT_RXB: Frame received +#define OPENETH_INT_RXB BIT(2) +// OPENETH_INT_TXB: Frame transmitted +#define OPENETH_INT_TXB BIT(0) + +// IPGT, IPGR1, IPGR2 registers are not implemented in QEMU, hence not used here +#define OPENETH_PACKETLEN_REG (OPENETH_BASE + 0x18) +// OPENETH_MINFL: minimum frame length +#define OPENETH_MINFL_S 16 +#define OPENETH_MINFL_V 0xffff +#define OPENETH_MINFL_M (OPENETH_MINFL_V << OPENETH_MINFL_S) +// OPENETH_MAXFL: maximum frame length +#define OPENETH_MAXFL_S 0 +#define OPENETH_MAXFL_V 0xffff +#define OPENETH_MAXFL_M (OPENETH_MAXFL_V << OPENETH_MAXFL_S) + +// COLLCONF is not implemented in QEMU +#define OPENETH_TX_BD_NUM_REG (OPENETH_BASE + 0x20) +// CTRLMODER, MIIMODER are not implemented in QEMU +#define OPENETH_MIICOMMAND_REG (OPENETH_BASE + 0x2c) +// OPENETH_WCTRLDATA: write control data +#define OPENETH_WCTRLDATA BIT(2) +// OPENETH_RSTAT: read status +#define OPENETH_RSTAT BIT(1) +// OPENETH_SCANSTAT: scan status +#define OPENETH_SCANSTAT BIT(0) + +#define OPENETH_MIIADDRESS_REG (OPENETH_BASE + 0x30) +// OPENETH_RGAD: register address +#define OPENETH_RGAD_S 8 +#define OPENETH_RGAD_V 0x1f +#define OPENETH_RGAD_M (OPENETH_RGAD_V << OPENETH_RGAD_S) +// OPENETH_FIAD: PHY address +#define OPENETH_FIAD_S 0 +#define OPENETH_FIAD_V 0x1f +#define OPENETH_FIAD_N (OPENETH_FIAD_V << OPENETH_FIAD_S) + +#define OPENETH_MIITX_DATA_REG (OPENETH_BASE + 0x34) +#define OPENETH_MIIRX_DATA_REG (OPENETH_BASE + 0x38) +#define OPENETH_MII_DATA_MASK 0xffff + +#define OPENETH_MIISTATUS_REG (OPENETH_BASE + 0x3c) +// OPENETH_LINKFAIL: link is down +#define OPENETH_LINKFAIL BIT(0) + +// OPENETH_MAC_ADDR0_REG: bytes 2-5 of the MAC address (byte 5 in LSB) +#define OPENETH_MAC_ADDR0_REG (OPENETH_BASE + 0x40) +// OPENETH_MAC_ADDR1_REG: bytes 0-1 of the MAC address (byte 1 in LSB) +#define OPENETH_MAC_ADDR1_REG (OPENETH_BASE + 0x44) + +#define OPENETH_HASH0_ADR_REG (OPENETH_BASE + 0x48) +#define OPENETH_HASH1_ADR_REG (OPENETH_BASE + 0x4c) + +// Location of the DMA descriptors +#define OPENETH_DESC_BASE (OPENETH_BASE + 0x400) +// Total number of (TX + RX) DMA descriptors +#define OPENETH_DESC_CNT 128 + + +// Structures describing TX and RX descriptors. +// The field names are same as in the OpenCores ethmac documentation. +typedef struct { + uint16_t cs: 1; //!< Carrier sense lost (flag set by HW) + uint16_t df: 1; //!< Defer indication (flag set by HW) + uint16_t lc: 1; //!< Late collision occured (flag set by HW) + uint16_t rl: 1; //!< TX failed due to retransmission limit (flag set by HW) + uint16_t rtry: 4; //!< Number of retries before the frame was sent (set by HW) + uint16_t ur: 1; //!< Underrun status (flag set by HW) + uint16_t rsv: 2; //!< Reserved + uint16_t crc: 1; //!< Add CRC at the end of the packet + uint16_t pad: 1; //!< Add padding to the end of short packets + uint16_t wr: 1; //!< Wrap-around. 0: not the last descriptor in the table, 1: last descriptor. + uint16_t irq: 1; //!< Generate interrupt after this descriptor is transmitted + uint16_t rd: 1; //!< Descriptor ready. 0: descriptor owned by SW, 1: descriptor owned by HW. Cleared by HW. + + uint16_t len; //!< Number of bytes to be transmitted + void* txpnt; //!< Pointer to the data to transmit +} openeth_tx_desc_t; + +_Static_assert(sizeof(openeth_tx_desc_t) == 8, "incorrect size of openeth_tx_desc_t"); + +typedef struct { + uint16_t lc: 1; //!< Late collision flag + uint16_t crc: 1; //!< RX CRC error flag + uint16_t sf: 1; //!< Frame shorter than set in PACKETLEN register + uint16_t tl: 1; //!< Frame longer than set in PACKETLEN register + uint16_t dn: 1; //!< Dribble nibble (frame length not divisible by 8 bits) flag + uint16_t is: 1; //!< Invalid symbol flag + uint16_t or: 1; //!< Overrun flag + uint16_t m: 1; //!< Frame received because of the promiscuous mode + uint16_t rsv: 5; //!< Reserved + uint16_t wr: 1; //!< Wrap-around. 0: not the last descriptor in the table, 1: last descriptor. + uint16_t irq: 1; //!< Generate interrupt after this descriptor is transmitted + uint16_t e: 1; //!< The buffer is empty. 0: descriptor owned by SW, 1: descriptor owned by HW. + + uint16_t len; //!< Number of bytes received (filled by HW) + void* rxpnt; //!< Pointer to the receive buffer +} openeth_rx_desc_t; + +_Static_assert(sizeof(openeth_rx_desc_t) == 8, "incorrect size of openeth_rx_desc_t"); + + +static inline openeth_tx_desc_t* openeth_tx_desc(int idx) +{ + assert(idx < TX_BUF_COUNT); + return &((openeth_tx_desc_t*)OPENETH_DESC_BASE)[idx]; +} + +static inline openeth_rx_desc_t* openeth_rx_desc(int idx) +{ + assert(idx < OPENETH_DESC_CNT - TX_BUF_COUNT); + return &((openeth_rx_desc_t*)OPENETH_DESC_BASE)[idx + TX_BUF_COUNT]; +} + +static inline void openeth_enable(void) +{ + REG_SET_BIT(OPENETH_MODER_REG, OPENETH_TXEN | OPENETH_RXEN | OPENETH_PRO); + REG_SET_BIT(OPENETH_INT_MASK_REG, OPENETH_INT_RXB); +} + +static inline void openeth_disable(void) +{ + REG_CLR_BIT(OPENETH_INT_MASK_REG, OPENETH_INT_RXB); + REG_CLR_BIT(OPENETH_MODER_REG, OPENETH_TXEN | OPENETH_RXEN | OPENETH_PRO); +} + +static inline void openeth_reset(void) +{ + REG_SET_BIT(OPENETH_MODER_REG, OPENETH_RST); + REG_CLR_BIT(OPENETH_MODER_REG, OPENETH_RST); +} + +static inline void openeth_init_tx_desc(openeth_tx_desc_t* desc, void* buf) +{ + *desc = (openeth_tx_desc_t) { + .rd = 0, + .txpnt = buf + }; +} + +static inline void openeth_init_rx_desc(openeth_rx_desc_t* desc, void* buf) +{ + *desc = (openeth_rx_desc_t) { + .e = 1, + .irq = 1, + .rxpnt = buf + }; +} + +static inline void openeth_set_tx_desc_cnt(int tx_desc_cnt) +{ + assert(tx_desc_cnt <= OPENETH_DESC_CNT); + REG_WRITE(OPENETH_TX_BD_NUM_REG, tx_desc_cnt); +} + +#ifdef __cplusplus +} +#endif diff --git a/examples/common_components/protocol_examples_common/Kconfig.projbuild b/examples/common_components/protocol_examples_common/Kconfig.projbuild index f5d288e9a..e1fee9b61 100644 --- a/examples/common_components/protocol_examples_common/Kconfig.projbuild +++ b/examples/common_components/protocol_examples_common/Kconfig.projbuild @@ -49,6 +49,17 @@ menu "Example Connection Configuration" select ETH_USE_SPI_ETHERNET help Select external SPI-Ethernet module. + + config EXAMPLE_USE_OPENETH + bool "OpenCores Ethernet MAC (EXPERIMENTAL)" + select ETH_USE_OPENETH + help + When this option is enabled, the example is built with support for + OpenCores Ethernet MAC, which allows testing the example in QEMU. + Note that this option is used for internal testing purposes, and + not officially supported. Examples built with this option enabled + will not run on a real ESP32 chip. + endchoice if EXAMPLE_USE_INTERNAL_ETHERNET diff --git a/examples/common_components/protocol_examples_common/connect.c b/examples/common_components/protocol_examples_common/connect.c index 36e5326eb..1c6bd98d1 100644 --- a/examples/common_components/protocol_examples_common/connect.c +++ b/examples/common_components/protocol_examples_common/connect.c @@ -232,7 +232,12 @@ static void start(void) eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(spi_handle); s_mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config); s_phy = esp_eth_phy_new_dm9051(&phy_config); +#elif CONFIG_EXAMPLE_USE_OPENETH + phy_config.autonego_timeout_ms = 100; + s_mac = esp_eth_mac_new_openeth(&mac_config); + s_phy = esp_eth_phy_new_dp83848(&phy_config); #endif + esp_eth_config_t config = ETH_DEFAULT_CONFIG(s_mac, s_phy); ESP_ERROR_CHECK(esp_eth_driver_install(&config, &s_eth_handle)); s_connection_name = "Ethernet";