]> git.hungrycats.org Git - linux/commitdiff
[BRIDGE]: Add sysfs support.
authorStephen Hemminger <shemminger@osdl.org>
Sat, 22 May 2004 06:44:51 +0000 (23:44 -0700)
committerStephen Hemminger <shemminger@osdl.org>
Sat, 22 May 2004 06:44:51 +0000 (23:44 -0700)
include/linux/if_bridge.h
net/bridge/Makefile
net/bridge/br.c
net/bridge/br_if.c
net/bridge/br_private.h
net/bridge/br_stp_if.c
net/bridge/br_sysfs_br.c [new file with mode: 0644]
net/bridge/br_sysfs_if.c [new file with mode: 0644]

index 5dff099e76b577988d369430aea2ad193594d9cb..895775cfb65da05bc8e054c7f320ca18328cf7c0 100644 (file)
 
 #include <linux/types.h>
 
+#define SYSFS_BRIDGE_ATTR      "bridge"
+#define SYSFS_BRIDGE_FDB       "brforward"
+#define SYSFS_BRIDGE_PORT_SUBDIR "brif"
+#define SYSFS_BRIDGE_PORT_ATTR "brport"
+#define SYSFS_BRIDGE_PORT_LINK "bridge"
+
 #define BRCTL_VERSION 1
 
 #define BRCTL_GET_VERSION 0
index b2d49f58f5029ddfd1195f8a3a0c07b9a516849d..59556e40e143457abdf3ab0d4af6b609796b2264 100644 (file)
@@ -8,6 +8,8 @@ bridge-y        := br.o br_device.o br_fdb.o br_forward.o br_if.o br_input.o \
                        br_ioctl.o br_notify.o br_stp.o br_stp_bpdu.o \
                        br_stp_if.o br_stp_timer.o
 
+bridge-$(CONFIG_SYSFS) += br_sysfs_if.o br_sysfs_br.o
+
 bridge-$(CONFIG_BRIDGE_NETFILTER) += br_netfilter.o
 
 obj-$(CONFIG_BRIDGE_NF_EBTABLES) += netfilter/
index 675d0709a06911a7241a579976b97aefc3500a26..06fdee661bfaa0c7cd263bfcb72c5b0dfc9f4654 100644 (file)
@@ -33,6 +33,8 @@ static int __init br_init(void)
 {
        br_fdb_init();
 
+       br_sysfs_init();
+
 #ifdef CONFIG_BRIDGE_NETFILTER
        if (br_netfilter_init())
                return 1;
@@ -67,6 +69,7 @@ static void __exit br_deinit(void)
 #endif
 
        br_handle_frame_hook = NULL;
+       br_sysfs_fini();
        br_fdb_fini();
 }
 
index 62d045c7e829562c78f691b486837bf3199609f8..8a6de043ec7e01ed0316d2418f1bdd03db379b06 100644 (file)
@@ -81,8 +81,11 @@ static void destroy_nbp(void *arg)
        struct net_device *dev = p->dev;
 
        dev->br_port = NULL;
+       p->br = NULL;
+       p->dev = NULL;
        dev_put(dev);
-       kfree(p);
+
+       br_sysfs_freeif(p);
 }
 
 /* called with RTNL */
@@ -111,14 +114,16 @@ static void del_nbp(struct net_bridge_port *p)
 /* called with RTNL */
 static void del_br(struct net_bridge *br)
 {
-       struct list_head *p, *n;
+       struct net_bridge_port *p, *n;
 
-       list_for_each_safe(p, n, &br->port_list) {
-               del_nbp(list_entry(p, struct net_bridge_port, list));
+       list_for_each_entry_safe(p, n, &br->port_list, list) {
+               br_sysfs_removeif(p);
+               del_nbp(p);
        }
 
        del_timer_sync(&br->gc_timer);
 
+       br_sysfs_delbr(br->dev);
        unregister_netdevice(br->dev);
 }
 
@@ -210,6 +215,7 @@ static struct net_bridge_port *new_nbp(struct net_bridge *br,
        p->port_no = index;
        br_init_port(p);
        p->state = BR_STATE_DISABLED;
+       kobject_init(&p->kobj);
 
        return p;
 }
@@ -223,10 +229,37 @@ int br_add_bridge(const char *name)
        if (!dev) 
                return -ENOMEM;
 
-       ret = register_netdev(dev);
+       rtnl_lock();
+       if (strchr(dev->name, '%')) {
+               ret = dev_alloc_name(dev, dev->name);
+               if (ret < 0)
+                       goto err1;
+       }
+
+       ret = register_netdevice(dev);
        if (ret)
-               free_netdev(dev);
+               goto err2;
+
+       /* network device kobject is not setup until
+        * after rtnl_unlock does it's hotplug magic.
+        * so hold reference to avoid race.
+        */
+       dev_hold(dev);
+       rtnl_unlock();
+
+       ret = br_sysfs_addbr(dev);
+       dev_put(dev);
+
+       if (ret) 
+               unregister_netdev(dev);
+ out:
        return ret;
+
+ err2:
+       free_netdev(dev);
+ err1:
+       rtnl_unlock();
+       goto out;
 }
 
 int br_del_bridge(const char *name)
@@ -277,6 +310,8 @@ int br_add_if(struct net_bridge *br, struct net_device *dev)
        if ((err = br_fdb_insert(br, p, dev->dev_addr, 1)))
                destroy_nbp(p);
  
+       else if ((err = br_sysfs_addif(p)))
+               del_nbp(p);
        else {
                dev_set_promiscuity(dev, 1);
 
@@ -300,6 +335,7 @@ int br_del_if(struct net_bridge *br, struct net_device *dev)
        if (!p || p->br != br) 
                return -EINVAL;
 
+       br_sysfs_removeif(p);
        del_nbp(p);
 
        spin_lock_bh(&br->lock);
index e2cbae3ec0557a63804ab68d669d72da7e15b754..ab160edb80b0eddfd8c836d17c0c7e4f1950178c 100644 (file)
@@ -76,6 +76,7 @@ struct net_bridge_port
        struct timer_list               forward_delay_timer;
        struct timer_list               hold_timer;
        struct timer_list               message_age_timer;
+       struct kobject                  kobj;
        struct rcu_head                 rcu;
 };
 
@@ -110,6 +111,7 @@ struct net_bridge
        struct timer_list               tcn_timer;
        struct timer_list               topology_change_timer;
        struct timer_list               gc_timer;
+       struct kobject                  ifobj;
 };
 
 extern struct notifier_block br_device_notifier;
@@ -198,6 +200,7 @@ extern void br_stp_set_port_priority(struct net_bridge_port *p,
                                     u8 newprio);
 extern void br_stp_set_path_cost(struct net_bridge_port *p,
                                 u32 path_cost);
+extern ssize_t br_show_bridge_id(char *buf, const struct bridge_id *id);
 
 /* br_stp_bpdu.c */
 extern int br_stp_handle_bpdu(struct sk_buff *skb);
@@ -207,4 +210,28 @@ extern void br_stp_timer_init(struct net_bridge *br);
 extern void br_stp_port_timer_init(struct net_bridge_port *p);
 extern unsigned long br_timer_value(const struct timer_list *timer);
 
+#ifdef CONFIG_SYSFS
+/* br_sysfs_if.c */
+extern int br_sysfs_addif(struct net_bridge_port *p);
+extern void br_sysfs_removeif(struct net_bridge_port *p);
+extern void br_sysfs_freeif(struct net_bridge_port *p);
+
+/* br_sysfs_br.c */
+extern struct subsystem bridge_subsys;
+extern void br_sysfs_init(void);
+extern void br_sysfs_fini(void);
+extern int br_sysfs_addbr(struct net_device *dev);
+extern void br_sysfs_delbr(struct net_device *dev);
+
+#else
+
+#define br_sysfs_addif(p)      (0)
+#define br_sysfs_removeif(p)   do { } while(0)
+#define br_sysfs_freeif(p)     kfree(p)
+#define br_sysfs_init()                do { } while(0)
+#define br_sysfs_fini()                do { } while(0)
+#define br_sysfs_addbr(dev)    (0)
+#define br_sysfs_delbr(dev)    do { } while(0)
+#endif /* CONFIG_SYSFS */
+
 #endif
index 80452970b6d92536cdd63b37ab4eaa23321e0408..9e1d296b6e95b49127a4ae1b98a57ce8a19ec2b1 100644 (file)
@@ -212,3 +212,11 @@ void br_stp_set_path_cost(struct net_bridge_port *p, u32 path_cost)
        br_configuration_update(p->br);
        br_port_state_selection(p->br);
 }
+
+ssize_t br_show_bridge_id(char *buf, const struct bridge_id *id)
+{
+       return sprintf(buf, "%.2x%.2x.%.2x%.2x%.2x%.2x%.2x%.2x\n",
+              id->prio[0], id->prio[1],
+              id->addr[0], id->addr[1], id->addr[2],
+              id->addr[3], id->addr[4], id->addr[5]);
+}
diff --git a/net/bridge/br_sysfs_br.c b/net/bridge/br_sysfs_br.c
new file mode 100644 (file)
index 0000000..61f00fa
--- /dev/null
@@ -0,0 +1,383 @@
+/*
+ *     Sysfs attributes of bridge ports
+ *     Linux ethernet bridge
+ *
+ *     Authors:
+ *     Stephen Hemminger               <shemminger@osdl.org>
+ *
+ *     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/netdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/rtnetlink.h>
+#include <linux/spinlock.h>
+#include <linux/times.h>
+
+#include "br_private.h"
+
+#define to_class_dev(obj) container_of(obj,struct class_device,kobj)
+#define to_net_dev(class) container_of(class, struct net_device, class_dev)
+#define to_bridge(cd)  ((struct net_bridge *)(to_net_dev(cd)->priv))
+
+/*
+ * Common code for storing bridge parameters.
+ */
+static ssize_t store_bridge_parm(struct class_device *cd,
+                                const char *buf, size_t len,
+                                void (*set)(struct net_bridge *, unsigned long))
+{
+       struct net_bridge *br = to_bridge(cd);
+       char *endp;
+       unsigned long val;
+
+       if (!capable(CAP_NET_ADMIN))
+               return -EPERM;
+
+       val = simple_strtoul(buf, &endp, 0);
+       if (endp == buf)
+               return -EINVAL;
+
+       spin_lock_bh(&br->lock);
+       (*set)(br, val);
+       spin_unlock_bh(&br->lock);
+       return len;
+}
+
+
+static ssize_t show_forward_delay(struct class_device *cd, char *buf)
+{
+       struct net_bridge *br = to_bridge(cd);
+       return sprintf(buf, "%lu\n", jiffies_to_clock_t(br->forward_delay));
+}
+
+static void set_forward_delay(struct net_bridge *br, unsigned long val)
+{
+       unsigned long delay = clock_t_to_jiffies(val);
+       br->forward_delay = delay;
+       if (br_is_root_bridge(br))
+               br->bridge_forward_delay = delay;
+}
+
+static ssize_t store_forward_delay(struct class_device *cd, const char *buf,
+                                  size_t len)
+{
+       return store_bridge_parm(cd, buf, len, set_forward_delay);
+}
+static CLASS_DEVICE_ATTR(forward_delay, S_IRUGO | S_IWUSR,
+                        show_forward_delay, store_forward_delay);
+
+static ssize_t show_hello_time(struct class_device *cd, char *buf)
+{
+       return sprintf(buf, "%lu\n",
+                      jiffies_to_clock_t(to_bridge(cd)->hello_time));
+}
+
+static void set_hello_time(struct net_bridge *br, unsigned long val)
+{
+       unsigned long t = clock_t_to_jiffies(val);
+       br->hello_time = t;
+       if (br_is_root_bridge(br))
+               br->bridge_hello_time = t;
+}
+
+static ssize_t store_hello_time(struct class_device *cd, const char *buf,
+                               size_t len)
+{
+       return store_bridge_parm(cd, buf, len, set_hello_time);
+}
+
+static CLASS_DEVICE_ATTR(hello_time, S_IRUGO | S_IWUSR, show_hello_time,
+                        store_hello_time);
+
+static ssize_t show_max_age(struct class_device *cd, char *buf)
+{
+       return sprintf(buf, "%lu\n",
+                      jiffies_to_clock_t(to_bridge(cd)->max_age));
+}
+
+static void set_max_age(struct net_bridge *br, unsigned long val)
+{
+       unsigned long t = clock_t_to_jiffies(val);
+       br->max_age = t;
+       if (br_is_root_bridge(br))
+               br->bridge_max_age = t;
+}
+
+static ssize_t store_max_age(struct class_device *cd, const char *buf,
+                               size_t len)
+{
+       return store_bridge_parm(cd, buf, len, set_max_age);
+}
+
+static CLASS_DEVICE_ATTR(max_age, S_IRUGO | S_IWUSR, show_max_age,
+                        store_max_age);
+
+static ssize_t show_ageing_time(struct class_device *cd, char *buf)
+{
+       struct net_bridge *br = to_bridge(cd);
+       return sprintf(buf, "%lu\n", jiffies_to_clock_t(br->ageing_time));
+}
+
+static void set_ageing_time(struct net_bridge *br, unsigned long val)
+{
+       br->ageing_time = clock_t_to_jiffies(val);
+}
+
+static ssize_t store_ageing_time(struct class_device *cd, const char *buf,
+                                size_t len)
+{
+       return store_bridge_parm(cd, buf, len, set_ageing_time);
+}
+
+static CLASS_DEVICE_ATTR(ageing_time, S_IRUGO | S_IWUSR, show_ageing_time,
+                        store_ageing_time);
+static ssize_t show_stp_state(struct class_device *cd, char *buf)
+{
+       struct net_bridge *br = to_bridge(cd);
+       return sprintf(buf, "%d\n", br->stp_enabled);
+}
+
+static void set_stp_state(struct net_bridge *br, unsigned long val)
+{
+       br->stp_enabled = val;
+}
+
+static ssize_t store_stp_state(struct class_device *cd,
+                              const char *buf, size_t len)
+{
+       return store_bridge_parm(cd, buf, len, set_stp_state);
+}
+
+static CLASS_DEVICE_ATTR(stp_state, S_IRUGO | S_IWUSR, show_stp_state,
+                        store_stp_state);
+
+static ssize_t show_priority(struct class_device *cd, char *buf)
+{
+       struct net_bridge *br = to_bridge(cd);
+       return sprintf(buf, "%d\n",
+                      (br->bridge_id.prio[0] << 8) | br->bridge_id.prio[1]);
+}
+
+static void set_priority(struct net_bridge *br, unsigned long val)
+{
+       br_stp_set_bridge_priority(br, (u16) val);
+}
+
+static ssize_t store_priority(struct class_device *cd,
+                              const char *buf, size_t len)
+{
+       return store_bridge_parm(cd, buf, len, set_priority);
+}
+static CLASS_DEVICE_ATTR(priority, S_IRUGO | S_IWUSR, show_priority,
+                        store_priority);
+
+static ssize_t show_root_id(struct class_device *cd, char *buf)
+{
+       return br_show_bridge_id(buf, &to_bridge(cd)->designated_root);
+}
+static CLASS_DEVICE_ATTR(root_id, S_IRUGO, show_root_id, NULL);
+
+static ssize_t show_bridge_id(struct class_device *cd, char *buf)
+{
+       return br_show_bridge_id(buf, &to_bridge(cd)->bridge_id);
+}
+static CLASS_DEVICE_ATTR(bridge_id, S_IRUGO, show_bridge_id, NULL);
+
+static ssize_t show_root_port(struct class_device *cd, char *buf)
+{
+       return sprintf(buf, "%d\n", to_bridge(cd)->root_port);
+}
+static CLASS_DEVICE_ATTR(root_port, S_IRUGO, show_root_port, NULL);
+
+static ssize_t show_root_path_cost(struct class_device *cd, char *buf)
+{
+       return sprintf(buf, "%d\n", to_bridge(cd)->root_path_cost);
+}
+static CLASS_DEVICE_ATTR(root_path_cost, S_IRUGO, show_root_path_cost, NULL);
+
+static ssize_t show_topology_change(struct class_device *cd, char *buf)
+{
+       return sprintf(buf, "%d\n", to_bridge(cd)->topology_change);
+}
+static CLASS_DEVICE_ATTR(topology_change, S_IRUGO, show_topology_change, NULL);
+
+static ssize_t show_topology_change_detected(struct class_device *cd, char *buf)
+{
+       struct net_bridge *br = to_bridge(cd);
+       return sprintf(buf, "%d\n", br->topology_change_detected);
+}
+static CLASS_DEVICE_ATTR(topology_change_detected, S_IRUGO, show_topology_change_detected, NULL);
+
+static ssize_t show_hello_timer(struct class_device *cd, char *buf)
+{
+       struct net_bridge *br = to_bridge(cd);
+       return sprintf(buf, "%ld\n", br_timer_value(&br->hello_timer));
+}
+static CLASS_DEVICE_ATTR(hello_timer, S_IRUGO, show_hello_timer, NULL);
+
+static ssize_t show_tcn_timer(struct class_device *cd, char *buf)
+{
+       struct net_bridge *br = to_bridge(cd);
+       return sprintf(buf, "%ld\n", br_timer_value(&br->tcn_timer));
+}
+static CLASS_DEVICE_ATTR(tcn_timer, S_IRUGO, show_tcn_timer, NULL);
+
+static ssize_t show_topology_change_timer(struct class_device *cd, char *buf)
+{
+       struct net_bridge *br = to_bridge(cd);
+       return sprintf(buf, "%ld\n", br_timer_value(&br->topology_change_timer));
+}
+static CLASS_DEVICE_ATTR(topology_change_timer, S_IRUGO, show_topology_change_timer, NULL);
+
+static ssize_t show_gc_timer(struct class_device *cd, char *buf)
+{
+       struct net_bridge *br = to_bridge(cd);
+       return sprintf(buf, "%ld\n", br_timer_value(&br->gc_timer));
+}
+static CLASS_DEVICE_ATTR(gc_timer, S_IRUGO, show_gc_timer, NULL);
+
+static struct attribute *bridge_attrs[] = {
+       &class_device_attr_forward_delay.attr,
+       &class_device_attr_hello_time.attr,
+       &class_device_attr_max_age.attr,
+       &class_device_attr_ageing_time.attr,
+       &class_device_attr_stp_state.attr,
+       &class_device_attr_priority.attr,
+       &class_device_attr_bridge_id.attr,
+       &class_device_attr_root_id.attr,
+       &class_device_attr_root_path_cost.attr,
+       &class_device_attr_root_port.attr,
+       &class_device_attr_topology_change.attr,
+       &class_device_attr_topology_change_detected.attr,
+       &class_device_attr_hello_timer.attr,
+       &class_device_attr_tcn_timer.attr,
+       &class_device_attr_topology_change_timer.attr,
+       &class_device_attr_gc_timer.attr,
+       NULL
+};
+
+static struct attribute_group bridge_group = {
+       .name = SYSFS_BRIDGE_ATTR,
+       .attrs = bridge_attrs,
+};
+
+/*
+ * Export the forwarding information table as a binary file
+ * The records are struct __fdb_entry.
+ *
+ * Returns the number of bytes read.
+ */
+static ssize_t brforward_read(struct kobject *kobj, char *buf,
+                          loff_t off, size_t count)
+{
+       struct class_device *cdev = to_class_dev(kobj);
+       struct net_bridge *br = to_bridge(cdev);
+       int n;
+
+       /* must read whole records */
+       if (off % sizeof(struct __fdb_entry) != 0)
+               return -EINVAL;
+
+       n =  br_fdb_fillbuf(br, buf, 
+                           count / sizeof(struct __fdb_entry),
+                           off / sizeof(struct __fdb_entry));
+
+       if (n > 0)
+               n *= sizeof(struct __fdb_entry);
+       
+       return n;
+}
+
+static struct bin_attribute bridge_forward = {
+       .attr = { .name = SYSFS_BRIDGE_FDB,
+                 .mode = S_IRUGO, 
+                 .owner = THIS_MODULE, },
+       .read = brforward_read,
+};
+
+
+/*
+ * This is a dummy kset so bridge objects don't cause
+ * hotplug events 
+ */
+struct subsystem bridge_subsys = { 
+       .kset = { .hotplug_ops = NULL },
+};
+
+void br_sysfs_init(void)
+{
+       subsystem_register(&bridge_subsys);
+}
+
+void br_sysfs_fini(void)
+{
+       subsystem_unregister(&bridge_subsys);
+}
+
+/*
+ * Add entries in sysfs onto the existing network class device
+ * for the bridge.
+ *   Adds a attribute group "bridge" containing tuning parameters.
+ *   Binary attribute containing the forward table
+ *   Sub directory to hold links to interfaces.
+ *
+ * Note: the ifobj exists only to be a subdirectory
+ *   to hold links.  The ifobj exists in same data structure
+ *   as it's parent the bridge so reference counting works.
+ */
+int br_sysfs_addbr(struct net_device *dev)
+{
+       struct kobject *brobj = &dev->class_dev.kobj;
+       struct net_bridge *br = netdev_priv(dev);
+       int err;
+
+       err = sysfs_create_group(brobj, &bridge_group);
+       if (err) {
+               pr_info("%s: can't create group %s/%s\n",
+                       __FUNCTION__, dev->name, bridge_group.name);
+               goto out1;
+       }
+
+       err = sysfs_create_bin_file(brobj, &bridge_forward);
+       if (err) {
+               pr_info("%s: can't create attribue file %s/%s\n",
+                       __FUNCTION__, dev->name, bridge_forward.attr.name);
+               goto out2;
+       }
+
+       
+       kobject_set_name(&br->ifobj, SYSFS_BRIDGE_PORT_SUBDIR);
+       br->ifobj.ktype = NULL;
+       br->ifobj.kset = &bridge_subsys.kset;
+       br->ifobj.parent = brobj;
+
+       err = kobject_register(&br->ifobj);
+       if (err) {
+               pr_info("%s: can't add kobject (directory) %s/%s\n",
+                       __FUNCTION__, dev->name, br->ifobj.name);
+               goto out3;
+       }
+       return 0;
+ out3:
+       sysfs_remove_bin_file(&dev->class_dev.kobj, &bridge_forward);
+ out2:
+       sysfs_remove_group(&dev->class_dev.kobj, &bridge_group);
+ out1:
+       return err;
+
+}
+
+void br_sysfs_delbr(struct net_device *dev)
+{
+       struct kobject *kobj = &dev->class_dev.kobj;
+       struct net_bridge *br = netdev_priv(dev);
+
+       kobject_unregister(&br->ifobj);
+       sysfs_remove_bin_file(kobj, &bridge_forward);
+       sysfs_remove_group(kobj, &bridge_group);
+}
diff --git a/net/bridge/br_sysfs_if.c b/net/bridge/br_sysfs_if.c
new file mode 100644 (file)
index 0000000..1788221
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ *     Sysfs attributes of bridge ports
+ *     Linux ethernet bridge
+ *
+ *     Authors:
+ *     Stephen Hemminger               <shemminger@osdl.org>
+ *
+ *     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/netdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/rtnetlink.h>
+#include <linux/spinlock.h>
+
+#include "br_private.h"
+
+struct brport_attribute {
+       struct attribute        attr;
+       ssize_t (*show)(struct net_bridge_port *, char *);
+       ssize_t (*store)(struct net_bridge_port *, unsigned long);
+};
+
+#define BRPORT_ATTR(_name,_mode,_show,_store)                  \
+struct brport_attribute brport_attr_##_name = {                \
+       .attr = {.name = __stringify(_name),                    \
+                .mode = _mode,                                 \
+                .owner = THIS_MODULE, },                       \
+       .show   = _show,                                        \
+       .store  = _store,                                       \
+};
+
+static ssize_t show_path_cost(struct net_bridge_port *p, char *buf)
+{
+       return sprintf(buf, "%d\n", p->path_cost);
+}
+static ssize_t store_path_cost(struct net_bridge_port *p, unsigned long v)
+{
+       br_stp_set_path_cost(p, v);
+       return 0;
+}
+static BRPORT_ATTR(path_cost, S_IRUGO | S_IWUSR,
+                  show_path_cost, store_path_cost);
+
+static ssize_t show_priority(struct net_bridge_port *p, char *buf)
+{
+       return sprintf(buf, "%d\n", p->priority);
+}
+static ssize_t store_priority(struct net_bridge_port *p, unsigned long v)
+{
+       if (v >= (1<<(16-BR_PORT_BITS)))
+               return -ERANGE;
+       br_stp_set_port_priority(p, v);
+       return 0;
+}
+static BRPORT_ATTR(priority, S_IRUGO | S_IWUSR,
+                        show_priority, store_priority);
+
+static ssize_t show_designated_root(struct net_bridge_port *p, char *buf)
+{
+       return br_show_bridge_id(buf, &p->designated_root);
+}
+static BRPORT_ATTR(designated_root, S_IRUGO, show_designated_root, NULL);
+
+static ssize_t show_designated_bridge(struct net_bridge_port *p, char *buf)
+{
+       return br_show_bridge_id(buf, &p->designated_bridge);
+}
+static BRPORT_ATTR(designated_bridge, S_IRUGO, show_designated_bridge, NULL);
+
+static ssize_t show_designated_port(struct net_bridge_port *p, char *buf)
+{
+       return sprintf(buf, "%d\n", p->designated_port);
+}
+static BRPORT_ATTR(designated_port, S_IRUGO, show_designated_port, NULL);
+
+static ssize_t show_designated_cost(struct net_bridge_port *p, char *buf)
+{
+       return sprintf(buf, "%d\n", p->designated_cost);
+}
+static BRPORT_ATTR(designated_cost, S_IRUGO, show_designated_cost, NULL);
+
+static ssize_t show_port_id(struct net_bridge_port *p, char *buf)
+{
+       return sprintf(buf, "0x%x\n", p->port_id);
+}
+static BRPORT_ATTR(port_id, S_IRUGO, show_port_id, NULL);
+
+static ssize_t show_port_no(struct net_bridge_port *p, char *buf)
+{
+       return sprintf(buf, "0x%x\n", p->port_no);
+}
+
+static BRPORT_ATTR(port_no, S_IRUGO, show_port_no, NULL);
+
+static ssize_t show_change_ack(struct net_bridge_port *p, char *buf)
+{
+       return sprintf(buf, "%d\n", p->topology_change_ack);
+}
+static BRPORT_ATTR(change_ack, S_IRUGO, show_change_ack, NULL);
+
+static ssize_t show_config_pending(struct net_bridge_port *p, char *buf)
+{
+       return sprintf(buf, "%d\n", p->config_pending);
+}
+static BRPORT_ATTR(config_pending, S_IRUGO, show_config_pending, NULL);
+
+static ssize_t show_port_state(struct net_bridge_port *p, char *buf)
+{
+       return sprintf(buf, "%d\n", p->state);
+}
+static BRPORT_ATTR(state, S_IRUGO, show_port_state, NULL);
+
+static ssize_t show_message_age_timer(struct net_bridge_port *p,
+                                           char *buf)
+{
+       return sprintf(buf, "%ld\n", br_timer_value(&p->message_age_timer));
+}
+static BRPORT_ATTR(message_age_timer, S_IRUGO, show_message_age_timer, NULL);
+
+static ssize_t show_forward_delay_timer(struct net_bridge_port *p,
+                                           char *buf)
+{
+       return sprintf(buf, "%ld\n", br_timer_value(&p->forward_delay_timer));
+}
+static BRPORT_ATTR(forward_delay_timer, S_IRUGO, show_forward_delay_timer, NULL);
+
+static ssize_t show_hold_timer(struct net_bridge_port *p,
+                                           char *buf)
+{
+       return sprintf(buf, "%ld\n", br_timer_value(&p->hold_timer));
+}
+static BRPORT_ATTR(hold_timer, S_IRUGO, show_hold_timer, NULL);
+
+static struct brport_attribute *brport_attrs[] = {
+       &brport_attr_path_cost,
+       &brport_attr_priority,
+       &brport_attr_port_id,
+       &brport_attr_port_no,
+       &brport_attr_designated_root,
+       &brport_attr_designated_bridge,
+       &brport_attr_designated_port,
+       &brport_attr_designated_cost,
+       &brport_attr_state,
+       &brport_attr_change_ack,
+       &brport_attr_config_pending,
+       &brport_attr_message_age_timer,
+       &brport_attr_forward_delay_timer,
+       &brport_attr_hold_timer,
+       NULL
+};
+
+#define to_brport_attr(_at) container_of(_at, struct brport_attribute, attr)
+#define to_brport(obj) container_of(obj, struct net_bridge_port, kobj)
+
+static ssize_t brport_show(struct kobject * kobj,
+                          struct attribute * attr, char * buf)
+{
+       struct brport_attribute * brport_attr = to_brport_attr(attr);
+       struct net_bridge_port * p = to_brport(kobj);
+
+       return brport_attr->show(p, buf);
+}
+
+static ssize_t brport_store(struct kobject * kobj,
+                           struct attribute * attr,
+                           const char * buf, size_t count)
+{
+       struct brport_attribute * brport_attr = to_brport_attr(attr);
+       struct net_bridge_port * p = to_brport(kobj);
+       ssize_t ret = -EINVAL;
+       char *endp;
+       unsigned long val;
+
+       if (!capable(CAP_NET_ADMIN))
+               return -EPERM;
+
+       val = simple_strtoul(buf, &endp, 0);
+       if (endp != buf) {
+               rtnl_lock();
+               if (p->dev && p->br && brport_attr->store) {
+                       spin_lock_bh(&p->br->lock);
+                       ret = brport_attr->store(p, val);
+                       spin_unlock_bh(&p->br->lock);
+                       if (ret == 0)
+                               ret = count;
+               }
+               rtnl_unlock();
+       }
+       return ret;
+}
+
+/* called from kobject_put when port ref count goes to zero. */
+static void brport_release(struct kobject *kobj)
+{
+       kfree(container_of(kobj, struct net_bridge_port, kobj));
+}
+
+static struct sysfs_ops brport_sysfs_ops = {
+       .show = brport_show,
+       .store = brport_store,
+};
+
+static struct kobj_type brport_ktype = {
+       .sysfs_ops = &brport_sysfs_ops,
+       .release = brport_release,
+};
+
+
+/*
+ * Add sysfs entries to ethernet device added to a bridge.
+ * Creates a brport subdirectory with bridge attributes.
+ * Puts symlink in bridge's brport subdirectory
+ */
+int br_sysfs_addif(struct net_bridge_port *p)
+{
+       struct net_bridge *br = p->br;
+       struct brport_attribute **a;
+       int err;
+
+       ASSERT_RTNL();
+
+       kobject_set_name(&p->kobj, SYSFS_BRIDGE_PORT_ATTR);
+       p->kobj.ktype = &brport_ktype;
+       p->kobj.parent = &(p->dev->class_dev.kobj);
+       p->kobj.kset = &bridge_subsys.kset;
+
+       err = kobject_add(&p->kobj);
+       if(err)
+               goto out1;
+
+       err = sysfs_create_link(&p->kobj, &br->dev->class_dev.kobj, 
+                               SYSFS_BRIDGE_PORT_LINK);
+       if (err)
+               goto out2;
+
+       for (a = brport_attrs; *a; ++a) {
+               err = sysfs_create_file(&p->kobj, &((*a)->attr));
+               if (err)
+                       goto out2;
+       }
+
+       err = sysfs_create_link(&br->ifobj, &p->kobj, p->dev->name);
+       if (err)
+               goto out2;
+
+       return 0;
+ out2:
+       kobject_del(&p->kobj);
+ out1:
+       return err;
+}
+
+void br_sysfs_removeif(struct net_bridge_port *p)
+{
+       pr_debug("br_sysfs_removeif\n");
+       sysfs_remove_link(&p->br->ifobj, p->dev->name);
+       kobject_del(&p->kobj);
+}
+
+void br_sysfs_freeif(struct net_bridge_port *p)
+{
+       pr_debug("br_sysfs_freeif\n");
+       kobject_put(&p->kobj);
+}