drivers/gpu/drm/drm_edid.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-)
From: Arnd Bergmann <arnd@arndb.de>
The drm_parse_cea_ext() calls into nine individual parser functions
based on the data format. If all of those get inlined and KASAN_STACK
is enabled, this can explode the combined stack usage beyond a
reasonable amount:
drivers/gpu/drm/drm_edid.c: In function 'update_display_info':
drivers/gpu/drm/drm_edid.c:7040:1: error: the frame size of 1888 bytes is larger than 1536 bytes [-Werror=frame-larger-than=]
7040 | }
| ^
Mark all of the individual parser functions as noinline_for_stack
to avoid this and keep their stack frames separate. This reduces
the stack usage for update_display_info/drm_parse_cea_ext to
a more manageable 744 bytes, while each parser only adds at most
200 bytes on top.
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
drivers/gpu/drm/drm_edid.c | 31 +++++++++++++++++--------------
1 file changed, 17 insertions(+), 14 deletions(-)
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index 3148038747b6..913a8bbe8894 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -5341,8 +5341,9 @@ static int edid_hfeeodb_extension_block_count(const struct edid *edid)
* etc). For example, if the bit 0 in bitmap is set, first mode in VDB can
* support YCBCR420 output too.
*/
-static void parse_cta_y420cmdb(struct drm_connector *connector,
- const struct cea_db *db, u64 *y420cmdb_map)
+static noinline_for_stack void
+parse_cta_y420cmdb(struct drm_connector *connector, const struct cea_db *db,
+ u64 *y420cmdb_map)
{
struct drm_display_info *info = &connector->display_info;
int i, map_len = cea_db_payload_len(db) - 1;
@@ -5515,7 +5516,7 @@ static uint8_t hdr_metadata_type(const u8 *edid_ext)
BIT(HDMI_STATIC_METADATA_TYPE1);
}
-static void
+static noinline_for_stack void
drm_parse_hdr_metadata_block(struct drm_connector *connector, const u8 *db)
{
struct hdr_static_metadata *hdr_metadata =
@@ -6070,7 +6071,8 @@ drm_default_rgb_quant_range(const struct drm_display_mode *mode)
EXPORT_SYMBOL(drm_default_rgb_quant_range);
/* CTA-861 Video Data Block (CTA VDB) */
-static void parse_cta_vdb(struct drm_connector *connector, const struct cea_db *db)
+static noinline_for_stack void
+parse_cta_vdb(struct drm_connector *connector, const struct cea_db *db)
{
struct drm_display_info *info = &connector->display_info;
int i, vic_index, len = cea_db_payload_len(db);
@@ -6136,8 +6138,8 @@ static bool cta_vdb_has_vic(const struct drm_connector *connector, u8 vic)
}
/* CTA-861-H YCbCr 4:2:0 Video Data Block (CTA Y420VDB) */
-static void parse_cta_y420vdb(struct drm_connector *connector,
- const struct cea_db *db)
+static noinline_for_stack void
+parse_cta_y420vdb(struct drm_connector *connector, const struct cea_db *db)
{
struct drm_display_info *info = &connector->display_info;
struct drm_hdmi_info *hdmi = &info->hdmi;
@@ -6155,7 +6157,8 @@ static void parse_cta_y420vdb(struct drm_connector *connector,
}
}
-static void drm_parse_vcdb(struct drm_connector *connector, const u8 *db)
+static noinline_for_stack void
+drm_parse_vcdb(struct drm_connector *connector, const u8 *db)
{
struct drm_display_info *info = &connector->display_info;
@@ -6310,8 +6313,8 @@ static void drm_parse_dsc_info(struct drm_hdmi_dsc_cap *hdmi_dsc,
}
/* Sink Capability Data Structure */
-static void drm_parse_hdmi_forum_scds(struct drm_connector *connector,
- const u8 *hf_scds)
+static noinline_for_stack void
+drm_parse_hdmi_forum_scds(struct drm_connector *connector, const u8 *hf_scds)
{
struct drm_display_info *info = &connector->display_info;
struct drm_hdmi_info *hdmi = &info->hdmi;
@@ -6451,7 +6454,7 @@ static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector,
}
/* HDMI Vendor-Specific Data Block (HDMI VSDB, H14b-VSDB) */
-static void
+static noinline_for_stack void
drm_parse_hdmi_vsdb_video(struct drm_connector *connector, const u8 *db)
{
struct drm_display_info *info = &connector->display_info;
@@ -6486,8 +6489,8 @@ drm_parse_hdmi_vsdb_video(struct drm_connector *connector, const u8 *db)
* See EDID extension for head-mounted and specialized monitors, specified at:
* https://docs.microsoft.com/en-us/windows-hardware/drivers/display/specialized-monitors-edid-extension
*/
-static void drm_parse_microsoft_vsdb(struct drm_connector *connector,
- const u8 *db)
+static noinline_for_stack void
+drm_parse_microsoft_vsdb(struct drm_connector *connector, const u8 *db)
{
struct drm_display_info *info = &connector->display_info;
u8 version = db[4];
@@ -6558,8 +6561,8 @@ static void drm_parse_amd_vsdb_v3(struct drm_display_info *info,
info->amd_vsdb.max_frame_rate = max_frame_rate;
}
-static void drm_parse_amd_vsdb(struct drm_connector *connector,
- const struct cea_db *db)
+static noinline_for_stack void
+drm_parse_amd_vsdb(struct drm_connector *connector, const struct cea_db *db)
{
struct drm_display_info *info = &connector->display_info;
const u8 *data = cea_db_data(db);
--
2.53.0
On Tue, 13 Oct 2026, Arnd Bergmann <arnd@kernel.org> wrote:
> From: Arnd Bergmann <arnd@arndb.de>
>
> The drm_parse_cea_ext() calls into nine individual parser functions
> based on the data format. If all of those get inlined and KASAN_STACK
> is enabled, this can explode the combined stack usage beyond a
> reasonable amount:
>
> drivers/gpu/drm/drm_edid.c: In function 'update_display_info':
> drivers/gpu/drm/drm_edid.c:7040:1: error: the frame size of 1888 bytes is larger than 1536 bytes [-Werror=frame-larger-than=]
> 7040 | }
> | ^
>
> Mark all of the individual parser functions as noinline_for_stack
> to avoid this and keep their stack frames separate. This reduces
> the stack usage for update_display_info/drm_parse_cea_ext to
> a more manageable 744 bytes, while each parser only adds at most
> 200 bytes on top.
Is there really no way to tell the compiler to not do silly things in a
more generic way?
BR,
Jani.
>
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> ---
> drivers/gpu/drm/drm_edid.c | 31 +++++++++++++++++--------------
> 1 file changed, 17 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
> index 3148038747b6..913a8bbe8894 100644
> --- a/drivers/gpu/drm/drm_edid.c
> +++ b/drivers/gpu/drm/drm_edid.c
> @@ -5341,8 +5341,9 @@ static int edid_hfeeodb_extension_block_count(const struct edid *edid)
> * etc). For example, if the bit 0 in bitmap is set, first mode in VDB can
> * support YCBCR420 output too.
> */
> -static void parse_cta_y420cmdb(struct drm_connector *connector,
> - const struct cea_db *db, u64 *y420cmdb_map)
> +static noinline_for_stack void
> +parse_cta_y420cmdb(struct drm_connector *connector, const struct cea_db *db,
> + u64 *y420cmdb_map)
> {
> struct drm_display_info *info = &connector->display_info;
> int i, map_len = cea_db_payload_len(db) - 1;
> @@ -5515,7 +5516,7 @@ static uint8_t hdr_metadata_type(const u8 *edid_ext)
> BIT(HDMI_STATIC_METADATA_TYPE1);
> }
>
> -static void
> +static noinline_for_stack void
> drm_parse_hdr_metadata_block(struct drm_connector *connector, const u8 *db)
> {
> struct hdr_static_metadata *hdr_metadata =
> @@ -6070,7 +6071,8 @@ drm_default_rgb_quant_range(const struct drm_display_mode *mode)
> EXPORT_SYMBOL(drm_default_rgb_quant_range);
>
> /* CTA-861 Video Data Block (CTA VDB) */
> -static void parse_cta_vdb(struct drm_connector *connector, const struct cea_db *db)
> +static noinline_for_stack void
> +parse_cta_vdb(struct drm_connector *connector, const struct cea_db *db)
> {
> struct drm_display_info *info = &connector->display_info;
> int i, vic_index, len = cea_db_payload_len(db);
> @@ -6136,8 +6138,8 @@ static bool cta_vdb_has_vic(const struct drm_connector *connector, u8 vic)
> }
>
> /* CTA-861-H YCbCr 4:2:0 Video Data Block (CTA Y420VDB) */
> -static void parse_cta_y420vdb(struct drm_connector *connector,
> - const struct cea_db *db)
> +static noinline_for_stack void
> +parse_cta_y420vdb(struct drm_connector *connector, const struct cea_db *db)
> {
> struct drm_display_info *info = &connector->display_info;
> struct drm_hdmi_info *hdmi = &info->hdmi;
> @@ -6155,7 +6157,8 @@ static void parse_cta_y420vdb(struct drm_connector *connector,
> }
> }
>
> -static void drm_parse_vcdb(struct drm_connector *connector, const u8 *db)
> +static noinline_for_stack void
> +drm_parse_vcdb(struct drm_connector *connector, const u8 *db)
> {
> struct drm_display_info *info = &connector->display_info;
>
> @@ -6310,8 +6313,8 @@ static void drm_parse_dsc_info(struct drm_hdmi_dsc_cap *hdmi_dsc,
> }
>
> /* Sink Capability Data Structure */
> -static void drm_parse_hdmi_forum_scds(struct drm_connector *connector,
> - const u8 *hf_scds)
> +static noinline_for_stack void
> +drm_parse_hdmi_forum_scds(struct drm_connector *connector, const u8 *hf_scds)
> {
> struct drm_display_info *info = &connector->display_info;
> struct drm_hdmi_info *hdmi = &info->hdmi;
> @@ -6451,7 +6454,7 @@ static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector,
> }
>
> /* HDMI Vendor-Specific Data Block (HDMI VSDB, H14b-VSDB) */
> -static void
> +static noinline_for_stack void
> drm_parse_hdmi_vsdb_video(struct drm_connector *connector, const u8 *db)
> {
> struct drm_display_info *info = &connector->display_info;
> @@ -6486,8 +6489,8 @@ drm_parse_hdmi_vsdb_video(struct drm_connector *connector, const u8 *db)
> * See EDID extension for head-mounted and specialized monitors, specified at:
> * https://docs.microsoft.com/en-us/windows-hardware/drivers/display/specialized-monitors-edid-extension
> */
> -static void drm_parse_microsoft_vsdb(struct drm_connector *connector,
> - const u8 *db)
> +static noinline_for_stack void
> +drm_parse_microsoft_vsdb(struct drm_connector *connector, const u8 *db)
> {
> struct drm_display_info *info = &connector->display_info;
> u8 version = db[4];
> @@ -6558,8 +6561,8 @@ static void drm_parse_amd_vsdb_v3(struct drm_display_info *info,
> info->amd_vsdb.max_frame_rate = max_frame_rate;
> }
>
> -static void drm_parse_amd_vsdb(struct drm_connector *connector,
> - const struct cea_db *db)
> +static noinline_for_stack void
> +drm_parse_amd_vsdb(struct drm_connector *connector, const struct cea_db *db)
> {
> struct drm_display_info *info = &connector->display_info;
> const u8 *data = cea_db_data(db);
--
Jani Nikula, Intel
On Mon, Oct 19, 2026, at 15:28, Jani Nikula wrote:
> On Tue, 13 Oct 2026, Arnd Bergmann <arnd@kernel.org> wrote:
>> From: Arnd Bergmann <arnd@arndb.de>
>>
>> The drm_parse_cea_ext() calls into nine individual parser functions
>> based on the data format. If all of those get inlined and KASAN_STACK
>> is enabled, this can explode the combined stack usage beyond a
>> reasonable amount:
>>
>> drivers/gpu/drm/drm_edid.c: In function 'update_display_info':
>> drivers/gpu/drm/drm_edid.c:7040:1: error: the frame size of 1888 bytes is larger than 1536 bytes [-Werror=frame-larger-than=]
>> 7040 | }
>> | ^
>>
>> Mark all of the individual parser functions as noinline_for_stack
>> to avoid this and keep their stack frames separate. This reduces
>> the stack usage for update_display_info/drm_parse_cea_ext to
>> a more manageable 744 bytes, while each parser only adds at most
>> 200 bytes on top.
>
> Is there really no way to tell the compiler to not do silly things in a
> more generic way?
The problem is really the sanitizers here, which turn off the
optimizations that gcc relies on for its normal behavior and
which cause additional register pressure for the inserted function
calls.
Arnd
On Mon, 19 Oct 2026, "Arnd Bergmann" <arnd@arndb.de> wrote: > On Mon, Oct 19, 2026, at 15:28, Jani Nikula wrote: >> On Tue, 13 Oct 2026, Arnd Bergmann <arnd@kernel.org> wrote: >>> From: Arnd Bergmann <arnd@arndb.de> >>> >>> The drm_parse_cea_ext() calls into nine individual parser functions >>> based on the data format. If all of those get inlined and KASAN_STACK >>> is enabled, this can explode the combined stack usage beyond a >>> reasonable amount: >>> >>> drivers/gpu/drm/drm_edid.c: In function 'update_display_info': >>> drivers/gpu/drm/drm_edid.c:7040:1: error: the frame size of 1888 bytes is larger than 1536 bytes [-Werror=frame-larger-than=] >>> 7040 | } >>> | ^ >>> >>> Mark all of the individual parser functions as noinline_for_stack >>> to avoid this and keep their stack frames separate. This reduces >>> the stack usage for update_display_info/drm_parse_cea_ext to >>> a more manageable 744 bytes, while each parser only adds at most >>> 200 bytes on top. >> >> Is there really no way to tell the compiler to not do silly things in a >> more generic way? > > The problem is really the sanitizers here, which turn off the > optimizations that gcc relies on for its normal behavior and > which cause additional register pressure for the inserted function > calls. Cc: a bunch of KASAN folks. I think it's really silly and ugly to have to sprinkle noinline_for_stack all over the place in pretty normal code [1]. The stack usage for each individual function is fairly small. BR, Jani. [1] https://lore.kernel.org/r/20261013205223.2396822-1-arnd@kernel.org -- Jani Nikula, Intel
On Thu, Oct 22, 2026 at 1:37 PM Jani Nikula <jani.nikula@intel.com> wrote: > > On Mon, 19 Oct 2026, "Arnd Bergmann" <arnd@arndb.de> wrote: > > On Mon, Oct 19, 2026, at 15:28, Jani Nikula wrote: > >> On Tue, 13 Oct 2026, Arnd Bergmann <arnd@kernel.org> wrote: > >>> From: Arnd Bergmann <arnd@arndb.de> > >>> > >>> The drm_parse_cea_ext() calls into nine individual parser functions > >>> based on the data format. If all of those get inlined and KASAN_STACK > >>> is enabled, this can explode the combined stack usage beyond a > >>> reasonable amount: > >>> > >>> drivers/gpu/drm/drm_edid.c: In function 'update_display_info': > >>> drivers/gpu/drm/drm_edid.c:7040:1: error: the frame size of 1888 bytes is larger than 1536 bytes [-Werror=frame-larger-than=] > >>> 7040 | } > >>> | ^ > >>> > >>> Mark all of the individual parser functions as noinline_for_stack > >>> to avoid this and keep their stack frames separate. This reduces > >>> the stack usage for update_display_info/drm_parse_cea_ext to > >>> a more manageable 744 bytes, while each parser only adds at most > >>> 200 bytes on top. > >> > >> Is there really no way to tell the compiler to not do silly things in a > >> more generic way? > > > > The problem is really the sanitizers here, which turn off the > > optimizations that gcc relies on for its normal behavior and > > which cause additional register pressure for the inserted function > > calls. > > Cc: a bunch of KASAN folks. > > I think it's really silly and ugly to have to sprinkle > noinline_for_stack all over the place in pretty normal code [1]. The > stack usage for each individual function is fairly small. > Together with my LLM friend, I did some digging to see whether this can be handled on the compiler side instead. Summary of what we found (x86_64 defconfig + KASAN_STACK, GCC 15.2 and Clang 21, -fstack-usage on every object): 1. The immediate cause here is that GCC's inliner has no idea what the frame will cost under KASAN. Its stack estimate for a callee only sums the raw sizes of address-taken locals, so it never includes the ASan redzones and never sees the register spills that instrumentation causes. For all nine parsers in drm_parse_cea_ext() the estimate is literally 0 bytes, which short-circuits the -fconserve-stack limits, and since each parser has a single caller, -finline-functions-called-once inlines them regardless of size. 2. A generic knob therefore exists: passing -fno-inline-functions-called-once under CONFIG_KASAN_STACK with GCC brings update_display_info() from 704 to 344 bytes here without touching the source (drm_parse_cea_ext stays a separate 368-byte frame). Kernel-wide on that config it removes 22 of the 103 frames above 1024 bytes and introduces 6 new ones, for +1.2% text. 3. The GCC estimate itself is also fixable (make account_stack_vars() add the redzones it is going to allocate anyway, ~30 lines in cfgexpand.cc). That reduces total KASAN stack usage by ~7% for +0.35% text and fixes this particular case on its own (704 -> 368), so it is probably worth upstreaming. But it is a correctness fix, not a cure: it doesn't change the number of frames above 1024/2048 at all. 4. The reason is that the big frames under KASAN are overwhelmingly functions that already have 0.5-1.5KB of genuine locals without KASAN (97 of the 103 above 1024 bytes); KASAN then multiplies them by 1.5x (median) to 2.3x (p90) through redzones and spills. No inlining heuristic can undo that, and this is inherent in how the stack instrumentation works. So for KASAN_STACK builds we think FRAME_WARN simply has to be higher than for regular builds. For what it's worth, Clang does worse on the same config (149 frames above 1024 vs. 103 for GCC, 1176 bytes for update_display_info), which is why KASAN_STACK is already off by default there. Arnd, one thing I could not do is reproduce the 1888 bytes: with linux-next's drm_edid.c the worst we get is 704 on x86_64, 752 on arm64 and 1264 on arm64 with PROFILE_ALL_BRANCHES. Could you share the .config and GCC version?
> 3. The GCC estimate itself is also fixable (make account_stack_vars()
> add the redzones it is going to allocate anyway, ~30 lines in
> cfgexpand.cc). That reduces total KASAN stack usage by ~7% for
> +0.35% text and fixes this particular case on its own (704 -> 368),
> so it is probably worth upstreaming. But it is a correctness fix, not a
> cure: it doesn't change the number of frames above 1024/2048 at all.
For what it's worth, the patch looks like this:
diff --git a/gcc/cfgexpand.cc b/gcc/cfgexpand.cc
index 0261f861b..56e335cb2 100644
--- a/gcc/cfgexpand.cc
+++ b/gcc/cfgexpand.cc
@@ -1653,12 +1653,17 @@ expand_stack_vars (bool (*pred) (unsigned),
class stack_vars_data *data)
gcc_assert (known_eq (large_alloc, large_size));
}
-/* Take into account all sizes of partitions and reset DECL_RTLs. */
+/* Take into account all sizes of partitions and reset DECL_RTLs.
+ When the stack is sanitized by ASan, account for the red zones that
+ expand_stack_vars will later allocate around each protected variable,
+ so that the inliner's stack size estimate is not wildly optimistic. */
static poly_uint64
account_stack_vars (void)
{
unsigned si, j, i, n = stack_vars_num;
poly_uint64 size = 0;
+ bool asan_vars = false;
+ bool asan_p = asan_sanitize_stack_p ();
for (si = 0; si < n; ++si)
{
@@ -1668,10 +1673,32 @@ account_stack_vars (void)
if (stack_vars[i].representative != i)
continue;
- size += stack_vars[i].size;
+ poly_uint64 vsize = stack_vars[i].size;
+ unsigned HOST_WIDE_INT csize;
+ if (asan_p
+ && vsize.is_constant (&csize)
+ && stack_vars[i].alignb * BITS_PER_UNIT <= MAX_SUPPORTED_STACK_ALIGNMENT)
+ {
+ bool prot = false;
+ for (j = i; j != EOC; j = stack_vars[j].next)
+ if (asan_protect_stack_decl (stack_vars[j].decl))
+ {
+ prot = true;
+ break;
+ }
+ if (prot)
+ {
+ vsize = asan_var_and_redzone_size (csize);
+ asan_vars = true;
+ }
+ }
+ size += vsize;
for (j = i; j != EOC; j = stack_vars[j].next)
set_rtl (stack_vars[j].decl, NULL);
}
+ /* Leading red zone of the ASan protected frame region. */
+ if (asan_vars)
+ size += ASAN_RED_ZONE_SIZE;
return size;
}
© 2016 - 2026 Red Hat, Inc.