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