Skip to content

Commit 3e767d6

Browse files
cris-marafaeljw
authored andcommitted
powercap: arm_scmi: Remove recursion while parsing zones
Powercap zones can be defined as arranged in a hierarchy of trees and when registering a zone with powercap_register_zone(), the kernel powercap subsystem expects this to happen starting from the root zones down to the leaves; on the other side, de-registration by powercap_deregister_zone() must begin from the leaf zones. Available SCMI powercap zones are retrieved dynamically from the platform at probe time and, while any defined hierarchy between the zones is described properly in the zones descriptor, the platform returns the availables zones with no particular well-defined order: as a consequence, the trees possibly composing the hierarchy of zones have to be somehow walked properly to register the retrieved zones from the root. Currently the ARM SCMI Powercap driver walks the zones using a recursive algorithm; this approach, even though correct and tested can lead to kernel stack overflow when processing a returned hierarchy of zones composed by particularly high trees. Avoid possible kernel stack overflow by substituting the recursive approach with an iterative one supported by a dynamically allocated stack-like data structure. Fixes: b55eef5 ("powercap: arm_scmi: Add SCMI Powercap based driver") Signed-off-by: Cristian Marussi <cristian.marussi@arm.com> Acked-by: Sudeep Holla <sudeep.holla@arm.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
1 parent fdf0eaf commit 3e767d6

File tree

1 file changed

+92
-67
lines changed

1 file changed

+92
-67
lines changed

drivers/powercap/arm_scmi_powercap.c

Lines changed: 92 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
#include <linux/module.h>
1313
#include <linux/powercap.h>
1414
#include <linux/scmi_protocol.h>
15+
#include <linux/slab.h>
1516

1617
#define to_scmi_powercap_zone(z) \
1718
container_of(z, struct scmi_powercap_zone, zone)
1819

1920
static const struct scmi_powercap_proto_ops *powercap_ops;
2021

2122
struct scmi_powercap_zone {
23+
bool registered;
24+
bool invalid;
2225
unsigned int height;
2326
struct device *dev;
2427
struct scmi_protocol_handle *ph;
@@ -32,6 +35,7 @@ struct scmi_powercap_root {
3235
unsigned int num_zones;
3336
struct scmi_powercap_zone *spzones;
3437
struct list_head *registered_zones;
38+
struct list_head scmi_zones;
3539
};
3640

3741
static struct powercap_control_type *scmi_top_pcntrl;
@@ -271,12 +275,6 @@ static void scmi_powercap_unregister_all_zones(struct scmi_powercap_root *pr)
271275
}
272276
}
273277

274-
static inline bool
275-
scmi_powercap_is_zone_registered(struct scmi_powercap_zone *spz)
276-
{
277-
return !list_empty(&spz->node);
278-
}
279-
280278
static inline unsigned int
281279
scmi_powercap_get_zone_height(struct scmi_powercap_zone *spz)
282280
{
@@ -295,11 +293,46 @@ scmi_powercap_get_parent_zone(struct scmi_powercap_zone *spz)
295293
return &spz->spzones[spz->info->parent_id];
296294
}
297295

296+
static int scmi_powercap_register_zone(struct scmi_powercap_root *pr,
297+
struct scmi_powercap_zone *spz,
298+
struct scmi_powercap_zone *parent)
299+
{
300+
int ret = 0;
301+
struct powercap_zone *z;
302+
303+
if (spz->invalid) {
304+
list_del(&spz->node);
305+
return -EINVAL;
306+
}
307+
308+
z = powercap_register_zone(&spz->zone, scmi_top_pcntrl, spz->info->name,
309+
parent ? &parent->zone : NULL,
310+
&zone_ops, 1, &constraint_ops);
311+
if (!IS_ERR(z)) {
312+
spz->height = scmi_powercap_get_zone_height(spz);
313+
spz->registered = true;
314+
list_move(&spz->node, &pr->registered_zones[spz->height]);
315+
dev_dbg(spz->dev, "Registered node %s - parent %s - height:%d\n",
316+
spz->info->name, parent ? parent->info->name : "ROOT",
317+
spz->height);
318+
} else {
319+
list_del(&spz->node);
320+
ret = PTR_ERR(z);
321+
dev_err(spz->dev,
322+
"Error registering node:%s - parent:%s - h:%d - ret:%d\n",
323+
spz->info->name,
324+
parent ? parent->info->name : "ROOT",
325+
spz->height, ret);
326+
}
327+
328+
return ret;
329+
}
330+
298331
/**
299-
* scmi_powercap_register_zone - Register an SCMI powercap zone recursively
332+
* scmi_zones_register- Register SCMI powercap zones starting from parent zones
300333
*
334+
* @dev: A reference to the SCMI device
301335
* @pr: A reference to the root powercap zones descriptors
302-
* @spz: A reference to the SCMI powercap zone to register
303336
*
304337
* When registering SCMI powercap zones with the powercap framework we should
305338
* take care to always register zones starting from the root ones and to
@@ -309,10 +342,10 @@ scmi_powercap_get_parent_zone(struct scmi_powercap_zone *spz)
309342
* zones provided by the SCMI platform firmware is built to comply with such
310343
* requirement.
311344
*
312-
* This function, given an SCMI powercap zone to register, takes care to walk
313-
* the SCMI powercap zones tree up to the root looking recursively for
314-
* unregistered parent zones before registering the provided zone; at the same
315-
* time each registered zone height in such a tree is accounted for and each
345+
* This function, given the set of SCMI powercap zones to register, takes care
346+
* to walk the SCMI powercap zones trees up to the root registering any
347+
* unregistered parent zone before registering the child zones; at the same
348+
* time each registered-zone height in such a tree is accounted for and each
316349
* zone, once registered, is stored in the @registered_zones array that is
317350
* indexed by zone height: this way will be trivial, at unregister time, to walk
318351
* the @registered_zones array backward and unregister all the zones starting
@@ -330,57 +363,55 @@ scmi_powercap_get_parent_zone(struct scmi_powercap_zone *spz)
330363
*
331364
* Return: 0 on Success
332365
*/
333-
static int scmi_powercap_register_zone(struct scmi_powercap_root *pr,
334-
struct scmi_powercap_zone *spz)
366+
static int scmi_zones_register(struct device *dev,
367+
struct scmi_powercap_root *pr)
335368
{
336369
int ret = 0;
337-
struct scmi_powercap_zone *parent;
338-
339-
if (!spz->info)
340-
return ret;
370+
unsigned int sp = 0, reg_zones = 0;
371+
struct scmi_powercap_zone *spz, **zones_stack;
341372

342-
parent = scmi_powercap_get_parent_zone(spz);
343-
if (parent && !scmi_powercap_is_zone_registered(parent)) {
344-
/*
345-
* Bail out if a parent domain was marked as unsupported:
346-
* only domains participating as leaves can be skipped.
347-
*/
348-
if (!parent->info)
349-
return -ENODEV;
373+
zones_stack = kcalloc(pr->num_zones, sizeof(spz), GFP_KERNEL);
374+
if (!zones_stack)
375+
return -ENOMEM;
350376

351-
ret = scmi_powercap_register_zone(pr, parent);
352-
if (ret)
353-
return ret;
354-
}
377+
spz = list_first_entry_or_null(&pr->scmi_zones,
378+
struct scmi_powercap_zone, node);
379+
while (spz) {
380+
struct scmi_powercap_zone *parent;
355381

356-
if (!scmi_powercap_is_zone_registered(spz)) {
357-
struct powercap_zone *z;
358-
359-
z = powercap_register_zone(&spz->zone,
360-
scmi_top_pcntrl,
361-
spz->info->name,
362-
parent ? &parent->zone : NULL,
363-
&zone_ops, 1, &constraint_ops);
364-
if (!IS_ERR(z)) {
365-
spz->height = scmi_powercap_get_zone_height(spz);
366-
list_add(&spz->node,
367-
&pr->registered_zones[spz->height]);
368-
dev_dbg(spz->dev,
369-
"Registered node %s - parent %s - height:%d\n",
370-
spz->info->name,
371-
parent ? parent->info->name : "ROOT",
372-
spz->height);
373-
ret = 0;
382+
parent = scmi_powercap_get_parent_zone(spz);
383+
if (parent && !parent->registered) {
384+
zones_stack[sp++] = spz;
385+
spz = parent;
374386
} else {
375-
ret = PTR_ERR(z);
376-
dev_err(spz->dev,
377-
"Error registering node:%s - parent:%s - h:%d - ret:%d\n",
378-
spz->info->name,
379-
parent ? parent->info->name : "ROOT",
380-
spz->height, ret);
387+
ret = scmi_powercap_register_zone(pr, spz, parent);
388+
if (!ret) {
389+
reg_zones++;
390+
} else if (sp) {
391+
/* Failed to register a non-leaf zone.
392+
* Bail-out.
393+
*/
394+
dev_err(dev,
395+
"Failed to register non-leaf zone - ret:%d\n",
396+
ret);
397+
scmi_powercap_unregister_all_zones(pr);
398+
reg_zones = 0;
399+
goto out;
400+
}
401+
/* Pick next zone to process */
402+
if (sp)
403+
spz = zones_stack[--sp];
404+
else
405+
spz = list_first_entry_or_null(&pr->scmi_zones,
406+
struct scmi_powercap_zone,
407+
node);
381408
}
382409
}
383410

411+
out:
412+
kfree(zones_stack);
413+
dev_info(dev, "Registered %d SCMI Powercap domains !\n", reg_zones);
414+
384415
return ret;
385416
}
386417

@@ -424,6 +455,8 @@ static int scmi_powercap_probe(struct scmi_device *sdev)
424455
if (!pr->registered_zones)
425456
return -ENOMEM;
426457

458+
INIT_LIST_HEAD(&pr->scmi_zones);
459+
427460
for (i = 0, spz = pr->spzones; i < pr->num_zones; i++, spz++) {
428461
/*
429462
* Powercap domains are validate by the protocol layer, i.e.
@@ -438,6 +471,7 @@ static int scmi_powercap_probe(struct scmi_device *sdev)
438471
INIT_LIST_HEAD(&spz->node);
439472
INIT_LIST_HEAD(&pr->registered_zones[i]);
440473

474+
list_add_tail(&spz->node, &pr->scmi_zones);
441475
/*
442476
* Forcibly skip powercap domains using an abstract scale.
443477
* Note that only leaves domains can be skipped, so this could
@@ -448,7 +482,7 @@ static int scmi_powercap_probe(struct scmi_device *sdev)
448482
dev_warn(dev,
449483
"Abstract power scale not supported. Skip %s.\n",
450484
spz->info->name);
451-
spz->info = NULL;
485+
spz->invalid = true;
452486
continue;
453487
}
454488
}
@@ -457,21 +491,12 @@ static int scmi_powercap_probe(struct scmi_device *sdev)
457491
* Scan array of retrieved SCMI powercap domains and register them
458492
* recursively starting from the root domains.
459493
*/
460-
for (i = 0, spz = pr->spzones; i < pr->num_zones; i++, spz++) {
461-
ret = scmi_powercap_register_zone(pr, spz);
462-
if (ret) {
463-
dev_err(dev,
464-
"Failed to register powercap zone %s - ret:%d\n",
465-
spz->info->name, ret);
466-
scmi_powercap_unregister_all_zones(pr);
467-
return ret;
468-
}
469-
}
494+
ret = scmi_zones_register(dev, pr);
495+
if (ret)
496+
return ret;
470497

471498
dev_set_drvdata(dev, pr);
472499

473-
dev_info(dev, "Registered %d SCMI Powercap domains !\n", pr->num_zones);
474-
475500
return ret;
476501
}
477502

0 commit comments

Comments
 (0)