diff options
Diffstat (limited to 'drivers/pci/pcie')
-rw-r--r-- | drivers/pci/pcie/Kconfig | 36 | ||||
-rw-r--r-- | drivers/pci/pcie/Makefile | 7 | ||||
-rw-r--r-- | drivers/pci/pcie/portdrv.h | 41 | ||||
-rw-r--r-- | drivers/pci/pcie/portdrv_bus.c | 77 | ||||
-rw-r--r-- | drivers/pci/pcie/portdrv_core.c | 434 | ||||
-rw-r--r-- | drivers/pci/pcie/portdrv_pci.c | 122 |
6 files changed, 717 insertions, 0 deletions
diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig new file mode 100644 index 000000000000..1012db8b8b2c --- /dev/null +++ b/drivers/pci/pcie/Kconfig @@ -0,0 +1,36 @@ +# +# PCI Express Port Bus Configuration +# +config PCIEPORTBUS + bool "PCI Express support" + depends on PCI + help + This automatically enables PCI Express Port Bus support. Users can + choose Native Hot-Plug support, Advanced Error Reporting support, + Power Management Event support and Virtual Channel support to run + on PCI Express Ports (Root or Switch). + +# +# Include service Kconfig here +# +config HOTPLUG_PCI_PCIE + tristate "PCI Express Hotplug driver" + depends on HOTPLUG_PCI && PCIEPORTBUS + help + Say Y here if you have a motherboard that supports PCI Express Native + Hotplug + + To compile this driver as a module, choose M here: the + module will be called pciehp. + + When in doubt, say N. + +config HOTPLUG_PCI_PCIE_POLL_EVENT_MODE + bool "Use polling mechanism for hot-plug events (for testing purpose)" + depends on HOTPLUG_PCI_PCIE + help + Say Y here if you want to use the polling mechanism for hot-plug + events for early platform testing. + + When in doubt, say N. + diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile new file mode 100644 index 000000000000..984fa87283e3 --- /dev/null +++ b/drivers/pci/pcie/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for PCI-Express PORT Driver +# + +pcieportdrv-y := portdrv_core.o portdrv_pci.o portdrv_bus.o + +obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h new file mode 100644 index 000000000000..6c3ad8918667 --- /dev/null +++ b/drivers/pci/pcie/portdrv.h @@ -0,0 +1,41 @@ +/* + * File: portdrv.h + * Purpose: PCI Express Port Bus Driver's Internal Data Structures + * + * Copyright (C) 2004 Intel + * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) + */ + +#ifndef _PORTDRV_H_ +#define _PORTDRV_H_ + +#if !defined(PCI_CAP_ID_PME) +#define PCI_CAP_ID_PME 1 +#endif + +#if !defined(PCI_CAP_ID_EXP) +#define PCI_CAP_ID_EXP 0x10 +#endif + +#define PORT_TYPE_MASK 0xf +#define PORT_TO_SLOT_MASK 0x100 +#define SLOT_HP_CAPABLE_MASK 0x40 +#define PCIE_CAPABILITIES_REG 0x2 +#define PCIE_SLOT_CAPABILITIES_REG 0x14 +#define PCIE_PORT_DEVICE_MAXSERVICES 4 +#define PCI_CFG_SPACE_SIZE 256 + +#define get_descriptor_id(type, service) (((type - 4) << 4) | service) + +extern struct bus_type pcie_port_bus_type; +extern int pcie_port_device_probe(struct pci_dev *dev); +extern int pcie_port_device_register(struct pci_dev *dev); +#ifdef CONFIG_PM +extern int pcie_port_device_suspend(struct pci_dev *dev, u32 state); +extern int pcie_port_device_resume(struct pci_dev *dev); +#endif +extern void pcie_port_device_remove(struct pci_dev *dev); +extern void pcie_port_bus_register(void); +extern void pcie_port_bus_unregister(void); + +#endif /* _PORTDRV_H_ */ diff --git a/drivers/pci/pcie/portdrv_bus.c b/drivers/pci/pcie/portdrv_bus.c new file mode 100644 index 000000000000..9a02f28ed05f --- /dev/null +++ b/drivers/pci/pcie/portdrv_bus.c @@ -0,0 +1,77 @@ +/* + * File: portdrv_bus.c + * Purpose: PCI Express Port Bus Driver's Bus Overloading Functions + * + * Copyright (C) 2004 Intel + * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> + +#include <linux/pcieport_if.h> + +static int pcie_port_bus_match(struct device *dev, struct device_driver *drv); +static int pcie_port_bus_suspend(struct device *dev, u32 state); +static int pcie_port_bus_resume(struct device *dev); + +struct bus_type pcie_port_bus_type = { + .name = "pci_express", + .match = pcie_port_bus_match, + .suspend = pcie_port_bus_suspend, + .resume = pcie_port_bus_resume, +}; + +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->id_table->vendor != PCI_ANY_ID && + driver->id_table->vendor != pciedev->id.vendor) || + (driver->id_table->device != PCI_ANY_ID && + driver->id_table->device != pciedev->id.device) || + driver->id_table->port_type != pciedev->id.port_type || + driver->id_table->service_type != pciedev->id.service_type ) + return 0; + + return 1; +} + +static int pcie_port_bus_suspend(struct device *dev, u32 state) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + if (driver && driver->suspend) + driver->suspend(pciedev, state); + return 0; +} + +static int pcie_port_bus_resume(struct device *dev) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + if (driver && driver->resume) + driver->resume(pciedev); + return 0; +} diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c new file mode 100644 index 000000000000..127f64f85dc5 --- /dev/null +++ b/drivers/pci/pcie/portdrv_core.c @@ -0,0 +1,434 @@ +/* + * File: portdrv_core.c + * Purpose: PCI Express Port Bus Driver's Core Functions + * + * Copyright (C) 2004 Intel + * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/pcieport_if.h> + +#include "portdrv.h" + +extern int pcie_mch_quirk; /* MSI-quirk Indicator */ + +static int pcie_port_probe_service(struct device *dev) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + int status = -ENODEV; + + if (!dev || !dev->driver) + return status; + + driver = to_service_driver(dev->driver); + if (!driver || !driver->probe) + return status; + + pciedev = to_pcie_device(dev); + status = driver->probe(pciedev, driver->id_table); + if (!status) { + printk(KERN_DEBUG "Load service driver %s on pcie device %s\n", + driver->name, dev->bus_id); + get_device(dev); + } + return status; +} + +static int pcie_port_remove_service(struct device *dev) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + if (driver && driver->remove) { + printk(KERN_DEBUG "Unload service driver %s on pcie device %s\n", + driver->name, dev->bus_id); + driver->remove(pciedev); + put_device(dev); + } + return 0; +} + +static void pcie_port_shutdown_service(struct device *dev) {} + +static int pcie_port_suspend_service(struct device *dev, u32 state, u32 level) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + if (driver && driver->suspend) + driver->suspend(pciedev, state); + return 0; +} + +static int pcie_port_resume_service(struct device *dev, u32 state) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + + if (driver && driver->resume) + driver->resume(pciedev); + return 0; +} + +/* + * release_pcie_device + * + * Being invoked automatically when device is being removed + * in response to device_unregister(dev) call. + * Release all resources being claimed. + */ +static void release_pcie_device(struct device *dev) +{ + printk(KERN_DEBUG "Free Port Service[%s]\n", dev->bus_id); + kfree(to_pcie_device(dev)); +} + +static int is_msi_quirked(struct pci_dev *dev) +{ + int port_type, quirk = 0; + u16 reg16; + + pci_read_config_word(dev, + pci_find_capability(dev, PCI_CAP_ID_EXP) + + PCIE_CAPABILITIES_REG, ®16); + port_type = (reg16 >> 4) & PORT_TYPE_MASK; + switch(port_type) { + case PCIE_RC_PORT: + if (pcie_mch_quirk == 1) + quirk = 1; + break; + case PCIE_SW_UPSTREAM_PORT: + case PCIE_SW_DOWNSTREAM_PORT: + default: + break; + } + return quirk; +} + +static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask) +{ + int i, pos, nvec, status = -EINVAL; + int interrupt_mode = PCIE_PORT_INTx_MODE; + + /* Set INTx as default */ + for (i = 0, nvec = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { + if (mask & (1 << i)) + nvec++; + vectors[i] = dev->irq; + } + + /* Check MSI quirk */ + if (is_msi_quirked(dev)) + return interrupt_mode; + + /* Select MSI-X over MSI if supported */ + pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); + if (pos) { + struct msix_entry msix_entries[PCIE_PORT_DEVICE_MAXSERVICES] = + {{0, 0}, {0, 1}, {0, 2}, {0, 3}}; + printk("%s Found MSIX capability\n", __FUNCTION__); + status = pci_enable_msix(dev, msix_entries, nvec); + if (!status) { + int j = 0; + + interrupt_mode = PCIE_PORT_MSIX_MODE; + for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { + if (mask & (1 << i)) + vectors[i] = msix_entries[j++].vector; + } + } + } + if (status) { + pos = pci_find_capability(dev, PCI_CAP_ID_MSI); + if (pos) { + printk("%s Found MSI capability\n", __FUNCTION__); + status = pci_enable_msi(dev); + if (!status) { + interrupt_mode = PCIE_PORT_MSI_MODE; + for (i = 0;i < PCIE_PORT_DEVICE_MAXSERVICES;i++) + vectors[i] = dev->irq; + } + } + } + return interrupt_mode; +} + +static int get_port_device_capability(struct pci_dev *dev) +{ + int services = 0, pos; + u16 reg16; + u32 reg32; + + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, ®16); + /* Hot-Plug Capable */ + if (reg16 & PORT_TO_SLOT_MASK) { + pci_read_config_dword(dev, + pos + PCIE_SLOT_CAPABILITIES_REG, ®32); + if (reg32 & SLOT_HP_CAPABLE_MASK) + services |= PCIE_PORT_SERVICE_HP; + } + /* PME Capable */ + pos = pci_find_capability(dev, PCI_CAP_ID_PME); + if (pos) + services |= PCIE_PORT_SERVICE_PME; + + pos = PCI_CFG_SPACE_SIZE; + while (pos) { + pci_read_config_dword(dev, pos, ®32); + switch (reg32 & 0xffff) { + case PCI_EXT_CAP_ID_ERR: + services |= PCIE_PORT_SERVICE_AER; + pos = reg32 >> 20; + break; + case PCI_EXT_CAP_ID_VC: + services |= PCIE_PORT_SERVICE_VC; + pos = reg32 >> 20; + break; + default: + pos = 0; + break; + } + } + + return services; +} + +static void pcie_device_init(struct pci_dev *parent, struct pcie_device *dev, + int port_type, int service_type, int irq, int irq_mode) +{ + struct device *device; + + dev->port = parent; + dev->interrupt_mode = irq_mode; + dev->irq = irq; + dev->id.vendor = parent->vendor; + dev->id.device = parent->device; + dev->id.port_type = port_type; + dev->id.service_type = (1 << service_type); + + /* Initialize generic device interface */ + device = &dev->device; + memset(device, 0, sizeof(struct device)); + INIT_LIST_HEAD(&device->node); + INIT_LIST_HEAD(&device->children); + INIT_LIST_HEAD(&device->bus_list); + device->bus = &pcie_port_bus_type; + device->driver = NULL; + device->driver_data = NULL; + device->release = release_pcie_device; /* callback to free pcie dev */ + sprintf(&device->bus_id[0], "pcie%02x", + get_descriptor_id(port_type, service_type)); + device->parent = &parent->dev; +} + +static struct pcie_device* alloc_pcie_device(struct pci_dev *parent, + int port_type, int service_type, int irq, int irq_mode) +{ + struct pcie_device *device; + + device = kmalloc(sizeof(struct pcie_device), GFP_KERNEL); + if (!device) + return NULL; + + memset(device, 0, sizeof(struct pcie_device)); + pcie_device_init(parent, device, port_type, service_type, irq,irq_mode); + printk(KERN_DEBUG "Allocate Port Service[%s]\n", device->device.bus_id); + return device; +} + +int pcie_port_device_probe(struct pci_dev *dev) +{ + int pos, type; + u16 reg; + + if (!(pos = pci_find_capability(dev, PCI_CAP_ID_EXP))) + return -ENODEV; + + pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, ®); + type = (reg >> 4) & PORT_TYPE_MASK; + if ( type == PCIE_RC_PORT || type == PCIE_SW_UPSTREAM_PORT || + type == PCIE_SW_DOWNSTREAM_PORT ) + return 0; + + return -ENODEV; +} + +int pcie_port_device_register(struct pci_dev *dev) +{ + int status, type, capabilities, irq_mode, i; + int vectors[PCIE_PORT_DEVICE_MAXSERVICES]; + u16 reg16; + + /* Get port type */ + pci_read_config_word(dev, + pci_find_capability(dev, PCI_CAP_ID_EXP) + + PCIE_CAPABILITIES_REG, ®16); + type = (reg16 >> 4) & PORT_TYPE_MASK; + + /* Now get port services */ + capabilities = get_port_device_capability(dev); + irq_mode = assign_interrupt_mode(dev, vectors, capabilities); + + /* Allocate child services if any */ + for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { + struct pcie_device *child; + + if (capabilities & (1 << i)) { + child = alloc_pcie_device( + dev, /* parent */ + type, /* port type */ + i, /* service type */ + vectors[i], /* irq */ + irq_mode /* interrupt mode */); + if (child) { + status = device_register(&child->device); + if (status) { + kfree(child); + continue; + } + get_device(&child->device); + } + } + } + return 0; +} + +#ifdef CONFIG_PM +int pcie_port_device_suspend(struct pci_dev *dev, u32 state) +{ + struct list_head *head, *tmp; + struct device *parent, *child; + struct device_driver *driver; + struct pcie_port_service_driver *service_driver; + + parent = &dev->dev; + head = &parent->children; + tmp = head->next; + while (head != tmp) { + child = container_of(tmp, struct device, node); + tmp = tmp->next; + if (child->bus != &pcie_port_bus_type) + continue; + driver = child->driver; + if (!driver) + continue; + service_driver = to_service_driver(driver); + if (service_driver->suspend) + service_driver->suspend(to_pcie_device(child), state); + } + return 0; +} + +int pcie_port_device_resume(struct pci_dev *dev) +{ + struct list_head *head, *tmp; + struct device *parent, *child; + struct device_driver *driver; + struct pcie_port_service_driver *service_driver; + + parent = &dev->dev; + head = &parent->children; + tmp = head->next; + while (head != tmp) { + child = container_of(tmp, struct device, node); + tmp = tmp->next; + if (child->bus != &pcie_port_bus_type) + continue; + driver = child->driver; + if (!driver) + continue; + service_driver = to_service_driver(driver); + if (service_driver->resume) + service_driver->resume(to_pcie_device(child)); + } + return 0; + +} +#endif + +void pcie_port_device_remove(struct pci_dev *dev) +{ + struct list_head *head, *tmp; + struct device *parent, *child; + struct device_driver *driver; + struct pcie_port_service_driver *service_driver; + int interrupt_mode = PCIE_PORT_INTx_MODE; + + parent = &dev->dev; + head = &parent->children; + tmp = head->next; + while (head != tmp) { + child = container_of(tmp, struct device, node); + tmp = tmp->next; + if (child->bus != &pcie_port_bus_type) + continue; + driver = child->driver; + if (driver) { + service_driver = to_service_driver(driver); + if (service_driver->remove) + service_driver->remove(to_pcie_device(child)); + } + interrupt_mode = (to_pcie_device(child))->interrupt_mode; + put_device(child); + device_unregister(child); + } + /* Switch to INTx by default if MSI enabled */ + if (interrupt_mode == PCIE_PORT_MSIX_MODE) + pci_disable_msix(dev); + else if (interrupt_mode == PCIE_PORT_MSI_MODE) + pci_disable_msi(dev); +} + +void pcie_port_bus_register(void) +{ + bus_register(&pcie_port_bus_type); +} + +void pcie_port_bus_unregister(void) +{ + bus_unregister(&pcie_port_bus_type); +} + +int pcie_port_service_register(struct pcie_port_service_driver *new) +{ + new->driver.name = (char *)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; + new->driver.suspend = pcie_port_suspend_service; + new->driver.resume = pcie_port_resume_service; + + return driver_register(&new->driver); +} + +void pcie_port_service_unregister(struct pcie_port_service_driver *new) +{ + driver_unregister(&new->driver); +} + +EXPORT_SYMBOL(pcie_port_service_register); +EXPORT_SYMBOL(pcie_port_service_unregister); diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c new file mode 100644 index 000000000000..3184843c3649 --- /dev/null +++ b/drivers/pci/pcie/portdrv_pci.c @@ -0,0 +1,122 @@ +/* + * File: portdrv_pci.c + * Purpose: PCI Express Port Bus Driver + * + * Copyright (C) 2004 Intel + * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/pcieport_if.h> + +#include "portdrv.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.0" +#define DRIVER_AUTHOR "tom.l.nguyen@intel.com" +#define DRIVER_DESC "PCIE Port Bus Driver" +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* global data */ +static const char device_name[] = "pcieport-driver"; + +/* + * 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 __devinit pcie_portdrv_probe (struct pci_dev *dev, + const struct pci_device_id *id ) +{ + int status; + + status = pcie_port_device_probe(dev); + if (status) + return status; + + if (pci_enable_device(dev) < 0) + return -ENODEV; + + pci_set_master(dev); + if (!dev->irq) { + printk(KERN_WARNING + "%s->Dev[%04x:%04x] has invalid IRQ. Check vendor BIOS\n", + __FUNCTION__, dev->device, dev->vendor); + } + if (pcie_port_device_register(dev)) + return -ENOMEM; + + return 0; +} + +static void pcie_portdrv_remove (struct pci_dev *dev) +{ + pcie_port_device_remove(dev); +} + +#ifdef CONFIG_PM +static int pcie_portdrv_suspend (struct pci_dev *dev, u32 state) +{ + return pcie_port_device_suspend(dev, state); +} + +static int pcie_portdrv_resume (struct pci_dev *dev) +{ + return pcie_port_device_resume(dev); +} +#endif + +/* + * 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), + }, { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, port_pci_ids); + +static struct pci_driver pcie_portdrv = { + .name = (char *)device_name, + .id_table = &port_pci_ids[0], + + .probe = pcie_portdrv_probe, + .remove = pcie_portdrv_remove, + +#ifdef CONFIG_PM + .suspend = pcie_portdrv_suspend, + .resume = pcie_portdrv_resume, +#endif /* PM */ +}; + +static int __init pcie_portdrv_init(void) +{ + int retval = 0; + + pcie_port_bus_register(); + retval = pci_register_driver(&pcie_portdrv); + if (retval) + pcie_port_bus_unregister(); + return retval; +} + +static void __exit pcie_portdrv_exit(void) +{ + pci_unregister_driver(&pcie_portdrv); + pcie_port_bus_unregister(); +} + +module_init(pcie_portdrv_init); +module_exit(pcie_portdrv_exit); |