mirror of https://github.com/google/pebble
577 lines
20 KiB
C
577 lines
20 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "drivers/accessory.h"
|
|
|
|
#include "board/board.h"
|
|
#include "console/console_internal.h"
|
|
#include "console/serial_console.h"
|
|
#include "console/prompt.h"
|
|
#include "drivers/dma.h"
|
|
#include "drivers/gpio.h"
|
|
#include "drivers/exti.h"
|
|
#include "drivers/periph_config.h"
|
|
#include "drivers/uart.h"
|
|
#include "kernel/util/stop.h"
|
|
#include "mcu/interrupts.h"
|
|
#include "os/mutex.h"
|
|
#include "os/tick.h"
|
|
#include "services/common/new_timer/new_timer.h"
|
|
#include "services/common/system_task.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
#include "kernel/util/delay.h"
|
|
#include "kernel/util/sleep.h"
|
|
#include "util/attributes.h"
|
|
#include "util/likely.h"
|
|
#include "util/size.h"
|
|
|
|
#include "FreeRTOS.h" /* FreeRTOS Kernel Prototypes/Constants. */
|
|
#include "semphr.h"
|
|
|
|
#define STM32F4_COMPATIBLE
|
|
#define STM32F7_COMPATIBLE
|
|
#include <mcu.h>
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
|
|
//! The default baudrate for the accessory UART.
|
|
#define DEFAULT_BAUD AccessoryBaud115200
|
|
//! How long each interval should be in milliseconds.
|
|
#define ACCESSORY_STOP_INTERVAL_PERIOD_MS (250)
|
|
//! How many intervals we should wait outside of stop mode when we first see any noise on the
|
|
//! serial port.
|
|
#define ACCESSORY_INITIAL_STOP_INTERVALS (500 / ACCESSORY_STOP_INTERVAL_PERIOD_MS)
|
|
//! How many intervals we should wait outside of stop mode when we first see valid data on the
|
|
//! serial port.
|
|
#define ACCESSORY_VALID_DATA_STOP_INTERVALS (3000 / ACCESSORY_STOP_INTERVAL_PERIOD_MS)
|
|
//! How many bytes of send history to keep. This needs to be 3 bytes because the TX buffer will be
|
|
//! moved into the shift register (with a new byte being loaded into the buffer) before we receive
|
|
//! the byte we previously sent. So, when we receive a byte, we will have sent 2 more bytes by then.
|
|
#define SEND_HISTORY_LENGTH (1)
|
|
//! Within accessory_send_stream(), how long we wait for a byte to be sent before timing-out.
|
|
#define SEND_BYTE_TIMEOUT_MS (100)
|
|
|
|
//! We DMA into this buffer as a circular buffer
|
|
#define RX_BUFFER_LENGTH (200)
|
|
static uint8_t DMA_BSS s_rx_buffer[RX_BUFFER_LENGTH];
|
|
|
|
//! The current baud rate
|
|
static uint32_t s_baudrate;
|
|
//! Whether or not the accessory power is enabled
|
|
static bool s_power_enabled;
|
|
//! Whether or not we are in input mode (receiving)
|
|
static bool s_input_enabled;
|
|
//! We'll store up to the last 3 bytes which were sent for detecting bus contention
|
|
typedef struct {
|
|
uint8_t data;
|
|
bool has_data;
|
|
} SendHistory;
|
|
static volatile SendHistory s_send_history;
|
|
//! Flag which states whether or not we've detected bus contention since last disabling input
|
|
static volatile bool s_bus_contention_detected;
|
|
//! Whether or not we sent data since disabling input
|
|
static bool s_sent_data;
|
|
//! The callback for a stream being sent via accessory_send_stream()
|
|
static volatile AccessoryDataStreamCallback s_stream_cb;
|
|
//! Context passed to accessory_send_stream()
|
|
static void *s_stream_context;
|
|
//! Semaphore used for accessory_send_stream()
|
|
static SemaphoreHandle_t s_send_semaphore;
|
|
//! Mutex used for accessory_block() / accessory_unblock()
|
|
static PebbleRecursiveMutex *s_blocked_lock;
|
|
//! Used to track whether or not the accessory_send_stream callback sent a new byte via
|
|
//! accessory_send_byte()
|
|
static volatile bool s_did_send_byte;
|
|
//! Whether or not we should use DMA for receiving
|
|
static bool s_use_dma;
|
|
//! Whether or not DMA is enabled
|
|
static bool s_dma_enabled;
|
|
//! Used by accessory_send_stream() to track whether or not we've sent a byte recently
|
|
static volatile bool s_has_sent_byte;
|
|
|
|
//! We need to disable stop mode in order to receive data on the accessory connector. To do this,
|
|
//! we set up an exti that kicks us out of stop mode when data is seen. Then, we schedule a timer
|
|
//! to check for additional data being seen on the connector. If we go 5 seconds without seeing
|
|
//! data, we can go back into stop mode.
|
|
static struct {
|
|
//! If the accessory connector is currently active...
|
|
bool active;
|
|
|
|
//! The timer that will fire once a second while we're active
|
|
TimerID timer;
|
|
|
|
//! How many intervals have gone by without data being seen
|
|
int intervals_without_data;
|
|
|
|
//! How many intervals we should wait for without data before going back into stop mode
|
|
int max_intervals_without_data;
|
|
|
|
//! If we saw data on the connector since the last time the timer fired.
|
|
bool data_seen_this_interval;
|
|
} s_stop_mode_monitor;
|
|
|
|
static bool prv_rx_irq_handler(UARTDevice *dev, uint8_t data, const UARTRXErrorFlags *err_flags);
|
|
static bool prv_tx_irq_handler(UARTDevice *dev);
|
|
|
|
|
|
static void prv_lock(void) {
|
|
if (mcu_state_is_isr()) {
|
|
// assume we're in an ISR for the UART and don't need to worry about being blocked
|
|
return;
|
|
}
|
|
mutex_lock_recursive(s_blocked_lock);
|
|
}
|
|
|
|
static void prv_unlock(void) {
|
|
if (mcu_state_is_isr()) {
|
|
// assume we're in an ISR for the UART and don't need to worry about being blocked
|
|
return;
|
|
}
|
|
mutex_unlock_recursive(s_blocked_lock);
|
|
}
|
|
|
|
static void prv_enable_dma(void) {
|
|
PBL_ASSERTN(!s_dma_enabled);
|
|
s_dma_enabled = true;
|
|
uart_start_rx_dma(ACCESSORY_UART, s_rx_buffer, sizeof(s_rx_buffer));
|
|
}
|
|
|
|
static void prv_disable_dma(void) {
|
|
if (!s_dma_enabled) {
|
|
return;
|
|
}
|
|
s_dma_enabled = false;
|
|
uart_stop_rx_dma(ACCESSORY_UART);
|
|
}
|
|
|
|
//! The interval timer callback.
|
|
static void prv_timer_interval_expired_cb(void *data) {
|
|
if (!s_stop_mode_monitor.data_seen_this_interval) {
|
|
// The accessory connector didn't have any data since the last time this callback fired.
|
|
++s_stop_mode_monitor.intervals_without_data;
|
|
|
|
if (s_stop_mode_monitor.intervals_without_data >=
|
|
s_stop_mode_monitor.max_intervals_without_data) {
|
|
// Enough intervals have passed and we should now turn stop mode back on.
|
|
stop_mode_enable(InhibitorAccessory);
|
|
|
|
s_stop_mode_monitor.active = false;
|
|
s_stop_mode_monitor.intervals_without_data = 0;
|
|
s_stop_mode_monitor.max_intervals_without_data = 0;
|
|
|
|
new_timer_stop(s_stop_mode_monitor.timer);
|
|
}
|
|
} else {
|
|
// Data was seen, reset the interval counter
|
|
s_stop_mode_monitor.intervals_without_data = 0;
|
|
}
|
|
|
|
// Regardless of what happened, this interval is over and should be reset
|
|
s_stop_mode_monitor.data_seen_this_interval = false;
|
|
}
|
|
|
|
static void prv_start_timer_cb(void *context) {
|
|
new_timer_start(s_stop_mode_monitor.timer, ACCESSORY_STOP_INTERVAL_PERIOD_MS,
|
|
prv_timer_interval_expired_cb, NULL, TIMER_START_FLAG_REPEATING);
|
|
}
|
|
|
|
//! Callback run whenever the EXTI fires
|
|
static void prv_exti_cb(bool *should_context_switch) {
|
|
if (!s_stop_mode_monitor.active) {
|
|
// First time seeing data, let's go active
|
|
|
|
s_stop_mode_monitor.active = true;
|
|
s_stop_mode_monitor.intervals_without_data = 0;
|
|
s_stop_mode_monitor.max_intervals_without_data = ACCESSORY_INITIAL_STOP_INTERVALS;
|
|
|
|
stop_mode_disable(InhibitorAccessory);
|
|
|
|
// Need to flip tasks because we can't start a timer from an interrupt
|
|
system_task_add_callback_from_isr(prv_start_timer_cb, NULL, should_context_switch);
|
|
}
|
|
|
|
s_stop_mode_monitor.data_seen_this_interval = true;
|
|
}
|
|
|
|
//! The UART peripheral only runs if the accessory is not in stop mode. We listen to the txrx
|
|
//! pin on the accessory connector and if we see anything we'll disable stop mode for a few
|
|
//! seconds to see if anyone has something to say.
|
|
static void prv_initialize_exti(void) {
|
|
s_stop_mode_monitor.timer = new_timer_create();
|
|
|
|
gpio_input_init(&BOARD_CONFIG_ACCESSORY.int_gpio);
|
|
exti_configure_pin(BOARD_CONFIG_ACCESSORY.exti, ExtiTrigger_Falling, prv_exti_cb);
|
|
exti_enable(BOARD_CONFIG_ACCESSORY.exti);
|
|
}
|
|
|
|
static void prv_initialize_uart(uint32_t baudrate) {
|
|
#if RECOVERY_FW
|
|
// In PRF / MFG, we'll have a strong (2k) external pull-up, so we should always be open-drain
|
|
const bool is_open_drain = true;
|
|
#else
|
|
// If we raise the baud rate above 115200 we need to configure as push-pull to ensure we are
|
|
// sufficiently driving the line. Ideally, the accessory would have a strong-enough pull-up, but
|
|
// now that we've released use of the accessory port via the smartstrap APIs, we can't easily
|
|
// change this.
|
|
const bool is_open_drain = (baudrate <= 115200);
|
|
#endif
|
|
s_baudrate = baudrate;
|
|
if (is_open_drain) {
|
|
uart_init_open_drain(ACCESSORY_UART);
|
|
} else {
|
|
uart_init(ACCESSORY_UART);
|
|
}
|
|
uart_set_rx_interrupt_handler(ACCESSORY_UART, prv_rx_irq_handler);
|
|
uart_set_tx_interrupt_handler(ACCESSORY_UART, prv_tx_irq_handler);
|
|
uart_set_baud_rate(ACCESSORY_UART, s_baudrate);
|
|
uart_set_rx_interrupt_enabled(ACCESSORY_UART, true);
|
|
}
|
|
|
|
static void prv_initialize_hardware(void) {
|
|
periph_config_acquire_lock();
|
|
|
|
gpio_output_init(&BOARD_CONFIG_ACCESSORY.power_en, GPIO_OType_PP, GPIO_Speed_2MHz);
|
|
gpio_output_set(&BOARD_CONFIG_ACCESSORY.power_en, false); // Turn power off
|
|
|
|
accessory_set_baudrate(DEFAULT_BAUD);
|
|
|
|
periph_config_release_lock();
|
|
|
|
prv_initialize_exti();
|
|
}
|
|
|
|
static void prv_set_baudrate(uint32_t baudrate, bool force_update) {
|
|
if ((baudrate != s_baudrate) || force_update) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Changing accessory connector baud rate to %"PRIu32, baudrate);
|
|
prv_initialize_uart(baudrate);
|
|
if (s_dma_enabled) {
|
|
// we need to reset DMA after resetting the UART
|
|
prv_disable_dma();
|
|
prv_enable_dma();
|
|
}
|
|
}
|
|
}
|
|
|
|
void accessory_init(void) {
|
|
s_send_semaphore = xSemaphoreCreateBinary();
|
|
xSemaphoreGive(s_send_semaphore);
|
|
s_blocked_lock = mutex_create_recursive();
|
|
prv_initialize_hardware();
|
|
accessory_set_power(false);
|
|
accessory_enable_input();
|
|
}
|
|
|
|
void accessory_block(void) {
|
|
prv_lock();
|
|
accessory_send_stream_stop();
|
|
uart_deinit(ACCESSORY_UART);
|
|
}
|
|
|
|
void accessory_unblock(void) {
|
|
// We want to restore the previous baudrate, but clear s_baudrate in order to force a complete
|
|
// re-init of the peripheral.
|
|
prv_set_baudrate(s_baudrate, true /* force_update */);
|
|
prv_unlock();
|
|
}
|
|
|
|
void accessory_send_byte(uint8_t data) {
|
|
// NOTE: this may be run within an ISR
|
|
prv_lock();
|
|
s_has_sent_byte = true;
|
|
s_did_send_byte = true;
|
|
PBL_ASSERTN(!s_input_enabled);
|
|
while (!(uart_is_tx_ready(ACCESSORY_UART))) continue;
|
|
// this section needs to be atomic since the UART IRQ also modifies these variables
|
|
portENTER_CRITICAL();
|
|
if (s_send_history.has_data) {
|
|
// The send buffer is full. This means that the receive interrupt hasn't fired to clear the
|
|
// buffer which indicates that there is bus contention preventing a stop bit from occuring.
|
|
s_bus_contention_detected = true;
|
|
} else {
|
|
s_send_history.data = data;
|
|
s_send_history.has_data = true;
|
|
}
|
|
portEXIT_CRITICAL();
|
|
uart_write_byte(ACCESSORY_UART, data);
|
|
s_sent_data = true;
|
|
prv_unlock();
|
|
}
|
|
|
|
void accessory_send_data(const uint8_t *data, size_t length) {
|
|
// NOTE: this may be run within an ISR
|
|
prv_lock();
|
|
// When sending data, we need to temporarily disable input, as there's only one data line for
|
|
// both directions and any data we send on that line will also be interpreted as data we can
|
|
// read. This means there's a bit of overhead for sending data as we have to also make sure
|
|
// we don't accidentally read it back. If you're going to be sending a large amount of data,
|
|
// calling accessory_disable_input before will give you a nice speed boost as we don't have
|
|
// to wait for it to be safe to turn the input back on after each byte.
|
|
|
|
const bool temporarily_disabled = s_input_enabled;
|
|
if (UNLIKELY(temporarily_disabled)) {
|
|
accessory_disable_input();
|
|
}
|
|
|
|
for (size_t i = 0; i < length; ++i) {
|
|
accessory_send_byte(data[i]);
|
|
}
|
|
|
|
if (UNLIKELY(temporarily_disabled)) {
|
|
accessory_enable_input();
|
|
}
|
|
prv_unlock();
|
|
}
|
|
|
|
bool accessory_send_stream(AccessoryDataStreamCallback stream_callback, void *context) {
|
|
bool success = true;
|
|
prv_lock();
|
|
PBL_ASSERTN(xSemaphoreTake(s_send_semaphore, portMAX_DELAY) == pdPASS);
|
|
PBL_ASSERTN(stream_callback != NULL);
|
|
PBL_ASSERTN(!s_input_enabled);
|
|
if (s_dma_enabled) {
|
|
uart_clear_rx_dma_buffer(ACCESSORY_UART);
|
|
}
|
|
s_stream_context = context;
|
|
s_stream_cb = stream_callback;
|
|
s_has_sent_byte = false;
|
|
uart_set_tx_interrupt_enabled(ACCESSORY_UART, true);
|
|
// Block until the sending is complete, but timeout if we aren't able to send a byte for a while.
|
|
while (xSemaphoreTake(s_send_semaphore, milliseconds_to_ticks(SEND_BYTE_TIMEOUT_MS)) != pdPASS) {
|
|
if (!s_has_sent_byte) {
|
|
// we haven't sent a byte in the last timeout period, so time out the whole send
|
|
s_stream_cb = NULL;
|
|
s_stream_context = NULL;
|
|
success = false;
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Timed-out while sending");
|
|
break;
|
|
}
|
|
s_has_sent_byte = false;
|
|
}
|
|
xSemaphoreGive(s_send_semaphore);
|
|
prv_unlock();
|
|
return success;
|
|
}
|
|
|
|
void accessory_send_stream_stop(void) {
|
|
prv_lock();
|
|
if (s_stream_cb) {
|
|
// wait for any in-progress write to finish
|
|
PBL_ASSERTN(xSemaphoreTake(s_send_semaphore, portMAX_DELAY) == pdPASS);
|
|
xSemaphoreGive(s_send_semaphore);
|
|
}
|
|
uart_set_tx_interrupt_enabled(ACCESSORY_UART, false);
|
|
s_stream_cb = NULL;
|
|
s_stream_context = NULL;
|
|
prv_unlock();
|
|
}
|
|
|
|
void accessory_disable_input(void) {
|
|
// NOTE: This function may be called from an ISR
|
|
prv_lock();
|
|
PBL_ASSERTN(s_input_enabled);
|
|
|
|
s_input_enabled = false;
|
|
s_send_history.has_data = false;
|
|
s_bus_contention_detected = false;
|
|
prv_unlock();
|
|
}
|
|
|
|
void accessory_enable_input(void) {
|
|
// NOTE: This function may be called from an ISR
|
|
if (s_input_enabled) {
|
|
return;
|
|
}
|
|
|
|
prv_lock();
|
|
if (s_sent_data) {
|
|
// wait for the TC flag to be set
|
|
uart_wait_for_tx_complete(ACCESSORY_UART);
|
|
// wait a little for the lines to settle down
|
|
const uint32_t us_to_wait = (1000000 / s_baudrate) * 2;
|
|
delay_us(us_to_wait);
|
|
s_sent_data = false;
|
|
}
|
|
|
|
// Read data and throw it away to clear the state. We don't want to handle data we received
|
|
// while input was disabled
|
|
uart_read_byte(ACCESSORY_UART);
|
|
|
|
s_input_enabled = true;
|
|
prv_unlock();
|
|
}
|
|
|
|
void accessory_use_dma(bool use_dma) {
|
|
prv_lock();
|
|
s_use_dma = use_dma;
|
|
if (s_use_dma) {
|
|
prv_enable_dma();
|
|
} else {
|
|
prv_disable_dma();
|
|
}
|
|
prv_unlock();
|
|
}
|
|
|
|
bool accessory_bus_contention_detected(void) {
|
|
return s_bus_contention_detected;
|
|
}
|
|
|
|
static uint32_t prv_get_baudrate(AccessoryBaud baud_select) {
|
|
const uint32_t BAUDS[] = { 9600, 14400, 19200, 28800, 38400, 57600, 62500, 115200, 125000, 230400,
|
|
250000, 460800, 921600 };
|
|
_Static_assert(ARRAY_LENGTH(BAUDS) == AccessoryBaudInvalid,
|
|
"bauds table doesn't match up with AccessoryBaud enum");
|
|
return BAUDS[baud_select];
|
|
}
|
|
|
|
void accessory_set_baudrate(AccessoryBaud baud_select) {
|
|
prv_lock();
|
|
PBL_ASSERTN(baud_select < AccessoryBaudInvalid);
|
|
prv_set_baudrate(prv_get_baudrate(baud_select), false /* !force_update */);
|
|
prv_unlock();
|
|
}
|
|
|
|
void accessory_set_power(bool on) {
|
|
if (on == s_power_enabled) {
|
|
return;
|
|
}
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Setting accessory power %s", on?"on":"off");
|
|
s_power_enabled = on;
|
|
gpio_output_set(&BOARD_CONFIG_ACCESSORY.power_en, on);
|
|
}
|
|
|
|
bool accessory_is_present(void) {
|
|
accessory_set_power(true);
|
|
gpio_input_init_pull_up_down(&BOARD_CONFIG_ACCESSORY.int_gpio, GPIO_PuPd_DOWN);
|
|
// budget for a capacitance up to ~1uF and a resistance of 10kOhm
|
|
psleep(10);
|
|
bool result = (gpio_input_read(&BOARD_CONFIG_ACCESSORY.int_gpio) == SET);
|
|
gpio_input_init(&BOARD_CONFIG_ACCESSORY.int_gpio);
|
|
return result;
|
|
}
|
|
|
|
// ISRs
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
static bool prv_rx_irq_handler(UARTDevice *dev, uint8_t data, const UARTRXErrorFlags *err_flags) {
|
|
bool should_context_switch = false;
|
|
// We've now seen valid data on the serial port, make sure we stay out of stop mode for a
|
|
// longer period of time.
|
|
s_stop_mode_monitor.max_intervals_without_data = ACCESSORY_VALID_DATA_STOP_INTERVALS;
|
|
if (s_input_enabled) {
|
|
// we are receiving data from the accessory
|
|
if (!err_flags->framing_error) {
|
|
should_context_switch = accessory_manager_handle_character_from_isr((char)data);
|
|
} else if (data == 0x00) {
|
|
should_context_switch = accessory_manager_handle_break_from_isr();
|
|
}
|
|
} else {
|
|
// we are receiving data we just sent since the RX/TX lines are tied together
|
|
if (s_send_history.has_data) {
|
|
if (s_send_history.data != data) {
|
|
// The byte we are receiving doesn't match the next byte in the send queue.
|
|
s_bus_contention_detected = true;
|
|
}
|
|
s_send_history.has_data = false;
|
|
} else {
|
|
// We received a byte without sending and the input is not enabled. This typically indicates
|
|
// a race condition between when we disable input and start sending, or between when we
|
|
// finish sending and enable input. Either way, we can't trust this data so treat it as bus
|
|
// contention.
|
|
s_bus_contention_detected = true;
|
|
}
|
|
}
|
|
if (s_stream_cb) {
|
|
// enable the TXE interrupt for sending the next byte
|
|
uart_set_tx_interrupt_enabled(dev, true);
|
|
}
|
|
return should_context_switch;
|
|
}
|
|
|
|
static bool prv_tx_irq_handler(UARTDevice *dev) {
|
|
bool should_context_switch = false;
|
|
if (s_stream_cb && !s_send_history.has_data) {
|
|
s_did_send_byte = false;
|
|
if (s_stream_cb(s_stream_context)) {
|
|
// the callback MUST send a byte in order for this interrupt to trigger again
|
|
PBL_ASSERTN(s_did_send_byte);
|
|
} else {
|
|
// we're done sending
|
|
portBASE_TYPE was_higher_task_woken = pdFALSE;
|
|
xSemaphoreGiveFromISR(s_send_semaphore, &was_higher_task_woken);
|
|
should_context_switch = (was_higher_task_woken != pdFALSE);
|
|
uart_set_tx_interrupt_enabled(dev, false);
|
|
s_stream_cb = NULL;
|
|
s_stream_context = NULL;
|
|
}
|
|
} else {
|
|
// we haven't yet received back the byte we sent
|
|
uart_set_tx_interrupt_enabled(dev, false);
|
|
}
|
|
return should_context_switch;
|
|
}
|
|
|
|
// Commands
|
|
////////////////////////////////////////////////////////////////////
|
|
void command_accessory_power_set(const char *on) {
|
|
if (!strcmp(on, "on")) {
|
|
accessory_set_power(true);
|
|
} else if (!strcmp(on, "off")) {
|
|
accessory_set_power(false);
|
|
} else {
|
|
prompt_send_response("Usage: accessory power (on|off)");
|
|
}
|
|
}
|
|
|
|
static volatile int32_t s_num_test_bytes;
|
|
static bool prv_test_send_stream(void *context) {
|
|
accessory_send_byte((uint8_t)s_num_test_bytes);
|
|
if (accessory_bus_contention_detected()) {
|
|
return false;
|
|
}
|
|
return (--s_num_test_bytes > 0);
|
|
}
|
|
|
|
void command_accessory_stress_test(void) {
|
|
if (s_baudrate != prv_get_baudrate(DEFAULT_BAUD)) {
|
|
prompt_send_response("FAILED: accessory port is busy");
|
|
return;
|
|
}
|
|
|
|
// send 1 second worth of data
|
|
s_num_test_bytes = 46080;
|
|
accessory_use_dma(true);
|
|
accessory_set_baudrate(AccessoryBaud460800);
|
|
accessory_disable_input();
|
|
const bool success = accessory_send_stream(prv_test_send_stream, NULL);
|
|
accessory_enable_input();
|
|
accessory_set_baudrate(DEFAULT_BAUD);
|
|
accessory_use_dma(false);
|
|
|
|
char buffer[50];
|
|
if (!success) {
|
|
prompt_send_response_fmt(buffer, sizeof(buffer), "FAILED: send timed-out");
|
|
} else if (s_num_test_bytes == 0) {
|
|
prompt_send_response_fmt(buffer, sizeof(buffer), "PASS!");
|
|
} else {
|
|
prompt_send_response_fmt(buffer, sizeof(buffer), "FAILED: %"PRId32" bytes left!",
|
|
s_num_test_bytes);
|
|
}
|
|
}
|