/* Teensy Loader, Command Line Interface * Program and Reboot Teensy Board with HalfKay Bootloader * http://www.pjrc.com/teensy/loader_cli.html * Copyright 2008-2010, PJRC.COM, LLC * * You may redistribute this program and/or modify it under the terms * of the GNU General Public License as published by the Free Software * Foundation, version 3 of the License. * * 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 http://www.gnu.org/licenses/ */ /* Want to incorporate this code into a proprietary application?? * Just email paul@pjrc.com to ask. Usually it's not a problem, * but you do need to ask to use this code in any way other than * those permitted by the GNU General Public License, version 3 */ /* For non-root permissions on ubuntu or similar udev-based linux * http://www.pjrc.com/teensy/49-teensy.rules */ #include #include #include #include #include #include void usage(void) { fprintf(stderr, "Usage: teensy_loader_cli -mmcu= [-w] [-h] [-n] [-v] \n"); fprintf(stderr, "\t-w : Wait for device to appear\n"); fprintf(stderr, "\t-r : Use hard reboot if device not online\n"); fprintf(stderr, "\t-n : No reboot after programming\n"); fprintf(stderr, "\t-v : Verbose output\n"); fprintf(stderr, "\n = atmega32u4 | at90usb162 | at90usb646 | at90usb1286\n"); fprintf(stderr, "\nFor more information, please visit:\n"); fprintf(stderr, "http://www.pjrc.com/teensy/loader_cli.html\n"); exit(1); } // USB Access Functions int teensy_open(void); int teensy_write(void *buf, int len, double timeout); void teensy_close(void); int hard_reboot(void); // Intel Hex File Functions int read_intel_hex(const char *filename); int ihex_bytes_within_range(int begin, int end); void ihex_get_data(int addr, int len, unsigned char *bytes); // Misc stuff int printf_verbose(const char *format, ...); void delay(double seconds); void die(const char *str, ...); void parse_options(int argc, char **argv); // options (from user via command line args) int wait_for_device_to_appear = 0; int hard_reboot_device = 0; int reboot_after_programming = 1; int verbose = 0; int code_size = 0, block_size = 0; const char *filename=NULL; /****************************************************************/ /* */ /* Main Program */ /* */ /****************************************************************/ int main(int argc, char **argv) { unsigned char buf[260]; int num, addr, r, first_block=1, waited=0; // parse command line arguments parse_options(argc, argv); if (!filename) { fprintf(stderr, "Filename must be specified\n\n"); usage(); } if (!code_size) { fprintf(stderr, "MCU type must be specified\n\n"); usage(); } printf_verbose("Teensy Loader, Command Line, Version 2.0\n"); // read the intel hex file // this is done first so any error is reported before using USB num = read_intel_hex(filename); if (num < 0) die("error reading intel hex file \"%s\"", filename); printf_verbose("Read \"%s\": %d bytes, %.1f%% usage\n", filename, num, (double)num / (double)code_size * 100.0); // open the USB device while (1) { if (teensy_open()) break; if (hard_reboot_device) { if (!hard_reboot()) die("Unable to find rebootor\n"); printf_verbose("Hard Reboot performed\n"); hard_reboot_device = 0; // only hard reboot once wait_for_device_to_appear = 1; } if (!wait_for_device_to_appear) die("Unable to open device\n"); if (!waited) { printf_verbose("Waiting for Teensy device...\n"); printf_verbose(" (hint: press the reset button)\n"); waited = 1; } delay(0.25); } printf_verbose("Found HalfKay Bootloader\n"); // if we waited for the device, read the hex file again // perhaps it changed while we were waiting? if (waited) { num = read_intel_hex(filename); if (num < 0) die("error reading intel hex file \"%s\"", filename); printf_verbose("Read \"%s\": %d bytes, %.1f%% usage\n", filename, num, (double)num / (double)code_size * 100.0); } // program the data printf_verbose("Programming"); fflush(stdout); for (addr = 0; addr < code_size; addr += block_size) { if (addr > 0 && !ihex_bytes_within_range(addr, addr + block_size - 1)) { // don't waste time on blocks that are unused, // but always do the first one to erase the chip continue; } printf_verbose("."); if (code_size < 0x10000) { buf[0] = addr & 255; buf[1] = (addr >> 8) & 255; } else { buf[0] = (addr >> 8) & 255; buf[1] = (addr >> 16) & 255; } ihex_get_data(addr, block_size, buf + 2); r = teensy_write(buf, block_size + 2, first_block ? 3.0 : 0.25); if (!r) die("error writing to Teensy\n"); first_block = 0; } printf_verbose("\n"); // reboot to the user's new code if (reboot_after_programming) { printf_verbose("Booting\n"); buf[0] = 0xFF; buf[1] = 0xFF; memset(buf + 2, 0, sizeof(buf) - 2); teensy_write(buf, block_size + 2, 0.25); } teensy_close(); return 0; } /****************************************************************/ /* */ /* USB Access - libusb (Linux & FreeBSD) */ /* */ /****************************************************************/ #if defined(USE_LIBUSB) // http://libusb.sourceforge.net/doc/index.html #include usb_dev_handle * open_usb_device(int vid, int pid) { struct usb_bus *bus; struct usb_device *dev; usb_dev_handle *h; char buf[128]; int r; usb_init(); usb_find_busses(); usb_find_devices(); //printf_verbose("\nSearching for USB device:\n"); for (bus = usb_get_busses(); bus; bus = bus->next) { for (dev = bus->devices; dev; dev = dev->next) { //printf_verbose("bus \"%s\", device \"%s\" vid=%04X, pid=%04X\n", // bus->dirname, dev->filename, // dev->descriptor.idVendor, // dev->descriptor.idProduct //); if (dev->descriptor.idVendor != vid) continue; if (dev->descriptor.idProduct != pid) continue; h = usb_open(dev); if (!h) { printf_verbose("Found device but unable to open"); continue; } #ifdef LIBUSB_HAS_GET_DRIVER_NP r = usb_get_driver_np(h, 0, buf, sizeof(buf)); if (r >= 0) { r = usb_detach_kernel_driver_np(h, 0); if (r < 0) { usb_close(h); printf_verbose("Device is in use by \"%s\" driver", buf); continue; } } #endif // Mac OS-X - removing this call to usb_claim_interface() might allow // this to work, even though it is a clear misuse of the libusb API. // normally Apple's IOKit should be used on Mac OS-X r = usb_claim_interface(h, 0); if (r < 0) { usb_close(h); printf_verbose("Unable to claim interface, check USB permissions"); continue; } return h; } } return NULL; } static usb_dev_handle *libusb_teensy_handle = NULL; int teensy_open(void) { teensy_close(); libusb_teensy_handle = open_usb_device(0x16C0, 0x0478); if (libusb_teensy_handle) return 1; return 0; } int teensy_write(void *buf, int len, double timeout) { int r; if (!libusb_teensy_handle) return 0; r = usb_control_msg(libusb_teensy_handle, 0x21, 9, 0x0200, 0, (char *)buf, len, (int)(timeout * 1000.0)); if (r < 0) return 0; return 1; } void teensy_close(void) { if (!libusb_teensy_handle) return; usb_release_interface(libusb_teensy_handle, 0); usb_close(libusb_teensy_handle); libusb_teensy_handle = NULL; } int hard_reboot(void) { usb_dev_handle *rebootor; int r; rebootor = open_usb_device(0x16C0, 0x0477); if (!rebootor) return 0; r = usb_control_msg(rebootor, 0x21, 9, 0x0200, 0, "reboot", 6, 100); usb_release_interface(rebootor, 0); usb_close(rebootor); if (r < 0) return 0; return 1; } #endif /****************************************************************/ /* */ /* USB Access - Microsoft WIN32 */ /* */ /****************************************************************/ #if defined(USE_WIN32) // http://msdn.microsoft.com/en-us/library/ms790932.aspx #include #include #include #include HANDLE open_usb_device(int vid, int pid) { GUID guid; HDEVINFO info; DWORD index, required_size; SP_DEVICE_INTERFACE_DATA iface; SP_DEVICE_INTERFACE_DETAIL_DATA *details; HIDD_ATTRIBUTES attrib; HANDLE h; BOOL ret; HidD_GetHidGuid(&guid); info = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (info == INVALID_HANDLE_VALUE) return NULL; for (index=0; 1 ;index++) { iface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); ret = SetupDiEnumDeviceInterfaces(info, NULL, &guid, index, &iface); if (!ret) { SetupDiDestroyDeviceInfoList(info); break; } SetupDiGetInterfaceDeviceDetail(info, &iface, NULL, 0, &required_size, NULL); details = (SP_DEVICE_INTERFACE_DETAIL_DATA *)malloc(required_size); if (details == NULL) continue; memset(details, 0, required_size); details->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); ret = SetupDiGetDeviceInterfaceDetail(info, &iface, details, required_size, NULL, NULL); if (!ret) { free(details); continue; } h = CreateFile(details->DevicePath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); free(details); if (h == INVALID_HANDLE_VALUE) continue; attrib.Size = sizeof(HIDD_ATTRIBUTES); ret = HidD_GetAttributes(h, &attrib); if (!ret) { CloseHandle(h); continue; } if (attrib.VendorID != vid || attrib.ProductID != pid) { CloseHandle(h); continue; } SetupDiDestroyDeviceInfoList(info); return h; } return NULL; } int write_usb_device(HANDLE h, void *buf, int len, int timeout) { static HANDLE event = NULL; unsigned char tmpbuf[1040]; OVERLAPPED ov; DWORD n, r; if (len > sizeof(tmpbuf) - 1) return 0; if (event == NULL) { event = CreateEvent(NULL, TRUE, TRUE, NULL); if (!event) return 0; } ResetEvent(&event); memset(&ov, 0, sizeof(ov)); ov.hEvent = event; tmpbuf[0] = 0; memcpy(tmpbuf + 1, buf, len); if (!WriteFile(h, tmpbuf, len + 1, NULL, &ov)) { if (GetLastError() != ERROR_IO_PENDING) return 0; r = WaitForSingleObject(event, timeout); if (r == WAIT_TIMEOUT) { CancelIo(h); return 0; } if (r != WAIT_OBJECT_0) return 0; } if (!GetOverlappedResult(h, &ov, &n, FALSE)) return 0; if (n <= 0) return 0; return 1; } void print_win32_err(void) { char buf[256]; DWORD err; err = GetLastError(); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, buf, sizeof(buf), NULL); printf("err %ld: %s\n", err, buf); } static HANDLE win32_teensy_handle = NULL; int teensy_open(void) { teensy_close(); win32_teensy_handle = open_usb_device(0x16C0, 0x0478); if (win32_teensy_handle) return 1; return 0; } int teensy_write(void *buf, int len, double timeout) { int r; if (!win32_teensy_handle) return 0; r = write_usb_device(win32_teensy_handle, buf, len, (int)(timeout * 1000.0)); //if (!r) print_win32_err(); return r; } void teensy_close(void) { if (!win32_teensy_handle) return; CloseHandle(win32_teensy_handle); win32_teensy_handle = NULL; } int hard_reboot(void) { HANDLE rebootor; int r; rebootor = open_usb_device(0x16C0, 0x0477); if (!rebootor) return 0; r = write_usb_device(rebootor, "reboot", 6, 100); CloseHandle(rebootor); return r; } #endif /****************************************************************/ /* */ /* USB Access - Apple's IOKit, Mac OS-X */ /* */ /****************************************************************/ #if defined(USE_APPLE_IOKIT) // http://developer.apple.com/technotes/tn2007/tn2187.html #include #include #include struct usb_list_struct { IOHIDDeviceRef ref; int pid; int vid; struct usb_list_struct *next; }; static struct usb_list_struct *usb_list=NULL; static IOHIDManagerRef hid_manager=NULL; void attach_callback(void *context, IOReturn r, void *hid_mgr, IOHIDDeviceRef dev) { CFTypeRef type; struct usb_list_struct *n, *p; int32_t pid, vid; if (!dev) return; type = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDVendorIDKey)); if (!type || CFGetTypeID(type) != CFNumberGetTypeID()) return; if (!CFNumberGetValue((CFNumberRef)type, kCFNumberSInt32Type, &vid)) return; type = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDProductIDKey)); if (!type || CFGetTypeID(type) != CFNumberGetTypeID()) return; if (!CFNumberGetValue((CFNumberRef)type, kCFNumberSInt32Type, &pid)) return; n = (struct usb_list_struct *)malloc(sizeof(struct usb_list_struct)); if (!n) return; //printf("attach callback: vid=%04X, pid=%04X\n", vid, pid); n->ref = dev; n->vid = vid; n->pid = pid; n->next = NULL; if (usb_list == NULL) { usb_list = n; } else { for (p = usb_list; p->next; p = p->next) ; p->next = n; } } void detach_callback(void *context, IOReturn r, void *hid_mgr, IOHIDDeviceRef dev) { struct usb_list_struct *p, *tmp, *prev=NULL; p = usb_list; while (p) { if (p->ref == dev) { if (prev) { prev->next = p->next; } else { usb_list = p->next; } tmp = p; p = p->next; free(tmp); } else { prev = p; p = p->next; } } } void init_hid_manager(void) { CFMutableDictionaryRef dict; IOReturn ret; if (hid_manager) return; hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); if (hid_manager == NULL || CFGetTypeID(hid_manager) != IOHIDManagerGetTypeID()) { if (hid_manager) CFRelease(hid_manager); printf_verbose("no HID Manager - maybe this is a pre-Leopard (10.5) system?\n"); return; } dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!dict) return; IOHIDManagerSetDeviceMatching(hid_manager, dict); CFRelease(dict); IOHIDManagerScheduleWithRunLoop(hid_manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, attach_callback, NULL); IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, detach_callback, NULL); ret = IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone); if (ret != kIOReturnSuccess) { IOHIDManagerUnscheduleFromRunLoop(hid_manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); CFRelease(hid_manager); printf_verbose("Error opening HID Manager"); } } static void do_run_loop(void) { while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource) ; } IOHIDDeviceRef open_usb_device(int vid, int pid) { struct usb_list_struct *p; IOReturn ret; init_hid_manager(); do_run_loop(); for (p = usb_list; p; p = p->next) { if (p->vid == vid && p->pid == pid) { ret = IOHIDDeviceOpen(p->ref, kIOHIDOptionsTypeNone); if (ret == kIOReturnSuccess) return p->ref; } } return NULL; } void close_usb_device(IOHIDDeviceRef dev) { struct usb_list_struct *p; do_run_loop(); for (p = usb_list; p; p = p->next) { if (p->ref == dev) { IOHIDDeviceClose(dev, kIOHIDOptionsTypeNone); return; } } } static IOHIDDeviceRef iokit_teensy_reference = NULL; int teensy_open(void) { teensy_close(); iokit_teensy_reference = open_usb_device(0x16C0, 0x0478); if (iokit_teensy_reference) return 1; return 0; } int teensy_write(void *buf, int len, double timeout) { IOReturn ret; // timeouts do not work on OS-X // IOHIDDeviceSetReportWithCallback is not implemented // even though Apple documents it with a code example! // submitted to Apple on 22-sep-2009, problem ID 7245050 if (!iokit_teensy_reference) return 0; ret = IOHIDDeviceSetReport(iokit_teensy_reference, kIOHIDReportTypeOutput, 0, buf, len); if (ret == kIOReturnSuccess) return 1; return 0; } void teensy_close(void) { if (!iokit_teensy_reference) return; close_usb_device(iokit_teensy_reference); iokit_teensy_reference = NULL; } int hard_reboot(void) { IOHIDDeviceRef rebootor; IOReturn ret; rebootor = open_usb_device(0x16C0, 0x0477); if (!rebootor) return 0; ret = IOHIDDeviceSetReport(rebootor, kIOHIDReportTypeOutput, 0, (uint8_t *)("reboot"), 6); close_usb_device(rebootor); if (ret == kIOReturnSuccess) return 1; return 0; } #endif /****************************************************************/ /* */ /* USB Access - BSD's UHID driver */ /* */ /****************************************************************/ #if defined(USE_UHID) // Thanks to Todd T Fries for help getting this working on OpenBSD // and to Chris Kuethe for the initial patch to use UHID. #include #include #include #include #ifndef USB_GET_DEVICEINFO #include #endif int open_usb_device(int vid, int pid) { int r, fd; DIR *dir; struct dirent *d; struct usb_device_info info; char buf[256]; dir = opendir("/dev"); if (!dir) return -1; while ((d = readdir(dir)) != NULL) { if (strncmp(d->d_name, "uhid", 4) != 0) continue; snprintf(buf, sizeof(buf), "/dev/%s", d->d_name); fd = open(buf, O_RDWR); if (fd < 0) continue; r = ioctl(fd, USB_GET_DEVICEINFO, &info); if (r < 0) { // NetBSD: added in 2004 // OpenBSD: added November 23, 2009 // FreeBSD: missing (FreeBSD 8.0) - USE_LIBUSB works! die("Error: your uhid driver does not support" " USB_GET_DEVICEINFO, please upgrade!\n"); close(fd); closedir(dir); exit(1); } //printf("%s: v=%d, p=%d\n", buf, info.udi_vendorNo, info.udi_productNo); if (info.udi_vendorNo == vid && info.udi_productNo == pid) { closedir(dir); return fd; } close(fd); } closedir(dir); return -1; } static int uhid_teensy_fd = -1; int teensy_open(void) { teensy_close(); uhid_teensy_fd = open_usb_device(0x16C0, 0x0478); if (uhid_teensy_fd < 0) return 0; return 1; } int teensy_write(void *buf, int len, double timeout) { int r; // TODO: imeplement timeout... how?? r = write(uhid_teensy_fd, buf, len); if (r == len) return 1; return 0; } void teensy_close(void) { if (uhid_teensy_fd >= 0) { close(uhid_teensy_fd); uhid_teensy_fd = -1; } } int hard_reboot(void) { int r, rebootor_fd; rebootor_fd = open_usb_device(0x16C0, 0x0477); if (rebootor_fd < 0) return 0; r = write(rebootor_fd, "reboot", 6); delay(0.1); close(rebootor_fd); if (r == 6) return 1; return 0; } #endif /****************************************************************/ /* */ /* Read Intel Hex File */ /* */ /****************************************************************/ // the maximum flash image size we can support // chips with larger memory may be used, but only this // much intel-hex data can be loaded into memory! #define MAX_MEMORY_SIZE 0x10000 static unsigned char firmware_image[MAX_MEMORY_SIZE]; static unsigned char firmware_mask[MAX_MEMORY_SIZE]; static int end_record_seen=0; static int byte_count; static unsigned int extended_addr = 0; static int parse_hex_line(char *line); int read_intel_hex(const char *filename) { FILE *fp; int i, lineno=0; char buf[1024]; byte_count = 0; end_record_seen = 0; for (i=0; i= MAX_MEMORY_SIZE) return 0; ptr += 2; sum = (len & 255) + ((addr >> 8) & 255) + (addr & 255) + (code & 255); if (code != 0) { if (code == 1) { end_record_seen = 1; return 1; } if (code == 2 && len == 2) { if (!sscanf(ptr, "%04x", &i)) return 1; ptr += 4; sum += ((i >> 8) & 255) + (i & 255); if (!sscanf(ptr, "%02x", &cksum)) return 1; if (((sum & 255) + (cksum & 255)) & 255) return 1; extended_addr = i << 4; //printf("ext addr = %05X\n", extended_addr); } if (code == 4 && len == 2) { if (!sscanf(ptr, "%04x", &i)) return 1; ptr += 4; sum += ((i >> 8) & 255) + (i & 255); if (!sscanf(ptr, "%02x", &cksum)) return 1; if (((sum & 255) + (cksum & 255)) & 255) return 1; extended_addr = i << 16; //printf("ext addr = %08X\n", extended_addr); } return 1; // non-data line } byte_count += len; while (num != len) { if (sscanf(ptr, "%02x", &i) != 1) return 0; i &= 255; firmware_image[addr + extended_addr + num] = i; firmware_mask[addr + extended_addr + num] = 1; ptr += 2; sum += i; (num)++; if (num >= 256) return 0; } if (!sscanf(ptr, "%02x", &cksum)) return 0; if (((sum & 255) + (cksum & 255)) & 255) return 0; /* checksum error */ return 1; } int ihex_bytes_within_range(int begin, int end) { int i; if (begin < 0 || begin >= MAX_MEMORY_SIZE || end < 0 || end >= MAX_MEMORY_SIZE) { return 0; } for (i=begin; i<=end; i++) { if (firmware_mask[i]) return 1; } return 0; } void ihex_get_data(int addr, int len, unsigned char *bytes) { int i; if (addr < 0 || len < 0 || addr + len >= MAX_MEMORY_SIZE) { for (i=0; i