Merge branch 'net-offloaded-stats'
authorDavid S. Miller <davem@davemloft.net>
Mon, 19 Sep 2016 02:33:47 +0000 (22:33 -0400)
committerDavid S. Miller <davem@davemloft.net>
Mon, 19 Sep 2016 02:33:47 +0000 (22:33 -0400)
Jiri Pirko says:

====================
net: return offloaded stats as default and expose original sw stats

The problem we try to handle is about offloaded forwarded packets
which are not seen by kernel. Let me try to draw it:

    port1                       port2 (HW stats are counted here)
      \                          /
       \                        /
        \                      /
         --(A)---- ASIC --(B)--
                    |
                   (C)
                    |
                   CPU (SW stats are counted here)

Now we have couple of flows for TX and RX (direction does not matter here):

1) port1->A->ASIC->C->CPU

   For this flow, HW and SW stats are equal.

2) port1->A->ASIC->C->CPU->C->ASIC->B->port2

   For this flow, HW and SW stats are equal.

3) port1->A->ASIC->B->port2

   For this flow, SW stats are 0.

The purpose of this patchset is to provide facility for user to
find out the difference between flows 1+2 and 3. In other words, user
will be able to see the statistics for the slow-path (through kernel).

Also note that HW stats are what someone calls "accumulated" stats.
Every packet counted by SW is also counted by HW. Not the other way around.

As a default the accumulated stats (HW) will be exposed to user
so the userspace apps can react properly.

This patchset add the SW stats (flows 1+2) under offload related stats, so
in the future we can expose other offload related stat in a similar way.

---
v9->v10:
- patch 2/3
 - removed unnecessary ()s as pointed out by Nik
v8->v9:
- patch 2/3
 - add using of idxattr and prividx
v7->v8:
- patch 2/3
 - move helping const from uapi to rtnetlink
 - cancel driver xstat nesting if it is empty
v6->v7:
- patch 1/3:
 - ndo interface changed to get the wanted stats type as an input.
 - change commit message.
- patch 2/3:
 - create a nesting for offloaded stat and put SW stats under it.
 - change the ndo call to indicate which offload stats we wants.
 - change commit message.
- patch 3/3:
 - change ndo implementation to match the changes in the previous patches.
 - change commit message.
v5->v6:
- patch 2/4 was dropped as requested by Roopa
- patch 1/3:
 - comment changed to indicate that default stats are combined stats
 - commit massage changed
- patch 2/3: (previously 3/4)
 - SW stats return nothing if there is no SW stats ndo
v4->v5:
- updated cover letter
- patch3/4:
  - using memcpy directly to copy stats as requested by DaveM
v3->v4:
- patch1/4:
  - fixed "return ()" pointed out by EricD
- patch2/4:
  - fixed if_nlmsg_size as pointed out by EricD
v2->v3:
- patch1/4:
  - added dev_have_sw_stats helper
- patch2/4:
  - avoided memcpy as requested by DaveM
- patch3/4:
  - use new dev_have_sw_stats helper
v1->v2:
- patch3/4:
  - fixed NULL initialization
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/mellanox/mlxsw/spectrum.c
drivers/net/ethernet/mellanox/mlxsw/spectrum.h
include/linux/netdevice.h
include/uapi/linux/if_link.h
net/core/rtnetlink.c

index 27bbcaf..171f8dd 100644 (file)
@@ -819,9 +819,9 @@ err_span_port_mtu_update:
        return err;
 }
 
-static struct rtnl_link_stats64 *
-mlxsw_sp_port_get_stats64(struct net_device *dev,
-                         struct rtnl_link_stats64 *stats)
+int
+mlxsw_sp_port_get_sw_stats64(const struct net_device *dev,
+                            struct rtnl_link_stats64 *stats)
 {
        struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
        struct mlxsw_sp_port_pcpu_stats *p;
@@ -848,6 +848,107 @@ mlxsw_sp_port_get_stats64(struct net_device *dev,
                tx_dropped      += p->tx_dropped;
        }
        stats->tx_dropped       = tx_dropped;
+       return 0;
+}
+
+bool mlxsw_sp_port_has_offload_stats(int attr_id)
+{
+       switch (attr_id) {
+       case IFLA_OFFLOAD_XSTATS_CPU_HIT:
+               return true;
+       }
+
+       return false;
+}
+
+int mlxsw_sp_port_get_offload_stats(int attr_id, const struct net_device *dev,
+                                   void *sp)
+{
+       switch (attr_id) {
+       case IFLA_OFFLOAD_XSTATS_CPU_HIT:
+               return mlxsw_sp_port_get_sw_stats64(dev, sp);
+       }
+
+       return -EINVAL;
+}
+
+static int mlxsw_sp_port_get_stats_raw(struct net_device *dev, int grp,
+                                      int prio, char *ppcnt_pl)
+{
+       struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
+
+       mlxsw_reg_ppcnt_pack(ppcnt_pl, mlxsw_sp_port->local_port, grp, prio);
+       return mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(ppcnt), ppcnt_pl);
+}
+
+static int mlxsw_sp_port_get_hw_stats(struct net_device *dev,
+                                     struct rtnl_link_stats64 *stats)
+{
+       char ppcnt_pl[MLXSW_REG_PPCNT_LEN];
+       int err;
+
+       err = mlxsw_sp_port_get_stats_raw(dev, MLXSW_REG_PPCNT_IEEE_8023_CNT,
+                                         0, ppcnt_pl);
+       if (err)
+               goto out;
+
+       stats->tx_packets =
+               mlxsw_reg_ppcnt_a_frames_transmitted_ok_get(ppcnt_pl);
+       stats->rx_packets =
+               mlxsw_reg_ppcnt_a_frames_received_ok_get(ppcnt_pl);
+       stats->tx_bytes =
+               mlxsw_reg_ppcnt_a_octets_transmitted_ok_get(ppcnt_pl);
+       stats->rx_bytes =
+               mlxsw_reg_ppcnt_a_octets_received_ok_get(ppcnt_pl);
+       stats->multicast =
+               mlxsw_reg_ppcnt_a_multicast_frames_received_ok_get(ppcnt_pl);
+
+       stats->rx_crc_errors =
+               mlxsw_reg_ppcnt_a_frame_check_sequence_errors_get(ppcnt_pl);
+       stats->rx_frame_errors =
+               mlxsw_reg_ppcnt_a_alignment_errors_get(ppcnt_pl);
+
+       stats->rx_length_errors = (
+               mlxsw_reg_ppcnt_a_in_range_length_errors_get(ppcnt_pl) +
+               mlxsw_reg_ppcnt_a_out_of_range_length_field_get(ppcnt_pl) +
+               mlxsw_reg_ppcnt_a_frame_too_long_errors_get(ppcnt_pl));
+
+       stats->rx_errors = (stats->rx_crc_errors +
+               stats->rx_frame_errors + stats->rx_length_errors);
+
+out:
+       return err;
+}
+
+static void update_stats_cache(struct work_struct *work)
+{
+       struct mlxsw_sp_port *mlxsw_sp_port =
+               container_of(work, struct mlxsw_sp_port,
+                            hw_stats.update_dw.work);
+
+       if (!netif_carrier_ok(mlxsw_sp_port->dev))
+               goto out;
+
+       mlxsw_sp_port_get_hw_stats(mlxsw_sp_port->dev,
+                                  mlxsw_sp_port->hw_stats.cache);
+
+out:
+       mlxsw_core_schedule_dw(&mlxsw_sp_port->hw_stats.update_dw,
+                              MLXSW_HW_STATS_UPDATE_TIME);
+}
+
+/* Return the stats from a cache that is updated periodically,
+ * as this function might get called in an atomic context.
+ */
+static struct rtnl_link_stats64 *
+mlxsw_sp_port_get_stats64(struct net_device *dev,
+                         struct rtnl_link_stats64 *stats)
+{
+       struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
+
+       memcpy(stats, mlxsw_sp_port->hw_stats.cache, sizeof(*stats));
+
        return stats;
 }
 
@@ -1209,6 +1310,8 @@ static const struct net_device_ops mlxsw_sp_port_netdev_ops = {
        .ndo_set_mac_address    = mlxsw_sp_port_set_mac_address,
        .ndo_change_mtu         = mlxsw_sp_port_change_mtu,
        .ndo_get_stats64        = mlxsw_sp_port_get_stats64,
+       .ndo_has_offload_stats  = mlxsw_sp_port_has_offload_stats,
+       .ndo_get_offload_stats  = mlxsw_sp_port_get_offload_stats,
        .ndo_vlan_rx_add_vid    = mlxsw_sp_port_add_vid,
        .ndo_vlan_rx_kill_vid   = mlxsw_sp_port_kill_vid,
        .ndo_neigh_construct    = mlxsw_sp_router_neigh_construct,
@@ -1547,8 +1650,6 @@ static void __mlxsw_sp_port_get_stats(struct net_device *dev,
                                      enum mlxsw_reg_ppcnt_grp grp, int prio,
                                      u64 *data, int data_index)
 {
-       struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
-       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
        struct mlxsw_sp_port_hw_stats *hw_stats;
        char ppcnt_pl[MLXSW_REG_PPCNT_LEN];
        int i, len;
@@ -1557,8 +1658,7 @@ static void __mlxsw_sp_port_get_stats(struct net_device *dev,
        err = mlxsw_sp_get_hw_stats_by_group(&hw_stats, &len, grp);
        if (err)
                return;
-       mlxsw_reg_ppcnt_pack(ppcnt_pl, mlxsw_sp_port->local_port, grp, prio);
-       err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(ppcnt), ppcnt_pl);
+       mlxsw_sp_port_get_stats_raw(dev, grp, prio, ppcnt_pl);
        for (i = 0; i < len; i++)
                data[data_index + i] = !err ? hw_stats[i].getter(ppcnt_pl) : 0;
 }
@@ -2145,6 +2245,16 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
                goto err_alloc_stats;
        }
 
+       mlxsw_sp_port->hw_stats.cache =
+               kzalloc(sizeof(*mlxsw_sp_port->hw_stats.cache), GFP_KERNEL);
+
+       if (!mlxsw_sp_port->hw_stats.cache) {
+               err = -ENOMEM;
+               goto err_alloc_hw_stats;
+       }
+       INIT_DELAYED_WORK(&mlxsw_sp_port->hw_stats.update_dw,
+                         &update_stats_cache);
+
        dev->netdev_ops = &mlxsw_sp_port_netdev_ops;
        dev->ethtool_ops = &mlxsw_sp_port_ethtool_ops;
 
@@ -2245,6 +2355,7 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
                goto err_core_port_init;
        }
 
+       mlxsw_core_schedule_dw(&mlxsw_sp_port->hw_stats.update_dw, 0);
        return 0;
 
 err_core_port_init:
@@ -2265,6 +2376,8 @@ err_port_system_port_mapping_set:
 err_dev_addr_init:
        mlxsw_sp_port_swid_set(mlxsw_sp_port, MLXSW_PORT_SWID_DISABLED_PORT);
 err_port_swid_set:
+       kfree(mlxsw_sp_port->hw_stats.cache);
+err_alloc_hw_stats:
        free_percpu(mlxsw_sp_port->pcpu_stats);
 err_alloc_stats:
        kfree(mlxsw_sp_port->untagged_vlans);
@@ -2281,6 +2394,7 @@ static void mlxsw_sp_port_remove(struct mlxsw_sp *mlxsw_sp, u8 local_port)
 
        if (!mlxsw_sp_port)
                return;
+       cancel_delayed_work_sync(&mlxsw_sp_port->hw_stats.update_dw);
        mlxsw_core_port_fini(&mlxsw_sp_port->core_port);
        unregister_netdev(mlxsw_sp_port->dev); /* This calls ndo_stop */
        mlxsw_sp->ports[local_port] = NULL;
@@ -2290,6 +2404,7 @@ static void mlxsw_sp_port_remove(struct mlxsw_sp *mlxsw_sp, u8 local_port)
        mlxsw_sp_port_swid_set(mlxsw_sp_port, MLXSW_PORT_SWID_DISABLED_PORT);
        mlxsw_sp_port_module_unmap(mlxsw_sp, mlxsw_sp_port->local_port);
        free_percpu(mlxsw_sp_port->pcpu_stats);
+       kfree(mlxsw_sp_port->hw_stats.cache);
        kfree(mlxsw_sp_port->untagged_vlans);
        kfree(mlxsw_sp_port->active_vlans);
        WARN_ON_ONCE(!list_empty(&mlxsw_sp_port->vports_list));
index 969c250..49f4caf 100644 (file)
@@ -361,6 +361,11 @@ struct mlxsw_sp_port {
        struct list_head vports_list;
        /* TC handles */
        struct list_head mall_tc_list;
+       struct {
+               #define MLXSW_HW_STATS_UPDATE_TIME HZ
+               struct rtnl_link_stats64 *cache;
+               struct delayed_work update_dw;
+       } hw_stats;
 };
 
 struct mlxsw_sp_port *mlxsw_sp_port_lower_dev_hold(struct net_device *dev);
index 2095b6a..a10d8d1 100644 (file)
@@ -924,6 +924,14 @@ struct netdev_xdp {
  *     3. Update dev->stats asynchronously and atomically, and define
  *        neither operation.
  *
+ * bool (*ndo_has_offload_stats)(int attr_id)
+ *     Return true if this device supports offload stats of this attr_id.
+ *
+ * int (*ndo_get_offload_stats)(int attr_id, const struct net_device *dev,
+ *     void *attr_data)
+ *     Get statistics for offload operations by attr_id. Write it into the
+ *     attr_data pointer.
+ *
  * int (*ndo_vlan_rx_add_vid)(struct net_device *dev, __be16 proto, u16 vid);
  *     If device supports VLAN filtering this function is called when a
  *     VLAN id is registered.
@@ -1155,6 +1163,10 @@ struct net_device_ops {
 
        struct rtnl_link_stats64* (*ndo_get_stats64)(struct net_device *dev,
                                                     struct rtnl_link_stats64 *storage);
+       bool                    (*ndo_has_offload_stats)(int attr_id);
+       int                     (*ndo_get_offload_stats)(int attr_id,
+                                                        const struct net_device *dev,
+                                                        void *attr_data);
        struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);
 
        int                     (*ndo_vlan_rx_add_vid)(struct net_device *dev,
index 9bf3aec..2351776 100644 (file)
@@ -826,6 +826,7 @@ enum {
        IFLA_STATS_LINK_64,
        IFLA_STATS_LINK_XSTATS,
        IFLA_STATS_LINK_XSTATS_SLAVE,
+       IFLA_STATS_LINK_OFFLOAD_XSTATS,
        __IFLA_STATS_MAX,
 };
 
@@ -845,6 +846,14 @@ enum {
 };
 #define LINK_XSTATS_TYPE_MAX (__LINK_XSTATS_TYPE_MAX - 1)
 
+/* These are stats embedded into IFLA_STATS_LINK_OFFLOAD_XSTATS */
+enum {
+       IFLA_OFFLOAD_XSTATS_UNSPEC,
+       IFLA_OFFLOAD_XSTATS_CPU_HIT, /* struct rtnl_link_stats64 */
+       __IFLA_OFFLOAD_XSTATS_MAX
+};
+#define IFLA_OFFLOAD_XSTATS_MAX (__IFLA_OFFLOAD_XSTATS_MAX - 1)
+
 /* XDP section */
 
 enum {
index 937e459..0dbae42 100644 (file)
@@ -3577,6 +3577,91 @@ static bool stats_attr_valid(unsigned int mask, int attrid, int idxattr)
               (!idxattr || idxattr == attrid);
 }
 
+#define IFLA_OFFLOAD_XSTATS_FIRST (IFLA_OFFLOAD_XSTATS_UNSPEC + 1)
+static int rtnl_get_offload_stats_attr_size(int attr_id)
+{
+       switch (attr_id) {
+       case IFLA_OFFLOAD_XSTATS_CPU_HIT:
+               return sizeof(struct rtnl_link_stats64);
+       }
+
+       return 0;
+}
+
+static int rtnl_get_offload_stats(struct sk_buff *skb, struct net_device *dev,
+                                 int *prividx)
+{
+       struct nlattr *attr = NULL;
+       int attr_id, size;
+       void *attr_data;
+       int err;
+
+       if (!(dev->netdev_ops && dev->netdev_ops->ndo_has_offload_stats &&
+             dev->netdev_ops->ndo_get_offload_stats))
+               return -ENODATA;
+
+       for (attr_id = IFLA_OFFLOAD_XSTATS_FIRST;
+            attr_id <= IFLA_OFFLOAD_XSTATS_MAX; attr_id++) {
+               if (attr_id < *prividx)
+                       continue;
+
+               size = rtnl_get_offload_stats_attr_size(attr_id);
+               if (!size)
+                       continue;
+
+               if (!dev->netdev_ops->ndo_has_offload_stats(attr_id))
+                       continue;
+
+               attr = nla_reserve_64bit(skb, attr_id, size,
+                                        IFLA_OFFLOAD_XSTATS_UNSPEC);
+               if (!attr)
+                       goto nla_put_failure;
+
+               attr_data = nla_data(attr);
+               memset(attr_data, 0, size);
+               err = dev->netdev_ops->ndo_get_offload_stats(attr_id, dev,
+                                                            attr_data);
+               if (err)
+                       goto get_offload_stats_failure;
+       }
+
+       if (!attr)
+               return -ENODATA;
+
+       *prividx = 0;
+       return 0;
+
+nla_put_failure:
+       err = -EMSGSIZE;
+get_offload_stats_failure:
+       *prividx = attr_id;
+       return err;
+}
+
+static int rtnl_get_offload_stats_size(const struct net_device *dev)
+{
+       int nla_size = 0;
+       int attr_id;
+       int size;
+
+       if (!(dev->netdev_ops && dev->netdev_ops->ndo_has_offload_stats &&
+             dev->netdev_ops->ndo_get_offload_stats))
+               return 0;
+
+       for (attr_id = IFLA_OFFLOAD_XSTATS_FIRST;
+            attr_id <= IFLA_OFFLOAD_XSTATS_MAX; attr_id++) {
+               if (!dev->netdev_ops->ndo_has_offload_stats(attr_id))
+                       continue;
+               size = rtnl_get_offload_stats_attr_size(attr_id);
+               nla_size += nla_total_size_64bit(size);
+       }
+
+       if (nla_size != 0)
+               nla_size += nla_total_size(0);
+
+       return nla_size;
+}
+
 static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
                               int type, u32 pid, u32 seq, u32 change,
                               unsigned int flags, unsigned int filter_mask,
@@ -3586,6 +3671,7 @@ static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
        struct nlmsghdr *nlh;
        struct nlattr *attr;
        int s_prividx = *prividx;
+       int err;
 
        ASSERT_RTNL();
 
@@ -3614,8 +3700,6 @@ static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
                const struct rtnl_link_ops *ops = dev->rtnl_link_ops;
 
                if (ops && ops->fill_linkxstats) {
-                       int err;
-
                        *idxattr = IFLA_STATS_LINK_XSTATS;
                        attr = nla_nest_start(skb,
                                              IFLA_STATS_LINK_XSTATS);
@@ -3639,8 +3723,6 @@ static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
                if (master)
                        ops = master->rtnl_link_ops;
                if (ops && ops->fill_linkxstats) {
-                       int err;
-
                        *idxattr = IFLA_STATS_LINK_XSTATS_SLAVE;
                        attr = nla_nest_start(skb,
                                              IFLA_STATS_LINK_XSTATS_SLAVE);
@@ -3655,6 +3737,24 @@ static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
                }
        }
 
+       if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_OFFLOAD_XSTATS,
+                            *idxattr)) {
+               *idxattr = IFLA_STATS_LINK_OFFLOAD_XSTATS;
+               attr = nla_nest_start(skb, IFLA_STATS_LINK_OFFLOAD_XSTATS);
+               if (!attr)
+                       goto nla_put_failure;
+
+               err = rtnl_get_offload_stats(skb, dev, prividx);
+               if (err == -ENODATA)
+                       nla_nest_cancel(skb, attr);
+               else
+                       nla_nest_end(skb, attr);
+
+               if (err && err != -ENODATA)
+                       goto nla_put_failure;
+               *idxattr = 0;
+       }
+
        nlmsg_end(skb, nlh);
 
        return 0;
@@ -3708,6 +3808,9 @@ static size_t if_nlmsg_stats_size(const struct net_device *dev,
                }
        }
 
+       if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_OFFLOAD_XSTATS, 0))
+               size += rtnl_get_offload_stats_size(dev);
+
        return size;
 }