Device properties framework updates for 4.21-rc1
- Introduce "software nodes", analogous to the DT and ACPI firmware nodes except that they can be created by kernel code, in order to complement fwnodes representing real firmware nodes when they are incomplete (for example missing device properties) and to supply the primary fwnode when the firmware lacks hardware description for a device completely, and replace the "property_set" struct fwnode_handle type with software nodes (Heikki Krogerus). - Clean up the just introduced software nodes support and fix a commet in the graph-handling code (Colin Ian King, Marco Felsch). -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCAAGBQJcHMUXAAoJEILEb/54YlRxqJIP/2bDrVQphjXoZWC0/BAOjAMO 1WIaS+LI1VpvnnSxVQllfGHSI4voKiF1fcnKMui/VjCaH/BYhh98RJtCoZ72fHHt WlKlMhNm2nQ5XibZP1Ge9u2NDkfebgOLTHkAam/7dMwxKBzxbK1ytcuggMnL2HRY 9wv6N/y1iLmfFABE0WEV/a3715i5MmBxC5ApIBvjK4tRv+KpYaJ7i03+4qGxpnHY hGvQE0b49hJkBGwVeoEPz/3HpsJ1WYBj4Oi7UyVa8A/GyWaRdwLtUVmidBua4Dah 1eVjpjj6vreuHRwpQbMPkMecSsdon7EzsoyWklAGKPXuA5GKmvoDoLu66DORH93O a3h60vq5SqsVhqJhzJpN7SZLAeGdQymSW0aABpKotKIrNVZMkWTIyxvPSw3PKnF0 3dhgv1QUktKteuCLFfuLqp+vHbPEauOul4iPwElpno6LJ1xzVaf/sNXoHPJwBOs9 LYHUsB8G9L3Y7qvDn4RpR3zYChFE6/J53U8B4P0g+lqV1nxz28ov00VwZJihdpcz wzj+lF53Tzf4gMiaIdjSsrWMtx7lNcAowYsbdb8nyng555HMaW/ROzw1gv/GB5hp 2Iaru6+J4QGbwAaLlOVeGY0TenCJUuF+tF+ufVHujceehjAisDzF+zev0VivRv26 UwRSmhd/g1tPsoeJVhQN =Z3Zj -----END PGP SIGNATURE----- Merge tag 'devprop-4.21-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm Pull device properties framework updates from Rafael Wysocki: "This introduces 'software nodes' that are analogous to the DT and ACPI firmware nodes except that they can be created by drivers themselves and do a couple of assorted cleanups. Specifics: - Introduce "software nodes", analogous to the DT and ACPI firmware nodes except that they can be created by kernel code, in order to complement fwnodes representing real firmware nodes when they are incomplete (for example missing device properties) and to supply the primary fwnode when the firmware lacks hardware description for a device completely, and replace the "property_set" struct fwnode_handle type with software nodes (Heikki Krogerus). - Clean up the just introduced software nodes support and fix a commet in the graph-handling code (Colin Ian King, Marco Felsch)" * tag 'devprop-4.21-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: device property: fix fwnode_graph_get_next_endpoint() documentation drivers: base: swnode: remove need for a temporary string for the node name device property: Remove struct property_set device property: Move device_add_properties() to swnode.c drivers: base: Introducing software nodes to the firmware node framework ACPI / glue: Add acpi_platform_notify() function drivers core: Prepare support for multiple platform notifications driver core: platform: Remove duplicated device_remove_properties() call
This commit is contained in:
commit
d8924c0d76
10
Documentation/ABI/testing/sysfs-devices-software_node
Normal file
10
Documentation/ABI/testing/sysfs-devices-software_node
Normal file
@ -0,0 +1,10 @@
|
||||
What: /sys/devices/.../software_node/
|
||||
Date: January 2019
|
||||
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||
Description:
|
||||
This directory contains the details about the device that are
|
||||
assigned in kernel (i.e. software), as opposed to the
|
||||
firmware_node directory which contains the details that are
|
||||
assigned for the device in firmware. The main attributes in the
|
||||
directory will show the properties the device has, and the
|
||||
relationship it has to some of the other devices.
|
@ -1237,7 +1237,6 @@ static int __init acpi_init(void)
|
||||
acpi_kobj = NULL;
|
||||
}
|
||||
|
||||
init_acpi_device_notify();
|
||||
result = acpi_bus_init();
|
||||
if (result) {
|
||||
disable_acpi();
|
||||
|
@ -296,7 +296,7 @@ int acpi_unbind_one(struct device *dev)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(acpi_unbind_one);
|
||||
|
||||
static int acpi_platform_notify(struct device *dev)
|
||||
static int acpi_device_notify(struct device *dev)
|
||||
{
|
||||
struct acpi_bus_type *type = acpi_get_bus_type(dev);
|
||||
struct acpi_device *adev;
|
||||
@ -343,7 +343,7 @@ static int acpi_platform_notify(struct device *dev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int acpi_platform_notify_remove(struct device *dev)
|
||||
static int acpi_device_notify_remove(struct device *dev)
|
||||
{
|
||||
struct acpi_device *adev = ACPI_COMPANION(dev);
|
||||
struct acpi_bus_type *type;
|
||||
@ -361,12 +361,17 @@ static int acpi_platform_notify_remove(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __init init_acpi_device_notify(void)
|
||||
int acpi_platform_notify(struct device *dev, enum kobject_action action)
|
||||
{
|
||||
if (platform_notify || platform_notify_remove) {
|
||||
printk(KERN_ERR PREFIX "Can't use platform_notify\n");
|
||||
return;
|
||||
switch (action) {
|
||||
case KOBJ_ADD:
|
||||
acpi_device_notify(dev);
|
||||
break;
|
||||
case KOBJ_REMOVE:
|
||||
acpi_device_notify_remove(dev);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
platform_notify = acpi_platform_notify;
|
||||
platform_notify_remove = acpi_platform_notify_remove;
|
||||
return 0;
|
||||
}
|
||||
|
@ -23,7 +23,6 @@
|
||||
int early_acpi_osi_init(void);
|
||||
int acpi_osi_init(void);
|
||||
acpi_status acpi_os_initialize1(void);
|
||||
void init_acpi_device_notify(void);
|
||||
int acpi_scan_init(void);
|
||||
#ifdef CONFIG_PCI
|
||||
void acpi_pci_root_init(void);
|
||||
|
@ -6,7 +6,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \
|
||||
cpu.o firmware.o init.o map.o devres.o \
|
||||
attribute_container.o transport_class.o \
|
||||
topology.o container.o property.o cacheinfo.o \
|
||||
devcon.o
|
||||
devcon.o swnode.o
|
||||
obj-$(CONFIG_DEVTMPFS) += devtmpfs.o
|
||||
obj-y += power/
|
||||
obj-$(CONFIG_ISA_BUS_API) += isa.o
|
||||
|
@ -8,6 +8,7 @@
|
||||
* Copyright (c) 2006 Novell, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fwnode.h>
|
||||
@ -728,6 +729,26 @@ static inline int device_is_not_partition(struct device *dev)
|
||||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
device_platform_notify(struct device *dev, enum kobject_action action)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = acpi_platform_notify(dev, action);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = software_node_notify(dev, action);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (platform_notify && action == KOBJ_ADD)
|
||||
platform_notify(dev);
|
||||
else if (platform_notify_remove && action == KOBJ_REMOVE)
|
||||
platform_notify_remove(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* dev_driver_string - Return a device's driver name, if at all possible
|
||||
* @dev: struct device to get the name of
|
||||
@ -1883,8 +1904,9 @@ int device_add(struct device *dev)
|
||||
}
|
||||
|
||||
/* notify platform of device entry */
|
||||
if (platform_notify)
|
||||
platform_notify(dev);
|
||||
error = device_platform_notify(dev, KOBJ_ADD);
|
||||
if (error)
|
||||
goto platform_error;
|
||||
|
||||
error = device_create_file(dev, &dev_attr_uevent);
|
||||
if (error)
|
||||
@ -1960,6 +1982,8 @@ done:
|
||||
SymlinkError:
|
||||
device_remove_file(dev, &dev_attr_uevent);
|
||||
attrError:
|
||||
device_platform_notify(dev, KOBJ_REMOVE);
|
||||
platform_error:
|
||||
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
|
||||
glue_dir = get_glue_dir(dev);
|
||||
kobject_del(&dev->kobj);
|
||||
@ -2077,14 +2101,10 @@ void device_del(struct device *dev)
|
||||
bus_remove_device(dev);
|
||||
device_pm_remove(dev);
|
||||
driver_deferred_probe_del(dev);
|
||||
device_platform_notify(dev, KOBJ_REMOVE);
|
||||
device_remove_properties(dev);
|
||||
device_links_purge(dev);
|
||||
|
||||
/* Notify the platform of the removal, in case they
|
||||
* need to do anything...
|
||||
*/
|
||||
if (platform_notify_remove)
|
||||
platform_notify_remove(dev);
|
||||
if (dev->bus)
|
||||
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
|
||||
BUS_NOTIFY_REMOVED_DEVICE, dev);
|
||||
|
@ -448,7 +448,6 @@ void platform_device_del(struct platform_device *pdev)
|
||||
int i;
|
||||
|
||||
if (pdev) {
|
||||
device_remove_properties(&pdev->dev);
|
||||
device_del(&pdev->dev);
|
||||
|
||||
if (pdev->id_auto) {
|
||||
|
@ -18,236 +18,6 @@
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/phy.h>
|
||||
|
||||
struct property_set {
|
||||
struct device *dev;
|
||||
struct fwnode_handle fwnode;
|
||||
const struct property_entry *properties;
|
||||
};
|
||||
|
||||
static const struct fwnode_operations pset_fwnode_ops;
|
||||
|
||||
static inline bool is_pset_node(const struct fwnode_handle *fwnode)
|
||||
{
|
||||
return !IS_ERR_OR_NULL(fwnode) && fwnode->ops == &pset_fwnode_ops;
|
||||
}
|
||||
|
||||
#define to_pset_node(__fwnode) \
|
||||
({ \
|
||||
typeof(__fwnode) __to_pset_node_fwnode = __fwnode; \
|
||||
\
|
||||
is_pset_node(__to_pset_node_fwnode) ? \
|
||||
container_of(__to_pset_node_fwnode, \
|
||||
struct property_set, fwnode) : \
|
||||
NULL; \
|
||||
})
|
||||
|
||||
static const struct property_entry *
|
||||
pset_prop_get(const struct property_set *pset, const char *name)
|
||||
{
|
||||
const struct property_entry *prop;
|
||||
|
||||
if (!pset || !pset->properties)
|
||||
return NULL;
|
||||
|
||||
for (prop = pset->properties; prop->name; prop++)
|
||||
if (!strcmp(name, prop->name))
|
||||
return prop;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const void *property_get_pointer(const struct property_entry *prop)
|
||||
{
|
||||
switch (prop->type) {
|
||||
case DEV_PROP_U8:
|
||||
if (prop->is_array)
|
||||
return prop->pointer.u8_data;
|
||||
return &prop->value.u8_data;
|
||||
case DEV_PROP_U16:
|
||||
if (prop->is_array)
|
||||
return prop->pointer.u16_data;
|
||||
return &prop->value.u16_data;
|
||||
case DEV_PROP_U32:
|
||||
if (prop->is_array)
|
||||
return prop->pointer.u32_data;
|
||||
return &prop->value.u32_data;
|
||||
case DEV_PROP_U64:
|
||||
if (prop->is_array)
|
||||
return prop->pointer.u64_data;
|
||||
return &prop->value.u64_data;
|
||||
case DEV_PROP_STRING:
|
||||
if (prop->is_array)
|
||||
return prop->pointer.str;
|
||||
return &prop->value.str;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void property_set_pointer(struct property_entry *prop, const void *pointer)
|
||||
{
|
||||
switch (prop->type) {
|
||||
case DEV_PROP_U8:
|
||||
if (prop->is_array)
|
||||
prop->pointer.u8_data = pointer;
|
||||
else
|
||||
prop->value.u8_data = *((u8 *)pointer);
|
||||
break;
|
||||
case DEV_PROP_U16:
|
||||
if (prop->is_array)
|
||||
prop->pointer.u16_data = pointer;
|
||||
else
|
||||
prop->value.u16_data = *((u16 *)pointer);
|
||||
break;
|
||||
case DEV_PROP_U32:
|
||||
if (prop->is_array)
|
||||
prop->pointer.u32_data = pointer;
|
||||
else
|
||||
prop->value.u32_data = *((u32 *)pointer);
|
||||
break;
|
||||
case DEV_PROP_U64:
|
||||
if (prop->is_array)
|
||||
prop->pointer.u64_data = pointer;
|
||||
else
|
||||
prop->value.u64_data = *((u64 *)pointer);
|
||||
break;
|
||||
case DEV_PROP_STRING:
|
||||
if (prop->is_array)
|
||||
prop->pointer.str = pointer;
|
||||
else
|
||||
prop->value.str = pointer;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const void *pset_prop_find(const struct property_set *pset,
|
||||
const char *propname, size_t length)
|
||||
{
|
||||
const struct property_entry *prop;
|
||||
const void *pointer;
|
||||
|
||||
prop = pset_prop_get(pset, propname);
|
||||
if (!prop)
|
||||
return ERR_PTR(-EINVAL);
|
||||
pointer = property_get_pointer(prop);
|
||||
if (!pointer)
|
||||
return ERR_PTR(-ENODATA);
|
||||
if (length > prop->length)
|
||||
return ERR_PTR(-EOVERFLOW);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
static int pset_prop_read_u8_array(const struct property_set *pset,
|
||||
const char *propname,
|
||||
u8 *values, size_t nval)
|
||||
{
|
||||
const 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(const struct property_set *pset,
|
||||
const char *propname,
|
||||
u16 *values, size_t nval)
|
||||
{
|
||||
const 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(const struct property_set *pset,
|
||||
const char *propname,
|
||||
u32 *values, size_t nval)
|
||||
{
|
||||
const 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(const struct property_set *pset,
|
||||
const char *propname,
|
||||
u64 *values, size_t nval)
|
||||
{
|
||||
const 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(const struct property_set *pset,
|
||||
const char *propname, size_t length)
|
||||
{
|
||||
const 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(const struct property_set *pset,
|
||||
const char *propname,
|
||||
const char **strings, size_t nval)
|
||||
{
|
||||
const struct property_entry *prop;
|
||||
const void *pointer;
|
||||
size_t array_len, length;
|
||||
|
||||
/* Find out the array length. */
|
||||
prop = pset_prop_get(pset, propname);
|
||||
if (!prop)
|
||||
return -EINVAL;
|
||||
|
||||
if (!prop->is_array)
|
||||
/* The array length for a non-array string property is 1. */
|
||||
array_len = 1;
|
||||
else
|
||||
/* Find the length of an array. */
|
||||
array_len = pset_prop_count_elems_of_size(pset, propname,
|
||||
sizeof(const char *));
|
||||
|
||||
/* Return how many there are if strings is NULL. */
|
||||
if (!strings)
|
||||
return array_len;
|
||||
|
||||
array_len = min(nval, array_len);
|
||||
length = array_len * sizeof(*strings);
|
||||
|
||||
pointer = pset_prop_find(pset, propname, length);
|
||||
if (IS_ERR(pointer))
|
||||
return PTR_ERR(pointer);
|
||||
|
||||
memcpy(strings, pointer, length);
|
||||
|
||||
return array_len;
|
||||
}
|
||||
|
||||
struct fwnode_handle *dev_fwnode(struct device *dev)
|
||||
{
|
||||
return IS_ENABLED(CONFIG_OF) && dev->of_node ?
|
||||
@ -255,51 +25,6 @@ struct fwnode_handle *dev_fwnode(struct device *dev)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dev_fwnode);
|
||||
|
||||
static bool pset_fwnode_property_present(const struct fwnode_handle *fwnode,
|
||||
const char *propname)
|
||||
{
|
||||
return !!pset_prop_get(to_pset_node(fwnode), propname);
|
||||
}
|
||||
|
||||
static int pset_fwnode_read_int_array(const struct fwnode_handle *fwnode,
|
||||
const char *propname,
|
||||
unsigned int elem_size, void *val,
|
||||
size_t nval)
|
||||
{
|
||||
const 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(const 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
|
||||
@ -759,223 +484,25 @@ int fwnode_property_get_reference_args(const struct fwnode_handle *fwnode,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fwnode_property_get_reference_args);
|
||||
|
||||
static void property_entry_free_data(const struct property_entry *p)
|
||||
{
|
||||
const void *pointer = property_get_pointer(p);
|
||||
size_t i, nval;
|
||||
|
||||
if (p->is_array) {
|
||||
if (p->type == DEV_PROP_STRING && p->pointer.str) {
|
||||
nval = p->length / sizeof(const char *);
|
||||
for (i = 0; i < nval; i++)
|
||||
kfree(p->pointer.str[i]);
|
||||
}
|
||||
kfree(pointer);
|
||||
} else if (p->type == DEV_PROP_STRING) {
|
||||
kfree(p->value.str);
|
||||
}
|
||||
kfree(p->name);
|
||||
}
|
||||
|
||||
static int property_copy_string_array(struct property_entry *dst,
|
||||
const struct property_entry *src)
|
||||
{
|
||||
const char **d;
|
||||
size_t nval = src->length / sizeof(*d);
|
||||
int i;
|
||||
|
||||
d = kcalloc(nval, sizeof(*d), GFP_KERNEL);
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < nval; i++) {
|
||||
d[i] = kstrdup(src->pointer.str[i], GFP_KERNEL);
|
||||
if (!d[i] && src->pointer.str[i]) {
|
||||
while (--i >= 0)
|
||||
kfree(d[i]);
|
||||
kfree(d);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
dst->pointer.str = d;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int property_entry_copy_data(struct property_entry *dst,
|
||||
const struct property_entry *src)
|
||||
{
|
||||
const void *pointer = property_get_pointer(src);
|
||||
const void *new;
|
||||
int error;
|
||||
|
||||
if (src->is_array) {
|
||||
if (!src->length)
|
||||
return -ENODATA;
|
||||
|
||||
if (src->type == DEV_PROP_STRING) {
|
||||
error = property_copy_string_array(dst, src);
|
||||
if (error)
|
||||
return error;
|
||||
new = dst->pointer.str;
|
||||
} else {
|
||||
new = kmemdup(pointer, src->length, GFP_KERNEL);
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
}
|
||||
} else if (src->type == DEV_PROP_STRING) {
|
||||
new = kstrdup(src->value.str, GFP_KERNEL);
|
||||
if (!new && src->value.str)
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
new = pointer;
|
||||
}
|
||||
|
||||
dst->length = src->length;
|
||||
dst->is_array = src->is_array;
|
||||
dst->type = src->type;
|
||||
|
||||
property_set_pointer(dst, new);
|
||||
|
||||
dst->name = kstrdup(src->name, GFP_KERNEL);
|
||||
if (!dst->name)
|
||||
goto out_free_data;
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_data:
|
||||
property_entry_free_data(dst);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/**
|
||||
* property_entries_dup - duplicate array of properties
|
||||
* @properties: array of properties to copy
|
||||
*
|
||||
* This function creates a deep copy of the given NULL-terminated array
|
||||
* of property entries.
|
||||
*/
|
||||
struct property_entry *
|
||||
property_entries_dup(const struct property_entry *properties)
|
||||
{
|
||||
struct property_entry *p;
|
||||
int i, n = 0;
|
||||
|
||||
while (properties[n].name)
|
||||
n++;
|
||||
|
||||
p = kcalloc(n + 1, sizeof(*p), GFP_KERNEL);
|
||||
if (!p)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
int ret = property_entry_copy_data(&p[i], &properties[i]);
|
||||
if (ret) {
|
||||
while (--i >= 0)
|
||||
property_entry_free_data(&p[i]);
|
||||
kfree(p);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(property_entries_dup);
|
||||
|
||||
/**
|
||||
* property_entries_free - free previously allocated array of properties
|
||||
* @properties: array of properties to destroy
|
||||
*
|
||||
* This function frees given NULL-terminated array of property entries,
|
||||
* along with their data.
|
||||
*/
|
||||
void property_entries_free(const struct property_entry *properties)
|
||||
{
|
||||
const struct property_entry *p;
|
||||
|
||||
for (p = properties; p->name; p++)
|
||||
property_entry_free_data(p);
|
||||
|
||||
kfree(properties);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(property_entries_free);
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
if (!pset)
|
||||
return;
|
||||
|
||||
property_entries_free(pset->properties);
|
||||
kfree(pset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
struct property_entry *properties;
|
||||
struct property_set *p;
|
||||
|
||||
p = kzalloc(sizeof(*p), GFP_KERNEL);
|
||||
if (!p)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
properties = property_entries_dup(pset->properties);
|
||||
if (IS_ERR(properties)) {
|
||||
kfree(p);
|
||||
return ERR_CAST(properties);
|
||||
}
|
||||
|
||||
p->properties = properties;
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* device_remove_properties - 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_properties(). Memory allocated
|
||||
* to the properties will also be released.
|
||||
* firmware node with device_add_properties(). Memory allocated to the
|
||||
* properties will also be released.
|
||||
*/
|
||||
void device_remove_properties(struct device *dev)
|
||||
{
|
||||
struct fwnode_handle *fwnode;
|
||||
struct property_set *pset;
|
||||
struct fwnode_handle *fwnode = dev_fwnode(dev);
|
||||
|
||||
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.
|
||||
*/
|
||||
pset = to_pset_node(fwnode);
|
||||
if (pset) {
|
||||
set_primary_fwnode(dev, NULL);
|
||||
} else {
|
||||
pset = to_pset_node(fwnode->secondary);
|
||||
if (pset && dev == pset->dev)
|
||||
set_secondary_fwnode(dev, NULL);
|
||||
|
||||
if (is_software_node(fwnode->secondary)) {
|
||||
fwnode_remove_software_node(fwnode->secondary);
|
||||
set_secondary_fwnode(dev, NULL);
|
||||
}
|
||||
if (pset && dev == pset->dev)
|
||||
pset_free_set(pset);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(device_remove_properties);
|
||||
|
||||
@ -985,26 +512,22 @@ EXPORT_SYMBOL_GPL(device_remove_properties);
|
||||
* @properties: Collection of properties to add.
|
||||
*
|
||||
* Associate a collection of device properties represented by @properties with
|
||||
* @dev as its secondary firmware node. The function takes a copy of
|
||||
* @properties.
|
||||
* @dev. The function takes a copy of @properties.
|
||||
*
|
||||
* WARNING: The callers should not use this function if it is known that there
|
||||
* is no real firmware node associated with @dev! In that case the callers
|
||||
* should create a software node and assign it to @dev directly.
|
||||
*/
|
||||
int device_add_properties(struct device *dev,
|
||||
const struct property_entry *properties)
|
||||
{
|
||||
struct property_set *p, pset;
|
||||
struct fwnode_handle *fwnode;
|
||||
|
||||
if (!properties)
|
||||
return -EINVAL;
|
||||
fwnode = fwnode_create_software_node(properties, NULL);
|
||||
if (IS_ERR(fwnode))
|
||||
return PTR_ERR(fwnode);
|
||||
|
||||
pset.properties = properties;
|
||||
|
||||
p = pset_copy_set(&pset);
|
||||
if (IS_ERR(p))
|
||||
return PTR_ERR(p);
|
||||
|
||||
p->fwnode.ops = &pset_fwnode_ops;
|
||||
set_secondary_fwnode(dev, &p->fwnode);
|
||||
p->dev = dev;
|
||||
set_secondary_fwnode(dev, fwnode);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(device_add_properties);
|
||||
@ -1341,7 +864,7 @@ int fwnode_irq_get(struct fwnode_handle *fwnode, unsigned int index)
|
||||
EXPORT_SYMBOL(fwnode_irq_get);
|
||||
|
||||
/**
|
||||
* device_graph_get_next_endpoint - Get next endpoint firmware node
|
||||
* fwnode_graph_get_next_endpoint - Get next endpoint firmware node
|
||||
* @fwnode: Pointer to the parent firmware node
|
||||
* @prev: Previous endpoint node or %NULL to get the first
|
||||
*
|
||||
|
675
drivers/base/swnode.c
Normal file
675
drivers/base/swnode.c
Normal file
@ -0,0 +1,675 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Software nodes for the firmware node framework.
|
||||
*
|
||||
* Copyright (C) 2018, Intel Corporation
|
||||
* Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct software_node {
|
||||
int id;
|
||||
struct kobject kobj;
|
||||
struct fwnode_handle fwnode;
|
||||
|
||||
/* hierarchy */
|
||||
struct ida child_ids;
|
||||
struct list_head entry;
|
||||
struct list_head children;
|
||||
struct software_node *parent;
|
||||
|
||||
/* properties */
|
||||
const struct property_entry *properties;
|
||||
};
|
||||
|
||||
static DEFINE_IDA(swnode_root_ids);
|
||||
static struct kset *swnode_kset;
|
||||
|
||||
#define kobj_to_swnode(_kobj_) container_of(_kobj_, struct software_node, kobj)
|
||||
|
||||
static const struct fwnode_operations software_node_ops;
|
||||
|
||||
bool is_software_node(const struct fwnode_handle *fwnode)
|
||||
{
|
||||
return !IS_ERR_OR_NULL(fwnode) && fwnode->ops == &software_node_ops;
|
||||
}
|
||||
|
||||
#define to_software_node(__fwnode) \
|
||||
({ \
|
||||
typeof(__fwnode) __to_software_node_fwnode = __fwnode; \
|
||||
\
|
||||
is_software_node(__to_software_node_fwnode) ? \
|
||||
container_of(__to_software_node_fwnode, \
|
||||
struct software_node, fwnode) : \
|
||||
NULL; \
|
||||
})
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* property_entry processing */
|
||||
|
||||
static const struct property_entry *
|
||||
property_entry_get(const struct property_entry *prop, const char *name)
|
||||
{
|
||||
if (!prop)
|
||||
return NULL;
|
||||
|
||||
for (; prop->name; prop++)
|
||||
if (!strcmp(name, prop->name))
|
||||
return prop;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
property_set_pointer(struct property_entry *prop, const void *pointer)
|
||||
{
|
||||
switch (prop->type) {
|
||||
case DEV_PROP_U8:
|
||||
if (prop->is_array)
|
||||
prop->pointer.u8_data = pointer;
|
||||
else
|
||||
prop->value.u8_data = *((u8 *)pointer);
|
||||
break;
|
||||
case DEV_PROP_U16:
|
||||
if (prop->is_array)
|
||||
prop->pointer.u16_data = pointer;
|
||||
else
|
||||
prop->value.u16_data = *((u16 *)pointer);
|
||||
break;
|
||||
case DEV_PROP_U32:
|
||||
if (prop->is_array)
|
||||
prop->pointer.u32_data = pointer;
|
||||
else
|
||||
prop->value.u32_data = *((u32 *)pointer);
|
||||
break;
|
||||
case DEV_PROP_U64:
|
||||
if (prop->is_array)
|
||||
prop->pointer.u64_data = pointer;
|
||||
else
|
||||
prop->value.u64_data = *((u64 *)pointer);
|
||||
break;
|
||||
case DEV_PROP_STRING:
|
||||
if (prop->is_array)
|
||||
prop->pointer.str = pointer;
|
||||
else
|
||||
prop->value.str = pointer;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const void *property_get_pointer(const struct property_entry *prop)
|
||||
{
|
||||
switch (prop->type) {
|
||||
case DEV_PROP_U8:
|
||||
if (prop->is_array)
|
||||
return prop->pointer.u8_data;
|
||||
return &prop->value.u8_data;
|
||||
case DEV_PROP_U16:
|
||||
if (prop->is_array)
|
||||
return prop->pointer.u16_data;
|
||||
return &prop->value.u16_data;
|
||||
case DEV_PROP_U32:
|
||||
if (prop->is_array)
|
||||
return prop->pointer.u32_data;
|
||||
return &prop->value.u32_data;
|
||||
case DEV_PROP_U64:
|
||||
if (prop->is_array)
|
||||
return prop->pointer.u64_data;
|
||||
return &prop->value.u64_data;
|
||||
case DEV_PROP_STRING:
|
||||
if (prop->is_array)
|
||||
return prop->pointer.str;
|
||||
return &prop->value.str;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const void *property_entry_find(const struct property_entry *props,
|
||||
const char *propname, size_t length)
|
||||
{
|
||||
const struct property_entry *prop;
|
||||
const void *pointer;
|
||||
|
||||
prop = property_entry_get(props, propname);
|
||||
if (!prop)
|
||||
return ERR_PTR(-EINVAL);
|
||||
pointer = property_get_pointer(prop);
|
||||
if (!pointer)
|
||||
return ERR_PTR(-ENODATA);
|
||||
if (length > prop->length)
|
||||
return ERR_PTR(-EOVERFLOW);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
static int property_entry_read_u8_array(const struct property_entry *props,
|
||||
const char *propname,
|
||||
u8 *values, size_t nval)
|
||||
{
|
||||
const void *pointer;
|
||||
size_t length = nval * sizeof(*values);
|
||||
|
||||
pointer = property_entry_find(props, propname, length);
|
||||
if (IS_ERR(pointer))
|
||||
return PTR_ERR(pointer);
|
||||
|
||||
memcpy(values, pointer, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int property_entry_read_u16_array(const struct property_entry *props,
|
||||
const char *propname,
|
||||
u16 *values, size_t nval)
|
||||
{
|
||||
const void *pointer;
|
||||
size_t length = nval * sizeof(*values);
|
||||
|
||||
pointer = property_entry_find(props, propname, length);
|
||||
if (IS_ERR(pointer))
|
||||
return PTR_ERR(pointer);
|
||||
|
||||
memcpy(values, pointer, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int property_entry_read_u32_array(const struct property_entry *props,
|
||||
const char *propname,
|
||||
u32 *values, size_t nval)
|
||||
{
|
||||
const void *pointer;
|
||||
size_t length = nval * sizeof(*values);
|
||||
|
||||
pointer = property_entry_find(props, propname, length);
|
||||
if (IS_ERR(pointer))
|
||||
return PTR_ERR(pointer);
|
||||
|
||||
memcpy(values, pointer, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int property_entry_read_u64_array(const struct property_entry *props,
|
||||
const char *propname,
|
||||
u64 *values, size_t nval)
|
||||
{
|
||||
const void *pointer;
|
||||
size_t length = nval * sizeof(*values);
|
||||
|
||||
pointer = property_entry_find(props, propname, length);
|
||||
if (IS_ERR(pointer))
|
||||
return PTR_ERR(pointer);
|
||||
|
||||
memcpy(values, pointer, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
property_entry_count_elems_of_size(const struct property_entry *props,
|
||||
const char *propname, size_t length)
|
||||
{
|
||||
const struct property_entry *prop;
|
||||
|
||||
prop = property_entry_get(props, propname);
|
||||
if (!prop)
|
||||
return -EINVAL;
|
||||
|
||||
return prop->length / length;
|
||||
}
|
||||
|
||||
static int property_entry_read_int_array(const struct property_entry *props,
|
||||
const char *name,
|
||||
unsigned int elem_size, void *val,
|
||||
size_t nval)
|
||||
{
|
||||
if (!val)
|
||||
return property_entry_count_elems_of_size(props, name,
|
||||
elem_size);
|
||||
switch (elem_size) {
|
||||
case sizeof(u8):
|
||||
return property_entry_read_u8_array(props, name, val, nval);
|
||||
case sizeof(u16):
|
||||
return property_entry_read_u16_array(props, name, val, nval);
|
||||
case sizeof(u32):
|
||||
return property_entry_read_u32_array(props, name, val, nval);
|
||||
case sizeof(u64):
|
||||
return property_entry_read_u64_array(props, name, val, nval);
|
||||
}
|
||||
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static int property_entry_read_string_array(const struct property_entry *props,
|
||||
const char *propname,
|
||||
const char **strings, size_t nval)
|
||||
{
|
||||
const struct property_entry *prop;
|
||||
const void *pointer;
|
||||
size_t array_len, length;
|
||||
|
||||
/* Find out the array length. */
|
||||
prop = property_entry_get(props, propname);
|
||||
if (!prop)
|
||||
return -EINVAL;
|
||||
|
||||
if (prop->is_array)
|
||||
/* Find the length of an array. */
|
||||
array_len = property_entry_count_elems_of_size(props, propname,
|
||||
sizeof(const char *));
|
||||
else
|
||||
/* The array length for a non-array string property is 1. */
|
||||
array_len = 1;
|
||||
|
||||
/* Return how many there are if strings is NULL. */
|
||||
if (!strings)
|
||||
return array_len;
|
||||
|
||||
array_len = min(nval, array_len);
|
||||
length = array_len * sizeof(*strings);
|
||||
|
||||
pointer = property_entry_find(props, propname, length);
|
||||
if (IS_ERR(pointer))
|
||||
return PTR_ERR(pointer);
|
||||
|
||||
memcpy(strings, pointer, length);
|
||||
|
||||
return array_len;
|
||||
}
|
||||
|
||||
static void property_entry_free_data(const struct property_entry *p)
|
||||
{
|
||||
const void *pointer = property_get_pointer(p);
|
||||
size_t i, nval;
|
||||
|
||||
if (p->is_array) {
|
||||
if (p->type == DEV_PROP_STRING && p->pointer.str) {
|
||||
nval = p->length / sizeof(const char *);
|
||||
for (i = 0; i < nval; i++)
|
||||
kfree(p->pointer.str[i]);
|
||||
}
|
||||
kfree(pointer);
|
||||
} else if (p->type == DEV_PROP_STRING) {
|
||||
kfree(p->value.str);
|
||||
}
|
||||
kfree(p->name);
|
||||
}
|
||||
|
||||
static int property_copy_string_array(struct property_entry *dst,
|
||||
const struct property_entry *src)
|
||||
{
|
||||
const char **d;
|
||||
size_t nval = src->length / sizeof(*d);
|
||||
int i;
|
||||
|
||||
d = kcalloc(nval, sizeof(*d), GFP_KERNEL);
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < nval; i++) {
|
||||
d[i] = kstrdup(src->pointer.str[i], GFP_KERNEL);
|
||||
if (!d[i] && src->pointer.str[i]) {
|
||||
while (--i >= 0)
|
||||
kfree(d[i]);
|
||||
kfree(d);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
dst->pointer.str = d;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int property_entry_copy_data(struct property_entry *dst,
|
||||
const struct property_entry *src)
|
||||
{
|
||||
const void *pointer = property_get_pointer(src);
|
||||
const void *new;
|
||||
int error;
|
||||
|
||||
if (src->is_array) {
|
||||
if (!src->length)
|
||||
return -ENODATA;
|
||||
|
||||
if (src->type == DEV_PROP_STRING) {
|
||||
error = property_copy_string_array(dst, src);
|
||||
if (error)
|
||||
return error;
|
||||
new = dst->pointer.str;
|
||||
} else {
|
||||
new = kmemdup(pointer, src->length, GFP_KERNEL);
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
}
|
||||
} else if (src->type == DEV_PROP_STRING) {
|
||||
new = kstrdup(src->value.str, GFP_KERNEL);
|
||||
if (!new && src->value.str)
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
new = pointer;
|
||||
}
|
||||
|
||||
dst->length = src->length;
|
||||
dst->is_array = src->is_array;
|
||||
dst->type = src->type;
|
||||
|
||||
property_set_pointer(dst, new);
|
||||
|
||||
dst->name = kstrdup(src->name, GFP_KERNEL);
|
||||
if (!dst->name)
|
||||
goto out_free_data;
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_data:
|
||||
property_entry_free_data(dst);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/**
|
||||
* property_entries_dup - duplicate array of properties
|
||||
* @properties: array of properties to copy
|
||||
*
|
||||
* This function creates a deep copy of the given NULL-terminated array
|
||||
* of property entries.
|
||||
*/
|
||||
struct property_entry *
|
||||
property_entries_dup(const struct property_entry *properties)
|
||||
{
|
||||
struct property_entry *p;
|
||||
int i, n = 0;
|
||||
int ret;
|
||||
|
||||
while (properties[n].name)
|
||||
n++;
|
||||
|
||||
p = kcalloc(n + 1, sizeof(*p), GFP_KERNEL);
|
||||
if (!p)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
ret = property_entry_copy_data(&p[i], &properties[i]);
|
||||
if (ret) {
|
||||
while (--i >= 0)
|
||||
property_entry_free_data(&p[i]);
|
||||
kfree(p);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(property_entries_dup);
|
||||
|
||||
/**
|
||||
* property_entries_free - free previously allocated array of properties
|
||||
* @properties: array of properties to destroy
|
||||
*
|
||||
* This function frees given NULL-terminated array of property entries,
|
||||
* along with their data.
|
||||
*/
|
||||
void property_entries_free(const struct property_entry *properties)
|
||||
{
|
||||
const struct property_entry *p;
|
||||
|
||||
if (!properties)
|
||||
return;
|
||||
|
||||
for (p = properties; p->name; p++)
|
||||
property_entry_free_data(p);
|
||||
|
||||
kfree(properties);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(property_entries_free);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* fwnode operations */
|
||||
|
||||
static struct fwnode_handle *software_node_get(struct fwnode_handle *fwnode)
|
||||
{
|
||||
struct software_node *swnode = to_software_node(fwnode);
|
||||
|
||||
kobject_get(&swnode->kobj);
|
||||
|
||||
return &swnode->fwnode;
|
||||
}
|
||||
|
||||
static void software_node_put(struct fwnode_handle *fwnode)
|
||||
{
|
||||
struct software_node *swnode = to_software_node(fwnode);
|
||||
|
||||
kobject_put(&swnode->kobj);
|
||||
}
|
||||
|
||||
static bool software_node_property_present(const struct fwnode_handle *fwnode,
|
||||
const char *propname)
|
||||
{
|
||||
return !!property_entry_get(to_software_node(fwnode)->properties,
|
||||
propname);
|
||||
}
|
||||
|
||||
static int software_node_read_int_array(const struct fwnode_handle *fwnode,
|
||||
const char *propname,
|
||||
unsigned int elem_size, void *val,
|
||||
size_t nval)
|
||||
{
|
||||
struct software_node *swnode = to_software_node(fwnode);
|
||||
|
||||
return property_entry_read_int_array(swnode->properties, propname,
|
||||
elem_size, val, nval);
|
||||
}
|
||||
|
||||
static int software_node_read_string_array(const struct fwnode_handle *fwnode,
|
||||
const char *propname,
|
||||
const char **val, size_t nval)
|
||||
{
|
||||
struct software_node *swnode = to_software_node(fwnode);
|
||||
|
||||
return property_entry_read_string_array(swnode->properties, propname,
|
||||
val, nval);
|
||||
}
|
||||
|
||||
struct fwnode_handle *
|
||||
software_node_get_parent(const struct fwnode_handle *fwnode)
|
||||
{
|
||||
struct software_node *swnode = to_software_node(fwnode);
|
||||
|
||||
return swnode->parent ? &swnode->parent->fwnode : NULL;
|
||||
}
|
||||
|
||||
struct fwnode_handle *
|
||||
software_node_get_next_child(const struct fwnode_handle *fwnode,
|
||||
struct fwnode_handle *child)
|
||||
{
|
||||
struct software_node *p = to_software_node(fwnode);
|
||||
struct software_node *c = to_software_node(child);
|
||||
|
||||
if (list_empty(&p->children) ||
|
||||
(c && list_is_last(&c->entry, &p->children)))
|
||||
return NULL;
|
||||
|
||||
if (c)
|
||||
c = list_next_entry(c, entry);
|
||||
else
|
||||
c = list_first_entry(&p->children, struct software_node, entry);
|
||||
return &c->fwnode;
|
||||
}
|
||||
|
||||
|
||||
static const struct fwnode_operations software_node_ops = {
|
||||
.get = software_node_get,
|
||||
.put = software_node_put,
|
||||
.property_present = software_node_property_present,
|
||||
.property_read_int_array = software_node_read_int_array,
|
||||
.property_read_string_array = software_node_read_string_array,
|
||||
.get_parent = software_node_get_parent,
|
||||
.get_next_child_node = software_node_get_next_child,
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static int
|
||||
software_node_register_properties(struct software_node *swnode,
|
||||
const struct property_entry *properties)
|
||||
{
|
||||
struct property_entry *props;
|
||||
|
||||
props = property_entries_dup(properties);
|
||||
if (IS_ERR(props))
|
||||
return PTR_ERR(props);
|
||||
|
||||
swnode->properties = props;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void software_node_release(struct kobject *kobj)
|
||||
{
|
||||
struct software_node *swnode = kobj_to_swnode(kobj);
|
||||
|
||||
if (swnode->parent) {
|
||||
ida_simple_remove(&swnode->parent->child_ids, swnode->id);
|
||||
list_del(&swnode->entry);
|
||||
} else {
|
||||
ida_simple_remove(&swnode_root_ids, swnode->id);
|
||||
}
|
||||
|
||||
ida_destroy(&swnode->child_ids);
|
||||
property_entries_free(swnode->properties);
|
||||
kfree(swnode);
|
||||
}
|
||||
|
||||
static struct kobj_type software_node_type = {
|
||||
.release = software_node_release,
|
||||
.sysfs_ops = &kobj_sysfs_ops,
|
||||
};
|
||||
|
||||
struct fwnode_handle *
|
||||
fwnode_create_software_node(const struct property_entry *properties,
|
||||
const struct fwnode_handle *parent)
|
||||
{
|
||||
struct software_node *p = NULL;
|
||||
struct software_node *swnode;
|
||||
int ret;
|
||||
|
||||
if (parent) {
|
||||
if (IS_ERR(parent))
|
||||
return ERR_CAST(parent);
|
||||
if (!is_software_node(parent))
|
||||
return ERR_PTR(-EINVAL);
|
||||
p = to_software_node(parent);
|
||||
}
|
||||
|
||||
swnode = kzalloc(sizeof(*swnode), GFP_KERNEL);
|
||||
if (!swnode)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ret = ida_simple_get(p ? &p->child_ids : &swnode_root_ids, 0, 0,
|
||||
GFP_KERNEL);
|
||||
if (ret < 0) {
|
||||
kfree(swnode);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
swnode->id = ret;
|
||||
swnode->kobj.kset = swnode_kset;
|
||||
swnode->fwnode.ops = &software_node_ops;
|
||||
|
||||
ida_init(&swnode->child_ids);
|
||||
INIT_LIST_HEAD(&swnode->entry);
|
||||
INIT_LIST_HEAD(&swnode->children);
|
||||
swnode->parent = p;
|
||||
|
||||
if (p)
|
||||
list_add_tail(&swnode->entry, &p->children);
|
||||
|
||||
ret = kobject_init_and_add(&swnode->kobj, &software_node_type,
|
||||
p ? &p->kobj : NULL, "node%d", swnode->id);
|
||||
if (ret) {
|
||||
kobject_put(&swnode->kobj);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
ret = software_node_register_properties(swnode, properties);
|
||||
if (ret) {
|
||||
kobject_put(&swnode->kobj);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
kobject_uevent(&swnode->kobj, KOBJ_ADD);
|
||||
return &swnode->fwnode;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fwnode_create_software_node);
|
||||
|
||||
void fwnode_remove_software_node(struct fwnode_handle *fwnode)
|
||||
{
|
||||
struct software_node *swnode = to_software_node(fwnode);
|
||||
|
||||
if (!swnode)
|
||||
return;
|
||||
|
||||
kobject_put(&swnode->kobj);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fwnode_remove_software_node);
|
||||
|
||||
int software_node_notify(struct device *dev, unsigned long action)
|
||||
{
|
||||
struct fwnode_handle *fwnode = dev_fwnode(dev);
|
||||
struct software_node *swnode;
|
||||
int ret;
|
||||
|
||||
if (!fwnode)
|
||||
return 0;
|
||||
|
||||
if (!is_software_node(fwnode))
|
||||
fwnode = fwnode->secondary;
|
||||
if (!is_software_node(fwnode))
|
||||
return 0;
|
||||
|
||||
swnode = to_software_node(fwnode);
|
||||
|
||||
switch (action) {
|
||||
case KOBJ_ADD:
|
||||
ret = sysfs_create_link(&dev->kobj, &swnode->kobj,
|
||||
"software_node");
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = sysfs_create_link(&swnode->kobj, &dev->kobj,
|
||||
dev_name(dev));
|
||||
if (ret) {
|
||||
sysfs_remove_link(&dev->kobj, "software_node");
|
||||
break;
|
||||
}
|
||||
kobject_get(&swnode->kobj);
|
||||
break;
|
||||
case KOBJ_REMOVE:
|
||||
sysfs_remove_link(&swnode->kobj, dev_name(dev));
|
||||
sysfs_remove_link(&dev->kobj, "software_node");
|
||||
kobject_put(&swnode->kobj);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init software_node_init(void)
|
||||
{
|
||||
swnode_kset = kset_create_and_add("software_nodes", NULL, kernel_kobj);
|
||||
if (!swnode_kset)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
postcore_initcall(software_node_init);
|
||||
|
||||
static void __exit software_node_exit(void)
|
||||
{
|
||||
ida_destroy(&swnode_root_ids);
|
||||
kset_unregister(swnode_kset);
|
||||
}
|
||||
__exitcall(software_node_exit);
|
@ -1331,4 +1331,14 @@ static inline int find_acpi_cpu_cache_topology(unsigned int cpu, int level)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
extern int acpi_platform_notify(struct device *dev, enum kobject_action action);
|
||||
#else
|
||||
static inline int
|
||||
acpi_platform_notify(struct device *dev, enum kobject_action action)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /*_LINUX_ACPI_H*/
|
||||
|
@ -311,4 +311,16 @@ fwnode_graph_get_remote_node(const struct fwnode_handle *fwnode, u32 port,
|
||||
int fwnode_graph_parse_endpoint(const struct fwnode_handle *fwnode,
|
||||
struct fwnode_endpoint *endpoint);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Software fwnode support - when HW description is incomplete or missing */
|
||||
|
||||
bool is_software_node(const struct fwnode_handle *fwnode);
|
||||
|
||||
int software_node_notify(struct device *dev, unsigned long action);
|
||||
|
||||
struct fwnode_handle *
|
||||
fwnode_create_software_node(const struct property_entry *properties,
|
||||
const struct fwnode_handle *parent);
|
||||
void fwnode_remove_software_node(struct fwnode_handle *fwnode);
|
||||
|
||||
#endif /* _LINUX_PROPERTY_H_ */
|
||||
|
Loading…
Reference in New Issue
Block a user