blob: 1b7bb0824b13539b5683141789176e83023d16e6 [file] [log] [blame]
# Lint as: python2, python3
# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provides Extension class and related classes with methods for parsing info.
Extensions are found after the 128-byte base EDID.
The various types of Extensions all inherit the basic Extension object.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import coordinated_video_timings as cvt_module
import data_block
import descriptor
import error
from six.moves import range
import standard_timings
TYPE_TIMING_EXTENSION = 'Timing Extension'
TYPE_CEA_861 = 'CEA-861 Series Timing Extension'
TYPE_VIDEO_TIMING_BLOCK = 'Video Timing Block Extension (VTB-EXT)'
TYPE_DISPLAY_INFORMATION = 'Display Information Extension (DI-EXT)'
TYPE_LOCALIZED_STRING = 'Localized String Extension (LS-EXT)'
TYPE_DIGITAL_PACKET_VIDEO_LINK = ('Digital Packet Video Link Extension '
'(DPVL-EXT)')
TYPE_EXTENSION_BLOCK_MAP = 'Extension Block Map'
TYPE_MANUFACTURER_EXTENSION = 'Extension defined by monitor manufacturer'
TYPE_UNKNOWN = 'Unknown Extension type'
def GetExtension(edid, index, version):
"""Fetches an extension to an EDID.
Args:
edid: The list form of the EDID being analyzed.
index: An integer indicating the index of the extension.
version: The EDID version (usually 1.3 or 1.4).
Returns:
An Extension object.
"""
block = edid[128 * index : 128 * (index + 1)]
tag = block[0]
if tag == 0x00:
return TimingExtension(block)
elif tag == 0x02:
return CEAExtension(block, version)
elif tag == 0x10:
return VTBExtension(block, version)
elif tag == 0x40:
return DisplayInformationExtension(block)
elif tag == 0x50:
return LocalizedStringExtension(block)
elif tag == 0x60:
return DPVLExtension(block)
elif tag == 0xF0:
return ExtensionBlockMap(block)
elif tag == 0xFF:
return ManufacturerExtension(block)
else:
return Extension(block, TYPE_UNKNOWN)
class Extension(object):
"""Defines a basic extension."""
def __init__(self, block, my_type, version=None):
"""Creates an Extension object.
Args:
block: A list of bytes that make up the extension.
my_type: A string indicating the type of extension.
version: The EDID version.
"""
self._block = block
self._type = my_type
self._version = version
@property
def tag(self):
"""Fetches the tag of the extension.
Returns:
An integer indicating the tag of the extension.
"""
return self._block[0]
@property
def type(self):
"""Fetches the type of the extension.
Returns:
A string indicating the type of the extension.
"""
return self._type
def GetBlock(self):
"""Fetches the bytes that make up the extension.
Returns:
A list of bytes that make up the extension.
"""
return self._block
def CheckErrors(self, index=None):
"""Creates a method for error checking to define in other Extensions.
Args:
index: The integer index of the extension being checked.
"""
pass
class TimingExtension(Extension):
"""Defines a Timing Extension."""
def __init__(self, block):
"""Creates a TimingExtension object.
Args:
block: The list of bytes that make up the extension.
"""
Extension.__init__(self, block, TYPE_TIMING_EXTENSION)
class CEAExtension(Extension):
"""Defines a CEA Extension, perhaps the most common type."""
def __init__(self, block, version):
"""Creates a CEAExtension object.
Args:
block: The list of bytes that make up the extension.
version: The version of the extension.
"""
Extension.__init__(self, block, TYPE_CEA_861, version)
self._dtd_start = block[2]
@property
def dtd_offset(self):
"""Fetches the start index of the Detailed Timing Descriptors.
Returns:
An integer indicating the start index of Detailed Timing Descriptors.
"""
return self._dtd_start
@property
def version(self):
"""Fetches the extension version.
Returns:
An integer indicating the extension version.
"""
return self._block[1]
@property
def underscan_support(self):
"""Fetches whether underscan is supported.
Returns:
A boolean indicating whether underscan is supported.
"""
return bool(self._block[3] & 0x80)
@property
def basic_audio_support(self):
"""Fetches whether basic audio is supported.
Returns:
A boolean indicating whether basic audio is supported.
"""
return bool(self._block[3] & 0x40)
@property
def ycbcr444_support(self):
"""Fetches whether YCbCr 4:4:4 is supported.
Returns:
A boolean indicating whether YCbCr 4:4:4 is supported.
"""
return bool(self._block[3] & 0x20)
@property
def ycbcr422_support(self):
"""Fetches whether YCbCr 4:2:2 is supported.
Returns:
A boolean indicating whether YCbCr 4:2:2 is supported.
"""
return bool(self._block[3] & 0x10)
@property
def native_dtd_count(self):
"""Fetches the number of native Detailed Timing Descriptors.
Returns:
An integer indicating the number of native Detailed Timing Descriptors.
"""
return self._block[3] & 0x0F
@property
def data_blocks(self):
"""Fetches the Data Block objects.
Returns:
A list of Data Block objects.
"""
# DTDs begin immediately, meaning there are no data blocks
if self._dtd_start == 4:
return None
dbs = []
current = 4
while current < self._dtd_start:
db = data_block.GetDataBlock(self._block, current)
dbs.append(db)
current += db.length + 1
return dbs
@property
def dtds(self):
"""Fetches the descriptor.DetailedTimingDescriptor objects.
Returns:
A list of descriptor.DetailedTimingDescriptor objects.
"""
dtds = []
for x in range(self._dtd_start, self._GetPadIndex(), 18):
dtd = descriptor.GetDescriptor(self._block, x, self._version)
dtds.append(dtd)
return dtds
def _GetPadIndex(self):
"""Fetches the start index of post-DTD padding.
Returns:
An integer indicating the start index of post-DTD padding.
"""
for x in range(self._dtd_start, 127, 18):
if self._block[x] == self._block[x + 1] == 0:
return x
return 127 - (127 - self._dtd_start) % 18
def CheckErrors(self, index=None):
"""Checks the extension for errors.
All bytes after the end of DTDs should be 0x00.
Args:
index: The integer index of the extension.
Returns:
An Error object.
"""
ext_index = '(Extension #%d)' % index if index else ''
padding_start = self._GetPadIndex()
padding = self._block[padding_start : 127]
if padding != [0] * len(padding):
found = '%02X ' * len(padding) % tuple(padding,)
return [error.Error('CEA extension %s' % ext_index, 'All bytes after DTDs'
' should be 0x00', 'All 0x00s', found)]
else:
return None
class VTBExtension(Extension):
"""Defines a VTB Extension."""
def __init__(self, block, version):
"""Creates a VTBExtension object.
Args:
block: The list of bytes that make up the extension.
version: The version of the extension.
"""
Extension.__init__(self, block, TYPE_VIDEO_TIMING_BLOCK, version)
self._dtb_count = block[2]
self._cvt_count = block[3]
self._st_count = block[4]
@property
def version(self):
"""Fetches the VTB extension version.
Returns:
An integer indicating the version.
"""
return self._block[1]
@property
def dtb_count(self):
"""Fetches the number of DetailedTimingDescriptor objects.
Returns:
An integer indicating the number of DetailedTimingDescriptor objects.
"""
return self._dtb_count
@property
def cvt_count(self):
"""Fetches the number of CoordinatedVideoTiming objects.
Returns:
An integer indicating the number of CoordinatedVideoTiming objects.
"""
return self._cvt_count
@property
def st_count(self):
"""Fetches the number of StandardTiming objects.
Returns:
An integer indicating the number of StandardTiming objects.
"""
return self._st_count
@property
def dtbs(self):
"""Fetches the descriptor.DetailedTimingDescriptors.
Returns:
A list of descriptor.DetailedTimingDescriptors.
"""
dtbs = []
dtb_start = 5
for x in range(0, self._dtb_count):
dtd = descriptor.GetDescriptor(self._block, dtb_start + (x * 18),
self._version)
dtbs.append(dtd)
return dtbs
@property
def cvts(self):
"""Fetches the coordinated_video_timings.CoordinatedVideoTiming objects.
Returns:
A list of coordinated_video_timings.CoordinatedVideoTiming objects.
"""
cvts = []
cvt_start = 5 + (18 * self._dtb_count)
for x in range(0, self._cvt_count):
cvt = cvt_module.GetCoordinatedVideoTiming(self._block,
cvt_start + (x * 3))
cvts.append(cvt)
return cvts
@property
def sts(self):
"""Fetches the standard_timings.StandardTiming objects.
Returns:
A list of standard_timings.StandardTiming objects.
"""
sts = []
st_start = 5 + (18 * self._dtb_count) + (3 * self._cvt_count)
for x in range(0, self._st_count):
st = standard_timings.GetStandardTiming(self._block, st_start + (x * 2),
self._version)
sts.append(st)
return sts
def CheckErrors(self, index=None):
"""Checks the extension for errors.
Args:
index: The integer index of the extension.
Returns:
A list of error.Error objects.
"""
errors = []
cvts = self.cvts
for x in range(0, self._cvt_count):
err = cvts[x].CheckErrors(x + 1)
if err:
errors.extend(err)
sts = self.sts
for x in range(0, self._st_count):
err = sts[x].CheckErrors(x + 1)
if err:
errors.extend(err)
# Check that all bytes after standard timing blocks end are 0x00
unused_start = (5 + (18 * self._dtb_count) + (3 * self._cvt_count) +
(2 * self._st_count))
ext_index = '(Extension #%d)' % index if index else ''
padding = self._block[unused_start : 127]
if padding != [0] * len(padding):
found = '0x%02X ' * len(padding) % tuple(padding,)
errors.append(error.Error('VTB Extension %s' % ext_index,
'All bytes after STs should be 0x00',
'All 0x00s', found))
return errors
class DisplayInformationExtension(Extension):
"""Analyzes a Display Information Extension."""
def __init__(self, block):
"""Creates a DisplayInformationExtension object.
Args:
block: The list of bytes that make up the extension.
"""
Extension.__init__(self, block, TYPE_DISPLAY_INFORMATION)
class LocalizedStringExtension(Extension):
"""Analyzes a Localized String Extension."""
def __init__(self, block):
"""Creates a LocalizedStringExtension object.
Args:
block: The list of bytes that make up the extension.
"""
Extension.__init__(self, block, TYPE_LOCALIZED_STRING)
class DPVLExtension(Extension):
"""Analyzes a Digital Packet Video Link Extension."""
def __init__(self, block):
"""Creates a DPVLExtension object.
Args:
block: The list of bytes that make up the extension.
"""
Extension.__init__(self, block, TYPE_DIGITAL_PACKET_VIDEO_LINK)
class ExtensionBlockMap(Extension):
"""Defines an Extension Block Map (which is also an extension)."""
def __init__(self, block):
"""Creates an ExtensionBlockMap object.
Args:
block: The list of bytes that make up the extension.
"""
Extension.__init__(self, block, TYPE_EXTENSION_BLOCK_MAP)
@property
def all_tags(self):
"""Fetches all 126 tags in the block map.
Zeroes indicate unused blocks.
Returns:
A list of 126 tags (integers) indicating the tag of each following block.
"""
return self._block[1:127]
class ManufacturerExtension(Extension):
"""Defines a Manufacturer Extension."""
def __init__(self, block):
"""Creates a ManufacturerExtension object.
Args:
block: The list of bytes that make up the extension.
"""
Extension.__init__(self, block, TYPE_MANUFACTURER_EXTENSION)