| /* |
| * 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); |