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