[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