Skip to content

Commit

Permalink
fix: adapt to use 4bytes address to dump more data (#162)
Browse files Browse the repository at this point in the history
* fix: adapt to use 4bytes address to dump more data

* fix: refactor pep8

* fix: refactor folder name

* fix: rename folder

* fix: pep8 requirement and refactoring

* fix: add argument parser and transforms in class format

* feat: add select spi1 or 2

* feat: add check of id
  • Loading branch information
dummys committed Feb 15, 2024
1 parent 8beb06b commit cc74074
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 171 deletions.
171 changes: 0 additions & 171 deletions contrib/hydra_spi_dump/hydra_spi_dump.py

This file was deleted.

File renamed without changes.
217 changes: 217 additions & 0 deletions contrib/hydra_spiflash_nor_dump/hydra_spiflash_nor_dump.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#!/usr/bin/python3

# Script to dump SPI flash chips with Hydrabus, tested with Hydrabus hardware v1.0 and firmware v0.8 beta
# Based on https://github.com/hydrabus/hydrafw/wiki/HydraFW-Binary-SPI-mode-guide
#
# Author: Pedro Ribeiro <[email protected]>
# Author: Jonathan Borgeaud <[email protected]>
# License: GPLv3 (https://choosealicense.com/licenses/gpl-3.0/)
#
import sys
import argparse
import serial

import signal

SECTORE_SIZE = 0x1000 # max buffer supported by hydrabus


def signal_handler(signal, frame):
print("CTRL+C pressed, cleaning up and exiting")


class HydrabusSpiFlash:

def __init__(self, serial_port):

self.top_parser = argparse.ArgumentParser(add_help=False)
self.top_parser.add_argument('--spi',
default=1,
help='Set the SPI port to SPI1 or SPI2',
metavar='{1,2}')

parser = argparse.ArgumentParser(
parents=[self.top_parser],
description='Tool to query NOR memory flash with Hydrabus',
epilog='This script requires python 3.7+, and serial',
usage='''hydra_spiflash_nor_dump.py <command> [<args>] [OPTIONS]
Commands are:
get_chip_id Return the chip identification
dump <dump_file> <n_4k_sectors> <hex_address> Dump the flash in <dump_file> starting at <hex_address>
''')

parser.add_argument('command',
help='dump to dump the flash or get_chip_id')

args_spi = parser.parse_args()
args = parser.parse_args(sys.argv[1:2])
if not hasattr(self, args.command):
print('Unrecognized command')
parser.print_help()
sys.exit(1)

self.serial_port = serial_port
self.hydrabus = None
self.setup(args_spi.spi)

# use dispatch pattern to invoke method with same name
getattr(self, args.command)()

def error(self, message):
print(message)
self.cleanup()
sys.exit(1)

def hex_to_bin(self, num, padding):
return num.to_bytes(padding, byteorder='big')

def calc_hex_addr(self, addr, add, addr_len):
byte_arr = self.hex_to_bin(addr + add, addr_len)
return byte_arr

def setup(self, spi):

# Open serial port
self.hydrabus = serial.Serial(self.serial_port, 115200)

# Open binary mode
for _ in range(20):
self.hydrabus.write(b'\x00')
if b"BBIO1" not in self.hydrabus.read(5):
self.error(
"Could not get into binary mode, try again or reset hydrabus.")

self.hydrabus.reset_input_buffer()

# Switching to SPI mode
self.hydrabus.write(b'\x01')
if b"SPI1" not in self.hydrabus.read(4):
self.error("Cannot set SPI mode, try again or reset hydrabus.")

if spi == 1:
# Configure SPI port (default polarity and clock phase, SPI1 device)
self.hydrabus.write(b'\x81')
else:
# Configure SPI port (default polarity and clock phase, SPI2 device)
self.hydrabus.write(b'\x80')
if b'\x01' not in self.hydrabus.read(1):
self.error(
"Cannot set SPI device settings, try again or reset hydrabus.")

def cleanup(self):

# Return to main binary mode
self.hydrabus.write(b'\x00')

# reset to console mode
self.hydrabus.write(b'\x0F\n')

def get_chip_id(self):

print("Sending RDID command...")
# write-then-read: write one byte, read 3 (rdid)
self.hydrabus.write(b'\x04\x00\x01\x00\x03')

# send rdid byte (0x9f)
self.hydrabus.write(b'\x9f')
if b'\x01' not in self.hydrabus.read(1):
self.error('Oups something went wrong...')

chip_id = self.hydrabus.read(3)
if int.from_bytes(chip_id) == 0:
print('Cannot read get chip ID')

else:
print(f"Chip ID: 0x{chip_id.hex()}")
print("Finished get_chip_id function.")
self.cleanup()
sys.exit(0)

def dump(self):
parser = argparse.ArgumentParser(description='Dump the NOR flash')

parser.add_argument('dump_file')
parser.add_argument('n_4k_sectors', type=int)
parser.add_argument('hex_address', help='Starting address')
parser.add_argument('--speed',
action='store',
help='Slow(320Khz) or fast(10.5Mhz)')
args = parser.parse_args(sys.argv[2:])
if '0x' in args.hex_address:
# we get hexadecimal address, need convert
start_address = int(args.hex_address, 16)
else:
start_address = int(args.hex_address)

if args.speed == 'slow':
# Set spi speed to 320 Khz
self.hydrabus.write(b'\x60')
else:
# Set spi speed to 10.5 mHz, a conservative fast speed
self.hydrabus.write(b'\x61')
print("Using fast mode (10.5 mHz) to dump the chip.")
if b'\x01' not in self.hydrabus.read(1):
self.error("Cannot set SPI speed, try again or reset hydrabus.")

print('Starting to read chip...')
print('Reading ' + str(args.n_4k_sectors) + ' sectors')

sector = 0

with open(args.dump_file, 'wb+') as dst:

print(f"sectors: {args.n_4k_sectors}: {SECTORE_SIZE}")
if args.n_4k_sectors * SECTORE_SIZE > 0xffffff:
print("Size is bigger than 3bytes address, using 0x13 command")
# size is bigger than 3bytes address so use the 0x13 command
while sector < args.n_4k_sectors:
# write-then-read: write 5 bytes
# (1 read cmd + 4 read addr), read SECTORE_SIZE bytes
self.hydrabus.write(b'\x04\x00\x05' +
self.hex_to_bin(SECTORE_SIZE, 2))

# read 4bytes address command (\x13) and address
self.hydrabus.write(b'\x13' + self.calc_hex_addr(
start_address, sector * SECTORE_SIZE, 4))

# Hydrabus will send \x01 in case of success...
if b'\x01' not in self.hydrabus.read(1):
self.error("Cannot read at address...")

# ...followed by SECTORE_SIZE read bytes
dst.write(self.hydrabus.read(SECTORE_SIZE))
print('Read sector ' + str(sector))
sector += 1
else:
# the size is less than 3bytes address so use the 0x03 command
while sector < args.n_4k_sectors:
# write-then-read: write 4 bytes
# (1 read cmd + 3 read addr), read SECTORE_SIZE bytes
self.hydrabus.write(b'\x04\x00\x04' +
self.hex_to_bin(SECTORE_SIZE, 2))

# read 4bytes address command (\x03) and address
self.hydrabus.write(b'\x03' + self.calc_hex_addr(
start_address, sector * SECTORE_SIZE, 3))

# Hydrabus will send \x01 in case of success...
if b'\x01' not in self.hydrabus.read(1):
self.error("Cannot read at address...")

# ...followed by SECTORE_SIZE read bytes
dst.write(self.hydrabus.read(SECTORE_SIZE))
print('Read sector ' + str(sector))
sector += 1

print('Finished dumping to ' + args.dump_file)
self.cleanup()


if __name__ == '__main__':

# disable this for debugging
# signal.signal(signal.SIGINT, signal_handler)

hydra = HydrabusSpiFlash("/dev/ttyACM0")

0 comments on commit cc74074

Please sign in to comment.