[PATCH] drm/edid: reduce update_display_info() stack usage

Arnd Bergmann posted 1 patch 1 week, 3 days ago
drivers/gpu/drm/drm_edid.c | 31 +++++++++++++++++--------------
1 file changed, 17 insertions(+), 14 deletions(-)
[PATCH] drm/edid: reduce update_display_info() stack usage
Posted by Arnd Bergmann 1 week, 3 days ago
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
Re: [PATCH] drm/edid: reduce update_display_info() stack usage
Posted by Jani Nikula 4 days, 23 hours ago
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
Re: [PATCH] drm/edid: reduce update_display_info() stack usage
Posted by Arnd Bergmann 4 days, 20 hours ago
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
Re: [PATCH] drm/edid: reduce update_display_info() stack usage
Posted by Jani Nikula 2 days, 1 hour ago
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
Re: [PATCH] drm/edid: reduce update_display_info() stack usage
Posted by Alexander Potapenko 1 day, 20 hours ago
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?
Re: [PATCH] drm/edid: reduce update_display_info() stack usage
Posted by Alexander Potapenko 1 day, 20 hours ago
> 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;
 }