// 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/acpi.h>
#include <linux/arm_ffa.h>
#include <linux/bitfield.h>
#include <linux/cpuhotplug.h>
#include <linux/device.h>
#include <linux/hashtable.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/mutex.h>
#include <linux/of_irq.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/uuid.h>
#include <linux/xarray.h>
#include "common.h"
#define FFA_DRIVER_VERSION FFA_VERSION_1_1
#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)))
/*
* 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
#define FFA_MAX_NOTIFICATIONS 64
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 */
-ENODATA, /* FFA_RET_NO_DATA */
};
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_pcpu_irq {
struct ffa_drv_info *info;
};
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;
bool mem_ops_native;
bool bitmap_created;
bool notif_enabled;
unsigned int sched_recv_irq;
unsigned int cpuhp_state;
struct ffa_pcpu_irq __percpu *irq_pcpu;
struct workqueue_struct *notif_pcpu_wq;
struct work_struct notif_pcpu_work;
struct work_struct irq_work;
struct xarray partition_info;
unsigned int partition_count;
DECLARE_HASHTABLE(notifier_hash, ilog2(FFA_MAX_NOTIFICATIONS));
struct mutex notify_lock; /* lock to protect notifier hashtable */
};
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 = FFA_MAJOR_VERSION(version), minor = FFA_MINOR_VERSION(version);
u16 drv_major = FFA_MAJOR_VERSION(FFA_DRIVER_VERSION);
u16 drv_minor = FFA_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",
FFA_MAJOR_VERSION(ver.a0), FFA_MINOR_VERSION(ver.a0),
FFA_MAJOR_VERSION(FFA_MIN_VERSION),
FFA_MINOR_VERSION(FFA_MIN_VERSION));
return -EINVAL;
}
pr_info("Driver version %d.%d\n", FFA_MAJOR_VERSION(FFA_DRIVER_VERSION),
FFA_MINOR_VERSION(FFA_DRIVER_VERSION));
pr_info("Firmware version %d.%d found\n", FFA_MAJOR_VERSION(ver.a0),
FFA_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;
}
#define PARTITION_INFO_GET_RETURN_COUNT_ONLY BIT(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 idx, count, flags = 0, sz, buf_sz;
ffa_value_t partition_info;
if (drv_info->version > FFA_VERSION_1_0 &&
(!buffer || !num_partitions)) /* Just get the count for now */
flags = PARTITION_INFO_GET_RETURN_COUNT_ONLY;
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,
.a5 = flags,
}, &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 (drv_info->version > FFA_VERSION_1_0) {
bu
|