lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <DB58ZL4QV1G7.H32DMUGWCUIB@gmail.com>
Date: Sun, 06 Jul 2025 17:17:30 -0300
From: "Kurt Borja" <kuurtb@...il.com>
To: Thomas Weißschuh <linux@...ssschuh.net>
Cc: "Hans de Goede" <hdegoede@...hat.com>, Ilpo Järvinen
 <ilpo.jarvinen@...ux.intel.com>, "Joshua Grisham" <josh@...huagrisham.com>,
 "Mark Pearson" <mpearson-lenovo@...ebb.ca>, "Armin Wolf" <W_Armin@....de>,
 "Mario Limonciello" <mario.limonciello@....com>, "Antheas Kapenekakis"
 <lkml@...heas.dev>, "Derek J. Clark" <derekjohn.clark@...il.com>, "Prasanth
 Ksr" <prasanth.ksr@...l.com>, "Jorge Lopez" <jorge.lopez2@...com>,
 <platform-driver-x86@...r.kernel.org>, <linux-kernel@...r.kernel.org>,
 <Dell.Client.Kernel@...l.com>
Subject: Re: [PATCH v5 2/6] platform/x86: firmware_attributes_class: Add
 high level API for the attributes interface

On Sun Jul 6, 2025 at 4:40 AM -03, Thomas Weißschuh wrote:
> On 2025-07-05 00:33:57-0300, Kurt Borja wrote:
>> Add high level API to aid in the creation of attribute groups attached
>> to the `attrs_kobj` (per ABI specification).
>> 
>> This new API lets users configure each group, either statically or
>> dynamically through a (per type) data struct and then create this group
>> through the generic fwat_create_group() macro.
>
> How does this deal with non-standard types and non-standard attribute
> "subattributes"?

IMO non-standard types aren't priority right now. Even if we accomodate
something for those, it would be rarely used, if ever. Also the
attrs_kset is exposed to users, so they can add custom kobjects to it
(with some consideration about ownership ofc).

Non-standard "subattributes" are easy to accomodate for. We can take
extra groups in fwat_create_group().

>
> Did you compare the code sizes of the framework and using drivers before
> and after? It looks like it will grow quite a bit.

I'll work a comparison for the next version.

By looking at users of the class (think-lmi for example). Drivers
already use some kind of fwat_group_data structure, which stores values
for the static properties of each "firmware attribute". These are also
dynamically allocated in most cases because FW provides enumeration
methods.

This makes me think that total used memory (static and dynamic) should
be fairly similar, in most cases.

>
>> 
>> Signed-off-by: Kurt Borja <kuurtb@...il.com>
>> ---
>>  drivers/platform/x86/firmware_attributes_class.c | 532 +++++++++++++++++++++++
>>  drivers/platform/x86/firmware_attributes_class.h | 335 ++++++++++++++
>>  2 files changed, 867 insertions(+)
>> 
>> diff --git a/drivers/platform/x86/firmware_attributes_class.c b/drivers/platform/x86/firmware_attributes_class.c
>> index 290364202cce64bb0e9046e0b2bbb8d85e2cbc6f..96473b3b1a2a87cf21a6e2a9a14d72ae322c94ae 100644
>> --- a/drivers/platform/x86/firmware_attributes_class.c
>> +++ b/drivers/platform/x86/firmware_attributes_class.c
>> @@ -10,13 +10,67 @@
>>  #include <linux/module.h>
>>  #include <linux/slab.h>
>>  #include <linux/types.h>
>> +#include <linux/string_choices.h>
>>  #include "firmware_attributes_class.h"
>>  
>> +#define FWAT_TYPE_NONE				-1
>
> Braces around '-1'.
>
> One the other hand this define is only used in two places and the value
> does not matter and is only passed because the macros expect something.
> Maybe move the definition to where it is used and do:
>
> __FWAT_TYPE_NONE (-1) /* dummy value, never evaluated */
>
>> +
>> +#define to_fwat_bool_data(_c) \
>> +	container_of_const(_c, struct fwat_bool_data, group)
>> +#define to_fwat_enum_data(_c) \
>> +	container_of_const(_c, struct fwat_enum_data, group)
>> +#define to_fwat_int_data(_c) \
>> +	container_of_const(_c, struct fwat_int_data, group)
>> +#define to_fwat_str_data(_c) \
>> +	container_of_const(_c, struct fwat_str_data, group)
>> +
>> +struct fwat_attribute {
>> +	struct attribute attr;
>> +	ssize_t (*show)(struct kobject *kobj, struct fwat_attribute *attr,
>
> Please make the 'struct fwat_attribute *' argument const.
> Somebody (me) is currently working on constifying 'struct attribute'.
>
>> +			char *buf);
>> +	ssize_t (*store)(struct kobject *kobj, struct fwat_attribute *attr,
>> +			 const char *buf, size_t count);
>> +	int type;
>> +};
>> +
>> +#define to_fwat_attribute(_a) \
>> +	container_of_const(_a, struct fwat_attribute, attr)
>> +
>> +#define __FWAT_ATTR(_name, _mode, _show, _store, _type) \
>> +	{								\
>
> Move the opening brace to the line above.
>
>> +		.attr = { .name = __stringify(_name), .mode = _mode },	\
>> +		.show = _show, .store = _store, .type = _type,		\
>> +	}
>> +
>
> <snip>
>
>> +static ssize_t
>> +enum_group_show(struct kobject *kobj, struct fwat_attribute *attr, char *buf)
>> +{
>> +	const struct fwat_group *group = kobj_to_fwat_group(kobj);
>> +	const struct fwat_enum_data *data = to_fwat_enum_data(group->data);
>> +	int val_idx, sz = 0;
>> +	int ret;
>> +
>> +	/* show_override does not affect current_value */
>> +	if (data->group.show_override && attr->type != fwat_enum_current_value)
>> +		return data->group.show_override(group->dev, attr->type, buf);
>> +
>> +	switch (attr->type) {
>> +	case fwat_enum_current_value:
>> +		ret = data->read(group->dev, data->group.id, &val_idx);
>> +		if (ret < 0)
>> +			return ret;
>> +		break;
>> +	case fwat_enum_default_value:
>> +		val_idx = data->default_idx;
>> +		break;
>> +	case fwat_enum_possible_values:
>> +		sz += sysfs_emit_at(buf, sz, "%s", data->possible_vals[0]);
>
> Slightly easier to read if the line above is:
>
> 	sz = sysfs_emit_at(buf, 0, "%s", data->possible_vals[0]);
>
> And drop the initialization of sz.
>
>
>> +		for (unsigned int i = 1; data->possible_vals[i]; i++)
>> +			sz += sysfs_emit_at(buf, sz, ";%s", data->possible_vals[i]);
>> +		sz += sysfs_emit_at(buf, sz, "\n");
>> +		return sz;
>> +	default:
>> +		return -EOPNOTSUPP;
>> +	}
>> +
>> +	return sysfs_emit(buf, "%s\n", data->possible_vals[val_idx]);
>> +}
>
> <snip>
>
>> +static umode_t fwat_attr_visible(struct kobject *kobj, struct attribute *attr, int n)
>> +{
>> +	struct fwat_attribute *fwat_attr = to_fwat_attribute(attr);
>
> const
>
>> +	struct fwat_group *group = kobj_to_fwat_group(kobj);
>> +	const struct fwat_group_data *data = group->data;
>> +
>> +	/* The `type` attribute is always first */
>> +	if (n == 0)
>> +		return attr->mode;
>> +
>> +	if (attr == &fwat_all_display_name_attr.attr)
>> +		return data->display_name ? attr->mode : 0;
>> +
>> +	if (attr == &fwat_all_display_name_language_code_attr.attr)
>> +		return data->language_code ? attr->mode : 0;
>> +
>> +	/* The `current_value` attribute always has type == 0 */
>> +	if (!fwat_attr->type)
>> +		return data->mode;
>> +
>> +	return test_bit(fwat_attr->type, &data->fattrs) ? attr->mode : 0;
>> +}
>> +
>> +static umode_t fwat_group_visible(struct kobject *kobj)
>> +{
>> +	return true;
>> +}
>> +
>> +DEFINE_SYSFS_GROUP_VISIBLE(fwat);
>
> Why DEFINE_SYSFS_GROUP_VISIBLE() if the per-group callback doesn't do
> anything?
>
>> +
>> +static const struct attribute_group fwat_bool_group = {
>> +	.attrs = fwat_bool_attrs,
>> +	.is_visible = SYSFS_GROUP_VISIBLE(fwat),
>> +};
>> +__ATTRIBUTE_GROUPS(fwat_bool);
>
> <snip>
>
>>  /**
>>   * fwat_device_register - Create and register a firmware-attributes class
>>   *			  device
>> @@ -89,6 +620,7 @@ void fwat_device_unregister(struct fwat_device *fadev)
>>  	if (!fadev)
>>  		return;
>>  
>> +	fwat_remove_auto_groups(fadev);
>
> As before, the groups are children of the device so should be removed
> automatically.

I'm pretty sure that would leak the fwat_group memory allocated in
__fwat_create_group(). Filesystem should be fine, but we still need to
put down kobject references.

>
>>  	sysfs_remove_groups(&fadev->attrs_kset->kobj, fadev->groups);
>>  	kset_unregister(fadev->attrs_kset);
>>  	device_unregister(&fadev->dev);
>> diff --git a/drivers/platform/x86/firmware_attributes_class.h b/drivers/platform/x86/firmware_attributes_class.h
>> index 048fd0904f767357ef856e687ec4cf3260016ec6..e8868ce05b595eda94a98975428391b9f9341e3d 100644
>> --- a/drivers/platform/x86/firmware_attributes_class.h
>> +++ b/drivers/platform/x86/firmware_attributes_class.h
>> @@ -10,6 +10,7 @@
>>  #include <linux/device/class.h>
>>  #include <linux/kobject.h>
>>  #include <linux/sysfs.h>
>> +#include <linux/list.h>
>
> Unnecessary for public header.
>
>>  
>>  extern const struct class firmware_attributes_class;
>>  
>> @@ -27,6 +28,340 @@ struct fwat_device {
>>  
>>  #define to_fwat_device(_d)	container_of_const(_d, struct fwat_device, dev)
>>  
>> +enum fwat_group_type {
>> +	fwat_group_boolean,
>
> I have a slight preference for "fwat_group_type_boolean".

Ack.

>
>> +	fwat_group_enumeration,
>> +	fwat_group_integer,
>> +	fwat_group_string,
>> +};
>> +
>> +enum fwat_bool_attrs {
>> +	fwat_bool_current_value,
>> +	fwat_bool_default_value,
>> +	fwat_bool_attrs_last
>> +};
>> +
>> +#define FWAT_BOOL_CURRENT_VALUE			BIT(fwat_bool_current_value)
>> +#define FWAT_BOOL_DEFAULT_VALUE			BIT(fwat_bool_default_value)
>> +#define FWAT_BOOL_ALL_ATTRS			GENMASK(fwat_bool_attrs_last, 0)
>
> Is the ALL_ATTRS really necessary? It could lead to issues when new
> optional attributes are added to the core.

Most drivers end up using all sub-attrs so I included it to avoid
seeing this every time: 

FWAT_INT_DEFAULT_VALUE | FWAT_INT_MIN_VALUE | FWAT_INT_MIN_VALUE | ...

>
>> +
>> +enum fwat_enum_attrs {
>> +	fwat_enum_current_value,
>> +	fwat_enum_default_value,
>> +	fwat_enum_possible_values,
>> +	fwat_enum_attrs_last
>> +};
>> +
>> +#define FWAT_ENUM_CURRENT_VALUE			BIT(fwat_enum_current_value)
>> +#define FWAT_ENUM_DEFAULT_VALUE			BIT(fwat_enum_default_value)
>> +#define FWAT_ENUM_POSSIBLE_VALUES		BIT(fwat_enum_possible_values)
>> +#define FWAT_ENUM_ALL_ATTRS			GENMASK(fwat_enum_attrs_last, 0)
>> +
>> +enum fwat_int_attrs {
>> +	fwat_int_current_value,
>> +	fwat_int_default_value,
>> +	fwat_int_min_value,
>> +	fwat_int_max_value,
>> +	fwat_int_scalar_increment,
>> +	fwat_int_attrs_last
>> +};
>> +
>> +#define FWAT_INT_CURRENT_VALUE			BIT(fwat_int_current_value)
>> +#define FWAT_INT_DEFAULT_VALUE			BIT(fwat_int_default_value)
>> +#define FWAT_INT_MIN_VALUE			BIT(fwat_int_min_value)
>> +#define FWAT_INT_MAX_VALUE			BIT(fwat_int_max_value)
>> +#define FWAT_INT_SCALAR_INCREMENT		BIT(fwat_int_scalar_increment)
>> +#define FWAT_INT_ALL_ATTRS			GENMASK(fwat_int_attrs_last, 0)
>> +
>> +enum fwat_str_attrs {
>> +	fwat_str_current_value,
>> +	fwat_str_default_value,
>> +	fwat_str_min_length,
>> +	fwat_str_max_length,
>> +	fwat_str_attrs_last
>> +};
>> +
>> +#define FWAT_STR_CURRENT_VALUE			BIT(fwat_str_current_value)
>> +#define FWAT_STR_DEFAULT_VALUE			BIT(fwat_str_default_value)
>> +#define FWAT_STR_MIN_LENGTH			BIT(fwat_str_min_length)
>> +#define FWAT_STR_MAX_LENGTH			BIT(fwat_str_max_length)
>> +#define FWAT_STR_ALL_ATTRS			GENMASK(fwat_str_attrs_last, 0)
>> +
>> +static_assert(fwat_bool_current_value == 0);
>> +static_assert(fwat_enum_current_value == 0);
>> +static_assert(fwat_int_current_value == 0);
>> +static_assert(fwat_str_current_value == 0);
>
> Move this into the the .c file right next to the code relying on this
> invariant.
>
> <snip>
>
>> +/**
>> + * DEFINE_FWAT_ENUM_GROUP - Convenience macro to quickly define an static
>> + *                          struct fwat_enum_data instance
>> + * @_name: Name of the group.
>> + * @_disp_name: Name showed in the display_name attribute. (Optional)
>> + * @_def_idx: Index of the default value in the @_poss_vals array.
>> + * @_poss_vals: Array of possible value strings for this group type.
>> + * @_mode: Mode for the current_value attribute. All other attributes will have
>> + *         0444 permissions.
>> + * @_fattrs: Bitmap of selected attributes for this group type.
>> + *
>> + * `read` and `write` callbacks are required to be already defined as
>> + * `_name##_read` and `_name##_write` respectively.
>> + *
>> + * NOTE: The `val_idx` argument in the `write` callback is guaranteed to be a
>> + *       valid (within bounds) index. However, the user is in charge of writing
>> + *       valid indexes to the `*val_idx` argument of the `read` callback.
>> + *       Failing to do so may result in an OOB access.
>> + */
>> +#define DEFINE_FWAT_ENUM_GROUP(_name, _disp_name, _poss_vals, _def_idx, _mode, _fattrs) \
>> +	static const struct fwat_enum_data _name##_group_data = {	\
>
> Align all backslashes of the definition.
>
>> +		.read = _name##_read,					\
>> +		.write = _name##_write,					\
>> +		.default_idx = _def_idx,				\
>> +		.possible_vals = _poss_vals,				\
>> +		.group = __FWAT_GROUP(_name, _disp_name, _mode, _fattrs), \
>> +	}
>
> <snip>
>
>> +int fwat_create_bool_group(struct fwat_device *fadev,
>> +			   const struct fwat_bool_data *data);
>> +int fwat_create_enum_group(struct fwat_device *fadev,
>> +			   const struct fwat_enum_data *data);
>> +int fwat_create_int_group(struct fwat_device *fadev,
>> +			  const struct fwat_int_data *data);
>> +int fwat_create_str_group(struct fwat_device *fadev,
>> +			  const struct fwat_str_data *data);
>
> If these are not meant to be used by users, give them a leading
> underscore?

Ack.

>
>> +
>> +/**
>> + * fwat_create_group - Convenience generic macro to create a group
>> + * @_dev: fwat_device
>> + * @_data: One of fwat_{bool,enum,int,str}_data instance
>> + *
>> + * This macro (and associated functions) creates a sysfs group under the
>> + * 'attributes' directory, which is located in the class device root directory.
>> + *
>> + * See Documentation/ABI/testing/sysfs-class-firmware-attributes for details.
>> + *
>> + * The @_data associated with this group may be created either statically,
>> + * through DEFINE_FWAT_*_GROUP macros or dynamically, in which case the user
>> + * would have allocate and fill the struct manually. The dynamic approach should
>> + * be preferred when group constraints and/or visibility is decided dynamically.
>
> I'd prefer if users don't need dynamic data allocations if possible.
> Let's see how it works out with the existing drivers.

That would be ideal ofc. But, as I said above, most drivers dynamically
enumerate and allocate "firmware attributes" so I think we need to
accomodate for this.

Most new drivers on the other hand know all these values beforehand so
in the end the user can decide how to define this data structs.

>
>> + *
>> + * Example:
>> + *
>> + * static int stat_read(...){...};
>> + * static int stat_write(...){...};
>> + *
>> + * DEFINE_FWAT_(BOOL|ENUM|INT|STR)_GROUP(stat, ...);
>> + *
>> + * static int create_groups(struct fwat_device *fadev)
>> + * {
>> + *	struct fwat_enum_data *dyn_group_data;
>> + *
>> + *	dyn_group_data = kzalloc(...);
>> + *	// Fill the data
>> + *	...
>> + *	fwat_create_group(fadev, &stat_group_data);
>> + *	fwat_create_group(fadev, &dyn_group_data);
>> + *	fwat_create_group(...);
>> + *	...
>> + * }
>> + *
>> + * Return: 0 on success, -errno on failure
>> + */
>> +#define fwat_create_group(_dev, _data) \
>> +	_Generic((_data),							\
>> +		 const struct fwat_bool_data * : fwat_create_bool_group,	\
>> +		 const struct fwat_enum_data * : fwat_create_enum_group,	\
>> +		 const struct fwat_int_data * : fwat_create_int_group,		\
>> +		 const struct fwat_str_data * : fwat_create_str_group)		\
>> +		(_dev, _data)
>> +
>>  struct fwat_device * __must_check
>>  fwat_device_register(struct device *parent, const char *name, void *drvdata,
>>  		     const struct attribute_group **groups);
>> 
>> -- 
>> 2.50.0
>> 

Ack to the rest of comments. Thank you for your feedback!


-- 
 ~ Kurt


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ