162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Export the firmware instance and label associated with a PCI device to
462306a36Sopenharmony_ci * sysfs
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2010 Dell Inc.
762306a36Sopenharmony_ci * by Narendra K <Narendra_K@dell.com>,
862306a36Sopenharmony_ci * Jordan Hargrave <Jordan_Hargrave@dell.com>
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * PCI Firmware Specification Revision 3.1 section 4.6.7 (DSM for Naming a
1162306a36Sopenharmony_ci * PCI or PCI Express Device Under Operating Systems) defines an instance
1262306a36Sopenharmony_ci * number and string name. This code retrieves them and exports them to sysfs.
1362306a36Sopenharmony_ci * If the system firmware does not provide the ACPI _DSM (Device Specific
1462306a36Sopenharmony_ci * Method), then the SMBIOS type 41 instance number and string is exported to
1562306a36Sopenharmony_ci * sysfs.
1662306a36Sopenharmony_ci *
1762306a36Sopenharmony_ci * SMBIOS defines type 41 for onboard pci devices. This code retrieves
1862306a36Sopenharmony_ci * the instance number and string from the type 41 record and exports
1962306a36Sopenharmony_ci * it to sysfs.
2062306a36Sopenharmony_ci *
2162306a36Sopenharmony_ci * Please see https://linux.dell.com/files/biosdevname/ for more
2262306a36Sopenharmony_ci * information.
2362306a36Sopenharmony_ci */
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#include <linux/dmi.h>
2662306a36Sopenharmony_ci#include <linux/sysfs.h>
2762306a36Sopenharmony_ci#include <linux/pci.h>
2862306a36Sopenharmony_ci#include <linux/pci_ids.h>
2962306a36Sopenharmony_ci#include <linux/module.h>
3062306a36Sopenharmony_ci#include <linux/device.h>
3162306a36Sopenharmony_ci#include <linux/nls.h>
3262306a36Sopenharmony_ci#include <linux/acpi.h>
3362306a36Sopenharmony_ci#include <linux/pci-acpi.h>
3462306a36Sopenharmony_ci#include "pci.h"
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic bool device_has_acpi_name(struct device *dev)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci#ifdef CONFIG_ACPI
3962306a36Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(dev);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	if (!handle)
4262306a36Sopenharmony_ci		return false;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	return acpi_check_dsm(handle, &pci_acpi_dsm_guid, 0x2,
4562306a36Sopenharmony_ci			      1 << DSM_PCI_DEVICE_NAME);
4662306a36Sopenharmony_ci#else
4762306a36Sopenharmony_ci	return false;
4862306a36Sopenharmony_ci#endif
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci#ifdef CONFIG_DMI
5262306a36Sopenharmony_cienum smbios_attr_enum {
5362306a36Sopenharmony_ci	SMBIOS_ATTR_NONE = 0,
5462306a36Sopenharmony_ci	SMBIOS_ATTR_LABEL_SHOW,
5562306a36Sopenharmony_ci	SMBIOS_ATTR_INSTANCE_SHOW,
5662306a36Sopenharmony_ci};
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic size_t find_smbios_instance_string(struct pci_dev *pdev, char *buf,
5962306a36Sopenharmony_ci					  enum smbios_attr_enum attribute)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	const struct dmi_device *dmi;
6262306a36Sopenharmony_ci	struct dmi_dev_onboard *donboard;
6362306a36Sopenharmony_ci	int domain_nr = pci_domain_nr(pdev->bus);
6462306a36Sopenharmony_ci	int bus = pdev->bus->number;
6562306a36Sopenharmony_ci	int devfn = pdev->devfn;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	dmi = NULL;
6862306a36Sopenharmony_ci	while ((dmi = dmi_find_device(DMI_DEV_TYPE_DEV_ONBOARD,
6962306a36Sopenharmony_ci				      NULL, dmi)) != NULL) {
7062306a36Sopenharmony_ci		donboard = dmi->device_data;
7162306a36Sopenharmony_ci		if (donboard && donboard->segment == domain_nr &&
7262306a36Sopenharmony_ci				donboard->bus == bus &&
7362306a36Sopenharmony_ci				donboard->devfn == devfn) {
7462306a36Sopenharmony_ci			if (buf) {
7562306a36Sopenharmony_ci				if (attribute == SMBIOS_ATTR_INSTANCE_SHOW)
7662306a36Sopenharmony_ci					return sysfs_emit(buf, "%d\n",
7762306a36Sopenharmony_ci							  donboard->instance);
7862306a36Sopenharmony_ci				else if (attribute == SMBIOS_ATTR_LABEL_SHOW)
7962306a36Sopenharmony_ci					return sysfs_emit(buf, "%s\n",
8062306a36Sopenharmony_ci							  dmi->name);
8162306a36Sopenharmony_ci			}
8262306a36Sopenharmony_ci			return strlen(dmi->name);
8362306a36Sopenharmony_ci		}
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci	return 0;
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic ssize_t smbios_label_show(struct device *dev,
8962306a36Sopenharmony_ci				 struct device_attribute *attr, char *buf)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct pci_dev *pdev = to_pci_dev(dev);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	return find_smbios_instance_string(pdev, buf,
9462306a36Sopenharmony_ci					   SMBIOS_ATTR_LABEL_SHOW);
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_cistatic struct device_attribute dev_attr_smbios_label = __ATTR(label, 0444,
9762306a36Sopenharmony_ci						    smbios_label_show, NULL);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_cistatic ssize_t index_show(struct device *dev, struct device_attribute *attr,
10062306a36Sopenharmony_ci			  char *buf)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	struct pci_dev *pdev = to_pci_dev(dev);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	return find_smbios_instance_string(pdev, buf,
10562306a36Sopenharmony_ci					   SMBIOS_ATTR_INSTANCE_SHOW);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_cistatic DEVICE_ATTR_RO(index);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic struct attribute *smbios_attrs[] = {
11062306a36Sopenharmony_ci	&dev_attr_smbios_label.attr,
11162306a36Sopenharmony_ci	&dev_attr_index.attr,
11262306a36Sopenharmony_ci	NULL,
11362306a36Sopenharmony_ci};
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic umode_t smbios_attr_is_visible(struct kobject *kobj, struct attribute *a,
11662306a36Sopenharmony_ci				      int n)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	struct device *dev = kobj_to_dev(kobj);
11962306a36Sopenharmony_ci	struct pci_dev *pdev = to_pci_dev(dev);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	if (device_has_acpi_name(dev))
12262306a36Sopenharmony_ci		return 0;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	if (!find_smbios_instance_string(pdev, NULL, SMBIOS_ATTR_NONE))
12562306a36Sopenharmony_ci		return 0;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	return a->mode;
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ciconst struct attribute_group pci_dev_smbios_attr_group = {
13162306a36Sopenharmony_ci	.attrs = smbios_attrs,
13262306a36Sopenharmony_ci	.is_visible = smbios_attr_is_visible,
13362306a36Sopenharmony_ci};
13462306a36Sopenharmony_ci#endif
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci#ifdef CONFIG_ACPI
13762306a36Sopenharmony_cienum acpi_attr_enum {
13862306a36Sopenharmony_ci	ACPI_ATTR_LABEL_SHOW,
13962306a36Sopenharmony_ci	ACPI_ATTR_INDEX_SHOW,
14062306a36Sopenharmony_ci};
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic int dsm_label_utf16s_to_utf8s(union acpi_object *obj, char *buf)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	int len;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	len = utf16s_to_utf8s((const wchar_t *)obj->buffer.pointer,
14762306a36Sopenharmony_ci			      obj->buffer.length,
14862306a36Sopenharmony_ci			      UTF16_LITTLE_ENDIAN,
14962306a36Sopenharmony_ci			      buf, PAGE_SIZE - 1);
15062306a36Sopenharmony_ci	buf[len++] = '\n';
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	return len;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic int dsm_get_label(struct device *dev, char *buf,
15662306a36Sopenharmony_ci			 enum acpi_attr_enum attr)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	acpi_handle handle = ACPI_HANDLE(dev);
15962306a36Sopenharmony_ci	union acpi_object *obj, *tmp;
16062306a36Sopenharmony_ci	int len = 0;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	if (!handle)
16362306a36Sopenharmony_ci		return -1;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	obj = acpi_evaluate_dsm(handle, &pci_acpi_dsm_guid, 0x2,
16662306a36Sopenharmony_ci				DSM_PCI_DEVICE_NAME, NULL);
16762306a36Sopenharmony_ci	if (!obj)
16862306a36Sopenharmony_ci		return -1;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	tmp = obj->package.elements;
17162306a36Sopenharmony_ci	if (obj->type == ACPI_TYPE_PACKAGE && obj->package.count == 2 &&
17262306a36Sopenharmony_ci	    tmp[0].type == ACPI_TYPE_INTEGER &&
17362306a36Sopenharmony_ci	    (tmp[1].type == ACPI_TYPE_STRING ||
17462306a36Sopenharmony_ci	     tmp[1].type == ACPI_TYPE_BUFFER)) {
17562306a36Sopenharmony_ci		/*
17662306a36Sopenharmony_ci		 * The second string element is optional even when
17762306a36Sopenharmony_ci		 * this _DSM is implemented; when not implemented,
17862306a36Sopenharmony_ci		 * this entry must return a null string.
17962306a36Sopenharmony_ci		 */
18062306a36Sopenharmony_ci		if (attr == ACPI_ATTR_INDEX_SHOW) {
18162306a36Sopenharmony_ci			len = sysfs_emit(buf, "%llu\n", tmp->integer.value);
18262306a36Sopenharmony_ci		} else if (attr == ACPI_ATTR_LABEL_SHOW) {
18362306a36Sopenharmony_ci			if (tmp[1].type == ACPI_TYPE_STRING)
18462306a36Sopenharmony_ci				len = sysfs_emit(buf, "%s\n",
18562306a36Sopenharmony_ci						 tmp[1].string.pointer);
18662306a36Sopenharmony_ci			else if (tmp[1].type == ACPI_TYPE_BUFFER)
18762306a36Sopenharmony_ci				len = dsm_label_utf16s_to_utf8s(tmp + 1, buf);
18862306a36Sopenharmony_ci		}
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	ACPI_FREE(obj);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	return len > 0 ? len : -1;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic ssize_t label_show(struct device *dev, struct device_attribute *attr,
19762306a36Sopenharmony_ci			  char *buf)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	return dsm_get_label(dev, buf, ACPI_ATTR_LABEL_SHOW);
20062306a36Sopenharmony_ci}
20162306a36Sopenharmony_cistatic DEVICE_ATTR_RO(label);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_cistatic ssize_t acpi_index_show(struct device *dev,
20462306a36Sopenharmony_ci			      struct device_attribute *attr, char *buf)
20562306a36Sopenharmony_ci{
20662306a36Sopenharmony_ci	return dsm_get_label(dev, buf, ACPI_ATTR_INDEX_SHOW);
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_cistatic DEVICE_ATTR_RO(acpi_index);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic struct attribute *acpi_attrs[] = {
21162306a36Sopenharmony_ci	&dev_attr_label.attr,
21262306a36Sopenharmony_ci	&dev_attr_acpi_index.attr,
21362306a36Sopenharmony_ci	NULL,
21462306a36Sopenharmony_ci};
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic umode_t acpi_attr_is_visible(struct kobject *kobj, struct attribute *a,
21762306a36Sopenharmony_ci				    int n)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	struct device *dev = kobj_to_dev(kobj);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	if (!device_has_acpi_name(dev))
22262306a36Sopenharmony_ci		return 0;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	return a->mode;
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ciconst struct attribute_group pci_dev_acpi_attr_group = {
22862306a36Sopenharmony_ci	.attrs = acpi_attrs,
22962306a36Sopenharmony_ci	.is_visible = acpi_attr_is_visible,
23062306a36Sopenharmony_ci};
23162306a36Sopenharmony_ci#endif
232