1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * linux/drivers/firmware/edd.c |
4 | * Copyright (C) 2002, 2003, 2004 Dell Inc. |
5 | * by Matt Domsch <Matt_Domsch@dell.com> |
6 | * disk signature by Matt Domsch, Andrew Wilks, and Sandeep K. Shandilya |
7 | * legacy CHS by Patrick J. LoPresti <patl@users.sourceforge.net> |
8 | * |
9 | * BIOS Enhanced Disk Drive Services (EDD) |
10 | * conformant to T13 Committee www.t13.org |
11 | * projects 1572D, 1484D, 1386D, 1226DT |
12 | * |
13 | * This code takes information provided by BIOS EDD calls |
14 | * fn41 - Check Extensions Present and |
15 | * fn48 - Get Device Parameters with EDD extensions |
16 | * made in setup.S, copied to safe structures in setup.c, |
17 | * and presents it in sysfs. |
18 | * |
19 | * Please see http://linux.dell.com/edd/results.html for |
20 | * the list of BIOSs which have been reported to implement EDD. |
21 | */ |
22 | |
23 | #include <linux/module.h> |
24 | #include <linux/string.h> |
25 | #include <linux/types.h> |
26 | #include <linux/init.h> |
27 | #include <linux/stat.h> |
28 | #include <linux/err.h> |
29 | #include <linux/ctype.h> |
30 | #include <linux/slab.h> |
31 | #include <linux/limits.h> |
32 | #include <linux/device.h> |
33 | #include <linux/pci.h> |
34 | #include <linux/blkdev.h> |
35 | #include <linux/edd.h> |
36 | |
37 | #define EDD_VERSION "0.16" |
38 | #define EDD_DATE "2004-Jun-25" |
39 | |
40 | MODULE_AUTHOR("Matt Domsch <Matt_Domsch@Dell.com>" ); |
41 | MODULE_DESCRIPTION("sysfs interface to BIOS EDD information" ); |
42 | MODULE_LICENSE("GPL" ); |
43 | MODULE_VERSION(EDD_VERSION); |
44 | |
45 | #define left (PAGE_SIZE - (p - buf) - 1) |
46 | |
47 | struct edd_device { |
48 | unsigned int index; |
49 | unsigned int mbr_signature; |
50 | struct edd_info *info; |
51 | struct kobject kobj; |
52 | }; |
53 | |
54 | struct edd_attribute { |
55 | struct attribute attr; |
56 | ssize_t(*show) (struct edd_device * edev, char *buf); |
57 | int (*test) (struct edd_device * edev); |
58 | }; |
59 | |
60 | /* forward declarations */ |
61 | static int edd_dev_is_type(struct edd_device *edev, const char *type); |
62 | static struct pci_dev *edd_get_pci_dev(struct edd_device *edev); |
63 | |
64 | static struct edd_device *edd_devices[EDD_MBR_SIG_MAX]; |
65 | |
66 | #define EDD_DEVICE_ATTR(_name,_mode,_show,_test) \ |
67 | struct edd_attribute edd_attr_##_name = { \ |
68 | .attr = {.name = __stringify(_name), .mode = _mode }, \ |
69 | .show = _show, \ |
70 | .test = _test, \ |
71 | }; |
72 | |
73 | static int |
74 | edd_has_mbr_signature(struct edd_device *edev) |
75 | { |
76 | return edev->index < min_t(unsigned char, edd.mbr_signature_nr, EDD_MBR_SIG_MAX); |
77 | } |
78 | |
79 | static int |
80 | edd_has_edd_info(struct edd_device *edev) |
81 | { |
82 | return edev->index < min_t(unsigned char, edd.edd_info_nr, EDDMAXNR); |
83 | } |
84 | |
85 | static inline struct edd_info * |
86 | edd_dev_get_info(struct edd_device *edev) |
87 | { |
88 | return edev->info; |
89 | } |
90 | |
91 | static inline void |
92 | edd_dev_set_info(struct edd_device *edev, int i) |
93 | { |
94 | edev->index = i; |
95 | if (edd_has_mbr_signature(edev)) |
96 | edev->mbr_signature = edd.mbr_signature[i]; |
97 | if (edd_has_edd_info(edev)) |
98 | edev->info = &edd.edd_info[i]; |
99 | } |
100 | |
101 | #define to_edd_attr(_attr) container_of(_attr,struct edd_attribute,attr) |
102 | #define to_edd_device(obj) container_of(obj,struct edd_device,kobj) |
103 | |
104 | static ssize_t |
105 | edd_attr_show(struct kobject * kobj, struct attribute *attr, char *buf) |
106 | { |
107 | struct edd_device *dev = to_edd_device(kobj); |
108 | struct edd_attribute *edd_attr = to_edd_attr(attr); |
109 | ssize_t ret = -EIO; |
110 | |
111 | if (edd_attr->show) |
112 | ret = edd_attr->show(dev, buf); |
113 | return ret; |
114 | } |
115 | |
116 | static const struct sysfs_ops edd_attr_ops = { |
117 | .show = edd_attr_show, |
118 | }; |
119 | |
120 | static ssize_t |
121 | edd_show_host_bus(struct edd_device *edev, char *buf) |
122 | { |
123 | struct edd_info *info; |
124 | char *p = buf; |
125 | int i; |
126 | |
127 | if (!edev) |
128 | return -EINVAL; |
129 | info = edd_dev_get_info(edev); |
130 | if (!info || !buf) |
131 | return -EINVAL; |
132 | |
133 | for (i = 0; i < 4; i++) { |
134 | if (isprint(info->params.host_bus_type[i])) { |
135 | p += scnprintf(buf: p, left, fmt: "%c" , info->params.host_bus_type[i]); |
136 | } else { |
137 | p += scnprintf(buf: p, left, fmt: " " ); |
138 | } |
139 | } |
140 | |
141 | if (!strncmp(info->params.host_bus_type, "ISA" , 3)) { |
142 | p += scnprintf(buf: p, left, fmt: "\tbase_address: %x\n" , |
143 | info->params.interface_path.isa.base_address); |
144 | } else if (!strncmp(info->params.host_bus_type, "PCIX" , 4) || |
145 | !strncmp(info->params.host_bus_type, "PCI" , 3) || |
146 | !strncmp(info->params.host_bus_type, "XPRS" , 4)) { |
147 | p += scnprintf(buf: p, left, |
148 | fmt: "\t%02x:%02x.%d channel: %u\n" , |
149 | info->params.interface_path.pci.bus, |
150 | info->params.interface_path.pci.slot, |
151 | info->params.interface_path.pci.function, |
152 | info->params.interface_path.pci.channel); |
153 | } else if (!strncmp(info->params.host_bus_type, "IBND" , 4) || |
154 | !strncmp(info->params.host_bus_type, "HTPT" , 4)) { |
155 | p += scnprintf(buf: p, left, |
156 | fmt: "\tTBD: %llx\n" , |
157 | info->params.interface_path.ibnd.reserved); |
158 | |
159 | } else { |
160 | p += scnprintf(buf: p, left, fmt: "\tunknown: %llx\n" , |
161 | info->params.interface_path.unknown.reserved); |
162 | } |
163 | return (p - buf); |
164 | } |
165 | |
166 | static ssize_t |
167 | edd_show_interface(struct edd_device *edev, char *buf) |
168 | { |
169 | struct edd_info *info; |
170 | char *p = buf; |
171 | int i; |
172 | |
173 | if (!edev) |
174 | return -EINVAL; |
175 | info = edd_dev_get_info(edev); |
176 | if (!info || !buf) |
177 | return -EINVAL; |
178 | |
179 | for (i = 0; i < 8; i++) { |
180 | if (isprint(info->params.interface_type[i])) { |
181 | p += scnprintf(buf: p, left, fmt: "%c" , info->params.interface_type[i]); |
182 | } else { |
183 | p += scnprintf(buf: p, left, fmt: " " ); |
184 | } |
185 | } |
186 | if (!strncmp(info->params.interface_type, "ATAPI" , 5)) { |
187 | p += scnprintf(buf: p, left, fmt: "\tdevice: %u lun: %u\n" , |
188 | info->params.device_path.atapi.device, |
189 | info->params.device_path.atapi.lun); |
190 | } else if (!strncmp(info->params.interface_type, "ATA" , 3)) { |
191 | p += scnprintf(buf: p, left, fmt: "\tdevice: %u\n" , |
192 | info->params.device_path.ata.device); |
193 | } else if (!strncmp(info->params.interface_type, "SCSI" , 4)) { |
194 | p += scnprintf(buf: p, left, fmt: "\tid: %u lun: %llu\n" , |
195 | info->params.device_path.scsi.id, |
196 | info->params.device_path.scsi.lun); |
197 | } else if (!strncmp(info->params.interface_type, "USB" , 3)) { |
198 | p += scnprintf(buf: p, left, fmt: "\tserial_number: %llx\n" , |
199 | info->params.device_path.usb.serial_number); |
200 | } else if (!strncmp(info->params.interface_type, "1394" , 4)) { |
201 | p += scnprintf(buf: p, left, fmt: "\teui: %llx\n" , |
202 | info->params.device_path.i1394.eui); |
203 | } else if (!strncmp(info->params.interface_type, "FIBRE" , 5)) { |
204 | p += scnprintf(buf: p, left, fmt: "\twwid: %llx lun: %llx\n" , |
205 | info->params.device_path.fibre.wwid, |
206 | info->params.device_path.fibre.lun); |
207 | } else if (!strncmp(info->params.interface_type, "I2O" , 3)) { |
208 | p += scnprintf(buf: p, left, fmt: "\tidentity_tag: %llx\n" , |
209 | info->params.device_path.i2o.identity_tag); |
210 | } else if (!strncmp(info->params.interface_type, "RAID" , 4)) { |
211 | p += scnprintf(buf: p, left, fmt: "\tidentity_tag: %x\n" , |
212 | info->params.device_path.raid.array_number); |
213 | } else if (!strncmp(info->params.interface_type, "SATA" , 4)) { |
214 | p += scnprintf(buf: p, left, fmt: "\tdevice: %u\n" , |
215 | info->params.device_path.sata.device); |
216 | } else { |
217 | p += scnprintf(buf: p, left, fmt: "\tunknown: %llx %llx\n" , |
218 | info->params.device_path.unknown.reserved1, |
219 | info->params.device_path.unknown.reserved2); |
220 | } |
221 | |
222 | return (p - buf); |
223 | } |
224 | |
225 | /** |
226 | * edd_show_raw_data() - copies raw data to buffer for userspace to parse |
227 | * @edev: target edd_device |
228 | * @buf: output buffer |
229 | * |
230 | * Returns: number of bytes written, or -EINVAL on failure |
231 | */ |
232 | static ssize_t |
233 | edd_show_raw_data(struct edd_device *edev, char *buf) |
234 | { |
235 | struct edd_info *info; |
236 | ssize_t len = sizeof (info->params); |
237 | if (!edev) |
238 | return -EINVAL; |
239 | info = edd_dev_get_info(edev); |
240 | if (!info || !buf) |
241 | return -EINVAL; |
242 | |
243 | if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE)) |
244 | len = info->params.length; |
245 | |
246 | /* In case of buggy BIOSs */ |
247 | if (len > (sizeof(info->params))) |
248 | len = sizeof(info->params); |
249 | |
250 | memcpy(buf, &info->params, len); |
251 | return len; |
252 | } |
253 | |
254 | static ssize_t |
255 | edd_show_version(struct edd_device *edev, char *buf) |
256 | { |
257 | struct edd_info *info; |
258 | char *p = buf; |
259 | if (!edev) |
260 | return -EINVAL; |
261 | info = edd_dev_get_info(edev); |
262 | if (!info || !buf) |
263 | return -EINVAL; |
264 | |
265 | p += scnprintf(buf: p, left, fmt: "0x%02x\n" , info->version); |
266 | return (p - buf); |
267 | } |
268 | |
269 | static ssize_t |
270 | edd_show_mbr_signature(struct edd_device *edev, char *buf) |
271 | { |
272 | char *p = buf; |
273 | p += scnprintf(buf: p, left, fmt: "0x%08x\n" , edev->mbr_signature); |
274 | return (p - buf); |
275 | } |
276 | |
277 | static ssize_t |
278 | edd_show_extensions(struct edd_device *edev, char *buf) |
279 | { |
280 | struct edd_info *info; |
281 | char *p = buf; |
282 | if (!edev) |
283 | return -EINVAL; |
284 | info = edd_dev_get_info(edev); |
285 | if (!info || !buf) |
286 | return -EINVAL; |
287 | |
288 | if (info->interface_support & EDD_EXT_FIXED_DISK_ACCESS) { |
289 | p += scnprintf(buf: p, left, fmt: "Fixed disk access\n" ); |
290 | } |
291 | if (info->interface_support & EDD_EXT_DEVICE_LOCKING_AND_EJECTING) { |
292 | p += scnprintf(buf: p, left, fmt: "Device locking and ejecting\n" ); |
293 | } |
294 | if (info->interface_support & EDD_EXT_ENHANCED_DISK_DRIVE_SUPPORT) { |
295 | p += scnprintf(buf: p, left, fmt: "Enhanced Disk Drive support\n" ); |
296 | } |
297 | if (info->interface_support & EDD_EXT_64BIT_EXTENSIONS) { |
298 | p += scnprintf(buf: p, left, fmt: "64-bit extensions\n" ); |
299 | } |
300 | return (p - buf); |
301 | } |
302 | |
303 | static ssize_t |
304 | edd_show_info_flags(struct edd_device *edev, char *buf) |
305 | { |
306 | struct edd_info *info; |
307 | char *p = buf; |
308 | if (!edev) |
309 | return -EINVAL; |
310 | info = edd_dev_get_info(edev); |
311 | if (!info || !buf) |
312 | return -EINVAL; |
313 | |
314 | if (info->params.info_flags & EDD_INFO_DMA_BOUNDARY_ERROR_TRANSPARENT) |
315 | p += scnprintf(buf: p, left, fmt: "DMA boundary error transparent\n" ); |
316 | if (info->params.info_flags & EDD_INFO_GEOMETRY_VALID) |
317 | p += scnprintf(buf: p, left, fmt: "geometry valid\n" ); |
318 | if (info->params.info_flags & EDD_INFO_REMOVABLE) |
319 | p += scnprintf(buf: p, left, fmt: "removable\n" ); |
320 | if (info->params.info_flags & EDD_INFO_WRITE_VERIFY) |
321 | p += scnprintf(buf: p, left, fmt: "write verify\n" ); |
322 | if (info->params.info_flags & EDD_INFO_MEDIA_CHANGE_NOTIFICATION) |
323 | p += scnprintf(buf: p, left, fmt: "media change notification\n" ); |
324 | if (info->params.info_flags & EDD_INFO_LOCKABLE) |
325 | p += scnprintf(buf: p, left, fmt: "lockable\n" ); |
326 | if (info->params.info_flags & EDD_INFO_NO_MEDIA_PRESENT) |
327 | p += scnprintf(buf: p, left, fmt: "no media present\n" ); |
328 | if (info->params.info_flags & EDD_INFO_USE_INT13_FN50) |
329 | p += scnprintf(buf: p, left, fmt: "use int13 fn50\n" ); |
330 | return (p - buf); |
331 | } |
332 | |
333 | static ssize_t |
334 | edd_show_legacy_max_cylinder(struct edd_device *edev, char *buf) |
335 | { |
336 | struct edd_info *info; |
337 | char *p = buf; |
338 | if (!edev) |
339 | return -EINVAL; |
340 | info = edd_dev_get_info(edev); |
341 | if (!info || !buf) |
342 | return -EINVAL; |
343 | |
344 | p += scnprintf(buf: p, left, fmt: "%u\n" , info->legacy_max_cylinder); |
345 | return (p - buf); |
346 | } |
347 | |
348 | static ssize_t |
349 | edd_show_legacy_max_head(struct edd_device *edev, char *buf) |
350 | { |
351 | struct edd_info *info; |
352 | char *p = buf; |
353 | if (!edev) |
354 | return -EINVAL; |
355 | info = edd_dev_get_info(edev); |
356 | if (!info || !buf) |
357 | return -EINVAL; |
358 | |
359 | p += scnprintf(buf: p, left, fmt: "%u\n" , info->legacy_max_head); |
360 | return (p - buf); |
361 | } |
362 | |
363 | static ssize_t |
364 | edd_show_legacy_sectors_per_track(struct edd_device *edev, char *buf) |
365 | { |
366 | struct edd_info *info; |
367 | char *p = buf; |
368 | if (!edev) |
369 | return -EINVAL; |
370 | info = edd_dev_get_info(edev); |
371 | if (!info || !buf) |
372 | return -EINVAL; |
373 | |
374 | p += scnprintf(buf: p, left, fmt: "%u\n" , info->legacy_sectors_per_track); |
375 | return (p - buf); |
376 | } |
377 | |
378 | static ssize_t |
379 | edd_show_default_cylinders(struct edd_device *edev, char *buf) |
380 | { |
381 | struct edd_info *info; |
382 | char *p = buf; |
383 | if (!edev) |
384 | return -EINVAL; |
385 | info = edd_dev_get_info(edev); |
386 | if (!info || !buf) |
387 | return -EINVAL; |
388 | |
389 | p += scnprintf(buf: p, left, fmt: "%u\n" , info->params.num_default_cylinders); |
390 | return (p - buf); |
391 | } |
392 | |
393 | static ssize_t |
394 | edd_show_default_heads(struct edd_device *edev, char *buf) |
395 | { |
396 | struct edd_info *info; |
397 | char *p = buf; |
398 | if (!edev) |
399 | return -EINVAL; |
400 | info = edd_dev_get_info(edev); |
401 | if (!info || !buf) |
402 | return -EINVAL; |
403 | |
404 | p += scnprintf(buf: p, left, fmt: "%u\n" , info->params.num_default_heads); |
405 | return (p - buf); |
406 | } |
407 | |
408 | static ssize_t |
409 | edd_show_default_sectors_per_track(struct edd_device *edev, char *buf) |
410 | { |
411 | struct edd_info *info; |
412 | char *p = buf; |
413 | if (!edev) |
414 | return -EINVAL; |
415 | info = edd_dev_get_info(edev); |
416 | if (!info || !buf) |
417 | return -EINVAL; |
418 | |
419 | p += scnprintf(buf: p, left, fmt: "%u\n" , info->params.sectors_per_track); |
420 | return (p - buf); |
421 | } |
422 | |
423 | static ssize_t |
424 | edd_show_sectors(struct edd_device *edev, char *buf) |
425 | { |
426 | struct edd_info *info; |
427 | char *p = buf; |
428 | if (!edev) |
429 | return -EINVAL; |
430 | info = edd_dev_get_info(edev); |
431 | if (!info || !buf) |
432 | return -EINVAL; |
433 | |
434 | p += scnprintf(buf: p, left, fmt: "%llu\n" , info->params.number_of_sectors); |
435 | return (p - buf); |
436 | } |
437 | |
438 | |
439 | /* |
440 | * Some device instances may not have all the above attributes, |
441 | * or the attribute values may be meaningless (i.e. if |
442 | * the device is < EDD 3.0, it won't have host_bus and interface |
443 | * information), so don't bother making files for them. Likewise |
444 | * if the default_{cylinders,heads,sectors_per_track} values |
445 | * are zero, the BIOS doesn't provide sane values, don't bother |
446 | * creating files for them either. |
447 | */ |
448 | |
449 | static int |
450 | edd_has_legacy_max_cylinder(struct edd_device *edev) |
451 | { |
452 | struct edd_info *info; |
453 | if (!edev) |
454 | return 0; |
455 | info = edd_dev_get_info(edev); |
456 | if (!info) |
457 | return 0; |
458 | return info->legacy_max_cylinder > 0; |
459 | } |
460 | |
461 | static int |
462 | edd_has_legacy_max_head(struct edd_device *edev) |
463 | { |
464 | struct edd_info *info; |
465 | if (!edev) |
466 | return 0; |
467 | info = edd_dev_get_info(edev); |
468 | if (!info) |
469 | return 0; |
470 | return info->legacy_max_head > 0; |
471 | } |
472 | |
473 | static int |
474 | edd_has_legacy_sectors_per_track(struct edd_device *edev) |
475 | { |
476 | struct edd_info *info; |
477 | if (!edev) |
478 | return 0; |
479 | info = edd_dev_get_info(edev); |
480 | if (!info) |
481 | return 0; |
482 | return info->legacy_sectors_per_track > 0; |
483 | } |
484 | |
485 | static int |
486 | edd_has_default_cylinders(struct edd_device *edev) |
487 | { |
488 | struct edd_info *info; |
489 | if (!edev) |
490 | return 0; |
491 | info = edd_dev_get_info(edev); |
492 | if (!info) |
493 | return 0; |
494 | return info->params.num_default_cylinders > 0; |
495 | } |
496 | |
497 | static int |
498 | edd_has_default_heads(struct edd_device *edev) |
499 | { |
500 | struct edd_info *info; |
501 | if (!edev) |
502 | return 0; |
503 | info = edd_dev_get_info(edev); |
504 | if (!info) |
505 | return 0; |
506 | return info->params.num_default_heads > 0; |
507 | } |
508 | |
509 | static int |
510 | edd_has_default_sectors_per_track(struct edd_device *edev) |
511 | { |
512 | struct edd_info *info; |
513 | if (!edev) |
514 | return 0; |
515 | info = edd_dev_get_info(edev); |
516 | if (!info) |
517 | return 0; |
518 | return info->params.sectors_per_track > 0; |
519 | } |
520 | |
521 | static int |
522 | edd_has_edd30(struct edd_device *edev) |
523 | { |
524 | struct edd_info *info; |
525 | int i; |
526 | u8 csum = 0; |
527 | |
528 | if (!edev) |
529 | return 0; |
530 | info = edd_dev_get_info(edev); |
531 | if (!info) |
532 | return 0; |
533 | |
534 | if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE)) { |
535 | return 0; |
536 | } |
537 | |
538 | |
539 | /* We support only T13 spec */ |
540 | if (info->params.device_path_info_length != 44) |
541 | return 0; |
542 | |
543 | for (i = 30; i < info->params.device_path_info_length + 30; i++) |
544 | csum += *(((u8 *)&info->params) + i); |
545 | |
546 | if (csum) |
547 | return 0; |
548 | |
549 | return 1; |
550 | } |
551 | |
552 | |
553 | static EDD_DEVICE_ATTR(raw_data, 0444, edd_show_raw_data, edd_has_edd_info); |
554 | static EDD_DEVICE_ATTR(version, 0444, edd_show_version, edd_has_edd_info); |
555 | static EDD_DEVICE_ATTR(extensions, 0444, edd_show_extensions, edd_has_edd_info); |
556 | static EDD_DEVICE_ATTR(info_flags, 0444, edd_show_info_flags, edd_has_edd_info); |
557 | static EDD_DEVICE_ATTR(sectors, 0444, edd_show_sectors, edd_has_edd_info); |
558 | static EDD_DEVICE_ATTR(legacy_max_cylinder, 0444, |
559 | edd_show_legacy_max_cylinder, |
560 | edd_has_legacy_max_cylinder); |
561 | static EDD_DEVICE_ATTR(legacy_max_head, 0444, edd_show_legacy_max_head, |
562 | edd_has_legacy_max_head); |
563 | static EDD_DEVICE_ATTR(legacy_sectors_per_track, 0444, |
564 | edd_show_legacy_sectors_per_track, |
565 | edd_has_legacy_sectors_per_track); |
566 | static EDD_DEVICE_ATTR(default_cylinders, 0444, edd_show_default_cylinders, |
567 | edd_has_default_cylinders); |
568 | static EDD_DEVICE_ATTR(default_heads, 0444, edd_show_default_heads, |
569 | edd_has_default_heads); |
570 | static EDD_DEVICE_ATTR(default_sectors_per_track, 0444, |
571 | edd_show_default_sectors_per_track, |
572 | edd_has_default_sectors_per_track); |
573 | static EDD_DEVICE_ATTR(interface, 0444, edd_show_interface, edd_has_edd30); |
574 | static EDD_DEVICE_ATTR(host_bus, 0444, edd_show_host_bus, edd_has_edd30); |
575 | static EDD_DEVICE_ATTR(mbr_signature, 0444, edd_show_mbr_signature, edd_has_mbr_signature); |
576 | |
577 | /* These attributes are conditional and only added for some devices. */ |
578 | static struct edd_attribute * edd_attrs[] = { |
579 | &edd_attr_raw_data, |
580 | &edd_attr_version, |
581 | &edd_attr_extensions, |
582 | &edd_attr_info_flags, |
583 | &edd_attr_sectors, |
584 | &edd_attr_legacy_max_cylinder, |
585 | &edd_attr_legacy_max_head, |
586 | &edd_attr_legacy_sectors_per_track, |
587 | &edd_attr_default_cylinders, |
588 | &edd_attr_default_heads, |
589 | &edd_attr_default_sectors_per_track, |
590 | &edd_attr_interface, |
591 | &edd_attr_host_bus, |
592 | &edd_attr_mbr_signature, |
593 | NULL, |
594 | }; |
595 | |
596 | /** |
597 | * edd_release - free edd structure |
598 | * @kobj: kobject of edd structure |
599 | * |
600 | * This is called when the refcount of the edd structure |
601 | * reaches 0. This should happen right after we unregister, |
602 | * but just in case, we use the release callback anyway. |
603 | */ |
604 | |
605 | static void edd_release(struct kobject * kobj) |
606 | { |
607 | struct edd_device * dev = to_edd_device(kobj); |
608 | kfree(objp: dev); |
609 | } |
610 | |
611 | static const struct kobj_type edd_ktype = { |
612 | .release = edd_release, |
613 | .sysfs_ops = &edd_attr_ops, |
614 | }; |
615 | |
616 | static struct kset *edd_kset; |
617 | |
618 | |
619 | /** |
620 | * edd_dev_is_type() - is this EDD device a 'type' device? |
621 | * @edev: target edd_device |
622 | * @type: a host bus or interface identifier string per the EDD spec |
623 | * |
624 | * Returns 1 (TRUE) if it is a 'type' device, 0 otherwise. |
625 | */ |
626 | static int |
627 | edd_dev_is_type(struct edd_device *edev, const char *type) |
628 | { |
629 | struct edd_info *info; |
630 | if (!edev) |
631 | return 0; |
632 | info = edd_dev_get_info(edev); |
633 | |
634 | if (type && info) { |
635 | if (!strncmp(info->params.host_bus_type, type, strlen(type)) || |
636 | !strncmp(info->params.interface_type, type, strlen(type))) |
637 | return 1; |
638 | } |
639 | return 0; |
640 | } |
641 | |
642 | /** |
643 | * edd_get_pci_dev() - finds pci_dev that matches edev |
644 | * @edev: edd_device |
645 | * |
646 | * Returns pci_dev if found, or NULL |
647 | */ |
648 | static struct pci_dev * |
649 | edd_get_pci_dev(struct edd_device *edev) |
650 | { |
651 | struct edd_info *info = edd_dev_get_info(edev); |
652 | |
653 | if (edd_dev_is_type(edev, type: "PCI" ) || edd_dev_is_type(edev, type: "XPRS" )) { |
654 | return pci_get_domain_bus_and_slot(domain: 0, |
655 | bus: info->params.interface_path.pci.bus, |
656 | PCI_DEVFN(info->params.interface_path.pci.slot, |
657 | info->params.interface_path.pci.function)); |
658 | } |
659 | return NULL; |
660 | } |
661 | |
662 | static int |
663 | edd_create_symlink_to_pcidev(struct edd_device *edev) |
664 | { |
665 | |
666 | struct pci_dev *pci_dev = edd_get_pci_dev(edev); |
667 | int ret; |
668 | if (!pci_dev) |
669 | return 1; |
670 | ret = sysfs_create_link(kobj: &edev->kobj,target: &pci_dev->dev.kobj,name: "pci_dev" ); |
671 | pci_dev_put(dev: pci_dev); |
672 | return ret; |
673 | } |
674 | |
675 | static inline void |
676 | edd_device_unregister(struct edd_device *edev) |
677 | { |
678 | kobject_put(kobj: &edev->kobj); |
679 | } |
680 | |
681 | static void edd_populate_dir(struct edd_device * edev) |
682 | { |
683 | struct edd_attribute * attr; |
684 | int error = 0; |
685 | int i; |
686 | |
687 | for (i = 0; (attr = edd_attrs[i]) && !error; i++) { |
688 | if (!attr->test || attr->test(edev)) |
689 | error = sysfs_create_file(kobj: &edev->kobj,attr: &attr->attr); |
690 | } |
691 | |
692 | if (!error) { |
693 | edd_create_symlink_to_pcidev(edev); |
694 | } |
695 | } |
696 | |
697 | static int |
698 | edd_device_register(struct edd_device *edev, int i) |
699 | { |
700 | int error; |
701 | |
702 | if (!edev) |
703 | return 1; |
704 | edd_dev_set_info(edev, i); |
705 | edev->kobj.kset = edd_kset; |
706 | error = kobject_init_and_add(kobj: &edev->kobj, ktype: &edd_ktype, NULL, |
707 | fmt: "int13_dev%02x" , 0x80 + i); |
708 | if (!error) { |
709 | edd_populate_dir(edev); |
710 | kobject_uevent(kobj: &edev->kobj, action: KOBJ_ADD); |
711 | } |
712 | return error; |
713 | } |
714 | |
715 | static inline int edd_num_devices(void) |
716 | { |
717 | return max_t(unsigned char, |
718 | min_t(unsigned char, EDD_MBR_SIG_MAX, edd.mbr_signature_nr), |
719 | min_t(unsigned char, EDDMAXNR, edd.edd_info_nr)); |
720 | } |
721 | |
722 | /** |
723 | * edd_init() - creates sysfs tree of EDD data |
724 | */ |
725 | static int __init |
726 | edd_init(void) |
727 | { |
728 | int i; |
729 | int rc=0; |
730 | struct edd_device *edev; |
731 | |
732 | if (!edd_num_devices()) |
733 | return -ENODEV; |
734 | |
735 | printk(KERN_INFO "BIOS EDD facility v%s %s, %d devices found\n" , |
736 | EDD_VERSION, EDD_DATE, edd_num_devices()); |
737 | |
738 | edd_kset = kset_create_and_add(name: "edd" , NULL, parent_kobj: firmware_kobj); |
739 | if (!edd_kset) |
740 | return -ENOMEM; |
741 | |
742 | for (i = 0; i < edd_num_devices(); i++) { |
743 | edev = kzalloc(size: sizeof (*edev), GFP_KERNEL); |
744 | if (!edev) { |
745 | rc = -ENOMEM; |
746 | goto out; |
747 | } |
748 | |
749 | rc = edd_device_register(edev, i); |
750 | if (rc) { |
751 | kfree(objp: edev); |
752 | goto out; |
753 | } |
754 | edd_devices[i] = edev; |
755 | } |
756 | |
757 | return 0; |
758 | |
759 | out: |
760 | while (--i >= 0) |
761 | edd_device_unregister(edev: edd_devices[i]); |
762 | kset_unregister(kset: edd_kset); |
763 | return rc; |
764 | } |
765 | |
766 | static void __exit |
767 | edd_exit(void) |
768 | { |
769 | int i; |
770 | struct edd_device *edev; |
771 | |
772 | for (i = 0; i < edd_num_devices(); i++) { |
773 | if ((edev = edd_devices[i])) |
774 | edd_device_unregister(edev); |
775 | } |
776 | kset_unregister(kset: edd_kset); |
777 | } |
778 | |
779 | late_initcall(edd_init); |
780 | module_exit(edd_exit); |
781 | |