1/*
2 * Copyright 2014 Intel Corporation All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.intel.thermal;
18
19import android.os.UEventObserver;
20import android.util.Log;
21
22import java.util.ArrayList;
23import java.util.Arrays;
24import java.util.Hashtable;
25import java.util.Iterator;
26
27/**
28 * The ThermalZone class contains attributes of a Thermal zone. A Thermal zone
29 * can have one or more sensors associated with it. Whenever the temperature of a
30 * thermal zone crosses the thresholds configured, actions are taken.
31 */
32public class ThermalZone {
33
34    private static final String TAG = "ThermalZone";
35
36    protected int mZoneID;               /* ID of the Thermal zone */
37    protected int mCurrThermalState;     /* Current thermal state of the zone */
38    protected int mCurrEventType;        /* specifies thermal event type, HIGH or LOW */
39    protected String mZoneName;          /* Name of the Thermal zone */
40    protected int mMaxStates;
41    /* List of sensors under this thermal zone */
42    protected ArrayList<ThermalSensor> mThermalSensors = null;
43    // sensor name - sensorAttrib object hash to improve lookup performace
44    // during runtime thermal monitoring like re-programming sensor thresholds
45    // calculating weighted zone temp.
46    protected Hashtable<String, ThermalSensorAttrib> mThermalSensorsAttribMap = null;
47    protected int mZoneTemp;             /* Temperature of the Thermal Zone */
48    protected boolean mSupportsEmulTemp = true;
49    private int mDebounceInterval;    /* Debounce value to avoid thrashing of throttling actions */
50    private Integer mPollDelay[];     /* Delay between sucessive polls in milli seconds */
51    protected boolean mSupportsUEvent;  /* Determines if Sensor supports Uevents */
52    private String mZoneLogic;      /* Logic to be used to determine thermal state of zone */
53    private boolean mIsZoneActive = false;
54    private boolean mMaxThreshExceeded = false;
55    private int mTripMax;
56    private int mOffset = 0;
57    protected Integer mZoneTempThresholds[];  /* Array containing temperature thresholds */
58    // mZoneTempThresholdsRaw contains the Raw thresholds (as specified in xml).
59    // mZoneTempThresholds contsins the calibrated thresholds that are used
60    // to detect zone state change at runtime.
61    protected Integer mZoneTempThresholdsRaw[];
62
63    /* MovingAverage related declarations */
64    private int mRecordedValuesHead = -1; /* Index pointing to the head of past values of sensor */
65    private int mRecordedValues[];        /* Recorded values of sensor */
66    private int mNumberOfInstances[];     /* Number of recorded instances to be considered */
67    private ArrayList<Integer> mWindowList = null;
68    private boolean mIsMovingAverage = false; /* By default false */
69
70    // override this method in ModemZone to limit num states to default
71    public void setMaxStates(int state) {
72        mMaxStates = state;
73    }
74
75    public void updateMaxStates(int state) {
76        setMaxStates(state);
77    }
78
79    public int getMaxStates() {
80        return mMaxStates;
81    }
82
83    public boolean getMovingAverageFlag() {
84        return mIsMovingAverage;
85    }
86
87    public void setMovingAvgWindow(ArrayList<Integer> windowList) {
88        int maxValue = Integer.MIN_VALUE; // -2^31
89
90        if (windowList == null || mPollDelay == null) {
91            Log.i(TAG, "setMovingAvgWindow input is null");
92            mIsMovingAverage = false;
93            return;
94        }
95        mNumberOfInstances = new int[windowList.size()];
96        if (mNumberOfInstances == null) {
97            Log.i(TAG, "failed to create poll windowlist");
98            mIsMovingAverage = false;
99            return;
100        }
101        mIsMovingAverage = true;
102        for (int i = 0; i < windowList.size(); i++) {
103            if (mPollDelay[i] == 0) {
104                mIsMovingAverage = false;
105                Log.i(TAG, "Polling delay is zero, WMA disabled\n");
106                return;
107            }
108            mNumberOfInstances[i] = windowList.get(i) / mPollDelay[i];
109            if (mNumberOfInstances[i] <= 0) {
110                mIsMovingAverage = false;
111                Log.i(TAG, "Polling delay greater than moving average window, WMA disabled\n");
112                return;
113            }
114            maxValue = Math.max(mNumberOfInstances[i], maxValue);
115        }
116        mRecordedValues = new int[maxValue];
117    }
118
119    public int movingAverageTemp() {
120        int index, calIndex;
121        int predictedTemp = 0;
122
123        mRecordedValuesHead = (mRecordedValuesHead + 1) % mRecordedValues.length;
124        mRecordedValues[mRecordedValuesHead] = mZoneTemp;
125
126        // Sensor State starts with -1, InstancesList starts with 0
127        for (index = 0; index < mNumberOfInstances[mCurrThermalState + 1]; index++) {
128            calIndex = mRecordedValuesHead - index;
129            if (calIndex < 0) {
130                calIndex = mRecordedValues.length + calIndex;
131            }
132            predictedTemp += mRecordedValues[calIndex];
133        }
134        return predictedTemp / index;
135    }
136
137    public void printAttrs() {
138        Log.i(TAG, "mZoneID:" + Integer.toString(mZoneID));
139        Log.i(TAG, "mDBInterval: " + Integer.toString(mDebounceInterval));
140        Log.i(TAG, "mZoneName:" + mZoneName);
141        Log.i(TAG, "mSupportsUEvent:" + Boolean.toString(mSupportsUEvent));
142        Log.i(TAG, "mZoneLogic:" + mZoneLogic);
143        Log.i(TAG, "mOffset:" + mOffset);
144        Log.i(TAG, "mPollDelay[]:" + Arrays.toString(mPollDelay));
145        Log.i(TAG, "mZoneTempThresholds[]: " + Arrays.toString(mZoneTempThresholds));
146        Log.i(TAG, "mNumberOfInstances[]: " + Arrays.toString(mNumberOfInstances));
147        Log.i(TAG, "mEmulTempFlag:" + mSupportsEmulTemp);
148        Log.i(TAG, "MaxStates:" + getMaxStates());
149        printStateThresholdMap();
150        printSensors();
151        printSensorAttribList();
152    }
153
154    public void printStateThresholdMap() {
155        if (mZoneTempThresholds == null
156                || mZoneTempThresholds.length < ThermalManager.DEFAULT_NUM_ZONE_STATES) return;
157        StringBuilder s = new StringBuilder();
158        s.append("[" + "State0" + "<" + mZoneTempThresholds[1] + "];");
159        for (int index = 2; index < getMaxStates(); index++) {
160            int curstate = index - 1;
161            s.append("[" + mZoneTempThresholds[index - 1] + "<=" + "State"
162                    + curstate + "<" + mZoneTempThresholds[index] + "];");
163        }
164        Log.i(TAG, "states-threshold map:" + s.toString());
165    }
166
167    private void printSensors() {
168        if (mThermalSensors == null) return;
169        StringBuilder s =  new StringBuilder();
170        for (ThermalSensor ts : mThermalSensors) {
171            if (ts != null) {
172                s.append(ts.getSensorName());
173                s.append(",");
174            }
175        }
176        Log.i(TAG, "zoneID: " + mZoneID + " sensors mapped:" + s.toString());
177    }
178
179    private void printSensorAttribList() {
180        if (mThermalSensorsAttribMap == null) return;
181        Iterator it = (Iterator) mThermalSensorsAttribMap.keySet().iterator();
182        if (it == null) return;
183        ThermalSensorAttrib sensorAttrib = null;
184        while (it.hasNext()) {
185            sensorAttrib = mThermalSensorsAttribMap.get((String) it.next());
186            if (sensorAttrib != null) sensorAttrib.printAttrs();
187        }
188    }
189
190    public ThermalZone() {
191        mCurrThermalState = ThermalManager.THERMAL_STATE_OFF;
192        mZoneTemp = ThermalManager.INVALID_TEMP;
193    }
194
195    public static String getStateAsString(int index) {
196        if (index < -1 || index > 3)
197            return "Invalid";
198        return ThermalManager.STATE_NAMES[index + 1];
199    }
200
201    public static String getEventTypeAsString(int type) {
202        return type == 0 ? "LOW" : "HIGH";
203    }
204
205    public void setSensorList(ArrayList<ThermalSensorAttrib> sensorAtribList) {
206        if (sensorAtribList == null || ThermalManager.sSensorMap == null) return;
207        for (ThermalSensorAttrib sa : sensorAtribList) {
208            // since each object of sensor attrib list is already validated during
209            // parsing it is gauranteed that 'sa != null' and a valid sensor object 's'
210            // will be returned. Hence skipping null check..
211            if (mThermalSensors == null) {
212                // first time allocation
213                mThermalSensors = new ArrayList<ThermalSensor>();
214                if (mThermalSensors == null) {
215                    // allocation failure. return
216                    return;
217                }
218            }
219            if (mThermalSensorsAttribMap == null) {
220                // first time allocation
221                mThermalSensorsAttribMap = new Hashtable<String, ThermalSensorAttrib>();
222                if (mThermalSensorsAttribMap == null) return;
223            }
224            mThermalSensors.add(ThermalManager.getSensor(sa.getSensorName()));
225            mThermalSensorsAttribMap.put(sa.getSensorName(), sa);
226        }
227    }
228
229    public ArrayList<ThermalSensor> getThermalSensorList() {
230        return mThermalSensors;
231    }
232
233    public int getZoneState() {
234        return mCurrThermalState;
235    }
236
237    public void setZoneState(int state) {
238        mCurrThermalState = state;
239    }
240
241    public int getEventType() {
242        return mCurrEventType;
243    }
244
245    public void setEventType(int type) {
246        mCurrEventType = type;
247    }
248
249    public void setZoneTemp(int temp) {
250        mZoneTemp = temp;
251    }
252
253    public int getZoneTemp() {
254        return mZoneTemp;
255    }
256
257    public void setZoneId(int id) {
258        mZoneID = id;
259    }
260
261    public int getZoneId() {
262        return mZoneID;
263    }
264
265    public void setZoneName(String name) {
266        mZoneName = name;
267    }
268
269    public String getZoneName() {
270        return mZoneName;
271    }
272
273    public void setSupportsUEvent(int flag) {
274        mSupportsUEvent = (flag == 1);
275    }
276
277    public boolean isUEventSupported() {
278        return mSupportsUEvent;
279    }
280
281    public boolean isMaxThreshExceed() {
282        return mMaxThreshExceeded;
283    }
284
285    public void setZoneLogic(String type) {
286        mZoneLogic = type;
287    }
288
289    public String getZoneLogic() {
290        return mZoneLogic;
291    }
292
293    public void setDBInterval(int interval) {
294        mDebounceInterval = interval;
295    }
296
297    public int getDBInterval() {
298        return mDebounceInterval;
299    }
300
301    public int getOffset() {
302        return mOffset;
303    }
304
305    public void setOffset(int offset) {
306        mOffset  = offset;
307    }
308
309    public void setPollDelay(ArrayList<Integer> delayList) {
310        if (delayList != null) {
311            mPollDelay = new Integer[delayList.size()];
312            if (mPollDelay != null) {
313                mPollDelay = delayList.toArray(mPollDelay);
314            }
315        }
316    }
317
318    public Integer[] getPollDelay() {
319        return mPollDelay;
320    }
321
322    /**
323     * In polldelay array, index of TOFF = 0, Normal = 1, Warning = 2, Alert =
324     * 3, Critical = 4. Whereas a ThermalZone states are enumerated as TOFF =
325     * -1, Normal = 0, Warning = 1, Alert = 2, Critical = 3. Hence we add 1
326     * while querying poll delay
327     */
328    public int getPollDelay(int index) {
329        index++;
330
331        // If poll delay is requested for an invalid state, return the delay
332        // corresponding to normal state
333        if (index < 0 || index >= mPollDelay.length)
334            index = 1;
335
336        return mPollDelay[index];
337    }
338
339    public void setZoneTempThreshold(ArrayList<Integer> thresholdList) {
340        if (mZoneName.contains("CPU") || mZoneName.contains("SoC"))
341            mTripMax = ThermalManager.sTjMaxTemp;
342        else
343            mTripMax = ThermalManager.sMaxSkinTrip;
344
345        if (thresholdList != null ) {
346            // In Uevent mode, if any threshold specified for a particular
347            // zone exceeds the max threshold temp, we de-activate that zone.
348            if (mSupportsUEvent) {
349                for (int i = 0; i < thresholdList.size(); i++) {
350                    if (thresholdList.get(i) <= mTripMax)
351                        continue;
352                    else
353                        mMaxThreshExceeded = true;
354                }
355            }
356            if (mMaxThreshExceeded == false) {
357                mZoneTempThresholds = new Integer[thresholdList.size()];
358                mZoneTempThresholdsRaw = new Integer[thresholdList.size()];
359                if (mZoneTempThresholds != null) {
360                    mZoneTempThresholds = thresholdList.toArray(mZoneTempThresholds);
361                }
362                if (mZoneTempThresholdsRaw != null) {
363                    mZoneTempThresholdsRaw = thresholdList.toArray(mZoneTempThresholdsRaw);
364                }
365            }
366        }
367    }
368
369    public int getZoneTempThreshold(int index) {
370        if (index < 0 || index >= mZoneTempThresholds.length)
371            return -1;
372        return mZoneTempThresholds[index];
373    }
374
375    public Integer[] getZoneTempThreshold() {
376        return mZoneTempThresholds;
377    }
378
379    public boolean getZoneActiveStatus() {
380        return mIsZoneActive;
381    }
382
383    public void computeZoneActiveStatus() {
384        // init again. needed because of a profile change
385        mIsZoneActive = false;
386        if (mSupportsUEvent) {
387            // Zone de-activated when any threshold for that zone is
388            // above the allowed Max threshold.
389            if (mMaxThreshExceeded == true) {
390                Log.i(TAG, "deactivate zone:" + mZoneName +
391                        ". Zone Threshold exceeds max trip temp:" + mTripMax);
392                mIsZoneActive = false;
393                return;
394            }
395        }
396        if (mZoneTempThresholds == null) {
397            Log.i(TAG, "deactivate zone:" + getZoneName() + " threshold list is NULL! ");
398            mIsZoneActive = false;
399            return;
400        }
401        // 1. minimum number of states supported must be DEFAULT NUM STATES
402        // 2. if sensor list null disable zone
403        if (mMaxStates < ThermalManager.DEFAULT_NUM_ZONE_STATES) {
404            // substract by 1 since TOFF is transparent to USER
405            int minStateSupport = ThermalManager.DEFAULT_NUM_ZONE_STATES - 1;
406            Log.i(TAG, "deactivate zone:" + getZoneName() + " supports < "
407                    + minStateSupport + " states");
408            mIsZoneActive = false;
409            return;
410        }
411        if (mThermalSensors == null) {
412            Log.i(TAG, "deactivate zone:" + getZoneName() + " sensor list null! ");
413            mIsZoneActive = false;
414            return;
415        }
416
417        if (mSupportsUEvent) {
418            // if uevent just check the first sensor
419            ThermalSensor s = mThermalSensors.get(0);
420            if (s != null && s.getSensorActiveStatus()) {
421                mIsZoneActive = true;
422                return;
423            }
424        } else {
425            if (mPollDelay == null) {
426                Log.i(TAG, "deactivate zone:" + getZoneName()
427                        + " polldelay list null in poll mode! ");
428                mIsZoneActive = false;
429                return;
430            }
431            if (mZoneTempThresholds.length != mPollDelay.length) {
432                Log.i(TAG, "deactivate zone:" + getZoneName()
433                        + " mismatch of polldelay and threshold list in polling mode!");
434                mIsZoneActive = false;
435                return;
436            }
437            for (ThermalSensor ts : mThermalSensors) {
438                if (ts != null && ts.getSensorActiveStatus()) {
439                    mIsZoneActive = true;
440                    return;
441                }
442            }
443        }
444    }
445
446    public void setEmulTempFlag(int flag) {
447        mSupportsEmulTemp = (flag == 1);
448    }
449
450    public boolean getEmulTempFlag() {
451        return mSupportsEmulTemp;
452    }
453
454    // override in Specific zone class which inherit ThermalZone
455    public void startMonitoring() {
456    }
457
458    // override in Specific zone class which inherit ThermalZone
459    public void stopMonitoring() {
460    }
461
462    // override in ModemZone to unregister Modem specific intents
463    // override in VirtualThermalZone to stop UEvent observers
464    public void unregisterReceiver() {
465        if (isUEventSupported()) {
466            mUEventObserver.stopObserving();
467        }
468    }
469
470    // override in VirtualThermalZone class
471    public void startEmulTempObserver() {
472    }
473
474    // override in VirtualThermalZone class
475    public void calibrateThresholds() {
476    }
477
478    /**
479     * Function that calculates the state of the Thermal Zone after reading
480     * temperatures of all sensors in the zone. This function is used when a
481     * zone operates in polling mode.
482     */
483    public boolean isZoneStateChanged() {
484        for (int i = 0; i < mThermalSensors.size(); i++) {
485            if (mThermalSensors.get(i).getSensorActiveStatus()) {
486                mThermalSensors.get(i).updateSensorTemp();
487            }
488        }
489        return updateZoneParams();
490    }
491
492    /**
493     * Function that calculates the state of the Thermal Zone after reading
494     * temperatures of all sensors in the zone. This is an overloaded function
495     * used when a zone supports UEvent notifications from kernel. When a
496     * sensor sends an UEvent, it also sends its current temperature as a
497     * parameter of the UEvent.
498     */
499    public boolean isZoneStateChanged(ThermalSensor s, int temp) {
500        if (s == null) return false;
501        s.setCurrTemp(temp);
502        setZoneTemp(temp);
503        return updateZoneParams();
504    }
505
506    /**
507     * Method to update Zone Temperature and Zone Thermal State
508     */
509    public boolean updateZoneParams() {
510        int newZoneState;
511        int prevZoneState = mCurrThermalState;
512
513        if (!updateZoneTemp()) {
514            return false;
515        }
516
517        newZoneState = ThermalUtils.calculateThermalState(mZoneTemp, mZoneTempThresholds);
518        if (newZoneState == prevZoneState) {
519            return false;
520        }
521
522        if (newZoneState == ThermalManager.THERMAL_STATE_OFF) {
523            setZoneState(newZoneState);
524            return true;
525        }
526
527        int threshold = ThermalUtils.getLowerThresholdTemp(prevZoneState, mZoneTempThresholds);
528        // For Interrupt based zones, HW (should) takes care of the debounce.
529        if (!isUEventSupported()) {
530            if (newZoneState < prevZoneState && getZoneTemp() > (threshold - getDBInterval())) {
531                Log.i(TAG, " THERMAL_LOW_EVENT for zone:" + getZoneName()
532                        + " rejected due to debounce interval");
533                return false;
534            }
535        }
536
537        setZoneState(newZoneState);
538        setEventType(newZoneState > prevZoneState
539                ? ThermalManager.THERMAL_HIGH_EVENT
540                : ThermalManager.THERMAL_LOW_EVENT);
541        return true;
542    }
543
544    public boolean updateZoneTemp() {
545        return false;
546    }
547
548    public void registerUevent() {
549        int indx;
550
551        if (mThermalSensors == null) return;
552        if (mThermalSensors.size() > 1) {
553            Log.i(TAG, "for zone:" + getZoneName() + " in uevent mode only first sensor used!");
554        }
555        ThermalSensor sensor = mThermalSensors.get(0);
556        if (sensor == null) return;
557        String path = sensor.getUEventDevPath();
558        if (path.equalsIgnoreCase("invalid")) return;
559        // first time update of sensor temp and zone temp
560        sensor.updateSensorTemp();
561        setZoneTemp(sensor.getCurrTemp());
562        if (updateZoneParams()) {
563            // first intent after initialization
564            sendThermalEvent();
565        }
566        mUEventObserver.startObserving(path);
567        programThresholds(sensor);
568    }
569
570    public UEventObserver mUEventObserver = new UEventObserver() {
571        @Override
572        public void onUEvent(UEventObserver.UEvent event) {
573            String sensorName;
574            int sensorTemp, errorVal, eventType = -1;
575            ThermalZone zone;
576            if (mThermalSensors ==  null) return;
577
578            // Name of the sensor and current temperature are mandatory parameters of an UEvent
579            sensorName = event.get("NAME");
580            sensorTemp = Integer.parseInt(event.get("TEMP"));
581
582            // eventType is an optional parameter. so, check for null case
583            if (event.get("EVENT") != null)
584                eventType = Integer.parseInt(event.get("EVENT"));
585
586            if (sensorName != null) {
587                Log.i(TAG, "UEvent received for sensor:" + sensorName + " temp:" + sensorTemp);
588                // check the name against the first sensor
589                ThermalSensor sensor = mThermalSensors.get(0);
590                if (sensor != null && sensor.getSensorName() != null
591                        && sensor.getSensorName().equalsIgnoreCase(sensorName)) {
592                    // Adjust the sensor temperature based on the 'error correction' temperature.
593                    // For 'LOW' event, debounce interval will take care of this.
594                    errorVal = sensor.getErrorCorrectionTemp();
595                    if (eventType == ThermalManager.THERMAL_HIGH_EVENT)
596                        sensorTemp += errorVal;
597
598                    if (isZoneStateChanged(sensor, sensorTemp)) {
599                        sendThermalEvent();
600                        // reprogram threshold
601                        programThresholds(sensor);
602                    }
603                }
604            }
605        }
606    };
607
608    public void programThresholds(ThermalSensor s) {
609        if (s == null) return;
610        int zoneState = getZoneState();
611        if (zoneState == ThermalManager.THERMAL_STATE_OFF) return;
612        int lowerTripPoint = ThermalUtils.getLowerThresholdTemp(zoneState, getZoneTempThreshold());
613        int upperTripPoint = ThermalUtils.getUpperThresholdTemp(zoneState, getZoneTempThreshold());
614        if (lowerTripPoint != ThermalManager.INVALID_TEMP
615                && upperTripPoint != ThermalManager.INVALID_TEMP) {
616            if (ThermalUtils.writeSysfs(s.getSensorLowTempPath(), lowerTripPoint) == -1) {
617                Log.i(TAG, "error while programming lower trip point:" + lowerTripPoint
618                        + "for sensor:" + s.getSensorName());
619            }
620            if (ThermalUtils.writeSysfs(s.getSensorHighTempPath(), upperTripPoint) == -1) {
621                Log.i(TAG, "error while programming upper trip point:" + upperTripPoint
622                        + "for sensor:" + s.getSensorName());
623            }
624        }
625    }
626
627    public void sendThermalEvent() {
628        ThermalEvent event = new ThermalEvent(mZoneID, mCurrEventType,
629                mCurrThermalState, mZoneTemp, mZoneName,
630                ThermalManager.getCurProfileName());
631        ThermalManager.addThermalEvent(event);
632    }
633}
634