xref: /openbmc/linux/net/core/drop_monitor.c (revision d37cf9b63113f13d742713881ce691fc615d8b3b)
109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
29a8afc8dSNeil Horman /*
39a8afc8dSNeil Horman  * Monitoring code for network dropped packet alerts
49a8afc8dSNeil Horman  *
59a8afc8dSNeil Horman  * Copyright (C) 2009 Neil Horman <nhorman@tuxdriver.com>
69a8afc8dSNeil Horman  */
79a8afc8dSNeil Horman 
8e005d193SJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9e005d193SJoe Perches 
109a8afc8dSNeil Horman #include <linux/netdevice.h>
119a8afc8dSNeil Horman #include <linux/etherdevice.h>
129a8afc8dSNeil Horman #include <linux/string.h>
139a8afc8dSNeil Horman #include <linux/if_arp.h>
149a8afc8dSNeil Horman #include <linux/inetdevice.h>
159a8afc8dSNeil Horman #include <linux/inet.h>
169a8afc8dSNeil Horman #include <linux/interrupt.h>
179a8afc8dSNeil Horman #include <linux/netpoll.h>
189a8afc8dSNeil Horman #include <linux/sched.h>
199a8afc8dSNeil Horman #include <linux/delay.h>
209a8afc8dSNeil Horman #include <linux/types.h>
219a8afc8dSNeil Horman #include <linux/workqueue.h>
229a8afc8dSNeil Horman #include <linux/netlink.h>
239a8afc8dSNeil Horman #include <linux/net_dropmon.h>
24071c0fc6SJohannes Berg #include <linux/bitfield.h>
259a8afc8dSNeil Horman #include <linux/percpu.h>
269a8afc8dSNeil Horman #include <linux/timer.h>
279a8afc8dSNeil Horman #include <linux/bitops.h>
285a0e3ad6STejun Heo #include <linux/slab.h>
29cad456d5SNeil Horman #include <linux/module.h>
309a8afc8dSNeil Horman #include <net/genetlink.h>
314ea7e386SNeil Horman #include <net/netevent.h>
32742b8cceSJiri Pirko #include <net/flow_offload.h>
33071c0fc6SJohannes Berg #include <net/dropreason.h>
345855357cSIdo Schimmel #include <net/devlink.h>
359a8afc8dSNeil Horman 
36ad8d75ffSSteven Rostedt #include <trace/events/skb.h>
379cbc1cb8SDavid S. Miller #include <trace/events/napi.h>
388ee2267aSIdo Schimmel #include <trace/events/devlink.h>
399a8afc8dSNeil Horman 
409a8afc8dSNeil Horman #include <asm/unaligned.h>
419a8afc8dSNeil Horman 
429a8afc8dSNeil Horman #define TRACE_ON 1
439a8afc8dSNeil Horman #define TRACE_OFF 0
449a8afc8dSNeil Horman 
459a8afc8dSNeil Horman /*
469a8afc8dSNeil Horman  * Globals, our netlink socket pointer
479a8afc8dSNeil Horman  * and the work handle that will send up
489a8afc8dSNeil Horman  * netlink alerts
499a8afc8dSNeil Horman  */
504ea7e386SNeil Horman static int trace_state = TRACE_OFF;
51edd3d007SIdo Schimmel static bool monitor_hw;
52dbf896b7SIdo Schimmel 
53dbf896b7SIdo Schimmel /* net_dm_mutex
54dbf896b7SIdo Schimmel  *
55dbf896b7SIdo Schimmel  * An overall lock guarding every operation coming from userspace.
56dbf896b7SIdo Schimmel  */
57dbf896b7SIdo Schimmel static DEFINE_MUTEX(net_dm_mutex);
589a8afc8dSNeil Horman 
59e9feb580SIdo Schimmel struct net_dm_stats {
60c6cce71eSEric Dumazet 	u64_stats_t dropped;
61e9feb580SIdo Schimmel 	struct u64_stats_sync syncp;
62e9feb580SIdo Schimmel };
63e9feb580SIdo Schimmel 
64d40e1debSIdo Schimmel #define NET_DM_MAX_HW_TRAP_NAME_LEN 40
65d40e1debSIdo Schimmel 
66d40e1debSIdo Schimmel struct net_dm_hw_entry {
67d40e1debSIdo Schimmel 	char trap_name[NET_DM_MAX_HW_TRAP_NAME_LEN];
68d40e1debSIdo Schimmel 	u32 count;
69d40e1debSIdo Schimmel };
70d40e1debSIdo Schimmel 
71d40e1debSIdo Schimmel struct net_dm_hw_entries {
72d40e1debSIdo Schimmel 	u32 num_entries;
73d2afb41aSGustavo A. R. Silva 	struct net_dm_hw_entry entries[];
74d40e1debSIdo Schimmel };
75d40e1debSIdo Schimmel 
769a8afc8dSNeil Horman struct per_cpu_dm_data {
7776ce2f91SWander Lairson Costa 	raw_spinlock_t		lock;	/* Protects 'skb', 'hw_entries' and
78d40e1debSIdo Schimmel 					 * 'send_timer'
79d40e1debSIdo Schimmel 					 */
80d40e1debSIdo Schimmel 	union {
81bec4596bSEric Dumazet 		struct sk_buff			*skb;
82d40e1debSIdo Schimmel 		struct net_dm_hw_entries	*hw_entries;
83d40e1debSIdo Schimmel 	};
84ca30707dSIdo Schimmel 	struct sk_buff_head	drop_queue;
859a8afc8dSNeil Horman 	struct work_struct	dm_alert_work;
869a8afc8dSNeil Horman 	struct timer_list	send_timer;
87e9feb580SIdo Schimmel 	struct net_dm_stats	stats;
889a8afc8dSNeil Horman };
899a8afc8dSNeil Horman 
904ea7e386SNeil Horman struct dm_hw_stat_delta {
915848cc09SNeil Horman 	unsigned long last_rx;
924ea7e386SNeil Horman 	unsigned long last_drop_val;
93b26ef81cSEric Dumazet 	struct rcu_head rcu;
944ea7e386SNeil Horman };
954ea7e386SNeil Horman 
96489111e5SJohannes Berg static struct genl_family net_drop_monitor_family;
979a8afc8dSNeil Horman 
989a8afc8dSNeil Horman static DEFINE_PER_CPU(struct per_cpu_dm_data, dm_cpu_data);
99cac1174fSIdo Schimmel static DEFINE_PER_CPU(struct per_cpu_dm_data, dm_hw_cpu_data);
1009a8afc8dSNeil Horman 
1019a8afc8dSNeil Horman static int dm_hit_limit = 64;
1029a8afc8dSNeil Horman static int dm_delay = 1;
1034ea7e386SNeil Horman static unsigned long dm_hw_check_delta = 2*HZ;
1049a8afc8dSNeil Horman 
10528315f79SIdo Schimmel static enum net_dm_alert_mode net_dm_alert_mode = NET_DM_ALERT_MODE_SUMMARY;
10657986617SIdo Schimmel static u32 net_dm_trunc_len;
10730328d46SIdo Schimmel static u32 net_dm_queue_len = 1000;
10828315f79SIdo Schimmel 
10928315f79SIdo Schimmel struct net_dm_alert_ops {
11028315f79SIdo Schimmel 	void (*kfree_skb_probe)(void *ignore, struct sk_buff *skb,
111c504e5c2SMenglong Dong 				void *location,
112c504e5c2SMenglong Dong 				enum skb_drop_reason reason);
11328315f79SIdo Schimmel 	void (*napi_poll_probe)(void *ignore, struct napi_struct *napi,
11428315f79SIdo Schimmel 				int work, int budget);
11528315f79SIdo Schimmel 	void (*work_item_func)(struct work_struct *work);
1165e58109bSIdo Schimmel 	void (*hw_work_item_func)(struct work_struct *work);
1175855357cSIdo Schimmel 	void (*hw_trap_probe)(void *ignore, const struct devlink *devlink,
1185855357cSIdo Schimmel 			      struct sk_buff *skb,
1195855357cSIdo Schimmel 			      const struct devlink_trap_metadata *metadata);
12028315f79SIdo Schimmel };
12128315f79SIdo Schimmel 
122ca30707dSIdo Schimmel struct net_dm_skb_cb {
1235e58109bSIdo Schimmel 	union {
124a848c05fSIdo Schimmel 		struct devlink_trap_metadata *hw_metadata;
125ca30707dSIdo Schimmel 		void *pc;
126ca30707dSIdo Schimmel 	};
1275cad527dSMenglong Dong 	enum skb_drop_reason reason;
1285e58109bSIdo Schimmel };
129ca30707dSIdo Schimmel 
130ca30707dSIdo Schimmel #define NET_DM_SKB_CB(__skb) ((struct net_dm_skb_cb *)&((__skb)->cb[0]))
131ca30707dSIdo Schimmel 
reset_per_cpu_data(struct per_cpu_dm_data * data)132bec4596bSEric Dumazet static struct sk_buff *reset_per_cpu_data(struct per_cpu_dm_data *data)
1339a8afc8dSNeil Horman {
1349a8afc8dSNeil Horman 	size_t al;
1359a8afc8dSNeil Horman 	struct net_dm_alert_msg *msg;
136683703a2SNeil Horman 	struct nlattr *nla;
1373885ca78SNeil Horman 	struct sk_buff *skb;
138bec4596bSEric Dumazet 	unsigned long flags;
1394200462dSReiter Wolfgang 	void *msg_header;
1409a8afc8dSNeil Horman 
1419a8afc8dSNeil Horman 	al = sizeof(struct net_dm_alert_msg);
1429a8afc8dSNeil Horman 	al += dm_hit_limit * sizeof(struct net_dm_drop_point);
143683703a2SNeil Horman 	al += sizeof(struct nlattr);
144683703a2SNeil Horman 
1453885ca78SNeil Horman 	skb = genlmsg_new(al, GFP_KERNEL);
1463885ca78SNeil Horman 
1474200462dSReiter Wolfgang 	if (!skb)
1484200462dSReiter Wolfgang 		goto err;
1494200462dSReiter Wolfgang 
1504200462dSReiter Wolfgang 	msg_header = genlmsg_put(skb, 0, 0, &net_drop_monitor_family,
1519a8afc8dSNeil Horman 				 0, NET_DM_CMD_ALERT);
1524200462dSReiter Wolfgang 	if (!msg_header) {
1534200462dSReiter Wolfgang 		nlmsg_free(skb);
1544200462dSReiter Wolfgang 		skb = NULL;
1554200462dSReiter Wolfgang 		goto err;
1564200462dSReiter Wolfgang 	}
1573885ca78SNeil Horman 	nla = nla_reserve(skb, NLA_UNSPEC,
1583885ca78SNeil Horman 			  sizeof(struct net_dm_alert_msg));
1594200462dSReiter Wolfgang 	if (!nla) {
1604200462dSReiter Wolfgang 		nlmsg_free(skb);
1614200462dSReiter Wolfgang 		skb = NULL;
1624200462dSReiter Wolfgang 		goto err;
1634200462dSReiter Wolfgang 	}
164683703a2SNeil Horman 	msg = nla_data(nla);
1659a8afc8dSNeil Horman 	memset(msg, 0, al);
1664200462dSReiter Wolfgang 	goto out;
1679a8afc8dSNeil Horman 
1684200462dSReiter Wolfgang err:
1694200462dSReiter Wolfgang 	mod_timer(&data->send_timer, jiffies + HZ / 10);
1704200462dSReiter Wolfgang out:
17176ce2f91SWander Lairson Costa 	raw_spin_lock_irqsave(&data->lock, flags);
172bec4596bSEric Dumazet 	swap(data->skb, skb);
17376ce2f91SWander Lairson Costa 	raw_spin_unlock_irqrestore(&data->lock, flags);
174bec4596bSEric Dumazet 
1753b48ab22SReiter Wolfgang 	if (skb) {
1763b48ab22SReiter Wolfgang 		struct nlmsghdr *nlh = (struct nlmsghdr *)skb->data;
1773b48ab22SReiter Wolfgang 		struct genlmsghdr *gnlh = (struct genlmsghdr *)nlmsg_data(nlh);
1783b48ab22SReiter Wolfgang 
1793b48ab22SReiter Wolfgang 		genlmsg_end(skb, genlmsg_data(gnlh));
1803b48ab22SReiter Wolfgang 	}
1813b48ab22SReiter Wolfgang 
182bec4596bSEric Dumazet 	return skb;
1833885ca78SNeil Horman }
1843885ca78SNeil Horman 
18585bae4bdSstephen hemminger static const struct genl_multicast_group dropmon_mcgrps[] = {
186e036a325SIdo Schimmel 	{ .name = "events", .cap_sys_admin = 1 },
187e5dcecbaSJohannes Berg };
188e5dcecbaSJohannes Berg 
send_dm_alert(struct work_struct * work)189bec4596bSEric Dumazet static void send_dm_alert(struct work_struct *work)
1909a8afc8dSNeil Horman {
1919a8afc8dSNeil Horman 	struct sk_buff *skb;
192bec4596bSEric Dumazet 	struct per_cpu_dm_data *data;
1939a8afc8dSNeil Horman 
194bec4596bSEric Dumazet 	data = container_of(work, struct per_cpu_dm_data, dm_alert_work);
1954fdcfa12SNeil Horman 
196bec4596bSEric Dumazet 	skb = reset_per_cpu_data(data);
1979a8afc8dSNeil Horman 
1983885ca78SNeil Horman 	if (skb)
19968eb5503SJohannes Berg 		genlmsg_multicast(&net_drop_monitor_family, skb, 0,
2002a94fe48SJohannes Berg 				  0, GFP_KERNEL);
2019a8afc8dSNeil Horman }
2029a8afc8dSNeil Horman 
2039a8afc8dSNeil Horman /*
2049a8afc8dSNeil Horman  * This is the timer function to delay the sending of an alert
2059a8afc8dSNeil Horman  * in the event that more drops will arrive during the
206bec4596bSEric Dumazet  * hysteresis period.
2079a8afc8dSNeil Horman  */
sched_send_work(struct timer_list * t)208e99e88a9SKees Cook static void sched_send_work(struct timer_list *t)
2099a8afc8dSNeil Horman {
210e99e88a9SKees Cook 	struct per_cpu_dm_data *data = from_timer(data, t, send_timer);
2119a8afc8dSNeil Horman 
212bec4596bSEric Dumazet 	schedule_work(&data->dm_alert_work);
2139a8afc8dSNeil Horman }
2149a8afc8dSNeil Horman 
trace_drop_common(struct sk_buff * skb,void * location)2154ea7e386SNeil Horman static void trace_drop_common(struct sk_buff *skb, void *location)
2169a8afc8dSNeil Horman {
2179a8afc8dSNeil Horman 	struct net_dm_alert_msg *msg;
218dc30b405SArnd Bergmann 	struct net_dm_drop_point *point;
2199a8afc8dSNeil Horman 	struct nlmsghdr *nlh;
220683703a2SNeil Horman 	struct nlattr *nla;
2219a8afc8dSNeil Horman 	int i;
2223885ca78SNeil Horman 	struct sk_buff *dskb;
223bec4596bSEric Dumazet 	struct per_cpu_dm_data *data;
224bec4596bSEric Dumazet 	unsigned long flags;
2259a8afc8dSNeil Horman 
226bec4596bSEric Dumazet 	local_irq_save(flags);
227903ceff7SChristoph Lameter 	data = this_cpu_ptr(&dm_cpu_data);
22876ce2f91SWander Lairson Costa 	raw_spin_lock(&data->lock);
229bec4596bSEric Dumazet 	dskb = data->skb;
2303885ca78SNeil Horman 
2313885ca78SNeil Horman 	if (!dskb)
2323885ca78SNeil Horman 		goto out;
2333885ca78SNeil Horman 
2343885ca78SNeil Horman 	nlh = (struct nlmsghdr *)dskb->data;
235683703a2SNeil Horman 	nla = genlmsg_data(nlmsg_data(nlh));
236683703a2SNeil Horman 	msg = nla_data(nla);
237dc30b405SArnd Bergmann 	point = msg->points;
2389a8afc8dSNeil Horman 	for (i = 0; i < msg->entries; i++) {
239dc30b405SArnd Bergmann 		if (!memcmp(&location, &point->pc, sizeof(void *))) {
240dc30b405SArnd Bergmann 			point->count++;
2419a8afc8dSNeil Horman 			goto out;
2429a8afc8dSNeil Horman 		}
243dc30b405SArnd Bergmann 		point++;
2449a8afc8dSNeil Horman 	}
245bec4596bSEric Dumazet 	if (msg->entries == dm_hit_limit)
246bec4596bSEric Dumazet 		goto out;
2479a8afc8dSNeil Horman 	/*
2489a8afc8dSNeil Horman 	 * We need to create a new entry
2499a8afc8dSNeil Horman 	 */
2503885ca78SNeil Horman 	__nla_reserve_nohdr(dskb, sizeof(struct net_dm_drop_point));
251683703a2SNeil Horman 	nla->nla_len += NLA_ALIGN(sizeof(struct net_dm_drop_point));
252dc30b405SArnd Bergmann 	memcpy(point->pc, &location, sizeof(void *));
253dc30b405SArnd Bergmann 	point->count = 1;
2549a8afc8dSNeil Horman 	msg->entries++;
2559a8afc8dSNeil Horman 
2569a8afc8dSNeil Horman 	if (!timer_pending(&data->send_timer)) {
2579a8afc8dSNeil Horman 		data->send_timer.expires = jiffies + dm_delay * HZ;
258bec4596bSEric Dumazet 		add_timer(&data->send_timer);
2599a8afc8dSNeil Horman 	}
2609a8afc8dSNeil Horman 
2619a8afc8dSNeil Horman out:
26276ce2f91SWander Lairson Costa 	raw_spin_unlock_irqrestore(&data->lock, flags);
2639a8afc8dSNeil Horman }
2649a8afc8dSNeil Horman 
trace_kfree_skb_hit(void * ignore,struct sk_buff * skb,void * location,enum skb_drop_reason reason)265c504e5c2SMenglong Dong static void trace_kfree_skb_hit(void *ignore, struct sk_buff *skb,
266c504e5c2SMenglong Dong 				void *location,
267c504e5c2SMenglong Dong 				enum skb_drop_reason reason)
2684ea7e386SNeil Horman {
2694ea7e386SNeil Horman 	trace_drop_common(skb, location);
2704ea7e386SNeil Horman }
2714ea7e386SNeil Horman 
trace_napi_poll_hit(void * ignore,struct napi_struct * napi,int work,int budget)2721db19db7SJesper Dangaard Brouer static void trace_napi_poll_hit(void *ignore, struct napi_struct *napi,
2731db19db7SJesper Dangaard Brouer 				int work, int budget)
2744ea7e386SNeil Horman {
275b26ef81cSEric Dumazet 	struct net_device *dev = napi->dev;
276b26ef81cSEric Dumazet 	struct dm_hw_stat_delta *stat;
2774ea7e386SNeil Horman 	/*
2785848cc09SNeil Horman 	 * Don't check napi structures with no associated device
2794ea7e386SNeil Horman 	 */
280b26ef81cSEric Dumazet 	if (!dev)
2814ea7e386SNeil Horman 		return;
2824ea7e386SNeil Horman 
2834ea7e386SNeil Horman 	rcu_read_lock();
284b26ef81cSEric Dumazet 	stat = rcu_dereference(dev->dm_private);
285b26ef81cSEric Dumazet 	if (stat) {
2865848cc09SNeil Horman 		/*
2875848cc09SNeil Horman 		 * only add a note to our monitor buffer if:
288b26ef81cSEric Dumazet 		 * 1) its after the last_rx delta
289b26ef81cSEric Dumazet 		 * 2) our rx_dropped count has gone up
2905848cc09SNeil Horman 		 */
291b26ef81cSEric Dumazet 		if (time_after(jiffies, stat->last_rx + dm_hw_check_delta) &&
292b26ef81cSEric Dumazet 		    (dev->stats.rx_dropped != stat->last_drop_val)) {
2934ea7e386SNeil Horman 			trace_drop_common(NULL, NULL);
294b26ef81cSEric Dumazet 			stat->last_drop_val = dev->stats.rx_dropped;
295b26ef81cSEric Dumazet 			stat->last_rx = jiffies;
2964ea7e386SNeil Horman 		}
2974ea7e386SNeil Horman 	}
2984ea7e386SNeil Horman 	rcu_read_unlock();
2994ea7e386SNeil Horman }
3004ea7e386SNeil Horman 
301d40e1debSIdo Schimmel static struct net_dm_hw_entries *
net_dm_hw_reset_per_cpu_data(struct per_cpu_dm_data * hw_data)302d40e1debSIdo Schimmel net_dm_hw_reset_per_cpu_data(struct per_cpu_dm_data *hw_data)
303d40e1debSIdo Schimmel {
304d40e1debSIdo Schimmel 	struct net_dm_hw_entries *hw_entries;
305d40e1debSIdo Schimmel 	unsigned long flags;
306d40e1debSIdo Schimmel 
307d40e1debSIdo Schimmel 	hw_entries = kzalloc(struct_size(hw_entries, entries, dm_hit_limit),
308d40e1debSIdo Schimmel 			     GFP_KERNEL);
309d40e1debSIdo Schimmel 	if (!hw_entries) {
310d40e1debSIdo Schimmel 		/* If the memory allocation failed, we try to perform another
311d40e1debSIdo Schimmel 		 * allocation in 1/10 second. Otherwise, the probe function
312d40e1debSIdo Schimmel 		 * will constantly bail out.
313d40e1debSIdo Schimmel 		 */
314d40e1debSIdo Schimmel 		mod_timer(&hw_data->send_timer, jiffies + HZ / 10);
315d40e1debSIdo Schimmel 	}
316d40e1debSIdo Schimmel 
31776ce2f91SWander Lairson Costa 	raw_spin_lock_irqsave(&hw_data->lock, flags);
318d40e1debSIdo Schimmel 	swap(hw_data->hw_entries, hw_entries);
31976ce2f91SWander Lairson Costa 	raw_spin_unlock_irqrestore(&hw_data->lock, flags);
320d40e1debSIdo Schimmel 
321d40e1debSIdo Schimmel 	return hw_entries;
322d40e1debSIdo Schimmel }
323d40e1debSIdo Schimmel 
net_dm_hw_entry_put(struct sk_buff * msg,const struct net_dm_hw_entry * hw_entry)324d40e1debSIdo Schimmel static int net_dm_hw_entry_put(struct sk_buff *msg,
325d40e1debSIdo Schimmel 			       const struct net_dm_hw_entry *hw_entry)
326d40e1debSIdo Schimmel {
327d40e1debSIdo Schimmel 	struct nlattr *attr;
328d40e1debSIdo Schimmel 
329d40e1debSIdo Schimmel 	attr = nla_nest_start(msg, NET_DM_ATTR_HW_ENTRY);
330d40e1debSIdo Schimmel 	if (!attr)
331d40e1debSIdo Schimmel 		return -EMSGSIZE;
332d40e1debSIdo Schimmel 
333d40e1debSIdo Schimmel 	if (nla_put_string(msg, NET_DM_ATTR_HW_TRAP_NAME, hw_entry->trap_name))
334d40e1debSIdo Schimmel 		goto nla_put_failure;
335d40e1debSIdo Schimmel 
336d40e1debSIdo Schimmel 	if (nla_put_u32(msg, NET_DM_ATTR_HW_TRAP_COUNT, hw_entry->count))
337d40e1debSIdo Schimmel 		goto nla_put_failure;
338d40e1debSIdo Schimmel 
339d40e1debSIdo Schimmel 	nla_nest_end(msg, attr);
340d40e1debSIdo Schimmel 
341d40e1debSIdo Schimmel 	return 0;
342d40e1debSIdo Schimmel 
343d40e1debSIdo Schimmel nla_put_failure:
344d40e1debSIdo Schimmel 	nla_nest_cancel(msg, attr);
345d40e1debSIdo Schimmel 	return -EMSGSIZE;
346d40e1debSIdo Schimmel }
347d40e1debSIdo Schimmel 
net_dm_hw_entries_put(struct sk_buff * msg,const struct net_dm_hw_entries * hw_entries)348d40e1debSIdo Schimmel static int net_dm_hw_entries_put(struct sk_buff *msg,
349d40e1debSIdo Schimmel 				 const struct net_dm_hw_entries *hw_entries)
350d40e1debSIdo Schimmel {
351d40e1debSIdo Schimmel 	struct nlattr *attr;
352d40e1debSIdo Schimmel 	int i;
353d40e1debSIdo Schimmel 
354d40e1debSIdo Schimmel 	attr = nla_nest_start(msg, NET_DM_ATTR_HW_ENTRIES);
355d40e1debSIdo Schimmel 	if (!attr)
356d40e1debSIdo Schimmel 		return -EMSGSIZE;
357d40e1debSIdo Schimmel 
358d40e1debSIdo Schimmel 	for (i = 0; i < hw_entries->num_entries; i++) {
359d40e1debSIdo Schimmel 		int rc;
360d40e1debSIdo Schimmel 
361d40e1debSIdo Schimmel 		rc = net_dm_hw_entry_put(msg, &hw_entries->entries[i]);
362d40e1debSIdo Schimmel 		if (rc)
363d40e1debSIdo Schimmel 			goto nla_put_failure;
364d40e1debSIdo Schimmel 	}
365d40e1debSIdo Schimmel 
366d40e1debSIdo Schimmel 	nla_nest_end(msg, attr);
367d40e1debSIdo Schimmel 
368d40e1debSIdo Schimmel 	return 0;
369d40e1debSIdo Schimmel 
370d40e1debSIdo Schimmel nla_put_failure:
371d40e1debSIdo Schimmel 	nla_nest_cancel(msg, attr);
372d40e1debSIdo Schimmel 	return -EMSGSIZE;
373d40e1debSIdo Schimmel }
374d40e1debSIdo Schimmel 
375d40e1debSIdo Schimmel static int
net_dm_hw_summary_report_fill(struct sk_buff * msg,const struct net_dm_hw_entries * hw_entries)376d40e1debSIdo Schimmel net_dm_hw_summary_report_fill(struct sk_buff *msg,
377d40e1debSIdo Schimmel 			      const struct net_dm_hw_entries *hw_entries)
378d40e1debSIdo Schimmel {
379d40e1debSIdo Schimmel 	struct net_dm_alert_msg anc_hdr = { 0 };
380d40e1debSIdo Schimmel 	void *hdr;
381d40e1debSIdo Schimmel 	int rc;
382d40e1debSIdo Schimmel 
383d40e1debSIdo Schimmel 	hdr = genlmsg_put(msg, 0, 0, &net_drop_monitor_family, 0,
384d40e1debSIdo Schimmel 			  NET_DM_CMD_ALERT);
385d40e1debSIdo Schimmel 	if (!hdr)
386d40e1debSIdo Schimmel 		return -EMSGSIZE;
387d40e1debSIdo Schimmel 
388d40e1debSIdo Schimmel 	/* We need to put the ancillary header in order not to break user
389d40e1debSIdo Schimmel 	 * space.
390d40e1debSIdo Schimmel 	 */
391d40e1debSIdo Schimmel 	if (nla_put(msg, NLA_UNSPEC, sizeof(anc_hdr), &anc_hdr))
392d40e1debSIdo Schimmel 		goto nla_put_failure;
393d40e1debSIdo Schimmel 
394d40e1debSIdo Schimmel 	rc = net_dm_hw_entries_put(msg, hw_entries);
395d40e1debSIdo Schimmel 	if (rc)
396d40e1debSIdo Schimmel 		goto nla_put_failure;
397d40e1debSIdo Schimmel 
398d40e1debSIdo Schimmel 	genlmsg_end(msg, hdr);
399d40e1debSIdo Schimmel 
400d40e1debSIdo Schimmel 	return 0;
401d40e1debSIdo Schimmel 
402d40e1debSIdo Schimmel nla_put_failure:
403d40e1debSIdo Schimmel 	genlmsg_cancel(msg, hdr);
404d40e1debSIdo Schimmel 	return -EMSGSIZE;
405d40e1debSIdo Schimmel }
406d40e1debSIdo Schimmel 
net_dm_hw_summary_work(struct work_struct * work)407d40e1debSIdo Schimmel static void net_dm_hw_summary_work(struct work_struct *work)
408d40e1debSIdo Schimmel {
409d40e1debSIdo Schimmel 	struct net_dm_hw_entries *hw_entries;
410d40e1debSIdo Schimmel 	struct per_cpu_dm_data *hw_data;
411d40e1debSIdo Schimmel 	struct sk_buff *msg;
412d40e1debSIdo Schimmel 	int rc;
413d40e1debSIdo Schimmel 
414d40e1debSIdo Schimmel 	hw_data = container_of(work, struct per_cpu_dm_data, dm_alert_work);
415d40e1debSIdo Schimmel 
416d40e1debSIdo Schimmel 	hw_entries = net_dm_hw_reset_per_cpu_data(hw_data);
417d40e1debSIdo Schimmel 	if (!hw_entries)
418d40e1debSIdo Schimmel 		return;
419d40e1debSIdo Schimmel 
420d40e1debSIdo Schimmel 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
421d40e1debSIdo Schimmel 	if (!msg)
422d40e1debSIdo Schimmel 		goto out;
423d40e1debSIdo Schimmel 
424d40e1debSIdo Schimmel 	rc = net_dm_hw_summary_report_fill(msg, hw_entries);
425d40e1debSIdo Schimmel 	if (rc) {
426d40e1debSIdo Schimmel 		nlmsg_free(msg);
427d40e1debSIdo Schimmel 		goto out;
428d40e1debSIdo Schimmel 	}
429d40e1debSIdo Schimmel 
430d40e1debSIdo Schimmel 	genlmsg_multicast(&net_drop_monitor_family, msg, 0, 0, GFP_KERNEL);
431d40e1debSIdo Schimmel 
432d40e1debSIdo Schimmel out:
433d40e1debSIdo Schimmel 	kfree(hw_entries);
434d40e1debSIdo Schimmel }
435d40e1debSIdo Schimmel 
436edd3d007SIdo Schimmel static void
net_dm_hw_trap_summary_probe(void * ignore,const struct devlink * devlink,struct sk_buff * skb,const struct devlink_trap_metadata * metadata)4375855357cSIdo Schimmel net_dm_hw_trap_summary_probe(void *ignore, const struct devlink *devlink,
4385855357cSIdo Schimmel 			     struct sk_buff *skb,
4395855357cSIdo Schimmel 			     const struct devlink_trap_metadata *metadata)
4405855357cSIdo Schimmel {
4415855357cSIdo Schimmel 	struct net_dm_hw_entries *hw_entries;
4425855357cSIdo Schimmel 	struct net_dm_hw_entry *hw_entry;
4435855357cSIdo Schimmel 	struct per_cpu_dm_data *hw_data;
4445855357cSIdo Schimmel 	unsigned long flags;
4455855357cSIdo Schimmel 	int i;
4465855357cSIdo Schimmel 
44793e15596SIdo Schimmel 	if (metadata->trap_type == DEVLINK_TRAP_TYPE_CONTROL)
44893e15596SIdo Schimmel 		return;
44993e15596SIdo Schimmel 
4505855357cSIdo Schimmel 	hw_data = this_cpu_ptr(&dm_hw_cpu_data);
45176ce2f91SWander Lairson Costa 	raw_spin_lock_irqsave(&hw_data->lock, flags);
4525855357cSIdo Schimmel 	hw_entries = hw_data->hw_entries;
4535855357cSIdo Schimmel 
4545855357cSIdo Schimmel 	if (!hw_entries)
4555855357cSIdo Schimmel 		goto out;
4565855357cSIdo Schimmel 
4575855357cSIdo Schimmel 	for (i = 0; i < hw_entries->num_entries; i++) {
4585855357cSIdo Schimmel 		hw_entry = &hw_entries->entries[i];
4595855357cSIdo Schimmel 		if (!strncmp(hw_entry->trap_name, metadata->trap_name,
4605855357cSIdo Schimmel 			     NET_DM_MAX_HW_TRAP_NAME_LEN - 1)) {
4615855357cSIdo Schimmel 			hw_entry->count++;
4625855357cSIdo Schimmel 			goto out;
4635855357cSIdo Schimmel 		}
4645855357cSIdo Schimmel 	}
4655855357cSIdo Schimmel 	if (WARN_ON_ONCE(hw_entries->num_entries == dm_hit_limit))
4665855357cSIdo Schimmel 		goto out;
4675855357cSIdo Schimmel 
4685855357cSIdo Schimmel 	hw_entry = &hw_entries->entries[hw_entries->num_entries];
46970986397SWolfram Sang 	strscpy(hw_entry->trap_name, metadata->trap_name,
4705855357cSIdo Schimmel 		NET_DM_MAX_HW_TRAP_NAME_LEN - 1);
4715855357cSIdo Schimmel 	hw_entry->count = 1;
4725855357cSIdo Schimmel 	hw_entries->num_entries++;
4735855357cSIdo Schimmel 
4745855357cSIdo Schimmel 	if (!timer_pending(&hw_data->send_timer)) {
4755855357cSIdo Schimmel 		hw_data->send_timer.expires = jiffies + dm_delay * HZ;
4765855357cSIdo Schimmel 		add_timer(&hw_data->send_timer);
4775855357cSIdo Schimmel 	}
4785855357cSIdo Schimmel 
4795855357cSIdo Schimmel out:
48076ce2f91SWander Lairson Costa 	raw_spin_unlock_irqrestore(&hw_data->lock, flags);
4815855357cSIdo Schimmel }
4825855357cSIdo Schimmel 
48328315f79SIdo Schimmel static const struct net_dm_alert_ops net_dm_alert_summary_ops = {
48428315f79SIdo Schimmel 	.kfree_skb_probe	= trace_kfree_skb_hit,
48528315f79SIdo Schimmel 	.napi_poll_probe	= trace_napi_poll_hit,
48628315f79SIdo Schimmel 	.work_item_func		= send_dm_alert,
487d40e1debSIdo Schimmel 	.hw_work_item_func	= net_dm_hw_summary_work,
4885855357cSIdo Schimmel 	.hw_trap_probe		= net_dm_hw_trap_summary_probe,
48928315f79SIdo Schimmel };
49028315f79SIdo Schimmel 
net_dm_packet_trace_kfree_skb_hit(void * ignore,struct sk_buff * skb,void * location,enum skb_drop_reason reason)491ca30707dSIdo Schimmel static void net_dm_packet_trace_kfree_skb_hit(void *ignore,
492ca30707dSIdo Schimmel 					      struct sk_buff *skb,
493c504e5c2SMenglong Dong 					      void *location,
494c504e5c2SMenglong Dong 					      enum skb_drop_reason reason)
495ca30707dSIdo Schimmel {
496ca30707dSIdo Schimmel 	ktime_t tstamp = ktime_get_real();
497ca30707dSIdo Schimmel 	struct per_cpu_dm_data *data;
4985cad527dSMenglong Dong 	struct net_dm_skb_cb *cb;
499ca30707dSIdo Schimmel 	struct sk_buff *nskb;
500ca30707dSIdo Schimmel 	unsigned long flags;
501ca30707dSIdo Schimmel 
502bef17466SIdo Schimmel 	if (!skb_mac_header_was_set(skb))
503bef17466SIdo Schimmel 		return;
504bef17466SIdo Schimmel 
505ca30707dSIdo Schimmel 	nskb = skb_clone(skb, GFP_ATOMIC);
506ca30707dSIdo Schimmel 	if (!nskb)
507ca30707dSIdo Schimmel 		return;
508ca30707dSIdo Schimmel 
5095cad527dSMenglong Dong 	cb = NET_DM_SKB_CB(nskb);
5105cad527dSMenglong Dong 	cb->reason = reason;
5115cad527dSMenglong Dong 	cb->pc = location;
512ca30707dSIdo Schimmel 	/* Override the timestamp because we care about the time when the
513ca30707dSIdo Schimmel 	 * packet was dropped.
514ca30707dSIdo Schimmel 	 */
515ca30707dSIdo Schimmel 	nskb->tstamp = tstamp;
516ca30707dSIdo Schimmel 
517ca30707dSIdo Schimmel 	data = this_cpu_ptr(&dm_cpu_data);
518ca30707dSIdo Schimmel 
519ca30707dSIdo Schimmel 	spin_lock_irqsave(&data->drop_queue.lock, flags);
52030328d46SIdo Schimmel 	if (skb_queue_len(&data->drop_queue) < net_dm_queue_len)
521ca30707dSIdo Schimmel 		__skb_queue_tail(&data->drop_queue, nskb);
522ca30707dSIdo Schimmel 	else
523ca30707dSIdo Schimmel 		goto unlock_free;
524ca30707dSIdo Schimmel 	spin_unlock_irqrestore(&data->drop_queue.lock, flags);
525ca30707dSIdo Schimmel 
526ca30707dSIdo Schimmel 	schedule_work(&data->dm_alert_work);
527ca30707dSIdo Schimmel 
528ca30707dSIdo Schimmel 	return;
529ca30707dSIdo Schimmel 
530ca30707dSIdo Schimmel unlock_free:
531ca30707dSIdo Schimmel 	spin_unlock_irqrestore(&data->drop_queue.lock, flags);
532e9feb580SIdo Schimmel 	u64_stats_update_begin(&data->stats.syncp);
533c6cce71eSEric Dumazet 	u64_stats_inc(&data->stats.dropped);
534e9feb580SIdo Schimmel 	u64_stats_update_end(&data->stats.syncp);
535ca30707dSIdo Schimmel 	consume_skb(nskb);
536ca30707dSIdo Schimmel }
537ca30707dSIdo Schimmel 
net_dm_packet_trace_napi_poll_hit(void * ignore,struct napi_struct * napi,int work,int budget)538ca30707dSIdo Schimmel static void net_dm_packet_trace_napi_poll_hit(void *ignore,
539ca30707dSIdo Schimmel 					      struct napi_struct *napi,
540ca30707dSIdo Schimmel 					      int work, int budget)
541ca30707dSIdo Schimmel {
542ca30707dSIdo Schimmel }
543ca30707dSIdo Schimmel 
net_dm_in_port_size(void)544ca30707dSIdo Schimmel static size_t net_dm_in_port_size(void)
545ca30707dSIdo Schimmel {
546ca30707dSIdo Schimmel 	       /* NET_DM_ATTR_IN_PORT nest */
547ca30707dSIdo Schimmel 	return nla_total_size(0) +
548ca30707dSIdo Schimmel 	       /* NET_DM_ATTR_PORT_NETDEV_IFINDEX */
5495e58109bSIdo Schimmel 	       nla_total_size(sizeof(u32)) +
5505e58109bSIdo Schimmel 	       /* NET_DM_ATTR_PORT_NETDEV_NAME */
5515e58109bSIdo Schimmel 	       nla_total_size(IFNAMSIZ + 1);
552ca30707dSIdo Schimmel }
553ca30707dSIdo Schimmel 
554ca30707dSIdo Schimmel #define NET_DM_MAX_SYMBOL_LEN 40
555071c0fc6SJohannes Berg #define NET_DM_MAX_REASON_LEN 50
556ca30707dSIdo Schimmel 
net_dm_packet_report_size(size_t payload_len)557071c0fc6SJohannes Berg static size_t net_dm_packet_report_size(size_t payload_len)
558ca30707dSIdo Schimmel {
559ca30707dSIdo Schimmel 	size_t size;
560ca30707dSIdo Schimmel 
561ca30707dSIdo Schimmel 	size = nlmsg_msg_size(GENL_HDRLEN + net_drop_monitor_family.hdrsize);
562ca30707dSIdo Schimmel 
563ca30707dSIdo Schimmel 	return NLMSG_ALIGN(size) +
5645e58109bSIdo Schimmel 	       /* NET_DM_ATTR_ORIGIN */
5655e58109bSIdo Schimmel 	       nla_total_size(sizeof(u16)) +
566ca30707dSIdo Schimmel 	       /* NET_DM_ATTR_PC */
567ca30707dSIdo Schimmel 	       nla_total_size(sizeof(u64)) +
568ca30707dSIdo Schimmel 	       /* NET_DM_ATTR_SYMBOL */
569ca30707dSIdo Schimmel 	       nla_total_size(NET_DM_MAX_SYMBOL_LEN + 1) +
570ca30707dSIdo Schimmel 	       /* NET_DM_ATTR_IN_PORT */
571ca30707dSIdo Schimmel 	       net_dm_in_port_size() +
572ca30707dSIdo Schimmel 	       /* NET_DM_ATTR_TIMESTAMP */
573bd1200b7SIdo Schimmel 	       nla_total_size(sizeof(u64)) +
57457986617SIdo Schimmel 	       /* NET_DM_ATTR_ORIG_LEN */
57557986617SIdo Schimmel 	       nla_total_size(sizeof(u32)) +
576ca30707dSIdo Schimmel 	       /* NET_DM_ATTR_PROTO */
577ca30707dSIdo Schimmel 	       nla_total_size(sizeof(u16)) +
5785cad527dSMenglong Dong 	       /* NET_DM_ATTR_REASON */
579071c0fc6SJohannes Berg 	       nla_total_size(NET_DM_MAX_REASON_LEN + 1) +
580ca30707dSIdo Schimmel 	       /* NET_DM_ATTR_PAYLOAD */
581ca30707dSIdo Schimmel 	       nla_total_size(payload_len);
582ca30707dSIdo Schimmel }
583ca30707dSIdo Schimmel 
net_dm_packet_report_in_port_put(struct sk_buff * msg,int ifindex,const char * name)5845e58109bSIdo Schimmel static int net_dm_packet_report_in_port_put(struct sk_buff *msg, int ifindex,
5855e58109bSIdo Schimmel 					    const char *name)
586ca30707dSIdo Schimmel {
587ca30707dSIdo Schimmel 	struct nlattr *attr;
588ca30707dSIdo Schimmel 
589ca30707dSIdo Schimmel 	attr = nla_nest_start(msg, NET_DM_ATTR_IN_PORT);
590ca30707dSIdo Schimmel 	if (!attr)
591ca30707dSIdo Schimmel 		return -EMSGSIZE;
592ca30707dSIdo Schimmel 
593ca30707dSIdo Schimmel 	if (ifindex &&
594ca30707dSIdo Schimmel 	    nla_put_u32(msg, NET_DM_ATTR_PORT_NETDEV_IFINDEX, ifindex))
595ca30707dSIdo Schimmel 		goto nla_put_failure;
596ca30707dSIdo Schimmel 
5975e58109bSIdo Schimmel 	if (name && nla_put_string(msg, NET_DM_ATTR_PORT_NETDEV_NAME, name))
5985e58109bSIdo Schimmel 		goto nla_put_failure;
5995e58109bSIdo Schimmel 
600ca30707dSIdo Schimmel 	nla_nest_end(msg, attr);
601ca30707dSIdo Schimmel 
602ca30707dSIdo Schimmel 	return 0;
603ca30707dSIdo Schimmel 
604ca30707dSIdo Schimmel nla_put_failure:
605ca30707dSIdo Schimmel 	nla_nest_cancel(msg, attr);
606ca30707dSIdo Schimmel 	return -EMSGSIZE;
607ca30707dSIdo Schimmel }
608ca30707dSIdo Schimmel 
net_dm_packet_report_fill(struct sk_buff * msg,struct sk_buff * skb,size_t payload_len)609ca30707dSIdo Schimmel static int net_dm_packet_report_fill(struct sk_buff *msg, struct sk_buff *skb,
610ca30707dSIdo Schimmel 				     size_t payload_len)
611ca30707dSIdo Schimmel {
6125cad527dSMenglong Dong 	struct net_dm_skb_cb *cb = NET_DM_SKB_CB(skb);
613071c0fc6SJohannes Berg 	const struct drop_reason_list *list = NULL;
614071c0fc6SJohannes Berg 	unsigned int subsys, subsys_reason;
615ca30707dSIdo Schimmel 	char buf[NET_DM_MAX_SYMBOL_LEN];
616ca30707dSIdo Schimmel 	struct nlattr *attr;
617ca30707dSIdo Schimmel 	void *hdr;
618ca30707dSIdo Schimmel 	int rc;
619ca30707dSIdo Schimmel 
620ca30707dSIdo Schimmel 	hdr = genlmsg_put(msg, 0, 0, &net_drop_monitor_family, 0,
621ca30707dSIdo Schimmel 			  NET_DM_CMD_PACKET_ALERT);
622ca30707dSIdo Schimmel 	if (!hdr)
623ca30707dSIdo Schimmel 		return -EMSGSIZE;
624ca30707dSIdo Schimmel 
6255e58109bSIdo Schimmel 	if (nla_put_u16(msg, NET_DM_ATTR_ORIGIN, NET_DM_ORIGIN_SW))
6265e58109bSIdo Schimmel 		goto nla_put_failure;
6275e58109bSIdo Schimmel 
6285cad527dSMenglong Dong 	if (nla_put_u64_64bit(msg, NET_DM_ATTR_PC, (u64)(uintptr_t)cb->pc,
6295cad527dSMenglong Dong 			      NET_DM_ATTR_PAD))
630ca30707dSIdo Schimmel 		goto nla_put_failure;
631ca30707dSIdo Schimmel 
632071c0fc6SJohannes Berg 	rcu_read_lock();
633071c0fc6SJohannes Berg 	subsys = u32_get_bits(cb->reason, SKB_DROP_REASON_SUBSYS_MASK);
634071c0fc6SJohannes Berg 	if (subsys < SKB_DROP_REASON_SUBSYS_NUM)
635071c0fc6SJohannes Berg 		list = rcu_dereference(drop_reasons_by_subsys[subsys]);
636071c0fc6SJohannes Berg 	subsys_reason = cb->reason & ~SKB_DROP_REASON_SUBSYS_MASK;
637071c0fc6SJohannes Berg 	if (!list ||
638071c0fc6SJohannes Berg 	    subsys_reason >= list->n_reasons ||
639071c0fc6SJohannes Berg 	    !list->reasons[subsys_reason] ||
640071c0fc6SJohannes Berg 	    strlen(list->reasons[subsys_reason]) > NET_DM_MAX_REASON_LEN) {
641071c0fc6SJohannes Berg 		list = rcu_dereference(drop_reasons_by_subsys[SKB_DROP_REASON_SUBSYS_CORE]);
642071c0fc6SJohannes Berg 		subsys_reason = SKB_DROP_REASON_NOT_SPECIFIED;
643071c0fc6SJohannes Berg 	}
6445cad527dSMenglong Dong 	if (nla_put_string(msg, NET_DM_ATTR_REASON,
645071c0fc6SJohannes Berg 			   list->reasons[subsys_reason])) {
646071c0fc6SJohannes Berg 		rcu_read_unlock();
6475cad527dSMenglong Dong 		goto nla_put_failure;
648071c0fc6SJohannes Berg 	}
649071c0fc6SJohannes Berg 	rcu_read_unlock();
6505cad527dSMenglong Dong 
6515cad527dSMenglong Dong 	snprintf(buf, sizeof(buf), "%pS", cb->pc);
652ca30707dSIdo Schimmel 	if (nla_put_string(msg, NET_DM_ATTR_SYMBOL, buf))
653ca30707dSIdo Schimmel 		goto nla_put_failure;
654ca30707dSIdo Schimmel 
6555e58109bSIdo Schimmel 	rc = net_dm_packet_report_in_port_put(msg, skb->skb_iif, NULL);
656ca30707dSIdo Schimmel 	if (rc)
657ca30707dSIdo Schimmel 		goto nla_put_failure;
658ca30707dSIdo Schimmel 
659bd1200b7SIdo Schimmel 	if (nla_put_u64_64bit(msg, NET_DM_ATTR_TIMESTAMP,
660bd1200b7SIdo Schimmel 			      ktime_to_ns(skb->tstamp), NET_DM_ATTR_PAD))
661ca30707dSIdo Schimmel 		goto nla_put_failure;
662ca30707dSIdo Schimmel 
66357986617SIdo Schimmel 	if (nla_put_u32(msg, NET_DM_ATTR_ORIG_LEN, skb->len))
66457986617SIdo Schimmel 		goto nla_put_failure;
66557986617SIdo Schimmel 
666ca30707dSIdo Schimmel 	if (!payload_len)
667ca30707dSIdo Schimmel 		goto out;
668ca30707dSIdo Schimmel 
669ca30707dSIdo Schimmel 	if (nla_put_u16(msg, NET_DM_ATTR_PROTO, be16_to_cpu(skb->protocol)))
670ca30707dSIdo Schimmel 		goto nla_put_failure;
671ca30707dSIdo Schimmel 
672ca30707dSIdo Schimmel 	attr = skb_put(msg, nla_total_size(payload_len));
673ca30707dSIdo Schimmel 	attr->nla_type = NET_DM_ATTR_PAYLOAD;
674ca30707dSIdo Schimmel 	attr->nla_len = nla_attr_size(payload_len);
675ca30707dSIdo Schimmel 	if (skb_copy_bits(skb, 0, nla_data(attr), payload_len))
676ca30707dSIdo Schimmel 		goto nla_put_failure;
677ca30707dSIdo Schimmel 
678ca30707dSIdo Schimmel out:
679ca30707dSIdo Schimmel 	genlmsg_end(msg, hdr);
680ca30707dSIdo Schimmel 
681ca30707dSIdo Schimmel 	return 0;
682ca30707dSIdo Schimmel 
683ca30707dSIdo Schimmel nla_put_failure:
684ca30707dSIdo Schimmel 	genlmsg_cancel(msg, hdr);
685ca30707dSIdo Schimmel 	return -EMSGSIZE;
686ca30707dSIdo Schimmel }
687ca30707dSIdo Schimmel 
688ca30707dSIdo Schimmel #define NET_DM_MAX_PACKET_SIZE (0xffff - NLA_HDRLEN - NLA_ALIGNTO)
689ca30707dSIdo Schimmel 
net_dm_packet_report(struct sk_buff * skb)690ca30707dSIdo Schimmel static void net_dm_packet_report(struct sk_buff *skb)
691ca30707dSIdo Schimmel {
692ca30707dSIdo Schimmel 	struct sk_buff *msg;
693ca30707dSIdo Schimmel 	size_t payload_len;
694ca30707dSIdo Schimmel 	int rc;
695ca30707dSIdo Schimmel 
696ca30707dSIdo Schimmel 	/* Make sure we start copying the packet from the MAC header */
697ca30707dSIdo Schimmel 	if (skb->data > skb_mac_header(skb))
698ca30707dSIdo Schimmel 		skb_push(skb, skb->data - skb_mac_header(skb));
699ca30707dSIdo Schimmel 	else
700ca30707dSIdo Schimmel 		skb_pull(skb, skb_mac_header(skb) - skb->data);
701ca30707dSIdo Schimmel 
702ca30707dSIdo Schimmel 	/* Ensure packet fits inside a single netlink attribute */
703ca30707dSIdo Schimmel 	payload_len = min_t(size_t, skb->len, NET_DM_MAX_PACKET_SIZE);
70457986617SIdo Schimmel 	if (net_dm_trunc_len)
70557986617SIdo Schimmel 		payload_len = min_t(size_t, net_dm_trunc_len, payload_len);
706ca30707dSIdo Schimmel 
707071c0fc6SJohannes Berg 	msg = nlmsg_new(net_dm_packet_report_size(payload_len), GFP_KERNEL);
708ca30707dSIdo Schimmel 	if (!msg)
709ca30707dSIdo Schimmel 		goto out;
710ca30707dSIdo Schimmel 
711ca30707dSIdo Schimmel 	rc = net_dm_packet_report_fill(msg, skb, payload_len);
712ca30707dSIdo Schimmel 	if (rc) {
713ca30707dSIdo Schimmel 		nlmsg_free(msg);
714ca30707dSIdo Schimmel 		goto out;
715ca30707dSIdo Schimmel 	}
716ca30707dSIdo Schimmel 
717ca30707dSIdo Schimmel 	genlmsg_multicast(&net_drop_monitor_family, msg, 0, 0, GFP_KERNEL);
718ca30707dSIdo Schimmel 
719ca30707dSIdo Schimmel out:
720ca30707dSIdo Schimmel 	consume_skb(skb);
721ca30707dSIdo Schimmel }
722ca30707dSIdo Schimmel 
net_dm_packet_work(struct work_struct * work)723ca30707dSIdo Schimmel static void net_dm_packet_work(struct work_struct *work)
724ca30707dSIdo Schimmel {
725ca30707dSIdo Schimmel 	struct per_cpu_dm_data *data;
726ca30707dSIdo Schimmel 	struct sk_buff_head list;
727ca30707dSIdo Schimmel 	struct sk_buff *skb;
728ca30707dSIdo Schimmel 	unsigned long flags;
729ca30707dSIdo Schimmel 
730ca30707dSIdo Schimmel 	data = container_of(work, struct per_cpu_dm_data, dm_alert_work);
731ca30707dSIdo Schimmel 
732ca30707dSIdo Schimmel 	__skb_queue_head_init(&list);
733ca30707dSIdo Schimmel 
734ca30707dSIdo Schimmel 	spin_lock_irqsave(&data->drop_queue.lock, flags);
735ca30707dSIdo Schimmel 	skb_queue_splice_tail_init(&data->drop_queue, &list);
736ca30707dSIdo Schimmel 	spin_unlock_irqrestore(&data->drop_queue.lock, flags);
737ca30707dSIdo Schimmel 
738ca30707dSIdo Schimmel 	while ((skb = __skb_dequeue(&list)))
739ca30707dSIdo Schimmel 		net_dm_packet_report(skb);
740ca30707dSIdo Schimmel }
741ca30707dSIdo Schimmel 
7425e58109bSIdo Schimmel static size_t
net_dm_flow_action_cookie_size(const struct devlink_trap_metadata * hw_metadata)743a848c05fSIdo Schimmel net_dm_flow_action_cookie_size(const struct devlink_trap_metadata *hw_metadata)
744742b8cceSJiri Pirko {
745742b8cceSJiri Pirko 	return hw_metadata->fa_cookie ?
746742b8cceSJiri Pirko 	       nla_total_size(hw_metadata->fa_cookie->cookie_len) : 0;
747742b8cceSJiri Pirko }
748742b8cceSJiri Pirko 
749742b8cceSJiri Pirko static size_t
net_dm_hw_packet_report_size(size_t payload_len,const struct devlink_trap_metadata * hw_metadata)7505e58109bSIdo Schimmel net_dm_hw_packet_report_size(size_t payload_len,
751a848c05fSIdo Schimmel 			     const struct devlink_trap_metadata *hw_metadata)
7525e58109bSIdo Schimmel {
7535e58109bSIdo Schimmel 	size_t size;
7545e58109bSIdo Schimmel 
7555e58109bSIdo Schimmel 	size = nlmsg_msg_size(GENL_HDRLEN + net_drop_monitor_family.hdrsize);
7565e58109bSIdo Schimmel 
7575e58109bSIdo Schimmel 	return NLMSG_ALIGN(size) +
7585e58109bSIdo Schimmel 	       /* NET_DM_ATTR_ORIGIN */
7595e58109bSIdo Schimmel 	       nla_total_size(sizeof(u16)) +
7605e58109bSIdo Schimmel 	       /* NET_DM_ATTR_HW_TRAP_GROUP_NAME */
7615e58109bSIdo Schimmel 	       nla_total_size(strlen(hw_metadata->trap_group_name) + 1) +
7625e58109bSIdo Schimmel 	       /* NET_DM_ATTR_HW_TRAP_NAME */
7635e58109bSIdo Schimmel 	       nla_total_size(strlen(hw_metadata->trap_name) + 1) +
7645e58109bSIdo Schimmel 	       /* NET_DM_ATTR_IN_PORT */
7655e58109bSIdo Schimmel 	       net_dm_in_port_size() +
766742b8cceSJiri Pirko 	       /* NET_DM_ATTR_FLOW_ACTION_COOKIE */
767742b8cceSJiri Pirko 	       net_dm_flow_action_cookie_size(hw_metadata) +
7685e58109bSIdo Schimmel 	       /* NET_DM_ATTR_TIMESTAMP */
769bd1200b7SIdo Schimmel 	       nla_total_size(sizeof(u64)) +
7705e58109bSIdo Schimmel 	       /* NET_DM_ATTR_ORIG_LEN */
7715e58109bSIdo Schimmel 	       nla_total_size(sizeof(u32)) +
7725e58109bSIdo Schimmel 	       /* NET_DM_ATTR_PROTO */
7735e58109bSIdo Schimmel 	       nla_total_size(sizeof(u16)) +
7745e58109bSIdo Schimmel 	       /* NET_DM_ATTR_PAYLOAD */
7755e58109bSIdo Schimmel 	       nla_total_size(payload_len);
7765e58109bSIdo Schimmel }
7775e58109bSIdo Schimmel 
net_dm_hw_packet_report_fill(struct sk_buff * msg,struct sk_buff * skb,size_t payload_len)7785e58109bSIdo Schimmel static int net_dm_hw_packet_report_fill(struct sk_buff *msg,
7795e58109bSIdo Schimmel 					struct sk_buff *skb, size_t payload_len)
7805e58109bSIdo Schimmel {
781a848c05fSIdo Schimmel 	struct devlink_trap_metadata *hw_metadata;
7825e58109bSIdo Schimmel 	struct nlattr *attr;
7835e58109bSIdo Schimmel 	void *hdr;
7845e58109bSIdo Schimmel 
7855e58109bSIdo Schimmel 	hw_metadata = NET_DM_SKB_CB(skb)->hw_metadata;
7865e58109bSIdo Schimmel 
7875e58109bSIdo Schimmel 	hdr = genlmsg_put(msg, 0, 0, &net_drop_monitor_family, 0,
7885e58109bSIdo Schimmel 			  NET_DM_CMD_PACKET_ALERT);
7895e58109bSIdo Schimmel 	if (!hdr)
7905e58109bSIdo Schimmel 		return -EMSGSIZE;
7915e58109bSIdo Schimmel 
7925e58109bSIdo Schimmel 	if (nla_put_u16(msg, NET_DM_ATTR_ORIGIN, NET_DM_ORIGIN_HW))
7935e58109bSIdo Schimmel 		goto nla_put_failure;
7945e58109bSIdo Schimmel 
7955e58109bSIdo Schimmel 	if (nla_put_string(msg, NET_DM_ATTR_HW_TRAP_GROUP_NAME,
7965e58109bSIdo Schimmel 			   hw_metadata->trap_group_name))
7975e58109bSIdo Schimmel 		goto nla_put_failure;
7985e58109bSIdo Schimmel 
7995e58109bSIdo Schimmel 	if (nla_put_string(msg, NET_DM_ATTR_HW_TRAP_NAME,
8005e58109bSIdo Schimmel 			   hw_metadata->trap_name))
8015e58109bSIdo Schimmel 		goto nla_put_failure;
8025e58109bSIdo Schimmel 
8035e58109bSIdo Schimmel 	if (hw_metadata->input_dev) {
8045e58109bSIdo Schimmel 		struct net_device *dev = hw_metadata->input_dev;
8055e58109bSIdo Schimmel 		int rc;
8065e58109bSIdo Schimmel 
8075e58109bSIdo Schimmel 		rc = net_dm_packet_report_in_port_put(msg, dev->ifindex,
8085e58109bSIdo Schimmel 						      dev->name);
8095e58109bSIdo Schimmel 		if (rc)
8105e58109bSIdo Schimmel 			goto nla_put_failure;
8115e58109bSIdo Schimmel 	}
8125e58109bSIdo Schimmel 
813742b8cceSJiri Pirko 	if (hw_metadata->fa_cookie &&
814742b8cceSJiri Pirko 	    nla_put(msg, NET_DM_ATTR_FLOW_ACTION_COOKIE,
815742b8cceSJiri Pirko 		    hw_metadata->fa_cookie->cookie_len,
816742b8cceSJiri Pirko 		    hw_metadata->fa_cookie->cookie))
817742b8cceSJiri Pirko 		goto nla_put_failure;
818742b8cceSJiri Pirko 
819bd1200b7SIdo Schimmel 	if (nla_put_u64_64bit(msg, NET_DM_ATTR_TIMESTAMP,
820bd1200b7SIdo Schimmel 			      ktime_to_ns(skb->tstamp), NET_DM_ATTR_PAD))
8215e58109bSIdo Schimmel 		goto nla_put_failure;
8225e58109bSIdo Schimmel 
8235e58109bSIdo Schimmel 	if (nla_put_u32(msg, NET_DM_ATTR_ORIG_LEN, skb->len))
8245e58109bSIdo Schimmel 		goto nla_put_failure;
8255e58109bSIdo Schimmel 
8265e58109bSIdo Schimmel 	if (!payload_len)
8275e58109bSIdo Schimmel 		goto out;
8285e58109bSIdo Schimmel 
8295e58109bSIdo Schimmel 	if (nla_put_u16(msg, NET_DM_ATTR_PROTO, be16_to_cpu(skb->protocol)))
8305e58109bSIdo Schimmel 		goto nla_put_failure;
8315e58109bSIdo Schimmel 
8325e58109bSIdo Schimmel 	attr = skb_put(msg, nla_total_size(payload_len));
8335e58109bSIdo Schimmel 	attr->nla_type = NET_DM_ATTR_PAYLOAD;
8345e58109bSIdo Schimmel 	attr->nla_len = nla_attr_size(payload_len);
8355e58109bSIdo Schimmel 	if (skb_copy_bits(skb, 0, nla_data(attr), payload_len))
8365e58109bSIdo Schimmel 		goto nla_put_failure;
8375e58109bSIdo Schimmel 
8385e58109bSIdo Schimmel out:
8395e58109bSIdo Schimmel 	genlmsg_end(msg, hdr);
8405e58109bSIdo Schimmel 
8415e58109bSIdo Schimmel 	return 0;
8425e58109bSIdo Schimmel 
8435e58109bSIdo Schimmel nla_put_failure:
8445e58109bSIdo Schimmel 	genlmsg_cancel(msg, hdr);
8455e58109bSIdo Schimmel 	return -EMSGSIZE;
8465e58109bSIdo Schimmel }
8475e58109bSIdo Schimmel 
848a848c05fSIdo Schimmel static struct devlink_trap_metadata *
net_dm_hw_metadata_copy(const struct devlink_trap_metadata * metadata)8495855357cSIdo Schimmel net_dm_hw_metadata_copy(const struct devlink_trap_metadata *metadata)
8505855357cSIdo Schimmel {
8515855357cSIdo Schimmel 	const struct flow_action_cookie *fa_cookie;
852a848c05fSIdo Schimmel 	struct devlink_trap_metadata *hw_metadata;
8535855357cSIdo Schimmel 	const char *trap_group_name;
8545855357cSIdo Schimmel 	const char *trap_name;
8555855357cSIdo Schimmel 
8565855357cSIdo Schimmel 	hw_metadata = kzalloc(sizeof(*hw_metadata), GFP_ATOMIC);
8575855357cSIdo Schimmel 	if (!hw_metadata)
8585855357cSIdo Schimmel 		return NULL;
8595855357cSIdo Schimmel 
8605855357cSIdo Schimmel 	trap_group_name = kstrdup(metadata->trap_group_name, GFP_ATOMIC);
8615855357cSIdo Schimmel 	if (!trap_group_name)
8625855357cSIdo Schimmel 		goto free_hw_metadata;
8635855357cSIdo Schimmel 	hw_metadata->trap_group_name = trap_group_name;
8645855357cSIdo Schimmel 
8655855357cSIdo Schimmel 	trap_name = kstrdup(metadata->trap_name, GFP_ATOMIC);
8665855357cSIdo Schimmel 	if (!trap_name)
8675855357cSIdo Schimmel 		goto free_trap_group;
8685855357cSIdo Schimmel 	hw_metadata->trap_name = trap_name;
8695855357cSIdo Schimmel 
8705855357cSIdo Schimmel 	if (metadata->fa_cookie) {
8715855357cSIdo Schimmel 		size_t cookie_size = sizeof(*fa_cookie) +
8725855357cSIdo Schimmel 				     metadata->fa_cookie->cookie_len;
8735855357cSIdo Schimmel 
8745855357cSIdo Schimmel 		fa_cookie = kmemdup(metadata->fa_cookie, cookie_size,
8755855357cSIdo Schimmel 				    GFP_ATOMIC);
8765855357cSIdo Schimmel 		if (!fa_cookie)
8775855357cSIdo Schimmel 			goto free_trap_name;
8785855357cSIdo Schimmel 		hw_metadata->fa_cookie = fa_cookie;
8795855357cSIdo Schimmel 	}
8805855357cSIdo Schimmel 
8815855357cSIdo Schimmel 	hw_metadata->input_dev = metadata->input_dev;
882d62607c3SJakub Kicinski 	netdev_hold(hw_metadata->input_dev, &hw_metadata->dev_tracker,
883d62607c3SJakub Kicinski 		    GFP_ATOMIC);
8845855357cSIdo Schimmel 
8855855357cSIdo Schimmel 	return hw_metadata;
8865855357cSIdo Schimmel 
8875855357cSIdo Schimmel free_trap_name:
8885855357cSIdo Schimmel 	kfree(trap_name);
8895855357cSIdo Schimmel free_trap_group:
8905855357cSIdo Schimmel 	kfree(trap_group_name);
8915855357cSIdo Schimmel free_hw_metadata:
8925855357cSIdo Schimmel 	kfree(hw_metadata);
8935855357cSIdo Schimmel 	return NULL;
8945855357cSIdo Schimmel }
8955855357cSIdo Schimmel 
8965e58109bSIdo Schimmel static void
net_dm_hw_metadata_free(struct devlink_trap_metadata * hw_metadata)8974dbd24f6SEric Dumazet net_dm_hw_metadata_free(struct devlink_trap_metadata *hw_metadata)
8985e58109bSIdo Schimmel {
899d62607c3SJakub Kicinski 	netdev_put(hw_metadata->input_dev, &hw_metadata->dev_tracker);
900742b8cceSJiri Pirko 	kfree(hw_metadata->fa_cookie);
9015e58109bSIdo Schimmel 	kfree(hw_metadata->trap_name);
9025e58109bSIdo Schimmel 	kfree(hw_metadata->trap_group_name);
9035e58109bSIdo Schimmel 	kfree(hw_metadata);
9045e58109bSIdo Schimmel }
9055e58109bSIdo Schimmel 
net_dm_hw_packet_report(struct sk_buff * skb)9065e58109bSIdo Schimmel static void net_dm_hw_packet_report(struct sk_buff *skb)
9075e58109bSIdo Schimmel {
908a848c05fSIdo Schimmel 	struct devlink_trap_metadata *hw_metadata;
9095e58109bSIdo Schimmel 	struct sk_buff *msg;
9105e58109bSIdo Schimmel 	size_t payload_len;
9115e58109bSIdo Schimmel 	int rc;
9125e58109bSIdo Schimmel 
9135e58109bSIdo Schimmel 	if (skb->data > skb_mac_header(skb))
9145e58109bSIdo Schimmel 		skb_push(skb, skb->data - skb_mac_header(skb));
9155e58109bSIdo Schimmel 	else
9165e58109bSIdo Schimmel 		skb_pull(skb, skb_mac_header(skb) - skb->data);
9175e58109bSIdo Schimmel 
9185e58109bSIdo Schimmel 	payload_len = min_t(size_t, skb->len, NET_DM_MAX_PACKET_SIZE);
9195e58109bSIdo Schimmel 	if (net_dm_trunc_len)
9205e58109bSIdo Schimmel 		payload_len = min_t(size_t, net_dm_trunc_len, payload_len);
9215e58109bSIdo Schimmel 
9225e58109bSIdo Schimmel 	hw_metadata = NET_DM_SKB_CB(skb)->hw_metadata;
9235e58109bSIdo Schimmel 	msg = nlmsg_new(net_dm_hw_packet_report_size(payload_len, hw_metadata),
9245e58109bSIdo Schimmel 			GFP_KERNEL);
9255e58109bSIdo Schimmel 	if (!msg)
9265e58109bSIdo Schimmel 		goto out;
9275e58109bSIdo Schimmel 
9285e58109bSIdo Schimmel 	rc = net_dm_hw_packet_report_fill(msg, skb, payload_len);
9295e58109bSIdo Schimmel 	if (rc) {
9305e58109bSIdo Schimmel 		nlmsg_free(msg);
9315e58109bSIdo Schimmel 		goto out;
9325e58109bSIdo Schimmel 	}
9335e58109bSIdo Schimmel 
9345e58109bSIdo Schimmel 	genlmsg_multicast(&net_drop_monitor_family, msg, 0, 0, GFP_KERNEL);
9355e58109bSIdo Schimmel 
9365e58109bSIdo Schimmel out:
9375e58109bSIdo Schimmel 	net_dm_hw_metadata_free(NET_DM_SKB_CB(skb)->hw_metadata);
9385e58109bSIdo Schimmel 	consume_skb(skb);
9395e58109bSIdo Schimmel }
9405e58109bSIdo Schimmel 
net_dm_hw_packet_work(struct work_struct * work)9415e58109bSIdo Schimmel static void net_dm_hw_packet_work(struct work_struct *work)
9425e58109bSIdo Schimmel {
9435e58109bSIdo Schimmel 	struct per_cpu_dm_data *hw_data;
9445e58109bSIdo Schimmel 	struct sk_buff_head list;
9455e58109bSIdo Schimmel 	struct sk_buff *skb;
9465e58109bSIdo Schimmel 	unsigned long flags;
9475e58109bSIdo Schimmel 
9485e58109bSIdo Schimmel 	hw_data = container_of(work, struct per_cpu_dm_data, dm_alert_work);
9495e58109bSIdo Schimmel 
9505e58109bSIdo Schimmel 	__skb_queue_head_init(&list);
9515e58109bSIdo Schimmel 
9525e58109bSIdo Schimmel 	spin_lock_irqsave(&hw_data->drop_queue.lock, flags);
9535e58109bSIdo Schimmel 	skb_queue_splice_tail_init(&hw_data->drop_queue, &list);
9545e58109bSIdo Schimmel 	spin_unlock_irqrestore(&hw_data->drop_queue.lock, flags);
9555e58109bSIdo Schimmel 
9565e58109bSIdo Schimmel 	while ((skb = __skb_dequeue(&list)))
9575e58109bSIdo Schimmel 		net_dm_hw_packet_report(skb);
9585e58109bSIdo Schimmel }
9595e58109bSIdo Schimmel 
960edd3d007SIdo Schimmel static void
net_dm_hw_trap_packet_probe(void * ignore,const struct devlink * devlink,struct sk_buff * skb,const struct devlink_trap_metadata * metadata)9615855357cSIdo Schimmel net_dm_hw_trap_packet_probe(void *ignore, const struct devlink *devlink,
9625855357cSIdo Schimmel 			    struct sk_buff *skb,
9635855357cSIdo Schimmel 			    const struct devlink_trap_metadata *metadata)
9645855357cSIdo Schimmel {
965a848c05fSIdo Schimmel 	struct devlink_trap_metadata *n_hw_metadata;
9665855357cSIdo Schimmel 	ktime_t tstamp = ktime_get_real();
9675855357cSIdo Schimmel 	struct per_cpu_dm_data *hw_data;
9685855357cSIdo Schimmel 	struct sk_buff *nskb;
9695855357cSIdo Schimmel 	unsigned long flags;
9705855357cSIdo Schimmel 
97193e15596SIdo Schimmel 	if (metadata->trap_type == DEVLINK_TRAP_TYPE_CONTROL)
97293e15596SIdo Schimmel 		return;
97393e15596SIdo Schimmel 
9745855357cSIdo Schimmel 	if (!skb_mac_header_was_set(skb))
9755855357cSIdo Schimmel 		return;
9765855357cSIdo Schimmel 
9775855357cSIdo Schimmel 	nskb = skb_clone(skb, GFP_ATOMIC);
9785855357cSIdo Schimmel 	if (!nskb)
9795855357cSIdo Schimmel 		return;
9805855357cSIdo Schimmel 
9815855357cSIdo Schimmel 	n_hw_metadata = net_dm_hw_metadata_copy(metadata);
9825855357cSIdo Schimmel 	if (!n_hw_metadata)
9835855357cSIdo Schimmel 		goto free;
9845855357cSIdo Schimmel 
9855855357cSIdo Schimmel 	NET_DM_SKB_CB(nskb)->hw_metadata = n_hw_metadata;
9865855357cSIdo Schimmel 	nskb->tstamp = tstamp;
9875855357cSIdo Schimmel 
9885855357cSIdo Schimmel 	hw_data = this_cpu_ptr(&dm_hw_cpu_data);
9895855357cSIdo Schimmel 
9905855357cSIdo Schimmel 	spin_lock_irqsave(&hw_data->drop_queue.lock, flags);
9915855357cSIdo Schimmel 	if (skb_queue_len(&hw_data->drop_queue) < net_dm_queue_len)
9925855357cSIdo Schimmel 		__skb_queue_tail(&hw_data->drop_queue, nskb);
9935855357cSIdo Schimmel 	else
9945855357cSIdo Schimmel 		goto unlock_free;
9955855357cSIdo Schimmel 	spin_unlock_irqrestore(&hw_data->drop_queue.lock, flags);
9965855357cSIdo Schimmel 
9975855357cSIdo Schimmel 	schedule_work(&hw_data->dm_alert_work);
9985855357cSIdo Schimmel 
9995855357cSIdo Schimmel 	return;
10005855357cSIdo Schimmel 
10015855357cSIdo Schimmel unlock_free:
10025855357cSIdo Schimmel 	spin_unlock_irqrestore(&hw_data->drop_queue.lock, flags);
10035855357cSIdo Schimmel 	u64_stats_update_begin(&hw_data->stats.syncp);
1004c6cce71eSEric Dumazet 	u64_stats_inc(&hw_data->stats.dropped);
10055855357cSIdo Schimmel 	u64_stats_update_end(&hw_data->stats.syncp);
10065855357cSIdo Schimmel 	net_dm_hw_metadata_free(n_hw_metadata);
10075855357cSIdo Schimmel free:
10085855357cSIdo Schimmel 	consume_skb(nskb);
10095855357cSIdo Schimmel }
10105855357cSIdo Schimmel 
1011ca30707dSIdo Schimmel static const struct net_dm_alert_ops net_dm_alert_packet_ops = {
1012ca30707dSIdo Schimmel 	.kfree_skb_probe	= net_dm_packet_trace_kfree_skb_hit,
1013ca30707dSIdo Schimmel 	.napi_poll_probe	= net_dm_packet_trace_napi_poll_hit,
1014ca30707dSIdo Schimmel 	.work_item_func		= net_dm_packet_work,
10155e58109bSIdo Schimmel 	.hw_work_item_func	= net_dm_hw_packet_work,
10165855357cSIdo Schimmel 	.hw_trap_probe		= net_dm_hw_trap_packet_probe,
1017ca30707dSIdo Schimmel };
1018ca30707dSIdo Schimmel 
101928315f79SIdo Schimmel static const struct net_dm_alert_ops *net_dm_alert_ops_arr[] = {
102028315f79SIdo Schimmel 	[NET_DM_ALERT_MODE_SUMMARY]	= &net_dm_alert_summary_ops,
1021ca30707dSIdo Schimmel 	[NET_DM_ALERT_MODE_PACKET]	= &net_dm_alert_packet_ops,
102228315f79SIdo Schimmel };
102328315f79SIdo Schimmel 
10248ee2267aSIdo Schimmel #if IS_ENABLED(CONFIG_NET_DEVLINK)
net_dm_hw_probe_register(const struct net_dm_alert_ops * ops)10258ee2267aSIdo Schimmel static int net_dm_hw_probe_register(const struct net_dm_alert_ops *ops)
1026edd3d007SIdo Schimmel {
10278ee2267aSIdo Schimmel 	return register_trace_devlink_trap_report(ops->hw_trap_probe, NULL);
1028edd3d007SIdo Schimmel }
10298ee2267aSIdo Schimmel 
net_dm_hw_probe_unregister(const struct net_dm_alert_ops * ops)10308ee2267aSIdo Schimmel static void net_dm_hw_probe_unregister(const struct net_dm_alert_ops *ops)
10318ee2267aSIdo Schimmel {
10328ee2267aSIdo Schimmel 	unregister_trace_devlink_trap_report(ops->hw_trap_probe, NULL);
10338ee2267aSIdo Schimmel 	tracepoint_synchronize_unregister();
10348ee2267aSIdo Schimmel }
10358ee2267aSIdo Schimmel #else
net_dm_hw_probe_register(const struct net_dm_alert_ops * ops)10368ee2267aSIdo Schimmel static int net_dm_hw_probe_register(const struct net_dm_alert_ops *ops)
10378ee2267aSIdo Schimmel {
10388ee2267aSIdo Schimmel 	return -EOPNOTSUPP;
10398ee2267aSIdo Schimmel }
10408ee2267aSIdo Schimmel 
net_dm_hw_probe_unregister(const struct net_dm_alert_ops * ops)10418ee2267aSIdo Schimmel static void net_dm_hw_probe_unregister(const struct net_dm_alert_ops *ops)
10428ee2267aSIdo Schimmel {
10438ee2267aSIdo Schimmel }
10448ee2267aSIdo Schimmel #endif
1045edd3d007SIdo Schimmel 
net_dm_hw_monitor_start(struct netlink_ext_ack * extack)10468e94c3bcSIdo Schimmel static int net_dm_hw_monitor_start(struct netlink_ext_ack *extack)
10478e94c3bcSIdo Schimmel {
10488e94c3bcSIdo Schimmel 	const struct net_dm_alert_ops *ops;
10498ee2267aSIdo Schimmel 	int cpu, rc;
10508e94c3bcSIdo Schimmel 
10518e94c3bcSIdo Schimmel 	if (monitor_hw) {
10528e94c3bcSIdo Schimmel 		NL_SET_ERR_MSG_MOD(extack, "Hardware monitoring already enabled");
10538e94c3bcSIdo Schimmel 		return -EAGAIN;
10548e94c3bcSIdo Schimmel 	}
10558e94c3bcSIdo Schimmel 
10568e94c3bcSIdo Schimmel 	ops = net_dm_alert_ops_arr[net_dm_alert_mode];
10578e94c3bcSIdo Schimmel 
10588e94c3bcSIdo Schimmel 	if (!try_module_get(THIS_MODULE)) {
10598e94c3bcSIdo Schimmel 		NL_SET_ERR_MSG_MOD(extack, "Failed to take reference on module");
10608e94c3bcSIdo Schimmel 		return -ENODEV;
10618e94c3bcSIdo Schimmel 	}
10628e94c3bcSIdo Schimmel 
10638e94c3bcSIdo Schimmel 	for_each_possible_cpu(cpu) {
10648e94c3bcSIdo Schimmel 		struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu);
10658e94c3bcSIdo Schimmel 		struct net_dm_hw_entries *hw_entries;
10668e94c3bcSIdo Schimmel 
10678e94c3bcSIdo Schimmel 		INIT_WORK(&hw_data->dm_alert_work, ops->hw_work_item_func);
10688e94c3bcSIdo Schimmel 		timer_setup(&hw_data->send_timer, sched_send_work, 0);
10698e94c3bcSIdo Schimmel 		hw_entries = net_dm_hw_reset_per_cpu_data(hw_data);
10708e94c3bcSIdo Schimmel 		kfree(hw_entries);
10718e94c3bcSIdo Schimmel 	}
10728e94c3bcSIdo Schimmel 
10738ee2267aSIdo Schimmel 	rc = net_dm_hw_probe_register(ops);
10748ee2267aSIdo Schimmel 	if (rc) {
10758ee2267aSIdo Schimmel 		NL_SET_ERR_MSG_MOD(extack, "Failed to connect probe to devlink_trap_probe() tracepoint");
10768ee2267aSIdo Schimmel 		goto err_module_put;
10778ee2267aSIdo Schimmel 	}
10788ee2267aSIdo Schimmel 
10798e94c3bcSIdo Schimmel 	monitor_hw = true;
10808e94c3bcSIdo Schimmel 
10818e94c3bcSIdo Schimmel 	return 0;
10828ee2267aSIdo Schimmel 
10838ee2267aSIdo Schimmel err_module_put:
10849398e9c0SIdo Schimmel 	for_each_possible_cpu(cpu) {
10859398e9c0SIdo Schimmel 		struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu);
10869398e9c0SIdo Schimmel 		struct sk_buff *skb;
10879398e9c0SIdo Schimmel 
10889398e9c0SIdo Schimmel 		del_timer_sync(&hw_data->send_timer);
10899398e9c0SIdo Schimmel 		cancel_work_sync(&hw_data->dm_alert_work);
10909398e9c0SIdo Schimmel 		while ((skb = __skb_dequeue(&hw_data->drop_queue))) {
10919398e9c0SIdo Schimmel 			struct devlink_trap_metadata *hw_metadata;
10929398e9c0SIdo Schimmel 
10939398e9c0SIdo Schimmel 			hw_metadata = NET_DM_SKB_CB(skb)->hw_metadata;
10949398e9c0SIdo Schimmel 			net_dm_hw_metadata_free(hw_metadata);
10959398e9c0SIdo Schimmel 			consume_skb(skb);
10969398e9c0SIdo Schimmel 		}
10979398e9c0SIdo Schimmel 	}
10988ee2267aSIdo Schimmel 	module_put(THIS_MODULE);
10998ee2267aSIdo Schimmel 	return rc;
11008e94c3bcSIdo Schimmel }
11018e94c3bcSIdo Schimmel 
net_dm_hw_monitor_stop(struct netlink_ext_ack * extack)11028e94c3bcSIdo Schimmel static void net_dm_hw_monitor_stop(struct netlink_ext_ack *extack)
11038e94c3bcSIdo Schimmel {
11048ee2267aSIdo Schimmel 	const struct net_dm_alert_ops *ops;
11058e94c3bcSIdo Schimmel 	int cpu;
11068e94c3bcSIdo Schimmel 
1107dfa7f709SIdo Schimmel 	if (!monitor_hw) {
11088e94c3bcSIdo Schimmel 		NL_SET_ERR_MSG_MOD(extack, "Hardware monitoring already disabled");
1109dfa7f709SIdo Schimmel 		return;
1110dfa7f709SIdo Schimmel 	}
11118e94c3bcSIdo Schimmel 
11128ee2267aSIdo Schimmel 	ops = net_dm_alert_ops_arr[net_dm_alert_mode];
11138ee2267aSIdo Schimmel 
11148e94c3bcSIdo Schimmel 	monitor_hw = false;
11158e94c3bcSIdo Schimmel 
11168ee2267aSIdo Schimmel 	net_dm_hw_probe_unregister(ops);
11178e94c3bcSIdo Schimmel 
11188e94c3bcSIdo Schimmel 	for_each_possible_cpu(cpu) {
11198e94c3bcSIdo Schimmel 		struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu);
11208e94c3bcSIdo Schimmel 		struct sk_buff *skb;
11218e94c3bcSIdo Schimmel 
11228e94c3bcSIdo Schimmel 		del_timer_sync(&hw_data->send_timer);
11238e94c3bcSIdo Schimmel 		cancel_work_sync(&hw_data->dm_alert_work);
11248e94c3bcSIdo Schimmel 		while ((skb = __skb_dequeue(&hw_data->drop_queue))) {
1125a848c05fSIdo Schimmel 			struct devlink_trap_metadata *hw_metadata;
11268e94c3bcSIdo Schimmel 
11278e94c3bcSIdo Schimmel 			hw_metadata = NET_DM_SKB_CB(skb)->hw_metadata;
11288e94c3bcSIdo Schimmel 			net_dm_hw_metadata_free(hw_metadata);
11298e94c3bcSIdo Schimmel 			consume_skb(skb);
11308e94c3bcSIdo Schimmel 		}
11318e94c3bcSIdo Schimmel 	}
11328e94c3bcSIdo Schimmel 
11338e94c3bcSIdo Schimmel 	module_put(THIS_MODULE);
11348e94c3bcSIdo Schimmel }
11358e94c3bcSIdo Schimmel 
net_dm_trace_on_set(struct netlink_ext_ack * extack)11367c747838SIdo Schimmel static int net_dm_trace_on_set(struct netlink_ext_ack *extack)
11379a8afc8dSNeil Horman {
113828315f79SIdo Schimmel 	const struct net_dm_alert_ops *ops;
113970c69274SIdo Schimmel 	int cpu, rc;
11404ea7e386SNeil Horman 
114128315f79SIdo Schimmel 	ops = net_dm_alert_ops_arr[net_dm_alert_mode];
114228315f79SIdo Schimmel 
1143cad456d5SNeil Horman 	if (!try_module_get(THIS_MODULE)) {
114496510096SIdo Schimmel 		NL_SET_ERR_MSG_MOD(extack, "Failed to take reference on module");
11457c747838SIdo Schimmel 		return -ENODEV;
1146cad456d5SNeil Horman 	}
1147cad456d5SNeil Horman 
114870c69274SIdo Schimmel 	for_each_possible_cpu(cpu) {
114970c69274SIdo Schimmel 		struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu);
115044075f56SIdo Schimmel 		struct sk_buff *skb;
115170c69274SIdo Schimmel 
115228315f79SIdo Schimmel 		INIT_WORK(&data->dm_alert_work, ops->work_item_func);
115370c69274SIdo Schimmel 		timer_setup(&data->send_timer, sched_send_work, 0);
115444075f56SIdo Schimmel 		/* Allocate a new per-CPU skb for the summary alert message and
115544075f56SIdo Schimmel 		 * free the old one which might contain stale data from
115644075f56SIdo Schimmel 		 * previous tracing.
115744075f56SIdo Schimmel 		 */
115844075f56SIdo Schimmel 		skb = reset_per_cpu_data(data);
115944075f56SIdo Schimmel 		consume_skb(skb);
116070c69274SIdo Schimmel 	}
116170c69274SIdo Schimmel 
116228315f79SIdo Schimmel 	rc = register_trace_kfree_skb(ops->kfree_skb_probe, NULL);
11637c747838SIdo Schimmel 	if (rc) {
11647c747838SIdo Schimmel 		NL_SET_ERR_MSG_MOD(extack, "Failed to connect probe to kfree_skb() tracepoint");
11657c747838SIdo Schimmel 		goto err_module_put;
11667c747838SIdo Schimmel 	}
1167cad456d5SNeil Horman 
116828315f79SIdo Schimmel 	rc = register_trace_napi_poll(ops->napi_poll_probe, NULL);
11697c747838SIdo Schimmel 	if (rc) {
11707c747838SIdo Schimmel 		NL_SET_ERR_MSG_MOD(extack, "Failed to connect probe to napi_poll() tracepoint");
11717c747838SIdo Schimmel 		goto err_unregister_trace;
11727c747838SIdo Schimmel 	}
11737c747838SIdo Schimmel 
11747c747838SIdo Schimmel 	return 0;
11757c747838SIdo Schimmel 
11767c747838SIdo Schimmel err_unregister_trace:
117728315f79SIdo Schimmel 	unregister_trace_kfree_skb(ops->kfree_skb_probe, NULL);
11787c747838SIdo Schimmel err_module_put:
11799398e9c0SIdo Schimmel 	for_each_possible_cpu(cpu) {
11809398e9c0SIdo Schimmel 		struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu);
11819398e9c0SIdo Schimmel 		struct sk_buff *skb;
11829398e9c0SIdo Schimmel 
11839398e9c0SIdo Schimmel 		del_timer_sync(&data->send_timer);
11849398e9c0SIdo Schimmel 		cancel_work_sync(&data->dm_alert_work);
11859398e9c0SIdo Schimmel 		while ((skb = __skb_dequeue(&data->drop_queue)))
11869398e9c0SIdo Schimmel 			consume_skb(skb);
11879398e9c0SIdo Schimmel 	}
11887c747838SIdo Schimmel 	module_put(THIS_MODULE);
11897c747838SIdo Schimmel 	return rc;
11907c747838SIdo Schimmel }
11917c747838SIdo Schimmel 
net_dm_trace_off_set(void)11927c747838SIdo Schimmel static void net_dm_trace_off_set(void)
11937c747838SIdo Schimmel {
119428315f79SIdo Schimmel 	const struct net_dm_alert_ops *ops;
119570c69274SIdo Schimmel 	int cpu;
11967c747838SIdo Schimmel 
119728315f79SIdo Schimmel 	ops = net_dm_alert_ops_arr[net_dm_alert_mode];
119828315f79SIdo Schimmel 
119928315f79SIdo Schimmel 	unregister_trace_napi_poll(ops->napi_poll_probe, NULL);
120028315f79SIdo Schimmel 	unregister_trace_kfree_skb(ops->kfree_skb_probe, NULL);
12019a8afc8dSNeil Horman 
12029a8afc8dSNeil Horman 	tracepoint_synchronize_unregister();
12034ea7e386SNeil Horman 
120470c69274SIdo Schimmel 	/* Make sure we do not send notifications to user space after request
120570c69274SIdo Schimmel 	 * to stop tracing returns.
120670c69274SIdo Schimmel 	 */
120770c69274SIdo Schimmel 	for_each_possible_cpu(cpu) {
120870c69274SIdo Schimmel 		struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu);
1209ca30707dSIdo Schimmel 		struct sk_buff *skb;
121070c69274SIdo Schimmel 
121170c69274SIdo Schimmel 		del_timer_sync(&data->send_timer);
121270c69274SIdo Schimmel 		cancel_work_sync(&data->dm_alert_work);
1213ca30707dSIdo Schimmel 		while ((skb = __skb_dequeue(&data->drop_queue)))
1214ca30707dSIdo Schimmel 			consume_skb(skb);
121570c69274SIdo Schimmel 	}
121670c69274SIdo Schimmel 
1217cad456d5SNeil Horman 	module_put(THIS_MODULE);
12187c747838SIdo Schimmel }
1219cad456d5SNeil Horman 
set_all_monitor_traces(int state,struct netlink_ext_ack * extack)12207c747838SIdo Schimmel static int set_all_monitor_traces(int state, struct netlink_ext_ack *extack)
12217c747838SIdo Schimmel {
12227c747838SIdo Schimmel 	int rc = 0;
12237c747838SIdo Schimmel 
12247c747838SIdo Schimmel 	if (state == trace_state) {
12257c747838SIdo Schimmel 		NL_SET_ERR_MSG_MOD(extack, "Trace state already set to requested state");
12267c747838SIdo Schimmel 		return -EAGAIN;
12277c747838SIdo Schimmel 	}
12287c747838SIdo Schimmel 
12297c747838SIdo Schimmel 	switch (state) {
12307c747838SIdo Schimmel 	case TRACE_ON:
12317c747838SIdo Schimmel 		rc = net_dm_trace_on_set(extack);
12327c747838SIdo Schimmel 		break;
12337c747838SIdo Schimmel 	case TRACE_OFF:
12347c747838SIdo Schimmel 		net_dm_trace_off_set();
12359a8afc8dSNeil Horman 		break;
12369a8afc8dSNeil Horman 	default:
12379a8afc8dSNeil Horman 		rc = 1;
12389a8afc8dSNeil Horman 		break;
12399a8afc8dSNeil Horman 	}
12409a8afc8dSNeil Horman 
12414ea7e386SNeil Horman 	if (!rc)
12424ea7e386SNeil Horman 		trace_state = state;
12434b706372SNeil Horman 	else
12444b706372SNeil Horman 		rc = -EINPROGRESS;
12454ea7e386SNeil Horman 
12469a8afc8dSNeil Horman 	return rc;
12479a8afc8dSNeil Horman }
12489a8afc8dSNeil Horman 
net_dm_is_monitoring(void)124980cebed8SIdo Schimmel static bool net_dm_is_monitoring(void)
125080cebed8SIdo Schimmel {
125180cebed8SIdo Schimmel 	return trace_state == TRACE_ON || monitor_hw;
125280cebed8SIdo Schimmel }
125380cebed8SIdo Schimmel 
net_dm_alert_mode_get_from_info(struct genl_info * info,enum net_dm_alert_mode * p_alert_mode)1254ca30707dSIdo Schimmel static int net_dm_alert_mode_get_from_info(struct genl_info *info,
1255ca30707dSIdo Schimmel 					   enum net_dm_alert_mode *p_alert_mode)
1256ca30707dSIdo Schimmel {
1257ca30707dSIdo Schimmel 	u8 val;
1258ca30707dSIdo Schimmel 
1259ca30707dSIdo Schimmel 	val = nla_get_u8(info->attrs[NET_DM_ATTR_ALERT_MODE]);
1260ca30707dSIdo Schimmel 
1261ca30707dSIdo Schimmel 	switch (val) {
1262df561f66SGustavo A. R. Silva 	case NET_DM_ALERT_MODE_SUMMARY:
1263ca30707dSIdo Schimmel 	case NET_DM_ALERT_MODE_PACKET:
1264ca30707dSIdo Schimmel 		*p_alert_mode = val;
1265ca30707dSIdo Schimmel 		break;
1266ca30707dSIdo Schimmel 	default:
1267ca30707dSIdo Schimmel 		return -EINVAL;
1268ca30707dSIdo Schimmel 	}
1269ca30707dSIdo Schimmel 
1270ca30707dSIdo Schimmel 	return 0;
1271ca30707dSIdo Schimmel }
1272ca30707dSIdo Schimmel 
net_dm_alert_mode_set(struct genl_info * info)1273ca30707dSIdo Schimmel static int net_dm_alert_mode_set(struct genl_info *info)
1274ca30707dSIdo Schimmel {
1275ca30707dSIdo Schimmel 	struct netlink_ext_ack *extack = info->extack;
1276ca30707dSIdo Schimmel 	enum net_dm_alert_mode alert_mode;
1277ca30707dSIdo Schimmel 	int rc;
1278ca30707dSIdo Schimmel 
1279ca30707dSIdo Schimmel 	if (!info->attrs[NET_DM_ATTR_ALERT_MODE])
1280ca30707dSIdo Schimmel 		return 0;
1281ca30707dSIdo Schimmel 
1282ca30707dSIdo Schimmel 	rc = net_dm_alert_mode_get_from_info(info, &alert_mode);
1283ca30707dSIdo Schimmel 	if (rc) {
1284ca30707dSIdo Schimmel 		NL_SET_ERR_MSG_MOD(extack, "Invalid alert mode");
1285ca30707dSIdo Schimmel 		return -EINVAL;
1286ca30707dSIdo Schimmel 	}
1287ca30707dSIdo Schimmel 
1288ca30707dSIdo Schimmel 	net_dm_alert_mode = alert_mode;
1289ca30707dSIdo Schimmel 
1290ca30707dSIdo Schimmel 	return 0;
1291ca30707dSIdo Schimmel }
1292ca30707dSIdo Schimmel 
net_dm_trunc_len_set(struct genl_info * info)129357986617SIdo Schimmel static void net_dm_trunc_len_set(struct genl_info *info)
129457986617SIdo Schimmel {
129557986617SIdo Schimmel 	if (!info->attrs[NET_DM_ATTR_TRUNC_LEN])
129657986617SIdo Schimmel 		return;
129757986617SIdo Schimmel 
129857986617SIdo Schimmel 	net_dm_trunc_len = nla_get_u32(info->attrs[NET_DM_ATTR_TRUNC_LEN]);
129957986617SIdo Schimmel }
130057986617SIdo Schimmel 
net_dm_queue_len_set(struct genl_info * info)130130328d46SIdo Schimmel static void net_dm_queue_len_set(struct genl_info *info)
130230328d46SIdo Schimmel {
130330328d46SIdo Schimmel 	if (!info->attrs[NET_DM_ATTR_QUEUE_LEN])
130430328d46SIdo Schimmel 		return;
130530328d46SIdo Schimmel 
130630328d46SIdo Schimmel 	net_dm_queue_len = nla_get_u32(info->attrs[NET_DM_ATTR_QUEUE_LEN]);
130730328d46SIdo Schimmel }
130830328d46SIdo Schimmel 
net_dm_cmd_config(struct sk_buff * skb,struct genl_info * info)13099a8afc8dSNeil Horman static int net_dm_cmd_config(struct sk_buff *skb,
13109a8afc8dSNeil Horman 			struct genl_info *info)
13119a8afc8dSNeil Horman {
1312ca30707dSIdo Schimmel 	struct netlink_ext_ack *extack = info->extack;
1313ca30707dSIdo Schimmel 	int rc;
131496510096SIdo Schimmel 
131580cebed8SIdo Schimmel 	if (net_dm_is_monitoring()) {
131680cebed8SIdo Schimmel 		NL_SET_ERR_MSG_MOD(extack, "Cannot configure drop monitor during monitoring");
1317ca30707dSIdo Schimmel 		return -EBUSY;
1318ca30707dSIdo Schimmel 	}
1319ca30707dSIdo Schimmel 
1320ca30707dSIdo Schimmel 	rc = net_dm_alert_mode_set(info);
1321ca30707dSIdo Schimmel 	if (rc)
1322ca30707dSIdo Schimmel 		return rc;
1323ca30707dSIdo Schimmel 
132457986617SIdo Schimmel 	net_dm_trunc_len_set(info);
132557986617SIdo Schimmel 
132630328d46SIdo Schimmel 	net_dm_queue_len_set(info);
132730328d46SIdo Schimmel 
1328ca30707dSIdo Schimmel 	return 0;
13299a8afc8dSNeil Horman }
13309a8afc8dSNeil Horman 
net_dm_monitor_start(bool set_sw,bool set_hw,struct netlink_ext_ack * extack)13318e94c3bcSIdo Schimmel static int net_dm_monitor_start(bool set_sw, bool set_hw,
13328e94c3bcSIdo Schimmel 				struct netlink_ext_ack *extack)
13338e94c3bcSIdo Schimmel {
13348e94c3bcSIdo Schimmel 	bool sw_set = false;
13358e94c3bcSIdo Schimmel 	int rc;
13368e94c3bcSIdo Schimmel 
13378e94c3bcSIdo Schimmel 	if (set_sw) {
13388e94c3bcSIdo Schimmel 		rc = set_all_monitor_traces(TRACE_ON, extack);
13398e94c3bcSIdo Schimmel 		if (rc)
13408e94c3bcSIdo Schimmel 			return rc;
13418e94c3bcSIdo Schimmel 		sw_set = true;
13428e94c3bcSIdo Schimmel 	}
13438e94c3bcSIdo Schimmel 
13448e94c3bcSIdo Schimmel 	if (set_hw) {
13458e94c3bcSIdo Schimmel 		rc = net_dm_hw_monitor_start(extack);
13468e94c3bcSIdo Schimmel 		if (rc)
13478e94c3bcSIdo Schimmel 			goto err_monitor_hw;
13488e94c3bcSIdo Schimmel 	}
13498e94c3bcSIdo Schimmel 
13508e94c3bcSIdo Schimmel 	return 0;
13518e94c3bcSIdo Schimmel 
13528e94c3bcSIdo Schimmel err_monitor_hw:
13538e94c3bcSIdo Schimmel 	if (sw_set)
13548e94c3bcSIdo Schimmel 		set_all_monitor_traces(TRACE_OFF, extack);
13558e94c3bcSIdo Schimmel 	return rc;
13568e94c3bcSIdo Schimmel }
13578e94c3bcSIdo Schimmel 
net_dm_monitor_stop(bool set_sw,bool set_hw,struct netlink_ext_ack * extack)13588e94c3bcSIdo Schimmel static void net_dm_monitor_stop(bool set_sw, bool set_hw,
13598e94c3bcSIdo Schimmel 				struct netlink_ext_ack *extack)
13608e94c3bcSIdo Schimmel {
13618e94c3bcSIdo Schimmel 	if (set_hw)
13628e94c3bcSIdo Schimmel 		net_dm_hw_monitor_stop(extack);
13638e94c3bcSIdo Schimmel 	if (set_sw)
13648e94c3bcSIdo Schimmel 		set_all_monitor_traces(TRACE_OFF, extack);
13658e94c3bcSIdo Schimmel }
13668e94c3bcSIdo Schimmel 
net_dm_cmd_trace(struct sk_buff * skb,struct genl_info * info)13679a8afc8dSNeil Horman static int net_dm_cmd_trace(struct sk_buff *skb,
13689a8afc8dSNeil Horman 			struct genl_info *info)
13699a8afc8dSNeil Horman {
13708e94c3bcSIdo Schimmel 	bool set_sw = !!info->attrs[NET_DM_ATTR_SW_DROPS];
13718e94c3bcSIdo Schimmel 	bool set_hw = !!info->attrs[NET_DM_ATTR_HW_DROPS];
13728e94c3bcSIdo Schimmel 	struct netlink_ext_ack *extack = info->extack;
13738e94c3bcSIdo Schimmel 
13748e94c3bcSIdo Schimmel 	/* To maintain backward compatibility, we start / stop monitoring of
13758e94c3bcSIdo Schimmel 	 * software drops if no flag is specified.
13768e94c3bcSIdo Schimmel 	 */
13778e94c3bcSIdo Schimmel 	if (!set_sw && !set_hw)
13788e94c3bcSIdo Schimmel 		set_sw = true;
13798e94c3bcSIdo Schimmel 
13809a8afc8dSNeil Horman 	switch (info->genlhdr->cmd) {
13819a8afc8dSNeil Horman 	case NET_DM_CMD_START:
13828e94c3bcSIdo Schimmel 		return net_dm_monitor_start(set_sw, set_hw, extack);
13839a8afc8dSNeil Horman 	case NET_DM_CMD_STOP:
13848e94c3bcSIdo Schimmel 		net_dm_monitor_stop(set_sw, set_hw, extack);
13858e94c3bcSIdo Schimmel 		return 0;
13869a8afc8dSNeil Horman 	}
13879a8afc8dSNeil Horman 
13882230a7efSIdo Schimmel 	return -EOPNOTSUPP;
13899a8afc8dSNeil Horman }
13909a8afc8dSNeil Horman 
net_dm_config_fill(struct sk_buff * msg,struct genl_info * info)1391444be061SIdo Schimmel static int net_dm_config_fill(struct sk_buff *msg, struct genl_info *info)
1392444be061SIdo Schimmel {
1393444be061SIdo Schimmel 	void *hdr;
1394444be061SIdo Schimmel 
1395444be061SIdo Schimmel 	hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
1396444be061SIdo Schimmel 			  &net_drop_monitor_family, 0, NET_DM_CMD_CONFIG_NEW);
1397444be061SIdo Schimmel 	if (!hdr)
1398444be061SIdo Schimmel 		return -EMSGSIZE;
1399444be061SIdo Schimmel 
1400444be061SIdo Schimmel 	if (nla_put_u8(msg, NET_DM_ATTR_ALERT_MODE, net_dm_alert_mode))
1401444be061SIdo Schimmel 		goto nla_put_failure;
1402444be061SIdo Schimmel 
1403444be061SIdo Schimmel 	if (nla_put_u32(msg, NET_DM_ATTR_TRUNC_LEN, net_dm_trunc_len))
1404444be061SIdo Schimmel 		goto nla_put_failure;
1405444be061SIdo Schimmel 
140630328d46SIdo Schimmel 	if (nla_put_u32(msg, NET_DM_ATTR_QUEUE_LEN, net_dm_queue_len))
140730328d46SIdo Schimmel 		goto nla_put_failure;
140830328d46SIdo Schimmel 
1409444be061SIdo Schimmel 	genlmsg_end(msg, hdr);
1410444be061SIdo Schimmel 
1411444be061SIdo Schimmel 	return 0;
1412444be061SIdo Schimmel 
1413444be061SIdo Schimmel nla_put_failure:
1414444be061SIdo Schimmel 	genlmsg_cancel(msg, hdr);
1415444be061SIdo Schimmel 	return -EMSGSIZE;
1416444be061SIdo Schimmel }
1417444be061SIdo Schimmel 
net_dm_cmd_config_get(struct sk_buff * skb,struct genl_info * info)1418444be061SIdo Schimmel static int net_dm_cmd_config_get(struct sk_buff *skb, struct genl_info *info)
1419444be061SIdo Schimmel {
1420444be061SIdo Schimmel 	struct sk_buff *msg;
1421444be061SIdo Schimmel 	int rc;
1422444be061SIdo Schimmel 
1423444be061SIdo Schimmel 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
1424444be061SIdo Schimmel 	if (!msg)
1425444be061SIdo Schimmel 		return -ENOMEM;
1426444be061SIdo Schimmel 
1427444be061SIdo Schimmel 	rc = net_dm_config_fill(msg, info);
1428444be061SIdo Schimmel 	if (rc)
1429444be061SIdo Schimmel 		goto free_msg;
1430444be061SIdo Schimmel 
1431444be061SIdo Schimmel 	return genlmsg_reply(msg, info);
1432444be061SIdo Schimmel 
1433444be061SIdo Schimmel free_msg:
1434444be061SIdo Schimmel 	nlmsg_free(msg);
1435444be061SIdo Schimmel 	return rc;
1436444be061SIdo Schimmel }
1437444be061SIdo Schimmel 
net_dm_stats_read(struct net_dm_stats * stats)1438e9feb580SIdo Schimmel static void net_dm_stats_read(struct net_dm_stats *stats)
1439e9feb580SIdo Schimmel {
1440e9feb580SIdo Schimmel 	int cpu;
1441e9feb580SIdo Schimmel 
1442e9feb580SIdo Schimmel 	memset(stats, 0, sizeof(*stats));
1443e9feb580SIdo Schimmel 	for_each_possible_cpu(cpu) {
1444e9feb580SIdo Schimmel 		struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu);
1445e9feb580SIdo Schimmel 		struct net_dm_stats *cpu_stats = &data->stats;
1446e9feb580SIdo Schimmel 		unsigned int start;
1447e9feb580SIdo Schimmel 		u64 dropped;
1448e9feb580SIdo Schimmel 
1449e9feb580SIdo Schimmel 		do {
1450d120d1a6SThomas Gleixner 			start = u64_stats_fetch_begin(&cpu_stats->syncp);
1451c6cce71eSEric Dumazet 			dropped = u64_stats_read(&cpu_stats->dropped);
1452d120d1a6SThomas Gleixner 		} while (u64_stats_fetch_retry(&cpu_stats->syncp, start));
1453e9feb580SIdo Schimmel 
1454c6cce71eSEric Dumazet 		u64_stats_add(&stats->dropped, dropped);
1455e9feb580SIdo Schimmel 	}
1456e9feb580SIdo Schimmel }
1457e9feb580SIdo Schimmel 
net_dm_stats_put(struct sk_buff * msg)1458e9feb580SIdo Schimmel static int net_dm_stats_put(struct sk_buff *msg)
1459e9feb580SIdo Schimmel {
1460e9feb580SIdo Schimmel 	struct net_dm_stats stats;
1461e9feb580SIdo Schimmel 	struct nlattr *attr;
1462e9feb580SIdo Schimmel 
1463e9feb580SIdo Schimmel 	net_dm_stats_read(&stats);
1464e9feb580SIdo Schimmel 
1465e9feb580SIdo Schimmel 	attr = nla_nest_start(msg, NET_DM_ATTR_STATS);
1466e9feb580SIdo Schimmel 	if (!attr)
1467e9feb580SIdo Schimmel 		return -EMSGSIZE;
1468e9feb580SIdo Schimmel 
1469e9feb580SIdo Schimmel 	if (nla_put_u64_64bit(msg, NET_DM_ATTR_STATS_DROPPED,
1470c6cce71eSEric Dumazet 			      u64_stats_read(&stats.dropped), NET_DM_ATTR_PAD))
1471e9feb580SIdo Schimmel 		goto nla_put_failure;
1472e9feb580SIdo Schimmel 
1473e9feb580SIdo Schimmel 	nla_nest_end(msg, attr);
1474e9feb580SIdo Schimmel 
1475e9feb580SIdo Schimmel 	return 0;
1476e9feb580SIdo Schimmel 
1477e9feb580SIdo Schimmel nla_put_failure:
1478e9feb580SIdo Schimmel 	nla_nest_cancel(msg, attr);
1479e9feb580SIdo Schimmel 	return -EMSGSIZE;
1480e9feb580SIdo Schimmel }
1481e9feb580SIdo Schimmel 
net_dm_hw_stats_read(struct net_dm_stats * stats)14825e58109bSIdo Schimmel static void net_dm_hw_stats_read(struct net_dm_stats *stats)
14835e58109bSIdo Schimmel {
14845e58109bSIdo Schimmel 	int cpu;
14855e58109bSIdo Schimmel 
14865e58109bSIdo Schimmel 	memset(stats, 0, sizeof(*stats));
14875e58109bSIdo Schimmel 	for_each_possible_cpu(cpu) {
14885e58109bSIdo Schimmel 		struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu);
14895e58109bSIdo Schimmel 		struct net_dm_stats *cpu_stats = &hw_data->stats;
14905e58109bSIdo Schimmel 		unsigned int start;
14915e58109bSIdo Schimmel 		u64 dropped;
14925e58109bSIdo Schimmel 
14935e58109bSIdo Schimmel 		do {
1494d120d1a6SThomas Gleixner 			start = u64_stats_fetch_begin(&cpu_stats->syncp);
1495c6cce71eSEric Dumazet 			dropped = u64_stats_read(&cpu_stats->dropped);
1496d120d1a6SThomas Gleixner 		} while (u64_stats_fetch_retry(&cpu_stats->syncp, start));
14975e58109bSIdo Schimmel 
1498c6cce71eSEric Dumazet 		u64_stats_add(&stats->dropped, dropped);
14995e58109bSIdo Schimmel 	}
15005e58109bSIdo Schimmel }
15015e58109bSIdo Schimmel 
net_dm_hw_stats_put(struct sk_buff * msg)15025e58109bSIdo Schimmel static int net_dm_hw_stats_put(struct sk_buff *msg)
15035e58109bSIdo Schimmel {
15045e58109bSIdo Schimmel 	struct net_dm_stats stats;
15055e58109bSIdo Schimmel 	struct nlattr *attr;
15065e58109bSIdo Schimmel 
15075e58109bSIdo Schimmel 	net_dm_hw_stats_read(&stats);
15085e58109bSIdo Schimmel 
15095e58109bSIdo Schimmel 	attr = nla_nest_start(msg, NET_DM_ATTR_HW_STATS);
15105e58109bSIdo Schimmel 	if (!attr)
15115e58109bSIdo Schimmel 		return -EMSGSIZE;
15125e58109bSIdo Schimmel 
15135e58109bSIdo Schimmel 	if (nla_put_u64_64bit(msg, NET_DM_ATTR_STATS_DROPPED,
1514c6cce71eSEric Dumazet 			      u64_stats_read(&stats.dropped), NET_DM_ATTR_PAD))
15155e58109bSIdo Schimmel 		goto nla_put_failure;
15165e58109bSIdo Schimmel 
15175e58109bSIdo Schimmel 	nla_nest_end(msg, attr);
15185e58109bSIdo Schimmel 
15195e58109bSIdo Schimmel 	return 0;
15205e58109bSIdo Schimmel 
15215e58109bSIdo Schimmel nla_put_failure:
15225e58109bSIdo Schimmel 	nla_nest_cancel(msg, attr);
15235e58109bSIdo Schimmel 	return -EMSGSIZE;
15245e58109bSIdo Schimmel }
15255e58109bSIdo Schimmel 
net_dm_stats_fill(struct sk_buff * msg,struct genl_info * info)1526e9feb580SIdo Schimmel static int net_dm_stats_fill(struct sk_buff *msg, struct genl_info *info)
1527e9feb580SIdo Schimmel {
1528e9feb580SIdo Schimmel 	void *hdr;
1529e9feb580SIdo Schimmel 	int rc;
1530e9feb580SIdo Schimmel 
1531e9feb580SIdo Schimmel 	hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
1532e9feb580SIdo Schimmel 			  &net_drop_monitor_family, 0, NET_DM_CMD_STATS_NEW);
1533e9feb580SIdo Schimmel 	if (!hdr)
1534e9feb580SIdo Schimmel 		return -EMSGSIZE;
1535e9feb580SIdo Schimmel 
1536e9feb580SIdo Schimmel 	rc = net_dm_stats_put(msg);
1537e9feb580SIdo Schimmel 	if (rc)
1538e9feb580SIdo Schimmel 		goto nla_put_failure;
1539e9feb580SIdo Schimmel 
15405e58109bSIdo Schimmel 	rc = net_dm_hw_stats_put(msg);
15415e58109bSIdo Schimmel 	if (rc)
15425e58109bSIdo Schimmel 		goto nla_put_failure;
15435e58109bSIdo Schimmel 
1544e9feb580SIdo Schimmel 	genlmsg_end(msg, hdr);
1545e9feb580SIdo Schimmel 
1546e9feb580SIdo Schimmel 	return 0;
1547e9feb580SIdo Schimmel 
1548e9feb580SIdo Schimmel nla_put_failure:
1549e9feb580SIdo Schimmel 	genlmsg_cancel(msg, hdr);
1550e9feb580SIdo Schimmel 	return -EMSGSIZE;
1551e9feb580SIdo Schimmel }
1552e9feb580SIdo Schimmel 
net_dm_cmd_stats_get(struct sk_buff * skb,struct genl_info * info)1553e9feb580SIdo Schimmel static int net_dm_cmd_stats_get(struct sk_buff *skb, struct genl_info *info)
1554e9feb580SIdo Schimmel {
1555e9feb580SIdo Schimmel 	struct sk_buff *msg;
1556e9feb580SIdo Schimmel 	int rc;
1557e9feb580SIdo Schimmel 
1558e9feb580SIdo Schimmel 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
1559e9feb580SIdo Schimmel 	if (!msg)
1560e9feb580SIdo Schimmel 		return -ENOMEM;
1561e9feb580SIdo Schimmel 
1562e9feb580SIdo Schimmel 	rc = net_dm_stats_fill(msg, info);
1563e9feb580SIdo Schimmel 	if (rc)
1564e9feb580SIdo Schimmel 		goto free_msg;
1565e9feb580SIdo Schimmel 
1566e9feb580SIdo Schimmel 	return genlmsg_reply(msg, info);
1567e9feb580SIdo Schimmel 
1568e9feb580SIdo Schimmel free_msg:
1569e9feb580SIdo Schimmel 	nlmsg_free(msg);
1570e9feb580SIdo Schimmel 	return rc;
1571e9feb580SIdo Schimmel }
1572e9feb580SIdo Schimmel 
dropmon_net_event(struct notifier_block * ev_block,unsigned long event,void * ptr)15734ea7e386SNeil Horman static int dropmon_net_event(struct notifier_block *ev_block,
15744ea7e386SNeil Horman 			     unsigned long event, void *ptr)
15754ea7e386SNeil Horman {
1576351638e7SJiri Pirko 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
1577b26ef81cSEric Dumazet 	struct dm_hw_stat_delta *stat;
15784ea7e386SNeil Horman 
15794ea7e386SNeil Horman 	switch (event) {
15804ea7e386SNeil Horman 	case NETDEV_REGISTER:
1581b26ef81cSEric Dumazet 		if (WARN_ON_ONCE(rtnl_dereference(dev->dm_private)))
1582b26ef81cSEric Dumazet 			break;
1583b26ef81cSEric Dumazet 		stat = kzalloc(sizeof(*stat), GFP_KERNEL);
1584b26ef81cSEric Dumazet 		if (!stat)
1585b26ef81cSEric Dumazet 			break;
15864ea7e386SNeil Horman 
1587b26ef81cSEric Dumazet 		stat->last_rx = jiffies;
1588b26ef81cSEric Dumazet 		rcu_assign_pointer(dev->dm_private, stat);
15894ea7e386SNeil Horman 
15904ea7e386SNeil Horman 		break;
15914ea7e386SNeil Horman 	case NETDEV_UNREGISTER:
1592b26ef81cSEric Dumazet 		stat = rtnl_dereference(dev->dm_private);
1593b26ef81cSEric Dumazet 		if (stat) {
1594b26ef81cSEric Dumazet 			rcu_assign_pointer(dev->dm_private, NULL);
1595b26ef81cSEric Dumazet 			kfree_rcu(stat, rcu);
1596b26ef81cSEric Dumazet 		}
15974ea7e386SNeil Horman 		break;
15984ea7e386SNeil Horman 	}
15994ea7e386SNeil Horman 	return NOTIFY_DONE;
16004ea7e386SNeil Horman }
16019a8afc8dSNeil Horman 
1602ca30707dSIdo Schimmel static const struct nla_policy net_dm_nl_policy[NET_DM_ATTR_MAX + 1] = {
1603ca30707dSIdo Schimmel 	[NET_DM_ATTR_UNSPEC] = { .strict_start_type = NET_DM_ATTR_UNSPEC + 1 },
1604ca30707dSIdo Schimmel 	[NET_DM_ATTR_ALERT_MODE] = { .type = NLA_U8 },
160557986617SIdo Schimmel 	[NET_DM_ATTR_TRUNC_LEN] = { .type = NLA_U32 },
160630328d46SIdo Schimmel 	[NET_DM_ATTR_QUEUE_LEN] = { .type = NLA_U32 },
16078e94c3bcSIdo Schimmel 	[NET_DM_ATTR_SW_DROPS]	= {. type = NLA_FLAG },
16088e94c3bcSIdo Schimmel 	[NET_DM_ATTR_HW_DROPS]	= {. type = NLA_FLAG },
1609ca30707dSIdo Schimmel };
1610ca30707dSIdo Schimmel 
161166a9b928SJakub Kicinski static const struct genl_small_ops dropmon_ops[] = {
16129a8afc8dSNeil Horman 	{
16139a8afc8dSNeil Horman 		.cmd = NET_DM_CMD_CONFIG,
1614ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
16159a8afc8dSNeil Horman 		.doit = net_dm_cmd_config,
1616c5ab9b1cSIdo Schimmel 		.flags = GENL_ADMIN_PERM,
16179a8afc8dSNeil Horman 	},
16189a8afc8dSNeil Horman 	{
16199a8afc8dSNeil Horman 		.cmd = NET_DM_CMD_START,
1620ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
16219a8afc8dSNeil Horman 		.doit = net_dm_cmd_trace,
1622e036a325SIdo Schimmel 		.flags = GENL_ADMIN_PERM,
16239a8afc8dSNeil Horman 	},
16249a8afc8dSNeil Horman 	{
16259a8afc8dSNeil Horman 		.cmd = NET_DM_CMD_STOP,
1626ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
16279a8afc8dSNeil Horman 		.doit = net_dm_cmd_trace,
1628e036a325SIdo Schimmel 		.flags = GENL_ADMIN_PERM,
16299a8afc8dSNeil Horman 	},
1630444be061SIdo Schimmel 	{
1631444be061SIdo Schimmel 		.cmd = NET_DM_CMD_CONFIG_GET,
1632444be061SIdo Schimmel 		.doit = net_dm_cmd_config_get,
1633444be061SIdo Schimmel 	},
1634e9feb580SIdo Schimmel 	{
1635e9feb580SIdo Schimmel 		.cmd = NET_DM_CMD_STATS_GET,
1636e9feb580SIdo Schimmel 		.doit = net_dm_cmd_stats_get,
1637e9feb580SIdo Schimmel 	},
16389a8afc8dSNeil Horman };
16399a8afc8dSNeil Horman 
net_dm_nl_pre_doit(const struct genl_split_ops * ops,struct sk_buff * skb,struct genl_info * info)164020b0b53aSJakub Kicinski static int net_dm_nl_pre_doit(const struct genl_split_ops *ops,
1641b19d9550SIdo Schimmel 			      struct sk_buff *skb, struct genl_info *info)
1642b19d9550SIdo Schimmel {
1643b19d9550SIdo Schimmel 	mutex_lock(&net_dm_mutex);
1644b19d9550SIdo Schimmel 
1645b19d9550SIdo Schimmel 	return 0;
1646b19d9550SIdo Schimmel }
1647b19d9550SIdo Schimmel 
net_dm_nl_post_doit(const struct genl_split_ops * ops,struct sk_buff * skb,struct genl_info * info)164820b0b53aSJakub Kicinski static void net_dm_nl_post_doit(const struct genl_split_ops *ops,
1649b19d9550SIdo Schimmel 				struct sk_buff *skb, struct genl_info *info)
1650b19d9550SIdo Schimmel {
1651b19d9550SIdo Schimmel 	mutex_unlock(&net_dm_mutex);
1652b19d9550SIdo Schimmel }
1653b19d9550SIdo Schimmel 
165456989f6dSJohannes Berg static struct genl_family net_drop_monitor_family __ro_after_init = {
1655489111e5SJohannes Berg 	.hdrsize        = 0,
1656489111e5SJohannes Berg 	.name           = "NET_DM",
1657489111e5SJohannes Berg 	.version        = 2,
1658ca30707dSIdo Schimmel 	.maxattr	= NET_DM_ATTR_MAX,
1659ca30707dSIdo Schimmel 	.policy		= net_dm_nl_policy,
1660b19d9550SIdo Schimmel 	.pre_doit	= net_dm_nl_pre_doit,
1661b19d9550SIdo Schimmel 	.post_doit	= net_dm_nl_post_doit,
1662489111e5SJohannes Berg 	.module		= THIS_MODULE,
166366a9b928SJakub Kicinski 	.small_ops	= dropmon_ops,
166466a9b928SJakub Kicinski 	.n_small_ops	= ARRAY_SIZE(dropmon_ops),
16659c5d03d3SJakub Kicinski 	.resv_start_op	= NET_DM_CMD_STATS_GET + 1,
1666489111e5SJohannes Berg 	.mcgrps		= dropmon_mcgrps,
1667489111e5SJohannes Berg 	.n_mcgrps	= ARRAY_SIZE(dropmon_mcgrps),
1668489111e5SJohannes Berg };
1669489111e5SJohannes Berg 
16704ea7e386SNeil Horman static struct notifier_block dropmon_net_notifier = {
16714ea7e386SNeil Horman 	.notifier_call = dropmon_net_event
16724ea7e386SNeil Horman };
16734ea7e386SNeil Horman 
__net_dm_cpu_data_init(struct per_cpu_dm_data * data)16749b63f57dSIdo Schimmel static void __net_dm_cpu_data_init(struct per_cpu_dm_data *data)
16759b63f57dSIdo Schimmel {
167676ce2f91SWander Lairson Costa 	raw_spin_lock_init(&data->lock);
16779b63f57dSIdo Schimmel 	skb_queue_head_init(&data->drop_queue);
16789b63f57dSIdo Schimmel 	u64_stats_init(&data->stats.syncp);
16799b63f57dSIdo Schimmel }
16809b63f57dSIdo Schimmel 
__net_dm_cpu_data_fini(struct per_cpu_dm_data * data)16819b63f57dSIdo Schimmel static void __net_dm_cpu_data_fini(struct per_cpu_dm_data *data)
16829b63f57dSIdo Schimmel {
16839b63f57dSIdo Schimmel 	WARN_ON(!skb_queue_empty(&data->drop_queue));
16849b63f57dSIdo Schimmel }
16859b63f57dSIdo Schimmel 
net_dm_cpu_data_init(int cpu)16869b63f57dSIdo Schimmel static void net_dm_cpu_data_init(int cpu)
16879a8afc8dSNeil Horman {
16889a8afc8dSNeil Horman 	struct per_cpu_dm_data *data;
16899b63f57dSIdo Schimmel 
16909b63f57dSIdo Schimmel 	data = &per_cpu(dm_cpu_data, cpu);
16919b63f57dSIdo Schimmel 	__net_dm_cpu_data_init(data);
16929b63f57dSIdo Schimmel }
16939b63f57dSIdo Schimmel 
net_dm_cpu_data_fini(int cpu)16949b63f57dSIdo Schimmel static void net_dm_cpu_data_fini(int cpu)
16959b63f57dSIdo Schimmel {
16969b63f57dSIdo Schimmel 	struct per_cpu_dm_data *data;
16979b63f57dSIdo Schimmel 
16989b63f57dSIdo Schimmel 	data = &per_cpu(dm_cpu_data, cpu);
16999b63f57dSIdo Schimmel 	/* At this point, we should have exclusive access
17009b63f57dSIdo Schimmel 	 * to this struct and can free the skb inside it.
17019b63f57dSIdo Schimmel 	 */
17029b63f57dSIdo Schimmel 	consume_skb(data->skb);
17039b63f57dSIdo Schimmel 	__net_dm_cpu_data_fini(data);
17049b63f57dSIdo Schimmel }
17059b63f57dSIdo Schimmel 
net_dm_hw_cpu_data_init(int cpu)1706cac1174fSIdo Schimmel static void net_dm_hw_cpu_data_init(int cpu)
1707cac1174fSIdo Schimmel {
1708cac1174fSIdo Schimmel 	struct per_cpu_dm_data *hw_data;
1709cac1174fSIdo Schimmel 
1710cac1174fSIdo Schimmel 	hw_data = &per_cpu(dm_hw_cpu_data, cpu);
1711cac1174fSIdo Schimmel 	__net_dm_cpu_data_init(hw_data);
1712cac1174fSIdo Schimmel }
1713cac1174fSIdo Schimmel 
net_dm_hw_cpu_data_fini(int cpu)1714cac1174fSIdo Schimmel static void net_dm_hw_cpu_data_fini(int cpu)
1715cac1174fSIdo Schimmel {
1716cac1174fSIdo Schimmel 	struct per_cpu_dm_data *hw_data;
1717cac1174fSIdo Schimmel 
1718cac1174fSIdo Schimmel 	hw_data = &per_cpu(dm_hw_cpu_data, cpu);
1719d40e1debSIdo Schimmel 	kfree(hw_data->hw_entries);
1720cac1174fSIdo Schimmel 	__net_dm_cpu_data_fini(hw_data);
1721cac1174fSIdo Schimmel }
1722cac1174fSIdo Schimmel 
init_net_drop_monitor(void)17239b63f57dSIdo Schimmel static int __init init_net_drop_monitor(void)
17249b63f57dSIdo Schimmel {
1725a256be70SChangli Gao 	int cpu, rc;
1726a256be70SChangli Gao 
1727e005d193SJoe Perches 	pr_info("Initializing network drop monitor service\n");
17289a8afc8dSNeil Horman 
17299a8afc8dSNeil Horman 	if (sizeof(void *) > 8) {
1730e005d193SJoe Perches 		pr_err("Unable to store program counters on this arch, Drop monitor failed\n");
17319a8afc8dSNeil Horman 		return -ENOSPC;
17329a8afc8dSNeil Horman 	}
17339a8afc8dSNeil Horman 
1734cac1174fSIdo Schimmel 	for_each_possible_cpu(cpu) {
17359b63f57dSIdo Schimmel 		net_dm_cpu_data_init(cpu);
1736cac1174fSIdo Schimmel 		net_dm_hw_cpu_data_init(cpu);
1737cac1174fSIdo Schimmel 	}
17384ea7e386SNeil Horman 
1739*0efa6c42SGavrilov Ilia 	rc = register_netdevice_notifier(&dropmon_net_notifier);
1740*0efa6c42SGavrilov Ilia 	if (rc < 0) {
1741*0efa6c42SGavrilov Ilia 		pr_crit("Failed to register netdevice notifier\n");
1742*0efa6c42SGavrilov Ilia 		return rc;
1743*0efa6c42SGavrilov Ilia 	}
1744*0efa6c42SGavrilov Ilia 
1745*0efa6c42SGavrilov Ilia 	rc = genl_register_family(&net_drop_monitor_family);
1746*0efa6c42SGavrilov Ilia 	if (rc) {
1747*0efa6c42SGavrilov Ilia 		pr_err("Could not create drop monitor netlink family\n");
1748*0efa6c42SGavrilov Ilia 		goto out_unreg;
1749*0efa6c42SGavrilov Ilia 	}
1750*0efa6c42SGavrilov Ilia 	WARN_ON(net_drop_monitor_family.mcgrp_offset != NET_DM_GRP_ALERT);
1751*0efa6c42SGavrilov Ilia 
1752*0efa6c42SGavrilov Ilia 	rc = 0;
1753*0efa6c42SGavrilov Ilia 
17549a8afc8dSNeil Horman 	goto out;
17559a8afc8dSNeil Horman 
17569a8afc8dSNeil Horman out_unreg:
1757*0efa6c42SGavrilov Ilia 	WARN_ON(unregister_netdevice_notifier(&dropmon_net_notifier));
17589a8afc8dSNeil Horman out:
17599a8afc8dSNeil Horman 	return rc;
17609a8afc8dSNeil Horman }
17619a8afc8dSNeil Horman 
exit_net_drop_monitor(void)1762cad456d5SNeil Horman static void exit_net_drop_monitor(void)
1763cad456d5SNeil Horman {
1764cad456d5SNeil Horman 	int cpu;
1765cad456d5SNeil Horman 
1766cad456d5SNeil Horman 	/*
1767cad456d5SNeil Horman 	 * Because of the module_get/put we do in the trace state change path
1768a835f903SXiong Zhenwu 	 * we are guaranteed not to have any current users when we get here
1769cad456d5SNeil Horman 	 */
1770*0efa6c42SGavrilov Ilia 	BUG_ON(genl_unregister_family(&net_drop_monitor_family));
1771*0efa6c42SGavrilov Ilia 
1772*0efa6c42SGavrilov Ilia 	BUG_ON(unregister_netdevice_notifier(&dropmon_net_notifier));
1773cad456d5SNeil Horman 
1774cac1174fSIdo Schimmel 	for_each_possible_cpu(cpu) {
1775cac1174fSIdo Schimmel 		net_dm_hw_cpu_data_fini(cpu);
17769b63f57dSIdo Schimmel 		net_dm_cpu_data_fini(cpu);
1777cac1174fSIdo Schimmel 	}
1778cad456d5SNeil Horman }
1779cad456d5SNeil Horman 
1780cad456d5SNeil Horman module_init(init_net_drop_monitor);
1781cad456d5SNeil Horman module_exit(exit_net_drop_monitor);
1782cad456d5SNeil Horman 
1783cad456d5SNeil Horman MODULE_LICENSE("GPL v2");
1784cad456d5SNeil Horman MODULE_AUTHOR("Neil Horman <nhorman@tuxdriver.com>");
17853fdcbd45SNeil Horman MODULE_ALIAS_GENL_FAMILY("NET_DM");
178667c20de3SRob Gill MODULE_DESCRIPTION("Monitoring code for network dropped packet alerts");
1787