1 /*
2  *  Extract microphone configuration from the ACPI NHLT table
3  *
4  *  Specification:
5  *         https://01.org/sites/default/files/595976_intel_sst_nhlt.pdf
6  *
7  *     Author: Jaroslav Kysela <perex@perex.cz>
8  *
9  *
10  *   This program is free software; you can redistribute it and/or modify
11  *   it under the terms of the GNU General Public License as published by
12  *   the Free Software Foundation; either version 2 of the License, or
13  *   (at your option) any later version.
14  *
15  *   This program is distributed in the hope that it will be useful,
16  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *   GNU General Public License for more details.
19  *
20  *   You should have received a copy of the GNU General Public License
21  *   along with this program; if not, write to the Free Software
22  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23  *
24  */
25 
26 #include "aconfig.h"
27 #include <stdint.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <getopt.h>
35 #include <sys/stat.h>
36 #include <arpa/inet.h>
37 
38 int debug = 0;
39 
40 /*
41  * Dump dmic parameters in json
42  */
43 
44 #define ACPI_HDR_SIZE (4 + 4 + 1 + 1 + 6 + 8 + 4 + 4 + 4)
45 #define NHLT_EP_HDR_SIZE (4 + 1 + 1 + 2 + 2 + 2 + 4 + 1 + 1 + 1)
46 #define VENDOR_MIC_CFG_SIZE (1 + 1 + 2 + 2 + 2 + 1 + 1 + 2 + 2 + 2 + 2 + 2 + 2)
47 
microphone_type(uint8_t type)48 static const char *microphone_type(uint8_t type)
49 {
50 	switch (type) {
51 	case 0: return "omnidirectional";
52 	case 1: return "subcardoid";
53 	case 2: return "cardoid";
54 	case 3: return "supercardoid";
55 	case 4: return "hypercardoid";
56 	case 5: return "8shaped";
57 	case 7: return "vendor";
58 	}
59 	return "unknown";
60 }
61 
microphone_location(uint8_t location)62 static const char *microphone_location(uint8_t location)
63 {
64 	switch (location) {
65 	case 0: return "laptop-top-panel";
66 	case 1: return "laptop-bottom-panel";
67 	case 2: return "laptop-left-panel";
68 	case 3: return "laptop-right-panel";
69 	case 4: return "laptop-front-panel";
70 	case 5: return "laptop-rear-panel";
71 	}
72 	return "unknown";
73 }
74 
75 
get_u8(uint8_t *base, uint32_t off)76 static inline uint8_t get_u8(uint8_t *base, uint32_t off)
77 {
78 	return *(base + off);
79 }
80 
get_s16le(uint8_t *base, uint32_t off)81 static inline int32_t get_s16le(uint8_t *base, uint32_t off)
82 {
83 	uint32_t v =  *(base + off + 0) |
84 		      (*(base + off + 1) << 8);
85 	if (v & 0x8000)
86 		return -((int32_t)0x10000 - (int32_t)v);
87 	return v;
88 }
89 
get_u32le(uint8_t *base, uint32_t off)90 static inline uint32_t get_u32le(uint8_t *base, uint32_t off)
91 {
92 	return   *(base + off + 0) |
93 		(*(base + off + 1) << 8) |
94 		(*(base + off + 2) << 16) |
95 		(*(base + off + 3) << 24);
96 }
97 
nhlt_dmic_config(FILE *out, uint8_t *dmic, uint8_t mic)98 static int nhlt_dmic_config(FILE *out, uint8_t *dmic, uint8_t mic)
99 {
100 	int32_t angle_begin, angle_end;
101 
102 	if (mic > 0)
103 		fprintf(out, ",\n");
104 	fprintf(out, "\t\t{\n");
105 	fprintf(out, "\t\t\t\"channel\":%i,\n", mic);
106 	fprintf(out, "\t\t\t\"type\":\"%s\",\n", microphone_type(get_u8(dmic, 0)));
107 	fprintf(out, "\t\t\t\"location\":\"%s\"", microphone_location(get_u8(dmic, 1)));
108 	if (get_s16le(dmic, 2) != 0)
109 		fprintf(out, ",\n\t\t\t\"speaker-distance\":%i", get_s16le(dmic, 2));
110 	if (get_s16le(dmic, 4) != 0)
111 		fprintf(out, ",\n\t\t\t\"horizontal-offset\":%i", get_s16le(dmic, 4));
112 	if (get_s16le(dmic, 6) != 0)
113 		fprintf(out, ",\n\t\t\t\"vertical-offset\":%i", get_s16le(dmic, 6));
114 	if (get_u8(dmic, 8) != 0)
115 		fprintf(out, ",\n\t\t\t\"freq-low-band\":%i", get_u8(dmic, 8) * 5);
116 	if (get_u8(dmic, 9) != 0)
117 		fprintf(out, ",\n\t\t\t\"freq-high-band\":%i", get_u8(dmic, 9) * 500);
118 	if (get_s16le(dmic, 10) != 0)
119 		fprintf(out, ",\n\t\t\t\"direction-angle\":%i", get_s16le(dmic, 10));
120 	if (get_s16le(dmic, 12) != 0)
121 		fprintf(out, ",\n\t\t\t\"elevation-angle\":%i", get_s16le(dmic, 12));
122 	angle_begin = get_s16le(dmic, 14);
123 	angle_end = get_s16le(dmic, 16);
124 	if (!((angle_begin == 180 && angle_end == -180) ||
125 	      (angle_begin == -180 && angle_end == 180))) {
126 		fprintf(out, ",\n\t\t\t\"vertical-angle-begin\":%i,\n", angle_begin);
127 		fprintf(out, "\t\t\t\"vertical-angle-end\":%i", angle_end);
128 	}
129 	angle_begin = get_s16le(dmic, 18);
130 	angle_end = get_s16le(dmic, 20);
131 	if (!((angle_begin == 180 && angle_end == -180) ||
132 	      (angle_begin == -180 && angle_end == 180))) {
133 		fprintf(out, ",\n\t\t\t\"horizontal-angle-begin\":%i,\n", angle_begin);
134 		fprintf(out, "\t\t\t\"horizontal-angle-end\":%i", angle_end);
135 	}
136 	fprintf(out, "\n\t\t}");
137 	return 0;
138 }
139 
nhlt_dmic_ep_to_json(FILE *out, uint8_t *ep, uint32_t ep_size)140 static int nhlt_dmic_ep_to_json(FILE *out, uint8_t *ep, uint32_t ep_size)
141 {
142 	uint32_t off, specific_cfg_size;
143 	uint8_t config_type, array_type, mic, num_mics;
144 	int res;
145 
146 	off = NHLT_EP_HDR_SIZE;
147 	specific_cfg_size = get_u32le(ep, off);
148 	if (off + specific_cfg_size > ep_size)
149 		goto oob;
150 	off += 4;
151 	config_type = get_u8(ep, off + 1);
152 	if (config_type != 1)	/* mic array */
153 		return 0;
154 	array_type = get_u8(ep, off + 2);
155 	if ((array_type & 0x0f) != 0x0f) {
156 		fprintf(stderr, "Unsupported ArrayType %02x\n", array_type & 0x0f);
157 		return -EINVAL;
158 	}
159 	num_mics = get_u8(ep, off + 3);
160 	fprintf(out, "{\n");
161 	fprintf(out, "\t\"mics-data-version\":1,\n");
162 	fprintf(out, "\t\"mics-data-source\":\"acpi-nhlt\"");
163 	for (mic = 0; mic < num_mics; mic++) {
164 		if (off - NHLT_EP_HDR_SIZE + VENDOR_MIC_CFG_SIZE > specific_cfg_size) {
165 			fprintf(out, "\n}\n");
166 			goto oob;
167 		}
168 		if (mic == 0)
169 			fprintf(out, ",\n\t\"mics\":[\n");
170 		res = nhlt_dmic_config(out, ep + off + 4, mic);
171 		if (res < 0)
172 			return res;
173 		off += VENDOR_MIC_CFG_SIZE;
174 	}
175 	if (num_mics > 0)
176 		fprintf(out, "\n\t]\n");
177 	fprintf(out, "}\n");
178 	return num_mics;
179 oob:
180 	fprintf(stderr, "Data (out-of-bounds) error\n");
181 	return -EINVAL;
182 }
183 
nhlt_table_to_json(FILE *out, uint8_t *nhlt, uint32_t size)184 static int nhlt_table_to_json(FILE *out, uint8_t *nhlt, uint32_t size)
185 {
186 	uint32_t _size, off, ep_size;
187 	uint8_t sum = 0, ep, ep_count, link_type, dmics = 0;
188 	int res;
189 
190 	_size = get_u32le(nhlt, 4);
191 	if (_size != size) {
192 		fprintf(stderr, "Table size mismatch (%08x != %08x)\n", _size, (uint32_t)size);
193 		return -EINVAL;
194 	}
195 	for (off = 0; off < size; off++)
196 		sum += get_u8(nhlt, off);
197 	if (sum != 0) {
198 		fprintf(stderr, "Checksum error (%02x)\n", sum);
199 		return -EINVAL;
200 	}
201 	/* skip header */
202 	off = ACPI_HDR_SIZE;
203 	ep_count = get_u8(nhlt, off++);
204 	for (ep = 0; ep < ep_count; ep++) {
205 		if (off + 17 > size)
206 			goto oob;
207 		ep_size = get_u32le(nhlt, off);
208 		if (off + ep_size > size)
209 			goto oob;
210 		link_type = get_u8(nhlt, off + 4);
211 		res = 0;
212 		if (link_type == 2) { 	/* PDM */
213 			res = nhlt_dmic_ep_to_json(out, nhlt + off, ep_size);
214 			if (res > 0)
215 				dmics++;
216 		}
217 		if (res < 0)
218 			return res;
219 		off += ep_size;
220 	}
221 	if (dmics == 0) {
222 		fprintf(stderr, "No dmic endpoint found\n");
223 		return -EINVAL;
224 	}
225 	return 0;
226 oob:
227 	fprintf(stderr, "Data (out-of-bounds) error\n");
228 	return -EINVAL;
229 }
230 
nhlt_to_json(FILE *out, const char *nhlt_file)231 static int nhlt_to_json(FILE *out, const char *nhlt_file)
232 {
233 	struct stat st;
234 	uint8_t *buf;
235 	int _errno, fd, res;
236 	size_t pos, size;
237 	ssize_t ret;
238 
239 	if (stat(nhlt_file, &st))
240 		return -errno;
241 	size = st.st_size;
242 	if (size < 45)
243 		return -EINVAL;
244 	buf = malloc(size);
245 	if (buf == NULL)
246 		return -ENOMEM;
247 	fd = open(nhlt_file, O_RDONLY);
248 	if (fd < 0) {
249 		_errno = errno;
250 		fprintf(stderr, "Unable to open file '%s': %s\n", nhlt_file, strerror(errno));
251 		free(buf);
252 		return _errno;
253 	}
254 	pos = 0;
255 	while (pos < size) {
256 		ret = read(fd, buf + pos, size - pos);
257 		if (ret <= 0) {
258 			fprintf(stderr, "Short read\n");
259 			close(fd);
260 			free(buf);
261 			return -EIO;
262 		}
263 		pos += ret;
264 	}
265 	close(fd);
266 	res = nhlt_table_to_json(out, buf, size);
267 	free(buf);
268 	return res;
269 }
270 
271 /*
272  *
273  */
274 
275 #define PROG "nhlt-dmic-info"
276 #define PROG_VERSION "1"
277 
278 #define NHLT_FILE "/sys/firmware/acpi/tables/NHLT"
279 
280 #define TITLE	0x0100
281 #define HEADER	0x0200
282 #define FILEARG 0x0400
283 
284 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
285 
286 struct arg {
287 	int sarg;
288 	char *larg;
289 	char *comment;
290 };
291 
292 static struct arg args[] = {
293 { TITLE, NULL, "Usage: nhtl-dmic-json <options>" },
294 { HEADER, NULL, "global options:" },
295 { 'h', "help", "this help" },
296 { 'v', "version", "print version of this program" },
297 { FILEARG | 'f', "file", "NHLT file (default " NHLT_FILE ")" },
298 { FILEARG | 'o', "output", "output file" },
299 { 0, NULL, NULL }
300 };
301 
help(void)302 static void help(void)
303 {
304 	struct arg *n = args, *a;
305 	char *larg, sa[4], buf[32];
306 	int sarg;
307 
308 	sa[0] = '-';
309 	sa[2] = ',';
310 	sa[3] = '\0';
311 	while (n->comment) {
312 		a = n;
313 		n++;
314 		sarg = a->sarg;
315 		if (sarg & (HEADER|TITLE)) {
316 			printf("%s%s\n", (sarg & HEADER) != 0 ? "\n" : "",
317 								a->comment);
318 			continue;
319 		}
320 		buf[0] = '\0';
321 		larg = a->larg;
322 		sa[1] = a->sarg;
323 		sprintf(buf, "%s%s%s", sa[1] ? sa : "",
324 				larg ? "--" : "", larg ? larg : "");
325 		if (sarg & FILEARG)
326 			strcat(buf, " #");
327 		printf("  %-15s  %s\n", buf, a->comment);
328 	}
329 }
330 
main(int argc, char *argv[])331 int main(int argc, char *argv[])
332 {
333 	char *nhlt_file = NHLT_FILE;
334 	char *output_file = "-";
335 	int i, j, k, res;
336 	struct arg *a;
337 	struct option *o, *long_option;
338 	char *short_option;
339 	FILE *output = NULL;
340 
341 	long_option = calloc(ARRAY_SIZE(args), sizeof(struct option));
342 	if (long_option == NULL)
343 		exit(EXIT_FAILURE);
344 	short_option = malloc(128);
345 	if (short_option == NULL) {
346 		free(long_option);
347 		exit(EXIT_FAILURE);
348 	}
349 	for (i = j = k = 0; i < (int)ARRAY_SIZE(args); i++) {
350 		a = &args[i];
351 		if ((a->sarg & 0xff) == 0)
352 			continue;
353 		o = &long_option[j];
354 		o->name = a->larg;
355 		o->has_arg = (a->sarg & FILEARG) != 0;
356 		o->flag = NULL;
357 		o->val = a->sarg & 0xff;
358 		j++;
359 		short_option[k++] = o->val;
360 		if (o->has_arg)
361 			short_option[k++] = ':';
362 	}
363 	short_option[k] = '\0';
364 	while (1) {
365 		int c;
366 
367 		if ((c = getopt_long(argc, argv, short_option, long_option,
368 								  NULL)) < 0)
369 			break;
370 		switch (c) {
371 		case 'h':
372 			help();
373 			res = EXIT_SUCCESS;
374 			goto out;
375 		case 'f':
376 			nhlt_file = optarg;
377 			break;
378 		case 'o':
379 			output_file = optarg;
380 			break;
381 		case 'd':
382 			debug = 1;
383 			break;
384 		case 'v':
385 			printf(PROG " version " PROG_VERSION "\n");
386 			res = EXIT_SUCCESS;
387 			goto out;
388 		case '?':		// error msg already printed
389 			help();
390 			res = EXIT_FAILURE;
391 			goto out;
392 		default:		// should never happen
393 			fprintf(stderr,
394 			"Invalid option '%c' (%d) not handled??\n", c, c);
395 		}
396 	}
397 	free(short_option);
398 	short_option = NULL;
399 	free(long_option);
400 	long_option = NULL;
401 
402 	if (strcmp(output_file, "-") == 0) {
403 		output = stdout;
404 	} else {
405 		output = fopen(output_file, "w+");
406 		if (output == NULL) {
407 			fprintf(stderr, "Unable to create output file \"%s\": %s\n",
408 						output_file, strerror(errno));
409 			res = EXIT_FAILURE;
410 			goto out;
411 		}
412 	}
413 
414 	if (argc - optind > 0)
415 		fprintf(stderr, PROG ": Ignoring extra parameters\n");
416 
417 	res = 0;
418 	if (nhlt_to_json(output, nhlt_file))
419 		res = EXIT_FAILURE;
420 
421 out:
422 	if (output)
423 		fclose(output);
424 	free(short_option);
425 	free(long_option);
426 	return res;
427 }
428