From nobody Fri Jun 19 09:50:03 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2C159C433F5 for ; Tue, 5 Apr 2022 22:24:07 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1383652AbiDEWQa (ORCPT ); Tue, 5 Apr 2022 18:16:30 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39084 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1348941AbiDEJst (ORCPT ); Tue, 5 Apr 2022 05:48:49 -0400 Received: from mail-wr1-x436.google.com (mail-wr1-x436.google.com [IPv6:2a00:1450:4864:20::436]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D7AA4A2069 for ; Tue, 5 Apr 2022 02:38:04 -0700 (PDT) Received: by mail-wr1-x436.google.com with SMTP id u3so18429569wrg.3 for ; Tue, 05 Apr 2022 02:38:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=9kxJqP7a1M2gpL6SMSPDw94NffEPUOvYaV+FubVSaZo=; b=wRmNlkC5m4iGc78HN11BCavIkWRbMMK1+Bo1yXjgsRM4gS/6LOiQbvEWwODolabhrb K4aC/mXYUssgxN07Op2s83ZJUItpexJBaX513jgSby+nmn990bj6rSPhbS3wahYS1Wbg BuMTBifAJbROYT9PcfHYDR4dT6KzfuJkDSVIe4lugEp4iDf/f8mNUysNj5OQmKQZvnCa i8cUmDjmPSa1u5KjKo0wzMZ9GOIOxocs5WLG/HH0YADIS1xs0fXHg7bTJCJGQ7tas71k IbhhGMroJpZh9JjHl0hYwyI0TIDbCiLC8zUU+V35N4qBs3p5xolKL7tuG0DgJ5k9iy8A Py1g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=9kxJqP7a1M2gpL6SMSPDw94NffEPUOvYaV+FubVSaZo=; b=6o0CU4/iY0wUcwDvVwHcYk9bKYnrI7Z54iU0nXzPVLyDdIJVCir/hkH5r+e1PjGWQX Z5FPQE7XolAPEH1iAjUiuOcuLJhLE/olpPd6/DSJz2u5AFpovXWr4Vmc8Wb1vud5luwg +c93Volo3W0EHNhQDgHtO0t4SOL3kb+cTIlDVDZcRXosPQ1aQyN+XcbTitPNl6+Y7jKj Nmflw2ApNov8Y+NuUflPySGnpIoS4j05eo85k/CDNl9RZdZ6/tMIJr7T1NG1V3u3JdOW C9e6Fm+YD6l3LlqRCZlQr1ZDeym+jEfYqdn37SunSeVwi8rroSQeOQmMLAyoWYX1FmYq F3/Q== X-Gm-Message-State: AOAM530K6fJaAYK2PRelGn6ZpWx3jxyh+UHgvkN13SIvwz1aTMuUzQsB xJNU6FFZpLaEGYsWfotKRyWvVg== X-Google-Smtp-Source: ABdhPJziSgg2ISzYD+NQwV22bFIPYzBUI9GfT/TLjFTC+cQ2qIT1LU0iej+X30xqxW1XrxfY6ap9EA== X-Received: by 2002:a5d:4712:0:b0:206:120d:b038 with SMTP id y18-20020a5d4712000000b00206120db038mr2052231wrq.542.1649151483317; Tue, 05 Apr 2022 02:38:03 -0700 (PDT) Received: from zen.linaroharston ([51.148.130.216]) by smtp.gmail.com with ESMTPSA id f18-20020a5d6652000000b001e669ebd528sm11346872wrw.91.2022.04.05.02.38.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 05 Apr 2022 02:38:00 -0700 (PDT) Received: from zen.lan (localhost [127.0.0.1]) by zen.linaroharston (Postfix) with ESMTP id 0E3441FFB8; Tue, 5 Apr 2022 10:38:00 +0100 (BST) From: =?UTF-8?q?Alex=20Benn=C3=A9e?= To: linux-kernel@vger.kernel.org Cc: maxim.uvarov@linaro.org, joakim.bech@linaro.org, ulf.hansson@linaro.org, ilias.apalodimas@linaro.org, arnd@linaro.org, ruchika.gupta@linaro.org, tomas.winkler@intel.com, yang.huang@intel.com, bing.zhu@intel.com, Matti.Moell@opensynergy.com, hmo@opensynergy.com, linux-mmc@vger.kernel.org, linux-scsi@vger.kernel.org, =?UTF-8?q?Alex=20Benn=C3=A9e?= , Linus Walleij , Arnd Bergmann Subject: [PATCH v2 1/4] rpmb: add Replay Protected Memory Block (RPMB) subsystem Date: Tue, 5 Apr 2022 10:37:56 +0100 Message-Id: <20220405093759.1126835-2-alex.bennee@linaro.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220405093759.1126835-1-alex.bennee@linaro.org> References: <20220405093759.1126835-1-alex.bennee@linaro.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org A number of storage technologies support a specialised hardware partition designed to be resistant to replay attacks. The underlying HW protocols differ but the operations are common. The RPMB partition cannot be accessed via standard block layer, but by a set of specific commands: WRITE, READ, GET_WRITE_COUNTER, and PROGRAM_KEY. Such a partition provides authenticated and replay protected access, hence suitable as a secure storage. The RPMB layer aims to provide in-kernel API for Trusted Execution Environment (TEE) devices that are capable to securely compute block frame signature. In case a TEE device wishes to store a replay protected data, requests the storage device via RPMB layer to store the data. A TEE device driver can claim the RPMB interface, for example, via class_interface_register(). The RPMB layer provides a series of operations for interacting with the device. * program_key - a one time operation for setting up a new device * get_capacity - introspect the device capacity * get_write_count - check the write counter * write_blocks - write a series of blocks to the RPMB device * read_blocks - read a series of blocks from the RPMB device The detailed operation of implementing the access is left to the TEE device driver itself. [This is based-on Thomas Winkler's proposed API from: https://lore.kernel.org/linux-mmc/1478548394-8184-2-git-send-email-tomas.= winkler@intel.com/ The principle difference is the framing details and HW specific bits (JDEC vs NVME frames) are left to the lower level TEE driver to worry about. The eventual userspace ioctl interface will aim to be similarly generic. This is an RFC to follow up on: Subject: RPMB user space ABI Date: Thu, 11 Feb 2021 14:07:00 +0000 Message-ID: <87mtwashi4.fsf@linaro.org>] Signed-off-by: Alex Benn=C3=A9e Cc: Tomas Winkler Cc: Ulf Hansson Cc: Linus Walleij Cc: Arnd Bergmann Cc: Ilias Apalodimas --- v2 - dropped keyid stuff - moved from char to its own subdirectory driver/rpmb - fixed compile errors for bisectability --- MAINTAINERS | 7 + drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/rpmb/Kconfig | 11 ++ drivers/rpmb/Makefile | 7 + drivers/rpmb/core.c | 434 ++++++++++++++++++++++++++++++++++++++++++ include/linux/rpmb.h | 172 +++++++++++++++++ 7 files changed, 634 insertions(+) create mode 100644 drivers/rpmb/Kconfig create mode 100644 drivers/rpmb/Makefile create mode 100644 drivers/rpmb/core.c create mode 100644 include/linux/rpmb.h diff --git a/MAINTAINERS b/MAINTAINERS index cd0f68d4a34a..9ab02b589005 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16744,6 +16744,13 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/allwinner,sun8i-a83t-de2-rotate= .yaml F: drivers/media/platform/sunxi/sun8i-rotate/ =20 +RPMB SUBSYSTEM +M: ? +L: linux-kernel@vger.kernel.org +S: Supported +F: drivers/rpmb/* +F: include/linux/rpmb.h + RPMSG TTY DRIVER M: Arnaud Pouliquen L: linux-remoteproc@vger.kernel.org diff --git a/drivers/Kconfig b/drivers/Kconfig index 0d399ddaa185..90d18a8a0e72 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -236,4 +236,6 @@ source "drivers/interconnect/Kconfig" source "drivers/counter/Kconfig" =20 source "drivers/most/Kconfig" + +source "drivers/rpmb/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index a110338c860c..ade465c4589f 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -187,3 +187,4 @@ obj-$(CONFIG_GNSS) +=3D gnss/ obj-$(CONFIG_INTERCONNECT) +=3D interconnect/ obj-$(CONFIG_COUNTER) +=3D counter/ obj-$(CONFIG_MOST) +=3D most/ +obj-$(CONFIG_RPMB) +=3D rpmb/ diff --git a/drivers/rpmb/Kconfig b/drivers/rpmb/Kconfig new file mode 100644 index 000000000000..f2a9ebdc4435 --- /dev/null +++ b/drivers/rpmb/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2015-2019, Intel Corporation. + +config RPMB + tristate "RPMB partition interface" + help + Unified RPMB partition interface for RPMB capable devices such as + eMMC and UFS. Provides interface for in kernel security controll= ers to + access RPMB partition. + + If unsure, select N. diff --git a/drivers/rpmb/Makefile b/drivers/rpmb/Makefile new file mode 100644 index 000000000000..24d4752a9a53 --- /dev/null +++ b/drivers/rpmb/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2015-2019, Intel Corporation. + +obj-$(CONFIG_RPMB) +=3D rpmb.o +rpmb-objs +=3D core.o + +ccflags-y +=3D -D__CHECK_ENDIAN__ diff --git a/drivers/rpmb/core.c b/drivers/rpmb/core.c new file mode 100644 index 000000000000..50b358a14db6 --- /dev/null +++ b/drivers/rpmb/core.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(c) 2015 - 2019 Intel Corporation. All rights reserved. + * Copyright(c) 2021 - 2022 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include + +#include + +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); + +/** + * rpmb_program_key() - program the RPMB access key + * @rdev: rpmb device + * @keylen: length of key data + * @key: key data + * + * A successful programming of the key implies it has been set by the + * driver and can be used. + * + * Return: + * * 0 on success + * * -EINVAL on wrong parameters + * * -EPERM key already programmed + * * -EOPNOTSUPP if device doesn't support the requested operation + * * < 0 if the operation fails + */ +int rpmb_program_key(struct rpmb_dev *rdev, int klen, u8 *key, int rlen, u= 8 *resp) +{ + int err; + + if (!rdev || !key) + return -EINVAL; + + mutex_lock(&rdev->lock); + err =3D -EOPNOTSUPP; + if (rdev->ops && rdev->ops->program_key) { + err =3D rdev->ops->program_key(rdev->dev.parent, rdev->target, + klen, key, rlen, resp); + } + mutex_unlock(&rdev->lock); + + return err; +} +EXPORT_SYMBOL_GPL(rpmb_program_key); + +/** + * rpmb_get_capacity() - returns the capacity of the rpmb device + * @rdev: rpmb device + * + * Return: + * * capacity of the device in units of 128K, on success + * * -EINVAL on wrong parameters + * * -EOPNOTSUPP if device doesn't support the requested operation + * * < 0 if the operation fails + */ +int rpmb_get_capacity(struct rpmb_dev *rdev) +{ + int err; + + if (!rdev) + return -EINVAL; + + mutex_lock(&rdev->lock); + err =3D -EOPNOTSUPP; + if (rdev->ops && rdev->ops->get_capacity) + err =3D rdev->ops->get_capacity(rdev->dev.parent, rdev->target); + mutex_unlock(&rdev->lock); + + return err; +} +EXPORT_SYMBOL_GPL(rpmb_get_capacity); + +/** + * rpmb_get_write_count() - returns the write counter of the rpmb device + * @rdev: rpmb device + * @len: size of request frame + * @request: request frame + * @rlen: size of response frame + * @resp: response frame + * + * Return: + * * counter + * * -EINVAL on wrong parameters + * * -EOPNOTSUPP if device doesn't support the requested operation + * * < 0 if the operation fails + */ +int rpmb_get_write_count(struct rpmb_dev *rdev, int len, u8 *request, int = rlen, u8 *resp) +{ + int err; + + if (!rdev) + return -EINVAL; + + mutex_lock(&rdev->lock); + err =3D -EOPNOTSUPP; + if (rdev->ops && rdev->ops->get_write_count) + err =3D rdev->ops->get_write_count(rdev->dev.parent, rdev->target, + len, request, rlen, resp); + mutex_unlock(&rdev->lock); + + return err; +} +EXPORT_SYMBOL_GPL(rpmb_get_write_count); + +/** + * rpmb_write_blocks() - write data to RPMB device + * @rdev: rpmb device + * @addr: block address (index of first block - 256B blocks) + * @count: number of 256B blosks + * @data: pointer to data to program + * + * Write a series of blocks to the RPMB device. + * + * Return: + * * 0 on success + * * -EINVAL on wrong parameters + * * -EACCESS no key set + * * -EOPNOTSUPP if device doesn't support the requested operation + * * < 0 if the operation fails + */ +int rpmb_write_blocks(struct rpmb_dev *rdev, int len, u8 *request, + int rlen, u8 *response) +{ + int err; + + if (!rdev || !len || !request) + return -EINVAL; + + mutex_lock(&rdev->lock); + err =3D -EOPNOTSUPP; + if (rdev->ops && rdev->ops->write_blocks) { + err =3D rdev->ops->write_blocks(rdev->dev.parent, rdev->target, + len, request, rlen, response); + } + mutex_unlock(&rdev->lock); + + return err; +} +EXPORT_SYMBOL_GPL(rpmb_write_blocks); + +/** + * rpmb_read_blocks() - read data from RPMB device + * @rdev: rpmb device + * @addr: block address (index of first block - 256B blocks) + * @count: number of 256B blocks + * @data: pointer to data to read + * + * Read a series of one or more blocks from the RPMB device. + * + * Return: + * * 0 on success + * * -EINVAL on wrong parameters + * * -EACCESS no key set + * * -EOPNOTSUPP if device doesn't support the requested operation + * * < 0 if the operation fails + */ +int rpmb_read_blocks(struct rpmb_dev *rdev, int addr, int count, int len, = u8 *data) +{ + int err; + + if (!rdev || !count || !data) + return -EINVAL; + + mutex_lock(&rdev->lock); + err =3D -EOPNOTSUPP; + if (rdev->ops && rdev->ops->read_blocks) { + err =3D rdev->ops->read_blocks(rdev->dev.parent, rdev->target, + addr, count, len, data); + } + mutex_unlock(&rdev->lock); + + return err; +} +EXPORT_SYMBOL_GPL(rpmb_read_blocks); + + +static void rpmb_dev_release(struct device *dev) +{ + struct rpmb_dev *rdev =3D to_rpmb_dev(dev); + + ida_simple_remove(&rpmb_ida, rdev->id); + kfree(rdev); +} + +struct class rpmb_class =3D { + .name =3D "rpmb", + .owner =3D THIS_MODULE, + .dev_release =3D 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 + */ +static +struct rpmb_dev *rpmb_dev_find_device(const void *data, + int (*match)(struct device *dev, + const void *data)) +{ + struct device *dev; + + dev =3D class_find_device(&rpmb_class, NULL, data, match); + + return dev ? to_rpmb_dev(dev) : NULL; +} + +struct device_with_target { + const struct device *dev; + u8 target; +}; + +static int match_by_parent(struct device *dev, const void *data) +{ + const struct device_with_target *d =3D data; + struct rpmb_dev *rdev =3D to_rpmb_dev(dev); + + return (d->dev && dev->parent =3D=3D d->dev && rdev->target =3D=3D d->tar= get); +} + +/** + * rpmb_dev_find_by_device() - retrieve rpmb device from the parent device + * @parent: parent device of the rpmb device + * @target: RPMB target/region within the physical device + * + * Return: NULL if there is no rpmb device associated with the parent devi= ce + */ +struct rpmb_dev *rpmb_dev_find_by_device(struct device *parent, u8 target) +{ + struct device_with_target t; + + if (!parent) + return NULL; + + t.dev =3D parent; + t.target =3D target; + + return rpmb_dev_find_device(&t, match_by_parent); +} +EXPORT_SYMBOL_GPL(rpmb_dev_find_by_device); + +/** + * rpmb_dev_unregister() - unregister RPMB partition from the RPMB subsyst= em + * @rdev: the rpmb device to unregister + * Return: + * * 0 on success + * * -EINVAL on wrong parameters + */ +int rpmb_dev_unregister(struct rpmb_dev *rdev) +{ + if (!rdev) + return -EINVAL; + + 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_unregister_by_device() - unregister RPMB partition + * from the RPMB subsystem + * @dev: the parent device of the rpmb device + * @target: RPMB target/region within the physical device + * Return: + * * 0 on success + * * -EINVAL on wrong parameters + * * -ENODEV if a device cannot be find. + */ +int rpmb_dev_unregister_by_device(struct device *dev, u8 target) +{ + struct rpmb_dev *rdev; + + if (!dev) + return -EINVAL; + + rdev =3D rpmb_dev_find_by_device(dev, target); + if (!rdev) { + dev_warn(dev, "no disk found %s\n", dev_name(dev->parent)); + return -ENODEV; + } + + rpmb_dev_put(rdev); + + return rpmb_dev_unregister(rdev); +} +EXPORT_SYMBOL_GPL(rpmb_dev_unregister_by_device); + +/** + * rpmb_dev_get_drvdata() - driver data getter + * @rdev: rpmb device + * + * Return: driver private data + */ +void *rpmb_dev_get_drvdata(const struct rpmb_dev *rdev) +{ + return dev_get_drvdata(&rdev->dev); +} +EXPORT_SYMBOL_GPL(rpmb_dev_get_drvdata); + +/** + * rpmb_dev_set_drvdata() - driver data setter + * @rdev: rpmb device + * @data: data to store + */ +void rpmb_dev_set_drvdata(struct rpmb_dev *rdev, void *data) +{ + dev_set_drvdata(&rdev->dev, data); +} +EXPORT_SYMBOL_GPL(rpmb_dev_set_drvdata); + +/** + * rpmb_dev_register - register RPMB partition with the RPMB subsystem + * @dev: storage device of the rpmb device + * @target: RPMB target/region within the physical device + * @ops: device specific operations + * + * Return: a pointer to rpmb device + */ +struct rpmb_dev *rpmb_dev_register(struct device *dev, u8 target, + const struct rpmb_ops *ops) +{ + struct rpmb_dev *rdev; + int id; + int ret; + + if (!dev || !ops) + return ERR_PTR(-EINVAL); + + if (!ops->program_key) + return ERR_PTR(-EINVAL); + + if (!ops->get_capacity) + return ERR_PTR(-EINVAL); + + if (!ops->get_write_count) + return ERR_PTR(-EINVAL); + + if (!ops->write_blocks) + return ERR_PTR(-EINVAL); + + if (!ops->read_blocks) + return ERR_PTR(-EINVAL); + + rdev =3D kzalloc(sizeof(*rdev), GFP_KERNEL); + if (!rdev) + return ERR_PTR(-ENOMEM); + + id =3D ida_simple_get(&rpmb_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + ret =3D id; + goto exit; + } + + mutex_init(&rdev->lock); + rdev->ops =3D ops; + rdev->id =3D id; + rdev->target =3D target; + + dev_set_name(&rdev->dev, "rpmb%d", id); + rdev->dev.class =3D &rpmb_class; + rdev->dev.parent =3D dev; + + rpmb_cdev_prepare(rdev); + + ret =3D device_register(&rdev->dev); + if (ret) + goto exit; + + dev_dbg(&rdev->dev, "registered device\n"); + + return rdev; + +exit: + if (id >=3D 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 v2"); diff --git a/include/linux/rpmb.h b/include/linux/rpmb.h new file mode 100644 index 000000000000..4ed5e299623e --- /dev/null +++ b/include/linux/rpmb.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */ +/* + * Copyright (C) 2015-2019 Intel Corp. All rights reserved + * Copyright (C) 2021-2022 Linaro Ltd + */ +#ifndef __RPMB_H__ +#define __RPMB_H__ + +#include +#include +#include + +/** + * struct rpmb_ops - RPMB ops to be implemented by underlying block device + * + * @program_key : program device key (once only op). + * @get_capacity : rpmb size in 128K units in for region/target. + * @get_write_count: return the device write counter + * @write_blocks : write blocks to RPMB device + * @read_blocks : read blocks from RPMB device + * @block_size : block size in half sectors (1 =3D=3D 256B) + * @wr_cnt_max : maximal number of blocks that can be + * written in one access. + * @rd_cnt_max : maximal number of blocks that can be + * read in one access. + * @dev_id : unique device identifier + * @dev_id_len : unique device identifier length + */ +struct rpmb_ops { + int (*program_key)(struct device *dev, u8 target, + int keylen, u8 *key_frame, + int rlen, u8 *resp); + int (*get_capacity)(struct device *dev, u8 target); + int (*get_write_count)(struct device *dev, u8 target, + int len, u8 *requests, + int rlen, u8 *resp); + int (*write_blocks)(struct device *dev, u8 target, + int len, u8 *requests, + int rlen, u8 *resp); + int (*read_blocks)(struct device *dev, u8 target, + int addr, int count, + int len, u8 *data); + u16 block_size; + u16 wr_cnt_max; + u16 rd_cnt_max; + const u8 *dev_id; + size_t dev_id_len; +}; + +/** + * struct rpmb_dev - device which can support RPMB partition + * + * @lock : the device lock + * @dev : device + * @id : device id + * @target : RPMB target/region within the physical device + * @ops : operation exported by rpmb + */ +struct rpmb_dev { + struct mutex lock; /* device serialization lock */ + struct device dev; + int id; + u8 target; + 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, u8 target); +struct rpmb_dev *rpmb_dev_get_by_type(u32 type); +struct rpmb_dev *rpmb_dev_register(struct device *dev, u8 target, + const struct rpmb_ops *ops); +void *rpmb_dev_get_drvdata(const struct rpmb_dev *rdev); +void rpmb_dev_set_drvdata(struct rpmb_dev *rdev, void *data); +int rpmb_dev_unregister(struct rpmb_dev *rdev); +int rpmb_dev_unregister_by_device(struct device *dev, u8 target); + +int rpmb_program_key(struct rpmb_dev *rdev, + int klen, u8 *key, int rlen, u8 *resp); +int rpmb_get_capacity(struct rpmb_dev *rdev); +int rpmb_get_write_count(struct rpmb_dev *rdev, + int len, u8 *request, int rlen, u8 *resp); +int rpmb_write_blocks(struct rpmb_dev *rdev, + int len, u8 *request, int rlen, u8 *resp); +int rpmb_read_blocks(struct rpmb_dev *rdev, int addr, int count, int len, = u8 *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 *pare= nt, + u8 target) +{ + return NULL; +} + +static inline +struct rpmb_dev *rpmb_dev_get_by_type(enum rpmb_type type) +{ + return NULL; +} + +static inline void *rpmb_dev_get_drvdata(const struct rpmb_dev *rdev) +{ + return NULL; +} + +static inline void rpmb_dev_set_drvdata(struct rpmb_dev *rdev, void *data) +{ +} + +static inline struct rpmb_dev * +rpmb_dev_register(struct device *dev, u8 target, const struct rpmb_ops *op= s) +{ + return NULL; +} + +static inline int rpmb_dev_unregister(struct rpmb_dev *dev) +{ + return 0; +} + +static inline int rpmb_dev_unregister_by_device(struct device *dev, u8 tar= get) +{ + return 0; +} + +static inline int rpmb_program_key(struct rpmb_dev *rdev, + int klen, u8 *key, + int rlen, u8 *resp) +{ + return 0; +} + +static inline rpmb_set_key(struct rpmb_dev *rdev, u8 *key, int keylen); +{ + return 0; +} + +static inline int rpmb_get_capacity(struct rpmb_dev *rdev) +{ + return 0; +} + +static inline int rpmb_get_write_count(struct rpmb_dev *rdev, + int len, u8 *request, int rlen, u8 *resp) +{ + return 0; +} + +static inline int rpmb_write_blocks(struct rpmb_dev *rdev, + int len, u8 *request, int rlen, u8 *resp); +{ + return 0; +} + +static inline int rpmb_read_blocks(struct rpmb_dev *rdev, int addr, int co= unt, + int len, u8 *data) +{ + return 0; +} + +#endif /* CONFIG_RPMB */ + +#endif /* __RPMB_H__ */ --=20 2.30.2 From nobody Fri Jun 19 09:50:03 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 16DEAC46467 for ; Wed, 6 Apr 2022 00:17:47 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1583021AbiDEXuw (ORCPT ); Tue, 5 Apr 2022 19:50:52 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43462 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1348947AbiDEJst (ORCPT ); Tue, 5 Apr 2022 05:48:49 -0400 Received: from mail-wr1-x429.google.com (mail-wr1-x429.google.com [IPv6:2a00:1450:4864:20::429]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A323EA5E84 for ; Tue, 5 Apr 2022 02:38:05 -0700 (PDT) Received: by mail-wr1-x429.google.com with SMTP id u3so18429628wrg.3 for ; Tue, 05 Apr 2022 02:38:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=NDQpY3c16JMV/jnTjMb+uHPqUg2ziPK08NP7ftGP+Bg=; b=GnJ5ccsU8yM52MY2dEtsITaa5H4dJ9cMEwh198EEPjheoMARjNFyADd/6dS78OVzGu AZwIJ1MuGn7tVuXhNvRlaID+FgWbOOK858AsaU1V3cFGb4D8Jlw4JFGmQtLKwdPxhIYK 4NeJRTtYYTRPD1TApLxhqLtNJdi8rQHHe2nMWPvl0mvNdpoHAPk60UznJ+UlUeZNlwE3 PjajDZH0G1qHhJYdCBx9yelx1XJPdqx5DqvQwcnTNLEIH2aKKZp7l2lDPIRXkm0PQaSA usVBu+B+Yj6+EIC2DDYr4165/qNI6X+ZFZq9FX/AQxW6rTT8NAmbC8UUcq5ldljQzULt OydA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=NDQpY3c16JMV/jnTjMb+uHPqUg2ziPK08NP7ftGP+Bg=; b=qTH1ouyHRwIVbztG7OfUr2/SWyH8b0bHwRRkmnr0qxa1EmT/8m+lVGQKv7nCrAWsaT W+/n6PDs/Dd9UgdumVk7sjsZ3FWJp7XBQeOZECD6qWqXnPv4Pr0S22WWqsR05Sb6tBYu ZT8PUGykKCS5pmoNQPQN2xuQc0YMCzVN1kQbetNmfLF9YwDszYvDxZNpJU3hWrQK6Gzm tTDYX5rjrAfVv4kGNHSaofxk4UbXZYtkefBHKHxHJuIsOXa3aKGZS/BU5TCHfbbRmtXH BijkZg5zt1nvmkBUNPGrjQlkYUDPZLdPwBoq2u3JHazsNNuE/vd9DbB06zr8Wru1tL78 Sw5Q== X-Gm-Message-State: AOAM531Fkk2lYSU5KHw+dpBG9w41ZGb9TfDWc6OWtZUGPR7lSdLbInLb wqxxGU1Xd1P6wIqXG5m0z0aX7Q== X-Google-Smtp-Source: ABdhPJwvNLAXuiwz//DhXAexU6iWu8RMaTontHrUC9haKp9k1m6EKR+Sd1sCAs0RfRTiq+r12YiG1w== X-Received: by 2002:a5d:6905:0:b0:205:d510:c73a with SMTP id t5-20020a5d6905000000b00205d510c73amr2001129wru.275.1649151484085; Tue, 05 Apr 2022 02:38:04 -0700 (PDT) Received: from zen.linaroharston ([51.148.130.216]) by smtp.gmail.com with ESMTPSA id t9-20020adfa2c9000000b002061561d4a7sm4426805wra.96.2022.04.05.02.38.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 05 Apr 2022 02:38:00 -0700 (PDT) Received: from zen.lan (localhost [127.0.0.1]) by zen.linaroharston (Postfix) with ESMTP id 165501FFBA; Tue, 5 Apr 2022 10:38:00 +0100 (BST) From: =?UTF-8?q?Alex=20Benn=C3=A9e?= To: linux-kernel@vger.kernel.org Cc: maxim.uvarov@linaro.org, joakim.bech@linaro.org, ulf.hansson@linaro.org, ilias.apalodimas@linaro.org, arnd@linaro.org, ruchika.gupta@linaro.org, tomas.winkler@intel.com, yang.huang@intel.com, bing.zhu@intel.com, Matti.Moell@opensynergy.com, hmo@opensynergy.com, linux-mmc@vger.kernel.org, linux-scsi@vger.kernel.org, =?UTF-8?q?Alex=20Benn=C3=A9e?= , Linus Walleij , Arnd Bergmann , Alexander Usyskin , Avri Altman Subject: [PATCH v2 2/4] char: rpmb: provide a user space interface Date: Tue, 5 Apr 2022 10:37:57 +0100 Message-Id: <20220405093759.1126835-3-alex.bennee@linaro.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220405093759.1126835-1-alex.bennee@linaro.org> References: <20220405093759.1126835-1-alex.bennee@linaro.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org The user space API is achieved via a number of synchronous IOCTLs. * RPMB_IOC_VER_CMD - simple versioning API * RPMB_IOC_CAP_CMD - query of underlying capabilities * RPMB_IOC_PKEY_CMD - one time programming of access key * RPMB_IOC_COUNTER_CMD - query the write counter * RPMB_IOC_WBLOCKS_CMD - write blocks to device * RPMB_IOC_RBLOCKS_CMD - read blocks from device The operations which require authenticated frames or will respond with MAC hashes of nonce filled frames that userspace will need to verify share a common command frame format. The other operations can be considered generic and allow for common handling. [AJB: here the are key difference is the avoiding a single ioctl where all the frame data is put together by user space. User space is still the only place where certain operations can be verified due to the need of a secret key] Signed-off-by: Alex Benn=C3=A9e Cc: Ulf Hansson Cc: Linus Walleij Cc: Arnd Bergmann Cc: Ilias Apalodimas Cc: Tomas Winkler Cc: Alexander Usyskin Cc: Avri Altman --- v2 - drop the key api stuff --- .../userspace-api/ioctl/ioctl-number.rst | 1 + MAINTAINERS | 1 + drivers/rpmb/Kconfig | 7 + drivers/rpmb/Makefile | 1 + drivers/rpmb/cdev.c | 309 ++++++++++++++++++ drivers/rpmb/core.c | 7 +- drivers/rpmb/rpmb-cdev.h | 17 + include/linux/rpmb.h | 10 + include/uapi/linux/rpmb.h | 99 ++++++ 9 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 drivers/rpmb/cdev.c create mode 100644 drivers/rpmb/rpmb-cdev.h create mode 100644 include/uapi/linux/rpmb.h diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documenta= tion/userspace-api/ioctl/ioctl-number.rst index e6fce2cbd99e..874d01f11caf 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -355,6 +355,7 @@ Code Seq# Include File = Comments 0xB6 all linux/fpga-dfl.h 0xB7 all uapi/linux/remoteproc_cdev.h 0xB7 all uapi/linux/nsfs.h > +0xB8 80-8F uapi/linux/rpmb.h 0xC0 00-0F linux/usb/iowarrior.h 0xCA 00-0F uapi/misc/cxl.h 0xCA 10-2F uapi/misc/ocxl.h diff --git a/MAINTAINERS b/MAINTAINERS index 9ab02b589005..0a744da21817 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16749,6 +16749,7 @@ M: ? L: linux-kernel@vger.kernel.org S: Supported F: drivers/rpmb/* +F: include/uapi/linux/rpmb.h F: include/linux/rpmb.h =20 RPMSG TTY DRIVER diff --git a/drivers/rpmb/Kconfig b/drivers/rpmb/Kconfig index f2a9ebdc4435..126f336fe9ea 100644 --- a/drivers/rpmb/Kconfig +++ b/drivers/rpmb/Kconfig @@ -9,3 +9,10 @@ config RPMB access RPMB partition. =20 If unsure, select N. + +config RPMB_INTF_DEV + bool "RPMB character device interface /dev/rpmbN" + depends on RPMB + help + Say yes here if you want to access RPMB from user space + via character device interface /dev/rpmb%d diff --git a/drivers/rpmb/Makefile b/drivers/rpmb/Makefile index 24d4752a9a53..f54b3f30514b 100644 --- a/drivers/rpmb/Makefile +++ b/drivers/rpmb/Makefile @@ -3,5 +3,6 @@ =20 obj-$(CONFIG_RPMB) +=3D rpmb.o rpmb-objs +=3D core.o +rpmb-$(CONFIG_RPMB_INTF_DEV) +=3D cdev.o =20 ccflags-y +=3D -D__CHECK_ENDIAN__ diff --git a/drivers/rpmb/cdev.c b/drivers/rpmb/cdev.c new file mode 100644 index 000000000000..d9beeba53432 --- /dev/null +++ b/drivers/rpmb/cdev.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(c) 2015 - 2019 Intel Corporation. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +#include + +#include "rpmb-cdev.h" + +static dev_t rpmb_devt; +#define RPMB_MAX_DEVS MINORMASK + +#define RPMB_DEV_OPEN 0 /** single open bit (position) */ + +/** + * rpmb_open - the open function + * + * @inode: pointer to inode structure + * @fp: pointer to file structure + * + * Return: 0 on success, <0 on error + */ +static int rpmb_open(struct inode *inode, struct file *fp) +{ + struct rpmb_dev *rdev; + + rdev =3D container_of(inode->i_cdev, struct rpmb_dev, cdev); + if (!rdev) + return -ENODEV; + + /* the rpmb is single open! */ + if (test_and_set_bit(RPMB_DEV_OPEN, &rdev->status)) + return -EBUSY; + + mutex_lock(&rdev->lock); + + fp->private_data =3D rdev; + + mutex_unlock(&rdev->lock); + + return nonseekable_open(inode, fp); +} + +/** + * rpmb_release - the cdev release function + * + * @inode: pointer to inode structure + * @fp: pointer to file structure + * + * Return: 0 always. + */ +static int rpmb_release(struct inode *inode, struct file *fp) +{ + struct rpmb_dev *rdev =3D fp->private_data; + + clear_bit(RPMB_DEV_OPEN, &rdev->status); + + return 0; +} + +static long rpmb_ioctl_ver_cmd(struct rpmb_dev *rdev, + struct rpmb_ioc_ver_cmd __user *ptr) +{ + struct rpmb_ioc_ver_cmd ver =3D { + .api_version =3D RPMB_API_VERSION, + }; + + return copy_to_user(ptr, &ver, sizeof(ver)) ? -EFAULT : 0; +} + +static long rpmb_ioctl_cap_cmd(struct rpmb_dev *rdev, + struct rpmb_ioc_cap_cmd __user *ptr) +{ + struct rpmb_ioc_cap_cmd cap; + + cap.target =3D rdev->target; + cap.block_size =3D rdev->ops->block_size; + cap.wr_cnt_max =3D rdev->ops->wr_cnt_max; + cap.rd_cnt_max =3D rdev->ops->rd_cnt_max; + cap.capacity =3D rpmb_get_capacity(rdev); + cap.reserved =3D 0; + + return copy_to_user(ptr, &cap, sizeof(cap)) ? -EFAULT : 0; +} + +static long rpmb_ioctl_pkey_cmd(struct rpmb_dev *rdev, struct rpmb_ioc_req= resp_cmd __user *ptr) +{ + struct rpmb_ioc_reqresp_cmd cmd; + u8 *request, *resp =3D NULL; + long ret; + + if (copy_from_user(&cmd, ptr, sizeof(struct rpmb_ioc_reqresp_cmd))) + return -EFAULT; + + request =3D kmalloc(cmd.len, GFP_KERNEL); + + if (!request) + return -ENOMEM; + + if (cmd.rlen && cmd.response) { + resp =3D kmalloc(cmd.rlen, GFP_KERNEL); + if (!resp) { + kfree(request); + return -ENOMEM; + } + } + + if (copy_from_user(request, cmd.request, cmd.len)) + ret =3D -EFAULT; + else + ret =3D rpmb_program_key(rdev, cmd.len, request, cmd.rlen, resp); + + if (!ret) + if (copy_to_user(cmd.response, resp, cmd.rlen)) + ret =3D -EFAULT; + + kfree(request); + kfree(resp); + + return ret; +} + +static long rpmb_ioctl_counter_cmd(struct rpmb_dev *rdev, struct rpmb_ioc_= reqresp_cmd __user *ptr) +{ + struct rpmb_ioc_reqresp_cmd cmd; + u8 *request, *resp =3D NULL; + long count; + + if (copy_from_user(&cmd, ptr, sizeof(struct rpmb_ioc_reqresp_cmd))) + return -EFAULT; + + request =3D kmalloc(cmd.len, GFP_KERNEL); + + if (!request) + return -ENOMEM; + + if (cmd.rlen && cmd.response) { + resp =3D kmalloc(cmd.rlen, GFP_KERNEL); + if (!resp) { + kfree(request); + return -ENOMEM; + } + } + + if (copy_from_user(request, cmd.request, cmd.len)) { + count =3D -EFAULT; + } else { + count =3D rpmb_get_write_count(rdev, cmd.len, request, cmd.rlen, resp); + if (resp) + if (copy_to_user(cmd.response, resp, cmd.rlen)) + count =3D -EFAULT; + } + + kfree(request); + kfree(resp); + + return count; +} + +static long rpmb_ioctl_wblocks_cmd(struct rpmb_dev *rdev, + struct rpmb_ioc_reqresp_cmd __user *ptr) +{ + struct rpmb_ioc_reqresp_cmd cmd; + u8 *data, *resp =3D NULL; + + long ret; + + if (copy_from_user(&cmd, ptr, sizeof(struct rpmb_ioc_reqresp_cmd))) + return -EFAULT; + + data =3D kmalloc(cmd.len, GFP_KERNEL); + + if (!data) + return -ENOMEM; + + if (cmd.rlen && cmd.response) { + resp =3D kmalloc(cmd.rlen, GFP_KERNEL); + if (!resp) { + kfree(data); + return -ENOMEM; + } + } + + if (copy_from_user(data, cmd.request, cmd.len)) + ret =3D -EFAULT; + else + ret =3D rpmb_write_blocks(rdev, cmd.len, data, cmd.rlen, resp); + + if (resp) + if (copy_to_user(cmd.response, resp, cmd.rlen)) + ret =3D -EFAULT; + + kfree(data); + kfree(resp); + + return ret; +} + +static long rpmb_ioctl_rblocks_cmd(struct rpmb_dev *rdev, + struct rpmb_ioc_rblocks_cmd __user *ptr) +{ + struct rpmb_ioc_rblocks_cmd rblocks; + long ret; + u8 *data; + + if (copy_from_user(&rblocks, ptr, sizeof(struct rpmb_ioc_rblocks_cmd))) + return -EFAULT; + + if (rblocks.count > rdev->ops->rd_cnt_max) + return -EINVAL; + + if (!rblocks.len || !rblocks.data) + return -EINVAL; + + data =3D kmalloc(rblocks.len, GFP_KERNEL); + + if (!data) + return -ENOMEM; + + ret =3D rpmb_read_blocks(rdev, rblocks.addr, rblocks.count, rblocks.len, = data); + + if (ret =3D=3D 0) + ret =3D copy_to_user(rblocks.data, data, rblocks.len); + + kfree(data); + return ret; +} + +/** + * rpmb_ioctl - rpmb ioctl dispatcher + * + * @fp: a file pointer + * @cmd: ioctl command RPMB_IOC_SEQ_CMD RPMB_IOC_VER_CMD RPMB_IOC_CAP_CMD + * @arg: ioctl data: rpmb_ioc_ver_cmd rpmb_ioc_cap_cmd pmb_ioc_seq_cmd + * + * Return: 0 on success; < 0 on error + */ +static long rpmb_ioctl(struct file *fp, unsigned int cmd, unsigned long ar= g) +{ + struct rpmb_dev *rdev =3D fp->private_data; + void __user *ptr =3D (void __user *)arg; + + switch (cmd) { + case RPMB_IOC_VER_CMD: + return rpmb_ioctl_ver_cmd(rdev, ptr); + case RPMB_IOC_CAP_CMD: + return rpmb_ioctl_cap_cmd(rdev, ptr); + case RPMB_IOC_PKEY_CMD: + return rpmb_ioctl_pkey_cmd(rdev, ptr); + case RPMB_IOC_COUNTER_CMD: + return rpmb_ioctl_counter_cmd(rdev, ptr); + case RPMB_IOC_WBLOCKS_CMD: + return rpmb_ioctl_wblocks_cmd(rdev, ptr); + case RPMB_IOC_RBLOCKS_CMD: + return rpmb_ioctl_rblocks_cmd(rdev, ptr); + default: + dev_err(&rdev->dev, "unsupported ioctl 0x%x.\n", cmd); + return -ENOIOCTLCMD; + } +} + +static const struct file_operations rpmb_fops =3D { + .open =3D rpmb_open, + .release =3D rpmb_release, + .unlocked_ioctl =3D rpmb_ioctl, + .owner =3D THIS_MODULE, + .llseek =3D noop_llseek, +}; + +void rpmb_cdev_prepare(struct rpmb_dev *rdev) +{ + rdev->dev.devt =3D MKDEV(MAJOR(rpmb_devt), rdev->id); + rdev->cdev.owner =3D THIS_MODULE; + cdev_init(&rdev->cdev, &rpmb_fops); +} + +void rpmb_cdev_add(struct rpmb_dev *rdev) +{ + cdev_add(&rdev->cdev, rdev->dev.devt, 1); +} + +void rpmb_cdev_del(struct rpmb_dev *rdev) +{ + if (rdev->dev.devt) + cdev_del(&rdev->cdev); +} + +int __init rpmb_cdev_init(void) +{ + int ret; + + ret =3D alloc_chrdev_region(&rpmb_devt, 0, RPMB_MAX_DEVS, "rpmb"); + if (ret < 0) + pr_err("unable to allocate char dev region\n"); + + return ret; +} + +void __exit rpmb_cdev_exit(void) +{ + unregister_chrdev_region(rpmb_devt, RPMB_MAX_DEVS); +} diff --git a/drivers/rpmb/core.c b/drivers/rpmb/core.c index 50b358a14db6..969536ba53cc 100644 --- a/drivers/rpmb/core.c +++ b/drivers/rpmb/core.c @@ -12,6 +12,7 @@ #include =20 #include +#include "rpmb-cdev.h" =20 static DEFINE_IDA(rpmb_ida); =20 @@ -282,6 +283,7 @@ int rpmb_dev_unregister(struct rpmb_dev *rdev) return -EINVAL; =20 mutex_lock(&rdev->lock); + rpmb_cdev_del(rdev); device_del(&rdev->dev); mutex_unlock(&rdev->lock); =20 @@ -401,6 +403,8 @@ struct rpmb_dev *rpmb_dev_register(struct device *dev, = u8 target, if (ret) goto exit; =20 + rpmb_cdev_add(rdev); + dev_dbg(&rdev->dev, "registered device\n"); =20 return rdev; @@ -417,11 +421,12 @@ static int __init rpmb_init(void) { ida_init(&rpmb_ida); class_register(&rpmb_class); - return 0; + return rpmb_cdev_init(); } =20 static void __exit rpmb_exit(void) { + rpmb_cdev_exit(); class_unregister(&rpmb_class); ida_destroy(&rpmb_ida); } diff --git a/drivers/rpmb/rpmb-cdev.h b/drivers/rpmb/rpmb-cdev.h new file mode 100644 index 000000000000..e59ff0c05e9d --- /dev/null +++ b/drivers/rpmb/rpmb-cdev.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */ +/* + * Copyright (C) 2015-2018 Intel Corp. All rights reserved + */ +#ifdef CONFIG_RPMB_INTF_DEV +int __init rpmb_cdev_init(void); +void __exit rpmb_cdev_exit(void); +void rpmb_cdev_prepare(struct rpmb_dev *rdev); +void rpmb_cdev_add(struct rpmb_dev *rdev); +void rpmb_cdev_del(struct rpmb_dev *rdev); +#else +static inline int __init rpmb_cdev_init(void) { return 0; } +static inline void __exit rpmb_cdev_exit(void) {} +static inline void rpmb_cdev_prepare(struct rpmb_dev *rdev) {} +static inline void rpmb_cdev_add(struct rpmb_dev *rdev) {} +static inline void rpmb_cdev_del(struct rpmb_dev *rdev) {} +#endif /* CONFIG_RPMB_INTF_DEV */ diff --git a/include/linux/rpmb.h b/include/linux/rpmb.h index 4ed5e299623e..3b0731c07528 100644 --- a/include/linux/rpmb.h +++ b/include/linux/rpmb.h @@ -8,8 +8,12 @@ =20 #include #include +#include +#include #include =20 +#define RPMB_API_VERSION 0x80000001 + /** * struct rpmb_ops - RPMB ops to be implemented by underlying block device * @@ -54,6 +58,8 @@ struct rpmb_ops { * @dev : device * @id : device id * @target : RPMB target/region within the physical device + * @cdev : character dev + * @status : device status * @ops : operation exported by rpmb */ struct rpmb_dev { @@ -61,6 +67,10 @@ struct rpmb_dev { struct device dev; int id; u8 target; +#ifdef CONFIG_RPMB_INTF_DEV + struct cdev cdev; + unsigned long status; +#endif /* CONFIG_RPMB_INTF_DEV */ const struct rpmb_ops *ops; }; =20 diff --git a/include/uapi/linux/rpmb.h b/include/uapi/linux/rpmb.h new file mode 100644 index 000000000000..2f4ee7279b1b --- /dev/null +++ b/include/uapi/linux/rpmb.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Cl= ause) */ +/* + * Copyright (C) 2015-2018 Intel Corp. All rights reserved + * Copyright (C) 2021-2022 Linaro Ltd + */ +#ifndef _UAPI_LINUX_RPMB_H_ +#define _UAPI_LINUX_RPMB_H_ + +#include + +/** + * struct rpmb_ioc_ver_cmd - rpmb api version + * + * @api_version: rpmb API version. + */ +struct rpmb_ioc_ver_cmd { + __u32 api_version; +} __packed; + +enum rpmb_auth_method { + RPMB_HMAC_ALGO_SHA_256 =3D 0, +}; + +/** + * struct rpmb_ioc_cap_cmd - rpmb capabilities + * + * @target: rpmb target/region within RPMB partition. + * @capacity: storage capacity (in units of 128K) + * @block_size: storage data block size (in units of 256B) + * @wr_cnt_max: maximal number of block that can be written in a single re= quest. + * @rd_cnt_max: maximal number of block that can be read in a single reque= st. + * @auth_method: authentication method: currently always HMAC_SHA_256 + * @reserved: reserved to align to 4 bytes. + */ +struct rpmb_ioc_cap_cmd { + __u16 target; + __u16 capacity; + __u16 block_size; + __u16 wr_cnt_max; + __u16 rd_cnt_max; + __u16 auth_method; + __u16 reserved; +} __packed; + +/** + * struct rpmb_ioc_reqresp_cmd - general purpose reqresp + * + * Most RPMB operations consist of a set of request frames and an + * optional response frame. If a response is requested the user must + * allocate enough space for the response, otherwise the fields should + * be set to 0/NULL. + * + * It is used for programming the key, reading the counter and writing + * blocks to the device. If the frames are malformed they may be + * rejected by the underlying driver or the device itself. + * + * Assuming the transaction succeeds it is still up to user space to + * validate the response and check MAC values correspond to the + * programmed keys. + * + * @len: length of write counter request + * @request: ptr to device specific request frame + * @rlen: length of response frame + * @resp: ptr to device specific response frame + */ +struct rpmb_ioc_reqresp_cmd { + __u32 len; + __u8 __user *request; + __u32 rlen; + __u8 __user *response; +} __packed; + +/** + * struct rpmb_ioc_rblocks_cmd - read blocks from RPMB + * + * @addr: index into device (units of 256B blocks) + * @count: number of 256B blocks + * @len: length of response frame + * @data: block data (in device specific framing) + * + * Reading blocks from an RPMB device doesn't require any specific + * authentication. However the result still needs to be validated by + * user space. + */ +struct rpmb_ioc_rblocks_cmd { + __u32 addr; + __u32 count; + __u32 len; + __u8 __user *data; +} __packed; + +#define RPMB_IOC_VER_CMD _IOR(0xB8, 80, struct rpmb_ioc_ver_cmd) +#define RPMB_IOC_CAP_CMD _IOR(0xB8, 81, struct rpmb_ioc_cap_cmd) +#define RPMB_IOC_PKEY_CMD _IOWR(0xB8, 82, struct rpmb_ioc_reqresp_cmd) +#define RPMB_IOC_COUNTER_CMD _IOWR(0xB8, 84, struct rpmb_ioc_reqresp_cmd) +#define RPMB_IOC_WBLOCKS_CMD _IOWR(0xB8, 85, struct rpmb_ioc_reqresp_cmd) +#define RPMB_IOC_RBLOCKS_CMD _IOR(0xB8, 86, struct rpmb_ioc_rblocks_cmd) + +#endif /* _UAPI_LINUX_RPMB_H_ */ --=20 2.30.2 From nobody Fri Jun 19 09:50:03 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3FBDFC4167E for ; Wed, 6 Apr 2022 00:40:42 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238937AbiDFAWv (ORCPT ); Tue, 5 Apr 2022 20:22:51 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47414 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1348956AbiDEJst (ORCPT ); Tue, 5 Apr 2022 05:48:49 -0400 Received: from mail-wr1-x436.google.com (mail-wr1-x436.google.com [IPv6:2a00:1450:4864:20::436]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 95C14A5E99 for ; Tue, 5 Apr 2022 02:38:07 -0700 (PDT) Received: by mail-wr1-x436.google.com with SMTP id r13so18361350wrr.9 for ; Tue, 05 Apr 2022 02:38:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=5XIz5OfvKltqZSeXren24cN8/vWoQ7uO5RENAi6vLig=; b=UrUfEXIlvxSp8sIeO6xh/kGe1WpSq4OqI/LhLEgsXMMfzgl7OpApM9zMvJvcWAe7sp C1NkO5Z54qjRtT/gZ0XEOf2fVGk0aY/gFKs0y0BkRSO9RQDYk1SoBvSR1M11Kph0SKyW kwYz/F8IvDRqTIEx00WxT93T7B6U5KTDejqbmM1mwqf6upU9lzQsnXTdRNUF0jpXzL4w fAMflltmSitvXC1olNJQuRNSY8rOcDxVgYPk1879U/D6meXkohynmsgz2Wifn7ALuygs Y9xRoD/7cV9jGnmLZ8E/5S296sec2HmVZi7H92tJXDz8EHGl/KHP3F5RjnW/89ewCzRF ihsA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=5XIz5OfvKltqZSeXren24cN8/vWoQ7uO5RENAi6vLig=; b=URTpCVMgcgA7ACUHsabAeCJx9N/P6R0zWuThtg07dsvq/PS8G/Zbn/olEGxLhTJ8hn gaV4q8zWeb18Ho2fXXNashb7jGCclgclymxSozJikmDs5+DAXxZTBe7dAGDoxfWmVf4H BYmBVOSrNq+CiveTdMla41HYGAEsWlbYHw/qWbPkpuVa6gXWmb+xKZU+JDJ4XC6BfB/5 7t9xtUrutWFOurM8tO5kI61TPm64EWkqr4HdXJ0URDA0J1JoF2/9U79nH3r65yG380pa lYlANbugdPaqPOEdlA9kw974wNuQUFKHqmJ3t8gzcgq1OKSMr3eTzXm1LNJkZNk77DaD 1coA== X-Gm-Message-State: AOAM531Ej5Od2J8fCEOdY/aJJwlq8VFi9i872CxwZuh6qCbBLHXiKUY4 EdIIKppkOoW7nEmcOp4sq/83uw== X-Google-Smtp-Source: ABdhPJyuOUieYVJh0pNqAbyEnkD6s6YTyfa0TavUfZNB+FdJRmfUgtO0dixX7I+3Wr7D2BXaYe9gtA== X-Received: by 2002:a5d:6782:0:b0:203:d6af:5869 with SMTP id v2-20020a5d6782000000b00203d6af5869mr1973756wru.213.1649151485956; Tue, 05 Apr 2022 02:38:05 -0700 (PDT) Received: from zen.linaroharston ([51.148.130.216]) by smtp.gmail.com with ESMTPSA id v15-20020a056000144f00b002057eac999fsm11955806wrx.76.2022.04.05.02.38.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 05 Apr 2022 02:38:00 -0700 (PDT) Received: from zen.lan (localhost [127.0.0.1]) by zen.linaroharston (Postfix) with ESMTP id 238991FFBB; Tue, 5 Apr 2022 10:38:00 +0100 (BST) From: =?UTF-8?q?Alex=20Benn=C3=A9e?= To: linux-kernel@vger.kernel.org Cc: maxim.uvarov@linaro.org, joakim.bech@linaro.org, ulf.hansson@linaro.org, ilias.apalodimas@linaro.org, arnd@linaro.org, ruchika.gupta@linaro.org, tomas.winkler@intel.com, yang.huang@intel.com, bing.zhu@intel.com, Matti.Moell@opensynergy.com, hmo@opensynergy.com, linux-mmc@vger.kernel.org, linux-scsi@vger.kernel.org, =?UTF-8?q?Alex=20Benn=C3=A9e?= Subject: [PATCH v2 3/4] rpmb: create virtio rpmb frontend driver Date: Tue, 5 Apr 2022 10:37:58 +0100 Message-Id: <20220405093759.1126835-4-alex.bennee@linaro.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220405093759.1126835-1-alex.bennee@linaro.org> References: <20220405093759.1126835-1-alex.bennee@linaro.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This implements a virtio rpmb frontend driver for the RPMB subsystem. This driver conforms to the rpmb internal API which attempts to stay common for the majority of cases and only expose the low level frame details as required. In these cases it is up to something outside of the driver itself to do the appropriate MAC calculations. The driver does do some basic verification of the incoming data and will reject frames which are the wrong size or have the wrong commands in them. Signed-off-by: Alex Benn=C3=A9e Cc: Tomas Winkler --- ajb: - rewrite for new API --- drivers/rpmb/Kconfig | 10 + drivers/rpmb/Makefile | 1 + drivers/rpmb/virtio_rpmb.c | 518 +++++++++++++++++++++++++++++++ include/uapi/linux/virtio_rpmb.h | 54 ++++ 4 files changed, 583 insertions(+) create mode 100644 drivers/rpmb/virtio_rpmb.c create mode 100644 include/uapi/linux/virtio_rpmb.h diff --git a/drivers/rpmb/Kconfig b/drivers/rpmb/Kconfig index 126f336fe9ea..cd0d1bb10910 100644 --- a/drivers/rpmb/Kconfig +++ b/drivers/rpmb/Kconfig @@ -16,3 +16,13 @@ config RPMB_INTF_DEV help Say yes here if you want to access RPMB from user space via character device interface /dev/rpmb%d + +config VIRTIO_RPMB + tristate "Virtio RPMB driver" + default n + depends on VIRTIO + select RPMB + help + Say yes here if you have a Virtio aware RPMB device or want to use + RPMB from a Virtual Machine images. + This device interface is only for guest/frontend virtio driver. diff --git a/drivers/rpmb/Makefile b/drivers/rpmb/Makefile index f54b3f30514b..4b397b50a42c 100644 --- a/drivers/rpmb/Makefile +++ b/drivers/rpmb/Makefile @@ -4,5 +4,6 @@ obj-$(CONFIG_RPMB) +=3D rpmb.o rpmb-objs +=3D core.o rpmb-$(CONFIG_RPMB_INTF_DEV) +=3D cdev.o +obj-$(CONFIG_VIRTIO_RPMB) +=3D virtio_rpmb.o =20 ccflags-y +=3D -D__CHECK_ENDIAN__ diff --git a/drivers/rpmb/virtio_rpmb.c b/drivers/rpmb/virtio_rpmb.c new file mode 100644 index 000000000000..db309014a157 --- /dev/null +++ b/drivers/rpmb/virtio_rpmb.c @@ -0,0 +1,518 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Virtio RPMB Front End Driver + * + * Copyright (c) 2018-2019 Intel Corporation. + * Copyright (c) 2021-2022 Linaro Ltd. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RPMB_MAC_SIZE 32 +#define VIRTIO_RPMB_FRAME_SZ 512 + +static const char id[] =3D "RPMB:VIRTIO"; + +struct virtio_rpmb_info { + /* The virtio device we're associated with */ + struct virtio_device *vdev; + + /* The virtq we use */ + struct virtqueue *vq; + + struct mutex lock; /* info lock */ + wait_queue_head_t have_data; + + /* Underlying RPMB device */ + struct rpmb_dev *rdev; + + /* Config values */ + u8 max_wr, max_rd, capacity; +}; + +/** + * virtio_rpmb_recv_done() - vq completion callback + */ +static void virtio_rpmb_recv_done(struct virtqueue *vq) +{ + struct virtio_rpmb_info *vi; + struct virtio_device *vdev =3D vq->vdev; + + vi =3D vq->vdev->priv; + if (!vi) { + dev_err(&vdev->dev, "Error: no found vi data.\n"); + return; + } + + wake_up(&vi->have_data); +} + +/** + * do_virtio_transaction() - send sg list and wait for result + * @dev: linux device structure + * @vi: the device info (where the lock is) + * @sgs: array of scatterlists + * @out: total outgoing scatter lists + * @in: total returning scatter lists + * + * This is just a simple helper for processing the sg list. It will + * block until the response arrives. Returns number of bytes written + * back or negative if it failed. + */ +static int do_virtio_transaction(struct device *dev, + struct virtio_rpmb_info *vi, + struct scatterlist *sgs[], + int out, int in) +{ + int ret, len =3D 0; + + mutex_lock(&vi->lock); + ret =3D virtqueue_add_sgs(vi->vq, sgs, out, in, vi, GFP_KERNEL); + if (ret) { + dev_err(dev, "failed to send %d, recv %d sgs (%d) to vq\n", + out, in, ret); + ret =3D -1; + } else { + virtqueue_kick(vi->vq); + wait_event(vi->have_data, virtqueue_get_buf(vi->vq, &len)); + } + mutex_unlock(&vi->lock); + + return len; +} + +/** + * rpmb_virtio_program_key(): program key into virtio device + * @dev: device handle + * @target: target region (unused for VirtIO devices) + * @klen: length of key programming request + * @key_frame: key programming frames + * @rlen: length of response buffer + * @resp_frame: pointer to optional response frame + * + * Handle programming of the key (VIRTIO_RPMB_REQ_PROGRAM_KEY) + * + * The mandatory first frame contains the programming sequence. An + * optional second frame may ask for the result of the operation + * (VIRTIO_RPMB_REQ_RESULT_READ) which would trigger a response frame. + * + * Returns success/fail with errno and optional response frame + */ +static int rpmb_virtio_program_key(struct device *dev, u8 target, + int klen, u8 *key_frame, int rlen, u8 *resp_frame) +{ + struct virtio_rpmb_info *vi =3D dev_get_drvdata(dev); + struct virtio_rpmb_frame *pkey =3D (struct virtio_rpmb_frame *) key_frame; + struct virtio_rpmb_frame *resp =3D NULL; + struct scatterlist out_frame; + struct scatterlist in_frame; + struct scatterlist *sgs[2] =3D { }; + int len; + + if (!pkey) + return -EINVAL; + + if (be16_to_cpu(pkey->req_resp) !=3D VIRTIO_RPMB_REQ_PROGRAM_KEY) + return -EINVAL; + + /* validate incoming frame */ + switch (klen) { + case VIRTIO_RPMB_FRAME_SZ: + if (rlen || resp_frame) + return -EINVAL; + break; + case VIRTIO_RPMB_FRAME_SZ * 2: + if (!rlen || !resp_frame) + return -EINVAL; + if (be16_to_cpu(pkey[1].req_resp) !=3D VIRTIO_RPMB_REQ_RESULT_READ) + return -EINVAL; + if (rlen < VIRTIO_RPMB_FRAME_SZ) + return -EINVAL; + break; + default: + return -EINVAL; + } + + /* setup outgoing frame(s) */ + sg_init_one(&out_frame, pkey, klen); + sgs[0] =3D &out_frame; + + /* optional incoming frame */ + if (rlen && resp_frame) { + resp =3D (struct virtio_rpmb_frame *) resp_frame; + sg_init_one(&in_frame, resp, sizeof(*resp)); + sgs[1] =3D &in_frame; + } + + len =3D do_virtio_transaction(dev, vi, sgs, 1, resp ? 1 : 0); + + if (len > 0 && resp) { + if (be16_to_cpu(resp->req_resp) !=3D VIRTIO_RPMB_RESP_PROGRAM_KEY) { + dev_err(dev, "Bad response from device (%x/%x)", + be16_to_cpu(resp->req_resp), be16_to_cpu(resp->result)); + return -EPROTO; + } else { + /* map responses to better errors? */ + return be16_to_cpu(resp->result) =3D=3D VIRTIO_RPMB_RES_OK ? 0 : -EIO; + } + } + + /* Something must have failed at this point. */ + return len < 0 ? -EIO : 0; +} + +static int rpmb_virtio_get_capacity(struct device *dev, u8 target) +{ + struct virtio_rpmb_info *vi =3D dev_get_drvdata(dev); + struct virtio_device *vdev =3D vi->vdev; + + u8 capacity; + + virtio_cread(vdev, struct virtio_rpmb_config, capacity, &capacity); + + if (capacity > 0x80) { + dev_err(&vdev->dev, "Error: invalid capacity reported.\n"); + capacity =3D 0x80; + } + + return capacity; +} + +static int rpmb_virtio_get_write_count(struct device *dev, u8 target, + int len, u8 *req, int rlen, u8 *resp) + +{ + struct virtio_rpmb_info *vi =3D dev_get_drvdata(dev); + struct virtio_rpmb_frame *request =3D (struct virtio_rpmb_frame *) req; + struct virtio_rpmb_frame *response =3D (struct virtio_rpmb_frame *) resp; + struct scatterlist out_frame; + struct scatterlist in_frame; + struct scatterlist *sgs[2]; + unsigned int received; + + if (!len || len !=3D VIRTIO_RPMB_FRAME_SZ || !request) + return -EINVAL; + + if (!rlen || rlen !=3D VIRTIO_RPMB_FRAME_SZ || !resp) + return -EINVAL; + + if (be16_to_cpu(request->req_resp) !=3D VIRTIO_RPMB_REQ_GET_WRITE_COUNTER) + return -EINVAL; + + /* Wrap into SG array */ + sg_init_one(&out_frame, request, VIRTIO_RPMB_FRAME_SZ); + sg_init_one(&in_frame, response, VIRTIO_RPMB_FRAME_SZ); + sgs[0] =3D &out_frame; + sgs[1] =3D &in_frame; + + /* Send it, blocks until response */ + received =3D do_virtio_transaction(dev, vi, sgs, 1, 1); + + if (received !=3D VIRTIO_RPMB_FRAME_SZ) + return -EPROTO; + + if (be16_to_cpu(response->req_resp) !=3D VIRTIO_RPMB_RESP_GET_COUNTER) { + dev_err(dev, "failed to get counter (%x/%x)", + be16_to_cpu(response->req_resp), be16_to_cpu(response->result)); + return -EPROTO; + } + + return be16_to_cpu(response->result) =3D=3D VIRTIO_RPMB_RES_OK ? + be32_to_cpu(response->write_counter) : -EIO; +} + +static int rpmb_virtio_write_blocks(struct device *dev, u8 target, + int len, u8 *req, int rlen, u8 *resp) +{ + struct virtio_rpmb_info *vi =3D dev_get_drvdata(dev); + struct virtio_rpmb_frame *request =3D (struct virtio_rpmb_frame *) req; + struct virtio_rpmb_frame *response =3D (struct virtio_rpmb_frame *) resp; + struct scatterlist out_frame; + struct scatterlist in_frame; + struct scatterlist *sgs[2]; + int blocks, data_len, received; + + if (!len || (len % VIRTIO_RPMB_FRAME_SZ) !=3D 0 || !request) + return -EINVAL; + + /* The first frame will contain the details of the request */ + if (be16_to_cpu(request->req_resp) !=3D VIRTIO_RPMB_REQ_DATA_WRITE) + return -EINVAL; + + blocks =3D be16_to_cpu(request->block_count); + if (blocks > vi->max_wr) + return -EINVAL; + + /* + * We either have exactly enough frames to write all the data + * or we have that plus a frame looking for a response. + */ + data_len =3D blocks * VIRTIO_RPMB_FRAME_SZ; + + if (len =3D=3D data_len + VIRTIO_RPMB_FRAME_SZ) { + struct virtio_rpmb_frame *reply =3D &request[blocks]; + + if (be16_to_cpu(reply->req_resp) !=3D VIRTIO_RPMB_REQ_RESULT_READ) + return -EINVAL; + + if (!rlen || rlen !=3D VIRTIO_RPMB_FRAME_SZ || !resp) + return -EINVAL; + } else if (len > data_len) { + return -E2BIG; + } else if (len < data_len) { + return -ENOSPC; + } else if (rlen || resp) { + return -EINVAL; + } + + /* time to do the transaction */ + sg_init_one(&out_frame, request, len); + sgs[0] =3D &out_frame; + + /* optional incoming frame */ + if (rlen && resp) { + sg_init_one(&in_frame, resp, VIRTIO_RPMB_FRAME_SZ); + sgs[1] =3D &in_frame; + } + + received =3D do_virtio_transaction(dev, vi, sgs, 1, resp ? 1 : 0); + + if (response && received !=3D VIRTIO_RPMB_FRAME_SZ) + return -EPROTO; + + if (response && be16_to_cpu(response->req_resp) !=3D VIRTIO_RPMB_RESP_DAT= A_WRITE) { + dev_err(dev, "didn't get a response result (%x/%x)", + be16_to_cpu(response->req_resp), be16_to_cpu(response->result)); + return -EPROTO; + } + + return be16_to_cpu(response->result) =3D=3D VIRTIO_RPMB_RES_OK ? 0 : -EIO; +} + +/** + * rpmb_virtio_read_blocks(): read blocks of data + * @dev: device handle + * @target: target region (unused for VirtIO devices) + * @addr: block address to start reading from + * @count: number of blocks to read + * @len: length of receiving buffer + * @data: receiving buffer + * + * Read a number of blocks from RPMB device. As there is no + * authentication required to read data we construct the outgoing + * frame in this driver. + * + * Returns success/fail with errno and filling in the buffer pointed + * to by @data. + */ +static int rpmb_virtio_read_blocks(struct device *dev, u8 target, + int addr, int count, int len, u8 *data) +{ + struct virtio_rpmb_info *vi =3D dev_get_drvdata(dev); + struct virtio_rpmb_frame *request; + struct virtio_rpmb_frame *response =3D (struct virtio_rpmb_frame *) data; + struct scatterlist out_frame; + struct scatterlist in_frame; + struct scatterlist *sgs[2]; + int computed_len =3D count * VIRTIO_RPMB_FRAME_SZ; + int received; + + if (!count || !data) + return -EINVAL; + + if (addr + count > vi->capacity) + return -ESPIPE; + + if (count > vi->max_rd) + return -EINVAL; + + /* EMSGSIZE? */ + if (len < computed_len) + return -EFBIG; + + /* + * With the basics done we can construct our request. + */ + request =3D kmalloc(VIRTIO_RPMB_FRAME_SZ, GFP_KERNEL); + if (!request) + return -ENOMEM; + + request->req_resp =3D cpu_to_be16(VIRTIO_RPMB_REQ_DATA_READ); + request->block_count =3D cpu_to_be16(count); + request->address =3D cpu_to_be16(addr); + + /* time to do the transaction */ + sg_init_one(&out_frame, request, sizeof(*request)); + sgs[0] =3D &out_frame; + sg_init_one(&in_frame, data, len); + sgs[1] =3D &in_frame; + + received =3D do_virtio_transaction(dev, vi, sgs, 1, 1); + + kfree(request); + + if (received !=3D computed_len) + return -EPROTO; + + if (be16_to_cpu(response->req_resp) !=3D VIRTIO_RPMB_RESP_DATA_READ) { + dev_err(dev, "didn't get a response result (%x/%x)", + be16_to_cpu(response->req_resp), be16_to_cpu(response->result)); + return -EPROTO; + } + + return be16_to_cpu(response->result) =3D=3D VIRTIO_RPMB_RES_OK ? 0 : -EIO; +} + +static struct rpmb_ops rpmb_virtio_ops =3D { + .program_key =3D rpmb_virtio_program_key, + .get_capacity =3D rpmb_virtio_get_capacity, + .get_write_count =3D rpmb_virtio_get_write_count, + .write_blocks =3D rpmb_virtio_write_blocks, + .read_blocks =3D rpmb_virtio_read_blocks, +}; + +static int rpmb_virtio_dev_init(struct virtio_rpmb_info *vi) +{ + struct virtio_device *vdev =3D vi->vdev; + /* XXX this seems very roundabout */ + struct device *dev =3D &vi->vq->vdev->dev; + int ret =3D 0; + + virtio_cread(vdev, struct virtio_rpmb_config, + max_wr_cnt, &vi->max_wr); + virtio_cread(vdev, struct virtio_rpmb_config, + max_rd_cnt, &vi->max_rd); + virtio_cread(vdev, struct virtio_rpmb_config, + capacity, &vi->capacity); + + rpmb_virtio_ops.dev_id_len =3D strlen(id); + rpmb_virtio_ops.dev_id =3D id; + rpmb_virtio_ops.wr_cnt_max =3D vi->max_wr; + rpmb_virtio_ops.rd_cnt_max =3D vi->max_rd; + rpmb_virtio_ops.block_size =3D 1; + + vi->rdev =3D rpmb_dev_register(dev, 0, &rpmb_virtio_ops); + if (IS_ERR(vi->rdev)) { + ret =3D PTR_ERR(vi->rdev); + goto err; + } + + dev_set_drvdata(dev, vi); +err: + return ret; +} + +static int virtio_rpmb_init(struct virtio_device *vdev) +{ + int ret; + struct virtio_rpmb_info *vi; + + vi =3D kzalloc(sizeof(*vi), GFP_KERNEL); + if (!vi) + return -ENOMEM; + + init_waitqueue_head(&vi->have_data); + mutex_init(&vi->lock); + + /* link virtio_rpmb_info to virtio_device */ + vdev->priv =3D vi; + vi->vdev =3D vdev; + + /* We expect a single virtqueue. */ + vi->vq =3D virtio_find_single_vq(vdev, virtio_rpmb_recv_done, "request"); + if (IS_ERR(vi->vq)) { + dev_err(&vdev->dev, "get single vq failed!\n"); + ret =3D PTR_ERR(vi->vq); + goto err; + } + + /* create vrpmb device. */ + ret =3D rpmb_virtio_dev_init(vi); + if (ret) { + dev_err(&vdev->dev, "create vrpmb device failed.\n"); + goto err; + } + + dev_info(&vdev->dev, "init done!\n"); + + return 0; + +err: + kfree(vi); + return ret; +} + +static void virtio_rpmb_remove(struct virtio_device *vdev) +{ + struct virtio_rpmb_info *vi; + + vi =3D vdev->priv; + if (!vi) + return; + + if (wq_has_sleeper(&vi->have_data)) + wake_up(&vi->have_data); + + rpmb_dev_unregister(vi->rdev); + + if (vdev->config->reset) + vdev->config->reset(vdev); + + if (vdev->config->del_vqs) + vdev->config->del_vqs(vdev); + + kfree(vi); +} + +static int virtio_rpmb_probe(struct virtio_device *vdev) +{ + return virtio_rpmb_init(vdev); +} + +#ifdef CONFIG_PM_SLEEP +static int virtio_rpmb_freeze(struct virtio_device *vdev) +{ + virtio_rpmb_remove(vdev); + return 0; +} + +static int virtio_rpmb_restore(struct virtio_device *vdev) +{ + return virtio_rpmb_init(vdev); +} +#endif + +static struct virtio_device_id id_table[] =3D { + { VIRTIO_ID_RPMB, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static struct virtio_driver virtio_rpmb_driver =3D { + .driver.name =3D KBUILD_MODNAME, + .driver.owner =3D THIS_MODULE, + .id_table =3D id_table, + .probe =3D virtio_rpmb_probe, + .remove =3D virtio_rpmb_remove, +#ifdef CONFIG_PM_SLEEP + .freeze =3D virtio_rpmb_freeze, + .restore =3D virtio_rpmb_restore, +#endif +}; + +module_virtio_driver(virtio_rpmb_driver); +MODULE_DEVICE_TABLE(virtio, id_table); + +MODULE_DESCRIPTION("Virtio rpmb frontend driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/include/uapi/linux/virtio_rpmb.h b/include/uapi/linux/virtio_r= pmb.h new file mode 100644 index 000000000000..f048cd968210 --- /dev/null +++ b/include/uapi/linux/virtio_rpmb.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Cl= ause) */ + +#ifndef _UAPI_LINUX_VIRTIO_RPMB_H +#define _UAPI_LINUX_VIRTIO_RPMB_H + +#include +#include +#include +#include + +struct virtio_rpmb_config { + __u8 capacity; + __u8 max_wr_cnt; + __u8 max_rd_cnt; +} __attribute__((packed)); + +/* RPMB Request Types (in .req_resp) */ +#define VIRTIO_RPMB_REQ_PROGRAM_KEY 0x0001 +#define VIRTIO_RPMB_REQ_GET_WRITE_COUNTER 0x0002 +#define VIRTIO_RPMB_REQ_DATA_WRITE 0x0003 +#define VIRTIO_RPMB_REQ_DATA_READ 0x0004 +#define VIRTIO_RPMB_REQ_RESULT_READ 0x0005 + +/* RPMB Response Types (in .req_resp) */ +#define VIRTIO_RPMB_RESP_PROGRAM_KEY 0x0100 +#define VIRTIO_RPMB_RESP_GET_COUNTER 0x0200 +#define VIRTIO_RPMB_RESP_DATA_WRITE 0x0300 +#define VIRTIO_RPMB_RESP_DATA_READ 0x0400 + +struct virtio_rpmb_frame { + __u8 stuff[196]; + __u8 key_mac[32]; + __u8 data[256]; + __u8 nonce[16]; + __be32 write_counter; + __be16 address; + __be16 block_count; + __be16 result; + __be16 req_resp; +} __attribute__((packed)); + +/* RPMB Operation Results (in .result) */ +#define VIRTIO_RPMB_RES_OK 0x0000 +#define VIRTIO_RPMB_RES_GENERAL_FAILURE 0x0001 +#define VIRTIO_RPMB_RES_AUTH_FAILURE 0x0002 +#define VIRTIO_RPMB_RES_COUNT_FAILURE 0x0003 +#define VIRTIO_RPMB_RES_ADDR_FAILURE 0x0004 +#define VIRTIO_RPMB_RES_WRITE_FAILURE 0x0005 +#define VIRTIO_RPMB_RES_READ_FAILURE 0x0006 +#define VIRTIO_RPMB_RES_NO_AUTH_KEY 0x0007 +#define VIRTIO_RPMB_RES_WRITE_COUNTER_EXPIRED 0x0080 + + +#endif /* _UAPI_LINUX_VIRTIO_RPMB_H */ --=20 2.30.2 From nobody Fri Jun 19 09:50:03 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id BCD75C35295 for ; Wed, 6 Apr 2022 00:35:14 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1350570AbiDFAT4 (ORCPT ); Tue, 5 Apr 2022 20:19:56 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43846 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1348962AbiDEJsu (ORCPT ); Tue, 5 Apr 2022 05:48:50 -0400 Received: from mail-wm1-x32c.google.com (mail-wm1-x32c.google.com [IPv6:2a00:1450:4864:20::32c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 907CDA5EBB for ; Tue, 5 Apr 2022 02:38:08 -0700 (PDT) Received: by mail-wm1-x32c.google.com with SMTP id bi13-20020a05600c3d8d00b0038c2c33d8f3so1177802wmb.4 for ; Tue, 05 Apr 2022 02:38:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=d4jB3djPX10WcZTI6rW+C0rQK94UmI79p5ZbxcZQAWw=; b=xd+udpVeXE7Et/uacVYzKho70vyeWGh4Ij0lXsOI/GaVlpFvDfdsc24KOcu8aJmBWF 3FuMDLcj/RVGcv44kUnmY6atkjAHTqbpA9UfwTg9gEw6ndkI4FIvFkMDb/vSPVwCIonK KGPci4JC4wcb0L7dXvyf/+HKbBBkr+fOM0s04oGf977g8MydlxKtQ7xZksQqjG/QoSZq 9tK+ewTvxM4kRRvUBPy0zwOi8N/dS/bCCx5y59k5oISAheaumxtCB46kgnX1Osz0MvdX oWLsR4CE7qg100mK2IlTrGazcRHwPl8zEWL2CZ8PxHLL0+fkx2srUf6cRmuqUGxhg/i9 ZqGg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=d4jB3djPX10WcZTI6rW+C0rQK94UmI79p5ZbxcZQAWw=; b=5m04odR9XS4R4n2aXAdiW2Ft/dAqhK3F/UwDtKGxfuTbauj0MmAydl/e71andCF+ve FjDvwukCz9m/x5glYUqngE5v6S6v/FFC4lJkJRRttKmzdxN5aKryCTNYiAAavuKTLjp8 rXcPCleF1C1SgQgCyyoQpdkuXgxQUHd2D1A4p5VO97ayFlFJUb6QR0IuZk41JBdPHLtC UnaUrJGX01/HKY9h5/TFLiLsoe5xDiRCPQa2fPz6l3dBRvjE23W2W0UaIezHFc8Q2c0d 4h0l4ecnKOgYBp5UXPWHUdBFEgK8vGzC2PMbyIfI4NTpe2SA+/foM9+dvfHU2td1hr1j 4uAw== X-Gm-Message-State: AOAM532qglz1Hh/qgPPLF1WuEXRUKQ6/FbIYAyrWKvaHEX5vnMsfRMVp L3gsBxvh9CARJce/V8lu8F4DGQ== X-Google-Smtp-Source: ABdhPJyfEKkHt/FOyqqqAk5VV5w8J0Eep1ova1ZnbCDT2b7IMjJfkjEjvAVgJbjsbDbQ7Le/0gBsFw== X-Received: by 2002:a1c:f717:0:b0:38e:6bed:2aad with SMTP id v23-20020a1cf717000000b0038e6bed2aadmr2272316wmh.45.1649151486917; Tue, 05 Apr 2022 02:38:06 -0700 (PDT) Received: from zen.linaroharston ([51.148.130.216]) by smtp.gmail.com with ESMTPSA id l3-20020a1ced03000000b0038ce57d28a1sm1564609wmh.26.2022.04.05.02.38.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 05 Apr 2022 02:38:00 -0700 (PDT) Received: from zen.lan (localhost [127.0.0.1]) by zen.linaroharston (Postfix) with ESMTP id 29DC41FFBC; Tue, 5 Apr 2022 10:38:00 +0100 (BST) From: =?UTF-8?q?Alex=20Benn=C3=A9e?= To: linux-kernel@vger.kernel.org Cc: maxim.uvarov@linaro.org, joakim.bech@linaro.org, ulf.hansson@linaro.org, ilias.apalodimas@linaro.org, arnd@linaro.org, ruchika.gupta@linaro.org, tomas.winkler@intel.com, yang.huang@intel.com, bing.zhu@intel.com, Matti.Moell@opensynergy.com, hmo@opensynergy.com, linux-mmc@vger.kernel.org, linux-scsi@vger.kernel.org, =?UTF-8?q?Alex=20Benn=C3=A9e?= , Alexander Usyskin Subject: [PATCH v2 4/4] tools rpmb: add RPBM access tool Date: Tue, 5 Apr 2022 10:37:59 +0100 Message-Id: <20220405093759.1126835-5-alex.bennee@linaro.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220405093759.1126835-1-alex.bennee@linaro.org> References: <20220405093759.1126835-1-alex.bennee@linaro.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Add simple RPMB host testing tool. It can be used to program key, write and read data block, and retrieve write counter. It's based on Tomas' original tool but only deals with the virtio-rpmb device. I've also split the RPMB specific functions (vrpmb_*) from the generic hooks (op_rpmb_*) to make it easier to add other RPMB implementations. A simple test.sh script will exercise the interface (assuming a virgin RPMB device awaiting a programmed key). Signed-off-by: Alex Benn=C3=A9e Cc: Tomas Winkler Cc: Alexander Usyskin --- ajb: - new per op ioctl API --- MAINTAINERS | 1 + tools/Makefile | 16 +- tools/rpmb/.gitignore | 2 + tools/rpmb/Makefile | 41 ++ tools/rpmb/key | 1 + tools/rpmb/rpmb.c | 1083 +++++++++++++++++++++++++++++++++++++++++ tools/rpmb/test.sh | 22 + 7 files changed, 1161 insertions(+), 5 deletions(-) create mode 100644 tools/rpmb/.gitignore create mode 100644 tools/rpmb/Makefile create mode 100644 tools/rpmb/key create mode 100644 tools/rpmb/rpmb.c create mode 100755 tools/rpmb/test.sh diff --git a/MAINTAINERS b/MAINTAINERS index 0a744da21817..70716250aa51 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16751,6 +16751,7 @@ S: Supported F: drivers/rpmb/* F: include/uapi/linux/rpmb.h F: include/linux/rpmb.h +F: tools/rpmb/ =20 RPMSG TTY DRIVER M: Arnaud Pouliquen diff --git a/tools/Makefile b/tools/Makefile index db2f7b8ebed5..0cc62dcae581 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -27,6 +27,7 @@ help: @echo ' objtool - an ELF object analysis tool' @echo ' pci - PCI tools' @echo ' perf - Linux performance measurement and analy= sis tool' + @echo ' rpmb - Replay protected memory block access to= ol' @echo ' selftests - various kernel selftests' @echo ' bootconfig - boot config tool' @echo ' spi - spi tools' @@ -65,7 +66,9 @@ acpi: FORCE cpupower: FORCE $(call descend,power/$@) =20 -cgroup counter firewire hv guest bootconfig spi usb virtio vm bpf iio gpio= objtool leds wmi pci firmware debugging tracing: FORCE +cgroup counter firewire hv guest bootconfig spi usb virtio vm bpf iio \ + gpio objtool leds wmi pci firmware debugging tracing rpmb: FORCE + $(call descend,$@) =20 bpf/%: FORCE @@ -101,7 +104,7 @@ all: acpi cgroup counter cpupower gpio hv firewire \ perf selftests bootconfig spi turbostat usb \ virtio vm bpf x86_energy_perf_policy \ tmon freefall iio objtool kvm_stat wmi \ - pci debugging tracing + pci debugging tracing rpmb =20 acpi_install: $(call descend,power/$(@:_install=3D),install) @@ -109,7 +112,7 @@ acpi_install: cpupower_install: $(call descend,power/$(@:_install=3D),install) =20 -cgroup_install counter_install firewire_install gpio_install hv_install ii= o_install perf_install bootconfig_install spi_install usb_install virtio_in= stall vm_install bpf_install objtool_install wmi_install pci_install debugg= ing_install tracing_install: +cgroup_install counter_install firewire_install gpio_install hv_install ii= o_install perf_install bootconfig_install spi_install usb_install virtio_in= stall vm_install bpf_install objtool_install wmi_install pci_install debugg= ing_install tracing_install rpmb_install: $(call descend,$(@:_install=3D),install) =20 selftests_install: @@ -129,7 +132,7 @@ kvm_stat_install: =20 install: acpi_install cgroup_install counter_install cpupower_install gpio= _install \ hv_install firewire_install iio_install \ - perf_install selftests_install turbostat_install usb_install \ + perf_install rpmb_install selftests_install turbostat_install usb_instal= l \ virtio_install vm_install bpf_install x86_energy_perf_policy_install \ tmon_install freefall_install objtool_install kvm_stat_install \ wmi_install pci_install debugging_install intel-speed-select_install \ @@ -157,6 +160,9 @@ perf_clean: $(Q)mkdir -p $(PERF_O) . $(Q)$(MAKE) --no-print-directory -C perf O=3D$(PERF_O) subdir=3D clean =20 +rpmb_clean: + $(call descend,$(@:_clean=3D),clean) + selftests_clean: $(call descend,testing/$(@:_clean=3D),clean) =20 @@ -173,7 +179,7 @@ build_clean: $(call descend,build,clean) =20 clean: acpi_clean cgroup_clean counter_clean cpupower_clean hv_clean firew= ire_clean \ - perf_clean selftests_clean turbostat_clean bootconfig_clean spi_clean us= b_clean virtio_clean \ + perf_clean rpmb_clean selftests_clean turbostat_clean bootconfig_clean s= pi_clean usb_clean virtio_clean \ vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean \ gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean d= ebugging_clean \ diff --git a/tools/rpmb/.gitignore b/tools/rpmb/.gitignore new file mode 100644 index 000000000000..218f680548e6 --- /dev/null +++ b/tools/rpmb/.gitignore @@ -0,0 +1,2 @@ +*.o +rpmb diff --git a/tools/rpmb/Makefile b/tools/rpmb/Makefile new file mode 100644 index 000000000000..e9429be4eaba --- /dev/null +++ b/tools/rpmb/Makefile @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../scripts/Makefile.include + +CC ?=3D $(CROSS_COMPILE)gcc +LD ?=3D $(CROSS_COMPILE)ld +PKG_CONFIG =3D $(CROSS_COMPILE)pkg-config + +ifeq ($(srctree),) +srctree :=3D $(patsubst %/,%,$(dir $(shell pwd))) +srctree :=3D $(patsubst %/,%,$(dir $(srctree))) +#$(info Determined 'srctree' to be $(srctree)) +endif + +INSTALL =3D install +prefix ?=3D /usr/local +bindir =3D $(prefix)/bin + + +CFLAGS +=3D $(HOSTCFLAGS) +CFLAGS +=3D -D__EXPORTED_HEADERS__ +CFLAGS +=3D -Wall -Wextra -ggdb +ifdef RPMB_STATIC +LDFLAGS +=3D -pthread -static -Wl,-u,pthread_mutex_unlock +CFLAGS +=3D -pthread -static +PKG_STATIC =3D --static +endif +CFLAGS +=3D -I$(srctree)/include/uapi -I$(srctree)/include +LDLIBS +=3D $(shell $(PKG_CONFIG) --libs $(PKG_STATIC) libcrypto) + +prog :=3D rpmb + +all : $(prog) + +$(prog): rpmb.o + +clean : + $(RM) $(prog) *.o + +install: $(prog) + $(INSTALL) -m755 -d $(DESTDIR)$(bindir) + $(INSTALL) $(prog) $(DESTDIR)$(bindir) diff --git a/tools/rpmb/key b/tools/rpmb/key new file mode 100644 index 000000000000..2b6bd3bc3fe6 --- /dev/null +++ b/tools/rpmb/key @@ -0,0 +1 @@ +=EF=BF=BD=EF=BF=BD=EF=BF=BD=EF=BF=BDh=EF=BF=BD#=D7=A2=EF=BF=BD=EF=BF=BDpRT= =E0=BF=AE=EF=BF=BD=1F=EF=BF=BD=1F\r|O=EF=BF=BD =EF=BF=BDmo=EF=BF=BD=10 \ No newline at end of file diff --git a/tools/rpmb/rpmb.c b/tools/rpmb/rpmb.c new file mode 100644 index 000000000000..ff7ea198eda1 --- /dev/null +++ b/tools/rpmb/rpmb.c @@ -0,0 +1,1083 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (C) 2016-2019 Intel Corp. All rights reserved + * Copyright (C) 2021-2022 Linaro Ltd + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* if uapi header isn't installed, this might not yet exist */ +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +#include "linux/rpmb.h" +#include "linux/virtio_rpmb.h" + +#define RPMB_KEY_SIZE 32 +#define RPMB_MAC_SIZE 32 +#define RPMB_NONCE_SIZE 16 +#define RPMB_BLOCK_SIZE 256 + +#define min(a, b) \ + ({ __typeof__ (a) _a =3D (a); \ + __typeof__ (b) _b =3D (b); \ + _a < _b ? _a : _b; }) + + +static bool verbose; +#define rpmb_dbg(fmt, ARGS...) do { \ + if (verbose) \ + fprintf(stderr, "rpmb: " fmt, ##ARGS); \ +} while (0) + +#define rpmb_msg(fmt, ARGS...) \ + fprintf(stderr, "rpmb: " fmt, ##ARGS) + +#define rpmb_err(fmt, ARGS...) \ + fprintf(stderr, "rpmb:%d error: " fmt, __LINE__, ##ARGS) + + +/* + * Utility functions + */ +static int open_dev_file(const char *devfile, struct rpmb_ioc_cap_cmd *cap) +{ + struct rpmb_ioc_ver_cmd ver; + int fd; + int ret; + + fd =3D open(devfile, O_RDWR); + if (fd < 0) + rpmb_err("Cannot open: %s: %s.\n", devfile, strerror(errno)); + + ret =3D ioctl(fd, RPMB_IOC_VER_CMD, &ver); + if (ret < 0) { + rpmb_err("ioctl failure %d: %s.\n", ret, strerror(errno)); + goto err; + } + + printf("RPMB API Version %X\n", ver.api_version); + + ret =3D ioctl(fd, RPMB_IOC_CAP_CMD, cap); + if (ret < 0) { + rpmb_err("ioctl failure %d: %s.\n", ret, strerror(errno)); + goto err; + } + + rpmb_dbg("RPMB rpmb_target =3D %d\n", cap->target); + rpmb_dbg("RPMB capacity =3D %d\n", cap->capacity); + rpmb_dbg("RPMB block_size =3D %d\n", cap->block_size); + rpmb_dbg("RPMB wr_cnt_max =3D %d\n", cap->wr_cnt_max); + rpmb_dbg("RPMB rd_cnt_max =3D %d\n", cap->rd_cnt_max); + rpmb_dbg("RPMB auth_method =3D %d\n", cap->auth_method); + + return fd; +err: + close(fd); + return -1; +} + +static int open_rd_file(const char *datafile, const char *type) +{ + int fd; + + if (!strcmp(datafile, "-")) + fd =3D STDIN_FILENO; + else + fd =3D open(datafile, O_RDONLY); + + if (fd < 0) + rpmb_err("Cannot open %s: %s: %s.\n", + type, datafile, strerror(errno)); + + return fd; +} + +static int open_wr_file(const char *datafile, const char *type) +{ + int fd; + + if (!strcmp(datafile, "-")) + fd =3D STDOUT_FILENO; + else + fd =3D open(datafile, O_WRONLY | O_CREAT | O_APPEND, 0600); + if (fd < 0) + rpmb_err("Cannot open %s: %s: %s.\n", + type, datafile, strerror(errno)); + return fd; +} + +static void close_fd(int fd) +{ + if (fd > 0 && fd !=3D STDIN_FILENO && fd !=3D STDOUT_FILENO) + close(fd); +} + +/* need to just cast out 'const' in write(2) */ +typedef ssize_t (*rwfunc_t)(int fd, void *buf, size_t count); +/* blocking rw wrapper */ +static ssize_t rw(rwfunc_t func, int fd, unsigned char *buf, size_t size) +{ + ssize_t ntotal =3D 0, n; + char *_buf =3D (char *)buf; + + do { + n =3D func(fd, _buf + ntotal, size - ntotal); + if (n =3D=3D -1 && errno !=3D EINTR) { + ntotal =3D -1; + break; + } else if (n > 0) { + ntotal +=3D n; + } + } while (n !=3D 0 && (size_t)ntotal !=3D size); + + return ntotal; +} + +static ssize_t read_file(int fd, unsigned char *data, size_t size) +{ + ssize_t ret; + + ret =3D rw(read, fd, data, size); + if (ret < 0) { + rpmb_err("cannot read file: %s\n.", strerror(errno)); + } else if ((size_t)ret !=3D size) { + rpmb_err("read %zd but must be %zu bytes length.\n", ret, size); + ret =3D -EINVAL; + } + + return ret; +} + +static ssize_t write_file(int fd, unsigned char *data, size_t size) +{ + ssize_t ret; + + ret =3D rw((rwfunc_t)write, fd, data, size); + if (ret < 0) { + rpmb_err("cannot read file: %s.\n", strerror(errno)); + } else if ((size_t)ret !=3D size) { + rpmb_err("data is %zd but must be %zu bytes length.\n", + ret, size); + ret =3D -EINVAL; + } + return ret; +} + +static void __dump_buffer(const char *buf) +{ + fprintf(stderr, "%s\n", buf); +} + +static void +dump_hex_buffer(const char *title, const void *buf, size_t len) +{ +#define PBUF_SZ (16 * 3) + const unsigned char *_buf =3D (const unsigned char *)buf; + char pbuf[PBUF_SZ]; + int j =3D 0; + + if (title) + fprintf(stderr, "%s\n", title); + while (len-- > 0) { + snprintf(&pbuf[j], PBUF_SZ - j, "%02X ", *_buf++); + j +=3D 3; + if (j =3D=3D PBUF_SZ) { + __dump_buffer(pbuf); + j =3D 0; + } + } + if (j) + __dump_buffer(pbuf); +} + +/* + * MAC bits + */ + +/* The hmac calculation is from data to the end of the frame */ +#define vrpmb_hmac_data_len \ + (sizeof(struct virtio_rpmb_frame) - \ + offsetof(struct virtio_rpmb_frame, data)) + +static int vrpmb_calc_hmac_sha256(struct virtio_rpmb_frame *frames, + size_t blocks_cnt, + const unsigned char key[], + unsigned int key_size, + unsigned char mac[], + unsigned int mac_size) +{ + HMAC_CTX *ctx; + int ret; + unsigned int i; + + /* SSL returns 1 on success 0 on failure */ + + ctx =3D HMAC_CTX_new(); + + ret =3D HMAC_Init_ex(ctx, key, key_size, EVP_sha256(), NULL); + if (ret =3D=3D 0) + goto out; + for (i =3D 0; i < blocks_cnt; i++) + HMAC_Update(ctx, frames[i].data, vrpmb_hmac_data_len); + + ret =3D HMAC_Final(ctx, mac, &mac_size); + if (ret =3D=3D 0) + goto out; + if (mac_size !=3D RPMB_MAC_SIZE) + ret =3D 0; + + ret =3D 1; +out: + HMAC_CTX_free(ctx); + return ret =3D=3D 1 ? 0 : -1; +} + + +static int vrpmb_check_mac(const unsigned char *key, + struct virtio_rpmb_frame *frames_out, + unsigned int cnt_out) +{ + unsigned char mac[RPMB_MAC_SIZE]; + + if (cnt_out =3D=3D 0) { + rpmb_err("RPMB 0 output frames.\n"); + return -1; + } + + vrpmb_calc_hmac_sha256(frames_out, cnt_out, + key, RPMB_KEY_SIZE, + mac, RPMB_MAC_SIZE); + + if (memcmp(mac, frames_out[cnt_out - 1].key_mac, RPMB_MAC_SIZE)) { + rpmb_err("RPMB hmac mismatch:\n"); + dump_hex_buffer("Result MAC: ", + frames_out[cnt_out - 1].key_mac, RPMB_MAC_SIZE); + dump_hex_buffer("Expected MAC: ", mac, RPMB_MAC_SIZE); + return -1; + } + + return 0; +} + +/* Compute the frames MAC and insert it */ +static void vrpmb_compute_mac(const unsigned char *key, + struct virtio_rpmb_frame *frame) +{ + vrpmb_calc_hmac_sha256(frame, 1, key, RPMB_KEY_SIZE, &frame->key_mac, RPM= B_MAC_SIZE); +} + +/* + * VirtIO RPMB Bits + */ + +static const char *vrpmb_op_str(uint16_t op) +{ +#define RPMB_OP(_op) case VIRTIO_RPMB_REQ_##_op: return #_op + + switch (op) { + RPMB_OP(PROGRAM_KEY); + RPMB_OP(GET_WRITE_COUNTER); + RPMB_OP(DATA_WRITE); + RPMB_OP(DATA_READ); + RPMB_OP(RESULT_READ); + break; + default: + return "unknown"; + } +#undef RPMB_OP +} + +static const char *vrpmb_result_str(uint16_t result) +{ +#define str(x) #x +#define RPMB_ERR(_res) case VIRTIO_RPMB_RES_##_res: \ + { if (result & VIRTIO_RPMB_RES_WRITE_COUNTER_EXPIRED) \ + return "COUNTER_EXPIRE:" str(_res); \ + else \ + return str(_res); \ + } + + switch (result & 0x000F) { + RPMB_ERR(OK); + RPMB_ERR(GENERAL_FAILURE); + RPMB_ERR(AUTH_FAILURE); + RPMB_ERR(COUNT_FAILURE); + RPMB_ERR(ADDR_FAILURE); + RPMB_ERR(WRITE_FAILURE); + RPMB_ERR(READ_FAILURE); + RPMB_ERR(NO_AUTH_KEY); + break; + default: + return "unknown"; + } +#undef RPMB_ERR +#undef str +}; + +#define RPMB_REQ2RESP(_OP) ((_OP) << 8) +#define RPMB_RESP2REQ(_OP) ((_OP) >> 8) + +static void vrpmb_dump_frame(const char *title, const struct virtio_rpmb_f= rame *f) +{ + uint16_t result, req_resp; + + if (!verbose) + return; + + if (!f) + return; + + result =3D be16toh(f->result); + req_resp =3D be16toh(f->req_resp); + if (req_resp & 0xf00) + req_resp =3D RPMB_RESP2REQ(req_resp); + + fprintf(stderr, "--------------- %s ---------------\n", + title ? title : "start"); + fprintf(stderr, "ptr: %p\n", f); + dump_hex_buffer("key_mac: ", f->key_mac, 32); + dump_hex_buffer("data: ", f->data, 256); + dump_hex_buffer("nonce: ", f->nonce, 16); + fprintf(stderr, "write_counter: %u\n", be32toh(f->write_counter)); + fprintf(stderr, "address: %0X\n", be16toh(f->address)); + fprintf(stderr, "block_count: %u\n", be16toh(f->block_count)); + fprintf(stderr, "result %s:%d\n", vrpmb_result_str(result), result); + fprintf(stderr, "req_resp %s\n", vrpmb_op_str(req_resp)); + fprintf(stderr, "--------------- End ---------------\n"); +} + +static bool vrpmb_check_req_resp(uint16_t req, struct virtio_rpmb_frame *f) +{ + uint16_t req_resp =3D be16toh(f->req_resp); + + if (RPMB_REQ2RESP(req) !=3D req_resp) { + rpmb_err("RPMB response mismatch %04X !=3D %04X\n.", + RPMB_REQ2RESP(req), req_resp); + return false; + } + + rpmb_msg("validated response: 0x%x\n", req_resp); + return true; +} + + + +static struct virtio_rpmb_frame * vrpmb_alloc_frames(int n) +{ + struct virtio_rpmb_frame *frames; + + frames =3D calloc(n, sizeof(struct virtio_rpmb_frame)); + if (frames) + memset(frames, 0, n * sizeof(struct virtio_rpmb_frame)); + return frames; +} + +static int vrpmb_program_key(int dev_fd, void *key) +{ + struct rpmb_ioc_reqresp_cmd cmd; + struct virtio_rpmb_frame *out, *in; + int ret; + + out =3D vrpmb_alloc_frames(2); + + /* construct outgoing frames */ + out[0].req_resp =3D htobe16(VIRTIO_RPMB_REQ_PROGRAM_KEY); + out[0].block_count =3D htobe16(1); + memcpy(&out[0].key_mac[0], key, RPMB_MAC_SIZE); + RAND_bytes((void *) &out[0].nonce, RPMB_NONCE_SIZE); + out[1].req_resp =3D htobe16(VIRTIO_RPMB_REQ_RESULT_READ); + RAND_bytes((void *) &out[1].nonce, RPMB_NONCE_SIZE); + + cmd.len =3D sizeof(struct virtio_rpmb_frame) * 2; + cmd.request =3D (void *) out; + + vrpmb_dump_frame("pkey", &out[0]); + vrpmb_dump_frame("request", &out[1]); + + /* space for response */ + in =3D vrpmb_alloc_frames(1); + cmd.rlen =3D sizeof(struct virtio_rpmb_frame); + cmd.response =3D (void *) in; + + /* do it */ + ret =3D ioctl(dev_fd, RPMB_IOC_PKEY_CMD, &cmd); + if (ret < 0) { + rpmb_err("pkey ioctl failure %d: %s.\n", ret, strerror(errno)); + goto out; + } + + vrpmb_dump_frame("response", in); + + /* validate response */ + if (!vrpmb_check_req_resp(VIRTIO_RPMB_REQ_PROGRAM_KEY, in)) { + ret =3D -1; + goto out; + } + + ret =3D vrpmb_check_mac(key, in, 1); + if (ret) { + rpmb_err("%s: check mac error %d\n", __func__, ret); + goto out; + } + +out: + if (ret) + rpmb_err("RPMB operation %s failed=3D%d %s[0x%04x]\n", + vrpmb_op_str(out->req_resp), ret, + vrpmb_result_str(in->result), in->result); + + free(in); + free(out); + return ret; +} + +static int vrpmb_get_write_counter(int dev_fd, void *key) +{ + struct rpmb_ioc_reqresp_cmd cmd; + struct virtio_rpmb_frame *out, *in =3D NULL; + int ret; + + out =3D vrpmb_alloc_frames(1); + in =3D vrpmb_alloc_frames(1); + if (!out || !in) { + rpmb_err("couldn't allocate frames"); + return -ENOMEM; + } + + /* Query frame */ + out[0].req_resp =3D htobe16(VIRTIO_RPMB_REQ_GET_WRITE_COUNTER); + out[0].block_count =3D htobe16(1); + RAND_bytes((void *) &out[0].nonce, RPMB_NONCE_SIZE); + + cmd.request =3D (void *) out; + cmd.len =3D sizeof(struct virtio_rpmb_frame); + cmd.response =3D (void *) in; + cmd.rlen =3D sizeof(struct virtio_rpmb_frame); + + ret =3D ioctl(dev_fd, RPMB_IOC_COUNTER_CMD, &cmd); + if (ret < 0) { + rpmb_err("get wcount ioctl failure %d: %s.\n", ret, + strerror(errno)); + goto out; + } + + vrpmb_dump_frame("write_counter", in); + + ret =3D be32toh(in->write_counter); + rpmb_msg("counter 0x%x\n", ret); + + /* validate response */ + if (!vrpmb_check_req_resp(VIRTIO_RPMB_REQ_GET_WRITE_COUNTER, in)) { + ret =3D -1; + goto out; + } + + if (memcmp(&in->nonce, &out[0].nonce, RPMB_NONCE_SIZE)) { + rpmb_err("RPMB NONCE mismatch\n"); + dump_hex_buffer("Result NONCE:", + &in->nonce, RPMB_NONCE_SIZE); + dump_hex_buffer("Expected NONCE: ", + &out[0].nonce, RPMB_NONCE_SIZE); + ret =3D -1; + goto out; + } + + if (key) { + ret =3D vrpmb_check_mac(key, in, 1); + if (ret) + rpmb_err("%s: check mac error %d\n", __func__, ret); + } + + ret =3D be32toh(in->write_counter); + rpmb_msg("counter 0x%x\n", ret); + +out: + free(out); + free(in); + return ret; + +} + +static int vrpmb_write_blocks(int dev_fd, void *key, void *data, int addr,= int len) +{ + struct rpmb_ioc_reqresp_cmd cmd; + struct virtio_rpmb_frame *out, *in =3D NULL; + int frames =3D (len / 256) + 1; + uint8_t *p =3D (uint8_t *) data; + int i, ret; + int write_count =3D vrpmb_get_write_counter(dev_fd, key); + + out =3D vrpmb_alloc_frames(frames); + in =3D vrpmb_alloc_frames(1); + if (!out || !in) { + rpmb_err("couldn't allocate frames"); + return -ENOMEM; + } + + /* First frame */ + out[0].req_resp =3D htobe16(VIRTIO_RPMB_REQ_DATA_WRITE); + out[0].block_count =3D htobe16(frames - 1); + out[0].address =3D htobe16(addr); + out[0].write_counter =3D htobe32(write_count); + + /* Copy data to write and prepare frames */ + for (i =3D 0; i < frames; i++) { + struct virtio_rpmb_frame *f =3D &out[i]; + + memcpy(&f->data, &p[i * 256], 256); + RAND_bytes((void *) &f->nonce, RPMB_NONCE_SIZE); + vrpmb_compute_mac(key, f); + } + + vrpmb_dump_frame("write_blocks", &out[0]); + + /* Response request */ + out[frames - 1].req_resp =3D htobe16(VIRTIO_RPMB_REQ_RESULT_READ); + out[frames - 1].block_count =3D htobe16(1); + RAND_bytes((void *) &out[frames - 1].nonce, RPMB_NONCE_SIZE); + vrpmb_compute_mac(key, &out[frames - 1]); + + vrpmb_dump_frame("result_req", &out[frames - 1]); + + cmd.request =3D (void *) out; + cmd.len =3D frames * sizeof(struct virtio_rpmb_frame); + cmd.response =3D (void *) in; + cmd.rlen =3D sizeof(struct virtio_rpmb_frame); + + ret =3D ioctl(dev_fd, RPMB_IOC_WBLOCKS_CMD, &cmd); + if (ret < 0) { + rpmb_err("wblocks ioctl failure %d: %s.\n", ret, + strerror(errno)); + } + + free(out); + free(in); + return ret; +} + +/* + * To read blocks we receive a bunch of frames from the vrpmb device + * which we need to validate and extract the actual data into + * requested buffer. + */ +static int vrpmb_read_blocks(int dev_fd, void *key, int addr, int count, v= oid *data, int len) +{ + struct rpmb_ioc_rblocks_cmd cmd; + int frame_length =3D count * sizeof(struct virtio_rpmb_frame); + struct virtio_rpmb_frame *frames =3D malloc(frame_length); + int i, ret =3D -1; + + rpmb_dbg("%s: reading %d blocks into %d bytes (%d bytes of frames)\n", + __func__, count, len, frame_length); + + if (!frames) { + rpmb_err("unable to allocate memory for frames"); + return -1; + } + + cmd.addr =3D addr; + cmd.count =3D count; + cmd.len =3D frame_length; + cmd.data =3D (__u8 *) frames; + + ret =3D ioctl(dev_fd, RPMB_IOC_RBLOCKS_CMD, &cmd); + if (ret < 0) { + rpmb_err("rblocks ioctl failure %d: %s.\n", ret, + strerror(errno)); + goto out; + } + + for (i =3D 0; i < count; i++) { + struct virtio_rpmb_frame *f =3D &frames[i]; + + vrpmb_dump_frame("block data", f); + + if (key) { + ret =3D vrpmb_check_mac(key, f, 1); + if (ret) { + rpmb_err("%s: check mac error frame %d/%d\n", __func__, i, ret); + break; + } + } + + memcpy(data, &f->data, RPMB_BLOCK_SIZE); + data +=3D RPMB_BLOCK_SIZE; + } + ret =3D 0; + + out: + free(frames); + return ret; +} + +/* + * Generic RPMB bits + */ +static int op_get_info(int nargs, char *argv[]) +{ + int dev_fd; + struct rpmb_ioc_cap_cmd cap; + + if (nargs !=3D 1) + return -EINVAL; + + memset(&cap, 0, sizeof(cap)); + dev_fd =3D open_dev_file(argv[0], &cap); + if (dev_fd < 0) + return -errno; + argv++; + + printf("RPMB rpmb_target =3D %d\n", cap.target); + printf("RPMB capacity =3D %d\n", cap.capacity); + printf("RPMB block_size =3D %d\n", cap.block_size); + printf("RPMB wr_cnt_max =3D %d\n", cap.wr_cnt_max); + printf("RPMB rd_cnt_max =3D %d\n", cap.rd_cnt_max); + printf("RPMB auth_method =3D %d\n", cap.auth_method); + + close(dev_fd); + + return 0; +} + +static void *read_key(const char *path) +{ + int key_fd =3D open_rd_file(path, "key file"); + void *key; + + if (key_fd < 0) + return NULL; + + key =3D malloc(RPMB_KEY_SIZE); + + if (!key) { + rpmb_err("out of memory for key\n"); + return NULL; + } + + if (read(key_fd, key, RPMB_KEY_SIZE) !=3D RPMB_KEY_SIZE) { + rpmb_err("couldn't read key (%s)\n", strerror(errno)); + return NULL; + } + + close(key_fd); + return key; +} + +static int op_rpmb_program_key(int nargs, char *argv[]) +{ + int ret =3D -EINVAL, fd =3D -1; + struct rpmb_ioc_cap_cmd cap; + void *key; + + if (nargs < 1 || nargs > 2) + return ret; + + fd =3D open_dev_file(argv[0], &cap); + if (fd < 0) { + perror("opening RPMB device"); + return ret; + } + argv++; + + key =3D read_key(argv[0]); + + if (key) + ret =3D vrpmb_program_key(fd, key); + + close_fd(fd); + return ret; +} + + +static int op_rpmb_get_write_counter(int nargs, char **argv) +{ + int ret, fd =3D -1; + struct rpmb_ioc_cap_cmd cap; + void *key =3D NULL; + + ret =3D -EINVAL; + if (nargs < 1 || nargs > 2) + return ret; + + fd =3D open_dev_file(argv[0], &cap); + if (fd < 0) { + perror("opening RPMB device"); + return ret; + } + argv++; + + if (argv[0]) { + key =3D read_key(argv[0]); + if (!key) + rpmb_err("failed to read key data"); + } + + ret =3D vrpmb_get_write_counter(fd, key); + if (ret < 0) { + rpmb_err("counter ioctl failure %d: %s.\n", ret, strerror(errno)); + } else { + printf("Counter value is: %d\n", ret); + ret =3D 0; + } + + close_fd(fd); + return ret; +} + +static int op_rpmb_read_blocks(int nargs, char **argv) +{ + int ret, data_fd, fd =3D -1; + struct rpmb_ioc_cap_cmd cap; + unsigned long numarg; + uint16_t addr, blocks_cnt; + void *key =3D NULL; + + ret =3D -EINVAL; + if (nargs < 4 || nargs > 5) + return ret; + + fd =3D open_dev_file(argv[0], &cap); + if (fd < 0) { + perror("opening RPMB device"); + return ret; + } + argv++; + + errno =3D 0; + numarg =3D strtoul(argv[0], NULL, 0); + if (errno || numarg > USHRT_MAX) { + rpmb_err("wrong block address\n"); + goto out; + } + addr =3D (uint16_t)numarg; + argv++; + + errno =3D 0; + numarg =3D strtoul(argv[0], NULL, 0); + if (errno || numarg > USHRT_MAX) { + rpmb_err("wrong blocks count\n"); + goto out; + } + blocks_cnt =3D (uint16_t)numarg; + argv++; + + if (blocks_cnt =3D=3D 0) { + rpmb_err("wrong blocks count\n"); + goto out; + } + + data_fd =3D open_wr_file(argv[0], "output data"); + if (data_fd < 0) + goto out; + argv++; + + if (argv[0]) { + key =3D read_key(argv[0]); + if (!key) { + rpmb_err("failed to read key data"); + goto out; + } + } + + while (blocks_cnt > 0) { + int to_copy =3D min(blocks_cnt, cap.rd_cnt_max); + int length =3D to_copy * RPMB_BLOCK_SIZE; + void *data =3D malloc(length); + + if (!data) { + ret =3D ENOMEM; + goto out; + } + + vrpmb_read_blocks(fd, key, addr, to_copy, data, length); + + ret =3D write_file(data_fd, data, length); + if (ret < 0) { + perror("writing data"); + goto out; + } else { + rpmb_dbg("wrote %d bytes/%d blocks to file\n", length, to_copy); + } + + free(data); + addr +=3D to_copy; + blocks_cnt -=3D to_copy; + } + + ret =3D 0; +out: + close_fd(fd); + close_fd(data_fd); + + return ret; +} + +static int op_rpmb_write_blocks(int nargs, char **argv) +{ + int ret, data_fd, fd =3D -1; + struct rpmb_ioc_cap_cmd cap; + unsigned long numarg; + uint16_t addr, blocks_cnt; + void *key; + + ret =3D -EINVAL; + if (nargs < 4 || nargs > 5) + return ret; + + fd =3D open_dev_file(argv[0], &cap); + if (fd < 0) { + perror("opening RPMB device"); + return ret; + } + argv++; + + errno =3D 0; + numarg =3D strtoul(argv[0], NULL, 0); + if (errno || numarg > USHRT_MAX) { + rpmb_err("wrong block address\n"); + goto out; + } + addr =3D (uint16_t)numarg; + argv++; + + errno =3D 0; + numarg =3D strtoul(argv[0], NULL, 0); + if (errno || numarg > USHRT_MAX) { + rpmb_err("wrong blocks count\n"); + goto out; + } + blocks_cnt =3D (uint16_t)numarg; + argv++; + + if (blocks_cnt =3D=3D 0) { + rpmb_err("wrong blocks count\n"); + goto out; + } + + data_fd =3D open_rd_file(argv[0], "input data"); + if (data_fd < 0) + goto out; + argv++; + + key =3D read_key(nargs =3D=3D 5 ? argv[0] : NULL); + if (!key) { + rpmb_err("failed to read key data"); + goto out; + } + + while (blocks_cnt > 0) { + int to_copy =3D min(blocks_cnt, cap.wr_cnt_max); + int length =3D to_copy * 256; + void *data =3D malloc(length); + + if (!data) { + ret =3D ENOMEM; + goto out; + } + + ret =3D read_file(data_fd, data, length); + if (ret < 0) { + perror("reading data"); + goto out; + } + + ret =3D vrpmb_write_blocks(fd, key, data, addr, length); + if (ret < 0) { + rpmb_err("wblocks ioctl failure %d: %s.\n", ret, + strerror(errno)); + goto out; + } + + free(data); + addr +=3D to_copy; + blocks_cnt -=3D to_copy; + } + + ret =3D 0; +out: + close_fd(fd); + close_fd(data_fd); + + return ret; +} + +typedef int (*rpmb_op)(int argc, char *argv[]); + +struct rpmb_cmd { + const char *op_name; + rpmb_op op; + const char *usage; /* usage title */ + const char *help; /* help */ +}; + +static const struct rpmb_cmd cmds[] =3D { + { + "get-info", + op_get_info, + "", + " Get RPMB device info\n", + }, + { + "program-key", + op_rpmb_program_key, + " ", + " Program authentication KEYFILE\n" + " NOTE: This is a one-time programmable irreversible change.\n", + }, + { + "write-counter", + op_rpmb_get_write_counter, + "", + " Rertrive write counter value from the to stdout.\n" + }, + { + "write-blocks", + op_rpmb_write_blocks, + "
", + " of 256 bytes will be written from the DATA_FILE\n" + " to the at block offset
.\n" + " When DATA_FILE is -, read from standard input.\n", + }, + { + "read-blocks", + op_rpmb_read_blocks, + "
", + " of 256 bytes will be read from \n" + " to the OUTPUT_FILE\n" + " When OUTPUT_FILE is -, write to standard output\n", + }, + + { NULL, NULL, NULL, NULL } +}; + +static void help(const char *prog, const struct rpmb_cmd *cmd) +{ + printf("%s %s %s\n", prog, cmd->op_name, cmd->usage); + printf("%s\n", cmd->help); +} + +static void usage(const char *prog) +{ + int i; + + printf("\n"); + printf("Usage: %s [-v] \n\n", prog); + for (i =3D 0; cmds[i].op_name; i++) + printf(" %s %s %s\n", + prog, cmds[i].op_name, cmds[i].usage); + + printf("\n"); + printf(" %s -v/--verbose: runs in verbose mode\n", prog); + printf(" %s help : shows this help\n", prog); + printf(" %s help : shows detailed help\n", prog); +} + +static bool call_for_help(const char *arg) +{ + return !strcmp(arg, "help") || + !strcmp(arg, "-h") || + !strcmp(arg, "--help"); +} + +static bool parse_verbose(const char *arg) +{ + return !strcmp(arg, "-v") || + !strcmp(arg, "--verbose"); +} + +static const +struct rpmb_cmd *parse_args(const char *prog, int *_argc, char **_argv[]) +{ + int i; + int argc =3D *_argc; + char **argv =3D *_argv; + const struct rpmb_cmd *cmd =3D NULL; + bool need_help =3D false; + + argc--; argv++; + + if (argc =3D=3D 0) + goto out; + + if (call_for_help(argv[0])) { + argc--; argv++; + if (argc =3D=3D 0) + goto out; + + need_help =3D true; + } + + if (parse_verbose(argv[0])) { + argc--; argv++; + if (argc =3D=3D 0) + goto out; + + verbose =3D true; + } + + for (i =3D 0; cmds[i].op_name; i++) { + if (!strncmp(argv[0], cmds[i].op_name, + strlen(cmds[i].op_name))) { + cmd =3D &cmds[i]; + argc--; argv++; + break; + } + } + + if (!cmd) + goto out; + + if (need_help || (argc > 0 && call_for_help(argv[0]))) { + help(prog, cmd); + argc--; argv++; + return NULL; + } + +out: + *_argc =3D argc; + *_argv =3D argv; + + if (!cmd) + usage(prog); + + return cmd; +} + +int main(int argc, char *argv[]) +{ + const char *prog =3D basename(argv[0]); + const struct rpmb_cmd *cmd; + int ret; + + cmd =3D parse_args(prog, &argc, &argv); + if (!cmd) + exit(EXIT_SUCCESS); + + ret =3D cmd->op(argc, argv); + if (ret =3D=3D -EINVAL) + help(prog, cmd); + + exit(ret); +} diff --git a/tools/rpmb/test.sh b/tools/rpmb/test.sh new file mode 100755 index 000000000000..2cbba5529618 --- /dev/null +++ b/tools/rpmb/test.sh @@ -0,0 +1,22 @@ +#!/bin/sh -e +# SPDX-License-Identifier: GPL-2.0+ +echo "get info" +./rpmb -v get-info /dev/rpmb0 +echo "program key" +./rpmb -v program-key /dev/rpmb0 key +echo "get write counter" +./rpmb -v write-counter /dev/rpmb0 +echo "get write counter (and verify)" +./rpmb -v write-counter /dev/rpmb0 key +echo "generating data" +dd if=3D/dev/urandom of=3Ddata.in count=3D4 bs=3D256 +echo "write data" +./rpmb -v write-blocks /dev/rpmb0 0 4 data.in key +echo "read data back" +rm -f data.out +./rpmb -v read-blocks /dev/rpmb0 0 4 data.out +cmp data.in data.out +echo "read data back with key check" +truncate -s 0 data.out +./rpmb -v read-blocks /dev/rpmb0 0 4 data.out key +cmp data.in data.out --=20 2.30.2