Prototype programming of Open vSwitch (-like) flows into hardware
by implementing SWITCHDEV_OBJ_OVS_FLOW type objects in the
rocker_port_obj_{add,del} SDO, a new object type that
was added by an earlier patch in that forms part of this prototype.A very limited subset of flows are accepted by this implementation, however, it is sufficient for a working ping test: the ICMP packets are programmed into hardware although the ARP packets are not. Follow-up patches in this prototype will modify the Open vSwitch kernel datapath to call the new SDOs and thus make use of the changes in this patch. Signed-off-by: Simon Horman <[email protected]> --- drivers/net/ethernet/rocker/rocker_ofdpa.c | 261 +++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) diff --git a/drivers/net/ethernet/rocker/rocker_ofdpa.c b/drivers/net/ethernet/rocker/rocker_ofdpa.c index fcad907baecf..6669f2ba2f97 100644 --- a/drivers/net/ethernet/rocker/rocker_ofdpa.c +++ b/drivers/net/ethernet/rocker/rocker_ofdpa.c @@ -19,6 +19,7 @@ #include <linux/inetdevice.h> #include <linux/if_vlan.h> #include <linux/if_bridge.h> +#include <linux/sw_flow.h> #include <net/neighbour.h> #include <net/switchdev.h> #include <net/ip_fib.h> @@ -2795,6 +2796,264 @@ static int ofdpa_port_obj_fdb_dump(const struct rocker_port *rocker_port, return err; } +#if IS_ENABLED(CONFIG_OPENVSWITCH) + +static int +ofdpa_port_sw_flow_actions(struct ofdpa_port *ofdpa_port, + const struct switchdev_obj_sw_flow *flow, + struct ofdpa_flow_tbl_key *out_key) +{ + int rem, err, out_ifindex = -1, len = flow->actions.len; + const struct nlattr *a, *attr = flow->actions.actions; + struct net_device *out_dev, *dev = ofdpa_port->dev; + struct rocker_port *out_rocker_port; + struct ofdpa_port *out_ofdpa_port; + __be16 vlan_id; + u32 out_pport; + + for (a = attr, rem = len; rem > 0; a = nla_next(a, &rem)) { + int type = nla_type(a); + + switch (type) { + case OVS_ACTION_ATTR_OUTPUT: + /* Only unicast is supported at this time */ + if (out_ifindex >= 0) + return -ENOTSUPP; + out_ifindex = nla_get_u32(a); + break; + + case OVS_ACTION_ATTR_POP_VLAN: + case OVS_ACTION_ATTR_PUSH_VLAN: + case OVS_ACTION_ATTR_SET: + case OVS_ACTION_ATTR_USERSPACE: + case OVS_ACTION_ATTR_HASH: + case OVS_ACTION_ATTR_RECIRC: + case OVS_ACTION_ATTR_PUSH_MPLS: + case OVS_ACTION_ATTR_POP_MPLS: + case OVS_ACTION_ATTR_SET_MASKED: + case OVS_ACTION_ATTR_SAMPLE: + return -ENOTSUPP; + + case OVS_ACTION_ATTR_UNSPEC: + default: + return -EINVAL; + } + } + + /* No output */ + if (out_ifindex == -1) + return -ENOTSUPP; + + out_dev = dev_get_by_index(dev_net(dev), out_ifindex); + if (!out_dev) + return -EINVAL; + + /* It is invalid to output to the input port */ + if (dev == out_dev) { + err = -EINVAL; + goto err; + } + + /* Only support flows whose input and output port are on + * the same rocker switch. + */ + if (!rocker_port_dev_cmp_rocker(dev, out_dev)) { + err = -ENOTSUPP; + goto err; + } + + out_rocker_port = netdev_priv(out_dev); + out_ofdpa_port = out_rocker_port->wpriv; + out_pport = out_ofdpa_port->pport; + vlan_id = out_ofdpa_port->internal_vlan_id; + out_key->acl.group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, out_pport); + + err = 0; +err: + dev_put(out_dev); + return err; +} + +static int ofdpa_port_sw_flow_match(struct ofdpa_port *ofdpa_port, + const struct switchdev_obj_sw_flow *flow, + struct ofdpa_flow_tbl_key *out_key) +{ + const struct sw_flow_key *mask = flow->mask; + const struct sw_flow_key *key = flow->key; + const u8 *eth_dst = NULL, *eth_dst_mask = NULL; + u64 key_allowed, key_required; + + key_required = + BIT_ULL(OVS_KEY_ATTR_IN_PORT) | + BIT_ULL(OVS_KEY_ATTR_ETHERNET) | + BIT_ULL(OVS_KEY_ATTR_ETHERTYPE); + + /* TODO: Support more key fields as per those + * permitted in the OF-DPA ACL Flow Table. + */ + key_allowed = key_required | + BIT_ULL(OVS_KEY_ATTR_PRIORITY) | /* Only zero */ + BIT_ULL(OVS_KEY_ATTR_IPV4) | + BIT_ULL(OVS_KEY_ATTR_ICMP) | + BIT_ULL(OVS_KEY_ATTR_SKB_MARK) | /* Only zero */ + BIT_ULL(OVS_KEY_ATTR_TUNNEL) | /* Only zero */ + BIT_ULL(OVS_KEY_ATTR_DP_HASH) | /* Only zero */ + BIT_ULL(OVS_KEY_ATTR_RECIRC_ID); /* Only zero */ + + if ((flow->attrs & key_required) != key_required || + (flow->attrs & key_allowed) != flow->attrs) + return -ENOTSUPP; + + /* Only support zero/no skb priority */ + if (flow->attrs | BIT_ULL(OVS_KEY_ATTR_PRIORITY) && + key->phy.priority) + return -ENOTSUPP; + + /* Only support zero/no tunnel id */ + if (flow->attrs | BIT_ULL(OVS_KEY_ATTR_TUNNEL) && + key->tun_key.tun_id != cpu_to_be64(0)) + return -ENOTSUPP; + + /* Only support zero/no skb mark */ + if (flow->attrs | BIT_ULL(OVS_KEY_ATTR_SKB_MARK) && key->phy.skb_mark) + return -ENOTSUPP; + + /* Only support zero/no dp hash */ + if (flow->attrs | BIT_ULL(OVS_KEY_ATTR_DP_HASH) && key->ovs_flow_hash) + return -ENOTSUPP; + + /* Only support zero/no recirculation id */ + if (flow->attrs | BIT_ULL(OVS_KEY_ATTR_RECIRC_ID) && key->recirc_id) + return -ENOTSUPP; + + /* The OF-DPA ACL table requires an unmasked match on ethernet type */ + if (mask->eth.type != cpu_to_be16(0xffff)) + return -ENOTSUPP; + + if (flow->attrs | BIT_ULL(OVS_KEY_ATTR_IPV4)) { + if (mask->ip.frag) + /* There is no IP frag match in OF-DPA */ + return -ENOTSUPP; + if (mask->ipv4.addr.src != cpu_to_be32(0) || + mask->ipv4.addr.dst != cpu_to_be32(0)) + /* Rocker doesn't implement these matches */ + return -ENOTSUPP; + out_key->acl.ip_proto = key->ip.proto; + out_key->acl.ip_proto_mask = mask->ip.proto; + out_key->acl.ip_tos = key->ip.tos; + out_key->acl.ip_tos_mask = mask->ip.tos; + } + + if (flow->attrs | BIT_ULL(OVS_KEY_ATTR_ICMP) && + (mask->tp.src != cpu_to_be16(0) || mask->tp.dst != cpu_to_be16(0))) + /* Rocker doesn't implement these matches */ + return -ENOTSUPP; + + out_key->acl.in_pport = ofdpa_port->pport; + out_key->acl.in_pport_mask = mask->phy.in_port; + ether_addr_copy(out_key->acl.eth_src, key->eth.src); + ether_addr_copy(out_key->acl.eth_src_mask, mask->eth.src); + ether_addr_copy(out_key->acl.eth_dst, key->eth.dst); + ether_addr_copy(out_key->acl.eth_dst_mask, mask->eth.dst); + out_key->acl.eth_type = key->eth.type; + + if (!ether_addr_equal(out_key->acl.eth_dst, zero_mac)) { + eth_dst = key->eth.dst; + eth_dst_mask = mask->eth.dst; + } + + out_key->acl.vlan_id = ofdpa_port->internal_vlan_id; + out_key->acl.vlan_id_mask = htons(0xffff); + out_key->priority = OFDPA_PRIORITY_ACL_CTRL; + + return 0; +} + +static struct ofdpa_flow_tbl_entry * +ofdpa_port_sw_flow_entry(struct ofdpa_port *ofdpa_port, + struct switchdev_trans *trans, + const struct switchdev_obj_sw_flow *flow) +{ + struct ofdpa_flow_tbl_entry *entry; + int flags = 0; + int err; + + entry = ofdpa_kzalloc(trans, flags, sizeof(*entry)); + if (!entry) + return ERR_PTR(-ENOMEM); + + entry->key.tbl_id = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY; + entry->key_len = offsetof(struct ofdpa_flow_tbl_key, acl.group_id); + + err = ofdpa_port_sw_flow_match(ofdpa_port, flow, &entry->key); + if (err) + goto err; + + return entry; + +err: + ofdpa_kfree(trans, entry); + return ERR_PTR(err); +} + +static int ofdpa_port_obj_sw_flow_add(struct rocker_port *rocker_port, + const struct switchdev_obj_sw_flow *flow, + struct switchdev_trans *trans) +{ + struct ofdpa_port *ofdpa_port = rocker_port->wpriv; + struct ofdpa_flow_tbl_entry *entry; + int err, flags = 0; + + if (!ofdpa_port_is_ovsed(ofdpa_port)) + return -EINVAL; + + entry = ofdpa_port_sw_flow_entry(ofdpa_port, trans, flow); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + err = ofdpa_port_sw_flow_actions(ofdpa_port, flow, &entry->key); + if (err) { + ofdpa_kfree(trans, entry); + return err; + } + + return ofdpa_flow_tbl_add(ofdpa_port, trans, flags, entry); +} + +static int ofdpa_port_obj_sw_flow_del(struct rocker_port *rocker_port, + const struct switchdev_obj_sw_flow *flow) +{ + struct ofdpa_port *ofdpa_port = rocker_port->wpriv; + struct switchdev_trans *trans = NULL; + struct ofdpa_flow_tbl_entry *entry; + int flags = 0; + + if (!ofdpa_port_is_ovsed(ofdpa_port)) + return -EINVAL; + + entry = ofdpa_port_sw_flow_entry(ofdpa_port, trans, flow); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + return ofdpa_flow_tbl_del(ofdpa_port, trans, flags, entry); +} + +#else + +static int ofdpa_port_obj_sw_flow_add(struct rocker_port *rocker_port, + const struct switchdev_obj_sw_flow *flow, + struct switchdev_trans *trans) { + return -ENOTSUPP; +} + +static int ofdpa_port_obj_sw_flow_del(struct rocker_port *rocker_port, + const struct switchdev_obj_sw_flow *flow) +{ + return -ENOTSUPP; +} + +#endif + static int ofdpa_port_bridge_join(struct ofdpa_port *ofdpa_port, struct net_device *bridge) { @@ -2946,6 +3205,8 @@ struct rocker_world_ops rocker_ofdpa_ops = { .port_obj_fdb_add = ofdpa_port_obj_fdb_add, .port_obj_fdb_del = ofdpa_port_obj_fdb_del, .port_obj_fdb_dump = ofdpa_port_obj_fdb_dump, + .port_obj_sw_flow_add = ofdpa_port_obj_sw_flow_add, + .port_obj_sw_flow_del = ofdpa_port_obj_sw_flow_del, .port_master_linked = ofdpa_port_master_linked, .port_master_unlinked = ofdpa_port_master_unlinked, .port_neigh_update = ofdpa_port_neigh_update, -- 2.7.0.rc3.207.g0ac5344 _______________________________________________ dev mailing list [email protected] http://openvswitch.org/mailman/listinfo/dev
