| /* |
| * This file is provided under a dual BSD/GPLv2 license. When using or |
| * redistributing this file, you may do so under either license. |
| * |
| * GPL LICENSE SUMMARY |
| * |
| * Copyright (C) 2015 EMC Corporation. All Rights Reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of version 2 of the GNU General Public License 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. |
| * |
| * BSD LICENSE |
| * |
| * Copyright (C) 2015 EMC Corporation. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copy |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Intel Corporation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * PCIe NTB Pingpong Linux driver |
| * |
| * Contact Information: |
| * Allen Hubbe <Allen.Hubbe@emc.com> |
| */ |
| |
| /* Note: load this module with option 'dyndbg=+p' */ |
| |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| |
| #include <linux/dma-mapping.h> |
| #include <linux/pci.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/debugfs.h> |
| |
| #include <linux/ntb.h> |
| |
| #define DRIVER_NAME "ntb_pingpong" |
| #define DRIVER_DESCRIPTION "PCIe NTB Simple Pingpong Client" |
| |
| #define DRIVER_LICENSE "Dual BSD/GPL" |
| #define DRIVER_VERSION "1.0" |
| #define DRIVER_RELDATE "24 March 2015" |
| #define DRIVER_AUTHOR "Allen Hubbe <Allen.Hubbe@emc.com>" |
| |
| MODULE_LICENSE(DRIVER_LICENSE); |
| MODULE_VERSION(DRIVER_VERSION); |
| MODULE_AUTHOR(DRIVER_AUTHOR); |
| MODULE_DESCRIPTION(DRIVER_DESCRIPTION); |
| |
| static unsigned int unsafe; |
| module_param(unsafe, uint, 0644); |
| MODULE_PARM_DESC(unsafe, "Run even though ntb operations may be unsafe"); |
| |
| static unsigned int delay_ms = 1000; |
| module_param(delay_ms, uint, 0644); |
| MODULE_PARM_DESC(delay_ms, "Milliseconds to delay the response to peer"); |
| |
| static unsigned long db_init = 0x7; |
| module_param(db_init, ulong, 0644); |
| MODULE_PARM_DESC(db_init, "Initial doorbell bits to ring on the peer"); |
| |
| struct pp_ctx { |
| struct ntb_dev *ntb; |
| u64 db_bits; |
| /* synchronize access to db_bits by ping and pong */ |
| spinlock_t db_lock; |
| struct timer_list db_timer; |
| unsigned long db_delay; |
| struct dentry *debugfs_node_dir; |
| struct dentry *debugfs_count; |
| atomic_t count; |
| }; |
| |
| static struct dentry *pp_debugfs_dir; |
| |
| static void pp_ping(unsigned long ctx) |
| { |
| struct pp_ctx *pp = (void *)ctx; |
| unsigned long irqflags; |
| u64 db_bits, db_mask; |
| u32 spad_rd, spad_wr; |
| |
| spin_lock_irqsave(&pp->db_lock, irqflags); |
| { |
| db_mask = ntb_db_valid_mask(pp->ntb); |
| db_bits = ntb_db_read(pp->ntb); |
| |
| if (db_bits) { |
| dev_dbg(&pp->ntb->dev, |
| "Masked pongs %#llx\n", |
| db_bits); |
| ntb_db_clear(pp->ntb, db_bits); |
| } |
| |
| db_bits = ((pp->db_bits | db_bits) << 1) & db_mask; |
| |
| if (!db_bits) |
| db_bits = db_init; |
| |
| spad_rd = ntb_spad_read(pp->ntb, 0); |
| spad_wr = spad_rd + 1; |
| |
| dev_dbg(&pp->ntb->dev, |
| "Ping bits %#llx read %#x write %#x\n", |
| db_bits, spad_rd, spad_wr); |
| |
| ntb_peer_spad_write(pp->ntb, 0, spad_wr); |
| ntb_peer_db_set(pp->ntb, db_bits); |
| ntb_db_clear_mask(pp->ntb, db_mask); |
| |
| pp->db_bits = 0; |
| } |
| spin_unlock_irqrestore(&pp->db_lock, irqflags); |
| } |
| |
| static void pp_link_event(void *ctx) |
| { |
| struct pp_ctx *pp = ctx; |
| |
| if (ntb_link_is_up(pp->ntb, NULL, NULL) == 1) { |
| dev_dbg(&pp->ntb->dev, "link is up\n"); |
| pp_ping((unsigned long)pp); |
| } else { |
| dev_dbg(&pp->ntb->dev, "link is down\n"); |
| del_timer(&pp->db_timer); |
| } |
| } |
| |
| static void pp_db_event(void *ctx, int vec) |
| { |
| struct pp_ctx *pp = ctx; |
| u64 db_bits, db_mask; |
| unsigned long irqflags; |
| |
| spin_lock_irqsave(&pp->db_lock, irqflags); |
| { |
| db_mask = ntb_db_vector_mask(pp->ntb, vec); |
| db_bits = db_mask & ntb_db_read(pp->ntb); |
| ntb_db_set_mask(pp->ntb, db_mask); |
| ntb_db_clear(pp->ntb, db_bits); |
| |
| pp->db_bits |= db_bits; |
| |
| mod_timer(&pp->db_timer, jiffies + pp->db_delay); |
| |
| dev_dbg(&pp->ntb->dev, |
| "Pong vec %d bits %#llx\n", |
| vec, db_bits); |
| atomic_inc(&pp->count); |
| } |
| spin_unlock_irqrestore(&pp->db_lock, irqflags); |
| } |
| |
| static int pp_debugfs_setup(struct pp_ctx *pp) |
| { |
| struct pci_dev *pdev = pp->ntb->pdev; |
| |
| if (!pp_debugfs_dir) |
| return -ENODEV; |
| |
| pp->debugfs_node_dir = debugfs_create_dir(pci_name(pdev), |
| pp_debugfs_dir); |
| if (!pp->debugfs_node_dir) |
| return -ENODEV; |
| |
| pp->debugfs_count = debugfs_create_atomic_t("count", S_IRUSR | S_IWUSR, |
| pp->debugfs_node_dir, |
| &pp->count); |
| if (!pp->debugfs_count) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| static const struct ntb_ctx_ops pp_ops = { |
| .link_event = pp_link_event, |
| .db_event = pp_db_event, |
| }; |
| |
| static int pp_probe(struct ntb_client *client, |
| struct ntb_dev *ntb) |
| { |
| struct pp_ctx *pp; |
| int rc; |
| |
| if (ntb_db_is_unsafe(ntb)) { |
| dev_dbg(&ntb->dev, "doorbell is unsafe\n"); |
| if (!unsafe) { |
| rc = -EINVAL; |
| goto err_pp; |
| } |
| } |
| |
| if (ntb_spad_is_unsafe(ntb)) { |
| dev_dbg(&ntb->dev, "scratchpad is unsafe\n"); |
| if (!unsafe) { |
| rc = -EINVAL; |
| goto err_pp; |
| } |
| } |
| |
| pp = kmalloc(sizeof(*pp), GFP_KERNEL); |
| if (!pp) { |
| rc = -ENOMEM; |
| goto err_pp; |
| } |
| |
| pp->ntb = ntb; |
| pp->db_bits = 0; |
| atomic_set(&pp->count, 0); |
| spin_lock_init(&pp->db_lock); |
| setup_timer(&pp->db_timer, pp_ping, (unsigned long)pp); |
| pp->db_delay = msecs_to_jiffies(delay_ms); |
| |
| rc = ntb_set_ctx(ntb, pp, &pp_ops); |
| if (rc) |
| goto err_ctx; |
| |
| rc = pp_debugfs_setup(pp); |
| if (rc) |
| goto err_ctx; |
| |
| ntb_link_enable(ntb, NTB_SPEED_AUTO, NTB_WIDTH_AUTO); |
| ntb_link_event(ntb); |
| |
| return 0; |
| |
| err_ctx: |
| kfree(pp); |
| err_pp: |
| return rc; |
| } |
| |
| static void pp_remove(struct ntb_client *client, |
| struct ntb_dev *ntb) |
| { |
| struct pp_ctx *pp = ntb->ctx; |
| |
| debugfs_remove_recursive(pp->debugfs_node_dir); |
| |
| ntb_clear_ctx(ntb); |
| del_timer_sync(&pp->db_timer); |
| ntb_link_disable(ntb); |
| |
| kfree(pp); |
| } |
| |
| static struct ntb_client pp_client = { |
| .ops = { |
| .probe = pp_probe, |
| .remove = pp_remove, |
| }, |
| }; |
| |
| static int __init pp_init(void) |
| { |
| int rc; |
| |
| if (debugfs_initialized()) |
| pp_debugfs_dir = debugfs_create_dir(KBUILD_MODNAME, NULL); |
| |
| rc = ntb_register_client(&pp_client); |
| if (rc) |
| goto err_client; |
| |
| return 0; |
| |
| err_client: |
| debugfs_remove_recursive(pp_debugfs_dir); |
| return rc; |
| } |
| module_init(pp_init); |
| |
| static void __exit pp_exit(void) |
| { |
| ntb_unregister_client(&pp_client); |
| debugfs_remove_recursive(pp_debugfs_dir); |
| } |
| module_exit(pp_exit); |