portdrv_pci.c 10.6 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13
/*
 * 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>
14
#include <linux/pm_runtime.h>
L
Linus Torvalds 已提交
15 16
#include <linux/init.h>
#include <linux/pcieport_if.h>
17
#include <linux/aer.h>
18
#include <linux/dmi.h>
19
#include <linux/pci-aspm.h>
L
Linus Torvalds 已提交
20 21

#include "portdrv.h"
22
#include "aer/aerdrv.h"
L
Linus Torvalds 已提交
23 24 25 26 27 28

/*
 * Version Information
 */
#define DRIVER_VERSION "v1.0"
#define DRIVER_AUTHOR "tom.l.nguyen@intel.com"
29
#define DRIVER_DESC "PCIe Port Bus Driver"
L
Linus Torvalds 已提交
30 31 32 33
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");

34 35 36
/* If this switch is set, PCIe port native services should not be enabled. */
bool pcie_ports_disabled;

37 38 39 40 41 42
/*
 * If this switch is set, ACPI _OSC will be used to determine whether or not to
 * enable PCIe port native services.
 */
bool pcie_ports_auto = true;

43 44
static int __init pcie_port_setup(char *str)
{
45
	if (!strncmp(str, "compat", 6)) {
46
		pcie_ports_disabled = true;
47 48 49 50 51 52 53
	} else if (!strncmp(str, "native", 6)) {
		pcie_ports_disabled = false;
		pcie_ports_auto = false;
	} else if (!strncmp(str, "auto", 4)) {
		pcie_ports_disabled = false;
		pcie_ports_auto = true;
	}
54 55 56 57 58

	return 1;
}
__setup("pcie_ports=", pcie_port_setup);

L
Linus Torvalds 已提交
59 60
/* global data */

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
/**
 * pcie_clear_root_pme_status - Clear root port PME interrupt status.
 * @dev: PCIe root port or event collector.
 */
void pcie_clear_root_pme_status(struct pci_dev *dev)
{
	int rtsta_pos;
	u32 rtsta;

	rtsta_pos = pci_pcie_cap(dev) + PCI_EXP_RTSTA;

	pci_read_config_dword(dev, rtsta_pos, &rtsta);
	rtsta |= PCI_EXP_RTSTA_PME;
	pci_write_config_dword(dev, rtsta_pos, rtsta);
}

77 78 79 80 81 82 83 84 85 86 87
static int pcie_portdrv_restore_config(struct pci_dev *dev)
{
	int retval;

	retval = pci_enable_device(dev);
	if (retval)
		return retval;
	pci_set_master(dev);
	return 0;
}

88
#ifdef CONFIG_PM
89 90 91 92 93 94 95 96 97 98 99 100 101 102
static int pcie_port_resume_noirq(struct device *dev)
{
	struct pci_dev *pdev = to_pci_dev(dev);

	/*
	 * Some BIOSes forget to clear Root PME Status bits after system wakeup
	 * which breaks ACPI-based runtime wakeup on PCI Express, so clear those
	 * bits now just in case (shouldn't hurt).
	 */
	if(pdev->pcie_type == PCI_EXP_TYPE_ROOT_PORT)
		pcie_clear_root_pme_status(pdev);
	return 0;
}

103
#ifdef CONFIG_PM_RUNTIME
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
struct d3cold_info {
	bool no_d3cold;
	unsigned int d3cold_delay;
};

static int pci_dev_d3cold_info(struct pci_dev *pdev, void *data)
{
	struct d3cold_info *info = data;

	info->d3cold_delay = max_t(unsigned int, pdev->d3cold_delay,
				   info->d3cold_delay);
	if (pdev->no_d3cold)
		info->no_d3cold = true;
	return 0;
}

static int pcie_port_runtime_suspend(struct device *dev)
{
	struct pci_dev *pdev = to_pci_dev(dev);
	struct d3cold_info d3cold_info = {
		.no_d3cold	= false,
		.d3cold_delay	= PCI_PM_D3_WAIT,
	};

	/*
	 * If any subordinate device disable D3cold, we should not put
	 * the port into D3cold.  The D3cold delay of port should be
	 * the max of that of all subordinate devices.
	 */
	pci_walk_bus(pdev->subordinate, pci_dev_d3cold_info, &d3cold_info);
	pdev->no_d3cold = d3cold_info.no_d3cold;
	pdev->d3cold_delay = d3cold_info.d3cold_delay;
	return 0;
}

static int pcie_port_runtime_resume(struct device *dev)
140 141 142 143
{
	return 0;
}
#else
144 145
#define pcie_port_runtime_suspend	NULL
#define pcie_port_runtime_resume	NULL
146 147
#endif

148
static const struct dev_pm_ops pcie_portdrv_pm_ops = {
149 150 151 152 153 154
	.suspend	= pcie_port_device_suspend,
	.resume		= pcie_port_device_resume,
	.freeze		= pcie_port_device_suspend,
	.thaw		= pcie_port_device_resume,
	.poweroff	= pcie_port_device_suspend,
	.restore	= pcie_port_device_resume,
155
	.resume_noirq	= pcie_port_resume_noirq,
156 157
	.runtime_suspend = pcie_port_runtime_suspend,
	.runtime_resume = pcie_port_runtime_resume,
158
};
159

160
#define PCIE_PORTDRV_PM_OPS	(&pcie_portdrv_pm_ops)
161

162 163 164 165
#else /* !PM */

#define PCIE_PORTDRV_PM_OPS	NULL
#endif /* !PM */
166

167 168 169 170 171 172 173 174
/*
 * PCIe port runtime suspend is broken for some chipsets, so use a
 * black list to disable runtime PM for these chipsets.
 */
static const struct pci_device_id port_runtime_pm_black_list[] = {
	{ /* end: all zeroes */ }
};

L
Linus Torvalds 已提交
175 176 177 178
/*
 * pcie_portdrv_probe - Probe PCI-Express port devices
 * @dev: PCI-Express port device being probed
 *
179
 * If detected invokes the pcie_port_device_register() method for
L
Linus Torvalds 已提交
180 181 182
 * this port device.
 *
 */
183 184
static int __devinit pcie_portdrv_probe(struct pci_dev *dev,
					const struct pci_device_id *id)
L
Linus Torvalds 已提交
185
{
186
	int status;
L
Linus Torvalds 已提交
187

188 189 190 191 192
	if (!pci_is_pcie(dev) ||
	    ((dev->pcie_type != PCI_EXP_TYPE_ROOT_PORT) &&
	     (dev->pcie_type != PCI_EXP_TYPE_UPSTREAM) &&
	     (dev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM)))
		return -ENODEV;
L
Linus Torvalds 已提交
193

194
	if (!dev->irq && dev->pin) {
195
		dev_warn(&dev->dev, "device [%04x:%04x] has invalid IRQ; "
196
			 "check vendor BIOS\n", dev->vendor, dev->device);
L
Linus Torvalds 已提交
197
	}
198 199 200
	status = pcie_port_device_register(dev);
	if (status)
		return status;
L
Linus Torvalds 已提交
201

202
	pci_save_state(dev);
203 204
	if (!pci_match_id(port_runtime_pm_black_list, dev))
		pm_runtime_put_noidle(&dev->dev);
205

L
Linus Torvalds 已提交
206 207 208
	return 0;
}

209
static void pcie_portdrv_remove(struct pci_dev *dev)
L
Linus Torvalds 已提交
210
{
211 212
	if (!pci_match_id(port_runtime_pm_black_list, dev))
		pm_runtime_get_noresume(&dev->dev);
L
Linus Torvalds 已提交
213
	pcie_port_device_remove(dev);
214
	pci_disable_device(dev);
L
Linus Torvalds 已提交
215 216
}

217
static int error_detected_iter(struct device *device, void *data)
218
{
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
	struct pcie_device *pcie_device;
	struct pcie_port_service_driver *driver;
	struct aer_broadcast_data *result_data;
	pci_ers_result_t status;

	result_data = (struct aer_broadcast_data *) data;

	if (device->bus == &pcie_port_bus_type && device->driver) {
		driver = to_service_driver(device->driver);
		if (!driver ||
			!driver->err_handler ||
			!driver->err_handler->error_detected)
			return 0;

		pcie_device = to_pcie_device(device);

		/* Forward error detected message to service drivers */
		status = driver->err_handler->error_detected(
			pcie_device->port,
			result_data->state);
		result_data->result =
			merge_result(result_data->result, status);
	}

	return 0;
244 245
}

246 247
static pci_ers_result_t pcie_portdrv_error_detected(struct pci_dev *dev,
					enum pci_channel_state error)
248
{
249 250
	struct aer_broadcast_data data = {error, PCI_ERS_RESULT_CAN_RECOVER};
	int ret;
251

252
	/* can not fail */
253
	ret = device_for_each_child(&dev->dev, &data, error_detected_iter);
254

255
	return data.result;
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
}

static int mmio_enabled_iter(struct device *device, void *data)
{
	struct pcie_device *pcie_device;
	struct pcie_port_service_driver *driver;
	pci_ers_result_t status, *result;

	result = (pci_ers_result_t *) data;

	if (device->bus == &pcie_port_bus_type && device->driver) {
		driver = to_service_driver(device->driver);
		if (driver &&
			driver->err_handler &&
			driver->err_handler->mmio_enabled) {
			pcie_device = to_pcie_device(device);

			/* Forward error message to service drivers */
			status = driver->err_handler->mmio_enabled(
					pcie_device->port);
			*result = merge_result(*result, status);
		}
	}
279 280 281 282

	return 0;
}

283
static pci_ers_result_t pcie_portdrv_mmio_enabled(struct pci_dev *dev)
L
Linus Torvalds 已提交
284
{
285
	pci_ers_result_t status = PCI_ERS_RESULT_RECOVERED;
286
	int retval;
287

288 289
	/* get true return value from &status */
	retval = device_for_each_child(&dev->dev, &status, mmio_enabled_iter);
290
	return status;
L
Linus Torvalds 已提交
291 292
}

293
static int slot_reset_iter(struct device *device, void *data)
L
Linus Torvalds 已提交
294
{
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
	struct pcie_device *pcie_device;
	struct pcie_port_service_driver *driver;
	pci_ers_result_t status, *result;

	result = (pci_ers_result_t *) data;

	if (device->bus == &pcie_port_bus_type && device->driver) {
		driver = to_service_driver(device->driver);
		if (driver &&
			driver->err_handler &&
			driver->err_handler->slot_reset) {
			pcie_device = to_pcie_device(device);

			/* Forward error message to service drivers */
			status = driver->err_handler->slot_reset(
					pcie_device->port);
			*result = merge_result(*result, status);
		}
	}

	return 0;
}

static pci_ers_result_t pcie_portdrv_slot_reset(struct pci_dev *dev)
{
320
	pci_ers_result_t status = PCI_ERS_RESULT_RECOVERED;
321
	int retval;
322 323 324

	/* If fatal, restore cfg space for possible link reset at upstream */
	if (dev->error_state == pci_channel_io_frozen) {
325
		dev->state_saved = true;
326
		pci_restore_state(dev);
327 328 329 330
		pcie_portdrv_restore_config(dev);
		pci_enable_pcie_error_reporting(dev);
	}

331 332
	/* get true return value from &status */
	retval = device_for_each_child(&dev->dev, &status, slot_reset_iter);
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358

	return status;
}

static int resume_iter(struct device *device, void *data)
{
	struct pcie_device *pcie_device;
	struct pcie_port_service_driver *driver;

	if (device->bus == &pcie_port_bus_type && device->driver) {
		driver = to_service_driver(device->driver);
		if (driver &&
			driver->err_handler &&
			driver->err_handler->resume) {
			pcie_device = to_pcie_device(device);

			/* Forward error message to service drivers */
			driver->err_handler->resume(pcie_device->port);
		}
	}

	return 0;
}

static void pcie_portdrv_err_resume(struct pci_dev *dev)
{
359 360 361
	int retval;
	/* nothing to do with error value, if it ever happens */
	retval = device_for_each_child(&dev->dev, NULL, resume_iter);
L
Linus Torvalds 已提交
362 363 364 365 366 367 368 369 370 371 372 373
}

/*
 * 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);

374 375 376 377 378 379 380
static struct pci_error_handlers pcie_portdrv_err_handler = {
		.error_detected = pcie_portdrv_error_detected,
		.mmio_enabled = pcie_portdrv_mmio_enabled,
		.slot_reset = pcie_portdrv_slot_reset,
		.resume = pcie_portdrv_err_resume,
};

381
static struct pci_driver pcie_portdriver = {
382
	.name		= "pcieport",
L
Linus Torvalds 已提交
383 384 385 386 387
	.id_table	= &port_pci_ids[0],

	.probe		= pcie_portdrv_probe,
	.remove		= pcie_portdrv_remove,

388
	.err_handler 	= &pcie_portdrv_err_handler,
389 390

	.driver.pm 	= PCIE_PORTDRV_PM_OPS,
L
Linus Torvalds 已提交
391 392
};

393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
static int __init dmi_pcie_pme_disable_msi(const struct dmi_system_id *d)
{
	pr_notice("%s detected: will not use MSI for PCIe PME signaling\n",
			d->ident);
	pcie_pme_disable_msi();
	return 0;
}

static struct dmi_system_id __initdata pcie_portdrv_dmi_table[] = {
	/*
	 * Boxes that should not use MSI for PCIe PME signaling.
	 */
	{
	 .callback = dmi_pcie_pme_disable_msi,
	 .ident = "MSI Wind U-100",
	 .matches = {
		     DMI_MATCH(DMI_SYS_VENDOR,
		     		"MICRO-STAR INTERNATIONAL CO., LTD"),
		     DMI_MATCH(DMI_PRODUCT_NAME, "U-100"),
		     },
	 },
	 {}
};

L
Linus Torvalds 已提交
417 418
static int __init pcie_portdrv_init(void)
{
419
	int retval;
L
Linus Torvalds 已提交
420

421 422
	if (pcie_ports_disabled)
		return pci_register_driver(&pcie_portdriver);
423

424 425
	dmi_check_system(pcie_portdrv_dmi_table);

426 427 428 429 430
	retval = pcie_port_bus_register();
	if (retval) {
		printk(KERN_WARNING "PCIE: bus_register error: %d\n", retval);
		goto out;
	}
431
	retval = pci_register_driver(&pcie_portdriver);
L
Linus Torvalds 已提交
432 433
	if (retval)
		pcie_port_bus_unregister();
434
 out:
L
Linus Torvalds 已提交
435 436 437 438
	return retval;
}

module_init(pcie_portdrv_init);