Commit b6301f43 authored by Chas Williams's avatar Chas Williams Committed by Stephen Hemminger

[ATM]: make br2684 more net_device * centric (from shemminger@osdl.org ala mitch@sfgoth.com)

parent b06c109f
...@@ -66,7 +66,7 @@ enum br2684_encaps { ...@@ -66,7 +66,7 @@ enum br2684_encaps {
struct br2684_vcc { struct br2684_vcc {
struct atm_vcc *atmvcc; struct atm_vcc *atmvcc;
struct br2684_dev *brdev; struct net_device *device;
/* keep old push,pop functions for chaining */ /* keep old push,pop functions for chaining */
void (*old_push)(struct atm_vcc *vcc,struct sk_buff *skb); void (*old_push)(struct atm_vcc *vcc,struct sk_buff *skb);
/* void (*old_pop)(struct atm_vcc *vcc,struct sk_buff *skb); */ /* void (*old_pop)(struct atm_vcc *vcc,struct sk_buff *skb); */
...@@ -81,7 +81,7 @@ struct br2684_vcc { ...@@ -81,7 +81,7 @@ struct br2684_vcc {
}; };
struct br2684_dev { struct br2684_dev {
struct net_device net_dev; struct net_device *net_dev;
struct list_head br2684_devs; struct list_head br2684_devs;
int number; int number;
struct list_head brvccs; /* one device <=> one vcc (before xmas) */ struct list_head brvccs; /* one device <=> one vcc (before xmas) */
...@@ -102,13 +102,12 @@ static LIST_HEAD(br2684_devs); ...@@ -102,13 +102,12 @@ static LIST_HEAD(br2684_devs);
static inline struct br2684_dev *BRPRIV(const struct net_device *net_dev) static inline struct br2684_dev *BRPRIV(const struct net_device *net_dev)
{ {
return (struct br2684_dev *) ((char *) (net_dev) - return (struct br2684_dev *) net_dev->priv;
(unsigned long) (&((struct br2684_dev *) 0)->net_dev));
} }
static inline struct br2684_dev *list_entry_brdev(const struct list_head *le) static inline struct net_device *list_entry_brdev(const struct list_head *le)
{ {
return list_entry(le, struct br2684_dev, br2684_devs); return list_entry(le, struct br2684_dev, br2684_devs)->net_dev;
} }
static inline struct br2684_vcc *BR2684_VCC(const struct atm_vcc *atmvcc) static inline struct br2684_vcc *BR2684_VCC(const struct atm_vcc *atmvcc)
...@@ -122,24 +121,23 @@ static inline struct br2684_vcc *list_entry_brvcc(const struct list_head *le) ...@@ -122,24 +121,23 @@ static inline struct br2684_vcc *list_entry_brvcc(const struct list_head *le)
} }
/* Caller should hold read_lock(&devs_lock) */ /* Caller should hold read_lock(&devs_lock) */
static struct br2684_dev *br2684_find_dev(const struct br2684_if_spec *s) static struct net_device *br2684_find_dev(const struct br2684_if_spec *s)
{ {
struct list_head *lh; struct list_head *lh;
struct br2684_dev *brdev; struct net_device *net_dev;
switch (s->method) { switch (s->method) {
case BR2684_FIND_BYNUM: case BR2684_FIND_BYNUM:
list_for_each(lh, &br2684_devs) { list_for_each(lh, &br2684_devs) {
brdev = list_entry_brdev(lh); net_dev = list_entry_brdev(lh);
if (brdev->number == s->spec.devnum) if (BRPRIV(net_dev)->number == s->spec.devnum)
return brdev; return net_dev;
} }
break; break;
case BR2684_FIND_BYIFNAME: case BR2684_FIND_BYIFNAME:
list_for_each(lh, &br2684_devs) { list_for_each(lh, &br2684_devs) {
brdev = list_entry_brdev(lh); net_dev = list_entry_brdev(lh);
if (!strncmp(brdev->net_dev.name, s->spec.ifname, if (!strncmp(net_dev->name, s->spec.ifname, IFNAMSIZ))
sizeof brdev->net_dev.name)) return net_dev;
return brdev;
} }
break; break;
} }
...@@ -357,7 +355,7 @@ static int br2684_setfilt(struct atm_vcc *atmvcc, unsigned long arg) ...@@ -357,7 +355,7 @@ static int br2684_setfilt(struct atm_vcc *atmvcc, unsigned long arg)
*/ */
struct br2684_dev *brdev; struct br2684_dev *brdev;
read_lock(&devs_lock); read_lock(&devs_lock);
brdev = br2684_find_dev(&fs.ifspec); brdev = BRPRIV(br2684_find_dev(&fs.ifspec));
if (brdev == NULL || list_empty(&brdev->brvccs) || if (brdev == NULL || list_empty(&brdev->brvccs) ||
brdev->brvccs.next != brdev->brvccs.prev) /* >1 VCC */ brdev->brvccs.next != brdev->brvccs.prev) /* >1 VCC */
brvcc = NULL; brvcc = NULL;
...@@ -393,33 +391,35 @@ packet_fails_filter(u16 type, struct br2684_vcc *brvcc, struct sk_buff *skb) ...@@ -393,33 +391,35 @@ packet_fails_filter(u16 type, struct br2684_vcc *brvcc, struct sk_buff *skb)
static void br2684_close_vcc(struct br2684_vcc *brvcc) static void br2684_close_vcc(struct br2684_vcc *brvcc)
{ {
DPRINTK("removing VCC %p from dev %p\n", brvcc, brvcc->brdev); DPRINTK("removing VCC %p from dev %p\n", brvcc, brvcc->device);
write_lock_irq(&devs_lock); write_lock_irq(&devs_lock);
list_del(&brvcc->brvccs); list_del(&brvcc->brvccs);
write_unlock_irq(&devs_lock); write_unlock_irq(&devs_lock);
brvcc->atmvcc->user_back = NULL; /* what about vcc->recvq ??? */ brvcc->atmvcc->user_back = NULL; /* what about vcc->recvq ??? */
brvcc->old_push(brvcc->atmvcc, NULL); /* pass on the bad news */ brvcc->old_push(brvcc->atmvcc, NULL); /* pass on the bad news */
kfree(brvcc); kfree(brvcc);
MOD_DEC_USE_COUNT; module_put(THIS_MODULE);
} }
/* when AAL5 PDU comes in: */ /* when AAL5 PDU comes in: */
static void br2684_push(struct atm_vcc *atmvcc, struct sk_buff *skb) static void br2684_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
{ {
struct br2684_vcc *brvcc = BR2684_VCC(atmvcc); struct br2684_vcc *brvcc = BR2684_VCC(atmvcc);
struct br2684_dev *brdev = brvcc->brdev; struct net_device *net_dev = brvcc->device;
struct br2684_dev *brdev = BRPRIV(net_dev);
int plen = sizeof(llc_oui_pid_pad) + ETH_HLEN; int plen = sizeof(llc_oui_pid_pad) + ETH_HLEN;
DPRINTK("br2684_push\n"); DPRINTK("br2684_push\n");
if (skb == NULL) { /* skb==NULL means VCC is being destroyed */ if (unlikely(skb == NULL)) {
/* skb==NULL means VCC is being destroyed */
br2684_close_vcc(brvcc); br2684_close_vcc(brvcc);
if (list_empty(&brdev->brvccs)) { if (list_empty(&brdev->brvccs)) {
read_lock(&devs_lock); read_lock(&devs_lock);
list_del(&brdev->br2684_devs); list_del(&brdev->br2684_devs);
read_unlock(&devs_lock); read_unlock(&devs_lock);
unregister_netdev(&brdev->net_dev); unregister_netdev(net_dev);
kfree(brdev); kfree(net_dev);
} }
return; return;
} }
...@@ -460,24 +460,25 @@ static void br2684_push(struct atm_vcc *atmvcc, struct sk_buff *skb) ...@@ -460,24 +460,25 @@ static void br2684_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
#ifdef CONFIG_BR2684_FAST_TRANS #ifdef CONFIG_BR2684_FAST_TRANS
skb->protocol = ((u16 *) skb->data)[-1]; skb->protocol = ((u16 *) skb->data)[-1];
#else /* some protocols might require this: */ #else /* some protocols might require this: */
skb->protocol = br_type_trans(skb, &brdev->net_dev); skb->protocol = br_type_trans(skb, net_dev);
#endif /* CONFIG_BR2684_FAST_TRANS */ #endif /* CONFIG_BR2684_FAST_TRANS */
#else #else
skb_pull(skb, plen - ETH_HLEN); skb_pull(skb, plen - ETH_HLEN);
skb->protocol = eth_type_trans(skb, &brdev->net_dev); skb->protocol = eth_type_trans(skb, net_dev);
#endif /* FASTER_VERSION */ #endif /* FASTER_VERSION */
#ifdef CONFIG_ATM_BR2684_IPFILTER #ifdef CONFIG_ATM_BR2684_IPFILTER
if (packet_fails_filter(skb->protocol, brvcc, skb)) { if (unlikely(packet_fails_filter(skb->protocol, brvcc, skb))) {
brdev->stats.rx_dropped++; brdev->stats.rx_dropped++;
dev_kfree_skb(skb); dev_kfree_skb(skb);
return; return;
} }
#endif /* CONFIG_ATM_BR2684_IPFILTER */ #endif /* CONFIG_ATM_BR2684_IPFILTER */
skb->dev = &brdev->net_dev; skb->dev = net_dev;
ATM_SKB(skb)->vcc = atmvcc; /* needed ? */ ATM_SKB(skb)->vcc = atmvcc; /* needed ? */
DPRINTK("received packet's protocol: %x\n", ntohs(skb->protocol)); DPRINTK("received packet's protocol: %x\n", ntohs(skb->protocol));
skb_debug(skb); skb_debug(skb);
if (!(brdev->net_dev.flags & IFF_UP)) { /* sigh, interface is down */ if (unlikely(!(net_dev->flags & IFF_UP))) {
/* sigh, interface is down */
brdev->stats.rx_dropped++; brdev->stats.rx_dropped++;
dev_kfree_skb(skb); dev_kfree_skb(skb);
return; return;
...@@ -498,26 +499,30 @@ Note: we do not have explicit unassign, but look at _push() ...@@ -498,26 +499,30 @@ Note: we do not have explicit unassign, but look at _push()
struct sk_buff_head copy; struct sk_buff_head copy;
struct sk_buff *skb; struct sk_buff *skb;
struct br2684_dev *brdev; struct br2684_dev *brdev;
struct net_device *net_dev;
struct atm_backend_br2684 be; struct atm_backend_br2684 be;
MOD_INC_USE_COUNT; if (copy_from_user(&be, (void *) arg, sizeof be))
if (copy_from_user(&be, (void *) arg, sizeof be)) {
MOD_DEC_USE_COUNT;
return -EFAULT; return -EFAULT;
} brvcc = kmalloc(sizeof(struct br2684_vcc), GFP_KERNEL);
if (!brvcc)
return -ENOMEM;
memset(brvcc, 0, sizeof(struct br2684_vcc));
write_lock_irq(&devs_lock); write_lock_irq(&devs_lock);
brdev = br2684_find_dev(&be.ifspec); net_dev = br2684_find_dev(&be.ifspec);
if (brdev == NULL) { if (net_dev == NULL) {
printk(KERN_ERR printk(KERN_ERR
"br2684: tried to attach to non-existant device\n"); "br2684: tried to attach to non-existant device\n");
err = -ENXIO; err = -ENXIO;
goto error; goto error;
} }
brdev = BRPRIV(net_dev);
if (atmvcc->push == NULL) { if (atmvcc->push == NULL) {
err = -EBADFD; err = -EBADFD;
goto error; goto error;
} }
if (!list_empty(&brdev->brvccs)) { /* Only 1 VCC/dev right now */ if (!list_empty(&brdev->brvccs)) {
/* Only 1 VCC/dev right now */
err = -EEXIST; err = -EEXIST;
goto error; goto error;
} }
...@@ -528,25 +533,18 @@ Note: we do not have explicit unassign, but look at _push() ...@@ -528,25 +533,18 @@ Note: we do not have explicit unassign, but look at _push()
err = -EINVAL; err = -EINVAL;
goto error; goto error;
} }
brvcc = kmalloc(sizeof(struct br2684_vcc), GFP_KERNEL);
if (!brvcc) {
err = -ENOMEM;
goto error;
}
memset(brvcc, 0, sizeof(struct br2684_vcc));
DPRINTK("br2684_regvcc vcc=%p, encaps=%d, brvcc=%p\n", atmvcc, be.encaps, DPRINTK("br2684_regvcc vcc=%p, encaps=%d, brvcc=%p\n", atmvcc, be.encaps,
brvcc); brvcc);
if (list_empty(&brdev->brvccs) && !brdev->mac_was_set) { if (list_empty(&brdev->brvccs) && !brdev->mac_was_set) {
unsigned char *esi = atmvcc->dev->esi; unsigned char *esi = atmvcc->dev->esi;
if (esi[0] | esi[1] | esi[2] | esi[3] | esi[4] | esi[5]) if (esi[0] | esi[1] | esi[2] | esi[3] | esi[4] | esi[5])
memcpy(brdev->net_dev.dev_addr, esi, memcpy(net_dev->dev_addr, esi, net_dev->addr_len);
brdev->net_dev.addr_len);
else else
brdev->net_dev.dev_addr[2] = 1; net_dev->dev_addr[2] = 1;
} }
list_add(&brvcc->brvccs, &brdev->brvccs); list_add(&brvcc->brvccs, &brdev->brvccs);
write_unlock_irq(&devs_lock); write_unlock_irq(&devs_lock);
brvcc->brdev = brdev; brvcc->device = net_dev;
brvcc->atmvcc = atmvcc; brvcc->atmvcc = atmvcc;
atmvcc->user_back = brvcc; atmvcc->user_back = brvcc;
brvcc->encaps = (enum br2684_encaps) be.encaps; brvcc->encaps = (enum br2684_encaps) be.encaps;
...@@ -560,80 +558,74 @@ Note: we do not have explicit unassign, but look at _push() ...@@ -560,80 +558,74 @@ Note: we do not have explicit unassign, but look at _push()
BRPRIV(skb->dev)->stats.rx_packets--; BRPRIV(skb->dev)->stats.rx_packets--;
br2684_push(atmvcc, skb); br2684_push(atmvcc, skb);
} }
(void) try_module_get(THIS_MODULE);
return 0; return 0;
error: error:
write_unlock_irq(&devs_lock); write_unlock_irq(&devs_lock);
MOD_DEC_USE_COUNT; kfree(brvcc);
return err; return err;
} }
static void br2684_setup(struct net_device *netdev)
{
struct br2684_dev *brdev = BRPRIV(netdev);
ether_setup(netdev);
brdev->net_dev = netdev;
#ifdef FASTER_VERSION
my_eth_header = netdev->hard_header;
netdev->hard_header = br2684_header;
my_eth_header_cache = netdev->hard_header_cache;
netdev->hard_header_cache = br2684_header_cache;
netdev->hard_header_len = sizeof(llc_oui_pid_pad) + ETH_HLEN; /* 10 + 14 */
#endif
my_eth_mac_addr = netdev->set_mac_address;
netdev->set_mac_address = br2684_mac_addr;
netdev->hard_start_xmit = br2684_start_xmit;
netdev->get_stats = br2684_get_stats;
INIT_LIST_HEAD(&brdev->brvccs);
}
static int br2684_create(unsigned long arg) static int br2684_create(unsigned long arg)
{ {
int err; int err;
struct net_device *netdev;
struct br2684_dev *brdev; struct br2684_dev *brdev;
struct atm_newif_br2684 ni; struct atm_newif_br2684 ni;
DPRINTK("br2684_create\n"); DPRINTK("br2684_create\n");
/*
* We track module use by vcc's NOT the devices they're on. We're
* protected here against module death by the kernel_lock, but if
* we need to sleep we should make sure that the module doesn't
* disappear under us.
*/
MOD_INC_USE_COUNT;
if (copy_from_user(&ni, (void *) arg, sizeof ni)) { if (copy_from_user(&ni, (void *) arg, sizeof ni)) {
MOD_DEC_USE_COUNT;
return -EFAULT; return -EFAULT;
} }
if (ni.media != BR2684_MEDIA_ETHERNET || ni.mtu != 1500) { if (ni.media != BR2684_MEDIA_ETHERNET || ni.mtu != 1500) {
MOD_DEC_USE_COUNT;
return -EINVAL; return -EINVAL;
} }
if ((brdev = kmalloc(sizeof(struct br2684_dev), GFP_KERNEL)) == NULL) {
MOD_DEC_USE_COUNT;
return -ENOMEM;
}
memset(brdev, 0, sizeof(struct br2684_dev));
INIT_LIST_HEAD(&brdev->brvccs);
write_lock_irq(&devs_lock); netdev = alloc_netdev(sizeof(struct br2684_dev),
brdev->number = list_empty(&br2684_devs) ? 1 : ni.ifname[0] ? ni.ifname : "nas%d",
list_entry_brdev(br2684_devs.prev)->number + 1; br2684_setup);
list_add_tail(&brdev->br2684_devs, &br2684_devs); if (!netdev)
write_unlock_irq(&devs_lock); return -ENOMEM;
if (ni.ifname[0] != '\0') { brdev = BRPRIV(netdev);
memcpy(brdev->net_dev.name, ni.ifname,
sizeof(brdev->net_dev.name));
brdev->net_dev.name[sizeof(brdev->net_dev.name) - 1] = '\0';
} else
sprintf(brdev->net_dev.name, "nas%d", brdev->number);
DPRINTK("registered netdev %s\n", brdev->net_dev.name);
ether_setup(&brdev->net_dev);
brdev->mac_was_set = 0;
#ifdef FASTER_VERSION
my_eth_header = brdev->net_dev.hard_header;
brdev->net_dev.hard_header = br2684_header;
my_eth_header_cache = brdev->net_dev.hard_header_cache;
brdev->net_dev.hard_header_cache = br2684_header_cache;
brdev->net_dev.hard_header_len = sizeof(llc_oui_pid_pad) + ETH_HLEN; /* 10 + 14 */
#endif
my_eth_mac_addr = brdev->net_dev.set_mac_address;
brdev->net_dev.set_mac_address = br2684_mac_addr;
brdev->net_dev.hard_start_xmit = br2684_start_xmit;
brdev->net_dev.get_stats = br2684_get_stats;
DPRINTK("registered netdev %s\n", netdev->name);
/* open, stop, do_ioctl ? */ /* open, stop, do_ioctl ? */
err = register_netdev(&brdev->net_dev); err = register_netdev(netdev);
MOD_DEC_USE_COUNT;
if (err < 0) { if (err < 0) {
printk(KERN_ERR "br2684_create: register_netdev failed\n"); printk(KERN_ERR "br2684_create: register_netdev failed\n");
write_lock_irq(&devs_lock); kfree(netdev);
list_del(&brdev->br2684_devs);
write_unlock_irq(&devs_lock);
kfree(brdev);
return err; return err;
} }
write_lock_irq(&devs_lock);
brdev->number = list_empty(&br2684_devs) ? 1 :
BRPRIV(list_entry_brdev(br2684_devs.prev))->number + 1;
list_add_tail(&brdev->br2684_devs, &br2684_devs);
write_unlock_irq(&devs_lock);
return 0; return 0;
} }
...@@ -649,9 +641,7 @@ static int br2684_ioctl(struct atm_vcc *atmvcc, unsigned int cmd, ...@@ -649,9 +641,7 @@ static int br2684_ioctl(struct atm_vcc *atmvcc, unsigned int cmd,
case ATM_SETBACKEND: case ATM_SETBACKEND:
case ATM_NEWBACKENDIF: { case ATM_NEWBACKENDIF: {
atm_backend_t b; atm_backend_t b;
MOD_INC_USE_COUNT;
err = get_user(b, (atm_backend_t *) arg); err = get_user(b, (atm_backend_t *) arg);
MOD_DEC_USE_COUNT;
if (err) if (err)
return -EFAULT; return -EFAULT;
if (b != ATM_BACKEND_BR2684) if (b != ATM_BACKEND_BR2684)
...@@ -669,9 +659,7 @@ static int br2684_ioctl(struct atm_vcc *atmvcc, unsigned int cmd, ...@@ -669,9 +659,7 @@ static int br2684_ioctl(struct atm_vcc *atmvcc, unsigned int cmd,
return -ENOIOCTLCMD; return -ENOIOCTLCMD;
if (!capable(CAP_NET_ADMIN)) if (!capable(CAP_NET_ADMIN))
return -EPERM; return -EPERM;
MOD_INC_USE_COUNT;
err = br2684_setfilt(atmvcc, arg); err = br2684_setfilt(atmvcc, arg);
MOD_DEC_USE_COUNT;
return err; return err;
#endif /* CONFIG_ATM_BR2684_IPFILTER */ #endif /* CONFIG_ATM_BR2684_IPFILTER */
} }
...@@ -682,20 +670,22 @@ static int br2684_ioctl(struct atm_vcc *atmvcc, unsigned int cmd, ...@@ -682,20 +670,22 @@ static int br2684_ioctl(struct atm_vcc *atmvcc, unsigned int cmd,
static int br2684_proc_engine(loff_t pos, char *buf) static int br2684_proc_engine(loff_t pos, char *buf)
{ {
struct list_head *lhd, *lhc; struct list_head *lhd, *lhc;
struct net_device *net_dev;
struct br2684_dev *brdev; struct br2684_dev *brdev;
struct br2684_vcc *brvcc; struct br2684_vcc *brvcc;
list_for_each(lhd, &br2684_devs) { list_for_each(lhd, &br2684_devs) {
brdev = list_entry_brdev(lhd); net_dev = list_entry_brdev(lhd);
brdev = BRPRIV(net_dev);
if (pos-- == 0) if (pos-- == 0)
return sprintf(buf, "dev %.16s: num=%d, mac=%02X:%02X:" return sprintf(buf, "dev %.16s: num=%d, mac=%02X:%02X:"
"%02X:%02X:%02X:%02X (%s)\n", brdev->net_dev.name, "%02X:%02X:%02X:%02X (%s)\n", net_dev->name,
brdev->number, brdev->number,
brdev->net_dev.dev_addr[0], net_dev->dev_addr[0],
brdev->net_dev.dev_addr[1], net_dev->dev_addr[1],
brdev->net_dev.dev_addr[2], net_dev->dev_addr[2],
brdev->net_dev.dev_addr[3], net_dev->dev_addr[3],
brdev->net_dev.dev_addr[4], net_dev->dev_addr[4],
brdev->net_dev.dev_addr[5], net_dev->dev_addr[5],
brdev->mac_was_set ? "set" : "auto"); brdev->mac_was_set ? "set" : "auto");
list_for_each(lhc, &brdev->brvccs) { list_for_each(lhc, &brdev->brvccs) {
brvcc = list_entry_brvcc(lhc); brvcc = list_entry_brvcc(lhc);
...@@ -766,15 +756,13 @@ static ssize_t br2684_proc_read(struct file *file, char *buf, size_t count, ...@@ -766,15 +756,13 @@ static ssize_t br2684_proc_read(struct file *file, char *buf, size_t count,
} }
static struct file_operations br2684_proc_operations = { static struct file_operations br2684_proc_operations = {
read: br2684_proc_read, .owner = THIS_MODULE,
.read = br2684_proc_read,
}; };
extern struct proc_dir_entry *atm_proc_root; /* from proc.c */ extern struct proc_dir_entry *atm_proc_root; /* from proc.c */
/* the following avoids some spurious warnings from the compiler */ static int __init br2684_init(void)
#define UNUSED __attribute__((unused))
static int __init UNUSED br2684_init(void)
{ {
struct proc_dir_entry *p; struct proc_dir_entry *p;
if ((p = create_proc_entry("br2684", 0, atm_proc_root)) == NULL) if ((p = create_proc_entry("br2684", 0, atm_proc_root)) == NULL)
...@@ -784,16 +772,26 @@ static int __init UNUSED br2684_init(void) ...@@ -784,16 +772,26 @@ static int __init UNUSED br2684_init(void)
return 0; return 0;
} }
static void __exit UNUSED br2684_exit(void) static void __exit br2684_exit(void)
{ {
struct net_device *net_dev;
struct br2684_dev *brdev; struct br2684_dev *brdev;
struct br2684_vcc *brvcc;
br2684_ioctl_set(NULL); br2684_ioctl_set(NULL);
remove_proc_entry("br2684", atm_proc_root); remove_proc_entry("br2684", atm_proc_root);
while (!list_empty(&br2684_devs)) { while (!list_empty(&br2684_devs)) {
brdev = list_entry_brdev(br2684_devs.next); net_dev = list_entry_brdev(br2684_devs.next);
unregister_netdev(&brdev->net_dev); brdev = BRPRIV(net_dev);
while (!list_empty(&brdev->brvccs)) {
brvcc = list_entry_brvcc(brdev->brvccs.next);
br2684_close_vcc(brvcc);
}
list_del(&brdev->br2684_devs); list_del(&brdev->br2684_devs);
kfree(brdev); unregister_netdev(net_dev);
kfree(net_dev);
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment