diff options
Diffstat (limited to 'security/smc/tf_crypto_digest.c')
-rw-r--r-- | security/smc/tf_crypto_digest.c | 992 |
1 files changed, 992 insertions, 0 deletions
diff --git a/security/smc/tf_crypto_digest.c b/security/smc/tf_crypto_digest.c new file mode 100644 index 0000000..d69a97f --- /dev/null +++ b/security/smc/tf_crypto_digest.c @@ -0,0 +1,992 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * 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 + */ + +#include "tf_defs.h" +#include "tf_util.h" +#include "tf_crypto.h" +#include "tf_dma.h" +#include "tf_zebra.h" + +#include <linux/io.h> +#include <mach/io.h> +#include <linux/crypto.h> +#include <crypto/internal/hash.h> + +/* + * SHA2/MD5 Hardware Accelerator: Base address for SHA2/MD5 HIB2 + * This is referenced as the SHA2MD5 module in the Crypto TRM + */ +#define DIGEST1_REGS_HW_ADDR 0x4B101000 + +/* + * IRQSTATUS register Masks + */ +#define DIGEST_IRQSTATUS_OUTPUT_READY_BIT (1 << 0) +#define DIGEST_IRQSTATUS_INPUT_READY_BIT (1 << 1) +#define DIGEST_IRQSTATUS_PARTHASH_READY_BIT (1 << 2) +#define DIGEST_IRQSTATUS_CONTEXT_READY_BIT (1 << 3) + +/* + * MODE register Masks + */ +#define DIGEST_MODE_GET_ALGO(x) ((x & 0x6) >> 1) +#define DIGEST_MODE_SET_ALGO(x, a) ((a << 1) | (x & 0xFFFFFFF9)) + +#define DIGEST_MODE_ALGO_CONST_BIT (1 << 3) +#define DIGEST_MODE_CLOSE_HASH_BIT (1 << 4) + +/* + * SYSCONFIG register masks + */ +#define DIGEST_SYSCONFIG_PIT_EN_BIT (1 << 2) +#define DIGEST_SYSCONFIG_PDMA_EN_BIT (1 << 3) +#define DIGEST_SYSCONFIG_PCONT_SWT_BIT (1 << 6) +#define DIGEST_SYSCONFIG_PADVANCED_BIT (1 << 7) + +/*-------------------------------------------------------------------------*/ +/* Digest Context */ +/*-------------------------------------------------------------------------*/ +/** + * This structure contains the registers of the SHA1/MD5 HW accelerator. + */ +struct sha1_md5_reg { + u32 ODIGEST_A; /* 0x00 Outer Digest A */ + u32 ODIGEST_B; /* 0x04 Outer Digest B */ + u32 ODIGEST_C; /* 0x08 Outer Digest C */ + u32 ODIGEST_D; /* 0x0C Outer Digest D */ + u32 ODIGEST_E; /* 0x10 Outer Digest E */ + u32 ODIGEST_F; /* 0x14 Outer Digest F */ + u32 ODIGEST_G; /* 0x18 Outer Digest G */ + u32 ODIGEST_H; /* 0x1C Outer Digest H */ + u32 IDIGEST_A; /* 0x20 Inner Digest A */ + u32 IDIGEST_B; /* 0x24 Inner Digest B */ + u32 IDIGEST_C; /* 0x28 Inner Digest C */ + u32 IDIGEST_D; /* 0x2C Inner Digest D */ + u32 IDIGEST_E; /* 0x30 Inner Digest E */ + u32 IDIGEST_F; /* 0x34 Inner Digest F */ + u32 IDIGEST_G; /* 0x38 Inner Digest G */ + u32 IDIGEST_H; /* 0x3C Inner Digest H */ + u32 DIGEST_COUNT; /* 0x40 Digest count */ + u32 MODE; /* 0x44 Digest mode */ + u32 LENGTH; /* 0x48 Data length */ + + u32 reserved0[13]; + + u32 DIN_0; /* 0x80 Data 0 */ + u32 DIN_1; /* 0x84 Data 1 */ + u32 DIN_2; /* 0x88 Data 2 */ + u32 DIN_3; /* 0x8C Data 3 */ + u32 DIN_4; /* 0x90 Data 4 */ + u32 DIN_5; /* 0x94 Data 5 */ + u32 DIN_6; /* 0x98 Data 6 */ + u32 DIN_7; /* 0x9C Data 7 */ + u32 DIN_8; /* 0xA0 Data 8 */ + u32 DIN_9; /* 0xA4 Data 9 */ + u32 DIN_10; /* 0xA8 Data 10 */ + u32 DIN_11; /* 0xAC Data 11 */ + u32 DIN_12; /* 0xB0 Data 12 */ + u32 DIN_13; /* 0xB4 Data 13 */ + u32 DIN_14; /* 0xB8 Data 14 */ + u32 DIN_15; /* 0xBC Data 15 */ + + u32 reserved1[16]; + + u32 REVISION; /* 0x100 Revision */ + + u32 reserved2[3]; + + u32 SYSCONFIG; /* 0x110 Config */ + u32 SYSSTATUS; /* 0x114 Status */ + u32 IRQSTATUS; /* 0x118 IRQ Status */ + u32 IRQENABLE; /* 0x11C IRQ Enable */ +}; + +static struct sha1_md5_reg *sha1_md5_reg; + +static const u8 md5OverEmptyString[] = { + 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, + 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e +}; + +static const u8 sha1OverEmptyString[] = { + 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, + 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, + 0xaf, 0xd8, 0x07, 0x09 +}; + +static const u8 sha224OverEmptyString[] = { + 0xd1, 0x4a, 0x02, 0x8c, 0x2a, 0x3a, 0x2b, 0xc9, + 0x47, 0x61, 0x02, 0xbb, 0x28, 0x82, 0x34, 0xc4, + 0x15, 0xa2, 0xb0, 0x1f, 0x82, 0x8e, 0xa6, 0x2a, + 0xc5, 0xb3, 0xe4, 0x2f +}; + +static const u8 sha256OverEmptyString[] = { + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, + 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, + 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 +}; + +/*------------------------------------------------------------------------ + *Forward declarations + *------------------------------------------------------------------------- */ + +static void tf_digest_hw_perform_64b(u32 *data, + u32 algo, u32 bytes_processed); +static bool tf_digest_hw_perform_dma(u8 *data, u32 nDataLength, + u32 algo, u32 bytes_processed); + +static bool tf_digest_update_dma( + struct tf_crypto_sha_operation_state *sha_state, + u8 *data, u32 data_length); + + +/*------------------------------------------------------------------------- + *Save HWA registers into the specified operation state structure + *------------------------------------------------------------------------*/ +static void tf_digest_save_registers( + struct tf_crypto_sha_operation_state *sha_state) +{ + dprintk(KERN_INFO "tf_digest_save_registers: State=%p\n", + sha_state); + + sha_state->SHA_DIGEST_A = INREG32(&sha1_md5_reg->IDIGEST_A); + sha_state->SHA_DIGEST_B = INREG32(&sha1_md5_reg->IDIGEST_B); + sha_state->SHA_DIGEST_C = INREG32(&sha1_md5_reg->IDIGEST_C); + sha_state->SHA_DIGEST_D = INREG32(&sha1_md5_reg->IDIGEST_D); + sha_state->SHA_DIGEST_E = INREG32(&sha1_md5_reg->IDIGEST_E); + sha_state->SHA_DIGEST_F = INREG32(&sha1_md5_reg->IDIGEST_F); + sha_state->SHA_DIGEST_G = INREG32(&sha1_md5_reg->IDIGEST_G); + sha_state->SHA_DIGEST_H = INREG32(&sha1_md5_reg->IDIGEST_H); +} + +/*------------------------------------------------------------------------- + *Restore the HWA registers from the operation state structure + *-------------------------------------------------------------------------*/ +static void tf_digest_restore_registers( + struct tf_crypto_sha_operation_state *sha_state) +{ + dprintk(KERN_INFO "tf_digest_restore_registers: State=%p\n", + sha_state); + + if (sha_state->bytes_processed != 0) { + /* + * Some bytes were already processed. Initialize + * previous digest + */ + OUTREG32(&sha1_md5_reg->IDIGEST_A, sha_state->SHA_DIGEST_A); + OUTREG32(&sha1_md5_reg->IDIGEST_B, sha_state->SHA_DIGEST_B); + OUTREG32(&sha1_md5_reg->IDIGEST_C, sha_state->SHA_DIGEST_C); + OUTREG32(&sha1_md5_reg->IDIGEST_D, sha_state->SHA_DIGEST_D); + OUTREG32(&sha1_md5_reg->IDIGEST_E, sha_state->SHA_DIGEST_E); + OUTREG32(&sha1_md5_reg->IDIGEST_F, sha_state->SHA_DIGEST_F); + OUTREG32(&sha1_md5_reg->IDIGEST_G, sha_state->SHA_DIGEST_G); + OUTREG32(&sha1_md5_reg->IDIGEST_H, sha_state->SHA_DIGEST_H); + } + + OUTREG32(&sha1_md5_reg->SYSCONFIG, 0); +} + +/*------------------------------------------------------------------------- */ + +void tf_digest_init(void) +{ + sha1_md5_reg = omap_ioremap(DIGEST1_REGS_HW_ADDR, SZ_1M, MT_DEVICE); + if (sha1_md5_reg == NULL) + panic("Unable to remap SHA2/MD5 module"); +} + +void tf_digest_exit(void) +{ + omap_iounmap(sha1_md5_reg); +} + +bool tf_digest_update(struct tf_crypto_sha_operation_state *sha_state, + u8 *data, u32 data_length) +{ + u32 dma_use = PUBLIC_CRYPTO_DMA_USE_NONE; + + /* + *Choice of the processing type + */ + if (data_length >= DMA_TRIGGER_IRQ_DIGEST) + dma_use = PUBLIC_CRYPTO_DMA_USE_IRQ; + + dprintk(KERN_INFO "tf_digest_update : "\ + "Data=0x%08x/%u, Chunk=%u, Processed=%u, dma_use=0x%08x\n", + (u32)data, (u32)data_length, + sha_state->chunk_length, sha_state->bytes_processed, + dma_use); + + if (data_length == 0) { + dprintk(KERN_INFO "tf_digest_update: "\ + "Nothing to process\n"); + return true; + } + + if (dma_use != PUBLIC_CRYPTO_DMA_USE_NONE) { + /* + * Restore the registers of the accelerator from the operation + * state + */ + tf_digest_restore_registers(sha_state); + + /*perform the updates with DMA */ + if (!tf_digest_update_dma(sha_state, data, data_length)) + return false; + + /* Save the accelerator registers into the operation state */ + tf_digest_save_registers(sha_state); + } else { + /*Non-DMA transfer */ + + /*(1)We take the chunk buffer wich contains the last saved + *data that could not be yet processed because we had not + *enough data to make a 64B buffer. Then we try to make a + *64B buffer by concatenating it with the new passed data + */ + + /*Is there any data in the chunk? If yes is it possible to + *make a 64B buffer with the new data passed ? */ + if ((sha_state->chunk_length != 0) + && (sha_state->chunk_length + data_length >= + HASH_BLOCK_BYTES_LENGTH)) { + + u8 vLengthToComplete = + HASH_BLOCK_BYTES_LENGTH - sha_state->chunk_length; + + /*So we fill the chunk buffer with the new data to + *complete to 64B */ + if (copy_from_user( + sha_state->chunk_buffer+sha_state->chunk_length, + data, + vLengthToComplete)) + return false; + + if (sha_state->chunk_length + data_length == + HASH_BLOCK_BYTES_LENGTH) { + /*We'll keep some data for the final */ + sha_state->chunk_length = + HASH_BLOCK_BYTES_LENGTH; + dprintk(KERN_INFO "tf_digest_update: "\ + "Done: Chunk=%u; Processed=%u\n", + sha_state->chunk_length, + sha_state->bytes_processed); + return true; + } + + /* + * Restore the registers of the accelerator from the + * operation state + */ + tf_digest_restore_registers(sha_state); + + /*Then we send this buffer to the HWA */ + tf_digest_hw_perform_64b( + (u32 *)sha_state->chunk_buffer, sha_state->CTRL, + sha_state->bytes_processed); + + /* + * Save the accelerator registers into the operation + * state + */ + tf_digest_save_registers(sha_state); + + sha_state->bytes_processed = + INREG32(&sha1_md5_reg->DIGEST_COUNT); + + /*We have flushed the chunk so it is empty now */ + sha_state->chunk_length = 0; + + /*Then we have less data to process */ + data += vLengthToComplete; + data_length -= vLengthToComplete; + } + + /*(2)We process all the 64B buffer that we can */ + if (sha_state->chunk_length + data_length >= + HASH_BLOCK_BYTES_LENGTH) { + + while (data_length > HASH_BLOCK_BYTES_LENGTH) { + u8 pTempAlignedBuffer[HASH_BLOCK_BYTES_LENGTH]; + + /* + *We process a 64B buffer + */ + /*We copy the data to process to an aligned + *buffer */ + if (copy_from_user( + pTempAlignedBuffer, + data, + HASH_BLOCK_BYTES_LENGTH)) + return false; + + /*Then we send this buffer to the hash + *hardware */ + tf_digest_restore_registers(sha_state); + tf_digest_hw_perform_64b( + (u32 *) pTempAlignedBuffer, + sha_state->CTRL, + sha_state->bytes_processed); + tf_digest_save_registers(sha_state); + + sha_state->bytes_processed = + INREG32(&sha1_md5_reg->DIGEST_COUNT); + + /*Then we decrease the remaining data of 64B */ + data += HASH_BLOCK_BYTES_LENGTH; + data_length -= HASH_BLOCK_BYTES_LENGTH; + } + } + + /*(3)We look if we have some data that could not be processed + *yet because it is not large enough to fill a buffer of 64B */ + if (data_length > 0) { + if (sha_state->chunk_length + data_length > + HASH_BLOCK_BYTES_LENGTH) { + /*Should never be in this case !!! */ + panic("tf_digest_update: chunk_length data_length > " + "HASH_BLOCK_BYTES_LENGTH\n"); + } + + /*So we fill the chunk buffer with the new data to + *complete to 64B */ + if (copy_from_user( + sha_state->chunk_buffer+sha_state->chunk_length, + data, + data_length)) + return false; + sha_state->chunk_length += data_length; + } + } + + dprintk(KERN_INFO "tf_digest_update: Done: "\ + "Chunk=%u; Processed=%u\n", + sha_state->chunk_length, sha_state->bytes_processed); + + return true; +} + +/*------------------------------------------------------------------------- */ + +static void tf_digest_hw_perform_64b(u32 *data, + u32 algo, u32 bytes_processed) +{ + u32 algo_constant = 0; + + OUTREG32(&sha1_md5_reg->DIGEST_COUNT, bytes_processed); + + if (bytes_processed == 0) { + /* No bytes processed so far. Will use the algo constant instead + of previous digest */ + algo_constant = 1 << 3; + } + + OUTREG32(&sha1_md5_reg->MODE, + algo_constant | (algo & 0x6)); + OUTREG32(&sha1_md5_reg->LENGTH, HASH_BLOCK_BYTES_LENGTH); + + if (tf_crypto_wait_for_ready_bit( + (u32 *)&sha1_md5_reg->IRQSTATUS, + DIGEST_IRQSTATUS_INPUT_READY_BIT) + != PUBLIC_CRYPTO_OPERATION_SUCCESS) { + /* Crash the system as this should never occur */ + panic("Wait too long for DIGEST HW accelerator" \ + "Input data to be ready\n"); + } + + /* + *The data buffer is a buffer of 64 bytes. + */ + OUTREG32(&sha1_md5_reg->DIN_0, data[0]); + OUTREG32(&sha1_md5_reg->DIN_1, data[1]); + OUTREG32(&sha1_md5_reg->DIN_2, data[2]); + OUTREG32(&sha1_md5_reg->DIN_3, data[3]); + OUTREG32(&sha1_md5_reg->DIN_4, data[4]); + OUTREG32(&sha1_md5_reg->DIN_5, data[5]); + OUTREG32(&sha1_md5_reg->DIN_6, data[6]); + OUTREG32(&sha1_md5_reg->DIN_7, data[7]); + OUTREG32(&sha1_md5_reg->DIN_8, data[8]); + OUTREG32(&sha1_md5_reg->DIN_9, data[9]); + OUTREG32(&sha1_md5_reg->DIN_10, data[10]); + OUTREG32(&sha1_md5_reg->DIN_11, data[11]); + OUTREG32(&sha1_md5_reg->DIN_12, data[12]); + OUTREG32(&sha1_md5_reg->DIN_13, data[13]); + OUTREG32(&sha1_md5_reg->DIN_14, data[14]); + OUTREG32(&sha1_md5_reg->DIN_15, data[15]); + + /* + *Wait until the hash operation is finished. + */ + tf_crypto_wait_for_ready_bit_infinitely( + (u32 *)&sha1_md5_reg->IRQSTATUS, + DIGEST_IRQSTATUS_OUTPUT_READY_BIT); +} + +/*------------------------------------------------------------------------- */ + +static bool tf_digest_hw_perform_dma(u8 *data, u32 nDataLength, + u32 algo, u32 bytes_processed) +{ + /* + *Note: The DMA only sees physical addresses ! + */ + + int dma_ch0; + struct omap_dma_channel_params ch0_parameters; + u32 length_loop = 0; + u32 algo_constant; + struct tf_device *dev = tf_get_device(); + + dprintk(KERN_INFO + "tf_digest_hw_perform_dma: Buffer=0x%08x/%u\n", + (u32)data, (u32)nDataLength); + + /*lock the DMA */ + mutex_lock(&dev->sm.dma_mutex); + if (tf_dma_request(&dma_ch0) != PUBLIC_CRYPTO_OPERATION_SUCCESS) { + mutex_unlock(&dev->sm.dma_mutex); + return false; + } + + while (nDataLength > 0) { + + algo_constant = 0; + if (bytes_processed == 0) { + /*No bytes processed so far. Will use the algo + *constant instead of previous digest */ + algo_constant = 1 << 3; + } + + /*check length */ + if (nDataLength <= dev->dma_buffer_length) + length_loop = nDataLength; + else + length_loop = dev->dma_buffer_length; + + /* + * Copy the data from the user input buffer into a preallocated + * buffer which has correct properties from efficient DMA + * transfers. + */ + if (copy_from_user(dev->dma_buffer, data, length_loop)) { + omap_free_dma(dma_ch0); + mutex_unlock(&dev->sm.dma_mutex); + return false; + } + + /*DMA1: Mem -> HASH */ + tf_dma_set_channel_common_params(&ch0_parameters, + length_loop / HASH_BLOCK_BYTES_LENGTH, + DMA_CEN_Elts_per_Frame_SHA, + DIGEST1_REGS_HW_ADDR + 0x80, + dev->dma_buffer_phys, + OMAP44XX_DMA_SHA2_DIN_P); + + /*specific for Mem -> HWA */ + ch0_parameters.src_amode = OMAP_DMA_AMODE_POST_INC; + ch0_parameters.dst_amode = OMAP_DMA_AMODE_CONSTANT; + ch0_parameters.src_or_dst_synch = OMAP_DMA_DST_SYNC; + + omap_set_dma_params(dma_ch0, &ch0_parameters); + + omap_set_dma_src_burst_mode(dma_ch0, OMAP_DMA_DATA_BURST_16); + omap_set_dma_dest_burst_mode(dma_ch0, OMAP_DMA_DATA_BURST_16); + + OUTREG32(&sha1_md5_reg->DIGEST_COUNT, bytes_processed); + OUTREG32(&sha1_md5_reg->MODE, + algo_constant | (algo & 0x6)); + + /* + * Triggers operation + * Interrupt, Free Running + GO (DMA on) + */ + OUTREG32(&sha1_md5_reg->SYSCONFIG, + INREG32(&sha1_md5_reg->SYSCONFIG) | + DIGEST_SYSCONFIG_PDMA_EN_BIT); + OUTREG32(&sha1_md5_reg->LENGTH, length_loop); + + wmb(); + + tf_dma_start(dma_ch0, OMAP_DMA_BLOCK_IRQ); + + tf_dma_wait(1); + + OUTREG32(&sha1_md5_reg->SYSCONFIG, 0); + + omap_clear_dma(dma_ch0); + + data += length_loop; + nDataLength -= length_loop; + bytes_processed = + INREG32(&sha1_md5_reg->DIGEST_COUNT); + } + + /*For safety reasons, let's clean the working buffer */ + memset(dev->dma_buffer, 0, length_loop); + + /*release the DMA */ + omap_free_dma(dma_ch0); + + mutex_unlock(&dev->sm.dma_mutex); + + /* + * The dma transfert is finished, now wait until the hash + * operation is finished. + */ + tf_crypto_wait_for_ready_bit_infinitely( + (u32 *)&sha1_md5_reg->IRQSTATUS, + DIGEST_IRQSTATUS_CONTEXT_READY_BIT); + + return true; +} + +/*------------------------------------------------------------------------- */ +/* + *Static function, perform data digest using the DMA for data transfer. + * + *inputs: + * data : pointer of the input data to process + * data_length : number of byte to process + */ +static bool tf_digest_update_dma( + struct tf_crypto_sha_operation_state *sha_state, + u8 *data, u32 data_length) +{ + dprintk(KERN_INFO "tf_digest_update_dma\n"); + + if (sha_state->chunk_length != 0) { + + u32 vLengthToComplete; + + /*Fill the chunk first */ + if (sha_state-> + chunk_length + data_length <= HASH_BLOCK_BYTES_LENGTH) { + + /*So we fill the chunk buffer with the new data */ + if (copy_from_user(sha_state->chunk_buffer + + sha_state->chunk_length, data, + data_length)) + return false; + sha_state->chunk_length += data_length; + + /*We'll keep some data for the final */ + return true; + } + + vLengthToComplete = HASH_BLOCK_BYTES_LENGTH - sha_state-> + chunk_length; + + if (vLengthToComplete != 0) { + /*So we fill the chunk buffer with the new data to + *complete to 64B */ + if (copy_from_user(sha_state->chunk_buffer + + sha_state->chunk_length, data, + vLengthToComplete)) + return false; + } + + /*Then we send this buffer to the HWA (no DMA) */ + tf_digest_hw_perform_64b( + (u32 *)sha_state->chunk_buffer, sha_state->CTRL, + sha_state->bytes_processed); + + sha_state->bytes_processed = + INREG32(&sha1_md5_reg->DIGEST_COUNT); + + /*We have flushed the chunk so it is empty now */ + sha_state->chunk_length = 0; + + /*Update the data buffer depending of the data already + *processed */ + data += vLengthToComplete; + data_length -= vLengthToComplete; + } + + if (data_length > HASH_BLOCK_BYTES_LENGTH) { + + /*DMA only manages data length that is multiple of 64b */ + u32 vDmaProcessize = data_length & 0xFFFFFFC0; + + if (vDmaProcessize == data_length) { + /*We keep one block for the final */ + vDmaProcessize -= HASH_BLOCK_BYTES_LENGTH; + } + + if (!tf_digest_hw_perform_dma(data, vDmaProcessize, + sha_state->CTRL, sha_state->bytes_processed)) + return false; + + sha_state->bytes_processed = + INREG32(&sha1_md5_reg->DIGEST_COUNT); + data += vDmaProcessize; + data_length -= vDmaProcessize; + } + + /*At that point, there is less than 64b left to process*/ + if ((data_length == 0) || (data_length > HASH_BLOCK_BYTES_LENGTH)) + /*Should never be in this case !!! */ + return false; + + /*We now fill the chunk buffer with the remaining data */ + if (copy_from_user(sha_state->chunk_buffer, data, data_length)) + return false; + sha_state->chunk_length = data_length; + + return true; +} + +#ifdef CONFIG_SMC_KERNEL_CRYPTO +static void tf_digest_init_operation(u32 alg, + struct tf_crypto_sha_operation_state *state) +{ + memset(state, 0, sizeof(struct tf_crypto_sha_operation_state)); + + state->CTRL = alg << 1; +} + +static int static_Hash_HwReadDigest(u32 algo, u8 *out) +{ + u32 regs, tmp; + u32 idx = 0, i; + + switch (algo) { + case DIGEST_CTRL_ALGO_MD5: + regs = 4; + break; + case DIGEST_CTRL_ALGO_SHA1: + regs = 5; + break; + case DIGEST_CTRL_ALGO_SHA224: + regs = 7; + break; + case DIGEST_CTRL_ALGO_SHA256: + regs = 8; + break; + default: + return -EINVAL; + } + + for (i = 0; i < regs; i++) { + tmp = INREG32(&sha1_md5_reg->IDIGEST_A + i); + + out[idx++] = (u8) ((tmp >> 0) & 0xff); + out[idx++] = (u8) ((tmp >> 8) & 0xff); + out[idx++] = (u8) ((tmp >> 16) & 0xff); + out[idx++] = (u8) ((tmp >> 24) & 0xff); + } + + return 0; +} + +static int tf_digest_final(struct tf_crypto_sha_operation_state *state, + u8 *out) +{ + u32 *data = (u32 *) state->chunk_buffer; + + /* Hashing an empty string? */ + if (state->bytes_processed + state->chunk_length == 0) { + switch (DIGEST_MODE_GET_ALGO(state->CTRL)) { + case DIGEST_CTRL_ALGO_MD5: + memcpy(out, md5OverEmptyString, HASH_MD5_LENGTH); + break; + case DIGEST_CTRL_ALGO_SHA1: + memcpy(out, sha1OverEmptyString, HASH_SHA1_LENGTH); + break; + case DIGEST_CTRL_ALGO_SHA224: + memcpy(out, sha224OverEmptyString, HASH_SHA224_LENGTH); + break; + case DIGEST_CTRL_ALGO_SHA256: + memcpy(out, sha256OverEmptyString, HASH_SHA256_LENGTH); + break; + default: + return -EINVAL; + } + + return 0; + } + + tf_digest_restore_registers(state); + + /* + * At this point, the chunk buffer should contain the last block of data + * needed for the final. + */ + OUTREG32(&sha1_md5_reg->DIGEST_COUNT, state->bytes_processed); + OUTREG32(&sha1_md5_reg->MODE, + (state->CTRL & 0x6) | 0x10 | + (state->bytes_processed == 0) << 3); + OUTREG32(&sha1_md5_reg->LENGTH, state->chunk_length); + + if (tf_crypto_wait_for_ready_bit( + (u32 *) &sha1_md5_reg->IRQSTATUS, + DIGEST_IRQSTATUS_INPUT_READY_BIT) + != PUBLIC_CRYPTO_OPERATION_SUCCESS) { + /* Crash the system as this should never occur */ + panic("Wait too long for DIGEST HW accelerator" + "Input data to be ready\n"); + } + + OUTREG32(&sha1_md5_reg->DIN_0, data[0]); + OUTREG32(&sha1_md5_reg->DIN_1, data[1]); + OUTREG32(&sha1_md5_reg->DIN_2, data[2]); + OUTREG32(&sha1_md5_reg->DIN_3, data[3]); + OUTREG32(&sha1_md5_reg->DIN_4, data[4]); + OUTREG32(&sha1_md5_reg->DIN_5, data[5]); + OUTREG32(&sha1_md5_reg->DIN_6, data[6]); + OUTREG32(&sha1_md5_reg->DIN_7, data[7]); + OUTREG32(&sha1_md5_reg->DIN_8, data[8]); + OUTREG32(&sha1_md5_reg->DIN_9, data[9]); + OUTREG32(&sha1_md5_reg->DIN_10, data[10]); + OUTREG32(&sha1_md5_reg->DIN_11, data[11]); + OUTREG32(&sha1_md5_reg->DIN_12, data[12]); + OUTREG32(&sha1_md5_reg->DIN_13, data[13]); + OUTREG32(&sha1_md5_reg->DIN_14, data[14]); + OUTREG32(&sha1_md5_reg->DIN_15, data[15]); + + /* Wait till the hash operation is finished */ + tf_crypto_wait_for_ready_bit_infinitely( + (u32 *) &sha1_md5_reg->IRQSTATUS, + DIGEST_IRQSTATUS_OUTPUT_READY_BIT); + + return static_Hash_HwReadDigest(DIGEST_MODE_GET_ALGO(state->CTRL), out); +} + +/* + * Digest HWA registration into kernel crypto framework + */ + +static int digest_update(struct shash_desc *desc, const u8 *data, + unsigned int len) +{ + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); + + /* Make sure SHA/MD5 HWA is accessible */ + tf_delayed_secure_resume(); + + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_SHA, LOCK_HWA); + + tf_crypto_enable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + + tf_digest_update(state, (u8 *) data, len); + + tf_crypto_disable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_SHA, UNLOCK_HWA); + + return 0; +} + +static int digest_final(struct shash_desc *desc, u8 *out) +{ + int ret; + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); + + /* Make sure SHA/MD5 HWA is accessible */ + tf_delayed_secure_resume(); + + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_SHA, LOCK_HWA); + + tf_crypto_enable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + + ret = tf_digest_final(state, out); + + tf_crypto_disable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG); + + tf_crypto_lock_hwa(PUBLIC_CRYPTO_HWA_SHA, UNLOCK_HWA); + + return ret; +} + +static int digest_import(struct shash_desc *desc, const void *in) +{ + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); + + memcpy(state, in, sizeof(*state)); + return 0; +} + +static int digest_export(struct shash_desc *desc, void *out) +{ + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); + + memcpy(out, state, sizeof(*state)); + return 0; +} + +/* MD5 */ +static int md5_init(struct shash_desc *desc) +{ + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); + + tf_digest_init_operation(DIGEST_CTRL_ALGO_MD5, state); + + return 0; +} + +static struct shash_alg smc_md5_alg = { + .digestsize = HASH_MD5_LENGTH, + .init = md5_init, + .update = digest_update, + .final = digest_final, + .export = digest_export, + .import = digest_import, + .descsize = sizeof(struct tf_crypto_sha_operation_state), + .statesize = sizeof(struct tf_crypto_sha_operation_state), + .base = { + .cra_name = "md5", + .cra_driver_name = "md5-smc", + .cra_flags = CRYPTO_ALG_TYPE_SHASH, + .cra_priority = 999, + .cra_blocksize = HASH_BLOCK_BYTES_LENGTH, + .cra_module = THIS_MODULE, + } +}; + +/* SHA1 */ +static int sha1_init(struct shash_desc *desc) +{ + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); + + tf_digest_init_operation(DIGEST_CTRL_ALGO_SHA1, state); + + return 0; +} + +static struct shash_alg smc_sha1_alg = { + .digestsize = HASH_SHA1_LENGTH, + .init = sha1_init, + .update = digest_update, + .final = digest_final, + .export = digest_export, + .import = digest_import, + .descsize = sizeof(struct tf_crypto_sha_operation_state), + .statesize = sizeof(struct tf_crypto_sha_operation_state), + .base = { + .cra_name = "sha1", + .cra_driver_name = "sha1-smc", + .cra_flags = CRYPTO_ALG_TYPE_SHASH, + .cra_priority = 999, + .cra_blocksize = HASH_BLOCK_BYTES_LENGTH, + .cra_module = THIS_MODULE, + } +}; + +/* SHA224 */ +static int sha224_init(struct shash_desc *desc) +{ + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); + + tf_digest_init_operation(DIGEST_CTRL_ALGO_SHA224, state); + + return 0; +} + +static struct shash_alg smc_sha224_alg = { + .digestsize = HASH_SHA224_LENGTH, + .init = sha224_init, + .update = digest_update, + .final = digest_final, + .export = digest_export, + .import = digest_import, + .descsize = sizeof(struct tf_crypto_sha_operation_state), + .statesize = sizeof(struct tf_crypto_sha_operation_state), + .base = { + .cra_name = "sha224", + .cra_driver_name = "sha224-smc", + .cra_flags = CRYPTO_ALG_TYPE_SHASH, + .cra_priority = 999, + .cra_blocksize = HASH_BLOCK_BYTES_LENGTH, + .cra_module = THIS_MODULE, + } +}; + +/* SHA256 */ +static int sha256_init(struct shash_desc *desc) +{ + struct tf_crypto_sha_operation_state *state = shash_desc_ctx(desc); + + tf_digest_init_operation(DIGEST_CTRL_ALGO_SHA256, state); + + return 0; +} + +static struct shash_alg smc_sha256_alg = { + .digestsize = HASH_SHA256_LENGTH, + .init = sha256_init, + .update = digest_update, + .final = digest_final, + .export = digest_export, + .import = digest_import, + .descsize = sizeof(struct tf_crypto_sha_operation_state), + .statesize = sizeof(struct tf_crypto_sha_operation_state), + .base = { + .cra_name = "sha256", + .cra_driver_name = "sha256-smc", + .cra_flags = CRYPTO_ALG_TYPE_SHASH, + .cra_priority = 999, + .cra_blocksize = HASH_BLOCK_BYTES_LENGTH, + .cra_module = THIS_MODULE, + } +}; + +int register_smc_public_crypto_digest(void) +{ + int ret; + + dprintk(KERN_INFO "SMC: Registering digest algorithms\n"); + + ret = crypto_register_shash(&smc_md5_alg); + if (ret) + return ret; + + ret = crypto_register_shash(&smc_sha1_alg); + if (ret) + goto sha1_err; + + ret = crypto_register_shash(&smc_sha224_alg); + if (ret) + goto sha224_err; + + ret = crypto_register_shash(&smc_sha256_alg); + if (ret) + goto sha256_err; + + return 0; + +sha256_err: + crypto_unregister_shash(&smc_sha224_alg); +sha224_err: + crypto_unregister_shash(&smc_sha1_alg); +sha1_err: + crypto_unregister_shash(&smc_md5_alg); + return ret; +} + +void unregister_smc_public_crypto_digest(void) +{ + dprintk(KERN_INFO "SMC: Unregistering digest algorithms\n"); + + crypto_unregister_shash(&smc_md5_alg); + crypto_unregister_shash(&smc_sha1_alg); + crypto_unregister_shash(&smc_sha224_alg); + crypto_unregister_shash(&smc_sha256_alg); +} +#endif |