| // 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(sizeof(*e), GFP_KERNEL); |
| 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(sizeof(*rule), GFP_KERNEL); |
| 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(sizeof(*rule), GFP_KERNEL); |
| 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 = kcalloc(num_entries, |
| sizeof(struct action_gate_entry), |
| GFP_KERNEL); |
| 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; |
| } |