| /* | |
| * Intel MIC Platform Software Stack (MPSS) | |
| * | |
| * Copyright(c) 2014 Intel Corporation. | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License, version 2, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This program is distributed in the hope that it will be useful, but | |
| * WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * General Public License for more details. | |
| * | |
| * Intel SCIF driver. | |
| * | |
| */ | |
| #include "../bus/scif_bus.h" | |
| #include "scif_peer_bus.h" | |
| #include "scif_main.h" | |
| #include "scif_nodeqp.h" | |
| #include "scif_map.h" | |
| /* | |
| ************************************************************************ | |
| * SCIF node Queue Pair (QP) setup flow: | |
| * | |
| * 1) SCIF driver gets probed with a scif_hw_dev via the scif_hw_bus | |
| * 2) scif_setup_qp(..) allocates the local qp and calls | |
| * scif_setup_qp_connect(..) which allocates and maps the local | |
| * buffer for the inbound QP | |
| * 3) The local node updates the device page with the DMA address of the QP | |
| * 4) A delayed work is scheduled (qp_dwork) which periodically reads if | |
| * the peer node has updated its QP DMA address | |
| * 5) Once a valid non zero address is found in the QP DMA address field | |
| * in the device page, the local node maps the remote node's QP, | |
| * updates its outbound QP and sends a SCIF_INIT message to the peer | |
| * 6) The SCIF_INIT message is received by the peer node QP interrupt bottom | |
| * half handler by calling scif_init(..) | |
| * 7) scif_init(..) registers a new SCIF peer node by calling | |
| * scif_peer_register_device(..) which signifies the addition of a new | |
| * SCIF node | |
| * 8) On the mgmt node, P2P network setup/teardown is initiated if all the | |
| * remote nodes are online via scif_p2p_setup(..) | |
| * 9) For P2P setup, the host maps the remote nodes' aperture and memory | |
| * bars and sends a SCIF_NODE_ADD message to both nodes | |
| * 10) As part of scif_nodeadd, both nodes set up their local inbound | |
| * QPs and send a SCIF_NODE_ADD_ACK to the mgmt node | |
| * 11) As part of scif_node_add_ack(..) the mgmt node forwards the | |
| * SCIF_NODE_ADD_ACK to the remote nodes | |
| * 12) As part of scif_node_add_ack(..) the remote nodes update their | |
| * outbound QPs, make sure they can access memory on the remote node | |
| * and then add a new SCIF peer node by calling | |
| * scif_peer_register_device(..) which signifies the addition of a new | |
| * SCIF node. | |
| * 13) The SCIF network is now established across all nodes. | |
| * | |
| ************************************************************************ | |
| * SCIF node QP teardown flow (initiated by non mgmt node): | |
| * | |
| * 1) SCIF driver gets a remove callback with a scif_hw_dev via the scif_hw_bus | |
| * 2) The device page QP DMA address field is updated with 0x0 | |
| * 3) A non mgmt node now cleans up all local data structures and sends a | |
| * SCIF_EXIT message to the peer and waits for a SCIF_EXIT_ACK | |
| * 4) As part of scif_exit(..) handling scif_disconnect_node(..) is called | |
| * 5) scif_disconnect_node(..) sends a SCIF_NODE_REMOVE message to all the | |
| * peers and waits for a SCIF_NODE_REMOVE_ACK | |
| * 6) As part of scif_node_remove(..) a remote node unregisters the peer | |
| * node from the SCIF network and sends a SCIF_NODE_REMOVE_ACK | |
| * 7) When the mgmt node has received all the SCIF_NODE_REMOVE_ACKs | |
| * it sends itself a node remove message whose handling cleans up local | |
| * data structures and unregisters the peer node from the SCIF network | |
| * 8) The mgmt node sends a SCIF_EXIT_ACK | |
| * 9) Upon receipt of the SCIF_EXIT_ACK the node initiating the teardown | |
| * completes the SCIF remove routine | |
| * 10) The SCIF network is now torn down for the node initiating the | |
| * teardown sequence | |
| * | |
| ************************************************************************ | |
| * SCIF node QP teardown flow (initiated by mgmt node): | |
| * | |
| * 1) SCIF driver gets a remove callback with a scif_hw_dev via the scif_hw_bus | |
| * 2) The device page QP DMA address field is updated with 0x0 | |
| * 3) The mgmt node calls scif_disconnect_node(..) | |
| * 4) scif_disconnect_node(..) sends a SCIF_NODE_REMOVE message to all the peers | |
| * and waits for a SCIF_NODE_REMOVE_ACK | |
| * 5) As part of scif_node_remove(..) a remote node unregisters the peer | |
| * node from the SCIF network and sends a SCIF_NODE_REMOVE_ACK | |
| * 6) When the mgmt node has received all the SCIF_NODE_REMOVE_ACKs | |
| * it unregisters the peer node from the SCIF network | |
| * 7) The mgmt node sends a SCIF_EXIT message and waits for a SCIF_EXIT_ACK. | |
| * 8) A non mgmt node upon receipt of a SCIF_EXIT message calls scif_stop(..) | |
| * which would clean up local data structures for all SCIF nodes and | |
| * then send a SCIF_EXIT_ACK back to the mgmt node | |
| * 9) Upon receipt of the SCIF_EXIT_ACK the the mgmt node sends itself a node | |
| * remove message whose handling cleans up local data structures and | |
| * destroys any P2P mappings. | |
| * 10) The SCIF hardware device for which a remove callback was received is now | |
| * disconnected from the SCIF network. | |
| */ | |
| /* | |
| * Initializes "local" data structures for the QP. Allocates the QP | |
| * ring buffer (rb) and initializes the "in bound" queue. | |
| */ | |
| int scif_setup_qp_connect(struct scif_qp *qp, dma_addr_t *qp_offset, | |
| int local_size, struct scif_dev *scifdev) | |
| { | |
| void *local_q = qp->inbound_q.rb_base; | |
| int err = 0; | |
| u32 tmp_rd = 0; | |
| spin_lock_init(&qp->send_lock); | |
| spin_lock_init(&qp->recv_lock); | |
| /* Allocate rb only if not already allocated */ | |
| if (!local_q) { | |
| local_q = kzalloc(local_size, GFP_KERNEL); | |
| if (!local_q) { | |
| err = -ENOMEM; | |
| return err; | |
| } | |
| } | |
| err = scif_map_single(&qp->local_buf, local_q, scifdev, local_size); | |
| if (err) | |
| goto kfree; | |
| /* | |
| * To setup the inbound_q, the buffer lives locally, the read pointer | |
| * is remote and the write pointer is local. | |
| */ | |
| scif_rb_init(&qp->inbound_q, | |
| &tmp_rd, | |
| &qp->local_write, | |
| local_q, get_count_order(local_size)); | |
| /* | |
| * The read pointer is NULL initially and it is unsafe to use the ring | |
| * buffer til this changes! | |
| */ | |
| qp->inbound_q.read_ptr = NULL; | |
| err = scif_map_single(qp_offset, qp, | |
| scifdev, sizeof(struct scif_qp)); | |
| if (err) | |
| goto unmap; | |
| qp->local_qp = *qp_offset; | |
| return err; | |
| unmap: | |
| scif_unmap_single(qp->local_buf, scifdev, local_size); | |
| qp->local_buf = 0; | |
| kfree: | |
| kfree(local_q); | |
| return err; | |
| } | |
| /* When the other side has already done it's allocation, this is called */ | |
| int scif_setup_qp_accept(struct scif_qp *qp, dma_addr_t *qp_offset, | |
| dma_addr_t phys, int local_size, | |
| struct scif_dev *scifdev) | |
| { | |
| void *local_q; | |
| void *remote_q; | |
| struct scif_qp *remote_qp; | |
| int remote_size; | |
| int err = 0; | |
| spin_lock_init(&qp->send_lock); | |
| spin_lock_init(&qp->recv_lock); | |
| /* Start by figuring out where we need to point */ | |
| remote_qp = scif_ioremap(phys, sizeof(struct scif_qp), scifdev); | |
| if (!remote_qp) | |
| return -EIO; | |
| qp->remote_qp = remote_qp; | |
| if (qp->remote_qp->magic != SCIFEP_MAGIC) { | |
| err = -EIO; | |
| goto iounmap; | |
| } | |
| qp->remote_buf = remote_qp->local_buf; | |
| remote_size = qp->remote_qp->inbound_q.size; | |
| remote_q = scif_ioremap(qp->remote_buf, remote_size, scifdev); | |
| if (!remote_q) { | |
| err = -EIO; | |
| goto iounmap; | |
| } | |
| qp->remote_qp->local_write = 0; | |
| /* | |
| * To setup the outbound_q, the buffer lives in remote memory, | |
| * the read pointer is local, the write pointer is remote | |
| */ | |
| scif_rb_init(&qp->outbound_q, | |
| &qp->local_read, | |
| &qp->remote_qp->local_write, | |
| remote_q, | |
| get_count_order(remote_size)); | |
| local_q = kzalloc(local_size, GFP_KERNEL); | |
| if (!local_q) { | |
| err = -ENOMEM; | |
| goto iounmap_1; | |
| } | |
| err = scif_map_single(&qp->local_buf, local_q, scifdev, local_size); | |
| if (err) | |
| goto kfree; | |
| qp->remote_qp->local_read = 0; | |
| /* | |
| * To setup the inbound_q, the buffer lives locally, the read pointer | |
| * is remote and the write pointer is local | |
| */ | |
| scif_rb_init(&qp->inbound_q, | |
| &qp->remote_qp->local_read, | |
| &qp->local_write, | |
| local_q, get_count_order(local_size)); | |
| err = scif_map_single(qp_offset, qp, scifdev, | |
| sizeof(struct scif_qp)); | |
| if (err) | |
| goto unmap; | |
| qp->local_qp = *qp_offset; | |
| return err; | |
| unmap: | |
| scif_unmap_single(qp->local_buf, scifdev, local_size); | |
| qp->local_buf = 0; | |
| kfree: | |
| kfree(local_q); | |
| iounmap_1: | |
| scif_iounmap(remote_q, remote_size, scifdev); | |
| qp->outbound_q.rb_base = NULL; | |
| iounmap: | |
| scif_iounmap(qp->remote_qp, sizeof(struct scif_qp), scifdev); | |
| qp->remote_qp = NULL; | |
| return err; | |
| } | |
| int scif_setup_qp_connect_response(struct scif_dev *scifdev, | |
| struct scif_qp *qp, u64 payload) | |
| { | |
| int err = 0; | |
| void *r_buf; | |
| int remote_size; | |
| phys_addr_t tmp_phys; | |
| qp->remote_qp = scif_ioremap(payload, sizeof(struct scif_qp), scifdev); | |
| if (!qp->remote_qp) { | |
| err = -ENOMEM; | |
| goto error; | |
| } | |
| if (qp->remote_qp->magic != SCIFEP_MAGIC) { | |
| dev_err(&scifdev->sdev->dev, | |
| "SCIFEP_MAGIC mismatch between self %d remote %d\n", | |
| scif_dev[scif_info.nodeid].node, scifdev->node); | |
| err = -ENODEV; | |
| goto error; | |
| } | |
| tmp_phys = qp->remote_qp->local_buf; | |
| remote_size = qp->remote_qp->inbound_q.size; | |
| r_buf = scif_ioremap(tmp_phys, remote_size, scifdev); | |
| if (!r_buf) | |
| return -EIO; | |
| qp->local_read = 0; | |
| scif_rb_init(&qp->outbound_q, | |
| &qp->local_read, | |
| &qp->remote_qp->local_write, | |
| r_buf, | |
| get_count_order(remote_size)); | |
| /* | |
| * Because the node QP may already be processing an INIT message, set | |
| * the read pointer so the cached read offset isn't lost | |
| */ | |
| qp->remote_qp->local_read = qp->inbound_q.current_read_offset; | |
| /* | |
| * resetup the inbound_q now that we know where the | |
| * inbound_read really is. | |
| */ | |
| scif_rb_init(&qp->inbound_q, | |
| &qp->remote_qp->local_read, | |
| &qp->local_write, | |
| qp->inbound_q.rb_base, | |
| get_count_order(qp->inbound_q.size)); | |
| error: | |
| return err; | |
| } | |
| static __always_inline void | |
| scif_send_msg_intr(struct scif_dev *scifdev) | |
| { | |
| struct scif_hw_dev *sdev = scifdev->sdev; | |
| if (scifdev_is_p2p(scifdev)) | |
| sdev->hw_ops->send_p2p_intr(sdev, scifdev->rdb, &scifdev->mmio); | |
| else | |
| sdev->hw_ops->send_intr(sdev, scifdev->rdb); | |
| } | |
| int scif_qp_response(phys_addr_t phys, struct scif_dev *scifdev) | |
| { | |
| int err = 0; | |
| struct scifmsg msg; | |
| err = scif_setup_qp_connect_response(scifdev, scifdev->qpairs, phys); | |
| if (!err) { | |
| /* | |
| * Now that everything is setup and mapped, we're ready | |
| * to tell the peer about our queue's location | |
| */ | |
| msg.uop = SCIF_INIT; | |
| msg.dst.node = scifdev->node; | |
| err = scif_nodeqp_send(scifdev, &msg); | |
| } | |
| return err; | |
| } | |
| void scif_send_exit(struct scif_dev *scifdev) | |
| { | |
| struct scifmsg msg; | |
| int ret; | |
| scifdev->exit = OP_IN_PROGRESS; | |
| msg.uop = SCIF_EXIT; | |
| msg.src.node = scif_info.nodeid; | |
| msg.dst.node = scifdev->node; | |
| ret = scif_nodeqp_send(scifdev, &msg); | |
| if (ret) | |
| goto done; | |
| /* Wait for a SCIF_EXIT_ACK message */ | |
| wait_event_timeout(scif_info.exitwq, scifdev->exit == OP_COMPLETED, | |
| SCIF_NODE_ALIVE_TIMEOUT); | |
| done: | |
| scifdev->exit = OP_IDLE; | |
| } | |
| int scif_setup_qp(struct scif_dev *scifdev) | |
| { | |
| int err = 0; | |
| int local_size; | |
| struct scif_qp *qp; | |
| local_size = SCIF_NODE_QP_SIZE; | |
| qp = kzalloc(sizeof(*qp), GFP_KERNEL); | |
| if (!qp) { | |
| err = -ENOMEM; | |
| return err; | |
| } | |
| qp->magic = SCIFEP_MAGIC; | |
| scifdev->qpairs = qp; | |
| err = scif_setup_qp_connect(qp, &scifdev->qp_dma_addr, | |
| local_size, scifdev); | |
| if (err) | |
| goto free_qp; | |
| /* | |
| * We're as setup as we can be. The inbound_q is setup, w/o a usable | |
| * outbound q. When we get a message, the read_ptr will be updated, | |
| * and we will pull the message. | |
| */ | |
| return err; | |
| free_qp: | |
| kfree(scifdev->qpairs); | |
| scifdev->qpairs = NULL; | |
| return err; | |
| } | |
| static void scif_p2p_freesg(struct scatterlist *sg) | |
| { | |
| kfree(sg); | |
| } | |
| static struct scatterlist * | |
| scif_p2p_setsg(phys_addr_t pa, int page_size, int page_cnt) | |
| { | |
| struct scatterlist *sg; | |
| struct page *page; | |
| int i; | |
| sg = kcalloc(page_cnt, sizeof(struct scatterlist), GFP_KERNEL); | |
| if (!sg) | |
| return NULL; | |
| sg_init_table(sg, page_cnt); | |
| for (i = 0; i < page_cnt; i++) { | |
| page = pfn_to_page(pa >> PAGE_SHIFT); | |
| sg_set_page(&sg[i], page, page_size, 0); | |
| pa += page_size; | |
| } | |
| return sg; | |
| } | |
| /* Init p2p mappings required to access peerdev from scifdev */ | |
| static struct scif_p2p_info * | |
| scif_init_p2p_info(struct scif_dev *scifdev, struct scif_dev *peerdev) | |
| { | |
| struct scif_p2p_info *p2p; | |
| int num_mmio_pages, num_aper_pages, sg_page_shift, err, num_aper_chunks; | |
| struct scif_hw_dev *psdev = peerdev->sdev; | |
| struct scif_hw_dev *sdev = scifdev->sdev; | |
| num_mmio_pages = psdev->mmio->len >> PAGE_SHIFT; | |
| num_aper_pages = psdev->aper->len >> PAGE_SHIFT; | |
| p2p = kzalloc(sizeof(*p2p), GFP_KERNEL); | |
| if (!p2p) | |
| return NULL; | |
| p2p->ppi_sg[SCIF_PPI_MMIO] = scif_p2p_setsg(psdev->mmio->pa, | |
| PAGE_SIZE, num_mmio_pages); | |
| if (!p2p->ppi_sg[SCIF_PPI_MMIO]) | |
| goto free_p2p; | |
| p2p->sg_nentries[SCIF_PPI_MMIO] = num_mmio_pages; | |
| sg_page_shift = get_order(min(psdev->aper->len, (u64)(1 << 30))); | |
| num_aper_chunks = num_aper_pages >> (sg_page_shift - PAGE_SHIFT); | |
| p2p->ppi_sg[SCIF_PPI_APER] = scif_p2p_setsg(psdev->aper->pa, | |
| 1 << sg_page_shift, | |
| num_aper_chunks); | |
| p2p->sg_nentries[SCIF_PPI_APER] = num_aper_chunks; | |
| err = dma_map_sg(&sdev->dev, p2p->ppi_sg[SCIF_PPI_MMIO], | |
| num_mmio_pages, PCI_DMA_BIDIRECTIONAL); | |
| if (err != num_mmio_pages) | |
| goto scif_p2p_free; | |
| err = dma_map_sg(&sdev->dev, p2p->ppi_sg[SCIF_PPI_APER], | |
| num_aper_chunks, PCI_DMA_BIDIRECTIONAL); | |
| if (err != num_aper_chunks) | |
| goto dma_unmap; | |
| p2p->ppi_da[SCIF_PPI_MMIO] = sg_dma_address(p2p->ppi_sg[SCIF_PPI_MMIO]); | |
| p2p->ppi_da[SCIF_PPI_APER] = sg_dma_address(p2p->ppi_sg[SCIF_PPI_APER]); | |
| p2p->ppi_len[SCIF_PPI_MMIO] = num_mmio_pages; | |
| p2p->ppi_len[SCIF_PPI_APER] = num_aper_pages; | |
| p2p->ppi_peer_id = peerdev->node; | |
| return p2p; | |
| dma_unmap: | |
| dma_unmap_sg(&sdev->dev, p2p->ppi_sg[SCIF_PPI_MMIO], | |
| p2p->sg_nentries[SCIF_PPI_MMIO], DMA_BIDIRECTIONAL); | |
| scif_p2p_free: | |
| scif_p2p_freesg(p2p->ppi_sg[SCIF_PPI_MMIO]); | |
| scif_p2p_freesg(p2p->ppi_sg[SCIF_PPI_APER]); | |
| free_p2p: | |
| kfree(p2p); | |
| return NULL; | |
| } | |
| /* Uninitialize and release resources from a p2p mapping */ | |
| static void scif_deinit_p2p_info(struct scif_dev *scifdev, | |
| struct scif_p2p_info *p2p) | |
| { | |
| struct scif_hw_dev *sdev = scifdev->sdev; | |
| dma_unmap_sg(&sdev->dev, p2p->ppi_sg[SCIF_PPI_MMIO], | |
| p2p->sg_nentries[SCIF_PPI_MMIO], DMA_BIDIRECTIONAL); | |
| dma_unmap_sg(&sdev->dev, p2p->ppi_sg[SCIF_PPI_APER], | |
| p2p->sg_nentries[SCIF_PPI_APER], DMA_BIDIRECTIONAL); | |
| scif_p2p_freesg(p2p->ppi_sg[SCIF_PPI_MMIO]); | |
| scif_p2p_freesg(p2p->ppi_sg[SCIF_PPI_APER]); | |
| kfree(p2p); | |
| } | |
| /** | |
| * scif_node_connect: Respond to SCIF_NODE_CONNECT interrupt message | |
| * @dst: Destination node | |
| * | |
| * Connect the src and dst node by setting up the p2p connection | |
| * between them. Management node here acts like a proxy. | |
| */ | |
| static void scif_node_connect(struct scif_dev *scifdev, int dst) | |
| { | |
| struct scif_dev *dev_j = scifdev; | |
| struct scif_dev *dev_i = NULL; | |
| struct scif_p2p_info *p2p_ij = NULL; /* bus addr for j from i */ | |
| struct scif_p2p_info *p2p_ji = NULL; /* bus addr for i from j */ | |
| struct scif_p2p_info *p2p; | |
| struct list_head *pos, *tmp; | |
| struct scifmsg msg; | |
| int err; | |
| u64 tmppayload; | |
| if (dst < 1 || dst > scif_info.maxid) | |
| return; | |
| dev_i = &scif_dev[dst]; | |
| if (!_scifdev_alive(dev_i)) | |
| return; | |
| /* | |
| * If the p2p connection is already setup or in the process of setting | |
| * up then just ignore this request. The requested node will get | |
| * informed by SCIF_NODE_ADD_ACK or SCIF_NODE_ADD_NACK | |
| */ | |
| if (!list_empty(&dev_i->p2p)) { | |
| list_for_each_safe(pos, tmp, &dev_i->p2p) { | |
| p2p = list_entry(pos, struct scif_p2p_info, ppi_list); | |
| if (p2p->ppi_peer_id == dev_j->node) | |
| return; | |
| } | |
| } | |
| p2p_ij = scif_init_p2p_info(dev_i, dev_j); | |
| if (!p2p_ij) | |
| return; | |
| p2p_ji = scif_init_p2p_info(dev_j, dev_i); | |
| if (!p2p_ji) { | |
| scif_deinit_p2p_info(dev_i, p2p_ij); | |
| return; | |
| } | |
| list_add_tail(&p2p_ij->ppi_list, &dev_i->p2p); | |
| list_add_tail(&p2p_ji->ppi_list, &dev_j->p2p); | |
| /* | |
| * Send a SCIF_NODE_ADD to dev_i, pass it its bus address | |
| * as seen from dev_j | |
| */ | |
| msg.uop = SCIF_NODE_ADD; | |
| msg.src.node = dev_j->node; | |
| msg.dst.node = dev_i->node; | |
| msg.payload[0] = p2p_ji->ppi_da[SCIF_PPI_APER]; | |
| msg.payload[1] = p2p_ij->ppi_da[SCIF_PPI_MMIO]; | |
| msg.payload[2] = p2p_ij->ppi_da[SCIF_PPI_APER]; | |
| msg.payload[3] = p2p_ij->ppi_len[SCIF_PPI_APER] << PAGE_SHIFT; | |
| err = scif_nodeqp_send(dev_i, &msg); | |
| if (err) { | |
| dev_err(&scifdev->sdev->dev, | |
| "%s %d error %d\n", __func__, __LINE__, err); | |
| return; | |
| } | |
| /* Same as above but to dev_j */ | |
| msg.uop = SCIF_NODE_ADD; | |
| msg.src.node = dev_i->node; | |
| msg.dst.node = dev_j->node; | |
| tmppayload = msg.payload[0]; | |
| msg.payload[0] = msg.payload[2]; | |
| msg.payload[2] = tmppayload; | |
| msg.payload[1] = p2p_ji->ppi_da[SCIF_PPI_MMIO]; | |
| msg.payload[3] = p2p_ji->ppi_len[SCIF_PPI_APER] << PAGE_SHIFT; | |
| scif_nodeqp_send(dev_j, &msg); | |
| } | |
| static void scif_p2p_setup(void) | |
| { | |
| int i, j; | |
| if (!scif_info.p2p_enable) | |
| return; | |
| for (i = 1; i <= scif_info.maxid; i++) | |
| if (!_scifdev_alive(&scif_dev[i])) | |
| return; | |
| for (i = 1; i <= scif_info.maxid; i++) { | |
| for (j = 1; j <= scif_info.maxid; j++) { | |
| struct scif_dev *scifdev = &scif_dev[i]; | |
| if (i == j) | |
| continue; | |
| scif_node_connect(scifdev, j); | |
| } | |
| } | |
| } | |
| static char *message_types[] = {"BAD", | |
| "INIT", | |
| "EXIT", | |
| "SCIF_EXIT_ACK", | |
| "SCIF_NODE_ADD", | |
| "SCIF_NODE_ADD_ACK", | |
| "SCIF_NODE_ADD_NACK", | |
| "REMOVE_NODE", | |
| "REMOVE_NODE_ACK", | |
| "CNCT_REQ", | |
| "CNCT_GNT", | |
| "CNCT_GNTACK", | |
| "CNCT_GNTNACK", | |
| "CNCT_REJ", | |
| "DISCNCT", | |
| "DISCNT_ACK", | |
| "CLIENT_SENT", | |
| "CLIENT_RCVD", | |
| "SCIF_GET_NODE_INFO", | |
| "REGISTER", | |
| "REGISTER_ACK", | |
| "REGISTER_NACK", | |
| "UNREGISTER", | |
| "UNREGISTER_ACK", | |
| "UNREGISTER_NACK", | |
| "ALLOC_REQ", | |
| "ALLOC_GNT", | |
| "ALLOC_REJ", | |
| "FREE_PHYS", | |
| "FREE_VIRT", | |
| "MUNMAP", | |
| "MARK", | |
| "MARK_ACK", | |
| "MARK_NACK", | |
| "WAIT", | |
| "WAIT_ACK", | |
| "WAIT_NACK", | |
| "SIGNAL_LOCAL", | |
| "SIGNAL_REMOTE", | |
| "SIG_ACK", | |
| "SIG_NACK"}; | |
| static void | |
| scif_display_message(struct scif_dev *scifdev, struct scifmsg *msg, | |
| const char *label) | |
| { | |
| if (!scif_info.en_msg_log) | |
| return; | |
| if (msg->uop > SCIF_MAX_MSG) { | |
| dev_err(&scifdev->sdev->dev, | |
| "%s: unknown msg type %d\n", label, msg->uop); | |
| return; | |
| } | |
| dev_info(&scifdev->sdev->dev, | |
| "%s: msg type %s, src %d:%d, dest %d:%d payload 0x%llx:0x%llx:0x%llx:0x%llx\n", | |
| label, message_types[msg->uop], msg->src.node, msg->src.port, | |
| msg->dst.node, msg->dst.port, msg->payload[0], msg->payload[1], | |
| msg->payload[2], msg->payload[3]); | |
| } | |
| int _scif_nodeqp_send(struct scif_dev *scifdev, struct scifmsg *msg) | |
| { | |
| struct scif_qp *qp = scifdev->qpairs; | |
| int err = -ENOMEM, loop_cnt = 0; | |
| scif_display_message(scifdev, msg, "Sent"); | |
| if (!qp) { | |
| err = -EINVAL; | |
| goto error; | |
| } | |
| spin_lock(&qp->send_lock); | |
| while ((err = scif_rb_write(&qp->outbound_q, | |
| msg, sizeof(struct scifmsg)))) { | |
| mdelay(1); | |
| #define SCIF_NODEQP_SEND_TO_MSEC (3 * 1000) | |
| if (loop_cnt++ > (SCIF_NODEQP_SEND_TO_MSEC)) { | |
| err = -ENODEV; | |
| break; | |
| } | |
| } | |
| if (!err) | |
| scif_rb_commit(&qp->outbound_q); | |
| spin_unlock(&qp->send_lock); | |
| if (!err) { | |
| if (scifdev_self(scifdev)) | |
| /* | |
| * For loopback we need to emulate an interrupt by | |
| * queuing work for the queue handling real node | |
| * Qp interrupts. | |
| */ | |
| queue_work(scifdev->intr_wq, &scifdev->intr_bh); | |
| else | |
| scif_send_msg_intr(scifdev); | |
| } | |
| error: | |
| if (err) | |
| dev_dbg(&scifdev->sdev->dev, | |
| "%s %d error %d uop %d\n", | |
| __func__, __LINE__, err, msg->uop); | |
| return err; | |
| } | |
| /** | |
| * scif_nodeqp_send - Send a message on the node queue pair | |
| * @scifdev: Scif Device. | |
| * @msg: The message to be sent. | |
| */ | |
| int scif_nodeqp_send(struct scif_dev *scifdev, struct scifmsg *msg) | |
| { | |
| int err; | |
| struct device *spdev = NULL; | |
| if (msg->uop > SCIF_EXIT_ACK) { | |
| /* Dont send messages once the exit flow has begun */ | |
| if (OP_IDLE != scifdev->exit) | |
| return -ENODEV; | |
| spdev = scif_get_peer_dev(scifdev); | |
| if (IS_ERR(spdev)) { | |
| err = PTR_ERR(spdev); | |
| return err; | |
| } | |
| } | |
| err = _scif_nodeqp_send(scifdev, msg); | |
| if (msg->uop > SCIF_EXIT_ACK) | |
| scif_put_peer_dev(spdev); | |
| return err; | |
| } | |
| /* | |
| * scif_misc_handler: | |
| * | |
| * Work queue handler for servicing miscellaneous SCIF tasks. | |
| * Examples include: | |
| * 1) Remote fence requests. | |
| * 2) Destruction of temporary registered windows | |
| * created during scif_vreadfrom()/scif_vwriteto(). | |
| * 3) Cleanup of zombie endpoints. | |
| */ | |
| void scif_misc_handler(struct work_struct *work) | |
| { | |
| scif_rma_handle_remote_fences(); | |
| scif_rma_destroy_windows(); | |
| scif_rma_destroy_tcw_invalid(); | |
| scif_cleanup_zombie_epd(); | |
| } | |
| /** | |
| * scif_init() - Respond to SCIF_INIT interrupt message | |
| * @scifdev: Remote SCIF device node | |
| * @msg: Interrupt message | |
| */ | |
| static __always_inline void | |
| scif_init(struct scif_dev *scifdev, struct scifmsg *msg) | |
| { | |
| /* | |
| * Allow the thread waiting for device page updates for the peer QP DMA | |
| * address to complete initializing the inbound_q. | |
| */ | |
| flush_delayed_work(&scifdev->qp_dwork); | |
| scif_peer_register_device(scifdev); | |
| if (scif_is_mgmt_node()) { | |
| mutex_lock(&scif_info.conflock); | |
| scif_p2p_setup(); | |
| mutex_unlock(&scif_info.conflock); | |
| } | |
| } | |
| /** | |
| * scif_exit() - Respond to SCIF_EXIT interrupt message | |
| * @scifdev: Remote SCIF device node | |
| * @msg: Interrupt message | |
| * | |
| * This function stops the SCIF interface for the node which sent | |
| * the SCIF_EXIT message and starts waiting for that node to | |
| * resetup the queue pair again. | |
| */ | |
| static __always_inline void | |
| scif_exit(struct scif_dev *scifdev, struct scifmsg *unused) | |
| { | |
| scifdev->exit_ack_pending = true; | |
| if (scif_is_mgmt_node()) | |
| scif_disconnect_node(scifdev->node, false); | |
| else | |
| scif_stop(scifdev); | |
| schedule_delayed_work(&scifdev->qp_dwork, | |
| msecs_to_jiffies(1000)); | |
| } | |
| /** | |
| * scif_exitack() - Respond to SCIF_EXIT_ACK interrupt message | |
| * @scifdev: Remote SCIF device node | |
| * @msg: Interrupt message | |
| * | |
| */ | |
| static __always_inline void | |
| scif_exit_ack(struct scif_dev *scifdev, struct scifmsg *unused) | |
| { | |
| scifdev->exit = OP_COMPLETED; | |
| wake_up(&scif_info.exitwq); | |
| } | |
| /** | |
| * scif_node_add() - Respond to SCIF_NODE_ADD interrupt message | |
| * @scifdev: Remote SCIF device node | |
| * @msg: Interrupt message | |
| * | |
| * When the mgmt node driver has finished initializing a MIC node queue pair it | |
| * marks the node as online. It then looks for all currently online MIC cards | |
| * and send a SCIF_NODE_ADD message to identify the ID of the new card for | |
| * peer to peer initialization | |
| * | |
| * The local node allocates its incoming queue and sends its address in the | |
| * SCIF_NODE_ADD_ACK message back to the mgmt node, the mgmt node "reflects" | |
| * this message to the new node | |
| */ | |
| static __always_inline void | |
| scif_node_add(struct scif_dev *scifdev, struct scifmsg *msg) | |
| { | |
| struct scif_dev *newdev; | |
| dma_addr_t qp_offset; | |
| int qp_connect; | |
| struct scif_hw_dev *sdev; | |
| dev_dbg(&scifdev->sdev->dev, | |
| "Scifdev %d:%d received NODE_ADD msg for node %d\n", | |
| scifdev->node, msg->dst.node, msg->src.node); | |
| dev_dbg(&scifdev->sdev->dev, | |
| "Remote address for this node's aperture %llx\n", | |
| msg->payload[0]); | |
| newdev = &scif_dev[msg->src.node]; | |
| newdev->node = msg->src.node; | |
| newdev->sdev = scif_dev[SCIF_MGMT_NODE].sdev; | |
| sdev = newdev->sdev; | |
| if (scif_setup_intr_wq(newdev)) { | |
| dev_err(&scifdev->sdev->dev, | |
| "failed to setup interrupts for %d\n", msg->src.node); | |
| goto interrupt_setup_error; | |
| } | |
| newdev->mmio.va = ioremap_nocache(msg->payload[1], sdev->mmio->len); | |
| if (!newdev->mmio.va) { | |
| dev_err(&scifdev->sdev->dev, | |
| "failed to map mmio for %d\n", msg->src.node); | |
| goto mmio_map_error; | |
| } | |
| newdev->qpairs = kzalloc(sizeof(*newdev->qpairs), GFP_KERNEL); | |
| if (!newdev->qpairs) | |
| goto qp_alloc_error; | |
| /* | |
| * Set the base address of the remote node's memory since it gets | |
| * added to qp_offset | |
| */ | |
| newdev->base_addr = msg->payload[0]; | |
| qp_connect = scif_setup_qp_connect(newdev->qpairs, &qp_offset, | |
| SCIF_NODE_QP_SIZE, newdev); | |
| if (qp_connect) { | |
| dev_err(&scifdev->sdev->dev, | |
| "failed to setup qp_connect %d\n", qp_connect); | |
| goto qp_connect_error; | |
| } | |
| newdev->db = sdev->hw_ops->next_db(sdev); | |
| newdev->cookie = sdev->hw_ops->request_irq(sdev, scif_intr_handler, | |
| "SCIF_INTR", newdev, | |
| newdev->db); | |
| if (IS_ERR(newdev->cookie)) | |
| goto qp_connect_error; | |
| newdev->qpairs->magic = SCIFEP_MAGIC; | |
| newdev->qpairs->qp_state = SCIF_QP_OFFLINE; | |
| msg->uop = SCIF_NODE_ADD_ACK; | |
| msg->dst.node = msg->src.node; | |
| msg->src.node = scif_info.nodeid; | |
| msg->payload[0] = qp_offset; | |
| msg->payload[2] = newdev->db; | |
| scif_nodeqp_send(&scif_dev[SCIF_MGMT_NODE], msg); | |
| return; | |
| qp_connect_error: | |
| kfree(newdev->qpairs); | |
| newdev->qpairs = NULL; | |
| qp_alloc_error: | |
| iounmap(newdev->mmio.va); | |
| newdev->mmio.va = NULL; | |
| mmio_map_error: | |
| interrupt_setup_error: | |
| dev_err(&scifdev->sdev->dev, | |
| "node add failed for node %d\n", msg->src.node); | |
| msg->uop = SCIF_NODE_ADD_NACK; | |
| msg->dst.node = msg->src.node; | |
| msg->src.node = scif_info.nodeid; | |
| scif_nodeqp_send(&scif_dev[SCIF_MGMT_NODE], msg); | |
| } | |
| void scif_poll_qp_state(struct work_struct *work) | |
| { | |
| #define SCIF_NODE_QP_RETRY 100 | |
| #define SCIF_NODE_QP_TIMEOUT 100 | |
| struct scif_dev *peerdev = container_of(work, struct scif_dev, | |
| p2p_dwork.work); | |
| struct scif_qp *qp = &peerdev->qpairs[0]; | |
| if (qp->qp_state != SCIF_QP_ONLINE || | |
| qp->remote_qp->qp_state != SCIF_QP_ONLINE) { | |
| if (peerdev->p2p_retry++ == SCIF_NODE_QP_RETRY) { | |
| dev_err(&peerdev->sdev->dev, | |
| "Warning: QP check timeout with state %d\n", | |
| qp->qp_state); | |
| goto timeout; | |
| } | |
| schedule_delayed_work(&peerdev->p2p_dwork, | |
| msecs_to_jiffies(SCIF_NODE_QP_TIMEOUT)); | |
| return; | |
| } | |
| return; | |
| timeout: | |
| dev_err(&peerdev->sdev->dev, | |
| "%s %d remote node %d offline, state = 0x%x\n", | |
| __func__, __LINE__, peerdev->node, qp->qp_state); | |
| qp->remote_qp->qp_state = SCIF_QP_OFFLINE; | |
| scif_peer_unregister_device(peerdev); | |
| scif_cleanup_scifdev(peerdev); | |
| } | |
| /** | |
| * scif_node_add_ack() - Respond to SCIF_NODE_ADD_ACK interrupt message | |
| * @scifdev: Remote SCIF device node | |
| * @msg: Interrupt message | |
| * | |
| * After a MIC node receives the SCIF_NODE_ADD_ACK message it send this | |
| * message to the mgmt node to confirm the sequence is finished. | |
| * | |
| */ | |
| static __always_inline void | |
| scif_node_add_ack(struct scif_dev *scifdev, struct scifmsg *msg) | |
| { | |
| struct scif_dev *peerdev; | |
| struct scif_qp *qp; | |
| struct scif_dev *dst_dev = &scif_dev[msg->dst.node]; | |
| dev_dbg(&scifdev->sdev->dev, | |
| "Scifdev %d received SCIF_NODE_ADD_ACK msg src %d dst %d\n", | |
| scifdev->node, msg->src.node, msg->dst.node); | |
| dev_dbg(&scifdev->sdev->dev, | |
| "payload %llx %llx %llx %llx\n", msg->payload[0], | |
| msg->payload[1], msg->payload[2], msg->payload[3]); | |
| if (scif_is_mgmt_node()) { | |
| /* | |
| * the lock serializes with scif_qp_response_ack. The mgmt node | |
| * is forwarding the NODE_ADD_ACK message from src to dst we | |
| * need to make sure that the dst has already received a | |
| * NODE_ADD for src and setup its end of the qp to dst | |
| */ | |
| mutex_lock(&scif_info.conflock); | |
| msg->payload[1] = scif_info.maxid; | |
| scif_nodeqp_send(dst_dev, msg); | |
| mutex_unlock(&scif_info.conflock); | |
| return; | |
| } | |
| peerdev = &scif_dev[msg->src.node]; | |
| peerdev->sdev = scif_dev[SCIF_MGMT_NODE].sdev; | |
| peerdev->node = msg->src.node; | |
| qp = &peerdev->qpairs[0]; | |
| if ((scif_setup_qp_connect_response(peerdev, &peerdev->qpairs[0], | |
| msg->payload[0]))) | |
| goto local_error; | |
| peerdev->rdb = msg->payload[2]; | |
| qp->remote_qp->qp_state = SCIF_QP_ONLINE; | |
| scif_peer_register_device(peerdev); | |
| schedule_delayed_work(&peerdev->p2p_dwork, 0); | |
| return; | |
| local_error: | |
| scif_cleanup_scifdev(peerdev); | |
| } | |
| /** | |
| * scif_node_add_nack: Respond to SCIF_NODE_ADD_NACK interrupt message | |
| * @msg: Interrupt message | |
| * | |
| * SCIF_NODE_ADD failed, so inform the waiting wq. | |
| */ | |
| static __always_inline void | |
| scif_node_add_nack(struct scif_dev *scifdev, struct scifmsg *msg) | |
| { | |
| if (scif_is_mgmt_node()) { | |
| struct scif_dev *dst_dev = &scif_dev[msg->dst.node]; | |
| dev_dbg(&scifdev->sdev->dev, | |
| "SCIF_NODE_ADD_NACK received from %d\n", scifdev->node); | |
| scif_nodeqp_send(dst_dev, msg); | |
| } | |
| } | |
| /* | |
| * scif_node_remove: Handle SCIF_NODE_REMOVE message | |
| * @msg: Interrupt message | |
| * | |
| * Handle node removal. | |
| */ | |
| static __always_inline void | |
| scif_node_remove(struct scif_dev *scifdev, struct scifmsg *msg) | |
| { | |
| int node = msg->payload[0]; | |
| struct scif_dev *scdev = &scif_dev[node]; | |
| scdev->node_remove_ack_pending = true; | |
| scif_handle_remove_node(node); | |
| } | |
| /* | |
| * scif_node_remove_ack: Handle SCIF_NODE_REMOVE_ACK message | |
| * @msg: Interrupt message | |
| * | |
| * The peer has acked a SCIF_NODE_REMOVE message. | |
| */ | |
| static __always_inline void | |
| scif_node_remove_ack(struct scif_dev *scifdev, struct scifmsg *msg) | |
| { | |
| struct scif_dev *sdev = &scif_dev[msg->payload[0]]; | |
| atomic_inc(&sdev->disconn_rescnt); | |
| wake_up(&sdev->disconn_wq); | |
| } | |
| /** | |
| * scif_get_node_info: Respond to SCIF_GET_NODE_INFO interrupt message | |
| * @msg: Interrupt message | |
| * | |
| * Retrieve node info i.e maxid and total from the mgmt node. | |
| */ | |
| static __always_inline void | |
| scif_get_node_info_resp(struct scif_dev *scifdev, struct scifmsg *msg) | |
| { | |
| if (scif_is_mgmt_node()) { | |
| swap(msg->dst.node, msg->src.node); | |
| mutex_lock(&scif_info.conflock); | |
| msg->payload[1] = scif_info.maxid; | |
| msg->payload[2] = scif_info.total; | |
| mutex_unlock(&scif_info.conflock); | |
| scif_nodeqp_send(scifdev, msg); | |
| } else { | |
| struct completion *node_info = | |
| (struct completion *)msg->payload[3]; | |
| mutex_lock(&scif_info.conflock); | |
| scif_info.maxid = msg->payload[1]; | |
| scif_info.total = msg->payload[2]; | |
| complete_all(node_info); | |
| mutex_unlock(&scif_info.conflock); | |
| } | |
| } | |
| static void | |
| scif_msg_unknown(struct scif_dev *scifdev, struct scifmsg *msg) | |
| { | |
| /* Bogus Node Qp Message? */ | |
| dev_err(&scifdev->sdev->dev, | |
| "Unknown message 0x%xn scifdev->node 0x%x\n", | |
| msg->uop, scifdev->node); | |
| } | |
| static void (*scif_intr_func[SCIF_MAX_MSG + 1]) | |
| (struct scif_dev *, struct scifmsg *msg) = { | |
| scif_msg_unknown, /* Error */ | |
| scif_init, /* SCIF_INIT */ | |
| scif_exit, /* SCIF_EXIT */ | |
| scif_exit_ack, /* SCIF_EXIT_ACK */ | |
| scif_node_add, /* SCIF_NODE_ADD */ | |
| scif_node_add_ack, /* SCIF_NODE_ADD_ACK */ | |
| scif_node_add_nack, /* SCIF_NODE_ADD_NACK */ | |
| scif_node_remove, /* SCIF_NODE_REMOVE */ | |
| scif_node_remove_ack, /* SCIF_NODE_REMOVE_ACK */ | |
| scif_cnctreq, /* SCIF_CNCT_REQ */ | |
| scif_cnctgnt, /* SCIF_CNCT_GNT */ | |
| scif_cnctgnt_ack, /* SCIF_CNCT_GNTACK */ | |
| scif_cnctgnt_nack, /* SCIF_CNCT_GNTNACK */ | |
| scif_cnctrej, /* SCIF_CNCT_REJ */ | |
| scif_discnct, /* SCIF_DISCNCT */ | |
| scif_discnt_ack, /* SCIF_DISCNT_ACK */ | |
| scif_clientsend, /* SCIF_CLIENT_SENT */ | |
| scif_clientrcvd, /* SCIF_CLIENT_RCVD */ | |
| scif_get_node_info_resp,/* SCIF_GET_NODE_INFO */ | |
| scif_recv_reg, /* SCIF_REGISTER */ | |
| scif_recv_reg_ack, /* SCIF_REGISTER_ACK */ | |
| scif_recv_reg_nack, /* SCIF_REGISTER_NACK */ | |
| scif_recv_unreg, /* SCIF_UNREGISTER */ | |
| scif_recv_unreg_ack, /* SCIF_UNREGISTER_ACK */ | |
| scif_recv_unreg_nack, /* SCIF_UNREGISTER_NACK */ | |
| scif_alloc_req, /* SCIF_ALLOC_REQ */ | |
| scif_alloc_gnt_rej, /* SCIF_ALLOC_GNT */ | |
| scif_alloc_gnt_rej, /* SCIF_ALLOC_REJ */ | |
| scif_free_virt, /* SCIF_FREE_VIRT */ | |
| scif_recv_munmap, /* SCIF_MUNMAP */ | |
| scif_recv_mark, /* SCIF_MARK */ | |
| scif_recv_mark_resp, /* SCIF_MARK_ACK */ | |
| scif_recv_mark_resp, /* SCIF_MARK_NACK */ | |
| scif_recv_wait, /* SCIF_WAIT */ | |
| scif_recv_wait_resp, /* SCIF_WAIT_ACK */ | |
| scif_recv_wait_resp, /* SCIF_WAIT_NACK */ | |
| scif_recv_sig_local, /* SCIF_SIG_LOCAL */ | |
| scif_recv_sig_remote, /* SCIF_SIG_REMOTE */ | |
| scif_recv_sig_resp, /* SCIF_SIG_ACK */ | |
| scif_recv_sig_resp, /* SCIF_SIG_NACK */ | |
| }; | |
| /** | |
| * scif_nodeqp_msg_handler() - Common handler for node messages | |
| * @scifdev: Remote device to respond to | |
| * @qp: Remote memory pointer | |
| * @msg: The message to be handled. | |
| * | |
| * This routine calls the appropriate routine to handle a Node Qp | |
| * message receipt | |
| */ | |
| static int scif_max_msg_id = SCIF_MAX_MSG; | |
| static void | |
| scif_nodeqp_msg_handler(struct scif_dev *scifdev, | |
| struct scif_qp *qp, struct scifmsg *msg) | |
| { | |
| scif_display_message(scifdev, msg, "Rcvd"); | |
| if (msg->uop > (u32)scif_max_msg_id) { | |
| /* Bogus Node Qp Message? */ | |
| dev_err(&scifdev->sdev->dev, | |
| "Unknown message 0x%xn scifdev->node 0x%x\n", | |
| msg->uop, scifdev->node); | |
| return; | |
| } | |
| scif_intr_func[msg->uop](scifdev, msg); | |
| } | |
| /** | |
| * scif_nodeqp_intrhandler() - Interrupt handler for node messages | |
| * @scifdev: Remote device to respond to | |
| * @qp: Remote memory pointer | |
| * | |
| * This routine is triggered by the interrupt mechanism. It reads | |
| * messages from the node queue RB and calls the Node QP Message handling | |
| * routine. | |
| */ | |
| void scif_nodeqp_intrhandler(struct scif_dev *scifdev, struct scif_qp *qp) | |
| { | |
| struct scifmsg msg; | |
| int read_size; | |
| do { | |
| read_size = scif_rb_get_next(&qp->inbound_q, &msg, sizeof(msg)); | |
| if (!read_size) | |
| break; | |
| scif_nodeqp_msg_handler(scifdev, qp, &msg); | |
| /* | |
| * The node queue pair is unmapped so skip the read pointer | |
| * update after receipt of a SCIF_EXIT_ACK | |
| */ | |
| if (SCIF_EXIT_ACK == msg.uop) | |
| break; | |
| scif_rb_update_read_ptr(&qp->inbound_q); | |
| } while (1); | |
| } | |
| /** | |
| * scif_loopb_wq_handler - Loopback Workqueue Handler. | |
| * @work: loop back work | |
| * | |
| * This work queue routine is invoked by the loopback work queue handler. | |
| * It grabs the recv lock, dequeues any available messages from the head | |
| * of the loopback message list, calls the node QP message handler, | |
| * waits for it to return, then frees up this message and dequeues more | |
| * elements of the list if available. | |
| */ | |
| static void scif_loopb_wq_handler(struct work_struct *unused) | |
| { | |
| struct scif_dev *scifdev = scif_info.loopb_dev; | |
| struct scif_qp *qp = scifdev->qpairs; | |
| struct scif_loopb_msg *msg; | |
| do { | |
| msg = NULL; | |
| spin_lock(&qp->recv_lock); | |
| if (!list_empty(&scif_info.loopb_recv_q)) { | |
| msg = list_first_entry(&scif_info.loopb_recv_q, | |
| struct scif_loopb_msg, | |
| list); | |
| list_del(&msg->list); | |
| } | |
| spin_unlock(&qp->recv_lock); | |
| if (msg) { | |
| scif_nodeqp_msg_handler(scifdev, qp, &msg->msg); | |
| kfree(msg); | |
| } | |
| } while (msg); | |
| } | |
| /** | |
| * scif_loopb_msg_handler() - Workqueue handler for loopback messages. | |
| * @scifdev: SCIF device | |
| * @qp: Queue pair. | |
| * | |
| * This work queue routine is triggered when a loopback message is received. | |
| * | |
| * We need special handling for receiving Node Qp messages on a loopback SCIF | |
| * device via two workqueues for receiving messages. | |
| * | |
| * The reason we need the extra workqueue which is not required with *normal* | |
| * non-loopback SCIF devices is the potential classic deadlock described below: | |
| * | |
| * Thread A tries to send a message on a loopback SCIF device and blocks since | |
| * there is no space in the RB while it has the send_lock held or another | |
| * lock called lock X for example. | |
| * | |
| * Thread B: The Loopback Node QP message receive workqueue receives the message | |
| * and tries to send a message (eg an ACK) to the loopback SCIF device. It tries | |
| * to grab the send lock again or lock X and deadlocks with Thread A. The RB | |
| * cannot be drained any further due to this classic deadlock. | |
| * | |
| * In order to avoid deadlocks as mentioned above we have an extra level of | |
| * indirection achieved by having two workqueues. | |
| * 1) The first workqueue whose handler is scif_loopb_msg_handler reads | |
| * messages from the Node QP RB, adds them to a list and queues work for the | |
| * second workqueue. | |
| * | |
| * 2) The second workqueue whose handler is scif_loopb_wq_handler dequeues | |
| * messages from the list, handles them, frees up the memory and dequeues | |
| * more elements from the list if possible. | |
| */ | |
| int | |
| scif_loopb_msg_handler(struct scif_dev *scifdev, struct scif_qp *qp) | |
| { | |
| int read_size; | |
| struct scif_loopb_msg *msg; | |
| do { | |
| msg = kmalloc(sizeof(*msg), GFP_KERNEL); | |
| if (!msg) | |
| return -ENOMEM; | |
| read_size = scif_rb_get_next(&qp->inbound_q, &msg->msg, | |
| sizeof(struct scifmsg)); | |
| if (read_size != sizeof(struct scifmsg)) { | |
| kfree(msg); | |
| scif_rb_update_read_ptr(&qp->inbound_q); | |
| break; | |
| } | |
| spin_lock(&qp->recv_lock); | |
| list_add_tail(&msg->list, &scif_info.loopb_recv_q); | |
| spin_unlock(&qp->recv_lock); | |
| queue_work(scif_info.loopb_wq, &scif_info.loopb_work); | |
| scif_rb_update_read_ptr(&qp->inbound_q); | |
| } while (read_size == sizeof(struct scifmsg)); | |
| return read_size; | |
| } | |
| /** | |
| * scif_setup_loopback_qp - One time setup work for Loopback Node Qp. | |
| * @scifdev: SCIF device | |
| * | |
| * Sets up the required loopback workqueues, queue pairs and ring buffers | |
| */ | |
| int scif_setup_loopback_qp(struct scif_dev *scifdev) | |
| { | |
| int err = 0; | |
| void *local_q; | |
| struct scif_qp *qp; | |
| err = scif_setup_intr_wq(scifdev); | |
| if (err) | |
| goto exit; | |
| INIT_LIST_HEAD(&scif_info.loopb_recv_q); | |
| snprintf(scif_info.loopb_wqname, sizeof(scif_info.loopb_wqname), | |
| "SCIF LOOPB %d", scifdev->node); | |
| scif_info.loopb_wq = | |
| alloc_ordered_workqueue(scif_info.loopb_wqname, 0); | |
| if (!scif_info.loopb_wq) { | |
| err = -ENOMEM; | |
| goto destroy_intr; | |
| } | |
| INIT_WORK(&scif_info.loopb_work, scif_loopb_wq_handler); | |
| /* Allocate Self Qpair */ | |
| scifdev->qpairs = kzalloc(sizeof(*scifdev->qpairs), GFP_KERNEL); | |
| if (!scifdev->qpairs) { | |
| err = -ENOMEM; | |
| goto destroy_loopb_wq; | |
| } | |
| qp = scifdev->qpairs; | |
| qp->magic = SCIFEP_MAGIC; | |
| spin_lock_init(&qp->send_lock); | |
| spin_lock_init(&qp->recv_lock); | |
| local_q = kzalloc(SCIF_NODE_QP_SIZE, GFP_KERNEL); | |
| if (!local_q) { | |
| err = -ENOMEM; | |
| goto free_qpairs; | |
| } | |
| /* | |
| * For loopback the inbound_q and outbound_q are essentially the same | |
| * since the Node sends a message on the loopback interface to the | |
| * outbound_q which is then received on the inbound_q. | |
| */ | |
| scif_rb_init(&qp->outbound_q, | |
| &qp->local_read, | |
| &qp->local_write, | |
| local_q, get_count_order(SCIF_NODE_QP_SIZE)); | |
| scif_rb_init(&qp->inbound_q, | |
| &qp->local_read, | |
| &qp->local_write, | |
| local_q, get_count_order(SCIF_NODE_QP_SIZE)); | |
| scif_info.nodeid = scifdev->node; | |
| scif_peer_register_device(scifdev); | |
| scif_info.loopb_dev = scifdev; | |
| return err; | |
| free_qpairs: | |
| kfree(scifdev->qpairs); | |
| destroy_loopb_wq: | |
| destroy_workqueue(scif_info.loopb_wq); | |
| destroy_intr: | |
| scif_destroy_intr_wq(scifdev); | |
| exit: | |
| return err; | |
| } | |
| /** | |
| * scif_destroy_loopback_qp - One time uninit work for Loopback Node Qp | |
| * @scifdev: SCIF device | |
| * | |
| * Destroys the workqueues and frees up the Ring Buffer and Queue Pair memory. | |
| */ | |
| int scif_destroy_loopback_qp(struct scif_dev *scifdev) | |
| { | |
| scif_peer_unregister_device(scifdev); | |
| destroy_workqueue(scif_info.loopb_wq); | |
| scif_destroy_intr_wq(scifdev); | |
| kfree(scifdev->qpairs->outbound_q.rb_base); | |
| kfree(scifdev->qpairs); | |
| scifdev->sdev = NULL; | |
| scif_info.loopb_dev = NULL; | |
| return 0; | |
| } | |
| void scif_destroy_p2p(struct scif_dev *scifdev) | |
| { | |
| struct scif_dev *peer_dev; | |
| struct scif_p2p_info *p2p; | |
| struct list_head *pos, *tmp; | |
| int bd; | |
| mutex_lock(&scif_info.conflock); | |
| /* Free P2P mappings in the given node for all its peer nodes */ | |
| list_for_each_safe(pos, tmp, &scifdev->p2p) { | |
| p2p = list_entry(pos, struct scif_p2p_info, ppi_list); | |
| dma_unmap_sg(&scifdev->sdev->dev, p2p->ppi_sg[SCIF_PPI_MMIO], | |
| p2p->sg_nentries[SCIF_PPI_MMIO], | |
| DMA_BIDIRECTIONAL); | |
| dma_unmap_sg(&scifdev->sdev->dev, p2p->ppi_sg[SCIF_PPI_APER], | |
| p2p->sg_nentries[SCIF_PPI_APER], | |
| DMA_BIDIRECTIONAL); | |
| scif_p2p_freesg(p2p->ppi_sg[SCIF_PPI_MMIO]); | |
| scif_p2p_freesg(p2p->ppi_sg[SCIF_PPI_APER]); | |
| list_del(pos); | |
| kfree(p2p); | |
| } | |
| /* Free P2P mapping created in the peer nodes for the given node */ | |
| for (bd = SCIF_MGMT_NODE + 1; bd <= scif_info.maxid; bd++) { | |
| peer_dev = &scif_dev[bd]; | |
| list_for_each_safe(pos, tmp, &peer_dev->p2p) { | |
| p2p = list_entry(pos, struct scif_p2p_info, ppi_list); | |
| if (p2p->ppi_peer_id == scifdev->node) { | |
| dma_unmap_sg(&peer_dev->sdev->dev, | |
| p2p->ppi_sg[SCIF_PPI_MMIO], | |
| p2p->sg_nentries[SCIF_PPI_MMIO], | |
| DMA_BIDIRECTIONAL); | |
| dma_unmap_sg(&peer_dev->sdev->dev, | |
| p2p->ppi_sg[SCIF_PPI_APER], | |
| p2p->sg_nentries[SCIF_PPI_APER], | |
| DMA_BIDIRECTIONAL); | |
| scif_p2p_freesg(p2p->ppi_sg[SCIF_PPI_MMIO]); | |
| scif_p2p_freesg(p2p->ppi_sg[SCIF_PPI_APER]); | |
| list_del(pos); | |
| kfree(p2p); | |
| } | |
| } | |
| } | |
| mutex_unlock(&scif_info.conflock); | |
| } |