diff options
-rw-r--r-- | core/init.c | 1 | ||||
-rw-r--r-- | core/sensor.c | 4 | ||||
-rw-r--r-- | hw/Makefile.inc | 2 | ||||
-rw-r--r-- | hw/occ-sensor.c | 596 | ||||
-rw-r--r-- | include/sensor.h | 1 | ||||
-rw-r--r-- | include/skiboot.h | 4 |
6 files changed, 607 insertions, 1 deletions
diff --git a/core/init.c b/core/init.c index 02bd30c..466a426 100644 --- a/core/init.c +++ b/core/init.c @@ -507,6 +507,7 @@ void __noreturn load_and_boot_kernel(bool is_reboot) * as possible to avoid delay. */ occ_pstates_init(); + occ_sensors_init(); /* Use nvram bootargs over device tree */ cmdline = nvram_query("bootargs"); diff --git a/core/sensor.c b/core/sensor.c index cc5341c..b0d3c5e 100644 --- a/core/sensor.c +++ b/core/sensor.c @@ -29,6 +29,10 @@ static int64_t opal_sensor_read(uint32_t sensor_hndl, int token, switch (sensor_get_family(sensor_hndl)) { case SENSOR_DTS: return dts_sensor_read(sensor_hndl, sensor_data); + case SENSOR_OCC: + return occ_sensor_read(sensor_hndl, sensor_data); + default: + break; } if (platform.sensor_read) diff --git a/hw/Makefile.inc b/hw/Makefile.inc index f22f0e8..f0fb4ad 100644 --- a/hw/Makefile.inc +++ b/hw/Makefile.inc @@ -7,7 +7,7 @@ HW_OBJS += p7ioc.o p7ioc-inits.o p7ioc-phb.o HW_OBJS += phb3.o sfc-ctrl.o fake-rtc.o bt.o p8-i2c.o prd.o HW_OBJS += dts.o lpc-rtc.o npu.o npu-hw-procedures.o xive.o phb4.o HW_OBJS += fake-nvram.o lpc-mbox.o npu2.o npu2-hw-procedures.o -HW_OBJS += phys-map.o sbe-p9.o capp.o +HW_OBJS += phys-map.o sbe-p9.o capp.o occ-sensor.o HW=hw/built-in.o # FIXME hack this for now diff --git a/hw/occ-sensor.c b/hw/occ-sensor.c new file mode 100644 index 0000000..7cee9a9 --- /dev/null +++ b/hw/occ-sensor.c @@ -0,0 +1,596 @@ +/* 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> + +/* + * 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_MAX, + SENSOR_MIN, + 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; + 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_MAX: + *data = sensor->sample_max; + break; + case SENSOR_MIN: + *data = sensor->sample_min; + 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; + + 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; + 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; + + for_each_chip(chip) { + struct occ_sensor_data_header *hb; + struct occ_sensor_name *md; + + 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; + + for (i = 0; i < hb->nr_sensors; i++) { + char name[30]; + const char *type, *loc; + struct dt_node *node; + u32 handler; + + if (!(md[i].type & HWMON_SENSORS_MASK)) + 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_MAX); + dt_add_property_cells(node, "sensor-data-max", handler); + + handler = sensor_handler(occ_num, i, SENSOR_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); + } + occ_num++; + } +} diff --git a/include/sensor.h b/include/sensor.h index 7eb3fa5..445a6bc 100644 --- a/include/sensor.h +++ b/include/sensor.h @@ -53,6 +53,7 @@ */ enum { SENSOR_FSP = 0, + SENSOR_OCC = 6, SENSOR_DTS = 7, }; diff --git a/include/skiboot.h b/include/skiboot.h index 1a153b0..db4ca36 100644 --- a/include/skiboot.h +++ b/include/skiboot.h @@ -310,4 +310,8 @@ extern int fake_nvram_info(uint32_t *total_size); extern int fake_nvram_start_read(void *dst, uint32_t src, uint32_t len); extern int fake_nvram_write(uint32_t offset, void *src, uint32_t size); +/* OCC Inband Sensors */ +extern void occ_sensors_init(void); +extern int occ_sensor_read(u32 handle, u32 *data); + #endif /* __SKIBOOT_H */ |