aboutsummaryrefslogtreecommitdiff
path: root/hw/net/rocker/rocker_of_dpa.c
diff options
context:
space:
mode:
authorScott Feldman <sfeldma@gmail.com>2015-03-13 21:09:30 -0700
committerStefan Hajnoczi <stefanha@redhat.com>2015-05-11 14:49:04 +0100
commitdc488f888060afdc129e0cc8812cf50c4c083423 (patch)
treee0f534912a160080fa7cbb726894b6a02c1ff2b2 /hw/net/rocker/rocker_of_dpa.c
parentdc407ae8a75d03cf48e114d3812d077fa29a8bd9 (diff)
downloadqemu-dc488f888060afdc129e0cc8812cf50c4c083423.zip
qemu-dc488f888060afdc129e0cc8812cf50c4c083423.tar.gz
qemu-dc488f888060afdc129e0cc8812cf50c4c083423.tar.bz2
rocker: add new rocker switch device
Rocker is a simulated ethernet switch device. The device supports up to 62 front-panel ports and supports L2 switching and L3 routing functions, as well as L2/L3/L4 ACLs. The device presents a single PCI device for each switch, with a memory-mapped register space for device driver access. Rocker device is invoked with -device, for example a 4-port switch: -device rocker,name=sw1,len-ports=4,ports[0]=dev0,ports[1]=dev1, \ ports[2]=dev2,ports[3]=dev3 Each port is a netdev and can be paired with using -netdev id=<port name>. Signed-off-by: Scott Feldman <sfeldma@gmail.com> Signed-off-by: Jiri Pirko <jiri@resnulli.us> Acked-by: Scott Feldman <sfeldma@gmail.com> Acked-by: Jiri Pirko <jiri@resnulli.us> Signed-off-by: David Ahern <dsahern@gmail.com> Message-id: 1426306173-24884-7-git-send-email-sfeldma@gmail.com rocker: fix clang compiler errors Consolidate all forward typedef declarations to rocker.h. Signed-off-by: David Ahern <dsahern@gmail.com> Acked-by: Scott Feldman <sfeldma@gmail.com> Acked-by: Jiri Pirko <jiri@resnulli.us> rocker: add support for flow modification We had support for flow add/del. This adds support for flow mod. I needed this for L3 support where an existing route is modified using NLM_F_REPLACE. For example: ip route add 12.0.0.0/30 nexthop via 11.0.0.1 dev swp1 ip route change 12.0.0.0/30 nexthop via 11.0.0.9 dev swp2 The first cmd adds the route. The second cmd changes the existing route by changing its nexthop info. In the device, a mod operation results in the matching flow enty being modified with the new settings. This is atomic to the device. Signed-off-by: Scott Feldman <sfeldma@gmail.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Diffstat (limited to 'hw/net/rocker/rocker_of_dpa.c')
-rw-r--r--hw/net/rocker/rocker_of_dpa.c2315
1 files changed, 2315 insertions, 0 deletions
diff --git a/hw/net/rocker/rocker_of_dpa.c b/hw/net/rocker/rocker_of_dpa.c
new file mode 100644
index 0000000..1bcb7af
--- /dev/null
+++ b/hw/net/rocker/rocker_of_dpa.c
@@ -0,0 +1,2315 @@
+/*
+ * QEMU rocker switch emulation - OF-DPA flow processing support
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "net/eth.h"
+#include "qemu/iov.h"
+#include "qemu/timer.h"
+#include "qmp-commands.h"
+
+#include "rocker.h"
+#include "rocker_hw.h"
+#include "rocker_fp.h"
+#include "rocker_tlv.h"
+#include "rocker_world.h"
+#include "rocker_desc.h"
+#include "rocker_of_dpa.h"
+
+static const MACAddr zero_mac = { .a = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };
+static const MACAddr ff_mac = { .a = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } };
+
+typedef struct of_dpa {
+ World *world;
+ GHashTable *flow_tbl;
+ GHashTable *group_tbl;
+ unsigned int flow_tbl_max_size;
+ unsigned int group_tbl_max_size;
+} OfDpa;
+
+/* flow_key stolen mostly from OVS
+ *
+ * Note: fields that compare with network packet header fields
+ * are stored in network order (BE) to avoid per-packet field
+ * byte-swaps.
+ */
+
+typedef struct of_dpa_flow_key {
+ uint32_t in_pport; /* ingress port */
+ uint32_t tunnel_id; /* overlay tunnel id */
+ uint32_t tbl_id; /* table id */
+ struct {
+ __be16 vlan_id; /* 0 if no VLAN */
+ MACAddr src; /* ethernet source address */
+ MACAddr dst; /* ethernet destination address */
+ __be16 type; /* ethernet frame type */
+ } eth;
+ struct {
+ uint8_t proto; /* IP protocol or ARP opcode */
+ uint8_t tos; /* IP ToS */
+ uint8_t ttl; /* IP TTL/hop limit */
+ uint8_t frag; /* one of FRAG_TYPE_* */
+ } ip;
+ union {
+ struct {
+ struct {
+ __be32 src; /* IP source address */
+ __be32 dst; /* IP destination address */
+ } addr;
+ union {
+ struct {
+ __be16 src; /* TCP/UDP/SCTP source port */
+ __be16 dst; /* TCP/UDP/SCTP destination port */
+ __be16 flags; /* TCP flags */
+ } tp;
+ struct {
+ MACAddr sha; /* ARP source hardware address */
+ MACAddr tha; /* ARP target hardware address */
+ } arp;
+ };
+ } ipv4;
+ struct {
+ struct {
+ Ipv6Addr src; /* IPv6 source address */
+ Ipv6Addr dst; /* IPv6 destination address */
+ } addr;
+ __be32 label; /* IPv6 flow label */
+ struct {
+ __be16 src; /* TCP/UDP/SCTP source port */
+ __be16 dst; /* TCP/UDP/SCTP destination port */
+ __be16 flags; /* TCP flags */
+ } tp;
+ struct {
+ Ipv6Addr target; /* ND target address */
+ MACAddr sll; /* ND source link layer address */
+ MACAddr tll; /* ND target link layer address */
+ } nd;
+ } ipv6;
+ };
+ int width; /* how many uint64_t's in key? */
+} OfDpaFlowKey;
+
+/* Width of key which includes field 'f' in u64s, rounded up */
+#define FLOW_KEY_WIDTH(f) \
+ ((offsetof(OfDpaFlowKey, f) + \
+ sizeof(((OfDpaFlowKey *)0)->f) + \
+ sizeof(uint64_t) - 1) / sizeof(uint64_t))
+
+typedef struct of_dpa_flow_action {
+ uint32_t goto_tbl;
+ struct {
+ uint32_t group_id;
+ uint32_t tun_log_lport;
+ __be16 vlan_id;
+ } write;
+ struct {
+ __be16 new_vlan_id;
+ uint32_t out_pport;
+ uint8_t copy_to_cpu;
+ __be16 vlan_id;
+ } apply;
+} OfDpaFlowAction;
+
+typedef struct of_dpa_flow {
+ uint32_t lpm;
+ uint32_t priority;
+ uint32_t hardtime;
+ uint32_t idletime;
+ uint64_t cookie;
+ OfDpaFlowKey key;
+ OfDpaFlowKey mask;
+ OfDpaFlowAction action;
+ struct {
+ uint64_t hits;
+ int64_t install_time;
+ int64_t refresh_time;
+ uint64_t rx_pkts;
+ uint64_t tx_pkts;
+ } stats;
+} OfDpaFlow;
+
+typedef struct of_dpa_flow_pkt_fields {
+ uint32_t tunnel_id;
+ struct eth_header *ethhdr;
+ __be16 *h_proto;
+ struct vlan_header *vlanhdr;
+ struct ip_header *ipv4hdr;
+ struct ip6_header *ipv6hdr;
+ Ipv6Addr *ipv6_src_addr;
+ Ipv6Addr *ipv6_dst_addr;
+} OfDpaFlowPktFields;
+
+typedef struct of_dpa_flow_context {
+ uint32_t in_pport;
+ uint32_t tunnel_id;
+ struct iovec *iov;
+ int iovcnt;
+ struct eth_header ethhdr_rewrite;
+ struct vlan_header vlanhdr_rewrite;
+ struct vlan_header vlanhdr;
+ OfDpa *of_dpa;
+ OfDpaFlowPktFields fields;
+ OfDpaFlowAction action_set;
+} OfDpaFlowContext;
+
+typedef struct of_dpa_flow_match {
+ OfDpaFlowKey value;
+ OfDpaFlow *best;
+} OfDpaFlowMatch;
+
+typedef struct of_dpa_group {
+ uint32_t id;
+ union {
+ struct {
+ uint32_t out_pport;
+ uint8_t pop_vlan;
+ } l2_interface;
+ struct {
+ uint32_t group_id;
+ MACAddr src_mac;
+ MACAddr dst_mac;
+ __be16 vlan_id;
+ } l2_rewrite;
+ struct {
+ uint16_t group_count;
+ uint32_t *group_ids;
+ } l2_flood;
+ struct {
+ uint32_t group_id;
+ MACAddr src_mac;
+ MACAddr dst_mac;
+ __be16 vlan_id;
+ uint8_t ttl_check;
+ } l3_unicast;
+ };
+} OfDpaGroup;
+
+static int of_dpa_mask2prefix(__be32 mask)
+{
+ int i;
+ int count = 32;
+
+ for (i = 0; i < 32; i++) {
+ if (!(ntohl(mask) & ((2 << i) - 1))) {
+ count--;
+ }
+ }
+
+ return count;
+}
+
+#if defined(DEBUG_ROCKER)
+static void of_dpa_flow_key_dump(OfDpaFlowKey *key, OfDpaFlowKey *mask)
+{
+ char buf[512], *b = buf, *mac;
+
+ b += sprintf(b, " tbl %2d", key->tbl_id);
+
+ if (key->in_pport || (mask && mask->in_pport)) {
+ b += sprintf(b, " in_pport %2d", key->in_pport);
+ if (mask && mask->in_pport != 0xffffffff) {
+ b += sprintf(b, "/0x%08x", key->in_pport);
+ }
+ }
+
+ if (key->tunnel_id || (mask && mask->tunnel_id)) {
+ b += sprintf(b, " tun %8d", key->tunnel_id);
+ if (mask && mask->tunnel_id != 0xffffffff) {
+ b += sprintf(b, "/0x%08x", key->tunnel_id);
+ }
+ }
+
+ if (key->eth.vlan_id || (mask && mask->eth.vlan_id)) {
+ b += sprintf(b, " vlan %4d", ntohs(key->eth.vlan_id));
+ if (mask && mask->eth.vlan_id != 0xffff) {
+ b += sprintf(b, "/0x%04x", ntohs(key->eth.vlan_id));
+ }
+ }
+
+ if (memcmp(key->eth.src.a, zero_mac.a, ETH_ALEN) ||
+ (mask && memcmp(mask->eth.src.a, zero_mac.a, ETH_ALEN))) {
+ mac = qemu_mac_strdup_printf(key->eth.src.a);
+ b += sprintf(b, " src %s", mac);
+ g_free(mac);
+ if (mask && memcmp(mask->eth.src.a, ff_mac.a, ETH_ALEN)) {
+ mac = qemu_mac_strdup_printf(mask->eth.src.a);
+ b += sprintf(b, "/%s", mac);
+ g_free(mac);
+ }
+ }
+
+ if (memcmp(key->eth.dst.a, zero_mac.a, ETH_ALEN) ||
+ (mask && memcmp(mask->eth.dst.a, zero_mac.a, ETH_ALEN))) {
+ mac = qemu_mac_strdup_printf(key->eth.dst.a);
+ b += sprintf(b, " dst %s", mac);
+ g_free(mac);
+ if (mask && memcmp(mask->eth.dst.a, ff_mac.a, ETH_ALEN)) {
+ mac = qemu_mac_strdup_printf(mask->eth.dst.a);
+ b += sprintf(b, "/%s", mac);
+ g_free(mac);
+ }
+ }
+
+ if (key->eth.type || (mask && mask->eth.type)) {
+ b += sprintf(b, " type 0x%04x", ntohs(key->eth.type));
+ if (mask && mask->eth.type != 0xffff) {
+ b += sprintf(b, "/0x%04x", ntohs(mask->eth.type));
+ }
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ case 0x86dd:
+ if (key->ip.proto || (mask && mask->ip.proto)) {
+ b += sprintf(b, " ip proto %2d", key->ip.proto);
+ if (mask && mask->ip.proto != 0xff) {
+ b += sprintf(b, "/0x%02x", mask->ip.proto);
+ }
+ }
+ if (key->ip.tos || (mask && mask->ip.tos)) {
+ b += sprintf(b, " ip tos %2d", key->ip.tos);
+ if (mask && mask->ip.tos != 0xff) {
+ b += sprintf(b, "/0x%02x", mask->ip.tos);
+ }
+ }
+ break;
+ }
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ if (key->ipv4.addr.dst || (mask && mask->ipv4.addr.dst)) {
+ b += sprintf(b, " dst %s",
+ inet_ntoa(*(struct in_addr *)&key->ipv4.addr.dst));
+ if (mask) {
+ b += sprintf(b, "/%d",
+ of_dpa_mask2prefix(mask->ipv4.addr.dst));
+ }
+ }
+ break;
+ }
+ }
+
+ DPRINTF("%s\n", buf);
+}
+#else
+#define of_dpa_flow_key_dump(k, m)
+#endif
+
+static void _of_dpa_flow_match(void *key, void *value, void *user_data)
+{
+ OfDpaFlow *flow = value;
+ OfDpaFlowMatch *match = user_data;
+ uint64_t *k = (uint64_t *)&flow->key;
+ uint64_t *m = (uint64_t *)&flow->mask;
+ uint64_t *v = (uint64_t *)&match->value;
+ int i;
+
+ if (flow->key.tbl_id == match->value.tbl_id) {
+ of_dpa_flow_key_dump(&flow->key, &flow->mask);
+ }
+
+ if (flow->key.width > match->value.width) {
+ return;
+ }
+
+ for (i = 0; i < flow->key.width; i++, k++, m++, v++) {
+ if ((~*k & *m & *v) | (*k & *m & ~*v)) {
+ return;
+ }
+ }
+
+ DPRINTF("match\n");
+
+ if (!match->best ||
+ flow->priority > match->best->priority ||
+ flow->lpm > match->best->lpm) {
+ match->best = flow;
+ }
+}
+
+static OfDpaFlow *of_dpa_flow_match(OfDpa *of_dpa, OfDpaFlowMatch *match)
+{
+ DPRINTF("\nnew search\n");
+ of_dpa_flow_key_dump(&match->value, NULL);
+
+ g_hash_table_foreach(of_dpa->flow_tbl, _of_dpa_flow_match, match);
+
+ return match->best;
+}
+
+static OfDpaFlow *of_dpa_flow_find(OfDpa *of_dpa, uint64_t cookie)
+{
+ return g_hash_table_lookup(of_dpa->flow_tbl, &cookie);
+}
+
+static int of_dpa_flow_add(OfDpa *of_dpa, OfDpaFlow *flow)
+{
+ g_hash_table_insert(of_dpa->flow_tbl, &flow->cookie, flow);
+
+ return ROCKER_OK;
+}
+
+static void of_dpa_flow_del(OfDpa *of_dpa, OfDpaFlow *flow)
+{
+ g_hash_table_remove(of_dpa->flow_tbl, &flow->cookie);
+}
+
+static OfDpaFlow *of_dpa_flow_alloc(uint64_t cookie)
+{
+ OfDpaFlow *flow;
+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) / 1000;
+
+ flow = g_malloc0(sizeof(OfDpaFlow));
+ if (!flow) {
+ return NULL;
+ }
+
+ flow->cookie = cookie;
+ flow->mask.tbl_id = 0xffffffff;
+
+ flow->stats.install_time = flow->stats.refresh_time = now;
+
+ return flow;
+}
+
+static void of_dpa_flow_pkt_hdr_reset(OfDpaFlowContext *fc)
+{
+ OfDpaFlowPktFields *fields = &fc->fields;
+
+ fc->iov[0].iov_base = fields->ethhdr;
+ fc->iov[0].iov_len = sizeof(struct eth_header);
+ fc->iov[1].iov_base = fields->vlanhdr;
+ fc->iov[1].iov_len = fields->vlanhdr ? sizeof(struct vlan_header) : 0;
+}
+
+static void of_dpa_flow_pkt_parse(OfDpaFlowContext *fc,
+ const struct iovec *iov, int iovcnt)
+{
+ OfDpaFlowPktFields *fields = &fc->fields;
+ size_t sofar = 0;
+ int i;
+
+ sofar += sizeof(struct eth_header);
+ if (iov->iov_len < sofar) {
+ DPRINTF("flow_pkt_parse underrun on eth_header\n");
+ return;
+ }
+
+ fields->ethhdr = iov->iov_base;
+ fields->h_proto = &fields->ethhdr->h_proto;
+
+ if (ntohs(*fields->h_proto) == ETH_P_VLAN) {
+ sofar += sizeof(struct vlan_header);
+ if (iov->iov_len < sofar) {
+ DPRINTF("flow_pkt_parse underrun on vlan_header\n");
+ return;
+ }
+ fields->vlanhdr = (struct vlan_header *)(fields->ethhdr + 1);
+ fields->h_proto = &fields->vlanhdr->h_proto;
+ }
+
+ switch (ntohs(*fields->h_proto)) {
+ case ETH_P_IP:
+ sofar += sizeof(struct ip_header);
+ if (iov->iov_len < sofar) {
+ DPRINTF("flow_pkt_parse underrun on ip_header\n");
+ return;
+ }
+ fields->ipv4hdr = (struct ip_header *)(fields->h_proto + 1);
+ break;
+ case ETH_P_IPV6:
+ sofar += sizeof(struct ip6_header);
+ if (iov->iov_len < sofar) {
+ DPRINTF("flow_pkt_parse underrun on ip6_header\n");
+ return;
+ }
+ fields->ipv6hdr = (struct ip6_header *)(fields->h_proto + 1);
+ break;
+ }
+
+ /* To facilitate (potential) VLAN tag insertion, Make a
+ * copy of the iov and insert two new vectors at the
+ * beginning for eth hdr and vlan hdr. No data is copied,
+ * just the vectors.
+ */
+
+ of_dpa_flow_pkt_hdr_reset(fc);
+
+ fc->iov[2].iov_base = fields->h_proto + 1;
+ fc->iov[2].iov_len = iov->iov_len - fc->iov[0].iov_len - fc->iov[1].iov_len;
+
+ for (i = 1; i < iovcnt; i++) {
+ fc->iov[i+2] = iov[i];
+ }
+
+ fc->iovcnt = iovcnt + 2;
+}
+
+static void of_dpa_flow_pkt_insert_vlan(OfDpaFlowContext *fc, __be16 vlan_id)
+{
+ OfDpaFlowPktFields *fields = &fc->fields;
+ uint16_t h_proto = fields->ethhdr->h_proto;
+
+ if (fields->vlanhdr) {
+ DPRINTF("flow_pkt_insert_vlan packet already has vlan\n");
+ return;
+ }
+
+ fields->ethhdr->h_proto = htons(ETH_P_VLAN);
+ fields->vlanhdr = &fc->vlanhdr;
+ fields->vlanhdr->h_tci = vlan_id;
+ fields->vlanhdr->h_proto = h_proto;
+ fields->h_proto = &fields->vlanhdr->h_proto;
+
+ fc->iov[1].iov_base = fields->vlanhdr;
+ fc->iov[1].iov_len = sizeof(struct vlan_header);
+}
+
+static void of_dpa_flow_pkt_strip_vlan(OfDpaFlowContext *fc)
+{
+ OfDpaFlowPktFields *fields = &fc->fields;
+
+ if (!fields->vlanhdr) {
+ return;
+ }
+
+ fc->iov[0].iov_len -= sizeof(fields->ethhdr->h_proto);
+ fc->iov[1].iov_base = fields->h_proto;
+ fc->iov[1].iov_len = sizeof(fields->ethhdr->h_proto);
+}
+
+static void of_dpa_flow_pkt_hdr_rewrite(OfDpaFlowContext *fc,
+ uint8_t *src_mac, uint8_t *dst_mac,
+ __be16 vlan_id)
+{
+ OfDpaFlowPktFields *fields = &fc->fields;
+
+ if (src_mac || dst_mac) {
+ memcpy(&fc->ethhdr_rewrite, fields->ethhdr, sizeof(struct eth_header));
+ if (src_mac && memcmp(src_mac, zero_mac.a, ETH_ALEN)) {
+ memcpy(fc->ethhdr_rewrite.h_source, src_mac, ETH_ALEN);
+ }
+ if (dst_mac && memcmp(dst_mac, zero_mac.a, ETH_ALEN)) {
+ memcpy(fc->ethhdr_rewrite.h_dest, dst_mac, ETH_ALEN);
+ }
+ fc->iov[0].iov_base = &fc->ethhdr_rewrite;
+ }
+
+ if (vlan_id && fields->vlanhdr) {
+ fc->vlanhdr_rewrite = fc->vlanhdr;
+ fc->vlanhdr_rewrite.h_tci = vlan_id;
+ fc->iov[1].iov_base = &fc->vlanhdr_rewrite;
+ }
+}
+
+static void of_dpa_flow_ig_tbl(OfDpaFlowContext *fc, uint32_t tbl_id);
+
+static void of_dpa_ig_port_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT;
+ match->value.in_pport = fc->in_pport;
+ match->value.width = FLOW_KEY_WIDTH(tbl_id);
+}
+
+static void of_dpa_ig_port_miss(OfDpaFlowContext *fc)
+{
+ uint32_t port;
+
+ /* The default on miss is for packets from physical ports
+ * to go to the VLAN Flow Table. There is no default rule
+ * for packets from logical ports, which are dropped on miss.
+ */
+
+ if (fp_port_from_pport(fc->in_pport, &port)) {
+ of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_VLAN);
+ }
+}
+
+static void of_dpa_vlan_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_VLAN;
+ match->value.in_pport = fc->in_pport;
+ if (fc->fields.vlanhdr) {
+ match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
+ }
+ match->value.width = FLOW_KEY_WIDTH(eth.vlan_id);
+}
+
+static void of_dpa_vlan_insert(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ if (flow->action.apply.new_vlan_id) {
+ of_dpa_flow_pkt_insert_vlan(fc, flow->action.apply.new_vlan_id);
+ }
+}
+
+static void of_dpa_term_mac_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC;
+ match->value.in_pport = fc->in_pport;
+ match->value.eth.type = *fc->fields.h_proto;
+ match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
+ memcpy(match->value.eth.dst.a, fc->fields.ethhdr->h_dest,
+ sizeof(match->value.eth.dst.a));
+ match->value.width = FLOW_KEY_WIDTH(eth.type);
+}
+
+static void of_dpa_term_mac_miss(OfDpaFlowContext *fc)
+{
+ of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_BRIDGING);
+}
+
+static void of_dpa_apply_actions(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ fc->action_set.apply.copy_to_cpu = flow->action.apply.copy_to_cpu;
+ fc->action_set.apply.vlan_id = flow->key.eth.vlan_id;
+}
+
+static void of_dpa_bridging_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_BRIDGING;
+ if (fc->fields.vlanhdr) {
+ match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
+ } else if (fc->tunnel_id) {
+ match->value.tunnel_id = fc->tunnel_id;
+ }
+ memcpy(match->value.eth.dst.a, fc->fields.ethhdr->h_dest,
+ sizeof(match->value.eth.dst.a));
+ match->value.width = FLOW_KEY_WIDTH(eth.dst);
+}
+
+static void of_dpa_bridging_learn(OfDpaFlowContext *fc,
+ OfDpaFlow *dst_flow)
+{
+ OfDpaFlowMatch match = { { 0, }, };
+ OfDpaFlow *flow;
+ uint8_t *addr;
+ uint16_t vlan_id;
+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) / 1000;
+ int64_t refresh_delay = 1;
+
+ /* Do a lookup in bridge table by src_mac/vlan */
+
+ addr = fc->fields.ethhdr->h_source;
+ vlan_id = fc->fields.vlanhdr->h_tci;
+
+ match.value.tbl_id = ROCKER_OF_DPA_TABLE_ID_BRIDGING;
+ match.value.eth.vlan_id = vlan_id;
+ memcpy(match.value.eth.dst.a, addr, sizeof(match.value.eth.dst.a));
+ match.value.width = FLOW_KEY_WIDTH(eth.dst);
+
+ flow = of_dpa_flow_match(fc->of_dpa, &match);
+ if (flow) {
+ if (!memcmp(flow->mask.eth.dst.a, ff_mac.a,
+ sizeof(flow->mask.eth.dst.a))) {
+ /* src_mac/vlan already learned; if in_port and out_port
+ * don't match, the end station has moved and the port
+ * needs updating */
+ /* XXX implement the in_port/out_port check */
+ if (now - flow->stats.refresh_time < refresh_delay) {
+ return;
+ }
+ flow->stats.refresh_time = now;
+ }
+ }
+
+ /* Let driver know about mac/vlan. This may be a new mac/vlan
+ * or a refresh of existing mac/vlan that's been hit after the
+ * refresh_delay.
+ */
+
+ rocker_event_mac_vlan_seen(world_rocker(fc->of_dpa->world),
+ fc->in_pport, addr, vlan_id);
+}
+
+static void of_dpa_bridging_miss(OfDpaFlowContext *fc)
+{
+ of_dpa_bridging_learn(fc, NULL);
+ of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_ACL_POLICY);
+}
+
+static void of_dpa_bridging_action_write(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
+ fc->action_set.write.group_id = flow->action.write.group_id;
+ }
+ fc->action_set.write.tun_log_lport = flow->action.write.tun_log_lport;
+}
+
+static void of_dpa_unicast_routing_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING;
+ match->value.eth.type = *fc->fields.h_proto;
+ if (fc->fields.ipv4hdr) {
+ match->value.ipv4.addr.dst = fc->fields.ipv4hdr->ip_dst;
+ }
+ if (fc->fields.ipv6_dst_addr) {
+ memcpy(&match->value.ipv6.addr.dst, fc->fields.ipv6_dst_addr,
+ sizeof(match->value.ipv6.addr.dst));
+ }
+ match->value.width = FLOW_KEY_WIDTH(ipv6.addr.dst);
+}
+
+static void of_dpa_unicast_routing_miss(OfDpaFlowContext *fc)
+{
+ of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_ACL_POLICY);
+}
+
+static void of_dpa_unicast_routing_action_write(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
+ fc->action_set.write.group_id = flow->action.write.group_id;
+ }
+}
+
+static void
+of_dpa_multicast_routing_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING;
+ match->value.eth.type = *fc->fields.h_proto;
+ match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
+ if (fc->fields.ipv4hdr) {
+ match->value.ipv4.addr.src = fc->fields.ipv4hdr->ip_src;
+ match->value.ipv4.addr.dst = fc->fields.ipv4hdr->ip_dst;
+ }
+ if (fc->fields.ipv6_src_addr) {
+ memcpy(&match->value.ipv6.addr.src, fc->fields.ipv6_src_addr,
+ sizeof(match->value.ipv6.addr.src));
+ }
+ if (fc->fields.ipv6_dst_addr) {
+ memcpy(&match->value.ipv6.addr.dst, fc->fields.ipv6_dst_addr,
+ sizeof(match->value.ipv6.addr.dst));
+ }
+ match->value.width = FLOW_KEY_WIDTH(ipv6.addr.dst);
+}
+
+static void of_dpa_multicast_routing_miss(OfDpaFlowContext *fc)
+{
+ of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_ACL_POLICY);
+}
+
+static void
+of_dpa_multicast_routing_action_write(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
+ fc->action_set.write.group_id = flow->action.write.group_id;
+ }
+ fc->action_set.write.vlan_id = flow->action.write.vlan_id;
+}
+
+static void of_dpa_acl_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY;
+ match->value.in_pport = fc->in_pport;
+ memcpy(match->value.eth.src.a, fc->fields.ethhdr->h_source,
+ sizeof(match->value.eth.src.a));
+ memcpy(match->value.eth.dst.a, fc->fields.ethhdr->h_dest,
+ sizeof(match->value.eth.dst.a));
+ match->value.eth.type = *fc->fields.h_proto;
+ match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
+ match->value.width = FLOW_KEY_WIDTH(eth.type);
+ if (fc->fields.ipv4hdr) {
+ match->value.ip.proto = fc->fields.ipv4hdr->ip_p;
+ match->value.ip.tos = fc->fields.ipv4hdr->ip_tos;
+ match->value.width = FLOW_KEY_WIDTH(ip.tos);
+ } else if (fc->fields.ipv6hdr) {
+ match->value.ip.proto =
+ fc->fields.ipv6hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt;
+ match->value.ip.tos = 0; /* XXX what goes here? */
+ match->value.width = FLOW_KEY_WIDTH(ip.tos);
+ }
+}
+
+static void of_dpa_eg(OfDpaFlowContext *fc);
+static void of_dpa_acl_hit(OfDpaFlowContext *fc,
+ OfDpaFlow *dst_flow)
+{
+ of_dpa_eg(fc);
+}
+
+static void of_dpa_acl_action_write(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
+ fc->action_set.write.group_id = flow->action.write.group_id;
+ }
+}
+
+static void of_dpa_drop(OfDpaFlowContext *fc)
+{
+ /* drop packet */
+}
+
+static OfDpaGroup *of_dpa_group_find(OfDpa *of_dpa,
+ uint32_t group_id)
+{
+ return g_hash_table_lookup(of_dpa->group_tbl, &group_id);
+}
+
+static int of_dpa_group_add(OfDpa *of_dpa, OfDpaGroup *group)
+{
+ g_hash_table_insert(of_dpa->group_tbl, &group->id, group);
+
+ return 0;
+}
+
+#if 0
+static int of_dpa_group_mod(OfDpa *of_dpa, OfDpaGroup *group)
+{
+ OfDpaGroup *old_group = of_dpa_group_find(of_dpa, group->id);
+
+ if (!old_group) {
+ return -ENOENT;
+ }
+
+ /* XXX */
+
+ return 0;
+}
+#endif
+
+static int of_dpa_group_del(OfDpa *of_dpa, OfDpaGroup *group)
+{
+ g_hash_table_remove(of_dpa->group_tbl, &group->id);
+
+ return 0;
+}
+
+#if 0
+static int of_dpa_group_get_stats(OfDpa *of_dpa, uint32_t id)
+{
+ OfDpaGroup *group = of_dpa_group_find(of_dpa, id);
+
+ if (!group) {
+ return -ENOENT;
+ }
+
+ /* XXX get/return stats */
+
+ return 0;
+}
+#endif
+
+static OfDpaGroup *of_dpa_group_alloc(uint32_t id)
+{
+ OfDpaGroup *group = g_malloc0(sizeof(OfDpaGroup));
+
+ if (!group) {
+ return NULL;
+ }
+
+ group->id = id;
+
+ return group;
+}
+
+static void of_dpa_output_l2_interface(OfDpaFlowContext *fc,
+ OfDpaGroup *group)
+{
+ if (group->l2_interface.pop_vlan) {
+ of_dpa_flow_pkt_strip_vlan(fc);
+ }
+
+ /* Note: By default, and as per the OpenFlow 1.3.1
+ * specification, a packet cannot be forwarded back
+ * to the IN_PORT from which it came in. An action
+ * bucket that specifies the particular packet's
+ * egress port is not evaluated.
+ */
+
+ if (group->l2_interface.out_pport == 0) {
+ rx_produce(fc->of_dpa->world, fc->in_pport, fc->iov, fc->iovcnt);
+ } else if (group->l2_interface.out_pport != fc->in_pport) {
+ rocker_port_eg(world_rocker(fc->of_dpa->world),
+ group->l2_interface.out_pport,
+ fc->iov, fc->iovcnt);
+ }
+}
+
+static void of_dpa_output_l2_rewrite(OfDpaFlowContext *fc,
+ OfDpaGroup *group)
+{
+ OfDpaGroup *l2_group =
+ of_dpa_group_find(fc->of_dpa, group->l2_rewrite.group_id);
+
+ if (!l2_group) {
+ return;
+ }
+
+ of_dpa_flow_pkt_hdr_rewrite(fc, group->l2_rewrite.src_mac.a,
+ group->l2_rewrite.dst_mac.a,
+ group->l2_rewrite.vlan_id);
+ of_dpa_output_l2_interface(fc, l2_group);
+}
+
+static void of_dpa_output_l2_flood(OfDpaFlowContext *fc,
+ OfDpaGroup *group)
+{
+ OfDpaGroup *l2_group;
+ int i;
+
+ for (i = 0; i < group->l2_flood.group_count; i++) {
+ of_dpa_flow_pkt_hdr_reset(fc);
+ l2_group = of_dpa_group_find(fc->of_dpa, group->l2_flood.group_ids[i]);
+ if (!l2_group) {
+ continue;
+ }
+ switch (ROCKER_GROUP_TYPE_GET(l2_group->id)) {
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
+ of_dpa_output_l2_interface(fc, l2_group);
+ break;
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
+ of_dpa_output_l2_rewrite(fc, l2_group);
+ break;
+ }
+ }
+}
+
+static void of_dpa_output_l3_unicast(OfDpaFlowContext *fc, OfDpaGroup *group)
+{
+ OfDpaGroup *l2_group =
+ of_dpa_group_find(fc->of_dpa, group->l3_unicast.group_id);
+
+ if (!l2_group) {
+ return;
+ }
+
+ of_dpa_flow_pkt_hdr_rewrite(fc, group->l3_unicast.src_mac.a,
+ group->l3_unicast.dst_mac.a,
+ group->l3_unicast.vlan_id);
+ /* XXX need ttl_check */
+ of_dpa_output_l2_interface(fc, l2_group);
+}
+
+static void of_dpa_eg(OfDpaFlowContext *fc)
+{
+ OfDpaFlowAction *set = &fc->action_set;
+ OfDpaGroup *group;
+ uint32_t group_id;
+
+ /* send a copy of pkt to CPU (controller)? */
+
+ if (set->apply.copy_to_cpu) {
+ group_id = ROCKER_GROUP_L2_INTERFACE(set->apply.vlan_id, 0);
+ group = of_dpa_group_find(fc->of_dpa, group_id);
+ if (group) {
+ of_dpa_output_l2_interface(fc, group);
+ of_dpa_flow_pkt_hdr_reset(fc);
+ }
+ }
+
+ /* process group write actions */
+
+ if (!set->write.group_id) {
+ return;
+ }
+
+ group = of_dpa_group_find(fc->of_dpa, set->write.group_id);
+ if (!group) {
+ return;
+ }
+
+ switch (ROCKER_GROUP_TYPE_GET(group->id)) {
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
+ of_dpa_output_l2_interface(fc, group);
+ break;
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
+ of_dpa_output_l2_rewrite(fc, group);
+ break;
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD:
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST:
+ of_dpa_output_l2_flood(fc, group);
+ break;
+ case ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST:
+ of_dpa_output_l3_unicast(fc, group);
+ break;
+ }
+}
+
+typedef struct of_dpa_flow_tbl_ops {
+ void (*build_match)(OfDpaFlowContext *fc, OfDpaFlowMatch *match);
+ void (*hit)(OfDpaFlowContext *fc, OfDpaFlow *flow);
+ void (*miss)(OfDpaFlowContext *fc);
+ void (*hit_no_goto)(OfDpaFlowContext *fc);
+ void (*action_apply)(OfDpaFlowContext *fc, OfDpaFlow *flow);
+ void (*action_write)(OfDpaFlowContext *fc, OfDpaFlow *flow);
+} OfDpaFlowTblOps;
+
+static OfDpaFlowTblOps of_dpa_tbl_ops[] = {
+ [ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT] = {
+ .build_match = of_dpa_ig_port_build_match,
+ .miss = of_dpa_ig_port_miss,
+ .hit_no_goto = of_dpa_drop,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_VLAN] = {
+ .build_match = of_dpa_vlan_build_match,
+ .hit_no_goto = of_dpa_drop,
+ .action_apply = of_dpa_vlan_insert,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC] = {
+ .build_match = of_dpa_term_mac_build_match,
+ .miss = of_dpa_term_mac_miss,
+ .hit_no_goto = of_dpa_drop,
+ .action_apply = of_dpa_apply_actions,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_BRIDGING] = {
+ .build_match = of_dpa_bridging_build_match,
+ .hit = of_dpa_bridging_learn,
+ .miss = of_dpa_bridging_miss,
+ .hit_no_goto = of_dpa_drop,
+ .action_apply = of_dpa_apply_actions,
+ .action_write = of_dpa_bridging_action_write,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING] = {
+ .build_match = of_dpa_unicast_routing_build_match,
+ .miss = of_dpa_unicast_routing_miss,
+ .hit_no_goto = of_dpa_drop,
+ .action_write = of_dpa_unicast_routing_action_write,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING] = {
+ .build_match = of_dpa_multicast_routing_build_match,
+ .miss = of_dpa_multicast_routing_miss,
+ .hit_no_goto = of_dpa_drop,
+ .action_write = of_dpa_multicast_routing_action_write,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_ACL_POLICY] = {
+ .build_match = of_dpa_acl_build_match,
+ .hit = of_dpa_acl_hit,
+ .miss = of_dpa_eg,
+ .action_apply = of_dpa_apply_actions,
+ .action_write = of_dpa_acl_action_write,
+ },
+};
+
+static void of_dpa_flow_ig_tbl(OfDpaFlowContext *fc, uint32_t tbl_id)
+{
+ OfDpaFlowTblOps *ops = &of_dpa_tbl_ops[tbl_id];
+ OfDpaFlowMatch match = { { 0, }, };
+ OfDpaFlow *flow;
+
+ if (ops->build_match) {
+ ops->build_match(fc, &match);
+ } else {
+ return;
+ }
+
+ flow = of_dpa_flow_match(fc->of_dpa, &match);
+ if (!flow) {
+ if (ops->miss) {
+ ops->miss(fc);
+ }
+ return;
+ }
+
+ flow->stats.hits++;
+
+ if (ops->action_apply) {
+ ops->action_apply(fc, flow);
+ }
+
+ if (ops->action_write) {
+ ops->action_write(fc, flow);
+ }
+
+ if (ops->hit) {
+ ops->hit(fc, flow);
+ }
+
+ if (flow->action.goto_tbl) {
+ of_dpa_flow_ig_tbl(fc, flow->action.goto_tbl);
+ } else if (ops->hit_no_goto) {
+ ops->hit_no_goto(fc);
+ }
+
+ /* drop packet */
+}
+
+static ssize_t of_dpa_ig(World *world, uint32_t pport,
+ const struct iovec *iov, int iovcnt)
+{
+ struct iovec iov_copy[iovcnt + 2];
+ OfDpaFlowContext fc = {
+ .of_dpa = world_private(world),
+ .in_pport = pport,
+ .iov = iov_copy,
+ .iovcnt = iovcnt + 2,
+ };
+
+ of_dpa_flow_pkt_parse(&fc, iov, iovcnt);
+ of_dpa_flow_ig_tbl(&fc, ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT);
+
+ return iov_size(iov, iovcnt);
+}
+
+#define ROCKER_TUNNEL_LPORT 0x00010000
+
+static int of_dpa_cmd_add_ig_port(OfDpaFlow *flow, RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ bool overlay_tunnel;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT;
+ key->width = FLOW_KEY_WIDTH(tbl_id);
+
+ key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]) {
+ mask->in_pport =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]);
+ }
+
+ overlay_tunnel = !!(key->in_pport & ROCKER_TUNNEL_LPORT);
+
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+
+ if (!overlay_tunnel && action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_VLAN) {
+ return -ROCKER_EINVAL;
+ }
+
+ if (overlay_tunnel && action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_BRIDGING) {
+ return -ROCKER_EINVAL;
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_vlan(OfDpaFlow *flow, RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ uint32_t port;
+ bool untagged;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ DPRINTF("Must give in_pport and vlan_id to install VLAN tbl entry\n");
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_VLAN;
+ key->width = FLOW_KEY_WIDTH(eth.vlan_id);
+
+ key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
+ if (!fp_port_from_pport(key->in_pport, &port)) {
+ DPRINTF("in_pport (%d) not a front-panel port\n", key->in_pport);
+ return -ROCKER_EINVAL;
+ }
+ mask->in_pport = 0xffffffff;
+
+ key->eth.vlan_id = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]) {
+ mask->eth.vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]);
+ }
+
+ if (key->eth.vlan_id) {
+ untagged = false; /* filtering */
+ } else {
+ untagged = true;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+ if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC) {
+ DPRINTF("Goto tbl (%d) must be TERM_MAC\n", action->goto_tbl);
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (untagged) {
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_NEW_VLAN_ID]) {
+ DPRINTF("Must specify new vlan_id if untagged\n");
+ return -ROCKER_EINVAL;
+ }
+ action->apply.new_vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_NEW_VLAN_ID]);
+ if (1 > ntohs(action->apply.new_vlan_id) ||
+ ntohs(action->apply.new_vlan_id) > 4095) {
+ DPRINTF("New vlan_id (%d) must be between 1 and 4095\n",
+ ntohs(action->apply.new_vlan_id));
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_term_mac(OfDpaFlow *flow, RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ const MACAddr ipv4_mcast = { .a = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 } };
+ const MACAddr ipv4_mask = { .a = { 0xff, 0xff, 0xff, 0x80, 0x00, 0x00 } };
+ const MACAddr ipv6_mcast = { .a = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x00 } };
+ const MACAddr ipv6_mask = { .a = { 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } };
+ uint32_t port;
+ bool unicast = false;
+ bool multicast = false;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC;
+ key->width = FLOW_KEY_WIDTH(eth.type);
+
+ key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
+ if (!fp_port_from_pport(key->in_pport, &port)) {
+ return -ROCKER_EINVAL;
+ }
+ mask->in_pport =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]);
+
+ key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
+ if (key->eth.type != htons(0x0800) && key->eth.type != htons(0x86dd)) {
+ return -ROCKER_EINVAL;
+ }
+ mask->eth.type = htons(0xffff);
+
+ memcpy(key->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
+ sizeof(key->eth.dst.a));
+ memcpy(mask->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]),
+ sizeof(mask->eth.dst.a));
+
+ if ((key->eth.dst.a[0] & 0x01) == 0x00) {
+ unicast = true;
+ }
+
+ /* only two wildcard rules are acceptable for IPv4 and IPv6 multicast */
+ if (memcmp(key->eth.dst.a, ipv4_mcast.a, sizeof(key->eth.dst.a)) == 0 &&
+ memcmp(mask->eth.dst.a, ipv4_mask.a, sizeof(mask->eth.dst.a)) == 0) {
+ multicast = true;
+ }
+ if (memcmp(key->eth.dst.a, ipv6_mcast.a, sizeof(key->eth.dst.a)) == 0 &&
+ memcmp(mask->eth.dst.a, ipv6_mask.a, sizeof(mask->eth.dst.a)) == 0) {
+ multicast = true;
+ }
+
+ if (!unicast && !multicast) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->eth.vlan_id = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+ mask->eth.vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]);
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+
+ if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING &&
+ action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING) {
+ return -ROCKER_EINVAL;
+ }
+
+ if (unicast &&
+ action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING) {
+ return -ROCKER_EINVAL;
+ }
+
+ if (multicast &&
+ action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]) {
+ action->apply.copy_to_cpu =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]);
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_bridging(OfDpaFlow *flow, RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ bool unicast = false;
+ bool dst_mac = false;
+ bool dst_mac_mask = false;
+ enum {
+ BRIDGING_MODE_UNKNOWN,
+ BRIDGING_MODE_VLAN_UCAST,
+ BRIDGING_MODE_VLAN_MCAST,
+ BRIDGING_MODE_VLAN_DFLT,
+ BRIDGING_MODE_TUNNEL_UCAST,
+ BRIDGING_MODE_TUNNEL_MCAST,
+ BRIDGING_MODE_TUNNEL_DFLT,
+ } mode = BRIDGING_MODE_UNKNOWN;
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_BRIDGING;
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ key->eth.vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+ mask->eth.vlan_id = 0xffff;
+ key->width = FLOW_KEY_WIDTH(eth.vlan_id);
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_ID]) {
+ key->tunnel_id =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_ID]);
+ mask->tunnel_id = 0xffffffff;
+ key->width = FLOW_KEY_WIDTH(tunnel_id);
+ }
+
+ /* can't do VLAN bridging and tunnel bridging at same time */
+ if (key->eth.vlan_id && key->tunnel_id) {
+ DPRINTF("can't do VLAN bridging and tunnel bridging at same time\n");
+ return -ROCKER_EINVAL;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
+ memcpy(key->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
+ sizeof(key->eth.dst.a));
+ key->width = FLOW_KEY_WIDTH(eth.dst);
+ dst_mac = true;
+ unicast = (key->eth.dst.a[0] & 0x01) == 0x00;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]) {
+ memcpy(mask->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]),
+ sizeof(mask->eth.dst.a));
+ key->width = FLOW_KEY_WIDTH(eth.dst);
+ dst_mac_mask = true;
+ } else if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
+ memcpy(mask->eth.dst.a, ff_mac.a, sizeof(mask->eth.dst.a));
+ }
+
+ if (key->eth.vlan_id) {
+ if (dst_mac && !dst_mac_mask) {
+ mode = unicast ? BRIDGING_MODE_VLAN_UCAST :
+ BRIDGING_MODE_VLAN_MCAST;
+ } else if ((dst_mac && dst_mac_mask) || !dst_mac) {
+ mode = BRIDGING_MODE_VLAN_DFLT;
+ }
+ } else if (key->tunnel_id) {
+ if (dst_mac && !dst_mac_mask) {
+ mode = unicast ? BRIDGING_MODE_TUNNEL_UCAST :
+ BRIDGING_MODE_TUNNEL_MCAST;
+ } else if ((dst_mac && dst_mac_mask) || !dst_mac) {
+ mode = BRIDGING_MODE_TUNNEL_DFLT;
+ }
+ }
+
+ if (mode == BRIDGING_MODE_UNKNOWN) {
+ DPRINTF("Unknown bridging mode\n");
+ return -ROCKER_EINVAL;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+ if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_ACL_POLICY) {
+ DPRINTF("Briding goto tbl must be ACL policy\n");
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
+ action->write.group_id =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
+ switch (mode) {
+ case BRIDGING_MODE_VLAN_UCAST:
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE) {
+ DPRINTF("Bridging mode vlan ucast needs L2 "
+ "interface group (0x%08x)\n",
+ action->write.group_id);
+ return -ROCKER_EINVAL;
+ }
+ break;
+ case BRIDGING_MODE_VLAN_MCAST:
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST) {
+ DPRINTF("Bridging mode vlan mcast needs L2 "
+ "mcast group (0x%08x)\n",
+ action->write.group_id);
+ return -ROCKER_EINVAL;
+ }
+ break;
+ case BRIDGING_MODE_VLAN_DFLT:
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD) {
+ DPRINTF("Bridging mode vlan dflt needs L2 "
+ "flood group (0x%08x)\n",
+ action->write.group_id);
+ return -ROCKER_EINVAL;
+ }
+ break;
+ case BRIDGING_MODE_TUNNEL_MCAST:
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_OVERLAY) {
+ DPRINTF("Bridging mode tunnel mcast needs L2 "
+ "overlay group (0x%08x)\n",
+ action->write.group_id);
+ return -ROCKER_EINVAL;
+ }
+ break;
+ case BRIDGING_MODE_TUNNEL_DFLT:
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_OVERLAY) {
+ DPRINTF("Bridging mode tunnel dflt needs L2 "
+ "overlay group (0x%08x)\n",
+ action->write.group_id);
+ return -ROCKER_EINVAL;
+ }
+ break;
+ default:
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_LPORT]) {
+ action->write.tun_log_lport =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_LPORT]);
+ if (mode != BRIDGING_MODE_TUNNEL_UCAST) {
+ DPRINTF("Have tunnel logical port but not "
+ "in bridging tunnel mode\n");
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]) {
+ action->apply.copy_to_cpu =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]);
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_unicast_routing(OfDpaFlow *flow,
+ RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ enum {
+ UNICAST_ROUTING_MODE_UNKNOWN,
+ UNICAST_ROUTING_MODE_IPV4,
+ UNICAST_ROUTING_MODE_IPV6,
+ } mode = UNICAST_ROUTING_MODE_UNKNOWN;
+ uint8_t type;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING;
+ key->width = FLOW_KEY_WIDTH(ipv6.addr.dst);
+
+ key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ mode = UNICAST_ROUTING_MODE_IPV4;
+ break;
+ case 0x86dd:
+ mode = UNICAST_ROUTING_MODE_IPV6;
+ break;
+ default:
+ return -ROCKER_EINVAL;
+ }
+ mask->eth.type = htons(0xffff);
+
+ switch (mode) {
+ case UNICAST_ROUTING_MODE_IPV4:
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]) {
+ return -ROCKER_EINVAL;
+ }
+ key->ipv4.addr.dst =
+ rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]);
+ if (ipv4_addr_is_multicast(key->ipv4.addr.dst)) {
+ return -ROCKER_EINVAL;
+ }
+ flow->lpm = of_dpa_mask2prefix(htonl(0xffffffff));
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP_MASK]) {
+ mask->ipv4.addr.dst =
+ rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP_MASK]);
+ flow->lpm = of_dpa_mask2prefix(mask->ipv4.addr.dst);
+ }
+ break;
+ case UNICAST_ROUTING_MODE_IPV6:
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]) {
+ return -ROCKER_EINVAL;
+ }
+ memcpy(&key->ipv6.addr.dst,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]),
+ sizeof(key->ipv6.addr.dst));
+ if (ipv6_addr_is_multicast(&key->ipv6.addr.dst)) {
+ return -ROCKER_EINVAL;
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6_MASK]) {
+ memcpy(&mask->ipv6.addr.dst,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6_MASK]),
+ sizeof(mask->ipv6.addr.dst));
+ }
+ break;
+ default:
+ return -ROCKER_EINVAL;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+ if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_ACL_POLICY) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
+ action->write.group_id =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
+ type = ROCKER_GROUP_TYPE_GET(action->write.group_id);
+ if (type != ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE &&
+ type != ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST &&
+ type != ROCKER_OF_DPA_GROUP_TYPE_L3_ECMP) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_multicast_routing(OfDpaFlow *flow,
+ RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ enum {
+ MULTICAST_ROUTING_MODE_UNKNOWN,
+ MULTICAST_ROUTING_MODE_IPV4,
+ MULTICAST_ROUTING_MODE_IPV6,
+ } mode = MULTICAST_ROUTING_MODE_UNKNOWN;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING;
+ key->width = FLOW_KEY_WIDTH(ipv6.addr.dst);
+
+ key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ mode = MULTICAST_ROUTING_MODE_IPV4;
+ break;
+ case 0x86dd:
+ mode = MULTICAST_ROUTING_MODE_IPV6;
+ break;
+ default:
+ return -ROCKER_EINVAL;
+ }
+
+ key->eth.vlan_id = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+
+ switch (mode) {
+ case MULTICAST_ROUTING_MODE_IPV4:
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP]) {
+ key->ipv4.addr.src =
+ rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP]);
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP_MASK]) {
+ mask->ipv4.addr.src =
+ rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP_MASK]);
+ }
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP]) {
+ if (mask->ipv4.addr.src != 0) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->ipv4.addr.dst =
+ rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]);
+ if (!ipv4_addr_is_multicast(key->ipv4.addr.dst)) {
+ return -ROCKER_EINVAL;
+ }
+
+ break;
+
+ case MULTICAST_ROUTING_MODE_IPV6:
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6]) {
+ memcpy(&key->ipv6.addr.src,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6]),
+ sizeof(key->ipv6.addr.src));
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6_MASK]) {
+ memcpy(&mask->ipv6.addr.src,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6_MASK]),
+ sizeof(mask->ipv6.addr.src));
+ }
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6]) {
+ if (mask->ipv6.addr.src.addr32[0] != 0 &&
+ mask->ipv6.addr.src.addr32[1] != 0 &&
+ mask->ipv6.addr.src.addr32[2] != 0 &&
+ mask->ipv6.addr.src.addr32[3] != 0) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]) {
+ return -ROCKER_EINVAL;
+ }
+
+ memcpy(&key->ipv6.addr.dst,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]),
+ sizeof(key->ipv6.addr.dst));
+ if (!ipv6_addr_is_multicast(&key->ipv6.addr.dst)) {
+ return -ROCKER_EINVAL;
+ }
+
+ break;
+
+ default:
+ return -ROCKER_EINVAL;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+ if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_ACL_POLICY) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
+ action->write.group_id =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L3_MCAST) {
+ return -ROCKER_EINVAL;
+ }
+ action->write.vlan_id = key->eth.vlan_id;
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_acl_ip(OfDpaFlowKey *key, OfDpaFlowKey *mask,
+ RockerTlv **flow_tlvs)
+{
+ key->width = FLOW_KEY_WIDTH(ip.tos);
+
+ key->ip.proto = 0;
+ key->ip.tos = 0;
+ mask->ip.proto = 0;
+ mask->ip.tos = 0;
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO]) {
+ key->ip.proto =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO]);
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO_MASK]) {
+ mask->ip.proto =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO_MASK]);
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP]) {
+ key->ip.tos =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP]);
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP_MASK]) {
+ mask->ip.tos =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP_MASK]);
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN]) {
+ key->ip.tos |=
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN]) << 6;
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN_MASK]) {
+ mask->ip.tos |=
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN_MASK]) << 6;
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_acl(OfDpaFlow *flow, RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ enum {
+ ACL_MODE_UNKNOWN,
+ ACL_MODE_IPV4_VLAN,
+ ACL_MODE_IPV6_VLAN,
+ ACL_MODE_IPV4_TENANT,
+ ACL_MODE_IPV6_TENANT,
+ ACL_MODE_NON_IP_VLAN,
+ ACL_MODE_NON_IP_TENANT,
+ ACL_MODE_ANY_VLAN,
+ ACL_MODE_ANY_TENANT,
+ } mode = ACL_MODE_UNKNOWN;
+ int err = ROCKER_OK;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]) {
+ return -ROCKER_EINVAL;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID] &&
+ flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_ID]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY;
+ key->width = FLOW_KEY_WIDTH(eth.type);
+
+ key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]) {
+ mask->in_pport =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]);
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]) {
+ memcpy(key->eth.src.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]),
+ sizeof(key->eth.src.a));
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC_MASK]) {
+ memcpy(mask->eth.src.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC_MASK]),
+ sizeof(mask->eth.src.a));
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
+ memcpy(key->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
+ sizeof(key->eth.dst.a));
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]) {
+ memcpy(mask->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]),
+ sizeof(mask->eth.dst.a));
+ }
+
+ key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
+ if (key->eth.type) {
+ mask->eth.type = 0xffff;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ key->eth.vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]) {
+ mask->eth.vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]);
+ }
+
+ switch (ntohs(key->eth.type)) {
+ case 0x0000:
+ mode = (key->eth.vlan_id) ? ACL_MODE_ANY_VLAN : ACL_MODE_ANY_TENANT;
+ break;
+ case 0x0800:
+ mode = (key->eth.vlan_id) ? ACL_MODE_IPV4_VLAN : ACL_MODE_IPV4_TENANT;
+ break;
+ case 0x86dd:
+ mode = (key->eth.vlan_id) ? ACL_MODE_IPV6_VLAN : ACL_MODE_IPV6_TENANT;
+ break;
+ default:
+ mode = (key->eth.vlan_id) ? ACL_MODE_NON_IP_VLAN :
+ ACL_MODE_NON_IP_TENANT;
+ break;
+ }
+
+ /* XXX only supporting VLAN modes for now */
+ if (mode != ACL_MODE_IPV4_VLAN &&
+ mode != ACL_MODE_IPV6_VLAN &&
+ mode != ACL_MODE_NON_IP_VLAN &&
+ mode != ACL_MODE_ANY_VLAN) {
+ return -ROCKER_EINVAL;
+ }
+
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ case 0x86dd:
+ err = of_dpa_cmd_add_acl_ip(key, mask, flow_tlvs);
+ break;
+ }
+
+ if (err) {
+ return err;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
+ action->write.group_id =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]) {
+ action->apply.copy_to_cpu =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]);
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_flow_add_mod(OfDpa *of_dpa, OfDpaFlow *flow,
+ RockerTlv **flow_tlvs)
+{
+ enum rocker_of_dpa_table_id tbl;
+ int err = ROCKER_OK;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_TABLE_ID] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_PRIORITY] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_HARDTIME]) {
+ return -ROCKER_EINVAL;
+ }
+
+ tbl = rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_TABLE_ID]);
+ flow->priority = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_PRIORITY]);
+ flow->hardtime = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_HARDTIME]);
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IDLETIME]) {
+ if (tbl == ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT ||
+ tbl == ROCKER_OF_DPA_TABLE_ID_VLAN ||
+ tbl == ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC) {
+ return -ROCKER_EINVAL;
+ }
+ flow->idletime =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IDLETIME]);
+ }
+
+ switch (tbl) {
+ case ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT:
+ err = of_dpa_cmd_add_ig_port(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_VLAN:
+ err = of_dpa_cmd_add_vlan(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC:
+ err = of_dpa_cmd_add_term_mac(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_BRIDGING:
+ err = of_dpa_cmd_add_bridging(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING:
+ err = of_dpa_cmd_add_unicast_routing(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING:
+ err = of_dpa_cmd_add_multicast_routing(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_ACL_POLICY:
+ err = of_dpa_cmd_add_acl(flow, flow_tlvs);
+ break;
+ }
+
+ return err;
+}
+
+static int of_dpa_cmd_flow_add(OfDpa *of_dpa, uint64_t cookie,
+ RockerTlv **flow_tlvs)
+{
+ OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
+ int err = ROCKER_OK;
+
+ if (flow) {
+ return -ROCKER_EEXIST;
+ }
+
+ flow = of_dpa_flow_alloc(cookie);
+ if (!flow) {
+ return -ROCKER_ENOMEM;
+ }
+
+ err = of_dpa_cmd_flow_add_mod(of_dpa, flow, flow_tlvs);
+ if (err) {
+ g_free(flow);
+ return err;
+ }
+
+ return of_dpa_flow_add(of_dpa, flow);
+}
+
+static int of_dpa_cmd_flow_mod(OfDpa *of_dpa, uint64_t cookie,
+ RockerTlv **flow_tlvs)
+{
+ OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
+
+ if (!flow) {
+ return -ROCKER_ENOENT;
+ }
+
+ return of_dpa_cmd_flow_add_mod(of_dpa, flow, flow_tlvs);
+}
+
+static int of_dpa_cmd_flow_del(OfDpa *of_dpa, uint64_t cookie)
+{
+ OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
+
+ if (!flow) {
+ return -ROCKER_ENOENT;
+ }
+
+ of_dpa_flow_del(of_dpa, flow);
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_flow_get_stats(OfDpa *of_dpa, uint64_t cookie,
+ struct desc_info *info, char *buf)
+{
+ OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
+ size_t tlv_size;
+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) / 1000;
+ int pos;
+
+ if (!flow) {
+ return -ROCKER_ENOENT;
+ }
+
+ tlv_size = rocker_tlv_total_size(sizeof(uint32_t)) + /* duration */
+ rocker_tlv_total_size(sizeof(uint64_t)) + /* rx_pkts */
+ rocker_tlv_total_size(sizeof(uint64_t)); /* tx_ptks */
+
+ if (tlv_size > desc_buf_size(info)) {
+ return -ROCKER_EMSGSIZE;
+ }
+
+ pos = 0;
+ rocker_tlv_put_le32(buf, &pos, ROCKER_TLV_OF_DPA_FLOW_STAT_DURATION,
+ (int32_t)(now - flow->stats.install_time));
+ rocker_tlv_put_le64(buf, &pos, ROCKER_TLV_OF_DPA_FLOW_STAT_RX_PKTS,
+ flow->stats.rx_pkts);
+ rocker_tlv_put_le64(buf, &pos, ROCKER_TLV_OF_DPA_FLOW_STAT_TX_PKTS,
+ flow->stats.tx_pkts);
+
+ return desc_set_buf(info, tlv_size);
+}
+
+static int of_dpa_flow_cmd(OfDpa *of_dpa, struct desc_info *info,
+ char *buf, uint16_t cmd,
+ RockerTlv **flow_tlvs)
+{
+ uint64_t cookie;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_COOKIE]) {
+ return -ROCKER_EINVAL;
+ }
+
+ cookie = rocker_tlv_get_le64(flow_tlvs[ROCKER_TLV_OF_DPA_COOKIE]);
+
+ switch (cmd) {
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_ADD:
+ return of_dpa_cmd_flow_add(of_dpa, cookie, flow_tlvs);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_MOD:
+ return of_dpa_cmd_flow_mod(of_dpa, cookie, flow_tlvs);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_DEL:
+ return of_dpa_cmd_flow_del(of_dpa, cookie);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_GET_STATS:
+ return of_dpa_cmd_flow_get_stats(of_dpa, cookie, info, buf);
+ }
+
+ return -ROCKER_ENOTSUP;
+}
+
+static int of_dpa_cmd_add_l2_interface(OfDpaGroup *group,
+ RockerTlv **group_tlvs)
+{
+ if (!group_tlvs[ROCKER_TLV_OF_DPA_OUT_PPORT] ||
+ !group_tlvs[ROCKER_TLV_OF_DPA_POP_VLAN]) {
+ return -ROCKER_EINVAL;
+ }
+
+ group->l2_interface.out_pport =
+ rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_OUT_PPORT]);
+ group->l2_interface.pop_vlan =
+ rocker_tlv_get_u8(group_tlvs[ROCKER_TLV_OF_DPA_POP_VLAN]);
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_l2_rewrite(OfDpa *of_dpa, OfDpaGroup *group,
+ RockerTlv **group_tlvs)
+{
+ OfDpaGroup *l2_interface_group;
+
+ if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]) {
+ return -ROCKER_EINVAL;
+ }
+
+ group->l2_rewrite.group_id =
+ rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]);
+
+ l2_interface_group = of_dpa_group_find(of_dpa, group->l2_rewrite.group_id);
+ if (!l2_interface_group ||
+ ROCKER_GROUP_TYPE_GET(l2_interface_group->id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE) {
+ DPRINTF("l2 rewrite group needs a valid l2 interface group\n");
+ return -ROCKER_EINVAL;
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]) {
+ memcpy(group->l2_rewrite.src_mac.a,
+ rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]),
+ sizeof(group->l2_rewrite.src_mac.a));
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
+ memcpy(group->l2_rewrite.dst_mac.a,
+ rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
+ sizeof(group->l2_rewrite.dst_mac.a));
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ group->l2_rewrite.vlan_id =
+ rocker_tlv_get_u16(group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+ if (ROCKER_GROUP_VLAN_GET(l2_interface_group->id) !=
+ (ntohs(group->l2_rewrite.vlan_id) & VLAN_VID_MASK)) {
+ DPRINTF("Set VLAN ID must be same as L2 interface group\n");
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_l2_flood(OfDpa *of_dpa, OfDpaGroup *group,
+ RockerTlv **group_tlvs)
+{
+ OfDpaGroup *l2_group;
+ RockerTlv **tlvs;
+ int err;
+ int i;
+
+ if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_COUNT] ||
+ !group_tlvs[ROCKER_TLV_OF_DPA_GROUP_IDS]) {
+ return -ROCKER_EINVAL;
+ }
+
+ group->l2_flood.group_count =
+ rocker_tlv_get_le16(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_COUNT]);
+
+ tlvs = g_malloc0((group->l2_flood.group_count + 1) *
+ sizeof(RockerTlv *));
+ if (!tlvs) {
+ return -ROCKER_ENOMEM;
+ }
+
+ g_free(group->l2_flood.group_ids);
+ group->l2_flood.group_ids =
+ g_malloc0(group->l2_flood.group_count * sizeof(uint32_t));
+ if (!group->l2_flood.group_ids) {
+ err = -ROCKER_ENOMEM;
+ goto err_out;
+ }
+
+ rocker_tlv_parse_nested(tlvs, group->l2_flood.group_count,
+ group_tlvs[ROCKER_TLV_OF_DPA_GROUP_IDS]);
+
+ for (i = 0; i < group->l2_flood.group_count; i++) {
+ group->l2_flood.group_ids[i] = rocker_tlv_get_le32(tlvs[i + 1]);
+ }
+
+ /* All of the L2 interface groups referenced by the L2 flood
+ * must have same VLAN
+ */
+
+ for (i = 0; i < group->l2_flood.group_count; i++) {
+ l2_group = of_dpa_group_find(of_dpa, group->l2_flood.group_ids[i]);
+ if (!l2_group) {
+ continue;
+ }
+ if ((ROCKER_GROUP_TYPE_GET(l2_group->id) ==
+ ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE) &&
+ (ROCKER_GROUP_VLAN_GET(l2_group->id) !=
+ ROCKER_GROUP_VLAN_GET(group->id))) {
+ DPRINTF("l2 interface group 0x%08x VLAN doesn't match l2 "
+ "flood group 0x%08x\n",
+ group->l2_flood.group_ids[i], group->id);
+ err = -ROCKER_EINVAL;
+ goto err_out;
+ }
+ }
+
+ g_free(tlvs);
+ return ROCKER_OK;
+
+err_out:
+ group->l2_flood.group_count = 0;
+ g_free(group->l2_flood.group_ids);
+ g_free(tlvs);
+
+ return err;
+}
+
+static int of_dpa_cmd_add_l3_unicast(OfDpaGroup *group, RockerTlv **group_tlvs)
+{
+ if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]) {
+ return -ROCKER_EINVAL;
+ }
+
+ group->l3_unicast.group_id =
+ rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]);
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]) {
+ memcpy(group->l3_unicast.src_mac.a,
+ rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]),
+ sizeof(group->l3_unicast.src_mac.a));
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
+ memcpy(group->l3_unicast.dst_mac.a,
+ rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
+ sizeof(group->l3_unicast.dst_mac.a));
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ group->l3_unicast.vlan_id =
+ rocker_tlv_get_u16(group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_TTL_CHECK]) {
+ group->l3_unicast.ttl_check =
+ rocker_tlv_get_u8(group_tlvs[ROCKER_TLV_OF_DPA_TTL_CHECK]);
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_group_do(OfDpa *of_dpa, uint32_t group_id,
+ OfDpaGroup *group, RockerTlv **group_tlvs)
+{
+ uint8_t type = ROCKER_GROUP_TYPE_GET(group_id);
+
+ switch (type) {
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
+ return of_dpa_cmd_add_l2_interface(group, group_tlvs);
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
+ return of_dpa_cmd_add_l2_rewrite(of_dpa, group, group_tlvs);
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD:
+ /* Treat L2 multicast group same as a L2 flood group */
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST:
+ return of_dpa_cmd_add_l2_flood(of_dpa, group, group_tlvs);
+ case ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST:
+ return of_dpa_cmd_add_l3_unicast(group, group_tlvs);
+ }
+
+ return -ROCKER_ENOTSUP;
+}
+
+static int of_dpa_cmd_group_add(OfDpa *of_dpa, uint32_t group_id,
+ RockerTlv **group_tlvs)
+{
+ OfDpaGroup *group = of_dpa_group_find(of_dpa, group_id);
+ int err;
+
+ if (group) {
+ return -ROCKER_EEXIST;
+ }
+
+ group = of_dpa_group_alloc(group_id);
+ if (!group) {
+ return -ROCKER_ENOMEM;
+ }
+
+ err = of_dpa_cmd_group_do(of_dpa, group_id, group, group_tlvs);
+ if (err) {
+ goto err_cmd_add;
+ }
+
+ err = of_dpa_group_add(of_dpa, group);
+ if (err) {
+ goto err_cmd_add;
+ }
+
+ return ROCKER_OK;
+
+err_cmd_add:
+ g_free(group);
+ return err;
+}
+
+static int of_dpa_cmd_group_mod(OfDpa *of_dpa, uint32_t group_id,
+ RockerTlv **group_tlvs)
+{
+ OfDpaGroup *group = of_dpa_group_find(of_dpa, group_id);
+
+ if (!group) {
+ return -ROCKER_ENOENT;
+ }
+
+ return of_dpa_cmd_group_do(of_dpa, group_id, group, group_tlvs);
+}
+
+static int of_dpa_cmd_group_del(OfDpa *of_dpa, uint32_t group_id)
+{
+ OfDpaGroup *group = of_dpa_group_find(of_dpa, group_id);
+
+ if (!group) {
+ return -ROCKER_ENOENT;
+ }
+
+ return of_dpa_group_del(of_dpa, group);
+}
+
+static int of_dpa_cmd_group_get_stats(OfDpa *of_dpa, uint32_t group_id,
+ struct desc_info *info, char *buf)
+{
+ return -ROCKER_ENOTSUP;
+}
+
+static int of_dpa_group_cmd(OfDpa *of_dpa, struct desc_info *info,
+ char *buf, uint16_t cmd, RockerTlv **group_tlvs)
+{
+ uint32_t group_id;
+
+ if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
+ return -ROCKER_EINVAL;
+ }
+
+ group_id = rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
+
+ switch (cmd) {
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_ADD:
+ return of_dpa_cmd_group_add(of_dpa, group_id, group_tlvs);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_MOD:
+ return of_dpa_cmd_group_mod(of_dpa, group_id, group_tlvs);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_DEL:
+ return of_dpa_cmd_group_del(of_dpa, group_id);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_GET_STATS:
+ return of_dpa_cmd_group_get_stats(of_dpa, group_id, info, buf);
+ }
+
+ return -ROCKER_ENOTSUP;
+}
+
+static int of_dpa_cmd(World *world, struct desc_info *info,
+ char *buf, uint16_t cmd, RockerTlv *cmd_info_tlv)
+{
+ OfDpa *of_dpa = world_private(world);
+ RockerTlv *tlvs[ROCKER_TLV_OF_DPA_MAX + 1];
+
+ rocker_tlv_parse_nested(tlvs, ROCKER_TLV_OF_DPA_MAX, cmd_info_tlv);
+
+ switch (cmd) {
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_ADD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_MOD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_DEL:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_GET_STATS:
+ return of_dpa_flow_cmd(of_dpa, info, buf, cmd, tlvs);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_ADD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_MOD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_DEL:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_GET_STATS:
+ return of_dpa_group_cmd(of_dpa, info, buf, cmd, tlvs);
+ }
+
+ return -ROCKER_ENOTSUP;
+}
+
+static gboolean rocker_int64_equal(gconstpointer v1, gconstpointer v2)
+{
+ return *((const uint64_t *)v1) == *((const uint64_t *)v2);
+}
+
+static guint rocker_int64_hash(gconstpointer v)
+{
+ return (guint)*(const uint64_t *)v;
+}
+
+static int of_dpa_init(World *world)
+{
+ OfDpa *of_dpa = world_private(world);
+
+ of_dpa->world = world;
+
+ of_dpa->flow_tbl = g_hash_table_new_full(rocker_int64_hash,
+ rocker_int64_equal,
+ NULL, g_free);
+ if (!of_dpa->flow_tbl) {
+ return -ENOMEM;
+ }
+
+ of_dpa->group_tbl = g_hash_table_new_full(g_int_hash, g_int_equal,
+ NULL, g_free);
+ if (!of_dpa->group_tbl) {
+ goto err_group_tbl;
+ }
+
+ /* XXX hardcode some artificial table max values */
+ of_dpa->flow_tbl_max_size = 100;
+ of_dpa->group_tbl_max_size = 100;
+
+ return 0;
+
+err_group_tbl:
+ g_hash_table_destroy(of_dpa->flow_tbl);
+ return -ENOMEM;
+}
+
+static void of_dpa_uninit(World *world)
+{
+ OfDpa *of_dpa = world_private(world);
+
+ g_hash_table_destroy(of_dpa->group_tbl);
+ g_hash_table_destroy(of_dpa->flow_tbl);
+}
+
+static WorldOps of_dpa_ops = {
+ .init = of_dpa_init,
+ .uninit = of_dpa_uninit,
+ .ig = of_dpa_ig,
+ .cmd = of_dpa_cmd,
+};
+
+World *of_dpa_world_alloc(Rocker *r)
+{
+ return world_alloc(r, sizeof(OfDpa), ROCKER_WORLD_TYPE_OF_DPA, &of_dpa_ops);
+}