Refactor USB/UART mutexes, code cleanup, CoreMutex addition

Add a CoreMutex class which implements a deadlock-safe mutex and reqork
the SerialUSB and SerialUART classes to use it to synchronize output
when in a multicore sketch.
parent 2464d604
/*
* CoreMutex for the Raspberry Pi Pico RP2040
*
* Implements a deadlock-safe multicore mutex for sharing things like the
* USB or UARTs.
*
* Copyright (c) 2021 Earle F. Philhower, III <earlephilhower@yahoo.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include "pico/mutex.h"
class CoreMutex {
public:
CoreMutex(mutex_t *mutex) {
uint32_t owner;
_mutex = mutex;
_acquired = false;
if (!mutex_try_enter(_mutex, &owner)) {
if (owner == get_core_num()) { // Deadlock!
return;
}
mutex_enter_blocking(_mutex);
}
_acquired = true;
}
~CoreMutex() {
if (_acquired) {
mutex_exit(_mutex);
}
}
operator bool() {
return _acquired;
}
private:
mutex_t *_mutex;
bool _acquired;
};
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
*/ */
#include "SerialUART.h" #include "SerialUART.h"
#include "CoreMutex.h"
#include <hardware/uart.h> #include <hardware/uart.h>
#include <hardware/gpio.h> #include <hardware/gpio.h>
...@@ -56,6 +57,13 @@ bool SerialUART::setTX(pin_size_t tx) { ...@@ -56,6 +57,13 @@ bool SerialUART::setTX(pin_size_t tx) {
} }
} }
SerialUART::SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx) {
_uart = uart;
_tx = tx;
_rx = rx;
mutex_init(&_mutex);
}
void SerialUART::begin(unsigned long baud, uint16_t config) { void SerialUART::begin(unsigned long baud, uint16_t config) {
_baud = baud; _baud = baud;
uart_init(_uart, baud); uart_init(_uart, baud);
...@@ -84,12 +92,16 @@ void SerialUART::begin(unsigned long baud, uint16_t config) { ...@@ -84,12 +92,16 @@ void SerialUART::begin(unsigned long baud, uint16_t config) {
} }
void SerialUART::end() { void SerialUART::end() {
if (!_running) {
return;
}
uart_deinit(_uart); uart_deinit(_uart);
_running = false; _running = false;
} }
int SerialUART::peek() { int SerialUART::peek() {
if (!_running) { CoreMutex m(&_mutex);
if (!_running || !m) {
return -1; return -1;
} }
if (_peek >= 0) { if (_peek >= 0) {
...@@ -100,7 +112,8 @@ int SerialUART::peek() { ...@@ -100,7 +112,8 @@ int SerialUART::peek() {
} }
int SerialUART::read() { int SerialUART::read() {
if (!_running) { CoreMutex m(&_mutex);
if (!_running || !m) {
return -1; return -1;
} }
if (_peek >= 0) { if (_peek >= 0) {
...@@ -112,28 +125,32 @@ int SerialUART::read() { ...@@ -112,28 +125,32 @@ int SerialUART::read() {
} }
int SerialUART::available() { int SerialUART::available() {
if (!_running) { CoreMutex m(&_mutex);
if (!_running || !m) {
return 0; return 0;
} }
return (uart_is_readable(_uart)) ? 1 : 0; return (uart_is_readable(_uart)) ? 1 : 0;
} }
int SerialUART::availableForWrite() { int SerialUART::availableForWrite() {
if (!_running) { CoreMutex m(&_mutex);
if (!_running || !m) {
return 0; return 0;
} }
return (uart_is_writable(_uart)) ? 1 : 0; return (uart_is_writable(_uart)) ? 1 : 0;
} }
void SerialUART::flush() { void SerialUART::flush() {
// TODO, must be smarter way. Now, just sleep long enough to guarantee a full FIFO goes out (very conservative) CoreMutex m(&_mutex);
//int us_per_bit = 1 + (1000000 / _baud); if (!_running || !m) {
//delayMicroseconds(us_per_bit * 32 * 8); return;
}
uart_default_tx_wait_blocking(); uart_default_tx_wait_blocking();
} }
size_t SerialUART::write(uint8_t c) { size_t SerialUART::write(uint8_t c) {
if (!_running) { CoreMutex m(&_mutex);
if (!_running || !m) {
return 0; return 0;
} }
uart_putc_raw(_uart, c); uart_putc_raw(_uart, c);
...@@ -141,7 +158,8 @@ size_t SerialUART::write(uint8_t c) { ...@@ -141,7 +158,8 @@ size_t SerialUART::write(uint8_t c) {
} }
size_t SerialUART::write(const uint8_t *p, size_t len) { size_t SerialUART::write(const uint8_t *p, size_t len) {
if (!_running) { CoreMutex m(&_mutex);
if (!_running || !m) {
return 0; return 0;
} }
size_t cnt = len; size_t cnt = len;
......
...@@ -24,12 +24,13 @@ ...@@ -24,12 +24,13 @@
#include <Arduino.h> #include <Arduino.h>
#include "api/HardwareSerial.h" #include "api/HardwareSerial.h"
#include <stdarg.h> #include <stdarg.h>
#include "CoreMutex.h"
extern "C" typedef struct uart_inst uart_inst_t; extern "C" typedef struct uart_inst uart_inst_t;
class SerialUART : public HardwareSerial { class SerialUART : public HardwareSerial {
public: public:
SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx) { _uart = uart; _tx = tx; _rx = rx; } SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx);
// Select the pinout. Call before .begin() // Select the pinout. Call before .begin()
bool setRX(pin_size_t pin); bool setRX(pin_size_t pin);
...@@ -79,6 +80,7 @@ private: ...@@ -79,6 +80,7 @@ private:
pin_size_t _tx, _rx; pin_size_t _tx, _rx;
int _baud; int _baud;
int _peek; int _peek;
mutex_t _mutex;
}; };
extern SerialUART Serial1; // HW UART 0 extern SerialUART Serial1; // HW UART 0
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
*/ */
#include <Arduino.h> #include <Arduino.h>
#include "CoreMutex.h"
#include "tusb.h" #include "tusb.h"
#include "pico/time.h" #include "pico/time.h"
...@@ -182,82 +183,52 @@ void SerialUSB::end() { ...@@ -182,82 +183,52 @@ void SerialUSB::end() {
} }
int SerialUSB::peek() { int SerialUSB::peek() {
if (!_running) { CoreMutex m(&usb_mutex);
if (!_running || !m) {
return 0; return 0;
} }
uint8_t c; uint8_t c;
uint32_t owner; return tud_cdc_peek(0, &c) ? (int) c : -1;
if (!mutex_try_enter(&usb_mutex, &owner)) {
if (owner == get_core_num()) return -1; // would deadlock otherwise
mutex_enter_blocking(&usb_mutex);
}
auto ret = tud_cdc_peek(0, &c) ? (int) c : -1;
mutex_exit(&usb_mutex);
return ret;
} }
int SerialUSB::read() { int SerialUSB::read() {
if (!_running) { CoreMutex m(&usb_mutex);
if (!_running || !m) {
return -1; return -1;
} }
uint32_t owner;
if (!mutex_try_enter(&usb_mutex, &owner)) {
if (owner == get_core_num()) return -1; // would deadlock otherwise
mutex_enter_blocking(&usb_mutex);
}
if (tud_cdc_connected() && tud_cdc_available()) { if (tud_cdc_connected() && tud_cdc_available()) {
int ch = tud_cdc_read_char(); return tud_cdc_read_char();
mutex_exit(&usb_mutex);
return ch;
} }
mutex_exit(&usb_mutex);
return -1; return -1;
} }
int SerialUSB::available() { int SerialUSB::available() {
if (!_running) { CoreMutex m(&usb_mutex);
if (!_running || !m) {
return 0; return 0;
} }
uint32_t owner; return tud_cdc_available();
if (!mutex_try_enter(&usb_mutex, &owner)) {
if (owner == get_core_num()) return 0; // would deadlock otherwise
mutex_enter_blocking(&usb_mutex);
}
auto ret = tud_cdc_available();
mutex_exit(&usb_mutex);
return ret;
} }
int SerialUSB::availableForWrite() { int SerialUSB::availableForWrite() {
if (!_running) { CoreMutex m(&usb_mutex);
if (!_running || !m) {
return 0; return 0;
} }
uint32_t owner; return tud_cdc_write_available();
if (!mutex_try_enter(&usb_mutex, &owner)) {
if (owner == get_core_num()) return 0; // would deadlock otherwise
mutex_enter_blocking(&usb_mutex);
}
auto ret = tud_cdc_write_available();
mutex_exit(&usb_mutex);
return ret;
} }
void SerialUSB::flush() { void SerialUSB::flush() {
if (!_running) { CoreMutex m(&usb_mutex);
if (!_running || !m) {
return; return;
} }
uint32_t owner;
if (!mutex_try_enter(&usb_mutex, &owner)) {
if (owner == get_core_num()) return; // would deadlock otherwise
mutex_enter_blocking(&usb_mutex);
}
tud_cdc_write_flush(); tud_cdc_write_flush();
mutex_exit(&usb_mutex);
} }
size_t SerialUSB::write(uint8_t c) { size_t SerialUSB::write(uint8_t c) {
...@@ -265,16 +236,12 @@ size_t SerialUSB::write(uint8_t c) { ...@@ -265,16 +236,12 @@ size_t SerialUSB::write(uint8_t c) {
} }
size_t SerialUSB::write(const uint8_t *buf, size_t length) { size_t SerialUSB::write(const uint8_t *buf, size_t length) {
if (!_running) { CoreMutex m(&usb_mutex);
if (!_running || !m) {
return 0; return 0;
} }
static uint64_t last_avail_time; static uint64_t last_avail_time;
uint32_t owner;
if (!mutex_try_enter(&usb_mutex, &owner)) {
if (owner == get_core_num()) return 0; // would deadlock otherwise
mutex_enter_blocking(&usb_mutex);
}
int i = 0; int i = 0;
if (tud_cdc_connected()) { if (tud_cdc_connected()) {
for (int i = 0; i < length;) { for (int i = 0; i < length;) {
...@@ -300,24 +267,17 @@ size_t SerialUSB::write(const uint8_t *buf, size_t length) { ...@@ -300,24 +267,17 @@ size_t SerialUSB::write(const uint8_t *buf, size_t length) {
// reset our timeout // reset our timeout
last_avail_time = 0; last_avail_time = 0;
} }
mutex_exit(&usb_mutex);
return i; return i;
} }
SerialUSB::operator bool() { SerialUSB::operator bool() {
if (!_running) { CoreMutex m(&usb_mutex);
if (!_running || !m) {
return false; return false;
} }
uint32_t owner;
if (!mutex_try_enter(&usb_mutex, &owner)) {
if (owner == get_core_num()) return -1; // would deadlock otherwise
mutex_enter_blocking(&usb_mutex);
}
tud_task(); tud_task();
auto ret = tud_cdc_connected(); return tud_cdc_connected();
mutex_exit(&usb_mutex);
return ret;
} }
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment