#include <oce_impl.h>
void oce_rx_pool_free(char *arg);
static void oce_rqb_dtor(oce_rq_bdesc_t *rqbd);
static int oce_rqb_ctor(oce_rq_bdesc_t *rqbd, struct oce_rq *rq,
size_t size, int flags);
static inline mblk_t *oce_rx(struct oce_dev *dev, struct oce_rq *rq,
struct oce_nic_rx_cqe *cqe);
static inline mblk_t *oce_rx_bcopy(struct oce_dev *dev,
struct oce_rq *rq, struct oce_nic_rx_cqe *cqe);
static int oce_rq_charge(struct oce_rq *rq, uint32_t nbufs, boolean_t repost);
static void oce_rx_insert_tag(mblk_t *mp, uint16_t vtag);
static void oce_set_rx_oflags(mblk_t *mp, struct oce_nic_rx_cqe *cqe);
static inline void oce_rx_drop_pkt(struct oce_rq *rq,
struct oce_nic_rx_cqe *cqe);
static oce_rq_bdesc_t *oce_rqb_alloc(struct oce_rq *rq);
static void oce_rqb_free(struct oce_rq *rq, oce_rq_bdesc_t *rqbd);
static void oce_rq_post_buffer(struct oce_rq *rq, int nbufs);
static ddi_dma_attr_t oce_rx_buf_attr = {
DMA_ATTR_V0,
0x0000000000000000ull,
0xFFFFFFFFFFFFFFFFull,
0x00000000FFFFFFFFull,
OCE_DMA_ALIGNMENT,
0x000007FF,
0x00000001,
0x00000000FFFFFFFFull,
0xFFFFFFFFFFFFFFFFull,
1,
0x00000001,
DDI_DMA_FLAGERR|DDI_DMA_RELAXED_ORDERING
};
int
oce_rqb_cache_create(struct oce_rq *rq, size_t buf_size)
{
int size;
int cnt;
int ret;
oce_rq_bdesc_t *rqbd;
_NOTE(ARGUNUSED(buf_size));
rqbd = rq->rq_bdesc_array;
size = rq->cfg.frag_size + OCE_RQE_BUF_HEADROOM;
for (cnt = 0; cnt < rq->cfg.nbufs; cnt++, rqbd++) {
rq->rqb_freelist[cnt] = rqbd;
ret = oce_rqb_ctor(rqbd, rq,
size, (DDI_DMA_RDWR|DDI_DMA_STREAMING));
if (ret != DDI_SUCCESS) {
goto rqb_fail;
}
}
rq->rqb_free = rq->cfg.nbufs;
rq->rqb_rc_head = 0;
rq->rqb_next_free = 0;
return (DDI_SUCCESS);
rqb_fail:
oce_rqb_cache_destroy(rq);
return (DDI_FAILURE);
}
void
oce_rqb_cache_destroy(struct oce_rq *rq)
{
oce_rq_bdesc_t *rqbd = NULL;
int cnt;
rqbd = rq->rq_bdesc_array;
for (cnt = 0; cnt < rq->cfg.nbufs; cnt++, rqbd++) {
oce_rqb_dtor(rqbd);
}
}
static void
oce_rqb_dtor(oce_rq_bdesc_t *rqbd)
{
if ((rqbd == NULL) || (rqbd->rq == NULL)) {
return;
}
if (rqbd->mp != NULL) {
rqbd->fr_rtn.free_arg = NULL;
freemsg(rqbd->mp);
rqbd->mp = NULL;
}
oce_free_dma_buffer(rqbd->rq->parent, rqbd->rqb);
}
static int
oce_rqb_ctor(oce_rq_bdesc_t *rqbd, struct oce_rq *rq, size_t size, int flags)
{
struct oce_dev *dev;
oce_dma_buf_t *dbuf;
dev = rq->parent;
dbuf = oce_alloc_dma_buffer(dev, size, &oce_rx_buf_attr, flags);
if (dbuf == NULL) {
return (DDI_FAILURE);
}
rqbd->fr_rtn.free_func = (void (*)())oce_rx_pool_free;
rqbd->fr_rtn.free_arg = (caddr_t)(void *)rqbd;
rqbd->mp = desballoc((uchar_t *)(dbuf->base),
dbuf->size, 0, &rqbd->fr_rtn);
if (rqbd->mp == NULL) {
oce_free_dma_buffer(dev, dbuf);
return (DDI_FAILURE);
}
rqbd->rqb = dbuf;
rqbd->rq = rq;
rqbd->frag_addr.dw.addr_lo = ADDR_LO(dbuf->addr + OCE_RQE_BUF_HEADROOM);
rqbd->frag_addr.dw.addr_hi = ADDR_HI(dbuf->addr + OCE_RQE_BUF_HEADROOM);
rqbd->mp->b_rptr = (uchar_t *)rqbd->rqb->base + OCE_RQE_BUF_HEADROOM;
return (DDI_SUCCESS);
}
static inline oce_rq_bdesc_t *
oce_rqb_alloc(struct oce_rq *rq)
{
oce_rq_bdesc_t *rqbd;
uint32_t free_index;
free_index = rq->rqb_next_free;
rqbd = rq->rqb_freelist[free_index];
rq->rqb_freelist[free_index] = NULL;
rq->rqb_next_free = GET_Q_NEXT(free_index, 1, rq->cfg.nbufs);
return (rqbd);
}
static inline void
oce_rqb_free(struct oce_rq *rq, oce_rq_bdesc_t *rqbd)
{
uint32_t free_index;
mutex_enter(&rq->rc_lock);
free_index = rq->rqb_rc_head;
rq->rqb_freelist[free_index] = rqbd;
rq->rqb_rc_head = GET_Q_NEXT(free_index, 1, rq->cfg.nbufs);
mutex_exit(&rq->rc_lock);
atomic_inc_32(&rq->rqb_free);
}
static void oce_rq_post_buffer(struct oce_rq *rq, int nbufs)
{
pd_rxulp_db_t rxdb_reg;
int count;
struct oce_dev *dev = rq->parent;
rxdb_reg.dw0 = 0;
rxdb_reg.bits.qid = rq->rq_id & DB_RQ_ID_MASK;
for (count = nbufs/OCE_MAX_RQ_POSTS; count > 0; count--) {
rxdb_reg.bits.num_posted = OCE_MAX_RQ_POSTS;
OCE_DB_WRITE32(dev, PD_RXULP_DB, rxdb_reg.dw0);
rq->buf_avail += OCE_MAX_RQ_POSTS;
nbufs -= OCE_MAX_RQ_POSTS;
}
if (nbufs > 0) {
rxdb_reg.bits.num_posted = nbufs;
OCE_DB_WRITE32(dev, PD_RXULP_DB, rxdb_reg.dw0);
rq->buf_avail += nbufs;
}
}
static inline int
oce_rq_charge(struct oce_rq *rq, uint32_t nbufs, boolean_t repost)
{
struct oce_nic_rqe *rqe;
oce_rq_bdesc_t *rqbd;
oce_rq_bdesc_t **shadow_rq;
int cnt;
int cur_index;
oce_ring_buffer_t *ring;
shadow_rq = rq->shadow_ring;
ring = rq->ring;
cur_index = ring->cidx;
for (cnt = 0; cnt < nbufs; cnt++) {
if (!repost) {
rqbd = oce_rqb_alloc(rq);
} else {
rqbd = shadow_rq[cur_index];
cur_index = GET_Q_NEXT(cur_index, 1, ring->num_items);
}
rqe = RING_GET_PRODUCER_ITEM_VA(rq->ring,
struct oce_nic_rqe);
rqe->u0.s.frag_pa_lo = rqbd->frag_addr.dw.addr_lo;
rqe->u0.s.frag_pa_hi = rqbd->frag_addr.dw.addr_hi;
shadow_rq[rq->ring->pidx] = rqbd;
DW_SWAP(u32ptr(rqe), sizeof (struct oce_nic_rqe));
RING_PUT(rq->ring, 1);
}
return (cnt);
}
void
oce_rq_discharge(struct oce_rq *rq)
{
oce_rq_bdesc_t *rqbd;
oce_rq_bdesc_t **shadow_rq;
shadow_rq = rq->shadow_ring;
while ((int32_t)rq->buf_avail > 0) {
rqbd = shadow_rq[rq->ring->cidx];
oce_rqb_free(rq, rqbd);
RING_GET(rq->ring, 1);
rq->buf_avail--;
}
}
static inline mblk_t *
oce_rx(struct oce_dev *dev, struct oce_rq *rq, struct oce_nic_rx_cqe *cqe)
{
mblk_t *mp;
int pkt_len;
int32_t frag_cnt = 0;
mblk_t **mblk_tail;
mblk_t *mblk_head;
int frag_size;
oce_rq_bdesc_t *rqbd;
uint16_t cur_index;
oce_ring_buffer_t *ring;
int i;
frag_cnt = cqe->u0.s.num_fragments & 0x7;
mblk_head = NULL;
mblk_tail = &mblk_head;
ring = rq->ring;
cur_index = ring->cidx;
pkt_len = cqe->u0.s.pkt_size;
for (i = 0; i < frag_cnt; i++) {
rqbd = rq->shadow_ring[cur_index];
if (rqbd->mp == NULL) {
rqbd->mp = desballoc((uchar_t *)rqbd->rqb->base,
rqbd->rqb->size, 0, &rqbd->fr_rtn);
if (rqbd->mp == NULL) {
return (NULL);
}
rqbd->mp->b_rptr =
(uchar_t *)rqbd->rqb->base + OCE_RQE_BUF_HEADROOM;
}
mp = rqbd->mp;
frag_size = (pkt_len > rq->cfg.frag_size) ?
rq->cfg.frag_size : pkt_len;
mp->b_wptr = mp->b_rptr + frag_size;
pkt_len -= frag_size;
mp->b_next = mp->b_cont = NULL;
*mblk_tail = mp;
mblk_tail = &mp->b_cont;
(void) DBUF_SYNC(rqbd->rqb, DDI_DMA_SYNC_FORCPU);
cur_index = GET_Q_NEXT(cur_index, 1, ring->num_items);
}
if (mblk_head == NULL) {
oce_log(dev, CE_WARN, MOD_RX, "%s", "oce_rx:no frags?");
return (NULL);
}
(void) oce_rq_charge(rq, frag_cnt, B_FALSE);
atomic_add_32(&rq->pending, frag_cnt);
return (mblk_head);
}
static inline mblk_t *
oce_rx_bcopy(struct oce_dev *dev, struct oce_rq *rq, struct oce_nic_rx_cqe *cqe)
{
mblk_t *mp;
int pkt_len;
int alloc_len;
int32_t frag_cnt = 0;
int frag_size;
oce_rq_bdesc_t *rqbd;
unsigned char *rptr;
uint32_t cur_index;
oce_ring_buffer_t *ring;
oce_rq_bdesc_t **shadow_rq;
int cnt = 0;
_NOTE(ARGUNUSED(dev));
shadow_rq = rq->shadow_ring;
pkt_len = cqe->u0.s.pkt_size;
alloc_len = pkt_len + OCE_RQE_BUF_HEADROOM;
frag_cnt = cqe->u0.s.num_fragments & 0x7;
mp = allocb(alloc_len, BPRI_HI);
if (mp == NULL) {
return (NULL);
}
mp->b_rptr += OCE_RQE_BUF_HEADROOM;
rptr = mp->b_rptr;
mp->b_wptr = mp->b_rptr + pkt_len;
ring = rq->ring;
cur_index = ring->cidx;
for (cnt = 0; cnt < frag_cnt; cnt++) {
rqbd = shadow_rq[cur_index];
frag_size = (pkt_len > rq->cfg.frag_size) ?
rq->cfg.frag_size : pkt_len;
(void) DBUF_SYNC(rqbd->rqb, DDI_DMA_SYNC_FORCPU);
bcopy(rqbd->rqb->base + OCE_RQE_BUF_HEADROOM, rptr, frag_size);
rptr += frag_size;
pkt_len -= frag_size;
cur_index = GET_Q_NEXT(cur_index, 1, ring->num_items);
}
(void) oce_rq_charge(rq, frag_cnt, B_TRUE);
return (mp);
}
static inline void
oce_set_rx_oflags(mblk_t *mp, struct oce_nic_rx_cqe *cqe)
{
int csum_flags = 0;
if (cqe->u0.s.ip_cksum_pass) {
csum_flags |= HCK_IPV4_HDRCKSUM_OK;
}
if (cqe->u0.s.l4_cksum_pass) {
csum_flags |= (HCK_FULLCKSUM | HCK_FULLCKSUM_OK);
}
if (csum_flags) {
(void) mac_hcksum_set(mp, 0, 0, 0, 0, csum_flags);
}
}
static inline void
oce_rx_insert_tag(mblk_t *mp, uint16_t vtag)
{
struct ether_vlan_header *ehp;
(void) memmove(mp->b_rptr - VTAG_SIZE,
mp->b_rptr, 2 * ETHERADDRL);
mp->b_rptr -= VTAG_SIZE;
ehp = (struct ether_vlan_header *)voidptr(mp->b_rptr);
ehp->ether_tpid = htons(ETHERTYPE_VLAN);
ehp->ether_tci = LE_16(vtag);
}
static inline void
oce_rx_drop_pkt(struct oce_rq *rq, struct oce_nic_rx_cqe *cqe)
{
int frag_cnt;
oce_rq_bdesc_t *rqbd;
oce_rq_bdesc_t **shadow_rq;
shadow_rq = rq->shadow_ring;
for (frag_cnt = 0; frag_cnt < cqe->u0.s.num_fragments; frag_cnt++) {
rqbd = shadow_rq[rq->ring->cidx];
oce_rqb_free(rq, rqbd);
RING_GET(rq->ring, 1);
}
}
uint16_t
oce_drain_rq_cq(void *arg)
{
struct oce_nic_rx_cqe *cqe;
struct oce_rq *rq;
mblk_t *mp = NULL;
mblk_t *mblk_head;
mblk_t **mblk_tail;
uint16_t num_cqe = 0;
struct oce_cq *cq;
struct oce_dev *dev;
int32_t frag_cnt;
uint32_t nbufs = 0;
rq = (struct oce_rq *)arg;
dev = rq->parent;
cq = rq->cq;
mblk_head = NULL;
mblk_tail = &mblk_head;
cqe = RING_GET_CONSUMER_ITEM_VA(cq->ring, struct oce_nic_rx_cqe);
(void) DBUF_SYNC(cq->ring->dbuf, DDI_DMA_SYNC_FORKERNEL);
while (RQ_CQE_VALID(cqe)) {
DW_SWAP(u32ptr(cqe), sizeof (struct oce_nic_rx_cqe));
frag_cnt = cqe->u0.s.num_fragments & 0x7;
if ((cqe->u0.s.pkt_size < dev->rx_bcopy_limit) ||
(oce_atomic_reserve(&rq->rqb_free, frag_cnt) < 0)) {
mp = oce_rx_bcopy(dev, rq, cqe);
} else {
mp = oce_rx(dev, rq, cqe);
if (mp == NULL) {
atomic_add_32(&rq->rqb_free, frag_cnt);
mp = oce_rx_bcopy(dev, rq, cqe);
}
}
if (mp != NULL) {
if (dev->function_mode & FLEX10_MODE) {
if (cqe->u0.s.vlan_tag_present &&
cqe->u0.s.qnq) {
oce_rx_insert_tag(mp,
cqe->u0.s.vlan_tag);
}
} else if (cqe->u0.s.vlan_tag_present) {
oce_rx_insert_tag(mp, cqe->u0.s.vlan_tag);
}
oce_set_rx_oflags(mp, cqe);
*mblk_tail = mp;
mblk_tail = &mp->b_next;
} else {
(void) oce_rq_charge(rq, frag_cnt, B_TRUE);
}
RING_GET(rq->ring, frag_cnt);
rq->buf_avail -= frag_cnt;
nbufs += frag_cnt;
oce_rq_post_buffer(rq, frag_cnt);
RQ_CQE_INVALIDATE(cqe);
RING_GET(cq->ring, 1);
cqe = RING_GET_CONSUMER_ITEM_VA(cq->ring,
struct oce_nic_rx_cqe);
num_cqe++;
if (num_cqe > dev->rx_pkt_per_intr) {
break;
}
}
if (mblk_head) {
mac_rx(dev->mac_handle, NULL, mblk_head);
}
oce_arm_cq(dev, cq->cq_id, num_cqe, B_TRUE);
return (num_cqe);
}
void
oce_rx_pool_free(char *arg)
{
oce_rq_bdesc_t *rqbd;
struct oce_rq *rq;
if (arg == NULL) {
return;
}
rqbd = (oce_rq_bdesc_t *)(void *)arg;
rq = rqbd->rq;
rqbd->mp = desballoc((uchar_t *)rqbd->rqb->base,
rqbd->rqb->size, 0, &rqbd->fr_rtn);
if (rqbd->mp) {
rqbd->mp->b_rptr =
(uchar_t *)rqbd->rqb->base + OCE_RQE_BUF_HEADROOM;
}
oce_rqb_free(rq, rqbd);
(void) atomic_dec_32(&rq->pending);
}
void
oce_clean_rq(struct oce_rq *rq)
{
uint16_t num_cqe = 0;
struct oce_cq *cq;
struct oce_dev *dev;
struct oce_nic_rx_cqe *cqe;
int32_t ti = 0;
dev = rq->parent;
cq = rq->cq;
cqe = RING_GET_CONSUMER_ITEM_VA(cq->ring, struct oce_nic_rx_cqe);
for (ti = 0; ti < DEFAULT_DRAIN_TIME; ti++) {
while (RQ_CQE_VALID(cqe)) {
DW_SWAP(u32ptr(cqe), sizeof (struct oce_nic_rx_cqe));
oce_rx_drop_pkt(rq, cqe);
atomic_add_32(&rq->buf_avail,
-(cqe->u0.s.num_fragments & 0x7));
oce_arm_cq(dev, cq->cq_id, 1, B_TRUE);
RQ_CQE_INVALIDATE(cqe);
RING_GET(cq->ring, 1);
cqe = RING_GET_CONSUMER_ITEM_VA(cq->ring,
struct oce_nic_rx_cqe);
num_cqe++;
}
OCE_MSDELAY(1);
}
}
int
oce_start_rq(struct oce_rq *rq)
{
int ret = 0;
int to_charge = 0;
struct oce_dev *dev = rq->parent;
to_charge = rq->cfg.q_len - rq->buf_avail;
to_charge = min(to_charge, rq->rqb_free);
atomic_add_32(&rq->rqb_free, -to_charge);
(void) oce_rq_charge(rq, to_charge, B_FALSE);
oce_rq_post_buffer(rq, to_charge);
oce_arm_cq(dev, rq->cq->cq_id, 0, B_TRUE);
return (ret);
}
int
oce_rx_pending(struct oce_dev *dev, struct oce_rq *rq, int32_t timeout)
{
int ti;
_NOTE(ARGUNUSED(dev));
for (ti = 0; ti < timeout; ti++) {
if (rq->pending > 0) {
OCE_MSDELAY(10);
continue;
} else {
rq->pending = 0;
break;
}
}
return (rq->pending);
}