blob: e5c416ce885c48a23068362165ee23f3ad91ec38 [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2012 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.
"""Wrapper for EDID data parsing and loading.
See for more info:
http://en.wikipedia.org/wiki/Extended_display_identification_data
"""
import binascii
import logging
import os
import re
from fcntl import ioctl
from time import sleep
import factory_common # pylint: disable=W0611
from cros.factory.hwid.v2 import hwid_tool
from cros.factory.gooftool.common import Shell
# Constants lifted from EDID documentation.
VERSION = 0x01
MAGIC = '\x00\xff\xff\xff\xff\xff\xff\x00'
MAGIC_OFFSET = 0
MANUFACTURER_ID_OFFSET = 8
PRODUCT_ID_OFFSET = 10
VERSION_OFFSET = 18
REVISION_OFFSET = 19
PIXEL_CLOCK_OFFSET = 54
HORIZONTAL_OFFSET = 56
HORIZONTAL_HIGH_OFFSET = 58
VERTICAL_OFFSET = 59
VERTICAL_HIGH_OFFSET = 61
CHECKSUM_OFFSET = 127
I2C_LVDS_ADDRESS = 0x50
MINIMAL_SIZE = 128
MANUFACTURER_ID_BITS = 5
PREFIX_TEGRA = 'edid[000]'
def Parse(content):
"""Public interface of EDID parser.
Args:
content: EDID data retrieved from device.
"""
# Check if it's from Tegra.
if content.startswith(PREFIX_TEGRA):
return _ParseTegra(content)
else:
return _ParseBinaryBlob(content)
def _ParseTegra(content):
"""Parser for EDID data exported by tegra_edid driver.
When tegra_edid driver is used, the exported EDID is in text format, ex:
edid[000] = 00 ff ff ff ff ff ff 00 06 af 2c 13 00 00 00 00
edid[010] = 00 18 01 03 80 1d 10 78 0a bb f5 94 55 54 90 27
edid[020] = 23 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01
edid[030] = 01 01 01 01 01 01 26 1b 56 64 50 00 16 30 30 20
edid[040] = 36 00 25 a4 10 00 00 18 00 00 00 0f 00 00 00 00
edid[050] = 00 00 00 00 00 00 00 00 00 20 00 00 00 fe 00 41
edid[060] = 55 4f 0a 20 20 20 20 20 20 20 20 20 00 00 00 fe
edid[070] = 00 42 31 33 33 58 54 4e 30 31 2e 33 20 0a 00 4b
Thus, we have to strip the first 12 characters, all white spaces, and all
newline characters, then transform the rest from hex code into a binary blob.
Args:
content: EDID file content from a Tegra device.
"""
return _ParseBinaryBlob(binascii.unhexlify(
re.sub(r'\s|(edid\[\d{3}\] = )', '', content)))
def _ParseBinaryBlob(blob):
"""Binary EDID Parser (light-weight parse-edid replacement).
Simple parsing of EDID. The full-feature parser (parse-edid) has
many more dependencies, and so is too heavy-weight for use here.
TODO(hungte) Use parse-edid if it becomes practical/available.
Specifically once we know that it will be available on all of our
systems.
Args:
blob: a binary blob with encoded EDID.
Returns:
A dict of extracted keys to extracted EDID fields. Return None if
the blob is not a valid EDID record, and also log warning messages
indicating the reason for parsing failure.
"""
def read_short_be(offset):
return (ord(blob[offset]) << 8) | ord(blob[offset + 1])
def read_short_le(offset):
return (ord(blob[offset + 1]) << 8) | ord(blob[offset])
# Check size, magic, and version
if len(blob) < MINIMAL_SIZE:
logging.warning('EDID parsing error: length too small.')
return None
if (blob[MAGIC_OFFSET:(MAGIC_OFFSET + len(MAGIC))] !=
MAGIC):
logging.warning('EDID parse error: incorrect header.')
return None
if ord(blob[VERSION_OFFSET]) != VERSION:
logging.warning('EDID parse error: unsupported EDID version.')
return None
# Verify checksum
if sum([ord(char) for char in blob[:CHECKSUM_OFFSET + 1]]) % 0x100 != 0:
logging.warning('EDID parse error: checksum error.')
return None
# Currently we don't support EDID not using pixel clock
pixel_clock = read_short_le(PIXEL_CLOCK_OFFSET)
if not pixel_clock:
logging.warning('EDID parse error: '
'non-pixel clock format is not supported yet.')
return None
# Extract manufactuer
vendor_name = ''
vendor_code = read_short_be(MANUFACTURER_ID_OFFSET)
# vendor_code: [0 | char1 | char2 | char3]
for i in range(2, -1, -1):
vendor_char = (vendor_code >> (i * MANUFACTURER_ID_BITS)) & 0x1F
vendor_char = chr(vendor_char + ord('@'))
vendor_name += vendor_char
product_id = read_short_le(PRODUCT_ID_OFFSET)
width = (ord(blob[HORIZONTAL_OFFSET]) |
((ord(blob[HORIZONTAL_HIGH_OFFSET]) >> 4) << 8))
height = (ord(blob[VERTICAL_OFFSET]) |
((ord(blob[VERTICAL_HIGH_OFFSET]) >> 4) << 8))
return {'vendor': vendor_name, 'product_id': '%04x' % product_id,
'width': str(width), 'height': str(height),
hwid_tool.COMPACT_PROBE_STR: (
'%s:%04x [%dx%d]' % (vendor_name, product_id, width, height))}
def _I2cDump(bus, address, size):
"""Reads binary dump from i2c bus."""
fd = -1
I2C_SLAVE = 0x0703
blob = None
try:
fd = os.open(bus, os.O_RDWR)
if ioctl(fd, I2C_SLAVE, address) != 0:
return blob
sleep(0.05) # Wait i2c to get ready
if os.write(fd, chr(0)) == 1:
blob = os.read(fd, size)
except: # pylint: disable=W0702
pass
finally:
if fd >= 0:
os.close(fd)
return blob
def LoadFromI2c(path):
"""Runs Parse() against the output of _I2cDump on the specified path.
Args:
path: i2c path, can be either int type (ex: 0) or string type (ex:
'/dev/i2c-0')
Returns:
Parsed I2c output, None if it fails to dump something for the specific
I2C.
"""
if isinstance(path, int):
path = '/dev/i2c-%d' % path
command = 'i2cdetect -y -r %s %d %d' % (
path.split('-')[1], I2C_LVDS_ADDRESS, I2C_LVDS_ADDRESS)
# Make sure there is a device in I2C_LVDS_ADDRESS
blob = None
if not '--' in Shell(command).stdout:
blob = _I2cDump(path, I2C_LVDS_ADDRESS, MINIMAL_SIZE)
return Parse(blob) if blob is not None else None
if __name__ == '__main__':
# For debugging, print parse result for specified i2c bus or raw file.
import sys
if len(sys.argv) != 2:
sys.exit('Usage: %s [i2c_bus_number | EDID_file]' % sys.argv[0])
source = sys.argv[1]
if os.path.exists(source):
print repr(Parse(open(source).read()))
else:
print repr(LoadFromI2c(int(source)))