/* * 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 "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 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_err(&sr_pdev->dev, "rpmb_ctx invalid\n"); return ; } if (!rpmb_ctx->dev) { dev_err(&sr_pdev->dev, "rpmb_ctx->dev invalid\n"); return ; } sdp = to_scsi_device(rpmb_ctx->dev); if (!sdp) { dev_err(&sr_pdev->dev, "sdp invalid\n"); return ; } if (!rpmb_ctx->vir_addr) { dev_err(&sr_pdev->dev, "rpmb_ctx->vir_addr invalid\n"); return ; } req = (Rpmb_Req *)rpmb_ctx->vir_addr; switch(req->type) { case GET_WRITE_COUNTER: if (req->data_len != RPMB_PACKET_SIZE) { req->status_flag = 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) { req->status_flag = 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) { req->status_flag = WRITE_COUTNER_SECURITY_IN_ERROR; dev_err(&sr_pdev->dev, "ioctl error : %x\n", ret); break; } req->status_flag = PASS_STATUS; break; case WRITE_DATA: if (req->data_len < RPMB_PACKET_SIZE || req->data_len > RPMB_PACKET_SIZE * 64) { req->status_flag = 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) { req->status_flag = 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) { req->status_flag = 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) { req->status_flag = 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) { req->status_flag = PASS_STATUS; } else { req->status_flag = 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) { req->status_flag = 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) { req->status_flag = 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) { req->status_flag = READ_DATA_SECURITY_IN_ERROR; dev_err(&sr_pdev->dev, "ioctl result read data error : %x\n", ret); break; } req->status_flag = PASS_STATUS; break; default: dev_err(&sr_pdev->dev, "invalid requset type : %x\n", req->type); } } static irqreturn_t rpmb_irq_handler(int intr, void *arg) { struct rpmb_irq_ctx *rpmb_ctx = (struct rpmb_irq_ctx *)arg; INIT_WORK(&rpmb_ctx->work, srpmb_worker); 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, RPMB_BUF_MAX_SIZE, &rpmb_ctx->phy_addr, GFP_KERNEL); if (rpmb_ctx->vir_addr && 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"); return -EINVAL; } /* 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"); return -EINVAL; } /* 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, 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; } 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_init_fail; } } else { dev_err(&sr_pdev->dev, "wsm dma alloc failed\n"); goto out_srpmb_dma_alloc_fail; } return 0; 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");