162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* Copyright 2020 NXP
362306a36Sopenharmony_ci */
462306a36Sopenharmony_ci#include <net/tc_act/tc_gate.h>
562306a36Sopenharmony_ci#include <linux/dsa/8021q.h>
662306a36Sopenharmony_ci#include "sja1105_vl.h"
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define SJA1105_SIZE_VL_STATUS			8
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci/* Insert into the global gate list, sorted by gate action time. */
1162306a36Sopenharmony_cistatic int sja1105_insert_gate_entry(struct sja1105_gating_config *gating_cfg,
1262306a36Sopenharmony_ci				     struct sja1105_rule *rule,
1362306a36Sopenharmony_ci				     u8 gate_state, s64 entry_time,
1462306a36Sopenharmony_ci				     struct netlink_ext_ack *extack)
1562306a36Sopenharmony_ci{
1662306a36Sopenharmony_ci	struct sja1105_gate_entry *e;
1762306a36Sopenharmony_ci	int rc;
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci	e = kzalloc(sizeof(*e), GFP_KERNEL);
2062306a36Sopenharmony_ci	if (!e)
2162306a36Sopenharmony_ci		return -ENOMEM;
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci	e->rule = rule;
2462306a36Sopenharmony_ci	e->gate_state = gate_state;
2562306a36Sopenharmony_ci	e->interval = entry_time;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	if (list_empty(&gating_cfg->entries)) {
2862306a36Sopenharmony_ci		list_add(&e->list, &gating_cfg->entries);
2962306a36Sopenharmony_ci	} else {
3062306a36Sopenharmony_ci		struct sja1105_gate_entry *p;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci		list_for_each_entry(p, &gating_cfg->entries, list) {
3362306a36Sopenharmony_ci			if (p->interval == e->interval) {
3462306a36Sopenharmony_ci				NL_SET_ERR_MSG_MOD(extack,
3562306a36Sopenharmony_ci						   "Gate conflict");
3662306a36Sopenharmony_ci				rc = -EBUSY;
3762306a36Sopenharmony_ci				goto err;
3862306a36Sopenharmony_ci			}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci			if (e->interval < p->interval)
4162306a36Sopenharmony_ci				break;
4262306a36Sopenharmony_ci		}
4362306a36Sopenharmony_ci		list_add(&e->list, p->list.prev);
4462306a36Sopenharmony_ci	}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	gating_cfg->num_entries++;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	return 0;
4962306a36Sopenharmony_cierr:
5062306a36Sopenharmony_ci	kfree(e);
5162306a36Sopenharmony_ci	return rc;
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/* The gate entries contain absolute times in their e->interval field. Convert
5562306a36Sopenharmony_ci * that to proper intervals (i.e. "0, 5, 10, 15" to "5, 5, 5, 5").
5662306a36Sopenharmony_ci */
5762306a36Sopenharmony_cistatic void
5862306a36Sopenharmony_cisja1105_gating_cfg_time_to_interval(struct sja1105_gating_config *gating_cfg,
5962306a36Sopenharmony_ci				    u64 cycle_time)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	struct sja1105_gate_entry *last_e;
6262306a36Sopenharmony_ci	struct sja1105_gate_entry *e;
6362306a36Sopenharmony_ci	struct list_head *prev;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	list_for_each_entry(e, &gating_cfg->entries, list) {
6662306a36Sopenharmony_ci		struct sja1105_gate_entry *p;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci		prev = e->list.prev;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci		if (prev == &gating_cfg->entries)
7162306a36Sopenharmony_ci			continue;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci		p = list_entry(prev, struct sja1105_gate_entry, list);
7462306a36Sopenharmony_ci		p->interval = e->interval - p->interval;
7562306a36Sopenharmony_ci	}
7662306a36Sopenharmony_ci	last_e = list_last_entry(&gating_cfg->entries,
7762306a36Sopenharmony_ci				 struct sja1105_gate_entry, list);
7862306a36Sopenharmony_ci	last_e->interval = cycle_time - last_e->interval;
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic void sja1105_free_gating_config(struct sja1105_gating_config *gating_cfg)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	struct sja1105_gate_entry *e, *n;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	list_for_each_entry_safe(e, n, &gating_cfg->entries, list) {
8662306a36Sopenharmony_ci		list_del(&e->list);
8762306a36Sopenharmony_ci		kfree(e);
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic int sja1105_compose_gating_subschedule(struct sja1105_private *priv,
9262306a36Sopenharmony_ci					      struct netlink_ext_ack *extack)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	struct sja1105_gating_config *gating_cfg = &priv->tas_data.gating_cfg;
9562306a36Sopenharmony_ci	struct sja1105_rule *rule;
9662306a36Sopenharmony_ci	s64 max_cycle_time = 0;
9762306a36Sopenharmony_ci	s64 its_base_time = 0;
9862306a36Sopenharmony_ci	int i, rc = 0;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	sja1105_free_gating_config(gating_cfg);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	list_for_each_entry(rule, &priv->flow_block.rules, list) {
10362306a36Sopenharmony_ci		if (rule->type != SJA1105_RULE_VL)
10462306a36Sopenharmony_ci			continue;
10562306a36Sopenharmony_ci		if (rule->vl.type != SJA1105_VL_TIME_TRIGGERED)
10662306a36Sopenharmony_ci			continue;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci		if (max_cycle_time < rule->vl.cycle_time) {
10962306a36Sopenharmony_ci			max_cycle_time = rule->vl.cycle_time;
11062306a36Sopenharmony_ci			its_base_time = rule->vl.base_time;
11162306a36Sopenharmony_ci		}
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	if (!max_cycle_time)
11562306a36Sopenharmony_ci		return 0;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	dev_dbg(priv->ds->dev, "max_cycle_time %lld its_base_time %lld\n",
11862306a36Sopenharmony_ci		max_cycle_time, its_base_time);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	gating_cfg->base_time = its_base_time;
12162306a36Sopenharmony_ci	gating_cfg->cycle_time = max_cycle_time;
12262306a36Sopenharmony_ci	gating_cfg->num_entries = 0;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	list_for_each_entry(rule, &priv->flow_block.rules, list) {
12562306a36Sopenharmony_ci		s64 time;
12662306a36Sopenharmony_ci		s64 rbt;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci		if (rule->type != SJA1105_RULE_VL)
12962306a36Sopenharmony_ci			continue;
13062306a36Sopenharmony_ci		if (rule->vl.type != SJA1105_VL_TIME_TRIGGERED)
13162306a36Sopenharmony_ci			continue;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci		/* Calculate the difference between this gating schedule's
13462306a36Sopenharmony_ci		 * base time, and the base time of the gating schedule with the
13562306a36Sopenharmony_ci		 * longest cycle time. We call it the relative base time (rbt).
13662306a36Sopenharmony_ci		 */
13762306a36Sopenharmony_ci		rbt = future_base_time(rule->vl.base_time, rule->vl.cycle_time,
13862306a36Sopenharmony_ci				       its_base_time);
13962306a36Sopenharmony_ci		rbt -= its_base_time;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci		time = rbt;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci		for (i = 0; i < rule->vl.num_entries; i++) {
14462306a36Sopenharmony_ci			u8 gate_state = rule->vl.entries[i].gate_state;
14562306a36Sopenharmony_ci			s64 entry_time = time;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci			while (entry_time < max_cycle_time) {
14862306a36Sopenharmony_ci				rc = sja1105_insert_gate_entry(gating_cfg, rule,
14962306a36Sopenharmony_ci							       gate_state,
15062306a36Sopenharmony_ci							       entry_time,
15162306a36Sopenharmony_ci							       extack);
15262306a36Sopenharmony_ci				if (rc)
15362306a36Sopenharmony_ci					goto err;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci				entry_time += rule->vl.cycle_time;
15662306a36Sopenharmony_ci			}
15762306a36Sopenharmony_ci			time += rule->vl.entries[i].interval;
15862306a36Sopenharmony_ci		}
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	sja1105_gating_cfg_time_to_interval(gating_cfg, max_cycle_time);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	return 0;
16462306a36Sopenharmony_cierr:
16562306a36Sopenharmony_ci	sja1105_free_gating_config(gating_cfg);
16662306a36Sopenharmony_ci	return rc;
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci/* The switch flow classification core implements TTEthernet, which 'thinks' in
17062306a36Sopenharmony_ci * terms of Virtual Links (VL), a concept borrowed from ARINC 664 part 7.
17162306a36Sopenharmony_ci * However it also has one other operating mode (VLLUPFORMAT=0) where it acts
17262306a36Sopenharmony_ci * somewhat closer to a pre-standard implementation of IEEE 802.1Qci
17362306a36Sopenharmony_ci * (Per-Stream Filtering and Policing), which is what the driver is going to be
17462306a36Sopenharmony_ci * implementing.
17562306a36Sopenharmony_ci *
17662306a36Sopenharmony_ci *                                 VL Lookup
17762306a36Sopenharmony_ci *        Key = {DMAC && VLANID   +---------+  Key = { (DMAC[47:16] & VLMASK ==
17862306a36Sopenharmony_ci *               && VLAN PCP      |         |                         VLMARKER)
17962306a36Sopenharmony_ci *               && INGRESS PORT} +---------+                      (both fixed)
18062306a36Sopenharmony_ci *            (exact match,            |             && DMAC[15:0] == VLID
18162306a36Sopenharmony_ci *         all specified in rule)      |                    (specified in rule)
18262306a36Sopenharmony_ci *                                     v             && INGRESS PORT }
18362306a36Sopenharmony_ci *                               ------------
18462306a36Sopenharmony_ci *                    0 (PSFP)  /            \  1 (ARINC664)
18562306a36Sopenharmony_ci *                 +-----------/  VLLUPFORMAT \----------+
18662306a36Sopenharmony_ci *                 |           \    (fixed)   /          |
18762306a36Sopenharmony_ci *                 |            \            /           |
18862306a36Sopenharmony_ci *  0 (forwarding) v             ------------            |
18962306a36Sopenharmony_ci *           ------------                                |
19062306a36Sopenharmony_ci *          /            \  1 (QoS classification)       |
19162306a36Sopenharmony_ci *     +---/  ISCRITICAL  \-----------+                  |
19262306a36Sopenharmony_ci *     |   \  (per rule)  /           |                  |
19362306a36Sopenharmony_ci *     |    \            /   VLID taken from      VLID taken from
19462306a36Sopenharmony_ci *     v     ------------     index of rule       contents of rule
19562306a36Sopenharmony_ci *  select                     that matched         that matched
19662306a36Sopenharmony_ci * DESTPORTS                          |                  |
19762306a36Sopenharmony_ci *  |                                 +---------+--------+
19862306a36Sopenharmony_ci *  |                                           |
19962306a36Sopenharmony_ci *  |                                           v
20062306a36Sopenharmony_ci *  |                                     VL Forwarding
20162306a36Sopenharmony_ci *  |                                   (indexed by VLID)
20262306a36Sopenharmony_ci *  |                                      +---------+
20362306a36Sopenharmony_ci *  |                       +--------------|         |
20462306a36Sopenharmony_ci *  |                       |  select TYPE +---------+
20562306a36Sopenharmony_ci *  |                       v
20662306a36Sopenharmony_ci *  |   0 (rate      ------------    1 (time
20762306a36Sopenharmony_ci *  |  constrained) /            \   triggered)
20862306a36Sopenharmony_ci *  |       +------/     TYPE     \------------+
20962306a36Sopenharmony_ci *  |       |      \  (per VLID)  /            |
21062306a36Sopenharmony_ci *  |       v       \            /             v
21162306a36Sopenharmony_ci *  |  VL Policing   ------------         VL Policing
21262306a36Sopenharmony_ci *  | (indexed by VLID)                (indexed by VLID)
21362306a36Sopenharmony_ci *  |  +---------+                        +---------+
21462306a36Sopenharmony_ci *  |  | TYPE=0  |                        | TYPE=1  |
21562306a36Sopenharmony_ci *  |  +---------+                        +---------+
21662306a36Sopenharmony_ci *  |  select SHARINDX                 select SHARINDX to
21762306a36Sopenharmony_ci *  |  to rate-limit                 re-enter VL Forwarding
21862306a36Sopenharmony_ci *  |  groups of VL's               with new VLID for egress
21962306a36Sopenharmony_ci *  |  to same quota                           |
22062306a36Sopenharmony_ci *  |       |                                  |
22162306a36Sopenharmony_ci *  |  select MAXLEN -> exceed => drop    select MAXLEN -> exceed => drop
22262306a36Sopenharmony_ci *  |       |                                  |
22362306a36Sopenharmony_ci *  |       v                                  v
22462306a36Sopenharmony_ci *  |  VL Forwarding                      VL Forwarding
22562306a36Sopenharmony_ci *  | (indexed by SHARINDX)             (indexed by SHARINDX)
22662306a36Sopenharmony_ci *  |  +---------+                        +---------+
22762306a36Sopenharmony_ci *  |  | TYPE=0  |                        | TYPE=1  |
22862306a36Sopenharmony_ci *  |  +---------+                        +---------+
22962306a36Sopenharmony_ci *  |  select PRIORITY,                 select PRIORITY,
23062306a36Sopenharmony_ci *  | PARTITION, DESTPORTS            PARTITION, DESTPORTS
23162306a36Sopenharmony_ci *  |       |                                  |
23262306a36Sopenharmony_ci *  |       v                                  v
23362306a36Sopenharmony_ci *  |  VL Policing                        VL Policing
23462306a36Sopenharmony_ci *  | (indexed by SHARINDX)           (indexed by SHARINDX)
23562306a36Sopenharmony_ci *  |  +---------+                        +---------+
23662306a36Sopenharmony_ci *  |  | TYPE=0  |                        | TYPE=1  |
23762306a36Sopenharmony_ci *  |  +---------+                        +---------+
23862306a36Sopenharmony_ci *  |       |                                  |
23962306a36Sopenharmony_ci *  |       v                                  |
24062306a36Sopenharmony_ci *  |  select BAG, -> exceed => drop           |
24162306a36Sopenharmony_ci *  |    JITTER                                v
24262306a36Sopenharmony_ci *  |       |             ----------------------------------------------
24362306a36Sopenharmony_ci *  |       |            /    Reception Window is open for this VL      \
24462306a36Sopenharmony_ci *  |       |           /    (the Schedule Table executes an entry i     \
24562306a36Sopenharmony_ci *  |       |          /   M <= i < N, for which these conditions hold):  \ no
24662306a36Sopenharmony_ci *  |       |    +----/                                                    \-+
24762306a36Sopenharmony_ci *  |       |    |yes \       WINST[M] == 1 && WINSTINDEX[M] == VLID       / |
24862306a36Sopenharmony_ci *  |       |    |     \     WINEND[N] == 1 && WINSTINDEX[N] == VLID      /  |
24962306a36Sopenharmony_ci *  |       |    |      \                                                /   |
25062306a36Sopenharmony_ci *  |       |    |       \ (the VL window has opened and not yet closed)/    |
25162306a36Sopenharmony_ci *  |       |    |        ----------------------------------------------     |
25262306a36Sopenharmony_ci *  |       |    v                                                           v
25362306a36Sopenharmony_ci *  |       |  dispatch to DESTPORTS when the Schedule Table               drop
25462306a36Sopenharmony_ci *  |       |  executes an entry i with TXEN == 1 && VLINDEX == i
25562306a36Sopenharmony_ci *  v       v
25662306a36Sopenharmony_ci * dispatch immediately to DESTPORTS
25762306a36Sopenharmony_ci *
25862306a36Sopenharmony_ci * The per-port classification key is always composed of {DMAC, VID, PCP} and
25962306a36Sopenharmony_ci * is non-maskable. This 'looks like' the NULL stream identification function
26062306a36Sopenharmony_ci * from IEEE 802.1CB clause 6, except for the extra VLAN PCP. When the switch
26162306a36Sopenharmony_ci * ports operate as VLAN-unaware, we do allow the user to not specify the VLAN
26262306a36Sopenharmony_ci * ID and PCP, and then the port-based defaults will be used.
26362306a36Sopenharmony_ci *
26462306a36Sopenharmony_ci * In TTEthernet, routing is something that needs to be done manually for each
26562306a36Sopenharmony_ci * Virtual Link. So the flow action must always include one of:
26662306a36Sopenharmony_ci * a. 'redirect', 'trap' or 'drop': select the egress port list
26762306a36Sopenharmony_ci * Additionally, the following actions may be applied on a Virtual Link,
26862306a36Sopenharmony_ci * turning it into 'critical' traffic:
26962306a36Sopenharmony_ci * b. 'police': turn it into a rate-constrained VL, with bandwidth limitation
27062306a36Sopenharmony_ci *    given by the maximum frame length, bandwidth allocation gap (BAG) and
27162306a36Sopenharmony_ci *    maximum jitter.
27262306a36Sopenharmony_ci * c. 'gate': turn it into a time-triggered VL, which can be only be received
27362306a36Sopenharmony_ci *    and forwarded according to a given schedule.
27462306a36Sopenharmony_ci */
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cistatic bool sja1105_vl_key_lower(struct sja1105_vl_lookup_entry *a,
27762306a36Sopenharmony_ci				 struct sja1105_vl_lookup_entry *b)
27862306a36Sopenharmony_ci{
27962306a36Sopenharmony_ci	if (a->macaddr < b->macaddr)
28062306a36Sopenharmony_ci		return true;
28162306a36Sopenharmony_ci	if (a->macaddr > b->macaddr)
28262306a36Sopenharmony_ci		return false;
28362306a36Sopenharmony_ci	if (a->vlanid < b->vlanid)
28462306a36Sopenharmony_ci		return true;
28562306a36Sopenharmony_ci	if (a->vlanid > b->vlanid)
28662306a36Sopenharmony_ci		return false;
28762306a36Sopenharmony_ci	if (a->port < b->port)
28862306a36Sopenharmony_ci		return true;
28962306a36Sopenharmony_ci	if (a->port > b->port)
29062306a36Sopenharmony_ci		return false;
29162306a36Sopenharmony_ci	if (a->vlanprior < b->vlanprior)
29262306a36Sopenharmony_ci		return true;
29362306a36Sopenharmony_ci	if (a->vlanprior > b->vlanprior)
29462306a36Sopenharmony_ci		return false;
29562306a36Sopenharmony_ci	/* Keys are equal */
29662306a36Sopenharmony_ci	return false;
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci/* FIXME: this should change when the bridge upper of the port changes. */
30062306a36Sopenharmony_cistatic u16 sja1105_port_get_tag_8021q_vid(struct dsa_port *dp)
30162306a36Sopenharmony_ci{
30262306a36Sopenharmony_ci	unsigned long bridge_num;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	if (!dp->bridge)
30562306a36Sopenharmony_ci		return dsa_tag_8021q_standalone_vid(dp);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	bridge_num = dsa_port_bridge_num_get(dp);
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	return dsa_tag_8021q_bridge_vid(bridge_num);
31062306a36Sopenharmony_ci}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_cistatic int sja1105_init_virtual_links(struct sja1105_private *priv,
31362306a36Sopenharmony_ci				      struct netlink_ext_ack *extack)
31462306a36Sopenharmony_ci{
31562306a36Sopenharmony_ci	struct sja1105_vl_policing_entry *vl_policing;
31662306a36Sopenharmony_ci	struct sja1105_vl_forwarding_entry *vl_fwd;
31762306a36Sopenharmony_ci	struct sja1105_vl_lookup_entry *vl_lookup;
31862306a36Sopenharmony_ci	bool have_critical_virtual_links = false;
31962306a36Sopenharmony_ci	struct sja1105_table *table;
32062306a36Sopenharmony_ci	struct sja1105_rule *rule;
32162306a36Sopenharmony_ci	int num_virtual_links = 0;
32262306a36Sopenharmony_ci	int max_sharindx = 0;
32362306a36Sopenharmony_ci	int i, j, k;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	/* Figure out the dimensioning of the problem */
32662306a36Sopenharmony_ci	list_for_each_entry(rule, &priv->flow_block.rules, list) {
32762306a36Sopenharmony_ci		if (rule->type != SJA1105_RULE_VL)
32862306a36Sopenharmony_ci			continue;
32962306a36Sopenharmony_ci		/* Each VL lookup entry matches on a single ingress port */
33062306a36Sopenharmony_ci		num_virtual_links += hweight_long(rule->port_mask);
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci		if (rule->vl.type != SJA1105_VL_NONCRITICAL)
33362306a36Sopenharmony_ci			have_critical_virtual_links = true;
33462306a36Sopenharmony_ci		if (max_sharindx < rule->vl.sharindx)
33562306a36Sopenharmony_ci			max_sharindx = rule->vl.sharindx;
33662306a36Sopenharmony_ci	}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	if (num_virtual_links > SJA1105_MAX_VL_LOOKUP_COUNT) {
33962306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Not enough VL entries available");
34062306a36Sopenharmony_ci		return -ENOSPC;
34162306a36Sopenharmony_ci	}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	if (max_sharindx + 1 > SJA1105_MAX_VL_LOOKUP_COUNT) {
34462306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Policer index out of range");
34562306a36Sopenharmony_ci		return -ENOSPC;
34662306a36Sopenharmony_ci	}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	max_sharindx = max_t(int, num_virtual_links, max_sharindx) + 1;
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	/* Discard previous VL Lookup Table */
35162306a36Sopenharmony_ci	table = &priv->static_config.tables[BLK_IDX_VL_LOOKUP];
35262306a36Sopenharmony_ci	if (table->entry_count) {
35362306a36Sopenharmony_ci		kfree(table->entries);
35462306a36Sopenharmony_ci		table->entry_count = 0;
35562306a36Sopenharmony_ci	}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	/* Discard previous VL Policing Table */
35862306a36Sopenharmony_ci	table = &priv->static_config.tables[BLK_IDX_VL_POLICING];
35962306a36Sopenharmony_ci	if (table->entry_count) {
36062306a36Sopenharmony_ci		kfree(table->entries);
36162306a36Sopenharmony_ci		table->entry_count = 0;
36262306a36Sopenharmony_ci	}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	/* Discard previous VL Forwarding Table */
36562306a36Sopenharmony_ci	table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING];
36662306a36Sopenharmony_ci	if (table->entry_count) {
36762306a36Sopenharmony_ci		kfree(table->entries);
36862306a36Sopenharmony_ci		table->entry_count = 0;
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	/* Discard previous VL Forwarding Parameters Table */
37262306a36Sopenharmony_ci	table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING_PARAMS];
37362306a36Sopenharmony_ci	if (table->entry_count) {
37462306a36Sopenharmony_ci		kfree(table->entries);
37562306a36Sopenharmony_ci		table->entry_count = 0;
37662306a36Sopenharmony_ci	}
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	/* Nothing to do */
37962306a36Sopenharmony_ci	if (!num_virtual_links)
38062306a36Sopenharmony_ci		return 0;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	/* Pre-allocate space in the static config tables */
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	/* VL Lookup Table */
38562306a36Sopenharmony_ci	table = &priv->static_config.tables[BLK_IDX_VL_LOOKUP];
38662306a36Sopenharmony_ci	table->entries = kcalloc(num_virtual_links,
38762306a36Sopenharmony_ci				 table->ops->unpacked_entry_size,
38862306a36Sopenharmony_ci				 GFP_KERNEL);
38962306a36Sopenharmony_ci	if (!table->entries)
39062306a36Sopenharmony_ci		return -ENOMEM;
39162306a36Sopenharmony_ci	table->entry_count = num_virtual_links;
39262306a36Sopenharmony_ci	vl_lookup = table->entries;
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	k = 0;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	list_for_each_entry(rule, &priv->flow_block.rules, list) {
39762306a36Sopenharmony_ci		unsigned long port;
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci		if (rule->type != SJA1105_RULE_VL)
40062306a36Sopenharmony_ci			continue;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci		for_each_set_bit(port, &rule->port_mask, SJA1105_MAX_NUM_PORTS) {
40362306a36Sopenharmony_ci			vl_lookup[k].format = SJA1105_VL_FORMAT_PSFP;
40462306a36Sopenharmony_ci			vl_lookup[k].port = port;
40562306a36Sopenharmony_ci			vl_lookup[k].macaddr = rule->key.vl.dmac;
40662306a36Sopenharmony_ci			if (rule->key.type == SJA1105_KEY_VLAN_AWARE_VL) {
40762306a36Sopenharmony_ci				vl_lookup[k].vlanid = rule->key.vl.vid;
40862306a36Sopenharmony_ci				vl_lookup[k].vlanprior = rule->key.vl.pcp;
40962306a36Sopenharmony_ci			} else {
41062306a36Sopenharmony_ci				/* FIXME */
41162306a36Sopenharmony_ci				struct dsa_port *dp = dsa_to_port(priv->ds, port);
41262306a36Sopenharmony_ci				u16 vid = sja1105_port_get_tag_8021q_vid(dp);
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci				vl_lookup[k].vlanid = vid;
41562306a36Sopenharmony_ci				vl_lookup[k].vlanprior = 0;
41662306a36Sopenharmony_ci			}
41762306a36Sopenharmony_ci			/* For critical VLs, the DESTPORTS mask is taken from
41862306a36Sopenharmony_ci			 * the VL Forwarding Table, so no point in putting it
41962306a36Sopenharmony_ci			 * in the VL Lookup Table
42062306a36Sopenharmony_ci			 */
42162306a36Sopenharmony_ci			if (rule->vl.type == SJA1105_VL_NONCRITICAL)
42262306a36Sopenharmony_ci				vl_lookup[k].destports = rule->vl.destports;
42362306a36Sopenharmony_ci			else
42462306a36Sopenharmony_ci				vl_lookup[k].iscritical = true;
42562306a36Sopenharmony_ci			vl_lookup[k].flow_cookie = rule->cookie;
42662306a36Sopenharmony_ci			k++;
42762306a36Sopenharmony_ci		}
42862306a36Sopenharmony_ci	}
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	/* UM10944.pdf chapter 4.2.3 VL Lookup table:
43162306a36Sopenharmony_ci	 * "the entries in the VL Lookup table must be sorted in ascending
43262306a36Sopenharmony_ci	 * order (i.e. the smallest value must be loaded first) according to
43362306a36Sopenharmony_ci	 * the following sort order: MACADDR, VLANID, PORT, VLANPRIOR."
43462306a36Sopenharmony_ci	 */
43562306a36Sopenharmony_ci	for (i = 0; i < num_virtual_links; i++) {
43662306a36Sopenharmony_ci		struct sja1105_vl_lookup_entry *a = &vl_lookup[i];
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci		for (j = i + 1; j < num_virtual_links; j++) {
43962306a36Sopenharmony_ci			struct sja1105_vl_lookup_entry *b = &vl_lookup[j];
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci			if (sja1105_vl_key_lower(b, a)) {
44262306a36Sopenharmony_ci				struct sja1105_vl_lookup_entry tmp = *a;
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci				*a = *b;
44562306a36Sopenharmony_ci				*b = tmp;
44662306a36Sopenharmony_ci			}
44762306a36Sopenharmony_ci		}
44862306a36Sopenharmony_ci	}
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	if (!have_critical_virtual_links)
45162306a36Sopenharmony_ci		return 0;
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	/* VL Policing Table */
45462306a36Sopenharmony_ci	table = &priv->static_config.tables[BLK_IDX_VL_POLICING];
45562306a36Sopenharmony_ci	table->entries = kcalloc(max_sharindx, table->ops->unpacked_entry_size,
45662306a36Sopenharmony_ci				 GFP_KERNEL);
45762306a36Sopenharmony_ci	if (!table->entries)
45862306a36Sopenharmony_ci		return -ENOMEM;
45962306a36Sopenharmony_ci	table->entry_count = max_sharindx;
46062306a36Sopenharmony_ci	vl_policing = table->entries;
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	/* VL Forwarding Table */
46362306a36Sopenharmony_ci	table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING];
46462306a36Sopenharmony_ci	table->entries = kcalloc(max_sharindx, table->ops->unpacked_entry_size,
46562306a36Sopenharmony_ci				 GFP_KERNEL);
46662306a36Sopenharmony_ci	if (!table->entries)
46762306a36Sopenharmony_ci		return -ENOMEM;
46862306a36Sopenharmony_ci	table->entry_count = max_sharindx;
46962306a36Sopenharmony_ci	vl_fwd = table->entries;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	/* VL Forwarding Parameters Table */
47262306a36Sopenharmony_ci	table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING_PARAMS];
47362306a36Sopenharmony_ci	table->entries = kcalloc(1, table->ops->unpacked_entry_size,
47462306a36Sopenharmony_ci				 GFP_KERNEL);
47562306a36Sopenharmony_ci	if (!table->entries)
47662306a36Sopenharmony_ci		return -ENOMEM;
47762306a36Sopenharmony_ci	table->entry_count = 1;
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	for (i = 0; i < num_virtual_links; i++) {
48062306a36Sopenharmony_ci		unsigned long cookie = vl_lookup[i].flow_cookie;
48162306a36Sopenharmony_ci		struct sja1105_rule *rule = sja1105_rule_find(priv, cookie);
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci		if (rule->vl.type == SJA1105_VL_NONCRITICAL)
48462306a36Sopenharmony_ci			continue;
48562306a36Sopenharmony_ci		if (rule->vl.type == SJA1105_VL_TIME_TRIGGERED) {
48662306a36Sopenharmony_ci			int sharindx = rule->vl.sharindx;
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci			vl_policing[i].type = 1;
48962306a36Sopenharmony_ci			vl_policing[i].sharindx = sharindx;
49062306a36Sopenharmony_ci			vl_policing[i].maxlen = rule->vl.maxlen;
49162306a36Sopenharmony_ci			vl_policing[sharindx].type = 1;
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci			vl_fwd[i].type = 1;
49462306a36Sopenharmony_ci			vl_fwd[sharindx].type = 1;
49562306a36Sopenharmony_ci			vl_fwd[sharindx].priority = rule->vl.ipv;
49662306a36Sopenharmony_ci			vl_fwd[sharindx].partition = 0;
49762306a36Sopenharmony_ci			vl_fwd[sharindx].destports = rule->vl.destports;
49862306a36Sopenharmony_ci		}
49962306a36Sopenharmony_ci	}
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	sja1105_frame_memory_partitioning(priv);
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	return 0;
50462306a36Sopenharmony_ci}
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ciint sja1105_vl_redirect(struct sja1105_private *priv, int port,
50762306a36Sopenharmony_ci			struct netlink_ext_ack *extack, unsigned long cookie,
50862306a36Sopenharmony_ci			struct sja1105_key *key, unsigned long destports,
50962306a36Sopenharmony_ci			bool append)
51062306a36Sopenharmony_ci{
51162306a36Sopenharmony_ci	struct sja1105_rule *rule = sja1105_rule_find(priv, cookie);
51262306a36Sopenharmony_ci	struct dsa_port *dp = dsa_to_port(priv->ds, port);
51362306a36Sopenharmony_ci	bool vlan_aware = dsa_port_is_vlan_filtering(dp);
51462306a36Sopenharmony_ci	int rc;
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci	if (!vlan_aware && key->type != SJA1105_KEY_VLAN_UNAWARE_VL) {
51762306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
51862306a36Sopenharmony_ci				   "Can only redirect based on DMAC");
51962306a36Sopenharmony_ci		return -EOPNOTSUPP;
52062306a36Sopenharmony_ci	} else if (vlan_aware && key->type != SJA1105_KEY_VLAN_AWARE_VL) {
52162306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
52262306a36Sopenharmony_ci				   "Can only redirect based on {DMAC, VID, PCP}");
52362306a36Sopenharmony_ci		return -EOPNOTSUPP;
52462306a36Sopenharmony_ci	}
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	if (!rule) {
52762306a36Sopenharmony_ci		rule = kzalloc(sizeof(*rule), GFP_KERNEL);
52862306a36Sopenharmony_ci		if (!rule)
52962306a36Sopenharmony_ci			return -ENOMEM;
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci		rule->cookie = cookie;
53262306a36Sopenharmony_ci		rule->type = SJA1105_RULE_VL;
53362306a36Sopenharmony_ci		rule->key = *key;
53462306a36Sopenharmony_ci		list_add(&rule->list, &priv->flow_block.rules);
53562306a36Sopenharmony_ci	}
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci	rule->port_mask |= BIT(port);
53862306a36Sopenharmony_ci	if (append)
53962306a36Sopenharmony_ci		rule->vl.destports |= destports;
54062306a36Sopenharmony_ci	else
54162306a36Sopenharmony_ci		rule->vl.destports = destports;
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	rc = sja1105_init_virtual_links(priv, extack);
54462306a36Sopenharmony_ci	if (rc) {
54562306a36Sopenharmony_ci		rule->port_mask &= ~BIT(port);
54662306a36Sopenharmony_ci		if (!rule->port_mask) {
54762306a36Sopenharmony_ci			list_del(&rule->list);
54862306a36Sopenharmony_ci			kfree(rule);
54962306a36Sopenharmony_ci		}
55062306a36Sopenharmony_ci	}
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	return rc;
55362306a36Sopenharmony_ci}
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ciint sja1105_vl_delete(struct sja1105_private *priv, int port,
55662306a36Sopenharmony_ci		      struct sja1105_rule *rule, struct netlink_ext_ack *extack)
55762306a36Sopenharmony_ci{
55862306a36Sopenharmony_ci	int rc;
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_ci	rule->port_mask &= ~BIT(port);
56162306a36Sopenharmony_ci	if (!rule->port_mask) {
56262306a36Sopenharmony_ci		list_del(&rule->list);
56362306a36Sopenharmony_ci		kfree(rule);
56462306a36Sopenharmony_ci	}
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	rc = sja1105_compose_gating_subschedule(priv, extack);
56762306a36Sopenharmony_ci	if (rc)
56862306a36Sopenharmony_ci		return rc;
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	rc = sja1105_init_virtual_links(priv, extack);
57162306a36Sopenharmony_ci	if (rc)
57262306a36Sopenharmony_ci		return rc;
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	rc = sja1105_init_scheduling(priv);
57562306a36Sopenharmony_ci	if (rc < 0)
57662306a36Sopenharmony_ci		return rc;
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci	return sja1105_static_config_reload(priv, SJA1105_VIRTUAL_LINKS);
57962306a36Sopenharmony_ci}
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ciint sja1105_vl_gate(struct sja1105_private *priv, int port,
58262306a36Sopenharmony_ci		    struct netlink_ext_ack *extack, unsigned long cookie,
58362306a36Sopenharmony_ci		    struct sja1105_key *key, u32 index, s32 prio,
58462306a36Sopenharmony_ci		    u64 base_time, u64 cycle_time, u64 cycle_time_ext,
58562306a36Sopenharmony_ci		    u32 num_entries, struct action_gate_entry *entries)
58662306a36Sopenharmony_ci{
58762306a36Sopenharmony_ci	struct sja1105_rule *rule = sja1105_rule_find(priv, cookie);
58862306a36Sopenharmony_ci	struct dsa_port *dp = dsa_to_port(priv->ds, port);
58962306a36Sopenharmony_ci	bool vlan_aware = dsa_port_is_vlan_filtering(dp);
59062306a36Sopenharmony_ci	int ipv = -1;
59162306a36Sopenharmony_ci	int i, rc;
59262306a36Sopenharmony_ci	s32 rem;
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	if (cycle_time_ext) {
59562306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
59662306a36Sopenharmony_ci				   "Cycle time extension not supported");
59762306a36Sopenharmony_ci		return -EOPNOTSUPP;
59862306a36Sopenharmony_ci	}
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	div_s64_rem(base_time, sja1105_delta_to_ns(1), &rem);
60162306a36Sopenharmony_ci	if (rem) {
60262306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
60362306a36Sopenharmony_ci				   "Base time must be multiple of 200 ns");
60462306a36Sopenharmony_ci		return -ERANGE;
60562306a36Sopenharmony_ci	}
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci	div_s64_rem(cycle_time, sja1105_delta_to_ns(1), &rem);
60862306a36Sopenharmony_ci	if (rem) {
60962306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
61062306a36Sopenharmony_ci				   "Cycle time must be multiple of 200 ns");
61162306a36Sopenharmony_ci		return -ERANGE;
61262306a36Sopenharmony_ci	}
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	if (!vlan_aware && key->type != SJA1105_KEY_VLAN_UNAWARE_VL) {
61562306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
61662306a36Sopenharmony_ci				   "Can only gate based on DMAC");
61762306a36Sopenharmony_ci		return -EOPNOTSUPP;
61862306a36Sopenharmony_ci	} else if (vlan_aware && key->type != SJA1105_KEY_VLAN_AWARE_VL) {
61962306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
62062306a36Sopenharmony_ci				   "Can only gate based on {DMAC, VID, PCP}");
62162306a36Sopenharmony_ci		return -EOPNOTSUPP;
62262306a36Sopenharmony_ci	}
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	if (!rule) {
62562306a36Sopenharmony_ci		rule = kzalloc(sizeof(*rule), GFP_KERNEL);
62662306a36Sopenharmony_ci		if (!rule)
62762306a36Sopenharmony_ci			return -ENOMEM;
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci		list_add(&rule->list, &priv->flow_block.rules);
63062306a36Sopenharmony_ci		rule->cookie = cookie;
63162306a36Sopenharmony_ci		rule->type = SJA1105_RULE_VL;
63262306a36Sopenharmony_ci		rule->key = *key;
63362306a36Sopenharmony_ci		rule->vl.type = SJA1105_VL_TIME_TRIGGERED;
63462306a36Sopenharmony_ci		rule->vl.sharindx = index;
63562306a36Sopenharmony_ci		rule->vl.base_time = base_time;
63662306a36Sopenharmony_ci		rule->vl.cycle_time = cycle_time;
63762306a36Sopenharmony_ci		rule->vl.num_entries = num_entries;
63862306a36Sopenharmony_ci		rule->vl.entries = kcalloc(num_entries,
63962306a36Sopenharmony_ci					   sizeof(struct action_gate_entry),
64062306a36Sopenharmony_ci					   GFP_KERNEL);
64162306a36Sopenharmony_ci		if (!rule->vl.entries) {
64262306a36Sopenharmony_ci			rc = -ENOMEM;
64362306a36Sopenharmony_ci			goto out;
64462306a36Sopenharmony_ci		}
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci		for (i = 0; i < num_entries; i++) {
64762306a36Sopenharmony_ci			div_s64_rem(entries[i].interval,
64862306a36Sopenharmony_ci				    sja1105_delta_to_ns(1), &rem);
64962306a36Sopenharmony_ci			if (rem) {
65062306a36Sopenharmony_ci				NL_SET_ERR_MSG_MOD(extack,
65162306a36Sopenharmony_ci						   "Interval must be multiple of 200 ns");
65262306a36Sopenharmony_ci				rc = -ERANGE;
65362306a36Sopenharmony_ci				goto out;
65462306a36Sopenharmony_ci			}
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci			if (!entries[i].interval) {
65762306a36Sopenharmony_ci				NL_SET_ERR_MSG_MOD(extack,
65862306a36Sopenharmony_ci						   "Interval cannot be zero");
65962306a36Sopenharmony_ci				rc = -ERANGE;
66062306a36Sopenharmony_ci				goto out;
66162306a36Sopenharmony_ci			}
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ci			if (ns_to_sja1105_delta(entries[i].interval) >
66462306a36Sopenharmony_ci			    SJA1105_TAS_MAX_DELTA) {
66562306a36Sopenharmony_ci				NL_SET_ERR_MSG_MOD(extack,
66662306a36Sopenharmony_ci						   "Maximum interval is 52 ms");
66762306a36Sopenharmony_ci				rc = -ERANGE;
66862306a36Sopenharmony_ci				goto out;
66962306a36Sopenharmony_ci			}
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_ci			if (entries[i].maxoctets != -1) {
67262306a36Sopenharmony_ci				NL_SET_ERR_MSG_MOD(extack,
67362306a36Sopenharmony_ci						   "Cannot offload IntervalOctetMax");
67462306a36Sopenharmony_ci				rc = -EOPNOTSUPP;
67562306a36Sopenharmony_ci				goto out;
67662306a36Sopenharmony_ci			}
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci			if (ipv == -1) {
67962306a36Sopenharmony_ci				ipv = entries[i].ipv;
68062306a36Sopenharmony_ci			} else if (ipv != entries[i].ipv) {
68162306a36Sopenharmony_ci				NL_SET_ERR_MSG_MOD(extack,
68262306a36Sopenharmony_ci						   "Only support a single IPV per VL");
68362306a36Sopenharmony_ci				rc = -EOPNOTSUPP;
68462306a36Sopenharmony_ci				goto out;
68562306a36Sopenharmony_ci			}
68662306a36Sopenharmony_ci
68762306a36Sopenharmony_ci			rule->vl.entries[i] = entries[i];
68862306a36Sopenharmony_ci		}
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_ci		if (ipv == -1) {
69162306a36Sopenharmony_ci			if (key->type == SJA1105_KEY_VLAN_AWARE_VL)
69262306a36Sopenharmony_ci				ipv = key->vl.pcp;
69362306a36Sopenharmony_ci			else
69462306a36Sopenharmony_ci				ipv = 0;
69562306a36Sopenharmony_ci		}
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci		/* TODO: support per-flow MTU */
69862306a36Sopenharmony_ci		rule->vl.maxlen = VLAN_ETH_FRAME_LEN + ETH_FCS_LEN;
69962306a36Sopenharmony_ci		rule->vl.ipv = ipv;
70062306a36Sopenharmony_ci	}
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci	rule->port_mask |= BIT(port);
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_ci	rc = sja1105_compose_gating_subschedule(priv, extack);
70562306a36Sopenharmony_ci	if (rc)
70662306a36Sopenharmony_ci		goto out;
70762306a36Sopenharmony_ci
70862306a36Sopenharmony_ci	rc = sja1105_init_virtual_links(priv, extack);
70962306a36Sopenharmony_ci	if (rc)
71062306a36Sopenharmony_ci		goto out;
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci	if (sja1105_gating_check_conflicts(priv, -1, extack)) {
71362306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Conflict with tc-taprio schedule");
71462306a36Sopenharmony_ci		rc = -ERANGE;
71562306a36Sopenharmony_ci		goto out;
71662306a36Sopenharmony_ci	}
71762306a36Sopenharmony_ci
71862306a36Sopenharmony_ciout:
71962306a36Sopenharmony_ci	if (rc) {
72062306a36Sopenharmony_ci		rule->port_mask &= ~BIT(port);
72162306a36Sopenharmony_ci		if (!rule->port_mask) {
72262306a36Sopenharmony_ci			list_del(&rule->list);
72362306a36Sopenharmony_ci			kfree(rule->vl.entries);
72462306a36Sopenharmony_ci			kfree(rule);
72562306a36Sopenharmony_ci		}
72662306a36Sopenharmony_ci	}
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci	return rc;
72962306a36Sopenharmony_ci}
73062306a36Sopenharmony_ci
73162306a36Sopenharmony_cistatic int sja1105_find_vlid(struct sja1105_private *priv, int port,
73262306a36Sopenharmony_ci			     struct sja1105_key *key)
73362306a36Sopenharmony_ci{
73462306a36Sopenharmony_ci	struct sja1105_vl_lookup_entry *vl_lookup;
73562306a36Sopenharmony_ci	struct sja1105_table *table;
73662306a36Sopenharmony_ci	int i;
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci	if (WARN_ON(key->type != SJA1105_KEY_VLAN_AWARE_VL &&
73962306a36Sopenharmony_ci		    key->type != SJA1105_KEY_VLAN_UNAWARE_VL))
74062306a36Sopenharmony_ci		return -1;
74162306a36Sopenharmony_ci
74262306a36Sopenharmony_ci	table = &priv->static_config.tables[BLK_IDX_VL_LOOKUP];
74362306a36Sopenharmony_ci	vl_lookup = table->entries;
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci	for (i = 0; i < table->entry_count; i++) {
74662306a36Sopenharmony_ci		if (key->type == SJA1105_KEY_VLAN_AWARE_VL) {
74762306a36Sopenharmony_ci			if (vl_lookup[i].port == port &&
74862306a36Sopenharmony_ci			    vl_lookup[i].macaddr == key->vl.dmac &&
74962306a36Sopenharmony_ci			    vl_lookup[i].vlanid == key->vl.vid &&
75062306a36Sopenharmony_ci			    vl_lookup[i].vlanprior == key->vl.pcp)
75162306a36Sopenharmony_ci				return i;
75262306a36Sopenharmony_ci		} else {
75362306a36Sopenharmony_ci			if (vl_lookup[i].port == port &&
75462306a36Sopenharmony_ci			    vl_lookup[i].macaddr == key->vl.dmac)
75562306a36Sopenharmony_ci				return i;
75662306a36Sopenharmony_ci		}
75762306a36Sopenharmony_ci	}
75862306a36Sopenharmony_ci
75962306a36Sopenharmony_ci	return -1;
76062306a36Sopenharmony_ci}
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ciint sja1105_vl_stats(struct sja1105_private *priv, int port,
76362306a36Sopenharmony_ci		     struct sja1105_rule *rule, struct flow_stats *stats,
76462306a36Sopenharmony_ci		     struct netlink_ext_ack *extack)
76562306a36Sopenharmony_ci{
76662306a36Sopenharmony_ci	const struct sja1105_regs *regs = priv->info->regs;
76762306a36Sopenharmony_ci	u8 buf[SJA1105_SIZE_VL_STATUS] = {0};
76862306a36Sopenharmony_ci	u64 unreleased;
76962306a36Sopenharmony_ci	u64 timingerr;
77062306a36Sopenharmony_ci	u64 lengtherr;
77162306a36Sopenharmony_ci	int vlid, rc;
77262306a36Sopenharmony_ci	u64 pkts;
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_ci	if (rule->vl.type != SJA1105_VL_TIME_TRIGGERED)
77562306a36Sopenharmony_ci		return 0;
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	vlid = sja1105_find_vlid(priv, port, &rule->key);
77862306a36Sopenharmony_ci	if (vlid < 0)
77962306a36Sopenharmony_ci		return 0;
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	rc = sja1105_xfer_buf(priv, SPI_READ, regs->vl_status + 2 * vlid, buf,
78262306a36Sopenharmony_ci			      SJA1105_SIZE_VL_STATUS);
78362306a36Sopenharmony_ci	if (rc) {
78462306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "SPI access failed");
78562306a36Sopenharmony_ci		return rc;
78662306a36Sopenharmony_ci	}
78762306a36Sopenharmony_ci
78862306a36Sopenharmony_ci	sja1105_unpack(buf, &timingerr,  31, 16, SJA1105_SIZE_VL_STATUS);
78962306a36Sopenharmony_ci	sja1105_unpack(buf, &unreleased, 15,  0, SJA1105_SIZE_VL_STATUS);
79062306a36Sopenharmony_ci	sja1105_unpack(buf, &lengtherr,  47, 32, SJA1105_SIZE_VL_STATUS);
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	pkts = timingerr + unreleased + lengtherr;
79362306a36Sopenharmony_ci
79462306a36Sopenharmony_ci	flow_stats_update(stats, 0, pkts - rule->vl.stats.pkts, 0,
79562306a36Sopenharmony_ci			  jiffies - rule->vl.stats.lastused,
79662306a36Sopenharmony_ci			  FLOW_ACTION_HW_STATS_IMMEDIATE);
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci	rule->vl.stats.pkts = pkts;
79962306a36Sopenharmony_ci	rule->vl.stats.lastused = jiffies;
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ci	return 0;
80262306a36Sopenharmony_ci}
803