/* * 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 #include #include #include 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); }