linux-imx/drivers/firmware/arm_ffa/bus.c
Sudeep Holla c153ef01dd firmware: arm_ffa: Assign the missing IDR allocation ID to the FFA device
[ Upstream commit 7d0bc6360f ]

Commit 19b8766459 ("firmware: arm_ffa: Fix FFA device names for logical
partitions") added an ID to the FFA device using ida_alloc() and append
the same to "arm-ffa" to make up a unique device name. However it missed
to stash the id value in ffa_dev to help freeing the ID later when the
device is destroyed.

Due to the missing/unassigned ID in FFA device, we get the following
warning when the FF-A device is unregistered.

  |   ida_free called for id=0 which is not allocated.
  |   WARNING: CPU: 7 PID: 1 at lib/idr.c:525 ida_free+0x114/0x164
  |   CPU: 7 PID: 1 Comm: swapper/0 Not tainted 6.6.0-rc4 #209
  |   pstate: 61400009 (nZCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--)
  |   pc : ida_free+0x114/0x164
  |   lr : ida_free+0x114/0x164
  |   Call trace:
  |    ida_free+0x114/0x164
  |    ffa_release_device+0x24/0x3c
  |    device_release+0x34/0x8c
  |    kobject_put+0x94/0xf8
  |    put_device+0x18/0x24
  |    klist_devices_put+0x14/0x20
  |    klist_next+0xc8/0x114
  |    bus_for_each_dev+0xd8/0x144
  |    arm_ffa_bus_exit+0x30/0x54
  |    ffa_init+0x68/0x330
  |    do_one_initcall+0xdc/0x250
  |    do_initcall_level+0x8c/0xac
  |    do_initcalls+0x54/0x94
  |    do_basic_setup+0x1c/0x28
  |    kernel_init_freeable+0x104/0x170
  |    kernel_init+0x20/0x1a0
  |    ret_from_fork+0x10/0x20

Fix the same by actually assigning the ID in the FFA device this time
for real.

Fixes: 19b8766459 ("firmware: arm_ffa: Fix FFA device names for logical partitions")
Link: https://lore.kernel.org/r/20231003085932.3553985-1-sudeep.holla@arm.com
Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
2023-11-20 11:59:16 +01:00

233 lines
4.9 KiB
C

// 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 DEFINE_IDA(ffa_bus_id);
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 void ffa_device_remove(struct device *dev)
{
struct ffa_driver *ffa_drv = to_ffa_driver(dev->driver);
if (ffa_drv->remove)
ffa_drv->remove(to_ffa_dev(dev));
}
static int ffa_device_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const 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);
ida_free(&ffa_bus_id, ffa_dev->id);
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,
const struct ffa_ops *ops)
{
int id, ret;
struct device *dev;
struct ffa_device *ffa_dev;
id = ida_alloc_min(&ffa_bus_id, 1, GFP_KERNEL);
if (id < 0)
return NULL;
ffa_dev = kzalloc(sizeof(*ffa_dev), GFP_KERNEL);
if (!ffa_dev) {
ida_free(&ffa_bus_id, id);
return NULL;
}
dev = &ffa_dev->dev;
dev->bus = &ffa_bus_type;
dev->release = ffa_release_device;
dev_set_name(&ffa_dev->dev, "arm-ffa-%d", id);
ffa_dev->id = id;
ffa_dev->vm_id = vm_id;
ffa_dev->ops = ops;
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);
ida_destroy(&ffa_bus_id);
}