[PATCH] Enable reading FRU chip via I2C v3
Andrey Grodzovsky
Andrey.Grodzovsky at amd.com
Thu Mar 19 14:45:02 UTC 2020
Looks good to me.
Reviewed-by: Andrey Grodzovsky <andrey.grodzovsky at amd.com>
Andrey
On 3/19/20 10:16 AM, Kent Russell wrote:
> 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 at amd.com>
> ---
> Documentation/gpu/amdgpu.rst | 24 +++
> drivers/gpu/drm/amd/amdgpu/Makefile | 2 +-
> drivers/gpu/drm/amd/amdgpu/amdgpu.h | 5 +
> drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 90 +++++++++++
> .../gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c | 143 ++++++++++++++++++
> .../gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.h | 29 ++++
> 6 files changed, 292 insertions(+), 1 deletion(-)
> create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c
> create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.h
>
> diff --git a/Documentation/gpu/amdgpu.rst b/Documentation/gpu/amdgpu.rst
> index 0efede580039..d9ea09ec8e24 100644
> --- a/Documentation/gpu/amdgpu.rst
> +++ b/Documentation/gpu/amdgpu.rst
> @@ -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
> diff --git a/drivers/gpu/drm/amd/amdgpu/Makefile b/drivers/gpu/drm/amd/amdgpu/Makefile
> index c2bbcdd9c875..210d57a4afc8 100644
> --- a/drivers/gpu/drm/amd/amdgpu/Makefile
> +++ b/drivers/gpu/drm/amd/amdgpu/Makefile
> @@ -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
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> index 87c2523076af..7dd74253e7b6 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> @@ -979,6 +979,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)
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
> index 729565f79cfe..80a654326190 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
> @@ -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>
> @@ -137,6 +138,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
> *
> @@ -1977,6 +2044,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);
> @@ -3188,6 +3257,24 @@ int amdgpu_device_init(struct amdgpu_device *adev,
> 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)
> @@ -3270,6 +3357,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
> index 000000000000..990dee6e22d5
> --- /dev/null
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c
> @@ -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
> index 000000000000..968115c97e33
> --- /dev/null
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.h
> @@ -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__
More information about the amd-gfx
mailing list