1 /*
2  * Copyright © 2019 Intel Corporation
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  *
23  * Authors: Simon Ser <simon.ser@intel.com>
24  */
25 
26 #include "config.h"
27 
28 #include <dirent.h>
29 #include <stdint.h>
30 #include <stdio.h>
31 #include <string.h>
32 
33 #include "igt_core.h"
34 #include "igt_eld.h"
35 
36 #define ELD_PREFIX "eld#"
37 #define ELD_DELIM " \t"
38 
39 /**
40  * EDID-Like Data (ELD) is metadata parsed and exposed by ALSA for HDMI and
41  * DisplayPort connectors supporting audio. This includes the monitor name and
42  * the supported audio parameters (formats, sampling rates, sample sizes and so
43  * on).
44  *
45  * Audio parameters come from Short Audio Descriptors (SAD) blocks in the
46  * EDID. Enumerations from igt_edid are used since they are the same.
47  */
48 
parse_sad_coding_type(const char * value)49 static enum cea_sad_format parse_sad_coding_type(const char *value)
50 {
51 	if (strcmp(value, "LPCM") == 0)
52 		return CEA_SAD_FORMAT_PCM;
53 	else
54 		return 0;
55 }
56 
parse_sad_rate(const char * value)57 static enum cea_sad_sampling_rate parse_sad_rate(const char *value)
58 {
59 	switch (atoi(value)) {
60 	case 32000:
61 		return CEA_SAD_SAMPLING_RATE_32KHZ;
62 	case 44100:
63 		return CEA_SAD_SAMPLING_RATE_44KHZ;
64 	case 48000:
65 		return CEA_SAD_SAMPLING_RATE_48KHZ;
66 	case 88000:
67 		return CEA_SAD_SAMPLING_RATE_88KHZ;
68 	case 96000:
69 		return CEA_SAD_SAMPLING_RATE_96KHZ;
70 	case 176000:
71 		return CEA_SAD_SAMPLING_RATE_176KHZ;
72 	case 192000:
73 		return CEA_SAD_SAMPLING_RATE_192KHZ;
74 	default:
75 		return 0;
76 	}
77 }
78 
parse_sad_bit(const char * value)79 static enum cea_sad_pcm_sample_size parse_sad_bit(const char *value)
80 {
81 	switch (atoi(value)) {
82 	case 16:
83 		return CEA_SAD_SAMPLE_SIZE_16;
84 	case 20:
85 		return CEA_SAD_SAMPLE_SIZE_20;
86 	case 24:
87 		return CEA_SAD_SAMPLE_SIZE_24;
88 	default:
89 		return 0;
90 	}
91 }
92 
parse_sad_field(struct eld_sad * sad,const char * key,char * value)93 static void parse_sad_field(struct eld_sad *sad, const char *key, char *value)
94 {
95 	char *tok;
96 
97 	/* Some fields are prefixed with the raw hex value, strip it */
98 	if (value[0] == '[') {
99 		value = strchr(value, ' ');
100 		igt_assert(value != NULL);
101 		value++; /* skip the space */
102 	}
103 
104 	/* Single-value fields */
105 	if (strcmp(key, "coding_type") == 0)
106 		sad->coding_type = parse_sad_coding_type(value);
107 	else if (strcmp(key, "channels") == 0)
108 		sad->channels = atoi(value);
109 
110 	/* Multiple-value fields */
111 	tok = strtok(value, " ");
112 	while (tok) {
113 		if (strcmp(key, "rates") == 0)
114 			sad->rates |= parse_sad_rate(tok);
115 		else if (strcmp(key, "bits") == 0)
116 			sad->bits |= parse_sad_bit(tok);
117 
118 		tok = strtok(NULL, " ");
119 	}
120 }
121 
122 /** eld_parse_entry: parse an ELD entry
123  *
124  * Here is an example of an ELD entry:
125  *
126  *     $ cat /proc/asound/card0/eld#0.2
127  *     monitor_present         1
128  *     eld_valid               1
129  *     monitor_name            U2879G6
130  *     connection_type         DisplayPort
131  *     eld_version             [0x2] CEA-861D or below
132  *     edid_version            [0x3] CEA-861-B, C or D
133  *     manufacture_id          0xe305
134  *     product_id              0x2879
135  *     port_id                 0x800
136  *     support_hdcp            0
137  *     support_ai              0
138  *     audio_sync_delay        0
139  *     speakers                [0x1] FL/FR
140  *     sad_count               1
141  *     sad0_coding_type        [0x1] LPCM
142  *     sad0_channels           2
143  *     sad0_rates              [0xe0] 32000 44100 48000
144  *     sad0_bits               [0xe0000] 16 20 24
145  *
146  * Each entry contains one or more SAD blocks. Their contents is exposed in
147  * sadN_* fields.
148  */
eld_parse_entry(const char * path,struct eld_entry * eld)149 static bool eld_parse_entry(const char *path, struct eld_entry *eld)
150 {
151 	FILE *f;
152 	char buf[1024];
153 	char *key, *value, *sad_key;
154 	size_t len;
155 	bool monitor_present = false;
156 	int sad_index;
157 
158 	memset(eld, 0, sizeof(*eld));
159 
160 	f = fopen(path, "r");
161 	if (!f) {
162 		igt_debug("Failed to open ELD file: %s\n", path);
163 		return false;
164 	}
165 
166 	while ((fgets(buf, sizeof(buf), f)) != NULL) {
167 		len = strlen(buf);
168 		if (buf[len - 1] == '\n')
169 			buf[len - 1] = '\0';
170 
171 		key = strtok(buf, ELD_DELIM);
172 		value = strtok(NULL, "");
173 		/* Skip whitespace at the beginning */
174 		value += strspn(value, ELD_DELIM);
175 
176 		if (strcmp(key, "monitor_present") == 0)
177 			monitor_present = strcmp(value, "1") == 0;
178 		else if (strcmp(key, "eld_valid") == 0)
179 			eld->valid = strcmp(value, "1") == 0;
180 		else if (strcmp(key, "monitor_name") == 0)
181 			snprintf(eld->monitor_name, sizeof(eld->monitor_name),
182 				 "%s", value);
183 		else if (strcmp(key, "sad_count") == 0)
184 			eld->sads_len = atoi(value);
185 		else if (sscanf(key, "sad%d_%ms", &sad_index, &sad_key) == 2) {
186 			igt_assert(sad_index < ELD_SADS_CAP);
187 			igt_assert(sad_index < eld->sads_len);
188 			parse_sad_field(&eld->sads[sad_index], sad_key, value);
189 			free(sad_key);
190 		}
191 	}
192 
193 	if (ferror(f) != 0) {
194 		igt_debug("Failed to read ELD file %s: %d\n", path, ferror(f));
195 		return false;
196 	}
197 
198 	fclose(f);
199 
200 	if (!monitor_present)
201 		igt_debug("Monitor not present in ELD: %s\n", path);
202 	return monitor_present;
203 }
204 
205 /** eld_get_igt: retrieve the ALSA ELD entry matching the IGT EDID */
eld_get_igt(struct eld_entry * eld)206 bool eld_get_igt(struct eld_entry *eld)
207 {
208 	DIR *dir;
209 	struct dirent *dirent;
210 	int i, n_elds;
211 	char card[64];
212 	char path[PATH_MAX];
213 
214 	n_elds = 0;
215 	for (i = 0; i < 8; i++) {
216 		snprintf(card, sizeof(card), "/proc/asound/card%d", i);
217 		dir = opendir(card);
218 		if (!dir)
219 			continue;
220 
221 		while ((dirent = readdir(dir))) {
222 			if (strncmp(dirent->d_name, ELD_PREFIX,
223 				    strlen(ELD_PREFIX)) != 0)
224 				continue;
225 
226 			n_elds++;
227 
228 			snprintf(path, sizeof(path), "%s/%s", card,
229 				 dirent->d_name);
230 			if (!eld_parse_entry(path, eld)) {
231 				continue;
232 			}
233 
234 			if (!eld->valid) {
235 				igt_debug("Skipping invalid ELD: %s\n", path);
236 				continue;
237 			}
238 
239 			if (strcmp(eld->monitor_name, "IGT") != 0) {
240 				igt_debug("Skipping non-IGT ELD: %s "
241 					  "(monitor name: %s)\n",
242 					  path, eld->monitor_name);
243 				continue;
244 			}
245 
246 			closedir(dir);
247 			return true;
248 		}
249 		closedir(dir);
250 	}
251 
252 	if (n_elds == 0)
253 		igt_debug("Found zero ELDs\n");
254 
255 	return false;
256 }
257 
258 /** eld_has_igt: check whether ALSA has detected the audio-capable IGT EDID by
259  * parsing ELD entries */
eld_has_igt(void)260 bool eld_has_igt(void)
261 {
262 	struct eld_entry eld;
263 	return eld_get_igt(&eld);
264 }
265