/ util / dtd_parser / dtd_parser.py
dtd_parser.py
  1  #!/usr/bin/env python2
  2  # dtd_parser.py - DTD structure parser
  3  #
  4  # SPDX-License-Identifier: GPL-2.0-only
  5  
  6  '''
  7  DTD string parser/generator.
  8  
  9  Detailed timing descriptor (DTD) is an 18 byte array describing video mode
 10  (screen resolution, display properties, etc.) in EDID and used by Intel Option
 11  ROM. Option ROM can support multiple video modes, specific mode is picked by
 12  the BIOS through the appropriate Option ROM callback function.
 13  
 14  This program allows to interpret the 18 byte hex DTD dump, and/or modify
 15  certain values and generate a new DTD.
 16  '''
 17  
 18  import 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  
 38  dtd_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  
 59  PREFIX = 'attr_'
 60  
 61  class 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  
172  def 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  
191  if __name__ == '__main__':
192      try:
193          main(sys.argv[1:])
194      except (ValueError, IndexError):
195          print """
196  A 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)