Commit b6c633a6 authored by Theodor-Adrian Stana's avatar Theodor-Adrian Stana

sw: Added mass MultiBoot script

parent a689ff59
......@@ -33,25 +33,12 @@ it is currently writing to. Go grab a coffee, since this will take at least
11 mins, maybe even more if your network connection is not that good, or
you're using Windows
readflash.py
============
mass-multiboot.py
=================
Use the FPGA MultiBoot logic to update the gateware on multiple boards in one crate.
The update can only be done one card per crate and one crate at a time.
This script reads flash pages from a start to an end address, both of these
given by the user. As the multiboot.py script, it relies on the ei2c.py and
defines the same methods for SPI flash access as the multiboot.py script.
Maybe sometime in the future it will make sense to put these methods in a
class within a common module, but for now it works as is.
When you run it, it asks (as multiboot.py) for crate IP and login data, and then
for a start and end address. It then goes and reads the flash data between these
two addresses via the FPGA logic. The output is stored to a local file called
`flash.bin'.
Note that the script reads 256 bytes at a time from the flash, but does not
check if you gave it an address on a flash page boundary. Therefore, if you
don't give it a starting address which is a page boundary, it will output
data which is shifted by the amount of bytes the starting address is shifted
from the start of the page. For example, if you start reading from address
0x000110, the data in flash.bin will be shifted by 16 bytes when compared
to the flash contents.
For details on how to use it, run the help on it:
%> ./mass-multiboot.py --help
\ No newline at end of file
# Author: Theodor Stana <theodor.stana@gmail.com>
# Date: October 2014
#
# 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, you may find one here:
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# or you may search the http://www.gnu.org website for the version 2 license,
# or you may write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
# Communication type definitions
#
# Communication types are used in several places to define how to interact to
# the MultiBoot module (what interface is used to the FPGA) and to the on-board
# flash module (what interface exists between the FPGA and the flash chip).
#
# The scripts that use these definitions are organized as series of 'if'
# branches that execute specific commands based on the communication type being
# used. As an example of usage, grep for ELMA_I2C_MULTIBOOT in the current
# folder to see where it is used and how the specific commands are sent to
# interface to the Flash via the ELMA I2C protocol and the MultiBoot logic
# implemented on the FPGAs of blocking and RS-485 converter boards:
#
# grep -rnH ELMA_I2C_MULTIBOOT .
#
# Browse the code under the if branches, and apply your own code if you are
# implementing a new communication type. Note that inside the constructors of
# the XilMultiboot and FlashM25P classes there are variable parameters passed by
# the protocol type. In these constructors, you should decide on a sequence of
# parameters to be passed to the constructor. This sequence is specific to the
# communication type you are implementing. Then, add parameters specific to this
# new communication type that are to be used along the classes, and implement
# specific 'if' branches for the new communication type. An example of
# communication-type-specific parameters is the elma.write() and elma.read()
# methods, in the case of the ELMA_I2C_MULTIBOOT type.
ELMA_I2C_MULTIBOOT = 0;
YOUR_COMM_TYPE_HERE = 1;
# Author: Theodor Stana <theodor.stana@gmail.com>
# Date: October 2014
#
# 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, you may find one here:
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# or you may search the http://www.gnu.org website for the version 2 license,
# or you may write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
import sys
sys.path.append("../../ei2c")
from ei2c import *
from comm_type import *
class FlashM25P:
# Constructor
# comm_params -- communication parameters defining details about how this
# class should interface to the M25P flash chip
def __init__(self, *comm_params):
self.comm = comm_params[0]
if (comm_params[0] == ELMA_I2C_MULTIBOOT):
self.elma = comm_params[1]
self.slot = comm_params[2]
self.mb_base = comm_params[3]
# Low-level SPI transfer using communication type defined in the constructor
#
# This method handles the OR-ing together of the data bytes and the control
# byte in FAR.
#
# Formatting of data bytes in the FAR register should be done outside this
# function.
def spi_transfer(self, nbytes, cs, dat):
# Communication type is ELMA I2C, therefore use MultiBoot logic on the
# FPGA to access the flash
if (self.comm == ELMA_I2C_MULTIBOOT):
retval = 0
wval = []
# control byte, to be shifted left by 24
ctrl = ((cs << 3) | 0x4) | (nbytes-1)
# Use appropriate command by type
#
# write - we send up to three data bytes in one FAR register write
# writemregs - we send up to 24 data bytes in eight FAR register writes
if isinstance(dat,int):
self.elma.write(self.slot, self.mb_base+0x10, (ctrl << 24) | dat)
else:
for i in xrange(len(dat)):
wval.append((ctrl << 24) | dat[i])
self.elma.writemregs(self.slot, self.mb_base+0x10, wval)
# Read the data and prepare the return value
while (retval & (1 << 28) == 0):
retval = self.elma.read(self.slot, self.mb_base+0x10)
return retval & 0xffffff
# This function is used to write data bytes to flash. It calls self.spi_transfer
# and sends the commands to the flash chip. You can follow the logic of this
# function by looking at the sequence of the write command in [1]
def write(self, addr, dat):
# write enable cmd
self.spi_transfer(1,1,0x06)
self.spi_transfer(1,0,0)
# write page cmd
self.spi_transfer(1,1,0x02)
# send address in reverse order
addr = ((addr & 0xff0000) >> 16) | (((addr & 0xff00) >> 8) << 8) | \
((addr & 0xff) << 16)
self.spi_transfer(3,1,addr)
# Now, massage the data array into 3-byte commands sent in 32-bit
# registers, in batches of eight registers (writemregs command).
i = 0
l = len(dat)
while (l):
wval = []
# If we have more than 24 bytes left in the data array,
# we can use the full writemregs
if (int(l/24)):
for j in xrange(0, 8):
wval.append((dat[(i+2)+3*j] << 16) | \
(dat[(i+1)+3*j] << 8) | dat[i+3*j])
self.spi_transfer(3, 1, wval)
i += 24;
l -= 24;
# When we're left to fewer than 24 bytes in the array,
# try to use one more writemregs with as many regs as possible
elif (int(l/3)):
for j in xrange(0, l/3):
wval.append((dat[(i+2)+3*j] << 16) | \
(dat[(i+1)+3*j] << 8) | dat[i+3*j])
self.spi_transfer(3,1,wval);
i += 3 * int(l/3)
l -= 3 * int(l/3)
# When we're down to less than three bytes, just use simple one-byte xfers
elif (l):
for j in xrange(0, l):
self.spi_transfer(1,1,dat[i])
i += 1
l -= 1
self.spi_transfer(1,0,0)
# This function is used to read a number of data bytes from the flash memory
# It returns the values of the three consecutive flash registers packed into
# one 24-bit data value in little-endian order
def read(self, addr, nrbytes):
ret = []
self.spi_transfer(1,1,0x0b)
# send address in reverse order
addr = ((addr & 0xff0000) >> 16) | (((addr & 0xff00) >> 8) << 8) | \
((addr & 0xff) << 16)
self.spi_transfer(3,1,addr)
self.spi_transfer(1,1,0)
# Read bytes in groups of three
for i in range(nrbytes):
ret.append(self.spi_transfer(1,1,0))
self.spi_transfer(1,0,0)
return ret
# Send a sector erase command
def serase(self, addr):
self.spi_transfer(1,1,0x06)
self.spi_transfer(1,0,0)
self.spi_transfer(1,1,0xD8)
# send address in reverse order
addr = ((addr & 0xff0000) >> 16) | (((addr & 0xff00) >> 8) << 8) | \
((addr & 0xff) << 16)
self.spi_transfer(3,1,addr)
self.spi_transfer(1,0,0)
# Send a block erase command
def berase(self):
self.spi_transfer(1,1,0x06)
self.spi_transfer(1,0,0)
self.spi_transfer(1,1,0xc7)
self.spi_transfer(1,0,0)
# Read status register command
def rsr(self):
self.spi_transfer(1,1,0x05)
ret = self.spi_transfer(1,1,0)
self.spi_transfer(1,0,0)
return ret
# Author: Theodor Stana <theodor.stana@gmail.com>
# Date: October 2014
#
# 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, you may find one here:
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# or you may search the http://www.gnu.org website for the version 2 license,
# or you may write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
import sys
sys.path.append("../../ei2c")
from ei2c import *
from ei2cexcept import *
import time
from functools import partial
from comm_type import *
from flash_m25p import *
#===============================================================================
MB_CR_OFS = 0x00
MB_SR_OFS = 0x04
MB_GBBAR_OFS = 0x08
MB_MBBAR_OFS = 0x0C
MB_FAR_OFS = 0x10
#===============================================================================
class XilMultiboot:
# Constructor
def __init__(self, *param):
if param[0] == ELMA_I2C_MULTIBOOT:
self.comm = param[0]
self.elma = param[1]
self.slot = param[2]
self.mb_base = param[3]
self.bitstream = param[4]
self.flash = FlashM25P(self.comm, self.elma, self.slot, self.mb_base)
#
# Read from flash
#
def read(self, sa, ea):
# Ask for and open bitstream file
fname = raw_input("Output file name: ")
f = open(fname,'wb')
# Read the data and dump to file
dat = []
for i in range(sa, ea, 256):
dat += self.flash.read(i, 256)
print("(r:%d) 0x%06x" % (self.slot, i))
dat = ''.join(map(chr,dat))
f.write(dat)
f.close()
#
# Write to flash
#
def write(self, addr):
print("Writing bitstream...")
# Ask for and open bitstream file
fname = self.bitstream
f = open(fname,'rb')
# Start reading input file 256 bytes at a time and write to flash
for dat in iter(partial(f.read, 256), ''):
dat = [int(d.encode("hex"), 16) for d in dat]
print("(w:%d) 0x%x" % (self.slot, addr))
# Erase on sector boundary
if not (addr % 0x10000):
print('erase')
self.flash.serase(addr)
while (self.flash.rsr() & 0x01):
pass
# Write data to page
self.flash.write(addr, dat)
while (self.flash.rsr() & 0x01):
pass
# increment to next page address
addr += 256
# Close file handle
f.close()
print("DONE!")
#
# Start IPROG sequence
#
def iprog(self, addr):
if (self.comm == ELMA_I2C_MULTIBOOT):
print("Issuing IPROG command to board in slot %d..." % self.slot)
self.elma.write(self.slot, self.mb_base+MB_GBBAR_OFS, 0x44 | (0x0b << 24))
self.elma.write(self.slot, self.mb_base+MB_MBBAR_OFS, addr | (0x0b << 24))
self.elma.write(self.slot, self.mb_base+MB_CR_OFS, 0x10000)
try:
self.elma.write(self.slot, self.mb_base+MB_CR_OFS, 0x20000)
except NAckError:
pass
# Set timeout...
t0 = time.time()
t1 = t0 + 20
# and wait for the FPGA to gracefully respond, or die trying
while (1):
try:
if (time.time() >= t1):
print("Timeout, IPROG unsuccessful!")
break
if ((self.elma.read(self.slot, 0x4) & 0xf0) == 0x00):
print("IPROG unsuccessful, fallback to Golden bitstream occured!")
break
else:
print("IPROG successful!")
break
except NAckError:
continue
#
# Sequence to read FPGA configuration register
#
def rdcfgreg(self):
print("Press 'q' to end config reg readout")
while 1:
try:
reg = raw_input('Address: 0x')
reg = int(reg, 16)
if (reg < 0x00) or (reg > 0x22):
raise ValueError
if (self.comm == ELMA_I2C_MULTIBOOT):
self.elma.write(self.slot, self.mb_base+MB_CR_OFS, (1 << 6) | reg)
val = self.elma.read(self.slot, self.mb_base+MB_SR_OFS)
if (val & (1 << 16)):
print("REG(0x%02X) = 0x%04X" % (reg, val & 0xffff))
else:
print("CFGREGIMG invalid!")
except ValueError:
if (reg == 'q'):
break
print("Please input a hex value in the range [0x00, 0x22] or 'q' to quit")
#!/usr/bin/python
# Author: Theodor Stana <theodor.stana@gmail.com>
# Date: October 2014
#
# 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, you may find one here:
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# or you may search the http://www.gnu.org website for the version 2 license,
# or you may write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
import os
import sys
sys.path.append("../ei2c")
sys.path.append("lib-multiboot")
from ei2cexcept import *
from comm_type import *
from xil_multiboot import *
import binascii
from optparse import OptionParser
GW_DIR = "/acc/local/share/firmware/"
# When you add new board type, search for where "ADDBOARD" appears as a comment
# and make the proper additions there
BOARDIDS = ["TBLO", "T485"]
# ADDBOARD: board ID string under mandatory arguments
USAGE = """Usage: %prog [options] elma-crate board-id
Mandatory:
elma-crate Hostname or IP address of ELMA crate to address
board-id ID of board to program; must be one of
"TBLO" -> CONV-TTL-BLO
"T485" -> CONV-TTL-RS485"""
# Return the list of gateware files at the requested path; each board should
# have a folder under "path" with its specific name. Files under this folder
# with the board's name should be bitstream files only.
def get_gw_list(path, board):
path = path+board+'/'
return [path+f for f in os.listdir(path) if not os.path.isdir(f)]
# Write bitstream and perform IPROG, depending on requested operation.
#
# The write address is inferred from whether the bitstream has a "golden" string
# in it, which denotes a golden bitsream, which should always be programmed from
# address 0x0
def mass_multiboot(write, gw_path, iprog, iprogaddr):
for slot in slots:
# The MultiBoot base address is normally 0x100
mb_base = 0x100
# ... but let's make sure we get the right address on older gateware or
# test gateware of blocking converters
if (boardid == "TBLO"):
gwvers = elma.read(slot, 0x4) & 0xff
if (gwvers == 0xff):
mb_base = 0x300
elif ((gwvers > 0x10) and (gwvers < 0x23)) or \
(((gwvers & 0xf0) == 0x00) and (gwvers < 0x02)):
mb_base = 0x40
# Run MultiBoot
mb = XilMultiboot(ELMA_I2C_MULTIBOOT, elma, slot, mb_base, gw_path)
if (write):
if ("golden" in gw_path):
waddr = 0x0
else:
waddr = iprogaddr
mb.write(waddr)
# Send IPROG command if requested
if (iprog):
mb.iprog(iprogaddr)
# main "function"
if __name__ == "__main__":
parser = OptionParser(usage=USAGE)
parser.add_option("-s", "--start", help="Start running programmed bitstream",
action="store_true", dest="iprogafter")
parser.add_option("-i", "--iprog", help="Run bitstream (IPROG) at a specific address and exit",
action="store", type=int, dest="iprogaddr", default=None)
(opts, args) = parser.parse_args()
if (len(args) < 2):
print("ERROR: wrong or missing arguments")
parser.print_help()
sys.exit(2)
hname = args[0]
board = args[1]
board = board.upper()
elma = EI2C(hname, "", "")
elma.open()
if not (board in BOARDIDS):
print("ERROR: Invalid board ID")
parser.print_help()
sys.exit(2)
# Walk a 17-slot crate to see if any boards of the requested type are found
slots = []
for i in range(1, 18):
try:
boardid = elma.read(i, 0x0);
boardid = binascii.unhexlify("%s" % "{0:x}".format(boardid))
if (boardid == board):
print("Board with ID \"%s\" found in slot %d" % (board, i))
slots.append(i)
except NAckError:
pass
# Exit if slots array is not populated
if (len(slots) == 0):
print("ERROR: no boards found in crate or crate power not turned on")
sys.exit(2)
# New line before babbling more
print("")
# Run IPROG and exit program, as promised by the option
if (opts.iprogaddr != None):
while(1):
inp = raw_input("Are you sure you want to run IPROG on ALL cards above? (y/n) ")
inp = inp.lower()
if (inp == 'y'):
# Run mass MultiBoot without writing, only with IPROG from
# defined address
mass_multiboot(0, None, 1, opts.iprogaddr)
break
elif (inp == 'n'):
break
sys.exit(0)
# Change board name to be according to bitstream folder names
# ADDBOARD: add new if branches with folder names where you store the bitstreams
# and start addresses for the release bitstreams on new FPGAs. Note that
# folder names have to be under the GW_DIR definition above
if (board == "TBLO"):
board = "conv-ttl-blo"
opts.iprogaddr = 0x170000
elif (board == "T485"):
board = "conv-ttl-rs485"
opts.iprogaddr = 0x170000
# Get gateware list
print("Which gateware would you like to program?")
gw_list = sorted(get_gw_list(GW_DIR, board))
for i, gw in enumerate(gw_list):
print(" %2d -> %s" % (i, os.path.basename(gw)))
start = False
while (not start):
is_valid = False
while(not is_valid):
try :
print("")
choice = int(raw_input("Enter your choice : "))
if choice < len(gw_list):
is_valid = 1
else:
print("%d is not in range 0-%d"%(choice, len(gw_list)-1))
except ValueError as e :
print ("%s is not a valid integer." % e.args[0].split(": ")[1])
gw_path = gw_list[choice]
print("Selected gateware: %s" % os.path.basename(gw_path))
inp = raw_input("Start writing bitstream? (y/n) ");
if (inp.lower() == 'y'):
start = True
# Start writing the boards in the crate
mass_multiboot(1, gw_path, opts.iprogafter, opts.iprogaddr)
elma.close()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment