diff options
Diffstat (limited to 'drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c')
| -rw-r--r-- | drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c | 453 |
1 files changed, 329 insertions, 124 deletions
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c index 39b6c6bfab45..b0082aa7f3c6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fru_eeprom.c @@ -27,12 +27,13 @@ #include "smu_v11_0_i2c.h" #include "atom.h" #include "amdgpu_fru_eeprom.h" +#include "amdgpu_eeprom.h" -#define I2C_PRODUCT_INFO_ADDR 0xAC -#define I2C_PRODUCT_INFO_ADDR_SIZE 0x2 -#define I2C_PRODUCT_INFO_OFFSET 0xC0 +#define FRU_EEPROM_MADDR_6 0x60000 +#define FRU_EEPROM_MADDR_8 0x80000 +#define FRU_EEPROM_MADDR_INV 0xFFFFF -static bool is_fru_eeprom_supported(struct amdgpu_device *adev) +static bool is_fru_eeprom_supported(struct amdgpu_device *adev, u32 *fru_addr) { /* Only server cards have the FRU EEPROM * TODO: See if we can figure this out dynamically instead of @@ -40,164 +41,368 @@ static bool is_fru_eeprom_supported(struct amdgpu_device *adev) */ struct atom_context *atom_ctx = adev->mode_info.atom_context; - /* VBIOS is of the format ###-DXXXYY-##. For SKU identification, + /* The i2c access is blocked on VF + * TODO: Need other way to get the info + * Also, FRU not valid for APU devices. + */ + if (amdgpu_sriov_vf(adev) || (adev->flags & AMD_IS_APU)) + return false; + + /* The default I2C EEPROM address of the FRU. + */ + if (fru_addr) + *fru_addr = FRU_EEPROM_MADDR_8; + + /* VBIOS is of the format ###-DXXXYYYY-##. For SKU identification, * we can use just the "DXXX" portion. If there were more models, we * could convert the 3 characters to a hex integer and use a switch * for ease/speed/readability. For now, 2 string comparisons are * reasonable and not too expensive */ - switch (adev->asic_type) { - case CHIP_VEGA20: - /* D161 and D163 are the VG20 server SKUs */ - if (strnstr(atom_ctx->vbios_version, "D161", - sizeof(atom_ctx->vbios_version)) || - strnstr(atom_ctx->vbios_version, "D163", - sizeof(atom_ctx->vbios_version))) + switch (amdgpu_ip_version(adev, MP1_HWIP, 0)) { + case IP_VERSION(11, 0, 2): + switch (adev->asic_type) { + case CHIP_VEGA20: + /* D161 and D163 are the VG20 server SKUs */ + if (atom_ctx && (strnstr(atom_ctx->vbios_pn, "D161", + sizeof(atom_ctx->vbios_pn)) || + strnstr(atom_ctx->vbios_pn, "D163", + sizeof(atom_ctx->vbios_pn)))) { + if (fru_addr) + *fru_addr = FRU_EEPROM_MADDR_6; + return true; + } else { + return false; + } + case CHIP_ARCTURUS: + default: + return false; + } + case IP_VERSION(11, 0, 7): + if (atom_ctx && strnstr(atom_ctx->vbios_pn, "D603", + sizeof(atom_ctx->vbios_pn))) { + if (strnstr(atom_ctx->vbios_pn, "D603GLXE", + sizeof(atom_ctx->vbios_pn))) { + return false; + } + + if (fru_addr) + *fru_addr = FRU_EEPROM_MADDR_6; return true; - else + + } else { return false; + } + case IP_VERSION(13, 0, 2): + /* All Aldebaran SKUs have an FRU */ + if (atom_ctx && !strnstr(atom_ctx->vbios_pn, "D673", + sizeof(atom_ctx->vbios_pn))) + if (fru_addr) + *fru_addr = FRU_EEPROM_MADDR_6; + return true; + case IP_VERSION(13, 0, 6): + case IP_VERSION(13, 0, 14): + if (fru_addr) + *fru_addr = FRU_EEPROM_MADDR_8; + return true; + case IP_VERSION(13, 0, 12): + if (fru_addr) + *fru_addr = FRU_EEPROM_MADDR_INV; + return true; default: return false; } } -static int amdgpu_fru_read_eeprom(struct amdgpu_device *adev, uint32_t addrptr, - unsigned char *buff) +int amdgpu_fru_get_product_info(struct amdgpu_device *adev) { - 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; - } + struct amdgpu_fru_info *fru_info; + unsigned char buf[8], *pia; + u32 addr, fru_addr; + int size, len; + u8 csum; - /* 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; + if (!is_fru_eeprom_supported(adev, &fru_addr)) + return 0; - msg.len = I2C_PRODUCT_INFO_ADDR_SIZE + size; - ret = i2c_transfer(&adev->pm.smu_i2c, &msg, 1); + /* FRU data avaialble, but no direct EEPROM access */ + if (fru_addr == FRU_EEPROM_MADDR_INV) + return 0; - if (ret < 1) { - DRM_WARN("FRU: Failed to get data field"); - return ret; + if (!adev->fru_info) { + adev->fru_info = kzalloc(sizeof(*adev->fru_info), GFP_KERNEL); + if (!adev->fru_info) + return -ENOMEM; } - return size; -} - -int amdgpu_fru_get_product_info(struct amdgpu_device *adev) -{ - unsigned char buff[34]; - int addrptr, size; - int len; - - if (!is_fru_eeprom_supported(adev)) - return 0; + fru_info = adev->fru_info; + /* For Arcturus-and-later, default value of serial_number is unique_id + * so convert it to a 16-digit HEX string for convenience and + * backwards-compatibility. + */ + sprintf(fru_info->serial, "%llx", adev->unique_id); /* 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"); + if (!adev->pm.fru_eeprom_i2c_bus || !adev->pm.fru_eeprom_i2c_bus->algo) { + dev_warn(adev->dev, + "Cannot access FRU, EEPROM accessor not initialized"); return -ENODEV; } - /* 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 -EINVAL; + /* Read the IPMI Common header */ + len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, fru_addr, buf, + sizeof(buf)); + if (len != 8) { + dev_err(adev->dev, "Couldn't read the IPMI Common Header: %d", + len); + return len < 0 ? len : -EIO; } - /* 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 -EINVAL; + if (buf[0] != 1) { + dev_err(adev->dev, "Bad IPMI Common Header version: 0x%02x", + buf[0]); + return -EIO; } - len = size; - /* Product name should only be 32 characters. Any more, - * and something could be wrong. Cap it at 32 to be safe - */ - if (len >= sizeof(adev->product_name)) { - DRM_WARN("FRU Product Number is larger than 32 characters. This is likely a mistake"); - len = sizeof(adev->product_name) - 1; + for (csum = 0; len > 0; len--) + csum += buf[len - 1]; + if (csum) { + dev_err(adev->dev, "Bad IPMI Common Header checksum: 0x%02x", + csum); + return -EIO; } - /* Start at 2 due to buff using fields 0 and 1 for the address */ - memcpy(adev->product_name, &buff[2], len); - adev->product_name[len] = '\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 -EINVAL; + + /* Get the offset to the Product Info Area (PIA). */ + addr = buf[4] * 8; + if (!addr) + return 0; + + /* Get the absolute address to the PIA. */ + addr += fru_addr; + + /* Read the header of the PIA. */ + len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, addr, buf, 3); + if (len != 3) { + dev_err(adev->dev, + "Couldn't read the Product Info Area header: %d", len); + return len < 0 ? len : -EIO; } - len = size; - /* Product number should only be 16 characters. Any more, - * and something could be wrong. Cap it at 16 to be safe - */ - if (len >= sizeof(adev->product_number)) { - DRM_WARN("FRU Product Number is larger than 16 characters. This is likely a mistake"); - len = sizeof(adev->product_number) - 1; + if (buf[0] != 1) { + dev_err(adev->dev, "Bad IPMI Product Info Area version: 0x%02x", + buf[0]); + return -EIO; } - memcpy(adev->product_number, &buff[2], len); - adev->product_number[len] = '\0'; - addrptr += size + 1; - size = amdgpu_fru_read_eeprom(adev, addrptr, buff); + size = buf[1] * 8; + pia = kzalloc(size, GFP_KERNEL); + if (!pia) + return -ENOMEM; - if (size < 1) { - DRM_ERROR("Failed to read FRU product version, ret:%d", size); - return -EINVAL; + /* Read the whole PIA. */ + len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, addr, pia, size); + if (len != size) { + kfree(pia); + dev_err(adev->dev, "Couldn't read the Product Info Area: %d", + len); + return len < 0 ? len : -EIO; } - 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 -EINVAL; + for (csum = 0; size > 0; size--) + csum += pia[size - 1]; + if (csum) { + dev_err(adev->dev, "Bad Product Info Area checksum: 0x%02x", + csum); + kfree(pia); + return -EIO; } - len = size; - /* Serial number should only be 16 characters. Any more, - * and something could be wrong. Cap it at 16 to be safe + /* Now extract useful information from the PIA. + * + * Read Manufacturer Name field whose length is [3]. */ - if (len >= sizeof(adev->serial)) { - DRM_WARN("FRU Serial Number is larger than 16 characters. This is likely a mistake"); - len = sizeof(adev->serial) - 1; - } - memcpy(adev->serial, &buff[2], len); - adev->serial[len] = '\0'; + addr = 3; + if (addr + 1 >= len) + goto Out; + memcpy(fru_info->manufacturer_name, pia + addr + 1, + min_t(size_t, sizeof(fru_info->manufacturer_name), + pia[addr] & 0x3F)); + fru_info->manufacturer_name[sizeof(fru_info->manufacturer_name) - 1] = + '\0'; + /* Read Product Name field. */ + addr += 1 + (pia[addr] & 0x3F); + if (addr + 1 >= len) + goto Out; + memcpy(fru_info->product_name, pia + addr + 1, + min_t(size_t, sizeof(fru_info->product_name), pia[addr] & 0x3F)); + fru_info->product_name[sizeof(fru_info->product_name) - 1] = '\0'; + + /* Go to the Product Part/Model Number field. */ + addr += 1 + (pia[addr] & 0x3F); + if (addr + 1 >= len) + goto Out; + memcpy(fru_info->product_number, pia + addr + 1, + min_t(size_t, sizeof(fru_info->product_number), + pia[addr] & 0x3F)); + fru_info->product_number[sizeof(fru_info->product_number) - 1] = '\0'; + + /* Go to the Product Version field. */ + addr += 1 + (pia[addr] & 0x3F); + + /* Go to the Product Serial Number field. */ + addr += 1 + (pia[addr] & 0x3F); + if (addr + 1 >= len) + goto Out; + memcpy(fru_info->serial, pia + addr + 1, + min_t(size_t, sizeof(fru_info->serial), pia[addr] & 0x3F)); + fru_info->serial[sizeof(fru_info->serial) - 1] = '\0'; + + /* Asset Tag field */ + addr += 1 + (pia[addr] & 0x3F); + + /* FRU File Id field. This could be 'null'. */ + addr += 1 + (pia[addr] & 0x3F); + if ((addr + 1 >= len) || !(pia[addr] & 0x3F)) + goto Out; + memcpy(fru_info->fru_id, pia + addr + 1, + min_t(size_t, sizeof(fru_info->fru_id), pia[addr] & 0x3F)); + fru_info->fru_id[sizeof(fru_info->fru_id) - 1] = '\0'; + +Out: + kfree(pia); return 0; } + +/** + * DOC: product_name + * + * The amdgpu driver provides a sysfs API for reporting the product name + * for the device + * The file product_name 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_fru_product_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct amdgpu_device *adev = drm_to_adev(ddev); + + return sysfs_emit(buf, "%s\n", adev->fru_info->product_name); +} + +static DEVICE_ATTR(product_name, 0444, amdgpu_fru_product_name_show, NULL); + +/** + * DOC: product_number + * + * The amdgpu driver provides a sysfs API for reporting the part number + * for the device + * The file product_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_fru_product_number_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct amdgpu_device *adev = drm_to_adev(ddev); + + return sysfs_emit(buf, "%s\n", adev->fru_info->product_number); +} + +static DEVICE_ATTR(product_number, 0444, amdgpu_fru_product_number_show, 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_fru_serial_number_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct amdgpu_device *adev = drm_to_adev(ddev); + + return sysfs_emit(buf, "%s\n", adev->fru_info->serial); +} + +static DEVICE_ATTR(serial_number, 0444, amdgpu_fru_serial_number_show, NULL); + +/** + * DOC: fru_id + * + * The amdgpu driver provides a sysfs API for reporting FRU File Id + * for the device. + * The file fru_id is used for this and returns the File Id value + * as returned from the FRU. + * NOTE: This is only available for certain server cards + */ + +static ssize_t amdgpu_fru_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct amdgpu_device *adev = drm_to_adev(ddev); + + return sysfs_emit(buf, "%s\n", adev->fru_info->fru_id); +} + +static DEVICE_ATTR(fru_id, 0444, amdgpu_fru_id_show, NULL); + +/** + * DOC: manufacturer + * + * The amdgpu driver provides a sysfs API for reporting manufacturer name from + * FRU information. + * The file manufacturer returns the value as returned from the FRU. + * NOTE: This is only available for certain server cards + */ + +static ssize_t amdgpu_fru_manufacturer_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct amdgpu_device *adev = drm_to_adev(ddev); + + return sysfs_emit(buf, "%s\n", adev->fru_info->manufacturer_name); +} + +static DEVICE_ATTR(manufacturer, 0444, amdgpu_fru_manufacturer_name_show, NULL); + +static const struct attribute *amdgpu_fru_attributes[] = { + &dev_attr_product_name.attr, + &dev_attr_product_number.attr, + &dev_attr_serial_number.attr, + &dev_attr_fru_id.attr, + &dev_attr_manufacturer.attr, + NULL +}; + +int amdgpu_fru_sysfs_init(struct amdgpu_device *adev) +{ + if (!is_fru_eeprom_supported(adev, NULL) || !adev->fru_info) + return 0; + + return sysfs_create_files(&adev->dev->kobj, amdgpu_fru_attributes); +} + +void amdgpu_fru_sysfs_fini(struct amdgpu_device *adev) +{ + if (!adev->fru_info) + return; + + sysfs_remove_files(&adev->dev->kobj, amdgpu_fru_attributes); +} |
