| #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; |
| } |