[SDFAB-357] Backport slicing in fabric and add support for QFI in PDR and fabric

Change-Id: Ieb10140dc0029a0cbf59ddfbb77f64f9a8c7379e
(cherry picked from commit 411f6f7f461db6491d627c2cb31642bdd6e7c8d8)
diff --git a/pipelines/fabric/impl/src/main/resources/include/control/acl.p4 b/pipelines/fabric/impl/src/main/resources/include/control/acl.p4
index ae86e3b..9d7c7ba 100644
--- a/pipelines/fabric/impl/src/main/resources/include/control/acl.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/control/acl.p4
@@ -21,29 +21,22 @@
 #include "../header.p4"
 
 control Acl (inout parsed_headers_t hdr,
-             inout fabric_metadata_t fabric_metadata,
+             inout fabric_metadata_t fabric_md,
              inout standard_metadata_t standard_metadata) {
-
-    ipv4_addr_t ipv4_src    = 0;
-    ipv4_addr_t ipv4_dst    = 0;
-    bit<8> ip_proto         = 0;
-    l4_port_t l4_sport      = 0;
-    l4_port_t l4_dport      = 0;
-
     /*
      * ACL Table.
      */
     direct_counter(CounterType.packets_and_bytes) acl_counter;
 
     action set_next_id_acl(next_id_t next_id) {
-        fabric_metadata.next_id = next_id;
+        fabric_md.next_id = next_id;
         acl_counter.count();
     }
 
     // Send immendiatelly to CPU - skip the rest of ingress.
     action punt_to_cpu() {
         standard_metadata.egress_spec = CPU_PORT;
-        fabric_metadata.skip_next = _TRUE;
+        fabric_md.skip_next = _TRUE;
         acl_counter.count();
     }
 
@@ -55,7 +48,7 @@
 
     action drop() {
         mark_to_drop(standard_metadata);
-        fabric_metadata.skip_next = _TRUE;
+        fabric_md.skip_next = _TRUE;
         acl_counter.count();
     }
 
@@ -70,14 +63,14 @@
             hdr.ethernet.src_addr           : ternary @name("eth_src");   // 48
             hdr.vlan_tag.vlan_id            : ternary @name("vlan_id");   // 12
             hdr.eth_type.value              : ternary @name("eth_type");  // 16
-            ipv4_src                        : ternary @name("ipv4_src");  // 32
-            ipv4_dst                        : ternary @name("ipv4_dst");  // 32
-            ip_proto                        : ternary @name("ip_proto");  // 8
+            fabric_md.lkp.ipv4_src          : ternary @name("ipv4_src");  // 32
+            fabric_md.lkp.ipv4_dst          : ternary @name("ipv4_dst");  // 32
+            fabric_md.lkp.ip_proto          : ternary @name("ip_proto");  // 8
             hdr.icmp.icmp_type              : ternary @name("icmp_type"); // 8
             hdr.icmp.icmp_code              : ternary @name("icmp_code"); // 8
-            l4_sport                        : ternary @name("l4_sport");  // 16
-            l4_dport                        : ternary @name("l4_dport");  // 16
-            fabric_metadata.port_type       : ternary @name("port_type"); // 2
+            fabric_md.lkp.l4_sport          : ternary @name("l4_sport");  // 16
+            fabric_md.lkp.l4_dport          : ternary @name("l4_dport");  // 16
+            fabric_md.port_type             : ternary @name("port_type"); // 2
         }
 
         actions = {
@@ -94,29 +87,6 @@
     }
 
     apply {
-        if (hdr.gtpu.isValid() && hdr.inner_ipv4.isValid()) {
-            ipv4_src = hdr.inner_ipv4.src_addr;
-            ipv4_dst = hdr.inner_ipv4.dst_addr;
-            ip_proto = hdr.inner_ipv4.protocol;
-            if (hdr.inner_tcp.isValid()) {
-                l4_sport = hdr.inner_tcp.sport;
-                l4_dport = hdr.inner_tcp.dport;
-            } else if (hdr.inner_udp.isValid()) {
-                l4_sport = hdr.inner_udp.sport;
-                l4_dport = hdr.inner_udp.dport;
-            }
-        } else if (hdr.ipv4.isValid()) {
-            ipv4_src = hdr.ipv4.src_addr;
-            ipv4_dst = hdr.ipv4.dst_addr;
-            ip_proto = hdr.ipv4.protocol;
-            if (hdr.tcp.isValid()) {
-                l4_sport = hdr.tcp.sport;
-                l4_dport = hdr.tcp.dport;
-            } else if (hdr.udp.isValid()) {
-                l4_sport = hdr.udp.sport;
-                l4_dport = hdr.udp.dport;
-            }
-        }
         acl.apply();
     }
 }
diff --git a/pipelines/fabric/impl/src/main/resources/include/control/lookup_md_init.p4 b/pipelines/fabric/impl/src/main/resources/include/control/lookup_md_init.p4
new file mode 100644
index 0000000..c672bc9
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/resources/include/control/lookup_md_init.p4
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __LOOKUP__
+#define __LOOKUP__
+
+control LookupMdInit (in parsed_headers_t hdr,
+                      out lookup_metadata_t lkp_md) {
+    apply {
+        lkp_md.is_ipv4 = _FALSE;
+        lkp_md.ipv4_src = 0;
+        lkp_md.ipv4_dst = 0;
+        lkp_md.ip_proto = 0;
+        lkp_md.l4_sport = 0;
+        lkp_md.l4_dport = 0;
+        lkp_md.icmp_type = 0;
+        lkp_md.icmp_code = 0;
+        if (hdr.inner_ipv4.isValid()) {
+            lkp_md.is_ipv4 = true;
+            lkp_md.ipv4_src = hdr.inner_ipv4.src_addr;
+            lkp_md.ipv4_dst = hdr.inner_ipv4.dst_addr;
+            lkp_md.ip_proto = hdr.inner_ipv4.protocol;
+            if (hdr.inner_tcp.isValid()) {
+                lkp_md.l4_sport = hdr.inner_tcp.sport;
+                lkp_md.l4_dport = hdr.inner_tcp.dport;
+            } else if (hdr.inner_udp.isValid()) {
+                lkp_md.l4_sport = hdr.inner_udp.sport;
+                lkp_md.l4_dport = hdr.inner_udp.dport;
+            } else if (hdr.inner_icmp.isValid()) {
+                lkp_md.icmp_type = hdr.inner_icmp.icmp_type;
+                lkp_md.icmp_code = hdr.inner_icmp.icmp_code;
+            }
+        } else if (hdr.ipv4.isValid()) {
+            lkp_md.is_ipv4 = true;
+            lkp_md.ipv4_src = hdr.ipv4.src_addr;
+            lkp_md.ipv4_dst = hdr.ipv4.dst_addr;
+            lkp_md.ip_proto = hdr.ipv4.protocol;
+            if (hdr.tcp.isValid()) {
+                lkp_md.l4_sport = hdr.tcp.sport;
+                lkp_md.l4_dport = hdr.tcp.dport;
+            } else if (hdr.udp.isValid()) {
+                lkp_md.l4_sport = hdr.udp.sport;
+                lkp_md.l4_dport = hdr.udp.dport;
+            } else if (hdr.icmp.isValid()) {
+                lkp_md.icmp_type = hdr.icmp.icmp_type;
+                lkp_md.icmp_code = hdr.icmp.icmp_code;
+            }
+        }
+    }
+}
+
+#endif
+
diff --git a/pipelines/fabric/impl/src/main/resources/include/control/slicing.p4 b/pipelines/fabric/impl/src/main/resources/include/control/slicing.p4
new file mode 100644
index 0000000..87d887a
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/resources/include/control/slicing.p4
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __SLICING__
+#define __SLICING__
+
+// ACL-like classification, maps lookup metadata to slice_id and tc. For UE
+// traffic, values can be overriden later by the SPGW PDR tables.
+// To apply the same slicing and QoS treatment end-to-end, we use the IPv4 DSCP
+// field to piggyback slice_id and tc (see EgressDscpRewriter). This is
+// especially important for UE traffic, where classification based on PDRs can
+// only happen at the ingress leaf switch (implementing the UPF function).
+// As such, for traffic coming from selected ports, we allow trusting the
+// slice_id and tc values carried in the dscp.
+control IngressSliceTcClassifier (in parsed_headers_t hdr,
+                                  inout fabric_metadata_t fabric_md,
+                                  in standard_metadata_t standard_metadata) {
+
+    direct_counter(CounterType.packets) classifier_stats;
+
+    action set_slice_id_tc(slice_id_t slice_id, tc_t tc) {
+        fabric_md.slice_id = slice_id;
+        fabric_md.tc = tc;
+        classifier_stats.count();
+    }
+
+    // Should be used only for infrastructure ports (leaf-leaf, or leaf-spine),
+    // or ports facing servers that implement early classification based on the
+    // SDFAB DSCP encoding (slice_id++tc).
+    action trust_dscp() {
+        fabric_md.slice_id = hdr.ipv4.dscp[SLICE_ID_WIDTH+TC_WIDTH-1:TC_WIDTH];
+        fabric_md.tc = hdr.ipv4.dscp[TC_WIDTH-1:0];
+        classifier_stats.count();
+    }
+
+    table classifier {
+        key = {
+            standard_metadata.ingress_port  : ternary @name("ig_port");
+            fabric_md.lkp.ipv4_src          : ternary @name("ipv4_src");
+            fabric_md.lkp.ipv4_dst          : ternary @name("ipv4_dst");
+            fabric_md.lkp.ip_proto          : ternary @name("ip_proto");
+            fabric_md.lkp.l4_sport          : ternary @name("l4_sport");
+            fabric_md.lkp.l4_dport          : ternary @name("l4_dport");
+        }
+        actions = {
+            set_slice_id_tc;
+            trust_dscp;
+        }
+        const default_action = set_slice_id_tc(DEFAULT_SLICE_ID, DEFAULT_TC);
+        counters = classifier_stats;
+        size = QOS_CLASSIFIER_TABLE_SIZE;
+    }
+
+    apply {
+        classifier.apply();
+    }
+}
+
+// Provides metering and mapping to queues based on slice_id and tc. Should be
+// applied after any other block writing slice_id and tc.
+control IngressQos (inout fabric_metadata_t fabric_md,
+                    inout standard_metadata_t standard_metadata) {
+
+    // One meter per tc per slice. The index should be slice_id++tc.
+    meter(1 << SLICE_TC_WIDTH, MeterType.bytes) slice_tc_meter;
+
+    direct_counter(CounterType.packets) queues_stats;
+
+    action set_queue(qid_t qid) {
+        // We can't set the queue id in bmv2.
+        queues_stats.count();
+    }
+
+    // For policing.
+    action meter_drop() {
+        mark_to_drop(standard_metadata);
+        queues_stats.count();
+    }
+
+    table queues {
+        key = {
+            fabric_md.slice_id:     exact   @name("slice_id");
+            fabric_md.tc:           exact   @name("tc");
+            fabric_md.packet_color: ternary @name("color"); // 0=GREEN, 1=YELLOW, 2=RED
+        }
+        actions = {
+            set_queue;
+            meter_drop;
+        }
+        const default_action = set_queue(0); // 0 = Best Effort
+        counters = queues_stats;
+        // Two times the number of tcs for all slices, because we might need to
+        // match on different colors for the same slice and tc.
+        size = 1 << (SLICE_TC_WIDTH + 1);
+    }
+
+    slice_tc_t slice_tc = fabric_md.slice_id++fabric_md.tc;
+
+    apply {
+        // Meter index should be 0 for all packets with default slice_id and tc.
+        slice_tc_meter.execute_meter((bit<32>) slice_tc, fabric_md.packet_color);
+        fabric_md.dscp = slice_tc;
+        queues.apply();
+    }
+}
+
+// Allows per-egress port rewriting of the outermost IPv4 DSCP field to
+// piggyback slice_id and tc across the fabric.
+control EgressDscpRewriter (inout parsed_headers_t hdr,
+                            in fabric_metadata_t fabric_md,
+                            in standard_metadata_t standard_metadata) {
+
+    bit<6> tmp_dscp = fabric_md.dscp;
+
+    action rewrite() {
+        // Do nothing, tmp_dscp is already initialized.
+    }
+
+    // Sets the DSCP field to zero. Should be used for edge ports facing devices
+    // that do not support the SDFAB DSCP encoding.
+    action clear() {
+        tmp_dscp = 0;
+    }
+
+    table rewriter {
+        key = {
+            standard_metadata.egress_port : exact @name("eg_port");
+        }
+        actions = {
+            rewrite;
+            clear;
+            @defaultonly nop;
+        }
+        const default_action = nop;
+        size = DSCP_REWRITER_TABLE_SIZE;
+    }
+
+    apply {
+        if (rewriter.apply().hit) {
+#ifdef WITH_SPGW
+            if (hdr.gtpu_ipv4.isValid()) {
+                hdr.ipv4.dscp = tmp_dscp;
+            } else
+#endif // WITH_SPGW
+            if (hdr.ipv4.isValid()) {
+                hdr.inner_ipv4.dscp = tmp_dscp;
+            }
+        }
+    }
+}
+
+#endif
diff --git a/pipelines/fabric/impl/src/main/resources/include/control/spgw.p4 b/pipelines/fabric/impl/src/main/resources/include/control/spgw.p4
index adacd9a..6750dbd 100644
--- a/pipelines/fabric/impl/src/main/resources/include/control/spgw.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/control/spgw.p4
@@ -45,6 +45,8 @@
         hdr.ipv4 = hdr.inner_ipv4;
         hdr.inner_ipv4.setInvalid();
         hdr.gtpu.setInvalid();
+        hdr.gtpu_options.setInvalid();
+        hdr.gtpu_ext_psc.setInvalid();
     }
     @hidden
     action decap_inner_tcp() {
@@ -115,10 +117,11 @@
     //===== Interface Tables ======//
     //=============================//
 
-    action load_iface(spgw_interface_t src_iface) {
+    action load_iface(spgw_interface_t src_iface, slice_id_t slice_id) {
         // Interface type can be access, core, from_dbuf (see InterfaceType enum)
         fabric_md.spgw.src_iface = src_iface;
         fabric_md.spgw.skip_spgw = _FALSE;
+        fabric_md.slice_id = slice_id;
     }
     action iface_miss() {
         fabric_md.spgw.src_iface = SPGW_IFACE_UNKNOWN;
@@ -143,21 +146,26 @@
     //=============================//
     //===== PDR Tables ======//
     //=============================//
-
     action load_pdr(pdr_ctr_id_t  ctr_id,
                     far_id_t      far_id,
-                    bit<1>        needs_gtpu_decap) {
+                    bit<1>        needs_gtpu_decap,
+                    tc_t          tc) {
         fabric_md.spgw.ctr_id = ctr_id;
         fabric_md.spgw.far_id = far_id;
         fabric_md.spgw.needs_gtpu_decap = (_BOOL)needs_gtpu_decap;
+        fabric_md.tc = tc;
     }
 
-    action load_pdr_qos(pdr_ctr_id_t ctr_id,
-                        far_id_t     far_id,
-                        bit<1>       needs_gtpu_decap,
-                        qid_t        qid) {
-        load_pdr(ctr_id, far_id, needs_gtpu_decap);
-        // we cannot set the qid, since bmv2 does not support it
+    action load_pdr_qos(pdr_ctr_id_t  ctr_id,
+                        far_id_t      far_id,
+                        bit<1>        needs_gtpu_decap,
+                        // Used to push QFI, valid for 5G traffic only
+                        bit<1>        needs_qfi_push,
+                        qfi_t         qfi,
+                        tc_t          tc) {
+        load_pdr(ctr_id, far_id, needs_gtpu_decap, tc);
+        fabric_md.spgw.qfi = qfi;
+        fabric_md.spgw.needs_qfi_push = (_BOOL)needs_qfi_push;
     }
 
     // These two tables scale well and cover the average case PDR
@@ -177,6 +185,10 @@
         key = {
             hdr.ipv4.dst_addr           : exact @name("tunnel_ipv4_dst");
             hdr.gtpu.teid               : exact @name("teid");
+            // Match valid only for 5G traffic
+            hdr.gtpu_ext_psc.isValid()  : exact @name("has_qfi");
+            // QFI metadata is 0 when gptu_ext_psc is invalid.
+            fabric_md.spgw.qfi          : exact @name("qfi");
         }
         actions = {
             load_pdr;
@@ -294,7 +306,6 @@
 
     counter(MAX_PDR_COUNTERS, CounterType.packets_and_bytes) pdr_counter;
 
-
     @hidden
     action gtpu_encap() {
         hdr.gtpu_ipv4.setValid();
@@ -320,7 +331,6 @@
                 + (UDP_HDR_SIZE + GTP_HDR_SIZE);
         hdr.gtpu_udp.checksum = 0; // Updated later, if WITH_SPGW_UDP_CSUM_UPDATE
 
-
         hdr.outer_gtpu.setValid();
         hdr.outer_gtpu.version = GTP_V1;
         hdr.outer_gtpu.pt = GTP_PROTOCOL_TYPE_GTP;
@@ -333,10 +343,35 @@
         hdr.outer_gtpu.teid = fabric_md.spgw.teid;
     }
 
+    @hidden
+    action gtpu_encap_qfi() {
+        gtpu_encap();
+        hdr.gtpu_ipv4.total_len = hdr.ipv4.total_len
+                    + IPV4_HDR_SIZE + UDP_HDR_SIZE + GTP_HDR_SIZE
+                    + GTPU_OPTIONS_HDR_BYTES + GTPU_EXT_PSC_HDR_BYTES;
+        hdr.gtpu_udp.len = fabric_md.spgw.ipv4_len
+                    + UDP_HDR_SIZE + GTP_HDR_SIZE
+                    + GTPU_OPTIONS_HDR_BYTES + GTPU_EXT_PSC_HDR_BYTES;
+        hdr.outer_gtpu.msglen = fabric_md.spgw.ipv4_len
+                    + GTPU_OPTIONS_HDR_BYTES + GTPU_EXT_PSC_HDR_BYTES;
+        hdr.outer_gtpu.ex_flag = 1;
+        hdr.outer_gtpu_options.setValid();
+        hdr.outer_gtpu_options.next_ext = GTPU_NEXT_EXT_PSC;
+        hdr.outer_gtpu_ext_psc.setValid();
+        hdr.outer_gtpu_ext_psc.type = GTPU_EXT_PSC_TYPE_DL;
+        hdr.outer_gtpu_ext_psc.len = GTPU_EXT_PSC_LEN;
+        hdr.outer_gtpu_ext_psc.qfi = fabric_md.spgw.qfi;
+        hdr.outer_gtpu_ext_psc.next_ext = GTPU_NEXT_EXT_NONE;
+    }
+
     apply {
         if (fabric_md.spgw.skip_spgw == _FALSE) {
             if (fabric_md.spgw.needs_gtpu_encap == _TRUE) {
-                gtpu_encap();
+                if (fabric_md.spgw.needs_qfi_push == _TRUE) {
+                    gtpu_encap_qfi();
+                } else {
+                    gtpu_encap();
+                }
             }
             if (fabric_md.spgw.skip_egress_pdr_ctr == _FALSE) {
                 pdr_counter.count(fabric_md.spgw.ctr_id);