// // ch341eeprom programmer version 0.1 (Beta) // // Programming tool for the 24Cxx serial EEPROMs using the Winchiphead CH341A IC // // (c) December 2011 asbokid // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include "libch341eeprom_internal.h" #include "libch341eeprom.h" #include #include #include #include #include #include #include uint32_t getnextpkt; // set by the callback function uint32_t syncackpkt; // synch / ack flag used by BULK OUT cb function uint16_t byteoffset; uint8_t *readbuf; int debug_log_enabled = FALSE; void log_debug(const char* fmt, ...) { if (debug_log_enabled == TRUE) { va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); } } // -------------------------------------------------------------------------- // parseEEPsize() // passed an EEPROM name (case-sensitive), returns its byte size int32_t parseEEPsize(const char *eepromname) { for (int i = 0; eepromlist[i].size; i++) { if (strstr(eepromlist[i].name, eepromname)) { return (eepromlist[i].size); } } return -1; } // -------------------------------------------------------------------------- // ch341configure() // lock USB device for exclusive use // claim default interface // set default configuration // retrieve device descriptor // identify device revision // returns *usb device handle struct libusb_device_handle *ch341configure(uint16_t vid, uint16_t pid) { struct libusb_device *dev; struct libusb_device_handle *devHandle; int32_t ret = 0, ret2 = 0; // set to < 0 to indicate USB errors uint32_t i = 0, j = 0; int32_t currentConfig = 0; uint8_t ch341DescriptorBuffer[0x12]; uint8_t ch341InBuffer[IN_BUF_SZ]; // 0x100 bytes in size uint8_t ch341OutBuffer[EEPROM_READ_BULKOUT_BUF_SZ]; ret = libusb_init(NULL); if (ret < 0) { fprintf(stderr, "Couldnt initialise libusb\n"); return NULL; } libusb_set_debug(NULL, 3); // maximum debug logging level log_debug( "Searching USB buses for WCH CH341a i2c EEPROM programmer [%04x:%04x]\n", USB_LOCK_VENDOR, USB_LOCK_PRODUCT); if (!(devHandle = libusb_open_device_with_vid_pid(NULL, USB_LOCK_VENDOR, USB_LOCK_PRODUCT))) { fprintf(stderr, "Couldn't open device [%04x:%04x]\n", USB_LOCK_VENDOR, USB_LOCK_PRODUCT); return NULL; } if (!(dev = libusb_get_device(devHandle))) { fprintf(stderr, "Couldnt get bus number and address of device\n"); return NULL; } printf( "Found [%04x:%04x] as device [%d] on USB bus [%d]\n", USB_LOCK_VENDOR, USB_LOCK_PRODUCT, libusb_get_device_address(dev), libusb_get_bus_number(dev)); log_debug("Opened device [%04x:%04x]\n", USB_LOCK_VENDOR, USB_LOCK_PRODUCT); if (libusb_kernel_driver_active(devHandle, DEFAULT_INTERFACE)) { ret = libusb_detach_kernel_driver(devHandle, DEFAULT_INTERFACE); if (ret) { fprintf(stderr, "Failed to detach kernel driver: '%s'\n", strerror(-ret)); return NULL; } else log_debug("Detached kernel driver\n"); } ret = libusb_get_configuration(devHandle, ¤tConfig); if (ret) { fprintf(stderr, "Failed to get current device configuration: '%s'\n", strerror(-ret)); return NULL; } if (currentConfig != DEFAULT_CONFIGURATION) ret = libusb_set_configuration(devHandle, currentConfig); if (ret) { fprintf( stderr, "Failed to set device configuration to %d: '%s'\n", DEFAULT_CONFIGURATION, strerror(-ret)); return NULL; } ret = libusb_claim_interface(devHandle, DEFAULT_INTERFACE); // interface 0 if (ret) { fprintf(stderr, "Failed to claim interface %d: '%s'\n", DEFAULT_INTERFACE, strerror(-ret)); return NULL; } log_debug("Claimed device interface [%d]\n", DEFAULT_INTERFACE); ret = libusb_get_descriptor(devHandle, LIBUSB_DT_DEVICE, 0x00, ch341DescriptorBuffer, 0x12); if (ret < 0) { fprintf(stderr, "Failed to get device descriptor: '%s'\n", strerror(-ret)); return NULL; } printf( "Device reported its revision [%d.%02d]\n", ch341DescriptorBuffer[12], ch341DescriptorBuffer[13]); for (i = 0; i < 0x12; i++) { log_debug("%02x ", ch341DescriptorBuffer[i]); } log_debug("\n"); return devHandle; } // -------------------------------------------------------------------------- // ch341setstream() // set the i2c bus speed (speed: 0 = 20kHz; 1 = 100kHz, 2 = 400kHz, 3 = 750kHz) int32_t ch341setstream(struct libusb_device_handle *devHandle, uint32_t speed) { int32_t ret, i; uint8_t ch341outBuffer[EEPROM_READ_BULKOUT_BUF_SZ], *outptr; int32_t actuallen = 0; outptr = ch341outBuffer; *outptr++ = mCH341A_CMD_I2C_STREAM; *outptr++ = mCH341A_CMD_I2C_STM_SET | (speed & 0x3); *outptr = mCH341A_CMD_I2C_STM_END; ret = libusb_bulk_transfer( devHandle, BULK_WRITE_ENDPOINT, ch341outBuffer, 3, &actuallen, DEFAULT_TIMEOUT); if (ret < 0) { fprintf(stderr, "ch341setstream(): Failed write %d bytes '%s'\n", 3, strerror(-ret)); return -1; } log_debug("ch341setstream(): Wrote %d bytes: ", 3); for (i = 0; i < 3; i++) log_debug("%02x ", ch341outBuffer[i]); log_debug("\n"); return 0; } // -------------------------------------------------------------------------- // ch341readEEPROM() // read n bytes from device (in packets of 32 bytes) int32_t ch341readEEPROM(struct libusb_device_handle *devHandle, uint8_t *buffer, uint32_t bytestoread) { uint8_t ch341outBuffer[EEPROM_READ_BULKOUT_BUF_SZ]; uint8_t ch341inBuffer[IN_BUF_SZ]; // 0x100 bytes int32_t ret = 0, i, exitflag = 0, readpktcount; uint32_t actuallen = 0; struct libusb_transfer *xferBulkIn, *xferBulkOut; struct timeval tv = { 0, 100 }; // our async polling interval xferBulkIn = libusb_alloc_transfer(0); xferBulkOut = libusb_alloc_transfer(0); if (!xferBulkIn || !xferBulkOut) { fprintf(stderr, "Couldnt allocate USB transfer structures\n"); return -1; } log_debug("Allocated USB transfer structures\n"); memset(ch341inBuffer, 0, EEPROM_READ_BULKIN_BUF_SZ); memcpy(ch341outBuffer, CH341_EEPROM_READ_SETUP_CMD, EEPROM_READ_BULKOUT_BUF_SZ); libusb_fill_bulk_transfer( xferBulkIn, devHandle, BULK_READ_ENDPOINT, ch341inBuffer, EEPROM_READ_BULKIN_BUF_SZ, cbBulkIn, NULL, DEFAULT_TIMEOUT); libusb_fill_bulk_transfer( xferBulkOut, devHandle, BULK_WRITE_ENDPOINT, ch341outBuffer, EEPROM_READ_BULKOUT_BUF_SZ, cbBulkOut, NULL, DEFAULT_TIMEOUT); log_debug("Filled USB transfer structures\n"); libusb_submit_transfer(xferBulkIn); log_debug("Submitted BULK IN start packet\n"); libusb_submit_transfer(xferBulkOut); log_debug("Submitted BULK OUT setup packet\n"); readbuf = buffer; byteoffset = 0; while (byteoffset < bytestoread) { log_debug( "Read [%d] of [%d] bytes \r", byteoffset, bytestoread); ret = libusb_handle_events_timeout(NULL, &tv); if (ret < 0 || getnextpkt == -1) { // indicates an error log_debug( "ret from libusb_handle_timeout = %d\n", ret); log_debug( "getnextpkt = %d\n", getnextpkt); fprintf(stderr, "USB read error : %s\n", strerror(-ret)); goto out_deinit; } if (getnextpkt == 1) { // callback function reports a new BULK IN packet received getnextpkt = 0; // reset the flag readpktcount++; // increment the read packet counter byteoffset += EEPROM_READ_BULKIN_BUF_SZ; log_debug("\nRe-submitting transfer request to BULK IN endpoint\n"); libusb_submit_transfer(xferBulkIn); // re-submit request for next BULK IN packet of EEPROM data if (syncackpkt) syncackpkt = 0; // if 4th packet received, we are at end of 0x80 byte data block, // if it is not the last block, then resubmit request for data if (readpktcount == 4 && byteoffset < bytestoread) { log_debug("\nSubmitting next transfer request to BULK OUT endpoint\n"); readpktcount = 0; memcpy(ch341outBuffer, CH341_EEPROM_READ_NEXT_CMD, CH341_EEPROM_READ_CMD_SZ); ch341outBuffer[4] = (uint8_t)(byteoffset >> 8 & 0xff); // MSB (big-endian) byte address ch341outBuffer[5] = (uint8_t)(byteoffset & 0xff); // LSB of 16-bit byte address libusb_fill_bulk_transfer( xferBulkOut, devHandle, BULK_WRITE_ENDPOINT, ch341outBuffer, EEPROM_READ_BULKOUT_BUF_SZ, cbBulkOut, NULL, DEFAULT_TIMEOUT); libusb_submit_transfer( xferBulkOut); // update transfer struct (with new EEPROM page offset) // and re-submit next transfer request to BULK OUT endpoint } } } out_deinit: libusb_free_transfer(xferBulkIn); libusb_free_transfer(xferBulkOut); return 0; } // Callback function for async bulk in comms void cbBulkIn(struct libusb_transfer *transfer) { int i; switch (transfer->status) { case LIBUSB_TRANSFER_COMPLETED: // display the contents of the BULK IN data buffer log_debug( "\ncbBulkIn(): status %d - Read %d bytes\n", transfer->status, transfer->actual_length); for (i = 0; i < transfer->actual_length; i++) { if (!(i % 16)) log_debug("\n "); log_debug("%02x ", transfer->buffer[i]); } log_debug("\n"); // copy read data to our EEPROM buffer memcpy(readbuf + byteoffset, transfer->buffer, transfer->actual_length); getnextpkt = 1; break; default: fprintf(stderr, "\ncbBulkIn: error : %d\n", transfer->status); getnextpkt = -1; } return; } // Callback function for async bulk out comms void cbBulkOut(struct libusb_transfer *transfer) { syncackpkt = 1; log_debug("\ncbBulkOut(): Sync/Ack received: status %d\n", transfer->status); return; } // -------------------------------------------------------------------------- // ch341writeEEPROM() // write n bytes to 24c32/24c64 device (in packets of 32 bytes) int32_t ch341writeEEPROM(struct libusb_device_handle *devHandle, uint8_t *buffer, uint32_t bytesum) { uint8_t ch341outBuffer[EEPROM_WRITE_BUF_SZ], *outptr, *bufptr; int32_t ret = 0, i; uint16_t byteoffset = 0, bytes = bytesum; uint8_t addrbytecount = 3; // 24c32 and 24c64 (and other 24c??) use 3 bytes for addressing int32_t actuallen = 0; bufptr = buffer; while (bytes) { outptr = ch341outBuffer; *outptr++ = mCH341A_CMD_I2C_STREAM; *outptr++ = mCH341A_CMD_I2C_STM_STA; *outptr++ = mCH341A_CMD_I2C_STM_OUT + addrbytecount + MIN(bytes, 25); *outptr++ = 0xa0; // EEPROM device address *outptr++ = (uint8_t)(byteoffset >> 8 & 0xff); // MSB (big-endian) byte address *outptr++ = (uint8_t)(byteoffset & 0xff); // LSB of 16-bit byte address memcpy(outptr, bufptr, MIN(bytes, 25)); // payload has two parts: 25 bytes & up to 7 more bytes outptr += MIN(bytes, 25); bufptr += MIN(bytes, 25); bytes -= MIN(bytes, 25); *outptr++ = mCH341A_CMD_I2C_STM_END; if (bytes) { *outptr++ = mCH341A_CMD_I2C_STREAM; *outptr++ = mCH341A_CMD_I2C_STM_OUT + MIN(bytes, 7); memcpy(outptr, bufptr, MIN(bytes, 7)); outptr += MIN(bytes, 7); bufptr += MIN(bytes, 7); bytes -= MIN(bytes, 7); } *outptr++ = mCH341A_CMD_I2C_STM_STO; *outptr = mCH341A_CMD_I2C_STM_END; byteoffset += 0x20; for (i = 0; i < EEPROM_WRITE_BUF_SZ; i++) { if (!(i % 0x10)) log_debug("\n%04x : ", i); log_debug("%02x ", ch341outBuffer[i]); } log_debug("\n"); ret = libusb_bulk_transfer( devHandle, BULK_WRITE_ENDPOINT, ch341outBuffer, EEPROM_WRITE_BUF_SZ, &actuallen, DEFAULT_TIMEOUT); if (ret < 0) { fprintf(stderr, "Failed to write to EEPROM: '%s'\n", strerror(-ret)); return -1; } log_debug("Writing [aa 5a 00] to EEPROM\n"); // Magic CH341a packet! Undocumented, unknown purpose outptr = ch341outBuffer; *outptr++ = mCH341A_CMD_I2C_STREAM; *outptr++ = 0x5a; // what is this 0x5a?? *outptr++ = mCH341A_CMD_I2C_STM_END; ret = libusb_bulk_transfer( devHandle, BULK_WRITE_ENDPOINT, ch341outBuffer, 3, &actuallen, DEFAULT_TIMEOUT); if (ret < 0) { fprintf(stderr, "Failed to write to EEPROM: '%s'\n", strerror(-ret)); return -1; } log_debug( "Written [%d] of [%d] bytes \r", bytes, bytesum); } return 0; } int ch341_operation(ch341_operation_desc opdesc) { int ret = 0; struct libusb_device_handle *devHandle = NULL; FILE *fp; int bytesread = 0; int eepromsize = 0; if ((eepromsize = parseEEPsize(opdesc.eepromname)) <= 0) { fprintf(stderr, "Invalid EEPROM name %s \n", opdesc.eepromname); goto error; } if (eepromsize <= 0) { fprintf(stderr, "Invalid EEPROM size\n"); ret = 255; goto error; } readbuf = (uint8_t *)malloc(MAX_EEPROM_SIZE); // space to store loaded EEPROM if (!readbuf) { fprintf(stderr, "Couldnt malloc space needed for EEPROM image\n"); ret = 255; goto error; } if (!(devHandle = ch341configure(USB_LOCK_VENDOR, USB_LOCK_PRODUCT))) { fprintf(stderr, "Couldnt configure USB device\n", USB_LOCK_VENDOR, USB_LOCK_PRODUCT); ret = 255; goto error; } log_debug("Configured USB device\n", USB_LOCK_VENDOR, USB_LOCK_PRODUCT); if (ch341setstream(devHandle, CH341_I2C_STANDARD_SPEED) < 0) { fprintf(stderr, "Couldnt set i2c bus speed\n"); ret = 255; goto error; } log_debug("Set i2c bus speed to [100kHz]\n"); switch (opdesc.operation) { case 'r': // read memset(readbuf, 0xff, MAX_EEPROM_SIZE); if (ch341readEEPROM(devHandle, readbuf, eepromsize) < 0) { fprintf( stderr, "Couldnt read [%d] bytes from [%s] EEPROM\n", eepromsize, opdesc.eepromname); ret = 255; goto error; } fprintf(stdout, "Read [%d] bytes from [%s] EEPROM\n", eepromsize, opdesc.eepromname); for (int i = 0; i < eepromsize; i++) { if (!(i % 16)) log_debug("\n%04x: ", i); log_debug("%02x ", readbuf[i]); } log_debug("\n"); if (!(fp = fopen(opdesc.filename, "wb"))) { fprintf(stderr, "Couldnt open file [%s] for writing\n", opdesc.filename); ret = 255; goto error; } fwrite(readbuf, 1, eepromsize, fp); if (ferror(fp)) { fprintf(stderr, "Error writing file [%s]\n", opdesc.filename); if (fp) fclose(fp); ret = 255; goto error; } fclose(fp); fprintf(stdout, "Wrote [%d] bytes to file [%s]\n", eepromsize, opdesc.filename); break; case 'w': // write if (!(fp = fopen(opdesc.filename, "rb"))) { fprintf(stderr, "Couldnt open file [%s] for reading\n", opdesc.filename); ret = 255; goto error; } memset(readbuf, 0xff, MAX_EEPROM_SIZE); bytesread = fread(readbuf, 1, MAX_EEPROM_SIZE, fp); if (ferror(fp)) { fprintf(stderr, "Error reading file [%s]\n", opdesc.filename); if (fp) fclose(fp); ret = 255; goto error; } fclose(fp); fprintf(stdout, "Read [%d] bytes from file [%s]\n", bytesread, opdesc.filename); if (bytesread < eepromsize) fprintf(stdout, "Padded to [%d] bytes for [%s] EEPROM\n", eepromsize, opdesc.eepromname); if (bytesread > eepromsize) fprintf( stdout, "Truncated to [%d] bytes for [%s] EEPROM\n", eepromsize, opdesc.eepromname); if (ch341writeEEPROM(devHandle, readbuf, eepromsize) < 0) { fprintf( stderr, "Failed to write [%d] bytes from [%s] to [%s] EEPROM\n", eepromsize, opdesc.filename, opdesc.eepromname); ret = 255; goto error; } fprintf(stdout, "Wrote [%d] bytes to [%s] EEPROM\n", eepromsize, opdesc.eepromname); break; case 'e': // erase memset(readbuf, 0xff, MAX_EEPROM_SIZE); if (ch341writeEEPROM(devHandle, readbuf, eepromsize) < 0) { fprintf( stderr, "Failed to erase [%d] bytes of [%s] EEPROM\n", eepromsize, opdesc.eepromname); ret = 255; goto error; } fprintf(stdout, "Erased [%d] bytes of [%s] EEPROM\n", eepromsize, opdesc.eepromname); break; default: fprintf(stderr, "Unknown option\n"); ret = 255; goto error; } error: if (readbuf) { free(readbuf); } if (devHandle) { libusb_release_interface(devHandle, DEFAULT_INTERFACE); printf("Released device interface [%d]\n", DEFAULT_INTERFACE); libusb_close(devHandle); printf("Closed USB device\n"); libusb_exit(NULL); } return ret; }