/* * Copyright (c) 2010 Broadcom Corporation * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* * DMA hardware requires each descriptor ring to be 8kB aligned, and fit within * a contiguous 8kB physical address.
*/ #define D64RINGALIGN_BITS 13 #define D64MAXRINGSZ (1 << D64RINGALIGN_BITS) #define D64RINGALIGN (1 << D64RINGALIGN_BITS)
/* descriptor control flags 1 */ #define D64_CTRL_COREFLAGS 0x0ff00000 /* core specific flags */ #define D64_CTRL1_EOT ((u32)1 << 28) /* end of descriptor table */ #define D64_CTRL1_IOC ((u32)1 << 29) /* interrupt on completion */ #define D64_CTRL1_EOF ((u32)1 << 30) /* end of frame */ #define D64_CTRL1_SOF ((u32)1 << 31) /* start of frame */
/* descriptor control flags 2 */ /* buffer byte count. real data len must <= 16KB */ #define D64_CTRL2_BC_MASK 0x00007fff /* address extension bits */ #define D64_CTRL2_AE 0x00030000 #define D64_CTRL2_AE_SHIFT 16 /* parity bit */ #define D64_CTRL2_PARITY 0x00040000
/* control flags in the range [27:20] are core-specific and not defined here */ #define D64_CTRL_CORE_MASK 0x0ff00000
#define D64_RX_FRM_STS_LEN 0x0000ffff /* frame length mask */ #define D64_RX_FRM_STS_OVFL 0x00800000 /* RxOverFlow */ #define D64_RX_FRM_STS_DSCRCNT 0x0f000000 /* no. of descriptors used - 1 */ #define D64_RX_FRM_STS_DATATYPE 0xf0000000 /* core-dependent data type */
/* * packet headroom necessary to accommodate the largest header * in the system, (i.e TXOFF). By doing, we avoid the need to * allocate an extra buffer for the header when bridging to WL. * There is a compile time check in wlc.c which ensure that this * value is at least as big as TXOFF. This value is used in * dma_rxfill().
*/
#define BCMEXTRAHDROOM 172
#define MAXNAMEL 8 /* 8 char names */
/* macros to convert between byte offsets and indexes */ #define B2I(bytes, type) ((bytes) / sizeof(type)) #define I2B(index, type) ((index) * sizeof(type))
/* * DMA Descriptor * Descriptors are only read by the hardware, never written back.
*/ struct dma64desc {
__le32 ctrl1; /* misc control bits & bufcount */
__le32 ctrl2; /* buffer count and address extension */
__le32 addrlow; /* memory address of the date buffer, bits 31:0 */
__le32 addrhigh; /* memory address of the date buffer, bits 63:32 */
};
/* dma engine software state */ struct dma_info { struct dma_pub dma; /* exported structure */ char name[MAXNAMEL]; /* callers name for diag msgs */
struct bcma_device *core; struct device *dmadev;
/* session information for AMPDU */ struct brcms_ampdu_session ampdu_session;
bool dma64; /* this dma engine is operating in 64-bit mode */ bool addrext; /* this dma engine supports DmaExtendedAddrChanges */
/* 64-bit dma tx engine registers */
uint d64txregbase; /* 64-bit dma rx engine registers */
uint d64rxregbase; /* pointer to dma64 tx descriptor ring */ struct dma64desc *txd64; /* pointer to dma64 rx descriptor ring */ struct dma64desc *rxd64;
u16 dmadesc_align; /* alignment requirement for dma descriptors */
u16 ntxd; /* # tx descriptors tunable */
u16 txin; /* index of next descriptor to reclaim */
u16 txout; /* index of next descriptor to post */ /* pointer to parallel array of pointers to packets */ struct sk_buff **txp; /* Aligned physical address of descriptor ring */
dma_addr_t txdpa; /* Original physical address of descriptor ring */
dma_addr_t txdpaorig;
u16 txdalign; /* #bytes added to alloc'd mem to align txd */
u32 txdalloc; /* #bytes allocated for the ring */
u32 xmtptrbase; /* When using unaligned descriptors, the ptr register * is not just an index, it needs all 13 bits to be * an offset from the addr register.
*/
u16 nrxd; /* # rx descriptors tunable */
u16 rxin; /* index of next descriptor to reclaim */
u16 rxout; /* index of next descriptor to post */ /* pointer to parallel array of pointers to packets */ struct sk_buff **rxp; /* Aligned physical address of descriptor ring */
dma_addr_t rxdpa; /* Original physical address of descriptor ring */
dma_addr_t rxdpaorig;
u16 rxdalign; /* #bytes added to alloc'd mem to align rxd */
u32 rxdalloc; /* #bytes allocated for the ring */
u32 rcvptrbase; /* Base for ptr reg when using unaligned descriptors */
/* tunables */ unsignedint rxbufsize; /* rx buffer size in bytes, not including * the extra headroom
*/
uint rxextrahdrroom; /* extra rx headroom, reverseved to assist upper * stack, e.g. some rx pkt buffers will be * bridged to tx side without byte copying. * The extra headroom needs to be large enough * to fit txheader needs. Some dongle driver may * not need it.
*/
uint nrxpost; /* # rx buffers to keep posted */ unsignedint rxoffset; /* rxcontrol offset */ /* add to get dma address of descriptor ring, low 32 bits */
uint ddoffsetlow; /* high 32 bits */
uint ddoffsethigh; /* add to get dma address of data buffer, low 32 bits */
uint dataoffsetlow; /* high 32 bits */
uint dataoffsethigh; /* descriptor base need to be aligned or not */ bool aligndesc_4k;
};
/* Check for odd number of 1's */ static u32 parity32(__le32 data)
{ /* no swap needed for counting 1's */
u32 par_data = *(u32 *)&data;
/* If trying to enable parity, check if parity is actually supported */ if (dmactrlflags & DMA_CTRL_PEN) {
u32 control;
control = bcma_read32(di->core, DMA64TXREGOFFS(di, control));
bcma_write32(di->core, DMA64TXREGOFFS(di, control),
control | D64_XC_PD); if (bcma_read32(di->core, DMA64TXREGOFFS(di, control)) &
D64_XC_PD) /* We *can* disable it so it is supported, * restore control register
*/
bcma_write32(di->core, DMA64TXREGOFFS(di, control),
control); else /* Not supported, don't allow it to be enabled */
dmactrlflags &= ~DMA_CTRL_PEN;
}
/* * return true if this dma engine supports DmaExtendedAddrChanges, * otherwise false
*/ staticbool _dma_isaddrext(struct dma_info *di)
{ /* DMA64 supports full 32- or 64-bit operation. AE is always valid */
/* not all tx or rx channel are available */ if (di->d64txregbase != 0) { if (!_dma64_addrext(di, DMA64TXREGOFFS(di, control)))
brcms_dbg_dma(di->core, "%s: DMA64 tx doesn't have AE set\n",
di->name); returntrue;
} elseif (di->d64rxregbase != 0) { if (!_dma64_addrext(di, DMA64RXREGOFFS(di, control)))
brcms_dbg_dma(di->core, "%s: DMA64 rx doesn't have AE set\n",
di->name); returntrue;
}
/* Check to see if the descriptors need to be aligned on 4K/8K or not */ if (di->d64txregbase != 0) {
bcma_write32(di->core, DMA64TXREGOFFS(di, addrlow), 0xff0);
addrl = bcma_read32(di->core, DMA64TXREGOFFS(di, addrlow)); if (addrl != 0) returnfalse;
} elseif (di->d64rxregbase != 0) {
bcma_write32(di->core, DMA64RXREGOFFS(di, addrlow), 0xff0);
addrl = bcma_read32(di->core, DMA64RXREGOFFS(di, addrlow)); if (addrl != 0) returnfalse;
} returntrue;
}
/* * Descriptor table must start at the DMA hardware dictated alignment, so * allocated memory must be large enough to support this requirement.
*/ staticvoid *dma_alloc_consistent(struct dma_info *di, uint size,
u16 align_bits, uint *alloced,
dma_addr_t *pap)
{ if (align_bits) {
u16 align = (1 << align_bits); if (!IS_ALIGNED(PAGE_SIZE, align))
size += align;
*alloced = size;
} return dma_alloc_coherent(di->dmadev, size, pap, GFP_ATOMIC);
}
/* This function ensures that the DMA descriptor ring will not get allocated * across Page boundary. If the allocation is done across the page boundary * at the first time, then it is freed and the allocation is done at * descriptor ring size aligned location. This will ensure that the ring will * not cross page boundary
*/ staticvoid *dma_ringalloc(struct dma_info *di, u32 boundary, uint size,
u16 *alignbits, uint *alloced,
dma_addr_t *descpa)
{ void *va;
u32 desc_strtaddr;
u32 alignbytes = 1 << *alignbits;
va = dma_alloc_consistent(di, size, *alignbits, alloced, descpa);
/* * Default flags (which can be changed by the driver calling * dma_ctrlflags before enable): For backwards compatibility * both Rx Overflow Continue and Parity are DISABLED.
*/
_dma_ctrlflags(di, DMA_CTRL_ROC | DMA_CTRL_PEN, 0);
/* * figure out the DMA physical address offset for dd and data * PCI/PCIE: they map silicon backplace address to zero * based memory, need offset * Other bus: use zero SI_BUS BIGENDIAN kludge: use sdram * swapped region for data buffer, not descriptor
*/
di->ddoffsetlow = 0;
di->dataoffsetlow = 0; /* for pci bus, add offset */ if (sii->icbus->hosttype == BCMA_HOSTTYPE_PCI) { /* add offset for pcie with DMA64 bus */
di->ddoffsetlow = 0;
di->ddoffsethigh = SI_PCIE_DMA_H32;
}
di->dataoffsetlow = di->ddoffsetlow;
di->dataoffsethigh = di->ddoffsethigh;
/* does the descriptor need to be aligned and if yes, on 4K/8K or not */
di->aligndesc_4k = _dma_descriptor_align(di); if (di->aligndesc_4k) {
di->dmadesc_align = D64RINGALIGN_BITS; if ((ntxd < D64MAXDD / 2) && (nrxd < D64MAXDD / 2)) /* for smaller dd table, HW relax alignment reqmnt */
di->dmadesc_align = D64RINGALIGN_BITS - 1;
} else {
di->dmadesc_align = 4; /* 16 byte alignment */
}
/* DMA engine with out alignment requirement requires table to be inited * before enabling the engine
*/ if (!di->aligndesc_4k)
_dma_ddtable_init(di, DMA_RX, di->rxdpa);
_dma_rxenable(di);
if (di->aligndesc_4k)
_dma_ddtable_init(di, DMA_RX, di->rxdpa);
}
/* * !! rx entry routine * returns the number packages in the next frame, or 0 if there are no more * if DMA_CTRL_RXMULTI is defined, DMA scattering(multiple buffers) is * supported with pkts chain * otherwise, it's treated as giant pkt and will be tossed. * The DMA scattering starts with normal DMA header, followed by first * buffer data. After it reaches the max size of buffer, the data continues * in next DMA descriptor buffer WITHOUT DMA header
*/ int dma_rx(struct dma_pub *pub, struct sk_buff_head *skb_list)
{ struct dma_info *di = container_of(pub, struct dma_info, dma); struct sk_buff_head dma_frames; struct sk_buff *p, *next;
uint len;
uint pkt_len; int resid = 0; int pktcnt = 1;
skb_queue_head_init(&dma_frames);
next_frame:
p = _dma_getnextrxp(di, false); if (p == NULL) return 0;
len = le16_to_cpu(*(__le16 *) (p->data));
brcms_dbg_dma(di->core, "%s: dma_rx len %d\n", di->name, len);
dma_spin_for_len(len, p);
/* set actual length */
pkt_len = min((di->rxoffset + len), di->rxbufsize);
__skb_trim(p, pkt_len);
skb_queue_tail(&dma_frames, p);
resid = len - (di->rxbufsize - di->rxoffset);
/* check for single or multi-buffer rx */ if (resid > 0) { while ((resid > 0) && (p = _dma_getnextrxp(di, false))) {
pkt_len = min_t(uint, resid, di->rxbufsize);
__skb_trim(p, pkt_len);
skb_queue_tail(&dma_frames, p);
resid -= di->rxbufsize;
pktcnt++;
}
/* * post receive buffers * Return false if refill failed completely or dma mapping failed. The ring * is empty, which will stall the rx dma and user might want to call rxfill * again asap. This is unlikely to happen on a memory-rich NIC, but often on * memory-constrained dongle.
*/ bool dma_rxfill(struct dma_pub *pub)
{ struct dma_info *di = container_of(pub, struct dma_info, dma); struct sk_buff *p;
u16 rxin, rxout;
u32 flags = 0;
uint n;
uint i;
dma_addr_t pa;
uint extra_offset = 0; bool ring_empty;
ring_empty = false;
/* * Determine how many receive buffers we're lacking * from the full complement, allocate, initialize, * and post them, then update the chip rx lastdscr.
*/
rxin = di->rxin;
rxout = di->rxout;
n = di->nrxpost - nrxdactive(di, rxin, rxout);
brcms_dbg_dma(di->core, "%s: post %d\n", di->name, n);
if (di->rxbufsize > BCMEXTRAHDROOM)
extra_offset = di->rxextrahdrroom;
for (i = 0; i < n; i++) { /* * the di->rxbufsize doesn't include the extra headroom, * we need to add it to the size to be allocated
*/
p = brcmu_pkt_buf_get_skb(di->rxbufsize + extra_offset);
if (p == NULL) {
brcms_dbg_dma(di->core, "%s: out of rxbufs\n",
di->name); if (i == 0 && dma64_rxidle(di)) {
brcms_dbg_dma(di->core, "%s: ring is empty !\n",
di->name);
ring_empty = true;
}
di->dma.rxnobuf++; break;
} /* reserve an extra headroom, if applicable */ if (extra_offset)
skb_pull(p, extra_offset);
/* Do a cached write instead of uncached write since DMA_MAP * will flush the cache.
*/
*(u32 *) (p->data) = 0;
pa = dma_map_single(di->dmadev, p->data, di->rxbufsize,
DMA_FROM_DEVICE); if (dma_mapping_error(di->dmadev, pa)) {
brcmu_pkt_buf_free_skb(p); returnfalse;
}
/* save the free packet pointer */
di->rxp[rxout] = p;
/* reset flags for each descriptor */
flags = 0; if (rxout == (di->nrxd - 1))
flags = D64_CTRL1_EOT;
/* get the address of the var in order to change later */ unsignedlong dma_getvar(struct dma_pub *pub, constchar *name)
{ struct dma_info *di = container_of(pub, struct dma_info, dma);
if (!strcmp(name, "&txavail")) return (unsignedlong)&(di->dma.txavail); return 0;
}
/* DMA engine with out alignment requirement requires table to be inited * before enabling the engine
*/ if (!di->aligndesc_4k)
_dma_ddtable_init(di, DMA_TX, di->txdpa);
if ((di->dma.dmactrlflags & DMA_CTRL_PEN) == 0)
control |= D64_XC_PD;
bcma_set32(di->core, DMA64TXREGOFFS(di, control), control);
/* DMA engine with alignment requirement requires table to be inited * before enabling the engine
*/ if (di->aligndesc_4k)
_dma_ddtable_init(di, DMA_TX, di->txdpa);
}
brcms_dbg_dma(di->core, "%s: %s\n",
di->name,
range == DMA_RANGE_ALL ? "all" :
range == DMA_RANGE_TRANSMITTED ? "transmitted" : "transferred");
if (di->txin == di->txout) return;
while ((p = dma_getnexttxp(pub, range))) { /* For unframed data, we don't have any packets to free */ if (!(di->dma.dmactrlflags & DMA_CTRL_UNFRAMED))
brcmu_pkt_buf_free_skb(p);
}
}
if (WARN_ON(nexttxd(di, txout) == di->txin)) return;
/* * obtain and initialize transmit descriptor entry.
*/
data = p->data;
len = p->len;
/* get physical address of buffer start */
pa = dma_map_single(di->dmadev, data, len, DMA_TO_DEVICE); /* if mapping failed, free skb */ if (dma_mapping_error(di->dmadev, pa)) {
brcmu_pkt_buf_free_skb(p); return;
} /* With a DMA segment list, Descriptor table is filled * using the segment list instead of looping over * buffers in multi-chain DMA. Therefore, EOF for SGLIST * is when end of segment list is reached.
*/
flags = D64_CTRL1_SOF | D64_CTRL1_IOC | D64_CTRL1_EOF; if (txout == (di->ntxd - 1))
flags |= D64_CTRL1_EOT;
ret = brcms_c_ampdu_add_frame(session, p); if (ret == -ENOSPC) { /* * AMPDU cannot accommodate this frame. Close out the in- * progress AMPDU session and start a new one.
*/
ampdu_finalize(di);
ret = brcms_c_ampdu_add_frame(session, p);
}
WARN_ON(ret);
}
/* Update count of available tx descriptors based on current DMA state */ staticvoid dma_update_txavail(struct dma_info *di)
{ /* * Available space is number of descriptors less the number of * active descriptors and the number of queued AMPDU frames.
*/
di->dma.txavail = di->ntxd - ntxdactive(di, di->txin, di->txout) -
skb_queue_len(&di->ampdu_session.skb_list) - 1;
}
/* * !! tx entry routine * WARNING: call must check the return value for error. * the error(toss frames) could be fatal and cause many subsequent hard * to debug problems
*/ int dma_txfast(struct brcms_c_info *wlc, struct dma_pub *pub, struct sk_buff *p)
{ struct dma_info *di = container_of(pub, struct dma_info, dma); struct brcms_ampdu_session *session = &di->ampdu_session; struct ieee80211_tx_info *tx_info; bool is_ampdu;
/* no use to transmit a zero length packet */ if (p->len == 0) return 0;
/* return nonzero if out of tx descriptors */ if (di->dma.txavail == 0 || nexttxd(di, di->txout) == di->txin) goto outoftxd;
/* kick the chip */ if (is_ampdu) { /* * Start sending data if we've got a full AMPDU, there's * no more space in the DMA ring, or the ring isn't * currently transmitting.
*/ if (skb_queue_len(&session->skb_list) == session->max_ampdu_frames ||
di->dma.txavail == 0 || dma64_txidle(di))
ampdu_finalize(di);
} else {
bcma_write32(di->core, DMA64TXREGOFFS(di, ptr),
di->xmtptrbase + I2B(di->txout, struct dma64desc));
}
return 0;
outoftxd:
brcms_dbg_dma(di->core, "%s: out of txds !!!\n", di->name);
brcmu_pkt_buf_free_skb(p);
di->dma.txavail = 0;
di->dma.txnobuf++; return -ENOSPC;
}
/* * If we have an active AMPDU session and are not transmitting, * this function will force tx to start.
*/ void dma_kick_tx(struct dma_pub *pub)
{ struct dma_info *di = container_of(pub, struct dma_info, dma); struct brcms_ampdu_session *session = &di->ampdu_session;
if (!skb_queue_empty(&session->skb_list) && dma64_txidle(di))
ampdu_finalize(di);
}
/* * Reclaim next completed txd (txds if using chained buffers) in the range * specified and return associated packet. * If range is DMA_RANGE_TRANSMITTED, reclaim descriptors that have be * transmitted as noted by the hardware "CurrDescr" pointer. * If range is DMA_RANGE_TRANSFERED, reclaim descriptors that have be * transferred by the DMA as noted by the hardware "ActiveDescr" pointer. * If range is DMA_RANGE_ALL, reclaim all txd(s) posted to the ring and * return associated packet regardless of the value of hardware pointers.
*/ struct sk_buff *dma_getnexttxp(struct dma_pub *pub, enum txd_range range)
{ struct dma_info *di = container_of(pub, struct dma_info, dma);
u16 start, end, i;
u16 active_desc; struct sk_buff *txp;
brcms_dbg_dma(di->core, "%s: %s\n",
di->name,
range == DMA_RANGE_ALL ? "all" :
range == DMA_RANGE_TRANSMITTED ? "transmitted" : "transferred");
if (di->ntxd == 0) return NULL;
txp = NULL;
start = di->txin; if (range == DMA_RANGE_ALL)
end = di->txout; else {
end = (u16) (B2I(((bcma_read32(di->core,
DMA64TXREGOFFS(di, status0)) &
D64_XS0_CD_MASK) - di->xmtptrbase) &
D64_XS0_CD_MASK, struct dma64desc));
/* * Mac80211 initiated actions sometimes require packets in the DMA queue to be * modified. The modified portion of the packet is not under control of the DMA * engine. This function calls a caller-supplied function for each packet in * the caller specified dma chain.
*/ void dma_walk_packets(struct dma_pub *dmah, void (*callback_fnc)
(void *pkt, void *arg_a), void *arg_a)
{ struct dma_info *di = container_of(dmah, struct dma_info, dma);
uint i = di->txin;
uint end = di->txout; struct sk_buff *skb; struct ieee80211_tx_info *tx_info;
while (i != end) {
skb = di->txp[i]; if (skb != NULL) {
tx_info = (struct ieee80211_tx_info *)skb->cb;
(callback_fnc)(tx_info, arg_a);
}
i = nexttxd(di, i);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.