diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index 83102dae256c..14013f635db6 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -37,18 +37,19 @@ static const u8 ads_uuid[16] = { static bool acpi_enumerate_nondev_subnodes(acpi_handle scope, const union acpi_object *desc, - struct acpi_device_data *data); + struct acpi_device_data *data, + struct fwnode_handle *parent); static bool acpi_extract_properties(const union acpi_object *desc, struct acpi_device_data *data); -static bool acpi_nondev_subnode_ok(acpi_handle scope, - const union acpi_object *link, - struct list_head *list) +static bool acpi_nondev_subnode_extract(const union acpi_object *desc, + acpi_handle handle, + const union acpi_object *link, + struct list_head *list, + struct fwnode_handle *parent) { - struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; struct acpi_data_node *dn; - acpi_handle handle; - acpi_status status; + bool result; dn = kzalloc(sizeof(*dn), GFP_KERNEL); if (!dn) @@ -56,62 +57,127 @@ static bool acpi_nondev_subnode_ok(acpi_handle scope, dn->name = link->package.elements[0].string.pointer; dn->fwnode.type = FWNODE_ACPI_DATA; + dn->fwnode.ops = &acpi_fwnode_ops; + dn->parent = parent; INIT_LIST_HEAD(&dn->data.subnodes); - status = acpi_get_handle(scope, link->package.elements[1].string.pointer, - &handle); - if (ACPI_FAILURE(status)) - goto fail; + result = acpi_extract_properties(desc, &dn->data); - status = acpi_evaluate_object_typed(handle, NULL, NULL, &buf, - ACPI_TYPE_PACKAGE); - if (ACPI_FAILURE(status)) - goto fail; + if (handle) { + acpi_handle scope; + acpi_status status; - if (acpi_extract_properties(buf.pointer, &dn->data)) + /* + * The scope for the subnode object lookup is the one of the + * namespace node (device) containing the object that has + * returned the package. That is, it's the scope of that + * object's parent. + */ + status = acpi_get_parent(handle, &scope); + if (ACPI_SUCCESS(status) + && acpi_enumerate_nondev_subnodes(scope, desc, &dn->data, + &dn->fwnode)) + result = true; + } else if (acpi_enumerate_nondev_subnodes(NULL, desc, &dn->data, + &dn->fwnode)) { + result = true; + } + + if (result) { dn->handle = handle; - - /* - * The scope for the subnode object lookup is the one of the namespace - * node (device) containing the object that has returned the package. - * That is, it's the scope of that object's parent. - */ - status = acpi_get_parent(handle, &scope); - if (ACPI_SUCCESS(status) - && acpi_enumerate_nondev_subnodes(scope, buf.pointer, &dn->data)) - dn->handle = handle; - - if (dn->handle) { - dn->data.pointer = buf.pointer; + dn->data.pointer = desc; list_add_tail(&dn->sibling, list); return true; } - acpi_handle_debug(handle, "Invalid properties/subnodes data, skipping\n"); - - fail: - ACPI_FREE(buf.pointer); kfree(dn); + acpi_handle_debug(handle, "Invalid properties/subnodes data, skipping\n"); return false; } +static bool acpi_nondev_subnode_data_ok(acpi_handle handle, + const union acpi_object *link, + struct list_head *list, + struct fwnode_handle *parent) +{ + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; + acpi_status status; + + status = acpi_evaluate_object_typed(handle, NULL, NULL, &buf, + ACPI_TYPE_PACKAGE); + if (ACPI_FAILURE(status)) + return false; + + if (acpi_nondev_subnode_extract(buf.pointer, handle, link, list, + parent)) + return true; + + ACPI_FREE(buf.pointer); + return false; +} + +static bool acpi_nondev_subnode_ok(acpi_handle scope, + const union acpi_object *link, + struct list_head *list, + struct fwnode_handle *parent) +{ + acpi_handle handle; + acpi_status status; + + if (!scope) + return false; + + status = acpi_get_handle(scope, link->package.elements[1].string.pointer, + &handle); + if (ACPI_FAILURE(status)) + return false; + + return acpi_nondev_subnode_data_ok(handle, link, list, parent); +} + static int acpi_add_nondev_subnodes(acpi_handle scope, const union acpi_object *links, - struct list_head *list) + struct list_head *list, + struct fwnode_handle *parent) { bool ret = false; int i; for (i = 0; i < links->package.count; i++) { - const union acpi_object *link; + const union acpi_object *link, *desc; + acpi_handle handle; + bool result; link = &links->package.elements[i]; - /* Only two elements allowed, both must be strings. */ - if (link->package.count == 2 - && link->package.elements[0].type == ACPI_TYPE_STRING - && link->package.elements[1].type == ACPI_TYPE_STRING - && acpi_nondev_subnode_ok(scope, link, list)) - ret = true; + /* Only two elements allowed. */ + if (link->package.count != 2) + continue; + + /* The first one must be a string. */ + if (link->package.elements[0].type != ACPI_TYPE_STRING) + continue; + + /* The second one may be a string, a reference or a package. */ + switch (link->package.elements[1].type) { + case ACPI_TYPE_STRING: + result = acpi_nondev_subnode_ok(scope, link, list, + parent); + break; + case ACPI_TYPE_LOCAL_REFERENCE: + handle = link->package.elements[1].reference.handle; + result = acpi_nondev_subnode_data_ok(handle, link, list, + parent); + break; + case ACPI_TYPE_PACKAGE: + desc = &link->package.elements[1]; + result = acpi_nondev_subnode_extract(desc, NULL, link, + list, parent); + break; + default: + result = false; + break; + } + ret = ret || result; } return ret; @@ -119,7 +185,8 @@ static int acpi_add_nondev_subnodes(acpi_handle scope, static bool acpi_enumerate_nondev_subnodes(acpi_handle scope, const union acpi_object *desc, - struct acpi_device_data *data) + struct acpi_device_data *data, + struct fwnode_handle *parent) { int i; @@ -141,7 +208,8 @@ static bool acpi_enumerate_nondev_subnodes(acpi_handle scope, if (memcmp(uuid->buffer.pointer, ads_uuid, sizeof(ads_uuid))) continue; - return acpi_add_nondev_subnodes(scope, links, &data->subnodes); + return acpi_add_nondev_subnodes(scope, links, &data->subnodes, + parent); } return false; @@ -292,7 +360,8 @@ void acpi_init_properties(struct acpi_device *adev) if (acpi_of) acpi_init_of_compatible(adev); } - if (acpi_enumerate_nondev_subnodes(adev->handle, buf.pointer, &adev->data)) + if (acpi_enumerate_nondev_subnodes(adev->handle, buf.pointer, + &adev->data, acpi_fwnode_handle(adev))) adev->data.pointer = buf.pointer; if (!adev->data.pointer) { @@ -346,7 +415,7 @@ void acpi_free_properties(struct acpi_device *adev) * * Return: %0 if property with @name has been found (success), * %-EINVAL if the arguments are invalid, - * %-ENODATA if the property doesn't exist, + * %-EINVAL if the property doesn't exist, * %-EPROTO if the property value type doesn't match @type. */ static int acpi_data_get_property(struct acpi_device_data *data, @@ -360,7 +429,7 @@ static int acpi_data_get_property(struct acpi_device_data *data, return -EINVAL; if (!data->pointer || !data->properties) - return -ENODATA; + return -EINVAL; properties = data->properties; for (i = 0; i < properties->package.count; i++) { @@ -375,13 +444,13 @@ static int acpi_data_get_property(struct acpi_device_data *data, if (!strcmp(name, propname->string.pointer)) { if (type != ACPI_TYPE_ANY && propvalue->type != type) return -EPROTO; - else if (obj) + if (obj) *obj = propvalue; return 0; } } - return -ENODATA; + return -EINVAL; } /** @@ -439,7 +508,7 @@ int acpi_node_prop_get(struct fwnode_handle *fwnode, const char *propname, * * Return: %0 if array property (package) with @name has been found (success), * %-EINVAL if the arguments are invalid, - * %-ENODATA if the property doesn't exist, + * %-EINVAL if the property doesn't exist, * %-EPROTO if the property is not a package or the type of its elements * doesn't match @type. */ @@ -468,10 +537,11 @@ static int acpi_data_get_property_array(struct acpi_device_data *data, } /** - * acpi_data_get_property_reference - returns handle to the referenced object - * @data: ACPI device data object containing the property + * __acpi_node_get_property_reference - returns handle to the referenced object + * @fwnode: Firmware node to get the property from * @propname: Name of the property * @index: Index of the reference to return + * @num_args: Maximum number of arguments after each reference * @args: Location to store the returned reference with optional arguments * * Find property with @name, verifify that it is a package containing at least @@ -482,17 +552,40 @@ static int acpi_data_get_property_array(struct acpi_device_data *data, * If there's more than one reference in the property value package, @index is * used to select the one to return. * + * It is possible to leave holes in the property value set like in the + * example below: + * + * Package () { + * "cs-gpios", + * Package () { + * ^GPIO, 19, 0, 0, + * ^GPIO, 20, 0, 0, + * 0, + * ^GPIO, 21, 0, 0, + * } + * } + * + * Calling this function with index %2 return %-ENOENT and with index %3 + * returns the last entry. If the property does not contain any more values + * %-ENODATA is returned. The NULL entry must be single integer and + * preferably contain value %0. + * * Return: %0 on success, negative error code on failure. */ -static int acpi_data_get_property_reference(struct acpi_device_data *data, - const char *propname, size_t index, - struct acpi_reference_args *args) +int __acpi_node_get_property_reference(struct fwnode_handle *fwnode, + const char *propname, size_t index, size_t num_args, + struct acpi_reference_args *args) { const union acpi_object *element, *end; const union acpi_object *obj; + struct acpi_device_data *data; struct acpi_device *device; int ret, idx = 0; + data = acpi_device_data_of_node(fwnode); + if (!data) + return -EINVAL; + ret = acpi_data_get_property(data, propname, ACPI_TYPE_ANY, &obj); if (ret) return ret; @@ -532,59 +625,54 @@ static int acpi_data_get_property_reference(struct acpi_device_data *data, while (element < end) { u32 nargs, i; - if (element->type != ACPI_TYPE_LOCAL_REFERENCE) - return -EPROTO; + if (element->type == ACPI_TYPE_LOCAL_REFERENCE) { + ret = acpi_bus_get_device(element->reference.handle, + &device); + if (ret) + return -ENODEV; - ret = acpi_bus_get_device(element->reference.handle, &device); - if (ret) - return -ENODEV; + nargs = 0; + element++; - element++; - nargs = 0; + /* assume following integer elements are all args */ + for (i = 0; element + i < end && i < num_args; i++) { + int type = element[i].type; - /* assume following integer elements are all args */ - for (i = 0; element + i < end; i++) { - int type = element[i].type; + if (type == ACPI_TYPE_INTEGER) + nargs++; + else if (type == ACPI_TYPE_LOCAL_REFERENCE) + break; + else + return -EPROTO; + } - if (type == ACPI_TYPE_INTEGER) - nargs++; - else if (type == ACPI_TYPE_LOCAL_REFERENCE) - break; - else + if (nargs > MAX_ACPI_REFERENCE_ARGS) return -EPROTO; + + if (idx == index) { + args->adev = device; + args->nargs = nargs; + for (i = 0; i < nargs; i++) + args->args[i] = element[i].integer.value; + + return 0; + } + + element += nargs; + } else if (element->type == ACPI_TYPE_INTEGER) { + if (idx == index) + return -ENOENT; + element++; + } else { + return -EPROTO; } - if (idx++ == index) { - args->adev = device; - args->nargs = nargs; - for (i = 0; i < nargs; i++) - args->args[i] = element[i].integer.value; - - return 0; - } - - element += nargs; + idx++; } - return -EPROTO; + return -ENODATA; } - -/** - * acpi_node_get_property_reference - get a handle to the referenced object. - * @fwnode: Firmware node to get the property from. - * @propname: Name of the property. - * @index: Index of the reference to return. - * @args: Location to store the returned reference with optional arguments. - */ -int acpi_node_get_property_reference(struct fwnode_handle *fwnode, - const char *name, size_t index, - struct acpi_reference_args *args) -{ - struct acpi_device_data *data = acpi_device_data_of_node(fwnode); - - return data ? acpi_data_get_property_reference(data, name, index, args) : -EINVAL; -} -EXPORT_SYMBOL_GPL(acpi_node_get_property_reference); +EXPORT_SYMBOL_GPL(__acpi_node_get_property_reference); static int acpi_data_prop_read_single(struct acpi_device_data *data, const char *propname, @@ -867,3 +955,235 @@ struct fwnode_handle *acpi_get_next_subnode(struct fwnode_handle *fwnode, } return NULL; } + +/** + * acpi_node_get_parent - Return parent fwnode of this fwnode + * @fwnode: Firmware node whose parent to get + * + * Returns parent node of an ACPI device or data firmware node or %NULL if + * not available. + */ +struct fwnode_handle *acpi_node_get_parent(struct fwnode_handle *fwnode) +{ + if (is_acpi_data_node(fwnode)) { + /* All data nodes have parent pointer so just return that */ + return to_acpi_data_node(fwnode)->parent; + } else if (is_acpi_device_node(fwnode)) { + acpi_handle handle, parent_handle; + + handle = to_acpi_device_node(fwnode)->handle; + if (ACPI_SUCCESS(acpi_get_parent(handle, &parent_handle))) { + struct acpi_device *adev; + + if (!acpi_bus_get_device(parent_handle, &adev)) + return acpi_fwnode_handle(adev); + } + } + + return NULL; +} + +/** + * acpi_graph_get_next_endpoint - Get next endpoint ACPI firmware node + * @fwnode: Pointer to the parent firmware node + * @prev: Previous endpoint node or %NULL to get the first + * + * Looks up next endpoint ACPI firmware node below a given @fwnode. Returns + * %NULL if there is no next endpoint, ERR_PTR() in case of error. In case + * of success the next endpoint is returned. + */ +struct fwnode_handle *acpi_graph_get_next_endpoint(struct fwnode_handle *fwnode, + struct fwnode_handle *prev) +{ + struct fwnode_handle *port = NULL; + struct fwnode_handle *endpoint; + + if (!prev) { + do { + port = fwnode_get_next_child_node(fwnode, port); + /* Ports must have port property */ + if (fwnode_property_present(port, "port")) + break; + } while (port); + } else { + port = fwnode_get_parent(prev); + } + + if (!port) + return NULL; + + endpoint = fwnode_get_next_child_node(port, prev); + while (!endpoint) { + port = fwnode_get_next_child_node(fwnode, port); + if (!port) + break; + if (fwnode_property_present(port, "port")) + endpoint = fwnode_get_next_child_node(port, NULL); + } + + if (endpoint) { + /* Endpoints must have "endpoint" property */ + if (!fwnode_property_present(endpoint, "endpoint")) + return ERR_PTR(-EPROTO); + } + + return endpoint; +} + +/** + * acpi_graph_get_child_prop_value - Return a child with a given property value + * @fwnode: device fwnode + * @prop_name: The name of the property to look for + * @val: the desired property value + * + * Return the port node corresponding to a given port number. Returns + * the child node on success, NULL otherwise. + */ +static struct fwnode_handle *acpi_graph_get_child_prop_value( + struct fwnode_handle *fwnode, const char *prop_name, unsigned int val) +{ + struct fwnode_handle *child; + + fwnode_for_each_child_node(fwnode, child) { + u32 nr; + + if (!fwnode_property_read_u32(fwnode, prop_name, &nr)) + continue; + + if (val == nr) + return child; + } + + return NULL; +} + + +/** + * acpi_graph_get_remote_enpoint - Parses and returns remote end of an endpoint + * @fwnode: Endpoint firmware node pointing to a remote device + * @parent: Firmware node of remote port parent is filled here if not %NULL + * @port: Firmware node of remote port is filled here if not %NULL + * @endpoint: Firmware node of remote endpoint is filled here if not %NULL + * + * Function parses remote end of ACPI firmware remote endpoint and fills in + * fields requested by the caller. Returns %0 in case of success and + * negative errno otherwise. + */ +int acpi_graph_get_remote_endpoint(struct fwnode_handle *fwnode, + struct fwnode_handle **parent, + struct fwnode_handle **port, + struct fwnode_handle **endpoint) +{ + unsigned int port_nr, endpoint_nr; + struct acpi_reference_args args; + int ret; + + memset(&args, 0, sizeof(args)); + ret = acpi_node_get_property_reference(fwnode, "remote-endpoint", 0, + &args); + if (ret) + return ret; + + /* + * Always require two arguments with the reference: port and + * endpoint indices. + */ + if (args.nargs != 2) + return -EPROTO; + + fwnode = acpi_fwnode_handle(args.adev); + port_nr = args.args[0]; + endpoint_nr = args.args[1]; + + if (parent) + *parent = fwnode; + + if (!port && !endpoint) + return 0; + + fwnode = acpi_graph_get_child_prop_value(fwnode, "port", port_nr); + if (!fwnode) + return -EPROTO; + + if (port) + *port = fwnode; + + if (!endpoint) + return 0; + + fwnode = acpi_graph_get_child_prop_value(fwnode, "endpoint", + endpoint_nr); + if (!fwnode) + return -EPROTO; + + *endpoint = fwnode; + + return 0; +} + +static bool acpi_fwnode_property_present(struct fwnode_handle *fwnode, + const char *propname) +{ + return !acpi_node_prop_get(fwnode, propname, NULL); +} + +static int acpi_fwnode_property_read_int_array(struct fwnode_handle *fwnode, + const char *propname, + unsigned int elem_size, + void *val, size_t nval) +{ + enum dev_prop_type type; + + switch (elem_size) { + case sizeof(u8): + type = DEV_PROP_U8; + break; + case sizeof(u16): + type = DEV_PROP_U16; + break; + case sizeof(u32): + type = DEV_PROP_U32; + break; + case sizeof(u64): + type = DEV_PROP_U64; + break; + default: + return -ENXIO; + } + + return acpi_node_prop_read(fwnode, propname, type, val, nval); +} + +static int acpi_fwnode_property_read_string_array(struct fwnode_handle *fwnode, + const char *propname, + const char **val, size_t nval) +{ + return acpi_node_prop_read(fwnode, propname, DEV_PROP_STRING, + val, nval); +} + +static struct fwnode_handle * +acpi_fwnode_get_named_child_node(struct fwnode_handle *fwnode, + const char *childname) +{ + struct fwnode_handle *child; + + /* + * Find first matching named child node of this fwnode. + * For ACPI this will be a data only sub-node. + */ + fwnode_for_each_child_node(fwnode, child) + if (acpi_data_node_match(child, childname)) + return child; + + return NULL; +} + +const struct fwnode_operations acpi_fwnode_ops = { + .property_present = acpi_fwnode_property_present, + .property_read_int_array = acpi_fwnode_property_read_int_array, + .property_read_string_array = acpi_fwnode_property_read_string_array, + .get_parent = acpi_node_get_parent, + .get_next_child_node = acpi_get_next_subnode, + .get_named_child_node = acpi_fwnode_get_named_child_node, +}; diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index dcb3d6245ca5..8f51dd2cead7 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1391,6 +1391,7 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, device->handle = handle; device->parent = acpi_bus_get_parent(handle); device->fwnode.type = FWNODE_ACPI; + device->fwnode.ops = &acpi_fwnode_ops; acpi_set_device_status(device, sta); acpi_device_get_busid(device); acpi_set_pnp_ids(handle, &device->pnp, type); diff --git a/drivers/base/property.c b/drivers/base/property.c index 53ec35a0d4b9..37d680238200 100644 --- a/drivers/base/property.c +++ b/drivers/base/property.c @@ -182,6 +182,50 @@ struct fwnode_handle *dev_fwnode(struct device *dev) } EXPORT_SYMBOL_GPL(dev_fwnode); +static bool pset_fwnode_property_present(struct fwnode_handle *fwnode, + const char *propname) +{ + return !!pset_prop_get(to_pset_node(fwnode), propname); +} + +static int pset_fwnode_read_int_array(struct fwnode_handle *fwnode, + const char *propname, + unsigned int elem_size, void *val, + size_t nval) +{ + struct property_set *node = to_pset_node(fwnode); + + if (!val) + return pset_prop_count_elems_of_size(node, propname, elem_size); + + switch (elem_size) { + case sizeof(u8): + return pset_prop_read_u8_array(node, propname, val, nval); + case sizeof(u16): + return pset_prop_read_u16_array(node, propname, val, nval); + case sizeof(u32): + return pset_prop_read_u32_array(node, propname, val, nval); + case sizeof(u64): + return pset_prop_read_u64_array(node, propname, val, nval); + } + + return -ENXIO; +} + +static int pset_fwnode_property_read_string_array(struct fwnode_handle *fwnode, + const char *propname, + const char **val, size_t nval) +{ + return pset_prop_read_string_array(to_pset_node(fwnode), propname, + val, nval); +} + +static const struct fwnode_operations pset_fwnode_ops = { + .property_present = pset_fwnode_property_present, + .property_read_int_array = pset_fwnode_read_int_array, + .property_read_string_array = pset_fwnode_property_read_string_array, +}; + /** * device_property_present - check if a property of a device is present * @dev: Device whose property is being checked @@ -195,18 +239,6 @@ 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 @@ -216,10 +248,11 @@ bool fwnode_property_present(struct fwnode_handle *fwnode, const char *propname) { bool ret; - ret = __fwnode_property_present(fwnode, propname); + ret = fwnode_call_int_op(fwnode, property_present, propname); if (ret == false && !IS_ERR_OR_NULL(fwnode) && !IS_ERR_OR_NULL(fwnode->secondary)) - ret = __fwnode_property_present(fwnode->secondary, propname); + ret = fwnode_call_int_op(fwnode->secondary, property_present, + propname); return ret; } EXPORT_SYMBOL_GPL(fwnode_property_present); @@ -393,42 +426,23 @@ 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)) \ - : of_property_count_elems_of_size((node), (propname), sizeof(type)) +static int fwnode_property_read_int_array(struct fwnode_handle *fwnode, + const char *propname, + unsigned int elem_size, void *val, + size_t nval) +{ + int 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)) + ret = fwnode_call_int_op(fwnode, property_read_int_array, propname, + elem_size, val, nval); + if (ret == -EINVAL && !IS_ERR_OR_NULL(fwnode) && + !IS_ERR_OR_NULL(fwnode->secondary)) + ret = fwnode_call_int_op( + fwnode->secondary, property_read_int_array, propname, + elem_size, val, nval); -#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 && !IS_ERR_OR_NULL(_fwnode_) && \ - !IS_ERR_OR_NULL(_fwnode_->secondary)) \ - _ret_ = FWNODE_PROP_READ(_fwnode_->secondary, _propname_, _type_, \ - _proptype_, _val_, _nval_); \ - _ret_; \ -}) + return ret; +} /** * fwnode_property_read_u8_array - return a u8 array property of firmware node @@ -451,8 +465,8 @@ EXPORT_SYMBOL_GPL(device_property_match_string); int fwnode_property_read_u8_array(struct fwnode_handle *fwnode, const char *propname, u8 *val, size_t nval) { - return FWNODE_PROP_READ_ARRAY(fwnode, propname, u8, DEV_PROP_U8, - val, nval); + return fwnode_property_read_int_array(fwnode, propname, sizeof(u8), + val, nval); } EXPORT_SYMBOL_GPL(fwnode_property_read_u8_array); @@ -477,8 +491,8 @@ EXPORT_SYMBOL_GPL(fwnode_property_read_u8_array); int fwnode_property_read_u16_array(struct fwnode_handle *fwnode, const char *propname, u16 *val, size_t nval) { - return FWNODE_PROP_READ_ARRAY(fwnode, propname, u16, DEV_PROP_U16, - val, nval); + return fwnode_property_read_int_array(fwnode, propname, sizeof(u16), + val, nval); } EXPORT_SYMBOL_GPL(fwnode_property_read_u16_array); @@ -503,8 +517,8 @@ EXPORT_SYMBOL_GPL(fwnode_property_read_u16_array); int fwnode_property_read_u32_array(struct fwnode_handle *fwnode, const char *propname, u32 *val, size_t nval) { - return FWNODE_PROP_READ_ARRAY(fwnode, propname, u32, DEV_PROP_U32, - val, nval); + return fwnode_property_read_int_array(fwnode, propname, sizeof(u32), + val, nval); } EXPORT_SYMBOL_GPL(fwnode_property_read_u32_array); @@ -529,29 +543,11 @@ EXPORT_SYMBOL_GPL(fwnode_property_read_u32_array); int fwnode_property_read_u64_array(struct fwnode_handle *fwnode, const char *propname, u64 *val, size_t nval) { - return FWNODE_PROP_READ_ARRAY(fwnode, propname, u64, DEV_PROP_U64, - val, nval); + return fwnode_property_read_int_array(fwnode, propname, sizeof(u64), + val, nval); } 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 pset_prop_read_string_array(to_pset_node(fwnode), - propname, val, nval); - return -ENXIO; -} - /** * fwnode_property_read_string_array - return string array property of a node * @fwnode: Firmware node to get the property of @@ -576,11 +572,13 @@ int fwnode_property_read_string_array(struct fwnode_handle *fwnode, { int ret; - ret = __fwnode_property_read_string_array(fwnode, propname, val, nval); + ret = fwnode_call_int_op(fwnode, property_read_string_array, propname, + val, nval); if (ret == -EINVAL && !IS_ERR_OR_NULL(fwnode) && !IS_ERR_OR_NULL(fwnode->secondary)) - ret = __fwnode_property_read_string_array(fwnode->secondary, - propname, val, nval); + ret = fwnode_call_int_op(fwnode->secondary, + property_read_string_array, propname, + val, nval); return ret; } EXPORT_SYMBOL_GPL(fwnode_property_read_string_array); @@ -830,6 +828,7 @@ int device_add_property_set(struct device *dev, const struct property_set *pset) return PTR_ERR(p); p->fwnode.type = FWNODE_PDATA; + p->fwnode.ops = &pset_fwnode_ops; set_secondary_fwnode(dev, &p->fwnode); return 0; } @@ -865,19 +864,7 @@ EXPORT_SYMBOL_GPL(fwnode_get_next_parent); */ struct fwnode_handle *fwnode_get_parent(struct fwnode_handle *fwnode) { - struct fwnode_handle *parent = NULL; - - if (is_of_node(fwnode)) { - struct device_node *node; - - node = of_get_parent(to_of_node(fwnode)); - if (node) - parent = &node->fwnode; - } else if (is_acpi_node(fwnode)) { - parent = acpi_node_get_parent(fwnode); - } - - return parent; + return fwnode_call_ptr_op(fwnode, get_parent); } EXPORT_SYMBOL_GPL(fwnode_get_parent); @@ -889,18 +876,7 @@ EXPORT_SYMBOL_GPL(fwnode_get_parent); struct fwnode_handle *fwnode_get_next_child_node(struct fwnode_handle *fwnode, struct fwnode_handle *child) { - if (is_of_node(fwnode)) { - struct device_node *node; - - node = of_get_next_available_child(to_of_node(fwnode), - to_of_node(child)); - if (node) - return &node->fwnode; - } else if (is_acpi_node(fwnode)) { - return acpi_get_next_subnode(fwnode, child); - } - - return NULL; + return fwnode_call_ptr_op(fwnode, get_next_child_node, child); } EXPORT_SYMBOL_GPL(fwnode_get_next_child_node); @@ -932,23 +908,7 @@ EXPORT_SYMBOL_GPL(device_get_next_child_node); struct fwnode_handle *fwnode_get_named_child_node(struct fwnode_handle *fwnode, const char *childname) { - struct fwnode_handle *child; - - /* - * Find first matching named child node of this fwnode. - * For ACPI this will be a data only sub-node. - */ - fwnode_for_each_child_node(fwnode, child) { - if (is_of_node(child)) { - if (!of_node_cmp(to_of_node(child)->name, childname)) - return child; - } else if (is_acpi_data_node(child)) { - if (acpi_data_node_match(child, childname)) - return child; - } - } - - return NULL; + return fwnode_call_ptr_op(fwnode, get_named_child_node, childname); } EXPORT_SYMBOL_GPL(fwnode_get_named_child_node); @@ -970,8 +930,7 @@ EXPORT_SYMBOL_GPL(device_get_named_child_node); */ void fwnode_handle_get(struct fwnode_handle *fwnode) { - if (is_of_node(fwnode)) - of_node_get(to_of_node(fwnode)); + fwnode_call_void_op(fwnode, get); } EXPORT_SYMBOL_GPL(fwnode_handle_get); @@ -985,8 +944,7 @@ EXPORT_SYMBOL_GPL(fwnode_handle_get); */ void fwnode_handle_put(struct fwnode_handle *fwnode) { - if (is_of_node(fwnode)) - of_node_put(to_of_node(fwnode)); + fwnode_call_void_op(fwnode, put); } EXPORT_SYMBOL_GPL(fwnode_handle_put); diff --git a/drivers/of/property.c b/drivers/of/property.c index ada84c549fa1..7c6686eb5637 100644 --- a/drivers/of/property.c +++ b/drivers/of/property.c @@ -764,3 +764,93 @@ struct device_node *of_graph_get_remote_node(const struct device_node *node, return remote; } EXPORT_SYMBOL(of_graph_get_remote_node); + +static void of_fwnode_get(struct fwnode_handle *fwnode) +{ + of_node_get(to_of_node(fwnode)); +} + +static void of_fwnode_put(struct fwnode_handle *fwnode) +{ + of_node_put(to_of_node(fwnode)); +} + +static bool of_fwnode_property_present(struct fwnode_handle *fwnode, + const char *propname) +{ + return of_property_read_bool(to_of_node(fwnode), propname); +} + +static int of_fwnode_property_read_int_array(struct fwnode_handle *fwnode, + const char *propname, + unsigned int elem_size, void *val, + size_t nval) +{ + struct device_node *node = to_of_node(fwnode); + + if (!val) + return of_property_count_elems_of_size(node, propname, + elem_size); + + switch (elem_size) { + case sizeof(u8): + return of_property_read_u8_array(node, propname, val, nval); + case sizeof(u16): + return of_property_read_u16_array(node, propname, val, nval); + case sizeof(u32): + return of_property_read_u32_array(node, propname, val, nval); + case sizeof(u64): + return of_property_read_u64_array(node, propname, val, nval); + } + + return -ENXIO; +} + +static int of_fwnode_property_read_string_array(struct fwnode_handle *fwnode, + const char *propname, + const char **val, size_t nval) +{ + struct device_node *node = to_of_node(fwnode); + + return val ? + of_property_read_string_array(node, propname, val, nval) : + of_property_count_strings(node, propname); +} + +static struct fwnode_handle *of_fwnode_get_parent(struct fwnode_handle *fwnode) +{ + return of_fwnode_handle(of_get_parent(to_of_node(fwnode))); +} + +static struct fwnode_handle * +of_fwnode_get_next_child_node(struct fwnode_handle *fwnode, + struct fwnode_handle *child) +{ + return of_fwnode_handle(of_get_next_available_child(to_of_node(fwnode), + to_of_node(child))); +} + +static struct fwnode_handle * +of_fwnode_get_named_child_node(struct fwnode_handle *fwnode, + const char *childname) +{ + struct device_node *node = to_of_node(fwnode); + struct device_node *child; + + for_each_available_child_of_node(node, child) + if (!of_node_cmp(child->name, childname)) + return of_fwnode_handle(child); + + return NULL; +} + +const struct fwnode_operations of_fwnode_ops = { + .get = of_fwnode_get, + .put = of_fwnode_put, + .property_present = of_fwnode_property_present, + .property_read_int_array = of_fwnode_property_read_int_array, + .property_read_string_array = of_fwnode_property_read_string_array, + .get_parent = of_fwnode_get_parent, + .get_next_child_node = of_fwnode_get_next_child_node, + .get_named_child_node = of_fwnode_get_named_child_node, +}; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index b2ecc72c049a..a3e0856c9600 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -54,6 +54,9 @@ static inline acpi_handle acpi_device_handle(struct acpi_device *adev) acpi_fwnode_handle(adev) : NULL) #define ACPI_HANDLE(dev) acpi_device_handle(ACPI_COMPANION(dev)) + +extern const struct fwnode_operations acpi_fwnode_ops; + /** * ACPI_DEVICE_CLASS - macro used to describe an ACPI device with * the PCI-defined class-code information diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h index 40563c71f4a5..571a48fd4c2d 100644 --- a/include/linux/fwnode.h +++ b/include/linux/fwnode.h @@ -12,6 +12,8 @@ #ifndef _LINUX_FWNODE_H_ #define _LINUX_FWNODE_H_ +#include + enum fwnode_type { FWNODE_INVALID = 0, FWNODE_OF, @@ -21,9 +23,12 @@ enum fwnode_type { FWNODE_IRQCHIP, }; +struct fwnode_operations; + struct fwnode_handle { enum fwnode_type type; struct fwnode_handle *secondary; + const struct fwnode_operations *ops; }; /** @@ -38,4 +43,53 @@ struct fwnode_endpoint { const struct fwnode_handle *local_fwnode; }; +/** + * struct fwnode_operations - Operations for fwnode interface + * @get: Get a reference to an fwnode. + * @put: Put a reference to an fwnode. + * @property_present: Return true if a property is present. + * @property_read_integer_array: Read an array of integer properties. Return + * zero on success, a negative error code + * otherwise. + * @property_read_string_array: Read an array of string properties. Return zero + * on success, a negative error code otherwise. + * @get_parent: Return the parent of an fwnode. + * @get_next_child_node: Return the next child node in an iteration. + * @get_named_child_node: Return a child node with a given name. + */ +struct fwnode_operations { + void (*get)(struct fwnode_handle *fwnode); + void (*put)(struct fwnode_handle *fwnode); + bool (*property_present)(struct fwnode_handle *fwnode, + const char *propname); + int (*property_read_int_array)(struct fwnode_handle *fwnode, + const char *propname, + unsigned int elem_size, void *val, + size_t nval); + int (*property_read_string_array)(struct fwnode_handle *fwnode_handle, + const char *propname, + const char **val, size_t nval); + struct fwnode_handle *(*get_parent)(struct fwnode_handle *fwnode); + struct fwnode_handle * + (*get_next_child_node)(struct fwnode_handle *fwnode, + struct fwnode_handle *child); + struct fwnode_handle * + (*get_named_child_node)(struct fwnode_handle *fwnode, const char *name); +}; + +#define fwnode_has_op(fwnode, op) \ + ((fwnode) && (fwnode)->ops && (fwnode)->ops->op) +#define fwnode_call_int_op(fwnode, op, ...) \ + (fwnode ? (fwnode_has_op(fwnode, op) ? \ + (fwnode)->ops->op(fwnode, ## __VA_ARGS__) : -ENXIO) : \ + -EINVAL) +#define fwnode_call_ptr_op(fwnode, op, ...) \ + (fwnode_has_op(fwnode, op) ? \ + (fwnode)->ops->op(fwnode, ## __VA_ARGS__) : NULL) +#define fwnode_call_void_op(fwnode, op, ...) \ + do { \ + if (fwnode_has_op(fwnode, op)) \ + (fwnode)->ops->op(fwnode, ## __VA_ARGS__); \ + } while (false) + #endif diff --git a/include/linux/of.h b/include/linux/of.h index b7faf77182d1..dedcac4a8777 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -83,10 +83,12 @@ struct of_reconfig_data { /* initialize a node */ extern struct kobj_type of_node_ktype; +extern const struct fwnode_operations of_fwnode_ops; static inline void of_node_init(struct device_node *node) { kobject_init(&node->kobj, &of_node_ktype); node->fwnode.type = FWNODE_OF; + node->fwnode.ops = &of_fwnode_ops; } /* true when node is initialized */