Statistics
| Branch: | Tag: | Revision:

root / host / utils / usrp_n2xx_net_burner.py @ ef9ca5f9

History | View | Annotate | Download (16.2 KB)

1
#!/usr/bin/env python
2
#
3
# Copyright 2010-2011 Ettus Research LLC
4
#
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
#
18

    
19
# TODO: make it autodetect UHD devices
20
# TODO: you should probably watch sequence numbers
21
# TODO: validate images in 1) size and 2) header content so you can't write a Justin Bieber MP3 to Flash
22

    
23
import optparse
24
import math
25
import os
26
import re
27
import struct
28
import socket
29
import sys
30
import time
31

    
32
########################################################################
33
# constants
34
########################################################################
35
UDP_FW_UPDATE_PORT = 49154
36
UDP_MAX_XFER_BYTES = 1024
37
UDP_TIMEOUT = 3
38
UDP_POLL_INTERVAL = 0.10 #in seconds
39

    
40
USRP2_FW_PROTO_VERSION = 7 #should be unused after r6
41

    
42
#from bootloader_utils.h
43

    
44
FPGA_IMAGE_SIZE_BYTES = 1572864
45
FW_IMAGE_SIZE_BYTES = 31744
46
SAFE_FPGA_IMAGE_LOCATION_ADDR = 0x00000000
47
SAFE_FW_IMAGE_LOCATION_ADDR = 0x003F0000
48
PROD_FPGA_IMAGE_LOCATION_ADDR = 0x00180000
49
PROD_FW_IMAGE_LOCATION_ADDR = 0x00300000
50

    
51
FLASH_DATA_PACKET_SIZE = 256
52

    
53
#see fw_common.h
54
FLASH_ARGS_FMT = '!LLLLL256s'
55
FLASH_INFO_FMT = '!LLLLL256x'
56
FLASH_IP_FMT =   '!LLLL260x'
57

    
58
class update_id_t:
59
  USRP2_FW_UPDATE_ID_WAT = ord(' ')
60
  USRP2_FW_UPDATE_ID_OHAI_LOL = ord('a')
61
  USRP2_FW_UPDATE_ID_OHAI_OMG = ord('A')
62
  USRP2_FW_UPDATE_ID_WATS_TEH_FLASH_INFO_LOL = ord('f')
63
  USRP2_FW_UPDATE_ID_HERES_TEH_FLASH_INFO_OMG = ord('F')
64
  USRP2_FW_UPDATE_ID_ERASE_TEH_FLASHES_LOL = ord('e')
65
  USRP2_FW_UPDATE_ID_ERASING_TEH_FLASHES_OMG = ord('E')
66
  USRP2_FW_UPDATE_ID_R_U_DONE_ERASING_LOL = ord('d')
67
  USRP2_FW_UPDATE_ID_IM_DONE_ERASING_OMG = ord('D')
68
  USRP2_FW_UPDATE_ID_NOPE_NOT_DONE_ERASING_OMG = ord('B')
69
  USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL = ord('w')
70
  USRP2_FW_UPDATE_ID_WROTE_TEH_FLASHES_OMG = ord('W')
71
  USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL = ord('r')
72
  USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG = ord('R')
73
  USRP2_FW_UPDATE_ID_RESET_MAH_COMPUTORZ_LOL = ord('s')
74
  USRP2_FW_UPDATE_ID_RESETTIN_TEH_COMPUTORZ_OMG = ord('S')
75
  USRP2_FW_UPDATE_ID_KTHXBAI = ord('~')
76

    
77
_seq = -1
78
def seq():
79
    global _seq
80
    _seq = _seq+1
81
    return _seq
82

    
83
########################################################################
84
# helper functions
85
########################################################################
86
def unpack_flash_args_fmt(s):
87
    return struct.unpack(FLASH_ARGS_FMT, s) #(proto_ver, pktid, seq, flash_addr, length, data)
88

    
89
def unpack_flash_info_fmt(s):
90
    return struct.unpack(FLASH_INFO_FMT, s) #(proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes)
91

    
92
def unpack_flash_ip_fmt(s):
93
    return struct.unpack(FLASH_IP_FMT, s) #(proto_ver, pktid, seq, ip_addr)
94

    
95
def pack_flash_args_fmt(proto_ver, pktid, seq, flash_addr, length, data=bytes()):
96
    return struct.pack(FLASH_ARGS_FMT, proto_ver, pktid, seq, flash_addr, length, data)
97

    
98
def pack_flash_info_fmt(proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes):
99
    return struct.pack(FLASH_INFO_FMT, proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes)
100

    
101
def is_valid_fpga_image(fpga_image):
102
    for i in range(0,63):
103
        if fpga_image[i:i+1] == bytes(b'\xFF'): continue
104
        if fpga_image[i:i+2] == bytes(b'\xAA\x99'): return True
105
    return False
106

    
107
def is_valid_fw_image(fw_image):
108
    return fw_image[:4] == bytes(b'\x0B\x0B\x0B\x0B')
109

    
110
########################################################################
111
# Burner class, holds a socket and send/recv routines
112
########################################################################
113
class burner_socket(object):
114
    def __init__(self, addr):
115
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
116
        self._sock.settimeout(UDP_TIMEOUT)
117
        self._sock.connect((addr, UDP_FW_UPDATE_PORT))
118
        self.set_callbacks(lambda *a: None, lambda *a: None)
119
        self.init_update() #check that the device is there
120

    
121
    def set_callbacks(self, progress_cb, status_cb):
122
        self._progress_cb = progress_cb
123
        self._status_cb = status_cb
124

    
125
    def send_and_recv(self, pkt):
126
        self._sock.send(pkt)
127
        return self._sock.recv(UDP_MAX_XFER_BYTES)
128

    
129
    #just here to validate comms
130
    def init_update(self):
131
        out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_OHAI_LOL, seq(), 0, 0)
132
        try: in_pkt = self.send_and_recv(out_pkt)
133
        except socket.timeout: raise Exception("No response from device")
134
        (proto_ver, pktid, rxseq, ip_addr) = unpack_flash_ip_fmt(in_pkt)
135
        if pktid == update_id_t.USRP2_FW_UPDATE_ID_OHAI_OMG:
136
            print("USRP-N2XX found.")
137
        else:
138
            raise Exception("Invalid reply received from device.")
139

    
140
        #  print "Incoming:\n\tVer: %i\n\tID: %c\n\tSeq: %i\n\tIP: %i\n" % (proto_ver, chr(pktid), rxseq, ip_addr)
141

    
142
    memory_size_bytes = 0
143
    sector_size_bytes = 0
144
    def get_flash_info(self):
145
        if (self.memory_size_bytes != 0) and (self.sector_size_bytes != 0):
146
            return (self.memory_size_bytes, self.sector_size_bytes)
147
            
148
        out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_WATS_TEH_FLASH_INFO_LOL, seq(), 0, 0)
149
        in_pkt = self.send_and_recv(out_pkt)
150

    
151
        (proto_ver, pktid, rxseq, self.sector_size_bytes, self.memory_size_bytes) = unpack_flash_info_fmt(in_pkt)
152

    
153
        if pktid != update_id_t.USRP2_FW_UPDATE_ID_HERES_TEH_FLASH_INFO_OMG:
154
            raise Exception("Invalid reply %c from device." % (chr(pktid)))
155

    
156
        return (self.memory_size_bytes, self.sector_size_bytes)
157

    
158
    def burn_fw(self, fw, fpga, reset, safe):
159
        (flash_size, sector_size) = self.get_flash_info()
160

    
161
        print("Flash size: %i\nSector size: %i\n\n" % (flash_size, sector_size))
162

    
163
        if fpga:
164
            if safe: image_location = SAFE_FPGA_IMAGE_LOCATION_ADDR
165
            else:    image_location = PROD_FPGA_IMAGE_LOCATION_ADDR
166

    
167
            fpga_file = open(fpga, 'rb')
168
            fpga_image = fpga_file.read()
169

    
170
            if len(fpga_image) > FPGA_IMAGE_SIZE_BYTES:
171
                raise Exception("Error: FPGA image file too large.")
172

    
173
            if not is_valid_fpga_image(fpga_image):
174
                raise Exception("Error: Invalid FPGA image file.")
175
                
176
            if (len(fpga_image) + image_location) > flash_size:
177
                raise Exception("Error: Cannot write past end of device")
178

    
179
            print("Begin FPGA write: this should take about 1 minute...")
180
            start_time = time.time()
181
            self.erase_image(image_location, FPGA_IMAGE_SIZE_BYTES)
182
            self.write_image(fpga_image, image_location)
183
            self.verify_image(fpga_image, image_location)
184
            print("Time elapsed: %f seconds"%(time.time() - start_time))
185
            print("\n\n")
186

    
187
        if fw:
188
            if safe: image_location = SAFE_FW_IMAGE_LOCATION_ADDR
189
            else:    image_location = PROD_FW_IMAGE_LOCATION_ADDR
190

    
191
            fw_file = open(fw, 'rb')
192
            fw_image = fw_file.read()
193

    
194
            if len(fw_image) > FW_IMAGE_SIZE_BYTES:
195
                raise Exception("Error: Firmware image file too large.")
196

    
197
            if not is_valid_fw_image(fw_image):
198
                raise Exception("Error: Invalid firmware image file.")
199
                
200
            if (len(fw_image) + image_location) > flash_size:
201
                raise Exception("Error: Cannot write past end of device")
202

    
203
            print("Begin firmware write: this should take about 1 second...")
204
            start_time = time.time()
205
            self.erase_image(image_location, FW_IMAGE_SIZE_BYTES)
206
            self.write_image(fw_image, image_location)
207
            self.verify_image(fw_image, image_location)
208
            print("Time elapsed: %f seconds"%(time.time() - start_time))
209
            print("\n\n")
210

    
211
        if reset: self.reset_usrp()
212

    
213
    def write_image(self, image, addr):
214
        print("Writing image")
215
        self._status_cb("Writing")
216
        writedata = image
217
        #we split the image into smaller (256B) bits and send them down the wire
218
        (mem_size, sector_size) = self.get_flash_info()
219
        if (addr + len(writedata)) > mem_size:
220
            raise Exception("Error: Cannot write past end of device")
221
            
222
        while writedata:
223
            out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL, seq(), addr, FLASH_DATA_PACKET_SIZE, writedata[:FLASH_DATA_PACKET_SIZE])
224
            in_pkt = self.send_and_recv(out_pkt)
225

    
226
            (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt)
227

    
228
            if pktid != update_id_t.USRP2_FW_UPDATE_ID_WROTE_TEH_FLASHES_OMG:
229
              raise Exception("Invalid reply %c from device." % (chr(pktid)))
230

    
231
            writedata = writedata[FLASH_DATA_PACKET_SIZE:]
232
            addr += FLASH_DATA_PACKET_SIZE
233
            self._progress_cb(float(len(image)-len(writedata))/len(image))
234

    
235
    def verify_image(self, image, addr):
236
        print("Verifying data")
237
        self._status_cb("Verifying")
238
        readsize = len(image)
239
        readdata = bytes()
240
        while readsize > 0:
241
            if readsize < FLASH_DATA_PACKET_SIZE: thisreadsize = readsize
242
            else: thisreadsize = FLASH_DATA_PACKET_SIZE
243
            out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL, seq(), addr, thisreadsize)
244
            in_pkt = self.send_and_recv(out_pkt)
245

    
246
            (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt)
247

    
248
            if pktid != update_id_t.USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG:
249
              raise Exception("Invalid reply %c from device." % (chr(pktid)))
250

    
251
            readdata += data[:thisreadsize]
252
            readsize -= FLASH_DATA_PACKET_SIZE
253
            addr += FLASH_DATA_PACKET_SIZE
254
            self._progress_cb(float(len(readdata))/len(image))
255

    
256
        print("Read back %i bytes" % len(readdata))
257
        #  print readdata
258

    
259
        #  for i in range(256, 512):
260
        #    print "out: %i in: %i" % (ord(image[i]), ord(readdata[i]))
261

    
262
        if readdata != image:
263
            raise Exception("Verify failed. Image did not write correctly.")
264
        else:
265
            print("Success.")
266

    
267
    def read_image(self, image, size, addr):
268
        print("Reading image")
269
        readsize = size
270
        readdata = str()
271
        while readsize > 0:
272
            if readsize < FLASH_DATA_PACKET_SIZE: thisreadsize = readsize
273
            else: thisreadsize = FLASH_DATA_PACKET_SIZE
274
            out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL, seq(), addr, thisreadsize)
275
            in_pkt = self.send_and_recv(out_pkt)
276

    
277
            (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt)
278

    
279
            if pktid != update_id_t.USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG:
280
              raise Exception("Invalid reply %c from device." % (chr(pktid)))
281

    
282
            readdata += data[:thisreadsize]
283
            readsize -= FLASH_DATA_PACKET_SIZE
284
            addr += FLASH_DATA_PACKET_SIZE
285

    
286
        print("Read back %i bytes" % len(readdata))
287

    
288
        #write to disk
289
        f = open(image, 'w')
290
        f.write(readdata)
291
        f.close()
292

    
293
    def reset_usrp(self):
294
        out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_RESET_MAH_COMPUTORZ_LOL, seq(), 0, 0)
295
        try: in_pkt = self.send_and_recv(out_pkt)
296
        except socket.timeout: return
297

    
298
        (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt)
299
        if pktid == update_id_t.USRP2_FW_UPDATE_ID_RESETTIN_TEH_COMPUTORZ_OMG:
300
            raise Exception("Device failed to reset.")
301

    
302
    def erase_image(self, addr, length):
303
        self._status_cb("Erasing")
304
        #get flash info first
305
        (flash_size, sector_size) = self.get_flash_info()
306
        if (addr + length) > flash_size:
307
            raise Exception("Cannot erase past end of device")
308
            
309
        out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_ERASE_TEH_FLASHES_LOL, seq(), addr, length)
310
        in_pkt = self.send_and_recv(out_pkt)
311

    
312
        (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt)
313

    
314
        if pktid != update_id_t.USRP2_FW_UPDATE_ID_ERASING_TEH_FLASHES_OMG:
315
            raise Exception("Invalid reply %c from device." % (chr(pktid)))
316

    
317
        print("Erasing %i bytes at %i" % (length, addr))
318
        start_time = time.time()
319

    
320
        #now wait for it to finish
321
        while(True):
322
            out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_R_U_DONE_ERASING_LOL, seq(), 0, 0)
323
            in_pkt = self.send_and_recv(out_pkt)
324

    
325
            (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt)
326

    
327
            if pktid == update_id_t.USRP2_FW_UPDATE_ID_IM_DONE_ERASING_OMG: break
328
            elif pktid != update_id_t.USRP2_FW_UPDATE_ID_NOPE_NOT_DONE_ERASING_OMG:
329
                raise Exception("Invalid reply %c from device." % (chr(pktid)))
330
            time.sleep(0.01) #decrease network overhead by waiting a bit before polling
331
            self._progress_cb(min(1.0, (time.time() - start_time)/(length/80e3)))
332

    
333

    
334
########################################################################
335
# command line options
336
########################################################################
337
def get_options():
338
    parser = optparse.OptionParser()
339
    parser.add_option("--addr", type="string",                 help="USRP-N2XX device address",       default='')
340
    parser.add_option("--fw",   type="string",                 help="firmware image path (optional)", default='')
341
    parser.add_option("--fpga", type="string",                 help="fpga image path (optional)",     default='')
342
    parser.add_option("--reset", action="store_true",          help="reset the device after writing", default=False)
343
    parser.add_option("--read", action="store_true",           help="read to file instead of write from file", default=False)
344
    parser.add_option("--overwrite-safe", action="store_true", help="never ever use this option", default=False)
345
    (options, args) = parser.parse_args()
346

    
347
    return options
348

    
349
########################################################################
350
# main
351
########################################################################
352
if __name__=='__main__':
353
    options = get_options()
354
    if not options.addr: raise Exception('no address specified')
355

    
356
    if not options.fpga and not options.fw and not options.reset: raise Exception('Must specify either a firmware image or FPGA image, and/or reset.')
357

    
358
    if options.overwrite_safe and not options.read:
359
        print("Are you REALLY, REALLY sure you want to overwrite the safe image? This is ALMOST ALWAYS a terrible idea.")
360
        print("If your image is faulty, your USRP2+ will become a brick until reprogrammed via JTAG.")
361
        response = input("""Type "yes" to continue, or anything else to quit: """)
362
        if response != "yes": sys.exit(0)
363

    
364
    burner = burner_socket(addr=options.addr)
365

    
366
    if options.read:
367
        if options.fw:
368
            file = options.fw
369
            if os.path.isfile(file):
370
                response = input("File already exists -- overwrite? (y/n) ")
371
                if response != "y": sys.exit(0)
372
            size = FW_IMAGE_SIZE_BYTES
373
            addr = SAFE_FW_IMAGE_LOCATION_ADDR if options.overwrite_safe else PROD_FW_IMAGE_LOCATION_ADDR
374
            burner.read_image(file, size, addr)
375

    
376
        if options.fpga:
377
            file = options.fpga
378
            if os.path.isfile(file):
379
                response = input("File already exists -- overwrite? (y/n) ")
380
                if response != "y": sys.exit(0)
381
            size = FPGA_IMAGE_SIZE_BYTES
382
            addr = SAFE_FPGA_IMAGE_LOCATION_ADDR if options.overwrite_safe else PROD_FPGA_IMAGE_LOCATION_ADDR
383
            burner.read_image(file, size, addr)
384

    
385
    else: burner.burn_fw(fw=options.fw, fpga=options.fpga, reset=options.reset, safe=options.overwrite_safe)