Skip to content

Instantly share code, notes, and snippets.

@leag
Created October 3, 2026 03:13
Show Gist options
  • Select an option

  • Save leag/3b8234b3f217489eb279eb8ce174a307 to your computer and use it in GitHub Desktop.

Select an option

Save leag/3b8234b3f217489eb279eb8ce174a307 to your computer and use it in GitHub Desktop.
Xiaomi Mi Curved 34 XMI3446 OSD repair on Linux — tested fix, adapted from supermar1010 and rm-killer

Xiaomi Mi Curved 34-inch XMI3446: OSD black-screen repair on Linux

Tested successfully on one monitor with EDID manufacturer XMI, product 0x3446, on 2026-10-03. Pressing the menu button caused a brief black screen without opening the OSD. Disconnecting power and video cables did not resolve it. Clearing bit 0x10 at EEPROM address 0x41, verifying the write, and immediately unplugging power restored the menu; the owner confirmed success.

Sources and credit

This is an adaptation of supermar1010's original Linux script and firmware research. That source provides the MStar vendor EEPROM protocol, address mapping, explanation of the stuck DDC power flag, and immediate-unplug procedure for product 0x3444, using flag address 0x43.

rm-killer's Windows repair for revision 0x3445 documents the shifted flag address 0x41. Our 0x3446 settings block exactly matched its published example. This adaptation retains the original Linux transport and uses that revised address. The 0x3446 firmware was not disassembled; the layout was inferred from the matching settings and then confirmed by the successful repair on this unit. This does not establish compatibility with every 0x3446 monitor.

Observed data

The original script stopped because the product ID differed and EEPROM byte 0x00 was 0x00, rather than the expected 0x74.

Observed first 16 EEPROM bytes:

00 10 00 10 00 10 03 80 80 80 80 80 80 46 4b 80

Settings block at 0x40 before and after the verified write:

before: 02 10 00 00 00 00 00 00 00 00 00 00 00 01 00 00
after:  02 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00

All 16 EEPROM read replies in the initial 256-byte snapshot passed checksum checks. Repeated header and settings reads were stable. The entire written 16-byte block matched on readback. Standard ddcutil diagnostics failed on this connection, while vendor EEPROM reads and the repair worked.

Usage

Download mi_monitor_osd_fix_3446.py. Use Linux with Python 3 and a direct GPU connection that passes the vendor DDC commands. The script uses only Python's standard library. Identify your monitor's /dev/i2c-N device; bus numbers vary.

Check first, replacing N with the bus number:

sudo python3 mi_monitor_osd_fix_3446.py N

The script checks the EDID and its checksum, requires the observed header and settings pattern, validates vendor reply checksums, saves a 256-byte EEPROM backup in the current directory, and checks that the settings block matches the backup. It defaults to read-only. The settings comparison allows the target bit to be either set or already clear. These exact data checks intentionally limit the repair to the observed configuration; they are not a firmware signature. If a check fails, do not bypass it.

Have the monitor's physical power cable within reach before the next command. Applying the fix writes the 16-byte block at 0x40, changing only bit 0x10 of byte 0x41:

sudo python3 -u mi_monitor_osd_fix_3446.py N --write

As soon as OK, written and verified appears:

  1. Immediately unplug the monitor's power cable, not merely its power button.
  2. Wait 10 seconds, then reconnect power.
  3. Test the OSD menu.

The original research reports that the monitor periodically saves its old RAM settings back to EEPROM, undoing the repair if power remains connected. Keep your backups. If verification fails, preserve the backup and investigate; this gist does not provide an automatic restore operation.

Prevent recurrence and limitations

The original research identifies DDC/CI power mode VCP 0xD6 as the trigger. Disable display-control software features that send it, including “turn display off.”

This writes monitor settings EEPROM and is provided without a guarantee. An incorrect layout can damage monitor configuration. Only one 0x3446 unit was repaired here. The published script was cleaned up after that repair: documentation and attribution were corrected, EDID checksum validation was added, and the settings guard now also accepts an already-cleared flag. Those changes were checked without performing another hardware write. Personal EEPROM backup files are not included in this gist.

#!/usr/bin/env python3
# Adapted for the observed XMI3446 layout matching the 3445 repair example.
# Source: https://github.com/rm-killer/xiaomi-curved-34-osd-fix-windows
# Layout was inferred from matching settings; repair confirmed on one XMI3446 unit.
# Original protocol and Linux script: https://gist.github.com/supermar1010/3e81e3f23ce16c826e383edbf9442351
"""Fix the Xiaomi Mi Curved 34" (EDID XMI3446) "OSD goes black" problem over DDC/CI (Linux).
Clears the stuck "DDC power-down pending" bit (0x10) in settings EEPROM byte 0x41.
Without --write it only reads and reports. With --write it changes exactly one bit
and verifies the result. Run it, then IMMEDIATELY pull the monitor's power cable.
Usage:
sudo python3 mi_monitor_osd_fix_3446.py <i2c-bus> # check only (read-only)
sudo python3 mi_monitor_osd_fix_3446.py <i2c-bus> --write # apply the fix
Find the bus with `ddcutil detect` (the "/dev/i2c-N" line of the Mi Monitor).
The script reads the EDID first and aborts with a warning if the bus is not
the Mi Monitor (vendor XMI, product code 0x3446).
Use at your own risk.
"""
import fcntl, functools, os, sys, time
I2C_SLAVE = 0x0703
DDC_ADDR = 0x37
BASE = 0xAA4 # firmware adds this to the requested EEPROM address
BLOCK = 0x40 # 16-byte block containing byte 0x41
FLAGS = 0x41
BIT = 0x10
EDID_ADDR = 0x50
EDID_HEADER = b"\x00\xff\xff\xff\xff\xff\xff\x00"
EDID_VENDOR = "XMI" # EDID manufacturer ID of the Mi Monitor
EDID_PRODUCT = 0x3446 # EDID product code of the Mi Curved 34" (EDID XMI3446)
def read_edid(bus):
"""Read the 128-byte base EDID block from the monitor on this bus."""
fd = os.open(f"/dev/i2c-{bus}", os.O_RDWR)
try:
fcntl.ioctl(fd, I2C_SLAVE, EDID_ADDR)
os.write(fd, b"\x00")
return os.read(fd, 128)
finally:
os.close(fd)
def check_monitor(bus):
"""Abort with a warning unless the bus belongs to the expected Mi Monitor."""
try:
edid = read_edid(bus)
except OSError as e:
sys.exit(f"WARNING: cannot read the EDID on /dev/i2c-{bus} ({e}). "
"Cannot confirm this is the Mi Monitor, aborting. Nothing was changed.")
if len(edid) != 128 or edid[:8] != EDID_HEADER or sum(edid) % 256:
sys.exit(f"WARNING: no valid EDID on /dev/i2c-{bus}. "
"Cannot confirm this is the Mi Monitor, aborting. Nothing was changed.")
mfg = edid[8] << 8 | edid[9]
vendor = "".join(chr(((mfg >> s) & 0x1F) + 64) for s in (10, 5, 0))
product = edid[10] | edid[11] << 8
if (vendor, product) != (EDID_VENDOR, EDID_PRODUCT):
sys.exit(f"WARNING: /dev/i2c-{bus} is monitor {vendor} {product:#06x}, not the "
f"Mi Monitor ({EDID_VENDOR} {EDID_PRODUCT:#06x}). This fix is only for "
"that monitor, aborting. Nothing was changed.")
print(f"monitor on /dev/i2c-{bus}: {vendor} {product:#06x} (Mi Monitor), OK")
def hi_lo(addr):
want = (addr - BASE) & 0xFFFF
return want >> 8, want & 0xFF
def xfer(fd, payload, rlen=40):
pkt = bytes([0x51, 0x80 | len(payload)]) + bytes(payload)
chk = 0x6E
for b in pkt:
chk ^= b
os.write(fd, pkt + bytes([chk]))
time.sleep(0.08)
return os.read(fd, rlen)
def read16(fd, addr):
"""Vendor command 7B 99 22: read 16 EEPROM bytes."""
for _ in range(3):
r = xfer(fd, [0x03, 0x7B, 0x99, 0x22, *hi_lo(addr)])
if len(r) >= 19 and r[0] == 0x6E and r[1] == 0x92 and functools.reduce(lambda a, b: a ^ b, r[:19], 0x50) == 0:
return bytes(r[2:18])
time.sleep(0.2)
sys.exit(f"no valid reply reading EEPROM {addr:#x} (is this the Mi Monitor's bus?)")
def write16(fd, addr, data):
"""Vendor command 7B 99 55: write 16 EEPROM bytes."""
return xfer(fd, [0x03, 0x7B, 0x99, 0x55, *hi_lo(addr), *data])
def main():
if len(sys.argv) < 2:
sys.exit(__doc__)
bus, do_write = sys.argv[1], "--write" in sys.argv[2:]
check_monitor(bus)
fd = os.open(f"/dev/i2c-{bus}", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, DDC_ADDR)
head = read16(fd, 0x00)
if head != bytes.fromhex("00 10 00 10 00 10 03 80 80 80 80 80 80 46 4b 80"):
sys.exit(f"EEPROM header is {head.hex()}, unexpected header for the observed 3446 layout: not the expected "
"monitor/firmware, aborting")
backup = b"".join(read16(fd, a) for a in range(0x00, 0x100, 16))
name = f"mi_monitor_eeprom_0000-00ff_{time.strftime('%Y%m%d-%H%M%S')}.bin"
with open(name, "xb") as backup_file:
backup_file.write(backup)
print(f"backup of EEPROM 0x00-0xFF saved to {name}")
cur = read16(fd, BLOCK)
if cur != backup[BLOCK:BLOCK + 16]:
sys.exit("block read is unstable, aborting")
expected = bytearray.fromhex("02 10 00 00 00 00 00 00 00 00 00 00 00 01 00 00")
observed = bytearray(cur)
expected[FLAGS - BLOCK] &= ~BIT
observed[FLAGS - BLOCK] &= ~BIT
if observed != expected:
sys.exit("Settings differ from the documented revision repair example; aborting.")
val = cur[FLAGS - BLOCK]
print(f"byte 0x41 = {val:#04x}")
if not val & BIT:
print("bit 0x10 is NOT set: this fix does not apply to your monitor.")
return
print("bit 0x10 is set: this is the stuck 'DDC power-down pending' flag.")
if not do_write:
print("Read-only run. Re-run with --write to clear it.")
return
new = bytearray(cur)
new[FLAGS - BLOCK] = val & ~BIT
print("old block:", cur.hex(" "))
print("new block:", new.hex(" "))
write16(fd, BLOCK, new)
time.sleep(0.3)
back = read16(fd, BLOCK)
os.close(fd)
if back != bytes(new):
sys.exit(f"readback mismatch: {back.hex(' ')}\nKeep backup {name} for investigation; do not blindly restore the EEPROM.")
print("\nOK, written and verified.")
print(">>> PULL THE MONITOR'S POWER CABLE NOW (not the power button), wait 10 s, "
"plug it back in. <<<")
print("The monitor periodically saves its RAM copy (still with the bit set) back "
"to the EEPROM, which would undo the fix if you wait.")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment