# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

description     "Temporary, quick-hack metrics collection & thermal daemon"
author          "chromium-os-dev@chromium.org"

# This is for quickly adding UMA stats that we may need for
# short-term experiments, when we don't have the time to add
# stuff to metrics_daemon.  That's where it should go in the
# long term.
#
# This is also currently doing a userland thermal loop to allow
# for quick experimentation.  This thermal loop will eventually
# move to the BIOS once the data from experiments help prove its
# efficacy.

start on started system-services
stop on stopping system-services
respawn

script
  TEMP_OFFSET=273  # difference between K (reported by EC) and C (used in UMA)

  # Thermal loop fields
  CPU_MAX_FREQ_FIELD=1
  CPU_MIN_FREQ_FIELD=2
  GPU_MAX_FREQ_FIELD=3
  CPU_DUTY_CYCLE_FIELD=4
  PKG_POWER_LIMIT_FIELD=5

  # Thermal loop steps
  all_steps="
  1801000 800000 1150 0 0x180aa00dd8088 # no throttling
  1801000 800000 1150 0 0x180aa00dd8080 # cap pkg to 16W
  1801000 800000 1150 0 0x180aa00dd8078 # cap pkg to 15W
  1801000 800000 1150 0 0x180aa00dd8070 # cap pkg to 14W
  1801000 800000 1150 0 0x180aa00dd8068 # cap pkg to 13W
  1800000 800000 900 0 0x180aa00dd8068 # disable turbo
  1600000 800000 800 0 0x180aa00dd8068 # cap CPU & GPU frequency
  1400000 800000 700 0 0x180aa00dd8068 # cap CPU & GPU frequency
  1200000 800000 600 0 0x180aa00dd8068 # cap CPU & GPU frequency
  1000000 800000 500 0 0x180aa00dd8068 # cap CPU & GPU frequency
  800000 800000 400 0 0x180aa00dd8068 # cap CPU & GPU frequency
  800000 800000 350 0 0x180aa00dd8068 # cap CPU & GPU frequency
  800000 800000 350 0x1c 0x180aa00dd8068 # duty cycle CPU
  800000 800000 350 0x18 0x180aa00dd8068 # duty cycle CPU
  "
  max_steps=$(($(echo "$all_steps" | wc -l) - 3))

  get_step() {
    row=$(($1 + 2))
    out=$(echo "$all_steps" | awk "{if (NR==$row) print}")
    echo "$out"
  }

  get_field() {
    out=$(echo "$2" | awk "{print \$$1}")
    echo $out
  }

  get_peci_temp() {
    tempk=$(ectool temps 9 | sed 's/[^0-9]//g')
    tempc=$((tempk - $TEMP_OFFSET))
    echo $tempc
  }

  get_sensor_temp() {
    s=$1
    tempc=0
    if out=$(ectool temps $s); then
       tempk=$(echo $out | sed 's/[^0-9]//g')
       tempc=$((tempk - $TEMP_OFFSET))
    fi
    echo $tempc
  }

  get_sensor_list() {
    # USB C-Object: 1 or 13
    # PCH D-Object: 3
    # Hinge C-Object: 5 or 15
    # Charger D-Object: 7
    if ectool tempsinfo 1 | grep -q "USB C-Object"; then
      usb_c_object=1
    else
      usb_c_object=13
    fi
    charger_d_object=7
    echo $usb_c_object $charger_d_object
  }

  set_calibration_data() {
    B0='-2.94e-5'
    B1='-5.7e-7'
    B2='4.63e-9'

    USB_C_S0='2.712e-14'
    PCH_D_S0='9.301e-14'
    HINGE_C_S0='-11.000e-14'
    CHARGER_D_S0='5.141e-14'

    # Note that the sensor numbering is different between the ectool tmp006
    # and temps/tempsinfo commands.
    USB_C="0 $USB_C_S0 $B0 $B1 $B2"
    PCH_D="1 $PCH_D_S0 $B0 $B1 $B2"
    HINGE_C="2 $HINGE_C_S0 $B0 $B1 $B2"
    CHARGER_D="3 $CHARGER_D_S0 $B0 $B1 $B2"

    for i in "$USB_C" "$PCH_D" "$HINGE_C" "$CHARGER_D"; do
      # Add "--" otherwise ectool will barf when trying to parse negative
      # coefficients.
      ectool tmp006cal -- $i
    done
  }

  max_skin_temp=0
  sensor_temperatures=

  get_max_skin_temp() {
    sensor_temperatures=
    max_skin_temp=0
    for i in $*; do
      t=$(get_sensor_temp $i)
      sensor_temperatures=$sensor_temperatures$i:$t:
      if [ $t -gt $max_skin_temp ]; then
        max_skin_temp=$t
      fi
    done

    # Record the PECI CPU temperature also.
    i=9
    t=$(get_sensor_temp $i)
    sensor_temperatures=$sensor_temperatures$i:$t:
  }

  set_cpu_freq() {
    max_freq=$1
    min_freq=$2
    for cpu in /sys/devices/system/cpu/cpu?/cpufreq; do
      echo 800000 > $cpu/scaling_min_freq
      echo 800000 > $cpu/scaling_max_freq
      echo $max_freq > $cpu/scaling_max_freq
      echo $min_freq > $cpu/scaling_min_freq
    done
  }

  set_gpu_min_freq() {
    GPU_MIN_FREQ=450
    echo $GPU_MIN_FREQ > /sys/kernel/debug/dri/0/i915_min_freq
  }

  set_gpu_max_freq() {
    gpu_max_freq=$1
    if [ $GPU_MIN_FREQ -gt $gpu_max_freq ]; then
      gpu_max_freq=$GPU_MIN_FREQ
    fi
    echo $gpu_max_freq > /sys/kernel/debug/dri/0/i915_max_freq
  }

  set_duty_cycle() {
    duty_cycle=$1
    for i in 0 1 2 3; do
      iotools wrmsr $i 0x19a $duty_cycle
    done
  }

  set_pkg_power_limit() {
    pwr_limit=$1
    iotools wrmsr 0 0x610 $pwr_limit
  }

  log_message() {
    logger -t temp_metrics "$*"
  }

  TEMP_THRESHOLD_1=38
  TEMP_THRESHOLD_1_WM=40
  TEMP_THRESHOLD_2=45
  TEMP_THRESHOLD_2_WM=47
  TEMP_THRESHOLD_3=50
  TEMP_THRESHOLD_3_WM=50

  TEMP_THRESHOLD_0_MIN_STEP=0
  TEMP_THRESHOLD_0_MAX_STEP=0
  TEMP_THRESHOLD_1_MIN_STEP=1
  TEMP_THRESHOLD_1_MAX_STEP=5
  TEMP_THRESHOLD_2_MIN_STEP=6
  TEMP_THRESHOLD_2_MAX_STEP=9
  TEMP_THRESHOLD_3_MIN_STEP=10
  TEMP_THRESHOLD_3_MAX_STEP=13

  current_step=1
  new_step=0

  thermal_loop() {
    # Hack to reset turbo activation threshold since BIOS can change it
    # underneath us.
    iotools wrmsr 0 0x64c 0x12

    skin_temp=$1
    if [ $skin_temp -gt $TEMP_THRESHOLD_3 ]; then
      temp_watermark=$TEMP_THRESHOLD_3_WM
      min_step=$TEMP_THRESHOLD_3_MIN_STEP
      max_step=$TEMP_THRESHOLD_3_MAX_STEP
    elif [ $skin_temp -gt $TEMP_THRESHOLD_2 ]; then
      temp_watermark=$TEMP_THRESHOLD_2_WM
      min_step=$TEMP_THRESHOLD_2_MIN_STEP
      max_step=$TEMP_THRESHOLD_2_MAX_STEP
    elif [ $skin_temp -gt $TEMP_THRESHOLD_1 ]; then
      temp_watermark=$TEMP_THRESHOLD_1_WM
      min_step=$TEMP_THRESHOLD_1_MIN_STEP
      max_step=$TEMP_THRESHOLD_1_MAX_STEP
    else
      temp_watermark=0
      min_step=$TEMP_THRESHOLD_0_MIN_STEP
      max_step=$TEMP_THRESHOLD_0_MAX_STEP
    fi

    if [ $skin_temp -gt $temp_watermark ]; then
      if [ $current_step -ne $max_step ]; then
        new_step=$(($current_step + 1))
      fi
    elif [ $skin_temp -lt $temp_watermark ]; then
      if [ $current_step -gt $min_step ]; then
        new_step=$(($current_step - 1))
      fi
    else
      new_step=$current_step
    fi

    if [ $new_step -gt $max_step ]; then
        new_step=$max_step
    elif [ $new_step -lt $min_step ]; then
        new_step=$min_step
    fi

    if  [ $new_step -eq $current_step ]; then
      return
    fi

    current_step=$new_step
    step=$(get_step $new_step)

    log_message "Throttling (temps: $sensor_temperatures):" $step

    cpu_max_freq=$(get_field $CPU_MAX_FREQ_FIELD "$step")
    cpu_min_freq=$(get_field $CPU_MIN_FREQ_FIELD "$step")
    gpu_max_freq=$(get_field $GPU_MAX_FREQ_FIELD "$step")
    cpu_duty_cycle=$(get_field $CPU_DUTY_CYCLE_FIELD "$step")
    pkg_power_limit=$(get_field $PKG_POWER_LIMIT_FIELD "$step")

    set_cpu_freq $cpu_max_freq $cpu_min_freq
    set_gpu_max_freq $gpu_max_freq
    set_duty_cycle $cpu_duty_cycle
    set_pkg_power_limit $pkg_power_limit
  }

  get_fan_rpm() {
    echo $(ectool pwmgetfanrpm | sed 's/[^0-9]//g')
  }

  set_fan_rpm() {
    ectool pwmsetfanrpm $1
  }

  reset_fan_thresholds() {
    temp_low1=105
    temp_low2=105
    temp_low3=105
    temp_low4=105
    temp_low5=105
    temp_low6=105
  }

  last_rpm=10
  temp_low1=105
  temp_low2=105
  temp_low3=105
  temp_low4=105
  temp_low5=105
  temp_low6=105

  fan_loop() {
    skin_temp=$1

    if [ $skin_temp -gt 48 ] || [ $skin_temp -gt $temp_low1 ]; then
      rpm=9300
      reset_fan_thresholds
      temp_low1=46
    elif [ $skin_temp -gt 44 ] || [ $skin_temp -gt $temp_low2 ]; then
      rpm=8000
      reset_fan_thresholds
      temp_low2=43
    elif [ $skin_temp -gt 42 ] || [ $skin_temp -gt $temp_low3 ]; then
      rpm=7000
      reset_fan_thresholds
      temp_low3=41
    elif [ $skin_temp -gt 40 ] || [ $skin_temp -gt $temp_low4 ]; then
      rpm=5500
      reset_fan_thresholds
      temp_low4=39
    elif [ $skin_temp -gt 38 ] || [ $skin_temp -gt $temp_low5 ]; then
      rpm=4000
      reset_fan_thresholds
      temp_low5=34
    elif [ $skin_temp -gt 33 ] || [ $skin_temp -gt $temp_low6 ]; then
      rpm=3000
      reset_fan_thresholds
      temp_low6=30
    else
      rpm=0
      reset_fan_thresholds
    fi

    # During S0->S3->S0 transitions, the EC sets the fan RPM to 0. This script
    # isn't aware of such transitions. Read the current fan RPM again to see
    # if it got set to 0. Note that comparing the current fan RPM against last
    # requested RPM won't suffice since the actual fan RPM may not be exactly
    # what was requested.
    cur_rpm=$(get_fan_rpm)
    if ([ $cur_rpm -ne 0 ] && [ $last_rpm -eq $rpm ]) || \
       ([ $cur_rpm -eq 0 ] && [ $rpm -eq 0 ]); then
      last_rpm=$rpm
      return
    fi

    log_message "Setting fan RPM (temps: $sensor_temperatures): $last_rpm -> $rpm"

    last_rpm=$rpm
    set_fan_rpm $rpm
  }

  # Thermal zone 1 is for operating systems where a userland thermal loop
  # doesn't exist. Disable it.
  if [ -e /sys/class/thermal/thermal_zone1/mode ]; then
    echo -n 'disabled' > /sys/class/thermal/thermal_zone1/mode
  fi

  # Get list of sensors to monitor.
  sensor_list=$(get_sensor_list)

  # Set sensor calibration data.
  set_calibration_data

  # Set minimum GPU frequency.
  set_gpu_min_freq

  loop_count=0
  ec_fan_loop=0

  while true; do
    sleep 10
    loop_count=$(($loop_count + 1))

    # Read the max skin temperature.
    get_max_skin_temp $sensor_list

    if [ $max_skin_temp -eq 0 ]; then
      if [ $ec_fan_loop -eq 0 ]; then
        log_message "Invalid max skin temp. Switching to EC fan loop."
        ectool autofanctrl
        ec_fan_loop=1
        last_rpm=10
      fi
    else
      # Run the fan loop.
      fan_loop $max_skin_temp
      ec_fan_loop=0

      # Run the thermal loop.
      thermal_loop $max_skin_temp
    fi

    # Report the metrics once every 30 seconds.
    if [ $loop_count -lt 3 ]; then
      continue
    fi
    loop_count=0

    ectool temps all | while read line; do
      index=$(printf "%02d" "${line%%:*}")
      tempk="${line##* }"
      tempc=$(($tempk - $TEMP_OFFSET))
      # ignore values below freezing
      if [ $tempc -lt 0 ]; then
        tempc=0
      fi
      # Use a linear histogram with 1 C buckets starting at 0.
      N_SLOTS=180
      metrics_client -e Platform.Temperature.Sensor$index $tempc $N_SLOTS
    done
  done
end script
