556 lines
14 KiB
C
556 lines
14 KiB
C
|
/*
|
||
|
* 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 "dw_mmc-srpmb.h"
|
||
|
|
||
|
#define MMC_SRPMB_DEVICE_PROPNAME "samsung,mmc-srpmb"
|
||
|
#define MMC_BLOCK_NAME "/dev/block/mmcblk0rpmb"
|
||
|
|
||
|
#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 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 update_rpmb_status_flag(struct _mmc_rpmb_ctx *ctx,
|
||
|
struct _mmc_rpmb_req *req, int status)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave(&ctx->lock, flags);
|
||
|
req->status_flag = status;
|
||
|
spin_unlock_irqrestore(&ctx->lock, flags);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static int mmc_rpmb_access(struct _mmc_rpmb_ctx *ctx, struct _mmc_rpmb_req *req)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct device *dev = ctx->dev;
|
||
|
static struct block_device *bdev = NULL;
|
||
|
struct gendisk *disk;
|
||
|
static const struct block_device_operations *fops;
|
||
|
struct mmc_ioc_cmd icmd;
|
||
|
struct rpmb_packet packet;
|
||
|
u8 *result_buf = NULL;
|
||
|
|
||
|
dev_info(dev, "start rpmb workqueue with command(%d)\n", req->type);
|
||
|
|
||
|
/* get block device for mmc rpmb */
|
||
|
if (bdev == NULL) {
|
||
|
bdev = blkdev_get_by_path(MMC_BLOCK_NAME,
|
||
|
FMODE_READ|FMODE_WRITE, NULL);
|
||
|
if (!bdev) {
|
||
|
dev_err(dev, "Fail to get block device for mmc srpmb\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
disk = bdev->bd_disk;
|
||
|
fops = disk->fops;
|
||
|
if (!fops->srpmb_access) {
|
||
|
dev_err(dev, "No function pointer for srpmb access\n");
|
||
|
return -ENOTTY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
wake_lock(&ctx->wakelock);
|
||
|
fops->get_card(bdev, 1);
|
||
|
|
||
|
/* Initialize mmc ioc command */
|
||
|
mmc_cmd_init(&icmd);
|
||
|
|
||
|
switch(req->type) {
|
||
|
case GET_WRITE_COUNTER:
|
||
|
icmd.write_flag = true;
|
||
|
icmd.flags = MMC_RSP_R1;
|
||
|
icmd.opcode = MMC_WRITE_MULTIPLE_BLOCK;
|
||
|
icmd.data_ptr = (unsigned long)req->rpmb_data;
|
||
|
|
||
|
ret = fops->srpmb_access(bdev, &icmd);
|
||
|
if (ret != 0) {
|
||
|
update_rpmb_status_flag(ctx, req,
|
||
|
WRITE_COUNTER_SECURITY_OUT_ERROR);
|
||
|
dev_err(dev, "Fail to execute for srpmb write counter \
|
||
|
security out: %d\n", ret);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
memset(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 != 0) {
|
||
|
update_rpmb_status_flag(ctx, req,
|
||
|
WRITE_COUNTER_SECURITY_IN_ERROR);
|
||
|
dev_err(dev, "Fail to execute for srpmb write counter \
|
||
|
security in: %d\n", ret);
|
||
|
break;
|
||
|
}
|
||
|
if (req->rpmb_data[RPMB_RESULT] || req->rpmb_data[RPMB_RESULT+1]) {
|
||
|
dev_info(dev, "GET_WRITE_COUNTER: REQ/RES = %02x%02x, RESULT = %02x%02x\n",
|
||
|
req->rpmb_data[RPMB_REQRES], req->rpmb_data[RPMB_REQRES+1],
|
||
|
req->rpmb_data[RPMB_RESULT], req->rpmb_data[RPMB_RESULT+1]);
|
||
|
}
|
||
|
|
||
|
update_rpmb_status_flag(ctx, req, RPMB_PASSED);
|
||
|
break;
|
||
|
case WRITE_DATA:
|
||
|
icmd.write_flag = RELIABLE_WRITE_REQ_SET;
|
||
|
icmd.flags = MMC_RSP_R1;
|
||
|
icmd.blocks = req->data_len / RPMB_PACKET_SIZE;
|
||
|
icmd.opcode = MMC_WRITE_MULTIPLE_BLOCK;
|
||
|
icmd.data_ptr = (unsigned long)req->rpmb_data;
|
||
|
|
||
|
if (icmd.blocks == 0) {
|
||
|
dev_err(dev, "Invalid block size from secure world\n"
|
||
|
"cmd(%d), type(%d), data length(%d)\n",
|
||
|
req->cmd, req->type, req->data_len);
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* program data packet */
|
||
|
ret = fops->srpmb_access(bdev, &icmd);
|
||
|
if (ret != 0) {
|
||
|
update_rpmb_status_flag(ctx, req, WRITE_DATA_SECURITY_OUT_ERROR);
|
||
|
dev_err(dev, "Fail to write block for program data: %d\n", ret);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
result_buf = (u8 *)kzalloc(RPMB_PACKET_SIZE, GFP_NOIO);
|
||
|
if (result_buf == NULL) {
|
||
|
dev_err(dev, "Memory allocation failed\n");
|
||
|
ret = -1;
|
||
|
break;
|
||
|
}
|
||
|
icmd.write_flag = true;
|
||
|
icmd.blocks = 1;
|
||
|
icmd.data_ptr = (unsigned long)result_buf;
|
||
|
memset(&packet, 0, RPMB_PACKET_SIZE);
|
||
|
packet.request = RESULT_READ_REQ;
|
||
|
swap_packet((u8 *)&packet, result_buf);
|
||
|
|
||
|
/* result read request */
|
||
|
ret = fops->srpmb_access(bdev, &icmd);
|
||
|
if (ret != 0) {
|
||
|
update_rpmb_status_flag(ctx, req, WRITE_DATA_SECURITY_OUT_ERROR);
|
||
|
dev_err(dev, "Fail to write block for result: %d\n", ret);
|
||
|
goto wout;
|
||
|
}
|
||
|
|
||
|
memset(result_buf, 0, RPMB_PACKET_SIZE);
|
||
|
icmd.write_flag = false;
|
||
|
icmd.blocks = 1;
|
||
|
icmd.opcode = MMC_READ_MULTIPLE_BLOCK;
|
||
|
|
||
|
/* read multiple block for response */
|
||
|
ret = fops->srpmb_access(bdev, &icmd);
|
||
|
if (ret != 0) {
|
||
|
update_rpmb_status_flag(ctx, req, WRITE_DATA_SECURITY_IN_ERROR);
|
||
|
dev_err(dev, "Fail to read block for response: %d\n", ret);
|
||
|
goto wout;
|
||
|
}
|
||
|
if (result_buf[RPMB_RESULT] || result_buf[RPMB_RESULT+1]) {
|
||
|
dev_info(dev, "WRITE_DATA: REQ/RES = %02x%02x, RESULT = %02x%02x\n",
|
||
|
result_buf[RPMB_REQRES], result_buf[RPMB_REQRES+1],
|
||
|
result_buf[RPMB_RESULT], result_buf[RPMB_RESULT+1]);
|
||
|
}
|
||
|
|
||
|
memcpy(req->rpmb_data, result_buf, RPMB_PACKET_SIZE);
|
||
|
update_rpmb_status_flag(ctx, req, RPMB_PASSED);
|
||
|
wout:
|
||
|
kfree(result_buf);
|
||
|
break;
|
||
|
case READ_DATA:
|
||
|
icmd.write_flag = true;
|
||
|
icmd.flags = MMC_RSP_R1;
|
||
|
icmd.opcode = MMC_WRITE_MULTIPLE_BLOCK;
|
||
|
icmd.data_ptr = (unsigned long)req->rpmb_data;
|
||
|
|
||
|
result_buf = (u8 *)kzalloc(RPMB_PACKET_SIZE, GFP_NOIO);
|
||
|
if (result_buf == NULL) {
|
||
|
dev_err(dev, "Memory allocation failed\n");
|
||
|
ret = -1;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* At read data with MMC, block count has to '0' */
|
||
|
swap_packet(req->rpmb_data, result_buf);
|
||
|
((struct rpmb_packet *)(result_buf))->count = 0;
|
||
|
swap_packet(result_buf, req->rpmb_data);
|
||
|
|
||
|
kfree(result_buf);
|
||
|
|
||
|
/* read data packet */
|
||
|
ret = fops->srpmb_access(bdev, &icmd);
|
||
|
if (ret != 0) {
|
||
|
update_rpmb_status_flag(ctx, req, READ_DATA_SECURITY_OUT_ERROR);
|
||
|
dev_err(dev, "Fail to write block for read data: %d\n", ret);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
memset(req->rpmb_data, 0, req->data_len);
|
||
|
icmd.write_flag = false;
|
||
|
icmd.opcode = MMC_READ_MULTIPLE_BLOCK;
|
||
|
icmd.blocks = req->data_len/RPMB_PACKET_SIZE;
|
||
|
|
||
|
if (icmd.blocks == 0) {
|
||
|
dev_err(dev, "Invalid block size from secure world\n"
|
||
|
"cmd(%d), type(%d), data length(%d)\n",
|
||
|
req->cmd, req->type, req->data_len);
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* read multiple block for response */
|
||
|
ret = fops->srpmb_access(bdev, &icmd);
|
||
|
if (ret != 0) {
|
||
|
update_rpmb_status_flag(ctx, req, READ_DATA_SECURITY_IN_ERROR);
|
||
|
dev_err(dev, "Fail to read block for response: %d\n", ret);
|
||
|
break;
|
||
|
}
|
||
|
if (req->rpmb_data[RPMB_RESULT] || req->rpmb_data[RPMB_RESULT+1]) {
|
||
|
dev_info(dev, "READ_DATA: REQ/RES = %02x%02x, RESULT = %02x%02x\n",
|
||
|
req->rpmb_data[RPMB_REQRES], req->rpmb_data[RPMB_REQRES+1],
|
||
|
req->rpmb_data[RPMB_RESULT], req->rpmb_data[RPMB_RESULT+1]);
|
||
|
}
|
||
|
|
||
|
update_rpmb_status_flag(ctx, req, RPMB_PASSED);
|
||
|
break;
|
||
|
default:
|
||
|
dev_err(dev, "Fail to invalid command: %x\n", req->type);
|
||
|
update_rpmb_status_flag(ctx, req, RPMB_INVALID_COMMAND);
|
||
|
ret = -EINVAL;
|
||
|
}
|
||
|
fops->get_card(bdev, 0);
|
||
|
|
||
|
wake_unlock(&ctx->wakelock);
|
||
|
dev_info(dev, "finish rpmb workqueue with command(%d)\n", req->type);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void mmc_rpmb_worker(struct work_struct *work)
|
||
|
{
|
||
|
int ret;
|
||
|
struct device *dev;
|
||
|
struct _mmc_rpmb_ctx *ctx;
|
||
|
struct _mmc_rpmb_req *req;
|
||
|
|
||
|
if (!work) {
|
||
|
printk(KERN_ERR "Fail to get work for mmc rpmb\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ctx = container_of(work, struct _mmc_rpmb_ctx, work);
|
||
|
dev = ctx->dev;
|
||
|
req = (struct _mmc_rpmb_req *)ctx->wsm_virtaddr;
|
||
|
if (!req) {
|
||
|
dev_err(dev, "Invalid wsm virtual address for rpmb\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ret = mmc_rpmb_access(ctx, req);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "Fail to access mmc rpmb\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static int mmc_rpmb_pm_notifier(struct notifier_block *nb, unsigned long event,
|
||
|
void *dummy)
|
||
|
{
|
||
|
struct device *dev;
|
||
|
struct _mmc_rpmb_ctx *ctx;
|
||
|
struct _mmc_rpmb_req *req;
|
||
|
|
||
|
if (!nb) {
|
||
|
printk(KERN_ERR "noti_blk work_struct data invalid\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ctx = container_of(nb, struct _mmc_rpmb_ctx, pm_notifier);
|
||
|
dev = ctx->dev;
|
||
|
req = (struct _mmc_rpmb_req *)ctx->wsm_virtaddr;
|
||
|
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(ctx->srpmb_queue);
|
||
|
update_rpmb_status_flag(ctx, req, RPMB_FAIL_SUSPEND_STATUS);
|
||
|
break;
|
||
|
case PM_POST_SUSPEND:
|
||
|
case PM_POST_HIBERNATION:
|
||
|
case PM_POST_RESTORE:
|
||
|
update_rpmb_status_flag(ctx, req, 0);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static irqreturn_t mmc_rpmb_interrupt(int intr, void *arg)
|
||
|
{
|
||
|
struct _mmc_rpmb_ctx *ctx = (struct _mmc_rpmb_ctx *)arg;
|
||
|
struct device *dev;
|
||
|
struct _mmc_rpmb_req *req;
|
||
|
|
||
|
dev = ctx->dev;
|
||
|
req = (struct _mmc_rpmb_req *)ctx->wsm_virtaddr;
|
||
|
if (!req) {
|
||
|
dev_err(dev, "Invalid wsm address for rpmb\n");
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
update_rpmb_status_flag(ctx, req, RPMB_IN_PROGRESS);
|
||
|
queue_work(ctx->srpmb_queue, &ctx->work);
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
static int init_mmc_srpmb(struct platform_device *pdev, struct _mmc_rpmb_ctx *ctx)
|
||
|
{
|
||
|
int ret;
|
||
|
struct irq_data *rpmb_irqd;
|
||
|
irq_hw_number_t hwirq;
|
||
|
struct device *dev = &pdev->dev;
|
||
|
struct resource *res;
|
||
|
|
||
|
if (!ctx) {
|
||
|
dev_err(dev, "Invalid rpmb context address\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
/* allocation for wsm(world shared memory) */
|
||
|
ctx->wsm_virtaddr = dma_alloc_coherent(dev,
|
||
|
sizeof(struct _mmc_rpmb_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");
|
||
|
goto alloc_wsm_fail;
|
||
|
}
|
||
|
|
||
|
dev_info(dev, "srpmb dma addr: virt_pK(%pK), phy(%llx)\n",
|
||
|
ctx->wsm_virtaddr, (uint64_t)ctx->wsm_phyaddr);
|
||
|
|
||
|
/* get mmc srpmb irq number */
|
||
|
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||
|
if (!res) {
|
||
|
dev_err(dev, "Fail to get IRQ resource\n");
|
||
|
goto get_irq_fail;
|
||
|
}
|
||
|
|
||
|
ctx->irq = res->start;
|
||
|
if (ctx->irq <= 0) {
|
||
|
dev_err(dev, "Fail to get irq number for mmc rpmb\n");
|
||
|
goto get_irq_fail;
|
||
|
}
|
||
|
|
||
|
/* 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");
|
||
|
goto get_irq_fail;
|
||
|
}
|
||
|
|
||
|
/* Get hwirq from irq_data */
|
||
|
hwirq = irqd_to_hwirq(rpmb_irqd);
|
||
|
|
||
|
/* Smc call to transfer wsm address to secure world */
|
||
|
ret = exynos_smc(SMC_SRPMB_WSM, ctx->wsm_phyaddr, hwirq, 0);
|
||
|
if (ret)
|
||
|
dev_err(dev, "Fail to smc call to initial wsm buffer\n");
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
get_irq_fail:
|
||
|
dma_free_coherent(dev, RPMB_BUF_MAX_SIZE, ctx->wsm_virtaddr,
|
||
|
ctx->wsm_phyaddr);
|
||
|
alloc_wsm_fail:
|
||
|
ret = -ENOMEM;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int mmc_srpmb_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
int ret;
|
||
|
struct _mmc_rpmb_ctx *ctx;
|
||
|
struct device *dev = &pdev->dev;
|
||
|
|
||
|
/* allocation for rpmb context */
|
||
|
ctx = kzalloc(sizeof(struct _mmc_rpmb_ctx), GFP_KERNEL);
|
||
|
if (!ctx) {
|
||
|
dev_err(dev, "Fail to alloc for mmc rpmb context\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* initialize mmc rpmb context */
|
||
|
ret = init_mmc_srpmb(pdev, ctx);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "Fail to initialize mmc srpmb\n");
|
||
|
goto ctx_kfree;
|
||
|
}
|
||
|
|
||
|
/* request irq for mmc rpmb handler */
|
||
|
ret = request_irq(ctx->irq, mmc_rpmb_interrupt,
|
||
|
IRQF_TRIGGER_RISING, pdev->name, ctx);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "Fail to request irq handler for mmc srpmb\n");
|
||
|
goto dma_free;
|
||
|
}
|
||
|
|
||
|
ctx->dev = dev;
|
||
|
ctx->pm_notifier.notifier_call = mmc_rpmb_pm_notifier;
|
||
|
|
||
|
ret = register_pm_notifier(&ctx->pm_notifier);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "Fail to setup pm notifier\n");
|
||
|
goto dma_free;
|
||
|
}
|
||
|
|
||
|
INIT_WORK(&ctx->work, mmc_rpmb_worker);
|
||
|
|
||
|
/* initialize workqueue for mmc rpmb handler */
|
||
|
ctx->srpmb_queue = alloc_workqueue("srpmb_wq",
|
||
|
WQ_MEM_RECLAIM | WQ_UNBOUND | WQ_HIGHPRI, 1);
|
||
|
if (!ctx->srpmb_queue) {
|
||
|
dev_err(dev, "Fail to alloc workqueue for mmc srpmb\n");
|
||
|
goto notifier_free;
|
||
|
}
|
||
|
|
||
|
platform_set_drvdata(pdev, ctx);
|
||
|
wake_lock_init(&ctx->wakelock, WAKE_LOCK_SUSPEND, "srpmb");
|
||
|
spin_lock_init(&ctx->lock);
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
notifier_free:
|
||
|
unregister_pm_notifier(&ctx->pm_notifier);
|
||
|
dma_free:
|
||
|
dma_free_coherent(dev, RPMB_BUF_MAX_SIZE, ctx->wsm_virtaddr,
|
||
|
ctx->wsm_phyaddr);
|
||
|
ctx_kfree:
|
||
|
kfree(ctx);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static int mmc_srpmb_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct _mmc_rpmb_ctx *ctx = platform_get_drvdata(pdev);
|
||
|
struct device *dev = &pdev->dev;
|
||
|
|
||
|
if (ctx->srpmb_queue)
|
||
|
destroy_workqueue(ctx->srpmb_queue);
|
||
|
|
||
|
unregister_pm_notifier(&ctx->pm_notifier);
|
||
|
dma_free_coherent(dev, RPMB_BUF_MAX_SIZE, ctx->wsm_virtaddr,
|
||
|
ctx->wsm_phyaddr);
|
||
|
|
||
|
wake_lock_destroy(&ctx->wakelock);
|
||
|
|
||
|
kfree(ctx);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
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,
|
||
|
},
|
||
|
.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);
|
||
|
}
|
||
|
|
||
|
late_initcall(mmc_srpmb_init);
|
||
|
module_exit(mmc_srpmb_exit);
|
||
|
|
||
|
MODULE_AUTHOR("Yongtaek Kwon <ycool.kwon@samsung.com>");
|
||
|
MODULE_LICENSE("GPL");
|
||
|
MODULE_DESCRIPTION("MMC SRPMB driver");
|