diff --git a/drivers/net/mlx4/mlx4.c b/drivers/net/mlx4/mlx4.c
index 8573e14..6842df1 100644
--- a/drivers/net/mlx4/mlx4.c
+++ b/drivers/net/mlx4/mlx4.c
@@ -571,6 +571,17 @@ struct mlx4_conf {
 		priv->pd = pd;
 		priv->mtu = ETHER_MTU;
 		priv->vf = vf;
+		priv->hw_csum =
+			((to_mctx(ctx)->exp_device_cap_flags &
+			  MLX4_DEVICE_RX_CSUM_TCP_UDP_PKT) &&
+			 (to_mctx(ctx)->exp_device_cap_flags &
+			  MLX4_DEVICE_RX_CSUM_IP_PKT));
+		DEBUG("checksum offloading is %ssupported",
+		      (priv->hw_csum ? "" : "not "));
+		priv->hw_csum_l2tun = !!(to_mctx(ctx)->exp_device_cap_flags &
+					 MLX4_DEVICE_VXLAN_SUPPORT);
+		DEBUG("L2 tunnel checksum offloads are %ssupported",
+		      (priv->hw_csum_l2tun ? "" : "not "));
 		/* Configure the first MAC address by default. */
 		if (mlx4_get_mac(priv, &mac.addr_bytes)) {
 			ERROR("cannot get MAC address, is mlx4_en loaded?"
diff --git a/drivers/net/mlx4/mlx4.h b/drivers/net/mlx4/mlx4.h
index 4b7f98b..b81dcb0 100644
--- a/drivers/net/mlx4/mlx4.h
+++ b/drivers/net/mlx4/mlx4.h
@@ -107,6 +107,8 @@ struct priv {
 	unsigned int vf:1; /* This is a VF device. */
 	unsigned int intr_alarm:1; /* An interrupt alarm is scheduled. */
 	unsigned int isolated:1; /* Toggle isolated mode. */
+	unsigned int hw_csum:1; /* Checksum offload is supported. */
+	unsigned int hw_csum_l2tun:1; /* Same for L2 tunnels. */
 	/* RX/TX queues. */
 	unsigned int rxqs_n; /* RX queues array size. */
 	unsigned int txqs_n; /* TX queues array size. */
diff --git a/drivers/net/mlx4/mlx4_ethdev.c b/drivers/net/mlx4/mlx4_ethdev.c
index 8c6b1fd..9b5ba31 100644
--- a/drivers/net/mlx4/mlx4_ethdev.c
+++ b/drivers/net/mlx4/mlx4_ethdev.c
@@ -551,7 +551,12 @@
 	info->max_tx_queues = max;
 	/* Last array entry is reserved for broadcast. */
 	info->max_mac_addrs = 1;
-	info->rx_offload_capa = 0;
+	info->rx_offload_capa =
+		(priv->hw_csum ?
+		 (DEV_RX_OFFLOAD_IPV4_CKSUM |
+		  DEV_RX_OFFLOAD_UDP_CKSUM |
+		  DEV_RX_OFFLOAD_TCP_CKSUM) :
+		 0);
 	info->tx_offload_capa = 0;
 	if (mlx4_get_ifname(priv, &ifname) == 0)
 		info->if_index = if_nametoindex(ifname);
diff --git a/drivers/net/mlx4/mlx4_prm.h b/drivers/net/mlx4/mlx4_prm.h
index 03c1192..a06781e 100644
--- a/drivers/net/mlx4/mlx4_prm.h
+++ b/drivers/net/mlx4/mlx4_prm.h
@@ -402,4 +402,13 @@ static inline struct mlx4_qp *to_mqp(struct ibv_qp *ibqp)
 			    struct mlx4_qp, verbs_qp);
 }
 
+enum mlx4_device_cap_flags {
+	MLX4_START_FLAG_LOC = 0x20,
+	MLX4_START_FLAG	    = (1ULL << MLX4_START_FLAG_LOC),
+
+	MLX4_DEVICE_VXLAN_SUPPORT	= (MLX4_START_FLAG << 10),
+	MLX4_DEVICE_RX_CSUM_TCP_UDP_PKT = (MLX4_START_FLAG << 11),
+	MLX4_DEVICE_RX_CSUM_IP_PKT	= (MLX4_START_FLAG << 12),
+};
+
 #endif /* RTE_PMD_MLX4_PRM_H_ */
diff --git a/drivers/net/mlx4/mlx4_rxq.c b/drivers/net/mlx4/mlx4_rxq.c
index bbe9c89..c431033 100644
--- a/drivers/net/mlx4/mlx4_rxq.c
+++ b/drivers/net/mlx4/mlx4_rxq.c
@@ -274,6 +274,11 @@
 
 	(void)conf; /* Thresholds configuration (ignored). */
 	mb_len = rte_pktmbuf_data_room_size(mp);
+	/* Toggle RX checksum offload if hardware supports it. */
+	if (priv->hw_csum)
+		tmpl.csum = !!dev->data->dev_conf.rxmode.hw_ip_checksum;
+	if (priv->hw_csum_l2tun)
+		tmpl.csum_l2tun = !!dev->data->dev_conf.rxmode.hw_ip_checksum;
 	/* Enable scattered packets support for this queue if necessary. */
 	assert(mb_len >= RTE_PKTMBUF_HEADROOM);
 	if (dev->data->dev_conf.rxmode.max_rx_pkt_len <=
diff --git a/drivers/net/mlx4/mlx4_rxtx.c b/drivers/net/mlx4/mlx4_rxtx.c
index f11c84c..f090354 100644
--- a/drivers/net/mlx4/mlx4_rxtx.c
+++ b/drivers/net/mlx4/mlx4_rxtx.c
@@ -348,6 +348,115 @@
 }
 
 /**
+ * Translate RX completion flags to packet type.
+ *
+ * @param flags
+ *   RX completion flags returned by poll_length_flags().
+ *
+ * @return
+ *   Packet type for struct rte_mbuf.
+ */
+static inline uint32_t
+rxq_cq_to_pkt_type(uint32_t flags)
+{
+	uint32_t pkt_type;
+
+	if (flags & MLX4_CQE_L2_TUNNEL)
+		pkt_type =
+			MLX4_TRANSPOSE(flags,
+				       MLX4_CQE_L2_TUNNEL_IPV4,
+				       RTE_PTYPE_L3_IPV4_EXT_UNKNOWN) |
+			MLX4_TRANSPOSE(~flags,
+				       MLX4_CQE_L2_TUNNEL_IPV4,
+				       RTE_PTYPE_L3_IPV6_EXT_UNKNOWN) |
+			MLX4_TRANSPOSE(flags,
+				       MLX4_CQE_STATUS_IPV4,
+				       RTE_PTYPE_INNER_L3_IPV4_EXT_UNKNOWN) |
+			MLX4_TRANSPOSE(flags,
+				       MLX4_CQE_STATUS_IPV6,
+				       RTE_PTYPE_INNER_L3_IPV6_EXT_UNKNOWN);
+	else
+		pkt_type =
+			MLX4_TRANSPOSE(flags,
+				       MLX4_CQE_STATUS_IPV4,
+				       RTE_PTYPE_L3_IPV4_EXT_UNKNOWN) |
+			MLX4_TRANSPOSE(flags,
+				       MLX4_CQE_STATUS_IPV6,
+				       RTE_PTYPE_L3_IPV6_EXT_UNKNOWN);
+	return pkt_type;
+}
+
+/**
+ * Translate RX completion flags to offload flags.
+ *
+ * @param[in] rxq
+ *   Pointer to RX queue structure.
+ * @param flags
+ *   RX completion flags returned by poll_length_flags().
+ *
+ * @return
+ *   Offload flags (ol_flags) for struct rte_mbuf.
+ */
+static inline uint32_t
+rxq_cq_to_ol_flags(const struct rxq *rxq, uint32_t flags)
+{
+	uint32_t ol_flags = 0;
+
+	if (rxq->csum)
+		ol_flags |=
+			MLX4_TRANSPOSE(flags,
+				       MLX4_CQE_STATUS_IPOK,
+				       PKT_RX_IP_CKSUM_GOOD) |
+			MLX4_TRANSPOSE(flags,
+				       MLX4_CQE_STATUS_L4_CSUM,
+				       PKT_RX_L4_CKSUM_GOOD);
+	if ((flags & MLX4_CQE_L2_TUNNEL) && (rxq->csum_l2tun))
+		ol_flags |=
+			MLX4_TRANSPOSE(flags,
+				       MLX4_CQE_L2_TUNNEL_IPOK,
+				       PKT_RX_IP_CKSUM_GOOD) |
+			MLX4_TRANSPOSE(flags,
+				       MLX4_CQE_L2_TUNNEL_L4_CSUM,
+				       PKT_RX_L4_CKSUM_GOOD);
+	return ol_flags;
+}
+
+/**
+ * Get cqe flags.
+ *
+ * @param cqe
+ *   Pointer to cqe structure.
+ * @param mlx4_qp
+ *   Pointer to QP structure.
+ *
+ * @return
+ *   CQE's flags.
+ */
+static inline uint32_t
+mlx4_cqe_flags(struct mlx4_cqe *cqe, struct ibv_qp *qp)
+{
+	uint32_t flags = 0;
+	struct mlx4_qp *mlx4_qp = to_mqp(qp);
+
+	/*
+	 * The relevant bits are in different locations on their
+	 * CQE fields therefore we can join them in one 32bit
+	 * variable.
+	 */
+	if (mlx4_qp->qp_cap_cache & MLX4_RX_CSUM_MODE_IP_OK_IP_NON_TCP_UDP)
+		flags = (cqe->badfcs_enc & MLX4_CQE_STATUS_L4_CSUM) |
+			(ntohs(cqe->status) & (MLX4_CQE_STATUS_IPOK |
+					       MLX4_CQE_STATUS_IPV4 |
+					       MLX4_CQE_STATUS_IPV6));
+	if (mlx4_qp->qp_cap_cache & MLX4_RX_VXLAN)
+		flags |= ntohl(cqe->vlan_my_qpn) & (MLX4_CQE_L2_TUNNEL     |
+						MLX4_CQE_L2_TUNNEL_IPOK    |
+						MLX4_CQE_L2_TUNNEL_L4_CSUM |
+						MLX4_CQE_L2_TUNNEL_IPV4);
+	return flags;
+}
+
+/**
  * Get next cqe from HW.
  *
  * @param cq
@@ -485,8 +594,15 @@
 				goto skip;
 			}
 			pkt = seg;
-			pkt->packet_type = 0;
-			pkt->ol_flags = 0;
+			if (rxq->csum | rxq->csum_l2tun) {
+				uint32_t flags = mlx4_cqe_flags(cqe, rxq->qp);
+
+				pkt->packet_type = rxq_cq_to_pkt_type(flags);
+				pkt->ol_flags = rxq_cq_to_ol_flags(rxq, flags);
+			} else {
+				pkt->packet_type = 0;
+				pkt->ol_flags = 0;
+			}
 			pkt->pkt_len = len;
 		}
 		rep->nb_segs = 1;
diff --git a/drivers/net/mlx4/mlx4_rxtx.h b/drivers/net/mlx4/mlx4_rxtx.h
index 077fdd8..2f03a94 100644
--- a/drivers/net/mlx4/mlx4_rxtx.h
+++ b/drivers/net/mlx4/mlx4_rxtx.h
@@ -87,6 +87,8 @@ struct rxq {
 		uint16_t rq_ci;
 	} hw;
 	unsigned int sge_n; /**< Log 2 of SGEs number. */
+	unsigned int csum:1; /**< Enable checksum offloading. */
+	unsigned int csum_l2tun:1; /**< Same for L2 tunnels. */
 	struct mlx4_rxq_stats stats; /**< Rx queue counters. */
 	unsigned int socket; /**< CPU socket ID for allocations. */
 };
