Skip to content

Commit

Permalink
Enable reading FRU chip via I2C
Browse files Browse the repository at this point in the history
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.

Change-Id: Id506dc3e29d58f2d23bc13df4fec5fa1da778e31
Signed-off-by: Kent Russell <[email protected]>
  • Loading branch information
kentrussell committed Mar 19, 2020
1 parent 9825ef3 commit 05883f1
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 0 deletions.
4 changes: 4 additions & 0 deletions drivers/gpu/drm/amd/amdgpu/amdgpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,10 @@ struct amdgpu_device {

bool pm_sysfs_en;
bool ucode_sysfs_en;

char product_name[32];
char product_number[16];
char serial[16];
};

static inline struct amdgpu_device *amdgpu_ttm_adev(struct ttm_bo_device *bdev)
Expand Down
87 changes: 87 additions & 0 deletions drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,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
*
Expand Down Expand Up @@ -3189,6 +3255,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)
Expand Down Expand Up @@ -3268,6 +3352,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);
amdgpu_debugfs_preempt_cleanup(adev);
Expand Down
153 changes: 153 additions & 0 deletions drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

#define EEPROM_I2C_TARGET_ADDR_ARCTURUS 0xA8
#define EEPROM_I2C_TARGET_ADDR_VEGA20 0xA0
#define EEPROM_I2C_PI_ADDR 0xAC
#define I2C_PRODUCT_INFO_OFFSET 0xC0

/*
* The 2 macros bellow represent the actual size in bytes that
Expand Down Expand Up @@ -79,6 +81,155 @@ static void __decode_table_header_from_buff(struct amdgpu_ras_eeprom_table_heade
hdr->checksum = le32_to_cpu(pp[4]);
}

static int amdgpu_ras_eeprom_get_product_info(struct amdgpu_ras_eeprom_control *control)
{
int ret = 0, i, addrptr = 0, size = 0;
struct amdgpu_device *adev = to_amdgpu_device(control);
unsigned char buff[34], sizebuff[3];
/* size_msg will be the message to get the size of a field,
* since it's always 1 for field length
*/
struct i2c_msg size_msg = {
.addr = EEPROM_I2C_PI_ADDR,
.flags = I2C_M_RD,
.len = EEPROM_ADDRESS_SIZE + 1,
.buf = sizebuff,
};

/* Add the size obtained above to .len as required */
struct i2c_msg msg = {
.addr = EEPROM_I2C_PI_ADDR,
.flags = I2C_M_RD,
.len = EEPROM_ADDRESS_SIZE,
.buf = buff,
};

/* Not supported before VG20 */
if (adev->asic_type < CHIP_VEGA20)
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, while we create 2 i2c messages, one to obtain
* size (since that's always 1-byte, and one to get the data.
*
* The size returned by the i2c requires subtraction of 0xC0 since the
* size apparently always reports as 0xC0+actual size.
*
* NOTE: We don't lock the mutex since this data is ummutable
*/

/* The first fields are all of size 1-byte, from 0-7 are offsets that
* aren't entirely useful. Bytes 8-e are all 1-byte and refer to the
* size of the entire struct, and the language field, so just start
* from 0xf, which is the size of the manufacturer
*/
addrptr = 0xf;

/* The i2c message reads the address from the buffer passed in */
sizebuff[0] = 0;
sizebuff[1] = 0xf;

ret = i2c_transfer(&control->eeprom_accessor, &size_msg, 1);

if (ret < 1) {
DRM_ERROR("Failed to read EEPROM manufacturer size, ret:%d", ret);
return ret;
}

size = sizebuff[2] - I2C_PRODUCT_INFO_OFFSET;
msg.len = EEPROM_ADDRESS_SIZE + size;
/* Add 1 since address field was 1 byte */
addrptr += 1;
/* Now fill in the actual buffer with the desired address */
buff[0] = 0;
buff[1] = addrptr;
ret = i2c_transfer(&control->eeprom_accessor, &msg, 1);

if (ret < 1) {
DRM_ERROR("Failed to read EEPROM product name, ret:%d", ret);
return ret;
}
memcpy(adev->product_name, &buff[2], size);
adev->product_name[size] = '\0';

/* Increase the addrptr by the appropriate size */
addrptr += size;

/* Get the next field, product name, starting with size */
sizebuff[0] = 0;
sizebuff[1] = addrptr;

ret = i2c_transfer(&control->eeprom_accessor, &size_msg, 1);

if (ret < 1) {
DRM_ERROR("Failed to read EEPROM product number size, ret:%d", ret);
return ret;
}

size = sizebuff[2] - I2C_PRODUCT_INFO_OFFSET;
msg.len = EEPROM_ADDRESS_SIZE + size;
/* Add 1 since address field was 1 byte */
addrptr += 1;
buff[0] = 0;
buff[1] = addrptr;

ret = i2c_transfer(&control->eeprom_accessor, &msg, 1);

if (ret < 1) {
DRM_ERROR("Failed to read EEPROM product number, ret:%d", ret);
return ret;
}
memcpy(adev->product_number, &buff[2], size);
adev->product_number[size] = '\0';

/* Increase the addrptr by the appropriate size */
addrptr += size;

/* Skip over the Product Version, it's not very useful */
sizebuff[0] = 0;
sizebuff[1] = addrptr;

ret = i2c_transfer(&control->eeprom_accessor, &size_msg, 1);

if (ret < 1) {
DRM_ERROR("Failed to read EEPROM product version size, ret:%d", ret);
return ret;
}
size = sizebuff[2] - I2C_PRODUCT_INFO_OFFSET;
/* Add 1 since address field was 1 byte, plus the size of the version */
addrptr += size + 1;

/* Get the last field, serial number */
sizebuff[0] = 0;
sizebuff[1] = addrptr;

ret = i2c_transfer(&control->eeprom_accessor, &size_msg, 1);

if (ret < 1) {
DRM_ERROR("Failed to read EEPROM serial number size, ret:%d", ret);
return ret;
}
size = sizebuff[2] - I2C_PRODUCT_INFO_OFFSET;
msg.len = EEPROM_ADDRESS_SIZE + size;
/* Add 1 since address field was 1 byte */
addrptr += 1;
buff[0] = 0;
buff[1] = addrptr;
ret = i2c_transfer(&control->eeprom_accessor, &msg, 1);

if (ret < 1) {
DRM_ERROR("Failed to read EEPROM serial info, ret:%d", ret);
return ret;
}
memcpy(adev->serial, &buff[2], size);
adev->serial[size] = '\0';

return 0;
}

static int __update_table_header(struct amdgpu_ras_eeprom_control *control,
unsigned char *buff)
{
Expand Down Expand Up @@ -260,6 +411,8 @@ int amdgpu_ras_eeprom_init(struct amdgpu_ras_eeprom_control *control)
ret = amdgpu_ras_eeprom_reset_table(control);
}

amdgpu_ras_eeprom_get_product_info(control);

return ret == 1 ? 0 : -EIO;
}

Expand Down

0 comments on commit 05883f1

Please sign in to comment.