| /* |
| * Copyright (C) 2016 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 <linux/module.h> |
| #include <linux/of.h> |
| #include <crypto/fmp.h> |
| |
| #include "ufs-exynos-fmp.h" |
| |
| static int check_data_equal(void *data1, void *data2) |
| { |
| return data1 == data2; |
| } |
| |
| static int is_valid_bio_data(struct bio *bio) |
| { |
| if (bio->private_enc_mode < 0 || |
| bio->private_enc_mode > EXYNOS_FMP_FILE_ENC) |
| return false; |
| |
| if (bio->private_algo_mode < 0 || |
| bio->private_algo_mode > EXYNOS_FMP_ALGO_MODE_AES_XTS) |
| return false; |
| |
| return true; |
| } |
| |
| static int exynos_ufs_fmp_key_size_cfg(struct fmp_crypto_setting *crypto, |
| uint32_t size) |
| { |
| int ret = 0; |
| |
| if (!crypto || !size) { |
| pr_err("%s: Invalid fmp data or size.\n", __func__); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (crypto->algo_mode == EXYNOS_FMP_ALGO_MODE_AES_XTS) |
| size = size >> 1; |
| |
| switch (size) { |
| case FMP_KEY_SIZE_16: |
| crypto->key_size = EXYNOS_FMP_KEY_SIZE_16; |
| break; |
| case FMP_KEY_SIZE_32: |
| crypto->key_size = EXYNOS_FMP_KEY_SIZE_32; |
| break; |
| default: |
| pr_err("%s: FMP doesn't support key size %d\n", __func__, size); |
| ret = -EINVAL; |
| goto out; |
| } |
| out: |
| return ret; |
| } |
| |
| static int exynos_ufs_fmp_iv_cfg(struct fmp_crypto_setting *crypto, |
| sector_t sector, pgoff_t page_index, |
| int sector_offset) |
| { |
| int ret = 0; |
| |
| if (!crypto) { |
| pr_err("%s: Invalid fmp data\n", __func__); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| crypto->index = page_index; |
| crypto->sector = sector + (sector_t)sector_offset; |
| out: |
| return ret; |
| } |
| |
| static int exynos_ufs_fmp_key_cfg(struct fmp_crypto_setting *crypto, |
| unsigned char *key, |
| unsigned long key_length) |
| { |
| int ret = 0; |
| |
| if (!crypto) { |
| pr_err("%s: Invalid fmp data\n", __func__); |
| ret = -EINVAL; |
| goto out; |
| } |
| memset(crypto->key, 0, FMP_MAX_KEY_SIZE); |
| memcpy(crypto->key, key, key_length); |
| out: |
| return ret; |
| } |
| |
| static int exynos_ufs_fmp_disk_cfg(struct scsi_cmnd *cmd, |
| struct fmp_crypto_setting *crypto, |
| int sector_offset) |
| { |
| int ret = 0; |
| struct bio *bio = cmd->request->bio; |
| |
| if (!crypto) { |
| pr_err("%s: Invalid fmp data\n", __func__); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| memset(crypto, 0, sizeof(struct fmp_crypto_setting)); |
| |
| crypto->enc_mode = EXYNOS_FMP_DISK_ENC; |
| |
| if (!is_valid_bio_data(bio)) |
| goto bypass_out; |
| |
| if (!bio) |
| goto bypass_out; |
| |
| if ((bio->private_algo_mode == EXYNOS_FMP_BYPASS_MODE) || |
| /* direct IO case */ |
| (bio->private_enc_mode == EXYNOS_FMP_FILE_ENC)) |
| goto bypass_out; |
| |
| if (!is_valid_bio_data(bio)) |
| goto bypass_out; |
| |
| if (!bio->key) { |
| pr_err("%s: Invalid disk key\n", __func__); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| ret = exynos_ufs_fmp_key_size_cfg(crypto, sizeof(bio->key)); |
| if (ret) |
| goto bypass_out; |
| |
| ret = exynos_ufs_fmp_iv_cfg(crypto, bio->bi_iter.bi_sector, 0, |
| sector_offset); |
| if (ret) { |
| pr_err("%s: Fail to configure fmp iv. ret(%d)\n", |
| __func__, ret); |
| ret = -EINVAL; |
| goto out; |
| } |
| out: |
| return ret; |
| bypass_out: |
| crypto->algo_mode = EXYNOS_FMP_BYPASS_MODE; |
| ret = 0; |
| return ret; |
| } |
| |
| static int exynos_ufs_fmp_direct_io_cfg(struct scsi_cmnd *cmd, |
| struct fmp_crypto_setting *crypto, |
| int sector_offset) |
| { |
| int ret = 0; |
| struct bio *bio = cmd->request->bio; |
| |
| if (!crypto) { |
| pr_err("%s: Invalid fmp data\n", __func__); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| memset(crypto, 0, sizeof(struct fmp_crypto_setting)); |
| |
| crypto->enc_mode = EXYNOS_FMP_FILE_ENC; |
| |
| if (!bio || (bio->private_algo_mode == EXYNOS_FMP_BYPASS_MODE)) |
| goto bypass_out; |
| |
| if (!is_valid_bio_data(bio)) |
| goto bypass_out; |
| |
| crypto->algo_mode = bio->private_algo_mode; |
| ret = exynos_ufs_fmp_key_size_cfg(crypto, sizeof(bio->key)); |
| if (ret) |
| goto bypass_out; |
| |
| ret = exynos_ufs_fmp_iv_cfg(crypto, bio->bi_iter.bi_sector, 0, |
| sector_offset); |
| if (ret) { |
| pr_err("%s: Fail to configure fmp iv. ret(%d)\n", |
| __func__, ret); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| ret = exynos_ufs_fmp_key_cfg(crypto, bio->key, bio->key_length); |
| if (ret) { |
| pr_err("%s: Fail to configure fmp key. ret(%d)\n", |
| __func__, ret); |
| ret = -EINVAL; |
| goto out; |
| } |
| out: |
| return ret; |
| bypass_out: |
| crypto->algo_mode = EXYNOS_FMP_BYPASS_MODE; |
| ret = 0; |
| return ret; |
| } |
| |
| static int exynos_ufs_fmp_file_cfg(struct scsi_cmnd *cmd, |
| struct page *page, |
| struct fmp_crypto_setting *crypto, |
| int sector_offset) |
| { |
| int ret = 0; |
| struct bio *bio = cmd->request->bio; |
| pgoff_t page_index; |
| |
| if (!crypto) { |
| pr_err("%s: Invalid fmp data\n", __func__); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| memset(crypto, 0, sizeof(struct fmp_crypto_setting)); |
| |
| crypto->enc_mode = EXYNOS_FMP_FILE_ENC; |
| |
| if (!page || PageAnon(page)) |
| goto bypass_out; |
| |
| if (!page->mapping || page->mapping->private_algo_mode == EXYNOS_FMP_BYPASS_MODE) |
| goto bypass_out; |
| |
| if (!bio) |
| goto bypass_out; |
| |
| crypto->algo_mode = page->mapping->private_algo_mode; |
| ret = exynos_ufs_fmp_key_size_cfg(crypto, page->mapping->key_length); |
| if (ret) |
| goto bypass_out; |
| |
| page_index = page->index - page->mapping->sensitive_data_index; |
| ret = exynos_ufs_fmp_iv_cfg(crypto, bio->bi_iter.bi_sector, page_index, |
| sector_offset); |
| if (ret) { |
| pr_err("%s: Fail to configure fmp iv. ret(%d)\n", |
| __func__, ret); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| ret = exynos_ufs_fmp_key_cfg(crypto, page->mapping->key, |
| page->mapping->key_length); |
| if (ret) { |
| pr_err("%s: Fail to configure fmp key. ret(%d)\n", |
| __func__, ret); |
| ret = -EINVAL; |
| goto out; |
| } |
| out: |
| return ret; |
| bypass_out: |
| crypto->algo_mode = EXYNOS_FMP_BYPASS_MODE; |
| ret = 0; |
| return ret; |
| } |
| |
| int exynos_ufs_fmp_host_set_device(struct platform_device *host_pdev, |
| struct platform_device *pdev, |
| struct exynos_fmp_variant_ops *fmp_vops) |
| { |
| struct exynos_ufs *ufs; |
| |
| if (!host_pdev || !pdev || !fmp_vops) { |
| pr_err("%s: Fail to set device for fmp host\n", __func__); |
| return -EINVAL; |
| } |
| |
| ufs = dev_get_platdata(&host_pdev->dev); |
| ufs->fmp.pdev = pdev; |
| ufs->fmp.vops = fmp_vops; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(exynos_ufs_fmp_host_set_device); |
| |
| static int is_ufs_fmp_test_enabled(struct scsi_cmnd *cmd, |
| struct platform_device *pdev) |
| { |
| struct bio *bio = cmd->request->bio; |
| struct exynos_fmp *fmp = dev_get_drvdata(&pdev->dev); |
| |
| if (!fmp) |
| return FALSE; |
| |
| if (!bio) { |
| fmp->test_mode = 0; |
| return FALSE; |
| } |
| |
| if (check_data_equal((void *)bio->bi_private, (void *)fmp->test_bh) |
| && (uint64_t)fmp->test_bh) { |
| fmp->test_mode = 1; |
| return TRUE; |
| } |
| |
| fmp->test_mode = 0; |
| return FALSE; |
| } |
| |
| static inline void exynos_ufs_fmp_bypass(void *desc) |
| { |
| SET_DAS((struct fmp_table_setting *)desc, 0); |
| SET_FAS((struct fmp_table_setting *)desc, 0); |
| } |
| |
| int exynos_ufs_fmp_cfg(struct ufs_hba *hba, |
| struct ufshcd_lrb *lrbp, |
| struct scatterlist *sg, |
| uint32_t index, |
| int sector_offset) |
| { |
| int ret; |
| struct fmp_data_setting data; |
| struct scsi_cmnd *cmd; |
| struct page *page; |
| struct exynos_ufs *ufs = dev_get_platdata(hba->dev); |
| |
| if (!ufs->fmp.pdev || !lrbp->cmd) { |
| exynos_ufs_fmp_bypass(&lrbp->ucd_prdt_ptr[index]); |
| return 0; |
| } |
| |
| cmd = lrbp->cmd; |
| |
| ret = is_ufs_fmp_test_enabled(cmd, ufs->fmp.pdev); |
| if (ret == TRUE) |
| goto out; |
| |
| ret = exynos_ufs_fmp_disk_cfg(cmd, &data.disk, sector_offset); |
| if (ret) { |
| pr_err("%s: Fail to configure FMP Disk Encryption. ret(%d)\n", |
| __func__, ret); |
| return -EINVAL; |
| } |
| |
| if (data.disk.algo_mode != EXYNOS_FMP_BYPASS_MODE) |
| goto file_cfg; |
| |
| ret = exynos_ufs_fmp_direct_io_cfg(cmd, &data.file, sector_offset); |
| if (ret) { |
| pr_err("%s: Fail to configure FMP direct IO Encryption. ret(%d)\n", |
| __func__, ret); |
| return -EINVAL; |
| } |
| |
| if (data.file.algo_mode != EXYNOS_FMP_BYPASS_MODE) |
| goto out; |
| |
| file_cfg: |
| page = sg_page(sg); |
| ret = exynos_ufs_fmp_file_cfg(cmd, page, &data.file, sector_offset); |
| if (ret) { |
| pr_err("%s: Fail to configure FMP File Encryption. ret(%d)\n", |
| __func__, ret); |
| return -EINVAL; |
| } |
| |
| out: |
| data.table = (struct fmp_table_setting *)&lrbp->ucd_prdt_ptr[index]; |
| data.mapping = page->mapping; |
| return ufs->fmp.vops->config(ufs->fmp.pdev, &data); |
| } |
| EXPORT_SYMBOL(exynos_ufs_fmp_cfg); |
| |
| int exynos_ufs_fmp_clear(struct ufs_hba *hba, |
| struct ufshcd_lrb *lrbp) |
| { |
| int ret = 0; |
| int sg_segments, idx; |
| struct scatterlist *sg; |
| struct exynos_ufs *ufs = dev_get_platdata(hba->dev); |
| struct ufshcd_sg_entry *prd_table; |
| struct fmp_data_setting data; |
| |
| if (ufs->fmp.pdev || !lrbp->cmd) |
| goto out; |
| |
| sg_segments = scsi_sg_count(lrbp->cmd); |
| if (!sg_segments) |
| goto out; |
| |
| prd_table = (struct ufshcd_sg_entry *)lrbp->ucd_prdt_ptr; |
| scsi_for_each_sg(lrbp->cmd, sg, sg_segments, idx) { |
| data.table = (struct fmp_table_setting *)&prd_table[idx]; |
| |
| if (!GET_FAS(data.table)) |
| continue; |
| |
| ret = ufs->fmp.vops->clear(ufs->fmp.pdev, &data); |
| if (ret) { |
| pr_err("%s: Fail to clear FMP desc (%d)\n", |
| __func__, ret); |
| ret = -EINVAL; |
| goto out; |
| } |
| } |
| out: |
| return ret; |
| } |