[PATCH] PM / devfreq: Convert to seq_buf

Biju posted 1 patch 1 month, 3 weeks ago
drivers/devfreq/devfreq.c | 64 ++++++++++++++++++---------------------
1 file changed, 30 insertions(+), 34 deletions(-)
[PATCH] PM / devfreq: Convert to seq_buf
Posted by Biju 1 month, 3 weeks ago
From: Biju Das <biju.das.jz@bp.renesas.com>

The devfreq transition table sysfs attribute (trans_stat_show()) builds
its output using manual sysfs_emit_at() calls with hand-rolled length
tracking and PAGE_SIZE bounds checks scattered throughout the loops.

On SoCs with large frequency tables, such as Renesas RZ/G3L, the
resulting table can exceed PAGE_SIZE. In that case the function bails
out entirely and returns -EFBIG, logging:

  devfreq transition table exceeds PAGE_SIZE. Disabling

which makes the attribute completely unusable on affected platforms.

Convert trans_stat_show() to use struct seq_buf instead. seq_buf_printf()
tracks the buffer position and handles overflow internally, removing the
need for manual length checks before every write. If the table still
overflows PAGE_SIZE, truncate the output to PAGE_SIZE - 1, NUL-terminate
it, and log a rate-limited warning rather than failing the read outright,
so the attribute still returns a usable (if truncated) table instead of
nothing.

Suggested-by: Geert Uytterhoeven <geert+renesas@glider.be>
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
 drivers/devfreq/devfreq.c | 64 ++++++++++++++++++---------------------
 1 file changed, 30 insertions(+), 34 deletions(-)

diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
index f08fc6966eae..3c4c6fa23cfe 100644
--- a/drivers/devfreq/devfreq.c
+++ b/drivers/devfreq/devfreq.c
@@ -28,6 +28,7 @@
 #include <linux/hrtimer.h>
 #include <linux/of.h>
 #include <linux/pm_qos.h>
+#include <linux/seq_buf.h>
 #include <linux/units.h>
 
 #define CREATE_TRACE_POINTS
@@ -1661,9 +1662,10 @@ static ssize_t trans_stat_show(struct device *dev,
 			       struct device_attribute *attr, char *buf)
 {
 	struct devfreq *df = to_devfreq(dev);
-	ssize_t len = 0;
-	int i, j;
 	unsigned int max_state;
+	struct seq_buf s;
+	ssize_t len;
+	int i, j;
 
 	if (!df->profile)
 		return -EINVAL;
@@ -1672,6 +1674,8 @@ static ssize_t trans_stat_show(struct device *dev,
 	if (max_state == 0)
 		return sysfs_emit(buf, "Not Supported.\n");
 
+	seq_buf_init(&s, buf, PAGE_SIZE);
+
 	mutex_lock(&df->lock);
 	if (!df->stop_polling &&
 			devfreq_update_status(df, df->previous_freq)) {
@@ -1680,47 +1684,39 @@ static ssize_t trans_stat_show(struct device *dev,
 	}
 	mutex_unlock(&df->lock);
 
-	len += sysfs_emit_at(buf, len, "     From  :   To\n");
-	len += sysfs_emit_at(buf, len, "           :");
-	for (i = 0; i < max_state; i++) {
-		if (len >= PAGE_SIZE - 1)
-			break;
-		len += sysfs_emit_at(buf, len, "%10lu",
-				     df->freq_table[i]);
-	}
+	seq_buf_printf(&s, "     From  :   To\n");
+	seq_buf_printf(&s, "           :");
+	for (i = 0; i < max_state; i++)
+		seq_buf_printf(&s, "%10lu", df->freq_table[i]);
 
-	if (len >= PAGE_SIZE - 1)
-		return PAGE_SIZE - 1;
-	len += sysfs_emit_at(buf, len, "   time(ms)\n");
+	seq_buf_printf(&s, "   time(ms)\n");
 
 	for (i = 0; i < max_state; i++) {
-		if (len >= PAGE_SIZE - 1)
-			break;
 		if (df->freq_table[i] == df->previous_freq)
-			len += sysfs_emit_at(buf, len, "*");
+			seq_buf_printf(&s, "*");
 		else
-			len += sysfs_emit_at(buf, len, " ");
-		if (len >= PAGE_SIZE - 1)
-			break;
-		len += sysfs_emit_at(buf, len, "%10lu:", df->freq_table[i]);
+			seq_buf_printf(&s, " ");
+		seq_buf_printf(&s, "%10lu:", df->freq_table[i]);
 		for (j = 0; j < max_state; j++) {
-			if (len >= PAGE_SIZE - 1)
-				break;
-			len += sysfs_emit_at(buf, len, "%10u",
-				df->stats.trans_table[(i * max_state) + j]);
+			seq_buf_printf(&s, "%10u",
+				       df->stats.trans_table[(i * max_state) + j]);
 		}
-		if (len >= PAGE_SIZE - 1)
-			break;
-		len += sysfs_emit_at(buf, len, "%10llu\n", (u64)
-				     jiffies64_to_msecs(df->stats.time_in_state[i]));
+		seq_buf_printf(&s, "%10llu\n", (u64)
+			       jiffies64_to_msecs(df->stats.time_in_state[i]));
 	}
 
-	if (len < PAGE_SIZE - 1)
-		len += sysfs_emit_at(buf, len, "Total transition : %u\n",
-				     df->stats.total_trans);
-	if (len >= PAGE_SIZE - 1) {
-		pr_warn_once("devfreq transition table exceeds PAGE_SIZE. Disabling\n");
-		return -EFBIG;
+	seq_buf_printf(&s, "Total transition : %u\n", df->stats.total_trans);
+	len = seq_buf_used(&s);
+	/*
+	 * If an overflow occurs, seq_buf_used() can return a value greater
+	 * than PAGE_SIZE. Clamp the return length to PAGE_SIZE - 1 and
+	 * ensure a null-terminator is written to prevent the sysfs
+	 * "bad count" or out-of-bounds warning.
+	 */
+	if (seq_buf_has_overflowed(&s)) {
+		pr_warn_ratelimited("devfreq transition table truncated due to PAGE_SIZE limit\n");
+		len = PAGE_SIZE - 1;
+		buf[len] = '\0';
 	}
 
 	return len;
-- 
2.43.0
Re: [PATCH] PM / devfreq: Convert to seq_buf
Posted by Geert Uytterhoeven 1 month, 3 weeks ago
Hi Biju,

On Fri, 7 Aug 2026 at 12:03, Biju <biju.das.au@gmail.com> wrote:
> From: Biju Das <biju.das.jz@bp.renesas.com>
>
> The devfreq transition table sysfs attribute (trans_stat_show()) builds
> its output using manual sysfs_emit_at() calls with hand-rolled length
> tracking and PAGE_SIZE bounds checks scattered throughout the loops.
>
> On SoCs with large frequency tables, such as Renesas RZ/G3L, the
> resulting table can exceed PAGE_SIZE. In that case the function bails
> out entirely and returns -EFBIG, logging:
>
>   devfreq transition table exceeds PAGE_SIZE. Disabling
>
> which makes the attribute completely unusable on affected platforms.
>
> Convert trans_stat_show() to use struct seq_buf instead. seq_buf_printf()
> tracks the buffer position and handles overflow internally, removing the
> need for manual length checks before every write. If the table still
> overflows PAGE_SIZE, truncate the output to PAGE_SIZE - 1, NUL-terminate
> it, and log a rate-limited warning rather than failing the read outright,
> so the attribute still returns a usable (if truncated) table instead of
> nothing.
>
> Suggested-by: Geert Uytterhoeven <geert+renesas@glider.be>
> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>

Thanks for your patch!

> --- a/drivers/devfreq/devfreq.c
> +++ b/drivers/devfreq/devfreq.c

> @@ -1661,9 +1662,10 @@ static ssize_t trans_stat_show(struct device *dev,

> +       /*
> +        * If an overflow occurs, seq_buf_used() can return a value greater
> +        * than PAGE_SIZE. Clamp the return length to PAGE_SIZE - 1 and
> +        * ensure a null-terminator is written to prevent the sysfs
> +        * "bad count" or out-of-bounds warning.
> +        */
> +       if (seq_buf_has_overflowed(&s)) {
> +               pr_warn_ratelimited("devfreq transition table truncated due to PAGE_SIZE limit\n");
> +               len = PAGE_SIZE - 1;
> +               buf[len] = '\0';

So shouldn't you use seq_file instead of seq_buf, to lift this limitation?
devfreq_summary_show() already uses that.

>         }
>
>         return len;

Gr{oetje,eeting}s,

                        Geert

-- 
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds