2019-02-27 14:58:17 -06:00
// SPDX-License-Identifier: GPL-2.0+
/*
* PCI Express Link Bandwidth Notification services driver
* Author : Alexandru Gagniuc < mr . nuke . me @ gmail . com >
*
* Copyright ( C ) 2019 , Dell Inc
*
* The PCIe Link Bandwidth Notification provides a way to notify the
* operating system when the link width or data rate changes . This
* capability is required for all root ports and downstream ports
* supporting links wider than x1 and / or multiple link speeds .
*
* This service port driver hooks into the bandwidth notification interrupt
* and warns when links become degraded in operation .
*/
2020-09-10 19:24:15 +08:00
# define dev_fmt(fmt) "bw_notification: " fmt
2019-02-27 14:58:17 -06:00
# include "../pci.h"
# include "portdrv.h"
static bool pcie_link_bandwidth_notification_supported ( struct pci_dev * dev )
{
int ret ;
u32 lnk_cap ;
ret = pcie_capability_read_dword ( dev , PCI_EXP_LNKCAP , & lnk_cap ) ;
return ( ret = = PCIBIOS_SUCCESSFUL ) & & ( lnk_cap & PCI_EXP_LNKCAP_LBNC ) ;
}
static void pcie_enable_link_bandwidth_notification ( struct pci_dev * dev )
{
u16 lnk_ctl ;
2019-03-20 12:05:30 +01:00
pcie_capability_write_word ( dev , PCI_EXP_LNKSTA , PCI_EXP_LNKSTA_LBMS ) ;
2019-02-27 14:58:17 -06:00
pcie_capability_read_word ( dev , PCI_EXP_LNKCTL , & lnk_ctl ) ;
lnk_ctl | = PCI_EXP_LNKCTL_LBMIE ;
pcie_capability_write_word ( dev , PCI_EXP_LNKCTL , lnk_ctl ) ;
}
static void pcie_disable_link_bandwidth_notification ( struct pci_dev * dev )
{
u16 lnk_ctl ;
pcie_capability_read_word ( dev , PCI_EXP_LNKCTL , & lnk_ctl ) ;
lnk_ctl & = ~ PCI_EXP_LNKCTL_LBMIE ;
pcie_capability_write_word ( dev , PCI_EXP_LNKCTL , lnk_ctl ) ;
}
2019-03-22 19:36:51 -05:00
static irqreturn_t pcie_bw_notification_irq ( int irq , void * context )
2019-02-27 14:58:17 -06:00
{
struct pcie_device * srv = context ;
struct pci_dev * port = srv - > port ;
u16 link_status , events ;
int ret ;
ret = pcie_capability_read_word ( port , PCI_EXP_LNKSTA , & link_status ) ;
events = link_status & PCI_EXP_LNKSTA_LBMS ;
if ( ret ! = PCIBIOS_SUCCESSFUL | | ! events )
return IRQ_NONE ;
2019-03-22 19:36:51 -05:00
pcie_capability_write_word ( port , PCI_EXP_LNKSTA , events ) ;
pcie_update_link_speed ( port - > subordinate , link_status ) ;
return IRQ_WAKE_THREAD ;
}
static irqreturn_t pcie_bw_notification_handler ( int irq , void * context )
{
struct pcie_device * srv = context ;
struct pci_dev * port = srv - > port ;
struct pci_dev * dev ;
2019-02-27 14:58:17 -06:00
/*
* Print status from downstream devices , not this root port or
* downstream switch port .
*/
down_read ( & pci_bus_sem ) ;
list_for_each_entry ( dev , & port - > subordinate - > devices , bus_list )
2019-03-20 12:05:30 +01:00
pcie_report_downtraining ( dev ) ;
2019-02-27 14:58:17 -06:00
up_read ( & pci_bus_sem ) ;
return IRQ_HANDLED ;
}
static int pcie_bandwidth_notification_probe ( struct pcie_device * srv )
{
int ret ;
/* Single-width or single-speed ports do not have to support this. */
if ( ! pcie_link_bandwidth_notification_supported ( srv - > port ) )
return - ENODEV ;
2019-03-22 19:36:51 -05:00
ret = request_threaded_irq ( srv - > irq , pcie_bw_notification_irq ,
pcie_bw_notification_handler ,
2019-02-27 14:58:17 -06:00
IRQF_SHARED , " PCIe BW notif " , srv ) ;
if ( ret )
return ret ;
pcie_enable_link_bandwidth_notification ( srv - > port ) ;
2020-09-10 19:24:15 +08:00
pci_info ( srv - > port , " enabled with IRQ %d \n " , srv - > irq ) ;
2019-02-27 14:58:17 -06:00
return 0 ;
}
static void pcie_bandwidth_notification_remove ( struct pcie_device * srv )
{
pcie_disable_link_bandwidth_notification ( srv - > port ) ;
free_irq ( srv - > irq , srv ) ;
}
2019-04-15 11:41:42 +03:00
static int pcie_bandwidth_notification_suspend ( struct pcie_device * srv )
{
pcie_disable_link_bandwidth_notification ( srv - > port ) ;
return 0 ;
}
static int pcie_bandwidth_notification_resume ( struct pcie_device * srv )
{
pcie_enable_link_bandwidth_notification ( srv - > port ) ;
return 0 ;
}
2019-02-27 14:58:17 -06:00
static struct pcie_port_service_driver pcie_bandwidth_notification_driver = {
. name = " pcie_bw_notification " ,
. port_type = PCIE_ANY_PORT ,
. service = PCIE_PORT_SERVICE_BWNOTIF ,
. probe = pcie_bandwidth_notification_probe ,
2019-04-15 11:41:42 +03:00
. suspend = pcie_bandwidth_notification_suspend ,
. resume = pcie_bandwidth_notification_resume ,
2019-02-27 14:58:17 -06:00
. remove = pcie_bandwidth_notification_remove ,
} ;
int __init pcie_bandwidth_notification_init ( void )
{
return pcie_port_service_register ( & pcie_bandwidth_notification_driver ) ;
}