1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Serial Attached SCSI (SAS) Port class |
4 | * |
5 | * Copyright (C) 2005 Adaptec, Inc. All rights reserved. |
6 | * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com> |
7 | */ |
8 | |
9 | #include "sas_internal.h" |
10 | |
11 | #include <scsi/scsi_transport.h> |
12 | #include <scsi/scsi_transport_sas.h> |
13 | #include "scsi_sas_internal.h" |
14 | |
15 | static bool phy_is_wideport_member(struct asd_sas_port *port, struct asd_sas_phy *phy) |
16 | { |
17 | struct sas_ha_struct *sas_ha = phy->ha; |
18 | |
19 | if (memcmp(p: port->attached_sas_addr, q: phy->attached_sas_addr, |
20 | SAS_ADDR_SIZE) != 0 || (sas_ha->strict_wide_ports && |
21 | memcmp(p: port->sas_addr, q: phy->sas_addr, SAS_ADDR_SIZE) != 0)) |
22 | return false; |
23 | return true; |
24 | } |
25 | |
26 | static void sas_resume_port(struct asd_sas_phy *phy) |
27 | { |
28 | struct domain_device *dev, *n; |
29 | struct asd_sas_port *port = phy->port; |
30 | struct sas_ha_struct *sas_ha = phy->ha; |
31 | struct sas_internal *si = to_sas_internal(sas_ha->shost->transportt); |
32 | |
33 | if (si->dft->lldd_port_formed) |
34 | si->dft->lldd_port_formed(phy); |
35 | |
36 | if (port->suspended) |
37 | port->suspended = 0; |
38 | else { |
39 | /* we only need to handle "link returned" actions once */ |
40 | return; |
41 | } |
42 | |
43 | /* if the port came back: |
44 | * 1/ presume every device came back |
45 | * 2/ force the next revalidation to check all expander phys |
46 | */ |
47 | list_for_each_entry_safe(dev, n, &port->dev_list, dev_list_node) { |
48 | int i, rc; |
49 | |
50 | rc = sas_notify_lldd_dev_found(dev); |
51 | if (rc) { |
52 | sas_unregister_dev(port, dev); |
53 | sas_destruct_devices(port); |
54 | continue; |
55 | } |
56 | |
57 | if (dev_is_expander(type: dev->dev_type)) { |
58 | dev->ex_dev.ex_change_count = -1; |
59 | for (i = 0; i < dev->ex_dev.num_phys; i++) { |
60 | struct ex_phy *phy = &dev->ex_dev.ex_phy[i]; |
61 | |
62 | phy->phy_change_count = -1; |
63 | } |
64 | } |
65 | } |
66 | |
67 | sas_discover_event(port, ev: DISCE_RESUME); |
68 | } |
69 | |
70 | static void sas_form_port_add_phy(struct asd_sas_port *port, |
71 | struct asd_sas_phy *phy, bool wideport) |
72 | { |
73 | list_add_tail(new: &phy->port_phy_el, head: &port->phy_list); |
74 | sas_phy_set_target(p: phy, dev: port->port_dev); |
75 | phy->port = port; |
76 | port->num_phys++; |
77 | port->phy_mask |= (1U << phy->id); |
78 | |
79 | if (wideport) |
80 | pr_debug("phy%d matched wide port%d\n" , phy->id, |
81 | port->id); |
82 | else |
83 | memcpy(port->sas_addr, phy->sas_addr, SAS_ADDR_SIZE); |
84 | |
85 | if (*(u64 *)port->attached_sas_addr == 0) { |
86 | memcpy(port->attached_sas_addr, phy->attached_sas_addr, |
87 | SAS_ADDR_SIZE); |
88 | port->iproto = phy->iproto; |
89 | port->tproto = phy->tproto; |
90 | port->oob_mode = phy->oob_mode; |
91 | port->linkrate = phy->linkrate; |
92 | } else { |
93 | port->linkrate = max(port->linkrate, phy->linkrate); |
94 | } |
95 | } |
96 | |
97 | /** |
98 | * sas_form_port - add this phy to a port |
99 | * @phy: the phy of interest |
100 | * |
101 | * This function adds this phy to an existing port, thus creating a wide |
102 | * port, or it creates a port and adds the phy to the port. |
103 | */ |
104 | static void sas_form_port(struct asd_sas_phy *phy) |
105 | { |
106 | int i; |
107 | struct sas_ha_struct *sas_ha = phy->ha; |
108 | struct asd_sas_port *port = phy->port; |
109 | struct domain_device *port_dev = NULL; |
110 | struct sas_internal *si = |
111 | to_sas_internal(sas_ha->shost->transportt); |
112 | unsigned long flags; |
113 | |
114 | if (port) { |
115 | if (!phy_is_wideport_member(port, phy)) |
116 | sas_deform_port(phy, gone: 0); |
117 | else if (phy->suspended) { |
118 | phy->suspended = 0; |
119 | sas_resume_port(phy); |
120 | |
121 | /* phy came back, try to cancel the timeout */ |
122 | wake_up(&sas_ha->eh_wait_q); |
123 | return; |
124 | } else { |
125 | pr_info("%s: phy%d belongs to port%d already(%d)!\n" , |
126 | __func__, phy->id, phy->port->id, |
127 | phy->port->num_phys); |
128 | return; |
129 | } |
130 | } |
131 | |
132 | /* see if the phy should be part of a wide port */ |
133 | spin_lock_irqsave(&sas_ha->phy_port_lock, flags); |
134 | for (i = 0; i < sas_ha->num_phys; i++) { |
135 | port = sas_ha->sas_port[i]; |
136 | spin_lock(lock: &port->phy_list_lock); |
137 | if (*(u64 *) port->sas_addr && |
138 | phy_is_wideport_member(port, phy) && port->num_phys > 0) { |
139 | /* wide port */ |
140 | port_dev = port->port_dev; |
141 | sas_form_port_add_phy(port, phy, wideport: true); |
142 | spin_unlock(lock: &port->phy_list_lock); |
143 | break; |
144 | } |
145 | spin_unlock(lock: &port->phy_list_lock); |
146 | } |
147 | /* The phy does not match any existing port, create a new one */ |
148 | if (i == sas_ha->num_phys) { |
149 | for (i = 0; i < sas_ha->num_phys; i++) { |
150 | port = sas_ha->sas_port[i]; |
151 | spin_lock(lock: &port->phy_list_lock); |
152 | if (*(u64 *)port->sas_addr == 0 |
153 | && port->num_phys == 0) { |
154 | port_dev = port->port_dev; |
155 | sas_form_port_add_phy(port, phy, wideport: false); |
156 | spin_unlock(lock: &port->phy_list_lock); |
157 | break; |
158 | } |
159 | spin_unlock(lock: &port->phy_list_lock); |
160 | } |
161 | |
162 | if (i >= sas_ha->num_phys) { |
163 | pr_err("%s: couldn't find a free port, bug?\n" , |
164 | __func__); |
165 | spin_unlock_irqrestore(lock: &sas_ha->phy_port_lock, flags); |
166 | return; |
167 | } |
168 | } |
169 | spin_unlock_irqrestore(lock: &sas_ha->phy_port_lock, flags); |
170 | |
171 | if (!port->port) { |
172 | port->port = sas_port_alloc(phy->phy->dev.parent, port->id); |
173 | BUG_ON(!port->port); |
174 | sas_port_add(port->port); |
175 | } |
176 | sas_port_add_phy(port->port, phy->phy); |
177 | |
178 | pr_debug("%s added to %s, phy_mask:0x%x (%016llx)\n" , |
179 | dev_name(&phy->phy->dev), dev_name(&port->port->dev), |
180 | port->phy_mask, |
181 | SAS_ADDR(port->attached_sas_addr)); |
182 | |
183 | if (port_dev) |
184 | port_dev->pathways = port->num_phys; |
185 | |
186 | /* Tell the LLDD about this port formation. */ |
187 | if (si->dft->lldd_port_formed) |
188 | si->dft->lldd_port_formed(phy); |
189 | |
190 | sas_discover_event(port: phy->port, ev: DISCE_DISCOVER_DOMAIN); |
191 | /* Only insert a revalidate event after initial discovery */ |
192 | if (port_dev && dev_is_expander(type: port_dev->dev_type)) { |
193 | struct expander_device *ex_dev = &port_dev->ex_dev; |
194 | |
195 | ex_dev->ex_change_count = -1; |
196 | sas_discover_event(port, ev: DISCE_REVALIDATE_DOMAIN); |
197 | } |
198 | flush_workqueue(sas_ha->disco_q); |
199 | } |
200 | |
201 | /** |
202 | * sas_deform_port - remove this phy from the port it belongs to |
203 | * @phy: the phy of interest |
204 | * @gone: whether or not the PHY is gone |
205 | * |
206 | * This is called when the physical link to the other phy has been |
207 | * lost (on this phy), in Event thread context. We cannot delay here. |
208 | */ |
209 | void sas_deform_port(struct asd_sas_phy *phy, int gone) |
210 | { |
211 | struct sas_ha_struct *sas_ha = phy->ha; |
212 | struct asd_sas_port *port = phy->port; |
213 | struct sas_internal *si = |
214 | to_sas_internal(sas_ha->shost->transportt); |
215 | struct domain_device *dev; |
216 | unsigned long flags; |
217 | |
218 | if (!port) |
219 | return; /* done by a phy event */ |
220 | |
221 | dev = port->port_dev; |
222 | if (dev) |
223 | dev->pathways--; |
224 | |
225 | if (port->num_phys == 1) { |
226 | sas_unregister_domain_devices(port, gone); |
227 | sas_destruct_devices(port); |
228 | sas_port_delete(port->port); |
229 | port->port = NULL; |
230 | } else { |
231 | sas_port_delete_phy(port->port, phy->phy); |
232 | sas_device_set_phy(dev, port: port->port); |
233 | } |
234 | |
235 | if (si->dft->lldd_port_deformed) |
236 | si->dft->lldd_port_deformed(phy); |
237 | |
238 | spin_lock_irqsave(&sas_ha->phy_port_lock, flags); |
239 | spin_lock(lock: &port->phy_list_lock); |
240 | |
241 | list_del_init(entry: &phy->port_phy_el); |
242 | sas_phy_set_target(p: phy, NULL); |
243 | phy->port = NULL; |
244 | port->num_phys--; |
245 | port->phy_mask &= ~(1U << phy->id); |
246 | |
247 | if (port->num_phys == 0) { |
248 | INIT_LIST_HEAD(list: &port->phy_list); |
249 | memset(port->sas_addr, 0, SAS_ADDR_SIZE); |
250 | memset(port->attached_sas_addr, 0, SAS_ADDR_SIZE); |
251 | port->iproto = 0; |
252 | port->tproto = 0; |
253 | port->oob_mode = 0; |
254 | port->phy_mask = 0; |
255 | } |
256 | spin_unlock(lock: &port->phy_list_lock); |
257 | spin_unlock_irqrestore(lock: &sas_ha->phy_port_lock, flags); |
258 | |
259 | /* Only insert revalidate event if the port still has members */ |
260 | if (port->port && dev && dev_is_expander(type: dev->dev_type)) { |
261 | struct expander_device *ex_dev = &dev->ex_dev; |
262 | |
263 | ex_dev->ex_change_count = -1; |
264 | sas_discover_event(port, ev: DISCE_REVALIDATE_DOMAIN); |
265 | } |
266 | flush_workqueue(sas_ha->disco_q); |
267 | |
268 | return; |
269 | } |
270 | |
271 | /* ---------- SAS port events ---------- */ |
272 | |
273 | void sas_porte_bytes_dmaed(struct work_struct *work) |
274 | { |
275 | struct asd_sas_event *ev = to_asd_sas_event(work); |
276 | struct asd_sas_phy *phy = ev->phy; |
277 | |
278 | sas_form_port(phy); |
279 | } |
280 | |
281 | void sas_porte_broadcast_rcvd(struct work_struct *work) |
282 | { |
283 | struct asd_sas_event *ev = to_asd_sas_event(work); |
284 | struct asd_sas_phy *phy = ev->phy; |
285 | unsigned long flags; |
286 | u32 prim; |
287 | |
288 | spin_lock_irqsave(&phy->sas_prim_lock, flags); |
289 | prim = phy->sas_prim; |
290 | spin_unlock_irqrestore(lock: &phy->sas_prim_lock, flags); |
291 | |
292 | pr_debug("broadcast received: %d\n" , prim); |
293 | sas_discover_event(port: phy->port, ev: DISCE_REVALIDATE_DOMAIN); |
294 | |
295 | if (phy->port) |
296 | flush_workqueue(phy->port->ha->disco_q); |
297 | } |
298 | |
299 | void sas_porte_link_reset_err(struct work_struct *work) |
300 | { |
301 | struct asd_sas_event *ev = to_asd_sas_event(work); |
302 | struct asd_sas_phy *phy = ev->phy; |
303 | |
304 | sas_deform_port(phy, gone: 1); |
305 | } |
306 | |
307 | void sas_porte_timer_event(struct work_struct *work) |
308 | { |
309 | struct asd_sas_event *ev = to_asd_sas_event(work); |
310 | struct asd_sas_phy *phy = ev->phy; |
311 | |
312 | sas_deform_port(phy, gone: 1); |
313 | } |
314 | |
315 | void sas_porte_hard_reset(struct work_struct *work) |
316 | { |
317 | struct asd_sas_event *ev = to_asd_sas_event(work); |
318 | struct asd_sas_phy *phy = ev->phy; |
319 | |
320 | sas_deform_port(phy, gone: 1); |
321 | } |
322 | |
323 | /* ---------- SAS port registration ---------- */ |
324 | |
325 | static void sas_init_port(struct asd_sas_port *port, |
326 | struct sas_ha_struct *sas_ha, int i) |
327 | { |
328 | memset(port, 0, sizeof(*port)); |
329 | port->id = i; |
330 | INIT_LIST_HEAD(list: &port->dev_list); |
331 | INIT_LIST_HEAD(list: &port->disco_list); |
332 | INIT_LIST_HEAD(list: &port->destroy_list); |
333 | INIT_LIST_HEAD(list: &port->sas_port_del_list); |
334 | spin_lock_init(&port->phy_list_lock); |
335 | INIT_LIST_HEAD(list: &port->phy_list); |
336 | port->ha = sas_ha; |
337 | |
338 | spin_lock_init(&port->dev_list_lock); |
339 | } |
340 | |
341 | int sas_register_ports(struct sas_ha_struct *sas_ha) |
342 | { |
343 | int i; |
344 | |
345 | /* initialize the ports and discovery */ |
346 | for (i = 0; i < sas_ha->num_phys; i++) { |
347 | struct asd_sas_port *port = sas_ha->sas_port[i]; |
348 | |
349 | sas_init_port(port, sas_ha, i); |
350 | sas_init_disc(disc: &port->disc, port); |
351 | } |
352 | return 0; |
353 | } |
354 | |
355 | void sas_unregister_ports(struct sas_ha_struct *sas_ha) |
356 | { |
357 | int i; |
358 | |
359 | for (i = 0; i < sas_ha->num_phys; i++) |
360 | if (sas_ha->sas_phy[i]->port) |
361 | sas_deform_port(phy: sas_ha->sas_phy[i], gone: 0); |
362 | |
363 | } |
364 | |
365 | const work_func_t sas_port_event_fns[PORT_NUM_EVENTS] = { |
366 | [PORTE_BYTES_DMAED] = sas_porte_bytes_dmaed, |
367 | [PORTE_BROADCAST_RCVD] = sas_porte_broadcast_rcvd, |
368 | [PORTE_LINK_RESET_ERR] = sas_porte_link_reset_err, |
369 | [PORTE_TIMER_EVENT] = sas_porte_timer_event, |
370 | [PORTE_HARD_RESET] = sas_porte_hard_reset, |
371 | }; |
372 | |