From f614f36043a22051774077d4e83ba976f44a9389 Mon Sep 17 00:00:00 2001 From: Shinkyu Park Date: Wed, 24 Jan 2018 21:54:03 +0900 Subject: [PATCH] [COMMON] scsi: rpmb: update RPMB drivers [Description] Initial RPMB drivers on Kernel 4.14 Platform Development Team Shinkyu Park (shinkyu.park@samsung.com) Change-Id: I36a79a54618484f67385327ade35425131220551 --- drivers/scsi/Kconfig | 6 + drivers/scsi/Makefile | 2 + drivers/scsi/scsi_ioctl.c | 171 +++++++++++++++- drivers/scsi/scsi_srpmb.c | 417 ++++++++++++++++++++++++++++++++++++++ drivers/scsi/scsi_srpmb.h | 76 +++++++ drivers/scsi/sd.c | 13 ++ drivers/scsi/sd.h | 4 + include/linux/smc.h | 0 include/scsi/scsi_ioctl.h | 16 +- 9 files changed, 693 insertions(+), 12 deletions(-) create mode 100644 drivers/scsi/scsi_srpmb.c create mode 100644 drivers/scsi/scsi_srpmb.h mode change 100755 => 100644 include/linux/smc.h diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index 41366339b950..83cfb4703841 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -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 diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 1639bf8b1ab6..1738b21c8a08 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -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 \ diff --git a/drivers/scsi/scsi_ioctl.c b/drivers/scsi/scsi_ioctl.c index 0e6c2f15b46a..81a029da8f3a 100644 --- a/drivers/scsi/scsi_ioctl.c +++ b/drivers/scsi/scsi_ioctl.c @@ -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 index 000000000000..64053449e3f2 --- /dev/null +++ b/drivers/scsi/scsi_srpmb.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 "); +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 index 000000000000..e4df7dc3014d --- /dev/null +++ b/drivers/scsi/scsi_srpmb.h @@ -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 + +#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 */ diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index 8d352e7f1d98..032cc1236d01 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -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: diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h index 320de758323e..3fe58ecf338a 100644 --- a/drivers/scsi/sd.h +++ b/drivers/scsi/sd.h @@ -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 diff --git a/include/linux/smc.h b/include/linux/smc.h old mode 100755 new mode 100644 diff --git a/include/scsi/scsi_ioctl.h b/include/scsi/scsi_ioctl.h index 578817848e85..8b8a3f8aefa6 100644 --- a/include/scsi/scsi_ioctl.h +++ b/include/scsi/scsi_ioctl.h @@ -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__ */ -- 2.20.1