]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blobdiff - drivers/base/property.c
Merge branch 'stable/for-linus-4.5' of git://git.kernel.org/pub/scm/linux/kernel...
[mirror_ubuntu-zesty-kernel.git] / drivers / base / property.c
index 1325ff225cc4a5b37a7c0b3fe0b3d7946c37811e..c359351d50f1c99e9758b74ea84d97e76b402b0f 100644 (file)
 #include <linux/etherdevice.h>
 #include <linux/phy.h>
 
-/**
- * device_add_property_set - Add a collection of properties to a device object.
- * @dev: Device to add properties to.
- * @pset: Collection of properties to add.
- *
- * Associate a collection of device properties represented by @pset with @dev
- * as its secondary firmware node.
- */
-void device_add_property_set(struct device *dev, struct property_set *pset)
-{
-       if (!pset)
-               return;
-
-       pset->fwnode.type = FWNODE_PDATA;
-       set_secondary_fwnode(dev, &pset->fwnode);
-}
-EXPORT_SYMBOL_GPL(device_add_property_set);
-
-static inline bool is_pset(struct fwnode_handle *fwnode)
+static inline bool is_pset_node(struct fwnode_handle *fwnode)
 {
        return fwnode && fwnode->type == FWNODE_PDATA;
 }
 
-static inline struct property_set *to_pset(struct fwnode_handle *fwnode)
+static inline struct property_set *to_pset_node(struct fwnode_handle *fwnode)
 {
-       return is_pset(fwnode) ?
+       return is_pset_node(fwnode) ?
                container_of(fwnode, struct property_set, fwnode) : NULL;
 }
 
@@ -63,45 +45,135 @@ static struct property_entry *pset_prop_get(struct property_set *pset,
        return NULL;
 }
 
-static int pset_prop_read_array(struct property_set *pset, const char *name,
-                               enum dev_prop_type type, void *val, size_t nval)
+static void *pset_prop_find(struct property_set *pset, const char *propname,
+                           size_t length)
 {
        struct property_entry *prop;
-       unsigned int item_size;
+       void *pointer;
 
-       prop = pset_prop_get(pset, name);
+       prop = pset_prop_get(pset, propname);
        if (!prop)
-               return -ENODATA;
+               return ERR_PTR(-EINVAL);
+       if (prop->is_array)
+               pointer = prop->pointer.raw_data;
+       else
+               pointer = &prop->value.raw_data;
+       if (!pointer)
+               return ERR_PTR(-ENODATA);
+       if (length > prop->length)
+               return ERR_PTR(-EOVERFLOW);
+       return pointer;
+}
+
+static int pset_prop_read_u8_array(struct property_set *pset,
+                                  const char *propname,
+                                  u8 *values, size_t nval)
+{
+       void *pointer;
+       size_t length = nval * sizeof(*values);
+
+       pointer = pset_prop_find(pset, propname, length);
+       if (IS_ERR(pointer))
+               return PTR_ERR(pointer);
+
+       memcpy(values, pointer, length);
+       return 0;
+}
+
+static int pset_prop_read_u16_array(struct property_set *pset,
+                                   const char *propname,
+                                   u16 *values, size_t nval)
+{
+       void *pointer;
+       size_t length = nval * sizeof(*values);
+
+       pointer = pset_prop_find(pset, propname, length);
+       if (IS_ERR(pointer))
+               return PTR_ERR(pointer);
+
+       memcpy(values, pointer, length);
+       return 0;
+}
+
+static int pset_prop_read_u32_array(struct property_set *pset,
+                                   const char *propname,
+                                   u32 *values, size_t nval)
+{
+       void *pointer;
+       size_t length = nval * sizeof(*values);
+
+       pointer = pset_prop_find(pset, propname, length);
+       if (IS_ERR(pointer))
+               return PTR_ERR(pointer);
+
+       memcpy(values, pointer, length);
+       return 0;
+}
+
+static int pset_prop_read_u64_array(struct property_set *pset,
+                                   const char *propname,
+                                   u64 *values, size_t nval)
+{
+       void *pointer;
+       size_t length = nval * sizeof(*values);
+
+       pointer = pset_prop_find(pset, propname, length);
+       if (IS_ERR(pointer))
+               return PTR_ERR(pointer);
+
+       memcpy(values, pointer, length);
+       return 0;
+}
+
+static int pset_prop_count_elems_of_size(struct property_set *pset,
+                                        const char *propname, size_t length)
+{
+       struct property_entry *prop;
+
+       prop = pset_prop_get(pset, propname);
+       if (!prop)
+               return -EINVAL;
+
+       return prop->length / length;
+}
+
+static int pset_prop_read_string_array(struct property_set *pset,
+                                      const char *propname,
+                                      const char **strings, size_t nval)
+{
+       void *pointer;
+       size_t length = nval * sizeof(*strings);
+
+       pointer = pset_prop_find(pset, propname, length);
+       if (IS_ERR(pointer))
+               return PTR_ERR(pointer);
+
+       memcpy(strings, pointer, length);
+       return 0;
+}
+
+static int pset_prop_read_string(struct property_set *pset,
+                                const char *propname, const char **strings)
+{
+       struct property_entry *prop;
+       const char **pointer;
 
-       if (prop->type != type)
-               return -EPROTO;
-
-       if (!val)
-               return prop->nval;
-
-       if (prop->nval < nval)
-               return -EOVERFLOW;
-
-       switch (type) {
-       case DEV_PROP_U8:
-               item_size = sizeof(u8);
-               break;
-       case DEV_PROP_U16:
-               item_size = sizeof(u16);
-               break;
-       case DEV_PROP_U32:
-               item_size = sizeof(u32);
-               break;
-       case DEV_PROP_U64:
-               item_size = sizeof(u64);
-               break;
-       case DEV_PROP_STRING:
-               item_size = sizeof(const char *);
-               break;
-       default:
+       prop = pset_prop_get(pset, propname);
+       if (!prop)
                return -EINVAL;
+       if (!prop->is_string)
+               return -EILSEQ;
+       if (prop->is_array) {
+               pointer = prop->pointer.str;
+               if (!pointer)
+                       return -ENODATA;
+       } else {
+               pointer = &prop->value.str;
+               if (*pointer && strnlen(*pointer, prop->length) >= prop->length)
+                       return -EILSEQ;
        }
-       memcpy(val, prop->value.raw_data, nval * item_size);
+
+       *strings = *pointer;
        return 0;
 }
 
@@ -124,6 +196,18 @@ bool device_property_present(struct device *dev, const char *propname)
 }
 EXPORT_SYMBOL_GPL(device_property_present);
 
+static bool __fwnode_property_present(struct fwnode_handle *fwnode,
+                                     const char *propname)
+{
+       if (is_of_node(fwnode))
+               return of_property_read_bool(to_of_node(fwnode), propname);
+       else if (is_acpi_node(fwnode))
+               return !acpi_node_prop_get(fwnode, propname, NULL);
+       else if (is_pset_node(fwnode))
+               return !!pset_prop_get(to_pset_node(fwnode), propname);
+       return false;
+}
+
 /**
  * fwnode_property_present - check if a property of a firmware node is present
  * @fwnode: Firmware node whose property to check
@@ -131,12 +215,12 @@ EXPORT_SYMBOL_GPL(device_property_present);
  */
 bool fwnode_property_present(struct fwnode_handle *fwnode, const char *propname)
 {
-       if (is_of_node(fwnode))
-               return of_property_read_bool(to_of_node(fwnode), propname);
-       else if (is_acpi_node(fwnode))
-               return !acpi_node_prop_get(fwnode, propname, NULL);
+       bool ret;
 
-       return !!pset_prop_get(to_pset(fwnode), propname);
+       ret = __fwnode_property_present(fwnode, propname);
+       if (ret == false && fwnode && fwnode->secondary)
+               ret = __fwnode_property_present(fwnode->secondary, propname);
+       return ret;
 }
 EXPORT_SYMBOL_GPL(fwnode_property_present);
 
@@ -309,25 +393,40 @@ int device_property_match_string(struct device *dev, const char *propname,
 }
 EXPORT_SYMBOL_GPL(device_property_match_string);
 
-#define OF_DEV_PROP_READ_ARRAY(node, propname, type, val, nval) \
-       (val) ? of_property_read_##type##_array((node), (propname), (val), (nval)) \
+#define OF_DEV_PROP_READ_ARRAY(node, propname, type, val, nval)                                \
+       (val) ? of_property_read_##type##_array((node), (propname), (val), (nval))      \
              : of_property_count_elems_of_size((node), (propname), sizeof(type))
 
-#define FWNODE_PROP_READ_ARRAY(_fwnode_, _propname_, _type_, _proptype_, _val_, _nval_) \
-({ \
-       int _ret_; \
-       if (is_of_node(_fwnode_)) \
-               _ret_ = OF_DEV_PROP_READ_ARRAY(to_of_node(_fwnode_), _propname_, \
-                                              _type_, _val_, _nval_); \
-       else if (is_acpi_node(_fwnode_)) \
-               _ret_ = acpi_node_prop_read(_fwnode_, _propname_, _proptype_, \
-                                           _val_, _nval_); \
-       else if (is_pset(_fwnode_)) \
-               _ret_ = pset_prop_read_array(to_pset(_fwnode_), _propname_, \
-                                            _proptype_, _val_, _nval_); \
-       else \
-               _ret_ = -ENXIO; \
-       _ret_; \
+#define PSET_PROP_READ_ARRAY(node, propname, type, val, nval)                          \
+       (val) ? pset_prop_read_##type##_array((node), (propname), (val), (nval))        \
+             : pset_prop_count_elems_of_size((node), (propname), sizeof(type))
+
+#define FWNODE_PROP_READ(_fwnode_, _propname_, _type_, _proptype_, _val_, _nval_)      \
+({                                                                                     \
+       int _ret_;                                                                      \
+       if (is_of_node(_fwnode_))                                                       \
+               _ret_ = OF_DEV_PROP_READ_ARRAY(to_of_node(_fwnode_), _propname_,        \
+                                              _type_, _val_, _nval_);                  \
+       else if (is_acpi_node(_fwnode_))                                                \
+               _ret_ = acpi_node_prop_read(_fwnode_, _propname_, _proptype_,           \
+                                           _val_, _nval_);                             \
+       else if (is_pset_node(_fwnode_))                                                \
+               _ret_ = PSET_PROP_READ_ARRAY(to_pset_node(_fwnode_), _propname_,        \
+                                            _type_, _val_, _nval_);                    \
+       else                                                                            \
+               _ret_ = -ENXIO;                                                         \
+       _ret_;                                                                          \
+})
+
+#define FWNODE_PROP_READ_ARRAY(_fwnode_, _propname_, _type_, _proptype_, _val_, _nval_)        \
+({                                                                                     \
+       int _ret_;                                                                      \
+       _ret_ = FWNODE_PROP_READ(_fwnode_, _propname_, _type_, _proptype_,              \
+                                _val_, _nval_);                                        \
+       if (_ret_ == -EINVAL && _fwnode_ && _fwnode_->secondary)                        \
+               _ret_ = FWNODE_PROP_READ(_fwnode_->secondary, _propname_, _type_,       \
+                               _proptype_, _val_, _nval_);                             \
+       _ret_;                                                                          \
 })
 
 /**
@@ -434,6 +533,41 @@ int fwnode_property_read_u64_array(struct fwnode_handle *fwnode,
 }
 EXPORT_SYMBOL_GPL(fwnode_property_read_u64_array);
 
+static int __fwnode_property_read_string_array(struct fwnode_handle *fwnode,
+                                              const char *propname,
+                                              const char **val, size_t nval)
+{
+       if (is_of_node(fwnode))
+               return val ?
+                       of_property_read_string_array(to_of_node(fwnode),
+                                                     propname, val, nval) :
+                       of_property_count_strings(to_of_node(fwnode), propname);
+       else if (is_acpi_node(fwnode))
+               return acpi_node_prop_read(fwnode, propname, DEV_PROP_STRING,
+                                          val, nval);
+       else if (is_pset_node(fwnode))
+               return val ?
+                       pset_prop_read_string_array(to_pset_node(fwnode),
+                                                   propname, val, nval) :
+                       pset_prop_count_elems_of_size(to_pset_node(fwnode),
+                                                     propname,
+                                                     sizeof(const char *));
+       return -ENXIO;
+}
+
+static int __fwnode_property_read_string(struct fwnode_handle *fwnode,
+                                        const char *propname, const char **val)
+{
+       if (is_of_node(fwnode))
+               return of_property_read_string(to_of_node(fwnode), propname, val);
+       else if (is_acpi_node(fwnode))
+               return acpi_node_prop_read(fwnode, propname, DEV_PROP_STRING,
+                                          val, 1);
+       else if (is_pset_node(fwnode))
+               return pset_prop_read_string(to_pset_node(fwnode), propname, val);
+       return -ENXIO;
+}
+
 /**
  * fwnode_property_read_string_array - return string array property of a node
  * @fwnode: Firmware node to get the property of
@@ -456,18 +590,13 @@ int fwnode_property_read_string_array(struct fwnode_handle *fwnode,
                                      const char *propname, const char **val,
                                      size_t nval)
 {
-       if (is_of_node(fwnode))
-               return val ?
-                       of_property_read_string_array(to_of_node(fwnode),
-                                                     propname, val, nval) :
-                       of_property_count_strings(to_of_node(fwnode), propname);
-       else if (is_acpi_node(fwnode))
-               return acpi_node_prop_read(fwnode, propname, DEV_PROP_STRING,
-                                          val, nval);
-       else if (is_pset(fwnode))
-               return pset_prop_read_array(to_pset(fwnode), propname,
-                                           DEV_PROP_STRING, val, nval);
-       return -ENXIO;
+       int ret;
+
+       ret = __fwnode_property_read_string_array(fwnode, propname, val, nval);
+       if (ret == -EINVAL && fwnode && fwnode->secondary)
+               ret = __fwnode_property_read_string_array(fwnode->secondary,
+                                                         propname, val, nval);
+       return ret;
 }
 EXPORT_SYMBOL_GPL(fwnode_property_read_string_array);
 
@@ -489,14 +618,13 @@ EXPORT_SYMBOL_GPL(fwnode_property_read_string_array);
 int fwnode_property_read_string(struct fwnode_handle *fwnode,
                                const char *propname, const char **val)
 {
-       if (is_of_node(fwnode))
-               return of_property_read_string(to_of_node(fwnode), propname, val);
-       else if (is_acpi_node(fwnode))
-               return acpi_node_prop_read(fwnode, propname, DEV_PROP_STRING,
-                                          val, 1);
+       int ret;
 
-       return pset_prop_read_array(to_pset(fwnode), propname,
-                                   DEV_PROP_STRING, val, 1);
+       ret = __fwnode_property_read_string(fwnode, propname, val);
+       if (ret == -EINVAL && fwnode && fwnode->secondary)
+               ret = __fwnode_property_read_string(fwnode->secondary,
+                                                   propname, val);
+       return ret;
 }
 EXPORT_SYMBOL_GPL(fwnode_property_read_string);
 
@@ -525,6 +653,9 @@ int fwnode_property_match_string(struct fwnode_handle *fwnode,
        if (nval < 0)
                return nval;
 
+       if (nval == 0)
+               return -ENODATA;
+
        values = kcalloc(nval, sizeof(*values), GFP_KERNEL);
        if (!values)
                return -ENOMEM;
@@ -546,6 +677,182 @@ out:
 }
 EXPORT_SYMBOL_GPL(fwnode_property_match_string);
 
+/**
+ * pset_free_set - releases memory allocated for copied property set
+ * @pset: Property set to release
+ *
+ * Function takes previously copied property set and releases all the
+ * memory allocated to it.
+ */
+static void pset_free_set(struct property_set *pset)
+{
+       const struct property_entry *prop;
+       size_t i, nval;
+
+       if (!pset)
+               return;
+
+       for (prop = pset->properties; prop->name; prop++) {
+               if (prop->is_array) {
+                       if (prop->is_string && prop->pointer.str) {
+                               nval = prop->length / sizeof(const char *);
+                               for (i = 0; i < nval; i++)
+                                       kfree(prop->pointer.str[i]);
+                       }
+                       kfree(prop->pointer.raw_data);
+               } else if (prop->is_string) {
+                       kfree(prop->value.str);
+               }
+               kfree(prop->name);
+       }
+
+       kfree(pset->properties);
+       kfree(pset);
+}
+
+static int pset_copy_entry(struct property_entry *dst,
+                          const struct property_entry *src)
+{
+       const char **d, **s;
+       size_t i, nval;
+
+       dst->name = kstrdup(src->name, GFP_KERNEL);
+       if (!dst->name)
+               return -ENOMEM;
+
+       if (src->is_array) {
+               if (!src->length)
+                       return -ENODATA;
+
+               if (src->is_string) {
+                       nval = src->length / sizeof(const char *);
+                       dst->pointer.str = kcalloc(nval, sizeof(const char *),
+                                                  GFP_KERNEL);
+                       if (!dst->pointer.str)
+                               return -ENOMEM;
+
+                       d = dst->pointer.str;
+                       s = src->pointer.str;
+                       for (i = 0; i < nval; i++) {
+                               d[i] = kstrdup(s[i], GFP_KERNEL);
+                               if (!d[i] && s[i])
+                                       return -ENOMEM;
+                       }
+               } else {
+                       dst->pointer.raw_data = kmemdup(src->pointer.raw_data,
+                                                       src->length, GFP_KERNEL);
+                       if (!dst->pointer.raw_data)
+                               return -ENOMEM;
+               }
+       } else if (src->is_string) {
+               dst->value.str = kstrdup(src->value.str, GFP_KERNEL);
+               if (!dst->value.str && src->value.str)
+                       return -ENOMEM;
+       } else {
+               dst->value.raw_data = src->value.raw_data;
+       }
+
+       dst->length = src->length;
+       dst->is_array = src->is_array;
+       dst->is_string = src->is_string;
+
+       return 0;
+}
+
+/**
+ * pset_copy_set - copies property set
+ * @pset: Property set to copy
+ *
+ * This function takes a deep copy of the given property set and returns
+ * pointer to the copy. Call device_free_property_set() to free resources
+ * allocated in this function.
+ *
+ * Return: Pointer to the new property set or error pointer.
+ */
+static struct property_set *pset_copy_set(const struct property_set *pset)
+{
+       const struct property_entry *entry;
+       struct property_set *p;
+       size_t i, n = 0;
+
+       p = kzalloc(sizeof(*p), GFP_KERNEL);
+       if (!p)
+               return ERR_PTR(-ENOMEM);
+
+       while (pset->properties[n].name)
+               n++;
+
+       p->properties = kcalloc(n + 1, sizeof(*entry), GFP_KERNEL);
+       if (!p->properties) {
+               kfree(p);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       for (i = 0; i < n; i++) {
+               int ret = pset_copy_entry(&p->properties[i],
+                                         &pset->properties[i]);
+               if (ret) {
+                       pset_free_set(p);
+                       return ERR_PTR(ret);
+               }
+       }
+
+       return p;
+}
+
+/**
+ * device_remove_property_set - Remove properties from a device object.
+ * @dev: Device whose properties to remove.
+ *
+ * The function removes properties previously associated to the device
+ * secondary firmware node with device_add_property_set(). Memory allocated
+ * to the properties will also be released.
+ */
+void device_remove_property_set(struct device *dev)
+{
+       struct fwnode_handle *fwnode;
+
+       fwnode = dev_fwnode(dev);
+       if (!fwnode)
+               return;
+       /*
+        * Pick either primary or secondary node depending which one holds
+        * the pset. If there is no real firmware node (ACPI/DT) primary
+        * will hold the pset.
+        */
+       if (!is_pset_node(fwnode))
+               fwnode = fwnode->secondary;
+       if (!IS_ERR(fwnode) && is_pset_node(fwnode))
+               pset_free_set(to_pset_node(fwnode));
+       set_secondary_fwnode(dev, NULL);
+}
+EXPORT_SYMBOL_GPL(device_remove_property_set);
+
+/**
+ * device_add_property_set - Add a collection of properties to a device object.
+ * @dev: Device to add properties to.
+ * @pset: Collection of properties to add.
+ *
+ * Associate a collection of device properties represented by @pset with @dev
+ * as its secondary firmware node. The function takes a copy of @pset.
+ */
+int device_add_property_set(struct device *dev, const struct property_set *pset)
+{
+       struct property_set *p;
+
+       if (!pset)
+               return -EINVAL;
+
+       p = pset_copy_set(pset);
+       if (IS_ERR(p))
+               return PTR_ERR(p);
+
+       p->fwnode.type = FWNODE_PDATA;
+       set_secondary_fwnode(dev, &p->fwnode);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(device_add_property_set);
+
 /**
  * device_get_next_child_node - Return the next child node handle for a device
  * @dev: Device to find the next child node for.