/* * Exynos FMP UFS driver for FIPS * * Copyright (C) 2014 Samsung Electronics Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include #include #if defined(CONFIG_UFS_FMP_ECRYPT_FS) #include #include "fmp_derive_iv.h" #endif #if defined(CONFIG_FIPS_FMP) #include "fmpdev_info.h" #endif #include "fmpdev_int.h" //For FIPS_FMP_FUNC_TEST macro extern bool in_fmp_fips_err(void); extern volatile unsigned int disk_key_flag; extern spinlock_t disk_key_lock; extern void exynos_ufs_ctrl_hci_core_clk(struct exynos_ufs *ufs, bool en); #define byte2word(b0, b1, b2, b3) \ ((unsigned int)(b0) << 24) | ((unsigned int)(b1) << 16) | ((unsigned int)(b2) << 8) | (b3) #define word_in(x, c) byte2word(((unsigned char *)(x) + 4 * (c))[0], ((unsigned char *)(x) + 4 * (c))[1], \ ((unsigned char *)(x) + 4 * (c))[2], ((unsigned char *)(x) + 4 * (c))[3]) int fmp_map_sg(struct ufshcd_sg_entry *prd_table, struct scatterlist *sg, uint32_t sector_key, uint32_t idx, uint32_t sector) { #if defined(CONFIG_FIPS_FMP) if (unlikely(in_fmp_fips_err())) { printk(KERN_ERR "Fail to work fmp due to fips in error\n"); return -EPERM; } #endif #if defined(CONFIG_UFS_FMP_DM_CRYPT) /* Disk Encryption */ if (sector_key & UFS_ENCRYPTION_SECTOR_BEGIN) { /* algorithm */ #if defined(CONFIG_FIPS_FMP) if (prd_table[idx].size > 0x1000) { printk(KERN_ERR "Fail to FMP XTS due to invalid size \ for Disk Encrytion. size = 0x%x\n", prd_table[idx].size); return -EINVAL; } #endif SET_DAS(&prd_table[idx], AES_XTS); prd_table[idx].size |= DKL; /* disk IV */ prd_table[idx].disk_iv0 = 0; prd_table[idx].disk_iv1 = 0; prd_table[idx].disk_iv2 = 0; prd_table[idx].disk_iv3 = htonl(sector); if (disk_key_flag) { int ret; if (disk_key_flag == 1) printk(KERN_INFO "FMP disk encryption key is set\n"); else if (disk_key_flag == 2) printk(KERN_INFO "FMP disk encryption key is set after clear\n"); ret = exynos_smc(SMC_CMD_FMP, FMP_KEY_SET, UFS_FMP, 0); if (ret < 0) panic("Fail to load FMP loadable firmware\n"); else if (ret) { printk(KERN_ERR "Fail to smc call for FMP key setting. ret = 0x%x\n", ret); return ret; } spin_lock(&disk_key_lock); disk_key_flag = 0; spin_unlock(&disk_key_lock); } } #endif #if defined(CONFIG_UFS_FMP_ECRYPT_FS) /* File Encryption */ if (sector_key & UFS_FILE_ENCRYPTION_SECTOR_BEGIN) { int ret; unsigned int aes_alg, j; loff_t index; #ifdef CONFIG_CRYPTO_FIPS char extent_iv[SHA256_HASH_SIZE]; #else char extent_iv[MD5_DIGEST_SIZE]; #endif /* algorithm */ if (!strncmp(sg_page(sg)->mapping->alg, "aes", sizeof("aes"))) aes_alg = AES_CBC; else if (!strncmp(sg_page(sg)->mapping->alg, "aesxts", sizeof("aesxts"))) { aes_alg = AES_XTS; #if defined(CONFIG_FIPS_FMP) if (prd_table[idx].size > 0x1000) { printk(KERN_ERR "Fail to FMP XTS due to invalid size \ for File Encrytion. size = 0x%x\n", prd_table[idx].size); return -EINVAL; } #endif } else { printk(KERN_ERR "Invalid file encryption algorithm %s \n", sg_page(sg)->mapping->alg); return -EINVAL; } SET_FAS(&prd_table[idx], aes_alg); /* file encryption key size */ switch (sg_page(sg)->mapping->key_length) { case 16: prd_table[idx].size &= ~FKL; break; case 32: case 64: prd_table[idx].size |= FKL; break; default: printk(KERN_ERR "Invalid file key length %x \n", (unsigned int)sg_page(sg)->mapping->key_length); return -EINVAL; } index = sg_page(sg)->index; index = index - sg_page(sg)->mapping->sensitive_data_index; ret = file_enc_derive_iv(sg_page(sg)->mapping, index, extent_iv); if (ret) { printk(KERN_ERR "Error attemping to derive IV. ret = %c\n", ret); return -EINVAL; } /* File IV */ prd_table[idx].file_iv0 = word_in(extent_iv, 3); prd_table[idx].file_iv1 = word_in(extent_iv, 2); prd_table[idx].file_iv2 = word_in(extent_iv, 1); prd_table[idx].file_iv3 = word_in(extent_iv, 0); /* File Enc key*/ for (j = 0; j < sg_page(sg)->mapping->key_length >> 2; j++) *(&prd_table[idx].file_enckey0 + j) = word_in(sg_page(sg)->mapping->key, (sg_page(sg)->mapping->key_length >> 2) - (j + 1)); } #endif return 0; } EXPORT_SYMBOL_GPL(fmp_map_sg); #if defined(CONFIG_FIPS_FMP) int fmp_map_sg_st(struct ufs_hba *hba, struct ufshcd_sg_entry *prd_table, struct scatterlist *sg, uint32_t sector_key, uint32_t idx, uint32_t sector) { struct ufshcd_sg_entry *prd_table_st = hba->ucd_prdt_ptr_st; if (sector_key == UFS_BYPASS_SECTOR_BEGIN) { SET_FAS(&prd_table[idx], CLEAR); SET_DAS(&prd_table[idx], CLEAR); return 0; } /* algorithm */ if (hba->self_test_mode == XTS_MODE) SET_FAS(&prd_table[idx], AES_XTS); else if (hba->self_test_mode == CBC_MODE) SET_FAS(&prd_table[idx], AES_CBC); else { printk(KERN_ERR "Invalid algorithm for FMP FIPS validation. mode = %d\n", hba->self_test_mode); return -EINVAL; } if (prd_table_st->size == 32) prd_table[idx].size |= FKL; /* File IV */ prd_table[idx].file_iv0 = prd_table_st->file_iv0; prd_table[idx].file_iv1 = prd_table_st->file_iv1; prd_table[idx].file_iv2 = prd_table_st->file_iv2; prd_table[idx].file_iv3 = prd_table_st->file_iv3; /* enc key */ prd_table[idx].file_enckey0 = prd_table_st->file_enckey0; prd_table[idx].file_enckey1 = prd_table_st->file_enckey1; prd_table[idx].file_enckey2 = prd_table_st->file_enckey2; prd_table[idx].file_enckey3 = prd_table_st->file_enckey3; prd_table[idx].file_enckey4 = prd_table_st->file_enckey4; prd_table[idx].file_enckey5 = prd_table_st->file_enckey5; prd_table[idx].file_enckey6 = prd_table_st->file_enckey6; prd_table[idx].file_enckey7 = prd_table_st->file_enckey7; /* tweak key */ prd_table[idx].file_twkey0 = prd_table_st->file_twkey0; prd_table[idx].file_twkey1 = prd_table_st->file_twkey1; prd_table[idx].file_twkey2 = prd_table_st->file_twkey2; prd_table[idx].file_twkey3 = prd_table_st->file_twkey3; prd_table[idx].file_twkey4 = prd_table_st->file_twkey4; prd_table[idx].file_twkey5 = prd_table_st->file_twkey5; prd_table[idx].file_twkey6 = prd_table_st->file_twkey6; prd_table[idx].file_twkey7 = prd_table_st->file_twkey7; return 0; } EXPORT_SYMBOL_GPL(fmp_map_sg_st); #endif #if defined(CONFIG_UFS_FMP_ECRYPT_FS) void fmp_clear_sg(struct ufshcd_lrb *lrbp) { struct scatterlist *sg; int sg_segments; struct ufshcd_sg_entry *prd_table; struct scsi_cmnd *cmd = lrbp->cmd; int i; sg_segments = scsi_sg_count(cmd); if (sg_segments) { prd_table = (struct ufshcd_sg_entry *)lrbp->ucd_prdt_ptr; scsi_for_each_sg(cmd, sg, sg_segments, i) { if (!((unsigned long)(sg_page(sg)->mapping) & 0x1) && \ sg_page(sg)->mapping && sg_page(sg)->mapping->key \ && ((unsigned int)(sg_page(sg)->index) >= 2)) { #if FIPS_FMP_FUNC_TEST == 6 //Key Zeroization print_hex_dump (KERN_ERR, "FIPS FMP descriptor before zeroize:", DUMP_PREFIX_NONE, 16, 1, &prd_table[i].file_iv0, sizeof(__le32)*20, false ); #endif memset(&prd_table[i].file_iv0, 0x0, sizeof(__le32)*20); #if FIPS_FMP_FUNC_TEST == 6 //Key Zeroization print_hex_dump (KERN_ERR, "FIPS FMP descriptor after zeroize:", DUMP_PREFIX_NONE, 16, 1, &prd_table[i].file_iv0, sizeof(__le32)*20, false ); #endif } } } return; } #endif #if defined(CONFIG_UFS_FMP_DM_CRYPT) int fmp_clear_disk_key(void) { struct device_node *dev_node; struct platform_device *pdev; struct device *dev; struct ufs_hba *hba; dev_node = of_find_compatible_node(NULL, NULL, "samsung,exynos-ufs"); if (!dev_node) { printk(KERN_ERR "Fail to find exynos ufs device node\n"); return -ENODEV; } pdev = of_find_device_by_node(dev_node); if (!pdev) { printk(KERN_ERR "Fail to find exynos ufs pdev\n"); return -ENODEV; } dev = &pdev->dev; hba = dev_get_drvdata(dev); if (!hba) { printk(KERN_ERR "Fail to find hba from dev\n"); return -ENODEV; } spin_lock(&disk_key_lock); disk_key_flag = 2; spin_unlock(&disk_key_lock); printk(KERN_INFO "FMP disk encryption key is clear\n"); exynos_ufs_ctrl_hci_core_clk(dev_get_platdata(hba->dev), false); return exynos_smc(SMC_CMD_FMP, FMP_KEY_CLEAR, 0, 0); } #endif