diff mbox series

[v5,3/6] drm/edid: Add a function to match EDID with identity

Message ID 20240306200353.1436198-4-hsinyi@chromium.org (mailing list archive)
State New, archived
Headers show
Series Match panel with identity | expand

Commit Message

Hsin-Yi Wang March 6, 2024, 7:55 p.m. UTC
Create a type drm_edid_ident as the identity of an EDID. Currently it
contains panel id and monitor name.

Create a function that can match a given EDID and an identity:
1. Reject if the panel id doesn't match.
2. If name is not null in identity, try to match it in the detailed timing
   blocks. Note that some panel vendors put the monitor name after
   EDID_DETAIL_MONITOR_STRING.

Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
---
v4->v5: use strncmp, change function/variable names.
---
 drivers/gpu/drm/drm_edid.c | 65 ++++++++++++++++++++++++++++++++++++++
 include/drm/drm_edid.h     |  8 +++++
 2 files changed, 73 insertions(+)

Comments

Doug Anderson March 6, 2024, 11:29 p.m. UTC | #1
Hi,

On Wed, Mar 6, 2024 at 12:04 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
>
> +static void
> +match_identity(const struct detailed_timing *timing, void *data)
> +{
> +       struct drm_edid_match_closure *closure = data;
> +       unsigned int i;
> +       const char *name = closure->ident->name;
> +       unsigned int name_len = strlen(name);
> +       const char *desc = timing->data.other_data.data.str.str;
> +       unsigned int desc_len = ARRAY_SIZE(timing->data.other_data.data.str.str);
> +
> +       if (name_len > desc_len ||
> +           !(is_display_descriptor(timing, EDID_DETAIL_MONITOR_NAME) ||
> +             is_display_descriptor(timing, EDID_DETAIL_MONITOR_STRING)))
> +               return;
> +
> +       if (strncmp(name, desc, name_len))
> +               return;
> +
> +       /* Allow trailing white spaces and \0. */
> +       for (i = name_len; i < desc_len; i++) {
> +               if (desc[i] == '\n')
> +                       break;
> +               if (!isspace(desc[i]) && !desc[i])
> +                       return;
> +       }

If my code analysis is correct, I think you'll reject the case where:

name = "foo"
desc[13] = "foo \0zzzzzzzz"

...but you'll accept these cases:

desc[13] = "foo \nzzzzzzzz"
desc[13] = "foo \0\0\0\0\0\0\0\0\0"

It somehow seems weird to me that a '\n' terminates the string but not a '\0'.

I would have done:

      for (i = name_len; i < desc_len; i++) {
              /* Consider \n or \0 to terminate the string */
              if (desc[i] == '\n' || desc[i] == '\0')
                      break;
              /* OK for spaces at the end, but non-space is a fail */
              if (!isspace(desc[i]))
                      return;
      }


> @@ -367,6 +367,12 @@ struct edid {
>         u8 checksum;
>  } __attribute__((packed));
>
> +/* EDID matching */
> +struct drm_edid_ident {
> +       u32 panel_id;
> +       const char *name;

Might not hurt to have a comment for panel_id saying that it's encoded
by drm_edid_encode_panel_id() so it's obvious what this random u32 is.


-Doug
Hsin-Yi Wang March 7, 2024, 12:20 a.m. UTC | #2
On Wed, Mar 6, 2024 at 3:30 PM Doug Anderson <dianders@chromium.org> wrote:
>
> Hi,
>
> On Wed, Mar 6, 2024 at 12:04 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
> >
> > +static void
> > +match_identity(const struct detailed_timing *timing, void *data)
> > +{
> > +       struct drm_edid_match_closure *closure = data;
> > +       unsigned int i;
> > +       const char *name = closure->ident->name;
> > +       unsigned int name_len = strlen(name);
> > +       const char *desc = timing->data.other_data.data.str.str;
> > +       unsigned int desc_len = ARRAY_SIZE(timing->data.other_data.data.str.str);
> > +
> > +       if (name_len > desc_len ||
> > +           !(is_display_descriptor(timing, EDID_DETAIL_MONITOR_NAME) ||
> > +             is_display_descriptor(timing, EDID_DETAIL_MONITOR_STRING)))
> > +               return;
> > +
> > +       if (strncmp(name, desc, name_len))
> > +               return;
> > +
> > +       /* Allow trailing white spaces and \0. */
> > +       for (i = name_len; i < desc_len; i++) {
> > +               if (desc[i] == '\n')
> > +                       break;
> > +               if (!isspace(desc[i]) && !desc[i])
> > +                       return;
> > +       }
>
> If my code analysis is correct, I think you'll reject the case where:
>
> name = "foo"
> desc[13] = "foo \0zzzzzzzz"
>
> ...but you'll accept these cases:
>
> desc[13] = "foo \nzzzzzzzz"
> desc[13] = "foo \0\0\0\0\0\0\0\0\0"
>
> It somehow seems weird to me that a '\n' terminates the string but not a '\0'.

I'm also not sure about \0... based on
https://git.linuxtv.org/edid-decode.git/tree/parse-base-block.cpp#n493,
they use \n as terminator. Maybe we should also reject \0 before\n?
Since it's not printable.

>
> I would have done:
>
>       for (i = name_len; i < desc_len; i++) {
>               /* Consider \n or \0 to terminate the string */
>               if (desc[i] == '\n' || desc[i] == '\0')
>                       break;
>               /* OK for spaces at the end, but non-space is a fail */
>               if (!isspace(desc[i]))
>                       return;
>       }
>
>
> > @@ -367,6 +367,12 @@ struct edid {
> >         u8 checksum;
> >  } __attribute__((packed));
> >
> > +/* EDID matching */
> > +struct drm_edid_ident {
> > +       u32 panel_id;
> > +       const char *name;
>
> Might not hurt to have a comment for panel_id saying that it's encoded
> by drm_edid_encode_panel_id() so it's obvious what this random u32 is.
>
>
> -Doug
Doug Anderson March 7, 2024, 12:37 a.m. UTC | #3
Hi,

On Wed, Mar 6, 2024 at 4:20 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
>
> On Wed, Mar 6, 2024 at 3:30 PM Doug Anderson <dianders@chromium.org> wrote:
> >
> > Hi,
> >
> > On Wed, Mar 6, 2024 at 12:04 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
> > >
> > > +static void
> > > +match_identity(const struct detailed_timing *timing, void *data)
> > > +{
> > > +       struct drm_edid_match_closure *closure = data;
> > > +       unsigned int i;
> > > +       const char *name = closure->ident->name;
> > > +       unsigned int name_len = strlen(name);
> > > +       const char *desc = timing->data.other_data.data.str.str;
> > > +       unsigned int desc_len = ARRAY_SIZE(timing->data.other_data.data.str.str);
> > > +
> > > +       if (name_len > desc_len ||
> > > +           !(is_display_descriptor(timing, EDID_DETAIL_MONITOR_NAME) ||
> > > +             is_display_descriptor(timing, EDID_DETAIL_MONITOR_STRING)))
> > > +               return;
> > > +
> > > +       if (strncmp(name, desc, name_len))
> > > +               return;
> > > +
> > > +       /* Allow trailing white spaces and \0. */
> > > +       for (i = name_len; i < desc_len; i++) {
> > > +               if (desc[i] == '\n')
> > > +                       break;
> > > +               if (!isspace(desc[i]) && !desc[i])
> > > +                       return;
> > > +       }
> >
> > If my code analysis is correct, I think you'll reject the case where:
> >
> > name = "foo"
> > desc[13] = "foo \0zzzzzzzz"
> >
> > ...but you'll accept these cases:
> >
> > desc[13] = "foo \nzzzzzzzz"
> > desc[13] = "foo \0\0\0\0\0\0\0\0\0"
> >
> > It somehow seems weird to me that a '\n' terminates the string but not a '\0'.
>
> I'm also not sure about \0... based on
> https://git.linuxtv.org/edid-decode.git/tree/parse-base-block.cpp#n493,
> they use \n as terminator. Maybe we should also reject \0 before\n?
> Since it's not printable.

Ah, OK. I guess the EDID spec simply doesn't allow for '\0' in there.
I guess in that case I'd prefer simply removing the code to handle
'\0' instead of treating it like space until we see some actual need
for it. So just get rid of the "!desc[i]" case?

-Doug
Jani Nikula March 7, 2024, 1:20 p.m. UTC | #4
On Wed, 06 Mar 2024, Doug Anderson <dianders@chromium.org> wrote:
> Hi,
>
> On Wed, Mar 6, 2024 at 4:20 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
>>
>> On Wed, Mar 6, 2024 at 3:30 PM Doug Anderson <dianders@chromium.org> wrote:
>> >
>> > Hi,
>> >
>> > On Wed, Mar 6, 2024 at 12:04 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
>> > >
>> > > +static void
>> > > +match_identity(const struct detailed_timing *timing, void *data)
>> > > +{
>> > > +       struct drm_edid_match_closure *closure = data;
>> > > +       unsigned int i;
>> > > +       const char *name = closure->ident->name;
>> > > +       unsigned int name_len = strlen(name);
>> > > +       const char *desc = timing->data.other_data.data.str.str;
>> > > +       unsigned int desc_len = ARRAY_SIZE(timing->data.other_data.data.str.str);
>> > > +
>> > > +       if (name_len > desc_len ||
>> > > +           !(is_display_descriptor(timing, EDID_DETAIL_MONITOR_NAME) ||
>> > > +             is_display_descriptor(timing, EDID_DETAIL_MONITOR_STRING)))
>> > > +               return;
>> > > +
>> > > +       if (strncmp(name, desc, name_len))
>> > > +               return;
>> > > +
>> > > +       /* Allow trailing white spaces and \0. */
>> > > +       for (i = name_len; i < desc_len; i++) {
>> > > +               if (desc[i] == '\n')
>> > > +                       break;
>> > > +               if (!isspace(desc[i]) && !desc[i])
>> > > +                       return;
>> > > +       }
>> >
>> > If my code analysis is correct, I think you'll reject the case where:
>> >
>> > name = "foo"
>> > desc[13] = "foo \0zzzzzzzz"
>> >
>> > ...but you'll accept these cases:
>> >
>> > desc[13] = "foo \nzzzzzzzz"
>> > desc[13] = "foo \0\0\0\0\0\0\0\0\0"
>> >
>> > It somehow seems weird to me that a '\n' terminates the string but not a '\0'.
>>
>> I'm also not sure about \0... based on
>> https://git.linuxtv.org/edid-decode.git/tree/parse-base-block.cpp#n493,
>> they use \n as terminator. Maybe we should also reject \0 before\n?
>> Since it's not printable.
>
> Ah, OK. I guess the EDID spec simply doesn't allow for '\0' in there.
> I guess in that case I'd prefer simply removing the code to handle
> '\0' instead of treating it like space until we see some actual need
> for it. So just get rid of the "!desc[i]" case?

The spec text, similar for both EDID_DETAIL_MONITOR_NAME and
EDID_DETAIL_MONITOR_STRING:

	Up to 13 alphanumeric characters (using ASCII codes) may be used
	to define the model name of the display product. The data shall
	be sequenced such that the 1st byte (ASCII code) = the 1st
	character, the 2nd byte (ASCII code) = the 2nd character,
	etc. If there are less than 13 characters in the string, then
	terminate the display product name string with ASCII code ‘0Ah’
	(line feed) and pad the unused bytes in the field with ASCII
	code ‘20h’ (space).

In theory, only checking for '\n' for termination should be enough, and
this is what drm_edid_get_monitor_name() does. If there's a space
*before* that, it should be considered part of the name, and not
ignored. (So my suggestion in reply to the previous version is wrong.)

However, since the match name uses NUL termination, maybe we should
ignore NULs *before* '\n'? Like so:

for (i = name_len; i < desc_len; i++) {
        if (desc[i] == '\n')
                break;
        if (!desc[i])
                return;
}


BR,
Jani.


>
> -Doug
Hsin-Yi Wang March 7, 2024, 7:34 p.m. UTC | #5
On Thu, Mar 7, 2024 at 5:20 AM Jani Nikula <jani.nikula@linux.intel.com> wrote:
>
> On Wed, 06 Mar 2024, Doug Anderson <dianders@chromium.org> wrote:
> > Hi,
> >
> > On Wed, Mar 6, 2024 at 4:20 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
> >>
> >> On Wed, Mar 6, 2024 at 3:30 PM Doug Anderson <dianders@chromium.org> wrote:
> >> >
> >> > Hi,
> >> >
> >> > On Wed, Mar 6, 2024 at 12:04 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
> >> > >
> >> > > +static void
> >> > > +match_identity(const struct detailed_timing *timing, void *data)
> >> > > +{
> >> > > +       struct drm_edid_match_closure *closure = data;
> >> > > +       unsigned int i;
> >> > > +       const char *name = closure->ident->name;
> >> > > +       unsigned int name_len = strlen(name);
> >> > > +       const char *desc = timing->data.other_data.data.str.str;
> >> > > +       unsigned int desc_len = ARRAY_SIZE(timing->data.other_data.data.str.str);
> >> > > +
> >> > > +       if (name_len > desc_len ||
> >> > > +           !(is_display_descriptor(timing, EDID_DETAIL_MONITOR_NAME) ||
> >> > > +             is_display_descriptor(timing, EDID_DETAIL_MONITOR_STRING)))
> >> > > +               return;
> >> > > +
> >> > > +       if (strncmp(name, desc, name_len))
> >> > > +               return;
> >> > > +
> >> > > +       /* Allow trailing white spaces and \0. */
> >> > > +       for (i = name_len; i < desc_len; i++) {
> >> > > +               if (desc[i] == '\n')
> >> > > +                       break;
> >> > > +               if (!isspace(desc[i]) && !desc[i])
> >> > > +                       return;
> >> > > +       }
> >> >
> >> > If my code analysis is correct, I think you'll reject the case where:
> >> >
> >> > name = "foo"
> >> > desc[13] = "foo \0zzzzzzzz"
> >> >
> >> > ...but you'll accept these cases:
> >> >
> >> > desc[13] = "foo \nzzzzzzzz"
> >> > desc[13] = "foo \0\0\0\0\0\0\0\0\0"
> >> >
> >> > It somehow seems weird to me that a '\n' terminates the string but not a '\0'.
> >>
> >> I'm also not sure about \0... based on
> >> https://git.linuxtv.org/edid-decode.git/tree/parse-base-block.cpp#n493,
> >> they use \n as terminator. Maybe we should also reject \0 before\n?
> >> Since it's not printable.
> >
> > Ah, OK. I guess the EDID spec simply doesn't allow for '\0' in there.
> > I guess in that case I'd prefer simply removing the code to handle
> > '\0' instead of treating it like space until we see some actual need
> > for it. So just get rid of the "!desc[i]" case?
>
> The spec text, similar for both EDID_DETAIL_MONITOR_NAME and
> EDID_DETAIL_MONITOR_STRING:
>
>         Up to 13 alphanumeric characters (using ASCII codes) may be used
>         to define the model name of the display product. The data shall
>         be sequenced such that the 1st byte (ASCII code) = the 1st
>         character, the 2nd byte (ASCII code) = the 2nd character,
>         etc. If there are less than 13 characters in the string, then
>         terminate the display product name string with ASCII code ‘0Ah’
>         (line feed) and pad the unused bytes in the field with ASCII
>         code ‘20h’ (space).
>
> In theory, only checking for '\n' for termination should be enough, and
> this is what drm_edid_get_monitor_name() does. If there's a space
> *before* that, it should be considered part of the name, and not
> ignored. (So my suggestion in reply to the previous version is wrong.)
>
> However, since the match name uses NUL termination, maybe we should
> ignore NULs *before* '\n'? Like so:
>
> for (i = name_len; i < desc_len; i++) {
>         if (desc[i] == '\n')
>                 break;
>         if (!desc[i])
>                 return;
> }
>
Allow trailing white spaces so we don't need to add the trailing white
space in edp_panel_entry.

https://lore.kernel.org/lkml/CAA8EJpr7LHvqeGXhbFQ8KNn0LGDuv19cw0i04qVUz51TJeSQrA@mail.gmail.com/

>
> BR,
> Jani.
>
>
> >
> > -Doug
>
> --
> Jani Nikula, Intel
Jani Nikula March 7, 2024, 10:36 p.m. UTC | #6
On Thu, 07 Mar 2024, Hsin-Yi Wang <hsinyi@chromium.org> wrote:
> On Thu, Mar 7, 2024 at 5:20 AM Jani Nikula <jani.nikula@linux.intel.com> wrote:
>>
>> On Wed, 06 Mar 2024, Doug Anderson <dianders@chromium.org> wrote:
>> > Hi,
>> >
>> > On Wed, Mar 6, 2024 at 4:20 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
>> >>
>> >> On Wed, Mar 6, 2024 at 3:30 PM Doug Anderson <dianders@chromium.org> wrote:
>> >> >
>> >> > Hi,
>> >> >
>> >> > On Wed, Mar 6, 2024 at 12:04 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
>> >> > >
>> >> > > +static void
>> >> > > +match_identity(const struct detailed_timing *timing, void *data)
>> >> > > +{
>> >> > > +       struct drm_edid_match_closure *closure = data;
>> >> > > +       unsigned int i;
>> >> > > +       const char *name = closure->ident->name;
>> >> > > +       unsigned int name_len = strlen(name);
>> >> > > +       const char *desc = timing->data.other_data.data.str.str;
>> >> > > +       unsigned int desc_len = ARRAY_SIZE(timing->data.other_data.data.str.str);
>> >> > > +
>> >> > > +       if (name_len > desc_len ||
>> >> > > +           !(is_display_descriptor(timing, EDID_DETAIL_MONITOR_NAME) ||
>> >> > > +             is_display_descriptor(timing, EDID_DETAIL_MONITOR_STRING)))
>> >> > > +               return;
>> >> > > +
>> >> > > +       if (strncmp(name, desc, name_len))
>> >> > > +               return;
>> >> > > +
>> >> > > +       /* Allow trailing white spaces and \0. */
>> >> > > +       for (i = name_len; i < desc_len; i++) {
>> >> > > +               if (desc[i] == '\n')
>> >> > > +                       break;
>> >> > > +               if (!isspace(desc[i]) && !desc[i])
>> >> > > +                       return;
>> >> > > +       }
>> >> >
>> >> > If my code analysis is correct, I think you'll reject the case where:
>> >> >
>> >> > name = "foo"
>> >> > desc[13] = "foo \0zzzzzzzz"
>> >> >
>> >> > ...but you'll accept these cases:
>> >> >
>> >> > desc[13] = "foo \nzzzzzzzz"
>> >> > desc[13] = "foo \0\0\0\0\0\0\0\0\0"
>> >> >
>> >> > It somehow seems weird to me that a '\n' terminates the string but not a '\0'.
>> >>
>> >> I'm also not sure about \0... based on
>> >> https://git.linuxtv.org/edid-decode.git/tree/parse-base-block.cpp#n493,
>> >> they use \n as terminator. Maybe we should also reject \0 before\n?
>> >> Since it's not printable.
>> >
>> > Ah, OK. I guess the EDID spec simply doesn't allow for '\0' in there.
>> > I guess in that case I'd prefer simply removing the code to handle
>> > '\0' instead of treating it like space until we see some actual need
>> > for it. So just get rid of the "!desc[i]" case?
>>
>> The spec text, similar for both EDID_DETAIL_MONITOR_NAME and
>> EDID_DETAIL_MONITOR_STRING:
>>
>>         Up to 13 alphanumeric characters (using ASCII codes) may be used
>>         to define the model name of the display product. The data shall
>>         be sequenced such that the 1st byte (ASCII code) = the 1st
>>         character, the 2nd byte (ASCII code) = the 2nd character,
>>         etc. If there are less than 13 characters in the string, then
>>         terminate the display product name string with ASCII code ‘0Ah’
>>         (line feed) and pad the unused bytes in the field with ASCII
>>         code ‘20h’ (space).
>>
>> In theory, only checking for '\n' for termination should be enough, and
>> this is what drm_edid_get_monitor_name() does. If there's a space
>> *before* that, it should be considered part of the name, and not
>> ignored. (So my suggestion in reply to the previous version is wrong.)
>>
>> However, since the match name uses NUL termination, maybe we should
>> ignore NULs *before* '\n'? Like so:
>>
>> for (i = name_len; i < desc_len; i++) {
>>         if (desc[i] == '\n')
>>                 break;
>>         if (!desc[i])
>>                 return;
>> }
>>
> Allow trailing white spaces so we don't need to add the trailing white
> space in edp_panel_entry.

Just so it's clear here too: Agreed.

>
> https://lore.kernel.org/lkml/CAA8EJpr7LHvqeGXhbFQ8KNn0LGDuv19cw0i04qVUz51TJeSQrA@mail.gmail.com/
>
>>
>> BR,
>> Jani.
>>
>>
>> >
>> > -Doug
>>
>> --
>> Jani Nikula, Intel
diff mbox series

Patch

diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index febe374fa969..29ef35ebee32 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -100,6 +100,11 @@  struct detailed_mode_closure {
 	int modes;
 };
 
+struct drm_edid_match_closure {
+	const struct drm_edid_ident *ident;
+	bool matched;
+};
+
 #define LEVEL_DMT	0
 #define LEVEL_GTF	1
 #define LEVEL_GTF2	2
@@ -5405,6 +5410,66 @@  drm_parse_hdmi_vsdb_audio(struct drm_connector *connector, const u8 *db)
 		    connector->audio_latency[0], connector->audio_latency[1]);
 }
 
+static void
+match_identity(const struct detailed_timing *timing, void *data)
+{
+	struct drm_edid_match_closure *closure = data;
+	unsigned int i;
+	const char *name = closure->ident->name;
+	unsigned int name_len = strlen(name);
+	const char *desc = timing->data.other_data.data.str.str;
+	unsigned int desc_len = ARRAY_SIZE(timing->data.other_data.data.str.str);
+
+	if (name_len > desc_len ||
+	    !(is_display_descriptor(timing, EDID_DETAIL_MONITOR_NAME) ||
+	      is_display_descriptor(timing, EDID_DETAIL_MONITOR_STRING)))
+		return;
+
+	if (strncmp(name, desc, name_len))
+		return;
+
+	/* Allow trailing white spaces and \0. */
+	for (i = name_len; i < desc_len; i++) {
+		if (desc[i] == '\n')
+			break;
+		if (!isspace(desc[i]) && !desc[i])
+			return;
+	}
+
+	closure->matched = true;
+}
+
+/**
+ * drm_edid_match - match drm_edid with given identity
+ * @drm_edid: EDID
+ * @ident: the EDID identity to match with
+ *
+ * Check if the EDID matches with the given identity.
+ *
+ * Return: True if the given identity matched with EDID, false otherwise.
+ */
+bool drm_edid_match(const struct drm_edid *drm_edid,
+		    const struct drm_edid_ident *ident)
+{
+	if (!drm_edid || drm_edid_get_panel_id(drm_edid) != ident->panel_id)
+		return false;
+
+	/* Match with name only if it's not NULL. */
+	if (ident->name) {
+		struct drm_edid_match_closure closure = {
+			.ident = ident,
+			.matched = false,
+		};
+
+		drm_for_each_detailed_block(drm_edid, match_identity, &closure);
+
+		return closure.matched;
+	}
+
+	return true;
+}
+EXPORT_SYMBOL(drm_edid_match);
+
 static void
 monitor_name(const struct detailed_timing *timing, void *data)
 {
diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h
index 8b233865b085..28f05a7b2f7b 100644
--- a/include/drm/drm_edid.h
+++ b/include/drm/drm_edid.h
@@ -367,6 +367,12 @@  struct edid {
 	u8 checksum;
 } __attribute__((packed));
 
+/* EDID matching */
+struct drm_edid_ident {
+	u32 panel_id;
+	const char *name;
+};
+
 #define EDID_PRODUCT_ID(e) ((e)->prod_code[0] | ((e)->prod_code[1] << 8))
 
 /* Short Audio Descriptor */
@@ -567,6 +573,8 @@  struct edid *drm_get_edid(struct drm_connector *connector,
 			  struct i2c_adapter *adapter);
 const struct drm_edid *drm_edid_read_base_block(struct i2c_adapter *adapter);
 u32 drm_edid_get_panel_id(const struct drm_edid *drm_edid);
+bool drm_edid_match(const struct drm_edid *drm_edid,
+		    const struct drm_edid_ident *ident);
 struct edid *drm_get_edid_switcheroo(struct drm_connector *connector,
 				     struct i2c_adapter *adapter);
 struct edid *drm_edid_duplicate(const struct edid *edid);