blob: 6ab9c7b6eaa824382b054fddd1c45dde5cb61690 [file] [log] [blame] [edit]
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <sys/types.h>
#include "bits.h"
#include "displayid.h"
#include "displayid2.h"
/**
* The size of a DisplayID v2 section header.
*/
#define DISPLAYID2_HEADER_SIZE 4
/**
* The size of the mandatory fields in a DisplayID v2 section (header + checksum).
*/
#define DISPLAYID2_MIN_SIZE (DISPLAYID2_HEADER_SIZE + 1)
/**
* The maximum size of a DisplayID v2 section.
*/
#define DISPLAYID2_MAX_SIZE 256
/**
* The size of a DisplayID v2 data block header (tag, revision and size).
*/
#define DISPLAYID2_DATA_BLOCK_HEADER_SIZE 3
static void
add_failure(struct di_displayid2 *displayid2, const char fmt[], ...)
{
va_list args;
va_start(args, fmt);
_di_logger_va_add_failure(displayid2->logger, fmt, args);
va_end(args);
}
static bool
parse_cta861_block(struct di_displayid2 *displayid2, struct di_displayid2_data_block *data_block,
const uint8_t *data, size_t size)
{
struct di_cta cta;
size_t i;
uint8_t data_block_tag, data_block_size;
struct di_cta_data_block *cta_data_block;
cta = (struct di_cta){
/* The DisplayID block doesn't specify the CTA revision. Use
* the latest one to silence warnings. */
.revision = 3,
.logger = displayid2->logger,
};
i = 0x03;
while (i < size) {
data_block_size = get_bit_range(data[i], 4, 0);
data_block_tag = get_bit_range(data[i], 7, 5);
if (data_block_size > size - i - 1) {
add_failure(displayid2,
"CTA data block size (%" PRIu8 ") exceeds number of remaining bytes (%zu)",
data_block_size, size - i - 1);
break;
}
if (!_di_cta_data_block_parse(&cta, data_block_tag,
&data[i + 1], data_block_size, &cta_data_block)) {
return false;
}
if (cta_data_block != NULL) {
assert(data_block->cta861.data_blocks_len < DISPLAYID2_CTA861_MAX_DATA_BLOCKS);
data_block->cta861.data_blocks[data_block->cta861.data_blocks_len++] = cta_data_block;
}
i += 1 + data_block_size;
}
return true;
}
static ssize_t
parse_data_block(struct di_displayid2 *displayid2, const uint8_t *data,
size_t size)
{
uint8_t tag;
size_t data_block_size;
struct di_displayid2_data_block *data_block = NULL;
assert(size >= DISPLAYID2_DATA_BLOCK_HEADER_SIZE);
tag = data[0x00];
data_block_size = (size_t) data[0x02] + DISPLAYID2_DATA_BLOCK_HEADER_SIZE;
if (data_block_size > size) {
add_failure(displayid2,
"The length of this DisplayID data block (%d) exceeds the number of bytes remaining (%zu)",
data_block_size, size);
goto skip;
}
data_block = calloc(1, sizeof(*data_block));
if (!data_block)
goto error;
switch (tag) {
case DI_DISPLAYID2_DATA_BLOCK_CTA861:
if (!parse_cta861_block(displayid2, data_block, data, data_block_size)) {
goto error;
}
break;
case DI_DISPLAYID2_DATA_BLOCK_PRODUCT_ID:
case DI_DISPLAYID2_DATA_BLOCK_DISPLAY_PARAMS:
case DI_DISPLAYID2_DATA_BLOCK_TYPE_VII_TIMING:
case DI_DISPLAYID2_DATA_BLOCK_TYPE_VIII_TIMING:
case DI_DISPLAYID2_DATA_BLOCK_TYPE_IX_TIMING:
case DI_DISPLAYID2_DATA_BLOCK_DYN_TIMING_RANGE_LIMITS:
case DI_DISPLAYID2_DATA_BLOCK_DISPLAY_INTERFACE_FEATURES:
case DI_DISPLAYID2_DATA_BLOCK_STEREO_DISPLAY_INTERFACE:
case DI_DISPLAYID2_DATA_BLOCK_TILED_DISPLAY_TOPO:
case DI_DISPLAYID2_DATA_BLOCK_CONTAINERID:
case DI_DISPLAYID2_DATA_BLOCK_TYPE_X_TIMING:
case DI_DISPLAYID2_DATA_BLOCK_ADAPTIVE_SYNC:
case DI_DISPLAYID2_DATA_BLOCK_ARVR_HMD:
case DI_DISPLAYID2_DATA_BLOCK_ARVR_LAYER:
break; /* Supported */
case 0x7E:
goto skip; /* Vendor-specific */
default:
add_failure(displayid2,
"Unknown DisplayID v2 Data Block (0x%" PRIx8 ", length %" PRIu8 ")",
tag, data_block_size - DISPLAYID2_DATA_BLOCK_HEADER_SIZE);
goto skip;
}
data_block->tag = tag;
assert(displayid2->data_blocks_len < DISPLAYID2_MAX_DATA_BLOCKS);
displayid2->data_blocks[displayid2->data_blocks_len++] = data_block;
return (ssize_t) data_block_size;
skip:
free(data_block);
return (ssize_t) data_block_size;
error:
free(data_block);
return -1;
}
static bool
is_all_zeroes(const uint8_t *data, size_t size)
{
size_t i;
for (i = 0; i < size; i++) {
if (data[i] != 0)
return false;
}
return true;
}
static bool
is_data_block_end(const uint8_t *data, size_t size)
{
if (size < DISPLAYID2_DATA_BLOCK_HEADER_SIZE)
return true;
return is_all_zeroes(data, DISPLAYID2_DATA_BLOCK_HEADER_SIZE);
}
static bool
validate_checksum(const uint8_t *data, size_t size)
{
uint8_t sum = 0;
size_t i;
for (i = 0; i < size; i++) {
sum += data[i];
}
return sum == 0;
}
bool
_di_displayid2_parse(struct di_displayid2 *displayid2, const uint8_t *data,
size_t size, struct di_logger *logger)
{
size_t section_size, i, max_data_block_size;
ssize_t data_block_size;
int version;
uint8_t product_primary_use_case;
if (size < DISPLAYID2_MIN_SIZE) {
errno = EINVAL;
return false;
}
displayid2->logger = logger;
version = _di_displayid_parse_version(data, size);
displayid2->revision = get_bit_range(data[0x00], 3, 0);
if (version != 2) {
errno = ENOTSUP;
return false;
}
section_size = (size_t) data[0x01] + DISPLAYID2_MIN_SIZE;
if (section_size > DISPLAYID2_MAX_SIZE || section_size > size) {
errno = EINVAL;
return false;
}
if (!validate_checksum(data, section_size)) {
errno = EINVAL;
return false;
}
product_primary_use_case = data[0x02];
switch (product_primary_use_case) {
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_EXTENSION:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_TEST:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_GENERIC:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_TV:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_DESKTOP_PRODUCTIVITY:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_DESKTOP_GAMING:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_PRESENTATION:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_HMD_VR:
case DI_DISPLAYID2_PRODUCT_PRIMARY_USE_CASE_HMD_AR:
displayid2->product_primary_use_case = product_primary_use_case;
break;
default:
errno = EINVAL;
return false;
}
i = DISPLAYID2_HEADER_SIZE;
max_data_block_size = 0;
while (i < section_size - 1) {
max_data_block_size = section_size - 1 - i;
if (is_data_block_end(&data[i], max_data_block_size))
break;
data_block_size = parse_data_block(displayid2, &data[i],
max_data_block_size);
if (data_block_size < 0)
return false;
assert(data_block_size > 0);
i += (size_t) data_block_size;
}
if (!is_all_zeroes(&data[i], max_data_block_size)) {
if (max_data_block_size < DISPLAYID2_DATA_BLOCK_HEADER_SIZE) {
add_failure(displayid2,
"Not enough bytes remain (%zu) for a DisplayID data block and the DisplayID filler is non-0.",
max_data_block_size);
} else {
add_failure(displayid2, "Padding: Contains non-zero bytes.");
}
}
displayid2->logger = NULL;
return true;
}
static void
data_block_destroy(struct di_displayid2_data_block *data_block)
{
size_t i;
for (i = 0; i < data_block->cta861.data_blocks_len; i++)
_di_cta_data_block_destroy(data_block->cta861.data_blocks[i]);
free(data_block);
}
void
_di_displayid2_finish(struct di_displayid2 *displayid2)
{
size_t i;
for (i = 0; i < displayid2->data_blocks_len; i++)
data_block_destroy(displayid2->data_blocks[i]);
}
int
di_displayid2_get_revision(const struct di_displayid2 *displayid2)
{
return displayid2->revision;
}
enum di_displayid2_product_primary_use_case
di_displayid2_get_product_primary_use_case(const struct di_displayid2 *displayid2)
{
return displayid2->product_primary_use_case;
}
const struct di_displayid2_data_block *const *
di_displayid2_get_data_blocks(const struct di_displayid2 *displayid2)
{
return (const struct di_displayid2_data_block *const *) displayid2->data_blocks;
}
enum di_displayid2_data_block_tag
di_displayid2_data_block_get_tag(const struct di_displayid2_data_block *data_block)
{
return data_block->tag;
}
const struct di_cta_data_block *const *
di_displayid2_data_block_get_cta_data_blocks(const struct di_displayid2_data_block *data_block)
{
if (data_block->tag != DI_DISPLAYID2_DATA_BLOCK_CTA861) {
return NULL;
}
return (const struct di_cta_data_block *const *) data_block->cta861.data_blocks;
}