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 369a3b7a42Sremi.denis-courmont@nokia struct phonet_net { 379a3b7a42Sremi.denis-courmont@nokia struct phonet_device_list pndevs; 38f8ff6028SRemi Denis-Courmont }; 39f8ff6028SRemi Denis-Courmont 409a3b7a42Sremi.denis-courmont@nokia int phonet_net_id; 419a3b7a42Sremi.denis-courmont@nokia 429a3b7a42Sremi.denis-courmont@nokia struct phonet_device_list *phonet_device_list(struct net *net) 439a3b7a42Sremi.denis-courmont@nokia { 449a3b7a42Sremi.denis-courmont@nokia struct phonet_net *pnn = net_generic(net, phonet_net_id); 459a3b7a42Sremi.denis-courmont@nokia return &pnn->pndevs; 469a3b7a42Sremi.denis-courmont@nokia } 479a3b7a42Sremi.denis-courmont@nokia 48f8ff6028SRemi Denis-Courmont /* Allocate new Phonet device. */ 49f8ff6028SRemi Denis-Courmont static struct phonet_device *__phonet_device_alloc(struct net_device *dev) 50f8ff6028SRemi Denis-Courmont { 519a3b7a42Sremi.denis-courmont@nokia struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev)); 52f8ff6028SRemi Denis-Courmont struct phonet_device *pnd = kmalloc(sizeof(*pnd), GFP_ATOMIC); 53f8ff6028SRemi Denis-Courmont if (pnd == NULL) 54f8ff6028SRemi Denis-Courmont return NULL; 55f8ff6028SRemi Denis-Courmont pnd->netdev = dev; 56f8ff6028SRemi Denis-Courmont bitmap_zero(pnd->addrs, 64); 57f8ff6028SRemi Denis-Courmont 589a3b7a42Sremi.denis-courmont@nokia list_add(&pnd->list, &pndevs->list); 59f8ff6028SRemi Denis-Courmont return pnd; 60f8ff6028SRemi Denis-Courmont } 61f8ff6028SRemi Denis-Courmont 62f8ff6028SRemi Denis-Courmont static struct phonet_device *__phonet_get(struct net_device *dev) 63f8ff6028SRemi Denis-Courmont { 649a3b7a42Sremi.denis-courmont@nokia struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev)); 65f8ff6028SRemi Denis-Courmont struct phonet_device *pnd; 66f8ff6028SRemi Denis-Courmont 679a3b7a42Sremi.denis-courmont@nokia list_for_each_entry(pnd, &pndevs->list, list) { 68f8ff6028SRemi Denis-Courmont if (pnd->netdev == dev) 69f8ff6028SRemi Denis-Courmont return pnd; 70f8ff6028SRemi Denis-Courmont } 71f8ff6028SRemi Denis-Courmont return NULL; 72f8ff6028SRemi Denis-Courmont } 73f8ff6028SRemi Denis-Courmont 742be6fa4cSRémi Denis-Courmont static void phonet_device_destroy(struct net_device *dev) 75f8ff6028SRemi Denis-Courmont { 762be6fa4cSRémi Denis-Courmont struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev)); 772be6fa4cSRémi Denis-Courmont struct phonet_device *pnd; 782be6fa4cSRémi Denis-Courmont 792be6fa4cSRémi Denis-Courmont ASSERT_RTNL(); 802be6fa4cSRémi Denis-Courmont 812be6fa4cSRémi Denis-Courmont spin_lock_bh(&pndevs->lock); 822be6fa4cSRémi Denis-Courmont pnd = __phonet_get(dev); 832be6fa4cSRémi Denis-Courmont if (pnd) 84f8ff6028SRemi Denis-Courmont list_del(&pnd->list); 852be6fa4cSRémi Denis-Courmont spin_unlock_bh(&pndevs->lock); 862be6fa4cSRémi Denis-Courmont 872be6fa4cSRémi Denis-Courmont if (pnd) { 882be6fa4cSRémi Denis-Courmont u8 addr; 892be6fa4cSRémi Denis-Courmont 902be6fa4cSRémi Denis-Courmont for (addr = find_first_bit(pnd->addrs, 64); addr < 64; 912be6fa4cSRémi Denis-Courmont addr = find_next_bit(pnd->addrs, 64, 1+addr)) 922be6fa4cSRémi Denis-Courmont phonet_address_notify(RTM_DELADDR, dev, addr); 93f8ff6028SRemi Denis-Courmont kfree(pnd); 94f8ff6028SRemi Denis-Courmont } 952be6fa4cSRémi Denis-Courmont } 96f8ff6028SRemi Denis-Courmont 97f8ff6028SRemi Denis-Courmont struct net_device *phonet_device_get(struct net *net) 98f8ff6028SRemi Denis-Courmont { 999a3b7a42Sremi.denis-courmont@nokia struct phonet_device_list *pndevs = phonet_device_list(net); 100f8ff6028SRemi Denis-Courmont struct phonet_device *pnd; 10159e57f44SEric Dumazet struct net_device *dev = NULL; 102f8ff6028SRemi Denis-Courmont 1039a3b7a42Sremi.denis-courmont@nokia spin_lock_bh(&pndevs->lock); 1049a3b7a42Sremi.denis-courmont@nokia list_for_each_entry(pnd, &pndevs->list, list) { 105f8ff6028SRemi Denis-Courmont dev = pnd->netdev; 106f8ff6028SRemi Denis-Courmont BUG_ON(!dev); 107f8ff6028SRemi Denis-Courmont 1089a3b7a42Sremi.denis-courmont@nokia if ((dev->reg_state == NETREG_REGISTERED) && 109f8ff6028SRemi Denis-Courmont ((pnd->netdev->flags & IFF_UP)) == IFF_UP) 110f8ff6028SRemi Denis-Courmont break; 111f8ff6028SRemi Denis-Courmont dev = NULL; 112f8ff6028SRemi Denis-Courmont } 113f8ff6028SRemi Denis-Courmont if (dev) 114f8ff6028SRemi Denis-Courmont dev_hold(dev); 1159a3b7a42Sremi.denis-courmont@nokia spin_unlock_bh(&pndevs->lock); 116f8ff6028SRemi Denis-Courmont return dev; 117f8ff6028SRemi Denis-Courmont } 118f8ff6028SRemi Denis-Courmont 119f8ff6028SRemi Denis-Courmont int phonet_address_add(struct net_device *dev, u8 addr) 120f8ff6028SRemi Denis-Courmont { 1219a3b7a42Sremi.denis-courmont@nokia struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev)); 122f8ff6028SRemi Denis-Courmont struct phonet_device *pnd; 123f8ff6028SRemi Denis-Courmont int err = 0; 124f8ff6028SRemi Denis-Courmont 1259a3b7a42Sremi.denis-courmont@nokia spin_lock_bh(&pndevs->lock); 126f8ff6028SRemi Denis-Courmont /* Find or create Phonet-specific device data */ 127f8ff6028SRemi Denis-Courmont pnd = __phonet_get(dev); 128f8ff6028SRemi Denis-Courmont if (pnd == NULL) 129f8ff6028SRemi Denis-Courmont pnd = __phonet_device_alloc(dev); 130f8ff6028SRemi Denis-Courmont if (unlikely(pnd == NULL)) 131f8ff6028SRemi Denis-Courmont err = -ENOMEM; 132f8ff6028SRemi Denis-Courmont else if (test_and_set_bit(addr >> 2, pnd->addrs)) 133f8ff6028SRemi Denis-Courmont err = -EEXIST; 1349a3b7a42Sremi.denis-courmont@nokia spin_unlock_bh(&pndevs->lock); 135f8ff6028SRemi Denis-Courmont return err; 136f8ff6028SRemi Denis-Courmont } 137f8ff6028SRemi Denis-Courmont 138f8ff6028SRemi Denis-Courmont int phonet_address_del(struct net_device *dev, u8 addr) 139f8ff6028SRemi Denis-Courmont { 1409a3b7a42Sremi.denis-courmont@nokia struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev)); 141f8ff6028SRemi Denis-Courmont struct phonet_device *pnd; 142f8ff6028SRemi Denis-Courmont int err = 0; 143f8ff6028SRemi Denis-Courmont 1449a3b7a42Sremi.denis-courmont@nokia spin_lock_bh(&pndevs->lock); 145f8ff6028SRemi Denis-Courmont pnd = __phonet_get(dev); 146f8ff6028SRemi Denis-Courmont if (!pnd || !test_and_clear_bit(addr >> 2, pnd->addrs)) 147f8ff6028SRemi Denis-Courmont err = -EADDRNOTAVAIL; 1482be6fa4cSRémi Denis-Courmont else if (bitmap_empty(pnd->addrs, 64)) { 1492be6fa4cSRémi Denis-Courmont list_del(&pnd->list); 1502be6fa4cSRémi Denis-Courmont kfree(pnd); 1512be6fa4cSRémi Denis-Courmont } 1529a3b7a42Sremi.denis-courmont@nokia spin_unlock_bh(&pndevs->lock); 153f8ff6028SRemi Denis-Courmont return err; 154f8ff6028SRemi Denis-Courmont } 155f8ff6028SRemi Denis-Courmont 156f8ff6028SRemi Denis-Courmont /* Gets a source address toward a destination, through a interface. */ 157f8ff6028SRemi Denis-Courmont u8 phonet_address_get(struct net_device *dev, u8 addr) 158f8ff6028SRemi Denis-Courmont { 1599a3b7a42Sremi.denis-courmont@nokia struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev)); 160f8ff6028SRemi Denis-Courmont struct phonet_device *pnd; 161f8ff6028SRemi Denis-Courmont 1629a3b7a42Sremi.denis-courmont@nokia spin_lock_bh(&pndevs->lock); 163f8ff6028SRemi Denis-Courmont pnd = __phonet_get(dev); 164f8ff6028SRemi Denis-Courmont if (pnd) { 165f8ff6028SRemi Denis-Courmont BUG_ON(bitmap_empty(pnd->addrs, 64)); 166f8ff6028SRemi Denis-Courmont 167f8ff6028SRemi Denis-Courmont /* Use same source address as destination, if possible */ 168f8ff6028SRemi Denis-Courmont if (!test_bit(addr >> 2, pnd->addrs)) 169f8ff6028SRemi Denis-Courmont addr = find_first_bit(pnd->addrs, 64) << 2; 170f8ff6028SRemi Denis-Courmont } else 171f8ff6028SRemi Denis-Courmont addr = PN_NO_ADDR; 1729a3b7a42Sremi.denis-courmont@nokia spin_unlock_bh(&pndevs->lock); 173f8ff6028SRemi Denis-Courmont return addr; 174f8ff6028SRemi Denis-Courmont } 175f8ff6028SRemi Denis-Courmont 17652404881SRémi Denis-Courmont int phonet_address_lookup(struct net *net, u8 addr) 177f8ff6028SRemi Denis-Courmont { 1789a3b7a42Sremi.denis-courmont@nokia struct phonet_device_list *pndevs = phonet_device_list(net); 179f8ff6028SRemi Denis-Courmont struct phonet_device *pnd; 1809a3b7a42Sremi.denis-courmont@nokia int err = -EADDRNOTAVAIL; 181f8ff6028SRemi Denis-Courmont 1829a3b7a42Sremi.denis-courmont@nokia spin_lock_bh(&pndevs->lock); 1839a3b7a42Sremi.denis-courmont@nokia list_for_each_entry(pnd, &pndevs->list, list) { 184f8ff6028SRemi Denis-Courmont /* Don't allow unregistering devices! */ 185f8ff6028SRemi Denis-Courmont if ((pnd->netdev->reg_state != NETREG_REGISTERED) || 186f8ff6028SRemi Denis-Courmont ((pnd->netdev->flags & IFF_UP)) != IFF_UP) 187f8ff6028SRemi Denis-Courmont continue; 188f8ff6028SRemi Denis-Courmont 189f8ff6028SRemi Denis-Courmont if (test_bit(addr >> 2, pnd->addrs)) { 1909a3b7a42Sremi.denis-courmont@nokia err = 0; 1919a3b7a42Sremi.denis-courmont@nokia goto found; 192f8ff6028SRemi Denis-Courmont } 193f8ff6028SRemi Denis-Courmont } 1949a3b7a42Sremi.denis-courmont@nokia found: 1959a3b7a42Sremi.denis-courmont@nokia spin_unlock_bh(&pndevs->lock); 1969a3b7a42Sremi.denis-courmont@nokia return err; 197f8ff6028SRemi Denis-Courmont } 198f8ff6028SRemi Denis-Courmont 199f5bb1c55SRémi Denis-Courmont /* automatically configure a Phonet device, if supported */ 200f5bb1c55SRémi Denis-Courmont static int phonet_device_autoconf(struct net_device *dev) 201f5bb1c55SRémi Denis-Courmont { 202f5bb1c55SRémi Denis-Courmont struct if_phonet_req req; 203f5bb1c55SRémi Denis-Courmont int ret; 204f5bb1c55SRémi Denis-Courmont 205f5bb1c55SRémi Denis-Courmont if (!dev->netdev_ops->ndo_do_ioctl) 206f5bb1c55SRémi Denis-Courmont return -EOPNOTSUPP; 207f5bb1c55SRémi Denis-Courmont 208f5bb1c55SRémi Denis-Courmont ret = dev->netdev_ops->ndo_do_ioctl(dev, (struct ifreq *)&req, 209f5bb1c55SRémi Denis-Courmont SIOCPNGAUTOCONF); 210f5bb1c55SRémi Denis-Courmont if (ret < 0) 211f5bb1c55SRémi Denis-Courmont return ret; 212*b11b5165SRémi Denis-Courmont 213*b11b5165SRémi Denis-Courmont ASSERT_RTNL(); 214*b11b5165SRémi Denis-Courmont ret = phonet_address_add(dev, req.ifr_phonet_autoconf.device); 215*b11b5165SRémi Denis-Courmont if (ret) 216*b11b5165SRémi Denis-Courmont return ret; 217*b11b5165SRémi Denis-Courmont phonet_address_notify(RTM_NEWADDR, dev, 218*b11b5165SRémi Denis-Courmont req.ifr_phonet_autoconf.device); 219*b11b5165SRémi Denis-Courmont return 0; 220f5bb1c55SRémi Denis-Courmont } 221f5bb1c55SRémi Denis-Courmont 222f8ff6028SRemi Denis-Courmont /* notify Phonet of device events */ 223f8ff6028SRemi Denis-Courmont static int phonet_device_notify(struct notifier_block *me, unsigned long what, 224f8ff6028SRemi Denis-Courmont void *arg) 225f8ff6028SRemi Denis-Courmont { 226f8ff6028SRemi Denis-Courmont struct net_device *dev = arg; 227f8ff6028SRemi Denis-Courmont 228f5bb1c55SRémi Denis-Courmont switch (what) { 229f5bb1c55SRémi Denis-Courmont case NETDEV_REGISTER: 230f5bb1c55SRémi Denis-Courmont if (dev->type == ARPHRD_PHONET) 231f5bb1c55SRémi Denis-Courmont phonet_device_autoconf(dev); 232f5bb1c55SRémi Denis-Courmont break; 233f5bb1c55SRémi Denis-Courmont case NETDEV_UNREGISTER: 2342be6fa4cSRémi Denis-Courmont phonet_device_destroy(dev); 235f5bb1c55SRémi Denis-Courmont break; 236f5bb1c55SRémi Denis-Courmont } 237f8ff6028SRemi Denis-Courmont return 0; 238f8ff6028SRemi Denis-Courmont 239f8ff6028SRemi Denis-Courmont } 240f8ff6028SRemi Denis-Courmont 241f8ff6028SRemi Denis-Courmont static struct notifier_block phonet_device_notifier = { 242f8ff6028SRemi Denis-Courmont .notifier_call = phonet_device_notify, 243f8ff6028SRemi Denis-Courmont .priority = 0, 244f8ff6028SRemi Denis-Courmont }; 245f8ff6028SRemi Denis-Courmont 2469a3b7a42Sremi.denis-courmont@nokia /* Per-namespace Phonet devices handling */ 2479a3b7a42Sremi.denis-courmont@nokia static int phonet_init_net(struct net *net) 2489a3b7a42Sremi.denis-courmont@nokia { 2499a3b7a42Sremi.denis-courmont@nokia struct phonet_net *pnn = kmalloc(sizeof(*pnn), GFP_KERNEL); 2509a3b7a42Sremi.denis-courmont@nokia if (!pnn) 2519a3b7a42Sremi.denis-courmont@nokia return -ENOMEM; 2529a3b7a42Sremi.denis-courmont@nokia 253c1dc13e9SRémi Denis-Courmont if (!proc_net_fops_create(net, "phonet", 0, &pn_sock_seq_fops)) { 254c1dc13e9SRémi Denis-Courmont kfree(pnn); 255c1dc13e9SRémi Denis-Courmont return -ENOMEM; 256c1dc13e9SRémi Denis-Courmont } 257c1dc13e9SRémi Denis-Courmont 2589a3b7a42Sremi.denis-courmont@nokia INIT_LIST_HEAD(&pnn->pndevs.list); 2599a3b7a42Sremi.denis-courmont@nokia spin_lock_init(&pnn->pndevs.lock); 2609a3b7a42Sremi.denis-courmont@nokia net_assign_generic(net, phonet_net_id, pnn); 2619a3b7a42Sremi.denis-courmont@nokia return 0; 2629a3b7a42Sremi.denis-courmont@nokia } 2639a3b7a42Sremi.denis-courmont@nokia 2649a3b7a42Sremi.denis-courmont@nokia static void phonet_exit_net(struct net *net) 2659a3b7a42Sremi.denis-courmont@nokia { 2669a3b7a42Sremi.denis-courmont@nokia struct phonet_net *pnn = net_generic(net, phonet_net_id); 2672be6fa4cSRémi Denis-Courmont struct net_device *dev; 2689a3b7a42Sremi.denis-courmont@nokia 2692be6fa4cSRémi Denis-Courmont rtnl_lock(); 2702be6fa4cSRémi Denis-Courmont for_each_netdev(net, dev) 2712be6fa4cSRémi Denis-Courmont phonet_device_destroy(dev); 2722be6fa4cSRémi Denis-Courmont rtnl_unlock(); 273c1dc13e9SRémi Denis-Courmont 274c1dc13e9SRémi Denis-Courmont proc_net_remove(net, "phonet"); 2759a3b7a42Sremi.denis-courmont@nokia kfree(pnn); 2769a3b7a42Sremi.denis-courmont@nokia } 2779a3b7a42Sremi.denis-courmont@nokia 2789a3b7a42Sremi.denis-courmont@nokia static struct pernet_operations phonet_net_ops = { 2799a3b7a42Sremi.denis-courmont@nokia .init = phonet_init_net, 2809a3b7a42Sremi.denis-courmont@nokia .exit = phonet_exit_net, 2819a3b7a42Sremi.denis-courmont@nokia }; 2829a3b7a42Sremi.denis-courmont@nokia 283f8ff6028SRemi Denis-Courmont /* Initialize Phonet devices list */ 28476e02cf6Sremi.denis-courmont@nokia int __init phonet_device_init(void) 285f8ff6028SRemi Denis-Courmont { 2869a3b7a42Sremi.denis-courmont@nokia int err = register_pernet_gen_device(&phonet_net_id, &phonet_net_ops); 2879a3b7a42Sremi.denis-courmont@nokia if (err) 2889a3b7a42Sremi.denis-courmont@nokia return err; 289660f706dSremi.denis-courmont@nokia 290f8ff6028SRemi Denis-Courmont register_netdevice_notifier(&phonet_device_notifier); 291660f706dSremi.denis-courmont@nokia err = phonet_netlink_register(); 292660f706dSremi.denis-courmont@nokia if (err) 293660f706dSremi.denis-courmont@nokia phonet_device_exit(); 294660f706dSremi.denis-courmont@nokia return err; 295f8ff6028SRemi Denis-Courmont } 296f8ff6028SRemi Denis-Courmont 297f8ff6028SRemi Denis-Courmont void phonet_device_exit(void) 298f8ff6028SRemi Denis-Courmont { 299f8ff6028SRemi Denis-Courmont rtnl_unregister_all(PF_PHONET); 3006530e0feSremi.denis-courmont@nokia unregister_netdevice_notifier(&phonet_device_notifier); 3019a3b7a42Sremi.denis-courmont@nokia unregister_pernet_gen_device(phonet_net_id, &phonet_net_ops); 302f8ff6028SRemi Denis-Courmont } 303