Skip to content

Commit 08c4044

Browse files
committed
pbio/sys/host: fix pbsys_host_stdout_write()
Fix issue with pbsys_host_stdout_write() not being cancellable. The static state variables in the function required that the function be called repeatedly until it returned something other than PBIO_ERROR_AGAIN. However, in MicroPython, an exception may be raised in between calls, which would leave the state variables in an inconsistent state causing incorrect behavior on the next call. We can save code size on platforms that only have one transport by only calling the relevant function instead of trying to duplicate the stream to a non-existent transport. Then, to handle platforms with multiple transports, rework the logic to be able to send data to multiple transports without having to maintain state between calls. This is done by ensuring that we only send the same amount of data to each transport.
1 parent 6fbb44b commit 08c4044

File tree

7 files changed

+110
-53
lines changed

7 files changed

+110
-53
lines changed

bricks/_common_stm32/mphalport.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,22 @@ int mp_hal_stdin_rx_chr(void) {
6060

6161
// Send string of given length
6262
mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) {
63-
while (pbsys_host_tx((const uint8_t *)str, len) == PBIO_ERROR_AGAIN) {
63+
size_t remaining = len;
64+
65+
while (remaining) {
66+
uint32_t size = remaining;
67+
pbio_error_t err = pbsys_host_stdout_write((const uint8_t *)str, &size);
68+
if (err == PBIO_SUCCESS) {
69+
str += size;
70+
remaining -= size;
71+
} else if (err != PBIO_ERROR_AGAIN) {
72+
// Ignoring error for now. This means stdout is lost if Bluetooth/USB
73+
// is disconnected.
74+
return len - remaining;
75+
}
76+
6477
MICROPY_EVENT_POLL_HOOK
6578
}
66-
// Not raising the error. This means stdout lost if host is not connected.
6779

6880
return len;
6981
}

lib/pbio/drv/usb/usb_stm32.c

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ void pbdrv_usb_stm32_handle_vbus_irq(bool active) {
145145
* @param data [in] The data to be sent.
146146
* @param size [in, out] The size of @p data in bytes. After return, @p size
147147
* contains the number of bytes actually written.
148-
* @return ::PBIO_SUCCESS if all @p data was queued, ::PBIO_ERROR_AGAIN
149-
* if not all @p data could not be queued at this time (e.g. buffer
148+
* @return ::PBIO_SUCCESS if some @p data was queued, ::PBIO_ERROR_AGAIN
149+
* if no @p data could not be queued at this time (e.g. buffer
150150
* is full), ::PBIO_ERROR_INVALID_OP if there is not an
151151
* active USB connection or ::PBIO_ERROR_NOT_SUPPORTED
152152
* if this platform does not support USB.
@@ -158,7 +158,6 @@ pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size) {
158158

159159
uint8_t *ptr = usb_stdout_buf;
160160
uint32_t ptr_len = sizeof(usb_stdout_buf);
161-
uint32_t full_size = *size;
162161

163162
// TODO: return PBIO_ERROR_INVALID_OP if app flag is not set. Also need a
164163
// timeout in case the app crashes and doesn't clear the flag on exit.
@@ -174,14 +173,24 @@ pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size) {
174173
*ptr++ = PBIO_PYBRICKS_EVENT_WRITE_STDOUT;
175174
ptr_len--;
176175

177-
*size = MIN(full_size, ptr_len);
176+
*size = MIN(*size, ptr_len);
178177
memcpy(ptr, data, *size);
179178

180179
usb_stdout_sz = 1 + 1 + *size;
181180

182181
process_poll(&pbdrv_usb_process);
183182

184-
return *size == full_size ? PBIO_SUCCESS : PBIO_ERROR_AGAIN;
183+
return PBIO_SUCCESS;
184+
}
185+
186+
uint32_t pbdrv_usb_stdout_tx_available(void) {
187+
// TODO: return UINT32_MAX if app flag is not set.
188+
189+
if (usb_stdout_sz) {
190+
return 0;
191+
}
192+
193+
return sizeof(usb_stdout_buf) - 2; // 2 bytes for header
185194
}
186195

187196
/**

lib/pbio/include/pbdrv/usb.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void);
4646
*/
4747
pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size);
4848

49+
/**
50+
* Gets the number of bytes that can be queued for sending stdout via USB.
51+
*
52+
* Returns UINT32_MAX if there is no USB connection or no app is subscribed to
53+
* stdout.
54+
*
55+
* @return The number of bytes that can be queued.
56+
*/
57+
uint32_t pbdrv_usb_stdout_tx_available(void);
58+
4959
/**
5060
* Indicates if the USB stdout stream is idle.
5161
* @return true if the USB stdout stream is idle.
@@ -62,6 +72,10 @@ static inline pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *si
6272
return PBIO_SUCCESS;
6373
}
6474

75+
static inline uint32_t pbdrv_usb_stdout_tx_available(void) {
76+
return UINT32_MAX;
77+
}
78+
6579
static inline bool pbdrv_usb_stdout_tx_is_idle(void) {
6680
return true;
6781
}

lib/pbio/include/pbsys/host.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ void pbsys_host_stdin_set_callback(pbsys_host_stdin_event_callback_t callback);
3232
void pbsys_host_stdin_flush(void);
3333
uint32_t pbsys_host_stdin_get_available(void);
3434
pbio_error_t pbsys_host_stdin_read(uint8_t *data, uint32_t *size);
35-
pbio_error_t pbsys_host_tx(const uint8_t *data, uint32_t size);
35+
pbio_error_t pbsys_host_stdout_write(const uint8_t *data, uint32_t *size);
3636
bool pbsys_host_tx_is_idle(void);
3737

3838
#else // PBSYS_CONFIG_HOST
@@ -44,7 +44,7 @@ bool pbsys_host_tx_is_idle(void);
4444
#define pbsys_host_stdin_flush()
4545
#define pbsys_host_stdin_get_available() 0
4646
#define pbsys_host_stdin_read(data, size) PBIO_ERROR_NOT_SUPPORTED
47-
#define pbsys_host_tx(data, size) { (void)(data); (void)(size); PBIO_ERROR_NOT_SUPPORTED; }
47+
#define pbsys_host_stdout_write(data, size) { (void)(data); (void)(size); PBIO_ERROR_NOT_SUPPORTED; }
4848
#define pbsys_host_tx_is_idle() false
4949

5050
#endif // PBSYS_CONFIG_HOST

lib/pbio/sys/bluetooth.c

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,15 @@ void pbsys_bluetooth_init(void) {
6767
* @param data [in] The data to be sent.
6868
* @param size [in, out] The size of @p data in bytes. After return, @p size
6969
* contains the number of bytes actually written.
70-
* @return ::PBIO_SUCCESS if all @p data was queued,
71-
* ::PBIO_ERROR_AGAIN if not all @p data could not be
70+
* @return ::PBIO_SUCCESS if some @p data was queued,
71+
* ::PBIO_ERROR_AGAIN if no @p data could be
7272
* queued at this time (e.g. buffer is full),
7373
* ::PBIO_ERROR_INVALID_OP if there is not an
7474
* active Bluetooth connection or ::PBIO_ERROR_NOT_SUPPORTED
7575
* if this platform does not support Bluetooth.
7676
*/
7777
pbio_error_t pbsys_bluetooth_tx(const uint8_t *data, uint32_t *size) {
7878

79-
uint32_t full_size = *size;
80-
8179
// make sure we have a Bluetooth connection
8280
if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) {
8381
return PBIO_ERROR_INVALID_OP;
@@ -94,15 +92,30 @@ pbio_error_t pbsys_bluetooth_tx(const uint8_t *data, uint32_t *size) {
9492
stdout_msg.is_queued = true;
9593
}
9694

97-
if ((*size = lwrb_write(&stdout_ring_buf, data, full_size)) == 0) {
95+
if ((*size = lwrb_write(&stdout_ring_buf, data, *size)) == 0) {
9896
return PBIO_ERROR_AGAIN;
9997
}
10098

10199
// poke the process to start tx soon-ish. This way, we can accumulate up to
102100
// MAX_CHAR_SIZE bytes before actually transmitting
103101
pbsys_bluetooth_process_poll();
104102

105-
return *size == full_size ? PBIO_SUCCESS : PBIO_ERROR_AGAIN;
103+
return PBIO_SUCCESS;
104+
}
105+
106+
/**
107+
* Gets the number of bytes that can be queued for transmission via Bluetooth.
108+
*
109+
* If there is no connection, this will return %UINT32_MAX.
110+
*
111+
* @returns The number of bytes that can be queued.
112+
*/
113+
uint32_t pbsys_bluetooth_tx_available(void) {
114+
if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) {
115+
return UINT32_MAX;
116+
}
117+
118+
return lwrb_get_free(&stdout_ring_buf);
106119
}
107120

108121
/**

lib/pbio/sys/bluetooth.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ void pbsys_bluetooth_process_poll(void);
1616

1717
void pbsys_bluetooth_init(void);
1818
pbio_error_t pbsys_bluetooth_tx(const uint8_t *data, uint32_t *size);
19+
uint32_t pbsys_bluetooth_tx_available(void);
1920
bool pbsys_bluetooth_tx_is_idle(void);
2021

2122
#else // PBSYS_CONFIG_BLUETOOTH
@@ -25,6 +26,9 @@ bool pbsys_bluetooth_tx_is_idle(void);
2526
static inline pbio_error_t pbsys_bluetooth_tx(const uint8_t *data, uint32_t *size) {
2627
return PBIO_ERROR_NOT_SUPPORTED;
2728
}
29+
static inline uint32_t pbsys_bluetooth_tx_available(void) {
30+
return UINT32_MAX;
31+
}
2832
static inline bool pbsys_bluetooth_tx_is_idle(void) {
2933
return false;
3034
}

lib/pbio/sys/host.c

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <lwrb/lwrb.h>
99

10+
#include <pbdrv/bluetooth.h>
1011
#include <pbdrv/usb.h>
1112
#include <pbsys/host.h>
1213

@@ -101,53 +102,57 @@ pbio_error_t pbsys_host_stdin_read(uint8_t *data, uint32_t *size) {
101102
}
102103

103104
/**
104-
* Transmits data over Bluetooth and USB.
105+
* Transmits data over any connected transport that is subscribed to Pybricks
106+
* protocol events.
105107
*
106-
* Should be called in a loop with the same arguments until it no longer
107-
* returns ::PBIO_ERROR_AGAIN.
108+
* This may perform partial writes. Callers should check the number of bytes
109+
* actually written and call again with the remaining data until all data is
110+
* written.
108111
*
109-
* @param data [in] The data to transmit.
110-
* @param size [in] The size of the data to transmit.
111-
* @return ::PBIO_ERROR_AGAIN if the data is still being transmitted
112-
* ::PBIO_SUCCESS if complete or failed.
112+
* @param data [in] The data to transmit.
113+
* @param size [inout] The size of the data to transmit. Upon success, this
114+
* contains the number of bytes actually processed.
115+
* @return ::PBIO_ERROR_INVALID_OP if there is no active transport,
116+
* ::PBIO_ERROR_AGAIN if no @p data could be queued,
117+
* ::PBIO_SUCCESS if at least some data was queued.
113118
*/
114-
pbio_error_t pbsys_host_tx(const uint8_t *data, uint32_t size) {
115-
116-
static bool transmitting = false;
117-
static uint32_t tx_done_ble;
118-
static uint32_t tx_done_usb;
119-
120-
if (!transmitting) {
121-
tx_done_ble = 0;
122-
tx_done_usb = 0;
123-
transmitting = true;
119+
pbio_error_t pbsys_host_stdout_write(const uint8_t *data, uint32_t *size) {
120+
#if PBSYS_CONFIG_BLUETOOTH && (!PBDRV_CONFIG_USB || PBDRV_CONFIG_USB_CHARGE_ONLY)
121+
return pbsys_bluetooth_tx(data, size);
122+
#elif !PBSYS_CONFIG_BLUETOOTH && PBDRV_CONFIG_USB && !PBDRV_CONFIG_USB_CHARGE_ONLY
123+
return pbdrv_usb_stdout_tx(data, size);
124+
#elif PBSYS_CONFIG_BLUETOOTH && PBDRV_CONFIG_USB && !PBDRV_CONFIG_USB_CHARGE_ONLY
125+
126+
uint32_t bt_avail = pbsys_bluetooth_tx_available();
127+
uint32_t usb_avail = pbdrv_usb_stdout_tx_available();
128+
uint32_t available = MIN(UINT32_MAX, MIN(bt_avail, usb_avail));
129+
130+
// If all tx_available() calls returned UINT32_MAX, then there is one listening.
131+
if (available == UINT32_MAX) {
132+
return PBIO_ERROR_INVALID_OP;
124133
}
125-
126-
pbio_error_t err_ble = PBIO_SUCCESS;
127-
pbio_error_t err_usb = PBIO_SUCCESS;
128-
uint32_t size_now;
129-
130-
if (tx_done_ble < size) {
131-
size_now = size - tx_done_ble;
132-
err_ble = pbsys_bluetooth_tx(data + tx_done_ble, &size_now);
133-
tx_done_ble += size_now;
134+
// If one or more tx_available() calls returned 0, then we need to wait.
135+
if (available == 0) {
136+
return PBIO_ERROR_AGAIN;
134137
}
135138

136-
if (tx_done_usb < size) {
137-
size_now = size - tx_done_usb;
138-
err_usb = pbdrv_usb_stdout_tx(data + tx_done_usb, &size_now);
139-
tx_done_usb += size_now;
140-
}
139+
// Limit size to smallest available space from all transports so that we
140+
// don't do partial writes to one transport and not the other.
141+
*size = MIN(*size, available);
141142

142-
// Keep waiting as long as at least has not completed or errored.
143-
if (err_ble == PBIO_ERROR_AGAIN || err_usb == PBIO_ERROR_AGAIN) {
144-
return PBIO_ERROR_AGAIN;
145-
}
143+
// Unless something became disconnected in an interrupt handler, these
144+
// functions should always succeed since we already checked tx_available().
145+
// And if both somehow got disconnected at the same time, it is not a big deal
146+
// if we return PBIO_SUCCESS without actually sending anything.
147+
(void)pbsys_bluetooth_tx(data, size);
148+
(void)pbdrv_usb_stdout_tx(data, size);
149+
150+
return PBIO_SUCCESS;
146151

147-
// Both of them are either complete or failed. The caller of this function
148-
// does not currently raise errors, so we just return success.
149-
transmitting = false;
152+
#else
153+
// stdout goes to /dev/null
150154
return PBIO_SUCCESS;
155+
#endif
151156
}
152157

153158
bool pbsys_host_tx_is_idle(void) {

0 commit comments

Comments
 (0)