root/drivers/net/dsa/sja1105/sja1105_vl.c
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2020 NXP
 */
#include <net/tc_act/tc_gate.h>
#include <linux/dsa/8021q.h>
#include "sja1105_vl.h"

#define SJA1105_SIZE_VL_STATUS                  8

/* Insert into the global gate list, sorted by gate action time. */
static int sja1105_insert_gate_entry(struct sja1105_gating_config *gating_cfg,
                                     struct sja1105_rule *rule,
                                     u8 gate_state, s64 entry_time,
                                     struct netlink_ext_ack *extack)
{
        struct sja1105_gate_entry *e;
        int rc;

        e = kzalloc_obj(*e);
        if (!e)
                return -ENOMEM;

        e->rule = rule;
        e->gate_state = gate_state;
        e->interval = entry_time;

        if (list_empty(&gating_cfg->entries)) {
                list_add(&e->list, &gating_cfg->entries);
        } else {
                struct sja1105_gate_entry *p;

                list_for_each_entry(p, &gating_cfg->entries, list) {
                        if (p->interval == e->interval) {
                                NL_SET_ERR_MSG_MOD(extack,
                                                   "Gate conflict");
                                rc = -EBUSY;
                                goto err;
                        }

                        if (e->interval < p->interval)
                                break;
                }
                list_add(&e->list, p->list.prev);
        }

        gating_cfg->num_entries++;

        return 0;
err:
        kfree(e);
        return rc;
}

/* The gate entries contain absolute times in their e->interval field. Convert
 * that to proper intervals (i.e. "0, 5, 10, 15" to "5, 5, 5, 5").
 */
static void
sja1105_gating_cfg_time_to_interval(struct sja1105_gating_config *gating_cfg,
                                    u64 cycle_time)
{
        struct sja1105_gate_entry *last_e;
        struct sja1105_gate_entry *e;
        struct list_head *prev;

        list_for_each_entry(e, &gating_cfg->entries, list) {
                struct sja1105_gate_entry *p;

                prev = e->list.prev;

                if (prev == &gating_cfg->entries)
                        continue;

                p = list_entry(prev, struct sja1105_gate_entry, list);
                p->interval = e->interval - p->interval;
        }
        last_e = list_last_entry(&gating_cfg->entries,
                                 struct sja1105_gate_entry, list);
        last_e->interval = cycle_time - last_e->interval;
}

static void sja1105_free_gating_config(struct sja1105_gating_config *gating_cfg)
{
        struct sja1105_gate_entry *e, *n;

        list_for_each_entry_safe(e, n, &gating_cfg->entries, list) {
                list_del(&e->list);
                kfree(e);
        }
}

static int sja1105_compose_gating_subschedule(struct sja1105_private *priv,
                                              struct netlink_ext_ack *extack)
{
        struct sja1105_gating_config *gating_cfg = &priv->tas_data.gating_cfg;
        struct sja1105_rule *rule;
        s64 max_cycle_time = 0;
        s64 its_base_time = 0;
        int i, rc = 0;

        sja1105_free_gating_config(gating_cfg);

        list_for_each_entry(rule, &priv->flow_block.rules, list) {
                if (rule->type != SJA1105_RULE_VL)
                        continue;
                if (rule->vl.type != SJA1105_VL_TIME_TRIGGERED)
                        continue;

                if (max_cycle_time < rule->vl.cycle_time) {
                        max_cycle_time = rule->vl.cycle_time;
                        its_base_time = rule->vl.base_time;
                }
        }

        if (!max_cycle_time)
                return 0;

        dev_dbg(priv->ds->dev, "max_cycle_time %lld its_base_time %lld\n",
                max_cycle_time, its_base_time);

        gating_cfg->base_time = its_base_time;
        gating_cfg->cycle_time = max_cycle_time;
        gating_cfg->num_entries = 0;

        list_for_each_entry(rule, &priv->flow_block.rules, list) {
                s64 time;
                s64 rbt;

                if (rule->type != SJA1105_RULE_VL)
                        continue;
                if (rule->vl.type != SJA1105_VL_TIME_TRIGGERED)
                        continue;

                /* Calculate the difference between this gating schedule's
                 * base time, and the base time of the gating schedule with the
                 * longest cycle time. We call it the relative base time (rbt).
                 */
                rbt = future_base_time(rule->vl.base_time, rule->vl.cycle_time,
                                       its_base_time);
                rbt -= its_base_time;

                time = rbt;

                for (i = 0; i < rule->vl.num_entries; i++) {
                        u8 gate_state = rule->vl.entries[i].gate_state;
                        s64 entry_time = time;

                        while (entry_time < max_cycle_time) {
                                rc = sja1105_insert_gate_entry(gating_cfg, rule,
                                                               gate_state,
                                                               entry_time,
                                                               extack);
                                if (rc)
                                        goto err;

                                entry_time += rule->vl.cycle_time;
                        }
                        time += rule->vl.entries[i].interval;
                }
        }

        sja1105_gating_cfg_time_to_interval(gating_cfg, max_cycle_time);

        return 0;
err:
        sja1105_free_gating_config(gating_cfg);
        return rc;
}

/* The switch flow classification core implements TTEthernet, which 'thinks' in
 * terms of Virtual Links (VL), a concept borrowed from ARINC 664 part 7.
 * However it also has one other operating mode (VLLUPFORMAT=0) where it acts
 * somewhat closer to a pre-standard implementation of IEEE 802.1Qci
 * (Per-Stream Filtering and Policing), which is what the driver is going to be
 * implementing.
 *
 *                                 VL Lookup
 *        Key = {DMAC && VLANID   +---------+  Key = { (DMAC[47:16] & VLMASK ==
 *               && VLAN PCP      |         |                         VLMARKER)
 *               && INGRESS PORT} +---------+                      (both fixed)
 *            (exact match,            |             && DMAC[15:0] == VLID
 *         all specified in rule)      |                    (specified in rule)
 *                                     v             && INGRESS PORT }
 *                               ------------
 *                    0 (PSFP)  /            \  1 (ARINC664)
 *                 +-----------/  VLLUPFORMAT \----------+
 *                 |           \    (fixed)   /          |
 *                 |            \            /           |
 *  0 (forwarding) v             ------------            |
 *           ------------                                |
 *          /            \  1 (QoS classification)       |
 *     +---/  ISCRITICAL  \-----------+                  |
 *     |   \  (per rule)  /           |                  |
 *     |    \            /   VLID taken from      VLID taken from
 *     v     ------------     index of rule       contents of rule
 *  select                     that matched         that matched
 * DESTPORTS                          |                  |
 *  |                                 +---------+--------+
 *  |                                           |
 *  |                                           v
 *  |                                     VL Forwarding
 *  |                                   (indexed by VLID)
 *  |                                      +---------+
 *  |                       +--------------|         |
 *  |                       |  select TYPE +---------+
 *  |                       v
 *  |   0 (rate      ------------    1 (time
 *  |  constrained) /            \   triggered)
 *  |       +------/     TYPE     \------------+
 *  |       |      \  (per VLID)  /            |
 *  |       v       \            /             v
 *  |  VL Policing   ------------         VL Policing
 *  | (indexed by VLID)                (indexed by VLID)
 *  |  +---------+                        +---------+
 *  |  | TYPE=0  |                        | TYPE=1  |
 *  |  +---------+                        +---------+
 *  |  select SHARINDX                 select SHARINDX to
 *  |  to rate-limit                 re-enter VL Forwarding
 *  |  groups of VL's               with new VLID for egress
 *  |  to same quota                           |
 *  |       |                                  |
 *  |  select MAXLEN -> exceed => drop    select MAXLEN -> exceed => drop
 *  |       |                                  |
 *  |       v                                  v
 *  |  VL Forwarding                      VL Forwarding
 *  | (indexed by SHARINDX)             (indexed by SHARINDX)
 *  |  +---------+                        +---------+
 *  |  | TYPE=0  |                        | TYPE=1  |
 *  |  +---------+                        +---------+
 *  |  select PRIORITY,                 select PRIORITY,
 *  | PARTITION, DESTPORTS            PARTITION, DESTPORTS
 *  |       |                                  |
 *  |       v                                  v
 *  |  VL Policing                        VL Policing
 *  | (indexed by SHARINDX)           (indexed by SHARINDX)
 *  |  +---------+                        +---------+
 *  |  | TYPE=0  |                        | TYPE=1  |
 *  |  +---------+                        +---------+
 *  |       |                                  |
 *  |       v                                  |
 *  |  select BAG, -> exceed => drop           |
 *  |    JITTER                                v
 *  |       |             ----------------------------------------------
 *  |       |            /    Reception Window is open for this VL      \
 *  |       |           /    (the Schedule Table executes an entry i     \
 *  |       |          /   M <= i < N, for which these conditions hold):  \ no
 *  |       |    +----/                                                    \-+
 *  |       |    |yes \       WINST[M] == 1 && WINSTINDEX[M] == VLID       / |
 *  |       |    |     \     WINEND[N] == 1 && WINSTINDEX[N] == VLID      /  |
 *  |       |    |      \                                                /   |
 *  |       |    |       \ (the VL window has opened and not yet closed)/    |
 *  |       |    |        ----------------------------------------------     |
 *  |       |    v                                                           v
 *  |       |  dispatch to DESTPORTS when the Schedule Table               drop
 *  |       |  executes an entry i with TXEN == 1 && VLINDEX == i
 *  v       v
 * dispatch immediately to DESTPORTS
 *
 * The per-port classification key is always composed of {DMAC, VID, PCP} and
 * is non-maskable. This 'looks like' the NULL stream identification function
 * from IEEE 802.1CB clause 6, except for the extra VLAN PCP. When the switch
 * ports operate as VLAN-unaware, we do allow the user to not specify the VLAN
 * ID and PCP, and then the port-based defaults will be used.
 *
 * In TTEthernet, routing is something that needs to be done manually for each
 * Virtual Link. So the flow action must always include one of:
 * a. 'redirect', 'trap' or 'drop': select the egress port list
 * Additionally, the following actions may be applied on a Virtual Link,
 * turning it into 'critical' traffic:
 * b. 'police': turn it into a rate-constrained VL, with bandwidth limitation
 *    given by the maximum frame length, bandwidth allocation gap (BAG) and
 *    maximum jitter.
 * c. 'gate': turn it into a time-triggered VL, which can be only be received
 *    and forwarded according to a given schedule.
 */

static bool sja1105_vl_key_lower(struct sja1105_vl_lookup_entry *a,
                                 struct sja1105_vl_lookup_entry *b)
{
        if (a->macaddr < b->macaddr)
                return true;
        if (a->macaddr > b->macaddr)
                return false;
        if (a->vlanid < b->vlanid)
                return true;
        if (a->vlanid > b->vlanid)
                return false;
        if (a->port < b->port)
                return true;
        if (a->port > b->port)
                return false;
        if (a->vlanprior < b->vlanprior)
                return true;
        if (a->vlanprior > b->vlanprior)
                return false;
        /* Keys are equal */
        return false;
}

/* FIXME: this should change when the bridge upper of the port changes. */
static u16 sja1105_port_get_tag_8021q_vid(struct dsa_port *dp)
{
        unsigned long bridge_num;

        if (!dp->bridge)
                return dsa_tag_8021q_standalone_vid(dp);

        bridge_num = dsa_port_bridge_num_get(dp);

        return dsa_tag_8021q_bridge_vid(bridge_num);
}

static int sja1105_init_virtual_links(struct sja1105_private *priv,
                                      struct netlink_ext_ack *extack)
{
        struct sja1105_vl_policing_entry *vl_policing;
        struct sja1105_vl_forwarding_entry *vl_fwd;
        struct sja1105_vl_lookup_entry *vl_lookup;
        bool have_critical_virtual_links = false;
        struct sja1105_table *table;
        struct sja1105_rule *rule;
        int num_virtual_links = 0;
        int max_sharindx = 0;
        int i, j, k;

        /* Figure out the dimensioning of the problem */
        list_for_each_entry(rule, &priv->flow_block.rules, list) {
                if (rule->type != SJA1105_RULE_VL)
                        continue;
                /* Each VL lookup entry matches on a single ingress port */
                num_virtual_links += hweight_long(rule->port_mask);

                if (rule->vl.type != SJA1105_VL_NONCRITICAL)
                        have_critical_virtual_links = true;
                if (max_sharindx < rule->vl.sharindx)
                        max_sharindx = rule->vl.sharindx;
        }

        if (num_virtual_links > SJA1105_MAX_VL_LOOKUP_COUNT) {
                NL_SET_ERR_MSG_MOD(extack, "Not enough VL entries available");
                return -ENOSPC;
        }

        if (max_sharindx + 1 > SJA1105_MAX_VL_LOOKUP_COUNT) {
                NL_SET_ERR_MSG_MOD(extack, "Policer index out of range");
                return -ENOSPC;
        }

        max_sharindx = max_t(int, num_virtual_links, max_sharindx) + 1;

        /* Discard previous VL Lookup Table */
        table = &priv->static_config.tables[BLK_IDX_VL_LOOKUP];
        if (table->entry_count) {
                kfree(table->entries);
                table->entry_count = 0;
        }

        /* Discard previous VL Policing Table */
        table = &priv->static_config.tables[BLK_IDX_VL_POLICING];
        if (table->entry_count) {
                kfree(table->entries);
                table->entry_count = 0;
        }

        /* Discard previous VL Forwarding Table */
        table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING];
        if (table->entry_count) {
                kfree(table->entries);
                table->entry_count = 0;
        }

        /* Discard previous VL Forwarding Parameters Table */
        table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING_PARAMS];
        if (table->entry_count) {
                kfree(table->entries);
                table->entry_count = 0;
        }

        /* Nothing to do */
        if (!num_virtual_links)
                return 0;

        /* Pre-allocate space in the static config tables */

        /* VL Lookup Table */
        table = &priv->static_config.tables[BLK_IDX_VL_LOOKUP];
        table->entries = kcalloc(num_virtual_links,
                                 table->ops->unpacked_entry_size,
                                 GFP_KERNEL);
        if (!table->entries)
                return -ENOMEM;
        table->entry_count = num_virtual_links;
        vl_lookup = table->entries;

        k = 0;

        list_for_each_entry(rule, &priv->flow_block.rules, list) {
                unsigned long port;

                if (rule->type != SJA1105_RULE_VL)
                        continue;

                for_each_set_bit(port, &rule->port_mask, SJA1105_MAX_NUM_PORTS) {
                        vl_lookup[k].format = SJA1105_VL_FORMAT_PSFP;
                        vl_lookup[k].port = port;
                        vl_lookup[k].macaddr = rule->key.vl.dmac;
                        if (rule->key.type == SJA1105_KEY_VLAN_AWARE_VL) {
                                vl_lookup[k].vlanid = rule->key.vl.vid;
                                vl_lookup[k].vlanprior = rule->key.vl.pcp;
                        } else {
                                /* FIXME */
                                struct dsa_port *dp = dsa_to_port(priv->ds, port);
                                u16 vid = sja1105_port_get_tag_8021q_vid(dp);

                                vl_lookup[k].vlanid = vid;
                                vl_lookup[k].vlanprior = 0;
                        }
                        /* For critical VLs, the DESTPORTS mask is taken from
                         * the VL Forwarding Table, so no point in putting it
                         * in the VL Lookup Table
                         */
                        if (rule->vl.type == SJA1105_VL_NONCRITICAL)
                                vl_lookup[k].destports = rule->vl.destports;
                        else
                                vl_lookup[k].iscritical = true;
                        vl_lookup[k].flow_cookie = rule->cookie;
                        k++;
                }
        }

        /* UM10944.pdf chapter 4.2.3 VL Lookup table:
         * "the entries in the VL Lookup table must be sorted in ascending
         * order (i.e. the smallest value must be loaded first) according to
         * the following sort order: MACADDR, VLANID, PORT, VLANPRIOR."
         */
        for (i = 0; i < num_virtual_links; i++) {
                struct sja1105_vl_lookup_entry *a = &vl_lookup[i];

                for (j = i + 1; j < num_virtual_links; j++) {
                        struct sja1105_vl_lookup_entry *b = &vl_lookup[j];

                        if (sja1105_vl_key_lower(b, a)) {
                                struct sja1105_vl_lookup_entry tmp = *a;

                                *a = *b;
                                *b = tmp;
                        }
                }
        }

        if (!have_critical_virtual_links)
                return 0;

        /* VL Policing Table */
        table = &priv->static_config.tables[BLK_IDX_VL_POLICING];
        table->entries = kcalloc(max_sharindx, table->ops->unpacked_entry_size,
                                 GFP_KERNEL);
        if (!table->entries)
                return -ENOMEM;
        table->entry_count = max_sharindx;
        vl_policing = table->entries;

        /* VL Forwarding Table */
        table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING];
        table->entries = kcalloc(max_sharindx, table->ops->unpacked_entry_size,
                                 GFP_KERNEL);
        if (!table->entries)
                return -ENOMEM;
        table->entry_count = max_sharindx;
        vl_fwd = table->entries;

        /* VL Forwarding Parameters Table */
        table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING_PARAMS];
        table->entries = kcalloc(1, table->ops->unpacked_entry_size,
                                 GFP_KERNEL);
        if (!table->entries)
                return -ENOMEM;
        table->entry_count = 1;

        for (i = 0; i < num_virtual_links; i++) {
                unsigned long cookie = vl_lookup[i].flow_cookie;
                struct sja1105_rule *rule = sja1105_rule_find(priv, cookie);

                if (rule->vl.type == SJA1105_VL_NONCRITICAL)
                        continue;
                if (rule->vl.type == SJA1105_VL_TIME_TRIGGERED) {
                        int sharindx = rule->vl.sharindx;

                        vl_policing[i].type = 1;
                        vl_policing[i].sharindx = sharindx;
                        vl_policing[i].maxlen = rule->vl.maxlen;
                        vl_policing[sharindx].type = 1;

                        vl_fwd[i].type = 1;
                        vl_fwd[sharindx].type = 1;
                        vl_fwd[sharindx].priority = rule->vl.ipv;
                        vl_fwd[sharindx].partition = 0;
                        vl_fwd[sharindx].destports = rule->vl.destports;
                }
        }

        sja1105_frame_memory_partitioning(priv);

        return 0;
}

int sja1105_vl_redirect(struct sja1105_private *priv, int port,
                        struct netlink_ext_ack *extack, unsigned long cookie,
                        struct sja1105_key *key, unsigned long destports,
                        bool append)
{
        struct sja1105_rule *rule = sja1105_rule_find(priv, cookie);
        struct dsa_port *dp = dsa_to_port(priv->ds, port);
        bool vlan_aware = dsa_port_is_vlan_filtering(dp);
        int rc;

        if (!vlan_aware && key->type != SJA1105_KEY_VLAN_UNAWARE_VL) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Can only redirect based on DMAC");
                return -EOPNOTSUPP;
        } else if (vlan_aware && key->type != SJA1105_KEY_VLAN_AWARE_VL) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Can only redirect based on {DMAC, VID, PCP}");
                return -EOPNOTSUPP;
        }

        if (!rule) {
                rule = kzalloc_obj(*rule);
                if (!rule)
                        return -ENOMEM;

                rule->cookie = cookie;
                rule->type = SJA1105_RULE_VL;
                rule->key = *key;
                list_add(&rule->list, &priv->flow_block.rules);
        }

        rule->port_mask |= BIT(port);
        if (append)
                rule->vl.destports |= destports;
        else
                rule->vl.destports = destports;

        rc = sja1105_init_virtual_links(priv, extack);
        if (rc) {
                rule->port_mask &= ~BIT(port);
                if (!rule->port_mask) {
                        list_del(&rule->list);
                        kfree(rule);
                }
        }

        return rc;
}

int sja1105_vl_delete(struct sja1105_private *priv, int port,
                      struct sja1105_rule *rule, struct netlink_ext_ack *extack)
{
        int rc;

        rule->port_mask &= ~BIT(port);
        if (!rule->port_mask) {
                list_del(&rule->list);
                kfree(rule);
        }

        rc = sja1105_compose_gating_subschedule(priv, extack);
        if (rc)
                return rc;

        rc = sja1105_init_virtual_links(priv, extack);
        if (rc)
                return rc;

        rc = sja1105_init_scheduling(priv);
        if (rc < 0)
                return rc;

        return sja1105_static_config_reload(priv, SJA1105_VIRTUAL_LINKS);
}

int sja1105_vl_gate(struct sja1105_private *priv, int port,
                    struct netlink_ext_ack *extack, unsigned long cookie,
                    struct sja1105_key *key, u32 index, s32 prio,
                    u64 base_time, u64 cycle_time, u64 cycle_time_ext,
                    u32 num_entries, struct action_gate_entry *entries)
{
        struct sja1105_rule *rule = sja1105_rule_find(priv, cookie);
        struct dsa_port *dp = dsa_to_port(priv->ds, port);
        bool vlan_aware = dsa_port_is_vlan_filtering(dp);
        int ipv = -1;
        int i, rc;
        s32 rem;

        if (cycle_time_ext) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Cycle time extension not supported");
                return -EOPNOTSUPP;
        }

        div_s64_rem(base_time, sja1105_delta_to_ns(1), &rem);
        if (rem) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Base time must be multiple of 200 ns");
                return -ERANGE;
        }

        div_s64_rem(cycle_time, sja1105_delta_to_ns(1), &rem);
        if (rem) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Cycle time must be multiple of 200 ns");
                return -ERANGE;
        }

        if (!vlan_aware && key->type != SJA1105_KEY_VLAN_UNAWARE_VL) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Can only gate based on DMAC");
                return -EOPNOTSUPP;
        } else if (vlan_aware && key->type != SJA1105_KEY_VLAN_AWARE_VL) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Can only gate based on {DMAC, VID, PCP}");
                return -EOPNOTSUPP;
        }

        if (!rule) {
                rule = kzalloc_obj(*rule);
                if (!rule)
                        return -ENOMEM;

                list_add(&rule->list, &priv->flow_block.rules);
                rule->cookie = cookie;
                rule->type = SJA1105_RULE_VL;
                rule->key = *key;
                rule->vl.type = SJA1105_VL_TIME_TRIGGERED;
                rule->vl.sharindx = index;
                rule->vl.base_time = base_time;
                rule->vl.cycle_time = cycle_time;
                rule->vl.num_entries = num_entries;
                rule->vl.entries = kzalloc_objs(struct action_gate_entry,
                                                num_entries);
                if (!rule->vl.entries) {
                        rc = -ENOMEM;
                        goto out;
                }

                for (i = 0; i < num_entries; i++) {
                        div_s64_rem(entries[i].interval,
                                    sja1105_delta_to_ns(1), &rem);
                        if (rem) {
                                NL_SET_ERR_MSG_MOD(extack,
                                                   "Interval must be multiple of 200 ns");
                                rc = -ERANGE;
                                goto out;
                        }

                        if (!entries[i].interval) {
                                NL_SET_ERR_MSG_MOD(extack,
                                                   "Interval cannot be zero");
                                rc = -ERANGE;
                                goto out;
                        }

                        if (ns_to_sja1105_delta(entries[i].interval) >
                            SJA1105_TAS_MAX_DELTA) {
                                NL_SET_ERR_MSG_MOD(extack,
                                                   "Maximum interval is 52 ms");
                                rc = -ERANGE;
                                goto out;
                        }

                        if (entries[i].maxoctets != -1) {
                                NL_SET_ERR_MSG_MOD(extack,
                                                   "Cannot offload IntervalOctetMax");
                                rc = -EOPNOTSUPP;
                                goto out;
                        }

                        if (ipv == -1) {
                                ipv = entries[i].ipv;
                        } else if (ipv != entries[i].ipv) {
                                NL_SET_ERR_MSG_MOD(extack,
                                                   "Only support a single IPV per VL");
                                rc = -EOPNOTSUPP;
                                goto out;
                        }

                        rule->vl.entries[i] = entries[i];
                }

                if (ipv == -1) {
                        if (key->type == SJA1105_KEY_VLAN_AWARE_VL)
                                ipv = key->vl.pcp;
                        else
                                ipv = 0;
                }

                /* TODO: support per-flow MTU */
                rule->vl.maxlen = VLAN_ETH_FRAME_LEN + ETH_FCS_LEN;
                rule->vl.ipv = ipv;
        }

        rule->port_mask |= BIT(port);

        rc = sja1105_compose_gating_subschedule(priv, extack);
        if (rc)
                goto out;

        rc = sja1105_init_virtual_links(priv, extack);
        if (rc)
                goto out;

        if (sja1105_gating_check_conflicts(priv, -1, extack)) {
                NL_SET_ERR_MSG_MOD(extack, "Conflict with tc-taprio schedule");
                rc = -ERANGE;
                goto out;
        }

out:
        if (rc) {
                rule->port_mask &= ~BIT(port);
                if (!rule->port_mask) {
                        list_del(&rule->list);
                        kfree(rule->vl.entries);
                        kfree(rule);
                }
        }

        return rc;
}

static int sja1105_find_vlid(struct sja1105_private *priv, int port,
                             struct sja1105_key *key)
{
        struct sja1105_vl_lookup_entry *vl_lookup;
        struct sja1105_table *table;
        int i;

        if (WARN_ON(key->type != SJA1105_KEY_VLAN_AWARE_VL &&
                    key->type != SJA1105_KEY_VLAN_UNAWARE_VL))
                return -1;

        table = &priv->static_config.tables[BLK_IDX_VL_LOOKUP];
        vl_lookup = table->entries;

        for (i = 0; i < table->entry_count; i++) {
                if (key->type == SJA1105_KEY_VLAN_AWARE_VL) {
                        if (vl_lookup[i].port == port &&
                            vl_lookup[i].macaddr == key->vl.dmac &&
                            vl_lookup[i].vlanid == key->vl.vid &&
                            vl_lookup[i].vlanprior == key->vl.pcp)
                                return i;
                } else {
                        if (vl_lookup[i].port == port &&
                            vl_lookup[i].macaddr == key->vl.dmac)
                                return i;
                }
        }

        return -1;
}

int sja1105_vl_stats(struct sja1105_private *priv, int port,
                     struct sja1105_rule *rule, struct flow_stats *stats,
                     struct netlink_ext_ack *extack)
{
        const struct sja1105_regs *regs = priv->info->regs;
        u8 buf[SJA1105_SIZE_VL_STATUS] = {0};
        u64 unreleased;
        u64 timingerr;
        u64 lengtherr;
        int vlid, rc;
        u64 pkts;

        if (rule->vl.type != SJA1105_VL_TIME_TRIGGERED)
                return 0;

        vlid = sja1105_find_vlid(priv, port, &rule->key);
        if (vlid < 0)
                return 0;

        rc = sja1105_xfer_buf(priv, SPI_READ, regs->vl_status + 2 * vlid, buf,
                              SJA1105_SIZE_VL_STATUS);
        if (rc) {
                NL_SET_ERR_MSG_MOD(extack, "SPI access failed");
                return rc;
        }

        sja1105_unpack(buf, &timingerr,  31, 16, SJA1105_SIZE_VL_STATUS);
        sja1105_unpack(buf, &unreleased, 15,  0, SJA1105_SIZE_VL_STATUS);
        sja1105_unpack(buf, &lengtherr,  47, 32, SJA1105_SIZE_VL_STATUS);

        pkts = timingerr + unreleased + lengtherr;

        flow_stats_update(stats, 0, pkts - rule->vl.stats.pkts, 0,
                          jiffies - rule->vl.stats.lastused,
                          FLOW_ACTION_HW_STATS_IMMEDIATE);

        rule->vl.stats.pkts = pkts;
        rule->vl.stats.lastused = jiffies;

        return 0;
}