@@ -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");
@@ -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)
@@ -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
new file mode 100644
@@ -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++;
+ }
+}
@@ -53,6 +53,7 @@
*/
enum {
SENSOR_FSP = 0,
+ SENSOR_OCC = 6,
SENSOR_DTS = 7,
};
@@ -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 */