portdrv_core.c 12.5 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13
/*
 * 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>
T
Tim Schmielau 已提交
14 15
#include <linux/string.h>
#include <linux/slab.h>
L
Linus Torvalds 已提交
16 17
#include <linux/pcieport_if.h>

18
#include "../pci.h"
L
Linus Torvalds 已提交
19 20
#include "portdrv.h"

21 22 23 24 25 26
/**
 * release_pcie_device - free PCI Express port service device structure
 * @dev: Port service device to release
 *
 * Invoked automatically when device is being removed in response to
 * device_unregister(dev).  Release all resources being claimed.
L
Linus Torvalds 已提交
27 28 29 30 31 32
 */
static void release_pcie_device(struct device *dev)
{
	kfree(to_pcie_device(dev));			
}

33 34 35 36 37 38 39 40 41
/**
 * assign_interrupt_mode - choose interrupt mode for PCI Express port services
 *                         (INTx, MSI-X, MSI) and set up vectors
 * @dev: PCI Express port to handle
 * @vectors: Array of interrupt vectors to populate
 * @mask: Bitmask of port capabilities returned by get_port_device_capability()
 *
 * Return value: Interrupt mode associated with the port
 */
L
Linus Torvalds 已提交
42 43
static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask)
{
44
	struct pcie_port_data *port_data = pci_get_drvdata(dev);
L
Linus Torvalds 已提交
45
	int i, pos, nvec, status = -EINVAL;
46
	int interrupt_mode = PCIE_PORT_NO_IRQ;
L
Linus Torvalds 已提交
47 48 49 50 51 52 53

	/* Set INTx as default */
	for (i = 0, nvec = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
		if (mask & (1 << i)) 
			nvec++;
		vectors[i] = dev->irq;
	}
54 55 56
	if (dev->pin)
		interrupt_mode = PCIE_PORT_INTx_MODE;

L
Linus Torvalds 已提交
57
	/* Check MSI quirk */
58
	if (port_data->port_type == PCIE_RC_PORT && pcie_mch_quirk)
L
Linus Torvalds 已提交
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
		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}};
		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) {
			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;
}

91 92 93 94 95 96 97 98 99 100
/**
 * get_port_device_capability - discover capabilities of a PCI Express port
 * @dev: PCI Express port to examine
 *
 * The capabilities are read from the port's PCI Express configuration registers
 * as described in PCI Express Base Specification 1.0a sections 7.8.2, 7.8.9 and
 * 7.9 - 7.11.
 *
 * Return value: Bitmask of discovered port capabilities
 */
L
Linus Torvalds 已提交
101 102 103 104 105 106 107 108 109 110 111 112 113 114
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, &reg16);
	/* Hot-Plug Capable */
	if (reg16 & PORT_TO_SLOT_MASK) {
		pci_read_config_dword(dev, 
			pos + PCIE_SLOT_CAPABILITIES_REG, &reg32);
		if (reg32 & SLOT_HP_CAPABLE_MASK)
			services |= PCIE_PORT_SERVICE_HP;
115 116
	}
	/* AER capable */
117 118
	if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR))
		services |= PCIE_PORT_SERVICE_AER;
119
	/* VC support */
120 121
	if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_VC))
		services |= PCIE_PORT_SERVICE_VC;
L
Linus Torvalds 已提交
122 123 124 125

	return services;
}

126 127 128 129 130 131 132 133
/**
 * pcie_device_init - initialize PCI Express port service device
 * @dev: Port service device to initialize
 * @parent: PCI Express port to associate the service device with
 * @port_type: Type of the port
 * @service_type: Type of service to associate with the service device
 * @irq: Interrupt vector to associate with the service device
 */
L
Linus Torvalds 已提交
134
static void pcie_device_init(struct pci_dev *parent, struct pcie_device *dev, 
135
	int service_type, int irq)
L
Linus Torvalds 已提交
136
{
137
	struct pcie_port_data *port_data = pci_get_drvdata(parent);
L
Linus Torvalds 已提交
138
	struct device *device;
139
	int port_type = port_data->port_type;
L
Linus Torvalds 已提交
140 141 142 143 144 145

	dev->port = parent;
	dev->irq = irq;
	dev->id.vendor = parent->vendor;
	dev->id.device = parent->device;
	dev->id.port_type = port_type;
146
	dev->id.service_type = service_type;
L
Linus Torvalds 已提交
147 148 149 150 151 152

	/* Initialize generic device interface */
	device = &dev->device;
	memset(device, 0, sizeof(struct device));
	device->bus = &pcie_port_bus_type;
	device->driver = NULL;
153
	device->driver_data = NULL;
L
Linus Torvalds 已提交
154
	device->release = release_pcie_device;	/* callback to free pcie dev */
155
	dev_set_name(device, "%s:pcie%02x",
156
		 pci_name(parent), get_descriptor_id(port_type, service_type));
L
Linus Torvalds 已提交
157 158 159
	device->parent = &parent->dev;
}

160 161 162 163 164 165 166
/**
 * alloc_pcie_device - allocate PCI Express port service device structure
 * @parent: PCI Express port to associate the service device with
 * @port_type: Type of the port
 * @service_type: Type of service to associate with the service device
 * @irq: Interrupt vector to associate with the service device
 */
167
static struct pcie_device* alloc_pcie_device(struct pci_dev *parent,
168
	int service_type, int irq)
L
Linus Torvalds 已提交
169 170 171
{
	struct pcie_device *device;

172
	device = kzalloc(sizeof(struct pcie_device), GFP_KERNEL);
L
Linus Torvalds 已提交
173 174 175
	if (!device)
		return NULL;

176
	pcie_device_init(parent, device, service_type, irq);
L
Linus Torvalds 已提交
177 178 179
	return device;
}

180 181 182 183
/**
 * pcie_port_device_probe - check if device is a PCI Express port
 * @dev: Device to check
 */
L
Linus Torvalds 已提交
184 185 186 187 188 189 190 191 192 193 194
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, &reg);
	type = (reg >> 4) & PORT_TYPE_MASK;
	if (	type == PCIE_RC_PORT || type == PCIE_SW_UPSTREAM_PORT ||
195
		type == PCIE_SW_DOWNSTREAM_PORT )
L
Linus Torvalds 已提交
196
		return 0;
197

L
Linus Torvalds 已提交
198 199 200
	return -ENODEV;
}

201 202 203 204 205 206 207
/**
 * 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.
 */
L
Linus Torvalds 已提交
208 209
int pcie_port_device_register(struct pci_dev *dev)
{
210 211
	struct pcie_port_data *port_data;
	int status, capabilities, irq_mode, i;
L
Linus Torvalds 已提交
212 213 214
	int vectors[PCIE_PORT_DEVICE_MAXSERVICES];
	u16 reg16;

215 216
	port_data = kzalloc(sizeof(*port_data), GFP_KERNEL);
	if (!port_data)
217
		return -ENOMEM;
218
	pci_set_drvdata(dev, port_data);
219

L
Linus Torvalds 已提交
220
	/* Get port type */
221 222
	pci_read_config_word(dev,
		pci_find_capability(dev, PCI_CAP_ID_EXP) +
L
Linus Torvalds 已提交
223
		PCIE_CAPABILITIES_REG, &reg16);
224
	port_data->port_type = (reg16 >> 4) & PORT_TYPE_MASK;
L
Linus Torvalds 已提交
225 226

	capabilities = get_port_device_capability(dev);
227 228 229 230
	/* Root ports are capable of generating PME too */
	if (port_data->port_type == PCIE_RC_PORT)
		capabilities |= PCIE_PORT_SERVICE_PME;

L
Linus Torvalds 已提交
231
	irq_mode = assign_interrupt_mode(dev, vectors, capabilities);
232
	port_data->port_irq_mode = irq_mode;
L
Linus Torvalds 已提交
233 234 235 236

	/* Allocate child services if any */
	for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
		struct pcie_device *child;
237
		int service = 1 << i;
L
Linus Torvalds 已提交
238

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
		if (!(capabilities & service))
			continue;

		/*
		 * Don't use service devices that require interrupts if there is
		 * no way to generate them.
		 */
		if (irq_mode == PCIE_PORT_NO_IRQ
		    && service != PCIE_PORT_SERVICE_VC)
			continue;

		child = alloc_pcie_device(dev, service, vectors[i]);
		if (!child)
			continue;

		status = device_register(&child->device);
		if (status) {
			kfree(child);
			continue;
L
Linus Torvalds 已提交
258
		}
259 260

		get_device(&child->device);
L
Linus Torvalds 已提交
261
	}
262

L
Linus Torvalds 已提交
263 264 265 266
	return 0;
}

#ifdef CONFIG_PM
267
static int suspend_iter(struct device *dev, void *data)
L
Linus Torvalds 已提交
268 269
{
	struct pcie_port_service_driver *service_driver;
270
	pm_message_t state = * (pm_message_t *) data;
271 272 273 274 275 276 277 278 279

 	if ((dev->bus == &pcie_port_bus_type) &&
 	    (dev->driver)) {
 		service_driver = to_service_driver(dev->driver);
 		if (service_driver->suspend)
 			service_driver->suspend(to_pcie_device(dev), state);
  	}
	return 0;
}
L
Linus Torvalds 已提交
280

281 282 283 284 285
/**
 * pcie_port_device_suspend - suspend port services associated with a PCIe port
 * @dev: PCI Express port to handle
 * @state: Representation of system power management transition in progress
 */
286
int pcie_port_device_suspend(struct pci_dev *dev, pm_message_t state)
287
{
288
	return device_for_each_child(&dev->dev, &state, suspend_iter);
L
Linus Torvalds 已提交
289 290
}

291 292
static int resume_iter(struct device *dev, void *data)
{
L
Linus Torvalds 已提交
293 294
	struct pcie_port_service_driver *service_driver;

295 296 297 298 299
	if ((dev->bus == &pcie_port_bus_type) &&
	    (dev->driver)) {
		service_driver = to_service_driver(dev->driver);
		if (service_driver->resume)
			service_driver->resume(to_pcie_device(dev));
L
Linus Torvalds 已提交
300
	}
301 302
	return 0;
}
L
Linus Torvalds 已提交
303

304 305 306 307
/**
 * pcie_port_device_suspend - resume port services associated with a PCIe port
 * @dev: PCI Express port to handle
 */
308 309
int pcie_port_device_resume(struct pci_dev *dev)
{
310
	return device_for_each_child(&dev->dev, NULL, resume_iter);
L
Linus Torvalds 已提交
311 312 313
}
#endif

314
static int remove_iter(struct device *dev, void *data)
L
Linus Torvalds 已提交
315 316 317
{
	struct pcie_port_service_driver *service_driver;

318 319 320 321 322
	if (dev->bus == &pcie_port_bus_type) {
		if (dev->driver) {
			service_driver = to_service_driver(dev->driver);
			if (service_driver->remove)
				service_driver->remove(to_pcie_device(dev));
L
Linus Torvalds 已提交
323
		}
324 325
		*(unsigned long*)data = (unsigned long)dev;
		return 1;
L
Linus Torvalds 已提交
326
	}
327 328 329
	return 0;
}

330 331 332 333 334 335 336
/**
 * pcie_port_device_remove - unregister PCI Express port service devices
 * @dev: PCI Express port the service devices to unregister are associated with
 *
 * Remove PCI Express port service devices associated with given port and
 * disable MSI-X or MSI for the port.
 */
337 338
void pcie_port_device_remove(struct pci_dev *dev)
{
339
	struct pcie_port_data *port_data = pci_get_drvdata(dev);
340 341 342
	int status;

	do {
343 344
		unsigned long device_addr;

345 346
		status = device_for_each_child(&dev->dev, &device_addr, remove_iter);
		if (status) {
347
			struct device *device = (struct device*)device_addr;
348 349 350 351
			put_device(device);
			device_unregister(device);
		}
	} while (status);
352 353 354

	switch (port_data->port_irq_mode) {
	case PCIE_PORT_MSIX_MODE:
L
Linus Torvalds 已提交
355
		pci_disable_msix(dev);
356 357
		break;
	case PCIE_PORT_MSI_MODE:
L
Linus Torvalds 已提交
358
		pci_disable_msi(dev);
359 360 361 362
		break;
	}

	kfree(port_data);
L
Linus Torvalds 已提交
363 364
}

365 366 367 368 369 370 371 372
/**
 * pcie_port_probe_service - probe driver for given PCI Express port service
 * @dev: PCI Express port service device to probe against
 *
 * If PCI Express port service driver is registered with
 * pcie_port_service_register(), this function will be called by the driver core
 * whenever match is found between the driver and a port service device.
 */
373
static int pcie_port_probe_service(struct device *dev)
L
Linus Torvalds 已提交
374
{
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
	struct pcie_device *pciedev;
	struct pcie_port_service_driver *driver;
	int status;

	if (!dev || !dev->driver)
		return -ENODEV;

	driver = to_service_driver(dev->driver);
	if (!driver || !driver->probe)
		return -ENODEV;

	pciedev = to_pcie_device(dev);
	status = driver->probe(pciedev, driver->id_table);
	if (!status) {
		dev_printk(KERN_DEBUG, dev, "service driver %s loaded\n",
			driver->name);
		get_device(dev);
	}
	return status;
L
Linus Torvalds 已提交
394 395
}

396 397 398 399 400 401 402 403 404
/**
 * pcie_port_remove_service - detach driver from 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_unregister() is called for the port service device associated
 * with the driver.
 */
405
static int pcie_port_remove_service(struct device *dev)
L
Linus Torvalds 已提交
406
{
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
	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) {
		dev_printk(KERN_DEBUG, dev, "unloading service driver %s\n",
			driver->name);
		driver->remove(pciedev);
		put_device(dev);
	}
	return 0;
L
Linus Torvalds 已提交
422 423
}

424 425 426 427 428 429 430 431 432
/**
 * 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.
 */
433 434
static void pcie_port_shutdown_service(struct device *dev) {}

435 436 437 438
/**
 * pcie_port_service_register - register PCI Express port service driver
 * @new: PCI Express port service driver to register
 */
L
Linus Torvalds 已提交
439 440 441 442 443 444 445 446 447
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;

	return driver_register(&new->driver);
448
}
L
Linus Torvalds 已提交
449

450 451 452 453
/**
 * pcie_port_service_unregister - unregister PCI Express port service driver
 * @drv: PCI Express port service driver to unregister
 */
454
void pcie_port_service_unregister(struct pcie_port_service_driver *drv)
L
Linus Torvalds 已提交
455
{
456
	driver_unregister(&drv->driver);
L
Linus Torvalds 已提交
457 458 459 460
}

EXPORT_SYMBOL(pcie_port_service_register);
EXPORT_SYMBOL(pcie_port_service_unregister);