blob: bf5904b981e0ba281ca5a98c05df75855579069d [file] [log] [blame]
/* Copyright 2012, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* DIMM Serial Presence Detect interface normal access is via I2C but some
* platforms hide SPD behind memory controller and other access method must be
* used.
*/
#define _LARGEFILE64_SOURCE
#include <arpa/inet.h> /* ntohl() */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include "mosys/alloc.h"
#include "mosys/list.h"
#include "mosys/log.h"
#include "mosys/platform.h"
#include "intf/i2c.h"
#include "lib/cbfs_core.h"
#include "lib/math.h"
#include "lib/smbios.h"
#include "lib/smbios_tables.h"
#include "lib/spd.h"
static spd_raw_override spd_raw_access_override;
const char *ddr_freq_prettyprint[] = {
[DDR_FREQ_UNKNOWN] = "Unknown",
[DDR_333] = "667",
[DDR_400] = "800",
[DDR_533] = "1066",
[DDR_667] = "1333",
[DDR_800] = "1600",
[DDR_933] = "1866",
[DDR_1067] = "2133",
[DDR_1200] = "2400",
[DDR_1333] = "2667",
[DDR_1355] = "2710",
[DDR_1600] = "3200",
};
/*
* spd_raw_i2c - Read/write to/from SPD via I2C
*
* @intf: platform interface
* @bus: bus to read from
* @address: address on bus
* @reg: configuration register
* @length: number of bytes to read
* @data: data buffer
* @rw: 1=write, 0=read
*
* returns number of bytes written or read
* returns <0 to indicate error
*/
static int spd_raw_i2c(struct platform_intf *intf, int bus,
int address, int reg, int length, void *data, int rw)
{
if (!intf->op->i2c) {
return -ENOSYS;
}
switch (rw) {
case SPD_READ:
return intf->op->i2c->smbus_read_reg(intf, bus, address,
reg, length, data);
case SPD_WRITE:
return intf->op->i2c->smbus_write_reg(intf, bus, address,
reg, length, data);
}
return -1;
}
/* spd_raw_access - read/write access method to SPDs
*
* @intf: platform interface
* @bus: SMBus number of requested SPD
* @address: SMBus address of requested SPD
* @reg: register in SPD to perform access on
* @length: length of access to perform
* @data: pointer to buffer that is either filled or read from to do the access
* @rw: specify SPD operation (SPD_READ or SPD_WRITE)
*
* returns 0 on success, < 0 on error.
*/
int spd_raw_access(struct platform_intf *intf, int bus, int address,
int reg, int length, void *data, int rw)
{
if (spd_raw_access_override != NULL) {
return spd_raw_access_override(intf, bus, address,
reg, length, data, rw);
}
return spd_raw_i2c(intf, bus, address, reg, length, data, rw);
}
int spd_read_i2c(struct platform_intf *intf, int bus,
int address, int reg, int length, void *data)
{
const char module[] = "eeprom";
if (!intf->cb->memory || !intf->cb->memory->dimm_map)
return -1;
/* Read info from /sys */
if (intf->op->i2c->find_driver(intf, module)) {
FILE *fp;
int ret;
char path[80];
/* Get the data */
snprintf(path, sizeof(path), "%s/%u-%04x/%s",
intf->op->i2c->sys_root, bus, address, module);
if (!(fp = fopen(path, "r"))) {
lprintf(LOG_DEBUG,
"Failed to open %s\n", path);
return -1;
}
fseek(fp, reg, SEEK_SET);
ret = fread(data, 1, length, fp);
fclose(fp);
return ret;
} else {
return spd_raw_access(intf, bus, address, reg,
length, data, SPD_READ);
}
return -1;
}
#if 0
int spd_write_i2c(struct platform_intf *intf,
int dimm, int reg, int length, const void *data)
{
int bus, address;
if (!intf->cb->memory->dimm_map)
return -1;
/* convert logical dimm map */
bus = intf->cb->memory->dimm_map(intf, DIMM_TO_BUS, dimm);
if (bus < 0)
return -1;
address = intf->cb->memory->dimm_map(intf, DIMM_TO_ADDRESS, dimm);
if (address < 0)
return -1;
//FIXME: we cast data to remove the const - can we do better?
return intf->cb->memory->spd->write(intf, bus, address, reg,
length, (void *)data);
}
#endif
int override_spd_raw_access(spd_raw_override override)
{
spd_raw_access_override = override;
return 0;
}
/* new_spd_device() - create a new instance of spd_device
*
* @intf: platform_intf for access
* @dimm: Google logical dimm number to represent
*
* returns allocated and filled in spd_devices on success, NULL if error
*/
struct spd_device *new_spd_device(struct platform_intf *intf, int dimm)
{
struct spd_device *spd;
if (intf == NULL || dimm < 0) {
return NULL;
}
spd = mosys_malloc(sizeof(*spd));
spd->dimm_num = dimm;
memset(&spd->eeprom.data[0], 0xff, SPD_MAX_LENGTH);
if (intf->cb->memory->spd->read(intf, dimm, 0, 3,
&spd->eeprom.data[0]) != 3) {
free(spd);
return NULL;
}
spd->dram_type = (enum spd_dram_type)spd->eeprom.data[2];
spd->eeprom.length = spd_total_size(&spd->eeprom.data[0]);
/* Invalid length. */
if (spd->eeprom.length <= 0) {
lperror(LOG_DEBUG, "Invalid DIMM(%d) SPD length(%d).\n",
dimm, spd->eeprom.length);
free(spd);
return NULL;
}
/* Fill in copy of SPD eeprom area. */
if (intf->cb->memory->spd->read(intf, dimm, 0,
spd->eeprom.length,
&spd->eeprom.data[0])
!= spd->eeprom.length) {
lperror(LOG_DEBUG,
"Unable to read full contents of SPD from DIMM %d.\n",
dimm);
free(spd);
return NULL;
}
return spd;
}
enum {
SPD_INFO_DDR4,
SPD_INFO_DEFAULT,
};
static const struct spd_info {
size_t spd_len;
uint32_t spd_part_off;
size_t spd_part_len;
} spd_mem_info[] = {
[SPD_INFO_DDR4] = {
.spd_len = SPD_DDR4_LENGTH,
.spd_part_off = SPD_DDR4_PART_OFF,
.spd_part_len = SPD_DDR4_PART_LEN,
},
[SPD_INFO_DEFAULT] = {
.spd_len = SPD_DEFAULT_LENGTH,
.spd_part_off = SPD_DEFAULT_PART_OFF,
.spd_part_len = SPD_DEFAULT_PART_LEN,
},
};
static ssize_t find_spd_by_part_number(struct platform_intf *intf, int dimm,
uint8_t *spd, size_t spd_file_len)
{
const char *smbios_part_num;
size_t smbios_part_num_len, spd_part_num_len;
ssize_t ret = -1, offset;
uint8_t i, num_spd;
uint8_t *ptr, *spd_part_num;
struct smbios_table *table = mosys_malloc(sizeof(*table));
const struct spd_info *info = &spd_mem_info[SPD_INFO_DEFAULT];
lprintf(LOG_DEBUG, "Use SMBIOS type 17 to get memory information\n");
if (smbios_find_table(intf, SMBIOS_TYPE_MEMORY, dimm, table,
SMBIOS_LEGACY_ENTRY_BASE,
SMBIOS_LEGACY_ENTRY_LEN) < 0) {
lprintf(LOG_DEBUG, "Can't find smbios type17\n");
goto out;
}
if (table->data.mem_device.type == SMBIOS_MEMORY_TYPE_DDR4)
info = &spd_mem_info[SPD_INFO_DDR4];
smbios_part_num = table->string[table->data.mem_device.part_number];
smbios_part_num_len = strnlen(smbios_part_num, info->spd_part_len);
if (smbios_part_num_len == 0) {
lprintf(LOG_DEBUG, "SMBIOS type 17 is missing part number\n");
goto out;
}
// Legacy firmware doesn't remove trailing whitespaces from SPD part
// number so needs to check again; otherwise the part number might never
// matches to spd_part_num[] below which did remove trailing whitespaces.
while (smbios_part_num_len > 0 &&
smbios_part_num[smbios_part_num_len - 1] == ' ')
smbios_part_num_len--;
num_spd = spd_file_len / info->spd_len;
for (i = 0; i < num_spd; i++) {
offset = i * info->spd_len;
ptr = spd + offset;
spd_part_num = ptr + info->spd_part_off;
spd_part_num_len = info->spd_part_len;
// Strip off trailing whitespace from SPD part number.
while (spd_part_num_len > 0 &&
spd_part_num[spd_part_num_len - 1] == ' ')
spd_part_num_len--;
if (spd_part_num_len != smbios_part_num_len)
continue;
if (!memcmp(smbios_part_num, spd_part_num,
smbios_part_num_len)) {
lprintf(LOG_DEBUG, "Using memory config %u\n", i);
ret = offset;
break;
}
}
out:
free(table);
return ret;
}
static int _spd_read_from_cbfs(const char *spd_cbfs_filename,
struct platform_intf *intf,
int module, int reg, int num_bytes_to_read,
uint8_t *spd, size_t fw_size, uint8_t *fw)
{
struct cbfs_file *file;
size_t file_len;
ssize_t spd_offset;
uint8_t *ptr;
lprintf(LOG_DEBUG, "Read SPD %s from cbfs\n", spd_cbfs_filename);
if ((file = cbfs_find(spd_cbfs_filename, fw, fw_size)) == NULL)
return -1;
ptr = (uint8_t *)file + ntohl(file->offset);
file_len = ntohl(file->len);
spd_offset = find_spd_by_part_number(intf, module, ptr, file_len);
if (spd_offset < 0)
return -1;
MOSYS_CHECK(spd_offset + reg + num_bytes_to_read <= file_len);
memcpy(spd, ptr + spd_offset + reg, num_bytes_to_read);
return num_bytes_to_read;
}
int spd_read_from_cbfs(struct platform_intf *intf,
int module, int reg, int num_bytes_to_read,
uint8_t *spd, size_t fw_size, uint8_t *fw)
{
const char *spd_cbfs_files[] = {
"spd.bin",
"sec-spd.bin",
};
int bytes_to_read;
int i;
for (i = 0; i < ARRAY_SIZE(spd_cbfs_files); i++) {
bytes_to_read = _spd_read_from_cbfs(spd_cbfs_files[i], intf,
module, reg,
num_bytes_to_read, spd,
fw_size, fw);
if (bytes_to_read >= 0)
return bytes_to_read;
}
return -1;
}