aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/team/team_mode_activebackup.c
blob: 6262b4defd93ebdaac7cdd2f468d050e3b238b72 (plain) (blame)
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
/*
 * drivers/net/team/team_mode_activebackup.c - Active-backup mode for team
 * Copyright (c) 2011 Jiri Pirko <jpirko@redhat.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <net/rtnetlink.h>
#include <linux/if_team.h>

struct ab_priv {
	struct team_port __rcu *active_port;
};

static struct ab_priv *ab_priv(struct team *team)
{
	return (struct ab_priv *) &team->mode_priv;
}

static rx_handler_result_t ab_receive(struct team *team, struct team_port *port,
				      struct sk_buff *skb) {
	struct team_port *active_port;

	active_port = rcu_dereference(ab_priv(team)->active_port);
	if (active_port != port)
		return RX_HANDLER_EXACT;
	return RX_HANDLER_ANOTHER;
}

static bool ab_transmit(struct team *team, struct sk_buff *skb)
{
	struct team_port *active_port;

	active_port = rcu_dereference_bh(ab_priv(team)->active_port);
	if (unlikely(!active_port))
		goto drop;
	if (team_dev_queue_xmit(team, active_port, skb))
		return false;
	return true;

drop:
	dev_kfree_skb_any(skb);
	return false;
}

static void ab_port_leave(struct team *team, struct team_port *port)
{
	if (ab_priv(team)->active_port == port)
		RCU_INIT_POINTER(ab_priv(team)->active_port, NULL);
}

static int ab_active_port_get(struct team *team, struct team_gsetter_ctx *ctx)
{
	struct team_port *active_port;

	active_port = rcu_dereference_protected(ab_priv(team)->active_port,
						lockdep_is_held(&team->lock));
	if (active_port)
		ctx->data.u32_val = active_port->dev->ifindex;
	else
		ctx->data.u32_val = 0;
	return 0;
}

static int ab_active_port_set(struct team *team, struct team_gsetter_ctx *ctx)
{
	struct team_port *port;

	list_for_each_entry(port, &team->port_list, list) {
		if (port->dev->ifindex == ctx->data.u32_val) {
			rcu_assign_pointer(ab_priv(team)->active_port, port);
			return 0;
		}
	}
	return -ENOENT;
}

static const struct team_option ab_options[] = {
	{
		.name = "activeport",
		.type = TEAM_OPTION_TYPE_U32,
		.getter = ab_active_port_get,
		.setter = ab_active_port_set,
	},
};

static int ab_init(struct team *team)
{
	return team_options_register(team, ab_options, ARRAY_SIZE(ab_options));
}

static void ab_exit(struct team *team)
{
	team_options_unregister(team, ab_options, ARRAY_SIZE(ab_options));
}

static const struct team_mode_ops ab_mode_ops = {
	.init			= ab_init,
	.exit			= ab_exit,
	.receive		= ab_receive,
	.transmit		= ab_transmit,
	.port_leave		= ab_port_leave,
};

static const struct team_mode ab_mode = {
	.kind		= "activebackup",
	.owner		= THIS_MODULE,
	.priv_size	= sizeof(struct ab_priv),
	.ops		= &ab_mode_ops,
};

static int __init ab_init_module(void)
{
	return team_mode_register(&ab_mode);
}

static void __exit ab_cleanup_module(void)
{
	team_mode_unregister(&ab_mode);
}

module_init(ab_init_module);
module_exit(ab_cleanup_module);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Jiri Pirko <jpirko@redhat.com>");
MODULE_DESCRIPTION("Active-backup mode for team");
MODULE_ALIAS("team-mode-activebackup");