blob: 824e247edc5882bc0ce80e1a161909d972bb4391 [file] [log] [blame]
Narendra K911e1c92010-07-26 05:56:50 -05001/*
2 * Purpose: Export the firmware instance and label associated with
3 * a pci device to sysfs
4 * Copyright (C) 2010 Dell Inc.
5 * by Narendra K <Narendra_K@dell.com>,
6 * Jordan Hargrave <Jordan_Hargrave@dell.com>
7 *
Narendra_K@Dell.com60589892011-03-02 22:34:17 +05308 * PCI Firmware Specification Revision 3.1 section 4.6.7 (DSM for Naming a
9 * PCI or PCI Express Device Under Operating Systems) defines an instance
10 * number and string name. This code retrieves them and exports them to sysfs.
11 * If the system firmware does not provide the ACPI _DSM (Device Specific
12 * Method), then the SMBIOS type 41 instance number and string is exported to
13 * sysfs.
14 *
Narendra K911e1c92010-07-26 05:56:50 -050015 * SMBIOS defines type 41 for onboard pci devices. This code retrieves
16 * the instance number and string from the type 41 record and exports
17 * it to sysfs.
18 *
19 * Please see http://linux.dell.com/wiki/index.php/Oss/libnetdevname for more
20 * information.
21 */
22
23#include <linux/dmi.h>
24#include <linux/sysfs.h>
25#include <linux/pci.h>
26#include <linux/pci_ids.h>
27#include <linux/module.h>
28#include <linux/device.h>
Narendra_K@Dell.com60589892011-03-02 22:34:17 +053029#include <linux/nls.h>
30#include <linux/acpi.h>
31#include <linux/pci-acpi.h>
32#include <acpi/acpi_drivers.h>
33#include <acpi/acpi_bus.h>
Narendra K911e1c92010-07-26 05:56:50 -050034#include "pci.h"
35
Narendra_K@Dell.com60589892011-03-02 22:34:17 +053036#define DEVICE_LABEL_DSM 0x07
37
38#ifndef CONFIG_DMI
39
40static inline int
41pci_create_smbiosname_file(struct pci_dev *pdev)
42{
43 return -1;
44}
45
46static inline void
47pci_remove_smbiosname_file(struct pci_dev *pdev)
48{
49}
50
51#else
52
Narendra K911e1c92010-07-26 05:56:50 -050053enum smbios_attr_enum {
54 SMBIOS_ATTR_NONE = 0,
55 SMBIOS_ATTR_LABEL_SHOW,
56 SMBIOS_ATTR_INSTANCE_SHOW,
57};
58
59static mode_t
60find_smbios_instance_string(struct pci_dev *pdev, char *buf,
61 enum smbios_attr_enum attribute)
62{
63 const struct dmi_device *dmi;
64 struct dmi_dev_onboard *donboard;
65 int bus;
66 int devfn;
67
68 bus = pdev->bus->number;
69 devfn = pdev->devfn;
70
71 dmi = NULL;
72 while ((dmi = dmi_find_device(DMI_DEV_TYPE_DEV_ONBOARD,
73 NULL, dmi)) != NULL) {
74 donboard = dmi->device_data;
75 if (donboard && donboard->bus == bus &&
76 donboard->devfn == devfn) {
77 if (buf) {
78 if (attribute == SMBIOS_ATTR_INSTANCE_SHOW)
79 return scnprintf(buf, PAGE_SIZE,
80 "%d\n",
81 donboard->instance);
82 else if (attribute == SMBIOS_ATTR_LABEL_SHOW)
83 return scnprintf(buf, PAGE_SIZE,
84 "%s\n",
85 dmi->name);
86 }
87 return strlen(dmi->name);
88 }
89 }
90 return 0;
91}
92
93static mode_t
94smbios_instance_string_exist(struct kobject *kobj, struct attribute *attr,
95 int n)
96{
97 struct device *dev;
98 struct pci_dev *pdev;
99
100 dev = container_of(kobj, struct device, kobj);
101 pdev = to_pci_dev(dev);
102
103 return find_smbios_instance_string(pdev, NULL, SMBIOS_ATTR_NONE) ?
104 S_IRUGO : 0;
105}
106
107static ssize_t
108smbioslabel_show(struct device *dev, struct device_attribute *attr, char *buf)
109{
110 struct pci_dev *pdev;
111 pdev = to_pci_dev(dev);
112
113 return find_smbios_instance_string(pdev, buf,
114 SMBIOS_ATTR_LABEL_SHOW);
115}
116
117static ssize_t
118smbiosinstance_show(struct device *dev,
119 struct device_attribute *attr, char *buf)
120{
121 struct pci_dev *pdev;
122 pdev = to_pci_dev(dev);
123
124 return find_smbios_instance_string(pdev, buf,
125 SMBIOS_ATTR_INSTANCE_SHOW);
126}
127
128static struct device_attribute smbios_attr_label = {
Stephen Rothwell763e9db2010-08-04 14:25:31 +1000129 .attr = {.name = "label", .mode = 0444},
Narendra K911e1c92010-07-26 05:56:50 -0500130 .show = smbioslabel_show,
131};
132
133static struct device_attribute smbios_attr_instance = {
Stephen Rothwell763e9db2010-08-04 14:25:31 +1000134 .attr = {.name = "index", .mode = 0444},
Narendra K911e1c92010-07-26 05:56:50 -0500135 .show = smbiosinstance_show,
136};
137
138static struct attribute *smbios_attributes[] = {
139 &smbios_attr_label.attr,
140 &smbios_attr_instance.attr,
141 NULL,
142};
143
144static struct attribute_group smbios_attr_group = {
145 .attrs = smbios_attributes,
146 .is_visible = smbios_instance_string_exist,
147};
148
149static int
150pci_create_smbiosname_file(struct pci_dev *pdev)
151{
Narendra_K@Dell.com60589892011-03-02 22:34:17 +0530152 return sysfs_create_group(&pdev->dev.kobj, &smbios_attr_group);
Narendra K911e1c92010-07-26 05:56:50 -0500153}
154
155static void
156pci_remove_smbiosname_file(struct pci_dev *pdev)
157{
158 sysfs_remove_group(&pdev->dev.kobj, &smbios_attr_group);
159}
160
Narendra_K@Dell.com60589892011-03-02 22:34:17 +0530161#endif
162
163#ifndef CONFIG_ACPI
164
165static inline int
166pci_create_acpi_index_label_files(struct pci_dev *pdev)
167{
168 return -1;
169}
170
171static inline int
172pci_remove_acpi_index_label_files(struct pci_dev *pdev)
173{
174 return -1;
175}
176
177#else
178
179static const char device_label_dsm_uuid[] = {
180 0xD0, 0x37, 0xC9, 0xE5, 0x53, 0x35, 0x7A, 0x4D,
181 0x91, 0x17, 0xEA, 0x4D, 0x19, 0xC3, 0x43, 0x4D
182};
183
184enum acpi_attr_enum {
185 ACPI_ATTR_NONE = 0,
186 ACPI_ATTR_LABEL_SHOW,
187 ACPI_ATTR_INDEX_SHOW,
188};
189
190static void dsm_label_utf16s_to_utf8s(union acpi_object *obj, char *buf)
191{
192 int len;
193 len = utf16s_to_utf8s((const wchar_t *)obj->
194 package.elements[1].string.pointer,
195 obj->package.elements[1].string.length,
196 UTF16_LITTLE_ENDIAN,
197 buf, PAGE_SIZE);
198 buf[len] = '\n';
199}
200
201static int
202dsm_get_label(acpi_handle handle, int func,
203 struct acpi_buffer *output,
204 char *buf, enum acpi_attr_enum attribute)
205{
206 struct acpi_object_list input;
207 union acpi_object params[4];
208 union acpi_object *obj;
209 int len = 0;
210
211 int err;
212
213 input.count = 4;
214 input.pointer = params;
215 params[0].type = ACPI_TYPE_BUFFER;
216 params[0].buffer.length = sizeof(device_label_dsm_uuid);
217 params[0].buffer.pointer = (char *)device_label_dsm_uuid;
218 params[1].type = ACPI_TYPE_INTEGER;
219 params[1].integer.value = 0x02;
220 params[2].type = ACPI_TYPE_INTEGER;
221 params[2].integer.value = func;
222 params[3].type = ACPI_TYPE_PACKAGE;
223 params[3].package.count = 0;
224 params[3].package.elements = NULL;
225
226 err = acpi_evaluate_object(handle, "_DSM", &input, output);
227 if (err)
228 return -1;
229
230 obj = (union acpi_object *)output->pointer;
231
232 switch (obj->type) {
233 case ACPI_TYPE_PACKAGE:
234 if (obj->package.count != 2)
235 break;
236 len = obj->package.elements[0].integer.value;
237 if (buf) {
238 if (attribute == ACPI_ATTR_INDEX_SHOW)
239 scnprintf(buf, PAGE_SIZE, "%llu\n",
240 obj->package.elements[0].integer.value);
241 else if (attribute == ACPI_ATTR_LABEL_SHOW)
242 dsm_label_utf16s_to_utf8s(obj, buf);
243 kfree(output->pointer);
244 return strlen(buf);
245 }
246 kfree(output->pointer);
247 return len;
248 break;
249 default:
250 kfree(output->pointer);
251 }
252 return -1;
253}
254
255static bool
256device_has_dsm(struct device *dev)
257{
258 acpi_handle handle;
259 struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
260
261 handle = DEVICE_ACPI_HANDLE(dev);
262
263 if (!handle)
264 return FALSE;
265
266 if (dsm_get_label(handle, DEVICE_LABEL_DSM, &output, NULL,
267 ACPI_ATTR_NONE) > 0)
268 return TRUE;
269
270 return FALSE;
271}
272
273static mode_t
274acpi_index_string_exist(struct kobject *kobj, struct attribute *attr, int n)
275{
276 struct device *dev;
277
278 dev = container_of(kobj, struct device, kobj);
279
280 if (device_has_dsm(dev))
281 return S_IRUGO;
282
283 return 0;
284}
285
286static ssize_t
287acpilabel_show(struct device *dev, struct device_attribute *attr, char *buf)
288{
289 struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
290 acpi_handle handle;
291 int length;
292
293 handle = DEVICE_ACPI_HANDLE(dev);
294
295 if (!handle)
296 return -1;
297
298 length = dsm_get_label(handle, DEVICE_LABEL_DSM,
299 &output, buf, ACPI_ATTR_LABEL_SHOW);
300
301 if (length < 1)
302 return -1;
303
304 return length;
305}
306
307static ssize_t
308acpiindex_show(struct device *dev, struct device_attribute *attr, char *buf)
309{
310 struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
311 acpi_handle handle;
312 int length;
313
314 handle = DEVICE_ACPI_HANDLE(dev);
315
316 if (!handle)
317 return -1;
318
319 length = dsm_get_label(handle, DEVICE_LABEL_DSM,
320 &output, buf, ACPI_ATTR_INDEX_SHOW);
321
322 if (length < 0)
323 return -1;
324
325 return length;
326
327}
328
329static struct device_attribute acpi_attr_label = {
330 .attr = {.name = "label", .mode = 0444},
331 .show = acpilabel_show,
332};
333
334static struct device_attribute acpi_attr_index = {
335 .attr = {.name = "acpi_index", .mode = 0444},
336 .show = acpiindex_show,
337};
338
339static struct attribute *acpi_attributes[] = {
340 &acpi_attr_label.attr,
341 &acpi_attr_index.attr,
342 NULL,
343};
344
345static struct attribute_group acpi_attr_group = {
346 .attrs = acpi_attributes,
347 .is_visible = acpi_index_string_exist,
348};
349
350static int
351pci_create_acpi_index_label_files(struct pci_dev *pdev)
352{
353 return sysfs_create_group(&pdev->dev.kobj, &acpi_attr_group);
354}
355
356static int
357pci_remove_acpi_index_label_files(struct pci_dev *pdev)
358{
359 sysfs_remove_group(&pdev->dev.kobj, &acpi_attr_group);
360 return 0;
361}
362#endif
363
Narendra K911e1c92010-07-26 05:56:50 -0500364void pci_create_firmware_label_files(struct pci_dev *pdev)
365{
Narendra_K@Dell.com60589892011-03-02 22:34:17 +0530366 if (device_has_dsm(&pdev->dev))
367 pci_create_acpi_index_label_files(pdev);
368 else
369 pci_create_smbiosname_file(pdev);
Narendra K911e1c92010-07-26 05:56:50 -0500370}
371
372void pci_remove_firmware_label_files(struct pci_dev *pdev)
373{
Narendra_K@Dell.com60589892011-03-02 22:34:17 +0530374 if (device_has_dsm(&pdev->dev))
375 pci_remove_acpi_index_label_files(pdev);
376 else
377 pci_remove_smbiosname_file(pdev);
Narendra K911e1c92010-07-26 05:56:50 -0500378}