// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2022 Gerhard Engleder */ #include "tsnep.h" #define ETHER_TYPE_FULL_MASK ((__force __be16)~0) static void tsnep_enable_rule(struct tsnep_adapter *adapter, struct tsnep_rxnfc_rule *rule) { u8 rx_assign; void __iomem *addr; rx_assign = TSNEP_RX_ASSIGN_ACTIVE; rx_assign |= (rule->queue_index << TSNEP_RX_ASSIGN_QUEUE_SHIFT) & TSNEP_RX_ASSIGN_QUEUE_MASK; addr = adapter->addr + TSNEP_RX_ASSIGN_ETHER_TYPE + TSNEP_RX_ASSIGN_ETHER_TYPE_OFFSET * rule->location; iowrite16(rule->filter.ether_type, addr); /* enable rule after all settings are done */ addr = adapter->addr + TSNEP_RX_ASSIGN + TSNEP_RX_ASSIGN_OFFSET * rule->location; iowrite8(rx_assign, addr); } static void tsnep_disable_rule(struct tsnep_adapter *adapter, struct tsnep_rxnfc_rule *rule) { void __iomem *addr; addr = adapter->addr + TSNEP_RX_ASSIGN + TSNEP_RX_ASSIGN_OFFSET * rule->location; iowrite8(0, addr); } static struct tsnep_rxnfc_rule *tsnep_get_rule(struct tsnep_adapter *adapter, int location) { struct tsnep_rxnfc_rule *rule; list_for_each_entry(rule, &adapter->rxnfc_rules, list) { if (rule->location == location) return rule; if (rule->location > location) break; } return NULL; } static void tsnep_add_rule(struct tsnep_adapter *adapter, struct tsnep_rxnfc_rule *rule) { struct tsnep_rxnfc_rule *pred, *cur; tsnep_enable_rule(adapter, rule); pred = NULL; list_for_each_entry(cur, &adapter->rxnfc_rules, list) { if (cur->location >= rule->location) break; pred = cur; } list_add(&rule->list, pred ? &pred->list : &adapter->rxnfc_rules); adapter->rxnfc_count++; } static void tsnep_delete_rule(struct tsnep_adapter *adapter, struct tsnep_rxnfc_rule *rule) { tsnep_disable_rule(adapter, rule); list_del(&rule->list); adapter->rxnfc_count--; kfree(rule); } static void tsnep_flush_rules(struct tsnep_adapter *adapter) { struct tsnep_rxnfc_rule *rule, *tmp; mutex_lock(&adapter->rxnfc_lock); list_for_each_entry_safe(rule, tmp, &adapter->rxnfc_rules, list) tsnep_delete_rule(adapter, rule); mutex_unlock(&adapter->rxnfc_lock); } int tsnep_rxnfc_get_rule(struct tsnep_adapter *adapter, struct ethtool_rxnfc *cmd) { struct ethtool_rx_flow_spec *fsp = &cmd->fs; struct tsnep_rxnfc_rule *rule = NULL; cmd->data = adapter->rxnfc_max; mutex_lock(&adapter->rxnfc_lock); rule = tsnep_get_rule(adapter, fsp->location); if (!rule) { mutex_unlock(&adapter->rxnfc_lock); return -ENOENT; } fsp->flow_type = ETHER_FLOW; fsp->ring_cookie = rule->queue_index; if (rule->filter.type == TSNEP_RXNFC_ETHER_TYPE) { fsp->h_u.ether_spec.h_proto = htons(rule->filter.ether_type); fsp->m_u.ether_spec.h_proto = ETHER_TYPE_FULL_MASK; } mutex_unlock(&adapter->rxnfc_lock); return 0; } int tsnep_rxnfc_get_all(struct tsnep_adapter *adapter, struct ethtool_rxnfc *cmd, u32 *rule_locs) { struct tsnep_rxnfc_rule *rule; int count = 0; cmd->data = adapter->rxnfc_max; mutex_lock(&adapter->rxnfc_lock); list_for_each_entry(rule, &adapter->rxnfc_rules, list) { if (count == cmd->rule_cnt) { mutex_unlock(&adapter->rxnfc_lock); return -EMSGSIZE; } rule_locs[count] = rule->location; count++; } mutex_unlock(&adapter->rxnfc_lock); cmd->rule_cnt = count; return 0; } static int tsnep_rxnfc_find_location(struct tsnep_adapter *adapter) { struct tsnep_rxnfc_rule *tmp; int location = 0; list_for_each_entry(tmp, &adapter->rxnfc_rules, list) { if (tmp->location == location) location++; else return location; } if (location >= adapter->rxnfc_max) return -ENOSPC; return location; } static void tsnep_rxnfc_init_rule(struct tsnep_rxnfc_rule *rule, const struct ethtool_rx_flow_spec *fsp) { INIT_LIST_HEAD(&rule->list); rule->queue_index = fsp->ring_cookie; rule->location = fsp->location; rule->filter.type = TSNEP_RXNFC_ETHER_TYPE; rule->filter.ether_type = ntohs(fsp->h_u.ether_spec.h_proto); } static int tsnep_rxnfc_check_rule(struct tsnep_adapter *adapter, struct tsnep_rxnfc_rule *rule) { struct net_device *dev = adapter->netdev; struct tsnep_rxnfc_rule *tmp; list_for_each_entry(tmp, &adapter->rxnfc_rules, list) { if (!memcmp(&rule->filter, &tmp->filter, sizeof(rule->filter)) && tmp->location != rule->location) { netdev_dbg(dev, "rule already exists\n"); return -EEXIST; } } return 0; } int tsnep_rxnfc_add_rule(struct tsnep_adapter *adapter, struct ethtool_rxnfc *cmd) { struct net_device *netdev = adapter->netdev; struct ethtool_rx_flow_spec *fsp = (struct ethtool_rx_flow_spec *)&cmd->fs; struct tsnep_rxnfc_rule *rule, *old_rule; int retval; /* only EtherType is supported */ if (fsp->flow_type != ETHER_FLOW || !is_zero_ether_addr(fsp->m_u.ether_spec.h_dest) || !is_zero_ether_addr(fsp->m_u.ether_spec.h_source) || fsp->m_u.ether_spec.h_proto != ETHER_TYPE_FULL_MASK) { netdev_dbg(netdev, "only ethernet protocol is supported\n"); return -EOPNOTSUPP; } if (fsp->ring_cookie > (TSNEP_RX_ASSIGN_QUEUE_MASK >> TSNEP_RX_ASSIGN_QUEUE_SHIFT)) { netdev_dbg(netdev, "invalid action\n"); return -EINVAL; } if (fsp->location != RX_CLS_LOC_ANY && fsp->location >= adapter->rxnfc_max) { netdev_dbg(netdev, "invalid location\n"); return -EINVAL; } rule = kzalloc(sizeof(*rule), GFP_KERNEL); if (!rule) return -ENOMEM; mutex_lock(&adapter->rxnfc_lock); if (fsp->location == RX_CLS_LOC_ANY) { retval = tsnep_rxnfc_find_location(adapter); if (retval < 0) goto failed; fsp->location = retval; } tsnep_rxnfc_init_rule(rule, fsp); retval = tsnep_rxnfc_check_rule(adapter, rule); if (retval) goto failed; old_rule = tsnep_get_rule(adapter, fsp->location); if (old_rule) tsnep_delete_rule(adapter, old_rule); tsnep_add_rule(adapter, rule); mutex_unlock(&adapter->rxnfc_lock); return 0; failed: mutex_unlock(&adapter->rxnfc_lock); kfree(rule); return retval; } int tsnep_rxnfc_del_rule(struct tsnep_adapter *adapter, struct ethtool_rxnfc *cmd) { struct ethtool_rx_flow_spec *fsp = (struct ethtool_rx_flow_spec *)&cmd->fs; struct tsnep_rxnfc_rule *rule; mutex_lock(&adapter->rxnfc_lock); rule = tsnep_get_rule(adapter, fsp->location); if (!rule) { mutex_unlock(&adapter->rxnfc_lock); return -ENOENT; } tsnep_delete_rule(adapter, rule); mutex_unlock(&adapter->rxnfc_lock); return 0; } int tsnep_rxnfc_init(struct tsnep_adapter *adapter) { int i; /* disable all rules */ for (i = 0; i < adapter->rxnfc_max; i += sizeof(u32) / TSNEP_RX_ASSIGN_OFFSET) iowrite32(0, adapter->addr + TSNEP_RX_ASSIGN + i); return 0; } void tsnep_rxnfc_cleanup(struct tsnep_adapter *adapter) { tsnep_flush_rules(adapter); }