diff --git a/drivers/net/octeontx/base/octeontx_pkovf.c b/drivers/net/octeontx/base/octeontx_pkovf.c
index 599edefd0..38693a9bc 100644
--- a/drivers/net/octeontx/base/octeontx_pkovf.c
+++ b/drivers/net/octeontx/base/octeontx_pkovf.c
@@ -77,6 +77,333 @@ struct octeontx_pko_vf_ctl_s {
 
 static struct octeontx_pko_vf_ctl_s pko_vf_ctl;
 
+static void *
+octeontx_pko_dq_vf_bar0(uint16_t txq)
+{
+	int vf_ix;
+
+	vf_ix = txq / PKO_VF_NUM_DQ;
+	return pko_vf_ctl.pko[vf_ix].bar0;
+}
+
+static int
+octeontx_pko_dq_gdq(uint16_t txq)
+{
+	return txq % PKO_VF_NUM_DQ;
+}
+
+/**
+ * Open a PKO DQ.
+ */
+static inline
+int octeontx_pko_dq_open(uint16_t txq)
+{
+	unsigned int reg_off;
+	uint8_t *vf_bar0;
+	uint64_t rtn;
+	int gdq;
+
+	vf_bar0 = octeontx_pko_dq_vf_bar0(txq);
+	gdq = octeontx_pko_dq_gdq(txq);
+
+	if (unlikely(gdq < 0 || vf_bar0 == NULL))
+		return -EINVAL;
+	*(volatile int64_t*)(pko_vf_ctl.fc_ctl + txq) =
+		PKO_DQ_FC_DEPTH_PAGES - PKO_DQ_FC_SKID;
+
+	rte_wmb();
+
+	octeontx_write64(PKO_DQ_FC_DEPTH_PAGES,
+			 vf_bar0 + PKO_VF_DQ_FC_STATUS(gdq));
+
+	/* Set the register to return descriptor (packet) count as DEPTH */
+	/* KIND=1, NCB_QUERY_RSP=0 */
+	octeontx_write64(1ull << PKO_DQ_KIND_BIT,
+				vf_bar0 + PKO_VF_DQ_WM_CTL(gdq));
+	reg_off = PKO_VF_DQ_OP_OPEN(gdq);
+
+	rtn = octeontx_reg_ldadd_u64(vf_bar0 + reg_off, 0);
+
+	/* PKO_DQOP_E::OPEN */
+	if (((rtn >> PKO_DQ_OP_BIT) & 0x3) != 0x1)
+		return -EIO;
+
+	switch (rtn >> PKO_DQ_STATUS_BIT) {
+	case 0xC:	/* DQALREADYCREATED */
+	case 0x0:	/* PASS */
+		break;
+	default:
+		return -EIO;
+	}
+
+	/* DRAIN=0, DRAIN_NULL_LINK=0, SW_XOFF=0 */
+	octeontx_write64(0, vf_bar0 + PKO_VF_DQ_SW_XOFF(gdq));
+
+	return rtn & ((1ull << PKO_DQ_OP_BIT) - 1);
+}
+
+/**
+ * Close a PKO DQ
+ * Flush all packets pending.
+ */
+static inline
+int octeontx_pko_dq_close(uint16_t txq)
+{
+	unsigned int reg_off;
+	uint8_t *vf_bar0;
+	uint64_t rtn;
+	int res;
+
+	vf_bar0 = octeontx_pko_dq_vf_bar0(txq);
+	res = octeontx_pko_dq_gdq(txq);
+
+	if (unlikely(res < 0 || vf_bar0 == NULL))
+		return -EINVAL;
+
+	reg_off = PKO_VF_DQ_OP_CLOSE(res);
+
+	rtn = octeontx_reg_ldadd_u64(vf_bar0 + reg_off, 0);
+
+	/* PKO_DQOP_E::CLOSE */
+	if (((rtn >> PKO_DQ_OP_BIT) & 0x3) != 0x2)
+		return -EIO;
+
+	switch (rtn >> PKO_DQ_STATUS_BIT) {
+	case 0xD:	/* DQNOTCREATED */
+	case 0x0:	/* PASS */
+		break;
+	default:
+		return -EIO;
+	}
+
+	res = rtn & ((1ull << PKO_DQ_OP_BIT) - 1); /* DEPTH */
+	return res;
+}
+
+/* Flush all packets pending on a DQ */
+static inline
+int octeontx_pko_dq_drain(uint16_t txq)
+{
+	unsigned int gdq;
+	uint8_t *vf_bar0;
+	uint64_t reg;
+	int res, timo = PKO_DQ_DRAIN_TO;
+
+	vf_bar0 = octeontx_pko_dq_vf_bar0(txq);
+	res = octeontx_pko_dq_gdq(txq);
+	gdq = res;
+
+	 /* DRAIN=1, DRAIN_NULL_LINK=0, SW_XOFF=1 */
+	 octeontx_write64(0x3, vf_bar0 + PKO_VF_DQ_SW_XOFF(gdq));
+	/* Wait until buffers leave DQs */
+	reg = octeontx_read64(vf_bar0 + PKO_VF_DQ_WM_CNT(gdq));
+	while (reg && timo > 0) {
+		rte_delay_us(100);
+		timo--;
+		reg = octeontx_read64(vf_bar0 + PKO_VF_DQ_WM_CNT(gdq));
+	}
+	/* DRAIN=0, DRAIN_NULL_LINK=0, SW_XOFF=0 */
+	octeontx_write64(0, vf_bar0 + PKO_VF_DQ_SW_XOFF(gdq));
+
+	return reg;
+}
+
+static inline int
+octeontx_pko_dq_range_lookup(struct octeontx_pko_vf_ctl_s *ctl, uint64_t chanid,
+			     unsigned int dq_num, unsigned int dq_from)
+{
+	unsigned int dq, dq_cnt;
+	unsigned int dq_base;
+
+	dq_cnt = 0;
+	dq = dq_from;
+	while (dq < RTE_DIM(ctl->dq_map)) {
+		dq_base = dq;
+		dq_cnt = 0;
+		while (ctl->dq_map[dq].chanid == ~chanid &&
+			dq < RTE_DIM(ctl->dq_map)) {
+			dq_cnt++;
+			if (dq_cnt == dq_num)
+				return dq_base;
+			dq++;
+		}
+		dq++;
+	}
+	return -1;
+}
+
+static inline void
+octeontx_pko_dq_range_assign(struct octeontx_pko_vf_ctl_s *ctl, uint64_t chanid,
+			     unsigned int dq_base, unsigned int dq_num)
+{
+	unsigned int dq, dq_cnt;
+
+	dq_cnt = 0;
+	while (dq_cnt < dq_num) {
+		dq = dq_base + dq_cnt;
+
+		octeontx_log_dbg("DQ# %u assigned to CHAN# %lx", dq, chanid);
+
+		ctl->dq_map[dq].chanid = ~chanid;
+		dq_cnt++;
+	}
+}
+
+static inline int
+octeontx_pko_dq_claim(struct octeontx_pko_vf_ctl_s *ctl, unsigned int dq_base,
+		      unsigned int dq_num, uint64_t chanid)
+{
+	const uint64_t null_chanid = ~0ull;
+	int dq;
+
+	rte_spinlock_lock(&ctl->lock);
+
+	dq = octeontx_pko_dq_range_lookup(ctl, null_chanid, dq_num, dq_base);
+	if (dq < 0 || (unsigned int)dq != dq_base) {
+		rte_spinlock_unlock(&ctl->lock);
+		return -1;
+	}
+	octeontx_pko_dq_range_assign(ctl, chanid, dq_base, dq_num);
+
+	rte_spinlock_unlock(&ctl->lock);
+
+	return 0;
+}
+
+static inline int
+octeontx_pko_dq_free(struct octeontx_pko_vf_ctl_s *ctl, uint64_t chanid)
+{
+	const uint64_t null_chanid = ~0ull;
+	unsigned int dq = 0, dq_cnt = 0;
+
+	rte_spinlock_lock(&ctl->lock);
+	while (dq < RTE_DIM(ctl->dq_map)) {
+		if (ctl->dq_map[dq].chanid == ~chanid) {
+			ctl->dq_map[dq].chanid = ~null_chanid;
+			dq_cnt++;
+		}
+		dq++;
+	}
+	rte_spinlock_unlock(&ctl->lock);
+
+	return dq_cnt > 0 ? 0 : -EINVAL;
+}
+
+int
+octeontx_pko_channel_open(int dq_base, int dq_num, int chanid)
+{
+	struct octeontx_pko_vf_ctl_s *ctl = &pko_vf_ctl;
+	int res;
+
+	res = octeontx_pko_dq_claim(ctl, dq_base, dq_num, chanid);
+	if (res < 0)
+		return -1;
+
+	return 0;
+}
+
+int
+octeontx_pko_channel_close(int chanid)
+{
+	struct octeontx_pko_vf_ctl_s *ctl = &pko_vf_ctl;
+	int res;
+
+	res = octeontx_pko_dq_free(ctl, chanid);
+	if (res < 0)
+		return -1;
+
+	return 0;
+}
+
+static inline int
+octeontx_pko_chan_start(struct octeontx_pko_vf_ctl_s *ctl, uint64_t chanid)
+{
+	unsigned int dq_vf;
+	unsigned int dq, dq_cnt;
+
+	dq_cnt = 0;
+	dq = 0;
+	while (dq < RTE_DIM(ctl->dq_map)) {
+		dq_vf = dq / PKO_VF_NUM_DQ;
+
+		if (!ctl->pko[dq_vf].bar0) {
+			dq += PKO_VF_NUM_DQ;
+			continue;
+		}
+
+		if (ctl->dq_map[dq].chanid != ~chanid) {
+			dq++;
+			continue;
+		}
+
+		if (octeontx_pko_dq_open(dq) < 0)
+			break;
+
+		dq_cnt++;
+		dq++;
+	}
+
+	return dq_cnt;
+}
+
+int
+octeontx_pko_channel_start(int chanid)
+{
+	struct octeontx_pko_vf_ctl_s *ctl = &pko_vf_ctl;
+	int dq_cnt;
+
+	dq_cnt = octeontx_pko_chan_start(ctl, chanid);
+	if (dq_cnt < 0)
+		return -1;
+
+	return dq_cnt;
+}
+
+static inline int
+octeontx_pko_chan_stop(struct octeontx_pko_vf_ctl_s *ctl, uint64_t chanid)
+{
+	unsigned int dq, dq_cnt, dq_vf;
+	int res;
+
+	dq_cnt = 0;
+	dq = 0;
+	while (dq < RTE_DIM(ctl->dq_map)) {
+		dq_vf = dq / PKO_VF_NUM_DQ;
+
+		if (!ctl->pko[dq_vf].bar0) {
+			dq += PKO_VF_NUM_DQ;
+			continue;
+		}
+
+		if (ctl->dq_map[dq].chanid != ~chanid) {
+			dq++;
+			continue;
+		}
+
+		res = octeontx_pko_dq_drain(dq);
+		if (res > 0)
+			octeontx_log_err("draining DQ%d, buffers left: %x",
+					 dq, res);
+
+		res = octeontx_pko_dq_close(dq);
+		if (res < 0)
+			octeontx_log_err("closing DQ%d failed\n", dq);
+
+		dq_cnt++;
+		dq++;
+	}
+	return dq_cnt;
+}
+
+int
+octeontx_pko_channel_stop(int chanid)
+{
+	struct octeontx_pko_vf_ctl_s *ctl = &pko_vf_ctl;
+
+	octeontx_pko_chan_stop(ctl, chanid);
+	return 0;
+}
+
 static void
 octeontx_pkovf_setup(void)
 {
diff --git a/drivers/net/octeontx/base/octeontx_pkovf.h b/drivers/net/octeontx/base/octeontx_pkovf.h
index bca4b2836..b33796a29 100644
--- a/drivers/net/octeontx/base/octeontx_pkovf.h
+++ b/drivers/net/octeontx/base/octeontx_pkovf.h
@@ -60,4 +60,21 @@
 #define	PKO_VF_DQ_OP_CLOSE(gdq)		(0x001200 | (gdq) << 17)
 #define	PKO_VF_DQ_OP_QUERY(gdq)		(0x001300 | (gdq) << 17)
 
+/* pko_send_hdr_s + pko_send_link */
+#define PKO_CMD_SZ			(2 << 1)
+#define PKO_SEND_GATHER_SUBDC		(0x0ull << 60)
+#define PKO_SEND_GATHER_LDTYPE(x)	((x) << 58)
+#define PKO_SEND_GATHER_GAUAR(x)	((x) << 24)
+
+typedef struct octeontx_dq_s {
+	void *lmtline_va;
+	void *ioreg_va;
+	void *fc_status_va;
+} octeontx_dq_t;
+
+int octeontx_pko_channel_open(int dq_base, int dq_num, int chanid);
+int octeontx_pko_channel_close(int chanid);
+int octeontx_pko_channel_start(int chanid);
+int octeontx_pko_channel_stop(int chanid);
+
 #endif /* __OCTEONTX_PKO_H__ */
