Merge tag 'backlight-next-5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/lee...
[linux-2.6-microblaze.git] / drivers / base / node.c
index 86d6cd9..8598fcb 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/nodemask.h>
 #include <linux/cpu.h>
 #include <linux/device.h>
+#include <linux/pm_runtime.h>
 #include <linux/swap.h>
 #include <linux/slab.h>
 
@@ -59,6 +60,302 @@ static inline ssize_t node_read_cpulist(struct device *dev,
 static DEVICE_ATTR(cpumap,  S_IRUGO, node_read_cpumask, NULL);
 static DEVICE_ATTR(cpulist, S_IRUGO, node_read_cpulist, NULL);
 
+/**
+ * struct node_access_nodes - Access class device to hold user visible
+ *                           relationships to other nodes.
+ * @dev:       Device for this memory access class
+ * @list_node: List element in the node's access list
+ * @access:    The access class rank
+ */
+struct node_access_nodes {
+       struct device           dev;
+       struct list_head        list_node;
+       unsigned                access;
+#ifdef CONFIG_HMEM_REPORTING
+       struct node_hmem_attrs  hmem_attrs;
+#endif
+};
+#define to_access_nodes(dev) container_of(dev, struct node_access_nodes, dev)
+
+static struct attribute *node_init_access_node_attrs[] = {
+       NULL,
+};
+
+static struct attribute *node_targ_access_node_attrs[] = {
+       NULL,
+};
+
+static const struct attribute_group initiators = {
+       .name   = "initiators",
+       .attrs  = node_init_access_node_attrs,
+};
+
+static const struct attribute_group targets = {
+       .name   = "targets",
+       .attrs  = node_targ_access_node_attrs,
+};
+
+static const struct attribute_group *node_access_node_groups[] = {
+       &initiators,
+       &targets,
+       NULL,
+};
+
+static void node_remove_accesses(struct node *node)
+{
+       struct node_access_nodes *c, *cnext;
+
+       list_for_each_entry_safe(c, cnext, &node->access_list, list_node) {
+               list_del(&c->list_node);
+               device_unregister(&c->dev);
+       }
+}
+
+static void node_access_release(struct device *dev)
+{
+       kfree(to_access_nodes(dev));
+}
+
+static struct node_access_nodes *node_init_node_access(struct node *node,
+                                                      unsigned access)
+{
+       struct node_access_nodes *access_node;
+       struct device *dev;
+
+       list_for_each_entry(access_node, &node->access_list, list_node)
+               if (access_node->access == access)
+                       return access_node;
+
+       access_node = kzalloc(sizeof(*access_node), GFP_KERNEL);
+       if (!access_node)
+               return NULL;
+
+       access_node->access = access;
+       dev = &access_node->dev;
+       dev->parent = &node->dev;
+       dev->release = node_access_release;
+       dev->groups = node_access_node_groups;
+       if (dev_set_name(dev, "access%u", access))
+               goto free;
+
+       if (device_register(dev))
+               goto free_name;
+
+       pm_runtime_no_callbacks(dev);
+       list_add_tail(&access_node->list_node, &node->access_list);
+       return access_node;
+free_name:
+       kfree_const(dev->kobj.name);
+free:
+       kfree(access_node);
+       return NULL;
+}
+
+#ifdef CONFIG_HMEM_REPORTING
+#define ACCESS_ATTR(name)                                                 \
+static ssize_t name##_show(struct device *dev,                            \
+                          struct device_attribute *attr,                  \
+                          char *buf)                                      \
+{                                                                         \
+       return sprintf(buf, "%u\n", to_access_nodes(dev)->hmem_attrs.name); \
+}                                                                         \
+static DEVICE_ATTR_RO(name);
+
+ACCESS_ATTR(read_bandwidth)
+ACCESS_ATTR(read_latency)
+ACCESS_ATTR(write_bandwidth)
+ACCESS_ATTR(write_latency)
+
+static struct attribute *access_attrs[] = {
+       &dev_attr_read_bandwidth.attr,
+       &dev_attr_read_latency.attr,
+       &dev_attr_write_bandwidth.attr,
+       &dev_attr_write_latency.attr,
+       NULL,
+};
+
+/**
+ * node_set_perf_attrs - Set the performance values for given access class
+ * @nid: Node identifier to be set
+ * @hmem_attrs: Heterogeneous memory performance attributes
+ * @access: The access class the for the given attributes
+ */
+void node_set_perf_attrs(unsigned int nid, struct node_hmem_attrs *hmem_attrs,
+                        unsigned access)
+{
+       struct node_access_nodes *c;
+       struct node *node;
+       int i;
+
+       if (WARN_ON_ONCE(!node_online(nid)))
+               return;
+
+       node = node_devices[nid];
+       c = node_init_node_access(node, access);
+       if (!c)
+               return;
+
+       c->hmem_attrs = *hmem_attrs;
+       for (i = 0; access_attrs[i] != NULL; i++) {
+               if (sysfs_add_file_to_group(&c->dev.kobj, access_attrs[i],
+                                           "initiators")) {
+                       pr_info("failed to add performance attribute to node %d\n",
+                               nid);
+                       break;
+               }
+       }
+}
+
+/**
+ * struct node_cache_info - Internal tracking for memory node caches
+ * @dev:       Device represeting the cache level
+ * @node:      List element for tracking in the node
+ * @cache_attrs:Attributes for this cache level
+ */
+struct node_cache_info {
+       struct device dev;
+       struct list_head node;
+       struct node_cache_attrs cache_attrs;
+};
+#define to_cache_info(device) container_of(device, struct node_cache_info, dev)
+
+#define CACHE_ATTR(name, fmt)                                          \
+static ssize_t name##_show(struct device *dev,                         \
+                          struct device_attribute *attr,               \
+                          char *buf)                                   \
+{                                                                      \
+       return sprintf(buf, fmt "\n", to_cache_info(dev)->cache_attrs.name);\
+}                                                                      \
+DEVICE_ATTR_RO(name);
+
+CACHE_ATTR(size, "%llu")
+CACHE_ATTR(line_size, "%u")
+CACHE_ATTR(indexing, "%u")
+CACHE_ATTR(write_policy, "%u")
+
+static struct attribute *cache_attrs[] = {
+       &dev_attr_indexing.attr,
+       &dev_attr_size.attr,
+       &dev_attr_line_size.attr,
+       &dev_attr_write_policy.attr,
+       NULL,
+};
+ATTRIBUTE_GROUPS(cache);
+
+static void node_cache_release(struct device *dev)
+{
+       kfree(dev);
+}
+
+static void node_cacheinfo_release(struct device *dev)
+{
+       struct node_cache_info *info = to_cache_info(dev);
+       kfree(info);
+}
+
+static void node_init_cache_dev(struct node *node)
+{
+       struct device *dev;
+
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return;
+
+       dev->parent = &node->dev;
+       dev->release = node_cache_release;
+       if (dev_set_name(dev, "memory_side_cache"))
+               goto free_dev;
+
+       if (device_register(dev))
+               goto free_name;
+
+       pm_runtime_no_callbacks(dev);
+       node->cache_dev = dev;
+       return;
+free_name:
+       kfree_const(dev->kobj.name);
+free_dev:
+       kfree(dev);
+}
+
+/**
+ * node_add_cache() - add cache attribute to a memory node
+ * @nid: Node identifier that has new cache attributes
+ * @cache_attrs: Attributes for the cache being added
+ */
+void node_add_cache(unsigned int nid, struct node_cache_attrs *cache_attrs)
+{
+       struct node_cache_info *info;
+       struct device *dev;
+       struct node *node;
+
+       if (!node_online(nid) || !node_devices[nid])
+               return;
+
+       node = node_devices[nid];
+       list_for_each_entry(info, &node->cache_attrs, node) {
+               if (info->cache_attrs.level == cache_attrs->level) {
+                       dev_warn(&node->dev,
+                               "attempt to add duplicate cache level:%d\n",
+                               cache_attrs->level);
+                       return;
+               }
+       }
+
+       if (!node->cache_dev)
+               node_init_cache_dev(node);
+       if (!node->cache_dev)
+               return;
+
+       info = kzalloc(sizeof(*info), GFP_KERNEL);
+       if (!info)
+               return;
+
+       dev = &info->dev;
+       dev->parent = node->cache_dev;
+       dev->release = node_cacheinfo_release;
+       dev->groups = cache_groups;
+       if (dev_set_name(dev, "index%d", cache_attrs->level))
+               goto free_cache;
+
+       info->cache_attrs = *cache_attrs;
+       if (device_register(dev)) {
+               dev_warn(&node->dev, "failed to add cache level:%d\n",
+                        cache_attrs->level);
+               goto free_name;
+       }
+       pm_runtime_no_callbacks(dev);
+       list_add_tail(&info->node, &node->cache_attrs);
+       return;
+free_name:
+       kfree_const(dev->kobj.name);
+free_cache:
+       kfree(info);
+}
+
+static void node_remove_caches(struct node *node)
+{
+       struct node_cache_info *info, *next;
+
+       if (!node->cache_dev)
+               return;
+
+       list_for_each_entry_safe(info, next, &node->cache_attrs, node) {
+               list_del(&info->node);
+               device_unregister(&info->dev);
+       }
+       device_unregister(node->cache_dev);
+}
+
+static void node_init_caches(unsigned int nid)
+{
+       INIT_LIST_HEAD(&node_devices[nid]->cache_attrs);
+}
+#else
+static void node_init_caches(unsigned int nid) { }
+static void node_remove_caches(struct node *node) { }
+#endif
+
 #define K(x) ((x) << (PAGE_SHIFT - 10))
 static ssize_t node_read_meminfo(struct device *dev,
                        struct device_attribute *attr, char *buf)
@@ -340,7 +637,8 @@ static int register_node(struct node *node, int num)
 void unregister_node(struct node *node)
 {
        hugetlb_unregister_node(node);          /* no-op, if memoryless node */
-
+       node_remove_accesses(node);
+       node_remove_caches(node);
        device_unregister(&node->dev);
 }
 
@@ -372,6 +670,56 @@ int register_cpu_under_node(unsigned int cpu, unsigned int nid)
                                 kobject_name(&node_devices[nid]->dev.kobj));
 }
 
+/**
+ * register_memory_node_under_compute_node - link memory node to its compute
+ *                                          node for a given access class.
+ * @mem_node:  Memory node number
+ * @cpu_node:  Cpu  node number
+ * @access:    Access class to register
+ *
+ * Description:
+ *     For use with platforms that may have separate memory and compute nodes.
+ *     This function will export node relationships linking which memory
+ *     initiator nodes can access memory targets at a given ranked access
+ *     class.
+ */
+int register_memory_node_under_compute_node(unsigned int mem_nid,
+                                           unsigned int cpu_nid,
+                                           unsigned access)
+{
+       struct node *init_node, *targ_node;
+       struct node_access_nodes *initiator, *target;
+       int ret;
+
+       if (!node_online(cpu_nid) || !node_online(mem_nid))
+               return -ENODEV;
+
+       init_node = node_devices[cpu_nid];
+       targ_node = node_devices[mem_nid];
+       initiator = node_init_node_access(init_node, access);
+       target = node_init_node_access(targ_node, access);
+       if (!initiator || !target)
+               return -ENOMEM;
+
+       ret = sysfs_add_link_to_group(&initiator->dev.kobj, "targets",
+                                     &targ_node->dev.kobj,
+                                     dev_name(&targ_node->dev));
+       if (ret)
+               return ret;
+
+       ret = sysfs_add_link_to_group(&target->dev.kobj, "initiators",
+                                     &init_node->dev.kobj,
+                                     dev_name(&init_node->dev));
+       if (ret)
+               goto err;
+
+       return 0;
+ err:
+       sysfs_remove_link_from_group(&initiator->dev.kobj, "targets",
+                                    dev_name(&targ_node->dev));
+       return ret;
+}
+
 int unregister_cpu_under_node(unsigned int cpu, unsigned int nid)
 {
        struct device *obj;
@@ -580,8 +928,10 @@ int __register_one_node(int nid)
                        register_cpu_under_node(cpu, nid);
        }
 
+       INIT_LIST_HEAD(&node_devices[nid]->access_list);
        /* initialize work queue for memory hot plug */
        init_node_hugetlb_work(nid);
+       node_init_caches(nid);
 
        return error;
 }