Arm FF-A fixes for v7.1

The updates fix several robustness issues in the FF-A bus and core driver,
 mostly around notification handling, RxTx buffer sizing, firmware-provided
 bounds, and cleanup paths.
 
 The notification fixes make per-CPU notification work truly per-CPU, avoid
 running per-vCPU notification handling from IPI context, keep RX buffer
 release serialized under rx_lock, validate framework notification payload
 layout before copying from the shared RX buffer, and avoid dereferencing
 notifier entries after they may have been unregistered.
 
 The remaining fixes reject FF-A drivers without an ID table at registration
 time, avoid freeing an unallocated RX buffer after allocation failure,
 unregister the FF-A v1.0 bus notifier on teardown, bound register-based
 PARTITION_INFO_GET descriptor copies, align the stored RxTx buffer size with
 the size mapped to firmware, and fix sched-recv callback partition lookup on
 the circular partition list.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEunHlEgbzHrJD3ZPhAEG6vDF+4pgFAmn7maMACgkQAEG6vDF+
 4pgGAw/+OClw1JVGF0i6nTBGxuQAmZQGtuM4HZEnxloFsxPLWFqoRGsIJ4MMLUnb
 xCClPqi7LoNCZKlSD3WxXcuVmAEO9J7gto8NzHzY+Mdn4ByhMfvlu6DZtLYLLwy2
 33HCoW++VW1iXomZ3yBnyZmA8U6jMNTs7z5pE420iArxy4HfA2BfrRpBx+L4dA5n
 wEj4unrab9GHo43rFAkQsTb2rwGh40H3w41mD7TnHzwwsvTbFgIfNiHo0kneTGBa
 P8eXFj3b5tNBv9sOccjqZQKeANDmwmm2ncxtWs6dFgOi3QXI+UyQQvylnG8C96DM
 tSjNmwCcw7wWvjpK0wmVHrL0jh9pkpPHNJ6qIxlNKPkd+96L8vWo7uNbel76qLjU
 vP1y1RCjdtHeJs9xYW9BYUNoNTDOWKXWGivzIe4P3PevD4q+imqTMBPe6lpF0+50
 4bIa//A2cm6iuPGL9ySgwJ9jEoXY5QAZD5TjGg3pcW2UFyL5F1Wg8yFQBHuU8XP2
 N7nlmntzLRRNn8pjjAJckQWKUlH+7hiPoQJMiBGuIWLevlkjt2SZCMbG2RL7Ymbd
 9+wlb8dUlNTMfdrBLZZMym1cp07gh/myDo0iSaK7WY5tvYJj0O/lI7DXf69adgH2
 GFFd30JAo+CMXJuTadOZf5+3aUYzwCl1l4I8axT1gqyNC8A4/Bs=
 =iFfw
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEo6/YBQwIrVS28WGKmmx57+YAGNkFAmn8gG4ACgkQmmx57+YA
 GNnsPQ/+Ij20fAnnPF0eyvYj1vPn4LGWHZneD8DzPcms77QF5oQZnkbC/HIecAsC
 6SqSSPtwuYVDQuPtEoALFh5RtZt0UF5lQgd8Xgk9Mi+cVLEVJkb8OJkNKXofC45Z
 FaW7apHcgZOMadmWATJHhniLnlYCodDP3LQq8n2CvfmL/RAXdvPrDnba6eiwj9nP
 iONVEsyFRHhpZAQvz/JgpsApRj2K6+XT9SR2yVAUQk+CNkA+QbvMI8QAA5YCiR2H
 GFFfi1AAzM41m5PQcWqFKUtgr3d8QIlDIvlaoGa7fofmxSSBgxXpC/BGLe+HlUrW
 ZGBuicbBg4eZIw2AX0zbBJxH/Zpg1Z7EuDM5EGEmyrMjyi35pDEouoFdTmIT1lr7
 MwC2odJlcS1KYYyHQDleVHKrcUwnwa4Pw1lgganhnnADb4Rv1ziqHhoTP1qxNbbu
 qqXXD1+go9WYxh9CiLA2HbjiTGzNLm9DMOoIc+/cEwLAD8H3Xa7oK4Yplee6P9d7
 utaTR3NwT7B5XQ2mbh7LZjBKz4W1DfMN+XrmI3y19bL30ufmOi8K6uKYrbLxRUAc
 gH9nO4BVCUXgCqTimfI0ORsA6ZoCXcNNpc491ANX15S9vmbN4rRKc6hNfriYEycw
 k5XIzT5H9v7Qu3OvkM4cTwP4iv0l6UFAB6u1+17I+2kG9/CJJZs=
 =wjLn
 -----END PGP SIGNATURE-----

Merge tag 'ffa-fixes-7.1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux into arm/fixes

Arm FF-A fixes for v7.1

The updates fix several robustness issues in the FF-A bus and core driver,
mostly around notification handling, RxTx buffer sizing, firmware-provided
bounds, and cleanup paths.

The notification fixes make per-CPU notification work truly per-CPU, avoid
running per-vCPU notification handling from IPI context, keep RX buffer
release serialized under rx_lock, validate framework notification payload
layout before copying from the shared RX buffer, and avoid dereferencing
notifier entries after they may have been unregistered.

The remaining fixes reject FF-A drivers without an ID table at registration
time, avoid freeing an unallocated RX buffer after allocation failure,
unregister the FF-A v1.0 bus notifier on teardown, bound register-based
PARTITION_INFO_GET descriptor copies, align the stored RxTx buffer size with
the size mapped to firmware, and fix sched-recv callback partition lookup on
the circular partition list.

* tag 'ffa-fixes-7.1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux:
  firmware: arm_ffa: Fix sched-recv callback partition lookup
  firmware: arm_ffa: Snapshot notifier callbacks under lock
  firmware: arm_ffa: Align RxTx buffer size before mapping
  firmware: arm_ffa: Validate framework notification message layout
  firmware: arm_ffa: Keep framework RX release under lock
  firmware: arm_ffa: Bound PARTITION_INFO_GET_REGS copies
  firmware: arm_ffa: Unregister bus notifier on teardown for FF-A v1.0
  firmware: arm_ffa: Fix per-vcpu self notifications handling in workqueue
  firmware: arm_ffa: Avoid collapsing NPI work from different CPUs
  firmware: arm_ffa: Skip free_pages on RX buffer alloc failure
  firmware: arm_ffa: Check for NULL FF-A ID table while driver registration

Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Arnd Bergmann 2026-05-07 14:06:53 +02:00
commit d1f29ea139
2 changed files with 105 additions and 45 deletions

View File

@ -26,6 +26,8 @@ static int ffa_device_match(struct device *dev, const struct device_driver *drv)
id_table = to_ffa_driver(drv)->id_table;
ffa_dev = to_ffa_dev(dev);
if (!id_table)
return 0;
while (!uuid_is_null(&id_table->uuid)) {
/*
@ -123,7 +125,7 @@ int ffa_driver_register(struct ffa_driver *driver, struct module *owner,
{
int ret;
if (!driver->probe)
if (!driver->probe || !driver->id_table)
return -EINVAL;
driver->driver.bus = &ffa_bus_type;

View File

@ -87,6 +87,7 @@ static inline int ffa_to_linux_errno(int errno)
struct ffa_pcpu_irq {
struct ffa_drv_info *info;
struct work_struct notif_pcpu_work;
};
struct ffa_drv_info {
@ -100,13 +101,13 @@ struct ffa_drv_info {
bool mem_ops_native;
bool msg_direct_req2_supp;
bool bitmap_created;
bool bus_notifier_registered;
bool notif_enabled;
unsigned int sched_recv_irq;
unsigned int notif_pend_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 sched_recv_irq_work;
struct xarray partition_info;
DECLARE_HASHTABLE(notifier_hash, ilog2(FFA_MAX_NOTIFICATIONS));
@ -322,6 +323,12 @@ __ffa_partition_info_get(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
#define PART_INFO_ID_MASK GENMASK(15, 0)
#define PART_INFO_EXEC_CXT_MASK GENMASK(31, 16)
#define PART_INFO_PROPS_MASK GENMASK(63, 32)
#define FFA_PART_INFO_GET_REGS_FIRST_REG 3
#define FFA_PART_INFO_GET_REGS_REGS_PER_DESC 3
#define FFA_PART_INFO_GET_REGS_MAX_DESC \
(((sizeof(ffa_value_t) / sizeof_field(ffa_value_t, a0)) - \
FFA_PART_INFO_GET_REGS_FIRST_REG) / \
FFA_PART_INFO_GET_REGS_REGS_PER_DESC)
#define PART_INFO_ID(x) ((u16)(FIELD_GET(PART_INFO_ID_MASK, (x))))
#define PART_INFO_EXEC_CXT(x) ((u16)(FIELD_GET(PART_INFO_EXEC_CXT_MASK, (x))))
#define PART_INFO_PROPERTIES(x) ((u32)(FIELD_GET(PART_INFO_PROPS_MASK, (x))))
@ -329,15 +336,13 @@ static int
__ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
struct ffa_partition_info *buffer, int num_parts)
{
u16 buf_sz, start_idx, cur_idx, count = 0, prev_idx = 0, tag = 0;
u16 buf_sz, start_idx = 0, cur_idx, count = 0, tag = 0;
struct ffa_partition_info *buf = buffer;
ffa_value_t partition_info;
do {
__le64 *regs;
int idx;
start_idx = prev_idx ? prev_idx + 1 : 0;
int idx, nr_desc, buf_idx;
invoke_ffa_fn((ffa_value_t){
.a0 = FFA_PARTITION_INFO_GET_REGS,
@ -353,15 +358,28 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
count = PARTITION_COUNT(partition_info.a2);
if (!buffer || !num_parts) /* count only */
return count;
if (count > num_parts)
return -EINVAL;
cur_idx = CURRENT_INDEX(partition_info.a2);
if (cur_idx < start_idx || cur_idx >= count)
return -EINVAL;
nr_desc = cur_idx - start_idx + 1;
if (nr_desc > FFA_PART_INFO_GET_REGS_MAX_DESC)
return -EINVAL;
buf_idx = buf - buffer;
if (buf_idx + nr_desc > num_parts)
return -EINVAL;
tag = UUID_INFO_TAG(partition_info.a2);
buf_sz = PARTITION_INFO_SZ(partition_info.a2);
if (buf_sz > sizeof(*buffer))
buf_sz = sizeof(*buffer);
regs = (void *)&partition_info.a3;
for (idx = 0; idx < cur_idx - start_idx + 1; idx++, buf++) {
for (idx = 0; idx < nr_desc; idx++, buf++) {
union {
uuid_t uuid;
u64 regs[2];
@ -379,7 +397,7 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
uuid_copy(&buf->uuid, &uuid_regs.uuid);
regs += 3;
}
prev_idx = cur_idx;
start_idx = cur_idx + 1;
} while (cur_idx < (count - 1));
@ -1189,7 +1207,7 @@ static int
ffa_sched_recv_cb_update(struct ffa_device *dev, ffa_sched_recv_cb callback,
void *cb_data, bool is_registration)
{
struct ffa_dev_part_info *partition = NULL, *tmp;
struct ffa_dev_part_info *partition = NULL;
struct list_head *phead;
bool cb_valid;
@ -1202,11 +1220,11 @@ ffa_sched_recv_cb_update(struct ffa_device *dev, ffa_sched_recv_cb callback,
return -EINVAL;
}
list_for_each_entry_safe(partition, tmp, phead, node)
list_for_each_entry(partition, phead, node)
if (partition->dev == dev)
break;
if (!partition) {
if (&partition->node == phead) {
pr_err("%s: No such partition ID 0x%x\n", __func__, dev->vm_id);
return -EINVAL;
}
@ -1445,20 +1463,25 @@ static int ffa_notify_send(struct ffa_device *dev, int notify_id,
static void handle_notif_callbacks(u64 bitmap, enum notify_type type)
{
ffa_notifier_cb cb;
void *cb_data;
int notify_id;
struct notifier_cb_info *cb_info = NULL;
for (notify_id = 0; notify_id <= FFA_MAX_NOTIFICATIONS && bitmap;
notify_id++, bitmap >>= 1) {
if (!(bitmap & 1))
continue;
read_lock(&drv_info->notify_lock);
cb_info = notifier_hnode_get_by_type(notify_id, type);
read_unlock(&drv_info->notify_lock);
scoped_guard(read_lock, &drv_info->notify_lock) {
struct notifier_cb_info *cb_info;
if (cb_info && cb_info->cb)
cb_info->cb(notify_id, cb_info->cb_data);
cb_info = notifier_hnode_get_by_type(notify_id, type);
cb = cb_info ? cb_info->cb : NULL;
cb_data = cb_info ? cb_info->cb_data : NULL;
}
if (cb)
cb(notify_id, cb_data);
}
}
@ -1466,39 +1489,56 @@ static void handle_fwk_notif_callbacks(u32 bitmap)
{
void *buf;
uuid_t uuid;
void *fwk_cb_data;
int notify_id = 0, target;
ffa_fwk_notifier_cb fwk_cb;
struct ffa_indirect_msg_hdr *msg;
struct notifier_cb_info *cb_info = NULL;
size_t min_offset = offsetof(struct ffa_indirect_msg_hdr, uuid);
/* Only one framework notification defined and supported for now */
if (!(bitmap & FRAMEWORK_NOTIFY_RX_BUFFER_FULL))
return;
mutex_lock(&drv_info->rx_lock);
scoped_guard(mutex, &drv_info->rx_lock) {
u32 offset, size;
msg = drv_info->rx_buffer;
buf = kmemdup((void *)msg + msg->offset, msg->size, GFP_KERNEL);
if (!buf) {
mutex_unlock(&drv_info->rx_lock);
return;
msg = drv_info->rx_buffer;
offset = msg->offset;
size = msg->size;
if (!size || (offset != min_offset && offset < sizeof(*msg)) ||
offset > drv_info->rxtx_bufsz ||
size > drv_info->rxtx_bufsz - offset) {
pr_err("invalid framework notification message\n");
ffa_rx_release();
return;
}
buf = kmemdup((void *)msg + offset, size, GFP_KERNEL);
if (!buf) {
ffa_rx_release();
return;
}
target = SENDER_ID(msg->send_recv_id);
if (offset >= sizeof(*msg))
uuid_copy(&uuid, &msg->uuid);
else
uuid_copy(&uuid, &uuid_null);
ffa_rx_release();
}
target = SENDER_ID(msg->send_recv_id);
if (msg->offset >= sizeof(*msg))
uuid_copy(&uuid, &msg->uuid);
else
uuid_copy(&uuid, &uuid_null);
scoped_guard(read_lock, &drv_info->notify_lock) {
struct notifier_cb_info *cb_info;
mutex_unlock(&drv_info->rx_lock);
cb_info = notifier_hnode_get_by_vmid_uuid(notify_id, target,
&uuid);
fwk_cb = cb_info ? cb_info->fwk_cb : NULL;
fwk_cb_data = cb_info ? cb_info->cb_data : NULL;
}
ffa_rx_release();
read_lock(&drv_info->notify_lock);
cb_info = notifier_hnode_get_by_vmid_uuid(notify_id, target, &uuid);
read_unlock(&drv_info->notify_lock);
if (cb_info && cb_info->fwk_cb)
cb_info->fwk_cb(notify_id, cb_info->cb_data, buf);
if (fwk_cb)
fwk_cb(notify_id, fwk_cb_data, buf);
kfree(buf);
}
@ -1539,10 +1579,11 @@ ffa_self_notif_handle(u16 vcpu, bool is_per_vcpu, void *cb_data)
static void notif_pcpu_irq_work_fn(struct work_struct *work)
{
struct ffa_drv_info *info = container_of(work, struct ffa_drv_info,
struct ffa_pcpu_irq *pcpu = container_of(work, struct ffa_pcpu_irq,
notif_pcpu_work);
struct ffa_drv_info *info = pcpu->info;
ffa_self_notif_handle(smp_processor_id(), true, info);
notif_get_and_handle(info);
}
static const struct ffa_info_ops ffa_drv_info_ops = {
@ -1629,6 +1670,15 @@ static struct notifier_block ffa_bus_nb = {
.notifier_call = ffa_bus_notifier,
};
static void ffa_bus_notifier_unregister(void)
{
if (!drv_info->bus_notifier_registered)
return;
bus_unregister_notifier(&ffa_bus_type, &ffa_bus_nb);
drv_info->bus_notifier_registered = false;
}
static int ffa_xa_add_partition_info(struct ffa_device *dev)
{
struct ffa_dev_part_info *info;
@ -1712,6 +1762,8 @@ static void ffa_partitions_cleanup(void)
struct list_head *phead;
unsigned long idx;
ffa_bus_notifier_unregister();
/* Clean up/free all registered devices */
ffa_devices_unregister();
@ -1739,11 +1791,14 @@ static int ffa_setup_partitions(void)
ret = bus_register_notifier(&ffa_bus_type, &ffa_bus_nb);
if (ret)
pr_err("Failed to register FF-A bus notifiers\n");
else
drv_info->bus_notifier_registered = true;
}
count = ffa_partition_probe(&uuid_null, &pbuf);
if (count <= 0) {
pr_info("%s: No partitions found, error %d\n", __func__, count);
ffa_bus_notifier_unregister();
return -EINVAL;
}
@ -1811,7 +1866,7 @@ static irqreturn_t notif_pend_irq_handler(int irq, void *irq_data)
struct ffa_drv_info *info = pcpu->info;
queue_work_on(smp_processor_id(), info->notif_pcpu_wq,
&info->notif_pcpu_work);
&pcpu->notif_pcpu_work);
return IRQ_HANDLED;
}
@ -1928,8 +1983,11 @@ static int ffa_init_pcpu_irq(void)
if (!irq_pcpu)
return -ENOMEM;
for_each_present_cpu(cpu)
for_each_present_cpu(cpu) {
per_cpu_ptr(irq_pcpu, cpu)->info = drv_info;
INIT_WORK(&per_cpu_ptr(irq_pcpu, cpu)->notif_pcpu_work,
notif_pcpu_irq_work_fn);
}
drv_info->irq_pcpu = irq_pcpu;
@ -1958,7 +2016,6 @@ static int ffa_init_pcpu_irq(void)
}
INIT_WORK(&drv_info->sched_recv_irq_work, ffa_sched_recv_irq_work_fn);
INIT_WORK(&drv_info->notif_pcpu_work, notif_pcpu_irq_work_fn);
drv_info->notif_pcpu_wq = create_workqueue("ffa_pcpu_irq_notification");
if (!drv_info->notif_pcpu_wq)
return -EINVAL;
@ -2063,11 +2120,12 @@ static int __init ffa_init(void)
rxtx_bufsz = SZ_4K;
}
rxtx_bufsz = PAGE_ALIGN(rxtx_bufsz);
drv_info->rxtx_bufsz = rxtx_bufsz;
drv_info->rx_buffer = alloc_pages_exact(rxtx_bufsz, GFP_KERNEL);
if (!drv_info->rx_buffer) {
ret = -ENOMEM;
goto free_pages;
goto free_drv_info;
}
drv_info->tx_buffer = alloc_pages_exact(rxtx_bufsz, GFP_KERNEL);
@ -2078,7 +2136,7 @@ static int __init ffa_init(void)
ret = ffa_rxtx_map(virt_to_phys(drv_info->tx_buffer),
virt_to_phys(drv_info->rx_buffer),
PAGE_ALIGN(rxtx_bufsz) / FFA_PAGE_SIZE);
rxtx_bufsz / FFA_PAGE_SIZE);
if (ret) {
pr_err("failed to register FFA RxTx buffers\n");
goto free_pages;