2022-09-02 12:48:35 +02:00
|
|
|
#include <assert.h>
|
2022-08-31 12:16:36 +02:00
|
|
|
#include <errno.h>
|
2022-09-02 12:48:35 +02:00
|
|
|
#include <inttypes.h>
|
|
|
|
#include <stdlib.h>
|
2022-08-31 12:16:36 +02:00
|
|
|
|
|
|
|
#include "bits.h"
|
|
|
|
#include "displayid.h"
|
|
|
|
|
2022-09-02 08:47:38 +02:00
|
|
|
/**
|
|
|
|
* The size of the mandatory fields in a DisplayID section.
|
|
|
|
*/
|
|
|
|
#define DISPLAYID_MIN_SIZE 5
|
|
|
|
/**
|
|
|
|
* The maximum size of a DisplayID section.
|
|
|
|
*/
|
|
|
|
#define DISPLAYID_MAX_SIZE 256
|
2022-09-02 12:48:35 +02:00
|
|
|
/**
|
|
|
|
* The size of a DisplayID data block header (tag, revision and size).
|
|
|
|
*/
|
|
|
|
#define DISPLAYID_DATA_BLOCK_HEADER_SIZE 3
|
2022-09-02 08:47:38 +02:00
|
|
|
|
2022-09-02 09:35:50 +02:00
|
|
|
static void
|
|
|
|
add_failure(struct di_displayid *displayid, const char fmt[], ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
va_start(args, fmt);
|
|
|
|
_di_logger_va_add_failure(displayid->logger, fmt, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
2022-09-02 12:48:35 +02:00
|
|
|
static ssize_t
|
|
|
|
parse_data_block(struct di_displayid *displayid, const uint8_t *data,
|
|
|
|
size_t size)
|
|
|
|
{
|
|
|
|
uint8_t tag;
|
|
|
|
size_t data_block_size;
|
|
|
|
struct di_displayid_data_block *data_block;
|
|
|
|
|
|
|
|
assert(size >= DISPLAYID_DATA_BLOCK_HEADER_SIZE);
|
|
|
|
|
|
|
|
tag = data[0x00];
|
|
|
|
data_block_size = (size_t) data[0x02] + DISPLAYID_DATA_BLOCK_HEADER_SIZE;
|
|
|
|
if (data_block_size > size) {
|
|
|
|
add_failure(displayid,
|
|
|
|
"The length of this DisplayID data block (%d) exceeds the number of bytes remaining (%zu)",
|
|
|
|
data_block_size, size);
|
|
|
|
goto skip;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (tag) {
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_PRODUCT_ID:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_DISPLAY_PARAMS:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_COLOR_CHARACT:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_TYPE_I_TIMING:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_TYPE_II_TIMING:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_TYPE_III_TIMING:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_TYPE_IV_TIMING:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_VESA_TIMING:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_CEA_TIMING:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_TIMING_RANGE_LIMITS:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_PRODUCT_SERIAL:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_ASCII_STRING:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_DISPLAY_DEVICE_DATA:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_INTERFACE_POWER_SEQ:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_TRANSFER_CHARACT:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_DISPLAY_INTERFACE:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_STEREO_DISPLAY_INTERFACE:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_TYPE_V_TIMING:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_TILED_DISPLAY_TOPO:
|
|
|
|
case DI_DISPLAYID_DATA_BLOCK_TYPE_VI_TIMING:
|
|
|
|
break; /* Supported */
|
|
|
|
case 0x7F:
|
|
|
|
goto skip; /* Vendor-specific */
|
|
|
|
default:
|
|
|
|
add_failure(displayid,
|
|
|
|
"Unknown DisplayID Data Block (0x%" PRIx8 ", length %" PRIu8 ")",
|
|
|
|
tag, data_block_size - DISPLAYID_DATA_BLOCK_HEADER_SIZE);
|
|
|
|
goto skip;
|
|
|
|
}
|
|
|
|
|
|
|
|
data_block = calloc(1, sizeof(*data_block));
|
|
|
|
if (!data_block)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
data_block->tag = tag;
|
|
|
|
|
|
|
|
assert(displayid->data_blocks_len < DISPLAYID_MAX_DATA_BLOCKS);
|
|
|
|
displayid->data_blocks[displayid->data_blocks_len++] = data_block;
|
|
|
|
|
|
|
|
skip:
|
|
|
|
return (ssize_t) data_block_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 < DISPLAYID_DATA_BLOCK_HEADER_SIZE)
|
|
|
|
return true;
|
|
|
|
return is_all_zeroes(data, DISPLAYID_DATA_BLOCK_HEADER_SIZE);
|
|
|
|
}
|
|
|
|
|
2022-09-02 09:15:53 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-08-31 12:16:36 +02:00
|
|
|
bool
|
|
|
|
_di_displayid_parse(struct di_displayid *displayid, const uint8_t *data,
|
|
|
|
size_t size, struct di_logger *logger)
|
|
|
|
{
|
2022-09-02 12:48:35 +02:00
|
|
|
size_t section_size, i, max_data_block_size;
|
|
|
|
ssize_t data_block_size;
|
2022-09-02 09:03:34 +02:00
|
|
|
uint8_t product_type;
|
2022-09-02 08:47:38 +02:00
|
|
|
|
2022-09-02 12:48:35 +02:00
|
|
|
if (size < DISPLAYID_MIN_SIZE) {
|
2022-08-31 12:16:36 +02:00
|
|
|
errno = EINVAL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-09-02 09:35:50 +02:00
|
|
|
displayid->logger = logger;
|
|
|
|
|
2022-08-31 12:16:36 +02:00
|
|
|
displayid->version = get_bit_range(data[0x00], 7, 4);
|
|
|
|
displayid->revision = get_bit_range(data[0x00], 3, 0);
|
|
|
|
if (displayid->version == 0 || displayid->version > 1) {
|
|
|
|
errno = ENOTSUP;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-09-02 08:47:38 +02:00
|
|
|
section_size = (size_t) data[0x01] + DISPLAYID_MIN_SIZE;
|
|
|
|
if (section_size > DISPLAYID_MAX_SIZE || section_size > size) {
|
|
|
|
errno = EINVAL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-09-02 09:15:53 +02:00
|
|
|
if (!validate_checksum(data, section_size)) {
|
|
|
|
errno = EINVAL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-09-02 09:03:34 +02:00
|
|
|
product_type = data[0x02];
|
|
|
|
switch (product_type) {
|
|
|
|
case DI_DISPLAYID_PRODUCT_TYPE_EXTENSION:
|
|
|
|
case DI_DISPLAYID_PRODUCT_TYPE_TEST:
|
|
|
|
case DI_DISPLAYID_PRODUCT_TYPE_DISPLAY_PANEL:
|
|
|
|
case DI_DISPLAYID_PRODUCT_TYPE_STANDALONE_DISPLAY:
|
|
|
|
case DI_DISPLAYID_PRODUCT_TYPE_TV_RECEIVER:
|
|
|
|
case DI_DISPLAYID_PRODUCT_TYPE_REPEATER:
|
|
|
|
case DI_DISPLAYID_PRODUCT_TYPE_DIRECT_DRIVE:
|
|
|
|
displayid->product_type = product_type;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
errno = EINVAL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-09-02 12:48:35 +02:00
|
|
|
i = DISPLAYID_MIN_SIZE - 1;
|
|
|
|
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(displayid, &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 < DISPLAYID_DATA_BLOCK_HEADER_SIZE)
|
|
|
|
add_failure(displayid,
|
|
|
|
"Not enough bytes remain (%zu) for a DisplayID data block and the DisplayID filler is non-0.",
|
|
|
|
max_data_block_size);
|
|
|
|
else
|
|
|
|
add_failure(displayid, "Padding: Contains non-zero bytes.");
|
|
|
|
}
|
|
|
|
|
2022-09-02 09:35:50 +02:00
|
|
|
displayid->logger = NULL;
|
2022-08-31 12:16:36 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-09-02 12:48:35 +02:00
|
|
|
void
|
|
|
|
_di_displayid_finish(struct di_displayid *displayid)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < displayid->data_blocks_len; i++)
|
|
|
|
free(displayid->data_blocks[i]);
|
|
|
|
}
|
|
|
|
|
2022-08-31 12:16:36 +02:00
|
|
|
int
|
|
|
|
di_displayid_get_version(const struct di_displayid *displayid)
|
|
|
|
{
|
|
|
|
return displayid->version;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
di_displayid_get_revision(const struct di_displayid *displayid)
|
|
|
|
{
|
|
|
|
return displayid->revision;
|
|
|
|
}
|
2022-09-02 09:03:34 +02:00
|
|
|
|
|
|
|
enum di_displayid_product_type
|
|
|
|
di_displayid_get_product_type(const struct di_displayid *displayid)
|
|
|
|
{
|
|
|
|
return displayid->product_type;
|
|
|
|
}
|
2022-09-02 12:48:35 +02:00
|
|
|
|
|
|
|
enum di_displayid_data_block_tag
|
|
|
|
di_displayid_data_block_get_tag(const struct di_displayid_data_block *data_block)
|
|
|
|
{
|
|
|
|
return data_block->tag;
|
|
|
|
}
|
|
|
|
|
|
|
|
const struct di_displayid_data_block *const *
|
|
|
|
di_displayid_get_data_blocks(const struct di_displayid *displayid)
|
|
|
|
{
|
|
|
|
return (const struct di_displayid_data_block *const *) displayid->data_blocks;
|
|
|
|
}
|