| From 85df9333f0adf60fd76eb5ebb21b89c5b0a86c10 Mon Sep 17 00:00:00 2001 |
| From: Sudeep Holla <sudeep.holla@arm.com> |
| Date: Tue, 18 May 2021 17:36:18 +0100 |
| Subject: [PATCH 01/32] firmware: arm_ffa: Backport of arm_ffa driver |
| |
| This is a backport of upstream ARM FFA driver from: |
| https://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux.git/commit/?h=v5.10/ffa&id=c0aff30cb9ad6a00c82acef0f2a48f99adf997c0 |
| |
| to branch=android12-5.10-lts |
| |
| arm64: smccc: Add support for SMCCCv1.2 extended input/output registers |
| commit 3fdc0cb59d97f87e2cc708d424f1538e31744286 upstream. |
| |
| firmware: arm_ffa: Add initial FFA bus support for device enumeration |
| commit e781858488b918e30a6ff28e9eab6058b787e3b3 upstream. |
| |
| firmware: arm_ffa: Add initial Arm FFA driver support |
| commit 3bbfe9871005f38df2955b2e125933edf1d2feef upstream. |
| |
| firmware: arm_ffa: Add support for SMCCC as transport to FFA driver |
| commit 714be77e976a4b013b935b3223b2ef68856084d0 upstream. |
| |
| firmware: arm_ffa: Setup in-kernel users of FFA partitions |
| commit d0c0bce831223b08e5bade2cefc93c3ddb790796 upstream. |
| |
| firmware: arm_ffa: Add support for MEM_* interfaces |
| commit cc2195fe536c28e192df5d07e6dd277af36814b4 upstream. |
| |
| firmware: arm_ffa: Ensure drivers provide a probe function |
| commit 92743071464fca5acbbe812d9a0d88de3eaaad36 upstream. |
| |
| firmware: arm_ffa: Simplify probe function |
| commit e362547addc39e4bb18ad5bdfd59ce4d512d0c08 upstream. |
| |
| firmware: arm_ffa: Fix the comment style |
| commit ba684a31d3626c86cd9097e12d6ed57d224d077d upstream. |
| |
| firmware: arm_ffa: Fix a possible ffa_linux_errmap buffer overflow |
| commit dd925db6f07556061c11ab1fbfa4a0145ae6b438 upstream. |
| |
| firmware: arm_ffa: Add missing remove callback to ffa_bus_type |
| commit 244f5d597e1ea519c2085fbd9819458688775e42 upstream. |
| |
| firmware: arm_ffa: Fix __ffa_devices_unregister |
| commit eb7b52e6db7c21400b9b2d539f9343fb6e94bd94 upstream. |
| |
| firmware: arm_ffa: Handle compatibility with different firmware versions |
| commit 8e3f9da608f14cfebac2659d8dd8737b79d01308 upstream. |
| |
| firmware: arm_ffa: Add support for MEM_LEND |
| commit 82a8daaecfd9382e9450a05f86be8a274cf69a27 upstream. |
| |
| firmware: arm_ffa: Remove unused 'compat_version' variable |
| commit 01537a078b86917c7bb69aa4b756b42b980c158b upstream. |
| |
| Signed-off-by: Sudeep Holla <sudeep.holla@arm.com> |
| Change-Id: If9df40d2d10be9e3c95298820bc20c201ea1774c |
| Signed-off-by: Arunachalam Ganapathy <arunachalam.ganapathy@arm.com> |
| |
| Upstream-Status: Backport |
| Change-Id: I8e6197d8b7ef6654dacd21450069b8e284a3cec5 |
| --- |
| MAINTAINERS | 7 + |
| arch/arm64/kernel/asm-offsets.c | 9 + |
| arch/arm64/kernel/smccc-call.S | 57 +++ |
| drivers/firmware/Kconfig | 1 + |
| drivers/firmware/Makefile | 1 + |
| drivers/firmware/arm_ffa/Kconfig | 21 + |
| drivers/firmware/arm_ffa/Makefile | 6 + |
| drivers/firmware/arm_ffa/bus.c | 220 +++++++++ |
| drivers/firmware/arm_ffa/common.h | 31 ++ |
| drivers/firmware/arm_ffa/driver.c | 776 ++++++++++++++++++++++++++++++ |
| drivers/firmware/arm_ffa/smccc.c | 39 ++ |
| include/linux/arm-smccc.h | 55 +++ |
| include/linux/arm_ffa.h | 269 +++++++++++ |
| 13 files changed, 1492 insertions(+) |
| create mode 100644 drivers/firmware/arm_ffa/Kconfig |
| create mode 100644 drivers/firmware/arm_ffa/Makefile |
| create mode 100644 drivers/firmware/arm_ffa/bus.c |
| create mode 100644 drivers/firmware/arm_ffa/common.h |
| create mode 100644 drivers/firmware/arm_ffa/driver.c |
| create mode 100644 drivers/firmware/arm_ffa/smccc.c |
| create mode 100644 include/linux/arm_ffa.h |
| |
| diff --git a/MAINTAINERS b/MAINTAINERS |
| index 5234423c477a..d5fdc9e68c89 100644 |
| --- a/MAINTAINERS |
| +++ b/MAINTAINERS |
| @@ -6847,6 +6847,13 @@ F: include/linux/firewire.h |
| F: include/uapi/linux/firewire*.h |
| F: tools/firewire/ |
| |
| +FIRMWARE FRAMEWORK FOR ARMV8-A |
| +M: Sudeep Holla <sudeep.holla@arm.com> |
| +L: linux-arm-kernel@lists.infradead.org |
| +S: Maintained |
| +F: drivers/firmware/arm_ffa/ |
| +F: include/linux/arm_ffa.h |
| + |
| FIRMWARE LOADER (request_firmware) |
| M: Luis Chamberlain <mcgrof@kernel.org> |
| L: linux-kernel@vger.kernel.org |
| diff --git a/arch/arm64/kernel/asm-offsets.c b/arch/arm64/kernel/asm-offsets.c |
| index 93da876a58e6..bad4a367da28 100644 |
| --- a/arch/arm64/kernel/asm-offsets.c |
| +++ b/arch/arm64/kernel/asm-offsets.c |
| @@ -139,6 +139,15 @@ int main(void) |
| DEFINE(ARM_SMCCC_RES_X2_OFFS, offsetof(struct arm_smccc_res, a2)); |
| DEFINE(ARM_SMCCC_QUIRK_ID_OFFS, offsetof(struct arm_smccc_quirk, id)); |
| DEFINE(ARM_SMCCC_QUIRK_STATE_OFFS, offsetof(struct arm_smccc_quirk, state)); |
| + DEFINE(ARM_SMCCC_1_2_REGS_X0_OFFS, offsetof(struct arm_smccc_1_2_regs, a0)); |
| + DEFINE(ARM_SMCCC_1_2_REGS_X2_OFFS, offsetof(struct arm_smccc_1_2_regs, a2)); |
| + DEFINE(ARM_SMCCC_1_2_REGS_X4_OFFS, offsetof(struct arm_smccc_1_2_regs, a4)); |
| + DEFINE(ARM_SMCCC_1_2_REGS_X6_OFFS, offsetof(struct arm_smccc_1_2_regs, a6)); |
| + DEFINE(ARM_SMCCC_1_2_REGS_X8_OFFS, offsetof(struct arm_smccc_1_2_regs, a8)); |
| + DEFINE(ARM_SMCCC_1_2_REGS_X10_OFFS, offsetof(struct arm_smccc_1_2_regs, a10)); |
| + DEFINE(ARM_SMCCC_1_2_REGS_X12_OFFS, offsetof(struct arm_smccc_1_2_regs, a12)); |
| + DEFINE(ARM_SMCCC_1_2_REGS_X14_OFFS, offsetof(struct arm_smccc_1_2_regs, a14)); |
| + DEFINE(ARM_SMCCC_1_2_REGS_X16_OFFS, offsetof(struct arm_smccc_1_2_regs, a16)); |
| BLANK(); |
| DEFINE(HIBERN_PBE_ORIG, offsetof(struct pbe, orig_address)); |
| DEFINE(HIBERN_PBE_ADDR, offsetof(struct pbe, address)); |
| diff --git a/arch/arm64/kernel/smccc-call.S b/arch/arm64/kernel/smccc-call.S |
| index d62447964ed9..2def9d0dd3dd 100644 |
| --- a/arch/arm64/kernel/smccc-call.S |
| +++ b/arch/arm64/kernel/smccc-call.S |
| @@ -43,3 +43,60 @@ SYM_FUNC_START(__arm_smccc_hvc) |
| SMCCC hvc |
| SYM_FUNC_END(__arm_smccc_hvc) |
| EXPORT_SYMBOL(__arm_smccc_hvc) |
| + |
| + .macro SMCCC_1_2 instr |
| + /* Save `res` and free a GPR that won't be clobbered */ |
| + stp x1, x19, [sp, #-16]! |
| + |
| + /* Ensure `args` won't be clobbered while loading regs in next step */ |
| + mov x19, x0 |
| + |
| + /* Load the registers x0 - x17 from the struct arm_smccc_1_2_regs */ |
| + ldp x0, x1, [x19, #ARM_SMCCC_1_2_REGS_X0_OFFS] |
| + ldp x2, x3, [x19, #ARM_SMCCC_1_2_REGS_X2_OFFS] |
| + ldp x4, x5, [x19, #ARM_SMCCC_1_2_REGS_X4_OFFS] |
| + ldp x6, x7, [x19, #ARM_SMCCC_1_2_REGS_X6_OFFS] |
| + ldp x8, x9, [x19, #ARM_SMCCC_1_2_REGS_X8_OFFS] |
| + ldp x10, x11, [x19, #ARM_SMCCC_1_2_REGS_X10_OFFS] |
| + ldp x12, x13, [x19, #ARM_SMCCC_1_2_REGS_X12_OFFS] |
| + ldp x14, x15, [x19, #ARM_SMCCC_1_2_REGS_X14_OFFS] |
| + ldp x16, x17, [x19, #ARM_SMCCC_1_2_REGS_X16_OFFS] |
| + |
| + \instr #0 |
| + |
| + /* Load the `res` from the stack */ |
| + ldr x19, [sp] |
| + |
| + /* Store the registers x0 - x17 into the result structure */ |
| + stp x0, x1, [x19, #ARM_SMCCC_1_2_REGS_X0_OFFS] |
| + stp x2, x3, [x19, #ARM_SMCCC_1_2_REGS_X2_OFFS] |
| + stp x4, x5, [x19, #ARM_SMCCC_1_2_REGS_X4_OFFS] |
| + stp x6, x7, [x19, #ARM_SMCCC_1_2_REGS_X6_OFFS] |
| + stp x8, x9, [x19, #ARM_SMCCC_1_2_REGS_X8_OFFS] |
| + stp x10, x11, [x19, #ARM_SMCCC_1_2_REGS_X10_OFFS] |
| + stp x12, x13, [x19, #ARM_SMCCC_1_2_REGS_X12_OFFS] |
| + stp x14, x15, [x19, #ARM_SMCCC_1_2_REGS_X14_OFFS] |
| + stp x16, x17, [x19, #ARM_SMCCC_1_2_REGS_X16_OFFS] |
| + |
| + /* Restore original x19 */ |
| + ldp xzr, x19, [sp], #16 |
| + ret |
| +.endm |
| + |
| +/* |
| + * void arm_smccc_1_2_hvc(const struct arm_smccc_1_2_regs *args, |
| + * struct arm_smccc_1_2_regs *res); |
| + */ |
| +SYM_FUNC_START(arm_smccc_1_2_hvc) |
| + SMCCC_1_2 hvc |
| +SYM_FUNC_END(arm_smccc_1_2_hvc) |
| +EXPORT_SYMBOL(arm_smccc_1_2_hvc) |
| + |
| +/* |
| + * void arm_smccc_1_2_smc(const struct arm_smccc_1_2_regs *args, |
| + * struct arm_smccc_1_2_regs *res); |
| + */ |
| +SYM_FUNC_START(arm_smccc_1_2_smc) |
| + SMCCC_1_2 smc |
| +SYM_FUNC_END(arm_smccc_1_2_smc) |
| +EXPORT_SYMBOL(arm_smccc_1_2_smc) |
| diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig |
| index bfef3d8d14e7..90e6dd32f2cd 100644 |
| --- a/drivers/firmware/Kconfig |
| +++ b/drivers/firmware/Kconfig |
| @@ -296,6 +296,7 @@ config TURRIS_MOX_RWTM |
| other manufacturing data and also utilize the Entropy Bit Generator |
| for hardware random number generation. |
| |
| +source "drivers/firmware/arm_ffa/Kconfig" |
| source "drivers/firmware/broadcom/Kconfig" |
| source "drivers/firmware/google/Kconfig" |
| source "drivers/firmware/efi/Kconfig" |
| diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile |
| index 523173cbff33..3c2af2e98def 100644 |
| --- a/drivers/firmware/Makefile |
| +++ b/drivers/firmware/Makefile |
| @@ -23,6 +23,7 @@ obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o |
| obj-$(CONFIG_TRUSTED_FOUNDATIONS) += trusted_foundations.o |
| obj-$(CONFIG_TURRIS_MOX_RWTM) += turris-mox-rwtm.o |
| |
| +obj-y += arm_ffa/ |
| obj-y += arm_scmi/ |
| obj-y += broadcom/ |
| obj-y += meson/ |
| diff --git a/drivers/firmware/arm_ffa/Kconfig b/drivers/firmware/arm_ffa/Kconfig |
| new file mode 100644 |
| index 000000000000..5e3ae5cf82e8 |
| --- /dev/null |
| +++ b/drivers/firmware/arm_ffa/Kconfig |
| @@ -0,0 +1,21 @@ |
| +# SPDX-License-Identifier: GPL-2.0-only |
| +config ARM_FFA_TRANSPORT |
| + tristate "Arm Firmware Framework for Armv8-A" |
| + depends on OF |
| + depends on ARM64 |
| + default n |
| + help |
| + This Firmware Framework(FF) for Arm A-profile processors describes |
| + interfaces that standardize communication between the various |
| + software images which includes communication between images in |
| + the Secure world and Normal world. It also leverages the |
| + virtualization extension to isolate software images provided |
| + by an ecosystem of vendors from each other. |
| + |
| + This driver provides interface for all the client drivers making |
| + use of the features offered by ARM FF-A. |
| + |
| +config ARM_FFA_SMCCC |
| + bool |
| + default ARM_FFA_TRANSPORT |
| + depends on ARM64 && HAVE_ARM_SMCCC_DISCOVERY |
| diff --git a/drivers/firmware/arm_ffa/Makefile b/drivers/firmware/arm_ffa/Makefile |
| new file mode 100644 |
| index 000000000000..9d9f37523200 |
| --- /dev/null |
| +++ b/drivers/firmware/arm_ffa/Makefile |
| @@ -0,0 +1,6 @@ |
| +# SPDX-License-Identifier: GPL-2.0-only |
| +ffa-bus-y = bus.o |
| +ffa-driver-y = driver.o |
| +ffa-transport-$(CONFIG_ARM_FFA_SMCCC) += smccc.o |
| +ffa-module-objs := $(ffa-bus-y) $(ffa-driver-y) $(ffa-transport-y) |
| +obj-$(CONFIG_ARM_FFA_TRANSPORT) = ffa-module.o |
| diff --git a/drivers/firmware/arm_ffa/bus.c b/drivers/firmware/arm_ffa/bus.c |
| new file mode 100644 |
| index 000000000000..fca1e311ea6c |
| --- /dev/null |
| +++ b/drivers/firmware/arm_ffa/bus.c |
| @@ -0,0 +1,220 @@ |
| +// SPDX-License-Identifier: GPL-2.0 |
| +/* |
| + * Copyright (C) 2021 ARM Ltd. |
| + */ |
| + |
| +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| + |
| +#include <linux/arm_ffa.h> |
| +#include <linux/device.h> |
| +#include <linux/fs.h> |
| +#include <linux/kernel.h> |
| +#include <linux/module.h> |
| +#include <linux/slab.h> |
| +#include <linux/types.h> |
| + |
| +#include "common.h" |
| + |
| +static int ffa_device_match(struct device *dev, struct device_driver *drv) |
| +{ |
| + const struct ffa_device_id *id_table; |
| + struct ffa_device *ffa_dev; |
| + |
| + id_table = to_ffa_driver(drv)->id_table; |
| + ffa_dev = to_ffa_dev(dev); |
| + |
| + while (!uuid_is_null(&id_table->uuid)) { |
| + /* |
| + * FF-A v1.0 doesn't provide discovery of UUIDs, just the |
| + * partition IDs, so fetch the partitions IDs for this |
| + * id_table UUID and assign the UUID to the device if the |
| + * partition ID matches |
| + */ |
| + if (uuid_is_null(&ffa_dev->uuid)) |
| + ffa_device_match_uuid(ffa_dev, &id_table->uuid); |
| + |
| + if (uuid_equal(&ffa_dev->uuid, &id_table->uuid)) |
| + return 1; |
| + id_table++; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static int ffa_device_probe(struct device *dev) |
| +{ |
| + struct ffa_driver *ffa_drv = to_ffa_driver(dev->driver); |
| + struct ffa_device *ffa_dev = to_ffa_dev(dev); |
| + |
| + return ffa_drv->probe(ffa_dev); |
| +} |
| + |
| +static int ffa_device_remove(struct device *dev) |
| +{ |
| + struct ffa_driver *ffa_drv = to_ffa_driver(dev->driver); |
| + |
| + ffa_drv->remove(to_ffa_dev(dev)); |
| + |
| + return 0; |
| +} |
| + |
| +static int ffa_device_uevent(struct device *dev, struct kobj_uevent_env *env) |
| +{ |
| + struct ffa_device *ffa_dev = to_ffa_dev(dev); |
| + |
| + return add_uevent_var(env, "MODALIAS=arm_ffa:%04x:%pUb", |
| + ffa_dev->vm_id, &ffa_dev->uuid); |
| +} |
| + |
| +static ssize_t partition_id_show(struct device *dev, |
| + struct device_attribute *attr, char *buf) |
| +{ |
| + struct ffa_device *ffa_dev = to_ffa_dev(dev); |
| + |
| + return sprintf(buf, "0x%04x\n", ffa_dev->vm_id); |
| +} |
| +static DEVICE_ATTR_RO(partition_id); |
| + |
| +static ssize_t uuid_show(struct device *dev, struct device_attribute *attr, |
| + char *buf) |
| +{ |
| + struct ffa_device *ffa_dev = to_ffa_dev(dev); |
| + |
| + return sprintf(buf, "%pUb\n", &ffa_dev->uuid); |
| +} |
| +static DEVICE_ATTR_RO(uuid); |
| + |
| +static struct attribute *ffa_device_attributes_attrs[] = { |
| + &dev_attr_partition_id.attr, |
| + &dev_attr_uuid.attr, |
| + NULL, |
| +}; |
| +ATTRIBUTE_GROUPS(ffa_device_attributes); |
| + |
| +struct bus_type ffa_bus_type = { |
| + .name = "arm_ffa", |
| + .match = ffa_device_match, |
| + .probe = ffa_device_probe, |
| + .remove = ffa_device_remove, |
| + .uevent = ffa_device_uevent, |
| + .dev_groups = ffa_device_attributes_groups, |
| +}; |
| +EXPORT_SYMBOL_GPL(ffa_bus_type); |
| + |
| +int ffa_driver_register(struct ffa_driver *driver, struct module *owner, |
| + const char *mod_name) |
| +{ |
| + int ret; |
| + |
| + if (!driver->probe) |
| + return -EINVAL; |
| + |
| + driver->driver.bus = &ffa_bus_type; |
| + driver->driver.name = driver->name; |
| + driver->driver.owner = owner; |
| + driver->driver.mod_name = mod_name; |
| + |
| + ret = driver_register(&driver->driver); |
| + if (!ret) |
| + pr_debug("registered new ffa driver %s\n", driver->name); |
| + |
| + return ret; |
| +} |
| +EXPORT_SYMBOL_GPL(ffa_driver_register); |
| + |
| +void ffa_driver_unregister(struct ffa_driver *driver) |
| +{ |
| + driver_unregister(&driver->driver); |
| +} |
| +EXPORT_SYMBOL_GPL(ffa_driver_unregister); |
| + |
| +static void ffa_release_device(struct device *dev) |
| +{ |
| + struct ffa_device *ffa_dev = to_ffa_dev(dev); |
| + |
| + kfree(ffa_dev); |
| +} |
| + |
| +static int __ffa_devices_unregister(struct device *dev, void *data) |
| +{ |
| + device_unregister(dev); |
| + |
| + return 0; |
| +} |
| + |
| +static void ffa_devices_unregister(void) |
| +{ |
| + bus_for_each_dev(&ffa_bus_type, NULL, NULL, |
| + __ffa_devices_unregister); |
| +} |
| + |
| +bool ffa_device_is_valid(struct ffa_device *ffa_dev) |
| +{ |
| + bool valid = false; |
| + struct device *dev = NULL; |
| + struct ffa_device *tmp_dev; |
| + |
| + do { |
| + dev = bus_find_next_device(&ffa_bus_type, dev); |
| + tmp_dev = to_ffa_dev(dev); |
| + if (tmp_dev == ffa_dev) { |
| + valid = true; |
| + break; |
| + } |
| + put_device(dev); |
| + } while (dev); |
| + |
| + put_device(dev); |
| + |
| + return valid; |
| +} |
| + |
| +struct ffa_device *ffa_device_register(const uuid_t *uuid, int vm_id) |
| +{ |
| + int ret; |
| + struct device *dev; |
| + struct ffa_device *ffa_dev; |
| + |
| + ffa_dev = kzalloc(sizeof(*ffa_dev), GFP_KERNEL); |
| + if (!ffa_dev) |
| + return NULL; |
| + |
| + dev = &ffa_dev->dev; |
| + dev->bus = &ffa_bus_type; |
| + dev->release = ffa_release_device; |
| + dev_set_name(&ffa_dev->dev, "arm-ffa-%04x", vm_id); |
| + |
| + ffa_dev->vm_id = vm_id; |
| + uuid_copy(&ffa_dev->uuid, uuid); |
| + |
| + ret = device_register(&ffa_dev->dev); |
| + if (ret) { |
| + dev_err(dev, "unable to register device %s err=%d\n", |
| + dev_name(dev), ret); |
| + put_device(dev); |
| + return NULL; |
| + } |
| + |
| + return ffa_dev; |
| +} |
| +EXPORT_SYMBOL_GPL(ffa_device_register); |
| + |
| +void ffa_device_unregister(struct ffa_device *ffa_dev) |
| +{ |
| + if (!ffa_dev) |
| + return; |
| + |
| + device_unregister(&ffa_dev->dev); |
| +} |
| +EXPORT_SYMBOL_GPL(ffa_device_unregister); |
| + |
| +int arm_ffa_bus_init(void) |
| +{ |
| + return bus_register(&ffa_bus_type); |
| +} |
| + |
| +void arm_ffa_bus_exit(void) |
| +{ |
| + ffa_devices_unregister(); |
| + bus_unregister(&ffa_bus_type); |
| +} |
| diff --git a/drivers/firmware/arm_ffa/common.h b/drivers/firmware/arm_ffa/common.h |
| new file mode 100644 |
| index 000000000000..d6eccf1fd3f6 |
| --- /dev/null |
| +++ b/drivers/firmware/arm_ffa/common.h |
| @@ -0,0 +1,31 @@ |
| +/* SPDX-License-Identifier: GPL-2.0 */ |
| +/* |
| + * Copyright (C) 2021 ARM Ltd. |
| + */ |
| + |
| +#ifndef _FFA_COMMON_H |
| +#define _FFA_COMMON_H |
| + |
| +#include <linux/arm_ffa.h> |
| +#include <linux/arm-smccc.h> |
| +#include <linux/err.h> |
| + |
| +typedef struct arm_smccc_1_2_regs ffa_value_t; |
| + |
| +typedef void (ffa_fn)(ffa_value_t, ffa_value_t *); |
| + |
| +int arm_ffa_bus_init(void); |
| +void arm_ffa_bus_exit(void); |
| +bool ffa_device_is_valid(struct ffa_device *ffa_dev); |
| +void ffa_device_match_uuid(struct ffa_device *ffa_dev, const uuid_t *uuid); |
| + |
| +#ifdef CONFIG_ARM_FFA_SMCCC |
| +int __init ffa_transport_init(ffa_fn **invoke_ffa_fn); |
| +#else |
| +static inline int __init ffa_transport_init(ffa_fn **invoke_ffa_fn) |
| +{ |
| + return -EOPNOTSUPP; |
| +} |
| +#endif |
| + |
| +#endif /* _FFA_COMMON_H */ |
| diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c |
| new file mode 100644 |
| index 000000000000..14f900047ac0 |
| --- /dev/null |
| +++ b/drivers/firmware/arm_ffa/driver.c |
| @@ -0,0 +1,776 @@ |
| +// SPDX-License-Identifier: GPL-2.0-only |
| +/* |
| + * Arm Firmware Framework for ARMv8-A(FFA) interface driver |
| + * |
| + * The Arm FFA specification[1] describes a software architecture to |
| + * leverages the virtualization extension to isolate software images |
| + * provided by an ecosystem of vendors from each other and describes |
| + * interfaces that standardize communication between the various software |
| + * images including communication between images in the Secure world and |
| + * Normal world. Any Hypervisor could use the FFA interfaces to enable |
| + * communication between VMs it manages. |
| + * |
| + * The Hypervisor a.k.a Partition managers in FFA terminology can assign |
| + * system resources(Memory regions, Devices, CPU cycles) to the partitions |
| + * and manage isolation amongst them. |
| + * |
| + * [1] https://developer.arm.com/docs/den0077/latest |
| + * |
| + * Copyright (C) 2021 ARM Ltd. |
| + */ |
| + |
| +#define DRIVER_NAME "ARM FF-A" |
| +#define pr_fmt(fmt) DRIVER_NAME ": " fmt |
| + |
| +#include <linux/arm_ffa.h> |
| +#include <linux/bitfield.h> |
| +#include <linux/device.h> |
| +#include <linux/io.h> |
| +#include <linux/kernel.h> |
| +#include <linux/module.h> |
| +#include <linux/mm.h> |
| +#include <linux/scatterlist.h> |
| +#include <linux/slab.h> |
| +#include <linux/uuid.h> |
| + |
| +#include "common.h" |
| + |
| +#define FFA_DRIVER_VERSION FFA_VERSION_1_0 |
| + |
| +#define FFA_SMC(calling_convention, func_num) \ |
| + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, (calling_convention), \ |
| + ARM_SMCCC_OWNER_STANDARD, (func_num)) |
| + |
| +#define FFA_SMC_32(func_num) FFA_SMC(ARM_SMCCC_SMC_32, (func_num)) |
| +#define FFA_SMC_64(func_num) FFA_SMC(ARM_SMCCC_SMC_64, (func_num)) |
| + |
| +#define FFA_ERROR FFA_SMC_32(0x60) |
| +#define FFA_SUCCESS FFA_SMC_32(0x61) |
| +#define FFA_INTERRUPT FFA_SMC_32(0x62) |
| +#define FFA_VERSION FFA_SMC_32(0x63) |
| +#define FFA_FEATURES FFA_SMC_32(0x64) |
| +#define FFA_RX_RELEASE FFA_SMC_32(0x65) |
| +#define FFA_RXTX_MAP FFA_SMC_32(0x66) |
| +#define FFA_FN64_RXTX_MAP FFA_SMC_64(0x66) |
| +#define FFA_RXTX_UNMAP FFA_SMC_32(0x67) |
| +#define FFA_PARTITION_INFO_GET FFA_SMC_32(0x68) |
| +#define FFA_ID_GET FFA_SMC_32(0x69) |
| +#define FFA_MSG_POLL FFA_SMC_32(0x6A) |
| +#define FFA_MSG_WAIT FFA_SMC_32(0x6B) |
| +#define FFA_YIELD FFA_SMC_32(0x6C) |
| +#define FFA_RUN FFA_SMC_32(0x6D) |
| +#define FFA_MSG_SEND FFA_SMC_32(0x6E) |
| +#define FFA_MSG_SEND_DIRECT_REQ FFA_SMC_32(0x6F) |
| +#define FFA_FN64_MSG_SEND_DIRECT_REQ FFA_SMC_64(0x6F) |
| +#define FFA_MSG_SEND_DIRECT_RESP FFA_SMC_32(0x70) |
| +#define FFA_FN64_MSG_SEND_DIRECT_RESP FFA_SMC_64(0x70) |
| +#define FFA_MEM_DONATE FFA_SMC_32(0x71) |
| +#define FFA_FN64_MEM_DONATE FFA_SMC_64(0x71) |
| +#define FFA_MEM_LEND FFA_SMC_32(0x72) |
| +#define FFA_FN64_MEM_LEND FFA_SMC_64(0x72) |
| +#define FFA_MEM_SHARE FFA_SMC_32(0x73) |
| +#define FFA_FN64_MEM_SHARE FFA_SMC_64(0x73) |
| +#define FFA_MEM_RETRIEVE_REQ FFA_SMC_32(0x74) |
| +#define FFA_FN64_MEM_RETRIEVE_REQ FFA_SMC_64(0x74) |
| +#define FFA_MEM_RETRIEVE_RESP FFA_SMC_32(0x75) |
| +#define FFA_MEM_RELINQUISH FFA_SMC_32(0x76) |
| +#define FFA_MEM_RECLAIM FFA_SMC_32(0x77) |
| +#define FFA_MEM_OP_PAUSE FFA_SMC_32(0x78) |
| +#define FFA_MEM_OP_RESUME FFA_SMC_32(0x79) |
| +#define FFA_MEM_FRAG_RX FFA_SMC_32(0x7A) |
| +#define FFA_MEM_FRAG_TX FFA_SMC_32(0x7B) |
| +#define FFA_NORMAL_WORLD_RESUME FFA_SMC_32(0x7C) |
| + |
| +/* |
| + * For some calls it is necessary to use SMC64 to pass or return 64-bit values. |
| + * For such calls FFA_FN_NATIVE(name) will choose the appropriate |
| + * (native-width) function ID. |
| + */ |
| +#ifdef CONFIG_64BIT |
| +#define FFA_FN_NATIVE(name) FFA_FN64_##name |
| +#else |
| +#define FFA_FN_NATIVE(name) FFA_##name |
| +#endif |
| + |
| +/* FFA error codes. */ |
| +#define FFA_RET_SUCCESS (0) |
| +#define FFA_RET_NOT_SUPPORTED (-1) |
| +#define FFA_RET_INVALID_PARAMETERS (-2) |
| +#define FFA_RET_NO_MEMORY (-3) |
| +#define FFA_RET_BUSY (-4) |
| +#define FFA_RET_INTERRUPTED (-5) |
| +#define FFA_RET_DENIED (-6) |
| +#define FFA_RET_RETRY (-7) |
| +#define FFA_RET_ABORTED (-8) |
| + |
| +#define MAJOR_VERSION_MASK GENMASK(30, 16) |
| +#define MINOR_VERSION_MASK GENMASK(15, 0) |
| +#define MAJOR_VERSION(x) ((u16)(FIELD_GET(MAJOR_VERSION_MASK, (x)))) |
| +#define MINOR_VERSION(x) ((u16)(FIELD_GET(MINOR_VERSION_MASK, (x)))) |
| +#define PACK_VERSION_INFO(major, minor) \ |
| + (FIELD_PREP(MAJOR_VERSION_MASK, (major)) | \ |
| + FIELD_PREP(MINOR_VERSION_MASK, (minor))) |
| +#define FFA_VERSION_1_0 PACK_VERSION_INFO(1, 0) |
| +#define FFA_MIN_VERSION FFA_VERSION_1_0 |
| + |
| +#define SENDER_ID_MASK GENMASK(31, 16) |
| +#define RECEIVER_ID_MASK GENMASK(15, 0) |
| +#define SENDER_ID(x) ((u16)(FIELD_GET(SENDER_ID_MASK, (x)))) |
| +#define RECEIVER_ID(x) ((u16)(FIELD_GET(RECEIVER_ID_MASK, (x)))) |
| +#define PACK_TARGET_INFO(s, r) \ |
| + (FIELD_PREP(SENDER_ID_MASK, (s)) | FIELD_PREP(RECEIVER_ID_MASK, (r))) |
| + |
| +/* |
| + * FF-A specification mentions explicitly about '4K pages'. This should |
| + * not be confused with the kernel PAGE_SIZE, which is the translation |
| + * granule kernel is configured and may be one among 4K, 16K and 64K. |
| + */ |
| +#define FFA_PAGE_SIZE SZ_4K |
| +/* |
| + * Keeping RX TX buffer size as 4K for now |
| + * 64K may be preferred to keep it min a page in 64K PAGE_SIZE config |
| + */ |
| +#define RXTX_BUFFER_SIZE SZ_4K |
| + |
| +static ffa_fn *invoke_ffa_fn; |
| + |
| +static const int ffa_linux_errmap[] = { |
| + /* better than switch case as long as return value is continuous */ |
| + 0, /* FFA_RET_SUCCESS */ |
| + -EOPNOTSUPP, /* FFA_RET_NOT_SUPPORTED */ |
| + -EINVAL, /* FFA_RET_INVALID_PARAMETERS */ |
| + -ENOMEM, /* FFA_RET_NO_MEMORY */ |
| + -EBUSY, /* FFA_RET_BUSY */ |
| + -EINTR, /* FFA_RET_INTERRUPTED */ |
| + -EACCES, /* FFA_RET_DENIED */ |
| + -EAGAIN, /* FFA_RET_RETRY */ |
| + -ECANCELED, /* FFA_RET_ABORTED */ |
| +}; |
| + |
| +static inline int ffa_to_linux_errno(int errno) |
| +{ |
| + int err_idx = -errno; |
| + |
| + if (err_idx >= 0 && err_idx < ARRAY_SIZE(ffa_linux_errmap)) |
| + return ffa_linux_errmap[err_idx]; |
| + return -EINVAL; |
| +} |
| + |
| +struct ffa_drv_info { |
| + u32 version; |
| + u16 vm_id; |
| + struct mutex rx_lock; /* lock to protect Rx buffer */ |
| + struct mutex tx_lock; /* lock to protect Tx buffer */ |
| + void *rx_buffer; |
| + void *tx_buffer; |
| +}; |
| + |
| +static struct ffa_drv_info *drv_info; |
| + |
| +/* |
| + * The driver must be able to support all the versions from the earliest |
| + * supported FFA_MIN_VERSION to the latest supported FFA_DRIVER_VERSION. |
| + * The specification states that if firmware supports a FFA implementation |
| + * that is incompatible with and at a greater version number than specified |
| + * by the caller(FFA_DRIVER_VERSION passed as parameter to FFA_VERSION), |
| + * it must return the NOT_SUPPORTED error code. |
| + */ |
| +static u32 ffa_compatible_version_find(u32 version) |
| +{ |
| + u16 major = MAJOR_VERSION(version), minor = MINOR_VERSION(version); |
| + u16 drv_major = MAJOR_VERSION(FFA_DRIVER_VERSION); |
| + u16 drv_minor = MINOR_VERSION(FFA_DRIVER_VERSION); |
| + |
| + if ((major < drv_major) || (major == drv_major && minor <= drv_minor)) |
| + return version; |
| + |
| + pr_info("Firmware version higher than driver version, downgrading\n"); |
| + return FFA_DRIVER_VERSION; |
| +} |
| + |
| +static int ffa_version_check(u32 *version) |
| +{ |
| + ffa_value_t ver; |
| + |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = FFA_VERSION, .a1 = FFA_DRIVER_VERSION, |
| + }, &ver); |
| + |
| + if (ver.a0 == FFA_RET_NOT_SUPPORTED) { |
| + pr_info("FFA_VERSION returned not supported\n"); |
| + return -EOPNOTSUPP; |
| + } |
| + |
| + if (ver.a0 < FFA_MIN_VERSION) { |
| + pr_err("Incompatible v%d.%d! Earliest supported v%d.%d\n", |
| + MAJOR_VERSION(ver.a0), MINOR_VERSION(ver.a0), |
| + MAJOR_VERSION(FFA_MIN_VERSION), |
| + MINOR_VERSION(FFA_MIN_VERSION)); |
| + return -EINVAL; |
| + } |
| + |
| + pr_info("Driver version %d.%d\n", MAJOR_VERSION(FFA_DRIVER_VERSION), |
| + MINOR_VERSION(FFA_DRIVER_VERSION)); |
| + pr_info("Firmware version %d.%d found\n", MAJOR_VERSION(ver.a0), |
| + MINOR_VERSION(ver.a0)); |
| + *version = ffa_compatible_version_find(ver.a0); |
| + |
| + return 0; |
| +} |
| + |
| +static int ffa_rx_release(void) |
| +{ |
| + ffa_value_t ret; |
| + |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = FFA_RX_RELEASE, |
| + }, &ret); |
| + |
| + if (ret.a0 == FFA_ERROR) |
| + return ffa_to_linux_errno((int)ret.a2); |
| + |
| + /* check for ret.a0 == FFA_RX_RELEASE ? */ |
| + |
| + return 0; |
| +} |
| + |
| +static int ffa_rxtx_map(phys_addr_t tx_buf, phys_addr_t rx_buf, u32 pg_cnt) |
| +{ |
| + ffa_value_t ret; |
| + |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = FFA_FN_NATIVE(RXTX_MAP), |
| + .a1 = tx_buf, .a2 = rx_buf, .a3 = pg_cnt, |
| + }, &ret); |
| + |
| + if (ret.a0 == FFA_ERROR) |
| + return ffa_to_linux_errno((int)ret.a2); |
| + |
| + return 0; |
| +} |
| + |
| +static int ffa_rxtx_unmap(u16 vm_id) |
| +{ |
| + ffa_value_t ret; |
| + |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = FFA_RXTX_UNMAP, .a1 = PACK_TARGET_INFO(vm_id, 0), |
| + }, &ret); |
| + |
| + if (ret.a0 == FFA_ERROR) |
| + return ffa_to_linux_errno((int)ret.a2); |
| + |
| + return 0; |
| +} |
| + |
| +/* buffer must be sizeof(struct ffa_partition_info) * num_partitions */ |
| +static int |
| +__ffa_partition_info_get(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3, |
| + struct ffa_partition_info *buffer, int num_partitions) |
| +{ |
| + int count; |
| + ffa_value_t partition_info; |
| + |
| + mutex_lock(&drv_info->rx_lock); |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = FFA_PARTITION_INFO_GET, |
| + .a1 = uuid0, .a2 = uuid1, .a3 = uuid2, .a4 = uuid3, |
| + }, &partition_info); |
| + |
| + if (partition_info.a0 == FFA_ERROR) { |
| + mutex_unlock(&drv_info->rx_lock); |
| + return ffa_to_linux_errno((int)partition_info.a2); |
| + } |
| + |
| + count = partition_info.a2; |
| + |
| + if (buffer && count <= num_partitions) |
| + memcpy(buffer, drv_info->rx_buffer, sizeof(*buffer) * count); |
| + |
| + ffa_rx_release(); |
| + |
| + mutex_unlock(&drv_info->rx_lock); |
| + |
| + return count; |
| +} |
| + |
| +/* buffer is allocated and caller must free the same if returned count > 0 */ |
| +static int |
| +ffa_partition_probe(const uuid_t *uuid, struct ffa_partition_info **buffer) |
| +{ |
| + int count; |
| + u32 uuid0_4[4]; |
| + struct ffa_partition_info *pbuf; |
| + |
| + export_uuid((u8 *)uuid0_4, uuid); |
| + count = __ffa_partition_info_get(uuid0_4[0], uuid0_4[1], uuid0_4[2], |
| + uuid0_4[3], NULL, 0); |
| + if (count <= 0) |
| + return count; |
| + |
| + pbuf = kcalloc(count, sizeof(*pbuf), GFP_KERNEL); |
| + if (!pbuf) |
| + return -ENOMEM; |
| + |
| + count = __ffa_partition_info_get(uuid0_4[0], uuid0_4[1], uuid0_4[2], |
| + uuid0_4[3], pbuf, count); |
| + if (count <= 0) |
| + kfree(pbuf); |
| + else |
| + *buffer = pbuf; |
| + |
| + return count; |
| +} |
| + |
| +#define VM_ID_MASK GENMASK(15, 0) |
| +static int ffa_id_get(u16 *vm_id) |
| +{ |
| + ffa_value_t id; |
| + |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = FFA_ID_GET, |
| + }, &id); |
| + |
| + if (id.a0 == FFA_ERROR) |
| + return ffa_to_linux_errno((int)id.a2); |
| + |
| + *vm_id = FIELD_GET(VM_ID_MASK, (id.a2)); |
| + |
| + return 0; |
| +} |
| + |
| +static int ffa_msg_send_direct_req(u16 src_id, u16 dst_id, bool mode_32bit, |
| + struct ffa_send_direct_data *data) |
| +{ |
| + u32 req_id, resp_id, src_dst_ids = PACK_TARGET_INFO(src_id, dst_id); |
| + ffa_value_t ret; |
| + |
| + if (mode_32bit) { |
| + req_id = FFA_MSG_SEND_DIRECT_REQ; |
| + resp_id = FFA_MSG_SEND_DIRECT_RESP; |
| + } else { |
| + req_id = FFA_FN_NATIVE(MSG_SEND_DIRECT_REQ); |
| + resp_id = FFA_FN_NATIVE(MSG_SEND_DIRECT_RESP); |
| + } |
| + |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = req_id, .a1 = src_dst_ids, .a2 = 0, |
| + .a3 = data->data0, .a4 = data->data1, .a5 = data->data2, |
| + .a6 = data->data3, .a7 = data->data4, |
| + }, &ret); |
| + |
| + while (ret.a0 == FFA_INTERRUPT) |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = FFA_RUN, .a1 = ret.a1, |
| + }, &ret); |
| + |
| + if (ret.a0 == FFA_ERROR) |
| + return ffa_to_linux_errno((int)ret.a2); |
| + |
| + if (ret.a0 == resp_id) { |
| + data->data0 = ret.a3; |
| + data->data1 = ret.a4; |
| + data->data2 = ret.a5; |
| + data->data3 = ret.a6; |
| + data->data4 = ret.a7; |
| + return 0; |
| + } |
| + |
| + return -EINVAL; |
| +} |
| + |
| +static int ffa_mem_first_frag(u32 func_id, phys_addr_t buf, u32 buf_sz, |
| + u32 frag_len, u32 len, u64 *handle) |
| +{ |
| + ffa_value_t ret; |
| + |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = func_id, .a1 = len, .a2 = frag_len, |
| + .a3 = buf, .a4 = buf_sz, |
| + }, &ret); |
| + |
| + while (ret.a0 == FFA_MEM_OP_PAUSE) |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = FFA_MEM_OP_RESUME, |
| + .a1 = ret.a1, .a2 = ret.a2, |
| + }, &ret); |
| + |
| + if (ret.a0 == FFA_ERROR) |
| + return ffa_to_linux_errno((int)ret.a2); |
| + |
| + if (ret.a0 != FFA_SUCCESS) |
| + return -EOPNOTSUPP; |
| + |
| + if (handle) |
| + *handle = PACK_HANDLE(ret.a2, ret.a3); |
| + |
| + return frag_len; |
| +} |
| + |
| +static int ffa_mem_next_frag(u64 handle, u32 frag_len) |
| +{ |
| + ffa_value_t ret; |
| + |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = FFA_MEM_FRAG_TX, |
| + .a1 = HANDLE_LOW(handle), .a2 = HANDLE_HIGH(handle), |
| + .a3 = frag_len, |
| + }, &ret); |
| + |
| + while (ret.a0 == FFA_MEM_OP_PAUSE) |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = FFA_MEM_OP_RESUME, |
| + .a1 = ret.a1, .a2 = ret.a2, |
| + }, &ret); |
| + |
| + if (ret.a0 == FFA_ERROR) |
| + return ffa_to_linux_errno((int)ret.a2); |
| + |
| + if (ret.a0 != FFA_MEM_FRAG_RX) |
| + return -EOPNOTSUPP; |
| + |
| + return ret.a3; |
| +} |
| + |
| +static int |
| +ffa_transmit_fragment(u32 func_id, phys_addr_t buf, u32 buf_sz, u32 frag_len, |
| + u32 len, u64 *handle, bool first) |
| +{ |
| + if (!first) |
| + return ffa_mem_next_frag(*handle, frag_len); |
| + |
| + return ffa_mem_first_frag(func_id, buf, buf_sz, frag_len, len, handle); |
| +} |
| + |
| +static u32 ffa_get_num_pages_sg(struct scatterlist *sg) |
| +{ |
| + u32 num_pages = 0; |
| + |
| + do { |
| + num_pages += sg->length / FFA_PAGE_SIZE; |
| + } while ((sg = sg_next(sg))); |
| + |
| + return num_pages; |
| +} |
| + |
| +static int |
| +ffa_setup_and_transmit(u32 func_id, void *buffer, u32 max_fragsize, |
| + struct ffa_mem_ops_args *args) |
| +{ |
| + int rc = 0; |
| + bool first = true; |
| + phys_addr_t addr = 0; |
| + struct ffa_composite_mem_region *composite; |
| + struct ffa_mem_region_addr_range *constituents; |
| + struct ffa_mem_region_attributes *ep_mem_access; |
| + struct ffa_mem_region *mem_region = buffer; |
| + u32 idx, frag_len, length, buf_sz = 0, num_entries = sg_nents(args->sg); |
| + |
| + mem_region->tag = args->tag; |
| + mem_region->flags = args->flags; |
| + mem_region->sender_id = drv_info->vm_id; |
| + mem_region->attributes = FFA_MEM_NORMAL | FFA_MEM_WRITE_BACK | |
| + FFA_MEM_INNER_SHAREABLE; |
| + ep_mem_access = &mem_region->ep_mem_access[0]; |
| + |
| + for (idx = 0; idx < args->nattrs; idx++, ep_mem_access++) { |
| + ep_mem_access->receiver = args->attrs[idx].receiver; |
| + ep_mem_access->attrs = args->attrs[idx].attrs; |
| + ep_mem_access->composite_off = COMPOSITE_OFFSET(args->nattrs); |
| + } |
| + mem_region->ep_count = args->nattrs; |
| + |
| + composite = buffer + COMPOSITE_OFFSET(args->nattrs); |
| + composite->total_pg_cnt = ffa_get_num_pages_sg(args->sg); |
| + composite->addr_range_cnt = num_entries; |
| + |
| + length = COMPOSITE_CONSTITUENTS_OFFSET(args->nattrs, num_entries); |
| + frag_len = COMPOSITE_CONSTITUENTS_OFFSET(args->nattrs, 0); |
| + if (frag_len > max_fragsize) |
| + return -ENXIO; |
| + |
| + if (!args->use_txbuf) { |
| + addr = virt_to_phys(buffer); |
| + buf_sz = max_fragsize / FFA_PAGE_SIZE; |
| + } |
| + |
| + constituents = buffer + frag_len; |
| + idx = 0; |
| + do { |
| + if (frag_len == max_fragsize) { |
| + rc = ffa_transmit_fragment(func_id, addr, buf_sz, |
| + frag_len, length, |
| + &args->g_handle, first); |
| + if (rc < 0) |
| + return -ENXIO; |
| + |
| + first = false; |
| + idx = 0; |
| + frag_len = 0; |
| + constituents = buffer; |
| + } |
| + |
| + if ((void *)constituents - buffer > max_fragsize) { |
| + pr_err("Memory Region Fragment > Tx Buffer size\n"); |
| + return -EFAULT; |
| + } |
| + |
| + constituents->address = sg_phys(args->sg); |
| + constituents->pg_cnt = args->sg->length / FFA_PAGE_SIZE; |
| + constituents++; |
| + frag_len += sizeof(struct ffa_mem_region_addr_range); |
| + } while ((args->sg = sg_next(args->sg))); |
| + |
| + return ffa_transmit_fragment(func_id, addr, buf_sz, frag_len, |
| + length, &args->g_handle, first); |
| +} |
| + |
| +static int ffa_memory_ops(u32 func_id, struct ffa_mem_ops_args *args) |
| +{ |
| + int ret; |
| + void *buffer; |
| + |
| + if (!args->use_txbuf) { |
| + buffer = alloc_pages_exact(RXTX_BUFFER_SIZE, GFP_KERNEL); |
| + if (!buffer) |
| + return -ENOMEM; |
| + } else { |
| + buffer = drv_info->tx_buffer; |
| + mutex_lock(&drv_info->tx_lock); |
| + } |
| + |
| + ret = ffa_setup_and_transmit(func_id, buffer, RXTX_BUFFER_SIZE, args); |
| + |
| + if (args->use_txbuf) |
| + mutex_unlock(&drv_info->tx_lock); |
| + else |
| + free_pages_exact(buffer, RXTX_BUFFER_SIZE); |
| + |
| + return ret < 0 ? ret : 0; |
| +} |
| + |
| +static int ffa_memory_reclaim(u64 g_handle, u32 flags) |
| +{ |
| + ffa_value_t ret; |
| + |
| + invoke_ffa_fn((ffa_value_t){ |
| + .a0 = FFA_MEM_RECLAIM, |
| + .a1 = HANDLE_LOW(g_handle), .a2 = HANDLE_HIGH(g_handle), |
| + .a3 = flags, |
| + }, &ret); |
| + |
| + if (ret.a0 == FFA_ERROR) |
| + return ffa_to_linux_errno((int)ret.a2); |
| + |
| + return 0; |
| +} |
| + |
| +static u32 ffa_api_version_get(void) |
| +{ |
| + return drv_info->version; |
| +} |
| + |
| +static int ffa_partition_info_get(const char *uuid_str, |
| + struct ffa_partition_info *buffer) |
| +{ |
| + int count; |
| + uuid_t uuid; |
| + struct ffa_partition_info *pbuf; |
| + |
| + if (uuid_parse(uuid_str, &uuid)) { |
| + pr_err("invalid uuid (%s)\n", uuid_str); |
| + return -ENODEV; |
| + } |
| + |
| + count = ffa_partition_probe(&uuid_null, &pbuf); |
| + if (count <= 0) |
| + return -ENOENT; |
| + |
| + memcpy(buffer, pbuf, sizeof(*pbuf) * count); |
| + kfree(pbuf); |
| + return 0; |
| +} |
| + |
| +static void ffa_mode_32bit_set(struct ffa_device *dev) |
| +{ |
| + dev->mode_32bit = true; |
| +} |
| + |
| +static int ffa_sync_send_receive(struct ffa_device *dev, |
| + struct ffa_send_direct_data *data) |
| +{ |
| + return ffa_msg_send_direct_req(drv_info->vm_id, dev->vm_id, |
| + dev->mode_32bit, data); |
| +} |
| + |
| +static int |
| +ffa_memory_share(struct ffa_device *dev, struct ffa_mem_ops_args *args) |
| +{ |
| + if (dev->mode_32bit) |
| + return ffa_memory_ops(FFA_MEM_SHARE, args); |
| + |
| + return ffa_memory_ops(FFA_FN_NATIVE(MEM_SHARE), args); |
| +} |
| + |
| +static int |
| +ffa_memory_lend(struct ffa_device *dev, struct ffa_mem_ops_args *args) |
| +{ |
| + /* Note that upon a successful MEM_LEND request the caller |
| + * must ensure that the memory region specified is not accessed |
| + * until a successful MEM_RECALIM call has been made. |
| + * On systems with a hypervisor present this will been enforced, |
| + * however on systems without a hypervisor the responsibility |
| + * falls to the calling kernel driver to prevent access. |
| + */ |
| + if (dev->mode_32bit) |
| + return ffa_memory_ops(FFA_MEM_LEND, args); |
| + |
| + return ffa_memory_ops(FFA_FN_NATIVE(MEM_LEND), args); |
| +} |
| + |
| +static const struct ffa_dev_ops ffa_ops = { |
| + .api_version_get = ffa_api_version_get, |
| + .partition_info_get = ffa_partition_info_get, |
| + .mode_32bit_set = ffa_mode_32bit_set, |
| + .sync_send_receive = ffa_sync_send_receive, |
| + .memory_reclaim = ffa_memory_reclaim, |
| + .memory_share = ffa_memory_share, |
| + .memory_lend = ffa_memory_lend, |
| +}; |
| + |
| +const struct ffa_dev_ops *ffa_dev_ops_get(struct ffa_device *dev) |
| +{ |
| + if (ffa_device_is_valid(dev)) |
| + return &ffa_ops; |
| + |
| + return NULL; |
| +} |
| +EXPORT_SYMBOL_GPL(ffa_dev_ops_get); |
| + |
| +void ffa_device_match_uuid(struct ffa_device *ffa_dev, const uuid_t *uuid) |
| +{ |
| + int count, idx; |
| + struct ffa_partition_info *pbuf, *tpbuf; |
| + |
| + count = ffa_partition_probe(uuid, &pbuf); |
| + if (count <= 0) |
| + return; |
| + |
| + for (idx = 0, tpbuf = pbuf; idx < count; idx++, tpbuf++) |
| + if (tpbuf->id == ffa_dev->vm_id) |
| + uuid_copy(&ffa_dev->uuid, uuid); |
| + kfree(pbuf); |
| +} |
| + |
| +static void ffa_setup_partitions(void) |
| +{ |
| + int count, idx; |
| + struct ffa_device *ffa_dev; |
| + struct ffa_partition_info *pbuf, *tpbuf; |
| + |
| + count = ffa_partition_probe(&uuid_null, &pbuf); |
| + if (count <= 0) { |
| + pr_info("%s: No partitions found, error %d\n", __func__, count); |
| + return; |
| + } |
| + |
| + for (idx = 0, tpbuf = pbuf; idx < count; idx++, tpbuf++) { |
| + /* Note that the &uuid_null parameter will require |
| + * ffa_device_match() to find the UUID of this partition id |
| + * with help of ffa_device_match_uuid(). Once the FF-A spec |
| + * is updated to provide correct UUID here for each partition |
| + * as part of the discovery API, we need to pass the |
| + * discovered UUID here instead. |
| + */ |
| + ffa_dev = ffa_device_register(&uuid_null, tpbuf->id); |
| + if (!ffa_dev) { |
| + pr_err("%s: failed to register partition ID 0x%x\n", |
| + __func__, tpbuf->id); |
| + continue; |
| + } |
| + |
| + ffa_dev_set_drvdata(ffa_dev, drv_info); |
| + } |
| + kfree(pbuf); |
| +} |
| + |
| +static int __init ffa_init(void) |
| +{ |
| + int ret; |
| + |
| + ret = ffa_transport_init(&invoke_ffa_fn); |
| + if (ret) |
| + return ret; |
| + |
| + ret = arm_ffa_bus_init(); |
| + if (ret) |
| + return ret; |
| + |
| + drv_info = kzalloc(sizeof(*drv_info), GFP_KERNEL); |
| + if (!drv_info) { |
| + ret = -ENOMEM; |
| + goto ffa_bus_exit; |
| + } |
| + |
| + ret = ffa_version_check(&drv_info->version); |
| + if (ret) |
| + goto free_drv_info; |
| + |
| + if (ffa_id_get(&drv_info->vm_id)) { |
| + pr_err("failed to obtain VM id for self\n"); |
| + ret = -ENODEV; |
| + goto free_drv_info; |
| + } |
| + |
| + drv_info->rx_buffer = alloc_pages_exact(RXTX_BUFFER_SIZE, GFP_KERNEL); |
| + if (!drv_info->rx_buffer) { |
| + ret = -ENOMEM; |
| + goto free_pages; |
| + } |
| + |
| + drv_info->tx_buffer = alloc_pages_exact(RXTX_BUFFER_SIZE, GFP_KERNEL); |
| + if (!drv_info->tx_buffer) { |
| + ret = -ENOMEM; |
| + goto free_pages; |
| + } |
| + |
| + ret = ffa_rxtx_map(virt_to_phys(drv_info->tx_buffer), |
| + virt_to_phys(drv_info->rx_buffer), |
| + RXTX_BUFFER_SIZE / FFA_PAGE_SIZE); |
| + if (ret) { |
| + pr_err("failed to register FFA RxTx buffers\n"); |
| + goto free_pages; |
| + } |
| + |
| + mutex_init(&drv_info->rx_lock); |
| + mutex_init(&drv_info->tx_lock); |
| + |
| + ffa_setup_partitions(); |
| + |
| + return 0; |
| +free_pages: |
| + if (drv_info->tx_buffer) |
| + free_pages_exact(drv_info->tx_buffer, RXTX_BUFFER_SIZE); |
| + free_pages_exact(drv_info->rx_buffer, RXTX_BUFFER_SIZE); |
| +free_drv_info: |
| + kfree(drv_info); |
| +ffa_bus_exit: |
| + arm_ffa_bus_exit(); |
| + return ret; |
| +} |
| +subsys_initcall(ffa_init); |
| + |
| +static void __exit ffa_exit(void) |
| +{ |
| + ffa_rxtx_unmap(drv_info->vm_id); |
| + free_pages_exact(drv_info->tx_buffer, RXTX_BUFFER_SIZE); |
| + free_pages_exact(drv_info->rx_buffer, RXTX_BUFFER_SIZE); |
| + kfree(drv_info); |
| + arm_ffa_bus_exit(); |
| +} |
| +module_exit(ffa_exit); |
| + |
| +MODULE_ALIAS("arm-ffa"); |
| +MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); |
| +MODULE_DESCRIPTION("Arm FF-A interface driver"); |
| +MODULE_LICENSE("GPL v2"); |
| diff --git a/drivers/firmware/arm_ffa/smccc.c b/drivers/firmware/arm_ffa/smccc.c |
| new file mode 100644 |
| index 000000000000..4d85bfff0a4e |
| --- /dev/null |
| +++ b/drivers/firmware/arm_ffa/smccc.c |
| @@ -0,0 +1,39 @@ |
| +// SPDX-License-Identifier: GPL-2.0-only |
| +/* |
| + * Copyright (C) 2021 ARM Ltd. |
| + */ |
| + |
| +#include <linux/printk.h> |
| + |
| +#include "common.h" |
| + |
| +static void __arm_ffa_fn_smc(ffa_value_t args, ffa_value_t *res) |
| +{ |
| + arm_smccc_1_2_smc(&args, res); |
| +} |
| + |
| +static void __arm_ffa_fn_hvc(ffa_value_t args, ffa_value_t *res) |
| +{ |
| + arm_smccc_1_2_hvc(&args, res); |
| +} |
| + |
| +int __init ffa_transport_init(ffa_fn **invoke_ffa_fn) |
| +{ |
| + enum arm_smccc_conduit conduit; |
| + |
| + if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2) |
| + return -EOPNOTSUPP; |
| + |
| + conduit = arm_smccc_1_1_get_conduit(); |
| + if (conduit == SMCCC_CONDUIT_NONE) { |
| + pr_err("%s: invalid SMCCC conduit\n", __func__); |
| + return -EOPNOTSUPP; |
| + } |
| + |
| + if (conduit == SMCCC_CONDUIT_SMC) |
| + *invoke_ffa_fn = __arm_ffa_fn_smc; |
| + else |
| + *invoke_ffa_fn = __arm_ffa_fn_hvc; |
| + |
| + return 0; |
| +} |
| diff --git a/include/linux/arm-smccc.h b/include/linux/arm-smccc.h |
| index 62c54234576c..c8eb24af3c62 100644 |
| --- a/include/linux/arm-smccc.h |
| +++ b/include/linux/arm-smccc.h |
| @@ -186,6 +186,61 @@ struct arm_smccc_res { |
| unsigned long a3; |
| }; |
| |
| +#ifdef CONFIG_ARM64 |
| +/** |
| + * struct arm_smccc_1_2_regs - Arguments for or Results from SMC/HVC call |
| + * @a0-a17 argument values from registers 0 to 17 |
| + */ |
| +struct arm_smccc_1_2_regs { |
| + unsigned long a0; |
| + unsigned long a1; |
| + unsigned long a2; |
| + unsigned long a3; |
| + unsigned long a4; |
| + unsigned long a5; |
| + unsigned long a6; |
| + unsigned long a7; |
| + unsigned long a8; |
| + unsigned long a9; |
| + unsigned long a10; |
| + unsigned long a11; |
| + unsigned long a12; |
| + unsigned long a13; |
| + unsigned long a14; |
| + unsigned long a15; |
| + unsigned long a16; |
| + unsigned long a17; |
| +}; |
| + |
| +/** |
| + * arm_smccc_1_2_hvc() - make HVC calls |
| + * @args: arguments passed via struct arm_smccc_1_2_regs |
| + * @res: result values via struct arm_smccc_1_2_regs |
| + * |
| + * This function is used to make HVC calls following SMC Calling Convention |
| + * v1.2 or above. The content of the supplied param are copied from the |
| + * structure to registers prior to the HVC instruction. The return values |
| + * are updated with the content from registers on return from the HVC |
| + * instruction. |
| + */ |
| +asmlinkage void arm_smccc_1_2_hvc(const struct arm_smccc_1_2_regs *args, |
| + struct arm_smccc_1_2_regs *res); |
| + |
| +/** |
| + * arm_smccc_1_2_smc() - make SMC calls |
| + * @args: arguments passed via struct arm_smccc_1_2_regs |
| + * @res: result values via struct arm_smccc_1_2_regs |
| + * |
| + * This function is used to make SMC calls following SMC Calling Convention |
| + * v1.2 or above. The content of the supplied param are copied from the |
| + * structure to registers prior to the SMC instruction. The return values |
| + * are updated with the content from registers on return from the SMC |
| + * instruction. |
| + */ |
| +asmlinkage void arm_smccc_1_2_smc(const struct arm_smccc_1_2_regs *args, |
| + struct arm_smccc_1_2_regs *res); |
| +#endif |
| + |
| /** |
| * struct arm_smccc_quirk - Contains quirk information |
| * @id: quirk identification |
| diff --git a/include/linux/arm_ffa.h b/include/linux/arm_ffa.h |
| new file mode 100644 |
| index 000000000000..85651e41ded8 |
| --- /dev/null |
| +++ b/include/linux/arm_ffa.h |
| @@ -0,0 +1,269 @@ |
| +/* SPDX-License-Identifier: GPL-2.0-only */ |
| +/* |
| + * Copyright (C) 2021 ARM Ltd. |
| + */ |
| + |
| +#ifndef _LINUX_ARM_FFA_H |
| +#define _LINUX_ARM_FFA_H |
| + |
| +#include <linux/device.h> |
| +#include <linux/module.h> |
| +#include <linux/types.h> |
| +#include <linux/uuid.h> |
| + |
| +/* FFA Bus/Device/Driver related */ |
| +struct ffa_device { |
| + int vm_id; |
| + bool mode_32bit; |
| + uuid_t uuid; |
| + struct device dev; |
| +}; |
| + |
| +#define to_ffa_dev(d) container_of(d, struct ffa_device, dev) |
| + |
| +struct ffa_device_id { |
| + uuid_t uuid; |
| +}; |
| + |
| +struct ffa_driver { |
| + const char *name; |
| + int (*probe)(struct ffa_device *sdev); |
| + void (*remove)(struct ffa_device *sdev); |
| + const struct ffa_device_id *id_table; |
| + |
| + struct device_driver driver; |
| +}; |
| + |
| +#define to_ffa_driver(d) container_of(d, struct ffa_driver, driver) |
| + |
| +static inline void ffa_dev_set_drvdata(struct ffa_device *fdev, void *data) |
| +{ |
| + fdev->dev.driver_data = data; |
| +} |
| + |
| +#if IS_REACHABLE(CONFIG_ARM_FFA_TRANSPORT) |
| +struct ffa_device *ffa_device_register(const uuid_t *uuid, int vm_id); |
| +void ffa_device_unregister(struct ffa_device *ffa_dev); |
| +int ffa_driver_register(struct ffa_driver *driver, struct module *owner, |
| + const char *mod_name); |
| +void ffa_driver_unregister(struct ffa_driver *driver); |
| +bool ffa_device_is_valid(struct ffa_device *ffa_dev); |
| +const struct ffa_dev_ops *ffa_dev_ops_get(struct ffa_device *dev); |
| + |
| +#else |
| +static inline |
| +struct ffa_device *ffa_device_register(const uuid_t *uuid, int vm_id) |
| +{ |
| + return NULL; |
| +} |
| + |
| +static inline void ffa_device_unregister(struct ffa_device *dev) {} |
| + |
| +static inline int |
| +ffa_driver_register(struct ffa_driver *driver, struct module *owner, |
| + const char *mod_name) |
| +{ |
| + return -EINVAL; |
| +} |
| + |
| +static inline void ffa_driver_unregister(struct ffa_driver *driver) {} |
| + |
| +static inline |
| +bool ffa_device_is_valid(struct ffa_device *ffa_dev) { return false; } |
| + |
| +static inline |
| +const struct ffa_dev_ops *ffa_dev_ops_get(struct ffa_device *dev) |
| +{ |
| + return NULL; |
| +} |
| +#endif /* CONFIG_ARM_FFA_TRANSPORT */ |
| + |
| +#define ffa_register(driver) \ |
| + ffa_driver_register(driver, THIS_MODULE, KBUILD_MODNAME) |
| +#define ffa_unregister(driver) \ |
| + ffa_driver_unregister(driver) |
| + |
| +/** |
| + * module_ffa_driver() - Helper macro for registering a psa_ffa driver |
| + * @__ffa_driver: ffa_driver structure |
| + * |
| + * Helper macro for psa_ffa drivers to set up proper module init / exit |
| + * functions. Replaces module_init() and module_exit() and keeps people from |
| + * printing pointless things to the kernel log when their driver is loaded. |
| + */ |
| +#define module_ffa_driver(__ffa_driver) \ |
| + module_driver(__ffa_driver, ffa_register, ffa_unregister) |
| + |
| +/* FFA transport related */ |
| +struct ffa_partition_info { |
| + u16 id; |
| + u16 exec_ctxt; |
| +/* partition supports receipt of direct requests */ |
| +#define FFA_PARTITION_DIRECT_RECV BIT(0) |
| +/* partition can send direct requests. */ |
| +#define FFA_PARTITION_DIRECT_SEND BIT(1) |
| +/* partition can send and receive indirect messages. */ |
| +#define FFA_PARTITION_INDIRECT_MSG BIT(2) |
| + u32 properties; |
| +}; |
| + |
| +/* For use with FFA_MSG_SEND_DIRECT_{REQ,RESP} which pass data via registers */ |
| +struct ffa_send_direct_data { |
| + unsigned long data0; /* w3/x3 */ |
| + unsigned long data1; /* w4/x4 */ |
| + unsigned long data2; /* w5/x5 */ |
| + unsigned long data3; /* w6/x6 */ |
| + unsigned long data4; /* w7/x7 */ |
| +}; |
| + |
| +struct ffa_mem_region_addr_range { |
| + /* The base IPA of the constituent memory region, aligned to 4 kiB */ |
| + u64 address; |
| + /* The number of 4 kiB pages in the constituent memory region. */ |
| + u32 pg_cnt; |
| + u32 reserved; |
| +}; |
| + |
| +struct ffa_composite_mem_region { |
| + /* |
| + * The total number of 4 kiB pages included in this memory region. This |
| + * must be equal to the sum of page counts specified in each |
| + * `struct ffa_mem_region_addr_range`. |
| + */ |
| + u32 total_pg_cnt; |
| + /* The number of constituents included in this memory region range */ |
| + u32 addr_range_cnt; |
| + u64 reserved; |
| + /** An array of `addr_range_cnt` memory region constituents. */ |
| + struct ffa_mem_region_addr_range constituents[]; |
| +}; |
| + |
| +struct ffa_mem_region_attributes { |
| + /* The ID of the VM to which the memory is being given or shared. */ |
| + u16 receiver; |
| + /* |
| + * The permissions with which the memory region should be mapped in the |
| + * receiver's page table. |
| + */ |
| +#define FFA_MEM_EXEC BIT(3) |
| +#define FFA_MEM_NO_EXEC BIT(2) |
| +#define FFA_MEM_RW BIT(1) |
| +#define FFA_MEM_RO BIT(0) |
| + u8 attrs; |
| + /* |
| + * Flags used during FFA_MEM_RETRIEVE_REQ and FFA_MEM_RETRIEVE_RESP |
| + * for memory regions with multiple borrowers. |
| + */ |
| +#define FFA_MEM_RETRIEVE_SELF_BORROWER BIT(0) |
| + u8 flag; |
| + u32 composite_off; |
| + /* |
| + * Offset in bytes from the start of the outer `ffa_memory_region` to |
| + * an `struct ffa_mem_region_addr_range`. |
| + */ |
| + u64 reserved; |
| +}; |
| + |
| +struct ffa_mem_region { |
| + /* The ID of the VM/owner which originally sent the memory region */ |
| + u16 sender_id; |
| +#define FFA_MEM_NORMAL BIT(5) |
| +#define FFA_MEM_DEVICE BIT(4) |
| + |
| +#define FFA_MEM_WRITE_BACK (3 << 2) |
| +#define FFA_MEM_NON_CACHEABLE (1 << 2) |
| + |
| +#define FFA_DEV_nGnRnE (0 << 2) |
| +#define FFA_DEV_nGnRE (1 << 2) |
| +#define FFA_DEV_nGRE (2 << 2) |
| +#define FFA_DEV_GRE (3 << 2) |
| + |
| +#define FFA_MEM_NON_SHAREABLE (0) |
| +#define FFA_MEM_OUTER_SHAREABLE (2) |
| +#define FFA_MEM_INNER_SHAREABLE (3) |
| + u8 attributes; |
| + u8 reserved_0; |
| +/* |
| + * Clear memory region contents after unmapping it from the sender and |
| + * before mapping it for any receiver. |
| + */ |
| +#define FFA_MEM_CLEAR BIT(0) |
| +/* |
| + * Whether the hypervisor may time slice the memory sharing or retrieval |
| + * operation. |
| + */ |
| +#define FFA_TIME_SLICE_ENABLE BIT(1) |
| + |
| +#define FFA_MEM_RETRIEVE_TYPE_IN_RESP (0 << 3) |
| +#define FFA_MEM_RETRIEVE_TYPE_SHARE (1 << 3) |
| +#define FFA_MEM_RETRIEVE_TYPE_LEND (2 << 3) |
| +#define FFA_MEM_RETRIEVE_TYPE_DONATE (3 << 3) |
| + |
| +#define FFA_MEM_RETRIEVE_ADDR_ALIGN_HINT BIT(9) |
| +#define FFA_MEM_RETRIEVE_ADDR_ALIGN(x) ((x) << 5) |
| + /* Flags to control behaviour of the transaction. */ |
| + u32 flags; |
| +#define HANDLE_LOW_MASK GENMASK_ULL(31, 0) |
| +#define HANDLE_HIGH_MASK GENMASK_ULL(63, 32) |
| +#define HANDLE_LOW(x) ((u32)(FIELD_GET(HANDLE_LOW_MASK, (x)))) |
| +#define HANDLE_HIGH(x) ((u32)(FIELD_GET(HANDLE_HIGH_MASK, (x)))) |
| + |
| +#define PACK_HANDLE(l, h) \ |
| + (FIELD_PREP(HANDLE_LOW_MASK, (l)) | FIELD_PREP(HANDLE_HIGH_MASK, (h))) |
| + /* |
| + * A globally-unique ID assigned by the hypervisor for a region |
| + * of memory being sent between VMs. |
| + */ |
| + u64 handle; |
| + /* |
| + * An implementation defined value associated with the receiver and the |
| + * memory region. |
| + */ |
| + u64 tag; |
| + u32 reserved_1; |
| + /* |
| + * The number of `ffa_mem_region_attributes` entries included in this |
| + * transaction. |
| + */ |
| + u32 ep_count; |
| + /* |
| + * An array of endpoint memory access descriptors. |
| + * Each one specifies a memory region offset, an endpoint and the |
| + * attributes with which this memory region should be mapped in that |
| + * endpoint's page table. |
| + */ |
| + struct ffa_mem_region_attributes ep_mem_access[]; |
| +}; |
| + |
| +#define COMPOSITE_OFFSET(x) \ |
| + (offsetof(struct ffa_mem_region, ep_mem_access[x])) |
| +#define CONSTITUENTS_OFFSET(x) \ |
| + (offsetof(struct ffa_composite_mem_region, constituents[x])) |
| +#define COMPOSITE_CONSTITUENTS_OFFSET(x, y) \ |
| + (COMPOSITE_OFFSET(x) + CONSTITUENTS_OFFSET(y)) |
| + |
| +struct ffa_mem_ops_args { |
| + bool use_txbuf; |
| + u32 nattrs; |
| + u32 flags; |
| + u64 tag; |
| + u64 g_handle; |
| + struct scatterlist *sg; |
| + struct ffa_mem_region_attributes *attrs; |
| +}; |
| + |
| +struct ffa_dev_ops { |
| + u32 (*api_version_get)(void); |
| + int (*partition_info_get)(const char *uuid_str, |
| + struct ffa_partition_info *buffer); |
| + void (*mode_32bit_set)(struct ffa_device *dev); |
| + int (*sync_send_receive)(struct ffa_device *dev, |
| + struct ffa_send_direct_data *data); |
| + int (*memory_reclaim)(u64 g_handle, u32 flags); |
| + int (*memory_share)(struct ffa_device *dev, |
| + struct ffa_mem_ops_args *args); |
| + int (*memory_lend)(struct ffa_device *dev, |
| + struct ffa_mem_ops_args *args); |
| +}; |
| + |
| +#endif /* _LINUX_ARM_FFA_H */ |
| -- |
| 2.30.2 |
| |