OVMS3-idf/components/ethernet/emac_main.c
morris 20b7f0ec87 ethernet: fix infinite loop when init phy or reset mac
1. fix infinite loop problem when init phy device
2. fix infinite loop problem when reset mac
3. fix little bugs in ethernetif_init
4. fix incompatible return value between lwip and esp-idf

Closes https://github.com/espressif/esp-idf/issues/2331
Closes https://github.com/espressif/esp-idf/issues/2141
2018-09-20 10:09:38 +08:00

1244 lines
36 KiB
C

// Copyright 2015-2017 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.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rom/ets_sys.h"
#include "rom/gpio.h"
#include "soc/dport_reg.h"
#include "soc/io_mux_reg.h"
#include "soc/rtc.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/gpio_reg.h"
#include "soc/dport_reg.h"
#include "soc/emac_ex_reg.h"
#include "soc/emac_reg_v2.h"
#include "soc/soc.h"
#include "tcpip_adapter.h"
#include "sdkconfig.h"
#include "esp_task_wdt.h"
#include "esp_event.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_eth.h"
#include "esp_intr_alloc.h"
#include "esp_pm.h"
#include "driver/periph_ctrl.h"
#include "emac_common.h"
#include "emac_desc.h"
#include "freertos/xtensa_api.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/timers.h"
#include "lwip/err.h"
#define EMAC_EVT_QNUM 200
#define EMAC_SIG_MAX 50
static struct emac_config_data emac_config;
static dma_extended_desc_t *emac_dma_rx_chain_buf[DMA_RX_BUF_NUM];
static dma_extended_desc_t *emac_dma_tx_chain_buf[DMA_TX_BUF_NUM];
static uint8_t *emac_dma_rx_buf[DMA_RX_BUF_NUM];
static uint8_t *emac_dma_tx_buf[DMA_TX_BUF_NUM];
static SemaphoreHandle_t emac_g_sem = NULL;
static portMUX_TYPE g_emac_mux = portMUX_INITIALIZER_UNLOCKED;
static xTaskHandle emac_task_hdl = NULL;
static xQueueHandle emac_xqueue = NULL;
static uint8_t emac_sig_cnt[EMAC_SIG_MAX] = {0};
static TimerHandle_t emac_timer = NULL;
static SemaphoreHandle_t emac_rx_xMutex = NULL;
static SemaphoreHandle_t emac_tx_xMutex = NULL;
static const char *TAG = "emac";
static bool pause_send = false;
#ifdef CONFIG_PM_ENABLE
static esp_pm_lock_handle_t s_pm_lock;
#endif
static esp_err_t emac_ioctl(emac_sig_t sig, emac_par_t par);
esp_err_t emac_post(emac_sig_t sig, emac_par_t par);
static void emac_macaddr_init(void)
{
esp_read_mac(&(emac_config.macaddr[0]), ESP_MAC_ETH);
}
void esp_eth_get_mac(uint8_t mac[6])
{
memcpy(mac, &(emac_config.macaddr[0]), 6);
}
esp_err_t esp_eth_set_mac(const uint8_t mac[6])
{
if ((mac[0] & 0x01) == 0) {
memcpy(&(emac_config.macaddr[0]), mac, 6);
return ESP_OK;
} else {
return ESP_ERR_INVALID_MAC;
}
}
eth_speed_mode_t esp_eth_get_speed(void)
{
return emac_config.emac_phy_get_speed_mode();
}
static void emac_setup_tx_desc(struct dma_extended_desc *tx_desc, uint32_t size)
{
tx_desc->basic.desc1 = size & 0xfff;
tx_desc->basic.desc0 = EMAC_DESC_TX_OWN | EMAC_DESC_INT_COMPL |
EMAC_DESC_LAST_SEGMENT | EMAC_DESC_FIRST_SEGMENT |
EMAC_DESC_SECOND_ADDR_CHAIN;
}
static void emac_clean_tx_desc(struct dma_extended_desc *tx_desc)
{
tx_desc->basic.desc1 = 0;
tx_desc->basic.desc0 = 0;
}
static void emac_clean_rx_desc(struct dma_extended_desc *rx_desc,
uint32_t buf_ptr)
{
if (buf_ptr != 0) {
rx_desc->basic.desc2 = buf_ptr;
}
rx_desc->basic.desc1 = EMAC_DESC_RX_SECOND_ADDR_CHAIN | DMA_RX_BUF_SIZE;
rx_desc->basic.desc0 = EMAC_DESC_RX_OWN;
}
static void emac_set_tx_base_reg(void)
{
REG_WRITE(EMAC_DMATXBASEADDR_REG, (uint32_t)(emac_config.dma_etx));
}
static void emac_set_rx_base_reg(void)
{
REG_WRITE(EMAC_DMARXBASEADDR_REG, (uint32_t)(emac_config.dma_erx));
}
/*
* dirty_rx indicates the hardware has been fed with data packets and is the
* first node software needs to handle;
*
* cur_rx indicates the completion of software handling and is the last node
* hardware could use;
*
* cnt_rx is to count the numbers of packets handled by software, passed to
* protocol stack and not been freed.
*
* (1) Initializing the Linked List. Connect the numerable nodes to a circular
* linked list, appoint one of the nodes as the head node, mark* the dirty_rx
* and cur_rx into the node, and mount the node on the hardware base address.
* Initialize cnt_rx into 0.
*
* (2) When hardware receives packets, nodes of linked lists will be fed with
* data packets from the base address by turns, marks the node
* of linked lists as “HARDWARE UNUSABLE” and reports interrupts.
*
* (3) When the software receives the interrupts, it will handle the linked
* lists by turns from dirty_rx, send data packets to protocol
* stack. dirty_rx will deviate backwards by turns and cnt_rx will by turns ++.
*
* (4) After the protocol stack handles all the data and calls the free function,
* it will deviate backwards by turns from cur_rx, mark the * node of linked
* lists as “HARDWARE USABLE” and cnt_rx will by turns --.
*
* (5) Cycle from Step 2 to Step 4 without break and build up circular linked
* list handling.
*/
static void emac_reset_dma_chain(void)
{
emac_config.cnt_tx = 0;
emac_config.cur_tx = 0;
emac_config.dirty_tx = 0;
emac_config.cnt_rx = 0;
emac_config.cur_rx = 0;
emac_config.dirty_rx = 0;
}
static void emac_init_dma_chain(void)
{
int i;
dma_extended_desc_t *p = NULL;
//init tx chain
emac_config.dma_etx = emac_dma_tx_chain_buf[0];
emac_config.cnt_tx = 0;
emac_config.cur_tx = 0;
emac_config.dirty_tx = 0;
p = emac_dma_tx_chain_buf[0];
for (i = 0; i < (DMA_TX_BUF_NUM - 1); i++) {
emac_clean_tx_desc(p);
/* point to the buffer */
p->basic.desc2 = (uint32_t)(emac_dma_tx_buf[i]);
/* point to next descriptor */
p->basic.desc3 = (uint32_t)(emac_dma_tx_chain_buf[i + 1]);
p = emac_dma_tx_chain_buf[i + 1];
}
emac_clean_tx_desc(p);
/* point to the buffer */
p->basic.desc2 = (uint32_t)(emac_dma_tx_buf[i]);
/* point to first descriptor */
p->basic.desc3 = (uint32_t)(emac_config.dma_etx);
//init rx chain
emac_config.dma_erx = emac_dma_rx_chain_buf[0];
emac_config.cnt_rx = 0;
emac_config.cur_rx = 0;
emac_config.dirty_rx = 0;
p = emac_dma_rx_chain_buf[0];
for (i = 0; i < (DMA_RX_BUF_NUM - 1); i++) {
emac_clean_rx_desc(p, (uint32_t)(emac_dma_rx_buf[i]));
/* point to the buffer */
p->basic.desc3 = (uint32_t)(emac_dma_rx_chain_buf[i + 1]);
/* point to next descriptor */
p = emac_dma_rx_chain_buf[i + 1];
}
/* point to the buffer */
emac_clean_rx_desc(p, (uint32_t)(emac_dma_rx_buf[i]));
/* point to first descriptor */
p->basic.desc3 = (uint32_t)(emac_config.dma_erx);
}
void esp_eth_smi_write(uint32_t reg_num, uint16_t value)
{
uint32_t phy_num = emac_config.phy_addr;
while (REG_GET_BIT(EMAC_GMIIADDR_REG, EMAC_MIIBUSY) == 1) {
}
REG_WRITE(EMAC_MIIDATA_REG, value);
REG_WRITE(EMAC_GMIIADDR_REG, 0x3 | ((reg_num & 0x1f) << 6) |
((phy_num & 0x1f) << 11) | ((0x3) << 2));
while (REG_GET_BIT(EMAC_GMIIADDR_REG, EMAC_MIIBUSY) == 1) {
}
}
uint16_t esp_eth_smi_read(uint32_t reg_num)
{
uint32_t phy_num = emac_config.phy_addr;
uint16_t value = 0;
while (REG_GET_BIT(EMAC_GMIIADDR_REG, EMAC_MIIBUSY) == 1) {
}
REG_WRITE(EMAC_GMIIADDR_REG, 0x1 | ((reg_num & 0x1f) << 6) |
((phy_num & 0x1f) << 11) | (0x3 << 2));
while (REG_GET_BIT(EMAC_GMIIADDR_REG, EMAC_MIIBUSY) == 1) {
}
value = (REG_READ(EMAC_MIIDATA_REG) & 0xffff);
return value;
}
esp_err_t esp_eth_smi_wait_value(uint32_t reg_num, uint16_t value,
uint16_t value_mask, int timeout_ms)
{
unsigned start = xTaskGetTickCount();
unsigned timeout_ticks = (timeout_ms + portTICK_PERIOD_MS - 1) /
portTICK_PERIOD_MS;
uint16_t current_value = 0;
while (timeout_ticks == 0 || (xTaskGetTickCount() - start < timeout_ticks)) {
current_value = esp_eth_smi_read(reg_num);
if ((current_value & value_mask) == (value & value_mask)) {
return ESP_OK;
}
vTaskDelay(1);
}
ESP_LOGE(TAG, "Timed out waiting for PHY register 0x%x to have value 0x%04x(mask 0x%04x). Current value 0x%04x",
reg_num, value, value_mask, current_value);
return ESP_ERR_TIMEOUT;
}
esp_err_t emac_reset(void)
{
REG_SET_BIT(EMAC_DMABUSMODE_REG, EMAC_SW_RST);
if (emac_config.reset_timeout_ms) {
int start = xTaskGetTickCount();
uint32_t timeout_ticks = (emac_config.reset_timeout_ms + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS;
while (timeout_ticks == 0 || (xTaskGetTickCount() - start < timeout_ticks)) {
if (REG_GET_BIT(EMAC_DMABUSMODE_REG, EMAC_SW_RST) != EMAC_SW_RST) {
goto reset_ok;
}
vTaskDelay(1);
}
ESP_LOGE(TAG, "Reset EMAC Timeout");
return ESP_ERR_TIMEOUT;
}
/* infinite wait loop */
else {
while (REG_GET_BIT(EMAC_DMABUSMODE_REG, EMAC_SW_RST) == EMAC_SW_RST) {
//nothing to do ,if stop here,maybe emac have not clk input.
ESP_LOGI(TAG, "emac resetting ....");
}
}
reset_ok:
ESP_LOGI(TAG, "emac reset done");
return ESP_OK;
}
static void emac_set_user_config_data(eth_config_t *config)
{
emac_config.phy_addr = config->phy_addr;
emac_config.mac_mode = config->mac_mode;
emac_config.clock_mode = config->clock_mode;
emac_config.phy_init = config->phy_init;
emac_config.emac_tcpip_input = config->tcpip_input;
emac_config.emac_gpio_config = config->gpio_config;
emac_config.emac_phy_check_link = config->phy_check_link;
emac_config.emac_phy_check_init = config->phy_check_init;
emac_config.emac_phy_get_speed_mode = config->phy_get_speed_mode;
emac_config.emac_phy_get_duplex_mode = config->phy_get_duplex_mode;
emac_config.reset_timeout_ms = config->reset_timeout_ms;
#if DMA_RX_BUF_NUM > 9
emac_config.emac_flow_ctrl_enable = config->flow_ctrl_enable;
#else
if (config->flow_ctrl_enable == true) {
ESP_LOGE(TAG, "Can only configure flow_ctrl_enable==true if DMA_RX_BUF_NUM in menuconfig is >9. Disabling flow control.");
}
emac_config.emac_flow_ctrl_enable = false;
#endif
emac_config.emac_phy_get_partner_pause_enable =
config->phy_get_partner_pause_enable;
emac_config.emac_phy_power_enable = config->phy_power_enable;
}
static void emac_enable_intr()
{
REG_WRITE(EMAC_DMAIN_EN_REG, EMAC_INTR_ENABLE_BIT);
}
static void emac_disable_intr()
{
REG_WRITE(EMAC_DMAIN_EN_REG, 0);
}
static esp_err_t emac_verify_args(void)
{
esp_err_t ret = ESP_OK;
if (emac_config.phy_addr > PHY31) {
ESP_LOGE(TAG, "phy addr err");
ret = ESP_FAIL;
}
if (emac_config.mac_mode != ETH_MODE_RMII) {
ESP_LOGE(TAG, "mac mode err, currently only support for RMII");
ret = ESP_FAIL;
}
if (emac_config.clock_mode > ETH_CLOCK_GPIO17_OUT) {
ESP_LOGE(TAG, "emac clock mode err");
ret = ESP_FAIL;
}
if (emac_config.phy_init == NULL) {
ESP_LOGE(TAG, "phy_init func is null");
ret = ESP_FAIL;
}
if (emac_config.emac_tcpip_input == NULL) {
ESP_LOGE(TAG, "tcpip_input func is null");
ret = ESP_FAIL;
}
if (emac_config.emac_gpio_config == NULL) {
ESP_LOGE(TAG, "gpio config func is null");
ret = ESP_FAIL;
}
if (emac_config.emac_phy_check_link == NULL) {
ESP_LOGE(TAG, "phy check link func is null");
ret = ESP_FAIL;
}
if (emac_config.emac_phy_check_init == NULL) {
ESP_LOGE(TAG, "phy check init func is null");
ret = ESP_FAIL;
}
if (emac_config.emac_phy_get_speed_mode == NULL) {
ESP_LOGE(TAG, "phy get speed mode func is null");
ret = ESP_FAIL;
}
if (emac_config.emac_phy_get_duplex_mode == NULL) {
ESP_LOGE(TAG, "phy get duplex mode func is null");
ret = ESP_FAIL;
}
if (emac_config.emac_flow_ctrl_enable == true &&
emac_config.emac_phy_get_partner_pause_enable == NULL) {
ESP_LOGE(TAG, "phy get partner pause enable func is null");
ret = ESP_FAIL;
}
if (emac_config.emac_phy_power_enable == NULL) {
ESP_LOGE(TAG, "phy power enable func is null");
ret = ESP_FAIL;
}
return ret;
}
static void emac_process_tx(void)
{
uint32_t cur_tx_desc = emac_read_tx_cur_reg();
if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
return;
}
xSemaphoreTakeRecursive(emac_tx_xMutex, portMAX_DELAY);
while ((uint32_t)(emac_dma_tx_chain_buf[emac_config.dirty_tx]) != cur_tx_desc) {
emac_clean_tx_desc(emac_dma_tx_chain_buf[emac_config.dirty_tx]);
emac_config.dirty_tx = (emac_config.dirty_tx + 1) % DMA_TX_BUF_NUM;
emac_config.cnt_tx--;
if (emac_config.cnt_tx < 0) {
ESP_LOGE(TAG, "emac tx chain err");
}
cur_tx_desc = emac_read_tx_cur_reg();
}
xSemaphoreGiveRecursive(emac_tx_xMutex);
}
void esp_eth_free_rx_buf(void *buf)
{
xSemaphoreTakeRecursive(emac_rx_xMutex, portMAX_DELAY);
emac_clean_rx_desc(emac_dma_rx_chain_buf[emac_config.cur_rx], (uint32_t)buf);
emac_config.cur_rx = (emac_config.cur_rx + 1) % DMA_RX_BUF_NUM;
emac_config.cnt_rx--;
if (emac_config.cnt_rx < 0) {
ESP_LOGE(TAG, "emac rx buf err!!\n");
}
emac_poll_rx_cmd();
xSemaphoreGiveRecursive(emac_rx_xMutex);
if (emac_config.emac_flow_ctrl_partner_support == true) {
portENTER_CRITICAL(&g_emac_mux);
if (pause_send == true && emac_config.cnt_rx <
FLOW_CONTROL_LOW_WATERMARK) {
emac_send_pause_zero_frame_enable();
pause_send = false;
}
portEXIT_CRITICAL(&g_emac_mux);
}
}
static uint32_t IRAM_ATTR emac_get_rxbuf_count_in_intr(void)
{
uint32_t cnt = 0;
uint32_t cur_rx_desc = emac_read_rx_cur_reg();
struct dma_extended_desc *cur_desc = (dma_extended_desc_t *)cur_rx_desc;
while (cur_desc->basic.desc0 == EMAC_DESC_RX_OWN && cnt < DMA_RX_BUF_NUM) {
cnt++;
cur_desc = (struct dma_extended_desc *)cur_desc->basic.desc3;
}
return cnt;
}
#if CONFIG_EMAC_L2_TO_L3_RX_BUF_MODE
static void emac_process_rx(void)
{
if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
return;
}
uint32_t cur_rx_desc = emac_read_rx_cur_reg();
while (((uint32_t)(emac_dma_rx_chain_buf[emac_config.dirty_rx]) != cur_rx_desc)) {
//copy data to lwip
emac_config.emac_tcpip_input((emac_dma_rx_buf[emac_config.dirty_rx]),
(((emac_dma_rx_chain_buf[emac_config.dirty_rx]->basic.desc0) >>
EMAC_DESC_FRAME_LENGTH_S) &
EMAC_DESC_FRAME_LENGTH),
NULL);
emac_clean_rx_desc(emac_dma_rx_chain_buf[emac_config.dirty_rx],
(uint32_t)(emac_dma_rx_buf[emac_config.dirty_rx]));
emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM;
//if open this ,one intr can do many intrs ?
cur_rx_desc = emac_read_rx_cur_reg();
}
emac_enable_rx_intr();
}
static void emac_process_rx_unavail(void)
{
if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
return;
}
uint32_t dirty_cnt = 0;
while (dirty_cnt < DMA_RX_BUF_NUM) {
if (emac_dma_rx_chain_buf[emac_config.dirty_rx]->basic.desc0 == EMAC_DESC_RX_OWN) {
break;
}
dirty_cnt++;
//copy data to lwip
emac_config.emac_tcpip_input((emac_dma_rx_buf[emac_config.dirty_rx]),
(((emac_dma_rx_chain_buf[emac_config.dirty_rx]->basic.desc0) >>
EMAC_DESC_FRAME_LENGTH_S) &
EMAC_DESC_FRAME_LENGTH),
NULL);
emac_clean_rx_desc(emac_dma_rx_chain_buf[emac_config.dirty_rx],
(uint32_t)(emac_dma_rx_buf[emac_config.dirty_rx]));
emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM;
}
emac_enable_rx_intr();
emac_enable_rx_unavail_intr();
emac_poll_rx_cmd();
}
#else
static void emac_process_rx_unavail(void)
{
if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
return;
}
xSemaphoreTakeRecursive(emac_rx_xMutex, portMAX_DELAY);
while (emac_config.cnt_rx < DMA_RX_BUF_NUM) {
if (emac_dma_rx_chain_buf[emac_config.dirty_rx]->basic.desc0 == EMAC_DESC_RX_OWN) {
break;
}
emac_config.cnt_rx++;
if (emac_config.cnt_rx > DMA_RX_BUF_NUM) {
ESP_LOGE(TAG, "emac rx unavail buf err !!\n");
}
uint32_t tmp_dirty = emac_config.dirty_rx;
emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM;
//copy data to lwip
emac_config.emac_tcpip_input((emac_dma_rx_buf[tmp_dirty]),
(((emac_dma_rx_chain_buf[tmp_dirty]->basic.desc0) >>
EMAC_DESC_FRAME_LENGTH_S) &
EMAC_DESC_FRAME_LENGTH),
NULL);
}
emac_enable_rx_intr();
emac_enable_rx_unavail_intr();
xSemaphoreGiveRecursive(emac_rx_xMutex);
}
static void emac_process_rx(void)
{
if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
return;
}
uint32_t cur_rx_desc = emac_read_rx_cur_reg();
xSemaphoreTakeRecursive(emac_rx_xMutex, portMAX_DELAY);
if (((uint32_t)(emac_dma_rx_chain_buf[emac_config.dirty_rx])) !=
cur_rx_desc) {
while (((uint32_t)(emac_dma_rx_chain_buf[emac_config.dirty_rx]) != cur_rx_desc) &&
emac_config.cnt_rx < DMA_RX_BUF_NUM) {
emac_config.cnt_rx++;
if (emac_config.cnt_rx > DMA_RX_BUF_NUM) {
ESP_LOGE(TAG, "emac rx buf err!!\n");
}
uint32_t tmp_dirty = emac_config.dirty_rx;
emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM;
//copy data to lwip
emac_config.emac_tcpip_input((emac_dma_rx_buf[tmp_dirty]),
(((emac_dma_rx_chain_buf[tmp_dirty]->basic.desc0) >>
EMAC_DESC_FRAME_LENGTH_S) &
EMAC_DESC_FRAME_LENGTH),
NULL);
cur_rx_desc = emac_read_rx_cur_reg();
}
} else {
if (emac_config.cnt_rx < DMA_RX_BUF_NUM) {
if ((emac_dma_rx_chain_buf[emac_config.dirty_rx]->basic.desc0 &
EMAC_DESC_RX_OWN) == 0) {
while (emac_config.cnt_rx < DMA_RX_BUF_NUM) {
if (emac_dma_rx_chain_buf[emac_config.dirty_rx]->basic.desc0 == EMAC_DESC_RX_OWN) {
break;
}
emac_config.cnt_rx++;
if (emac_config.cnt_rx > DMA_RX_BUF_NUM) {
ESP_LOGE(TAG, "emac rx buf err!!!\n");
}
uint32_t tmp_dirty = emac_config.dirty_rx;
emac_config.dirty_rx = (emac_config.dirty_rx + 1) %
DMA_RX_BUF_NUM;
//copy data to lwip
emac_config.emac_tcpip_input((emac_dma_rx_buf[tmp_dirty]),
(((emac_dma_rx_chain_buf[tmp_dirty]->basic.desc0) >>
EMAC_DESC_FRAME_LENGTH_S) &
EMAC_DESC_FRAME_LENGTH),
NULL);
}
}
}
}
emac_enable_rx_intr();
xSemaphoreGiveRecursive(emac_rx_xMutex);
}
#endif
//TODO other events need to do something
static void IRAM_ATTR emac_process_intr(void *arg)
{
uint32_t event;
event = REG_READ(EMAC_DMASTATUS_REG);
//clr intrs
REG_WRITE(EMAC_DMASTATUS_REG, event);
if (event & EMAC_RECV_INT) {
emac_disable_rx_intr();
if (emac_config.emac_flow_ctrl_partner_support == true) {
if (emac_get_rxbuf_count_in_intr() < FLOW_CONTROL_HIGH_WATERMARK &&
pause_send == false) {
pause_send = true;
emac_send_pause_frame_enable();
}
}
emac_post(SIG_EMAC_RX_DONE, 0);
}
if (event & EMAC_RECV_BUF_UNAVAIL) {
emac_disable_rx_unavail_intr();
emac_post(SIG_EMAC_RX_UNAVAIL, 0);
}
if (event & EMAC_TRANS_INT) {
emac_post(SIG_EMAC_TX_DONE, 0);
}
}
static void emac_set_macaddr_reg(void)
{
REG_SET_FIELD(EMAC_ADDR0HIGH_REG, EMAC_ADDRESS0_HI, (emac_config.macaddr[5] << 8) | (emac_config.macaddr[4]));
REG_WRITE(EMAC_ADDR0LOW_REG, (emac_config.macaddr[3] << 24) |
(emac_config.macaddr[2] << 16) | (emac_config.macaddr[1] << 8) |
(emac_config.macaddr[0]));
}
static void emac_check_phy_init(void)
{
emac_config.emac_phy_check_init();
if (emac_config.emac_phy_get_duplex_mode() == ETH_MODE_FULLDUPLEX) {
REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_EMACDUPLEX);
} else {
REG_CLR_BIT(EMAC_GMACCONFIG_REG, EMAC_EMACDUPLEX);
}
if (emac_config.emac_phy_get_speed_mode() == ETH_SPEED_MODE_100M) {
REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_EMACFESPEED);
} else {
REG_CLR_BIT(EMAC_GMACCONFIG_REG, EMAC_EMACFESPEED);
}
#if CONFIG_EMAC_L2_TO_L3_RX_BUF_MODE
emac_disable_flowctrl();
emac_config.emac_flow_ctrl_partner_support = false;
#else
if (emac_config.emac_flow_ctrl_enable == true) {
if (emac_config.emac_phy_get_partner_pause_enable() == true &&
emac_config.emac_phy_get_duplex_mode() == ETH_MODE_FULLDUPLEX) {
emac_enable_flowctrl();
emac_config.emac_flow_ctrl_partner_support = true;
} else {
emac_disable_flowctrl();
emac_config.emac_flow_ctrl_partner_support = false;
}
} else {
emac_disable_flowctrl();
emac_config.emac_flow_ctrl_partner_support = false;
}
#endif
emac_mac_enable_txrx();
}
static void emac_process_link_updown(bool link_status)
{
system_event_t evt;
uint8_t i = 0;
emac_config.phy_link_up = link_status;
if (link_status == true) {
emac_check_phy_init();
ESP_LOGD(TAG, "eth link_up");
emac_enable_dma_tx();
emac_enable_dma_rx();
for (i = 0; i < PHY_LINK_CHECK_NUM; i++) {
emac_check_phy_init();
}
evt.event_id = SYSTEM_EVENT_ETH_CONNECTED;
} else {
ESP_LOGD(TAG, "eth link_down");
emac_disable_dma_tx();
emac_disable_dma_rx();
evt.event_id = SYSTEM_EVENT_ETH_DISCONNECTED;
}
esp_event_send(&evt);
}
static void emac_hw_init(void)
{
//init chain
emac_init_dma_chain();
//get hw features TODO
//ipc TODO
}
esp_err_t esp_eth_tx(uint8_t *buf, uint16_t size)
{
esp_err_t ret = ESP_OK;
if (emac_config.emac_status != EMAC_RUNTIME_START) {
ESP_LOGE(TAG, "tx netif is not ready, emac_status=%d",
emac_config.emac_status);
ret = ESP_ERR_INVALID_STATE;
return ret;
}
xSemaphoreTakeRecursive(emac_tx_xMutex, portMAX_DELAY);
if (emac_config.cnt_tx == DMA_TX_BUF_NUM - 1) {
ESP_LOGD(TAG, "tx buf full");
ret = ESP_ERR_NO_MEM;
goto _exit;
}
memcpy(emac_dma_tx_buf[emac_config.cur_tx], buf, size);
emac_setup_tx_desc(emac_dma_tx_chain_buf[emac_config.cur_tx], size);
emac_config.cnt_tx++;
emac_config.cur_tx = (emac_config.cur_tx + 1) % DMA_TX_BUF_NUM;
emac_poll_tx_cmd();
_exit:
xSemaphoreGiveRecursive(emac_tx_xMutex);
return ret;
}
static void emac_init_default_data(void)
{
memset((uint8_t *)&emac_config, 0, sizeof(struct emac_config_data));
}
void emac_process_link_check(void)
{
if (emac_config.emac_status != EMAC_RUNTIME_START) {
return;
}
if (emac_config.emac_phy_check_link()) {
if (!emac_config.phy_link_up) {
emac_process_link_updown(true);
}
} else {
if (emac_config.phy_link_up) {
emac_process_link_updown(false);
}
}
}
void emac_link_check_func(void *pv_parameters)
{
emac_post(SIG_EMAC_CHECK_LINK, 0);
}
static bool emac_link_check_timer_init(void)
{
emac_timer = xTimerCreate("emac_timer",
(CONFIG_EMAC_CHECK_LINK_PERIOD_MS /
portTICK_PERIOD_MS),
pdTRUE,
NULL,
emac_link_check_func);
if (emac_timer == NULL) {
return false;
} else {
return true;
}
}
static bool emac_link_check_timer_start(void)
{
if (xTimerStart(emac_timer, portMAX_DELAY) != pdPASS) {
return false;
} else {
return true;
}
}
static bool emac_link_check_timer_stop(void)
{
if (xTimerStop(emac_timer, portMAX_DELAY) != pdPASS) {
return false;
} else {
return true;
}
}
static bool emac_link_check_timer_delete(void)
{
xTimerDelete(emac_timer, portMAX_DELAY);
emac_timer = NULL;
return true;
}
static void emac_start(void *param)
{
struct emac_post_cmd *post_cmd = (struct emac_post_cmd *)param;
struct emac_open_cmd *cmd = (struct emac_open_cmd *)(post_cmd->cmd);
ESP_LOGD(TAG, "emac start");
cmd->err = EMAC_CMD_OK;
emac_enable_clk(true);
if (emac_reset() != ESP_OK) {
return;
}
emac_reset_dma_chain();
emac_dma_init();
emac_set_macaddr_reg();
emac_set_tx_base_reg();
emac_set_rx_base_reg();
emac_mac_init();
//ptp TODO
emac_enable_intr();
emac_config.emac_status = EMAC_RUNTIME_START;
system_event_t evt;
evt.event_id = SYSTEM_EVENT_ETH_START;
esp_event_send(&evt);
//set a timer to check link up status
if (emac_link_check_timer_init() == true) {
if (emac_link_check_timer_start() != true) {
cmd->err = EMAC_CMD_FAIL;
emac_link_check_timer_delete();
}
} else {
cmd->err = EMAC_CMD_FAIL;
}
if (post_cmd->post_type == EMAC_POST_SYNC) {
xSemaphoreGive(emac_g_sem);
}
ESP_LOGD(TAG, "emac start success");
}
esp_err_t esp_eth_enable(void)
{
struct emac_post_cmd post_cmd;
struct emac_open_cmd open_cmd;
post_cmd.cmd = (void *)(&open_cmd);
open_cmd.err = EMAC_CMD_OK;
if (emac_config.emac_status == EMAC_RUNTIME_START) {
open_cmd.err = EMAC_CMD_OK;
return open_cmd.err;
}
#ifdef CONFIG_PM_ENABLE
esp_err_t err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "ethernet", &s_pm_lock);
if (err != ESP_OK) {
return err;
}
esp_pm_lock_acquire(s_pm_lock);
#endif //CONFIG_PM_ENABLE
/* init phy device */
if (emac_config.phy_init() != ESP_OK) {
ESP_LOGE(TAG, "Initialise PHY device Timeout");
return ESP_FAIL;
}
if (emac_config.emac_status != EMAC_RUNTIME_NOT_INIT) {
if (emac_ioctl(SIG_EMAC_START, (emac_par_t)(&post_cmd)) != 0) {
open_cmd.err = EMAC_CMD_FAIL;
goto cleanup;
}
} else {
open_cmd.err = EMAC_CMD_FAIL;
goto cleanup;
}
return EMAC_CMD_OK;
cleanup:
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_release(s_pm_lock);
esp_pm_lock_delete(s_pm_lock);
s_pm_lock = NULL;
#endif //CONFIG_PM_ENABLE
return open_cmd.err;
}
static void emac_stop(void *param)
{
struct emac_post_cmd *post_cmd = (struct emac_post_cmd *)param;
ESP_LOGD(TAG, "emac stop");
emac_link_check_timer_stop();
emac_link_check_timer_delete();
emac_process_link_updown(false);
emac_disable_intr();
emac_enable_clk(false);
emac_config.emac_status = EMAC_RUNTIME_STOP;
system_event_t evt;
evt.event_id = SYSTEM_EVENT_ETH_STOP;
esp_event_send(&evt);
if (post_cmd->post_type == EMAC_POST_SYNC) {
xSemaphoreGive(emac_g_sem);
}
ESP_LOGD(TAG, "emac stop success");
}
esp_err_t esp_eth_disable(void)
{
struct emac_post_cmd post_cmd;
struct emac_close_cmd close_cmd;
post_cmd.cmd = (void *)(&close_cmd);
close_cmd.err = EMAC_CMD_OK;
if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
close_cmd.err = EMAC_CMD_OK;
return close_cmd.err;
}
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_release(s_pm_lock);
esp_pm_lock_delete(s_pm_lock);
s_pm_lock = NULL;
#endif // CONFIG_PM_ENABLE
if (emac_config.emac_status == EMAC_RUNTIME_START) {
if (emac_ioctl(SIG_EMAC_STOP, (emac_par_t)(&post_cmd)) != 0) {
close_cmd.err = EMAC_CMD_FAIL;
}
} else {
close_cmd.err = EMAC_CMD_FAIL;
}
return close_cmd.err;
}
static esp_err_t emac_ioctl(emac_sig_t sig, emac_par_t par)
{
esp_err_t ret = ESP_OK;
struct emac_post_cmd *post_cmd = (struct emac_post_cmd *)par;
xTaskHandle task_hdl = xTaskGetCurrentTaskHandle();
if (emac_task_hdl != task_hdl) {
post_cmd->post_type = EMAC_POST_SYNC;
if (emac_post(sig, par) != ESP_OK) {
ret = ESP_FAIL;
return ret;
};
if (xSemaphoreTake(emac_g_sem, portMAX_DELAY) == pdTRUE) {
return ret;
}
} else {
post_cmd->post_type = EMAC_POST_ASYNC;
switch (sig) {
case SIG_EMAC_RX_DONE:
emac_process_rx();
break;
case SIG_EMAC_TX_DONE:
emac_process_tx();
break;
case SIG_EMAC_START:
emac_start((void *)par);
break;
case SIG_EMAC_STOP:
emac_stop((void *)par);
break;
default:
ESP_LOGE(TAG, "unexpect sig %d", sig);
break;
}
}
return ret;
}
void emac_task(void *pv)
{
emac_event_t e;
for (;;) {
if (xQueueReceive(emac_xqueue, &e, portMAX_DELAY) == pdTRUE) {
portENTER_CRITICAL(&g_emac_mux);
emac_sig_cnt[e.sig]--;
portEXIT_CRITICAL(&g_emac_mux);
switch (e.sig) {
case SIG_EMAC_RX_DONE:
emac_process_rx();
break;
case SIG_EMAC_RX_UNAVAIL:
emac_process_rx_unavail();
break;
case SIG_EMAC_TX_DONE:
emac_process_tx();
break;
case SIG_EMAC_START:
emac_start((void *)e.par);
break;
case SIG_EMAC_STOP:
emac_stop((void *)e.par);
break;
case SIG_EMAC_CHECK_LINK:
emac_process_link_check();
break;
default:
ESP_LOGE(TAG, "unexpect sig %d", e.sig);
break;
}
}
}
}
esp_err_t IRAM_ATTR emac_post(emac_sig_t sig, emac_par_t par)
{
if (sig <= SIG_EMAC_RX_DONE) {
if (emac_sig_cnt[sig]) {
return ESP_OK;
} else {
emac_sig_cnt[sig]++;
emac_event_t evt;
signed portBASE_TYPE ret;
evt.sig = sig;
evt.par = par;
portBASE_TYPE tmp;
ret = xQueueSendFromISR(emac_xqueue, &evt, &tmp);
if (tmp != pdFALSE) {
portYIELD_FROM_ISR();
}
if (ret != pdPASS) {
return ESP_FAIL;
}
}
} else {
portENTER_CRITICAL(&g_emac_mux);
emac_sig_cnt[sig]++;
portEXIT_CRITICAL(&g_emac_mux);
emac_event_t evt;
evt.sig = sig;
evt.par = par;
if (xQueueSend(emac_xqueue, &evt, 10 / portTICK_PERIOD_MS) != pdTRUE) {
return ESP_FAIL;
}
}
return ESP_OK;
}
esp_err_t esp_eth_init(eth_config_t *config)
{
/* dynamically alloc memory for ethernet dma */
for (int i = 0; i < DMA_RX_BUF_NUM; i++) {
emac_dma_rx_chain_buf[i] = (struct dma_extended_desc *)heap_caps_malloc(sizeof(struct dma_extended_desc), MALLOC_CAP_DMA);
emac_dma_rx_buf[i] = (uint8_t *)heap_caps_malloc(DMA_RX_BUF_SIZE, MALLOC_CAP_DMA);
}
for (int i = 0; i < DMA_TX_BUF_NUM; i++) {
emac_dma_tx_chain_buf[i] = (struct dma_extended_desc *)heap_caps_malloc(sizeof(struct dma_extended_desc), MALLOC_CAP_DMA);
emac_dma_tx_buf[i] = (uint8_t *)heap_caps_malloc(DMA_TX_BUF_SIZE, MALLOC_CAP_DMA);
}
esp_event_set_default_eth_handlers();
return esp_eth_init_internal(config);
}
esp_err_t esp_eth_init_internal(eth_config_t *config)
{
esp_err_t ret = ESP_OK;
if (emac_config.emac_status != EMAC_RUNTIME_NOT_INIT) {
goto _exit;
}
emac_init_default_data();
if (config != NULL) {
emac_set_user_config_data(config);
}
ret = emac_verify_args();
if (ret != ESP_OK) {
goto _exit;
}
emac_config.emac_phy_power_enable(true);
//before set emac reg must enable clk
periph_module_enable(PERIPH_EMAC_MODULE);
if (emac_config.clock_mode != ETH_CLOCK_GPIO0_IN) {
// 50 MHz = 40MHz * (6 + 4) / (2 * (2 + 2) = 400MHz / 8
rtc_clk_apll_enable(1, 0, 0, 6, 2);
REG_SET_FIELD(EMAC_EX_CLKOUT_CONF_REG, EMAC_EX_CLK_OUT_H_DIV_NUM, 0);
REG_SET_FIELD(EMAC_EX_CLKOUT_CONF_REG, EMAC_EX_CLK_OUT_DIV_NUM, 0);
if (emac_config.clock_mode == ETH_CLOCK_GPIO16_OUT) {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO16_U, FUNC_GPIO16_EMAC_CLK_OUT);
ESP_LOGD(TAG, "EMAC 50MHz clock output on GPIO16");
} else if (emac_config.clock_mode == ETH_CLOCK_GPIO17_OUT) {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO17_U,
FUNC_GPIO17_EMAC_CLK_OUT_180);
ESP_LOGD(TAG, "EMAC 50MHz inverted clock output on GPIO17");
}
}
emac_enable_clk(true);
REG_SET_FIELD(EMAC_EX_PHYINF_CONF_REG, EMAC_EX_PHY_INTF_SEL,
EMAC_EX_PHY_INTF_RMII);
emac_dma_init();
if (emac_config.clock_mode == ETH_CLOCK_GPIO0_IN) {
// external clock on GPIO0
REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_EXT_OSC_EN);
REG_CLR_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_INT_OSC_EN);
REG_SET_BIT(EMAC_EX_OSCCLK_CONF_REG, EMAC_EX_OSC_CLK_SEL);
ESP_LOGD(TAG, "External clock input 50MHz on GPIO0");
if (emac_config.mac_mode == ETH_MODE_MII) {
REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_MII_CLK_RX_EN);
REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_MII_CLK_TX_EN);
}
} else {
// internal clock by APLL
REG_CLR_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_EXT_OSC_EN);
REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_INT_OSC_EN);
REG_CLR_BIT(EMAC_EX_OSCCLK_CONF_REG, EMAC_EX_OSC_CLK_SEL);
}
emac_config.emac_gpio_config();
emac_hw_init();
emac_macaddr_init();
//watchdog TODO
//init task for emac
emac_g_sem = xSemaphoreCreateBinary();
emac_rx_xMutex = xSemaphoreCreateRecursiveMutex();
emac_tx_xMutex = xSemaphoreCreateRecursiveMutex();
emac_xqueue = xQueueCreate(EMAC_EVT_QNUM, sizeof(emac_event_t));
xTaskCreate(emac_task, "emacT", EMAC_TASK_STACK_SIZE, NULL,
EMAC_TASK_PRIORITY, &emac_task_hdl);
esp_intr_alloc(ETS_ETH_MAC_INTR_SOURCE, 0, emac_process_intr, NULL, NULL);
emac_config.emac_status = EMAC_RUNTIME_INIT;
_exit:
return ret;
}
esp_err_t esp_eth_deinit(void)
{
esp_err_t ret = ESP_FAIL;
if (!emac_task_hdl) {
ret = ESP_ERR_INVALID_STATE;
goto _exit;
}
vTaskDelete(emac_task_hdl);
emac_task_hdl = NULL;
vQueueDelete(emac_xqueue);
vSemaphoreDelete(emac_tx_xMutex);
vSemaphoreDelete(emac_rx_xMutex);
vSemaphoreDelete(emac_g_sem);
emac_reset_dma_chain();
emac_config.emac_phy_power_enable(false);
periph_module_disable(PERIPH_EMAC_MODULE);
emac_config.emac_status = EMAC_RUNTIME_NOT_INIT;
/* free memory that dynamically allocted */
for (int i = 0; i < DMA_RX_BUF_NUM; i++) {
free(emac_dma_rx_chain_buf[i]);
free(emac_dma_rx_buf[i]);
emac_dma_rx_chain_buf[i] = NULL;
emac_dma_rx_buf[i] = NULL;
}
for (int i = 0; i < DMA_TX_BUF_NUM; i++) {
free(emac_dma_tx_chain_buf[i]);
free(emac_dma_tx_buf[i]);
emac_dma_tx_chain_buf[i] = NULL;
emac_dma_tx_buf[i] = NULL;
}
ret = ESP_OK;
_exit:
return ret;
}