diff options
Diffstat (limited to 'drivers/scsi/isci/port_config.c')
-rw-r--r-- | drivers/scsi/isci/port_config.c | 754 |
1 files changed, 754 insertions, 0 deletions
diff --git a/drivers/scsi/isci/port_config.c b/drivers/scsi/isci/port_config.c new file mode 100644 index 00000000000..486b113c634 --- /dev/null +++ b/drivers/scsi/isci/port_config.c | |||
@@ -0,0 +1,754 @@ | |||
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 (100) | ||
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 | ||
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, &phy_sas_address); | ||
131 | sci_phy_get_attached_sas_address(iphy, &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, &port_sas_address); | ||
137 | sci_port_get_attached_sas_address(iport, &port_attached_device_address); | ||
138 | |||
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) | ||
141 | return iport; | ||
142 | } | ||
143 | |||
144 | return NULL; | ||
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 | */ | ||
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(&ihost->phys[0], &first_address); | ||
198 | sci_phy_get_sas_address(&ihost->phys[3], &second_address); | ||
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. */ | ||
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(&ihost->phys[0], &first_address); | ||
211 | sci_phy_get_sas_address(&ihost->phys[2], &second_address); | ||
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. */ | ||
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(&ihost->phys[1], &first_address); | ||
225 | sci_phy_get_sas_address(&ihost->phys[3], &second_address); | ||
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 | |||
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(&ihost->phys[phy_index], | ||
273 | &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 | while (phy_index < SCI_MAX_PHYS) { | ||
295 | if ((phy_mask & (1 << phy_index)) == 0) | ||
296 | continue; | ||
297 | sci_phy_get_sas_address(&ihost->phys[phy_index], | ||
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 | } | ||
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(&ihost->ports[port_index], | ||
311 | &ihost->phys[phy_index]); | ||
312 | |||
313 | assigned_phy_mask |= (1 << phy_index); | ||
314 | } | ||
315 | |||
316 | phy_index++; | ||
317 | } | ||
318 | |||
319 | return sci_port_configuration_agent_validate_ports(ihost, port_agent); | ||
320 | } | ||
321 | |||
322 | static void mpc_agent_timeout(unsigned long data) | ||
323 | { | ||
324 | u8 index; | ||
325 | struct sci_timer *tmr = (struct sci_timer *)data; | ||
326 | struct sci_port_configuration_agent *port_agent; | ||
327 | struct isci_host *ihost; | ||
328 | unsigned long flags; | ||
329 | u16 configure_phy_mask; | ||
330 | |||
331 | port_agent = container_of(tmr, typeof(*port_agent), timer); | ||
332 | ihost = container_of(port_agent, typeof(*ihost), port_agent); | ||
333 | |||
334 | spin_lock_irqsave(&ihost->scic_lock, flags); | ||
335 | |||
336 | if (tmr->cancel) | ||
337 | goto done; | ||
338 | |||
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++) { | ||
345 | struct isci_phy *iphy = &ihost->phys[index]; | ||
346 | |||
347 | if (configure_phy_mask & (1 << index)) { | ||
348 | port_agent->link_up_handler(ihost, port_agent, | ||
349 | phy_get_non_dummy_port(iphy), | ||
350 | iphy); | ||
351 | } | ||
352 | } | ||
353 | |||
354 | done: | ||
355 | spin_unlock_irqrestore(&ihost->scic_lock, flags); | ||
356 | } | ||
357 | |||
358 | static void sci_mpc_agent_link_up(struct isci_host *ihost, | ||
359 | struct sci_port_configuration_agent *port_agent, | ||
360 | struct isci_port *iport, | ||
361 | struct isci_phy *iphy) | ||
362 | { | ||
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. | ||
366 | */ | ||
367 | if (!iport) | ||
368 | return; | ||
369 | |||
370 | port_agent->phy_ready_mask |= (1 << iphy->phy_index); | ||
371 | sci_port_link_up(iport, iphy); | ||
372 | if ((iport->active_phy_mask & (1 << iphy->phy_index))) | ||
373 | port_agent->phy_configured_mask |= (1 << iphy->phy_index); | ||
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 | ||
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 | * @phy: 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(&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(&ihost->phys[phy_index], | ||
448 | &sas_address); | ||
449 | |||
450 | while (++phy_index < SCI_MAX_PHYS) { | ||
451 | sci_phy_get_sas_address(&ihost->phys[phy_index], | ||
452 | &phy_assigned_address); | ||
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 | |||
466 | return sci_port_configuration_agent_validate_ports(ihost, port_agent); | ||
467 | } | ||
468 | |||
469 | static void sci_apc_agent_configure_ports(struct isci_host *ihost, | ||
470 | struct sci_port_configuration_agent *port_agent, | ||
471 | struct isci_phy *iphy, | ||
472 | bool start_timer) | ||
473 | { | ||
474 | u8 port_index; | ||
475 | enum sci_status status; | ||
476 | struct isci_port *iport; | ||
477 | enum SCIC_SDS_APC_ACTIVITY apc_activity = SCIC_SDS_APC_SKIP_PHY; | ||
478 | |||
479 | iport = sci_port_configuration_agent_find_port(ihost, iphy); | ||
480 | |||
481 | if (iport) { | ||
482 | if (sci_port_is_valid_phy_assignment(iport, iphy->phy_index)) | ||
483 | apc_activity = SCIC_SDS_APC_ADD_PHY; | ||
484 | else | ||
485 | apc_activity = SCIC_SDS_APC_SKIP_PHY; | ||
486 | } else { | ||
487 | /* | ||
488 | * There is no matching Port for this PHY so lets search through the | ||
489 | * Ports and see if we can add the PHY to its own port or maybe start | ||
490 | * the timer and wait to see if a wider port can be made. | ||
491 | * | ||
492 | * Note the break when we reach the condition of the port id == phy id */ | ||
493 | for (port_index = port_agent->phy_valid_port_range[iphy->phy_index].min_index; | ||
494 | port_index <= port_agent->phy_valid_port_range[iphy->phy_index].max_index; | ||
495 | port_index++) { | ||
496 | |||
497 | iport = &ihost->ports[port_index]; | ||
498 | |||
499 | /* First we must make sure that this PHY can be added to this Port. */ | ||
500 | if (sci_port_is_valid_phy_assignment(iport, iphy->phy_index)) { | ||
501 | /* | ||
502 | * Port contains a PHY with a greater PHY ID than the current | ||
503 | * PHY that has gone link up. This phy can not be part of any | ||
504 | * port so skip it and move on. */ | ||
505 | if (iport->active_phy_mask > (1 << iphy->phy_index)) { | ||
506 | apc_activity = SCIC_SDS_APC_SKIP_PHY; | ||
507 | break; | ||
508 | } | ||
509 | |||
510 | /* | ||
511 | * We have reached the end of our Port list and have not found | ||
512 | * any reason why we should not either add the PHY to the port | ||
513 | * or wait for more phys to become active. */ | ||
514 | if (iport->physical_port_index == iphy->phy_index) { | ||
515 | /* | ||
516 | * The Port either has no active PHYs. | ||
517 | * Consider that if the port had any active PHYs we would have | ||
518 | * or active PHYs with | ||
519 | * a lower PHY Id than this PHY. */ | ||
520 | if (apc_activity != SCIC_SDS_APC_START_TIMER) { | ||
521 | apc_activity = SCIC_SDS_APC_ADD_PHY; | ||
522 | } | ||
523 | |||
524 | break; | ||
525 | } | ||
526 | |||
527 | /* | ||
528 | * The current Port has no active PHYs and this PHY could be part | ||
529 | * of this Port. Since we dont know as yet setup to start the | ||
530 | * timer and see if there is a better configuration. */ | ||
531 | if (iport->active_phy_mask == 0) { | ||
532 | apc_activity = SCIC_SDS_APC_START_TIMER; | ||
533 | } | ||
534 | } else if (iport->active_phy_mask != 0) { | ||
535 | /* | ||
536 | * The Port has an active phy and the current Phy can not | ||
537 | * participate in this port so skip the PHY and see if | ||
538 | * there is a better configuration. */ | ||
539 | apc_activity = SCIC_SDS_APC_SKIP_PHY; | ||
540 | } | ||
541 | } | ||
542 | } | ||
543 | |||
544 | /* | ||
545 | * Check to see if the start timer operations should instead map to an | ||
546 | * add phy operation. This is caused because we have been waiting to | ||
547 | * add a phy to a port but could not becuase the automatic port | ||
548 | * configuration engine had a choice of possible ports for the phy. | ||
549 | * Since we have gone through a timeout we are going to restrict the | ||
550 | * choice to the smallest possible port. */ | ||
551 | if ( | ||
552 | (start_timer == false) | ||
553 | && (apc_activity == SCIC_SDS_APC_START_TIMER) | ||
554 | ) { | ||
555 | apc_activity = SCIC_SDS_APC_ADD_PHY; | ||
556 | } | ||
557 | |||
558 | switch (apc_activity) { | ||
559 | case SCIC_SDS_APC_ADD_PHY: | ||
560 | status = sci_port_add_phy(iport, iphy); | ||
561 | |||
562 | if (status == SCI_SUCCESS) { | ||
563 | port_agent->phy_configured_mask |= (1 << iphy->phy_index); | ||
564 | } | ||
565 | break; | ||
566 | |||
567 | case SCIC_SDS_APC_START_TIMER: | ||
568 | /* | ||
569 | * This can occur for either a link down event, or a link | ||
570 | * up event where we cannot yet tell the port to which a | ||
571 | * phy belongs. | ||
572 | */ | ||
573 | if (port_agent->timer_pending) | ||
574 | sci_del_timer(&port_agent->timer); | ||
575 | |||
576 | port_agent->timer_pending = true; | ||
577 | sci_mod_timer(&port_agent->timer, | ||
578 | SCIC_SDS_APC_WAIT_LINK_UP_NOTIFICATION); | ||
579 | break; | ||
580 | |||
581 | case SCIC_SDS_APC_SKIP_PHY: | ||
582 | default: | ||
583 | /* do nothing the PHY can not be made part of a port at this time. */ | ||
584 | break; | ||
585 | } | ||
586 | } | ||
587 | |||
588 | /** | ||
589 | * sci_apc_agent_link_up - handle apc link up events | ||
590 | * @scic: This is the controller object that receives the link up | ||
591 | * notification. | ||
592 | * @sci_port: This is the port object associated with the phy. If the is no | ||
593 | * associated port this is an NULL. | ||
594 | * @sci_phy: This is the phy object which has gone link up. | ||
595 | * | ||
596 | * This method handles the automatic port configuration for link up | ||
597 | * notifications. Is it possible to get a link down notification from a phy | ||
598 | * that has no assocoated port? | ||
599 | */ | ||
600 | static void sci_apc_agent_link_up(struct isci_host *ihost, | ||
601 | struct sci_port_configuration_agent *port_agent, | ||
602 | struct isci_port *iport, | ||
603 | struct isci_phy *iphy) | ||
604 | { | ||
605 | u8 phy_index = iphy->phy_index; | ||
606 | |||
607 | if (!iport) { | ||
608 | /* the phy is not the part of this port */ | ||
609 | port_agent->phy_ready_mask |= 1 << phy_index; | ||
610 | sci_apc_agent_configure_ports(ihost, port_agent, iphy, true); | ||
611 | } else { | ||
612 | /* the phy is already the part of the port */ | ||
613 | u32 port_state = iport->sm.current_state_id; | ||
614 | |||
615 | /* if the PORT'S state is resetting then the link up is from | ||
616 | * port hard reset in this case, we need to tell the port | ||
617 | * that link up is recieved | ||
618 | */ | ||
619 | BUG_ON(port_state != SCI_PORT_RESETTING); | ||
620 | port_agent->phy_ready_mask |= 1 << phy_index; | ||
621 | sci_port_link_up(iport, iphy); | ||
622 | } | ||
623 | } | ||
624 | |||
625 | /** | ||
626 | * | ||
627 | * @controller: This is the controller object that receives the link down | ||
628 | * notification. | ||
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(unsigned long data) | ||
660 | { | ||
661 | u32 index; | ||
662 | struct sci_timer *tmr = (struct sci_timer *)data; | ||
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 | return; | ||
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 | &ihost->phys[index], false); | ||
689 | } | ||
690 | |||
691 | done: | ||
692 | spin_unlock_irqrestore(&ihost->scic_lock, flags); | ||
693 | } | ||
694 | |||
695 | /* | ||
696 | * ****************************************************************************** | ||
697 | * Public port configuration agent routines | ||
698 | * ****************************************************************************** */ | ||
699 | |||
700 | /** | ||
701 | * | ||
702 | * | ||
703 | * This method will construct the port configuration agent for operation. This | ||
704 | * call is universal for both manual port configuration and automatic port | ||
705 | * configuration modes. | ||
706 | */ | ||
707 | void sci_port_configuration_agent_construct( | ||
708 | struct sci_port_configuration_agent *port_agent) | ||
709 | { | ||
710 | u32 index; | ||
711 | |||
712 | port_agent->phy_configured_mask = 0x00; | ||
713 | port_agent->phy_ready_mask = 0x00; | ||
714 | |||
715 | port_agent->link_up_handler = NULL; | ||
716 | port_agent->link_down_handler = NULL; | ||
717 | |||
718 | port_agent->timer_pending = false; | ||
719 | |||
720 | for (index = 0; index < SCI_MAX_PORTS; index++) { | ||
721 | port_agent->phy_valid_port_range[index].min_index = 0; | ||
722 | port_agent->phy_valid_port_range[index].max_index = 0; | ||
723 | } | ||
724 | } | ||
725 | |||
726 | enum sci_status sci_port_configuration_agent_initialize( | ||
727 | struct isci_host *ihost, | ||
728 | struct sci_port_configuration_agent *port_agent) | ||
729 | { | ||
730 | enum sci_status status; | ||
731 | enum sci_port_configuration_mode mode; | ||
732 | |||
733 | mode = ihost->oem_parameters.controller.mode_type; | ||
734 | |||
735 | if (mode == SCIC_PORT_MANUAL_CONFIGURATION_MODE) { | ||
736 | status = sci_mpc_agent_validate_phy_configuration( | ||
737 | ihost, port_agent); | ||
738 | |||
739 | port_agent->link_up_handler = sci_mpc_agent_link_up; | ||
740 | port_agent->link_down_handler = sci_mpc_agent_link_down; | ||
741 | |||
742 | sci_init_timer(&port_agent->timer, mpc_agent_timeout); | ||
743 | } else { | ||
744 | status = sci_apc_agent_validate_phy_configuration( | ||
745 | ihost, port_agent); | ||
746 | |||
747 | port_agent->link_up_handler = sci_apc_agent_link_up; | ||
748 | port_agent->link_down_handler = sci_apc_agent_link_down; | ||
749 | |||
750 | sci_init_timer(&port_agent->timer, apc_agent_timeout); | ||
751 | } | ||
752 | |||
753 | return status; | ||
754 | } | ||