diff options
Diffstat (limited to 'drivers/dibs/dibs_main.c')
-rw-r--r-- | drivers/dibs/dibs_main.c | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/drivers/dibs/dibs_main.c b/drivers/dibs/dibs_main.c new file mode 100644 index 000000000000..0374f8350ff7 --- /dev/null +++ b/drivers/dibs/dibs_main.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DIBS - Direct Internal Buffer Sharing + * + * Implementation of the DIBS class module + * + * Copyright IBM Corp. 2025 + */ +#define KMSG_COMPONENT "dibs" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/dibs.h> + +#include "dibs_loopback.h" + +MODULE_DESCRIPTION("Direct Internal Buffer Sharing class"); +MODULE_LICENSE("GPL"); + +static struct class *dibs_class; + +/* use an array rather a list for fast mapping: */ +static struct dibs_client *clients[MAX_DIBS_CLIENTS]; +static u8 max_client; +static DEFINE_MUTEX(clients_lock); +struct dibs_dev_list { + struct list_head list; + struct mutex mutex; /* protects dibs device list */ +}; + +static struct dibs_dev_list dibs_dev_list = { + .list = LIST_HEAD_INIT(dibs_dev_list.list), + .mutex = __MUTEX_INITIALIZER(dibs_dev_list.mutex), +}; + +static void dibs_setup_forwarding(struct dibs_client *client, + struct dibs_dev *dibs) +{ + unsigned long flags; + + spin_lock_irqsave(&dibs->lock, flags); + dibs->subs[client->id] = client; + spin_unlock_irqrestore(&dibs->lock, flags); +} + +int dibs_register_client(struct dibs_client *client) +{ + struct dibs_dev *dibs; + int i, rc = -ENOSPC; + + mutex_lock(&dibs_dev_list.mutex); + mutex_lock(&clients_lock); + for (i = 0; i < MAX_DIBS_CLIENTS; ++i) { + if (!clients[i]) { + clients[i] = client; + client->id = i; + if (i == max_client) + max_client++; + rc = 0; + break; + } + } + mutex_unlock(&clients_lock); + + if (i < MAX_DIBS_CLIENTS) { + /* initialize with all devices that we got so far */ + list_for_each_entry(dibs, &dibs_dev_list.list, list) { + dibs->priv[i] = NULL; + client->ops->add_dev(dibs); + dibs_setup_forwarding(client, dibs); + } + } + mutex_unlock(&dibs_dev_list.mutex); + + return rc; +} +EXPORT_SYMBOL_GPL(dibs_register_client); + +int dibs_unregister_client(struct dibs_client *client) +{ + struct dibs_dev *dibs; + unsigned long flags; + int max_dmbs; + int rc = 0; + + mutex_lock(&dibs_dev_list.mutex); + list_for_each_entry(dibs, &dibs_dev_list.list, list) { + spin_lock_irqsave(&dibs->lock, flags); + max_dmbs = dibs->ops->max_dmbs(); + for (int i = 0; i < max_dmbs; ++i) { + if (dibs->dmb_clientid_arr[i] == client->id) { + WARN(1, "%s: attempt to unregister '%s' with registered dmb(s)\n", + __func__, client->name); + rc = -EBUSY; + goto err_reg_dmb; + } + } + /* Stop forwarding IRQs and events */ + dibs->subs[client->id] = NULL; + spin_unlock_irqrestore(&dibs->lock, flags); + clients[client->id]->ops->del_dev(dibs); + dibs->priv[client->id] = NULL; + } + + mutex_lock(&clients_lock); + clients[client->id] = NULL; + if (client->id + 1 == max_client) + max_client--; + mutex_unlock(&clients_lock); + + mutex_unlock(&dibs_dev_list.mutex); + return rc; + +err_reg_dmb: + spin_unlock_irqrestore(&dibs->lock, flags); + mutex_unlock(&dibs_dev_list.mutex); + return rc; +} +EXPORT_SYMBOL_GPL(dibs_unregister_client); + +static void dibs_dev_release(struct device *dev) +{ + struct dibs_dev *dibs; + + dibs = container_of(dev, struct dibs_dev, dev); + + kfree(dibs); +} + +struct dibs_dev *dibs_dev_alloc(void) +{ + struct dibs_dev *dibs; + + dibs = kzalloc(sizeof(*dibs), GFP_KERNEL); + if (!dibs) + return dibs; + dibs->dev.release = dibs_dev_release; + dibs->dev.class = dibs_class; + device_initialize(&dibs->dev); + + return dibs; +} +EXPORT_SYMBOL_GPL(dibs_dev_alloc); + +static ssize_t gid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dibs_dev *dibs; + + dibs = container_of(dev, struct dibs_dev, dev); + + return sysfs_emit(buf, "%pUb\n", &dibs->gid); +} +static DEVICE_ATTR_RO(gid); + +static ssize_t fabric_id_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dibs_dev *dibs; + u16 fabric_id; + + dibs = container_of(dev, struct dibs_dev, dev); + fabric_id = dibs->ops->get_fabric_id(dibs); + + return sysfs_emit(buf, "0x%04x\n", fabric_id); +} +static DEVICE_ATTR_RO(fabric_id); + +static struct attribute *dibs_dev_attrs[] = { + &dev_attr_gid.attr, + &dev_attr_fabric_id.attr, + NULL, +}; + +static const struct attribute_group dibs_dev_attr_group = { + .attrs = dibs_dev_attrs, +}; + +int dibs_dev_add(struct dibs_dev *dibs) +{ + int max_dmbs; + int i, ret; + + max_dmbs = dibs->ops->max_dmbs(); + spin_lock_init(&dibs->lock); + dibs->dmb_clientid_arr = kzalloc(max_dmbs, GFP_KERNEL); + if (!dibs->dmb_clientid_arr) + return -ENOMEM; + memset(dibs->dmb_clientid_arr, NO_DIBS_CLIENT, max_dmbs); + + ret = device_add(&dibs->dev); + if (ret) + goto free_client_arr; + + ret = sysfs_create_group(&dibs->dev.kobj, &dibs_dev_attr_group); + if (ret) { + dev_err(&dibs->dev, "sysfs_create_group failed for dibs_dev\n"); + goto err_device_del; + } + mutex_lock(&dibs_dev_list.mutex); + mutex_lock(&clients_lock); + for (i = 0; i < max_client; ++i) { + if (clients[i]) { + clients[i]->ops->add_dev(dibs); + dibs_setup_forwarding(clients[i], dibs); + } + } + mutex_unlock(&clients_lock); + list_add(&dibs->list, &dibs_dev_list.list); + mutex_unlock(&dibs_dev_list.mutex); + + return 0; + +err_device_del: + device_del(&dibs->dev); +free_client_arr: + kfree(dibs->dmb_clientid_arr); + return ret; + +} +EXPORT_SYMBOL_GPL(dibs_dev_add); + +void dibs_dev_del(struct dibs_dev *dibs) +{ + unsigned long flags; + int i; + + sysfs_remove_group(&dibs->dev.kobj, &dibs_dev_attr_group); + + spin_lock_irqsave(&dibs->lock, flags); + for (i = 0; i < MAX_DIBS_CLIENTS; ++i) + dibs->subs[i] = NULL; + spin_unlock_irqrestore(&dibs->lock, flags); + + mutex_lock(&dibs_dev_list.mutex); + mutex_lock(&clients_lock); + for (i = 0; i < max_client; ++i) { + if (clients[i]) + clients[i]->ops->del_dev(dibs); + } + mutex_unlock(&clients_lock); + list_del_init(&dibs->list); + mutex_unlock(&dibs_dev_list.mutex); + + device_del(&dibs->dev); + kfree(dibs->dmb_clientid_arr); +} +EXPORT_SYMBOL_GPL(dibs_dev_del); + +static int __init dibs_init(void) +{ + int rc; + + memset(clients, 0, sizeof(clients)); + max_client = 0; + + dibs_class = class_create("dibs"); + if (IS_ERR(dibs_class)) + return PTR_ERR(dibs_class); + + rc = dibs_loopback_init(); + if (rc) + pr_err("%s fails with %d\n", __func__, rc); + + return rc; +} + +static void __exit dibs_exit(void) +{ + dibs_loopback_exit(); + class_destroy(dibs_class); +} + +module_init(dibs_init); +module_exit(dibs_exit); |