ethdev: fix xstat name of basic stats per queue
[dpdk.git] / lib / librte_ethdev / rte_ethdev.c
index 72aed59..b54af39 100644 (file)
@@ -38,6 +38,7 @@
 #include <rte_kvargs.h>
 #include <rte_class.h>
 #include <rte_ether.h>
+#include <rte_telemetry.h>
 
 #include "rte_ethdev_trace.h"
 #include "rte_ethdev.h"
@@ -45,8 +46,6 @@
 #include "ethdev_profile.h"
 #include "ethdev_private.h"
 
-int rte_eth_dev_logtype;
-
 static const char *MZ_RTE_ETH_DEV_DATA = "rte_eth_dev_data";
 struct rte_eth_dev rte_eth_devices[RTE_MAX_ETHPORTS];
 
@@ -161,6 +160,7 @@ static const struct {
        RTE_TX_OFFLOAD_BIT2STR(UDP_TNL_TSO),
        RTE_TX_OFFLOAD_BIT2STR(IP_TNL_TSO),
        RTE_TX_OFFLOAD_BIT2STR(OUTER_UDP_CKSUM),
+       RTE_TX_OFFLOAD_BIT2STR(SEND_ON_TIMESTAMP),
 };
 
 #undef RTE_TX_OFFLOAD_BIT2STR
@@ -278,7 +278,7 @@ end:
 
 error:
        if (ret == -ENOTSUP)
-               RTE_LOG(ERR, EAL, "Bus %s does not support iterating.\n",
+               RTE_ETHDEV_LOG(ERR, "Bus %s does not support iterating.\n",
                                iter->bus->name);
        free(devargs.args);
        free(bus_str);
@@ -500,6 +500,7 @@ rte_eth_dev_allocate(const char *name)
        strlcpy(eth_dev->data->name, name, sizeof(eth_dev->data->name));
        eth_dev->data->port_id = port_id;
        eth_dev->data->mtu = RTE_ETHER_MTU;
+       pthread_mutex_init(&eth_dev->data->flow_ops_mutex, NULL);
 
 unlock:
        rte_spinlock_unlock(&rte_eth_dev_shared_data->ownership_lock);
@@ -549,12 +550,14 @@ rte_eth_dev_release_port(struct rte_eth_dev *eth_dev)
        rte_eth_dev_shared_data_prepare();
 
        if (eth_dev->state != RTE_ETH_DEV_UNUSED)
-               _rte_eth_dev_callback_process(eth_dev,
+               rte_eth_dev_callback_process(eth_dev,
                                RTE_ETH_EVENT_DESTROY, NULL);
 
        rte_spinlock_lock(&rte_eth_dev_shared_data->ownership_lock);
 
        eth_dev->state = RTE_ETH_DEV_UNUSED;
+       eth_dev->device = NULL;
+       eth_dev->intr_handle = NULL;
 
        if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
                rte_free(eth_dev->data->rx_queues);
@@ -562,6 +565,7 @@ rte_eth_dev_release_port(struct rte_eth_dev *eth_dev)
                rte_free(eth_dev->data->mac_addrs);
                rte_free(eth_dev->data->hash_mac_addrs);
                rte_free(eth_dev->data->dev_private);
+               pthread_mutex_destroy(&eth_dev->data->flow_ops_mutex);
                memset(eth_dev->data, 0, sizeof(struct rte_eth_dev_data));
        }
 
@@ -1099,6 +1103,8 @@ rte_eth_speed_bitflag(uint32_t speed, int duplex)
                return ETH_LINK_SPEED_56G;
        case ETH_SPEED_NUM_100G:
                return ETH_LINK_SPEED_100G;
+       case ETH_SPEED_NUM_200G:
+               return ETH_LINK_SPEED_200G;
        default:
                return 0;
        }
@@ -1484,7 +1490,7 @@ rollback:
 }
 
 void
-_rte_eth_dev_reset(struct rte_eth_dev *dev)
+rte_eth_dev_internal_reset(struct rte_eth_dev *dev)
 {
        if (dev->data->dev_started) {
                RTE_ETHDEV_LOG(ERR, "Port %u must be stopped to allow reset\n",
@@ -1716,22 +1722,7 @@ rte_eth_dev_close(uint16_t port_id)
        (*dev->dev_ops->dev_close)(dev);
 
        rte_ethdev_trace_close(port_id);
-       /* check behaviour flag - temporary for PMD migration */
-       if ((dev->data->dev_flags & RTE_ETH_DEV_CLOSE_REMOVE) != 0) {
-               /* new behaviour: send event + reset state + free all data */
-               rte_eth_dev_release_port(dev);
-               return;
-       }
-       RTE_ETHDEV_LOG(DEBUG, "Port closing is using an old behaviour.\n"
-                       "The driver %s should migrate to the new behaviour.\n",
-                       dev->device->driver->name);
-       /* old behaviour: only free queue arrays */
-       dev->data->nb_rx_queues = 0;
-       rte_free(dev->data->rx_queues);
-       dev->data->rx_queues = NULL;
-       dev->data->nb_tx_queues = 0;
-       rte_free(dev->data->tx_queues);
-       dev->data->tx_queues = NULL;
+       rte_eth_dev_release_port(dev);
 }
 
 int
@@ -1819,7 +1810,7 @@ rte_eth_rx_queue_setup(uint16_t port_id, uint16_t rx_queue_id,
        }
        mbp_buf_size = rte_pktmbuf_data_room_size(mp);
 
-       if ((mbp_buf_size - RTE_PKTMBUF_HEADROOM) < dev_info.min_rx_bufsize) {
+       if (mbp_buf_size < dev_info.min_rx_bufsize + RTE_PKTMBUF_HEADROOM) {
                RTE_ETHDEV_LOG(ERR,
                        "%s mbuf_data_room_size %d < %d (RTE_PKTMBUF_HEADROOM=%d + min_rx_bufsize(dev)=%d)\n",
                        mp->name, (int)mbp_buf_size,
@@ -2177,10 +2168,7 @@ void
 rte_eth_tx_buffer_drop_callback(struct rte_mbuf **pkts, uint16_t unsent,
                void *userdata __rte_unused)
 {
-       unsigned i;
-
-       for (i = 0; i < unsent; i++)
-               rte_pktmbuf_free(pkts[i]);
+       rte_pktmbuf_free_bulk(pkts, unsent);
 }
 
 void
@@ -2188,11 +2176,8 @@ rte_eth_tx_buffer_count_callback(struct rte_mbuf **pkts, uint16_t unsent,
                void *userdata)
 {
        uint64_t *count = userdata;
-       unsigned i;
-
-       for (i = 0; i < unsent; i++)
-               rte_pktmbuf_free(pkts[i]);
 
+       rte_pktmbuf_free_bulk(pkts, unsent);
        *count += unsent;
 }
 
@@ -2382,6 +2367,43 @@ rte_eth_link_get_nowait(uint16_t port_id, struct rte_eth_link *eth_link)
        return 0;
 }
 
+const char *
+rte_eth_link_speed_to_str(uint32_t link_speed)
+{
+       switch (link_speed) {
+       case ETH_SPEED_NUM_NONE: return "None";
+       case ETH_SPEED_NUM_10M:  return "10 Mbps";
+       case ETH_SPEED_NUM_100M: return "100 Mbps";
+       case ETH_SPEED_NUM_1G:   return "1 Gbps";
+       case ETH_SPEED_NUM_2_5G: return "2.5 Gbps";
+       case ETH_SPEED_NUM_5G:   return "5 Gbps";
+       case ETH_SPEED_NUM_10G:  return "10 Gbps";
+       case ETH_SPEED_NUM_20G:  return "20 Gbps";
+       case ETH_SPEED_NUM_25G:  return "25 Gbps";
+       case ETH_SPEED_NUM_40G:  return "40 Gbps";
+       case ETH_SPEED_NUM_50G:  return "50 Gbps";
+       case ETH_SPEED_NUM_56G:  return "56 Gbps";
+       case ETH_SPEED_NUM_100G: return "100 Gbps";
+       case ETH_SPEED_NUM_200G: return "200 Gbps";
+       case ETH_SPEED_NUM_UNKNOWN: return "Unknown";
+       default: return "Invalid";
+       }
+}
+
+int
+rte_eth_link_to_str(char *str, size_t len, const struct rte_eth_link *eth_link)
+{
+       if (eth_link->link_status == ETH_LINK_DOWN)
+               return snprintf(str, len, "Link down");
+       else
+               return snprintf(str, len, "Link up at %s %s %s",
+                       rte_eth_link_speed_to_str(eth_link->link_speed),
+                       (eth_link->link_duplex == ETH_LINK_FULL_DUPLEX) ?
+                       "FDX" : "HDX",
+                       (eth_link->link_autoneg == ETH_LINK_AUTONEG) ?
+                       "Autoneg" : "Fixed");
+}
+
 int
 rte_eth_stats_get(uint16_t port_id, struct rte_eth_stats *stats)
 {
@@ -2523,7 +2545,7 @@ rte_eth_basic_stats_get_names(struct rte_eth_dev *dev,
                for (idx = 0; idx < RTE_NB_RXQ_STATS; idx++) {
                        snprintf(xstats_names[cnt_used_entries].name,
                                sizeof(xstats_names[0].name),
-                               "rx_q%u%s",
+                               "rx_q%u_%s",
                                id_queue, rte_rxq_stats_strings[idx].name);
                        cnt_used_entries++;
                }
@@ -2534,7 +2556,7 @@ rte_eth_basic_stats_get_names(struct rte_eth_dev *dev,
                for (idx = 0; idx < RTE_NB_TXQ_STATS; idx++) {
                        snprintf(xstats_names[cnt_used_entries].name,
                                sizeof(xstats_names[0].name),
-                               "tx_q%u%s",
+                               "tx_q%u_%s",
                                id_queue, rte_txq_stats_strings[idx].name);
                        cnt_used_entries++;
                }
@@ -3257,12 +3279,14 @@ rte_eth_dev_set_vlan_ether_type(uint16_t port_id,
 int
 rte_eth_dev_set_vlan_offload(uint16_t port_id, int offload_mask)
 {
+       struct rte_eth_dev_info dev_info;
        struct rte_eth_dev *dev;
        int ret = 0;
        int mask = 0;
        int cur, org = 0;
        uint64_t orig_offloads;
        uint64_t dev_offloads;
+       uint64_t new_offloads;
 
        RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
        dev = &rte_eth_devices[port_id];
@@ -3316,6 +3340,22 @@ rte_eth_dev_set_vlan_offload(uint16_t port_id, int offload_mask)
        if (mask == 0)
                return ret;
 
+       ret = rte_eth_dev_info_get(port_id, &dev_info);
+       if (ret != 0)
+               return ret;
+
+       /* Rx VLAN offloading must be within its device capabilities */
+       if ((dev_offloads & dev_info.rx_offload_capa) != dev_offloads) {
+               new_offloads = dev_offloads & ~orig_offloads;
+               RTE_ETHDEV_LOG(ERR,
+                       "Ethdev port_id=%u requested new added VLAN offloads "
+                       "0x%" PRIx64 " must be within Rx offloads capabilities "
+                       "0x%" PRIx64 " in %s()\n",
+                       port_id, new_offloads, dev_info.rx_offload_capa,
+                       __func__);
+               return -EINVAL;
+       }
+
        RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->vlan_offload_set, -ENOTSUP);
        dev->data->dev_conf.rxmode.offloads = dev_offloads;
        ret = (*dev->dev_ops->vlan_offload_set)(dev, mask);
@@ -3622,6 +3662,50 @@ rte_eth_led_off(uint16_t port_id)
        return eth_err(port_id, (*dev->dev_ops->dev_led_off)(dev));
 }
 
+int
+rte_eth_fec_get_capability(uint16_t port_id,
+                          struct rte_eth_fec_capa *speed_fec_capa,
+                          unsigned int num)
+{
+       struct rte_eth_dev *dev;
+       int ret;
+
+       if (speed_fec_capa == NULL && num > 0)
+               return -EINVAL;
+
+       RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+       dev = &rte_eth_devices[port_id];
+       RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->fec_get_capability, -ENOTSUP);
+       ret = (*dev->dev_ops->fec_get_capability)(dev, speed_fec_capa, num);
+
+       return ret;
+}
+
+int
+rte_eth_fec_get(uint16_t port_id, uint32_t *fec_capa)
+{
+       struct rte_eth_dev *dev;
+
+       if (fec_capa == NULL)
+               return -EINVAL;
+
+       RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+       dev = &rte_eth_devices[port_id];
+       RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->fec_get, -ENOTSUP);
+       return eth_err(port_id, (*dev->dev_ops->fec_get)(dev, fec_capa));
+}
+
+int
+rte_eth_fec_set(uint16_t port_id, uint32_t fec_capa)
+{
+       struct rte_eth_dev *dev;
+
+       RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+       dev = &rte_eth_devices[port_id];
+       RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->fec_set, -ENOTSUP);
+       return eth_err(port_id, (*dev->dev_ops->fec_set)(dev, fec_capa));
+}
+
 /*
  * Returns index into MAC address array of addr. Use 00:00:00:00:00:00 to find
  * an empty spot.
@@ -4070,7 +4154,7 @@ rte_eth_dev_callback_unregister(uint16_t port_id,
 }
 
 int
-_rte_eth_dev_callback_process(struct rte_eth_dev *dev,
+rte_eth_dev_callback_process(struct rte_eth_dev *dev,
        enum rte_eth_event_type event, void *ret_param)
 {
        struct rte_eth_dev_callback *cb_lst;
@@ -4102,7 +4186,7 @@ rte_eth_dev_probing_finish(struct rte_eth_dev *dev)
        if (dev == NULL)
                return;
 
-       _rte_eth_dev_callback_process(dev, RTE_ETH_EVENT_NEW, NULL);
+       rte_eth_dev_callback_process(dev, RTE_ETH_EVENT_NEW, NULL);
 
        dev->state = RTE_ETH_DEV_ATTACHED;
 }
@@ -4181,6 +4265,14 @@ rte_eth_dev_rx_intr_ctl_q_get_fd(uint16_t port_id, uint16_t queue_id)
        return fd;
 }
 
+static inline int
+eth_dma_mzone_name(char *name, size_t len, uint16_t port_id, uint16_t queue_id,
+               const char *ring_name)
+{
+       return snprintf(name, len, "eth_p%d_q%d_%s",
+                       port_id, queue_id, ring_name);
+}
+
 const struct rte_memzone *
 rte_eth_dma_zone_reserve(const struct rte_eth_dev *dev, const char *ring_name,
                         uint16_t queue_id, size_t size, unsigned align,
@@ -4190,8 +4282,8 @@ rte_eth_dma_zone_reserve(const struct rte_eth_dev *dev, const char *ring_name,
        const struct rte_memzone *mz;
        int rc;
 
-       rc = snprintf(z_name, sizeof(z_name), "eth_p%d_q%d_%s",
-                     dev->data->port_id, queue_id, ring_name);
+       rc = eth_dma_mzone_name(z_name, sizeof(z_name), dev->data->port_id,
+                       queue_id, ring_name);
        if (rc >= RTE_MEMZONE_NAMESIZE) {
                RTE_ETHDEV_LOG(ERR, "ring name too long\n");
                rte_errno = ENAMETOOLONG;
@@ -4199,13 +4291,47 @@ rte_eth_dma_zone_reserve(const struct rte_eth_dev *dev, const char *ring_name,
        }
 
        mz = rte_memzone_lookup(z_name);
-       if (mz)
+       if (mz) {
+               if ((socket_id != SOCKET_ID_ANY && socket_id != mz->socket_id) ||
+                               size > mz->len ||
+                               ((uintptr_t)mz->addr & (align - 1)) != 0) {
+                       RTE_ETHDEV_LOG(ERR,
+                               "memzone %s does not justify the requested attributes\n",
+                               mz->name);
+                       return NULL;
+               }
+
                return mz;
+       }
 
        return rte_memzone_reserve_aligned(z_name, size, socket_id,
                        RTE_MEMZONE_IOVA_CONTIG, align);
 }
 
+int
+rte_eth_dma_zone_free(const struct rte_eth_dev *dev, const char *ring_name,
+               uint16_t queue_id)
+{
+       char z_name[RTE_MEMZONE_NAMESIZE];
+       const struct rte_memzone *mz;
+       int rc = 0;
+
+       rc = eth_dma_mzone_name(z_name, sizeof(z_name), dev->data->port_id,
+                       queue_id, ring_name);
+       if (rc >= RTE_MEMZONE_NAMESIZE) {
+               RTE_ETHDEV_LOG(ERR, "ring name too long\n");
+               return -ENAMETOOLONG;
+       }
+
+       mz = rte_memzone_lookup(z_name);
+       if (mz)
+               rc = rte_memzone_free(mz);
+       else
+               rc = -ENOENT;
+
+       return rc;
+}
+
 int
 rte_eth_dev_create(struct rte_device *device, const char *name,
        size_t priv_data_size,
@@ -4229,7 +4355,8 @@ rte_eth_dev_create(struct rte_device *device, const char *name,
                                device->numa_node);
 
                        if (!ethdev->data->dev_private) {
-                               RTE_LOG(ERR, EAL, "failed to allocate private data");
+                               RTE_ETHDEV_LOG(ERR,
+                                       "failed to allocate private data\n");
                                retval = -ENOMEM;
                                goto probe_failed;
                        }
@@ -4237,8 +4364,8 @@ rte_eth_dev_create(struct rte_device *device, const char *name,
        } else {
                ethdev = rte_eth_dev_attach_secondary(name);
                if (!ethdev) {
-                       RTE_LOG(ERR, EAL, "secondary process attach failed, "
-                               "ethdev doesn't exist");
+                       RTE_ETHDEV_LOG(ERR,
+                               "secondary process attach failed, ethdev doesn't exist\n");
                        return  -ENODEV;
                }
        }
@@ -4248,15 +4375,15 @@ rte_eth_dev_create(struct rte_device *device, const char *name,
        if (ethdev_bus_specific_init) {
                retval = ethdev_bus_specific_init(ethdev, bus_init_params);
                if (retval) {
-                       RTE_LOG(ERR, EAL,
-                               "ethdev bus specific initialisation failed");
+                       RTE_ETHDEV_LOG(ERR,
+                               "ethdev bus specific initialisation failed\n");
                        goto probe_failed;
                }
        }
 
        retval = ethdev_init(ethdev, init_params);
        if (retval) {
-               RTE_LOG(ERR, EAL, "ethdev initialisation failed");
+               RTE_ETHDEV_LOG(ERR, "ethdev initialisation failed\n");
                goto probe_failed;
        }
 
@@ -4607,6 +4734,15 @@ rte_eth_rx_queue_info_get(uint16_t port_id, uint16_t queue_id,
                return -EINVAL;
        }
 
+       if (dev->data->rx_queues == NULL ||
+                       dev->data->rx_queues[queue_id] == NULL) {
+               RTE_ETHDEV_LOG(ERR,
+                              "Rx queue %"PRIu16" of device with port_id=%"
+                              PRIu16" has not been setup\n",
+                              queue_id, port_id);
+               return -EINVAL;
+       }
+
        if (rte_eth_dev_is_rx_hairpin_queue(dev, queue_id)) {
                RTE_ETHDEV_LOG(INFO,
                        "Can't get hairpin Rx queue %"PRIu16" info of device with port_id=%"PRIu16"\n",
@@ -4638,6 +4774,15 @@ rte_eth_tx_queue_info_get(uint16_t port_id, uint16_t queue_id,
                return -EINVAL;
        }
 
+       if (dev->data->tx_queues == NULL ||
+                       dev->data->tx_queues[queue_id] == NULL) {
+               RTE_ETHDEV_LOG(ERR,
+                              "Tx queue %"PRIu16" of device with port_id=%"
+                              PRIu16" has not been setup\n",
+                              queue_id, port_id);
+               return -EINVAL;
+       }
+
        if (rte_eth_dev_is_tx_hairpin_queue(dev, queue_id)) {
                RTE_ETHDEV_LOG(INFO,
                        "Can't get hairpin Tx queue %"PRIu16" info of device with port_id=%"PRIu16"\n",
@@ -5199,9 +5344,169 @@ parse_cleanup:
        return result;
 }
 
-RTE_INIT(ethdev_init_log)
+static int
+handle_port_list(const char *cmd __rte_unused,
+               const char *params __rte_unused,
+               struct rte_tel_data *d)
+{
+       int port_id;
+
+       rte_tel_data_start_array(d, RTE_TEL_INT_VAL);
+       RTE_ETH_FOREACH_DEV(port_id)
+               rte_tel_data_add_array_int(d, port_id);
+       return 0;
+}
+
+static void
+add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
+               const char *stat_name)
+{
+       int q;
+       struct rte_tel_data *q_data = rte_tel_data_alloc();
+       rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
+       for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
+               rte_tel_data_add_array_u64(q_data, q_stats[q]);
+       rte_tel_data_add_dict_container(d, stat_name, q_data, 0);
+}
+
+#define ADD_DICT_STAT(stats, s) rte_tel_data_add_dict_u64(d, #s, stats.s)
+
+static int
+handle_port_stats(const char *cmd __rte_unused,
+               const char *params,
+               struct rte_tel_data *d)
+{
+       struct rte_eth_stats stats;
+       int port_id, ret;
+
+       if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+               return -1;
+
+       port_id = atoi(params);
+       if (!rte_eth_dev_is_valid_port(port_id))
+               return -1;
+
+       ret = rte_eth_stats_get(port_id, &stats);
+       if (ret < 0)
+               return -1;
+
+       rte_tel_data_start_dict(d);
+       ADD_DICT_STAT(stats, ipackets);
+       ADD_DICT_STAT(stats, opackets);
+       ADD_DICT_STAT(stats, ibytes);
+       ADD_DICT_STAT(stats, obytes);
+       ADD_DICT_STAT(stats, imissed);
+       ADD_DICT_STAT(stats, ierrors);
+       ADD_DICT_STAT(stats, oerrors);
+       ADD_DICT_STAT(stats, rx_nombuf);
+       add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
+       add_port_queue_stats(d, stats.q_opackets, "q_opackets");
+       add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
+       add_port_queue_stats(d, stats.q_obytes, "q_obytes");
+       add_port_queue_stats(d, stats.q_errors, "q_errors");
+
+       return 0;
+}
+
+static int
+handle_port_xstats(const char *cmd __rte_unused,
+               const char *params,
+               struct rte_tel_data *d)
+{
+       struct rte_eth_xstat *eth_xstats;
+       struct rte_eth_xstat_name *xstat_names;
+       int port_id, num_xstats;
+       int i, ret;
+       char *end_param;
+
+       if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+               return -1;
+
+       port_id = strtoul(params, &end_param, 0);
+       if (*end_param != '\0')
+               RTE_ETHDEV_LOG(NOTICE,
+                       "Extra parameters passed to ethdev telemetry command, ignoring");
+       if (!rte_eth_dev_is_valid_port(port_id))
+               return -1;
+
+       num_xstats = rte_eth_xstats_get(port_id, NULL, 0);
+       if (num_xstats < 0)
+               return -1;
+
+       /* use one malloc for both names and stats */
+       eth_xstats = malloc((sizeof(struct rte_eth_xstat) +
+                       sizeof(struct rte_eth_xstat_name)) * num_xstats);
+       if (eth_xstats == NULL)
+               return -1;
+       xstat_names = (void *)&eth_xstats[num_xstats];
+
+       ret = rte_eth_xstats_get_names(port_id, xstat_names, num_xstats);
+       if (ret < 0 || ret > num_xstats) {
+               free(eth_xstats);
+               return -1;
+       }
+
+       ret = rte_eth_xstats_get(port_id, eth_xstats, num_xstats);
+       if (ret < 0 || ret > num_xstats) {
+               free(eth_xstats);
+               return -1;
+       }
+
+       rte_tel_data_start_dict(d);
+       for (i = 0; i < num_xstats; i++)
+               rte_tel_data_add_dict_u64(d, xstat_names[i].name,
+                               eth_xstats[i].value);
+       return 0;
+}
+
+static int
+handle_port_link_status(const char *cmd __rte_unused,
+               const char *params,
+               struct rte_tel_data *d)
+{
+       static const char *status_str = "status";
+       int ret, port_id;
+       struct rte_eth_link link;
+       char *end_param;
+
+       if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+               return -1;
+
+       port_id = strtoul(params, &end_param, 0);
+       if (*end_param != '\0')
+               RTE_ETHDEV_LOG(NOTICE,
+                       "Extra parameters passed to ethdev telemetry command, ignoring");
+       if (!rte_eth_dev_is_valid_port(port_id))
+               return -1;
+
+       ret = rte_eth_link_get(port_id, &link);
+       if (ret < 0)
+               return -1;
+
+       rte_tel_data_start_dict(d);
+       if (!link.link_status) {
+               rte_tel_data_add_dict_string(d, status_str, "DOWN");
+               return 0;
+       }
+       rte_tel_data_add_dict_string(d, status_str, "UP");
+       rte_tel_data_add_dict_u64(d, "speed", link.link_speed);
+       rte_tel_data_add_dict_string(d, "duplex",
+                       (link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
+                               "full-duplex" : "half-duplex");
+       return 0;
+}
+
+RTE_LOG_REGISTER(rte_eth_dev_logtype, lib.ethdev, INFO);
+
+RTE_INIT(ethdev_init_telemetry)
 {
-       rte_eth_dev_logtype = rte_log_register("lib.ethdev");
-       if (rte_eth_dev_logtype >= 0)
-               rte_log_set_level(rte_eth_dev_logtype, RTE_LOG_INFO);
+       rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
+                       "Returns list of available ethdev ports. Takes no parameters");
+       rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
+                       "Returns the common stats for a port. Parameters: int port_id");
+       rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
+                       "Returns the extended stats for a port. Parameters: int port_id");
+       rte_telemetry_register_cmd("/ethdev/link_status",
+                       handle_port_link_status,
+                       "Returns the link status for a port. Parameters: int port_id");
 }