blob: b4dad22cd96c05c4f976339afbc994594f305b61 [file] [log] [blame]
/****************************************************************************
* arch/xtensa/src/esp32/esp32_openeth.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <netinet/if_ether.h>
#include <nuttx/net/netdev_lowerhalf.h>
#include "hardware/esp32_soc.h"
#include "esp32_irq.h"
#ifdef CONFIG_ESP32_OPENETH
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* These are the register definitions for the OpenCores Ethernet MAC. */
/* DMA buffers configuration */
#define DMA_BUF_SIZE 1600
#define RX_BUF_COUNT CONFIG_ESP32_OPENETH_DMA_RX_BUFFER_NUM
/* Only need 1 TX buf because packets are transmitted immediately */
#define TX_BUF_COUNT 1
/* 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_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)
/* COLLCONF is not implemented in QEMU */
#define OPENETH_TX_BD_NUM_REG (OPENETH_BASE + 0x20)
/* 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)
/* Location of the DMA descriptors */
#define OPENETH_DESC_BASE (OPENETH_BASE + 0x400)
/* Total number of (TX + RX) DMA descriptors */
#define OPENETH_DESC_CNT 128
/****************************************************************************
* Private Types
****************************************************************************/
struct openeth_priv_s
{
struct netdev_lowerhalf_s dev;
int cpuint; /* EMAC interrupt ID */
int cur_rx_desc;
int cur_tx_desc;
uint8_t *rx_buf[RX_BUF_COUNT];
uint8_t *tx_buf[TX_BUF_COUNT];
};
/* 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");
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* NuttX callback functions */
static int openeth_ifup(struct netdev_lowerhalf_s *dev);
static int openeth_ifdown(struct netdev_lowerhalf_s *dev);
static int openeth_transmit(struct netdev_lowerhalf_s *dev, netpkt_t *pkt);
static netpkt_t *openeth_receive(struct netdev_lowerhalf_s *dev);
/****************************************************************************
* Private Data
****************************************************************************/
static struct openeth_priv_s g_openeth;
static const struct netdev_ops_s g_ops =
{
.ifup = openeth_ifup,
.ifdown = openeth_ifdown,
.transmit = openeth_transmit,
.receive = openeth_receive,
};
/****************************************************************************
* Private Functions
****************************************************************************/
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);
}
static IRAM_ATTR int openeth_isr_handler(int irq, void *context, void *arg)
{
FAR struct netdev_lowerhalf_s *dev = &g_openeth.dev;
uint32_t status = REG_READ(OPENETH_INT_SOURCE_REG);
if (status & OPENETH_INT_RXB)
{
netdev_lower_rxready(dev);
}
if (status & OPENETH_INT_BUSY)
{
ninfo("RX frame dropped (0x%x)", status);
}
/* Clear interrupt */
REG_WRITE(OPENETH_INT_SOURCE_REG, status);
return 0;
}
static FAR netpkt_t *openeth_receive(FAR struct netdev_lowerhalf_s *dev)
{
FAR netpkt_t *pkt = netpkt_alloc(dev, NETPKT_RX);
struct openeth_priv_s *priv = &g_openeth;
if (pkt)
{
openeth_rx_desc_t *desc_ptr = openeth_rx_desc(priv->cur_rx_desc);
openeth_rx_desc_t desc_val = *desc_ptr;
ninfo("desc %d (%p) e=%d len=%d wr=%d",
priv->cur_rx_desc, desc_ptr, desc_val.e, desc_val.len, desc_val.wr);
if (desc_val.e)
{
ninfo("descriptor is not owned by HW (e=%d)", desc_val.e);
goto err;
}
if (!desc_val.len)
{
nerr("ERROR desc_val.len is zero");
goto err;
}
netpkt_copyin(dev, pkt, desc_val.rxpnt, desc_val.len, 0);
/* Free up the descriptor */
desc_val.e = 1;
desc_val.len = 0;
*desc_ptr = desc_val;
priv->cur_rx_desc = (priv->cur_rx_desc + 1) % RX_BUF_COUNT;
}
return pkt;
err:
netpkt_free(dev, pkt, NETPKT_RX);
return NULL;
}
/****************************************************************************
* Name: openeth_ifup
*
* Description:
* NuttX Callback: Bring up the Ethernet interface when an IP address is
* provided
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
static int openeth_ifup(FAR struct netdev_lowerhalf_s *dev)
{
irqstate_t flags;
/* Disable the Ethernet interrupt */
flags = enter_critical_section();
/* Enable TX and RX */
openeth_enable();
leave_critical_section(flags);
netdev_lower_carrier_on(dev);
return OK;
}
/****************************************************************************
* Name: openeth_ifdown
*
* Description:
* NuttX Callback: Stop the interface.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
static int openeth_ifdown(FAR struct netdev_lowerhalf_s *dev)
{
irqstate_t flags;
/* Disable the Ethernet interrupt */
flags = enter_critical_section();
/* Disable TX and RX */
openeth_enable();
leave_critical_section(flags);
netdev_lower_carrier_off(dev);
return OK;
}
static int openeth_transmit(FAR struct netdev_lowerhalf_s *dev,
FAR netpkt_t *pkt)
{
FAR struct openeth_priv_s *priv = (FAR struct openeth_priv_s *)dev;
unsigned int len = netpkt_getdatalen(dev, pkt);
/* In QEMU, there never is a TX operation in progress */
/* Copyout the L2 data and transmit. */
netpkt_copyout(dev, priv->tx_buf[priv->cur_tx_desc], pkt, len, 0);
/* Do Transmit */
openeth_tx_desc_t *desc_ptr = openeth_tx_desc(priv->cur_tx_desc);
openeth_tx_desc_t desc_val = *desc_ptr;
desc_val.wr = (priv->cur_tx_desc == TX_BUF_COUNT - 1);
desc_val.len = len;
desc_val.rd = 1;
*desc_ptr = desc_val;
/* Free the buffer and notify the upper layer */
netpkt_free(dev, pkt, NETPKT_TX);
netdev_lower_txdone(dev);
return OK;
}
static int openeth_set_addr(uint8_t *addr)
{
ninfo("set mac\n");
if (!addr)
{
nerr("can't set mac addr to null");
goto err;
}
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;
uint32_t 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 0;
err:
return -1;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: esp32_openeth_initialize
*
* Description:
* Initialize the openeth driver
*
* Input Parameters:
* None
*
* Returned Value:
* OK on success; Negated errno on failure.
*
****************************************************************************/
int esp32_openeth_initialize(void)
{
int ret;
FAR struct openeth_priv_s *priv = &g_openeth;
FAR struct netdev_lowerhalf_s *dev = &priv->dev;
/* Sanity check */
if (REG_READ(OPENETH_MODER_REG) != OPENETH_MODER_DEFAULT)
{
nerr("CONFIG_ESP32_OPENETH should only be used when running in QEMU.");
nerr("When running the app on the ESP32, use ESP32 EMAC instead.");
abort();
}
/* Initialize the driver structure */
memset(priv, 0, sizeof(struct openeth_priv_s));
dev->ops = &g_ops;
dev->quota[NETPKT_TX] = TX_BUF_COUNT;
dev->quota[NETPKT_RX] = RX_BUF_COUNT;
/* Allocate DMA buffers */
for (int i = 0; i < RX_BUF_COUNT; i++)
{
priv->rx_buf[i] = kmm_calloc(1, DMA_BUF_SIZE);
if (!(priv->rx_buf[i]))
{
nerr("ERROR: Failed allocate RX descriptors\n");
ret = -ENOMEM;
goto err;
}
openeth_init_rx_desc(openeth_rx_desc(i), priv->rx_buf[i]);
}
openeth_rx_desc(RX_BUF_COUNT - 1)->wr = 1;
priv->cur_rx_desc = 0;
for (int i = 0; i < TX_BUF_COUNT; i++)
{
priv->tx_buf[i] = kmm_calloc(1, DMA_BUF_SIZE);
if (!(priv->tx_buf[i]))
{
nerr("ERROR: Failed allocate TX descriptors\n");
ret = -ENOMEM;
goto err;
}
openeth_init_tx_desc(openeth_tx_desc(i), priv->tx_buf[i]);
}
openeth_tx_desc(TX_BUF_COUNT - 1)->wr = 1;
priv->cur_tx_desc = 0;
/* Setup interrupts */
priv->cpuint = esp32_setup_irq(0, ESP32_PERIPH_EMAC,
1, ESP32_CPUINT_LEVEL);
if (priv->cpuint < 0)
{
nerr("ERROR: Failed allocate interrupt\n");
ret = -ENOMEM;
goto err;
}
/* Initialize the MAC */
openeth_reset();
openeth_set_tx_desc_cnt(TX_BUF_COUNT);
memcpy(priv->dev.netdev.d_mac.ether.ether_addr_octet,
"\x00\x02\x03\x04\x05\x06\x07\x08", ETH_ALEN);
openeth_set_addr(priv->dev.netdev.d_mac.ether.ether_addr_octet);
/* Attach the interrupt */
ret = irq_attach(ESP32_IRQ_EMAC, openeth_isr_handler, priv);
/* Register the device with the OS so that socket IOCTLs can be
* performed.
*/
ret = netdev_lower_register(dev, NET_LL_ETHERNET);
if (ret)
{
nerr("ERROR: netdev_lower_register\n");
goto err;
}
/* Put the network in the UP state */
return openeth_ifup(dev);
err:
nerr("Failed initializing ret = %d", ret);
abort();
}
#endif /* CONFIG_ESP32_OPENETH */