| // SPDX-License-Identifier: BSD-2-Clause |
| /* |
| * Copyright (c) 2018 Linaro Limited |
| */ |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <log.h> |
| #include <tee.h> |
| #include <mmc.h> |
| |
| #include "optee_msg.h" |
| #include "optee_private.h" |
| |
| /* |
| * Request and response definitions must be in sync with the secure side of |
| * OP-TEE. |
| */ |
| |
| /* Request */ |
| struct rpmb_req { |
| u16 cmd; |
| #define RPMB_CMD_DATA_REQ 0x00 |
| #define RPMB_CMD_GET_DEV_INFO 0x01 |
| u16 dev_id; |
| u16 block_count; |
| /* Optional data frames (rpmb_data_frame) follow */ |
| }; |
| |
| #define RPMB_REQ_DATA(req) ((void *)((struct rpmb_req *)(req) + 1)) |
| |
| /* Response to device info request */ |
| struct rpmb_dev_info { |
| u8 cid[16]; |
| u8 rpmb_size_mult; /* EXT CSD-slice 168: RPMB Size */ |
| u8 rel_wr_sec_c; /* EXT CSD-slice 222: Reliable Write Sector */ |
| /* Count */ |
| u8 ret_code; |
| #define RPMB_CMD_GET_DEV_INFO_RET_OK 0x00 |
| #define RPMB_CMD_GET_DEV_INFO_RET_ERROR 0x01 |
| }; |
| |
| static void release_mmc(struct optee_private *priv) |
| { |
| int rc; |
| |
| if (!priv->rpmb_mmc) |
| return; |
| |
| rc = blk_select_hwpart_devnum(IF_TYPE_MMC, priv->rpmb_dev_id, |
| priv->rpmb_original_part); |
| if (rc) |
| debug("%s: blk_select_hwpart_devnum() failed: %d\n", |
| __func__, rc); |
| |
| priv->rpmb_mmc = NULL; |
| } |
| |
| static struct mmc *get_mmc(struct optee_private *priv, int dev_id) |
| { |
| struct mmc *mmc; |
| int rc; |
| |
| if (priv->rpmb_mmc && priv->rpmb_dev_id == dev_id) |
| return priv->rpmb_mmc; |
| |
| release_mmc(priv); |
| |
| mmc = find_mmc_device(dev_id); |
| if (!mmc) { |
| debug("Cannot find RPMB device\n"); |
| return NULL; |
| } |
| if (!(mmc->version & MMC_VERSION_MMC)) { |
| debug("Device id %d is not an eMMC device\n", dev_id); |
| return NULL; |
| } |
| if (mmc->version < MMC_VERSION_4_41) { |
| debug("Device id %d: RPMB not supported before version 4.41\n", |
| dev_id); |
| return NULL; |
| } |
| |
| priv->rpmb_original_part = mmc_get_blk_desc(mmc)->hwpart; |
| |
| rc = blk_select_hwpart_devnum(IF_TYPE_MMC, dev_id, MMC_PART_RPMB); |
| if (rc) { |
| debug("Device id %d: cannot select RPMB partition: %d\n", |
| dev_id, rc); |
| return NULL; |
| } |
| |
| priv->rpmb_mmc = mmc; |
| priv->rpmb_dev_id = dev_id; |
| return mmc; |
| } |
| |
| static u32 rpmb_get_dev_info(u16 dev_id, struct rpmb_dev_info *info) |
| { |
| struct mmc *mmc = find_mmc_device(dev_id); |
| |
| if (!mmc) |
| return TEE_ERROR_ITEM_NOT_FOUND; |
| |
| if (!mmc->ext_csd) |
| return TEE_ERROR_GENERIC; |
| |
| /* |
| * Reverse each 4 bytes of the 16 bytes CID array, as expected over at |
| * the OP-TEE side in order to derive the correct RPMB key. |
| * |
| * As it turns out, the swaped array also matches the definition of JEDEC |
| * spec, at least for the eMMC part used on newman, which means the original |
| * bytes order from mmc driver is off. |
| * |
| * This is all admittedly very confusing and there is no reliable way to |
| * verify which ordering is correct for a given part. The best we can do |
| * is probably to manually dump out the CID and compare against the spec. |
| */ |
| u8 *next = info->cid; |
| for (int i = 0; i < ARRAY_SIZE(mmc->cid); i++) { |
| *(next++) = mmc->cid[i] >> 24; |
| *(next++) = mmc->cid[i] >> 16; |
| *(next++) = mmc->cid[i] >> 8; |
| *(next++) = mmc->cid[i]; |
| } |
| |
| info->rel_wr_sec_c = mmc->ext_csd[222]; |
| info->rpmb_size_mult = mmc->ext_csd[168]; |
| info->ret_code = RPMB_CMD_GET_DEV_INFO_RET_OK; |
| |
| return TEE_SUCCESS; |
| } |
| |
| static u32 rpmb_process_request(struct optee_private *priv, void *req, |
| ulong req_size, void *rsp, ulong rsp_size) |
| { |
| struct rpmb_req *sreq = req; |
| struct mmc *mmc; |
| |
| if (req_size < sizeof(*sreq)) |
| return TEE_ERROR_BAD_PARAMETERS; |
| |
| switch (sreq->cmd) { |
| case RPMB_CMD_DATA_REQ: |
| mmc = get_mmc(priv, sreq->dev_id); |
| if (!mmc) |
| return TEE_ERROR_ITEM_NOT_FOUND; |
| if (mmc_rpmb_route_frames(mmc, RPMB_REQ_DATA(req), |
| req_size - sizeof(struct rpmb_req), |
| rsp, rsp_size)) |
| return TEE_ERROR_BAD_PARAMETERS; |
| return TEE_SUCCESS; |
| |
| case RPMB_CMD_GET_DEV_INFO: |
| if (req_size != sizeof(struct rpmb_req) || |
| rsp_size != sizeof(struct rpmb_dev_info)) { |
| debug("Invalid req/rsp size\n"); |
| return TEE_ERROR_BAD_PARAMETERS; |
| } |
| return rpmb_get_dev_info(sreq->dev_id, rsp); |
| |
| default: |
| debug("Unsupported RPMB command: %d\n", sreq->cmd); |
| return TEE_ERROR_BAD_PARAMETERS; |
| } |
| } |
| |
| void optee_suppl_cmd_rpmb(struct udevice *dev, struct optee_msg_arg *arg) |
| { |
| void *req_buf; |
| void *rsp_buf; |
| ulong req_size; |
| ulong rsp_size; |
| |
| if (arg->num_params != 2 || |
| arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_TMEM_INPUT || |
| arg->params[1].attr != OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT) { |
| arg->ret = TEE_ERROR_BAD_PARAMETERS; |
| return; |
| } |
| |
| req_buf = (u8 *)arg->params[0].u.tmem.buf_ptr; |
| req_size = arg->params[0].u.tmem.size; |
| |
| rsp_buf = (u8 *)arg->params[1].u.tmem.buf_ptr; |
| rsp_size = arg->params[1].u.tmem.size; |
| |
| arg->ret = rpmb_process_request(dev_get_priv(dev), req_buf, req_size, |
| rsp_buf, rsp_size); |
| } |
| |
| void optee_suppl_rpmb_release(struct udevice *dev) |
| { |
| release_mmc(dev_get_priv(dev)); |
| } |