1e2f8db50SDan Williams /* 2e2f8db50SDan Williams * This file is provided under a dual BSD/GPLv2 license. When using or 3e2f8db50SDan Williams * redistributing this file, you may do so under either license. 4e2f8db50SDan Williams * 5e2f8db50SDan Williams * GPL LICENSE SUMMARY 6e2f8db50SDan Williams * 7e2f8db50SDan Williams * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. 8e2f8db50SDan Williams * 9e2f8db50SDan Williams * This program is free software; you can redistribute it and/or modify 10e2f8db50SDan Williams * it under the terms of version 2 of the GNU General Public License as 11e2f8db50SDan Williams * published by the Free Software Foundation. 12e2f8db50SDan Williams * 13e2f8db50SDan Williams * This program is distributed in the hope that it will be useful, but 14e2f8db50SDan Williams * WITHOUT ANY WARRANTY; without even the implied warranty of 15e2f8db50SDan Williams * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16e2f8db50SDan Williams * General Public License for more details. 17e2f8db50SDan Williams * 18e2f8db50SDan Williams * You should have received a copy of the GNU General Public License 19e2f8db50SDan Williams * along with this program; if not, write to the Free Software 20e2f8db50SDan Williams * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. 21e2f8db50SDan Williams * The full GNU General Public License is included in this distribution 22e2f8db50SDan Williams * in the file called LICENSE.GPL. 23e2f8db50SDan Williams * 24e2f8db50SDan Williams * BSD LICENSE 25e2f8db50SDan Williams * 26e2f8db50SDan Williams * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. 27e2f8db50SDan Williams * All rights reserved. 28e2f8db50SDan Williams * 29e2f8db50SDan Williams * Redistribution and use in source and binary forms, with or without 30e2f8db50SDan Williams * modification, are permitted provided that the following conditions 31e2f8db50SDan Williams * are met: 32e2f8db50SDan Williams * 33e2f8db50SDan Williams * * Redistributions of source code must retain the above copyright 34e2f8db50SDan Williams * notice, this list of conditions and the following disclaimer. 35e2f8db50SDan Williams * * Redistributions in binary form must reproduce the above copyright 36e2f8db50SDan Williams * notice, this list of conditions and the following disclaimer in 37e2f8db50SDan Williams * the documentation and/or other materials provided with the 38e2f8db50SDan Williams * distribution. 39e2f8db50SDan Williams * * Neither the name of Intel Corporation nor the names of its 40e2f8db50SDan Williams * contributors may be used to endorse or promote products derived 41e2f8db50SDan Williams * from this software without specific prior written permission. 42e2f8db50SDan Williams * 43e2f8db50SDan Williams * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 44e2f8db50SDan Williams * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 45e2f8db50SDan Williams * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 46e2f8db50SDan Williams * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 47e2f8db50SDan Williams * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 48e2f8db50SDan Williams * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 49e2f8db50SDan Williams * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 50e2f8db50SDan Williams * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 51e2f8db50SDan Williams * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 52e2f8db50SDan Williams * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 53e2f8db50SDan Williams * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54e2f8db50SDan Williams */ 55e2f8db50SDan Williams 56e2f8db50SDan Williams #include "host.h" 57e2f8db50SDan Williams 58e2f8db50SDan Williams #define SCIC_SDS_MPC_RECONFIGURATION_TIMEOUT (10) 59e2f8db50SDan Williams #define SCIC_SDS_APC_RECONFIGURATION_TIMEOUT (10) 6050a92d93SDan Williams #define SCIC_SDS_APC_WAIT_LINK_UP_NOTIFICATION (1000) 61e2f8db50SDan Williams 62e2f8db50SDan Williams enum SCIC_SDS_APC_ACTIVITY { 63e2f8db50SDan Williams SCIC_SDS_APC_SKIP_PHY, 64e2f8db50SDan Williams SCIC_SDS_APC_ADD_PHY, 65e2f8db50SDan Williams SCIC_SDS_APC_START_TIMER, 66e2f8db50SDan Williams 67e2f8db50SDan Williams SCIC_SDS_APC_ACTIVITY_MAX 68e2f8db50SDan Williams }; 69e2f8db50SDan Williams 70e2f8db50SDan Williams /* 71e2f8db50SDan Williams * ****************************************************************************** 72e2f8db50SDan Williams * General port configuration agent routines 73e2f8db50SDan Williams * ****************************************************************************** */ 74e2f8db50SDan Williams 75e2f8db50SDan Williams /** 76e2f8db50SDan Williams * 77e2f8db50SDan Williams * @address_one: A SAS Address to be compared. 78e2f8db50SDan Williams * @address_two: A SAS Address to be compared. 79e2f8db50SDan Williams * 80e2f8db50SDan Williams * Compare the two SAS Address and if SAS Address One is greater than SAS 81e2f8db50SDan Williams * Address Two then return > 0 else if SAS Address One is less than SAS Address 82e2f8db50SDan Williams * Two return < 0 Otherwise they are the same return 0 A signed value of x > 0 83e2f8db50SDan Williams * > y where x is returned for Address One > Address Two y is returned for 84e2f8db50SDan Williams * Address One < Address Two 0 is returned ofr Address One = Address Two 85e2f8db50SDan Williams */ 86e2f8db50SDan Williams static s32 sci_sas_address_compare( 87e2f8db50SDan Williams struct sci_sas_address address_one, 88e2f8db50SDan Williams struct sci_sas_address address_two) 89e2f8db50SDan Williams { 90e2f8db50SDan Williams if (address_one.high > address_two.high) { 91e2f8db50SDan Williams return 1; 92e2f8db50SDan Williams } else if (address_one.high < address_two.high) { 93e2f8db50SDan Williams return -1; 94e2f8db50SDan Williams } else if (address_one.low > address_two.low) { 95e2f8db50SDan Williams return 1; 96e2f8db50SDan Williams } else if (address_one.low < address_two.low) { 97e2f8db50SDan Williams return -1; 98e2f8db50SDan Williams } 99e2f8db50SDan Williams 100e2f8db50SDan Williams /* The two SAS Address must be identical */ 101e2f8db50SDan Williams return 0; 102e2f8db50SDan Williams } 103e2f8db50SDan Williams 104e2f8db50SDan Williams /** 105e2f8db50SDan Williams * 106e2f8db50SDan Williams * @controller: The controller object used for the port search. 107e2f8db50SDan Williams * @phy: The phy object to match. 108e2f8db50SDan Williams * 109e2f8db50SDan Williams * This routine will find a matching port for the phy. This means that the 110e2f8db50SDan Williams * port and phy both have the same broadcast sas address and same received sas 111e2f8db50SDan Williams * address. The port address or the NULL if there is no matching 112e2f8db50SDan Williams * port. port address if the port can be found to match the phy. 113e2f8db50SDan Williams * NULL if there is no matching port for the phy. 114e2f8db50SDan Williams */ 11589a7301fSDan Williams static struct isci_port *sci_port_configuration_agent_find_port( 116d9dcb4baSDan Williams struct isci_host *ihost, 11785280955SDan Williams struct isci_phy *iphy) 118e2f8db50SDan Williams { 119e2f8db50SDan Williams u8 i; 120e2f8db50SDan Williams struct sci_sas_address port_sas_address; 121e2f8db50SDan Williams struct sci_sas_address port_attached_device_address; 122e2f8db50SDan Williams struct sci_sas_address phy_sas_address; 123e2f8db50SDan Williams struct sci_sas_address phy_attached_device_address; 124e2f8db50SDan Williams 125e2f8db50SDan Williams /* 126e2f8db50SDan Williams * Since this phy can be a member of a wide port check to see if one or 127e2f8db50SDan Williams * more phys match the sent and received SAS address as this phy in which 128e2f8db50SDan Williams * case it should participate in the same port. 129e2f8db50SDan Williams */ 13089a7301fSDan Williams sci_phy_get_sas_address(iphy, &phy_sas_address); 13189a7301fSDan Williams sci_phy_get_attached_sas_address(iphy, &phy_attached_device_address); 132e2f8db50SDan Williams 133d9dcb4baSDan Williams for (i = 0; i < ihost->logical_port_entries; i++) { 134ffe191c9SDan Williams struct isci_port *iport = &ihost->ports[i]; 135e2f8db50SDan Williams 13689a7301fSDan Williams sci_port_get_sas_address(iport, &port_sas_address); 13789a7301fSDan Williams sci_port_get_attached_sas_address(iport, &port_attached_device_address); 138e2f8db50SDan Williams 139e2f8db50SDan Williams if (sci_sas_address_compare(port_sas_address, phy_sas_address) == 0 && 140e2f8db50SDan Williams sci_sas_address_compare(port_attached_device_address, phy_attached_device_address) == 0) 141ffe191c9SDan Williams return iport; 142e2f8db50SDan Williams } 143e2f8db50SDan Williams 144e2f8db50SDan Williams return NULL; 145e2f8db50SDan Williams } 146e2f8db50SDan Williams 147e2f8db50SDan Williams /** 148e2f8db50SDan Williams * 149e2f8db50SDan Williams * @controller: This is the controller object that contains the port agent 150e2f8db50SDan Williams * @port_agent: This is the port configruation agent for the controller. 151e2f8db50SDan Williams * 152e2f8db50SDan Williams * This routine will validate the port configuration is correct for the SCU 153e2f8db50SDan Williams * hardware. The SCU hardware allows for port configurations as follows. LP0 154e2f8db50SDan Williams * -> (PE0), (PE0, PE1), (PE0, PE1, PE2, PE3) LP1 -> (PE1) LP2 -> (PE2), (PE2, 155e2f8db50SDan Williams * PE3) LP3 -> (PE3) enum sci_status SCI_SUCCESS the port configuration is valid for 156e2f8db50SDan Williams * this port configuration agent. SCI_FAILURE_UNSUPPORTED_PORT_CONFIGURATION 157e2f8db50SDan Williams * the port configuration is not valid for this port configuration agent. 158e2f8db50SDan Williams */ 15989a7301fSDan Williams static enum sci_status sci_port_configuration_agent_validate_ports( 160d9dcb4baSDan Williams struct isci_host *ihost, 16189a7301fSDan Williams struct sci_port_configuration_agent *port_agent) 162e2f8db50SDan Williams { 163e2f8db50SDan Williams struct sci_sas_address first_address; 164e2f8db50SDan Williams struct sci_sas_address second_address; 165e2f8db50SDan Williams 166e2f8db50SDan Williams /* 167e2f8db50SDan Williams * Sanity check the max ranges for all the phys the max index 168e2f8db50SDan Williams * is always equal to the port range index */ 169e2f8db50SDan Williams if (port_agent->phy_valid_port_range[0].max_index != 0 || 170e2f8db50SDan Williams port_agent->phy_valid_port_range[1].max_index != 1 || 171e2f8db50SDan Williams port_agent->phy_valid_port_range[2].max_index != 2 || 172e2f8db50SDan Williams port_agent->phy_valid_port_range[3].max_index != 3) 173e2f8db50SDan Williams return SCI_FAILURE_UNSUPPORTED_PORT_CONFIGURATION; 174e2f8db50SDan Williams 175e2f8db50SDan Williams /* 176e2f8db50SDan Williams * This is a request to configure a single x4 port or at least attempt 177e2f8db50SDan Williams * to make all the phys into a single port */ 178e2f8db50SDan Williams if (port_agent->phy_valid_port_range[0].min_index == 0 && 179e2f8db50SDan Williams port_agent->phy_valid_port_range[1].min_index == 0 && 180e2f8db50SDan Williams port_agent->phy_valid_port_range[2].min_index == 0 && 181e2f8db50SDan Williams port_agent->phy_valid_port_range[3].min_index == 0) 182e2f8db50SDan Williams return SCI_SUCCESS; 183e2f8db50SDan Williams 184e2f8db50SDan Williams /* 185e2f8db50SDan Williams * This is a degenerate case where phy 1 and phy 2 are assigned 186e2f8db50SDan Williams * to the same port this is explicitly disallowed by the hardware 187e2f8db50SDan Williams * unless they are part of the same x4 port and this condition was 188e2f8db50SDan Williams * already checked above. */ 189e2f8db50SDan Williams if (port_agent->phy_valid_port_range[2].min_index == 1) { 190e2f8db50SDan Williams return SCI_FAILURE_UNSUPPORTED_PORT_CONFIGURATION; 191e2f8db50SDan Williams } 192e2f8db50SDan Williams 193e2f8db50SDan Williams /* 194e2f8db50SDan Williams * PE0 and PE3 can never have the same SAS Address unless they 195e2f8db50SDan Williams * are part of the same x4 wide port and we have already checked 196e2f8db50SDan Williams * for this condition. */ 19789a7301fSDan Williams sci_phy_get_sas_address(&ihost->phys[0], &first_address); 19889a7301fSDan Williams sci_phy_get_sas_address(&ihost->phys[3], &second_address); 199e2f8db50SDan Williams 200e2f8db50SDan Williams if (sci_sas_address_compare(first_address, second_address) == 0) { 201e2f8db50SDan Williams return SCI_FAILURE_UNSUPPORTED_PORT_CONFIGURATION; 202e2f8db50SDan Williams } 203e2f8db50SDan Williams 204e2f8db50SDan Williams /* 205e2f8db50SDan Williams * PE0 and PE1 are configured into a 2x1 ports make sure that the 206e2f8db50SDan Williams * SAS Address for PE0 and PE2 are different since they can not be 207e2f8db50SDan Williams * part of the same port. */ 208e2f8db50SDan Williams if (port_agent->phy_valid_port_range[0].min_index == 0 && 209e2f8db50SDan Williams port_agent->phy_valid_port_range[1].min_index == 1) { 21089a7301fSDan Williams sci_phy_get_sas_address(&ihost->phys[0], &first_address); 21189a7301fSDan Williams sci_phy_get_sas_address(&ihost->phys[2], &second_address); 212e2f8db50SDan Williams 213e2f8db50SDan Williams if (sci_sas_address_compare(first_address, second_address) == 0) { 214e2f8db50SDan Williams return SCI_FAILURE_UNSUPPORTED_PORT_CONFIGURATION; 215e2f8db50SDan Williams } 216e2f8db50SDan Williams } 217e2f8db50SDan Williams 218e2f8db50SDan Williams /* 219e2f8db50SDan Williams * PE2 and PE3 are configured into a 2x1 ports make sure that the 220e2f8db50SDan Williams * SAS Address for PE1 and PE3 are different since they can not be 221e2f8db50SDan Williams * part of the same port. */ 222e2f8db50SDan Williams if (port_agent->phy_valid_port_range[2].min_index == 2 && 223e2f8db50SDan Williams port_agent->phy_valid_port_range[3].min_index == 3) { 22489a7301fSDan Williams sci_phy_get_sas_address(&ihost->phys[1], &first_address); 22589a7301fSDan Williams sci_phy_get_sas_address(&ihost->phys[3], &second_address); 226e2f8db50SDan Williams 227e2f8db50SDan Williams if (sci_sas_address_compare(first_address, second_address) == 0) { 228e2f8db50SDan Williams return SCI_FAILURE_UNSUPPORTED_PORT_CONFIGURATION; 229e2f8db50SDan Williams } 230e2f8db50SDan Williams } 231e2f8db50SDan Williams 232e2f8db50SDan Williams return SCI_SUCCESS; 233e2f8db50SDan Williams } 234e2f8db50SDan Williams 235e2f8db50SDan Williams /* 236e2f8db50SDan Williams * ****************************************************************************** 237e2f8db50SDan Williams * Manual port configuration agent routines 238e2f8db50SDan Williams * ****************************************************************************** */ 239e2f8db50SDan Williams 240d9dcb4baSDan Williams /* verify all of the phys in the same port are using the same SAS address */ 241d9dcb4baSDan Williams static enum sci_status 24289a7301fSDan Williams sci_mpc_agent_validate_phy_configuration(struct isci_host *ihost, 24389a7301fSDan Williams struct sci_port_configuration_agent *port_agent) 244e2f8db50SDan Williams { 245e2f8db50SDan Williams u32 phy_mask; 246e2f8db50SDan Williams u32 assigned_phy_mask; 247e2f8db50SDan Williams struct sci_sas_address sas_address; 248e2f8db50SDan Williams struct sci_sas_address phy_assigned_address; 249e2f8db50SDan Williams u8 port_index; 250e2f8db50SDan Williams u8 phy_index; 251e2f8db50SDan Williams 252e2f8db50SDan Williams assigned_phy_mask = 0; 253e2f8db50SDan Williams sas_address.high = 0; 254e2f8db50SDan Williams sas_address.low = 0; 255e2f8db50SDan Williams 256e2f8db50SDan Williams for (port_index = 0; port_index < SCI_MAX_PORTS; port_index++) { 25789a7301fSDan Williams phy_mask = ihost->oem_parameters.ports[port_index].phy_mask; 258e2f8db50SDan Williams 259e2f8db50SDan Williams if (!phy_mask) 260e2f8db50SDan Williams continue; 261e2f8db50SDan Williams /* 262e2f8db50SDan Williams * Make sure that one or more of the phys were not already assinged to 263e2f8db50SDan Williams * a different port. */ 264e2f8db50SDan Williams if ((phy_mask & ~assigned_phy_mask) == 0) { 265e2f8db50SDan Williams return SCI_FAILURE_UNSUPPORTED_PORT_CONFIGURATION; 266e2f8db50SDan Williams } 267e2f8db50SDan Williams 268e2f8db50SDan Williams /* Find the starting phy index for this round through the loop */ 269e2f8db50SDan Williams for (phy_index = 0; phy_index < SCI_MAX_PHYS; phy_index++) { 270e2f8db50SDan Williams if ((phy_mask & (1 << phy_index)) == 0) 271e2f8db50SDan Williams continue; 27289a7301fSDan Williams sci_phy_get_sas_address(&ihost->phys[phy_index], 273e2f8db50SDan Williams &sas_address); 274e2f8db50SDan Williams 275e2f8db50SDan Williams /* 276e2f8db50SDan Williams * The phy_index can be used as the starting point for the 277e2f8db50SDan Williams * port range since the hardware starts all logical ports 278e2f8db50SDan Williams * the same as the PE index. */ 279e2f8db50SDan Williams port_agent->phy_valid_port_range[phy_index].min_index = port_index; 280e2f8db50SDan Williams port_agent->phy_valid_port_range[phy_index].max_index = phy_index; 281e2f8db50SDan Williams 282e2f8db50SDan Williams if (phy_index != port_index) { 283e2f8db50SDan Williams return SCI_FAILURE_UNSUPPORTED_PORT_CONFIGURATION; 284e2f8db50SDan Williams } 285e2f8db50SDan Williams 286e2f8db50SDan Williams break; 287e2f8db50SDan Williams } 288e2f8db50SDan Williams 289e2f8db50SDan Williams /* 290e2f8db50SDan Williams * See how many additional phys are being added to this logical port. 291e2f8db50SDan Williams * Note: We have not moved the current phy_index so we will actually 292e2f8db50SDan Williams * compare the startting phy with itself. 293e2f8db50SDan Williams * This is expected and required to add the phy to the port. */ 2944bc83b3fSColin Ian King for (; phy_index < SCI_MAX_PHYS; phy_index++) { 295e2f8db50SDan Williams if ((phy_mask & (1 << phy_index)) == 0) 296e2f8db50SDan Williams continue; 29789a7301fSDan Williams sci_phy_get_sas_address(&ihost->phys[phy_index], 298e2f8db50SDan Williams &phy_assigned_address); 299e2f8db50SDan Williams 300e2f8db50SDan Williams if (sci_sas_address_compare(sas_address, phy_assigned_address) != 0) { 301e2f8db50SDan Williams /* 302e2f8db50SDan Williams * The phy mask specified that this phy is part of the same port 303e2f8db50SDan Williams * as the starting phy and it is not so fail this configuration */ 304e2f8db50SDan Williams return SCI_FAILURE_UNSUPPORTED_PORT_CONFIGURATION; 305e2f8db50SDan Williams } 306e2f8db50SDan Williams 307e2f8db50SDan Williams port_agent->phy_valid_port_range[phy_index].min_index = port_index; 308e2f8db50SDan Williams port_agent->phy_valid_port_range[phy_index].max_index = phy_index; 309e2f8db50SDan Williams 31089a7301fSDan Williams sci_port_add_phy(&ihost->ports[port_index], 31185280955SDan Williams &ihost->phys[phy_index]); 312e2f8db50SDan Williams 313e2f8db50SDan Williams assigned_phy_mask |= (1 << phy_index); 314e2f8db50SDan Williams } 315e2f8db50SDan Williams 316e2f8db50SDan Williams } 317e2f8db50SDan Williams 31889a7301fSDan Williams return sci_port_configuration_agent_validate_ports(ihost, port_agent); 319e2f8db50SDan Williams } 320e2f8db50SDan Williams 321b0a2dc66SKees Cook static void mpc_agent_timeout(struct timer_list *t) 322e2f8db50SDan Williams { 323e2f8db50SDan Williams u8 index; 324b0a2dc66SKees Cook struct sci_timer *tmr = from_timer(tmr, t, timer); 32589a7301fSDan Williams struct sci_port_configuration_agent *port_agent; 326ac0eeb4fSEdmund Nadolski struct isci_host *ihost; 327ac0eeb4fSEdmund Nadolski unsigned long flags; 328e2f8db50SDan Williams u16 configure_phy_mask; 329e2f8db50SDan Williams 330ac0eeb4fSEdmund Nadolski port_agent = container_of(tmr, typeof(*port_agent), timer); 331d9dcb4baSDan Williams ihost = container_of(port_agent, typeof(*ihost), port_agent); 332ac0eeb4fSEdmund Nadolski 333ac0eeb4fSEdmund Nadolski spin_lock_irqsave(&ihost->scic_lock, flags); 334ac0eeb4fSEdmund Nadolski 335ac0eeb4fSEdmund Nadolski if (tmr->cancel) 336ac0eeb4fSEdmund Nadolski goto done; 337ac0eeb4fSEdmund Nadolski 338e2f8db50SDan Williams port_agent->timer_pending = false; 339e2f8db50SDan Williams 340e2f8db50SDan Williams /* Find the mask of phys that are reported read but as yet unconfigured into a port */ 341e2f8db50SDan Williams configure_phy_mask = ~port_agent->phy_configured_mask & port_agent->phy_ready_mask; 342e2f8db50SDan Williams 343e2f8db50SDan Williams for (index = 0; index < SCI_MAX_PHYS; index++) { 34485280955SDan Williams struct isci_phy *iphy = &ihost->phys[index]; 345e2f8db50SDan Williams 346e2f8db50SDan Williams if (configure_phy_mask & (1 << index)) { 347d9dcb4baSDan Williams port_agent->link_up_handler(ihost, port_agent, 34885280955SDan Williams phy_get_non_dummy_port(iphy), 34985280955SDan Williams iphy); 350e2f8db50SDan Williams } 351e2f8db50SDan Williams } 352ac0eeb4fSEdmund Nadolski 353ac0eeb4fSEdmund Nadolski done: 354ac0eeb4fSEdmund Nadolski spin_unlock_irqrestore(&ihost->scic_lock, flags); 355e2f8db50SDan Williams } 356e2f8db50SDan Williams 35789a7301fSDan Williams static void sci_mpc_agent_link_up(struct isci_host *ihost, 35889a7301fSDan Williams struct sci_port_configuration_agent *port_agent, 359ffe191c9SDan Williams struct isci_port *iport, 36085280955SDan Williams struct isci_phy *iphy) 361e2f8db50SDan Williams { 362ffe191c9SDan Williams /* If the port is NULL then the phy was not assigned to a port. 363ffe191c9SDan Williams * This is because the phy was not given the same SAS Address as 364ffe191c9SDan Williams * the other PHYs in the port. 36585280955SDan Williams */ 366ffe191c9SDan Williams if (!iport) 367ffe191c9SDan Williams return; 368ffe191c9SDan Williams 36934a99158SDan Williams port_agent->phy_ready_mask |= (1 << iphy->phy_index); 37089a7301fSDan Williams sci_port_link_up(iport, iphy); 37134a99158SDan Williams if ((iport->active_phy_mask & (1 << iphy->phy_index))) 37234a99158SDan Williams port_agent->phy_configured_mask |= (1 << iphy->phy_index); 373e2f8db50SDan Williams } 374e2f8db50SDan Williams 375e2f8db50SDan Williams /** 376e2f8db50SDan Williams * 377e2f8db50SDan Williams * @controller: This is the controller object that receives the link down 378e2f8db50SDan Williams * notification. 379e2f8db50SDan Williams * @port: This is the port object associated with the phy. If the is no 380e2f8db50SDan Williams * associated port this is an NULL. The port is an invalid 381e2f8db50SDan Williams * handle only if the phy was never port of this port. This happens when 382e2f8db50SDan Williams * the phy is not broadcasting the same SAS address as the other phys in the 383e2f8db50SDan Williams * assigned port. 384e2f8db50SDan Williams * @phy: This is the phy object which has gone link down. 385e2f8db50SDan Williams * 386e2f8db50SDan Williams * This function handles the manual port configuration link down notifications. 387e2f8db50SDan Williams * Since all ports and phys are associated at initialization time we just turn 388e2f8db50SDan Williams * around and notifiy the port object of the link down event. If this PHY is 389e2f8db50SDan Williams * not associated with a port there is no action taken. Is it possible to get a 390e2f8db50SDan Williams * link down notification from a phy that has no assocoated port? 391e2f8db50SDan Williams */ 39289a7301fSDan Williams static void sci_mpc_agent_link_down( 393d9dcb4baSDan Williams struct isci_host *ihost, 39489a7301fSDan Williams struct sci_port_configuration_agent *port_agent, 395ffe191c9SDan Williams struct isci_port *iport, 39685280955SDan Williams struct isci_phy *iphy) 397e2f8db50SDan Williams { 398ffe191c9SDan Williams if (iport != NULL) { 399e2f8db50SDan Williams /* 400e2f8db50SDan Williams * If we can form a new port from the remainder of the phys 401e2f8db50SDan Williams * then we want to start the timer to allow the SCI User to 402e2f8db50SDan Williams * cleanup old devices and rediscover the port before 403e2f8db50SDan Williams * rebuilding the port with the phys that remain in the ready 404e2f8db50SDan Williams * state. 405e2f8db50SDan Williams */ 40634a99158SDan Williams port_agent->phy_ready_mask &= ~(1 << iphy->phy_index); 40734a99158SDan Williams port_agent->phy_configured_mask &= ~(1 << iphy->phy_index); 408e2f8db50SDan Williams 409e2f8db50SDan Williams /* 410e2f8db50SDan Williams * Check to see if there are more phys waiting to be 411e2f8db50SDan Williams * configured into a port. If there are allow the SCI User 412e2f8db50SDan Williams * to tear down this port, if necessary, and then reconstruct 413e2f8db50SDan Williams * the port after the timeout. 414e2f8db50SDan Williams */ 415e2f8db50SDan Williams if ((port_agent->phy_configured_mask == 0x0000) && 416e2f8db50SDan Williams (port_agent->phy_ready_mask != 0x0000) && 417e2f8db50SDan Williams !port_agent->timer_pending) { 418e2f8db50SDan Williams port_agent->timer_pending = true; 419e2f8db50SDan Williams 420ac0eeb4fSEdmund Nadolski sci_mod_timer(&port_agent->timer, 421e2f8db50SDan Williams SCIC_SDS_MPC_RECONFIGURATION_TIMEOUT); 422e2f8db50SDan Williams } 423e2f8db50SDan Williams 42489a7301fSDan Williams sci_port_link_down(iport, iphy); 425e2f8db50SDan Williams } 426e2f8db50SDan Williams } 427e2f8db50SDan Williams 428d9dcb4baSDan Williams /* verify phys are assigned a valid SAS address for automatic port 429d9dcb4baSDan Williams * configuration mode. 430e2f8db50SDan Williams */ 431d9dcb4baSDan Williams static enum sci_status 43289a7301fSDan Williams sci_apc_agent_validate_phy_configuration(struct isci_host *ihost, 43389a7301fSDan Williams struct sci_port_configuration_agent *port_agent) 434e2f8db50SDan Williams { 435e2f8db50SDan Williams u8 phy_index; 436e2f8db50SDan Williams u8 port_index; 437e2f8db50SDan Williams struct sci_sas_address sas_address; 438e2f8db50SDan Williams struct sci_sas_address phy_assigned_address; 439e2f8db50SDan Williams 440e2f8db50SDan Williams phy_index = 0; 441e2f8db50SDan Williams 442e2f8db50SDan Williams while (phy_index < SCI_MAX_PHYS) { 443e2f8db50SDan Williams port_index = phy_index; 444e2f8db50SDan Williams 445e2f8db50SDan Williams /* Get the assigned SAS Address for the first PHY on the controller. */ 44689a7301fSDan Williams sci_phy_get_sas_address(&ihost->phys[phy_index], 447e2f8db50SDan Williams &sas_address); 448e2f8db50SDan Williams 449e2f8db50SDan Williams while (++phy_index < SCI_MAX_PHYS) { 45089a7301fSDan Williams sci_phy_get_sas_address(&ihost->phys[phy_index], 451e2f8db50SDan Williams &phy_assigned_address); 452e2f8db50SDan Williams 453e2f8db50SDan Williams /* Verify each of the SAS address are all the same for every PHY */ 454e2f8db50SDan Williams if (sci_sas_address_compare(sas_address, phy_assigned_address) == 0) { 455e2f8db50SDan Williams port_agent->phy_valid_port_range[phy_index].min_index = port_index; 456e2f8db50SDan Williams port_agent->phy_valid_port_range[phy_index].max_index = phy_index; 457e2f8db50SDan Williams } else { 458e2f8db50SDan Williams port_agent->phy_valid_port_range[phy_index].min_index = phy_index; 459e2f8db50SDan Williams port_agent->phy_valid_port_range[phy_index].max_index = phy_index; 460e2f8db50SDan Williams break; 461e2f8db50SDan Williams } 462e2f8db50SDan Williams } 463e2f8db50SDan Williams } 464e2f8db50SDan Williams 46589a7301fSDan Williams return sci_port_configuration_agent_validate_ports(ihost, port_agent); 466e2f8db50SDan Williams } 467e2f8db50SDan Williams 468be778341SMarcin Tomczak /* 469be778341SMarcin Tomczak * This routine will restart the automatic port configuration timeout 470be778341SMarcin Tomczak * timer for the next time period. This could be caused by either a link 471be778341SMarcin Tomczak * down event or a link up event where we can not yet tell to which a phy 472be778341SMarcin Tomczak * belongs. 473be778341SMarcin Tomczak */ 47450a92d93SDan Williams static void sci_apc_agent_start_timer(struct sci_port_configuration_agent *port_agent, 475be778341SMarcin Tomczak u32 timeout) 476be778341SMarcin Tomczak { 477be778341SMarcin Tomczak port_agent->timer_pending = true; 478be778341SMarcin Tomczak sci_mod_timer(&port_agent->timer, timeout); 479be778341SMarcin Tomczak } 480be778341SMarcin Tomczak 48189a7301fSDan Williams static void sci_apc_agent_configure_ports(struct isci_host *ihost, 48289a7301fSDan Williams struct sci_port_configuration_agent *port_agent, 48385280955SDan Williams struct isci_phy *iphy, 484e2f8db50SDan Williams bool start_timer) 485e2f8db50SDan Williams { 486e2f8db50SDan Williams u8 port_index; 487e2f8db50SDan Williams enum sci_status status; 488ffe191c9SDan Williams struct isci_port *iport; 489e2f8db50SDan Williams enum SCIC_SDS_APC_ACTIVITY apc_activity = SCIC_SDS_APC_SKIP_PHY; 490e2f8db50SDan Williams 49189a7301fSDan Williams iport = sci_port_configuration_agent_find_port(ihost, iphy); 492e2f8db50SDan Williams 493ffe191c9SDan Williams if (iport) { 49489a7301fSDan Williams if (sci_port_is_valid_phy_assignment(iport, iphy->phy_index)) 495e2f8db50SDan Williams apc_activity = SCIC_SDS_APC_ADD_PHY; 496e2f8db50SDan Williams else 497e2f8db50SDan Williams apc_activity = SCIC_SDS_APC_SKIP_PHY; 498e2f8db50SDan Williams } else { 499e2f8db50SDan Williams /* 500e2f8db50SDan Williams * There is no matching Port for this PHY so lets search through the 501e2f8db50SDan Williams * Ports and see if we can add the PHY to its own port or maybe start 502e2f8db50SDan Williams * the timer and wait to see if a wider port can be made. 503e2f8db50SDan Williams * 504e2f8db50SDan Williams * Note the break when we reach the condition of the port id == phy id */ 505ffe191c9SDan Williams for (port_index = port_agent->phy_valid_port_range[iphy->phy_index].min_index; 50685280955SDan Williams port_index <= port_agent->phy_valid_port_range[iphy->phy_index].max_index; 507ffe191c9SDan Williams port_index++) { 508e2f8db50SDan Williams 509ffe191c9SDan Williams iport = &ihost->ports[port_index]; 510e2f8db50SDan Williams 511e2f8db50SDan Williams /* First we must make sure that this PHY can be added to this Port. */ 51289a7301fSDan Williams if (sci_port_is_valid_phy_assignment(iport, iphy->phy_index)) { 513e2f8db50SDan Williams /* 514e2f8db50SDan Williams * Port contains a PHY with a greater PHY ID than the current 515e2f8db50SDan Williams * PHY that has gone link up. This phy can not be part of any 516e2f8db50SDan Williams * port so skip it and move on. */ 517ffe191c9SDan Williams if (iport->active_phy_mask > (1 << iphy->phy_index)) { 518e2f8db50SDan Williams apc_activity = SCIC_SDS_APC_SKIP_PHY; 519e2f8db50SDan Williams break; 520e2f8db50SDan Williams } 521e2f8db50SDan Williams 522e2f8db50SDan Williams /* 523e2f8db50SDan Williams * We have reached the end of our Port list and have not found 524e2f8db50SDan Williams * any reason why we should not either add the PHY to the port 525e2f8db50SDan Williams * or wait for more phys to become active. */ 526ffe191c9SDan Williams if (iport->physical_port_index == iphy->phy_index) { 527e2f8db50SDan Williams /* 528e2f8db50SDan Williams * The Port either has no active PHYs. 529e2f8db50SDan Williams * Consider that if the port had any active PHYs we would have 530e2f8db50SDan Williams * or active PHYs with 531e2f8db50SDan Williams * a lower PHY Id than this PHY. */ 532e2f8db50SDan Williams if (apc_activity != SCIC_SDS_APC_START_TIMER) { 533e2f8db50SDan Williams apc_activity = SCIC_SDS_APC_ADD_PHY; 534e2f8db50SDan Williams } 535e2f8db50SDan Williams 536e2f8db50SDan Williams break; 537e2f8db50SDan Williams } 538e2f8db50SDan Williams 539e2f8db50SDan Williams /* 540e2f8db50SDan Williams * The current Port has no active PHYs and this PHY could be part 541e2f8db50SDan Williams * of this Port. Since we dont know as yet setup to start the 542e2f8db50SDan Williams * timer and see if there is a better configuration. */ 543ffe191c9SDan Williams if (iport->active_phy_mask == 0) { 544e2f8db50SDan Williams apc_activity = SCIC_SDS_APC_START_TIMER; 545e2f8db50SDan Williams } 546ffe191c9SDan Williams } else if (iport->active_phy_mask != 0) { 547e2f8db50SDan Williams /* 548e2f8db50SDan Williams * The Port has an active phy and the current Phy can not 549e2f8db50SDan Williams * participate in this port so skip the PHY and see if 550e2f8db50SDan Williams * there is a better configuration. */ 551e2f8db50SDan Williams apc_activity = SCIC_SDS_APC_SKIP_PHY; 552e2f8db50SDan Williams } 553e2f8db50SDan Williams } 554e2f8db50SDan Williams } 555e2f8db50SDan Williams 556e2f8db50SDan Williams /* 557e2f8db50SDan Williams * Check to see if the start timer operations should instead map to an 558e2f8db50SDan Williams * add phy operation. This is caused because we have been waiting to 559e2f8db50SDan Williams * add a phy to a port but could not becuase the automatic port 560e2f8db50SDan Williams * configuration engine had a choice of possible ports for the phy. 561e2f8db50SDan Williams * Since we have gone through a timeout we are going to restrict the 562e2f8db50SDan Williams * choice to the smallest possible port. */ 563e2f8db50SDan Williams if ( 564e2f8db50SDan Williams (start_timer == false) 565e2f8db50SDan Williams && (apc_activity == SCIC_SDS_APC_START_TIMER) 566e2f8db50SDan Williams ) { 567e2f8db50SDan Williams apc_activity = SCIC_SDS_APC_ADD_PHY; 568e2f8db50SDan Williams } 569e2f8db50SDan Williams 570e2f8db50SDan Williams switch (apc_activity) { 571e2f8db50SDan Williams case SCIC_SDS_APC_ADD_PHY: 57289a7301fSDan Williams status = sci_port_add_phy(iport, iphy); 573e2f8db50SDan Williams 574e2f8db50SDan Williams if (status == SCI_SUCCESS) { 57585280955SDan Williams port_agent->phy_configured_mask |= (1 << iphy->phy_index); 576e2f8db50SDan Williams } 577e2f8db50SDan Williams break; 578e2f8db50SDan Williams 579e2f8db50SDan Williams case SCIC_SDS_APC_START_TIMER: 580be778341SMarcin Tomczak sci_apc_agent_start_timer(port_agent, 581ac0eeb4fSEdmund Nadolski SCIC_SDS_APC_WAIT_LINK_UP_NOTIFICATION); 582e2f8db50SDan Williams break; 583e2f8db50SDan Williams 584e2f8db50SDan Williams case SCIC_SDS_APC_SKIP_PHY: 585e2f8db50SDan Williams default: 586e2f8db50SDan Williams /* do nothing the PHY can not be made part of a port at this time. */ 587e2f8db50SDan Williams break; 588e2f8db50SDan Williams } 589e2f8db50SDan Williams } 590e2f8db50SDan Williams 591e2f8db50SDan Williams /** 59289a7301fSDan Williams * sci_apc_agent_link_up - handle apc link up events 593e2f8db50SDan Williams * @scic: This is the controller object that receives the link up 594e2f8db50SDan Williams * notification. 595e2f8db50SDan Williams * @sci_port: This is the port object associated with the phy. If the is no 596e2f8db50SDan Williams * associated port this is an NULL. 597e2f8db50SDan Williams * @sci_phy: This is the phy object which has gone link up. 598e2f8db50SDan Williams * 599e2f8db50SDan Williams * This method handles the automatic port configuration for link up 600e2f8db50SDan Williams * notifications. Is it possible to get a link down notification from a phy 601e2f8db50SDan Williams * that has no assocoated port? 602e2f8db50SDan Williams */ 60389a7301fSDan Williams static void sci_apc_agent_link_up(struct isci_host *ihost, 60489a7301fSDan Williams struct sci_port_configuration_agent *port_agent, 605ffe191c9SDan Williams struct isci_port *iport, 60685280955SDan Williams struct isci_phy *iphy) 607e2f8db50SDan Williams { 60885280955SDan Williams u8 phy_index = iphy->phy_index; 609e2f8db50SDan Williams 610ffe191c9SDan Williams if (!iport) { 611e2f8db50SDan Williams /* the phy is not the part of this port */ 612e2f8db50SDan Williams port_agent->phy_ready_mask |= 1 << phy_index; 613be778341SMarcin Tomczak sci_apc_agent_start_timer(port_agent, 614be778341SMarcin Tomczak SCIC_SDS_APC_WAIT_LINK_UP_NOTIFICATION); 615e2f8db50SDan Williams } else { 616e2f8db50SDan Williams /* the phy is already the part of the port */ 617e2f8db50SDan Williams port_agent->phy_ready_mask |= 1 << phy_index; 61889a7301fSDan Williams sci_port_link_up(iport, iphy); 619e2f8db50SDan Williams } 620e2f8db50SDan Williams } 621e2f8db50SDan Williams 622e2f8db50SDan Williams /** 623e2f8db50SDan Williams * 624e2f8db50SDan Williams * @controller: This is the controller object that receives the link down 625e2f8db50SDan Williams * notification. 626ffe191c9SDan Williams * @iport: This is the port object associated with the phy. If the is no 627e2f8db50SDan Williams * associated port this is an NULL. 628ffe191c9SDan Williams * @iphy: This is the phy object which has gone link down. 629e2f8db50SDan Williams * 630e2f8db50SDan Williams * This method handles the automatic port configuration link down 631e2f8db50SDan Williams * notifications. not associated with a port there is no action taken. Is it 632e2f8db50SDan Williams * possible to get a link down notification from a phy that has no assocoated 633e2f8db50SDan Williams * port? 634e2f8db50SDan Williams */ 63589a7301fSDan Williams static void sci_apc_agent_link_down( 636d9dcb4baSDan Williams struct isci_host *ihost, 63789a7301fSDan Williams struct sci_port_configuration_agent *port_agent, 638ffe191c9SDan Williams struct isci_port *iport, 63985280955SDan Williams struct isci_phy *iphy) 640e2f8db50SDan Williams { 64134a99158SDan Williams port_agent->phy_ready_mask &= ~(1 << iphy->phy_index); 642e2f8db50SDan Williams 643ffe191c9SDan Williams if (!iport) 644ffe191c9SDan Williams return; 64585280955SDan Williams if (port_agent->phy_configured_mask & (1 << iphy->phy_index)) { 646e2f8db50SDan Williams enum sci_status status; 647e2f8db50SDan Williams 64889a7301fSDan Williams status = sci_port_remove_phy(iport, iphy); 649e2f8db50SDan Williams 650ffe191c9SDan Williams if (status == SCI_SUCCESS) 65185280955SDan Williams port_agent->phy_configured_mask &= ~(1 << iphy->phy_index); 652e2f8db50SDan Williams } 653e2f8db50SDan Williams } 654e2f8db50SDan Williams 655e2f8db50SDan Williams /* configure the phys into ports when the timer fires */ 656b0a2dc66SKees Cook static void apc_agent_timeout(struct timer_list *t) 657e2f8db50SDan Williams { 658e2f8db50SDan Williams u32 index; 659b0a2dc66SKees Cook struct sci_timer *tmr = from_timer(tmr, t, timer); 66089a7301fSDan Williams struct sci_port_configuration_agent *port_agent; 661ac0eeb4fSEdmund Nadolski struct isci_host *ihost; 662ac0eeb4fSEdmund Nadolski unsigned long flags; 663e2f8db50SDan Williams u16 configure_phy_mask; 664e2f8db50SDan Williams 665ac0eeb4fSEdmund Nadolski port_agent = container_of(tmr, typeof(*port_agent), timer); 666d9dcb4baSDan Williams ihost = container_of(port_agent, typeof(*ihost), port_agent); 667ac0eeb4fSEdmund Nadolski 668ac0eeb4fSEdmund Nadolski spin_lock_irqsave(&ihost->scic_lock, flags); 669ac0eeb4fSEdmund Nadolski 670ac0eeb4fSEdmund Nadolski if (tmr->cancel) 671ac0eeb4fSEdmund Nadolski goto done; 672e2f8db50SDan Williams 673e2f8db50SDan Williams port_agent->timer_pending = false; 674e2f8db50SDan Williams 675e2f8db50SDan Williams configure_phy_mask = ~port_agent->phy_configured_mask & port_agent->phy_ready_mask; 676e2f8db50SDan Williams 677e2f8db50SDan Williams if (!configure_phy_mask) 678983d3fddSJeff Skirvin goto done; 679e2f8db50SDan Williams 680e2f8db50SDan Williams for (index = 0; index < SCI_MAX_PHYS; index++) { 681e2f8db50SDan Williams if ((configure_phy_mask & (1 << index)) == 0) 682e2f8db50SDan Williams continue; 683e2f8db50SDan Williams 68489a7301fSDan Williams sci_apc_agent_configure_ports(ihost, port_agent, 68585280955SDan Williams &ihost->phys[index], false); 686e2f8db50SDan Williams } 687ac0eeb4fSEdmund Nadolski 68850a92d93SDan Williams if (is_controller_start_complete(ihost)) 68950a92d93SDan Williams sci_controller_transition_to_ready(ihost, SCI_SUCCESS); 69050a92d93SDan Williams 691ac0eeb4fSEdmund Nadolski done: 692ac0eeb4fSEdmund Nadolski spin_unlock_irqrestore(&ihost->scic_lock, flags); 693e2f8db50SDan Williams } 694e2f8db50SDan Williams 695e2f8db50SDan Williams /* 696e2f8db50SDan Williams * ****************************************************************************** 697e2f8db50SDan Williams * Public port configuration agent routines 698e2f8db50SDan Williams * ****************************************************************************** */ 699e2f8db50SDan Williams 700e2f8db50SDan Williams /** 701e2f8db50SDan Williams * 702e2f8db50SDan Williams * 703e2f8db50SDan Williams * This method will construct the port configuration agent for operation. This 704e2f8db50SDan Williams * call is universal for both manual port configuration and automatic port 705e2f8db50SDan Williams * configuration modes. 706e2f8db50SDan Williams */ 70789a7301fSDan Williams void sci_port_configuration_agent_construct( 70889a7301fSDan Williams struct sci_port_configuration_agent *port_agent) 709e2f8db50SDan Williams { 710e2f8db50SDan Williams u32 index; 711e2f8db50SDan Williams 712e2f8db50SDan Williams port_agent->phy_configured_mask = 0x00; 713e2f8db50SDan Williams port_agent->phy_ready_mask = 0x00; 714e2f8db50SDan Williams 715e2f8db50SDan Williams port_agent->link_up_handler = NULL; 716e2f8db50SDan Williams port_agent->link_down_handler = NULL; 717e2f8db50SDan Williams 718e2f8db50SDan Williams port_agent->timer_pending = false; 719e2f8db50SDan Williams 720e2f8db50SDan Williams for (index = 0; index < SCI_MAX_PORTS; index++) { 721e2f8db50SDan Williams port_agent->phy_valid_port_range[index].min_index = 0; 722e2f8db50SDan Williams port_agent->phy_valid_port_range[index].max_index = 0; 723e2f8db50SDan Williams } 724e2f8db50SDan Williams } 725e2f8db50SDan Williams 72650a92d93SDan Williams bool is_port_config_apc(struct isci_host *ihost) 72750a92d93SDan Williams { 72850a92d93SDan Williams return ihost->port_agent.link_up_handler == sci_apc_agent_link_up; 72950a92d93SDan Williams } 73050a92d93SDan Williams 73189a7301fSDan Williams enum sci_status sci_port_configuration_agent_initialize( 732d9dcb4baSDan Williams struct isci_host *ihost, 73389a7301fSDan Williams struct sci_port_configuration_agent *port_agent) 734e2f8db50SDan Williams { 735ac0eeb4fSEdmund Nadolski enum sci_status status; 73689a7301fSDan Williams enum sci_port_configuration_mode mode; 737e2f8db50SDan Williams 73889a7301fSDan Williams mode = ihost->oem_parameters.controller.mode_type; 739e2f8db50SDan Williams 740e2f8db50SDan Williams if (mode == SCIC_PORT_MANUAL_CONFIGURATION_MODE) { 74189a7301fSDan Williams status = sci_mpc_agent_validate_phy_configuration( 742d9dcb4baSDan Williams ihost, port_agent); 743e2f8db50SDan Williams 74489a7301fSDan Williams port_agent->link_up_handler = sci_mpc_agent_link_up; 74589a7301fSDan Williams port_agent->link_down_handler = sci_mpc_agent_link_down; 746e2f8db50SDan Williams 747ac0eeb4fSEdmund Nadolski sci_init_timer(&port_agent->timer, mpc_agent_timeout); 748e2f8db50SDan Williams } else { 74989a7301fSDan Williams status = sci_apc_agent_validate_phy_configuration( 750d9dcb4baSDan Williams ihost, port_agent); 751e2f8db50SDan Williams 75289a7301fSDan Williams port_agent->link_up_handler = sci_apc_agent_link_up; 75389a7301fSDan Williams port_agent->link_down_handler = sci_apc_agent_link_down; 754e2f8db50SDan Williams 755ac0eeb4fSEdmund Nadolski sci_init_timer(&port_agent->timer, apc_agent_timeout); 756e2f8db50SDan Williams } 757e2f8db50SDan Williams 758e2f8db50SDan Williams return status; 759e2f8db50SDan Williams } 760