/* * The driver of SUNXI SecuritySystem controller. * * Copyright (C) 2013 Allwinner. * * Mintow * * This file is licensed under the terms of the GNU General Public * License version 2. This program is licensed "as is" without any * warranty of any kind, whether express or implied. */ #include #include #include #include #include #include #include #include "../sunxi_ss.h" #include "../sunxi_ss_proc.h" #include "sunxi_ss_reg.h" /* Callback of DMA completion. */ static void ss_dma_cb(void *data) { ss_aes_req_ctx_t *req_ctx = (ss_aes_req_ctx_t *)data; SS_DBG("DMA transfer data complete!\n"); complete(&req_ctx->done); } /* request dma channel and set callback function */ static int ss_dma_prepare(ss_dma_info_t *info) { dma_cap_mask_t mask; /* Try to acquire a generic DMA engine slave channel */ dma_cap_zero(mask); dma_cap_set(DMA_SG, mask); dma_cap_set(DMA_MEMCPY, mask); info->chan = dma_request_channel(mask, NULL, NULL); if (info->chan == NULL) { SS_ERR("Request DMA() failed!\n"); return -EINVAL; } return 0; } /* set dma start flag, if queue, it will auto restart to transfer next queue */ static void ss_dma_start(ss_dma_info_t *info) { dma_async_issue_pending(info->chan); } /* Make a sg_table based on sg[] of crypto request. */ static int ss_sg_table_init(struct sg_table *sgt, struct scatterlist *sg, int len, char *vbase, dma_addr_t pbase) { int i; int npages = 0; int offset = 0; struct scatterlist *src_sg = sg; struct scatterlist *dst_sg = NULL; npages = ss_sg_cnt(sg, len); WARN_ON(npages == 0); if (sg_alloc_table(sgt, npages, GFP_KERNEL)) { SS_ERR("sg_alloc_table(%d) failed!\n", npages); WARN_ON(1); } dst_sg = sgt->sgl; for (i = 0; i < npages; i++) { sg_set_buf(dst_sg, vbase + offset, sg_dma_len(src_sg)); offset += sg_dma_len(src_sg); src_sg = sg_next(src_sg); dst_sg = sg_next(dst_sg); } return 0; } static int ss_dma_dst_config(sunxi_ss_t *sss, void *ctx, ss_aes_req_ctx_t *req_ctx, int len, int cb) { int flow = ((ss_comm_ctx_t *)ctx)->flow; ss_dma_info_t *info = &req_ctx->dma_dst; struct dma_slave_config dma_conf = {0}; struct dma_async_tx_descriptor *dma_desc = NULL; info->dir = DMA_MEM_TO_MEM; dma_conf.direction = info->dir; #ifdef SS_CTR_MODE_ENABLE if (req_ctx->mode == SS_AES_MODE_CTR) dma_conf.src_addr_width = dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; else #endif dma_conf.src_addr_width = dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; dma_conf.src_maxburst = 1; dma_conf.dst_maxburst = 1; dma_conf.slave_id = sunxi_slave_id(DRQDST_SDRAM, DRQSRC_SDRAM); dmaengine_slave_config(info->chan, &dma_conf); ss_sg_table_init(&info->sgt_for_cp, info->sg, len, sss->flows[flow].buf_dst, sss->flows[flow].buf_dst_dma); info->nents = info->sgt_for_cp.nents; SS_DBG("flow: %d, sg num: %d, total len: %d\n", flow, info->nents, len); dma_map_sg(&sss->pdev->dev, info->sg, info->nents, info->dir); dma_map_sg(&sss->pdev->dev, info->sgt_for_cp.sgl, info->nents, info->dir); dma_desc = info->chan->device->device_prep_dma_sg(info->chan, info->sg, info->nents, info->sgt_for_cp.sgl, info->nents, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!dma_desc) { SS_ERR("dmaengine_prep_slave_sg() failed!\n"); return -1; } if (cb == 1) { dma_desc->callback = ss_dma_cb; dma_desc->callback_param = (void *)req_ctx; } dmaengine_submit(dma_desc); return 0; } static int ss_dma_src_config(sunxi_ss_t *sss, void *ctx, ss_aes_req_ctx_t *req_ctx, int len, int cb) { int flow = ((ss_comm_ctx_t *)ctx)->flow; ss_dma_info_t *info = &req_ctx->dma_src; struct dma_slave_config dma_conf = {0}; struct dma_async_tx_descriptor *dma_desc = NULL; info->dir = DMA_MEM_TO_MEM; dma_conf.direction = info->dir; #ifdef SS_CTR_MODE_ENABLE if (req_ctx->mode == SS_AES_MODE_CTR) dma_conf.src_addr_width = dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; else #endif dma_conf.src_addr_width = dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; dma_conf.src_maxburst = 1; dma_conf.dst_maxburst = 1; dma_conf.slave_id = sunxi_slave_id(DRQDST_SDRAM, DRQSRC_SDRAM); dmaengine_slave_config(info->chan, &dma_conf); ss_sg_table_init(&info->sgt_for_cp, info->sg, len, sss->flows[flow].buf_src, sss->flows[flow].buf_src_dma); SS_DBG("chan: 0x%p, info->sgt_for_cp.sgl: 0x%p\n", info->chan, info->sgt_for_cp.sgl); info->nents = info->sgt_for_cp.nents; SS_DBG("flow: %d, sg num: %d, total len: %d\n", flow, info->nents, len); dma_map_sg(&sss->pdev->dev, info->sg, info->nents, info->dir); dma_map_sg(&sss->pdev->dev, info->sgt_for_cp.sgl, info->nents, info->dir); if (SS_METHOD_IS_HASH(req_ctx->type)) { /* Total len is too small, so there is no data for DMA. */ if (len < SHA1_BLOCK_SIZE) return 1; ss_hash_padding_sg_prepare(&info->sg[info->nents-1], len); ss_hash_padding_sg_prepare(&info->sgt_for_cp.sgl[info->nents-1], len); } dma_desc = info->chan->device->device_prep_dma_sg(info->chan, info->sgt_for_cp.sgl, info->nents, info->sg, info->nents, DMA_PREP_INTERRUPT|DMA_CTRL_ACK); if (!dma_desc) { SS_ERR("dmaengine_prep_slave_sg() failed!\n"); return -1; } if (cb == 1) { dma_desc->callback = ss_dma_cb; dma_desc->callback_param = (void *)req_ctx; } dmaengine_submit(dma_desc); return 0; } static void ss_dma_release(sunxi_ss_t *sss, ss_dma_info_t *info) { dma_unmap_sg(&sss->pdev->dev, info->sgt_for_cp.sgl, info->nents, info->dir); sg_free_table(&info->sgt_for_cp); dma_unmap_sg(&sss->pdev->dev, info->sg, info->nents, info->dir); dma_release_channel(info->chan); } static int ss_aes_start(ss_aes_ctx_t *ctx, ss_aes_req_ctx_t *req_ctx, int len) { int ret = 0; int flow = ctx->comm.flow; ss_pending_clear(flow); ss_irq_enable(flow); ss_flow_enable(flow); ss_method_set(req_ctx->dir, req_ctx->type); ss_aes_mode_set(req_ctx->mode); SS_DBG("Flow: %d, Dir: %d, Method: %d, Mode: %d, len: %d\n", flow, req_ctx->dir, req_ctx->type, req_ctx->mode, len); init_completion(&req_ctx->done); /* 1. Copy data from user space to sss->flows[flow].buf_src. */ if (ss_dma_prepare(&req_ctx->dma_src)) return -EBUSY; #ifdef SS_CTR_MODE_ENABLE if ((req_ctx->mode == SS_AES_MODE_CTR) && ((len%AES_BLOCK_SIZE) != 0)) memset(&ss_dev->flows[flow].buf_src[len], 0, AES_BLOCK_SIZE); #endif ss_dma_src_config(ss_dev, ctx, req_ctx, len, 1); ss_dma_start(&req_ctx->dma_src); ret = wait_for_completion_timeout(&req_ctx->done, msecs_to_jiffies(SS_WAIT_TIME)); if (ret == 0) { SS_ERR("Timed out\n"); return -ETIMEDOUT; } /* 2. Start the SS. */ ss_data_src_set(ss_dev->flows[flow].buf_src_dma); ss_data_dst_set(ss_dev->flows[flow].buf_dst_dma); SS_DBG("ss_dev->buf_dst_dma = %#x\n", ss_dev->flows[flow].buf_dst_dma); #ifdef SS_CTS_MODE_ENABLE if (req_ctx->mode == SS_AES_MODE_CTS) { ss_data_len_set(len); /* A bad way to determin the last packet of CTS mode. */ /* if (len < SZ_4K) */ ss_cts_last(); } else #endif ss_data_len_set(DIV_ROUND_UP(len, AES_BLOCK_SIZE)*4); ss_ctrl_start(); ret = wait_for_completion_timeout(&ss_dev->flows[flow].done, msecs_to_jiffies(SS_WAIT_TIME)); if (ret == 0) { SS_ERR("Timed out\n"); ss_reset(); return -ETIMEDOUT; } /* 3. Copy the result from sss->flows[flow].buf_dst to user space. */ if (ss_dma_prepare(&req_ctx->dma_dst)) return -EBUSY; ss_dma_dst_config(ss_dev, ctx, req_ctx, len, 1); ss_dma_start(&req_ctx->dma_dst); ret = wait_for_completion_timeout(&req_ctx->done, msecs_to_jiffies(SS_WAIT_TIME)); if (ret == 0) { SS_ERR("Timed out\n"); return -ETIMEDOUT; } ss_ctrl_stop(); ss_irq_disable(flow); ss_dma_release(ss_dev, &req_ctx->dma_src); ss_dma_release(ss_dev, &req_ctx->dma_dst); return 0; } int ss_aes_key_valid(struct crypto_ablkcipher *tfm, int len) { if (unlikely(len > SS_RSA_MAX_SIZE)) { SS_ERR("Unsupported key size: %d\n", len); tfm->base.crt_flags |= CRYPTO_TFM_RES_BAD_KEY_LEN; return -EINVAL; } return 0; } static int ss_rng_start(ss_aes_ctx_t *ctx, u8 *rdata, u32 dlen, u32 trng) { int ret = 0; int flow = ctx->comm.flow; u32 len = dlen; if (len > SS_RNG_MAX_LEN) { SS_ERR("The RNG length is too large: %d\n", len); len = SS_RNG_MAX_LEN; } ss_pending_clear(flow); ss_irq_enable(flow); ss_flow_enable(flow); if (trng) { ss_method_set(SS_DIR_ENCRYPT, SS_METHOD_TRNG); ss_trng_osc_enable(); } else ss_method_set(SS_DIR_ENCRYPT, SS_METHOD_PRNG); ss_rng_mode_set(SS_RNG_MODE_CONTINUE); ss_data_dst_set(ss_dev->flows[flow].buf_dst_dma); if (trng) /* align with 32 Bytes */ ss_data_len_set(DIV_ROUND_UP(len, 32)*(32>>2)); else /* align with 20 Bytes */ ss_data_len_set(DIV_ROUND_UP(len, 20)*(20>>2)); SS_DBG("Flow: %d, Request: %d, Aligned: %d\n", flow, len, DIV_ROUND_UP(len, 20)*5); dma_map_single(&ss_dev->pdev->dev, ss_dev->flows[flow].buf_dst, SS_DMA_BUF_SIZE, DMA_DEV_TO_MEM); ss_ctrl_start(); ret = wait_for_completion_timeout(&ss_dev->flows[flow].done, msecs_to_jiffies(SS_WAIT_TIME)); if (ret == 0) { SS_ERR("Timed out\n"); ss_reset(); return -ETIMEDOUT; } dma_unmap_single(&ss_dev->pdev->dev, ss_dev->flows[flow].buf_dst_dma, SS_DMA_BUF_SIZE, DMA_DEV_TO_MEM); memcpy(rdata, ss_dev->flows[flow].buf_dst, len); if (trng) ss_trng_osc_disable(); ss_irq_disable(flow); ss_ctrl_stop(); ret = len; return ret; } int ss_rng_get_random(struct crypto_rng *tfm, u8 *rdata, u32 dlen, u32 trng) { int ret = 0; u8 *data = rdata; u32 len = dlen; ss_aes_ctx_t *ctx = crypto_rng_ctx(tfm); SS_DBG("flow = %d, data = %p, len = %d, trng = %d\n", ctx->comm.flow, data, len, trng); if (ss_dev->suspend) { SS_ERR("SS has already suspend.\n"); return -EAGAIN; } #ifdef SS_TRNG_POSTPROCESS_ENABLE if (trng) { len = DIV_ROUND_UP(dlen, SHA256_DIGEST_SIZE)*SHA256_BLOCK_SIZE; data = kzalloc(len, GFP_KERNEL); if (data == NULL) { SS_ERR("Failed to malloc(%d)\n", len); return -ENOMEM; } SS_DBG("In fact, flow = %d, data = %p, len = %d\n", ctx->comm.flow, data, len); } #endif ss_dev_lock(); /* Must set the seed addr in PRNG/TRNG. */ ss_key_set(ctx->key, ctx->key_size); ctx->comm.flags &= ~SS_FLAG_NEW_KEY; dma_map_single(&ss_dev->pdev->dev, ctx->key, ctx->key_size, DMA_MEM_TO_DEV); ret = ss_rng_start(ctx, data, len, trng); ss_dev_unlock(); SS_DBG("Get %d byte random.\n", ret); dma_unmap_single(&ss_dev->pdev->dev, virt_to_phys(ctx->key), ctx->key_size, DMA_MEM_TO_DEV); #ifdef SS_TRNG_POSTPROCESS_ENABLE if (trng) { ss_trng_postprocess(rdata, dlen, data, len); ret = dlen; kfree(data); } #endif return ret; } u32 ss_hash_start(ss_hash_ctx_t *ctx, ss_aes_req_ctx_t *req_ctx, u32 len, u32 last) { int ret = 0; int flow = ctx->comm.flow; int md_map_flag = 0; ss_pending_clear(flow); ss_irq_enable(flow); ss_flow_enable(flow); ss_method_set(req_ctx->dir, req_ctx->type); SS_DBG("Flow: %d, Dir: %d, Method: %d, Mode: %d, len: %d/%d\n", flow, req_ctx->dir, req_ctx->type, req_ctx->mode, len, ctx->cnt); SS_DBG("IV address = 0x%p, size = %d\n", ctx->md, ctx->md_size); ss_iv_set(ctx->md, ctx->md_size); ss_iv_mode_set(SS_IV_MODE_ARBITRARY); init_completion(&req_ctx->done); if (ss_dma_prepare(&req_ctx->dma_src)) return -EBUSY; ret = ss_dma_src_config(ss_dev, ctx, req_ctx, len, 1); if (ret == 0) { /* 1. Copy data from user space to sss->flows[flow].buf_src. */ ss_dma_start(&req_ctx->dma_src); ret = wait_for_completion_timeout(&req_ctx->done, msecs_to_jiffies(SS_WAIT_TIME)); if (ret == 0) { SS_ERR("Timed out\n"); return -ETIMEDOUT; } /* 2. Start the SS. */ ss_data_src_set(ss_dev->flows[flow].buf_src_dma); ss_data_dst_set(ss_dev->flows[flow].buf_dst_dma); SS_DBG("buf_dst_dma: %#x\n", ss_dev->flows[flow].buf_dst_dma); ss_data_len_set((len - len%SHA1_BLOCK_SIZE)/4); #ifdef SS_SHA_SWAP_MID_ENABLE if (req_ctx->type != SS_METHOD_MD5) ss_hash_swap(ctx->md, ctx->md_size); #endif dma_map_single(&ss_dev->pdev->dev, ctx->md, ctx->md_size, DMA_MEM_TO_DEV); md_map_flag = 1; SS_DBG("Before SS, CTRL: 0x%08x\n", ss_reg_rd(SS_REG_CTL)); memset(ss_dev->flows[flow].buf_dst, 0xF0, ctx->md_size); dma_map_single(&ss_dev->pdev->dev, ss_dev->flows[flow].buf_dst, ctx->md_size, DMA_DEV_TO_MEM); ss_ctrl_start(); ret = wait_for_completion_timeout(&ss_dev->flows[flow].done, msecs_to_jiffies(SS_WAIT_TIME)); if (ret == 0) { SS_ERR("Timed out\n"); ss_reset(); return -ETIMEDOUT; } SS_DBG("After SS, CTRL: 0x%08x\n", ss_reg_rd(SS_REG_CTL)); SS_DBG("After SS, dst data:\n"); ss_print_hex(ss_dev->flows[flow].buf_dst, 32, ss_dev->flows[flow].buf_dst); /* 3. Copy the MD from sss->buf_dst to ctx->md. */ memcpy(ctx->md, ss_dev->flows[flow].buf_dst, ctx->md_size); } ss_ctrl_stop(); ss_irq_disable(flow); if (md_map_flag == 1) { dma_unmap_single(&ss_dev->pdev->dev, ss_dev->flows[flow].buf_dst_dma, ctx->md_size, DMA_DEV_TO_MEM); dma_unmap_single(&ss_dev->pdev->dev, virt_to_phys(ctx->md), ctx->md_size, DMA_MEM_TO_DEV); } ss_dma_release(ss_dev, &req_ctx->dma_src); ctx->cnt += len; return 0; } int ss_aes_one_req(sunxi_ss_t *sss, struct ablkcipher_request *req) { int ret = 0; struct crypto_ablkcipher *tfm = NULL; ss_aes_ctx_t *ctx = NULL; ss_aes_req_ctx_t *req_ctx = NULL; int key_map_flag = 0; int iv_map_flag = 0; SS_ENTER(); if (!req->src || !req->dst) { SS_ERR("Invalid sg: src = %p, dst = %p\n", req->src, req->dst); return -EINVAL; } ss_dev_lock(); tfm = crypto_ablkcipher_reqtfm(req); req_ctx = ablkcipher_request_ctx(req); ctx = crypto_ablkcipher_ctx(tfm); /* A31 SS need update key each cycle in decryption. */ if ((ctx->comm.flags & SS_FLAG_NEW_KEY) || (req_ctx->dir == SS_DIR_DECRYPT)) { SS_DBG("KEY address: %p, size: %d\n", ctx->key, ctx->key_size); ss_key_set(ctx->key, ctx->key_size); dma_map_single(&sss->pdev->dev, ctx->key, ctx->key_size, DMA_MEM_TO_DEV); key_map_flag = 1; ctx->comm.flags &= ~SS_FLAG_NEW_KEY; } #ifdef SS_CTS_MODE_ENABLE if (((req_ctx->mode == SS_AES_MODE_CBC) || (req_ctx->mode == SS_AES_MODE_CTS)) && (req->info != NULL)) { #else if ((req_ctx->mode == SS_AES_MODE_CBC) && (req->info != NULL)) { #endif SS_DBG("IV address = %p, size = %d\n", req->info, crypto_ablkcipher_ivsize(tfm)); memcpy(ctx->iv, req->info, crypto_ablkcipher_ivsize(tfm)); ss_iv_set(ctx->iv, crypto_ablkcipher_ivsize(tfm)); dma_map_single(&sss->pdev->dev, ctx->iv, crypto_ablkcipher_ivsize(tfm), DMA_MEM_TO_DEV); iv_map_flag = 1; } #ifdef SS_CTR_MODE_ENABLE if (req_ctx->mode == SS_AES_MODE_CTR) { SS_DBG("Cnt address = %p, size = %d\n", req->info, crypto_ablkcipher_ivsize(tfm)); if (ctx->cnt == 0) memcpy(ctx->iv, req->info, crypto_ablkcipher_ivsize(tfm)); SS_DBG("CNT: %08x %08x %08x %08x\n", *(int *)&ctx->iv[0], *(int *)&ctx->iv[4], *(int *)&ctx->iv[8], *(int *)&ctx->iv[12]); ss_cnt_set(ctx->iv, crypto_ablkcipher_ivsize(tfm)); dma_map_single(&sss->pdev->dev, ctx->iv, crypto_ablkcipher_ivsize(tfm), DMA_MEM_TO_DEV); iv_map_flag = 1; } #endif SS_DBG("The current IV:\n"); ss_print_hex(ctx->iv, 16, ctx->iv); if (req_ctx->type == SS_METHOD_RSA) ss_rsa_width_set(crypto_ablkcipher_ivsize(tfm)); req_ctx->dma_src.sg = req->src; req_ctx->dma_dst.sg = req->dst; ret = ss_aes_start(ctx, req_ctx, req->nbytes); if (ret < 0) SS_ERR("ss_aes_start fail(%d)\n", ret); ss_dev_unlock(); if (key_map_flag == 1) dma_unmap_single(&ss_dev->pdev->dev, virt_to_phys(ctx->key), ctx->key_size, DMA_MEM_TO_DEV); if (iv_map_flag == 1) dma_unmap_single(&sss->pdev->dev, virt_to_phys(ctx->iv), crypto_ablkcipher_ivsize(tfm), DMA_MEM_TO_DEV); #ifdef SS_CTR_MODE_ENABLE if (req_ctx->mode == SS_AES_MODE_CTR) { ss_cnt_get(ctx->comm.flow, ctx->iv, crypto_ablkcipher_ivsize(tfm)); SS_DBG("CNT: %08x %08x %08x %08x\n", *(int *)&ctx->iv[0], *(int *)&ctx->iv[4], *(int *)&ctx->iv[8], *(int *)&ctx->iv[12]); } #endif ctx->cnt += req->nbytes; return ret; } irqreturn_t sunxi_ss_irq_handler(int irq, void *dev_id) { sunxi_ss_t *sss = (sunxi_ss_t *)dev_id; unsigned long flags = 0; int pending = 0; spin_lock_irqsave(&sss->lock, flags); pending = ss_pending_get(); SS_DBG("SS pending %#x\n", pending); if (pending&SS_REG_ICR_FLOW0_PENDING_MASK) { ss_pending_clear(0); complete(&sss->flows[0].done); } if (pending&SS_REG_ICR_FLOW1_PENDING_MASK) { ss_pending_clear(1); complete(&sss->flows[1].done); } spin_unlock_irqrestore(&sss->lock, flags); SS_DBG("SS pending %#x, CTRL: 0x%08x\n", ss_pending_get(), ss_reg_rd(SS_REG_CTL)); return IRQ_HANDLED; }