// // Copyright 2010-2013 Ettus Research LLC // // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const static boost::uint16_t FX3_VID = 0x04b4; const static boost::uint16_t FX3_DEFAULT_PID = 0x00f3; const static boost::uint16_t FX3_REENUM_PID = 0x00f0; const static boost::uint16_t B2XX_VID = 0x2500; const static boost::uint16_t B2XX_PID = 0x0020; const static boost::uint8_t VRT_VENDOR_OUT = (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT); const static boost::uint8_t VRT_VENDOR_IN = (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN); const static boost::uint8_t FX3_FIRMWARE_LOAD = 0xA0; const static boost::uint8_t B2XX_VREQ_FPGA_START = 0x02; const static boost::uint8_t B2XX_VREQ_FPGA_DATA = 0x12; const static boost::uint8_t B2XX_VREQ_SET_FPGA_HASH = 0x1C; const static boost::uint8_t B2XX_VREQ_GET_FPGA_HASH = 0x1D; const static boost::uint8_t B2XX_VREQ_FPGA_RESET = 0x62; const static boost::uint8_t B2XX_VREQ_GET_USB = 0x80; const static boost::uint8_t B2XX_VREQ_GET_STATUS = 0x83; const static boost::uint8_t B2XX_VREQ_FX3_RESET = 0x99; const static boost::uint8_t B2XX_VREQ_EEPROM_WRITE = 0xBA; const static boost::uint8_t FX3_STATE_FPGA_READY = 0x00; const static boost::uint8_t FX3_STATE_CONFIGURING_FPGA = 0x01; const static boost::uint8_t FX3_STATE_BUSY = 0x02; const static boost::uint8_t FX3_STATE_RUNNING = 0x03; typedef boost::uint32_t hash_type; typedef std::vector byte_vector_t; namespace po = boost::program_options; namespace fs = boost::filesystem; //!used with lexical cast to parse a hex string template struct to_hex{ T value; operator T() const {return value;} friend std::istream& operator>>(std::istream& in, to_hex& out){ in >> std::hex >> out.value; return in; } }; //!parse hex-formatted ASCII text into an int boost::uint16_t atoh(const std::string &string){ if (string.substr(0, 2) == "0x"){ return boost::lexical_cast >(string); } return boost::lexical_cast(string); } /*! * Create a file hash * The hash will be used to identify the loaded firmware and fpga image * \param filename file used to generate hash value * \return hash value in a size_t type */ static hash_type generate_hash(const char *filename) { std::ifstream file(filename); if (not file){ std::cerr << std::string("cannot open input file ") + filename << std::endl; } size_t hash = 0; char ch; while (file.get(ch)) { boost::hash_combine(hash, ch); } if (not file.eof()){ std::cerr << std::string("file error ") + filename << std::endl; } file.close(); return hash_type(hash); } /*! * Verify checksum of a Intel HEX record * \param record a line from an Intel HEX file * \return true if record is valid, false otherwise */ bool checksum(std::string *record) { size_t len = record->length(); unsigned int i; unsigned char sum = 0; unsigned int val; for (i = 1; i < len; i += 2) { std::istringstream(record->substr(i, 2)) >> std::hex >> val; sum += val; } if (sum == 0) return true; else return false; } /*! * Parse Intel HEX record * * \param record a line from an Intel HEX file * \param len output length of record * \param addr output address * \param type output type * \param data output data * \return true if record is sucessfully read, false on error */ bool parse_record(std::string *record, boost::uint16_t &len, boost::uint16_t &addr, uint16_t &type, unsigned char* data) { unsigned int i; std::string _data; unsigned int val; if (record->substr(0, 1) != ":") return false; std::istringstream(record->substr(1, 2)) >> std::hex >> len; std::istringstream(record->substr(3, 4)) >> std::hex >> addr; std::istringstream(record->substr(7, 2)) >> std::hex >> type; for (i = 0; i < len; i++) { std::istringstream(record->substr(9 + 2 * i, 2)) >> std::hex >> val; data[i] = (unsigned char) val; } return true; } /*! * Write data to the FX3. * * \param dev_handle the libusb-1.0 device handle * \param request the usb transfer request type * \param value the USB bValue * \param index the USB bIndex * \param buff the data to write * \param length the number of bytes to write * \return the number of bytes written */ libusb_error fx3_control_write(libusb_device_handle *dev_handle, boost::uint8_t request, boost::uint16_t value, boost::uint16_t index, unsigned char *buff, boost::uint16_t length, boost::uint32_t timeout = 0) { #if 0 if(DEBUG) { std::cout << "Writing: <" << std::hex << std::setw(6) << std::showbase \ << std::internal << std::setfill('0') << (int) request \ << ", " << std::setw(6) << (int) VRT_VENDOR_OUT \ << ", " << std::setw(6) << value \ << ", " << std::setw(6) << index \ << ", " << std::dec << std::setw(2) << length \ << ">" << std::endl; std::cout << "\t\tData: 0x " << std::noshowbase; for(int count = 0; count < length; count++) { std::cout << std::hex << std::setw(2) << (int) buff[count] << " "; } std::cout << std::showbase << std::endl; } #endif return (libusb_error) libusb_control_transfer(dev_handle, VRT_VENDOR_OUT, request, \ value, index, buff, length, timeout); } /*! * Read data from the FX3. * * \param dev_handle the libusb-1.0 device handle * \param request the usb transfer request type * \param value the USB bValue * \param index the USB bIndex * \param buff a buffer to store the read bytes to * \param length the number of bytes to read * \return the number of bytes read */ libusb_error fx3_control_read(libusb_device_handle *dev_handle, boost::uint8_t request, boost::uint16_t value, boost::uint16_t index, unsigned char *buff, boost::uint16_t length, boost::uint32_t timeout = 0) { #if 0 if(DEBUG) { std::cout << "Reading: <" << std::hex << std::setw(6) << std::showbase \ << std::internal << std::setfill('0') << (int) request \ << ", " << std::setw(6) << (int) VRT_VENDOR_IN \ << ", " << std::setw(6) << value \ << ", " << std::setw(6) << index \ << ", " << std::dec << std::setw(2) << length \ << ">" << std::endl << std::endl; } #endif return (libusb_error) libusb_control_transfer(dev_handle, VRT_VENDOR_IN, request, \ value, index, buff, length, timeout); } void write_eeprom(libusb_device_handle *dev_handle, boost::uint8_t addr, boost::uint8_t offset, const byte_vector_t &bytes) { fx3_control_write(dev_handle, B2XX_VREQ_EEPROM_WRITE, 0, offset | (boost::uint16_t(addr) << 8), (unsigned char *) &bytes[0], bytes.size()); } boost::uint8_t get_fx3_status(libusb_device_handle *dev_handle) { unsigned char rx_data[1]; fx3_control_read(dev_handle, B2XX_VREQ_GET_STATUS, 0x00, 0x00, rx_data, 1); return boost::lexical_cast(rx_data[0]); } void usrp_get_fpga_hash(libusb_device_handle *dev_handle, hash_type &hash) { fx3_control_read(dev_handle, B2XX_VREQ_GET_FPGA_HASH, 0x00, 0x00, (unsigned char*) &hash, 4, 500); } void usrp_set_fpga_hash(libusb_device_handle *dev_handle, hash_type hash) { fx3_control_write(dev_handle, B2XX_VREQ_SET_FPGA_HASH, 0x00, 0x00, (unsigned char*) &hash, 4); } boost::int32_t load_fpga(libusb_device_handle *dev_handle, const std::string filestring) { if (filestring.empty()) { std::cerr << "load_fpga: input file is empty." << std::endl; exit(-1); } boost::uint8_t fx3_state = 0; const char *filename = filestring.c_str(); hash_type hash = generate_hash(filename); hash_type loaded_hash; usrp_get_fpga_hash(dev_handle, loaded_hash); if (hash == loaded_hash) return 0; size_t file_size = 0; { std::ifstream file(filename, std::ios::in | std::ios::binary | std::ios::ate); file_size = file.tellg(); } std::ifstream file; file.open(filename, std::ios::in | std::ios::binary); if(!file.good()) { std::cerr << "load_fpga: cannot open FPGA input file." << std::endl; exit(-1); } do { fx3_state = get_fx3_status(dev_handle); boost::this_thread::sleep(boost::posix_time::milliseconds(10)); } while(fx3_state != FX3_STATE_FPGA_READY); std::cout << "Loading FPGA image: " \ << filestring << "..." << std::flush; unsigned char out_buff[64]; memset(out_buff, 0x00, sizeof(out_buff)); fx3_control_write(dev_handle, B2XX_VREQ_FPGA_START, 0, 0, out_buff, 1, 1000); do { fx3_state = get_fx3_status(dev_handle); boost::this_thread::sleep(boost::posix_time::milliseconds(10)); } while(fx3_state != FX3_STATE_CONFIGURING_FPGA); size_t bytes_sent = 0; while(!file.eof()) { file.read((char *) out_buff, sizeof(out_buff)); const std::streamsize n = file.gcount(); if(n == 0) continue; boost::uint16_t transfer_count = boost::uint16_t(n); /* Send the data to the device. */ fx3_control_write(dev_handle, B2XX_VREQ_FPGA_DATA, 0, 0, out_buff, transfer_count, 5000); if (bytes_sent == 0) std::cout << " 0%" << std::flush; const size_t percent_before = size_t((bytes_sent*100)/file_size); bytes_sent += transfer_count; const size_t percent_after = size_t((bytes_sent*100)/file_size); if (percent_before/10 != percent_after/10) { std::cout << "\b\b\b\b" << std::setw(3) << percent_after << "%" << std::flush; } } file.close(); do { fx3_state = get_fx3_status(dev_handle); boost::this_thread::sleep(boost::posix_time::milliseconds(10)); } while(fx3_state != FX3_STATE_RUNNING); usrp_set_fpga_hash(dev_handle, hash); std::cout << "\b\b\b\b done" << std::endl; return 0; } /*! * Program the FX3 with a firmware file (Intel HEX format) * * \param dev_handle the libusb-1.0 device handle * \param filestring the filename of the firmware file * \return 0 for success, otherwise error code */ boost::int32_t fx3_load_firmware(libusb_device_handle *dev_handle, \ std::string filestring) { if (filestring.empty()) { std::cerr << "fx3_load_firmware: input file is empty." << std::endl; exit(-1); } const char *filename = filestring.c_str(); /* Fields used in each USB control transfer. */ boost::uint16_t len = 0; boost::uint16_t type = 0; boost::uint16_t lower_address_bits = 0x0000; unsigned char data[512]; /* Can be set by the Intel HEX record 0x04, used for all 0x00 records * thereafter. Note this field takes the place of the 'index' parameter in * libusb calls, and is necessary for FX3's 32-bit addressing. */ boost::uint16_t upper_address_bits = 0x0000; std::ifstream file; file.open(filename, std::ifstream::in); if(!file.good()) { std::cerr << "fx3_load_firmware: cannot open firmware input file" << std::endl; exit(-1); } std::cout << "Loading firmware image: " \ << filestring << "..." << std::flush; while (!file.eof()) { boost::int32_t ret = 0; std::string record; file >> record; /* Check for valid Intel HEX record. */ if (!checksum(&record) || !parse_record(&record, len, \ lower_address_bits, type, data)) { std::cerr << "fx3_load_firmware: bad intel hex record checksum" << std::endl; } /* Type 0x00: Data. */ if (type == 0x00) { ret = fx3_control_write(dev_handle, FX3_FIRMWARE_LOAD, \ lower_address_bits, upper_address_bits, data, len); if (ret < 0) { std::cerr << "usrp_load_firmware: usrp_control_write failed" << std::endl; } } /* Type 0x01: EOF. */ else if (type == 0x01) { if (lower_address_bits != 0x0000 || len != 0 ) { std::cerr << "fx3_load_firmware: For EOF record, address must be 0, length must be 0." << std::endl; } /* Successful termination! */ file.close(); /* Let the system settle. */ boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); return 0; } /* Type 0x04: Extended Linear Address Record. */ else if (type == 0x04) { if (lower_address_bits != 0x0000 || len != 2 ) { std::cerr << "fx3_load_firmware: For ELA record, address must be 0, length must be 2." << std::endl; } upper_address_bits = ((boost::uint16_t)((data[0] & 0x00FF) << 8))\ + ((boost::uint16_t)(data[1] & 0x00FF)); } /* Type 0x05: Start Linear Address Record. */ else if (type == 0x05) { if (lower_address_bits != 0x0000 || len != 4 ) { std::cerr << "fx3_load_firmware: For SLA record, address must be 0, length must be 4." << std::endl; } /* The firmware load is complete. We now need to tell the CPU * to jump to an execution address start point, now contained within * the data field. Parse these address bits out, and then push the * instruction. */ upper_address_bits = ((boost::uint16_t)((data[0] & 0x00FF) << 8))\ + ((boost::uint16_t)(data[1] & 0x00FF)); lower_address_bits = ((boost::uint16_t)((data[2] & 0x00FF) << 8))\ + ((boost::uint16_t)(data[3] & 0x00FF)); fx3_control_write(dev_handle, FX3_FIRMWARE_LOAD, lower_address_bits, \ upper_address_bits, 0, 0); std::cout << " done" << std::endl; } /* If we receive an unknown record type, error out. */ else { std::cerr << "fx3_load_firmware: unsupported record type." << std::endl; } } /* There was no valid EOF. */ std::cerr << "fx3_load_firmware: No EOF record found." << std::cout; return ~0; } boost::int32_t main(boost::int32_t argc, char *argv[]) { boost::uint16_t vid, pid; std::string pid_str, vid_str, fw_file, fpga_file; po::options_description desc("Allowed options"); desc.add_options() ("help,h", "help message") ("vid,v", po::value(&vid_str)->default_value("0x2500"), "Specify VID of device to use.") ("pid,p", po::value(&pid_str)->default_value("0x0020"), "Specify PID of device to use.") ("speed,S", "Read back the USB mode currently in use.") ("reset-device,D", "Reset the B2xx Device.") ("reset-fpga,F", "Reset the FPGA (does not require re-programming.") ("reset-usb,U", "Reset the USB subsystem on your host computer.") ("init-device,I", "Initialize a B2xx device.") ("load-fw,W", po::value(&fw_file)->default_value(""), "Load a firmware (hex) file into the FX3.") ("load-fpga,L", po::value(&fpga_file)->default_value(""), "Load a FPGA (bin) file into the FPGA.") ; po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); po::notify(vm); if (vm.count("help")){ std::cout << boost::format("B2xx Utilitiy Program %s") % desc << std::endl; return ~0; } else if (vm.count("reset-usb")) { /* Okay, first, we need to discover what the path is to the ehci and * xhci device files. */ std::set path_list; path_list.insert("/sys/bus/pci/drivers/xhci-pci/"); path_list.insert("/sys/bus/pci/drivers/ehci-pci/"); path_list.insert("/sys/bus/pci/drivers/xhci_hcd/"); path_list.insert("/sys/bus/pci/drivers/ehci_hcd/"); /* Check each of the possible paths above to find which ones this system * uses. */ for(std::set::iterator found = path_list.begin(); found != path_list.end(); ++found) { if(fs::exists(*found)) { fs::path devpath = *found; std::set globbed; /* Now, glob all of the files in the directory. */ fs::directory_iterator end_itr; for(fs::directory_iterator itr(devpath); itr != end_itr; ++itr) { globbed.insert((*itr).path()); } /* Check each file path string to see if it is a device file. */ for(std::set::iterator it = globbed.begin(); it != globbed.end(); ++it) { std::string file = (*it).filename().string(); if(file.compare(0, 5, "0000:") == 0) { /* Un-bind the device. */ std::fstream unbind((devpath.string() + "unbind").c_str(), std::fstream::out); unbind << file; unbind.close(); /* Re-bind the device. */ std::cout << "Re-binding: " << file << " in " << devpath.string() << std::endl; std::fstream bind((devpath.string() + "bind").c_str(), std::fstream::out); bind << file; bind.close(); } } } } return 0; } vid = atoh(vid_str); pid = atoh(pid_str); /* Pointer to pointer of device, used to retrieve a list of devices. */ libusb_device **devs; libusb_device_handle *dev_handle; libusb_context *ctx = NULL; libusb_error error_code; libusb_init(&ctx); libusb_set_debug(ctx, 3); libusb_get_device_list(ctx, &devs); /* If we are initializing the device, the VID/PID will default to the * Cypress VID/PID for the initial FW load. */ if (vm.count("init-device")) { dev_handle = libusb_open_device_with_vid_pid(ctx, FX3_VID, FX3_DEFAULT_PID); if(dev_handle == NULL) { std::cerr << "Cannot open device with vid: " << vid << " and pid: " << pid << std::endl; return -1; } else { std::cout << "Uninitialized B2xx detected..." << std::flush; } libusb_free_device_list(devs, 1); /* Find out if kernel driver is attached, and if so, detach it. */ if(libusb_kernel_driver_active(dev_handle, 0) == 1) { std::cout << " Competing Driver Identified... " << std::flush; if(libusb_detach_kernel_driver(dev_handle, 0) == 0) { std::cout << " Competing Driver Destroyed!" << std::flush; } } libusb_claim_interface(dev_handle, 0); std::cout << " Control of B2xx granted..." << std::endl << std::endl; /* Load the FW. */ error_code = (libusb_error) fx3_load_firmware(dev_handle, fw_file); if(error_code != 0) { std::cerr << std::flush << "Error loading firmware. Error code: " << error_code << std::endl; libusb_release_interface(dev_handle, 0); libusb_close(dev_handle); libusb_exit(ctx); return ~0; } /* Let the device re-enumerate. */ libusb_release_interface(dev_handle, 0); libusb_close(dev_handle); dev_handle = libusb_open_device_with_vid_pid(ctx, FX3_VID, FX3_REENUM_PID); if(dev_handle == NULL) { std::cerr << "Cannot open device with vid: " << vid << " and pid: " << pid << std::endl; return -1; } else { std::cout << "Detected in-progress init of B2xx..." << std::flush; } //libusb_free_device_list(devs, 1); libusb_claim_interface(dev_handle, 0); std::cout << " Reenumeration complete, Device claimed..." << std::endl; /* Now, initialize the device. */ byte_vector_t bytes(8); bytes[0] = 0x43; bytes[1] = 0x59; bytes[2] = 0x14; bytes[3] = 0xB2; bytes[4] = (B2XX_PID & 0xff); bytes[5] = (B2XX_PID >> 8); bytes[6] = (B2XX_VID & 0xff); bytes[7] = (B2XX_VID >> 8); write_eeprom(dev_handle, 0x0, 0x0, bytes); std::cout << "EEPROM initialized, resetting device..." << std::endl << std::endl; /* Reset the device! */ boost::uint8_t data_buffer[1]; fx3_control_write(dev_handle, B2XX_VREQ_FX3_RESET, 0x00, 0x00, data_buffer, 1, 5000); std::cout << "Initialization Process Complete." << std::endl << std::endl; libusb_release_interface(dev_handle, 0); libusb_close(dev_handle); libusb_exit(ctx); return 0; } dev_handle = libusb_open_device_with_vid_pid(ctx, vid, pid); if(dev_handle == NULL) { std::cerr << "Cannot open device with vid: " << vid << " and pid: " << pid << std::endl; return -1; } else { std::cout << "Reactor Core Online..." << std::flush; } libusb_free_device_list(devs, 1); /* Find out if kernel driver is attached, and if so, detach it. */ if(libusb_kernel_driver_active(dev_handle, 0) == 1) { std::cout << " Competing Driver Identified... " << std::flush; if(libusb_detach_kernel_driver(dev_handle, 0) == 0) { std::cout << " Competing Driver Destroyed!" << std::flush; } } /* Claim interface 0 of device. */ error_code = (libusb_error) libusb_claim_interface(dev_handle, 0); std::cout << " All Systems Nominal..." << std::endl << std::endl; boost::uint8_t data_buffer[16]; memset(data_buffer, 0x0, sizeof(data_buffer)); if (vm.count("speed")){ error_code = fx3_control_read(dev_handle, B2XX_VREQ_GET_USB, 0x00, 0x00, data_buffer, 1, 5000); boost::uint8_t speed = boost::lexical_cast(data_buffer[0]); std::cout << "Currently operating at USB " << (int) speed << std::endl; } else if (vm.count("reset-device")) { error_code = fx3_control_write(dev_handle, B2XX_VREQ_FX3_RESET, 0x00, 0x00, data_buffer, 1, 5000); } else if (vm.count("reset-fpga")) { error_code = fx3_control_write(dev_handle, B2XX_VREQ_FPGA_RESET, 0x00, 0x00, data_buffer, 1, 5000); } else if (vm.count("load-fw")) { error_code = (libusb_error) fx3_load_firmware(dev_handle, fw_file); if(error_code != 0) { std::cerr << std::flush << "Error loading firmware. Error code: " << error_code << std::endl; libusb_release_interface(dev_handle, 0); libusb_close(dev_handle); libusb_exit(ctx); return ~0; } std::cout << "Firmware load complete, releasing USB interface..." << std::endl; } else if (vm.count("load-fpga")) { error_code = (libusb_error) load_fpga(dev_handle, fpga_file); if(error_code != 0) { std::cerr << std::flush << "Error loading FPGA. Error code: " << error_code << std::endl; libusb_release_interface(dev_handle, 0); libusb_close(dev_handle); libusb_exit(ctx); return ~0; } std::cout << "FPGA load complete, releasing USB interface..." << std::endl; } else { std::cout << boost::format("B2xx Utilitiy Program %s") % desc << std::endl; libusb_release_interface(dev_handle, 0); libusb_close(dev_handle); libusb_exit(ctx); return ~0; } std::cout << std::endl << "Reactor Shutting Down..." << std::endl; error_code = (libusb_error) libusb_release_interface(dev_handle, 0); libusb_close(dev_handle); libusb_exit(ctx); return 0; }