]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright(c) 2011 - 2012 Intel Corporation. All rights reserved. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify it | |
5 | * under the terms and conditions of the GNU General Public License, | |
6 | * version 2, as published by the Free Software Foundation. | |
7 | * | |
8 | * This program is distributed in the hope it will be useful, but WITHOUT | |
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
11 | * more details. | |
12 | * | |
13 | * You should have received a copy of the GNU General Public License along with | |
14 | * this program; if not, write to the Free Software Foundation, Inc., | |
15 | * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | |
16 | * | |
17 | * Maintained at www.Open-FCoE.org | |
18 | */ | |
19 | ||
20 | #include <linux/module.h> | |
21 | #include <linux/types.h> | |
22 | #include <linux/kernel.h> | |
23 | #include <linux/etherdevice.h> | |
24 | #include <linux/ctype.h> | |
25 | ||
26 | #include <scsi/fcoe_sysfs.h> | |
27 | #include <scsi/libfcoe.h> | |
28 | ||
29 | /* | |
30 | * OK to include local libfcoe.h for debug_logging, but cannot include | |
31 | * <scsi/libfcoe.h> otherwise non-netdev based fcoe solutions would have | |
32 | * have to include more than fcoe_sysfs.h. | |
33 | */ | |
34 | #include "libfcoe.h" | |
35 | ||
36 | static atomic_t ctlr_num; | |
37 | static atomic_t fcf_num; | |
38 | ||
39 | /* | |
40 | * fcoe_fcf_dev_loss_tmo: the default number of seconds that fcoe sysfs | |
41 | * should insulate the loss of a fcf. | |
42 | */ | |
43 | static unsigned int fcoe_fcf_dev_loss_tmo = 1800; /* seconds */ | |
44 | ||
45 | module_param_named(fcf_dev_loss_tmo, fcoe_fcf_dev_loss_tmo, | |
46 | uint, S_IRUGO|S_IWUSR); | |
47 | MODULE_PARM_DESC(fcf_dev_loss_tmo, | |
48 | "Maximum number of seconds that libfcoe should" | |
49 | " insulate the loss of a fcf. Once this value is" | |
50 | " exceeded, the fcf is removed."); | |
51 | ||
52 | /* | |
53 | * These are used by the fcoe_*_show_function routines, they | |
54 | * are intentionally placed in the .c file as they're not intended | |
55 | * for use throughout the code. | |
56 | */ | |
57 | #define fcoe_ctlr_id(x) \ | |
58 | ((x)->id) | |
59 | #define fcoe_ctlr_work_q_name(x) \ | |
60 | ((x)->work_q_name) | |
61 | #define fcoe_ctlr_work_q(x) \ | |
62 | ((x)->work_q) | |
63 | #define fcoe_ctlr_devloss_work_q_name(x) \ | |
64 | ((x)->devloss_work_q_name) | |
65 | #define fcoe_ctlr_devloss_work_q(x) \ | |
66 | ((x)->devloss_work_q) | |
67 | #define fcoe_ctlr_mode(x) \ | |
68 | ((x)->mode) | |
69 | #define fcoe_ctlr_fcf_dev_loss_tmo(x) \ | |
70 | ((x)->fcf_dev_loss_tmo) | |
71 | #define fcoe_ctlr_link_fail(x) \ | |
72 | ((x)->lesb.lesb_link_fail) | |
73 | #define fcoe_ctlr_vlink_fail(x) \ | |
74 | ((x)->lesb.lesb_vlink_fail) | |
75 | #define fcoe_ctlr_miss_fka(x) \ | |
76 | ((x)->lesb.lesb_miss_fka) | |
77 | #define fcoe_ctlr_symb_err(x) \ | |
78 | ((x)->lesb.lesb_symb_err) | |
79 | #define fcoe_ctlr_err_block(x) \ | |
80 | ((x)->lesb.lesb_err_block) | |
81 | #define fcoe_ctlr_fcs_error(x) \ | |
82 | ((x)->lesb.lesb_fcs_error) | |
83 | #define fcoe_ctlr_enabled(x) \ | |
84 | ((x)->enabled) | |
85 | #define fcoe_fcf_state(x) \ | |
86 | ((x)->state) | |
87 | #define fcoe_fcf_fabric_name(x) \ | |
88 | ((x)->fabric_name) | |
89 | #define fcoe_fcf_switch_name(x) \ | |
90 | ((x)->switch_name) | |
91 | #define fcoe_fcf_fc_map(x) \ | |
92 | ((x)->fc_map) | |
93 | #define fcoe_fcf_vfid(x) \ | |
94 | ((x)->vfid) | |
95 | #define fcoe_fcf_mac(x) \ | |
96 | ((x)->mac) | |
97 | #define fcoe_fcf_priority(x) \ | |
98 | ((x)->priority) | |
99 | #define fcoe_fcf_fka_period(x) \ | |
100 | ((x)->fka_period) | |
101 | #define fcoe_fcf_dev_loss_tmo(x) \ | |
102 | ((x)->dev_loss_tmo) | |
103 | #define fcoe_fcf_selected(x) \ | |
104 | ((x)->selected) | |
105 | #define fcoe_fcf_vlan_id(x) \ | |
106 | ((x)->vlan_id) | |
107 | ||
108 | /* | |
109 | * dev_loss_tmo attribute | |
110 | */ | |
111 | static int fcoe_str_to_dev_loss(const char *buf, unsigned long *val) | |
112 | { | |
113 | int ret; | |
114 | ||
115 | ret = kstrtoul(buf, 0, val); | |
116 | if (ret) | |
117 | return -EINVAL; | |
118 | /* | |
119 | * Check for overflow; dev_loss_tmo is u32 | |
120 | */ | |
121 | if (*val > UINT_MAX) | |
122 | return -EINVAL; | |
123 | ||
124 | return 0; | |
125 | } | |
126 | ||
127 | static int fcoe_fcf_set_dev_loss_tmo(struct fcoe_fcf_device *fcf, | |
128 | unsigned long val) | |
129 | { | |
130 | if ((fcf->state == FCOE_FCF_STATE_UNKNOWN) || | |
131 | (fcf->state == FCOE_FCF_STATE_DISCONNECTED) || | |
132 | (fcf->state == FCOE_FCF_STATE_DELETED)) | |
133 | return -EBUSY; | |
134 | /* | |
135 | * Check for overflow; dev_loss_tmo is u32 | |
136 | */ | |
137 | if (val > UINT_MAX) | |
138 | return -EINVAL; | |
139 | ||
140 | fcoe_fcf_dev_loss_tmo(fcf) = val; | |
141 | return 0; | |
142 | } | |
143 | ||
144 | #define FCOE_DEVICE_ATTR(_prefix, _name, _mode, _show, _store) \ | |
145 | struct device_attribute device_attr_fcoe_##_prefix##_##_name = \ | |
146 | __ATTR(_name, _mode, _show, _store) | |
147 | ||
148 | #define fcoe_ctlr_show_function(field, format_string, sz, cast) \ | |
149 | static ssize_t show_fcoe_ctlr_device_##field(struct device *dev, \ | |
150 | struct device_attribute *attr, \ | |
151 | char *buf) \ | |
152 | { \ | |
153 | struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); \ | |
154 | if (ctlr->f->get_fcoe_ctlr_##field) \ | |
155 | ctlr->f->get_fcoe_ctlr_##field(ctlr); \ | |
156 | return snprintf(buf, sz, format_string, \ | |
157 | cast fcoe_ctlr_##field(ctlr)); \ | |
158 | } | |
159 | ||
160 | #define fcoe_fcf_show_function(field, format_string, sz, cast) \ | |
161 | static ssize_t show_fcoe_fcf_device_##field(struct device *dev, \ | |
162 | struct device_attribute *attr, \ | |
163 | char *buf) \ | |
164 | { \ | |
165 | struct fcoe_fcf_device *fcf = dev_to_fcf(dev); \ | |
166 | struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); \ | |
167 | if (ctlr->f->get_fcoe_fcf_##field) \ | |
168 | ctlr->f->get_fcoe_fcf_##field(fcf); \ | |
169 | return snprintf(buf, sz, format_string, \ | |
170 | cast fcoe_fcf_##field(fcf)); \ | |
171 | } | |
172 | ||
173 | #define fcoe_ctlr_private_show_function(field, format_string, sz, cast) \ | |
174 | static ssize_t show_fcoe_ctlr_device_##field(struct device *dev, \ | |
175 | struct device_attribute *attr, \ | |
176 | char *buf) \ | |
177 | { \ | |
178 | struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); \ | |
179 | return snprintf(buf, sz, format_string, cast fcoe_ctlr_##field(ctlr)); \ | |
180 | } | |
181 | ||
182 | #define fcoe_fcf_private_show_function(field, format_string, sz, cast) \ | |
183 | static ssize_t show_fcoe_fcf_device_##field(struct device *dev, \ | |
184 | struct device_attribute *attr, \ | |
185 | char *buf) \ | |
186 | { \ | |
187 | struct fcoe_fcf_device *fcf = dev_to_fcf(dev); \ | |
188 | return snprintf(buf, sz, format_string, cast fcoe_fcf_##field(fcf)); \ | |
189 | } | |
190 | ||
191 | #define fcoe_ctlr_private_rd_attr(field, format_string, sz) \ | |
192 | fcoe_ctlr_private_show_function(field, format_string, sz, ) \ | |
193 | static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO, \ | |
194 | show_fcoe_ctlr_device_##field, NULL) | |
195 | ||
196 | #define fcoe_ctlr_rd_attr(field, format_string, sz) \ | |
197 | fcoe_ctlr_show_function(field, format_string, sz, ) \ | |
198 | static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO, \ | |
199 | show_fcoe_ctlr_device_##field, NULL) | |
200 | ||
201 | #define fcoe_fcf_rd_attr(field, format_string, sz) \ | |
202 | fcoe_fcf_show_function(field, format_string, sz, ) \ | |
203 | static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO, \ | |
204 | show_fcoe_fcf_device_##field, NULL) | |
205 | ||
206 | #define fcoe_fcf_private_rd_attr(field, format_string, sz) \ | |
207 | fcoe_fcf_private_show_function(field, format_string, sz, ) \ | |
208 | static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO, \ | |
209 | show_fcoe_fcf_device_##field, NULL) | |
210 | ||
211 | #define fcoe_ctlr_private_rd_attr_cast(field, format_string, sz, cast) \ | |
212 | fcoe_ctlr_private_show_function(field, format_string, sz, (cast)) \ | |
213 | static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO, \ | |
214 | show_fcoe_ctlr_device_##field, NULL) | |
215 | ||
216 | #define fcoe_fcf_private_rd_attr_cast(field, format_string, sz, cast) \ | |
217 | fcoe_fcf_private_show_function(field, format_string, sz, (cast)) \ | |
218 | static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO, \ | |
219 | show_fcoe_fcf_device_##field, NULL) | |
220 | ||
221 | #define fcoe_enum_name_search(title, table_type, table) \ | |
222 | static const char *get_fcoe_##title##_name(enum table_type table_key) \ | |
223 | { \ | |
224 | if (table_key < 0 || table_key >= ARRAY_SIZE(table)) \ | |
225 | return NULL; \ | |
226 | return table[table_key]; \ | |
227 | } | |
228 | ||
229 | static char *fip_conn_type_names[] = { | |
230 | [ FIP_CONN_TYPE_UNKNOWN ] = "Unknown", | |
231 | [ FIP_CONN_TYPE_FABRIC ] = "Fabric", | |
232 | [ FIP_CONN_TYPE_VN2VN ] = "VN2VN", | |
233 | }; | |
234 | fcoe_enum_name_search(ctlr_mode, fip_conn_type, fip_conn_type_names) | |
235 | ||
236 | static enum fip_conn_type fcoe_parse_mode(const char *buf) | |
237 | { | |
238 | int i; | |
239 | ||
240 | for (i = 0; i < ARRAY_SIZE(fip_conn_type_names); i++) { | |
241 | if (strcasecmp(buf, fip_conn_type_names[i]) == 0) | |
242 | return i; | |
243 | } | |
244 | ||
245 | return FIP_CONN_TYPE_UNKNOWN; | |
246 | } | |
247 | ||
248 | static char *fcf_state_names[] = { | |
249 | [ FCOE_FCF_STATE_UNKNOWN ] = "Unknown", | |
250 | [ FCOE_FCF_STATE_DISCONNECTED ] = "Disconnected", | |
251 | [ FCOE_FCF_STATE_CONNECTED ] = "Connected", | |
252 | }; | |
253 | fcoe_enum_name_search(fcf_state, fcf_state, fcf_state_names) | |
254 | #define FCOE_FCF_STATE_MAX_NAMELEN 50 | |
255 | ||
256 | static ssize_t show_fcf_state(struct device *dev, | |
257 | struct device_attribute *attr, | |
258 | char *buf) | |
259 | { | |
260 | struct fcoe_fcf_device *fcf = dev_to_fcf(dev); | |
261 | const char *name; | |
262 | name = get_fcoe_fcf_state_name(fcf->state); | |
263 | if (!name) | |
264 | return -EINVAL; | |
265 | return snprintf(buf, FCOE_FCF_STATE_MAX_NAMELEN, "%s\n", name); | |
266 | } | |
267 | static FCOE_DEVICE_ATTR(fcf, state, S_IRUGO, show_fcf_state, NULL); | |
268 | ||
269 | #define FCOE_MAX_MODENAME_LEN 20 | |
270 | static ssize_t show_ctlr_mode(struct device *dev, | |
271 | struct device_attribute *attr, | |
272 | char *buf) | |
273 | { | |
274 | struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); | |
275 | const char *name; | |
276 | ||
277 | name = get_fcoe_ctlr_mode_name(ctlr->mode); | |
278 | if (!name) | |
279 | return -EINVAL; | |
280 | return snprintf(buf, FCOE_MAX_MODENAME_LEN, | |
281 | "%s\n", name); | |
282 | } | |
283 | ||
284 | static ssize_t store_ctlr_mode(struct device *dev, | |
285 | struct device_attribute *attr, | |
286 | const char *buf, size_t count) | |
287 | { | |
288 | struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); | |
289 | char mode[FCOE_MAX_MODENAME_LEN + 1]; | |
290 | ||
291 | if (count > FCOE_MAX_MODENAME_LEN) | |
292 | return -EINVAL; | |
293 | ||
294 | strncpy(mode, buf, count); | |
295 | ||
296 | if (mode[count - 1] == '\n') | |
297 | mode[count - 1] = '\0'; | |
298 | else | |
299 | mode[count] = '\0'; | |
300 | ||
301 | switch (ctlr->enabled) { | |
302 | case FCOE_CTLR_ENABLED: | |
303 | LIBFCOE_SYSFS_DBG(ctlr, "Cannot change mode when enabled."); | |
304 | return -EBUSY; | |
305 | case FCOE_CTLR_DISABLED: | |
306 | if (!ctlr->f->set_fcoe_ctlr_mode) { | |
307 | LIBFCOE_SYSFS_DBG(ctlr, | |
308 | "Mode change not supported by LLD."); | |
309 | return -ENOTSUPP; | |
310 | } | |
311 | ||
312 | ctlr->mode = fcoe_parse_mode(mode); | |
313 | if (ctlr->mode == FIP_CONN_TYPE_UNKNOWN) { | |
314 | LIBFCOE_SYSFS_DBG(ctlr, | |
315 | "Unknown mode %s provided.", buf); | |
316 | return -EINVAL; | |
317 | } | |
318 | ||
319 | ctlr->f->set_fcoe_ctlr_mode(ctlr); | |
320 | LIBFCOE_SYSFS_DBG(ctlr, "Mode changed to %s.", buf); | |
321 | ||
322 | return count; | |
323 | case FCOE_CTLR_UNUSED: | |
324 | default: | |
325 | LIBFCOE_SYSFS_DBG(ctlr, "Mode change not supported."); | |
326 | return -ENOTSUPP; | |
327 | }; | |
328 | } | |
329 | ||
330 | static FCOE_DEVICE_ATTR(ctlr, mode, S_IRUGO | S_IWUSR, | |
331 | show_ctlr_mode, store_ctlr_mode); | |
332 | ||
333 | static ssize_t store_ctlr_enabled(struct device *dev, | |
334 | struct device_attribute *attr, | |
335 | const char *buf, size_t count) | |
336 | { | |
337 | struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); | |
338 | int rc; | |
339 | ||
340 | switch (ctlr->enabled) { | |
341 | case FCOE_CTLR_ENABLED: | |
342 | if (*buf == '1') | |
343 | return count; | |
344 | ctlr->enabled = FCOE_CTLR_DISABLED; | |
345 | break; | |
346 | case FCOE_CTLR_DISABLED: | |
347 | if (*buf == '0') | |
348 | return count; | |
349 | ctlr->enabled = FCOE_CTLR_ENABLED; | |
350 | break; | |
351 | case FCOE_CTLR_UNUSED: | |
352 | return -ENOTSUPP; | |
353 | }; | |
354 | ||
355 | rc = ctlr->f->set_fcoe_ctlr_enabled(ctlr); | |
356 | if (rc) | |
357 | return rc; | |
358 | ||
359 | return count; | |
360 | } | |
361 | ||
362 | static char *ctlr_enabled_state_names[] = { | |
363 | [ FCOE_CTLR_ENABLED ] = "1", | |
364 | [ FCOE_CTLR_DISABLED ] = "0", | |
365 | }; | |
366 | fcoe_enum_name_search(ctlr_enabled_state, ctlr_enabled_state, | |
367 | ctlr_enabled_state_names) | |
368 | #define FCOE_CTLR_ENABLED_MAX_NAMELEN 50 | |
369 | ||
370 | static ssize_t show_ctlr_enabled_state(struct device *dev, | |
371 | struct device_attribute *attr, | |
372 | char *buf) | |
373 | { | |
374 | struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); | |
375 | const char *name; | |
376 | ||
377 | name = get_fcoe_ctlr_enabled_state_name(ctlr->enabled); | |
378 | if (!name) | |
379 | return -EINVAL; | |
380 | return snprintf(buf, FCOE_CTLR_ENABLED_MAX_NAMELEN, | |
381 | "%s\n", name); | |
382 | } | |
383 | ||
384 | static FCOE_DEVICE_ATTR(ctlr, enabled, S_IRUGO | S_IWUSR, | |
385 | show_ctlr_enabled_state, | |
386 | store_ctlr_enabled); | |
387 | ||
388 | static ssize_t | |
389 | store_private_fcoe_ctlr_fcf_dev_loss_tmo(struct device *dev, | |
390 | struct device_attribute *attr, | |
391 | const char *buf, size_t count) | |
392 | { | |
393 | struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); | |
394 | struct fcoe_fcf_device *fcf; | |
395 | unsigned long val; | |
396 | int rc; | |
397 | ||
398 | rc = fcoe_str_to_dev_loss(buf, &val); | |
399 | if (rc) | |
400 | return rc; | |
401 | ||
402 | fcoe_ctlr_fcf_dev_loss_tmo(ctlr) = val; | |
403 | mutex_lock(&ctlr->lock); | |
404 | list_for_each_entry(fcf, &ctlr->fcfs, peers) | |
405 | fcoe_fcf_set_dev_loss_tmo(fcf, val); | |
406 | mutex_unlock(&ctlr->lock); | |
407 | return count; | |
408 | } | |
409 | fcoe_ctlr_private_show_function(fcf_dev_loss_tmo, "%d\n", 20, ); | |
410 | static FCOE_DEVICE_ATTR(ctlr, fcf_dev_loss_tmo, S_IRUGO | S_IWUSR, | |
411 | show_fcoe_ctlr_device_fcf_dev_loss_tmo, | |
412 | store_private_fcoe_ctlr_fcf_dev_loss_tmo); | |
413 | ||
414 | /* Link Error Status Block (LESB) */ | |
415 | fcoe_ctlr_rd_attr(link_fail, "%u\n", 20); | |
416 | fcoe_ctlr_rd_attr(vlink_fail, "%u\n", 20); | |
417 | fcoe_ctlr_rd_attr(miss_fka, "%u\n", 20); | |
418 | fcoe_ctlr_rd_attr(symb_err, "%u\n", 20); | |
419 | fcoe_ctlr_rd_attr(err_block, "%u\n", 20); | |
420 | fcoe_ctlr_rd_attr(fcs_error, "%u\n", 20); | |
421 | ||
422 | fcoe_fcf_private_rd_attr_cast(fabric_name, "0x%llx\n", 20, unsigned long long); | |
423 | fcoe_fcf_private_rd_attr_cast(switch_name, "0x%llx\n", 20, unsigned long long); | |
424 | fcoe_fcf_private_rd_attr(priority, "%u\n", 20); | |
425 | fcoe_fcf_private_rd_attr(fc_map, "0x%x\n", 20); | |
426 | fcoe_fcf_private_rd_attr(vfid, "%u\n", 20); | |
427 | fcoe_fcf_private_rd_attr(mac, "%pM\n", 20); | |
428 | fcoe_fcf_private_rd_attr(fka_period, "%u\n", 20); | |
429 | fcoe_fcf_rd_attr(selected, "%u\n", 20); | |
430 | fcoe_fcf_rd_attr(vlan_id, "%u\n", 20); | |
431 | ||
432 | fcoe_fcf_private_show_function(dev_loss_tmo, "%d\n", 20, ) | |
433 | static ssize_t | |
434 | store_fcoe_fcf_dev_loss_tmo(struct device *dev, struct device_attribute *attr, | |
435 | const char *buf, size_t count) | |
436 | { | |
437 | struct fcoe_fcf_device *fcf = dev_to_fcf(dev); | |
438 | unsigned long val; | |
439 | int rc; | |
440 | ||
441 | rc = fcoe_str_to_dev_loss(buf, &val); | |
442 | if (rc) | |
443 | return rc; | |
444 | ||
445 | rc = fcoe_fcf_set_dev_loss_tmo(fcf, val); | |
446 | if (rc) | |
447 | return rc; | |
448 | return count; | |
449 | } | |
450 | static FCOE_DEVICE_ATTR(fcf, dev_loss_tmo, S_IRUGO | S_IWUSR, | |
451 | show_fcoe_fcf_device_dev_loss_tmo, | |
452 | store_fcoe_fcf_dev_loss_tmo); | |
453 | ||
454 | static struct attribute *fcoe_ctlr_lesb_attrs[] = { | |
455 | &device_attr_fcoe_ctlr_link_fail.attr, | |
456 | &device_attr_fcoe_ctlr_vlink_fail.attr, | |
457 | &device_attr_fcoe_ctlr_miss_fka.attr, | |
458 | &device_attr_fcoe_ctlr_symb_err.attr, | |
459 | &device_attr_fcoe_ctlr_err_block.attr, | |
460 | &device_attr_fcoe_ctlr_fcs_error.attr, | |
461 | NULL, | |
462 | }; | |
463 | ||
464 | static struct attribute_group fcoe_ctlr_lesb_attr_group = { | |
465 | .name = "lesb", | |
466 | .attrs = fcoe_ctlr_lesb_attrs, | |
467 | }; | |
468 | ||
469 | static struct attribute *fcoe_ctlr_attrs[] = { | |
470 | &device_attr_fcoe_ctlr_fcf_dev_loss_tmo.attr, | |
471 | &device_attr_fcoe_ctlr_enabled.attr, | |
472 | &device_attr_fcoe_ctlr_mode.attr, | |
473 | NULL, | |
474 | }; | |
475 | ||
476 | static struct attribute_group fcoe_ctlr_attr_group = { | |
477 | .attrs = fcoe_ctlr_attrs, | |
478 | }; | |
479 | ||
480 | static const struct attribute_group *fcoe_ctlr_attr_groups[] = { | |
481 | &fcoe_ctlr_attr_group, | |
482 | &fcoe_ctlr_lesb_attr_group, | |
483 | NULL, | |
484 | }; | |
485 | ||
486 | static struct attribute *fcoe_fcf_attrs[] = { | |
487 | &device_attr_fcoe_fcf_fabric_name.attr, | |
488 | &device_attr_fcoe_fcf_switch_name.attr, | |
489 | &device_attr_fcoe_fcf_dev_loss_tmo.attr, | |
490 | &device_attr_fcoe_fcf_fc_map.attr, | |
491 | &device_attr_fcoe_fcf_vfid.attr, | |
492 | &device_attr_fcoe_fcf_mac.attr, | |
493 | &device_attr_fcoe_fcf_priority.attr, | |
494 | &device_attr_fcoe_fcf_fka_period.attr, | |
495 | &device_attr_fcoe_fcf_state.attr, | |
496 | &device_attr_fcoe_fcf_selected.attr, | |
497 | &device_attr_fcoe_fcf_vlan_id.attr, | |
498 | NULL | |
499 | }; | |
500 | ||
501 | static struct attribute_group fcoe_fcf_attr_group = { | |
502 | .attrs = fcoe_fcf_attrs, | |
503 | }; | |
504 | ||
505 | static const struct attribute_group *fcoe_fcf_attr_groups[] = { | |
506 | &fcoe_fcf_attr_group, | |
507 | NULL, | |
508 | }; | |
509 | ||
510 | static struct bus_type fcoe_bus_type; | |
511 | ||
512 | static int fcoe_bus_match(struct device *dev, | |
513 | struct device_driver *drv) | |
514 | { | |
515 | if (dev->bus == &fcoe_bus_type) | |
516 | return 1; | |
517 | return 0; | |
518 | } | |
519 | ||
520 | /** | |
521 | * fcoe_ctlr_device_release() - Release the FIP ctlr memory | |
522 | * @dev: Pointer to the FIP ctlr's embedded device | |
523 | * | |
524 | * Called when the last FIP ctlr reference is released. | |
525 | */ | |
526 | static void fcoe_ctlr_device_release(struct device *dev) | |
527 | { | |
528 | struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); | |
529 | kfree(ctlr); | |
530 | } | |
531 | ||
532 | /** | |
533 | * fcoe_fcf_device_release() - Release the FIP fcf memory | |
534 | * @dev: Pointer to the fcf's embedded device | |
535 | * | |
536 | * Called when the last FIP fcf reference is released. | |
537 | */ | |
538 | static void fcoe_fcf_device_release(struct device *dev) | |
539 | { | |
540 | struct fcoe_fcf_device *fcf = dev_to_fcf(dev); | |
541 | kfree(fcf); | |
542 | } | |
543 | ||
544 | static struct device_type fcoe_ctlr_device_type = { | |
545 | .name = "fcoe_ctlr", | |
546 | .groups = fcoe_ctlr_attr_groups, | |
547 | .release = fcoe_ctlr_device_release, | |
548 | }; | |
549 | ||
550 | static struct device_type fcoe_fcf_device_type = { | |
551 | .name = "fcoe_fcf", | |
552 | .groups = fcoe_fcf_attr_groups, | |
553 | .release = fcoe_fcf_device_release, | |
554 | }; | |
555 | ||
556 | static struct bus_attribute fcoe_bus_attr_group[] = { | |
557 | __ATTR(ctlr_create, S_IWUSR, NULL, fcoe_ctlr_create_store), | |
558 | __ATTR(ctlr_destroy, S_IWUSR, NULL, fcoe_ctlr_destroy_store), | |
559 | __ATTR_NULL | |
560 | }; | |
561 | ||
562 | static struct bus_type fcoe_bus_type = { | |
563 | .name = "fcoe", | |
564 | .match = &fcoe_bus_match, | |
565 | .bus_attrs = fcoe_bus_attr_group, | |
566 | }; | |
567 | ||
568 | /** | |
569 | * fcoe_ctlr_device_flush_work() - Flush a FIP ctlr's workqueue | |
570 | * @ctlr: Pointer to the FIP ctlr whose workqueue is to be flushed | |
571 | */ | |
572 | static void fcoe_ctlr_device_flush_work(struct fcoe_ctlr_device *ctlr) | |
573 | { | |
574 | if (!fcoe_ctlr_work_q(ctlr)) { | |
575 | printk(KERN_ERR | |
576 | "ERROR: FIP Ctlr '%d' attempted to flush work, " | |
577 | "when no workqueue created.\n", ctlr->id); | |
578 | dump_stack(); | |
579 | return; | |
580 | } | |
581 | ||
582 | flush_workqueue(fcoe_ctlr_work_q(ctlr)); | |
583 | } | |
584 | ||
585 | /** | |
586 | * fcoe_ctlr_device_queue_work() - Schedule work for a FIP ctlr's workqueue | |
587 | * @ctlr: Pointer to the FIP ctlr who owns the devloss workqueue | |
588 | * @work: Work to queue for execution | |
589 | * | |
590 | * Return value: | |
591 | * 1 on success / 0 already queued / < 0 for error | |
592 | */ | |
593 | static int fcoe_ctlr_device_queue_work(struct fcoe_ctlr_device *ctlr, | |
594 | struct work_struct *work) | |
595 | { | |
596 | if (unlikely(!fcoe_ctlr_work_q(ctlr))) { | |
597 | printk(KERN_ERR | |
598 | "ERROR: FIP Ctlr '%d' attempted to queue work, " | |
599 | "when no workqueue created.\n", ctlr->id); | |
600 | dump_stack(); | |
601 | ||
602 | return -EINVAL; | |
603 | } | |
604 | ||
605 | return queue_work(fcoe_ctlr_work_q(ctlr), work); | |
606 | } | |
607 | ||
608 | /** | |
609 | * fcoe_ctlr_device_flush_devloss() - Flush a FIP ctlr's devloss workqueue | |
610 | * @ctlr: Pointer to FIP ctlr whose workqueue is to be flushed | |
611 | */ | |
612 | static void fcoe_ctlr_device_flush_devloss(struct fcoe_ctlr_device *ctlr) | |
613 | { | |
614 | if (!fcoe_ctlr_devloss_work_q(ctlr)) { | |
615 | printk(KERN_ERR | |
616 | "ERROR: FIP Ctlr '%d' attempted to flush work, " | |
617 | "when no workqueue created.\n", ctlr->id); | |
618 | dump_stack(); | |
619 | return; | |
620 | } | |
621 | ||
622 | flush_workqueue(fcoe_ctlr_devloss_work_q(ctlr)); | |
623 | } | |
624 | ||
625 | /** | |
626 | * fcoe_ctlr_device_queue_devloss_work() - Schedule work for a FIP ctlr's devloss workqueue | |
627 | * @ctlr: Pointer to the FIP ctlr who owns the devloss workqueue | |
628 | * @work: Work to queue for execution | |
629 | * @delay: jiffies to delay the work queuing | |
630 | * | |
631 | * Return value: | |
632 | * 1 on success / 0 already queued / < 0 for error | |
633 | */ | |
634 | static int fcoe_ctlr_device_queue_devloss_work(struct fcoe_ctlr_device *ctlr, | |
635 | struct delayed_work *work, | |
636 | unsigned long delay) | |
637 | { | |
638 | if (unlikely(!fcoe_ctlr_devloss_work_q(ctlr))) { | |
639 | printk(KERN_ERR | |
640 | "ERROR: FIP Ctlr '%d' attempted to queue work, " | |
641 | "when no workqueue created.\n", ctlr->id); | |
642 | dump_stack(); | |
643 | ||
644 | return -EINVAL; | |
645 | } | |
646 | ||
647 | return queue_delayed_work(fcoe_ctlr_devloss_work_q(ctlr), work, delay); | |
648 | } | |
649 | ||
650 | static int fcoe_fcf_device_match(struct fcoe_fcf_device *new, | |
651 | struct fcoe_fcf_device *old) | |
652 | { | |
653 | if (new->switch_name == old->switch_name && | |
654 | new->fabric_name == old->fabric_name && | |
655 | new->fc_map == old->fc_map && | |
656 | compare_ether_addr(new->mac, old->mac) == 0) | |
657 | return 1; | |
658 | return 0; | |
659 | } | |
660 | ||
661 | /** | |
662 | * fcoe_ctlr_device_add() - Add a FIP ctlr to sysfs | |
663 | * @parent: The parent device to which the fcoe_ctlr instance | |
664 | * should be attached | |
665 | * @f: The LLD's FCoE sysfs function template pointer | |
666 | * @priv_size: Size to be allocated with the fcoe_ctlr_device for the LLD | |
667 | * | |
668 | * This routine allocates a FIP ctlr object with some additional memory | |
669 | * for the LLD. The FIP ctlr is initialized, added to sysfs and then | |
670 | * attributes are added to it. | |
671 | */ | |
672 | struct fcoe_ctlr_device *fcoe_ctlr_device_add(struct device *parent, | |
673 | struct fcoe_sysfs_function_template *f, | |
674 | int priv_size) | |
675 | { | |
676 | struct fcoe_ctlr_device *ctlr; | |
677 | int error = 0; | |
678 | ||
679 | ctlr = kzalloc(sizeof(struct fcoe_ctlr_device) + priv_size, | |
680 | GFP_KERNEL); | |
681 | if (!ctlr) | |
682 | goto out; | |
683 | ||
684 | ctlr->id = atomic_inc_return(&ctlr_num) - 1; | |
685 | ctlr->f = f; | |
686 | ctlr->mode = FIP_CONN_TYPE_FABRIC; | |
687 | INIT_LIST_HEAD(&ctlr->fcfs); | |
688 | mutex_init(&ctlr->lock); | |
689 | ctlr->dev.parent = parent; | |
690 | ctlr->dev.bus = &fcoe_bus_type; | |
691 | ctlr->dev.type = &fcoe_ctlr_device_type; | |
692 | ||
693 | ctlr->fcf_dev_loss_tmo = fcoe_fcf_dev_loss_tmo; | |
694 | ||
695 | snprintf(ctlr->work_q_name, sizeof(ctlr->work_q_name), | |
696 | "ctlr_wq_%d", ctlr->id); | |
697 | ctlr->work_q = create_singlethread_workqueue( | |
698 | ctlr->work_q_name); | |
699 | if (!ctlr->work_q) | |
700 | goto out_del; | |
701 | ||
702 | snprintf(ctlr->devloss_work_q_name, | |
703 | sizeof(ctlr->devloss_work_q_name), | |
704 | "ctlr_dl_wq_%d", ctlr->id); | |
705 | ctlr->devloss_work_q = create_singlethread_workqueue( | |
706 | ctlr->devloss_work_q_name); | |
707 | if (!ctlr->devloss_work_q) | |
708 | goto out_del_q; | |
709 | ||
710 | dev_set_name(&ctlr->dev, "ctlr_%d", ctlr->id); | |
711 | error = device_register(&ctlr->dev); | |
712 | if (error) | |
713 | goto out_del_q2; | |
714 | ||
715 | return ctlr; | |
716 | ||
717 | out_del_q2: | |
718 | destroy_workqueue(ctlr->devloss_work_q); | |
719 | ctlr->devloss_work_q = NULL; | |
720 | out_del_q: | |
721 | destroy_workqueue(ctlr->work_q); | |
722 | ctlr->work_q = NULL; | |
723 | out_del: | |
724 | kfree(ctlr); | |
725 | out: | |
726 | return NULL; | |
727 | } | |
728 | EXPORT_SYMBOL_GPL(fcoe_ctlr_device_add); | |
729 | ||
730 | /** | |
731 | * fcoe_ctlr_device_delete() - Delete a FIP ctlr and its subtree from sysfs | |
732 | * @ctlr: A pointer to the ctlr to be deleted | |
733 | * | |
734 | * Deletes a FIP ctlr and any fcfs attached | |
735 | * to it. Deleting fcfs will cause their childen | |
736 | * to be deleted as well. | |
737 | * | |
738 | * The ctlr is detached from sysfs and it's resources | |
739 | * are freed (work q), but the memory is not freed | |
740 | * until its last reference is released. | |
741 | * | |
742 | * This routine expects no locks to be held before | |
743 | * calling. | |
744 | * | |
745 | * TODO: Currently there are no callbacks to clean up LLD data | |
746 | * for a fcoe_fcf_device. LLDs must keep this in mind as they need | |
747 | * to clean up each of their LLD data for all fcoe_fcf_device before | |
748 | * calling fcoe_ctlr_device_delete. | |
749 | */ | |
750 | void fcoe_ctlr_device_delete(struct fcoe_ctlr_device *ctlr) | |
751 | { | |
752 | struct fcoe_fcf_device *fcf, *next; | |
753 | /* Remove any attached fcfs */ | |
754 | mutex_lock(&ctlr->lock); | |
755 | list_for_each_entry_safe(fcf, next, | |
756 | &ctlr->fcfs, peers) { | |
757 | list_del(&fcf->peers); | |
758 | fcf->state = FCOE_FCF_STATE_DELETED; | |
759 | fcoe_ctlr_device_queue_work(ctlr, &fcf->delete_work); | |
760 | } | |
761 | mutex_unlock(&ctlr->lock); | |
762 | ||
763 | fcoe_ctlr_device_flush_work(ctlr); | |
764 | ||
765 | destroy_workqueue(ctlr->devloss_work_q); | |
766 | ctlr->devloss_work_q = NULL; | |
767 | destroy_workqueue(ctlr->work_q); | |
768 | ctlr->work_q = NULL; | |
769 | ||
770 | device_unregister(&ctlr->dev); | |
771 | } | |
772 | EXPORT_SYMBOL_GPL(fcoe_ctlr_device_delete); | |
773 | ||
774 | /** | |
775 | * fcoe_fcf_device_final_delete() - Final delete routine | |
776 | * @work: The FIP fcf's embedded work struct | |
777 | * | |
778 | * It is expected that the fcf has been removed from | |
779 | * the FIP ctlr's list before calling this routine. | |
780 | */ | |
781 | static void fcoe_fcf_device_final_delete(struct work_struct *work) | |
782 | { | |
783 | struct fcoe_fcf_device *fcf = | |
784 | container_of(work, struct fcoe_fcf_device, delete_work); | |
785 | struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); | |
786 | ||
787 | /* | |
788 | * Cancel any outstanding timers. These should really exist | |
789 | * only when rmmod'ing the LLDD and we're asking for | |
790 | * immediate termination of the rports | |
791 | */ | |
792 | if (!cancel_delayed_work(&fcf->dev_loss_work)) | |
793 | fcoe_ctlr_device_flush_devloss(ctlr); | |
794 | ||
795 | device_unregister(&fcf->dev); | |
796 | } | |
797 | ||
798 | /** | |
799 | * fip_timeout_deleted_fcf() - Delete a fcf when the devloss timer fires | |
800 | * @work: The FIP fcf's embedded work struct | |
801 | * | |
802 | * Removes the fcf from the FIP ctlr's list of fcfs and | |
803 | * queues the final deletion. | |
804 | */ | |
805 | static void fip_timeout_deleted_fcf(struct work_struct *work) | |
806 | { | |
807 | struct fcoe_fcf_device *fcf = | |
808 | container_of(work, struct fcoe_fcf_device, dev_loss_work.work); | |
809 | struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); | |
810 | ||
811 | mutex_lock(&ctlr->lock); | |
812 | ||
813 | /* | |
814 | * If the fcf is deleted or reconnected before the timer | |
815 | * fires the devloss queue will be flushed, but the state will | |
816 | * either be CONNECTED or DELETED. If that is the case we | |
817 | * cancel deleting the fcf. | |
818 | */ | |
819 | if (fcf->state != FCOE_FCF_STATE_DISCONNECTED) | |
820 | goto out; | |
821 | ||
822 | dev_printk(KERN_ERR, &fcf->dev, | |
823 | "FIP fcf connection time out: removing fcf\n"); | |
824 | ||
825 | list_del(&fcf->peers); | |
826 | fcf->state = FCOE_FCF_STATE_DELETED; | |
827 | fcoe_ctlr_device_queue_work(ctlr, &fcf->delete_work); | |
828 | ||
829 | out: | |
830 | mutex_unlock(&ctlr->lock); | |
831 | } | |
832 | ||
833 | /** | |
834 | * fcoe_fcf_device_delete() - Delete a FIP fcf | |
835 | * @fcf: Pointer to the fcf which is to be deleted | |
836 | * | |
837 | * Queues the FIP fcf on the devloss workqueue | |
838 | * | |
839 | * Expects the ctlr_attrs mutex to be held for fcf | |
840 | * state change. | |
841 | */ | |
842 | void fcoe_fcf_device_delete(struct fcoe_fcf_device *fcf) | |
843 | { | |
844 | struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); | |
845 | int timeout = fcf->dev_loss_tmo; | |
846 | ||
847 | if (fcf->state != FCOE_FCF_STATE_CONNECTED) | |
848 | return; | |
849 | ||
850 | fcf->state = FCOE_FCF_STATE_DISCONNECTED; | |
851 | ||
852 | /* | |
853 | * FCF will only be re-connected by the LLD calling | |
854 | * fcoe_fcf_device_add, and it should be setting up | |
855 | * priv then. | |
856 | */ | |
857 | fcf->priv = NULL; | |
858 | ||
859 | fcoe_ctlr_device_queue_devloss_work(ctlr, &fcf->dev_loss_work, | |
860 | timeout * HZ); | |
861 | } | |
862 | EXPORT_SYMBOL_GPL(fcoe_fcf_device_delete); | |
863 | ||
864 | /** | |
865 | * fcoe_fcf_device_add() - Add a FCoE sysfs fcoe_fcf_device to the system | |
866 | * @ctlr: The fcoe_ctlr_device that will be the fcoe_fcf_device parent | |
867 | * @new_fcf: A temporary FCF used for lookups on the current list of fcfs | |
868 | * | |
869 | * Expects to be called with the ctlr->lock held | |
870 | */ | |
871 | struct fcoe_fcf_device *fcoe_fcf_device_add(struct fcoe_ctlr_device *ctlr, | |
872 | struct fcoe_fcf_device *new_fcf) | |
873 | { | |
874 | struct fcoe_fcf_device *fcf; | |
875 | int error = 0; | |
876 | ||
877 | list_for_each_entry(fcf, &ctlr->fcfs, peers) { | |
878 | if (fcoe_fcf_device_match(new_fcf, fcf)) { | |
879 | if (fcf->state == FCOE_FCF_STATE_CONNECTED) | |
880 | return fcf; | |
881 | ||
882 | fcf->state = FCOE_FCF_STATE_CONNECTED; | |
883 | ||
884 | if (!cancel_delayed_work(&fcf->dev_loss_work)) | |
885 | fcoe_ctlr_device_flush_devloss(ctlr); | |
886 | ||
887 | return fcf; | |
888 | } | |
889 | } | |
890 | ||
891 | fcf = kzalloc(sizeof(struct fcoe_fcf_device), GFP_ATOMIC); | |
892 | if (unlikely(!fcf)) | |
893 | goto out; | |
894 | ||
895 | INIT_WORK(&fcf->delete_work, fcoe_fcf_device_final_delete); | |
896 | INIT_DELAYED_WORK(&fcf->dev_loss_work, fip_timeout_deleted_fcf); | |
897 | ||
898 | fcf->dev.parent = &ctlr->dev; | |
899 | fcf->dev.bus = &fcoe_bus_type; | |
900 | fcf->dev.type = &fcoe_fcf_device_type; | |
901 | fcf->id = atomic_inc_return(&fcf_num) - 1; | |
902 | fcf->state = FCOE_FCF_STATE_UNKNOWN; | |
903 | ||
904 | fcf->dev_loss_tmo = ctlr->fcf_dev_loss_tmo; | |
905 | ||
906 | dev_set_name(&fcf->dev, "fcf_%d", fcf->id); | |
907 | ||
908 | fcf->fabric_name = new_fcf->fabric_name; | |
909 | fcf->switch_name = new_fcf->switch_name; | |
910 | fcf->fc_map = new_fcf->fc_map; | |
911 | fcf->vfid = new_fcf->vfid; | |
912 | memcpy(fcf->mac, new_fcf->mac, ETH_ALEN); | |
913 | fcf->priority = new_fcf->priority; | |
914 | fcf->fka_period = new_fcf->fka_period; | |
915 | fcf->selected = new_fcf->selected; | |
916 | ||
917 | error = device_register(&fcf->dev); | |
918 | if (error) | |
919 | goto out_del; | |
920 | ||
921 | fcf->state = FCOE_FCF_STATE_CONNECTED; | |
922 | list_add_tail(&fcf->peers, &ctlr->fcfs); | |
923 | ||
924 | return fcf; | |
925 | ||
926 | out_del: | |
927 | kfree(fcf); | |
928 | out: | |
929 | return NULL; | |
930 | } | |
931 | EXPORT_SYMBOL_GPL(fcoe_fcf_device_add); | |
932 | ||
933 | int __init fcoe_sysfs_setup(void) | |
934 | { | |
935 | int error; | |
936 | ||
937 | atomic_set(&ctlr_num, 0); | |
938 | atomic_set(&fcf_num, 0); | |
939 | ||
940 | error = bus_register(&fcoe_bus_type); | |
941 | if (error) | |
942 | return error; | |
943 | ||
944 | return 0; | |
945 | } | |
946 | ||
947 | void __exit fcoe_sysfs_teardown(void) | |
948 | { | |
949 | bus_unregister(&fcoe_bus_type); | |
950 | } |