| /* |
| * Secure RPMB Driver for Exynos MMC 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/fs.h> |
| #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/smc.h> |
| #include <linux/blkdev.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/mmc/core.h> |
| #include <linux/mmc/ioctl.h> |
| #include <linux/mmc/mmc.h> |
| #include <linux/delay.h> |
| #include <linux/wakelock.h> |
| #include <linux/suspend.h> |
| #include <linux/kthread.h> |
| |
| #include "tz_iwsock.h" |
| |
| #define GET_WRITE_COUNTER 1 |
| #define WRITE_DATA 2 |
| #define READ_DATA 3 |
| |
| #define RESULT_READ_REQ 0x0005 |
| |
| #define RPMB_PACKET_SIZE 512 |
| #define RPMB_REQRES 510 |
| #define RPMB_RESULT 508 |
| |
| #define WRITE_COUNTER_SECURITY_OUT_ERROR 0x602 |
| #define WRITE_COUNTER_SECURITY_IN_ERROR 0x603 |
| #define WRITE_DATA_SECURITY_OUT_ERROR 0x605 |
| #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 RPMB_BUF_MAX_SIZE (32 * 1024) |
| #define RELIABLE_WRITE_REQ_SET (1 << 31) |
| |
| #define MMC_SRPMB_DEVICE_PROPNAME "samsung,mmc-srpmb" |
| #define MMC_BLOCK_NAME "/dev/block/mmcblk0rpmb" |
| #define TZ_RPMB_SOCK_NAME "rpmb_socket" |
| |
| #define RPMB_REQUEST_MAGIC 0x44444444 |
| #define RPMB_REPLY_MAGIC 0x66666666 |
| |
| struct mmc_srpmb_req { |
| uint32_t cmd; |
| volatile uint32_t status_flag; |
| uint32_t type; |
| uint32_t data_len; |
| uint32_t inlen; |
| uint32_t outlen; |
| uint8_t rpmb_data[0]; |
| }; |
| |
| struct rpmb_packet { |
| uint16_t request; |
| uint16_t result; |
| uint16_t count; |
| uint16_t address; |
| uint32_t write_counter; |
| uint8_t nonce[16]; |
| uint8_t data[256]; |
| uint8_t Key_MAC[32]; |
| uint8_t stuff[196]; |
| }; |
| |
| struct mmc_srpmb_ctx { |
| struct platform_device *pdev; |
| struct block_device *bdev; |
| const struct block_device_operations *fops; |
| |
| int irq; |
| irq_hw_number_t hwirq; |
| |
| void *wsm_virtaddr; |
| dma_addr_t wsm_phyaddr; |
| |
| struct mmc_srpmb_req *req; |
| spinlock_t lock; |
| |
| struct wake_lock wakelock; |
| |
| struct rpmb_packet packet; |
| struct rpmb_packet result_packet; |
| }; |
| |
| static struct task_struct *srpmb_kthread; |
| |
| static void swap_packet(struct rpmb_packet *src, struct rpmb_packet *dst) |
| { |
| int i; |
| char *src_buf = (char *)src; |
| char *dst_buf = (char *)dst; |
| |
| for (i = 0; i < RPMB_PACKET_SIZE; i++) |
| dst_buf[i] = src_buf[RPMB_PACKET_SIZE - 1 - i]; |
| } |
| |
| static void mmc_cmd_init(struct mmc_ioc_cmd *icmd) |
| { |
| icmd->is_acmd = false; |
| icmd->arg = 0; |
| icmd->flags = MMC_RSP_R1; |
| icmd->blksz = RPMB_PACKET_SIZE; |
| icmd->blocks = 1; |
| icmd->postsleep_min_us = 0; |
| icmd->postsleep_max_us = 0; |
| icmd->data_timeout_ns = 0; |
| icmd->cmd_timeout_ms = 0; |
| } |
| |
| static void mmc_srpmb_update_status_flag(struct mmc_srpmb_ctx *ctx, int status) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&ctx->lock, flags); |
| ctx->req->status_flag = status; |
| spin_unlock_irqrestore(&ctx->lock, flags); |
| } |
| |
| static void mmc_srpmb_get_write_counter(struct mmc_srpmb_ctx *ctx) |
| { |
| int ret; |
| unsigned int status; |
| struct mmc_ioc_cmd icmd; |
| struct device *dev = &ctx->pdev->dev; |
| struct block_device *bdev = ctx->bdev; |
| const struct block_device_operations *fops = ctx->fops; |
| |
| mmc_cmd_init(&icmd); |
| |
| icmd.write_flag = true; |
| icmd.flags = MMC_RSP_R1; |
| icmd.opcode = MMC_WRITE_MULTIPLE_BLOCK; |
| icmd.data_ptr = (unsigned long)ctx->req->rpmb_data; |
| |
| wake_lock(&ctx->wakelock); |
| fops->get_card(bdev, 1); |
| |
| mmc_srpmb_update_status_flag(ctx, RPMB_IN_PROGRESS); |
| |
| ret = fops->srpmb_access(bdev, &icmd); |
| if (ret) { |
| status = WRITE_COUNTER_SECURITY_OUT_ERROR; |
| dev_err(dev, "Fail to execute for srpmb write counter security out: %d\n", ret); |
| goto out; |
| } |
| |
| memset(ctx->req->rpmb_data, 0, RPMB_PACKET_SIZE); |
| icmd.write_flag = false; |
| icmd.flags = MMC_RSP_R1; |
| icmd.opcode = MMC_READ_MULTIPLE_BLOCK; |
| |
| ret = fops->srpmb_access(bdev, &icmd); |
| if (ret) { |
| status = WRITE_COUNTER_SECURITY_IN_ERROR; |
| dev_err(dev, "Fail to execute for srpmb write counter security in: %d\n", ret); |
| goto out; |
| } |
| if (ctx->req->rpmb_data[RPMB_RESULT] || ctx->req->rpmb_data[RPMB_RESULT+1]) { |
| dev_info(dev, "GET_WRITE_COUNTER: REQ/RES = %02x%02x, RESULT = %02x%02x\n", |
| ctx->req->rpmb_data[RPMB_REQRES], ctx->req->rpmb_data[RPMB_REQRES+1], |
| ctx->req->rpmb_data[RPMB_RESULT], ctx->req->rpmb_data[RPMB_RESULT+1]); |
| } |
| status = RPMB_PASSED; |
| out: |
| mmc_srpmb_update_status_flag(ctx, status); |
| fops->get_card(bdev, 0); |
| wake_unlock(&ctx->wakelock); |
| } |
| |
| static void mmc_srpmb_write(struct mmc_srpmb_ctx *ctx) |
| { |
| int ret; |
| unsigned int status; |
| struct mmc_ioc_cmd icmd; |
| struct device *dev = &ctx->pdev->dev; |
| struct block_device *bdev = ctx->bdev; |
| const struct block_device_operations *fops = ctx->fops; |
| |
| mmc_cmd_init(&icmd); |
| |
| icmd.write_flag = RELIABLE_WRITE_REQ_SET; |
| icmd.flags = MMC_RSP_R1; |
| icmd.blocks = ctx->req->data_len / RPMB_PACKET_SIZE; |
| icmd.opcode = MMC_WRITE_MULTIPLE_BLOCK; |
| icmd.data_ptr = (unsigned long)ctx->req->rpmb_data; |
| |
| if (!icmd.blocks) { |
| dev_err(dev, "Invalid block size from secure world\n" |
| "cmd(%d), type(%d), data length(%d)\n", |
| ctx->req->cmd, ctx->req->type, ctx->req->data_len); |
| return; |
| } |
| |
| wake_lock(&ctx->wakelock); |
| fops->get_card(bdev, 1); |
| |
| mmc_srpmb_update_status_flag(ctx, RPMB_IN_PROGRESS); |
| |
| ret = fops->srpmb_access(bdev, &icmd); |
| if (ret) { |
| status = WRITE_DATA_SECURITY_OUT_ERROR; |
| dev_err(dev, "Fail to write block for program data: %d\n", ret); |
| goto out; |
| } |
| |
| icmd.write_flag = true; |
| icmd.blocks = 1; |
| icmd.data_ptr = (unsigned long)&ctx->result_packet; |
| |
| memset(&ctx->packet, 0, RPMB_PACKET_SIZE); |
| ctx->packet.request = RESULT_READ_REQ; |
| swap_packet(&ctx->packet, &ctx->result_packet); |
| |
| ret = fops->srpmb_access(bdev, &icmd); |
| if (ret) { |
| status = WRITE_DATA_SECURITY_OUT_ERROR; |
| dev_err(dev, "Fail to write block for result: %d\n", ret); |
| goto out; |
| } |
| |
| memset(&ctx->result_packet, 0, RPMB_PACKET_SIZE); |
| icmd.write_flag = false; |
| icmd.blocks = 1; |
| icmd.opcode = MMC_READ_MULTIPLE_BLOCK; |
| |
| ret = fops->srpmb_access(bdev, &icmd); |
| if (ret) { |
| status = WRITE_DATA_SECURITY_IN_ERROR; |
| dev_err(dev, "Fail to read block for response: %d\n", ret); |
| goto out; |
| } |
| if (ctx->req->rpmb_data[RPMB_RESULT] || ctx->req->rpmb_data[RPMB_RESULT+1]) { |
| dev_info(dev, "WRITE_DATA: REQ/RES = %02x%02x, RESULT = %02x%02x\n", |
| ctx->req->rpmb_data[RPMB_REQRES], ctx->req->rpmb_data[RPMB_REQRES+1], |
| ctx->req->rpmb_data[RPMB_RESULT], ctx->req->rpmb_data[RPMB_RESULT+1]); |
| } |
| memcpy(&ctx->req->rpmb_data, &ctx->result_packet, RPMB_PACKET_SIZE); |
| status = RPMB_PASSED; |
| out: |
| mmc_srpmb_update_status_flag(ctx, status); |
| fops->get_card(bdev, 0); |
| wake_unlock(&ctx->wakelock); |
| } |
| |
| static void mmc_srpmb_read(struct mmc_srpmb_ctx *ctx) |
| { |
| int ret; |
| unsigned int status; |
| struct mmc_ioc_cmd icmd; |
| struct device *dev = &ctx->pdev->dev; |
| struct block_device *bdev = ctx->bdev; |
| const struct block_device_operations *fops = ctx->fops; |
| |
| mmc_cmd_init(&icmd); |
| |
| icmd.write_flag = true; |
| icmd.flags = MMC_RSP_R1; |
| icmd.opcode = MMC_WRITE_MULTIPLE_BLOCK; |
| icmd.data_ptr = (unsigned long)ctx->req->rpmb_data; |
| |
| wake_lock(&ctx->wakelock); |
| fops->get_card(bdev, 1); |
| |
| mmc_srpmb_update_status_flag(ctx, RPMB_IN_PROGRESS); |
| |
| swap_packet((struct rpmb_packet *)ctx->req->rpmb_data, &ctx->result_packet); |
| ctx->result_packet.count = 0; |
| swap_packet(&ctx->result_packet, (struct rpmb_packet *)ctx->req->rpmb_data); |
| |
| ret = fops->srpmb_access(bdev, &icmd); |
| if (ret) { |
| status = READ_DATA_SECURITY_OUT_ERROR; |
| dev_err(dev, "Fail to write block for read data: %d\n", ret); |
| goto out; |
| } |
| |
| memset(ctx->req->rpmb_data, 0, ctx->req->data_len); |
| icmd.write_flag = false; |
| icmd.opcode = MMC_READ_MULTIPLE_BLOCK; |
| icmd.blocks = ctx->req->data_len/RPMB_PACKET_SIZE; |
| |
| if (!icmd.blocks) { |
| status = READ_LEN_ERROR; |
| dev_err(dev, "Invalid block size from secure world\n" |
| "cmd(%d), type(%d), data length(%d)\n", |
| ctx->req->cmd, ctx->req->type, ctx->req->data_len); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| ret = fops->srpmb_access(bdev, &icmd); |
| if (ret) { |
| status = READ_DATA_SECURITY_IN_ERROR; |
| dev_err(dev, "Fail to read block for response: %d\n", ret); |
| goto out; |
| } |
| if (ctx->req->rpmb_data[RPMB_RESULT] || ctx->req->rpmb_data[RPMB_RESULT+1]) { |
| dev_info(dev, "READ_DATA: REQ/RES = %02x%02x, RESULT = %02x%02x\n", |
| ctx->req->rpmb_data[RPMB_REQRES], ctx->req->rpmb_data[RPMB_REQRES+1], |
| ctx->req->rpmb_data[RPMB_RESULT], ctx->req->rpmb_data[RPMB_RESULT+1]); |
| } |
| status = RPMB_PASSED; |
| out: |
| mmc_srpmb_update_status_flag(ctx, status); |
| fops->get_card(bdev, 0); |
| wake_unlock(&ctx->wakelock); |
| } |
| |
| static struct sock_desc *mmc_srpmb_accept_swd_connection(struct device *dev) |
| { |
| int ret; |
| struct sock_desc *srpmb_conn; |
| struct sock_desc *srpmb_listen; |
| |
| srpmb_listen = tz_iwsock_socket(1); |
| if (IS_ERR(srpmb_listen)) { |
| dev_err(dev, "failed to create iwd socket, err = %ld\n", PTR_ERR(srpmb_listen)); |
| return srpmb_listen; |
| } |
| dev_info(dev, "Created socket\n"); |
| |
| ret = tz_iwsock_listen(srpmb_listen, TZ_RPMB_SOCK_NAME); |
| if (ret) { |
| dev_err(dev, "failed make iwd socket listening, err = %d\n", ret); |
| srpmb_conn = ERR_PTR(ret); |
| goto out; |
| } |
| dev_info(dev, "Make socket listening\n"); |
| |
| /* Accept connection */ |
| srpmb_conn = tz_iwsock_accept(srpmb_listen); |
| if (IS_ERR(srpmb_conn)) { |
| dev_err(dev, "failed to accept connection, err = %ld\n", PTR_ERR(srpmb_conn)); |
| goto out; |
| } |
| dev_info(dev, "Accepted connection\n"); |
| out: |
| tz_iwsock_release(srpmb_listen); |
| |
| return srpmb_conn; |
| } |
| |
| static void mmc_srpmb_release_connection(struct sock_desc *srpmb_conn) |
| { |
| tz_iwsock_release(srpmb_conn); |
| } |
| |
| static int mmc_srpmb_wait_request(struct sock_desc *srpmb_conn, struct device *dev) |
| { |
| int ret; |
| ssize_t len; |
| unsigned int sock_data; |
| |
| len = tz_iwsock_read(srpmb_conn, &sock_data, sizeof(sock_data), 0); |
| if (len > 0 && len != sizeof(sock_data)) { |
| dev_err(dev, "failed to receive request, invalid len = %zd\n", len); |
| return -EMSGSIZE; |
| } else if (!len) { |
| dev_err(dev, "connection was reset by peer\n"); |
| return -ECONNRESET; |
| } else if (len < 0) { |
| ret = len; |
| dev_err(dev, "error while receiving request from SWd, err = %u\n", ret); |
| return ret; |
| } |
| dev_info(dev, "Read request\n"); |
| |
| if (sock_data != RPMB_REQUEST_MAGIC) { |
| dev_err(dev, "received invalid request, data = %u\n", sock_data); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int mmc_srpmb_send_reply(struct sock_desc *srpmb_conn, struct device *dev) |
| { |
| int ret = 0; |
| ssize_t len; |
| unsigned int sock_data; |
| |
| sock_data = RPMB_REPLY_MAGIC; |
| len = tz_iwsock_write(srpmb_conn, &sock_data, sizeof(sock_data), 0); |
| if (len != sizeof(sock_data)) { |
| ret = len >= 0 ? -EMSGSIZE : len; |
| dev_err(dev, "failed to send reply, err = %d\n", ret); |
| } else { |
| dev_info(dev, "Sent reply\n"); |
| } |
| |
| return ret; |
| } |
| |
| static int mmc_srpmb_handle_unsupported_ops(struct mmc_srpmb_ctx *ctx) |
| { |
| dev_err(&ctx->pdev->dev, "Received unsupported request: %x\n", ctx->req->type); |
| mmc_srpmb_update_status_flag(ctx, RPMB_INVALID_COMMAND); |
| |
| return -EINVAL; |
| } |
| |
| static void release_mmc_srpmb_blkdev(struct mmc_srpmb_ctx *ctx) |
| { |
| blkdev_put(ctx->bdev, FMODE_READ|FMODE_WRITE); |
| ctx->bdev = NULL; |
| } |
| |
| static int init_mmc_srpmb_blkdev(struct mmc_srpmb_ctx *ctx) |
| { |
| int ret; |
| struct device *dev = &ctx->pdev->dev; |
| |
| /* get block device for mmc rpmb */ |
| ctx->bdev = blkdev_get_by_path(MMC_BLOCK_NAME, FMODE_READ|FMODE_WRITE, NULL); |
| if (!ctx->bdev) { |
| dev_err(dev, "Fail to get block device\n"); |
| return -ENOENT; |
| } |
| dev_info(dev, "Found block device\n"); |
| |
| if (!ctx->bdev->bd_disk) { |
| dev_err(dev, "Fail to get gendisk\n"); |
| ret = -ENOENT; |
| goto put_blkdev; |
| } |
| |
| ctx->fops = ctx->bdev->bd_disk->fops; |
| if (!ctx->fops->srpmb_access) { |
| dev_err(dev, "No valid function pointer\n"); |
| ret = -ENOTTY; |
| goto put_blkdev; |
| } |
| dev_info(dev, "Initialized block device fops\n"); |
| |
| return 0; |
| |
| put_blkdev: |
| release_mmc_srpmb_blkdev(ctx); |
| |
| return ret; |
| } |
| |
| static int mmc_srpmb_kthread(void *data) |
| { |
| int ret; |
| struct sock_desc *srpmb_conn; |
| struct mmc_srpmb_ctx *ctx = (struct mmc_srpmb_ctx *)data; |
| struct device *dev = &ctx->pdev->dev; |
| |
| srpmb_conn = mmc_srpmb_accept_swd_connection(dev); |
| if (IS_ERR(srpmb_conn)) |
| return PTR_ERR(srpmb_conn); |
| |
| while (!kthread_should_stop()) { |
| ret = mmc_srpmb_wait_request(srpmb_conn, dev); |
| if (ret) |
| goto out; |
| |
| /* There is no any way to wait for rpmb block device creation, |
| * so in case there is no block rpmb device yet we just notify |
| * secure OS about request completion but we doesn't change |
| * status in shared memory to enable secure OS to detect, |
| * transaction error */ |
| if (!ctx->bdev) { |
| ret = init_mmc_srpmb_blkdev(ctx); |
| if (ret) { |
| dev_err(dev, "Fail to initialize blkdev\n"); |
| goto send_reply; |
| } |
| } |
| |
| switch(ctx->req->type) { |
| case GET_WRITE_COUNTER: |
| mmc_srpmb_get_write_counter(ctx); |
| break; |
| case WRITE_DATA: |
| mmc_srpmb_write(ctx); |
| break; |
| case READ_DATA: |
| mmc_srpmb_read(ctx); |
| break; |
| default: |
| ret = mmc_srpmb_handle_unsupported_ops(ctx); |
| goto out; |
| } |
| send_reply: |
| ret = mmc_srpmb_send_reply(srpmb_conn, dev); |
| if (ret) |
| goto out; |
| } |
| |
| out: |
| if (ctx->bdev) |
| release_mmc_srpmb_blkdev(ctx); |
| |
| mmc_srpmb_release_connection(srpmb_conn); |
| |
| return ret; |
| } |
| |
| static struct mmc_srpmb_ctx *create_mmc_srpmb_ctx(struct platform_device *pdev) |
| { |
| struct mmc_srpmb_ctx *ctx; |
| struct device *dev = &pdev->dev; |
| |
| /* allocation for rpmb context */ |
| ctx = kzalloc(sizeof(struct mmc_srpmb_ctx), GFP_KERNEL); |
| if (!ctx) { |
| dev_err(dev, "Fail to alloc for mmc rpmb context\n"); |
| return ERR_PTR(-ENOMEM); |
| } |
| dev_info(dev, "Allocated mmc_rpmb context\n"); |
| |
| ctx->pdev = pdev; |
| |
| spin_lock_init(&ctx->lock); |
| wake_lock_init(&ctx->wakelock, WAKE_LOCK_SUSPEND, "srpmb"); |
| |
| return ctx; |
| } |
| |
| static void destroy_mmc_srpmb_ctx(struct mmc_srpmb_ctx *ctx) |
| { |
| wake_lock_destroy(&ctx->wakelock); |
| kfree(ctx); |
| } |
| |
| static int init_mmc_srpmb_wsm(struct mmc_srpmb_ctx *ctx) |
| { |
| struct device *dev = &ctx->pdev->dev; |
| |
| /* allocation for wsm(world shared memory) */ |
| ctx->wsm_virtaddr = dma_alloc_coherent(dev, |
| sizeof(struct mmc_srpmb_req) + RPMB_BUF_MAX_SIZE, |
| &ctx->wsm_phyaddr, GFP_KERNEL); |
| if (!ctx->wsm_virtaddr || !ctx->wsm_phyaddr) { |
| dev_err(dev, "Fail to alloc for srpmb wsm (world shared memory)\n"); |
| return -ENOMEM; |
| } |
| |
| dev_info(dev, "srpmb dma addr: virt(%llx), phy(%llx)\n", |
| (uint64_t)ctx->wsm_virtaddr, (uint64_t)ctx->wsm_phyaddr); |
| |
| ctx->req = (struct mmc_srpmb_req *)ctx->wsm_virtaddr; |
| |
| return 0; |
| } |
| |
| static void release_mmc_srpmb_wsm(struct mmc_srpmb_ctx *ctx) |
| { |
| struct device *dev = &ctx->pdev->dev; |
| |
| dma_free_coherent(dev, RPMB_BUF_MAX_SIZE, ctx->wsm_virtaddr, ctx->wsm_phyaddr); |
| } |
| |
| static irqreturn_t mmc_srpmb_interrupt(int intr, void *arg) |
| { |
| return IRQ_HANDLED; |
| } |
| |
| static int init_mmc_srpmb_irq(struct mmc_srpmb_ctx *ctx) |
| { |
| int ret; |
| struct resource *res; |
| struct irq_data *rpmb_irqd; |
| struct device *dev = &ctx->pdev->dev; |
| |
| /* get mmc srpmb irq number */ |
| res = platform_get_resource(ctx->pdev, IORESOURCE_IRQ, 0); |
| if (!res) { |
| dev_err(dev, "Fail to get IRQ resource\n"); |
| return -ENOENT; |
| } |
| dev_info(dev, "Requested mmc_rpmb IRQ resource\n"); |
| |
| ctx->irq = res->start; |
| if (ctx->irq <= 0) { |
| dev_err(dev, "Fail to get irq number for mmc rpmb\n"); |
| return -ENOENT; |
| } |
| |
| /* Get irq_data from irq number */ |
| rpmb_irqd = irq_get_irq_data(ctx->irq); |
| if (!rpmb_irqd) { |
| dev_err(dev, "Fail to get irq_data from irq number\n"); |
| return -ENOENT; |
| } |
| dev_info(dev, "Requested IRQ data\n"); |
| |
| /* Get hwirq from irq_data */ |
| ctx->hwirq = irqd_to_hwirq(rpmb_irqd); |
| |
| /* request irq for mmc rpmb handler */ |
| ret = request_irq(ctx->irq, mmc_srpmb_interrupt, |
| IRQF_TRIGGER_RISING, ctx->pdev->name, ctx); |
| if (ret) { |
| dev_err(dev, "Fail to request irq handler for mmc srpmb, error=%d\n", ret); |
| return ret; |
| } |
| dev_info(dev, "Requested IRQ %d\n", ctx->irq); |
| |
| return 0; |
| } |
| |
| static void release_mmc_srpmb_irq(struct mmc_srpmb_ctx *ctx) |
| { |
| free_irq(ctx->irq, ctx); |
| } |
| |
| static int register_mmc_srpmb_resources(struct mmc_srpmb_ctx *ctx) |
| { |
| int ret; |
| struct device *dev = &ctx->pdev->dev; |
| |
| /* Smc call to transfer wsm address to secure world */ |
| ret = exynos_smc(SMC_SRPMB_WSM, ctx->wsm_phyaddr, ctx->hwirq, 0); |
| if (ret) |
| dev_err(dev, "Fail to smc call to registed mmc srpmb resources: error=%d\n", ret); |
| |
| dev_info(dev, "Registered WSM\n"); |
| |
| return ret; |
| } |
| |
| static int mmc_srpmb_probe(struct platform_device *pdev) |
| { |
| int ret; |
| struct mmc_srpmb_ctx *ctx; |
| struct device *dev = &pdev->dev; |
| |
| BUILD_BUG_ON(sizeof(struct rpmb_packet) != RPMB_PACKET_SIZE); |
| |
| ctx = create_mmc_srpmb_ctx(pdev); |
| if (IS_ERR(ctx)) { |
| dev_err(dev, "Fail to alloc context: error=%ld\n", PTR_ERR(ctx)); |
| return PTR_ERR(ctx); |
| } |
| |
| ret = init_mmc_srpmb_wsm(ctx); |
| if (ret) { |
| dev_err(dev, "Fail to initialize wsm\n"); |
| goto destroy_ctx; |
| } |
| |
| ret = init_mmc_srpmb_irq(ctx); |
| if (ret) { |
| dev_err(dev, "Fail to initialize irq\n"); |
| goto release_wsm; |
| } |
| |
| ret = register_mmc_srpmb_resources(ctx); |
| if (ret) { |
| dev_err(dev, "Fail to register resources\n"); |
| goto release_irq; |
| } |
| |
| platform_set_drvdata(pdev, ctx); |
| dev_info(dev, "Published context\n"); |
| |
| srpmb_kthread = kthread_run(mmc_srpmb_kthread, ctx, "mmc_srpmb_worker"); |
| if (IS_ERR(srpmb_kthread)) { |
| /* Here we can't release IRQ ir WSM due to ATF |
| * doesn't support such option, so system can crash |
| * if we release IRQ or WSM in here without |
| * releasing it in ATF */ |
| return PTR_ERR(srpmb_kthread); |
| } |
| |
| dev_info(dev, "Started kernel thread\n"); |
| dev_info(dev, "Probe done\n"); |
| |
| return ret; |
| |
| release_irq: |
| release_mmc_srpmb_irq(ctx); |
| release_wsm: |
| release_mmc_srpmb_wsm(ctx); |
| destroy_ctx: |
| destroy_mmc_srpmb_ctx(ctx); |
| |
| return ret; |
| } |
| |
| static int mmc_srpmb_remove(struct platform_device *pdev) |
| { |
| struct mmc_srpmb_ctx *ctx = platform_get_drvdata(pdev); |
| |
| kthread_stop(srpmb_kthread); |
| |
| release_mmc_srpmb_irq(ctx); |
| release_mmc_srpmb_wsm(ctx); |
| destroy_mmc_srpmb_ctx(ctx); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM |
| static int mmc_srpmb_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| struct mmc_srpmb_ctx *ctx = platform_get_drvdata(pdev); |
| |
| mmc_srpmb_update_status_flag(ctx, RPMB_FAIL_SUSPEND_STATUS); |
| |
| return 0; |
| } |
| |
| static int mmc_srpmb_resume(struct platform_device *pdev) |
| { |
| struct mmc_srpmb_ctx *ctx = platform_get_drvdata(pdev); |
| |
| mmc_srpmb_update_status_flag(ctx, 0); |
| |
| return 0; |
| } |
| #else |
| #define mmc_srpmb_suspend NULL |
| #define mmc_srpmb_resume NULL |
| #endif |
| |
| static const struct of_device_id of_match_table[] = { |
| { .compatible = MMC_SRPMB_DEVICE_PROPNAME }, |
| { } |
| }; |
| |
| static struct platform_driver mmc_srpmb_plat_driver = { |
| .probe = mmc_srpmb_probe, |
| .driver = { |
| .name = "exynos-mmc-srpmb", |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_table, |
| }, |
| .suspend = mmc_srpmb_suspend, |
| .resume = mmc_srpmb_resume, |
| .remove = mmc_srpmb_remove, |
| }; |
| |
| static int __init mmc_srpmb_init(void) |
| { |
| return platform_driver_register(&mmc_srpmb_plat_driver); |
| } |
| |
| static void __exit mmc_srpmb_exit(void) |
| { |
| platform_driver_unregister(&mmc_srpmb_plat_driver); |
| } |
| |
| module_init(mmc_srpmb_init); |
| module_exit(mmc_srpmb_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("MMC SRPMB driver"); |