kern/114915: pcn (sys/pci/if_pcn.c) ethernet driver fixes
(including endianness)
Till Straumann
strauman at slac.stanford.edu
Wed Jul 25 22:00:09 UTC 2007
>Number: 114915
>Category: kern
>Synopsis: pcn (sys/pci/if_pcn.c) ethernet driver fixes (including endianness)
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: freebsd-bugs
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: sw-bug
>Submitter-Id: current-users
>Arrival-Date: Wed Jul 25 22:00:08 GMT 2007
>Closed-Date:
>Last-Modified:
>Originator: Till Straumann
>Release: cvs head (as of 20070717)
>Organization:
SLAC
>Environment:
I am not running FreeBSD but found the problems described below
when porting the pcn driver to RTEMS. I believe the issues are
relevant to FreeBSD, too, so I report them here.
>Description:
a) The if_pcn driver is not endian-safe. It won't work on a big-endian platform.
b) The driver accesses a field in a RX descriptor after handing the descriptor
over to the pcn chip which may alter the value before the CPU access happens.
Unless the ring is completely filled this is unlikely
to cause problems but IMO it should nevertheless be fixed.
Also, IMO, descriptor fields should be 'volatile'.
c) No attempts are made to synchronize access to descriptors in memory --
reordering of accesses by the compiler and/or the CPU itself may cause
problems (e.g., if on a platform that does out-of-order stores the OWN bit
is effectively written before other fields in the descriptor are).
The attached patch addresses a-c. I don't know how to properly address c] in
FreeBSD (probably, descriptors should be allocated using bus_space_alloc()
rather than contigmalloc() and synchronized with bus_space_barrier() ?).
Anyhow, the places where synchronization would be prudent can be located in
the patch by looking for
#ifdef __rtems__
membarrier_xy();
#endif
constructs. In addition to using synchronization, relevant fields in the
descriptors have been declared 'volatile'.
>How-To-Repeat:
>Fix:
See attached patch.
Patch attached with submission follows:
*** if_pcn.c.orig 2007-07-17 16:55:23.000000000 -0700
--- if_pcn.c 2007-07-25 14:21:18.000000000 -0700
***************
*** 607,612 ****
--- 607,618 ----
eaddr[0] = CSR_READ_4(sc, PCN_IO32_APROM00);
eaddr[1] = CSR_READ_4(sc, PCN_IO32_APROM01);
+ /* if we are on a big endian host, read did swap the byte order
+ * and we need to swap back. On a LE host, the following is a noop
+ */
+ eaddr[0] = htole32(eaddr[0]);
+ eaddr[1] = htole32(eaddr[1]);
+
callout_init_mtx(&sc->pcn_stat_callout, &sc->pcn_mtx, 0);
sc->pcn_ldata = contigmalloc(sizeof(struct pcn_list_data), M_DEVBUF,
***************
*** 753,761 ****
for (i = 0; i < PCN_TX_LIST_CNT; i++) {
cd->pcn_tx_chain[i] = NULL;
! ld->pcn_tx_list[i].pcn_tbaddr = 0;
! ld->pcn_tx_list[i].pcn_txctl = 0;
! ld->pcn_tx_list[i].pcn_txstat = 0;
}
cd->pcn_tx_prod = cd->pcn_tx_cons = cd->pcn_tx_cnt = 0;
--- 759,767 ----
for (i = 0; i < PCN_TX_LIST_CNT; i++) {
cd->pcn_tx_chain[i] = NULL;
! ld->pcn_tx_list[i].pcn_tbaddr = htole32(0);
! ld->pcn_tx_list[i].pcn_txctl = htole32(0);
! ld->pcn_tx_list[i].pcn_txstat = htole32(0);
}
cd->pcn_tx_prod = cd->pcn_tx_cons = cd->pcn_tx_cnt = 0;
***************
*** 820,829 ****
m_adj(m_new, ETHER_ALIGN);
sc->pcn_cdata.pcn_rx_chain[idx] = m_new;
! c->pcn_rbaddr = vtophys(mtod(m_new, caddr_t));
! c->pcn_bufsz = (~(PCN_RXLEN) + 1) & PCN_RXLEN_BUFSZ;
! c->pcn_bufsz |= PCN_RXLEN_MBO;
! c->pcn_rxstat = PCN_RXSTAT_STP|PCN_RXSTAT_ENP|PCN_RXSTAT_OWN;
return(0);
}
--- 826,838 ----
m_adj(m_new, ETHER_ALIGN);
sc->pcn_cdata.pcn_rx_chain[idx] = m_new;
! c->pcn_rbaddr = htole32(vtophys(mtod(m_new, caddr_t)));
! c->pcn_bufsz = htole16((~(PCN_RXLEN) + 1) & PCN_RXLEN_BUFSZ);
! c->pcn_bufsz |= htole16(PCN_RXLEN_MBO);
! #ifdef __rtems__
! membarrier_w();
! #endif
! c->pcn_rxstat = htole16(PCN_RXSTAT_STP|PCN_RXSTAT_ENP|PCN_RXSTAT_OWN);
return(0);
}
***************
*** 839,845 ****
struct mbuf *m;
struct ifnet *ifp;
struct pcn_rx_desc *cur_rx;
! int i;
PCN_LOCK_ASSERT(sc);
--- 848,854 ----
struct mbuf *m;
struct ifnet *ifp;
struct pcn_rx_desc *cur_rx;
! int i,len;
PCN_LOCK_ASSERT(sc);
***************
*** 847,855 ****
--- 856,868 ----
i = sc->pcn_cdata.pcn_rx_prod;
while(PCN_OWN_RXDESC(&sc->pcn_ldata->pcn_rx_list[i])) {
+ #ifdef __rtems__
+ membarrier_rw();
+ #endif
cur_rx = &sc->pcn_ldata->pcn_rx_list[i];
m = sc->pcn_cdata.pcn_rx_chain[i];
sc->pcn_cdata.pcn_rx_chain[i] = NULL;
+ len = le16toh(cur_rx->pcn_rxlen);
/*
* If an error occurs, update stats, clear the
***************
*** 857,869 ****
* it should simply get re-used next time this descriptor
* comes up in the ring.
*/
! if (cur_rx->pcn_rxstat & PCN_RXSTAT_ERR) {
ifp->if_ierrors++;
pcn_newbuf(sc, i, m);
PCN_INC(i, PCN_RX_LIST_CNT);
continue;
}
if (pcn_newbuf(sc, i, NULL)) {
/* Ran out of mbufs; recycle this one. */
pcn_newbuf(sc, i, m);
--- 870,885 ----
* it should simply get re-used next time this descriptor
* comes up in the ring.
*/
! if (le16toh(cur_rx->pcn_rxstat) & PCN_RXSTAT_ERR) {
ifp->if_ierrors++;
pcn_newbuf(sc, i, m);
PCN_INC(i, PCN_RX_LIST_CNT);
continue;
}
+ /* MUST NOT use cur_rx descriptor beyond this point;
+ * pcn_newbuf() writes to it and hands it over to the chip.
+ */
if (pcn_newbuf(sc, i, NULL)) {
/* Ran out of mbufs; recycle this one. */
pcn_newbuf(sc, i, m);
***************
*** 877,883 ****
/* No errors; receive the packet. */
ifp->if_ipackets++;
m->m_len = m->m_pkthdr.len =
! cur_rx->pcn_rxlen - ETHER_CRC_LEN;
m->m_pkthdr.rcvif = ifp;
PCN_UNLOCK(sc);
--- 893,899 ----
/* No errors; receive the packet. */
ifp->if_ipackets++;
m->m_len = m->m_pkthdr.len =
! len - ETHER_CRC_LEN;
m->m_pkthdr.rcvif = ifp;
PCN_UNLOCK(sc);
***************
*** 915,937 ****
if (!PCN_OWN_TXDESC(cur_tx))
break;
!
! if (!(cur_tx->pcn_txctl & PCN_TXCTL_ENP)) {
sc->pcn_cdata.pcn_tx_cnt--;
PCN_INC(idx, PCN_TX_LIST_CNT);
continue;
}
! if (cur_tx->pcn_txctl & PCN_TXCTL_ERR) {
ifp->if_oerrors++;
! if (cur_tx->pcn_txstat & PCN_TXSTAT_EXDEF)
ifp->if_collisions++;
! if (cur_tx->pcn_txstat & PCN_TXSTAT_RTRY)
ifp->if_collisions++;
}
ifp->if_collisions +=
! cur_tx->pcn_txstat & PCN_TXSTAT_TRC;
ifp->if_opackets++;
if (sc->pcn_cdata.pcn_tx_chain[idx] != NULL) {
--- 931,955 ----
if (!PCN_OWN_TXDESC(cur_tx))
break;
! #ifdef __rtems__
! membarrier_rw();
! #endif
! if (!(le32toh(cur_tx->pcn_txctl) & PCN_TXCTL_ENP)) {
sc->pcn_cdata.pcn_tx_cnt--;
PCN_INC(idx, PCN_TX_LIST_CNT);
continue;
}
! if (le32toh(cur_tx->pcn_txctl) & PCN_TXCTL_ERR) {
ifp->if_oerrors++;
! if (le32toh(cur_tx->pcn_txstat) & PCN_TXSTAT_EXDEF)
ifp->if_collisions++;
! if (le32toh(cur_tx->pcn_txstat) & PCN_TXSTAT_RTRY)
ifp->if_collisions++;
}
ifp->if_collisions +=
! le32toh(cur_tx->pcn_txstat) & PCN_TXSTAT_TRC;
ifp->if_opackets++;
if (sc->pcn_cdata.pcn_tx_chain[idx] != NULL) {
***************
*** 1058,1070 ****
if ((PCN_TX_LIST_CNT - (sc->pcn_cdata.pcn_tx_cnt + cnt)) < 2)
return(ENOBUFS);
f = &sc->pcn_ldata->pcn_tx_list[frag];
! f->pcn_txctl = (~(m->m_len) + 1) & PCN_TXCTL_BUFSZ;
! f->pcn_txctl |= PCN_TXCTL_MBO;
! f->pcn_tbaddr = vtophys(mtod(m, vm_offset_t));
if (cnt == 0)
! f->pcn_txctl |= PCN_TXCTL_STP;
else
! f->pcn_txctl |= PCN_TXCTL_OWN;
cur = frag;
PCN_INC(frag, PCN_TX_LIST_CNT);
cnt++;
--- 1076,1091 ----
if ((PCN_TX_LIST_CNT - (sc->pcn_cdata.pcn_tx_cnt + cnt)) < 2)
return(ENOBUFS);
f = &sc->pcn_ldata->pcn_tx_list[frag];
! f->pcn_txctl = htole32((~(m->m_len) + 1) & PCN_TXCTL_BUFSZ);
! f->pcn_txctl |= htole32(PCN_TXCTL_MBO);
! f->pcn_tbaddr = htole32(vtophys(mtod(m, vm_offset_t)));
! #ifdef __rtems__
! membarrier_w();
! #endif
if (cnt == 0)
! f->pcn_txctl |= htole32(PCN_TXCTL_STP);
else
! f->pcn_txctl |= htole32(PCN_TXCTL_OWN);
cur = frag;
PCN_INC(frag, PCN_TX_LIST_CNT);
cnt++;
***************
*** 1075,1082 ****
sc->pcn_cdata.pcn_tx_chain[cur] = m_head;
sc->pcn_ldata->pcn_tx_list[cur].pcn_txctl |=
! PCN_TXCTL_ENP|PCN_TXCTL_ADD_FCS|PCN_TXCTL_MORE_LTINT;
! sc->pcn_ldata->pcn_tx_list[*txidx].pcn_txctl |= PCN_TXCTL_OWN;
sc->pcn_cdata.pcn_tx_cnt += cnt;
*txidx = frag;
--- 1096,1106 ----
sc->pcn_cdata.pcn_tx_chain[cur] = m_head;
sc->pcn_ldata->pcn_tx_list[cur].pcn_txctl |=
! htole32(PCN_TXCTL_ENP|PCN_TXCTL_ADD_FCS|PCN_TXCTL_MORE_LTINT);
! #ifdef __rtems__
! membarrier_w();
! #endif
! sc->pcn_ldata->pcn_tx_list[*txidx].pcn_txctl |= htole32(PCN_TXCTL_OWN);
sc->pcn_cdata.pcn_tx_cnt += cnt;
*txidx = frag;
***************
*** 1208,1219 ****
ife = mii->mii_media.ifm_cur;
/* Set MAC address */
! pcn_csr_write(sc, PCN_CSR_PAR0,
! ((u_int16_t *)IF_LLADDR(sc->pcn_ifp))[0]);
! pcn_csr_write(sc, PCN_CSR_PAR1,
! ((u_int16_t *)IF_LLADDR(sc->pcn_ifp))[1]);
! pcn_csr_write(sc, PCN_CSR_PAR2,
! ((u_int16_t *)IF_LLADDR(sc->pcn_ifp))[2]);
/* Init circular RX list. */
if (pcn_list_rx_init(sc) == ENOBUFS) {
--- 1232,1246 ----
ife = mii->mii_media.ifm_cur;
/* Set MAC address */
! { unsigned tmp;
! /* fix endinanness; LLADDR gets swapped on a BE machine */
! tmp = htole16(((u_int16_t *)IF_LLADDR(sc->pcn_ifp))[0]);
! pcn_csr_write(sc, PCN_CSR_PAR0, tmp);
! tmp = htole16(((u_int16_t *)IF_LLADDR(sc->pcn_ifp))[1]);
! pcn_csr_write(sc, PCN_CSR_PAR1, tmp);
! tmp = htole16(((u_int16_t *)IF_LLADDR(sc->pcn_ifp))[2]);
! pcn_csr_write(sc, PCN_CSR_PAR2, tmp);
! }
/* Init circular RX list. */
if (pcn_list_rx_init(sc) == ENOBUFS) {
Index: if_pcnreg.h
===================================================================
RCS file: /afs/slac/g/spear/cvsrep/drivers/if_pcn-new-rtems/if_pcnreg.h,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 if_pcnreg.h
*** if_pcnreg.h 17 Jul 2007 23:55:23 -0000 1.1.1.1
--- if_pcnreg.h 25 Jul 2007 21:15:33 -0000
***************
*** 362,371 ****
#define PCN_PHY_EXTERNAL 0x0002
struct pcn_rx_desc {
! u_int16_t pcn_rxlen;
! u_int16_t pcn_rsvd0;
u_int16_t pcn_bufsz;
! u_int16_t pcn_rxstat;
u_int32_t pcn_rbaddr;
u_int32_t pcn_uspace;
};
--- 362,371 ----
#define PCN_PHY_EXTERNAL 0x0002
struct pcn_rx_desc {
! volatile u_int16_t pcn_rxlen;
! volatile u_int16_t pcn_rsvd0;
u_int16_t pcn_bufsz;
! volatile u_int16_t pcn_rxstat;
u_int32_t pcn_rbaddr;
u_int32_t pcn_uspace;
};
***************
*** 383,393 ****
#define PCN_RXLEN_MBO 0xF000
#define PCN_RXLEN_BUFSZ 0x0FFF
! #define PCN_OWN_RXDESC(x) (((x)->pcn_rxstat & PCN_RXSTAT_OWN) == 0)
struct pcn_tx_desc {
! u_int32_t pcn_txstat;
! u_int32_t pcn_txctl;
u_int32_t pcn_tbaddr;
u_int32_t pcn_uspace;
};
--- 383,393 ----
#define PCN_RXLEN_MBO 0xF000
#define PCN_RXLEN_BUFSZ 0x0FFF
! #define PCN_OWN_RXDESC(x) ((le16toh((x)->pcn_rxstat) & PCN_RXSTAT_OWN) == 0)
struct pcn_tx_desc {
! volatile u_int32_t pcn_txstat;
! volatile u_int32_t pcn_txctl;
u_int32_t pcn_tbaddr;
u_int32_t pcn_uspace;
};
***************
*** 412,418 ****
#define PCN_TXCTL_MBO 0x0000F000
#define PCN_TXCTL_BUFSZ 0x00000FFF
! #define PCN_OWN_TXDESC(x) (((x)->pcn_txctl & PCN_TXCTL_OWN) == 0)
#define PCN_RX_LIST_CNT 64
#define PCN_TX_LIST_CNT 256
--- 412,418 ----
#define PCN_TXCTL_MBO 0x0000F000
#define PCN_TXCTL_BUFSZ 0x00000FFF
! #define PCN_OWN_TXDESC(x) ((le32toh((x)->pcn_txctl) & PCN_TXCTL_OWN) == 0)
#define PCN_RX_LIST_CNT 64
#define PCN_TX_LIST_CNT 256
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-bugs
mailing list