pebble/src/fw/drivers/qemu/qemu_serial.c
Josh Soref 4a26ea9f66 spelling: callback
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2025-01-28 21:19:00 -05:00

338 lines
11 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/qemu/qemu_accel.h"
#include "drivers/qemu/qemu_battery.h"
#include "drivers/qemu/qemu_serial.h"
#include "drivers/qemu/qemu_serial_private.h"
#include "drivers/qemu/qemu_settings.h"
#include "drivers/uart.h"
#include "kernel/events.h"
#include "kernel/pbl_malloc.h"
#include "popups/timeline/peek.h"
#include "process_management/app_manager.h"
#include "shell/system_theme.h"
#include "services/common/clock.h"
#include "services/common/system_task.h"
#include "system/hexdump.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/likely.h"
#include "util/net.h"
#include "util/size.h"
#include "FreeRTOS.h"
#include <bluetooth/qemu_transport.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
static bool prv_uart_irq_handler(UARTDevice *dev, uint8_t byte, const UARTRXErrorFlags *err_flags);
// Our globals
static QemuSerialGlobals s_qemu_state;
// -----------------------------------------------------------------------------------------
// Handle incoming Tap packet data (QemuProtocol_Tap)
static void prv_tap_msg_callback(const uint8_t *data, uint32_t len) {
QemuProtocolTapHeader *hdr = (QemuProtocolTapHeader *)data;
if (len != sizeof(*hdr)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid packet length");
return;
}
QEMU_LOG_DEBUG("Got tap msg: axis: %d, direction: %d", hdr->axis, hdr->direction);
PebbleEvent e = {
.type = PEBBLE_ACCEL_SHAKE_EVENT,
.accel_tap = {
.axis = hdr->axis,
.direction = hdr->direction,
},
};
event_put(&e);
}
// -----------------------------------------------------------------------------------------
// Handle incoming Bluetooth connection packet data (QemuProtocol_BluetoothConnection)
static void prv_bluetooth_connection_msg_callback(const uint8_t *data, uint32_t len) {
QemuProtocolBluetoothConnectionHeader *hdr = (QemuProtocolBluetoothConnectionHeader *)data;
if (len != sizeof(*hdr)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid packet length");
return;
}
QEMU_LOG_DEBUG("Got bluetooth connection msg: connected:%d", hdr->connected);
bool current_status = qemu_transport_is_connected();
bool new_status = (hdr->connected != 0);
if (new_status != current_status && !bt_ctl_is_airplane_mode_on()) {
// Change to new status if we're not in airplane mode
qemu_transport_set_connected(new_status);
}
}
// -----------------------------------------------------------------------------------------
// Handle incoming compass packet data (QemuProtocol_Compass)
static void prv_compass_msg_callback(const uint8_t *data, uint32_t len) {
QemuProtocolCompassHeader *hdr = (QemuProtocolCompassHeader *)data;
if (len != sizeof(*hdr)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid packet length");
return;
}
QEMU_LOG_DEBUG("Got compass msg: magnetic_heading: %"PRId32", calib_status:%u",
ntohl(hdr->magnetic_heading), hdr->calib_status);
PebbleEvent e = {
.type = PEBBLE_COMPASS_DATA_EVENT,
.compass_data = {
.magnetic_heading = ntohl(hdr->magnetic_heading),
.calib_status = hdr->calib_status
}
};
event_put(&e);
}
// -----------------------------------------------------------------------------------------
// Handle incoming time format data (QemuProtocol_TimeFormat)
static void prv_time_format_msg_callback(const uint8_t *data, uint32_t len) {
QemuProtocolTimeFormatHeader *hdr = (QemuProtocolTimeFormatHeader *)data;
if (len != sizeof(*hdr)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid packet length");
return;
}
PBL_LOG(LOG_LEVEL_DEBUG, "Got time format msg: is 24 hour: %d", hdr->is_24_hour);
clock_set_24h_style(hdr->is_24_hour);
}
// -----------------------------------------------------------------------------------------
// Handle incoming timeline peek format data (QemuProtocol_TimelinePeek)
static void prv_timeline_peek_msg_callback(const uint8_t *data, uint32_t len) {
QemuProtocolTimelinePeekHeader *hdr = (QemuProtocolTimelinePeekHeader *)data;
if (len != sizeof(*hdr)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid packet length");
return;
}
PBL_LOG(LOG_LEVEL_DEBUG, "Got timeline peek msg: enabled: %d", hdr->enabled);
#if !RECOVERY_FW && CAPABILITY_HAS_TIMELINE_PEEK
timeline_peek_set_enabled(hdr->enabled);
#endif
}
static void prv_content_size_msg_callback(const uint8_t *data, uint32_t len) {
QemuProtocolContentSizeHeader *hdr = (QemuProtocolContentSizeHeader *)data;
if (len != sizeof(*hdr)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid packet length");
return;
}
PBL_LOG(LOG_LEVEL_DEBUG, "Got content size msg: size: %d", hdr->size);
#if !RECOVERY_FW
system_theme_set_content_size(hdr->size);
// Exit out of any currently running app so we force the UI to update to the new content size
// (must be called from the KernelMain task)
PBL_ASSERT_TASK(PebbleTask_KernelMain);
app_manager_close_current_app(true /* gracefully */);
#endif
}
// -----------------------------------------------------------------------------------------
// List of incoming message handlers
static const QemuMessageHandler s_qemu_endpoints[] = {
// IMPORTANT: These must be in sorted order!!
{ QemuProtocol_SPP, qemu_transport_handle_received_data },
{ QemuProtocol_Tap, prv_tap_msg_callback },
{ QemuProtocol_BluetoothConnection, prv_bluetooth_connection_msg_callback },
{ QemuProtocol_Compass, prv_compass_msg_callback },
{ QemuProtocol_Battery, qemu_battery_msg_callback },
{ QemuProtocol_Accel, qemu_accel_msg_callback },
{ QemuProtocol_TimeFormat, prv_time_format_msg_callback },
{ QemuProtocol_TimelinePeek, prv_timeline_peek_msg_callback },
{ QemuProtocol_ContentSize, prv_content_size_msg_callback },
// Button messages are handled by QEMU directly
};
// -----------------------------------------------------------------------------------------
// Find handler from s_qemu_endpoints for a given protocol
static const QemuMessageHandler* prv_find_handler(uint16_t protocol_id) {
for (size_t i = 0; i < ARRAY_LENGTH(s_qemu_endpoints); ++i) {
const QemuMessageHandler* handler = &s_qemu_endpoints[i];
if (!handler || handler->protocol_id > protocol_id) {
break;
}
if (handler->protocol_id == protocol_id) {
return handler;
}
}
return NULL;
}
// -----------------------------------------------------------------------------------------
void qemu_serial_init(void) {
// Init our state variables
qemu_serial_private_init_state(&s_qemu_state);
// Init the UART
uart_init(QEMU_UART);
uart_set_baud_rate(QEMU_UART, UART_SERIAL_BAUD_RATE);
uart_set_rx_interrupt_handler(QEMU_UART, prv_uart_irq_handler);
// enable the UART RX interrupt
uart_set_rx_interrupt_enabled(QEMU_UART, true);
}
// -----------------------------------------------------------------------------------------
// KernelMain callback triggred by our ISR handler when we detect a high water mark on our
// receive buffer or a footer signature
static void prv_process_receive_buffer(void *context) {
uint32_t msg_bytes;
uint16_t protocol;
// Process ISR receive buffer, see if we have a complete message
// Prevent our ISR from putting more characters in while we muck with the receive buffer by
// disabling UART interrupts while we process it.
while (true) {
uart_set_rx_interrupt_enabled(QEMU_UART, false);
uint8_t *msg_ptr = qemu_serial_private_assemble_message(&s_qemu_state, &msg_bytes, &protocol);
uart_set_rx_interrupt_enabled(QEMU_UART, true);
if (!msg_ptr) {
break;
}
// Dispatch the received message
PBL_LOG(LOG_LEVEL_DEBUG, "Dispatching msg of len %"PRIu32" for protocol %d", msg_bytes,
protocol);
const QemuMessageHandler* handler = prv_find_handler(protocol);
if (!handler) {
PBL_LOG(LOG_LEVEL_WARNING, "No handler for protocol: %d", protocol);
} else {
handler->callback(msg_ptr, msg_bytes);
}
}
}
// -----------------------------------------------------------------------------------------
static bool prv_uart_irq_handler(UARTDevice *dev, uint8_t byte, const UARTRXErrorFlags *err_flags) {
// The interrupt triggers when a byte has been read from the UART. QEMU's
// emulated UARTs don't emulate receive overruns by default so we don't have
// to worry about that case. QEMU will buffer the data stream until we're
// ready to consume more data by reading from the UART again.
PBL_ASSERTN(!err_flags->overrun_error);
// Add to circular buffer. It's safe to assume that the buffer has space
// remaining as the RX interrupt will be disabled from the time the buffer
// fills up until when the buffer is drained.
bool success = shared_circular_buffer_write(&s_qemu_state.isr_buffer, &byte, 1,
false/*advance_slackers*/);
if (!success) {
PBL_LOG(LOG_LEVEL_ERROR, "ISR buf too small 0x%x", byte);
s_qemu_state.recv_error_count++;
}
bool buffer_full = false;
if (!shared_circular_buffer_get_write_space_remaining(&s_qemu_state.isr_buffer)) {
// There's no more room in the buffer, so disable the RX interrupt. No more
// data will be read from the UART until prv_process_receive_buffer() is
// run, draining the buffer and re-enabling the RX interrupt. QEMU will
// buffer the remaining data until the interrupt is re-enabled.
uart_set_rx_interrupt_enabled(dev, false);
buffer_full = true;
}
// Is it time to wake up the main thread?
bool should_context_switch = false;
if (s_qemu_state.recv_error_count || buffer_full ||
(byte == QEMU_FOOTER_LSB && s_qemu_state.prev_byte == QEMU_FOOTER_MSB)) {
if (!s_qemu_state.callback_pending) {
s_qemu_state.callback_pending = true;
PebbleEvent e = {
.type = PEBBLE_CALLBACK_EVENT,
.callback = {
.callback = prv_process_receive_buffer,
.data = NULL
}
};
should_context_switch = event_put_isr(&e);
}
}
s_qemu_state.prev_byte = byte;
return should_context_switch;
}
// -----------------------------------------------------------------------------------------
static void prv_send(const uint8_t *data, uint32_t len) {
QEMU_LOG_DEBUG("Sending data:");
QEMU_HEXDUMP(data, len);
while (len--) {
uart_write_byte(QEMU_UART, *data++);
}
uart_wait_for_tx_complete(QEMU_UART);
}
// -----------------------------------------------------------------------------------------
void qemu_serial_send(QemuProtocol protocol, const uint8_t *data, uint32_t len) {
if (!s_qemu_state.initialized) {
return;
}
mutex_lock(s_qemu_state.qemu_comm_lock);
// Send the header
QemuCommChannelHdr hdr = (QemuCommChannelHdr) {
.signature = htons(QEMU_HEADER_SIGNATURE),
.protocol = htons(protocol),
.len = htons(len)
};
prv_send((uint8_t *)&hdr, sizeof(hdr));
// Send the data
prv_send(data, len);
// Send the footer
QemuCommChannelFooter footer = (QemuCommChannelFooter) {
.signature = htons(QEMU_FOOTER_SIGNATURE)
};
prv_send((uint8_t *)&footer, sizeof(footer));
mutex_unlock(s_qemu_state.qemu_comm_lock);
}