#include "bnxsnd.h"
#define BNX_DCOPY_ALIGN 32
#define BNX_XMIT_INIT_FAIL_THRESH 1
#define BNX_PDWM_THRESHOLD 8
#ifndef NUM_TX_CHAIN
#error NUM_TX_CHAIN is not defined.
#else
#if NUM_TX_CHAIN < 0
#error Invalid NUM_TX_CHAIN definition.
#elif NUM_TX_CHAIN > 1
#warning NUM_TX_CHAIN is greater than 1.
#endif
#endif
static ddi_dma_attr_t bnx_snd_dma_attrib = {
DMA_ATTR_V0,
0,
0xffffffffffffffff,
0x0ffffff,
BNX_DMA_ALIGNMENT,
0xffffffff,
1,
0x00ffffff,
0xffffffff,
BNX_MAX_SGL_ENTRIES,
BNX_MIN_BYTES_PER_FRAGMENT,
0,
};
static int
bnx_xmit_frag_map(mblk_t *mp, ddi_dma_handle_t *handle,
lm_frag_list_t *fraglist)
{
int i;
uint_t ccount;
ddi_dma_cookie_t cookie;
lm_frag_t *fragment;
if (fraglist->cnt >= BNX_MAX_SGL_ENTRIES) {
return (DDI_DMA_NOMAPPING);
}
i = ddi_dma_addr_bind_handle(*handle, NULL,
(caddr_t)mp->b_rptr, mp->b_wptr - mp->b_rptr,
DDI_DMA_WRITE | DDI_DMA_STREAMING, DDI_DMA_DONTWAIT, NULL,
&cookie, &ccount);
if (i != DDI_DMA_MAPPED) {
return (i);
}
if (fraglist->cnt + ccount > BNX_MAX_SGL_ENTRIES) {
(void) ddi_dma_unbind_handle(*handle);
return (DDI_DMA_NOMAPPING);
}
fragment = &(fraglist->frag_arr[fraglist->cnt]);
fraglist->cnt += ccount;
for (i = 0; i < ccount-1; i++) {
fragment->addr.as_u64 = cookie.dmac_laddress;
fragment->size = cookie.dmac_size;
fragment++;
ddi_dma_nextcookie(*handle, &cookie);
}
fragment->addr.as_u64 = cookie.dmac_laddress;
fragment->size = cookie.dmac_size;
return (0);
}
static void
bnx_xmit_pkt_unmap(um_txpacket_t * const umpacket)
{
int i;
for (i = 0; i < umpacket->num_handles; i++) {
(void) ddi_dma_unbind_handle(umpacket->dma_handle[i]);
}
umpacket->num_handles = 0;
}
int
bnx_xmit_pkt_map(um_txpacket_t * const umpacket, mblk_t * mp)
{
int rc;
u32_t num_dma_handle;
num_dma_handle = umpacket->num_handles;
if (num_dma_handle == BNX_MAX_SGL_ENTRIES) {
return (BNX_TX_RESOURCES_TOO_MANY_FRAGS);
}
rc = bnx_xmit_frag_map(mp, &umpacket->dma_handle[num_dma_handle++],
&(umpacket->frag_list));
if (rc) {
return (BNX_TX_RESOURCES_NO_OS_DMA_RES);
}
umpacket->num_handles = num_dma_handle;
return (0);
}
static void
bnx_xmit_pkt_cpy(um_device_t * const umdevice, um_txpacket_t * const umpacket)
{
size_t msgsize;
u32_t cpysize;
lm_frag_t *cpyfrag;
boolean_t map_enable;
mblk_t *mp;
int rc;
map_enable = B_TRUE;
cpysize = 0;
cpyfrag = NULL;
for (mp = umpacket->mp; mp; mp = mp->b_cont) {
msgsize = MBLKL(mp);
if (msgsize == 0)
continue;
if (map_enable && msgsize > umdevice->tx_copy_threshold) {
rc = bnx_xmit_pkt_map(umpacket, mp);
if (rc == 0) {
cpyfrag = NULL;
continue;
} else {
map_enable = B_FALSE;
}
}
ASSERT(cpysize + msgsize <= umdevice->dev_var.mtu +
sizeof (struct ether_vlan_header));
bcopy(mp->b_rptr, (char *)umpacket->cpymem + cpysize, msgsize);
if (cpyfrag != NULL) {
cpyfrag->size += msgsize;
} else {
cpyfrag = &umpacket->frag_list.frag_arr[
umpacket->frag_list.cnt++];
ASSERT(umpacket->frag_list.cnt <= BNX_MAX_SGL_ENTRIES +
1);
cpyfrag->size = msgsize;
cpyfrag->addr.as_u64 = umpacket->cpyphy.as_u64 +
cpysize;
}
cpysize += msgsize;
}
if (cpysize > 0) {
(void) ddi_dma_sync(*(umpacket->cpyhdl), umpacket->cpyoff,
cpysize, DDI_DMA_SYNC_FORDEV);
}
if (umpacket->num_handles == 0) {
freemsg(umpacket->mp);
umpacket->mp = NULL;
}
}
static int
bnx_xmit_pkt_init(um_device_t * const umdevice, um_txpacket_t * const umpacket,
int num, lm_u64_t memphys)
{
int i;
int rc;
um_xmit_qinfo * xmitinfo;
xmitinfo = &_TX_QINFO(umdevice, 0);
for (i = 0; i < BNX_MAX_SGL_ENTRIES; i++) {
rc = ddi_dma_alloc_handle(umdevice->os_param.dip,
&bnx_snd_dma_attrib, DDI_DMA_DONTWAIT,
(void *)0, &umpacket->dma_handle[i]);
if (rc != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s:%s failed. (errno=%d)",
umdevice->dev_name, __func__, rc);
goto error;
}
}
umpacket->mp = NULL;
umpacket->num_handles = 0;
umpacket->frag_list.cnt = 0;
umpacket->cpyhdl = &(xmitinfo->dcpyhndl);
umpacket->cpyoff = num * xmitinfo->dcpyhard;
umpacket->cpymem = xmitinfo->dcpyvirt + umpacket->cpyoff;
umpacket->cpyphy = memphys;
return (rc);
error:
for (i--; i >= 0; i--) {
ddi_dma_free_handle(&umpacket->dma_handle[i]);
}
return (-1);
}
static void
bnx_xmit_pkt_fini(um_txpacket_t * const umpacket)
{
int i;
for (i = BNX_MAX_SGL_ENTRIES - 1; i >= 0; i--) {
ddi_dma_free_handle(&umpacket->dma_handle[i]);
}
umpacket->mp = NULL;
umpacket->num_handles = 0;
umpacket->frag_list.cnt = 0;
umpacket->cpyhdl = NULL;
umpacket->cpyoff = 0;
umpacket->cpymem = NULL;
}
static int
bnx_xmit_packet(um_device_t * const umdevice, const unsigned int ringidx,
um_txpacket_t * const umpacket)
{
int rc;
s_list_t *waitq;
lm_tx_chain_t *txq;
lm_packet_t *lmpacket;
lm_device_t *lmdevice;
lm_frag_list_t *lmfraglist;
lmdevice = &(umdevice->lm_dev);
lmpacket = &(umpacket->lm_pkt);
lmfraglist = &(umpacket->frag_list);
txq = &lmdevice->tx_info.chain[ringidx];
if (txq->bd_left < BNX_MAX_SGL_ENTRIES) {
s_list_t xmitpkts;
s_list_init(&xmitpkts, NULL, NULL, 0);
rc = lm_get_packets_sent(lmdevice, ringidx, 0, &xmitpkts);
if (rc) {
bnx_xmit_ring_reclaim(umdevice, ringidx, &xmitpkts);
}
}
waitq = &_TXQ_RESC_DESC(umdevice, ringidx);
if (s_list_is_empty(waitq) && txq->bd_left >= lmfraglist->cnt) {
(void) lm_send_packet(lmdevice, ringidx, lmpacket, lmfraglist);
return (BNX_SEND_GOODXMIT);
}
s_list_push_tail(waitq, &umpacket->lm_pkt.link);
if (txq->bd_left >= BNX_MAX_SGL_ENTRIES) {
rc = bnx_xmit_ring_xmit_qpkt(umdevice, ringidx);
if (rc == BNX_SEND_GOODXMIT) {
return (BNX_SEND_GOODXMIT);
}
}
umdevice->no_tx_credits |= BNX_TX_RESOURCES_NO_CREDIT;
return (BNX_SEND_DEFERPKT);
}
static int
bnx_xmit_ring_cpybuf_alloc(um_device_t * const umdevice,
um_xmit_qinfo * const xmitinfo,
unsigned int buffsize)
{
int rc;
size_t actualsize;
unsigned int alignedsize;
unsigned int count;
ddi_dma_cookie_t cookie;
ASSERT(buffsize > 0);
alignedsize = buffsize;
alignedsize += (BNX_DCOPY_ALIGN - 1);
alignedsize &= ~((unsigned int)(BNX_DCOPY_ALIGN - 1));
rc = ddi_dma_alloc_handle(umdevice->os_param.dip, &bnx_std_dma_attrib,
DDI_DMA_DONTWAIT, (void *)0, &xmitinfo->dcpyhndl);
if (rc != DDI_SUCCESS) {
cmn_err(CE_WARN,
"%s: %s: Failed to alloc phys dma handle.\n",
umdevice->dev_name, __func__);
return (-1);
}
rc = ddi_dma_mem_alloc(xmitinfo->dcpyhndl,
alignedsize * xmitinfo->desc_cnt, &bnxAccessAttribBUF,
DDI_DMA_STREAMING, DDI_DMA_DONTWAIT, (void *)0,
&xmitinfo->dcpyvirt, &actualsize, &xmitinfo->dcpyahdl);
if (rc != DDI_SUCCESS) {
cmn_err(CE_WARN,
"%s: %s: Failed to alloc phys memory.\n",
umdevice->dev_name, __func__);
goto error1;
}
rc = ddi_dma_addr_bind_handle(xmitinfo->dcpyhndl,
(struct as *)0, xmitinfo->dcpyvirt, actualsize,
DDI_DMA_WRITE | DDI_DMA_STREAMING, DDI_DMA_DONTWAIT, (void *)0,
&cookie, &count);
if (rc != DDI_SUCCESS) {
cmn_err(CE_WARN,
"%s: %s: Failed to bind DMA address.\n",
umdevice->dev_name, __func__);
goto error2;
}
xmitinfo->dcpyhard = alignedsize;
xmitinfo->dcpyphys.as_u64 = (u64_t)cookie.dmac_laddress;
return (0);
error2:
ddi_dma_mem_free(&xmitinfo->dcpyahdl);
error1:
ddi_dma_free_handle(&xmitinfo->dcpyhndl);
return (-1);
}
static void
bnx_xmit_ring_cpybuf_free(um_device_t * const umdevice,
um_xmit_qinfo * const xmitinfo)
{
(void) ddi_dma_unbind_handle(xmitinfo->dcpyhndl);
ddi_dma_mem_free(&xmitinfo->dcpyahdl);
ddi_dma_free_handle(&xmitinfo->dcpyhndl);
xmitinfo->dcpyvirt = NULL;
xmitinfo->dcpyphys.as_u64 = 0;
xmitinfo->dcpyhard = 0;
}
static int
bnx_xmit_ring_init(um_device_t * const umdevice, const unsigned int ringidx)
{
int i;
size_t memsize;
void *memvirt;
s_list_t *freeq;
lm_u64_t memphys;
um_txpacket_t *umpacket;
um_xmit_qinfo *xmitinfo;
xmitinfo = &_TX_QINFO(umdevice, ringidx);
s_list_init(&_TXQ_FREE_DESC(umdevice, ringidx), NULL, NULL, 0);
s_list_init(&_TXQ_RESC_DESC(umdevice, ringidx), NULL, NULL, 0);
if (xmitinfo->desc_cnt == 0) {
return (0);
}
xmitinfo->thresh_pdwm = BNX_PDWM_THRESHOLD;
memsize = xmitinfo->desc_cnt * sizeof (um_txpacket_t);
memvirt = kmem_zalloc(memsize, KM_NOSLEEP);
if (memvirt == NULL) {
cmn_err(CE_WARN, "%s: Failed to allocate TX packet "
"descriptor memory (%d).\n", umdevice->dev_name, ringidx);
return (-1);
}
xmitinfo->desc_mem.addr = memvirt;
xmitinfo->desc_mem.size = memsize;
if (bnx_xmit_ring_cpybuf_alloc(umdevice, xmitinfo,
umdevice->dev_var.mtu + sizeof (struct ether_vlan_header))) {
kmem_free(xmitinfo->desc_mem.addr, xmitinfo->desc_mem.size);
xmitinfo->desc_mem.addr = NULL;
xmitinfo->desc_mem.size = 0;
return (-1);
}
freeq = &_TXQ_FREE_DESC(umdevice, ringidx);
umpacket = (um_txpacket_t *)memvirt;
memphys = xmitinfo->dcpyphys;
for (i = 0; i < xmitinfo->desc_cnt; i++) {
if (bnx_xmit_pkt_init(umdevice, umpacket, i, memphys)) {
break;
}
LM_INC64(&memphys, xmitinfo->dcpyhard);
s_list_push_tail(freeq, &umpacket->lm_pkt.link);
umpacket++;
}
mutex_init(&xmitinfo->free_mutex, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(umdevice->intrPriority));
return (0);
}
void
bnx_xmit_ring_reclaim(um_device_t * const umdevice,
const unsigned int ringidx, s_list_t *srcq)
{
s_list_t *freeq;
s_list_entry_t *lmpacket;
um_txpacket_t *umpacket;
um_xmit_qinfo *xmitinfo;
if (s_list_entry_cnt(srcq) == 0) {
return;
}
for (lmpacket = s_list_peek_head(srcq); lmpacket;
lmpacket = s_list_next_entry(lmpacket)) {
umpacket = (um_txpacket_t *)lmpacket;
if (umpacket->num_handles > 0) {
bnx_xmit_pkt_unmap(umpacket);
}
if (umpacket->mp != NULL) {
freemsg(umpacket->mp);
umpacket->mp = NULL;
}
}
freeq = &_TXQ_FREE_DESC(umdevice, ringidx);
xmitinfo = &_TX_QINFO(umdevice, ringidx);
mutex_enter(&xmitinfo->free_mutex);
s_list_add_tail(freeq, srcq);
mutex_exit(&xmitinfo->free_mutex);
}
int
bnx_xmit_ring_xmit_qpkt(um_device_t * const umdevice,
const unsigned int ringidx)
{
s_list_t *waitq;
lm_tx_chain_t *txq;
lm_packet_t *lmpacket;
lm_device_t *lmdevice;
lm_frag_list_t *lmfraglist;
um_txpacket_t *umpacket;
int rc = 0;
lmdevice = &(umdevice->lm_dev);
waitq = &_TXQ_RESC_DESC(umdevice, ringidx);
txq = &lmdevice->tx_info.chain[ringidx];
while (s_list_entry_cnt(waitq)) {
umpacket = (um_txpacket_t *)s_list_peek_head(waitq);
lmfraglist = &(umpacket->frag_list);
if (lmfraglist->cnt > txq->bd_left) {
rc = BNX_SEND_DEFERPKT;
break;
}
umpacket = (um_txpacket_t *)s_list_pop_head(waitq);
lmpacket = &(umpacket->lm_pkt);
(void) lm_send_packet(lmdevice, ringidx, lmpacket, lmfraglist);
}
return (rc);
}
int
bnx_xmit_ring_xmit_mblk(um_device_t * const umdevice,
const unsigned int ringidx, mblk_t *mp)
{
int rc;
uint32_t pflags;
s_list_t *txfreeq;
lm_packet_t *lmpacket;
um_txpacket_t *umpacket;
um_xmit_qinfo *xmitinfo;
xmitinfo = &_TX_QINFO(umdevice, ringidx);
txfreeq = &_TXQ_FREE_DESC(umdevice, ringidx);
mutex_enter(&xmitinfo->free_mutex);
umpacket = (um_txpacket_t *)s_list_pop_head(txfreeq);
mutex_exit(&xmitinfo->free_mutex);
if (umpacket == NULL) {
s_list_t xmitpkts;
lm_device_t *lmdevice;
lmdevice = &(umdevice->lm_dev);
s_list_init(&xmitpkts, NULL, NULL, 0);
mutex_enter(&umdevice->os_param.xmit_mutex);
rc = lm_get_packets_sent(lmdevice, ringidx, 0, &xmitpkts);
if (rc == 0) {
umdevice->no_tx_credits |= BNX_TX_RESOURCES_NO_DESC;
mutex_exit(&umdevice->os_param.xmit_mutex);
return (BNX_SEND_HDWRFULL);
}
mutex_exit(&umdevice->os_param.xmit_mutex);
umpacket = (um_txpacket_t *)s_list_pop_head(&xmitpkts);
if (umpacket->num_handles > 0) {
bnx_xmit_pkt_unmap(umpacket);
}
if (umpacket->mp != NULL) {
freemsg(umpacket->mp);
umpacket->mp = NULL;
}
bnx_xmit_ring_reclaim(umdevice, ringidx, &xmitpkts);
}
umpacket->lm_pkt.link.next = NULL;
ASSERT(umpacket->mp == NULL);
ASSERT(umpacket->num_handles == 0);
umpacket->frag_list.cnt = 0;
umpacket->mp = mp;
mac_hcksum_get(mp, NULL, NULL, NULL, NULL, &pflags);
bnx_xmit_pkt_cpy(umdevice, umpacket);
lmpacket = &(umpacket->lm_pkt);
lmpacket->u1.tx.flags = 0;
lmpacket->u1.tx.lso_mss = 0;
lmpacket->u1.tx.vlan_tag = 0;
if (pflags & HCK_IPV4_HDRCKSUM) {
lmpacket->u1.tx.flags |= LM_TX_FLAG_COMPUTE_IP_CKSUM;
}
if (pflags & HCK_FULLCKSUM) {
lmpacket->u1.tx.flags |= LM_TX_FLAG_COMPUTE_TCP_UDP_CKSUM;
}
mutex_enter(&umdevice->os_param.xmit_mutex);
rc = bnx_xmit_packet(umdevice, ringidx, umpacket);
mutex_exit(&umdevice->os_param.xmit_mutex);
return (rc);
}
void
bnx_xmit_ring_intr(um_device_t * const umdevice, const unsigned int ringidx)
{
u32_t rc;
s_list_t xmitpkts;
lm_device_t *lmdevice;
lmdevice = &(umdevice->lm_dev);
s_list_init(&xmitpkts, NULL, NULL, 0);
mutex_enter(&umdevice->os_param.xmit_mutex);
rc = lm_get_packets_sent(lmdevice, ringidx, 0, &xmitpkts);
mutex_exit(&umdevice->os_param.xmit_mutex);
if (rc) {
bnx_xmit_ring_reclaim(umdevice, ringidx, &xmitpkts);
}
}
void
bnx_xmit_ring_post(um_device_t * const umdevice, const unsigned int ringidx)
{
int rc;
s_list_t *freeq;
lm_device_t *lmdevice;
um_xmit_qinfo *xmitinfo;
lm_tx_chain_t *lmtxring;
if (umdevice->no_tx_credits != 0) {
if (umdevice->no_tx_credits & BNX_TX_RESOURCES_NO_CREDIT) {
rc = bnx_xmit_ring_xmit_qpkt(umdevice, ringidx);
if (rc == BNX_SEND_GOODXMIT) {
lmdevice = &(umdevice->lm_dev);
lmtxring = &(lmdevice->tx_info.chain[ringidx]);
if (lmtxring->bd_left >= BNX_MAX_SGL_ENTRIES) {
umdevice->no_tx_credits &=
~BNX_TX_RESOURCES_NO_CREDIT;
}
}
}
if (umdevice->no_tx_credits & BNX_TX_RESOURCES_NO_DESC) {
freeq = &_TXQ_FREE_DESC(umdevice, ringidx);
xmitinfo = &_TX_QINFO(umdevice, ringidx);
if (s_list_entry_cnt(freeq) > xmitinfo->thresh_pdwm) {
umdevice->no_tx_credits &=
~BNX_TX_RESOURCES_NO_DESC;
}
}
if (umdevice->no_tx_credits == 0) {
mac_tx_update(umdevice->os_param.macp);
}
}
}
static void
bnx_xmit_ring_fini(um_device_t * const umdevice, const unsigned int ringidx)
{
s_list_t *srcq;
um_txpacket_t *umpacket;
um_xmit_qinfo *xmitinfo;
xmitinfo = &_TX_QINFO(umdevice, ringidx);
mutex_destroy(&xmitinfo->free_mutex);
srcq = &_TXQ_FREE_DESC(umdevice, ringidx);
while (1) {
umpacket = (um_txpacket_t *)s_list_pop_head(srcq);
if (umpacket == NULL) {
break;
}
bnx_xmit_pkt_fini(umpacket);
}
bnx_xmit_ring_cpybuf_free(umdevice, xmitinfo);
kmem_free(xmitinfo->desc_mem.addr, xmitinfo->desc_mem.size);
xmitinfo->desc_mem.addr = NULL;
xmitinfo->desc_mem.size = 0;
}
int
bnx_txpkts_init(um_device_t * const umdevice)
{
int i;
int alloccnt;
um_xmit_qinfo *xmitinfo;
xmitinfo = &_TX_QINFO(umdevice, 0);
mutex_init(&umdevice->os_param.xmit_mutex, NULL,
MUTEX_DRIVER, DDI_INTR_PRI(umdevice->intrPriority));
alloccnt = 0;
for (i = TX_CHAIN_IDX0; i < NUM_TX_CHAIN; i++) {
int desc_cnt;
if (bnx_xmit_ring_init(umdevice, i)) {
goto error;
}
desc_cnt = s_list_entry_cnt(&_TXQ_FREE_DESC(umdevice, i));
if (desc_cnt != xmitinfo->desc_cnt) {
cmn_err(CE_NOTE,
"%s: %d tx buffers requested. %d allocated.\n",
umdevice->dev_name, xmitinfo->desc_cnt, desc_cnt);
}
alloccnt += desc_cnt;
}
if (alloccnt < BNX_XMIT_INIT_FAIL_THRESH) {
cmn_err(CE_WARN,
"%s: Failed to allocate minimum number of TX buffers.\n",
umdevice->dev_name);
goto error;
}
return (0);
error:
for (i--; i >= TX_CHAIN_IDX0; i--) {
bnx_xmit_ring_fini(umdevice, i);
}
mutex_destroy(&umdevice->os_param.xmit_mutex);
return (-1);
}
void
bnx_txpkts_flush(um_device_t * const umdevice)
{
int i;
boolean_t notx_fl = B_FALSE;
for (i = NUM_TX_CHAIN - 1; i >= TX_CHAIN_IDX0; i--) {
lm_abort(&(umdevice->lm_dev), ABORT_OP_TX_CHAIN, i);
bnx_xmit_ring_reclaim(umdevice, i,
&_TXQ_RESC_DESC(umdevice, i));
s_list_init(&_TXQ_RESC_DESC(umdevice, i), NULL, NULL, 0);
if (umdevice->no_tx_credits & BNX_TX_RESOURCES_NO_CREDIT) {
umdevice->no_tx_credits &= ~BNX_TX_RESOURCES_NO_CREDIT;
notx_fl = B_TRUE;
}
if (umdevice->no_tx_credits & BNX_TX_RESOURCES_NO_DESC) {
umdevice->no_tx_credits &= ~BNX_TX_RESOURCES_NO_DESC;
notx_fl = B_TRUE;
}
if (umdevice->no_tx_credits == 0 && notx_fl == B_TRUE) {
mac_tx_update(umdevice->os_param.macp);
}
}
}
void
bnx_txpkts_intr(um_device_t * const umdevice)
{
int i;
for (i = TX_CHAIN_IDX0; i < NUM_TX_CHAIN; i++) {
bnx_xmit_ring_post(umdevice, i);
}
}
void
bnx_txpkts_fini(um_device_t * const umdevice)
{
int i;
for (i = NUM_TX_CHAIN - 1; i >= TX_CHAIN_IDX0; i--) {
bnx_xmit_ring_fini(umdevice, i);
}
mutex_destroy(&umdevice->os_param.xmit_mutex);
}