drop_monitor: Convert to using devlink tracepoint
[linux-2.6-microblaze.git] / net / core / drop_monitor.c
index 9704522..c14278f 100644 (file)
 #include <linux/bitops.h>
 #include <linux/slab.h>
 #include <linux/module.h>
-#include <net/drop_monitor.h>
 #include <net/genetlink.h>
 #include <net/netevent.h>
 #include <net/flow_offload.h>
+#include <net/devlink.h>
 
 #include <trace/events/skb.h>
 #include <trace/events/napi.h>
+#include <trace/events/devlink.h>
 
 #include <asm/unaligned.h>
 
@@ -107,6 +108,13 @@ static enum net_dm_alert_mode net_dm_alert_mode = NET_DM_ALERT_MODE_SUMMARY;
 static u32 net_dm_trunc_len;
 static u32 net_dm_queue_len = 1000;
 
+struct net_dm_hw_metadata {
+       const char *trap_group_name;
+       const char *trap_name;
+       struct net_device *input_dev;
+       const struct flow_action_cookie *fa_cookie;
+};
+
 struct net_dm_alert_ops {
        void (*kfree_skb_probe)(void *ignore, struct sk_buff *skb,
                                void *location);
@@ -116,6 +124,9 @@ struct net_dm_alert_ops {
        void (*hw_work_item_func)(struct work_struct *work);
        void (*hw_probe)(struct sk_buff *skb,
                         const struct net_dm_hw_metadata *hw_metadata);
+       void (*hw_trap_probe)(void *ignore, const struct devlink *devlink,
+                             struct sk_buff *skb,
+                             const struct devlink_trap_metadata *metadata);
 };
 
 struct net_dm_skb_cb {
@@ -474,12 +485,57 @@ out:
        spin_unlock_irqrestore(&hw_data->lock, flags);
 }
 
+static void
+net_dm_hw_trap_summary_probe(void *ignore, const struct devlink *devlink,
+                            struct sk_buff *skb,
+                            const struct devlink_trap_metadata *metadata)
+{
+       struct net_dm_hw_entries *hw_entries;
+       struct net_dm_hw_entry *hw_entry;
+       struct per_cpu_dm_data *hw_data;
+       unsigned long flags;
+       int i;
+
+       hw_data = this_cpu_ptr(&dm_hw_cpu_data);
+       spin_lock_irqsave(&hw_data->lock, flags);
+       hw_entries = hw_data->hw_entries;
+
+       if (!hw_entries)
+               goto out;
+
+       for (i = 0; i < hw_entries->num_entries; i++) {
+               hw_entry = &hw_entries->entries[i];
+               if (!strncmp(hw_entry->trap_name, metadata->trap_name,
+                            NET_DM_MAX_HW_TRAP_NAME_LEN - 1)) {
+                       hw_entry->count++;
+                       goto out;
+               }
+       }
+       if (WARN_ON_ONCE(hw_entries->num_entries == dm_hit_limit))
+               goto out;
+
+       hw_entry = &hw_entries->entries[hw_entries->num_entries];
+       strlcpy(hw_entry->trap_name, metadata->trap_name,
+               NET_DM_MAX_HW_TRAP_NAME_LEN - 1);
+       hw_entry->count = 1;
+       hw_entries->num_entries++;
+
+       if (!timer_pending(&hw_data->send_timer)) {
+               hw_data->send_timer.expires = jiffies + dm_delay * HZ;
+               add_timer(&hw_data->send_timer);
+       }
+
+out:
+       spin_unlock_irqrestore(&hw_data->lock, flags);
+}
+
 static const struct net_dm_alert_ops net_dm_alert_summary_ops = {
        .kfree_skb_probe        = trace_kfree_skb_hit,
        .napi_poll_probe        = trace_napi_poll_hit,
        .work_item_func         = send_dm_alert,
        .hw_work_item_func      = net_dm_hw_summary_work,
        .hw_probe               = net_dm_hw_summary_probe,
+       .hw_trap_probe          = net_dm_hw_trap_summary_probe,
 };
 
 static void net_dm_packet_trace_kfree_skb_hit(void *ignore,
@@ -858,6 +914,54 @@ free_hw_metadata:
        return NULL;
 }
 
+static struct net_dm_hw_metadata *
+net_dm_hw_metadata_copy(const struct devlink_trap_metadata *metadata)
+{
+       const struct flow_action_cookie *fa_cookie;
+       struct net_dm_hw_metadata *hw_metadata;
+       const char *trap_group_name;
+       const char *trap_name;
+
+       hw_metadata = kzalloc(sizeof(*hw_metadata), GFP_ATOMIC);
+       if (!hw_metadata)
+               return NULL;
+
+       trap_group_name = kstrdup(metadata->trap_group_name, GFP_ATOMIC);
+       if (!trap_group_name)
+               goto free_hw_metadata;
+       hw_metadata->trap_group_name = trap_group_name;
+
+       trap_name = kstrdup(metadata->trap_name, GFP_ATOMIC);
+       if (!trap_name)
+               goto free_trap_group;
+       hw_metadata->trap_name = trap_name;
+
+       if (metadata->fa_cookie) {
+               size_t cookie_size = sizeof(*fa_cookie) +
+                                    metadata->fa_cookie->cookie_len;
+
+               fa_cookie = kmemdup(metadata->fa_cookie, cookie_size,
+                                   GFP_ATOMIC);
+               if (!fa_cookie)
+                       goto free_trap_name;
+               hw_metadata->fa_cookie = fa_cookie;
+       }
+
+       hw_metadata->input_dev = metadata->input_dev;
+       if (hw_metadata->input_dev)
+               dev_hold(hw_metadata->input_dev);
+
+       return hw_metadata;
+
+free_trap_name:
+       kfree(trap_name);
+free_trap_group:
+       kfree(trap_group_name);
+free_hw_metadata:
+       kfree(hw_metadata);
+       return NULL;
+}
+
 static void
 net_dm_hw_metadata_free(const struct net_dm_hw_metadata *hw_metadata)
 {
@@ -970,12 +1074,61 @@ free:
        consume_skb(nskb);
 }
 
+static void
+net_dm_hw_trap_packet_probe(void *ignore, const struct devlink *devlink,
+                           struct sk_buff *skb,
+                           const struct devlink_trap_metadata *metadata)
+{
+       struct net_dm_hw_metadata *n_hw_metadata;
+       ktime_t tstamp = ktime_get_real();
+       struct per_cpu_dm_data *hw_data;
+       struct sk_buff *nskb;
+       unsigned long flags;
+
+       if (!skb_mac_header_was_set(skb))
+               return;
+
+       nskb = skb_clone(skb, GFP_ATOMIC);
+       if (!nskb)
+               return;
+
+       n_hw_metadata = net_dm_hw_metadata_copy(metadata);
+       if (!n_hw_metadata)
+               goto free;
+
+       NET_DM_SKB_CB(nskb)->hw_metadata = n_hw_metadata;
+       nskb->tstamp = tstamp;
+
+       hw_data = this_cpu_ptr(&dm_hw_cpu_data);
+
+       spin_lock_irqsave(&hw_data->drop_queue.lock, flags);
+       if (skb_queue_len(&hw_data->drop_queue) < net_dm_queue_len)
+               __skb_queue_tail(&hw_data->drop_queue, nskb);
+       else
+               goto unlock_free;
+       spin_unlock_irqrestore(&hw_data->drop_queue.lock, flags);
+
+       schedule_work(&hw_data->dm_alert_work);
+
+       return;
+
+unlock_free:
+       spin_unlock_irqrestore(&hw_data->drop_queue.lock, flags);
+       u64_stats_update_begin(&hw_data->stats.syncp);
+       hw_data->stats.dropped++;
+       u64_stats_update_end(&hw_data->stats.syncp);
+       net_dm_hw_metadata_free(n_hw_metadata);
+free:
+       consume_skb(nskb);
+}
+
 static const struct net_dm_alert_ops net_dm_alert_packet_ops = {
        .kfree_skb_probe        = net_dm_packet_trace_kfree_skb_hit,
        .napi_poll_probe        = net_dm_packet_trace_napi_poll_hit,
        .work_item_func         = net_dm_packet_work,
        .hw_work_item_func      = net_dm_hw_packet_work,
        .hw_probe               = net_dm_hw_packet_probe,
+       .hw_trap_probe          = net_dm_hw_trap_packet_probe,
 };
 
 static const struct net_dm_alert_ops *net_dm_alert_ops_arr[] = {
@@ -983,25 +1136,32 @@ static const struct net_dm_alert_ops *net_dm_alert_ops_arr[] = {
        [NET_DM_ALERT_MODE_PACKET]      = &net_dm_alert_packet_ops,
 };
 
-void net_dm_hw_report(struct sk_buff *skb,
-                     const struct net_dm_hw_metadata *hw_metadata)
+#if IS_ENABLED(CONFIG_NET_DEVLINK)
+static int net_dm_hw_probe_register(const struct net_dm_alert_ops *ops)
 {
-       rcu_read_lock();
-
-       if (!monitor_hw)
-               goto out;
+       return register_trace_devlink_trap_report(ops->hw_trap_probe, NULL);
+}
 
-       net_dm_alert_ops_arr[net_dm_alert_mode]->hw_probe(skb, hw_metadata);
+static void net_dm_hw_probe_unregister(const struct net_dm_alert_ops *ops)
+{
+       unregister_trace_devlink_trap_report(ops->hw_trap_probe, NULL);
+       tracepoint_synchronize_unregister();
+}
+#else
+static int net_dm_hw_probe_register(const struct net_dm_alert_ops *ops)
+{
+       return -EOPNOTSUPP;
+}
 
-out:
-       rcu_read_unlock();
+static void net_dm_hw_probe_unregister(const struct net_dm_alert_ops *ops)
+{
 }
-EXPORT_SYMBOL_GPL(net_dm_hw_report);
+#endif
 
 static int net_dm_hw_monitor_start(struct netlink_ext_ack *extack)
 {
        const struct net_dm_alert_ops *ops;
-       int cpu;
+       int cpu, rc;
 
        if (monitor_hw) {
                NL_SET_ERR_MSG_MOD(extack, "Hardware monitoring already enabled");
@@ -1025,13 +1185,24 @@ static int net_dm_hw_monitor_start(struct netlink_ext_ack *extack)
                kfree(hw_entries);
        }
 
+       rc = net_dm_hw_probe_register(ops);
+       if (rc) {
+               NL_SET_ERR_MSG_MOD(extack, "Failed to connect probe to devlink_trap_probe() tracepoint");
+               goto err_module_put;
+       }
+
        monitor_hw = true;
 
        return 0;
+
+err_module_put:
+       module_put(THIS_MODULE);
+       return rc;
 }
 
 static void net_dm_hw_monitor_stop(struct netlink_ext_ack *extack)
 {
+       const struct net_dm_alert_ops *ops;
        int cpu;
 
        if (!monitor_hw) {
@@ -1039,12 +1210,11 @@ static void net_dm_hw_monitor_stop(struct netlink_ext_ack *extack)
                return;
        }
 
+       ops = net_dm_alert_ops_arr[net_dm_alert_mode];
+
        monitor_hw = false;
 
-       /* After this call returns we are guaranteed that no CPU is processing
-        * any hardware drops.
-        */
-       synchronize_rcu();
+       net_dm_hw_probe_unregister(ops);
 
        for_each_possible_cpu(cpu) {
                struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu);