Commit | Line | Data |
---|---|---|
138fe4e0 | 1 | /* |
4e639fdf | 2 | * Copyright 2007-2010 Red Hat, Inc. |
138fe4e0 KR |
3 | * by Peter Jones <pjones@redhat.com> |
4 | * Copyright 2008 IBM, Inc. | |
5 | * by Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
6 | * Copyright 2008 | |
7 | * by Konrad Rzeszutek <ketuzsezr@darnok.org> | |
8 | * | |
9 | * This code exposes the iSCSI Boot Format Table to userland via sysfs. | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or modify | |
12 | * it under the terms of the GNU General Public License v2.0 as published by | |
13 | * the Free Software Foundation | |
14 | * | |
15 | * This program is distributed in the hope that it will be useful, | |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | * GNU General Public License for more details. | |
19 | * | |
20 | * Changelog: | |
21 | * | |
4e639fdf PJ |
22 | * 06 Jan 2010 - Peter Jones <pjones@redhat.com> |
23 | * New changelog entries are in the git log from now on. Not here. | |
24 | * | |
138fe4e0 KR |
25 | * 14 Mar 2008 - Konrad Rzeszutek <ketuzsezr@darnok.org> |
26 | * Updated comments and copyrights. (v0.4.9) | |
27 | * | |
28 | * 11 Feb 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
29 | * Converted to using ibft_addr. (v0.4.8) | |
30 | * | |
31 | * 8 Feb 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
32 | * Combined two functions in one: reserve_ibft_region. (v0.4.7) | |
33 | * | |
34 | * 30 Jan 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
35 | * Added logic to handle IPv6 addresses. (v0.4.6) | |
36 | * | |
37 | * 25 Jan 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
38 | * Added logic to handle badly not-to-spec iBFT. (v0.4.5) | |
39 | * | |
40 | * 4 Jan 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
41 | * Added __init to function declarations. (v0.4.4) | |
42 | * | |
43 | * 21 Dec 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
44 | * Updated kobject registration, combined unregister functions in one | |
45 | * and code and style cleanup. (v0.4.3) | |
46 | * | |
47 | * 5 Dec 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
48 | * Added end-markers to enums and re-organized kobject registration. (v0.4.2) | |
49 | * | |
50 | * 4 Dec 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
51 | * Created 'device' sysfs link to the NIC and style cleanup. (v0.4.1) | |
52 | * | |
53 | * 28 Nov 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
54 | * Added sysfs-ibft documentation, moved 'find_ibft' function to | |
55 | * in its own file and added text attributes for every struct field. (v0.4) | |
56 | * | |
57 | * 21 Nov 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
58 | * Added text attributes emulating OpenFirmware /proc/device-tree naming. | |
59 | * Removed binary /sysfs interface (v0.3) | |
60 | * | |
61 | * 29 Aug 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
62 | * Added functionality in setup.c to reserve iBFT region. (v0.2) | |
63 | * | |
64 | * 27 Aug 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> | |
65 | * First version exposing iBFT data via a binary /sysfs. (v0.1) | |
66 | * | |
67 | */ | |
68 | ||
69 | ||
70 | #include <linux/blkdev.h> | |
71 | #include <linux/capability.h> | |
72 | #include <linux/ctype.h> | |
73 | #include <linux/device.h> | |
74 | #include <linux/err.h> | |
75 | #include <linux/init.h> | |
76 | #include <linux/iscsi_ibft.h> | |
77 | #include <linux/limits.h> | |
78 | #include <linux/module.h> | |
79 | #include <linux/pci.h> | |
80 | #include <linux/slab.h> | |
81 | #include <linux/stat.h> | |
82 | #include <linux/string.h> | |
83 | #include <linux/types.h> | |
4e639fdf | 84 | #include <linux/acpi.h> |
138fe4e0 | 85 | |
4e639fdf PJ |
86 | #define IBFT_ISCSI_VERSION "0.5.0" |
87 | #define IBFT_ISCSI_DATE "2010-Feb-25" | |
138fe4e0 KR |
88 | |
89 | MODULE_AUTHOR("Peter Jones <pjones@redhat.com> and \ | |
90 | Konrad Rzeszutek <ketuzsezr@darnok.org>"); | |
91 | MODULE_DESCRIPTION("sysfs interface to BIOS iBFT information"); | |
92 | MODULE_LICENSE("GPL"); | |
93 | MODULE_VERSION(IBFT_ISCSI_VERSION); | |
94 | ||
95 | struct ibft_hdr { | |
96 | u8 id; | |
97 | u8 version; | |
98 | u16 length; | |
99 | u8 index; | |
100 | u8 flags; | |
101 | } __attribute__((__packed__)); | |
102 | ||
103 | struct ibft_control { | |
104 | struct ibft_hdr hdr; | |
105 | u16 extensions; | |
106 | u16 initiator_off; | |
107 | u16 nic0_off; | |
108 | u16 tgt0_off; | |
109 | u16 nic1_off; | |
110 | u16 tgt1_off; | |
111 | } __attribute__((__packed__)); | |
112 | ||
113 | struct ibft_initiator { | |
114 | struct ibft_hdr hdr; | |
115 | char isns_server[16]; | |
116 | char slp_server[16]; | |
117 | char pri_radius_server[16]; | |
118 | char sec_radius_server[16]; | |
119 | u16 initiator_name_len; | |
120 | u16 initiator_name_off; | |
121 | } __attribute__((__packed__)); | |
122 | ||
123 | struct ibft_nic { | |
124 | struct ibft_hdr hdr; | |
125 | char ip_addr[16]; | |
126 | u8 subnet_mask_prefix; | |
127 | u8 origin; | |
128 | char gateway[16]; | |
129 | char primary_dns[16]; | |
130 | char secondary_dns[16]; | |
131 | char dhcp[16]; | |
132 | u16 vlan; | |
133 | char mac[6]; | |
134 | u16 pci_bdf; | |
135 | u16 hostname_len; | |
136 | u16 hostname_off; | |
137 | } __attribute__((__packed__)); | |
138 | ||
139 | struct ibft_tgt { | |
140 | struct ibft_hdr hdr; | |
141 | char ip_addr[16]; | |
142 | u16 port; | |
143 | char lun[8]; | |
144 | u8 chap_type; | |
145 | u8 nic_assoc; | |
146 | u16 tgt_name_len; | |
147 | u16 tgt_name_off; | |
148 | u16 chap_name_len; | |
149 | u16 chap_name_off; | |
150 | u16 chap_secret_len; | |
151 | u16 chap_secret_off; | |
152 | u16 rev_chap_name_len; | |
153 | u16 rev_chap_name_off; | |
154 | u16 rev_chap_secret_len; | |
155 | u16 rev_chap_secret_off; | |
156 | } __attribute__((__packed__)); | |
157 | ||
158 | /* | |
159 | * The kobject different types and its names. | |
160 | * | |
161 | */ | |
162 | enum ibft_id { | |
163 | id_reserved = 0, /* We don't support. */ | |
164 | id_control = 1, /* Should show up only once and is not exported. */ | |
165 | id_initiator = 2, | |
166 | id_nic = 3, | |
167 | id_target = 4, | |
168 | id_extensions = 5, /* We don't support. */ | |
169 | id_end_marker, | |
170 | }; | |
171 | ||
172 | /* | |
173 | * We do not support the other types, hence the usage of NULL. | |
174 | * This maps to the enum ibft_id. | |
175 | */ | |
176 | static const char *ibft_id_names[] = | |
177 | {NULL, NULL, "initiator", "ethernet%d", "target%d", NULL, NULL}; | |
178 | ||
179 | /* | |
180 | * The text attributes names for each of the kobjects. | |
181 | */ | |
182 | enum ibft_eth_properties_enum { | |
183 | ibft_eth_index, | |
184 | ibft_eth_flags, | |
185 | ibft_eth_ip_addr, | |
186 | ibft_eth_subnet_mask, | |
187 | ibft_eth_origin, | |
188 | ibft_eth_gateway, | |
189 | ibft_eth_primary_dns, | |
190 | ibft_eth_secondary_dns, | |
191 | ibft_eth_dhcp, | |
192 | ibft_eth_vlan, | |
193 | ibft_eth_mac, | |
194 | /* ibft_eth_pci_bdf - this is replaced by link to the device itself. */ | |
195 | ibft_eth_hostname, | |
196 | ibft_eth_end_marker, | |
197 | }; | |
198 | ||
199 | static const char *ibft_eth_properties[] = | |
200 | {"index", "flags", "ip-addr", "subnet-mask", "origin", "gateway", | |
201 | "primary-dns", "secondary-dns", "dhcp", "vlan", "mac", "hostname", | |
202 | NULL}; | |
203 | ||
204 | enum ibft_tgt_properties_enum { | |
205 | ibft_tgt_index, | |
206 | ibft_tgt_flags, | |
207 | ibft_tgt_ip_addr, | |
208 | ibft_tgt_port, | |
209 | ibft_tgt_lun, | |
210 | ibft_tgt_chap_type, | |
211 | ibft_tgt_nic_assoc, | |
212 | ibft_tgt_name, | |
213 | ibft_tgt_chap_name, | |
214 | ibft_tgt_chap_secret, | |
215 | ibft_tgt_rev_chap_name, | |
216 | ibft_tgt_rev_chap_secret, | |
217 | ibft_tgt_end_marker, | |
218 | }; | |
219 | ||
220 | static const char *ibft_tgt_properties[] = | |
221 | {"index", "flags", "ip-addr", "port", "lun", "chap-type", "nic-assoc", | |
222 | "target-name", "chap-name", "chap-secret", "rev-chap-name", | |
223 | "rev-chap-name-secret", NULL}; | |
224 | ||
225 | enum ibft_initiator_properties_enum { | |
226 | ibft_init_index, | |
227 | ibft_init_flags, | |
228 | ibft_init_isns_server, | |
229 | ibft_init_slp_server, | |
230 | ibft_init_pri_radius_server, | |
231 | ibft_init_sec_radius_server, | |
232 | ibft_init_initiator_name, | |
233 | ibft_init_end_marker, | |
234 | }; | |
235 | ||
236 | static const char *ibft_initiator_properties[] = | |
237 | {"index", "flags", "isns-server", "slp-server", "pri-radius-server", | |
238 | "sec-radius-server", "initiator-name", NULL}; | |
239 | ||
240 | /* | |
241 | * The kobject and attribute structures. | |
242 | */ | |
243 | ||
244 | struct ibft_kobject { | |
4e639fdf | 245 | struct acpi_table_ibft *header; |
138fe4e0 KR |
246 | union { |
247 | struct ibft_initiator *initiator; | |
248 | struct ibft_nic *nic; | |
249 | struct ibft_tgt *tgt; | |
250 | struct ibft_hdr *hdr; | |
251 | }; | |
252 | struct kobject kobj; | |
253 | struct list_head node; | |
254 | }; | |
255 | ||
256 | struct ibft_attribute { | |
257 | struct attribute attr; | |
258 | ssize_t (*show) (struct ibft_kobject *entry, | |
259 | struct ibft_attribute *attr, char *buf); | |
260 | union { | |
261 | struct ibft_initiator *initiator; | |
262 | struct ibft_nic *nic; | |
263 | struct ibft_tgt *tgt; | |
264 | struct ibft_hdr *hdr; | |
265 | }; | |
266 | struct kobject *kobj; | |
267 | int type; /* The enum of the type. This can be any value of: | |
268 | ibft_eth_properties_enum, ibft_tgt_properties_enum, | |
269 | or ibft_initiator_properties_enum. */ | |
270 | struct list_head node; | |
271 | }; | |
272 | ||
273 | static LIST_HEAD(ibft_attr_list); | |
274 | static LIST_HEAD(ibft_kobject_list); | |
275 | ||
276 | static const char nulls[16]; | |
277 | ||
278 | /* | |
279 | * Helper functions to parse data properly. | |
280 | */ | |
281 | static ssize_t sprintf_ipaddr(char *buf, u8 *ip) | |
282 | { | |
283 | char *str = buf; | |
284 | ||
285 | if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && | |
286 | ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && ip[7] == 0 && | |
287 | ip[8] == 0 && ip[9] == 0 && ip[10] == 0xff && ip[11] == 0xff) { | |
288 | /* | |
289 | * IPV4 | |
290 | */ | |
63779436 | 291 | str += sprintf(buf, "%pI4", ip + 12); |
138fe4e0 KR |
292 | } else { |
293 | /* | |
294 | * IPv6 | |
295 | */ | |
5b095d98 | 296 | str += sprintf(str, "%pI6", ip); |
138fe4e0 KR |
297 | } |
298 | str += sprintf(str, "\n"); | |
299 | return str - buf; | |
300 | } | |
301 | ||
302 | static ssize_t sprintf_string(char *str, int len, char *buf) | |
303 | { | |
304 | return sprintf(str, "%.*s\n", len, buf); | |
305 | } | |
306 | ||
307 | /* | |
308 | * Helper function to verify the IBFT header. | |
309 | */ | |
310 | static int ibft_verify_hdr(char *t, struct ibft_hdr *hdr, int id, int length) | |
311 | { | |
312 | if (hdr->id != id) { | |
313 | printk(KERN_ERR "iBFT error: We expected the " \ | |
314 | "field header.id to have %d but " \ | |
315 | "found %d instead!\n", id, hdr->id); | |
316 | return -ENODEV; | |
317 | } | |
318 | if (hdr->length != length) { | |
319 | printk(KERN_ERR "iBFT error: We expected the " \ | |
320 | "field header.length to have %d but " \ | |
321 | "found %d instead!\n", length, hdr->length); | |
322 | return -ENODEV; | |
323 | } | |
324 | ||
325 | return 0; | |
326 | } | |
327 | ||
328 | static void ibft_release(struct kobject *kobj) | |
329 | { | |
330 | struct ibft_kobject *ibft = | |
331 | container_of(kobj, struct ibft_kobject, kobj); | |
332 | kfree(ibft); | |
333 | } | |
334 | ||
335 | /* | |
336 | * Routines for parsing the iBFT data to be human readable. | |
337 | */ | |
9cf899d1 AB |
338 | static ssize_t ibft_attr_show_initiator(struct ibft_kobject *entry, |
339 | struct ibft_attribute *attr, | |
340 | char *buf) | |
138fe4e0 KR |
341 | { |
342 | struct ibft_initiator *initiator = entry->initiator; | |
343 | void *ibft_loc = entry->header; | |
344 | char *str = buf; | |
345 | ||
346 | if (!initiator) | |
347 | return 0; | |
348 | ||
349 | switch (attr->type) { | |
350 | case ibft_init_index: | |
351 | str += sprintf(str, "%d\n", initiator->hdr.index); | |
352 | break; | |
353 | case ibft_init_flags: | |
354 | str += sprintf(str, "%d\n", initiator->hdr.flags); | |
355 | break; | |
356 | case ibft_init_isns_server: | |
357 | str += sprintf_ipaddr(str, initiator->isns_server); | |
358 | break; | |
359 | case ibft_init_slp_server: | |
360 | str += sprintf_ipaddr(str, initiator->slp_server); | |
361 | break; | |
362 | case ibft_init_pri_radius_server: | |
363 | str += sprintf_ipaddr(str, initiator->pri_radius_server); | |
364 | break; | |
365 | case ibft_init_sec_radius_server: | |
366 | str += sprintf_ipaddr(str, initiator->sec_radius_server); | |
367 | break; | |
368 | case ibft_init_initiator_name: | |
369 | str += sprintf_string(str, initiator->initiator_name_len, | |
370 | (char *)ibft_loc + | |
371 | initiator->initiator_name_off); | |
372 | break; | |
373 | default: | |
374 | break; | |
375 | } | |
376 | ||
377 | return str - buf; | |
378 | } | |
379 | ||
9cf899d1 AB |
380 | static ssize_t ibft_attr_show_nic(struct ibft_kobject *entry, |
381 | struct ibft_attribute *attr, | |
382 | char *buf) | |
138fe4e0 KR |
383 | { |
384 | struct ibft_nic *nic = entry->nic; | |
385 | void *ibft_loc = entry->header; | |
386 | char *str = buf; | |
00e7825b | 387 | __be32 val; |
138fe4e0 KR |
388 | |
389 | if (!nic) | |
390 | return 0; | |
391 | ||
392 | switch (attr->type) { | |
393 | case ibft_eth_index: | |
394 | str += sprintf(str, "%d\n", nic->hdr.index); | |
395 | break; | |
396 | case ibft_eth_flags: | |
397 | str += sprintf(str, "%d\n", nic->hdr.flags); | |
398 | break; | |
399 | case ibft_eth_ip_addr: | |
400 | str += sprintf_ipaddr(str, nic->ip_addr); | |
401 | break; | |
402 | case ibft_eth_subnet_mask: | |
00e7825b JP |
403 | val = cpu_to_be32(~((1 << (32-nic->subnet_mask_prefix))-1)); |
404 | str += sprintf(str, "%pI4", &val); | |
138fe4e0 KR |
405 | break; |
406 | case ibft_eth_origin: | |
407 | str += sprintf(str, "%d\n", nic->origin); | |
408 | break; | |
409 | case ibft_eth_gateway: | |
410 | str += sprintf_ipaddr(str, nic->gateway); | |
411 | break; | |
412 | case ibft_eth_primary_dns: | |
413 | str += sprintf_ipaddr(str, nic->primary_dns); | |
414 | break; | |
415 | case ibft_eth_secondary_dns: | |
416 | str += sprintf_ipaddr(str, nic->secondary_dns); | |
417 | break; | |
418 | case ibft_eth_dhcp: | |
419 | str += sprintf_ipaddr(str, nic->dhcp); | |
420 | break; | |
421 | case ibft_eth_vlan: | |
422 | str += sprintf(str, "%d\n", nic->vlan); | |
423 | break; | |
424 | case ibft_eth_mac: | |
2c352948 | 425 | str += sprintf(str, "%pM\n", nic->mac); |
138fe4e0 KR |
426 | break; |
427 | case ibft_eth_hostname: | |
428 | str += sprintf_string(str, nic->hostname_len, | |
429 | (char *)ibft_loc + nic->hostname_off); | |
430 | break; | |
431 | default: | |
432 | break; | |
433 | } | |
434 | ||
435 | return str - buf; | |
436 | }; | |
437 | ||
9cf899d1 AB |
438 | static ssize_t ibft_attr_show_target(struct ibft_kobject *entry, |
439 | struct ibft_attribute *attr, | |
440 | char *buf) | |
138fe4e0 KR |
441 | { |
442 | struct ibft_tgt *tgt = entry->tgt; | |
443 | void *ibft_loc = entry->header; | |
444 | char *str = buf; | |
445 | int i; | |
446 | ||
447 | if (!tgt) | |
448 | return 0; | |
449 | ||
450 | switch (attr->type) { | |
451 | case ibft_tgt_index: | |
452 | str += sprintf(str, "%d\n", tgt->hdr.index); | |
453 | break; | |
454 | case ibft_tgt_flags: | |
455 | str += sprintf(str, "%d\n", tgt->hdr.flags); | |
456 | break; | |
457 | case ibft_tgt_ip_addr: | |
458 | str += sprintf_ipaddr(str, tgt->ip_addr); | |
459 | break; | |
460 | case ibft_tgt_port: | |
461 | str += sprintf(str, "%d\n", tgt->port); | |
462 | break; | |
463 | case ibft_tgt_lun: | |
464 | for (i = 0; i < 8; i++) | |
465 | str += sprintf(str, "%x", (u8)tgt->lun[i]); | |
466 | str += sprintf(str, "\n"); | |
467 | break; | |
468 | case ibft_tgt_nic_assoc: | |
469 | str += sprintf(str, "%d\n", tgt->nic_assoc); | |
470 | break; | |
471 | case ibft_tgt_chap_type: | |
472 | str += sprintf(str, "%d\n", tgt->chap_type); | |
473 | break; | |
474 | case ibft_tgt_name: | |
475 | str += sprintf_string(str, tgt->tgt_name_len, | |
476 | (char *)ibft_loc + tgt->tgt_name_off); | |
477 | break; | |
478 | case ibft_tgt_chap_name: | |
479 | str += sprintf_string(str, tgt->chap_name_len, | |
480 | (char *)ibft_loc + tgt->chap_name_off); | |
481 | break; | |
482 | case ibft_tgt_chap_secret: | |
483 | str += sprintf_string(str, tgt->chap_secret_len, | |
484 | (char *)ibft_loc + tgt->chap_secret_off); | |
485 | break; | |
486 | case ibft_tgt_rev_chap_name: | |
487 | str += sprintf_string(str, tgt->rev_chap_name_len, | |
488 | (char *)ibft_loc + | |
489 | tgt->rev_chap_name_off); | |
490 | break; | |
491 | case ibft_tgt_rev_chap_secret: | |
492 | str += sprintf_string(str, tgt->rev_chap_secret_len, | |
493 | (char *)ibft_loc + | |
494 | tgt->rev_chap_secret_off); | |
495 | break; | |
496 | default: | |
497 | break; | |
498 | } | |
499 | ||
500 | return str - buf; | |
501 | } | |
502 | ||
503 | /* | |
504 | * The routine called for all sysfs attributes. | |
505 | */ | |
506 | static ssize_t ibft_show_attribute(struct kobject *kobj, | |
507 | struct attribute *attr, | |
508 | char *buf) | |
509 | { | |
510 | struct ibft_kobject *dev = | |
511 | container_of(kobj, struct ibft_kobject, kobj); | |
512 | struct ibft_attribute *ibft_attr = | |
513 | container_of(attr, struct ibft_attribute, attr); | |
514 | ssize_t ret = -EIO; | |
515 | char *str = buf; | |
516 | ||
517 | if (!capable(CAP_SYS_ADMIN)) | |
518 | return -EACCES; | |
519 | ||
520 | if (ibft_attr->show) | |
521 | ret = ibft_attr->show(dev, ibft_attr, str); | |
522 | ||
523 | return ret; | |
524 | } | |
525 | ||
52cf25d0 | 526 | static const struct sysfs_ops ibft_attr_ops = { |
138fe4e0 KR |
527 | .show = ibft_show_attribute, |
528 | }; | |
529 | ||
530 | static struct kobj_type ibft_ktype = { | |
531 | .release = ibft_release, | |
532 | .sysfs_ops = &ibft_attr_ops, | |
533 | }; | |
534 | ||
535 | static struct kset *ibft_kset; | |
536 | ||
537 | static int __init ibft_check_device(void) | |
538 | { | |
539 | int len; | |
540 | u8 *pos; | |
541 | u8 csum = 0; | |
542 | ||
4e639fdf | 543 | len = ibft_addr->header.length; |
138fe4e0 KR |
544 | |
545 | /* Sanity checking of iBFT. */ | |
4e639fdf | 546 | if (ibft_addr->header.revision != 1) { |
138fe4e0 | 547 | printk(KERN_ERR "iBFT module supports only revision 1, " \ |
4e639fdf PJ |
548 | "while this is %d.\n", |
549 | ibft_addr->header.revision); | |
138fe4e0 KR |
550 | return -ENOENT; |
551 | } | |
552 | for (pos = (u8 *)ibft_addr; pos < (u8 *)ibft_addr + len; pos++) | |
553 | csum += *pos; | |
554 | ||
555 | if (csum) { | |
556 | printk(KERN_ERR "iBFT has incorrect checksum (0x%x)!\n", csum); | |
557 | return -ENOENT; | |
558 | } | |
559 | ||
560 | return 0; | |
561 | } | |
562 | ||
563 | /* | |
564 | * Helper function for ibft_register_kobjects. | |
565 | */ | |
4e639fdf | 566 | static int __init ibft_create_kobject(struct acpi_table_ibft *header, |
138fe4e0 KR |
567 | struct ibft_hdr *hdr, |
568 | struct list_head *list) | |
569 | { | |
570 | struct ibft_kobject *ibft_kobj = NULL; | |
571 | struct ibft_nic *nic = (struct ibft_nic *)hdr; | |
572 | struct pci_dev *pci_dev; | |
573 | int rc = 0; | |
574 | ||
575 | ibft_kobj = kzalloc(sizeof(*ibft_kobj), GFP_KERNEL); | |
576 | if (!ibft_kobj) | |
577 | return -ENOMEM; | |
578 | ||
579 | ibft_kobj->header = header; | |
580 | ibft_kobj->hdr = hdr; | |
581 | ||
582 | switch (hdr->id) { | |
583 | case id_initiator: | |
584 | rc = ibft_verify_hdr("initiator", hdr, id_initiator, | |
585 | sizeof(*ibft_kobj->initiator)); | |
586 | break; | |
587 | case id_nic: | |
588 | rc = ibft_verify_hdr("ethernet", hdr, id_nic, | |
589 | sizeof(*ibft_kobj->nic)); | |
590 | break; | |
591 | case id_target: | |
592 | rc = ibft_verify_hdr("target", hdr, id_target, | |
593 | sizeof(*ibft_kobj->tgt)); | |
594 | break; | |
595 | case id_reserved: | |
596 | case id_control: | |
597 | case id_extensions: | |
598 | /* Fields which we don't support. Ignore them */ | |
599 | rc = 1; | |
600 | break; | |
601 | default: | |
602 | printk(KERN_ERR "iBFT has unknown structure type (%d). " \ | |
603 | "Report this bug to %.6s!\n", hdr->id, | |
4e639fdf | 604 | header->header.oem_id); |
138fe4e0 KR |
605 | rc = 1; |
606 | break; | |
607 | } | |
608 | ||
609 | if (rc) { | |
610 | /* Skip adding this kobject, but exit with non-fatal error. */ | |
611 | kfree(ibft_kobj); | |
612 | goto out_invalid_struct; | |
613 | } | |
614 | ||
615 | ibft_kobj->kobj.kset = ibft_kset; | |
616 | ||
617 | rc = kobject_init_and_add(&ibft_kobj->kobj, &ibft_ktype, | |
618 | NULL, ibft_id_names[hdr->id], hdr->index); | |
619 | ||
620 | if (rc) { | |
621 | kfree(ibft_kobj); | |
622 | goto out; | |
623 | } | |
624 | ||
625 | kobject_uevent(&ibft_kobj->kobj, KOBJ_ADD); | |
626 | ||
627 | if (hdr->id == id_nic) { | |
628 | /* | |
629 | * We don't search for the device in other domains than | |
630 | * zero. This is because on x86 platforms the BIOS | |
631 | * executes only devices which are in domain 0. Furthermore, the | |
632 | * iBFT spec doesn't have a domain id field :-( | |
633 | */ | |
634 | pci_dev = pci_get_bus_and_slot((nic->pci_bdf & 0xff00) >> 8, | |
635 | (nic->pci_bdf & 0xff)); | |
636 | if (pci_dev) { | |
637 | rc = sysfs_create_link(&ibft_kobj->kobj, | |
638 | &pci_dev->dev.kobj, "device"); | |
639 | pci_dev_put(pci_dev); | |
640 | } | |
641 | } | |
642 | ||
643 | /* Nothing broke so lets add it to the list. */ | |
644 | list_add_tail(&ibft_kobj->node, list); | |
645 | out: | |
646 | return rc; | |
647 | out_invalid_struct: | |
648 | /* Unsupported structs are skipped. */ | |
649 | return 0; | |
650 | } | |
651 | ||
652 | /* | |
653 | * Scan the IBFT table structure for the NIC and Target fields. When | |
654 | * found add them on the passed-in list. We do not support the other | |
655 | * fields at this point, so they are skipped. | |
656 | */ | |
4e639fdf | 657 | static int __init ibft_register_kobjects(struct acpi_table_ibft *header, |
138fe4e0 KR |
658 | struct list_head *list) |
659 | { | |
660 | struct ibft_control *control = NULL; | |
661 | void *ptr, *end; | |
662 | int rc = 0; | |
663 | u16 offset; | |
664 | u16 eot_offset; | |
665 | ||
666 | control = (void *)header + sizeof(*header); | |
667 | end = (void *)control + control->hdr.length; | |
4e639fdf | 668 | eot_offset = (void *)header + header->header.length - (void *)control; |
138fe4e0 KR |
669 | rc = ibft_verify_hdr("control", (struct ibft_hdr *)control, id_control, |
670 | sizeof(*control)); | |
671 | ||
672 | /* iBFT table safety checking */ | |
673 | rc |= ((control->hdr.index) ? -ENODEV : 0); | |
674 | if (rc) { | |
675 | printk(KERN_ERR "iBFT error: Control header is invalid!\n"); | |
676 | return rc; | |
677 | } | |
678 | for (ptr = &control->initiator_off; ptr < end; ptr += sizeof(u16)) { | |
679 | offset = *(u16 *)ptr; | |
4e639fdf PJ |
680 | if (offset && offset < header->header.length && |
681 | offset < eot_offset) { | |
138fe4e0 KR |
682 | rc = ibft_create_kobject(header, |
683 | (void *)header + offset, | |
684 | list); | |
685 | if (rc) | |
686 | break; | |
687 | } | |
688 | } | |
689 | ||
690 | return rc; | |
691 | } | |
692 | ||
693 | static void ibft_unregister(struct list_head *attr_list, | |
694 | struct list_head *kobj_list) | |
695 | { | |
696 | struct ibft_kobject *data = NULL, *n; | |
697 | struct ibft_attribute *attr = NULL, *m; | |
698 | ||
699 | list_for_each_entry_safe(attr, m, attr_list, node) { | |
700 | sysfs_remove_file(attr->kobj, &attr->attr); | |
701 | list_del(&attr->node); | |
702 | kfree(attr); | |
703 | }; | |
704 | list_del_init(attr_list); | |
705 | ||
706 | list_for_each_entry_safe(data, n, kobj_list, node) { | |
707 | list_del(&data->node); | |
708 | if (data->hdr->id == id_nic) | |
709 | sysfs_remove_link(&data->kobj, "device"); | |
710 | kobject_put(&data->kobj); | |
711 | }; | |
712 | list_del_init(kobj_list); | |
713 | } | |
714 | ||
715 | static int __init ibft_create_attribute(struct ibft_kobject *kobj_data, | |
716 | int type, | |
717 | const char *name, | |
718 | ssize_t (*show)(struct ibft_kobject *, | |
719 | struct ibft_attribute*, | |
720 | char *buf), | |
721 | struct list_head *list) | |
722 | { | |
723 | struct ibft_attribute *attr = NULL; | |
724 | struct ibft_hdr *hdr = kobj_data->hdr; | |
725 | ||
726 | attr = kmalloc(sizeof(*attr), GFP_KERNEL); | |
727 | if (!attr) | |
728 | return -ENOMEM; | |
729 | ||
730 | attr->attr.name = name; | |
731 | attr->attr.mode = S_IRUSR; | |
138fe4e0 KR |
732 | |
733 | attr->hdr = hdr; | |
734 | attr->show = show; | |
735 | attr->kobj = &kobj_data->kobj; | |
736 | attr->type = type; | |
737 | ||
738 | list_add_tail(&attr->node, list); | |
739 | ||
740 | return 0; | |
741 | } | |
742 | ||
743 | /* | |
744 | * Helper routiners to check to determine if the entry is valid | |
745 | * in the proper iBFT structure. | |
746 | */ | |
747 | static int __init ibft_check_nic_for(struct ibft_nic *nic, int entry) | |
748 | { | |
749 | int rc = 0; | |
750 | ||
751 | switch (entry) { | |
752 | case ibft_eth_index: | |
753 | case ibft_eth_flags: | |
754 | rc = 1; | |
755 | break; | |
756 | case ibft_eth_ip_addr: | |
65fd2105 | 757 | if (memcmp(nic->ip_addr, nulls, sizeof(nic->ip_addr))) |
138fe4e0 KR |
758 | rc = 1; |
759 | break; | |
760 | case ibft_eth_subnet_mask: | |
65fd2105 | 761 | if (nic->subnet_mask_prefix) |
138fe4e0 KR |
762 | rc = 1; |
763 | break; | |
764 | case ibft_eth_origin: | |
765 | rc = 1; | |
766 | break; | |
767 | case ibft_eth_gateway: | |
768 | if (memcmp(nic->gateway, nulls, sizeof(nic->gateway))) | |
769 | rc = 1; | |
770 | break; | |
771 | case ibft_eth_primary_dns: | |
772 | if (memcmp(nic->primary_dns, nulls, | |
773 | sizeof(nic->primary_dns))) | |
774 | rc = 1; | |
775 | break; | |
776 | case ibft_eth_secondary_dns: | |
777 | if (memcmp(nic->secondary_dns, nulls, | |
778 | sizeof(nic->secondary_dns))) | |
779 | rc = 1; | |
780 | break; | |
781 | case ibft_eth_dhcp: | |
782 | if (memcmp(nic->dhcp, nulls, sizeof(nic->dhcp))) | |
783 | rc = 1; | |
784 | break; | |
785 | case ibft_eth_vlan: | |
786 | case ibft_eth_mac: | |
787 | rc = 1; | |
788 | break; | |
789 | case ibft_eth_hostname: | |
790 | if (nic->hostname_off) | |
791 | rc = 1; | |
792 | break; | |
793 | default: | |
794 | break; | |
795 | } | |
796 | ||
797 | return rc; | |
798 | } | |
799 | ||
800 | static int __init ibft_check_tgt_for(struct ibft_tgt *tgt, int entry) | |
801 | { | |
802 | int rc = 0; | |
803 | ||
804 | switch (entry) { | |
805 | case ibft_tgt_index: | |
806 | case ibft_tgt_flags: | |
807 | case ibft_tgt_ip_addr: | |
808 | case ibft_tgt_port: | |
809 | case ibft_tgt_lun: | |
810 | case ibft_tgt_nic_assoc: | |
811 | case ibft_tgt_chap_type: | |
812 | rc = 1; | |
813 | case ibft_tgt_name: | |
814 | if (tgt->tgt_name_len) | |
815 | rc = 1; | |
816 | break; | |
817 | case ibft_tgt_chap_name: | |
818 | case ibft_tgt_chap_secret: | |
819 | if (tgt->chap_name_len) | |
820 | rc = 1; | |
821 | break; | |
822 | case ibft_tgt_rev_chap_name: | |
823 | case ibft_tgt_rev_chap_secret: | |
824 | if (tgt->rev_chap_name_len) | |
825 | rc = 1; | |
826 | break; | |
827 | default: | |
828 | break; | |
829 | } | |
830 | ||
831 | return rc; | |
832 | } | |
833 | ||
834 | static int __init ibft_check_initiator_for(struct ibft_initiator *init, | |
835 | int entry) | |
836 | { | |
837 | int rc = 0; | |
838 | ||
839 | switch (entry) { | |
840 | case ibft_init_index: | |
841 | case ibft_init_flags: | |
842 | rc = 1; | |
843 | break; | |
844 | case ibft_init_isns_server: | |
845 | if (memcmp(init->isns_server, nulls, | |
846 | sizeof(init->isns_server))) | |
847 | rc = 1; | |
848 | break; | |
849 | case ibft_init_slp_server: | |
850 | if (memcmp(init->slp_server, nulls, | |
851 | sizeof(init->slp_server))) | |
852 | rc = 1; | |
853 | break; | |
854 | case ibft_init_pri_radius_server: | |
855 | if (memcmp(init->pri_radius_server, nulls, | |
856 | sizeof(init->pri_radius_server))) | |
857 | rc = 1; | |
858 | break; | |
859 | case ibft_init_sec_radius_server: | |
860 | if (memcmp(init->sec_radius_server, nulls, | |
861 | sizeof(init->sec_radius_server))) | |
862 | rc = 1; | |
863 | break; | |
864 | case ibft_init_initiator_name: | |
865 | if (init->initiator_name_len) | |
866 | rc = 1; | |
867 | break; | |
868 | default: | |
869 | break; | |
870 | } | |
871 | ||
872 | return rc; | |
873 | } | |
874 | ||
875 | /* | |
876 | * Register the attributes for all of the kobjects. | |
877 | */ | |
878 | static int __init ibft_register_attributes(struct list_head *kobject_list, | |
879 | struct list_head *attr_list) | |
880 | { | |
881 | int rc = 0, i = 0; | |
882 | struct ibft_kobject *data = NULL; | |
883 | struct ibft_attribute *attr = NULL, *m; | |
884 | ||
885 | list_for_each_entry(data, kobject_list, node) { | |
886 | switch (data->hdr->id) { | |
887 | case id_nic: | |
888 | for (i = 0; i < ibft_eth_end_marker && !rc; i++) | |
889 | if (ibft_check_nic_for(data->nic, i)) | |
890 | rc = ibft_create_attribute(data, i, | |
891 | ibft_eth_properties[i], | |
892 | ibft_attr_show_nic, attr_list); | |
893 | break; | |
894 | case id_target: | |
895 | for (i = 0; i < ibft_tgt_end_marker && !rc; i++) | |
896 | if (ibft_check_tgt_for(data->tgt, i)) | |
897 | rc = ibft_create_attribute(data, i, | |
898 | ibft_tgt_properties[i], | |
899 | ibft_attr_show_target, | |
900 | attr_list); | |
901 | break; | |
902 | case id_initiator: | |
903 | for (i = 0; i < ibft_init_end_marker && !rc; i++) | |
904 | if (ibft_check_initiator_for( | |
905 | data->initiator, i)) | |
906 | rc = ibft_create_attribute(data, i, | |
907 | ibft_initiator_properties[i], | |
908 | ibft_attr_show_initiator, | |
909 | attr_list); | |
910 | break; | |
911 | default: | |
912 | break; | |
913 | } | |
914 | if (rc) | |
915 | break; | |
916 | } | |
917 | list_for_each_entry_safe(attr, m, attr_list, node) { | |
918 | rc = sysfs_create_file(attr->kobj, &attr->attr); | |
919 | if (rc) { | |
920 | list_del(&attr->node); | |
921 | kfree(attr); | |
922 | break; | |
923 | } | |
924 | } | |
925 | ||
926 | return rc; | |
927 | } | |
928 | ||
929 | /* | |
930 | * ibft_init() - creates sysfs tree entries for the iBFT data. | |
931 | */ | |
932 | static int __init ibft_init(void) | |
933 | { | |
934 | int rc = 0; | |
935 | ||
936 | ibft_kset = kset_create_and_add("ibft", NULL, firmware_kobj); | |
937 | if (!ibft_kset) | |
938 | return -ENOMEM; | |
939 | ||
940 | if (ibft_addr) { | |
f537a53d | 941 | printk(KERN_INFO "iBFT detected at 0x%llx.\n", |
ed3c6614 | 942 | (u64)isa_virt_to_bus(ibft_addr)); |
138fe4e0 KR |
943 | |
944 | rc = ibft_check_device(); | |
945 | if (rc) | |
946 | goto out_firmware_unregister; | |
947 | ||
948 | /* Scan the IBFT for data and register the kobjects. */ | |
949 | rc = ibft_register_kobjects(ibft_addr, &ibft_kobject_list); | |
950 | if (rc) | |
951 | goto out_free; | |
952 | ||
953 | /* Register the attributes */ | |
954 | rc = ibft_register_attributes(&ibft_kobject_list, | |
955 | &ibft_attr_list); | |
956 | if (rc) | |
957 | goto out_free; | |
958 | } else | |
959 | printk(KERN_INFO "No iBFT detected.\n"); | |
960 | ||
961 | return 0; | |
962 | ||
963 | out_free: | |
964 | ibft_unregister(&ibft_attr_list, &ibft_kobject_list); | |
965 | out_firmware_unregister: | |
966 | kset_unregister(ibft_kset); | |
967 | return rc; | |
968 | } | |
969 | ||
970 | static void __exit ibft_exit(void) | |
971 | { | |
972 | ibft_unregister(&ibft_attr_list, &ibft_kobject_list); | |
973 | kset_unregister(ibft_kset); | |
974 | } | |
975 | ||
976 | module_init(ibft_init); | |
977 | module_exit(ibft_exit); |