diff mbox series

[v3,net-next,1/2] ravb: Add Rx checksum offload support for GbEth

Message ID 20240201194521.139472-2-biju.das.jz@bp.renesas.com (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series Add HW checksum offload support for RZ/G2L GbEthernet IP | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1048 this patch: 1048
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers success CCed 0 of 0 maintainers
netdev/build_clang success Errors and warnings before: 1065 this patch: 1065
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1065 this patch: 1065
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 166 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Biju Das Feb. 1, 2024, 7:45 p.m. UTC
TOE has hardware support for calculating IP header and TCP/UDP/ICMP
checksum for both IPv4 and IPv6.

Add Rx checksum offload supported by TOE for IPv4 and TCP/UDP protocols.

For Rx, the 4-byte result of checksum calculation is attached to the
Ethernet frames.First 2-bytes is result of IPv4 header checksum and next
2-bytes is TCP/UDP/ICMP checksum.

If a frame does not have checksum error, 0x0000 is attached as checksum
calculation result. For unsupported frames 0xFFFF is attached as checksum
calculation result. In case of an IPv6 packet, IPv4 checksum is always set
to 0xFFFF.

We can test this functionality by the below commands

ethtool -K eth0 rx on --> to turn on Rx checksum offload
ethtool -K eth0 rx off --> to turn off Rx checksum offload

Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v2->v3:
 * Updated commit header and description suggested by Sergey.
 * Dropped TOE_RX_CSUM_OK macro.
 * Renamed ravb_csum_offload_init_gbeth()->ravb_csum_init_gbeth().
 * Moved enabling {RPE,TPE} in ravb_csum_init_gbeth().
 * Updated the error message in ravb_csum_init_gbeth() as
   "Timeout enabling hardware checksum"
 * Introduced ravb_endisable_csum_gbeth() for enabling/disabling CSR{1,2} registers.
   
v1->v2:
 * Fixed sparse warning by replacing __sum16->__wsum.
---
 drivers/net/ethernet/renesas/ravb.h      | 16 ++++
 drivers/net/ethernet/renesas/ravb_main.c | 93 +++++++++++++++++++++++-
 2 files changed, 106 insertions(+), 3 deletions(-)

Comments

Sergey Shtylyov Feb. 2, 2024, 7:12 p.m. UTC | #1
On 2/1/24 10:45 PM, Biju Das wrote:

> TOE has hardware support for calculating IP header and TCP/UDP/ICMP
> checksum for both IPv4 and IPv6.
> 
> Add Rx checksum offload supported by TOE for IPv4 and TCP/UDP protocols.
> 
> For Rx, the 4-byte result of checksum calculation is attached to the
> Ethernet frames.First 2-bytes is result of IPv4 header checksum and next
> 2-bytes is TCP/UDP/ICMP checksum.
> 
> If a frame does not have checksum error, 0x0000 is attached as checksum
> calculation result. For unsupported frames 0xFFFF is attached as checksum
> calculation result. In case of an IPv6 packet, IPv4 checksum is always set
> to 0xFFFF.
> 
> We can test this functionality by the below commands
> 
> ethtool -K eth0 rx on --> to turn on Rx checksum offload
> ethtool -K eth0 rx off --> to turn off Rx checksum offload
> 
> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
[...]

> diff --git a/drivers/net/ethernet/renesas/ravb.h b/drivers/net/ethernet/renesas/ravb.h
> index e0f8276cffed..a7fe9d8b6b41 100644
> --- a/drivers/net/ethernet/renesas/ravb.h
> +++ b/drivers/net/ethernet/renesas/ravb.h
> @@ -206,6 +206,7 @@ enum ravb_reg {
>  	RFCR	= 0x0760,
>  	MAFCR	= 0x0778,

   Would be good to add this comment here:

	/* TOE registers */

>  	CSR0    = 0x0800,	/* RZ/G2L only */
> +	CSR2    = 0x0808,	/* RZ/G2L only */
>  };
>  
>  
[...]
> diff --git a/drivers/net/ethernet/renesas/ravb_main.c b/drivers/net/ethernet/renesas/ravb_main.c
> index 0e3731f50fc2..c4dc6ec54287 100644
> --- a/drivers/net/ethernet/renesas/ravb_main.c
> +++ b/drivers/net/ethernet/renesas/ravb_main.c
> @@ -522,6 +522,23 @@ static int ravb_ring_init(struct net_device *ndev, int q)
>  	return -ENOMEM;
>  }
>  
> +static void ravb_csum_init_gbeth(struct net_device *ndev)
> +{
> +	if (!(ndev->features & NETIF_F_RXCSUM))
> +		goto done;
> +
> +	ravb_write(ndev, 0, CSR0);
> +	if (ravb_wait(ndev, CSR0, CSR0_RPE, 0)) {
> +		netdev_err(ndev, "Timeout enabling hardware checksum\n");
> +		ndev->features &= ~NETIF_F_RXCSUM;
> +	} else {
> +		ravb_write(ndev, CSR2_ALL, CSR2);

   Does it make sense to set the IPv6 specific bits if we don't yet
support IPv6 checksumming anyways?

> +	}
> +
> +done:
> +	ravb_write(ndev, CSR0_TPE | CSR0_RPE, CSR0);
> +}
> +
>  static void ravb_emac_init_gbeth(struct net_device *ndev)
>  {
>  	struct ravb_private *priv = netdev_priv(ndev);
[...]
> @@ -734,6 +752,31 @@ static void ravb_get_tx_tstamp(struct net_device *ndev)
>  	}
>  }
>  
> +static void ravb_rx_csum_gbeth(struct sk_buff *skb)
> +{
> +	__wsum csum_ip_hdr, csum_proto;
> +	u8 *hw_csum;
> +
> +	/* The hardware checksum status is contained in sizeof(__sum16) * 2 = 4
> +	 * bytes appended to packet data. First 2 bytes is ip header csum and
> +	 * last 2 bytes is protocol csum.

   Hm, maybe spell out csum as checksum?

> +	 */
> +	if (unlikely(skb->len < sizeof(__sum16) * 2))
> +		return;
> +
> +	hw_csum = skb_tail_pointer(skb) - sizeof(__sum16);
> +	csum_proto = csum_unfold((__force __sum16)get_unaligned_le16(hw_csum));
> +
> +	hw_csum -= sizeof(__sum16);
> +	csum_ip_hdr = csum_unfold((__force __sum16)get_unaligned_le16(hw_csum));
> +	skb_trim(skb, skb->len - 2 * sizeof(__sum16));
> +
> +	/* TODO: IPV6 Rx csum */
> +	if (skb->protocol == htons(ETH_P_IP) && !csum_ip_hdr && !csum_proto)
> +		/* Hardware validated our checksum */
> +		skb->ip_summed = CHECKSUM_UNNECESSARY;

   I think you need {} because of the comment (but would be good without it
as well)...

[...]
> @@ -2334,11 +2381,49 @@ static void ravb_set_rx_csum(struct net_device *ndev, bool enable)
>  	spin_unlock_irqrestore(&priv->lock, flags);
>  }
>  
> +static int ravb_endisable_csum_gbeth(struct net_device *ndev, enum ravb_reg reg,
> +				     u32 val, u32 mask)

   I'd suggest to mimic ravb_wait() with the the mask param followed by
the val[ue] param...

> +{
> +	u32 csr0;
> +	int ret;
> +
> +	csr0 = ravb_read(ndev, CSR0);

   Hm... I think we already know CSR0's value as ravb_csum_init_gbeth()
sets it to (CSR0_TPE | CSR0_RPE) on exit...

> +	ravb_write(ndev, csr0 & ~mask, CSR0);
> +	ret = ravb_wait(ndev, CSR0, mask, 0);
> +	if (!ret)
> +		ravb_write(ndev, val, reg);
> +
> +	ravb_write(ndev, csr0, CSR0);
> +
> +	return ret;
> +}
> +
>  static int ravb_set_features_gbeth(struct net_device *ndev,
>  				   netdev_features_t features)
>  {
> -	/* Place holder */
> -	return 0;
> +	netdev_features_t changed = ndev->features ^ features;
> +	struct ravb_private *priv = netdev_priv(ndev);
> +	unsigned long flags;
> +	int ret = 0;
> +	u32 val;
> +
> +	spin_lock_irqsave(&priv->lock, flags);
> +	if (changed & NETIF_F_RXCSUM) {
> +		if (features & NETIF_F_RXCSUM)
> +			val = CSR2_ALL;

   Again, does it make sense to set the IPv6 bits in CSR2?

> +		else
> +			val = 0;
> +
> +		ret = ravb_endisable_csum_gbeth(ndev, CSR2, val, CSR0_RPE);
> +		if (ret)
> +			goto done;
> +	}
> +
> +	ndev->features = features;
> +done:
> +	spin_unlock_irqrestore(&priv->lock, flags);
> +
> +	return ret;
>  }
>  
>  static int ravb_set_features_rcar(struct net_device *ndev,
[...]

MBR, Sergey
Sergey Shtylyov Feb. 2, 2024, 7:47 p.m. UTC | #2
On 2/2/24 10:12 PM, Sergey Shtylyov wrote:

>> TOE has hardware support for calculating IP header and TCP/UDP/ICMP
>> checksum for both IPv4 and IPv6.
>>
>> Add Rx checksum offload supported by TOE for IPv4 and TCP/UDP protocols.
>>
>> For Rx, the 4-byte result of checksum calculation is attached to the
>> Ethernet frames.First 2-bytes is result of IPv4 header checksum and next
>> 2-bytes is TCP/UDP/ICMP checksum.
>>
>> If a frame does not have checksum error, 0x0000 is attached as checksum
>> calculation result. For unsupported frames 0xFFFF is attached as checksum
>> calculation result. In case of an IPv6 packet, IPv4 checksum is always set
>> to 0xFFFF.
>>
>> We can test this functionality by the below commands
>>
>> ethtool -K eth0 rx on --> to turn on Rx checksum offload
>> ethtool -K eth0 rx off --> to turn off Rx checksum offload
>>
>> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
[...]
>> diff --git a/drivers/net/ethernet/renesas/ravb_main.c b/drivers/net/ethernet/renesas/ravb_main.c
>> index 0e3731f50fc2..c4dc6ec54287 100644
>> --- a/drivers/net/ethernet/renesas/ravb_main.c
>> +++ b/drivers/net/ethernet/renesas/ravb_main.c
[...]
>> @@ -2334,11 +2381,49 @@ static void ravb_set_rx_csum(struct net_device *ndev, bool enable)
>>  	spin_unlock_irqrestore(&priv->lock, flags);
>>  }
>>  
>> +static int ravb_endisable_csum_gbeth(struct net_device *ndev, enum ravb_reg reg,
>> +				     u32 val, u32 mask)
> 
>    I'd suggest to mimic ravb_wait() with the the mask param followed by
> the val[ue] param...

   Nevermind, I see now they are for different registers...

[...]

MBR, Sergey
Biju Das Feb. 3, 2024, 2:01 p.m. UTC | #3
> -----Original Message-----
> From: Sergey Shtylyov <s.shtylyov@omp.ru>
> Sent: Friday, February 2, 2024 7:12 PM
> To: Biju Das <biju.das.jz@bp.renesas.com>; David S. Miller
> <davem@davemloft.net>; Eric Dumazet <edumazet@google.com>; Jakub Kicinski
> <kuba@kernel.org>; Paolo Abeni <pabeni@redhat.com>
> Cc: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>; Yoshihiro Shimoda
> <yoshihiro.shimoda.uh@renesas.com>; Wolfram Sang <wsa+renesas@sang-
> engineering.com>; nikita.yoush <nikita.yoush@cogentembedded.com>;
> netdev@vger.kernel.org; linux-renesas-soc@vger.kernel.org; Geert
> Uytterhoeven <geert+renesas@glider.be>; Prabhakar Mahadev Lad
> <prabhakar.mahadev-lad.rj@bp.renesas.com>; biju.das.au
> <biju.das.au@gmail.com>
> Subject: Re: [PATCH v3 net-next 1/2] ravb: Add Rx checksum offload support
> for GbEth
> 
> On 2/1/24 10:45 PM, Biju Das wrote:
> 
> > TOE has hardware support for calculating IP header and TCP/UDP/ICMP
> > checksum for both IPv4 and IPv6.
> >
> > Add Rx checksum offload supported by TOE for IPv4 and TCP/UDP protocols.
> >
> > For Rx, the 4-byte result of checksum calculation is attached to the
> > Ethernet frames.First 2-bytes is result of IPv4 header checksum and
> > next 2-bytes is TCP/UDP/ICMP checksum.
> >
> > If a frame does not have checksum error, 0x0000 is attached as
> > checksum calculation result. For unsupported frames 0xFFFF is attached
> > as checksum calculation result. In case of an IPv6 packet, IPv4
> > checksum is always set to 0xFFFF.
> >
> > We can test this functionality by the below commands
> >
> > ethtool -K eth0 rx on --> to turn on Rx checksum offload ethtool -K
> > eth0 rx off --> to turn off Rx checksum offload
> >
> > Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> [...]
> 
> > diff --git a/drivers/net/ethernet/renesas/ravb.h
> > b/drivers/net/ethernet/renesas/ravb.h
> > index e0f8276cffed..a7fe9d8b6b41 100644
> > --- a/drivers/net/ethernet/renesas/ravb.h
> > +++ b/drivers/net/ethernet/renesas/ravb.h
> > @@ -206,6 +206,7 @@ enum ravb_reg {
> >  	RFCR	= 0x0760,
> >  	MAFCR	= 0x0778,
> 
>    Would be good to add this comment here:
> 
> 	/* TOE registers */

OK will change as below, so that we don't need to repeat /* RZ/G2L only */
+
+	/* RZ/G2L TOE registers */
+	CSR0    = 0x0800,
+	CSR2    = 0x0808,

> 
> >  	CSR0    = 0x0800,	/* RZ/G2L only */
> > +	CSR2    = 0x0808,	/* RZ/G2L only */
> >  };
> >
> >
> [...]
> > diff --git a/drivers/net/ethernet/renesas/ravb_main.c
> > b/drivers/net/ethernet/renesas/ravb_main.c
> > index 0e3731f50fc2..c4dc6ec54287 100644
> > --- a/drivers/net/ethernet/renesas/ravb_main.c
> > +++ b/drivers/net/ethernet/renesas/ravb_main.c
> > @@ -522,6 +522,23 @@ static int ravb_ring_init(struct net_device *ndev,
> int q)
> >  	return -ENOMEM;
> >  }
> >
> > +static void ravb_csum_init_gbeth(struct net_device *ndev) {
> > +	if (!(ndev->features & NETIF_F_RXCSUM))
> > +		goto done;
> > +
> > +	ravb_write(ndev, 0, CSR0);
> > +	if (ravb_wait(ndev, CSR0, CSR0_RPE, 0)) {
> > +		netdev_err(ndev, "Timeout enabling hardware checksum\n");
> > +		ndev->features &= ~NETIF_F_RXCSUM;
> > +	} else {
> > +		ravb_write(ndev, CSR2_ALL, CSR2);
> 
>    Does it make sense to set the IPv6 specific bits if we don't yet
> support IPv6 checksumming anyways?

OK will drop IPv6 bits now and will add when we enable IPv6.

> 
> > +	}
> > +
> > +done:
> > +	ravb_write(ndev, CSR0_TPE | CSR0_RPE, CSR0); }
> > +
> >  static void ravb_emac_init_gbeth(struct net_device *ndev)  {
> >  	struct ravb_private *priv = netdev_priv(ndev);
> [...]
> > @@ -734,6 +752,31 @@ static void ravb_get_tx_tstamp(struct net_device
> *ndev)
> >  	}
> >  }
> >
> > +static void ravb_rx_csum_gbeth(struct sk_buff *skb) {
> > +	__wsum csum_ip_hdr, csum_proto;
> > +	u8 *hw_csum;
> > +
> > +	/* The hardware checksum status is contained in sizeof(__sum16) * 2
> = 4
> > +	 * bytes appended to packet data. First 2 bytes is ip header csum
> and
> > +	 * last 2 bytes is protocol csum.
> 
>    Hm, maybe spell out csum as checksum?

OK.

> 
> > +	 */
> > +	if (unlikely(skb->len < sizeof(__sum16) * 2))
> > +		return;
> > +
> > +	hw_csum = skb_tail_pointer(skb) - sizeof(__sum16);
> > +	csum_proto = csum_unfold((__force
> > +__sum16)get_unaligned_le16(hw_csum));
> > +
> > +	hw_csum -= sizeof(__sum16);
> > +	csum_ip_hdr = csum_unfold((__force
> __sum16)get_unaligned_le16(hw_csum));
> > +	skb_trim(skb, skb->len - 2 * sizeof(__sum16));
> > +
> > +	/* TODO: IPV6 Rx csum */
> > +	if (skb->protocol == htons(ETH_P_IP) && !csum_ip_hdr && !csum_proto)
> > +		/* Hardware validated our checksum */
> > +		skb->ip_summed = CHECKSUM_UNNECESSARY;
> 
>    I think you need {} because of the comment (but would be good without
> it as well)...

OK, I will drop the unnecessary comment "Hardware validated our checksum"
to avoid confusion related to multi-line comment.

> 
> [...]
> > @@ -2334,11 +2381,49 @@ static void ravb_set_rx_csum(struct net_device
> *ndev, bool enable)
> >  	spin_unlock_irqrestore(&priv->lock, flags);  }
> >
> > +static int ravb_endisable_csum_gbeth(struct net_device *ndev, enum
> ravb_reg reg,
> > +				     u32 val, u32 mask)
> 
>    I'd suggest to mimic ravb_wait() with the the mask param followed by
> the val[ue] param...
> 
> > +{
> > +	u32 csr0;
> > +	int ret;
> > +
> > +	csr0 = ravb_read(ndev, CSR0);
> 
>    Hm... I think we already know CSR0's value as ravb_csum_init_gbeth()
> sets it to (CSR0_TPE | CSR0_RPE) on exit...

OK will drop register read.

> 
> > +	ravb_write(ndev, csr0 & ~mask, CSR0);
> > +	ret = ravb_wait(ndev, CSR0, mask, 0);
> > +	if (!ret)
> > +		ravb_write(ndev, val, reg);
> > +
> > +	ravb_write(ndev, csr0, CSR0);
> > +
> > +	return ret;
> > +}
> > +
> >  static int ravb_set_features_gbeth(struct net_device *ndev,
> >  				   netdev_features_t features)
> >  {
> > -	/* Place holder */
> > -	return 0;
> > +	netdev_features_t changed = ndev->features ^ features;
> > +	struct ravb_private *priv = netdev_priv(ndev);
> > +	unsigned long flags;
> > +	int ret = 0;
> > +	u32 val;
> > +
> > +	spin_lock_irqsave(&priv->lock, flags);
> > +	if (changed & NETIF_F_RXCSUM) {
> > +		if (features & NETIF_F_RXCSUM)
> > +			val = CSR2_ALL;
> 
>    Again, does it make sense to set the IPv6 bits in CSR2?

OK will drop it.

I am agreeing to all the comments and will send v4.

Cheers,
Biju

> 
> > +		else
> > +			val = 0;
> > +
> > +		ret = ravb_endisable_csum_gbeth(ndev, CSR2, val, CSR0_RPE);
> > +		if (ret)
> > +			goto done;
> > +	}
> > +
> > +	ndev->features = features;
> > +done:
> > +	spin_unlock_irqrestore(&priv->lock, flags);
> > +
> > +	return ret;
> >  }
> >
> >  static int ravb_set_features_rcar(struct net_device *ndev,
> [...]
> 
> MBR, Sergey
diff mbox series

Patch

diff --git a/drivers/net/ethernet/renesas/ravb.h b/drivers/net/ethernet/renesas/ravb.h
index e0f8276cffed..a7fe9d8b6b41 100644
--- a/drivers/net/ethernet/renesas/ravb.h
+++ b/drivers/net/ethernet/renesas/ravb.h
@@ -206,6 +206,7 @@  enum ravb_reg {
 	RFCR	= 0x0760,
 	MAFCR	= 0x0778,
 	CSR0    = 0x0800,	/* RZ/G2L only */
+	CSR2    = 0x0808,	/* RZ/G2L only */
 };
 
 
@@ -978,6 +979,21 @@  enum CSR0_BIT {
 	CSR0_RPE	= 0x00000020,
 };
 
+enum CSR2_BIT {
+	CSR2_RIP4	= 0x00000001,
+	CSR2_RTCP4	= 0x00000010,
+	CSR2_RUDP4	= 0x00000020,
+	CSR2_RICMP4	= 0x00000040,
+	CSR2_RTCP6	= 0x00100000,
+	CSR2_RUDP6	= 0x00200000,
+	CSR2_RICMP6	= 0x00400000,
+	CSR2_RHOP	= 0x01000000,
+	CSR2_RROUT	= 0x02000000,
+	CSR2_RAHD	= 0x04000000,
+	CSR2_RDHD	= 0x08000000,
+	CSR2_ALL	= 0x0F700071,
+};
+
 #define DBAT_ENTRY_NUM	22
 #define RX_QUEUE_OFFSET	4
 #define NUM_RX_QUEUE	2
diff --git a/drivers/net/ethernet/renesas/ravb_main.c b/drivers/net/ethernet/renesas/ravb_main.c
index 0e3731f50fc2..c4dc6ec54287 100644
--- a/drivers/net/ethernet/renesas/ravb_main.c
+++ b/drivers/net/ethernet/renesas/ravb_main.c
@@ -522,6 +522,23 @@  static int ravb_ring_init(struct net_device *ndev, int q)
 	return -ENOMEM;
 }
 
+static void ravb_csum_init_gbeth(struct net_device *ndev)
+{
+	if (!(ndev->features & NETIF_F_RXCSUM))
+		goto done;
+
+	ravb_write(ndev, 0, CSR0);
+	if (ravb_wait(ndev, CSR0, CSR0_RPE, 0)) {
+		netdev_err(ndev, "Timeout enabling hardware checksum\n");
+		ndev->features &= ~NETIF_F_RXCSUM;
+	} else {
+		ravb_write(ndev, CSR2_ALL, CSR2);
+	}
+
+done:
+	ravb_write(ndev, CSR0_TPE | CSR0_RPE, CSR0);
+}
+
 static void ravb_emac_init_gbeth(struct net_device *ndev)
 {
 	struct ravb_private *priv = netdev_priv(ndev);
@@ -553,7 +570,8 @@  static void ravb_emac_init_gbeth(struct net_device *ndev)
 
 	/* E-MAC status register clear */
 	ravb_write(ndev, ECSR_ICD | ECSR_LCHNG | ECSR_PFRI, ECSR);
-	ravb_write(ndev, CSR0_TPE | CSR0_RPE, CSR0);
+
+	ravb_csum_init_gbeth(ndev);
 
 	/* E-MAC interrupt enable register */
 	ravb_write(ndev, ECSIPR_ICDIP, ECSIPR);
@@ -734,6 +752,31 @@  static void ravb_get_tx_tstamp(struct net_device *ndev)
 	}
 }
 
+static void ravb_rx_csum_gbeth(struct sk_buff *skb)
+{
+	__wsum csum_ip_hdr, csum_proto;
+	u8 *hw_csum;
+
+	/* The hardware checksum status is contained in sizeof(__sum16) * 2 = 4
+	 * bytes appended to packet data. First 2 bytes is ip header csum and
+	 * last 2 bytes is protocol csum.
+	 */
+	if (unlikely(skb->len < sizeof(__sum16) * 2))
+		return;
+
+	hw_csum = skb_tail_pointer(skb) - sizeof(__sum16);
+	csum_proto = csum_unfold((__force __sum16)get_unaligned_le16(hw_csum));
+
+	hw_csum -= sizeof(__sum16);
+	csum_ip_hdr = csum_unfold((__force __sum16)get_unaligned_le16(hw_csum));
+	skb_trim(skb, skb->len - 2 * sizeof(__sum16));
+
+	/* TODO: IPV6 Rx csum */
+	if (skb->protocol == htons(ETH_P_IP) && !csum_ip_hdr && !csum_proto)
+		/* Hardware validated our checksum */
+		skb->ip_summed = CHECKSUM_UNNECESSARY;
+}
+
 static void ravb_rx_csum(struct sk_buff *skb)
 {
 	u8 *hw_csum;
@@ -819,6 +862,8 @@  static bool ravb_rx_gbeth(struct net_device *ndev, int *quota, int q)
 				skb = ravb_get_skb_gbeth(ndev, entry, desc);
 				skb_put(skb, pkt_len);
 				skb->protocol = eth_type_trans(skb, ndev);
+				if (ndev->features & NETIF_F_RXCSUM)
+					ravb_rx_csum_gbeth(skb);
 				napi_gro_receive(&priv->napi[q], skb);
 				stats->rx_packets++;
 				stats->rx_bytes += pkt_len;
@@ -846,6 +891,8 @@  static bool ravb_rx_gbeth(struct net_device *ndev, int *quota, int q)
 				dev_kfree_skb(skb);
 				priv->rx_1st_skb->protocol =
 					eth_type_trans(priv->rx_1st_skb, ndev);
+				if (ndev->features & NETIF_F_RXCSUM)
+					ravb_rx_csum_gbeth(skb);
 				napi_gro_receive(&priv->napi[q],
 						 priv->rx_1st_skb);
 				stats->rx_packets++;
@@ -2334,11 +2381,49 @@  static void ravb_set_rx_csum(struct net_device *ndev, bool enable)
 	spin_unlock_irqrestore(&priv->lock, flags);
 }
 
+static int ravb_endisable_csum_gbeth(struct net_device *ndev, enum ravb_reg reg,
+				     u32 val, u32 mask)
+{
+	u32 csr0;
+	int ret;
+
+	csr0 = ravb_read(ndev, CSR0);
+	ravb_write(ndev, csr0 & ~mask, CSR0);
+	ret = ravb_wait(ndev, CSR0, mask, 0);
+	if (!ret)
+		ravb_write(ndev, val, reg);
+
+	ravb_write(ndev, csr0, CSR0);
+
+	return ret;
+}
+
 static int ravb_set_features_gbeth(struct net_device *ndev,
 				   netdev_features_t features)
 {
-	/* Place holder */
-	return 0;
+	netdev_features_t changed = ndev->features ^ features;
+	struct ravb_private *priv = netdev_priv(ndev);
+	unsigned long flags;
+	int ret = 0;
+	u32 val;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	if (changed & NETIF_F_RXCSUM) {
+		if (features & NETIF_F_RXCSUM)
+			val = CSR2_ALL;
+		else
+			val = 0;
+
+		ret = ravb_endisable_csum_gbeth(ndev, CSR2, val, CSR0_RPE);
+		if (ret)
+			goto done;
+	}
+
+	ndev->features = features;
+done:
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return ret;
 }
 
 static int ravb_set_features_rcar(struct net_device *ndev,
@@ -2518,6 +2603,8 @@  static const struct ravb_hw_info gbeth_hw_info = {
 	.emac_init = ravb_emac_init_gbeth,
 	.gstrings_stats = ravb_gstrings_stats_gbeth,
 	.gstrings_size = sizeof(ravb_gstrings_stats_gbeth),
+	.net_hw_features = NETIF_F_RXCSUM,
+	.net_features = NETIF_F_RXCSUM,
 	.stats_len = ARRAY_SIZE(ravb_gstrings_stats_gbeth),
 	.max_rx_len = ALIGN(GBETH_RX_BUFF_MAX, RAVB_ALIGN),
 	.tccr_mask = TCCR_TSRQ0,