#include "opt_inet.h"
#include "opt_inet6.h"
#include <dev/mlx5/mlx5_en/en.h>
#include <dev/mlx5/mlx5_core/fs_core.h>
#include <dev/mlx5/mlx5_core/fs_tcp.h>
#include <dev/mlx5/device.h>
#include <sys/domain.h>
#include <netinet/in_pcb.h>
#if defined(INET) || defined(INET6)
static void
accel_fs_tcp_set_ipv4_flow(struct mlx5_flow_spec *spec, struct inpcb *inp)
{
MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, outer_headers.ip_protocol);
MLX5_SET(fte_match_param, spec->match_value, outer_headers.ip_protocol, IPPROTO_TCP);
MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, outer_headers.ip_version);
MLX5_SET(fte_match_param, spec->match_value, outer_headers.ip_version, 4);
memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value,
outer_headers.src_ipv4_src_ipv6.ipv4_layout.ipv4),
&inp->inp_faddr, 4);
memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value,
outer_headers.dst_ipv4_dst_ipv6.ipv4_layout.ipv4),
&inp->inp_laddr, 4);
MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria,
outer_headers.src_ipv4_src_ipv6.ipv4_layout.ipv4);
MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria,
outer_headers.dst_ipv4_dst_ipv6.ipv4_layout.ipv4);
}
#endif
#ifdef INET6
static void
accel_fs_tcp_set_ipv6_flow(struct mlx5_flow_spec *spec, struct inpcb *inp)
{
MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, outer_headers.ip_protocol);
MLX5_SET(fte_match_param, spec->match_value, outer_headers.ip_protocol, IPPROTO_TCP);
MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, outer_headers.ip_version);
MLX5_SET(fte_match_param, spec->match_value, outer_headers.ip_version, 6);
memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value,
outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6),
&inp->in6p_faddr, 16);
memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value,
outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6),
&inp->in6p_laddr, 16);
memset(MLX5_ADDR_OF(fte_match_param, spec->match_criteria,
outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6),
0xff, 16);
memset(MLX5_ADDR_OF(fte_match_param, spec->match_criteria,
outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6),
0xff, 16);
}
#endif
void
mlx5e_accel_fs_del_inpcb(struct mlx5_flow_handle *rule)
{
mlx5_del_flow_rules(&rule);
}
struct mlx5_flow_handle *
mlx5e_accel_fs_add_inpcb(struct mlx5e_priv *priv,
struct inpcb *inp, uint32_t tirn, uint32_t flow_tag,
uint16_t vlan_id)
{
struct mlx5_flow_destination dest = {};
struct mlx5e_flow_table *ft = NULL;
#if defined(INET) || defined(INET6)
struct mlx5e_accel_fs_tcp *fs_tcp = &priv->fts.accel_tcp;
#endif
struct mlx5_flow_handle *flow;
struct mlx5_flow_spec *spec;
struct mlx5_flow_act flow_act = {};
spec = kvzalloc(sizeof(*spec), GFP_KERNEL);
if (!spec)
return (ERR_PTR(-ENOMEM));
spec->match_criteria_enable = MLX5_MATCH_OUTER_HEADERS;
spec->flow_context.flags = FLOW_CONTEXT_HAS_TAG;
spec->flow_context.flow_tag = flow_tag;
INP_RLOCK(inp);
MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, outer_headers.cvlan_tag);
MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, outer_headers.first_vid);
if (vlan_id != MLX5E_ACCEL_FS_ADD_INPCB_NO_VLAN) {
MLX5_SET_TO_ONES(fte_match_param, spec->match_value, outer_headers.cvlan_tag);
MLX5_SET(fte_match_param, spec->match_value, outer_headers.first_vid, vlan_id);
}
MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria,
outer_headers.tcp_dport);
MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria,
outer_headers.tcp_sport);
MLX5_SET(fte_match_param, spec->match_value, outer_headers.tcp_dport,
ntohs(inp->inp_lport));
MLX5_SET(fte_match_param, spec->match_value, outer_headers.tcp_sport,
ntohs(inp->inp_fport));
switch (INP_SOCKAF(inp->inp_socket)) {
#ifdef INET
case AF_INET:
accel_fs_tcp_set_ipv4_flow(spec, inp);
ft = &fs_tcp->tables[MLX5E_ACCEL_FS_IPV4_TCP];
break;
#endif
#ifdef INET6
case AF_INET6:
if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0 &&
IN6_IS_ADDR_V4MAPPED(&inp->in6p_faddr)) {
accel_fs_tcp_set_ipv4_flow(spec, inp);
ft = &fs_tcp->tables[MLX5E_ACCEL_FS_IPV4_TCP];
} else {
accel_fs_tcp_set_ipv6_flow(spec, inp);
ft = &fs_tcp->tables[MLX5E_ACCEL_FS_IPV6_TCP];
}
break;
#endif
default:
break;
}
INP_RUNLOCK(inp);
if (!ft) {
flow = ERR_PTR(-EINVAL);
goto out;
}
dest.type = MLX5_FLOW_DESTINATION_TYPE_TIR;
dest.tir_num = tirn;
flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST;
flow = mlx5_add_flow_rules(ft->t, spec, &flow_act, &dest, 1);
out:
kvfree(spec);
return (flow);
}
static int
accel_fs_tcp_add_default_rule(struct mlx5e_priv *priv, int type)
{
static struct mlx5_flow_spec spec = {};
struct mlx5_flow_destination dest = {};
struct mlx5e_accel_fs_tcp *fs_tcp;
struct mlx5_flow_handle *rule;
struct mlx5_flow_act flow_act = {
.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST,
};
fs_tcp = &priv->fts.accel_tcp;
spec.flow_context.flags = FLOW_CONTEXT_HAS_TAG;
spec.flow_context.flow_tag = MLX5_FS_DEFAULT_FLOW_TAG;
dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
dest.ft = (type == MLX5E_ACCEL_FS_TCP_NUM_TYPES - 1) ?
((priv->fts.ipsec_ft) ? priv->fts.ipsec_ft : priv->fts.vlan.t) :
fs_tcp->tables[type + 1].t;
rule = mlx5_add_flow_rules(fs_tcp->tables[type].t, &spec, &flow_act,
&dest, 1);
if (IS_ERR(rule))
return (PTR_ERR(rule));
fs_tcp->default_rules[type] = rule;
return (0);
}
#define MLX5E_ACCEL_FS_TCP_NUM_GROUPS (2)
#define MLX5E_ACCEL_FS_TCP_GROUP1_SIZE (BIT(16) - 1)
#define MLX5E_ACCEL_FS_TCP_GROUP2_SIZE (BIT(0))
#define MLX5E_ACCEL_FS_TCP_TABLE_SIZE (MLX5E_ACCEL_FS_TCP_GROUP1_SIZE +\
MLX5E_ACCEL_FS_TCP_GROUP2_SIZE)
static int
accel_fs_tcp_create_groups(struct mlx5e_flow_table *ft, int type)
{
int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in);
void *outer_headers_c;
int ix = 0;
u32 *in;
int err;
u8 *mc;
ft->g = kcalloc(MLX5E_ACCEL_FS_TCP_NUM_GROUPS, sizeof(*ft->g), GFP_KERNEL);
in = kvzalloc(inlen, GFP_KERNEL);
if (!in || !ft->g) {
kfree(ft->g);
kvfree(in);
return (-ENOMEM);
}
mc = MLX5_ADDR_OF(create_flow_group_in, in, match_criteria);
outer_headers_c = MLX5_ADDR_OF(fte_match_param, mc, outer_headers);
MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, outer_headers_c, ip_protocol);
MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, outer_headers_c, ip_version);
MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, outer_headers_c, cvlan_tag);
MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, outer_headers_c, first_vid);
switch (type) {
case MLX5E_ACCEL_FS_IPV4_TCP:
case MLX5E_ACCEL_FS_IPV6_TCP:
MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, outer_headers_c, tcp_dport);
MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, outer_headers_c, tcp_sport);
break;
default:
err = -EINVAL;
goto out;
}
switch (type) {
case MLX5E_ACCEL_FS_IPV4_TCP:
MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, outer_headers_c,
src_ipv4_src_ipv6.ipv4_layout.ipv4);
MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, outer_headers_c,
dst_ipv4_dst_ipv6.ipv4_layout.ipv4);
break;
case MLX5E_ACCEL_FS_IPV6_TCP:
memset(MLX5_ADDR_OF(fte_match_set_lyr_2_4, outer_headers_c,
src_ipv4_src_ipv6.ipv6_layout.ipv6),
0xff, 16);
memset(MLX5_ADDR_OF(fte_match_set_lyr_2_4, outer_headers_c,
dst_ipv4_dst_ipv6.ipv6_layout.ipv6),
0xff, 16);
break;
default:
err = -EINVAL;
goto out;
}
MLX5_SET_CFG(in, match_criteria_enable, MLX5_MATCH_OUTER_HEADERS);
MLX5_SET_CFG(in, start_flow_index, ix);
ix += MLX5E_ACCEL_FS_TCP_GROUP1_SIZE;
MLX5_SET_CFG(in, end_flow_index, ix - 1);
ft->g[ft->num_groups] = mlx5_create_flow_group(ft->t, in);
if (IS_ERR(ft->g[ft->num_groups]))
goto err;
ft->num_groups++;
memset(in, 0, inlen);
MLX5_SET_CFG(in, start_flow_index, ix);
ix += MLX5E_ACCEL_FS_TCP_GROUP2_SIZE;
MLX5_SET_CFG(in, end_flow_index, ix - 1);
ft->g[ft->num_groups] = mlx5_create_flow_group(ft->t, in);
if (IS_ERR(ft->g[ft->num_groups]))
goto err;
ft->num_groups++;
kvfree(in);
return (0);
err:
err = PTR_ERR(ft->g[ft->num_groups]);
ft->g[ft->num_groups] = NULL;
out:
kvfree(in);
return (err);
}
static void
accel_fs_tcp_destroy_groups(struct mlx5e_flow_table *ft)
{
int i;
for (i = ft->num_groups - 1; i >= 0; i--) {
if (!IS_ERR_OR_NULL(ft->g[i]))
mlx5_destroy_flow_group(ft->g[i]);
ft->g[i] = NULL;
}
ft->num_groups = 0;
}
static int
accel_fs_tcp_create_table(struct mlx5e_priv *priv, int type)
{
struct mlx5e_flow_table *ft = &priv->fts.accel_tcp.tables[type];
struct mlx5_flow_table_attr ft_attr = {};
int err;
ft->num_groups = 0;
ft_attr.max_fte = MLX5E_ACCEL_FS_TCP_TABLE_SIZE;
ft_attr.level = type;
ft->t = mlx5_create_flow_table(priv->fts.accel_tcp.ns, &ft_attr);
if (IS_ERR(ft->t)) {
err = PTR_ERR(ft->t);
ft->t = NULL;
return (err);
}
err = accel_fs_tcp_create_groups(ft, type);
if (err)
goto err_destroy_flow_table;
return (0);
err_destroy_flow_table:
mlx5_destroy_flow_table(ft->t);
ft->t = NULL;
return (err);
}
static void
accel_fs_tcp_destroy_table(struct mlx5e_priv *priv, int i)
{
struct mlx5e_accel_fs_tcp *fs_tcp;
struct mlx5e_flow_table *ft;
fs_tcp = &priv->fts.accel_tcp;
ft = fs_tcp->tables + i;
accel_fs_tcp_destroy_groups(ft);
kfree(ft->g);
ft->g = NULL;
mlx5_destroy_flow_table(ft->t);
ft->t = NULL;
}
void
mlx5e_accel_fs_tcp_destroy(struct mlx5e_priv *priv)
{
int i;
if (!MLX5_CAP_FLOWTABLE_NIC_RX(priv->mdev, ft_field_support.outer_ip_version))
return;
for (i = 0; i < MLX5E_ACCEL_FS_TCP_NUM_TYPES; i++) {
mlx5_del_flow_rules(&priv->fts.accel_tcp.default_rules[i]);
accel_fs_tcp_destroy_table(priv, i);
}
}
int
mlx5e_accel_fs_tcp_create(struct mlx5e_priv *priv)
{
int i, err;
if (!MLX5_CAP_FLOWTABLE_NIC_RX(priv->mdev, ft_field_support.outer_ip_version))
return (0);
priv->fts.accel_tcp.ns = mlx5_get_flow_namespace(
priv->mdev, MLX5_FLOW_NAMESPACE_OFFLOADS);
for (i = 0; i != MLX5E_ACCEL_FS_TCP_NUM_TYPES; i++) {
err = accel_fs_tcp_create_table(priv, i);
if (err)
goto err_destroy_tables;
}
for (i = 0; i != MLX5E_ACCEL_FS_TCP_NUM_TYPES; i++) {
err = accel_fs_tcp_add_default_rule(priv, i);
if (err)
goto err_destroy_rules;
}
return (0);
err_destroy_rules:
while (i--)
mlx5_del_flow_rules(&priv->fts.accel_tcp.default_rules[i]);
i = MLX5E_ACCEL_FS_TCP_NUM_TYPES;
err_destroy_tables:
while (i--)
accel_fs_tcp_destroy_table(priv, i);
return (err);
}