pn_dev.c 5.0 KB
Newer Older
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
/*
 * File: pn_dev.c
 *
 * Phonet network device
 *
 * Copyright (C) 2008 Nokia Corporation.
 *
 * Contact: Remi Denis-Courmont <remi.denis-courmont@nokia.com>
 * Original author: Sakari Ailus <sakari.ailus@nokia.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/phonet.h>
#include <net/sock.h>
#include <net/phonet/pn_dev.h>

/* when accessing, remember to lock with spin_lock(&pndevs.lock); */
struct phonet_device_list pndevs = {
	.list = LIST_HEAD_INIT(pndevs.list),
	.lock = __SPIN_LOCK_UNLOCKED(pndevs.lock),
};

/* Allocate new Phonet device. */
static struct phonet_device *__phonet_device_alloc(struct net_device *dev)
{
	struct phonet_device *pnd = kmalloc(sizeof(*pnd), GFP_ATOMIC);
	if (pnd == NULL)
		return NULL;
	pnd->netdev = dev;
	bitmap_zero(pnd->addrs, 64);

	list_add(&pnd->list, &pndevs.list);
	return pnd;
}

static struct phonet_device *__phonet_get(struct net_device *dev)
{
	struct phonet_device *pnd;

	list_for_each_entry(pnd, &pndevs.list, list) {
		if (pnd->netdev == dev)
			return pnd;
	}
	return NULL;
}

static void __phonet_device_free(struct phonet_device *pnd)
{
	list_del(&pnd->list);
	kfree(pnd);
}

struct net_device *phonet_device_get(struct net *net)
{
	struct phonet_device *pnd;
	struct net_device *dev;

	spin_lock_bh(&pndevs.lock);
	list_for_each_entry(pnd, &pndevs.list, list) {
		dev = pnd->netdev;
		BUG_ON(!dev);

79
		if (net_eq(dev_net(dev), net) &&
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
			(dev->reg_state == NETREG_REGISTERED) &&
			((pnd->netdev->flags & IFF_UP)) == IFF_UP)
			break;
		dev = NULL;
	}
	if (dev)
		dev_hold(dev);
	spin_unlock_bh(&pndevs.lock);
	return dev;
}

int phonet_address_add(struct net_device *dev, u8 addr)
{
	struct phonet_device *pnd;
	int err = 0;

	spin_lock_bh(&pndevs.lock);
	/* Find or create Phonet-specific device data */
	pnd = __phonet_get(dev);
	if (pnd == NULL)
		pnd = __phonet_device_alloc(dev);
	if (unlikely(pnd == NULL))
		err = -ENOMEM;
	else if (test_and_set_bit(addr >> 2, pnd->addrs))
		err = -EEXIST;
	spin_unlock_bh(&pndevs.lock);
	return err;
}

int phonet_address_del(struct net_device *dev, u8 addr)
{
	struct phonet_device *pnd;
	int err = 0;

	spin_lock_bh(&pndevs.lock);
	pnd = __phonet_get(dev);
	if (!pnd || !test_and_clear_bit(addr >> 2, pnd->addrs))
		err = -EADDRNOTAVAIL;
118
	else if (bitmap_empty(pnd->addrs, 64))
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
		__phonet_device_free(pnd);
	spin_unlock_bh(&pndevs.lock);
	return err;
}

/* Gets a source address toward a destination, through a interface. */
u8 phonet_address_get(struct net_device *dev, u8 addr)
{
	struct phonet_device *pnd;

	spin_lock_bh(&pndevs.lock);
	pnd = __phonet_get(dev);
	if (pnd) {
		BUG_ON(bitmap_empty(pnd->addrs, 64));

		/* Use same source address as destination, if possible */
		if (!test_bit(addr >> 2, pnd->addrs))
			addr = find_first_bit(pnd->addrs, 64) << 2;
	} else
		addr = PN_NO_ADDR;
	spin_unlock_bh(&pndevs.lock);
	return addr;
}

143
int phonet_address_lookup(struct net *net, u8 addr)
144 145 146 147 148
{
	struct phonet_device *pnd;

	spin_lock_bh(&pndevs.lock);
	list_for_each_entry(pnd, &pndevs.list, list) {
149 150
		if (!net_eq(dev_net(pnd->netdev), net))
			continue;
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
		/* Don't allow unregistering devices! */
		if ((pnd->netdev->reg_state != NETREG_REGISTERED) ||
				((pnd->netdev->flags & IFF_UP)) != IFF_UP)
			continue;

		if (test_bit(addr >> 2, pnd->addrs)) {
			spin_unlock_bh(&pndevs.lock);
			return 0;
		}
	}
	spin_unlock_bh(&pndevs.lock);
	return -EADDRNOTAVAIL;
}

/* notify Phonet of device events */
static int phonet_device_notify(struct notifier_block *me, unsigned long what,
				void *arg)
{
	struct net_device *dev = arg;

	if (what == NETDEV_UNREGISTER) {
		struct phonet_device *pnd;

		/* Destroy phonet-specific device data */
		spin_lock_bh(&pndevs.lock);
		pnd = __phonet_get(dev);
		if (pnd)
			__phonet_device_free(pnd);
		spin_unlock_bh(&pndevs.lock);
	}
	return 0;

}

static struct notifier_block phonet_device_notifier = {
	.notifier_call = phonet_device_notify,
	.priority = 0,
};

/* Initialize Phonet devices list */
void phonet_device_init(void)
{
	register_netdevice_notifier(&phonet_device_notifier);
}

void phonet_device_exit(void)
{
	struct phonet_device *pnd, *n;

	rtnl_unregister_all(PF_PHONET);
	rtnl_lock();
	spin_lock_bh(&pndevs.lock);

	list_for_each_entry_safe(pnd, n, &pndevs.list, list)
		__phonet_device_free(pnd);

	spin_unlock_bh(&pndevs.lock);
	rtnl_unlock();
	unregister_netdevice_notifier(&phonet_device_notifier);
}