RDMA/core: Split gid_attrs related sysfs from add_port()
[linux-2.6-microblaze.git] / drivers / infiniband / core / sysfs.c
index 05b702d..d2a089a 100644 (file)
@@ -60,8 +60,7 @@ struct ib_port {
        struct attribute_group gid_group;
        struct attribute_group *pkey_group;
        const struct attribute_group *pma_table;
-       struct attribute_group *hw_stats_ag;
-       struct rdma_hw_stats   *hw_stats;
+       struct hw_stats_port_data *hw_stats_data;
        u32                     port_num;
 };
 
@@ -85,16 +84,35 @@ struct port_table_attribute {
        __be16                  attr_id;
 };
 
-struct hw_stats_attribute {
-       struct attribute        attr;
-       ssize_t                 (*show)(struct kobject *kobj,
-                                       struct attribute *attr, char *buf);
-       ssize_t                 (*store)(struct kobject *kobj,
-                                        struct attribute *attr,
-                                        const char *buf,
-                                        size_t count);
-       int                     index;
-       u32                     port_num;
+struct hw_stats_device_attribute {
+       struct device_attribute attr;
+       ssize_t (*show)(struct ib_device *ibdev, struct rdma_hw_stats *stats,
+                       unsigned int index, unsigned int port_num, char *buf);
+       ssize_t (*store)(struct ib_device *ibdev, struct rdma_hw_stats *stats,
+                        unsigned int index, unsigned int port_num,
+                        const char *buf, size_t count);
+};
+
+struct hw_stats_port_attribute {
+       struct port_attribute attr;
+       ssize_t (*show)(struct ib_device *ibdev, struct rdma_hw_stats *stats,
+                       unsigned int index, unsigned int port_num, char *buf);
+       ssize_t (*store)(struct ib_device *ibdev, struct rdma_hw_stats *stats,
+                        unsigned int index, unsigned int port_num,
+                        const char *buf, size_t count);
+};
+
+struct hw_stats_device_data {
+       struct attribute_group group;
+       const struct attribute_group *groups[2];
+       struct rdma_hw_stats *stats;
+       struct hw_stats_device_attribute attrs[];
+};
+
+struct hw_stats_port_data {
+       struct attribute_group group;
+       struct rdma_hw_stats *stats;
+       struct hw_stats_port_attribute attrs[];
 };
 
 static ssize_t port_attr_show(struct kobject *kobj,
@@ -128,6 +146,53 @@ static const struct sysfs_ops port_sysfs_ops = {
        .store  = port_attr_store
 };
 
+static ssize_t hw_stat_device_show(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct hw_stats_device_attribute *stat_attr =
+               container_of(attr, struct hw_stats_device_attribute, attr);
+       struct ib_device *ibdev = container_of(dev, struct ib_device, dev);
+
+       return stat_attr->show(ibdev, ibdev->hw_stats_data->stats,
+                              stat_attr - ibdev->hw_stats_data->attrs, 0, buf);
+}
+
+static ssize_t hw_stat_device_store(struct device *dev,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t count)
+{
+       struct hw_stats_device_attribute *stat_attr =
+               container_of(attr, struct hw_stats_device_attribute, attr);
+       struct ib_device *ibdev = container_of(dev, struct ib_device, dev);
+
+       return stat_attr->store(ibdev, ibdev->hw_stats_data->stats,
+                               stat_attr - ibdev->hw_stats_data->attrs, 0, buf,
+                               count);
+}
+
+static ssize_t hw_stat_port_show(struct ib_port *port,
+                                struct port_attribute *attr, char *buf)
+{
+       struct hw_stats_port_attribute *stat_attr =
+               container_of(attr, struct hw_stats_port_attribute, attr);
+
+       return stat_attr->show(port->ibdev, port->hw_stats_data->stats,
+                              stat_attr - port->hw_stats_data->attrs,
+                              port->port_num, buf);
+}
+
+static ssize_t hw_stat_port_store(struct ib_port *port,
+                                 struct port_attribute *attr, const char *buf,
+                                 size_t count)
+{
+       struct hw_stats_port_attribute *stat_attr =
+               container_of(attr, struct hw_stats_port_attribute, attr);
+
+       return stat_attr->store(port->ibdev, port->hw_stats_data->stats,
+                               stat_attr - port->hw_stats_data->attrs,
+                               port->port_num, buf, count);
+}
+
 static ssize_t gid_attr_show(struct kobject *kobj,
                             struct attribute *attr, char *buf)
 {
@@ -835,56 +900,30 @@ static int print_hw_stat(struct ib_device *dev, int port_num,
        return sysfs_emit(buf, "%llu\n", stats->value[index] + v);
 }
 
-static ssize_t show_hw_stats(struct kobject *kobj, struct attribute *attr,
-                            char *buf)
+static ssize_t show_hw_stats(struct ib_device *ibdev,
+                            struct rdma_hw_stats *stats, unsigned int index,
+                            unsigned int port_num, char *buf)
 {
-       struct ib_device *dev;
-       struct ib_port *port;
-       struct hw_stats_attribute *hsa;
-       struct rdma_hw_stats *stats;
        int ret;
 
-       hsa = container_of(attr, struct hw_stats_attribute, attr);
-       if (!hsa->port_num) {
-               dev = container_of((struct device *)kobj,
-                                  struct ib_device, dev);
-               stats = dev->hw_stats;
-       } else {
-               port = container_of(kobj, struct ib_port, kobj);
-               dev = port->ibdev;
-               stats = port->hw_stats;
-       }
        mutex_lock(&stats->lock);
-       ret = update_hw_stats(dev, stats, hsa->port_num, hsa->index);
+       ret = update_hw_stats(ibdev, stats, port_num, index);
        if (ret)
                goto unlock;
-       ret = print_hw_stat(dev, hsa->port_num, stats, hsa->index, buf);
+       ret = print_hw_stat(ibdev, port_num, stats, index, buf);
 unlock:
        mutex_unlock(&stats->lock);
 
        return ret;
 }
 
-static ssize_t show_stats_lifespan(struct kobject *kobj,
-                                  struct attribute *attr,
+static ssize_t show_stats_lifespan(struct ib_device *ibdev,
+                                  struct rdma_hw_stats *stats,
+                                  unsigned int index, unsigned int port_num,
                                   char *buf)
 {
-       struct hw_stats_attribute *hsa;
-       struct rdma_hw_stats *stats;
        int msecs;
 
-       hsa = container_of(attr, struct hw_stats_attribute, attr);
-       if (!hsa->port_num) {
-               struct ib_device *dev = container_of((struct device *)kobj,
-                                                    struct ib_device, dev);
-
-               stats = dev->hw_stats;
-       } else {
-               struct ib_port *p = container_of(kobj, struct ib_port, kobj);
-
-               stats = p->hw_stats;
-       }
-
        mutex_lock(&stats->lock);
        msecs = jiffies_to_msecs(stats->lifespan);
        mutex_unlock(&stats->lock);
@@ -892,12 +931,11 @@ static ssize_t show_stats_lifespan(struct kobject *kobj,
        return sysfs_emit(buf, "%d\n", msecs);
 }
 
-static ssize_t set_stats_lifespan(struct kobject *kobj,
-                                 struct attribute *attr,
-                                 const char *buf, size_t count)
+static ssize_t set_stats_lifespan(struct ib_device *ibdev,
+                                  struct rdma_hw_stats *stats,
+                                  unsigned int index, unsigned int port_num,
+                                  const char *buf, size_t count)
 {
-       struct hw_stats_attribute *hsa;
-       struct rdma_hw_stats *stats;
        int msecs;
        int jiffies;
        int ret;
@@ -908,17 +946,6 @@ static ssize_t set_stats_lifespan(struct kobject *kobj,
        if (msecs < 0 || msecs > 10000)
                return -EINVAL;
        jiffies = msecs_to_jiffies(msecs);
-       hsa = container_of(attr, struct hw_stats_attribute, attr);
-       if (!hsa->port_num) {
-               struct ib_device *dev = container_of((struct device *)kobj,
-                                                    struct ib_device, dev);
-
-               stats = dev->hw_stats;
-       } else {
-               struct ib_port *p = container_of(kobj, struct ib_port, kobj);
-
-               stats = p->hw_stats;
-       }
 
        mutex_lock(&stats->lock);
        stats->lifespan = jiffies;
@@ -927,65 +954,125 @@ static ssize_t set_stats_lifespan(struct kobject *kobj,
        return count;
 }
 
-static void free_hsag(struct kobject *kobj, struct attribute_group *attr_group)
+static struct hw_stats_device_data *
+alloc_hw_stats_device(struct ib_device *ibdev)
 {
-       struct attribute **attr;
+       struct hw_stats_device_data *data;
+       struct rdma_hw_stats *stats;
 
-       sysfs_remove_group(kobj, attr_group);
+       if (!ibdev->ops.alloc_hw_device_stats)
+               return ERR_PTR(-EOPNOTSUPP);
+       stats = ibdev->ops.alloc_hw_device_stats(ibdev);
+       if (!stats)
+               return ERR_PTR(-ENOMEM);
+       if (!stats->names || stats->num_counters <= 0)
+               goto err_free_stats;
 
-       for (attr = attr_group->attrs; *attr; attr++)
-               kfree(*attr);
-       kfree(attr_group);
+       /*
+        * Two extra attribue elements here, one for the lifespan entry and
+        * one to NULL terminate the list for the sysfs core code
+        */
+       data = kzalloc(struct_size(data, attrs, stats->num_counters + 1),
+                      GFP_KERNEL);
+       if (!data)
+               goto err_free_stats;
+       data->group.attrs = kcalloc(stats->num_counters + 2,
+                                   sizeof(*data->group.attrs), GFP_KERNEL);
+       if (!data->group.attrs)
+               goto err_free_data;
+
+       mutex_init(&stats->lock);
+       data->group.name = "hw_counters";
+       data->stats = stats;
+       data->groups[0] = &data->group;
+       return data;
+
+err_free_data:
+       kfree(data);
+err_free_stats:
+       kfree(stats);
+       return ERR_PTR(-ENOMEM);
 }
 
-static struct attribute *alloc_hsa(int index, u32 port_num, const char *name)
+static void free_hw_stats_device(struct hw_stats_device_data *data)
 {
-       struct hw_stats_attribute *hsa;
+       kfree(data->group.attrs);
+       kfree(data->stats);
+       kfree(data);
+}
 
-       hsa = kmalloc(sizeof(*hsa), GFP_KERNEL);
-       if (!hsa)
-               return NULL;
+static int setup_hw_device_stats(struct ib_device *ibdev)
+{
+       struct hw_stats_device_attribute *attr;
+       struct hw_stats_device_data *data;
+       int i, ret;
 
-       hsa->attr.name = (char *)name;
-       hsa->attr.mode = S_IRUGO;
-       hsa->show = show_hw_stats;
-       hsa->store = NULL;
-       hsa->index = index;
-       hsa->port_num = port_num;
+       data = alloc_hw_stats_device(ibdev);
+       if (IS_ERR(data))
+               return PTR_ERR(data);
 
-       return &hsa->attr;
-}
+       ret = ibdev->ops.get_hw_stats(ibdev, data->stats, 0,
+                                     data->stats->num_counters);
+       if (ret != data->stats->num_counters) {
+               if (WARN_ON(ret >= 0))
+                       ret = -EINVAL;
+               goto err_free;
+       }
 
-static struct attribute *alloc_hsa_lifespan(char *name, u32 port_num)
-{
-       struct hw_stats_attribute *hsa;
+       data->stats->timestamp = jiffies;
 
-       hsa = kmalloc(sizeof(*hsa), GFP_KERNEL);
-       if (!hsa)
-               return NULL;
+       for (i = 0; i < data->stats->num_counters; i++) {
+               attr = &data->attrs[i];
+               sysfs_attr_init(&attr->attr.attr);
+               attr->attr.attr.name = data->stats->names[i];
+               attr->attr.attr.mode = 0444;
+               attr->attr.show = hw_stat_device_show;
+               attr->show = show_hw_stats;
+               data->group.attrs[i] = &attr->attr.attr;
+       }
 
-       hsa->attr.name = name;
-       hsa->attr.mode = S_IWUSR | S_IRUGO;
-       hsa->show = show_stats_lifespan;
-       hsa->store = set_stats_lifespan;
-       hsa->index = 0;
-       hsa->port_num = port_num;
+       attr = &data->attrs[i];
+       sysfs_attr_init(&attr->attr.attr);
+       attr->attr.attr.name = "lifespan";
+       attr->attr.attr.mode = 0644;
+       attr->attr.show = hw_stat_device_show;
+       attr->show = show_stats_lifespan;
+       attr->attr.store = hw_stat_device_store;
+       attr->store = set_stats_lifespan;
+       data->group.attrs[i] = &attr->attr.attr;
+
+       ibdev->hw_stats_data = data;
+       ret = device_add_groups(&ibdev->dev, data->groups);
+       if (ret)
+               goto err_free;
+       return 0;
 
-       return &hsa->attr;
+err_free:
+       free_hw_stats_device(data);
+       ibdev->hw_stats_data = NULL;
+       return ret;
 }
 
-static void setup_hw_stats(struct ib_device *device, struct ib_port *port,
-                          u32 port_num)
+static void destroy_hw_device_stats(struct ib_device *ibdev)
 {
-       struct attribute_group *hsag;
-       struct rdma_hw_stats *stats;
-       int i, ret;
+       if (!ibdev->hw_stats_data)
+               return;
+       device_remove_groups(&ibdev->dev, ibdev->hw_stats_data->groups);
+       free_hw_stats_device(ibdev->hw_stats_data);
+       ibdev->hw_stats_data = NULL;
+}
 
-       stats = device->ops.alloc_hw_stats(device, port_num);
+static struct hw_stats_port_data *alloc_hw_stats_port(struct ib_port *port)
+{
+       struct ib_device *ibdev = port->ibdev;
+       struct hw_stats_port_data *data;
+       struct rdma_hw_stats *stats;
 
+       if (!ibdev->ops.alloc_hw_port_stats)
+               return ERR_PTR(-EOPNOTSUPP);
+       stats = ibdev->ops.alloc_hw_port_stats(port->ibdev, port->port_num);
        if (!stats)
-               return;
-
+               return ERR_PTR(-ENOMEM);
        if (!stats->names || stats->num_counters <= 0)
                goto err_free_stats;
 
@@ -993,62 +1080,181 @@ static void setup_hw_stats(struct ib_device *device, struct ib_port *port,
         * Two extra attribue elements here, one for the lifespan entry and
         * one to NULL terminate the list for the sysfs core code
         */
-       hsag = kzalloc(sizeof(*hsag) +
-                      sizeof(void *) * (stats->num_counters + 2),
+       data = kzalloc(struct_size(data, attrs, stats->num_counters + 1),
                       GFP_KERNEL);
-       if (!hsag)
+       if (!data)
                goto err_free_stats;
+       data->group.attrs = kcalloc(stats->num_counters + 2,
+                                   sizeof(*data->group.attrs), GFP_KERNEL);
+       if (!data->group.attrs)
+               goto err_free_data;
+
+       mutex_init(&stats->lock);
+       data->group.name = "hw_counters";
+       data->stats = stats;
+       return data;
 
-       ret = device->ops.get_hw_stats(device, stats, port_num,
-                                      stats->num_counters);
-       if (ret != stats->num_counters)
-               goto err_free_hsag;
+err_free_data:
+       kfree(data);
+err_free_stats:
+       kfree(stats);
+       return ERR_PTR(-ENOMEM);
+}
 
-       stats->timestamp = jiffies;
+static void free_hw_stats_port(struct hw_stats_port_data *data)
+{
+       kfree(data->group.attrs);
+       kfree(data->stats);
+       kfree(data);
+}
 
-       hsag->name = "hw_counters";
-       hsag->attrs = (void *)hsag + sizeof(*hsag);
+static int setup_hw_port_stats(struct ib_port *port)
+{
+       struct hw_stats_port_attribute *attr;
+       struct hw_stats_port_data *data;
+       int i, ret;
 
-       for (i = 0; i < stats->num_counters; i++) {
-               hsag->attrs[i] = alloc_hsa(i, port_num, stats->names[i]);
-               if (!hsag->attrs[i])
-                       goto err;
-               sysfs_attr_init(hsag->attrs[i]);
+       data = alloc_hw_stats_port(port);
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       ret = port->ibdev->ops.get_hw_stats(port->ibdev, data->stats,
+                                           port->port_num,
+                                           data->stats->num_counters);
+       if (ret != data->stats->num_counters) {
+               if (WARN_ON(ret >= 0))
+                       ret = -EINVAL;
+               goto err_free;
+       }
+       data->stats->timestamp = jiffies;
+
+       for (i = 0; i < data->stats->num_counters; i++) {
+               attr = &data->attrs[i];
+               sysfs_attr_init(&attr->attr.attr);
+               attr->attr.attr.name = data->stats->names[i];
+               attr->attr.attr.mode = 0444;
+               attr->attr.show = hw_stat_port_show;
+               attr->show = show_hw_stats;
+               data->group.attrs[i] = &attr->attr.attr;
        }
 
-       mutex_init(&stats->lock);
-       /* treat an error here as non-fatal */
-       hsag->attrs[i] = alloc_hsa_lifespan("lifespan", port_num);
-       if (hsag->attrs[i])
-               sysfs_attr_init(hsag->attrs[i]);
-
-       if (port) {
-               struct kobject *kobj = &port->kobj;
-               ret = sysfs_create_group(kobj, hsag);
-               if (ret)
-                       goto err;
-               port->hw_stats_ag = hsag;
-               port->hw_stats = stats;
-               if (device->port_data)
-                       device->port_data[port_num].hw_stats = stats;
-       } else {
-               struct kobject *kobj = &device->dev.kobj;
-               ret = sysfs_create_group(kobj, hsag);
-               if (ret)
-                       goto err;
-               device->hw_stats_ag = hsag;
-               device->hw_stats = stats;
+       attr = &data->attrs[i];
+       sysfs_attr_init(&attr->attr.attr);
+       attr->attr.attr.name = "lifespan";
+       attr->attr.attr.mode = 0644;
+       attr->attr.show = hw_stat_port_show;
+       attr->show = show_stats_lifespan;
+       attr->attr.store = hw_stat_port_store;
+       attr->store = set_stats_lifespan;
+       data->group.attrs[i] = &attr->attr.attr;
+
+       port->hw_stats_data = data;
+       ret = sysfs_create_group(&port->kobj, &data->group);
+       if (ret)
+               goto err_free;
+       return 0;
+
+err_free:
+       free_hw_stats_port(data);
+       port->hw_stats_data = NULL;
+       return ret;
+}
+
+static void destroy_hw_port_stats(struct ib_port *port)
+{
+       if (!port->hw_stats_data)
+               return;
+       sysfs_remove_group(&port->kobj, &port->hw_stats_data->group);
+       free_hw_stats_port(port->hw_stats_data);
+       port->hw_stats_data = NULL;
+}
+
+struct rdma_hw_stats *ib_get_hw_stats_port(struct ib_device *ibdev,
+                                          u32 port_num)
+{
+       if (!ibdev->port_data || !rdma_is_port_valid(ibdev, port_num) ||
+           !ibdev->port_data[port_num].sysfs->hw_stats_data)
+               return NULL;
+       return ibdev->port_data[port_num].sysfs->hw_stats_data->stats;
+}
+
+/*
+ * Create the sysfs:
+ *  ibp0s9/ports/XX/gid_attrs/{ndevs,types}/YYY
+ * YYY is the gid table index in decimal
+ */
+static int setup_gid_attrs(struct ib_port *port,
+                          const struct ib_port_attr *attr)
+{
+       struct gid_attr_group *gid_attr_group;
+       int ret;
+       int i;
+
+       gid_attr_group = kzalloc(sizeof(*gid_attr_group), GFP_KERNEL);
+       if (!gid_attr_group)
+               return -ENOMEM;
+
+       gid_attr_group->port = port;
+       ret = kobject_init_and_add(&gid_attr_group->kobj, &gid_attr_type,
+                                  &port->kobj, "gid_attrs");
+       if (ret)
+               goto err_put_gid_attrs;
+
+       gid_attr_group->ndev.name = "ndevs";
+       gid_attr_group->ndev.attrs =
+               alloc_group_attrs(show_port_gid_attr_ndev, attr->gid_tbl_len);
+       if (!gid_attr_group->ndev.attrs) {
+               ret = -ENOMEM;
+               goto err_put_gid_attrs;
        }
 
-       return;
+       ret = sysfs_create_group(&gid_attr_group->kobj, &gid_attr_group->ndev);
+       if (ret)
+               goto err_free_gid_ndev;
 
-err:
-       for (; i >= 0; i--)
-               kfree(hsag->attrs[i]);
-err_free_hsag:
-       kfree(hsag);
-err_free_stats:
-       kfree(stats);
+       gid_attr_group->type.name = "types";
+       gid_attr_group->type.attrs = alloc_group_attrs(
+               show_port_gid_attr_gid_type, attr->gid_tbl_len);
+       if (!gid_attr_group->type.attrs) {
+               ret = -ENOMEM;
+               goto err_remove_gid_ndev;
+       }
+
+       ret = sysfs_create_group(&gid_attr_group->kobj, &gid_attr_group->type);
+       if (ret)
+               goto err_free_gid_type;
+
+       port->gid_attr_group = gid_attr_group;
+       return 0;
+
+err_free_gid_type:
+       for (i = 0; i < attr->gid_tbl_len; ++i)
+               kfree(gid_attr_group->type.attrs[i]);
+
+       kfree(gid_attr_group->type.attrs);
+       gid_attr_group->type.attrs = NULL;
+err_remove_gid_ndev:
+       sysfs_remove_group(&gid_attr_group->kobj, &gid_attr_group->ndev);
+err_free_gid_ndev:
+       for (i = 0; i < attr->gid_tbl_len; ++i)
+               kfree(gid_attr_group->ndev.attrs[i]);
+
+       kfree(gid_attr_group->ndev.attrs);
+       gid_attr_group->ndev.attrs = NULL;
+err_put_gid_attrs:
+       kobject_put(&gid_attr_group->kobj);
+       return ret;
+}
+
+static void destroy_gid_attrs(struct ib_port *port)
+{
+       struct gid_attr_group *gid_attr_group = port->gid_attr_group;
+
+       sysfs_remove_group(&gid_attr_group->kobj,
+                          &gid_attr_group->ndev);
+       sysfs_remove_group(&gid_attr_group->kobj,
+                          &gid_attr_group->type);
+       kobject_put(&gid_attr_group->kobj);
 }
 
 static int add_port(struct ib_core_device *coredev, int port_num)
@@ -1077,23 +1283,11 @@ static int add_port(struct ib_core_device *coredev, int port_num)
        if (ret)
                goto err_put;
 
-       p->gid_attr_group = kzalloc(sizeof(*p->gid_attr_group), GFP_KERNEL);
-       if (!p->gid_attr_group) {
-               ret = -ENOMEM;
-               goto err_put;
-       }
-
-       p->gid_attr_group->port = p;
-       ret = kobject_init_and_add(&p->gid_attr_group->kobj, &gid_attr_type,
-                                  &p->kobj, "gid_attrs");
-       if (ret)
-               goto err_put_gid_attrs;
-
        if (device->ops.process_mad && is_full_dev) {
                p->pma_table = get_counter_table(device, port_num);
                ret = sysfs_create_group(&p->kobj, p->pma_table);
                if (ret)
-                       goto err_put_gid_attrs;
+                       goto err_put;
        }
 
        p->gid_group.name  = "gids";
@@ -1107,37 +1301,11 @@ static int add_port(struct ib_core_device *coredev, int port_num)
        if (ret)
                goto err_free_gid;
 
-       p->gid_attr_group->ndev.name = "ndevs";
-       p->gid_attr_group->ndev.attrs = alloc_group_attrs(show_port_gid_attr_ndev,
-                                                         attr.gid_tbl_len);
-       if (!p->gid_attr_group->ndev.attrs) {
-               ret = -ENOMEM;
-               goto err_remove_gid;
-       }
-
-       ret = sysfs_create_group(&p->gid_attr_group->kobj,
-                                &p->gid_attr_group->ndev);
-       if (ret)
-               goto err_free_gid_ndev;
-
-       p->gid_attr_group->type.name = "types";
-       p->gid_attr_group->type.attrs = alloc_group_attrs(show_port_gid_attr_gid_type,
-                                                         attr.gid_tbl_len);
-       if (!p->gid_attr_group->type.attrs) {
-               ret = -ENOMEM;
-               goto err_remove_gid_ndev;
-       }
-
-       ret = sysfs_create_group(&p->gid_attr_group->kobj,
-                                &p->gid_attr_group->type);
-       if (ret)
-               goto err_free_gid_type;
-
        if (attr.pkey_tbl_len) {
                p->pkey_group = kzalloc(sizeof(*p->pkey_group), GFP_KERNEL);
                if (!p->pkey_group) {
                        ret = -ENOMEM;
-                       goto err_remove_gid_type;
+                       goto err_remove_gid;
                }
 
                p->pkey_group->name  = "pkeys";
@@ -1153,26 +1321,39 @@ static int add_port(struct ib_core_device *coredev, int port_num)
                        goto err_free_pkey;
        }
 
+       /*
+        * If port == 0, it means hw_counters are per device and not per
+        * port, so holder should be device. Therefore skip per port
+        * counter initialization.
+        */
+       if (port_num && is_full_dev) {
+               ret = setup_hw_port_stats(p);
+               if (ret && ret != -EOPNOTSUPP)
+                       goto err_remove_pkey;
+       }
+       ret = setup_gid_attrs(p, &attr);
+       if (ret)
+               goto err_remove_stats;
 
        if (device->ops.init_port && is_full_dev) {
                ret = device->ops.init_port(device, port_num, &p->kobj);
                if (ret)
-                       goto err_remove_pkey;
+                       goto err_remove_gid_attrs;
        }
 
-       /*
-        * If port == 0, it means hw_counters are per device and not per
-        * port, so holder should be device. Therefore skip per port conunter
-        * initialization.
-        */
-       if (device->ops.alloc_hw_stats && port_num && is_full_dev)
-               setup_hw_stats(device, p, port_num);
-
        list_add_tail(&p->kobj.entry, &coredev->port_list);
+       if (device->port_data && is_full_dev)
+               device->port_data[port_num].sysfs = p;
 
        kobject_uevent(&p->kobj, KOBJ_ADD);
        return 0;
 
+err_remove_gid_attrs:
+       destroy_gid_attrs(p);
+
+err_remove_stats:
+       destroy_hw_port_stats(p);
+
 err_remove_pkey:
        if (p->pkey_group)
                sysfs_remove_group(&p->kobj, p->pkey_group);
@@ -1189,28 +1370,6 @@ err_free_pkey:
 err_free_pkey_group:
        kfree(p->pkey_group);
 
-err_remove_gid_type:
-       sysfs_remove_group(&p->gid_attr_group->kobj,
-                          &p->gid_attr_group->type);
-
-err_free_gid_type:
-       for (i = 0; i < attr.gid_tbl_len; ++i)
-               kfree(p->gid_attr_group->type.attrs[i]);
-
-       kfree(p->gid_attr_group->type.attrs);
-       p->gid_attr_group->type.attrs = NULL;
-
-err_remove_gid_ndev:
-       sysfs_remove_group(&p->gid_attr_group->kobj,
-                          &p->gid_attr_group->ndev);
-
-err_free_gid_ndev:
-       for (i = 0; i < attr.gid_tbl_len; ++i)
-               kfree(p->gid_attr_group->ndev.attrs[i]);
-
-       kfree(p->gid_attr_group->ndev.attrs);
-       p->gid_attr_group->ndev.attrs = NULL;
-
 err_remove_gid:
        sysfs_remove_group(&p->kobj, &p->gid_group);
 
@@ -1225,9 +1384,6 @@ err_remove_pma:
        if (p->pma_table)
                sysfs_remove_group(&p->kobj, p->pma_table);
 
-err_put_gid_attrs:
-       kobject_put(&p->gid_attr_group->kobj);
-
 err_put:
        kobject_put(&p->kobj);
        return ret;
@@ -1355,22 +1511,16 @@ void ib_free_port_attrs(struct ib_core_device *coredev)
                struct ib_port *port = container_of(p, struct ib_port, kobj);
 
                list_del(&p->entry);
-               if (port->hw_stats_ag)
-                       free_hsag(&port->kobj, port->hw_stats_ag);
-               kfree(port->hw_stats);
+               destroy_hw_port_stats(port);
                if (device->port_data && is_full_dev)
-                       device->port_data[port->port_num].hw_stats = NULL;
+                       device->port_data[port->port_num].sysfs = NULL;
 
                if (port->pma_table)
                        sysfs_remove_group(p, port->pma_table);
                if (port->pkey_group)
                        sysfs_remove_group(p, port->pkey_group);
                sysfs_remove_group(p, &port->gid_group);
-               sysfs_remove_group(&port->gid_attr_group->kobj,
-                                  &port->gid_attr_group->ndev);
-               sysfs_remove_group(&port->gid_attr_group->kobj,
-                                  &port->gid_attr_group->type);
-               kobject_put(&port->gid_attr_group->kobj);
+               destroy_gid_attrs(port);
                kobject_put(p);
        }
 
@@ -1409,18 +1559,18 @@ int ib_device_register_sysfs(struct ib_device *device)
        if (ret)
                return ret;
 
-       if (device->ops.alloc_hw_stats)
-               setup_hw_stats(device, NULL, 0);
+       ret = setup_hw_device_stats(device);
+       if (ret && ret != -EOPNOTSUPP) {
+               ib_free_port_attrs(&device->coredev);
+               return ret;
+       }
 
        return 0;
 }
 
 void ib_device_unregister_sysfs(struct ib_device *device)
 {
-       if (device->hw_stats_ag)
-               free_hsag(&device->dev.kobj, device->hw_stats_ag);
-       kfree(device->hw_stats);
-
+       destroy_hw_device_stats(device);
        ib_free_port_attrs(&device->coredev);
 }