diff options
author | Luden <luden@ghostmail.com> | 2016-03-10 01:07:37 +0100 |
---|---|---|
committer | Ziyan <jaraidaniel@gmail.com> | 2016-04-03 14:57:30 +0200 |
commit | f460346c9be8bfe2c5b904ba54c46b53fdee6a43 (patch) | |
tree | 533e1852c6ef75901a61febe97f8e6e71634bb5c /security | |
parent | e1c6fce23182b44f8d9e27e6fba09cb64519d15e (diff) | |
download | kernel_samsung_tuna-f460346c9be8bfe2c5b904ba54c46b53fdee6a43.zip kernel_samsung_tuna-f460346c9be8bfe2c5b904ba54c46b53fdee6a43.tar.gz kernel_samsung_tuna-f460346c9be8bfe2c5b904ba54c46b53fdee6a43.tar.bz2 |
Power usage bug work-around for tuna SMC.
This commit implements the work-around for the power usage bug in tuna SMC
PA and enables SMC-related kernel options. Note that in order to take
advantage of this code there are also some userspace changes that are
required, e.g. enabling SMC PA loading and using tuna keymaster version
that is ported to Keymaster HAL 0.
Change-Id: I0e4705c6959e73fbeaec9bfb62d5e82e1dfa62a4
Diffstat (limited to 'security')
-rwxr-xr-x | security/smc/Makefile | 1 | ||||
-rw-r--r-- | security/smc/tf_comm_mshield.c | 15 | ||||
-rw-r--r-- | security/smc/tf_crypto.c | 22 | ||||
-rw-r--r-- | security/smc/tf_crypto.h | 5 | ||||
-rw-r--r-- | security/smc/tf_crypto_digest_clock_workaround.c | 235 |
5 files changed, 278 insertions, 0 deletions
diff --git a/security/smc/Makefile b/security/smc/Makefile index 6b5fefd..44e74ae 100755 --- a/security/smc/Makefile +++ b/security/smc/Makefile @@ -35,6 +35,7 @@ tf_driver-objs += tf_device.o tf_driver-objs += tf_comm.o tf_driver-objs += tf_crypto.o tf_driver-objs += tf_crypto_digest.o +tf_driver-objs += tf_crypto_digest_clock_workaround.o tf_driver-objs += tf_crypto_aes.o tf_driver-objs += tf_crypto_des.o tf_driver-objs += tf_dma.o diff --git a/security/smc/tf_comm_mshield.c b/security/smc/tf_comm_mshield.c index 23b6203..4b1a3f5 100644 --- a/security/smc/tf_comm_mshield.c +++ b/security/smc/tf_comm_mshield.c @@ -115,6 +115,20 @@ static struct wake_lock g_tf_wake_lock; static struct clockdomain *smc_l4_sec_clkdm; +void tf_crypto_clockdomain_wakeup(void) +{ + if (smc_l4_sec_clkdm) { + clkdm_wakeup(smc_l4_sec_clkdm); + } +} + +void tf_crypto_clockdomain_idle(void) +{ + if (smc_l4_sec_clkdm) { + clkdm_allow_idle(smc_l4_sec_clkdm); + } +} + static int __init tf_early_init(void) { g_secure_task_id = 0; @@ -203,6 +217,7 @@ static void tf_clock_timer_cb(unsigned long data) clkdm_allow_idle(smc_l4_sec_clkdm); spin_unlock_irqrestore(&clk_timer_lock, flags); + tf_crypto_digest_apply_clock_workaround(); dprintk(KERN_INFO "%s success\n", __func__); return; diff --git a/security/smc/tf_crypto.c b/security/smc/tf_crypto.c index 1be70ce..f70b087 100644 --- a/security/smc/tf_crypto.c +++ b/security/smc/tf_crypto.c @@ -850,6 +850,28 @@ end: spin_unlock_irqrestore(&clk_lock, flags); } +u32 tf_crypto_read_clock_value(uint32_t clock_paddr) +{ + u32 *clock_reg; + u32 val; + unsigned long flags; + + dprintk(KERN_INFO "tf_crypto_read_clock_value: " \ + "clock_paddr=0x%08X\n", + clock_paddr); + + /* Ensure none concurrent access when changing clock registers */ + spin_lock_irqsave(&clk_lock, flags); + + clock_reg = (u32 *)IO_ADDRESS(clock_paddr); + + val = __raw_readl(clock_reg); + + spin_unlock_irqrestore(&clk_lock, flags); + + return val; +} + /*------------------------------------------------------------------------- */ /* CUS RPCs */ /*------------------------------------------------------------------------- */ diff --git a/security/smc/tf_crypto.h b/security/smc/tf_crypto.h index 797b082..92fe9f0 100644 --- a/security/smc/tf_crypto.h +++ b/security/smc/tf_crypto.h @@ -395,4 +395,9 @@ extern char *tf_integrity_hmac_sha256_expected_value; #endif /* CONFIG_TF_DRIVER_CRYPTO_FIPS */ +u32 tf_crypto_read_clock_value(uint32_t clock_paddr); +void tf_crypto_digest_apply_clock_workaround(void); +void tf_crypto_clockdomain_wakeup(void); +void tf_crypto_clockdomain_idle(void); + #endif /*__TF_PUBLIC_CRYPTO_H */ diff --git a/security/smc/tf_crypto_digest_clock_workaround.c b/security/smc/tf_crypto_digest_clock_workaround.c new file mode 100644 index 0000000..4f135ea --- /dev/null +++ b/security/smc/tf_crypto_digest_clock_workaround.c @@ -0,0 +1,235 @@ +/** + * Copyright (c) 2016 Luden + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* SMC PA implementation for tuna devices has the following bug. + * Once SMC PA is loaded and PKCS11 session is opened at least once, + * SHA2MD5 clock is stuck in TRANSITION state. This consumes extra + * battery and prevents the device from going into sleep mode. + * + * See also the report of another user about this problem: + * https://e2e.ti.com/support/omap/f/849/t/235401, + * potentially the bug reported in this commit + * https://android.googlesource.com/device/samsung/tuna/+/b74801dc22bb4945ddf79b2e12e6328a862d68c3 + * is also related. + * + * It's hard to say what is the exact cause of this bug given that + * no information about either SHA2MD5, SMC PA or OMAP4 PPA is public. + * + * I was able to reproduce the similar issue by setting SHA2MD5 + * SYSCONFIG register to 0x88 (SADVANCED | SDMA_EN), potentially + * there's a "secure world" version of this register that is + * programmed incorrectly in PKCS11 session initialization? + * + * In any case, given that there's no way to modify SMC PA to fix + * this bug even if the exact reason was known (SMC PA is signed + * and the signature is verified during its loading), the whole + * investigation as to what causes it exactly is not that useful. + * + * However, apparently once SHA2MD5 "secure world" functionality + * is used at least once, the stuckness goes away (most likely HWA + * is re-initialized at this point in "secure world" and this time + * it's done properly?). It's enough to do this just once after the + * boot. + * + * Therefore, the code below implements a work-around for the + * stuckness problem by performing dummy calculation of the MD5 + * hash of 1 byte buffer. + * + * Note that the computation has to happen in the "secure world", + * using just the "normal world" interface to HWA doesn't cut it. */ + +#include <linux/types.h> +#include <linux/slab.h> + +#include "tee_client_api.h" +#include "tf_crypto.h" + +#define SERVICE_SYSTEM_UUID \ + { 0x56304b83, 0x5c4e, 0x4428, \ + { 0xb9, 0x9e, 0x60, 0x5c, 0x96, 0xae, 0x58, 0xd6 } } + +#define CKF_SERIAL_SESSION 0x00000004 +/* It doesn't matter which hash is used as long as HWA supports it. */ +#define CKM_MD5 0x00000210 + +#define SERVICE_SYSTEM_PKCS11_C_DIGESTINIT_COMMAND_ID 0x00000026 +#define SERVICE_SYSTEM_PKCS11_C_DIGEST_COMMAND_ID 0x00000027 +#define SERVICE_SYSTEM_PKCS11_C_OPEN_SESSION_COMMAND_ID 0x00000042 +#define SERVICE_SYSTEM_PKCS11_C_CLOSE_SESSION_COMMAND_ID 0x00000043 + +/* Exact buffer size doesn't matter as long as it's > 0. */ +#define BUFFER_SIZE 1 +/* Size of MD5 digest. */ +#define DIGEST_SIZE 16 +#define TOTAL_BUFFER_SIZE (BUFFER_SIZE + DIGEST_SIZE) + +static void _tf_crypto_digest_apply_clock_workaround(struct work_struct *work) +{ + uint8_t *buf, *digest; + u32 cmd; + u32 crypto_session; + TEEC_Result ret; + TEEC_Operation op; + TEEC_Context teec_context; + TEEC_Session teec_session; + TEEC_UUID uuid = SERVICE_SYSTEM_UUID; + bool result = true; + + tf_crypto_clockdomain_wakeup(); + + ret = TEEC_InitializeContext(NULL, &teec_context); + if (ret != TEEC_SUCCESS) { + tf_crypto_clockdomain_idle(); + return; + } + + buf = kmalloc(TOTAL_BUFFER_SIZE, GFP_KERNEL); + + if (buf == NULL) { + TEEC_FinalizeContext(&teec_context); + tf_crypto_clockdomain_idle(); + return; + } + + digest = buf + BUFFER_SIZE; + + /* Call C_OpenSession */ + memset(&op, 0, sizeof(TEEC_Operation)); + + op.paramTypes = TEEC_PARAM_TYPES(TEEC_NONE, TEEC_NONE, TEEC_NONE, + TEEC_NONE); + + ret = TEEC_OpenSession(&teec_context, &teec_session, + &uuid, TEEC_LOGIN_PUBLIC, NULL, &op, NULL); + if (ret != TEEC_SUCCESS) { + TEEC_FinalizeContext(&teec_context); + tf_crypto_clockdomain_idle(); + kfree(buf); + return; + } + + memset(&op, 0, sizeof(TEEC_Operation)); + + op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INOUT, TEEC_NONE, TEEC_NONE, + TEEC_NONE); + op.params[0].value.a = 0; + op.params[0].value.b = CKF_SERIAL_SESSION; + + cmd = SERVICE_SYSTEM_PKCS11_C_OPEN_SESSION_COMMAND_ID & 0x00007FFF; + + ret = TEEC_InvokeCommand(&teec_session, cmd, &op, NULL); + if (ret != TEEC_SUCCESS) { + printk(KERN_ERR "%s: TEEC_InvokeCommand returned 0x%08x\n", + __func__, ret); + result = false; + goto exit; + } + + crypto_session = op.params[0].value.a; + + /* Call C_DigestInit */ + memset(&op, 0, sizeof(TEEC_Operation)); + + op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, + TEEC_MEMREF_TEMP_INPUT, TEEC_NONE, TEEC_NONE); + op.params[0].value.a = CKM_MD5; + op.params[1].tmpref.buffer = NULL; + op.params[1].tmpref.size = 0; + + cmd = (crypto_session << 16) | + (SERVICE_SYSTEM_PKCS11_C_DIGESTINIT_COMMAND_ID & 0x7fff); + + ret = TEEC_InvokeCommand(&teec_session, cmd, &op, NULL); + if (ret != TEEC_SUCCESS) { + printk(KERN_ERR "%s: TEEC_InvokeCommand returned 0x%08x\n", + __func__, ret); + result = false; + } + + /* Call C_Digest */ + memset(&op, 0, sizeof(TEEC_Operation)); + + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, + TEEC_MEMREF_TEMP_OUTPUT, TEEC_NONE, TEEC_NONE); + op.params[0].tmpref.buffer = (uint8_t *) buf; + op.params[0].tmpref.size = (uint32_t) BUFFER_SIZE; + op.params[1].tmpref.buffer = (uint8_t *) digest; + op.params[1].tmpref.size = (uint32_t) DIGEST_SIZE; + + cmd = (crypto_session << 16) | + (SERVICE_SYSTEM_PKCS11_C_DIGEST_COMMAND_ID & 0x7fff); + + ret = TEEC_InvokeCommand(&teec_session, cmd, &op, NULL); + if (ret != TEEC_SUCCESS) { + printk(KERN_ERR "%s: TEEC_InvokeCommand returned 0x%08x\n", + __func__, ret); + result = false; + } + + /* Call C_CloseSession */ + memset(&op, 0, sizeof(TEEC_Operation)); + + op.paramTypes = TEEC_PARAM_TYPES(TEEC_NONE, TEEC_NONE, TEEC_NONE, + TEEC_NONE); + + cmd = (crypto_session << 16) | + (SERVICE_SYSTEM_PKCS11_C_CLOSE_SESSION_COMMAND_ID & 0x7fff); + + ret = TEEC_InvokeCommand(&teec_session, cmd, &op, NULL); + if (ret != TEEC_SUCCESS) { + printk(KERN_ERR "%s: TEEC_InvokeCommand returned 0x%08x\n", + __func__, ret); + result = false; + } + + if (result) { + u32 clock_val = tf_crypto_read_clock_value(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + result = (clock_val & 0x30000) != 0x10000; + } + +exit: + TEEC_CloseSession(&teec_session); + TEEC_FinalizeContext(&teec_context); + + tf_crypto_clockdomain_idle(); + kfree(buf); + + printk(KERN_INFO "%s: SHA2MD5 clock work-around result: %s\n", + __func__, result ? "succeeded" : "failed"); + + return; +} + +static DECLARE_WORK(digest_clock_workaround_work, _tf_crypto_digest_apply_clock_workaround); + +void tf_crypto_digest_apply_clock_workaround(void) +{ + u32 clock_val; + + clock_val = tf_crypto_read_clock_value(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + if ((clock_val & 0x30000) == 0x10000) { + printk(KERN_INFO "%s: SHA2MD5 clock is stuck, trying to work-around\n", __func__); + /* The calling path of our workaround function might involve interrupt handlers + * and other interesting parts of the kernel code. That doesn't play nice + * with the functionality used by that function (e.g. memory allocation) - + * therefore, just schedule it for execution from the more appropriate + * context instead of executing it directly. */ + schedule_work(&digest_clock_workaround_work); + } +} |