/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of The Linux Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define pr_fmt(fmt) "QSEECOM: %s: " fmt, __func__ #include #include #include #include #include "qseecom_lk_api.h" #include #include #include #include #include #include #include #include #define QSEOS_VERSION_14 0x14 #define QSEEE_VERSION_00 0x400000 #define QSEE_VERSION_20 0x800000 #define QSEOS_CHECK_VERSION_CMD 0x00001803 #define MAX_SCM_ARGS 10 #define N_EXT_SCM_ARGS 7 #define FIRST_EXT_ARG_IDX 3 #define N_REGISTER_ARGS (MAX_SCM_ARGS - N_EXT_SCM_ARGS + 1) #define QSEE_LOG_BUF_SIZE (4096) #define GENERIC_ERROR -1 #define LISTENER_ALREADY_PRESENT_ERROR -2 #define TZ_CALL 6 enum qseecom_client_handle_type { QSEECOM_CLIENT_APP = 1, QSEECOM_LISTENER_SERVICE, QSEECOM_SECURE_SERVICE, QSEECOM_GENERIC, QSEECOM_UNAVAILABLE_CLIENT_APP, }; struct qseecom_registered_listener_list { struct list_node node; struct qseecom_register_listener_req svc; ListenerCallback CallbackFn; }; struct qseecom_registered_app_list { struct list_node node; uint32_t app_id; uint32_t ref_cnt; char app_name[MAX_APP_NAME_SIZE]; int handle; }; struct qseecom_control { struct list_node registered_listener_list_head; mutex_t registered_listener_list_lock; struct list_node registered_app_list_head; mutex_t registered_app_list_lock; uint32_t qseos_version; uint32_t qsee_version; int handle; bool commonlib_loaded; mutex_t global_data_lock; uint32_t cmnlib_loaded; uint32_t qseecom_init_done; uint32_t qseecom_tz_init_done; }; struct qseecom_listener_handle { uint32_t id; }; static struct qseecom_reg_log_buf_ireq logbuf_req; static struct qseecom_control qseecom; static int __qseecom_process_incomplete_cmd(struct qseecom_command_scm_resp *resp, struct qseecom_client_listener_data_irsp *send_data_rsp); /* * Appsbl runs in Aarch32 when this is ported for Aarch64, * change return type for uint64_t. */ static uint32_t __qseecom_uvirt_to_kphys(uint64_t virt) { dprintf(SPEW, "%s called\n", __func__); return (uint32_t)platform_get_virt_to_phys_mapping((addr_t)virt); } static int _disp_log_stats(struct tzdbg_log_t *log, uint32_t log_len, uint32_t startOffset, uint32_t endOffset) { uint32_t MaxBufSize = 0; uint32_t LogBufSize = 0; uint32_t LogBufFirstHalf = 0; uint32_t len = 0; char *pCurPos, *pPrintPos = NULL; void *pLogBuf = NULL; int ret = GENERIC_ERROR; MaxBufSize = QSEE_LOG_BUF_SIZE - sizeof(struct tzdbg_log_pos_t); dprintf(SPEW, "%s called\n", __func__); if (startOffset < endOffset) { LogBufSize = endOffset - startOffset; pLogBuf = malloc(LogBufSize); if (NULL == pLogBuf) { ret = GENERIC_ERROR; dprintf(CRITICAL, "Failed to alloc buffer to print TZ Log:%u\n", LogBufSize); goto err; } memset(pLogBuf, 0, LogBufSize); memscpy(pLogBuf, LogBufSize, (char *)((uint32_t)log->log_buf + startOffset), LogBufSize); } else if ( endOffset < startOffset) { LogBufSize = MaxBufSize - (startOffset - endOffset); LogBufFirstHalf = MaxBufSize - startOffset; pLogBuf = malloc(LogBufSize); if (NULL == pLogBuf) { ret = GENERIC_ERROR; dprintf(CRITICAL, "Failed to alloc buffer to print TZ Log:%u\n", LogBufSize); goto err; } memset(pLogBuf, 0, LogBufSize); memscpy(pLogBuf, LogBufSize, (char *)((uint32_t)log->log_buf + startOffset), LogBufFirstHalf); memscpy((char *)((uint32_t)pLogBuf+ LogBufFirstHalf), (LogBufSize - LogBufFirstHalf), log->log_buf, endOffset); } else //endOffset == startOffset { ret = 0; goto err; } /* * Read from ring buff while there is data and space in return buff */ pCurPos = pLogBuf; pPrintPos = pCurPos; while (len < LogBufSize) { //QSEE separate each line by "\r \n" if ((*pCurPos == '\r')&&(*(pCurPos+1) == '\n')) { //update the line to dump *pCurPos = '\0'; len++; pCurPos++; *pCurPos = '\0'; len++; pCurPos++; dprintf(ALWAYS, "%s\n", pPrintPos); pPrintPos = pCurPos; continue; } len++; pCurPos++; } ret = 0; free(pLogBuf); err: return ret; } static int allocate_extra_arg_buffer(uint32_t fn_id, struct scm_desc *desc) { int i; int ret = GENERIC_ERROR; scmcall_arg arg = {0}; scmcall_ret ret_arg = {0}; int arglen = 0; if (!desc) { dprintf(CRITICAL, "%s: Invalid input\n", __func__); return GENERIC_ERROR; } dprintf(SPEW, "%s called\n", __func__); arglen = desc->arginfo & 0xf; dprintf(SPEW, "%s:fn_id:%u, desc->arginfo:%u desc->args[0]:%u desc->args[1]:%u desc->args[2]:%u desc->args[3]:%u desc->args[4]:%u\n", __func__, fn_id, desc->arginfo, desc->args[0], desc->args[1], desc->args[2], desc->args[3], desc->args[4]); arg.x0 = fn_id; arg.x1 = desc->arginfo; arg.x2 = desc->args[0]; arg.x3 = desc->args[1]; arg.x4 = desc->args[2]; if (arglen > FIRST_EXT_ARG_IDX) { for (i = 0; i < (arglen - FIRST_EXT_ARG_IDX); i++) { arg.x5[i] = desc->args[i + FIRST_EXT_ARG_IDX]; } } ret = scm_call2(&arg, &ret_arg); desc->ret[0] = ret_arg.x1; desc->ret[1] = ret_arg.x2; desc->ret[2] = ret_arg.x3; dprintf(SPEW, "%s:ret:%d, desc->ret[0]]:%u desc->ret[1]:%u desc->ret[2]:%u\n", __func__, ret, desc->ret[0], desc->ret[1], desc->ret[2]); return ret; } static int qseecom_scm_call2(uint32_t svc_id, uint32_t tz_cmd_id, const void *req_buf, void *resp_buf) { int ret = 0; uint32_t smc_id = 0; uint32_t qseos_cmd_id = 0; struct scm_desc desc = {0}; struct qseecom_command_scm_resp *scm_resp = NULL; if (!req_buf || !resp_buf) { dprintf(CRITICAL, "Invalid buffer pointer\n"); return GENERIC_ERROR; } dprintf(SPEW, "%s called\n", __func__); qseos_cmd_id = *(uint32_t *)req_buf; scm_resp = (struct qseecom_command_scm_resp *)resp_buf; switch (svc_id) { case TZ_CALL: { if (tz_cmd_id == 1) { smc_id = TZ_INFO_IS_SVC_AVAILABLE_ID; desc.arginfo = TZ_INFO_IS_SVC_AVAILABLE_ID_PARAM_ID; desc.args[0] = TZ_INFO_GET_FEATURE_VERSION_ID; } else if (tz_cmd_id == 3) { smc_id = TZ_INFO_GET_FEATURE_VERSION_ID; desc.arginfo = TZ_INFO_GET_FEATURE_VERSION_ID_PARAM_ID; desc.args[0] = *(uint32_t *)req_buf; } ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case SCM_SVC_TZSCHEDULER: { switch (qseos_cmd_id) { case QSEE_APP_START_COMMAND: { struct qseecom_load_app_ireq *req; req = (struct qseecom_load_app_ireq *)req_buf; smc_id = TZ_OS_APP_START_ID; desc.arginfo = TZ_OS_APP_START_ID_PARAM_ID; desc.args[0] = req->mdt_len; desc.args[1] = req->img_len; desc.args[2] = req->phy_addr; dprintf(SPEW, "args[0]:%u args[1]:%u args[2]:%u\n", desc.args[0], desc.args[1], desc.args[2]); dprintf(SPEW, "mdt_len:%u img_len:%u phy_addr:%u\n", req->mdt_len, req->img_len, req->phy_addr); ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case QSEE_APP_SHUTDOWN_COMMAND: { struct qseecom_unload_app_ireq *req; req = (struct qseecom_unload_app_ireq *)req_buf; smc_id = TZ_OS_APP_SHUTDOWN_ID; desc.arginfo = TZ_OS_APP_SHUTDOWN_ID_PARAM_ID; desc.args[0] = req->app_id; ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case QSEE_APP_REGION_NOTIFICATION: { struct qsee_apps_region_info_ireq *req; req = (struct qsee_apps_region_info_ireq *)req_buf; smc_id = TZ_OS_APP_REGION_NOTIFICATION_ID; desc.arginfo = TZ_OS_APP_REGION_NOTIFICATION_ID_PARAM_ID; desc.args[0] = req->addr; desc.args[1] = req->size; ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case QSEE_LOAD_SERV_IMAGE_COMMAND: { struct qseecom_load_app_ireq *req; req = (struct qseecom_load_app_ireq *)req_buf; smc_id = TZ_OS_LOAD_SERVICES_IMAGE_ID; desc.arginfo = TZ_OS_LOAD_SERVICES_IMAGE_ID_PARAM_ID; desc.args[0] = req->mdt_len; desc.args[1] = req->img_len; desc.args[2] = req->phy_addr; dprintf(SPEW, "QSEE_LOAD_SERV_IMAGE_COMMAND mdt_len:%u img_len:%u phy_addr:%u\n", req->mdt_len, req->img_len, req->phy_addr); ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case QSEE_UNLOAD_SERV_IMAGE_COMMAND: { smc_id = TZ_OS_UNLOAD_SERVICES_IMAGE_ID; desc.arginfo = TZ_OS_UNLOAD_SERVICES_IMAGE_ID_PARAM_ID; ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case QSEE_REGISTER_LISTENER: { struct qseecom_register_listener_ireq *req; req = (struct qseecom_register_listener_ireq *)req_buf; smc_id = TZ_OS_REGISTER_LISTENER_ID; desc.arginfo = TZ_OS_REGISTER_LISTENER_ID_PARAM_ID; desc.args[0] = req->listener_id; desc.args[1] = req->sb_ptr; desc.args[2] = req->sb_len; ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case QSEE_DEREGISTER_LISTENER: { struct qseecom_unregister_listener_ireq *req; req = (struct qseecom_unregister_listener_ireq *) req_buf; smc_id = TZ_OS_DEREGISTER_LISTENER_ID; desc.arginfo = TZ_OS_DEREGISTER_LISTENER_ID_PARAM_ID; desc.args[0] = req->listener_id; ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case QSEE_LISTENER_DATA_RSP_COMMAND: { struct qseecom_client_listener_data_irsp *req; req = (struct qseecom_client_listener_data_irsp *) req_buf; smc_id = TZ_OS_LISTENER_RESPONSE_HANDLER_ID; desc.arginfo = TZ_OS_LISTENER_RESPONSE_HANDLER_ID_PARAM_ID; desc.args[0] = req->listener_id; desc.args[1] = req->status; ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case QSEE_CLIENT_SEND_DATA_COMMAND: { struct qseecom_client_send_data_ireq *req; req = (struct qseecom_client_send_data_ireq *)req_buf; smc_id = TZ_APP_QSAPP_SEND_DATA_ID; desc.arginfo = TZ_APP_QSAPP_SEND_DATA_ID_PARAM_ID; desc.args[0] = req->app_id; desc.args[1] = req->req_ptr; desc.args[2] = req->req_len; desc.args[3] = req->rsp_ptr; desc.args[4] = req->rsp_len; ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case QSEE_RPMB_PROVISION_KEY_COMMAND: { struct qseecom_client_send_service_ireq *req; req = (struct qseecom_client_send_service_ireq *) req_buf; smc_id = TZ_OS_RPMB_PROVISION_KEY_ID; desc.arginfo = TZ_OS_RPMB_PROVISION_KEY_ID_PARAM_ID; desc.args[0] = req->key_type; ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case QSEE_RPMB_ERASE_COMMAND: { smc_id = TZ_OS_RPMB_ERASE_ID; desc.arginfo = TZ_OS_RPMB_ERASE_ID_PARAM_ID; ret = allocate_extra_arg_buffer(smc_id, &desc); break; } case QSEE_REGISTER_LOG_BUF_COMMAND: { struct qseecom_reg_log_buf_ireq *req; req = (struct qseecom_reg_log_buf_ireq *)req_buf; smc_id = TZ_OS_REGISTER_LOG_BUFFER_ID; desc.arginfo = TZ_OS_REGISTER_LOG_BUFFER_ID_PARAM_ID; desc.args[0] = req->phy_addr; desc.args[1] = req->len; ret = allocate_extra_arg_buffer(smc_id, &desc); break; } default: { dprintf(CRITICAL, "qseos_cmd_id 0x%d is not supported by armv8 scm_call2.\n", qseos_cmd_id); ret = GENERIC_ERROR; break; } } /*end of switch (qsee_cmd_id) */ break; } /*end of case SCM_SVC_TZSCHEDULER*/ default: { dprintf(CRITICAL, "svc_id 0x%x is not supported by armv8 scm_call2.\n", svc_id); ret = GENERIC_ERROR; break; } } /*end of switch svc_id */ scm_resp->result = desc.ret[0]; scm_resp->resp_type = desc.ret[1]; scm_resp->data = desc.ret[2]; dprintf(SPEW, "svc_id = 0x%x, tz_cmd_id = 0x%x, qseos_cmd_id = 0x%x, smc_id = 0x%x, param_id = 0x%x\n", svc_id, tz_cmd_id, qseos_cmd_id, smc_id, desc.arginfo); dprintf(SPEW, "scm_resp->result = 0x%x, scm_resp->resp_type = 0x%x, scm_resp->data = 0x%x\n", scm_resp->result, scm_resp->resp_type, scm_resp->data); return ret; } static int qseecom_scm_call(uint32_t svc_id, uint32_t tz_cmd_id, void *cmd_buf, size_t cmd_len, void *resp_buf, size_t resp_len) { void *req = NULL; struct qseecom_command_scm_resp *resp = NULL; struct qseecom_client_listener_data_irsp send_data_rsp = {0}; int ret = GENERIC_ERROR; uint32_t qseos_cmd_id = 0; if ((!cmd_buf) || (!resp_buf)) return GENERIC_ERROR; dprintf(SPEW, "%s called\n", __func__); mutex_acquire(&qseecom.registered_app_list_lock); req = cmd_buf; qseos_cmd_id = *(uint32_t *)req; resp = (struct qseecom_command_scm_resp *) resp_buf; do { if (!is_scm_armv8_support()) { ret = scm_call(svc_id, tz_cmd_id, req, cmd_len, resp_buf, resp_len); } else { ret = qseecom_scm_call2(svc_id, tz_cmd_id, req, resp); } if (ret) { dprintf(CRITICAL, "ERROR: scm_call to load failed : ret %d\n", ret); ret = GENERIC_ERROR; goto err; } if (svc_id == TZ_CALL) { goto err; } switch (resp->result) { case QSEOS_RESULT_SUCCESS: if(((resp->resp_type != QSEOS_APP_ID) || (resp->data <= 0)) && ((qseos_cmd_id == QSEE_CLIENT_SEND_DATA_COMMAND) || (qseos_cmd_id == QSEE_LISTENER_DATA_RSP_COMMAND))) { dprintf(CRITICAL, "ERROR: Resp type %d or Resp Data %d incorrect\n", resp->resp_type, resp->data); ret = GENERIC_ERROR; goto err; } goto err; case QSEOS_RESULT_FAILURE: dprintf(CRITICAL, "scm call failed w/response result%d\n", resp->result); ret = GENERIC_ERROR; goto err; case QSEOS_RESULT_INCOMPLETE: if(resp->resp_type != QSEOS_LISTENER_ID) { ret = GENERIC_ERROR; dprintf(CRITICAL, "Listener service incorrect resp->result:%d resp->resp_type:%d\n", resp->result, resp->resp_type); goto err; } __qseecom_process_incomplete_cmd(resp, &send_data_rsp); req = (void *)&send_data_rsp; qseos_cmd_id = QSEE_LISTENER_DATA_RSP_COMMAND; break; default: dprintf(CRITICAL, "scm call return unknown response %d\n", resp->result); ret = GENERIC_ERROR; goto err; } } while(true); err: mutex_release(&qseecom.registered_app_list_lock); return ret; } static int __qseecom_process_incomplete_cmd(struct qseecom_command_scm_resp *resp, struct qseecom_client_listener_data_irsp *send_data_rsp) { int ret = 0; struct qseecom_registered_listener_list *entry; if ((!resp) || (!send_data_rsp)) { return GENERIC_ERROR; } dprintf(SPEW, "%s called\n", __func__); mutex_acquire(&qseecom.global_data_lock); list_for_every_entry(&qseecom.registered_listener_list_head, entry, struct qseecom_registered_listener_list, node) { if (resp->data == entry->svc.listener_id) { arch_invalidate_cache_range((addr_t) entry->svc.virt_sb_base, entry->svc.sb_size); entry->CallbackFn(entry->svc.virt_sb_base, entry->svc.sb_size); arch_clean_invalidate_cache_range((addr_t) entry->svc.virt_sb_base, entry->svc.sb_size); break; } } send_data_rsp->qsee_cmd_id = QSEE_LISTENER_DATA_RSP_COMMAND; send_data_rsp->listener_id = entry->svc.listener_id; send_data_rsp->status = 0; mutex_release(&qseecom.global_data_lock); return ret; } static int __qseecom_load_app(const char *app_name, unsigned int *app_id) { int index = INVALID_PTN; unsigned long long ptn = 0; unsigned long long size = 0; void *buf = NULL; void *req = NULL; struct qseecom_load_app_ireq load_req = {0}; struct qseecom_command_scm_resp resp; struct tzdbg_log_t *log = NULL; uint32_t QseeLogStart = 0; uint32_t QseeLogNewStart = 0; int ret = GENERIC_ERROR; uint8_t lun = 0; if (!app_name) return GENERIC_ERROR; dprintf(SPEW, "%s called\n", __func__); index = partition_get_index(app_name); lun = partition_get_lun(index); mmc_set_lun(lun); size = partition_get_size(index); buf = memalign(PAGE_SIZE, ROUNDUP(size, PAGE_SIZE)); if (!buf) { dprintf(CRITICAL, "%s: Aloc failed for %s image\n", __func__, app_name); ret = GENERIC_ERROR; goto err; } ptn = partition_get_offset(index); if(ptn == 0) { dprintf(CRITICAL, "ERROR: No %s found\n", app_name); ret = GENERIC_ERROR; goto err; } if (mmc_read(ptn, (unsigned int *) buf, size)) { dprintf(CRITICAL, "ERROR: Cannot read %s image\n", app_name); ret = GENERIC_ERROR; goto err; } /* Currently on 8994 only 32-bit phy addr is supported * Hence downcasting is okay */ load_req.phy_addr = (uint32_t)__qseecom_uvirt_to_kphys((uint32_t) buf); load_req.qsee_cmd_id = QSEE_APP_START_COMMAND; load_req.img_len = size; load_req.mdt_len = 0; dprintf(SPEW, "phy_addr:%u img_len:%u\n", load_req.phy_addr, load_req.img_len); memscpy(&load_req.app_name, MAX_APP_NAME_SIZE, app_name, MAX_APP_NAME_SIZE); req = (void *)&load_req; log = (struct tzdbg_log_t *)logbuf_req.phy_addr; arch_invalidate_cache_range((addr_t) logbuf_req.phy_addr, logbuf_req.len); QseeLogStart = (uint32_t) log->log_pos.offset; arch_clean_invalidate_cache_range((addr_t) load_req.phy_addr, load_req.img_len); ret = qseecom_scm_call(SCM_SVC_TZSCHEDULER, 1, req, sizeof(struct qseecom_load_lib_image_ireq), &resp, sizeof(resp)); if(ret == 0) *app_id = resp.data; else *app_id = 0; arch_invalidate_cache_range((addr_t) logbuf_req.phy_addr, logbuf_req.len); QseeLogNewStart = (uint32_t) log->log_pos.offset; _disp_log_stats((struct tzdbg_log_t *) logbuf_req.phy_addr, QSEE_LOG_BUF_SIZE - sizeof(struct tzdbg_log_pos_t), QseeLogStart, QseeLogNewStart); err: if (buf) free(buf); return ret; } static int qseecom_load_commonlib_image(char * app_name) { int index = INVALID_PTN; unsigned long long ptn = 0; unsigned long long size = 0; void *buf = NULL; void *req = NULL; struct qseecom_load_app_ireq load_req = {0}; struct qseecom_command_scm_resp resp = {0}; int ret = GENERIC_ERROR; uint8_t lun = 0; dprintf(SPEW, "%s called\n", __func__); index = partition_get_index(app_name); lun = partition_get_lun(index); mmc_set_lun(lun); size = partition_get_size(index); buf = memalign(PAGE_SIZE, ROUNDUP(size, PAGE_SIZE)); if (!buf) { dprintf(CRITICAL, "%s: Aloc failed for %s image\n", __func__, app_name); ret = GENERIC_ERROR; goto err; } ptn = partition_get_offset(index); if(ptn == 0) { dprintf(CRITICAL, "ERROR: No %s found\n", app_name); ret = GENERIC_ERROR; goto err; } if (mmc_read(ptn, (unsigned int *) buf, size)) { dprintf(CRITICAL, "ERROR: Cannot read %s image\n", app_name); ret = GENERIC_ERROR; goto err; } /* Currently on 8994 only 32-bit phy addr is supported * Hence downcasting is okay */ load_req.phy_addr = (uint32_t)__qseecom_uvirt_to_kphys((uint32_t) buf); load_req.qsee_cmd_id = QSEE_LOAD_SERV_IMAGE_COMMAND; load_req.img_len = size; load_req.mdt_len = 0; memscpy(load_req.app_name, MAX_APP_NAME_SIZE, app_name, MAX_APP_NAME_SIZE); req = (void *)&load_req; arch_clean_invalidate_cache_range((addr_t) load_req.phy_addr, load_req.img_len); ret = qseecom_scm_call(SCM_SVC_TZSCHEDULER, 1, req, sizeof(struct qseecom_load_lib_image_ireq), &resp, sizeof(resp)); if(ret == 0) ret = resp.data; err: if (buf) free(buf); return ret; } static int qseecom_unload_commonlib_image(void) { int ret = GENERIC_ERROR; struct qseecom_unload_lib_image_ireq unload_req = {0}; struct qseecom_command_scm_resp resp; dprintf(SPEW, "%s called\n", __func__); /* Populate the remaining parameters */ unload_req.qsee_cmd_id = QSEE_UNLOAD_SERV_IMAGE_COMMAND; /* SCM_CALL to load the image */ ret = qseecom_scm_call(SCM_SVC_TZSCHEDULER, 1, &unload_req, sizeof(struct qseecom_unload_lib_image_ireq), &resp, sizeof(resp)); return ret; } /* * This function is called with the global * data mutex acquired. */ static struct qseecom_registered_app_list * __qseecom_add_app_entry(char *app_name, uint32_t app_id) { struct qseecom_registered_app_list *entry = NULL; int32_t ret = GENERIC_ERROR; if ((!app_name) || (app_id == 0)) { dprintf(CRITICAL, "%s: Invalid Input\n", __func__); return NULL; } dprintf(SPEW, "%s called\n", __func__); entry = malloc(sizeof(*entry)); if (!entry) { dprintf(CRITICAL, "malloc for app entry failed\n"); ret = GENERIC_ERROR; goto err; } entry->app_id = app_id; entry->ref_cnt = 1; strlcpy(entry->app_name, app_name, MAX_APP_NAME_SIZE); dprintf(SPEW, "%s: Adding app:%s app_id:%u to list\n", __func__, entry->app_name, entry->app_id); list_add_tail(&qseecom.registered_app_list_head, &entry->node); ret = 0; err: if (entry && (ret < 0)) { free(entry); return NULL; } return entry; } /* * This function is called with the global * data mutex acquired. */ static int __qseecom_remove_app_entry(struct qseecom_registered_app_list *entry) { if (!entry) { dprintf(CRITICAL, "%s: Invalid Input\n", __func__); return GENERIC_ERROR; } dprintf(SPEW, "%s called\n", __func__); list_delete(&entry->node); free(entry); return 0; } /* * This function is called with the global * data mutex acquired. */ struct qseecom_registered_listener_list * __qseecom_check_listener_exists(uint32_t listener_id) { struct qseecom_registered_listener_list *entry = NULL; bool listener_present = false; if (!listener_id) { dprintf(CRITICAL, "%s: Invalid Input\n", __func__); return NULL; } dprintf(SPEW, "%s called\n", __func__); list_for_every_entry(&qseecom.registered_listener_list_head, entry, struct qseecom_registered_listener_list, node) { if (entry->svc.listener_id == listener_id) { listener_present = true; break; } } if (listener_present) return entry; else return NULL; } /* * This function is called with the global * data mutex acquired. */ static struct qseecom_registered_app_list *__qseecom_check_handle_exists(int handle) { struct qseecom_registered_app_list *entry; bool app_present = false; if (handle <= 0) { dprintf(CRITICAL, "%s: Invalid Input\n", __func__); return NULL; } dprintf(SPEW, "%s called\n", __func__); list_for_every_entry(&qseecom.registered_app_list_head, entry, struct qseecom_registered_app_list, node) { if (entry->handle == handle) { app_present = true; break; } } if (app_present == true) return entry; else return NULL; } static struct qseecom_registered_app_list * __qseecom_check_app_exists(char *app_name) { struct qseecom_registered_app_list *entry = NULL; dprintf(SPEW, "%s called\n", __func__); list_for_every_entry(&qseecom.registered_app_list_head, entry, struct qseecom_registered_app_list, node) { if (!strncmp(app_name, entry->app_name, 32)) { dprintf(SPEW, "%s: app_name:%s\n", __func__, app_name); return entry; } } return NULL; } static int qseecom_unload_app(uint32_t app_id) { int ret = 0; struct qseecom_command_scm_resp resp; struct qseecom_unload_app_ireq req; dprintf(SPEW, "%s called\n", __func__); /* Populate the structure for sending scm call to load image */ req.qsee_cmd_id = QSEE_APP_SHUTDOWN_COMMAND; req.app_id = app_id; /* SCM_CALL to unload the app */ ret = qseecom_scm_call(SCM_SVC_TZSCHEDULER, 1, &req, sizeof(struct qseecom_unload_app_ireq), &resp, sizeof(resp)); return ret; } static int __qseecom_send_cmd(uint32_t app_id, struct qseecom_send_cmd_req *req) { int ret = 0; struct qseecom_client_send_data_ireq send_data_req; struct qseecom_command_scm_resp resp; void *buf = NULL; void *rsp_buf_temp = NULL; uint32_t size = 0; if (req->cmd_req_buf == NULL || req->resp_buf == NULL) { dprintf(CRITICAL, "%s: cmd buffer or response buffer is null\n", __func__); return GENERIC_ERROR; } dprintf(SPEW, "%s called\n", __func__); if (req->cmd_req_len > (UINT_MAX - req->resp_len)) { dprintf(CRITICAL, "%s:Integer overflow\n", __func__); dprintf(CRITICAL, "req->cmd_req_len: %u\n", req->cmd_req_len); dprintf(CRITICAL, "req->resp_len: %u\n", req->resp_len); return GENERIC_ERROR; } if ((req->cmd_req_len + req->resp_len) > (RPMB_SND_RCV_BUF_SZ * 1024 * 1024)) { dprintf(CRITICAL, "%s:Cmd + Rsp len greater than TA buf\n", __func__); dprintf(CRITICAL, "req->cmd_req_len: %u\n", req->cmd_req_len); dprintf(CRITICAL, "req->resp_len: %u\n", req->resp_len); return GENERIC_ERROR; } /* The req rsp buffer will be xPU protected by TZ during a TZ APP call * This will still be protected during a listener call and there is a * possibility of prefetching happening, which will cause xPU violation. * Hence using (device memory with xN set) to prevent I or D prefetching. * This is a contiguous region of 1MB used only for this, hence will not * free this. */ buf = (void *)RPMB_SND_RCV_BUF; if (!buf) { dprintf(CRITICAL, "%s: Aloc failed for app_id:%d of size:%d\n", __func__, app_id, size); return GENERIC_ERROR; } send_data_req.qsee_cmd_id = QSEE_CLIENT_SEND_DATA_COMMAND; send_data_req.app_id = app_id; /* Currently on 8994 only 32-bit phy addr is supported * Hence downcasting is okay */ send_data_req.req_ptr = (uint32_t)__qseecom_uvirt_to_kphys((uint32_t) buf); send_data_req.req_len = req->cmd_req_len; size = ROUNDUP(req->cmd_req_len, PAGE_SIZE); rsp_buf_temp = (uint8_t *)buf + size; send_data_req.rsp_ptr = (uint32_t)__qseecom_uvirt_to_kphys((uint32_t)rsp_buf_temp); send_data_req.rsp_len = req->resp_len; memscpy(buf, (RPMB_SND_RCV_BUF_SZ * 1024 * 1024), req->cmd_req_buf, req->cmd_req_len); memscpy(rsp_buf_temp, req->resp_len, req->resp_buf, req->resp_len); ret = qseecom_scm_call(SCM_SVC_TZSCHEDULER, 1, (void *)&send_data_req, sizeof(send_data_req), (void *)&resp, sizeof(resp)); memscpy(req->cmd_req_buf, req->cmd_req_len, (void *)buf, send_data_req.req_len); memscpy(req->resp_buf, req->resp_len, (void *)rsp_buf_temp, send_data_req.rsp_len); return ret; } /** * Start a Secure App * * @param char* app_name * App name of the Secure App to be started * * @return int * Success: handle to be used for all calls to * Secure app. Always greater than zero. * Failure: Error code (negative only). */ int qseecom_start_app(char *app_name) { int32_t ret = GENERIC_ERROR; int handle = 0; struct qseecom_registered_app_list *entry = NULL; unsigned int app_id = 0; if (!app_name) { dprintf(CRITICAL, "%s: Input error\n", __func__); goto err; } dprintf(SPEW, "%s called\n", __func__); mutex_acquire(&qseecom.global_data_lock); if ((!qseecom.qseecom_init_done) || (!qseecom.qseecom_tz_init_done)){ dprintf(CRITICAL, "%s qseecom_init not done\n", __func__); mutex_release(&qseecom.global_data_lock); return ret; } /* Load commonlib image*/ if (!qseecom.cmnlib_loaded) { ret = qseecom_load_commonlib_image("cmnlib"); if (ret) { mutex_release(&qseecom.global_data_lock); dprintf(CRITICAL, "%s qseecom_load_commonlib_image failed with status:%d\n", __func__, ret); goto err; } dprintf(DEBUG, "Loading cmnlib done\n"); #if ENABLE_CMNLIB64_LOADING ret = qseecom_load_commonlib_image("cmnlib64"); if (ret) { dprintf(CRITICAL, "%s qseecom_load_commonlib_image failed with status:%d\n", __func__, ret); goto err; } dprintf(DEBUG, "Loading cmnlib64 done\n"); #endif qseecom.cmnlib_loaded = 1; } /* Check if App already exits, if exits increase ref_cnt * and return handle, else load the app from partition, * call into TZ to load it, add to list and then return * handle. */ entry = __qseecom_check_app_exists(app_name); if (!entry) { mutex_release(&qseecom.global_data_lock); /* load the app and get the app_id */ dprintf(INFO, "%s: Loading app %s for the first time'\n", __func__, app_name); ret = __qseecom_load_app(app_name, &app_id); if ((ret < 0) || (app_id ==0)) { dprintf(CRITICAL, "%s: __qseecom_load_app failed with err:%d for app:%s\n", __func__, ret, app_name); ret = GENERIC_ERROR; goto err; } mutex_acquire(&qseecom.global_data_lock); entry = __qseecom_add_app_entry(app_name, app_id); if (!entry) { dprintf(CRITICAL, "%s: __qseecom_add_app_entry failed\n", __func__); ret = GENERIC_ERROR; mutex_release(&qseecom.global_data_lock); goto err; } qseecom.handle++; entry->handle = qseecom.handle; handle = entry->handle; mutex_release(&qseecom.global_data_lock); } else { entry->ref_cnt++; handle = entry->handle; mutex_release(&qseecom.global_data_lock); } return handle; err: return ret; } /** * Shutdown a Secure App * * @param int handle * Handle of the Secure App to be shutdown * * @return int * Status: * 0 - Success * Negative value indicates failure. */ int qseecom_shutdown_app(int handle) { int ret = GENERIC_ERROR; int ref_cnt = 0; struct qseecom_registered_app_list *entry = NULL; struct tzdbg_log_t *log = NULL; uint32_t QseeLogStart = 0; uint32_t QseeLogNewStart = 0; if (handle <= 0) { dprintf(CRITICAL, "%s: Invalid Handle %d\n", __func__, handle); goto err; } dprintf(SPEW, "%s called\n", __func__); mutex_acquire(&qseecom.global_data_lock); if ((!qseecom.qseecom_init_done) || (!qseecom.qseecom_tz_init_done)) { dprintf(CRITICAL, "%s qseecom_init not done\n", __func__); mutex_release(&qseecom.global_data_lock); return ret; } entry = __qseecom_check_handle_exists(handle); if (!entry) { dprintf(CRITICAL, "%s: Shutdown on an app that was never loaded handle:%d\n", __func__, handle); ret = GENERIC_ERROR; mutex_release(&qseecom.global_data_lock); goto err; } /* Decrement ref_cnt by 1, if ref_cnt is 0 after * decrementing unload the app by calling into * TZ else just return. */ if(entry->ref_cnt != 0) entry->ref_cnt--; ref_cnt = entry->ref_cnt; mutex_release(&qseecom.global_data_lock); if (ref_cnt == 0) { log = (struct tzdbg_log_t *)logbuf_req.phy_addr; arch_invalidate_cache_range((addr_t) logbuf_req.phy_addr, logbuf_req.len); QseeLogStart = (uint32_t) log->log_pos.offset; ret = qseecom_unload_app(entry->app_id); arch_invalidate_cache_range((addr_t) logbuf_req.phy_addr, logbuf_req.len); QseeLogNewStart = (uint32_t) log->log_pos.offset; _disp_log_stats((struct tzdbg_log_t *) logbuf_req.phy_addr, QSEE_LOG_BUF_SIZE - sizeof(struct tzdbg_log_pos_t), QseeLogStart, QseeLogNewStart); if(ret) { dprintf(CRITICAL, "%s: qseecom_unload_app failed with err:%d for handle:%d\n", __func__, ret, handle); goto err; } mutex_acquire(&qseecom.global_data_lock); ret = __qseecom_remove_app_entry(entry); mutex_release(&qseecom.global_data_lock); if(ret) { dprintf(CRITICAL, "%s: __qseecom_remove_app_entry failed with err:%d for handle:%d\n", __func__, ret, handle); goto err; } } ret = 0; err: return ret; } /** * Send cmd to a Secure App * * @param int handle * Handle of the Secure App to send the cmd * * @param void *send_buf * Pointer to the App request buffer * * @param uint32_t sbuf_len * Size of the request buffer * * @param void *resp_buf * Pointer to the App response buffer * * @param uint32_t rbuf_len * Size of the response buffer * * @return int * Status: * 0 - Success * Negative value indicates failure. */ int qseecom_send_command(int handle, void *send_buf, uint32_t sbuf_len, void *resp_buf, uint32_t rbuf_len) { int ret = GENERIC_ERROR; uint32_t app_id = 0; struct qseecom_registered_app_list *entry = NULL; struct qseecom_send_cmd_req req = {0, 0, 0, 0}; struct tzdbg_log_t *log = NULL; uint32_t QseeLogStart = 0; uint32_t QseeLogNewStart = 0; if (handle <= 0) { dprintf(CRITICAL, "%s Handle is Invalid\n", __func__); return GENERIC_ERROR; } if((!send_buf) || (!resp_buf)) { dprintf(CRITICAL, "%s: Input Buffers invalid\n", __func__); return GENERIC_ERROR; } dprintf(SPEW, "%s called\n", __func__); mutex_acquire(&qseecom.global_data_lock); if ((!qseecom.qseecom_init_done) || (!qseecom.qseecom_tz_init_done)) { dprintf(CRITICAL, "%s qseecom_init not done\n", __func__); mutex_release(&qseecom.global_data_lock); return ret; } entry = __qseecom_check_handle_exists(handle); if (!entry) { dprintf(CRITICAL, "%s: Send cmd on an app that was never loaded handle:%d\n", __func__, handle); ret = GENERIC_ERROR; mutex_release(&qseecom.global_data_lock); goto err; } app_id = entry->app_id; mutex_release(&qseecom.global_data_lock); req.cmd_req_len = sbuf_len; req.resp_len = rbuf_len; req.cmd_req_buf = send_buf; req.resp_buf = resp_buf; log = (struct tzdbg_log_t *)logbuf_req.phy_addr; arch_invalidate_cache_range((addr_t) logbuf_req.phy_addr, logbuf_req.len); QseeLogStart = (uint32_t) log->log_pos.offset; ret = __qseecom_send_cmd(app_id, &req); if (ret) { dprintf(CRITICAL, "%s __qseecom_send_cmd failed with err:%d for handle:%d\n", __func__, ret, handle); goto err; } arch_invalidate_cache_range((addr_t) logbuf_req.phy_addr, logbuf_req.len); QseeLogNewStart = (uint32_t) log->log_pos.offset; _disp_log_stats((struct tzdbg_log_t *) logbuf_req.phy_addr, QSEE_LOG_BUF_SIZE - sizeof(struct tzdbg_log_pos_t), QseeLogStart, QseeLogNewStart); ret = 0; dprintf(SPEW, "sending cmd_req->rsp size: %u, ptr: 0x%p\n", req.resp_len, req.resp_buf); err: return ret; } /** * Registers a Listener Service with QSEE * * @param uint32_t listnr_id * Pre-defined Listener ID to be registered * * @param uint32_t sb_size * Shared buffer size required for the listener * service. * * @return int * Status: * 0 - Success * Negative value indicates failure. */ int qseecom_register_listener(struct qseecom_listener_services *listnr) { int ret = GENERIC_ERROR; struct qseecom_registered_listener_list *new_entry = NULL; struct qseecom_register_listener_ireq req; struct qseecom_command_scm_resp resp; mutex_acquire(&qseecom.global_data_lock); if (!qseecom.qseecom_init_done) { dprintf(CRITICAL, "%s qseecom_init not done\n", __func__); mutex_release(&qseecom.global_data_lock); return ret; } mutex_release(&qseecom.global_data_lock); mutex_acquire(&qseecom.registered_listener_list_lock); if ((!listnr)) { dprintf(CRITICAL, "%s Invalid Input listnr\n", __func__); return GENERIC_ERROR; } if ((!listnr->id) || (!listnr->sb_size) || (!listnr->service_cmd_handler)) { dprintf(CRITICAL, "%s Invalid Input listnr_id:%d sb_size:%d\n", __func__, listnr->id, listnr->sb_size); return GENERIC_ERROR; } dprintf(SPEW, "%s called\n", __func__); new_entry = __qseecom_check_listener_exists(listnr->id); if (new_entry) { dprintf(CRITICAL, "Service is not unique and is already registered\n"); ret = LISTENER_ALREADY_PRESENT_ERROR; goto err; } new_entry = malloc(sizeof(*new_entry)); if (!new_entry) { dprintf(CRITICAL, "%s new_entry malloc failed for size:%d\n", __func__, sizeof(*new_entry)); ret = GENERIC_ERROR; goto err; } memset(new_entry, 0, sizeof(*new_entry)); new_entry->svc.listener_id = listnr->id; new_entry->svc.sb_size = listnr->sb_size; new_entry->CallbackFn = listnr->service_cmd_handler; new_entry->svc.virt_sb_base = memalign(PAGE_SIZE, ROUNDUP(listnr->sb_size, PAGE_SIZE)); if (!new_entry->svc.virt_sb_base) { dprintf(CRITICAL, "%s virt_sb_base malloc failed for size:%d\n", __func__, listnr->sb_size); ret = GENERIC_ERROR; goto err; } memset(new_entry->svc.virt_sb_base, 0, ROUNDUP(listnr->sb_size, PAGE_SIZE)); arch_clean_invalidate_cache_range((addr_t) new_entry->svc.virt_sb_base, ROUNDUP(listnr->sb_size, PAGE_SIZE)); req.qsee_cmd_id = QSEE_REGISTER_LISTENER; req.listener_id = new_entry->svc.listener_id; req.sb_len = new_entry->svc.sb_size; /* convert to 32bit addr for tz */ req.sb_ptr = (uint32_t)__qseecom_uvirt_to_kphys((uint32_t) new_entry->svc.virt_sb_base); resp.result = QSEOS_RESULT_INCOMPLETE; ret = qseecom_scm_call(SCM_SVC_TZSCHEDULER, 1, &req, sizeof(req), &resp, sizeof(resp)); if (ret) { dprintf(CRITICAL, "qseecom_scm_call failed with err: %d\n", ret); ret = GENERIC_ERROR; goto err; } /* Add entry to Listener list */ list_add_tail(&qseecom.registered_listener_list_head, &new_entry->node); err: if ((ret) && (ret != LISTENER_ALREADY_PRESENT_ERROR)) { if ((new_entry) && (new_entry->svc.virt_sb_base)) free(new_entry->svc.virt_sb_base); if (new_entry) free(new_entry); } mutex_release(&qseecom.registered_listener_list_lock); return ret; } /** * De-Registers a Listener Service with QSEE * * @param uint32_t listnr_id * Pre-defined Listener ID to be de-registered * * @return int * Status: * 0 - Success * Negative value indicates failure. */ int qseecom_deregister_listener(uint32_t listnr_id) { int ret = GENERIC_ERROR; struct qseecom_registered_listener_list *new_entry = NULL; struct qseecom_unregister_listener_ireq req; struct qseecom_command_scm_resp resp; mutex_acquire(&qseecom.global_data_lock); if (!qseecom.qseecom_init_done) { dprintf(CRITICAL, "%s qseecom_init not done\n", __func__); mutex_release(&qseecom.global_data_lock); return ret; } mutex_release(&qseecom.global_data_lock); mutex_acquire(&qseecom.registered_listener_list_lock); dprintf(SPEW, "%s called\n", __func__); new_entry = __qseecom_check_listener_exists(listnr_id); if (!new_entry) { dprintf(CRITICAL, "Service not present\n"); ret = GENERIC_ERROR; goto err; } req.qsee_cmd_id = QSEE_DEREGISTER_LISTENER; req.listener_id = listnr_id; resp.result = QSEOS_RESULT_INCOMPLETE; ret = qseecom_scm_call(SCM_SVC_TZSCHEDULER, 1, &req, sizeof(req), &resp, sizeof(resp)); if (ret) { dprintf(CRITICAL, "scm_call() failed with err: %d (lstnr id=%d)\n", ret, req.listener_id); ret = GENERIC_ERROR; goto err; } list_delete(&new_entry->node); err: if (ret == 0) { if (new_entry) free(new_entry); } mutex_release(&qseecom.registered_listener_list_lock); return ret; } int qseecom_tz_init() { struct qsee_apps_region_info_ireq req; struct qseecom_command_scm_resp resp; int rc = GENERIC_ERROR; /* register log buffer scm request */ void *buf = NULL; /* Register app region with TZ */ req.qsee_cmd_id = QSEE_APP_REGION_NOTIFICATION; req.addr = APP_REGION_ADDR; req.size = APP_REGION_SIZE; dprintf(ALWAYS, "secure app region addr=0x%x size=0x%x", req.addr, req.size); rc = qseecom_scm_call(SCM_SVC_TZSCHEDULER, 1, &req, sizeof(req), &resp, sizeof(resp)); dprintf(ALWAYS, "TZ App region notif returned with status:%d addr:%x size:%d\n", rc, req.addr, req.size); if (rc) goto err; buf = memalign(PAGE_SIZE, ROUNDUP(QSEE_LOG_BUF_SIZE, PAGE_SIZE)); if (!buf) { rc = GENERIC_ERROR; goto err; } memset(buf, 0, ROUNDUP(QSEE_LOG_BUF_SIZE, PAGE_SIZE)); /* Make sure the buffer given to TZ is flushed */ arch_clean_invalidate_cache_range((addr_t) buf, QSEE_LOG_BUF_SIZE); logbuf_req.qsee_cmd_id = QSEE_REGISTER_LOG_BUF_COMMAND; logbuf_req.phy_addr = (uint32_t)__qseecom_uvirt_to_kphys((uint32_t) buf); logbuf_req.len = QSEE_LOG_BUF_SIZE; rc = qseecom_scm_call(SCM_SVC_TZSCHEDULER, 1, &logbuf_req, sizeof(logbuf_req), &resp, sizeof(resp)); dprintf(ALWAYS, "TZ App log region register returned with status:%d addr:%x size:%d\n", rc, logbuf_req.phy_addr, logbuf_req.len); if (rc) goto err; err: if (!rc) { qseecom.qseecom_tz_init_done = 1; dprintf(ALWAYS, "Qseecom TZ Init Done in Appsbl\n"); } return rc; } int qseecom_init() { int rc = GENERIC_ERROR; memset (&qseecom, 0, sizeof(struct qseecom_control)); dprintf(SPEW, "%s called\n", __func__); mutex_init(&(qseecom.global_data_lock)); mutex_init(&(qseecom.registered_app_list_lock)); mutex_init(&(qseecom.registered_listener_list_lock)); list_initialize(&(qseecom.registered_app_list_head)); list_initialize(&(qseecom.registered_listener_list_head)); qseecom.qseos_version = QSEOS_VERSION_14; rc = 0; if (!rc) { qseecom.qseecom_init_done = 1; dprintf(ALWAYS, "Qseecom Init Done in Appsbl\n"); } return rc; } int qseecom_exit() { dprintf(SPEW, "%s called\n", __func__); if (logbuf_req.phy_addr) free((void *)logbuf_req.phy_addr); qseecom.qseecom_init_done = 0; dprintf(ALWAYS, "Qseecom De-Init Done in Appsbl\n"); return 0; }