summaryrefslogtreecommitdiffstats
path: root/hal/gonk/GonkHal.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'hal/gonk/GonkHal.cpp')
-rw-r--r--hal/gonk/GonkHal.cpp2045
1 files changed, 2045 insertions, 0 deletions
diff --git a/hal/gonk/GonkHal.cpp b/hal/gonk/GonkHal.cpp
new file mode 100644
index 000000000..05d9295a2
--- /dev/null
+++ b/hal/gonk/GonkHal.cpp
@@ -0,0 +1,2045 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * 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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/android_alarm.h>
+#include <math.h>
+#include <regex.h>
+#include <sched.h>
+#include <stdio.h>
+#include <sys/klog.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/resource.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "mozilla/DebugOnly.h"
+
+#include "android/log.h"
+#include "cutils/properties.h"
+#include "hardware/hardware.h"
+#include "hardware/lights.h"
+#include "hardware_legacy/uevent.h"
+#include "hardware_legacy/vibrator.h"
+#include "hardware_legacy/power.h"
+#include "libdisplay/GonkDisplay.h"
+#include "utils/threads.h"
+
+#include "base/message_loop.h"
+#include "base/task.h"
+
+#include "Hal.h"
+#include "HalImpl.h"
+#include "HalLog.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/battery/Constants.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsAlgorithm.h"
+#include "nsPrintfCString.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIRecoveryService.h"
+#include "nsIRunnable.h"
+#include "nsScreenManagerGonk.h"
+#include "nsThreadUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIThread.h"
+#include "nsXULAppAPI.h"
+#include "OrientationObserver.h"
+#include "UeventPoller.h"
+#include "nsIWritablePropertyBag2.h"
+#include <algorithm>
+
+#define NsecPerMsec 1000000LL
+#define NsecPerSec 1000000000
+
+// The header linux/oom.h is not available in bionic libc. We
+// redefine some of its constants here.
+
+#ifndef OOM_DISABLE
+#define OOM_DISABLE (-17)
+#endif
+
+#ifndef OOM_ADJUST_MIN
+#define OOM_ADJUST_MIN (-16)
+#endif
+
+#ifndef OOM_ADJUST_MAX
+#define OOM_ADJUST_MAX 15
+#endif
+
+#ifndef OOM_SCORE_ADJ_MIN
+#define OOM_SCORE_ADJ_MIN (-1000)
+#endif
+
+#ifndef OOM_SCORE_ADJ_MAX
+#define OOM_SCORE_ADJ_MAX 1000
+#endif
+
+#ifndef BATTERY_CHARGING_ARGB
+#define BATTERY_CHARGING_ARGB 0x00FF0000
+#endif
+#ifndef BATTERY_FULL_ARGB
+#define BATTERY_FULL_ARGB 0x0000FF00
+#endif
+
+using namespace mozilla;
+using namespace mozilla::hal;
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace hal_impl {
+
+/**
+ * These are defined by libhardware, specifically, hardware/libhardware/include/hardware/lights.h
+ * in the gonk subsystem.
+ * If these change and are exposed to JS, make sure nsIHal.idl is updated as well.
+ */
+enum LightType {
+ eHalLightID_Backlight = 0,
+ eHalLightID_Keyboard = 1,
+ eHalLightID_Buttons = 2,
+ eHalLightID_Battery = 3,
+ eHalLightID_Notifications = 4,
+ eHalLightID_Attention = 5,
+ eHalLightID_Bluetooth = 6,
+ eHalLightID_Wifi = 7,
+ eHalLightID_Count // This should stay at the end
+};
+enum LightMode {
+ eHalLightMode_User = 0, // brightness is managed by user setting
+ eHalLightMode_Sensor = 1, // brightness is managed by a light sensor
+ eHalLightMode_Count
+};
+enum FlashMode {
+ eHalLightFlash_None = 0,
+ eHalLightFlash_Timed = 1, // timed flashing. Use flashOnMS and flashOffMS for timing
+ eHalLightFlash_Hardware = 2, // hardware assisted flashing
+ eHalLightFlash_Count
+};
+
+struct LightConfiguration {
+ LightType light;
+ LightMode mode;
+ FlashMode flash;
+ uint32_t flashOnMS;
+ uint32_t flashOffMS;
+ uint32_t color;
+};
+
+static light_device_t* sLights[eHalLightID_Count]; // will be initialized to nullptr
+
+static light_device_t*
+GetDevice(hw_module_t* module, char const* name)
+{
+ int err;
+ hw_device_t* device;
+ err = module->methods->open(module, name, &device);
+ if (err == 0) {
+ return (light_device_t*)device;
+ } else {
+ return nullptr;
+ }
+}
+
+static void
+InitLights()
+{
+ // assume that if backlight is nullptr, nothing has been set yet
+ // if this is not true, the initialization will occur everytime a light is read or set!
+ if (!sLights[eHalLightID_Backlight]) {
+ int err;
+ hw_module_t* module;
+
+ err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
+ if (err == 0) {
+ sLights[eHalLightID_Backlight]
+ = GetDevice(module, LIGHT_ID_BACKLIGHT);
+ sLights[eHalLightID_Keyboard]
+ = GetDevice(module, LIGHT_ID_KEYBOARD);
+ sLights[eHalLightID_Buttons]
+ = GetDevice(module, LIGHT_ID_BUTTONS);
+ sLights[eHalLightID_Battery]
+ = GetDevice(module, LIGHT_ID_BATTERY);
+ sLights[eHalLightID_Notifications]
+ = GetDevice(module, LIGHT_ID_NOTIFICATIONS);
+ sLights[eHalLightID_Attention]
+ = GetDevice(module, LIGHT_ID_ATTENTION);
+ sLights[eHalLightID_Bluetooth]
+ = GetDevice(module, LIGHT_ID_BLUETOOTH);
+ sLights[eHalLightID_Wifi]
+ = GetDevice(module, LIGHT_ID_WIFI);
+ }
+ }
+}
+
+/**
+ * The state last set for the lights until liblights supports
+ * getting the light state.
+ */
+static light_state_t sStoredLightState[eHalLightID_Count];
+
+/**
+* Set the value of a light to a particular color, with a specific flash pattern.
+* light specifices which light. See Hal.idl for the list of constants
+* mode specifies user set or based on ambient light sensor
+* flash specifies whether or how to flash the light
+* flashOnMS and flashOffMS specify the pattern for XXX flash mode
+* color specifies the color. If the light doesn't support color, the given color is
+* transformed into a brightness, or just an on/off if that is all the light is capable of.
+* returns true if successful and false if failed.
+*/
+static bool
+SetLight(LightType light, const LightConfiguration& aConfig)
+{
+ light_state_t state;
+
+ InitLights();
+
+ if (light < 0 || light >= eHalLightID_Count ||
+ sLights[light] == nullptr) {
+ return false;
+ }
+
+ memset(&state, 0, sizeof(light_state_t));
+ state.color = aConfig.color;
+ state.flashMode = aConfig.flash;
+ state.flashOnMS = aConfig.flashOnMS;
+ state.flashOffMS = aConfig.flashOffMS;
+ state.brightnessMode = aConfig.mode;
+
+ sLights[light]->set_light(sLights[light], &state);
+ sStoredLightState[light] = state;
+ return true;
+}
+
+/**
+* GET the value of a light returning a particular color, with a specific flash pattern.
+* returns true if successful and false if failed.
+*/
+static bool
+GetLight(LightType light, LightConfiguration* aConfig)
+{
+ light_state_t state;
+
+ if (light < 0 || light >= eHalLightID_Count ||
+ sLights[light] == nullptr) {
+ return false;
+ }
+
+ memset(&state, 0, sizeof(light_state_t));
+ state = sStoredLightState[light];
+
+ aConfig->light = light;
+ aConfig->color = state.color;
+ aConfig->flash = FlashMode(state.flashMode);
+ aConfig->flashOnMS = state.flashOnMS;
+ aConfig->flashOffMS = state.flashOffMS;
+ aConfig->mode = LightMode(state.brightnessMode);
+
+ return true;
+}
+
+namespace {
+
+/**
+ * This runnable runs for the lifetime of the program, once started. It's
+ * responsible for "playing" vibration patterns.
+ */
+class VibratorRunnable final
+ : public nsIRunnable
+ , public nsIObserver
+{
+public:
+ VibratorRunnable()
+ : mMonitor("VibratorRunnable")
+ , mIndex(0)
+ {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (!os) {
+ NS_WARNING("Could not get observer service!");
+ return;
+ }
+
+ os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIOBSERVER
+
+ // Run on the main thread, not the vibrator thread.
+ void Vibrate(const nsTArray<uint32_t> &pattern);
+ void CancelVibrate();
+
+ static bool ShuttingDown() { return sShuttingDown; }
+
+protected:
+ ~VibratorRunnable() {}
+
+private:
+ Monitor mMonitor;
+
+ // The currently-playing pattern.
+ nsTArray<uint32_t> mPattern;
+
+ // The index we're at in the currently-playing pattern. If mIndex >=
+ // mPattern.Length(), then we're not currently playing anything.
+ uint32_t mIndex;
+
+ // Set to true in our shutdown observer. When this is true, we kill the
+ // vibrator thread.
+ static bool sShuttingDown;
+};
+
+NS_IMPL_ISUPPORTS(VibratorRunnable, nsIRunnable, nsIObserver);
+
+bool VibratorRunnable::sShuttingDown = false;
+
+static StaticRefPtr<VibratorRunnable> sVibratorRunnable;
+
+NS_IMETHODIMP
+VibratorRunnable::Run()
+{
+ MonitorAutoLock lock(mMonitor);
+
+ // We currently assume that mMonitor.Wait(X) waits for X milliseconds. But in
+ // reality, the kernel might not switch to this thread for some time after the
+ // wait expires. So there's potential for some inaccuracy here.
+ //
+ // This doesn't worry me too much. Note that we don't even start vibrating
+ // immediately when VibratorRunnable::Vibrate is called -- we go through a
+ // condvar onto another thread. Better just to be chill about small errors in
+ // the timing here.
+
+ while (!sShuttingDown) {
+ if (mIndex < mPattern.Length()) {
+ uint32_t duration = mPattern[mIndex];
+ if (mIndex % 2 == 0) {
+ vibrator_on(duration);
+ }
+ mIndex++;
+ mMonitor.Wait(PR_MillisecondsToInterval(duration));
+ }
+ else {
+ mMonitor.Wait();
+ }
+ }
+ sVibratorRunnable = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+VibratorRunnable::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data)
+{
+ MOZ_ASSERT(strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
+ MonitorAutoLock lock(mMonitor);
+ sShuttingDown = true;
+ mMonitor.Notify();
+
+ return NS_OK;
+}
+
+void
+VibratorRunnable::Vibrate(const nsTArray<uint32_t> &pattern)
+{
+ MonitorAutoLock lock(mMonitor);
+ mPattern = pattern;
+ mIndex = 0;
+ mMonitor.Notify();
+}
+
+void
+VibratorRunnable::CancelVibrate()
+{
+ MonitorAutoLock lock(mMonitor);
+ mPattern.Clear();
+ mPattern.AppendElement(0);
+ mIndex = 0;
+ mMonitor.Notify();
+}
+
+void
+EnsureVibratorThreadInitialized()
+{
+ if (sVibratorRunnable) {
+ return;
+ }
+
+ sVibratorRunnable = new VibratorRunnable();
+ nsCOMPtr<nsIThread> thread;
+ NS_NewThread(getter_AddRefs(thread), sVibratorRunnable);
+}
+
+} // namespace
+
+void
+Vibrate(const nsTArray<uint32_t> &pattern, const hal::WindowIdentifier &)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (VibratorRunnable::ShuttingDown()) {
+ return;
+ }
+ EnsureVibratorThreadInitialized();
+ sVibratorRunnable->Vibrate(pattern);
+}
+
+void
+CancelVibrate(const hal::WindowIdentifier &)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (VibratorRunnable::ShuttingDown()) {
+ return;
+ }
+ EnsureVibratorThreadInitialized();
+ sVibratorRunnable->CancelVibrate();
+}
+
+namespace {
+
+class BatteryUpdater : public Runnable {
+public:
+ NS_IMETHOD Run() override
+ {
+ hal::BatteryInformation info;
+ hal_impl::GetCurrentBatteryInformation(&info);
+
+ // Control the battery indicator (led light) here using BatteryInformation
+ // we just retrieved.
+ uint32_t color = 0; // Format: 0x00rrggbb.
+ if (info.charging() && (info.level() == 1)) {
+ // Charging and battery full.
+ color = BATTERY_FULL_ARGB;
+ } else if (info.charging() && (info.level() < 1)) {
+ // Charging but not full.
+ color = BATTERY_CHARGING_ARGB;
+ } // else turn off battery indicator.
+
+ LightConfiguration aConfig;
+ aConfig.light = eHalLightID_Battery;
+ aConfig.mode = eHalLightMode_User;
+ aConfig.flash = eHalLightFlash_None;
+ aConfig.flashOnMS = aConfig.flashOffMS = 0;
+ aConfig.color = color;
+
+ SetLight(eHalLightID_Battery, aConfig);
+
+ hal::NotifyBatteryChange(info);
+
+ {
+ // bug 975667
+ // Gecko gonk hal is required to emit battery charging/level notification via nsIObserverService.
+ // This is useful for XPCOM components that are not statically linked to Gecko and cannot call
+ // hal::EnableBatteryNotifications
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ nsCOMPtr<nsIWritablePropertyBag2> propbag =
+ do_CreateInstance("@mozilla.org/hash-property-bag;1");
+ if (obsService && propbag) {
+ propbag->SetPropertyAsBool(NS_LITERAL_STRING("charging"),
+ info.charging());
+ propbag->SetPropertyAsDouble(NS_LITERAL_STRING("level"),
+ info.level());
+
+ obsService->NotifyObservers(propbag, "gonkhal-battery-notifier", nullptr);
+ }
+ }
+
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+class BatteryObserver final : public IUeventObserver
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(BatteryObserver)
+
+ BatteryObserver()
+ :mUpdater(new BatteryUpdater())
+ {
+ }
+
+ virtual void Notify(const NetlinkEvent &aEvent)
+ {
+ // this will run on IO thread
+ NetlinkEvent *event = const_cast<NetlinkEvent*>(&aEvent);
+ const char *subsystem = event->getSubsystem();
+ // e.g. DEVPATH=/devices/platform/sec-battery/power_supply/battery
+ const char *devpath = event->findParam("DEVPATH");
+ if (strcmp(subsystem, "power_supply") == 0 &&
+ strstr(devpath, "battery")) {
+ // aEvent will be valid only in this method.
+ NS_DispatchToMainThread(mUpdater);
+ }
+ }
+
+protected:
+ ~BatteryObserver() {}
+
+private:
+ RefPtr<BatteryUpdater> mUpdater;
+};
+
+// sBatteryObserver is owned by the IO thread. Only the IO thread may
+// create or destroy it.
+static StaticRefPtr<BatteryObserver> sBatteryObserver;
+
+static void
+RegisterBatteryObserverIOThread()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(!sBatteryObserver);
+
+ sBatteryObserver = new BatteryObserver();
+ RegisterUeventListener(sBatteryObserver);
+}
+
+void
+EnableBatteryNotifications()
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(RegisterBatteryObserverIOThread));
+}
+
+static void
+UnregisterBatteryObserverIOThread()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(sBatteryObserver);
+
+ UnregisterUeventListener(sBatteryObserver);
+ sBatteryObserver = nullptr;
+}
+
+void
+DisableBatteryNotifications()
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(UnregisterBatteryObserverIOThread));
+}
+
+static bool
+GetCurrentBatteryCharge(int* aCharge)
+{
+ bool success = ReadSysFile("/sys/class/power_supply/battery/capacity",
+ aCharge);
+ if (!success) {
+ return false;
+ }
+
+ #ifdef DEBUG
+ if ((*aCharge < 0) || (*aCharge > 100)) {
+ HAL_LOG("charge level contains unknown value: %d", *aCharge);
+ }
+ #endif
+
+ return (*aCharge >= 0) && (*aCharge <= 100);
+}
+
+static bool
+GetCurrentBatteryCharging(int* aCharging)
+{
+ static const DebugOnly<int> BATTERY_NOT_CHARGING = 0;
+ static const int BATTERY_CHARGING_USB = 1;
+ static const int BATTERY_CHARGING_AC = 2;
+
+ // Generic device support
+
+ int chargingSrc;
+ bool success =
+ ReadSysFile("/sys/class/power_supply/battery/charging_source", &chargingSrc);
+
+ if (success) {
+ #ifdef DEBUG
+ if (chargingSrc != BATTERY_NOT_CHARGING &&
+ chargingSrc != BATTERY_CHARGING_USB &&
+ chargingSrc != BATTERY_CHARGING_AC) {
+ HAL_LOG("charging_source contained unknown value: %d", chargingSrc);
+ }
+ #endif
+
+ *aCharging = (chargingSrc == BATTERY_CHARGING_USB ||
+ chargingSrc == BATTERY_CHARGING_AC);
+ return true;
+ }
+
+ // Otoro device support
+
+ char chargingSrcString[16];
+
+ success = ReadSysFile("/sys/class/power_supply/battery/status",
+ chargingSrcString, sizeof(chargingSrcString));
+ if (success) {
+ *aCharging = strcmp(chargingSrcString, "Charging") == 0 ||
+ strcmp(chargingSrcString, "Full") == 0;
+ return true;
+ }
+
+ return false;
+}
+
+void
+GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo)
+{
+ int charge;
+ static bool previousCharging = false;
+ static double previousLevel = 0.0, remainingTime = 0.0;
+ static struct timespec lastLevelChange;
+ struct timespec now;
+ double dtime, dlevel;
+
+ if (GetCurrentBatteryCharge(&charge)) {
+ aBatteryInfo->level() = (double)charge / 100.0;
+ } else {
+ aBatteryInfo->level() = dom::battery::kDefaultLevel;
+ }
+
+ int charging;
+
+ if (GetCurrentBatteryCharging(&charging)) {
+ aBatteryInfo->charging() = charging;
+ } else {
+ aBatteryInfo->charging() = true;
+ }
+
+ if (aBatteryInfo->charging() != previousCharging){
+ aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime;
+ memset(&lastLevelChange, 0, sizeof(struct timespec));
+ remainingTime = 0.0;
+ }
+
+ if (aBatteryInfo->charging()) {
+ if (aBatteryInfo->level() == 1.0) {
+ aBatteryInfo->remainingTime() = dom::battery::kDefaultRemainingTime;
+ } else if (aBatteryInfo->level() != previousLevel){
+ if (lastLevelChange.tv_sec != 0) {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ dtime = now.tv_sec - lastLevelChange.tv_sec;
+ dlevel = aBatteryInfo->level() - previousLevel;
+
+ if (dlevel <= 0.0) {
+ aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime;
+ } else {
+ remainingTime = (double) round(dtime / dlevel * (1.0 - aBatteryInfo->level()));
+ aBatteryInfo->remainingTime() = remainingTime;
+ }
+
+ lastLevelChange = now;
+ } else { // lastLevelChange.tv_sec == 0
+ clock_gettime(CLOCK_MONOTONIC, &lastLevelChange);
+ aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime;
+ }
+
+ } else {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ dtime = now.tv_sec - lastLevelChange.tv_sec;
+ if (dtime < remainingTime) {
+ aBatteryInfo->remainingTime() = round(remainingTime - dtime);
+ } else {
+ aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime;
+ }
+
+ }
+
+ } else {
+ if (aBatteryInfo->level() == 0.0) {
+ aBatteryInfo->remainingTime() = dom::battery::kDefaultRemainingTime;
+ } else if (aBatteryInfo->level() != previousLevel){
+ if (lastLevelChange.tv_sec != 0) {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ dtime = now.tv_sec - lastLevelChange.tv_sec;
+ dlevel = previousLevel - aBatteryInfo->level();
+
+ if (dlevel <= 0.0) {
+ aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime;
+ } else {
+ remainingTime = (double) round(dtime / dlevel * aBatteryInfo->level());
+ aBatteryInfo->remainingTime() = remainingTime;
+ }
+
+ lastLevelChange = now;
+ } else { // lastLevelChange.tv_sec == 0
+ clock_gettime(CLOCK_MONOTONIC, &lastLevelChange);
+ aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime;
+ }
+
+ } else {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ dtime = now.tv_sec - lastLevelChange.tv_sec;
+ if (dtime < remainingTime) {
+ aBatteryInfo->remainingTime() = round(remainingTime - dtime);
+ } else {
+ aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime;
+ }
+
+ }
+ }
+
+ previousCharging = aBatteryInfo->charging();
+ previousLevel = aBatteryInfo->level();
+}
+
+namespace {
+
+// We can write to screenEnabledFilename to enable/disable the screen, but when
+// we read, we always get "mem"! So we have to keep track ourselves whether
+// the screen is on or not.
+bool sScreenEnabled = true;
+
+// We can read wakeLockFilename to find out whether the cpu wake lock
+// is already acquired, but reading and parsing it is a lot more work
+// than tracking it ourselves, and it won't be accurate anyway (kernel
+// internal wake locks aren't counted here.)
+bool sCpuSleepAllowed = true;
+
+// Some CPU wake locks may be acquired internally in HAL. We use a counter to
+// keep track of these needs. Note we have to hold |sInternalLockCpuMutex|
+// when reading or writing this variable to ensure thread-safe.
+int32_t sInternalLockCpuCount = 0;
+
+} // namespace
+
+bool
+GetScreenEnabled()
+{
+ return sScreenEnabled;
+}
+
+void
+SetScreenEnabled(bool aEnabled)
+{
+ GetGonkDisplay()->SetEnabled(aEnabled);
+ sScreenEnabled = aEnabled;
+}
+
+bool
+GetKeyLightEnabled()
+{
+ LightConfiguration config;
+ bool ok = GetLight(eHalLightID_Buttons, &config);
+ if (ok) {
+ return (config.color != 0x00000000);
+ }
+ return false;
+}
+
+void
+SetKeyLightEnabled(bool aEnabled)
+{
+ LightConfiguration config;
+ config.mode = eHalLightMode_User;
+ config.flash = eHalLightFlash_None;
+ config.flashOnMS = config.flashOffMS = 0;
+ config.color = 0x00000000;
+
+ if (aEnabled) {
+ // Convert the value in [0, 1] to an int between 0 and 255 and then convert
+ // it to a color. Note that the high byte is FF, corresponding to the alpha
+ // channel.
+ double brightness = GetScreenBrightness();
+ uint32_t val = static_cast<int>(round(brightness * 255.0));
+ uint32_t color = (0xff<<24) + (val<<16) + (val<<8) + val;
+
+ config.color = color;
+ }
+
+ SetLight(eHalLightID_Buttons, config);
+ SetLight(eHalLightID_Keyboard, config);
+}
+
+double
+GetScreenBrightness()
+{
+ LightConfiguration config;
+ LightType light = eHalLightID_Backlight;
+
+ bool ok = GetLight(light, &config);
+ if (ok) {
+ // backlight is brightness only, so using one of the RGB elements as value.
+ int brightness = config.color & 0xFF;
+ return brightness / 255.0;
+ }
+ // If GetLight fails, it's because the light doesn't exist. So return
+ // a value corresponding to "off".
+ return 0;
+}
+
+void
+SetScreenBrightness(double brightness)
+{
+ // Don't use De Morgan's law to push the ! into this expression; we want to
+ // catch NaN too.
+ if (!(0 <= brightness && brightness <= 1)) {
+ HAL_LOG("SetScreenBrightness: Dropping illegal brightness %f.", brightness);
+ return;
+ }
+
+ // Convert the value in [0, 1] to an int between 0 and 255 and convert to a color
+ // note that the high byte is FF, corresponding to the alpha channel.
+ uint32_t val = static_cast<int>(round(brightness * 255.0));
+ uint32_t color = (0xff<<24) + (val<<16) + (val<<8) + val;
+
+ LightConfiguration config;
+ config.mode = eHalLightMode_User;
+ config.flash = eHalLightFlash_None;
+ config.flashOnMS = config.flashOffMS = 0;
+ config.color = color;
+ SetLight(eHalLightID_Backlight, config);
+ if (GetKeyLightEnabled()) {
+ SetLight(eHalLightID_Buttons, config);
+ SetLight(eHalLightID_Keyboard, config);
+ }
+}
+
+static StaticMutex sInternalLockCpuMutex;
+
+static void
+UpdateCpuSleepState()
+{
+ const char *wakeLockFilename = "/sys/power/wake_lock";
+ const char *wakeUnlockFilename = "/sys/power/wake_unlock";
+
+ sInternalLockCpuMutex.AssertCurrentThreadOwns();
+ bool allowed = sCpuSleepAllowed && !sInternalLockCpuCount;
+ WriteSysFile(allowed ? wakeUnlockFilename : wakeLockFilename, "gecko");
+}
+
+static void
+InternalLockCpu() {
+ StaticMutexAutoLock lock(sInternalLockCpuMutex);
+ ++sInternalLockCpuCount;
+ UpdateCpuSleepState();
+}
+
+static void
+InternalUnlockCpu() {
+ StaticMutexAutoLock lock(sInternalLockCpuMutex);
+ --sInternalLockCpuCount;
+ UpdateCpuSleepState();
+}
+
+bool
+GetCpuSleepAllowed()
+{
+ return sCpuSleepAllowed;
+}
+
+void
+SetCpuSleepAllowed(bool aAllowed)
+{
+ StaticMutexAutoLock lock(sInternalLockCpuMutex);
+ sCpuSleepAllowed = aAllowed;
+ UpdateCpuSleepState();
+}
+
+void
+AdjustSystemClock(int64_t aDeltaMilliseconds)
+{
+ int fd;
+ struct timespec now;
+
+ if (aDeltaMilliseconds == 0) {
+ return;
+ }
+
+ // Preventing context switch before setting system clock
+ sched_yield();
+ clock_gettime(CLOCK_REALTIME, &now);
+ now.tv_sec += (time_t)(aDeltaMilliseconds / 1000LL);
+ now.tv_nsec += (long)((aDeltaMilliseconds % 1000LL) * NsecPerMsec);
+ if (now.tv_nsec >= NsecPerSec) {
+ now.tv_sec += 1;
+ now.tv_nsec -= NsecPerSec;
+ }
+
+ if (now.tv_nsec < 0) {
+ now.tv_nsec += NsecPerSec;
+ now.tv_sec -= 1;
+ }
+
+ do {
+ fd = open("/dev/alarm", O_RDWR);
+ } while (fd == -1 && errno == EINTR);
+ ScopedClose autoClose(fd);
+ if (fd < 0) {
+ HAL_LOG("Failed to open /dev/alarm: %s", strerror(errno));
+ return;
+ }
+
+ if (ioctl(fd, ANDROID_ALARM_SET_RTC, &now) < 0) {
+ HAL_LOG("ANDROID_ALARM_SET_RTC failed: %s", strerror(errno));
+ }
+
+ hal::NotifySystemClockChange(aDeltaMilliseconds);
+}
+
+int32_t
+GetTimezoneOffset()
+{
+ PRExplodedTime prTime;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &prTime);
+
+ // Daylight saving time (DST) will be taken into account.
+ int32_t offset = prTime.tm_params.tp_gmt_offset;
+ offset += prTime.tm_params.tp_dst_offset;
+
+ // Returns the timezone offset relative to UTC in minutes.
+ return -(offset / 60);
+}
+
+static int32_t sKernelTimezoneOffset = 0;
+
+static void
+UpdateKernelTimezone(int32_t timezoneOffset)
+{
+ if (sKernelTimezoneOffset == timezoneOffset) {
+ return;
+ }
+
+ // Tell the kernel about the new time zone as well, so that FAT filesystems
+ // will get local timestamps rather than UTC timestamps.
+ //
+ // We assume that /init.rc has a sysclktz entry so that settimeofday has
+ // already been called once before we call it (there is a side-effect in
+ // the kernel the very first time settimeofday is called where it does some
+ // special processing if you only set the timezone).
+ struct timezone tz;
+ memset(&tz, 0, sizeof(tz));
+ tz.tz_minuteswest = timezoneOffset;
+ settimeofday(nullptr, &tz);
+ sKernelTimezoneOffset = timezoneOffset;
+}
+
+void
+SetTimezone(const nsCString& aTimezoneSpec)
+{
+ if (aTimezoneSpec.Equals(GetTimezone())) {
+ // Even though the timezone hasn't changed, we still need to tell the
+ // kernel what the current timezone is. The timezone is persisted in
+ // a property and doesn't change across reboots, but the kernel still
+ // needs to be updated on every boot.
+ UpdateKernelTimezone(GetTimezoneOffset());
+ return;
+ }
+
+ int32_t oldTimezoneOffsetMinutes = GetTimezoneOffset();
+ property_set("persist.sys.timezone", aTimezoneSpec.get());
+ // This function is automatically called by the other time conversion
+ // functions that depend on the timezone. To be safe, we call it manually.
+ tzset();
+ int32_t newTimezoneOffsetMinutes = GetTimezoneOffset();
+ UpdateKernelTimezone(newTimezoneOffsetMinutes);
+ hal::NotifySystemTimezoneChange(
+ hal::SystemTimezoneChangeInformation(
+ oldTimezoneOffsetMinutes, newTimezoneOffsetMinutes));
+}
+
+nsCString
+GetTimezone()
+{
+ char timezone[32];
+ property_get("persist.sys.timezone", timezone, "");
+ return nsCString(timezone);
+}
+
+void
+EnableSystemClockChangeNotifications()
+{
+}
+
+void
+DisableSystemClockChangeNotifications()
+{
+}
+
+void
+EnableSystemTimezoneChangeNotifications()
+{
+}
+
+void
+DisableSystemTimezoneChangeNotifications()
+{
+}
+
+// Nothing to do here. Gonk widgetry always listens for screen
+// orientation changes.
+void
+EnableScreenConfigurationNotifications()
+{
+}
+
+void
+DisableScreenConfigurationNotifications()
+{
+}
+
+void
+GetCurrentScreenConfiguration(hal::ScreenConfiguration* aScreenConfiguration)
+{
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+ *aScreenConfiguration = screen->GetConfiguration();
+}
+
+bool
+LockScreenOrientation(const dom::ScreenOrientationInternal& aOrientation)
+{
+ return OrientationObserver::GetInstance()->LockScreenOrientation(aOrientation);
+}
+
+void
+UnlockScreenOrientation()
+{
+ OrientationObserver::GetInstance()->UnlockScreenOrientation();
+}
+
+// This thread will wait for the alarm firing by a blocking IO.
+static pthread_t sAlarmFireWatcherThread;
+
+// If |sAlarmData| is non-null, it's owned by the alarm-watcher thread.
+struct AlarmData {
+public:
+ AlarmData(int aFd) : mFd(aFd),
+ mGeneration(sNextGeneration++),
+ mShuttingDown(false) {}
+ ScopedClose mFd;
+ int mGeneration;
+ bool mShuttingDown;
+
+ static int sNextGeneration;
+
+};
+
+int AlarmData::sNextGeneration = 0;
+
+AlarmData* sAlarmData = nullptr;
+
+class AlarmFiredEvent : public Runnable {
+public:
+ AlarmFiredEvent(int aGeneration) : mGeneration(aGeneration) {}
+
+ NS_IMETHOD Run() override {
+ // Guard against spurious notifications caused by an alarm firing
+ // concurrently with it being disabled.
+ if (sAlarmData && !sAlarmData->mShuttingDown &&
+ mGeneration == sAlarmData->mGeneration) {
+ hal::NotifyAlarmFired();
+ }
+ // The fired alarm event has been delivered to the observer (if needed);
+ // we can now release a CPU wake lock.
+ InternalUnlockCpu();
+ return NS_OK;
+ }
+
+private:
+ int mGeneration;
+};
+
+// Runs on alarm-watcher thread.
+static void
+DestroyAlarmData(void* aData)
+{
+ AlarmData* alarmData = static_cast<AlarmData*>(aData);
+ delete alarmData;
+}
+
+// Runs on alarm-watcher thread.
+void ShutDownAlarm(int aSigno)
+{
+ if (aSigno == SIGUSR1 && sAlarmData) {
+ sAlarmData->mShuttingDown = true;
+ }
+ return;
+}
+
+static void*
+WaitForAlarm(void* aData)
+{
+ pthread_cleanup_push(DestroyAlarmData, aData);
+
+ AlarmData* alarmData = static_cast<AlarmData*>(aData);
+
+ while (!alarmData->mShuttingDown) {
+ int alarmTypeFlags = 0;
+
+ // ALARM_WAIT apparently will block even if an alarm hasn't been
+ // programmed, although this behavior doesn't seem to be
+ // documented. We rely on that here to avoid spinning the CPU
+ // while awaiting an alarm to be programmed.
+ do {
+ alarmTypeFlags = ioctl(alarmData->mFd, ANDROID_ALARM_WAIT);
+ } while (alarmTypeFlags < 0 && errno == EINTR &&
+ !alarmData->mShuttingDown);
+
+ if (!alarmData->mShuttingDown && alarmTypeFlags >= 0 &&
+ (alarmTypeFlags & ANDROID_ALARM_RTC_WAKEUP_MASK)) {
+ // To make sure the observer can get the alarm firing notification
+ // *on time* (the system won't sleep during the process in any way),
+ // we need to acquire a CPU wake lock before firing the alarm event.
+ InternalLockCpu();
+ RefPtr<AlarmFiredEvent> event =
+ new AlarmFiredEvent(alarmData->mGeneration);
+ NS_DispatchToMainThread(event);
+ }
+ }
+
+ pthread_cleanup_pop(1);
+ return nullptr;
+}
+
+bool
+EnableAlarm()
+{
+ MOZ_ASSERT(!sAlarmData);
+
+ int alarmFd = open("/dev/alarm", O_RDWR);
+ if (alarmFd < 0) {
+ HAL_LOG("Failed to open alarm device: %s.", strerror(errno));
+ return false;
+ }
+
+ UniquePtr<AlarmData> alarmData = MakeUnique<AlarmData>(alarmFd);
+
+ struct sigaction actions;
+ memset(&actions, 0, sizeof(actions));
+ sigemptyset(&actions.sa_mask);
+ actions.sa_flags = 0;
+ actions.sa_handler = ShutDownAlarm;
+ if (sigaction(SIGUSR1, &actions, nullptr)) {
+ HAL_LOG("Failed to set SIGUSR1 signal for alarm-watcher thread.");
+ return false;
+ }
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ int status = pthread_create(&sAlarmFireWatcherThread, &attr, WaitForAlarm,
+ alarmData.get());
+ if (status) {
+ alarmData.reset();
+ HAL_LOG("Failed to create alarm-watcher thread. Status: %d.", status);
+ return false;
+ }
+
+ pthread_attr_destroy(&attr);
+
+ // The thread owns this now. We only hold a pointer.
+ sAlarmData = alarmData.release();
+ return true;
+}
+
+void
+DisableAlarm()
+{
+ MOZ_ASSERT(sAlarmData);
+
+ // NB: this must happen-before the thread cancellation.
+ sAlarmData = nullptr;
+
+ // The cancel will interrupt the thread and destroy it, freeing the
+ // data pointed at by sAlarmData.
+ DebugOnly<int> err = pthread_kill(sAlarmFireWatcherThread, SIGUSR1);
+ MOZ_ASSERT(!err);
+}
+
+bool
+SetAlarm(int32_t aSeconds, int32_t aNanoseconds)
+{
+ if (!sAlarmData) {
+ HAL_LOG("We should have enabled the alarm.");
+ return false;
+ }
+
+ struct timespec ts;
+ ts.tv_sec = aSeconds;
+ ts.tv_nsec = aNanoseconds;
+
+ // Currently we only support RTC wakeup alarm type.
+ const int result = ioctl(sAlarmData->mFd,
+ ANDROID_ALARM_SET(ANDROID_ALARM_RTC_WAKEUP), &ts);
+
+ if (result < 0) {
+ HAL_LOG("Unable to set alarm: %s.", strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+static int
+OomAdjOfOomScoreAdj(int aOomScoreAdj)
+{
+ // Convert OOM adjustment from the domain of /proc/<pid>/oom_score_adj
+ // to the domain of /proc/<pid>/oom_adj.
+
+ int adj;
+
+ if (aOomScoreAdj < 0) {
+ adj = (OOM_DISABLE * aOomScoreAdj) / OOM_SCORE_ADJ_MIN;
+ } else {
+ adj = (OOM_ADJUST_MAX * aOomScoreAdj) / OOM_SCORE_ADJ_MAX;
+ }
+
+ return adj;
+}
+
+static void
+RoundOomScoreAdjUpWithLRU(int& aOomScoreAdj, uint32_t aLRU)
+{
+ // We want to add minimum value to round OomScoreAdj up according to
+ // the steps by aLRU.
+ aOomScoreAdj +=
+ ceil(((float)OOM_SCORE_ADJ_MAX / OOM_ADJUST_MAX) * aLRU);
+}
+
+#define OOM_LOG(level, args...) __android_log_print(level, "OomLogger", ##args)
+class OomVictimLogger final
+ : public nsIObserver
+{
+public:
+ OomVictimLogger()
+ : mLastLineChecked(-1.0),
+ mRegexes(nullptr)
+ {
+ // Enable timestamps in kernel's printk
+ WriteSysFile("/sys/module/printk/parameters/time", "Y");
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+protected:
+ ~OomVictimLogger() {}
+
+private:
+ double mLastLineChecked;
+ UniqueFreePtr<regex_t> mRegexes;
+};
+NS_IMPL_ISUPPORTS(OomVictimLogger, nsIObserver);
+
+NS_IMETHODIMP
+OomVictimLogger::Observe(
+ nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ nsDependentCString event_type(aTopic);
+ if (!event_type.EqualsLiteral("ipc:content-shutdown")) {
+ return NS_OK;
+ }
+
+ // OOM message finding regexes
+ const char* const regexes_raw[] = {
+ ".*select.*to kill.*",
+ ".*send sigkill to.*",
+ ".*lowmem_shrink.*",
+ ".*[Oo]ut of [Mm]emory.*",
+ ".*[Kk]ill [Pp]rocess.*",
+ ".*[Kk]illed [Pp]rocess.*",
+ ".*oom-killer.*",
+ // The regexes below are for the output of dump_task from oom_kill.c
+ // 1st - title 2nd - body lines (8 ints and a string)
+ // oom_adj and oom_score_adj can be negative
+ "\\[ pid \\] uid tgid total_vm rss cpu oom_adj oom_score_adj name",
+ "\\[.*[0-9][0-9]*\\][ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*.[0-9][0-9]*[ ]*.[0-9][0-9]*.*"
+ };
+ const size_t regex_count = ArrayLength(regexes_raw);
+
+ // Compile our regex just in time
+ if (!mRegexes) {
+ UniqueFreePtr<regex_t> regexes(
+ static_cast<regex_t*>(malloc(sizeof(regex_t) * regex_count))
+ );
+ mRegexes.swap(regexes);
+ for (size_t i = 0; i < regex_count; i++) {
+ int compilation_err =
+ regcomp(&(mRegexes.get()[i]), regexes_raw[i], REG_NOSUB);
+ if (compilation_err) {
+ OOM_LOG(ANDROID_LOG_ERROR, "Cannot compile regex \"%s\"\n", regexes_raw[i]);
+ return NS_OK;
+ }
+ }
+ }
+
+#ifndef KLOG_SIZE_BUFFER
+ // Upstream bionic in commit
+ // e249b059637b49a285ed9f58a2a18bfd054e5d95
+ // deprecated the old klog defs.
+ // Our current bionic does not hit this
+ // change yet so handle the future change.
+ // (ICS doesn't have KLOG_SIZE_BUFFER but
+ // JB and onwards does.)
+ #define KLOG_SIZE_BUFFER KLOG_WRITE
+#endif
+ // Retreive kernel log
+ int msg_buf_size = klogctl(KLOG_SIZE_BUFFER, NULL, 0);
+ UniqueFreePtr<char> msg_buf(static_cast<char *>(malloc(msg_buf_size + 1)));
+ int read_size = klogctl(KLOG_READ_ALL, msg_buf.get(), msg_buf_size);
+
+ // Turn buffer into cstring
+ read_size = read_size > msg_buf_size ? msg_buf_size : read_size;
+ msg_buf.get()[read_size] = '\0';
+
+ // Foreach line
+ char* line_end;
+ char* line_begin = msg_buf.get();
+ for (; (line_end = strchr(line_begin, '\n')); line_begin = line_end + 1) {
+ // make line into cstring
+ *line_end = '\0';
+
+ // Note: Kernel messages look like:
+ // <5>[63648.286409] sd 35:0:0:0: Attached scsi generic sg1 type 0
+ // 5 is the loging level
+ // [*] is the time timestamp, seconds since boot
+ // last comes the logged message
+
+ // Since the logging level can be a string we must
+ // skip it since scanf lacks wildcard matching
+ char* timestamp_begin = strchr(line_begin, '[');
+ char after_float;
+ double lineTimestamp = -1;
+ bool lineTimestampFound = false;
+ if (timestamp_begin &&
+ // Note: scanf treats a ' ' as [ ]*
+ // Note: scanf treats [ %lf] as [ %lf thus we must check
+ // for the closing bracket outselves.
+ 2 == sscanf(timestamp_begin, "[ %lf%c", &lineTimestamp, &after_float) &&
+ after_float == ']') {
+ if (lineTimestamp <= mLastLineChecked) {
+ continue;
+ }
+
+ lineTimestampFound = true;
+ mLastLineChecked = lineTimestamp;
+ }
+
+ // Log interesting lines
+ for (size_t i = 0; i < regex_count; i++) {
+ int matching = !regexec(&(mRegexes.get()[i]), line_begin, 0, NULL, 0);
+ if (matching) {
+ // Log content of kernel message. We try to skip the ], but if for
+ // some reason (most likely due to buffer overflow/wraparound), we
+ // can't find the ] then we just log the entire line.
+ char* endOfTimestamp = strchr(line_begin, ']');
+ if (endOfTimestamp && endOfTimestamp[1] == ' ') {
+ // skip the ] and the space that follows it
+ line_begin = endOfTimestamp + 2;
+ }
+ if (!lineTimestampFound) {
+ OOM_LOG(ANDROID_LOG_WARN, "following kill message may be a duplicate");
+ }
+ OOM_LOG(ANDROID_LOG_ERROR, "[Kill]: %s\n", line_begin);
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Wraps a particular ProcessPriority, giving us easy access to the prefs that
+ * are relevant to it.
+ *
+ * Creating a PriorityClass also ensures that the control group is created.
+ */
+class PriorityClass
+{
+public:
+ /**
+ * Create a PriorityClass for the given ProcessPriority. This implicitly
+ * reads the relevant prefs and opens the cgroup.procs file of the relevant
+ * control group caching its file descriptor for later use.
+ */
+ PriorityClass(ProcessPriority aPriority);
+
+ /**
+ * Closes the file descriptor for the cgroup.procs file of the associated
+ * control group.
+ */
+ ~PriorityClass();
+
+ PriorityClass(const PriorityClass& aOther);
+ PriorityClass& operator=(const PriorityClass& aOther);
+
+ ProcessPriority Priority()
+ {
+ return mPriority;
+ }
+
+ int32_t OomScoreAdj()
+ {
+ return clamped<int32_t>(mOomScoreAdj, OOM_SCORE_ADJ_MIN, OOM_SCORE_ADJ_MAX);
+ }
+
+ int32_t KillUnderKB()
+ {
+ return mKillUnderKB;
+ }
+
+ nsCString CGroup()
+ {
+ return mGroup;
+ }
+
+ /**
+ * Adds a process to this priority class, this moves the process' PID into
+ * the associated control group.
+ *
+ * @param aPid The PID of the process to be added.
+ */
+ void AddProcess(int aPid);
+
+private:
+ ProcessPriority mPriority;
+ int32_t mOomScoreAdj;
+ int32_t mKillUnderKB;
+ int mCpuCGroupProcsFd;
+ int mMemCGroupProcsFd;
+ nsCString mGroup;
+
+ /**
+ * Return a string that identifies where we can find the value of aPref
+ * that's specific to mPriority. For example, we might return
+ * "hal.processPriorityManager.gonk.FOREGROUND_HIGH.oomScoreAdjust".
+ */
+ nsCString PriorityPrefName(const char* aPref)
+ {
+ return nsPrintfCString("hal.processPriorityManager.gonk.%s.%s",
+ ProcessPriorityToString(mPriority), aPref);
+ }
+
+ /**
+ * Get the full path of the cgroup.procs file associated with the group.
+ */
+ nsCString CpuCGroupProcsFilename()
+ {
+ nsCString cgroupName = mGroup;
+
+ /* If mGroup is empty, our cgroup.procs file is the root procs file,
+ * located at /dev/cpuctl/cgroup.procs. Otherwise our procs file is
+ * /dev/cpuctl/NAME/cgroup.procs. */
+
+ if (!mGroup.IsEmpty()) {
+ cgroupName.AppendLiteral("/");
+ }
+
+ return NS_LITERAL_CSTRING("/dev/cpuctl/") + cgroupName +
+ NS_LITERAL_CSTRING("cgroup.procs");
+ }
+
+ nsCString MemCGroupProcsFilename()
+ {
+ nsCString cgroupName = mGroup;
+
+ /* If mGroup is empty, our cgroup.procs file is the root procs file,
+ * located at /sys/fs/cgroup/memory/cgroup.procs. Otherwise our procs
+ * file is /sys/fs/cgroup/memory/NAME/cgroup.procs. */
+
+ if (!mGroup.IsEmpty()) {
+ cgroupName.AppendLiteral("/");
+ }
+
+ return NS_LITERAL_CSTRING("/sys/fs/cgroup/memory/") + cgroupName +
+ NS_LITERAL_CSTRING("cgroup.procs");
+ }
+
+ int OpenCpuCGroupProcs()
+ {
+ return open(CpuCGroupProcsFilename().get(), O_WRONLY);
+ }
+
+ int OpenMemCGroupProcs()
+ {
+ return open(MemCGroupProcsFilename().get(), O_WRONLY);
+ }
+};
+
+/**
+ * Try to create the cgroup for the given PriorityClass, if it doesn't already
+ * exist. This essentially implements mkdir -p; that is, we create parent
+ * cgroups as necessary. The group parameters are also set according to
+ * the corresponding preferences.
+ *
+ * @param aGroup The name of the group.
+ * @return true if we successfully created the cgroup, or if it already
+ * exists. Otherwise, return false.
+ */
+static bool
+EnsureCpuCGroupExists(const nsACString &aGroup)
+{
+ NS_NAMED_LITERAL_CSTRING(kDevCpuCtl, "/dev/cpuctl/");
+ NS_NAMED_LITERAL_CSTRING(kSlash, "/");
+
+ nsAutoCString groupName(aGroup);
+ HAL_LOG("EnsureCpuCGroupExists for group '%s'", groupName.get());
+
+ nsAutoCString prefPrefix("hal.processPriorityManager.gonk.cgroups.");
+
+ /* If cgroup is not empty, append the cgroup name and a dot to obtain the
+ * group specific preferences. */
+ if (!aGroup.IsEmpty()) {
+ prefPrefix += aGroup + NS_LITERAL_CSTRING(".");
+ }
+
+ nsAutoCString cpuSharesPref(prefPrefix + NS_LITERAL_CSTRING("cpu_shares"));
+ int cpuShares = Preferences::GetInt(cpuSharesPref.get());
+
+ nsAutoCString cpuNotifyOnMigratePref(prefPrefix
+ + NS_LITERAL_CSTRING("cpu_notify_on_migrate"));
+ int cpuNotifyOnMigrate = Preferences::GetInt(cpuNotifyOnMigratePref.get());
+
+ // Create mCGroup and its parent directories, as necessary.
+ nsCString cgroupIter = aGroup + kSlash;
+
+ int32_t offset = 0;
+ while ((offset = cgroupIter.FindChar('/', offset)) != -1) {
+ nsAutoCString path = kDevCpuCtl + Substring(cgroupIter, 0, offset);
+ int rv = mkdir(path.get(), 0744);
+
+ if (rv == -1 && errno != EEXIST) {
+ HAL_LOG("Could not create the %s control group.", path.get());
+ return false;
+ }
+
+ offset++;
+ }
+ HAL_LOG("EnsureCpuCGroupExists created group '%s'", groupName.get());
+
+ nsAutoCString pathPrefix(kDevCpuCtl + aGroup + kSlash);
+ nsAutoCString cpuSharesPath(pathPrefix + NS_LITERAL_CSTRING("cpu.shares"));
+ if (cpuShares && !WriteSysFile(cpuSharesPath.get(),
+ nsPrintfCString("%d", cpuShares).get())) {
+ HAL_LOG("Could not set the cpu share for group %s", cpuSharesPath.get());
+ return false;
+ }
+
+ nsAutoCString notifyOnMigratePath(pathPrefix
+ + NS_LITERAL_CSTRING("cpu.notify_on_migrate"));
+ if (!WriteSysFile(notifyOnMigratePath.get(),
+ nsPrintfCString("%d", cpuNotifyOnMigrate).get())) {
+ HAL_LOG("Could not set the cpu migration notification flag for group %s",
+ notifyOnMigratePath.get());
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+EnsureMemCGroupExists(const nsACString &aGroup)
+{
+ NS_NAMED_LITERAL_CSTRING(kMemCtl, "/sys/fs/cgroup/memory/");
+ NS_NAMED_LITERAL_CSTRING(kSlash, "/");
+
+ nsAutoCString groupName(aGroup);
+ HAL_LOG("EnsureMemCGroupExists for group '%s'", groupName.get());
+
+ nsAutoCString prefPrefix("hal.processPriorityManager.gonk.cgroups.");
+
+ /* If cgroup is not empty, append the cgroup name and a dot to obtain the
+ * group specific preferences. */
+ if (!aGroup.IsEmpty()) {
+ prefPrefix += aGroup + NS_LITERAL_CSTRING(".");
+ }
+
+ nsAutoCString memSwappinessPref(prefPrefix + NS_LITERAL_CSTRING("memory_swappiness"));
+ int memSwappiness = Preferences::GetInt(memSwappinessPref.get());
+
+ // Create mCGroup and its parent directories, as necessary.
+ nsCString cgroupIter = aGroup + kSlash;
+
+ int32_t offset = 0;
+ while ((offset = cgroupIter.FindChar('/', offset)) != -1) {
+ nsAutoCString path = kMemCtl + Substring(cgroupIter, 0, offset);
+ int rv = mkdir(path.get(), 0744);
+
+ if (rv == -1 && errno != EEXIST) {
+ HAL_LOG("Could not create the %s control group.", path.get());
+ return false;
+ }
+
+ offset++;
+ }
+ HAL_LOG("EnsureMemCGroupExists created group '%s'", groupName.get());
+
+ nsAutoCString pathPrefix(kMemCtl + aGroup + kSlash);
+ nsAutoCString memSwappinessPath(pathPrefix + NS_LITERAL_CSTRING("memory.swappiness"));
+ if (!WriteSysFile(memSwappinessPath.get(),
+ nsPrintfCString("%d", memSwappiness).get())) {
+ HAL_LOG("Could not set the memory.swappiness for group %s", memSwappinessPath.get());
+ return false;
+ }
+ HAL_LOG("Set memory.swappiness for group %s to %d", memSwappinessPath.get(), memSwappiness);
+
+ return true;
+}
+
+PriorityClass::PriorityClass(ProcessPriority aPriority)
+ : mPriority(aPriority)
+ , mOomScoreAdj(0)
+ , mKillUnderKB(0)
+ , mCpuCGroupProcsFd(-1)
+ , mMemCGroupProcsFd(-1)
+{
+ DebugOnly<nsresult> rv;
+
+ rv = Preferences::GetInt(PriorityPrefName("OomScoreAdjust").get(),
+ &mOomScoreAdj);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Missing oom_score_adj preference");
+
+ rv = Preferences::GetInt(PriorityPrefName("KillUnderKB").get(),
+ &mKillUnderKB);
+
+ rv = Preferences::GetCString(PriorityPrefName("cgroup").get(), &mGroup);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Missing control group preference");
+
+ if (EnsureCpuCGroupExists(mGroup)) {
+ mCpuCGroupProcsFd = OpenCpuCGroupProcs();
+ }
+ if (EnsureMemCGroupExists(mGroup)) {
+ mMemCGroupProcsFd = OpenMemCGroupProcs();
+ }
+}
+
+PriorityClass::~PriorityClass()
+{
+ if (mCpuCGroupProcsFd != -1) {
+ close(mCpuCGroupProcsFd);
+ }
+ if (mMemCGroupProcsFd != -1) {
+ close(mMemCGroupProcsFd);
+ }
+}
+
+PriorityClass::PriorityClass(const PriorityClass& aOther)
+ : mPriority(aOther.mPriority)
+ , mOomScoreAdj(aOther.mOomScoreAdj)
+ , mKillUnderKB(aOther.mKillUnderKB)
+ , mGroup(aOther.mGroup)
+{
+ mCpuCGroupProcsFd = OpenCpuCGroupProcs();
+ mMemCGroupProcsFd = OpenMemCGroupProcs();
+}
+
+PriorityClass& PriorityClass::operator=(const PriorityClass& aOther)
+{
+ mPriority = aOther.mPriority;
+ mOomScoreAdj = aOther.mOomScoreAdj;
+ mKillUnderKB = aOther.mKillUnderKB;
+ mGroup = aOther.mGroup;
+ mCpuCGroupProcsFd = OpenCpuCGroupProcs();
+ mMemCGroupProcsFd = OpenMemCGroupProcs();
+ return *this;
+}
+
+void PriorityClass::AddProcess(int aPid)
+{
+ if (mCpuCGroupProcsFd >= 0) {
+ nsPrintfCString str("%d", aPid);
+
+ if (write(mCpuCGroupProcsFd, str.get(), strlen(str.get())) < 0) {
+ HAL_ERR("Couldn't add PID %d to the %s cpu control group", aPid, mGroup.get());
+ }
+ }
+ if (mMemCGroupProcsFd >= 0) {
+ nsPrintfCString str("%d", aPid);
+
+ if (write(mMemCGroupProcsFd, str.get(), strlen(str.get())) < 0) {
+ HAL_ERR("Couldn't add PID %d to the %s memory control group", aPid, mGroup.get());
+ }
+ }
+}
+
+/**
+ * Get the PriorityClass associated with the given ProcessPriority.
+ *
+ * If you pass an invalid ProcessPriority value, we return null.
+ *
+ * The pointers returned here are owned by GetPriorityClass (don't free them
+ * yourself). They are guaranteed to stick around until shutdown.
+ */
+PriorityClass*
+GetPriorityClass(ProcessPriority aPriority)
+{
+ static StaticAutoPtr<nsTArray<PriorityClass>> priorityClasses;
+
+ // Initialize priorityClasses if this is the first time we're running this
+ // method.
+ if (!priorityClasses) {
+ priorityClasses = new nsTArray<PriorityClass>();
+ ClearOnShutdown(&priorityClasses);
+
+ for (int32_t i = 0; i < NUM_PROCESS_PRIORITY; i++) {
+ priorityClasses->AppendElement(PriorityClass(ProcessPriority(i)));
+ }
+ }
+
+ if (aPriority < 0 ||
+ static_cast<uint32_t>(aPriority) >= priorityClasses->Length()) {
+ return nullptr;
+ }
+
+ return &(*priorityClasses)[aPriority];
+}
+
+static void
+EnsureKernelLowMemKillerParamsSet()
+{
+ static bool kernelLowMemKillerParamsSet;
+ if (kernelLowMemKillerParamsSet) {
+ return;
+ }
+ kernelLowMemKillerParamsSet = true;
+
+ HAL_LOG("Setting kernel's low-mem killer parameters.");
+
+ // Set /sys/module/lowmemorykiller/parameters/{adj,minfree,notify_trigger}
+ // according to our prefs. These files let us tune when the kernel kills
+ // processes when we're low on memory, and when it notifies us that we're
+ // running low on available memory.
+ //
+ // adj and minfree are both comma-separated lists of integers. If adj="A,B"
+ // and minfree="X,Y", then the kernel will kill processes with oom_adj
+ // A or higher once we have fewer than X pages of memory free, and will kill
+ // processes with oom_adj B or higher once we have fewer than Y pages of
+ // memory free.
+ //
+ // notify_trigger is a single integer. If we set notify_trigger=Z, then
+ // we'll get notified when there are fewer than Z pages of memory free. (See
+ // GonkMemoryPressureMonitoring.cpp.)
+
+ // Build the adj and minfree strings.
+ nsAutoCString adjParams;
+ nsAutoCString minfreeParams;
+
+ DebugOnly<int32_t> lowerBoundOfNextOomScoreAdj = OOM_SCORE_ADJ_MIN - 1;
+ DebugOnly<int32_t> lowerBoundOfNextKillUnderKB = 0;
+ int32_t countOfLowmemorykillerParametersSets = 0;
+
+ long page_size = sysconf(_SC_PAGESIZE);
+
+ for (int i = NUM_PROCESS_PRIORITY - 1; i >= 0; i--) {
+ // The system doesn't function correctly if we're missing these prefs, so
+ // crash loudly.
+
+ PriorityClass* pc = GetPriorityClass(static_cast<ProcessPriority>(i));
+
+ int32_t oomScoreAdj = pc->OomScoreAdj();
+ int32_t killUnderKB = pc->KillUnderKB();
+
+ if (killUnderKB == 0) {
+ // ProcessPriority values like PROCESS_PRIORITY_FOREGROUND_KEYBOARD,
+ // which has only OomScoreAdjust but lacks KillUnderMB value, will not
+ // create new LMK parameters.
+ continue;
+ }
+
+ // The LMK in kernel silently malfunctions if we assign the parameters
+ // in non-increasing order, so we add this assertion here. See bug 887192.
+ MOZ_ASSERT(oomScoreAdj > lowerBoundOfNextOomScoreAdj);
+ MOZ_ASSERT(killUnderKB > lowerBoundOfNextKillUnderKB);
+
+ // The LMK in kernel only accept 6 sets of LMK parameters. See bug 914728.
+ MOZ_ASSERT(countOfLowmemorykillerParametersSets < 6);
+
+ // adj is in oom_adj units.
+ adjParams.AppendPrintf("%d,", OomAdjOfOomScoreAdj(oomScoreAdj));
+
+ // minfree is in pages.
+ minfreeParams.AppendPrintf("%ld,", killUnderKB * 1024 / page_size);
+
+ lowerBoundOfNextOomScoreAdj = oomScoreAdj;
+ lowerBoundOfNextKillUnderKB = killUnderKB;
+ countOfLowmemorykillerParametersSets++;
+ }
+
+ // Strip off trailing commas.
+ adjParams.Cut(adjParams.Length() - 1, 1);
+ minfreeParams.Cut(minfreeParams.Length() - 1, 1);
+ if (!adjParams.IsEmpty() && !minfreeParams.IsEmpty()) {
+ WriteSysFile("/sys/module/lowmemorykiller/parameters/adj", adjParams.get());
+ WriteSysFile("/sys/module/lowmemorykiller/parameters/minfree",
+ minfreeParams.get());
+ }
+
+ // Set the low-memory-notification threshold.
+ int32_t lowMemNotifyThresholdKB;
+ if (NS_SUCCEEDED(Preferences::GetInt(
+ "hal.processPriorityManager.gonk.notifyLowMemUnderKB",
+ &lowMemNotifyThresholdKB))) {
+
+ // notify_trigger is in pages.
+ WriteSysFile("/sys/module/lowmemorykiller/parameters/notify_trigger",
+ nsPrintfCString("%ld", lowMemNotifyThresholdKB * 1024 / page_size).get());
+ }
+
+ // Ensure OOM events appear in logcat
+ RefPtr<OomVictimLogger> oomLogger = new OomVictimLogger();
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->AddObserver(oomLogger, "ipc:content-shutdown", false);
+ }
+}
+
+void
+SetProcessPriority(int aPid, ProcessPriority aPriority, uint32_t aLRU)
+{
+ HAL_LOG("SetProcessPriority(pid=%d, priority=%d, LRU=%u)",
+ aPid, aPriority, aLRU);
+
+ // If this is the first time SetProcessPriority was called, set the kernel's
+ // OOM parameters according to our prefs.
+ //
+ // We could/should do this on startup instead of waiting for the first
+ // SetProcessPriorityCall. But in practice, the master process needs to set
+ // its priority early in the game, so we can reasonably rely on
+ // SetProcessPriority being called early in startup.
+ EnsureKernelLowMemKillerParamsSet();
+
+ PriorityClass* pc = GetPriorityClass(aPriority);
+
+ int oomScoreAdj = pc->OomScoreAdj();
+
+ RoundOomScoreAdjUpWithLRU(oomScoreAdj, aLRU);
+
+ // We try the newer interface first, and fall back to the older interface
+ // on failure.
+ if (!WriteSysFile(nsPrintfCString("/proc/%d/oom_score_adj", aPid).get(),
+ nsPrintfCString("%d", oomScoreAdj).get()))
+ {
+ WriteSysFile(nsPrintfCString("/proc/%d/oom_adj", aPid).get(),
+ nsPrintfCString("%d", OomAdjOfOomScoreAdj(oomScoreAdj)).get());
+ }
+
+ HAL_LOG("Assigning pid %d to cgroup %s", aPid, pc->CGroup().get());
+ pc->AddProcess(aPid);
+}
+
+static bool
+IsValidRealTimePriority(int aValue, int aSchedulePolicy)
+{
+ return (aValue >= sched_get_priority_min(aSchedulePolicy)) &&
+ (aValue <= sched_get_priority_max(aSchedulePolicy));
+}
+
+static void
+SetThreadNiceValue(pid_t aTid, ThreadPriority aThreadPriority, int aValue)
+{
+ MOZ_ASSERT(aThreadPriority < NUM_THREAD_PRIORITY);
+ MOZ_ASSERT(aThreadPriority >= 0);
+
+ HAL_LOG("Setting thread %d to priority level %s; nice level %d",
+ aTid, ThreadPriorityToString(aThreadPriority), aValue);
+ int rv = setpriority(PRIO_PROCESS, aTid, aValue);
+
+ if (rv) {
+ HAL_LOG("Failed to set thread %d to priority level %s; error %s", aTid,
+ ThreadPriorityToString(aThreadPriority), strerror(errno));
+ }
+}
+
+static void
+SetRealTimeThreadPriority(pid_t aTid,
+ ThreadPriority aThreadPriority,
+ int aValue)
+{
+ int policy = SCHED_FIFO;
+
+ MOZ_ASSERT(aThreadPriority < NUM_THREAD_PRIORITY);
+ MOZ_ASSERT(aThreadPriority >= 0);
+ MOZ_ASSERT(IsValidRealTimePriority(aValue, policy), "Invalid real time priority");
+
+ // Setting real time priorities requires using sched_setscheduler
+ HAL_LOG("Setting thread %d to priority level %s; Real Time priority %d, "
+ "Schedule FIFO", aTid, ThreadPriorityToString(aThreadPriority),
+ aValue);
+ sched_param schedParam;
+ schedParam.sched_priority = aValue;
+ int rv = sched_setscheduler(aTid, policy, &schedParam);
+
+ if (rv) {
+ HAL_LOG("Failed to set thread %d to real time priority level %s; error %s",
+ aTid, ThreadPriorityToString(aThreadPriority), strerror(errno));
+ }
+}
+
+/*
+ * Used to store the nice value adjustments and real time priorities for the
+ * various thread priority levels.
+ */
+struct ThreadPriorityPrefs {
+ bool initialized;
+ struct {
+ int nice;
+ int realTime;
+ } priorities[NUM_THREAD_PRIORITY];
+};
+
+/*
+ * Reads the preferences for the various process priority levels and sets up
+ * watchers so that if they're dynamically changed the change is reflected on
+ * the appropriate variables.
+ */
+void
+EnsureThreadPriorityPrefs(ThreadPriorityPrefs* prefs)
+{
+ if (prefs->initialized) {
+ return;
+ }
+
+ for (int i = THREAD_PRIORITY_COMPOSITOR; i < NUM_THREAD_PRIORITY; i++) {
+ ThreadPriority priority = static_cast<ThreadPriority>(i);
+
+ // Read the nice values
+ const char* threadPriorityStr = ThreadPriorityToString(priority);
+ nsPrintfCString niceStr("hal.gonk.%s.nice", threadPriorityStr);
+ Preferences::AddIntVarCache(&prefs->priorities[i].nice, niceStr.get());
+
+ // Read the real-time priorities
+ nsPrintfCString realTimeStr("hal.gonk.%s.rt_priority", threadPriorityStr);
+ Preferences::AddIntVarCache(&prefs->priorities[i].realTime,
+ realTimeStr.get());
+ }
+
+ prefs->initialized = true;
+}
+
+static void
+DoSetThreadPriority(pid_t aTid, hal::ThreadPriority aThreadPriority)
+{
+ // See bug 999115, we can only read preferences on the main thread otherwise
+ // we create a race condition in HAL
+ MOZ_ASSERT(NS_IsMainThread(), "Can only set thread priorities on main thread");
+ MOZ_ASSERT(aThreadPriority >= 0);
+
+ static ThreadPriorityPrefs prefs = { 0 };
+ EnsureThreadPriorityPrefs(&prefs);
+
+ switch (aThreadPriority) {
+ case THREAD_PRIORITY_COMPOSITOR:
+ break;
+ default:
+ HAL_ERR("Unrecognized thread priority %d; Doing nothing",
+ aThreadPriority);
+ return;
+ }
+
+ int realTimePriority = prefs.priorities[aThreadPriority].realTime;
+
+ if (IsValidRealTimePriority(realTimePriority, SCHED_FIFO)) {
+ SetRealTimeThreadPriority(aTid, aThreadPriority, realTimePriority);
+ return;
+ }
+
+ SetThreadNiceValue(aTid, aThreadPriority,
+ prefs.priorities[aThreadPriority].nice);
+}
+
+namespace {
+
+/**
+ * This class sets the priority of threads given the kernel thread's id and a
+ * value taken from hal::ThreadPriority.
+ *
+ * This runnable must always be dispatched to the main thread otherwise it will fail.
+ * We have to run this from the main thread since preferences can only be read on
+ * main thread.
+ */
+class SetThreadPriorityRunnable : public Runnable
+{
+public:
+ SetThreadPriorityRunnable(pid_t aThreadId, hal::ThreadPriority aThreadPriority)
+ : mThreadId(aThreadId)
+ , mThreadPriority(aThreadPriority)
+ { }
+
+ NS_IMETHOD Run() override
+ {
+ NS_ASSERTION(NS_IsMainThread(), "Can only set thread priorities on main thread");
+ hal_impl::DoSetThreadPriority(mThreadId, mThreadPriority);
+ return NS_OK;
+ }
+
+private:
+ pid_t mThreadId;
+ hal::ThreadPriority mThreadPriority;
+};
+
+} // namespace
+
+void
+SetCurrentThreadPriority(ThreadPriority aThreadPriority)
+{
+ pid_t threadId = gettid();
+ hal_impl::SetThreadPriority(threadId, aThreadPriority);
+}
+
+void
+SetThreadPriority(PlatformThreadId aThreadId,
+ ThreadPriority aThreadPriority)
+{
+ switch (aThreadPriority) {
+ case THREAD_PRIORITY_COMPOSITOR: {
+ nsCOMPtr<nsIRunnable> runnable =
+ new SetThreadPriorityRunnable(aThreadId, aThreadPriority);
+ NS_DispatchToMainThread(runnable);
+ break;
+ }
+ default:
+ HAL_LOG("Unrecognized thread priority %d; Doing nothing",
+ aThreadPriority);
+ return;
+ }
+}
+
+void
+FactoryReset(FactoryResetReason& aReason)
+{
+ nsCOMPtr<nsIRecoveryService> recoveryService =
+ do_GetService("@mozilla.org/recovery-service;1");
+ if (!recoveryService) {
+ NS_WARNING("Could not get recovery service!");
+ return;
+ }
+
+ if (aReason == FactoryResetReason::Wipe) {
+ recoveryService->FactoryReset("wipe");
+ } else if (aReason == FactoryResetReason::Root) {
+ recoveryService->FactoryReset("root");
+ } else {
+ recoveryService->FactoryReset("normal");
+ }
+}
+
+} // hal_impl
+} // mozilla