| /* |
| * Copyright (c) 2021 Samsung Electronics Co., Ltd. All Rights Reserved |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 |
| * as published by the Free Software Foundation. |
| */ |
| |
| #include <linux/dsms.h> |
| #include <linux/errno.h> |
| #include <linux/llist.h> |
| #include <linux/kernel.h> |
| #include <linux/kthread.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include "dsms_init.h" |
| #include "dsms_kernel_api.h" |
| #include "dsms_netlink.h" |
| #include "dsms_preboot_buffer.h" |
| |
| #define MESSAGE_COUNT_LIMIT (50) |
| |
| struct dsms_message_node { |
| struct dsms_message *message; |
| struct llist_node llist; |
| }; |
| |
| __visible_for_testing atomic_t message_counter = ATOMIC_INIT(0); |
| __visible_for_testing struct llist_head message_list = LLIST_HEAD_INIT(message_list); |
| __visible_for_testing struct task_struct *sender_thread; |
| |
| __visible_for_testing struct dsms_message *create_message(const char *feature_code, |
| const char *detail, |
| int64_t value) |
| { |
| size_t len_detail; |
| struct dsms_message *message; |
| |
| message = kmalloc(sizeof(struct dsms_message), GFP_KERNEL); |
| if (!message) { |
| DSMS_LOG_ERROR("Message allocation error."); |
| return NULL; |
| } |
| |
| message->feature_code = kmalloc_array(FEATURE_CODE_LENGTH + 1, |
| sizeof(char), GFP_KERNEL); |
| if (!message->feature_code) { |
| DSMS_LOG_ERROR("Feature code allocation error."); |
| kfree(message); |
| return NULL; |
| } |
| strncpy(message->feature_code, feature_code, sizeof(char) * |
| FEATURE_CODE_LENGTH); |
| message->feature_code[FEATURE_CODE_LENGTH] = '\0'; |
| |
| len_detail = strnlen(detail, MAX_ALLOWED_DETAIL_LENGTH) + 1; |
| message->detail = kmalloc_array(len_detail, sizeof(char), GFP_KERNEL); |
| if (!message->detail) { |
| DSMS_LOG_ERROR("Detail allocation error."); |
| kfree(message->feature_code); |
| kfree(message); |
| return NULL; |
| } |
| strncpy(message->detail, detail, len_detail); |
| message->detail[len_detail - 1] = '\0'; |
| message->value = value; |
| |
| return message; |
| } |
| |
| __visible_for_testing void destroy_message(struct dsms_message *message) |
| { |
| kfree(message->feature_code); |
| kfree(message->detail); |
| kfree(message); |
| } |
| |
| __visible_for_testing struct dsms_message_node *create_node(struct dsms_message *message) |
| { |
| struct dsms_message_node *node; |
| |
| node = kmalloc(sizeof(struct dsms_message_node), GFP_KERNEL); |
| if (!node) { |
| DSMS_LOG_ERROR("Node allocation error."); |
| return NULL; |
| } |
| |
| node->message = message; |
| return node; |
| } |
| |
| __visible_for_testing void destroy_node(struct dsms_message_node *node) |
| { |
| kfree(node); |
| } |
| |
| int dsms_preboot_buffer_add(const char *feature_code, |
| const char *detail, int64_t value) |
| { |
| struct dsms_message *message; |
| struct dsms_message_node *node; |
| |
| DSMS_LOG_DEBUG("Storing message to preboot buffer."); |
| |
| if (!atomic_add_unless(&message_counter, 1, MESSAGE_COUNT_LIMIT)) { |
| DSMS_LOG_ERROR("Preboot buffer has reached its limit."); |
| return -EBUSY; |
| } |
| |
| message = create_message(feature_code, detail, value); |
| if (!message) { |
| atomic_dec(&message_counter); |
| return -ENOMEM; |
| } |
| |
| node = create_node(message); |
| if (!node) { |
| destroy_message(message); |
| atomic_dec(&message_counter); |
| return -ENOMEM; |
| } |
| |
| llist_add(&node->llist, &message_list); |
| |
| return 0; |
| } |
| |
| __visible_for_testing struct dsms_message *dsms_preboot_buffer_get(void) |
| { |
| struct llist_node *first; |
| struct dsms_message_node *node; |
| struct dsms_message *message; |
| |
| first = llist_del_first(&message_list); |
| if (!first) |
| return NULL; |
| |
| node = llist_entry(first, struct dsms_message_node, llist); |
| message = node->message; |
| destroy_node(node); |
| |
| return message; |
| } |
| |
| __visible_for_testing int preboot_sender(void *unused) |
| { |
| int ret; |
| size_t len_detail; |
| struct dsms_message *message; |
| |
| DSMS_LOG_DEBUG("Preboot sender running."); |
| |
| while (1) { |
| message = dsms_preboot_buffer_get(); |
| if (!message) { |
| DSMS_LOG_DEBUG("Preboot buffer empty."); |
| break; |
| } |
| |
| len_detail = strnlen(message->detail, |
| MAX_ALLOWED_DETAIL_LENGTH); |
| DSMS_LOG_DEBUG("Preboot sender message {'%s', '%s' (%zu bytes), %lld}", |
| message->feature_code, message->detail, |
| len_detail, message->value); |
| ret = dsms_send_netlink_message(message->feature_code, |
| message->detail, |
| message->value); |
| if (ret < 0) |
| DSMS_LOG_ERROR("Preboot sender failed to send a message"); |
| |
| destroy_message(message); |
| } |
| |
| DSMS_LOG_DEBUG("Preboot sender exiting."); |
| return 0; |
| } |
| |
| void wakeup_preboot_sender(void) |
| { |
| wake_up_process(sender_thread); |
| } |
| |
| int __kunit_init dsms_preboot_buffer_init(void) |
| { |
| DSMS_LOG_DEBUG("Preboot buffer init."); |
| |
| sender_thread = kthread_create(preboot_sender, |
| NULL, "dsms_sender_kthread"); |
| if (IS_ERR(sender_thread)) { |
| DSMS_LOG_ERROR("Preboot sender thread failed."); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| void __kunit_exit dsms_preboot_buffer_exit(void) |
| { |
| // TODO: The 'sender_thread' must exit before the module exit function |
| // returns. Since the module is built-in, the exit function is never |
| // called at the moment. However, if it changes to a loadable module |
| // that must be guaranteed to occur. For example, by using a completion |
| // structure. |
| |
| DSMS_LOG_DEBUG("Preboot buffer exit."); |
| } |