@@ -9706,6 +9706,13 @@ F: include/net/rose.h
F: include/uapi/linux/rose.h
F: net/rose/
+RPMB SUBSYSTEM
+M: Tomas Winkler <tomas.winkler@intel.com>
+L: linux-kernel@vger.kernel.org
+S: Supported
+F: drivers/char/rpmb/*
+F: include/linux/rpmb.h
+
RTL2830 MEDIA DRIVER
M: Antti Palosaari <crope@iki.fi>
L: linux-media@vger.kernel.org
@@ -602,5 +602,7 @@ config TILE_SROM
source "drivers/char/xillybus/Kconfig"
+source "drivers/char/rpmb/Kconfig"
+
endmenu
@@ -60,3 +60,4 @@ js-rtc-y = rtc.o
obj-$(CONFIG_TILE_SROM) += tile-srom.o
obj-$(CONFIG_XILLYBUS) += xillybus/
+obj-$(CONFIG_RPMB) += rpmb/
new file mode 100644
@@ -0,0 +1,8 @@
+config RPMB
+ tristate "RPMB partition interface"
+ help
+ Unified RPMB partition interface for eMMC and UFS.
+ Provides interface for in kernel security controllers to
+ access RPMB partition.
+
+ If unsure, select N.
new file mode 100644
@@ -0,0 +1,4 @@
+obj-$(CONFIG_RPMB) += rpmb.o
+rpmb-objs += core.o
+
+ccflags-y += -D__CHECK_ENDIAN__
new file mode 100644
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2015-2016 Intel Corp. All rights reserved
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+
+#include <linux/rpmb.h>
+
+static DEFINE_IDA(rpmb_ida);
+
+/**
+ * rpmb_dev_get - increase rpmb device ref counter
+ *
+ * @rdev: rpmb device
+ */
+struct rpmb_dev *rpmb_dev_get(struct rpmb_dev *rdev)
+{
+ return get_device(&rdev->dev) ? rdev : NULL;
+}
+EXPORT_SYMBOL_GPL(rpmb_dev_get);
+
+/**
+ * rpmb_dev_put - decrease rpmb device ref counter
+ *
+ * @rdev: rpmb device
+ */
+void rpmb_dev_put(struct rpmb_dev *rdev)
+{
+ put_device(&rdev->dev);
+}
+EXPORT_SYMBOL_GPL(rpmb_dev_put);
+
+static int rpmb_request_verify(struct rpmb_dev *rdev, struct rpmb_data *rpmbd)
+{
+ u16 req_type, block_count;
+ struct rpmb_cmd *in_cmd = &rpmbd->icmd;
+ struct rpmb_cmd *out_cmd = &rpmbd->ocmd;
+
+ if (!in_cmd->frames || !in_cmd->nframes ||
+ !out_cmd->frames || !out_cmd->nframes)
+ return -EINVAL;
+
+ req_type = be16_to_cpu(in_cmd->frames[0].req_resp);
+ block_count = be16_to_cpu(in_cmd->frames[0].block_count);
+
+ if (rpmbd->req_type != req_type) {
+ dev_err(&rdev->dev, "rpmb req type doesn't match 0x%04X = 0x%04X\n",
+ req_type, rpmbd->req_type);
+ return -EINVAL;
+ }
+
+ switch (req_type) {
+ case RPMB_PROGRAM_KEY:
+ dev_dbg(&rdev->dev, "rpmb program key = 0x%1x blk = %d\n",
+ req_type, block_count);
+ break;
+ case RPMB_GET_WRITE_COUNTER:
+ dev_dbg(&rdev->dev, "rpmb get write counter = 0x%1x blk = %d\n",
+ req_type, block_count);
+
+ break;
+ case RPMB_WRITE_DATA:
+ dev_dbg(&rdev->dev, "rpmb write data = 0x%1x blk = %d\n",
+ req_type, block_count);
+
+ if (rdev->ops->reliable_wr_cnt &&
+ block_count > rdev->ops->reliable_wr_cnt) {
+ dev_err(&rdev->dev, "rpmb write data: block count %u > reliable wr count %u\n",
+ block_count, rdev->ops->reliable_wr_cnt);
+ return -EINVAL;
+ }
+
+ if (block_count > in_cmd->nframes) {
+ dev_err(&rdev->dev, "rpmb write data: block count %u > in frame count %u\n",
+ block_count, in_cmd->nframes);
+ return -EINVAL;
+ }
+ break;
+ case RPMB_READ_DATA:
+ dev_dbg(&rdev->dev, "rpmb read data = 0x%1x blk = %d\n",
+ req_type, block_count);
+
+ if (block_count > out_cmd->nframes) {
+ dev_err(&rdev->dev, "rpmb read data: block count %u > out frame count %u\n",
+ block_count, out_cmd->nframes);
+ return -EINVAL;
+ }
+ break;
+ case RPMB_RESULT_READ:
+ /* Internal command not supported */
+ dev_err(&rdev->dev, "NOTSUPPORTED rpmb resut read = 0x%1x blk = %d\n",
+ req_type, block_count);
+ return -EOPNOTSUPP;
+
+ default:
+ dev_err(&rdev->dev, "Error rpmb invalid command = 0x%1x blk = %d\n",
+ req_type, block_count);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * rpmb_cmd_seq - send RPMB command sequence
+ *
+ * @rdev: rpmb device
+ * @cmds: rpmb command list
+ * @ncmds: number of commands
+ *
+ * Return: 0 on success
+ * -EINVAL on wrong parameters
+ * -EOPNOTSUPP if device doesn't support the requested operation
+ * < 0 if the operation fails
+ */
+int rpmb_cmd_seq(struct rpmb_dev *rdev, struct rpmb_cmd *cmds, u32 ncmds)
+{
+ int err;
+
+ if (!rdev || !cmds || !ncmds)
+ return -EINVAL;
+
+ mutex_lock(&rdev->lock);
+ if (rdev->ops && rdev->ops->cmd_seq)
+ err = rdev->ops->cmd_seq(rdev->dev.parent, cmds, ncmds);
+ else
+ err = -EOPNOTSUPP;
+ mutex_unlock(&rdev->lock);
+ return err;
+}
+EXPORT_SYMBOL_GPL(rpmb_cmd_seq);
+
+static void rpmb_cmd_set(struct rpmb_cmd *cmd, u32 flags,
+ struct rpmb_frame *frames, u32 nframes)
+{
+ cmd->flags = flags;
+ cmd->frames = frames;
+ cmd->nframes = nframes;
+}
+
+/**
+ * rpmb_cmd_req - send rpmb request command
+ *
+ * @rdev: rpmb device
+ * @rpmbd: rpmb request data
+ *
+ * Return: 0 on success
+ * -EINVAL on wrong parameters
+ * -EOPNOTSUPP if device doesn't support the requested operation
+ * < 0 if the operation fails
+ */
+int rpmb_cmd_req(struct rpmb_dev *rdev, struct rpmb_data *rpmbd)
+{
+ struct rpmb_cmd cmd[3];
+ struct rpmb_frame *res_frame;
+ u32 cnt_in, cnt_out;
+ u32 ncmds;
+ u16 type;
+ int ret;
+
+ if (!rdev || !rpmbd)
+ return -EINVAL;
+
+ ret = rpmb_request_verify(rdev, rpmbd);
+ if (ret)
+ return ret;
+
+ if (!rdev->ops || !rdev->ops->cmd_seq)
+ return -EOPNOTSUPP;
+
+ cnt_in = rpmbd->icmd.nframes;
+ cnt_out = rpmbd->ocmd.nframes;
+ type = rpmbd->req_type;
+ switch (type) {
+ case RPMB_PROGRAM_KEY:
+ cnt_in = 1;
+ cnt_out = 1;
+ /* fall through */
+ case RPMB_WRITE_DATA:
+ rpmb_cmd_set(&cmd[0], RPMB_F_WRITE | RPMB_F_REL_WRITE,
+ rpmbd->icmd.frames, cnt_in);
+
+ res_frame = rpmbd->ocmd.frames;
+ memset(res_frame, 0, sizeof(*res_frame));
+ res_frame->req_resp = cpu_to_be16(RPMB_RESULT_READ);
+ rpmb_cmd_set(&cmd[1], RPMB_F_WRITE, res_frame, 1);
+
+ rpmb_cmd_set(&cmd[2], 0, rpmbd->ocmd.frames, cnt_out);
+ ncmds = 3;
+ break;
+ case RPMB_GET_WRITE_COUNTER:
+ cnt_in = 1;
+ cnt_out = 1;
+ /* fall through */
+ case RPMB_READ_DATA:
+ rpmb_cmd_set(&cmd[0], RPMB_F_WRITE, rpmbd->icmd.frames, cnt_in);
+ rpmb_cmd_set(&cmd[1], 0, rpmbd->ocmd.frames, cnt_out);
+ ncmds = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&rdev->lock);
+ ret = rdev->ops->cmd_seq(rdev->dev.parent, cmd, ncmds);
+ mutex_unlock(&rdev->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(rpmb_cmd_req);
+
+static void rpmb_dev_release(struct device *dev)
+{
+ struct rpmb_dev *rdev = to_rpmb_dev(dev);
+
+ ida_simple_remove(&rpmb_ida, rdev->id);
+ kfree(rdev);
+}
+
+struct class rpmb_class = {
+ .name = "rpmb",
+ .owner = THIS_MODULE,
+ .dev_release = rpmb_dev_release,
+};
+EXPORT_SYMBOL(rpmb_class);
+
+/**
+ * rpmb_dev_find_device - return first matching rpmb device
+ *
+ * @data: data for the match function
+ * @match: the matching function
+ *
+ * Return: matching rpmb device or NULL on failure
+ */
+struct rpmb_dev *rpmb_dev_find_device(void *data,
+ int (*match)(struct device *dev, const void *data))
+{
+ struct device *dev;
+
+ dev = class_find_device(&rpmb_class, NULL, data, match);
+
+ return dev ? to_rpmb_dev(dev) : NULL;
+}
+EXPORT_SYMBOL_GPL(rpmb_dev_find_device);
+
+static int match_by_type(struct device *dev, const void *data)
+{
+ struct rpmb_dev *rdev = to_rpmb_dev(dev);
+ enum rpmb_type *type = (enum rpmb_type *)data;
+
+ return (*type == RPMB_TYPE_ANY || rdev->ops->type == *type);
+}
+
+/**
+ * rpmb_dev_get_by_type - return first registered rpmb device
+ * with matching type.
+ * If run with RPMB_TYPE_ANY the first an probably only
+ * device is returned
+ *
+ * @type: rpbm underlying device type
+ *
+ * Return: matching rpmb device or NULL/ERR_PTR on failure
+ */
+struct rpmb_dev *rpmb_dev_get_by_type(enum rpmb_type type)
+{
+ if (type > RPMB_TYPE_MAX)
+ return ERR_PTR(-EINVAL);
+
+ return rpmb_dev_find_device(&type, match_by_type);
+}
+EXPORT_SYMBOL_GPL(rpmb_dev_get_by_type);
+
+static int match_by_parent(struct device *dev, const void *data)
+{
+ const struct device *parent = data;
+
+ return (parent && dev->parent == parent);
+}
+
+/**
+ * rpmb_dev_find_by_device - retrieve rpmb device from the parent device
+ *
+ * @parent: parent device of the rpmb device
+ *
+ * Return: NULL if there is no rpmb device associated with the parent device
+ */
+struct rpmb_dev *rpmb_dev_find_by_device(struct device *parent)
+{
+ if (!parent)
+ return NULL;
+
+ return rpmb_dev_find_device(parent, match_by_parent);
+}
+EXPORT_SYMBOL_GPL(rpmb_dev_find_by_device);
+
+/**
+ * rpmb_dev_unregister - unregister RPMB partition from the RPMB subsystem
+ *
+ * @dev: parent device of the rpmb device
+ */
+int rpmb_dev_unregister(struct device *dev)
+{
+ struct rpmb_dev *rdev;
+
+ if (!dev)
+ return -EINVAL;
+
+ rdev = rpmb_dev_find_by_device(dev);
+ if (!rdev) {
+ dev_warn(dev, "no disk found %s\n", dev_name(dev->parent));
+ return -ENODEV;
+ }
+
+ rpmb_dev_put(rdev);
+
+ mutex_lock(&rdev->lock);
+ device_del(&rdev->dev);
+ mutex_unlock(&rdev->lock);
+
+ rpmb_dev_put(rdev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rpmb_dev_unregister);
+
+/**
+ * rpmb_dev_register - register RPMB partition with the RPMB subsystem
+ *
+ * @dev: storage device of the rpmb device
+ * @ops: device specific operations
+ */
+struct rpmb_dev *rpmb_dev_register(struct device *dev,
+ const struct rpmb_ops *ops)
+{
+ struct rpmb_dev *rdev;
+ int id;
+ int ret;
+
+ if (!dev || !ops)
+ return ERR_PTR(-EINVAL);
+
+ if (!ops->cmd_seq)
+ return ERR_PTR(-EINVAL);
+
+ if (ops->type == RPMB_TYPE_ANY || ops->type > RPMB_TYPE_MAX)
+ return ERR_PTR(-EINVAL);
+
+ rdev = kzalloc(sizeof(*rdev), GFP_KERNEL);
+ if (!rdev)
+ return ERR_PTR(-ENOMEM);
+
+ id = ida_simple_get(&rpmb_ida, 0, 0, GFP_KERNEL);
+ if (id < 0) {
+ ret = id;
+ goto exit;
+ }
+
+ mutex_init(&rdev->lock);
+ rdev->ops = ops;
+ rdev->id = id;
+
+ dev_set_name(&rdev->dev, "rpmb%d", id);
+ rdev->dev.class = &rpmb_class;
+ rdev->dev.parent = dev;
+ ret = device_register(&rdev->dev);
+ if (ret)
+ goto exit;
+
+ dev_dbg(&rdev->dev, "registered disk\n");
+
+ return rdev;
+
+exit:
+ if (id >= 0)
+ ida_simple_remove(&rpmb_ida, id);
+ kfree(rdev);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(rpmb_dev_register);
+
+static int __init rpmb_init(void)
+{
+ ida_init(&rpmb_ida);
+ class_register(&rpmb_class);
+ return 0;
+}
+
+static void __exit rpmb_exit(void)
+{
+ class_unregister(&rpmb_class);
+ ida_destroy(&rpmb_ida);
+}
+
+subsys_initcall(rpmb_init);
+module_exit(rpmb_exit);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_DESCRIPTION("RPMB class");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2015-2016 Intel Corp. All rights reserved
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+#ifndef __RPMB_H__
+#define __RPMB_H__
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/kref.h>
+
+/**
+ * struct rpmb_frame - rpmb frame as defined by specs
+ *
+ * @stuff : stuff bytes
+ * @key_mac : The authentication key or the message authentication
+ * code (MAC) depending on the request/response type.
+ * The MAC will be delivered in the last (or the only)
+ * block of data.
+ * @data : Data to be written or read by signed access.
+ * @nonce : Random number generated by the host for the requests
+ * and copied to the response by the RPMB engine.
+ * @write_counter: Counter value for the total amount of the successful
+ * authenticated data write requests made by the host.
+ * @addr : Address of the data to be programmed to or read
+ * from the RPMB. Address is the serial number of
+ * the accessed block (half sector 256B).
+ * @block_count : Number of blocks (half sectors, 256B) requested to be
+ * read/programmed.
+ * @result : Includes information about the status of the write counter
+ * (valid, expired) and result of the access made to the RPMB.
+ * @req_resp : Defines the type of request and response to/from the memory.
+ */
+struct rpmb_frame {
+ u8 stuff[196];
+ u8 key_mac[32];
+ u8 data[256];
+ u8 nonce[16];
+ __be32 write_counter;
+ __be16 addr;
+ __be16 block_count;
+ __be16 result;
+ __be16 req_resp;
+} __packed;
+
+#define RPMB_PROGRAM_KEY 0x1 /* Program RPMB Authentication Key */
+#define RPMB_GET_WRITE_COUNTER 0x2 /* Read RPMB write counter */
+#define RPMB_WRITE_DATA 0x3 /* Write data to RPMB partition */
+#define RPMB_READ_DATA 0x4 /* Read data from RPMB partition */
+#define RPMB_RESULT_READ 0x5 /* Read result request (Internal) */
+
+#define RPMB_REQ2RESP(_OP) ((_OP) << 8)
+#define RPMB_RESP2REQ(_OP) ((_OP) >> 8)
+
+/**
+ * enum rpmb_op_result - rpmb operation results
+ *
+ * @RPMB_ERR_OK : operation successful
+ * @RPMB_ERR_GENERAL : general failure
+ * @RPMB_ERR_AUTH : mac doesn't match or ac calculation failure
+ * @RPMB_ERR_COUNTER : counter doesn't match or counter increment failure
+ * @RPMB_ERR_ADDRESS : address out of range or wrong address alignment
+ * @RPMB_ERR_WRITE : data, counter, or result write failure
+ * @RPMB_ERR_READ : data, counter, or result read failure
+ * @RPMB_ERR_NO_KEY : authentication key not yet programmed
+ *
+ * @RPMB_ERR_COUNTER_EXPIRED: counter expired
+ */
+enum rpmb_op_result {
+ RPMB_ERR_OK = 0x0000,
+ RPMB_ERR_GENERAL = 0x0001,
+ RPMB_ERR_AUTH = 0x0002,
+ RPMB_ERR_COUNTER = 0x0003,
+ RPMB_ERR_ADDRESS = 0x0004,
+ RPMB_ERR_WRITE = 0x0005,
+ RPMB_ERR_READ = 0x0006,
+ RPMB_ERR_NO_KEY = 0x0007,
+
+ RPMB_ERR_COUNTER_EXPIRED = 0x0080
+};
+
+/**
+ * enum rpmb_type - type of underlaying storage technology
+ *
+ * @RPMB_TYPE_ANY : any type used for search only
+ * @RPMB_TYPE_EMMC : emmc (JESD84-B50.1)
+ * @RPMB_TYPE_UFS : UFS (JESD220)
+ * @RPMB_TYPE_MAX : upper sentinel
+ */
+enum rpmb_type {
+ RPMB_TYPE_ANY = 0,
+ RPMB_TYPE_EMMC,
+ RPMB_TYPE_UFS,
+ RPMB_TYPE_MAX = RPMB_TYPE_UFS
+};
+
+extern struct class rpmb_class;
+
+#define RPMB_F_WRITE BIT(0)
+#define RPMB_F_REL_WRITE BIT(1)
+
+/**
+ * struct rpmb_cmd: rpmb access command
+ *
+ * @flags: command flags
+ * 0 - read command
+ * 1 - write commnad RPMB_F_WRITE
+ * 2 - reliable write RPMB_F_REL_WRITE
+ * @nframes: number of rpmb frames in the command
+ * @frames: list of rpmb frames
+ */
+struct rpmb_cmd {
+ u32 flags;
+ u32 nframes;
+ struct rpmb_frame *frames __aligned(8);
+};
+
+/**
+ * struct rpmb_data - rpmb data be transmitted in RPMB request
+ *
+ * @req_type: request type (program key, read, write, write counter)
+ * @icmd: list of input frames
+ * @ocmd: list of result frames
+ */
+struct rpmb_data {
+ u16 req_type;
+ struct rpmb_cmd icmd;
+ struct rpmb_cmd ocmd;
+};
+
+/**
+ * struct rpmb_ops - RPMB ops to be implemented by underlaying block device
+ *
+ * @cmd_seq : send RPMB command sequence to the RPBM partition
+ * backed by the disk
+ * @type : block device type
+ * @dev_id : unique device identifier
+ * @dev_id_len : unique device identifier length
+ * @reliable_wr_cnt: number of sectors that can be written in one access
+ */
+struct rpmb_ops {
+ int (*cmd_seq)(struct device *dev, struct rpmb_cmd *cmds, u32 ncmds);
+ enum rpmb_type type;
+ const u8 *dev_id;
+ size_t dev_id_len;
+ u16 reliable_wr_cnt;
+};
+
+/**
+ * struct rpmb_dev - device which can support RPMB partition
+ *
+ * @lock : the device lock
+ * @dev : device
+ * @id : device id
+ * @ops : operation exported by block layer
+ */
+struct rpmb_dev {
+ struct mutex lock; /* device serialization lock */
+ struct device dev;
+ int id;
+ const struct rpmb_ops *ops;
+};
+
+#define to_rpmb_dev(x) container_of((x), struct rpmb_dev, dev)
+
+#if IS_ENABLED(CONFIG_RPMB)
+struct rpmb_dev *rpmb_dev_get(struct rpmb_dev *rdev);
+void rpmb_dev_put(struct rpmb_dev *rdev);
+struct rpmb_dev *rpmb_dev_find_by_device(struct device *parent);
+struct rpmb_dev *rpmb_dev_get_by_type(enum rpmb_type type);
+struct rpmb_dev *rpmb_dev_register(struct device *dev,
+ const struct rpmb_ops *ops);
+struct rpmb_dev *rpmb_dev_find_device(void *data,
+ int (*match)(struct device *dev, const void *data));
+int rpmb_dev_unregister(struct device *dev);
+int rpmb_cmd_seq(struct rpmb_dev *rdev, struct rpmb_cmd *cmds, u32 ncmds);
+int rpmb_cmd_req(struct rpmb_dev *rdev, struct rpmb_data *data);
+
+#else
+static inline struct rpmb_dev *rpmb_dev_get(struct rpmb_dev *rdev)
+{
+ return NULL;
+}
+
+static inline void rpmb_dev_put(struct rpmb_dev *rdev) { }
+
+static inline struct rpmb_dev *rpmb_dev_find_by_device(struct device *parent)
+{
+ return NULL;
+}
+
+static inline
+struct rpmb_dev *rpmb_dev_get_by_type(enum rpmb_type type)
+{
+ return NULL;
+}
+
+static inline struct rpmb_dev *
+rpmb_dev_register(struct device *dev, const struct rpmb_ops *ops)
+{
+ return NULL;
+}
+
+static inline int rpmb_dev_unregister(struct device *dev)
+{
+ return 0;
+}
+
+static inline int rpmb_cmd_seq(struct rpmb_dev *rdev,
+ struct rpmb_cmd *cmds, u32 ncmds)
+{
+ return 0;
+}
+
+static inline int rpmb_cmd_req(struct rpmb_dev *rdev, struct rpmb_data *data)
+{
+ return 0;
+}
+
+#endif /* CONFIG_RPMB */
+
+#endif /* __RPMB_H__ */