<p dir="ltr">OK. I'm done.</p>
<p dir="ltr">I've literally been discussing these patches on this list for months, and you bring this up now?</p>
<p dir="ltr">It's far easier to simply recompile every kernel that comes out than to continue this dance.</p>
<div class="gmail_quote">On Oct 9, 2012 7:10 AM, "Jani Nikula" <<a href="mailto:jani.nikula@linux.intel.com">jani.nikula@linux.intel.com</a>> wrote:<br type="attribution"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
On Fri, 28 Sep 2012, Ian Pilcher <<a href="mailto:arequipeno@gmail.com">arequipeno@gmail.com</a>> wrote:<br>
> Add the ability for users to define their own EDID quirks via a<br>
> module parameter or sysfs attribute.<br>
<br>
Hi Ian -<br>
<br>
IMHO this patch should be chopped up to smaller pieces. For example,<br>
change the edid_quirk_list format first (if you must), then add module<br>
parameter support, then add sysfs support, in separate patches. It'll be<br>
easier to review.<br>
<br>
Please see some other comments inline.<br>
<br>
BR,<br>
Jani.<br>
<br>
><br>
> Signed-off-by: Ian Pilcher <<a href="mailto:arequipeno@gmail.com">arequipeno@gmail.com</a>><br>
> ---<br>
>  Documentation/EDID/edid_quirks.txt | 126 ++++++++++<br>
>  drivers/gpu/drm/drm_drv.c          |   2 +<br>
>  drivers/gpu/drm/drm_edid.c         | 500 ++++++++++++++++++++++++++++++++-----<br>
>  drivers/gpu/drm/drm_stub.c         |   6 +<br>
>  drivers/gpu/drm/drm_sysfs.c        |  19 ++<br>
>  include/drm/drmP.h                 |  10 +<br>
>  include/drm/drm_edid.h             |  13 +-<br>
>  7 files changed, 615 insertions(+), 61 deletions(-)<br>
>  create mode 100644 Documentation/EDID/edid_quirks.txt<br>
><br>
> diff --git a/Documentation/EDID/edid_quirks.txt b/Documentation/EDID/edid_quirks.txt<br>
> new file mode 100644<br>
> index 0000000..0c9c746<br>
> --- /dev/null<br>
> +++ b/Documentation/EDID/edid_quirks.txt<br>
> @@ -0,0 +1,126 @@<br>
> +                                  EDID Quirks<br>
> +                                 =============<br>
> +                       Ian Pilcher <<a href="mailto:arequipeno@gmail.com">arequipeno@gmail.com</a>><br>
> +                                August 11, 2012<br>
> +<br>
> +<br>
> +    "EDID blocks out in the wild have a variety of bugs"<br>
> +        -- from drivers/gpu/drm/drm_edid.c<br>
> +<br>
> +<br>
> +Overview<br>
> +========<br>
> +<br>
> +EDID quirks provide a mechanism for working around display hardware with buggy<br>
> +EDID data.<br>
> +<br>
> +An individual EDID quirk maps a display type (identified by its EDID<br>
> +manufacturer ID and product code[1]) to a set of "quirk flags."  The kernel<br>
> +includes a variety of built-in quirks.  (They are stored in the edid_quirk_list<br>
> +array in drivers/gpu/drm/drm_edid.c.)<br>
> +<br>
> +An example of a built-in EDID quirk is:<br>
> +<br>
> +    ACR:0xad46:0x00000001<br>
> +<br>
> +The first field is the manufacturer ID (Acer, Inc.), the second field is the<br>
> +manufacturer's product code, and the third field contains the quirk flags for<br>
> +that display type.<br>
> +<br>
> +The quirk flags are defined in drivers/gpu/drm/drm_edid.c.  Each flag has a<br>
> +symbolic name beginning with EDID_QUIRK_, along with a numerical value.  Each<br>
> +flag should also have an associated comment which provides an idea of its<br>
> +effect.  Note that the values in the source file are expressed as bit shifts[2]:<br>
> +<br>
> +    * 1 << 0: 0x0001<br>
> +    * 1 << 1: 0x0002<br>
> +    * 1 << 2: 0x0004<br>
> +    * etc.<br>
> +<br>
> +<br>
> +sysfs interface<br>
> +===============<br>
> +<br>
> +The current EDID quirk list can be read from /sys/class/drm/edid_quirks:<br>
> +<br>
> +    # cat /sys/class/drm/edid_quirks<br>
> +       ACR:0xad46:0x00000001<br>
> +       API:0x7602:0x00000001<br>
> +       ACR:0x0977:0x00000020<br>
> +    0x9e6a:0x077e:0x00000080<br>
> +    ...<br>
> +<br>
> +("Nonconformant" manufacturer IDs are displayed as hexadecimal values.)<br>
> +<br>
> +The number of total "slots" in the list can be read from<br>
> +/sys/class/drm/edid_quirks_size.  This total includes both occupied slots (i.e.<br>
> +the current list) and any slots available for additional quirks.  The number of<br>
> +available slots can be calculated by subtracting the number of quirks in the<br>
> +current list from the total number of slots.<br>
> +<br>
> +If a slot is available, an additional quirk can be added to the list by writing<br>
> +it to /sys/class/drm/edid_quirks:<br>
> +<br>
> +    # echo FOO:0xffff:0x100 > /sys/class/drm/edid_quirks<br>
> +<br>
> +Manufacturer IDs can also be specified numerically.  (This is the only way to<br>
> +specify a nonconformant ID.) This command is equivalent to the previous one:<br>
> +<br>
> +    # echo 0x19ef:0xffff:0x100 > /sys/class/drm/edid_quirks<br>
> +<br>
> +Numeric values can also be specified in decimal or octal formats; a number that<br>
> +begins with a 0 is assumed to be octal:<br>
> +<br>
> +    # echo FOO:65535:0400 > /sys/class/drm/edid_quirks<br>
> +<br>
> +An existing quirk can be replaced by writing a new set of flags:<br>
> +<br>
> +    # echo FOO:0xffff:0x200 > /sys/class/drm/edid_quirks<br>
> +<br>
> +A quirk can be deleted from the list by writing an empty flag set (0). This<br>
> +makes the slot occupied by that quirk available.<br>
> +<br>
> +    # echo FOO:0xffff:0 > /sys/class/drm/edid_quirks<br>
> +<br>
> +Writing an "at symbol" (@) clears the entire quirk list:<br>
> +<br>
> +    # echo @ > /sys/class/drm/edid_quirks<br>
> +<br>
> +Multiple changes to the list can be specified in a comma (or newline) separated<br>
> +list. For example, the following command clears all of the existing quirks in<br>
> +the list and adds 3 new quirks:<br>
> +<br>
> +    # echo @,FOO:0xffff:0x100,BAR:0x1111:0x001,BAZ:0x2222:0x002 > \<br>
> +            /sys/class/drm/edid_quirks<br>
> +<br>
> +Note however, that any error (an incorrectly formatted quirk or an attempt to<br>
> +add a quirk when no slot is available) will abort processing of any further<br>
> +changes, potentially making it difficult to determine exactly which change<br>
> +caused the error and what changes were made.  For this reason, making changes<br>
> +one at a time is recommended, particularly if the changes are being made by a<br>
> +script or program.<br>
<br>
Generally it seems like a bad idea to add support for something you<br>
specifically recommend against using. It should be a hint not to add<br>
it. It looks like you support multiple changes in sysfs only because it<br>
comes free with the module parameter support.<br>
<br>
> +<br>
> +<br>
> +Module parameter<br>
> +================<br>
> +<br>
> +The EDID quirk list can also be modified via the edid_quirks module parameter<br>
> +(drm.edid_quirks on the kernel command line).  The effect of setting this<br>
> +parameter is identical to the effect of writing its value to<br>
> +/sys/class/drm/edid_quirks, with one important difference.  When an error is<br>
> +encountered during module parameter parsing or processing, any remaining quirks<br>
> +in the parameter string will still be processed.  (It is hoped that this approach<br>
> +maximizes the probability of producing a working display.)<br>
> +<br>
> +<br>
> +Follow-up<br>
> +=========<br>
> +<br>
> +If you encounter a display that requires an additional EDID quirk in order to<br>
> +function properly, please report it to the direct rendering development mailing<br>
> +list <<a href="mailto:dri-devel@lists.freedesktop.org">dri-devel@lists.freedesktop.org</a>>.<br>
> +<br>
> +<br>
> +[1] See <a href="http://en.wikipedia.org/wiki/Extended_display_identification_data" target="_blank">http://en.wikipedia.org/wiki/Extended_display_identification_data</a> for a<br>
> +    description of the manufacturer ID and product code fields.<br>
> +[2] <a href="https://en.wikipedia.org/wiki/Bitwise_operation#Bit_shifts" target="_blank">https://en.wikipedia.org/wiki/Bitwise_operation#Bit_shifts</a><br>
> diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c<br>
> index 9238de4..7fe39e0 100644<br>
> --- a/drivers/gpu/drm/drm_drv.c<br>
> +++ b/drivers/gpu/drm/drm_drv.c<br>
> @@ -276,6 +276,8 @@ static int __init drm_core_init(void)<br>
>               goto err_p3;<br>
>       }<br>
><br>
> +     drm_edid_quirks_param_process();<br>
> +<br>
>       DRM_INFO("Initialized %s %d.%d.%d %s\n",<br>
>                CORE_NAME, CORE_MAJOR, CORE_MINOR, CORE_PATCHLEVEL, CORE_DATE);<br>
>       return 0;<br>
> diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c<br>
> index a8743c3..ea535f6 100644<br>
> --- a/drivers/gpu/drm/drm_edid.c<br>
> +++ b/drivers/gpu/drm/drm_edid.c<br>
> @@ -31,6 +31,8 @@<br>
>  #include <linux/slab.h><br>
>  #include <linux/i2c.h><br>
>  #include <linux/module.h><br>
> +#include <linux/ctype.h><br>
> +<br>
>  #include "drmP.h"<br>
>  #include "drm_edid.h"<br>
>  #include "drm_edid_modes.h"<br>
> @@ -82,51 +84,457 @@ struct detailed_mode_closure {<br>
>  #define LEVEL_GTF2   2<br>
>  #define LEVEL_CVT    3<br>
><br>
> -static struct edid_quirk {<br>
> -     char vendor[4];<br>
> -     int product_id;<br>
> -     u32 quirks;<br>
> -} edid_quirk_list[] = {<br>
> +union edid_quirk {<br>
> +     struct {<br>
> +             union edid_display_id display_id;<br>
> +             u32 quirks;<br>
> +     } __attribute__((packed)) s;<br>
> +     u64 u;<br>
> +};<br>
<br>
This does not need to be an union. Just make it a struct, and in the<br>
couple of places you need .u, you can do a memset and a struct<br>
assignment or memcpy.<br>
<br>
> +<br>
> +#define EDID_MFG_ID(c1, c2, c3)              cpu_to_be16(                    \<br>
> +                                             (c1 & 0x1f) << 10 |     \<br>
> +                                             (c2 & 0x1f) << 5 |      \<br>
> +                                             (c3 & 0x1f)             \<br>
> +                                     )<br>
> +<br>
> +#define EDID_QUIRK_LIST_SIZE 24<br>
> +<br>
> +union edid_quirk edid_quirk_list[EDID_QUIRK_LIST_SIZE] = {<br>
> +<br>
>       /* Acer AL1706 */<br>
> -     { "ACR", 44358, EDID_QUIRK_PREFER_LARGE_60 },<br>
> +     { { { { EDID_MFG_ID('A', 'C', 'R'), cpu_to_le16(44358) } },<br>
> +             EDID_QUIRK_PREFER_LARGE_60 } },<br>
<br>
I wonder whether would be better to have this all in cpu byte order and<br>
code to handle it, or this confusing mixture of explicit big-endian,<br>
explicit little-endian, and cpu order. Someone, somewhere is bound to<br>
miss a byte order change later on...<br>
<br>
>       /* Acer F51 */<br>
> -     { "API", 0x7602, EDID_QUIRK_PREFER_LARGE_60 },<br>
> +     { { { { EDID_MFG_ID('A', 'P', 'I'), cpu_to_le16(0x7602) } },<br>
> +             EDID_QUIRK_PREFER_LARGE_60 } },<br>
>       /* Unknown Acer */<br>
> -     { "ACR", 2423, EDID_QUIRK_FIRST_DETAILED_PREFERRED },<br>
> +     { { { { EDID_MFG_ID('A', 'C', 'R'), cpu_to_le16(2423) } },<br>
> +             EDID_QUIRK_FIRST_DETAILED_PREFERRED } },<br>
><br>
>       /* Belinea 10 15 55 */<br>
> -     { "MAX", 1516, EDID_QUIRK_PREFER_LARGE_60 },<br>
> -     { "MAX", 0x77e, EDID_QUIRK_PREFER_LARGE_60 },<br>
> +     { { { { EDID_MFG_ID('M', 'A', 'X'), cpu_to_le16(1516) } },<br>
> +             EDID_QUIRK_PREFER_LARGE_60 } },<br>
> +     { { { { EDID_MFG_ID('M', 'A', 'X'), cpu_to_le16(0x77e) } },<br>
> +             EDID_QUIRK_PREFER_LARGE_60 } },<br>
><br>
>       /* Envision Peripherals, Inc. EN-7100e */<br>
> -     { "EPI", 59264, EDID_QUIRK_135_CLOCK_TOO_HIGH },<br>
> +     { { { { EDID_MFG_ID('E', 'P', 'I'), cpu_to_le16(59264) } },<br>
> +             EDID_QUIRK_135_CLOCK_TOO_HIGH } },<br>
>       /* Envision EN2028 */<br>
> -     { "EPI", 8232, EDID_QUIRK_PREFER_LARGE_60 },<br>
> +     { { { { EDID_MFG_ID('E', 'P', 'I'), cpu_to_le16(8232) } },<br>
> +             EDID_QUIRK_PREFER_LARGE_60 } },<br>
><br>
>       /* Funai Electronics PM36B */<br>
> -     { "FCM", 13600, EDID_QUIRK_PREFER_LARGE_75 |<br>
> -       EDID_QUIRK_DETAILED_IN_CM },<br>
> +     { { { { EDID_MFG_ID('F', 'C', 'M'), cpu_to_le16(13600) } },<br>
> +             EDID_QUIRK_PREFER_LARGE_75 | EDID_QUIRK_DETAILED_IN_CM } },<br>
><br>
>       /* LG Philips LCD LP154W01-A5 */<br>
> -     { "LPL", 0, EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE },<br>
> -     { "LPL", 0x2a00, EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE },<br>
> +     { { { { EDID_MFG_ID('L', 'P', 'L'), cpu_to_le16(0) } },<br>
> +             EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE } },<br>
> +     { { { { EDID_MFG_ID('L', 'P', 'L'), cpu_to_le16(0x2a00) } },<br>
> +             EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE } },<br>
><br>
>       /* Philips 107p5 CRT */<br>
> -     { "PHL", 57364, EDID_QUIRK_FIRST_DETAILED_PREFERRED },<br>
> +     { { { { EDID_MFG_ID('P', 'H', 'L'), cpu_to_le16(57364) } },<br>
> +             EDID_QUIRK_FIRST_DETAILED_PREFERRED } },<br>
><br>
>       /* Proview AY765C */<br>
> -     { "PTS", 765, EDID_QUIRK_FIRST_DETAILED_PREFERRED },<br>
> +     { { { { EDID_MFG_ID('P', 'T', 'S'), cpu_to_le16(765) } },<br>
> +             EDID_QUIRK_FIRST_DETAILED_PREFERRED } },<br>
><br>
>       /* Samsung SyncMaster 205BW.  Note: irony */<br>
> -     { "SAM", 541, EDID_QUIRK_DETAILED_SYNC_PP },<br>
> +     { { { { EDID_MFG_ID('S', 'A', 'M'), cpu_to_le16(541) } },<br>
> +             EDID_QUIRK_DETAILED_SYNC_PP } },<br>
>       /* Samsung SyncMaster 22[5-6]BW */<br>
> -     { "SAM", 596, EDID_QUIRK_PREFER_LARGE_60 },<br>
> -     { "SAM", 638, EDID_QUIRK_PREFER_LARGE_60 },<br>
> +     { { { { EDID_MFG_ID('S', 'A', 'M'), cpu_to_le16(596) } },<br>
> +             EDID_QUIRK_PREFER_LARGE_60 } },<br>
> +     { { { { EDID_MFG_ID('S', 'A', 'M'), cpu_to_le16(638) } },<br>
> +             EDID_QUIRK_PREFER_LARGE_60 } },<br>
><br>
>       /* ViewSonic VA2026w */<br>
> -     { "VSC", 5020, EDID_QUIRK_FORCE_REDUCED_BLANKING },<br>
> +     { { { { EDID_MFG_ID('V', 'S', 'C'), cpu_to_le16(5020) } },<br>
> +             EDID_QUIRK_FORCE_REDUCED_BLANKING } },<br>
> +<br>
> +     /*<br>
> +      * When adding built-in quirks, please adjust EDID_QUIRK_LIST_SIZE to<br>
> +      * provide some room for user-supplied quirks.<br>
> +      */<br>
>  };<br>
><br>
> +DEFINE_MUTEX(edid_quirk_list_mutex);<br>
> +<br>
> +/**<br>
> + * drm_edid_mfg_format - format an "encoded" EDID manufacturer ID for printing<br>
> + * @mfg_id: the encoded manufacturer ID<br>
> + * @buf: destination buffer for the formatted manufacturer ID (minimum 7 bytes)<br>
> + * @strip: if non-zero, the returned pointer will skip any leading spaces<br>
> + *<br>
> + * An EDID manufacturer ID is supposed to consist of 3 capital letters (A-Z).<br>
> + * Each letter is stored as a 5-bit value between 1 and 26, taking up 15 bits of<br>
> + * the 16-bit ID. The remaining bit should always be 0. If display manufacturers<br>
> + * always did things correctly, however, EDID quirks wouldn't be required in<br>
> + * the first place. This function does the following:<br>
> + *<br>
> + * - Broken IDs are printed in hexadecimal (0xffff).<br>
> + * - "Correct" IDs are formatted as a 3-letter ID string, preceded by 3 spaces;<br>
> + *   the spaces ensure that both output formats are the same length.<br>
> + *<br>
> + * Thus, a formatted manufacturer ID is always 6 characters long (not including<br>
> + * the terminating 0).<br>
> + *<br>
> + * If @strip is 0, or the manufacturer ID has been formatted as a hexadecimal<br>
> + * number, @buf is returned.  If @strip is non-zero, and the manufacturer ID has<br>
> + * been formatted as a 3-letter string, a pointer to the first non-space<br>
> + * character (@buf + 3) is returned.<br>
> + */<br>
> +static const char *drm_edid_mfg_format(__be16 mfg_id, char *buf, int strip)<br>
> +{<br>
> +     u16 id = be16_to_cpu(mfg_id);<br>
> +<br>
> +     if (id & 0x8000)<br>
> +             goto bad_id;<br>
> +<br>
> +     buf[3] = ((id & 0x7c00) >> 10) + '@';<br>
<br>
Shift first and you can use the same mask for all three.<br>
<br>
> +     if (!isupper(buf[3]))<br>
> +             goto bad_id;<br>
> +<br>
> +     buf[4] = ((id & 0x03e0) >> 5) + '@';<br>
> +     if (!isupper(buf[4]))<br>
> +             goto bad_id;<br>
> +<br>
> +     buf[5] = (id & 0x001f) + '@';<br>
> +     if (!isupper(buf[5]))<br>
> +             goto bad_id;<br>
> +<br>
> +     memset(buf, ' ', 3);<br>
> +     buf[6] = 0;<br>
> +<br>
> +     return strip ? (buf + 3) : buf;<br>
> +<br>
> +bad_id:<br>
> +     sprintf(buf, "0x%04hx", id);<br>
> +     return buf;<br>
> +}<br>
> +<br>
> +#define EDID_MFG_BUF_SIZE            7<br>
> +<br>
> +/**<br>
> + * drm_edid_display_id_format - format an EDID "display ID" (manufacturer ID<br>
> + *                           and product code) for printing<br>
> + * @display_id: the display ID<br>
> + * @buf: destination buffer for the formatted display ID (minimum 14 bytes)<br>
> + * @strip: if non-zero, the returned pointer will skip any leading spaces<br>
> + *<br>
> + * A formatted display ID is always 13 characters long (not including the<br>
> + * terminating 0).<br>
> + *<br>
> + * If @strip is 0, or the manufacturer ID has been formatted as a hexadecimal<br>
> + * number, @buf is returned.  If @strip is non-zero, and the manufacturer ID has<br>
> + * been formatted as a 3-letter string, a pointer to the first non-space<br>
> + * character (@buf + 3) is returned.<br>
> + */<br>
> +static const char *drm_edid_display_id_format(union edid_display_id display_id,<br>
> +                                           char *buf, int strip)<br>
> +{<br>
> +     const char *s;<br>
> +<br>
> +     s = drm_edid_mfg_format(display_id.s.mfg_id, buf, strip);<br>
> +     sprintf(buf + EDID_MFG_BUF_SIZE - 1, ":0x%04hx",<br>
> +             le16_to_cpu(display_id.s.prod_code));<br>
> +<br>
> +     return s;<br>
> +}<br>
> +<br>
> +#define EDID_DISPLAY_ID_BUF_SIZE     (EDID_MFG_BUF_SIZE + 7)<br>
> +<br>
> +/**<br>
> + * drm_edid_quirk_format - format an EDID quirk for printing<br>
> + * @quirk: the quirk<br>
> + * @buf: destination buffer for the formatted quirk (minimum 25 bytes)<br>
> + * @strip: if non-zero, the returned pointer will skip any leading spaces<br>
> + *<br>
> + * A formatted EDID quirk is always 24 characters long (not including the<br>
> + * terminating 0).<br>
> + *<br>
> + * If @strip is 0, or the manufacturer ID has been formatted as a hexadecimal<br>
> + * number, @buf is returned.  If @strip is non-zero, and the manufacturer ID has<br>
> + * been formatted as a 3-letter string, a pointer to the first non-space<br>
> + * character (@buf + 3) is returned.<br>
> + */<br>
> +static const char *drm_edid_quirk_format(const union edid_quirk *quirk,<br>
> +                                      char *buf, int strip)<br>
> +{<br>
> +     const char *s;<br>
> +<br>
> +     s = drm_edid_display_id_format(quirk->s.display_id, buf, strip);<br>
> +     sprintf(buf + EDID_DISPLAY_ID_BUF_SIZE - 1, ":0x%08x", quirk->s.quirks);<br>
> +<br>
> +     return s;<br>
> +}<br>
> +<br>
> +#define EDID_QUIRK_BUF_SIZE          (EDID_DISPLAY_ID_BUF_SIZE + 11)<br>
> +<br>
> +/**<br>
> + * drm_edid_quirk_parse - parse an EDID quirk<br>
> + * @s: string containing the quirk to be parsed<br>
> + * @quirk: destination for parsed quirk<br>
> + *<br>
> + * Returns 0 on success, < 0 (currently -EINVAL) on error.<br>
> + */<br>
> +static int drm_edid_quirk_parse(const char *s, union edid_quirk *quirk)<br>
> +{<br>
> +     char buf[EDID_QUIRK_BUF_SIZE];<br>
> +     s32 mfg;<br>
> +     s32 product;<br>
> +     s64 quirks;<br>
> +     char *c;<br>
> +<br>
> +     if (sscanf(s, "%i:%i:%lli", &mfg, &product, &quirks) == 3) {<br>
> +             if (mfg < 0 || mfg > 0xffff)<br>
> +                     goto error;<br>
> +             quirk->s.display_id.s.mfg_id = cpu_to_be16((u16)mfg);<br>
> +     } else {<br>
> +             if (sscanf(s, "%3s:%i:%lli", buf, &product, &quirks) != 3 ||<br>
> +                             !isupper(buf[0]) ||<br>
> +                             !isupper(buf[1]) ||<br>
> +                             !isupper(buf[2]))<br>
> +                     goto error;<br>
> +             quirk->s.display_id.s.mfg_id =<br>
> +                             EDID_MFG_ID(buf[0], buf[1], buf[2]);<br>
> +     }<br>
> +<br>
> +     if (product < 0 || product > 0xffff ||<br>
> +                     quirks < 0 || quirks > 0xffffffffLL)<br>
> +             goto error;<br>
> +<br>
> +     quirk->s.display_id.s.prod_code = cpu_to_le16((u16)product);<br>
> +     quirk->s.quirks = (u32)quirks;<br>
> +<br>
> +     DRM_DEBUG("Successfully parsed EDID quirk: %s\n",<br>
> +               drm_edid_quirk_format(quirk, buf, 1));<br>
> +<br>
> +     return 0;<br>
> +<br>
> +error:<br>
> +     c = strpbrk(s, ",\n");<br>
> +     if (c == NULL) {<br>
> +             printk(KERN_WARNING "Invalid EDID quirk: '%s'\n", s);<br>
> +     } else {<br>
> +             printk(KERN_WARNING "Invalid EDID quirk: '%.*s'\n",<br>
> +                   (int)(c - s), s);<br>
> +     }<br>
> +<br>
> +     return -EINVAL;<br>
> +}<br>
> +<br>
> +/**<br>
> + * drm_edid_quirk_find_by_id - find the EDID quirk matching a display ID<br>
> + * @display_id: the display ID to match<br>
> + *<br>
> + * Caller MUST hold edid_quirk_list_mutex.<br>
> + *<br>
> + * Returns a pointer to the matching quirk list entry, NULL if no such entry<br>
> + * exists.<br>
> + */<br>
> +static union edid_quirk *drm_edid_quirk_find_by_id(union edid_display_id id)<br>
> +{<br>
> +     union edid_quirk *q = edid_quirk_list;<br>
> +<br>
> +     do {<br>
> +             if (q->s.display_id.u == id.u && q->s.quirks != 0)<br>
> +                     return q;<br>
> +     } while (++q < edid_quirk_list + ARRAY_SIZE(edid_quirk_list));<br>
<br>
The same with less cognitive burden on the reader:<br>
<br>
        for (i = 0; i < ARRAY_SIZE(edid_quirk_list); i++) {<br>
                ...<br>
        }<br>
<br>
Ditto elsewhere.<br>
<br>
> +<br>
> +     return NULL;<br>
> +}<br>
> +<br>
> +/**<br>
> + * drm_edid_quirk_find_slot - find an empty slot in the EDID quirk list<br>
> + *<br>
> + * Caller MUST hold edid_quirk_list_mutex.<br>
> + *<br>
> + * Returns a pointer to the first empty slot, NULL if no empty slots exist.<br>
> + */<br>
> +static union edid_quirk *drm_edid_quirk_find_empty(void)<br>
> +{<br>
> +     union edid_quirk *q = edid_quirk_list;<br>
> +<br>
> +     do {<br>
> +             if (q->s.quirks == 0)<br>
> +                     return q;<br>
> +     } while (++q < edid_quirk_list + ARRAY_SIZE(edid_quirk_list));<br>
> +<br>
> +     return NULL;<br>
> +}<br>
> +<br>
> +/**<br>
> + * drm_edid_quirk_process - process a newly parsed EDID quirk<br>
> + * @quirk: the quirk to be processed<br>
> + *<br>
> + * Depending on the newly parsed quirk and the contents of the quirks list, this<br>
> + * function will add, remove, or replace a quirk.<br>
> + *<br>
> + * Returns 0 on success, < 0 on error (-ENOSPC if there is no free slot for a<br>
> + * new quirk). Note that trying to remove a quirk that isn't present is not<br>
> + * considered an error.<br>
> + */<br>
> +static int drm_edid_quirk_process(const union edid_quirk *quirk)<br>
> +{<br>
> +     char buf[EDID_QUIRK_BUF_SIZE];<br>
> +     union edid_quirk *q;<br>
> +     int res = 0;<br>
> +<br>
> +     mutex_lock(&edid_quirk_list_mutex);<br>
> +<br>
> +     if (quirk->s.quirks == 0) {<br>
> +             DRM_INFO("Removing EDID quirk for display %s\n",<br>
> +                      drm_edid_display_id_format(quirk->s.display_id,<br>
> +                                                 buf, 1));<br>
> +             q = drm_edid_quirk_find_by_id(quirk->s.display_id);<br>
> +             if (q == NULL) {<br>
> +                     printk(KERN_WARNING "No quirk found for display %s\n",<br>
> +                            drm_edid_display_id_format(quirk->s.display_id,<br>
> +                                                       buf, 1));<br>
> +             } else {<br>
> +                     q->u = 0;<br>
<br>
Ditch the union and use memset.<br>
<br>
> +             }<br>
> +     } else {<br>
> +             DRM_INFO("Adding EDID quirk: %s\n",<br>
> +                      drm_edid_quirk_format(quirk, buf, 1));<br>
> +             q = drm_edid_quirk_find_by_id(quirk->s.display_id);<br>
> +             if (q == NULL) {<br>
> +                     q = drm_edid_quirk_find_empty();<br>
> +                     if (q == NULL) {<br>
> +                             printk(KERN_WARNING<br>
> +                                    "No free slot in EDID quirk list\n");<br>
> +                             res = -ENOSPC;<br>
> +                     } else {<br>
> +                             q->u = quirk->u;<br>
<br>
Ditch the union and use memcpy or struct assignment.<br>
<br>
> +                     }<br>
> +             } else {<br>
> +                     DRM_INFO("Replacing existing quirk: %s\n",<br>
> +                              drm_edid_quirk_format(q, buf, 1));<br>
> +                     q->s.quirks = quirk->s.quirks;<br>
> +             }<br>
> +     }<br>
> +<br>
> +     mutex_unlock(&edid_quirk_list_mutex);<br>
> +<br>
> +     return res;<br>
> +}<br>
> +<br>
> +/**<br>
> + * drm_edid_quirks_process - parse and process a comma separated list of EDID<br>
> + *                        quirks<br>
> + * @s: string containing the quirks to be processed<br>
> + * @strict: if non-zero, any parsing or processing error aborts further<br>
> + *       processing<br>
> + *<br>
> + * Returns 0 on success, < 0 if any error is encountered.  (If multiple errors<br>
> + * occur when strict is set to 0, the last error encountered is returned.)<br>
> + */<br>
> +static int drm_edid_quirks_process(const char *s, int strict)<br>
> +{<br>
> +     union edid_quirk quirk;<br>
> +     int res = 0;<br>
> +<br>
> +     do {<br>
> +<br>
> +             if (*s == '@') {<br>
> +                     DRM_INFO("Clearing EDID quirk list\n");<br>
> +                     mutex_lock(&edid_quirk_list_mutex);<br>
> +                     memset(edid_quirk_list, 0, sizeof edid_quirk_list);<br>
> +                     mutex_unlock(&edid_quirk_list_mutex);<br>
> +             } else {<br>
> +                     res = drm_edid_quirk_parse(s, &quirk);<br>
> +                     if (res != 0) {<br>
> +                             if (strict)<br>
> +                                     goto error;<br>
> +                             continue;<br>
> +                     }<br>
> +<br>
> +                     res = drm_edid_quirk_process(&quirk);<br>
> +                     if (res != 0) {<br>
> +                             if (strict)<br>
> +                                     goto error;<br>
> +                     }<br>
> +             }<br>
> +<br>
> +             s = strpbrk(s, ",\n");<br>
> +<br>
> +     } while (s != NULL && *(++s) != 0);<br>
> +<br>
> +     return res;<br>
> +<br>
> +error:<br>
> +     printk(KERN_WARNING "Aborting EDID quirk parsing\n");<br>
> +     return res;<br>
> +}<br>
> +<br>
> +/**<br>
> + * drm_edid_quirks_param_process - process the edid_quirks module parameter<br>
> + */<br>
> +void drm_edid_quirks_param_process(void)<br>
> +{<br>
> +     if (drm_edid_quirks != NULL)<br>
> +             drm_edid_quirks_process(drm_edid_quirks, 0);<br>
> +}<br>
> +<br>
> +/**<br>
> + * drm_edid_quirks_size_show - show the size of the EDID quirk list in sysfs<br>
> + * @buf: destination buffer (PAGE_SIZE bytes)<br>
> + */<br>
> +ssize_t drm_edid_quirks_size_show(struct class *class,<br>
> +                               struct class_attribute *attr, char *buf)<br>
> +{<br>
> +     return sprintf(buf, "%zu\n", ARRAY_SIZE(edid_quirk_list));<br>
> +}<br>
> +<br>
> +/**<br>
> + * drm_edid_quirks_show - show the contents of the EDID quirk list in sysfs<br>
> + * @buf: destination buffer (PAGE_SIZE bytes)<br>
> + */<br>
> +ssize_t drm_edid_quirks_show(struct class *class, struct class_attribute *attr,<br>
> +                          char *buf)<br>
> +{<br>
> +     const union edid_quirk *q = edid_quirk_list;<br>
> +     ssize_t count = 0;<br>
> +<br>
> +     BUILD_BUG_ON(ARRAY_SIZE(edid_quirk_list) ><br>
> +                             PAGE_SIZE / EDID_QUIRK_BUF_SIZE);<br>
> +<br>
> +     mutex_lock(&edid_quirk_list_mutex);<br>
> +<br>
> +     do {<br>
> +             if (q->s.quirks != 0) {<br>
> +                     drm_edid_quirk_format(q, buf + count, 0);<br>
> +                     (buf + count)[EDID_QUIRK_BUF_SIZE - 1] = '\n';<br>
> +                     count += EDID_QUIRK_BUF_SIZE;<br>
> +             }<br>
> +     } while (++q < edid_quirk_list + ARRAY_SIZE(edid_quirk_list));<br>
> +<br>
> +     mutex_unlock(&edid_quirk_list_mutex);<br>
> +<br>
> +     return count;<br>
> +}<br>
> +<br>
> +/**<br>
> + * drm_edid_quirks_store - parse and process EDID qurik list changes written<br>
> + *                      to sysfs attribute<br>
> + */<br>
> +ssize_t drm_edid_quirks_store(struct class *class, struct class_attribute *attr,<br>
> +                           const char *buf, size_t count)<br>
> +{<br>
> +     int res;<br>
> +<br>
> +     res = drm_edid_quirks_process(buf, 1);<br>
> +     if (res != 0)<br>
> +             return res;<br>
> +<br>
> +     return count;<br>
> +}<br>
> +<br>
>  /*** DDC fetch and block validation ***/<br>
><br>
>  static const u8 edid_header[] = {<br>
> @@ -409,25 +817,6 @@ EXPORT_SYMBOL(drm_get_edid);<br>
>  /*** EDID parsing ***/<br>
><br>
>  /**<br>
> - * edid_vendor - match a string against EDID's obfuscated vendor field<br>
> - * @edid: EDID to match<br>
> - * @vendor: vendor string<br>
> - *<br>
> - * Returns true if @vendor is in @edid, false otherwise<br>
> - */<br>
> -static bool edid_vendor(struct edid *edid, char *vendor)<br>
> -{<br>
> -     char edid_vendor[3];<br>
> -<br>
> -     edid_vendor[0] = ((edid->mfg_id[0] & 0x7c) >> 2) + '@';<br>
> -     edid_vendor[1] = (((edid->mfg_id[0] & 0x3) << 3) |<br>
> -                       ((edid->mfg_id[1] & 0xe0) >> 5)) + '@';<br>
> -     edid_vendor[2] = (edid->mfg_id[1] & 0x1f) + '@';<br>
> -<br>
> -     return !strncmp(edid_vendor, vendor, 3);<br>
> -}<br>
> -<br>
> -/**<br>
>   * edid_get_quirks - return quirk flags for a given EDID<br>
>   * @edid: EDID to process<br>
>   *<br>
> @@ -435,18 +824,18 @@ static bool edid_vendor(struct edid *edid, char *vendor)<br>
>   */<br>
>  static u32 edid_get_quirks(struct edid *edid)<br>
>  {<br>
> -     struct edid_quirk *quirk;<br>
> -     int i;<br>
> +     union edid_quirk *q;<br>
> +     u32 quirks = 0;<br>
><br>
> -     for (i = 0; i < ARRAY_SIZE(edid_quirk_list); i++) {<br>
> -             quirk = &edid_quirk_list[i];<br>
> +     mutex_lock(&edid_quirk_list_mutex);<br>
><br>
> -             if (edid_vendor(edid, quirk->vendor) &&<br>
> -                 (EDID_PRODUCT_ID(edid) == quirk->product_id))<br>
> -                     return quirk->quirks;<br>
> -     }<br>
> +     q = drm_edid_quirk_find_by_id(edid->display_id);<br>
> +     if (q != NULL)<br>
> +             quirks = q->s.quirks;<br>
><br>
> -     return 0;<br>
> +     mutex_unlock(&edid_quirk_list_mutex);<br>
> +<br>
> +     return quirks;<br>
>  }<br>
><br>
>  #define MODE_SIZE(m) ((m)->hdisplay * (m)->vdisplay)<br>
> @@ -1162,7 +1551,7 @@ do_inferred_modes(struct detailed_timing *timing, void *c)<br>
>       closure->modes += drm_dmt_modes_for_range(closure->connector,<br>
>                                                 closure->edid,<br>
>                                                 timing);<br>
> -<br>
> +<br>
>       if (!version_greater(closure->edid, 1, 1))<br>
>               return; /* GTF not defined yet */<br>
><br>
> @@ -1399,7 +1788,7 @@ do_cvt_mode(struct detailed_timing *timing, void *c)<br>
><br>
>  static int<br>
>  add_cvt_modes(struct drm_connector *connector, struct edid *edid)<br>
> -{<br>
> +{<br>
>       struct detailed_mode_closure closure = {<br>
>               connector, edid, 0, 0, 0<br>
>       };<br>
> @@ -1615,15 +2004,12 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid)<br>
><br>
>       eld[0] = 2 << 3;                /* ELD version: 2 */<br>
><br>
> -     eld[16] = edid->mfg_id[0];<br>
> -     eld[17] = edid->mfg_id[1];<br>
> -     eld[18] = edid->prod_code[0];<br>
> -     eld[19] = edid->prod_code[1];<br>
> +     *(u32 *)(&eld[16]) = edid->display_id.u;<br>
><br>
>       if (cea[1] >= 3)<br>
>               for (db = cea + 4; db < cea + cea[2]; db += dbl + 1) {<br>
>                       dbl = db[0] & 0x1f;<br>
> -<br>
> +<br>
>                       switch ((db[0] & 0xe0) >> 5) {<br>
>                       case AUDIO_BLOCK:<br>
>                               /* Audio Data Block, contains SADs */<br>
> diff --git a/drivers/gpu/drm/drm_stub.c b/drivers/gpu/drm/drm_stub.c<br>
> index 21bcd4a..b939d51 100644<br>
> --- a/drivers/gpu/drm/drm_stub.c<br>
> +++ b/drivers/gpu/drm/drm_stub.c<br>
> @@ -46,16 +46,22 @@ EXPORT_SYMBOL(drm_vblank_offdelay);<br>
>  unsigned int drm_timestamp_precision = 20;  /* Default to 20 usecs. */<br>
>  EXPORT_SYMBOL(drm_timestamp_precision);<br>
><br>
> +char *drm_edid_quirks = NULL;<br>
> +EXPORT_SYMBOL(drm_edid_quirks);<br>
> +<br>
>  MODULE_AUTHOR(CORE_AUTHOR);<br>
>  MODULE_DESCRIPTION(CORE_DESC);<br>
>  MODULE_LICENSE("GPL and additional rights");<br>
>  MODULE_PARM_DESC(debug, "Enable debug output");<br>
>  MODULE_PARM_DESC(vblankoffdelay, "Delay until vblank irq auto-disable [msecs]");<br>
>  MODULE_PARM_DESC(timestamp_precision_usec, "Max. error on timestamps [usecs]");<br>
> +MODULE_PARM_DESC(edid_quirks, "MFG:prod:flags[,MFG:prod:flags[...]]\n"<br>
> +                           "(See Documentation/EDID/edid_quirks.txt)");<br>
><br>
>  module_param_named(debug, drm_debug, int, 0600);<br>
>  module_param_named(vblankoffdelay, drm_vblank_offdelay, int, 0600);<br>
>  module_param_named(timestamp_precision_usec, drm_timestamp_precision, int, 0600);<br>
> +module_param_named(edid_quirks, drm_edid_quirks, charp, 0400);<br>
><br>
>  struct idr drm_minors_idr;<br>
><br>
> diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c<br>
> index 45ac8d6..84dc365 100644<br>
> --- a/drivers/gpu/drm/drm_sysfs.c<br>
> +++ b/drivers/gpu/drm/drm_sysfs.c<br>
> @@ -84,6 +84,11 @@ static CLASS_ATTR_STRING(version, S_IRUGO,<br>
>               __stringify(CORE_PATCHLEVEL) " "<br>
>               CORE_DATE);<br>
><br>
> +static CLASS_ATTR(edid_quirks_size, 0400, drm_edid_quirks_size_show, 0);<br>
> +<br>
> +static CLASS_ATTR(edid_quirks, 0600, drm_edid_quirks_show,<br>
> +               drm_edid_quirks_store);<br>
> +<br>
>  /**<br>
>   * drm_sysfs_create - create a struct drm_sysfs_class structure<br>
>   * @owner: pointer to the module that is to "own" this struct drm_sysfs_class<br>
> @@ -113,10 +118,22 @@ struct class *drm_sysfs_create(struct module *owner, char *name)<br>
>       if (err)<br>
>               goto err_out_class;<br>
><br>
> +     err = class_create_file(class, &class_attr_edid_quirks_size);<br>
> +     if (err)<br>
> +             goto err_out_version;<br>
> +<br>
> +     err = class_create_file(class, &class_attr_edid_quirks);<br>
> +     if (err)<br>
> +             goto err_out_quirks_size;<br>
> +<br>
>       class->devnode = drm_devnode;<br>
><br>
>       return class;<br>
><br>
> +err_out_quirks_size:<br>
> +     class_remove_file(class, &class_attr_edid_quirks_size);<br>
> +err_out_version:<br>
> +     class_remove_file(class, &class_attr_version.attr);<br>
>  err_out_class:<br>
>       class_destroy(class);<br>
>  err_out:<br>
> @@ -132,6 +149,8 @@ void drm_sysfs_destroy(void)<br>
>  {<br>
>       if ((drm_class == NULL) || (IS_ERR(drm_class)))<br>
>               return;<br>
> +     class_remove_file(drm_class, &class_attr_edid_quirks);<br>
> +     class_remove_file(drm_class, &class_attr_edid_quirks_size);<br>
>       class_remove_file(drm_class, &class_attr_version.attr);<br>
>       class_destroy(drm_class);<br>
>       drm_class = NULL;<br>
> diff --git a/include/drm/drmP.h b/include/drm/drmP.h<br>
> index d6b67bb..c947f3e 100644<br>
> --- a/include/drm/drmP.h<br>
> +++ b/include/drm/drmP.h<br>
> @@ -1501,6 +1501,7 @@ extern unsigned int drm_debug;<br>
><br>
>  extern unsigned int drm_vblank_offdelay;<br>
>  extern unsigned int drm_timestamp_precision;<br>
> +extern char *drm_edid_quirks;<br>
><br>
>  extern struct class *drm_class;<br>
>  extern struct proc_dir_entry *drm_proc_root;<br>
> @@ -1612,6 +1613,15 @@ void drm_gem_vm_open(struct vm_area_struct *vma);<br>
>  void drm_gem_vm_close(struct vm_area_struct *vma);<br>
>  int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma);<br>
><br>
> +                                     /* EDID support (drm_edid.c) */<br>
> +void drm_edid_quirks_param_process(void);<br>
> +ssize_t drm_edid_quirks_size_show(struct class *class,<br>
> +                               struct class_attribute *attr, char *buf);<br>
> +ssize_t drm_edid_quirks_show(struct class *class, struct class_attribute *attr,<br>
> +                          char *buf);<br>
> +ssize_t drm_edid_quirks_store(struct class *class, struct class_attribute *attr,<br>
> +                           const char *buf, size_t count);<br>
> +<br>
>  #include "drm_global.h"<br>
><br>
>  static inline void<br>
> diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h<br>
> index 0cac551..713229b 100644<br>
> --- a/include/drm/drm_edid.h<br>
> +++ b/include/drm/drm_edid.h<br>
> @@ -202,11 +202,18 @@ struct detailed_timing {<br>
>  #define DRM_EDID_FEATURE_PM_SUSPEND       (1 << 6)<br>
>  #define DRM_EDID_FEATURE_PM_STANDBY       (1 << 7)<br>
><br>
> +union edid_display_id {<br>
> +     struct {<br>
> +             __be16 mfg_id;<br>
> +             __le16 prod_code;<br>
> +     } __attribute__((packed)) s;<br>
> +     u32 u;<br>
> +};<br>
<br>
I think adding this union is counterproductive. The u32 version is<br>
helpful in one comparison and one assignment, while making all the rest<br>
just slightly more confusing.<br>
<br>
> +<br>
>  struct edid {<br>
>       u8 header[8];<br>
>       /* Vendor & product info */<br>
> -     u8 mfg_id[2];<br>
> -     u8 prod_code[2];<br>
> +     union edid_display_id display_id;<br>
>       u32 serial; /* FIXME: byte order */<br>
>       u8 mfg_week;<br>
>       u8 mfg_year;<br>
> @@ -242,8 +249,6 @@ struct edid {<br>
>       u8 checksum;<br>
>  } __attribute__((packed));<br>
><br>
> -#define EDID_PRODUCT_ID(e) ((e)->prod_code[0] | ((e)->prod_code[1] << 8))<br>
> -<br>
>  struct drm_encoder;<br>
>  struct drm_connector;<br>
>  struct drm_display_mode;<br>
> --<br>
> 1.7.11.4<br>
><br>
> _______________________________________________<br>
> dri-devel mailing list<br>
> <a href="mailto:dri-devel@lists.freedesktop.org">dri-devel@lists.freedesktop.org</a><br>
> <a href="http://lists.freedesktop.org/mailman/listinfo/dri-devel" target="_blank">http://lists.freedesktop.org/mailman/listinfo/dri-devel</a><br>
</blockquote></div>