mirror of https://gitee.com/openkylin/linux.git
2932 lines
91 KiB
C
2932 lines
91 KiB
C
/*
|
|
* This file is provided under a GPLv2 license. When using or
|
|
* redistributing this file, you may do so under that license.
|
|
*
|
|
* GPL LICENSE SUMMARY
|
|
*
|
|
* Copyright (C) 2016-2018 T-Platforms JSC All Rights Reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, one can be found http://www.gnu.org/licenses/.
|
|
*
|
|
* The full GNU General Public License is included in this distribution in
|
|
* the file called "COPYING".
|
|
*
|
|
* 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.
|
|
*
|
|
* IDT PCIe-switch NTB Linux driver
|
|
*
|
|
* Contact Information:
|
|
* Serge Semin <fancer.lancer@gmail.com>, <Sergey.Semin@t-platforms.ru>
|
|
*/
|
|
|
|
#include <linux/stddef.h>
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/sizes.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/aer.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/list.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/hwmon.h>
|
|
#include <linux/hwmon-sysfs.h>
|
|
#include <linux/ntb.h>
|
|
|
|
#include "ntb_hw_idt.h"
|
|
|
|
#define NTB_NAME "ntb_hw_idt"
|
|
#define NTB_DESC "IDT PCI-E Non-Transparent Bridge Driver"
|
|
#define NTB_VER "2.0"
|
|
#define NTB_IRQNAME "ntb_irq_idt"
|
|
|
|
MODULE_DESCRIPTION(NTB_DESC);
|
|
MODULE_VERSION(NTB_VER);
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR("T-platforms");
|
|
|
|
/*
|
|
* NT Endpoint registers table simplifying a loop access to the functionally
|
|
* related registers
|
|
*/
|
|
static const struct idt_ntb_regs ntdata_tbl = {
|
|
{ {IDT_NT_BARSETUP0, IDT_NT_BARLIMIT0,
|
|
IDT_NT_BARLTBASE0, IDT_NT_BARUTBASE0},
|
|
{IDT_NT_BARSETUP1, IDT_NT_BARLIMIT1,
|
|
IDT_NT_BARLTBASE1, IDT_NT_BARUTBASE1},
|
|
{IDT_NT_BARSETUP2, IDT_NT_BARLIMIT2,
|
|
IDT_NT_BARLTBASE2, IDT_NT_BARUTBASE2},
|
|
{IDT_NT_BARSETUP3, IDT_NT_BARLIMIT3,
|
|
IDT_NT_BARLTBASE3, IDT_NT_BARUTBASE3},
|
|
{IDT_NT_BARSETUP4, IDT_NT_BARLIMIT4,
|
|
IDT_NT_BARLTBASE4, IDT_NT_BARUTBASE4},
|
|
{IDT_NT_BARSETUP5, IDT_NT_BARLIMIT5,
|
|
IDT_NT_BARLTBASE5, IDT_NT_BARUTBASE5} },
|
|
{ {IDT_NT_INMSG0, IDT_NT_OUTMSG0, IDT_NT_INMSGSRC0},
|
|
{IDT_NT_INMSG1, IDT_NT_OUTMSG1, IDT_NT_INMSGSRC1},
|
|
{IDT_NT_INMSG2, IDT_NT_OUTMSG2, IDT_NT_INMSGSRC2},
|
|
{IDT_NT_INMSG3, IDT_NT_OUTMSG3, IDT_NT_INMSGSRC3} }
|
|
};
|
|
|
|
/*
|
|
* NT Endpoint ports data table with the corresponding pcie command, link
|
|
* status, control and BAR-related registers
|
|
*/
|
|
static const struct idt_ntb_port portdata_tbl[IDT_MAX_NR_PORTS] = {
|
|
/*0*/ { IDT_SW_NTP0_PCIECMDSTS, IDT_SW_NTP0_PCIELCTLSTS,
|
|
IDT_SW_NTP0_NTCTL,
|
|
IDT_SW_SWPORT0CTL, IDT_SW_SWPORT0STS,
|
|
{ {IDT_SW_NTP0_BARSETUP0, IDT_SW_NTP0_BARLIMIT0,
|
|
IDT_SW_NTP0_BARLTBASE0, IDT_SW_NTP0_BARUTBASE0},
|
|
{IDT_SW_NTP0_BARSETUP1, IDT_SW_NTP0_BARLIMIT1,
|
|
IDT_SW_NTP0_BARLTBASE1, IDT_SW_NTP0_BARUTBASE1},
|
|
{IDT_SW_NTP0_BARSETUP2, IDT_SW_NTP0_BARLIMIT2,
|
|
IDT_SW_NTP0_BARLTBASE2, IDT_SW_NTP0_BARUTBASE2},
|
|
{IDT_SW_NTP0_BARSETUP3, IDT_SW_NTP0_BARLIMIT3,
|
|
IDT_SW_NTP0_BARLTBASE3, IDT_SW_NTP0_BARUTBASE3},
|
|
{IDT_SW_NTP0_BARSETUP4, IDT_SW_NTP0_BARLIMIT4,
|
|
IDT_SW_NTP0_BARLTBASE4, IDT_SW_NTP0_BARUTBASE4},
|
|
{IDT_SW_NTP0_BARSETUP5, IDT_SW_NTP0_BARLIMIT5,
|
|
IDT_SW_NTP0_BARLTBASE5, IDT_SW_NTP0_BARUTBASE5} } },
|
|
/*1*/ {0},
|
|
/*2*/ { IDT_SW_NTP2_PCIECMDSTS, IDT_SW_NTP2_PCIELCTLSTS,
|
|
IDT_SW_NTP2_NTCTL,
|
|
IDT_SW_SWPORT2CTL, IDT_SW_SWPORT2STS,
|
|
{ {IDT_SW_NTP2_BARSETUP0, IDT_SW_NTP2_BARLIMIT0,
|
|
IDT_SW_NTP2_BARLTBASE0, IDT_SW_NTP2_BARUTBASE0},
|
|
{IDT_SW_NTP2_BARSETUP1, IDT_SW_NTP2_BARLIMIT1,
|
|
IDT_SW_NTP2_BARLTBASE1, IDT_SW_NTP2_BARUTBASE1},
|
|
{IDT_SW_NTP2_BARSETUP2, IDT_SW_NTP2_BARLIMIT2,
|
|
IDT_SW_NTP2_BARLTBASE2, IDT_SW_NTP2_BARUTBASE2},
|
|
{IDT_SW_NTP2_BARSETUP3, IDT_SW_NTP2_BARLIMIT3,
|
|
IDT_SW_NTP2_BARLTBASE3, IDT_SW_NTP2_BARUTBASE3},
|
|
{IDT_SW_NTP2_BARSETUP4, IDT_SW_NTP2_BARLIMIT4,
|
|
IDT_SW_NTP2_BARLTBASE4, IDT_SW_NTP2_BARUTBASE4},
|
|
{IDT_SW_NTP2_BARSETUP5, IDT_SW_NTP2_BARLIMIT5,
|
|
IDT_SW_NTP2_BARLTBASE5, IDT_SW_NTP2_BARUTBASE5} } },
|
|
/*3*/ {0},
|
|
/*4*/ { IDT_SW_NTP4_PCIECMDSTS, IDT_SW_NTP4_PCIELCTLSTS,
|
|
IDT_SW_NTP4_NTCTL,
|
|
IDT_SW_SWPORT4CTL, IDT_SW_SWPORT4STS,
|
|
{ {IDT_SW_NTP4_BARSETUP0, IDT_SW_NTP4_BARLIMIT0,
|
|
IDT_SW_NTP4_BARLTBASE0, IDT_SW_NTP4_BARUTBASE0},
|
|
{IDT_SW_NTP4_BARSETUP1, IDT_SW_NTP4_BARLIMIT1,
|
|
IDT_SW_NTP4_BARLTBASE1, IDT_SW_NTP4_BARUTBASE1},
|
|
{IDT_SW_NTP4_BARSETUP2, IDT_SW_NTP4_BARLIMIT2,
|
|
IDT_SW_NTP4_BARLTBASE2, IDT_SW_NTP4_BARUTBASE2},
|
|
{IDT_SW_NTP4_BARSETUP3, IDT_SW_NTP4_BARLIMIT3,
|
|
IDT_SW_NTP4_BARLTBASE3, IDT_SW_NTP4_BARUTBASE3},
|
|
{IDT_SW_NTP4_BARSETUP4, IDT_SW_NTP4_BARLIMIT4,
|
|
IDT_SW_NTP4_BARLTBASE4, IDT_SW_NTP4_BARUTBASE4},
|
|
{IDT_SW_NTP4_BARSETUP5, IDT_SW_NTP4_BARLIMIT5,
|
|
IDT_SW_NTP4_BARLTBASE5, IDT_SW_NTP4_BARUTBASE5} } },
|
|
/*5*/ {0},
|
|
/*6*/ { IDT_SW_NTP6_PCIECMDSTS, IDT_SW_NTP6_PCIELCTLSTS,
|
|
IDT_SW_NTP6_NTCTL,
|
|
IDT_SW_SWPORT6CTL, IDT_SW_SWPORT6STS,
|
|
{ {IDT_SW_NTP6_BARSETUP0, IDT_SW_NTP6_BARLIMIT0,
|
|
IDT_SW_NTP6_BARLTBASE0, IDT_SW_NTP6_BARUTBASE0},
|
|
{IDT_SW_NTP6_BARSETUP1, IDT_SW_NTP6_BARLIMIT1,
|
|
IDT_SW_NTP6_BARLTBASE1, IDT_SW_NTP6_BARUTBASE1},
|
|
{IDT_SW_NTP6_BARSETUP2, IDT_SW_NTP6_BARLIMIT2,
|
|
IDT_SW_NTP6_BARLTBASE2, IDT_SW_NTP6_BARUTBASE2},
|
|
{IDT_SW_NTP6_BARSETUP3, IDT_SW_NTP6_BARLIMIT3,
|
|
IDT_SW_NTP6_BARLTBASE3, IDT_SW_NTP6_BARUTBASE3},
|
|
{IDT_SW_NTP6_BARSETUP4, IDT_SW_NTP6_BARLIMIT4,
|
|
IDT_SW_NTP6_BARLTBASE4, IDT_SW_NTP6_BARUTBASE4},
|
|
{IDT_SW_NTP6_BARSETUP5, IDT_SW_NTP6_BARLIMIT5,
|
|
IDT_SW_NTP6_BARLTBASE5, IDT_SW_NTP6_BARUTBASE5} } },
|
|
/*7*/ {0},
|
|
/*8*/ { IDT_SW_NTP8_PCIECMDSTS, IDT_SW_NTP8_PCIELCTLSTS,
|
|
IDT_SW_NTP8_NTCTL,
|
|
IDT_SW_SWPORT8CTL, IDT_SW_SWPORT8STS,
|
|
{ {IDT_SW_NTP8_BARSETUP0, IDT_SW_NTP8_BARLIMIT0,
|
|
IDT_SW_NTP8_BARLTBASE0, IDT_SW_NTP8_BARUTBASE0},
|
|
{IDT_SW_NTP8_BARSETUP1, IDT_SW_NTP8_BARLIMIT1,
|
|
IDT_SW_NTP8_BARLTBASE1, IDT_SW_NTP8_BARUTBASE1},
|
|
{IDT_SW_NTP8_BARSETUP2, IDT_SW_NTP8_BARLIMIT2,
|
|
IDT_SW_NTP8_BARLTBASE2, IDT_SW_NTP8_BARUTBASE2},
|
|
{IDT_SW_NTP8_BARSETUP3, IDT_SW_NTP8_BARLIMIT3,
|
|
IDT_SW_NTP8_BARLTBASE3, IDT_SW_NTP8_BARUTBASE3},
|
|
{IDT_SW_NTP8_BARSETUP4, IDT_SW_NTP8_BARLIMIT4,
|
|
IDT_SW_NTP8_BARLTBASE4, IDT_SW_NTP8_BARUTBASE4},
|
|
{IDT_SW_NTP8_BARSETUP5, IDT_SW_NTP8_BARLIMIT5,
|
|
IDT_SW_NTP8_BARLTBASE5, IDT_SW_NTP8_BARUTBASE5} } },
|
|
/*9*/ {0},
|
|
/*10*/ {0},
|
|
/*11*/ {0},
|
|
/*12*/ { IDT_SW_NTP12_PCIECMDSTS, IDT_SW_NTP12_PCIELCTLSTS,
|
|
IDT_SW_NTP12_NTCTL,
|
|
IDT_SW_SWPORT12CTL, IDT_SW_SWPORT12STS,
|
|
{ {IDT_SW_NTP12_BARSETUP0, IDT_SW_NTP12_BARLIMIT0,
|
|
IDT_SW_NTP12_BARLTBASE0, IDT_SW_NTP12_BARUTBASE0},
|
|
{IDT_SW_NTP12_BARSETUP1, IDT_SW_NTP12_BARLIMIT1,
|
|
IDT_SW_NTP12_BARLTBASE1, IDT_SW_NTP12_BARUTBASE1},
|
|
{IDT_SW_NTP12_BARSETUP2, IDT_SW_NTP12_BARLIMIT2,
|
|
IDT_SW_NTP12_BARLTBASE2, IDT_SW_NTP12_BARUTBASE2},
|
|
{IDT_SW_NTP12_BARSETUP3, IDT_SW_NTP12_BARLIMIT3,
|
|
IDT_SW_NTP12_BARLTBASE3, IDT_SW_NTP12_BARUTBASE3},
|
|
{IDT_SW_NTP12_BARSETUP4, IDT_SW_NTP12_BARLIMIT4,
|
|
IDT_SW_NTP12_BARLTBASE4, IDT_SW_NTP12_BARUTBASE4},
|
|
{IDT_SW_NTP12_BARSETUP5, IDT_SW_NTP12_BARLIMIT5,
|
|
IDT_SW_NTP12_BARLTBASE5, IDT_SW_NTP12_BARUTBASE5} } },
|
|
/*13*/ {0},
|
|
/*14*/ {0},
|
|
/*15*/ {0},
|
|
/*16*/ { IDT_SW_NTP16_PCIECMDSTS, IDT_SW_NTP16_PCIELCTLSTS,
|
|
IDT_SW_NTP16_NTCTL,
|
|
IDT_SW_SWPORT16CTL, IDT_SW_SWPORT16STS,
|
|
{ {IDT_SW_NTP16_BARSETUP0, IDT_SW_NTP16_BARLIMIT0,
|
|
IDT_SW_NTP16_BARLTBASE0, IDT_SW_NTP16_BARUTBASE0},
|
|
{IDT_SW_NTP16_BARSETUP1, IDT_SW_NTP16_BARLIMIT1,
|
|
IDT_SW_NTP16_BARLTBASE1, IDT_SW_NTP16_BARUTBASE1},
|
|
{IDT_SW_NTP16_BARSETUP2, IDT_SW_NTP16_BARLIMIT2,
|
|
IDT_SW_NTP16_BARLTBASE2, IDT_SW_NTP16_BARUTBASE2},
|
|
{IDT_SW_NTP16_BARSETUP3, IDT_SW_NTP16_BARLIMIT3,
|
|
IDT_SW_NTP16_BARLTBASE3, IDT_SW_NTP16_BARUTBASE3},
|
|
{IDT_SW_NTP16_BARSETUP4, IDT_SW_NTP16_BARLIMIT4,
|
|
IDT_SW_NTP16_BARLTBASE4, IDT_SW_NTP16_BARUTBASE4},
|
|
{IDT_SW_NTP16_BARSETUP5, IDT_SW_NTP16_BARLIMIT5,
|
|
IDT_SW_NTP16_BARLTBASE5, IDT_SW_NTP16_BARUTBASE5} } },
|
|
/*17*/ {0},
|
|
/*18*/ {0},
|
|
/*19*/ {0},
|
|
/*20*/ { IDT_SW_NTP20_PCIECMDSTS, IDT_SW_NTP20_PCIELCTLSTS,
|
|
IDT_SW_NTP20_NTCTL,
|
|
IDT_SW_SWPORT20CTL, IDT_SW_SWPORT20STS,
|
|
{ {IDT_SW_NTP20_BARSETUP0, IDT_SW_NTP20_BARLIMIT0,
|
|
IDT_SW_NTP20_BARLTBASE0, IDT_SW_NTP20_BARUTBASE0},
|
|
{IDT_SW_NTP20_BARSETUP1, IDT_SW_NTP20_BARLIMIT1,
|
|
IDT_SW_NTP20_BARLTBASE1, IDT_SW_NTP20_BARUTBASE1},
|
|
{IDT_SW_NTP20_BARSETUP2, IDT_SW_NTP20_BARLIMIT2,
|
|
IDT_SW_NTP20_BARLTBASE2, IDT_SW_NTP20_BARUTBASE2},
|
|
{IDT_SW_NTP20_BARSETUP3, IDT_SW_NTP20_BARLIMIT3,
|
|
IDT_SW_NTP20_BARLTBASE3, IDT_SW_NTP20_BARUTBASE3},
|
|
{IDT_SW_NTP20_BARSETUP4, IDT_SW_NTP20_BARLIMIT4,
|
|
IDT_SW_NTP20_BARLTBASE4, IDT_SW_NTP20_BARUTBASE4},
|
|
{IDT_SW_NTP20_BARSETUP5, IDT_SW_NTP20_BARLIMIT5,
|
|
IDT_SW_NTP20_BARLTBASE5, IDT_SW_NTP20_BARUTBASE5} } },
|
|
/*21*/ {0},
|
|
/*22*/ {0},
|
|
/*23*/ {0}
|
|
};
|
|
|
|
/*
|
|
* IDT PCIe-switch partitions table with the corresponding control, status
|
|
* and messages control registers
|
|
*/
|
|
static const struct idt_ntb_part partdata_tbl[IDT_MAX_NR_PARTS] = {
|
|
/*0*/ { IDT_SW_SWPART0CTL, IDT_SW_SWPART0STS,
|
|
{IDT_SW_SWP0MSGCTL0, IDT_SW_SWP0MSGCTL1,
|
|
IDT_SW_SWP0MSGCTL2, IDT_SW_SWP0MSGCTL3} },
|
|
/*1*/ { IDT_SW_SWPART1CTL, IDT_SW_SWPART1STS,
|
|
{IDT_SW_SWP1MSGCTL0, IDT_SW_SWP1MSGCTL1,
|
|
IDT_SW_SWP1MSGCTL2, IDT_SW_SWP1MSGCTL3} },
|
|
/*2*/ { IDT_SW_SWPART2CTL, IDT_SW_SWPART2STS,
|
|
{IDT_SW_SWP2MSGCTL0, IDT_SW_SWP2MSGCTL1,
|
|
IDT_SW_SWP2MSGCTL2, IDT_SW_SWP2MSGCTL3} },
|
|
/*3*/ { IDT_SW_SWPART3CTL, IDT_SW_SWPART3STS,
|
|
{IDT_SW_SWP3MSGCTL0, IDT_SW_SWP3MSGCTL1,
|
|
IDT_SW_SWP3MSGCTL2, IDT_SW_SWP3MSGCTL3} },
|
|
/*4*/ { IDT_SW_SWPART4CTL, IDT_SW_SWPART4STS,
|
|
{IDT_SW_SWP4MSGCTL0, IDT_SW_SWP4MSGCTL1,
|
|
IDT_SW_SWP4MSGCTL2, IDT_SW_SWP4MSGCTL3} },
|
|
/*5*/ { IDT_SW_SWPART5CTL, IDT_SW_SWPART5STS,
|
|
{IDT_SW_SWP5MSGCTL0, IDT_SW_SWP5MSGCTL1,
|
|
IDT_SW_SWP5MSGCTL2, IDT_SW_SWP5MSGCTL3} },
|
|
/*6*/ { IDT_SW_SWPART6CTL, IDT_SW_SWPART6STS,
|
|
{IDT_SW_SWP6MSGCTL0, IDT_SW_SWP6MSGCTL1,
|
|
IDT_SW_SWP6MSGCTL2, IDT_SW_SWP6MSGCTL3} },
|
|
/*7*/ { IDT_SW_SWPART7CTL, IDT_SW_SWPART7STS,
|
|
{IDT_SW_SWP7MSGCTL0, IDT_SW_SWP7MSGCTL1,
|
|
IDT_SW_SWP7MSGCTL2, IDT_SW_SWP7MSGCTL3} }
|
|
};
|
|
|
|
/*
|
|
* DebugFS directory to place the driver debug file
|
|
*/
|
|
static struct dentry *dbgfs_topdir;
|
|
|
|
/*=============================================================================
|
|
* 1. IDT PCIe-switch registers IO-functions
|
|
*
|
|
* Beside ordinary configuration space registers IDT PCIe-switch expose
|
|
* global configuration registers, which are used to determine state of other
|
|
* device ports as well as being notified of some switch-related events.
|
|
* Additionally all the configuration space registers of all the IDT
|
|
* PCIe-switch functions are mapped to the Global Address space, so each
|
|
* function can determine a configuration of any other PCI-function.
|
|
* Functions declared in this chapter are created to encapsulate access
|
|
* to configuration and global registers, so the driver code just need to
|
|
* provide IDT NTB hardware descriptor and a register address.
|
|
*=============================================================================
|
|
*/
|
|
|
|
/*
|
|
* idt_nt_write() - PCI configuration space registers write method
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
* @reg: Register to write data to
|
|
* @data: Value to write to the register
|
|
*
|
|
* IDT PCIe-switch registers are all Little endian.
|
|
*/
|
|
static void idt_nt_write(struct idt_ntb_dev *ndev,
|
|
const unsigned int reg, const u32 data)
|
|
{
|
|
/*
|
|
* It's obvious bug to request a register exceeding the maximum possible
|
|
* value as well as to have it unaligned.
|
|
*/
|
|
if (WARN_ON(reg > IDT_REG_PCI_MAX || !IS_ALIGNED(reg, IDT_REG_ALIGN)))
|
|
return;
|
|
|
|
/* Just write the value to the specified register */
|
|
iowrite32(data, ndev->cfgspc + (ptrdiff_t)reg);
|
|
}
|
|
|
|
/*
|
|
* idt_nt_read() - PCI configuration space registers read method
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
* @reg: Register to write data to
|
|
*
|
|
* IDT PCIe-switch Global configuration registers are all Little endian.
|
|
*
|
|
* Return: register value
|
|
*/
|
|
static u32 idt_nt_read(struct idt_ntb_dev *ndev, const unsigned int reg)
|
|
{
|
|
/*
|
|
* It's obvious bug to request a register exceeding the maximum possible
|
|
* value as well as to have it unaligned.
|
|
*/
|
|
if (WARN_ON(reg > IDT_REG_PCI_MAX || !IS_ALIGNED(reg, IDT_REG_ALIGN)))
|
|
return ~0;
|
|
|
|
/* Just read the value from the specified register */
|
|
return ioread32(ndev->cfgspc + (ptrdiff_t)reg);
|
|
}
|
|
|
|
/*
|
|
* idt_sw_write() - Global registers write method
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
* @reg: Register to write data to
|
|
* @data: Value to write to the register
|
|
*
|
|
* IDT PCIe-switch Global configuration registers are all Little endian.
|
|
*/
|
|
static void idt_sw_write(struct idt_ntb_dev *ndev,
|
|
const unsigned int reg, const u32 data)
|
|
{
|
|
unsigned long irqflags;
|
|
|
|
/*
|
|
* It's obvious bug to request a register exceeding the maximum possible
|
|
* value as well as to have it unaligned.
|
|
*/
|
|
if (WARN_ON(reg > IDT_REG_SW_MAX || !IS_ALIGNED(reg, IDT_REG_ALIGN)))
|
|
return;
|
|
|
|
/* Lock GASA registers operations */
|
|
spin_lock_irqsave(&ndev->gasa_lock, irqflags);
|
|
/* Set the global register address */
|
|
iowrite32((u32)reg, ndev->cfgspc + (ptrdiff_t)IDT_NT_GASAADDR);
|
|
/* Put the new value of the register */
|
|
iowrite32(data, ndev->cfgspc + (ptrdiff_t)IDT_NT_GASADATA);
|
|
/* Unlock GASA registers operations */
|
|
spin_unlock_irqrestore(&ndev->gasa_lock, irqflags);
|
|
}
|
|
|
|
/*
|
|
* idt_sw_read() - Global registers read method
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
* @reg: Register to write data to
|
|
*
|
|
* IDT PCIe-switch Global configuration registers are all Little endian.
|
|
*
|
|
* Return: register value
|
|
*/
|
|
static u32 idt_sw_read(struct idt_ntb_dev *ndev, const unsigned int reg)
|
|
{
|
|
unsigned long irqflags;
|
|
u32 data;
|
|
|
|
/*
|
|
* It's obvious bug to request a register exceeding the maximum possible
|
|
* value as well as to have it unaligned.
|
|
*/
|
|
if (WARN_ON(reg > IDT_REG_SW_MAX || !IS_ALIGNED(reg, IDT_REG_ALIGN)))
|
|
return ~0;
|
|
|
|
/* Lock GASA registers operations */
|
|
spin_lock_irqsave(&ndev->gasa_lock, irqflags);
|
|
/* Set the global register address */
|
|
iowrite32((u32)reg, ndev->cfgspc + (ptrdiff_t)IDT_NT_GASAADDR);
|
|
/* Get the data of the register (read ops acts as MMIO barrier) */
|
|
data = ioread32(ndev->cfgspc + (ptrdiff_t)IDT_NT_GASADATA);
|
|
/* Unlock GASA registers operations */
|
|
spin_unlock_irqrestore(&ndev->gasa_lock, irqflags);
|
|
|
|
return data;
|
|
}
|
|
|
|
/*
|
|
* idt_reg_set_bits() - set bits of a passed register
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
* @reg: Register to change bits of
|
|
* @reg_lock: Register access spin lock
|
|
* @valid_mask: Mask of valid bits
|
|
* @set_bits: Bitmask to set
|
|
*
|
|
* Helper method to check whether a passed bitfield is valid and set
|
|
* corresponding bits of a register.
|
|
*
|
|
* WARNING! Make sure the passed register isn't accessed over plane
|
|
* idt_nt_write() method (read method is ok to be used concurrently).
|
|
*
|
|
* Return: zero on success, negative error on invalid bitmask.
|
|
*/
|
|
static inline int idt_reg_set_bits(struct idt_ntb_dev *ndev, unsigned int reg,
|
|
spinlock_t *reg_lock,
|
|
u64 valid_mask, u64 set_bits)
|
|
{
|
|
unsigned long irqflags;
|
|
u32 data;
|
|
|
|
if (set_bits & ~(u64)valid_mask)
|
|
return -EINVAL;
|
|
|
|
/* Lock access to the register unless the change is written back */
|
|
spin_lock_irqsave(reg_lock, irqflags);
|
|
data = idt_nt_read(ndev, reg) | (u32)set_bits;
|
|
idt_nt_write(ndev, reg, data);
|
|
/* Unlock the register */
|
|
spin_unlock_irqrestore(reg_lock, irqflags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_reg_clear_bits() - clear bits of a passed register
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
* @reg: Register to change bits of
|
|
* @reg_lock: Register access spin lock
|
|
* @set_bits: Bitmask to clear
|
|
*
|
|
* Helper method to check whether a passed bitfield is valid and clear
|
|
* corresponding bits of a register.
|
|
*
|
|
* NOTE! Invalid bits are always considered cleared so it's not an error
|
|
* to clear them over.
|
|
*
|
|
* WARNING! Make sure the passed register isn't accessed over plane
|
|
* idt_nt_write() method (read method is ok to use concurrently).
|
|
*/
|
|
static inline void idt_reg_clear_bits(struct idt_ntb_dev *ndev,
|
|
unsigned int reg, spinlock_t *reg_lock,
|
|
u64 clear_bits)
|
|
{
|
|
unsigned long irqflags;
|
|
u32 data;
|
|
|
|
/* Lock access to the register unless the change is written back */
|
|
spin_lock_irqsave(reg_lock, irqflags);
|
|
data = idt_nt_read(ndev, reg) & ~(u32)clear_bits;
|
|
idt_nt_write(ndev, reg, data);
|
|
/* Unlock the register */
|
|
spin_unlock_irqrestore(reg_lock, irqflags);
|
|
}
|
|
|
|
/*===========================================================================
|
|
* 2. Ports operations
|
|
*
|
|
* IDT PCIe-switches can have from 3 up to 8 ports with possible
|
|
* NT-functions enabled. So all the possible ports need to be scanned looking
|
|
* for NTB activated. NTB API will have enumerated only the ports with NTB.
|
|
*===========================================================================
|
|
*/
|
|
|
|
/*
|
|
* idt_scan_ports() - scan IDT PCIe-switch ports collecting info in the tables
|
|
* @ndev: Pointer to the PCI device descriptor
|
|
*
|
|
* Return: zero on success, otherwise a negative error number.
|
|
*/
|
|
static int idt_scan_ports(struct idt_ntb_dev *ndev)
|
|
{
|
|
unsigned char pidx, port, part;
|
|
u32 data, portsts, partsts;
|
|
|
|
/* Retrieve the local port number */
|
|
data = idt_nt_read(ndev, IDT_NT_PCIELCAP);
|
|
ndev->port = GET_FIELD(PCIELCAP_PORTNUM, data);
|
|
|
|
/* Retrieve the local partition number */
|
|
portsts = idt_sw_read(ndev, portdata_tbl[ndev->port].sts);
|
|
ndev->part = GET_FIELD(SWPORTxSTS_SWPART, portsts);
|
|
|
|
/* Initialize port/partition -> index tables with invalid values */
|
|
memset(ndev->port_idx_map, -EINVAL, sizeof(ndev->port_idx_map));
|
|
memset(ndev->part_idx_map, -EINVAL, sizeof(ndev->part_idx_map));
|
|
|
|
/*
|
|
* Walk over all the possible ports checking whether any of them has
|
|
* NT-function activated
|
|
*/
|
|
ndev->peer_cnt = 0;
|
|
for (pidx = 0; pidx < ndev->swcfg->port_cnt; pidx++) {
|
|
port = ndev->swcfg->ports[pidx];
|
|
/* Skip local port */
|
|
if (port == ndev->port)
|
|
continue;
|
|
|
|
/* Read the port status register to get it partition */
|
|
portsts = idt_sw_read(ndev, portdata_tbl[port].sts);
|
|
part = GET_FIELD(SWPORTxSTS_SWPART, portsts);
|
|
|
|
/* Retrieve the partition status */
|
|
partsts = idt_sw_read(ndev, partdata_tbl[part].sts);
|
|
/* Check if partition state is active and port has NTB */
|
|
if (IS_FLD_SET(SWPARTxSTS_STATE, partsts, ACT) &&
|
|
(IS_FLD_SET(SWPORTxSTS_MODE, portsts, NT) ||
|
|
IS_FLD_SET(SWPORTxSTS_MODE, portsts, USNT) ||
|
|
IS_FLD_SET(SWPORTxSTS_MODE, portsts, USNTDMA) ||
|
|
IS_FLD_SET(SWPORTxSTS_MODE, portsts, NTDMA))) {
|
|
/* Save the port and partition numbers */
|
|
ndev->peers[ndev->peer_cnt].port = port;
|
|
ndev->peers[ndev->peer_cnt].part = part;
|
|
/* Fill in the port/partition -> index tables */
|
|
ndev->port_idx_map[port] = ndev->peer_cnt;
|
|
ndev->part_idx_map[part] = ndev->peer_cnt;
|
|
ndev->peer_cnt++;
|
|
}
|
|
}
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "Local port: %hhu, num of peers: %hhu\n",
|
|
ndev->port, ndev->peer_cnt);
|
|
|
|
/* It's useless to have this driver loaded if there is no any peer */
|
|
if (ndev->peer_cnt == 0) {
|
|
dev_warn(&ndev->ntb.pdev->dev, "No active peer found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_port_number() - get the local port number
|
|
* @ntb: NTB device context.
|
|
*
|
|
* Return: the local port number
|
|
*/
|
|
static int idt_ntb_port_number(struct ntb_dev *ntb)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
return ndev->port;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_peer_port_count() - get the number of peer ports
|
|
* @ntb: NTB device context.
|
|
*
|
|
* Return the count of detected peer NT-functions.
|
|
*
|
|
* Return: number of peer ports
|
|
*/
|
|
static int idt_ntb_peer_port_count(struct ntb_dev *ntb)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
return ndev->peer_cnt;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_peer_port_number() - get peer port by given index
|
|
* @ntb: NTB device context.
|
|
* @pidx: Peer port index.
|
|
*
|
|
* Return: peer port or negative error
|
|
*/
|
|
static int idt_ntb_peer_port_number(struct ntb_dev *ntb, int pidx)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
if (pidx < 0 || ndev->peer_cnt <= pidx)
|
|
return -EINVAL;
|
|
|
|
/* Return the detected NT-function port number */
|
|
return ndev->peers[pidx].port;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_peer_port_idx() - get peer port index by given port number
|
|
* @ntb: NTB device context.
|
|
* @port: Peer port number.
|
|
*
|
|
* Internal port -> index table is pre-initialized with -EINVAL values,
|
|
* so we just need to return it value
|
|
*
|
|
* Return: peer NT-function port index or negative error
|
|
*/
|
|
static int idt_ntb_peer_port_idx(struct ntb_dev *ntb, int port)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
if (port < 0 || IDT_MAX_NR_PORTS <= port)
|
|
return -EINVAL;
|
|
|
|
return ndev->port_idx_map[port];
|
|
}
|
|
|
|
/*===========================================================================
|
|
* 3. Link status operations
|
|
* There is no any ready-to-use method to have peer ports notified if NTB
|
|
* link is set up or got down. Instead global signal can be used instead.
|
|
* In case if any one of ports changes local NTB link state, it sends
|
|
* global signal and clears corresponding global state bit. Then all the ports
|
|
* receive a notification of that, so to make client driver being aware of
|
|
* possible NTB link change.
|
|
* Additionally each of active NT-functions is subscribed to PCIe-link
|
|
* state changes of peer ports.
|
|
*===========================================================================
|
|
*/
|
|
|
|
static void idt_ntb_local_link_disable(struct idt_ntb_dev *ndev);
|
|
|
|
/*
|
|
* idt_init_link() - Initialize NTB link state notification subsystem
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* Function performs the basic initialization of some global registers
|
|
* needed to enable IRQ-based notifications of PCIe Link Up/Down and
|
|
* Global Signal events.
|
|
* NOTE Since it's not possible to determine when all the NTB peer drivers are
|
|
* unloaded as well as have those registers accessed concurrently, we must
|
|
* preinitialize them with the same value and leave it uncleared on local
|
|
* driver unload.
|
|
*/
|
|
static void idt_init_link(struct idt_ntb_dev *ndev)
|
|
{
|
|
u32 part_mask, port_mask, se_mask;
|
|
unsigned char pidx;
|
|
|
|
/* Initialize spin locker of Mapping Table access registers */
|
|
spin_lock_init(&ndev->mtbl_lock);
|
|
|
|
/* Walk over all detected peers collecting port and partition masks */
|
|
port_mask = ~BIT(ndev->port);
|
|
part_mask = ~BIT(ndev->part);
|
|
for (pidx = 0; pidx < ndev->peer_cnt; pidx++) {
|
|
port_mask &= ~BIT(ndev->peers[pidx].port);
|
|
part_mask &= ~BIT(ndev->peers[pidx].part);
|
|
}
|
|
|
|
/* Clean the Link Up/Down and GLobal Signal status registers */
|
|
idt_sw_write(ndev, IDT_SW_SELINKUPSTS, (u32)-1);
|
|
idt_sw_write(ndev, IDT_SW_SELINKDNSTS, (u32)-1);
|
|
idt_sw_write(ndev, IDT_SW_SEGSIGSTS, (u32)-1);
|
|
|
|
/* Unmask NT-activated partitions to receive Global Switch events */
|
|
idt_sw_write(ndev, IDT_SW_SEPMSK, part_mask);
|
|
|
|
/* Enable PCIe Link Up events of NT-activated ports */
|
|
idt_sw_write(ndev, IDT_SW_SELINKUPMSK, port_mask);
|
|
|
|
/* Enable PCIe Link Down events of NT-activated ports */
|
|
idt_sw_write(ndev, IDT_SW_SELINKDNMSK, port_mask);
|
|
|
|
/* Unmask NT-activated partitions to receive Global Signal events */
|
|
idt_sw_write(ndev, IDT_SW_SEGSIGMSK, part_mask);
|
|
|
|
/* Unmask Link Up/Down and Global Switch Events */
|
|
se_mask = ~(IDT_SEMSK_LINKUP | IDT_SEMSK_LINKDN | IDT_SEMSK_GSIGNAL);
|
|
idt_sw_write(ndev, IDT_SW_SEMSK, se_mask);
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "NTB link status events initialized");
|
|
}
|
|
|
|
/*
|
|
* idt_deinit_link() - deinitialize link subsystem
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* Just disable the link back.
|
|
*/
|
|
static void idt_deinit_link(struct idt_ntb_dev *ndev)
|
|
{
|
|
/* Disable the link */
|
|
idt_ntb_local_link_disable(ndev);
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "NTB link status events deinitialized");
|
|
}
|
|
|
|
/*
|
|
* idt_se_isr() - switch events ISR
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
* @ntint_sts: NT-function interrupt status
|
|
*
|
|
* This driver doesn't support IDT PCIe-switch dynamic reconfigurations,
|
|
* Failover capability, etc, so switch events are utilized to notify of
|
|
* PCIe and NTB link events.
|
|
* The method is called from PCIe ISR bottom-half routine.
|
|
*/
|
|
static void idt_se_isr(struct idt_ntb_dev *ndev, u32 ntint_sts)
|
|
{
|
|
u32 sests;
|
|
|
|
/* Read Switch Events status */
|
|
sests = idt_sw_read(ndev, IDT_SW_SESTS);
|
|
|
|
/* Clean the Link Up/Down and Global Signal status registers */
|
|
idt_sw_write(ndev, IDT_SW_SELINKUPSTS, (u32)-1);
|
|
idt_sw_write(ndev, IDT_SW_SELINKDNSTS, (u32)-1);
|
|
idt_sw_write(ndev, IDT_SW_SEGSIGSTS, (u32)-1);
|
|
|
|
/* Clean the corresponding interrupt bit */
|
|
idt_nt_write(ndev, IDT_NT_NTINTSTS, IDT_NTINTSTS_SEVENT);
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "SE IRQ detected %#08x (SESTS %#08x)",
|
|
ntint_sts, sests);
|
|
|
|
/* Notify the client driver of possible link state change */
|
|
ntb_link_event(&ndev->ntb);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_local_link_enable() - enable the local NTB link.
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* In order to enable the NTB link we need:
|
|
* - enable Completion TLPs translation
|
|
* - initialize mapping table to enable the Request ID translation
|
|
* - notify peers of NTB link state change
|
|
*/
|
|
static void idt_ntb_local_link_enable(struct idt_ntb_dev *ndev)
|
|
{
|
|
u32 reqid, mtbldata = 0;
|
|
unsigned long irqflags;
|
|
|
|
/* Enable the ID protection and Completion TLPs translation */
|
|
idt_nt_write(ndev, IDT_NT_NTCTL, IDT_NTCTL_CPEN);
|
|
|
|
/* Retrieve the current Requester ID (Bus:Device:Function) */
|
|
reqid = idt_nt_read(ndev, IDT_NT_REQIDCAP);
|
|
|
|
/*
|
|
* Set the corresponding NT Mapping table entry of port partition index
|
|
* with the data to perform the Request ID translation
|
|
*/
|
|
mtbldata = SET_FIELD(NTMTBLDATA_REQID, 0, reqid) |
|
|
SET_FIELD(NTMTBLDATA_PART, 0, ndev->part) |
|
|
IDT_NTMTBLDATA_VALID;
|
|
spin_lock_irqsave(&ndev->mtbl_lock, irqflags);
|
|
idt_nt_write(ndev, IDT_NT_NTMTBLADDR, ndev->part);
|
|
idt_nt_write(ndev, IDT_NT_NTMTBLDATA, mtbldata);
|
|
spin_unlock_irqrestore(&ndev->mtbl_lock, irqflags);
|
|
|
|
/* Notify the peers by setting and clearing the global signal bit */
|
|
idt_nt_write(ndev, IDT_NT_NTGSIGNAL, IDT_NTGSIGNAL_SET);
|
|
idt_sw_write(ndev, IDT_SW_SEGSIGSTS, (u32)1 << ndev->part);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_local_link_disable() - disable the local NTB link.
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* In order to enable the NTB link we need:
|
|
* - disable Completion TLPs translation
|
|
* - clear corresponding mapping table entry
|
|
* - notify peers of NTB link state change
|
|
*/
|
|
static void idt_ntb_local_link_disable(struct idt_ntb_dev *ndev)
|
|
{
|
|
unsigned long irqflags;
|
|
|
|
/* Disable Completion TLPs translation */
|
|
idt_nt_write(ndev, IDT_NT_NTCTL, 0);
|
|
|
|
/* Clear the corresponding NT Mapping table entry */
|
|
spin_lock_irqsave(&ndev->mtbl_lock, irqflags);
|
|
idt_nt_write(ndev, IDT_NT_NTMTBLADDR, ndev->part);
|
|
idt_nt_write(ndev, IDT_NT_NTMTBLDATA, 0);
|
|
spin_unlock_irqrestore(&ndev->mtbl_lock, irqflags);
|
|
|
|
/* Notify the peers by setting and clearing the global signal bit */
|
|
idt_nt_write(ndev, IDT_NT_NTGSIGNAL, IDT_NTGSIGNAL_SET);
|
|
idt_sw_write(ndev, IDT_SW_SEGSIGSTS, (u32)1 << ndev->part);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_local_link_is_up() - test wethter local NTB link is up
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* Local link is up under the following conditions:
|
|
* - Bus mastering is enabled
|
|
* - NTCTL has Completion TLPs translation enabled
|
|
* - Mapping table permits Request TLPs translation
|
|
* NOTE: We don't need to check PCIe link state since it's obviously
|
|
* up while we are able to communicate with IDT PCIe-switch
|
|
*
|
|
* Return: true if link is up, otherwise false
|
|
*/
|
|
static bool idt_ntb_local_link_is_up(struct idt_ntb_dev *ndev)
|
|
{
|
|
unsigned long irqflags;
|
|
u32 data;
|
|
|
|
/* Read the local Bus Master Enable status */
|
|
data = idt_nt_read(ndev, IDT_NT_PCICMDSTS);
|
|
if (!(data & IDT_PCICMDSTS_BME))
|
|
return false;
|
|
|
|
/* Read the local Completion TLPs translation enable status */
|
|
data = idt_nt_read(ndev, IDT_NT_NTCTL);
|
|
if (!(data & IDT_NTCTL_CPEN))
|
|
return false;
|
|
|
|
/* Read Mapping table entry corresponding to the local partition */
|
|
spin_lock_irqsave(&ndev->mtbl_lock, irqflags);
|
|
idt_nt_write(ndev, IDT_NT_NTMTBLADDR, ndev->part);
|
|
data = idt_nt_read(ndev, IDT_NT_NTMTBLDATA);
|
|
spin_unlock_irqrestore(&ndev->mtbl_lock, irqflags);
|
|
|
|
return !!(data & IDT_NTMTBLDATA_VALID);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_peer_link_is_up() - test whether peer NTB link is up
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
* @pidx: Peer port index
|
|
*
|
|
* Peer link is up under the following conditions:
|
|
* - PCIe link is up
|
|
* - Bus mastering is enabled
|
|
* - NTCTL has Completion TLPs translation enabled
|
|
* - Mapping table permits Request TLPs translation
|
|
*
|
|
* Return: true if link is up, otherwise false
|
|
*/
|
|
static bool idt_ntb_peer_link_is_up(struct idt_ntb_dev *ndev, int pidx)
|
|
{
|
|
unsigned long irqflags;
|
|
unsigned char port;
|
|
u32 data;
|
|
|
|
/* Retrieve the device port number */
|
|
port = ndev->peers[pidx].port;
|
|
|
|
/* Check whether PCIe link is up */
|
|
data = idt_sw_read(ndev, portdata_tbl[port].sts);
|
|
if (!(data & IDT_SWPORTxSTS_LINKUP))
|
|
return false;
|
|
|
|
/* Check whether bus mastering is enabled on the peer port */
|
|
data = idt_sw_read(ndev, portdata_tbl[port].pcicmdsts);
|
|
if (!(data & IDT_PCICMDSTS_BME))
|
|
return false;
|
|
|
|
/* Check if Completion TLPs translation is enabled on the peer port */
|
|
data = idt_sw_read(ndev, portdata_tbl[port].ntctl);
|
|
if (!(data & IDT_NTCTL_CPEN))
|
|
return false;
|
|
|
|
/* Read Mapping table entry corresponding to the peer partition */
|
|
spin_lock_irqsave(&ndev->mtbl_lock, irqflags);
|
|
idt_nt_write(ndev, IDT_NT_NTMTBLADDR, ndev->peers[pidx].part);
|
|
data = idt_nt_read(ndev, IDT_NT_NTMTBLDATA);
|
|
spin_unlock_irqrestore(&ndev->mtbl_lock, irqflags);
|
|
|
|
return !!(data & IDT_NTMTBLDATA_VALID);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_link_is_up() - get the current ntb link state (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @speed: OUT - The link speed expressed as PCIe generation number.
|
|
* @width: OUT - The link width expressed as the number of PCIe lanes.
|
|
*
|
|
* Get the bitfield of NTB link states for all peer ports
|
|
*
|
|
* Return: bitfield of indexed ports link state: bit is set/cleared if the
|
|
* link is up/down respectively.
|
|
*/
|
|
static u64 idt_ntb_link_is_up(struct ntb_dev *ntb,
|
|
enum ntb_speed *speed, enum ntb_width *width)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
unsigned char pidx;
|
|
u64 status;
|
|
u32 data;
|
|
|
|
/* Retrieve the local link speed and width */
|
|
if (speed != NULL || width != NULL) {
|
|
data = idt_nt_read(ndev, IDT_NT_PCIELCTLSTS);
|
|
if (speed != NULL)
|
|
*speed = GET_FIELD(PCIELCTLSTS_CLS, data);
|
|
if (width != NULL)
|
|
*width = GET_FIELD(PCIELCTLSTS_NLW, data);
|
|
}
|
|
|
|
/* If local NTB link isn't up then all the links are considered down */
|
|
if (!idt_ntb_local_link_is_up(ndev))
|
|
return 0;
|
|
|
|
/* Collect all the peer ports link states into the bitfield */
|
|
status = 0;
|
|
for (pidx = 0; pidx < ndev->peer_cnt; pidx++) {
|
|
if (idt_ntb_peer_link_is_up(ndev, pidx))
|
|
status |= ((u64)1 << pidx);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_link_enable() - enable local port ntb link (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @max_speed: The maximum link speed expressed as PCIe generation number.
|
|
* @max_width: The maximum link width expressed as the number of PCIe lanes.
|
|
*
|
|
* Enable just local NTB link. PCIe link parameters are ignored.
|
|
*
|
|
* Return: always zero.
|
|
*/
|
|
static int idt_ntb_link_enable(struct ntb_dev *ntb, enum ntb_speed speed,
|
|
enum ntb_width width)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
/* Just enable the local NTB link */
|
|
idt_ntb_local_link_enable(ndev);
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "Local NTB link enabled");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_link_disable() - disable local port ntb link (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
*
|
|
* Disable just local NTB link.
|
|
*
|
|
* Return: always zero.
|
|
*/
|
|
static int idt_ntb_link_disable(struct ntb_dev *ntb)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
/* Just disable the local NTB link */
|
|
idt_ntb_local_link_disable(ndev);
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "Local NTB link disabled");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*=============================================================================
|
|
* 4. Memory Window operations
|
|
*
|
|
* IDT PCIe-switches have two types of memory windows: MWs with direct
|
|
* address translation and MWs with LUT based translation. The first type of
|
|
* MWs is simple map of corresponding BAR address space to a memory space
|
|
* of specified target port. So it implemets just ont-to-one mapping. Lookup
|
|
* table in its turn can map one BAR address space to up to 24 different
|
|
* memory spaces of different ports.
|
|
* NT-functions BARs can be turned on to implement either direct or lookup
|
|
* table based address translations, so:
|
|
* BAR0 - NT configuration registers space/direct address translation
|
|
* BAR1 - direct address translation/upper address of BAR0x64
|
|
* BAR2 - direct address translation/Lookup table with either 12 or 24 entries
|
|
* BAR3 - direct address translation/upper address of BAR2x64
|
|
* BAR4 - direct address translation/Lookup table with either 12 or 24 entries
|
|
* BAR5 - direct address translation/upper address of BAR4x64
|
|
* Additionally BAR2 and BAR4 can't have 24-entries LUT enabled at the same
|
|
* time. Since the BARs setup can be rather complicated this driver implements
|
|
* a scanning algorithm to have all the possible memory windows configuration
|
|
* covered.
|
|
*
|
|
* NOTE 1 BAR setup must be done before Linux kernel enumerated NT-function
|
|
* of any port, so this driver would have memory windows configurations fixed.
|
|
* In this way all initializations must be performed either by platform BIOS
|
|
* or using EEPROM connected to IDT PCIe-switch master SMBus.
|
|
*
|
|
* NOTE 2 This driver expects BAR0 mapping NT-function configuration space.
|
|
* Easy calculation can give us an upper boundary of 29 possible memory windows
|
|
* per each NT-function if all the BARs are of 32bit type.
|
|
*=============================================================================
|
|
*/
|
|
|
|
/*
|
|
* idt_get_mw_count() - get memory window count
|
|
* @mw_type: Memory window type
|
|
*
|
|
* Return: number of memory windows with respect to the BAR type
|
|
*/
|
|
static inline unsigned char idt_get_mw_count(enum idt_mw_type mw_type)
|
|
{
|
|
switch (mw_type) {
|
|
case IDT_MW_DIR:
|
|
return 1;
|
|
case IDT_MW_LUT12:
|
|
return 12;
|
|
case IDT_MW_LUT24:
|
|
return 24;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_get_mw_name() - get memory window name
|
|
* @mw_type: Memory window type
|
|
*
|
|
* Return: pointer to a string with name
|
|
*/
|
|
static inline char *idt_get_mw_name(enum idt_mw_type mw_type)
|
|
{
|
|
switch (mw_type) {
|
|
case IDT_MW_DIR:
|
|
return "DIR ";
|
|
case IDT_MW_LUT12:
|
|
return "LUT12";
|
|
case IDT_MW_LUT24:
|
|
return "LUT24";
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
/*
|
|
* idt_scan_mws() - scan memory windows of the port
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
* @port: Port to get number of memory windows for
|
|
* @mw_cnt: Out - number of memory windows
|
|
*
|
|
* It walks over BAR setup registers of the specified port and determines
|
|
* the memory windows parameters if any activated.
|
|
*
|
|
* Return: array of memory windows
|
|
*/
|
|
static struct idt_mw_cfg *idt_scan_mws(struct idt_ntb_dev *ndev, int port,
|
|
unsigned char *mw_cnt)
|
|
{
|
|
struct idt_mw_cfg mws[IDT_MAX_NR_MWS], *ret_mws;
|
|
const struct idt_ntb_bar *bars;
|
|
enum idt_mw_type mw_type;
|
|
unsigned char widx, bidx, en_cnt;
|
|
bool bar_64bit = false;
|
|
int aprt_size;
|
|
u32 data;
|
|
|
|
/* Retrieve the array of the BARs registers */
|
|
bars = portdata_tbl[port].bars;
|
|
|
|
/* Scan all the BARs belonging to the port */
|
|
*mw_cnt = 0;
|
|
for (bidx = 0; bidx < IDT_BAR_CNT; bidx += 1 + bar_64bit) {
|
|
/* Read BARSETUP register value */
|
|
data = idt_sw_read(ndev, bars[bidx].setup);
|
|
|
|
/* Skip disabled BARs */
|
|
if (!(data & IDT_BARSETUP_EN)) {
|
|
bar_64bit = false;
|
|
continue;
|
|
}
|
|
|
|
/* Skip next BARSETUP if current one has 64bit addressing */
|
|
bar_64bit = IS_FLD_SET(BARSETUP_TYPE, data, 64);
|
|
|
|
/* Skip configuration space mapping BARs */
|
|
if (data & IDT_BARSETUP_MODE_CFG)
|
|
continue;
|
|
|
|
/* Retrieve MW type/entries count and aperture size */
|
|
mw_type = GET_FIELD(BARSETUP_ATRAN, data);
|
|
en_cnt = idt_get_mw_count(mw_type);
|
|
aprt_size = (u64)1 << GET_FIELD(BARSETUP_SIZE, data);
|
|
|
|
/* Save configurations of all available memory windows */
|
|
for (widx = 0; widx < en_cnt; widx++, (*mw_cnt)++) {
|
|
/*
|
|
* IDT can expose a limited number of MWs, so it's bug
|
|
* to have more than the driver expects
|
|
*/
|
|
if (*mw_cnt >= IDT_MAX_NR_MWS)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
/* Save basic MW info */
|
|
mws[*mw_cnt].type = mw_type;
|
|
mws[*mw_cnt].bar = bidx;
|
|
mws[*mw_cnt].idx = widx;
|
|
/* It's always DWORD aligned */
|
|
mws[*mw_cnt].addr_align = IDT_TRANS_ALIGN;
|
|
/* DIR and LUT approachs differently configure MWs */
|
|
if (mw_type == IDT_MW_DIR)
|
|
mws[*mw_cnt].size_max = aprt_size;
|
|
else if (mw_type == IDT_MW_LUT12)
|
|
mws[*mw_cnt].size_max = aprt_size / 16;
|
|
else
|
|
mws[*mw_cnt].size_max = aprt_size / 32;
|
|
mws[*mw_cnt].size_align = (mw_type == IDT_MW_DIR) ?
|
|
IDT_DIR_SIZE_ALIGN : mws[*mw_cnt].size_max;
|
|
}
|
|
}
|
|
|
|
/* Allocate memory for memory window descriptors */
|
|
ret_mws = devm_kcalloc(&ndev->ntb.pdev->dev, *mw_cnt, sizeof(*ret_mws),
|
|
GFP_KERNEL);
|
|
if (!ret_mws)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
/* Copy the info of detected memory windows */
|
|
memcpy(ret_mws, mws, (*mw_cnt)*sizeof(*ret_mws));
|
|
|
|
return ret_mws;
|
|
}
|
|
|
|
/*
|
|
* idt_init_mws() - initialize memory windows subsystem
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* Scan BAR setup registers of local and peer ports to determine the
|
|
* outbound and inbound memory windows parameters
|
|
*
|
|
* Return: zero on success, otherwise a negative error number
|
|
*/
|
|
static int idt_init_mws(struct idt_ntb_dev *ndev)
|
|
{
|
|
struct idt_ntb_peer *peer;
|
|
unsigned char pidx;
|
|
|
|
/* Scan memory windows of the local port */
|
|
ndev->mws = idt_scan_mws(ndev, ndev->port, &ndev->mw_cnt);
|
|
if (IS_ERR(ndev->mws)) {
|
|
dev_err(&ndev->ntb.pdev->dev,
|
|
"Failed to scan mws of local port %hhu", ndev->port);
|
|
return PTR_ERR(ndev->mws);
|
|
}
|
|
|
|
/* Scan memory windows of the peer ports */
|
|
for (pidx = 0; pidx < ndev->peer_cnt; pidx++) {
|
|
peer = &ndev->peers[pidx];
|
|
peer->mws = idt_scan_mws(ndev, peer->port, &peer->mw_cnt);
|
|
if (IS_ERR(peer->mws)) {
|
|
dev_err(&ndev->ntb.pdev->dev,
|
|
"Failed to scan mws of port %hhu", peer->port);
|
|
return PTR_ERR(peer->mws);
|
|
}
|
|
}
|
|
|
|
/* Initialize spin locker of the LUT registers */
|
|
spin_lock_init(&ndev->lut_lock);
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "Outbound and inbound MWs initialized");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_mw_count() - number of inbound memory windows (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @pidx: Port index of peer device.
|
|
*
|
|
* The value is returned for the specified peer, so generally speaking it can
|
|
* be different for different port depending on the IDT PCIe-switch
|
|
* initialization.
|
|
*
|
|
* Return: the number of memory windows.
|
|
*/
|
|
static int idt_ntb_mw_count(struct ntb_dev *ntb, int pidx)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
if (pidx < 0 || ndev->peer_cnt <= pidx)
|
|
return -EINVAL;
|
|
|
|
return ndev->peers[pidx].mw_cnt;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_mw_get_align() - inbound memory window parameters (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @pidx: Port index of peer device.
|
|
* @widx: Memory window index.
|
|
* @addr_align: OUT - the base alignment for translating the memory window
|
|
* @size_align: OUT - the size alignment for translating the memory window
|
|
* @size_max: OUT - the maximum size of the memory window
|
|
*
|
|
* The peer memory window parameters have already been determined, so just
|
|
* return the corresponding values, which mustn't change within session.
|
|
*
|
|
* Return: Zero on success, otherwise a negative error number.
|
|
*/
|
|
static int idt_ntb_mw_get_align(struct ntb_dev *ntb, int pidx, int widx,
|
|
resource_size_t *addr_align,
|
|
resource_size_t *size_align,
|
|
resource_size_t *size_max)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
struct idt_ntb_peer *peer;
|
|
|
|
if (pidx < 0 || ndev->peer_cnt <= pidx)
|
|
return -EINVAL;
|
|
|
|
peer = &ndev->peers[pidx];
|
|
|
|
if (widx < 0 || peer->mw_cnt <= widx)
|
|
return -EINVAL;
|
|
|
|
if (addr_align != NULL)
|
|
*addr_align = peer->mws[widx].addr_align;
|
|
|
|
if (size_align != NULL)
|
|
*size_align = peer->mws[widx].size_align;
|
|
|
|
if (size_max != NULL)
|
|
*size_max = peer->mws[widx].size_max;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_peer_mw_count() - number of outbound memory windows
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
*
|
|
* Outbound memory windows parameters have been determined based on the
|
|
* BAR setup registers value, which are mostly constants within one session.
|
|
*
|
|
* Return: the number of memory windows.
|
|
*/
|
|
static int idt_ntb_peer_mw_count(struct ntb_dev *ntb)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
return ndev->mw_cnt;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_peer_mw_get_addr() - get map address of an outbound memory window
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @widx: Memory window index (within ntb_peer_mw_count() return value).
|
|
* @base: OUT - the base address of mapping region.
|
|
* @size: OUT - the size of mapping region.
|
|
*
|
|
* Return just parameters of BAR resources mapping. Size reflects just the size
|
|
* of the resource
|
|
*
|
|
* Return: Zero on success, otherwise a negative error number.
|
|
*/
|
|
static int idt_ntb_peer_mw_get_addr(struct ntb_dev *ntb, int widx,
|
|
phys_addr_t *base, resource_size_t *size)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
if (widx < 0 || ndev->mw_cnt <= widx)
|
|
return -EINVAL;
|
|
|
|
/* Mapping address is just properly shifted BAR resource start */
|
|
if (base != NULL)
|
|
*base = pci_resource_start(ntb->pdev, ndev->mws[widx].bar) +
|
|
ndev->mws[widx].idx * ndev->mws[widx].size_max;
|
|
|
|
/* Mapping size has already been calculated at MWs scanning */
|
|
if (size != NULL)
|
|
*size = ndev->mws[widx].size_max;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_peer_mw_set_trans() - set a translation address of a memory window
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @pidx: Port index of peer device the translation address received from.
|
|
* @widx: Memory window index.
|
|
* @addr: The dma address of the shared memory to access.
|
|
* @size: The size of the shared memory to access.
|
|
*
|
|
* The Direct address translation and LUT base translation is initialized a
|
|
* bit differenet. Although the parameters restriction are now determined by
|
|
* the same code.
|
|
*
|
|
* Return: Zero on success, otherwise an error number.
|
|
*/
|
|
static int idt_ntb_peer_mw_set_trans(struct ntb_dev *ntb, int pidx, int widx,
|
|
u64 addr, resource_size_t size)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
struct idt_mw_cfg *mw_cfg;
|
|
u32 data = 0, lutoff = 0;
|
|
|
|
if (pidx < 0 || ndev->peer_cnt <= pidx)
|
|
return -EINVAL;
|
|
|
|
if (widx < 0 || ndev->mw_cnt <= widx)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Retrieve the memory window config to make sure the passed arguments
|
|
* fit it restrictions
|
|
*/
|
|
mw_cfg = &ndev->mws[widx];
|
|
if (!IS_ALIGNED(addr, mw_cfg->addr_align))
|
|
return -EINVAL;
|
|
if (!IS_ALIGNED(size, mw_cfg->size_align) || size > mw_cfg->size_max)
|
|
return -EINVAL;
|
|
|
|
/* DIR and LUT based translations are initialized differently */
|
|
if (mw_cfg->type == IDT_MW_DIR) {
|
|
const struct idt_ntb_bar *bar = &ntdata_tbl.bars[mw_cfg->bar];
|
|
u64 limit;
|
|
/* Set destination partition of translation */
|
|
data = idt_nt_read(ndev, bar->setup);
|
|
data = SET_FIELD(BARSETUP_TPART, data, ndev->peers[pidx].part);
|
|
idt_nt_write(ndev, bar->setup, data);
|
|
/* Set translation base address */
|
|
idt_nt_write(ndev, bar->ltbase, (u32)addr);
|
|
idt_nt_write(ndev, bar->utbase, (u32)(addr >> 32));
|
|
/* Set the custom BAR aperture limit */
|
|
limit = pci_bus_address(ntb->pdev, mw_cfg->bar) + size;
|
|
idt_nt_write(ndev, bar->limit, (u32)limit);
|
|
if (IS_FLD_SET(BARSETUP_TYPE, data, 64))
|
|
idt_nt_write(ndev, (bar + 1)->limit, (limit >> 32));
|
|
} else {
|
|
unsigned long irqflags;
|
|
/* Initialize corresponding LUT entry */
|
|
lutoff = SET_FIELD(LUTOFFSET_INDEX, 0, mw_cfg->idx) |
|
|
SET_FIELD(LUTOFFSET_BAR, 0, mw_cfg->bar);
|
|
data = SET_FIELD(LUTUDATA_PART, 0, ndev->peers[pidx].part) |
|
|
IDT_LUTUDATA_VALID;
|
|
spin_lock_irqsave(&ndev->lut_lock, irqflags);
|
|
idt_nt_write(ndev, IDT_NT_LUTOFFSET, lutoff);
|
|
idt_nt_write(ndev, IDT_NT_LUTLDATA, (u32)addr);
|
|
idt_nt_write(ndev, IDT_NT_LUTMDATA, (u32)(addr >> 32));
|
|
idt_nt_write(ndev, IDT_NT_LUTUDATA, data);
|
|
spin_unlock_irqrestore(&ndev->lut_lock, irqflags);
|
|
/* Limit address isn't specified since size is fixed for LUT */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_peer_mw_clear_trans() - clear the outbound MW translation address
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @pidx: Port index of peer device.
|
|
* @widx: Memory window index.
|
|
*
|
|
* It effectively disables the translation over the specified outbound MW.
|
|
*
|
|
* Return: Zero on success, otherwise an error number.
|
|
*/
|
|
static int idt_ntb_peer_mw_clear_trans(struct ntb_dev *ntb, int pidx,
|
|
int widx)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
struct idt_mw_cfg *mw_cfg;
|
|
|
|
if (pidx < 0 || ndev->peer_cnt <= pidx)
|
|
return -EINVAL;
|
|
|
|
if (widx < 0 || ndev->mw_cnt <= widx)
|
|
return -EINVAL;
|
|
|
|
mw_cfg = &ndev->mws[widx];
|
|
|
|
/* DIR and LUT based translations are initialized differently */
|
|
if (mw_cfg->type == IDT_MW_DIR) {
|
|
const struct idt_ntb_bar *bar = &ntdata_tbl.bars[mw_cfg->bar];
|
|
u32 data;
|
|
/* Read BARSETUP to check BAR type */
|
|
data = idt_nt_read(ndev, bar->setup);
|
|
/* Disable translation by specifying zero BAR limit */
|
|
idt_nt_write(ndev, bar->limit, 0);
|
|
if (IS_FLD_SET(BARSETUP_TYPE, data, 64))
|
|
idt_nt_write(ndev, (bar + 1)->limit, 0);
|
|
} else {
|
|
unsigned long irqflags;
|
|
u32 lutoff;
|
|
/* Clear the corresponding LUT entry up */
|
|
lutoff = SET_FIELD(LUTOFFSET_INDEX, 0, mw_cfg->idx) |
|
|
SET_FIELD(LUTOFFSET_BAR, 0, mw_cfg->bar);
|
|
spin_lock_irqsave(&ndev->lut_lock, irqflags);
|
|
idt_nt_write(ndev, IDT_NT_LUTOFFSET, lutoff);
|
|
idt_nt_write(ndev, IDT_NT_LUTLDATA, 0);
|
|
idt_nt_write(ndev, IDT_NT_LUTMDATA, 0);
|
|
idt_nt_write(ndev, IDT_NT_LUTUDATA, 0);
|
|
spin_unlock_irqrestore(&ndev->lut_lock, irqflags);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*=============================================================================
|
|
* 5. Doorbell operations
|
|
*
|
|
* Doorbell functionality of IDT PCIe-switches is pretty unusual. First of
|
|
* all there is global doorbell register which state can be changed by any
|
|
* NT-function of the IDT device in accordance with global permissions. These
|
|
* permissions configs are not supported by NTB API, so it must be done by
|
|
* either BIOS or EEPROM settings. In the same way the state of the global
|
|
* doorbell is reflected to the NT-functions local inbound doorbell registers.
|
|
* It can lead to situations when client driver sets some peer doorbell bits
|
|
* and get them bounced back to local inbound doorbell if permissions are
|
|
* granted.
|
|
* Secondly there is just one IRQ vector for Doorbell, Message, Temperature
|
|
* and Switch events, so if client driver left any of Doorbell bits set and
|
|
* some other event occurred, the driver will be notified of Doorbell event
|
|
* again.
|
|
*=============================================================================
|
|
*/
|
|
|
|
/*
|
|
* idt_db_isr() - doorbell event ISR
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
* @ntint_sts: NT-function interrupt status
|
|
*
|
|
* Doorbell event happans when DBELL bit of NTINTSTS switches from 0 to 1.
|
|
* It happens only when unmasked doorbell bits are set to ones on completely
|
|
* zeroed doorbell register.
|
|
* The method is called from PCIe ISR bottom-half routine.
|
|
*/
|
|
static void idt_db_isr(struct idt_ntb_dev *ndev, u32 ntint_sts)
|
|
{
|
|
/*
|
|
* Doorbell IRQ status will be cleaned only when client
|
|
* driver unsets all the doorbell bits.
|
|
*/
|
|
dev_dbg(&ndev->ntb.pdev->dev, "DB IRQ detected %#08x", ntint_sts);
|
|
|
|
/* Notify the client driver of possible doorbell state change */
|
|
ntb_db_event(&ndev->ntb, 0);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_db_valid_mask() - get a mask of doorbell bits supported by the ntb
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
*
|
|
* IDT PCIe-switches expose just one Doorbell register of DWORD size.
|
|
*
|
|
* Return: A mask of doorbell bits supported by the ntb.
|
|
*/
|
|
static u64 idt_ntb_db_valid_mask(struct ntb_dev *ntb)
|
|
{
|
|
return IDT_DBELL_MASK;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_db_read() - read the local doorbell register (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
*
|
|
* There is just on inbound doorbell register of each NT-function, so
|
|
* this method return it value.
|
|
*
|
|
* Return: The bits currently set in the local doorbell register.
|
|
*/
|
|
static u64 idt_ntb_db_read(struct ntb_dev *ntb)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
return idt_nt_read(ndev, IDT_NT_INDBELLSTS);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_db_clear() - clear bits in the local doorbell register
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @db_bits: Doorbell bits to clear.
|
|
*
|
|
* Clear bits of inbound doorbell register by writing ones to it.
|
|
*
|
|
* NOTE! Invalid bits are always considered cleared so it's not an error
|
|
* to clear them over.
|
|
*
|
|
* Return: always zero as success.
|
|
*/
|
|
static int idt_ntb_db_clear(struct ntb_dev *ntb, u64 db_bits)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
idt_nt_write(ndev, IDT_NT_INDBELLSTS, (u32)db_bits);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_db_read_mask() - read the local doorbell mask (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
*
|
|
* Each inbound doorbell bit can be masked from generating IRQ by setting
|
|
* the corresponding bit in inbound doorbell mask. So this method returns
|
|
* the value of the register.
|
|
*
|
|
* Return: The bits currently set in the local doorbell mask register.
|
|
*/
|
|
static u64 idt_ntb_db_read_mask(struct ntb_dev *ntb)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
return idt_nt_read(ndev, IDT_NT_INDBELLMSK);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_db_set_mask() - set bits in the local doorbell mask
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @db_bits: Doorbell mask bits to set.
|
|
*
|
|
* The inbound doorbell register mask value must be read, then OR'ed with
|
|
* passed field and only then set back.
|
|
*
|
|
* Return: zero on success, negative error if invalid argument passed.
|
|
*/
|
|
static int idt_ntb_db_set_mask(struct ntb_dev *ntb, u64 db_bits)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
return idt_reg_set_bits(ndev, IDT_NT_INDBELLMSK, &ndev->db_mask_lock,
|
|
IDT_DBELL_MASK, db_bits);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_db_clear_mask() - clear bits in the local doorbell mask
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @db_bits: Doorbell bits to clear.
|
|
*
|
|
* The method just clears the set bits up in accordance with the passed
|
|
* bitfield. IDT PCIe-switch shall generate an interrupt if there hasn't
|
|
* been any unmasked bit set before current unmasking. Otherwise IRQ won't
|
|
* be generated since there is only one IRQ vector for all doorbells.
|
|
*
|
|
* Return: always zero as success
|
|
*/
|
|
static int idt_ntb_db_clear_mask(struct ntb_dev *ntb, u64 db_bits)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
idt_reg_clear_bits(ndev, IDT_NT_INDBELLMSK, &ndev->db_mask_lock,
|
|
db_bits);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_peer_db_set() - set bits in the peer doorbell register
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @db_bits: Doorbell bits to set.
|
|
*
|
|
* IDT PCIe-switches exposes local outbound doorbell register to change peer
|
|
* inbound doorbell register state.
|
|
*
|
|
* Return: zero on success, negative error if invalid argument passed.
|
|
*/
|
|
static int idt_ntb_peer_db_set(struct ntb_dev *ntb, u64 db_bits)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
if (db_bits & ~(u64)IDT_DBELL_MASK)
|
|
return -EINVAL;
|
|
|
|
idt_nt_write(ndev, IDT_NT_OUTDBELLSET, (u32)db_bits);
|
|
return 0;
|
|
}
|
|
|
|
/*=============================================================================
|
|
* 6. Messaging operations
|
|
*
|
|
* Each NT-function of IDT PCIe-switch has four inbound and four outbound
|
|
* message registers. Each outbound message register can be connected to one or
|
|
* even more than one peer inbound message registers by setting global
|
|
* configurations. Since NTB API permits one-on-one message registers mapping
|
|
* only, the driver acts in according with that restriction.
|
|
*=============================================================================
|
|
*/
|
|
|
|
/*
|
|
* idt_init_msg() - initialize messaging interface
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* Just initialize the message registers routing tables locker.
|
|
*/
|
|
static void idt_init_msg(struct idt_ntb_dev *ndev)
|
|
{
|
|
unsigned char midx;
|
|
|
|
/* Init the messages routing table lockers */
|
|
for (midx = 0; midx < IDT_MSG_CNT; midx++)
|
|
spin_lock_init(&ndev->msg_locks[midx]);
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "NTB Messaging initialized");
|
|
}
|
|
|
|
/*
|
|
* idt_msg_isr() - message event ISR
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
* @ntint_sts: NT-function interrupt status
|
|
*
|
|
* Message event happens when MSG bit of NTINTSTS switches from 0 to 1.
|
|
* It happens only when unmasked message status bits are set to ones on
|
|
* completely zeroed message status register.
|
|
* The method is called from PCIe ISR bottom-half routine.
|
|
*/
|
|
static void idt_msg_isr(struct idt_ntb_dev *ndev, u32 ntint_sts)
|
|
{
|
|
/*
|
|
* Message IRQ status will be cleaned only when client
|
|
* driver unsets all the message status bits.
|
|
*/
|
|
dev_dbg(&ndev->ntb.pdev->dev, "Message IRQ detected %#08x", ntint_sts);
|
|
|
|
/* Notify the client driver of possible message status change */
|
|
ntb_msg_event(&ndev->ntb);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_msg_count() - get the number of message registers (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
*
|
|
* IDT PCIe-switches support four message registers.
|
|
*
|
|
* Return: the number of message registers.
|
|
*/
|
|
static int idt_ntb_msg_count(struct ntb_dev *ntb)
|
|
{
|
|
return IDT_MSG_CNT;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_msg_inbits() - get a bitfield of inbound message registers status
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
*
|
|
* NT message status register is shared between inbound and outbound message
|
|
* registers status
|
|
*
|
|
* Return: bitfield of inbound message registers.
|
|
*/
|
|
static u64 idt_ntb_msg_inbits(struct ntb_dev *ntb)
|
|
{
|
|
return (u64)IDT_INMSG_MASK;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_msg_outbits() - get a bitfield of outbound message registers status
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
*
|
|
* NT message status register is shared between inbound and outbound message
|
|
* registers status
|
|
*
|
|
* Return: bitfield of outbound message registers.
|
|
*/
|
|
static u64 idt_ntb_msg_outbits(struct ntb_dev *ntb)
|
|
{
|
|
return (u64)IDT_OUTMSG_MASK;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_msg_read_sts() - read the message registers status (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
*
|
|
* IDT PCIe-switches expose message status registers to notify drivers of
|
|
* incoming data and failures in case if peer message register isn't freed.
|
|
*
|
|
* Return: status bits of message registers
|
|
*/
|
|
static u64 idt_ntb_msg_read_sts(struct ntb_dev *ntb)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
return idt_nt_read(ndev, IDT_NT_MSGSTS);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_msg_clear_sts() - clear status bits of message registers
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @sts_bits: Status bits to clear.
|
|
*
|
|
* Clear bits in the status register by writing ones.
|
|
*
|
|
* NOTE! Invalid bits are always considered cleared so it's not an error
|
|
* to clear them over.
|
|
*
|
|
* Return: always zero as success.
|
|
*/
|
|
static int idt_ntb_msg_clear_sts(struct ntb_dev *ntb, u64 sts_bits)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
idt_nt_write(ndev, IDT_NT_MSGSTS, sts_bits);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_msg_set_mask() - set mask of message register status bits
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @mask_bits: Mask bits.
|
|
*
|
|
* Mask the message status bits from raising an IRQ.
|
|
*
|
|
* Return: zero on success, negative error if invalid argument passed.
|
|
*/
|
|
static int idt_ntb_msg_set_mask(struct ntb_dev *ntb, u64 mask_bits)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
return idt_reg_set_bits(ndev, IDT_NT_MSGSTSMSK, &ndev->msg_mask_lock,
|
|
IDT_MSG_MASK, mask_bits);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_msg_clear_mask() - clear message registers mask
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @mask_bits: Mask bits.
|
|
*
|
|
* Clear mask of message status bits IRQs.
|
|
*
|
|
* Return: always zero as success.
|
|
*/
|
|
static int idt_ntb_msg_clear_mask(struct ntb_dev *ntb, u64 mask_bits)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
idt_reg_clear_bits(ndev, IDT_NT_MSGSTSMSK, &ndev->msg_mask_lock,
|
|
mask_bits);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_msg_read() - read message register with specified index
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @pidx: OUT - Port index of peer device a message retrieved from
|
|
* @midx: Message register index
|
|
*
|
|
* Read data from the specified message register and source register.
|
|
*
|
|
* Return: inbound message register value.
|
|
*/
|
|
static u32 idt_ntb_msg_read(struct ntb_dev *ntb, int *pidx, int midx)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
|
|
if (midx < 0 || IDT_MSG_CNT <= midx)
|
|
return ~(u32)0;
|
|
|
|
/* Retrieve source port index of the message */
|
|
if (pidx != NULL) {
|
|
u32 srcpart;
|
|
|
|
srcpart = idt_nt_read(ndev, ntdata_tbl.msgs[midx].src);
|
|
*pidx = ndev->part_idx_map[srcpart];
|
|
|
|
/* Sanity check partition index (for initial case) */
|
|
if (*pidx == -EINVAL)
|
|
*pidx = 0;
|
|
}
|
|
|
|
/* Retrieve data of the corresponding message register */
|
|
return idt_nt_read(ndev, ntdata_tbl.msgs[midx].in);
|
|
}
|
|
|
|
/*
|
|
* idt_ntb_peer_msg_write() - write data to the specified message register
|
|
* (NTB API callback)
|
|
* @ntb: NTB device context.
|
|
* @pidx: Port index of peer device a message being sent to
|
|
* @midx: Message register index
|
|
* @msg: Data to send
|
|
*
|
|
* Just try to send data to a peer. Message status register should be
|
|
* checked by client driver.
|
|
*
|
|
* Return: zero on success, negative error if invalid argument passed.
|
|
*/
|
|
static int idt_ntb_peer_msg_write(struct ntb_dev *ntb, int pidx, int midx,
|
|
u32 msg)
|
|
{
|
|
struct idt_ntb_dev *ndev = to_ndev_ntb(ntb);
|
|
unsigned long irqflags;
|
|
u32 swpmsgctl = 0;
|
|
|
|
if (midx < 0 || IDT_MSG_CNT <= midx)
|
|
return -EINVAL;
|
|
|
|
if (pidx < 0 || ndev->peer_cnt <= pidx)
|
|
return -EINVAL;
|
|
|
|
/* Collect the routing information */
|
|
swpmsgctl = SET_FIELD(SWPxMSGCTL_REG, 0, midx) |
|
|
SET_FIELD(SWPxMSGCTL_PART, 0, ndev->peers[pidx].part);
|
|
|
|
/* Lock the messages routing table of the specified register */
|
|
spin_lock_irqsave(&ndev->msg_locks[midx], irqflags);
|
|
/* Set the route and send the data */
|
|
idt_sw_write(ndev, partdata_tbl[ndev->part].msgctl[midx], swpmsgctl);
|
|
idt_nt_write(ndev, ntdata_tbl.msgs[midx].out, msg);
|
|
/* Unlock the messages routing table */
|
|
spin_unlock_irqrestore(&ndev->msg_locks[midx], irqflags);
|
|
|
|
/* Client driver shall check the status register */
|
|
return 0;
|
|
}
|
|
|
|
/*=============================================================================
|
|
* 7. Temperature sensor operations
|
|
*
|
|
* IDT PCIe-switch has an embedded temperature sensor, which can be used to
|
|
* check current chip core temperature. Since a workload environment can be
|
|
* different on different platforms, an offset and ADC/filter settings can be
|
|
* specified. Although the offset configuration is only exposed to the sysfs
|
|
* hwmon interface at the moment. The rest of the settings can be adjusted
|
|
* for instance by the BIOS/EEPROM firmware.
|
|
*=============================================================================
|
|
*/
|
|
|
|
/*
|
|
* idt_get_deg() - convert millidegree Celsius value to just degree
|
|
* @mdegC: IN - millidegree Celsius value
|
|
*
|
|
* Return: Degree corresponding to the passed millidegree value
|
|
*/
|
|
static inline s8 idt_get_deg(long mdegC)
|
|
{
|
|
return mdegC / 1000;
|
|
}
|
|
|
|
/*
|
|
* idt_get_frac() - retrieve 0/0.5 fraction of the millidegree Celsius value
|
|
* @mdegC: IN - millidegree Celsius value
|
|
*
|
|
* Return: 0/0.5 degree fraction of the passed millidegree value
|
|
*/
|
|
static inline u8 idt_get_deg_frac(long mdegC)
|
|
{
|
|
return (mdegC % 1000) >= 500 ? 5 : 0;
|
|
}
|
|
|
|
/*
|
|
* idt_get_temp_fmt() - convert millidegree Celsius value to 0:7:1 format
|
|
* @mdegC: IN - millidegree Celsius value
|
|
*
|
|
* Return: 0:7:1 format acceptable by the IDT temperature sensor
|
|
*/
|
|
static inline u8 idt_temp_get_fmt(long mdegC)
|
|
{
|
|
return (idt_get_deg(mdegC) << 1) | (idt_get_deg_frac(mdegC) ? 1 : 0);
|
|
}
|
|
|
|
/*
|
|
* idt_get_temp_sval() - convert temp sample to signed millidegree Celsius
|
|
* @data: IN - shifted to LSB 8-bits temperature sample
|
|
*
|
|
* Return: signed millidegree Celsius
|
|
*/
|
|
static inline long idt_get_temp_sval(u32 data)
|
|
{
|
|
return ((s8)data / 2) * 1000 + (data & 0x1 ? 500 : 0);
|
|
}
|
|
|
|
/*
|
|
* idt_get_temp_sval() - convert temp sample to unsigned millidegree Celsius
|
|
* @data: IN - shifted to LSB 8-bits temperature sample
|
|
*
|
|
* Return: unsigned millidegree Celsius
|
|
*/
|
|
static inline long idt_get_temp_uval(u32 data)
|
|
{
|
|
return (data / 2) * 1000 + (data & 0x1 ? 500 : 0);
|
|
}
|
|
|
|
/*
|
|
* idt_read_temp() - read temperature from chip sensor
|
|
* @ntb: NTB device context.
|
|
* @type: IN - type of the temperature value to read
|
|
* @val: OUT - integer value of temperature in millidegree Celsius
|
|
*/
|
|
static void idt_read_temp(struct idt_ntb_dev *ndev,
|
|
const enum idt_temp_val type, long *val)
|
|
{
|
|
u32 data;
|
|
|
|
/* Alter the temperature field in accordance with the passed type */
|
|
switch (type) {
|
|
case IDT_TEMP_CUR:
|
|
data = GET_FIELD(TMPSTS_TEMP,
|
|
idt_sw_read(ndev, IDT_SW_TMPSTS));
|
|
break;
|
|
case IDT_TEMP_LOW:
|
|
data = GET_FIELD(TMPSTS_LTEMP,
|
|
idt_sw_read(ndev, IDT_SW_TMPSTS));
|
|
break;
|
|
case IDT_TEMP_HIGH:
|
|
data = GET_FIELD(TMPSTS_HTEMP,
|
|
idt_sw_read(ndev, IDT_SW_TMPSTS));
|
|
break;
|
|
case IDT_TEMP_OFFSET:
|
|
/* This is the only field with signed 0:7:1 format */
|
|
data = GET_FIELD(TMPADJ_OFFSET,
|
|
idt_sw_read(ndev, IDT_SW_TMPADJ));
|
|
*val = idt_get_temp_sval(data);
|
|
return;
|
|
default:
|
|
data = GET_FIELD(TMPSTS_TEMP,
|
|
idt_sw_read(ndev, IDT_SW_TMPSTS));
|
|
break;
|
|
}
|
|
|
|
/* The rest of the fields accept unsigned 0:7:1 format */
|
|
*val = idt_get_temp_uval(data);
|
|
}
|
|
|
|
/*
|
|
* idt_write_temp() - write temperature to the chip sensor register
|
|
* @ntb: NTB device context.
|
|
* @type: IN - type of the temperature value to change
|
|
* @val: IN - integer value of temperature in millidegree Celsius
|
|
*/
|
|
static void idt_write_temp(struct idt_ntb_dev *ndev,
|
|
const enum idt_temp_val type, const long val)
|
|
{
|
|
unsigned int reg;
|
|
u32 data;
|
|
u8 fmt;
|
|
|
|
/* Retrieve the properly formatted temperature value */
|
|
fmt = idt_temp_get_fmt(val);
|
|
|
|
mutex_lock(&ndev->hwmon_mtx);
|
|
switch (type) {
|
|
case IDT_TEMP_LOW:
|
|
reg = IDT_SW_TMPALARM;
|
|
data = SET_FIELD(TMPALARM_LTEMP, idt_sw_read(ndev, reg), fmt) &
|
|
~IDT_TMPALARM_IRQ_MASK;
|
|
break;
|
|
case IDT_TEMP_HIGH:
|
|
reg = IDT_SW_TMPALARM;
|
|
data = SET_FIELD(TMPALARM_HTEMP, idt_sw_read(ndev, reg), fmt) &
|
|
~IDT_TMPALARM_IRQ_MASK;
|
|
break;
|
|
case IDT_TEMP_OFFSET:
|
|
reg = IDT_SW_TMPADJ;
|
|
data = SET_FIELD(TMPADJ_OFFSET, idt_sw_read(ndev, reg), fmt);
|
|
break;
|
|
default:
|
|
goto inval_spin_unlock;
|
|
}
|
|
|
|
idt_sw_write(ndev, reg, data);
|
|
|
|
inval_spin_unlock:
|
|
mutex_unlock(&ndev->hwmon_mtx);
|
|
}
|
|
|
|
/*
|
|
* idt_sysfs_show_temp() - printout corresponding temperature value
|
|
* @dev: Pointer to the NTB device structure
|
|
* @da: Sensor device attribute structure
|
|
* @buf: Buffer to print temperature out
|
|
*
|
|
* Return: Number of written symbols or negative error
|
|
*/
|
|
static ssize_t idt_sysfs_show_temp(struct device *dev,
|
|
struct device_attribute *da, char *buf)
|
|
{
|
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
struct idt_ntb_dev *ndev = dev_get_drvdata(dev);
|
|
enum idt_temp_val type = attr->index;
|
|
long mdeg;
|
|
|
|
idt_read_temp(ndev, type, &mdeg);
|
|
return sprintf(buf, "%ld\n", mdeg);
|
|
}
|
|
|
|
/*
|
|
* idt_sysfs_set_temp() - set corresponding temperature value
|
|
* @dev: Pointer to the NTB device structure
|
|
* @da: Sensor device attribute structure
|
|
* @buf: Buffer to print temperature out
|
|
* @count: Size of the passed buffer
|
|
*
|
|
* Return: Number of written symbols or negative error
|
|
*/
|
|
static ssize_t idt_sysfs_set_temp(struct device *dev,
|
|
struct device_attribute *da, const char *buf,
|
|
size_t count)
|
|
{
|
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
struct idt_ntb_dev *ndev = dev_get_drvdata(dev);
|
|
enum idt_temp_val type = attr->index;
|
|
long mdeg;
|
|
int ret;
|
|
|
|
ret = kstrtol(buf, 10, &mdeg);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Clamp the passed value in accordance with the type */
|
|
if (type == IDT_TEMP_OFFSET)
|
|
mdeg = clamp_val(mdeg, IDT_TEMP_MIN_OFFSET,
|
|
IDT_TEMP_MAX_OFFSET);
|
|
else
|
|
mdeg = clamp_val(mdeg, IDT_TEMP_MIN_MDEG, IDT_TEMP_MAX_MDEG);
|
|
|
|
idt_write_temp(ndev, type, mdeg);
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
* idt_sysfs_reset_hist() - reset temperature history
|
|
* @dev: Pointer to the NTB device structure
|
|
* @da: Sensor device attribute structure
|
|
* @buf: Buffer to print temperature out
|
|
* @count: Size of the passed buffer
|
|
*
|
|
* Return: Number of written symbols or negative error
|
|
*/
|
|
static ssize_t idt_sysfs_reset_hist(struct device *dev,
|
|
struct device_attribute *da,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct idt_ntb_dev *ndev = dev_get_drvdata(dev);
|
|
|
|
/* Just set the maximal value to the lowest temperature field and
|
|
* minimal value to the highest temperature field
|
|
*/
|
|
idt_write_temp(ndev, IDT_TEMP_LOW, IDT_TEMP_MAX_MDEG);
|
|
idt_write_temp(ndev, IDT_TEMP_HIGH, IDT_TEMP_MIN_MDEG);
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
* Hwmon IDT sysfs attributes
|
|
*/
|
|
static SENSOR_DEVICE_ATTR(temp1_input, 0444, idt_sysfs_show_temp, NULL,
|
|
IDT_TEMP_CUR);
|
|
static SENSOR_DEVICE_ATTR(temp1_lowest, 0444, idt_sysfs_show_temp, NULL,
|
|
IDT_TEMP_LOW);
|
|
static SENSOR_DEVICE_ATTR(temp1_highest, 0444, idt_sysfs_show_temp, NULL,
|
|
IDT_TEMP_HIGH);
|
|
static SENSOR_DEVICE_ATTR(temp1_offset, 0644, idt_sysfs_show_temp,
|
|
idt_sysfs_set_temp, IDT_TEMP_OFFSET);
|
|
static DEVICE_ATTR(temp1_reset_history, 0200, NULL, idt_sysfs_reset_hist);
|
|
|
|
/*
|
|
* Hwmon IDT sysfs attributes group
|
|
*/
|
|
static struct attribute *idt_temp_attrs[] = {
|
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
|
&sensor_dev_attr_temp1_lowest.dev_attr.attr,
|
|
&sensor_dev_attr_temp1_highest.dev_attr.attr,
|
|
&sensor_dev_attr_temp1_offset.dev_attr.attr,
|
|
&dev_attr_temp1_reset_history.attr,
|
|
NULL
|
|
};
|
|
ATTRIBUTE_GROUPS(idt_temp);
|
|
|
|
/*
|
|
* idt_init_temp() - initialize temperature sensor interface
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* Simple sensor initializarion method is responsible for device switching
|
|
* on and resource management based hwmon interface registration. Note, that
|
|
* since the device is shared we won't disable it on remove, but leave it
|
|
* working until the system is powered off.
|
|
*/
|
|
static void idt_init_temp(struct idt_ntb_dev *ndev)
|
|
{
|
|
struct device *hwmon;
|
|
|
|
/* Enable sensor if it hasn't been already */
|
|
idt_sw_write(ndev, IDT_SW_TMPCTL, 0x0);
|
|
|
|
/* Initialize hwmon interface fields */
|
|
mutex_init(&ndev->hwmon_mtx);
|
|
|
|
hwmon = devm_hwmon_device_register_with_groups(&ndev->ntb.pdev->dev,
|
|
ndev->swcfg->name, ndev, idt_temp_groups);
|
|
if (IS_ERR(hwmon)) {
|
|
dev_err(&ndev->ntb.pdev->dev, "Couldn't create hwmon device");
|
|
return;
|
|
}
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "Temperature HWmon interface registered");
|
|
}
|
|
|
|
/*=============================================================================
|
|
* 8. ISRs related operations
|
|
*
|
|
* IDT PCIe-switch has strangely developed IRQ system. There is just one
|
|
* interrupt vector for doorbell and message registers. So the hardware driver
|
|
* can't determine actual source of IRQ if, for example, message event happened
|
|
* while any of unmasked doorbell is still set. The similar situation may be if
|
|
* switch or temperature sensor events pop up. The difference is that SEVENT
|
|
* and TMPSENSOR bits of NT interrupt status register can be cleaned by
|
|
* IRQ handler so a next interrupt request won't have false handling of
|
|
* corresponding events.
|
|
* The hardware driver has only bottom-half handler of the IRQ, since if any
|
|
* of events happened the device won't raise it again before the last one is
|
|
* handled by clearing of corresponding NTINTSTS bit.
|
|
*=============================================================================
|
|
*/
|
|
|
|
static irqreturn_t idt_thread_isr(int irq, void *devid);
|
|
|
|
/*
|
|
* idt_init_isr() - initialize PCIe interrupt handler
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* Return: zero on success, otherwise a negative error number.
|
|
*/
|
|
static int idt_init_isr(struct idt_ntb_dev *ndev)
|
|
{
|
|
struct pci_dev *pdev = ndev->ntb.pdev;
|
|
u32 ntint_mask;
|
|
int ret;
|
|
|
|
/* Allocate just one interrupt vector for the ISR */
|
|
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI | PCI_IRQ_LEGACY);
|
|
if (ret != 1) {
|
|
dev_err(&pdev->dev, "Failed to allocate IRQ vector");
|
|
return ret;
|
|
}
|
|
|
|
/* Retrieve the IRQ vector */
|
|
ret = pci_irq_vector(pdev, 0);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to get IRQ vector");
|
|
goto err_free_vectors;
|
|
}
|
|
|
|
/* Set the IRQ handler */
|
|
ret = devm_request_threaded_irq(&pdev->dev, ret, NULL, idt_thread_isr,
|
|
IRQF_ONESHOT, NTB_IRQNAME, ndev);
|
|
if (ret != 0) {
|
|
dev_err(&pdev->dev, "Failed to set MSI IRQ handler, %d", ret);
|
|
goto err_free_vectors;
|
|
}
|
|
|
|
/* Unmask Message/Doorbell/SE interrupts */
|
|
ntint_mask = idt_nt_read(ndev, IDT_NT_NTINTMSK) & ~IDT_NTINTMSK_ALL;
|
|
idt_nt_write(ndev, IDT_NT_NTINTMSK, ntint_mask);
|
|
|
|
/* From now on the interrupts are enabled */
|
|
dev_dbg(&pdev->dev, "NTB interrupts initialized");
|
|
|
|
return 0;
|
|
|
|
err_free_vectors:
|
|
pci_free_irq_vectors(pdev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* idt_deinit_ist() - deinitialize PCIe interrupt handler
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* Disable corresponding interrupts and free allocated IRQ vectors.
|
|
*/
|
|
static void idt_deinit_isr(struct idt_ntb_dev *ndev)
|
|
{
|
|
struct pci_dev *pdev = ndev->ntb.pdev;
|
|
u32 ntint_mask;
|
|
|
|
/* Mask interrupts back */
|
|
ntint_mask = idt_nt_read(ndev, IDT_NT_NTINTMSK) | IDT_NTINTMSK_ALL;
|
|
idt_nt_write(ndev, IDT_NT_NTINTMSK, ntint_mask);
|
|
|
|
/* Manually free IRQ otherwise PCI free irq vectors will fail */
|
|
devm_free_irq(&pdev->dev, pci_irq_vector(pdev, 0), ndev);
|
|
|
|
/* Free allocated IRQ vectors */
|
|
pci_free_irq_vectors(pdev);
|
|
|
|
dev_dbg(&pdev->dev, "NTB interrupts deinitialized");
|
|
}
|
|
|
|
/*
|
|
* idt_thread_isr() - NT function interrupts handler
|
|
* @irq: IRQ number
|
|
* @devid: Custom buffer
|
|
*
|
|
* It reads current NT interrupts state register and handles all the event
|
|
* it declares.
|
|
* The method is bottom-half routine of actual default PCIe IRQ handler.
|
|
*/
|
|
static irqreturn_t idt_thread_isr(int irq, void *devid)
|
|
{
|
|
struct idt_ntb_dev *ndev = devid;
|
|
bool handled = false;
|
|
u32 ntint_sts;
|
|
|
|
/* Read the NT interrupts status register */
|
|
ntint_sts = idt_nt_read(ndev, IDT_NT_NTINTSTS);
|
|
|
|
/* Handle messaging interrupts */
|
|
if (ntint_sts & IDT_NTINTSTS_MSG) {
|
|
idt_msg_isr(ndev, ntint_sts);
|
|
handled = true;
|
|
}
|
|
|
|
/* Handle doorbell interrupts */
|
|
if (ntint_sts & IDT_NTINTSTS_DBELL) {
|
|
idt_db_isr(ndev, ntint_sts);
|
|
handled = true;
|
|
}
|
|
|
|
/* Handle switch event interrupts */
|
|
if (ntint_sts & IDT_NTINTSTS_SEVENT) {
|
|
idt_se_isr(ndev, ntint_sts);
|
|
handled = true;
|
|
}
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "IDT IRQs 0x%08x handled", ntint_sts);
|
|
|
|
return handled ? IRQ_HANDLED : IRQ_NONE;
|
|
}
|
|
|
|
/*===========================================================================
|
|
* 9. NTB hardware driver initialization
|
|
*===========================================================================
|
|
*/
|
|
|
|
/*
|
|
* NTB API operations
|
|
*/
|
|
static const struct ntb_dev_ops idt_ntb_ops = {
|
|
.port_number = idt_ntb_port_number,
|
|
.peer_port_count = idt_ntb_peer_port_count,
|
|
.peer_port_number = idt_ntb_peer_port_number,
|
|
.peer_port_idx = idt_ntb_peer_port_idx,
|
|
.link_is_up = idt_ntb_link_is_up,
|
|
.link_enable = idt_ntb_link_enable,
|
|
.link_disable = idt_ntb_link_disable,
|
|
.mw_count = idt_ntb_mw_count,
|
|
.mw_get_align = idt_ntb_mw_get_align,
|
|
.peer_mw_count = idt_ntb_peer_mw_count,
|
|
.peer_mw_get_addr = idt_ntb_peer_mw_get_addr,
|
|
.peer_mw_set_trans = idt_ntb_peer_mw_set_trans,
|
|
.peer_mw_clear_trans = idt_ntb_peer_mw_clear_trans,
|
|
.db_valid_mask = idt_ntb_db_valid_mask,
|
|
.db_read = idt_ntb_db_read,
|
|
.db_clear = idt_ntb_db_clear,
|
|
.db_read_mask = idt_ntb_db_read_mask,
|
|
.db_set_mask = idt_ntb_db_set_mask,
|
|
.db_clear_mask = idt_ntb_db_clear_mask,
|
|
.peer_db_set = idt_ntb_peer_db_set,
|
|
.msg_count = idt_ntb_msg_count,
|
|
.msg_inbits = idt_ntb_msg_inbits,
|
|
.msg_outbits = idt_ntb_msg_outbits,
|
|
.msg_read_sts = idt_ntb_msg_read_sts,
|
|
.msg_clear_sts = idt_ntb_msg_clear_sts,
|
|
.msg_set_mask = idt_ntb_msg_set_mask,
|
|
.msg_clear_mask = idt_ntb_msg_clear_mask,
|
|
.msg_read = idt_ntb_msg_read,
|
|
.peer_msg_write = idt_ntb_peer_msg_write
|
|
};
|
|
|
|
/*
|
|
* idt_register_device() - register IDT NTB device
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* Return: zero on success, otherwise a negative error number.
|
|
*/
|
|
static int idt_register_device(struct idt_ntb_dev *ndev)
|
|
{
|
|
int ret;
|
|
|
|
/* Initialize the rest of NTB device structure and register it */
|
|
ndev->ntb.ops = &idt_ntb_ops;
|
|
ndev->ntb.topo = NTB_TOPO_SWITCH;
|
|
|
|
ret = ntb_register_device(&ndev->ntb);
|
|
if (ret != 0) {
|
|
dev_err(&ndev->ntb.pdev->dev, "Failed to register NTB device");
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "NTB device successfully registered");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_unregister_device() - unregister IDT NTB device
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*/
|
|
static void idt_unregister_device(struct idt_ntb_dev *ndev)
|
|
{
|
|
/* Just unregister the NTB device */
|
|
ntb_unregister_device(&ndev->ntb);
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "NTB device unregistered");
|
|
}
|
|
|
|
/*=============================================================================
|
|
* 10. DebugFS node initialization
|
|
*=============================================================================
|
|
*/
|
|
|
|
static ssize_t idt_dbgfs_info_read(struct file *filp, char __user *ubuf,
|
|
size_t count, loff_t *offp);
|
|
|
|
/*
|
|
* Driver DebugFS info file operations
|
|
*/
|
|
static const struct file_operations idt_dbgfs_info_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = simple_open,
|
|
.read = idt_dbgfs_info_read
|
|
};
|
|
|
|
/*
|
|
* idt_dbgfs_info_read() - DebugFS read info node callback
|
|
* @file: File node descriptor.
|
|
* @ubuf: User-space buffer to put data to
|
|
* @count: Size of the buffer
|
|
* @offp: Offset within the buffer
|
|
*/
|
|
static ssize_t idt_dbgfs_info_read(struct file *filp, char __user *ubuf,
|
|
size_t count, loff_t *offp)
|
|
{
|
|
struct idt_ntb_dev *ndev = filp->private_data;
|
|
unsigned char idx, pidx, cnt;
|
|
unsigned long irqflags, mdeg;
|
|
ssize_t ret = 0, off = 0;
|
|
enum ntb_speed speed;
|
|
enum ntb_width width;
|
|
char *strbuf;
|
|
size_t size;
|
|
u32 data;
|
|
|
|
/* Lets limit the buffer size the way the Intel/AMD drivers do */
|
|
size = min_t(size_t, count, 0x1000U);
|
|
|
|
/* Allocate the memory for the buffer */
|
|
strbuf = kmalloc(size, GFP_KERNEL);
|
|
if (strbuf == NULL)
|
|
return -ENOMEM;
|
|
|
|
/* Put the data into the string buffer */
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"\n\t\tIDT NTB device Information:\n\n");
|
|
|
|
/* General local device configurations */
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Local Port %hhu, Partition %hhu\n", ndev->port, ndev->part);
|
|
|
|
/* Peer ports information */
|
|
off += scnprintf(strbuf + off, size - off, "Peers:\n");
|
|
for (idx = 0; idx < ndev->peer_cnt; idx++) {
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"\t%hhu. Port %hhu, Partition %hhu\n",
|
|
idx, ndev->peers[idx].port, ndev->peers[idx].part);
|
|
}
|
|
|
|
/* Links status */
|
|
data = idt_ntb_link_is_up(&ndev->ntb, &speed, &width);
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"NTB link status\t- 0x%08x, ", data);
|
|
off += scnprintf(strbuf + off, size - off, "PCIe Gen %d x%d lanes\n",
|
|
speed, width);
|
|
|
|
/* Mapping table entries */
|
|
off += scnprintf(strbuf + off, size - off, "NTB Mapping Table:\n");
|
|
for (idx = 0; idx < IDT_MTBL_ENTRY_CNT; idx++) {
|
|
spin_lock_irqsave(&ndev->mtbl_lock, irqflags);
|
|
idt_nt_write(ndev, IDT_NT_NTMTBLADDR, idx);
|
|
data = idt_nt_read(ndev, IDT_NT_NTMTBLDATA);
|
|
spin_unlock_irqrestore(&ndev->mtbl_lock, irqflags);
|
|
|
|
/* Print valid entries only */
|
|
if (data & IDT_NTMTBLDATA_VALID) {
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"\t%hhu. Partition %d, Requester ID 0x%04x\n",
|
|
idx, GET_FIELD(NTMTBLDATA_PART, data),
|
|
GET_FIELD(NTMTBLDATA_REQID, data));
|
|
}
|
|
}
|
|
off += scnprintf(strbuf + off, size - off, "\n");
|
|
|
|
/* Outbound memory windows information */
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Outbound Memory Windows:\n");
|
|
for (idx = 0; idx < ndev->mw_cnt; idx += cnt) {
|
|
data = ndev->mws[idx].type;
|
|
cnt = idt_get_mw_count(data);
|
|
|
|
/* Print Memory Window information */
|
|
if (data == IDT_MW_DIR)
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"\t%hhu.\t", idx);
|
|
else
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"\t%hhu-%hhu.\t", idx, idx + cnt - 1);
|
|
|
|
off += scnprintf(strbuf + off, size - off, "%s BAR%hhu, ",
|
|
idt_get_mw_name(data), ndev->mws[idx].bar);
|
|
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Address align 0x%08llx, ", ndev->mws[idx].addr_align);
|
|
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Size align 0x%08llx, Size max %llu\n",
|
|
ndev->mws[idx].size_align, ndev->mws[idx].size_max);
|
|
}
|
|
|
|
/* Inbound memory windows information */
|
|
for (pidx = 0; pidx < ndev->peer_cnt; pidx++) {
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Inbound Memory Windows for peer %hhu (Port %hhu):\n",
|
|
pidx, ndev->peers[pidx].port);
|
|
|
|
/* Print Memory Windows information */
|
|
for (idx = 0; idx < ndev->peers[pidx].mw_cnt; idx += cnt) {
|
|
data = ndev->peers[pidx].mws[idx].type;
|
|
cnt = idt_get_mw_count(data);
|
|
|
|
if (data == IDT_MW_DIR)
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"\t%hhu.\t", idx);
|
|
else
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"\t%hhu-%hhu.\t", idx, idx + cnt - 1);
|
|
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"%s BAR%hhu, ", idt_get_mw_name(data),
|
|
ndev->peers[pidx].mws[idx].bar);
|
|
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Address align 0x%08llx, ",
|
|
ndev->peers[pidx].mws[idx].addr_align);
|
|
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Size align 0x%08llx, Size max %llu\n",
|
|
ndev->peers[pidx].mws[idx].size_align,
|
|
ndev->peers[pidx].mws[idx].size_max);
|
|
}
|
|
}
|
|
off += scnprintf(strbuf + off, size - off, "\n");
|
|
|
|
/* Doorbell information */
|
|
data = idt_sw_read(ndev, IDT_SW_GDBELLSTS);
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Global Doorbell state\t- 0x%08x\n", data);
|
|
data = idt_ntb_db_read(&ndev->ntb);
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Local Doorbell state\t- 0x%08x\n", data);
|
|
data = idt_nt_read(ndev, IDT_NT_INDBELLMSK);
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Local Doorbell mask\t- 0x%08x\n", data);
|
|
off += scnprintf(strbuf + off, size - off, "\n");
|
|
|
|
/* Messaging information */
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Message event valid\t- 0x%08x\n", IDT_MSG_MASK);
|
|
data = idt_ntb_msg_read_sts(&ndev->ntb);
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Message event status\t- 0x%08x\n", data);
|
|
data = idt_nt_read(ndev, IDT_NT_MSGSTSMSK);
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Message event mask\t- 0x%08x\n", data);
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Message data:\n");
|
|
for (idx = 0; idx < IDT_MSG_CNT; idx++) {
|
|
int src;
|
|
data = idt_ntb_msg_read(&ndev->ntb, &src, idx);
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"\t%hhu. 0x%08x from peer %hhu (Port %hhu)\n",
|
|
idx, data, src, ndev->peers[src].port);
|
|
}
|
|
off += scnprintf(strbuf + off, size - off, "\n");
|
|
|
|
/* Current temperature */
|
|
idt_read_temp(ndev, IDT_TEMP_CUR, &mdeg);
|
|
off += scnprintf(strbuf + off, size - off,
|
|
"Switch temperature\t\t- %hhd.%hhuC\n",
|
|
idt_get_deg(mdeg), idt_get_deg_frac(mdeg));
|
|
|
|
/* Copy the buffer to the User Space */
|
|
ret = simple_read_from_buffer(ubuf, count, offp, strbuf, off);
|
|
kfree(strbuf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* idt_init_dbgfs() - initialize DebugFS node
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* Return: zero on success, otherwise a negative error number.
|
|
*/
|
|
static int idt_init_dbgfs(struct idt_ntb_dev *ndev)
|
|
{
|
|
char devname[64];
|
|
|
|
/* If the top directory is not created then do nothing */
|
|
if (IS_ERR_OR_NULL(dbgfs_topdir)) {
|
|
dev_info(&ndev->ntb.pdev->dev, "Top DebugFS directory absent");
|
|
return PTR_ERR(dbgfs_topdir);
|
|
}
|
|
|
|
/* Create the info file node */
|
|
snprintf(devname, 64, "info:%s", pci_name(ndev->ntb.pdev));
|
|
ndev->dbgfs_info = debugfs_create_file(devname, 0400, dbgfs_topdir,
|
|
ndev, &idt_dbgfs_info_ops);
|
|
if (IS_ERR(ndev->dbgfs_info)) {
|
|
dev_dbg(&ndev->ntb.pdev->dev, "Failed to create DebugFS node");
|
|
return PTR_ERR(ndev->dbgfs_info);
|
|
}
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "NTB device DebugFS node created");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* idt_deinit_dbgfs() - deinitialize DebugFS node
|
|
* @ndev: IDT NTB hardware driver descriptor
|
|
*
|
|
* Just discard the info node from DebugFS
|
|
*/
|
|
static void idt_deinit_dbgfs(struct idt_ntb_dev *ndev)
|
|
{
|
|
debugfs_remove(ndev->dbgfs_info);
|
|
|
|
dev_dbg(&ndev->ntb.pdev->dev, "NTB device DebugFS node discarded");
|
|
}
|
|
|
|
/*=============================================================================
|
|
* 11. Basic PCIe device initialization
|
|
*=============================================================================
|
|
*/
|
|
|
|
/*
|
|
* idt_check_setup() - Check whether the IDT PCIe-swtich is properly
|
|
* pre-initialized
|
|
* @pdev: Pointer to the PCI device descriptor
|
|
*
|
|
* Return: zero on success, otherwise a negative error number.
|
|
*/
|
|
static int idt_check_setup(struct pci_dev *pdev)
|
|
{
|
|
u32 data;
|
|
int ret;
|
|
|
|
/* Read the BARSETUP0 */
|
|
ret = pci_read_config_dword(pdev, IDT_NT_BARSETUP0, &data);
|
|
if (ret != 0) {
|
|
dev_err(&pdev->dev,
|
|
"Failed to read BARSETUP0 config register");
|
|
return ret;
|
|
}
|
|
|
|
/* Check whether the BAR0 register is enabled to be of config space */
|
|
if (!(data & IDT_BARSETUP_EN) || !(data & IDT_BARSETUP_MODE_CFG)) {
|
|
dev_err(&pdev->dev, "BAR0 doesn't map config space");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Configuration space BAR0 must have certain size */
|
|
if ((data & IDT_BARSETUP_SIZE_MASK) != IDT_BARSETUP_SIZE_CFG) {
|
|
dev_err(&pdev->dev, "Invalid size of config space");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(&pdev->dev, "NTB device pre-initialized correctly");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Create the IDT PCIe-switch driver descriptor
|
|
* @pdev: Pointer to the PCI device descriptor
|
|
* @id: IDT PCIe-device configuration
|
|
*
|
|
* It just allocates a memory for IDT PCIe-switch device structure and
|
|
* initializes some commonly used fields.
|
|
*
|
|
* No need of release method, since managed device resource is used for
|
|
* memory allocation.
|
|
*
|
|
* Return: pointer to the descriptor, otherwise a negative error number.
|
|
*/
|
|
static struct idt_ntb_dev *idt_create_dev(struct pci_dev *pdev,
|
|
const struct pci_device_id *id)
|
|
{
|
|
struct idt_ntb_dev *ndev;
|
|
|
|
/* Allocate memory for the IDT PCIe-device descriptor */
|
|
ndev = devm_kzalloc(&pdev->dev, sizeof(*ndev), GFP_KERNEL);
|
|
if (!ndev) {
|
|
dev_err(&pdev->dev, "Memory allocation failed for descriptor");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
/* Save the IDT PCIe-switch ports configuration */
|
|
ndev->swcfg = (struct idt_89hpes_cfg *)id->driver_data;
|
|
/* Save the PCI-device pointer inside the NTB device structure */
|
|
ndev->ntb.pdev = pdev;
|
|
|
|
/* Initialize spin locker of Doorbell, Message and GASA registers */
|
|
spin_lock_init(&ndev->db_mask_lock);
|
|
spin_lock_init(&ndev->msg_mask_lock);
|
|
spin_lock_init(&ndev->gasa_lock);
|
|
|
|
dev_info(&pdev->dev, "IDT %s discovered", ndev->swcfg->name);
|
|
|
|
dev_dbg(&pdev->dev, "NTB device descriptor created");
|
|
|
|
return ndev;
|
|
}
|
|
|
|
/*
|
|
* idt_init_pci() - initialize the basic PCI-related subsystem
|
|
* @ndev: Pointer to the IDT PCIe-switch driver descriptor
|
|
*
|
|
* Managed device resources will be freed automatically in case of failure or
|
|
* driver detachment.
|
|
*
|
|
* Return: zero on success, otherwise negative error number.
|
|
*/
|
|
static int idt_init_pci(struct idt_ntb_dev *ndev)
|
|
{
|
|
struct pci_dev *pdev = ndev->ntb.pdev;
|
|
int ret;
|
|
|
|
/* Initialize the bit mask of PCI/NTB DMA */
|
|
ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
|
|
if (ret != 0) {
|
|
ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
|
|
if (ret != 0) {
|
|
dev_err(&pdev->dev, "Failed to set DMA bit mask\n");
|
|
return ret;
|
|
}
|
|
dev_warn(&pdev->dev, "Cannot set DMA highmem bit mask\n");
|
|
}
|
|
ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
|
|
if (ret != 0) {
|
|
ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
|
|
if (ret != 0) {
|
|
dev_err(&pdev->dev,
|
|
"Failed to set consistent DMA bit mask\n");
|
|
return ret;
|
|
}
|
|
dev_warn(&pdev->dev,
|
|
"Cannot set consistent DMA highmem bit mask\n");
|
|
}
|
|
ret = dma_coerce_mask_and_coherent(&ndev->ntb.dev,
|
|
dma_get_mask(&pdev->dev));
|
|
if (ret != 0) {
|
|
dev_err(&pdev->dev, "Failed to set NTB device DMA bit mask\n");
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Enable the device advanced error reporting. It's not critical to
|
|
* have AER disabled in the kernel.
|
|
*/
|
|
ret = pci_enable_pcie_error_reporting(pdev);
|
|
if (ret != 0)
|
|
dev_warn(&pdev->dev, "PCIe AER capability disabled\n");
|
|
else /* Cleanup uncorrectable error status before getting to init */
|
|
pci_cleanup_aer_uncorrect_error_status(pdev);
|
|
|
|
/* First enable the PCI device */
|
|
ret = pcim_enable_device(pdev);
|
|
if (ret != 0) {
|
|
dev_err(&pdev->dev, "Failed to enable PCIe device\n");
|
|
goto err_disable_aer;
|
|
}
|
|
|
|
/*
|
|
* Enable the bus mastering, which effectively enables MSI IRQs and
|
|
* Request TLPs translation
|
|
*/
|
|
pci_set_master(pdev);
|
|
|
|
/* Request all BARs resources and map BAR0 only */
|
|
ret = pcim_iomap_regions_request_all(pdev, 1, NTB_NAME);
|
|
if (ret != 0) {
|
|
dev_err(&pdev->dev, "Failed to request resources\n");
|
|
goto err_clear_master;
|
|
}
|
|
|
|
/* Retrieve virtual address of BAR0 - PCI configuration space */
|
|
ndev->cfgspc = pcim_iomap_table(pdev)[0];
|
|
|
|
/* Put the IDT driver data pointer to the PCI-device private pointer */
|
|
pci_set_drvdata(pdev, ndev);
|
|
|
|
dev_dbg(&pdev->dev, "NT-function PCIe interface initialized");
|
|
|
|
return 0;
|
|
|
|
err_clear_master:
|
|
pci_clear_master(pdev);
|
|
err_disable_aer:
|
|
(void)pci_disable_pcie_error_reporting(pdev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* idt_deinit_pci() - deinitialize the basic PCI-related subsystem
|
|
* @ndev: Pointer to the IDT PCIe-switch driver descriptor
|
|
*
|
|
* Managed resources will be freed on the driver detachment
|
|
*/
|
|
static void idt_deinit_pci(struct idt_ntb_dev *ndev)
|
|
{
|
|
struct pci_dev *pdev = ndev->ntb.pdev;
|
|
|
|
/* Clean up the PCI-device private data pointer */
|
|
pci_set_drvdata(pdev, NULL);
|
|
|
|
/* Clear the bus master disabling the Request TLPs translation */
|
|
pci_clear_master(pdev);
|
|
|
|
/* Disable the AER capability */
|
|
(void)pci_disable_pcie_error_reporting(pdev);
|
|
|
|
dev_dbg(&pdev->dev, "NT-function PCIe interface cleared");
|
|
}
|
|
|
|
/*===========================================================================
|
|
* 12. PCI bus callback functions
|
|
*===========================================================================
|
|
*/
|
|
|
|
/*
|
|
* idt_pci_probe() - PCI device probe callback
|
|
* @pdev: Pointer to PCI device structure
|
|
* @id: PCIe device custom descriptor
|
|
*
|
|
* Return: zero on success, otherwise negative error number
|
|
*/
|
|
static int idt_pci_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *id)
|
|
{
|
|
struct idt_ntb_dev *ndev;
|
|
int ret;
|
|
|
|
/* Check whether IDT PCIe-switch is properly pre-initialized */
|
|
ret = idt_check_setup(pdev);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
/* Allocate the memory for IDT NTB device data */
|
|
ndev = idt_create_dev(pdev, id);
|
|
if (IS_ERR_OR_NULL(ndev))
|
|
return PTR_ERR(ndev);
|
|
|
|
/* Initialize the basic PCI subsystem of the device */
|
|
ret = idt_init_pci(ndev);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
/* Scan ports of the IDT PCIe-switch */
|
|
(void)idt_scan_ports(ndev);
|
|
|
|
/* Initialize NTB link events subsystem */
|
|
idt_init_link(ndev);
|
|
|
|
/* Initialize MWs subsystem */
|
|
ret = idt_init_mws(ndev);
|
|
if (ret != 0)
|
|
goto err_deinit_link;
|
|
|
|
/* Initialize Messaging subsystem */
|
|
idt_init_msg(ndev);
|
|
|
|
/* Initialize hwmon interface */
|
|
idt_init_temp(ndev);
|
|
|
|
/* Initialize IDT interrupts handler */
|
|
ret = idt_init_isr(ndev);
|
|
if (ret != 0)
|
|
goto err_deinit_link;
|
|
|
|
/* Register IDT NTB devices on the NTB bus */
|
|
ret = idt_register_device(ndev);
|
|
if (ret != 0)
|
|
goto err_deinit_isr;
|
|
|
|
/* Initialize DebugFS info node */
|
|
(void)idt_init_dbgfs(ndev);
|
|
|
|
/* IDT PCIe-switch NTB driver is finally initialized */
|
|
dev_info(&pdev->dev, "IDT NTB device is ready");
|
|
|
|
/* May the force be with us... */
|
|
return 0;
|
|
|
|
err_deinit_isr:
|
|
idt_deinit_isr(ndev);
|
|
err_deinit_link:
|
|
idt_deinit_link(ndev);
|
|
idt_deinit_pci(ndev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* idt_pci_probe() - PCI device remove callback
|
|
* @pdev: Pointer to PCI device structure
|
|
*/
|
|
static void idt_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct idt_ntb_dev *ndev = pci_get_drvdata(pdev);
|
|
|
|
/* Deinit the DebugFS node */
|
|
idt_deinit_dbgfs(ndev);
|
|
|
|
/* Unregister NTB device */
|
|
idt_unregister_device(ndev);
|
|
|
|
/* Stop the interrupts handling */
|
|
idt_deinit_isr(ndev);
|
|
|
|
/* Deinitialize link event subsystem */
|
|
idt_deinit_link(ndev);
|
|
|
|
/* Deinit basic PCI subsystem */
|
|
idt_deinit_pci(ndev);
|
|
|
|
/* IDT PCIe-switch NTB driver is finally initialized */
|
|
dev_info(&pdev->dev, "IDT NTB device is removed");
|
|
|
|
/* Sayonara... */
|
|
}
|
|
|
|
/*
|
|
* IDT PCIe-switch models ports configuration structures
|
|
*/
|
|
static const struct idt_89hpes_cfg idt_89hpes24nt6ag2_config = {
|
|
.name = "89HPES24NT6AG2",
|
|
.port_cnt = 6, .ports = {0, 2, 4, 6, 8, 12}
|
|
};
|
|
static const struct idt_89hpes_cfg idt_89hpes32nt8ag2_config = {
|
|
.name = "89HPES32NT8AG2",
|
|
.port_cnt = 8, .ports = {0, 2, 4, 6, 8, 12, 16, 20}
|
|
};
|
|
static const struct idt_89hpes_cfg idt_89hpes32nt8bg2_config = {
|
|
.name = "89HPES32NT8BG2",
|
|
.port_cnt = 8, .ports = {0, 2, 4, 6, 8, 12, 16, 20}
|
|
};
|
|
static const struct idt_89hpes_cfg idt_89hpes12nt12g2_config = {
|
|
.name = "89HPES12NT12G2",
|
|
.port_cnt = 3, .ports = {0, 8, 16}
|
|
};
|
|
static const struct idt_89hpes_cfg idt_89hpes16nt16g2_config = {
|
|
.name = "89HPES16NT16G2",
|
|
.port_cnt = 4, .ports = {0, 8, 12, 16}
|
|
};
|
|
static const struct idt_89hpes_cfg idt_89hpes24nt24g2_config = {
|
|
.name = "89HPES24NT24G2",
|
|
.port_cnt = 8, .ports = {0, 2, 4, 6, 8, 12, 16, 20}
|
|
};
|
|
static const struct idt_89hpes_cfg idt_89hpes32nt24ag2_config = {
|
|
.name = "89HPES32NT24AG2",
|
|
.port_cnt = 8, .ports = {0, 2, 4, 6, 8, 12, 16, 20}
|
|
};
|
|
static const struct idt_89hpes_cfg idt_89hpes32nt24bg2_config = {
|
|
.name = "89HPES32NT24BG2",
|
|
.port_cnt = 8, .ports = {0, 2, 4, 6, 8, 12, 16, 20}
|
|
};
|
|
|
|
/*
|
|
* PCI-ids table of the supported IDT PCIe-switch devices
|
|
*/
|
|
static const struct pci_device_id idt_pci_tbl[] = {
|
|
{IDT_PCI_DEVICE_IDS(89HPES24NT6AG2, idt_89hpes24nt6ag2_config)},
|
|
{IDT_PCI_DEVICE_IDS(89HPES32NT8AG2, idt_89hpes32nt8ag2_config)},
|
|
{IDT_PCI_DEVICE_IDS(89HPES32NT8BG2, idt_89hpes32nt8bg2_config)},
|
|
{IDT_PCI_DEVICE_IDS(89HPES12NT12G2, idt_89hpes12nt12g2_config)},
|
|
{IDT_PCI_DEVICE_IDS(89HPES16NT16G2, idt_89hpes16nt16g2_config)},
|
|
{IDT_PCI_DEVICE_IDS(89HPES24NT24G2, idt_89hpes24nt24g2_config)},
|
|
{IDT_PCI_DEVICE_IDS(89HPES32NT24AG2, idt_89hpes32nt24ag2_config)},
|
|
{IDT_PCI_DEVICE_IDS(89HPES32NT24BG2, idt_89hpes32nt24bg2_config)},
|
|
{0}
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, idt_pci_tbl);
|
|
|
|
/*
|
|
* IDT PCIe-switch NT-function device driver structure definition
|
|
*/
|
|
static struct pci_driver idt_pci_driver = {
|
|
.name = KBUILD_MODNAME,
|
|
.probe = idt_pci_probe,
|
|
.remove = idt_pci_remove,
|
|
.id_table = idt_pci_tbl,
|
|
};
|
|
|
|
static int __init idt_pci_driver_init(void)
|
|
{
|
|
pr_info("%s %s\n", NTB_DESC, NTB_VER);
|
|
|
|
/* Create the top DebugFS directory if the FS is initialized */
|
|
if (debugfs_initialized())
|
|
dbgfs_topdir = debugfs_create_dir(KBUILD_MODNAME, NULL);
|
|
|
|
/* Register the NTB hardware driver to handle the PCI device */
|
|
return pci_register_driver(&idt_pci_driver);
|
|
}
|
|
module_init(idt_pci_driver_init);
|
|
|
|
static void __exit idt_pci_driver_exit(void)
|
|
{
|
|
/* Unregister the NTB hardware driver */
|
|
pci_unregister_driver(&idt_pci_driver);
|
|
|
|
/* Discard the top DebugFS directory */
|
|
debugfs_remove_recursive(dbgfs_topdir);
|
|
}
|
|
module_exit(idt_pci_driver_exit);
|
|
|