Summary
I tested continuous binary data transmission from a Dabao board to a PC over USB serial using usb_bao1x::UsbHid::serial_send().
When I add a delay of approximately 1 ms between calls, transmission continues at about 4.3–4.8 MB/s.
However, when I call serial_send() continuously without any delay, the receive rate on the PC drops to 0 bytes/sec after several seconds and does not recover.
I have not yet identified the root cause of the stall, but I found several points in the current API design that I would like to clarify.
Reproduction
After waiting for the USB device state to become Configured, repeatedly send a fixed 3840-byte buffer:
loop {
usb.serial_send(&tx_data)?;
}Observed behavior
With a 1 ms delay
Adding the following after every call:
timer.sleep_ms(1).ok();allows transmission to continue at approximately:
4435200 bytes/sec
4335104 bytes/sec
4295936 bytes/sec
4492288 bytes/sec
4745472 bytes/sec
...Without a delay
When the delay is removed, the receive rate eventually becomes:
0 bytes/sec
0 bytes/sec
...and does not recover.
Findings from the current implementation
services/usb-bao1x/src/lib.rs
The current serial_send() implementation calculates:
let sendable_len = data.len().min(SERIAL_BINARY_BUFLEN);It then copies the input data using:
sender.d.extend_from_slice(data);and finally performs the IPC send and returns:
buf.send(...).ok();
Ok(sendable_len)The returned value is therefore computed locally from:
data.len().min(SERIAL_BINARY_BUFLEN)It is not derived from the result of serial_port.write() inside the USB service.
The result of Buffer::send() is also discarded through .ok().
There also appears to be an inconsistency when the input is larger than SERIAL_BINARY_BUFLEN: sendable_len is limited, but extend_from_slice(data) copies the entire input slice.
services/usb-bao1x/src/main.rs
In the Opcode::SerialSendData handler, the return values from:
serial_port.write(...)
are accumulated in:
total_sent
However, total_sent is only written to the log and is not returned to the caller.
In addition, if a write() call fails for one chunk, the handler logs the error but continues processing later chunks.
Proposal
Independently of the root cause of the stall, it may be useful to provide an API that allows the caller to obtain the number of bytes actually accepted by serial_port.write().
For example, would the following approach be appropriate for this service?
- Add a new synchronous serial-send API.
- Wait for the USB service to process the request.
- Return the number of bytes accepted from the beginning of the input buffer.
- Stop processing after the first short write or error. Allow the caller to retry the remaining bytes.
This could allow callers to implement partial-write handling and retry logic without changing the behavior of the existing asynchronous serial_send() API.
If there is a more appropriate design for this service, I would appreciate guidance.
Questions
- Is the current
serial_send()intentionally designed as a best-effort asynchronous API? - What is the intended meaning of the current
serial_send()return value? - Would adding a new API that returns the number of bytes accepted by
serial_port.write()be consistent with the design of this service?
If this direction is acceptable, I would like to prepare a pull request implementing and testing the change on Dabao hardware.
Activity
30You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the serial_send() command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, the serial_send() API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.
send_str() was instead the API that got fully fleshed out. You can see for example that send_str() uses a blocking call (buf.lend_mut() instead of buf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.
As written, send_str() will simply flood the USB stack's input queue, which leads to the deadlock you observe.
You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for serial_send() is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.
One "feature" of serial_send() is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.
I think fundamentally it would be less surprising if usb_send() blocked until the data was sent. So maybe it would make sense to change the current API into something like usb_send_nb() (usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.
Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.
You're correct on all your observations about serial_send().
Basically, the
serial_send()command never matured from a placeholder. In fact, aside from some test functions and keyboard return values, theserial_send()API is barely used. So, what you're seeing is an API that has a lot of placeholder code that was "good enough" for what it needed to be at the time.send_str()was instead the API that got fully fleshed out. You can see for example thatsend_str()uses a blocking call (buf.lend_mut()instead ofbuf.send()), which means that the call would self-synchronize and rate limit if the data rate got too high.As written,
send_str()will simply flood the USB stack's input queue, which leads to the deadlock you observe.You are correct to observe that this API could do with some improvement.
So, to some extent, the actual API format for
serial_send()is up for grabs - I would turn the question around and ask what sorts of behavior you would want to see from that, and then we can work on implementing it so that it meets your needs.One "feature" of
serial_send()is that it doesn't block if no host is connected - it just ".ok()"'s out if there is an error. This is what allows the one "actual" use of it (to do the echo back of characters on serial) to not block when no host is connected.I think fundamentally it would be less surprising if
usb_send()blocked until the data was sent. So maybe it would make sense to change the current API into something likeusb_send_nb()(usb send non-blocking) for backward compatibility with the one use, and convert usb_send() into a more sensible API.Another option would be to upgrade the console REPL shell to be aware of when no USB is attached and simply not make the call to send when nothing is attached. That would probably be the more correct way to do it, but this would require a bit more testing because connection state reporting from the USB API hasn't been thoroughly tested, so there could be a bit of a side quest to make that more robust.
I would suggest since that's not the issue you're trying to solve, let's not put that improvement in your critical path. If you want to wait, I will try to take a swag at improving this API but it might take a week or two for me to get to it. But if you're eager to make a version that suits your needs, you're welcome to try.
One other significant note - there is a limit of 4096 bytes (one page of memory) currently for a standard "Buffer" object to be moved between processes, and that puts a cap on how much data you can send at once. There's some fairly significant overhead to moving a memory page, so if you anticipate sending much larger buffers, there is a way to do that - but it does require considering that explicitly in the API design.
One thing I might suggest is, start with just a page (since that's easy) and then if the performance still isn't good enough we can look int optimizations like multi-page movement extensions to see if that helps.