mirror of
https://github.com/google/pebble.git
synced 2025-07-13 17:51:58 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
100
src/fw/services/normal/data_logging/data_logging_service.h
Normal file
100
src/fw/services/normal/data_logging/data_logging_service.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 "applib/data_logging.h"
|
||||
#include "util/uuid.h"
|
||||
#include "kernel/pebble_tasks.h"
|
||||
|
||||
//! This can be helpful when debugging. It changes the behavior of data logging to send any
|
||||
//! stored data to the phone immediately after every dls_log()
|
||||
//! Another helpful testing feature is that doing a long press on any item in the launcher menu will
|
||||
//! also trigger a flush of all data logging data to the phone
|
||||
// #define DLS_DEBUG_SEND_IMMEDIATELY
|
||||
|
||||
|
||||
struct DataLoggingSession;
|
||||
typedef struct DataLoggingSession DataLoggingSession;
|
||||
|
||||
//! List of tags used by system services. These are all registered with a uuid of UUID_SYSTEM
|
||||
typedef enum {
|
||||
DlsSystemTagAnalyticsDeviceHeartbeat = 78,
|
||||
DlsSystemTagAnalyticsAppHeartbeat = 79,
|
||||
DlsSystemTagAnalyticsEvent = 80,
|
||||
DlsSystemTagActivityMinuteData = 81,
|
||||
DlsSystemTagActivityAccelSamples = 82,
|
||||
DlsSystemTagActivitySession = 84,
|
||||
DlsSystemTagProtobufLogSession = 85,
|
||||
} DlsSystemTag;
|
||||
|
||||
//! Init the data logging service. Called by the system at boot time.
|
||||
void dls_init(void);
|
||||
|
||||
//! Return true if data logging initialized
|
||||
bool dls_initialized(void);
|
||||
|
||||
//! The nuclear option! Clear out all data logging state in memory as well as on the flash storage.
|
||||
void dls_clear(void);
|
||||
|
||||
//! Pause the data logging service
|
||||
void dls_pause(void);
|
||||
|
||||
//! Resume the data logging service.
|
||||
void dls_resume(void);
|
||||
|
||||
//! Find any sessions that the given task may have left in the DataLoggingStatusActive state and
|
||||
//! moves them forcibly to the inactive state. This may result in data loss if the buffers haven't
|
||||
//! been flushed to flash yet.
|
||||
void dls_inactivate_sessions(PebbleTask task);
|
||||
|
||||
//! Create a new session using the UUID of the current process. This always creates a buffered
|
||||
//! session. Unless this is the worker task, buffer must be allocated by the caller and must be at
|
||||
//! least DLS_SESSION_BUFFER_SIZE bytes large. It will be freed by the data logging service when the
|
||||
//! session is closed if this method returns no error. The worker task can optionally pass NULL for
|
||||
//! buffer and the buffer will be allocated in the system heap for it by the data logging service.
|
||||
DataLoggingSession* dls_create_current_process(uint32_t tag, DataLoggingItemType item_type,
|
||||
uint16_t item_size, void* buffer, bool resume);
|
||||
|
||||
//! Create a new session
|
||||
DataLoggingSession* dls_create(uint32_t tag, DataLoggingItemType item_type, uint16_t item_size,
|
||||
bool buffered, bool resume, const Uuid* uuid);
|
||||
|
||||
//! Append data to a logging session. Buffered sessions log asynchronously. Non buffered ones block.
|
||||
DataLoggingResult dls_log(DataLoggingSession *s, const void* data, uint32_t num_items);
|
||||
|
||||
//! Finish up a session
|
||||
void dls_finish(DataLoggingSession *s);
|
||||
|
||||
//! Checks to see if this is an actual valid data session.
|
||||
//! Note that we pass in the logging_session parameter without making sure it's the same. Make sure
|
||||
//! this function handles passing in random pointers that don't actually point to valid sessions or
|
||||
//! even valid memory.
|
||||
bool dls_is_session_valid(DataLoggingSession *logging_session);
|
||||
|
||||
//! Triggers data logging to immediately send all stored data to the phone rather than wait for the
|
||||
//! next regular minute heartbeat. As a testing aid, a long press on any item in the launcher menu
|
||||
//! calls into this method.
|
||||
void dls_send_all_sessions(void);
|
||||
|
||||
//! Get the send_enable setting
|
||||
bool dls_get_send_enable(void);
|
||||
|
||||
//! Set the send_enable setting for PebbleProtocol
|
||||
void dls_set_send_enable_pp(bool setting);
|
||||
|
||||
//! Set the send_enable setting for Run Level
|
||||
void dls_set_send_enable_run_level(bool setting);
|
80
src/fw/services/normal/data_logging/dls_command.c
Normal file
80
src/fw/services/normal/data_logging/dls_command.c
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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 "data_logging_service.h"
|
||||
#include "dls_list.h"
|
||||
|
||||
#include "console/prompt.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
static bool command_dls_list_cb(DataLoggingSession *session, void *data) {
|
||||
char buffer[80];
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer),
|
||||
"session_id : %"PRIu8", tag: %"PRIu32", bytes: %"PRIu32", write_offset: %"PRIu32,
|
||||
session->comm.session_id, session->tag, session->storage.num_bytes,
|
||||
session->storage.write_offset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void command_dls_list(void) {
|
||||
dls_list_for_each_session(command_dls_list_cb, 0);
|
||||
}
|
||||
|
||||
// Unused, comment out to avoid pulling in the string literals
|
||||
#if 0
|
||||
void command_dls_show(const char *id) {
|
||||
uint8_t session_id = strtol(id, NULL, 0);
|
||||
DataLoggingSession *logging_session = dls_list_find_by_session_id(session_id);
|
||||
|
||||
if (logging_session == NULL) {
|
||||
prompt_send_response("LoggingSession not found");
|
||||
return;
|
||||
}
|
||||
|
||||
char uuid_b[UUID_STRING_BUFFER_LENGTH];
|
||||
uuid_to_string(&logging_session->app_uuid, uuid_b);
|
||||
|
||||
char buffer[80];
|
||||
|
||||
#define WRITE_LINE(fmt, arg) \
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), fmt, arg)
|
||||
|
||||
WRITE_LINE("session_id %u", logging_session->comm.session_id);
|
||||
WRITE_LINE("uuid %s", uuid_b);
|
||||
WRITE_LINE("data state %u", logging_session->status);
|
||||
WRITE_LINE("tag %lu", logging_session->tag);
|
||||
WRITE_LINE("item type %u", logging_session->item_type);
|
||||
WRITE_LINE("item size %u", logging_session->item_size);
|
||||
WRITE_LINE("storage %lu bytes", logging_session->storage.num_bytes);
|
||||
WRITE_LINE(" r sector %u", logging_session->storage.read_sector);
|
||||
WRITE_LINE(" r offset %"PRIu32, logging_session->storage.read_offset);
|
||||
WRITE_LINE(" w sector %u", logging_session->storage.write_sector);
|
||||
WRITE_LINE(" w offset %"PRIu32, logging_session->storage.write_offset);
|
||||
|
||||
#undef WRITE_LINE
|
||||
}
|
||||
#endif
|
||||
|
||||
void command_dls_erase_all(void) {
|
||||
dls_clear();
|
||||
}
|
||||
|
||||
void command_dls_send_all(void) {
|
||||
// Use this to trigger a send of all data logging data to the phone, helpful for testing
|
||||
dls_send_all_sessions();
|
||||
}
|
566
src/fw/services/normal/data_logging/dls_endpoint.c
Normal file
566
src/fw/services/normal/data_logging/dls_endpoint.c
Normal file
|
@ -0,0 +1,566 @@
|
|||
/*
|
||||
* 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 "dls_private.h"
|
||||
#include "dls_endpoint.h"
|
||||
#include "dls_list.h"
|
||||
#include "dls_storage.h"
|
||||
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/comm_session/protocol.h"
|
||||
#include "services/common/comm_session/session_send_buffer.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "services/normal/data_logging/data_logging_service.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/legacy_checksum.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "timers.h"
|
||||
|
||||
typedef struct {
|
||||
ListNode list_node;
|
||||
DataLoggingSession *session;
|
||||
// Session metadata to make sure the session pointer corresponds to
|
||||
// the same session that was added to the reopen list. This guards
|
||||
// against the session being destroyed and another getting allocated
|
||||
// to the same address.
|
||||
Uuid app_uuid;
|
||||
time_t timestamp;
|
||||
uint32_t tag;
|
||||
} DataLoggingReopenEntry;
|
||||
|
||||
static struct {
|
||||
PebbleMutex * mutex;
|
||||
TimerID ack_timer;
|
||||
bool report_in_progress;
|
||||
} s_endpoint_data;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint8_t command;
|
||||
uint8_t session_id;
|
||||
} DataLoggingCloseSessionMessage;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint8_t command;
|
||||
uint8_t session_id;
|
||||
Uuid app_uuid;
|
||||
uint32_t timestamp;
|
||||
uint32_t logging_session_tag;
|
||||
DataLoggingItemType data_item_type:8;
|
||||
uint16_t data_item_size;
|
||||
} DataLoggingOpenSessionMessage;
|
||||
|
||||
static const uint16_t ENDPOINT_ID_DATA_LOGGING = 0x1a7a;
|
||||
|
||||
#define ACK_NACK_TIMEOUT_TICKS (30 * RTC_TICKS_HZ)
|
||||
|
||||
static const uint8_t MAX_NACK_COUNT = 20;
|
||||
|
||||
static void reschedule_ack_timeout(void);
|
||||
|
||||
static void update_session_state(DataLoggingSession *session, DataLoggingSessionCommState new_state,
|
||||
bool reschedule) {
|
||||
session->comm.state = new_state;
|
||||
|
||||
switch (new_state) {
|
||||
case DataLoggingSessionCommStateOpening:
|
||||
case DataLoggingSessionCommStateSending:
|
||||
// These states need an ack from the phone.
|
||||
session->comm.ack_timeout = rtc_get_ticks() + ACK_NACK_TIMEOUT_TICKS;
|
||||
break;
|
||||
case DataLoggingSessionCommStateIdle:
|
||||
session->comm.ack_timeout = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (reschedule) {
|
||||
reschedule_ack_timeout();
|
||||
}
|
||||
}
|
||||
|
||||
static void send_timeout_msg(void *session_id_param) {
|
||||
uint8_t session_id = (uint8_t)(uintptr_t)session_id_param;
|
||||
CommSession *session = comm_session_get_system_session();
|
||||
if (!session) {
|
||||
// timed out because of lost connection
|
||||
return;
|
||||
}
|
||||
|
||||
DataLoggingSession *logging_session = dls_list_find_by_session_id(session_id);
|
||||
|
||||
struct PACKED {
|
||||
uint8_t command;
|
||||
uint8_t session_id;
|
||||
} msg = {
|
||||
.command = DataLoggingEndpointCmdTimeout,
|
||||
.session_id = logging_session->comm.session_id,
|
||||
};
|
||||
|
||||
comm_session_send_data(session, ENDPOINT_ID_DATA_LOGGING, (uint8_t *)&msg,
|
||||
sizeof(msg), COMM_SESSION_DEFAULT_TIMEOUT);
|
||||
}
|
||||
|
||||
static bool check_ack_timeout_for_session(DataLoggingSession *session, void *data) {
|
||||
RtcTicks *current_ticks = (RtcTicks*) data;
|
||||
|
||||
if (session->comm.ack_timeout != 0 && session->comm.ack_timeout <= *current_ticks) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "session %"PRIu8" timeout", session->comm.session_id);
|
||||
|
||||
// Send timeout msg from system task because it could take a while and also require
|
||||
// more stack space than provided by the timer task.
|
||||
system_task_add_callback(send_timeout_msg, (void*)(uintptr_t)(session->comm.session_id));
|
||||
|
||||
// Set reschedule to false because: 1.) we don't need to reschedule the timer since all
|
||||
// we did was process one that already expired, 2.) it can cause an infinite recursion
|
||||
// because reschedule_ack_timeout() will call check_ack_timeout() (which we are already in) if
|
||||
// any other timers have already expired.
|
||||
update_session_state(session, DataLoggingSessionCommStateIdle, false /*reschedule*/);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void check_ack_timeout(void) {
|
||||
RtcTicks current_ticks = rtc_get_ticks();
|
||||
|
||||
dls_list_for_each_session(check_ack_timeout_for_session, ¤t_ticks);
|
||||
|
||||
reschedule_ack_timeout();
|
||||
}
|
||||
|
||||
static void ack_timer_cb(void *cb_data) {
|
||||
mutex_lock(s_endpoint_data.mutex);
|
||||
|
||||
check_ack_timeout();
|
||||
|
||||
mutex_unlock(s_endpoint_data.mutex);
|
||||
}
|
||||
|
||||
static bool find_soonest_ack_timeout_cb(DataLoggingSession *session, void *data) {
|
||||
RtcTicks *soonest_ack_timeout = (RtcTicks*) data;
|
||||
if (session->comm.ack_timeout != 0
|
||||
&& (session->comm.ack_timeout < *soonest_ack_timeout || *soonest_ack_timeout == 0)) {
|
||||
*soonest_ack_timeout = session->comm.ack_timeout;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void reschedule_ack_timeout(void) {
|
||||
RtcTicks soonest_ack_timeout = 0;
|
||||
dls_list_for_each_session(find_soonest_ack_timeout_cb, &soonest_ack_timeout);
|
||||
|
||||
if (soonest_ack_timeout == 0) {
|
||||
// No one is waiting for ack, just stop the timer
|
||||
new_timer_stop(s_endpoint_data.ack_timer);
|
||||
return;
|
||||
}
|
||||
|
||||
RtcTicks current_ticks = rtc_get_ticks();
|
||||
if (soonest_ack_timeout < current_ticks) {
|
||||
// Handle the timeout immediately. This will result the in the timer being rescheduled if we're still
|
||||
// waiting for an ack.
|
||||
check_ack_timeout();
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert from ticks to ms for the timer
|
||||
RtcTicks ticks_until_timeout = soonest_ack_timeout - current_ticks;
|
||||
uint32_t ms_until_timeout = ((uint64_t) ticks_until_timeout * 1000) / RTC_TICKS_HZ;
|
||||
|
||||
bool success = new_timer_start(s_endpoint_data.ack_timer, ms_until_timeout, ack_timer_cb, NULL, 0 /*flags*/);
|
||||
PBL_ASSERTN(success);
|
||||
}
|
||||
|
||||
static void dls_endpoint_print_message(uint8_t *message, int num_bytes) {
|
||||
PBL_ASSERTN(message != NULL);
|
||||
|
||||
switch (message[0]) {
|
||||
case DataLoggingEndpointCmdClose:
|
||||
{
|
||||
DataLoggingCloseSessionMessage *msg = (DataLoggingCloseSessionMessage *)message;
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Closing session %d", msg->session_id);
|
||||
break;
|
||||
}
|
||||
case DataLoggingEndpointCmdOpen:
|
||||
{
|
||||
DataLoggingOpenSessionMessage *msg = (DataLoggingOpenSessionMessage *)message;
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Opening session %u with tag %"PRIu32", type %u, size %hu",
|
||||
msg->session_id, msg->logging_session_tag, msg->data_item_type, msg->data_item_size);
|
||||
break;
|
||||
}
|
||||
case DataLoggingEndpointCmdData:
|
||||
{
|
||||
DataLoggingSendDataMessage *msg = (DataLoggingSendDataMessage *)message;
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Sending data with session_id %"PRIu8", items remaining %"PRIu32", crc 0x%"PRIx32", num_bytes %d",
|
||||
msg->session_id, msg->items_left_hereafter, msg->crc32, num_bytes);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Message type 0x%x not recognized", message[0]);
|
||||
}
|
||||
}
|
||||
|
||||
bool dls_endpoint_open_session(DataLoggingSession *session) {
|
||||
CommSession *comm_session = comm_session_get_system_session();
|
||||
if (!session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DataLoggingOpenSessionMessage msg = {
|
||||
.command = DataLoggingEndpointCmdOpen,
|
||||
.session_id = session->comm.session_id,
|
||||
.app_uuid = session->app_uuid,
|
||||
.timestamp = session->session_created_timestamp,
|
||||
.logging_session_tag = session->tag,
|
||||
.data_item_type = session->item_type,
|
||||
.data_item_size = session->item_size,
|
||||
};
|
||||
|
||||
dls_endpoint_print_message((uint8_t *)&msg, 0);
|
||||
|
||||
update_session_state(session, DataLoggingSessionCommStateOpening, true /*reschedule*/);
|
||||
|
||||
return (comm_session_send_data(comm_session, ENDPOINT_ID_DATA_LOGGING,
|
||||
(uint8_t *)&msg, sizeof(DataLoggingOpenSessionMessage),
|
||||
COMM_SESSION_DEFAULT_TIMEOUT));
|
||||
}
|
||||
|
||||
void dls_endpoint_close_session(uint8_t session_id) {
|
||||
CommSession *session = comm_session_get_system_session();
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
DataLoggingCloseSessionMessage msg = {
|
||||
.command = DataLoggingEndpointCmdClose,
|
||||
.session_id = session_id,
|
||||
};
|
||||
|
||||
dls_endpoint_print_message((uint8_t *)&msg, 0);
|
||||
|
||||
comm_session_send_data(session, ENDPOINT_ID_DATA_LOGGING,
|
||||
(uint8_t *)&msg, sizeof(DataLoggingCloseSessionMessage),
|
||||
COMM_SESSION_DEFAULT_TIMEOUT);
|
||||
}
|
||||
|
||||
bool dls_endpoint_send_data(DataLoggingSession *logging_session, const uint8_t *data,
|
||||
unsigned int num_bytes) {
|
||||
if (num_bytes < 1) {
|
||||
// not sending anything
|
||||
return true;
|
||||
}
|
||||
|
||||
CommSession *session = comm_session_get_system_session();
|
||||
if (!session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mutex_lock(s_endpoint_data.mutex);
|
||||
if (logging_session->comm.state != DataLoggingSessionCommStateIdle) {
|
||||
mutex_unlock(s_endpoint_data.mutex);
|
||||
// logging_session is waiting for an ack, we'll send next time around
|
||||
// don't return a failure, this is pretty innocuous.
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t total_length = sizeof(DataLoggingSendDataMessage) + num_bytes;
|
||||
const uint32_t timeout_ms = 500;
|
||||
SendBuffer *sb = comm_session_send_buffer_begin_write(session, ENDPOINT_ID_DATA_LOGGING,
|
||||
total_length, timeout_ms);
|
||||
if (!sb) {
|
||||
mutex_unlock(s_endpoint_data.mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_DATA_LOGGING_ENDPOINT_SENDS,
|
||||
AnalyticsClient_System);
|
||||
|
||||
const DataLoggingSendDataMessage header = (const DataLoggingSendDataMessage) {
|
||||
.command = DataLoggingEndpointCmdData,
|
||||
.session_id = logging_session->comm.session_id,
|
||||
.items_left_hereafter = 0xffff, // FIXME: logging_session->storage.num_bytes - num_bytes,
|
||||
.crc32 = legacy_defective_checksum_memory(data, num_bytes),
|
||||
};
|
||||
comm_session_send_buffer_write(sb, (const uint8_t *) &header, sizeof(header));
|
||||
comm_session_send_buffer_write(sb, data, num_bytes);
|
||||
comm_session_send_buffer_end_write(sb);
|
||||
|
||||
dls_endpoint_print_message((uint8_t *) &header, num_bytes);
|
||||
DLS_HEXDUMP(data, MIN(num_bytes, 64));
|
||||
|
||||
logging_session->comm.num_bytes_pending = num_bytes;
|
||||
|
||||
update_session_state(logging_session, DataLoggingSessionCommStateSending, true /*reschedule*/);
|
||||
|
||||
mutex_unlock(s_endpoint_data.mutex);
|
||||
|
||||
unsigned int data_buffer_length = sizeof(DataLoggingSendDataMessage) + num_bytes;
|
||||
if (!uuid_is_system(&logging_session->app_uuid)) {
|
||||
analytics_inc_for_uuid(ANALYTICS_APP_METRIC_LOG_OUT_COUNT, &logging_session->app_uuid);
|
||||
analytics_add_for_uuid(ANALYTICS_APP_METRIC_LOG_BYTE_OUT_COUNT, data_buffer_length, &logging_session->app_uuid);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_dls_endpoint_handle_ack(uint8_t session_id) {
|
||||
DataLoggingSession *session = dls_list_find_by_session_id(session_id);
|
||||
if (session == NULL) {
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_WARNING, "Received ack for non-existent session id: %"PRIu8, session_id);
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(s_endpoint_data.mutex);
|
||||
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Received ACK for id: %"PRIu8" state: %u", session->comm.session_id, session->comm.state);
|
||||
|
||||
switch (session->comm.state) {
|
||||
case DataLoggingSessionCommStateIdle:
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Unexpected ACK");
|
||||
break;
|
||||
case DataLoggingSessionCommStateOpening:
|
||||
update_session_state(session, DataLoggingSessionCommStateIdle, true /*reschedule*/);
|
||||
break;
|
||||
case DataLoggingSessionCommStateSending:
|
||||
session->comm.nack_count = 0;
|
||||
update_session_state(session, DataLoggingSessionCommStateIdle, true /*reschedule*/);
|
||||
|
||||
mutex_unlock(s_endpoint_data.mutex);
|
||||
|
||||
// unlock for time consuming activities
|
||||
dls_storage_consume(session, session->comm.num_bytes_pending);
|
||||
session->comm.num_bytes_pending = 0;
|
||||
|
||||
// the bt session is likely already active so continue to flush data
|
||||
dls_private_send_session(session, true);
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_unlock(s_endpoint_data.mutex);
|
||||
}
|
||||
|
||||
static void prv_dls_endpoint_handle_nack(uint8_t session_id) {
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Received NACK for id: %"PRIu8, session_id);
|
||||
|
||||
DataLoggingSession *logging_session = dls_list_find_by_session_id(session_id);
|
||||
if (!logging_session) {
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_WARNING, "Received nack for non-existent session id: %"PRIu8, session_id);
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(s_endpoint_data.mutex);
|
||||
switch (logging_session->comm.state) {
|
||||
case DataLoggingSessionCommStateIdle:
|
||||
case DataLoggingSessionCommStateOpening:
|
||||
//Currently, these messages never get NACK'd
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Unexpected NACK");
|
||||
break;
|
||||
case DataLoggingSessionCommStateSending:
|
||||
//Maybe queue a resend
|
||||
logging_session->comm.num_bytes_pending = 0;
|
||||
if (++logging_session->comm.nack_count > MAX_NACK_COUNT) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Too many nacks. Flushing...");
|
||||
dls_storage_consume(logging_session, logging_session->storage.num_bytes);
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_DATA_LOGGING_FLUSH_COUNT, AnalyticsClient_System);
|
||||
logging_session->comm.nack_count = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
update_session_state(logging_session, DataLoggingSessionCommStateIdle, true /*reschedule*/);
|
||||
|
||||
mutex_unlock(s_endpoint_data.mutex);
|
||||
|
||||
// reopen the session that was NACK'ed
|
||||
dls_endpoint_open_session(logging_session);
|
||||
}
|
||||
|
||||
//! System task callback executed which reopens the next session in the list built up by report_cmd_system_task_cb
|
||||
static void prv_reopen_next_session_system_task_cb(void* data) {
|
||||
DataLoggingReopenEntry *entry = (DataLoggingReopenEntry *)data;
|
||||
if (!entry) {
|
||||
s_endpoint_data.report_in_progress = false;
|
||||
return;
|
||||
}
|
||||
DataLoggingReopenEntry *new_head = (DataLoggingReopenEntry *)list_pop_head((ListNode *)entry);
|
||||
|
||||
// Try and reopen this session
|
||||
bool success = false;
|
||||
if (dls_list_is_session_valid(entry->session) &&
|
||||
uuid_equal(&entry->app_uuid, &entry->session->app_uuid) &&
|
||||
entry->timestamp == entry->session->session_created_timestamp &&
|
||||
entry->tag == entry->session->tag) {
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Reopening session %d",
|
||||
entry->session->comm.session_id);
|
||||
success = (dls_endpoint_open_session(entry->session)
|
||||
&& dls_private_send_session(entry->session, false));
|
||||
} else {
|
||||
// Session has disappeared between the time that the reopen list was
|
||||
// created and now. This ideally shouldn't happen, but there's a lot
|
||||
// that's broken about datalogging. See PBL-37078.
|
||||
success = true;
|
||||
}
|
||||
kernel_free(entry);
|
||||
|
||||
if (success) {
|
||||
// Schedule next one
|
||||
if (new_head) {
|
||||
bool result = system_task_add_callback(prv_reopen_next_session_system_task_cb, new_head);
|
||||
PBL_ASSERTN(result);
|
||||
} else {
|
||||
s_endpoint_data.report_in_progress = false;
|
||||
}
|
||||
} else {
|
||||
s_endpoint_data.report_in_progress = false;
|
||||
// If we failed, give up on the remaining ones
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Aborting all remaining open requests");
|
||||
while (new_head) {
|
||||
DataLoggingReopenEntry *entry = new_head;
|
||||
new_head = (DataLoggingReopenEntry *)list_pop_head((ListNode *)new_head);
|
||||
kernel_free(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! For use with dls_list_for_each_session. Appends this session to our list of sesions we need to open.
|
||||
//! On entry, 'data' points to the variable holding the head of the list.
|
||||
static bool dls_endpoint_add_reopen_sessions_cb(DataLoggingSession *session, void *data) {
|
||||
DataLoggingReopenEntry **head_ptr = (DataLoggingReopenEntry **)data;
|
||||
DataLoggingReopenEntry *entry = kernel_malloc_check(sizeof(DataLoggingReopenEntry));
|
||||
*entry = (DataLoggingReopenEntry) {
|
||||
.session = session,
|
||||
.app_uuid = session->app_uuid,
|
||||
.timestamp = session->session_created_timestamp,
|
||||
.tag = session->tag,
|
||||
};
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "adding session %d to reopen list", session->comm.session_id);
|
||||
*head_ptr = (DataLoggingReopenEntry *)list_insert_before((ListNode *)(*head_ptr), &entry->list_node);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_handle_report_cmd(const uint8_t *session_ids, size_t num_sessions) {
|
||||
for (size_t i = 0; i < num_sessions; ++i) {
|
||||
const uint8_t session_id = session_ids[i];
|
||||
|
||||
DataLoggingSession *logging_session = dls_list_find_by_session_id(session_id);
|
||||
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Phone reported session %u opened", session_id);
|
||||
|
||||
// If the phone thinks we're open and we're not, send a close message.
|
||||
if (logging_session == NULL) {
|
||||
dls_endpoint_close_session(session_id);
|
||||
}
|
||||
}
|
||||
|
||||
// If the bluetooth connection is flaky, a session reopen could take a few seconds, so we will chain them
|
||||
// and only do 1 re-open per system callback so that we don't trigger a watchdog timeout.
|
||||
DataLoggingReopenEntry *head = NULL;
|
||||
dls_list_for_each_session(dls_endpoint_add_reopen_sessions_cb, (void *)&head);
|
||||
|
||||
// Re-open the first one and reschedule the next one
|
||||
prv_reopen_next_session_system_task_cb((void *)head);
|
||||
}
|
||||
|
||||
//! Empty a session by session id
|
||||
static void prv_empty_session(uint8_t session_id) {
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Phone requested empty of session %u",
|
||||
session_id);
|
||||
DataLoggingSession *logging_session = dls_list_find_by_session_id(session_id);
|
||||
if (logging_session) {
|
||||
dls_private_send_session(logging_session, true /*empty_all_data*/);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! data_logging_protocol_msg_callback runs on Bluetooth task. Keep it quick.
|
||||
void data_logging_protocol_msg_callback(CommSession *session, const uint8_t *data, size_t length) {
|
||||
// consume the first byte to read the command
|
||||
uint8_t command = data[0];
|
||||
|
||||
--length; // the length now reflects the sizeof the payload
|
||||
|
||||
// All commands from the phone have their high bit set.
|
||||
if ((command & ~DLS_ENDPOINT_CMD_MASK) == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Invalid data logging endpoint command 0x%x", command);
|
||||
// TODO: send some error code back?
|
||||
return;
|
||||
}
|
||||
|
||||
switch (command & DLS_ENDPOINT_CMD_MASK) {
|
||||
case (DataLoggingEndpointCmdAck):
|
||||
prv_dls_endpoint_handle_ack(data[1]);
|
||||
break;
|
||||
|
||||
case (DataLoggingEndpointCmdNack):
|
||||
prv_dls_endpoint_handle_nack(data[1]);
|
||||
break;
|
||||
|
||||
case (DataLoggingEndpointCmdReport):
|
||||
if (s_endpoint_data.report_in_progress) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Report already in progress");
|
||||
} else {
|
||||
s_endpoint_data.report_in_progress = true;
|
||||
prv_handle_report_cmd(&data[1], length);
|
||||
}
|
||||
break;
|
||||
|
||||
case (DataLoggingEndpointCmdEmptySession):
|
||||
prv_empty_session(data[1]);
|
||||
break;
|
||||
|
||||
case (DataLoggingEndpointCmdGetSendEnableReq):
|
||||
{
|
||||
bool enabled = dls_get_send_enable();
|
||||
struct PACKED {
|
||||
uint8_t command;
|
||||
uint8_t enabled;
|
||||
} msg = {
|
||||
.command = DataLoggingEndpointCmdGetSendEnableRsp,
|
||||
.enabled = enabled,
|
||||
};
|
||||
|
||||
comm_session_send_data(session, ENDPOINT_ID_DATA_LOGGING, (uint8_t *)&msg,
|
||||
sizeof(msg), COMM_SESSION_DEFAULT_TIMEOUT);
|
||||
}
|
||||
break;
|
||||
|
||||
case (DataLoggingEndpointCmdSetSendEnable):
|
||||
dls_set_send_enable_pp(data[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void dls_endpoint_init(void) {
|
||||
s_endpoint_data.mutex = mutex_create();
|
||||
s_endpoint_data.ack_timer = new_timer_create();
|
||||
}
|
||||
|
||||
static bool prv_handle_disconnect_cb(DataLoggingSession *session, void *data) {
|
||||
session->comm.state = DataLoggingSessionCommStateIdle;
|
||||
return true;
|
||||
}
|
||||
|
||||
void dls_private_handle_disconnect(void *data) {
|
||||
mutex_lock(s_endpoint_data.mutex);
|
||||
dls_list_for_each_session(prv_handle_disconnect_cb, 0);
|
||||
mutex_unlock(s_endpoint_data.mutex);
|
||||
}
|
30
src/fw/services/normal/data_logging/dls_endpoint.h
Normal file
30
src/fw/services/normal/data_logging/dls_endpoint.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 "dls_private.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void dls_endpoint_init(void);
|
||||
|
||||
void dls_endpoint_close_session(uint8_t session_id);
|
||||
|
||||
bool dls_endpoint_send_data(DataLoggingSession *logging_session, const uint8_t *data, unsigned int num_bytes);
|
||||
|
||||
bool dls_endpoint_open_session(DataLoggingSession *logging_session);
|
||||
|
369
src/fw/services/normal/data_logging/dls_list.c
Normal file
369
src/fw/services/normal/data_logging/dls_list.c
Normal file
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
* 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 "dls_list.h"
|
||||
#include "dls_storage.h"
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "kernel/events.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_management/process_manager.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/uuid.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static DataLoggingSession *s_logging_sessions;
|
||||
static PebbleRecursiveMutex * s_list_mutex;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// Assert that the current task owns the list mutex
|
||||
void dls_assert_own_list_mutex(void) {
|
||||
PBL_ASSERTN(mutex_is_owned_recursive(s_list_mutex));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// Lock a session (if active). If session was active, locks it and returns true.
|
||||
// If session is not active, does no locking and returns false.
|
||||
//
|
||||
// Note regarding the list_mutex and the session->data-mutex:
|
||||
// * session->status can only be read/modified while holding the list mutex
|
||||
// * session->data->open_count can only be read/modified while holding the list mutex
|
||||
// and is only available if session->status == DataLoggingStatusActive
|
||||
// * In order to avoid deadlocks,
|
||||
// - s_list_mutex MUST be released befored trying to grab session->data->mutex.
|
||||
// - session->data->open_count must incremented to be > 0 under s_list_mutex before you can
|
||||
// grab session->data-mutex
|
||||
// - if you already own session->data-mutex, it is OK to grab s_list_mutex
|
||||
bool dls_lock_session(DataLoggingSession *session) {
|
||||
mutex_lock_recursive(s_list_mutex);
|
||||
if (session->status != DataLoggingStatusActive) {
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
PBL_ASSERTN(session->data);
|
||||
|
||||
// Incrementing open_count insures that no one else can do a dls_unlock_session(inactivate=true)
|
||||
// on it and cause it to be freed before we grab the session->data->mutex below.
|
||||
session->data->open_count++;
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
|
||||
mutex_lock(session->data->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// Callback used to free a storage buffer from unprivileged mode.
|
||||
static void prv_free_storage_buffer_cb(void *p) {
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Freeing buffer storage ptr: %p", p);
|
||||
task_free(p);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
static void prv_free_storage_buffer(DataLoggingSession *session) {
|
||||
if (!session->data->buffer_storage) {
|
||||
// Skip if no buffer to free.
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->data->buffer_in_kernel_heap) {
|
||||
kernel_free(session->data->buffer_storage);
|
||||
} else {
|
||||
// The subscriber's buffer was allocated on its unprivileged process heap (app or worker). It
|
||||
// is unsafe to free it from here in privileged mode because a corrupted heap could crash the
|
||||
// watch. We will post a callback event to the process's event handler which is executed
|
||||
// in unprivileged mode.
|
||||
PebbleEvent e = {
|
||||
.type = PEBBLE_CALLBACK_EVENT,
|
||||
.callback = {
|
||||
.callback = prv_free_storage_buffer_cb,
|
||||
.data = session->data->buffer_storage
|
||||
}
|
||||
};
|
||||
PebbleTask task = pebble_task_get_current();
|
||||
process_manager_send_event_to_process(task, &e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// Unlock a session previous locked by dls_lock_session(). If inactive is true, this also marks
|
||||
// the session inactive and frees the memory used for maintaining the active state. See the
|
||||
// comments above in dls_lock_session() for a description of the locking strategy.
|
||||
void dls_unlock_session(DataLoggingSession *session, bool inactivate) {
|
||||
mutex_lock_recursive(s_list_mutex);
|
||||
|
||||
PBL_ASSERTN(session->data->open_count > 0);
|
||||
if (inactivate) {
|
||||
session->data->inactivate_pending = true;
|
||||
}
|
||||
session->data->open_count--;
|
||||
if (session->data->inactivate_pending && session->data->open_count == 0) {
|
||||
session->status = DataLoggingStatusInactive;
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
|
||||
prv_free_storage_buffer(session);
|
||||
mutex_unlock(session->data->mutex);
|
||||
mutex_destroy(session->data->mutex);
|
||||
kernel_free(session->data);
|
||||
session->data = NULL;
|
||||
|
||||
} else {
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
mutex_unlock(session->data->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// Return session status
|
||||
DataLoggingStatus dls_get_session_status(DataLoggingSession *session) {
|
||||
mutex_lock_recursive(s_list_mutex);
|
||||
DataLoggingStatus status = session->status;
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
DataLoggingSession *dls_list_find_by_session_id(uint8_t session_id) {
|
||||
mutex_lock_recursive(s_list_mutex);
|
||||
DataLoggingSession *iter = s_logging_sessions;
|
||||
while (iter != NULL) {
|
||||
if (iter->comm.session_id > session_id) {
|
||||
break;
|
||||
}
|
||||
if (iter->comm.session_id == session_id) {
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
return (iter);
|
||||
}
|
||||
iter = iter->next;
|
||||
}
|
||||
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
DataLoggingSession *dls_list_find_active_session(uint32_t tag, const Uuid *app_uuid) {
|
||||
mutex_lock_recursive(s_list_mutex);
|
||||
DataLoggingSession *iter = s_logging_sessions;
|
||||
while (iter != NULL) {
|
||||
if (iter->tag == tag && uuid_equal(&(iter->app_uuid), app_uuid)
|
||||
&& iter->status == DataLoggingStatusActive) {
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
return (iter);
|
||||
}
|
||||
iter = iter->next;
|
||||
}
|
||||
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
void dls_list_remove_session(DataLoggingSession *logging_session) {
|
||||
if (uuid_is_system(&logging_session->app_uuid)) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Deleting the system data logging session with tag %"PRIu32,
|
||||
logging_session->tag);
|
||||
}
|
||||
|
||||
mutex_lock_recursive(s_list_mutex);
|
||||
DataLoggingSession **iter = &s_logging_sessions;
|
||||
|
||||
while (*iter != NULL) {
|
||||
if (*iter == logging_session) {
|
||||
*iter = (*iter)->next;
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
if (logging_session->data) {
|
||||
mutex_destroy(logging_session->data->mutex);
|
||||
kernel_free(logging_session->data);
|
||||
}
|
||||
kernel_free(logging_session);
|
||||
return;
|
||||
}
|
||||
iter = &((*iter)->next);
|
||||
}
|
||||
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
}
|
||||
|
||||
void dls_list_remove_all(void) {
|
||||
mutex_lock_recursive(s_list_mutex);
|
||||
DataLoggingSession *cur = s_logging_sessions;
|
||||
DataLoggingSession *next;
|
||||
while (cur != NULL) {
|
||||
next = cur->next;
|
||||
if (cur->data) {
|
||||
mutex_destroy(cur->data->mutex);
|
||||
kernel_free(cur->data);
|
||||
}
|
||||
kernel_free(cur);
|
||||
cur = next;
|
||||
}
|
||||
|
||||
s_logging_sessions = NULL;
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
}
|
||||
|
||||
//! Insert logging session with known id
|
||||
void dls_list_insert_session(DataLoggingSession *logging_session) {
|
||||
mutex_lock_recursive(s_list_mutex);
|
||||
DataLoggingSession **iter = &s_logging_sessions;
|
||||
|
||||
for (int i = 0; i < logging_session->comm.session_id; i++) {
|
||||
if (*iter == NULL) {
|
||||
break;
|
||||
}
|
||||
PBL_ASSERTN(logging_session->comm.session_id != (*iter)->comm.session_id);
|
||||
if ((*iter)->comm.session_id > logging_session->comm.session_id) {
|
||||
break;
|
||||
}
|
||||
iter = &((*iter)->next);
|
||||
}
|
||||
|
||||
logging_session->next = *iter;
|
||||
*iter = logging_session;
|
||||
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Created session: %p id %"PRIu8
|
||||
" tag %"PRIu32, logging_session, logging_session->comm.session_id, logging_session->tag);
|
||||
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
}
|
||||
|
||||
// The newlib headers do not expose this because of the __STRICT_ANSI__ define
|
||||
extern int rand_r(unsigned *seed);
|
||||
|
||||
uint8_t dls_list_add_new_session(DataLoggingSession *logging_session) {
|
||||
// generate random ID
|
||||
int loops = 0;
|
||||
uint8_t session_id;
|
||||
unsigned seed = rtc_get_time() ^ (uintptr_t)&session_id;
|
||||
do {
|
||||
// use a custom seed to avoid apps maliciously setting the task-global seed
|
||||
session_id = rand_r(&seed) % 255;
|
||||
// FIXME better way to avoid infinite loop? or tune this
|
||||
PBL_ASSERTN(++loops < 100);
|
||||
} while (dls_list_find_by_session_id(session_id));
|
||||
logging_session->comm.session_id = session_id;
|
||||
|
||||
// insert in the spool list
|
||||
dls_list_insert_session(logging_session);
|
||||
|
||||
return (session_id);
|
||||
}
|
||||
|
||||
static bool count_session_cb(DataLoggingSession *session, void *data) {
|
||||
uint32_t *counter = (uint32_t *) data;
|
||||
++(*counter);
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t prv_get_num_sessions(void) {
|
||||
uint32_t counter = 0;
|
||||
dls_list_for_each_session(count_session_cb, &counter);
|
||||
return counter;
|
||||
}
|
||||
|
||||
DataLoggingSession *dls_list_create_session(uint32_t tag, DataLoggingItemType type, uint16_t size,
|
||||
const Uuid *app_uuid, time_t timestamp, DataLoggingStatus status) {
|
||||
|
||||
uint32_t num_sessions = prv_get_num_sessions();
|
||||
if (num_sessions >= DLS_MAX_NUM_SESSIONS) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Could not allocate additional DataLoggingSession objects");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DataLoggingSession *logging_session = kernel_malloc_check(sizeof(DataLoggingSession));
|
||||
|
||||
*logging_session = (DataLoggingSession){
|
||||
.status = status,
|
||||
.app_uuid = *app_uuid,
|
||||
.tag = tag,
|
||||
.task = pebble_task_get_current(),
|
||||
.item_type = type,
|
||||
.item_size = size,
|
||||
.session_created_timestamp = timestamp,
|
||||
.storage.fd = DLS_INVALID_FILE
|
||||
};
|
||||
|
||||
if (status == DataLoggingStatusActive) {
|
||||
DataLoggingActiveState *active_state = kernel_malloc_check(sizeof(DataLoggingActiveState));
|
||||
*active_state = (DataLoggingActiveState){};
|
||||
active_state->mutex = mutex_create();
|
||||
logging_session->data = active_state;
|
||||
}
|
||||
|
||||
return (logging_session);
|
||||
}
|
||||
|
||||
DataLoggingSession *dls_list_get_next(DataLoggingSession *cur) {
|
||||
mutex_lock_recursive(s_list_mutex);
|
||||
if (cur == NULL) {
|
||||
// Return the head
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
return s_logging_sessions;
|
||||
}
|
||||
|
||||
DataLoggingSession *logging_session = ((DataLoggingSession *)cur)->next;
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
return logging_session;
|
||||
}
|
||||
|
||||
bool dls_list_for_each_session(bool (callback(DataLoggingSession*, void*)), void *data) {
|
||||
mutex_lock_recursive(s_list_mutex);
|
||||
DataLoggingSession *logging_session = s_logging_sessions;
|
||||
|
||||
while (logging_session != NULL) {
|
||||
// Read the next pointer first, just in case the callback ends up removing the session.
|
||||
DataLoggingSession *next_logging_session = logging_session->next;
|
||||
|
||||
if (!callback(logging_session, data)) {
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
logging_session = next_logging_session;
|
||||
}
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
void dls_list_init(void) {
|
||||
s_list_mutex = mutex_create_recursive();
|
||||
s_logging_sessions = NULL;
|
||||
}
|
||||
|
||||
bool dls_list_is_session_valid(DataLoggingSession *logging_session) {
|
||||
mutex_lock_recursive(s_list_mutex);
|
||||
DataLoggingSession *iter = s_logging_sessions;
|
||||
|
||||
while (iter != NULL) {
|
||||
if (iter == logging_session) {
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
return true;
|
||||
}
|
||||
iter = iter->next;
|
||||
}
|
||||
mutex_unlock_recursive(s_list_mutex);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
79
src/fw/services/normal/data_logging/dls_list.h
Normal file
79
src/fw/services/normal/data_logging/dls_list.h
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dls_private.h"
|
||||
#include "applib/data_logging.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
DataLoggingSession *dls_list_find_by_session_id(uint8_t session_id);
|
||||
|
||||
DataLoggingSession *dls_list_find_active_session(uint32_t tag, const Uuid *app_uuid);
|
||||
|
||||
void dls_list_remove_session(DataLoggingSession *logging_session);
|
||||
|
||||
//! Deletes all session state in memory without changing the flash state.
|
||||
void dls_list_remove_all(void);
|
||||
|
||||
//! Add logging_session and assign ID
|
||||
uint8_t dls_list_add_new_session(DataLoggingSession *logging_session);
|
||||
|
||||
//! Add logging session with an already assigned ID. Used at startup when restoring previous
|
||||
//! sessions from flash.
|
||||
void dls_list_insert_session(DataLoggingSession *logging_session);
|
||||
|
||||
//! Creates a new DataLoggingSession object that is only initialized with the parameters given. The
|
||||
//! session will only be initialized with the given parameters. The .storage and .comm members must
|
||||
//! be seperately initialized. Also, the resulting object will need to be added to the list of
|
||||
//! sessions using one of dls_list_add_new_session and dls_list_insert_session. May return NULL if
|
||||
//! we've created too many sessions.
|
||||
DataLoggingSession *dls_list_create_session(uint32_t tag, DataLoggingItemType type, uint16_t size,
|
||||
const Uuid *app_uuid, time_t timestamp,
|
||||
DataLoggingStatus status);
|
||||
|
||||
DataLoggingSession *dls_list_get_next(DataLoggingSession *cur);
|
||||
|
||||
void dls_list_rebuild_from_storage(void);
|
||||
|
||||
//! Call callback for each session we have. Pass the data param through to the callback each time.
|
||||
//! If the callback returns false, stop iterating immediately and return false. Returns true
|
||||
//! otherwise.
|
||||
typedef bool (*DlsListCallback)(DataLoggingSession*, void*);
|
||||
bool dls_list_for_each_session(DlsListCallback cb, void *data);
|
||||
|
||||
void dls_list_init(void);
|
||||
|
||||
//! Checks to see if this is an actual valid data session
|
||||
//! Note that we pass in the logging_session parameter without making sure it's same. Make sure
|
||||
//! this function handles passing in random pointers that don't actually point to valid sessions or
|
||||
//! even valid memory.
|
||||
bool dls_list_is_session_valid(DataLoggingSession *logging_session);
|
||||
|
||||
//! Lock a session (if active). If session was active, locks it and returns true.
|
||||
//! If session is not active, returns false
|
||||
bool dls_lock_session(DataLoggingSession *session);
|
||||
|
||||
//! Unlock a session previous locked by dls_lock_session()
|
||||
void dls_unlock_session(DataLoggingSession *session, bool inactivate);
|
||||
|
||||
//! Return session status
|
||||
DataLoggingStatus dls_get_session_status(DataLoggingSession *session);
|
||||
|
||||
//! Assert that the current task owns the list mutex
|
||||
void dls_assert_own_list_mutex(void);
|
536
src/fw/services/normal/data_logging/dls_main.c
Normal file
536
src/fw/services/normal/data_logging/dls_main.c
Normal file
|
@ -0,0 +1,536 @@
|
|||
/*
|
||||
* 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 "applib/data_logging.h"
|
||||
#include "util/uuid.h"
|
||||
|
||||
#include "data_logging_service.h"
|
||||
#include "dls_endpoint.h"
|
||||
#include "dls_list.h"
|
||||
#include "dls_storage.h"
|
||||
|
||||
#include "comm/bt_lock.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/rtc.h"
|
||||
#include "drivers/watchdog.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_management/pebble_process_md.h"
|
||||
#include "process_management/process_manager.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/comm_session/session.h"
|
||||
#include "services/common/regular_timer.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/logging.h"
|
||||
#include "os/mutex.h"
|
||||
#include "system/passert.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static bool s_initialized = false;
|
||||
static bool s_sends_enabled_pp = true;
|
||||
static bool s_sends_enabled_run_level = true;
|
||||
|
||||
#define DATALOGGING_DO_FLUSH_CHECK_INTERVAL_MINUTES 5
|
||||
|
||||
|
||||
static bool prv_sends_enabled(void) {
|
||||
return (s_sends_enabled_run_level && s_sends_enabled_pp);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
//! Wrapper for data_logging_send_session that makes it usable in dls_list_for_each_session
|
||||
//! @param empty_all_data A bool that indicates if the session should be force emptied
|
||||
static bool prv_send_session(DataLoggingSession *logging_session, void *empty_all_data) {
|
||||
dls_private_send_session(logging_session, (bool) empty_all_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
//! @param empty_all_data A bool that indicates if the session should be force emptied
|
||||
static void prv_send_all_sessions_system_task_cb(void *empty_all_data) {
|
||||
dls_list_for_each_session(prv_send_session, (void*) empty_all_data);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
//! @param data unused
|
||||
static void prv_check_all_sessions_timer_cb(void *data) {
|
||||
// If sends are not enabled, do nothing
|
||||
if (!prv_sends_enabled()) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Not sending sessions beause sending is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// We regularly check all our sessions to see if we have any data to send. Normally we want to
|
||||
// avoid sending the data unless there's a lot of data spooled up. This allows us to reduce the
|
||||
// number of times we have to send data for each session by batching it up into larger, fewer
|
||||
// messages. However, occasionally we do want to flush everything out.
|
||||
static int check_counter = 0;
|
||||
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "send all sessions: empty %s connected %s counter %u",
|
||||
bool_to_str(check_counter == 0),
|
||||
bool_to_str(comm_session_get_system_session() != NULL),
|
||||
check_counter);
|
||||
|
||||
system_task_add_callback(prv_send_all_sessions_system_task_cb,
|
||||
(void*)(uintptr_t)(check_counter == 0));
|
||||
|
||||
// force a flush every 15 minutes
|
||||
static const int EMPTY_ALL_SESSIONS_INTERVAL_MINUTES =
|
||||
15 / DATALOGGING_DO_FLUSH_CHECK_INTERVAL_MINUTES;
|
||||
check_counter = (check_counter + 1) % EMPTY_ALL_SESSIONS_INTERVAL_MINUTES;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
static RegularTimerInfo prv_check_all_sessions_timer_info = {
|
||||
.cb = prv_check_all_sessions_timer_cb
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
static void prv_remove_logging_session(DataLoggingSession *data) {
|
||||
DataLoggingSession *logging_session = (DataLoggingSession *)data;
|
||||
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Removing session %d.",
|
||||
logging_session->comm.session_id);
|
||||
|
||||
dls_endpoint_close_session(logging_session->comm.session_id);
|
||||
dls_storage_delete_logging_storage(logging_session);
|
||||
dls_list_remove_session(logging_session);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Grab the next chunk of bytes out of the session's storage and send it to the mobile
|
||||
// Returns false on unexpected errors, else true
|
||||
bool dls_private_send_session(DataLoggingSession *logging_session, bool empty) {
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelBackground);
|
||||
|
||||
// If sends are not enabled, ignore
|
||||
if (!prv_sends_enabled()) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Not sending session beause sending is disabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only attempt to send data out if we can communicate with the phone
|
||||
if (comm_session_get_system_session() == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t total_bytes = logging_session->storage.num_bytes;
|
||||
bool inactive = (dls_get_session_status(logging_session) == DataLoggingStatusInactive);
|
||||
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG,
|
||||
"de-logging session %"PRIu8", tag %"PRIu32
|
||||
" (inactive %s tot_bytes %"PRIu32" empty %s)",
|
||||
logging_session->comm.session_id, logging_session->tag,
|
||||
bool_to_str(inactive), total_bytes, bool_to_str(empty));
|
||||
|
||||
if (inactive && (total_bytes == 0)) {
|
||||
prv_remove_logging_session(logging_session);
|
||||
return true;
|
||||
} else if (!empty && !inactive && (total_bytes < 8000)) {
|
||||
return true; // nothing to flush yet
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
uint8_t *buffer = kernel_malloc_check(DLS_ENDPOINT_MAX_PAYLOAD);
|
||||
unsigned int num_bytes = DLS_ENDPOINT_MAX_PAYLOAD;
|
||||
PBL_ASSERTN(logging_session->item_size <= num_bytes);
|
||||
num_bytes -= (num_bytes % logging_session->item_size);
|
||||
|
||||
uint32_t new_read_offset;
|
||||
int32_t read_bytes = dls_storage_read(logging_session, buffer, num_bytes, &new_read_offset);
|
||||
if (read_bytes < 0) {
|
||||
goto exit;
|
||||
}
|
||||
PBL_ASSERTN((uint32_t)read_bytes <= DLS_ENDPOINT_MAX_PAYLOAD);
|
||||
|
||||
unsigned int leftover_bytes = read_bytes % logging_session->item_size;
|
||||
if (leftover_bytes) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "leftover bytes in the session. Flushing...");
|
||||
// remove the number of leftover bytes so we fall back on our feet
|
||||
read_bytes -= leftover_bytes;
|
||||
dls_storage_consume(logging_session, leftover_bytes);
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_DATA_LOGGING_FLUSH_COUNT, AnalyticsClient_System);
|
||||
}
|
||||
|
||||
success = dls_endpoint_send_data(logging_session, buffer, read_bytes);
|
||||
|
||||
exit:
|
||||
kernel_free(buffer);
|
||||
return (success);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
void dls_pause(void) {
|
||||
regular_timer_remove_callback(&prv_check_all_sessions_timer_info);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
void dls_resume(void) {
|
||||
regular_timer_add_multiminute_callback(&prv_check_all_sessions_timer_info,
|
||||
DATALOGGING_DO_FLUSH_CHECK_INTERVAL_MINUTES);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
void dls_init(void) {
|
||||
dls_endpoint_init();
|
||||
dls_list_init();
|
||||
|
||||
// rebuild data logging_sessions
|
||||
dls_storage_rebuild();
|
||||
|
||||
// add callbacks to empty and check logging_sessions
|
||||
dls_resume();
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
bool dls_initialized(void) {
|
||||
return s_initialized;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
void dls_clear(void) {
|
||||
dls_list_remove_all();
|
||||
dls_storage_invalidate_all();
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Get the send_enable setting
|
||||
bool dls_get_send_enable(void) {
|
||||
return prv_sends_enabled();
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Set the send_enable setting
|
||||
void dls_set_send_enable_pp(bool setting) {
|
||||
s_sends_enabled_pp = setting;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Set the send_enable setting
|
||||
void dls_set_send_enable_run_level(bool setting) {
|
||||
s_sends_enabled_run_level = setting;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Callback used by dls_inactivate_sessions.
|
||||
static bool prv_inactivate_sessions_each_cb(DataLoggingSession *session, void *data) {
|
||||
// Note that s_list_mutex is already owned because this is called from
|
||||
// dls_list_for_each_session(), so we CANNOT (and don't need to) call dls_lock_session() from
|
||||
// here because that could result in a deadlock (see comments in dls_lock_session).
|
||||
dls_assert_own_list_mutex();
|
||||
if (session->status != DataLoggingStatusActive) {
|
||||
// Already inactive
|
||||
return true;
|
||||
}
|
||||
|
||||
PebbleTask task = (PebbleTask)(uintptr_t)data;
|
||||
|
||||
// System data logging sessions are responsible for killing themselves
|
||||
Uuid system_uuid = UUID_SYSTEM;
|
||||
if (!uuid_equal(&session->app_uuid, &system_uuid)) {
|
||||
if (task == session->task) {
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Inactivating session: %"PRIu8,
|
||||
session->comm.session_id);
|
||||
|
||||
// Free the buffer if it's in kernel heap. If not in kernel heap we are intentionally not
|
||||
// freeing the data->buffer_storage because it was allocated on the client's heap, and the
|
||||
// client is being destroyed.
|
||||
if (session->data->buffer_in_kernel_heap) {
|
||||
kernel_free(session->data->buffer_storage);
|
||||
}
|
||||
|
||||
// All the lock/unlock session calls are made from privileged mode, so it is impossible
|
||||
// for the task to exit with the session locked (open_count > 0)
|
||||
PBL_ASSERTN(session->data->open_count == 0);
|
||||
|
||||
session->status = DataLoggingStatusInactive;
|
||||
// Free up the data and mutex for this session
|
||||
mutex_destroy(session->data->mutex);
|
||||
kernel_free(session->data);
|
||||
session->data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
void dls_send_all_sessions(void) {
|
||||
// If sends are not enabled, do nothing
|
||||
if (!prv_sends_enabled()) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Not sending sessions beause sending is disabled");
|
||||
return;
|
||||
}
|
||||
system_task_add_callback(prv_send_all_sessions_system_task_cb, (void*) true);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Mark all sessions belonging to 'task' as inactive so that no more data can be added to them.
|
||||
// They will only be deleted after the endpoint finishes sending the data to the mobile.
|
||||
void dls_inactivate_sessions(PebbleTask task) {
|
||||
dls_list_for_each_session(prv_inactivate_sessions_each_cb, (void *)(uintptr_t)task);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
static DataLoggingSession *prv_dls_create(uint32_t tag, DataLoggingItemType item_type,
|
||||
uint16_t item_size, bool buffered, void *buffer,
|
||||
bool resume, const Uuid* uuid) {
|
||||
// validate size parameter
|
||||
if (item_size == 0 || (buffered && item_size > DLS_SESSION_MAX_BUFFERED_ITEM_SIZE)
|
||||
|| (!buffered && item_size > DLS_ENDPOINT_MAX_PAYLOAD)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "invalid logging_session item size, %d", item_size);
|
||||
return (NULL);
|
||||
} else if (item_type == DATA_LOGGING_UINT || item_type == DATA_LOGGING_INT) {
|
||||
if (item_size > 4 || item_size == 3) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Invalid data width: integer types can be 1, 2, or 4 bytes");
|
||||
return (NULL);
|
||||
}
|
||||
}
|
||||
|
||||
DataLoggingSession *logging_session = dls_list_find_active_session(tag, uuid);
|
||||
|
||||
if (!resume && logging_session != NULL) {
|
||||
dls_finish(logging_session);
|
||||
logging_session = NULL;
|
||||
}
|
||||
|
||||
if (logging_session == NULL) {
|
||||
logging_session = dls_list_create_session(tag, item_type, item_size, uuid, rtc_get_time(),
|
||||
DataLoggingStatusActive);
|
||||
if (logging_session == NULL) {
|
||||
// No need to log again here, dls_list_create_session will log on our behalf
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add to the linked list of logging_sessions. This assigns a new unique session_id to this
|
||||
// session.
|
||||
dls_list_add_new_session(logging_session);
|
||||
|
||||
if (buffered) {
|
||||
uint32_t buf_size = DLS_SESSION_MIN_BUFFER_SIZE;
|
||||
|
||||
// Allocate the buffer if the caller didn't
|
||||
if (!buffer) {
|
||||
// Workers are allowed to allocate the buffer storage in the system heap because
|
||||
// they have such limited memory
|
||||
PebbleTask task = pebble_task_get_current();
|
||||
PBL_ASSERTN(task == PebbleTask_Worker || task == PebbleTask_KernelMain ||
|
||||
task == PebbleTask_KernelBackground);
|
||||
buffer = kernel_malloc_check(buf_size);
|
||||
logging_session->data->buffer_in_kernel_heap = true;
|
||||
}
|
||||
logging_session->data->buffer_storage = buffer;
|
||||
shared_circular_buffer_init(&logging_session->data->buffer,
|
||||
logging_session->data->buffer_storage, buf_size);
|
||||
shared_circular_buffer_add_client(&logging_session->data->buffer,
|
||||
&logging_session->data->buffer_client);
|
||||
} else {
|
||||
// non buffered sessions can only be created/used from KernelBG
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelBackground);
|
||||
}
|
||||
}
|
||||
|
||||
// send an open message
|
||||
dls_endpoint_open_session(logging_session);
|
||||
|
||||
return (logging_session);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
DataLoggingSession* dls_create(uint32_t tag, DataLoggingItemType item_type, uint16_t item_size,
|
||||
bool buffered, bool resume, const Uuid* uuid) {
|
||||
return prv_dls_create(tag, item_type, item_size, buffered, NULL /*buffer*/, resume, uuid);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
DataLoggingSession* dls_create_current_process(uint32_t tag, DataLoggingItemType item_type,
|
||||
uint16_t item_size, void* buffer, bool resume) {
|
||||
const PebbleProcessMd *md = sys_process_manager_get_current_process_md();
|
||||
return prv_dls_create(tag, item_type, item_size, true, buffer, resume, &md->uuid);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
void dls_finish(DataLoggingSession *logging_session) {
|
||||
PBL_ASSERTN(logging_session != NULL);
|
||||
if (uuid_is_system(&logging_session->app_uuid)) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Finishing the system data logging session at %p", logging_session);
|
||||
}
|
||||
|
||||
bool is_active = dls_lock_session(logging_session);
|
||||
if (!is_active) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Tried to close a non-active data logging session");
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for write buffer to empty
|
||||
int timeout = 1000; // 1 second
|
||||
while (logging_session->data->buffer_storage != NULL && timeout) {
|
||||
int bytes_pending = shared_circular_buffer_get_read_space_remaining(
|
||||
&logging_session->data->buffer, &logging_session->data->buffer_client);
|
||||
if (bytes_pending == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// There's still bytes in the circular buffer that haven't been persisted to flash yet. Just
|
||||
// unlock and wait a little bit, since the system task should be busy writing these to flash.
|
||||
dls_unlock_session(logging_session, false /*inactivate*/);
|
||||
timeout -= 10;
|
||||
psleep(10);
|
||||
if (!dls_lock_session(logging_session)) {
|
||||
// Someone snuck in and marked it inactive on us
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout <= 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Timed out waiting for logging_session to write");
|
||||
}
|
||||
dls_unlock_session(logging_session, true /*inactivate*/);
|
||||
exit:
|
||||
dls_send_all_sessions();
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
static bool prv_write_session_to_flash(DataLoggingSession* session, void *data) {
|
||||
dls_storage_write_session(session);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_write_all_sessions_to_flash(void *data) {
|
||||
dls_list_for_each_session(prv_write_session_to_flash, NULL);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
DataLoggingResult dls_log(DataLoggingSession *session, const void* data, uint32_t num_items) {
|
||||
#if !RELEASE
|
||||
// TODO: We should be able to remove this requirement once PBL-23925 is fixed
|
||||
//
|
||||
// Some datalogging code holds the dls_list.c:s_list_mutex while taking the
|
||||
// bt_lock. Since we are locking the list and then trying to get the bt_lock,
|
||||
// any other thread which holds the bt_lock and then trys to call a log could
|
||||
// result in a deadlock (since dls_lock_session() uses the list mutex). For non-release
|
||||
// builds assert when this happens so we can catch the cases and fix them.
|
||||
bt_lock_assert_held(false);
|
||||
#endif
|
||||
|
||||
PBL_ASSERTN(session != NULL && data != NULL);
|
||||
DataLoggingResult result = DATA_LOGGING_SUCCESS;
|
||||
|
||||
if (num_items == 0 || data == NULL) {
|
||||
return (DATA_LOGGING_INVALID_PARAMS);
|
||||
}
|
||||
|
||||
uint32_t num_bytes = num_items * session->item_size;
|
||||
if (session->data->buffer_storage && num_bytes > DLS_SESSION_MAX_BUFFERED_ITEM_SIZE) {
|
||||
return (DATA_LOGGING_INVALID_PARAMS);
|
||||
}
|
||||
|
||||
bool active = dls_lock_session(session);
|
||||
if (!active) {
|
||||
return (DATA_LOGGING_CLOSED);
|
||||
}
|
||||
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "logging %d items of size %d to session %d",
|
||||
(int)num_items, (int)session->item_size, session->comm.session_id);
|
||||
|
||||
if (!session->data->buffer_storage) {
|
||||
// Unbuffered, we can write to storage immediately
|
||||
if (!dls_storage_write_data(session, data, num_bytes)) {
|
||||
// We always overwrite old data, so the only possibility for failure here is an internal
|
||||
// PFS error.
|
||||
result = DATA_LOGGING_INTERNAL_ERR;
|
||||
}
|
||||
goto unlock_and_exit;
|
||||
}
|
||||
|
||||
if (shared_circular_buffer_get_write_space_remaining(&session->data->buffer) < num_bytes) {
|
||||
result = DATA_LOGGING_BUSY;
|
||||
goto unlock_and_exit;
|
||||
}
|
||||
|
||||
shared_circular_buffer_write(&session->data->buffer, data, num_bytes,
|
||||
false /*advance_slackers*/);
|
||||
|
||||
// Only enqueue work on the system_task if we're not already waiting on the system task to handle
|
||||
// previously enqueued work for this session.
|
||||
if (!session->data->write_request_pending) {
|
||||
session->data->write_request_pending = true;
|
||||
system_task_add_callback(prv_write_all_sessions_to_flash, NULL);
|
||||
}
|
||||
|
||||
unlock_and_exit:
|
||||
dls_unlock_session(session, false /*inactive*/);
|
||||
#ifdef DLS_DEBUG_SEND_IMMEDIATELY
|
||||
dls_send_all_sessions();
|
||||
#endif
|
||||
return (result);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
bool dls_is_session_valid(DataLoggingSession *logging_session) {
|
||||
return dls_list_is_session_valid(logging_session);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// These methods provided for unit tests
|
||||
int dls_test_read(DataLoggingSession *logging_session, uint8_t *buffer, int num_bytes) {
|
||||
uint32_t new_read_offset;
|
||||
return (dls_storage_read(logging_session, buffer, num_bytes, &new_read_offset));
|
||||
}
|
||||
|
||||
int dls_test_consume(DataLoggingSession *logging_session, int num_bytes) {
|
||||
dls_storage_consume(logging_session, num_bytes);
|
||||
|
||||
return (num_bytes);
|
||||
}
|
||||
|
||||
int dls_test_get_num_bytes(DataLoggingSession *logging_session) {
|
||||
return (logging_session->storage.num_bytes);
|
||||
}
|
||||
|
||||
int dls_test_get_tag(DataLoggingSession *logging_session) {
|
||||
return (logging_session->tag);
|
||||
}
|
||||
|
||||
uint8_t dls_test_get_session_id(DataLoggingSession *logging_session) {
|
||||
return (logging_session->comm.session_id);
|
||||
}
|
235
src/fw/services/normal/data_logging/dls_private.h
Normal file
235
src/fw/services/normal/data_logging/dls_private.h
Normal file
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* 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 "applib/data_logging.h"
|
||||
#include "drivers/rtc.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "kernel/pebble_tasks.h"
|
||||
#include "os/mutex.h"
|
||||
#include "services/common/comm_session/protocol.h"
|
||||
#include "system/hexdump.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/shared_circular_buffer.h"
|
||||
#include "util/units.h"
|
||||
#include "util/uuid.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#define DLS_HEXDUMP(data, length) \
|
||||
PBL_HEXDUMP_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, data, length)
|
||||
|
||||
|
||||
// File name is formatted as: ("%s%d", DLS_FILE_NAME_PREFIX, session_id)
|
||||
#define DLS_FILE_NAME_PREFIX "dls_storage_"
|
||||
static const uint32_t DLS_FILE_NAME_MAX_LEN = 20;
|
||||
static const uint32_t DLS_FILE_INIT_SIZE_BYTES = KiBYTES(4);
|
||||
|
||||
// Limits on how much free space we try to reserver for a session file
|
||||
static const uint32_t DLS_MIN_FILE_FREE_BYTES = KiBYTES(8);
|
||||
static const uint32_t DLS_MAX_FILE_FREE_BYTES = KiBYTES(100);
|
||||
|
||||
// Min amount of available space at the end of a file before we decide to grow it
|
||||
static const uint32_t DLS_MIN_FREE_BYTES = KiBYTES(1);
|
||||
|
||||
// Max # of sessions we allow
|
||||
static const uint32_t DLS_MAX_NUM_SESSIONS = 20;
|
||||
|
||||
// Maximum total amount of storage we are allowed to use on the file system.
|
||||
static const uint32_t DLS_TOTAL_STORAGE_BYTES = KiBYTES(640);
|
||||
|
||||
// Maximum amount of space allowed for data over and above the minimum allotment per session
|
||||
#define DLS_MAX_DATA_BYTES (DLS_TOTAL_STORAGE_BYTES \
|
||||
- (DLS_MAX_NUM_SESSIONS * DLS_FILE_INIT_SIZE_BYTES))
|
||||
|
||||
typedef enum {
|
||||
//! A session is active when it's first created and it's still being logged to.
|
||||
DataLoggingStatusActive = 0x01,
|
||||
//! A session is inactive when we have data to spool to the phone but the app that created the
|
||||
//! session has since closed or the app has closed it by calling dls_finish.
|
||||
DataLoggingStatusInactive = 0x02,
|
||||
} DataLoggingStatus;
|
||||
|
||||
|
||||
// Endpoint commands
|
||||
typedef enum {
|
||||
DataLoggingEndpointCmdOpen = 0x01,
|
||||
DataLoggingEndpointCmdData = 0x02,
|
||||
DataLoggingEndpointCmdClose = 0x03,
|
||||
DataLoggingEndpointCmdReport = 0x04,
|
||||
DataLoggingEndpointCmdAck = 0x05,
|
||||
DataLoggingEndpointCmdNack = 0x06,
|
||||
DataLoggingEndpointCmdTimeout = 0x07,
|
||||
DataLoggingEndpointCmdEmptySession = 0x08,
|
||||
DataLoggingEndpointCmdGetSendEnableReq = 0x09,
|
||||
DataLoggingEndpointCmdGetSendEnableRsp = 0x0A,
|
||||
DataLoggingEndpointCmdSetSendEnable = 0x0B,
|
||||
} DataLoggingEndpointCmd;
|
||||
|
||||
//! Every command starts off with a 8-bit command byte. Commands from the phone will have their
|
||||
//! top bit set, where commands from watch will have the top bit cleared. See
|
||||
//! DataLoggingEndpointCmd for the values of the other 7 bits.
|
||||
static const uint8_t DLS_ENDPOINT_CMD_MASK = 0x7f;
|
||||
|
||||
|
||||
#define DLS_INVALID_FILE (-1)
|
||||
typedef struct DataLoggingSessionStorage {
|
||||
//! Handle to the pfs file we are using. Set to DLS_INVALID_FILE if no storage yet
|
||||
int fd;
|
||||
|
||||
//! Which byte offset in the file we are writing to
|
||||
uint32_t write_offset;
|
||||
|
||||
//! Which byte offset in the file we are reading from
|
||||
uint32_t read_offset;
|
||||
|
||||
//! Number of unread bytes in storage
|
||||
uint32_t num_bytes;
|
||||
} DataLoggingSessionStorage;
|
||||
|
||||
|
||||
// Our little comm state machine...
|
||||
//
|
||||
// +----------+ Rx Ack +----------+ Tx Data +----------+
|
||||
// | Opening |----------->| Idle |+------------>| Sending |
|
||||
// +----------+ +----------+ +----------+
|
||||
// ^ |
|
||||
// | Rx Ack |
|
||||
// +-------------------------+
|
||||
|
||||
typedef enum {
|
||||
//! The session is opening and waiting for the phone to acknowledge our open command.
|
||||
DataLoggingSessionCommStateOpening,
|
||||
//! The session is idle, ready to send data
|
||||
DataLoggingSessionCommStateIdle,
|
||||
//! The session has sent data to the phone and is waiting for an ack
|
||||
DataLoggingSessionCommStateSending,
|
||||
} DataLoggingSessionCommState;
|
||||
|
||||
typedef struct {
|
||||
//! A session ID that is chosen by the watch and is unique to all the session IDs that the
|
||||
//! watch knows about.
|
||||
uint8_t session_id;
|
||||
|
||||
DataLoggingSessionCommState state:8;
|
||||
|
||||
//! The number of times this session got nacked
|
||||
uint8_t nack_count;
|
||||
|
||||
//! How many bytes we've sent to the phone that haven't been acked yet.
|
||||
int num_bytes_pending;
|
||||
|
||||
//! The time in RtcTicks at which the current state will timeout while waiting for an ack. Set
|
||||
//! to zero if we're not waiting for one.
|
||||
RtcTicks ack_timeout;
|
||||
} DataLoggingSessionComm;
|
||||
|
||||
|
||||
//! Information needed while a session is active (watch app still adding more data).
|
||||
typedef struct {
|
||||
PebbleMutex *mutex;
|
||||
SharedCircularBuffer buffer; //! A data buffer
|
||||
SharedCircularBufferClient buffer_client;
|
||||
uint8_t *buffer_storage; //! Storage for the buffer
|
||||
//! true if buffer_storage is in kernel heap, else it's in dls_create() caller's heap
|
||||
bool buffer_in_kernel_heap:1;
|
||||
//! bool used to rate control how often we ask the system task to write us out to flash.
|
||||
bool write_request_pending:1;
|
||||
//! bool used to record the fact that a session should be inactivated once it is unlocked
|
||||
//! (by dls_unlock_session())
|
||||
bool inactivate_pending:1;
|
||||
//! Incremented/decremented under global list mutex. This structure can only be freed up when
|
||||
//! this reaches 0.
|
||||
uint8_t open_count;
|
||||
} DataLoggingActiveState;
|
||||
|
||||
|
||||
//! Data logging session metadata, struct in memory
|
||||
typedef struct DataLoggingSession {
|
||||
// FIXME use a ListNode instead of this custom list
|
||||
struct DataLoggingSession *next; //!< The next logging_session in the linked list
|
||||
|
||||
Uuid app_uuid;
|
||||
uint32_t tag;
|
||||
PebbleTask task;
|
||||
|
||||
DataLoggingItemType item_type:4;
|
||||
DataLoggingStatus status:4;
|
||||
uint16_t item_size;
|
||||
|
||||
// A timestamp of when this session was first created.
|
||||
time_t session_created_timestamp;
|
||||
|
||||
DataLoggingSessionComm comm;
|
||||
|
||||
DataLoggingSessionStorage storage;
|
||||
|
||||
//! This pointer only allocated for active sessions
|
||||
DataLoggingActiveState *data;
|
||||
} DataLoggingSession;
|
||||
|
||||
|
||||
bool dls_private_send_session(DataLoggingSession *logging_session, bool empty);
|
||||
|
||||
//! Must be called on the system task
|
||||
//! @param data unused
|
||||
void dls_private_handle_disconnect(void *data);
|
||||
|
||||
//! Get/Set the current send_enable setting
|
||||
bool dls_private_get_send_enable(void);
|
||||
void dls_private_set_send_enable(bool setting);
|
||||
|
||||
|
||||
typedef struct PACKED {
|
||||
uint8_t command;
|
||||
uint8_t session_id;
|
||||
uint32_t items_left_hereafter;
|
||||
uint32_t crc32;
|
||||
uint8_t bytes[];
|
||||
} DataLoggingSendDataMessage;
|
||||
|
||||
|
||||
//! Size of the buffer we create for buffered sessions. This is the largest item size allowed
|
||||
//! for buffered sessions.
|
||||
static const uint32_t DLS_SESSION_MAX_BUFFERED_ITEM_SIZE = 300;
|
||||
|
||||
//! Size of the buffer we create for buffered sessions. This must be 1 bigger than
|
||||
//! DLS_SESSION_MAX_BUFFERED_ITEM_SIZE because we build a circular buffer out of it
|
||||
#define DLS_SESSION_MIN_BUFFER_SIZE (DLS_SESSION_MAX_BUFFERED_ITEM_SIZE + 1)
|
||||
|
||||
//! Max payload we can send when we send logging data to the phone. This is the largest item
|
||||
//! size allowed for non-buffered sessions.
|
||||
static const uint32_t DLS_ENDPOINT_MAX_PAYLOAD = (COMM_MAX_OUTBOUND_PAYLOAD_SIZE
|
||||
- sizeof(DataLoggingSendDataMessage));
|
||||
|
||||
|
||||
//! Unit tests only
|
||||
int dls_test_read(DataLoggingSession *logging_session, uint8_t *buffer, int num_bytes);
|
||||
|
||||
//! Unit tests only
|
||||
int dls_test_consume(DataLoggingSession *logging_session, int num_bytes);
|
||||
|
||||
//! Unit tests only
|
||||
int dls_test_get_num_bytes(DataLoggingSession *logging_session);
|
||||
|
||||
//! Unit tests only
|
||||
int dls_test_get_tag(DataLoggingSession *logging_session);
|
||||
|
||||
//! Unit tests only
|
||||
uint8_t dls_test_get_session_id(DataLoggingSession *logging_session);
|
999
src/fw/services/normal/data_logging/dls_storage.c
Normal file
999
src/fw/services/normal/data_logging/dls_storage.c
Normal file
|
@ -0,0 +1,999 @@
|
|||
/*
|
||||
* 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 "data_logging_service.h"
|
||||
#include "dls_storage.h"
|
||||
#include "dls_list.h"
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "kernel/pebble_tasks.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/normal/filesystem/pfs.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/math.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
typedef enum {
|
||||
DLS_VERSION_0 = 0x20,
|
||||
} DLSFileHeaderVersion;
|
||||
|
||||
static const DLSFileHeaderVersion DLS_CURRENT_VERSION = DLS_VERSION_0;
|
||||
|
||||
// Set while executing dls_storage_rebuild() which is called from dls_init() during boot time
|
||||
// When set, we allow storage accesses from KernelMain whereas normally, only KernelBG is allowed.
|
||||
static bool s_initializing_storage = false;
|
||||
|
||||
// Each session stores data in a separate pfs file with this data in the front. The file name
|
||||
// is constructed as ("%s%d", DLS_FILE_NAME_PREFIX, comm_session_id)
|
||||
typedef struct PACKED {
|
||||
DLSFileHeaderVersion version:8;
|
||||
|
||||
uint8_t comm_session_id;
|
||||
uint32_t timestamp;
|
||||
uint32_t tag;
|
||||
Uuid app_uuid;
|
||||
DataLoggingItemType item_type:8;
|
||||
uint16_t item_size;
|
||||
} DLSFileHeader;
|
||||
|
||||
|
||||
// We organize data in the file into chunks with this header at the front of each chunk.
|
||||
// This allows us to mark chunks as already read by setting the valid bit to 0 after we
|
||||
// successfully read it out. This is necessary to keep track of read chunks in the file system
|
||||
// so that we can recover our read position after a reboot.
|
||||
#define DLS_CHUNK_HDR_NUM_BYTES_UNINITIALIZED 0x7f
|
||||
typedef struct PACKED {
|
||||
//! The number of data bytes after this header, not including this header. If this value
|
||||
//! is DLS_CHUNK_HDR_NUM_BYTES_UNINITIALIZED (all bits set), it means no data follows.
|
||||
uint8_t num_bytes:7;
|
||||
bool valid:1; // Set to false after chunk is consumed.
|
||||
} DLSChunkHeader;
|
||||
|
||||
// The most we try to fit into a data chunk. This value must be small enough to fit within the 7
|
||||
// bytes reserved for it within the DLSChunkHeader.
|
||||
#define DLS_MAX_CHUNK_SIZE_BYTES 100
|
||||
_Static_assert(DLS_MAX_CHUNK_SIZE_BYTES < DLS_CHUNK_HDR_NUM_BYTES_UNINITIALIZED,
|
||||
"DLS_MAX_CHUNK_SIZE_BYTES must be less than DLS_CHUNK_HDR_NUM_BYTES_UNINITIALIZED");
|
||||
|
||||
// Forward declarations
|
||||
static bool prv_realloc_storage(DataLoggingSession *session, uint32_t new_size);
|
||||
static bool prv_get_session_file(DataLoggingSession *session, uint32_t space_needed);
|
||||
static void prv_release_session_file(DataLoggingSession *session);
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
static void prv_assert_valid_task(void) {
|
||||
if (!s_initializing_storage) {
|
||||
// The s_initializing_storage is only set during boot, when dls_storage_rebuild() is called.
|
||||
// That is the only time we allow a task (KernelMain) other than KernelBG access to the storage
|
||||
// functions.
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelBackground);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
static void prv_get_filename(char *name, DataLoggingSession *session) {
|
||||
concat_str_int(DLS_FILE_NAME_PREFIX, session->comm.session_id, name, DLS_FILE_NAME_MAX_LEN);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Logs if an error occurs, returns true on success
|
||||
static bool prv_pfs_read(int fd, void *buf, size_t size) {
|
||||
int bytes_read;
|
||||
bytes_read = pfs_read(fd, buf, size);
|
||||
if (bytes_read < S_SUCCESS) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Err %d while reading", (int)bytes_read);
|
||||
return false;
|
||||
} else if (bytes_read < (int)size) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Read only %d bytes, expected %d", (int)bytes_read, (int)size);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Logs if an error occurs, returns true on success
|
||||
static bool prv_pfs_write(int fd, void *buf, size_t size) {
|
||||
int bytes_wrote = pfs_write(fd, buf, size);
|
||||
if (bytes_wrote < S_SUCCESS) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Err %d while writing", (int)bytes_wrote);
|
||||
return false;
|
||||
} else if (bytes_wrote < (int)size) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Wrote only %d bytes, expected %d", (int)bytes_wrote, (int)size);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Logs if an error occurs, returns true on success
|
||||
static bool prv_pfs_seek(int fd, int offset, FSeekType seek_type) {
|
||||
int result;
|
||||
result = pfs_seek(fd, offset, seek_type);
|
||||
if (result < S_SUCCESS) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Err %d while seeking", result);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Logs if an error occurs, returns true on success
|
||||
static size_t prv_pfs_get_file_size(int fd) {
|
||||
size_t result = pfs_get_file_size(fd);
|
||||
if (result == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Err getting size");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Callback passed to pfs_iterate_files. Used to find data logging files by name
|
||||
static bool prv_filename_filter_cb(const char *name) {
|
||||
const int prefix_len = strlen(DLS_FILE_NAME_PREFIX);
|
||||
return (strncmp(name, DLS_FILE_NAME_PREFIX, prefix_len) == 0);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Given a session pointer, return how much larger we want to grow the file for it if/when
|
||||
// we decide to reallocate it
|
||||
static uint32_t prv_get_desired_free_bytes(DataLoggingSession *session) {
|
||||
// By default, we will make the file 50% larger than the currently used number of bytes
|
||||
uint32_t free_bytes = session->storage.num_bytes / 2;
|
||||
|
||||
// Set some lower and upper bounds
|
||||
free_bytes = CLIP(free_bytes, DLS_MIN_FILE_FREE_BYTES, DLS_MAX_FILE_FREE_BYTES);
|
||||
return free_bytes;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
static bool prv_accumulate_size_cb(DataLoggingSession* session, void *data) {
|
||||
uint32_t *size_p = (uint32_t *)data;
|
||||
if (session->storage.write_offset != 0) {
|
||||
if (prv_get_session_file(session, 0)) {
|
||||
*size_p += prv_pfs_get_file_size(session->storage.fd);
|
||||
prv_release_session_file(session);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Get total amount of space we have allocated from the file system. This is the sum of the
|
||||
// file sizes of all the DLS files.
|
||||
static uint32_t prv_get_total_file_system_bytes(void) {
|
||||
uint32_t size = 0;
|
||||
dls_list_for_each_session(prv_accumulate_size_cb, &size);
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Total used space: %d", (int)size);
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Compact all storage files. Used to free up space for new data.
|
||||
static bool prv_compact_session_cb(DataLoggingSession* session, void *data) {
|
||||
if (session->storage.write_offset == 0) {
|
||||
// The write offset is 0 if we've never created storage for this session.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!prv_get_session_file(session, 0)) {
|
||||
// We couldn't open up this storage file. Since we are just compacting where we can,
|
||||
// just return true so that we go on to the next session in the list.
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t cur_size = prv_pfs_get_file_size(session->storage.fd);
|
||||
uint32_t target_free_bytes = prv_get_desired_free_bytes(session);
|
||||
if ((cur_size > 0) && (session->storage.num_bytes + target_free_bytes < cur_size)) {
|
||||
// We have more than the desired number of free bytes in this file, so it is a candidate
|
||||
// for compaction.
|
||||
uint32_t new_size = session->storage.num_bytes + target_free_bytes;
|
||||
new_size = MAX(new_size, DLS_FILE_INIT_SIZE_BYTES);
|
||||
prv_release_session_file(session);
|
||||
if (new_size != cur_size || session->storage.read_offset > sizeof(DLSFileHeader)) {
|
||||
prv_realloc_storage(session, new_size);
|
||||
}
|
||||
} else {
|
||||
prv_release_session_file(session);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Make sure there is at least 'needed' bytes available in our file system space
|
||||
// allowed for data logging
|
||||
static bool prv_make_file_system_space(uint32_t needed) {
|
||||
// Make sure we have at least 'needed' bytes free in the file system
|
||||
uint32_t used_space = prv_get_total_file_system_bytes();
|
||||
if (used_space + needed >= DLS_MAX_DATA_BYTES) {
|
||||
dls_list_for_each_session(prv_compact_session_cb, NULL);
|
||||
|
||||
used_space = prv_get_total_file_system_bytes();
|
||||
if (used_space + needed >= DLS_MAX_DATA_BYTES) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Open an existing or create a new storage file. If the write_offset in the storage structure
|
||||
// is 0, then write a new file header based on the info from the given session.
|
||||
static bool prv_open_file(DataLoggingSessionStorage *storage, uint8_t op_flags,
|
||||
int32_t size, DataLoggingSession *session) {
|
||||
// Open/Create the file
|
||||
char name[DLS_FILE_NAME_MAX_LEN];
|
||||
prv_get_filename(name, session);
|
||||
|
||||
int fd = pfs_open(name, op_flags, FILE_TYPE_STATIC, size);
|
||||
if (fd < S_SUCCESS) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Could not open/create DLS file %s", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (storage->write_offset != 0) {
|
||||
storage->fd = fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
DLSFileHeader hdr = (DLSFileHeader) {
|
||||
.version = DLS_CURRENT_VERSION,
|
||||
.comm_session_id = session->comm.session_id,
|
||||
.timestamp = session->session_created_timestamp,
|
||||
.tag = session->tag,
|
||||
.app_uuid = session->app_uuid,
|
||||
.item_type = session->item_type,
|
||||
.item_size = session->item_size
|
||||
};
|
||||
|
||||
// Write the header
|
||||
if (!prv_pfs_write(fd, &hdr, sizeof(hdr))) {
|
||||
pfs_close_and_remove(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Init the storage struct
|
||||
*storage = (DataLoggingSessionStorage) {
|
||||
.fd = fd,
|
||||
.write_offset = sizeof(hdr),
|
||||
.read_offset = sizeof(hdr)
|
||||
};
|
||||
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Created session-storage: "
|
||||
"id %"PRIu8", filename: %s, fd: %d, size: %d", session->comm.session_id, name, fd,
|
||||
(int)size);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Close the session file
|
||||
static void prv_release_session_file(DataLoggingSession *session) {
|
||||
PBL_ASSERTN(session->storage.fd != DLS_INVALID_FILE);
|
||||
|
||||
status_t status = pfs_close(session->storage.fd);
|
||||
if (status != S_SUCCESS) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Error %d closing file for session %d", (int)status,
|
||||
session->comm.session_id);
|
||||
}
|
||||
session->storage.fd = DLS_INVALID_FILE;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Open the session file, creating it if necessary. If space_needed is > 0, then make sure there is
|
||||
// enough space in the file to add 'space_needed' more bytes, compacting, growing, or lopping off
|
||||
// older data in the file if necessary. Returns true if there is enough space and the file was
|
||||
// successfully opened.
|
||||
// If space_needed is 0 (generally used when reading only), we just attempt to open the file and
|
||||
// don't check the available space for writing.
|
||||
// If this method returns true, the caller must eventually close the file using
|
||||
// prv_release_session_file().
|
||||
static bool prv_get_session_file(DataLoggingSession *session, uint32_t space_needed) {
|
||||
bool success = false;
|
||||
PBL_ASSERTN(session->storage.fd == DLS_INVALID_FILE);
|
||||
|
||||
// Open/create the file
|
||||
// We always reserve enough space to create a file of DLS_FILE_INIT_SIZE_BYTES, so no need to
|
||||
// check the quota (see calculation of DLS_MAX_DATA_BYTES).
|
||||
if (!prv_open_file(&session->storage, OP_FLAG_WRITE | OP_FLAG_READ, DLS_FILE_INIT_SIZE_BYTES,
|
||||
session)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (space_needed == 0) {
|
||||
// If no extra space needed, we are done because we successfully opened the file.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the file size
|
||||
size_t file_size = prv_pfs_get_file_size(session->storage.fd);
|
||||
if (file_size == 0) {
|
||||
prv_release_session_file(session);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add a minium buffer to needed. This gives us a little insurance and also allows for the
|
||||
// extra space needed for the chunk header byte that occurs at least once evvery
|
||||
// DLS_MAX_CHUNK_SIZE_BYTES bytes.
|
||||
space_needed += DLS_MIN_FREE_BYTES;
|
||||
uint32_t space_avail = file_size - session->storage.write_offset;
|
||||
if (space_needed <= space_avail) {
|
||||
// We have enough space
|
||||
return true;
|
||||
}
|
||||
uint32_t min_delta_size = space_needed - space_avail;
|
||||
|
||||
// The remaining strategies rely on reallocating the file, so we need to close it first
|
||||
prv_release_session_file(session);
|
||||
|
||||
// If we can free up space by reallocating this file, try that next. Since we are
|
||||
// reallocating anyways, take this chance to optimize the amount of free space in the file.
|
||||
uint32_t target_file_size = session->storage.num_bytes + space_needed
|
||||
+ prv_get_desired_free_bytes(session);
|
||||
target_file_size = MAX(target_file_size, DLS_FILE_INIT_SIZE_BYTES);
|
||||
uint32_t optimum_delta_size = target_file_size - file_size;
|
||||
|
||||
bool have_space_to_grow = prv_make_file_system_space(optimum_delta_size);
|
||||
if (!have_space_to_grow) {
|
||||
// If we don't have enough space to grow to our optimum size, see if growing to fill whatever
|
||||
// free space is left in the file system is sufficient.
|
||||
uint32_t total_allocated_bytes = prv_get_total_file_system_bytes();
|
||||
if (total_allocated_bytes + min_delta_size <= DLS_MAX_DATA_BYTES) {
|
||||
target_file_size = DLS_MAX_DATA_BYTES - total_allocated_bytes + file_size;
|
||||
have_space_to_grow = true;
|
||||
}
|
||||
}
|
||||
if (have_space_to_grow) {
|
||||
if (!prv_realloc_storage(session, target_file_size)) {
|
||||
goto exit;
|
||||
}
|
||||
success = prv_open_file(&session->storage, OP_FLAG_WRITE | OP_FLAG_READ,
|
||||
DLS_FILE_INIT_SIZE_BYTES, session);
|
||||
if (success) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Lop off old data at the beginning of the file if there is enough there.
|
||||
success = false;
|
||||
// If we are going to consume, we have to be prepared to consume at least 1 data chunk
|
||||
min_delta_size = MAX(min_delta_size, DLS_MAX_CHUNK_SIZE_BYTES);
|
||||
if (session->storage.num_bytes <= min_delta_size) {
|
||||
// Lopping off the used bytes won't satisfy space_needed
|
||||
goto exit;
|
||||
}
|
||||
uint32_t consume_bytes = MAX(session->storage.num_bytes/2, min_delta_size);
|
||||
if (dls_storage_consume(session, consume_bytes) < 0) {
|
||||
// We failed to lop off the used bytes
|
||||
goto exit;
|
||||
}
|
||||
// Reallocate which removes the consumed bytes from the beginning of the file.
|
||||
if (!prv_realloc_storage(session, file_size)) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// Re-open it now
|
||||
success = prv_open_file(&session->storage, OP_FLAG_WRITE | OP_FLAG_READ, DLS_FILE_INIT_SIZE_BYTES,
|
||||
session);
|
||||
|
||||
exit:
|
||||
// If success, double-check that we have enough space.
|
||||
if (success && space_needed > 0) {
|
||||
file_size = prv_pfs_get_file_size(session->storage.fd);
|
||||
PBL_ASSERTN(session->storage.write_offset + space_needed <= file_size);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
static bool prv_write_data(DataLoggingSessionStorage *storage, const void *data,
|
||||
uint32_t remaining_bytes) {
|
||||
const uint8_t *data_ptr = data;
|
||||
|
||||
// Write out in chunks
|
||||
while (remaining_bytes > 0) {
|
||||
uint8_t data_chunk_length = MIN(DLS_MAX_CHUNK_SIZE_BYTES, remaining_bytes);
|
||||
|
||||
// Write the data first, so if an error occurs, the header is left in the uninitialized state
|
||||
if (!prv_pfs_seek(storage->fd, storage->write_offset + sizeof(DLSChunkHeader), FSeekSet)) {
|
||||
return false;
|
||||
}
|
||||
if (!prv_pfs_write(storage->fd, (void *)data_ptr, data_chunk_length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write data chunk header now
|
||||
if (!prv_pfs_seek(storage->fd, storage->write_offset, FSeekSet)) {
|
||||
return false;
|
||||
}
|
||||
DLSChunkHeader data_hdr = { .num_bytes = data_chunk_length, .valid = true };
|
||||
if (!prv_pfs_write(storage->fd, &data_hdr, sizeof(DLSChunkHeader))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bump pointer and count
|
||||
storage->write_offset += data_chunk_length + sizeof(DLSChunkHeader);
|
||||
storage->num_bytes += data_chunk_length;
|
||||
|
||||
remaining_bytes -= data_chunk_length;
|
||||
data_ptr += data_chunk_length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Migrate a session's data to a new file, removing already consumed bytes from the front
|
||||
static bool prv_realloc_storage(DataLoggingSession *session, uint32_t new_size) {
|
||||
bool success;
|
||||
uint8_t *tmp_buf = NULL;
|
||||
|
||||
// Record in metrics
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_DATA_LOGGING_REALLOC_COUNT, AnalyticsClient_System);
|
||||
|
||||
// Must be called with the file closed
|
||||
PBL_ASSERTN(session->storage.fd == DLS_INVALID_FILE);
|
||||
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Compacting storage for session %d", session->comm.session_id);
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG,
|
||||
"Before compaction: num_bytes: %"PRIu32", write_offset:%"PRIu32,
|
||||
session->storage.num_bytes, session->storage.write_offset);
|
||||
|
||||
// Init a storage struct and create a new file for the compacted data
|
||||
DataLoggingSessionStorage new_storage = {
|
||||
.fd = DLS_INVALID_FILE,
|
||||
};
|
||||
success = prv_open_file(&new_storage, OP_FLAG_OVERWRITE | OP_FLAG_READ, new_size,
|
||||
session);
|
||||
if (!success) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Could not create temporary file to migrate storage file");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// Copy data in chunks from the old file to the new one. Things go faster with a bigger buffer.
|
||||
success = false;
|
||||
// We have to make sure we have at least 1 delineated item within each DLS_ENDPOINT_MAX_PAYLOAD
|
||||
// bytes and clipping max_chunk_size to DLS_ENDPOINT_MAX_PAYLOAD insures that. If we didn't clip
|
||||
// it and the item size was 645 for example, we might pack 2 items back to back in storage
|
||||
// using DLS_MAX_CHUNK_SIZE_BYTES (100) byte chunks and dls_private_send_session() wouldn't be
|
||||
// able to get a complete single item because we wrote 1290 bytes using DLS_MAX_CHUNK_SIZE_BYTES
|
||||
// byte chunks and there is no chunk boundary at the 645 byte offset.
|
||||
int32_t max_chunk_size = DLS_ENDPOINT_MAX_PAYLOAD;
|
||||
while (true) {
|
||||
tmp_buf = kernel_malloc(max_chunk_size);
|
||||
if (tmp_buf) {
|
||||
break;
|
||||
}
|
||||
if (max_chunk_size < 256) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Not enough memory for reallocation");
|
||||
goto exit;
|
||||
}
|
||||
max_chunk_size /= 2;
|
||||
}
|
||||
|
||||
int32_t bytes_to_copy = session->storage.num_bytes;
|
||||
while (bytes_to_copy) {
|
||||
uint32_t new_read_offset;
|
||||
int32_t bytes_read = dls_storage_read(session, tmp_buf, MIN(max_chunk_size, bytes_to_copy),
|
||||
&new_read_offset);
|
||||
if (bytes_read <= 0) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// Write to new file
|
||||
if (!prv_write_data(&new_storage, tmp_buf, bytes_read)) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// Consume out of old one now.
|
||||
if (dls_storage_consume(session, bytes_read) < 0) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bytes_to_copy -= bytes_read;
|
||||
}
|
||||
|
||||
// We successfully transferred the unread data to the new storage, place it into the session
|
||||
// info
|
||||
pfs_close(session->storage.fd);
|
||||
|
||||
// Close the new file now. That will finish up the swap for us
|
||||
pfs_close(new_storage.fd);
|
||||
new_storage.fd = DLS_INVALID_FILE;
|
||||
|
||||
// Plug in the new storage info into the session
|
||||
session->storage = new_storage;
|
||||
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG,
|
||||
"After compaction: size: %d, num_bytes: %d, write_offset:%d",
|
||||
(int)new_size, (int)session->storage.num_bytes, (int)session->storage.write_offset);
|
||||
success = true;
|
||||
|
||||
exit:
|
||||
kernel_free(tmp_buf);
|
||||
if (new_storage.fd != DLS_INVALID_FILE) {
|
||||
pfs_close_and_remove(new_storage.fd);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Migration failed of session file %d", session->comm.session_id);
|
||||
dls_storage_delete_logging_storage(session);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
void dls_storage_invalidate_all(void) {
|
||||
// Iterate through all files in the file system, looking for all DLS storage files and
|
||||
// deleting them.
|
||||
pfs_remove_files(prv_filename_filter_cb);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
void dls_storage_delete_logging_storage(DataLoggingSession *session) {
|
||||
prv_assert_valid_task();
|
||||
PBL_ASSERTN(session->storage.fd == DLS_INVALID_FILE);
|
||||
|
||||
char name[DLS_FILE_NAME_MAX_LEN];
|
||||
prv_get_filename(name, session);
|
||||
status_t status = pfs_remove(name);
|
||||
if (status != S_SUCCESS) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Error %d removing file", (int) status);
|
||||
}
|
||||
|
||||
// Clear out storage info
|
||||
session->storage = (DataLoggingSessionStorage) {
|
||||
.fd = DLS_INVALID_FILE,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Write data directly to flash. Called from dls_log() when the session is
|
||||
// unbuffered. Assumes the caller has already locked the session using dls_lock_session().
|
||||
bool dls_storage_write_data(DataLoggingSession *session, const void *data, uint32_t num_bytes) {
|
||||
prv_assert_valid_task();
|
||||
|
||||
bool success = false;
|
||||
bool got_session_file = prv_get_session_file(session, num_bytes);
|
||||
if (!got_session_file) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
success = prv_write_data(&session->storage, data, num_bytes);
|
||||
|
||||
exit:
|
||||
if (got_session_file) {
|
||||
prv_release_session_file(session);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Nuking storage for session %d", session->comm.session_id);
|
||||
dls_storage_delete_logging_storage(session);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Copy data out of a session's circular buffer and write it to flash. Called from a KernelBG
|
||||
// system task callback triggered by dls_log() after data is added to a buffered session.
|
||||
bool dls_storage_write_session(DataLoggingSession *session) {
|
||||
prv_assert_valid_task();
|
||||
|
||||
bool success = true;
|
||||
bool got_session_file = false;
|
||||
|
||||
// Note that s_list_mutex is already owned because this is called from
|
||||
// dls_list_for_each_session(), so we CANNOT (and don't need to) call dls_lock_session() from
|
||||
// here because that could result in a deadlock (see comments in dls_lock_session).
|
||||
dls_assert_own_list_mutex();
|
||||
if (session->status != DataLoggingStatusActive) {
|
||||
// Not active
|
||||
return true;
|
||||
}
|
||||
|
||||
// If this session is not buffered, there is no circular buffer to move data out of, it would
|
||||
// have been written directly to flash during dls_log. But, we can end up here because of a
|
||||
// call to prv_write_all_sessions_to_flash() which iterates through ALL active sessions.
|
||||
if (!session->data->buffer_storage) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
session->data->write_request_pending = false;
|
||||
int bytes_remaining = shared_circular_buffer_get_read_space_remaining(
|
||||
&session->data->buffer, &session->data->buffer_client);
|
||||
if (bytes_remaining == 0) {
|
||||
goto exit;
|
||||
}
|
||||
got_session_file = prv_get_session_file(session, bytes_remaining);
|
||||
if (!got_session_file) {
|
||||
success = false;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
while (bytes_remaining > 0) {
|
||||
const uint8_t* read_ptr;
|
||||
uint16_t bytes_read;
|
||||
success = shared_circular_buffer_read(&session->data->buffer,
|
||||
&session->data->buffer_client,
|
||||
bytes_remaining, &read_ptr, &bytes_read);
|
||||
PBL_ASSERTN(success);
|
||||
success = prv_write_data(&session->storage, read_ptr, bytes_read);
|
||||
if (!success) {
|
||||
goto exit;
|
||||
}
|
||||
shared_circular_buffer_consume(&session->data->buffer, &session->data->buffer_client,
|
||||
bytes_read);
|
||||
bytes_remaining -= bytes_read;
|
||||
}
|
||||
|
||||
exit:
|
||||
if (got_session_file) {
|
||||
prv_release_session_file(session);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Nuking storage for session %d", session->comm.session_id);
|
||||
dls_storage_delete_logging_storage(session);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Special case: if buffer is NULL, just doesn't perform any reads, it just returns the # of bytes
|
||||
// of data available for reading. Returns -1 on error.
|
||||
// On exit, *new_read_offset contains the new read_offset
|
||||
int32_t dls_storage_read(DataLoggingSession *logging_session, uint8_t *buffer, int32_t num_bytes,
|
||||
uint32_t *new_read_offset) {
|
||||
prv_assert_valid_task();
|
||||
|
||||
int32_t read_bytes = 0;
|
||||
int32_t last_whole_items_read_bytes = 0;
|
||||
bool got_session_file = false;
|
||||
|
||||
if (logging_session->storage.write_offset == 0) {
|
||||
// no data available for this session
|
||||
goto exit;
|
||||
}
|
||||
|
||||
got_session_file = prv_get_session_file(logging_session, 0);
|
||||
if (!got_session_file) {
|
||||
last_whole_items_read_bytes = -1; // error
|
||||
goto exit;
|
||||
}
|
||||
|
||||
uint32_t read_offset = logging_session->storage.read_offset;
|
||||
|
||||
while (!buffer || read_bytes < num_bytes) {
|
||||
DLSChunkHeader chunk_hdr;
|
||||
|
||||
// Reached the end of the file?
|
||||
// NOTE: we don't do this check if we are scanning for the last written byte (buffer == NULL)
|
||||
if (buffer) {
|
||||
if (read_offset >= logging_session->storage.write_offset) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the chunk header
|
||||
if (!prv_pfs_seek(logging_session->storage.fd, read_offset, FSeekSet)) {
|
||||
last_whole_items_read_bytes = -1;
|
||||
goto exit;
|
||||
}
|
||||
if (!prv_pfs_read(logging_session->storage.fd, &chunk_hdr, sizeof(chunk_hdr))) {
|
||||
last_whole_items_read_bytes = -1;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// Reached the end of the valid data?
|
||||
if (chunk_hdr.valid && chunk_hdr.num_bytes == DLS_CHUNK_HDR_NUM_BYTES_UNINITIALIZED) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Valid data?
|
||||
if (chunk_hdr.valid) {
|
||||
if (buffer) {
|
||||
if (chunk_hdr.num_bytes + read_bytes > num_bytes) {
|
||||
// Not enough room in buffer to read next chunk.
|
||||
break;
|
||||
}
|
||||
|
||||
if (!prv_pfs_read(logging_session->storage.fd, buffer, chunk_hdr.num_bytes)) {
|
||||
last_whole_items_read_bytes = -1;
|
||||
goto exit;
|
||||
}
|
||||
read_bytes += chunk_hdr.num_bytes;
|
||||
buffer += chunk_hdr.num_bytes;
|
||||
|
||||
} else {
|
||||
// Just scanning for the last written byte
|
||||
read_bytes += chunk_hdr.num_bytes;
|
||||
}
|
||||
}
|
||||
read_offset += sizeof(chunk_hdr) + chunk_hdr.num_bytes;
|
||||
|
||||
// Did we reach a whole item boundary? If so, update our "last_whole_item" bookkeeping now
|
||||
if ((read_bytes % logging_session->item_size) == 0) {
|
||||
last_whole_items_read_bytes = read_bytes;
|
||||
*new_read_offset = read_offset;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
if (got_session_file) {
|
||||
prv_release_session_file(logging_session);
|
||||
}
|
||||
|
||||
if (last_whole_items_read_bytes < 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Nuking storage for session %d", logging_session->comm.session_id);
|
||||
dls_storage_delete_logging_storage(logging_session);
|
||||
}
|
||||
|
||||
return last_whole_items_read_bytes;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Consume num_bytes of data. As a special case, if num_bytes is 0, this simply advances the
|
||||
// internal storage.read_offset to match the # of bytes already consumed without consuming any more.
|
||||
// This special mode is only used by dls_storage_rebuild() when we are resurrecting old
|
||||
// sessions from the file system.
|
||||
int32_t dls_storage_consume(DataLoggingSession *logging_session, int32_t num_bytes) {
|
||||
prv_assert_valid_task();
|
||||
|
||||
int32_t consumed_bytes = 0;
|
||||
bool got_session_file = false;
|
||||
|
||||
if (logging_session->storage.write_offset == 0) {
|
||||
// no data available for this session
|
||||
goto exit;
|
||||
}
|
||||
|
||||
got_session_file = prv_get_session_file(logging_session, 0);
|
||||
if (!got_session_file) {
|
||||
consumed_bytes = -1; // error
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool reset_read_offset = (num_bytes == 0);
|
||||
while (reset_read_offset || consumed_bytes < num_bytes) {
|
||||
DLSChunkHeader chunk_hdr;
|
||||
|
||||
// Reached the end of the file?
|
||||
if (logging_session->storage.read_offset >= logging_session->storage.write_offset) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!prv_pfs_seek(logging_session->storage.fd, logging_session->storage.read_offset,
|
||||
FSeekSet)) {
|
||||
consumed_bytes = -1; // error
|
||||
goto exit;
|
||||
}
|
||||
if (!prv_pfs_read(logging_session->storage.fd, &chunk_hdr, sizeof(chunk_hdr))) {
|
||||
consumed_bytes = -1; // error
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (chunk_hdr.valid && chunk_hdr.num_bytes == DLS_CHUNK_HDR_NUM_BYTES_UNINITIALIZED) {
|
||||
// End of valid data
|
||||
break;
|
||||
}
|
||||
|
||||
if (chunk_hdr.valid) {
|
||||
if (reset_read_offset) {
|
||||
// If we are only resetting the read offset, break out now.
|
||||
break;
|
||||
}
|
||||
if (chunk_hdr.num_bytes > num_bytes) {
|
||||
// Somehow the caller tried to consume less than they read?
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Read/consume out of sync");
|
||||
goto exit;
|
||||
}
|
||||
// Invalidate the chunk, now that we have consumed it
|
||||
chunk_hdr.valid = false;
|
||||
if (!prv_pfs_seek(logging_session->storage.fd, logging_session->storage.read_offset,
|
||||
FSeekSet)) {
|
||||
consumed_bytes = -1; // error
|
||||
goto exit;
|
||||
}
|
||||
if (!prv_pfs_write(logging_session->storage.fd, &chunk_hdr, sizeof(chunk_hdr))) {
|
||||
consumed_bytes = -1; // error
|
||||
goto exit;
|
||||
}
|
||||
if (logging_session->storage.num_bytes < chunk_hdr.num_bytes) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Inconsistent tracking of num_bytes");
|
||||
consumed_bytes = -1; // error
|
||||
goto exit;
|
||||
}
|
||||
logging_session->storage.num_bytes -= chunk_hdr.num_bytes;
|
||||
}
|
||||
|
||||
logging_session->storage.read_offset += sizeof(DLSChunkHeader) + chunk_hdr.num_bytes;
|
||||
consumed_bytes += chunk_hdr.num_bytes;
|
||||
}
|
||||
|
||||
exit:
|
||||
if (consumed_bytes > 0) {
|
||||
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG,
|
||||
"Consumed %d bytes from session %d", (int)consumed_bytes,
|
||||
logging_session->comm.session_id);
|
||||
}
|
||||
|
||||
if (got_session_file) {
|
||||
prv_release_session_file(logging_session);
|
||||
}
|
||||
|
||||
if (consumed_bytes < 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Nuking storage for session %d", logging_session->comm.session_id);
|
||||
dls_storage_delete_logging_storage(logging_session);
|
||||
}
|
||||
return consumed_bytes;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Called from dls_init() during boot time to scan for existing DLS storage files in the file
|
||||
// system and recreate sessions from them.
|
||||
void dls_storage_rebuild(void) {
|
||||
char name[DLS_FILE_NAME_MAX_LEN];
|
||||
|
||||
// This disables the checks that verify that only KernelBG is accessing the storage files.
|
||||
// dls_storage_rebuild() is called from KernelMain during boot.
|
||||
s_initializing_storage = true;
|
||||
|
||||
// Iterate through all files in the file system, looking for DLS storage files by name
|
||||
PFSFileListEntry *dir_list = pfs_create_file_list(prv_filename_filter_cb);
|
||||
|
||||
// Create a session for each entry
|
||||
PFSFileListEntry *head = dir_list;
|
||||
int num_sessions_restored = 0;
|
||||
while (head) {
|
||||
DataLoggingSession *session = NULL;
|
||||
|
||||
int fd = pfs_open(head->name, OP_FLAG_READ | OP_FLAG_WRITE, FILE_TYPE_STATIC,
|
||||
DLS_FILE_INIT_SIZE_BYTES);
|
||||
if (fd < S_SUCCESS) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Error %d opening file %s", fd, head->name);
|
||||
pfs_remove(head->name);
|
||||
goto bad_session;
|
||||
}
|
||||
|
||||
// Get the session info
|
||||
DLSFileHeader hdr;
|
||||
if (!prv_pfs_read(fd, &hdr, sizeof(hdr))) {
|
||||
pfs_close(fd);
|
||||
goto bad_session;
|
||||
}
|
||||
pfs_close(fd);
|
||||
|
||||
// Create a new session based on the file info
|
||||
session = dls_list_create_session(hdr.tag, hdr.item_type, hdr.item_size, &hdr.app_uuid,
|
||||
hdr.timestamp, DataLoggingStatusInactive);
|
||||
if (!session) {
|
||||
goto bad_session;
|
||||
}
|
||||
session->comm.session_id = hdr.comm_session_id;
|
||||
session->storage = (DataLoggingSessionStorage) {
|
||||
.fd = DLS_INVALID_FILE,
|
||||
.write_offset = sizeof(hdr),
|
||||
.read_offset = sizeof(hdr)
|
||||
};
|
||||
|
||||
// Make sure the filename is what we expect
|
||||
prv_get_filename(name, session);
|
||||
if (strncmp(name, head->name, DLS_FILE_NAME_MAX_LEN)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Expected name of %s, got %s", head->name, name);
|
||||
pfs_remove(head->name);
|
||||
goto bad_session;
|
||||
}
|
||||
|
||||
// We need to figure out how many bytes of data are unread and the offset of the
|
||||
// last byte of data (which becomes the write offset). We pass NULL into the buffer argument
|
||||
// of dls_storage_read() to tell it to compute these for us.
|
||||
uint32_t write_offset;
|
||||
int32_t num_bytes = dls_storage_read(session, NULL, 0 /*numbytes*/, &write_offset);
|
||||
if (num_bytes < 0) {
|
||||
goto bad_session;
|
||||
}
|
||||
session->storage.num_bytes = num_bytes;
|
||||
session->storage.write_offset = write_offset;
|
||||
|
||||
// To update the read offset, we pass 0 as num_bytes into dls_storage_consume()
|
||||
if (dls_storage_consume(session, 0) < 0) {
|
||||
goto bad_session;
|
||||
}
|
||||
|
||||
PBL_LOG(LOG_LEVEL_INFO,
|
||||
"Restored session %"PRIu8
|
||||
" num_bytes:%"PRIu32", read_offset:%"PRIu32", write_offset:%"PRIu32,
|
||||
session->comm.session_id, session->storage.num_bytes,
|
||||
session->storage.read_offset, session->storage.write_offset);
|
||||
|
||||
// Insert this session into our list
|
||||
dls_list_insert_session(session);
|
||||
head = (PFSFileListEntry *)head->list_node.next;
|
||||
|
||||
if (head) {
|
||||
// This operation can take awhile and tends to starve out other threads while it's on going.
|
||||
// It typically takes 100-200ms to restore a session, so if you have a lot of sessions you
|
||||
// can take 2-4 seconds to do. The KernelMain task_watchdog isn't a problem at this time
|
||||
// because we haven't started monitoring it yet, but if we starve KernelBG we'll hit false
|
||||
// watchdog reboots. Sleep a bit here so the background task has a chance to run. See
|
||||
// PBL-24560 for a long term fix.
|
||||
psleep(10);
|
||||
}
|
||||
|
||||
num_sessions_restored++;
|
||||
continue;
|
||||
|
||||
bad_session:
|
||||
pfs_remove(head->name);
|
||||
kernel_free(session);
|
||||
head = (PFSFileListEntry *)head->list_node.next;
|
||||
}
|
||||
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Restored %d sessions. Total %"PRIu32" bytes allocated",
|
||||
num_sessions_restored, prv_get_total_file_system_bytes());
|
||||
|
||||
// Free the directory list
|
||||
pfs_delete_file_list(dir_list);
|
||||
|
||||
// No longer in initialization. From now on, only KernelBG can use the session storage calls.
|
||||
s_initializing_storage = false;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Analytics
|
||||
static bool prv_max_numbytes_cb(DataLoggingSession* session, void *data) {
|
||||
uint32_t *max_bytes = (uint32_t *)data;
|
||||
*max_bytes = MAX(*max_bytes, session->storage.num_bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
void analytics_external_collect_dls_stats(void) {
|
||||
uint32_t max_bytes = 0;
|
||||
dls_list_for_each_session(prv_max_numbytes_cb, &max_bytes);
|
||||
analytics_set(ANALYTICS_DEVICE_METRIC_DATA_LOGGING_MAX_SPOOLED_BYTES, max_bytes,
|
||||
AnalyticsClient_System);
|
||||
}
|
63
src/fw/services/normal/data_logging/dls_storage.h
Normal file
63
src/fw/services/normal/data_logging/dls_storage.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 "services/normal/data_logging/dls_private.h"
|
||||
|
||||
//! @file dls_storage.h
|
||||
//!
|
||||
//! All these functions are only safe to call from the system task.
|
||||
|
||||
//! Invalidate all data logging storage space.
|
||||
void dls_storage_invalidate_all(void);
|
||||
|
||||
//! Erase the storage for the given session
|
||||
//! @param[in] session pointer to session
|
||||
void dls_storage_delete_logging_storage(DataLoggingSession *session);
|
||||
|
||||
//! Read data from the given session. If buffer is NULL, this routine simply returns
|
||||
//! the number bytes available for reading (and num_bytes is ignored). This method returns
|
||||
//! the actual number of bytes read, which may be less than the number of bytes requested
|
||||
//! if the last read would end in the middle of a data chunk.
|
||||
//! @param[in] logging_session session to read from
|
||||
//! @param[in] buffer buffer to read bytes into
|
||||
//! @param[in] num_bytes number of bytes to read
|
||||
//! @param[out] *new_read_offset file offset of the next byte to read
|
||||
//! @return number of bytes read, or -1 if error
|
||||
int32_t dls_storage_read(DataLoggingSession *logging_session, uint8_t *buffer, int32_t num_bytes,
|
||||
uint32_t *new_read_offset);
|
||||
|
||||
//! Consume data from the session without reading it into a buffer.
|
||||
//! @param[in] logging_session session to read from
|
||||
//! @param[in] num_bytes number of bytes to consume.
|
||||
//! @return number of bytes consumed, or -1 if error
|
||||
int32_t dls_storage_consume(DataLoggingSession *logging_session, int32_t num_bytes);
|
||||
|
||||
//! Move the data from the circular buffer in memory to flash.
|
||||
//! @param[in] logging_session session to write to
|
||||
//! @return true on success, false on failure
|
||||
bool dls_storage_write_session(DataLoggingSession *session);
|
||||
|
||||
//! Write data directly to logging session storage from a passed in buffer
|
||||
//! @param[in] logging_session session to write to
|
||||
//! @param[in] data buffer to write from
|
||||
//! @param[in] num_bytes number of bytes to write
|
||||
//! @return true on success, false on failure
|
||||
bool dls_storage_write_data(DataLoggingSession *session, const void *data, uint32_t num_bytes);
|
||||
|
||||
//! Call this at startup to read the session state that's been stored in the file system.
|
||||
void dls_storage_rebuild(void);
|
67
src/fw/services/normal/data_logging/dls_syscalls.c
Normal file
67
src/fw/services/normal/data_logging/dls_syscalls.c
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 "data_logging_service.h"
|
||||
#include "dls_private.h"
|
||||
|
||||
#include "kernel/memory_layout.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
DEFINE_SYSCALL(DataLoggingSessionRef, sys_data_logging_create, uint32_t tag,
|
||||
DataLoggingItemType item_type, uint16_t item_size,
|
||||
void *buffer, bool resume) {
|
||||
return dls_create_current_process(tag, item_type, item_size, buffer, resume);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(void, sys_data_logging_finish, DataLoggingSessionRef session_ref) {
|
||||
// TODO: It would be nice to verify the session itself, because they could be
|
||||
// passing us any memory address (not necesarilly a valid DataLoggingSession).
|
||||
// An evil developer could potentially use this to confuse the data_logging
|
||||
// logic, and do evil things with kernel rights. However, it's pretty unlikely
|
||||
// (especially since our executable code lives in microflash, and hence can't
|
||||
// just be overwritten by a buffer overrun), so it's probably fine.
|
||||
DataLoggingSession* session = (DataLoggingSession*)session_ref;
|
||||
|
||||
if (!dls_is_session_valid(session)) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "finish: Invalid session %p", session);
|
||||
return; // TODO: Return error code?
|
||||
}
|
||||
|
||||
dls_finish(session);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(DataLoggingResult, sys_data_logging_log,
|
||||
DataLoggingSessionRef session_ref, void* data, uint32_t num_items) {
|
||||
DataLoggingSession* session = (DataLoggingSession*)session_ref;
|
||||
|
||||
if (!dls_is_session_valid(session)) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "log: Invalid session %p", session);
|
||||
return DATA_LOGGING_INVALID_PARAMS;
|
||||
}
|
||||
if (data == NULL) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "log: NULL data pointer");
|
||||
return DATA_LOGGING_INVALID_PARAMS;
|
||||
}
|
||||
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
syscall_assert_userspace_buffer(data, num_items * session->item_size);
|
||||
}
|
||||
|
||||
return dls_log(session, data, num_items);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue