321 lines
8.6 KiB
C
321 lines
8.6 KiB
C
/*
|
|
* Copyright 2022 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(nxp_s32_eth);
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/mbox.h>
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#include <zephyr/net/ethernet.h>
|
|
#include <zephyr/net/net_if.h>
|
|
#include <zephyr/net/net_pkt.h>
|
|
#include <zephyr/net/phy.h>
|
|
#include <ethernet/eth_stats.h>
|
|
|
|
#include <soc.h>
|
|
#include <Netc_Eth_Ip.h>
|
|
#include <Netc_Eth_Ip_Irq.h>
|
|
#include <Netc_EthSwt_Ip.h>
|
|
|
|
#include "eth.h"
|
|
#include "eth_nxp_s32_netc_priv.h"
|
|
|
|
/* Global MAC filter hash table required for the baremetal driver */
|
|
Netc_Eth_Ip_MACFilterHashTableEntryType * MACFilterHashTableAddrs[FEATURE_NETC_ETH_NUMBER_OF_CTRLS];
|
|
|
|
static void nxp_s32_eth_rx_thread(void *arg1, void *unused1, void *unused2);
|
|
|
|
static void nxp_s32_eth_msix_wrapper(const struct device *dev, uint32_t channel,
|
|
void *user_data, struct mbox_msg *msg)
|
|
{
|
|
const struct nxp_s32_eth_msix *msix = (const struct nxp_s32_eth_msix *)user_data;
|
|
|
|
ARG_UNUSED(dev);
|
|
ARG_UNUSED(msg);
|
|
|
|
/* Handler doesn't require any data to be passed, used only for signalling */
|
|
msix->handler(channel, NULL, 0);
|
|
}
|
|
|
|
static inline struct net_if *get_iface(struct nxp_s32_eth_data *ctx)
|
|
{
|
|
return ctx->iface;
|
|
}
|
|
|
|
int nxp_s32_eth_initialize_common(const struct device *dev)
|
|
{
|
|
const struct nxp_s32_eth_config *cfg = dev->config;
|
|
struct nxp_s32_eth_data *ctx = dev->data;
|
|
Netc_Eth_Ip_StatusType status;
|
|
const struct nxp_s32_eth_msix *msix;
|
|
int err;
|
|
|
|
/* Populate the MAC filter hash table addresses for this SI */
|
|
__ASSERT_NO_MSG(cfg->si_idx < FEATURE_NETC_ETH_NUMBER_OF_CTRLS);
|
|
MACFilterHashTableAddrs[cfg->si_idx] = cfg->mac_filter_hash_table;
|
|
|
|
status = Netc_Eth_Ip_Init(cfg->si_idx, &cfg->netc_cfg);
|
|
if (status != NETC_ETH_IP_STATUS_SUCCESS) {
|
|
LOG_ERR("Failed to initialize SI%d (%d)", cfg->si_idx, status);
|
|
return -EIO;
|
|
}
|
|
|
|
for (int i = 0; i < NETC_MSIX_EVENTS_COUNT; i++) {
|
|
msix = &cfg->msix[i];
|
|
if (mbox_is_ready_dt(&msix->mbox_spec)) {
|
|
err = mbox_register_callback_dt(&msix->mbox_spec,
|
|
nxp_s32_eth_msix_wrapper,
|
|
(void *)msix);
|
|
if (err != 0) {
|
|
LOG_ERR("Failed to register MRU callback on channel %u",
|
|
msix->mbox_spec.channel_id);
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
k_mutex_init(&ctx->tx_mutex);
|
|
k_sem_init(&ctx->rx_sem, 0, 1);
|
|
|
|
k_thread_create(&ctx->rx_thread, ctx->rx_thread_stack,
|
|
K_KERNEL_STACK_SIZEOF(ctx->rx_thread_stack),
|
|
nxp_s32_eth_rx_thread, (void *)dev, NULL, NULL,
|
|
K_PRIO_COOP(CONFIG_ETH_NXP_S32_RX_THREAD_PRIO),
|
|
0, K_NO_WAIT);
|
|
k_thread_name_set(&ctx->rx_thread, "nxp_s32_eth_rx");
|
|
|
|
status = Netc_Eth_Ip_EnableController(cfg->si_idx);
|
|
if (status != NETC_ETH_IP_STATUS_SUCCESS) {
|
|
LOG_ERR("Failed to enable ENETC SI%d (%d)", cfg->si_idx, status);
|
|
return -EIO;
|
|
}
|
|
|
|
if (cfg->generate_mac) {
|
|
cfg->generate_mac(&ctx->mac_addr[0]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void nxp_s32_eth_mcast_filter(const struct device *dev, const struct ethernet_filter *filter)
|
|
{
|
|
const struct nxp_s32_eth_config *cfg = dev->config;
|
|
Netc_Eth_Ip_StatusType status;
|
|
|
|
if (filter->set) {
|
|
status = Netc_Eth_Ip_AddMulticastDstAddrToHashFilter(cfg->si_idx,
|
|
filter->mac_address.addr);
|
|
} else {
|
|
status = Netc_Eth_Ip_RemoveMulticastDstAddrFromHashFilter(cfg->si_idx,
|
|
filter->mac_address.addr);
|
|
}
|
|
if (status != NETC_ETH_IP_STATUS_SUCCESS) {
|
|
LOG_ERR("Failed to update multicast hash table: %d", status);
|
|
}
|
|
}
|
|
|
|
int nxp_s32_eth_tx(const struct device *dev, struct net_pkt *pkt)
|
|
{
|
|
struct nxp_s32_eth_data *ctx = dev->data;
|
|
const struct nxp_s32_eth_config *cfg = dev->config;
|
|
size_t pkt_len = net_pkt_get_len(pkt);
|
|
int res = 0;
|
|
Netc_Eth_Ip_StatusType status;
|
|
Netc_Eth_Ip_BufferType buf;
|
|
|
|
__ASSERT(pkt, "Packet pointer is NULL");
|
|
|
|
k_mutex_lock(&ctx->tx_mutex, K_FOREVER);
|
|
|
|
buf.length = (uint16_t)pkt_len;
|
|
buf.data = NULL;
|
|
status = Netc_Eth_Ip_GetTxBuff(cfg->si_idx, cfg->tx_ring_idx, &buf, NULL);
|
|
if (status == NETC_ETH_IP_STATUS_TX_BUFF_BUSY) {
|
|
/* Reclaim the buffers already transmitted and try again */
|
|
Netc_Eth_Ip_ReleaseTxBuffers(cfg->si_idx, cfg->tx_ring_idx);
|
|
status = Netc_Eth_Ip_GetTxBuff(cfg->si_idx, cfg->tx_ring_idx, &buf, NULL);
|
|
}
|
|
if (status != NETC_ETH_IP_STATUS_SUCCESS) {
|
|
LOG_ERR("Failed to get tx buffer: %d", status);
|
|
res = -ENOBUFS;
|
|
goto error;
|
|
}
|
|
buf.length = (uint16_t)pkt_len;
|
|
|
|
res = net_pkt_read(pkt, buf.data, pkt_len);
|
|
if (res) {
|
|
LOG_ERR("Failed to copy packet to tx buffer: %d", res);
|
|
res = -ENOBUFS;
|
|
goto error;
|
|
}
|
|
|
|
status = Netc_Eth_Ip_SendFrame(cfg->si_idx, cfg->tx_ring_idx, &buf, NULL);
|
|
if (status != NETC_ETH_IP_STATUS_SUCCESS) {
|
|
LOG_ERR("Failed to tx frame: %d", status);
|
|
res = -EIO;
|
|
goto error;
|
|
}
|
|
|
|
error:
|
|
k_mutex_unlock(&ctx->tx_mutex);
|
|
|
|
if (res != 0) {
|
|
eth_stats_update_errors_tx(ctx->iface);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static struct net_pkt *nxp_s32_eth_get_pkt(const struct device *dev,
|
|
Netc_Eth_Ip_BufferType *buf)
|
|
{
|
|
struct nxp_s32_eth_data *ctx = dev->data;
|
|
struct net_pkt *pkt = NULL;
|
|
int res = 0;
|
|
|
|
/* Use root iface, it will be updated later in net_recv_data() */
|
|
pkt = net_pkt_rx_alloc_with_buffer(ctx->iface, buf->length,
|
|
AF_UNSPEC, 0, NETC_TIMEOUT);
|
|
if (!pkt) {
|
|
goto exit;
|
|
}
|
|
|
|
res = net_pkt_write(pkt, buf->data, buf->length);
|
|
if (res) {
|
|
net_pkt_unref(pkt);
|
|
pkt = NULL;
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
if (!pkt) {
|
|
eth_stats_update_errors_rx(get_iface(ctx));
|
|
}
|
|
|
|
return pkt;
|
|
}
|
|
|
|
static int nxp_s32_eth_rx(const struct device *dev)
|
|
{
|
|
struct nxp_s32_eth_data *ctx = dev->data;
|
|
const struct nxp_s32_eth_config *cfg = dev->config;
|
|
Netc_Eth_Ip_BufferType buf;
|
|
Netc_Eth_Ip_RxInfoType info;
|
|
Netc_Eth_Ip_StatusType status;
|
|
struct net_pkt *pkt;
|
|
int key;
|
|
int res = 0;
|
|
|
|
key = irq_lock();
|
|
status = Netc_Eth_Ip_ReadFrame(cfg->si_idx, cfg->rx_ring_idx, &buf, &info);
|
|
if (status == NETC_ETH_IP_STATUS_RX_QUEUE_EMPTY) {
|
|
res = -ENOBUFS;
|
|
} else if (status != NETC_ETH_IP_STATUS_SUCCESS) {
|
|
LOG_ERR("Error on received frame: %d (0x%X)", status, info.rxStatus);
|
|
res = -EIO;
|
|
} else {
|
|
pkt = nxp_s32_eth_get_pkt(dev, &buf);
|
|
Netc_Eth_Ip_ProvideRxBuff(cfg->si_idx, cfg->rx_ring_idx, &buf);
|
|
|
|
if (pkt != NULL) {
|
|
res = net_recv_data(get_iface(ctx), pkt);
|
|
if (res < 0) {
|
|
eth_stats_update_errors_rx(get_iface(ctx));
|
|
net_pkt_unref(pkt);
|
|
LOG_ERR("Failed to enqueue frame into rx queue: %d", res);
|
|
}
|
|
}
|
|
}
|
|
irq_unlock(key);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void nxp_s32_eth_rx_thread(void *arg1, void *unused1, void *unused2)
|
|
{
|
|
const struct device *dev = (const struct device *)arg1;
|
|
struct nxp_s32_eth_data *ctx = dev->data;
|
|
int res;
|
|
int work;
|
|
|
|
ARG_UNUSED(unused1);
|
|
ARG_UNUSED(unused2);
|
|
__ASSERT_NO_MSG(arg1 != NULL);
|
|
__ASSERT_NO_MSG(ctx != NULL);
|
|
|
|
while (1) {
|
|
res = k_sem_take(&ctx->rx_sem, K_FOREVER);
|
|
__ASSERT_NO_MSG(res == 0);
|
|
|
|
work = 0;
|
|
while (nxp_s32_eth_rx(dev) != -ENOBUFS) {
|
|
if (++work == CONFIG_ETH_NXP_S32_RX_BUDGET) {
|
|
/* more work to do, reschedule */
|
|
work = 0;
|
|
k_yield();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
enum ethernet_hw_caps nxp_s32_eth_get_capabilities(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
return (ETHERNET_LINK_10BASE_T
|
|
| ETHERNET_LINK_100BASE_T
|
|
| ETHERNET_LINK_1000BASE_T
|
|
| ETHERNET_HW_RX_CHKSUM_OFFLOAD
|
|
| ETHERNET_HW_FILTERING
|
|
#if defined(CONFIG_NET_VLAN)
|
|
| ETHERNET_HW_VLAN
|
|
#endif
|
|
#if defined(CONFIG_NET_PROMISCUOUS_MODE)
|
|
| ETHERNET_PROMISC_MODE
|
|
#endif
|
|
);
|
|
}
|
|
|
|
int nxp_s32_eth_set_config(const struct device *dev, enum ethernet_config_type type,
|
|
const struct ethernet_config *config)
|
|
{
|
|
struct nxp_s32_eth_data *ctx = dev->data;
|
|
const struct nxp_s32_eth_config *cfg = dev->config;
|
|
int res = 0;
|
|
|
|
switch (type) {
|
|
case ETHERNET_CONFIG_TYPE_MAC_ADDRESS:
|
|
/* Set new Ethernet MAC address and register it with the upper layer */
|
|
memcpy(ctx->mac_addr, config->mac_address.addr, sizeof(ctx->mac_addr));
|
|
Netc_Eth_Ip_SetMacAddr(cfg->si_idx, (const uint8_t *)ctx->mac_addr);
|
|
net_if_set_link_addr(ctx->iface, ctx->mac_addr, sizeof(ctx->mac_addr),
|
|
NET_LINK_ETHERNET);
|
|
LOG_INF("SI%d MAC set to: %02x:%02x:%02x:%02x:%02x:%02x", cfg->si_idx,
|
|
ctx->mac_addr[0], ctx->mac_addr[1], ctx->mac_addr[2],
|
|
ctx->mac_addr[3], ctx->mac_addr[4], ctx->mac_addr[5]);
|
|
break;
|
|
case ETHERNET_CONFIG_TYPE_FILTER:
|
|
nxp_s32_eth_mcast_filter(dev, &config->filter);
|
|
break;
|
|
default:
|
|
res = -ENOTSUP;
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
BUILD_ASSERT((CONFIG_ETH_NXP_S32_RX_RING_LEN % 8) == 0,
|
|
"Rx ring length must be multiple of 8");
|
|
BUILD_ASSERT((CONFIG_ETH_NXP_S32_TX_RING_LEN % 8) == 0,
|
|
"Tx ring length must be multiple of 8");
|
|
BUILD_ASSERT((CONFIG_ETH_NXP_S32_RX_RING_BUF_SIZE % 8) == 0,
|
|
"Rx ring data buffer size must be multiple of 8");
|
|
BUILD_ASSERT((CONFIG_ETH_NXP_S32_TX_RING_BUF_SIZE % 8) == 0,
|
|
"Tx ring data buffer size must be multiple of 8");
|