mirror of
https://github.com/google/pebble.git
synced 2025-06-07 02:33:12 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
284
src/fw/drivers/qemu/qemu_accel.c
Normal file
284
src/fw/drivers/qemu/qemu_accel.c
Normal file
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
//! The QEMU accelerometer driver is pretty broken, but it requires
|
||||
//! a complete overhaul of both the QEMU Serial messages and all senders
|
||||
//! of those messages (pebble-tool and CloudPebble, via libpebble2) to
|
||||
//! fix the brokenness. Since it's not a critical feature, the
|
||||
//! brokenness will stay for the time being.
|
||||
//!
|
||||
//! What's broken about it? The protocol is braindead: it doesn't know
|
||||
//! anything about sample rates. The senders just send a sequence of
|
||||
//! (x,y,z) tuples with no timing information attached. The driver then
|
||||
//! plays them back one after the other at whatever sample rate the
|
||||
//! accel manager happens to request. This means that depending on the
|
||||
//! sample rates that the samples were recorded and the current
|
||||
//! configured sample rate, the samples could be replayed anywhere from
|
||||
//! 10x slower to 10x faster than they were recorded (100 Hz recording
|
||||
//! with 10 Hz replay, or vice versa).
|
||||
//!
|
||||
//! The driver was exceptionally braindead before, buffering up all of
|
||||
//! the samples it could and replaying them from a 256-sample deep
|
||||
//! buffer. With a typical replay rate of 25 Hz and samples being
|
||||
//! recorded at 100 Hz, that results in samples being replayed at 1/4
|
||||
//! speed with ten second latency. No good.
|
||||
//!
|
||||
//! The way libpebble2/pebble-tool/CloudPebble sends accel samples to be
|
||||
//! replayed is also braindead. It pays no attention to the
|
||||
//! QemuProtocolAccelResponse messages and just sends samples as soon as
|
||||
//! they're received. So for replaying samples from the command-line or
|
||||
//! a file, they're all batched up and sent in a single message. Samples
|
||||
//! being recorded live from a phone are taken at 100 Hz and sent to
|
||||
//! QEMU as soon as they are received. By knowing how the protocol is
|
||||
//! actually used, we can improve the user experience quite
|
||||
//! significantly, making the driver a bit simpler in the process.
|
||||
//! Instead of buffering all samples as they are received, throw out and
|
||||
//! replace the sample buffer every time a new QemuProtocolAccel message
|
||||
//! is received. Play those back at the driver's current sampling rate,
|
||||
//! latching the last sample received if the sample buffer underruns.
|
||||
//! Replaying of prerecorded accelerometer samples e.g. from a file will
|
||||
//! still play back at the wrong sample rate most of the time, but live
|
||||
//! replay from a phone will work in realtime with minimal latency
|
||||
//! without speeding up or slowing down the signal during replay.
|
||||
|
||||
#include "drivers/accel.h"
|
||||
#include "drivers/imu/bmi160/bmi160.h"
|
||||
|
||||
#include "drivers/qemu/qemu_serial.h"
|
||||
#include "drivers/rtc.h"
|
||||
#include "os/mutex.h"
|
||||
#include "pebble_errors.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/math.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
#define ACCEL_LOG_DEBUG(fmt, args...) PBL_LOG_D(LOG_DOMAIN_ACCEL, LOG_LEVEL_DEBUG, fmt, ## args)
|
||||
|
||||
|
||||
static bool s_initialized;
|
||||
static PebbleMutex * s_accel_mutex;
|
||||
|
||||
static uint32_t s_sampling_interval_ms = 0;
|
||||
static const AccelRawData s_default_sample = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.z = -1000
|
||||
};
|
||||
|
||||
// We copy accel data received over the QEMU serial connection into this buffer.
|
||||
// This data gets moved into s_latest_reading when the s_timer_id timer callback
|
||||
// executes.
|
||||
#define QEMU_ACCEL_RCV_BUFFER_SAMPLES 256
|
||||
static AccelRawData s_rcv_buffer[QEMU_ACCEL_RCV_BUFFER_SAMPLES];
|
||||
static uint16_t s_num_rcv_samples;
|
||||
static uint16_t s_current_rcv_sample;
|
||||
|
||||
static AccelRawData s_latest_reading;
|
||||
static uint32_t s_num_fifo_samples; // # of samples in the fifo
|
||||
|
||||
// This timer is used to feed the FIFO
|
||||
static bool s_timer_running;
|
||||
static TimerID s_timer_id; // timer used to copy data from s_rcv_samples into
|
||||
// s_latest_reading.
|
||||
|
||||
|
||||
static void prv_construct_driver_sample(AccelDriverSample *sample) {
|
||||
time_t time_s;
|
||||
uint16_t time_ms;
|
||||
rtc_get_time_ms(&time_s, &time_ms);
|
||||
uint64_t timestamp_ms = ((uint64_t)time_s) * 1000 + time_ms;
|
||||
|
||||
*sample = (AccelDriverSample) {
|
||||
.timestamp_us = timestamp_ms * 1000,
|
||||
.x = s_latest_reading.x,
|
||||
.y = s_latest_reading.y,
|
||||
.z = s_latest_reading.z,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
static void prv_stop_timer(void) {
|
||||
new_timer_stop(s_timer_id);
|
||||
s_timer_running = false;
|
||||
}
|
||||
|
||||
|
||||
// This timer runs as long as we have samples in our s_rcv_buffer or there is
|
||||
// any subscription to the accel that expects samples to arrive at a given
|
||||
// frequency. It feeds samples at the right rate into the s_latest_reading
|
||||
// global (for peek mode) and into the accel driver.
|
||||
static void prv_timer_cb(void *data) {
|
||||
mutex_lock(s_accel_mutex);
|
||||
|
||||
if (s_current_rcv_sample < s_num_rcv_samples) {
|
||||
s_latest_reading = s_rcv_buffer[s_current_rcv_sample++];
|
||||
}
|
||||
|
||||
// Keep it simple; this accelerometer has no FIFO.
|
||||
if (s_num_fifo_samples > 0) {
|
||||
AccelDriverSample sample;
|
||||
prv_construct_driver_sample(&sample);
|
||||
ACCEL_LOG_DEBUG("Accel sample to manager: %d, %d, %d", sample.x, sample.y, sample.z);
|
||||
accel_cb_new_sample(&sample);
|
||||
}
|
||||
|
||||
if (s_num_fifo_samples == 0 && s_current_rcv_sample >= s_num_rcv_samples) {
|
||||
prv_stop_timer();
|
||||
}
|
||||
|
||||
mutex_unlock(s_accel_mutex);
|
||||
}
|
||||
|
||||
|
||||
// Start/reschedule the timer that feeds the FIFO/s_latest_reading out of the
|
||||
// samples received from the host
|
||||
static void prv_reschedule_timer(void) {
|
||||
bool success = new_timer_start(s_timer_id, s_sampling_interval_ms,
|
||||
prv_timer_cb, NULL,
|
||||
TIMER_START_FLAG_REPEATING);
|
||||
PBL_ASSERTN(success);
|
||||
s_timer_running = true;
|
||||
}
|
||||
|
||||
|
||||
// Called by the qemu_serial driver when we receive an accel packet from the
|
||||
// remote side. This copies the received data into our s_rcv_buffer buffer. It
|
||||
// will gradually be pulled out of that and replayed by the timer callback.
|
||||
void qemu_accel_msg_callack(const uint8_t *data, uint32_t len) {
|
||||
QemuProtocolAccelHeader *hdr = (QemuProtocolAccelHeader *)data;
|
||||
|
||||
// Validate the packet
|
||||
uint32_t data_bytes = hdr->num_samples * sizeof(AccelRawData);
|
||||
if (data_bytes != len - sizeof(QemuProtocolAccelHeader)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Invalid packet received");
|
||||
return;
|
||||
}
|
||||
ACCEL_LOG_DEBUG("Got accel msg from host: num samples: %d", hdr->num_samples);
|
||||
|
||||
// Copy the received samples into the s_rcv_buffer
|
||||
#if QEMU_ACCEL_RCV_BUFFER_SAMPLES < 256
|
||||
s_num_rcv_samples = MIN(hdr->num_samples,
|
||||
QEMU_ACCEL_RCV_BUFFER_SAMPLES);
|
||||
#else
|
||||
s_num_rcv_samples = hdr->num_samples;
|
||||
#endif
|
||||
s_current_rcv_sample = 0;
|
||||
mutex_lock(s_accel_mutex);
|
||||
{
|
||||
for (uint32_t i=0; i < s_num_rcv_samples; ++i) {
|
||||
s_rcv_buffer[i].x = ntohs(hdr->samples[i].x);
|
||||
s_rcv_buffer[i].y = ntohs(hdr->samples[i].y);
|
||||
s_rcv_buffer[i].z = ntohs(hdr->samples[i].z);
|
||||
ACCEL_LOG_DEBUG(" x,y,z from host: %d, %d, %d", s_rcv_buffer[i].x,
|
||||
s_rcv_buffer[i].y, s_rcv_buffer[i].z);
|
||||
}
|
||||
|
||||
// If we have any samples at all, make sure the timer is running. This is
|
||||
// required in order to feed the data at the right speed for peek mode.
|
||||
if (!s_timer_running && s_num_rcv_samples > 0) {
|
||||
prv_reschedule_timer();
|
||||
}
|
||||
}
|
||||
mutex_unlock(s_accel_mutex);
|
||||
|
||||
// Send a response, even though none of the clients care about it.
|
||||
QemuProtocolAccelResponseHeader resp = {
|
||||
.avail_space = htons(QEMU_ACCEL_RCV_BUFFER_SAMPLES),
|
||||
};
|
||||
qemu_serial_send(QemuProtocol_Accel, (uint8_t *)&resp, sizeof(resp));
|
||||
}
|
||||
|
||||
|
||||
void qemu_accel_init(void) {
|
||||
PBL_ASSERTN(!s_initialized);
|
||||
s_initialized = true;
|
||||
s_latest_reading = s_default_sample;
|
||||
s_accel_mutex = mutex_create();
|
||||
s_timer_id = new_timer_create();
|
||||
}
|
||||
|
||||
|
||||
uint32_t accel_set_sampling_interval(uint32_t interval_us) {
|
||||
mutex_lock(s_accel_mutex);
|
||||
{
|
||||
s_sampling_interval_ms = interval_us / 1000;
|
||||
|
||||
if (s_timer_running) {
|
||||
// If timer is already running, update it's frequency
|
||||
prv_reschedule_timer();
|
||||
}
|
||||
}
|
||||
mutex_unlock(s_accel_mutex);
|
||||
return accel_get_sampling_interval();
|
||||
}
|
||||
|
||||
uint32_t accel_get_sampling_interval(void) {
|
||||
return s_sampling_interval_ms * 1000;
|
||||
}
|
||||
|
||||
|
||||
void accel_set_num_samples(uint32_t num_samples) {
|
||||
mutex_lock(s_accel_mutex);
|
||||
{
|
||||
s_num_fifo_samples = num_samples;
|
||||
|
||||
// Setup our timer to fire at the right frequency. If using peek mode, then
|
||||
// the timer still has to run if there are any samples received from the
|
||||
// host that we need to feed into the current peek value.
|
||||
if (num_samples > 0 || s_num_rcv_samples > 0) {
|
||||
prv_reschedule_timer();
|
||||
} else {
|
||||
prv_stop_timer();
|
||||
}
|
||||
}
|
||||
mutex_unlock(s_accel_mutex);
|
||||
}
|
||||
|
||||
|
||||
int accel_peek(AccelDriverSample *data) {
|
||||
prv_construct_driver_sample(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void accel_enable_shake_detection(bool on) {
|
||||
}
|
||||
|
||||
|
||||
bool accel_get_shake_detection_enabled(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void accel_enable_double_tap_detection(bool on) {
|
||||
}
|
||||
|
||||
|
||||
bool accel_get_double_tap_detection_enabled(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void accel_set_shake_sensitivity_high(bool sensitivity_high) {
|
||||
}
|
||||
|
||||
bool accel_run_selftest(void) {
|
||||
return true;
|
||||
}
|
25
src/fw/drivers/qemu/qemu_accel.h
Normal file
25
src/fw/drivers/qemu/qemu_accel.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
void qemu_accel_init(void);
|
||||
|
||||
//! Handler called by qemu_serial driver when we receive a QemuProtocol_Accel message
|
||||
//! over the qemu serial connection.
|
||||
void qemu_accel_msg_callack(const uint8_t *data, uint32_t len);
|
79
src/fw/drivers/qemu/qemu_battery.c
Normal file
79
src/fw/drivers/qemu/qemu_battery.c
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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/battery.h"
|
||||
#include "drivers/qemu/qemu_serial.h"
|
||||
#include "drivers/qemu/qemu_settings.h"
|
||||
|
||||
#include "system/passert.h"
|
||||
#include "services/common/battery/battery_state.h"
|
||||
#include "services/common/battery/battery_curve.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
#include "util/math.h"
|
||||
#include "util/net.h"
|
||||
|
||||
static uint16_t s_battery_mv = 4000;
|
||||
static bool s_usb_connected;
|
||||
static uint8_t s_percent = 100;
|
||||
|
||||
void battery_init(void) {
|
||||
s_usb_connected = qemu_setting_get(QemuSetting_DefaultPluggedIn);
|
||||
}
|
||||
|
||||
// TODO: update whoever uses this function
|
||||
int battery_get_millivolts(void) {
|
||||
return s_battery_mv;
|
||||
}
|
||||
|
||||
bool battery_charge_controller_thinks_we_are_charging_impl(void) {
|
||||
return s_usb_connected && (s_percent < 100);
|
||||
}
|
||||
|
||||
bool battery_is_usb_connected_impl(void) {
|
||||
return s_usb_connected;
|
||||
}
|
||||
|
||||
void battery_set_charge_enable(bool charging_enabled) {
|
||||
s_usb_connected = false;
|
||||
}
|
||||
|
||||
void battery_set_fast_charge(bool fast_charge_enabled) {
|
||||
}
|
||||
|
||||
|
||||
void qemu_battery_msg_callack(const uint8_t *data, uint32_t len) {
|
||||
QemuProtocolBatteryHeader *hdr = (QemuProtocolBatteryHeader *)data;
|
||||
if (len != sizeof(*hdr)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Invalid packet length");
|
||||
return;
|
||||
}
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Got battery msg: pct: %d, charger_connected:%d",
|
||||
hdr->battery_pct, hdr->charger_connected);
|
||||
|
||||
s_percent = MIN(100, hdr->battery_pct);
|
||||
s_usb_connected = hdr->charger_connected;
|
||||
s_battery_mv = battery_curve_lookup_voltage_by_percent(s_percent, hdr->charger_connected);
|
||||
|
||||
// Reset the time averaging so these new values take effect immediately
|
||||
battery_state_reset_filter();
|
||||
|
||||
// Force a state machine update
|
||||
battery_state_handle_connection_event(s_usb_connected);
|
||||
}
|
||||
|
||||
|
23
src/fw/drivers/qemu/qemu_battery.h
Normal file
23
src/fw/drivers/qemu/qemu_battery.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
//! Handler called by qemu_serial driver when we receive a QemuProtocol_Battery message
|
||||
//! over the qemu serial connection.
|
||||
void qemu_battery_msg_callack(const uint8_t *data, uint32_t len);
|
338
src/fw/drivers/qemu/qemu_serial.c
Normal file
338
src/fw/drivers/qemu/qemu_serial.c
Normal file
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* 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_callack },
|
||||
{ QemuProtocol_Accel, qemu_accel_msg_callack },
|
||||
{ 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);
|
||||
}
|
133
src/fw/drivers/qemu/qemu_serial.h
Normal file
133
src/fw/drivers/qemu/qemu_serial.h
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "applib/accel_service.h"
|
||||
#include "applib/compass_service.h"
|
||||
#include "applib/preferred_content_size.h"
|
||||
#include "drivers/button_id.h"
|
||||
#include "util/attributes.h"
|
||||
|
||||
// The QEMU protocols implemented
|
||||
typedef enum {
|
||||
QemuProtocol_SPP = 1,
|
||||
QemuProtocol_Tap = 2,
|
||||
QemuProtocol_BluetoothConnection = 3,
|
||||
QemuProtocol_Compass = 4,
|
||||
QemuProtocol_Battery = 5,
|
||||
QemuProtocol_Accel = 6,
|
||||
QemuProtocol_Vibration = 7,
|
||||
QemuProtocol_Button = 8,
|
||||
QemuProtocol_TimeFormat = 9,
|
||||
QemuProtocol_TimelinePeek = 10,
|
||||
QemuProtocol_ContentSize = 11,
|
||||
} QemuProtocol;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// Structure of the data for various protocols
|
||||
|
||||
// For QemuProtocol_SPP, the data is raw Pebble Protocol
|
||||
|
||||
// QemuProtocol_Tap
|
||||
typedef struct PACKED {
|
||||
uint8_t axis; // 0: x-axis, 1: y-axis, 2: z-axis
|
||||
int8_t direction; // either +1 or -1
|
||||
} QemuProtocolTapHeader;
|
||||
|
||||
|
||||
// QemuProtocol_BluetoothConnection
|
||||
typedef struct PACKED {
|
||||
uint8_t connected; // true if connected
|
||||
} QemuProtocolBluetoothConnectionHeader;
|
||||
|
||||
|
||||
// QemuProtocol_Compass
|
||||
typedef struct PACKED {
|
||||
uint32_t magnetic_heading; // 0x10000 represents 360 degress
|
||||
CompassStatus calib_status:8; // CompassStatus enum
|
||||
} QemuProtocolCompassHeader;
|
||||
|
||||
|
||||
// QemuProtocol_Battery
|
||||
typedef struct PACKED {
|
||||
uint8_t battery_pct; // from 0 to 100
|
||||
uint8_t charger_connected;
|
||||
} QemuProtocolBatteryHeader;
|
||||
|
||||
|
||||
// QemuProtocol_Accel request (to Pebble)
|
||||
typedef struct PACKED {
|
||||
uint8_t num_samples;
|
||||
AccelRawData samples[0];
|
||||
} QemuProtocolAccelHeader;
|
||||
|
||||
// QemuProtocol_Accel response (back to host)
|
||||
typedef struct PACKED {
|
||||
uint16_t avail_space; // Number of samples we can accept
|
||||
} QemuProtocolAccelResponseHeader;
|
||||
|
||||
|
||||
// QemuProtocol_Vibration notification (sent from Pebble to host)
|
||||
typedef struct PACKED {
|
||||
uint8_t on; // non-zero if vibe is on, 0 if off
|
||||
} QemuProtocolVibrationNotificationHeader;
|
||||
|
||||
|
||||
// QemuProtocol_Button
|
||||
typedef struct PACKED {
|
||||
// New button state. Bit x specifies the state of button x, where x is one of the
|
||||
// ButtonId enum values.
|
||||
uint8_t button_state;
|
||||
} QemuProtocolButtonHeader;
|
||||
|
||||
|
||||
// QemuProtocol_TimeFormat
|
||||
typedef struct PACKED {
|
||||
uint8_t is_24_hour; // non-zero if 24h format, 0 if 12h format
|
||||
} QemuProtocolTimeFormatHeader;
|
||||
|
||||
|
||||
// QemuProtocol_TimelinePeek
|
||||
typedef struct PACKED {
|
||||
//! Decides whether the Timeline Peek will show. Timeline Peek will animate only when this state
|
||||
//! toggles, and subsequent interactions that manipulate Timeline Peek outside of this
|
||||
//! QemuProtocol packet apply without an animation. The state received by this packet is also
|
||||
//! persisted, for example if enabled is true, exiting the watchface will instantly hide the
|
||||
//! peek, but returning to the watchface will instantly show the peek since this state persists.
|
||||
bool enabled;
|
||||
} QemuProtocolTimelinePeekHeader;
|
||||
|
||||
|
||||
// QemuProtocol_ContentSize
|
||||
typedef struct PACKED {
|
||||
//! New system content size.
|
||||
uint8_t size;
|
||||
} QemuProtocolContentSizeHeader;
|
||||
#if !UNITTEST
|
||||
_Static_assert(sizeof(PreferredContentSize) == sizeof(((QemuProtocolContentSizeHeader *)0)->size),
|
||||
"sizeof(PreferredContentSize) grew, need to update QemuContentSize in libpebble2 !");
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// API
|
||||
void qemu_serial_init(void);
|
||||
|
||||
void qemu_serial_send(QemuProtocol protocol, const uint8_t *data, uint32_t len);
|
109
src/fw/drivers/qemu/qemu_serial_private.h
Normal file
109
src/fw/drivers/qemu/qemu_serial_private.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "os/mutex.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/shared_circular_buffer.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#define QEMU_HEADER_SIGNATURE 0xFEED
|
||||
#define QEMU_FOOTER_SIGNATURE 0xBEEF
|
||||
#define QEMU_MAX_DATA_LEN 2048
|
||||
|
||||
|
||||
// Every message sent over the QEMU comm channel has the following header. All
|
||||
// data is set in network byte order. The maximum data len (not including header or footer)
|
||||
// allowed is QEMU_MAX_DATA_LEN bytes
|
||||
typedef struct PACKED {
|
||||
uint16_t signature; // QEMU_HEADER_SIGNATURE
|
||||
uint16_t protocol; // one of QemuProtocol
|
||||
uint16_t len; // number of bytes that follow (not including this header or footer)
|
||||
} QemuCommChannelHdr;
|
||||
|
||||
// Every message sent over the QEMU comm channel has the following footer.
|
||||
typedef struct PACKED {
|
||||
uint16_t signature; // QEMU_FOOTER_SIGNATURE
|
||||
} QemuCommChannelFooter;
|
||||
|
||||
|
||||
// Incoming message handlers are defined using this structure
|
||||
typedef void (*QemuMessageCallback)(const uint8_t* data, uint32_t length);
|
||||
typedef struct {
|
||||
uint16_t protocol_id;
|
||||
QemuMessageCallback callback;
|
||||
} QemuMessageHandler;
|
||||
|
||||
|
||||
// Which state our incoming message state machine is in
|
||||
typedef enum {
|
||||
QemuRecvState_WaitingHdrSignatureMSB,
|
||||
QemuRecvState_WaitingHdrSignatureLSB,
|
||||
QemuRecvState_WaitingHdr,
|
||||
QemuRecvState_WaitingData,
|
||||
QemuRecvState_WaitingFooter,
|
||||
} QemuRecvState;
|
||||
|
||||
|
||||
// Structure of our globals
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
PebbleMutex *qemu_comm_lock;
|
||||
SharedCircularBuffer isr_buffer;
|
||||
SharedCircularBufferClient isr_buffer_client;
|
||||
|
||||
QemuRecvState recv_state;
|
||||
uint8_t prev_byte;
|
||||
QemuCommChannelHdr hdr;
|
||||
uint8_t *msg_buffer;
|
||||
uint16_t msg_buffer_bytes;
|
||||
bool callback_pending;
|
||||
uint32_t recv_error_count;
|
||||
time_t start_recv_packet_time;
|
||||
} QemuSerialGlobals;
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Defines and private structures
|
||||
#define UART_SERIAL_BAUD_RATE 230400
|
||||
|
||||
#define QEMU_ISR_RECV_HIGH_WATER_DELTA (128)
|
||||
#define QEMU_ISR_RECV_BUFFER_SIZE (QEMU_MAX_DATA_LEN + QEMU_ISR_RECV_HIGH_WATER_DELTA)
|
||||
#define QEMU_RECV_PACKET_TIMEOUT_SEC 10 // We have to receive a complete packet within this
|
||||
// amount of time
|
||||
|
||||
#define QEMU_FOOTER_MSB ((uint8_t)(QEMU_FOOTER_SIGNATURE >> 8))
|
||||
#define QEMU_FOOTER_LSB ((uint8_t)(QEMU_FOOTER_SIGNATURE & 0x00FF))
|
||||
#define QEMU_HEADER_MSB ((uint8_t)(QEMU_HEADER_SIGNATURE >> 8))
|
||||
#define QEMU_HEADER_LSB ((uint8_t)(QEMU_HEADER_SIGNATURE & 0x00FF))
|
||||
|
||||
|
||||
#define QEMU_LOG_DEBUG(fmt, args...) PBL_LOG_D(LOG_DOMAIN_QEMU_COMM, LOG_LEVEL_DEBUG, fmt, ## args)
|
||||
#define QEMU_LOG_ERROR(fmt, args...) PBL_LOG_D(LOG_DOMAIN_QEMU_COMM, LOG_LEVEL_ERROR, fmt, ## args)
|
||||
#define QEMU_HEXDUMP(data, length) \
|
||||
PBL_HEXDUMP_D(LOG_DOMAIN_QEMU_COMM, LOG_LEVEL_DEBUG, data, length)
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------
|
||||
void qemu_serial_private_init_state(QemuSerialGlobals *state);
|
||||
|
||||
uint8_t *qemu_serial_private_assemble_message(QemuSerialGlobals *state, uint32_t *msg_bytes,
|
||||
uint16_t *protocol);
|
205
src/fw/drivers/qemu/qemu_serial_util.c
Normal file
205
src/fw/drivers/qemu/qemu_serial_util.c
Normal file
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* 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/pbl_malloc.h"
|
||||
|
||||
#include "drivers/rtc.h"
|
||||
#include "drivers/qemu/qemu_serial.h"
|
||||
#include "drivers/qemu/qemu_serial_private.h"
|
||||
|
||||
#include "system/passert.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
#include "util/math.h"
|
||||
#include "util/net.h"
|
||||
#include "system/hexdump.h"
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
void qemu_serial_private_init_state(QemuSerialGlobals *state)
|
||||
{
|
||||
|
||||
// Create our mutex
|
||||
state->qemu_comm_lock = mutex_create();
|
||||
state->initialized = true;
|
||||
|
||||
// Allocate buffer for received characters from the ISR
|
||||
uint32_t buffer_size = QEMU_ISR_RECV_BUFFER_SIZE;
|
||||
uint8_t *buffer_data = kernel_malloc_check(buffer_size);
|
||||
shared_circular_buffer_init(&state->isr_buffer, buffer_data, buffer_size);
|
||||
shared_circular_buffer_add_client(&state->isr_buffer, &state->isr_buffer_client);
|
||||
|
||||
// Allocate buffer for the received message
|
||||
state->msg_buffer = kernel_malloc_check(QEMU_MAX_DATA_LEN);
|
||||
state->msg_buffer_bytes = 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Helper function triggred by our ISR handler when we detect a high water mark on our receive
|
||||
// buffer or a footer signature.
|
||||
//
|
||||
// Parses the ISR's circular buffer and collects assembled message into a message buffer. If
|
||||
// a complete packets has been assembled, it returns a pointer to the message buffer,
|
||||
// the size of the packet (in *msg_bytes), and the protocol (in *protocol). If a complete packet
|
||||
// is not ready yet, it returns a NULL.
|
||||
//
|
||||
// @param[in] state pointer to our state variables
|
||||
// @param[out] *msg_bytes number of bytes in assembled message
|
||||
// @param[out] *protocol protocol for the message
|
||||
// @return pointer to message, or NULL if no message available yet
|
||||
uint8_t *qemu_serial_private_assemble_message(QemuSerialGlobals *state, uint32_t *msg_bytes,
|
||||
uint16_t *protocol) {
|
||||
uint16_t bytes_read;
|
||||
uint8_t byte;
|
||||
bool exit = false;
|
||||
bool got_msg = false;
|
||||
time_t cur_time = rtc_get_time();
|
||||
|
||||
// Reset our state if too much time has passed since we detected start of a packet
|
||||
if (state->recv_state != QemuRecvState_WaitingHdrSignatureMSB
|
||||
&& cur_time > state->start_recv_packet_time + QEMU_RECV_PACKET_TIMEOUT_SEC) {
|
||||
state->recv_state = QemuRecvState_WaitingHdrSignatureMSB;
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Resetting receive state - max packet time expired");
|
||||
}
|
||||
|
||||
state->callback_pending = false;
|
||||
|
||||
uint16_t bytes_avail = shared_circular_buffer_get_read_space_remaining(&state->isr_buffer,
|
||||
&state->isr_buffer_client);
|
||||
QEMU_LOG_DEBUG("prv_assemble_packet, state:%d, bytes:%d", state->recv_state,
|
||||
bytes_avail);
|
||||
|
||||
// Log message if we detected any receive errors
|
||||
if (state->recv_error_count) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "%"PRIu32" receive errors detected", state->recv_error_count);
|
||||
state->recv_error_count = 0;
|
||||
}
|
||||
|
||||
while (!exit && bytes_avail) {
|
||||
switch (state->recv_state) {
|
||||
case QemuRecvState_WaitingHdrSignatureMSB: {
|
||||
state->msg_buffer_bytes = 0;
|
||||
shared_circular_buffer_read_consume(&state->isr_buffer, &state->isr_buffer_client, 1, &byte,
|
||||
&bytes_read);
|
||||
bytes_avail -= bytes_read;
|
||||
if (byte == QEMU_HEADER_MSB) {
|
||||
QEMU_LOG_DEBUG("got header signature MSB");
|
||||
state->recv_state = QemuRecvState_WaitingHdrSignatureLSB;
|
||||
state->start_recv_packet_time = cur_time;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case QemuRecvState_WaitingHdrSignatureLSB: {
|
||||
shared_circular_buffer_read_consume(&state->isr_buffer, &state->isr_buffer_client, 1, &byte,
|
||||
&bytes_read);
|
||||
bytes_avail -= bytes_read;
|
||||
if (byte == QEMU_HEADER_LSB) {
|
||||
state->recv_state = QemuRecvState_WaitingHdr;
|
||||
QEMU_LOG_DEBUG("got header signature LSB");
|
||||
} else {
|
||||
state->recv_state = QemuRecvState_WaitingHdr;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case QemuRecvState_WaitingHdr: {
|
||||
// We already read in the header signature
|
||||
uint16_t req_bytes = sizeof(state->hdr) - sizeof(state->hdr.signature);
|
||||
if (bytes_avail < req_bytes) {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
shared_circular_buffer_read_consume(&state->isr_buffer, &state->isr_buffer_client,
|
||||
req_bytes, (uint8_t *)&state->hdr.protocol, &bytes_read);
|
||||
bytes_avail -= bytes_read;
|
||||
|
||||
// Do byte swapping
|
||||
state->hdr.signature = QEMU_HEADER_SIGNATURE;
|
||||
state->hdr.protocol = ntohs(state->hdr.protocol);
|
||||
state->hdr.len = ntohs(state->hdr.len);
|
||||
|
||||
// Validity checking
|
||||
if (state->hdr.len > QEMU_MAX_DATA_LEN) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Invalid header data size %d", state->hdr.len);
|
||||
state->recv_state = QemuRecvState_WaitingHdrSignatureMSB;
|
||||
} else {
|
||||
QEMU_LOG_DEBUG("got header: protocol: %d, len: %d", state->hdr.protocol, state->hdr.len);
|
||||
state->recv_state = QemuRecvState_WaitingData;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case QemuRecvState_WaitingData: {
|
||||
uint16_t bytes_needed = state->hdr.len - state->msg_buffer_bytes;
|
||||
shared_circular_buffer_read_consume(&state->isr_buffer, &state->isr_buffer_client,
|
||||
MIN(bytes_avail, bytes_needed), state->msg_buffer + state->msg_buffer_bytes,
|
||||
&bytes_read);
|
||||
state->msg_buffer_bytes += bytes_read;
|
||||
bytes_avail -= bytes_read;
|
||||
|
||||
QEMU_LOG_DEBUG("received %d bytes of msg data, need %d more", bytes_read,
|
||||
state->hdr.len - state->msg_buffer_bytes);
|
||||
|
||||
// Got the complete message?
|
||||
if (state->msg_buffer_bytes >= state->hdr.len) {
|
||||
state->recv_state = QemuRecvState_WaitingFooter;
|
||||
got_msg = true;
|
||||
exit = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case QemuRecvState_WaitingFooter: {
|
||||
QemuCommChannelFooter footer;
|
||||
if (bytes_avail < sizeof(QemuCommChannelFooter)) {
|
||||
exit = true;
|
||||
} else {
|
||||
shared_circular_buffer_read_consume(&state->isr_buffer, &state->isr_buffer_client,
|
||||
sizeof(footer), (uint8_t *)&footer, &bytes_read);
|
||||
bytes_avail -= bytes_read;
|
||||
footer.signature = ntohs(footer.signature);
|
||||
if (footer.signature != QEMU_FOOTER_SIGNATURE) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Invalid footer signature");
|
||||
}
|
||||
state->recv_state = QemuRecvState_WaitingHdrSignatureMSB;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} // switch()
|
||||
} // while (!exit && bytes_avail)
|
||||
|
||||
// Return pointer if we got a complete message
|
||||
if (got_msg) {
|
||||
*msg_bytes= state->msg_buffer_bytes;
|
||||
*protocol = state->hdr.protocol;
|
||||
return state->msg_buffer;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Unit test support
|
||||
|
||||
// @return true if successful (buffer not full)
|
||||
bool qemu_test_add_byte_from_isr(QemuSerialGlobals *state, uint8_t byte) {
|
||||
return shared_circular_buffer_write(&state->isr_buffer, &byte, 1, false /*advance_slackers*/);
|
||||
}
|
72
src/fw/drivers/qemu/qemu_settings.c
Normal file
72
src/fw/drivers/qemu/qemu_settings.c
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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_settings.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
// QEMU backup registers and bit indices. These are also defined in the qemu project in
|
||||
// hw/arm/pebble.c
|
||||
#define QEMU_REG_0_FIRST_BOOT_LOGIC_ENABLE 0x00000001
|
||||
#define QEMU_REG_0_DEFAULT_CONNECTED 0x00000002
|
||||
#define QEMU_REG_0_DEFAULT_PLUGGED_IN 0x00000004
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// Read a QEMU specific register from the RTC backup register area
|
||||
static uint32_t prv_rtc_read_qemu_register(uint32_t qemu_register) {
|
||||
__IO uint32_t tmp = 0;
|
||||
|
||||
// The first qemu_register (0) starts 1 past the implemented registers in the STM
|
||||
uint32_t backup_reg = RTC_BKP_DR19 + 1 + qemu_register;
|
||||
|
||||
tmp = RTC_BASE + 0x50;
|
||||
tmp += (backup_reg * 4);
|
||||
|
||||
// Read the specified register
|
||||
return (*(__IO uint32_t *)tmp);
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// Return the value of a QEMU setting. QEMU communicates these by setting values into an
|
||||
// unused area of the RTC registers, what would be RTC_BKP20R on up.
|
||||
uint32_t qemu_setting_get(QemuSetting setting) {
|
||||
switch (setting) {
|
||||
case QemuSetting_FirstBootLogicEnable:
|
||||
return prv_rtc_read_qemu_register(0) & QEMU_REG_0_FIRST_BOOT_LOGIC_ENABLE;
|
||||
break;
|
||||
|
||||
case QemuSetting_DefaultConnected:
|
||||
return prv_rtc_read_qemu_register(0) & QEMU_REG_0_DEFAULT_CONNECTED;
|
||||
break;
|
||||
|
||||
case QemuSetting_DefaultPluggedIn:
|
||||
return prv_rtc_read_qemu_register(0) & QEMU_REG_0_DEFAULT_PLUGGED_IN;
|
||||
break;
|
||||
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
}
|
32
src/fw/drivers/qemu/qemu_settings.h
Normal file
32
src/fw/drivers/qemu/qemu_settings.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
QemuSetting_FirstBootLogicEnable = 1, // Returns a bool
|
||||
QemuSetting_DefaultConnected = 2, // Returns a bool
|
||||
QemuSetting_DefaultPluggedIn = 3, // Returns a bool
|
||||
} QemuSetting;
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// API
|
||||
uint32_t qemu_setting_get(QemuSetting);
|
18
src/fw/drivers/qemu/qemu_touch.c
Normal file
18
src/fw/drivers/qemu/qemu_touch.c
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
void touch_sensor_init(void) {
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue