linux/net/smc/smc_ism.c

// SPDX-License-Identifier: GPL-2.0
/* Shared Memory Communications Direct over ISM devices (SMC-D)
 *
 * Functions for ISM device.
 *
 * Copyright IBM Corp. 2018
 */

#include <linux/if_vlan.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <asm/page.h>

#include "smc.h"
#include "smc_core.h"
#include "smc_ism.h"
#include "smc_pnet.h"
#include "smc_netlink.h"
#include "linux/ism.h"

struct smcd_dev_list smcd_dev_list =;

static bool smc_ism_v2_capable;
static u8 smc_ism_v2_system_eid[SMC_MAX_EID_LEN];

#if IS_ENABLED(CONFIG_ISM)
static void smcd_register_dev(struct ism_dev *ism);
static void smcd_unregister_dev(struct ism_dev *ism);
static void smcd_handle_event(struct ism_dev *ism, struct ism_event *event);
static void smcd_handle_irq(struct ism_dev *ism, unsigned int dmbno,
			    u16 dmbemask);

static struct ism_client smc_ism_client = {
	.name = "SMC-D",
	.add = smcd_register_dev,
	.remove = smcd_unregister_dev,
	.handle_event = smcd_handle_event,
	.handle_irq = smcd_handle_irq,
};
#endif

static void smc_ism_create_system_eid(void)
{}

/* Test if an ISM communication is possible - same CPC */
int smc_ism_cantalk(struct smcd_gid *peer_gid, unsigned short vlan_id,
		    struct smcd_dev *smcd)
{}

void smc_ism_get_system_eid(u8 **eid)
{}

u16 smc_ism_get_chid(struct smcd_dev *smcd)
{}

/* HW supports ISM V2 and thus System EID is defined */
bool smc_ism_is_v2_capable(void)
{}

void smc_ism_set_v2_capable(void)
{}

/* Set a connection using this DMBE. */
void smc_ism_set_conn(struct smc_connection *conn)
{}

/* Unset a connection using this DMBE. */
void smc_ism_unset_conn(struct smc_connection *conn)
{}

/* Register a VLAN identifier with the ISM device. Use a reference count
 * and add a VLAN identifier only when the first DMB using this VLAN is
 * registered.
 */
int smc_ism_get_vlan(struct smcd_dev *smcd, unsigned short vlanid)
{}

/* Unregister a VLAN identifier with the ISM device. Use a reference count
 * and remove a VLAN identifier only when the last DMB using this VLAN is
 * unregistered.
 */
int smc_ism_put_vlan(struct smcd_dev *smcd, unsigned short vlanid)
{}

int smc_ism_unregister_dmb(struct smcd_dev *smcd, struct smc_buf_desc *dmb_desc)
{}

int smc_ism_register_dmb(struct smc_link_group *lgr, int dmb_len,
			 struct smc_buf_desc *dmb_desc)
{}

bool smc_ism_support_dmb_nocopy(struct smcd_dev *smcd)
{}

int smc_ism_attach_dmb(struct smcd_dev *dev, u64 token,
		       struct smc_buf_desc *dmb_desc)
{}

int smc_ism_detach_dmb(struct smcd_dev *dev, u64 token)
{}

static int smc_nl_handle_smcd_dev(struct smcd_dev *smcd,
				  struct sk_buff *skb,
				  struct netlink_callback *cb)
{}

static void smc_nl_prep_smcd_dev(struct smcd_dev_list *dev_list,
				 struct sk_buff *skb,
				 struct netlink_callback *cb)
{}

int smcd_nl_get_device(struct sk_buff *skb, struct netlink_callback *cb)
{}

#if IS_ENABLED(CONFIG_ISM)
struct smc_ism_event_work {
	struct work_struct work;
	struct smcd_dev *smcd;
	struct ism_event event;
};

#define ISM_EVENT_REQUEST
#define ISM_EVENT_RESPONSE
#define ISM_EVENT_REQUEST_IR
#define ISM_EVENT_CODE_SHUTDOWN
#define ISM_EVENT_CODE_TESTLINK

union smcd_sw_event_info {
	u64	info;
	struct {
		u8		uid[SMC_LGR_ID_SIZE];
		unsigned short	vlan_id;
		u16		code;
	};
};

static void smcd_handle_sw_event(struct smc_ism_event_work *wrk)
{
	struct smcd_gid peer_gid = { .gid = wrk->event.tok,
				     .gid_ext = 0 };
	union smcd_sw_event_info ev_info;

	ev_info.info = wrk->event.info;
	switch (wrk->event.code) {
	case ISM_EVENT_CODE_SHUTDOWN:	/* Peer shut down DMBs */
		smc_smcd_terminate(wrk->smcd, &peer_gid, ev_info.vlan_id);
		break;
	case ISM_EVENT_CODE_TESTLINK:	/* Activity timer */
		if (ev_info.code == ISM_EVENT_REQUEST &&
		    wrk->smcd->ops->signal_event) {
			ev_info.code = ISM_EVENT_RESPONSE;
			wrk->smcd->ops->signal_event(wrk->smcd,
						     &peer_gid,
						     ISM_EVENT_REQUEST_IR,
						     ISM_EVENT_CODE_TESTLINK,
						     ev_info.info);
			}
		break;
	}
}

/* worker for SMC-D events */
static void smc_ism_event_work(struct work_struct *work)
{
	struct smc_ism_event_work *wrk =
		container_of(work, struct smc_ism_event_work, work);
	struct smcd_gid smcd_gid = { .gid = wrk->event.tok,
				     .gid_ext = 0 };

	switch (wrk->event.type) {
	case ISM_EVENT_GID:	/* GID event, token is peer GID */
		smc_smcd_terminate(wrk->smcd, &smcd_gid, VLAN_VID_MASK);
		break;
	case ISM_EVENT_DMB:
		break;
	case ISM_EVENT_SWR:	/* Software defined event */
		smcd_handle_sw_event(wrk);
		break;
	}
	kfree(wrk);
}

static struct smcd_dev *smcd_alloc_dev(struct device *parent, const char *name,
				       const struct smcd_ops *ops, int max_dmbs)
{
	struct smcd_dev *smcd;

	smcd = devm_kzalloc(parent, sizeof(*smcd), GFP_KERNEL);
	if (!smcd)
		return NULL;
	smcd->conn = devm_kcalloc(parent, max_dmbs,
				  sizeof(struct smc_connection *), GFP_KERNEL);
	if (!smcd->conn)
		return NULL;

	smcd->event_wq = alloc_ordered_workqueue("ism_evt_wq-%s)",
						 WQ_MEM_RECLAIM, name);
	if (!smcd->event_wq)
		return NULL;

	smcd->ops = ops;

	spin_lock_init(&smcd->lock);
	spin_lock_init(&smcd->lgr_lock);
	INIT_LIST_HEAD(&smcd->vlan);
	INIT_LIST_HEAD(&smcd->lgr_list);
	init_waitqueue_head(&smcd->lgrs_deleted);
	return smcd;
}

static void smcd_register_dev(struct ism_dev *ism)
{
	const struct smcd_ops *ops = ism_get_smcd_ops();
	struct smcd_dev *smcd, *fentry;

	if (!ops)
		return;

	smcd = smcd_alloc_dev(&ism->pdev->dev, dev_name(&ism->pdev->dev), ops,
			      ISM_NR_DMBS);
	if (!smcd)
		return;
	smcd->priv = ism;
	smcd->client = &smc_ism_client;
	ism_set_priv(ism, &smc_ism_client, smcd);
	if (smc_pnetid_by_dev_port(&ism->pdev->dev, 0, smcd->pnetid))
		smc_pnetid_by_table_smcd(smcd);

	if (smcd->ops->supports_v2())
		smc_ism_set_v2_capable();
	mutex_lock(&smcd_dev_list.mutex);
	/* sort list:
	 * - devices without pnetid before devices with pnetid;
	 * - loopback-ism always at the very beginning;
	 */
	if (!smcd->pnetid[0]) {
		fentry = list_first_entry_or_null(&smcd_dev_list.list,
						  struct smcd_dev, list);
		if (fentry && smc_ism_is_loopback(fentry))
			list_add(&smcd->list, &fentry->list);
		else
			list_add(&smcd->list, &smcd_dev_list.list);
	} else {
		list_add_tail(&smcd->list, &smcd_dev_list.list);
	}
	mutex_unlock(&smcd_dev_list.mutex);

	pr_warn_ratelimited("smc: adding smcd device %s with pnetid %.16s%s\n",
			    dev_name(&ism->dev), smcd->pnetid,
			    smcd->pnetid_by_user ? " (user defined)" : "");

	return;
}

static void smcd_unregister_dev(struct ism_dev *ism)
{
	struct smcd_dev *smcd = ism_get_priv(ism, &smc_ism_client);

	pr_warn_ratelimited("smc: removing smcd device %s\n",
			    dev_name(&ism->dev));
	smcd->going_away = 1;
	smc_smcd_terminate_all(smcd);
	mutex_lock(&smcd_dev_list.mutex);
	list_del_init(&smcd->list);
	mutex_unlock(&smcd_dev_list.mutex);
	destroy_workqueue(smcd->event_wq);
}

/* SMCD Device event handler. Called from ISM device interrupt handler.
 * Parameters are ism device pointer,
 * - event->type (0 --> DMB, 1 --> GID),
 * - event->code (event code),
 * - event->tok (either DMB token when event type 0, or GID when event type 1)
 * - event->time (time of day)
 * - event->info (debug info).
 *
 * Context:
 * - Function called in IRQ context from ISM device driver event handler.
 */
static void smcd_handle_event(struct ism_dev *ism, struct ism_event *event)
{
	struct smcd_dev *smcd = ism_get_priv(ism, &smc_ism_client);
	struct smc_ism_event_work *wrk;

	if (smcd->going_away)
		return;
	/* copy event to event work queue, and let it be handled there */
	wrk = kmalloc(sizeof(*wrk), GFP_ATOMIC);
	if (!wrk)
		return;
	INIT_WORK(&wrk->work, smc_ism_event_work);
	wrk->smcd = smcd;
	wrk->event = *event;
	queue_work(smcd->event_wq, &wrk->work);
}

/* SMCD Device interrupt handler. Called from ISM device interrupt handler.
 * Parameters are the ism device pointer, DMB number, and the DMBE bitmask.
 * Find the connection and schedule the tasklet for this connection.
 *
 * Context:
 * - Function called in IRQ context from ISM device driver IRQ handler.
 */
static void smcd_handle_irq(struct ism_dev *ism, unsigned int dmbno,
			    u16 dmbemask)
{
	struct smcd_dev *smcd = ism_get_priv(ism, &smc_ism_client);
	struct smc_connection *conn = NULL;
	unsigned long flags;

	spin_lock_irqsave(&smcd->lock, flags);
	conn = smcd->conn[dmbno];
	if (conn && !conn->killed)
		tasklet_schedule(&conn->rx_tsklet);
	spin_unlock_irqrestore(&smcd->lock, flags);
}
#endif

int smc_ism_signal_shutdown(struct smc_link_group *lgr)
{}

int smc_ism_init(void)
{}

void smc_ism_exit(void)
{}