mirror of
https://github.com/google/pebble.git
synced 2025-05-05 09:21:40 -04:00
338 lines
11 KiB
C
338 lines
11 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "applib/app_message/app_message_internal.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "os/mutex.h"
|
|
#include "process_management/process_manager.h"
|
|
#include "services/normal/app_message/app_message_sender.h"
|
|
#include "services/normal/app_outbox_service.h"
|
|
#include "syscall/syscall.h"
|
|
#include "syscall/syscall_internal.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
#include "util/list.h"
|
|
|
|
static PebbleRecursiveMutex *s_app_outbox_mutex;
|
|
|
|
typedef struct {
|
|
AppOutboxMessage *head;
|
|
AppOutboxMessageHandler message_handler;
|
|
size_t consumer_data_length;
|
|
PebbleTask consumer_task;
|
|
} AppOutboxConsumer;
|
|
|
|
//! Array with the consuming kernel services that have been registered at run-time:
|
|
static AppOutboxConsumer s_app_outbox_consumer[NumAppOutboxServiceTag];
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Declarations of permitted senders:
|
|
|
|
typedef struct {
|
|
AppOutboxSentHandler sent_handler;
|
|
size_t max_length;
|
|
uint32_t max_pending_messages;
|
|
} AppOutboxSenderDef;
|
|
|
|
extern void app_message_outbox_handle_app_outbox_message_sent(AppOutboxStatus status, void *cb_ctx);
|
|
|
|
#ifdef UNITTEST
|
|
extern void test_app_outbox_sent_handler(AppOutboxStatus status, void *cb_ctx);
|
|
#endif
|
|
|
|
//! Constant array defining the allowed handlers and their restrictions:
|
|
static const AppOutboxSenderDef s_app_outbox_sender_defs[] = {
|
|
[AppOutboxServiceTagAppMessageSender] = {
|
|
.sent_handler = app_message_outbox_handle_app_outbox_message_sent,
|
|
.max_length = (sizeof(AppMessageAppOutboxData) + APP_MSG_HDR_OVRHD_SIZE + APP_MSG_8K_DICT_SIZE),
|
|
.max_pending_messages = 1,
|
|
},
|
|
#ifdef UNITTEST
|
|
[AppOutboxServiceTagUnitTest] = {
|
|
.sent_handler = test_app_outbox_sent_handler,
|
|
.max_length = 1,
|
|
.max_pending_messages = 2,
|
|
},
|
|
#endif
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Syscalls
|
|
|
|
static const AppOutboxSenderDef *prv_find_def_and_tag_by_handler(AppOutboxSentHandler sent_handler,
|
|
AppOutboxServiceTag *tag_out) {
|
|
for (AppOutboxServiceTag tag = 0; tag < NumAppOutboxServiceTag; ++tag) {
|
|
if (s_app_outbox_sender_defs[tag].sent_handler == sent_handler) {
|
|
if (tag_out) {
|
|
*tag_out = tag;
|
|
}
|
|
return &s_app_outbox_sender_defs[tag];
|
|
}
|
|
}
|
|
if (tag_out) {
|
|
*tag_out = AppOutboxServiceTagInvalid;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void app_outbox_service_send(const uint8_t *data, size_t length,
|
|
AppOutboxSentHandler sent_handler, void *cb_ctx);
|
|
|
|
DEFINE_SYSCALL(void, sys_app_outbox_send, const uint8_t *data, size_t length,
|
|
AppOutboxSentHandler sent_handler, void *cb_ctx) {
|
|
if (PRIVILEGE_WAS_ELEVATED) {
|
|
// Check that data is in app space:
|
|
syscall_assert_userspace_buffer(data, length);
|
|
}
|
|
|
|
const AppOutboxSenderDef *def = prv_find_def_and_tag_by_handler(sent_handler, NULL);
|
|
if (!def) {
|
|
PBL_LOG(LOG_LEVEL_ERROR, "AppOutbox sent_handler not allowed <%p>", sent_handler);
|
|
syscall_failed();
|
|
}
|
|
|
|
const size_t max_length = def->max_length;
|
|
if (length > max_length) {
|
|
PBL_LOG(LOG_LEVEL_ERROR, "AppOutbox max_length exceeded %"PRIu32" vs %"PRIu32,
|
|
(uint32_t)length, (uint32_t)max_length);
|
|
syscall_failed();
|
|
}
|
|
app_outbox_service_send(data, length, sent_handler, cb_ctx);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Helpers
|
|
|
|
static void prv_lock(void) {
|
|
// Using one "global" lock for all app outboxes.
|
|
// If needed, we could easily give each app outbox its own mutex, but it seems overkill right now.
|
|
mutex_lock_recursive(s_app_outbox_mutex);
|
|
}
|
|
|
|
static void prv_unlock(void) {
|
|
mutex_unlock_recursive(s_app_outbox_mutex);
|
|
}
|
|
|
|
static AppOutboxConsumer *prv_consumer_for_tag(AppOutboxServiceTag tag) {
|
|
if (tag == AppOutboxServiceTagInvalid) {
|
|
return NULL;
|
|
}
|
|
AppOutboxConsumer *consumer = &s_app_outbox_consumer[tag];
|
|
if (consumer->message_handler == NULL) {
|
|
return NULL;
|
|
}
|
|
return consumer;
|
|
}
|
|
|
|
static void prv_schedule_sent_handler(AppOutboxSentHandler sent_handler,
|
|
void *cb_ctx, AppOutboxStatus status) {
|
|
if (!sent_handler) {
|
|
return;
|
|
}
|
|
PebbleEvent event = {
|
|
.type = PEBBLE_APP_OUTBOX_SENT_EVENT,
|
|
.app_outbox_sent = {
|
|
.sent_handler = sent_handler,
|
|
.cb_ctx = cb_ctx,
|
|
.status = status,
|
|
},
|
|
};
|
|
process_manager_send_event_to_process(PebbleTask_App, &event);
|
|
}
|
|
|
|
//! @note This executes on App Task
|
|
static void prv_schedule_consumer_message_handler(AppOutboxConsumer *consumer,
|
|
AppOutboxMessage *message) {
|
|
void (*callback)(void *) = (__typeof__(callback))consumer->message_handler;
|
|
PebbleEvent event = {
|
|
.type = PEBBLE_APP_OUTBOX_MSG_EVENT,
|
|
.app_outbox_msg = {
|
|
.callback = callback,
|
|
.data = message,
|
|
},
|
|
};
|
|
sys_send_pebble_event_to_kernel(&event);
|
|
}
|
|
|
|
static uint32_t prv_num_pending_messages(const AppOutboxConsumer *consumer) {
|
|
return list_count((ListNode *)consumer->head);
|
|
}
|
|
|
|
static AppOutboxConsumer *prv_find_consumer_with_message(const AppOutboxMessage *message) {
|
|
AppOutboxMessage *head = (AppOutboxMessage *)list_get_head((ListNode *)message);
|
|
for (AppOutboxServiceTag tag = 0; tag < NumAppOutboxServiceTag; ++tag) {
|
|
if (s_app_outbox_consumer[tag].head == head) {
|
|
return &s_app_outbox_consumer[tag];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void prv_cleanup_pending_messages(AppOutboxConsumer *consumer, bool should_call_sent_handler) {
|
|
AppOutboxMessage *message = consumer->head;
|
|
consumer->head = NULL;
|
|
while (message) {
|
|
if (should_call_sent_handler) {
|
|
prv_schedule_sent_handler(message->sent_handler, message->cb_ctx,
|
|
AppOutboxStatusConsumerDoesNotExist);
|
|
}
|
|
|
|
AppOutboxMessage *next = (AppOutboxMessage *)message->node.next;
|
|
message->node = (ListNode) {};
|
|
// Don't free it, it's the responsibility of the consumer to eventually call
|
|
// app_outbox_service_consume_message(), which will free the message!
|
|
message = next;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Exported functions
|
|
|
|
void app_outbox_service_register(AppOutboxServiceTag tag,
|
|
AppOutboxMessageHandler message_handler,
|
|
PebbleTask consumer_task,
|
|
size_t consumer_data_length) {
|
|
prv_lock();
|
|
{
|
|
PBL_ASSERTN(!prv_consumer_for_tag(tag));
|
|
AppOutboxConsumer *consumer = &s_app_outbox_consumer[tag];
|
|
consumer->message_handler = message_handler;
|
|
consumer->consumer_data_length = consumer_data_length;
|
|
consumer->consumer_task = consumer_task;
|
|
}
|
|
prv_unlock();
|
|
}
|
|
|
|
void app_outbox_service_unregister(AppOutboxServiceTag service_tag) {
|
|
prv_lock();
|
|
{
|
|
prv_cleanup_pending_messages(&s_app_outbox_consumer[service_tag],
|
|
true /* should_call_sent_handler */);
|
|
s_app_outbox_consumer[service_tag].message_handler = NULL;
|
|
}
|
|
prv_unlock();
|
|
}
|
|
|
|
//! @note This executes on App Task
|
|
//! Should only get called through the syscall, sys_app_outbox_send
|
|
static void app_outbox_service_send(const uint8_t *data, size_t length,
|
|
AppOutboxSentHandler sent_handler, void *cb_ctx) {
|
|
AppOutboxStatus status = AppOutboxStatusSuccess;
|
|
prv_lock();
|
|
{
|
|
AppOutboxServiceTag tag;
|
|
const AppOutboxSenderDef *def = prv_find_def_and_tag_by_handler(sent_handler, &tag);
|
|
AppOutboxConsumer *consumer = prv_consumer_for_tag(tag);
|
|
if (!consumer) {
|
|
status = AppOutboxStatusConsumerDoesNotExist;
|
|
goto finally;
|
|
}
|
|
|
|
if (prv_num_pending_messages(consumer) >= def->max_pending_messages) {
|
|
status = AppOutboxStatusOutOfResources;
|
|
goto finally;
|
|
}
|
|
|
|
const size_t consumer_data_length = consumer->consumer_data_length;
|
|
AppOutboxMessage *message =
|
|
(AppOutboxMessage *)kernel_zalloc(sizeof(AppOutboxMessage) + consumer_data_length);
|
|
if (!message) {
|
|
status = AppOutboxStatusOutOfMemory;
|
|
goto finally;
|
|
}
|
|
|
|
*message = (AppOutboxMessage) {
|
|
.data = data,
|
|
.length = length,
|
|
.sent_handler = sent_handler,
|
|
.cb_ctx = cb_ctx,
|
|
};
|
|
|
|
consumer->head = (AppOutboxMessage *)list_prepend((ListNode *)consumer->head,
|
|
(ListNode *)message);
|
|
|
|
prv_schedule_consumer_message_handler(consumer, message);
|
|
}
|
|
finally:
|
|
if (AppOutboxStatusSuccess != status) {
|
|
prv_schedule_sent_handler(sent_handler, cb_ctx, status);
|
|
}
|
|
prv_unlock();
|
|
}
|
|
|
|
bool app_outbox_service_is_message_cancelled(AppOutboxMessage *message) {
|
|
prv_lock();
|
|
bool cancelled = !prv_find_consumer_with_message(message);
|
|
prv_unlock();
|
|
return cancelled;
|
|
}
|
|
|
|
void app_outbox_service_consume_message(AppOutboxMessage *message, AppOutboxStatus status) {
|
|
prv_lock();
|
|
{
|
|
if (app_outbox_service_is_message_cancelled(message)) {
|
|
// Don't call the sent_handler
|
|
goto finally;
|
|
}
|
|
AppOutboxConsumer *consumer = prv_find_consumer_with_message(message);
|
|
PBL_ASSERTN(consumer);
|
|
list_remove(&message->node, (ListNode **)&consumer->head, NULL);
|
|
prv_schedule_sent_handler(message->sent_handler, message->cb_ctx, status);
|
|
}
|
|
finally:
|
|
kernel_free(message);
|
|
prv_unlock();
|
|
}
|
|
|
|
void app_outbox_service_cleanup_all_pending_messages(void) {
|
|
prv_lock();
|
|
for (AppOutboxServiceTag tag = 0; tag < NumAppOutboxServiceTag; ++tag) {
|
|
AppOutboxConsumer *consumer = &s_app_outbox_consumer[tag];
|
|
prv_cleanup_pending_messages(consumer, false /* should_call_sent_handler */);
|
|
}
|
|
prv_unlock();
|
|
}
|
|
|
|
void app_outbox_service_cleanup_event(PebbleEvent *event) {
|
|
if (event->type != PEBBLE_APP_OUTBOX_MSG_EVENT) {
|
|
return;
|
|
}
|
|
// Call consume directly to clean up the message, it's not valid anyway:
|
|
app_outbox_service_consume_message((AppOutboxMessage *)event->app_outbox_msg.data,
|
|
AppOutboxStatusSuccess /* ignored */);
|
|
}
|
|
|
|
void app_outbox_service_init(void) {
|
|
s_app_outbox_mutex = mutex_create_recursive();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Unit Test Interfaces
|
|
|
|
void app_outbox_service_deinit(void) {
|
|
app_outbox_service_cleanup_all_pending_messages();
|
|
memset(&s_app_outbox_consumer, 0, sizeof(s_app_outbox_consumer));
|
|
mutex_destroy((PebbleMutex *)s_app_outbox_mutex);
|
|
s_app_outbox_mutex = NULL;
|
|
}
|
|
|
|
uint32_t app_outbox_service_max_pending_messages(AppOutboxServiceTag tag) {
|
|
return s_app_outbox_sender_defs[tag].max_pending_messages;
|
|
}
|
|
|
|
uint32_t app_outbox_service_max_message_length(AppOutboxServiceTag tag) {
|
|
return s_app_outbox_sender_defs[tag].max_length;
|
|
}
|