xref: /openbmc/linux/net/phonet/pn_dev.c (revision eeb74a9d45f781ec6f47b9e0a75a6a427b53f165)
1f8ff6028SRemi Denis-Courmont /*
2f8ff6028SRemi Denis-Courmont  * File: pn_dev.c
3f8ff6028SRemi Denis-Courmont  *
4f8ff6028SRemi Denis-Courmont  * Phonet network device
5f8ff6028SRemi Denis-Courmont  *
6f8ff6028SRemi Denis-Courmont  * Copyright (C) 2008 Nokia Corporation.
7f8ff6028SRemi Denis-Courmont  *
8f8ff6028SRemi Denis-Courmont  * Contact: Remi Denis-Courmont <remi.denis-courmont@nokia.com>
9f8ff6028SRemi Denis-Courmont  * Original author: Sakari Ailus <sakari.ailus@nokia.com>
10f8ff6028SRemi Denis-Courmont  *
11f8ff6028SRemi Denis-Courmont  * This program is free software; you can redistribute it and/or
12f8ff6028SRemi Denis-Courmont  * modify it under the terms of the GNU General Public License
13f8ff6028SRemi Denis-Courmont  * version 2 as published by the Free Software Foundation.
14f8ff6028SRemi Denis-Courmont  *
15f8ff6028SRemi Denis-Courmont  * This program is distributed in the hope that it will be useful, but
16f8ff6028SRemi Denis-Courmont  * WITHOUT ANY WARRANTY; without even the implied warranty of
17f8ff6028SRemi Denis-Courmont  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18f8ff6028SRemi Denis-Courmont  * General Public License for more details.
19f8ff6028SRemi Denis-Courmont  *
20f8ff6028SRemi Denis-Courmont  * You should have received a copy of the GNU General Public License
21f8ff6028SRemi Denis-Courmont  * along with this program; if not, write to the Free Software
22f8ff6028SRemi Denis-Courmont  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
23f8ff6028SRemi Denis-Courmont  * 02110-1301 USA
24f8ff6028SRemi Denis-Courmont  */
25f8ff6028SRemi Denis-Courmont 
26f8ff6028SRemi Denis-Courmont #include <linux/kernel.h>
27f8ff6028SRemi Denis-Courmont #include <linux/net.h>
28f8ff6028SRemi Denis-Courmont #include <linux/netdevice.h>
29f8ff6028SRemi Denis-Courmont #include <linux/phonet.h>
30421d20a3SDavid S. Miller #include <linux/proc_fs.h>
31f5bb1c55SRémi Denis-Courmont #include <linux/if_arp.h>
32f8ff6028SRemi Denis-Courmont #include <net/sock.h>
339a3b7a42Sremi.denis-courmont@nokia #include <net/netns/generic.h>
34f8ff6028SRemi Denis-Courmont #include <net/phonet/pn_dev.h>
35f8ff6028SRemi Denis-Courmont 
3655748ac0SRémi Denis-Courmont struct phonet_routes {
3788880135SRémi Denis-Courmont 	struct mutex		lock;
3855748ac0SRémi Denis-Courmont 	struct net_device	*table[64];
3955748ac0SRémi Denis-Courmont };
4055748ac0SRémi Denis-Courmont 
419a3b7a42Sremi.denis-courmont@nokia struct phonet_net {
429a3b7a42Sremi.denis-courmont@nokia 	struct phonet_device_list pndevs;
4355748ac0SRémi Denis-Courmont 	struct phonet_routes routes;
44f8ff6028SRemi Denis-Courmont };
45f8ff6028SRemi Denis-Courmont 
46f99189b1SEric Dumazet int phonet_net_id __read_mostly;
479a3b7a42Sremi.denis-courmont@nokia 
489a3b7a42Sremi.denis-courmont@nokia struct phonet_device_list *phonet_device_list(struct net *net)
499a3b7a42Sremi.denis-courmont@nokia {
509a3b7a42Sremi.denis-courmont@nokia 	struct phonet_net *pnn = net_generic(net, phonet_net_id);
519a3b7a42Sremi.denis-courmont@nokia 	return &pnn->pndevs;
529a3b7a42Sremi.denis-courmont@nokia }
539a3b7a42Sremi.denis-courmont@nokia 
54f8ff6028SRemi Denis-Courmont /* Allocate new Phonet device. */
55f8ff6028SRemi Denis-Courmont static struct phonet_device *__phonet_device_alloc(struct net_device *dev)
56f8ff6028SRemi Denis-Courmont {
579a3b7a42Sremi.denis-courmont@nokia 	struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
58f8ff6028SRemi Denis-Courmont 	struct phonet_device *pnd = kmalloc(sizeof(*pnd), GFP_ATOMIC);
59f8ff6028SRemi Denis-Courmont 	if (pnd == NULL)
60f8ff6028SRemi Denis-Courmont 		return NULL;
61f8ff6028SRemi Denis-Courmont 	pnd->netdev = dev;
62f8ff6028SRemi Denis-Courmont 	bitmap_zero(pnd->addrs, 64);
63f8ff6028SRemi Denis-Courmont 
64*eeb74a9dSRémi Denis-Courmont 	BUG_ON(!mutex_is_locked(&pndevs->lock));
65*eeb74a9dSRémi Denis-Courmont 	list_add_rcu(&pnd->list, &pndevs->list);
66f8ff6028SRemi Denis-Courmont 	return pnd;
67f8ff6028SRemi Denis-Courmont }
68f8ff6028SRemi Denis-Courmont 
69f8ff6028SRemi Denis-Courmont static struct phonet_device *__phonet_get(struct net_device *dev)
70f8ff6028SRemi Denis-Courmont {
719a3b7a42Sremi.denis-courmont@nokia 	struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
72f8ff6028SRemi Denis-Courmont 	struct phonet_device *pnd;
73f8ff6028SRemi Denis-Courmont 
74*eeb74a9dSRémi Denis-Courmont 	BUG_ON(!mutex_is_locked(&pndevs->lock));
759a3b7a42Sremi.denis-courmont@nokia 	list_for_each_entry(pnd, &pndevs->list, list) {
76f8ff6028SRemi Denis-Courmont 		if (pnd->netdev == dev)
77f8ff6028SRemi Denis-Courmont 			return pnd;
78f8ff6028SRemi Denis-Courmont 	}
79f8ff6028SRemi Denis-Courmont 	return NULL;
80f8ff6028SRemi Denis-Courmont }
81f8ff6028SRemi Denis-Courmont 
82*eeb74a9dSRémi Denis-Courmont static struct phonet_device *__phonet_get_rcu(struct net_device *dev)
83*eeb74a9dSRémi Denis-Courmont {
84*eeb74a9dSRémi Denis-Courmont 	struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
85*eeb74a9dSRémi Denis-Courmont 	struct phonet_device *pnd;
86*eeb74a9dSRémi Denis-Courmont 
87*eeb74a9dSRémi Denis-Courmont 	list_for_each_entry_rcu(pnd, &pndevs->list, list) {
88*eeb74a9dSRémi Denis-Courmont 		if (pnd->netdev == dev)
89*eeb74a9dSRémi Denis-Courmont 			return pnd;
90*eeb74a9dSRémi Denis-Courmont 	}
91*eeb74a9dSRémi Denis-Courmont 	return NULL;
92*eeb74a9dSRémi Denis-Courmont }
93*eeb74a9dSRémi Denis-Courmont 
942be6fa4cSRémi Denis-Courmont static void phonet_device_destroy(struct net_device *dev)
95f8ff6028SRemi Denis-Courmont {
962be6fa4cSRémi Denis-Courmont 	struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
972be6fa4cSRémi Denis-Courmont 	struct phonet_device *pnd;
982be6fa4cSRémi Denis-Courmont 
992be6fa4cSRémi Denis-Courmont 	ASSERT_RTNL();
1002be6fa4cSRémi Denis-Courmont 
101*eeb74a9dSRémi Denis-Courmont 	mutex_lock(&pndevs->lock);
1022be6fa4cSRémi Denis-Courmont 	pnd = __phonet_get(dev);
1032be6fa4cSRémi Denis-Courmont 	if (pnd)
104*eeb74a9dSRémi Denis-Courmont 		list_del_rcu(&pnd->list);
105*eeb74a9dSRémi Denis-Courmont 	mutex_unlock(&pndevs->lock);
1062be6fa4cSRémi Denis-Courmont 
1072be6fa4cSRémi Denis-Courmont 	if (pnd) {
1082be6fa4cSRémi Denis-Courmont 		u8 addr;
1092be6fa4cSRémi Denis-Courmont 
1102be6fa4cSRémi Denis-Courmont 		for (addr = find_first_bit(pnd->addrs, 64); addr < 64;
1112be6fa4cSRémi Denis-Courmont 			addr = find_next_bit(pnd->addrs, 64, 1+addr))
1122be6fa4cSRémi Denis-Courmont 			phonet_address_notify(RTM_DELADDR, dev, addr);
113f8ff6028SRemi Denis-Courmont 		kfree(pnd);
114f8ff6028SRemi Denis-Courmont 	}
1152be6fa4cSRémi Denis-Courmont }
116f8ff6028SRemi Denis-Courmont 
117f8ff6028SRemi Denis-Courmont struct net_device *phonet_device_get(struct net *net)
118f8ff6028SRemi Denis-Courmont {
1199a3b7a42Sremi.denis-courmont@nokia 	struct phonet_device_list *pndevs = phonet_device_list(net);
120f8ff6028SRemi Denis-Courmont 	struct phonet_device *pnd;
12159e57f44SEric Dumazet 	struct net_device *dev = NULL;
122f8ff6028SRemi Denis-Courmont 
123*eeb74a9dSRémi Denis-Courmont 	rcu_read_lock();
124*eeb74a9dSRémi Denis-Courmont 	list_for_each_entry_rcu(pnd, &pndevs->list, list) {
125f8ff6028SRemi Denis-Courmont 		dev = pnd->netdev;
126f8ff6028SRemi Denis-Courmont 		BUG_ON(!dev);
127f8ff6028SRemi Denis-Courmont 
1289a3b7a42Sremi.denis-courmont@nokia 		if ((dev->reg_state == NETREG_REGISTERED) &&
129f8ff6028SRemi Denis-Courmont 			((pnd->netdev->flags & IFF_UP)) == IFF_UP)
130f8ff6028SRemi Denis-Courmont 			break;
131f8ff6028SRemi Denis-Courmont 		dev = NULL;
132f8ff6028SRemi Denis-Courmont 	}
133f8ff6028SRemi Denis-Courmont 	if (dev)
134f8ff6028SRemi Denis-Courmont 		dev_hold(dev);
135*eeb74a9dSRémi Denis-Courmont 	rcu_read_unlock();
136f8ff6028SRemi Denis-Courmont 	return dev;
137f8ff6028SRemi Denis-Courmont }
138f8ff6028SRemi Denis-Courmont 
139f8ff6028SRemi Denis-Courmont int phonet_address_add(struct net_device *dev, u8 addr)
140f8ff6028SRemi Denis-Courmont {
1419a3b7a42Sremi.denis-courmont@nokia 	struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
142f8ff6028SRemi Denis-Courmont 	struct phonet_device *pnd;
143f8ff6028SRemi Denis-Courmont 	int err = 0;
144f8ff6028SRemi Denis-Courmont 
145*eeb74a9dSRémi Denis-Courmont 	mutex_lock(&pndevs->lock);
146f8ff6028SRemi Denis-Courmont 	/* Find or create Phonet-specific device data */
147f8ff6028SRemi Denis-Courmont 	pnd = __phonet_get(dev);
148f8ff6028SRemi Denis-Courmont 	if (pnd == NULL)
149f8ff6028SRemi Denis-Courmont 		pnd = __phonet_device_alloc(dev);
150f8ff6028SRemi Denis-Courmont 	if (unlikely(pnd == NULL))
151f8ff6028SRemi Denis-Courmont 		err = -ENOMEM;
152f8ff6028SRemi Denis-Courmont 	else if (test_and_set_bit(addr >> 2, pnd->addrs))
153f8ff6028SRemi Denis-Courmont 		err = -EEXIST;
154*eeb74a9dSRémi Denis-Courmont 	mutex_unlock(&pndevs->lock);
155f8ff6028SRemi Denis-Courmont 	return err;
156f8ff6028SRemi Denis-Courmont }
157f8ff6028SRemi Denis-Courmont 
158f8ff6028SRemi Denis-Courmont int phonet_address_del(struct net_device *dev, u8 addr)
159f8ff6028SRemi Denis-Courmont {
1609a3b7a42Sremi.denis-courmont@nokia 	struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
161f8ff6028SRemi Denis-Courmont 	struct phonet_device *pnd;
162f8ff6028SRemi Denis-Courmont 	int err = 0;
163f8ff6028SRemi Denis-Courmont 
164*eeb74a9dSRémi Denis-Courmont 	mutex_lock(&pndevs->lock);
165f8ff6028SRemi Denis-Courmont 	pnd = __phonet_get(dev);
166*eeb74a9dSRémi Denis-Courmont 	if (!pnd || !test_and_clear_bit(addr >> 2, pnd->addrs)) {
167f8ff6028SRemi Denis-Courmont 		err = -EADDRNOTAVAIL;
168*eeb74a9dSRémi Denis-Courmont 		pnd = NULL;
169*eeb74a9dSRémi Denis-Courmont 	} else if (bitmap_empty(pnd->addrs, 64))
170*eeb74a9dSRémi Denis-Courmont 		list_del_rcu(&pnd->list);
171*eeb74a9dSRémi Denis-Courmont 	else
172*eeb74a9dSRémi Denis-Courmont 		pnd = NULL;
173*eeb74a9dSRémi Denis-Courmont 	mutex_unlock(&pndevs->lock);
174*eeb74a9dSRémi Denis-Courmont 
175*eeb74a9dSRémi Denis-Courmont 	if (pnd) {
176*eeb74a9dSRémi Denis-Courmont 		synchronize_rcu();
1772be6fa4cSRémi Denis-Courmont 		kfree(pnd);
1782be6fa4cSRémi Denis-Courmont 	}
179f8ff6028SRemi Denis-Courmont 	return err;
180f8ff6028SRemi Denis-Courmont }
181f8ff6028SRemi Denis-Courmont 
182f8ff6028SRemi Denis-Courmont /* Gets a source address toward a destination, through a interface. */
18355748ac0SRémi Denis-Courmont u8 phonet_address_get(struct net_device *dev, u8 daddr)
184f8ff6028SRemi Denis-Courmont {
185f8ff6028SRemi Denis-Courmont 	struct phonet_device *pnd;
18655748ac0SRémi Denis-Courmont 	u8 saddr;
187f8ff6028SRemi Denis-Courmont 
188*eeb74a9dSRémi Denis-Courmont 	rcu_read_lock();
189*eeb74a9dSRémi Denis-Courmont 	pnd = __phonet_get_rcu(dev);
190f8ff6028SRemi Denis-Courmont 	if (pnd) {
191f8ff6028SRemi Denis-Courmont 		BUG_ON(bitmap_empty(pnd->addrs, 64));
192f8ff6028SRemi Denis-Courmont 
193f8ff6028SRemi Denis-Courmont 		/* Use same source address as destination, if possible */
19455748ac0SRémi Denis-Courmont 		if (test_bit(daddr >> 2, pnd->addrs))
19555748ac0SRémi Denis-Courmont 			saddr = daddr;
19655748ac0SRémi Denis-Courmont 		else
19755748ac0SRémi Denis-Courmont 			saddr = find_first_bit(pnd->addrs, 64) << 2;
198f8ff6028SRemi Denis-Courmont 	} else
19955748ac0SRémi Denis-Courmont 		saddr = PN_NO_ADDR;
200*eeb74a9dSRémi Denis-Courmont 	rcu_read_unlock();
20155748ac0SRémi Denis-Courmont 
20255748ac0SRémi Denis-Courmont 	if (saddr == PN_NO_ADDR) {
20355748ac0SRémi Denis-Courmont 		/* Fallback to another device */
20455748ac0SRémi Denis-Courmont 		struct net_device *def_dev;
20555748ac0SRémi Denis-Courmont 
20655748ac0SRémi Denis-Courmont 		def_dev = phonet_device_get(dev_net(dev));
20755748ac0SRémi Denis-Courmont 		if (def_dev) {
20855748ac0SRémi Denis-Courmont 			if (def_dev != dev)
20955748ac0SRémi Denis-Courmont 				saddr = phonet_address_get(def_dev, daddr);
21055748ac0SRémi Denis-Courmont 			dev_put(def_dev);
21155748ac0SRémi Denis-Courmont 		}
21255748ac0SRémi Denis-Courmont 	}
21355748ac0SRémi Denis-Courmont 	return saddr;
214f8ff6028SRemi Denis-Courmont }
215f8ff6028SRemi Denis-Courmont 
21652404881SRémi Denis-Courmont int phonet_address_lookup(struct net *net, u8 addr)
217f8ff6028SRemi Denis-Courmont {
2189a3b7a42Sremi.denis-courmont@nokia 	struct phonet_device_list *pndevs = phonet_device_list(net);
219f8ff6028SRemi Denis-Courmont 	struct phonet_device *pnd;
2209a3b7a42Sremi.denis-courmont@nokia 	int err = -EADDRNOTAVAIL;
221f8ff6028SRemi Denis-Courmont 
222*eeb74a9dSRémi Denis-Courmont 	rcu_read_lock();
223*eeb74a9dSRémi Denis-Courmont 	list_for_each_entry_rcu(pnd, &pndevs->list, list) {
224f8ff6028SRemi Denis-Courmont 		/* Don't allow unregistering devices! */
225f8ff6028SRemi Denis-Courmont 		if ((pnd->netdev->reg_state != NETREG_REGISTERED) ||
226f8ff6028SRemi Denis-Courmont 				((pnd->netdev->flags & IFF_UP)) != IFF_UP)
227f8ff6028SRemi Denis-Courmont 			continue;
228f8ff6028SRemi Denis-Courmont 
229f8ff6028SRemi Denis-Courmont 		if (test_bit(addr >> 2, pnd->addrs)) {
2309a3b7a42Sremi.denis-courmont@nokia 			err = 0;
2319a3b7a42Sremi.denis-courmont@nokia 			goto found;
232f8ff6028SRemi Denis-Courmont 		}
233f8ff6028SRemi Denis-Courmont 	}
2349a3b7a42Sremi.denis-courmont@nokia found:
235*eeb74a9dSRémi Denis-Courmont 	rcu_read_unlock();
2369a3b7a42Sremi.denis-courmont@nokia 	return err;
237f8ff6028SRemi Denis-Courmont }
238f8ff6028SRemi Denis-Courmont 
239f5bb1c55SRémi Denis-Courmont /* automatically configure a Phonet device, if supported */
240f5bb1c55SRémi Denis-Courmont static int phonet_device_autoconf(struct net_device *dev)
241f5bb1c55SRémi Denis-Courmont {
242f5bb1c55SRémi Denis-Courmont 	struct if_phonet_req req;
243f5bb1c55SRémi Denis-Courmont 	int ret;
244f5bb1c55SRémi Denis-Courmont 
245f5bb1c55SRémi Denis-Courmont 	if (!dev->netdev_ops->ndo_do_ioctl)
246f5bb1c55SRémi Denis-Courmont 		return -EOPNOTSUPP;
247f5bb1c55SRémi Denis-Courmont 
248f5bb1c55SRémi Denis-Courmont 	ret = dev->netdev_ops->ndo_do_ioctl(dev, (struct ifreq *)&req,
249f5bb1c55SRémi Denis-Courmont 						SIOCPNGAUTOCONF);
250f5bb1c55SRémi Denis-Courmont 	if (ret < 0)
251f5bb1c55SRémi Denis-Courmont 		return ret;
252b11b5165SRémi Denis-Courmont 
253b11b5165SRémi Denis-Courmont 	ASSERT_RTNL();
254b11b5165SRémi Denis-Courmont 	ret = phonet_address_add(dev, req.ifr_phonet_autoconf.device);
255b11b5165SRémi Denis-Courmont 	if (ret)
256b11b5165SRémi Denis-Courmont 		return ret;
257b11b5165SRémi Denis-Courmont 	phonet_address_notify(RTM_NEWADDR, dev,
258b11b5165SRémi Denis-Courmont 				req.ifr_phonet_autoconf.device);
259b11b5165SRémi Denis-Courmont 	return 0;
260f5bb1c55SRémi Denis-Courmont }
261f5bb1c55SRémi Denis-Courmont 
262f062f41dSRémi Denis-Courmont static void phonet_route_autodel(struct net_device *dev)
263f062f41dSRémi Denis-Courmont {
264f062f41dSRémi Denis-Courmont 	struct phonet_net *pnn = net_generic(dev_net(dev), phonet_net_id);
265f062f41dSRémi Denis-Courmont 	unsigned i;
266f062f41dSRémi Denis-Courmont 	DECLARE_BITMAP(deleted, 64);
267f062f41dSRémi Denis-Courmont 
268f062f41dSRémi Denis-Courmont 	/* Remove left-over Phonet routes */
269f062f41dSRémi Denis-Courmont 	bitmap_zero(deleted, 64);
27088880135SRémi Denis-Courmont 	mutex_lock(&pnn->routes.lock);
271f062f41dSRémi Denis-Courmont 	for (i = 0; i < 64; i++)
272f062f41dSRémi Denis-Courmont 		if (dev == pnn->routes.table[i]) {
27388880135SRémi Denis-Courmont 			rcu_assign_pointer(pnn->routes.table[i], NULL);
274f062f41dSRémi Denis-Courmont 			set_bit(i, deleted);
27588880135SRémi Denis-Courmont 		}
27688880135SRémi Denis-Courmont 	mutex_unlock(&pnn->routes.lock);
27788880135SRémi Denis-Courmont 
27888880135SRémi Denis-Courmont 	if (bitmap_empty(deleted, 64))
27988880135SRémi Denis-Courmont 		return; /* short-circuit RCU */
28088880135SRémi Denis-Courmont 	synchronize_rcu();
28188880135SRémi Denis-Courmont 	for (i = find_first_bit(deleted, 64); i < 64;
28288880135SRémi Denis-Courmont 			i = find_next_bit(deleted, 64, i + 1)) {
28388880135SRémi Denis-Courmont 		rtm_phonet_notify(RTM_DELROUTE, dev, i);
284f062f41dSRémi Denis-Courmont 		dev_put(dev);
285f062f41dSRémi Denis-Courmont 	}
286f062f41dSRémi Denis-Courmont }
287f062f41dSRémi Denis-Courmont 
288f8ff6028SRemi Denis-Courmont /* notify Phonet of device events */
289f8ff6028SRemi Denis-Courmont static int phonet_device_notify(struct notifier_block *me, unsigned long what,
290f8ff6028SRemi Denis-Courmont 				void *arg)
291f8ff6028SRemi Denis-Courmont {
292f8ff6028SRemi Denis-Courmont 	struct net_device *dev = arg;
293f8ff6028SRemi Denis-Courmont 
294f5bb1c55SRémi Denis-Courmont 	switch (what) {
295f5bb1c55SRémi Denis-Courmont 	case NETDEV_REGISTER:
296f5bb1c55SRémi Denis-Courmont 		if (dev->type == ARPHRD_PHONET)
297f5bb1c55SRémi Denis-Courmont 			phonet_device_autoconf(dev);
298f5bb1c55SRémi Denis-Courmont 		break;
299f5bb1c55SRémi Denis-Courmont 	case NETDEV_UNREGISTER:
3002be6fa4cSRémi Denis-Courmont 		phonet_device_destroy(dev);
301f062f41dSRémi Denis-Courmont 		phonet_route_autodel(dev);
302f5bb1c55SRémi Denis-Courmont 		break;
303f5bb1c55SRémi Denis-Courmont 	}
304f8ff6028SRemi Denis-Courmont 	return 0;
305f8ff6028SRemi Denis-Courmont 
306f8ff6028SRemi Denis-Courmont }
307f8ff6028SRemi Denis-Courmont 
308f8ff6028SRemi Denis-Courmont static struct notifier_block phonet_device_notifier = {
309f8ff6028SRemi Denis-Courmont 	.notifier_call = phonet_device_notify,
310f8ff6028SRemi Denis-Courmont 	.priority = 0,
311f8ff6028SRemi Denis-Courmont };
312f8ff6028SRemi Denis-Courmont 
3139a3b7a42Sremi.denis-courmont@nokia /* Per-namespace Phonet devices handling */
3149a3b7a42Sremi.denis-courmont@nokia static int phonet_init_net(struct net *net)
3159a3b7a42Sremi.denis-courmont@nokia {
31655748ac0SRémi Denis-Courmont 	struct phonet_net *pnn = kzalloc(sizeof(*pnn), GFP_KERNEL);
3179a3b7a42Sremi.denis-courmont@nokia 	if (!pnn)
3189a3b7a42Sremi.denis-courmont@nokia 		return -ENOMEM;
3199a3b7a42Sremi.denis-courmont@nokia 
320c1dc13e9SRémi Denis-Courmont 	if (!proc_net_fops_create(net, "phonet", 0, &pn_sock_seq_fops)) {
321c1dc13e9SRémi Denis-Courmont 		kfree(pnn);
322c1dc13e9SRémi Denis-Courmont 		return -ENOMEM;
323c1dc13e9SRémi Denis-Courmont 	}
324c1dc13e9SRémi Denis-Courmont 
3259a3b7a42Sremi.denis-courmont@nokia 	INIT_LIST_HEAD(&pnn->pndevs.list);
326*eeb74a9dSRémi Denis-Courmont 	mutex_init(&pnn->pndevs.lock);
32788880135SRémi Denis-Courmont 	mutex_init(&pnn->routes.lock);
3289a3b7a42Sremi.denis-courmont@nokia 	net_assign_generic(net, phonet_net_id, pnn);
3299a3b7a42Sremi.denis-courmont@nokia 	return 0;
3309a3b7a42Sremi.denis-courmont@nokia }
3319a3b7a42Sremi.denis-courmont@nokia 
3329a3b7a42Sremi.denis-courmont@nokia static void phonet_exit_net(struct net *net)
3339a3b7a42Sremi.denis-courmont@nokia {
3349a3b7a42Sremi.denis-courmont@nokia 	struct phonet_net *pnn = net_generic(net, phonet_net_id);
3352be6fa4cSRémi Denis-Courmont 	struct net_device *dev;
336f062f41dSRémi Denis-Courmont 	unsigned i;
3379a3b7a42Sremi.denis-courmont@nokia 
3382be6fa4cSRémi Denis-Courmont 	rtnl_lock();
3392be6fa4cSRémi Denis-Courmont 	for_each_netdev(net, dev)
3402be6fa4cSRémi Denis-Courmont 		phonet_device_destroy(dev);
341f062f41dSRémi Denis-Courmont 
342f062f41dSRémi Denis-Courmont 	for (i = 0; i < 64; i++) {
343f062f41dSRémi Denis-Courmont 		dev = pnn->routes.table[i];
344f062f41dSRémi Denis-Courmont 		if (dev) {
345f062f41dSRémi Denis-Courmont 			rtm_phonet_notify(RTM_DELROUTE, dev, i);
346f062f41dSRémi Denis-Courmont 			dev_put(dev);
347f062f41dSRémi Denis-Courmont 		}
348f062f41dSRémi Denis-Courmont 	}
3492be6fa4cSRémi Denis-Courmont 	rtnl_unlock();
350c1dc13e9SRémi Denis-Courmont 
351c1dc13e9SRémi Denis-Courmont 	proc_net_remove(net, "phonet");
3529a3b7a42Sremi.denis-courmont@nokia 	kfree(pnn);
3539a3b7a42Sremi.denis-courmont@nokia }
3549a3b7a42Sremi.denis-courmont@nokia 
3559a3b7a42Sremi.denis-courmont@nokia static struct pernet_operations phonet_net_ops = {
3569a3b7a42Sremi.denis-courmont@nokia 	.init = phonet_init_net,
3579a3b7a42Sremi.denis-courmont@nokia 	.exit = phonet_exit_net,
3589a3b7a42Sremi.denis-courmont@nokia };
3599a3b7a42Sremi.denis-courmont@nokia 
360f8ff6028SRemi Denis-Courmont /* Initialize Phonet devices list */
36176e02cf6Sremi.denis-courmont@nokia int __init phonet_device_init(void)
362f8ff6028SRemi Denis-Courmont {
3639a3b7a42Sremi.denis-courmont@nokia 	int err = register_pernet_gen_device(&phonet_net_id, &phonet_net_ops);
3649a3b7a42Sremi.denis-courmont@nokia 	if (err)
3659a3b7a42Sremi.denis-courmont@nokia 		return err;
366660f706dSremi.denis-courmont@nokia 
367f8ff6028SRemi Denis-Courmont 	register_netdevice_notifier(&phonet_device_notifier);
368660f706dSremi.denis-courmont@nokia 	err = phonet_netlink_register();
369660f706dSremi.denis-courmont@nokia 	if (err)
370660f706dSremi.denis-courmont@nokia 		phonet_device_exit();
371660f706dSremi.denis-courmont@nokia 	return err;
372f8ff6028SRemi Denis-Courmont }
373f8ff6028SRemi Denis-Courmont 
374f8ff6028SRemi Denis-Courmont void phonet_device_exit(void)
375f8ff6028SRemi Denis-Courmont {
376f8ff6028SRemi Denis-Courmont 	rtnl_unregister_all(PF_PHONET);
3776530e0feSremi.denis-courmont@nokia 	unregister_netdevice_notifier(&phonet_device_notifier);
3789a3b7a42Sremi.denis-courmont@nokia 	unregister_pernet_gen_device(phonet_net_id, &phonet_net_ops);
379f8ff6028SRemi Denis-Courmont }
38055748ac0SRémi Denis-Courmont 
38155748ac0SRémi Denis-Courmont int phonet_route_add(struct net_device *dev, u8 daddr)
38255748ac0SRémi Denis-Courmont {
38355748ac0SRémi Denis-Courmont 	struct phonet_net *pnn = net_generic(dev_net(dev), phonet_net_id);
38455748ac0SRémi Denis-Courmont 	struct phonet_routes *routes = &pnn->routes;
38555748ac0SRémi Denis-Courmont 	int err = -EEXIST;
38655748ac0SRémi Denis-Courmont 
38755748ac0SRémi Denis-Courmont 	daddr = daddr >> 2;
38888880135SRémi Denis-Courmont 	mutex_lock(&routes->lock);
38955748ac0SRémi Denis-Courmont 	if (routes->table[daddr] == NULL) {
39088880135SRémi Denis-Courmont 		rcu_assign_pointer(routes->table[daddr], dev);
39155748ac0SRémi Denis-Courmont 		dev_hold(dev);
39255748ac0SRémi Denis-Courmont 		err = 0;
39355748ac0SRémi Denis-Courmont 	}
39488880135SRémi Denis-Courmont 	mutex_unlock(&routes->lock);
39555748ac0SRémi Denis-Courmont 	return err;
39655748ac0SRémi Denis-Courmont }
39755748ac0SRémi Denis-Courmont 
39855748ac0SRémi Denis-Courmont int phonet_route_del(struct net_device *dev, u8 daddr)
39955748ac0SRémi Denis-Courmont {
40055748ac0SRémi Denis-Courmont 	struct phonet_net *pnn = net_generic(dev_net(dev), phonet_net_id);
40155748ac0SRémi Denis-Courmont 	struct phonet_routes *routes = &pnn->routes;
40255748ac0SRémi Denis-Courmont 
40355748ac0SRémi Denis-Courmont 	daddr = daddr >> 2;
40488880135SRémi Denis-Courmont 	mutex_lock(&routes->lock);
40588880135SRémi Denis-Courmont 	if (dev == routes->table[daddr])
40688880135SRémi Denis-Courmont 		rcu_assign_pointer(routes->table[daddr], NULL);
40788880135SRémi Denis-Courmont 	else
40888880135SRémi Denis-Courmont 		dev = NULL;
40988880135SRémi Denis-Courmont 	mutex_unlock(&routes->lock);
41088880135SRémi Denis-Courmont 
41188880135SRémi Denis-Courmont 	if (!dev)
41288880135SRémi Denis-Courmont 		return -ENOENT;
41388880135SRémi Denis-Courmont 	synchronize_rcu();
41455748ac0SRémi Denis-Courmont 	dev_put(dev);
41588880135SRémi Denis-Courmont 	return 0;
41655748ac0SRémi Denis-Courmont }
41755748ac0SRémi Denis-Courmont 
41855748ac0SRémi Denis-Courmont struct net_device *phonet_route_get(struct net *net, u8 daddr)
41955748ac0SRémi Denis-Courmont {
42055748ac0SRémi Denis-Courmont 	struct phonet_net *pnn = net_generic(net, phonet_net_id);
42155748ac0SRémi Denis-Courmont 	struct phonet_routes *routes = &pnn->routes;
42255748ac0SRémi Denis-Courmont 	struct net_device *dev;
42355748ac0SRémi Denis-Courmont 
42455748ac0SRémi Denis-Courmont 	ASSERT_RTNL(); /* no need to hold the device */
42555748ac0SRémi Denis-Courmont 
42655748ac0SRémi Denis-Courmont 	daddr >>= 2;
42788880135SRémi Denis-Courmont 	rcu_read_lock();
42888880135SRémi Denis-Courmont 	dev = rcu_dereference(routes->table[daddr]);
42988880135SRémi Denis-Courmont 	rcu_read_unlock();
43055748ac0SRémi Denis-Courmont 	return dev;
43155748ac0SRémi Denis-Courmont }
43255748ac0SRémi Denis-Courmont 
43355748ac0SRémi Denis-Courmont struct net_device *phonet_route_output(struct net *net, u8 daddr)
43455748ac0SRémi Denis-Courmont {
43555748ac0SRémi Denis-Courmont 	struct phonet_net *pnn = net_generic(net, phonet_net_id);
43655748ac0SRémi Denis-Courmont 	struct phonet_routes *routes = &pnn->routes;
43755748ac0SRémi Denis-Courmont 	struct net_device *dev;
43855748ac0SRémi Denis-Courmont 
43988880135SRémi Denis-Courmont 	daddr >>= 2;
44088880135SRémi Denis-Courmont 	rcu_read_lock();
44188880135SRémi Denis-Courmont 	dev = rcu_dereference(routes->table[daddr]);
44255748ac0SRémi Denis-Courmont 	if (dev)
44355748ac0SRémi Denis-Courmont 		dev_hold(dev);
44488880135SRémi Denis-Courmont 	rcu_read_unlock();
44555748ac0SRémi Denis-Courmont 
44655748ac0SRémi Denis-Courmont 	if (!dev)
44755748ac0SRémi Denis-Courmont 		dev = phonet_device_get(net); /* Default route */
44855748ac0SRémi Denis-Courmont 	return dev;
44955748ac0SRémi Denis-Courmont }
450