[PATCH 08/11] Add code to write rom of the OSTC3
Jef Driesen
jef at libdivecomputer.org
Tue Dec 16 07:48:39 PST 2014
On 2014-11-21 21:28, Anton Lundin wrote:
> +static dc_status_t
> +hw_ostc3_device_write_block (dc_device_t *abstract, unsigned int
> addr, unsigned char block[], unsigned int block_size)
> +{
> + dc_status_t rc = DC_STATUS_SUCCESS;
> + hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract;
> + dc_context_t *context = (abstract ? abstract->context : NULL);
> +#if 0
> + // FIXME: This variant doens't work
> + // Things doesn't get written to memory. Timing issues?
> + // Version below works, serial_sleep (2) is probably the thing.
> + unsigned char buffer[4 + block_size];
> + buffer[0] = WRITE_BLOCK;
> + uint24_be_array(addr, buffer + 1);
> + memcpy(buffer + 4, block, block_size);
> +
> + rc = hw_ostc3_transfer(device, NULL, WRITE_BLOCK, buffer,
> sizeof(buffer), NULL, 0);
> + serial_sleep (device->port, 1100);
> + return rc;
> +#else
> + int n;
> + unsigned char response;
> + unsigned char buffer[4];
> + buffer[0] = WRITE_BLOCK;
> + uint24_be_array(addr, buffer + 1);
> +
> + n = serial_write (device->port, buffer, sizeof(buffer));
> + if (n != sizeof(buffer)) {
> + ERROR (context, "failed to send address to device");
> + return EXITCODE (n);
> + }
> +
> + serial_flush (device->port, SERIAL_QUEUE_BOTH);
> + serial_sleep (device->port, 2);
> +
> + // Send the new block
> + n = serial_write (device->port, block, block_size);
> + if (n != block_size) {
> + ERROR (context, "failed to send block to device");
> + return EXITCODE (n);
> + }
> +
> + serial_flush (device->port, SERIAL_QUEUE_BOTH);
Do we really need those two flushes? They might cause serious problems.
When writing data to a serial port, the data ends up in the output
buffer of the driver/hardware, and is transmitted async. Thus when the
write call returns, the data is not always guaranteed to be transmitted
yet. That's why libdivecomputer also calls tcdrain internally, but even
that is not always reliable.
Normally this isn't a real problem, because if the data isn't
transmitted yet, the next read, to receive the response, will just take
a bit longer. But if you flush the input/output buffers, then you might
throw away the data you just send. And if the response arrives fast, you
might throw that away as well.
The serial_flush() call is typically only necessary after opening the
serial port, in order to discard any garbage data that may have been
generated while connecting the serial port, or data that somehow was
somehow still sitting in the internal buffers. The second use case is
when receiving invalid packets. Then you flush the buffers in order in
order to discard all received data and start again from a clean state.
But neither of that applies here.
I just checked the ostc companion source, and their implementation of
the flush function does something completely different than the
libdivecomputer one. They call tcdrain internally. The libdivecomputer
flush function corresponds to the purge function in the ostc companion.
So I'm pretty sure that confirms those two serial_flush() calls should
be removed.
I'm still surprised by the need for that 2ms delay. But okay, if it's
necessary then so be it. Note that on Windows the delay will be a lot
longer than those 2ms. A Windows Sleep() call will typically take at
least 10-15ms.
> + // A sleep lenght copied from ostc-companion
> + // Feels a bit slower than that what that does?
> + serial_sleep (device->port, 1100);
Why do we need this one? I might be wrong, but I think it does something
different than what you think. Based on the comments in the companion
code, it waits the time it takes to write the block to flash. I'm not
exactly sure how it's calculated.
In theory, the 3000ms timeout we're using for reading data should be
able to handle this delay already. Unless the S_READY is send much
later. You should be able to see this in the timings when you enable the
libdivecomputer debug log.
Another thing I noticed is that your sequence is quite different from
the companion one. When mapping the companion calls to the equivalent
libdivecomputer calls, it does this:
sleep(100)
flush(SERIAL_QUEUE_BOTH)
write(buffer + 0, 1)
read(echo, 1)
write(buffer + 1,3)
sleep(2)
write(block, block_size)
sleep(1100)
read(S_READY,1)
The first sleep and flush are most likely unnecessary. Such a cleanup is
only necessary when something went wrong. Since we have no code to
recover from errors, that's not necessary.
With the exception of those sleep calls, that's identical to the
hw_ostc3_transfer() function. Notice how your code is missing the read
of the echo. I'm pretty sure that got lost due to the flushing. So this
probably works correctly only by accident.
The reason why hw_ostc3_transfer doesn't work for you, might be because
you include the WRITE_BLOCK byte in the input buffer. That causes it to
send twice. The ostc probably doesn't like that. Can you have a look at
this? I have the feeling that we can use hw_ostc3_transfer here after
all. Worst case we may need to add those sleep calls there, wrapped in a
"if (cmd == WRITE_BLOCK)".
> + n = serial_read (device->port, &response, sizeof (response));
> + if (n != sizeof (response) || response != S_READY)
> + return EXITCODE (n);
You should split those checks in two parts. Right now you'll return
DC_STATUS_TIMEOUT when response!=S_READY, and that's misleading. If we
can re-use hw_ostc3_transfer, then this is no longer an issue.
Jef
More information about the devel
mailing list