static int tx_consume(Rocker *r, DescInfo *info)

{

    PCIDevice *dev = PCI_DEVICE(r);

    char *buf = desc_get_buf(info, true);

    RockerTlv *tlv_frag;

    RockerTlv *tlvs[ROCKER_TLV_TX_MAX + 1];

    struct iovec iov[ROCKER_TX_FRAGS_MAX] = { { 0, }, };

    uint32_t pport;

    uint32_t port;

    uint16_t tx_offload = ROCKER_TX_OFFLOAD_NONE;

    uint16_t tx_l3_csum_off = 0;

    uint16_t tx_tso_mss = 0;

    uint16_t tx_tso_hdr_len = 0;

    int iovcnt = 0;

    int err = ROCKER_OK;

    int rem;

    int i;



    if (!buf) {

        return -ROCKER_ENXIO;

    }



    rocker_tlv_parse(tlvs, ROCKER_TLV_TX_MAX, buf, desc_tlv_size(info));



    if (!tlvs[ROCKER_TLV_TX_FRAGS]) {

        return -ROCKER_EINVAL;

    }



    pport = rocker_get_pport_by_tx_ring(r, desc_get_ring(info));

    if (!fp_port_from_pport(pport, &port)) {

        return -ROCKER_EINVAL;

    }



    if (tlvs[ROCKER_TLV_TX_OFFLOAD]) {

        tx_offload = rocker_tlv_get_u8(tlvs[ROCKER_TLV_TX_OFFLOAD]);

    }



    switch (tx_offload) {

    case ROCKER_TX_OFFLOAD_L3_CSUM:

        if (!tlvs[ROCKER_TLV_TX_L3_CSUM_OFF]) {

            return -ROCKER_EINVAL;

        }

        break;

    case ROCKER_TX_OFFLOAD_TSO:

        if (!tlvs[ROCKER_TLV_TX_TSO_MSS] ||

            !tlvs[ROCKER_TLV_TX_TSO_HDR_LEN]) {

            return -ROCKER_EINVAL;

        }

        break;

    }



    if (tlvs[ROCKER_TLV_TX_L3_CSUM_OFF]) {

        tx_l3_csum_off = rocker_tlv_get_le16(tlvs[ROCKER_TLV_TX_L3_CSUM_OFF]);

    }



    if (tlvs[ROCKER_TLV_TX_TSO_MSS]) {

        tx_tso_mss = rocker_tlv_get_le16(tlvs[ROCKER_TLV_TX_TSO_MSS]);

    }



    if (tlvs[ROCKER_TLV_TX_TSO_HDR_LEN]) {

        tx_tso_hdr_len = rocker_tlv_get_le16(tlvs[ROCKER_TLV_TX_TSO_HDR_LEN]);

    }



    rocker_tlv_for_each_nested(tlv_frag, tlvs[ROCKER_TLV_TX_FRAGS], rem) {

        hwaddr frag_addr;

        uint16_t frag_len;



        if (rocker_tlv_type(tlv_frag) != ROCKER_TLV_TX_FRAG) {

            err = -ROCKER_EINVAL;

            goto err_bad_attr;

        }



        rocker_tlv_parse_nested(tlvs, ROCKER_TLV_TX_FRAG_ATTR_MAX, tlv_frag);



        if (!tlvs[ROCKER_TLV_TX_FRAG_ATTR_ADDR] ||

            !tlvs[ROCKER_TLV_TX_FRAG_ATTR_LEN]) {

            err = -ROCKER_EINVAL;

            goto err_bad_attr;

        }



        frag_addr = rocker_tlv_get_le64(tlvs[ROCKER_TLV_TX_FRAG_ATTR_ADDR]);

        frag_len = rocker_tlv_get_le16(tlvs[ROCKER_TLV_TX_FRAG_ATTR_LEN]);



        iov[iovcnt].iov_len = frag_len;

        iov[iovcnt].iov_base = g_malloc(frag_len);

        if (!iov[iovcnt].iov_base) {

            err = -ROCKER_ENOMEM;

            goto err_no_mem;

        }



        if (pci_dma_read(dev, frag_addr, iov[iovcnt].iov_base,

                     iov[iovcnt].iov_len)) {

            err = -ROCKER_ENXIO;

            goto err_bad_io;

        }



        if (++iovcnt > ROCKER_TX_FRAGS_MAX) {

            goto err_too_many_frags;

        }

    }



    if (iovcnt) {

        /* XXX perform Tx offloads */

        /* XXX   silence compiler for now */

        tx_l3_csum_off += tx_tso_mss = tx_tso_hdr_len = 0;

    }



    err = fp_port_eg(r->fp_port[port], iov, iovcnt);



err_too_many_frags:

err_bad_io:

err_no_mem:

err_bad_attr:

    for (i = 0; i < ROCKER_TX_FRAGS_MAX; i++) {

        g_free(iov[i].iov_base);

    }



    return err;

}
