blob: 3d7fd4dd0a827ff70971f16041e0f6a3e3bcc369 [file] [log] [blame]
/*
* Copyright © 2019 Intel Corporation
*
* 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.
*
* Authors: Simon Ser <simon.ser@intel.com>
*/
#include "config.h"
#include <dirent.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "igt_core.h"
#include "igt_eld.h"
#define ELD_PREFIX "eld#"
#define ELD_DELIM " \t"
/**
* EDID-Like Data (ELD) is metadata parsed and exposed by ALSA for HDMI and
* DisplayPort connectors supporting audio. This includes the monitor name and
* the supported audio parameters (formats, sampling rates, sample sizes and so
* on).
*
* Audio parameters come from Short Audio Descriptors (SAD) blocks in the
* EDID. Enumerations from igt_edid are used since they are the same.
*/
static enum cea_sad_format parse_sad_coding_type(const char *value)
{
if (strcmp(value, "LPCM") == 0)
return CEA_SAD_FORMAT_PCM;
else
return 0;
}
static enum cea_sad_sampling_rate parse_sad_rate(const char *value)
{
switch (atoi(value)) {
case 32000:
return CEA_SAD_SAMPLING_RATE_32KHZ;
case 44100:
return CEA_SAD_SAMPLING_RATE_44KHZ;
case 48000:
return CEA_SAD_SAMPLING_RATE_48KHZ;
case 88000:
return CEA_SAD_SAMPLING_RATE_88KHZ;
case 96000:
return CEA_SAD_SAMPLING_RATE_96KHZ;
case 176000:
return CEA_SAD_SAMPLING_RATE_176KHZ;
case 192000:
return CEA_SAD_SAMPLING_RATE_192KHZ;
default:
return 0;
}
}
static enum cea_sad_pcm_sample_size parse_sad_bit(const char *value)
{
switch (atoi(value)) {
case 16:
return CEA_SAD_SAMPLE_SIZE_16;
case 20:
return CEA_SAD_SAMPLE_SIZE_20;
case 24:
return CEA_SAD_SAMPLE_SIZE_24;
default:
return 0;
}
}
static void parse_sad_field(struct eld_sad *sad, const char *key, char *value)
{
char *tok;
/* Some fields are prefixed with the raw hex value, strip it */
if (value[0] == '[') {
value = strchr(value, ' ');
igt_assert(value != NULL);
value++; /* skip the space */
}
/* Single-value fields */
if (strcmp(key, "coding_type") == 0)
sad->coding_type = parse_sad_coding_type(value);
else if (strcmp(key, "channels") == 0)
sad->channels = atoi(value);
/* Multiple-value fields */
tok = strtok(value, " ");
while (tok) {
if (strcmp(key, "rates") == 0)
sad->rates |= parse_sad_rate(tok);
else if (strcmp(key, "bits") == 0)
sad->bits |= parse_sad_bit(tok);
tok = strtok(NULL, " ");
}
}
/** eld_parse_entry: parse an ELD entry
*
* Here is an example of an ELD entry:
*
* $ cat /proc/asound/card0/eld#0.2
* monitor_present 1
* eld_valid 1
* monitor_name U2879G6
* connection_type DisplayPort
* eld_version [0x2] CEA-861D or below
* edid_version [0x3] CEA-861-B, C or D
* manufacture_id 0xe305
* product_id 0x2879
* port_id 0x800
* support_hdcp 0
* support_ai 0
* audio_sync_delay 0
* speakers [0x1] FL/FR
* sad_count 1
* sad0_coding_type [0x1] LPCM
* sad0_channels 2
* sad0_rates [0xe0] 32000 44100 48000
* sad0_bits [0xe0000] 16 20 24
*
* Each entry contains one or more SAD blocks. Their contents is exposed in
* sadN_* fields.
*/
static bool eld_parse_entry(const char *path, struct eld_entry *eld)
{
FILE *f;
char buf[1024];
char *key, *value, *sad_key;
size_t len;
bool monitor_present = false;
int sad_index;
memset(eld, 0, sizeof(*eld));
f = fopen(path, "r");
if (!f) {
igt_debug("Failed to open ELD file: %s\n", path);
return false;
}
while ((fgets(buf, sizeof(buf), f)) != NULL) {
len = strlen(buf);
if (buf[len - 1] == '\n')
buf[len - 1] = '\0';
key = strtok(buf, ELD_DELIM);
value = strtok(NULL, "");
/* Skip whitespace at the beginning */
value += strspn(value, ELD_DELIM);
if (strcmp(key, "monitor_present") == 0)
monitor_present = strcmp(value, "1") == 0;
else if (strcmp(key, "eld_valid") == 0)
eld->valid = strcmp(value, "1") == 0;
else if (strcmp(key, "monitor_name") == 0)
snprintf(eld->monitor_name, sizeof(eld->monitor_name),
"%s", value);
else if (strcmp(key, "sad_count") == 0)
eld->sads_len = atoi(value);
else if (sscanf(key, "sad%d_%ms", &sad_index, &sad_key) == 2) {
igt_assert(sad_index < ELD_SADS_CAP);
igt_assert(sad_index < eld->sads_len);
parse_sad_field(&eld->sads[sad_index], sad_key, value);
free(sad_key);
}
}
if (ferror(f) != 0) {
igt_debug("Failed to read ELD file %s: %d\n", path, ferror(f));
return false;
}
fclose(f);
if (!monitor_present)
igt_debug("Monitor not present in ELD: %s\n", path);
return monitor_present;
}
/** eld_get_igt: retrieve the ALSA ELD entry matching the IGT EDID */
bool eld_get_igt(struct eld_entry *eld)
{
DIR *dir;
struct dirent *dirent;
int i;
char card[64];
char path[PATH_MAX];
for (i = 0; i < 8; i++) {
snprintf(card, sizeof(card), "/proc/asound/card%d", i);
dir = opendir(card);
if (!dir)
continue;
while ((dirent = readdir(dir))) {
if (strncmp(dirent->d_name, ELD_PREFIX,
strlen(ELD_PREFIX)) != 0)
continue;
snprintf(path, sizeof(path), "%s/%s", card,
dirent->d_name);
if (!eld_parse_entry(path, eld)) {
continue;
}
if (!eld->valid) {
igt_debug("Skipping invalid ELD: %s\n", path);
continue;
}
if (strcmp(eld->monitor_name, "IGT") != 0) {
igt_debug("Skipping non-IGT ELD: %s "
"(monitor name: %s)\n",
path, eld->monitor_name);
continue;
}
closedir(dir);
return true;
}
closedir(dir);
}
return false;
}
/** eld_has_igt: check whether ALSA has detected the audio-capable IGT EDID by
* parsing ELD entries */
bool eld_has_igt(void)
{
struct eld_entry eld;
return eld_get_igt(&eld);
}