xref: /openbmc/linux/net/smc/smc_ism.c (revision c6ba7c9b)
1c6ba7c9bSHans Wippel // SPDX-License-Identifier: GPL-2.0
2c6ba7c9bSHans Wippel /* Shared Memory Communications Direct over ISM devices (SMC-D)
3c6ba7c9bSHans Wippel  *
4c6ba7c9bSHans Wippel  * Functions for ISM device.
5c6ba7c9bSHans Wippel  *
6c6ba7c9bSHans Wippel  * Copyright IBM Corp. 2018
7c6ba7c9bSHans Wippel  */
8c6ba7c9bSHans Wippel 
9c6ba7c9bSHans Wippel #include <linux/spinlock.h>
10c6ba7c9bSHans Wippel #include <linux/slab.h>
11c6ba7c9bSHans Wippel #include <asm/page.h>
12c6ba7c9bSHans Wippel 
13c6ba7c9bSHans Wippel #include "smc.h"
14c6ba7c9bSHans Wippel #include "smc_core.h"
15c6ba7c9bSHans Wippel #include "smc_ism.h"
16c6ba7c9bSHans Wippel 
17c6ba7c9bSHans Wippel struct smcd_dev_list smcd_dev_list = {
18c6ba7c9bSHans Wippel 	.list = LIST_HEAD_INIT(smcd_dev_list.list),
19c6ba7c9bSHans Wippel 	.lock = __SPIN_LOCK_UNLOCKED(smcd_dev_list.lock)
20c6ba7c9bSHans Wippel };
21c6ba7c9bSHans Wippel 
22c6ba7c9bSHans Wippel /* Test if an ISM communication is possible. */
23c6ba7c9bSHans Wippel int smc_ism_cantalk(u64 peer_gid, unsigned short vlan_id, struct smcd_dev *smcd)
24c6ba7c9bSHans Wippel {
25c6ba7c9bSHans Wippel 	return smcd->ops->query_remote_gid(smcd, peer_gid, vlan_id ? 1 : 0,
26c6ba7c9bSHans Wippel 					   vlan_id);
27c6ba7c9bSHans Wippel }
28c6ba7c9bSHans Wippel 
29c6ba7c9bSHans Wippel int smc_ism_write(struct smcd_dev *smcd, const struct smc_ism_position *pos,
30c6ba7c9bSHans Wippel 		  void *data, size_t len)
31c6ba7c9bSHans Wippel {
32c6ba7c9bSHans Wippel 	int rc;
33c6ba7c9bSHans Wippel 
34c6ba7c9bSHans Wippel 	rc = smcd->ops->move_data(smcd, pos->token, pos->index, pos->signal,
35c6ba7c9bSHans Wippel 				  pos->offset, data, len);
36c6ba7c9bSHans Wippel 
37c6ba7c9bSHans Wippel 	return rc < 0 ? rc : 0;
38c6ba7c9bSHans Wippel }
39c6ba7c9bSHans Wippel 
40c6ba7c9bSHans Wippel /* Set a connection using this DMBE. */
41c6ba7c9bSHans Wippel void smc_ism_set_conn(struct smc_connection *conn)
42c6ba7c9bSHans Wippel {
43c6ba7c9bSHans Wippel 	unsigned long flags;
44c6ba7c9bSHans Wippel 
45c6ba7c9bSHans Wippel 	spin_lock_irqsave(&conn->lgr->smcd->lock, flags);
46c6ba7c9bSHans Wippel 	conn->lgr->smcd->conn[conn->rmb_desc->sba_idx] = conn;
47c6ba7c9bSHans Wippel 	spin_unlock_irqrestore(&conn->lgr->smcd->lock, flags);
48c6ba7c9bSHans Wippel }
49c6ba7c9bSHans Wippel 
50c6ba7c9bSHans Wippel /* Unset a connection using this DMBE. */
51c6ba7c9bSHans Wippel void smc_ism_unset_conn(struct smc_connection *conn)
52c6ba7c9bSHans Wippel {
53c6ba7c9bSHans Wippel 	unsigned long flags;
54c6ba7c9bSHans Wippel 
55c6ba7c9bSHans Wippel 	if (!conn->rmb_desc)
56c6ba7c9bSHans Wippel 		return;
57c6ba7c9bSHans Wippel 
58c6ba7c9bSHans Wippel 	spin_lock_irqsave(&conn->lgr->smcd->lock, flags);
59c6ba7c9bSHans Wippel 	conn->lgr->smcd->conn[conn->rmb_desc->sba_idx] = NULL;
60c6ba7c9bSHans Wippel 	spin_unlock_irqrestore(&conn->lgr->smcd->lock, flags);
61c6ba7c9bSHans Wippel }
62c6ba7c9bSHans Wippel 
63c6ba7c9bSHans Wippel /* Register a VLAN identifier with the ISM device. Use a reference count
64c6ba7c9bSHans Wippel  * and add a VLAN identifier only when the first DMB using this VLAN is
65c6ba7c9bSHans Wippel  * registered.
66c6ba7c9bSHans Wippel  */
67c6ba7c9bSHans Wippel int smc_ism_get_vlan(struct smcd_dev *smcd, unsigned short vlanid)
68c6ba7c9bSHans Wippel {
69c6ba7c9bSHans Wippel 	struct smc_ism_vlanid *new_vlan, *vlan;
70c6ba7c9bSHans Wippel 	unsigned long flags;
71c6ba7c9bSHans Wippel 	int rc = 0;
72c6ba7c9bSHans Wippel 
73c6ba7c9bSHans Wippel 	if (!vlanid)			/* No valid vlan id */
74c6ba7c9bSHans Wippel 		return -EINVAL;
75c6ba7c9bSHans Wippel 
76c6ba7c9bSHans Wippel 	/* create new vlan entry, in case we need it */
77c6ba7c9bSHans Wippel 	new_vlan = kzalloc(sizeof(*new_vlan), GFP_KERNEL);
78c6ba7c9bSHans Wippel 	if (!new_vlan)
79c6ba7c9bSHans Wippel 		return -ENOMEM;
80c6ba7c9bSHans Wippel 	new_vlan->vlanid = vlanid;
81c6ba7c9bSHans Wippel 	refcount_set(&new_vlan->refcnt, 1);
82c6ba7c9bSHans Wippel 
83c6ba7c9bSHans Wippel 	/* if there is an existing entry, increase count and return */
84c6ba7c9bSHans Wippel 	spin_lock_irqsave(&smcd->lock, flags);
85c6ba7c9bSHans Wippel 	list_for_each_entry(vlan, &smcd->vlan, list) {
86c6ba7c9bSHans Wippel 		if (vlan->vlanid == vlanid) {
87c6ba7c9bSHans Wippel 			refcount_inc(&vlan->refcnt);
88c6ba7c9bSHans Wippel 			kfree(new_vlan);
89c6ba7c9bSHans Wippel 			goto out;
90c6ba7c9bSHans Wippel 		}
91c6ba7c9bSHans Wippel 	}
92c6ba7c9bSHans Wippel 
93c6ba7c9bSHans Wippel 	/* no existing entry found.
94c6ba7c9bSHans Wippel 	 * add new entry to device; might fail, e.g., if HW limit reached
95c6ba7c9bSHans Wippel 	 */
96c6ba7c9bSHans Wippel 	if (smcd->ops->add_vlan_id(smcd, vlanid)) {
97c6ba7c9bSHans Wippel 		kfree(new_vlan);
98c6ba7c9bSHans Wippel 		rc = -EIO;
99c6ba7c9bSHans Wippel 		goto out;
100c6ba7c9bSHans Wippel 	}
101c6ba7c9bSHans Wippel 	list_add_tail(&new_vlan->list, &smcd->vlan);
102c6ba7c9bSHans Wippel out:
103c6ba7c9bSHans Wippel 	spin_unlock_irqrestore(&smcd->lock, flags);
104c6ba7c9bSHans Wippel 	return rc;
105c6ba7c9bSHans Wippel }
106c6ba7c9bSHans Wippel 
107c6ba7c9bSHans Wippel /* Unregister a VLAN identifier with the ISM device. Use a reference count
108c6ba7c9bSHans Wippel  * and remove a VLAN identifier only when the last DMB using this VLAN is
109c6ba7c9bSHans Wippel  * unregistered.
110c6ba7c9bSHans Wippel  */
111c6ba7c9bSHans Wippel int smc_ism_put_vlan(struct smcd_dev *smcd, unsigned short vlanid)
112c6ba7c9bSHans Wippel {
113c6ba7c9bSHans Wippel 	struct smc_ism_vlanid *vlan;
114c6ba7c9bSHans Wippel 	unsigned long flags;
115c6ba7c9bSHans Wippel 	bool found = false;
116c6ba7c9bSHans Wippel 	int rc = 0;
117c6ba7c9bSHans Wippel 
118c6ba7c9bSHans Wippel 	if (!vlanid)			/* No valid vlan id */
119c6ba7c9bSHans Wippel 		return -EINVAL;
120c6ba7c9bSHans Wippel 
121c6ba7c9bSHans Wippel 	spin_lock_irqsave(&smcd->lock, flags);
122c6ba7c9bSHans Wippel 	list_for_each_entry(vlan, &smcd->vlan, list) {
123c6ba7c9bSHans Wippel 		if (vlan->vlanid == vlanid) {
124c6ba7c9bSHans Wippel 			if (!refcount_dec_and_test(&vlan->refcnt))
125c6ba7c9bSHans Wippel 				goto out;
126c6ba7c9bSHans Wippel 			found = true;
127c6ba7c9bSHans Wippel 			break;
128c6ba7c9bSHans Wippel 		}
129c6ba7c9bSHans Wippel 	}
130c6ba7c9bSHans Wippel 	if (!found) {
131c6ba7c9bSHans Wippel 		rc = -ENOENT;
132c6ba7c9bSHans Wippel 		goto out;		/* VLAN id not in table */
133c6ba7c9bSHans Wippel 	}
134c6ba7c9bSHans Wippel 
135c6ba7c9bSHans Wippel 	/* Found and the last reference just gone */
136c6ba7c9bSHans Wippel 	if (smcd->ops->del_vlan_id(smcd, vlanid))
137c6ba7c9bSHans Wippel 		rc = -EIO;
138c6ba7c9bSHans Wippel 	list_del(&vlan->list);
139c6ba7c9bSHans Wippel 	kfree(vlan);
140c6ba7c9bSHans Wippel out:
141c6ba7c9bSHans Wippel 	spin_unlock_irqrestore(&smcd->lock, flags);
142c6ba7c9bSHans Wippel 	return rc;
143c6ba7c9bSHans Wippel }
144c6ba7c9bSHans Wippel 
145c6ba7c9bSHans Wippel int smc_ism_unregister_dmb(struct smcd_dev *smcd, struct smc_buf_desc *dmb_desc)
146c6ba7c9bSHans Wippel {
147c6ba7c9bSHans Wippel 	struct smcd_dmb dmb;
148c6ba7c9bSHans Wippel 
149c6ba7c9bSHans Wippel 	memset(&dmb, 0, sizeof(dmb));
150c6ba7c9bSHans Wippel 	dmb.dmb_tok = dmb_desc->token;
151c6ba7c9bSHans Wippel 	dmb.sba_idx = dmb_desc->sba_idx;
152c6ba7c9bSHans Wippel 	dmb.cpu_addr = dmb_desc->cpu_addr;
153c6ba7c9bSHans Wippel 	dmb.dma_addr = dmb_desc->dma_addr;
154c6ba7c9bSHans Wippel 	dmb.dmb_len = dmb_desc->len;
155c6ba7c9bSHans Wippel 	return smcd->ops->unregister_dmb(smcd, &dmb);
156c6ba7c9bSHans Wippel }
157c6ba7c9bSHans Wippel 
158c6ba7c9bSHans Wippel int smc_ism_register_dmb(struct smc_link_group *lgr, int dmb_len,
159c6ba7c9bSHans Wippel 			 struct smc_buf_desc *dmb_desc)
160c6ba7c9bSHans Wippel {
161c6ba7c9bSHans Wippel 	struct smcd_dmb dmb;
162c6ba7c9bSHans Wippel 	int rc;
163c6ba7c9bSHans Wippel 
164c6ba7c9bSHans Wippel 	memset(&dmb, 0, sizeof(dmb));
165c6ba7c9bSHans Wippel 	dmb.dmb_len = dmb_len;
166c6ba7c9bSHans Wippel 	dmb.sba_idx = dmb_desc->sba_idx;
167c6ba7c9bSHans Wippel 	dmb.vlan_id = lgr->vlan_id;
168c6ba7c9bSHans Wippel 	dmb.rgid = lgr->peer_gid;
169c6ba7c9bSHans Wippel 	rc = lgr->smcd->ops->register_dmb(lgr->smcd, &dmb);
170c6ba7c9bSHans Wippel 	if (!rc) {
171c6ba7c9bSHans Wippel 		dmb_desc->sba_idx = dmb.sba_idx;
172c6ba7c9bSHans Wippel 		dmb_desc->token = dmb.dmb_tok;
173c6ba7c9bSHans Wippel 		dmb_desc->cpu_addr = dmb.cpu_addr;
174c6ba7c9bSHans Wippel 		dmb_desc->dma_addr = dmb.dma_addr;
175c6ba7c9bSHans Wippel 		dmb_desc->len = dmb.dmb_len;
176c6ba7c9bSHans Wippel 	}
177c6ba7c9bSHans Wippel 	return rc;
178c6ba7c9bSHans Wippel }
179c6ba7c9bSHans Wippel 
180c6ba7c9bSHans Wippel struct smc_ism_event_work {
181c6ba7c9bSHans Wippel 	struct work_struct work;
182c6ba7c9bSHans Wippel 	struct smcd_dev *smcd;
183c6ba7c9bSHans Wippel 	struct smcd_event event;
184c6ba7c9bSHans Wippel };
185c6ba7c9bSHans Wippel 
186c6ba7c9bSHans Wippel /* worker for SMC-D events */
187c6ba7c9bSHans Wippel static void smc_ism_event_work(struct work_struct *work)
188c6ba7c9bSHans Wippel {
189c6ba7c9bSHans Wippel 	struct smc_ism_event_work *wrk =
190c6ba7c9bSHans Wippel 		container_of(work, struct smc_ism_event_work, work);
191c6ba7c9bSHans Wippel 
192c6ba7c9bSHans Wippel 	switch (wrk->event.type) {
193c6ba7c9bSHans Wippel 	case ISM_EVENT_GID:	/* GID event, token is peer GID */
194c6ba7c9bSHans Wippel 		smc_smcd_terminate(wrk->smcd, wrk->event.tok);
195c6ba7c9bSHans Wippel 		break;
196c6ba7c9bSHans Wippel 	case ISM_EVENT_DMB:
197c6ba7c9bSHans Wippel 		break;
198c6ba7c9bSHans Wippel 	}
199c6ba7c9bSHans Wippel 	kfree(wrk);
200c6ba7c9bSHans Wippel }
201c6ba7c9bSHans Wippel 
202c6ba7c9bSHans Wippel static void smcd_release(struct device *dev)
203c6ba7c9bSHans Wippel {
204c6ba7c9bSHans Wippel 	struct smcd_dev *smcd = container_of(dev, struct smcd_dev, dev);
205c6ba7c9bSHans Wippel 
206c6ba7c9bSHans Wippel 	kfree(smcd->conn);
207c6ba7c9bSHans Wippel 	kfree(smcd);
208c6ba7c9bSHans Wippel }
209c6ba7c9bSHans Wippel 
210c6ba7c9bSHans Wippel struct smcd_dev *smcd_alloc_dev(struct device *parent, const char *name,
211c6ba7c9bSHans Wippel 				const struct smcd_ops *ops, int max_dmbs)
212c6ba7c9bSHans Wippel {
213c6ba7c9bSHans Wippel 	struct smcd_dev *smcd;
214c6ba7c9bSHans Wippel 
215c6ba7c9bSHans Wippel 	smcd = kzalloc(sizeof(*smcd), GFP_KERNEL);
216c6ba7c9bSHans Wippel 	if (!smcd)
217c6ba7c9bSHans Wippel 		return NULL;
218c6ba7c9bSHans Wippel 	smcd->conn = kcalloc(max_dmbs, sizeof(struct smc_connection *),
219c6ba7c9bSHans Wippel 			     GFP_KERNEL);
220c6ba7c9bSHans Wippel 	if (!smcd->conn) {
221c6ba7c9bSHans Wippel 		kfree(smcd);
222c6ba7c9bSHans Wippel 		return NULL;
223c6ba7c9bSHans Wippel 	}
224c6ba7c9bSHans Wippel 
225c6ba7c9bSHans Wippel 	smcd->dev.parent = parent;
226c6ba7c9bSHans Wippel 	smcd->dev.release = smcd_release;
227c6ba7c9bSHans Wippel 	device_initialize(&smcd->dev);
228c6ba7c9bSHans Wippel 	dev_set_name(&smcd->dev, name);
229c6ba7c9bSHans Wippel 	smcd->ops = ops;
230c6ba7c9bSHans Wippel 
231c6ba7c9bSHans Wippel 	spin_lock_init(&smcd->lock);
232c6ba7c9bSHans Wippel 	INIT_LIST_HEAD(&smcd->vlan);
233c6ba7c9bSHans Wippel 	smcd->event_wq = alloc_ordered_workqueue("ism_evt_wq-%s)",
234c6ba7c9bSHans Wippel 						 WQ_MEM_RECLAIM, name);
235c6ba7c9bSHans Wippel 	return smcd;
236c6ba7c9bSHans Wippel }
237c6ba7c9bSHans Wippel EXPORT_SYMBOL_GPL(smcd_alloc_dev);
238c6ba7c9bSHans Wippel 
239c6ba7c9bSHans Wippel int smcd_register_dev(struct smcd_dev *smcd)
240c6ba7c9bSHans Wippel {
241c6ba7c9bSHans Wippel 	spin_lock(&smcd_dev_list.lock);
242c6ba7c9bSHans Wippel 	list_add_tail(&smcd->list, &smcd_dev_list.list);
243c6ba7c9bSHans Wippel 	spin_unlock(&smcd_dev_list.lock);
244c6ba7c9bSHans Wippel 
245c6ba7c9bSHans Wippel 	return device_add(&smcd->dev);
246c6ba7c9bSHans Wippel }
247c6ba7c9bSHans Wippel EXPORT_SYMBOL_GPL(smcd_register_dev);
248c6ba7c9bSHans Wippel 
249c6ba7c9bSHans Wippel void smcd_unregister_dev(struct smcd_dev *smcd)
250c6ba7c9bSHans Wippel {
251c6ba7c9bSHans Wippel 	spin_lock(&smcd_dev_list.lock);
252c6ba7c9bSHans Wippel 	list_del(&smcd->list);
253c6ba7c9bSHans Wippel 	spin_unlock(&smcd_dev_list.lock);
254c6ba7c9bSHans Wippel 	flush_workqueue(smcd->event_wq);
255c6ba7c9bSHans Wippel 	destroy_workqueue(smcd->event_wq);
256c6ba7c9bSHans Wippel 	smc_smcd_terminate(smcd, 0);
257c6ba7c9bSHans Wippel 
258c6ba7c9bSHans Wippel 	device_del(&smcd->dev);
259c6ba7c9bSHans Wippel }
260c6ba7c9bSHans Wippel EXPORT_SYMBOL_GPL(smcd_unregister_dev);
261c6ba7c9bSHans Wippel 
262c6ba7c9bSHans Wippel void smcd_free_dev(struct smcd_dev *smcd)
263c6ba7c9bSHans Wippel {
264c6ba7c9bSHans Wippel 	put_device(&smcd->dev);
265c6ba7c9bSHans Wippel }
266c6ba7c9bSHans Wippel EXPORT_SYMBOL_GPL(smcd_free_dev);
267c6ba7c9bSHans Wippel 
268c6ba7c9bSHans Wippel /* SMCD Device event handler. Called from ISM device interrupt handler.
269c6ba7c9bSHans Wippel  * Parameters are smcd device pointer,
270c6ba7c9bSHans Wippel  * - event->type (0 --> DMB, 1 --> GID),
271c6ba7c9bSHans Wippel  * - event->code (event code),
272c6ba7c9bSHans Wippel  * - event->tok (either DMB token when event type 0, or GID when event type 1)
273c6ba7c9bSHans Wippel  * - event->time (time of day)
274c6ba7c9bSHans Wippel  * - event->info (debug info).
275c6ba7c9bSHans Wippel  *
276c6ba7c9bSHans Wippel  * Context:
277c6ba7c9bSHans Wippel  * - Function called in IRQ context from ISM device driver event handler.
278c6ba7c9bSHans Wippel  */
279c6ba7c9bSHans Wippel void smcd_handle_event(struct smcd_dev *smcd, struct smcd_event *event)
280c6ba7c9bSHans Wippel {
281c6ba7c9bSHans Wippel 	struct smc_ism_event_work *wrk;
282c6ba7c9bSHans Wippel 
283c6ba7c9bSHans Wippel 	/* copy event to event work queue, and let it be handled there */
284c6ba7c9bSHans Wippel 	wrk = kmalloc(sizeof(*wrk), GFP_ATOMIC);
285c6ba7c9bSHans Wippel 	if (!wrk)
286c6ba7c9bSHans Wippel 		return;
287c6ba7c9bSHans Wippel 	INIT_WORK(&wrk->work, smc_ism_event_work);
288c6ba7c9bSHans Wippel 	wrk->smcd = smcd;
289c6ba7c9bSHans Wippel 	wrk->event = *event;
290c6ba7c9bSHans Wippel 	queue_work(smcd->event_wq, &wrk->work);
291c6ba7c9bSHans Wippel }
292c6ba7c9bSHans Wippel EXPORT_SYMBOL_GPL(smcd_handle_event);
293c6ba7c9bSHans Wippel 
294c6ba7c9bSHans Wippel /* SMCD Device interrupt handler. Called from ISM device interrupt handler.
295c6ba7c9bSHans Wippel  * Parameters are smcd device pointer and DMB number. Find the connection and
296c6ba7c9bSHans Wippel  * schedule the tasklet for this connection.
297c6ba7c9bSHans Wippel  *
298c6ba7c9bSHans Wippel  * Context:
299c6ba7c9bSHans Wippel  * - Function called in IRQ context from ISM device driver IRQ handler.
300c6ba7c9bSHans Wippel  */
301c6ba7c9bSHans Wippel void smcd_handle_irq(struct smcd_dev *smcd, unsigned int dmbno)
302c6ba7c9bSHans Wippel {
303c6ba7c9bSHans Wippel }
304c6ba7c9bSHans Wippel EXPORT_SYMBOL_GPL(smcd_handle_irq);
305