drm/amdgpu: Enable reading FRU chip via I2C v3
authorKent Russell <kent.russell@amd.com>
Fri, 13 Mar 2020 13:21:55 +0000 (09:21 -0400)
committerAlex Deucher <alexander.deucher@amd.com>
Wed, 1 Apr 2020 18:44:41 +0000 (14:44 -0400)
Allow for reading of information like manufacturer, product number
and serial number from the FRU chip. Report the serial number as
the new sysfs file serial_number. Note that this only works on
server cards, as consumer cards do not feature the FRU chip, which
contains this information.

v2: Add documentation to amdgpu.rst, add helper functions,
    rename functions for consistency, fix bad starting offset
v3: Remove testing definitions

Signed-off-by: Kent Russell <kent.russell@amd.com>
Reviewed-by: Andrey Grodzovsky <andrey.grodzovsky@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
Documentation/gpu/amdgpu.rst
drivers/gpu/drm/amd/amdgpu/Makefile
drivers/gpu/drm/amd/amdgpu/amdgpu.h
drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c [new file with mode: 0644]
drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.h [new file with mode: 0644]

index 0efede5..d9ea09e 100644 (file)
@@ -202,3 +202,27 @@ busy_percent
 
 .. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c
    :doc: busy_percent
+
+GPU Product Information
+=======================
+
+Information about the GPU can be obtained on certain cards
+via sysfs
+
+product_name
+------------
+
+.. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+   :doc: product_name
+
+product_number
+--------------
+
+.. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+   :doc: product_name
+
+serial_number
+-------------
+
+.. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+   :doc: serial_number
index c2bbcdd..210d57a 100644 (file)
@@ -55,7 +55,7 @@ amdgpu-y += amdgpu_device.o amdgpu_kms.o \
        amdgpu_vf_error.o amdgpu_sched.o amdgpu_debugfs.o amdgpu_ids.o \
        amdgpu_gmc.o amdgpu_mmhub.o amdgpu_xgmi.o amdgpu_csa.o amdgpu_ras.o amdgpu_vm_cpu.o \
        amdgpu_vm_sdma.o amdgpu_discovery.o amdgpu_ras_eeprom.o amdgpu_nbio.o \
-       amdgpu_umc.o smu_v11_0_i2c.o
+       amdgpu_umc.o smu_v11_0_i2c.o amdgpu_fru_eeprom.o
 
 amdgpu-$(CONFIG_PERF_EVENTS) += amdgpu_pmu.o
 
index 2992a49..b0597a8 100644 (file)
@@ -974,6 +974,11 @@ struct amdgpu_device {
 
        bool                            pm_sysfs_en;
        bool                            ucode_sysfs_en;
+
+       /* Chip product information */
+       char                            product_number[16];
+       char                            product_name[32];
+       char                            serial[16];
 };
 
 static inline struct amdgpu_device *amdgpu_ttm_adev(struct ttm_bo_device *bdev)
index faa3e71..f422ef5 100644 (file)
@@ -64,6 +64,7 @@
 #include "amdgpu_xgmi.h"
 #include "amdgpu_ras.h"
 #include "amdgpu_pmu.h"
+#include "amdgpu_fru_eeprom.h"
 
 #include <linux/suspend.h>
 #include <drm/task_barrier.h>
@@ -138,6 +139,72 @@ static DEVICE_ATTR(pcie_replay_count, S_IRUGO,
 static void amdgpu_device_get_pcie_info(struct amdgpu_device *adev);
 
 /**
+ * DOC: product_name
+ *
+ * The amdgpu driver provides a sysfs API for reporting the product name
+ * for the device
+ * The file serial_number is used for this and returns the product name
+ * as returned from the FRU.
+ * NOTE: This is only available for certain server cards
+ */
+
+static ssize_t amdgpu_device_get_product_name(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct drm_device *ddev = dev_get_drvdata(dev);
+       struct amdgpu_device *adev = ddev->dev_private;
+
+       return snprintf(buf, PAGE_SIZE, "%s\n", adev->product_name);
+}
+
+static DEVICE_ATTR(product_name, S_IRUGO,
+               amdgpu_device_get_product_name, NULL);
+
+/**
+ * DOC: product_number
+ *
+ * The amdgpu driver provides a sysfs API for reporting the part number
+ * for the device
+ * The file serial_number is used for this and returns the part number
+ * as returned from the FRU.
+ * NOTE: This is only available for certain server cards
+ */
+
+static ssize_t amdgpu_device_get_product_number(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct drm_device *ddev = dev_get_drvdata(dev);
+       struct amdgpu_device *adev = ddev->dev_private;
+
+       return snprintf(buf, PAGE_SIZE, "%s\n", adev->product_number);
+}
+
+static DEVICE_ATTR(product_number, S_IRUGO,
+               amdgpu_device_get_product_number, NULL);
+
+/**
+ * DOC: serial_number
+ *
+ * The amdgpu driver provides a sysfs API for reporting the serial number
+ * for the device
+ * The file serial_number is used for this and returns the serial number
+ * as returned from the FRU.
+ * NOTE: This is only available for certain server cards
+ */
+
+static ssize_t amdgpu_device_get_serial_number(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct drm_device *ddev = dev_get_drvdata(dev);
+       struct amdgpu_device *adev = ddev->dev_private;
+
+       return snprintf(buf, PAGE_SIZE, "%s\n", adev->serial);
+}
+
+static DEVICE_ATTR(serial_number, S_IRUGO,
+               amdgpu_device_get_serial_number, NULL);
+
+/**
  * amdgpu_device_supports_boco - Is the device a dGPU with HG/PX power control
  *
  * @dev: drm_device pointer
@@ -1975,6 +2042,8 @@ static int amdgpu_device_ip_init(struct amdgpu_device *adev)
                amdgpu_xgmi_add_device(adev);
        amdgpu_amdkfd_device_init(adev);
 
+       amdgpu_fru_get_product_info(adev);
+
 init_failed:
        if (amdgpu_sriov_vf(adev))
                amdgpu_virt_release_full_gpu(adev, true);
@@ -3189,6 +3258,24 @@ fence_driver_init:
                return r;
        }
 
+       r = device_create_file(adev->dev, &dev_attr_product_name);
+       if (r) {
+               dev_err(adev->dev, "Could not create product_name");
+               return r;
+       }
+
+       r = device_create_file(adev->dev, &dev_attr_product_number);
+       if (r) {
+               dev_err(adev->dev, "Could not create product_number");
+               return r;
+       }
+
+       r = device_create_file(adev->dev, &dev_attr_serial_number);
+       if (r) {
+               dev_err(adev->dev, "Could not create serial_number");
+               return r;
+       }
+
        if (IS_ENABLED(CONFIG_PERF_EVENTS))
                r = amdgpu_pmu_init(adev);
        if (r)
@@ -3271,6 +3358,9 @@ void amdgpu_device_fini(struct amdgpu_device *adev)
        device_remove_file(adev->dev, &dev_attr_pcie_replay_count);
        if (adev->ucode_sysfs_en)
                amdgpu_ucode_sysfs_fini(adev);
+       device_remove_file(adev->dev, &dev_attr_product_name);
+       device_remove_file(adev->dev, &dev_attr_product_number);
+       device_remove_file(adev->dev, &dev_attr_serial_number);
        if (IS_ENABLED(CONFIG_PERF_EVENTS))
                amdgpu_pmu_fini(adev);
        if (amdgpu_discovery && adev->asic_type >= CHIP_NAVI10)
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c
new file mode 100644 (file)
index 0000000..990dee6
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2019 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+#include "amdgpu.h"
+#include "amdgpu_i2c.h"
+#include "smu_v11_0_i2c.h"
+#include "atom.h"
+
+#define I2C_PRODUCT_INFO_ADDR          0xAC
+#define I2C_PRODUCT_INFO_ADDR_SIZE     0x2
+#define I2C_PRODUCT_INFO_OFFSET                0xC0
+
+int amdgpu_fru_read_eeprom(struct amdgpu_device *adev, uint32_t addrptr,
+                          unsigned char *buff)
+{
+       int ret, size;
+       struct i2c_msg msg = {
+                       .addr   = I2C_PRODUCT_INFO_ADDR,
+                       .flags  = I2C_M_RD,
+                       .buf    = buff,
+       };
+       buff[0] = 0;
+       buff[1] = addrptr;
+       msg.len = I2C_PRODUCT_INFO_ADDR_SIZE + 1;
+       ret = i2c_transfer(&adev->pm.smu_i2c, &msg, 1);
+
+       if (ret < 1) {
+               DRM_WARN("FRU: Failed to get size field");
+               return ret;
+       }
+
+       /* The size returned by the i2c requires subtraction of 0xC0 since the
+        * size apparently always reports as 0xC0+actual size.
+        */
+       size = buff[2] - I2C_PRODUCT_INFO_OFFSET;
+       /* Add 1 since address field was 1 byte */
+       buff[1] = addrptr + 1;
+
+       msg.len = I2C_PRODUCT_INFO_ADDR_SIZE + size;
+       ret = i2c_transfer(&adev->pm.smu_i2c, &msg, 1);
+
+       if (ret < 1) {
+               DRM_WARN("FRU: Failed to get data field");
+               return ret;
+       }
+
+       return size;
+}
+
+int amdgpu_fru_get_product_info(struct amdgpu_device *adev)
+{
+       unsigned char buff[32];
+       int addrptr = 0, size = 0;
+
+       /* If algo exists, it means that the i2c_adapter's initialized */
+       if (!adev->pm.smu_i2c.algo) {
+               DRM_WARN("Cannot access FRU, EEPROM accessor not initialized");
+               return 0;
+       }
+
+       /* There's a lot of repetition here. This is due to the FRU having
+        * variable-length fields. To get the information, we have to find the
+        * size of each field, and then keep reading along and reading along
+        * until we get all of the data that we want. We use addrptr to track
+        * the address as we go
+        */
+
+       /* The first fields are all of size 1-byte, from 0-7 are offsets that
+        * contain information that isn't useful to us.
+        * Bytes 8-a are all 1-byte and refer to the size of the entire struct,
+        * and the language field, so just start from 0xb, manufacturer size
+        */
+       addrptr = 0xb;
+       size = amdgpu_fru_read_eeprom(adev, addrptr, buff);
+       if (size < 1) {
+               DRM_ERROR("Failed to read FRU Manufacturer, ret:%d", size);
+               return size;
+       }
+
+       /* Increment the addrptr by the size of the field, and 1 due to the
+        * size field being 1 byte. This pattern continues below.
+        */
+       addrptr += size + 1;
+       size = amdgpu_fru_read_eeprom(adev, addrptr, buff);
+       if (size < 1) {
+               DRM_ERROR("Failed to read FRU product name, ret:%d", size);
+               return size;
+       }
+
+       /* Start at 2 due to buff using fields 0 and 1 for the address */
+       memcpy(adev->product_name, &buff[2], size);
+       adev->product_name[size] = '\0';
+
+       addrptr += size + 1;
+       size = amdgpu_fru_read_eeprom(adev, addrptr, buff);
+       if (size < 1) {
+               DRM_ERROR("Failed to read FRU product number, ret:%d", size);
+               return size;
+       }
+
+       memcpy(adev->product_number, &buff[2], size);
+       adev->product_number[size] = '\0';
+
+       addrptr += size + 1;
+       size = amdgpu_fru_read_eeprom(adev, addrptr, buff);
+
+       if (size < 1) {
+               DRM_ERROR("Failed to read FRU product version, ret:%d", size);
+               return size;
+       }
+
+       addrptr += size + 1;
+       size = amdgpu_fru_read_eeprom(adev, addrptr, buff);
+
+       if (size < 1) {
+               DRM_ERROR("Failed to read FRU serial number, ret:%d", size);
+               return size;
+       }
+
+       memcpy(adev->serial, &buff[2], size);
+       adev->serial[size] = '\0';
+
+       return 0;
+}
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.h
new file mode 100644 (file)
index 0000000..968115c
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#ifndef __AMDGPU_PRODINFO_H__
+#define __AMDGPU_PRODINFO_H__
+
+int amdgpu_fru_get_product_info(struct amdgpu_device *adev);
+
+#endif  // __AMDGPU_PRODINFO_H__