-
Notifications
You must be signed in to change notification settings - Fork 13
/
edid.c
177 lines (160 loc) · 5.89 KB
/
edid.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
* EDID (Extended Display Information Data) parsing, taken from libweston's
* compositor-drm.c; original author information below.
*
* EDID is an extensible binary block provided by monitors to describe their
* physical capabilities, preferred resolutions, etc. The most common
* extension to EDID is CEA. There are a multitude of timing formulae used,
* including GTF (General Timing Formula), which is these days less preferred
* to CVT (Continuously Variable Timing).
*
* The raw EDID block can usually be found in
* /sys/class/drm/cardN-CONNECTOR-N/edid, as well as through the KMS connector
* property helpfully named 'EDID'.
*
* Parsing EDID can be difficult, and even if you parse it correctly, you are
* likely to need to apply specific quirks, such as for when the physical size
* fields instead just contain the panel's aspect ratio:
* https://lists.fedoraproject.org/pipermail/devel/2011-October/157671.html
*
* This should eventually make its way out into shared source which could be
* used by all compositors, as well as the kernel. The kernel already parses
* EDID and makes some decisions based on that, filling out the 'mode' file
* in sysfs. However, this is not always reliable, and throws away a lot of
* information.
*
* Xorg has probably the most complete EDID parser, which can be found in
* hw/xfree86/ddc/interpret_edid.c and hw/xfree86/modes/xf86EdidModes.c from
* https://gitlab.freedesktop.org/xorg/xserver
*
* The kernel's can be found in drivers/gpu/drm/drm_edid.c.
*/
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
* Copyright © 2017-2019 Collabora, Ltd.
* Copyright © 2017, 2018 General Electric Company
* Copyright (c) 2018 DisplayLink (UK) Ltd.
* Copyright © 2018-2019 DAQRI, LLC and its affiliates
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <ctype.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "kms-quads.h"
#define EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe
#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc
#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff
#define EDID_OFFSET_DATA_BLOCKS 0x36
#define EDID_OFFSET_LAST_BLOCK 0x6c
#define EDID_OFFSET_PNPID 0x08
#define EDID_OFFSET_SERIAL 0x0c
/* EDID strings are at most 12 bytes. They may or may not contain an
* actual string. */
static void
edid_parse_string(const uint8_t *data, char text[])
{
int i;
int replaced = 0;
/* this is always 12 bytes, but we can't guarantee it's null
* terminated or not junk. */
strncpy(text, (const char *) data, 12);
/* guarantee our new string is null-terminated */
text[12] = '\0';
/* remove insane chars */
for (i = 0; text[i] != '\0'; i++) {
if (text[i] == '\n' ||
text[i] == '\r') {
text[i] = '\0';
break;
}
}
/* ensure string is printable */
for (i = 0; text[i] != '\0'; i++) {
if (!isprint(text[i])) {
text[i] = '-';
replaced++;
}
}
/* if the string is random junk, ignore the string */
if (replaced > 4)
text[0] = '\0';
}
/* Parse a standard EDID block. */
struct edid_info *
edid_parse(const uint8_t *data, size_t length)
{
struct edid_info *edid = calloc(1, sizeof(*edid));
int i;
uint32_t serial_number;
/* check header */
if (length < 128)
goto err;
if (data[0] != 0x00 || data[1] != 0xff)
goto err;
/* decode the PNP ID from three 5 bit words packed into 2 bytes
* /--08--\/--09--\
* 7654321076543210
* |\---/\---/\---/
* R C1 C2 C3 */
edid->pnp_id[0] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x7c) / 4) - 1;
edid->pnp_id[1] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x3) * 8) + ((data[EDID_OFFSET_PNPID + 1] & 0xe0) / 32) - 1;
edid->pnp_id[2] = 'A' + (data[EDID_OFFSET_PNPID + 1] & 0x1f) - 1;
edid->pnp_id[3] = '\0';
/* maybe there isn't a ASCII serial number descriptor, so use this instead */
serial_number = (uint32_t) data[EDID_OFFSET_SERIAL + 0];
serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 1] * 0x100;
serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 2] * 0x10000;
serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 3] * 0x1000000;
if (serial_number > 0)
sprintf(edid->serial_number, "%lu", (unsigned long) serial_number);
/* parse EDID data */
for (i = EDID_OFFSET_DATA_BLOCKS;
i <= EDID_OFFSET_LAST_BLOCK;
i += 18) {
/* ignore pixel clock data */
if (data[i] != 0)
continue;
if (data[i+2] != 0)
continue;
/* any useful blocks? */
if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME) {
edid_parse_string(&data[i+5],
edid->monitor_name);
} else if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER) {
edid_parse_string(&data[i+5],
edid->serial_number);
} else if (data[i+3] == EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING) {
edid_parse_string(&data[i+5],
edid->eisa_id);
}
}
return edid;
err:
free(edid);
return NULL;
}