blob: 835266b49866527b1f007eaa8954db0ae840eece [file] [log] [blame]
/* Copyright (c) 2013, 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/firmware.h>
#include <linux/elf.h>
#include <linux/platform_device.h>
#include <linux/msm_ion.h>
#include <linux/platform_data/qcom_ssm.h>
#include <mach/scm.h>
#include <mach/msm_smd.h>
#include "ssm.h"
/* Macros */
#define SSM_DEV_NAME "ssm"
#define MPSS_SUBSYS 0
#define SSM_INFO_CMD_ID 1
#define QSEOS_CHECK_VERSION_CMD 0x00001803
#define MAX_APP_NAME_SIZE 32
#define SSM_MSG_LEN (104 + 4) /* bytes + pad */
#define SSM_MSG_FIELD_LEN 11
#define SSM_HEADER_LEN (SSM_MSG_FIELD_LEN * 4)
#define ATOM_MSG_LEN (SSM_HEADER_LEN + SSM_MSG_LEN)
#define FIRMWARE_NAME "ssmapp"
#define TZAPP_NAME "SsmApp"
#define CHANNEL_NAME "SSM_RTR"
#define ALIGN_BUFFER(size) ((size + 4095) & ~4095)
/* SSM driver structure.*/
struct ssm_driver {
int32_t app_id;
int32_t app_status;
int32_t update_status;
int32_t atom_replay;
int32_t mtoa_replay;
uint32_t buff_len;
unsigned char *channel_name;
unsigned char *smd_buffer;
struct ion_client *ssm_ion_client;
struct ion_handle *ssm_ion_handle;
struct tzapp_get_mode_info_rsp *resp;
struct device *dev;
smd_channel_t *ch;
ion_phys_addr_t buff_phys;
void *buff_virt;
dev_t ssm_device_no;
struct work_struct ipc_work;
struct mutex mutex;
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);
}
/*
* 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 = SSM_HEADER_LEN + length + 1;
int rc = 0;
ssm->atom_replay += 1;
snprintf(ssm->smd_buffer, SSM_HEADER_LEN + 1, "%10u|%10u|%10u|%10u|"
, packet_len, ssm->atom_replay, ipc_req, length);
memcpy(ssm->smd_buffer + SSM_HEADER_LEN, data, length);
ssm->smd_buffer[packet_len - 1] = '|';
if (smd_write_avail(ssm->ch) < packet_len) {
dev_err(ssm->dev, "Not enough space dropping request\n");
rc = -ENOSPC;
}
rc = smd_write(ssm->ch, ssm->smd_buffer, packet_len);
if (rc < packet_len) {
dev_err(ssm->dev, "smd_write failed for %d\n", ipc_req);
rc = -EIO;
}
return rc;
}
/*
* Header Format
* Each member of header is of 10 byte (ASCII).
* Each entry is separated by '|' delimiter.
* |<-10 bytes->|<-10 bytes->|<-10 bytes->|<-10 bytes->|<-10 bytes->|
* |-----------------------------------------------------------------
* | length | replay no. | request | msg_len | message |
* |-----------------------------------------------------------------
*
*/
static int decode_header(char *buffer, int length,
struct ssm_common_msg *pkt)
{
int rc;
rc = getint(buffer, &pkt->pktlen);
if (rc < 0)
return -EINVAL;
buffer += SSM_MSG_FIELD_LEN;
rc = getint(buffer, &pkt->replaynum);
if (rc < 0)
return -EINVAL;
buffer += SSM_MSG_FIELD_LEN;
rc = getint(buffer, (unsigned long *)&pkt->ipc_req);
if (rc < 0)
return -EINVAL;
buffer += SSM_MSG_FIELD_LEN;
rc = getint(buffer, &pkt->msg_len);
if ((rc < 0) || (pkt->msg_len > SSM_MSG_LEN))
return -EINVAL;
pkt->msg = buffer + SSM_MSG_FIELD_LEN;
dev_dbg(ssm_drv->dev, "len %lu rep %lu req %d msg_len %lu\n",
pkt->pktlen, pkt->replaynum, pkt->ipc_req,
pkt->msg_len);
return 0;
}
/*
* Decode address for storing the decryption key.
* Only for Key Exchange
* Message Format
* |Length@Address|
*/
static int decode_message(char *msg, unsigned int len, unsigned long *length,
unsigned long *address)
{
int i = 0, rc = 0;
char *buff;
buff = kzalloc(len, GFP_KERNEL);
if (!buff)
return -ENOMEM;
while (i < len) {
if (msg[i] == '@')
break;
i++;
}
if ((i < len) && (msg[i] == '@')) {
memcpy(buff, msg, i);
buff[i] = '\0';
rc = kstrtoul(skip_spaces(buff), 10, length);
if (rc || (length <= 0)) {
rc = -EINVAL;
goto exit;
}
memcpy(buff, &msg[i + 1], len - (i + 1));
buff[len - i] = '\0';
rc = kstrtoul(skip_spaces(buff), 10, address);
} else
rc = -EINVAL;
exit:
kfree(buff);
return rc;
}
static void process_message(int cmd, char *msg, int len,
struct ssm_driver *ssm)
{
int rc;
unsigned long key_len = 0, key_add = 0, val;
struct ssm_keyexchg_req req;
switch (cmd) {
case SSM_MTOA_KEY_EXCHANGE:
if (len < 3) {
dev_err(ssm->dev, "Invalid message\n");
break;
}
if (ssm->key_status) {
dev_err(ssm->dev, "Key exchange already done\n");
break;
}
rc = decode_message(msg, len, &key_len, &key_add);
if (rc) {
rc = update_modem(SSM_ATOM_KEY_STATUS, ssm,
1, "1");
break;
}
/*
* We are doing key-exchange part here as it is very
* specific for this case. For all other tz
* communication we have generic function.
*/
req.ssid = MPSS_SUBSYS;
req.address = (void *)key_add;
req.length = key_len;
req.status = (uint32_t *)ssm->buff_phys;
*(unsigned int *)ssm->buff_virt = -1;
rc = scm_call(KEY_EXCHANGE, 0x1, &req,
sizeof(struct ssm_keyexchg_req), NULL, 0);
if (rc) {
dev_err(ssm->dev, "Call for key exchg failed %d", rc);
rc = update_modem(SSM_ATOM_KEY_STATUS, ssm,
1, "1");
} else {
/* Success encode packet and update modem */
rc = update_modem(SSM_ATOM_KEY_STATUS, ssm,
1, "0");
ssm->key_status = true;
}
break;
case SSM_MTOA_MODE_UPDATE_STATUS:
msg[len] = '\0';
rc = kstrtoul(skip_spaces(msg), 10, &val);
if (val) {
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 %lu\n", val);
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 <= 0) || (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 (sz < SSM_HEADER_LEN) {
dev_err(ssm_drv->dev, "Invalid packet\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_header(ssm->smd_buffer, sz, &pkt);
if (rc < 0) {
dev_err(ssm_drv->dev, "Corrupted header\n");
goto unlock;
}
/* Check validity of message */
if (ssm->mtoa_replay >= (int)pkt.replaynum) {
dev_err(ssm_drv->dev, "Replay attack...\n");
goto unlock;
}
if (pkt.msg[pkt.msg_len] != '|') {
dev_err(ssm_drv->dev, "Garbled message\n");
goto unlock;
}
ssm->mtoa_replay = pkt.replaynum;
process_message(pkt.ipc_req, pkt.msg, pkt.msg_len, 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_info(ssm->dev, "Port %s\n",
(event == SMD_EVENT_OPEN) ? "opened" : "closed");
break;
case SMD_EVENT_DATA:
if (smd_read_avail(ssm->ch) > 0)
schedule_work(&ssm->ipc_work);
break;
};
}
/*
* Communication interface between ssm driver and TZ.
*/
static int tz_scm_call(struct ssm_driver *ssm, void *tz_req, int tz_req_len,
void **tz_resp, int tz_resp_len)
{
int rc;
struct common_req req;
struct common_resp resp;
memcpy((void *)ssm->buff_virt, tz_req, tz_req_len);
req.cmd_id = CLIENT_SEND_DATA_COMMAND;
req.app_id = ssm->app_id;
req.req_ptr = (void *)ssm->buff_phys;
req.req_len = tz_req_len;
req.resp_ptr = (void *)(ssm->buff_phys + tz_req_len);
req.resp_len = tz_resp_len;
rc = scm_call(SCM_SVC_TZSCHEDULER, 1, (const void *) &req,
sizeof(req), (void *)&resp, sizeof(resp));
if (rc) {
dev_err(ssm->dev, "SCM call failed for data command\n");
return rc;
}
if (resp.result != RESULT_SUCCESS) {
dev_err(ssm->dev, "Data command response failure %d\n",
resp.result);
return -EINVAL;
}
*tz_resp = (void *)(ssm->buff_virt + tz_req_len);
return rc;
}
/*
* Load SSM application in TZ and start application:
* 1. Check if SSM application is already loaded.
* 2. Load SSM application firmware.
* 3. Start SSM application in TZ.
*/
static int ssm_load_app(struct ssm_driver *ssm)
{
unsigned char name[MAX_APP_NAME_SIZE], *pos;
int rc, i, fw_count;
uint32_t buff_len, size = 0, ion_len;
struct check_app_req app_req;
struct scm_resp app_resp;
struct load_app app_img_info;
const struct firmware **fw, *fw_mdt;
const struct elf32_hdr *ehdr;
const struct elf32_phdr *phdr;
struct ion_handle *ion_handle;
ion_phys_addr_t buff_phys;
void *buff_virt;
/* Check if TZ app already loaded */
app_req.cmd_id = APP_LOOKUP_COMMAND;
memcpy(app_req.app_name, TZAPP_NAME, MAX_APP_NAME_SIZE);
rc = scm_call(SCM_SVC_TZSCHEDULER, 1, &app_req,
sizeof(struct check_app_req),
&app_resp, sizeof(app_resp));
if (rc) {
dev_err(ssm->dev, "SCM call failed for LOOKUP COMMAND\n");
return -EINVAL;
}
if (app_resp.result == RESULT_FAILURE)
ssm->app_id = 0;
else
ssm->app_id = app_resp.data;
if (ssm->app_id) {
rc = 0;
dev_info(ssm->dev, "TZAPP already loaded...\n");
goto out;
}
/* APP not loaded get the firmware */
/* Get .mdt first */
rc = request_firmware(&fw_mdt, FIRMWARE_NAME".mdt", ssm->dev);
if (rc) {
dev_err(ssm->dev, "Unable to get mdt file %s\n",
FIRMWARE_NAME".mdt");
rc = -EIO;
goto out;
}
if (fw_mdt->size < sizeof(*ehdr)) {
dev_err(ssm->dev, "Not big enough to be an elf header\n");
rc = -EIO;
goto release_mdt;
}
ehdr = (struct elf32_hdr *)fw_mdt->data;
if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
dev_err(ssm->dev, "Not an elf header\n");
rc = -EIO;
goto release_mdt;
}
if (ehdr->e_phnum == 0) {
dev_err(ssm->dev, "No loadable segments\n");
rc = -EIO;
goto release_mdt;
}
phdr = (const struct elf32_phdr *)(fw_mdt->data +
sizeof(struct elf32_hdr));
fw = kzalloc((sizeof(struct firmware *) * ehdr->e_phnum), GFP_KERNEL);
if (!fw) {
rc = -ENOMEM;
goto release_mdt;
}
/* Valid .mdt now we need to load other parts .b0* */
for (fw_count = 0; fw_count < ehdr->e_phnum ; fw_count++) {
snprintf(name, MAX_APP_NAME_SIZE, FIRMWARE_NAME".b%02d",
fw_count);
rc = request_firmware(&fw[fw_count], name, ssm->dev);
if (rc < 0) {
rc = -EIO;
dev_err(ssm->dev, "Unable to get blob file\n");
goto release_blob;
}
if (fw[fw_count]->size != phdr->p_filesz) {
dev_err(ssm->dev, "Blob size %u doesn't match %u\n",
fw[fw_count]->size, phdr->p_filesz);
rc = -EIO;
goto release_blob;
}
phdr++;
size += fw[fw_count]->size;
}
/* Ion allocation for loading tzapp */
/* ION buffer size 4k aligned */
ion_len = ALIGN_BUFFER(size);
ion_handle = ion_alloc(ssm_drv->ssm_ion_client,
ion_len, SZ_4K, ION_HEAP(ION_QSECOM_HEAP_ID), 0);
if (IS_ERR_OR_NULL(ion_handle)) {
rc = PTR_ERR(ion_handle);
dev_err(ssm->dev, "Unable to get ion handle\n");
goto release_blob;
}
rc = ion_phys(ssm_drv->ssm_ion_client, ion_handle,
&buff_phys, &buff_len);
if (rc < 0) {
dev_err(ssm->dev, "Unable to get ion physical address\n");
goto ion_free;
}
if (buff_len < size) {
rc = -ENOMEM;
goto ion_free;
}
buff_virt = ion_map_kernel(ssm_drv->ssm_ion_client,
ion_handle);
if (IS_ERR_OR_NULL((void *)buff_virt)) {
rc = PTR_ERR((void *)buff_virt);
dev_err(ssm->dev, "Unable to get ion virtual address\n");
goto ion_free;
}
/* Copy firmware to ION memory */
memcpy((unsigned char *)buff_virt, fw_mdt->data, fw_mdt->size);
pos = (unsigned char *)buff_virt + fw_mdt->size;
for (i = 0; i < ehdr->e_phnum; i++) {
memcpy(pos, fw[i]->data, fw[i]->size);
pos += fw[i]->size;
}
/* Loading app */
app_img_info.cmd_id = APP_START_COMMAND;
app_img_info.mdt_len = fw_mdt->size;
app_img_info.img_len = size;
app_img_info.phy_addr = buff_phys;
/* SCM call to load the TZ APP */
rc = scm_call(SCM_SVC_TZSCHEDULER, 1, &app_img_info,
sizeof(struct load_app), &app_resp, sizeof(app_resp));
if (rc) {
rc = -EIO;
dev_err(ssm->dev, "SCM call to load APP failed\n");
goto ion_unmap;
}
if (app_resp.result == RESULT_FAILURE) {
rc = -EIO;
dev_err(ssm->dev, "SCM command to load TzAPP failed\n");
goto ion_unmap;
}
ssm->app_id = app_resp.data;
ssm->app_status = SUCCESS;
ion_unmap:
ion_unmap_kernel(ssm_drv->ssm_ion_client, ion_handle);
ion_free:
ion_free(ssm_drv->ssm_ion_client, ion_handle);
release_blob:
while (--fw_count >= 0)
release_firmware(fw[fw_count]);
kfree(fw);
release_mdt:
release_firmware(fw_mdt);
out:
return rc;
}
/*
* Allocate buffer for transactions.
*/
static int ssm_setup_ion(struct ssm_driver *ssm)
{
int rc = 0;
unsigned int size;
size = ALIGN_BUFFER(ATOM_MSG_LEN);
/* ION client for communicating with TZ */
ssm->ssm_ion_client = msm_ion_client_create(UINT_MAX,
"ssm-kernel");
if (IS_ERR_OR_NULL(ssm->ssm_ion_client)) {
rc = PTR_ERR(ssm->ssm_ion_client);
dev_err(ssm->dev, "Ion client not created\n");
return rc;
}
/* Setup a small ION buffer for tz communication */
ssm->ssm_ion_handle = ion_alloc(ssm->ssm_ion_client,
size, SZ_4K, ION_HEAP(ION_QSECOM_HEAP_ID), 0);
if (IS_ERR_OR_NULL(ssm->ssm_ion_handle)) {
rc = PTR_ERR(ssm->ssm_ion_handle);
dev_err(ssm->dev, "Unable to get ion handle\n");
goto out;
}
rc = ion_phys(ssm->ssm_ion_client, ssm->ssm_ion_handle,
&ssm->buff_phys, &ssm->buff_len);
if (rc < 0) {
dev_err(ssm->dev,
"Unable to get ion buffer physical address\n");
goto ion_free;
}
if (ssm->buff_len < size) {
rc = -ENOMEM;
goto ion_free;
}
ssm->buff_virt = ion_map_kernel(ssm->ssm_ion_client,
ssm->ssm_ion_handle);
if (IS_ERR_OR_NULL((void *)ssm->buff_virt)) {
rc = PTR_ERR((void *)ssm->buff_virt);
dev_err(ssm->dev,
"Unable to get ion buffer virtual address\n");
goto ion_free;
}
return rc;
ion_free:
ion_free(ssm->ssm_ion_client, ssm->ssm_ion_handle);
out:
ion_client_destroy(ssm_drv->ssm_ion_client);
return rc;
}
static struct ssm_platform_data *populate_ssm_pdata(struct device *dev)
{
struct ssm_platform_data *pdata;
int rc;
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");
rc = of_property_read_string(dev->of_node, "qcom,channel-name",
&pdata->channel_name);
if (rc && rc != -EINVAL) {
dev_err(dev, "Error reading channel_name property %d\n", rc);
return NULL;
} else if (rc == -EINVAL)
pdata->channel_name = CHANNEL_NAME;
return pdata;
}
static int ssm_probe(struct platform_device *pdev)
{
int rc;
uint32_t system_call_id;
char legacy = '\0';
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;
}
/* Initialize the driver structure */
drv->atom_replay = -1;
drv->mtoa_replay = -1;
drv->app_id = -1;
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;
}
/* 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;
}
/* Check for TZ version */
system_call_id = QSEOS_CHECK_VERSION_CMD;
rc = scm_call(SCM_SVC_INFO, SSM_INFO_CMD_ID, &system_call_id,
sizeof(system_call_id), &legacy, sizeof(legacy));
if (rc) {
dev_err(&pdev->dev, "Get version failed %d\n", rc);
rc = -EINVAL;
goto exit;
}
/* This driver only support 1.4 TZ and QSEOS */
if (!legacy) {
dev_err(&pdev->dev,
"Driver doesn't support legacy version\n");
rc = -EINVAL;
goto exit;
}
/* Setup the ion buffer for transaction */
rc = ssm_setup_ion(drv);
if (rc < 0)
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)
{
int rc;
struct scm_shutdown_req req;
struct scm_resp resp;
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_sync(&ssm_drv->ipc_work);
/* ION clean up*/
ion_unmap_kernel(ssm_drv->ssm_ion_client, ssm_drv->ssm_ion_handle);
ion_free(ssm_drv->ssm_ion_client, ssm_drv->ssm_ion_handle);
ion_client_destroy(ssm_drv->ssm_ion_client);
/* Shutdown tzapp */
req.app_id = ssm_drv->app_id;
req.cmd_id = APP_SHUTDOWN_COMMAND;
rc = scm_call(SCM_SVC_TZSCHEDULER, 1, &req, sizeof(req),
&resp, sizeof(resp));
if (rc)
dev_err(&pdev->dev, "TZ_app Unload failed\n");
return rc;
}
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. Get TZAPP ID.
* 2. Set default mode.
* 3. Set mode (encrypted mode and it's length is passed as parameter).
* 4. Set mode from TZ.
* 5. 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;
ssm_drv->app_status = FAILED;
goto unlock;
}
} else if (ssm_drv->app_status == FAILED) {
rc = -ENODEV;
goto unlock;
}
/* Open modem SMD interface */
if (!ssm_drv->ready) {
rc = smd_open(ssm_drv->channel_name, &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_GET_APP_ID:
rc = ssm_drv->app_id;
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);
get_mode_req.tzapp_ssm_cmd = GET_ENC_MODE;
rc = tz_scm_call(ssm_drv, (void *)&get_mode_req,
req_len, (void **)&get_mode_resp, resp_len);
if (rc) {
ssm_drv->update_status = FAILED;
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;
case SSM_SET_DEFAULT_MODE:
/* Modem does not send response for this */
ssm_drv->update_status = RETRY;
rc = update_modem(SSM_ATOM_SET_DEFAULT_MODE, ssm_drv,
1, "0");
if (rc)
ssm_drv->update_status = FAILED;
else
/* For default mode we don't get any resp
* from modem.
*/
ssm_drv->update_status = SUCCESS;
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");