From patchwork Fri Apr 11 00:24:55 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andreas Noever X-Patchwork-Id: 3965731 X-Patchwork-Delegate: bhelgaas@google.com Return-Path: X-Original-To: patchwork-linux-pci@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id C9508BFF02 for ; Fri, 11 Apr 2014 00:30:24 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id D064220826 for ; Fri, 11 Apr 2014 00:30:23 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id E884320824 for ; Fri, 11 Apr 2014 00:30:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1759305AbaDKA3b (ORCPT ); Thu, 10 Apr 2014 20:29:31 -0400 Received: from mail-ee0-f41.google.com ([74.125.83.41]:48115 "EHLO mail-ee0-f41.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1422768AbaDKAZu (ORCPT ); Thu, 10 Apr 2014 20:25:50 -0400 Received: by mail-ee0-f41.google.com with SMTP id t10so3563970eei.14 for ; Thu, 10 Apr 2014 17:25:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=1qXQLjcAcH6XFcx56y+C9yKjWt/LRXg+65HeT+tzoTM=; b=jL2P2g+Pkb8VjZTVxMrSjqqez5e5LjWTm5fxse97l4+h+ngplz3Re1cJHSTzan8dfI 5Ho7uKouKwz2/eMRHGLPB6ecsdII9SqC+7pHclMXEib4zmwmPLqmJUTLqdojT92BL8Uw oKVhCAUA2whiQh78zV89ojYyusIG65hRvsFEq+OrwN1wJaKlc4ulBVb7bik8G9aUyqve 1zHTv7hvKKPITajvOsOtk+dzazC6IKmcRvIC2vdXoCTfeW9VeHl/wPRq3yvW2uvVVFkI uwzKbw+MWcFnMxSramnLo8xgRh4lWW7Q/NxMq6cUMicKnlgy06XEu+FvzcrohhtES/m0 kLYQ== X-Received: by 10.14.203.199 with SMTP id f47mr6118093eeo.75.1397175949282; Thu, 10 Apr 2014 17:25:49 -0700 (PDT) Received: from localhost.localdomain (77-58-151-250.dclient.hispeed.ch. [77.58.151.250]) by mx.google.com with ESMTPSA id g3sm13535401eet.35.2014.04.10.17.25.48 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Thu, 10 Apr 2014 17:25:48 -0700 (PDT) From: Andreas Noever To: linux-kernel@vger.kernel.org, Matthew Garrett Cc: Daniel J Blueman , Bjorn Helgaas , linux-pci@vger.kernel.org, Andreas Noever Subject: [PATCH v2 08/14] thunderbolt: Scan for downstream switches Date: Fri, 11 Apr 2014 02:24:55 +0200 Message-Id: <1397175901-4023-9-git-send-email-andreas.noever@gmail.com> X-Mailer: git-send-email 1.9.2 In-Reply-To: <1397175901-4023-1-git-send-email-andreas.noever@gmail.com> References: <1397175901-4023-1-git-send-email-andreas.noever@gmail.com> Sender: linux-pci-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pci@vger.kernel.org X-Spam-Status: No, score=-7.3 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add utility methods tb_port_state and tb_wait_for_port. Add tb_scan_switch which recursively checks for downstream switches. Signed-off-by: Andreas Noever --- drivers/thunderbolt/switch.c | 97 ++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.c | 44 ++++++++++++++++++++ drivers/thunderbolt/tb.h | 16 ++++++++ 3 files changed, 157 insertions(+) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 21457cc..279b9c5 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -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. */ diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index f1b6100..3b716fd 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -11,6 +11,47 @@ #include "tb.h" #include "tb_regs.h" + +/* enumeration & hot plug handling */ + + +static void tb_scan_port(struct tb_port *port); + +/** + * tb_scan_switch() - scan for and initialize downstream switches + */ +static void tb_scan_switch(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 and initialize switches below port + */ +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, false) <= 0) + return; + if (port->remote) { + tb_port_WARN(port, "port already has a remote!\n"); + return; + } + sw = tb_switch_alloc(port->sw->tb, tb_downstream_route(port)); + if (!sw) + return; + port->remote = tb_upstream_port(sw); + tb_upstream_port(sw)->remote = port; + tb_scan_switch(sw); +} + + /* hotplug handling */ struct tb_hotplug_event { @@ -134,6 +175,9 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) if (!tb->root_switch) goto err_locked; + /* Full scan to discover devices added before the driver was loaded. */ + tb_scan_switch(tb->root_switch); + /* Allow tb_handle_hotplug to progress events */ tb->hotplug_active = true; mutex_unlock(&tb->lock); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index feb6c7a..7e7b3fe 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -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