]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - drivers/infiniband/hw/mlx5/main.c
Merge tag 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dledford/rdma
[mirror_ubuntu-bionic-kernel.git] / drivers / infiniband / hw / mlx5 / main.c
index a55bf05c852255147aca32036e7e3e0252c27d6f..ec737e2287fe150ff8d5d83e6ca7042a2cca8b62 100644 (file)
@@ -45,6 +45,9 @@
 #include <linux/mlx5/vport.h>
 #include <rdma/ib_smi.h>
 #include <rdma/ib_umem.h>
+#include <linux/in.h>
+#include <linux/etherdevice.h>
+#include <linux/mlx5/fs.h>
 #include "user.h"
 #include "mlx5_ib.h"
 
@@ -1153,6 +1156,457 @@ static int mlx5_ib_dealloc_pd(struct ib_pd *pd)
        return 0;
 }
 
+static bool outer_header_zero(u32 *match_criteria)
+{
+       int size = MLX5_ST_SZ_BYTES(fte_match_param);
+       char *outer_headers_c = MLX5_ADDR_OF(fte_match_param, match_criteria,
+                                            outer_headers);
+
+       return outer_headers_c[0] == 0 && !memcmp(outer_headers_c,
+                                                 outer_headers_c + 1,
+                                                 size - 1);
+}
+
+static int parse_flow_attr(u32 *match_c, u32 *match_v,
+                          union ib_flow_spec *ib_spec)
+{
+       void *outer_headers_c = MLX5_ADDR_OF(fte_match_param, match_c,
+                                            outer_headers);
+       void *outer_headers_v = MLX5_ADDR_OF(fte_match_param, match_v,
+                                            outer_headers);
+       switch (ib_spec->type) {
+       case IB_FLOW_SPEC_ETH:
+               if (ib_spec->size != sizeof(ib_spec->eth))
+                       return -EINVAL;
+
+               ether_addr_copy(MLX5_ADDR_OF(fte_match_set_lyr_2_4, outer_headers_c,
+                                            dmac_47_16),
+                               ib_spec->eth.mask.dst_mac);
+               ether_addr_copy(MLX5_ADDR_OF(fte_match_set_lyr_2_4, outer_headers_v,
+                                            dmac_47_16),
+                               ib_spec->eth.val.dst_mac);
+
+               if (ib_spec->eth.mask.vlan_tag) {
+                       MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c,
+                                vlan_tag, 1);
+                       MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v,
+                                vlan_tag, 1);
+
+                       MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c,
+                                first_vid, ntohs(ib_spec->eth.mask.vlan_tag));
+                       MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v,
+                                first_vid, ntohs(ib_spec->eth.val.vlan_tag));
+
+                       MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c,
+                                first_cfi,
+                                ntohs(ib_spec->eth.mask.vlan_tag) >> 12);
+                       MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v,
+                                first_cfi,
+                                ntohs(ib_spec->eth.val.vlan_tag) >> 12);
+
+                       MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c,
+                                first_prio,
+                                ntohs(ib_spec->eth.mask.vlan_tag) >> 13);
+                       MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v,
+                                first_prio,
+                                ntohs(ib_spec->eth.val.vlan_tag) >> 13);
+               }
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c,
+                        ethertype, ntohs(ib_spec->eth.mask.ether_type));
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v,
+                        ethertype, ntohs(ib_spec->eth.val.ether_type));
+               break;
+       case IB_FLOW_SPEC_IPV4:
+               if (ib_spec->size != sizeof(ib_spec->ipv4))
+                       return -EINVAL;
+
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c,
+                        ethertype, 0xffff);
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v,
+                        ethertype, ETH_P_IP);
+
+               memcpy(MLX5_ADDR_OF(fte_match_set_lyr_2_4, outer_headers_c,
+                                   src_ipv4_src_ipv6.ipv4_layout.ipv4),
+                      &ib_spec->ipv4.mask.src_ip,
+                      sizeof(ib_spec->ipv4.mask.src_ip));
+               memcpy(MLX5_ADDR_OF(fte_match_set_lyr_2_4, outer_headers_v,
+                                   src_ipv4_src_ipv6.ipv4_layout.ipv4),
+                      &ib_spec->ipv4.val.src_ip,
+                      sizeof(ib_spec->ipv4.val.src_ip));
+               memcpy(MLX5_ADDR_OF(fte_match_set_lyr_2_4, outer_headers_c,
+                                   dst_ipv4_dst_ipv6.ipv4_layout.ipv4),
+                      &ib_spec->ipv4.mask.dst_ip,
+                      sizeof(ib_spec->ipv4.mask.dst_ip));
+               memcpy(MLX5_ADDR_OF(fte_match_set_lyr_2_4, outer_headers_v,
+                                   dst_ipv4_dst_ipv6.ipv4_layout.ipv4),
+                      &ib_spec->ipv4.val.dst_ip,
+                      sizeof(ib_spec->ipv4.val.dst_ip));
+               break;
+       case IB_FLOW_SPEC_TCP:
+               if (ib_spec->size != sizeof(ib_spec->tcp_udp))
+                       return -EINVAL;
+
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c, ip_protocol,
+                        0xff);
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v, ip_protocol,
+                        IPPROTO_TCP);
+
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c, tcp_sport,
+                        ntohs(ib_spec->tcp_udp.mask.src_port));
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v, tcp_sport,
+                        ntohs(ib_spec->tcp_udp.val.src_port));
+
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c, tcp_dport,
+                        ntohs(ib_spec->tcp_udp.mask.dst_port));
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v, tcp_dport,
+                        ntohs(ib_spec->tcp_udp.val.dst_port));
+               break;
+       case IB_FLOW_SPEC_UDP:
+               if (ib_spec->size != sizeof(ib_spec->tcp_udp))
+                       return -EINVAL;
+
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c, ip_protocol,
+                        0xff);
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v, ip_protocol,
+                        IPPROTO_UDP);
+
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c, udp_sport,
+                        ntohs(ib_spec->tcp_udp.mask.src_port));
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v, udp_sport,
+                        ntohs(ib_spec->tcp_udp.val.src_port));
+
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_c, udp_dport,
+                        ntohs(ib_spec->tcp_udp.mask.dst_port));
+               MLX5_SET(fte_match_set_lyr_2_4, outer_headers_v, udp_dport,
+                        ntohs(ib_spec->tcp_udp.val.dst_port));
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* If a flow could catch both multicast and unicast packets,
+ * it won't fall into the multicast flow steering table and this rule
+ * could steal other multicast packets.
+ */
+static bool flow_is_multicast_only(struct ib_flow_attr *ib_attr)
+{
+       struct ib_flow_spec_eth *eth_spec;
+
+       if (ib_attr->type != IB_FLOW_ATTR_NORMAL ||
+           ib_attr->size < sizeof(struct ib_flow_attr) +
+           sizeof(struct ib_flow_spec_eth) ||
+           ib_attr->num_of_specs < 1)
+               return false;
+
+       eth_spec = (struct ib_flow_spec_eth *)(ib_attr + 1);
+       if (eth_spec->type != IB_FLOW_SPEC_ETH ||
+           eth_spec->size != sizeof(*eth_spec))
+               return false;
+
+       return is_multicast_ether_addr(eth_spec->mask.dst_mac) &&
+              is_multicast_ether_addr(eth_spec->val.dst_mac);
+}
+
+static bool is_valid_attr(struct ib_flow_attr *flow_attr)
+{
+       union ib_flow_spec *ib_spec = (union ib_flow_spec *)(flow_attr + 1);
+       bool has_ipv4_spec = false;
+       bool eth_type_ipv4 = true;
+       unsigned int spec_index;
+
+       /* Validate that ethertype is correct */
+       for (spec_index = 0; spec_index < flow_attr->num_of_specs; spec_index++) {
+               if (ib_spec->type == IB_FLOW_SPEC_ETH &&
+                   ib_spec->eth.mask.ether_type) {
+                       if (!((ib_spec->eth.mask.ether_type == htons(0xffff)) &&
+                             ib_spec->eth.val.ether_type == htons(ETH_P_IP)))
+                               eth_type_ipv4 = false;
+               } else if (ib_spec->type == IB_FLOW_SPEC_IPV4) {
+                       has_ipv4_spec = true;
+               }
+               ib_spec = (void *)ib_spec + ib_spec->size;
+       }
+       return !has_ipv4_spec || eth_type_ipv4;
+}
+
+static void put_flow_table(struct mlx5_ib_dev *dev,
+                          struct mlx5_ib_flow_prio *prio, bool ft_added)
+{
+       prio->refcount -= !!ft_added;
+       if (!prio->refcount) {
+               mlx5_destroy_flow_table(prio->flow_table);
+               prio->flow_table = NULL;
+       }
+}
+
+static int mlx5_ib_destroy_flow(struct ib_flow *flow_id)
+{
+       struct mlx5_ib_dev *dev = to_mdev(flow_id->qp->device);
+       struct mlx5_ib_flow_handler *handler = container_of(flow_id,
+                                                         struct mlx5_ib_flow_handler,
+                                                         ibflow);
+       struct mlx5_ib_flow_handler *iter, *tmp;
+
+       mutex_lock(&dev->flow_db.lock);
+
+       list_for_each_entry_safe(iter, tmp, &handler->list, list) {
+               mlx5_del_flow_rule(iter->rule);
+               list_del(&iter->list);
+               kfree(iter);
+       }
+
+       mlx5_del_flow_rule(handler->rule);
+       put_flow_table(dev, &dev->flow_db.prios[handler->prio], true);
+       mutex_unlock(&dev->flow_db.lock);
+
+       kfree(handler);
+
+       return 0;
+}
+
+#define MLX5_FS_MAX_TYPES       10
+#define MLX5_FS_MAX_ENTRIES     32000UL
+static struct mlx5_ib_flow_prio *get_flow_table(struct mlx5_ib_dev *dev,
+                                               struct ib_flow_attr *flow_attr)
+{
+       struct mlx5_flow_namespace *ns = NULL;
+       struct mlx5_ib_flow_prio *prio;
+       struct mlx5_flow_table *ft;
+       int num_entries;
+       int num_groups;
+       int priority;
+       int err = 0;
+
+       if (flow_attr->type == IB_FLOW_ATTR_NORMAL) {
+               if (flow_is_multicast_only(flow_attr))
+                       priority = MLX5_IB_FLOW_MCAST_PRIO;
+               else
+                       priority = flow_attr->priority;
+               ns = mlx5_get_flow_namespace(dev->mdev,
+                                            MLX5_FLOW_NAMESPACE_BYPASS);
+               num_entries = MLX5_FS_MAX_ENTRIES;
+               num_groups = MLX5_FS_MAX_TYPES;
+               prio = &dev->flow_db.prios[priority];
+       } else if (flow_attr->type == IB_FLOW_ATTR_ALL_DEFAULT ||
+                  flow_attr->type == IB_FLOW_ATTR_MC_DEFAULT) {
+               ns = mlx5_get_flow_namespace(dev->mdev,
+                                            MLX5_FLOW_NAMESPACE_LEFTOVERS);
+               build_leftovers_ft_param(&priority,
+                                        &num_entries,
+                                        &num_groups);
+               prio = &dev->flow_db.prios[MLX5_IB_FLOW_LEFTOVERS_PRIO];
+       }
+
+       if (!ns)
+               return ERR_PTR(-ENOTSUPP);
+
+       ft = prio->flow_table;
+       if (!ft) {
+               ft = mlx5_create_auto_grouped_flow_table(ns, priority,
+                                                        num_entries,
+                                                        num_groups);
+
+               if (!IS_ERR(ft)) {
+                       prio->refcount = 0;
+                       prio->flow_table = ft;
+               } else {
+                       err = PTR_ERR(ft);
+               }
+       }
+
+       return err ? ERR_PTR(err) : prio;
+}
+
+static struct mlx5_ib_flow_handler *create_flow_rule(struct mlx5_ib_dev *dev,
+                                                    struct mlx5_ib_flow_prio *ft_prio,
+                                                    struct ib_flow_attr *flow_attr,
+                                                    struct mlx5_flow_destination *dst)
+{
+       struct mlx5_flow_table  *ft = ft_prio->flow_table;
+       struct mlx5_ib_flow_handler *handler;
+       void *ib_flow = flow_attr + 1;
+       u8 match_criteria_enable = 0;
+       unsigned int spec_index;
+       u32 *match_c;
+       u32 *match_v;
+       int err = 0;
+
+       if (!is_valid_attr(flow_attr))
+               return ERR_PTR(-EINVAL);
+
+       match_c = kzalloc(MLX5_ST_SZ_BYTES(fte_match_param), GFP_KERNEL);
+       match_v = kzalloc(MLX5_ST_SZ_BYTES(fte_match_param), GFP_KERNEL);
+       handler = kzalloc(sizeof(*handler), GFP_KERNEL);
+       if (!handler || !match_c || !match_v) {
+               err = -ENOMEM;
+               goto free;
+       }
+
+       INIT_LIST_HEAD(&handler->list);
+
+       for (spec_index = 0; spec_index < flow_attr->num_of_specs; spec_index++) {
+               err = parse_flow_attr(match_c, match_v, ib_flow);
+               if (err < 0)
+                       goto free;
+
+               ib_flow += ((union ib_flow_spec *)ib_flow)->size;
+       }
+
+       /* Outer header support only */
+       match_criteria_enable = (!outer_header_zero(match_c)) << 0;
+       handler->rule = mlx5_add_flow_rule(ft, match_criteria_enable,
+                                          match_c, match_v,
+                                          MLX5_FLOW_CONTEXT_ACTION_FWD_DEST,
+                                          MLX5_FS_DEFAULT_FLOW_TAG,
+                                          dst);
+
+       if (IS_ERR(handler->rule)) {
+               err = PTR_ERR(handler->rule);
+               goto free;
+       }
+
+       handler->prio = ft_prio - dev->flow_db.prios;
+
+       ft_prio->flow_table = ft;
+free:
+       if (err)
+               kfree(handler);
+       kfree(match_c);
+       kfree(match_v);
+       return err ? ERR_PTR(err) : handler;
+}
+
+enum {
+       LEFTOVERS_MC,
+       LEFTOVERS_UC,
+};
+
+static struct mlx5_ib_flow_handler *create_leftovers_rule(struct mlx5_ib_dev *dev,
+                                                         struct mlx5_ib_flow_prio *ft_prio,
+                                                         struct ib_flow_attr *flow_attr,
+                                                         struct mlx5_flow_destination *dst)
+{
+       struct mlx5_ib_flow_handler *handler_ucast = NULL;
+       struct mlx5_ib_flow_handler *handler = NULL;
+
+       static struct {
+               struct ib_flow_attr     flow_attr;
+               struct ib_flow_spec_eth eth_flow;
+       } leftovers_specs[] = {
+               [LEFTOVERS_MC] = {
+                       .flow_attr = {
+                               .num_of_specs = 1,
+                               .size = sizeof(leftovers_specs[0])
+                       },
+                       .eth_flow = {
+                               .type = IB_FLOW_SPEC_ETH,
+                               .size = sizeof(struct ib_flow_spec_eth),
+                               .mask = {.dst_mac = {0x1} },
+                               .val =  {.dst_mac = {0x1} }
+                       }
+               },
+               [LEFTOVERS_UC] = {
+                       .flow_attr = {
+                               .num_of_specs = 1,
+                               .size = sizeof(leftovers_specs[0])
+                       },
+                       .eth_flow = {
+                               .type = IB_FLOW_SPEC_ETH,
+                               .size = sizeof(struct ib_flow_spec_eth),
+                               .mask = {.dst_mac = {0x1} },
+                               .val = {.dst_mac = {} }
+                       }
+               }
+       };
+
+       handler = create_flow_rule(dev, ft_prio,
+                                  &leftovers_specs[LEFTOVERS_MC].flow_attr,
+                                  dst);
+       if (!IS_ERR(handler) &&
+           flow_attr->type == IB_FLOW_ATTR_ALL_DEFAULT) {
+               handler_ucast = create_flow_rule(dev, ft_prio,
+                                                &leftovers_specs[LEFTOVERS_UC].flow_attr,
+                                                dst);
+               if (IS_ERR(handler_ucast)) {
+                       kfree(handler);
+                       handler = handler_ucast;
+               } else {
+                       list_add(&handler_ucast->list, &handler->list);
+               }
+       }
+
+       return handler;
+}
+
+static struct ib_flow *mlx5_ib_create_flow(struct ib_qp *qp,
+                                          struct ib_flow_attr *flow_attr,
+                                          int domain)
+{
+       struct mlx5_ib_dev *dev = to_mdev(qp->device);
+       struct mlx5_ib_flow_handler *handler = NULL;
+       struct mlx5_flow_destination *dst = NULL;
+       struct mlx5_ib_flow_prio *ft_prio;
+       int err;
+
+       if (flow_attr->priority > MLX5_IB_FLOW_LAST_PRIO)
+               return ERR_PTR(-ENOSPC);
+
+       if (domain != IB_FLOW_DOMAIN_USER ||
+           flow_attr->port > MLX5_CAP_GEN(dev->mdev, num_ports) ||
+           flow_attr->flags)
+               return ERR_PTR(-EINVAL);
+
+       dst = kzalloc(sizeof(*dst), GFP_KERNEL);
+       if (!dst)
+               return ERR_PTR(-ENOMEM);
+
+       mutex_lock(&dev->flow_db.lock);
+
+       ft_prio = get_flow_table(dev, flow_attr);
+       if (IS_ERR(ft_prio)) {
+               err = PTR_ERR(ft_prio);
+               goto unlock;
+       }
+
+       dst->type = MLX5_FLOW_DESTINATION_TYPE_TIR;
+       dst->tir_num = to_mqp(qp)->raw_packet_qp.rq.tirn;
+
+       if (flow_attr->type == IB_FLOW_ATTR_NORMAL) {
+               handler = create_flow_rule(dev, ft_prio, flow_attr,
+                                          dst);
+       } else if (flow_attr->type == IB_FLOW_ATTR_ALL_DEFAULT ||
+                  flow_attr->type == IB_FLOW_ATTR_MC_DEFAULT) {
+               handler = create_leftovers_rule(dev, ft_prio, flow_attr,
+                                               dst);
+       } else {
+               err = -EINVAL;
+               goto destroy_ft;
+       }
+
+       if (IS_ERR(handler)) {
+               err = PTR_ERR(handler);
+               handler = NULL;
+               goto destroy_ft;
+       }
+
+       ft_prio->refcount++;
+       mutex_unlock(&dev->flow_db.lock);
+       kfree(dst);
+
+       return &handler->ibflow;
+
+destroy_ft:
+       put_flow_table(dev, ft_prio, false);
+unlock:
+       mutex_unlock(&dev->flow_db.lock);
+       kfree(dst);
+       kfree(handler);
+       return ERR_PTR(err);
+}
+
 static int mlx5_ib_mcg_attach(struct ib_qp *ibqp, union ib_gid *gid, u16 lid)
 {
        struct mlx5_ib_dev *dev = to_mdev(ibqp->device);
@@ -1819,10 +2273,19 @@ static void *mlx5_ib_add(struct mlx5_core_dev *mdev)
                        (1ull << IB_USER_VERBS_CMD_CLOSE_XRCD);
        }
 
+       if (mlx5_ib_port_link_layer(&dev->ib_dev, 1) ==
+           IB_LINK_LAYER_ETHERNET) {
+               dev->ib_dev.create_flow = mlx5_ib_create_flow;
+               dev->ib_dev.destroy_flow = mlx5_ib_destroy_flow;
+               dev->ib_dev.uverbs_ex_cmd_mask |=
+                       (1ull << IB_USER_VERBS_EX_CMD_CREATE_FLOW) |
+                       (1ull << IB_USER_VERBS_EX_CMD_DESTROY_FLOW);
+       }
        err = init_node_data(dev);
        if (err)
                goto err_dealloc;
 
+       mutex_init(&dev->flow_db.lock);
        mutex_init(&dev->cap_mask_mutex);
 
        if (ll == IB_LINK_LAYER_ETHERNET) {