Forked from twitchyliquid64/mcp2221a_set_strings.py
Created
February 12, 2023 02:48
Example code for reading settings from the MCP2221 or MCP2221A chip, and writing product/vendor strings. Trivial to set/read other settings as well.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Make sure you install pyusb and libusb on your system yo | |
import usb.core | |
import usb.util | |
import usb.control | |
HID_INTERFACE = 0x02 | |
INPUT_ENDPOINT = 0x83 | |
OUTPUT_ENDPOINT = 0x3 | |
HID_PKT_SIZE = 64 | |
STATUS_COMMAND = '\x10' + ('\x00' * 63) | |
class FlashError(Exception): | |
pass | |
class ByteDecoder(object): | |
def __init__(self, b, multiplier): | |
self.b = b | |
self.multiplier = multiplier | |
def value(self, data): | |
return data[self.b] * self.multiplier | |
class HexDecoder(object): | |
def __init__(self, start, end): | |
self.start = start | |
self.end = end | |
def value(self, data): | |
section = data[self.start:self.end] | |
section.reverse() | |
return ''.join('{:02x}'.format(x) for x in section) | |
class BitDecoder(object): | |
def __init__(self, byte, bit): | |
self.byte = byte | |
self.bit = bit | |
def value(self, data): | |
return bool(data[self.byte] & (1 << self.bit)) | |
class EnumDecoder(object): | |
def __init__(self, byte, mask, opts): | |
self.byte = byte | |
self.mask = mask | |
self.opts = opts | |
def value(self, data): | |
return self.opts[data[self.byte] & self.mask] | |
READ_FLASH_CHIP_SETTINGS = '\x00' | |
CHIP_SETTINGS_MAP = { | |
'Provide serial number on enumeration': BitDecoder(4, 7), | |
'USB vendorID': HexDecoder(8, 10), | |
'USB productID': HexDecoder(10, 12), | |
'Requested mA': ByteDecoder(13, 2), | |
'Chip security': EnumDecoder(4, 0b11, {0: 'Unsecured', 1: 'Password-protected', 2: 'Permanently-locked', 3: 'Permanently-locked'}) | |
} | |
READ_SERIAL_NUMBER = '\x04' | |
def getManufacturer(device): | |
try: | |
return usb.util.get_string(device, device.iManufacturer) | |
except ValueError: | |
return '' | |
def getProduct(device): | |
try: | |
return usb.util.get_string(device, device.iProduct) | |
except ValueError: | |
return '' | |
def writeFlash(device, data): | |
device.write(OUTPUT_ENDPOINT, '\xB1' + data) | |
info = device.read(INPUT_ENDPOINT, HID_PKT_SIZE) | |
assert info[0] == 0xB1 | |
if info[1] == 0x02: | |
raise FlashError('Command not supported') | |
if info[1] == 0x03: | |
raise FlashError('Command not allowed') | |
def writeProduct(device, name): | |
if len(name) > 29: | |
raise ValueError('Too long') | |
ps = ''.join([c+'\x00' for c in name]) | |
b = '\x03' + chr((2*len(name))+2) + '\x03' + ps | |
return writeFlash(device, b) | |
def writeManufacturer(device, name): | |
if len(name) > 29: | |
raise ValueError('Too long') | |
ps = ''.join([c+'\x00' for c in name]) | |
b = '\x02' + chr((2*len(name))+2) + '\x03' + ps | |
return writeFlash(device, b) | |
def readFlash(device, section): | |
device.write(OUTPUT_ENDPOINT, '\xB0' + section + ('\x00' * 62)) | |
info = device.read(INPUT_ENDPOINT, HID_PKT_SIZE) | |
assert info[0] == 0xB0 | |
if info[1] == 0x01: | |
raise FlashError('Command not supported') | |
return info | |
def readChipSettings(device): | |
chip_settings = readFlash(device, READ_FLASH_CHIP_SETTINGS) | |
output = dict() | |
for attr in CHIP_SETTINGS_MAP: | |
output[attr] = CHIP_SETTINGS_MAP[attr].value(chip_settings) | |
return output | |
def readSerialNumber(device): | |
sn = readFlash(device, READ_SERIAL_NUMBER) | |
assert sn[3] == 0x3 | |
length = sn[2] | |
l = [chr(x) if x > 0 else '' for x in sn[4:4+length]] | |
return ''.join(l) | |
def getStatus(device): | |
device.write(OUTPUT_ENDPOINT, STATUS_COMMAND) | |
info = device.read(INPUT_ENDPOINT, HID_PKT_SIZE) | |
assert info[0] == 0x10 | |
output = { | |
'HW revision': chr(info[46]) + chr(info[47]), | |
'Firmware revision': chr(info[48]) + chr(info[49]), | |
'SN': readSerialNumber(device) | |
} | |
chip_settings = readChipSettings(device) | |
output.update(chip_settings) | |
return output | |
def lsUSB(): | |
dev = usb.core.find(find_all=True) | |
for device in dev: | |
print '\033[92m ~=== ' + str(getProduct(device)) + ' ===~\033[0m' | |
print ' \033[95m' + str(getManufacturer(device)) + '\033[0m' | |
print ' productID=' + hex(device.idProduct) + ' vendorID=' + hex(device.idVendor) | |
print device.get_active_configuration() | |
if __name__ == '__main__': | |
device = usb.core.find(idVendor=0x4d8, idProduct=0xdd) | |
if device is None: | |
raise ValueError('No MCP2221A device found') | |
#device.reset() - try this line if shiz isnt working | |
#tell the kernel to stop tracking it | |
try: | |
if device.is_kernel_driver_active(HID_INTERFACE) is True: | |
device.detach_kernel_driver(HID_INTERFACE) | |
except usb.core.USBError as e: | |
raise ValueError("Kernel driver won't give up control over device: %s" % str(e)) | |
#device.set_configuration() - try this line if shiz isnt working | |
#start using the device with pre-determined endpoint numbers | |
status = getStatus(device) | |
print getProduct(device) + ' found' | |
for attr in status: | |
print '\t %s => %s' % (attr, status[attr]) | |
if raw_input("Everything looks good. Write manufacturer/product strings? (y/N): ").lower() == 'y': | |
writeProduct(device, '<PRODUCT NAME HERE>') | |
writeManufacturer(device, '<MANUFACTURER NAME HERE>') | |
else: | |
print("Abort.") | |
print("USB device information:") | |
lsUSB() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment