summaryrefslogtreecommitdiff
path: root/drivers/base/property.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base/property.c')
-rw-r--r--drivers/base/property.c495
1 files changed, 401 insertions, 94 deletions
diff --git a/drivers/base/property.c b/drivers/base/property.c
index 1325ff225..a163f2c59 100644
--- a/drivers/base/property.c
+++ b/drivers/base/property.c
@@ -19,32 +19,14 @@
#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 && !IS_ERR_OR_NULL(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_ && !IS_ERR_OR_NULL(_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 && !IS_ERR_OR_NULL(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 && !IS_ERR_OR_NULL(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;
@@ -547,6 +678,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.
* @child: Handle to one of the device's child nodes or a null handle.