Unverified Commit d1f29ea1 authored by Arnd Bergmann's avatar Arnd Bergmann
Browse files

Merge tag 'ffa-fixes-7.1-2' of...

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: default avatarArnd Bergmann <arnd@arndb.de>
parents 79524bed a6848a50
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -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;
+101 −43
Original line number Diff line number Diff line
@@ -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);
		scoped_guard(read_lock, &drv_info->notify_lock) {
			struct notifier_cb_info *cb_info;

			cb_info = notifier_hnode_get_by_type(notify_id, type);
		read_unlock(&drv_info->notify_lock);
			cb = cb_info ? cb_info->cb : NULL;
			cb_data = cb_info ? cb_info->cb_data : NULL;
		}

		if (cb_info && cb_info->cb)
			cb_info->cb(notify_id, cb_info->cb_data);
		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);
		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) {
		mutex_unlock(&drv_info->rx_lock);
			ffa_rx_release();
			return;
		}

		target = SENDER_ID(msg->send_recv_id);
	if (msg->offset >= sizeof(*msg))
		if (offset >= sizeof(*msg))
			uuid_copy(&uuid, &msg->uuid);
		else
			uuid_copy(&uuid, &uuid_null);

	mutex_unlock(&drv_info->rx_lock);

		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);
	scoped_guard(read_lock, &drv_info->notify_lock) {
		struct notifier_cb_info *cb_info;

	if (cb_info && cb_info->fwk_cb)
		cb_info->fwk_cb(notify_id, cb_info->cb_data, buf);
		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;
	}

	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;