Statistics
| Branch: | Tag: | Revision:

root / host / utils / usrp2_card_burner.py @ 0df49b75

History | View | Annotate | Download (10 KB)

1 9fd30e92 Josh Blum
#!/usr/bin/env python
2 2e6123a7 Josh Blum
#
3 07222254 Josh Blum
# Copyright 2010-2011 Ettus Research LLC
4 2e6123a7 Josh Blum
#
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 9fd30e92 Josh Blum
19
import platform
20
import tempfile
21
import subprocess
22 07222254 Josh Blum
try:
23
    import urllib.request
24
except ImportError:
25
    import urllib
26
    urllib.request = urllib
27 a0bdd2d2 Josh Blum
import optparse
28 97624cfe Josh Blum
import math
29 a0bdd2d2 Josh Blum
import os
30 76ce1d82 Josh Blum
import re
31 9fd30e92 Josh Blum
32 a0bdd2d2 Josh Blum
########################################################################
33
# constants
34
########################################################################
35 9fd30e92 Josh Blum
SECTOR_SIZE = 512                 # bytes
36
MAX_FILE_SIZE =  1 * (2**20)      # maximum number of bytes we'll burn to a slot
37
38
FPGA_OFFSET = 0                   # offset in flash to fpga image
39
FIRMWARE_OFFSET = 1 * (2**20)     # offset in flash to firmware image
40
41 2e6123a7 Josh Blum
MAX_SD_CARD_SIZE = 2048e6         # bytes (any bigger is sdhc)
42
43 a0bdd2d2 Josh Blum
########################################################################
44
# helper functions
45
########################################################################
46 2e6123a7 Josh Blum
def command(*args):
47
    p = subprocess.Popen(
48
        args,
49
        stdout=subprocess.PIPE,
50
        stderr=subprocess.STDOUT,
51
    )
52
    ret = p.wait()
53 435d14ea Josh Blum
    verbose = p.stdout.read().decode()
54 07222254 Josh Blum
    if ret != 0: raise Exception(verbose)
55 2e6123a7 Josh Blum
    return verbose
56
57 9fd30e92 Josh Blum
def get_dd_path():
58
    if platform.system() == 'Windows':
59 f289d03d Josh Blum
        dd_path = os.path.join(os.path.dirname(__file__), 'dd.exe')
60
        if os.path.exists(dd_path): return dd_path
61 9fd30e92 Josh Blum
        dd_path = os.path.join(tempfile.gettempdir(), 'dd.exe')
62
        if not os.path.exists(dd_path):
63 07222254 Josh Blum
            print('Downloading dd.exe to %s'%dd_path)
64 0df49b75 Josh Blum
            dd_bin = urllib.request.urlopen('http://files.ettus.com/dd.exe').read()
65 9fd30e92 Josh Blum
            open(dd_path, 'wb').write(dd_bin)
66
        return dd_path
67
    return 'dd'
68
69 97624cfe Josh Blum
def int_ceil_div(num, den):
70
    return int(math.ceil(float(num)/float(den)))
71
72
def get_tmp_file():
73
    tmp = tempfile.mkstemp()
74
    os.close(tmp[0])
75
    return tmp[1]
76
77 a0bdd2d2 Josh Blum
########################################################################
78
# list possible devices
79
########################################################################
80 9fd30e92 Josh Blum
def get_raw_device_hints():
81 2e6123a7 Josh Blum
    ####################################################################
82
    # Platform Windows: parse the output of dd.exe --list
83
    ####################################################################
84 9fd30e92 Josh Blum
    if platform.system() == 'Windows':
85 98bd3e1d Josh Blum
        def extract_info_value(info, key):
86
            return info.split(key)[-1].split()[0]
87
        def get_info_list(output):
88
            in_info = False
89 529297b9 Josh Blum
            for line in output.splitlines():
90 98bd3e1d Josh Blum
                if line.startswith('\\\\'): in_info = True; info = ''
91
                elif in_info and not line.strip(): in_info = False; yield info
92
                if in_info: info += '\n'+line.strip()
93
        def is_info_valid(info):
94
            try:
95 435d14ea Josh Blum
                if 'link to' not in info: return False
96 98bd3e1d Josh Blum
                #handles two spellings of remov(e)able:
97 435d14ea Josh Blum
                if 'remov' not in info.lower(): return False
98
                if 'size is' in info and int(extract_info_value(info, 'size is')) > MAX_SD_CARD_SIZE: return False
99 98bd3e1d Josh Blum
            except: return False
100 435d14ea Josh Blum
            return True
101 98bd3e1d Josh Blum
        def extract_info_name(info):
102
            for key in ('Mounted on', 'link to'):
103
                if key in info: return extract_info_value(info, key)
104
            return info.splitlines()[0].strip()
105 2e6123a7 Josh Blum
106 07222254 Josh Blum
        return sorted(set(map(extract_info_name, list(filter(is_info_valid, get_info_list(command(get_dd_path(), '--list')))))))
107 2e6123a7 Josh Blum
108
    ####################################################################
109 221909b3 Josh Blum
    # Platform Linux: parse procfs /proc/partitions
110 2e6123a7 Josh Blum
    ####################################################################
111 9fd30e92 Josh Blum
    if platform.system() == 'Linux':
112 2e6123a7 Josh Blum
        devs = list()
113 435d14ea Josh Blum
        for line in command('cat', '/proc/partitions').splitlines():
114 2e6123a7 Josh Blum
            try:
115
                major, minor, blocks, name = line.split()
116 435d14ea Josh Blum
                if not name[-1].isdigit() and int(minor) == 0: continue
117
                if int(blocks)*1024 > MAX_SD_CARD_SIZE: continue
118 2e6123a7 Josh Blum
            except: continue
119 221909b3 Josh Blum
            devs.append(os.path.join('/dev', name))
120 2e6123a7 Josh Blum
121
        return sorted(set(devs))
122
123
    ####################################################################
124 76ce1d82 Josh Blum
    # Platform Mac OS X: parse diskutil list and info commands
125
    ####################################################################
126
    if platform.system() == 'Darwin':
127 07222254 Josh Blum
        devs = [d.split()[0] for d in [l for l in command('diskutil', 'list').splitlines() if l.startswith('/dev')]]
128 76ce1d82 Josh Blum
        def output_to_info(output):
129 a18b7e01 Josh Blum
            return dict([list(map(lambda x: x.strip(), pair.lower().split(':'))) for pair in [l for l in output.splitlines() if ':' in l]])
130 76ce1d82 Josh Blum
        def is_dev_valid(dev):
131
            info = output_to_info(command('diskutil', 'info', dev))
132
            try:
133 435d14ea Josh Blum
                if 'internal' in info and info['internal'] == 'yes': return False
134
                if 'ejectable' in info and info['ejectable'] == 'no': return False
135 07222254 Josh Blum
                if 'total size' in info:
136 76ce1d82 Josh Blum
                    size_match = re.match('^.*\((\d+)\s*bytes\).*$', info['total size'])
137 435d14ea Josh Blum
                    if size_match and int(size_match.groups()[0]) > MAX_SD_CARD_SIZE: return False
138 76ce1d82 Josh Blum
            except: return False
139 435d14ea Josh Blum
            return True
140 76ce1d82 Josh Blum
141
        return sorted(set(filter(is_dev_valid, devs)))
142
143
    ####################################################################
144 2e6123a7 Josh Blum
    # Platform Others:
145
    ####################################################################
146 9fd30e92 Josh Blum
    return ()
147
148 a0bdd2d2 Josh Blum
########################################################################
149
# write and verify with dd
150
########################################################################
151 9fd30e92 Josh Blum
def verify_image(image_file, device_file, offset):
152 97624cfe Josh Blum
    #create a temporary file to store the readback image
153
    tmp_file = get_tmp_file()
154
155
    #read the image data
156
    img_data = open(image_file, 'rb').read()
157
    count = int_ceil_div(len(img_data), SECTOR_SIZE)
158 9fd30e92 Josh Blum
159
    #execute a dd subprocess
160 2e6123a7 Josh Blum
    verbose = command(
161 9fd30e92 Josh Blum
        get_dd_path(),
162
        "of=%s"%tmp_file,
163
        "if=%s"%device_file,
164 7c98884e Josh Blum
        "skip=%d"%(offset/SECTOR_SIZE),
165 9fd30e92 Josh Blum
        "bs=%d"%SECTOR_SIZE,
166 97624cfe Josh Blum
        "count=%d"%count,
167 9fd30e92 Josh Blum
    )
168
169
    #verfy the data
170 97624cfe Josh Blum
    tmp_data = open(tmp_file, 'rb').read(len(img_data))
171 9fd30e92 Josh Blum
    if img_data != tmp_data: return 'Verification Failed:\n%s'%verbose
172
    return 'Verification Passed:\n%s'%verbose
173
174
def write_image(image_file, device_file, offset):
175 97624cfe Josh Blum
    #create a temporary file to store the padded image
176
    tmp_file = get_tmp_file()
177
178
    #write the padded image data
179
    img_data = open(image_file, 'rb').read()
180
    count = int_ceil_div(len(img_data), SECTOR_SIZE)
181
    pad_len = SECTOR_SIZE*count - len(img_data)
182 60669a28 Josh Blum
    padding = bytes(b'\x00')*pad_len #zero-padding
183
    open(tmp_file, 'wb').write(img_data + padding)
184 97624cfe Josh Blum
185
    #execute a dd subprocess
186 573872ed Josh Blum
    verbose = command(
187 9fd30e92 Josh Blum
        get_dd_path(),
188 97624cfe Josh Blum
        "if=%s"%tmp_file,
189 9fd30e92 Josh Blum
        "of=%s"%device_file,
190 7c98884e Josh Blum
        "seek=%d"%(offset/SECTOR_SIZE),
191 9fd30e92 Josh Blum
        "bs=%d"%SECTOR_SIZE,
192 97624cfe Josh Blum
        "count=%d"%count,
193 9fd30e92 Josh Blum
    )
194
195 573872ed Josh Blum
    try: #exec the sync command (only works on linux)
196
        if platform.system() == 'Linux': command('sync')
197
    except: pass
198
199
    return verbose
200
201 9fd30e92 Josh Blum
def write_and_verify(image_file, device_file, offset):
202
    if os.path.getsize(image_file) > MAX_FILE_SIZE:
203 07222254 Josh Blum
        raise Exception('Image file larger than %d bytes!'%MAX_FILE_SIZE)
204 9fd30e92 Josh Blum
    return '%s\n%s'%(
205
        write_image(
206
            image_file=image_file,
207
            device_file=device_file,
208
            offset=offset,
209
        ), verify_image(
210
            image_file=image_file,
211
            device_file=device_file,
212
            offset=offset,
213
        ),
214
    )
215
216
def burn_sd_card(dev, fw, fpga):
217
    verbose = ''
218 2e6123a7 Josh Blum
    if fw: verbose += 'Burn firmware image:\n%s\n'%write_and_verify(
219 9fd30e92 Josh Blum
        image_file=fw, device_file=dev, offset=FIRMWARE_OFFSET
220
    )
221 2e6123a7 Josh Blum
    if fpga: verbose += 'Burn fpga image:\n%s\n'%write_and_verify(
222 9fd30e92 Josh Blum
        image_file=fpga, device_file=dev, offset=FPGA_OFFSET
223
    )
224
    return verbose
225
226
########################################################################
227 a0bdd2d2 Josh Blum
# command line options
228 9fd30e92 Josh Blum
########################################################################
229 a0bdd2d2 Josh Blum
def get_options():
230 9fd30e92 Josh Blum
    parser = optparse.OptionParser()
231 573872ed Josh Blum
    parser.add_option("--dev",  type="string",       help="raw device path",                default='')
232
    parser.add_option("--fw",   type="string",       help="firmware image path (optional)", default='')
233
    parser.add_option("--fpga", type="string",       help="fpga image path (optional)",     default='')
234
    parser.add_option("--list", action="store_true", help="list possible raw devices",      default=False)
235 35d88b50 Josh Blum
    parser.add_option("--force", action="store_true", help="override safety check",         default=False)
236 9fd30e92 Josh Blum
    (options, args) = parser.parse_args()
237
238 1e57d1c0 Josh Blum
    return options
239
240
########################################################################
241
# main
242
########################################################################
243
if __name__=='__main__':
244
    options = get_options()
245 35d88b50 Josh Blum
    device_hints = get_raw_device_hints()
246
    show_listing = options.list
247
248 1e57d1c0 Josh Blum
    if not show_listing and not options.force and options.dev and options.dev not in device_hints:
249 35d88b50 Josh Blum
        print('The device "%s" was not in the list of possible raw devices.'%options.dev)
250
        print('The card burner application will now exit without burning your card.')
251
        print('To override this safety check, specify the --force option.\n')
252
        show_listing = True
253
254
    if show_listing:
255 07222254 Josh Blum
        print('Possible raw devices:')
256 35d88b50 Josh Blum
        print('  ' + '\n  '.join(device_hints))
257 573872ed Josh Blum
        exit()
258
259 07222254 Josh Blum
    if not options.dev: raise Exception('no raw device path specified')
260
    print(burn_sd_card(dev=options.dev, fw=options.fw, fpga=options.fpga))