diff --git a/alsa-utils-git.patch b/alsa-utils-git.patch index e69de29..b6afcff 100644 --- a/alsa-utils-git.patch +++ b/alsa-utils-git.patch @@ -0,0 +1,554 @@ +From ee3965f6fac6c8b003acb097191125070708cccb Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Tue, 16 May 2023 15:38:24 +0200 +Subject: [PATCH] nhlt: add nhlt-dmic-info utility + +The microphone arrays for Intel platforms are described in the +ACPI NHLT table. This table is available in sysfs. Parse this +information and use a more common format (json) for output. This +information is usable for the further DSP processing. + +Signed-off-by: Jaroslav Kysela +--- + .gitignore | 1 + + Makefile.am | 3 + + configure.ac | 13 +- + nhlt/Makefile.am | 6 + + nhlt/nhlt-dmic-info.1 | 37 ++++ + nhlt/nhlt-dmic-info.c | 425 ++++++++++++++++++++++++++++++++++++++++++ + 6 files changed, 484 insertions(+), 1 deletion(-) + create mode 100644 nhlt/Makefile.am + create mode 100644 nhlt/nhlt-dmic-info.1 + create mode 100644 nhlt/nhlt-dmic-info.c + +diff --git a/Makefile.am b/Makefile.am +index 20dcfc8..b961506 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -31,6 +31,9 @@ endif + if HAVE_TOPOLOGY + SUBDIRS += topology + endif ++if NHLT ++SUBDIRS += nhlt ++endif + + EXTRA_DIST= README.md TODO gitcompile + AUTOMAKE_OPTIONS=foreign +diff --git a/configure.ac b/configure.ac +index e079e24..c91817a 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -189,6 +189,16 @@ AC_ARG_ENABLE(alsaloop, + esac],[alsaloop=true]) + AM_CONDITIONAL(ALSALOOP, test x$alsaloop = xtrue) + ++dnl Disable nhlt ++AC_ARG_ENABLE(nhlt, ++ AS_HELP_STRING([--disable-nhlt], [Disable nhlt packaging]), ++ [case "${enableval}" in ++ yes) nhlt=true ;; ++ no) nhlt=false ;; ++ *) AC_MSG_ERROR(bad value ${enableval} for --enable-nhlt) ;; ++ esac],[nhlt=true]) ++AM_CONDITIONAL(NHLT, test x$nhlt = xtrue) ++ + xmlto_available="" + AC_ARG_ENABLE(xmlto, + AS_HELP_STRING([--disable-xmlto], [Disable man page creation via xmlto]), +@@ -475,4 +485,5 @@ AC_OUTPUT(Makefile alsactl/Makefile alsactl/init/Makefile \ + seq/aplaymidi/Makefile seq/aseqdump/Makefile seq/aseqnet/Makefile \ + speaker-test/Makefile speaker-test/samples/Makefile \ + alsaloop/Makefile alsa-info/Makefile \ +- axfer/Makefile axfer/test/Makefile) ++ axfer/Makefile axfer/test/Makefile \ ++ nhlt/Makefile) +diff --git a/nhlt/Makefile.am b/nhlt/Makefile.am +new file mode 100644 +index 0000000..5aadda7 +--- /dev/null ++++ b/nhlt/Makefile.am +@@ -0,0 +1,6 @@ ++AM_CPPFLAGS = -I$(top_srcdir)/include ++ ++bin_PROGRAMS = nhlt-dmic-info ++nhlt_dmic_info_SOURCES = nhlt-dmic-info.c ++man_MANS = nhlt-dmic-info.1 ++EXTRA_DIST = nhlt-dmic-info.1 +diff --git a/nhlt/nhlt-dmic-info.1 b/nhlt/nhlt-dmic-info.1 +new file mode 100644 +index 0000000..22fdc5a +--- /dev/null ++++ b/nhlt/nhlt-dmic-info.1 +@@ -0,0 +1,37 @@ ++.TH NHLT-DMIC-INFO 1 "16 May 2023" ++.SH NAME ++nhlt-dmic-info \- dump microphone array information from ACPI NHLT table ++.SH SYNOPSIS ++\fBnhlt-dmic-info\fP [\fI\-option\fP] ++.SH DESCRIPTION ++ ++\fB\fBnhlt-dmic-info\fP\fP dumps microphone array information from ACPI NHLT ++table in JSON format. ++ ++.SH OPTIONS ++ ++.TP ++\fI\-h\fP | \fI\-\-help\fP ++ ++Prints the help information. ++ ++.TP ++\fI\-f \fP | \fI\-\-file=\fP ++ ++Input file with the binary ACPI NHLT table (default is \fB/sys/firmware/acpi/tables/NHLT\fR). ++ ++.TP ++\fI\-o \fP | \fI\-\-output=\fP ++ ++JSON output file (default is stdout: \fB\-\fR). ++ ++.SH EXAMPLES ++.nf ++\fBnhlt-dmic-info \-f nhlt.bin \-o dmic.json\fR ++ ++.ne ++.SH BUGS ++None known. ++.SH AUTHOR ++\fBnhlt-dmic-info\fP is by Jaroslav Kysela . ++This document is by Jaroslav Kysela . +diff --git a/nhlt/nhlt-dmic-info.c b/nhlt/nhlt-dmic-info.c +new file mode 100644 +index 0000000..44a7255 +--- /dev/null ++++ b/nhlt/nhlt-dmic-info.c +@@ -0,0 +1,425 @@ ++/* ++ * Extract microphone configuration from the ACPI NHLT table ++ * ++ * Specification: ++ * https://01.org/sites/default/files/595976_intel_sst_nhlt.pdf ++ * ++ * Author: Jaroslav Kysela ++ * ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++int debug = 0; ++ ++/* ++ * Dump dmic parameters in json ++ */ ++ ++#define ACPI_HDR_SIZE (4 + 4 + 1 + 1 + 6 + 8 + 4 + 4 + 4) ++#define NHLT_EP_HDR_SIZE (4 + 1 + 1 + 2 + 2 + 2 + 4 + 1 + 1 + 1) ++#define VENDOR_MIC_CFG_SIZE (1 + 1 + 2 + 2 + 2 + 1 + 1 + 2 + 2 + 2 + 2 + 2 + 2) ++ ++static const char *microphone_type(u_int8_t type) ++{ ++ switch (type) { ++ case 0: return "omnidirectional"; ++ case 1: return "subcardoid"; ++ case 2: return "cardoid"; ++ case 3: return "supercardoid"; ++ case 4: return "hypercardoid"; ++ case 5: return "8shaped"; ++ case 7: return "vendor"; ++ } ++ return "unknown"; ++} ++ ++static const char *microphone_location(u_int8_t location) ++{ ++ switch (location) { ++ case 0: return "laptop-top-panel"; ++ case 1: return "laptop-bottom-panel"; ++ case 2: return "laptop-left-panel"; ++ case 3: return "laptop-right-panel"; ++ case 4: return "laptop-front-panel"; ++ case 5: return "laptop-rear-panel"; ++ } ++ return "unknown"; ++} ++ ++ ++static inline u_int8_t get_u8(u_int8_t *base, u_int32_t off) ++{ ++ return *(base + off); ++} ++ ++static inline int32_t get_s16le(u_int8_t *base, u_int32_t off) ++{ ++ u_int32_t v = *(base + off + 0) | ++ (*(base + off + 1) << 8); ++ if (v & 0x8000) ++ return -((int32_t)0x10000 - (int32_t)v); ++ return v; ++} ++ ++static inline u_int32_t get_u32le(u_int8_t *base, u_int32_t off) ++{ ++ return *(base + off + 0) | ++ (*(base + off + 1) << 8) | ++ (*(base + off + 2) << 16) | ++ (*(base + off + 3) << 24); ++} ++ ++static int nhlt_dmic_config(FILE *out, uint8_t *dmic, uint8_t mic) ++{ ++ int32_t angle_begin, angle_end; ++ ++ if (mic > 0) ++ fprintf(out, ",\n"); ++ fprintf(out, "\t\t{\n"); ++ fprintf(out, "\t\t\t\"channel\":%i,\n", mic); ++ fprintf(out, "\t\t\t\"type\":\"%s\",\n", microphone_type(get_u8(dmic, 0))); ++ fprintf(out, "\t\t\t\"location\":\"%s\"", microphone_location(get_u8(dmic, 1))); ++ if (get_s16le(dmic, 2) != 0) ++ fprintf(out, ",\n\t\t\t\"speaker-distance\":%i", get_s16le(dmic, 2)); ++ if (get_s16le(dmic, 4) != 0) ++ fprintf(out, ",\n\t\t\t\"horizontal-offset\":%i", get_s16le(dmic, 4)); ++ if (get_s16le(dmic, 6) != 0) ++ fprintf(out, ",\n\t\t\t\"vertical-offset\":%i", get_s16le(dmic, 6)); ++ if (get_u8(dmic, 8) != 0) ++ fprintf(out, ",\n\t\t\t\"freq-low-band\":%i", get_u8(dmic, 8) * 5); ++ if (get_u8(dmic, 9) != 0) ++ fprintf(out, ",\n\t\t\t\"freq-high-band\":%i", get_u8(dmic, 9) * 500); ++ if (get_s16le(dmic, 10) != 0) ++ fprintf(out, ",\n\t\t\t\"direction-angle\":%i", get_s16le(dmic, 10)); ++ if (get_s16le(dmic, 12) != 0) ++ fprintf(out, ",\n\t\t\t\"elevation-angle\":%i", get_s16le(dmic, 12)); ++ angle_begin = get_s16le(dmic, 14); ++ angle_end = get_s16le(dmic, 16); ++ if (!((angle_begin == 180 && angle_end == -180) || ++ (angle_begin == -180 && angle_end == 180))) { ++ fprintf(out, ",\n\t\t\t\"vertical-angle-begin\":%i,\n", angle_begin); ++ fprintf(out, "\t\t\t\"vertical-angle-end\":%i", angle_end); ++ } ++ angle_begin = get_s16le(dmic, 18); ++ angle_end = get_s16le(dmic, 20); ++ if (!((angle_begin == 180 && angle_end == -180) || ++ (angle_begin == -180 && angle_end == 180))) { ++ fprintf(out, ",\n\t\t\t\"horizontal-angle-begin\":%i,\n", angle_begin); ++ fprintf(out, "\t\t\t\"horizontal-angle-end\":%i", angle_end); ++ } ++ fprintf(out, "\n\t\t}"); ++ return 0; ++} ++ ++static int nhlt_dmic_ep_to_json(FILE *out, uint8_t *ep, u_int32_t ep_size) ++{ ++ u_int32_t off, specific_cfg_size; ++ u_int8_t config_type, array_type, mic, num_mics; ++ int res; ++ ++ off = NHLT_EP_HDR_SIZE; ++ specific_cfg_size = get_u32le(ep, off); ++ if (off + specific_cfg_size > ep_size) ++ goto oob; ++ off += 4; ++ config_type = get_u8(ep, off + 1); ++ if (config_type != 1) /* mic array */ ++ return 0; ++ array_type = get_u8(ep, off + 2); ++ if ((array_type & 0x0f) != 0x0f) { ++ fprintf(stderr, "Unsupported ArrayType %02x\n", array_type & 0x0f); ++ return -EINVAL; ++ } ++ num_mics = get_u8(ep, off + 3); ++ fprintf(out, "{\n"); ++ fprintf(out, "\t\"mics-data-version\":1,\n"); ++ fprintf(out, "\t\"mics-data-source\":\"acpi-nhlt\""); ++ for (mic = 0; mic < num_mics; mic++) { ++ if (off - NHLT_EP_HDR_SIZE + VENDOR_MIC_CFG_SIZE > specific_cfg_size) { ++ fprintf(out, "\n}\n"); ++ goto oob; ++ } ++ if (mic == 0) ++ fprintf(out, ",\n\t\"mics\":[\n"); ++ res = nhlt_dmic_config(out, ep + off + 4, mic); ++ if (res < 0) ++ return res; ++ off += VENDOR_MIC_CFG_SIZE; ++ } ++ if (num_mics > 0) ++ fprintf(out, "\n\t]\n"); ++ fprintf(out, "}\n"); ++ return num_mics; ++oob: ++ fprintf(stderr, "Data (out-of-bounds) error\n"); ++ return -EINVAL; ++} ++ ++static int nhlt_table_to_json(FILE *out, u_int8_t *nhlt, u_int32_t size) ++{ ++ u_int32_t _size, off, ep_size; ++ u_int8_t sum = 0, ep, ep_count, link_type, dmics = 0; ++ int res; ++ ++ _size = get_u32le(nhlt, 4); ++ if (_size != size) { ++ fprintf(stderr, "Table size mismatch (%08x != %08x)\n", _size, (u_int32_t)size); ++ return -EINVAL; ++ } ++ for (off = 0; off < size; off++) ++ sum += get_u8(nhlt, off); ++ if (sum != 0) { ++ fprintf(stderr, "Checksum error (%02x)\n", sum); ++ return -EINVAL; ++ } ++ /* skip header */ ++ off = ACPI_HDR_SIZE; ++ ep_count = get_u8(nhlt, off++); ++ for (ep = 0; ep < ep_count; ep++) { ++ if (off + 17 > size) ++ goto oob; ++ ep_size = get_u32le(nhlt, off); ++ if (off + ep_size > size) ++ goto oob; ++ link_type = get_u8(nhlt, off + 4); ++ res = 0; ++ if (link_type == 2) { /* PDM */ ++ res = nhlt_dmic_ep_to_json(out, nhlt + off, ep_size); ++ if (res > 0) ++ dmics++; ++ } ++ if (res < 0) ++ return res; ++ off += ep_size; ++ } ++ if (dmics == 0) { ++ fprintf(stderr, "No dmic endpoint found\n"); ++ return -EINVAL; ++ } ++ return 0; ++oob: ++ fprintf(stderr, "Data (out-of-bounds) error\n"); ++ return -EINVAL; ++} ++ ++static int nhlt_to_json(FILE *out, const char *nhlt_file) ++{ ++ struct stat st; ++ u_int8_t *buf; ++ int _errno, fd, res; ++ size_t pos, size; ++ ssize_t ret; ++ ++ if (stat(nhlt_file, &st)) ++ return -errno; ++ size = st.st_size; ++ if (size < 45) ++ return -EINVAL; ++ buf = malloc(size); ++ if (buf == NULL) ++ return -ENOMEM; ++ fd = open(nhlt_file, O_RDONLY); ++ if (fd < 0) { ++ _errno = errno; ++ fprintf(stderr, "Unable to open file '%s': %s\n", nhlt_file, strerror(errno)); ++ return _errno; ++ } ++ pos = 0; ++ while (pos < size) { ++ ret = read(fd, buf + pos, size - pos); ++ if (ret <= 0) { ++ fprintf(stderr, "Short read\n"); ++ close(fd); ++ free(buf); ++ return -EIO; ++ } ++ pos += ret; ++ } ++ close(fd); ++ res = nhlt_table_to_json(out, buf, size); ++ free(buf); ++ return res; ++} ++ ++/* ++ * ++ */ ++ ++#define PROG "nhlt-dmic-info" ++#define VERSION "1" ++ ++#define NHLT_FILE "/sys/firmware/acpi/tables/NHLT" ++ ++#define TITLE 0x0100 ++#define HEADER 0x0200 ++#define FILEARG 0x0400 ++ ++#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) ++ ++struct arg { ++ int sarg; ++ char *larg; ++ char *comment; ++}; ++ ++static struct arg args[] = { ++{ TITLE, NULL, "Usage: nhtl-dmic-json " }, ++{ HEADER, NULL, "global options:" }, ++{ 'h', "help", "this help" }, ++{ 'v', "version", "print version of this program" }, ++{ FILEARG | 'f', "file", "NHLT file (default " NHLT_FILE ")" }, ++{ FILEARG | 'o', "output", "output file" }, ++{ 0, NULL, NULL } ++}; ++ ++static void help(void) ++{ ++ struct arg *n = args, *a; ++ char *larg, sa[4], buf[32]; ++ int sarg; ++ ++ sa[0] = '-'; ++ sa[2] = ','; ++ sa[3] = '\0'; ++ while (n->comment) { ++ a = n; ++ n++; ++ sarg = a->sarg; ++ if (sarg & (HEADER|TITLE)) { ++ printf("%s%s\n", (sarg & HEADER) != 0 ? "\n" : "", ++ a->comment); ++ continue; ++ } ++ buf[0] = '\0'; ++ larg = a->larg; ++ sa[1] = a->sarg; ++ sprintf(buf, "%s%s%s", sa[1] ? sa : "", ++ larg ? "--" : "", larg ? larg : ""); ++ if (sarg & FILEARG) ++ strcat(buf, " #"); ++ printf(" %-15s %s\n", buf, a->comment); ++ } ++} ++ ++int main(int argc, char *argv[]) ++{ ++ char *nhlt_file = NHLT_FILE; ++ char *output_file = "-"; ++ int i, j, k, res; ++ struct arg *a; ++ struct option *o, *long_option; ++ char *short_option; ++ FILE *output = NULL; ++ ++ long_option = calloc(ARRAY_SIZE(args), sizeof(struct option)); ++ if (long_option == NULL) ++ exit(EXIT_FAILURE); ++ short_option = malloc(128); ++ if (short_option == NULL) { ++ free(long_option); ++ exit(EXIT_FAILURE); ++ } ++ for (i = j = k = 0; i < ARRAY_SIZE(args); i++) { ++ a = &args[i]; ++ if ((a->sarg & 0xff) == 0) ++ continue; ++ o = &long_option[j]; ++ o->name = a->larg; ++ o->has_arg = (a->sarg & FILEARG) != 0; ++ o->flag = NULL; ++ o->val = a->sarg & 0xff; ++ j++; ++ short_option[k++] = o->val; ++ if (o->has_arg) ++ short_option[k++] = ':'; ++ } ++ short_option[k] = '\0'; ++ while (1) { ++ int c; ++ ++ if ((c = getopt_long(argc, argv, short_option, long_option, ++ NULL)) < 0) ++ break; ++ switch (c) { ++ case 'h': ++ help(); ++ res = EXIT_SUCCESS; ++ goto out; ++ case 'f': ++ nhlt_file = optarg; ++ break; ++ case 'o': ++ output_file = optarg; ++ break; ++ case 'd': ++ debug = 1; ++ break; ++ case 'v': ++ printf(PROG " version " VERSION "\n"); ++ res = EXIT_SUCCESS; ++ goto out; ++ case '?': // error msg already printed ++ help(); ++ res = EXIT_FAILURE; ++ goto out; ++ default: // should never happen ++ fprintf(stderr, ++ "Invalid option '%c' (%d) not handled??\n", c, c); ++ } ++ } ++ free(short_option); ++ short_option = NULL; ++ free(long_option); ++ long_option = NULL; ++ ++ if (strcmp(output_file, "-") == 0) { ++ output = stdout; ++ } else { ++ output = fopen(output_file, "w+"); ++ if (output == NULL) { ++ fprintf(stderr, "Unable to create output file \"%s\": %s\n", ++ output_file, strerror(-errno)); ++ res = EXIT_FAILURE; ++ goto out; ++ } ++ } ++ ++ if (argc - optind > 0) ++ fprintf(stderr, PROG ": Ignoring extra parameters\n"); ++ ++ res = 0; ++ if (nhlt_to_json(output, nhlt_file)) ++ res = EXIT_FAILURE; ++ ++out: ++ if (output) ++ fclose(output); ++ free(short_option); ++ free(long_option); ++ return res; ++} +-- +2.39.2 + diff --git a/alsa-utils.spec b/alsa-utils.spec index 4fdf776..27fbe64 100644 --- a/alsa-utils.spec +++ b/alsa-utils.spec @@ -5,7 +5,7 @@ Summary: Advanced Linux Sound Architecture (ALSA) utilities Name: alsa-utils Version: %{baseversion}%{?fixversion} -Release: 1%{?dist} +Release: 2%{?dist} License: GPLv2+ URL: http://www.alsa-project.org/ Source: ftp://ftp.alsa-project.org/pub/utils/alsa-utils-%{version}.tar.bz2 @@ -128,6 +128,7 @@ find %{buildroot} -name "*.la" -exec rm {} \; %{_bindir}/axfer %{_bindir}/iecset %{_bindir}/speaker-test +%{_bindir}/nhlt-dmic-info %{_sbindir}/* %exclude %{_sbindir}/alsabat-test.sh %{_datadir}/alsa/ @@ -152,6 +153,7 @@ find %{buildroot} -name "*.la" -exec rm {} \; %{_mandir}/man1/speaker-test.1.gz %{_mandir}/man1/aconnect.1.gz %{_mandir}/man1/alsa-info.sh.1.gz +%{_mandir}/man1/nhlt-dmic-info.1.gz %dir /etc/alsa/ %dir %{alsacfgdir}/ @@ -195,6 +197,9 @@ fi %systemd_postun_with_restart alsa-state.service %changelog +* Tue May 16 2023 Jaroslav Kysela - 1.2.9-2 +* Add nhlt-dmic-info utility + * Thu May 4 2023 Jaroslav Kysela - 1.2.9-1 * Updated to 1.2.9