| 1 | |
| 2 | * Copyright 2019 Advanced Micro Devices, Inc.
|
| 3 | *
|
| 4 | * Permission is hereby granted, free of charge, to any person obtaining a
|
| 5 | * copy of this software and associated documentation files (the "Software"),
|
| 6 | * to deal in the Software without restriction, including without limitation
|
| 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
| 8 | * and/or sell copies of the Software, and to permit persons to whom the
|
| 9 | * Software is furnished to do so, subject to the following conditions:
|
| 10 | *
|
| 11 | * The above copyright notice and this permission notice shall be included in
|
| 12 | * all copies or substantial portions of the Software.
|
| 13 | *
|
| 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
| 17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
| 18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
| 19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
| 20 | * OTHER DEALINGS IN THE SOFTWARE.
|
| 21 | *
|
| 22 |
|
| 23 | #include <linux/pci.h>
|
| 24 |
|
| 25 | #include "amdgpu.h"
|
| 26 | #include "amdgpu_i2c.h"
|
| 27 | #include "smu_v11_0_i2c.h"
|
| 28 | #include "atom.h"
|
| 29 | #include "amdgpu_fru_eeprom.h"
|
| 30 | #include "amdgpu_eeprom.h"
|
| 31 |
|
| 32 | #define FRU_EEPROM_MADDR_6 0x60000
|
| 33 | #define FRU_EEPROM_MADDR_8 0x80000
|
| 34 | #define FRU_EEPROM_MADDR_INV 0xFFFFF
|
| 35 |
|
| 36 | static bool is_fru_eeprom_supported(struct amdgpu_device *adev, u32 *fru_addr)
|
| 37 | {
|
| 38 | |
| 39 | * TODO: See if we can figure this out dynamically instead of
|
| 40 | * having to parse VBIOS versions.
|
| 41 |
|
| 42 | struct atom_context *atom_ctx = adev->mode_info.atom_context;
|
| 43 |
|
| 44 | |
| 45 | * TODO: Need other way to get the info
|
| 46 | * Also, FRU not valid for APU devices.
|
| 47 |
|
| 48 | if (amdgpu_sriov_vf(adev) || (adev->flags & AMD_IS_APU))
|
| 49 | return false;
|
| 50 |
|
| 51 | |
| 52 |
|
| 53 | if (fru_addr)
|
| 54 | *fru_addr = FRU_EEPROM_MADDR_8;
|
| 55 |
|
| 56 | |
| 57 | * we can use just the "DXXX" portion. If there were more models, we
|
| 58 | * could convert the 3 characters to a hex integer and use a switch
|
| 59 | * for ease/speed/readability. For now, 2 string comparisons are
|
| 60 | * reasonable and not too expensive
|
| 61 |
|
| 62 | switch (amdgpu_ip_version(adev, MP1_HWIP, 0)) {
|
| 63 | case IP_VERSION(11, 0, 2):
|
| 64 | switch (adev->asic_type) {
|
| 65 | case CHIP_VEGA20:
|
| 66 |
|
| 67 | if (atom_ctx && (strnstr(atom_ctx->vbios_pn, "D161",
|
| 68 | sizeof(atom_ctx->vbios_pn)) ||
|
| 69 | strnstr(atom_ctx->vbios_pn, "D163",
|
| 70 | sizeof(atom_ctx->vbios_pn)))) {
|
| 71 | if (fru_addr)
|
| 72 | *fru_addr = FRU_EEPROM_MADDR_6;
|
| 73 | return true;
|
| 74 | } else {
|
| 75 | return false;
|
| 76 | }
|
| 77 | case CHIP_ARCTURUS:
|
| 78 | default:
|
| 79 | return false;
|
| 80 | }
|
| 81 | case IP_VERSION(11, 0, 7):
|
| 82 | if (atom_ctx && strnstr(atom_ctx->vbios_pn, "D603",
|
| 83 | sizeof(atom_ctx->vbios_pn))) {
|
| 84 | if (strnstr(atom_ctx->vbios_pn, "D603GLXE",
|
| 85 | sizeof(atom_ctx->vbios_pn))) {
|
| 86 | return false;
|
| 87 | }
|
| 88 |
|
| 89 | if (fru_addr)
|
| 90 | *fru_addr = FRU_EEPROM_MADDR_6;
|
| 91 | return true;
|
| 92 |
|
| 93 | } else {
|
| 94 | return false;
|
| 95 | }
|
| 96 | case IP_VERSION(13, 0, 2):
|
| 97 |
|
| 98 | if (atom_ctx && !strnstr(atom_ctx->vbios_pn, "D673",
|
| 99 | sizeof(atom_ctx->vbios_pn)))
|
| 100 | if (fru_addr)
|
| 101 | *fru_addr = FRU_EEPROM_MADDR_6;
|
| 102 | return true;
|
| 103 | case IP_VERSION(13, 0, 6):
|
| 104 | case IP_VERSION(13, 0, 14):
|
| 105 | if (fru_addr)
|
| 106 | *fru_addr = FRU_EEPROM_MADDR_8;
|
| 107 | return true;
|
| 108 | case IP_VERSION(13, 0, 12):
|
| 109 | case IP_VERSION(15, 0, 8):
|
| 110 | if (fru_addr)
|
| 111 | *fru_addr = FRU_EEPROM_MADDR_INV;
|
| 112 | return true;
|
| 113 | default:
|
| 114 | return false;
|
| 115 | }
|
| 116 | }
|
| 117 |
|
| 118 | |
| 119 | * IPMI FRU Product Info Area fields are TLV: one type/length byte
|
| 120 | * (low 6 bits = data length) followed by that many data bytes. These
|
| 121 | * helpers walk the cursor and copy a single field while bounding all
|
| 122 | * accesses to the actual buffer length read from the EEPROM.
|
| 123 |
|
| 124 | #define FRU_FIELD_LEN(p, a) ((p)[a] & 0x3F)
|
| 125 |
|
| 126 |
|
| 127 | static bool fru_pia_advance(u32 *addr, const unsigned char *pia, int len)
|
| 128 | {
|
| 129 | if (*addr >= (u32)len)
|
| 130 | return false;
|
| 131 | *addr += 1 + FRU_FIELD_LEN(pia, *addr);
|
| 132 | return true;
|
| 133 | }
|
| 134 |
|
| 135 | |
| 136 | * Copy the current TLV's data into dst (NUL-terminated). Returns false if
|
| 137 | * the TLV header or data would read past the end of pia.
|
| 138 |
|
| 139 | static bool fru_pia_copy_field(char *dst, size_t dst_size,
|
| 140 | const unsigned char *pia, u32 addr, int len)
|
| 141 | {
|
| 142 | size_t fl;
|
| 143 |
|
| 144 | if (addr + 1 >= (u32)len)
|
| 145 | return false;
|
| 146 |
|
| 147 | fl = min3((size_t)FRU_FIELD_LEN(pia, addr),
|
| 148 | dst_size - 1,
|
| 149 | (size_t)(len - addr - 1));
|
| 150 | memcpy(dst, pia + addr + 1, fl);
|
| 151 | dst[fl] = '\0';
|
| 152 | return true;
|
| 153 | }
|
| 154 |
|
| 155 | int amdgpu_fru_get_product_info(struct amdgpu_device *adev)
|
| 156 | {
|
| 157 | struct amdgpu_fru_info *fru_info;
|
| 158 | unsigned char buf[8], *pia;
|
| 159 | u32 addr, fru_addr;
|
| 160 | int size, len;
|
| 161 | u8 csum;
|
| 162 |
|
| 163 | if (!is_fru_eeprom_supported(adev, &fru_addr))
|
| 164 | return 0;
|
| 165 |
|
| 166 |
|
| 167 | if (fru_addr == FRU_EEPROM_MADDR_INV)
|
| 168 | return 0;
|
| 169 |
|
| 170 | if (!adev->fru_info) {
|
| 171 | adev->fru_info = kzalloc_obj(*adev->fru_info);
|
| 172 | if (!adev->fru_info)
|
| 173 | return -ENOMEM;
|
| 174 | }
|
| 175 |
|
| 176 | fru_info = adev->fru_info;
|
| 177 | |
| 178 | * so convert it to a 16-digit HEX string for convenience and
|
| 179 | * backwards-compatibility.
|
| 180 |
|
| 181 | sprintf(fru_info->serial, "%llx", adev->unique_id);
|
| 182 |
|
| 183 |
|
| 184 | if (!adev->pm.fru_eeprom_i2c_bus || !adev->pm.fru_eeprom_i2c_bus->algo) {
|
| 185 | dev_warn(adev->dev,
|
| 186 | "Cannot access FRU, EEPROM accessor not initialized");
|
| 187 | return -ENODEV;
|
| 188 | }
|
| 189 |
|
| 190 |
|
| 191 | len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, fru_addr, buf,
|
| 192 | sizeof(buf));
|
| 193 | if (len != 8) {
|
| 194 | dev_err(adev->dev, "Couldn't read the IPMI Common Header: %d",
|
| 195 | len);
|
| 196 | return len < 0 ? len : -EIO;
|
| 197 | }
|
| 198 |
|
| 199 | if (buf[0] != 1) {
|
| 200 | dev_err(adev->dev, "Bad IPMI Common Header version: 0x%02x",
|
| 201 | buf[0]);
|
| 202 | return -EIO;
|
| 203 | }
|
| 204 |
|
| 205 | for (csum = 0; len > 0; len--)
|
| 206 | csum += buf[len - 1];
|
| 207 | if (csum) {
|
| 208 | dev_err(adev->dev, "Bad IPMI Common Header checksum: 0x%02x",
|
| 209 | csum);
|
| 210 | return -EIO;
|
| 211 | }
|
| 212 |
|
| 213 |
|
| 214 | addr = buf[4] * 8;
|
| 215 | if (!addr)
|
| 216 | return 0;
|
| 217 |
|
| 218 |
|
| 219 | addr += fru_addr;
|
| 220 |
|
| 221 |
|
| 222 | len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, addr, buf, 3);
|
| 223 | if (len != 3) {
|
| 224 | dev_err(adev->dev,
|
| 225 | "Couldn't read the Product Info Area header: %d", len);
|
| 226 | return len < 0 ? len : -EIO;
|
| 227 | }
|
| 228 |
|
| 229 | if (buf[0] != 1) {
|
| 230 | dev_err(adev->dev, "Bad IPMI Product Info Area version: 0x%02x",
|
| 231 | buf[0]);
|
| 232 | return -EIO;
|
| 233 | }
|
| 234 |
|
| 235 | size = buf[1] * 8;
|
| 236 | pia = kzalloc(size, GFP_KERNEL);
|
| 237 | if (!pia)
|
| 238 | return -ENOMEM;
|
| 239 |
|
| 240 |
|
| 241 | len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, addr, pia, size);
|
| 242 | if (len != size) {
|
| 243 | kfree(pia);
|
| 244 | dev_err(adev->dev, "Couldn't read the Product Info Area: %d",
|
| 245 | len);
|
| 246 | return len < 0 ? len : -EIO;
|
| 247 | }
|
| 248 |
|
| 249 | for (csum = 0; size > 0; size--)
|
| 250 | csum += pia[size - 1];
|
| 251 | if (csum) {
|
| 252 | dev_err(adev->dev, "Bad Product Info Area checksum: 0x%02x",
|
| 253 | csum);
|
| 254 | kfree(pia);
|
| 255 | return -EIO;
|
| 256 | }
|
| 257 |
|
| 258 | |
| 259 | *
|
| 260 | * Read Manufacturer Name field whose length is [3].
|
| 261 |
|
| 262 | addr = 3;
|
| 263 | if (!fru_pia_copy_field(fru_info->manufacturer_name,
|
| 264 | sizeof(fru_info->manufacturer_name),
|
| 265 | pia, addr, len))
|
| 266 | goto Out;
|
| 267 |
|
| 268 |
|
| 269 | if (!fru_pia_advance(&addr, pia, len) ||
|
| 270 | !fru_pia_copy_field(fru_info->product_name,
|
| 271 | sizeof(fru_info->product_name),
|
| 272 | pia, addr, len))
|
| 273 | goto Out;
|
| 274 |
|
| 275 |
|
| 276 | if (!fru_pia_advance(&addr, pia, len) ||
|
| 277 | !fru_pia_copy_field(fru_info->product_number,
|
| 278 | sizeof(fru_info->product_number),
|
| 279 | pia, addr, len))
|
| 280 | goto Out;
|
| 281 |
|
| 282 |
|
| 283 | if (!fru_pia_advance(&addr, pia, len))
|
| 284 | goto Out;
|
| 285 |
|
| 286 |
|
| 287 | if (!fru_pia_advance(&addr, pia, len) ||
|
| 288 | !fru_pia_copy_field(fru_info->serial,
|
| 289 | sizeof(fru_info->serial),
|
| 290 | pia, addr, len))
|
| 291 | goto Out;
|
| 292 |
|
| 293 |
|
| 294 | if (!fru_pia_advance(&addr, pia, len))
|
| 295 | goto Out;
|
| 296 |
|
| 297 |
|
| 298 | if (!fru_pia_advance(&addr, pia, len) ||
|
| 299 | !fru_pia_copy_field(fru_info->fru_id,
|
| 300 | sizeof(fru_info->fru_id),
|
| 301 | pia, addr, len))
|
| 302 | goto Out;
|
| 303 |
|
| 304 | Out:
|
| 305 | kfree(pia);
|
| 306 | return 0;
|
| 307 | }
|
| 308 |
|
| 309 | |
| 310 | * DOC: product_name
|
| 311 | *
|
| 312 | * The amdgpu driver provides a sysfs API for reporting the product name
|
| 313 | * for the device
|
| 314 | * The file product_name is used for this and returns the product name
|
| 315 | * as returned from the FRU.
|
| 316 | * NOTE: This is only available for certain server cards
|
| 317 |
|
| 318 |
|
| 319 | static ssize_t amdgpu_fru_product_name_show(struct device *dev,
|
| 320 | struct device_attribute *attr,
|
| 321 | char *buf)
|
| 322 | {
|
| 323 | struct drm_device *ddev = dev_get_drvdata(dev);
|
| 324 | struct amdgpu_device *adev = drm_to_adev(ddev);
|
| 325 |
|
| 326 | return sysfs_emit(buf, "%s\n", adev->fru_info->product_name);
|
| 327 | }
|
| 328 |
|
| 329 | static DEVICE_ATTR(product_name, 0444, amdgpu_fru_product_name_show, NULL);
|
| 330 |
|
| 331 | |
| 332 | * DOC: product_number
|
| 333 | *
|
| 334 | * The amdgpu driver provides a sysfs API for reporting the part number
|
| 335 | * for the device
|
| 336 | * The file product_number is used for this and returns the part number
|
| 337 | * as returned from the FRU.
|
| 338 | * NOTE: This is only available for certain server cards
|
| 339 |
|
| 340 |
|
| 341 | static ssize_t amdgpu_fru_product_number_show(struct device *dev,
|
| 342 | struct device_attribute *attr,
|
| 343 | char *buf)
|
| 344 | {
|
| 345 | struct drm_device *ddev = dev_get_drvdata(dev);
|
| 346 | struct amdgpu_device *adev = drm_to_adev(ddev);
|
| 347 |
|
| 348 | return sysfs_emit(buf, "%s\n", adev->fru_info->product_number);
|
| 349 | }
|
| 350 |
|
| 351 | static DEVICE_ATTR(product_number, 0444, amdgpu_fru_product_number_show, NULL);
|
| 352 |
|
| 353 | |
| 354 | * DOC: serial_number
|
| 355 | *
|
| 356 | * The amdgpu driver provides a sysfs API for reporting the serial number
|
| 357 | * for the device
|
| 358 | * The file serial_number is used for this and returns the serial number
|
| 359 | * as returned from the FRU.
|
| 360 | * NOTE: This is only available for certain server cards
|
| 361 |
|
| 362 |
|
| 363 | static ssize_t amdgpu_fru_serial_number_show(struct device *dev,
|
| 364 | struct device_attribute *attr,
|
| 365 | char *buf)
|
| 366 | {
|
| 367 | struct drm_device *ddev = dev_get_drvdata(dev);
|
| 368 | struct amdgpu_device *adev = drm_to_adev(ddev);
|
| 369 |
|
| 370 | return sysfs_emit(buf, "%s\n", adev->fru_info->serial);
|
| 371 | }
|
| 372 |
|
| 373 | static DEVICE_ATTR(serial_number, 0444, amdgpu_fru_serial_number_show, NULL);
|
| 374 |
|
| 375 | |
| 376 | * DOC: fru_id
|
| 377 | *
|
| 378 | * The amdgpu driver provides a sysfs API for reporting FRU File Id
|
| 379 | * for the device.
|
| 380 | * The file fru_id is used for this and returns the File Id value
|
| 381 | * as returned from the FRU.
|
| 382 | * NOTE: This is only available for certain server cards
|
| 383 |
|
| 384 |
|
| 385 | static ssize_t amdgpu_fru_id_show(struct device *dev,
|
| 386 | struct device_attribute *attr, char *buf)
|
| 387 | {
|
| 388 | struct drm_device *ddev = dev_get_drvdata(dev);
|
| 389 | struct amdgpu_device *adev = drm_to_adev(ddev);
|
| 390 |
|
| 391 | return sysfs_emit(buf, "%s\n", adev->fru_info->fru_id);
|
| 392 | }
|
| 393 |
|
| 394 | static DEVICE_ATTR(fru_id, 0444, amdgpu_fru_id_show, NULL);
|
| 395 |
|
| 396 | |
| 397 | * DOC: manufacturer
|
| 398 | *
|
| 399 | * The amdgpu driver provides a sysfs API for reporting manufacturer name from
|
| 400 | * FRU information.
|
| 401 | * The file manufacturer returns the value as returned from the FRU.
|
| 402 | * NOTE: This is only available for certain server cards
|
| 403 |
|
| 404 |
|
| 405 | static ssize_t amdgpu_fru_manufacturer_name_show(struct device *dev,
|
| 406 | struct device_attribute *attr,
|
| 407 | char *buf)
|
| 408 | {
|
| 409 | struct drm_device *ddev = dev_get_drvdata(dev);
|
| 410 | struct amdgpu_device *adev = drm_to_adev(ddev);
|
| 411 |
|
| 412 | return sysfs_emit(buf, "%s\n", adev->fru_info->manufacturer_name);
|
| 413 | }
|
| 414 |
|
| 415 | static DEVICE_ATTR(manufacturer, 0444, amdgpu_fru_manufacturer_name_show, NULL);
|
| 416 |
|
| 417 | static const struct attribute *amdgpu_fru_attributes[] = {
|
| 418 | &dev_attr_product_name.attr,
|
| 419 | &dev_attr_product_number.attr,
|
| 420 | &dev_attr_serial_number.attr,
|
| 421 | &dev_attr_fru_id.attr,
|
| 422 | &dev_attr_manufacturer.attr,
|
| 423 | NULL
|
| 424 | };
|
| 425 |
|
| 426 | int amdgpu_fru_sysfs_init(struct amdgpu_device *adev)
|
| 427 | {
|
| 428 | if (!is_fru_eeprom_supported(adev, NULL) || !adev->fru_info)
|
| 429 | return 0;
|
| 430 |
|
| 431 | return sysfs_create_files(&adev->dev->kobj, amdgpu_fru_attributes);
|
| 432 | }
|
| 433 |
|
| 434 | void amdgpu_fru_sysfs_fini(struct amdgpu_device *adev)
|
| 435 | {
|
| 436 | if (!adev->fru_info)
|
| 437 | return;
|
| 438 |
|
| 439 | sysfs_remove_files(&adev->dev->kobj, amdgpu_fru_attributes);
|
| 440 | }
|