Thunderbolt driver for Apple MacBooks
Subsystems (3)
Andreas Noever
maintainerv3 cover Thunderbolt driver for Apple MacBooks
Hi
This is version 3 of my Thunderbolt driver for Apple hardware (see [1] for v2).
Changes since v2:
- Use pci quirks instead of changing the pcie port driver.
- Relax the DMI checks to run on all MacBooks with a Cactus Ridge chip (MBP Mid 2012 - Early 2013, MBA Mid 2012 - Early 2014). I have tested the driver
on my early 2013 MBP and have gotten reports of it working on a MBA mid 2013).
- Fix some typos.
Limitations & Notes:
- No chaining
- No Apple TB display (non TB displays work)
- Requires Matthew's acpi fixes [2]
- Hibernate only works if you resume with the same (or similar) devices as during the initial boot. The memory layout reported by efi depends on
whether TB devices are connected during boot and linux refuses to resumeif the total amount of memory differs. Suspend works fine.
- No device handoff. The driver will not properly resume a device that was initially coldplugged. Unplug/replug works fine though.
To ease testing a git repository based on 3.15-rc7 can be found at:
https://github.com/anoever/thunderbolt
The repository includes Matthew's patches.
Would it be possible to get this into 3.16?
Thanks, Andreas
[1]: https://lkml.org/lkml/2014/4/10/681
[2]: https://lkml.org/lkml/2014/3/11/619
Andreas Noever (15):
thunderbolt: Add initial cactus ridge NHI support
thunderbolt: Add control channel interface
thunderbolt: Setup control channel
thunderbolt: Add tb_regs.h
thunderbolt: Initialize root switch and ports
thunderbolt: Add thunderbolt capability handling
thunderbolt: Enable plug events
thunderbolt: Scan for downstream switches
thunderbolt: Handle hotplug events
thunderbolt: Add path setup code.
thunderbolt: Add support for simple pci tunnels.
pci: Add pci_fixup_suspend_late quirk pass.
pci: Suspend/resume quirks for appel thunderbolt
thunderbolt: Read switch uid from EEPROM
thunderbolt: Add suspend/hibernate support
drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/pci/pci-driver.c | 18 +- drivers/pci/quirks.c | 129 +++++++ drivers/thunderbolt/Kconfig | 12 + drivers/thunderbolt/Makefile | 3 + drivers/thunderbolt/cap.c | 116 ++++++ drivers/thunderbolt/ctl.c | 731 ++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/ctl.h | 75 ++++ drivers/thunderbolt/eeprom.c | 189 ++++++++++ drivers/thunderbolt/nhi.c | 672 +++++++++++++++++++++++++++++++++++ drivers/thunderbolt/nhi.h | 114 ++++++ drivers/thunderbolt/nhi_regs.h | 101 ++++++ drivers/thunderbolt/path.c | 215 +++++++++++ drivers/thunderbolt/switch.c | 492 +++++++++++++++++++++++++ drivers/thunderbolt/tb.c | 431 ++++++++++++++++++++++ drivers/thunderbolt/tb.h | 266 ++++++++++++++ drivers/thunderbolt/tb_regs.h | 213 +++++++++++ drivers/thunderbolt/tunnel_pci.c | 232 ++++++++++++ drivers/thunderbolt/tunnel_pci.h | 30 ++ include/asm-generic/vmlinux.lds.h | 3 + include/linux/pci.h | 12 +- 22 files changed, 4052 insertions(+), 5 deletions(-) create mode 100644 drivers/thunderbolt/Kconfig create mode 100644 drivers/thunderbolt/Makefile create mode 100644 drivers/thunderbolt/cap.c create mode 100644 drivers/thunderbolt/ctl.c create mode 100644 drivers/thunderbolt/ctl.h create mode 100644 drivers/thunderbolt/eeprom.c create mode 100644 drivers/thunderbolt/nhi.c create mode 100644 drivers/thunderbolt/nhi.h create mode 100644 drivers/thunderbolt/nhi_regs.h create mode 100644 drivers/thunderbolt/path.c create mode 100644 drivers/thunderbolt/switch.c create mode 100644 drivers/thunderbolt/tb.c create mode 100644 drivers/thunderbolt/tb.h create mode 100644 drivers/thunderbolt/tb_regs.h create mode 100644 drivers/thunderbolt/tunnel_pci.c create mode 100644 drivers/thunderbolt/tunnel_pci.h
Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 1/15 thunderbolt: Add initial cactus ridge NHI support
Thunderbolt hotplug is supposed to be handled by the firmware. But Apple decided to implement thunderbolt at the operating system level. The firmare only initializes thunderbolt devices that are present at boot time. This driver enables hotplug of thunderbolt of non-chained thunderbolt devices on Apple systems with a cactus ridge controller.
This first patch adds the Kconfig file as well the parts of the driver which talk directly to the hardware (that is pci device setup, interrupt handling and RX/TX ring management).
--- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/thunderbolt/Kconfig | 12 + drivers/thunderbolt/Makefile | 3 + drivers/thunderbolt/nhi.c | 625 +++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/nhi.h | 114 ++++++++ drivers/thunderbolt/nhi_regs.h | 101 +++++++ 7 files changed, 858 insertions(+) create mode 100644 drivers/thunderbolt/Kconfig create mode 100644 drivers/thunderbolt/Makefile create mode 100644 drivers/thunderbolt/nhi.c create mode 100644 drivers/thunderbolt/nhi.h create mode 100644 drivers/thunderbolt/nhi_regs.h
drivers/Kconfig +2 -0
@@ -174,4 +174,6 @@ source "drivers/powercap/Kconfig" source "drivers/mcb/Kconfig" +source "drivers/thunderbolt/Kconfig" + endmenu
drivers/Makefile +1 -0
@@ -157,3 +157,4 @@ obj-$(CONFIG_NTB) += ntb/ obj-$(CONFIG_FMC) += fmc/ obj-$(CONFIG_POWERCAP) += powercap/ obj-$(CONFIG_MCB) += mcb/ +obj-$(CONFIG_THUNDERBOLT) += thunderbolt/
drivers/thunderbolt/Kconfig new file +12 -0
@@ -0,0 +1,12 @@ +menuconfig THUNDERBOLT + tristate "Thunderbolt support for Apple devices" + default no + help + Cactus Ridge Thunderbolt Controller driver + This driver is required if you want to hotplug Thunderbolt devices on + Apple hardware. + + Device chaining is currently not supported. + + To compile this driver a module, choose M here. The module will be + called thunderbolt.
drivers/thunderbolt/Makefile new file +3 -0
@@ -0,0 +1,3 @@ +obj-${CONFIG_THUNDERBOLT} := thunderbolt.o +thunderbolt-objs := nhi.o +
drivers/thunderbolt/nhi.c new file +625 -0
625 lines not on this page · raw message
drivers/thunderbolt/nhi.h new file +114 -0
114 lines not on this page · raw message
drivers/thunderbolt/nhi_regs.h new file +101 -0
101 lines not on this page · raw message
Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 2/15 thunderbolt: Add control channel interface
In reply to Andreas NoeverThunderbolt devices are configured by reading/writing into their configuration space (similar to pci). This is done by sending packets through the NHI (native host interface) onto the control channel.
This patch handles the low level packet based protocol and exposes higher level operations like tb_cfg_read/tb_cfg_write.
--- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/ctl.c | 731 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/ctl.h | 75 +++++ 3 files changed, 807 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/ctl.c create mode 100644 drivers/thunderbolt/ctl.h
drivers/thunderbolt/Makefile +1 -1
@@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o +thunderbolt-objs := nhi.o ctl.o
drivers/thunderbolt/ctl.c new file +731 -0
731 lines not on this page · raw message
drivers/thunderbolt/ctl.h new file +75 -0
75 lines not on this page · raw message
Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 3/15 thunderbolt: Setup control channel
In reply to Andreas NoeverAdd struct tb which will contain our view of the thunderbolt bus. For now it just contains a pointer to the control channel and a workqueue for hotplug events.
Add thunderbolt_alloc_and_start() and thunderbolt_shutdown_and_free() which are responsible for setup and teardown of struct tb.
--- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/nhi.c | 18 +++++- drivers/thunderbolt/tb.c | 134 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 35 +++++++++++ 4 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 drivers/thunderbolt/tb.c create mode 100644 drivers/thunderbolt/tb.h
drivers/thunderbolt/Makefile +1 -1
@@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o +thunderbolt-objs := nhi.o ctl.o tb.o
drivers/thunderbolt/nhi.c +16 -2
@@ -16,6 +16,7 @@#include "nhi.h" #include "nhi_regs.h" +#include "tb.h" #define RING_TYPE (ring) ((ring)->is_tx ? "TX ring" : "RX ring")
@@ -517,6 +518,7 @@ static void nhi_shutdown(struct tb_nhi *nhi)static int nhi_probe (struct pci_dev *pdev,const struct pci_device_id *id) {struct tb_nhi *nhi; +struct tb *tb;int res; res =pcim_enable_device (pdev);
@@ -575,14 +577,26 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)/* magic value - clock related? */ iowrite32 (3906250 /10000 , nhi->iobase +0x38c00 ); -pci_set_drvdata (pdev, nhi); +dev_info (&nhi->pdev->dev,"NHI initialized, starting thunderbolt\n" ); + tb =thunderbolt_alloc_and_start (nhi); +if (!tb) { +/* +* At this point the RX/TX rings might already have been +* activated. Do a proper shutdown. +*/ +nhi_shutdown (nhi); +return -EIO ; + } +pci_set_drvdata (pdev, tb);return 0 ; }static void nhi_remove (struct pci_dev *pdev) { -struct tb_nhi *nhi =pci_get_drvdata (pdev); +struct tb *tb =pci_get_drvdata (pdev); +struct tb_nhi *nhi = tb->nhi; +thunderbolt_shutdown_and_free (tb);nhi_shutdown (nhi); }
drivers/thunderbolt/tb.c new file +134 -0
134 lines not on this page · raw message
drivers/thunderbolt/tb.h new file +35 -0
@@ -0,0 +1,35 @@ +/* +* Thunderbolt Cactus Ridge driver - bus logic (NHI independent) +* +* Copyright (c) 2014 Andreas Noever <[email protected]> +*/ + +#ifndef TB_H_ +#define TB_H_ + +#include "ctl.h" + +/** +* struct tb - main thunderbolt bus structure +*/ +struct tb { +struct mutex lock;/* +* Big lock. Must be held when accessing cfg or +* any struct tb_switch / struct tb_port. +*/ +struct tb_nhi *nhi; +struct tb_ctl *ctl; +struct workqueue_struct *wq;/* ordered workqueue for plug events */ +bool hotplug_active;/* +* tb_handle_hotplug will stop progressing plug +* events and exit if this is not set (it needs to +* acquire the lock one more time). Used to drain +* wq after cfg has been paused. +*/ + +}; + +struct tb *thunderbolt_alloc_and_start (struct tb_nhi *nhi); +void thunderbolt_shutdown_and_free (struct tb *tb); + +#endif
Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 4/15 thunderbolt: Add tb_regs.h
In reply to Andreas NoeverEvery thunderbolt device consists (logically) of a switch with multiple ports. Every port contains up to four config regions (HOPS, PORT, SWITCH, COUNTERS) which are used to configure the device.
The tb_regs.h file contains all known registers and capabilities from these config regions.
--- drivers/thunderbolt/tb.c | 5 + drivers/thunderbolt/tb_regs.h | 213 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 drivers/thunderbolt/tb_regs.h
drivers/thunderbolt/tb.c +5 -0
@@ -9,6 +9,7 @@#include <linux/delay.h> #include "tb.h" +#include "tb_regs.h" /* hotplug handling */
@@ -100,6 +101,10 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) {struct tb *tb; +BUILD_BUG_ON (sizeof (struct tb_regs_switch_header ) !=5 *4 ); +BUILD_BUG_ON (sizeof (struct tb_regs_port_header ) !=8 *4 ); +BUILD_BUG_ON (sizeof (struct tb_regs_hop ) !=2 *4 ); + tb =kzalloc (sizeof (*tb),GFP_KERNEL );if (!tb)return NULL ;
drivers/thunderbolt/tb_regs.h new file +213 -0
213 lines not on this page · raw message
Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 5/15 thunderbolt: Initialize root switch and ports
In reply to Andreas NoeverThis patch adds the structures tb_switch and tb_port as well as code to initialize the root switch.
--- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/switch.c | 186 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.c | 8 ++ drivers/thunderbolt/tb.h | 138 ++++++++++++++++++++++++++++++++ 4 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/switch.c
drivers/thunderbolt/Makefile +1 -1
@@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o tb.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o
drivers/thunderbolt/switch.c new file +186 -0
186 lines not on this page · raw message
drivers/thunderbolt/tb.c +8 -0
@@ -71,6 +71,10 @@ void thunderbolt_shutdown_and_free(struct tb *tb) {mutex_lock (&tb->lock); +if (tb->root_switch) +tb_switch_free (tb->root_switch); + tb->root_switch =NULL ; +if (tb->ctl) {tb_ctl_stop (tb->ctl);tb_ctl_free (tb->ctl);
@@ -126,6 +130,10 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi)*/ tb_ctl_start (tb->ctl); + tb->root_switch =tb_switch_alloc (tb,0 ); +if (!tb->root_switch) + goto err_locked; +/* Allow tb_handle_hotplug to progress events */ tb->hotplug_active = true;mutex_unlock (&tb->lock);
drivers/thunderbolt/tb.h +138 -0
160 lines not on this page · raw message
Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 6/15 thunderbolt: Add thunderbolt capability handling
In reply to Andreas NoeverThunderbolt config areas contain capability lists similar to those found on pci devices. This patch introduces a tb_find_cap utility method to search for capabilities.
--- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/cap.c | 116 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 2 + 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/cap.c
drivers/thunderbolt/Makefile +1 -1
@@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o tb.o switch.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o
drivers/thunderbolt/cap.c new file +116 -0
116 lines not on this page · raw message
drivers/thunderbolt/tb.h +2 -0
@@ -159,6 +159,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb);struct tb_switch *tb_switch_alloc (struct tb *tb,u64 route);void tb_switch_free (struct tb_switch *sw); +int tb_find_cap (struct tb_port *port,enum tb_cfg_space space,u32 value); +static inline int tb_route_length (u64 route) {
Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 7/15 thunderbolt: Enable plug events
In reply to Andreas NoeverThunderbolt switches have a plug events capability. This patch adds the tb_plug_events_active method and uses it to activate plug events during switch allocation.
--- drivers/thunderbolt/switch.c | 46 ++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 1 + 2 files changed, 47 insertions(+)
drivers/thunderbolt/switch.c +46 -0
@@ -99,6 +99,39 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) }/** +* tb_plug_events_active() - enable/disable plug events on a switch +* +* Also configures a sane plug_events_delay of 255ms. +* +* Return: Returns 0 on success or an error code on failure. +*/ +static int tb_plug_events_active (struct tb_switch *sw,bool active) +{ +u32 data; +int res; + + sw->config.plug_events_delay =0xff ; + res =tb_sw_write (sw, ((u32 *) &sw->config) +4 ,TB_CFG_SWITCH ,4 ,1 ); +if (res) +return res; + + res =tb_sw_read (sw, &data,TB_CFG_SWITCH , sw->cap_plug_events +1 ,1 ); +if (res) +return res; + +if (active) { + data = data &0xFFFFFF83 ; +if (sw->config.device_id ==0x1547 ) + data |=4 ; + }else { + data = data |0x7c ; + } +return tb_sw_write (sw, &data,TB_CFG_SWITCH , + sw->cap_plug_events +1 ,1 ); +} + + +/** * tb_switch_free() - free a tb_switch and all downstream switches */ void tb_switch_free (struct tb_switch *sw)
@@ -113,6 +146,8 @@ void tb_switch_free(struct tb_switch *sw) sw->ports[i].remote =NULL ; } +tb_plug_events_active (sw, false); +kfree (sw->ports);kfree (sw); }
@@ -125,6 +160,7 @@ void tb_switch_free(struct tb_switch *sw)struct tb_switch *tb_switch_alloc (struct tb *tb,u64 route) {int i; +int cap;struct tb_switch *sw;int upstream_port =tb_cfg_get_upstream_port (tb->ctl, route);if (upstream_port <0 )
@@ -177,6 +213,16 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)/* TODO: I2C, IECS, EEPROM, link controller */ + cap =tb_find_cap (&sw->ports[0 ],TB_CFG_SWITCH ,TB_CAP_PLUG_EVENTS ); +if (cap <0 ) { +tb_sw_warn (sw,"cannot find TB_CAP_PLUG_EVENTS aborting\n" ); + goto err; + } + sw->cap_plug_events = cap; + +if (tb_plug_events_active (sw, true)) + goto err; +return sw; err:kfree (sw->ports);
drivers/thunderbolt/tb.h +1 -0
@@ -19,6 +19,7 @@ struct tb_switch {struct tb_regs_switch_header config;struct tb_port *ports;struct tb *tb; +int cap_plug_events;/* offset, zero if not found */ };/**
Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 8/15 thunderbolt: Scan for downstream switches
In reply to Andreas NoeverAdd utility methods tb_port_state and tb_wait_for_port. Add tb_scan_switch which recursively checks for downstream switches.
--- drivers/thunderbolt/switch.c | 97 ++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.c | 44 ++++++++++++++++++++ drivers/thunderbolt/tb.h | 16 ++++++++ 3 files changed, 157 insertions(+)
drivers/thunderbolt/switch.c +97 -0
@@ -53,6 +53,92 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) }/** +* tb_port_state() - get connectedness state of a port +* +* The port must have a TB_CAP_PHY (i.e. it should be a real port). +* +* Return: Returns an enum tb_port_state on success or an error code on failure. +*/ +static int tb_port_state (struct tb_port *port) +{ +struct tb_cap_phy phy; +int res; +if (port->cap_phy ==0 ) { +tb_port_WARN (port,"does not have a PHY\n" ); +return -EINVAL ; + } + res =tb_port_read (port, &phy,TB_CFG_PORT , port->cap_phy,2 ); +if (res) +return res; +return phy.state; +} + +/** +* tb_wait_for_port() - wait for a port to become ready +* +* Wait up to 1 second for a port to reach state TB_PORT_UP. If +* wait_if_unplugged is set then we also wait if the port is in state +* TB_PORT_UNPLUGGED (it takes a while for the device to be registered after +* switch resume). Otherwise we only wait if a device is registered but the link +* has not yet been established. +* +* Return: Returns an error code on failure. Returns 0 if the port is not +* connected or failed to reach state TB_PORT_UP within one second. Returns 1 +* if the port is connected and in state TB_PORT_UP. +*/ +int tb_wait_for_port (struct tb_port *port,bool wait_if_unplugged) +{ +int retries =10 ; +int state; +if (!port->cap_phy) { +tb_port_WARN (port,"does not have PHY\n" ); +return -EINVAL ; + } +if (tb_is_upstream_port (port)) { +tb_port_WARN (port,"is the upstream port\n" ); +return -EINVAL ; + } + +while (retries--) { + state =tb_port_state (port); +if (state <0 ) +return state; +if (state ==TB_PORT_DISABLED ) { +tb_port_info (port,"is disabled (state: 0)\n" ); +return 0 ; + } +if (state ==TB_PORT_UNPLUGGED ) { +if (wait_if_unplugged) { +/* used during resume */ +tb_port_info (port, +"is unplugged (state: 7), retrying...\n" ); +msleep (100 ); +continue ; + } +tb_port_info (port,"is unplugged (state: 7)\n" ); +return 0 ; + } +if (state ==TB_PORT_UP ) { +tb_port_info (port, +"is connected, link is up (state: 2)\n" ); +return 1 ; + } + +/* +* After plug-in the state is TB_PORT_CONNECTING. Give it some +* time. +*/ +tb_port_info (port, +"is connected, link is not up (state: %d), retrying...\n" , + state); +msleep (100 ); + } +tb_port_warn (port, +"failed to reach state TB_PORT_UP. Ignoring port...\n" ); +return 0 ; +} + +/** * tb_init_port() - initialize a port * * This is a helper method for tb_switch_alloc. Does not check or initialize
@@ -63,6 +149,7 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port)static int tb_init_port (struct tb_switch *sw,u8 port_nr) {int res; +int cap;struct tb_port *port = &sw->ports[port_nr]; port->sw = sw; port->port = port_nr;
@@ -71,6 +158,16 @@ static int tb_init_port(struct tb_switch *sw, u8 port_nr)if (res)return res; +/* Port 0 is the switch itself and has no PHY. */ +if (port->config.type ==TB_TYPE_PORT && port_nr !=0 ) { + cap =tb_find_cap (port,TB_CFG_PORT ,TB_CAP_PHY ); + +if (cap >0 ) + port->cap_phy = cap; +else +tb_port_WARN (port,"non switch port without a PHY\n" ); + } +tb_dump_port (sw->tb, &port->config);/* TODO: Read dual link port, DP port and more from EEPROM. */
drivers/thunderbolt/tb.c +44 -0
56 lines not on this page · raw message
drivers/thunderbolt/tb.h +16 -0
@@ -29,6 +29,7 @@ struct tb_port {struct tb_regs_port_header config;struct tb_switch *sw;struct tb_port *remote;/* remote port, NULL if not connected */ +int cap_phy;/* offset, zero if not found */ u8 port;/* port number on switch */ };
@@ -160,6 +161,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb);struct tb_switch *tb_switch_alloc (struct tb *tb,u64 route);void tb_switch_free (struct tb_switch *sw); +int tb_wait_for_port (struct tb_port *port,bool wait_if_unplugged); +int tb_find_cap (struct tb_port *port,enum tb_cfg_space space,u32 value);
@@ -173,4 +176,17 @@ static inline bool tb_is_upstream_port(struct tb_port *port) return port == tb_upstream_port(port->sw); } +/** +* tb_downstream_route() - get route to downstream switch +* +* Port must not be the upstream port (otherwise a loop is created). +* +* Return: Returns a route to the switch behind @port. +*/ +static inline u64 tb_downstream_route (struct tb_port *port) +{ +return tb_route (port->sw) + | ((u64 ) port->port << (port->sw->config.depth *8 )); +} +#endif
Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 9/15 thunderbolt: Handle hotplug events
In reply to Andreas NoeverWe reveive a plug event callback whenever a thunderbolt device is added or removed. This patch fills in the tb_handle_hotplug method and starts reacting to these events by adding/removing switches from the hierarchy.
--- drivers/thunderbolt/switch.c | 42 +++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/tb.c | 46 +++++++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/tb.h | 3 +++ 3 files changed, 89 insertions(+), 2 deletions(-)
drivers/thunderbolt/switch.c +41 -1
@@ -195,6 +195,24 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) sw->__unknown1, sw->__unknown4); } +struct tb_switch *get_switch_at_route (struct tb_switch *sw,u64 route) +{ +u8 next_port = route;/* +* Routes use a stride of 8 bits, +* eventhough a port index has 6 bits at most. +* */ +if (route ==0 ) +return sw; +if (next_port > sw->config.max_port_number) +return 0 ; +if (tb_is_upstream_port (&sw->ports[next_port])) +return 0 ; +if (!sw->ports[next_port].remote) +return 0 ; +return get_switch_at_route (sw->ports[next_port].remote->sw, + route >>TB_ROUTE_SHIFT ); +} +/** * tb_plug_events_active() - enable/disable plug events on a switch *
@@ -243,7 +261,8 @@ void tb_switch_free(struct tb_switch *sw) sw->ports[i].remote =NULL ; } -tb_plug_events_active (sw, false); +if (!sw->is_unplugged) +tb_plug_events_active (sw, false);kfree (sw->ports);kfree (sw);
@@ -327,3 +346,24 @@ err:return NULL ; } +/** +* tb_sw_set_unpplugged() - set is_unplugged on switch and downstream switches +*/ +void tb_sw_set_unpplugged (struct tb_switch *sw) +{ +int i; +if (sw == sw->tb->root_switch) { +tb_sw_WARN (sw,"cannot unplug root switch\n" ); +return ; + } +if (sw->is_unplugged) { +tb_sw_WARN (sw,"is_unplugged already set\n" ); +return ; + } + sw->is_unplugged = true; +for (i =0 ; i <= sw->config.max_port_number; i++) { +if (!tb_is_upstream_port (&sw->ports[i]) && sw->ports[i].remote) +tb_sw_set_unpplugged (sw->ports[i].remote->sw); + } +} +
drivers/thunderbolt/tb.c +45 -1
56 lines not on this page · raw message
drivers/thunderbolt/tb.h +3 -0
@@ -20,6 +20,7 @@ struct tb_switch {struct tb_port *ports;struct tb *tb;int cap_plug_events;/* offset, zero if not found */ +bool is_unplugged;/* unplugged, will go away */ };/**
@@ -160,6 +161,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb);struct tb_switch *tb_switch_alloc (struct tb *tb,u64 route);void tb_switch_free (struct tb_switch *sw); +void tb_sw_set_unpplugged (struct tb_switch *sw); +struct tb_switch *get_switch_at_route (struct tb_switch *sw,u64 route);int tb_wait_for_port (struct tb_port *port,bool wait_if_unplugged);
Signature · 1 line
1.9.3
Bjorn Helgaas
maintainerRe: [PATCH v3 09/15] thunderbolt: Handle hotplug events
Quoted 1 line · Andreas Noever
We reveive a plug event callback whenever a thunderbolt device is added
s/reveive/receive/
Quoted 159 lines
or removed. This patch fills in the
tb_handle_hotplugmethod and starts reacting to these events by adding/removing switches from the hierarchy.Signed-off-by Andreas Noever <[email protected]>--- drivers/thunderbolt/switch.c | 42 +++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/tb.c | 46 +++++++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/tb.h | 3 +++ 3 files changed, 89 insertions(+), 2 deletions(-)drivers/thunderbolt/switch.c +41
-1@@ -195,6 +195,24 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) sw->__unknown1, sw->__unknown4); } +struct tb_switch *get_switch_at_route (struct tb_switch *sw,u64 route) +{ +u8 next_port = route;/* +* Routes use a stride of 8 bits, +* eventhough a port index has 6 bits at most. +* */ +if (route ==0 ) +return sw; +if (next_port > sw->config.max_port_number) +return 0 ; +if (tb_is_upstream_port (&sw->ports[next_port])) +return 0 ; +if (!sw->ports[next_port].remote) +return 0 ; +return get_switch_at_route (sw->ports[next_port].remote->sw, + route >>TB_ROUTE_SHIFT ); +} +/** * tb_plug_events_active() - enable/disable plug events on a switch * @@ -243,7 +261,8 @@ void tb_switch_free(struct tb_switch *sw) sw->ports[i].remote =NULL ; } -tb_plug_events_active (sw, false); +if (!sw->is_unplugged) +tb_plug_events_active (sw, false);kfree (sw->ports);kfree (sw);@@ -327,3 +346,24 @@ err:return NULL ; } +/** +* tb_sw_set_unpplugged() - set is_unplugged on switch and downstream switches +*/ +void tb_sw_set_unpplugged (struct tb_switch *sw) +{ +int i; +if (sw == sw->tb->root_switch) { +tb_sw_WARN (sw,"cannot unplug root switch\n" ); +return ; + } +if (sw->is_unplugged) { +tb_sw_WARN (sw,"is_unplugged already set\n" ); +return ; + } + sw->is_unplugged = true; +for (i =0 ; i <= sw->config.max_port_number; i++) { +if (!tb_is_upstream_port (&sw->ports[i]) && sw->ports[i].remote) +tb_sw_set_unpplugged (sw->ports[i].remote->sw); + } +} +drivers/thunderbolt/tb.c +45
-1@@ -71,11 +71,55 @@ static void tb_handle_hotplug(struct work_struct *work) {struct tb_hotplug_event *ev =container_of (work,typeof (*ev), work);struct tb *tb = ev->tb; +struct tb_switch *sw; +struct tb_port *port;mutex_lock (&tb->lock);if (!tb->hotplug_active) goto out;/* during init, suspend or shutdown */ -/* do nothing for now */ + sw =get_switch_at_route (tb->root_switch, ev->route); +if (!sw) { +tb_warn (tb, +"hotplug event from non existent switch %llx:%x (unplug: %d)\n" , + ev->route, ev->port, ev->unplug); + goto out; + } +if (ev->port > sw->config.max_port_number) { +tb_warn (tb, +"hotplug event from non existent port %llx:%x (unplug: %d)\n" , + ev->route, ev->port, ev->unplug); + goto out; + } + port = &sw->ports[ev->port]; +if (tb_is_upstream_port (port)) { +tb_warn (tb, +"hotplug event for upstream port %llx:%x (unplug: %d)\n" , + ev->route, ev->port, ev->unplug); + goto out; + } +if (ev->unplug) { +if (port->remote) { +tb_port_info (port,"unplugged\n" ); +tb_sw_set_unpplugged (port->remote->sw); +tb_switch_free (port->remote->sw); + port->remote =NULL ; + }else { +tb_port_info (port, +"got unplug event for disconnected port, ignoring\n" ); + } + }else if (port->remote) { +tb_port_info (port, +"got plug event for connected port, ignoring\n" ); + }else { +tb_port_info (port,"hotplug: scanning\n" ); +tb_scan_port (port); +if (!port->remote) { +tb_port_info (port,"hotplug: no switch found\n" ); + }else if (port->remote->sw->config.depth >1 ) { +tb_sw_warn (port->remote->sw, +"hotplug: chaining not supported\n" ); + } + } out:mutex_unlock (&tb->lock);kfree (ev);drivers/thunderbolt/tb.h +3
-0@@ -20,6 +20,7 @@ struct tb_switch {struct tb_port *ports;struct tb *tb;int cap_plug_events;/* offset, zero if not found */ +bool is_unplugged;/* unplugged, will go away */ };/** @@ -160,6 +161,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb);struct tb_switch *tb_switch_alloc (struct tb *tb,u64 route);void tb_switch_free (struct tb_switch *sw); +void tb_sw_set_unpplugged (struct tb_switch *sw); +struct tb_switch *get_switch_at_route (struct tb_switch *sw,u64 route);int tb_wait_for_port (struct tb_port *port,bool wait_if_unplugged);Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 10/15 thunderbolt: Add path setup code.
In reply to Andreas NoeverA thunderbolt path is a unidirectional channel between two thunderbolt ports. Two such paths are needed to establish a pci tunnel.
This patch introduces struct tb_path as well as a set of tb_path_* methods which are used do activate & deactive paths.
--- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/path.c | 215 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 34 +++++++ drivers/thunderbolt/tb.h | 62 +++++++++++++ 4 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/path.c
drivers/thunderbolt/Makefile +1 -1
@@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o
drivers/thunderbolt/path.c new file +215 -0
215 lines not on this page · raw message
drivers/thunderbolt/switch.c +34 -0
@@ -139,6 +139,40 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) }/** +* tb_port_add_nfc_credits() - add/remove non flow controlled credits to port +* +* Change the number of NFC credits allocated to @port by @credits. To remove +* NFC credits pass a negative amount of credits. +* +* Return: Returns 0 on success or an error code on failure. +*/ +int tb_port_add_nfc_credits (struct tb_port *port,int credits) +{ +if (credits ==0 ) +return 0 ; +tb_port_info (port, +"adding %#x NFC credits (%#x -> %#x)" , + credits, + port->config.nfc_credits, + port->config.nfc_credits + credits); + port->config.nfc_credits += credits; +return tb_port_write (port, &port->config.nfc_credits, +TB_CFG_PORT ,4 ,1 ); +} + +/** +* tb_port_clear_counter() - clear a counter in TB_CFG_COUNTER +* +* Return: Returns 0 on success or an error code on failure. +*/ +int tb_port_clear_counter (struct tb_port *port,int counter) +{ +u32 zero[3 ] = {0 ,0 ,0 }; +tb_port_info (port,"clearing counter %d\n" , counter); +return tb_port_write (port, zero,TB_CFG_COUNTERS ,3 * counter,3 ); +} + +/** * tb_init_port() - initialize a port * * This is a helper method for tb_switch_alloc. Does not check or initialize
drivers/thunderbolt/tb.h +62 -0
77 lines not on this page · raw message
Signature · 1 line
1.9.3
Bjorn Helgaas
maintainerRe: [PATCH v3 10/15] thunderbolt: Add path setup code.
Quoted 5 lines · Andreas Noever
A thunderbolt path is a unidirectional channel between two thunderbolt ports. Two such paths are needed to establish a pci tunnel.
This patch introduces struct
tb_pathas well as a set of tb_path_* methods which are used do activate & deactive paths.
s/do activate & deactive/to activate & deactivate/
Quoted 370 lines
Signed-off-by Andreas Noever <[email protected]>--- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/path.c | 215 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 34 +++++++ drivers/thunderbolt/tb.h | 62 +++++++++++++ 4 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/path.cdrivers/thunderbolt/Makefile +1
-1@@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.odrivers/thunderbolt/path.c new file +215
-0@@ -0,0 +1,215 @@ +/* +* Thunderbolt Cactus Ridge driver - path/tunnel functionality +* +* Copyright (c) 2014 Andreas Noever <[email protected]> +*/ + +#include <linux/slab.h> +#include <linux/errno.h> + +#include "tb.h" + + +static void tb_dump_hop (struct tb_port *port,struct tb_regs_hop *hop) +{ +tb_port_info (port," Hop through port %d to hop %d (%s)\n" , + hop->out_port, hop->next_hop, + hop->enable ?"enabled" :"disabled" ); +tb_port_info (port," Weight: %d Priority: %d Credits: %d Drop: %d\n" , + hop->weight, hop->priority, + hop->initial_credits, hop->drop_packages); +tb_port_info (port," Counter enabled: %d Counter index: %d\n" , + hop->counter_enable, hop->counter); +tb_port_info (port," Flow Control (In/Eg): %d/%d Shared Buffer (In/Eg): %d/%d\n" , + hop->ingress_fc, hop->egress_fc, + hop->ingress_shared_buffer, hop->egress_shared_buffer); +tb_port_info (port," Unknown1: %#x Unknown2: %#x Unknown3: %#x\n" , + hop->unknown1, hop->unknown2, hop->unknown3); +} + +/** +* tb_path_alloc() - allocate a thunderbolt path +* +* Return: Returns a tb_path on success or NULL on failure. +*/ +struct tb_path *tb_path_alloc (struct tb *tb,int num_hops) +{ +struct tb_path *path =kzalloc (sizeof (*path),GFP_KERNEL ); +if (!path) +return NULL ; + path->hops =kcalloc (num_hops,sizeof (*path->hops),GFP_KERNEL ); +if (!path->hops) { +kfree (path); +return NULL ; + } + path->tb = tb; + path->path_length = num_hops; +return path; +} + +/** +* tb_path_free() - free a deactivated path +*/ +void tb_path_free (struct tb_path *path) +{ +if (path->activated) { +tb_WARN (path->tb,"trying to free an activated path\n" ) + return; + } +kfree (path->hops); +kfree (path); +} + +static void __tb_path_deallocate_nfc (struct tb_path *path,int first_hop) +{ +int i, res; +for (i = first_hop; i < path->path_length; i++) { + res =tb_port_add_nfc_credits (path->hops[i].in_port, + -path->nfc_credits); +if (res) +tb_port_warn (path->hops[i].in_port, +"nfc credits deallocation failed for hop %d\n" , + i); + } +} + +static void __tb_path_deactivate_hops (struct tb_path *path,int first_hop) +{ +int i, res; +struct tb_regs_hop hop = { }; +for (i = first_hop; i < path->path_length; i++) { + res =tb_port_write (path->hops[i].in_port, &hop,TB_CFG_HOPS , +2 * path->hops[i].in_hop_index,2 ); +if (res) +tb_port_warn (path->hops[i].in_port, +"hop deactivation failed for hop %d, index %d\n" , + i, path->hops[i].in_hop_index); + } +} + +void tb_path_deactivate (struct tb_path *path) +{ +if (!path->activated) { +tb_WARN (path->tb,"trying to deactivate an inactive path\n" ); +return ; + } +tb_info (path->tb, +"deactivating path from %llx:%x to %llx:%x\n" , +tb_route (path->hops[0 ].in_port->sw), + path->hops[0 ].in_port->port, +tb_route (path->hops[path->path_length -1 ].out_port->sw), + path->hops[path->path_length -1 ].out_port->port); +__tb_path_deactivate_hops (path,0 ); +__tb_path_deallocate_nfc (path,0 ); + path->activated = false; +} + +/** +* tb_path_activate() - activate a path +* +* Activate a path starting with the last hop and iterating backwards. The +* caller must fill path->hops before calling tb_path_activate(). +* +* Return: Returns 0 on success or an error code on failure. +*/ +int tb_path_activate (struct tb_path *path) +{ +int i, res; +enum tb_path_port out_mask, in_mask; +if (path->activated) { +tb_WARN (path->tb,"trying to activate already activated path\n" ); +return -EINVAL ; + } + +tb_info (path->tb, +"activating path from %llx:%x to %llx:%x\n" , +tb_route (path->hops[0 ].in_port->sw), + path->hops[0 ].in_port->port, +tb_route (path->hops[path->path_length -1 ].out_port->sw), + path->hops[path->path_length -1 ].out_port->port); + +/* Clear counters. */ +for (i = path->path_length -1 ; i >=0 ; i--) { +if (path->hops[i].in_counter_index ==-1 ) +continue ; + res =tb_port_clear_counter (path->hops[i].in_port, + path->hops[i].in_counter_index); +if (res) + goto err; + } + +/* Add non flow controlled credits. */ +for (i = path->path_length -1 ; i >=0 ; i--) { + res =tb_port_add_nfc_credits (path->hops[i].in_port, + path->nfc_credits); +if (res) { +__tb_path_deallocate_nfc (path, i); + goto err; + } + } + +/* Activate hops. */ +for (i = path->path_length -1 ; i >=0 ; i--) { +struct tb_regs_hop hop; + +/* dword 0 */ + hop.next_hop = path->hops[i].next_hop_index; + hop.out_port = path->hops[i].out_port->port; +/* TODO: figure out why these are good values */ + hop.initial_credits = (i == path->path_length -1 ) ?16 :7 ; + hop.unknown1 =0 ; + hop.enable =1 ; + +/* dword 1 */ + out_mask = (i == path->path_length -1 ) ? +TB_PATH_DESTINATION :TB_PATH_INTERNAL ; + in_mask = (i ==0 ) ?TB_PATH_SOURCE :TB_PATH_INTERNAL ; + hop.weight = path->weight; + hop.unknown2 =0 ; + hop.priority = path->priority; + hop.drop_packages = path->drop_packages; + hop.counter = path->hops[i].in_counter_index; + hop.counter_enable = path->hops[i].in_counter_index !=-1 ; + hop.ingress_fc = path->ingress_fc_enable & in_mask; + hop.egress_fc = path->egress_fc_enable & out_mask; + hop.ingress_shared_buffer = path->ingress_shared_buffer + & in_mask; + hop.egress_shared_buffer = path->egress_shared_buffer + & out_mask; + hop.unknown3 =0 ; + +tb_port_info (path->hops[i].in_port,"Writing hop %d, index %d" , + i, path->hops[i].in_hop_index); +tb_dump_hop (path->hops[i].in_port, &hop); + res =tb_port_write (path->hops[i].in_port, &hop,TB_CFG_HOPS , +2 * path->hops[i].in_hop_index,2 ); +if (res) { +__tb_path_deactivate_hops (path, i); +__tb_path_deallocate_nfc (path,0 ); + goto err; + } + } + path->activated = true; +tb_info (path->tb,"path activation complete\n" ); +return 0 ; +err: +tb_WARN (path->tb,"path activation failed\n" ); +return res; +} + +/** +* tb_path_is_invalid() - check whether any ports on the path are invalid +* +* Return: Returns true if the path is invalid, false otherwise. +*/ +bool tb_path_is_invalid (struct tb_path *path) +{ +int i =0 ; +for (i =0 ; i < path->path_length; i++) { +if (path->hops[i].in_port->sw->is_unplugged) +return true; +if (path->hops[i].out_port->sw->is_unplugged) +return true; + } +return false; +}drivers/thunderbolt/switch.c +34
-0@@ -139,6 +139,40 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) }/** +* tb_port_add_nfc_credits() - add/remove non flow controlled credits to port +* +* Change the number of NFC credits allocated to @port by @credits. To remove +* NFC credits pass a negative amount of credits. +* +* Return: Returns 0 on success or an error code on failure. +*/ +int tb_port_add_nfc_credits (struct tb_port *port,int credits) +{ +if (credits ==0 ) +return 0 ; +tb_port_info (port, +"adding %#x NFC credits (%#x -> %#x)" , + credits, + port->config.nfc_credits, + port->config.nfc_credits + credits); + port->config.nfc_credits += credits; +return tb_port_write (port, &port->config.nfc_credits, +TB_CFG_PORT ,4 ,1 ); +} + +/** +* tb_port_clear_counter() - clear a counter in TB_CFG_COUNTER +* +* Return: Returns 0 on success or an error code on failure. +*/ +int tb_port_clear_counter (struct tb_port *port,int counter) +{ +u32 zero[3 ] = {0 ,0 ,0 }; +tb_port_info (port,"clearing counter %d\n" , counter); +return tb_port_write (port, zero,TB_CFG_COUNTERS ,3 * counter,3 ); +} + +/** * tb_init_port() - initialize a port * * This is a helper method for tb_switch_alloc. Does not check or initialize drivers/thunderbolt/tb.h +62
-0@@ -35,6 +35,60 @@ struct tb_port { };/** +* struct tb_path_hop - routing information for a tb_path +* +* Hop configuration is always done on the IN port of a switch. +* in_port and out_port have to be on the same switch. Packets arriving on +* in_port with "hop" = in_hop_index will get routed to through out_port. The +* next hop to take (on out_port->remote) is determined by next_hop_index. +* +* in_counter_index is the index of a counter (in TB_CFG_COUNTERS) on the in +* port. +*/ +struct tb_path_hop { +struct tb_port *in_port; +struct tb_port *out_port; +int in_hop_index; +int in_counter_index;/* write -1 to disable counters for this hop. */ +int next_hop_index; +}; + +/** +* enum tb_path_port - path options mask +*/ +enum tb_path_port { +TB_PATH_NONE =0 , +TB_PATH_SOURCE =1 ,/* activate on the first hop (out of src) */ +TB_PATH_INTERNAL =2 ,/* activate on other hops (not the first/last) */ +TB_PATH_DESTINATION =4 ,/* activate on the last hop (into dst) */ +TB_PATH_ALL =7 , +}; + +/** +* struct tb_path - a unidirectional path between two ports +* +* A path consists of a number of hops (see tb_path_hop). To establish a PCIe +* tunnel two paths have to be created between the two PCIe ports. +* +*/ +struct tb_path { +struct tb *tb; +int nfc_credits;/* non flow controlled credits */ +enum tb_path_port ingress_shared_buffer; +enum tb_path_port egress_shared_buffer; +enum tb_path_port ingress_fc_enable; +enum tb_path_port egress_fc_enable; + +int priority:3 ; +int weight:4 ; +bool drop_packages; +bool activated; +struct tb_path_hop *hops; +int path_length;/* number of hops */ +}; + + +/** * struct tb - main thunderbolt bus structure */ struct tb {@@ -165,9 +219,17 @@ void tb_sw_set_unpplugged(struct tb_switch *sw);struct tb_switch *get_switch_at_route (struct tb_switch *sw,u64 route);int tb_wait_for_port (struct tb_port *port,bool wait_if_unplugged); +int tb_port_add_nfc_credits (struct tb_port *port,int credits); +int tb_port_clear_counter (struct tb_port *port,int counter);int tb_find_cap (struct tb_port *port,enum tb_cfg_space space,u32 value); +struct tb_path *tb_path_alloc (struct tb *tb,int num_hops); +void tb_path_free (struct tb_path *path); +int tb_path_activate (struct tb_path *path); +void tb_path_deactivate (struct tb_path *path); +bool tb_path_is_invalid (struct tb_path *path); +static inline int tb_route_length (u64 route) {Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 11/15 thunderbolt: Add support for simple pci tunnels.
In reply to Andreas NoeverA pci downstream and pci upstream port can be connected through a tunnel. To establish the tunnel we have to setup two unidirectional paths between the two ports.
Right now we only support paths with two hops (i.e. no chaining) and at most one pci device per thunderbolt device.
--- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/tb.c | 135 +++++++++++++++++++++++ drivers/thunderbolt/tb.h | 1 + drivers/thunderbolt/tunnel_pci.c | 232 +++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tunnel_pci.h | 30 +++++ 5 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/tunnel_pci.c create mode 100644 drivers/thunderbolt/tunnel_pci.h
drivers/thunderbolt/Makefile +1 -1
@@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o
drivers/thunderbolt/tb.c +135 -0
179 lines not on this page · raw message
drivers/thunderbolt/tb.h +1 -0
@@ -100,6 +100,7 @@ struct tb {struct tb_ctl *ctl;struct workqueue_struct *wq;/* ordered workqueue for plug events */ struct tb_switch *root_switch; +struct list_head tunnel_list;/* list of active PCIe tunnels */ bool hotplug_active;/* * tb_handle_hotplug will stop progressing plug * events and exit if this is not set (it needs to
drivers/thunderbolt/tunnel_pci.c new file +232 -0
232 lines not on this page · raw message
drivers/thunderbolt/tunnel_pci.h new file +30 -0
@@ -0,0 +1,30 @@ +/* +* Thunderbolt Cactus Ridge driver - PCIe tunnel +* +* Copyright (c) 2014 Andreas Noever <[email protected]> +*/ + +#ifndef TB_PCI_H_ +#define TB_PCI_H_ + +#include "tb.h" + +struct tb_pci_tunnel { +struct tb *tb; +struct tb_port *up_port; +struct tb_port *down_port; +struct tb_path *path_to_up; +struct tb_path *path_to_down; +struct list_head list; +}; + +struct tb_pci_tunnel *tb_pci_alloc (struct tb *tb,struct tb_port *up, +struct tb_port *down); +void tb_pci_free (struct tb_pci_tunnel *tunnel); +int tb_pci_activate (struct tb_pci_tunnel *tunnel); +int tb_pci_restart (struct tb_pci_tunnel *tunnel); +void tb_pci_deactivate (struct tb_pci_tunnel *tunnel); +bool tb_pci_is_invalid (struct tb_pci_tunnel *tunnel); + +#endif +
Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 12/15 pci: Add pci_fixup_suspend_late quirk pass.
In reply to Andreas NoeverAdd pci_fixup_suspend_late as a new pci_fixup_pass. The pass is called from suspend_noirq and poweroff_noirq. Using the same pass for suspend and hibernate is consistent with resume_early which is called by resume_noirq and restore_noirq.
The new quirk pass is required for Thunderbolt support on Apple hardware.
--- drivers/pci/pci-driver.c | 18 ++++++++++++++---- drivers/pci/quirks.c | 7 +++++++ include/asm-generic/vmlinux.lds.h | 3 +++ include/linux/pci.h | 12 +++++++++++- 4 files changed, 35 insertions(+), 5 deletions(-)
drivers/pci/pci-driver.c +14 -4
@@ -542,7 +542,7 @@ static int pci_legacy_suspend_late(struct device *dev, pm_message_t state)WARN_ONCE (pci_dev->current_state != prev,"PCI PM: Device state not saved by %pF\n" , drv->suspend_late); -return 0 ; + goto Fixup; } }
@@ -551,6 +551,9 @@ static int pci_legacy_suspend_late(struct device *dev, pm_message_t state)pci_pm_set_unknown_state (pci_dev); +Fixup: +pci_fixup_device (pci_fixup_suspend_late, pci_dev); +return 0 ; }
@@ -694,7 +697,7 @@ static int pci_pm_suspend_noirq(struct device *dev)if (!pm) {pci_save_state (pci_dev); -return 0 ; + goto Fixup; }if (pm->suspend_noirq) {
@@ -711,7 +714,7 @@ static int pci_pm_suspend_noirq(struct device *dev)WARN_ONCE (pci_dev->current_state != prev,"PCI PM: State of device not saved by %pF\n" , pm->suspend_noirq); -return 0 ; + goto Fixup; } }
@@ -735,6 +738,9 @@ static int pci_pm_suspend_noirq(struct device *dev)if (pci_dev->class ==PCI_CLASS_SERIAL_USB_EHCI )pci_write_config_word (pci_dev,PCI_COMMAND ,0 ); +Fixup: +pci_fixup_device (pci_fixup_suspend_late, pci_dev); +return 0 ; }
@@ -959,8 +965,10 @@ static int pci_pm_poweroff_noirq(struct device *dev)if (pci_has_legacy_pm_support (to_pci_dev (dev)))return pci_legacy_suspend_late (dev,PMSG_HIBERNATE ); -if (!drv || !drv->pm) +if (!drv || !drv->pm) { +pci_fixup_device (pci_fixup_suspend_late, pci_dev);return 0 ; + }if (drv->pm->poweroff_noirq) {int error;
@@ -981,6 +989,8 @@ static int pci_pm_poweroff_noirq(struct device *dev)if (pci_dev->class ==PCI_CLASS_SERIAL_USB_EHCI )pci_write_config_word (pci_dev,PCI_COMMAND ,0 ); +pci_fixup_device (pci_fixup_suspend_late, pci_dev); +if (pcibios_pm_ops.poweroff_noirq)return pcibios_pm_ops.poweroff_noirq (dev);
drivers/pci/quirks.c +7 -0
@@ -3024,6 +3024,8 @@ extern struct pci_fixup __start_pci_fixups_resume_early[];extern struct pci_fixup __end_pci_fixups_resume_early[];extern struct pci_fixup __start_pci_fixups_suspend[];extern struct pci_fixup __end_pci_fixups_suspend[]; +extern struct pci_fixup __start_pci_fixups_suspend_late[]; +extern struct pci_fixup __end_pci_fixups_suspend_late[];static bool pci_apply_fixup_final_quirks;
@@ -3069,6 +3071,11 @@ void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev) end = __end_pci_fixups_suspend;break ; +case pci_fixup_suspend_late: + start = __start_pci_fixups_suspend_late; + end = __end_pci_fixups_suspend_late; +break ; +default :/* stupid compiler warning, you would think with an enum... */ return ;
include/asm-generic/vmlinux.lds.h +3 -0
@@ -288,6 +288,9 @@ VMLINUX_SYMBOL(__start_pci_fixups_suspend) = .; \ *(.pci_fixup_suspend) \ VMLINUX_SYMBOL(__end_pci_fixups_suspend) = .; \ + VMLINUX_SYMBOL(__start_pci_fixups_suspend_late) = .; \ + *(.pci_fixup_suspend_late) \ + VMLINUX_SYMBOL(__end_pci_fixups_suspend_late) = .; \ } \ \/* Built-in firmware blobs */ \
include/linux/pci.h +11 -1
@@ -1458,8 +1458,9 @@ enum pci_fixup_pass { pci_fixup_final,/* Final phase of device fixups */ pci_fixup_enable,/* pci_enable_device() time */ pci_fixup_resume,/* pci_device_resume() */ - pci_fixup_suspend,/* pci_device_suspend */ + pci_fixup_suspend,/* pci_device_suspend() */ pci_fixup_resume_early,/* pci_device_resume_early() */ + pci_fixup_suspend_late, /* pci_device_suspend_late() */ };/* Anonymous variables would be nice... */
@@ -1500,6 +1501,11 @@ enum pci_fixup_pass { DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend, \ suspend##hook, vendor, device, class, \ class_shift, hook) +#define DECLARE_PCI_FIXUP_CLASS_SUSPEND_LATE (vendor, device, class, \ + class_shift, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend_late, \ + suspend_late##hook, vendor, device, \ + class, class_shift, hook)#define DECLARE_PCI_FIXUP_EARLY (vendor, device, hook) \ DECLARE_PCI_FIXUP_SECTION(.pci_fixup_early, \
@@ -1525,6 +1531,10 @@ enum pci_fixup_pass { DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend, \ suspend##hook, vendor, device, \ PCI_ANY_ID, 0, hook) +#define DECLARE_PCI_FIXUP_SUSPEND_LATE (vendor, device, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend_late, \ + suspend_late##hook, vendor, device, \ + PCI_ANY_ID, 0, hook)#ifdef CONFIG_PCI_QUIRKS void pci_fixup_device (enum pci_fixup_pass pass,struct pci_dev *dev);
Signature · 1 line
1.9.3
Re: [PATCH v3 12/15] pci: Add pci_fixup_suspend_late quirk pass.
On Mon, May 26, 2014 at 05:18:09PM +0200, Andreas Noever wrote:
Please adjust the subject to match the drivers/pci convention, i.e., change
pci: Add pci_fixup_suspend_late quirk pass.
to
PCI: Add pci_fixup_suspend_late quirk pass
Quoted 9 lines
Add
pci_fixup_suspend_lateas a newpci_fixup_pass. The pass is called fromsuspend_noirqandpoweroff_noirq. Using the same pass for suspend and hibernate is consistent withresume_earlywhich is called byresume_noirqandrestore_noirq.The new quirk pass is required for Thunderbolt support on Apple hardware.
Signed-off-by Andreas Noever <[email protected]>
I assume you'll merge this along with the rest of the series, not through my PCI tree.
Bjorn
Quoted 159 lines
--- drivers/pci/pci-driver.c | 18 ++++++++++++++---- drivers/pci/quirks.c | 7 +++++++ include/asm-generic/vmlinux.lds.h | 3 +++ include/linux/pci.h | 12 +++++++++++- 4 files changed, 35 insertions(+), 5 deletions(-)drivers/pci/pci-driver.c +14
-4@@ -542,7 +542,7 @@ static int pci_legacy_suspend_late(struct device *dev, pm_message_t state)WARN_ONCE (pci_dev->current_state != prev,"PCI PM: Device state not saved by %pF\n" , drv->suspend_late); -return 0 ; + goto Fixup; } }@@ -551,6 +551,9 @@ static int pci_legacy_suspend_late(struct device *dev, pm_message_t state)pci_pm_set_unknown_state (pci_dev); +Fixup: +pci_fixup_device (pci_fixup_suspend_late, pci_dev); +return 0 ; }@@ -694,7 +697,7 @@ static int pci_pm_suspend_noirq(struct device *dev)if (!pm) {pci_save_state (pci_dev); -return 0 ; + goto Fixup; }if (pm->suspend_noirq) {@@ -711,7 +714,7 @@ static int pci_pm_suspend_noirq(struct device *dev)WARN_ONCE (pci_dev->current_state != prev,"PCI PM: State of device not saved by %pF\n" , pm->suspend_noirq); -return 0 ; + goto Fixup; } }@@ -735,6 +738,9 @@ static int pci_pm_suspend_noirq(struct device *dev)if (pci_dev->class ==PCI_CLASS_SERIAL_USB_EHCI )pci_write_config_word (pci_dev,PCI_COMMAND ,0 ); +Fixup: +pci_fixup_device (pci_fixup_suspend_late, pci_dev); +return 0 ; }@@ -959,8 +965,10 @@ static int pci_pm_poweroff_noirq(struct device *dev)if (pci_has_legacy_pm_support (to_pci_dev (dev)))return pci_legacy_suspend_late (dev,PMSG_HIBERNATE ); -if (!drv || !drv->pm) +if (!drv || !drv->pm) { +pci_fixup_device (pci_fixup_suspend_late, pci_dev);return 0 ; + }if (drv->pm->poweroff_noirq) {int error;@@ -981,6 +989,8 @@ static int pci_pm_poweroff_noirq(struct device *dev)if (pci_dev->class ==PCI_CLASS_SERIAL_USB_EHCI )pci_write_config_word (pci_dev,PCI_COMMAND ,0 ); +pci_fixup_device (pci_fixup_suspend_late, pci_dev); +if (pcibios_pm_ops.poweroff_noirq)return pcibios_pm_ops.poweroff_noirq (dev);drivers/pci/quirks.c +7
-0@@ -3024,6 +3024,8 @@ extern struct pci_fixup __start_pci_fixups_resume_early[];extern struct pci_fixup __end_pci_fixups_resume_early[];extern struct pci_fixup __start_pci_fixups_suspend[];extern struct pci_fixup __end_pci_fixups_suspend[]; +extern struct pci_fixup __start_pci_fixups_suspend_late[]; +extern struct pci_fixup __end_pci_fixups_suspend_late[];static bool pci_apply_fixup_final_quirks;@@ -3069,6 +3071,11 @@ void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev) end = __end_pci_fixups_suspend;break ; +case pci_fixup_suspend_late: + start = __start_pci_fixups_suspend_late; + end = __end_pci_fixups_suspend_late; +break ; +default :/* stupid compiler warning, you would think with an enum... */ return ;include/asm-generic/vmlinux.lds.h +3
-0@@ -288,6 +288,9 @@ VMLINUX_SYMBOL(__start_pci_fixups_suspend) = .; \ *(.pci_fixup_suspend) \ VMLINUX_SYMBOL(__end_pci_fixups_suspend) = .; \ + VMLINUX_SYMBOL(__start_pci_fixups_suspend_late) = .; \ + *(.pci_fixup_suspend_late) \ + VMLINUX_SYMBOL(__end_pci_fixups_suspend_late) = .; \ } \ \/* Built-in firmware blobs */ \include/linux/pci.h +11
-1@@ -1458,8 +1458,9 @@ enum pci_fixup_pass { pci_fixup_final,/* Final phase of device fixups */ pci_fixup_enable,/* pci_enable_device() time */ pci_fixup_resume,/* pci_device_resume() */ - pci_fixup_suspend,/* pci_device_suspend */ + pci_fixup_suspend,/* pci_device_suspend() */ pci_fixup_resume_early,/* pci_device_resume_early() */ + pci_fixup_suspend_late, /* pci_device_suspend_late() */ };/* Anonymous variables would be nice... */ @@ -1500,6 +1501,11 @@ enum pci_fixup_pass { DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend, \ suspend##hook, vendor, device, class, \ class_shift, hook) +#define DECLARE_PCI_FIXUP_CLASS_SUSPEND_LATE (vendor, device, class, \ + class_shift, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend_late, \ + suspend_late##hook, vendor, device, \ + class, class_shift, hook)#define DECLARE_PCI_FIXUP_EARLY (vendor, device, hook) \ DECLARE_PCI_FIXUP_SECTION(.pci_fixup_early, \@@ -1525,6 +1531,10 @@ enum pci_fixup_pass { DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend, \ suspend##hook, vendor, device, \ PCI_ANY_ID, 0, hook) +#define DECLARE_PCI_FIXUP_SUSPEND_LATE (vendor, device, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend_late, \ + suspend_late##hook, vendor, device, \ + PCI_ANY_ID, 0, hook)#ifdef CONFIG_PCI_QUIRKS void pci_fixup_device (enum pci_fixup_pass pass,struct pci_dev *dev);Signature · 1 line
1.9.3
Andreas Noever
maintainerRe: [PATCH v3 12/15] pci: Add pci_fixup_suspend_late quirk pass.
Quoted 15 lines · Bjorn Helgaas
On Mon, May 26, 2014 at 05:18:09PM +0200, Andreas Noever wrote:
Please adjust the subject to match the
drivers/pciconvention, i.e., changepci: Add
pci_fixup_suspend_latequirk pass.to
PCI: Add
pci_fixup_suspend_latequirk passQuoted 5 lines
Add
pci_fixup_suspend_lateas a newpci_fixup_pass. The pass is called fromsuspend_noirqandpoweroff_noirq. Using the same pass for suspend and hibernate is consistent withresume_earlywhich is called byresume_noirqandrestore_noirq.
Quoted 6 lines
Quoted 4 lines
The new quirk pass is required for Thunderbolt support on Apple hardware.
Signed-off-by Andreas Noever <[email protected]>Acked-by Bjorn Helgaas <[email protected]>
Thanks for reading through the series. I will make those changes for the next version.
Quoted 2 lines
I assume you'll merge this along with the rest of the series, not through my PCI tree.
Agreed, since I am the only user of this pass. Can you tell me whose tree normally carries new drivers?
Thanks,
Andreas
Re: [PATCH v3 12/15] pci: Add pci_fixup_suspend_late quirk pass.
Quoted 23 lines · Andreas Noever
Quoted 15 lines · Bjorn Helgaas
On Mon, May 26, 2014 at 05:18:09PM +0200, Andreas Noever wrote:
Please adjust the subject to match the
drivers/pciconvention, i.e., changepci: Add
pci_fixup_suspend_latequirk pass.to
PCI: Add
pci_fixup_suspend_latequirk passQuoted 5 lines
Add
pci_fixup_suspend_lateas a newpci_fixup_pass. The pass is called fromsuspend_noirqandpoweroff_noirq. Using the same pass for suspend and hibernate is consistent withresume_earlywhich is called byresume_noirqandrestore_noirq.Quoted 6 lines
Quoted 4 lines
The new quirk pass is required for Thunderbolt support on Apple hardware.
Signed-off-by Andreas Noever <[email protected]>Acked-by Bjorn Helgaas <[email protected]>
Quoted 7 lines
Thanks for reading through the series. I will make those changes for the next version.
Quoted 2 lines
I assume you'll merge this along with the rest of the series, not through my PCI tree.
Agreed, since I am the only user of this pass. Can you tell me whose tree normally carries new drivers?
That's mine. But if Matthew wants to take this in his trees, I'll have no objection.
thanks,
greg k-h
Andreas Noever
maintainerPATCH v3 13/15 pci: Suspend/resume quirks for appel thunderbolt
In reply to Andreas NoeverAdd two quirks to support thunderbolt suspend/resume on apple systems. We need to perform two different actions during suspend and resume:
The whole controller has to be powered down before suspend. If this is not done then the NHI device will be gone after resume if a thunderbolt device was plugged in while suspending. The controller represents itself as multiple PCI devices/bridges. To power it down we hook into the upstream bridge of the controller and call the magic ACPI methods. Power will be restored automatically during resume (by the firmware presumably).
During resume we have to wait for the NHI do reestablish all pci tunnels. Since there is no parent-child relationship between the NHI and the bridges we have to explicitly wait for them using device_pm_wait_for_dev. We do this in the resume_noirq phase of the downstream bridges of the controller (which lead into the thunderbolt tunnels).
--- drivers/pci/quirks.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+)
drivers/pci/quirks.c +122 -0
@@ -2992,6 +2992,128 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CHELSIO, 0x0030,DECLARE_PCI_FIXUP_HEADER (0x1814 ,0x0601 ,/* Ralink RT2800 802.11n PCI */ quirk_broken_intx_masking); +/* Apple systems with a Cactus Ridge Thunderbolt controller. */ +static struct dmi_system_id apple_thunderbolt_whitelist[] = { + { + .matches = { +DMI_MATCH (DMI_BOARD_VENDOR ,"Apple Inc." ), +DMI_MATCH (DMI_PRODUCT_NAME ,"MacBookPro9" ), + }, + }, + { + .matches = { +DMI_MATCH (DMI_BOARD_VENDOR ,"Apple Inc." ), +DMI_MATCH (DMI_PRODUCT_NAME ,"MacBookPro10" ), + }, + }, + { + .matches = { +DMI_MATCH (DMI_BOARD_VENDOR ,"Apple Inc." ), +DMI_MATCH (DMI_PRODUCT_NAME ,"MacBookAir5" ), + }, + }, + { + .matches = { +DMI_MATCH (DMI_BOARD_VENDOR ,"Apple Inc." ), +DMI_MATCH (DMI_PRODUCT_NAME ,"MacBookAir6" ), + }, + }, + { } +}; + +/* +* Apple: Shutdown Cactus Ridge Thunderbolt controller. +* +* On Apple hardware the Cactus Ridge Thunderbolt controller needs to be +* shutdown before suspend. Otherwise the native host interface (NHI) will not +* be present after resume if a device was plugged in before suspend. +* +* The thunderbolt controller consists of a pcie switch with downstream +* bridges leading to the NHI and to the tunnel pci bridges. +* +* This quirk cuts power to the whole chip. Therefore we have to apply it +* during suspend_noirq of the upstream bridge. +* +* Power is automagically restored before resume. No action is needed. +*/ +static void quirk_apple_poweroff_thunderbolt (struct pci_dev *dev) +{ +#ifdef CONFIG_ACPI +acpi_handle bridge,SXIO ,SXFP ,SXLV ; +if (!dmi_check_system (apple_thunderbolt_whitelist)) +return ; +if (pci_pcie_type (dev) !=PCI_EXP_TYPE_UPSTREAM ) +return ; + bridge =ACPI_HANDLE (&dev->dev); +if (!bridge) +return ; +/* +* TB bridges in external devices might have the same device id as those +* on the host, but they will not have the associated ACPI methods. This +* implicitly checks that we are at the right bridge. +*/ +if (ACPI_FAILURE (acpi_get_handle (bridge,"DSB0.NHI0.SXIO" , &SXIO )) + ||ACPI_FAILURE (acpi_get_handle (bridge,"DSB0.NHI0.SXFP" , &SXFP )) + ||ACPI_FAILURE (acpi_get_handle (bridge,"DSB0.NHI0.SXLV" , &SXLV ))) +return ; +dev_info (&dev->dev,"quirk: cutting power to thunderbolt controller...\n" ); + +/* magic sequence */ +acpi_execute_simple_method (SXIO ,NULL ,1 ); +acpi_execute_simple_method (SXFP ,NULL ,0 ); +msleep (300 ); +acpi_execute_simple_method (SXLV ,NULL ,0 ); +acpi_execute_simple_method (SXIO ,NULL ,0 ); +acpi_execute_simple_method (SXLV ,NULL ,0 ); +#endif +} +DECLARE_PCI_FIXUP_SUSPEND_LATE (PCI_VENDOR_ID_INTEL ,0x1547 , + quirk_apple_poweroff_thunderbolt); + +/* +* Apple: Wait for the thunderbolt controller to reestablish pci tunnels. +* +* During suspend the thunderbolt controller is reset and all pci +* tunnels are lost. The NHI driver will try to reestablish all tunnels +* during resume. We have to manually wait for the NHI since there is +* no parent child relationship between the NHI and the tunneled +* bridges. +*/ +static void quirk_apple_wait_for_thunderbolt (struct pci_dev *dev) +{ +#ifdef CONFIG_ACPI +struct pci_dev *sibling =NULL ; +struct pci_dev *nhi =NULL ; +if (!dmi_check_system (apple_thunderbolt_whitelist)) +return ; +if (pci_pcie_type (dev) !=PCI_EXP_TYPE_DOWNSTREAM ) +return ; +/* +* Find the NHI and confirm that we are a bridge on the tb host +* controller and not on a tb endpoint. +*/ + sibling =pci_get_slot (dev->bus,0x0 ); +if (sibling == dev) + goto out;/* we are the downstream bridge to the NHI */ +if (!sibling || !sibling->subordinate) + goto out; + nhi =pci_get_slot (sibling->subordinate,0x0 ); +if (!nhi) + goto out; +if (nhi->vendor !=PCI_VENDOR_ID_INTEL || nhi->device !=0x1547 + || nhi->subsystem_vendor !=0x2222 + || nhi->subsystem_device !=0x1111 ) + goto out; +dev_info (&dev->dev,"quirk: wating for thunderbolt to reestablish pci tunnels...\n" ); +device_pm_wait_for_dev (&dev->dev, &nhi->dev); +out: +pci_dev_put (nhi); +pci_dev_put (sibling); +#endif +} +DECLARE_PCI_FIXUP_RESUME_EARLY (PCI_VENDOR_ID_INTEL ,0x1547 , + quirk_apple_wait_for_thunderbolt); +static void pci_do_fixups (struct pci_dev *dev,struct pci_fixup *f,struct pci_fixup *end) {
Signature · 1 line
1.9.3
Re: [PATCH v3 13/15] pci: Suspend/resume quirks for appel thunderbolt
T24gTW9uLCAyMDE0LTA1LTI2IGF0IDE3OjE4ICswMjAwLCBBbmRyZWFzIE5vZXZlciB3cm90ZToN Cj4gQWRkIHR3byBxdWlya3MgdG8gc3VwcG9ydCB0aHVuZGVyYm9sdCBzdXNwZW5kL3Jlc3VtZSBv biBhcHBsZSBzeXN0ZW1zLg0KPiBXZSBuZWVkIHRvIHBlcmZvcm0gdHdvIGRpZmZlcmVudCBhY3Rp b25zIGR1cmluZyBzdXNwZW5kIGFuZCByZXN1bWU6DQoNCkl0IHNlZW1zIGxpa2Ugd2Ugb3VnaHQg dG8gYmUgYWJsZSB0byBkbyB0aGlzIHdpdGggYSBwb3dlciBkb21haW4sIHJhdGhlcg0KdGhhbiBo YXZpbmcgdG8gYWRkIGNvZGUgdG8gdGhlIFBDSSBjb3JlPyBJJ3ZlIGdvdCBhbiBpbXBsZW1lbnRh dGlvbiB0aGF0DQpjdXJyZW50bHkgZmFpbHMgZm9yIHJlYXNvbnMgSSBoYXZlbid0IGRlYnVnZ2Vk LCBJJ2xsIHNlZSBpZiBJIGNhbiBnZXQgaXQNCndvcmtpbmcgdG9kYXkuDQoNCi0tIA0KTWF0dGhl dyBHYXJyZXR0IDxtYXR0aGV3LmdhcnJldHRAbmVidWxhLmNvbT4NCg==
Re: [PATCH v3 13/15] pci: Suspend/resume quirks for appel thunderbolt
In reply to Andreas NoeverOn Mon, May 26, 2014 at 05:18:10PM +0200, Andreas Noever wrote:
Please change subject:
pci: Suspend/resume quirks for appel thunderbolt
to (note "appel" typo fix):
PCI: Suspend/resume quirks for Apple thunderbolt
Quoted 1 line
Add two quirks to support thunderbolt suspend/resume on apple systems.
s/apple/Apple/
Quoted 4 lines
We need to perform two different actions during suspend and resume:
The whole controller has to be powered down before suspend. If this is not done then the NHI device will be gone after resume if a thunderbolt
Please expand "NHI." I assume it stands for "Native Host Interface."
Quoted 1 line
device was plugged in while suspending. The controller represents itself
s/while suspending/while suspended/ (I assume)
Quoted 6 lines
as multiple PCI devices/bridges. To power it down we hook into the upstream bridge of the controller and call the magic ACPI methods. Power will be restored automatically during resume (by the firmware presumably).
During resume we have to wait for the NHI do reestablish all pci
s/do/to/
Quoted 7 lines
tunnels. Since there is no parent-child relationship between the NHI and the bridges we have to explicitly wait for them using
device_pm_wait_for_dev. We do this in theresume_noirqphase of the downstream bridges of the controller (which lead into the thunderbolt tunnels).Signed-off-by Andreas Noever <[email protected]>
Trivial comments below. Ack applies whether you change them or not.
Quoted 49 lines
--- drivers/pci/quirks.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+)drivers/pci/quirks.c +37
-0@@ -2992,6 +2992,128 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CHELSIO, 0x0030,DECLARE_PCI_FIXUP_HEADER (0x1814 ,0x0601 ,/* Ralink RT2800 802.11n PCI */ quirk_broken_intx_masking); +/* Apple systems with a Cactus Ridge Thunderbolt controller. */ +static struct dmi_system_id apple_thunderbolt_whitelist[] = { + { + .matches = { +DMI_MATCH (DMI_BOARD_VENDOR ,"Apple Inc." ), +DMI_MATCH (DMI_PRODUCT_NAME ,"MacBookPro9" ), + }, + }, + { + .matches = { +DMI_MATCH (DMI_BOARD_VENDOR ,"Apple Inc." ), +DMI_MATCH (DMI_PRODUCT_NAME ,"MacBookPro10" ), + }, + }, + { + .matches = { +DMI_MATCH (DMI_BOARD_VENDOR ,"Apple Inc." ), +DMI_MATCH (DMI_PRODUCT_NAME ,"MacBookAir5" ), + }, + }, + { + .matches = { +DMI_MATCH (DMI_BOARD_VENDOR ,"Apple Inc." ), +DMI_MATCH (DMI_PRODUCT_NAME ,"MacBookAir6" ), + }, + }, + { } +}; + +/* +* Apple: Shutdown Cactus Ridge Thunderbolt controller. +* +* On Apple hardware the Cactus Ridge Thunderbolt controller needs to be +* shutdown before suspend. Otherwise the native host interface (NHI) will not +* be present after resume if a device was plugged in before suspend. +* +* The thunderbolt controller consists of a pcie switch with downstream
drivers/pci/quirks.c DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CHELSIO, 0x0030, · line 3041
Quoted 10 lines
drivers/pci/quirks.c +10
-0+* bridges leading to the NHI and to the tunnel pci bridges. +* +* This quirk cuts power to the whole chip. Therefore we have to apply it +* during suspend_noirq of the upstream bridge. +* +* Power is automagically restored before resume. No action is needed. +*/ +static void quirk_apple_poweroff_thunderbolt (struct pci_dev *dev) +{ +#ifdef CONFIG_ACPI
Why not put the #ifdef around the whole thing, including the entire function definition and the DECLARE_PCI_FIXUP...? It doesn't seem useful to compile a quirk that does nothing.
drivers/pci/quirks.c DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CHELSIO, 0x0030, · line 3042
Quoted 1 line
drivers/pci/quirks.c +1
-0+acpi_handle bridge,SXIO ,SXFP ,SXLV ;
A blank line is conventional here.
Quoted 34 lines
drivers/pci/quirks.c +34
-0+if (!dmi_check_system (apple_thunderbolt_whitelist)) +return ; +if (pci_pcie_type (dev) !=PCI_EXP_TYPE_UPSTREAM ) +return ; + bridge =ACPI_HANDLE (&dev->dev); +if (!bridge) +return ; +/* +* TB bridges in external devices might have the same device id as those +* on the host, but they will not have the associated ACPI methods. This +* implicitly checks that we are at the right bridge. +*/ +if (ACPI_FAILURE (acpi_get_handle (bridge,"DSB0.NHI0.SXIO" , &SXIO )) + ||ACPI_FAILURE (acpi_get_handle (bridge,"DSB0.NHI0.SXFP" , &SXFP )) + ||ACPI_FAILURE (acpi_get_handle (bridge,"DSB0.NHI0.SXLV" , &SXLV ))) +return ; +dev_info (&dev->dev,"quirk: cutting power to thunderbolt controller...\n" ); + +/* magic sequence */ +acpi_execute_simple_method (SXIO ,NULL ,1 ); +acpi_execute_simple_method (SXFP ,NULL ,0 ); +msleep (300 ); +acpi_execute_simple_method (SXLV ,NULL ,0 ); +acpi_execute_simple_method (SXIO ,NULL ,0 ); +acpi_execute_simple_method (SXLV ,NULL ,0 ); +#endif +} +DECLARE_PCI_FIXUP_SUSPEND_LATE (PCI_VENDOR_ID_INTEL ,0x1547 , + quirk_apple_poweroff_thunderbolt); + +/* +* Apple: Wait for the thunderbolt controller to reestablish pci tunnels. +* +* During suspend the thunderbolt controller is reset and all pci
drivers/pci/quirks.c DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CHELSIO, 0x0030, · line 3086
Quoted 10 lines
drivers/pci/quirks.c +10
-0+* tunnels are lost. The NHI driver will try to reestablish all tunnels +* during resume. We have to manually wait for the NHI since there is +* no parent child relationship between the NHI and the tunneled +* bridges. +*/ +static void quirk_apple_wait_for_thunderbolt (struct pci_dev *dev) +{ +#ifdef CONFIG_ACPI +struct pci_dev *sibling =NULL ; +struct pci_dev *nhi =NULL ;
Blank line.
Quoted 36 lines
drivers/pci/quirks.c +30
-0+if (!dmi_check_system (apple_thunderbolt_whitelist)) +return ; +if (pci_pcie_type (dev) !=PCI_EXP_TYPE_DOWNSTREAM ) +return ; +/* +* Find the NHI and confirm that we are a bridge on the tb host +* controller and not on a tb endpoint. +*/ + sibling =pci_get_slot (dev->bus,0x0 ); +if (sibling == dev) + goto out;/* we are the downstream bridge to the NHI */ +if (!sibling || !sibling->subordinate) + goto out; + nhi =pci_get_slot (sibling->subordinate,0x0 ); +if (!nhi) + goto out; +if (nhi->vendor !=PCI_VENDOR_ID_INTEL || nhi->device !=0x1547 + || nhi->subsystem_vendor !=0x2222 + || nhi->subsystem_device !=0x1111 ) + goto out; +dev_info (&dev->dev,"quirk: wating for thunderbolt to reestablish pci tunnels...\n" ); +device_pm_wait_for_dev (&dev->dev, &nhi->dev); +out: +pci_dev_put (nhi); +pci_dev_put (sibling); +#endif +} +DECLARE_PCI_FIXUP_RESUME_EARLY (PCI_VENDOR_ID_INTEL ,0x1547 , + quirk_apple_wait_for_thunderbolt); +static void pci_do_fixups (struct pci_dev *dev,struct pci_fixup *f,struct pci_fixup *end) {Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 14/15 thunderbolt: Read switch uid from EEPROM
In reply to Andreas NoeverAdd eeprom access code and read the uid during switch initialization. The UID will be used to check device identity after suspend.
--- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/eeprom.c | 189 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 5 ++ drivers/thunderbolt/tb.h | 3 + 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/eeprom.c
drivers/thunderbolt/Makefile +1 -1
@@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o eeprom.o
drivers/thunderbolt/eeprom.c new file +189 -0
189 lines not on this page · raw message
drivers/thunderbolt/switch.c +5 -0
@@ -370,6 +370,11 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) } sw->cap_plug_events = cap; +if (tb_eeprom_read_uid (sw, &sw->uid)) +tb_sw_warn (sw,"could not read uid from eeprom\n" ); +else +tb_sw_info (sw,"uid: %#llx\n" , sw->uid); +if (tb_plug_events_active (sw, true)) goto err;
drivers/thunderbolt/tb.h +3 -0
@@ -19,6 +19,7 @@ struct tb_switch {struct tb_regs_switch_header config;struct tb_port *ports;struct tb *tb; +u64 uid;int cap_plug_events;/* offset, zero if not found */ bool is_unplugged;/* unplugged, will go away */ };
@@ -231,6 +232,8 @@ int tb_path_activate(struct tb_path *path);void tb_path_deactivate (struct tb_path *path);bool tb_path_is_invalid (struct tb_path *path); +int tb_eeprom_read_uid (struct tb_switch *sw,u64 *uid); +static inline int tb_route_length (u64 route) {
Signature · 1 line
1.9.3
Andreas Noever
maintainerPATCH v3 15/15 thunderbolt: Add suspend/hibernate support
In reply to Andreas NoeverWe use _noirq since we have to restore the pci tunnels before the pci core wakes the tunneled devices.
--- drivers/thunderbolt/nhi.c | 33 +++++++++++++++++ drivers/thunderbolt/switch.c | 84 ++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.c | 61 ++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 5 +++ 4 files changed, 183 insertions(+)
drivers/thunderbolt/nhi.c +33 -0
@@ -7,6 +7,7 @@* Copyright (c) 2014 Andreas Noever <[email protected]> */ +#include <linux/pm_runtime.h> #include <linux/slab.h> #include <linux/errno.h> #include <linux/pci.h>
@@ -492,6 +493,22 @@ static irqreturn_t nhi_msi(int irq, void *data)return IRQ_HANDLED ; } +static int nhi_suspend_noirq (struct device *dev) +{ +struct pci_dev *pdev =to_pci_dev (dev); +struct tb *tb =pci_get_drvdata (pdev); +thunderbolt_suspend (tb); +return 0 ; +} + +static int nhi_resume_noirq (struct device *dev) +{ +struct pci_dev *pdev =to_pci_dev (dev); +struct tb *tb =pci_get_drvdata (pdev); +thunderbolt_resume (tb); +return 0 ; +} +static void nhi_shutdown (struct tb_nhi *nhi) {int i;
@@ -600,6 +617,21 @@ static void nhi_remove(struct pci_dev *pdev)nhi_shutdown (nhi); } +/* +* The tunneled pci bridges are siblings of us. Use resume_noirq to reenable +* the tunnels asap. A corresponding pci quirk blocks the downstream bridges +* resume_noirq until we are done. +*/ +static const struct dev_pm_ops nhi_pm_ops = { + .suspend_noirq = nhi_suspend_noirq, + .resume_noirq = nhi_resume_noirq, + .freeze_noirq = nhi_suspend_noirq,/* +* we just disable hotplug, the +* pci-tunnels stay alive. +*/ + .restore_noirq = nhi_resume_noirq, +}; +struct pci_device_id nhi_ids[] = { {/*
@@ -621,6 +653,7 @@ static struct pci_driver nhi_driver = { .id_table = nhi_ids, .probe = nhi_probe, .remove = nhi_remove, + .driver.pm = &nhi_pm_ops, };static int __init nhi_init (void )
drivers/thunderbolt/switch.c +84 -0
93 lines not on this page · raw message
drivers/thunderbolt/tb.c +61 -0
70 lines not on this page · raw message
drivers/thunderbolt/tb.h +5 -0
@@ -214,9 +214,14 @@ static inline int tb_port_write(struct tb_port *port, void *buffer,struct tb *thunderbolt_alloc_and_start (struct tb_nhi *nhi);void thunderbolt_shutdown_and_free (struct tb *tb); +void thunderbolt_suspend (struct tb *tb); +void thunderbolt_resume (struct tb *tb);struct tb_switch *tb_switch_alloc (struct tb *tb,u64 route);void tb_switch_free (struct tb_switch *sw); +void tb_switch_suspend (struct tb_switch *sw); +int tb_switch_resume (struct tb_switch *sw); +int tb_switch_reset (struct tb *tb,u64 route);void tb_sw_set_unpplugged (struct tb_switch *sw);struct tb_switch *get_switch_at_route (struct tb_switch *sw,u64 route);
Signature · 1 line
1.9.3
QWRkaW5nIHRoZSBQQ0kgSURzIGZvciB0aGUgVGh1bmRlcmJvbHQgMiBkZXZpY2UgaW4gdGhlIE1h YyBQcm8gc2VlbXMgdG8NCndvcmssIHdpdGggdGhlIHBvc3NpYmxlIGV4Y2VwdGlvbiBvZiBob3Rw bHVnIC0gSSdsbCBsb29rIGludG8gdGhhdCBzb21lDQptb3JlLiBJIGFsc28gaGF2ZSBhIG5ldyBz ZXQgb2YgdGhlIEFDUEkgcGF0Y2hlcywgd2hpY2ggSSdsbCB0cnkgdG8gc2VuZA0Kb3V0IHRvZGF5 Lg0KDQotLSANCk1hdHRoZXcgR2FycmV0dCA8bWF0dGhldy5nYXJyZXR0QG5lYnVsYS5jb20+DQo=