Пример #1
0
def main():

    # Set up a simple argument parser.
    parser = GreatFETArgumentParser(
        description=
        "Utility for loading runtime extensions on to a GreatFET board.")
    parser.add_argument(
        '--m0',
        dest="m0",
        type=argparse.FileType('rb'),
        metavar='<filename>',
        help="loads the provided loadable file to run on the GreatFET's m0 core"
    )

    args = parser.parse_args()
    log_function = parser.get_log_function()
    device = parser.find_specified_device()

    if not args.m0:
        parser.print_help()
        sys.exit(-1)

    if args.m0:
        data = args.m0.read()

        log_function(
            "Loading {} byte loadable onto the M0 coprocessor.\n".format(
                len(data)))
        device.m0.run_loadable(data)
Пример #2
0
def main():
    # Set up a simple argument parser.
    parser = GreatFETArgumentParser(
        dfu=True,
        description="Utility for flashing firmware on GreatFET boards")
    parser.add_argument('-a',
                        '--address',
                        metavar='<n>',
                        type=int,
                        help="starting address (default: 0)",
                        default=0)
    parser.add_argument(
        '-l',
        '--length',
        metavar='<n>',
        type=int,
        help="number of bytes to read (default: {})".format(MAX_FLASH_LENGTH),
        default=MAX_FLASH_LENGTH)
    parser.add_argument('-r',
                        '--read',
                        dest='read',
                        metavar='<filename>',
                        type=str,
                        help="Read data into file",
                        default='')
    parser.add_argument('-w',
                        '--write',
                        dest='write',
                        metavar='<filename>',
                        type=str,
                        help="Write data from file",
                        default='')
    parser.add_argument(
        '-R',
        '--reset',
        dest='reset',
        action='store_true',
        help="Reset GreatFET after performing other operations.")
    args = parser.parse_args()

    # Validate our options.

    # If we don't have an option, print our usage.
    if not any((
            args.read,
            args.write,
            args.reset,
    )):
        parser.print_help()
        sys.exit(0)

    # Determine whether we're going to log to the stdout, or not at all.
    log_function = parser.get_log_function()

    # If we're supposed to install firmware via a DFU stub, install it first.
    if args.dfu:
        try:
            load_dfu_stub(args)
        except DeviceNotFoundError:
            print("Couldn't find a GreatFET-compatible board in DFU mode!",
                  file=sys.stderr)
            sys.exit(errno.ENODEV)

    # Create our GreatFET connection.
    log_function("Trying to find a GreatFET device...")
    device = parser.find_specified_device()
    log_function("{} found. (Serial number: {})".format(
        device.board_name(), device.serial_number()))

    # Ensure that the device supports an onboard SPI flash.
    try:
        device.onboard_flash
    except AttributeError:
        print(
            "The attached GreatFET ({}) doesn't appear to have an SPI flash to program!"
            .format(device.board_name()),
            file=sys.stderr)
        sys.exit(errno.ENOSYS)

    # If we have a write command, write first, to match the behavior of hackrf_spiflash.
    if args.write:
        log_function("Writing data to SPI flash...")
        spi_flash_write(device, args.write, args.address, log_function)
        log_function("Write complete!")
        if not (args.reset or args.dfu):
            log_function(
                "Reset not specified; new firmware will not start until next reset."
            )

    # Handle any read commands.
    if args.read:
        log_function("Reading data from SPI flash...")
        spi_flash_read(device, args.read, args.address, args.length,
                       log_function)
        log_function("Read complete!")

    # Finally, reset the target
    if args.reset or args.dfu:
        log_function("Resetting GreatFET...")
        device.reset(reconnect=False)
        log_function("Reset complete!")
Пример #3
0
def main():

    # Simple type-arguments for parsing.
    int_from_msps = lambda x: from_eng_notation(
        x, units=['Hz', 'SPS'], to_type=int)

    # Set up our argument parser.
    parser = GreatFETArgumentParser(
        description="Logic analyzer implementation for GreatFET",
        verbose_by_default=True)
    parser.add_argument(
        '-o',
        '-b',
        '--binary',
        dest='binary',
        metavar='<filename>',
        type=str,
        help="Write the raw samples captured to a file with the provided name."
    )
    parser.add_argument(
        '-p',
        '--pulseview',
        '--sigrok',
        dest='pulseview',
        metavar="<filename>",
        type=str,
        help=
        "Generate a Sigrok/PulseView session file, and write it to the provided filename."
    )
    parser.add_argument('-f',
                        '--samplerate',
                        metavar='samples_per_second',
                        type=int_from_msps,
                        default=17000000,
                        dest='sample_rate',
                        help='samples to capture per second')
    parser.add_argument('-n',
                        '--num-channels',
                        metavar='channels',
                        type=int,
                        default=8,
                        dest='bus_width',
                        help='the number of channels to capture')
    parser.add_argument(
        '-B',
        '--second-bank',
        action='store_const',
        const=8,
        default=0,
        dest='first_pin',
        help=
        "Provide this option to capture from SGPIO8 up, rather than from SGPIO0 up."
    )
    parser.add_argument(
        '-O',
        '--stdout',
        dest='write_to_stdout',
        action='store_true',
        help=
        'Provide this option to write the raw binary samples to the standard out. Implies -q.'
    )
    parser.add_argument(
        '--rhododendron',
        dest='rhododendron',
        action='store_true',
        help=
        'Capture raw packets for e.g. low or full speed USB using the Rhodadendon neighbor.'
    )
    parser.add_argument(
        '--raw-usb',
        dest='raw_usb',
        action='store_true',
        help=
        'Capture raw packets for e.g. low or full speed USB on SGPIO0 and SGPIO1.'
    )
    parser.add_argument(
        '--debug-sgpio',
        dest='debug_sgpio',
        action='store_true',
        help=
        'Developer option for debugging; dumps the SGPIO configuration before starting.'
    )
    parser.add_argument(
        '--stats',
        dest='print_stats',
        action='store_true',
        help='Print capture statistics after the transfer is complete.')

    # And grab our GreatFET.
    args = parser.parse_args()

    # If we're writing binary samples directly to stdout, don't emit logs to stdout; otherwise, honor the
    # --quiet flag.
    if args.write_to_stdout:
        log_function = log_silent
    else:
        log_function = parser.get_log_function()

    # Ensure we have at least one write operation.
    if not (args.pulseview or args.binary or args.write_to_stdout):
        parser.print_help()
        sys.exit(-1)

    # Capture a few of the arguments.
    sample_rate = args.sample_rate
    bus_width = args.bus_width

    channel_names = None

    # If we have one of the raw-usb options, apply their settings.
    if args.raw_usb or args.rhododendron:
        sample_rate = int(51e6)
        bus_width = 2
        channel_names = {0: 'D-', 1: 'D+'}

    if args.rhododendron:
        args.first_pin = 8

    # Find our GreatFET.
    device = parser.find_specified_device()

    # Configure which locations we're using for SGPIO8/9.
    device.apis.logic_analyzer.configure_alt_mappings(args.rhododendron)

    # Set the first pin in our capture according to our bank setting.
    device.apis.logic_analyzer.change_first_pin(args.first_pin)

    # Replace the sample rate with the actual achieved sample rate.
    sample_rate, buffer_size, endpoint = device.apis.logic_analyzer.configure(
        sample_rate, bus_width)

    # If we've been asked to dump SGPIO debug info, do so.
    if args.debug_sgpio:
        device.apis.logic_analyzer.dump_sgpio_configuration(False)
        print(device.read_debug_ring(), file=sys.stderr)
        sys.stderr.flush()

    # Print what we're doing and our status.
    log_function("Sampling {} channels at {}Hz.".format(
        bus_width, eng_notation(sample_rate)))
    log_function("Press Ctrl+C to stop reading data from device.")

    # If we have a target binary file, open the target filename and use that to store samples.
    if args.binary:
        bin_file = open(args.binary, 'wb')
        bin_file_name = args.binary

    # Otherwise, create an temporary file and use that. (It's automatically destroyed on close, which is fancy.)
    elif args.pulseview:
        try:
            holding_dir = os.path.dirname(os.path.abspath(args.pulseview))
        except:
            holding_dir = None

        bin_file = tempfile.NamedTemporaryFile(dir=holding_dir)
        bin_file_name = bin_file.name

    # Create queues of transfer objects that we'll use as a producer/consumer interface for our comm thread.
    empty_buffers = []
    full_buffers = []

    # Allocate a set of transfer buffers, so we don't have to continuously allocate them.
    for _ in range(DEFAULT_PREALLOCATED_BUFFERS):
        empty_buffers.append(allocate_transfer_buffer(buffer_size))

    # Finally, spawn the thread that will handle our data processing and output.
    termination_request = threading.Event()
    thread_arguments = (termination_request, args, bus_width, bin_file,
                        empty_buffers, full_buffers)
    data_thread = threading.Thread(target=background_process_data,
                                   args=thread_arguments)

    # Now that we're done with all of that setup, perform our actual sampling, in a tight loop,
    data_thread.start()
    device.apis.logic_analyzer.start()
    start_time = time.time()

    try:
        while True:

            # Grab a transfer buffer from the empty list...
            try:
                transfer_buffer = empty_buffers.pop()
            except IndexError:
                # If we don't have a buffer to fill, allocate a new one. It'll wind up in our buffer pool shortly.
                transfer_buffer = allocate_transfer_buffer(buffer_size)

            # Capture data from the device, and unpack it.
            device.comms.device.read(endpoint, transfer_buffer, 3000)

            # ... and pop it into the to-be-processed queue.
            full_buffers.append(transfer_buffer)

    except KeyboardInterrupt:
        pass
    except usb.core.USBError as e:
        log_error("")
        if e.errno == 32:
            log_error(
                "ERROR: Couldn't pull data from the device fast enough! Aborting."
            )
            log_error(
                "(Lowering the sample rate may help. Sometimes, switching to another USB bus / port may help.)"
            )
        else:
            log_error(
                "ERROR: Communications failure -- check the connection to -- and state of  -- the GreatFET. "
            )
            log_error(
                "(More debug information may be available if you run 'gf dmesg')."
            )
            log_error(e)
    finally:
        elapsed_time = time.time() - start_time

        # No matter what, once we're done stop the device from sampling.
        device.apis.logic_analyzer.stop()

        # Signal to our data processing thread that it's time to terminate.
        termination_request.set()

    # Wait for our data processing thread to complete.
    log_function('')
    log_function(
        'Capture terminated -- waiting for data processing to complete.')
    data_thread.join()

    # Flush whatever data we've read to disk, so it can be correctly read by subsequent operations.
    if args.pulseview:
        bin_file.flush()

    # Finally, generate our output.
    if args.binary:
        log_function("Binary data written to file '{}'.".format(args.binary))
    if args.pulseview:
        emit_sigrok_file(args.pulseview, bin_file_name, bus_width, sample_rate,
                         args.first_pin, channel_names)
        log_function(
            "Sigrok/PulseView compatible session file created: '{}'.".format(
                args.pulseview))

    # Print how long we sampled for, as a nicety.
    log_function("Sampled for {} seconds.".format(round(elapsed_time, 4)))
Пример #4
0
def main():

    # Start off with a default packet state.
    current_packet_type = None
    current_packet_data = array.array('B')
    current_packet_remaining = 0
    next_data_packet_emit_after = []
    next_data_packet_timestamp = None

    current_usb_data = array.array('B')

    def emit_usb_packet(packet_data):
        """
        Emits a raw USB packet to the target format.
        """

        print("Packet: [{}]".format(packet_data))

    def is_valid_pid_byte(byte):
        """ Returns true iff the given byte could be a valid PID. """

        pid = byte & 0xf
        inverse = byte >> 4

        return (pid ^ inverse) == 0xf

    def hack_smoothe_out_jitter(packet_data, emit_point):
        """ XXX Horrorhack intended to "smoothe" over a missing firmware piece until it's implemented. """

        try:
            print("next PID would be {} -- valid: {}".format(
                packet_data[emit_point],
                is_valid_pid_byte(packet_data[emit_point])))

            # If the next byte is a valid PID, there's no need to hack anything.
            # Move along..
            if is_valid_pid_byte(packet_data[emit_point]):
                return 0
        except IndexError:
            pass

        print("trying to smoothe out a bit of jitter:")

        try:

            print("trying delta -1 [{}] -- {}".format(
                packet_data[emit_point - 1],
                is_valid_pid_byte(packet_data[emit_point - 1])))

            # Otherwise, if the previous byte was a valid PID, move back to it.
            if is_valid_pid_byte(packet_data[emit_point - 1]):
                return -1
        except IndexError:
            pass

        try:

            print("trying delta +1 [{}] -- {}".format(
                packet_data[emit_point + 1],
                is_valid_pid_byte(packet_data[emit_point + 1])))

            # Otherwise, if the previous byte was a valid PID, move back to it.
            if is_valid_pid_byte(packet_data[emit_point + 1]):
                return 1
        except IndexError:
            pass

        return 0

    def handle_capture_packet(packet_type, packet_data):
        """
        Handles a received full packet from the analyzer.
        """

        nonlocal current_packet_type, current_packet_data, current_packet_remaining
        nonlocal next_data_packet_emit_after, next_data_packet_timestamp, current_usb_data

        # If this is an "end event" packet, grab the point at which
        # we're supposed to emit the packet.
        if packet_type == PACKET_TYPE_EVENT_END_OK:

            if (not next_data_packet_emit_after) or (
                    next_data_packet_emit_after[-1] != packet_data[0]):
                next_data_packet_emit_after.append(packet_data[0])

        # If this is start packet, grab the timestamp from it.
        elif packet_type == PACKET_TYPE_EVENT_START:

            # FIXME: implement
            pass

        # If this is a USB data packet, handle it.
        elif packet_type == PACKET_TYPE_USB_DATA:
            print("got {} bytes of USB data [{}] <emit at: {}>".format(
                len(packet_data), packet_data, next_data_packet_emit_after))

            existing_packet_length = len(current_usb_data)

            # Add the data to the current packet.
            current_usb_data.extend(packet_data)

            # Store that we're handled 0 bytes into the current packet.
            position_in_packet = 0

            # If this packet ends a USB packet, emit the completed usb packet.
            while next_data_packet_emit_after:

                emit_after_bytes = next_data_packet_emit_after.pop(
                    0) - position_in_packet + 1

                print("emit after: {} bytes [data: {}]".format(
                    emit_after_bytes, current_usb_data))

                # Emit the USB packet up until this point...
                emit_point = existing_packet_length + emit_after_bytes

                # XXX: Temporary hack to smoothe out single-byte event offsets until
                # the firmware properly has NXT and DIR tied to an SCT counter.
                delta = hack_smoothe_out_jitter(current_usb_data, emit_point)
                emit_point += delta
                emit_after_bytes += delta

                emit_usb_packet(current_usb_data[0:emit_point])

                # ... and mark ourselves as already having emitted the relevant bytes, so
                # the future "emit afters" can be scaled properly.
                position_in_packet += emit_after_bytes

                # ... and remove those packets from the buffer.
                del current_usb_data[0:emit_point]

            next_data_packet_emit_after = []

    def parse_capture_packets(samples, args, bin_file):
        """
        Parses a set of packets coming from a USB capture device.
        """

        nonlocal current_packet_type, current_packet_data, current_packet_remaining
        nonlocal next_data_packet_emit_after, next_data_packet_timestamp, current_usb_data

        # Parse all of the samples we have in our buffer.
        while samples:

            sample = samples.pop(0)
            #print("sample: {} / current_packet_remaining: {}".format(sample, current_packet_remaining))

            # If we have data remaining in our packet, parse this sample as data.
            if current_packet_remaining:
                current_packet_data.append(sample)
                current_packet_remaining -= 1

                # If we just completed a given packet, handle it.
                if current_packet_remaining == 0:
                    handle_capture_packet(current_packet_type,
                                          current_packet_data)

                    # Clear out our packet state.
                    del current_packet_data[:]

            # Otherwise, handle this as a new packet.
            elif (current_packet_remaining == 0) and (sample in PACKET_SIZES):
                current_packet_type = sample
                current_packet_remaining = PACKET_SIZES[current_packet_type] - 1
            else:
                raise IOError(
                    "unknown packet type {}! stream error?\n".format(sample))

    # Set up our argument parser.
    parser = GreatFETArgumentParser(
        description="Simple Rhododendron capture utility for GreatFET.",
        verbose_by_default=True)
    parser.add_argument(
        '-o',
        '-b',
        '--binary',
        dest='binary',
        metavar='<filename>',
        type=str,
        help="Write the raw samples captured to a file with the provided name."
    )
    parser.add_argument(
        '--m0',
        dest="m0",
        type=argparse.FileType('rb'),
        metavar='<filename>',
        help=
        "loads the specific m0 coprocessor 'loadable' instead of the default Rhododendron one"
    )
    parser.add_argument('-F',
                        '--full-speed',
                        dest='speed',
                        action='store_const',
                        const=SPEED_FULL,
                        default=SPEED_HIGH,
                        help="Capture full-speed data.")
    parser.add_argument('-L',
                        '--low-speed',
                        dest='speed',
                        action='store_const',
                        const=SPEED_LOW,
                        default=SPEED_HIGH,
                        help="Capture low-speed data.")
    parser.add_argument('-H',
                        '--high-speed',
                        dest='speed',
                        action='store_const',
                        const=SPEED_HIGH,
                        help="Capture high-speed data. The default.")
    parser.add_argument(
        '-O',
        '--stdout',
        dest='write_to_stdout',
        action='store_true',
        help=
        'Provide this option to log the received data to the stdout.. Implies -q.'
    )

    # And grab our GreatFET.
    args = parser.parse_args()

    # If we're writing binary samples directly to stdout, don't emit logs to stdout; otherwise, honor the
    # --quiet flag.
    if args.write_to_stdout:
        log_function = log_silent
    else:
        log_function = parser.get_log_function()

    # Ensure we have at least one write operation.
    if not (args.binary or args.write_to_stdout):
        parser.print_help()
        sys.exit(-1)

    # Find our GreatFET.
    device = parser.find_specified_device()

    # Bring our Rhododendron board online; and capture communication parameters.
    buffer_size, endpoint = device.apis.usb_analyzer.initialize(
        args.speed, timeout=10000, comms_timeout=10000)

    # $Load the Rhododendron firmware loadable into memory...
    try:
        if args.m0:
            data = args.m0.read()
        else:
            data = read_rhododendron_m0_loadable()
    except (OSError, TypeError):
        log_error("Can't find a Rhododendron m0 program to load!")
        log_error("We can't run without one.")
        sys.exit(-1)

    # Debug only: setup a pin to track when we're handling SGPIO data.
    debug_pin = device.gpio.get_pin('J1_P3')
    debug_pin.set_direction(debug_pin.DIRECTION_OUT)

    # ... and then run it on our m0 coprocessor.
    device.m0.run_loadable(data)

    # Print what we're doing and our status.
    log_function("Reading raw {}-speed USB data!\n".format(
        SPEED_NAMES[args.speed]))
    log_function("Press Ctrl+C to stop reading data from device.")

    # If we have a target binary file, open the target filename and use that to store samples.
    bin_file = None
    if args.binary:
        bin_file = open(args.binary, 'wb')
        bin_file_name = args.binary

    # Now that we're done with all of that setup, perform our actual sampling, in a tight loop,
    device.apis.usb_analyzer.start_capture()

    transfer_buffer = allocate_transfer_buffer(buffer_size)

    total_captured = 0

    try:
        while True:

            # Capture data from the device, and unpack it.
            try:
                new_samples = device.comms.device.read(
                    endpoint, transfer_buffer, SAMPLE_DELIVERY_TIMEOUT_MS)
                samples = transfer_buffer[0:new_samples - 1]

                total_captured += new_samples
                log_function("Captured {} bytes.".format(total_captured),
                             end="\r")

                parse_capture_packets(samples, args, bin_file)

            except usb.core.USBError as e:
                if e.errno != errno.ETIMEDOUT:
                    raise

    except KeyboardInterrupt:
        pass
    except usb.core.USBError as e:
        log_error("")
        if e.errno == 32:
            log_error(
                "ERROR: Couldn't pull data from the device fast enough! Aborting."
            )
        else:
            log_error(
                "ERROR: Communications failure -- check the connection to -- and state of  -- the GreatFET. "
            )
            log_error(
                "(More debug information may be available if you run 'gf dmesg')."
            )
            log_error(e)
    finally:

        # No matter what, once we're done stop the device from sampling.
        device.apis.usb_analyzer.stop_capture()

        if args.binary:
            log_function("Binary data written to file '{}'.".format(
                args.binary))
Пример #5
0
def main():

    # Grab any GreatFET assets that should have shipped with the tool.
    dfu_stub_path = find_greatfet_asset('flash_stub.bin')
    auto_firmware_path = find_greatfet_asset("greatfet_usb.bin")

    # Set up a simple argument parser.-
    parser = GreatFETArgumentParser(
        dfu=True,
        verbose_by_default=True,
        description="Utility for flashing firmware on GreatFET boards")
    parser.add_argument('-a',
                        '--address',
                        metavar='<n>',
                        type=int,
                        help="starting address (default: 0)",
                        default=0)
    parser.add_argument(
        '-l',
        '--length',
        metavar='<n>',
        type=int,
        default=None,
        help=
        "number of bytes to read; if not specified, we try to read the programmed sections"
    )
    parser.add_argument('-r',
                        '--read',
                        dest='read',
                        metavar='<filename>',
                        type=str,
                        help="Read data into file",
                        default='')
    parser.add_argument('-w',
                        '--write',
                        dest='write',
                        metavar='<filename>',
                        type=str,
                        help="Write data from file",
                        default='')
    parser.add_argument(
        '-R',
        '--reset',
        dest='reset',
        action='store_true',
        help="Reset GreatFET after performing other operations.")
    parser.add_argument(
        '-V',
        '--volatile-upload',
        dest='volatile',
        metavar="<filename>",
        type=str,
        help=
        "Uploads a GreatFET firmware image to RAM via DFU mode. Firmware is not flashed."
    )

    # If we have the ability to automatically install firmware, provide that as an option.
    if auto_firmware_path:
        parser.add_argument(
            '--autoflash',
            action='store_true',
            dest='autoflash',
            help=
            "Automatically flash the attached board with the firmware corresponding to the installed tools."
        )
        parser.add_argument(
            '-U',
            '--volatile-upload-auto',
            dest='volatile_auto',
            action='store_true',
            help=
            "Automatically upload the tools' firmware via DFU mode. Firmware is not flashed."
        )

    args = parser.parse_args()

    # If we're trying to automatically flash the given firmware, set the relevant options accordingly.
    try:
        if not args.write and args.autoflash:
            args.write = auto_firmware_path
            args.reset = True
    except AttributeError:
        pass

    try:
        if not args.volatile and args.volatile_auto:
            args.volatile = auto_firmware_path
    except AttributeError:
        pass

    # Validate our options.

    # If we don't have an option, print our usage.
    if not any((args.read, args.write, args.reset, args.volatile)):
        parser.print_help()
        sys.exit(0)

    # Determine whether we're going to log to the stdout, or not at all.
    log_function = parser.get_log_function()

    if args.dfu_stub:
        dfu_stub_path = args.dfu_stub

    # If we're uploading a file via DFU for a "volatile" flash, do so and abort.
    if args.volatile:

        try:
            device = LPC43xxTarget()
        except BoardNotFoundError:
            print("Couldn't find a GreatFET-compatible board in DFU mode!",
                  file=sys.stderr)
            sys.exit(errno.ENODEV)

        log_function("Uploading data to RAM...\n")
        dfu_upload(device, args.volatile, log_function)
        sys.exit(0)

    # If we're supposed to install firmware via a DFU stub, install it first.
    if args.dfu:
        try:
            load_dfu_stub(dfu_stub_path)
        except DeviceNotFoundError:
            print("Couldn't find a GreatFET-compatible board in DFU mode!",
                  file=sys.stderr)
            sys.exit(errno.ENODEV)

    # Create our GreatFET connection.
    log_function("Trying to find a GreatFET device...")
    device = parser.find_specified_device()
    log_function("{} found. (Serial number: {})".format(
        device.board_name(), device.serial_number()))

    # Ensure that the device supports an onboard SPI flash.
    try:
        device.onboard_flash
    except AttributeError:
        print(
            "The attached GreatFET ({}) doesn't appear to have an SPI flash to program!"
            .format(device.board_name()),
            file=sys.stderr)
        sys.exit(errno.ENOSYS)

    # If we have a write command, write first, to match the behavior of hackrf_spiflash.
    if args.write:
        log_function("Writing data to SPI flash...\n")
        spi_flash_write(device, args.write, args.address, log_function)
        log_function("Write complete!")
        if not (args.reset or args.dfu):
            log_function(
                "Reset not specified; new firmware will not start until next reset."
            )

    # Handle any read commands.
    if args.read:
        log_function("Reading data from SPI flash...\n")
        spi_flash_read(device, args.read, args.address, args.length,
                       log_function)
        log_function("Read complete!")

    # Finally, reset the target
    if args.reset or args.dfu:
        log_function("Resetting GreatFET...")
        device.reset(reconnect=False, is_post_firmware_flash=bool(args.write))
        log_function("Reset complete!")