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) | |
472d4d2c | 60 | #define SCIC_SDS_APC_WAIT_LINK_UP_NOTIFICATION (250) |
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); |
6f231dda | 314 | } |
4b33981a DW |
315 | |
316 | phy_index++; | |
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 | */ | |
475 | static void sci_apc_agent_start_timer( | |
476 | struct sci_port_configuration_agent *port_agent, | |
477 | u32 timeout) | |
478 | { | |
479 | if (port_agent->timer_pending) | |
480 | sci_del_timer(&port_agent->timer); | |
481 | ||
482 | port_agent->timer_pending = true; | |
483 | sci_mod_timer(&port_agent->timer, timeout); | |
484 | } | |
485 | ||
89a7301f DW |
486 | static void sci_apc_agent_configure_ports(struct isci_host *ihost, |
487 | struct sci_port_configuration_agent *port_agent, | |
85280955 DW |
488 | struct isci_phy *iphy, |
489 | bool start_timer) | |
6f231dda DW |
490 | { |
491 | u8 port_index; | |
492 | enum sci_status status; | |
ffe191c9 | 493 | struct isci_port *iport; |
6f231dda DW |
494 | enum SCIC_SDS_APC_ACTIVITY apc_activity = SCIC_SDS_APC_SKIP_PHY; |
495 | ||
89a7301f | 496 | iport = sci_port_configuration_agent_find_port(ihost, iphy); |
6f231dda | 497 | |
ffe191c9 | 498 | if (iport) { |
89a7301f | 499 | if (sci_port_is_valid_phy_assignment(iport, iphy->phy_index)) |
6f231dda DW |
500 | apc_activity = SCIC_SDS_APC_ADD_PHY; |
501 | else | |
502 | apc_activity = SCIC_SDS_APC_SKIP_PHY; | |
503 | } else { | |
504 | /* | |
505 | * There is no matching Port for this PHY so lets search through the | |
506 | * Ports and see if we can add the PHY to its own port or maybe start | |
507 | * the timer and wait to see if a wider port can be made. | |
508 | * | |
509 | * Note the break when we reach the condition of the port id == phy id */ | |
ffe191c9 DW |
510 | for (port_index = port_agent->phy_valid_port_range[iphy->phy_index].min_index; |
511 | port_index <= port_agent->phy_valid_port_range[iphy->phy_index].max_index; | |
512 | port_index++) { | |
6f231dda | 513 | |
ffe191c9 | 514 | iport = &ihost->ports[port_index]; |
6f231dda DW |
515 | |
516 | /* First we must make sure that this PHY can be added to this Port. */ | |
89a7301f | 517 | if (sci_port_is_valid_phy_assignment(iport, iphy->phy_index)) { |
6f231dda DW |
518 | /* |
519 | * Port contains a PHY with a greater PHY ID than the current | |
520 | * PHY that has gone link up. This phy can not be part of any | |
521 | * port so skip it and move on. */ | |
ffe191c9 | 522 | if (iport->active_phy_mask > (1 << iphy->phy_index)) { |
6f231dda DW |
523 | apc_activity = SCIC_SDS_APC_SKIP_PHY; |
524 | break; | |
525 | } | |
526 | ||
527 | /* | |
528 | * We have reached the end of our Port list and have not found | |
529 | * any reason why we should not either add the PHY to the port | |
530 | * or wait for more phys to become active. */ | |
ffe191c9 | 531 | if (iport->physical_port_index == iphy->phy_index) { |
6f231dda DW |
532 | /* |
533 | * The Port either has no active PHYs. | |
534 | * Consider that if the port had any active PHYs we would have | |
535 | * or active PHYs with | |
536 | * a lower PHY Id than this PHY. */ | |
537 | if (apc_activity != SCIC_SDS_APC_START_TIMER) { | |
538 | apc_activity = SCIC_SDS_APC_ADD_PHY; | |
539 | } | |
540 | ||
541 | break; | |
542 | } | |
543 | ||
544 | /* | |
545 | * The current Port has no active PHYs and this PHY could be part | |
546 | * of this Port. Since we dont know as yet setup to start the | |
547 | * timer and see if there is a better configuration. */ | |
ffe191c9 | 548 | if (iport->active_phy_mask == 0) { |
6f231dda DW |
549 | apc_activity = SCIC_SDS_APC_START_TIMER; |
550 | } | |
ffe191c9 | 551 | } else if (iport->active_phy_mask != 0) { |
6f231dda DW |
552 | /* |
553 | * The Port has an active phy and the current Phy can not | |
554 | * participate in this port so skip the PHY and see if | |
555 | * there is a better configuration. */ | |
556 | apc_activity = SCIC_SDS_APC_SKIP_PHY; | |
557 | } | |
558 | } | |
559 | } | |
560 | ||
561 | /* | |
562 | * Check to see if the start timer operations should instead map to an | |
563 | * add phy operation. This is caused because we have been waiting to | |
564 | * add a phy to a port but could not becuase the automatic port | |
565 | * configuration engine had a choice of possible ports for the phy. | |
566 | * Since we have gone through a timeout we are going to restrict the | |
567 | * choice to the smallest possible port. */ | |
568 | if ( | |
569 | (start_timer == false) | |
570 | && (apc_activity == SCIC_SDS_APC_START_TIMER) | |
571 | ) { | |
572 | apc_activity = SCIC_SDS_APC_ADD_PHY; | |
573 | } | |
574 | ||
575 | switch (apc_activity) { | |
576 | case SCIC_SDS_APC_ADD_PHY: | |
89a7301f | 577 | status = sci_port_add_phy(iport, iphy); |
6f231dda DW |
578 | |
579 | if (status == SCI_SUCCESS) { | |
85280955 | 580 | port_agent->phy_configured_mask |= (1 << iphy->phy_index); |
6f231dda DW |
581 | } |
582 | break; | |
583 | ||
584 | case SCIC_SDS_APC_START_TIMER: | |
be778341 MT |
585 | sci_apc_agent_start_timer(port_agent, |
586 | SCIC_SDS_APC_WAIT_LINK_UP_NOTIFICATION); | |
6f231dda DW |
587 | break; |
588 | ||
589 | case SCIC_SDS_APC_SKIP_PHY: | |
590 | default: | |
591 | /* do nothing the PHY can not be made part of a port at this time. */ | |
592 | break; | |
593 | } | |
594 | } | |
595 | ||
596 | /** | |
89a7301f | 597 | * sci_apc_agent_link_up - handle apc link up events |
b3824292 | 598 | * @scic: This is the controller object that receives the link up |
6f231dda | 599 | * notification. |
b3824292 | 600 | * @sci_port: This is the port object associated with the phy. If the is no |
a7e536c7 | 601 | * associated port this is an NULL. |
b3824292 | 602 | * @sci_phy: This is the phy object which has gone link up. |
6f231dda DW |
603 | * |
604 | * This method handles the automatic port configuration for link up | |
605 | * notifications. Is it possible to get a link down notification from a phy | |
606 | * that has no assocoated port? | |
607 | */ | |
89a7301f DW |
608 | static void sci_apc_agent_link_up(struct isci_host *ihost, |
609 | struct sci_port_configuration_agent *port_agent, | |
ffe191c9 | 610 | struct isci_port *iport, |
85280955 | 611 | struct isci_phy *iphy) |
6f231dda | 612 | { |
85280955 | 613 | u8 phy_index = iphy->phy_index; |
6f231dda | 614 | |
ffe191c9 | 615 | if (!iport) { |
b3824292 PS |
616 | /* the phy is not the part of this port */ |
617 | port_agent->phy_ready_mask |= 1 << phy_index; | |
be778341 MT |
618 | sci_apc_agent_start_timer(port_agent, |
619 | SCIC_SDS_APC_WAIT_LINK_UP_NOTIFICATION); | |
b3824292 PS |
620 | } else { |
621 | /* the phy is already the part of the port */ | |
ffe191c9 | 622 | u32 port_state = iport->sm.current_state_id; |
b3824292 PS |
623 | |
624 | /* if the PORT'S state is resetting then the link up is from | |
625 | * port hard reset in this case, we need to tell the port | |
626 | * that link up is recieved | |
627 | */ | |
e301370a | 628 | BUG_ON(port_state != SCI_PORT_RESETTING); |
b3824292 | 629 | port_agent->phy_ready_mask |= 1 << phy_index; |
89a7301f | 630 | sci_port_link_up(iport, iphy); |
b3824292 | 631 | } |
6f231dda DW |
632 | } |
633 | ||
634 | /** | |
635 | * | |
636 | * @controller: This is the controller object that receives the link down | |
637 | * notification. | |
ffe191c9 | 638 | * @iport: This is the port object associated with the phy. If the is no |
a7e536c7 | 639 | * associated port this is an NULL. |
ffe191c9 | 640 | * @iphy: This is the phy object which has gone link down. |
6f231dda DW |
641 | * |
642 | * This method handles the automatic port configuration link down | |
643 | * notifications. not associated with a port there is no action taken. Is it | |
644 | * possible to get a link down notification from a phy that has no assocoated | |
645 | * port? | |
646 | */ | |
89a7301f | 647 | static void sci_apc_agent_link_down( |
d9dcb4ba | 648 | struct isci_host *ihost, |
89a7301f | 649 | struct sci_port_configuration_agent *port_agent, |
ffe191c9 | 650 | struct isci_port *iport, |
85280955 | 651 | struct isci_phy *iphy) |
6f231dda | 652 | { |
34a99158 | 653 | port_agent->phy_ready_mask &= ~(1 << iphy->phy_index); |
6f231dda | 654 | |
ffe191c9 DW |
655 | if (!iport) |
656 | return; | |
657 | if (port_agent->phy_configured_mask & (1 << iphy->phy_index)) { | |
658 | enum sci_status status; | |
6f231dda | 659 | |
89a7301f | 660 | status = sci_port_remove_phy(iport, iphy); |
6f231dda | 661 | |
ffe191c9 DW |
662 | if (status == SCI_SUCCESS) |
663 | port_agent->phy_configured_mask &= ~(1 << iphy->phy_index); | |
6f231dda DW |
664 | } |
665 | } | |
666 | ||
4b33981a | 667 | /* configure the phys into ports when the timer fires */ |
ac0eeb4f | 668 | static void apc_agent_timeout(unsigned long data) |
6f231dda DW |
669 | { |
670 | u32 index; | |
ac0eeb4f | 671 | struct sci_timer *tmr = (struct sci_timer *)data; |
89a7301f | 672 | struct sci_port_configuration_agent *port_agent; |
ac0eeb4f EN |
673 | struct isci_host *ihost; |
674 | unsigned long flags; | |
6f231dda DW |
675 | u16 configure_phy_mask; |
676 | ||
ac0eeb4f | 677 | port_agent = container_of(tmr, typeof(*port_agent), timer); |
d9dcb4ba | 678 | ihost = container_of(port_agent, typeof(*ihost), port_agent); |
ac0eeb4f EN |
679 | |
680 | spin_lock_irqsave(&ihost->scic_lock, flags); | |
681 | ||
682 | if (tmr->cancel) | |
683 | goto done; | |
6f231dda DW |
684 | |
685 | port_agent->timer_pending = false; | |
686 | ||
687 | configure_phy_mask = ~port_agent->phy_configured_mask & port_agent->phy_ready_mask; | |
688 | ||
4b33981a | 689 | if (!configure_phy_mask) |
983d3fdd | 690 | goto done; |
4b33981a DW |
691 | |
692 | for (index = 0; index < SCI_MAX_PHYS; index++) { | |
693 | if ((configure_phy_mask & (1 << index)) == 0) | |
694 | continue; | |
695 | ||
89a7301f | 696 | sci_apc_agent_configure_ports(ihost, port_agent, |
85280955 | 697 | &ihost->phys[index], false); |
6f231dda | 698 | } |
ac0eeb4f EN |
699 | |
700 | done: | |
701 | spin_unlock_irqrestore(&ihost->scic_lock, flags); | |
6f231dda DW |
702 | } |
703 | ||
704 | /* | |
705 | * ****************************************************************************** | |
706 | * Public port configuration agent routines | |
707 | * ****************************************************************************** */ | |
708 | ||
709 | /** | |
710 | * | |
711 | * | |
712 | * This method will construct the port configuration agent for operation. This | |
713 | * call is universal for both manual port configuration and automatic port | |
714 | * configuration modes. | |
715 | */ | |
89a7301f DW |
716 | void sci_port_configuration_agent_construct( |
717 | struct sci_port_configuration_agent *port_agent) | |
6f231dda DW |
718 | { |
719 | u32 index; | |
720 | ||
721 | port_agent->phy_configured_mask = 0x00; | |
722 | port_agent->phy_ready_mask = 0x00; | |
723 | ||
724 | port_agent->link_up_handler = NULL; | |
725 | port_agent->link_down_handler = NULL; | |
726 | ||
727 | port_agent->timer_pending = false; | |
6f231dda DW |
728 | |
729 | for (index = 0; index < SCI_MAX_PORTS; index++) { | |
730 | port_agent->phy_valid_port_range[index].min_index = 0; | |
731 | port_agent->phy_valid_port_range[index].max_index = 0; | |
732 | } | |
733 | } | |
734 | ||
89a7301f | 735 | enum sci_status sci_port_configuration_agent_initialize( |
d9dcb4ba | 736 | struct isci_host *ihost, |
89a7301f | 737 | struct sci_port_configuration_agent *port_agent) |
6f231dda | 738 | { |
ac0eeb4f | 739 | enum sci_status status; |
89a7301f | 740 | enum sci_port_configuration_mode mode; |
6f231dda | 741 | |
89a7301f | 742 | mode = ihost->oem_parameters.controller.mode_type; |
6f231dda DW |
743 | |
744 | if (mode == SCIC_PORT_MANUAL_CONFIGURATION_MODE) { | |
89a7301f | 745 | status = sci_mpc_agent_validate_phy_configuration( |
d9dcb4ba | 746 | ihost, port_agent); |
6f231dda | 747 | |
89a7301f DW |
748 | port_agent->link_up_handler = sci_mpc_agent_link_up; |
749 | port_agent->link_down_handler = sci_mpc_agent_link_down; | |
6f231dda | 750 | |
ac0eeb4f | 751 | sci_init_timer(&port_agent->timer, mpc_agent_timeout); |
6f231dda | 752 | } else { |
89a7301f | 753 | status = sci_apc_agent_validate_phy_configuration( |
d9dcb4ba | 754 | ihost, port_agent); |
6f231dda | 755 | |
89a7301f DW |
756 | port_agent->link_up_handler = sci_apc_agent_link_up; |
757 | port_agent->link_down_handler = sci_apc_agent_link_down; | |
6f231dda | 758 | |
ac0eeb4f | 759 | sci_init_timer(&port_agent->timer, apc_agent_timeout); |
6f231dda DW |
760 | } |
761 | ||
762 | return status; | |
763 | } |