/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include "nge.h"
#define TXD_OWN 0x80000000
#define TXD_ERR 0x40000000
#define TXD_END 0x20000000
#define TXD_BCNT_MSK 0x00003FFF
#undef NGE_DBG
#define NGE_DBG NGE_DBG_SEND
#define NGE_TXSWD_RECYCLE(sd) {\
(sd)->mp = NULL; \
(sd)->frags = 0; \
(sd)->mp_hndl.head = NULL; \
(sd)->mp_hndl.tail = NULL; \
(sd)->flags = HOST_OWN; \
}
static size_t nge_tx_dmah_pop(nge_dmah_list_t *, nge_dmah_list_t *, size_t);
static void nge_tx_dmah_push(nge_dmah_list_t *, nge_dmah_list_t *);
void nge_tx_recycle_all(nge_t *ngep);
#pragma no_inline(nge_tx_recycle_all)
void
nge_tx_recycle_all(nge_t *ngep)
{
send_ring_t *srp;
sw_tx_sbd_t *ssbdp;
nge_dmah_node_t *dmah;
uint32_t slot;
uint32_t nslots;
srp = ngep->send;
nslots = srp->desc.nslots;
for (slot = 0; slot < nslots; ++slot) {
ssbdp = srp->sw_sbds + slot;
DMA_ZERO(ssbdp->desc);
if (ssbdp->mp != NULL) {
for (dmah = ssbdp->mp_hndl.head; dmah != NULL;
dmah = dmah->next)
(void) ddi_dma_unbind_handle(dmah->hndl);
freemsg(ssbdp->mp);
}
NGE_TXSWD_RECYCLE(ssbdp);
}
if (ngep->nge_mac_state == NGE_MAC_STARTED &&
ngep->resched_needed == 1) {
ngep->resched_needed = 0;
mac_tx_update(ngep->mh);
}
}
static size_t
nge_tx_dmah_pop(nge_dmah_list_t *src, nge_dmah_list_t *dst, size_t num)
{
nge_dmah_node_t *node;
for (node = src->head; node != NULL && --num != 0; node = node->next)
;
if (num == 0) {
dst->head = src->head;
dst->tail = node;
if ((src->head = node->next) == NULL)
src->tail = NULL;
node->next = NULL;
}
return (num);
}
static void
nge_tx_dmah_push(nge_dmah_list_t *src, nge_dmah_list_t *dst)
{
if (dst->tail != NULL)
dst->tail->next = src->head;
else
dst->head = src->head;
dst->tail = src->tail;
}
static void
nge_tx_desc_sync(nge_t *ngep, uint32_t start_index, uint32_t bds, uint_t type)
{
send_ring_t *srp = ngep->send;
const size_t txd_size = ngep->desc_attr.txd_size;
const uint64_t end = srp->desc.nslots * txd_size;
uint64_t start;
uint64_t num;
start = start_index * txd_size;
num = bds * txd_size;
if (start + num <= end)
(void) ddi_dma_sync(srp->desc.dma_hdl, start, num, type);
else {
(void) ddi_dma_sync(srp->desc.dma_hdl, start, 0, type);
(void) ddi_dma_sync(srp->desc.dma_hdl, 0, start + num - end,
type);
}
}
/*
* Reclaim the resource after tx's completion
*/
void
nge_tx_recycle(nge_t *ngep, boolean_t is_intr)
{
int resched;
uint32_t stflg;
uint32_t free;
uint32_t slot;
uint32_t used;
uint32_t next;
uint32_t nslots;
mblk_t *mp;
sw_tx_sbd_t *ssbdp;
void *hw_sbd_p;
send_ring_t *srp;
nge_dmah_node_t *dme;
nge_dmah_list_t dmah;
srp = ngep->send;
if (is_intr) {
if (mutex_tryenter(srp->tc_lock) == 0)
return;
} else
mutex_enter(srp->tc_lock);
mutex_enter(srp->tx_lock);
next = srp->tx_next;
used = srp->tx_flow;
free = srp->tx_free;
mutex_exit(srp->tx_lock);
slot = srp->tc_next;
nslots = srp->desc.nslots;
used = nslots - free - used;
ASSERT(slot == NEXT_INDEX(next, free, nslots));
if (used == 0) {
ngep->watchdog = 0;
mutex_exit(srp->tc_lock);
return;
}
if (used > srp->tx_hwmark && ngep->resched_needed == 0)
used = srp->tx_hwmark;
nge_tx_desc_sync(ngep, slot, used, DDI_DMA_SYNC_FORKERNEL);
/*
* Look through the send ring by bd's status part
* to find all the bds which has been transmitted sucessfully
* then reclaim all resouces associated with these bds
*/
mp = NULL;
dmah.head = NULL;
dmah.tail = NULL;
for (free = 0; used-- != 0; slot = NEXT(slot, nslots), ++free) {
ssbdp = &srp->sw_sbds[slot];
hw_sbd_p = DMA_VPTR(ssbdp->desc);
if (ssbdp->flags == HOST_OWN)
break;
stflg = ngep->desc_attr.txd_check(hw_sbd_p);
if ((stflg & TXD_OWN) != 0)
break;
DMA_ZERO(ssbdp->desc);
if (ssbdp->mp != NULL) {
ssbdp->mp->b_next = mp;
mp = ssbdp->mp;
if (ssbdp->mp_hndl.head != NULL)
nge_tx_dmah_push(&ssbdp->mp_hndl, &dmah);
}
NGE_TXSWD_RECYCLE(ssbdp);
}
/*
* We're about to release one or more places :-)
* These ASSERTions check that our invariants still hold:
* there must always be at least one free place
* at this point, there must be at least one place NOT free
* we're not about to free more places than were claimed!
*/
if (free == 0) {
mutex_exit(srp->tc_lock);
return;
}
mutex_enter(srp->tx_lock);
srp->tx_free += free;
ngep->watchdog = (srp->desc.nslots - srp->tx_free != 0);
srp->tc_next = slot;
ASSERT(srp->tx_free <= nslots);
ASSERT(srp->tc_next == NEXT_INDEX(srp->tx_next, srp->tx_free, nslots));
resched = (ngep->resched_needed != 0 && srp->tx_hwmark <= srp->tx_free);
mutex_exit(srp->tx_lock);
mutex_exit(srp->tc_lock);
/* unbind/free mblks */
for (dme = dmah.head; dme != NULL; dme = dme->next)
(void) ddi_dma_unbind_handle(dme->hndl);
if (dmah.head != NULL) {
mutex_enter(&srp->dmah_lock);
nge_tx_dmah_push(&dmah, &srp->dmah_free);
mutex_exit(&srp->dmah_lock);
}
freemsgchain(mp);
/*
* up to this place, we maybe have reclaim some resouce
* if there is a requirement to report to gld, report this.
*/
if (resched)
(void) ddi_intr_trigger_softint(ngep->resched_hdl, NULL);
}
static uint32_t
nge_tx_alloc(nge_t *ngep, uint32_t num)
{
uint32_t start;
send_ring_t *srp;
start = (uint32_t)-1;
srp = ngep->send;
mutex_enter(srp->tx_lock);
if (srp->tx_free < srp->tx_lwmark) {
mutex_exit(srp->tx_lock);
nge_tx_recycle(ngep, B_FALSE);
mutex_enter(srp->tx_lock);
}
if (srp->tx_free >= num) {
start = srp->tx_next;
srp->tx_next = NEXT_INDEX(start, num, srp->desc.nslots);
srp->tx_free -= num;
srp->tx_flow += num;
}
mutex_exit(srp->tx_lock);
return (start);
}
static void
nge_tx_start(nge_t *ngep, uint32_t slotnum)
{
nge_mode_cntl mode_cntl;
send_ring_t *srp;
srp = ngep->send;
/*
* Because there can be multiple concurrent threads in
* transit through this code, we only want to notify the
* hardware once the last one is departing ...
*/
mutex_enter(srp->tx_lock);
srp->tx_flow -= slotnum;
if (srp->tx_flow == 0) {
/*
* Bump the watchdog counter, thus guaranteeing that it's
* nonzero (watchdog activated). Note that non-synchonised
* access here means we may race with the reclaim() code
* above, but the outcome will be harmless. At worst, the
* counter may not get reset on a partial reclaim; but the
* large trigger threshold makes false positives unlikely
*/
if (ngep->watchdog == 0)
ngep->watchdog = 1;
mode_cntl.mode_val = nge_reg_get32(ngep, NGE_MODE_CNTL);
mode_cntl.mode_bits.txdm = NGE_SET;
mode_cntl.mode_bits.tx_rcom_en = NGE_SET;
nge_reg_put32(ngep, NGE_MODE_CNTL, mode_cntl.mode_val);
}
mutex_exit(srp->tx_lock);
}
static enum send_status
nge_send_copy(nge_t *ngep, mblk_t *mp, send_ring_t *srp);
#pragma inline(nge_send_copy)
static enum send_status
nge_send_copy(nge_t *ngep, mblk_t *mp, send_ring_t *srp)
{
size_t totlen;
size_t mblen;
uint32_t flags;
uint32_t bds;
uint32_t start_index;
char *txb;
mblk_t *bp;
void *hw_sbd_p;
sw_tx_sbd_t *ssbdp;
boolean_t tfint;
mac_hcksum_get(mp, NULL, NULL, NULL, NULL, &flags);
bds = 0x1;
if ((uint32_t)-1 == (start_index = nge_tx_alloc(ngep, bds)))
return (SEND_COPY_FAIL);
ASSERT(start_index < srp->desc.nslots);
/*
* up to this point, there's nothing that can fail,
* so we can go straight to claiming our
* already-reserved place son the train.
*
* This is the point of no return!
*/
tfint = ((start_index % ngep->tfint_threshold) == 0);
bp = mp;
totlen = 0;
ssbdp = &srp->sw_sbds[start_index];
ASSERT(ssbdp->flags == HOST_OWN);
txb = DMA_VPTR(ssbdp->pbuf);
totlen = 0;
for (; bp != NULL; bp = bp->b_cont) {
if ((mblen = MBLKL(bp)) == 0)
continue;
if ((totlen += mblen) <= ngep->max_sdu) {
bcopy(bp->b_rptr, txb, mblen);
txb += mblen;
}
}
DMA_SYNC(ssbdp->pbuf, DDI_DMA_SYNC_FORDEV);
/* Fill & sync hw desc */
hw_sbd_p = DMA_VPTR(ssbdp->desc);
ngep->desc_attr.txd_fill(hw_sbd_p, &ssbdp->pbuf.cookie, totlen,
flags, B_TRUE, tfint);
nge_tx_desc_sync(ngep, start_index, bds, DDI_DMA_SYNC_FORDEV);
ssbdp->flags = CONTROLER_OWN;
nge_tx_start(ngep, bds);
/*
* The return status indicates that the message can be freed
* right away, as we've already copied the contents ...
*/
freemsg(mp);
return (SEND_COPY_SUCESS);
}
/*
* static enum send_status
* nge_send_mapped(nge_t *ngep, mblk_t *mp, size_t fragno);
* #pragma inline(nge_send_mapped)
*/
static enum send_status
nge_send_mapped(nge_t *ngep, mblk_t *mp, size_t fragno)
{
int err;
boolean_t end;
uint32_t i;
uint32_t j;
uint32_t ncookies;
uint32_t slot;
uint32_t nslots;
uint32_t mblen;
uint32_t flags;
uint32_t start_index;
uint32_t end_index;
mblk_t *bp;
void *hw_sbd_p;
send_ring_t *srp;
nge_dmah_node_t *dmah;
nge_dmah_node_t *dmer;
nge_dmah_list_t dmah_list;
ddi_dma_cookie_t cookie[NGE_MAX_COOKIES * NGE_MAP_FRAGS];
boolean_t tfint;
srp = ngep->send;
nslots = srp->desc.nslots;
mutex_enter(&srp->dmah_lock);
err = nge_tx_dmah_pop(&srp->dmah_free, &dmah_list, fragno);
mutex_exit(&srp->dmah_lock);
if (err != 0) {
return (SEND_MAP_FAIL);
}
/*
* Pre-scan the message chain, noting the total number of bytes,
* the number of fragments by pre-doing dma addr bind
* if the fragment is larger than NGE_COPY_SIZE.
* This way has the following advantages:
* 1. Acquire the detailed information of resouce
* need to send the message
*
* 2. If can not pre-apply enough resouce, fails at once
* and the driver will chose copy way to send out the
* message
*/
slot = 0;
dmah = dmah_list.head;
mac_hcksum_get(mp, NULL, NULL, NULL, NULL, &flags);
for (bp = mp; bp != NULL; bp = bp->b_cont) {
mblen = MBLKL(bp);
if (mblen == 0)
continue;
err = ddi_dma_addr_bind_handle(dmah->hndl,
NULL, (caddr_t)bp->b_rptr, mblen,
DDI_DMA_STREAMING | DDI_DMA_WRITE,
DDI_DMA_DONTWAIT, NULL, cookie + slot, &ncookies);
/*
* If there can not map successfully, it is uncessary
* sending the message by map way. Sending the message
* by copy way.
*
* By referring to intel's suggestion, it is better
* the number of cookies should be less than 4.
*/
if (err != DDI_DMA_MAPPED || ncookies > NGE_MAX_COOKIES) {
NGE_DEBUG(("err(%x) map tx bulk fails"
" cookie(%x), ncookies(%x)",
err, cookie[slot].dmac_laddress, ncookies));
goto map_fail;
}
/*
* Check How many bds a cookie will consume
*/
for (end_index = slot + ncookies;
++slot != end_index;
ddi_dma_nextcookie(dmah->hndl, cookie + slot))
;
dmah = dmah->next;
}
/*
* Now allocate tx descriptors and fill them
* IMPORTANT:
* Up to the point where it claims a place, It is impossibel
* to fail.
*
* In this version, there's no setup to be done here, and there's
* nothing that can fail, so we can go straight to claiming our
* already-reserved places on the train.
*
* This is the point of no return!
*/
if ((uint32_t)-1 == (start_index = nge_tx_alloc(ngep, slot)))
goto map_fail;
ASSERT(start_index < nslots);
/* fill&sync hw desc, going in reverse order */
end = B_TRUE;
end_index = NEXT_INDEX(start_index, slot - 1, nslots);
for (i = slot - 1, j = end_index; start_index - j != 0;
j = PREV(j, nslots), --i) {
tfint = ((j % ngep->tfint_threshold) == 0);
hw_sbd_p = DMA_VPTR(srp->sw_sbds[j].desc);
ngep->desc_attr.txd_fill(hw_sbd_p, cookie + i,
cookie[i].dmac_size, 0, end, tfint);
end = B_FALSE;
}
hw_sbd_p = DMA_VPTR(srp->sw_sbds[j].desc);
tfint = ((j % ngep->tfint_threshold) == 0);
ngep->desc_attr.txd_fill(hw_sbd_p, cookie + i, cookie[i].dmac_size,
flags, end, tfint);
nge_tx_desc_sync(ngep, start_index, slot, DDI_DMA_SYNC_FORDEV);
/* fill sw desc */
for (j = start_index; end_index - j != 0; j = NEXT(j, nslots)) {
srp->sw_sbds[j].flags = CONTROLER_OWN;
}
srp->sw_sbds[j].mp = mp;
srp->sw_sbds[j].mp_hndl = dmah_list;
srp->sw_sbds[j].frags = (uint32_t)fragno;
srp->sw_sbds[j].flags = CONTROLER_OWN;
nge_tx_start(ngep, slot);
/*
* The return status indicates that the message can not be freed
* right away, until we can make assure the message has been sent
* out sucessfully.
*/
return (SEND_MAP_SUCCESS);
map_fail:
for (dmer = dmah_list.head; dmah - dmer != 0; dmer = dmer->next)
(void) ddi_dma_unbind_handle(dmer->hndl);
mutex_enter(&srp->dmah_lock);
nge_tx_dmah_push(&dmah_list, &srp->dmah_free);
mutex_exit(&srp->dmah_lock);
return (SEND_MAP_FAIL);
}
static boolean_t
nge_send(nge_t *ngep, mblk_t *mp)
{
mblk_t *bp;
send_ring_t *srp;
enum send_status status;
uint32_t mblen = 0;
uint32_t frags = 0;
nge_statistics_t *nstp = &ngep->statistics;
nge_sw_statistics_t *sw_stp = &nstp->sw_statistics;
ASSERT(mp != NULL);
ASSERT(ngep->nge_mac_state == NGE_MAC_STARTED);
srp = ngep->send;
/*
* 1.Check the number of the fragments of the messages
* If the total number is larger than 3,
* Chose copy way
*
* 2. Check the length of the message whether is larger than
* NGE_TX_COPY_SIZE, if so, choose the map way.
*/
for (frags = 0, bp = mp; bp != NULL; bp = bp->b_cont) {
if (MBLKL(bp) == 0)
continue;
frags++;
mblen += MBLKL(bp);
}
if (mblen > (ngep->max_sdu) || mblen == 0) {
freemsg(mp);
return (B_TRUE);
}
if ((mblen > ngep->param_txbcopy_threshold) &&
(frags <= NGE_MAP_FRAGS) &&
(srp->tx_free > frags * NGE_MAX_COOKIES)) {
status = nge_send_mapped(ngep, mp, frags);
if (status == SEND_MAP_FAIL)
status = nge_send_copy(ngep, mp, srp);
} else {
status = nge_send_copy(ngep, mp, srp);
}
if (status == SEND_COPY_FAIL) {
nge_tx_recycle(ngep, B_FALSE);
status = nge_send_copy(ngep, mp, srp);
if (status == SEND_COPY_FAIL) {
ngep->resched_needed = 1;
NGE_DEBUG(("nge_send: send fail!"));
return (B_FALSE);
}
}
/* Update the software statistics */
sw_stp->obytes += mblen + ETHERFCSL;
sw_stp->xmit_count ++;
return (B_TRUE);
}
/*
* nge_m_tx : Send a chain of packets.
*/
mblk_t *
nge_m_tx(void *arg, mblk_t *mp)
{
nge_t *ngep = arg;
mblk_t *next;
rw_enter(ngep->rwlock, RW_READER);
ASSERT(mp != NULL);
if (ngep->nge_chip_state != NGE_CHIP_RUNNING) {
freemsgchain(mp);
mp = NULL;
}
while (mp != NULL) {
next = mp->b_next;
mp->b_next = NULL;
if (!nge_send(ngep, mp)) {
mp->b_next = next;
break;
}
mp = next;
}
rw_exit(ngep->rwlock);
return (mp);
}
/* ARGSUSED */
uint_t
nge_reschedule(caddr_t args1, caddr_t args2)
{
nge_t *ngep;
uint_t rslt;
ngep = (nge_t *)args1;
rslt = DDI_INTR_UNCLAIMED;
/*
* when softintr is trigged, checking whether this
* is caused by our expected interrupt
*/
if (ngep->nge_mac_state == NGE_MAC_STARTED &&
ngep->resched_needed == 1) {
ngep->resched_needed = 0;
++ngep->statistics.sw_statistics.tx_resched;
mac_tx_update(ngep->mh);
rslt = DDI_INTR_CLAIMED;
}
return (rslt);
}
uint32_t
nge_hot_txd_check(const void *hwd)
{
uint32_t err_flag;
const hot_tx_bd * htbdp;
htbdp = hwd;
err_flag = htbdp->control_status.cntl_val;
return (err_flag);
}
uint32_t
nge_sum_txd_check(const void *hwd)
{
uint32_t err_flag;
const sum_tx_bd * htbdp;
htbdp = hwd;
err_flag = htbdp->control_status.cntl_val;
return (err_flag);
}
/*
* Filling the contents of Tx's data descriptor
* before transmitting.
*/
void
nge_hot_txd_fill(void *hwdesc, const ddi_dma_cookie_t *cookie,
size_t length, uint32_t sum_flag, boolean_t end, boolean_t tfint)
{
hot_tx_bd * hw_sbd_p = hwdesc;
hw_sbd_p->host_buf_addr_hi = cookie->dmac_laddress >> 32;
hw_sbd_p->host_buf_addr_lo = cookie->dmac_laddress;
/*
* Setting the length of the packet
* Note: the length filled in the part should be
* the original length subtract 1;
*/
hw_sbd_p->control_status.control_sum_bits.bcnt = length - 1;
/* setting ip checksum */
if (sum_flag & HCK_IPV4_HDRCKSUM)
hw_sbd_p->control_status.control_sum_bits.ip_hsum
= NGE_SET;
/* setting tcp checksum */
if (sum_flag & HCK_FULLCKSUM)
hw_sbd_p->control_status.control_sum_bits.tcp_hsum
= NGE_SET;
/*
* indicating the end of BDs
*/
if (tfint)
hw_sbd_p->control_status.control_sum_bits.inten = NGE_SET;
if (end)
hw_sbd_p->control_status.control_sum_bits.end = NGE_SET;
membar_producer();
/* pass desc to HW */
hw_sbd_p->control_status.control_sum_bits.own = NGE_SET;
}
void
nge_sum_txd_fill(void *hwdesc, const ddi_dma_cookie_t *cookie,
size_t length, uint32_t sum_flag, boolean_t end, boolean_t tfint)
{
sum_tx_bd * hw_sbd_p = hwdesc;
hw_sbd_p->host_buf_addr = cookie->dmac_address;
/*
* Setting the length of the packet
* Note: the length filled in the part should be
* the original length subtract 1;
*/
hw_sbd_p->control_status.control_sum_bits.bcnt = length - 1;
/* setting ip checksum */
if (sum_flag & HCK_IPV4_HDRCKSUM)
hw_sbd_p->control_status.control_sum_bits.ip_hsum
= NGE_SET;
/* setting tcp checksum */
if (sum_flag & HCK_FULLCKSUM)
hw_sbd_p->control_status.control_sum_bits.tcp_hsum
= NGE_SET;
/*
* indicating the end of BDs
*/
if (tfint)
hw_sbd_p->control_status.control_sum_bits.inten = NGE_SET;
if (end)
hw_sbd_p->control_status.control_sum_bits.end = NGE_SET;
membar_producer();
/* pass desc to HW */
hw_sbd_p->control_status.control_sum_bits.own = NGE_SET;
}