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