修改Linux内核中PCIe设备的复位模块
修改Linux内核中PCIe设备的复位模块以适配NPU,以Linux-5.4.0内核修改为例,在linux-source-5.4.0\drivers\pci\quirks.c文件中新增如下代码,重新编译内核并安装使用。
#include "hotplug/pciehp.h"
#ifndef PCIE_RESET_READY_POLL_MS
#define PCIE_RESET_READY_POLL_MS 60000
#endif
#ifndef PCI_VENDOR_ID_HUAWEI
#define PCI_VENDOR_ID_HUAWEI 0x19e5
#endif
#define PCI_DEVICE_ID_910A3 0xd803
#define SLOT_ID_REG 0x9C
static inline struct pci_dev *ctrl_dev(struct controller *ctrl)
{
return ctrl->pcie->port;
}
static int pcie_poll_cmd(struct controller *ctrl, int timeout)
{
struct pci_dev *pdev = ctrl_dev(ctrl);
u16 slot_status;
do {
pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status);
if (slot_status == (u16) ~0) {
ctrl_info(ctrl, "%s: no response from device\n",
__func__);
return 0;
}
if (slot_status & PCI_EXP_SLTSTA_CC) {
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
PCI_EXP_SLTSTA_CC);
return 1;
}
msleep(10);
timeout -= 10;
} while (timeout >= 0);
return 0; /* timeout */
}
static void pcie_wait_cmd(struct controller *ctrl)
{
unsigned int msecs = pciehp_poll_mode ? 2500 : 1000;
unsigned long duration = msecs_to_jiffies(msecs);
unsigned long cmd_timeout = ctrl->cmd_started + duration;
unsigned long now, timeout;
int rc;
/*
* If the controller does not generate notifications for command
* completions, we never need to wait between writes.
*/
if (NO_CMD_CMPL(ctrl))
return;
if (!ctrl->cmd_busy)
return;
/*
* Even if the command has already timed out, we want to call
* pcie_poll_cmd() so it can clear PCI_EXP_SLTSTA_CC.
*/
now = jiffies;
if (time_before_eq(cmd_timeout, now))
timeout = 1;
else
timeout = cmd_timeout - now;
if (ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE &&
ctrl->slot_ctrl & PCI_EXP_SLTCTL_CCIE)
rc = wait_event_timeout(ctrl->queue, !ctrl->cmd_busy, timeout);
else
rc = pcie_poll_cmd(ctrl, jiffies_to_msecs(timeout));
if (!rc)
ctrl_info(ctrl, "Timeout on hotplug command %#06x (issued %u msec ago)\n",
ctrl->slot_ctrl,
jiffies_to_msecs(jiffies - ctrl->cmd_started));
}
#define CC_ERRATUM_MASK (PCI_EXP_SLTCTL_PCC | \
PCI_EXP_SLTCTL_PIC | \
PCI_EXP_SLTCTL_AIC | \
PCI_EXP_SLTCTL_EIC)
static void pcie_do_write_cmd(struct controller *ctrl, u16 cmd,
u16 mask, bool wait)
{
struct pci_dev *pdev = ctrl_dev(ctrl);
u16 slot_ctrl_orig, slot_ctrl;
mutex_lock(&ctrl->ctrl_lock);
/*
* Always wait for any previous command that might still be in progress
*/
pcie_wait_cmd(ctrl);
pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl);
if (slot_ctrl == (u16) ~0) {
ctrl_info(ctrl, "%s: no response from device\n", __func__);
goto out;
}
slot_ctrl_orig = slot_ctrl;
slot_ctrl &= ~mask;
slot_ctrl |= (cmd & mask);
ctrl->cmd_busy = 1;
smp_mb();
ctrl->slot_ctrl = slot_ctrl;
pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl);
ctrl->cmd_started = jiffies;
/*
* Controllers with the Intel CF118 and similar errata advertise
* Command Completed support, but they only set Command Completed
* if we change the "Control" bits for power, power indicator,
* attention indicator, or interlock. If we only change the
* "Enable" bits, they never set the Command Completed bit.
*/
if (pdev->broken_cmd_compl &&
(slot_ctrl_orig & CC_ERRATUM_MASK) == (slot_ctrl & CC_ERRATUM_MASK))
ctrl->cmd_busy = 0;
/*
* Optionally wait for the hardware to be ready for a new command,
* indicating completion of the above issued command.
*/
if (wait)
pcie_wait_cmd(ctrl);
out:
mutex_unlock(&ctrl->ctrl_lock);
}
/**
* pcie_write_cmd - Issue controller command
* @ctrl: controller to which the command is issued
* @cmd: command value written to slot control register
* @mask: bitmask of slot control register to be modified
*/
static void pcie_write_cmd(struct controller *ctrl, u16 cmd, u16 mask)
{
pcie_do_write_cmd(ctrl, cmd, mask, true);
}
/* Same as above without waiting for the hardware to latch */
static void pcie_write_cmd_nowait(struct controller *ctrl, u16 cmd, u16 mask)
{
pcie_do_write_cmd(ctrl, cmd, mask, false);
}
static int pci_dev_wait(struct pci_dev *dev, char *reset_type, int timeout)
{
int delay = 1;
u32 id;
/*
* After reset, the device should not silently discard config
* requests, but it may still indicate that it needs more time by
* responding to them with CRS completions. The Root Port will
* generally synthesize ~0 data to complete the read (except when
* CRS SV is enabled and the read was for the Vendor ID; in that
* case it synthesizes 0x0001 data).
*
* Wait for the device to return a non-CRS completion. Read the
* Command register instead of Vendor ID so we don't have to
* contend with the CRS SV value.
*/
pci_read_config_dword(dev, PCI_COMMAND, &id);
while (id == ~0) {
if (delay > timeout) {
pci_warn(dev, "not ready %dms after %s; giving up\n",
delay - 1, reset_type);
return -ENOTTY;
}
if (delay > 1000)
pci_info(dev, "not ready %dms after %s; waiting\n",
delay - 1, reset_type);
msleep(delay);
delay *= 2;
pci_read_config_dword(dev, PCI_COMMAND, &id);
}
if (delay > 1000)
pci_info(dev, "ready %dms after %s\n", delay - 1,
reset_type);
return 0;
}
/* Return 1 on successful lock, 0 on contention */
static int pci_dev_trylock(struct pci_dev *dev)
{
if (pci_cfg_access_trylock(dev)) {
if (device_trylock(&dev->dev))
return 1;
pci_cfg_access_unlock(dev);
}
return 0;
}
static void pci_dev_unlock(struct pci_dev *dev)
{
device_unlock(&dev->dev);
pci_cfg_access_unlock(dev);
}
static void pci_reset_secondary_bus_910a3(struct pci_dev *master_dev, struct pci_dev *slave_dev)
{
u16 slave_ctrl;
u16 master_ctrl;
pci_read_config_word(slave_dev, PCI_BRIDGE_CONTROL, &slave_ctrl);
slave_ctrl |= PCI_BRIDGE_CTL_BUS_RESET;
pci_write_config_word(slave_dev, PCI_BRIDGE_CONTROL, slave_ctrl);
pci_read_config_word(master_dev, PCI_BRIDGE_CONTROL, &master_ctrl);
master_ctrl |= PCI_BRIDGE_CTL_BUS_RESET;
pci_write_config_word(master_dev, PCI_BRIDGE_CONTROL, master_ctrl);
/*
* PCI spec v3.0 7.6.4.2 requires minimum Trst of 1ms. Double
* this to 2ms to ensure that we meet the minimum requirement.
*/
msleep(2);
slave_ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET;
pci_write_config_word(slave_dev, PCI_BRIDGE_CONTROL, slave_ctrl);
master_ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET;
pci_write_config_word(master_dev, PCI_BRIDGE_CONTROL, master_ctrl);
ssleep(1);
}
static void pcibios_reset_secondary_bus_910a3(struct pci_dev *master_dev, struct pci_dev *slave_dev)
{
pci_reset_secondary_bus_910a3(master_dev, slave_dev);
}
static int pci_bridge_secondary_bus_reset_910a3(struct pci_dev *master_dev, struct pci_dev *slave_dev)
{
pcibios_reset_secondary_bus_910a3(master_dev, slave_dev);
return pci_dev_wait(master_dev, "bus reset", PCIE_RESET_READY_POLL_MS);
}
static int pci_parent_bus_reset_910a3(struct pci_dev *master_dev, struct pci_dev *slave_dev)
{
struct pci_dev *pdev;
pci_dbg(master_dev, "[%s] master[%s] & slave[%s]\n", __func__, dev_name(&master_dev->dev), dev_name(&slave_dev->dev));
if (pci_is_root_bus(master_dev->bus) || master_dev->subordinate ||
!master_dev->bus->self || master_dev->dev_flags & PCI_DEV_FLAGS_NO_BUS_RESET)
return -ENOTTY;
list_for_each_entry(pdev, &master_dev->bus->devices, bus_list)
if (pdev != master_dev)
return -ENOTTY;
if (pci_is_root_bus(slave_dev->bus) || slave_dev->subordinate ||
!slave_dev->bus->self || slave_dev->dev_flags & PCI_DEV_FLAGS_NO_BUS_RESET)
return -ENOTTY;
list_for_each_entry(pdev, &slave_dev->bus->devices, bus_list)
if (pdev != slave_dev)
return -ENOTTY;
return pci_bridge_secondary_bus_reset_910a3(master_dev->bus->self, slave_dev->bus->self);
}
static int pciehp_reset_slot_910a3(struct hotplug_slot *hotplug_master, struct hotplug_slot *hotplug_slave)
{
struct controller *ctrl0 = to_ctrl(hotplug_master);
struct pci_dev *pdev0 = ctrl_dev(ctrl0);
struct controller *ctrl1 = to_ctrl(hotplug_slave);
struct pci_dev *pdev1 = ctrl_dev(ctrl1);
u16 stat_mask0 = 0, ctrl_mask0 = 0;
u16 stat_mask1 = 0, ctrl_mask1 = 0;
int rc;
down_write(&ctrl0->reset_lock);
down_write(&ctrl1->reset_lock);
if (!ATTN_BUTTN(ctrl0)) {
ctrl_mask0 |= PCI_EXP_SLTCTL_PDCE;
stat_mask0 |= PCI_EXP_SLTSTA_PDC;
}
ctrl_mask0 |= PCI_EXP_SLTCTL_DLLSCE;
stat_mask0 |= PCI_EXP_SLTSTA_DLLSC;
if (!ATTN_BUTTN(ctrl1)) {
ctrl_mask1 |= PCI_EXP_SLTCTL_PDCE;
stat_mask1 |= PCI_EXP_SLTSTA_PDC;
}
ctrl_mask1 |= PCI_EXP_SLTCTL_DLLSCE;
stat_mask1 |= PCI_EXP_SLTSTA_DLLSC;
pcie_write_cmd(ctrl0, 0, ctrl_mask0);
ctrl_dbg(ctrl0, "%s: SLOTCTRL %x write cmd %x\n", __func__,
pci_pcie_cap(ctrl0->pcie->port) + PCI_EXP_SLTCTL, 0);
pcie_write_cmd(ctrl1, 0, ctrl_mask1);
ctrl_dbg(ctrl1, "%s: SLOTCTRL %x write cmd %x\n", __func__,
pci_pcie_cap(ctrl1->pcie->port) + PCI_EXP_SLTCTL, 0);
rc = pci_bridge_secondary_bus_reset_910a3(ctrl0->pcie->port, ctrl1->pcie->port);
pcie_capability_write_word(pdev0, PCI_EXP_SLTSTA, stat_mask0);
pcie_capability_write_word(pdev1, PCI_EXP_SLTSTA, stat_mask1);
pcie_write_cmd_nowait(ctrl0, ctrl_mask0, ctrl_mask0);
ctrl_dbg(ctrl0, "%s: SLOTCTRL %x write cmd %x\n", __func__,
pci_pcie_cap(ctrl0->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask0);
pcie_write_cmd_nowait(ctrl1, ctrl_mask1, ctrl_mask1);
ctrl_dbg(ctrl1, "%s: SLOTCTRL %x write cmd %x\n", __func__,
pci_pcie_cap(ctrl1->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask1);
up_write(&ctrl0->reset_lock);
up_write(&ctrl1->reset_lock);
return rc;
}
static int pci_reset_hotplug_slot_910a3(struct hotplug_slot *hotplug_master, struct hotplug_slot *hotplug_slave)
{
int rc = -ENOTTY;
if (!hotplug_master || !try_module_get(hotplug_master->owner) ||
!hotplug_slave || !try_module_get(hotplug_slave->owner))
return rc;
rc = pciehp_reset_slot_910a3(hotplug_master, hotplug_slave);
module_put(hotplug_master->owner);
module_put(hotplug_slave->owner);
return rc;
}
static int pci_dev_reset_slot_function_910a3(struct pci_dev *master_dev, struct pci_dev *slave_dev)
{
pci_dbg(master_dev, "[%s] die0[%s] & die1[%s]\n", __func__, dev_name(&master_dev->dev), dev_name(&slave_dev->dev));
if (master_dev->subordinate || !master_dev->slot ||
master_dev->dev_flags & PCI_DEV_FLAGS_NO_BUS_RESET ||
slave_dev->subordinate || !slave_dev->slot ||
slave_dev->dev_flags & PCI_DEV_FLAGS_NO_BUS_RESET) {
pci_dbg(master_dev, "[%s] Not support hotplug function.\n", __func__);
return -ENOTTY;
}
return pci_reset_hotplug_slot_910a3(master_dev->slot->hotplug, slave_dev->slot->hotplug);
}
static int pci_reset_bus_function_910a3(struct pci_dev *master_dev, struct pci_dev *slave_dev)
{
int rc;
rc = pci_dev_reset_slot_function_910a3(master_dev, slave_dev);
if (rc != -ENOTTY)
return rc;
return pci_parent_bus_reset_910a3(master_dev, slave_dev);
}
static int ascend_910a3_double_device_reset(struct pci_dev *pdev, int probe)
{
int ret = 0;
struct pci_bus *slave_bus = NULL;
struct pci_dev *slave_dev = NULL;
u8 slot_id;
u8 slot_id_master;
struct pci_dev *dev = NULL;
pci_dbg(pdev, "===>>>[%s] Entering, probe = %d\n", __func__, probe);
if (probe)
return 0;
pci_read_config_byte(pdev, SLOT_ID_REG, &slot_id);
if (slot_id >= 0x10) {
pci_warn(pdev, "[%s] SLOT_ID is error[slot%d]\n", __func__, slot_id);
ret = -ENOTTY;
goto out;
}
/* slave, directly return */
if (slot_id % 2) {
pci_dbg(pdev, "[%s] slave[slot%d], directly return\n", __func__, slot_id);
return 0;
}
/* master, need to find slave */
slot_id_master = slot_id;
do {
dev = pci_get_device(PCI_VENDOR_ID_HUAWEI, PCI_DEVICE_ID_910A3, dev);
if (NULL == dev) {
break;
} else if (dev == pdev) {
continue;
}
pci_read_config_byte(dev, SLOT_ID_REG, &slot_id);
if (slot_id == slot_id_master + 1) {
/* find slave */
slave_dev = dev;
pci_info(pdev, "[%s] Find slave(%s)[slot%d]\n", __func__, dev_name(&slave_dev->dev), slot_id);
break;
}
} while (1);
if (NULL == slave_dev) {
pci_warn(pdev, "[%s] Can't find slave device, master[slot%d]\n", __func__, slot_id_master);
ret = -ENOTTY;
goto out;
}
if (!pci_dev_trylock(slave_dev)) {
pci_warn(slave_dev, "[%s] pci_dev_trylock error\n", __func__);
ret = -ENOTTY;
goto out;
}
slave_bus = slave_dev->bus;
ret = pci_save_state(slave_bus->self);
if (ret) {
pci_err(slave_bus->self, "[%s] Save slave bus device state error: %d\n", __func__, ret);
goto out_lock;
}
ret = pci_save_state(slave_dev);
if (ret) {
pci_err(slave_dev, "[%s] Save slave device state error: %d\n", __func__, ret);
goto out_lock;
}
/* bus reset */
ret = pci_reset_bus_function_910a3(pdev, slave_dev);
if (ret) {
pci_err(pdev, "[%s] bus reset error: %d\n", __func__, ret);
}
pci_restore_state(slave_bus->self);
pci_restore_state(slave_dev);
out_lock:
pci_dev_unlock(slave_dev);
out:
if (slave_dev) {
pci_dev_put(slave_dev);
}
pci_dbg(pdev, "<<<===[%s] Exiting, probe = %d\n", __func__, probe);
return ret;
}
// 在pci_dev_reset_methods[] 全局变量的{ 0 }前面, 插入如下代码
{ PCI_VENDOR_ID_HUAWEI, PCI_DEVICE_ID_910A3,
ascend_910a3_double_device_reset },