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.
This commit is contained in:
Ivan Grokhotkov 2019-10-01 18:50:34 +02:00
parent 420ee45279
commit 31dac92e5f
8 changed files with 680 additions and 0 deletions

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <string.h>
#include <stdlib.h>
#include <sys/cdefs.h>
#include <sys/param.h>
#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;
}

View File

@ -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 <stdint.h>
#include <stddef.h>
#include <assert.h>
#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

View File

@ -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

View File

@ -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";