232 lines
4.9 KiB
C
232 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->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);
|
|
}
|