| // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) |
| |
| #include <linux/irq.h> |
| #include <linux/module.h> |
| #include <linux/ntb.h> |
| #include <linux/msi.h> |
| #include <linux/pci.h> |
| |
| struct ntb_msi { |
| u64 base_addr; |
| u64 end_addr; |
| |
| void (*desc_changed)(void *ctx); |
| |
| u32 __iomem *peer_mws[]; |
| }; |
| |
| /** |
| * ntb_msi_init() - Initialize the MSI context |
| * @ntb: NTB device context |
| * |
| * This function must be called before any other ntb_msi function. |
| * It initializes the context for MSI operations and maps |
| * the peer memory windows. |
| * |
| * This function reserves the last N outbound memory windows (where N |
| * is the number of peers). |
| * |
| * Return: Zero on success, otherwise a negative error number. |
| */ |
| int ntb_msi_init(struct ntb_dev *ntb, |
| void (*desc_changed)(void *ctx)) |
| { |
| phys_addr_t mw_phys_addr; |
| resource_size_t mw_size; |
| int peer_widx; |
| int peers; |
| int ret; |
| int i; |
| |
| peers = ntb_peer_port_count(ntb); |
| if (peers <= 0) |
| return -EINVAL; |
| |
| ntb->msi = devm_kzalloc(&ntb->dev, struct_size(ntb->msi, peer_mws, peers), |
| GFP_KERNEL); |
| if (!ntb->msi) |
| return -ENOMEM; |
| |
| ntb->msi->desc_changed = desc_changed; |
| |
| for (i = 0; i < peers; i++) { |
| peer_widx = ntb_peer_mw_count(ntb) - 1 - i; |
| |
| ret = ntb_peer_mw_get_addr(ntb, peer_widx, &mw_phys_addr, |
| &mw_size); |
| if (ret) |
| goto unroll; |
| |
| ntb->msi->peer_mws[i] = devm_ioremap(&ntb->dev, mw_phys_addr, |
| mw_size); |
| if (!ntb->msi->peer_mws[i]) { |
| ret = -EFAULT; |
| goto unroll; |
| } |
| } |
| |
| return 0; |
| |
| unroll: |
| for (i = 0; i < peers; i++) |
| if (ntb->msi->peer_mws[i]) |
| devm_iounmap(&ntb->dev, ntb->msi->peer_mws[i]); |
| |
| devm_kfree(&ntb->dev, ntb->msi); |
| ntb->msi = NULL; |
| return ret; |
| } |
| EXPORT_SYMBOL(ntb_msi_init); |
| |
| /** |
| * ntb_msi_setup_mws() - Initialize the MSI inbound memory windows |
| * @ntb: NTB device context |
| * |
| * This function sets up the required inbound memory windows. It should be |
| * called from a work function after a link up event. |
| * |
| * Over the entire network, this function will reserves the last N |
| * inbound memory windows for each peer (where N is the number of peers). |
| * |
| * ntb_msi_init() must be called before this function. |
| * |
| * Return: Zero on success, otherwise a negative error number. |
| */ |
| int ntb_msi_setup_mws(struct ntb_dev *ntb) |
| { |
| struct msi_desc *desc; |
| u64 addr; |
| int peer, peer_widx; |
| resource_size_t addr_align, size_align, size_max; |
| resource_size_t mw_size = SZ_32K; |
| resource_size_t mw_min_size = mw_size; |
| int i; |
| int ret; |
| |
| if (!ntb->msi) |
| return -EINVAL; |
| |
| msi_lock_descs(&ntb->pdev->dev); |
| desc = msi_first_desc(&ntb->pdev->dev, MSI_DESC_ASSOCIATED); |
| addr = desc->msg.address_lo + ((uint64_t)desc->msg.address_hi << 32); |
| msi_unlock_descs(&ntb->pdev->dev); |
| |
| for (peer = 0; peer < ntb_peer_port_count(ntb); peer++) { |
| peer_widx = ntb_peer_highest_mw_idx(ntb, peer); |
| if (peer_widx < 0) |
| return peer_widx; |
| |
| ret = ntb_mw_get_align(ntb, peer, peer_widx, &addr_align, |
| NULL, NULL); |
| if (ret) |
| return ret; |
| |
| addr &= ~(addr_align - 1); |
| } |
| |
| for (peer = 0; peer < ntb_peer_port_count(ntb); peer++) { |
| peer_widx = ntb_peer_highest_mw_idx(ntb, peer); |
| if (peer_widx < 0) { |
| ret = peer_widx; |
| goto error_out; |
| } |
| |
| ret = ntb_mw_get_align(ntb, peer, peer_widx, NULL, |
| &size_align, &size_max); |
| if (ret) |
| goto error_out; |
| |
| mw_size = round_up(mw_size, size_align); |
| mw_size = max(mw_size, size_max); |
| if (mw_size < mw_min_size) |
| mw_min_size = mw_size; |
| |
| ret = ntb_mw_set_trans(ntb, peer, peer_widx, |
| addr, mw_size); |
| if (ret) |
| goto error_out; |
| } |
| |
| ntb->msi->base_addr = addr; |
| ntb->msi->end_addr = addr + mw_min_size; |
| |
| return 0; |
| |
| error_out: |
| for (i = 0; i < peer; i++) { |
| peer_widx = ntb_peer_highest_mw_idx(ntb, peer); |
| if (peer_widx < 0) |
| continue; |
| |
| ntb_mw_clear_trans(ntb, i, peer_widx); |
| } |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(ntb_msi_setup_mws); |
| |
| /** |
| * ntb_msi_clear_mws() - Clear all inbound memory windows |
| * @ntb: NTB device context |
| * |
| * This function tears down the resources used by ntb_msi_setup_mws(). |
| */ |
| void ntb_msi_clear_mws(struct ntb_dev *ntb) |
| { |
| int peer; |
| int peer_widx; |
| |
| for (peer = 0; peer < ntb_peer_port_count(ntb); peer++) { |
| peer_widx = ntb_peer_highest_mw_idx(ntb, peer); |
| if (peer_widx < 0) |
| continue; |
| |
| ntb_mw_clear_trans(ntb, peer, peer_widx); |
| } |
| } |
| EXPORT_SYMBOL(ntb_msi_clear_mws); |
| |
| struct ntb_msi_devres { |
| struct ntb_dev *ntb; |
| struct msi_desc *entry; |
| struct ntb_msi_desc *msi_desc; |
| }; |
| |
| static int ntb_msi_set_desc(struct ntb_dev *ntb, struct msi_desc *entry, |
| struct ntb_msi_desc *msi_desc) |
| { |
| u64 addr; |
| |
| addr = entry->msg.address_lo + |
| ((uint64_t)entry->msg.address_hi << 32); |
| |
| if (addr < ntb->msi->base_addr || addr >= ntb->msi->end_addr) { |
| dev_warn_once(&ntb->dev, |
| "IRQ %d: MSI Address not within the memory window (%llx, [%llx %llx])\n", |
| entry->irq, addr, ntb->msi->base_addr, |
| ntb->msi->end_addr); |
| return -EFAULT; |
| } |
| |
| msi_desc->addr_offset = addr - ntb->msi->base_addr; |
| msi_desc->data = entry->msg.data; |
| |
| return 0; |
| } |
| |
| static void ntb_msi_write_msg(struct msi_desc *entry, void *data) |
| { |
| struct ntb_msi_devres *dr = data; |
| |
| WARN_ON(ntb_msi_set_desc(dr->ntb, entry, dr->msi_desc)); |
| |
| if (dr->ntb->msi->desc_changed) |
| dr->ntb->msi->desc_changed(dr->ntb->ctx); |
| } |
| |
| static void ntbm_msi_callback_release(struct device *dev, void *res) |
| { |
| struct ntb_msi_devres *dr = res; |
| |
| dr->entry->write_msi_msg = NULL; |
| dr->entry->write_msi_msg_data = NULL; |
| } |
| |
| static int ntbm_msi_setup_callback(struct ntb_dev *ntb, struct msi_desc *entry, |
| struct ntb_msi_desc *msi_desc) |
| { |
| struct ntb_msi_devres *dr; |
| |
| dr = devres_alloc(ntbm_msi_callback_release, |
| sizeof(struct ntb_msi_devres), GFP_KERNEL); |
| if (!dr) |
| return -ENOMEM; |
| |
| dr->ntb = ntb; |
| dr->entry = entry; |
| dr->msi_desc = msi_desc; |
| |
| devres_add(&ntb->dev, dr); |
| |
| dr->entry->write_msi_msg = ntb_msi_write_msg; |
| dr->entry->write_msi_msg_data = dr; |
| |
| return 0; |
| } |
| |
| /** |
| * ntbm_msi_request_threaded_irq() - allocate an MSI interrupt |
| * @ntb: NTB device context |
| * @handler: Function to be called when the IRQ occurs |
| * @thread_fn: Function to be called in a threaded interrupt context. NULL |
| * for clients which handle everything in @handler |
| * @name: An ascii name for the claiming device, dev_name(dev) if NULL |
| * @dev_id: A cookie passed back to the handler function |
| * @msi_desc: MSI descriptor data which triggers the interrupt |
| * |
| * This function assigns an interrupt handler to an unused |
| * MSI interrupt and returns the descriptor used to trigger |
| * it. The descriptor can then be sent to a peer to trigger |
| * the interrupt. |
| * |
| * The interrupt resource is managed with devres so it will |
| * be automatically freed when the NTB device is torn down. |
| * |
| * If an IRQ allocated with this function needs to be freed |
| * separately, ntbm_free_irq() must be used. |
| * |
| * Return: IRQ number assigned on success, otherwise a negative error number. |
| */ |
| int ntbm_msi_request_threaded_irq(struct ntb_dev *ntb, irq_handler_t handler, |
| irq_handler_t thread_fn, |
| const char *name, void *dev_id, |
| struct ntb_msi_desc *msi_desc) |
| { |
| struct device *dev = &ntb->pdev->dev; |
| struct msi_desc *entry; |
| int ret; |
| |
| if (!ntb->msi) |
| return -EINVAL; |
| |
| msi_lock_descs(dev); |
| msi_for_each_desc(entry, dev, MSI_DESC_ASSOCIATED) { |
| if (irq_has_action(entry->irq)) |
| continue; |
| |
| ret = devm_request_threaded_irq(&ntb->dev, entry->irq, handler, |
| thread_fn, 0, name, dev_id); |
| if (ret) |
| continue; |
| |
| if (ntb_msi_set_desc(ntb, entry, msi_desc)) { |
| devm_free_irq(&ntb->dev, entry->irq, dev_id); |
| continue; |
| } |
| |
| ret = ntbm_msi_setup_callback(ntb, entry, msi_desc); |
| if (ret) { |
| devm_free_irq(&ntb->dev, entry->irq, dev_id); |
| goto unlock; |
| } |
| |
| ret = entry->irq; |
| goto unlock; |
| } |
| ret = -ENODEV; |
| |
| unlock: |
| msi_unlock_descs(dev); |
| return ret; |
| } |
| EXPORT_SYMBOL(ntbm_msi_request_threaded_irq); |
| |
| static int ntbm_msi_callback_match(struct device *dev, void *res, void *data) |
| { |
| struct ntb_dev *ntb = dev_ntb(dev); |
| struct ntb_msi_devres *dr = res; |
| |
| return dr->ntb == ntb && dr->entry == data; |
| } |
| |
| /** |
| * ntbm_msi_free_irq() - free an interrupt |
| * @ntb: NTB device context |
| * @irq: Interrupt line to free |
| * @dev_id: Device identity to free |
| * |
| * This function should be used to manually free IRQs allocated with |
| * ntbm_request_[threaded_]irq(). |
| */ |
| void ntbm_msi_free_irq(struct ntb_dev *ntb, unsigned int irq, void *dev_id) |
| { |
| struct msi_desc *entry = irq_get_msi_desc(irq); |
| |
| entry->write_msi_msg = NULL; |
| entry->write_msi_msg_data = NULL; |
| |
| WARN_ON(devres_destroy(&ntb->dev, ntbm_msi_callback_release, |
| ntbm_msi_callback_match, entry)); |
| |
| devm_free_irq(&ntb->dev, irq, dev_id); |
| } |
| EXPORT_SYMBOL(ntbm_msi_free_irq); |
| |
| /** |
| * ntb_msi_peer_trigger() - Trigger an interrupt handler on a peer |
| * @ntb: NTB device context |
| * @peer: Peer index |
| * @desc: MSI descriptor data which triggers the interrupt |
| * |
| * This function triggers an interrupt on a peer. It requires |
| * the descriptor structure to have been passed from that peer |
| * by some other means. |
| * |
| * Return: Zero on success, otherwise a negative error number. |
| */ |
| int ntb_msi_peer_trigger(struct ntb_dev *ntb, int peer, |
| struct ntb_msi_desc *desc) |
| { |
| int idx; |
| |
| if (!ntb->msi) |
| return -EINVAL; |
| |
| idx = desc->addr_offset / sizeof(*ntb->msi->peer_mws[peer]); |
| |
| iowrite32(desc->data, &ntb->msi->peer_mws[peer][idx]); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(ntb_msi_peer_trigger); |
| |
| /** |
| * ntb_msi_peer_addr() - Get the DMA address to trigger a peer's MSI interrupt |
| * @ntb: NTB device context |
| * @peer: Peer index |
| * @desc: MSI descriptor data which triggers the interrupt |
| * @msi_addr: Physical address to trigger the interrupt |
| * |
| * This function allows using DMA engines to trigger an interrupt |
| * (for example, trigger an interrupt to process the data after |
| * sending it). To trigger the interrupt, write @desc.data to the address |
| * returned in @msi_addr |
| * |
| * Return: Zero on success, otherwise a negative error number. |
| */ |
| int ntb_msi_peer_addr(struct ntb_dev *ntb, int peer, |
| struct ntb_msi_desc *desc, |
| phys_addr_t *msi_addr) |
| { |
| int peer_widx = ntb_peer_mw_count(ntb) - 1 - peer; |
| phys_addr_t mw_phys_addr; |
| int ret; |
| |
| ret = ntb_peer_mw_get_addr(ntb, peer_widx, &mw_phys_addr, NULL); |
| if (ret) |
| return ret; |
| |
| if (msi_addr) |
| *msi_addr = mw_phys_addr + desc->addr_offset; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(ntb_msi_peer_addr); |