618 lines
14 KiB
C
618 lines
14 KiB
C
/*
|
|
* Copyright (c) 2018 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
*
|
|
* Ethernet driver for native posix board. This is meant for network
|
|
* connectivity between host and Zephyr.
|
|
*/
|
|
|
|
#define LOG_MODULE_NAME eth_posix
|
|
#define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include <cmdline.h>
|
|
#include <posix_native_task.h>
|
|
|
|
#include <zephyr/net/net_pkt.h>
|
|
#include <zephyr/net/net_core.h>
|
|
#include <zephyr/net/net_if.h>
|
|
#include <zephyr/net/ethernet.h>
|
|
#include <ethernet/eth_stats.h>
|
|
|
|
#include <zephyr/drivers/ptp_clock.h>
|
|
#include <zephyr/net/gptp.h>
|
|
#include <zephyr/net/lldp.h>
|
|
|
|
#include "eth_native_posix_priv.h"
|
|
#include "nsi_host_trampolines.h"
|
|
#include "eth.h"
|
|
|
|
#define NET_BUF_TIMEOUT K_MSEC(100)
|
|
|
|
#if defined(CONFIG_NET_VLAN)
|
|
#define ETH_HDR_LEN sizeof(struct net_eth_vlan_hdr)
|
|
#else
|
|
#define ETH_HDR_LEN sizeof(struct net_eth_hdr)
|
|
#endif
|
|
|
|
struct eth_context {
|
|
uint8_t recv[NET_ETH_MTU + ETH_HDR_LEN];
|
|
uint8_t send[NET_ETH_MTU + ETH_HDR_LEN];
|
|
uint8_t mac_addr[6];
|
|
struct net_linkaddr ll_addr;
|
|
struct net_if *iface;
|
|
const char *if_name;
|
|
k_tid_t rx_thread;
|
|
struct z_thread_stack_element *rx_stack;
|
|
size_t rx_stack_size;
|
|
int dev_fd;
|
|
bool init_done;
|
|
bool status;
|
|
bool promisc_mode;
|
|
|
|
#if defined(CONFIG_NET_STATISTICS_ETHERNET)
|
|
struct net_stats_eth stats;
|
|
#endif
|
|
#if defined(CONFIG_ETH_NATIVE_POSIX_PTP_CLOCK)
|
|
const struct device *ptp_clock;
|
|
#endif
|
|
};
|
|
|
|
static const char *if_name_cmd_opt;
|
|
|
|
#define DEFINE_RX_THREAD(x, _) \
|
|
K_KERNEL_STACK_DEFINE(rx_thread_stack_##x, \
|
|
CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE);\
|
|
static struct k_thread rx_thread_data_##x
|
|
|
|
LISTIFY(CONFIG_ETH_NATIVE_POSIX_INTERFACE_COUNT, DEFINE_RX_THREAD, (;), _);
|
|
|
|
#if defined(CONFIG_NET_GPTP)
|
|
static bool need_timestamping(struct gptp_hdr *hdr)
|
|
{
|
|
switch (hdr->message_type) {
|
|
case GPTP_SYNC_MESSAGE:
|
|
case GPTP_PATH_DELAY_RESP_MESSAGE:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static struct gptp_hdr *check_gptp_msg(struct net_if *iface,
|
|
struct net_pkt *pkt,
|
|
bool is_tx)
|
|
{
|
|
uint8_t *msg_start = net_pkt_data(pkt);
|
|
struct gptp_hdr *gptp_hdr;
|
|
int eth_hlen;
|
|
struct net_eth_hdr *hdr;
|
|
|
|
hdr = (struct net_eth_hdr *)msg_start;
|
|
if (ntohs(hdr->type) != NET_ETH_PTYPE_PTP) {
|
|
return NULL;
|
|
}
|
|
|
|
eth_hlen = sizeof(struct net_eth_hdr);
|
|
|
|
/* In TX, the first net_buf contains the Ethernet header
|
|
* and the actual gPTP header is in the second net_buf.
|
|
* In RX, the Ethernet header + other headers are in the
|
|
* first net_buf.
|
|
*/
|
|
if (is_tx) {
|
|
if (pkt->frags->frags == NULL) {
|
|
return false;
|
|
}
|
|
|
|
gptp_hdr = (struct gptp_hdr *)pkt->frags->frags->data;
|
|
} else {
|
|
gptp_hdr = (struct gptp_hdr *)(pkt->frags->data + eth_hlen);
|
|
}
|
|
|
|
return gptp_hdr;
|
|
}
|
|
|
|
static void update_pkt_priority(struct gptp_hdr *hdr, struct net_pkt *pkt)
|
|
{
|
|
if (GPTP_IS_EVENT_MSG(hdr->message_type)) {
|
|
net_pkt_set_priority(pkt, NET_PRIORITY_CA);
|
|
} else {
|
|
net_pkt_set_priority(pkt, NET_PRIORITY_IC);
|
|
}
|
|
}
|
|
|
|
static void update_gptp(struct net_if *iface, struct net_pkt *pkt,
|
|
bool send)
|
|
{
|
|
struct net_ptp_time timestamp;
|
|
struct gptp_hdr *hdr;
|
|
int ret;
|
|
|
|
ret = eth_clock_gettime(×tamp.second, ×tamp.nanosecond);
|
|
if (ret < 0) {
|
|
return;
|
|
}
|
|
|
|
net_pkt_set_timestamp(pkt, ×tamp);
|
|
|
|
hdr = check_gptp_msg(iface, pkt, send);
|
|
if (!hdr) {
|
|
return;
|
|
}
|
|
|
|
if (send) {
|
|
ret = need_timestamping(hdr);
|
|
if (ret) {
|
|
net_if_add_tx_timestamp(pkt);
|
|
}
|
|
} else {
|
|
update_pkt_priority(hdr, pkt);
|
|
}
|
|
}
|
|
#else
|
|
#define update_gptp(iface, pkt, send)
|
|
#endif /* CONFIG_NET_GPTP */
|
|
|
|
static int eth_send(const struct device *dev, struct net_pkt *pkt)
|
|
{
|
|
struct eth_context *ctx = dev->data;
|
|
int count = net_pkt_get_len(pkt);
|
|
int ret;
|
|
|
|
ret = net_pkt_read(pkt, ctx->send, count);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
update_gptp(net_pkt_iface(pkt), pkt, true);
|
|
|
|
LOG_DBG("Send pkt %p len %d", pkt, count);
|
|
|
|
ret = nsi_host_write(ctx->dev_fd, ctx->send, count);
|
|
if (ret < 0) {
|
|
LOG_DBG("Cannot send pkt %p (%d)", pkt, ret);
|
|
}
|
|
|
|
return ret < 0 ? ret : 0;
|
|
}
|
|
|
|
static struct net_linkaddr *eth_get_mac(struct eth_context *ctx)
|
|
{
|
|
ctx->ll_addr.addr = ctx->mac_addr;
|
|
ctx->ll_addr.len = sizeof(ctx->mac_addr);
|
|
|
|
return &ctx->ll_addr;
|
|
}
|
|
|
|
static struct net_pkt *prepare_pkt(struct eth_context *ctx,
|
|
int count, int *status)
|
|
{
|
|
struct net_pkt *pkt;
|
|
|
|
pkt = net_pkt_rx_alloc_with_buffer(ctx->iface, count,
|
|
AF_UNSPEC, 0, NET_BUF_TIMEOUT);
|
|
if (!pkt) {
|
|
*status = -ENOMEM;
|
|
return NULL;
|
|
}
|
|
|
|
if (net_pkt_write(pkt, ctx->recv, count)) {
|
|
net_pkt_unref(pkt);
|
|
*status = -ENOBUFS;
|
|
return NULL;
|
|
}
|
|
|
|
*status = 0;
|
|
|
|
LOG_DBG("Recv pkt %p len %d", pkt, count);
|
|
|
|
return pkt;
|
|
}
|
|
|
|
static int read_data(struct eth_context *ctx, int fd)
|
|
{
|
|
struct net_if *iface = ctx->iface;
|
|
struct net_pkt *pkt = NULL;
|
|
int status;
|
|
int count;
|
|
|
|
count = nsi_host_read(fd, ctx->recv, sizeof(ctx->recv));
|
|
if (count <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
pkt = prepare_pkt(ctx, count, &status);
|
|
if (!pkt) {
|
|
return status;
|
|
}
|
|
|
|
update_gptp(iface, pkt, false);
|
|
|
|
if (net_recv_data(iface, pkt) < 0) {
|
|
net_pkt_unref(pkt);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void eth_rx(void *p1, void *p2, void *p3)
|
|
{
|
|
ARG_UNUSED(p2);
|
|
ARG_UNUSED(p3);
|
|
|
|
struct eth_context *ctx = p1;
|
|
LOG_DBG("Starting ZETH RX thread");
|
|
|
|
while (1) {
|
|
if (net_if_is_up(ctx->iface)) {
|
|
while (!eth_wait_data(ctx->dev_fd)) {
|
|
read_data(ctx, ctx->dev_fd);
|
|
k_yield();
|
|
}
|
|
}
|
|
|
|
k_sleep(K_MSEC(CONFIG_ETH_NATIVE_POSIX_RX_TIMEOUT));
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_THREAD_MAX_NAME_LEN)
|
|
#define THREAD_MAX_NAME_LEN CONFIG_THREAD_MAX_NAME_LEN
|
|
#else
|
|
#define THREAD_MAX_NAME_LEN 1
|
|
#endif
|
|
|
|
static void create_rx_handler(struct eth_context *ctx)
|
|
{
|
|
k_thread_create(ctx->rx_thread,
|
|
ctx->rx_stack,
|
|
ctx->rx_stack_size,
|
|
eth_rx,
|
|
ctx, NULL, NULL, K_PRIO_COOP(14),
|
|
0, K_NO_WAIT);
|
|
|
|
if (IS_ENABLED(CONFIG_THREAD_NAME)) {
|
|
char name[THREAD_MAX_NAME_LEN];
|
|
|
|
snprintk(name, sizeof(name), "eth_native_posix_rx-%s",
|
|
ctx->if_name);
|
|
k_thread_name_set(ctx->rx_thread, name);
|
|
}
|
|
}
|
|
|
|
static void eth_iface_init(struct net_if *iface)
|
|
{
|
|
struct eth_context *ctx = net_if_get_device(iface)->data;
|
|
struct net_linkaddr *ll_addr = eth_get_mac(ctx);
|
|
|
|
ctx->iface = iface;
|
|
|
|
ethernet_init(iface);
|
|
|
|
if (ctx->init_done) {
|
|
return;
|
|
}
|
|
|
|
net_lldp_set_lldpdu(iface);
|
|
|
|
ctx->init_done = true;
|
|
|
|
#if defined(CONFIG_ETH_NATIVE_POSIX_RANDOM_MAC)
|
|
/* 00-00-5E-00-53-xx Documentation RFC 7042 */
|
|
gen_random_mac(ctx->mac_addr, 0x00, 0x00, 0x5E);
|
|
|
|
ctx->mac_addr[3] = 0x00;
|
|
ctx->mac_addr[4] = 0x53;
|
|
|
|
/* The TUN/TAP setup script will by default set the MAC address of host
|
|
* interface to 00:00:5E:00:53:FF so do not allow that.
|
|
*/
|
|
if (ctx->mac_addr[5] == 0xff) {
|
|
ctx->mac_addr[5] = 0x01;
|
|
}
|
|
#else
|
|
/* Difficult to configure MAC addresses any sane way if we have more
|
|
* than one network interface.
|
|
*/
|
|
BUILD_ASSERT(CONFIG_ETH_NATIVE_POSIX_INTERFACE_COUNT == 1,
|
|
"Cannot have static MAC if interface count > 1");
|
|
|
|
if (CONFIG_ETH_NATIVE_POSIX_MAC_ADDR[0] != 0) {
|
|
if (net_bytes_from_str(ctx->mac_addr, sizeof(ctx->mac_addr),
|
|
CONFIG_ETH_NATIVE_POSIX_MAC_ADDR) < 0) {
|
|
LOG_ERR("Invalid MAC address %s",
|
|
CONFIG_ETH_NATIVE_POSIX_MAC_ADDR);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* If we have only one network interface, then use the name
|
|
* defined in the Kconfig directly. This way there is no need to
|
|
* change the documentation etc. and break things.
|
|
*/
|
|
if (CONFIG_ETH_NATIVE_POSIX_INTERFACE_COUNT == 1) {
|
|
ctx->if_name = CONFIG_ETH_NATIVE_POSIX_DRV_NAME;
|
|
}
|
|
|
|
if (if_name_cmd_opt != NULL) {
|
|
ctx->if_name = if_name_cmd_opt;
|
|
}
|
|
|
|
LOG_DBG("Interface %p using \"%s\"", iface, ctx->if_name);
|
|
|
|
net_if_set_link_addr(iface, ll_addr->addr, ll_addr->len,
|
|
NET_LINK_ETHERNET);
|
|
|
|
ctx->dev_fd = eth_iface_create(CONFIG_ETH_NATIVE_POSIX_DEV_NAME, ctx->if_name, false);
|
|
if (ctx->dev_fd < 0) {
|
|
LOG_ERR("Cannot create %s (%d/%s)", ctx->if_name, ctx->dev_fd,
|
|
strerror(-ctx->dev_fd));
|
|
} else {
|
|
/* Create a thread that will handle incoming data from host */
|
|
create_rx_handler(ctx);
|
|
}
|
|
}
|
|
|
|
static
|
|
enum ethernet_hw_caps eth_posix_native_get_capabilities(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
return ETHERNET_TXTIME
|
|
#if defined(CONFIG_NET_VLAN)
|
|
| ETHERNET_HW_VLAN
|
|
#endif
|
|
#if defined(CONFIG_ETH_NATIVE_POSIX_VLAN_TAG_STRIP)
|
|
| ETHERNET_HW_VLAN_TAG_STRIP
|
|
#endif
|
|
#if defined(CONFIG_ETH_NATIVE_POSIX_PTP_CLOCK)
|
|
| ETHERNET_PTP
|
|
#endif
|
|
#if defined(CONFIG_NET_PROMISCUOUS_MODE)
|
|
| ETHERNET_PROMISC_MODE
|
|
#endif
|
|
#if defined(CONFIG_NET_LLDP)
|
|
| ETHERNET_LLDP
|
|
#endif
|
|
;
|
|
}
|
|
|
|
#if defined(CONFIG_ETH_NATIVE_POSIX_PTP_CLOCK)
|
|
static const struct device *eth_get_ptp_clock(const struct device *dev)
|
|
{
|
|
struct eth_context *context = dev->data;
|
|
|
|
return context->ptp_clock;
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_NET_STATISTICS_ETHERNET)
|
|
static struct net_stats_eth *get_stats(const struct device *dev)
|
|
{
|
|
struct eth_context *context = dev->data;
|
|
|
|
return &(context->stats);
|
|
}
|
|
#endif
|
|
|
|
static int set_config(const struct device *dev,
|
|
enum ethernet_config_type type,
|
|
const struct ethernet_config *config)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (IS_ENABLED(CONFIG_NET_PROMISCUOUS_MODE) &&
|
|
type == ETHERNET_CONFIG_TYPE_PROMISC_MODE) {
|
|
struct eth_context *context = dev->data;
|
|
|
|
if (config->promisc_mode) {
|
|
if (context->promisc_mode) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
context->promisc_mode = true;
|
|
} else {
|
|
if (!context->promisc_mode) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
context->promisc_mode = false;
|
|
}
|
|
|
|
ret = eth_promisc_mode(context->if_name,
|
|
context->promisc_mode);
|
|
} else if (type == ETHERNET_CONFIG_TYPE_MAC_ADDRESS) {
|
|
struct eth_context *context = dev->data;
|
|
|
|
memcpy(context->mac_addr, config->mac_address.addr,
|
|
sizeof(context->mac_addr));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(CONFIG_NET_VLAN)
|
|
static int vlan_setup(const struct device *dev, struct net_if *iface,
|
|
uint16_t tag, bool enable)
|
|
{
|
|
if (enable) {
|
|
net_lldp_set_lldpdu(iface);
|
|
} else {
|
|
net_lldp_unset_lldpdu(iface);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_NET_VLAN */
|
|
|
|
static const struct ethernet_api eth_if_api = {
|
|
.iface_api.init = eth_iface_init,
|
|
|
|
.get_capabilities = eth_posix_native_get_capabilities,
|
|
.set_config = set_config,
|
|
.send = eth_send,
|
|
|
|
#if defined(CONFIG_NET_VLAN)
|
|
.vlan_setup = vlan_setup,
|
|
#endif
|
|
#if defined(CONFIG_NET_STATISTICS_ETHERNET)
|
|
.get_stats = get_stats,
|
|
#endif
|
|
#if defined(CONFIG_ETH_NATIVE_POSIX_PTP_CLOCK)
|
|
.get_ptp_clock = eth_get_ptp_clock,
|
|
#endif
|
|
};
|
|
|
|
#define DEFINE_ETH_DEV_DATA(x, _) \
|
|
static struct eth_context eth_context_data_##x = { \
|
|
.if_name = CONFIG_ETH_NATIVE_POSIX_DRV_NAME #x, \
|
|
.rx_thread = &rx_thread_data_##x, \
|
|
.rx_stack = rx_thread_stack_##x, \
|
|
.rx_stack_size = K_KERNEL_STACK_SIZEOF(rx_thread_stack_##x), \
|
|
}
|
|
|
|
LISTIFY(CONFIG_ETH_NATIVE_POSIX_INTERFACE_COUNT, DEFINE_ETH_DEV_DATA, (;), _);
|
|
|
|
#define DEFINE_ETH_DEVICE(x, _) \
|
|
ETH_NET_DEVICE_INIT(eth_native_posix_##x, \
|
|
CONFIG_ETH_NATIVE_POSIX_DRV_NAME #x, \
|
|
NULL, NULL, ð_context_data_##x, NULL, \
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
|
|
ð_if_api, \
|
|
NET_ETH_MTU)
|
|
|
|
LISTIFY(CONFIG_ETH_NATIVE_POSIX_INTERFACE_COUNT, DEFINE_ETH_DEVICE, (;), _);
|
|
|
|
#if defined(CONFIG_ETH_NATIVE_POSIX_PTP_CLOCK)
|
|
|
|
#if defined(CONFIG_NET_GPTP)
|
|
BUILD_ASSERT( \
|
|
CONFIG_ETH_NATIVE_POSIX_INTERFACE_COUNT == CONFIG_NET_GPTP_NUM_PORTS, \
|
|
"Number of network interfaces must match gPTP port count");
|
|
#endif
|
|
|
|
struct ptp_context {
|
|
struct eth_context *eth_context;
|
|
};
|
|
|
|
#define DEFINE_PTP_DEV_DATA(x, _) \
|
|
static struct ptp_context ptp_context_##x
|
|
|
|
LISTIFY(CONFIG_ETH_NATIVE_POSIX_INTERFACE_COUNT, DEFINE_PTP_DEV_DATA, (;), _);
|
|
|
|
static int ptp_clock_set_native_posix(const struct device *clk,
|
|
struct net_ptp_time *tm)
|
|
{
|
|
ARG_UNUSED(clk);
|
|
ARG_UNUSED(tm);
|
|
|
|
/* We cannot set the host device time so this function
|
|
* does nothing.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ptp_clock_get_native_posix(const struct device *clk,
|
|
struct net_ptp_time *tm)
|
|
{
|
|
ARG_UNUSED(clk);
|
|
|
|
return eth_clock_gettime(&tm->second, &tm->nanosecond);
|
|
}
|
|
|
|
static int ptp_clock_adjust_native_posix(const struct device *clk,
|
|
int increment)
|
|
{
|
|
ARG_UNUSED(clk);
|
|
ARG_UNUSED(increment);
|
|
|
|
/* We cannot adjust the host device time so this function
|
|
* does nothing.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ptp_clock_rate_adjust_native_posix(const struct device *clk,
|
|
double ratio)
|
|
{
|
|
ARG_UNUSED(clk);
|
|
ARG_UNUSED(ratio);
|
|
|
|
/* We cannot adjust the host device time so this function
|
|
* does nothing.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct ptp_clock_driver_api api = {
|
|
.set = ptp_clock_set_native_posix,
|
|
.get = ptp_clock_get_native_posix,
|
|
.adjust = ptp_clock_adjust_native_posix,
|
|
.rate_adjust = ptp_clock_rate_adjust_native_posix,
|
|
};
|
|
|
|
#define PTP_INIT_FUNC(x, _) \
|
|
static int ptp_init_##x(const struct device *port) \
|
|
{ \
|
|
const struct device *const eth_dev = DEVICE_GET(eth_native_posix_##x); \
|
|
struct eth_context *context = eth_dev->data; \
|
|
struct ptp_context *ptp_context = port->data; \
|
|
\
|
|
context->ptp_clock = port; \
|
|
ptp_context->eth_context = context; \
|
|
\
|
|
return 0; \
|
|
}
|
|
|
|
LISTIFY(CONFIG_ETH_NATIVE_POSIX_INTERFACE_COUNT, PTP_INIT_FUNC, (), _)
|
|
|
|
#define DEFINE_PTP_DEVICE(x, _) \
|
|
DEVICE_DEFINE(eth_native_posix_ptp_clock_##x, \
|
|
PTP_CLOCK_NAME "_" #x, \
|
|
ptp_init_##x, \
|
|
NULL, \
|
|
&ptp_context_##x, \
|
|
NULL, \
|
|
POST_KERNEL, \
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
|
|
&api)
|
|
|
|
LISTIFY(CONFIG_ETH_NATIVE_POSIX_INTERFACE_COUNT, DEFINE_PTP_DEVICE, (;), _);
|
|
|
|
#endif /* CONFIG_ETH_NATIVE_POSIX_PTP_CLOCK */
|
|
|
|
static void add_native_posix_options(void)
|
|
{
|
|
static struct args_struct_t eth_native_posix_options[] = {
|
|
{
|
|
.is_mandatory = false,
|
|
.option = "eth-if",
|
|
.name = "name",
|
|
.type = 's',
|
|
.dest = (void *)&if_name_cmd_opt,
|
|
.descript = "Name of the eth interface to use",
|
|
},
|
|
ARG_TABLE_ENDMARKER,
|
|
};
|
|
|
|
native_add_command_line_opts(eth_native_posix_options);
|
|
}
|
|
|
|
NATIVE_TASK(add_native_posix_options, PRE_BOOT_1, 10);
|