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 | |
56 | #include "host.h" |
57 | |
58 | #define SCIC_SDS_MPC_RECONFIGURATION_TIMEOUT (10) |
59 | #define SCIC_SDS_APC_RECONFIGURATION_TIMEOUT (10) |
60 | #define SCIC_SDS_APC_WAIT_LINK_UP_NOTIFICATION (1000) |
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 | * sci_sas_address_compare() |
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 | * sci_port_configuration_agent_find_port() |
106 | * @ihost: The controller object used for the port search. |
107 | * @iphy: 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 |
111 | * address. The port address or the NULL if there is no matching |
112 | * port. port address if the port can be found to match the phy. |
113 | * NULL if there is no matching port for the phy. |
114 | */ |
115 | static struct isci_port *sci_port_configuration_agent_find_port( |
116 | struct isci_host *ihost, |
117 | struct isci_phy *iphy) |
118 | { |
119 | u8 i; |
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 |
128 | * case it should participate in the same port. |
129 | */ |
130 | sci_phy_get_sas_address(iphy, sas_address: &phy_sas_address); |
131 | sci_phy_get_attached_sas_address(iphy, sas_address: &phy_attached_device_address); |
132 | |
133 | for (i = 0; i < ihost->logical_port_entries; i++) { |
134 | struct isci_port *iport = &ihost->ports[i]; |
135 | |
136 | sci_port_get_sas_address(iport, sas_address: &port_sas_address); |
137 | sci_port_get_attached_sas_address(iport, sas_address: &port_attached_device_address); |
138 | |
139 | if (sci_sas_address_compare(address_one: port_sas_address, address_two: phy_sas_address) == 0 && |
140 | sci_sas_address_compare(address_one: port_attached_device_address, address_two: phy_attached_device_address) == 0) |
141 | return iport; |
142 | } |
143 | |
144 | return NULL; |
145 | } |
146 | |
147 | /** |
148 | * sci_port_configuration_agent_validate_ports() |
149 | * @ihost: This is the controller object that contains the port agent |
150 | * @port_agent: This is the port configuration 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 | */ |
159 | static enum sci_status sci_port_configuration_agent_validate_ports( |
160 | struct isci_host *ihost, |
161 | struct sci_port_configuration_agent *port_agent) |
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 */ |
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) |
173 | return SCI_FAILURE_UNSUPPORTED_PORT_CONFIGURATION; |
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 */ |
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) |
182 | return SCI_SUCCESS; |
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. */ |
197 | sci_phy_get_sas_address(iphy: &ihost->phys[0], sas_address: &first_address); |
198 | sci_phy_get_sas_address(iphy: &ihost->phys[3], sas_address: &second_address); |
199 | |
200 | if (sci_sas_address_compare(address_one: first_address, address_two: 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. */ |
208 | if (port_agent->phy_valid_port_range[0].min_index == 0 && |
209 | port_agent->phy_valid_port_range[1].min_index == 1) { |
210 | sci_phy_get_sas_address(iphy: &ihost->phys[0], sas_address: &first_address); |
211 | sci_phy_get_sas_address(iphy: &ihost->phys[2], sas_address: &second_address); |
212 | |
213 | if (sci_sas_address_compare(address_one: first_address, address_two: 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. */ |
222 | if (port_agent->phy_valid_port_range[2].min_index == 2 && |
223 | port_agent->phy_valid_port_range[3].min_index == 3) { |
224 | sci_phy_get_sas_address(iphy: &ihost->phys[1], sas_address: &first_address); |
225 | sci_phy_get_sas_address(iphy: &ihost->phys[3], sas_address: &second_address); |
226 | |
227 | if (sci_sas_address_compare(address_one: first_address, address_two: 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 | |
240 | /* verify all of the phys in the same port are using the same SAS address */ |
241 | static enum sci_status |
242 | sci_mpc_agent_validate_phy_configuration(struct isci_host *ihost, |
243 | struct sci_port_configuration_agent *port_agent) |
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++) { |
257 | phy_mask = ihost->oem_parameters.ports[port_index].phy_mask; |
258 | |
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 | } |
267 | |
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; |
272 | sci_phy_get_sas_address(iphy: &ihost->phys[phy_index], |
273 | sas_address: &sas_address); |
274 | |
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; |
281 | |
282 | if (phy_index != port_index) { |
283 | return SCI_FAILURE_UNSUPPORTED_PORT_CONFIGURATION; |
284 | } |
285 | |
286 | break; |
287 | } |
288 | |
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 | for (; phy_index < SCI_MAX_PHYS; phy_index++) { |
295 | if ((phy_mask & (1 << phy_index)) == 0) |
296 | continue; |
297 | sci_phy_get_sas_address(iphy: &ihost->phys[phy_index], |
298 | sas_address: &phy_assigned_address); |
299 | |
300 | if (sci_sas_address_compare(address_one: sas_address, address_two: 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 | } |
306 | |
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; |
309 | |
310 | sci_port_add_phy(iport: &ihost->ports[port_index], |
311 | iphy: &ihost->phys[phy_index]); |
312 | |
313 | assigned_phy_mask |= (1 << phy_index); |
314 | } |
315 | |
316 | } |
317 | |
318 | return sci_port_configuration_agent_validate_ports(ihost, port_agent); |
319 | } |
320 | |
321 | static void mpc_agent_timeout(struct timer_list *t) |
322 | { |
323 | u8 index; |
324 | struct sci_timer *tmr = from_timer(tmr, t, timer); |
325 | struct sci_port_configuration_agent *port_agent; |
326 | struct isci_host *ihost; |
327 | unsigned long flags; |
328 | u16 configure_phy_mask; |
329 | |
330 | port_agent = container_of(tmr, typeof(*port_agent), timer); |
331 | ihost = container_of(port_agent, typeof(*ihost), port_agent); |
332 | |
333 | spin_lock_irqsave(&ihost->scic_lock, flags); |
334 | |
335 | if (tmr->cancel) |
336 | goto done; |
337 | |
338 | port_agent->timer_pending = false; |
339 | |
340 | /* Find the mask of phys that are reported read but as yet unconfigured into a port */ |
341 | configure_phy_mask = ~port_agent->phy_configured_mask & port_agent->phy_ready_mask; |
342 | |
343 | for (index = 0; index < SCI_MAX_PHYS; index++) { |
344 | struct isci_phy *iphy = &ihost->phys[index]; |
345 | |
346 | if (configure_phy_mask & (1 << index)) { |
347 | port_agent->link_up_handler(ihost, port_agent, |
348 | phy_get_non_dummy_port(iphy), |
349 | iphy); |
350 | } |
351 | } |
352 | |
353 | done: |
354 | spin_unlock_irqrestore(lock: &ihost->scic_lock, flags); |
355 | } |
356 | |
357 | static void sci_mpc_agent_link_up(struct isci_host *ihost, |
358 | struct sci_port_configuration_agent *port_agent, |
359 | struct isci_port *iport, |
360 | struct isci_phy *iphy) |
361 | { |
362 | /* If the port is NULL then the phy was not assigned to a port. |
363 | * This is because the phy was not given the same SAS Address as |
364 | * the other PHYs in the port. |
365 | */ |
366 | if (!iport) |
367 | return; |
368 | |
369 | port_agent->phy_ready_mask |= (1 << iphy->phy_index); |
370 | sci_port_link_up(iport, iphy); |
371 | if ((iport->active_phy_mask & (1 << iphy->phy_index))) |
372 | port_agent->phy_configured_mask |= (1 << iphy->phy_index); |
373 | } |
374 | |
375 | /** |
376 | * sci_mpc_agent_link_down() |
377 | * @ihost: This is the controller object that receives the link down |
378 | * notification. |
379 | * @port_agent: This is the port configuration agent for the controller. |
380 | * @iport: This is the port object associated with the phy. If the is no |
381 | * associated port this is an NULL. The port is an invalid |
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 | * @iphy: This is the phy object which has gone link down. |
386 | * |
387 | * This function handles the manual port configuration link down notifications. |
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 | */ |
393 | static void sci_mpc_agent_link_down( |
394 | struct isci_host *ihost, |
395 | struct sci_port_configuration_agent *port_agent, |
396 | struct isci_port *iport, |
397 | struct isci_phy *iphy) |
398 | { |
399 | if (iport != NULL) { |
400 | /* |
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 | */ |
407 | port_agent->phy_ready_mask &= ~(1 << iphy->phy_index); |
408 | port_agent->phy_configured_mask &= ~(1 << iphy->phy_index); |
409 | |
410 | /* |
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) { |
419 | port_agent->timer_pending = true; |
420 | |
421 | sci_mod_timer(tmr: &port_agent->timer, |
422 | SCIC_SDS_MPC_RECONFIGURATION_TIMEOUT); |
423 | } |
424 | |
425 | sci_port_link_down(iport, iphy); |
426 | } |
427 | } |
428 | |
429 | /* verify phys are assigned a valid SAS address for automatic port |
430 | * configuration mode. |
431 | */ |
432 | static enum sci_status |
433 | sci_apc_agent_validate_phy_configuration(struct isci_host *ihost, |
434 | struct sci_port_configuration_agent *port_agent) |
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. */ |
447 | sci_phy_get_sas_address(iphy: &ihost->phys[phy_index], |
448 | sas_address: &sas_address); |
449 | |
450 | while (++phy_index < SCI_MAX_PHYS) { |
451 | sci_phy_get_sas_address(iphy: &ihost->phys[phy_index], |
452 | sas_address: &phy_assigned_address); |
453 | |
454 | /* Verify each of the SAS address are all the same for every PHY */ |
455 | if (sci_sas_address_compare(address_one: sas_address, address_two: 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 | |
466 | return sci_port_configuration_agent_validate_ports(ihost, port_agent); |
467 | } |
468 | |
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 | */ |
475 | static void sci_apc_agent_start_timer(struct sci_port_configuration_agent *port_agent, |
476 | u32 timeout) |
477 | { |
478 | port_agent->timer_pending = true; |
479 | sci_mod_timer(tmr: &port_agent->timer, msec: timeout); |
480 | } |
481 | |
482 | static void sci_apc_agent_configure_ports(struct isci_host *ihost, |
483 | struct sci_port_configuration_agent *port_agent, |
484 | struct isci_phy *iphy, |
485 | bool start_timer) |
486 | { |
487 | u8 port_index; |
488 | enum sci_status status; |
489 | struct isci_port *iport; |
490 | enum SCIC_SDS_APC_ACTIVITY apc_activity = SCIC_SDS_APC_SKIP_PHY; |
491 | |
492 | iport = sci_port_configuration_agent_find_port(ihost, iphy); |
493 | |
494 | if (iport) { |
495 | if (sci_port_is_valid_phy_assignment(iport, phy_index: iphy->phy_index)) |
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 */ |
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++) { |
509 | |
510 | iport = &ihost->ports[port_index]; |
511 | |
512 | /* First we must make sure that this PHY can be added to this Port. */ |
513 | if (sci_port_is_valid_phy_assignment(iport, phy_index: iphy->phy_index)) { |
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. */ |
518 | if (iport->active_phy_mask > (1 << iphy->phy_index)) { |
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. */ |
527 | if (iport->physical_port_index == iphy->phy_index) { |
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. */ |
544 | if (iport->active_phy_mask == 0) { |
545 | apc_activity = SCIC_SDS_APC_START_TIMER; |
546 | } |
547 | } else if (iport->active_phy_mask != 0) { |
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: |
573 | status = sci_port_add_phy(iport, iphy); |
574 | |
575 | if (status == SCI_SUCCESS) { |
576 | port_agent->phy_configured_mask |= (1 << iphy->phy_index); |
577 | } |
578 | break; |
579 | |
580 | case SCIC_SDS_APC_START_TIMER: |
581 | sci_apc_agent_start_timer(port_agent, |
582 | SCIC_SDS_APC_WAIT_LINK_UP_NOTIFICATION); |
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 | /** |
593 | * sci_apc_agent_link_up - handle apc link up events |
594 | * @ihost: This is the controller object that receives the link up |
595 | * notification. |
596 | * @port_agent: This is the port configuration agent for the controller. |
597 | * @iport: This is the port object associated with the phy. If the is no |
598 | * associated port this is an NULL. |
599 | * @iphy: This is the phy object which has gone link up. |
600 | * |
601 | * This method handles the automatic port configuration for link up |
602 | * notifications. Is it possible to get a link down notification from a phy |
603 | * that has no assocoated port? |
604 | */ |
605 | static void sci_apc_agent_link_up(struct isci_host *ihost, |
606 | struct sci_port_configuration_agent *port_agent, |
607 | struct isci_port *iport, |
608 | struct isci_phy *iphy) |
609 | { |
610 | u8 phy_index = iphy->phy_index; |
611 | |
612 | if (!iport) { |
613 | /* the phy is not the part of this port */ |
614 | port_agent->phy_ready_mask |= 1 << phy_index; |
615 | sci_apc_agent_start_timer(port_agent, |
616 | SCIC_SDS_APC_WAIT_LINK_UP_NOTIFICATION); |
617 | } else { |
618 | /* the phy is already the part of the port */ |
619 | port_agent->phy_ready_mask |= 1 << phy_index; |
620 | sci_port_link_up(iport, iphy); |
621 | } |
622 | } |
623 | |
624 | /** |
625 | * sci_apc_agent_link_down() |
626 | * @ihost: This is the controller object that receives the link down |
627 | * notification. |
628 | * @port_agent: This is the port configuration agent for the controller. |
629 | * @iport: This is the port object associated with the phy. If the is no |
630 | * associated port this is an NULL. |
631 | * @iphy: This is the phy object which has gone link down. |
632 | * |
633 | * This method handles the automatic port configuration link down |
634 | * notifications. not associated with a port there is no action taken. Is it |
635 | * possible to get a link down notification from a phy that has no assocoated |
636 | * port? |
637 | */ |
638 | static void sci_apc_agent_link_down( |
639 | struct isci_host *ihost, |
640 | struct sci_port_configuration_agent *port_agent, |
641 | struct isci_port *iport, |
642 | struct isci_phy *iphy) |
643 | { |
644 | port_agent->phy_ready_mask &= ~(1 << iphy->phy_index); |
645 | |
646 | if (!iport) |
647 | return; |
648 | if (port_agent->phy_configured_mask & (1 << iphy->phy_index)) { |
649 | enum sci_status status; |
650 | |
651 | status = sci_port_remove_phy(iport, iphy); |
652 | |
653 | if (status == SCI_SUCCESS) |
654 | port_agent->phy_configured_mask &= ~(1 << iphy->phy_index); |
655 | } |
656 | } |
657 | |
658 | /* configure the phys into ports when the timer fires */ |
659 | static void apc_agent_timeout(struct timer_list *t) |
660 | { |
661 | u32 index; |
662 | struct sci_timer *tmr = from_timer(tmr, t, timer); |
663 | struct sci_port_configuration_agent *port_agent; |
664 | struct isci_host *ihost; |
665 | unsigned long flags; |
666 | u16 configure_phy_mask; |
667 | |
668 | port_agent = container_of(tmr, typeof(*port_agent), timer); |
669 | ihost = container_of(port_agent, typeof(*ihost), port_agent); |
670 | |
671 | spin_lock_irqsave(&ihost->scic_lock, flags); |
672 | |
673 | if (tmr->cancel) |
674 | goto done; |
675 | |
676 | port_agent->timer_pending = false; |
677 | |
678 | configure_phy_mask = ~port_agent->phy_configured_mask & port_agent->phy_ready_mask; |
679 | |
680 | if (!configure_phy_mask) |
681 | goto done; |
682 | |
683 | for (index = 0; index < SCI_MAX_PHYS; index++) { |
684 | if ((configure_phy_mask & (1 << index)) == 0) |
685 | continue; |
686 | |
687 | sci_apc_agent_configure_ports(ihost, port_agent, |
688 | iphy: &ihost->phys[index], start_timer: false); |
689 | } |
690 | |
691 | if (is_controller_start_complete(ihost)) |
692 | sci_controller_transition_to_ready(ihost, status: SCI_SUCCESS); |
693 | |
694 | done: |
695 | spin_unlock_irqrestore(lock: &ihost->scic_lock, flags); |
696 | } |
697 | |
698 | /* |
699 | * ****************************************************************************** |
700 | * Public port configuration agent routines |
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 | */ |
708 | void sci_port_configuration_agent_construct( |
709 | struct sci_port_configuration_agent *port_agent) |
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; |
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 | |
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 | |
732 | enum sci_status sci_port_configuration_agent_initialize( |
733 | struct isci_host *ihost, |
734 | struct sci_port_configuration_agent *port_agent) |
735 | { |
736 | enum sci_status status; |
737 | enum sci_port_configuration_mode mode; |
738 | |
739 | mode = ihost->oem_parameters.controller.mode_type; |
740 | |
741 | if (mode == SCIC_PORT_MANUAL_CONFIGURATION_MODE) { |
742 | status = sci_mpc_agent_validate_phy_configuration( |
743 | ihost, port_agent); |
744 | |
745 | port_agent->link_up_handler = sci_mpc_agent_link_up; |
746 | port_agent->link_down_handler = sci_mpc_agent_link_down; |
747 | |
748 | sci_init_timer(tmr: &port_agent->timer, fn: mpc_agent_timeout); |
749 | } else { |
750 | status = sci_apc_agent_validate_phy_configuration( |
751 | ihost, port_agent); |
752 | |
753 | port_agent->link_up_handler = sci_apc_agent_link_up; |
754 | port_agent->link_down_handler = sci_apc_agent_link_down; |
755 | |
756 | sci_init_timer(tmr: &port_agent->timer, fn: apc_agent_timeout); |
757 | } |
758 | |
759 | return status; |
760 | } |
761 | |