int ref_ok, struct funeth_txq *xdp_q)
 {
        struct bpf_prog *xdp_prog;
+       struct xdp_frame *xdpf;
        struct xdp_buff xdp;
        u32 act;
 
        case XDP_TX:
                if (unlikely(!ref_ok))
                        goto pass;
-               if (!fun_xdp_tx(xdp_q, xdp.data, xdp.data_end - xdp.data))
+
+               xdpf = xdp_convert_buff_to_frame(&xdp);
+               if (!xdpf || !fun_xdp_tx(xdp_q, xdpf))
                        goto xdp_error;
                FUN_QSTAT_INC(q, xdp_tx);
                q->xdp_flush |= FUN_XDP_FLUSH_TX;
 
 
                do {
                        fun_xdp_unmap(q, reclaim_idx);
-                       page_frag_free(q->info[reclaim_idx].vaddr);
+                       xdp_return_frame(q->info[reclaim_idx].xdpf);
 
                        trace_funeth_tx_free(q, reclaim_idx, 1, head);
 
        return npkts;
 }
 
-bool fun_xdp_tx(struct funeth_txq *q, void *data, unsigned int len)
+bool fun_xdp_tx(struct funeth_txq *q, struct xdp_frame *xdpf)
 {
        struct fun_eth_tx_req *req;
        struct fun_dataop_gl *gle;
-       unsigned int idx;
+       unsigned int idx, len;
        dma_addr_t dma;
 
        if (fun_txq_avail(q) < FUN_XDP_CLEAN_THRES)
                return false;
        }
 
-       dma = dma_map_single(q->dma_dev, data, len, DMA_TO_DEVICE);
+       len = xdpf->len;
+       dma = dma_map_single(q->dma_dev, xdpf->data, len, DMA_TO_DEVICE);
        if (unlikely(dma_mapping_error(q->dma_dev, dma))) {
                FUN_QSTAT_INC(q, tx_map_err);
                return false;
        gle = (struct fun_dataop_gl *)req->dataop.imm;
        fun_dataop_gl_init(gle, 0, 0, len, dma);
 
-       q->info[idx].vaddr = data;
+       q->info[idx].xdpf = xdpf;
 
        u64_stats_update_begin(&q->syncp);
        q->stats.tx_bytes += len;
        if (unlikely(q_idx >= fp->num_xdpqs))
                return -ENXIO;
 
-       for (q = xdpqs[q_idx], i = 0; i < n; i++) {
-               const struct xdp_frame *xdpf = frames[i];
-
-               if (!fun_xdp_tx(q, xdpf->data, xdpf->len))
+       for (q = xdpqs[q_idx], i = 0; i < n; i++)
+               if (!fun_xdp_tx(q, frames[i]))
                        break;
-       }
 
        if (unlikely(flags & XDP_XMIT_FLUSH))
                fun_txq_wr_db(q);
                unsigned int idx = q->cons_cnt & q->mask;
 
                fun_xdp_unmap(q, idx);
-               page_frag_free(q->info[idx].vaddr);
+               xdp_return_frame(q->info[idx].xdpf);
                q->cons_cnt++;
        }
 }
 
 
 struct funeth_tx_info {      /* per Tx descriptor state */
        union {
-               struct sk_buff *skb; /* associated packet */
-               void *vaddr;         /* start address for XDP */
+               struct sk_buff *skb;    /* associated packet (sk_buff path) */
+               struct xdp_frame *xdpf; /* associated XDP frame (XDP path) */
        };
 };
 
 int fun_rxq_napi_poll(struct napi_struct *napi, int budget);
 int fun_txq_napi_poll(struct napi_struct *napi, int budget);
 netdev_tx_t fun_start_xmit(struct sk_buff *skb, struct net_device *netdev);
-bool fun_xdp_tx(struct funeth_txq *q, void *data, unsigned int len);
+bool fun_xdp_tx(struct funeth_txq *q, struct xdp_frame *xdpf);
 int fun_xdp_xmit_frames(struct net_device *dev, int n,
                        struct xdp_frame **frames, u32 flags);