root/include/linux/dibs.h
/* SPDX-License-Identifier: GPL-2.0 */
/*
 *  Direct Internal Buffer Sharing
 *
 *  Definitions for the DIBS module
 *
 *  Copyright IBM Corp. 2025
 */
#ifndef _DIBS_H
#define _DIBS_H

#include <linux/device.h>
#include <linux/uuid.h>

/* DIBS - Direct Internal Buffer Sharing - concept
 * -----------------------------------------------
 * In the case of multiple system sharing the same hardware, dibs fabrics can
 * provide dibs devices to these systems. The systems use dibs devices of the
 * same fabric to communicate via dmbs (Direct Memory Buffers). Each dmb has
 * exactly one owning local dibs device and one remote using dibs device, that
 * is authorized to write into this dmb. This access control is provided by the
 * dibs fabric.
 *
 * Because the access to the dmb is based on access to physical memory, it is
 * lossless and synchronous. The remote devices can directly access any offset
 * of the dmb.
 *
 * Dibs fabrics, dibs devices and dmbs are identified by tokens and ids.
 * Dibs fabric id is unique within the same hardware (with the exception of the
 * dibs loopback fabric), dmb token is unique within the same fabric, dibs
 * device gids are guaranteed to be unique within the same fabric and
 * statistically likely to be globally unique. The exchange of these tokens and
 * ids between the systems is not part of the dibs concept.
 *
 * The dibs layer provides an abstraction between dibs device drivers and dibs
 * clients.
 */

/* DMB - Direct Memory Buffer
 * --------------------------
 * A dibs client provides a dmb as input buffer for a local receiving
 * dibs device for exactly one (remote) sending dibs device. Only this
 * sending device can send data into this dmb using move_data(). Sender
 * and receiver can be the same device. A dmb belongs to exactly one client.
 */
struct dibs_dmb {
        /* tok - Token for this dmb
         * Used by remote and local devices and clients to address this dmb.
         * Provided by dibs fabric. Unique per dibs fabric.
         */
        u64 dmb_tok;
        /* rgid - GID of designated remote sending device */
        uuid_t rgid;
        /* cpu_addr - buffer address */
        void *cpu_addr;
        /* len - buffer length */
        u32 dmb_len;
        /* idx - Index of this DMB on this receiving device */
        u32 idx;
        /* VLAN support (deprecated)
         * In order to write into a vlan-tagged dmb, the remote device needs
         * to belong to the this vlan
         */
        u32 vlan_valid;
        u32 vlan_id;
        /* optional, used by device driver */
        dma_addr_t dma_addr;
};

/* DIBS events
 * -----------
 * Dibs devices can optionally notify dibs clients about events that happened
 * in the fabric or at the remote device or remote dmb.
 */
enum dibs_event_type {
        /* Buffer event, e.g. a remote dmb was unregistered */
        DIBS_BUF_EVENT,
        /* Device event, e.g. a remote dibs device was disabled */
        DIBS_DEV_EVENT,
        /* Software event, a dibs client can send an event signal to a
         * remote dibs device.
         */
        DIBS_SW_EVENT,
        DIBS_OTHER_TYPE };

enum dibs_event_subtype {
        DIBS_BUF_UNREGISTERED,
        DIBS_DEV_DISABLED,
        DIBS_DEV_ERR_STATE,
        DIBS_OTHER_SUBTYPE
};

struct dibs_event {
        u32 type;
        u32 subtype;
        /* uuid_null if invalid */
        uuid_t gid;
        /* zero if invalid */
        u64 buffer_tok;
        u64 time;
        /* additional data or zero */
        u64 data;
};

struct dibs_dev;

/* DIBS client
 * -----------
 */
#define MAX_DIBS_CLIENTS        8
#define NO_DIBS_CLIENT          0xff
/* All dibs clients have access to all dibs devices.
 * A dibs client provides the following functions to be called by dibs layer or
 * dibs device drivers:
 */
struct dibs_client_ops {
        /**
         *  add_dev() - add a dibs device
         *  @dev: device that was added
         *
         * Will be called during dibs_register_client() for all existing
         * dibs devices and whenever a new dibs device is registered.
         * dev is usable until dibs_client.remove() is called.
         * *dev is protected by device refcounting.
         */
        void (*add_dev)(struct dibs_dev *dev);
        /**
         * del_dev() - remove a dibs device
         * @dev: device to be removed
         *
         * Will be called whenever a dibs device is removed.
         * Will be called during dibs_unregister_client() for all existing
         * dibs devices and whenever a dibs device is unregistered.
         * The device has already stopped initiative for this client:
         * No new handlers will be started.
         * The device is no longer usable by this client after this call.
         */
        void (*del_dev)(struct dibs_dev *dev);
        /**
         * handle_irq() - Handle signaling for a DMB
         * @dev: device that owns the dmb
         * @idx: Index of the dmb that got signalled
         * @dmbemask: signaling mask of the dmb
         *
         * Handle signaling for a dmb that was registered by this client
         * for this device.
         * The dibs device can coalesce multiple signaling triggers into a
         * single call of handle_irq(). dmbemask can be used to indicate
         * different kinds of triggers.
         *
         * Context: Called in IRQ context by dibs device driver
         */
        void (*handle_irq)(struct dibs_dev *dev, unsigned int idx,
                           u16 dmbemask);
        /**
         * handle_event() - Handle control information sent by device
         * @dev: device reporting the event
         * @event: ism event structure
         *
         * * Context: Called in IRQ context by dibs device driver
         */
        void (*handle_event)(struct dibs_dev *dev,
                             const struct dibs_event *event);
};

struct dibs_client {
        /* client name for logging and debugging purposes */
        const char *name;
        const struct dibs_client_ops *ops;
        /* client index - provided and used by dibs layer */
        u8 id;
};

/* Functions to be called by dibs clients:
 */
/**
 * dibs_register_client() - register a client with dibs layer
 * @client: this client
 *
 * Will call client->ops->add_dev() for all existing dibs devices.
 * Return: zero on success.
 */
int dibs_register_client(struct dibs_client *client);
/**
 * dibs_unregister_client() - unregister a client with dibs layer
 * @client: this client
 *
 * Will call client->ops->del_dev() for all existing dibs devices.
 * Return: zero on success.
 */
int dibs_unregister_client(struct dibs_client *client);

/* dibs clients can call dibs device ops. */

/* DIBS devices
 * ------------
 */

/* Defined fabric id / CHID for all loopback devices:
 * All dibs loopback devices report this fabric id. In this case devices with
 * the same fabric id can NOT communicate via dibs. Only loopback devices with
 * the same dibs device gid can communicate (=same device with itself).
 */
#define DIBS_LOOPBACK_FABRIC    0xFFFF

/* A dibs device provides the following functions to be called by dibs clients.
 * They are mandatory, unless marked 'optional'.
 */
struct dibs_dev_ops {
        /**
         * get_fabric_id()
         * @dev: local dibs device
         *
         * Only devices on the same dibs fabric can communicate. Fabric_id is
         * unique inside the same HW system. Use fabric_id for fast negative
         * checks, but only query_remote_gid() can give a reliable positive
         * answer:
         * Different fabric_id: dibs is not possible
         * Same fabric_id: dibs may be possible or not
         *                 (e.g. different HW systems)
         * EXCEPTION: DIBS_LOOPBACK_FABRIC denotes an ism_loopback device
         *            that can only communicate with itself. Use dibs_dev.gid
         *            or query_remote_gid()to determine whether sender and
         *            receiver use the same ism_loopback device.
         * Return: 2 byte dibs fabric id
         */
        u16 (*get_fabric_id)(struct dibs_dev *dev);
        /**
         * query_remote_gid()
         * @dev: local dibs device
         * @rgid: gid of remote dibs device
         * @vid_valid: if zero, vid will be ignored;
         *             deprecated, ignored if device does not support vlan
         * @vid: VLAN id; deprecated, ignored if device does not support vlan
         *
         * Query whether a remote dibs device is reachable via this local device
         * and this vlan id.
         * Return: 0 if remote gid is reachable.
         */
        int (*query_remote_gid)(struct dibs_dev *dev, const uuid_t *rgid,
                                u32 vid_valid, u32 vid);
        /**
         * max_dmbs()
         * Return: Max number of DMBs that can be registered for this kind of
         *         dibs_dev
         */
        int (*max_dmbs)(void);
        /**
         * register_dmb() - allocate and register a dmb
         * @dev: dibs device
         * @dmb: dmb struct to be registered
         * @client: dibs client
         * @vid: VLAN id; deprecated, ignored if device does not support vlan
         *
         * The following fields of dmb must provide valid input:
         *      @rgid: gid of remote user device
         *      @dmb_len: buffer length
         *      @idx: Optionally:requested idx (if non-zero)
         *      @vlan_valid: if zero, vlan_id will be ignored;
         *                   deprecated, ignored if device does not support vlan
         *      @vlan_id: deprecated, ignored if device does not support vlan
         * Upon return in addition the following fields will be valid:
         *      @dmb_tok: for usage by remote and local devices and clients
         *      @cpu_addr: allocated buffer
         *      @idx: dmb index, unique per dibs device
         *      @dma_addr: to be used by device driver,if applicable
         *
         * Allocate a dmb buffer and register it with this device and for this
         * client.
         * Return: zero on success
         */
        int (*register_dmb)(struct dibs_dev *dev, struct dibs_dmb *dmb,
                            struct dibs_client *client);
        /**
         * unregister_dmb() - unregister and free a dmb
         * @dev: dibs device
         * @dmb: dmb struct to be unregistered
         * The following fields of dmb must provide valid input:
         *      @dmb_tok
         *      @cpu_addr
         *      @idx
         *
         * Free dmb.cpu_addr and unregister the dmb from this device.
         * Return: zero on success
         */
        int (*unregister_dmb)(struct dibs_dev *dev, struct dibs_dmb *dmb);
        /**
         * move_data() - write into a remote dmb
         * @dev: Local sending dibs device
         * @dmb_tok: Token of the remote dmb
         * @idx: signaling index in dmbemask
         * @sf: signaling flag;
         *      if true, idx will be turned on at target dmbemask mask
         *      and target device will be signaled.
         * @offset: offset within target dmb
         * @data: pointer to data to be sent
         * @size: length of data to be sent, can be zero.
         *
         * Use dev to write data of size at offset into a remote dmb
         * identified by dmb_tok. Data is moved synchronously, *data can
         * be freed when this function returns.
         *
         * If signaling flag (sf) is true, bit number idx bit will be turned
         * on in the dmbemask mask when handle_irq() is called at the remote
         * dibs client that owns the target dmb. The target device may chose
         * to coalesce the signaling triggers of multiple move_data() calls
         * to the same target dmb into a single handle_irq() call.
         * Return: zero on success
         */
        int (*move_data)(struct dibs_dev *dev, u64 dmb_tok, unsigned int idx,
                         bool sf, unsigned int offset, void *data,
                         unsigned int size);
        /**
         * add_vlan_id() - add dibs device to vlan (optional, deprecated)
         * @dev: dibs device
         * @vlan_id: vlan id
         *
         * In order to write into a vlan-tagged dmb, the remote device needs
         * to belong to the this vlan. A device can belong to more than 1 vlan.
         * Any device can access an untagged dmb.
         * Deprecated, only supported for backwards compatibility.
         * Return: zero on success
         */
        int (*add_vlan_id)(struct dibs_dev *dev, u64 vlan_id);
        /**
         * del_vlan_id() - remove dibs device from vlan (optional, deprecated)
         * @dev: dibs device
         * @vlan_id: vlan id
         * Return: zero on success
         */
        int (*del_vlan_id)(struct dibs_dev *dev, u64 vlan_id);
        /**
         * signal_event() - trigger an event at a remote dibs device (optional)
         * @dev: local dibs device
         * @rgid: gid of remote dibs device
         * trigger_irq: zero: notification may be coalesced with other events
         *              non-zero: notify immediately
         * @subtype: 4 byte event code, meaning is defined by dibs client
         * @data: 8 bytes of additional information,
         *        meaning is defined by dibs client
         *
         * dibs devices can offer support for sending a control event of type
         * EVENT_SWR to a remote dibs device.
         * NOTE: handle_event() will be called for all registered dibs clients
         * at the remote device.
         * Return: zero on success
         */
        int (*signal_event)(struct dibs_dev *dev, const uuid_t *rgid,
                            u32 trigger_irq, u32 event_code, u64 info);
        /**
         * support_mmapped_rdmb() - can this device provide memory mapped
         *                          remote dmbs? (optional)
         * @dev: dibs device
         *
         * A dibs device can provide a kernel address + length, that represent
         * a remote target dmb (like MMIO). Alternatively to calling
         * move_data(), a dibs client can write into such a ghost-send-buffer
         * (= to this kernel address) and the data will automatically
         * immediately appear in the target dmb, even without calling
         * move_data().
         *
         * Either all 3 function pointers for support_dmb_nocopy(),
         * attach_dmb() and detach_dmb() are defined, or all of them must
         * be NULL.
         *
         * Return: non-zero, if memory mapped remote dmbs are supported.
         */
        int (*support_mmapped_rdmb)(struct dibs_dev *dev);
        /**
         * attach_dmb() - attach local memory to a remote dmb
         * @dev: Local sending ism device
         * @dmb: all other parameters are passed in the form of a
         *       dmb struct
         *       TODO: (THIS IS CONFUSING, should be changed)
         *  dmb_tok: (in) Token of the remote dmb, we want to attach to
         *  cpu_addr: (out) MMIO address
         *  dma_addr: (out) MMIO address (if applicable, invalid otherwise)
         *  dmb_len: (out) length of local MMIO region,
         *           equal to length of remote DMB.
         *  sba_idx: (out) index of remote dmb (NOT HELPFUL, should be removed)
         *
         * Provides a memory address to the sender that can be used to
         * directly write into the remote dmb.
         * Memory is available until detach_dmb is called
         *
         * Return: Zero upon success, Error code otherwise
         */
        int (*attach_dmb)(struct dibs_dev *dev, struct dibs_dmb *dmb);
        /**
         * detach_dmb() - Detach the ghost buffer from a remote dmb
         * @dev: ism device
         * @token: dmb token of the remote dmb
         *
         * No need to free cpu_addr.
         *
         * Return: Zero upon success, Error code otherwise
         */
        int (*detach_dmb)(struct dibs_dev *dev, u64 token);
};

struct dibs_dev {
        struct list_head list;
        struct device dev;
        /* To be filled by device driver, before calling dibs_dev_add(): */
        const struct dibs_dev_ops *ops;
        uuid_t gid;
        /* priv pointer for device driver */
        void *drv_priv;

        /* priv pointer per client; for client usage only */
        void *priv[MAX_DIBS_CLIENTS];

        /* get this lock before accessing any of the fields below */
        spinlock_t lock;
        /* array of client ids indexed by dmb idx;
         * can be used as indices into priv and subs arrays
         */
        u8 *dmb_clientid_arr;
        /* Sparse array of all ISM clients */
        struct dibs_client *subs[MAX_DIBS_CLIENTS];
};

static inline void dibs_set_priv(struct dibs_dev *dev,
                                 struct dibs_client *client, void *priv)
{
        dev->priv[client->id] = priv;
}

static inline void *dibs_get_priv(struct dibs_dev *dev,
                                  struct dibs_client *client)
{
        return dev->priv[client->id];
}

/* ------- End of client-only functions ----------- */

/* Functions to be called by dibs device drivers:
 */
/**
 * dibs_dev_alloc() - allocate and reference device structure
 *
 * The following fields will be valid upon successful return: dev
 * NOTE: Use put_device(dibs_get_dev(@dibs)) to give up your reference instead
 * of freeing @dibs @dev directly once you have successfully called this
 * function.
 * Return: Pointer to dibs device structure
 */
struct dibs_dev *dibs_dev_alloc(void);
/**
 * dibs_dev_add() - register with dibs layer and all clients
 * @dibs: dibs device
 *
 * The following fields must be valid upon entry: dev, ops, drv_priv
 * All fields will be valid upon successful return.
 * Return: zero on success
 */
int dibs_dev_add(struct dibs_dev *dibs);
/**
 * dibs_dev_del() - unregister from dibs layer and all clients
 * @dibs: dibs device
 */
void dibs_dev_del(struct dibs_dev *dibs);

#endif  /* _DIBS_H */