blob: 3de91a026a3ac833ba09e5b19644692ecc2d5884 [file] [log] [blame]
/* Copyright 2017 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <skiboot.h>
#include <opal.h>
#include <chip.h>
#include <sensor.h>
#include <device.h>
#include <cpu.h>
/*
* OCC Sensor Data
*
* OCC sensor data will use BAR2 (OCC Common is per physical drawer).
* Starting address is at offset 0x00580000 from BAR2 base address.
* Maximum size is 1.5MB.
*
* -------------------------------------------------------------------------
* | Start (Offset from | End | Size |Description |
* | BAR2 base address) | | | |
* -------------------------------------------------------------------------
* | 0x00580000 | 0x005A57FF |150kB |OCC 0 Sensor Data Block|
* | 0x005A5800 | 0x005CAFFF |150kB |OCC 1 Sensor Data Block|
* | : | : | : | : |
* | 0x00686800 | 0x006ABFFF |150kB |OCC 7 Sensor Data Block|
* | 0x006AC000 | 0x006FFFFF |336kB |Reserved |
* -------------------------------------------------------------------------
*
*
* OCC N Sensor Data Block Layout (150kB)
*
* The sensor data block layout is the same for each OCC N. It contains
* sensor-header-block, sensor-names buffer, sensor-readings-ping buffer and
* sensor-readings-pong buffer.
*
* ----------------------------------------------------------------------------
* | Start (Offset from OCC | End | Size |Description |
* | N Sensor Data Block) | | | |
* ----------------------------------------------------------------------------
* | 0x00000000 | 0x000003FF |1kB |Sensor Data Header Block |
* | 0x00000400 | 0x0000CBFF |50kB |Sensor Names |
* | 0x0000CC00 | 0x0000DBFF |4kB |Reserved |
* | 0x0000DC00 | 0x00017BFF |40kB |Sensor Readings ping buffer|
* | 0x00017C00 | 0x00018BFF |4kB |Reserved |
* | 0x00018C00 | 0x00022BFF |40kB |Sensor Readings pong buffer|
* | 0x00022C00 | 0x000257FF |11kB |Reserved |
* ----------------------------------------------------------------------------
*
* Sensor Data Header Block : This is written once by the OCC during
* initialization after a load or reset. Layout is defined in 'struct
* occ_sensor_data_header'
*
* Sensor Names : This is written once by the OCC during initialization after a
* load or reset. It contains static information for each sensor. The number of
* sensors, format version and length of each sensor is defined in
* 'Sensor Data Header Block'. Format of each sensor name is defined in
* 'struct occ_sensor_name'. The first sensor starts at offset 0 followed
* immediately by the next sensor.
*
* Sensor Readings Ping/Pong Buffer:
* There are two 40kB buffers to store the sensor readings. One buffer that
* is currently being updated by the OCC and one that is available to be read.
* Each of these buffers will be of the same format. The number of sensors and
* the format version of the ping and pong buffers is defined in the
* 'Sensor Data Header Block'.
*
* Each sensor within the ping and pong buffers may be of a different format
* and length. For each sensor the length and format is determined by its
* 'struct occ_sensor_name.structure_type' in the Sensor Names buffer.
*
* --------------------------------------------------------------------------
* | Offset | Byte0 | Byte1 | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 |
* --------------------------------------------------------------------------
* | 0x0000 |Valid | Reserved |
* | |(0x01) | |
* --------------------------------------------------------------------------
* | 0x0008 | Sensor Readings |
* --------------------------------------------------------------------------
* | : | : |
* --------------------------------------------------------------------------
* | 0xA000 | End of Data |
* --------------------------------------------------------------------------
*
*/
#define MAX_OCCS 8
#define MAX_CHARS_SENSOR_NAME 16
#define MAX_CHARS_SENSOR_UNIT 4
#define OCC_SENSOR_DATA_BLOCK_OFFSET 0x00580000
#define OCC_SENSOR_DATA_BLOCK_SIZE 0x00025800
enum occ_sensor_type {
OCC_SENSOR_TYPE_GENERIC = 0x0001,
OCC_SENSOR_TYPE_CURRENT = 0x0002,
OCC_SENSOR_TYPE_VOLTAGE = 0x0004,
OCC_SENSOR_TYPE_TEMPERATURE = 0x0008,
OCC_SENSOR_TYPE_UTILIZATION = 0x0010,
OCC_SENSOR_TYPE_TIME = 0x0020,
OCC_SENSOR_TYPE_FREQUENCY = 0x0040,
OCC_SENSOR_TYPE_POWER = 0x0080,
OCC_SENSOR_TYPE_PERFORMANCE = 0x0200,
};
enum occ_sensor_location {
OCC_SENSOR_LOC_SYSTEM = 0x0001,
OCC_SENSOR_LOC_PROCESSOR = 0x0002,
OCC_SENSOR_LOC_PARTITION = 0x0004,
OCC_SENSOR_LOC_MEMORY = 0x0008,
OCC_SENSOR_LOC_VRM = 0x0010,
OCC_SENSOR_LOC_OCC = 0x0020,
OCC_SENSOR_LOC_CORE = 0x0040,
OCC_SENSOR_LOC_QUAD = 0x0080,
OCC_SENSOR_LOC_GPU = 0x0100,
};
enum sensor_struct_type {
OCC_SENSOR_READING_FULL = 0x01,
OCC_SENSOR_READING_COUNTER = 0x02,
};
/**
* struct occ_sensor_data_header - Sensor Data Header Block
* @valid: When the value is 0x01 it indicates
* that this header block and the sensor
* names buffer are ready
* @version: Format version of this block
* @nr_sensors: Number of sensors in names, ping and
* pong buffer
* @reading_version: Format version of the Ping/Pong buffer
* @names_offset: Offset to the location of names buffer
* @names_version: Format version of names buffer
* @names_length: Length of each sensor in names buffer
* @reading_ping_offset: Offset to the location of Ping buffer
* @reading_pong_offset: Offset to the location of Pong buffer
* @pad/reserved: Unused data
*/
struct occ_sensor_data_header {
u8 valid;
u8 version;
u16 nr_sensors;
u8 reading_version;
u8 pad[3];
u32 names_offset;
u8 names_version;
u8 name_length;
u16 reserved;
u32 reading_ping_offset;
u32 reading_pong_offset;
} __packed;
/**
* struct occ_sensor_name - Format of Sensor Name
* @name: Sensor name
* @units: Sensor units of measurement
* @gsid: Global sensor id (OCC)
* @freq: Update frequency
* @scale_factor: Scaling factor
* @type: Sensor type as defined in
* 'enum occ_sensor_type'
* @location: Sensor location as defined in
* 'enum occ_sensor_location'
* @structure_type: Indicates type of data structure used
* for the sensor readings in the ping and
* pong buffers for this sensor as defined
* in 'enum sensor_struct_type'
* @reading_offset: Offset from the start of the ping/pong
* reading buffers for this sensor
* @sensor_data: Sensor specific info
* @pad: Padding to fit the size of 48 bytes.
*/
struct occ_sensor_name {
char name[MAX_CHARS_SENSOR_NAME];
char units[MAX_CHARS_SENSOR_UNIT];
u16 gsid;
u32 freq;
u32 scale_factor;
u16 type;
u16 location;
u8 structure_type;
u32 reading_offset;
u8 sensor_data;
u8 pad[8];
} __packed;
/**
* struct occ_sensor_record - Sensor Reading Full
* @gsid: Global sensor id (OCC)
* @timestamp: Time base counter value while updating
* the sensor
* @sample: Latest sample of this sensor
* @sample_min: Minimum value since last OCC reset
* @sample_max: Maximum value since last OCC reset
* @csm_min: Minimum value since last reset request
* by CSM (CORAL)
* @csm_max: Maximum value since last reset request
* by CSM (CORAL)
* @profiler_min: Minimum value since last reset request
* by profiler (CORAL)
* @profiler_max: Maximum value since last reset request
* by profiler (CORAL)
* @job_scheduler_min: Minimum value since last reset request
* by job scheduler(CORAL)
* @job_scheduler_max: Maximum value since last reset request
* by job scheduler (CORAL)
* @accumulator: Accumulator for this sensor
* @update_tag: Count of the number of ticks that have
* passed between updates
* @pad: Padding to fit the size of 48 bytes
*/
struct occ_sensor_record {
u16 gsid;
u64 timestamp;
u16 sample;
u16 sample_min;
u16 sample_max;
u16 csm_min;
u16 csm_max;
u16 profiler_min;
u16 profiler_max;
u16 job_scheduler_min;
u16 job_scheduler_max;
u64 accumulator;
u32 update_tag;
u8 pad[8];
} __packed;
/**
* struct occ_sensor_counter - Sensor Reading Counter
* @gsid: Global sensor id (OCC)
* @timestamp: Time base counter value while updating
* the sensor
* @accumulator: Accumulator/Counter
* @sample: Latest sample of this sensor (0/1)
* @pad: Padding to fit the size of 24 bytes
*/
struct occ_sensor_counter {
u16 gsid;
u64 timestamp;
u64 accumulator;
u8 sample;
u8 pad[5];
} __packed;
enum sensor_attr {
SENSOR_SAMPLE,
SENSOR_SAMPLE_MIN, /* OCC's min/max */
SENSOR_SAMPLE_MAX,
SENSOR_CSM_MIN, /* CSM's min/max */
SENSOR_CSM_MAX,
MAX_SENSOR_ATTR,
};
#define HWMON_SENSORS_MASK (OCC_SENSOR_TYPE_CURRENT | \
OCC_SENSOR_TYPE_VOLTAGE | \
OCC_SENSOR_TYPE_TEMPERATURE | \
OCC_SENSOR_TYPE_POWER)
static struct str_map {
const char *occ_str;
const char *opal_str;
} str_maps[] = {
{"PWRSYS", "System"},
{"PWRFAN", "Fan"},
{"PWRIO", "IO"},
{"PWRSTORE", "Storage"},
{"PWRGPU", "GPU"},
{"PWRAPSSCH", "APSS"},
{"PWRPROC", ""},
{"PWRVDD", "Vdd"},
{"CURVDD", "Vdd"},
{"VOLTVDDSENSE", "Vdd Remote Sense"},
{"VOLTVDD", "Vdd"},
{"PWRVDN", "Vdn"},
{"CURVDN", "Vdn"},
{"VOLTVDNSENSE", "Vdn Remote Sense"},
{"VOLTVDN", "Vdn"},
{"PWRMEM", "Memory"},
{"TEMPC", "Core"},
{"TEMPQ", "Quad"},
{"TEMPNEST", "Nest"},
{"TEMPPROCTHRMC", "Core"},
{"TEMPDIMM", "DIMM"},
{"TEMPGPU", "GPU"},
};
static u64 occ_sensor_base;
static inline
struct occ_sensor_data_header *get_sensor_header_block(int occ_num)
{
return (struct occ_sensor_data_header *)
(occ_sensor_base + occ_num * OCC_SENSOR_DATA_BLOCK_SIZE);
}
static inline
struct occ_sensor_name *get_names_block(struct occ_sensor_data_header *hb)
{
return ((struct occ_sensor_name *)((u64)hb + hb->names_offset));
}
static inline u32 sensor_handler(int occ_num, int sensor_id, int attr)
{
return sensor_make_handler(SENSOR_OCC, occ_num, sensor_id, attr);
}
int occ_sensor_read(u32 handle, u32 *data)
{
struct occ_sensor_data_header *hb;
struct occ_sensor_name *md;
struct occ_sensor_record *sping, *spong;
struct occ_sensor_record *sensor = NULL;
u8 *ping, *pong;
u16 id = sensor_get_rid(handle);
u8 occ_num = sensor_get_frc(handle);
u8 attr = sensor_get_attr(handle);
if (occ_num > MAX_OCCS)
return OPAL_PARAMETER;
if (attr > MAX_SENSOR_ATTR)
return OPAL_PARAMETER;
hb = get_sensor_header_block(occ_num);
md = get_names_block(hb);
if (hb->valid != 1)
return OPAL_HARDWARE;
if (id > hb->nr_sensors)
return OPAL_PARAMETER;
ping = (u8 *)((u64)hb + hb->reading_ping_offset);
pong = (u8 *)((u64)hb + hb->reading_pong_offset);
sping = (struct occ_sensor_record *)((u64)ping + md[id].reading_offset);
spong = (struct occ_sensor_record *)((u64)pong + md[id].reading_offset);
/* Check which buffer is valid and read the data from that.
* Ping Pong Action
* 0 0 Return with error
* 0 1 Read Pong
* 1 0 Read Ping
* 1 1 Read the buffer with latest timestamp
*/
if (*ping && *pong) {
if (sping->timestamp > spong->timestamp)
sensor = sping;
else
sensor = spong;
} else if (*ping && !*pong) {
sensor = sping;
} else if (!*ping && *pong) {
sensor = spong;
} else if (!*ping && !*pong) {
prlog(PR_DEBUG, "OCC: Both ping and pong sensor buffers are invalid\n");
return OPAL_HARDWARE;
}
switch (attr) {
case SENSOR_SAMPLE:
*data = sensor->sample;
break;
case SENSOR_SAMPLE_MIN:
*data = sensor->sample_min;
break;
case SENSOR_SAMPLE_MAX:
*data = sensor->sample_max;
break;
case SENSOR_CSM_MIN:
*data = sensor->csm_min;
break;
case SENSOR_CSM_MAX:
*data = sensor->csm_max;
break;
default:
*data = 0;
}
return OPAL_SUCCESS;
}
static bool occ_sensor_sanity(struct occ_sensor_data_header *hb, int chipid)
{
if (hb->valid != 0x01) {
prerror("OCC: Chip %d sensor data invalid\n", chipid);
return false;
}
if (hb->version != 0x01) {
prerror("OCC: Chip %d unsupported sensor header block version %d\n",
chipid, hb->version);
return false;
}
if (hb->reading_version != 0x01) {
prerror("OCC: Chip %d unsupported sensor record format %d\n",
chipid, hb->reading_version);
return false;
}
if (hb->names_version != 0x01) {
prerror("OCC: Chip %d unsupported sensor names format %d\n",
chipid, hb->names_version);
return false;
}
if (hb->name_length != sizeof(struct occ_sensor_name)) {
prerror("OCC: Chip %d unsupported sensor names length %d\n",
chipid, hb->name_length);
return false;
}
if (!hb->nr_sensors) {
prerror("OCC: Chip %d has no sensors\n", chipid);
return false;
}
if (!hb->names_offset || !hb->reading_ping_offset ||
!hb->reading_pong_offset) {
prerror("OCC: Chip %d Invalid sensor buffer pointers\n",
chipid);
return false;
}
return true;
}
/*
* parse_entity: Parses OCC sensor name to return the entity number like
* chipid, core-id, dimm-no, gpu-no. 'end' is used to
* get the subentity strings. Returns -1 if no number is found.
* TEMPC4 --> returns 4, end will be NULL
* TEMPGPU2DRAM1 --> returns 2, end = "DRAM1"
* PWRSYS --> returns -1, end = NULL
*/
static int parse_entity(const char *name, char **end)
{
while (*name != '\0') {
if (isdigit(*name))
break;
name++;
}
if (*name)
return strtol(name, end, 10);
else
return -1;
}
static void add_sensor_label(struct dt_node *node, struct occ_sensor_name *md,
int chipid)
{
char sname[30] = "";
char prefix[30] = "";
int i;
if (md->location != OCC_SENSOR_LOC_SYSTEM)
snprintf(prefix, sizeof(prefix), "%s %d ", "Chip", chipid);
for (i = 0; i < ARRAY_SIZE(str_maps); i++)
if (!strncmp(str_maps[i].occ_str, md->name,
strlen(str_maps[i].occ_str))) {
char *end;
int num = -1;
if (md->location != OCC_SENSOR_LOC_CORE)
num = parse_entity(md->name, &end);
if (num != -1) {
snprintf(sname, sizeof(sname), "%s%s %d %s",
prefix, str_maps[i].opal_str, num,
end);
} else {
snprintf(sname, sizeof(sname), "%s%s", prefix,
str_maps[i].opal_str);
}
dt_add_property_string(node, "label", sname);
return;
}
/* Fallback to OCC literal if mapping is not found */
if (md->location == OCC_SENSOR_LOC_SYSTEM) {
dt_add_property_string(node, "label", md->name);
} else {
snprintf(sname, sizeof(sname), "%s%s", prefix, md->name);
dt_add_property_string(node, "label", sname);
}
}
static const char *get_sensor_type_string(enum occ_sensor_type type)
{
switch (type) {
case OCC_SENSOR_TYPE_POWER:
return "power";
case OCC_SENSOR_TYPE_TEMPERATURE:
return "temp";
case OCC_SENSOR_TYPE_CURRENT:
return "curr";
case OCC_SENSOR_TYPE_VOLTAGE:
return "in";
default:
break;
}
return "unknown";
}
static const char *get_sensor_loc_string(enum occ_sensor_location loc)
{
switch (loc) {
case OCC_SENSOR_LOC_SYSTEM:
return "sys";
case OCC_SENSOR_LOC_PROCESSOR:
return "proc";
case OCC_SENSOR_LOC_MEMORY:
return "mem";
case OCC_SENSOR_LOC_VRM:
return "vrm";
case OCC_SENSOR_LOC_CORE:
return "core";
case OCC_SENSOR_LOC_QUAD:
return "quad";
case OCC_SENSOR_LOC_GPU:
return "gpu";
default:
break;
}
return "unknown";
}
void occ_sensors_init(void)
{
struct proc_chip *chip;
struct dt_node *sg, *exports;
int occ_num = 0, i;
/* OCC inband sensors is only supported in P9 */
if (proc_gen != proc_gen_p9)
return;
/* Sensors are copied to BAR2 OCC Common Area */
chip = next_chip(NULL);
if (!chip->occ_common_base) {
prerror("OCC: Unassigned OCC Common Area. No sensors found\n");
return;
}
occ_sensor_base = chip->occ_common_base + OCC_SENSOR_DATA_BLOCK_OFFSET;
sg = dt_new(opal_node, "sensor-groups");
if (!sg) {
prerror("OCC: Failed to create sensor groups node\n");
return;
}
dt_add_property_string(sg, "compatible", "ibm,opal-sensor-group");
for_each_chip(chip) {
struct occ_sensor_data_header *hb;
struct occ_sensor_name *md;
u32 *phandles, phcount = 0;
hb = get_sensor_header_block(occ_num);
md = get_names_block(hb);
/* Sanity check of the Sensor Data Header Block */
if (!occ_sensor_sanity(hb, chip->id))
continue;
phandles = malloc(hb->nr_sensors * sizeof(u32));
assert(phandles);
for (i = 0; i < hb->nr_sensors; i++) {
char name[30];
const char *type, *loc;
struct dt_node *node;
struct cpu_thread *c = NULL;
u32 handler;
if (md[i].structure_type != OCC_SENSOR_READING_FULL)
continue;
if (!(md[i].type & HWMON_SENSORS_MASK))
continue;
if (md[i].location == OCC_SENSOR_LOC_CORE) {
int num = parse_entity(md[i].name, NULL);
for_each_available_core_in_chip(c, chip->id)
if (pir_to_core_id(c->pir) == num)
break;
if (!c)
continue;
}
type = get_sensor_type_string(md[i].type);
loc = get_sensor_loc_string(md[i].location);
snprintf(name, sizeof(name), "%s-%s", loc, type);
handler = sensor_handler(occ_num, i, SENSOR_SAMPLE);
node = dt_new_addr(sensor_node, name, handler);
dt_add_property_string(node, "sensor-type", type);
dt_add_property_cells(node, "sensor-data", handler);
handler = sensor_handler(occ_num, i, SENSOR_CSM_MAX);
dt_add_property_cells(node, "sensor-data-max", handler);
handler = sensor_handler(occ_num, i, SENSOR_CSM_MIN);
dt_add_property_cells(node, "sensor-data-min", handler);
dt_add_property_string(node, "compatible",
"ibm,opal-sensor");
dt_add_property_string(node, "occ_label", md[i].name);
add_sensor_label(node, &md[i], chip->id);
if (md[i].location == OCC_SENSOR_LOC_CORE)
dt_add_property_cells(node, "ibm,pir", c->pir);
phandles[phcount++] = node->phandle;
}
occ_num++;
occ_add_sensor_groups(sg, phandles, phcount, chip->id);
free(phandles);
}
if (!occ_num)
return;
exports = dt_find_by_path(dt_root, "/ibm,opal/firmware/exports");
if (!exports) {
prerror("OCC: dt node /ibm,opal/firmware/exports not found\n");
return;
}
dt_add_property_u64s(exports, "occ_inband_sensors", occ_sensor_base,
OCC_SENSOR_DATA_BLOCK_SIZE * occ_num);
}