| // 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" |
| |
| #define SCMI_UEVENT_MODALIAS_FMT "arm_ffa:%04x:%pUb" |
| |
| static DEFINE_IDA(ffa_bus_id); |
| |
| static int ffa_device_match(struct device *dev, const 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 match it unconditionally here and handle |
| * it via the installed bus notifier during driver binding. |
| */ |
| if (uuid_is_null(&ffa_dev->uuid)) |
| return 1; |
| |
| 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); |
| |
| /* UUID can be still NULL with FF-A v1.0, so just skip probing them */ |
| if (uuid_is_null(&ffa_dev->uuid)) |
| return -ENODEV; |
| |
| 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=" SCMI_UEVENT_MODALIAS_FMT, |
| ffa_dev->vm_id, &ffa_dev->uuid); |
| } |
| |
| static ssize_t modalias_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ffa_device *ffa_dev = to_ffa_dev(dev); |
| |
| return sysfs_emit(buf, SCMI_UEVENT_MODALIAS_FMT, ffa_dev->vm_id, |
| &ffa_dev->uuid); |
| } |
| static DEVICE_ATTR_RO(modalias); |
| |
| 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, |
| &dev_attr_modalias.attr, |
| NULL, |
| }; |
| ATTRIBUTE_GROUPS(ffa_device_attributes); |
| |
| const 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); |
| |
| static int __init arm_ffa_bus_init(void) |
| { |
| return bus_register(&ffa_bus_type); |
| } |
| subsys_initcall(arm_ffa_bus_init); |
| |
| static void __exit arm_ffa_bus_exit(void) |
| { |
| ffa_devices_unregister(); |
| bus_unregister(&ffa_bus_type); |
| ida_destroy(&ffa_bus_id); |
| } |
| module_exit(arm_ffa_bus_exit); |
| |
| MODULE_ALIAS("ffa-core"); |
| MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); |
| MODULE_DESCRIPTION("ARM FF-A bus"); |
| MODULE_LICENSE("GPL"); |