| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2013-2019 TRUSTONIC LIMITED |
| * 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. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/string.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/kernel.h> |
| #include <linux/kthread.h> |
| |
| #include "mobicore_driver_api.h" |
| #include "tui_ioctl.h" |
| #include "tlcTui.h" |
| #include "dciTui.h" |
| #include "tui-hal.h" |
| |
| /* ------------------------------------------------------------- */ |
| /* Globals */ |
| struct tui_dci_msg_t *dci; |
| static DECLARE_COMPLETION(dci_comp); |
| static DECLARE_COMPLETION(io_comp); |
| static struct tlc_tui_ioctl_buffer_info buff_info; |
| |
| /* ------------------------------------------------------------- */ |
| /* Static */ |
| static const u32 DEVICE_ID = MC_DEVICE_ID_DEFAULT; |
| static struct task_struct *thread_id; |
| static DEFINE_MUTEX(thread_mutex); |
| static struct tlc_tui_command_t g_user_cmd = {.id = TLC_TUI_CMD_NONE}; |
| static struct mc_session_handle dr_session_handle = {0, 0}; |
| struct tlc_tui_response_t g_user_rsp = {.id = TLC_TUI_CMD_NONE, |
| .return_code = TLC_TUI_ERR_UNKNOWN_CMD}; |
| static bool g_dci_version_checked; |
| |
| /* Functions */ |
| |
| /* ------------------------------------------------------------- */ |
| static bool tlc_open_driver(void) |
| { |
| bool ret = false; |
| enum mc_result mc_ret; |
| struct mc_uuid_t dr_uuid = DR_TUI_UUID; |
| |
| /* Allocate WSM buffer for the DCI */ |
| mc_ret = mc_malloc_wsm(DEVICE_ID, 0, sizeof(struct tui_dci_msg_t), |
| (uint8_t **)&dci, 0); |
| if (mc_ret != MC_DRV_OK) { |
| tui_dev_err(mc_ret, "%d Allocation of DCI WSM failed", |
| __LINE__); |
| return false; |
| } |
| |
| /* Clear the session handle */ |
| memset(&dr_session_handle, 0, sizeof(dr_session_handle)); |
| /* The device ID (default device is used */ |
| dr_session_handle.device_id = DEVICE_ID; |
| /* Open session with the Driver */ |
| mc_ret = mc_open_session(&dr_session_handle, &dr_uuid, (uint8_t *)dci, |
| (u32)sizeof(struct tui_dci_msg_t)); |
| if (mc_ret != MC_DRV_OK) { |
| tui_dev_err(mc_ret, "%d mc_open_session() failed", __LINE__); |
| ret = false; |
| } else { |
| ret = true; |
| } |
| |
| return ret; |
| } |
| |
| /* ------------------------------------------------------------- */ |
| static bool tlc_open(void) |
| { |
| bool ret = false; |
| enum mc_result mc_ret; |
| |
| /* Open the tbase device */ |
| tui_dev_devel("Opening TEE device"); |
| mc_ret = mc_open_device(DEVICE_ID); |
| |
| /* In case the device is already open, mc_open_device will return an |
| * error (MC_DRV_ERR_INVALID_OPERATION). But in this case, we can |
| * continue, even though mc_open_device returned an error. Stop in all |
| * other case of error |
| */ |
| if (MC_DRV_OK != mc_ret && MC_DRV_ERR_INVALID_OPERATION != mc_ret) { |
| tui_dev_err(mc_ret, "%d mc_open_device() failed", __LINE__); |
| return false; |
| } |
| |
| tui_dev_devel("Opening driver session"); |
| ret = tlc_open_driver(); |
| |
| return ret; |
| } |
| |
| /* ------------------------------------------------------------- */ |
| static void tlc_wait_cmd_from_driver(void) |
| { |
| u32 ret = TUI_DCI_ERR_INTERNAL_ERROR; |
| |
| /* Wait for a command from secure driver */ |
| ret = mc_wait_notification(&dr_session_handle, -1); |
| if (ret == MC_DRV_OK) |
| tui_dev_devel("Got a command"); |
| else |
| tui_dev_err(ret, "%d mc_wait_notification() failed", __LINE__); |
| } |
| |
| struct mc_session_handle *get_session_handle(void) |
| { |
| return &dr_session_handle; |
| } |
| |
| u32 send_cmd_to_user(u32 command_id, u32 data0, u32 data1) |
| { |
| u32 ret = TUI_DCI_ERR_NO_RESPONSE; |
| int retry = 10; |
| |
| /* Init shared variables */ |
| g_user_cmd.id = command_id; |
| g_user_cmd.data[0] = data0; |
| g_user_cmd.data[1] = data1; |
| /* Erase the rsp struct */ |
| memset(&g_user_rsp, 0, sizeof(g_user_rsp)); |
| g_user_rsp.id = TLC_TUI_CMD_NONE; |
| g_user_rsp.return_code = TLC_TUI_ERR_UNKNOWN_CMD; |
| |
| while (!atomic_read(&fileopened) && retry--) { |
| msleep(100); |
| tui_dev_devel( |
| "sleep for atomic_read(&fileopened) with retry = %d", |
| retry); |
| } |
| |
| /* |
| * Check that the client (TuiService) is still present before to return |
| * the command. |
| */ |
| if (atomic_read(&fileopened)) { |
| /* Clean up previous response. */ |
| complete_all(&io_comp); |
| reinit_completion(&io_comp); |
| |
| /* |
| * Unlock the ioctl thread (IOCTL_WAIT) in order to let the |
| * client know that there is a command to process. |
| */ |
| tui_dev_devel("give way to ioctl thread"); |
| complete(&dci_comp); |
| tui_dev_devel( |
| "TUI TLC is running, waiting for the userland response" |
| ); |
| /* Wait for the client acknowledge (IOCTL_ACK). */ |
| unsigned long completed = wait_for_completion_timeout(&io_comp, |
| msecs_to_jiffies(5000)); |
| if (!completed) { |
| tui_dev_err(-1, |
| "%d No acknowledge from client, timeout!", |
| __LINE__); |
| } |
| } else { |
| /* |
| * There is no client, do nothing except reporting an error to |
| * SWd. |
| */ |
| ret = TUI_DCI_ERR_INTERNAL_ERROR; |
| tui_dev_err(ret, |
| "TUI TLC seems dead. Not waiting for userland answer"); |
| goto end; |
| } |
| |
| tui_dev_devel("Got an answer from ioctl thread."); |
| reinit_completion(&io_comp); |
| |
| /* Check id of the cmd processed by ioctl thread (paranoia) */ |
| if (g_user_rsp.id != command_id) { |
| ret = TUI_DCI_ERR_INTERNAL_ERROR; |
| tui_dev_err(ret, "%d Wrong response id 0x%08x iso 0x%08x", |
| __LINE__, |
| dci->nwd_rsp.id, |
| (u32)RSP_ID(command_id)); |
| } else { |
| /* retrieve return code */ |
| switch (g_user_rsp.return_code) { |
| case TLC_TUI_OK: |
| ret = TUI_DCI_OK; |
| break; |
| case TLC_TUI_ERROR: |
| ret = TUI_DCI_ERR_INTERNAL_ERROR; |
| break; |
| case TLC_TUI_ERR_UNKNOWN_CMD: |
| ret = TUI_DCI_ERR_UNKNOWN_CMD; |
| break; |
| } |
| } |
| |
| end: |
| /* |
| * In any case, reset the value of the command, to ensure that commands |
| * sent due to inturrupted wait_for_completion are TLC_TUI_CMD_NONE. |
| */ |
| reset_global_command_id(); |
| return ret; |
| } |
| |
| /* ------------------------------------------------------------- */ |
| static void tlc_process_cmd(void) |
| { |
| u32 ret = TUI_DCI_ERR_INTERNAL_ERROR; |
| u32 command_id = CMD_TUI_SW_NONE; |
| |
| if (!dci) { |
| tui_dev_err(-1, "%d DCI has not been set up properly - exiting", |
| __LINE__); |
| return; |
| } |
| |
| command_id = dci->cmd_nwd.id; |
| |
| if (dci->hal_rsp) |
| hal_tui_notif(); |
| |
| /* Warn if previous response was not acknowledged */ |
| if (command_id == CMD_TUI_SW_NONE) { |
| tui_dev_err(-1, "%d Notified without command", |
| __LINE__); |
| return; |
| } |
| |
| if (dci->nwd_rsp.id != CMD_TUI_SW_NONE) |
| tui_dev_err(-1, "Warning, previous response not ack"); |
| |
| /* Handle command */ |
| switch (command_id) { |
| case CMD_TUI_SW_OPEN_SESSION: |
| tui_dev_devel("CMD_TUI_SW_OPEN_SESSION."); |
| |
| if (!g_dci_version_checked) { |
| ret = TUI_DCI_ERR_INTERNAL_ERROR; |
| tui_dev_err(ret, "%d DrTui version is not compatible!", |
| __LINE__); |
| break; |
| } |
| |
| /* Start android TUI activity */ |
| ret = send_cmd_to_user( |
| TLC_TUI_CMD_START_ACTIVITY, |
| dci->cmd_nwd.payload.alloc_data.num_of_buff, |
| dci->cmd_nwd.payload.alloc_data.alloc_size); |
| |
| if (ret != TUI_DCI_OK) { |
| tui_dev_err(ret, "%d send_cmd_to_user() failed", |
| __LINE__); |
| break; |
| } |
| |
| /* Set the global tlc_tui_ioctl_buffer_info variable using |
| * tui_alloc_data_t fiel in the dci, received from DrTui. |
| */ |
| set_buffer_info(dci->cmd_nwd.payload.alloc_data); |
| |
| /* Alloc work buffer separately and send it as last buffer */ |
| ret = hal_tui_alloc( |
| dci->nwd_rsp.alloc_buffer, |
| dci->cmd_nwd.payload.alloc_data.alloc_size, |
| dci->cmd_nwd.payload.alloc_data.num_of_buff); |
| |
| if (ret != TUI_DCI_OK) { |
| tui_dev_err(ret, "%d hal_tui_alloc() failed", __LINE__); |
| send_cmd_to_user(TLC_TUI_CMD_STOP_ACTIVITY, 0, 0); |
| break; |
| } |
| |
| /* Deactivate linux UI drivers */ |
| ret = hal_tui_deactivate(); |
| |
| if (ret != TUI_DCI_OK) { |
| hal_tui_free(); |
| send_cmd_to_user(TLC_TUI_CMD_STOP_ACTIVITY, 0, 0); |
| break; |
| } |
| |
| break; |
| |
| case CMD_TUI_SW_GET_VERSION: { |
| tui_dev_devel("CMD_TUI_SW_GET_VERSION."); |
| u32 drtui_dci_version = dci->version; |
| u32 tlctui_dci_version = |
| TUI_DCI_VERSION(TUI_DCI_VERSION_MAJOR, |
| TUI_DCI_VERSION_MINOR); |
| tui_dev_info("TlcTui DCI Version (%u.%u)", |
| TUI_DCI_VERSION_GET_MAJOR(tlctui_dci_version), |
| TUI_DCI_VERSION_GET_MINOR(tlctui_dci_version)); |
| tui_dev_info("DrTui DCI Version (%u.%u)", |
| TUI_DCI_VERSION_GET_MAJOR(drtui_dci_version), |
| TUI_DCI_VERSION_GET_MINOR(drtui_dci_version)); |
| /* Write the TlcTui DCI version in the response for the SWd */ |
| dci->version = tlctui_dci_version; |
| g_dci_version_checked = true; |
| ret = TUI_DCI_OK; |
| break; |
| } |
| |
| case CMD_TUI_SW_HAL: |
| /* TODO Always answer, even if there is a cancel!! */ |
| ret = hal_tui_process_cmd(&dci->cmd_nwd.payload.hal, |
| &dci->nwd_rsp.hal_rsp); |
| break; |
| |
| case CMD_TUI_SW_CLOSE_SESSION: |
| tui_dev_devel("CMD_TUI_SW_CLOSE_SESSION."); |
| |
| /* QC: close ion client before activating Linux UI */ |
| hal_tui_free(); |
| |
| /* Activate linux UI drivers */ |
| ret = hal_tui_activate(); |
| |
| /* Stop android TUI activity */ |
| /* Ignore return code, because an error means the TLC has been |
| * killed, which imply that the activity is stopped already. |
| */ |
| send_cmd_to_user(TLC_TUI_CMD_STOP_ACTIVITY, 0, 0); |
| ret = TUI_DCI_OK; |
| |
| break; |
| |
| default: |
| ret = TUI_DCI_ERR_UNKNOWN_CMD; |
| tui_dev_err(ret, "%d Unknown command", __LINE__); |
| break; |
| } |
| |
| /* Fill in response to SWd, fill ID LAST */ |
| tui_dev_devel("return 0x%08x to cmd 0x%08x", ret, command_id); |
| |
| /* TODO: fill data fields of pDci->nwdRsp */ |
| dci->nwd_rsp.return_code = ret; |
| dci->nwd_rsp.id = RSP_ID(command_id); |
| |
| /* Acknowledge command */ |
| dci->cmd_nwd.id = CMD_TUI_SW_NONE; |
| |
| /* Notify SWd */ |
| tui_dev_devel("DCI RSP NOTIFY CORE"); |
| ret = mc_notify(&dr_session_handle); |
| if (ret != MC_DRV_OK) |
| tui_dev_err(ret, "%d mc_notify() failed", __LINE__); |
| } |
| |
| /* ------------------------------------------------------------- */ |
| static void tlc_close_driver(void) |
| { |
| enum mc_result ret; |
| |
| /* Close session with the Driver */ |
| ret = mc_close_session(&dr_session_handle); |
| if (ret != MC_DRV_OK) |
| tui_dev_err(ret, "%d mc_close_session() failed", __LINE__); |
| } |
| |
| /* ------------------------------------------------------------- */ |
| static void tlc_close(void) |
| { |
| enum mc_result ret; |
| |
| tui_dev_devel("Closing driver session"); |
| tlc_close_driver(); |
| |
| tui_dev_devel("Closing TEE"); |
| /* Close the tbase device */ |
| ret = mc_close_device(DEVICE_ID); |
| if (ret != MC_DRV_OK) |
| tui_dev_err(ret, "%d mc_close_device() failed", __LINE__); |
| } |
| |
| void reset_global_command_id(void) |
| { |
| g_user_cmd.id = TLC_TUI_CMD_NONE; |
| } |
| |
| /* ------------------------------------------------------------- */ |
| |
| bool tlc_notify_event(u32 event_type) |
| { |
| bool ret = false; |
| enum mc_result result; |
| |
| if (!dci) { |
| tui_dev_err(-1, "DCI has not been set up properly - exiting"); |
| return false; |
| } |
| |
| /* Prepare notification message in DCI */ |
| tui_dev_devel("event_type = %d", event_type); |
| dci->nwd_notif = event_type; |
| |
| /* Signal the Driver */ |
| tui_dev_devel("DCI EVENT NOTIFY CORE"); |
| result = mc_notify(&dr_session_handle); |
| if (result != MC_DRV_OK) { |
| tui_dev_err(result, "%d mc_notify() failed", __LINE__); |
| ret = false; |
| } else { |
| ret = true; |
| } |
| |
| return ret; |
| } |
| |
| /* ------------------------------------------------------------- */ |
| /** |
| */ |
| static int main_thread(void *uarg) |
| { |
| tui_dev_devel("TlcTui start!"); |
| |
| /* Open session on the driver */ |
| if (!tlc_open()) |
| return 1; |
| |
| /* TlcTui main thread loop */ |
| for (;;) { |
| /* Wait for a command from the DrTui on DCI */ |
| tlc_wait_cmd_from_driver(); |
| /* Something has been received, process it. */ |
| tlc_process_cmd(); |
| } |
| |
| /* |
| * Close tlc. Note that this frees the DCI pointer. |
| * Do not use this pointer after tlc_close(). |
| */ |
| tlc_close(); |
| |
| return 0; |
| } |
| |
| static int start_thread_if_needed(void) |
| { |
| int rc = 0; |
| |
| /* |
| * Create the TlcTui Main thread and start secure driver (only 1st time) |
| */ |
| mutex_lock(&thread_mutex); |
| if (thread_id) |
| /* Already started */ |
| goto end; |
| |
| thread_id = kthread_run(main_thread, NULL, "tee_tui"); |
| if (IS_ERR_OR_NULL(thread_id)) { |
| rc = PTR_ERR(thread_id); |
| tui_dev_err(rc, "Unable to start Trusted UI main thread"); |
| thread_id = NULL; |
| } |
| |
| end: |
| mutex_unlock(&thread_mutex); |
| return rc; |
| } |
| |
| int tlc_wait_cmd(struct tlc_tui_command_t *cmd_id) |
| { |
| int ret = start_thread_if_needed(); |
| |
| if (ret) |
| return ret; |
| |
| /* Wait for signal from DCI handler */ |
| /* In case of an interrupted sys call, return with -EINTR */ |
| if (wait_for_completion_interruptible(&dci_comp)) { |
| ret = -ERESTARTSYS; |
| tui_dev_err(ret, "interrupted by system"); |
| return ret; |
| } |
| reinit_completion(&dci_comp); |
| |
| *cmd_id = g_user_cmd; |
| return 0; |
| } |
| |
| int tlc_init_driver(void) |
| { |
| return start_thread_if_needed(); |
| } |
| |
| int tlc_ack_cmd(struct tlc_tui_response_t *rsp) |
| { |
| g_user_rsp = *rsp; |
| |
| if (g_user_rsp.id == TLC_TUI_CMD_ALLOC_FB) |
| hal_tui_post_start(&g_user_rsp); |
| |
| /* Send signal to DCI */ |
| complete(&io_comp); |
| |
| return 0; |
| } |
| |
| /* Use to set the struct tlc_tui_ioctl_buffer_info using |
| * the tui_alloc_data_t field in the dci, received from DrTui. |
| */ |
| void set_buffer_info(struct tui_alloc_data_t alloc_data) |
| { |
| tui_dev_devel("%d", __LINE__); |
| |
| buff_info.num_of_buff = alloc_data.num_of_buff; |
| buff_info.size = alloc_data.alloc_size; |
| buff_info.width = alloc_data.screen_width; |
| buff_info.height = alloc_data.screen_height; |
| buff_info.stride = alloc_data.screen_stride; |
| buff_info.bits_per_pixel = alloc_data.bits_per_pixel; |
| } |
| |
| /* Use to get the tlc_tui_ioctl_buffer_info. |
| */ |
| void get_buffer_info(struct tlc_tui_ioctl_buffer_info *buffer_info) |
| { |
| tui_dev_devel("%d", __LINE__); |
| |
| buffer_info->num_of_buff = buff_info.num_of_buff; |
| buffer_info->size = buff_info.size; |
| buffer_info->width = buff_info.width; |
| buffer_info->height = buff_info.height; |
| buffer_info->stride = buff_info.stride; |
| buffer_info->bits_per_pixel = buff_info.bits_per_pixel; |
| } |
| |
| /** @} */ |