1#!/usr/bin/env python2 2# dtd_parser.py - DTD structure parser 3# 4# SPDX-License-Identifier: GPL-2.0-only 5 6''' 7DTD string parser/generator. 8 9Detailed timing descriptor (DTD) is an 18 byte array describing video mode 10(screen resolution, display properties, etc.) in EDID and used by Intel Option 11ROM. Option ROM can support multiple video modes, specific mode is picked by 12the BIOS through the appropriate Option ROM callback function. 13 14This program allows to interpret the 18 byte hex DTD dump, and/or modify 15certain values and generate a new DTD. 16''' 17 18import sys 19 20# 21# The DTD array format description can be found in 22# http://en.wikipedia.org/wiki/Extended_display_identification_data, (see the 23# EDID Detailed Timing Descriptor section). 24# 25# The below dictionary describes how different DTD parameters are laid out in 26# the array. Note that many parameters span multiple bit fields in the DTD. 27# 28# The keys in the dictionary are stings (field names), the values are tuples 29# of either numbers or tri-tuples. If the element of the tuple is a number, it 30# is the offset in DTD, and the entire byte is used in this field. If the 31# element is a tri-tuple, its components are (DTD offset, bit shift, field 32# width). 33# 34# The partial values are extracted from the DTD fields and concatenated 35# together to form actual parameter value. 36# 37 38dtd_descriptor = { 39 'dclck' : (1, 0), 40 'hor_active' : ((4, 4, 4), 2), 41 'hor_blank' : ((4, 0, 4), 3), 42 'vert_act' : ((7, 4, 4), 5), 43 'vert_blank' : ((7, 0, 4), 6), 44 'hsync_offset' : ((11, 6, 2), 8), 45 'hsync_pulse_width' : ((11, 4, 2), 9), 46 'vsync_offset' : ((11, 2, 2), (10, 4, 4)), 47 'vsync_pulse_width' : ((11, 0, 2), (10, 0, 4)), 48 'hor_image_size' : ((14, 4, 4), 12), 49 'vert_image_size' : ((14, 0, 4), 13), 50 'hor_border' : (15,), 51 'vert_border' : (16,), 52 'interlaced' : ((17, 7, 1),), 53 'reserved' : ((17, 5, 2), (17, 0, 1)), 54 'digital_separate' : ((17, 3, 2),), 55 'vert_polarity' : ((17, 2, 1),), 56 'hor_polarity' : ((17, 1, 1),), 57 } 58 59PREFIX = 'attr_' 60 61class DTD(object): 62 '''An object containing all DTD information. 63 64 The attributes are created dynamically when the input DTD string is 65 parsed. For each element of the above dictionary two attributes are added: 66 67 'attr_<param>' to hold the actual parameter value 68 'max_attr_<param>' to hold the maximum allowed value for this parameter. 69 ''' 70 71 def __init__(self): 72 for name in dtd_descriptor: 73 setattr(self, PREFIX + name, 0) 74 75 def init(self, sarray): 76 '''Initialize the object with values from a DTD array. 77 78 Inputs: 79 80 sarray: a string, an array of ASCII hex representations of the 18 DTD 81 bytes. 82 83 Raises: implicitly raises ValueError or IndexError exceptions in case 84 the input string has less than 18 elements, or some of the 85 elements can not be converted to integer. 86 ''' 87 88 harray = [int(x, 16) for x in sarray] 89 for name, desc in dtd_descriptor.iteritems(): 90 attr_value = 0 91 total_width = 0 92 for tup in desc: 93 if isinstance(tup, tuple): 94 offset, shift, width = tup 95 else: 96 offset, shift, width = tup, 0, 8 97 98 mask = (1 << width) - 1 99 attr_value = (attr_value << width) + ( 100 (harray[offset] >> shift) & mask) 101 total_width += width 102 setattr(self, PREFIX + name, attr_value) 103 setattr(self, 'max_' + PREFIX + name, (1 << total_width) - 1) 104 105 def __str__(self): 106 text = [] 107 for name in sorted(dtd_descriptor.keys()): 108 text.append('%20s: %d' % (name, getattr(self, PREFIX + name))) 109 return '\n'.join(text) 110 111 def inhex(self): 112 '''Generate contents of the DTD as a 18 byte ASCII hex array.''' 113 114 result = [0] * 18 115 for name, desc in dtd_descriptor.iteritems(): 116 attr_value = getattr(self, PREFIX + name) 117 rdesc = list(desc) 118 rdesc.reverse() 119 for tup in rdesc: 120 if isinstance(tup, tuple): 121 offset, shift, width = tup 122 else: 123 offset, shift, width = tup, 0, 8 124 125 mask = (1 << width) - 1 126 value = attr_value & mask 127 attr_value = attr_value >> width 128 result[offset] = (result[offset] & ~( 129 mask << shift)) | (value << shift) 130 131 return ' '.join('%2.2x' % x for x in result) 132 133 def handle_input(self, name): 134 '''Get user input and set a new parameter value if required. 135 136 Display the parameter name, its current value, and prompt user for a 137 new value. 138 139 If the user enters a dot, stop processing (return True). 140 141 Empty user input means that this parameter does not have to change, 142 but the next parameter should be prompted. 143 144 If input is non-empty, it is interpreted as a hex number, checked if 145 it fits the parameter and the new parameter value is set if checks 146 pass. 147 148 Inputs: 149 150 name - a string, parameter name, a key in dtd_descriptor 151 152 Returns: 153 154 Boolean, True meaning no more field are required to be modified, False 155 meaning that more field mods need to be prompted.. 156 ''' 157 158 param = PREFIX + name 159 vmax = getattr(self, 'max_' + param) 160 new_value = raw_input('%s : %d ' % (name, getattr(self, param))) 161 if new_value == '': 162 return False 163 if new_value == '.': 164 return True 165 new_int = int(new_value) 166 if new_int > vmax: 167 print '%s exceeds maximum for %s (%d)' % (new_value, name, vmax) 168 else: 169 setattr(self, param, new_int) 170 return False 171 172def main(args): 173 if args[0] == '-m': 174 modify = True 175 base = 1 176 else: 177 modify = False 178 base = 0 179 180 d = DTD() 181 d.init(args[base:]) 182 if modify: 183 for line in str(d).splitlines(): 184 if d.handle_input(line.split(':')[0].strip()): 185 break 186 print d 187 if modify: 188 print d.inhex() 189 190 191if __name__ == '__main__': 192 try: 193 main(sys.argv[1:]) 194 except (ValueError, IndexError): 195 print """ 196A string of 18 byte values in hex is required. 197'-m' preceding the string will allow setting new parameter values. 198""" 199 sys.exit(1) 200