| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * DIBS - Direct Internal Buffer Sharing | |
| * | |
| * Implementation of the DIBS class module | |
| * | |
| * Copyright IBM Corp. 2025 | |
| */ | |
| #define pr_fmt(fmt) "dibs: " 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 const struct class dibs_class = { | |
| .name = "dibs", | |
| }; | |
| /* 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->dmb_clientid_arr); | |
| kfree(dibs); | |
| } | |
| struct dibs_dev *dibs_dev_alloc(void) | |
| { | |
| struct dibs_dev *dibs; | |
| dibs = kzalloc_obj(*dibs); | |
| if (!dibs) | |
| return dibs; | |
| spin_lock_init(&dibs->lock); | |
| 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(); | |
| 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) | |
| return ret; | |
| 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"); | |
| device_del(&dibs->dev); | |
| return ret; | |
| } | |
| 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; | |
| } | |
| 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); | |
| } | |
| EXPORT_SYMBOL_GPL(dibs_dev_del); | |
| static int __init dibs_init(void) | |
| { | |
| int rc; | |
| rc = class_register(&dibs_class); | |
| if (rc) | |
| goto err; | |
| rc = dibs_loopback_init(); | |
| if (rc) | |
| goto err_unregister; | |
| return rc; | |
| err_unregister: | |
| class_unregister(&dibs_class); | |
| err: | |
| pr_err("%s fails with %d\n", __func__, rc); | |
| return rc; | |
| } | |
| static void __exit dibs_exit(void) | |
| { | |
| dibs_loopback_exit(); | |
| class_unregister(&dibs_class); | |
| } | |
| subsys_initcall(dibs_init); | |
| module_exit(dibs_exit); |