概述

上图展示两种类型的PCIe端口设备:Root Port和 Switch Port

  • Root Complex下的的两个口叫Root Port
  • Switch上游连接的端口叫做Switch Upstream Port
  • Switch下游连接的端口叫做Switch Downstream Port

每个PCIe端口设备最多可以支持最多五种不同服务:热插拔(HP)、电源管理事件(PME)、高级错误报告(AER)、虚拟通道(VC)、带宽监控(BWNOTIF)。

PCIe port service有以下几种功能:

/* Service Type */
#define PCIE_PORT_SERVICE_PME_SHIFT        0        /* Power Management Event */
#define PCIE_PORT_SERVICE_PME                (1 << PCIE_PORT_SERVICE_PME_SHIFT)
#define PCIE_PORT_SERVICE_AER_SHIFT        1        /* Advanced Error Reporting */
#define PCIE_PORT_SERVICE_AER                (1 << PCIE_PORT_SERVICE_AER_SHIFT)
#define PCIE_PORT_SERVICE_HP_SHIFT        2        /* Native Hotplug */
#define PCIE_PORT_SERVICE_HP                (1 << PCIE_PORT_SERVICE_HP_SHIFT)
#define PCIE_PORT_SERVICE_DPC_SHIFT        3        /* Downstream Port Containment */
#define PCIE_PORT_SERVICE_DPC                (1 << PCIE_PORT_SERVICE_DPC_SHIFT)
#define PCIE_PORT_SERVICE_BWNOTIF_SHIFT        4        /* Bandwidth notification */
#define PCIE_PORT_SERVICE_BWNOTIF        (1 << PCIE_PORT_SERVICE_BWNOTIF_SHIFT)

#define PCIE_PORT_DEVICE_MAXSERVICES   5
struct pcie_port_service_driver {
        const char *name;                                                // service name
        int (*probe)(struct pcie_device *dev);
        void (*remove)(struct pcie_device *dev);
        int (*suspend)(struct pcie_device *dev);
        int (*resume_noirq)(struct pcie_device *dev);
        int (*resume)(struct pcie_device *dev);
        int (*runtime_suspend)(struct pcie_device *dev);
        int (*runtime_resume)(struct pcie_device *dev);

        /* Device driver may resume normal operations */
        void (*error_resume)(struct pci_dev *dev);

        /* Link Reset Capability - AER service driver specific */
        pci_ers_result_t (*reset_link)(struct pci_dev *dev);

        // port type,一般为 PCI_EXP_TYPE_ROOT_PORT
        int port_type;  /* Type of the port this driver can handle */
        
        // port service类型,有以下 5种:
        // PCIE_PORT_SERVICE_PME     Power Management Event
        // PCIE_PORT_SERVICE_AER     Advanced Error Reporting
        // PCIE_PORT_SERVICE_HP      Native Hotplug
        // PCIE_PORT_SERVICE_DPC     Downstream Port Containment
        // PCIE_PORT_SERVICE_BWNOTIF Bandwidth notification
        u32 service;    /* Port service this device represents */

        struct device_driver driver;
};

portdrv的初始化

struct pci_driver pcie_portdriver

其vendor、device等ID均为 PCI_ANY_ID,表示每一个pci_bus下均需要注册此设备。在 pci_scan_single_device 通过 pci_device_add 添加 pci_dev 时,将会match上,并调用 pcie_portdrv_probe,实现相关的初始化。

/**
 * PCI_DEVICE_CLASS - macro used to describe a specific PCI device class
 * @dev_class: the class, subclass, prog-if triple for this device
 * @dev_class_mask: the class mask for this device
 *
 * This macro is used to create a struct pci_device_id that matches a
 * specific PCI class.  The vendor, device, subvendor, and subdevice
 * fields will be set to PCI_ANY_ID.
 */
#define PCI_DEVICE_CLASS(dev_class,dev_class_mask) \
        .class = (dev_class), .class_mask = (dev_class_mask), \
        .vendor = PCI_ANY_ID, .device = PCI_ANY_ID, \
        .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID

/*
 * LINUX Device Driver Model
 */
static const struct pci_device_id port_pci_ids[] = {
        /* handle any PCI-Express port */
        { PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0) },
        /* subtractive decode PCI-to-PCI bridge, class type is 060401h */
        { PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x01), ~0) },
        { },
};

static struct pci_driver pcie_portdriver = {
        .name                = "pcieport",
        .id_table        = &port_pci_ids[0],

        .probe                = pcie_portdrv_probe,
        .remove                = pcie_portdrv_remove,
        .shutdown        = pcie_portdrv_remove,

        .err_handler        = &pcie_portdrv_err_handler,

        .driver.pm        = PCIE_PORTDRV_PM_OPS,
};

初始化流程

pcie_portdrv_init
    |---> pcie_init_services
    |        |---> pcie_aer_init
    |        |            |---> pcie_port_service_register(&aerdriver);
    |        |---> pcie_pme_init
    |        |            |---> pcie_port_service_register(&pcie_pme_driver);
    |        |---> pcie_dpc_init
    |        |            |---> pcie_port_service_register(&dpcdriver);
    |        |---> pcie_hp_init
    |        |            |---> pcie_port_service_register(&hpdriver_portdrv);
    |        |---> pcie_bandwidth_notification_init
    |                     |---> pcie_port_service_register(&pcie_bandwidth_notification_driver);
    |---> pci_register_driver(&pcie_portdriver);
static void __init pcie_init_services(void)
{
        pcie_aer_init();
        pcie_pme_init();
        pcie_dpc_init();
        pcie_hp_init();
        pcie_bandwidth_notification_init();
}

static int __init pcie_portdrv_init(void)
{
        if (pcie_ports_disabled)
                return -EACCES;

        pcie_init_services();
        dmi_check_system(pcie_portdrv_dmi_table);

        return pci_register_driver(&pcie_portdriver);
}
device_initcall(pcie_portdrv_init);
/*
 * pcie_portdrv_probe - Probe PCI-Express port devices
 * @dev: PCI-Express port device being probed
 *
 * If detected invokes the pcie_port_device_register() method for
 * this port device.
 *
 */
static int pcie_portdrv_probe(struct pci_dev *dev,
                                        const struct pci_device_id *id)
{
        int status;

        // 仅支持 root、up、down 三种port
        if (!pci_is_pcie(dev) ||
            ((pci_pcie_type(dev) != PCI_EXP_TYPE_ROOT_PORT) &&
             (pci_pcie_type(dev) != PCI_EXP_TYPE_UPSTREAM) &&
             (pci_pcie_type(dev) != PCI_EXP_TYPE_DOWNSTREAM)))
                return -ENODEV;

        // 注册 port service device,如:aer、hp等
        status = pcie_port_device_register(dev);
        if (status)
                return status;

        pci_save_state(dev);

        dev_pm_set_driver_flags(&dev->dev, DPM_FLAG_NEVER_SKIP |
                                           DPM_FLAG_SMART_SUSPEND);

        if (pci_bridge_d3_possible(dev)) {
                /*
                 * Keep the port resumed 100ms to make sure things like
                 * config space accesses from userspace (lspci) will not
                 * cause the port to repeatedly suspend and resume.
                 */
                pm_runtime_set_autosuspend_delay(&dev->dev, 100);
                pm_runtime_use_autosuspend(&dev->dev);
                pm_runtime_mark_last_busy(&dev->dev);
                pm_runtime_put_autosuspend(&dev->dev);
                pm_runtime_allow(&dev->dev);
        }

        return 0;
}

port service driver注册

AER注册

/**
 * aer_service_init - register AER root service driver
 *
 * Invoked when AER root service driver is loaded.
 */
int __init pcie_aer_init(void)
{
        if (!pci_aer_available() || aer_acpi_firmware_first())
                return -ENXIO;
        return pcie_port_service_register(&aerdriver);
}

PME注册

/**
 * pcie_pme_service_init - Register the PCIe PME service driver.
 */
int __init pcie_pme_init(void)
{
        return pcie_port_service_register(&pcie_pme_driver);
}

DPC注册

int __init pcie_dpc_init(void)
{
        return pcie_port_service_register(&dpcdriver);
}

HP注册

int __init pcie_hp_init(void)
{
        int retval = 0;

        retval = pcie_port_service_register(&hpdriver_portdrv);
        pr_debug("pcie_port_service_register = %d\n", retval);
        if (retval)
                pr_debug("Failure to register service\n");

        return retval;
}

BW notification注册

int __init pcie_bandwidth_notification_init(void)
{
        return pcie_port_service_register(&pcie_bandwidth_notification_driver);
}

pcie_port_service_register

/**
 * pcie_port_shutdown_service - shut down given PCI Express port service
 * @dev: PCI Express port service device to handle
 *
 * If PCI Express port service driver is registered with
 * pcie_port_service_register(), this function will be called by the driver core
 * when device_shutdown() is called for the port service device associated
 * with the driver.
 */
static void pcie_port_shutdown_service(struct device *dev) {}

/**
 * pcie_port_service_register - register PCI Express port service driver
 * @new: PCI Express port service driver to register
 */
int pcie_port_service_register(struct pcie_port_service_driver *new)
{
        if (pcie_ports_disabled)
                return -ENODEV;

        new->driver.name = new->name;
        new->driver.bus = &pcie_port_bus_type;
        new->driver.probe = pcie_port_probe_service;
        new->driver.remove = pcie_port_remove_service;
        new->driver.shutdown = pcie_port_shutdown_service;

        return driver_register(&new->driver);
}
EXPORT_SYMBOL(pcie_port_service_register);

/**
 * pcie_port_service_unregister - unregister PCI Express port service driver
 * @drv: PCI Express port service driver to unregister
 */
void pcie_port_service_unregister(struct pcie_port_service_driver *drv)
{
        driver_unregister(&drv->driver);
}
EXPORT_SYMBOL(pcie_port_service_unregister);

pcie_port_bus_type

static int pcie_port_bus_match(struct device *dev, struct device_driver *drv)
{
        struct pcie_device *pciedev;
        struct pcie_port_service_driver *driver;

        if (drv->bus != &pcie_port_bus_type || dev->bus != &pcie_port_bus_type)
                return 0;

        pciedev = to_pcie_device(dev);
        driver = to_service_driver(drv);

        if (driver->service != pciedev->service)
                return 0;

        if (driver->port_type != PCIE_ANY_PORT &&
            driver->port_type != pci_pcie_type(pciedev->port))
                return 0;

        return 1;
}

struct bus_type pcie_port_bus_type = {
        .name                = "pci_express",
        .match                = pcie_port_bus_match,
};
EXPORT_SYMBOL_GPL(pcie_port_bus_type);
#endif

static int __init pci_driver_init(void)
{
        int ret;

        // 创建PCI总线
        ret = bus_register(&pci_bus_type);
        if (ret)
                return ret;

#ifdef CONFIG_PCIEPORTBUS
        // 创建PCIe总线
        ret = bus_register(&pcie_port_bus_type);
        if (ret)
                return ret;
#endif
        dma_debug_add_bus(&pci_bus_type);
        return 0;
}
postcore_initcall(pci_driver_init);

port service device注册

pcie_portdrv_probe
        |---> pcie_port_device_register
                    |---> get_port_device_capability
                    |---> pcie_init_service_irqs
                    |            |---> pcie_port_enable_irq_vec
                    |            |            |---> pci_alloc_irq_vectors
                    |            |---> pci_alloc_irq_vectors
                    |---> pcie_device_init

pcie_port_device_register

/**
 * pcie_port_device_register - register PCI Express port
 * @dev: PCI Express port to register
 *
 * Allocate the port extension structure and register services associated with
 * the port.
 */
int pcie_port_device_register(struct pci_dev *dev)
{
        int status, capabilities, i, nr_service;
        int irqs[PCIE_PORT_DEVICE_MAXSERVICES];

        /* Enable PCI Express port device */
        status = pci_enable_device(dev);
        if (status)
                return status;

        /* Get and check PCI Express port services */
        // 获取 pci_dev 支持的 port service,如:aer、hp等
        capabilities = get_port_device_capability(dev);
        if (!capabilities)
                return 0;

        pci_set_master(dev);
        /*
         * Initialize service irqs. Don't use service devices that
         * require interrupts if there is no way to generate them.
         * However, some drivers may have a polling mode (e.g. pciehp_poll_mode)
         * that can be used in the absence of irqs.  Allow them to determine
         * if that is to be used.
         */
        // pci port service device的irq初始化
        status = pcie_init_service_irqs(dev, irqs, capabilities);
        if (status) {
                capabilities &= PCIE_PORT_SERVICE_HP;
                if (!capabilities)
                        goto error_disable;
        }

        /* Allocate child services if any */
        status = -ENODEV;
        nr_service = 0;
        for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
                int service = 1 << i;
                if (!(capabilities & service))
                        continue;
                // 添加 pcie_port_service_device,即注册 struct device,其bus为 pcie_port_bus_type
                if (!pcie_device_init(dev, service, irqs[i]))
                        nr_service++;
        }
        if (!nr_service)
                goto error_cleanup_irqs;

        return 0;

error_cleanup_irqs:
        pci_free_irq_vectors(dev);
error_disable:
        pci_disable_device(dev);
        return status;
}

pcie_init_service_irqs

/**
 * pcie_init_service_irqs - initialize irqs for PCI Express port services
 * @dev: PCI Express port to handle
 * @irqs: Array of irqs to populate
 * @mask: Bitmask of port capabilities returned by get_port_device_capability()
 *
 * Return value: Interrupt mode associated with the port
 */
static int pcie_init_service_irqs(struct pci_dev *dev, int *irqs, int mask)
{
        int ret, i;

        for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
                irqs[i] = -1;

        /*
         * If we support PME but can't use MSI/MSI-X for it, we have to
         * fall back to INTx or other interrupts, e.g., a system shared
         * interrupt.
         */
        if ((mask & PCIE_PORT_SERVICE_PME) && pcie_pme_no_msi())
                goto legacy_irq;

        /* Try to use MSI-X or MSI if supported */
        // 优先申请 MSI 或 MSI-X 的中断
        if (pcie_port_enable_irq_vec(dev, irqs, mask) == 0)
                return 0;

legacy_irq:
        /* fall back to legacy IRQ */
        ret = pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_LEGACY);
        if (ret < 0)
                return -ENODEV;

        for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
                irqs[i] = pci_irq_vector(dev, 0);

        return 0;
}

pcie_device_init

/**
 * pcie_device_init - allocate and initialize PCI Express port service device
 * @pdev: PCI Express port to associate the service device with
 * @service: Type of service to associate with the service device
 * @irq: Interrupt vector to associate with the service device
 */
static int pcie_device_init(struct pci_dev *pdev, int service, int irq)
{
        int retval;
        struct pcie_device *pcie;
        struct device *device;

        pcie = kzalloc(sizeof(*pcie), GFP_KERNEL);
        if (!pcie)
                return -ENOMEM;
        pcie->port = pdev;
        pcie->irq = irq;
        pcie->service = service;

        /* Initialize generic device interface */
        device = &pcie->device;
        device->bus = &pcie_port_bus_type;
        device->release = release_pcie_device;        /* callback to free pcie dev */
        dev_set_name(device, "%s:pcie%03x",
                     pci_name(pdev),
                     get_descriptor_id(pci_pcie_type(pdev), service));
        device->parent = &pdev->dev;
        device_enable_async_suspend(device);

        retval = device_register(device);
        if (retval) {
                put_device(device);
                return retval;
        }

        pm_runtime_no_callbacks(device);

        return 0;
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐