diff options
Diffstat (limited to 'drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c')
-rw-r--r-- | drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c | 856 |
1 files changed, 856 insertions, 0 deletions
diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c new file mode 100644 index 0000000..9859814 --- /dev/null +++ b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c @@ -0,0 +1,856 @@ +/* Copyright 2014-2015 Freescale Semiconductor Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Freescale Semiconductor nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "dpni.h" /* DPNI_LINK_OPT_* */ +#include "dpaa2-eth.h" + +/* To be kept in sync with dpni_statistics */ +static char dpaa2_ethtool_stats[][ETH_GSTRING_LEN] = { + "rx frames", + "rx bytes", + "rx mcast frames", + "rx mcast bytes", + "rx bcast frames", + "rx bcast bytes", + "tx frames", + "tx bytes", + "tx mcast frames", + "tx mcast bytes", + "tx bcast frames", + "tx bcast bytes", + "rx filtered frames", + "rx discarded frames", + "rx nobuffer discards", + "tx discarded frames", + "tx confirmed frames", +}; + +#define DPAA2_ETH_NUM_STATS ARRAY_SIZE(dpaa2_ethtool_stats) + +/* To be kept in sync with 'struct dpaa2_eth_drv_stats' */ +static char dpaa2_ethtool_extras[][ETH_GSTRING_LEN] = { + /* per-cpu stats */ + + "tx conf frames", + "tx conf bytes", + "tx sg frames", + "tx sg bytes", + "rx sg frames", + "rx sg bytes", + /* how many times we had to retry the enqueue command */ + "enqueue portal busy", + + /* Channel stats */ + /* How many times we had to retry the volatile dequeue command */ + "dequeue portal busy", + "channel pull errors", + /* Number of notifications received */ + "cdan", + "tx congestion state", +#ifdef CONFIG_FSL_QBMAN_DEBUG + /* FQ stats */ + "rx pending frames", + "rx pending bytes", + "tx conf pending frames", + "tx conf pending bytes", + "buffer count" +#endif +}; + +#define DPAA2_ETH_NUM_EXTRA_STATS ARRAY_SIZE(dpaa2_ethtool_extras) + +static void dpaa2_eth_get_drvinfo(struct net_device *net_dev, + struct ethtool_drvinfo *drvinfo) +{ + strlcpy(drvinfo->driver, KBUILD_MODNAME, sizeof(drvinfo->driver)); + strlcpy(drvinfo->version, dpaa2_eth_drv_version, + sizeof(drvinfo->version)); + strlcpy(drvinfo->fw_version, "N/A", sizeof(drvinfo->fw_version)); + strlcpy(drvinfo->bus_info, dev_name(net_dev->dev.parent->parent), + sizeof(drvinfo->bus_info)); +} + +static int dpaa2_eth_get_settings(struct net_device *net_dev, + struct ethtool_cmd *cmd) +{ + struct dpni_link_state state = {0}; + int err = 0; + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + + err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state); + if (err) { + netdev_err(net_dev, "ERROR %d getting link state", err); + goto out; + } + + /* At the moment, we have no way of interrogating the DPMAC + * from the DPNI side - and for that matter there may exist + * no DPMAC at all. So for now we just don't report anything + * beyond the DPNI attributes. + */ + if (state.options & DPNI_LINK_OPT_AUTONEG) + cmd->autoneg = AUTONEG_ENABLE; + if (!(state.options & DPNI_LINK_OPT_HALF_DUPLEX)) + cmd->duplex = DUPLEX_FULL; + ethtool_cmd_speed_set(cmd, state.rate); + +out: + return err; +} + +static int dpaa2_eth_set_settings(struct net_device *net_dev, + struct ethtool_cmd *cmd) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct dpni_link_state state = {0}; + struct dpni_link_cfg cfg = {0}; + int err = 0; + + netdev_dbg(net_dev, "Setting link parameters..."); + + /* Need to interrogate on link state to get flow control params */ + err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state); + if (err) { + netdev_err(net_dev, "ERROR %d getting link state", err); + goto out; + } + + cfg.options = state.options; + cfg.rate = ethtool_cmd_speed(cmd); + if (cmd->autoneg == AUTONEG_ENABLE) + cfg.options |= DPNI_LINK_OPT_AUTONEG; + else + cfg.options &= ~DPNI_LINK_OPT_AUTONEG; + if (cmd->duplex == DUPLEX_HALF) + cfg.options |= DPNI_LINK_OPT_HALF_DUPLEX; + else + cfg.options &= ~DPNI_LINK_OPT_HALF_DUPLEX; + + err = dpni_set_link_cfg(priv->mc_io, 0, priv->mc_token, &cfg); + if (err) + /* ethtool will be loud enough if we return an error; no point + * in putting our own error message on the console by default + */ + netdev_dbg(net_dev, "ERROR %d setting link cfg", err); + +out: + return err; +} + +static void dpaa2_eth_get_pauseparam(struct net_device *net_dev, + struct ethtool_pauseparam *pause) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct dpni_link_state state = {0}; + int err; + + err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state); + if (err) + netdev_dbg(net_dev, "ERROR %d getting link state", err); + + /* for now, pause frames autonegotiation is not separate */ + pause->autoneg = !!(state.options & DPNI_LINK_OPT_AUTONEG); + pause->rx_pause = !!(state.options & DPNI_LINK_OPT_PAUSE); + pause->tx_pause = pause->rx_pause ^ + !!(state.options & DPNI_LINK_OPT_ASYM_PAUSE); +} + +static int dpaa2_eth_set_pauseparam(struct net_device *net_dev, + struct ethtool_pauseparam *pause) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct dpni_link_state state = {0}; + struct dpni_link_cfg cfg = {0}; + u32 current_tx_pause; + int err = 0; + + err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state); + if (err) { + netdev_dbg(net_dev, "ERROR %d getting link state", err); + goto out; + } + + cfg.rate = state.rate; + cfg.options = state.options; + current_tx_pause = !!(cfg.options & DPNI_LINK_OPT_PAUSE) ^ + !!(cfg.options & DPNI_LINK_OPT_ASYM_PAUSE); + + if (pause->autoneg != !!(state.options & DPNI_LINK_OPT_AUTONEG)) + netdev_warn(net_dev, + "WARN: Can't change pause frames autoneg separately\n"); + + if (pause->rx_pause) + cfg.options |= DPNI_LINK_OPT_PAUSE; + else + cfg.options &= ~DPNI_LINK_OPT_PAUSE; + + if (pause->rx_pause ^ pause->tx_pause) + cfg.options |= DPNI_LINK_OPT_ASYM_PAUSE; + else + cfg.options &= ~DPNI_LINK_OPT_ASYM_PAUSE; + + err = dpni_set_link_cfg(priv->mc_io, 0, priv->mc_token, &cfg); + if (err) { + /* ethtool will be loud enough if we return an error; no point + * in putting our own error message on the console by default + */ + netdev_dbg(net_dev, "ERROR %d setting link cfg", err); + goto out; + } + + /* Enable / disable taildrops if Tx pause frames have changed */ + if (current_tx_pause == pause->tx_pause) + goto out; + + err = setup_fqs_taildrop(priv, !pause->tx_pause); + if (err) + netdev_dbg(net_dev, "ERROR %d configuring taildrop", err); + + priv->tx_pause_frames = pause->tx_pause; +out: + return err; +} + +static void dpaa2_eth_get_strings(struct net_device *netdev, u32 stringset, + u8 *data) +{ + u8 *p = data; + int i; + + switch (stringset) { + case ETH_SS_STATS: + for (i = 0; i < DPAA2_ETH_NUM_STATS; i++) { + strlcpy(p, dpaa2_ethtool_stats[i], ETH_GSTRING_LEN); + p += ETH_GSTRING_LEN; + } + for (i = 0; i < DPAA2_ETH_NUM_EXTRA_STATS; i++) { + strlcpy(p, dpaa2_ethtool_extras[i], ETH_GSTRING_LEN); + p += ETH_GSTRING_LEN; + } + break; + } +} + +static int dpaa2_eth_get_sset_count(struct net_device *net_dev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: /* ethtool_get_stats(), ethtool_get_drvinfo() */ + return DPAA2_ETH_NUM_STATS + DPAA2_ETH_NUM_EXTRA_STATS; + default: + return -EOPNOTSUPP; + } +} + +/** Fill in hardware counters, as returned by MC. + */ +static void dpaa2_eth_get_ethtool_stats(struct net_device *net_dev, + struct ethtool_stats *stats, + u64 *data) +{ + int i = 0; /* Current index in the data array */ + int j = 0, k, err; + union dpni_statistics dpni_stats; + +#ifdef CONFIG_FSL_QBMAN_DEBUG + u32 fcnt, bcnt; + u32 fcnt_rx_total = 0, fcnt_tx_total = 0; + u32 bcnt_rx_total = 0, bcnt_tx_total = 0; + u32 buf_cnt; +#endif + u64 cdan = 0; + u64 portal_busy = 0, pull_err = 0; + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct dpaa2_eth_drv_stats *extras; + struct dpaa2_eth_ch_stats *ch_stats; + + memset(data, 0, + sizeof(u64) * (DPAA2_ETH_NUM_STATS + DPAA2_ETH_NUM_EXTRA_STATS)); + + /* Print standard counters, from DPNI statistics */ + for (j = 0; j <= 2; j++) { + err = dpni_get_statistics(priv->mc_io, 0, priv->mc_token, + j, &dpni_stats); + if (err != 0) + netdev_warn(net_dev, "Err %d getting DPNI stats page %d", + err, j); + + switch (j) { + case 0: + *(data + i++) = dpni_stats.page_0.ingress_all_frames; + *(data + i++) = dpni_stats.page_0.ingress_all_bytes; + *(data + i++) = dpni_stats.page_0.ingress_multicast_frames; + *(data + i++) = dpni_stats.page_0.ingress_multicast_bytes; + *(data + i++) = dpni_stats.page_0.ingress_broadcast_frames; + *(data + i++) = dpni_stats.page_0.ingress_broadcast_bytes; + break; + case 1: + *(data + i++) = dpni_stats.page_1.egress_all_frames; + *(data + i++) = dpni_stats.page_1.egress_all_bytes; + *(data + i++) = dpni_stats.page_1.egress_multicast_frames; + *(data + i++) = dpni_stats.page_1.egress_multicast_bytes; + *(data + i++) = dpni_stats.page_1.egress_broadcast_frames; + *(data + i++) = dpni_stats.page_1.egress_broadcast_bytes; + break; + case 2: + *(data + i++) = dpni_stats.page_2.ingress_filtered_frames; + *(data + i++) = dpni_stats.page_2.ingress_discarded_frames; + *(data + i++) = dpni_stats.page_2.ingress_nobuffer_discards; + *(data + i++) = dpni_stats.page_2.egress_discarded_frames; + *(data + i++) = dpni_stats.page_2.egress_confirmed_frames; + break; + default: + break; + } + } + + /* Print per-cpu extra stats */ + for_each_online_cpu(k) { + extras = per_cpu_ptr(priv->percpu_extras, k); + for (j = 0; j < sizeof(*extras) / sizeof(__u64); j++) + *((__u64 *)data + i + j) += *((__u64 *)extras + j); + } + + i += j; + + /* We may be using fewer DPIOs than actual CPUs */ + for (j = 0; j < priv->num_channels; j++) { + ch_stats = &priv->channel[j]->stats; + cdan += ch_stats->cdan; + portal_busy += ch_stats->dequeue_portal_busy; + pull_err += ch_stats->pull_err; + } + + *(data + i++) = portal_busy; + *(data + i++) = pull_err; + *(data + i++) = cdan; + + *(data + i++) = dpaa2_cscn_state_congested(priv->cscn_mem); + +#ifdef CONFIG_FSL_QBMAN_DEBUG + for (j = 0; j < priv->num_fqs; j++) { + /* Print FQ instantaneous counts */ + err = dpaa2_io_query_fq_count(NULL, priv->fq[j].fqid, + &fcnt, &bcnt); + if (err) { + netdev_warn(net_dev, "FQ query error %d", err); + return; + } + + if (priv->fq[j].type == DPAA2_TX_CONF_FQ) { + fcnt_tx_total += fcnt; + bcnt_tx_total += bcnt; + } else { + fcnt_rx_total += fcnt; + bcnt_rx_total += bcnt; + } + } + + *(data + i++) = fcnt_rx_total; + *(data + i++) = bcnt_rx_total; + *(data + i++) = fcnt_tx_total; + *(data + i++) = bcnt_tx_total; + + err = dpaa2_io_query_bp_count(NULL, priv->bpid, &buf_cnt); + if (err) { + netdev_warn(net_dev, "Buffer count query error %d\n", err); + return; + } + *(data + i++) = buf_cnt; +#endif +} + +static int cls_key_off(struct dpaa2_eth_priv *priv, int prot, int field) +{ + int i, off = 0; + + for (i = 0; i < priv->num_hash_fields; i++) { + if (priv->hash_fields[i].cls_prot == prot && + priv->hash_fields[i].cls_field == field) + return off; + off += priv->hash_fields[i].size; + } + + return -1; +} + +static u8 cls_key_size(struct dpaa2_eth_priv *priv) +{ + u8 i, size = 0; + + for (i = 0; i < priv->num_hash_fields; i++) + size += priv->hash_fields[i].size; + + return size; +} + +void check_cls_support(struct dpaa2_eth_priv *priv) +{ + u8 key_size = cls_key_size(priv); + struct device *dev = priv->net_dev->dev.parent; + + if (dpaa2_eth_hash_enabled(priv)) { + if (priv->dpni_attrs.fs_key_size < key_size) { + dev_info(dev, "max_dist_key_size = %d, expected %d. Hashing and steering are disabled\n", + priv->dpni_attrs.fs_key_size, + key_size); + goto disable_fs; + } + if (priv->num_hash_fields > DPKG_MAX_NUM_OF_EXTRACTS) { + dev_info(dev, "Too many key fields (max = %d). Hashing and steering are disabled\n", + DPKG_MAX_NUM_OF_EXTRACTS); + goto disable_fs; + } + } + + if (dpaa2_eth_fs_enabled(priv)) { + if (!dpaa2_eth_hash_enabled(priv)) { + dev_info(dev, "Insufficient queues. Steering is disabled\n"); + goto disable_fs; + } + + if (!dpaa2_eth_fs_mask_enabled(priv)) { + dev_info(dev, "Key masks not supported. Steering is disabled\n"); + goto disable_fs; + } + } + + return; + +disable_fs: + priv->dpni_attrs.options |= DPNI_OPT_NO_FS; + priv->dpni_attrs.options &= ~DPNI_OPT_HAS_KEY_MASKING; +} + +static int prep_l4_rule(struct dpaa2_eth_priv *priv, + struct ethtool_tcpip4_spec *l4_value, + struct ethtool_tcpip4_spec *l4_mask, + void *key, void *mask, u8 l4_proto) +{ + int offset; + + if (l4_mask->tos) { + netdev_err(priv->net_dev, "ToS is not supported for IPv4 L4\n"); + return -EOPNOTSUPP; + } + + if (l4_mask->ip4src) { + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_SRC); + *(u32 *)(key + offset) = l4_value->ip4src; + *(u32 *)(mask + offset) = l4_mask->ip4src; + } + + if (l4_mask->ip4dst) { + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_DST); + *(u32 *)(key + offset) = l4_value->ip4dst; + *(u32 *)(mask + offset) = l4_mask->ip4dst; + } + + if (l4_mask->psrc) { + offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_SRC); + *(u32 *)(key + offset) = l4_value->psrc; + *(u32 *)(mask + offset) = l4_mask->psrc; + } + + if (l4_mask->pdst) { + offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_DST); + *(u32 *)(key + offset) = l4_value->pdst; + *(u32 *)(mask + offset) = l4_mask->pdst; + } + + /* Only apply the rule for the user-specified L4 protocol + * and if ethertype matches IPv4 + */ + offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_TYPE); + *(u16 *)(key + offset) = htons(ETH_P_IP); + *(u16 *)(mask + offset) = 0xFFFF; + + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_PROTO); + *(u8 *)(key + offset) = l4_proto; + *(u8 *)(mask + offset) = 0xFF; + + /* TODO: check IP version */ + + return 0; +} + +static int prep_eth_rule(struct dpaa2_eth_priv *priv, + struct ethhdr *eth_value, struct ethhdr *eth_mask, + void *key, void *mask) +{ + int offset; + + if (eth_mask->h_proto) { + netdev_err(priv->net_dev, "Ethertype is not supported!\n"); + return -EOPNOTSUPP; + } + + if (!is_zero_ether_addr(eth_mask->h_source)) { + offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_SA); + ether_addr_copy(key + offset, eth_value->h_source); + ether_addr_copy(mask + offset, eth_mask->h_source); + } + + if (!is_zero_ether_addr(eth_mask->h_dest)) { + offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_DA); + ether_addr_copy(key + offset, eth_value->h_dest); + ether_addr_copy(mask + offset, eth_mask->h_dest); + } + + return 0; +} + +static int prep_user_ip_rule(struct dpaa2_eth_priv *priv, + struct ethtool_usrip4_spec *uip_value, + struct ethtool_usrip4_spec *uip_mask, + void *key, void *mask) +{ + int offset; + + if (uip_mask->tos) + return -EOPNOTSUPP; + + if (uip_mask->ip4src) { + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_SRC); + *(u32 *)(key + offset) = uip_value->ip4src; + *(u32 *)(mask + offset) = uip_mask->ip4src; + } + + if (uip_mask->ip4dst) { + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_DST); + *(u32 *)(key + offset) = uip_value->ip4dst; + *(u32 *)(mask + offset) = uip_mask->ip4dst; + } + + if (uip_mask->proto) { + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_PROTO); + *(u32 *)(key + offset) = uip_value->proto; + *(u32 *)(mask + offset) = uip_mask->proto; + } + if (uip_mask->l4_4_bytes) { + offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_SRC); + *(u16 *)(key + offset) = uip_value->l4_4_bytes << 16; + *(u16 *)(mask + offset) = uip_mask->l4_4_bytes << 16; + + offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_DST); + *(u16 *)(key + offset) = uip_value->l4_4_bytes & 0xFFFF; + *(u16 *)(mask + offset) = uip_mask->l4_4_bytes & 0xFFFF; + } + + /* Ethertype must be IP */ + offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_TYPE); + *(u16 *)(key + offset) = htons(ETH_P_IP); + *(u16 *)(mask + offset) = 0xFFFF; + + return 0; +} + +static int prep_ext_rule(struct dpaa2_eth_priv *priv, + struct ethtool_flow_ext *ext_value, + struct ethtool_flow_ext *ext_mask, + void *key, void *mask) +{ + int offset; + + if (ext_mask->vlan_etype) + return -EOPNOTSUPP; + + if (ext_mask->vlan_tci) { + offset = cls_key_off(priv, NET_PROT_VLAN, NH_FLD_VLAN_TCI); + *(u16 *)(key + offset) = ext_value->vlan_tci; + *(u16 *)(mask + offset) = ext_mask->vlan_tci; + } + + return 0; +} + +static int prep_mac_ext_rule(struct dpaa2_eth_priv *priv, + struct ethtool_flow_ext *ext_value, + struct ethtool_flow_ext *ext_mask, + void *key, void *mask) +{ + int offset; + + if (!is_zero_ether_addr(ext_mask->h_dest)) { + offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_DA); + ether_addr_copy(key + offset, ext_value->h_dest); + ether_addr_copy(mask + offset, ext_mask->h_dest); + } + + return 0; +} + +static int prep_cls_rule(struct net_device *net_dev, + struct ethtool_rx_flow_spec *fs, + void *key) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + const u8 key_size = cls_key_size(priv); + void *msk = key + key_size; + int err; + + memset(key, 0, key_size * 2); + + switch (fs->flow_type & 0xff) { + case TCP_V4_FLOW: + err = prep_l4_rule(priv, &fs->h_u.tcp_ip4_spec, + &fs->m_u.tcp_ip4_spec, key, msk, + IPPROTO_TCP); + break; + case UDP_V4_FLOW: + err = prep_l4_rule(priv, &fs->h_u.udp_ip4_spec, + &fs->m_u.udp_ip4_spec, key, msk, + IPPROTO_UDP); + break; + case SCTP_V4_FLOW: + err = prep_l4_rule(priv, &fs->h_u.sctp_ip4_spec, + &fs->m_u.sctp_ip4_spec, key, msk, + IPPROTO_SCTP); + break; + case ETHER_FLOW: + err = prep_eth_rule(priv, &fs->h_u.ether_spec, + &fs->m_u.ether_spec, key, msk); + break; + case IP_USER_FLOW: + err = prep_user_ip_rule(priv, &fs->h_u.usr_ip4_spec, + &fs->m_u.usr_ip4_spec, key, msk); + break; + default: + /* TODO: AH, ESP */ + return -EOPNOTSUPP; + } + if (err) + return err; + + if (fs->flow_type & FLOW_EXT) { + err = prep_ext_rule(priv, &fs->h_ext, &fs->m_ext, key, msk); + if (err) + return err; + } + + if (fs->flow_type & FLOW_MAC_EXT) { + err = prep_mac_ext_rule(priv, &fs->h_ext, &fs->m_ext, key, msk); + if (err) + return err; + } + + return 0; +} + +static int del_cls(struct net_device *net_dev, int location); + +static int do_cls(struct net_device *net_dev, + struct ethtool_rx_flow_spec *fs, + bool add) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct device *dev = net_dev->dev.parent; + const int rule_cnt = dpaa2_eth_fs_count(priv); + struct dpni_rule_cfg rule_cfg; + struct dpni_fs_action_cfg fs_act = { 0 }; + void *dma_mem; + int err = 0; + + if (!dpaa2_eth_fs_enabled(priv)) { + netdev_err(net_dev, "dev does not support steering!\n"); + /* dev doesn't support steering */ + return -EOPNOTSUPP; + } + + if ((fs->ring_cookie != RX_CLS_FLOW_DISC && + fs->ring_cookie >= dpaa2_eth_queue_count(priv)) || + fs->location >= rule_cnt) + return -EINVAL; + + /* When adding a new rule, check if location if available, + * and if not free the existing table entry before inserting + * the new one + */ + if (add && (priv->cls_rule[fs->location].in_use == true)) + del_cls(net_dev, fs->location); + + memset(&rule_cfg, 0, sizeof(rule_cfg)); + rule_cfg.key_size = cls_key_size(priv); + + /* allocate twice the key size, for the actual key and for mask */ + dma_mem = kzalloc(rule_cfg.key_size * 2, GFP_DMA | GFP_KERNEL); + if (!dma_mem) + return -ENOMEM; + + err = prep_cls_rule(net_dev, fs, dma_mem); + if (err) + goto err_free_mem; + + rule_cfg.key_iova = dma_map_single(dev, dma_mem, + rule_cfg.key_size * 2, + DMA_TO_DEVICE); + + rule_cfg.mask_iova = rule_cfg.key_iova + rule_cfg.key_size; + + if (fs->ring_cookie == RX_CLS_FLOW_DISC) + fs_act.options |= DPNI_FS_OPT_DISCARD; + else + fs_act.flow_id = fs->ring_cookie; + + if (add) + err = dpni_add_fs_entry(priv->mc_io, 0, priv->mc_token, + 0, fs->location, &rule_cfg, &fs_act); + else + err = dpni_remove_fs_entry(priv->mc_io, 0, priv->mc_token, + 0, &rule_cfg); + + dma_unmap_single(dev, rule_cfg.key_iova, + rule_cfg.key_size * 2, DMA_TO_DEVICE); + + if (err) + netdev_err(net_dev, "dpaa2_add/remove_cls() error %d\n", err); + +err_free_mem: + kfree(dma_mem); + + return err; +} + +static int add_cls(struct net_device *net_dev, + struct ethtool_rx_flow_spec *fs) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + int err; + + err = do_cls(net_dev, fs, true); + if (err) + return err; + + priv->cls_rule[fs->location].in_use = true; + priv->cls_rule[fs->location].fs = *fs; + + return 0; +} + +static int del_cls(struct net_device *net_dev, int location) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + int err; + + err = do_cls(net_dev, &priv->cls_rule[location].fs, false); + if (err) + return err; + + priv->cls_rule[location].in_use = false; + + return 0; +} + +static int dpaa2_eth_set_rxnfc(struct net_device *net_dev, + struct ethtool_rxnfc *rxnfc) +{ + int err = 0; + + switch (rxnfc->cmd) { + case ETHTOOL_SRXCLSRLINS: + err = add_cls(net_dev, &rxnfc->fs); + break; + + case ETHTOOL_SRXCLSRLDEL: + err = del_cls(net_dev, rxnfc->fs.location); + break; + + default: + err = -EOPNOTSUPP; + } + + return err; +} + +static int dpaa2_eth_get_rxnfc(struct net_device *net_dev, + struct ethtool_rxnfc *rxnfc, u32 *rule_locs) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + const int rule_cnt = dpaa2_eth_fs_count(priv); + int i, j; + + switch (rxnfc->cmd) { + case ETHTOOL_GRXFH: + /* we purposely ignore cmd->flow_type, because the hashing key + * is the same (and fixed) for all protocols + */ + rxnfc->data = priv->rx_flow_hash; + break; + + case ETHTOOL_GRXRINGS: + rxnfc->data = dpaa2_eth_queue_count(priv); + break; + + case ETHTOOL_GRXCLSRLCNT: + for (i = 0, rxnfc->rule_cnt = 0; i < rule_cnt; i++) + if (priv->cls_rule[i].in_use) + rxnfc->rule_cnt++; + rxnfc->data = rule_cnt; + break; + + case ETHTOOL_GRXCLSRULE: + if (!priv->cls_rule[rxnfc->fs.location].in_use) + return -EINVAL; + + rxnfc->fs = priv->cls_rule[rxnfc->fs.location].fs; + break; + + case ETHTOOL_GRXCLSRLALL: + for (i = 0, j = 0; i < rule_cnt; i++) { + if (!priv->cls_rule[i].in_use) + continue; + if (j == rxnfc->rule_cnt) + return -EMSGSIZE; + rule_locs[j++] = i; + } + rxnfc->rule_cnt = j; + rxnfc->data = rule_cnt; + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +const struct ethtool_ops dpaa2_ethtool_ops = { + .get_drvinfo = dpaa2_eth_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_settings = dpaa2_eth_get_settings, + .set_settings = dpaa2_eth_set_settings, + .get_pauseparam = dpaa2_eth_get_pauseparam, + .set_pauseparam = dpaa2_eth_set_pauseparam, + .get_sset_count = dpaa2_eth_get_sset_count, + .get_ethtool_stats = dpaa2_eth_get_ethtool_stats, + .get_strings = dpaa2_eth_get_strings, + .get_rxnfc = dpaa2_eth_get_rxnfc, + .set_rxnfc = dpaa2_eth_set_rxnfc, +}; |