diff --git a/drivers/net/nfp/flower/nfp_flower.c b/drivers/net/nfp/flower/nfp_flower.c
index 5353daa47d..7b46dc0f6a 100644
--- a/drivers/net/nfp/flower/nfp_flower.c
+++ b/drivers/net/nfp/flower/nfp_flower.c
@@ -194,6 +194,8 @@ nfp_flower_pf_close(struct rte_eth_dev *dev)
 	pf_dev = hw->pf_dev;
 	app_fw_flower = NFP_PRIV_TO_APP_FW_FLOWER(pf_dev->app_fw_priv);
 
+	nfp_mtr_priv_uninit(pf_dev);
+
 	/*
 	 * We assume that the DPDK application is stopping all the
 	 * threads/queues before calling the device close function.
@@ -1097,13 +1099,19 @@ nfp_init_app_fw_flower(struct nfp_pf_dev *pf_dev)
 		goto app_cleanup;
 	}
 
+	ret = nfp_mtr_priv_init(pf_dev);
+	if (ret != 0) {
+		PMD_INIT_LOG(ERR, "Error initializing metering private data");
+		goto flow_priv_cleanup;
+	}
+
 	/* Allocate memory for the PF AND ctrl vNIC here (hence the * 2) */
 	pf_hw = rte_zmalloc_socket("nfp_pf_vnic", 2 * sizeof(struct nfp_net_adapter),
 			RTE_CACHE_LINE_SIZE, numa_node);
 	if (pf_hw == NULL) {
 		PMD_INIT_LOG(ERR, "Could not malloc nfp pf vnic");
 		ret = -ENOMEM;
-		goto flow_priv_cleanup;
+		goto mtr_priv_cleanup;
 	}
 
 	/* Map the PF ctrl bar */
@@ -1193,6 +1201,8 @@ nfp_init_app_fw_flower(struct nfp_pf_dev *pf_dev)
 	nfp_cpp_area_free(pf_dev->ctrl_area);
 vnic_cleanup:
 	rte_free(pf_hw);
+mtr_priv_cleanup:
+	nfp_mtr_priv_uninit(pf_dev);
 flow_priv_cleanup:
 	nfp_flow_priv_uninit(pf_dev);
 app_cleanup:
diff --git a/drivers/net/nfp/flower/nfp_flower.h b/drivers/net/nfp/flower/nfp_flower.h
index c05a761a95..0ce5f7bd9e 100644
--- a/drivers/net/nfp/flower/nfp_flower.h
+++ b/drivers/net/nfp/flower/nfp_flower.h
@@ -76,6 +76,7 @@ struct nfp_app_fw_flower {
 	uint64_t ext_features;
 
 	struct nfp_flow_priv *flow_priv;
+	struct nfp_mtr_priv *mtr_priv;
 };
 
 static inline bool
diff --git a/drivers/net/nfp/flower/nfp_flower_cmsg.c b/drivers/net/nfp/flower/nfp_flower_cmsg.c
index f04b9bd921..eaddd6c52c 100644
--- a/drivers/net/nfp/flower/nfp_flower_cmsg.c
+++ b/drivers/net/nfp/flower/nfp_flower_cmsg.c
@@ -467,3 +467,61 @@ nfp_flower_cmsg_tun_mac_rule(struct nfp_app_fw_flower *app_fw_flower,
 
 	return 0;
 }
+
+int
+nfp_flower_cmsg_qos_add(struct nfp_app_fw_flower *app_fw_flower,
+		struct nfp_profile_conf *conf)
+{
+	char *msg;
+	uint16_t cnt;
+	uint32_t len;
+	struct rte_mbuf *mbuf;
+
+	mbuf = rte_pktmbuf_alloc(app_fw_flower->ctrl_pktmbuf_pool);
+	if (mbuf == NULL) {
+		PMD_DRV_LOG(DEBUG, "Failed to alloc mbuf for qos add");
+		return -ENOMEM;
+	}
+
+	len = sizeof(struct nfp_profile_conf);
+	msg = nfp_flower_cmsg_init(mbuf, NFP_FLOWER_CMSG_TYPE_QOS_MOD, len);
+	rte_memcpy(msg, conf, len);
+
+	cnt = nfp_flower_ctrl_vnic_xmit(app_fw_flower, mbuf);
+	if (cnt == 0) {
+		PMD_DRV_LOG(ERR, "Send cmsg through ctrl vnic failed.");
+		rte_pktmbuf_free(mbuf);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int
+nfp_flower_cmsg_qos_delete(struct nfp_app_fw_flower *app_fw_flower,
+		struct nfp_profile_conf *conf)
+{
+	char *msg;
+	uint16_t cnt;
+	uint32_t len;
+	struct rte_mbuf *mbuf;
+
+	mbuf = rte_pktmbuf_alloc(app_fw_flower->ctrl_pktmbuf_pool);
+	if (mbuf == NULL) {
+		PMD_DRV_LOG(DEBUG, "Failed to alloc mbuf for qos delete");
+		return -ENOMEM;
+	}
+
+	len = sizeof(struct nfp_profile_conf);
+	msg = nfp_flower_cmsg_init(mbuf, NFP_FLOWER_CMSG_TYPE_QOS_DEL, len);
+	rte_memcpy(msg, conf, len);
+
+	cnt = nfp_flower_ctrl_vnic_xmit(app_fw_flower, mbuf);
+	if (cnt == 0) {
+		PMD_DRV_LOG(ERR, "Send cmsg through ctrl vnic failed.");
+		rte_pktmbuf_free(mbuf);
+		return -EIO;
+	}
+
+	return 0;
+}
diff --git a/drivers/net/nfp/flower/nfp_flower_cmsg.h b/drivers/net/nfp/flower/nfp_flower_cmsg.h
index bdfde70fbe..1894c454d3 100644
--- a/drivers/net/nfp/flower/nfp_flower_cmsg.h
+++ b/drivers/net/nfp/flower/nfp_flower_cmsg.h
@@ -9,6 +9,7 @@
 #include <rte_byteorder.h>
 #include <rte_ether.h>
 
+#include "../nfp_mtr.h"
 #include "../nfp_flow.h"
 
 struct nfp_flower_cmsg_hdr {
@@ -923,5 +924,9 @@ int nfp_flower_cmsg_tun_mac_rule(struct nfp_app_fw_flower *app_fw_flower,
 		struct rte_ether_addr *mac,
 		uint16_t mac_idx,
 		bool is_del);
+int nfp_flower_cmsg_qos_add(struct nfp_app_fw_flower *app_fw_flower,
+		struct nfp_profile_conf *conf);
+int nfp_flower_cmsg_qos_delete(struct nfp_app_fw_flower *app_fw_flower,
+		struct nfp_profile_conf *conf);
 
 #endif /* _NFP_CMSG_H_ */
diff --git a/drivers/net/nfp/flower/nfp_flower_representor.c b/drivers/net/nfp/flower/nfp_flower_representor.c
index 76be22efa2..362c67f7b5 100644
--- a/drivers/net/nfp/flower/nfp_flower_representor.c
+++ b/drivers/net/nfp/flower/nfp_flower_representor.c
@@ -553,6 +553,7 @@ static const struct eth_dev_ops nfp_flower_repr_dev_ops = {
 	.mac_addr_set         = nfp_flower_repr_mac_addr_set,
 
 	.flow_ops_get         = nfp_net_flow_ops_get,
+	.mtr_ops_get          = nfp_net_mtr_ops_get,
 };
 
 static uint32_t
diff --git a/drivers/net/nfp/meson.build b/drivers/net/nfp/meson.build
index 7416fd3706..fc8fd906bc 100644
--- a/drivers/net/nfp/meson.build
+++ b/drivers/net/nfp/meson.build
@@ -28,6 +28,7 @@ sources = files(
         'nfp_ethdev_vf.c',
         'nfp_ethdev.c',
         'nfp_flow.c',
+        'nfp_mtr.c',
 )
 
 deps += ['hash']
diff --git a/drivers/net/nfp/nfp_mtr.c b/drivers/net/nfp/nfp_mtr.c
new file mode 100644
index 0000000000..50efb56879
--- /dev/null
+++ b/drivers/net/nfp/nfp_mtr.c
@@ -0,0 +1,429 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2022 Corigine, Inc.
+ * All rights reserved.
+ */
+
+#include <rte_mtr_driver.h>
+#include <bus_pci_driver.h>
+#include <rte_malloc.h>
+
+#include "nfp_common.h"
+#include "nfp_mtr.h"
+#include "nfp_logs.h"
+#include "flower/nfp_flower.h"
+#include "flower/nfp_flower_cmsg.h"
+#include "flower/nfp_flower_representor.h"
+
+#define NFP_FL_QOS_PPS          RTE_BIT32(15)
+#define NFP_FL_QOS_METER        RTE_BIT32(10)
+#define NFP_FL_QOS_RFC2697      RTE_BIT32(0)
+
+/**
+ * Callback to get MTR capabilities.
+ *
+ * @param[in] dev
+ *   Pointer to the device (unused).
+ * @param[out] cap
+ *   Pointer to the meter object capabilities.
+ * @param[out] error
+ *   Pointer to the error (unused).
+ *
+ * @returns
+ *   0 on success, a negative value otherwise and rte_errno is set.
+ */
+static int
+nfp_mtr_cap_get(struct rte_eth_dev *dev __rte_unused,
+		struct rte_mtr_capabilities *cap,
+		struct rte_mtr_error *error)
+{
+	if (cap == NULL) {
+		return -rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+				NULL, "NULL pointer for capabilitie argument");
+	}
+
+	memset(cap, 0, sizeof(struct rte_mtr_capabilities));
+
+	cap->n_max                               = NFP_MAX_MTR_CNT;
+	cap->n_shared_max                        = NFP_MAX_MTR_CNT;
+	cap->identical                           = 1;
+	cap->shared_identical                    = 1;
+	cap->chaining_n_mtrs_per_flow_max        = 1;
+	cap->meter_srtcm_rfc2697_n_max           = NFP_MAX_MTR_CNT;
+	cap->meter_trtcm_rfc2698_n_max           = NFP_MAX_MTR_CNT;
+	cap->meter_rate_max                      = UINT64_MAX;
+	cap->meter_policy_n_max                  = NFP_MAX_POLICY_CNT;
+	cap->srtcm_rfc2697_byte_mode_supported   = 1;
+	cap->srtcm_rfc2697_packet_mode_supported = 1;
+	cap->trtcm_rfc2698_byte_mode_supported   = 1;
+	cap->trtcm_rfc2698_packet_mode_supported = 1;
+	cap->stats_mask = RTE_MTR_STATS_N_PKTS_GREEN |
+			RTE_MTR_STATS_N_PKTS_DROPPED |
+			RTE_MTR_STATS_N_BYTES_GREEN |
+			RTE_MTR_STATS_N_BYTES_DROPPED;
+
+	return 0;
+}
+
+static int
+nfp_mtr_profile_validate(uint32_t mtr_profile_id,
+		struct rte_mtr_meter_profile *profile,
+		struct rte_mtr_error *error)
+{
+	/* Profile must not be NULL. */
+	if (profile == NULL) {
+		return -rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_METER_PROFILE,
+				NULL, "Meter profile is null");
+	}
+
+	/* Meter profile ID must be valid. */
+	if (mtr_profile_id >= NFP_MAX_PROFILE_CNT) {
+		return -rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_METER_PROFILE_ID,
+				NULL, "Meter profile id not valid");
+	}
+
+	switch (profile->alg) {
+	case RTE_MTR_SRTCM_RFC2697:
+	case RTE_MTR_TRTCM_RFC2698:
+		return 0;
+	case RTE_MTR_TRTCM_RFC4115:
+		return -rte_mtr_error_set(error, ENOTSUP,
+				RTE_MTR_ERROR_TYPE_METER_PROFILE,
+				NULL, "Unsupported metering algorithm");
+	default:
+		return -rte_mtr_error_set(error, ENOTSUP,
+				RTE_MTR_ERROR_TYPE_METER_PROFILE,
+				NULL, "Unknown metering algorithm");
+	}
+}
+
+static void
+nfp_mtr_profile_config_2698(uint32_t mtr_profile_id,
+		struct rte_mtr_meter_profile *profile,
+		struct nfp_profile_conf *conf)
+{
+	if (profile->packet_mode != 0)
+		conf->head.flags_opts |= rte_cpu_to_be_32(NFP_FL_QOS_PPS);
+
+	conf->head.flags_opts |= rte_cpu_to_be_32(NFP_FL_QOS_METER);
+	conf->head.profile_id = rte_cpu_to_be_32(mtr_profile_id);
+
+	conf->bkt_tkn_c = rte_cpu_to_be_32(profile->trtcm_rfc2698.cbs);
+	conf->bkt_tkn_p = rte_cpu_to_be_32(profile->trtcm_rfc2698.pbs);
+	conf->cbs = rte_cpu_to_be_32(profile->trtcm_rfc2698.cbs);
+	conf->pbs = rte_cpu_to_be_32(profile->trtcm_rfc2698.pbs);
+	conf->cir = rte_cpu_to_be_32(profile->trtcm_rfc2698.cir);
+	conf->pir = rte_cpu_to_be_32(profile->trtcm_rfc2698.pir);
+}
+
+static void
+nfp_mtr_profile_config_2697(uint32_t mtr_profile_id,
+		struct rte_mtr_meter_profile *profile,
+		struct nfp_profile_conf *conf)
+{
+	if (profile->packet_mode != 0)
+		conf->head.flags_opts |= rte_cpu_to_be_32(NFP_FL_QOS_PPS);
+
+	conf->head.flags_opts |= rte_cpu_to_be_32(NFP_FL_QOS_RFC2697);
+	conf->head.flags_opts |= rte_cpu_to_be_32(NFP_FL_QOS_METER);
+	conf->head.profile_id = rte_cpu_to_be_32(mtr_profile_id);
+
+	conf->bkt_tkn_c = rte_cpu_to_be_32(profile->srtcm_rfc2697.cbs);
+	conf->bkt_tkn_p = rte_cpu_to_be_32(profile->srtcm_rfc2697.ebs);
+	conf->cbs = rte_cpu_to_be_32(profile->srtcm_rfc2697.cbs);
+	conf->pbs = rte_cpu_to_be_32(profile->srtcm_rfc2697.ebs);
+	conf->cir = rte_cpu_to_be_32(profile->srtcm_rfc2697.cir);
+	conf->pir = rte_cpu_to_be_32(profile->srtcm_rfc2697.cir);
+}
+
+static int
+nfp_mtr_profile_conf_mod(uint32_t mtr_profile_id,
+		struct rte_mtr_meter_profile *profile,
+		struct nfp_profile_conf *conf)
+{
+	switch (profile->alg) {
+	case RTE_MTR_SRTCM_RFC2697:
+		nfp_mtr_profile_config_2697(mtr_profile_id, profile, conf);
+		return 0;
+	case RTE_MTR_TRTCM_RFC2698:
+		nfp_mtr_profile_config_2698(mtr_profile_id, profile, conf);
+		return 0;
+	case RTE_MTR_TRTCM_RFC4115:
+		return -ENOTSUP;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int
+nfp_mtr_profile_conf_insert(uint32_t mtr_profile_id,
+		struct rte_mtr_meter_profile *profile,
+		struct nfp_mtr_profile *mtr_profile)
+{
+	mtr_profile->profile_id = mtr_profile_id;
+	mtr_profile->in_use = false;
+
+	return nfp_mtr_profile_conf_mod(mtr_profile_id, profile,
+			&mtr_profile->conf);
+}
+
+static struct nfp_mtr_profile *
+nfp_mtr_profile_search(struct nfp_mtr_priv *priv, uint32_t mtr_profile_id)
+{
+	struct nfp_mtr_profile *mtr_profile;
+
+	LIST_FOREACH(mtr_profile, &priv->profiles, next)
+		if (mtr_profile->profile_id == mtr_profile_id)
+			break;
+
+	return mtr_profile;
+}
+
+static int
+nfp_mtr_profile_insert(struct nfp_app_fw_flower *app_fw_flower,
+		struct rte_mtr_meter_profile *profile,
+		uint32_t mtr_profile_id,
+		struct rte_mtr_error *error)
+{
+	int ret;
+	struct nfp_mtr_priv *priv;
+	struct nfp_mtr_profile *mtr_profile;
+
+	priv = app_fw_flower->mtr_priv;
+
+	/* Meter profile memory allocation. */
+	mtr_profile = rte_zmalloc(NULL, sizeof(struct nfp_mtr_profile), 0);
+	if (mtr_profile == NULL) {
+		return -rte_mtr_error_set(error, ENOMEM,
+				RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+				NULL, "Meter profile alloc failed");
+	}
+
+	ret = nfp_mtr_profile_conf_insert(mtr_profile_id,
+			profile, mtr_profile);
+	if (ret != 0) {
+		rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+				NULL, "Insert profile config failed");
+		goto free_profile;
+	}
+
+	ret = nfp_flower_cmsg_qos_add(app_fw_flower, &mtr_profile->conf);
+	if (ret != 0) {
+		rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+				NULL, "Add meter to firmware failed");
+		goto free_profile;
+	}
+
+	/* Insert profile into profile list */
+	LIST_INSERT_HEAD(&priv->profiles, mtr_profile, next);
+
+	return 0;
+
+free_profile:
+	rte_free(mtr_profile);
+
+	return ret;
+}
+
+static int
+nfp_mtr_profile_mod(struct nfp_app_fw_flower *app_fw_flower,
+		struct rte_mtr_meter_profile *profile,
+		struct nfp_mtr_profile *mtr_profile,
+		struct rte_mtr_error *error)
+{
+	int ret;
+	struct nfp_profile_conf old_conf;
+
+	/* Get the old profile config */
+	rte_memcpy(&old_conf, &mtr_profile->conf, sizeof(old_conf));
+
+	ret = nfp_mtr_profile_conf_mod(mtr_profile->profile_id,
+			profile, &mtr_profile->conf);
+	if (ret != 0) {
+		rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+				NULL, "Mod profile config failed");
+		goto rollback;
+	}
+
+	ret = nfp_flower_cmsg_qos_add(app_fw_flower, &mtr_profile->conf);
+	if (ret != 0) {
+		rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+				NULL, "Mod meter to firmware failed");
+		goto rollback;
+	}
+
+	return 0;
+
+rollback:
+	rte_memcpy(&mtr_profile->conf, &old_conf, sizeof(old_conf));
+
+	return ret;
+}
+
+/**
+ * Callback to add MTR profile.
+ *
+ * @param[in] dev
+ *   Pointer to Ethernet device.
+ * @param[in] mtr_profile_id
+ *   Meter profile id.
+ * @param[in] profile
+ *   Pointer to meter profile detail.
+ * @param[out] error
+ *   Pointer to the error structure.
+ *
+ * @return
+ *   0 on success, a negative value otherwise and rte_errno is set.
+ */
+static int
+nfp_mtr_profile_add(struct rte_eth_dev *dev,
+		uint32_t mtr_profile_id,
+		struct rte_mtr_meter_profile *profile,
+		struct rte_mtr_error *error)
+{
+	int ret;
+	struct nfp_mtr_priv *priv;
+	struct nfp_mtr_profile *mtr_profile;
+	struct nfp_app_fw_flower *app_fw_flower;
+	struct nfp_flower_representor *representor;
+
+	representor = dev->data->dev_private;
+	app_fw_flower = representor->app_fw_flower;
+	priv = app_fw_flower->mtr_priv;
+
+	/* Check input params */
+	ret = nfp_mtr_profile_validate(mtr_profile_id, profile, error);
+	if (ret != 0)
+		return ret;
+
+	/* Check if mtr profile id exist */
+	mtr_profile = nfp_mtr_profile_search(priv, mtr_profile_id);
+	if (mtr_profile == NULL) {
+		ret = nfp_mtr_profile_insert(app_fw_flower,
+				profile, mtr_profile_id, error);
+	} else {
+		ret = nfp_mtr_profile_mod(app_fw_flower,
+				profile, mtr_profile, error);
+	}
+
+	return ret;
+}
+
+/**
+ * Callback to delete MTR profile.
+ *
+ * @param[in] dev
+ *   Pointer to Ethernet device.
+ * @param[in] mtr_profile_id
+ *   Meter profile id.
+ * @param[out] error
+ *   Pointer to the error structure.
+ *
+ * @return
+ *   0 on success, a negative value otherwise and rte_errno is set.
+ */
+static int
+nfp_mtr_profile_delete(struct rte_eth_dev *dev,
+		uint32_t mtr_profile_id,
+		struct rte_mtr_error *error)
+{
+	int ret;
+	struct nfp_mtr_priv *priv;
+	struct nfp_mtr_profile *mtr_profile;
+	struct nfp_app_fw_flower *app_fw_flower;
+	struct nfp_flower_representor *representor;
+
+	representor = dev->data->dev_private;
+	app_fw_flower = representor->app_fw_flower;
+	priv = app_fw_flower->mtr_priv;
+
+	/* Check if mtr profile id exist */
+	mtr_profile = nfp_mtr_profile_search(priv, mtr_profile_id);
+	if (mtr_profile == NULL) {
+		return -rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_METER_PROFILE_ID,
+				NULL, "Request meter profile not exist");
+	}
+
+	if (mtr_profile->in_use) {
+		return -rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_METER_PROFILE,
+				NULL, "Request meter profile is been used");
+	}
+
+	ret = nfp_flower_cmsg_qos_delete(app_fw_flower, &mtr_profile->conf);
+	if (ret != 0) {
+		return -rte_mtr_error_set(error, EINVAL,
+				RTE_MTR_ERROR_TYPE_UNSPECIFIED,
+				NULL, "Delete meter from firmware failed");
+	}
+
+	/* Remove profile from profile list */
+	LIST_REMOVE(mtr_profile, next);
+	rte_free(mtr_profile);
+
+	return 0;
+}
+
+static const struct rte_mtr_ops nfp_mtr_ops = {
+	.capabilities_get      = nfp_mtr_cap_get,
+	.meter_profile_add     = nfp_mtr_profile_add,
+	.meter_profile_delete  = nfp_mtr_profile_delete,
+};
+
+int
+nfp_net_mtr_ops_get(struct rte_eth_dev *dev, void *arg)
+{
+	if ((dev->data->dev_flags & RTE_ETH_DEV_REPRESENTOR) == 0) {
+		PMD_DRV_LOG(ERR, "Port is not a representor");
+		return -EINVAL;
+	}
+
+	*(const struct rte_mtr_ops **)arg = &nfp_mtr_ops;
+
+	return 0;
+}
+
+int
+nfp_mtr_priv_init(struct nfp_pf_dev *pf_dev)
+{
+	struct nfp_mtr_priv *priv;
+	struct nfp_app_fw_flower *app_fw_flower;
+
+	priv = rte_zmalloc("nfp_app_mtr_priv", sizeof(struct nfp_mtr_priv), 0);
+	if (priv == NULL) {
+		PMD_INIT_LOG(ERR, "nfp app mtr priv creation failed");
+		return -ENOMEM;
+	}
+
+	app_fw_flower = NFP_PRIV_TO_APP_FW_FLOWER(pf_dev->app_fw_priv);
+	app_fw_flower->mtr_priv = priv;
+
+	LIST_INIT(&priv->profiles);
+
+	return 0;
+}
+
+void
+nfp_mtr_priv_uninit(struct nfp_pf_dev *pf_dev)
+{
+	struct nfp_mtr_priv *priv;
+	struct nfp_mtr_profile *mtr_profile;
+	struct nfp_app_fw_flower *app_fw_flower;
+
+	app_fw_flower = NFP_PRIV_TO_APP_FW_FLOWER(pf_dev->app_fw_priv);
+	priv = app_fw_flower->mtr_priv;
+
+	LIST_FOREACH(mtr_profile, &priv->profiles, next) {
+		LIST_REMOVE(mtr_profile, next);
+		rte_free(mtr_profile);
+	}
+
+	rte_free(priv);
+}
diff --git a/drivers/net/nfp/nfp_mtr.h b/drivers/net/nfp/nfp_mtr.h
new file mode 100644
index 0000000000..05919c2bde
--- /dev/null
+++ b/drivers/net/nfp/nfp_mtr.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2022 Corigine, Inc.
+ * All rights reserved.
+ */
+
+#ifndef __NFP_MTR_H__
+#define __NFP_MTR_H__
+
+/**
+ * The max meter count is determined by firmware.
+ * The max count is 65536 defined by OF_METER_COUNT.
+ */
+#define NFP_MAX_MTR_CNT                65536
+#define NFP_MAX_POLICY_CNT             NFP_MAX_MTR_CNT
+#define NFP_MAX_PROFILE_CNT            NFP_MAX_MTR_CNT
+
+/**
+ * See RFC 2698 for more details.
+ * Word[0](Flag options):
+ * [15] p(pps) 1 for pps, 0 for bps
+ *
+ * Meter control message
+ *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+ * +-------------------------------+-+---+-----+-+---------+-+---+-+
+ * |            Reserved           |p| Y |TYPE |E|  TSHFV  |P| PC|R|
+ * +-------------------------------+-+---+-----+-+---------+-+---+-+
+ * |                           Profile ID                          |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                        Token Bucket Peak                      |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                     Token Bucket Committed                    |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                         Peak Burst Size                       |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                      Committed Burst Size                     |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                      Peak Information Rate                    |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                    Committed Information Rate                 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+struct nfp_cfg_head {
+	rte_be32_t flags_opts;
+	rte_be32_t profile_id;
+};
+
+/**
+ * Struct nfp_profile_conf - profile config, offload to NIC
+ * @head:        config head information
+ * @bkt_tkn_p:   token bucket peak
+ * @bkt_tkn_c:   token bucket committed
+ * @pbs:         peak burst size
+ * @cbs:         committed burst size
+ * @pir:         peak information rate
+ * @cir:         committed information rate
+ */
+struct nfp_profile_conf {
+	struct nfp_cfg_head head;
+	rte_be32_t bkt_tkn_p;
+	rte_be32_t bkt_tkn_c;
+	rte_be32_t pbs;
+	rte_be32_t cbs;
+	rte_be32_t pir;
+	rte_be32_t cir;
+};
+
+/**
+ * Struct nfp_mtr_profile - meter profile, stored in driver
+ * Can only be used by one meter
+ * @next:        next meter profile object
+ * @profile_id:  meter profile id
+ * @conf:        meter profile config
+ * @in_use:      if profile is been used by meter
+ */
+struct nfp_mtr_profile {
+	LIST_ENTRY(nfp_mtr_profile) next;
+	uint32_t profile_id;
+	struct nfp_profile_conf conf;
+	bool in_use;
+};
+
+/**
+ * Struct nfp_mtr_priv - meter private data
+ * @profiles:        the head node of profile list
+ */
+struct nfp_mtr_priv {
+	LIST_HEAD(, nfp_mtr_profile) profiles;
+};
+
+int nfp_net_mtr_ops_get(struct rte_eth_dev *dev, void *arg);
+int nfp_mtr_priv_init(struct nfp_pf_dev *pf_dev);
+void nfp_mtr_priv_uninit(struct nfp_pf_dev *pf_dev);
+
+#endif /* __NFP_MTR_H__ */
