14
14
from os .path import dirname , join as joinpath , normpath
15
15
from time import sleep , time as now
16
16
from traceback import format_exc
17
- from typing import Optional
17
+ from typing import Optional , Union
18
18
import sys
19
19
20
20
QEMU_PYPATH = joinpath (dirname (dirname (dirname (normpath (__file__ )))),
21
21
'python' , 'qemu' )
22
22
sys .path .append (QEMU_PYPATH )
23
23
24
- from ot .spi import SpiDevice
24
+ from ot .spi import JedecId , SpiDevice
25
25
from ot .util .log import configure_loggers
26
26
from ot .util .misc import HexInt
27
27
28
28
29
29
class SpiDeviceFlasher :
30
- "Simple SPI device flasher, using OT protocol."
30
+ """Simple SPI device flasher, using OT protocol.
31
+ """
32
+
33
+ DEFAULT_SYNC_TIME = 5.0
34
+ """Default synchronization max time."""
31
35
32
36
DEFAULT_PORT = 8004
33
37
"""Default TCP port for SPI device."""
34
38
39
+ JEDEC_MANUFACTURER = (12 , 0xef )
40
+ """Expected JEDEC manufacturer."""
41
+
35
42
def __init__ (self ):
36
43
self ._log = getLogger ('spidev.flash' )
37
44
self ._spidev = SpiDevice ()
@@ -60,49 +67,57 @@ def connect(self, host: str, port: Optional[int], retry_count: int = 1,
60
67
retry_count -= 1
61
68
if not retry_count :
62
69
raise
63
- self ._wait_for_remote ()
70
+ duration = now ()
71
+ self ._log .info ('Synchronizing' )
72
+ jedec_id = self ._synchronize (sync_time or self .DEFAULT_SYNC_TIME )
73
+ if (jedec_id .bank , jedec_id .jedec [0 ]) != self .JEDEC_MANUFACTURER :
74
+ raise ValueError (f'Unexpected manufacurer '
75
+ f'{ jedec_id .bank } :{ jedec_id .jedec [0 ]} ' )
76
+ duration = now () - duration
77
+ self ._log .info ('Synchronization completed in %.0f ms' , duration * 1000 )
64
78
65
79
def disconnect (self ):
66
80
"""Disconnect from the remote host."""
67
81
self ._spidev .power_down ()
68
82
69
- def program (self , data : memoryview , offset : int = 0 ):
83
+ def program (self , data : Union [ bytes , bytearray ] , offset : int = 0 ):
70
84
"""Programm a buffer into the remote flash device."""
71
85
start = now ()
72
86
total = 0
73
87
page_size = 256
74
88
page_count = (len (data ) + page_size - 1 ) // page_size
75
- log = getLogger ('spidev' )
76
- log .info ('\n Read SFTP' )
77
- self ._spidev .read_sfdp ()
78
- log .info ('\n Chip erase' )
89
+ self ._log .info ('Chip erase' )
79
90
self ._spidev .enable_write ()
80
91
self ._spidev .chip_erase ()
81
- self ._spidev .wait_idle ()
92
+ self ._spidev .wait_idle (timeout = self .DEFAULT_SYNC_TIME , pace = 0.1 )
93
+ self ._log .info ('Upload file' )
82
94
for pos in range (0 , len (data ), page_size ):
83
95
page = data [pos :pos + page_size ]
84
- log .debug ('Program page @ 0x%06x %d/%d, %d bytes' ,
85
- pos + offset , pos // page_size , page_count , len (page ))
96
+ self . _log .debug ('Program page @ 0x%06x %d/%d, %d bytes' ,
97
+ pos + offset , pos // page_size , page_count , len (page ))
86
98
self ._spidev .enable_write ()
87
99
self ._spidev .page_program (pos + offset , page )
88
- sleep ( 0.003 )
89
- self . _spidev . wait_idle ( pace = 0.001 ) # bootrom is slow :- )
100
+ self . _spidev . wait_idle ( timeout = self . DEFAULT_SYNC_TIME ,
101
+ pace = 0.01 )
90
102
total += len (page )
91
103
delta = now () - start
92
104
msg = f'{ delta :.1f} s to send { total / 1024 :.1f} KB: ' \
93
105
f'{ total / (1024 * delta ):.1f} KB/s'
94
- log .info ('%s' , msg )
106
+ self . _log .info ('%s' , msg )
95
107
self ._spidev .reset ()
96
108
97
- def _wait_for_remote (self ):
109
+ def _synchronize (self , timeout : float ) -> JedecId :
110
+ """Wait for remote peer to be ready."""
98
111
# use JEDEC ID presence as a sycnhronisation token
99
112
# remote SPI device firware should set JEDEC ID when it is full ready
100
113
# to handle requests
101
- timeout = now () + 3.0
102
- while now () < timeout :
103
- jedec = set (self ._spidev .read_jedec_id ().jedec )
114
+ expire = now () + timeout
115
+ while now () < expire :
116
+ jedec_id = self ._spidev .read_jedec_id ()
117
+ jedec = set (jedec_id .jedec )
104
118
if len (jedec ) > 1 or jedec .pop () not in (0x00 , 0xff ):
105
- return
119
+ return jedec_id
120
+ sleep (0.1 )
106
121
raise RuntimeError ('Remote SPI device not ready' )
107
122
108
123
@@ -122,30 +137,40 @@ def main():
122
137
help = 'connection string' )
123
138
argparser .add_argument ('-R' , '--retry-count' , type = int , default = 1 ,
124
139
help = 'connection retry count (default: 1)' )
140
+ argparser .add_argument ('-T' , '--sync-time' , type = float ,
141
+ default = SpiDeviceFlasher .DEFAULT_SYNC_TIME ,
142
+ help = f'synchronization max time (default: '
143
+ f'{ SpiDeviceFlasher .DEFAULT_SYNC_TIME :.1f} '
144
+ f's)' )
125
145
argparser .add_argument ('-r' , '--host' ,
126
146
help = 'remote host name (default: localhost)' )
127
147
argparser .add_argument ('-p' , '--port' , type = int ,
128
- help = f'remote host TCP port (defaults to '
148
+ help = f'remote host TCP port (default: '
129
149
f'{ SpiDeviceFlasher .DEFAULT_PORT } )' )
150
+ argparser .add_argument ('--log-udp' , type = int , metavar = 'UDP_PORT' ,
151
+ help = 'Log to a local UDP logger' )
130
152
argparser .add_argument ('-v' , '--verbose' , action = 'count' ,
131
153
help = 'increase verbosity' )
132
154
argparser .add_argument ('-d' , '--debug' , action = 'store_true' ,
133
155
help = 'enable debug mode' )
134
156
args = argparser .parse_args ()
135
157
debug = args .debug
136
158
137
- configure_loggers (args .verbose , 'spidev' )
159
+ configure_loggers (args .verbose , 'spidev' , - 1 , 'spidev.dev' ,
160
+ udplog = args .log_udp )
138
161
139
162
flasher = SpiDeviceFlasher ()
140
163
if args .socket :
141
164
if any ((args .host , args .port )):
142
165
argparser .error ('Connection string is mutually exclusive '
143
166
'with host and port' )
144
- flasher .connect (args .socket , None , retry_count = args .retry_count )
167
+ flasher .connect (args .socket , None , retry_count = args .retry_count ,
168
+ sync_time = args .sync_time )
145
169
else :
146
170
flasher .connect (args .host or 'localhost' ,
147
171
args .port or SpiDeviceFlasher .DEFAULT_PORT ,
148
- retry_count = args .retry_count )
172
+ retry_count = args .retry_count ,
173
+ sync_time = args .sync_time )
149
174
data = args .file .read ()
150
175
args .file .close ()
151
176
flasher .program (data , args .address )
0 commit comments