diff options
Diffstat (limited to 'hal/gonk/GonkHal.cpp')
-rw-r--r-- | hal/gonk/GonkHal.cpp | 2045 |
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 |