You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
690 lines
16 KiB
690 lines
16 KiB
/* |
|
* Copyright (c) 2018 Intel Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
/** |
|
* @file |
|
* |
|
* TAP Ethernet driver for the native_sim board. This is meant for network |
|
* connectivity between the host and Zephyr. |
|
* |
|
* Note this driver is divided in two files. This one, built in the embedded code context, |
|
* with whichever libC is used in that context, and eth_native_tap_adapt.c built with the host |
|
* libC. |
|
*/ |
|
|
|
#define LOG_MODULE_NAME eth_tap |
|
#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_tap_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_TAP_PTP_CLOCK) |
|
const struct device *ptp_clock; |
|
#endif |
|
}; |
|
|
|
static const char *if_name_cmd_opt; |
|
static const char *mac_addr_cmd_opt; |
|
#ifdef CONFIG_NET_IPV4 |
|
static const char *ipv4_addr_cmd_opt; |
|
static const char *ipv4_nm_cmd_opt; |
|
static const char *ipv4_gw_cmd_opt; |
|
#endif |
|
|
|
|
|
#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_TAP_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 *ghdr; |
|
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; |
|
} |
|
|
|
ghdr = (struct gptp_hdr *)pkt->frags->frags->data; |
|
} else { |
|
ghdr = (struct gptp_hdr *)(pkt->frags->data + eth_hlen); |
|
} |
|
|
|
return ghdr; |
|
} |
|
|
|
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) |
|
{ |
|
(void)net_linkaddr_set(&ctx->ll_addr, ctx->mac_addr, |
|
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_TAP_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_tap_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; |
|
#if !defined(CONFIG_ETH_NATIVE_TAP_RANDOM_MAC) |
|
const char *mac_addr = |
|
mac_addr_cmd_opt ? mac_addr_cmd_opt : CONFIG_ETH_NATIVE_TAP_MAC_ADDR; |
|
#endif |
|
#ifdef CONFIG_NET_IPV4 |
|
struct in_addr addr, netmask; |
|
#endif |
|
|
|
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_TAP_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_TAP_INTERFACE_COUNT == 1, |
|
"Cannot have static MAC if interface count > 1"); |
|
|
|
if (mac_addr[0] != 0) { |
|
if (net_bytes_from_str(ctx->mac_addr, sizeof(ctx->mac_addr), mac_addr) < 0) { |
|
LOG_ERR("Invalid MAC address %s", mac_addr); |
|
} |
|
} |
|
#endif |
|
|
|
ll_addr = eth_get_mac(ctx); |
|
|
|
/* 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_TAP_INTERFACE_COUNT == 1) { |
|
ctx->if_name = CONFIG_ETH_NATIVE_TAP_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); |
|
|
|
#ifdef CONFIG_NET_IPV4 |
|
if (ipv4_addr_cmd_opt != NULL) { |
|
if (net_addr_pton(AF_INET, ipv4_addr_cmd_opt, &addr) == 0) { |
|
net_if_ipv4_addr_add(iface, &addr, NET_ADDR_MANUAL, 0); |
|
|
|
if (ipv4_nm_cmd_opt != NULL) { |
|
if (net_addr_pton(AF_INET, ipv4_nm_cmd_opt, &netmask) == 0) { |
|
net_if_ipv4_set_netmask_by_addr(iface, &addr, &netmask); |
|
} else { |
|
NET_ERR("Invalid netmask: %s", ipv4_nm_cmd_opt); |
|
} |
|
} |
|
} else { |
|
NET_ERR("Invalid address: %s", ipv4_addr_cmd_opt); |
|
} |
|
} |
|
|
|
if (ipv4_gw_cmd_opt != NULL) { |
|
if (net_addr_pton(AF_INET, ipv4_gw_cmd_opt, &addr) == 0) { |
|
net_if_ipv4_set_gw(iface, &addr); |
|
} else { |
|
NET_ERR("Invalid gateway: %s", ipv4_gw_cmd_opt); |
|
} |
|
} |
|
#endif |
|
|
|
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_native_tap_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_TAP_VLAN_TAG_STRIP) |
|
| ETHERNET_HW_VLAN_TAG_STRIP |
|
#endif |
|
#if defined(CONFIG_ETH_NATIVE_TAP_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_TAP_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_native_tap_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_TAP_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_TAP_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_TAP_INTERFACE_COUNT, DEFINE_ETH_DEV_DATA, (;), _); |
|
|
|
#define DEFINE_ETH_DEVICE(x, _) \ |
|
ETH_NET_DEVICE_INIT(eth_native_tap_##x, \ |
|
CONFIG_ETH_NATIVE_TAP_DRV_NAME #x, \ |
|
NULL, NULL, ð_context_data_##x, NULL, \ |
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ |
|
ð_if_api, \ |
|
NET_ETH_MTU) |
|
|
|
LISTIFY(CONFIG_ETH_NATIVE_TAP_INTERFACE_COUNT, DEFINE_ETH_DEVICE, (;), _); |
|
|
|
#if defined(CONFIG_ETH_NATIVE_TAP_PTP_CLOCK) |
|
|
|
#if defined(CONFIG_NET_GPTP) |
|
BUILD_ASSERT( \ |
|
CONFIG_ETH_NATIVE_TAP_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_TAP_INTERFACE_COUNT, DEFINE_PTP_DEV_DATA, (;), _); |
|
|
|
static int ptp_clock_set_native_tap(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_tap(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_tap(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_tap(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 DEVICE_API(ptp_clock, api) = { |
|
.set = ptp_clock_set_native_tap, |
|
.get = ptp_clock_get_native_tap, |
|
.adjust = ptp_clock_adjust_native_tap, |
|
.rate_adjust = ptp_clock_rate_adjust_native_tap, |
|
}; |
|
|
|
#define PTP_INIT_FUNC(x, _) \ |
|
static int ptp_init_##x(const struct device *port) \ |
|
{ \ |
|
const struct device *const eth_dev = DEVICE_GET(eth_native_tap_##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_TAP_INTERFACE_COUNT, PTP_INIT_FUNC, (), _) |
|
|
|
#define DEFINE_PTP_DEVICE(x, _) \ |
|
DEVICE_DEFINE(eth_native_tap_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_TAP_INTERFACE_COUNT, DEFINE_PTP_DEVICE, (;), _); |
|
|
|
#endif /* CONFIG_ETH_NATIVE_TAP_PTP_CLOCK */ |
|
|
|
static void add_native_tap_options(void) |
|
{ |
|
static struct args_struct_t eth_native_tap_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", |
|
}, |
|
{ |
|
.is_mandatory = false, |
|
.option = "mac-addr", |
|
.name = "mac", |
|
.type = 's', |
|
.dest = (void *)&mac_addr_cmd_opt, |
|
.descript = "MAC address", |
|
}, |
|
#ifdef CONFIG_NET_IPV4 |
|
{ |
|
.is_mandatory = false, |
|
.option = "ipv4-addr", |
|
.name = "ipv4", |
|
.type = 's', |
|
.dest = (void *)&ipv4_addr_cmd_opt, |
|
.descript = "IPv4 address", |
|
}, |
|
{ |
|
.is_mandatory = false, |
|
.option = "ipv4-gw", |
|
.name = "ipv4", |
|
.type = 's', |
|
.dest = (void *)&ipv4_gw_cmd_opt, |
|
.descript = "IPv4 gateway", |
|
}, |
|
{ |
|
.is_mandatory = false, |
|
.option = "ipv4-nm", |
|
.name = "ipv4", |
|
.type = 's', |
|
.dest = (void *)&ipv4_nm_cmd_opt, |
|
.descript = "IPv4 netmask", |
|
}, |
|
#endif |
|
ARG_TABLE_ENDMARKER, |
|
}; |
|
|
|
native_add_command_line_opts(eth_native_tap_options); |
|
} |
|
|
|
NATIVE_TASK(add_native_tap_options, PRE_BOOT_1, 10);
|
|
|