Skip to content

Commit 3ee01f5

Browse files
committed
[ot] scripts/opentitan: spidevflash.py: rework peer synchronization
Signed-off-by: Emmanuel Blot <eblot@rivosinc.com>
1 parent 1604d97 commit 3ee01f5

File tree

2 files changed

+61
-26
lines changed

2 files changed

+61
-26
lines changed

docs/opentitan/spidevflash.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
````text
88
usage: spidevflash.py [-h] -f FILE [-a ADDRESS] [-S SOCKET] [-R RETRY_COUNT]
9-
[-r HOST] [-p PORT] [-v] [-d]
9+
[-T SYNC_TIME] [-r HOST] [-p PORT] [--log-udp UDP_PORT]
10+
[-v] [-d]
1011
1112
SPI device flasher tool.
1213
@@ -18,8 +19,11 @@ options:
1819
-S, --socket SOCKET connection string
1920
-R, --retry-count RETRY_COUNT
2021
connection retry count (default: 1)
22+
-T, --sync-time SYNC_TIME
23+
synchronization max time (default: 5.0s)
2124
-r, --host HOST remote host name (default: localhost)
22-
-p, --port PORT remote host TCP port (defaults to 8004)
25+
-p, --port PORT remote host TCP port (default: 8004)
26+
--log-udp UDP_PORT Log to a local UDP logger
2327
-v, --verbose increase verbosity
2428
-d, --debug enable debug mode
2529
````
@@ -42,8 +46,14 @@ options:
4246

4347
* `-S` specify a connection string to the remote host running the QEMU instance, _e.g._
4448
`tcp:localhost:8004` or `unix:/tmp/socket`. Mutually exclusive with `-r` and `-p`.
49+
50+
* `-T` specify the maximum allowed time to successfully synchronize with the remote SPI device once
51+
the connection has been established
52+
4553
* `-v` can be repeated to increase verbosity of the script, mostly for debug purpose.
4654

55+
* `--log-udp` specify a UDP port on the local host to redirect log messages,
56+
4757
### Examples
4858

4959
With the following examples:

scripts/opentitan/spidevflash.py

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,31 @@
1414
from os.path import dirname, join as joinpath, normpath
1515
from time import sleep, time as now
1616
from traceback import format_exc
17-
from typing import Optional
17+
from typing import Optional, Union
1818
import sys
1919

2020
QEMU_PYPATH = joinpath(dirname(dirname(dirname(normpath(__file__)))),
2121
'python', 'qemu')
2222
sys.path.append(QEMU_PYPATH)
2323

24-
from ot.spi import SpiDevice
24+
from ot.spi import JedecId, SpiDevice
2525
from ot.util.log import configure_loggers
2626
from ot.util.misc import HexInt
2727

2828

2929
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."""
3135

3236
DEFAULT_PORT = 8004
3337
"""Default TCP port for SPI device."""
3438

39+
JEDEC_MANUFACTURER = (12, 0xef)
40+
"""Expected JEDEC manufacturer."""
41+
3542
def __init__(self):
3643
self._log = getLogger('spidev.flash')
3744
self._spidev = SpiDevice()
@@ -60,49 +67,57 @@ def connect(self, host: str, port: Optional[int], retry_count: int = 1,
6067
retry_count -= 1
6168
if not retry_count:
6269
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)
6478

6579
def disconnect(self):
6680
"""Disconnect from the remote host."""
6781
self._spidev.power_down()
6882

69-
def program(self, data: memoryview, offset: int = 0):
83+
def program(self, data: Union[bytes, bytearray], offset: int = 0):
7084
"""Programm a buffer into the remote flash device."""
7185
start = now()
7286
total = 0
7387
page_size = 256
7488
page_count = (len(data) + page_size - 1) // page_size
75-
log = getLogger('spidev')
76-
log.info('\nRead SFTP')
77-
self._spidev.read_sfdp()
78-
log.info('\nChip erase')
89+
self._log.info('Chip erase')
7990
self._spidev.enable_write()
8091
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')
8294
for pos in range(0, len(data), page_size):
8395
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))
8698
self._spidev.enable_write()
8799
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)
90102
total += len(page)
91103
delta = now() - start
92104
msg = f'{delta:.1f}s to send {total/1024:.1f}KB: ' \
93105
f'{total/(1024*delta):.1f}KB/s'
94-
log.info('%s', msg)
106+
self._log.info('%s', msg)
95107
self._spidev.reset()
96108

97-
def _wait_for_remote(self):
109+
def _synchronize(self, timeout: float) -> JedecId:
110+
"""Wait for remote peer to be ready."""
98111
# use JEDEC ID presence as a sycnhronisation token
99112
# remote SPI device firware should set JEDEC ID when it is full ready
100113
# 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)
104118
if len(jedec) > 1 or jedec.pop() not in (0x00, 0xff):
105-
return
119+
return jedec_id
120+
sleep(0.1)
106121
raise RuntimeError('Remote SPI device not ready')
107122

108123

@@ -122,30 +137,40 @@ def main():
122137
help='connection string')
123138
argparser.add_argument('-R', '--retry-count', type=int, default=1,
124139
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)')
125145
argparser.add_argument('-r', '--host',
126146
help='remote host name (default: localhost)')
127147
argparser.add_argument('-p', '--port', type=int,
128-
help=f'remote host TCP port (defaults to '
148+
help=f'remote host TCP port (default: '
129149
f'{SpiDeviceFlasher.DEFAULT_PORT})')
150+
argparser.add_argument('--log-udp', type=int, metavar='UDP_PORT',
151+
help='Log to a local UDP logger')
130152
argparser.add_argument('-v', '--verbose', action='count',
131153
help='increase verbosity')
132154
argparser.add_argument('-d', '--debug', action='store_true',
133155
help='enable debug mode')
134156
args = argparser.parse_args()
135157
debug = args.debug
136158

137-
configure_loggers(args.verbose, 'spidev')
159+
configure_loggers(args.verbose, 'spidev', -1, 'spidev.dev',
160+
udplog=args.log_udp)
138161

139162
flasher = SpiDeviceFlasher()
140163
if args.socket:
141164
if any((args.host, args.port)):
142165
argparser.error('Connection string is mutually exclusive '
143166
'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)
145169
else:
146170
flasher.connect(args.host or 'localhost',
147171
args.port or SpiDeviceFlasher.DEFAULT_PORT,
148-
retry_count=args.retry_count)
172+
retry_count=args.retry_count,
173+
sync_time=args.sync_time)
149174
data = args.file.read()
150175
args.file.close()
151176
flasher.program(data, args.address)

0 commit comments

Comments
 (0)