forked from bulletmark/edid-rw
-
Notifications
You must be signed in to change notification settings - Fork 0
/
edid-rw
executable file
·200 lines (165 loc) · 7.27 KB
/
edid-rw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#!/usr/bin/env python3
'''
Program to read or write Extended Display Identification Data (EDID) to
a digital display. Default action is to READ binary EDID data from
specified i2c_device and write it to standard output. With "-w", will
instead read EDID binary data from standard input and WRITE it to
specified i2c_device. Always validates the EDID checksum before allowing
a write to device.
'''
# (C) Mark Blakeney, April 2012.
import os
import sys
import array
import argparse
import binascii
import datetime
import subprocess
import time
from smbus import SMBus
EDID_HDR = 20
EDID_LEN = (128, 256)
EDID_ADDR = 0x50
class DEV:
def read_entire_edid(self):
'Read entire edid from given SMB device'
# Read EDID from device
edid = [self.read(i) for i in range(EDID_HDR)]
edid_len = getlen(edid)
edid += [self.read(i) for i in range(EDID_HDR, edid_len)]
# Read extensions from device (if any)
edid += [self.read(i) for i in
range(edid_len, edid_len + edid_len * edid[-2])]
return edid
def write_entire_edid(self, edid, sleeps_between_writes):
'Writes entire edid to given SMB device'
# Write EDID to device
for i, val in enumerate(edid):
self.write(i, val)
time.sleep(sleeps_between_writes)
class STDDEV(DEV):
'Standard input/output device for testing'
def __init__(self):
'Create standard input/output device'
self.in_array = array.array('B', os.read(sys.stdin.fileno(), max(EDID_LEN))).tolist()
def read(self, n):
'Read a byte from stdin'
return self.in_array[n]
def write(self, n, val):
'Write a byte to stdout'
os.write(sys.stdout.fileno(), array.array('B', (val,)).tobytes())
class SMBDEV(DEV):
'SMB device'
def __init__(self, i2c_bus_index):
'Create SMB device'
# Access i2c device
self.smb = SMBus(i2c_bus_index)
def read(self, n):
'Read a byte from given SMB device'
return self.smb.read_byte_data(EDID_ADDR, n)
def write(self, n, val):
'Write a byte to given SMB device'
self.smb.write_byte_data(EDID_ADDR, n, val)
def getlen(edidhdr):
'Return the number of bytes expected for this edid hdr'
if edidhdr[18] <= 0 or edidhdr[18] > len(EDID_LEN):
sys.exit('ERROR: Unknown EDID version %d' % edidhdr[18])
return EDID_LEN[edidhdr[18] - 1]
def blkname(blk):
'Return a name for given block number'
return 'EDID' if blk == 0 else ('Ext block %d' % blk)
def checksum(edid):
'Return EDID checksum of the given buffer'
return (0 - (sum(edid[:-1]) % 256)) % 256
def main():
'Main code'
# Need ic2 device module installed
if subprocess.call(('modprobe', 'i2c-dev')) != 0:
sys.exit('ERROR: Can not load i2c-dev. Must use sudo or be root.')
if len(sys.argv) == 1:
print('Listing available I2C buses via `i2cdetect -l`:')
subprocess.call('i2cdetect -l', shell=True)
print('You have to carefully select correct bus')
print('and pass the number X from the i2c-X bus name as command line "i2c_bus_index" argument')
print('')
# Process command line options
opt = argparse.ArgumentParser(description=__doc__.strip(),
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
opt.add_argument('-w', '--write', action='store_true',
help='Write EDID to i2c_device from data read from stdin')
opt.add_argument('-t', '--std', action='store_true',
help='Read stdin or write stdout only, for testing')
opt.add_argument('-f', '--fix', action='store_true',
help='Fix last checksum byte automatically when writing the '
'EDID and extensions')
opt.add_argument('-s', '--sleep', default=0.01, type=float,
help='Sleep this many seconds between writes')
opt.add_argument('i2c_bus_index', type=int,
help='i2c bus index, the number X from the i2c-X bus name')
args = opt.parse_args()
# Assign smb device, or stdin/stdout for testing
dev = STDDEV() if args.std else SMBDEV(args.i2c_bus_index)
if args.write:
# Read EDID from stdin
edid = array.array('B', os.read(sys.stdin.fileno(),
max(EDID_LEN))).tolist()
edid_len = getlen(edid)
edid_n = edid[edid_len - 2]
nbytes = edid_len + (edid_len * edid_n)
if len(edid) != nbytes:
sys.exit('ERROR: Input must be %d bytes' % nbytes)
# Check each EDID block checksum is valid
for b in range(edid_n + 1):
x = (b + 1) * edid_len
actsum = edid[x - 1]
calsum = checksum(edid[x - edid_len:x])
if actsum != calsum:
if args.fix:
edid[x - 1] = calsum
sys.stderr.write(
'%s checksum 0x%02x was BAD, rewrote it to '
'0x%02x.\n' % (blkname(b), actsum, calsum))
else:
sys.exit('ERROR: %s checksum 0x%02x is BAD, should be '
'0x%02x.' % (blkname(b), actsum, calsum))
print("Provided file is valid EDID, checking original EDID in a EEPROM...")
orig_edid = dev.read_entire_edid()
if edid == orig_edid:
print('OK: Device already contains identical EDID, nothing to do.')
return
verified_edid = None
print("Writing new EDID to EEPROM...")
try:
dev.write_entire_edid(edid, args.sleep)
finally:
# wait before verification to ensure that EEPROM completes all its inner workings
time.sleep(2)
verified_edid = dev.read_entire_edid()
if verified_edid == edid:
print('OK: New EDID written and verified')
elif verified_edid == orig_edid:
sys.exit('ERROR: Failed writing new EDID, maybe device is write-protected.\n'
'Nothing changed, existing device EDID kept unmodified')
else:
orig_edid_bytes = bytes(orig_edid)
sys.stderr.write('CRITICAL: EDID writing failed, EEPROM contents damaged!\n')
sys.stderr.write('Here is a hexdump of original edid, you may want to save it for recovering:\n')
sys.stderr.write(binascii.hexlify(orig_edid_bytes, '\n', 16).decode())
sys.stderr.write('\nTrying to recover original EDID...\n')
dev.write_entire_edid(orig_edid, max(args.sleep, 0.05))
time.sleep(2)
if dev.read_entire_edid() == orig_edid:
sys.exit('Original EDID recovered')
else:
sys.stderr.write('CRITICAL: Recovering original EDID failed too, EEPROM contents damaged!\n')
backup_fname = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H-%M-%S-orig-EDID.bin')
with open(backup_fname, 'wb') as original_backup:
original_backup.write(orig_edid_bytes)
sys.stderr.write('backup of original EDID saved to:\n')
sys.exit(backup_fname)
else:
edid = dev.read_entire_edid()
# Write EDID to stdout
os.write(sys.stdout.fileno(), array.array('B', edid).tobytes())
if __name__ == '__main__':
main()