|  | // 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); |