mm/mempolicy: implement the sysfs-based weighted_interleave interface
[linux-block.git] / mm / mempolicy.c
index 5e519163c4dcb6873dd1ce184401bb30eca12f4b..b4fccc921b62382c33d8d09299eadc6b3d2a7b3d 100644 (file)
@@ -131,6 +131,32 @@ static struct mempolicy default_policy = {
 
 static struct mempolicy preferred_node_policy[MAX_NUMNODES];
 
+/*
+ * iw_table is the sysfs-set interleave weight table, a value of 0 denotes
+ * system-default value should be used. A NULL iw_table also denotes that
+ * system-default values should be used. Until the system-default table
+ * is implemented, the system-default is always 1.
+ *
+ * iw_table is RCU protected
+ */
+static u8 __rcu *iw_table;
+static DEFINE_MUTEX(iw_table_lock);
+
+static u8 get_il_weight(int node)
+{
+       u8 *table;
+       u8 weight;
+
+       rcu_read_lock();
+       table = rcu_dereference(iw_table);
+       /* if no iw_table, use system default */
+       weight = table ? table[node] : 1;
+       /* if value in iw_table is 0, use system default */
+       weight = weight ? weight : 1;
+       rcu_read_unlock();
+       return weight;
+}
+
 /**
  * numa_nearest_node - Find nearest node by state
  * @node: Node id to start the search
@@ -3063,3 +3089,200 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
                p += scnprintf(p, buffer + maxlen - p, ":%*pbl",
                               nodemask_pr_args(&nodes));
 }
+
+#ifdef CONFIG_SYSFS
+struct iw_node_attr {
+       struct kobj_attribute kobj_attr;
+       int nid;
+};
+
+static ssize_t node_show(struct kobject *kobj, struct kobj_attribute *attr,
+                        char *buf)
+{
+       struct iw_node_attr *node_attr;
+       u8 weight;
+
+       node_attr = container_of(attr, struct iw_node_attr, kobj_attr);
+       weight = get_il_weight(node_attr->nid);
+       return sysfs_emit(buf, "%d\n", weight);
+}
+
+static ssize_t node_store(struct kobject *kobj, struct kobj_attribute *attr,
+                         const char *buf, size_t count)
+{
+       struct iw_node_attr *node_attr;
+       u8 *new;
+       u8 *old;
+       u8 weight = 0;
+
+       node_attr = container_of(attr, struct iw_node_attr, kobj_attr);
+       if (count == 0 || sysfs_streq(buf, ""))
+               weight = 0;
+       else if (kstrtou8(buf, 0, &weight))
+               return -EINVAL;
+
+       new = kzalloc(nr_node_ids, GFP_KERNEL);
+       if (!new)
+               return -ENOMEM;
+
+       mutex_lock(&iw_table_lock);
+       old = rcu_dereference_protected(iw_table,
+                                       lockdep_is_held(&iw_table_lock));
+       if (old)
+               memcpy(new, old, nr_node_ids);
+       new[node_attr->nid] = weight;
+       rcu_assign_pointer(iw_table, new);
+       mutex_unlock(&iw_table_lock);
+       synchronize_rcu();
+       kfree(old);
+       return count;
+}
+
+static struct iw_node_attr **node_attrs;
+
+static void sysfs_wi_node_release(struct iw_node_attr *node_attr,
+                                 struct kobject *parent)
+{
+       if (!node_attr)
+               return;
+       sysfs_remove_file(parent, &node_attr->kobj_attr.attr);
+       kfree(node_attr->kobj_attr.attr.name);
+       kfree(node_attr);
+}
+
+static void sysfs_wi_release(struct kobject *wi_kobj)
+{
+       int i;
+
+       for (i = 0; i < nr_node_ids; i++)
+               sysfs_wi_node_release(node_attrs[i], wi_kobj);
+       kobject_put(wi_kobj);
+}
+
+static const struct kobj_type wi_ktype = {
+       .sysfs_ops = &kobj_sysfs_ops,
+       .release = sysfs_wi_release,
+};
+
+static int add_weight_node(int nid, struct kobject *wi_kobj)
+{
+       struct iw_node_attr *node_attr;
+       char *name;
+
+       node_attr = kzalloc(sizeof(*node_attr), GFP_KERNEL);
+       if (!node_attr)
+               return -ENOMEM;
+
+       name = kasprintf(GFP_KERNEL, "node%d", nid);
+       if (!name) {
+               kfree(node_attr);
+               return -ENOMEM;
+       }
+
+       sysfs_attr_init(&node_attr->kobj_attr.attr);
+       node_attr->kobj_attr.attr.name = name;
+       node_attr->kobj_attr.attr.mode = 0644;
+       node_attr->kobj_attr.show = node_show;
+       node_attr->kobj_attr.store = node_store;
+       node_attr->nid = nid;
+
+       if (sysfs_create_file(wi_kobj, &node_attr->kobj_attr.attr)) {
+               kfree(node_attr->kobj_attr.attr.name);
+               kfree(node_attr);
+               pr_err("failed to add attribute to weighted_interleave\n");
+               return -ENOMEM;
+       }
+
+       node_attrs[nid] = node_attr;
+       return 0;
+}
+
+static int add_weighted_interleave_group(struct kobject *root_kobj)
+{
+       struct kobject *wi_kobj;
+       int nid, err;
+
+       wi_kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL);
+       if (!wi_kobj)
+               return -ENOMEM;
+
+       err = kobject_init_and_add(wi_kobj, &wi_ktype, root_kobj,
+                                  "weighted_interleave");
+       if (err) {
+               kfree(wi_kobj);
+               return err;
+       }
+
+       for_each_node_state(nid, N_POSSIBLE) {
+               err = add_weight_node(nid, wi_kobj);
+               if (err) {
+                       pr_err("failed to add sysfs [node%d]\n", nid);
+                       break;
+               }
+       }
+       if (err)
+               kobject_put(wi_kobj);
+       return 0;
+}
+
+static void mempolicy_kobj_release(struct kobject *kobj)
+{
+       u8 *old;
+
+       mutex_lock(&iw_table_lock);
+       old = rcu_dereference_protected(iw_table,
+                                       lockdep_is_held(&iw_table_lock));
+       rcu_assign_pointer(iw_table, NULL);
+       mutex_unlock(&iw_table_lock);
+       synchronize_rcu();
+       kfree(old);
+       kfree(node_attrs);
+       kfree(kobj);
+}
+
+static const struct kobj_type mempolicy_ktype = {
+       .release = mempolicy_kobj_release
+};
+
+static int __init mempolicy_sysfs_init(void)
+{
+       int err;
+       static struct kobject *mempolicy_kobj;
+
+       mempolicy_kobj = kzalloc(sizeof(*mempolicy_kobj), GFP_KERNEL);
+       if (!mempolicy_kobj) {
+               err = -ENOMEM;
+               goto err_out;
+       }
+
+       node_attrs = kcalloc(nr_node_ids, sizeof(struct iw_node_attr *),
+                            GFP_KERNEL);
+       if (!node_attrs) {
+               err = -ENOMEM;
+               goto mempol_out;
+       }
+
+       err = kobject_init_and_add(mempolicy_kobj, &mempolicy_ktype, mm_kobj,
+                                  "mempolicy");
+       if (err)
+               goto node_out;
+
+       err = add_weighted_interleave_group(mempolicy_kobj);
+       if (err) {
+               pr_err("mempolicy sysfs structure failed to initialize\n");
+               kobject_put(mempolicy_kobj);
+               return err;
+       }
+
+       return err;
+node_out:
+       kfree(node_attrs);
+mempol_out:
+       kfree(mempolicy_kobj);
+err_out:
+       pr_err("failed to add mempolicy kobject to the system\n");
+       return err;
+}
+
+late_initcall(mempolicy_sysfs_init);
+#endif /* CONFIG_SYSFS */