summaryrefslogtreecommitdiff
path: root/include/linux/if_team.h
blob: cdc684e04a2fb6fcabda07a3427270af79b7eca8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * include/linux/if_team.h - Network team device driver header
 * Copyright (c) 2011 Jiri Pirko <jpirko@redhat.com>
 */
#ifndef _LINUX_IF_TEAM_H_
#define _LINUX_IF_TEAM_H_

#include <linux/netpoll.h>
#include <net/sch_generic.h>
#include <linux/types.h>
#include <uapi/linux/if_team.h>

struct team_pcpu_stats {
	u64_stats_t		rx_packets;
	u64_stats_t		rx_bytes;
	u64_stats_t		rx_multicast;
	u64_stats_t		tx_packets;
	u64_stats_t		tx_bytes;
	struct u64_stats_sync	syncp;
	u32			rx_dropped;
	u32			tx_dropped;
	u32			rx_nohandler;
};

struct team;

struct team_port {
	struct net_device *dev;
	struct hlist_node hlist; /* node in enabled ports hash list */
	struct list_head list; /* node in ordinary list */
	struct team *team;
	int index; /* index of enabled port. If disabled, it's set to -1 */

	bool linkup; /* either state.linkup or user.linkup */

	struct {
		bool linkup;
		u32 speed;
		u8 duplex;
	} state;

	/* Values set by userspace */
	struct {
		bool linkup;
		bool linkup_enabled;
	} user;

	/* Custom gennetlink interface related flags */
	bool changed;
	bool removed;

	/*
	 * A place for storing original values of the device before it
	 * become a port.
	 */
	struct {
		unsigned char dev_addr[MAX_ADDR_LEN];
		unsigned int mtu;
	} orig;

#ifdef CONFIG_NET_POLL_CONTROLLER
	struct netpoll *np;
#endif

	s32 priority; /* lower number ~ higher priority */
	u16 queue_id;
	struct list_head qom_list; /* node in queue override mapping list */
	struct rcu_head	rcu;
	long mode_priv[];
};

static inline struct team_port *team_port_get_rcu(const struct net_device *dev)
{
	return rcu_dereference(dev->rx_handler_data);
}

static inline bool team_port_enabled(struct team_port *port)
{
	return port->index != -1;
}

static inline bool team_port_txable(struct team_port *port)
{
	return port->linkup && team_port_enabled(port);
}

static inline bool team_port_dev_txable(const struct net_device *port_dev)
{
	struct team_port *port;
	bool txable;

	rcu_read_lock();
	port = team_port_get_rcu(port_dev);
	txable = port ? team_port_txable(port) : false;
	rcu_read_unlock();

	return txable;
}

#ifdef CONFIG_NET_POLL_CONTROLLER
static inline void team_netpoll_send_skb(struct team_port *port,
					 struct sk_buff *skb)
{
	netpoll_send_skb(port->np, skb);
}
#else
static inline void team_netpoll_send_skb(struct team_port *port,
					 struct sk_buff *skb)
{
}
#endif

struct team_mode_ops {
	int (*init)(struct team *team);
	void (*exit)(struct team *team);
	rx_handler_result_t (*receive)(struct team *team,
				       struct team_port *port,
				       struct sk_buff *skb);
	bool (*transmit)(struct team *team, struct sk_buff *skb);
	int (*port_enter)(struct team *team, struct team_port *port);
	void (*port_leave)(struct team *team, struct team_port *port);
	void (*port_change_dev_addr)(struct team *team, struct team_port *port);
	void (*port_enabled)(struct team *team, struct team_port *port);
	void (*port_disabled)(struct team *team, struct team_port *port);
};

extern int team_modeop_port_enter(struct team *team, struct team_port *port);
extern void team_modeop_port_change_dev_addr(struct team *team,
					     struct team_port *port);

enum team_option_type {
	TEAM_OPTION_TYPE_U32,
	TEAM_OPTION_TYPE_STRING,
	TEAM_OPTION_TYPE_BINARY,
	TEAM_OPTION_TYPE_BOOL,
	TEAM_OPTION_TYPE_S32,
};

struct team_option_inst_info {
	u32 array_index;
	struct team_port *port; /* != NULL if per-port */
};

struct team_gsetter_ctx {
	union {
		u32 u32_val;
		const char *str_val;
		struct {
			const void *ptr;
			u32 len;
		} bin_val;
		bool bool_val;
		s32 s32_val;
	} data;
	struct team_option_inst_info *info;
};

struct team_option {
	struct list_head list;
	const char *name;
	bool per_port;
	unsigned int array_size; /* != 0 means the option is array */
	enum team_option_type type;
	void (*init)(struct team *team, struct team_option_inst_info *info);
	void (*getter)(struct team *team, struct team_gsetter_ctx *ctx);
	int (*setter)(struct team *team, struct team_gsetter_ctx *ctx);
};

extern void team_option_inst_set_change(struct team_option_inst_info *opt_inst_info);
extern void team_options_change_check(struct team *team);

struct team_mode {
	const char *kind;
	struct module *owner;
	size_t priv_size;
	size_t port_priv_size;
	const struct team_mode_ops *ops;
	enum netdev_lag_tx_type lag_tx_type;
};

#define TEAM_PORT_HASHBITS 4
#define TEAM_PORT_HASHENTRIES (1 << TEAM_PORT_HASHBITS)

#define TEAM_MODE_PRIV_LONGS 4
#define TEAM_MODE_PRIV_SIZE (sizeof(long) * TEAM_MODE_PRIV_LONGS)

struct team {
	struct net_device *dev; /* associated netdevice */
	struct team_pcpu_stats __percpu *pcpu_stats;

	const struct header_ops *header_ops_cache;

	struct mutex lock; /* used for overall locking, e.g. port lists write */

	/*
	 * List of enabled ports and their count
	 */
	int en_port_count;
	struct hlist_head en_port_hlist[TEAM_PORT_HASHENTRIES];

	struct list_head port_list; /* list of all ports */

	struct list_head option_list;
	struct list_head option_inst_list; /* list of option instances */

	const struct team_mode *mode;
	struct team_mode_ops ops;
	bool user_carrier_enabled;
	bool queue_override_enabled;
	struct list_head *qom_lists; /* array of queue override mapping lists */
	bool port_mtu_change_allowed;
	bool notifier_ctx;
	struct {
		unsigned int count;
		unsigned int interval; /* in ms */
		atomic_t count_pending;
		struct delayed_work dw;
	} notify_peers;
	struct {
		unsigned int count;
		unsigned int interval; /* in ms */
		atomic_t count_pending;
		struct delayed_work dw;
	} mcast_rejoin;
	struct lock_class_key team_lock_key;
	long mode_priv[TEAM_MODE_PRIV_LONGS];
};

static inline int team_dev_queue_xmit(struct team *team, struct team_port *port,
				      struct sk_buff *skb)
{
	BUILD_BUG_ON(sizeof(skb->queue_mapping) !=
		     sizeof(qdisc_skb_cb(skb)->slave_dev_queue_mapping));
	skb_set_queue_mapping(skb, qdisc_skb_cb(skb)->slave_dev_queue_mapping);

	skb->dev = port->dev;
	if (unlikely(netpoll_tx_running(team->dev))) {
		team_netpoll_send_skb(port, skb);
		return 0;
	}
	return dev_queue_xmit(skb);
}

static inline struct hlist_head *team_port_index_hash(struct team *team,
						      int port_index)
{
	return &team->en_port_hlist[port_index & (TEAM_PORT_HASHENTRIES - 1)];
}

static inline struct team_port *team_get_port_by_index(struct team *team,
						       int port_index)
{
	struct team_port *port;
	struct hlist_head *head = team_port_index_hash(team, port_index);

	hlist_for_each_entry(port, head, hlist)
		if (port->index == port_index)
			return port;
	return NULL;
}

static inline int team_num_to_port_index(struct team *team, unsigned int num)
{
	int en_port_count = READ_ONCE(team->en_port_count);

	if (unlikely(!en_port_count))
		return 0;
	return num % en_port_count;
}

static inline struct team_port *team_get_port_by_index_rcu(struct team *team,
							   int port_index)
{
	struct team_port *port;
	struct hlist_head *head = team_port_index_hash(team, port_index);

	hlist_for_each_entry_rcu(port, head, hlist)
		if (port->index == port_index)
			return port;
	return NULL;
}

static inline struct team_port *
team_get_first_port_txable_rcu(struct team *team, struct team_port *port)
{
	struct team_port *cur;

	if (likely(team_port_txable(port)))
		return port;
	cur = port;
	list_for_each_entry_continue_rcu(cur, &team->port_list, list)
		if (team_port_txable(cur))
			return cur;
	list_for_each_entry_rcu(cur, &team->port_list, list) {
		if (cur == port)
			break;
		if (team_port_txable(cur))
			return cur;
	}
	return NULL;
}

extern int team_options_register(struct team *team,
				 const struct team_option *option,
				 size_t option_count);
extern void team_options_unregister(struct team *team,
				    const struct team_option *option,
				    size_t option_count);
extern int team_mode_register(const struct team_mode *mode);
extern void team_mode_unregister(const struct team_mode *mode);

#define TEAM_DEFAULT_NUM_TX_QUEUES 16
#define TEAM_DEFAULT_NUM_RX_QUEUES 16

#define MODULE_ALIAS_TEAM_MODE(kind) MODULE_ALIAS("team-mode-" kind)

#endif /* _LINUX_IF_TEAM_H_ */