Thunderbolt hotplug support for Apple hardware (testers needed)
Subsystems (4)
Andreas Noever
maintainercover Thunderbolt hotplug support for Apple hardware (testers needed)
Thunderbolt hotplug is supposed to be implemented by the firmware. But Apple's firmeware only initializes devices during boot and ignores hotplugged devices. This patch series adds a driver for the Intel Cactus Ridge Thunderbolt controller. The driver supports hotplug operations of simple (one PCI device, no chaining) thunderbolt devices.
The patches in this series are self contained and I have tried to sort them (roughly) from lowest to highest level of abstraction. They all compile but only the final driver has been tested.
The driver works on my MacBookPro10,1 with two Thunderbolt ethernet adapters. The system contains the Cactus Ridge C4 TB controller (device id 0x1547). I could not run tests on other systems, but there is a good chance that the driver will work on other models as well. If something is controller specific then it should be confined to patch 01.
There are still a number of limitations:
(1) The system must be booted with acpi_osi=Darwin. Otherwise ACPI will cut
power to the controller.
(2) After suspend the controller is gone. I think that ACPI thinks that we are
Windows and cuts power, even with acpi_osi=Darwin.
(3) No support for chaining. Also the driver only activates the first PCI
device found on a Thunderbolt device.
(4) Rapidly replugging a device confuses pciehp.
(1) and (2) should be solveable given some time. I also have a pretty good idea on how chaining should work. The main problem is that I have yet to see any Thunderbolt device besides the Apple ethernet adapter. If someone with a MacBook and access to more complicated TB hardware is willing to run some
tests: Please contact me.
I will try to debug (4) further and write a seperate bug report for the issue.
Andreas
Andreas Noever
maintainerPATCH 1/12 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.
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/dsl3510.c | 591 +++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/dsl3510.h | 116 ++++++++ drivers/thunderbolt/dsl3510_regs.h | 102 +++++++ 7 files changed, 827 insertions(+) create mode 100644 drivers/thunderbolt/Kconfig create mode 100644 drivers/thunderbolt/Makefile create mode 100644 drivers/thunderbolt/dsl3510.c create mode 100644 drivers/thunderbolt/dsl3510.h create mode 100644 drivers/thunderbolt/dsl3510_regs.h
drivers/Kconfig +2 -0
@@ -170,4 +170,6 @@ source "drivers/phy/Kconfig" source "drivers/powercap/Kconfig" +source "drivers/thunderbolt/Kconfig" + endmenu
drivers/Makefile +1 -0
@@ -155,3 +155,4 @@ obj-$(CONFIG_IPACK_BUS) += ipack/ obj-$(CONFIG_NTB) += ntb/ obj-$(CONFIG_FMC) += fmc/ obj-$(CONFIG_POWERCAP) += powercap/ +obj-$(CONFIG_THUNDERBOLT) += thunderbolt/
drivers/thunderbolt/Kconfig new file +12 -0
@@ -0,0 +1,12 @@ +menuconfig THUNDERBOLT + tristate "Apple Thunderbolt hotplug support" + 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 := dsl3510.o +
drivers/thunderbolt/dsl3510.c new file +591 -0
591 lines not on this page · raw message
drivers/thunderbolt/dsl3510.h new file +116 -0
116 lines not on this page · raw message
drivers/thunderbolt/dsl3510_regs.h new file +102 -0
102 lines not on this page · raw message
Signature · 1 line
1.8.4.2
Andreas Noever
maintainerPATCH 2/12 thunderbolt: Add configuration 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 configuration 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/tb_cfg.c | 649 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb_cfg.h | 67 +++++ 3 files changed, 717 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/tb_cfg.c create mode 100644 drivers/thunderbolt/tb_cfg.h
drivers/thunderbolt/Makefile +1 -1
@@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := dsl3510.o +thunderbolt-objs := dsl3510.o tb_cfg.o
drivers/thunderbolt/tb_cfg.c new file +649 -0
649 lines not on this page · raw message
drivers/thunderbolt/tb_cfg.h new file +67 -0
67 lines not on this page · raw message
Signature · 1 line
1.8.4.2
Andreas Noever
maintainerPATCH 3/12 thunderbolt: Setup configuration 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 configuration 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/dsl3510.c | 18 +++++- drivers/thunderbolt/tb.c | 124 ++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 34 ++++++++++++ 4 files changed, 175 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 := dsl3510.o tb_cfg.o +thunderbolt-objs := dsl3510.o tb_cfg.o tb.o
drivers/thunderbolt/dsl3510.c +16 -2
@@ -12,6 +12,7 @@#include "dsl3510.h" #include "dsl3510_regs.h" +#include "tb.h" #define RING_TYPE (ring) ((ring)->is_tx ? "TX ring" : "RX ring")
@@ -481,6 +482,7 @@ static void dsl3510_shutdown(struct tb_nhi *nhi)static int dsl3510_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);
@@ -543,14 +545,26 @@ static int dsl3510_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);/* for dsl3510_remove only */ +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. +*/ +dsl3510_shutdown (nhi); +return -EIO ; + } +pci_set_drvdata (pdev, tb);/* for dsl3510_remove only */ return 0 ; }static void dsl3510_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);dsl3510_shutdown (nhi); }
drivers/thunderbolt/tb.c new file +124 -0
124 lines not on this page · raw message
drivers/thunderbolt/tb.h new file +34 -0
@@ -0,0 +1,34 @@ +/* +* Device independent Thunderbolt bus logic +* +* Copyright (c) 2013 Andreas Noever <[email protected]> +*/ + +#ifndef TB_H_ +#define TB_H_ + +#include <linux/pci.h> + +#include "tb_cfg.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_cfg *cfg; +struct workqueue_struct *wq;/* ordered workqueue for plug events */ +bool shutdown;/* +* Once this is set tb_handle_hotplug will exit (once it +* can aquire lock at least once). Used to drain wq. +*/ +}; + +struct tb *thunderbolt_alloc_and_start (struct tb_nhi *nhi); +void thunderbolt_shutdown_and_free (struct tb *tb); + +#endif
Signature · 1 line
1.8.4.2
Andreas Noever
maintainerPATCH 4/12 thunderbolt: Add tb_regs.h
In reply to Andreas NoeverEvery thunderbolt device consists (logically) of a switch with multiple ports. Every port contains multiple 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_regs.h | 187 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 drivers/thunderbolt/tb_regs.h
drivers/thunderbolt/tb_regs.h new file +187 -0
@@ -0,0 +1,187 @@ +/* +* Thunderbolt Port/Switch config area registers +* +* Copyright (c) 2013 Andreas Noever <[email protected]> +*/ + +#ifndef _TB_REGS +#define _TB_REGS + +#include <linux/types.h> + +/* +* TODO: should be 63? But we do not know how to receive frames larger than 256 +* bytes. (header + checksum = 16, 60*4 = 240) +*/ +#define TB_MAX_CONFIG_RW_LENGTH 60 + +enum tb_cap { +TB_CAP_PHY =0x0001 , +TB_CAP_TIME1 =0x0003 , +TB_CAP_PCIE =0x0004 , +TB_CAP_I2C =0x0005 , +TB_CAP_PLUG_EVENTS =0x0105 ,/* also EEPROM */ +TB_CAP_TIME2 =0x0305 , +TB_CAL_IECS =0x0405 , +TB_CAP_LINK_CONTROLLER =0x0605 ,/* also IECS */ +}; + +enum tb_port_state { +TB_PORT_DISABLED =0 ,/* tb_cap_phy.disable == 1 */ +TB_PORT_CONNECTING =1 ,/* retry */ +TB_PORT_UP =2 , +TB_PORT_UNPLUGGED =7 , +}; + +/* capability headers */ + +struct tb_cap_basic { +u8 next; +/* enum tb_cap cap:8; prevent "narrower than values of its type" */ +u8 cap;/* if cap == 0x05 then we have a extended capability */ +} __packed; + +struct tb_cap_extended_short { +u8 next;/* if next and length are zero then we have a long cap */ +enum tb_cap cap:16 ; +u8 length; +} __packed; + +struct tb_cap_extended_long { +u8 zero1; +enum tb_cap cap:16 ; +u8 zero2; +u16 next; +u16 length; +} __packed; + +/* capabilities */ + +struct tb_cap_link_controller { +struct tb_cap_extended_long cap_header; +u32 count:4 ;/* number of link controllers */ +u32 unknown1:4 ; +u32 base_offset:8 ;/* +* offset (into this capability) of the configuration +* area of the first link controller +*/ +u32 length:12 ;/* link controller configuration area length */ +u32 unknown2:4 ;/* TODO check that length is correct */ +} __packed; + +struct tb_cap_phy { +struct tb_cap_basic cap_header; +u32 unknown1:16 ; +u32 unknown2:14 ; +bool disable:1 ; +u32 unknown3:11 ; +enum tb_port_state state:4 ; +u32 unknown4:2 ; +} __packed; + +struct tb_cap_plug_events { +struct tb_cap_extended_short cap_header; +u32 __unknown1:2 ; +u32 plug_events:5 ; +u32 __unknown2:25 ; +} __packed; + +/* device headers */ + +/* Present on port 0 in TB_CFG_SWITCH at address zero. */ +struct tb_regs_switch_header { +/* DWORD 0 */ +u16 vendor_id; +u16 device_id; +/* DWORD 1 */ +u32 first_cap_offset:8 ; +u32 upstream_port_number:6 ; +u32 max_port_number:6 ; +u32 depth:3 ; +u32 __unknown1:1 ; +u32 revision:8 ; +/* DWORD 2 */ +u32 route_lo; +/* DWORD 3 */ +u32 route_hi:31 ; +bool enabled:1 ; +/* DWORD 4 */ +u32 plug_events_delay:8 ;/* +* RW, pause between plug events in +* milliseconds. Writing 0x00 is interpreted +* as 255ms. +*/ +u32 __unknown4:16 ; +u32 thunderbolt_version:8 ; +} __packed; + +enum tb_port_type { +TB_TYPE_INACTIVE =0x000000 , +TB_TYPE_PORT =0x000001 , +TB_TYPE_NHI =0x000002 , +/* TB_TYPE_ETHERNET = 0x020000, lower order bits are not known */ +/* TB_TYPE_SATA = 0x080000, lower order bits are not known */ +TB_TYPE_DP_HDMI_IN =0x0e0101 , +TB_TYPE_DP_HDMI_OUT =0x0e0102 , +TB_TYPE_PCIE_DOWN =0x100101 , +TB_TYPE_PCIE_UP =0x100102 , +/* TB_TYPE_USB = 0x200000, lower order bits are not known */ +}; + +/* Present on every port in TB_CF_PORT at address zero. */ +struct tb_regs_port_header { +/* DWORD 0 */ +u16 vendor_id; +u16 device_id; +/* DWORD 1 */ +u32 first_cap_offset:8 ; +u32 __unknown1:16 ; +u32 revision:8 ; +/* DWORD 2 */ +enum tb_port_type type:24 ; +u32 thunderbolt_version:8 ; +/* DWORD 3 */ +u32 __unknown2:20 ; +u32 port_number:6 ; +u32 __unknown3:6 ; +/* DWORD 4 */ +u32 nfc_credits; +/* DWORD 5 */ +u32 max_in_hop_id:11 ; +u32 max_out_hop_id:11 ; +u32 __unkown4:10 ; +/* DWORD 6 */ +u32 __unknown5; +/* DWORD 7 */ +u32 __unknown6; + +} __packed; + +/* Hop register from TB_CFG_HOPS. 8 byte per entry. */ +struct tb_regs_hop { +/* DWORD 0 */ +u32 next_hop:11 ;/* +* hop to take after sending the packet through +* out_port (on the incoming port of the next switch) +*/ +u32 out_port:6 ;/* next port of the path (on the same switch) */ +u32 initial_credits:8 ; +u32 unknown1:6 ;/* set to zero */ +bool enable:1 ; + +/* DWORD 1 */ +u32 weight:4 ; +u32 unknown2:4 ;/* set to zero */ +u32 priority:3 ; +bool drop_packages:1 ; +u32 counter:11 ;/* index into TB_CFG_COUNTERS on this port */ +bool counter_enable:1 ; +bool ingress_fc:1 ; +bool egress_fc:1 ; +bool ingress_shared_buffer:1 ; +bool egress_shared_buffer:1 ; +u32 unknown3:4 ;/* set to zero */ +} __packed; + + +#endif
Signature · 1 line
1.8.4.2
Andreas Noever
maintainerPATCH 5/12 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 reset and configure the root switch.
--- drivers/thunderbolt/tb.c | 229 ++++++++++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/tb.h | 113 +++++++++++++++++++++++ 2 files changed, 340 insertions(+), 2 deletions(-)
drivers/thunderbolt/tb.c +227 -2
@@ -8,7 +8,217 @@#include <linux/errno.h> #include <linux/delay.h> +#include "dsl3510.h" #include "tb.h" +#include "tb_regs.h" + +/* utility functions */ + +static void tb_dump_switch (struct tb *tb,struct tb_regs_switch_header *sw) +{ +tb_info (tb, +" Switch: %x:%x (Revision: %d, TB Version: %d)\n" , + sw->vendor_id, + sw->device_id, + sw->revision, + sw->thunderbolt_version); +tb_info (tb," Max Port Number: %d\n" , sw->max_port_number); +tb_info (tb," Config:\n" ); +tb_info (tb, +" Upstream Port Number: %d Depth: %d Route String: %#llx Enabled: %d, PlugEventsDelay: %dms\n" , + sw->upstream_port_number, + sw->depth, + (((u64 ) sw->route_hi) <<32 ) | sw->route_lo, + sw->enabled, + sw->plug_events_delay); +tb_info (tb, +" unknown1: %#x unknown4: %#x\n" , + sw->__unknown1, + sw->__unknown4); +} + +static char *tb_port_type (struct tb_regs_port_header *port) +{ +switch (port->type >>16 ) { +case 0 : +switch ((u8 ) port->type) { +case 0 : +return "Inactive" ; +case 1 : +return "Port" ; +case 2 : +return "NHI" ; +default : +return "unknown" ; + } +case 0x2 : +return "Ethernet" ; +case 0x8 : +return "SATA" ; +case 0xe : +return "DP/HDMI" ; +case 0x10 : +return "PCIe" ; +case 0x20 : +return "USB" ; +default : +return "unknown" ; + } +} + +static void tb_dump_port (struct tb *tb,struct tb_regs_port_header *port) +{ +tb_info (tb, +" Port %d: %x:%x (Revision: %d, TB Version: %d, Type: %s (%#x))\n" , + port->port_number, + port->vendor_id, + port->device_id, + port->revision, + port->thunderbolt_version, +tb_port_type (port), + port->type); +tb_info (tb, +" Max hop id (in/out): %d/%d, NFC Credits: %#x\n" , + port->max_in_hop_id, + port->max_out_hop_id, + port->nfc_credits); +} + +static int tb_route_length (u64 route) +{ +return (fls64 (route) +TB_ROUTE_SHIFT -1 ) /TB_ROUTE_SHIFT ; +} + +/* switch/port allocation & initialization */ + +/** +* tb_init_port() - initialize a port +* +* This is a helper method for tb_switch_alloc. Does not check or initialize +* any downstream switches. +* +* Return: Returns 0 on success or an error code on failure. +*/ +static int tb_init_port (struct tb_switch *sw,u8 port_nr) +{ +int res; +struct tb_port *port = &sw->ports[port_nr]; + port->sw = sw; + port->port = port_nr; + res =tb_port_read (port, &port->config,TB_CFG_PORT ,0 ,8 ); +if (res) +return res; + +tb_dump_port (sw->tb, &port->config); + +/* TODO: Read dual link port, DP port and more from EEPROM. */ +return 0 ; + +} + +/** +* tb_switch_free() - free a tb_switch and all downstream switches +*/ +static void tb_switch_free (struct tb_switch *sw) +{ +kfree (sw->ports); +kfree (sw); +} + +/** +* tb_switch_alloc() - allocate and initialize a switch +* +* Return: Returns a NULL on failure. +*/ +static struct tb_switch *tb_switch_alloc (struct tb *tb,u64 route) +{ +int i; +struct tb_switch *sw; +int upstream_port =tb_cfg_get_upstream_port (tb->cfg, route); +if (upstream_port <0 ) +return NULL ; + + sw =kzalloc (sizeof (*sw),GFP_KERNEL ); +if (!sw) +return NULL ; + + sw->tb = tb; +if (tb_cfg_read (tb->cfg, &sw->config, route,0 ,2 ,0 ,5 )) + goto err; +tb_info (tb, +"initializing Switch at %#llx (depth: %d, up port: %d)\n" , + route, +tb_route_length (route), + upstream_port); +tb_info (tb,"old switch config:\n" ); +tb_dump_switch (tb, &sw->config); + +/* configure switch */ + sw->config.upstream_port_number = upstream_port; + sw->config.depth =tb_route_length (route); + sw->config.route_lo = route; + sw->config.route_hi = route >>32 ; + sw->config.enabled =1 ; +/* from here on we may use the tb_sw_* functions & macros */ + +if (sw->config.vendor_id !=0x8086 ) { +tb_sw_WARN (sw, +"unsupported switch vendor id %#x, aborting\n" , + sw->config.vendor_id); + goto err; + } +if (sw->config.device_id !=0x1547 && sw->config.device_id !=0x1549 ) { + +tb_sw_WARN (sw, +"unsupported switch device id %#x, aborting\n" , + sw->config.device_id); + goto err; + } + +/* upload configuration */ +if (tb_sw_write (sw,1 + (u32 *) &sw->config,TB_CFG_SWITCH ,1 ,3 )) + goto err; + +/* initialize ports */ + sw->ports =kcalloc (sw->config.max_port_number +1 ,sizeof (*sw->ports), +GFP_KERNEL ); +if (!sw->ports) + goto err; + +for (i =0 ; i <= sw->config.max_port_number; i++) { +if (tb_init_port (sw, i)) + goto err; +/* TODO: check if port is disabled (EEPROM) */ + } + +/* TODO: I2C, IECS, EEPROM, link controller */ + +return sw; +err: +kfree (sw->ports); +kfree (sw); +return NULL ; +} + +/** +* reset_switch() - send TB_CFG_PKG_RESET and enable switch +* +* Return: Returns 0 on success or an error code on failure. +*/ +static int tb_switch_reset (struct tb *tb,u64 route) +{ +int res; +struct tb_regs_switch_header header = { + header.route_hi = route >>32 , + header.route_lo = route, + header.enabled = true, + }; +tb_info (tb,"resetting switch at %llx\n" , route); + res =tb_cfg_reset (tb->cfg, route); +if (res) +return res; +return tb_cfg_write (tb->cfg, ((u32 *) &header) +2 , route,0 ,2 ,2 ,2 ); +}struct tb_hotplug_event {struct work_struct work;
@@ -59,13 +269,17 @@ static void tb_schedule_hotplug_handler(void *data, u64 route, u8 port,/** * thunderbolt_shutdown_and_free() - shutdown everything * -* Free the config channel. +* Free all switches and the config channel. */ void thunderbolt_shutdown_and_free (struct tb *tb) {mutex_lock (&tb->lock); tb->shutdown = true;/* signal tb_handle_hotplug to quit */ +if (tb->root_switch) +tb_switch_free (tb->root_switch); + tb->root_switch =NULL ; +if (tb->cfg)tb_cfg_free (tb->cfg); tb->cfg =NULL ;
@@ -84,7 +298,7 @@ void thunderbolt_shutdown_and_free(struct tb *tb)/** * thunderbolt_alloc_and_start() - setup the thunderbolt bus * -* Allocates a tb_cfg control channel. +* Allocates a tb_cfg control channel and initializes the root switch. * * Return: Returns NULL on error. */
@@ -92,6 +306,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 ;
@@ -112,6 +330,13 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi)if (!tb->cfg) goto err_locked; +if (tb_switch_reset (tb,0 )) + goto err_locked; + + tb->root_switch =tb_switch_alloc (tb,0 ); +if (!tb->root_switch) + goto err_locked; +mutex_unlock (&tb->lock);return tb;
drivers/thunderbolt/tb.h +113 -0
133 lines not on this page · raw message
Signature · 1 line
1.8.4.2
Andreas Noever
maintainerPATCH 6/12 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/tb.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 2 + 2 files changed, 105 insertions(+)
drivers/thunderbolt/tb.c +103 -0
@@ -89,6 +89,109 @@ static int tb_route_length(u64 route)return (fls64 (route) +TB_ROUTE_SHIFT -1 ) /TB_ROUTE_SHIFT ; } + +/* thunderbolt capability lookup */ + +struct tb_cap_any { +union { +struct tb_cap_basic basic; +struct tb_cap_extended_short extended_short; +struct tb_cap_extended_long extended_long; + }; +} __packed; + +static bool tb_cap_is_basic (struct tb_cap_any *cap) +{ +/* basic.cap is u8. This checks only the lower 8 bit of cap. */ +return cap->basic.cap !=5 ; +} + +static bool tb_cap_is_long (struct tb_cap_any *cap) +{ +return !tb_cap_is_basic (cap) + && cap->extended_short.next ==0 + && cap->extended_short.length ==0 ; +} + +static enum tb_cap tb_cap (struct tb_cap_any *cap) +{ +if (tb_cap_is_basic (cap)) +return cap->basic.cap; +else +/* extended_short/long have cap at the same position. */ +return cap->extended_short.cap; +} + +static u32 tb_cap_next (struct tb_cap_any *cap,u32 offset) +{ +int next; +if (offset ==1 ) { +/* +* The first pointer is part of the switch header and always +* a simple pointer. +*/ + next = cap->basic.next; + }else { +if (tb_cap_is_basic (cap)) + next = cap->basic.next; +/* "255 byte config areas should be enough for anybody." */ +else if (!tb_cap_is_long (cap)) + next = cap->extended_short.next; +/* +* "Also we should have at least three types of capability +* headers in version 1." +*/ +else + next = cap->extended_long.next; + } +/* +* "Hey, we could terminate some capability lists with a null offset +* and others with a pointer to the last element." - "Great idea!" +*/ +if (next == offset) +return 0 ; +return next; +} + +/** +* tb_find_cap() - find a capability +* +* Return: Returns a positive offset if the capability was found and 0 if not. +* Returns an error code on failure. +*/ +int tb_find_cap (struct tb_port *port,enum tb_cfg_space space,enum tb_cap cap) +{ +u32 offset =1 ; +struct tb_cap_any header; +int res; +int retries =10 ; +while (retries--) { + res =tb_port_read (port, &header, space, offset,1 ); +if (res) { +/* Intel needs some help with linked lists. */ +if (space ==TB_CFG_PORT + && offset ==0xa + && port->config.type ==TB_TYPE_DP_HDMI_OUT ) { + offset =0x39 ; +continue ; + } +return res; + } +if (offset !=1 &&tb_cap (&header) == cap) +return offset; + offset =tb_cap_next (&header, offset); +if (!offset) +return 0 ; +continue ; + } +tb_port_WARN (port, +"could not find cap %#x in config space %d, last offset: %#x\n" , + cap, + space, + offset); +return -EIO ; +} +/* switch/port allocation & initialization */ /**
drivers/thunderbolt/tb.h +2 -0
@@ -144,4 +144,6 @@ 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); +int tb_find_cap (struct tb_port *port,enum tb_cfg_space space,u32 value); +#endif
Signature · 1 line
1.8.4.2
Andreas Noever
maintainerPATCH 7/12 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 on switch allocation.
--- drivers/thunderbolt/tb.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/tb.h | 1 + 2 files changed, 49 insertions(+), 1 deletion(-)
drivers/thunderbolt/tb.c +48 -1
@@ -192,6 +192,41 @@ int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, enum tb_cap cap)return -EIO ; } + +/* thunderbolt switch utility functions */ + +/** +* 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 ); +} +/* switch/port allocation & initialization */ /**
@@ -236,6 +271,7 @@ static void tb_switch_free(struct tb_switch *sw)static 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->cfg, route);if (upstream_port <0 )
@@ -296,6 +332,16 @@ static 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);
@@ -401,7 +447,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb)/** * thunderbolt_alloc_and_start() - setup the thunderbolt bus * -* Allocates a tb_cfg control channel and initializes the root switch. +* Allocates a tb_cfg control channel, initializes the root switch and enables +* plug events. * * Return: Returns NULL on error. */
drivers/thunderbolt/tb.h +1 -0
@@ -22,6 +22,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 */ bool invalid;/* unplugged, will go away */ };
Signature · 1 line
1.8.4.2
Andreas Noever
maintainerPATCH 8/12 thunderbolt: Scan for downstream switches
In reply to Andreas NoeverAdd utility methods tb_port_state and tb_wait_for_port. Add tb_scan_port which checks whether a port is connected and if so allocates a downstream switch.
--- drivers/thunderbolt/tb.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 17 +++++ 2 files changed, 177 insertions(+)
drivers/thunderbolt/tb.c +160 -0
@@ -84,6 +84,25 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) port->nfc_credits); } + +/** +* 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 u64 tb_downstream_route (struct tb_port *port) +{ +return tb_route (port->sw) + | ((u64 ) port->port << (port->sw->config.depth *8 )); +} + +static bool tb_is_upstream_port (struct tb_port *port) +{ +return port ==tb_upstream_port (port->sw); +} +static int tb_route_length (u64 route) {return (fls64 (route) +TB_ROUTE_SHIFT -1 ) /TB_ROUTE_SHIFT ;
@@ -192,6 +211,84 @@ int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, enum tb_cap cap)return -EIO ; } +/* thunderbolt port utility functions */ + +/** +* 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 a tb_port_state on success or an error code on failure. +*/ +static enum tb_port_state 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 +* +* Check if the port is connected but not up. If so we wait for some +* time to see whether it comes up. +* +* 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. +*/ +static int tb_wait_for_port (struct tb_port *port) +{ +int retries =10 ; +enum tb_port_state 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 ) { +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 ; +} +/* thunderbolt switch utility functions */
@@ -240,13 +337,25 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)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; + port->remote =NULL ; res =tb_port_read (port, &port->config,TB_CFG_PORT ,0 ,8 );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. */
@@ -259,6 +368,14 @@ static int tb_init_port(struct tb_switch *sw, u8 port_nr)*/ static void tb_switch_free (struct tb_switch *sw) { +int i; +/* port 0 is the switch itself and never has a remote */ +for (i =1 ; i <= sw->config.max_port_number; i++) { +if (tb_is_upstream_port (&sw->ports[i])) +continue ; +if (sw->ports[i].remote) +tb_switch_free (sw->ports[i].remote->sw); + }kfree (sw->ports);kfree (sw); }
@@ -349,6 +466,9 @@ err:return NULL ; } + +/* startup, enumeration, hot plug handling and shutdown */ +/** * reset_switch() - send TB_CFG_PKG_RESET and enable switch *
@@ -369,6 +489,44 @@ static int tb_switch_reset(struct tb *tb, u64 route)return tb_cfg_write (tb->cfg, ((u32 *) &header) +2 , route,0 ,2 ,2 ,2 ); } +static void tb_link_ports (struct tb_port *down,struct tb_port *up) +{ + down->remote = up; + up->remote = down; +} + +static void tb_scan_port (struct tb_port *port); + +static void tb_scan_ports (struct tb_switch *sw) +{ +int i; +for (i =1 ; i <= sw->config.max_port_number; i++) +tb_scan_port (&sw->ports[i]); +} + +/** +* tb_scan_port() - check for switches below port +* +* Checks whether port is connected. If so then we try to create a downstream +* switch and recursively scan its ports +*/ +static void tb_scan_port (struct tb_port *port) +{ +struct tb_switch *sw; +if (tb_is_upstream_port (port)) +return ; +if (port->config.type !=TB_TYPE_PORT ) +return ; +if (tb_wait_for_port (port) <=0 ) +return ; + + sw =tb_switch_alloc (port->sw->tb,tb_downstream_route (port)); +if (!sw) +return ; +tb_link_ports (port,tb_upstream_port (sw)); +tb_scan_ports (sw); +} +struct tb_hotplug_event {struct work_struct work;struct tb *tb;
@@ -487,6 +645,8 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi)if (!tb->root_switch) goto err_locked; +tb_scan_ports (tb->root_switch); +mutex_unlock (&tb->lock);return tb;
drivers/thunderbolt/tb.h +17 -0
@@ -32,6 +32,8 @@ struct tb_switch {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 */ bool invalid;/* unplugged, will go away */ };
@@ -54,6 +56,21 @@ struct tb {*/ }; +/** +* tb_upstream_port() - return the upstream port of a switch +* +* Every switch has an upstream port (for the root switch it is the NHI). +* +* During switch alloc/init tb_upstream_port()->remote may be NULL, even for +* non root switches (on the NHI port remote is always NULL). +* +* Return: Returns the upstream port of the switch. +*/ +static inline struct tb_port *tb_upstream_port (struct tb_switch *sw) +{ +return &sw->ports[sw->config.upstream_port_number]; +} +static inline u64 tb_route (struct tb_switch *sw) {return ((u64 ) sw->config.route_hi) <<32 | sw->config.route_lo;
Signature · 1 line
1.8.4.2
Andreas Noever
maintainerPATCH 9/12 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/tb.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-)
drivers/thunderbolt/tb.c +82 -1
@@ -108,6 +108,22 @@ static int tb_route_length(u64 route)return (fls64 (route) +TB_ROUTE_SHIFT -1 ) /TB_ROUTE_SHIFT ; } +static struct tb_switch *get_switch_at_route (struct tb_switch *sw,u64 route) +{ +u8 next_port = route &TB_PORT_MASK ; +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 ); +} + +/* thunderbolt capability lookup */
@@ -527,6 +543,27 @@ static void tb_scan_port(struct tb_port *port)tb_scan_ports (sw); } +/** +* tb_invalidate_below() - recursively invalidate all ports below a given port +*/ +static void tb_invalidate_below (struct tb_port *port) +{ +struct tb_switch *sw; +int i; +if (tb_is_upstream_port (port)) { +tb_port_WARN (port,"trying to invalidate an upstream port.\n" ); +return ; + } +if (!port->remote) +return ; + sw = port->remote->sw; +for (i =0 ; i <= sw->config.max_port_number; i++) { + sw->ports[i].invalid = true; +if (!tb_is_upstream_port (&sw->ports[i])) +tb_invalidate_below (&sw->ports[i]); + } +} +struct tb_hotplug_event {struct work_struct work;struct tb *tb;
@@ -544,10 +581,54 @@ 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->shutdown) goto out; -/* do nothing for now */ + + sw =get_switch_at_route (tb->root_switch, ev->route); +if (!sw) { +tb_WARN (tb, +"hotplug event for non existent switch %llx:%x (unplug: %d)\n" , + ev->route, + ev->port, + ev->unplug) + goto out; + } +if (sw->config.max_port_number < ev->port) { +tb_WARN (tb, +"hotplug event for 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_invalidate_below (port); +tb_switch_free (port->remote->sw); + port->remote =NULL ; + }else { +tb_port_info (port, +"got unplug event for disconnected port\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" ); + } out:mutex_unlock (&tb->lock);kfree (ev);
Signature · 1 line
1.8.4.2
Andreas Noever
maintainerPATCH 10/12 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/tb.c | 260 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 60 +++++++++++ 2 files changed, 320 insertions(+)
drivers/thunderbolt/tb.c +260 -0
@@ -84,6 +84,35 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) port->nfc_credits); } +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_downstream_route() - get route to downstream switch
@@ -305,6 +334,40 @@ static int tb_wait_for_port(struct tb_port *port)return 0 ; } +/** +* 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. +*/ +static 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. +*/ +static 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 ); +} +/* thunderbolt switch utility functions */
@@ -482,6 +545,203 @@ err:return NULL ; } +/* thunderbolt path handling */ + +/** +* tb_path_alloc() - allocate a thunderbolt path +* +* Return: Returns a tb_path on success or an error code 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 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->invalid) +return true; +if (path->hops[i].out_port->invalid) +return true; + } +return false; +}/* startup, enumeration, hot plug handling and shutdown */
drivers/thunderbolt/tb.h +60 -0
70 lines not on this page · raw message
Signature · 1 line
1.8.4.2
Andreas Noever
maintainerPATCH 11/12 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 | 27 ++++++ drivers/thunderbolt/tb.h | 2 + drivers/thunderbolt/tb_pci.c | 215 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb_pci.h | 29 ++++++ 5 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/tb_pci.c create mode 100644 drivers/thunderbolt/tb_pci.h
drivers/thunderbolt/Makefile +1 -1
@@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := dsl3510.o tb_cfg.o tb.o +thunderbolt-objs := dsl3510.o tb_cfg.o tb.o tb_pci.o
drivers/thunderbolt/tb.c +27 -0
@@ -11,6 +11,7 @@#include "dsl3510.h" #include "tb.h" #include "tb_regs.h" +#include "tb_pci.h" /* utility functions */
@@ -824,6 +825,19 @@ static void tb_invalidate_below(struct tb_port *port) } } +static void destroy_invalid_tunnels (struct tb *tb) +{ +struct tb_pci_tunnel *tunnel; +struct tb_pci_tunnel *n; +list_for_each_entry_safe (tunnel, n, &tb->tunnel_list, list) + { +if (tb_pci_is_invalid (tunnel)) { +tb_pci_deactivate (tunnel); +tb_pci_free (tunnel); + } + } +} +struct tb_hotplug_event {struct work_struct work;struct tb *tb;
@@ -877,6 +891,7 @@ static void tb_handle_hotplug(struct work_struct *work)if (port->remote) {tb_port_info (port,"unplugged\n" );tb_invalidate_below (port); +destroy_invalid_tunnels (tb);tb_switch_free (port->remote->sw); port->remote =NULL ; }else {
@@ -918,12 +933,23 @@ static void tb_schedule_hotplug_handler(void *data, u64 route, u8 port,* thunderbolt_shutdown_and_free() - shutdown everything * * Free all switches and the config channel. +* +* Only tb->lock and tb->tunnel_list must be initialized. If tb->tunnel_list +* is populated then we assume that tb->cfg is setup. */ void thunderbolt_shutdown_and_free (struct tb *tb) { +struct tb_pci_tunnel *tunnel; +struct tb_pci_tunnel *n; +mutex_lock (&tb->lock); tb->shutdown = true;/* signal tb_handle_hotplug to quit */ +list_for_each_entry_safe (tunnel, n, &tb->tunnel_list, list) { +tb_pci_deactivate (tunnel); +tb_pci_free (tunnel); + } +if (tb->root_switch)tb_switch_free (tb->root_switch); tb->root_switch =NULL ;
@@ -966,6 +992,7 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) tb->nhi = nhi;mutex_init (&tb->lock);mutex_lock (&tb->lock); +INIT_LIST_HEAD (&tb->tunnel_list); tb->wq =alloc_ordered_workqueue ("thunderbolt" ,0 );if (!tb->wq)
drivers/thunderbolt/tb.h +2 -0
@@ -104,10 +104,12 @@ struct tb {struct tb_cfg *cfg;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 shutdown;/* * Once this is set tb_handle_hotplug will exit (once it * can aquire lock at least once). Used to drain wq. */ + };/**
drivers/thunderbolt/tb_pci.c new file +215 -0
215 lines not on this page · raw message
drivers/thunderbolt/tb_pci.h new file +29 -0
@@ -0,0 +1,29 @@ +/* +* Thunderbolt PCIe tunnel +* +* Copyright (c) 2013 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); +void tb_pci_deactivate (struct tb_pci_tunnel *tunnel); +bool tb_pci_is_invalid (struct tb_pci_tunnel *tunnel); + +#endif +
Signature · 1 line
1.8.4.2
Andreas Noever
maintainerPATCH 12/12 thunderbolt: Scan and activate one PCI device
In reply to Andreas NoeverUse the tb_pci_* methods introduces in the last patch to activate a the first PCI device on every plugged in thunderbolt device.
--- drivers/thunderbolt/tb.c | 118 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 3 deletions(-)
drivers/thunderbolt/tb.c +115 -3
@@ -838,6 +838,109 @@ static void destroy_invalid_tunnels(struct tb *tb) } } +/** +* find_pci_up_port() - return the first PCIe up port on @sw +*/ +static struct tb_port *find_pci_up_port (struct tb_switch *sw) +{ +int i; +for (i =1 ; i <= sw->config.max_port_number; i++) +if (sw->ports[i].config.type ==TB_TYPE_PCIE_UP ) +return &sw->ports[i]; +return NULL ; +} + +/** +* find_unused_down_port() - return the first inactive PCIe down port on @sw +*/ +static struct tb_port *find_unused_down_port (struct tb_switch *sw) +{ +int i; +int cap; +int res; +int data; +for (i =1 ; i <= sw->config.max_port_number; i++) { +if (tb_is_upstream_port (&sw->ports[i])) +continue ; +if (sw->ports[i].config.type !=TB_TYPE_PCIE_DOWN ) +continue ; + cap =tb_find_cap (&sw->ports[i],TB_CFG_PORT ,TB_CAP_PCIE ); +if (cap <=0 ) +continue ; + res =tb_port_read (&sw->ports[i], &data,TB_CFG_PORT , cap,1 ); +if (res <0 ) +continue ; +if (data &0x80000000 ) +continue ; +return &sw->ports[i]; + } +return NULL ; +} + +/** +* tb_activate_pcie_devices() - scan for and activate PCIe devices +* +* This method is somewhat ad hoc. For now it only supports one device +* per port and only devices at depth 1. +*/ +static void tb_activate_pcie_devices (struct tb *tb) +{ +int i; +int cap; +u32 data; +struct tb_switch *sw; +struct tb_port *up_port; +struct tb_port *down_port; +struct tb_pci_tunnel *tunnel; +/* scan for pcie devices at depth 1*/ +for (i =1 ; i <= tb->root_switch->config.max_port_number; i++) { +if (tb_is_upstream_port (&tb->root_switch->ports[i])) +continue ; +if (tb->root_switch->ports[i].config.type !=TB_TYPE_PORT ) +continue ; +if (!tb->root_switch->ports[i].remote) +continue ; + sw = tb->root_switch->ports[i].remote->sw; + up_port =find_pci_up_port (sw); +if (!up_port) { +tb_sw_info (sw,"no PCIe devices found, aborting\n" ); +continue ; + } + +/* check whether port is already activated */ + cap =tb_find_cap (up_port,TB_CFG_PORT ,TB_CAP_PCIE ); +if (cap <=0 ) +continue ; +if (tb_port_read (up_port, &data,TB_CFG_PORT , cap,1 )) +continue ; +if (data &0x80000000 ) { +tb_port_info (up_port, +"PCIe port already activated, aborting\n" ); +continue ; + } + + down_port =find_unused_down_port (tb->root_switch); +if (!down_port) { +tb_port_info (up_port, +"All PCIe down ports are occupied, aborting\n" ); +continue ; + } + tunnel =tb_pci_alloc (tb, up_port, down_port); +if (!tunnel) { +tb_port_info (up_port, +"PCIe tunnel allocation failed, aborting\n" ); +continue ; + } + +if (tb_pci_activate (tunnel)) { +tb_port_info (up_port, +"PCIe tunnel activation failed, aborting\n" ); +tb_pci_free (tunnel); + } + + } +} +struct tb_hotplug_event {struct work_struct work;struct tb *tb;
@@ -901,8 +1004,16 @@ static void tb_handle_hotplug(struct work_struct *work) }else {tb_port_info (port,"hotplug: scanning\n" );tb_scan_port (port); -if (!port->remote) +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" ); + }else { +tb_sw_info (port->remote->sw, +"hotplug: activating pcie devices\n" ); +tb_activate_pcie_devices (tb); + } } out:mutex_unlock (&tb->lock);
@@ -972,8 +1083,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb)/** * thunderbolt_alloc_and_start() - setup the thunderbolt bus * -* Allocates a tb_cfg control channel, initializes the root switch and enables -* plug events. +* Allocates a tb_cfg control channel, initializes the root switch, enables +* plug events and activates pci devices. * * Return: Returns NULL on error. */
@@ -1014,6 +1125,7 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) goto err_locked;tb_scan_ports (tb->root_switch); +tb_activate_pcie_devices (tb);mutex_unlock (&tb->lock);return tb;
Signature · 1 line
1.8.4.2
On Fri, Nov 29, 2013 at 02:35:37AM +0100, Andreas Noever wrote:>
Quoted 5 lines
There are still a number of limitations:
(1) The system must be booted with acpi_osi=Darwin. Otherwise ACPI will cut
power to the controller.
(2) After suspend the controller is gone. I think that ACPI thinks that we are
Windows and cuts power, even with acpi_osi=Darwin.
There's a few ACPI methods involved here. The first is DTLK, which is called by the firmware on resume. It'll cut power to the chip. So, you need to be able to power it back up. However, you only want to power it up if there's a device connected. The XRIL method will return 1 if there's a device connected and 0 otherwise. If there's a device connected, call XRPE with an argument of 1 to power it back up.
That works fine if there's no hotplugging involved, but there is so things get a little more complicated. Apple provide an out of band mechanism for receiving notifications on the device. Call the _GPE method and install a gpe handler for the value that you get back. This will be called on every device plug or unplug (possibly just plug? It's been a while since I tested). Call XRIL to confirm that there's a device attached and then call XRPE to power it up.
At least, that's my recollection. There's also the SXIO, SXIL, SXLV and SXFP methods that provide some kind of power management, but I'm not sure how those fit in.
If you want to confirm that you're supposed to be using these methods, call _DSM on the device with a UUID of C6B7B5A0-1813-1C44-B0C9-FE695EAF949B (see DTGP in the DSDT), arg1 of 1, arg2 of 0. If it's an Apple you'll get back return value of 1 and a buffer containing the string "power-save". If it's not an Apple, or if the device doesn't support power saving, you'll get back a zero or an error that the method wasn't found.
Signature · 1 line
Matthew Garrett | [email protected]
Actually, turns out there's a much easier way. Can you try this patch? I see the Thunderbolt controller after resume, although it doesn't seem to be in a working state.
commit 102547d63e2cbbda42a25f650df9a33cf929a385
Author: Matthew Garrett <[email protected]> Date: Mon Mar 3 18:49:28 2014 -0500
ACPI: Support _OSI("Darwin") correctly
Apple hardware queries _OSI("Darwin") in order to determine whether the system is running OS X, and changes firmware behaviour based on the answer. The most obvious difference in behaviour is that Thunderbolt hardware is forcibly powered down unless the system is running OS X. The obvious solution would be to simply add Darwin to the list of supported _OSI strings, but this causes problems.
Recent Apple hardware includes two separate methods for checking _OSI strings. The first will check whether Darwin is supported, and if so will exit. The second will check whether Darwin is supported, but will then continue to check for further operating systems. If a further operating system is found then later firmware code will assume that the OS is not OS X. This results in the unfortunate situation where the Thunderbolt controller is available at boot time but remains powered down after suspend.
The easiest way to handle this is to special-case it in the Linux-specific OSI handling code. If we see Darwin, we should answer true and then disable all other _OSI vendor strings.
drivers/acpi/osl.c +10 -0
@@ -156,6 +156,16 @@ static u32 acpi_osi_handler(acpi_string interface, u32 supported) osi_linux.dmi ? " via DMI" : ""); } +if (!strcmp ("Darwin" , interface)) { +/* +* Apple firmware will behave poorly if it receives positive +* answers to "Darwin" and any other OS. Respond positively +* to Darwin and then disable all other vendor strings. +*/ +acpi_update_interfaces (ACPI_DISABLE_ALL_VENDOR_STRINGS ); + supported =ACPI_UINT32_MAX ; + } +return supported; }
Signature · 1 line
Matthew Garrett | [email protected]
Andreas Noever
maintainerQuoted 55 lines · Matthew Garrett
Actually, turns out there's a much easier way. Can you try this patch? I see the Thunderbolt controller after resume, although it doesn't seem to be in a working state.
commit
102547d63e2cbbda42a25f650df9a33cf929a385Author: Matthew Garrett <[email protected]> Date: Mon Mar 3 18:49:28 2014 -0500ACPI: Support
_OSI("Darwin")correctlyApple hardware queries
_OSI("Darwin")in order to determine whether the system is running OS X, and changes firmware behaviour based on the answer. The most obvious difference in behaviour is that Thunderbolt hardware is forcibly powered down unless the system is running OS X. The obvious solution would be to simply add Darwin to the list of supported_OSIstrings, but this causes problems.Recent Apple hardware includes two separate methods for checking
_OSIstrings. The first will check whether Darwin is supported, and if so will exit. The second will check whether Darwin is supported, but will then continue to check for further operating systems. If a further operating system is found then later firmware code will assume that the OS is not OS X. This results in the unfortunate situation where the Thunderbolt controller is available at boot time but remains powered down after suspend.The easiest way to handle this is to special-case it in the Linux-specific OSI handling code. If we see Darwin, we should answer true and then disable all other
_OSIvendor strings.Signed-off-by Matthew Garrett <[email protected]>drivers/acpi/osl.c +10
-1@@ -156,6 +156,16 @@ static u32 acpi_osi_handler(acpi_string interface, u32 supported) osi_linux.dmi ? " via DMI" : ""); } +if (!strcmp ("Darwin" , interface)) { +/* +* Apple firmware will behave poorly if it receives positive +* answers to "Darwin" and any other OS. Respond positively +* to Darwin and then disable all other vendor strings. +*/ +acpi_update_interfaces (ACPI_DISABLE_ALL_VENDOR_STRINGS ); + supported =ACPI_UINT32_MAX ; + } +return supported; } --
Quoted 1 line
Matthew Garrett | [email protected]
I belive that the patch has the same effect as passing
acpi_osi=! acpi_osi=Darwin
to the kernel. The problem with that approach is that it changes the firmware behaviour quite a lot. In particular it prevents Linux from taking over pci hotplug control:
acpi PNP0A08:00: _OSC: OS supports [ExtendedConfig ASPM ClockPM Segments MSI] \_SB_.PCI0:_OSC invalid UUID_OSC request data:1 1f 0
acpi PNP0A08:00: _OSC failed (AE_ERROR); disabling ASPM
Booting with just acpi_osi=Darwin (or without acpi_osi) gives:
acpi PNP0A08:00: _OSC: OS supports [ExtendedConfig ASPM ClockPM Segments MSI] acpi PNP0A08:00: _OSC: platform does not support [PME]
acpi PNP0A08:00: _OSC: OS now controls [PCIeHotplug AER PCIeCapability]
The end result is that that pci hotplog events are not handled. If I unplug the Thunderbolt Ethernet adapter the device is not removed from lspci.
I would prefer to find a solution that boots without acpi_osi=Darwin as seems to trigger quite a lot of ACPI code. My current approach is to inject a custom OSDW method somewhere into the NHI namespace and to replace _PTS and _WAK from my driver. I can then wake the controller with the XRPE method. The last problem is that the PCI code does not allocate enough (or any) bus numbers below the hotplug ports. I'm trying to add some quirks to it but the code is not really made for that...
Andreas
Quoted 6 lines · Andreas Noever
I belive that the patch has the same effect as passing
acpi_osi=! acpi_osi=Darwin
to the kernel. The problem with that approach is that it changes the firmware behaviour quite a lot. In particular it prevents Linux from taking over pci hotplug control:
There's not really any way around this. The method to power up the chip will refuse to run unless the system claims Darwin and nothing else.
Quoted 8 lines
I would prefer to find a solution that boots without acpi_osi=Darwin as seems to trigger quite a lot of ACPI code. My current approach is to inject a custom OSDW method somewhere into the NHI namespace and to replace
_PTSand_WAKfrom my driver. I can then wake the controller with the XRPE method. The last problem is that the PCI code does not allocate enough (or any) bus numbers below the hotplug ports. I'm trying to add some quirks to it but the code is not really made for that...
I don't think that's a workable approach - any change in the firmware implementation could break it. If we're going to insert quirks then I think it makes more sense to do it in the PCI layer rather than injecting things into ACPI.
Signature · 1 line
Matthew Garrett | [email protected]
Ok, can you try this one?
drivers/acpi/acpica/utosi.c +1 -0
@@ -62,6 +62,7 @@ ACPI_MODULE_NAME("utosi")static struct acpi_interface_info acpi_default_supported_interfaces[] = {/* Operating System Vendor Strings */ + {"Darwin" ,NULL ,0 ,ACPI_OSI_DARWIN },/* OS X */ {"Windows 2000" ,NULL ,0 ,ACPI_OSI_WIN_2000 },/* Windows 2000 */ {"Windows 2001" ,NULL ,0 ,ACPI_OSI_WIN_XP },/* Windows XP */ {"Windows 2001 SP1" ,NULL ,0 ,ACPI_OSI_WIN_XP_SP1 },/* Windows XP SP1 */
drivers/acpi/osl.c +10 -0
@@ -152,6 +152,16 @@ static u32 acpi_osi_handler(acpi_string interface, u32 supported) osi_linux.dmi ? " via DMI" : ""); } +if (!strcmp ("Darwin" , interface)) { +/* +* Apple firmware will behave poorly if it receives positive +* answers to "Darwin" and any other OS. Respond positively +* to Darwin and then disable all other vendor strings. +*/ +acpi_update_interfaces (ACPI_DISABLE_ALL_VENDOR_STRINGS ); + supported =ACPI_UINT32_MAX ; + } +return supported; }
drivers/acpi/pci_root.c +11 -0
@@ -432,6 +432,17 @@ static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm,acpi_handle handle = device->handle;/* +* Apple always return failure on _OSC calls when _OSI("Darwin") has +* been called successfully. We know the feature set supported by the +* platform, so avoid calling _OSC at all +*/ + +if (acpi_gbl_osi_data ==ACPI_OSI_DARWIN ) { + root->osc_control_set = ~OSC_PCI_EXPRESS_PME_CONTROL ; +return ; + } + +/* * All supported architectures that use ACPI have support for * PCI domains, so we indicate this in _OSC support capabilities. */
include/acpi/actypes.h +13 -12
@@ -1210,17 +1210,18 @@ struct acpi_memory_list {#define ACPI_ENABLE_ALL_FEATURE_STRINGS (ACPI_ENABLE_INTERFACES | ACPI_FEATURE_STRINGS)#define ACPI_ENABLE_ALL_STRINGS (ACPI_ENABLE_INTERFACES | ACPI_VENDOR_STRINGS | ACPI_FEATURE_STRINGS) -#define ACPI_OSI_WIN_2000 0x01 -#define ACPI_OSI_WIN_XP 0x02 -#define ACPI_OSI_WIN_XP_SP1 0x03 -#define ACPI_OSI_WINSRV_2003 0x04 -#define ACPI_OSI_WIN_XP_SP2 0x05 -#define ACPI_OSI_WINSRV_2003_SP1 0x06 -#define ACPI_OSI_WIN_VISTA 0x07 -#define ACPI_OSI_WINSRV_2008 0x08 -#define ACPI_OSI_WIN_VISTA_SP1 0x09 -#define ACPI_OSI_WIN_VISTA_SP2 0x0A -#define ACPI_OSI_WIN_7 0x0B -#define ACPI_OSI_WIN_8 0x0C +#define ACPI_OSI_DARWIN 0x01 +#define ACPI_OSI_WIN_2000 0x02 +#define ACPI_OSI_WIN_XP 0x03 +#define ACPI_OSI_WIN_XP_SP1 0x04 +#define ACPI_OSI_WINSRV_2003 0x05 +#define ACPI_OSI_WIN_XP_SP2 0x06 +#define ACPI_OSI_WINSRV_2003_SP1 0x07 +#define ACPI_OSI_WIN_VISTA 0x08 +#define ACPI_OSI_WINSRV_2008 0x09 +#define ACPI_OSI_WIN_VISTA_SP1 0x0A +#define ACPI_OSI_WIN_VISTA_SP2 0x0B +#define ACPI_OSI_WIN_7 0x0C +#define ACPI_OSI_WIN_8 0x0D#endif /* __ACTYPES_H__ */
Signature · 1 line
Matthew Garrett | [email protected]
Andreas Noever
maintainerQuoted 2 lines · Matthew Garrett
Ok, can you try this one?
That seems to do it. I was afraid that setting OSDW globally would affect other parts (USB?) But that does not seem to be the case.
What is the reason for clearing the PME flag?
Andreas
Quoted 2 lines · Andreas Noever
That seems to do it. I was afraid that setting OSDW globally would affect other parts (USB?) But that does not seem to be the case.
I think it results in the USB ports coming up in XHCI mode by default, but we can handle that. There's one other thing - the battery switches from being a control method battery to being an SBS one, and Apple's implementation seems to break our driver. I've got a trivial patch that makes it work (attached).
Quoted 1 line
What is the reason for clearing the PME flag?
It looks like the PME flag gets cleared on the _OSC route in non-Darwin mode? I'll go back and check that to make sure.
Signature · 1 line
Matthew Garrett | [email protected]
Quoted 1 line
From
2a8ffff11f91aad146916ca14c768ebc407865aaMon Sep 17 00:00:00 2001
From: Matthew Garrett <[email protected]>
Date: Mon, 10 Mar 2014 14:44:55 -0400
Subject: [PATCH 21/28] ACPI: Don't re-select SBS battery if it's already
selected
The existing SBS code explicitly sets the selected battery in the SBS manager regardless of whether the battery in question is already selected. This causes bus timeouts on Apple hardware. Check for this case and avoid it.
--- drivers/acpi/sbs.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-)
drivers/acpi/sbs.c +13 -5
@@ -470,17 +470,25 @@ static struct device_attribute alarm_attr = {static int acpi_battery_read (struct acpi_battery *battery) {int result =0 , saved_present = battery->present; -u16 state; +u16 state, selected, desired;if (battery->sbs->manager_present) { result =acpi_smbus_read (battery->sbs->hc,SMBUS_READ_WORD ,ACPI_SBS_MANAGER ,0x01 , (u8 *)&state);if (!result) battery->present = state & (1 << battery->id); - state &=0x0fff ; - state |=1 << (battery->id +12 ); -acpi_smbus_write (battery->sbs->hc,SMBUS_WRITE_WORD , -ACPI_SBS_MANAGER ,0x01 , (u8 *)&state,2 ); +/* +* Don't switch battery if the correct one is already selected +*/ + selected = state &0xf000 ; + desired =1 << (battery->id +12 ); +if (selected != desired) { + state &=0x0fff ; + state |= desired; +acpi_smbus_write (battery->sbs->hc,SMBUS_WRITE_WORD , +ACPI_SBS_MANAGER ,0x01 , + (u8 *)&state,2 ); + } }else if (battery->id ==0 ) battery->present =1 ;if (result || !battery->present)
Signature · 1 line
1.8.5.3