[COMMON] scsi: rpmb: update RPMB drivers
authorShinkyu Park <shinkyu.park@samsung.com>
Wed, 24 Jan 2018 12:54:03 +0000 (21:54 +0900)
committerJaeHun Jung <jh0801.jung@samsung.com>
Tue, 8 May 2018 08:21:03 +0000 (17:21 +0900)
[Description]
Initial RPMB drivers on Kernel 4.14

Platform Development Team
Shinkyu Park (shinkyu.park@samsung.com)

Change-Id: I36a79a54618484f67385327ade35425131220551

drivers/scsi/Kconfig
drivers/scsi/Makefile
drivers/scsi/scsi_ioctl.c
drivers/scsi/scsi_srpmb.c [new file with mode: 0644]
drivers/scsi/scsi_srpmb.h [new file with mode: 0644]
drivers/scsi/sd.c
drivers/scsi/sd.h
include/linux/smc.h [changed mode: 0755->0644]
include/scsi/scsi_ioctl.h

index 41366339b95015c2cf608b3bccbe86f4ffaaf920..83cfb47038416d2821731cce43c106315420355d 100644 (file)
@@ -38,6 +38,12 @@ config SCSI
          However, do not compile this as a module if your root file system
          (the one containing the directory /) is located on a SCSI device.
 
+config UFS_SRPMB
+       bool "UFS Secure RPMB support"
+       depends on SCSI
+       help
+         This option activates ufs secure rpmb.
+
 config SCSI_DMA
        bool
        default n
index 1639bf8b1ab667759f2310b6cc29680c6f728533..1738b21c8a08b0032b2d12bb7d93d777263cb78e 100644 (file)
@@ -156,6 +156,8 @@ obj-$(CONFIG_SCSI_ENCLOSURE)        += ses.o
 obj-$(CONFIG_SCSI_OSD_INITIATOR) += osd/
 obj-$(CONFIG_SCSI_HISI_SAS) += hisi_sas/
 
+obj-$(CONFIG_UFS_SRPMB)                += scsi_srpmb.o
+
 # This goes last, so that "real" scsi devices probe earlier
 obj-$(CONFIG_SCSI_DEBUG)       += scsi_debug.o
 scsi_mod-y                     += scsi.o hosts.o scsi_ioctl.o \
index 0e6c2f15b46a3f9a0d97011e119be3ba54e460a7..81a029da8f3a0c017d15d53c60c2c021225666d9 100644 (file)
@@ -67,24 +67,24 @@ static int ioctl_probe(struct Scsi_Host *host, void __user *buffer)
 /*
 
  * The SCSI_IOCTL_SEND_COMMAND ioctl sends a command out to the SCSI host.
- * The IOCTL_NORMAL_TIMEOUT and NORMAL_RETRIES  variables are used.  
- * 
+ * The IOCTL_NORMAL_TIMEOUT and NORMAL_RETRIES  variables are used.
+ *
  * dev is the SCSI device struct ptr, *(int *) arg is the length of the
- * input data, if any, not including the command string & counts, 
+ * input data, if any, not including the command string & counts,
  * *((int *)arg + 1) is the output buffer size in bytes.
- * 
- * *(char *) ((int *) arg)[2] the actual command byte.   
- * 
+ *
+ * *(char *) ((int *) arg)[2] the actual command byte.
+ *
  * Note that if more than MAX_BUF bytes are requested to be transferred,
  * the ioctl will fail with error EINVAL.
- * 
+ *
  * This size *does not* include the initial lengths that were passed.
- * 
+ *
  * The SCSI command is read from the memory location immediately after the
  * length words, and the input data is right after the command.  The SCSI
- * routines know the command size based on the opcode decode.  
- * 
- * The output area is then filled in starting from the command byte. 
+ * routines know the command size based on the opcode decode.
+ *
+ * The output area is then filled in starting from the command byte.
  */
 
 static int ioctl_internal_command(struct scsi_device *sdev, char *cmd,
@@ -140,6 +140,81 @@ static int ioctl_internal_command(struct scsi_device *sdev, char *cmd,
        return result;
 }
 
+#if defined(CONFIG_UFS_SRPMB)
+static int srpmb_ioctl_secu_prot_command(struct scsi_device *sdev, char *cmd,
+                                       Rpmb_Req *req,
+                                       int timeout, int retries)
+{
+       int result, dma_direction;
+       struct scsi_sense_hdr sshdr;
+       unsigned char *buf = NULL;
+       unsigned int bufflen;
+       int prot_in_out = req->cmd;
+
+       SCSI_LOG_IOCTL(1, printk("Trying ioctl with scsi command %d\n", *cmd));
+       if (prot_in_out == SCSI_IOCTL_SECURITY_PROTOCOL_IN) {
+               dma_direction = DMA_FROM_DEVICE;
+               bufflen = req->inlen;
+               if (bufflen <= 0 || bufflen > MAX_BUFFLEN) {
+                       sdev_printk(KERN_INFO, sdev,
+                                       "Invalid bufflen : %x\n", bufflen);
+                       result = -EFAULT;
+                       goto err_pre_buf_alloc;
+               }
+               buf = kzalloc(bufflen, GFP_KERNEL);
+               if (!virt_addr_valid(buf)) {
+                       result = -ENOMEM;
+                       goto err_kzalloc;
+               }
+       } else if (prot_in_out == SCSI_IOCTL_SECURITY_PROTOCOL_OUT) {
+               dma_direction = DMA_TO_DEVICE;
+               bufflen = req->outlen;
+               if (bufflen <= 0 || bufflen > MAX_BUFFLEN) {
+                       sdev_printk(KERN_INFO, sdev,
+                                       "Invalid bufflen : %x\n", bufflen);
+                       result = -EFAULT;
+                       goto err_pre_buf_alloc;
+               }
+               buf = kzalloc(bufflen, GFP_KERNEL);
+               if (!virt_addr_valid(buf)) {
+                       result = -ENOMEM;
+                       goto err_kzalloc;
+               }
+               memcpy(buf, req->rpmb_data, bufflen);
+       } else {
+               sdev_printk(KERN_INFO, sdev,
+                               "prot_in_out not set!! %d\n", prot_in_out);
+               result = -EFAULT;
+               goto err_pre_buf_alloc;
+       }
+
+       result = scsi_execute_req(sdev, cmd, dma_direction, buf, bufflen,
+                                 &sshdr, timeout, retries, NULL);
+
+       if (prot_in_out == SCSI_IOCTL_SECURITY_PROTOCOL_IN) {
+               memcpy(req->rpmb_data, buf, req->data_len);
+       }
+       SCSI_LOG_IOCTL(2, printk("Ioctl returned  0x%x\n", result));
+
+       if ((driver_byte(result) & DRIVER_SENSE) &&
+           (scsi_sense_valid(&sshdr))) {
+               sdev_printk(KERN_INFO, sdev,
+                           "ioctl_secu_prot_command return code = %x\n",
+                           result);
+               scsi_print_sense_hdr(sdev, NULL, &sshdr);
+       }
+
+       kfree(buf);
+err_pre_buf_alloc:
+       SCSI_LOG_IOCTL(2, printk("IOCTL Releasing command\n"));
+       return result;
+err_kzalloc:
+       if (buf)
+               kfree(buf);
+       printk(KERN_INFO "%s kzalloc faild\n", __func__);
+       return result;
+}
+#endif
 
 static int ioctl_secu_prot_command(struct scsi_device *sdev, char *cmd,
                                        int prot_in_out, void __user *arg,
@@ -168,10 +243,22 @@ static int ioctl_secu_prot_command(struct scsi_device *sdev, char *cmd,
        if (prot_in_out == SCSI_IOCTL_SECURITY_PROTOCOL_IN) {
                dma_direction = DMA_FROM_DEVICE;
                bufflen = s_ioc_arg->inlen;
+               if (bufflen <= 0 || bufflen > MAX_BUFFLEN) {
+                       sdev_printk(KERN_INFO, sdev,
+                                       "Invalid bufflen : %x\n", bufflen);
+                       result = -EFAULT;
+                       goto err_pre_buf_alloc;
+               }
                buf = kzalloc(bufflen, GFP_KERNEL);
        } else if (prot_in_out == SCSI_IOCTL_SECURITY_PROTOCOL_OUT) {
                dma_direction = DMA_TO_DEVICE;
                bufflen = s_ioc_arg->outlen;
+               if (bufflen <= 0 || bufflen > MAX_BUFFLEN) {
+                       sdev_printk(KERN_INFO, sdev,
+                                       "Invalid bufflen : %x\n", bufflen);
+                       result = -EFAULT;
+                       goto err_pre_buf_alloc;
+               }
                buf = kzalloc(bufflen, GFP_KERNEL);
                if (copy_from_user(buf, arg + sizeof(*s_ioc_arg), s_ioc_arg->outlen)) {
                        printk(KERN_INFO "copy_from_user failed\n");
@@ -262,6 +349,68 @@ static int scsi_ioctl_get_pci(struct scsi_device *sdev, void __user *arg)
                ? -EFAULT: 0;
 }
 
+#if defined(CONFIG_UFS_SRPMB)
+/**
+ * srpmb_scsi_ioctl - Dispatch rpmb ioctl to scsi device
+ * @sdev: scsi device receiving srpmb_worker
+ * @req: rpmb struct with srpmb_worker
+ *
+ * Description: The scsi_ioctl() function differs from most ioctls in that it
+ * does not take a major/minor number as the dev field.  Rather, it takes
+ * a pointer to a &struct scsi_device.
+ */
+int srpmb_scsi_ioctl(struct scsi_device *sdev, Rpmb_Req *req)
+{
+       char scsi_cmd[MAX_COMMAND_SIZE];
+       unsigned short prot_spec;
+       unsigned long t_len;
+       int _cmd;
+
+       /* No idea how this happens.... */
+       if (!sdev) {
+               printk(KERN_ERR "sdev empty\n");
+               return -ENXIO;
+       }
+
+       memset(scsi_cmd, 0x0, MAX_COMMAND_SIZE);
+       /*
+        * If we are in the middle of error recovery, don't let anyone
+        * else try and use this device.  Also, if error recovery fails, it
+        * may try and take the device offline, in which case all further
+        * access to the device is prohibited.
+        */
+       if (!scsi_block_when_processing_errors(sdev))
+               return -ENODEV;
+
+       _cmd = SCSI_UFS_REQUEST_SENSE;
+       if (sdev->host->wlun_clr_uac)
+               sdev->host->hostt->ioctl(sdev, _cmd, NULL);
+
+       prot_spec = SECU_PROT_SPEC_CERT_DATA;
+       if (req->cmd == SCSI_IOCTL_SECURITY_PROTOCOL_IN)
+               t_len = req->inlen;
+       else
+               t_len = req->outlen;
+
+       scsi_cmd[0] = (req->cmd == SCSI_IOCTL_SECURITY_PROTOCOL_IN) ?
+               SECURITY_PROTOCOL_IN :
+               SECURITY_PROTOCOL_OUT;
+       scsi_cmd[1] = SECU_PROT_UFS;
+       scsi_cmd[2] = ((unsigned char)(prot_spec >> 8)) & 0xff;
+       scsi_cmd[3] = ((unsigned char)(prot_spec)) & 0xff;
+       scsi_cmd[4] = 0;
+       scsi_cmd[5] = 0;
+       scsi_cmd[6] = ((unsigned char)(t_len >> 24)) & 0xff;
+       scsi_cmd[7] = ((unsigned char)(t_len >> 16)) & 0xff;
+       scsi_cmd[8] = ((unsigned char)(t_len >> 8)) & 0xff;
+       scsi_cmd[9] = (unsigned char)t_len & 0xff;
+       scsi_cmd[10] = 0;
+       scsi_cmd[11] = 0;
+       return srpmb_ioctl_secu_prot_command(sdev, scsi_cmd,
+                       req,
+                       START_STOP_TIMEOUT, NORMAL_RETRIES);
+}
+#endif
 
 /**
  * scsi_ioctl - Dispatch ioctl to scsi device
diff --git a/drivers/scsi/scsi_srpmb.c b/drivers/scsi/scsi_srpmb.c
new file mode 100644 (file)
index 0000000..6405344
--- /dev/null
@@ -0,0 +1,417 @@
+/*
+ * Secure RPMB Driver for Exynos scsi rpmb
+ *
+ * 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/cdev.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/smc.h>
+
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_ioctl.h>
+
+#include "scsi_srpmb.h"
+
+#define SRPMB_DEVICE_PROPNAME  "samsung,ufs-srpmb"
+
+struct platform_device *sr_pdev;
+
+#if defined(DEBUG_SRPMB)
+static void dump_packet(u8 *data, int len)
+{
+       u8 s[17];
+       int i, j;
+
+       s[16] = '\0';
+       for (i = 0; i < len; i += 16) {
+               printk("%06x :", i);
+               for (j = 0; j < 16; j++) {
+                       printk(" %02x", data[i+j]);
+                       s[j] = (data[i+j] < ' ' ? '.' : (data[i+j] > '}' ? '.' : data[i+j]));
+               }
+               printk(" |%s|\n", s);
+       }
+       printk("\n");
+}
+#endif
+
+static void swap_packet(u8 *p, u8 *d)
+{
+       int i;
+       for (i = 0; i < RPMB_PACKET_SIZE; i++)
+               d[i] = p[RPMB_PACKET_SIZE - 1 - i];
+}
+
+static void update_rpmb_status_flag(struct rpmb_irq_ctx *ctx,
+                               Rpmb_Req *req, int status)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&ctx->lock, flags);
+       req->status_flag = status;
+       spin_unlock_irqrestore(&ctx->lock, flags);
+}
+
+static void srpmb_worker(struct work_struct *data)
+{
+       int ret;
+       struct rpmb_packet packet;
+       struct rpmb_irq_ctx *rpmb_ctx;
+       struct scsi_device *sdp;
+       Rpmb_Req *req;
+
+       if (!data) {
+               dev_err(&sr_pdev->dev, "rpmb work_struct data invalid\n");
+               return ;
+       }
+       rpmb_ctx = container_of(data, struct rpmb_irq_ctx, work);
+       if (!rpmb_ctx->dev) {
+               dev_err(&sr_pdev->dev, "rpmb_ctx->dev invalid\n");
+               return ;
+       }
+       sdp = to_scsi_device(rpmb_ctx->dev);
+
+       if (!rpmb_ctx->vir_addr) {
+               dev_err(&sr_pdev->dev, "rpmb_ctx->vir_addr invalid\n");
+               return ;
+       }
+       req = (Rpmb_Req *)rpmb_ctx->vir_addr;
+
+       __pm_stay_awake(&rpmb_ctx->wakesrc);
+
+       dev_info(&sr_pdev->dev, "start rpmb workqueue with command(%d)\n", req->type);
+
+       switch (req->type) {
+       case GET_WRITE_COUNTER:
+               if (req->data_len != RPMB_PACKET_SIZE) {
+                       update_rpmb_status_flag(rpmb_ctx, req,
+                                       WRITE_COUTNER_DATA_LEN_ERROR);
+                       dev_err(&sr_pdev->dev, "data len is invalid\n");
+                       break;
+               }
+
+               req->cmd = SCSI_IOCTL_SECURITY_PROTOCOL_OUT;
+               req->outlen = RPMB_PACKET_SIZE;
+
+               ret = srpmb_scsi_ioctl(sdp, req);
+               if (ret < 0) {
+                       update_rpmb_status_flag(rpmb_ctx, req,
+                                       WRITE_COUTNER_SECURITY_OUT_ERROR);
+                       dev_err(&sr_pdev->dev, "ioctl read_counter error: %x\n", ret);
+                       break;
+               }
+
+               memset(req->rpmb_data, 0x0, req->data_len);
+               req->cmd = SCSI_IOCTL_SECURITY_PROTOCOL_IN;
+               req->inlen = req->data_len;
+
+               ret = srpmb_scsi_ioctl(sdp, req);
+               if (ret < 0) {
+                       update_rpmb_status_flag(rpmb_ctx, req,
+                                       WRITE_COUTNER_SECURITY_IN_ERROR);
+                       dev_err(&sr_pdev->dev, "ioctl error : %x\n", ret);
+                       break;
+               }
+               update_rpmb_status_flag(rpmb_ctx, req, RPMB_PASSED);
+
+               break;
+       case WRITE_DATA:
+               if (req->data_len < RPMB_PACKET_SIZE ||
+                       req->data_len > RPMB_PACKET_SIZE * 64) {
+                       update_rpmb_status_flag(rpmb_ctx, req,
+                                       WRITE_DATA_LEN_ERROR);
+                       dev_err(&sr_pdev->dev, "data len is invalid\n");
+                       break;
+               }
+
+               req->cmd = SCSI_IOCTL_SECURITY_PROTOCOL_OUT;
+               req->outlen = req->data_len;
+
+               ret = srpmb_scsi_ioctl(sdp, req);
+               if (ret < 0) {
+                       update_rpmb_status_flag(rpmb_ctx, req,
+                                       WRITE_DATA_SECURITY_OUT_ERROR);
+                       dev_err(&sr_pdev->dev, "ioctl write data error: %x\n", ret);
+                       break;
+               }
+
+               memset(req->rpmb_data, 0x0, req->data_len);
+               memset(&packet, 0x0, RPMB_PACKET_SIZE);
+               packet.request = RESULT_READ_REQ;
+               swap_packet((uint8_t *)&packet, req->rpmb_data);
+               req->cmd = SCSI_IOCTL_SECURITY_PROTOCOL_OUT;
+               req->outlen = RPMB_PACKET_SIZE;
+
+               ret = srpmb_scsi_ioctl(sdp, req);
+               if (ret < 0) {
+                       update_rpmb_status_flag(rpmb_ctx, req,
+                                       WRITE_DATA_RESULT_SECURITY_OUT_ERROR);
+                       dev_err(&sr_pdev->dev,
+                                       "ioctl write_data result error: %x\n", ret);
+                       break;
+               }
+
+               memset(req->rpmb_data, 0x0, req->data_len);
+               req->cmd = SCSI_IOCTL_SECURITY_PROTOCOL_IN;
+               req->inlen = RPMB_PACKET_SIZE;
+
+               ret = srpmb_scsi_ioctl(sdp, req);
+               if (ret < 0) {
+                       update_rpmb_status_flag(rpmb_ctx, req,
+                                       WRITE_DATA_SECURITY_IN_ERROR);
+                       dev_err(&sr_pdev->dev,
+                                       "ioctl write_data result error: %x\n", ret);
+                       break;
+               }
+
+               swap_packet(req->rpmb_data, (uint8_t *)&packet);
+               if (packet.result == 0) {
+                       update_rpmb_status_flag(rpmb_ctx, req, RPMB_PASSED);
+               } else {
+                       update_rpmb_status_flag(rpmb_ctx, req, packet.result);
+                       dev_err(&sr_pdev->dev,
+                                       "packet result error: %x\n", req->status_flag);
+               }
+               break;
+       case READ_DATA:
+               if (req->data_len < RPMB_PACKET_SIZE ||
+                       req->data_len > RPMB_PACKET_SIZE * 64) {
+                       update_rpmb_status_flag(rpmb_ctx, req, READ_LEN_ERROR);
+                       dev_err(&sr_pdev->dev, "data len is invalid\n");
+                       break;
+               }
+
+               req->cmd = SCSI_IOCTL_SECURITY_PROTOCOL_OUT;
+               req->outlen = RPMB_PACKET_SIZE;
+
+               ret = srpmb_scsi_ioctl(sdp, req);
+               if (ret < 0) {
+                       update_rpmb_status_flag(rpmb_ctx, req,
+                                       READ_DATA_SECURITY_OUT_ERROR);
+                       dev_err(&sr_pdev->dev, "ioctl read data error: %x\n", ret);
+                       break;
+               }
+
+               memset(req->rpmb_data, 0x0, req->data_len);
+               req->cmd = SCSI_IOCTL_SECURITY_PROTOCOL_IN;
+               req->inlen = req->data_len;
+
+               ret = srpmb_scsi_ioctl(sdp, req);
+               if (ret < 0) {
+                       update_rpmb_status_flag(rpmb_ctx, req,
+                                       READ_DATA_SECURITY_IN_ERROR);
+                       dev_err(&sr_pdev->dev,
+                                       "ioctl result read data error : %x\n", ret);
+                       break;
+               }
+               update_rpmb_status_flag(rpmb_ctx, req, RPMB_PASSED);
+
+               break;
+       default:
+               dev_err(&sr_pdev->dev, "invalid requset type : %x\n", req->type);
+       }
+
+       __pm_relax(&rpmb_ctx->wakesrc);
+       dev_info(&sr_pdev->dev, "finish rpmb workqueue with command(%d)\n", req->type);
+}
+
+static int srpmb_suspend_notifier(struct notifier_block *nb, unsigned long event,
+                               void *dummy)
+{
+       struct rpmb_irq_ctx *rpmb_ctx;
+       struct device *dev;
+       Rpmb_Req *req;
+
+       if (!nb) {
+               dev_err(&sr_pdev->dev, "noti_blk work_struct data invalid\n");
+               return -1;
+       }
+       rpmb_ctx = container_of(nb, struct rpmb_irq_ctx, pm_notifier);
+       dev = rpmb_ctx->dev;
+       req = (Rpmb_Req *)rpmb_ctx->vir_addr;
+       if (!req) {
+               dev_err(dev, "Invalid wsm address for rpmb\n");
+               return -EINVAL;
+       }
+
+       switch (event) {
+       case PM_HIBERNATION_PREPARE:
+       case PM_SUSPEND_PREPARE:
+       case PM_RESTORE_PREPARE:
+               flush_workqueue(rpmb_ctx->srpmb_queue);
+               update_rpmb_status_flag(rpmb_ctx, req, RPMB_FAIL_SUSPEND_STATUS);
+               break;
+       case PM_POST_SUSPEND:
+       case PM_POST_HIBERNATION:
+       case PM_POST_RESTORE:
+               update_rpmb_status_flag(rpmb_ctx, req, 0);
+               break;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static irqreturn_t rpmb_irq_handler(int intr, void *arg)
+{
+       struct rpmb_irq_ctx *rpmb_ctx = (struct rpmb_irq_ctx *)arg;
+       struct device *dev;
+       Rpmb_Req *req;
+
+       dev = rpmb_ctx->dev;
+       req = (Rpmb_Req *)rpmb_ctx->vir_addr;
+       if (!req) {
+               dev_err(dev, "Invalid wsm address for rpmb\n");
+               return IRQ_HANDLED;
+       }
+
+       update_rpmb_status_flag(rpmb_ctx, req, RPMB_IN_PROGRESS);
+       queue_work(rpmb_ctx->srpmb_queue, &rpmb_ctx->work);
+
+       return IRQ_HANDLED;
+}
+
+int init_wsm(struct device *dev)
+{
+       int ret;
+       struct rpmb_irq_ctx *rpmb_ctx;
+       struct irq_data *rpmb_irqd = NULL;
+       irq_hw_number_t hwirq = 0;
+
+       rpmb_ctx = kzalloc(sizeof(struct rpmb_irq_ctx), GFP_KERNEL);
+       if (!rpmb_ctx) {
+               dev_err(&sr_pdev->dev, "kzalloc failed\n");
+               goto out_srpmb_ctx_alloc_fail;
+       }
+
+       /* buffer init */
+       rpmb_ctx->vir_addr = dma_alloc_coherent(&sr_pdev->dev,
+                       sizeof(Rpmb_Req) + RPMB_BUF_MAX_SIZE,
+                       &rpmb_ctx->phy_addr, GFP_KERNEL);
+
+       if (rpmb_ctx->vir_addr && rpmb_ctx->phy_addr) {
+               dev_info(dev, "srpmb dma addr: virt(%llx), phy(%llx)\n",
+                       (uint64_t)rpmb_ctx->vir_addr, (uint64_t)rpmb_ctx->phy_addr);
+
+               rpmb_ctx->irq = irq_of_parse_and_map(sr_pdev->dev.of_node, 0);
+               if (rpmb_ctx->irq <= 0) {
+                       dev_err(&sr_pdev->dev, "No IRQ number, aborting\n");
+                       goto out_srpmb_init_fail;
+               }
+
+               /* Get irq_data for secure log */
+               rpmb_irqd = irq_get_irq_data(rpmb_ctx->irq);
+               if (!rpmb_irqd) {
+                       dev_err(&sr_pdev->dev, "Fail to get irq_data\n");
+                       goto out_srpmb_init_fail;
+               }
+
+               /* Get hardware interrupt number */
+               hwirq = irqd_to_hwirq(rpmb_irqd);
+               dev_dbg(&sr_pdev->dev, "hwirq for srpmb (%ld)\n", hwirq);
+
+               rpmb_ctx->dev = dev;
+               rpmb_ctx->srpmb_queue = alloc_workqueue("srpmb_wq",
+                               WQ_MEM_RECLAIM | WQ_UNBOUND | WQ_HIGHPRI, 1);
+               if (!rpmb_ctx->srpmb_queue) {
+                       dev_err(&sr_pdev->dev,
+                               "Fail to alloc workqueue for ufs sprmb\n");
+                       goto out_srpmb_init_fail;
+               }
+
+               ret = request_irq(rpmb_ctx->irq, rpmb_irq_handler,
+                               IRQF_TRIGGER_RISING, sr_pdev->name, rpmb_ctx);
+               if (ret) {
+                       dev_err(&sr_pdev->dev, "request irq failed: %x\n", ret);
+                       goto out_srpmb_init_fail;
+               }
+
+               rpmb_ctx->pm_notifier.notifier_call = srpmb_suspend_notifier;
+               ret = register_pm_notifier(&rpmb_ctx->pm_notifier);
+               if (ret) {
+                       dev_err(&sr_pdev->dev, "Failed to setup pm notifier\n");
+                       goto out_srpmb_init_fail;
+               }
+
+               ret = exynos_smc(SMC_SRPMB_WSM, rpmb_ctx->phy_addr, hwirq, 0);
+               if (ret) {
+                       dev_err(&sr_pdev->dev, "wsm smc init failed: %x\n", ret);
+                       goto out_srpmb_unregister_pm;
+               }
+
+               wakeup_source_init(&rpmb_ctx->wakesrc, "srpmb");
+               spin_lock_init(&rpmb_ctx->lock);
+               INIT_WORK(&rpmb_ctx->work, srpmb_worker);
+
+       } else {
+               dev_err(&sr_pdev->dev, "wsm dma alloc failed\n");
+               goto out_srpmb_dma_alloc_fail;
+       }
+
+       return 0;
+
+out_srpmb_unregister_pm:
+       unregister_pm_notifier(&rpmb_ctx->pm_notifier);
+out_srpmb_init_fail:
+       if (rpmb_ctx->srpmb_queue)
+               destroy_workqueue(rpmb_ctx->srpmb_queue);
+
+       dma_free_coherent(&sr_pdev->dev, RPMB_BUF_MAX_SIZE,
+                       rpmb_ctx->vir_addr, rpmb_ctx->phy_addr);
+
+out_srpmb_dma_alloc_fail:
+       kfree(rpmb_ctx);
+
+out_srpmb_ctx_alloc_fail:
+       return -ENOMEM;
+}
+
+static int srpmb_probe(struct platform_device *pdev)
+{
+       sr_pdev = pdev;
+
+       return 0;
+}
+
+static const struct of_device_id of_match_table[] = {
+       { .compatible = SRPMB_DEVICE_PROPNAME },
+       { }
+};
+
+static struct platform_driver srpmb_plat_driver = {
+       .probe = srpmb_probe,
+       .driver = {
+               .name = "exynos-ufs-srpmb",
+               .owner = THIS_MODULE,
+               .of_match_table = of_match_table,
+       }
+};
+
+static int __init srpmb_init(void)
+{
+       return platform_driver_register(&srpmb_plat_driver);
+}
+
+static void __exit srpmb_exit(void)
+{
+       platform_driver_unregister(&srpmb_plat_driver);
+}
+
+subsys_initcall(srpmb_init);
+module_exit(srpmb_exit);
+
+MODULE_AUTHOR("Yongtaek Kwon <ycool.kwon@samsung.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("UFS SRPMB driver");
diff --git a/drivers/scsi/scsi_srpmb.h b/drivers/scsi/scsi_srpmb.h
new file mode 100644 (file)
index 0000000..e4df7dc
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Secure RPMB header for Exynos scsi rpmb
+ *
+ * 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.
+ */
+#ifndef _SCSI_SRPMB_H
+#define _SCSI_SRPMB_H
+
+#include <linux/pm_wakeup.h>
+
+#define GET_WRITE_COUNTER                      1
+#define WRITE_DATA                             2
+#define READ_DATA                              3
+
+#define AUTHEN_KEY_PROGRAM_RES                 0x0100
+#define AUTHEN_KEY_PROGRAM_REQ                 0x0001
+#define RESULT_READ_REQ                                0x0005
+#define RPMB_END_ADDRESS                       0x4000
+
+#define RPMB_PACKET_SIZE                       512
+
+#define WRITE_COUTNER_DATA_LEN_ERROR           0x601
+#define WRITE_COUTNER_SECURITY_OUT_ERROR       0x602
+#define WRITE_COUTNER_SECURITY_IN_ERROR                0x603
+#define WRITE_DATA_LEN_ERROR                   0x604
+#define WRITE_DATA_SECURITY_OUT_ERROR          0x605
+#define WRITE_DATA_RESULT_SECURITY_OUT_ERROR   0x606
+#define WRITE_DATA_SECURITY_IN_ERROR           0x607
+#define READ_LEN_ERROR                         0x608
+#define READ_DATA_SECURITY_OUT_ERROR           0x609
+#define READ_DATA_SECURITY_IN_ERROR            0x60A
+#define RPMB_INVALID_COMMAND                   0x60B
+#define RPMB_FAIL_SUSPEND_STATUS               0x60C
+
+#define RPMB_IN_PROGRESS                       0xDCDC
+#define RPMB_PASSED                            0xBABA
+
+#define IS_INCLUDE_RPMB_DEVICE                 "0:0:0:1"
+
+#define ON                                     1
+#define OFF                                    0
+
+#define RPMB_BUF_MAX_SIZE                      (32 * 1024)
+
+struct rpmb_irq_ctx {
+       struct device *dev;
+       int irq;
+       u8 *vir_addr;
+       dma_addr_t phy_addr;
+       struct work_struct work;
+       struct workqueue_struct *srpmb_queue;
+       struct notifier_block pm_notifier;
+       struct wakeup_source wakesrc;
+       spinlock_t lock;
+};
+
+struct rpmb_packet {
+       u16     request;
+       u16     result;
+       u16     count;
+       u16     address;
+       u32     write_counter;
+       u8      nonce[16];
+       u8      data[256];
+       u8      Key_MAC[32];
+       u8      stuff[196];
+};
+
+int init_wsm(struct device *dev);
+
+#endif /* _SCSI_SRPMB_H */
index 8d352e7f1d981053ed5b0a7c60b8b2feacaaa2a5..032cc1236d01f632458848c58d77145a0e28e910 100644 (file)
@@ -70,6 +70,9 @@
 #include "sd.h"
 #include "scsi_priv.h"
 #include "scsi_logging.h"
+#if defined(CONFIG_UFS_SRPMB)
+#include "scsi_srpmb.h"
+#endif
 
 MODULE_AUTHOR("Eric Youngdale");
 MODULE_DESCRIPTION("SCSI disk (sd) driver");
@@ -3397,6 +3400,16 @@ static int sd_probe(struct device *dev)
        get_device(&sdkp->dev); /* prevent release before async_schedule */
        async_schedule_domain(sd_probe_async, sdkp, &scsi_sd_probe_domain);
 
+#if defined(CONFIG_UFS_SRPMB)
+       /* rpmb operation for LDFW */
+       if (strncmp(dev_name(dev), IS_INCLUDE_RPMB_DEVICE,
+                               sizeof(IS_INCLUDE_RPMB_DEVICE)) == 0) {
+               int ret;
+               ret = init_wsm(dev);
+               if (ret)
+                       printk("srpmb init_wsm failed: %x\n", ret);
+       }
+#endif
        return 0;
 
  out_free_index:
index 320de758323eac6cb40bbf7275e6a7c60fb6fefc..3fe58ecf338a844b77a3ef8e78866562143ca467 100644 (file)
@@ -2,6 +2,10 @@
 #ifndef _SCSI_DISK_H
 #define _SCSI_DISK_H
 
+#if defined(CONFIG_UFS_SRPMB)
+#include "scsi_srpmb.h"
+#endif
+
 /*
  * More than enough for everybody ;)  The huge number of majors
  * is a leftover from 16bit dev_t days, we don't really need that
old mode 100755 (executable)
new mode 100644 (file)
index 578817848e8502d3c0f52b692382637501024ae1..8b8a3f8aefa6cbb754517347d4230df3473f9b3f 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 #ifndef _SCSI_IOCTL_H
-#define _SCSI_IOCTL_H 
+#define _SCSI_IOCTL_H
 
 #define SCSI_IOCTL_SEND_COMMAND 1
 #define SCSI_IOCTL_TEST_UNIT_READY 2
@@ -47,6 +47,20 @@ typedef struct scsi_fctargaddress {
 
 int scsi_ioctl_block_when_processing_errors(struct scsi_device *sdev,
                int cmd, bool ndelay);
+#if defined(CONFIG_UFS_SRPMB)
+typedef struct rpmb_req {
+       u32 cmd;
+       volatile u32 status_flag;
+       u32 type;
+       u32 data_len;
+       u32 inlen;
+       u32 outlen;
+       u8 rpmb_data[0];
+} Rpmb_Req;
+
+extern int srpmb_scsi_ioctl(struct scsi_device *, Rpmb_Req *req);
+#endif
+
 extern int scsi_ioctl(struct scsi_device *, int, void __user *);
 
 #endif /* __KERNEL__ */