it should be possible to load new programs into OpenLCB nodes connected via CAN bus. Even better would be a common protocol for that.
The rest of this note is where we develop that.
Only use standard OpenLCB protocols
Loads may fail, but that must leave the node in a state that can attempt another load
Must work when the loader and loaded nodes are not on the same CAN segment.
Must be straightforward to implement in low cost, low capability devices
Minimal complexity in the loaded node
Must handle limitations of rewriting memory in PICs, AVRs, etc
Erase requirement
Block size and alignment
Timing (dead during write, erase?)
Conformance must be testable with reasonable infrastructure.
Manufacturers must be able to prevent unauthorized loading of their hardware
Either end can initiate load
Efficient transfer
Must be able to load a single node while it's still on a complex OpenLCB setup; don't want to require recabling.
Put as much work as possible at the loading end or further upstream, not the loaded end. E.g. it's OK to insist that the download data have a certain structure, which the loading node (or even better, the tools that create the program load fine in the first place) must ensure, rather than having the loaded node have to deal with a more general format.
This overlaps a little with questions of configuration: Both are changing the internal state of a node. (See the memory configuration protcol document) Do we want a single protocol that can do everything? The real difference about a bootloader is that you can't assume that the node is actually working. Cheap nodes don't have room for two copies of the program, so they have to retreat to a small corner of memory, erase the rest, and get the download. This is really different from modifying some configuration variables.
Does a OpenLCB node have to really be an OpenLCB node while downloading? E.g. if the little downloader kernel can't take part in various other interactions, what are the implications?
An approach based on e.g. the Digitrax board load protocol, in OpenLCB terms:
Datagrams to set up the load process (request, get permission, negotiate stream)
Followed by a stream that contains the data. End of stream means “boot into new code”
That raises the question of the format of the data within the stream – Intel Hex format, with restrictions on e.g. data alignment and order.
What does the negotiation at the beginning doing?
It has to tell the code that a download is coming, but that could also be done i.e. by the stream content ID at the start of the stream.
Issue: How to handle timing for writing, and any requirement to erase first. Is this something that gets negotiated at the start? The download is prepared for specific hardware, so the programmer knows what the limitations are. Those could be specified and control how fast the data is being sent down the stream?
(From David Harris email)
So, I think you are suggesting that we use datagrams to set up a bidirectional stream between two nodes:
The originating node would do: Stream.setup(originating-NID, target-NID, originating-stream-id, originating-maxbuffer)
And expect a response from the target: Stream.setupAck(target-NID, originating-NID, target-stream-id, target-maxbuffer).
After this, the buffer-size has been negotiated as min(originating, target), zero means the stream has been rejected.
At this point each node can send stream messages to its opposite, and these stream-messages are identified by their NID:stream-id.
I suggest we let the stream-messages be one of two types: (1)
'command' messages, that inform the far-end of the intent and the
content-format of subsequent data:
Stream.sendCommand(src-NID,
src-stream-id, command, *data);
and (2) 'data' messages:
Stream.sendData(src-NID,
src-stream-id, *data).
Examples of 'command' messages are:
(Proceed)
(Abort)
(Error, type, data)
(Upload, mem-type, address)
An example of an stream might be:
--> Stream.setup(0001,
0055, 05, 200)
<-- Stream.setupAck(0055, 0001, 01, 100) // the
stream is now set-up, with buffer of 100 bytes
-->
Stream.sendCommand(0001, 05, upload, flash, 0x0000) // start an
upload
--> Stream.sendData(0001, 05, 0x45, 0x64, 0x00, 0x55,
0x55, 0x55, ...)
--> Stream.sendData(0001, 05, 0xFF, 0xFE,
0x00, 0x8F, 0x55, 0x55, ...)
...
<--
Stream.sendCommand(0055, 01, dataProceed) // target can accept more
data
--> Stream.sendCommand(0001, 05, upload, flash, 0x0100)
// new upload address
--> Stream.sendData(0001, 05, 0x45,
0x64, 0x00, 0x55, 0x55, 0x55, ...)
--> Stream.sendData(0001,
05, 0xFF, 0xFE, 0x00, 0x8F, 0x55, 0x55, ...)
...
<--
Stream.sendCommand(0055, 01, dataProceed)
-->
Stream.sendCommand(0001, 05, setVariable32, 0x0234, 0x12345678) //
set a 32-bit variable
--> Stream.sendCommand(0001, 05,
setVariable16, 0x0010, 0x1234) // set a 16-bit variable
-->
Stream.sendCommand(0001, 05, reqVariable16, 0x0010) // request a
variable
<-- Stream.sendCommand(0055, 01, repVariable16,
0x0010, 0x1234) // report a variable
I am not sure at all about the last stuff, may be duplicating what datagrams should be doing.
This is SVN $Revision: 827 $