Date: Mon, 16 Nov 2020 10:19:10 +0900 From: YongHyeon PYUN <pyunyh@gmail.com> To: Carsten =?iso-8859-1?Q?B=E4cker?= <carbaecker@gmx.de> Cc: Hans Petter Selasky <hps@selasky.org>, Kristof Provost <kp@FreeBSD.org>, freebsd-arm@freebsd.org, freebsd-hackers@freebsd.org Subject: Re: Problem with checksum offloading on RPi3 (PF + Jails involved) Message-ID: <20201116011910.GB1941@michelle> In-Reply-To: <e43b42e0-80fe-847a-f1bc-025b6914f98a@gmx.de> References: <748edc3d-4ef7-c4de-291f-7c0b460a6052@gmx.de> <D8CE4762-4D94-47C7-A8D1-6C537766813B@FreeBSD.org> <5130ee46-5832-d4df-d774-c6bd32e10b30@gmx.de> <A3890336-BE8F-438C-8C3E-7B21FB729FCA@FreeBSD.org> <20201029213622.GM31099@funkthat.com> <55713894-A896-4F12-ABB9-93DFEB2F16B9@FreeBSD.org> <20201103045215.GA2524@michelle> <46d08198-530c-cb4b-efa8-4edaf89471c1@selasky.org> <4dfaa9a3-c085-8466-a6e4-19f988b5ed3d@selasky.org> <e43b42e0-80fe-847a-f1bc-025b6914f98a@gmx.de>
next in thread | previous in thread | raw e-mail | index | archive | help
--liOOAslEiF7prFVr
Content-Type: text/plain; charset=iso-8859-1
Content-Disposition: inline
Content-Transfer-Encoding: 8bit
On Wed, Nov 04, 2020 at 07:36:44AM +0100, Carsten Bäcker wrote:
[...]
> Hi,
>
> i applied the patch on -CURRENT but got a panic right after loading the
> kernel. Most likely an unrelated problem.
>
> But i was able to apply the patch on releng/12.2 (with an offset).
> Unfortunately it doesn't change the previously described behavior with
> rxcsum and i didn't manage to get any reasonable debug-output.
>
> Since i can easily reproduce the problem. How else can i help?
>
Finally had time to read the LAN89530 data sheet. The data sheet
still does not clear on several cases and it requires real H/W to
experiment for various cases. I created a patch which adds more
RX validation but it was not tested at all due to lack of H/W. Also
I even don't know whether it works or not after this change. When
it does not work it would be good to know debug out of smsc(4).
--liOOAslEiF7prFVr
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="smsc.rx.patch"
Index: sys/dev/usb/net/if_smsc.c
===================================================================
--- sys/dev/usb/net/if_smsc.c (revision 367712)
+++ sys/dev/usb/net/if_smsc.c (working copy)
@@ -94,6 +94,8 @@ __FBSDID("$FreeBSD$");
#include <netinet/in.h>
#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
#include "opt_platform.h"
@@ -198,6 +200,9 @@ static void smsc_ifmedia_sts(struct ifnet *, struc
static int smsc_chip_init(struct smsc_softc *sc);
static int smsc_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data);
+static void smsc_rxfixup(struct ifnet *ifp, struct mbuf *m, int *pktlen);
+static int smsc_rxeof(struct usb_ether *ue, struct usb_page_cache *pc,
+ unsigned int offset, struct ifnet *ifp, int pktlen);
static const struct usb_config smsc_config[SMSC_N_TRANSFER] = {
[SMSC_BULK_DT_WR] = {
@@ -795,11 +800,8 @@ static int smsc_sethwcsum(struct smsc_softc *sc)
return (err);
}
- /* Enable/disable the Rx checksum */
- if ((ifp->if_capabilities & ifp->if_capenable) & IFCAP_RXCSUM)
- val |= SMSC_COE_CTRL_RX_EN;
- else
- val &= ~SMSC_COE_CTRL_RX_EN;
+ /* Always enable Rx checksum to simplify RX packet validation. */
+ val |= SMSC_COE_CTRL_RX_EN;
/* Enable/disable the Tx checksum (currently not supported) */
if ((ifp->if_capabilities & ifp->if_capenable) & IFCAP_TXCSUM)
@@ -925,6 +927,119 @@ smsc_init(struct usb_ether *ue)
smsc_start(ue);
}
+static void
+smsc_rxfixup(struct ifnet *ifp, struct mbuf *m, int *pktlen)
+{
+ struct ether_header *eh;
+ struct ip *ip;
+ struct udphdr *uh;
+ int hlen, iplen, len;
+ uint16_t csum;
+
+ len = *pktlen;
+ if (len < sizeof(struct ether_header) + sizeof(struct ip))
+ return;
+ eh = mtod(m, struct ether_header *);
+ if (eh->ether_type != htons(ETHERTYPE_IP))
+ return;
+ ip = (struct ip *)(eh + 1);
+ if (ip->ip_v != IPVERSION)
+ return;
+
+ hlen = ip->ip_hl << 2;
+ len -= sizeof(struct ether_header);
+ if (hlen < sizeof(struct ip))
+ return;
+ if (ntohs(ip->ip_len) < hlen)
+ return;
+ iplen = ntohs(ip->ip_len);
+ if (iplen != len) {
+ if (iplen < sizeof(struct ip) + 26) {
+ /*
+ * XXX
+ * It seems H/W does not provide a reliable way to
+ * know received ethernet frame length when sender
+ * adds padding bytes to meet minimum ethernet frame
+ * length.
+ * Find actual ethernet frame size by checking total IP
+ * datagram length. This does not work for VLAN tagged
+ * frames or non-IPv4 datagrams though.
+ */
+ *pktlen -= (26 - iplen);
+
+ /*
+ * The checksum appears to be simplistically calculated
+ * over the udp/tcp header and data up to the end of the
+ * eth frame. Which means if the eth frame is padded
+ * the csum calculation is incorrectly performed over
+ * the padding bytes as well. Therefore to be safe we
+ * ignore the H/W csum on frames less than or equal to
+ * 64 bytes.
+ */
+ }
+ /* IP datagram length does not match packet length. */
+ return;
+ }
+ if ((ifp->if_capenable & IFCAP_RXCSUM) == 0)
+ return;
+
+ if (ip->ip_off & htons(IP_MF | IP_OFFMASK))
+ return; /* can't handle fragmented packet. */
+
+ switch (ip->ip_p) {
+ case IPPROTO_TCP:
+ if (len < (hlen + sizeof(struct tcphdr)))
+ return;
+ break;
+ case IPPROTO_UDP:
+ if (len < (hlen + sizeof(struct udphdr)))
+ return;
+ uh = (struct udphdr *)((caddr_t)ip + hlen);
+ if (uh->uh_sum == 0)
+ return; /* no checksum. */
+ break;
+ default:
+ return;
+ }
+
+ if ((hlen - sizeof(struct ip)) > 0) {
+ /* XXX checksum fixup for IP options. */
+ return;
+ }
+ /* Extract H/W computed checksum. */
+ csum = be16dec(mtod(m, caddr_t) + *pktlen + ETHER_CRC_LEN);
+ m->m_pkthdr.csum_flags |= CSUM_DATA_VALID;
+ m->m_pkthdr.csum_data = csum;
+}
+
+static int
+smsc_rxeof(struct usb_ether *ue, struct usb_page_cache *pc, unsigned int offset,
+ struct ifnet *ifp, int pktlen)
+{
+ struct mbuf *m;
+
+ m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (m == NULL) {
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ return (ENOMEM);
+ }
+ m->m_len = m->m_pkthdr.len = MCLBYTES;
+ m_adj(m, ETHER_ALIGN);
+
+ usbd_copy_out(pc, offset, mtod(m, caddr_t), pktlen);
+
+ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
+ m->m_pkthdr.rcvif = ifp;
+ m->m_pkthdr.len = m->m_len = pktlen;
+
+ pktlen -= (ETHER_CRC_LEN + SMSC_RX_CSUM_LEN);
+ smsc_rxfixup(ifp, m, &pktlen);
+ m->m_pkthdr.len = m->m_len = pktlen;
+
+ (void)mbufq_enqueue(&ue->ue_rxq, m);
+ return (0);
+}
+
/**
* smsc_bulk_read_callback - Read callback used to process the USB URB
* @xfer: the USB transfer
@@ -942,7 +1057,6 @@ smsc_bulk_read_callback(struct usb_xfer *xfer, usb
struct smsc_softc *sc = usbd_xfer_softc(xfer);
struct usb_ether *ue = &sc->sc_ue;
struct ifnet *ifp = uether_getifp(ue);
- struct mbuf *m;
struct usb_page_cache *pc;
uint32_t rxhdr;
int pktlen;
@@ -956,7 +1070,7 @@ smsc_bulk_read_callback(struct usb_xfer *xfer, usb
case USB_ST_TRANSFERRED:
/* There is always a zero length frame after bringing the IF up */
- if (actlen < (sizeof(rxhdr) + ETHER_CRC_LEN))
+ if (actlen < (sizeof(rxhdr) + ETHER_MIN_LEN + SMSC_RX_CSUM_LEN))
goto tr_setup;
/* There maybe multiple packets in the USB frame, each will have a
@@ -975,9 +1089,19 @@ smsc_bulk_read_callback(struct usb_xfer *xfer, usb
goto tr_setup;
usbd_copy_out(pc, off, &rxhdr, sizeof(rxhdr));
- off += (sizeof(rxhdr) + ETHER_ALIGN);
+ off += sizeof(rxhdr);
rxhdr = le32toh(rxhdr);
-
+
+ /*
+ * XXX
+ * It seems H/W does not have a feature to limit maximum
+ * ethernet frame length allowed to receive path. The
+ * SMSC_RX_STAT_FRM_LENGTH macro can return up to 2^14
+ * bytes. I think it's too dangerous to trust the H/W
+ * reported RX size without further information from
+ * vendor. Drop all RX ethernet frames bigger than
+ * (MCLBYTES - ETHER_ALIGN) bytes for safety.
+ */
pktlen = (uint16_t)SMSC_RX_STAT_FRM_LENGTH(rxhdr);
smsc_dbg_printf(sc, "rx : rxhdr 0x%08x : pktlen %d : actlen %d : "
@@ -989,88 +1113,19 @@ smsc_bulk_read_callback(struct usb_xfer *xfer, usb
if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
if (rxhdr & SMSC_RX_STAT_COLLISION)
if_inc_counter(ifp, IFCOUNTER_COLLISIONS, 1);
- } else {
- /* Check if the ethernet frame is too big or too small */
- if ((pktlen < ETHER_HDR_LEN) || (pktlen > (actlen - off)))
+ if (pktlen > (MCLBYTES - ETHER_ALIGN)) {
+ /* Lost sync. */
goto tr_setup;
-
- /* Create a new mbuf to store the packet in */
- m = uether_newbuf();
- if (m == NULL) {
- smsc_warn_printf(sc, "failed to create new mbuf\n");
- if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
- goto tr_setup;
}
- if (pktlen > m->m_len) {
- smsc_dbg_printf(sc, "buffer too small %d vs %d bytes",
- pktlen, m->m_len);
- if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
- m_freem(m);
+ } else {
+ if (pktlen < (ETHER_MIN_LEN + SMSC_RX_CSUM_LEN) ||
+ pktlen > (MCLBYTES - ETHER_ALIGN) ||
+ pktlen > (actlen - off)) {
+ /* Lost sync. */
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
goto tr_setup;
}
- usbd_copy_out(pc, off, mtod(m, uint8_t *), pktlen);
-
- /* Check if RX TCP/UDP checksumming is being offloaded */
- if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) {
- struct ether_header *eh;
-
- eh = mtod(m, struct ether_header *);
-
- /* Remove the extra 2 bytes of the csum */
- pktlen -= 2;
-
- /* The checksum appears to be simplistically calculated
- * over the udp/tcp header and data up to the end of the
- * eth frame. Which means if the eth frame is padded
- * the csum calculation is incorrectly performed over
- * the padding bytes as well. Therefore to be safe we
- * ignore the H/W csum on frames less than or equal to
- * 64 bytes.
- *
- * Ignore H/W csum for non-IPv4 packets.
- */
- if ((be16toh(eh->ether_type) == ETHERTYPE_IP) &&
- (pktlen > ETHER_MIN_LEN)) {
- struct ip *ip;
-
- ip = (struct ip *)(eh + 1);
- if ((ip->ip_v == IPVERSION) &&
- ((ip->ip_p == IPPROTO_TCP) ||
- (ip->ip_p == IPPROTO_UDP))) {
- /* Indicate the UDP/TCP csum has been calculated */
- m->m_pkthdr.csum_flags |= CSUM_DATA_VALID;
-
- /* Copy the TCP/UDP checksum from the last 2 bytes
- * of the transfer and put in the csum_data field.
- */
- usbd_copy_out(pc, (off + pktlen),
- &m->m_pkthdr.csum_data, 2);
-
- /* The data is copied in network order, but the
- * csum algorithm in the kernel expects it to be
- * in host network order.
- */
- m->m_pkthdr.csum_data = ntohs(m->m_pkthdr.csum_data);
-
- smsc_dbg_printf(sc, "RX checksum offloaded (0x%04x)\n",
- m->m_pkthdr.csum_data);
- }
- }
-
- /* Need to adjust the offset as well or we'll be off
- * by 2 because the csum is removed from the packet
- * length.
- */
- off += 2;
- }
-
- /* Finally enqueue the mbuf on the receive queue */
- /* Remove 4 trailing bytes */
- if (pktlen < (4 + ETHER_HDR_LEN)) {
- m_freem(m);
- goto tr_setup;
- }
- uether_rxmbuf(ue, m, pktlen - 4);
+ smsc_rxeof(ue, pc, off, ifp, (int)pktlen);
}
/* Update the offset to move to the next potential packet */
@@ -1395,11 +1450,12 @@ smsc_chip_init(struct smsc_softc *sc)
goto init_failed;
}
- /* Adjust the packet offset in the buffer (designed to try and align IP
- * header on 4 byte boundary)
+ /*
+ * Set the RX packet offset in the buffer to 0(no padding) and
+ * discard errored ethernet frames.
*/
reg_val &= ~SMSC_HW_CFG_RXDOFF;
- reg_val |= (ETHER_ALIGN << 9) & SMSC_HW_CFG_RXDOFF;
+ reg_val |= SMSC_HW_CFG_DRP;
/* The following setings are used for 'turbo mode', a.k.a multiple frames
* per Rx transaction (again info taken form Linux driver).
@@ -1494,7 +1550,6 @@ smsc_ioctl(struct ifnet *ifp, u_long cmd, caddr_t
struct ifreq *ifr;
int rc;
int mask;
- int reinit;
if (cmd == SIOCSIFCAP) {
sc = uether_getsc(ue);
@@ -1503,25 +1558,14 @@ smsc_ioctl(struct ifnet *ifp, u_long cmd, caddr_t
SMSC_LOCK(sc);
rc = 0;
- reinit = 0;
mask = ifr->ifr_reqcap ^ ifp->if_capenable;
/* Modify the RX CSUM enable bits */
if ((mask & IFCAP_RXCSUM) != 0 &&
- (ifp->if_capabilities & IFCAP_RXCSUM) != 0) {
+ (ifp->if_capabilities & IFCAP_RXCSUM) != 0)
ifp->if_capenable ^= IFCAP_RXCSUM;
-
- if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
- ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
- reinit = 1;
- }
- }
-
SMSC_UNLOCK(sc);
- if (reinit)
- uether_init(ue);
-
} else {
rc = uether_ioctl(ifp, cmd, data);
}
Index: sys/dev/usb/net/if_smscreg.h
===================================================================
--- sys/dev/usb/net/if_smscreg.h (revision 367712)
+++ sys/dev/usb/net/if_smscreg.h (working copy)
@@ -116,6 +116,8 @@
#define SMSC_RX_STAT_DRIBBLING (0x1UL << 2)
#define SMSC_RX_STAT_CRC_ERROR (0x1UL << 1)
+#define SMSC_RX_CSUM_LEN 2
+
/**
* REGISTERS
*
--liOOAslEiF7prFVr--
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20201116011910.GB1941>
