pci-label.c 7.14 KB
Newer Older
1 2 3 4 5 6 7
/*
 * Purpose: Export the firmware instance and label associated with
 * a pci device to sysfs
 * Copyright (C) 2010 Dell Inc.
 * by Narendra K <Narendra_K@dell.com>,
 * Jordan Hargrave <Jordan_Hargrave@dell.com>
 *
8 9 10 11 12 13 14
 * PCI Firmware Specification Revision 3.1 section 4.6.7 (DSM for Naming a
 * PCI or PCI Express Device Under Operating Systems) defines an instance
 * number and string name. This code retrieves them and exports them to sysfs.
 * If the system firmware does not provide the ACPI _DSM (Device Specific
 * Method), then the SMBIOS type 41 instance number and string is exported to
 * sysfs.
 *
15 16 17 18
 * SMBIOS defines type 41 for onboard pci devices. This code retrieves
 * the instance number and string from the type 41 record and exports
 * it to sysfs.
 *
19
 * Please see http://linux.dell.com/files/biosdevname/ for more
20 21 22 23 24 25 26 27 28
 * information.
 */

#include <linux/dmi.h>
#include <linux/sysfs.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>
#include <linux/module.h>
#include <linux/device.h>
29 30 31
#include <linux/nls.h>
#include <linux/acpi.h>
#include <linux/pci-acpi.h>
32 33
#include "pci.h"

34
#ifdef CONFIG_DMI
35 36 37 38 39 40
enum smbios_attr_enum {
	SMBIOS_ATTR_NONE = 0,
	SMBIOS_ATTR_LABEL_SHOW,
	SMBIOS_ATTR_INSTANCE_SHOW,
};

Ryan Desfosses's avatar
Ryan Desfosses committed
41 42
static size_t find_smbios_instance_string(struct pci_dev *pdev, char *buf,
					  enum smbios_attr_enum attribute)
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
{
	const struct dmi_device *dmi;
	struct dmi_dev_onboard *donboard;
	int bus;
	int devfn;

	bus = pdev->bus->number;
	devfn = pdev->devfn;

	dmi = NULL;
	while ((dmi = dmi_find_device(DMI_DEV_TYPE_DEV_ONBOARD,
				      NULL, dmi)) != NULL) {
		donboard = dmi->device_data;
		if (donboard && donboard->bus == bus &&
					donboard->devfn == devfn) {
			if (buf) {
				if (attribute == SMBIOS_ATTR_INSTANCE_SHOW)
					return scnprintf(buf, PAGE_SIZE,
							 "%d\n",
							 donboard->instance);
				else if (attribute == SMBIOS_ATTR_LABEL_SHOW)
					return scnprintf(buf, PAGE_SIZE,
							 "%s\n",
							 dmi->name);
			}
			return strlen(dmi->name);
		}
	}
	return 0;
}

Ryan Desfosses's avatar
Ryan Desfosses committed
74 75
static umode_t smbios_instance_string_exist(struct kobject *kobj,
					    struct attribute *attr, int n)
76 77 78 79
{
	struct device *dev;
	struct pci_dev *pdev;

80
	dev = kobj_to_dev(kobj);
81 82 83 84 85 86
	pdev = to_pci_dev(dev);

	return find_smbios_instance_string(pdev, NULL, SMBIOS_ATTR_NONE) ?
					   S_IRUGO : 0;
}

Ryan Desfosses's avatar
Ryan Desfosses committed
87 88
static ssize_t smbioslabel_show(struct device *dev,
				struct device_attribute *attr, char *buf)
89 90 91 92 93 94 95 96
{
	struct pci_dev *pdev;
	pdev = to_pci_dev(dev);

	return find_smbios_instance_string(pdev, buf,
					   SMBIOS_ATTR_LABEL_SHOW);
}

Ryan Desfosses's avatar
Ryan Desfosses committed
97 98
static ssize_t smbiosinstance_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
99 100 101 102 103 104 105 106 107
{
	struct pci_dev *pdev;
	pdev = to_pci_dev(dev);

	return find_smbios_instance_string(pdev, buf,
					   SMBIOS_ATTR_INSTANCE_SHOW);
}

static struct device_attribute smbios_attr_label = {
108
	.attr = {.name = "label", .mode = 0444},
109 110 111 112
	.show = smbioslabel_show,
};

static struct device_attribute smbios_attr_instance = {
113
	.attr = {.name = "index", .mode = 0444},
114 115 116 117 118 119 120 121 122 123 124 125 126 127
	.show = smbiosinstance_show,
};

static struct attribute *smbios_attributes[] = {
	&smbios_attr_label.attr,
	&smbios_attr_instance.attr,
	NULL,
};

static struct attribute_group smbios_attr_group = {
	.attrs = smbios_attributes,
	.is_visible = smbios_instance_string_exist,
};

Ryan Desfosses's avatar
Ryan Desfosses committed
128
static int pci_create_smbiosname_file(struct pci_dev *pdev)
129
{
130
	return sysfs_create_group(&pdev->dev.kobj, &smbios_attr_group);
131 132
}

Ryan Desfosses's avatar
Ryan Desfosses committed
133
static void pci_remove_smbiosname_file(struct pci_dev *pdev)
134 135 136
{
	sysfs_remove_group(&pdev->dev.kobj, &smbios_attr_group);
}
137
#else
Ryan Desfosses's avatar
Ryan Desfosses committed
138
static inline int pci_create_smbiosname_file(struct pci_dev *pdev)
139 140 141 142
{
	return -1;
}

Ryan Desfosses's avatar
Ryan Desfosses committed
143
static inline void pci_remove_smbiosname_file(struct pci_dev *pdev)
144 145
{
}
146
#endif
147

148
#ifdef CONFIG_ACPI
149 150 151 152 153 154 155 156
enum acpi_attr_enum {
	ACPI_ATTR_LABEL_SHOW,
	ACPI_ATTR_INDEX_SHOW,
};

static void dsm_label_utf16s_to_utf8s(union acpi_object *obj, char *buf)
{
	int len;
157 158
	len = utf16s_to_utf8s((const wchar_t *)obj->buffer.pointer,
			      obj->buffer.length,
159 160 161 162 163
			      UTF16_LITTLE_ENDIAN,
			      buf, PAGE_SIZE);
	buf[len] = '\n';
}

Ryan Desfosses's avatar
Ryan Desfosses committed
164 165
static int dsm_get_label(struct device *dev, char *buf,
			 enum acpi_attr_enum attr)
166
{
167 168 169 170 171 172
	acpi_handle handle;
	union acpi_object *obj, *tmp;
	int len = -1;

	handle = ACPI_HANDLE(dev);
	if (!handle)
173 174
		return -1;

175
	obj = acpi_evaluate_dsm(handle, pci_acpi_dsm_uuid, 0x2,
176 177 178 179 180 181 182
				DEVICE_LABEL_DSM, NULL);
	if (!obj)
		return -1;

	tmp = obj->package.elements;
	if (obj->type == ACPI_TYPE_PACKAGE && obj->package.count == 2 &&
	    tmp[0].type == ACPI_TYPE_INTEGER &&
183 184
	    (tmp[1].type == ACPI_TYPE_STRING ||
	     tmp[1].type == ACPI_TYPE_BUFFER)) {
185 186 187 188 189
		/*
		 * The second string element is optional even when
		 * this _DSM is implemented; when not implemented,
		 * this entry must return a null string.
		 */
190
		if (attr == ACPI_ATTR_INDEX_SHOW) {
191
			scnprintf(buf, PAGE_SIZE, "%llu\n", tmp->integer.value);
192 193 194 195 196 197 198
		} else if (attr == ACPI_ATTR_LABEL_SHOW) {
			if (tmp[1].type == ACPI_TYPE_STRING)
				scnprintf(buf, PAGE_SIZE, "%s\n",
					  tmp[1].string.pointer);
			else if (tmp[1].type == ACPI_TYPE_BUFFER)
				dsm_label_utf16s_to_utf8s(tmp + 1, buf);
		}
199
		len = strlen(buf) > 0 ? strlen(buf) : -1;
200
	}
201

202
	ACPI_FREE(obj);
203

204
	return len;
205 206
}

Ryan Desfosses's avatar
Ryan Desfosses committed
207
static bool device_has_dsm(struct device *dev)
208 209 210
{
	acpi_handle handle;

211
	handle = ACPI_HANDLE(dev);
212
	if (!handle)
213
		return false;
214

215
	return !!acpi_check_dsm(handle, pci_acpi_dsm_uuid, 0x2,
216
				1 << DEVICE_LABEL_DSM);
217 218
}

Ryan Desfosses's avatar
Ryan Desfosses committed
219 220
static umode_t acpi_index_string_exist(struct kobject *kobj,
				       struct attribute *attr, int n)
221 222 223
{
	struct device *dev;

224
	dev = kobj_to_dev(kobj);
225 226 227 228 229 230 231

	if (device_has_dsm(dev))
		return S_IRUGO;

	return 0;
}

Ryan Desfosses's avatar
Ryan Desfosses committed
232 233
static ssize_t acpilabel_show(struct device *dev,
			      struct device_attribute *attr, char *buf)
234
{
235
	return dsm_get_label(dev, buf, ACPI_ATTR_LABEL_SHOW);
236 237
}

Ryan Desfosses's avatar
Ryan Desfosses committed
238 239
static ssize_t acpiindex_show(struct device *dev,
			      struct device_attribute *attr, char *buf)
240
{
241
	return dsm_get_label(dev, buf, ACPI_ATTR_INDEX_SHOW);
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
}

static struct device_attribute acpi_attr_label = {
	.attr = {.name = "label", .mode = 0444},
	.show = acpilabel_show,
};

static struct device_attribute acpi_attr_index = {
	.attr = {.name = "acpi_index", .mode = 0444},
	.show = acpiindex_show,
};

static struct attribute *acpi_attributes[] = {
	&acpi_attr_label.attr,
	&acpi_attr_index.attr,
	NULL,
};

static struct attribute_group acpi_attr_group = {
	.attrs = acpi_attributes,
	.is_visible = acpi_index_string_exist,
};

Ryan Desfosses's avatar
Ryan Desfosses committed
265
static int pci_create_acpi_index_label_files(struct pci_dev *pdev)
266 267 268 269
{
	return sysfs_create_group(&pdev->dev.kobj, &acpi_attr_group);
}

Ryan Desfosses's avatar
Ryan Desfosses committed
270
static int pci_remove_acpi_index_label_files(struct pci_dev *pdev)
271 272 273 274
{
	sysfs_remove_group(&pdev->dev.kobj, &acpi_attr_group);
	return 0;
}
275
#else
Ryan Desfosses's avatar
Ryan Desfosses committed
276
static inline int pci_create_acpi_index_label_files(struct pci_dev *pdev)
277 278 279 280
{
	return -1;
}

Ryan Desfosses's avatar
Ryan Desfosses committed
281
static inline int pci_remove_acpi_index_label_files(struct pci_dev *pdev)
282 283 284 285
{
	return -1;
}

Ryan Desfosses's avatar
Ryan Desfosses committed
286
static inline bool device_has_dsm(struct device *dev)
287 288 289
{
	return false;
}
290 291
#endif

292 293
void pci_create_firmware_label_files(struct pci_dev *pdev)
{
294 295 296 297
	if (device_has_dsm(&pdev->dev))
		pci_create_acpi_index_label_files(pdev);
	else
		pci_create_smbiosname_file(pdev);
298 299 300 301
}

void pci_remove_firmware_label_files(struct pci_dev *pdev)
{
302 303 304 305
	if (device_has_dsm(&pdev->dev))
		pci_remove_acpi_index_label_files(pdev);
	else
		pci_remove_smbiosname_file(pdev);
306
}