diff --git a/platform/linux-generic/include/odp_packet_internal.h b/platform/linux-generic/include/odp_packet_internal.h
index 392d670..9b4f59e 100644
--- a/platform/linux-generic/include/odp_packet_internal.h
+++ b/platform/linux-generic/include/odp_packet_internal.h
@@ -41,7 +41,6 @@ typedef union {
 
 	struct {
 		uint64_t parsed_l2:1; /**< L2 parsed */
-		uint64_t parsed_all:1;/**< Parsing complete */
 		uint64_t dst_queue:1; /**< Dst queue present */
 
 		uint64_t flow_hash:1; /**< Flow hash present */
@@ -131,6 +130,18 @@ ODP_STATIC_ASSERT(sizeof(output_flags_t) == sizeof(uint32_t),
 		  "OUTPUT_FLAGS_SIZE_ERROR");
 
 /**
+ * Protocol stack layers
+ */
+typedef enum {
+	LAYER_NONE = 0,
+	LAYER_L1,
+	LAYER_L2,
+	LAYER_L3,
+	LAYER_L4,
+	LAYER_ALL
+} layer_t;
+
+/**
  * Packet parser metadata
  */
 typedef struct {
@@ -145,6 +156,10 @@ typedef struct {
 	uint32_t l3_len;    /**< Layer 3 length */
 	uint32_t l4_len;    /**< Layer 4 length */
 
+	layer_t parsed_layers;	/**< Highest parsed protocol stack layer */
+	uint16_t ethtype;	/**< EtherType */
+	uint8_t ip_proto;	/**< IP protocol */
+
 } packet_parser_t;
 
 /**
@@ -300,7 +315,7 @@ static inline int packet_parse_l2_not_done(packet_parser_t *prs)
 
 static inline int packet_parse_not_complete(odp_packet_hdr_t *pkt_hdr)
 {
-	return !pkt_hdr->p.input_flags.parsed_all;
+	return pkt_hdr->p.parsed_layers != LAYER_ALL;
 }
 
 /* Forward declarations */
@@ -316,6 +331,9 @@ void packet_parse_l2(packet_parser_t *prs, uint32_t frame_len);
 /* Perform full packet parse */
 int packet_parse_full(odp_packet_hdr_t *pkt_hdr);
 
+/* Perform packet parse up to a given protocol layer */
+int packet_parse_layer(odp_packet_hdr_t *pkt_hdr, layer_t layer);
+
 /* Reset parser metadata for a new parse */
 void packet_parse_reset(odp_packet_hdr_t *pkt_hdr);
 
@@ -349,7 +367,7 @@ static inline void packet_set_ts(odp_packet_hdr_t *pkt_hdr, odp_time_t *ts)
 }
 
 int packet_parse_common(packet_parser_t *pkt_hdr, const uint8_t *ptr,
-			uint32_t pkt_len, uint32_t seg_len);
+			uint32_t pkt_len, uint32_t seg_len, layer_t layer);
 
 int _odp_cls_parse(odp_packet_hdr_t *pkt_hdr, const uint8_t *parseptr);
 
diff --git a/platform/linux-generic/odp_classification.c b/platform/linux-generic/odp_classification.c
index ea223bf..868058d 100644
--- a/platform/linux-generic/odp_classification.c
+++ b/platform/linux-generic/odp_classification.c
@@ -821,7 +821,7 @@ int cls_classify_packet(pktio_entry_t *entry, const uint8_t *base,
 	packet_parse_reset(pkt_hdr);
 	packet_set_len(pkt_hdr, pkt_len);
 
-	packet_parse_common(&pkt_hdr->p, base, pkt_len, seg_len);
+	packet_parse_common(&pkt_hdr->p, base, pkt_len, seg_len, LAYER_ALL);
 	cos = cls_select_cos(entry, base, pkt_hdr);
 
 	if (cos == NULL)
diff --git a/platform/linux-generic/odp_packet.c b/platform/linux-generic/odp_packet.c
index c4cf324..5f84869 100644
--- a/platform/linux-generic/odp_packet.c
+++ b/platform/linux-generic/odp_packet.c
@@ -30,12 +30,13 @@
 static inline void packet_parse_disable(odp_packet_hdr_t *pkt_hdr)
 {
 	pkt_hdr->p.input_flags.parsed_l2  = 1;
-	pkt_hdr->p.input_flags.parsed_all = 1;
+	pkt_hdr->p.parsed_layers = LAYER_ALL;
 }
 
 void packet_parse_reset(odp_packet_hdr_t *pkt_hdr)
 {
 	/* Reset parser metadata before new parse */
+	pkt_hdr->p.parsed_layers    = LAYER_NONE;
 	pkt_hdr->p.error_flags.all  = 0;
 	pkt_hdr->p.input_flags.all  = 0;
 	pkt_hdr->p.output_flags.all = 0;
@@ -50,6 +51,8 @@ void packet_parse_reset(odp_packet_hdr_t *pkt_hdr)
 static void packet_init(pool_entry_t *pool, odp_packet_hdr_t *pkt_hdr,
 			size_t size, int parse)
 {
+	pkt_hdr->p.parsed_layers    = LAYER_NONE;
+
 	pkt_hdr->p.input_flags.all  = 0;
 	pkt_hdr->p.output_flags.all = 0;
 	pkt_hdr->p.error_flags.all  = 0;
@@ -1166,151 +1169,185 @@ void packet_parse_l2(packet_parser_t *prs, uint32_t frame_len)
 }
 
 /**
- * Parse common packet headers
+ * Parse common packet headers up to given layer
  *
  * The function expects at least PACKET_PARSE_SEG_LEN bytes of data to be
  * available from the ptr.
  */
 int packet_parse_common(packet_parser_t *prs, const uint8_t *ptr,
-			uint32_t frame_len, uint32_t seg_len)
+			uint32_t frame_len, uint32_t seg_len, layer_t layer)
 {
-	const _odp_ethhdr_t *eth;
-	const _odp_vlanhdr_t *vlan;
-	uint16_t ethtype;
 	uint32_t offset;
-	uint8_t ip_proto = 0;
 	const uint8_t *parseptr;
-	uint16_t macaddr0, macaddr2, macaddr4;
-
-	offset = sizeof(_odp_ethhdr_t);
-	if (packet_parse_l2_not_done(prs))
-		packet_parse_l2(prs, frame_len);
-
-	eth = (const _odp_ethhdr_t *)ptr;
-
-	/* Handle Ethernet broadcast/multicast addresses */
-	macaddr0 = odp_be_to_cpu_16(*((const uint16_t *)(const void *)eth));
-	prs->input_flags.eth_mcast = (macaddr0 & 0x0100) == 0x0100;
-
-	if (macaddr0 == 0xffff) {
-		macaddr2 =
-			odp_be_to_cpu_16(*((const uint16_t *)
-					   (const void *)eth + 1));
-		macaddr4 =
-			odp_be_to_cpu_16(*((const uint16_t *)
-					   (const void *)eth + 2));
-		prs->input_flags.eth_bcast =
-			(macaddr2 == 0xffff) && (macaddr4 == 0xffff);
-	} else {
-		prs->input_flags.eth_bcast = 0;
-	}
-
-	/* Get Ethertype */
-	ethtype = odp_be_to_cpu_16(eth->type);
-	parseptr = (const uint8_t *)(eth + 1);
 
-	/* Check for SNAP vs. DIX */
-	if (ethtype < _ODP_ETH_LEN_MAX) {
-		prs->input_flags.snap = 1;
-		if (ethtype > frame_len - offset) {
-			prs->error_flags.snap_len = 1;
-			goto parse_exit;
+	switch (prs->parsed_layers) {
+	case LAYER_NONE:
+	case LAYER_L2:
+	{
+		const _odp_ethhdr_t *eth;
+		uint16_t macaddr0, macaddr2, macaddr4;
+		const _odp_vlanhdr_t *vlan;
+
+		offset = sizeof(_odp_ethhdr_t);
+		if (packet_parse_l2_not_done(prs))
+			packet_parse_l2(prs, frame_len);
+
+		eth = (const _odp_ethhdr_t *)ptr;
+
+		/* Handle Ethernet broadcast/multicast addresses */
+		macaddr0 = odp_be_to_cpu_16(*((const uint16_t *)
+					    (const void *)eth));
+		prs->input_flags.eth_mcast = (macaddr0 & 0x0100) == 0x0100;
+
+		if (macaddr0 == 0xffff) {
+			macaddr2 =
+				odp_be_to_cpu_16(*((const uint16_t *)
+						   (const void *)eth + 1));
+			macaddr4 =
+				odp_be_to_cpu_16(*((const uint16_t *)
+						   (const void *)eth + 2));
+			prs->input_flags.eth_bcast =
+				(macaddr2 == 0xffff) && (macaddr4 == 0xffff);
+		} else {
+			prs->input_flags.eth_bcast = 0;
 		}
-		ethtype = odp_be_to_cpu_16(*((const uint16_t *)
-					     (uintptr_t)(parseptr + 6)));
-		offset   += 8;
-		parseptr += 8;
-	}
-
-	/* Parse the VLAN header(s), if present */
-	if (ethtype == _ODP_ETHTYPE_VLAN_OUTER) {
-		prs->input_flags.vlan_qinq = 1;
-		prs->input_flags.vlan = 1;
-
-		vlan = (const _odp_vlanhdr_t *)parseptr;
-		ethtype = odp_be_to_cpu_16(vlan->type);
-		offset += sizeof(_odp_vlanhdr_t);
-		parseptr += sizeof(_odp_vlanhdr_t);
-	}
-
-	if (ethtype == _ODP_ETHTYPE_VLAN) {
-		prs->input_flags.vlan = 1;
-		vlan = (const _odp_vlanhdr_t *)parseptr;
-		ethtype = odp_be_to_cpu_16(vlan->type);
-		offset += sizeof(_odp_vlanhdr_t);
-		parseptr += sizeof(_odp_vlanhdr_t);
-	}
 
-	/* Set l3_offset+flag only for known ethtypes */
-	prs->input_flags.l3 = 1;
-	prs->l3_offset = offset;
+		/* Get Ethertype */
+		prs->ethtype = odp_be_to_cpu_16(eth->type);
+		parseptr = (const uint8_t *)(eth + 1);
+
+		/* Check for SNAP vs. DIX */
+		if (prs->ethtype < _ODP_ETH_LEN_MAX) {
+			prs->input_flags.snap = 1;
+			if (prs->ethtype > frame_len - offset) {
+				prs->error_flags.snap_len = 1;
+				goto parse_exit;
+			}
+			prs->ethtype = odp_be_to_cpu_16(*((const uint16_t *)
+							(uintptr_t)
+							(parseptr + 6)));
+			offset   += 8;
+			parseptr += 8;
+		}
 
-	/* Parse Layer 3 headers */
-	switch (ethtype) {
-	case _ODP_ETHTYPE_IPV4:
-		prs->input_flags.ipv4 = 1;
-		ip_proto = parse_ipv4(prs, &parseptr, &offset, frame_len);
-		break;
+		/* Parse the VLAN header(s), if present */
+		if (prs->ethtype == _ODP_ETHTYPE_VLAN_OUTER) {
+			prs->input_flags.vlan_qinq = 1;
+			prs->input_flags.vlan = 1;
 
-	case _ODP_ETHTYPE_IPV6:
-		prs->input_flags.ipv6 = 1;
-		ip_proto = parse_ipv6(prs, &parseptr, &offset, frame_len,
-				      seg_len);
-		break;
+			vlan = (const _odp_vlanhdr_t *)parseptr;
+			prs->ethtype = odp_be_to_cpu_16(vlan->type);
+			offset += sizeof(_odp_vlanhdr_t);
+			parseptr += sizeof(_odp_vlanhdr_t);
+		}
 
-	case _ODP_ETHTYPE_ARP:
-		prs->input_flags.arp = 1;
-		ip_proto = 255;  /* Reserved invalid by IANA */
-		break;
+		if (prs->ethtype == _ODP_ETHTYPE_VLAN) {
+			prs->input_flags.vlan = 1;
+			vlan = (const _odp_vlanhdr_t *)parseptr;
+			prs->ethtype = odp_be_to_cpu_16(vlan->type);
+			offset += sizeof(_odp_vlanhdr_t);
+			parseptr += sizeof(_odp_vlanhdr_t);
+		}
 
-	default:
-		prs->input_flags.l3 = 0;
-		prs->l3_offset = ODP_PACKET_OFFSET_INVALID;
-		ip_proto = 255;  /* Reserved invalid by IANA */
+		prs->l3_offset = offset;
+		prs->parsed_layers = LAYER_L2;
+		if (layer == LAYER_L2)
+			return prs->error_flags.all != 0;
 	}
+	case LAYER_L3:
+	{
+		offset = prs->l3_offset;
+		parseptr = (const uint8_t *)(ptr + offset);
+		/* Set l3_offset+flag only for known ethtypes */
+		prs->input_flags.l3 = 1;
+
+		/* Parse Layer 3 headers */
+		switch (prs->ethtype) {
+		case _ODP_ETHTYPE_IPV4:
+			prs->input_flags.ipv4 = 1;
+			prs->ip_proto = parse_ipv4(prs, &parseptr, &offset,
+						   frame_len);
+			break;
+
+		case _ODP_ETHTYPE_IPV6:
+			prs->input_flags.ipv6 = 1;
+			prs->ip_proto = parse_ipv6(prs, &parseptr, &offset,
+						   frame_len, seg_len);
+			break;
+
+		case _ODP_ETHTYPE_ARP:
+			prs->input_flags.arp = 1;
+			prs->ip_proto = 255;  /* Reserved invalid by IANA */
+			break;
+
+		default:
+			prs->input_flags.l3 = 0;
+			prs->l3_offset = ODP_PACKET_OFFSET_INVALID;
+			prs->ip_proto = 255;  /* Reserved invalid by IANA */
+		}
 
-	/* Set l4_offset+flag only for known ip_proto */
-	prs->input_flags.l4 = 1;
-	prs->l4_offset = offset;
-
-	/* Parse Layer 4 headers */
-	switch (ip_proto) {
-	case _ODP_IPPROTO_ICMP:
-		prs->input_flags.icmp = 1;
-		break;
-
-	case _ODP_IPPROTO_TCP:
-		if (odp_unlikely(offset + _ODP_TCPHDR_LEN > seg_len))
-			return -1;
-		prs->input_flags.tcp = 1;
-		parse_tcp(prs, &parseptr, NULL);
-		break;
-
-	case _ODP_IPPROTO_UDP:
-		if (odp_unlikely(offset + _ODP_UDPHDR_LEN > seg_len))
-			return -1;
-		prs->input_flags.udp = 1;
-		parse_udp(prs, &parseptr, NULL);
-		break;
+		/* Set l4_offset+flag only for known ip_proto */
+		prs->l4_offset = offset;
+		prs->parsed_layers = LAYER_L3;
+		if (layer == LAYER_L3)
+			return prs->error_flags.all != 0;
+	}
+	case LAYER_L4:
+	{
+		offset = prs->l4_offset;
+		parseptr = (const uint8_t *)(ptr + offset);
+		prs->input_flags.l4 = 1;
+
+		/* Parse Layer 4 headers */
+		switch (prs->ip_proto) {
+		case _ODP_IPPROTO_ICMP:
+			prs->input_flags.icmp = 1;
+			break;
+
+		case _ODP_IPPROTO_TCP:
+			if (odp_unlikely(offset + _ODP_TCPHDR_LEN > seg_len))
+				return -1;
+			prs->input_flags.tcp = 1;
+			parse_tcp(prs, &parseptr, NULL);
+			break;
+
+		case _ODP_IPPROTO_UDP:
+			if (odp_unlikely(offset + _ODP_UDPHDR_LEN > seg_len))
+				return -1;
+			prs->input_flags.udp = 1;
+			parse_udp(prs, &parseptr, NULL);
+			break;
+
+		case _ODP_IPPROTO_AH:
+			prs->input_flags.ipsec = 1;
+			prs->input_flags.ipsec_ah = 1;
+			break;
+
+		case _ODP_IPPROTO_ESP:
+			prs->input_flags.ipsec = 1;
+			prs->input_flags.ipsec_esp = 1;
+			break;
+
+		default:
+			prs->input_flags.l4 = 0;
+			prs->l4_offset = ODP_PACKET_OFFSET_INVALID;
+			break;
+		}
 
-	case _ODP_IPPROTO_AH:
-		prs->input_flags.ipsec = 1;
-		prs->input_flags.ipsec_ah = 1;
+		prs->parsed_layers = LAYER_L4;
 		break;
-
-	case _ODP_IPPROTO_ESP:
-		prs->input_flags.ipsec = 1;
-		prs->input_flags.ipsec_esp = 1;
+	}
+	case LAYER_ALL:
 		break;
 
 	default:
-		prs->input_flags.l4 = 0;
-		prs->l4_offset = ODP_PACKET_OFFSET_INVALID;
-		break;
+		ODP_ERR("Invalid parse layer: %d\n", (int)layer);
+		return -1;
 	}
 
+	prs->parsed_layers = LAYER_ALL;
+
 parse_exit:
-	prs->input_flags.parsed_all = 1;
 	return prs->error_flags.all != 0;
 }
 
@@ -1323,5 +1360,17 @@ int packet_parse_full(odp_packet_hdr_t *pkt_hdr)
 	void *base = packet_map(pkt_hdr, 0, &seg_len);
 
 	return packet_parse_common(&pkt_hdr->p, base, pkt_hdr->frame_len,
-				   seg_len);
+				   seg_len, LAYER_ALL);
+}
+
+/**
+ * Simple packet parser
+ */
+int packet_parse_layer(odp_packet_hdr_t *pkt_hdr, layer_t layer)
+{
+	uint32_t seg_len;
+	void *base = packet_map(pkt_hdr, 0, &seg_len);
+
+	return packet_parse_common(&pkt_hdr->p, base, pkt_hdr->frame_len,
+				   seg_len, layer);
 }
