Skip to content

Commit 778b94d

Browse files
superm1rafaeljw
authored andcommitted
ACPI: platform_profile: Add support for hidden choices
When two drivers don't support all the same profiles the legacy interface only exports the common profiles. This causes problems for cases where one driver uses low-power but another uses quiet because the result is that neither is exported to sysfs. To allow two drivers to disagree, add support for "hidden choices". Hidden choices are platform profiles that a driver supports to be compatible with the platform profile of another driver. Fixes: 6888347 ("ACPI: platform_profile: Allow multiple handlers") Reported-by: Antheas Kapenekakis <lkml@antheas.dev> Closes: https://lore.kernel.org/platform-driver-x86/e64b771e-3255-42ad-9257-5b8fc6c24ac9@gmx.de/T/#mc068042dd29df36c16c8af92664860fc4763974b Signed-off-by: Mario Limonciello <mario.limonciello@amd.com> Tested-by: Antheas Kapenekakis <lkml@antheas.dev> Tested-by: Derek J. Clark <derekjohn.clark@gmail.com> Acked-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> Link: https://patch.msgid.link/20250228170155.2623386-2-superm1@kernel.org Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
1 parent 7eb1721 commit 778b94d

File tree

2 files changed

+76
-21
lines changed

2 files changed

+76
-21
lines changed

drivers/acpi/platform_profile.c

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,15 @@ struct platform_profile_handler {
2121
struct device dev;
2222
int minor;
2323
unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
24+
unsigned long hidden_choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
2425
const struct platform_profile_ops *ops;
2526
};
2627

28+
struct aggregate_choices_data {
29+
unsigned long aggregate[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
30+
int count;
31+
};
32+
2733
static const char * const profile_names[] = {
2834
[PLATFORM_PROFILE_LOW_POWER] = "low-power",
2935
[PLATFORM_PROFILE_COOL] = "cool",
@@ -73,7 +79,7 @@ static int _store_class_profile(struct device *dev, void *data)
7379

7480
lockdep_assert_held(&profile_lock);
7581
handler = to_pprof_handler(dev);
76-
if (!test_bit(*bit, handler->choices))
82+
if (!test_bit(*bit, handler->choices) && !test_bit(*bit, handler->hidden_choices))
7783
return -EOPNOTSUPP;
7884

7985
return handler->ops->profile_set(dev, *bit);
@@ -239,21 +245,44 @@ static const struct class platform_profile_class = {
239245
/**
240246
* _aggregate_choices - Aggregate the available profile choices
241247
* @dev: The device
242-
* @data: The available profile choices
248+
* @arg: struct aggregate_choices_data
243249
*
244250
* Return: 0 on success, -errno on failure
245251
*/
246-
static int _aggregate_choices(struct device *dev, void *data)
252+
static int _aggregate_choices(struct device *dev, void *arg)
247253
{
254+
unsigned long tmp[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
255+
struct aggregate_choices_data *data = arg;
248256
struct platform_profile_handler *handler;
249-
unsigned long *aggregate = data;
250257

251258
lockdep_assert_held(&profile_lock);
252259
handler = to_pprof_handler(dev);
253-
if (test_bit(PLATFORM_PROFILE_LAST, aggregate))
254-
bitmap_copy(aggregate, handler->choices, PLATFORM_PROFILE_LAST);
260+
bitmap_or(tmp, handler->choices, handler->hidden_choices, PLATFORM_PROFILE_LAST);
261+
if (test_bit(PLATFORM_PROFILE_LAST, data->aggregate))
262+
bitmap_copy(data->aggregate, tmp, PLATFORM_PROFILE_LAST);
255263
else
256-
bitmap_and(aggregate, handler->choices, aggregate, PLATFORM_PROFILE_LAST);
264+
bitmap_and(data->aggregate, tmp, data->aggregate, PLATFORM_PROFILE_LAST);
265+
data->count++;
266+
267+
return 0;
268+
}
269+
270+
/**
271+
* _remove_hidden_choices - Remove hidden choices from aggregate data
272+
* @dev: The device
273+
* @arg: struct aggregate_choices_data
274+
*
275+
* Return: 0 on success, -errno on failure
276+
*/
277+
static int _remove_hidden_choices(struct device *dev, void *arg)
278+
{
279+
struct aggregate_choices_data *data = arg;
280+
struct platform_profile_handler *handler;
281+
282+
lockdep_assert_held(&profile_lock);
283+
handler = to_pprof_handler(dev);
284+
bitmap_andnot(data->aggregate, handler->choices,
285+
handler->hidden_choices, PLATFORM_PROFILE_LAST);
257286

258287
return 0;
259288
}
@@ -270,22 +299,31 @@ static ssize_t platform_profile_choices_show(struct device *dev,
270299
struct device_attribute *attr,
271300
char *buf)
272301
{
273-
unsigned long aggregate[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
302+
struct aggregate_choices_data data = {
303+
.aggregate = { [0 ... BITS_TO_LONGS(PLATFORM_PROFILE_LAST) - 1] = ~0UL },
304+
.count = 0,
305+
};
274306
int err;
275307

276-
set_bit(PLATFORM_PROFILE_LAST, aggregate);
308+
set_bit(PLATFORM_PROFILE_LAST, data.aggregate);
277309
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
278310
err = class_for_each_device(&platform_profile_class, NULL,
279-
aggregate, _aggregate_choices);
311+
&data, _aggregate_choices);
280312
if (err)
281313
return err;
314+
if (data.count == 1) {
315+
err = class_for_each_device(&platform_profile_class, NULL,
316+
&data, _remove_hidden_choices);
317+
if (err)
318+
return err;
319+
}
282320
}
283321

284322
/* no profile handler registered any more */
285-
if (bitmap_empty(aggregate, PLATFORM_PROFILE_LAST))
323+
if (bitmap_empty(data.aggregate, PLATFORM_PROFILE_LAST))
286324
return -EINVAL;
287325

288-
return _commmon_choices_show(aggregate, buf);
326+
return _commmon_choices_show(data.aggregate, buf);
289327
}
290328

291329
/**
@@ -373,21 +411,24 @@ static ssize_t platform_profile_store(struct device *dev,
373411
struct device_attribute *attr,
374412
const char *buf, size_t count)
375413
{
376-
unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
414+
struct aggregate_choices_data data = {
415+
.aggregate = { [0 ... BITS_TO_LONGS(PLATFORM_PROFILE_LAST) - 1] = ~0UL },
416+
.count = 0,
417+
};
377418
int ret;
378419
int i;
379420

380421
/* Scan for a matching profile */
381422
i = sysfs_match_string(profile_names, buf);
382423
if (i < 0 || i == PLATFORM_PROFILE_CUSTOM)
383424
return -EINVAL;
384-
set_bit(PLATFORM_PROFILE_LAST, choices);
425+
set_bit(PLATFORM_PROFILE_LAST, data.aggregate);
385426
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
386427
ret = class_for_each_device(&platform_profile_class, NULL,
387-
choices, _aggregate_choices);
428+
&data, _aggregate_choices);
388429
if (ret)
389430
return ret;
390-
if (!test_bit(i, choices))
431+
if (!test_bit(i, data.aggregate))
391432
return -EOPNOTSUPP;
392433

393434
ret = class_for_each_device(&platform_profile_class, NULL, &i,
@@ -453,12 +494,15 @@ EXPORT_SYMBOL_GPL(platform_profile_notify);
453494
*/
454495
int platform_profile_cycle(void)
455496
{
497+
struct aggregate_choices_data data = {
498+
.aggregate = { [0 ... BITS_TO_LONGS(PLATFORM_PROFILE_LAST) - 1] = ~0UL },
499+
.count = 0,
500+
};
456501
enum platform_profile_option next = PLATFORM_PROFILE_LAST;
457502
enum platform_profile_option profile = PLATFORM_PROFILE_LAST;
458-
unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
459503
int err;
460504

461-
set_bit(PLATFORM_PROFILE_LAST, choices);
505+
set_bit(PLATFORM_PROFILE_LAST, data.aggregate);
462506
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
463507
err = class_for_each_device(&platform_profile_class, NULL,
464508
&profile, _aggregate_profiles);
@@ -470,14 +514,14 @@ int platform_profile_cycle(void)
470514
return -EINVAL;
471515

472516
err = class_for_each_device(&platform_profile_class, NULL,
473-
choices, _aggregate_choices);
517+
&data, _aggregate_choices);
474518
if (err)
475519
return err;
476520

477521
/* never iterate into a custom if all drivers supported it */
478-
clear_bit(PLATFORM_PROFILE_CUSTOM, choices);
522+
clear_bit(PLATFORM_PROFILE_CUSTOM, data.aggregate);
479523

480-
next = find_next_bit_wrap(choices,
524+
next = find_next_bit_wrap(data.aggregate,
481525
PLATFORM_PROFILE_LAST,
482526
profile + 1);
483527

@@ -532,6 +576,14 @@ struct device *platform_profile_register(struct device *dev, const char *name,
532576
return ERR_PTR(-EINVAL);
533577
}
534578

579+
if (ops->hidden_choices) {
580+
err = ops->hidden_choices(drvdata, pprof->hidden_choices);
581+
if (err) {
582+
dev_err(dev, "platform_profile hidden_choices failed\n");
583+
return ERR_PTR(err);
584+
}
585+
}
586+
535587
guard(mutex)(&profile_lock);
536588

537589
/* create class interface for individual handler */

include/linux/platform_profile.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,16 @@ enum platform_profile_option {
3333
* @probe: Callback to setup choices available to the new class device. These
3434
* choices will only be enforced when setting a new profile, not when
3535
* getting the current one.
36+
* @hidden_choices: Callback to setup choices that are not visible to the user
37+
* but can be set by the driver.
3638
* @profile_get: Callback that will be called when showing the current platform
3739
* profile in sysfs.
3840
* @profile_set: Callback that will be called when storing a new platform
3941
* profile in sysfs.
4042
*/
4143
struct platform_profile_ops {
4244
int (*probe)(void *drvdata, unsigned long *choices);
45+
int (*hidden_choices)(void *drvdata, unsigned long *choices);
4346
int (*profile_get)(struct device *dev, enum platform_profile_option *profile);
4447
int (*profile_set)(struct device *dev, enum platform_profile_option profile);
4548
};

0 commit comments

Comments
 (0)