diff options
-rw-r--r-- | drivers/net/bfin_mac.c | 35 |
1 files changed, 33 insertions, 2 deletions
diff --git a/drivers/net/bfin_mac.c b/drivers/net/bfin_mac.c index 2b364ba6b62e..9e010d69f34a 100644 --- a/drivers/net/bfin_mac.c +++ b/drivers/net/bfin_mac.c @@ -985,6 +985,7 @@ out: return NETDEV_TX_OK; } +#define IP_HEADER_OFF 0 #define RX_ERROR_MASK (RX_LONG | RX_ALIGN | RX_CRC | RX_LEN | \ RX_FRAG | RX_ADDR | RX_DMAO | RX_PHY | RX_LATE | RX_RANGE) @@ -993,6 +994,10 @@ static void bfin_mac_rx(struct net_device *dev) struct sk_buff *skb, *new_skb; unsigned short len; struct bfin_mac_local *lp __maybe_unused = netdev_priv(dev); +#if defined(BFIN_MAC_CSUM_OFFLOAD) + unsigned int i; + unsigned char fcs[ETH_FCS_LEN + 1]; +#endif /* check if frame status word reports an error condition * we which case we simply drop the packet @@ -1026,6 +1031,8 @@ static void bfin_mac_rx(struct net_device *dev) current_rx_ptr->desc_a.start_addr = (unsigned long)new_skb->data - 2; len = (unsigned short)((current_rx_ptr->status.status_word) & RX_FRLEN); + /* Deduce Ethernet FCS length from Ethernet payload length */ + len -= ETH_FCS_LEN; skb_put(skb, len); skb->protocol = eth_type_trans(skb, dev); @@ -1033,8 +1040,32 @@ static void bfin_mac_rx(struct net_device *dev) bfin_rx_hwtstamp(dev, skb); #if defined(BFIN_MAC_CSUM_OFFLOAD) - skb->csum = current_rx_ptr->status.ip_payload_csum; - skb->ip_summed = CHECKSUM_COMPLETE; + /* Checksum offloading only works for IPv4 packets with the standard IP header + * length of 20 bytes, because the blackfin MAC checksum calculation is + * based on that assumption. We must NOT use the calculated checksum if our + * IP version or header break that assumption. + */ + if (skb->data[IP_HEADER_OFF] == 0x45) { + skb->csum = current_rx_ptr->status.ip_payload_csum; + /* + * Deduce Ethernet FCS from hardware generated IP payload checksum. + * IP checksum is based on 16-bit one's complement algorithm. + * To deduce a value from checksum is equal to add its inversion. + * If the IP payload len is odd, the inversed FCS should also + * begin from odd address and leave first byte zero. + */ + if (skb->len % 2) { + fcs[0] = 0; + for (i = 0; i < ETH_FCS_LEN; i++) + fcs[i + 1] = ~skb->data[skb->len + i]; + skb->csum = csum_partial(fcs, ETH_FCS_LEN + 1, skb->csum); + } else { + for (i = 0; i < ETH_FCS_LEN; i++) + fcs[i] = ~skb->data[skb->len + i]; + skb->csum = csum_partial(fcs, ETH_FCS_LEN, skb->csum); + } + skb->ip_summed = CHECKSUM_COMPLETE; + } #endif netif_rx(skb); |