/* * 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 "kernel/event_loop.h" #include "kernel/events.h" #include "kernel/pbl_malloc.h" #include "services/common/comm_session/session_transport.h" #include "system/passert.h" #include "system/logging.h" #include "comm/bt_lock.h" #include "drivers/qemu/qemu_serial.h" #include "drivers/qemu/qemu_serial_private.h" #include "util/math.h" #include #include typedef struct { CommSession *session; } QemuTransport; // ----------------------------------------------------------------------------------------- // Static Variables (protected by bt_lock) //! The CommSession that the QEMU transport is managing. //! Currently there's only one for the System session. static QemuTransport s_transport; // ----------------------------------------------------------------------------------------- // bt_lock() is held by caller static void prv_send_next(Transport *transport) { CommSession *session = s_transport.session; PBL_ASSERTN(session); size_t bytes_remaining = comm_session_send_queue_get_length(session); if (bytes_remaining == 0) { return; } const size_t temp_buffer_size = MIN(bytes_remaining, QEMU_MAX_DATA_LEN); uint8_t *temp_buffer = kernel_malloc_check(temp_buffer_size); while (bytes_remaining) { const size_t bytes_to_copy = MIN(bytes_remaining, temp_buffer_size); comm_session_send_queue_copy(session, 0 /* start_offset */, bytes_to_copy, temp_buffer); qemu_serial_send(QemuProtocol_SPP, temp_buffer, bytes_to_copy); comm_session_send_queue_consume(session, bytes_to_copy); bytes_remaining -= bytes_to_copy; } kernel_free(temp_buffer); } // ----------------------------------------------------------------------------------------- // bt_lock() is held by caller static void prv_reset(Transport *transport) { PBL_LOG(LOG_LEVEL_INFO, "Unimplemented"); } static void prv_granted_kernel_main_cb(void *ctx) { ResponsivenessGrantedHandler granted_handler = ctx; granted_handler(); } static void prv_set_connection_responsiveness( Transport *transport, BtConsumer consumer, ResponseTimeState state, uint16_t max_period_secs, ResponsivenessGrantedHandler granted_handler) { PBL_LOG(LOG_LEVEL_INFO, "Consumer %d: requesting change to %d for %" PRIu16 "seconds", consumer, state, max_period_secs); // it's qemu, our request to bump the speed is always granted! if (granted_handler) { launcher_task_add_callback(prv_granted_kernel_main_cb, granted_handler); } } static CommSessionTransportType prv_get_type(struct Transport *transport) { return CommSessionTransportType_QEMU; } //! Defined in session.c extern void comm_session_set_capabilities( CommSession *session, CommSessionCapability capability_flags); // ----------------------------------------------------------------------------------------- void qemu_transport_set_connected(bool is_connected) { bt_lock(); const bool transport_is_connected = (s_transport.session); if (transport_is_connected == is_connected) { bt_unlock(); return; } static const TransportImplementation s_qemu_transport_implementation = { .send_next = prv_send_next, .reset = prv_reset, .set_connection_responsiveness = prv_set_connection_responsiveness, .get_type = prv_get_type, }; bool send_event = true; if (is_connected) { s_transport.session = comm_session_open((Transport *) &s_transport, &s_qemu_transport_implementation, TransportDestinationHybrid); if (!s_transport.session) { PBL_LOG(LOG_LEVEL_ERROR, "CommSession couldn't be opened"); send_event = false; } // Give it the appropriate capabilities const CommSessionCapability capabilities = CommSessionRunState | CommSessionInfiniteLogDumping | CommSessionVoiceApiSupport | CommSessionAppMessage8kSupport; comm_session_set_capabilities(s_transport.session, capabilities); } else { comm_session_close(s_transport.session, CommSessionCloseReason_UnderlyingDisconnection); s_transport.session = NULL; } if (send_event) { PebbleEvent e = { .type = PEBBLE_BT_CONNECTION_EVENT, .bluetooth = { .connection = { .state = (s_transport.session) ? PebbleBluetoothConnectionEventStateConnected : PebbleBluetoothConnectionEventStateDisconnected } } }; event_put(&e); } bt_unlock(); } // ----------------------------------------------------------------------------------------- bool qemu_transport_is_connected(void) { return (s_transport.session != NULL); } // ----------------------------------------------------------------------------------------- // Handle incoming Qemu-SPP packet data void qemu_transport_handle_received_data(const uint8_t *data, uint32_t length) { bt_lock(); if (!s_transport.session) { PBL_LOG(LOG_LEVEL_ERROR, "Received QEMU serial data, but session not connected!"); goto unlock; } comm_session_receive_router_write(s_transport.session, data, length); unlock: bt_unlock(); }