| /* Copyright (c) 2013-2014, The Linux Foundation. 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 and |
| * only 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. |
| */ |
| /* |
| * Qualcomm Secure Service Module(SSM) driver |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/fs.h> |
| #include <linux/of.h> |
| #include <linux/cdev.h> |
| #include <linux/uaccess.h> |
| #include <linux/mutex.h> |
| #include <linux/ion.h> |
| #include <linux/types.h> |
| #include <linux/elf.h> |
| #include <linux/platform_device.h> |
| #include <linux/msm_ion.h> |
| #include <linux/platform_data/qcom_ssm.h> |
| #include <soc/qcom/scm.h> |
| #include <soc/qcom/smd.h> |
| |
| #include "qseecom_kernel.h" |
| #include "ssm.h" |
| |
| /* Macros */ |
| #define SSM_DEV_NAME "ssm" |
| #define MPSS_SUBSYS 0 |
| #define SSM_INFO_CMD_ID 1 |
| #define MAX_APP_NAME_SIZE 32 |
| #define SSM_MSG_LEN 200 |
| #define SSM_MSG_FIELD_LEN 11 |
| #define ATOM_MSG_LEN (SSM_MSG_FIELD_LEN + SSM_MSG_LEN + 40) |
| |
| #define TZAPP_NAME "SsmApp" |
| #define CHANNEL_NAME "SSM_RTR_MODEM_APPS" |
| |
| /* SSM driver structure.*/ |
| struct ssm_driver { |
| int32_t app_status; |
| int32_t update_status; |
| unsigned char *channel_name; |
| unsigned char *smd_buffer; |
| struct device *dev; |
| smd_channel_t *ch; |
| struct work_struct ipc_work; |
| struct mutex mutex; |
| struct qseecom_handle *qseecom_handle; |
| struct tzapp_get_mode_info_rsp *resp; |
| bool key_status; |
| bool ready; |
| }; |
| |
| static struct ssm_driver *ssm_drv; |
| |
| static unsigned int getint(char *buff, unsigned long *res) |
| { |
| char value[SSM_MSG_FIELD_LEN]; |
| |
| memcpy(value, buff, SSM_MSG_FIELD_LEN); |
| value[SSM_MSG_FIELD_LEN - 1] = '\0'; |
| |
| return kstrtoul(skip_spaces(value), 10, res); |
| } |
| |
| /* |
| * Setup CMD/RSP pointers. |
| */ |
| static void setup_cmd_rsp_buffers(struct qseecom_handle *handle, void **cmd, |
| int *cmd_len, void **resp, int *resp_len) |
| { |
| *cmd = handle->sbuf; |
| if (*cmd_len & QSEECOM_ALIGN_MASK) |
| *cmd_len = QSEECOM_ALIGN(*cmd_len); |
| |
| *resp = handle->sbuf + *cmd_len; |
| if (*resp_len & QSEECOM_ALIGN_MASK) |
| *resp_len = QSEECOM_ALIGN(*resp_len); |
| } |
| |
| /* |
| * Send packet to modem over SMD channel. |
| */ |
| static int update_modem(enum ssm_ipc_req ipc_req, struct ssm_driver *ssm, |
| int length, char *data) |
| { |
| unsigned int packet_len = length + SSM_MSG_FIELD_LEN; |
| int rc = 0, count; |
| |
| snprintf(ssm->smd_buffer, SSM_MSG_FIELD_LEN + 1, "%10u|", ipc_req); |
| memcpy(ssm->smd_buffer + SSM_MSG_FIELD_LEN, data, length); |
| |
| if (smd_write_avail(ssm->ch) < packet_len) { |
| dev_err(ssm->dev, "Not enough space dropping request\n"); |
| rc = -ENOSPC; |
| goto out; |
| } |
| |
| count = smd_write(ssm->ch, ssm->smd_buffer, packet_len); |
| if (count < packet_len) { |
| dev_err(ssm->dev, "smd_write failed for %d\n", ipc_req); |
| rc = -EIO; |
| } |
| |
| out: |
| return rc; |
| } |
| |
| /* |
| * Header Format |
| * Each member of header is of 10 byte (ASCII). |
| * Each entry is separated by '|' delimiter. |
| * |<-10 bytes->|<-10 bytes->| |
| * |-------------------------| |
| * | IPC code | error code | |
| * |-------------------------| |
| * |
| */ |
| static int decode_packet(char *buffer, struct ssm_common_msg *pkt) |
| { |
| int rc; |
| |
| rc = getint(buffer, (unsigned long *)&pkt->ipc_req); |
| if (rc < 0) |
| return -EINVAL; |
| |
| buffer += SSM_MSG_FIELD_LEN; |
| rc = getint(buffer, (unsigned long *)&pkt->err_code); |
| if (rc < 0) |
| return -EINVAL; |
| |
| dev_dbg(ssm_drv->dev, "req %d error code %d\n", |
| pkt->ipc_req, pkt->err_code); |
| return 0; |
| } |
| |
| static void process_message(struct ssm_common_msg pkt, struct ssm_driver *ssm) |
| { |
| |
| switch (pkt.ipc_req) { |
| |
| case SSM_MTOA_MODE_UPDATE_STATUS: |
| if (pkt.err_code) { |
| dev_err(ssm->dev, "Modem mode update failed\n"); |
| ssm->update_status = FAILED; |
| } else |
| ssm->update_status = SUCCESS; |
| |
| dev_dbg(ssm->dev, "Modem mode update status %d\n", |
| pkt.err_code); |
| break; |
| |
| default: |
| dev_dbg(ssm->dev, "Invalid message\n"); |
| break; |
| }; |
| } |
| |
| /* |
| * Work function to handle and process packets coming from modem. |
| */ |
| static void ssm_app_modem_work_fn(struct work_struct *work) |
| { |
| int sz, rc; |
| struct ssm_common_msg pkt; |
| struct ssm_driver *ssm; |
| |
| ssm = container_of(work, struct ssm_driver, ipc_work); |
| |
| mutex_lock(&ssm->mutex); |
| sz = smd_cur_packet_size(ssm->ch); |
| if ((sz < SSM_MSG_FIELD_LEN) || (sz > ATOM_MSG_LEN)) { |
| dev_dbg(ssm_drv->dev, "Garbled message size\n"); |
| goto unlock; |
| } |
| |
| if (smd_read_avail(ssm->ch) < sz) { |
| dev_err(ssm_drv->dev, "SMD error data in channel\n"); |
| goto unlock; |
| } |
| |
| if (smd_read(ssm->ch, ssm->smd_buffer, sz) != sz) { |
| dev_err(ssm_drv->dev, "Incomplete data\n"); |
| goto unlock; |
| } |
| |
| rc = decode_packet(ssm->smd_buffer, &pkt); |
| if (rc < 0) { |
| dev_err(ssm_drv->dev, "Corrupted header\n"); |
| goto unlock; |
| } |
| |
| process_message(pkt, ssm); |
| |
| unlock: |
| mutex_unlock(&ssm->mutex); |
| } |
| |
| /* |
| * MODEM-APPS smd channel callback function. |
| */ |
| static void modem_request(void *ctxt, unsigned event) |
| { |
| struct ssm_driver *ssm; |
| |
| ssm = (struct ssm_driver *)ctxt; |
| |
| switch (event) { |
| case SMD_EVENT_OPEN: |
| case SMD_EVENT_CLOSE: |
| dev_dbg(ssm->dev, "SMD port status changed\n"); |
| break; |
| case SMD_EVENT_DATA: |
| if (smd_read_avail(ssm->ch) > 0) |
| schedule_work(&ssm->ipc_work); |
| break; |
| }; |
| } |
| |
| /* |
| * Load SSM application in TZ and start application: |
| */ |
| static int ssm_load_app(struct ssm_driver *ssm) |
| { |
| int rc; |
| |
| /* Load the APP */ |
| rc = qseecom_start_app(&ssm->qseecom_handle, TZAPP_NAME, SZ_4K); |
| if (rc < 0) { |
| dev_err(ssm->dev, "Unable to load SSM app\n"); |
| ssm->app_status = FAILED; |
| return -EIO; |
| } |
| |
| ssm->app_status = SUCCESS; |
| return 0; |
| } |
| |
| static struct ssm_platform_data *populate_ssm_pdata(struct device *dev) |
| { |
| struct ssm_platform_data *pdata; |
| |
| pdata = devm_kzalloc(dev, sizeof(struct ssm_platform_data), |
| GFP_KERNEL); |
| if (!pdata) |
| return NULL; |
| |
| pdata->need_key_exchg = |
| of_property_read_bool(dev->of_node, "qcom,need-keyexhg"); |
| |
| pdata->channel_name = CHANNEL_NAME; |
| |
| return pdata; |
| } |
| |
| static int ssm_probe(struct platform_device *pdev) |
| { |
| int rc; |
| struct ssm_platform_data *pdata; |
| struct ssm_driver *drv; |
| |
| if (pdev->dev.of_node) |
| pdata = populate_ssm_pdata(&pdev->dev); |
| else |
| pdata = pdev->dev.platform_data; |
| |
| if (!pdata) { |
| dev_err(&pdev->dev, "Empty platform data\n"); |
| return -ENOMEM; |
| } |
| |
| drv = devm_kzalloc(&pdev->dev, sizeof(struct ssm_driver), |
| GFP_KERNEL); |
| if (!drv) { |
| dev_err(&pdev->dev, "Unable to allocate memory\n"); |
| return -ENOMEM; |
| } |
| |
| /* Allocate response buffer */ |
| drv->resp = devm_kzalloc(&pdev->dev, |
| sizeof(struct tzapp_get_mode_info_rsp), |
| GFP_KERNEL); |
| if (!drv->resp) { |
| rc = -ENOMEM; |
| goto exit; |
| } |
| |
| /* Initialize the driver structure */ |
| drv->app_status = RETRY; |
| drv->ready = false; |
| drv->update_status = FAILED; |
| mutex_init(&drv->mutex); |
| drv->key_status = !pdata->need_key_exchg; |
| drv->channel_name = (char *)pdata->channel_name; |
| INIT_WORK(&drv->ipc_work, ssm_app_modem_work_fn); |
| |
| /* Allocate memory for smd buffer */ |
| drv->smd_buffer = devm_kzalloc(&pdev->dev, |
| (sizeof(char) * ATOM_MSG_LEN), GFP_KERNEL); |
| if (!drv->smd_buffer) { |
| rc = -ENOMEM; |
| goto exit; |
| } |
| |
| drv->dev = &pdev->dev; |
| ssm_drv = drv; |
| platform_set_drvdata(pdev, ssm_drv); |
| |
| dev_dbg(&pdev->dev, "probe success\n"); |
| return 0; |
| |
| exit: |
| mutex_destroy(&drv->mutex); |
| platform_set_drvdata(pdev, NULL); |
| return rc; |
| |
| } |
| |
| static int ssm_remove(struct platform_device *pdev) |
| { |
| |
| if (!ssm_drv) |
| return 0; |
| /* |
| * Step to exit |
| * 1. set ready to 0 (oem access closed). |
| * 2. Close SMD modem connection closed. |
| * 3. cleanup ion. |
| */ |
| ssm_drv->ready = false; |
| smd_close(ssm_drv->ch); |
| flush_work(&ssm_drv->ipc_work); |
| |
| /* Shutdown tzapp */ |
| dev_dbg(&pdev->dev, "Shutting down TZapp\n"); |
| qseecom_shutdown_app(&ssm_drv->qseecom_handle); |
| |
| return 0; |
| } |
| |
| static struct of_device_id ssm_match_table[] = { |
| { |
| .compatible = "qcom,ssm", |
| }, |
| {} |
| }; |
| |
| static struct platform_driver ssm_pdriver = { |
| .probe = ssm_probe, |
| .remove = ssm_remove, |
| .driver = { |
| .name = SSM_DEV_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = ssm_match_table, |
| }, |
| }; |
| module_platform_driver(ssm_pdriver); |
| |
| /* |
| * Interface for external OEM driver. |
| * This interface supports following functionalities: |
| * 1. Set mode (encrypted mode and it's length is passed as parameter). |
| * 2. Set mode from TZ (read encrypted mode from TZ) |
| * 3. Get status of mode update. |
| * |
| */ |
| int ssm_oem_driver_intf(int cmd, char *mode, int len) |
| { |
| int rc, req_len, resp_len; |
| struct tzapp_get_mode_info_req *get_mode_req; |
| struct tzapp_get_mode_info_rsp *get_mode_resp; |
| |
| /* If ssm_drv is NULL, probe failed */ |
| if (!ssm_drv) |
| return -ENODEV; |
| |
| mutex_lock(&ssm_drv->mutex); |
| |
| if (ssm_drv->app_status == RETRY) { |
| /* Load TZAPP */ |
| rc = ssm_load_app(ssm_drv); |
| if (rc) { |
| rc = -ENODEV; |
| goto unlock; |
| } |
| } else if (ssm_drv->app_status == FAILED) { |
| rc = -ENODEV; |
| goto unlock; |
| } |
| |
| /* Open modem SMD interface */ |
| if (!ssm_drv->ready) { |
| rc = smd_named_open_on_edge(ssm_drv->channel_name, |
| SMD_APPS_MODEM, |
| &ssm_drv->ch, |
| ssm_drv, |
| modem_request); |
| if (rc) { |
| rc = -EAGAIN; |
| goto unlock; |
| } else |
| ssm_drv->ready = true; |
| } |
| |
| /* Try again modem key-exchange not yet done.*/ |
| if (!ssm_drv->key_status) { |
| rc = -EAGAIN; |
| goto unlock; |
| } |
| |
| /* Set return status to success */ |
| rc = 0; |
| |
| switch (cmd) { |
| case SSM_READY: |
| break; |
| |
| case SSM_MODE_INFO_READY: |
| ssm_drv->update_status = RETRY; |
| /* Fill command structure */ |
| req_len = sizeof(struct tzapp_get_mode_info_req); |
| resp_len = sizeof(struct tzapp_get_mode_info_rsp); |
| setup_cmd_rsp_buffers(ssm_drv->qseecom_handle, |
| (void **)&get_mode_req, &req_len, |
| (void **)&get_mode_resp, &resp_len); |
| get_mode_req->tzapp_ssm_cmd = GET_ENC_MODE; |
| |
| rc = qseecom_set_bandwidth(ssm_drv->qseecom_handle, 1); |
| if (rc) { |
| ssm_drv->update_status = FAILED; |
| dev_err(ssm_drv->dev, "set bandwidth failed\n"); |
| rc = -EIO; |
| break; |
| } |
| rc = qseecom_send_command(ssm_drv->qseecom_handle, |
| (void *)get_mode_req, req_len, |
| (void *)get_mode_resp, resp_len); |
| if (rc || get_mode_resp->status) { |
| ssm_drv->update_status = FAILED; |
| break; |
| } |
| rc = qseecom_set_bandwidth(ssm_drv->qseecom_handle, 0); |
| if (rc) { |
| ssm_drv->update_status = FAILED; |
| dev_err(ssm_drv->dev, "clear bandwidth failed\n"); |
| rc = -EIO; |
| break; |
| } |
| |
| if (get_mode_resp->enc_mode_len > ENC_MODE_MAX_SIZE) { |
| ssm_drv->update_status = FAILED; |
| rc = -EINVAL; |
| break; |
| } |
| /* Send mode_info to modem */ |
| rc = update_modem(SSM_ATOM_MODE_UPDATE, ssm_drv, |
| get_mode_resp->enc_mode_len, |
| get_mode_resp->enc_mode_info); |
| if (rc) |
| ssm_drv->update_status = FAILED; |
| break; |
| |
| case SSM_SET_MODE: |
| ssm_drv->update_status = RETRY; |
| |
| if (len > ENC_MODE_MAX_SIZE) { |
| ssm_drv->update_status = FAILED; |
| rc = -EINVAL; |
| break; |
| } |
| memcpy(ssm_drv->resp->enc_mode_info, mode, len); |
| ssm_drv->resp->enc_mode_len = len; |
| |
| /* Send mode_info to modem */ |
| rc = update_modem(SSM_ATOM_MODE_UPDATE, ssm_drv, |
| ssm_drv->resp->enc_mode_len, |
| ssm_drv->resp->enc_mode_info); |
| if (rc) |
| ssm_drv->update_status = FAILED; |
| break; |
| |
| case SSM_GET_MODE_STATUS: |
| rc = ssm_drv->update_status; |
| break; |
| |
| default: |
| rc = -EINVAL; |
| dev_err(ssm_drv->dev, "Invalid command\n"); |
| break; |
| }; |
| |
| unlock: |
| mutex_unlock(&ssm_drv->mutex); |
| return rc; |
| } |
| EXPORT_SYMBOL_GPL(ssm_oem_driver_intf); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("Qualcomm Secure Service Module"); |
| |