Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

thunderbolt: Add suspend/hibernate support

We use _noirq since we have to restore the pci tunnels before the pci
core wakes the tunneled devices.

Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Andreas Noever and committed by
Greg Kroah-Hartman
23dd5bb4 c90553b3

+183
+33
drivers/thunderbolt/nhi.c
··· 7 7 * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> 8 8 */ 9 9 10 + #include <linux/pm_runtime.h> 10 11 #include <linux/slab.h> 11 12 #include <linux/errno.h> 12 13 #include <linux/pci.h> ··· 493 492 return IRQ_HANDLED; 494 493 } 495 494 495 + static int nhi_suspend_noirq(struct device *dev) 496 + { 497 + struct pci_dev *pdev = to_pci_dev(dev); 498 + struct tb *tb = pci_get_drvdata(pdev); 499 + thunderbolt_suspend(tb); 500 + return 0; 501 + } 502 + 503 + static int nhi_resume_noirq(struct device *dev) 504 + { 505 + struct pci_dev *pdev = to_pci_dev(dev); 506 + struct tb *tb = pci_get_drvdata(pdev); 507 + thunderbolt_resume(tb); 508 + return 0; 509 + } 510 + 496 511 static void nhi_shutdown(struct tb_nhi *nhi) 497 512 { 498 513 int i; ··· 617 600 nhi_shutdown(nhi); 618 601 } 619 602 603 + /* 604 + * The tunneled pci bridges are siblings of us. Use resume_noirq to reenable 605 + * the tunnels asap. A corresponding pci quirk blocks the downstream bridges 606 + * resume_noirq until we are done. 607 + */ 608 + static const struct dev_pm_ops nhi_pm_ops = { 609 + .suspend_noirq = nhi_suspend_noirq, 610 + .resume_noirq = nhi_resume_noirq, 611 + .freeze_noirq = nhi_suspend_noirq, /* 612 + * we just disable hotplug, the 613 + * pci-tunnels stay alive. 614 + */ 615 + .restore_noirq = nhi_resume_noirq, 616 + }; 617 + 620 618 struct pci_device_id nhi_ids[] = { 621 619 /* 622 620 * We have to specify class, the TB bridges use the same device and ··· 658 626 .id_table = nhi_ids, 659 627 .probe = nhi_probe, 660 628 .remove = nhi_remove, 629 + .driver.pm = &nhi_pm_ops, 661 630 }; 662 631 663 632 static int __init nhi_init(void)
+84
drivers/thunderbolt/switch.c
··· 229 229 sw->__unknown1, sw->__unknown4); 230 230 } 231 231 232 + /** 233 + * reset_switch() - reconfigure route, enable and send TB_CFG_PKG_RESET 234 + * 235 + * Return: Returns 0 on success or an error code on failure. 236 + */ 237 + int tb_switch_reset(struct tb *tb, u64 route) 238 + { 239 + struct tb_cfg_result res; 240 + struct tb_regs_switch_header header = { 241 + header.route_hi = route >> 32, 242 + header.route_lo = route, 243 + header.enabled = true, 244 + }; 245 + tb_info(tb, "resetting switch at %llx\n", route); 246 + res.err = tb_cfg_write(tb->ctl, ((u32 *) &header) + 2, route, 247 + 0, 2, 2, 2); 248 + if (res.err) 249 + return res.err; 250 + res = tb_cfg_reset(tb->ctl, route, TB_CFG_DEFAULT_TIMEOUT); 251 + if (res.err > 0) 252 + return -EIO; 253 + return res.err; 254 + } 255 + 232 256 struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route) 233 257 { 234 258 u8 next_port = route; /* ··· 436 412 } 437 413 } 438 414 415 + int tb_switch_resume(struct tb_switch *sw) 416 + { 417 + int i, err; 418 + u64 uid; 419 + tb_sw_info(sw, "resuming switch\n"); 420 + 421 + err = tb_eeprom_read_uid(sw, &uid); 422 + if (err) { 423 + tb_sw_warn(sw, "uid read failed\n"); 424 + return err; 425 + } 426 + if (sw->uid != uid) { 427 + tb_sw_info(sw, 428 + "changed while suspended (uid %#llx -> %#llx)\n", 429 + sw->uid, uid); 430 + return -ENODEV; 431 + } 432 + 433 + /* upload configuration */ 434 + err = tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3); 435 + if (err) 436 + return err; 437 + 438 + err = tb_plug_events_active(sw, true); 439 + if (err) 440 + return err; 441 + 442 + /* check for surviving downstream switches */ 443 + for (i = 1; i <= sw->config.max_port_number; i++) { 444 + struct tb_port *port = &sw->ports[i]; 445 + if (tb_is_upstream_port(port)) 446 + continue; 447 + if (!port->remote) 448 + continue; 449 + if (tb_wait_for_port(port, true) <= 0 450 + || tb_switch_resume(port->remote->sw)) { 451 + tb_port_warn(port, 452 + "lost during suspend, disconnecting\n"); 453 + tb_sw_set_unpplugged(port->remote->sw); 454 + } 455 + } 456 + return 0; 457 + } 458 + 459 + void tb_switch_suspend(struct tb_switch *sw) 460 + { 461 + int i, err; 462 + err = tb_plug_events_active(sw, false); 463 + if (err) 464 + return; 465 + 466 + for (i = 1; i <= sw->config.max_port_number; i++) { 467 + if (!tb_is_upstream_port(&sw->ports[i]) && sw->ports[i].remote) 468 + tb_switch_suspend(sw->ports[i].remote->sw); 469 + } 470 + /* 471 + * TODO: invoke tb_cfg_prepare_to_sleep here? does not seem to have any 472 + * effect? 473 + */ 474 + }
+61
drivers/thunderbolt/tb.c
··· 69 69 } 70 70 71 71 /** 72 + * tb_free_unplugged_children() - traverse hierarchy and free unplugged switches 73 + */ 74 + static void tb_free_unplugged_children(struct tb_switch *sw) 75 + { 76 + int i; 77 + for (i = 1; i <= sw->config.max_port_number; i++) { 78 + struct tb_port *port = &sw->ports[i]; 79 + if (tb_is_upstream_port(port)) 80 + continue; 81 + if (!port->remote) 82 + continue; 83 + if (port->remote->sw->is_unplugged) { 84 + tb_switch_free(port->remote->sw); 85 + port->remote = NULL; 86 + } else { 87 + tb_free_unplugged_children(port->remote->sw); 88 + } 89 + } 90 + } 91 + 92 + 93 + /** 72 94 * find_pci_up_port() - return the first PCIe up port on @sw or NULL 73 95 */ 74 96 static struct tb_port *tb_find_pci_up_port(struct tb_switch *sw) ··· 390 368 return NULL; 391 369 } 392 370 371 + void thunderbolt_suspend(struct tb *tb) 372 + { 373 + tb_info(tb, "suspending...\n"); 374 + mutex_lock(&tb->lock); 375 + tb_switch_suspend(tb->root_switch); 376 + tb_ctl_stop(tb->ctl); 377 + tb->hotplug_active = false; /* signal tb_handle_hotplug to quit */ 378 + mutex_unlock(&tb->lock); 379 + tb_info(tb, "suspend finished\n"); 380 + } 381 + 382 + void thunderbolt_resume(struct tb *tb) 383 + { 384 + struct tb_pci_tunnel *tunnel, *n; 385 + tb_info(tb, "resuming...\n"); 386 + mutex_lock(&tb->lock); 387 + tb_ctl_start(tb->ctl); 388 + 389 + /* remove any pci devices the firmware might have setup */ 390 + tb_switch_reset(tb, 0); 391 + 392 + tb_switch_resume(tb->root_switch); 393 + tb_free_invalid_tunnels(tb); 394 + tb_free_unplugged_children(tb->root_switch); 395 + list_for_each_entry_safe(tunnel, n, &tb->tunnel_list, list) 396 + tb_pci_restart(tunnel); 397 + if (!list_empty(&tb->tunnel_list)) { 398 + /* 399 + * the pcie links need some time to get going. 400 + * 100ms works for me... 401 + */ 402 + tb_info(tb, "tunnels restarted, sleeping for 100ms\n"); 403 + msleep(100); 404 + } 405 + /* Allow tb_handle_hotplug to progress events */ 406 + tb->hotplug_active = true; 407 + mutex_unlock(&tb->lock); 408 + tb_info(tb, "resume finished\n"); 409 + }
+5
drivers/thunderbolt/tb.h
··· 214 214 215 215 struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi); 216 216 void thunderbolt_shutdown_and_free(struct tb *tb); 217 + void thunderbolt_suspend(struct tb *tb); 218 + void thunderbolt_resume(struct tb *tb); 217 219 218 220 struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); 219 221 void tb_switch_free(struct tb_switch *sw); 222 + void tb_switch_suspend(struct tb_switch *sw); 223 + int tb_switch_resume(struct tb_switch *sw); 224 + int tb_switch_reset(struct tb *tb, u64 route); 220 225 void tb_sw_set_unpplugged(struct tb_switch *sw); 221 226 struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); 222 227