diff options
Diffstat (limited to 'hal/gonk')
-rw-r--r-- | hal/gonk/GonkDiskSpaceWatcher.cpp | 324 | ||||
-rw-r--r-- | hal/gonk/GonkHal.cpp | 2045 | ||||
-rw-r--r-- | hal/gonk/GonkSensor.cpp | 861 | ||||
-rw-r--r-- | hal/gonk/GonkSensorsHelpers.cpp | 112 | ||||
-rw-r--r-- | hal/gonk/GonkSensorsHelpers.h | 226 | ||||
-rw-r--r-- | hal/gonk/GonkSensorsInterface.cpp | 494 | ||||
-rw-r--r-- | hal/gonk/GonkSensorsInterface.h | 191 | ||||
-rw-r--r-- | hal/gonk/GonkSensorsPollInterface.cpp | 431 | ||||
-rw-r--r-- | hal/gonk/GonkSensorsPollInterface.h | 340 | ||||
-rw-r--r-- | hal/gonk/GonkSensorsRegistryInterface.cpp | 213 | ||||
-rw-r--r-- | hal/gonk/GonkSensorsRegistryInterface.h | 182 | ||||
-rw-r--r-- | hal/gonk/GonkSwitch.cpp | 479 | ||||
-rw-r--r-- | hal/gonk/SensorsTypes.h | 140 | ||||
-rw-r--r-- | hal/gonk/SystemService.cpp | 131 | ||||
-rw-r--r-- | hal/gonk/UeventPoller.cpp | 312 | ||||
-rw-r--r-- | hal/gonk/UeventPoller.h | 49 | ||||
-rw-r--r-- | hal/gonk/fanotify.h | 118 | ||||
-rw-r--r-- | hal/gonk/nsIRecoveryService.idl | 39 | ||||
-rw-r--r-- | hal/gonk/tavarua.h | 484 |
19 files changed, 7171 insertions, 0 deletions
diff --git a/hal/gonk/GonkDiskSpaceWatcher.cpp b/hal/gonk/GonkDiskSpaceWatcher.cpp new file mode 100644 index 0000000000..cdc48ef89a --- /dev/null +++ b/hal/gonk/GonkDiskSpaceWatcher.cpp @@ -0,0 +1,324 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include <sys/syscall.h> +#include <sys/vfs.h> +#include <fcntl.h> +#include <errno.h> +#include "base/message_loop.h" +#include "base/task.h" +#include "DiskSpaceWatcher.h" +#include "fanotify.h" +#include "nsIObserverService.h" +#include "nsIDiskSpaceWatcher.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "mozilla/ModuleUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" + +using namespace mozilla; + +namespace mozilla { namespace hal_impl { class GonkDiskSpaceWatcher; } } + +using namespace mozilla::hal_impl; + +namespace mozilla { +namespace hal_impl { + +// NOTE: this should be unnecessary once we no longer support ICS. +#ifndef __NR_fanotify_init +#if defined(__ARM_EABI__) +#define __NR_fanotify_init 367 +#define __NR_fanotify_mark 368 +#elif defined(__i386__) +#define __NR_fanotify_init 338 +#define __NR_fanotify_mark 339 +#else +#error "Unhandled architecture" +#endif +#endif + +// fanotify_init and fanotify_mark functions are syscalls. +// The user space bits are not part of bionic so we add them here +// as well as fanotify.h +int fanotify_init (unsigned int flags, unsigned int event_f_flags) +{ + return syscall(__NR_fanotify_init, flags, event_f_flags); +} + +// Add, remove, or modify an fanotify mark on a filesystem object. +int fanotify_mark (int fanotify_fd, unsigned int flags, + uint64_t mask, int dfd, const char *pathname) +{ + + // On 32 bits platforms we have to convert the 64 bits mask into + // two 32 bits ints. + if (sizeof(void *) == 4) { + union { + uint64_t _64; + uint32_t _32[2]; + } _mask; + _mask._64 = mask; + return syscall(__NR_fanotify_mark, fanotify_fd, flags, + _mask._32[0], _mask._32[1], dfd, pathname); + } + + return syscall(__NR_fanotify_mark, fanotify_fd, flags, mask, dfd, pathname); +} + +class GonkDiskSpaceWatcher final : public MessageLoopForIO::Watcher +{ +public: + GonkDiskSpaceWatcher(); + ~GonkDiskSpaceWatcher() {}; + + virtual void OnFileCanReadWithoutBlocking(int aFd); + + // We should never write to the fanotify fd. + virtual void OnFileCanWriteWithoutBlocking(int aFd) + { + MOZ_CRASH("Must not write to fanotify fd"); + } + + void DoStart(); + void DoStop(); + +private: + void NotifyUpdate(); + + uint64_t mLowThreshold; + uint64_t mHighThreshold; + TimeDuration mTimeout; + TimeStamp mLastTimestamp; + uint64_t mLastFreeSpace; + uint32_t mSizeDelta; + + bool mIsDiskFull; + uint64_t mFreeSpace; + + int mFd; + MessageLoopForIO::FileDescriptorWatcher mReadWatcher; +}; + +static GonkDiskSpaceWatcher* gHalDiskSpaceWatcher = nullptr; + +#define WATCHER_PREF_LOW "disk_space_watcher.low_threshold" +#define WATCHER_PREF_HIGH "disk_space_watcher.high_threshold" +#define WATCHER_PREF_TIMEOUT "disk_space_watcher.timeout" +#define WATCHER_PREF_SIZE_DELTA "disk_space_watcher.size_delta" + +static const char kWatchedPath[] = "/data"; + +// Helper class to dispatch calls to xpcom on the main thread. +class DiskSpaceNotifier : public Runnable +{ +public: + DiskSpaceNotifier(const bool aIsDiskFull, const uint64_t aFreeSpace) : + mIsDiskFull(aIsDiskFull), + mFreeSpace(aFreeSpace) {} + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + DiskSpaceWatcher::UpdateState(mIsDiskFull, mFreeSpace); + return NS_OK; + } + +private: + bool mIsDiskFull; + uint64_t mFreeSpace; +}; + +// Helper runnable to delete the watcher on the main thread. +class DiskSpaceCleaner : public Runnable +{ +public: + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + if (gHalDiskSpaceWatcher) { + delete gHalDiskSpaceWatcher; + gHalDiskSpaceWatcher = nullptr; + } + return NS_OK; + } +}; + +GonkDiskSpaceWatcher::GonkDiskSpaceWatcher() : + mLastFreeSpace(UINT64_MAX), + mIsDiskFull(false), + mFreeSpace(UINT64_MAX), + mFd(-1) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(gHalDiskSpaceWatcher == nullptr); + + // Default values: 5MB for low threshold, 10MB for high threshold, and + // a timeout of 5 seconds. + mLowThreshold = Preferences::GetInt(WATCHER_PREF_LOW, 5) * 1024 * 1024; + mHighThreshold = Preferences::GetInt(WATCHER_PREF_HIGH, 10) * 1024 * 1024; + mTimeout = TimeDuration::FromSeconds(Preferences::GetInt(WATCHER_PREF_TIMEOUT, 5)); + mSizeDelta = Preferences::GetInt(WATCHER_PREF_SIZE_DELTA, 1) * 1024 * 1024; +} + +void +GonkDiskSpaceWatcher::DoStart() +{ + NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), + "Not on the correct message loop"); + + mFd = fanotify_init(FAN_CLASS_NOTIF, FAN_CLOEXEC | O_LARGEFILE); + if (mFd == -1) { + if (errno == ENOSYS) { + // Don't change these printf_stderr since we need these logs even + // in opt builds. + printf_stderr("Warning: No fanotify support in this device's kernel.\n"); +#if ANDROID_VERSION >= 19 + MOZ_CRASH("Fanotify support must be enabled in the kernel."); +#endif + } else { + printf_stderr("Error calling fanotify_init()"); + } + return; + } + + if (fanotify_mark(mFd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_CLOSE, + 0, kWatchedPath) < 0) { + NS_WARNING("Error calling fanotify_mark"); + close(mFd); + mFd = -1; + return; + } + + if (!MessageLoopForIO::current()->WatchFileDescriptor( + mFd, /* persistent = */ true, + MessageLoopForIO::WATCH_READ, + &mReadWatcher, gHalDiskSpaceWatcher)) { + NS_WARNING("Unable to watch fanotify fd."); + close(mFd); + mFd = -1; + } +} + +void +GonkDiskSpaceWatcher::DoStop() +{ + NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), + "Not on the correct message loop"); + + if (mFd != -1) { + mReadWatcher.StopWatchingFileDescriptor(); + fanotify_mark(mFd, FAN_MARK_FLUSH, 0, 0, kWatchedPath); + close(mFd); + mFd = -1; + } + + // Dispatch the cleanup to the main thread. + nsCOMPtr<nsIRunnable> runnable = new DiskSpaceCleaner(); + NS_DispatchToMainThread(runnable); +} + +// We are called off the main thread, so we proxy first to the main thread +// before calling the xpcom object. +void +GonkDiskSpaceWatcher::NotifyUpdate() +{ + mLastTimestamp = TimeStamp::Now(); + mLastFreeSpace = mFreeSpace; + + nsCOMPtr<nsIRunnable> runnable = + new DiskSpaceNotifier(mIsDiskFull, mFreeSpace); + NS_DispatchToMainThread(runnable); +} + +void +GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd) +{ + struct fanotify_event_metadata* fem = nullptr; + char buf[4096]; + struct statfs sfs; + int32_t len, rc; + + do { + len = read(aFd, buf, sizeof(buf)); + } while(len == -1 && errno == EINTR); + + // Bail out if the file is busy. + if (len < 0 && errno == ETXTBSY) { + return; + } + + // We should get an exact multiple of fanotify_event_metadata + if (len <= 0 || (len % FAN_EVENT_METADATA_LEN != 0)) { + MOZ_CRASH("About to crash: fanotify_event_metadata read error."); + } + + fem = reinterpret_cast<fanotify_event_metadata *>(buf); + + while (FAN_EVENT_OK(fem, len)) { + rc = fstatfs(fem->fd, &sfs); + if (rc < 0) { + NS_WARNING("Unable to stat fan_notify fd"); + } else { + bool firstRun = mFreeSpace == UINT64_MAX; + mFreeSpace = sfs.f_bavail * sfs.f_bsize; + // We change from full <-> free depending on the free space and the + // low and high thresholds. + // Once we are in 'full' mode we send updates for all size changes with + // a minimum of time between messages or when we cross a size change + // threshold. + if (firstRun) { + mIsDiskFull = mFreeSpace <= mLowThreshold; + // Always notify the current state at first run. + NotifyUpdate(); + } else if (!mIsDiskFull && (mFreeSpace <= mLowThreshold)) { + mIsDiskFull = true; + NotifyUpdate(); + } else if (mIsDiskFull && (mFreeSpace > mHighThreshold)) { + mIsDiskFull = false; + NotifyUpdate(); + } else if (mIsDiskFull) { + if (mTimeout < TimeStamp::Now() - mLastTimestamp || + mSizeDelta < llabs(mFreeSpace - mLastFreeSpace)) { + NotifyUpdate(); + } + } + } + close(fem->fd); + fem = FAN_EVENT_NEXT(fem, len); + } +} + +void +StartDiskSpaceWatcher() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Bail out if called several times. + if (gHalDiskSpaceWatcher != nullptr) { + return; + } + + gHalDiskSpaceWatcher = new GonkDiskSpaceWatcher(); + + XRE_GetIOMessageLoop()->PostTask( + NewNonOwningRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStart)); +} + +void +StopDiskSpaceWatcher() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!gHalDiskSpaceWatcher) { + return; + } + + XRE_GetIOMessageLoop()->PostTask( + NewNonOwningRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStop)); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/gonk/GonkHal.cpp b/hal/gonk/GonkHal.cpp new file mode 100644 index 0000000000..05d9295a22 --- /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 diff --git a/hal/gonk/GonkSensor.cpp b/hal/gonk/GonkSensor.cpp new file mode 100644 index 0000000000..7bd2d3c9bf --- /dev/null +++ b/hal/gonk/GonkSensor.cpp @@ -0,0 +1,861 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 <pthread.h> +#include <stdio.h> + +#include "mozilla/DebugOnly.h" +#include "mozilla/Saturate.h" + +#include "base/basictypes.h" +#include "base/thread.h" +#include "base/task.h" + +#include "GonkSensorsInterface.h" +#include "GonkSensorsPollInterface.h" +#include "GonkSensorsRegistryInterface.h" +#include "Hal.h" +#include "HalLog.h" +#include "HalSensor.h" +#include "hardware/sensors.h" +#include "nsThreadUtils.h" + +using namespace mozilla::hal; + +namespace mozilla { + +// +// Internal implementation +// + +// The value from SensorDevice.h (Android) +#define DEFAULT_DEVICE_POLL_RATE 200000000 /*200ms*/ +// ProcessOrientation.cpp needs smaller poll rate to detect delay between +// different orientation angles +#define ACCELEROMETER_POLL_RATE 66667000 /*66.667ms*/ + +// This is present in Android from API level 18 onwards, which is 4.3. We might +// be building on something before 4.3, so use a local define for its value +#define MOZ_SENSOR_TYPE_GAME_ROTATION_VECTOR 15 + +double radToDeg(double a) { + return a * (180.0 / M_PI); +} + +static SensorType +HardwareSensorToHalSensor(int type) +{ + switch(type) { + case SENSOR_TYPE_ORIENTATION: + return SENSOR_ORIENTATION; + case SENSOR_TYPE_ACCELEROMETER: + return SENSOR_ACCELERATION; + case SENSOR_TYPE_PROXIMITY: + return SENSOR_PROXIMITY; + case SENSOR_TYPE_LIGHT: + return SENSOR_LIGHT; + case SENSOR_TYPE_GYROSCOPE: + return SENSOR_GYROSCOPE; + case SENSOR_TYPE_LINEAR_ACCELERATION: + return SENSOR_LINEAR_ACCELERATION; + case SENSOR_TYPE_ROTATION_VECTOR: + return SENSOR_ROTATION_VECTOR; + case MOZ_SENSOR_TYPE_GAME_ROTATION_VECTOR: + return SENSOR_GAME_ROTATION_VECTOR; + default: + return SENSOR_UNKNOWN; + } +} + +static SensorAccuracyType +HardwareStatusToHalAccuracy(int status) { + return static_cast<SensorAccuracyType>(status); +} + +static int +HalSensorToHardwareSensor(SensorType type) +{ + switch(type) { + case SENSOR_ORIENTATION: + return SENSOR_TYPE_ORIENTATION; + case SENSOR_ACCELERATION: + return SENSOR_TYPE_ACCELEROMETER; + case SENSOR_PROXIMITY: + return SENSOR_TYPE_PROXIMITY; + case SENSOR_LIGHT: + return SENSOR_TYPE_LIGHT; + case SENSOR_GYROSCOPE: + return SENSOR_TYPE_GYROSCOPE; + case SENSOR_LINEAR_ACCELERATION: + return SENSOR_TYPE_LINEAR_ACCELERATION; + case SENSOR_ROTATION_VECTOR: + return SENSOR_TYPE_ROTATION_VECTOR; + case SENSOR_GAME_ROTATION_VECTOR: + return MOZ_SENSOR_TYPE_GAME_ROTATION_VECTOR; + default: + return -1; + } +} + +static int +SensorseventStatus(const sensors_event_t& data) +{ + int type = data.type; + switch(type) { + case SENSOR_ORIENTATION: + return data.orientation.status; + case SENSOR_LINEAR_ACCELERATION: + case SENSOR_ACCELERATION: + return data.acceleration.status; + case SENSOR_GYROSCOPE: + return data.gyro.status; + } + + return SENSOR_STATUS_UNRELIABLE; +} + +class SensorRunnable : public Runnable +{ +public: + SensorRunnable(const sensors_event_t& data, const sensor_t* sensors, ssize_t size) + { + mSensorData.sensor() = HardwareSensorToHalSensor(data.type); + mSensorData.accuracy() = HardwareStatusToHalAccuracy(SensorseventStatus(data)); + mSensorData.timestamp() = data.timestamp; + if (mSensorData.sensor() == SENSOR_GYROSCOPE) { + // libhardware returns gyro as rad. convert. + mSensorValues.AppendElement(radToDeg(data.data[0])); + mSensorValues.AppendElement(radToDeg(data.data[1])); + mSensorValues.AppendElement(radToDeg(data.data[2])); + } else if (mSensorData.sensor() == SENSOR_PROXIMITY) { + mSensorValues.AppendElement(data.data[0]); + mSensorValues.AppendElement(0); + + // Determine the maxRange for this sensor. + for (ssize_t i = 0; i < size; i++) { + if (sensors[i].type == SENSOR_TYPE_PROXIMITY) { + mSensorValues.AppendElement(sensors[i].maxRange); + } + } + } else if (mSensorData.sensor() == SENSOR_LIGHT) { + mSensorValues.AppendElement(data.data[0]); + } else if (mSensorData.sensor() == SENSOR_ROTATION_VECTOR) { + mSensorValues.AppendElement(data.data[0]); + mSensorValues.AppendElement(data.data[1]); + mSensorValues.AppendElement(data.data[2]); + if (data.data[3] == 0.0) { + // data.data[3] was optional in Android <= API level 18. It can be computed from 012, + // but it's better to take the actual value if one is provided. The computation is + // v = 1 - d[0]*d[0] - d[1]*d[1] - d[2]*d[2] + // d[3] = v > 0 ? sqrt(v) : 0; + // I'm assuming that it will be 0 if it's not passed in. (The values form a unit + // quaternion, so the angle can be computed from the direction vector.) + float sx = data.data[0], sy = data.data[1], sz = data.data[2]; + float v = 1.0f - sx*sx - sy*sy - sz*sz; + mSensorValues.AppendElement(v > 0.0f ? sqrt(v) : 0.0f); + } else { + mSensorValues.AppendElement(data.data[3]); + } + } else if (mSensorData.sensor() == SENSOR_GAME_ROTATION_VECTOR) { + mSensorValues.AppendElement(data.data[0]); + mSensorValues.AppendElement(data.data[1]); + mSensorValues.AppendElement(data.data[2]); + mSensorValues.AppendElement(data.data[3]); + } else { + mSensorValues.AppendElement(data.data[0]); + mSensorValues.AppendElement(data.data[1]); + mSensorValues.AppendElement(data.data[2]); + } + mSensorData.values() = mSensorValues; + } + + ~SensorRunnable() {} + + NS_IMETHOD Run() override + { + NotifySensorChange(mSensorData); + return NS_OK; + } + +private: + SensorData mSensorData; + AutoTArray<float, 4> mSensorValues; +}; + +namespace hal_impl { + +static DebugOnly<int> sSensorRefCount[NUM_SENSOR_TYPE]; +static base::Thread* sPollingThread; +static sensors_poll_device_t* sSensorDevice; +static sensors_module_t* sSensorModule; + +static void +PollSensors() +{ + const size_t numEventMax = 16; + sensors_event_t buffer[numEventMax]; + const sensor_t* sensors; + int size = sSensorModule->get_sensors_list(sSensorModule, &sensors); + + do { + // didn't check sSensorDevice because already be done on creating pollingThread. + int n = sSensorDevice->poll(sSensorDevice, buffer, numEventMax); + if (n < 0) { + HAL_ERR("Error polling for sensor data (err=%d)", n); + break; + } + + for (int i = 0; i < n; ++i) { + // FIXME: bug 802004, add proper support for the magnetic field sensor. + if (buffer[i].type == SENSOR_TYPE_MAGNETIC_FIELD) + continue; + + // Bug 938035, transfer HAL data for orientation sensor to meet w3c spec + // ex: HAL report alpha=90 means East but alpha=90 means West in w3c spec + if (buffer[i].type == SENSOR_TYPE_ORIENTATION) { + buffer[i].orientation.azimuth = 360 - buffer[i].orientation.azimuth; + buffer[i].orientation.pitch = -buffer[i].orientation.pitch; + buffer[i].orientation.roll = -buffer[i].orientation.roll; + } + + if (HardwareSensorToHalSensor(buffer[i].type) == SENSOR_UNKNOWN) { + // Emulator is broken and gives us events without types set + int index; + for (index = 0; index < size; index++) { + if (sensors[index].handle == buffer[i].sensor) { + break; + } + } + if (index < size && + HardwareSensorToHalSensor(sensors[index].type) != SENSOR_UNKNOWN) { + buffer[i].type = sensors[index].type; + } else { + HAL_LOG("Could not determine sensor type of event"); + continue; + } + } + + NS_DispatchToMainThread(new SensorRunnable(buffer[i], sensors, size)); + } + } while (true); +} + +static void +SwitchSensor(bool aActivate, sensor_t aSensor, pthread_t aThreadId) +{ + int index = HardwareSensorToHalSensor(aSensor.type); + + MOZ_ASSERT(sSensorRefCount[index] || aActivate); + + sSensorDevice->activate(sSensorDevice, aSensor.handle, aActivate); + + if (aActivate) { + if (aSensor.type == SENSOR_TYPE_ACCELEROMETER) { + sSensorDevice->setDelay(sSensorDevice, aSensor.handle, + ACCELEROMETER_POLL_RATE); + } else { + sSensorDevice->setDelay(sSensorDevice, aSensor.handle, + DEFAULT_DEVICE_POLL_RATE); + } + } + + if (aActivate) { + sSensorRefCount[index]++; + } else { + sSensorRefCount[index]--; + } +} + +static void +SetSensorState(SensorType aSensor, bool activate) +{ + int type = HalSensorToHardwareSensor(aSensor); + const sensor_t* sensors = nullptr; + + int size = sSensorModule->get_sensors_list(sSensorModule, &sensors); + for (ssize_t i = 0; i < size; i++) { + if (sensors[i].type == type) { + SwitchSensor(activate, sensors[i], pthread_self()); + break; + } + } +} + +static void +EnableSensorNotificationsInternal(SensorType aSensor) +{ + if (!sSensorModule) { + hw_get_module(SENSORS_HARDWARE_MODULE_ID, + (hw_module_t const**)&sSensorModule); + if (!sSensorModule) { + HAL_ERR("Can't get sensor HAL module\n"); + return; + } + + sensors_open(&sSensorModule->common, &sSensorDevice); + if (!sSensorDevice) { + sSensorModule = nullptr; + HAL_ERR("Can't get sensor poll device from module \n"); + return; + } + + sensor_t const* sensors; + int count = sSensorModule->get_sensors_list(sSensorModule, &sensors); + for (size_t i=0 ; i<size_t(count) ; i++) { + sSensorDevice->activate(sSensorDevice, sensors[i].handle, 0); + } + } + + if (!sPollingThread) { + sPollingThread = new base::Thread("GonkSensors"); + MOZ_ASSERT(sPollingThread); + // sPollingThread never terminates because poll may never return + sPollingThread->Start(); + sPollingThread->message_loop()->PostTask( + NewRunnableFunction(PollSensors)); + } + + SetSensorState(aSensor, true); +} + +static void +DisableSensorNotificationsInternal(SensorType aSensor) +{ + if (!sSensorModule) { + return; + } + SetSensorState(aSensor, false); +} + +// +// Daemon +// + +typedef detail::SaturateOp<uint32_t> SaturateOpUint32; + +/** + * The poll notification handler receives all events about sensors and + * sensor events. + */ +class SensorsPollNotificationHandler final + : public GonkSensorsPollNotificationHandler +{ +public: + SensorsPollNotificationHandler(GonkSensorsPollInterface* aPollInterface) + : mPollInterface(aPollInterface) + { + MOZ_ASSERT(mPollInterface); + + mPollInterface->SetNotificationHandler(this); + } + + void EnableSensorsByType(SensorsType aType) + { + if (SaturateOpUint32(mClasses[aType].mActivated)++) { + return; + } + + SensorsDeliveryMode deliveryMode = DefaultSensorsDeliveryMode(aType); + + // Old ref-count for the sensor type was 0, so we + // activate all sensors of the type. + for (size_t i = 0; i < mSensors.Length(); ++i) { + if (mSensors[i].mType == aType && + mSensors[i].mDeliveryMode == deliveryMode) { + mPollInterface->EnableSensor(mSensors[i].mId, nullptr); + mPollInterface->SetPeriod(mSensors[i].mId, DefaultSensorPeriod(aType), + nullptr); + } + } + } + + void DisableSensorsByType(SensorsType aType) + { + if (SaturateOpUint32(mClasses[aType].mActivated)-- != 1) { + return; + } + + SensorsDeliveryMode deliveryMode = DefaultSensorsDeliveryMode(aType); + + // Old ref-count for the sensor type was 1, so we + // deactivate all sensors of the type. + for (size_t i = 0; i < mSensors.Length(); ++i) { + if (mSensors[i].mType == aType && + mSensors[i].mDeliveryMode == deliveryMode) { + mPollInterface->DisableSensor(mSensors[i].mId, nullptr); + } + } + } + + void ClearSensorClasses() + { + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mClasses); ++i) { + mClasses[i] = SensorsSensorClass(); + } + } + + void ClearSensors() + { + mSensors.Clear(); + } + + // Methods for SensorsPollNotificationHandler + // + + void ErrorNotification(SensorsError aError) override + { + // XXX: Bug 1206056: Try to repair some of the errors or restart cleanly. + } + + void SensorDetectedNotification(int32_t aId, SensorsType aType, + float aRange, float aResolution, + float aPower, int32_t aMinPeriod, + int32_t aMaxPeriod, + SensorsTriggerMode aTriggerMode, + SensorsDeliveryMode aDeliveryMode) override + { + auto i = FindSensorIndexById(aId); + if (i == -1) { + // Add a new sensor... + i = mSensors.Length(); + mSensors.AppendElement(SensorsSensor(aId, aType, aRange, aResolution, + aPower, aMinPeriod, aMaxPeriod, + aTriggerMode, aDeliveryMode)); + } else { + // ...or update an existing one. + mSensors[i] = SensorsSensor(aId, aType, aRange, aResolution, aPower, + aMinPeriod, aMaxPeriod, aTriggerMode, + aDeliveryMode); + } + + mClasses[aType].UpdateFromSensor(mSensors[i]); + + if (mClasses[aType].mActivated && + mSensors[i].mDeliveryMode == DefaultSensorsDeliveryMode(aType)) { + // The new sensor's type is enabled, so enable sensor. + mPollInterface->EnableSensor(aId, nullptr); + mPollInterface->SetPeriod(mSensors[i].mId, DefaultSensorPeriod(aType), + nullptr); + } + } + + void SensorLostNotification(int32_t aId) override + { + auto i = FindSensorIndexById(aId); + if (i != -1) { + mSensors.RemoveElementAt(i); + } + } + + void EventNotification(int32_t aId, const SensorsEvent& aEvent) override + { + auto i = FindSensorIndexById(aId); + if (i == -1) { + HAL_ERR("Sensor %d not registered", aId); + return; + } + + SensorData sensorData; + auto rv = CreateSensorData(aEvent, mClasses[mSensors[i].mType], + sensorData); + if (NS_FAILED(rv)) { + return; + } + + NotifySensorChange(sensorData); + } + +private: + ssize_t FindSensorIndexById(int32_t aId) const + { + for (size_t i = 0; i < mSensors.Length(); ++i) { + if (mSensors[i].mId == aId) { + return i; + } + } + return -1; + } + + uint64_t DefaultSensorPeriod(SensorsType aType) const + { + return aType == SENSORS_TYPE_ACCELEROMETER ? ACCELEROMETER_POLL_RATE + : DEFAULT_DEVICE_POLL_RATE; + } + + SensorsDeliveryMode DefaultSensorsDeliveryMode(SensorsType aType) const + { + if (aType == SENSORS_TYPE_PROXIMITY || + aType == SENSORS_TYPE_SIGNIFICANT_MOTION) { + return SENSORS_DELIVERY_MODE_IMMEDIATE; + } + return SENSORS_DELIVERY_MODE_BEST_EFFORT; + } + + SensorType HardwareSensorToHalSensor(SensorsType aType) const + { + // FIXME: bug 802004, add proper support for the magnetic-field sensor. + switch (aType) { + case SENSORS_TYPE_ORIENTATION: + return SENSOR_ORIENTATION; + case SENSORS_TYPE_ACCELEROMETER: + return SENSOR_ACCELERATION; + case SENSORS_TYPE_PROXIMITY: + return SENSOR_PROXIMITY; + case SENSORS_TYPE_LIGHT: + return SENSOR_LIGHT; + case SENSORS_TYPE_GYROSCOPE: + return SENSOR_GYROSCOPE; + case SENSORS_TYPE_LINEAR_ACCELERATION: + return SENSOR_LINEAR_ACCELERATION; + case SENSORS_TYPE_ROTATION_VECTOR: + return SENSOR_ROTATION_VECTOR; + case SENSORS_TYPE_GAME_ROTATION_VECTOR: + return SENSOR_GAME_ROTATION_VECTOR; + default: + NS_NOTREACHED("Invalid sensors type"); + } + return SENSOR_UNKNOWN; + } + + SensorAccuracyType HardwareStatusToHalAccuracy(SensorsStatus aStatus) const + { + return static_cast<SensorAccuracyType>(aStatus - 1); + } + + nsresult CreateSensorData(const SensorsEvent& aEvent, + const SensorsSensorClass& aSensorClass, + SensorData& aSensorData) const + { + AutoTArray<float, 4> sensorValues; + + auto sensor = HardwareSensorToHalSensor(aEvent.mType); + + if (sensor == SENSOR_UNKNOWN) { + return NS_ERROR_ILLEGAL_VALUE; + } + + aSensorData.sensor() = sensor; + aSensorData.accuracy() = HardwareStatusToHalAccuracy(aEvent.mStatus); + aSensorData.timestamp() = aEvent.mTimestamp; + + if (aSensorData.sensor() == SENSOR_ORIENTATION) { + // Bug 938035: transfer HAL data for orientation sensor to meet W3C spec + // ex: HAL report alpha=90 means East but alpha=90 means West in W3C spec + sensorValues.AppendElement(360.0 - radToDeg(aEvent.mData.mFloat[0])); + sensorValues.AppendElement(-radToDeg(aEvent.mData.mFloat[1])); + sensorValues.AppendElement(-radToDeg(aEvent.mData.mFloat[2])); + } else if (aSensorData.sensor() == SENSOR_ACCELERATION) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + sensorValues.AppendElement(aEvent.mData.mFloat[1]); + sensorValues.AppendElement(aEvent.mData.mFloat[2]); + } else if (aSensorData.sensor() == SENSOR_PROXIMITY) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + sensorValues.AppendElement(aSensorClass.mMinValue); + sensorValues.AppendElement(aSensorClass.mMaxValue); + } else if (aSensorData.sensor() == SENSOR_LINEAR_ACCELERATION) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + sensorValues.AppendElement(aEvent.mData.mFloat[1]); + sensorValues.AppendElement(aEvent.mData.mFloat[2]); + } else if (aSensorData.sensor() == SENSOR_GYROSCOPE) { + sensorValues.AppendElement(radToDeg(aEvent.mData.mFloat[0])); + sensorValues.AppendElement(radToDeg(aEvent.mData.mFloat[1])); + sensorValues.AppendElement(radToDeg(aEvent.mData.mFloat[2])); + } else if (aSensorData.sensor() == SENSOR_LIGHT) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + } else if (aSensorData.sensor() == SENSOR_ROTATION_VECTOR) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + sensorValues.AppendElement(aEvent.mData.mFloat[1]); + sensorValues.AppendElement(aEvent.mData.mFloat[2]); + sensorValues.AppendElement(aEvent.mData.mFloat[3]); + } else if (aSensorData.sensor() == SENSOR_GAME_ROTATION_VECTOR) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + sensorValues.AppendElement(aEvent.mData.mFloat[1]); + sensorValues.AppendElement(aEvent.mData.mFloat[2]); + sensorValues.AppendElement(aEvent.mData.mFloat[3]); + } + + aSensorData.values() = sensorValues; + + return NS_OK; + } + + GonkSensorsPollInterface* mPollInterface; + nsTArray<SensorsSensor> mSensors; + SensorsSensorClass mClasses[SENSORS_NUM_TYPES]; +}; + +static StaticAutoPtr<SensorsPollNotificationHandler> sPollNotificationHandler; + +/** + * This is the notifiaction handler for the Sensors interface. If the backend + * crashes, we can restart it from here. + */ +class SensorsNotificationHandler final : public GonkSensorsNotificationHandler +{ +public: + SensorsNotificationHandler(GonkSensorsInterface* aInterface) + : mInterface(aInterface) + { + MOZ_ASSERT(mInterface); + + mInterface->SetNotificationHandler(this); + } + + void BackendErrorNotification(bool aCrashed) override + { + // XXX: Bug 1206056: restart sensorsd + } + +private: + GonkSensorsInterface* mInterface; +}; + +static StaticAutoPtr<SensorsNotificationHandler> sNotificationHandler; + +/** + * |SensorsRegisterModuleResultHandler| implements the result-handler + * callback for registering the Poll service and activating the first + * sensors. If an error occures during the process, the result handler + * disconnects and closes the backend. + */ +class SensorsRegisterModuleResultHandler final + : public GonkSensorsRegistryResultHandler +{ +public: + SensorsRegisterModuleResultHandler( + uint32_t* aSensorsTypeActivated, + GonkSensorsInterface* aInterface) + : mSensorsTypeActivated(aSensorsTypeActivated) + , mInterface(aInterface) + { + MOZ_ASSERT(mSensorsTypeActivated); + MOZ_ASSERT(mInterface); + } + void OnError(SensorsError aError) override + { + GonkSensorsRegistryResultHandler::OnError(aError); // print error message + Disconnect(); // Registering failed, so close the connection completely + } + void RegisterModule(uint32_t aProtocolVersion) override + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sPollNotificationHandler); + + // Init, step 3: set notification handler for poll service and vice versa + auto pollInterface = mInterface->GetSensorsPollInterface(); + if (!pollInterface) { + Disconnect(); + return; + } + if (NS_FAILED(pollInterface->SetProtocolVersion(aProtocolVersion))) { + Disconnect(); + return; + } + + sPollNotificationHandler = + new SensorsPollNotificationHandler(pollInterface); + + // Init, step 4: activate sensors + for (int i = 0; i < SENSORS_NUM_TYPES; ++i) { + while (mSensorsTypeActivated[i]) { + sPollNotificationHandler->EnableSensorsByType( + static_cast<SensorsType>(i)); + --mSensorsTypeActivated[i]; + } + } + } +public: + void Disconnect() + { + class DisconnectResultHandler final : public GonkSensorsResultHandler + { + public: + void OnError(SensorsError aError) + { + GonkSensorsResultHandler::OnError(aError); // print error message + sNotificationHandler = nullptr; + } + void Disconnect() override + { + sNotificationHandler = nullptr; + } + }; + mInterface->Disconnect(new DisconnectResultHandler()); + } +private: + uint32_t* mSensorsTypeActivated; + GonkSensorsInterface* mInterface; +}; + +/** + * |SensorsConnectResultHandler| implements the result-handler + * callback for starting the Sensors backend. + */ +class SensorsConnectResultHandler final : public GonkSensorsResultHandler +{ +public: + SensorsConnectResultHandler( + uint32_t* aSensorsTypeActivated, + GonkSensorsInterface* aInterface) + : mSensorsTypeActivated(aSensorsTypeActivated) + , mInterface(aInterface) + { + MOZ_ASSERT(mSensorsTypeActivated); + MOZ_ASSERT(mInterface); + } + void OnError(SensorsError aError) override + { + GonkSensorsResultHandler::OnError(aError); // print error message + sNotificationHandler = nullptr; + } + void Connect() override + { + MOZ_ASSERT(NS_IsMainThread()); + + // Init, step 2: register poll service + auto registryInterface = mInterface->GetSensorsRegistryInterface(); + if (!registryInterface) { + return; + } + registryInterface->RegisterModule( + GonkSensorsPollModule::SERVICE_ID, + new SensorsRegisterModuleResultHandler(mSensorsTypeActivated, + mInterface)); + } +private: + uint32_t* mSensorsTypeActivated; + GonkSensorsInterface* mInterface; +}; + +static uint32_t sSensorsTypeActivated[SENSORS_NUM_TYPES]; + +static const SensorsType sSensorsType[] = { + [SENSOR_ORIENTATION] = SENSORS_TYPE_ORIENTATION, + [SENSOR_ACCELERATION] = SENSORS_TYPE_ACCELEROMETER, + [SENSOR_PROXIMITY] = SENSORS_TYPE_PROXIMITY, + [SENSOR_LINEAR_ACCELERATION] = SENSORS_TYPE_LINEAR_ACCELERATION, + [SENSOR_GYROSCOPE] = SENSORS_TYPE_GYROSCOPE, + [SENSOR_LIGHT] = SENSORS_TYPE_LIGHT, + [SENSOR_ROTATION_VECTOR] = SENSORS_TYPE_ROTATION_VECTOR, + [SENSOR_GAME_ROTATION_VECTOR] = SENSORS_TYPE_GAME_ROTATION_VECTOR +}; + +void +EnableSensorNotificationsDaemon(SensorType aSensor) +{ + if ((aSensor < 0) || + (aSensor > static_cast<ssize_t>(MOZ_ARRAY_LENGTH(sSensorsType)))) { + HAL_ERR("Sensor type %d not known", aSensor); + return; // Unsupported sensor type + } + + auto interface = GonkSensorsInterface::GetInstance(); + if (!interface) { + return; + } + + if (sPollNotificationHandler) { + // Everythings already up and running; enable sensor type. + sPollNotificationHandler->EnableSensorsByType(sSensorsType[aSensor]); + return; + } + + ++SaturateOpUint32(sSensorsTypeActivated[sSensorsType[aSensor]]); + + if (sNotificationHandler) { + // We are in the middle of a pending start up; nothing else to do. + return; + } + + // Start up + + MOZ_ASSERT(!sPollNotificationHandler); + MOZ_ASSERT(!sNotificationHandler); + + sNotificationHandler = new SensorsNotificationHandler(interface); + + // Init, step 1: connect to Sensors backend + interface->Connect( + sNotificationHandler, + new SensorsConnectResultHandler(sSensorsTypeActivated, interface)); +} + +void +DisableSensorNotificationsDaemon(SensorType aSensor) +{ + if ((aSensor < 0) || + (aSensor > static_cast<ssize_t>(MOZ_ARRAY_LENGTH(sSensorsType)))) { + HAL_ERR("Sensor type %d not known", aSensor); + return; // Unsupported sensor type + } + + if (sPollNotificationHandler) { + // Everthings up and running; disable sensors type + sPollNotificationHandler->DisableSensorsByType(sSensorsType[aSensor]); + return; + } + + // We might be in the middle of a startup; decrement type's ref-counter. + --SaturateOpUint32(sSensorsTypeActivated[sSensorsType[aSensor]]); + + // TODO: stop sensorsd if all sensors are disabled +} + +// +// Public interface +// + +// TODO: Remove in-Gecko sensors code. Until all devices' base +// images come with sensorsd installed, we have to support the +// in-Gecko implementation as well. So we test for the existance +// of the binary. If it's there, we use it. Otherwise we run the +// old code. +static bool +HasDaemon() +{ + static bool tested; + static bool hasDaemon; + + if (MOZ_UNLIKELY(!tested)) { + hasDaemon = !access("/system/bin/sensorsd", X_OK); + tested = true; + } + + return hasDaemon; +} + +void +EnableSensorNotifications(SensorType aSensor) +{ + if (HasDaemon()) { + EnableSensorNotificationsDaemon(aSensor); + } else { + EnableSensorNotificationsInternal(aSensor); + } +} + +void +DisableSensorNotifications(SensorType aSensor) +{ + if (HasDaemon()) { + DisableSensorNotificationsDaemon(aSensor); + } else { + DisableSensorNotificationsInternal(aSensor); + } +} + +} // hal_impl +} // mozilla diff --git a/hal/gonk/GonkSensorsHelpers.cpp b/hal/gonk/GonkSensorsHelpers.cpp new file mode 100644 index 0000000000..ccc940c7c0 --- /dev/null +++ b/hal/gonk/GonkSensorsHelpers.cpp @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GonkSensorsHelpers.h" + +namespace mozilla { +namespace hal { + +// +// Unpacking +// + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsEvent& aOut) +{ + nsresult rv = UnpackPDU(aPDU, aOut.mType); + if (NS_FAILED(rv)) { + return rv; + } + rv = UnpackPDU(aPDU, aOut.mTimestamp); + if (NS_FAILED(rv)) { + return rv; + } + rv = UnpackPDU(aPDU, aOut.mStatus); + if (NS_FAILED(rv)) { + return rv; + } + + size_t i = 0; + + switch (aOut.mType) { + case SENSORS_TYPE_MAGNETIC_FIELD_UNCALIBRATED: + case SENSORS_TYPE_GYROSCOPE_UNCALIBRATED: + /* 6 data values */ + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + /* fall through */ + case SENSORS_TYPE_ROTATION_VECTOR: + case SENSORS_TYPE_GAME_ROTATION_VECTOR: + case SENSORS_TYPE_GEOMAGNETIC_ROTATION_VECTOR: + /* 5 data values */ + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + /* fall through */ + case SENSORS_TYPE_ACCELEROMETER: + case SENSORS_TYPE_GEOMAGNETIC_FIELD: + case SENSORS_TYPE_ORIENTATION: + case SENSORS_TYPE_GYROSCOPE: + case SENSORS_TYPE_GRAVITY: + case SENSORS_TYPE_LINEAR_ACCELERATION: + /* 3 data values */ + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + /* fall through */ + case SENSORS_TYPE_LIGHT: + case SENSORS_TYPE_PRESSURE: + case SENSORS_TYPE_TEMPERATURE: + case SENSORS_TYPE_PROXIMITY: + case SENSORS_TYPE_RELATIVE_HUMIDITY: + case SENSORS_TYPE_AMBIENT_TEMPERATURE: + case SENSORS_TYPE_HEART_RATE: + case SENSORS_TYPE_TILT_DETECTOR: + case SENSORS_TYPE_WAKE_GESTURE: + case SENSORS_TYPE_GLANCE_GESTURE: + case SENSORS_TYPE_PICK_UP_GESTURE: + case SENSORS_TYPE_WRIST_TILT_GESTURE: + case SENSORS_TYPE_SIGNIFICANT_MOTION: + case SENSORS_TYPE_STEP_DETECTED: + /* 1 data value */ + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + break; + case SENSORS_TYPE_STEP_COUNTER: + /* 1 data value */ + rv = UnpackPDU(aPDU, aOut.mData.mUint[0]); + if (NS_FAILED(rv)) { + return rv; + } + break; + default: + if (MOZ_HAL_IPC_UNPACK_WARN_IF(true, SensorsEvent)) { + return NS_ERROR_ILLEGAL_VALUE; + } + } + rv = UnpackPDU(aPDU, aOut.mDeliveryMode); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; +} + +} // namespace hal +} // namespace mozilla diff --git a/hal/gonk/GonkSensorsHelpers.h b/hal/gonk/GonkSensorsHelpers.h new file mode 100644 index 0000000000..5218af53af --- /dev/null +++ b/hal/gonk/GonkSensorsHelpers.h @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef hal_gonk_GonkSensorsHelpers_h +#define hal_gonk_GonkSensorsHelpers_h + +#include <mozilla/ipc/DaemonSocketPDU.h> +#include <mozilla/ipc/DaemonSocketPDUHelpers.h> +#include "SensorsTypes.h" + +namespace mozilla { +namespace hal { + +using mozilla::ipc::DaemonSocketPDU; +using mozilla::ipc::DaemonSocketPDUHeader; +using mozilla::ipc::DaemonSocketPDUHelpers::Convert; +using mozilla::ipc::DaemonSocketPDUHelpers::PackPDU; +using mozilla::ipc::DaemonSocketPDUHelpers::UnpackPDU; + +using namespace mozilla::ipc::DaemonSocketPDUHelpers; + +// +// Conversion +// +// The functions below convert the input value to the output value's +// type and perform extension tests on the validity of the result. On +// success the output value will be returned in |aOut|. The functions +// return NS_OK on success, or an XPCOM error code otherwise. +// +// See the documentation of the HAL IPC framework for more information +// on conversion functions. +// + +nsresult +Convert(int32_t aIn, SensorsStatus& aOut) +{ + static const uint8_t sStatus[] = { + [0] = SENSORS_STATUS_NO_CONTACT, // '-1' + [1] = SENSORS_STATUS_UNRELIABLE, // '0' + [2] = SENSORS_STATUS_ACCURACY_LOW, // '1' + [3] = SENSORS_STATUS_ACCURACY_MEDIUM, // '2' + [4] = SENSORS_STATUS_ACCURACY_HIGH // '3' + }; + static const int8_t sOffset = -1; // '-1' is the lower bound of the status + + if (MOZ_HAL_IPC_CONVERT_WARN_IF(aIn < sOffset, int32_t, SensorsStatus) || + MOZ_HAL_IPC_CONVERT_WARN_IF( + aIn >= (static_cast<ssize_t>(MOZ_ARRAY_LENGTH(sStatus)) + sOffset), + int32_t, SensorsStatus)) { + return NS_ERROR_ILLEGAL_VALUE; + } + aOut = static_cast<SensorsStatus>(sStatus[aIn - sOffset]); + return NS_OK; +} + +nsresult +Convert(uint8_t aIn, SensorsDeliveryMode& aOut) +{ + static const uint8_t sMode[] = { + [0x00] = SENSORS_DELIVERY_MODE_BEST_EFFORT, + [0x01] = SENSORS_DELIVERY_MODE_IMMEDIATE + }; + if (MOZ_HAL_IPC_CONVERT_WARN_IF( + aIn >= MOZ_ARRAY_LENGTH(sMode), uint8_t, SensorsDeliveryMode)) { + return NS_ERROR_ILLEGAL_VALUE; + } + aOut = static_cast<SensorsDeliveryMode>(sMode[aIn]); + return NS_OK; +} + +nsresult +Convert(uint8_t aIn, SensorsError& aOut) +{ + static const uint8_t sError[] = { + [0x00] = SENSORS_ERROR_NONE, + [0x01] = SENSORS_ERROR_FAIL, + [0x02] = SENSORS_ERROR_NOT_READY, + [0x03] = SENSORS_ERROR_NOMEM, + [0x04] = SENSORS_ERROR_BUSY, + [0x05] = SENSORS_ERROR_DONE, + [0x06] = SENSORS_ERROR_UNSUPPORTED, + [0x07] = SENSORS_ERROR_PARM_INVALID + }; + if (MOZ_HAL_IPC_CONVERT_WARN_IF( + aIn >= MOZ_ARRAY_LENGTH(sError), uint8_t, SensorsError)) { + return NS_ERROR_ILLEGAL_VALUE; + } + aOut = static_cast<SensorsError>(sError[aIn]); + return NS_OK; +} + +nsresult +Convert(uint8_t aIn, SensorsTriggerMode& aOut) +{ + static const uint8_t sMode[] = { + [0x00] = SENSORS_TRIGGER_MODE_CONTINUOUS, + [0x01] = SENSORS_TRIGGER_MODE_ON_CHANGE, + [0x02] = SENSORS_TRIGGER_MODE_ONE_SHOT, + [0x03] = SENSORS_TRIGGER_MODE_SPECIAL + }; + if (MOZ_HAL_IPC_CONVERT_WARN_IF( + aIn >= MOZ_ARRAY_LENGTH(sMode), uint8_t, SensorsTriggerMode)) { + return NS_ERROR_ILLEGAL_VALUE; + } + aOut = static_cast<SensorsTriggerMode>(sMode[aIn]); + return NS_OK; +} + +nsresult +Convert(uint32_t aIn, SensorsType& aOut) +{ + static const uint8_t sType[] = { + [0x00] = 0, // invalid, required by gcc + [0x01] = SENSORS_TYPE_ACCELEROMETER, + [0x02] = SENSORS_TYPE_GEOMAGNETIC_FIELD, + [0x03] = SENSORS_TYPE_ORIENTATION, + [0x04] = SENSORS_TYPE_GYROSCOPE, + [0x05] = SENSORS_TYPE_LIGHT, + [0x06] = SENSORS_TYPE_PRESSURE, + [0x07] = SENSORS_TYPE_TEMPERATURE, + [0x08] = SENSORS_TYPE_PROXIMITY, + [0x09] = SENSORS_TYPE_GRAVITY, + [0x0a] = SENSORS_TYPE_LINEAR_ACCELERATION, + [0x0b] = SENSORS_TYPE_ROTATION_VECTOR, + [0x0c] = SENSORS_TYPE_RELATIVE_HUMIDITY, + [0x0d] = SENSORS_TYPE_AMBIENT_TEMPERATURE, + [0x0e] = SENSORS_TYPE_MAGNETIC_FIELD_UNCALIBRATED, + [0x0f] = SENSORS_TYPE_GAME_ROTATION_VECTOR, + [0x10] = SENSORS_TYPE_GYROSCOPE_UNCALIBRATED, + [0x11] = SENSORS_TYPE_SIGNIFICANT_MOTION, + [0x12] = SENSORS_TYPE_STEP_DETECTED, + [0x13] = SENSORS_TYPE_STEP_COUNTER, + [0x14] = SENSORS_TYPE_GEOMAGNETIC_ROTATION_VECTOR, + [0x15] = SENSORS_TYPE_HEART_RATE, + [0x16] = SENSORS_TYPE_TILT_DETECTOR, + [0x17] = SENSORS_TYPE_WAKE_GESTURE, + [0x18] = SENSORS_TYPE_GLANCE_GESTURE, + [0x19] = SENSORS_TYPE_PICK_UP_GESTURE, + [0x1a] = SENSORS_TYPE_WRIST_TILT_GESTURE + }; + if (MOZ_HAL_IPC_CONVERT_WARN_IF( + !aIn, uint32_t, SensorsType) || + MOZ_HAL_IPC_CONVERT_WARN_IF( + aIn >= MOZ_ARRAY_LENGTH(sType), uint32_t, SensorsType)) { + return NS_ERROR_ILLEGAL_VALUE; + } + aOut = static_cast<SensorsType>(sType[aIn]); + return NS_OK; +} + +nsresult +Convert(nsresult aIn, SensorsError& aOut) +{ + if (NS_SUCCEEDED(aIn)) { + aOut = SENSORS_ERROR_NONE; + } else if (aIn == NS_ERROR_OUT_OF_MEMORY) { + aOut = SENSORS_ERROR_NOMEM; + } else if (aIn == NS_ERROR_ILLEGAL_VALUE) { + aOut = SENSORS_ERROR_PARM_INVALID; + } else { + aOut = SENSORS_ERROR_FAIL; + } + return NS_OK; +} + +// +// Packing +// +// Pack functions store a value in PDU. See the documentation of the +// HAL IPC framework for more information. +// +// There are currently no sensor-specific pack functions necessary. If +// you add one, put it below. +// + +// +// Unpacking +// +// Unpack function retrieve a value from a PDU. The functions return +// NS_OK on success, or an XPCOM error code otherwise. On sucess, the +// returned value is stored in the second argument |aOut|. +// +// See the documentation of the HAL IPC framework for more information +// on unpack functions. +// + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsDeliveryMode& aOut) +{ + return UnpackPDU(aPDU, UnpackConversion<uint8_t, SensorsDeliveryMode>(aOut)); +} + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsError& aOut) +{ + return UnpackPDU(aPDU, UnpackConversion<uint8_t, SensorsError>(aOut)); +} + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsEvent& aOut); + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsStatus& aOut) +{ + return UnpackPDU(aPDU, UnpackConversion<int32_t, SensorsStatus>(aOut)); +} + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsTriggerMode& aOut) +{ + return UnpackPDU(aPDU, UnpackConversion<uint8_t, SensorsTriggerMode>(aOut)); +} + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsType& aOut) +{ + return UnpackPDU(aPDU, UnpackConversion<uint32_t, SensorsType>(aOut)); +} + +} // namespace hal +} // namespace mozilla + +#endif // hal_gonk_GonkSensorsHelpers_h diff --git a/hal/gonk/GonkSensorsInterface.cpp b/hal/gonk/GonkSensorsInterface.cpp new file mode 100644 index 0000000000..51e1ff50c5 --- /dev/null +++ b/hal/gonk/GonkSensorsInterface.cpp @@ -0,0 +1,494 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GonkSensorsInterface.h" +#include "GonkSensorsPollInterface.h" +#include "GonkSensorsRegistryInterface.h" +#include "HalLog.h" +#include <mozilla/ipc/DaemonSocket.h> +#include <mozilla/ipc/DaemonSocketConnector.h> +#include <mozilla/ipc/ListenSocket.h> + +namespace mozilla { +namespace hal { + +using namespace mozilla::ipc; + +// +// GonkSensorsResultHandler +// + +void +GonkSensorsResultHandler::OnError(SensorsError aError) +{ + HAL_ERR("Received error code %d", static_cast<int>(aError)); +} + +void +GonkSensorsResultHandler::Connect() +{ } + +void +GonkSensorsResultHandler::Disconnect() +{ } + +GonkSensorsResultHandler::~GonkSensorsResultHandler() +{ } + +// +// GonkSensorsNotificationHandler +// + +void +GonkSensorsNotificationHandler::BackendErrorNotification(bool aCrashed) +{ + if (aCrashed) { + HAL_ERR("Sensors backend crashed"); + } else { + HAL_ERR("Error in sensors backend"); + } +} + +GonkSensorsNotificationHandler::~GonkSensorsNotificationHandler() +{ } + +// +// GonkSensorsProtocol +// + +class GonkSensorsProtocol final + : public DaemonSocketIOConsumer + , public GonkSensorsRegistryModule + , public GonkSensorsPollModule +{ +public: + GonkSensorsProtocol(); + + void SetConnection(DaemonSocket* aConnection); + + already_AddRefed<DaemonSocketResultHandler> FetchResultHandler( + const DaemonSocketPDUHeader& aHeader); + + // Methods for |SensorsRegistryModule| and |SensorsPollModule| + // + + nsresult Send(DaemonSocketPDU* aPDU, + DaemonSocketResultHandler* aRes) override; + + // Methods for |DaemonSocketIOConsumer| + // + + void Handle(DaemonSocketPDU& aPDU) override; + void StoreResultHandler(const DaemonSocketPDU& aPDU) override; + +private: + void HandleRegistrySvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes); + void HandlePollSvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes); + + DaemonSocket* mConnection; + nsTArray<RefPtr<DaemonSocketResultHandler>> mResultHandlerQ; +}; + +GonkSensorsProtocol::GonkSensorsProtocol() +{ } + +void +GonkSensorsProtocol::SetConnection(DaemonSocket* aConnection) +{ + mConnection = aConnection; +} + +already_AddRefed<DaemonSocketResultHandler> +GonkSensorsProtocol::FetchResultHandler(const DaemonSocketPDUHeader& aHeader) +{ + MOZ_ASSERT(!NS_IsMainThread()); + + if (aHeader.mOpcode & 0x80) { + return nullptr; // Ignore notifications + } + + RefPtr<DaemonSocketResultHandler> res = mResultHandlerQ.ElementAt(0); + mResultHandlerQ.RemoveElementAt(0); + + return res.forget(); +} + +void +GonkSensorsProtocol::HandleRegistrySvc( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + GonkSensorsRegistryModule::HandleSvc(aHeader, aPDU, aRes); +} + +void +GonkSensorsProtocol::HandlePollSvc( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + GonkSensorsPollModule::HandleSvc(aHeader, aPDU, aRes); +} + +// |SensorsRegistryModule|, |SensorsPollModule| + +nsresult +GonkSensorsProtocol::Send(DaemonSocketPDU* aPDU, + DaemonSocketResultHandler* aRes) +{ + MOZ_ASSERT(mConnection); + MOZ_ASSERT(aPDU); + + aPDU->SetConsumer(this); + aPDU->SetResultHandler(aRes); + aPDU->UpdateHeader(); + + if (mConnection->GetConnectionStatus() == SOCKET_DISCONNECTED) { + HAL_ERR("Sensors socket is disconnected"); + return NS_ERROR_FAILURE; + } + + mConnection->SendSocketData(aPDU); // Forward PDU to data channel + + return NS_OK; +} + +// |DaemonSocketIOConsumer| + +void +GonkSensorsProtocol::Handle(DaemonSocketPDU& aPDU) +{ + static void (GonkSensorsProtocol::* const HandleSvc[])( + const DaemonSocketPDUHeader&, DaemonSocketPDU&, + DaemonSocketResultHandler*) = { + [GonkSensorsRegistryModule::SERVICE_ID] = + &GonkSensorsProtocol::HandleRegistrySvc, + [GonkSensorsPollModule::SERVICE_ID] = + &GonkSensorsProtocol::HandlePollSvc + }; + + DaemonSocketPDUHeader header; + + if (NS_FAILED(UnpackPDU(aPDU, header))) { + return; + } + if (!(header.mService < MOZ_ARRAY_LENGTH(HandleSvc)) || + !HandleSvc[header.mService]) { + HAL_ERR("Sensors service %d unknown", header.mService); + return; + } + + RefPtr<DaemonSocketResultHandler> res = FetchResultHandler(header); + + (this->*(HandleSvc[header.mService]))(header, aPDU, res); +} + +void +GonkSensorsProtocol::StoreResultHandler(const DaemonSocketPDU& aPDU) +{ + MOZ_ASSERT(!NS_IsMainThread()); + + mResultHandlerQ.AppendElement(aPDU.GetResultHandler()); +} + +// +// GonkSensorsInterface +// + +GonkSensorsInterface* +GonkSensorsInterface::GetInstance() +{ + static GonkSensorsInterface* sGonkSensorsInterface; + + if (sGonkSensorsInterface) { + return sGonkSensorsInterface; + } + + sGonkSensorsInterface = new GonkSensorsInterface(); + + return sGonkSensorsInterface; +} + +void +GonkSensorsInterface::SetNotificationHandler( + GonkSensorsNotificationHandler* aNotificationHandler) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mNotificationHandler = aNotificationHandler; +} + +/* + * The connect procedure consists of several steps. + * + * (1) Start listening for the command channel's socket connection: We + * do this before anything else, so that we don't miss connection + * requests from the Sensors daemon. This step will create a listen + * socket. + * + * (2) Start the Sensors daemon: When the daemon starts up it will open + * a socket connection to Gecko and thus create the data channel. + * Gecko already opened the listen socket in step (1). Step (2) ends + * with the creation of the data channel. + * + * (3) Signal success to the caller. + * + * If any step fails, we roll-back the procedure and signal an error to the + * caller. + */ +void +GonkSensorsInterface::Connect(GonkSensorsNotificationHandler* aNotificationHandler, + GonkSensorsResultHandler* aRes) +{ +#define BASE_SOCKET_NAME "sensorsd" + static unsigned long POSTFIX_LENGTH = 16; + + // If we could not cleanup properly before and an old + // instance of the daemon is still running, we kill it + // here. + mozilla::hal::StopSystemService("sensorsd"); + + mNotificationHandler = aNotificationHandler; + + mResultHandlerQ.AppendElement(aRes); + + if (!mProtocol) { + mProtocol = MakeUnique<GonkSensorsProtocol>(); + } + + if (!mListenSocket) { + mListenSocket = new ListenSocket(this, LISTEN_SOCKET); + } + + // Init, step 1: Listen for data channel... */ + + if (!mDataSocket) { + mDataSocket = new DaemonSocket(mProtocol.get(), this, DATA_SOCKET); + } else if (mDataSocket->GetConnectionStatus() == SOCKET_CONNECTED) { + // Command channel should not be open; let's close it. + mDataSocket->Close(); + } + + // The listen socket's name is generated with a random postfix. This + // avoids naming collisions if we still have a listen socket from a + // previously failed cleanup. It also makes it hard for malicious + // external programs to capture the socket name or connect before + // the daemon can do so. If no random postfix can be generated, we + // simply use the base name as-is. + nsresult rv = DaemonSocketConnector::CreateRandomAddressString( + NS_LITERAL_CSTRING(BASE_SOCKET_NAME), POSTFIX_LENGTH, mListenSocketName); + if (NS_FAILED(rv)) { + mListenSocketName.AssignLiteral(BASE_SOCKET_NAME); + } + + rv = mListenSocket->Listen(new DaemonSocketConnector(mListenSocketName), + mDataSocket); + if (NS_FAILED(rv)) { + OnConnectError(DATA_SOCKET); + return; + } + + // The protocol implementation needs a data channel for + // sending commands to the daemon. We set it here, because + // this is the earliest time when it's available. + mProtocol->SetConnection(mDataSocket); +} + +/* + * Disconnecting is inverse to connecting. + * + * (1) Close data socket: We close the data channel and the daemon will + * will notice. Once we see the socket's disconnect, we continue with + * the cleanup. + * + * (2) Close listen socket: The listen socket is not active any longer + * and we simply close it. + * + * (3) Signal success to the caller. + * + * We don't have to stop the daemon explicitly. It will cleanup and quit + * after it noticed the closing of the data channel + * + * Rolling back half-completed cleanups is not possible. In the case of + * an error, we simply push forward and try to recover during the next + * initialization. + */ +void +GonkSensorsInterface::Disconnect(GonkSensorsResultHandler* aRes) +{ + mNotificationHandler = nullptr; + + // Cleanup, step 1: Close data channel + mDataSocket->Close(); + + mResultHandlerQ.AppendElement(aRes); +} + +GonkSensorsRegistryInterface* +GonkSensorsInterface::GetSensorsRegistryInterface() +{ + if (mRegistryInterface) { + return mRegistryInterface.get(); + } + + mRegistryInterface = MakeUnique<GonkSensorsRegistryInterface>(mProtocol.get()); + + return mRegistryInterface.get(); +} + +GonkSensorsPollInterface* +GonkSensorsInterface::GetSensorsPollInterface() +{ + if (mPollInterface) { + return mPollInterface.get(); + } + + mPollInterface = MakeUnique<GonkSensorsPollInterface>(mProtocol.get()); + + return mPollInterface.get(); +} + +GonkSensorsInterface::GonkSensorsInterface() + : mNotificationHandler(nullptr) +{ } + +GonkSensorsInterface::~GonkSensorsInterface() +{ } + +void +GonkSensorsInterface::DispatchError(GonkSensorsResultHandler* aRes, + SensorsError aError) +{ + DaemonResultRunnable1<GonkSensorsResultHandler, void, + SensorsError, SensorsError>::Dispatch( + aRes, &GonkSensorsResultHandler::OnError, + ConstantInitOp1<SensorsError>(aError)); +} + +void +GonkSensorsInterface::DispatchError( + GonkSensorsResultHandler* aRes, nsresult aRv) +{ + SensorsError error; + + if (NS_FAILED(Convert(aRv, error))) { + error = SENSORS_ERROR_FAIL; + } + DispatchError(aRes, error); +} + +// |DaemonSocketConsumer|, |ListenSocketConsumer| + +void +GonkSensorsInterface::OnConnectSuccess(int aIndex) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mResultHandlerQ.IsEmpty()); + + switch (aIndex) { + case LISTEN_SOCKET: { + // Init, step 2: Start Sensors daemon + nsCString args("-a "); + args.Append(mListenSocketName); + mozilla::hal::StartSystemService("sensorsd", args.get()); + } + break; + case DATA_SOCKET: + if (!mResultHandlerQ.IsEmpty()) { + // Init, step 3: Signal success + RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0); + mResultHandlerQ.RemoveElementAt(0); + if (res) { + res->Connect(); + } + } + break; + } +} + +void +GonkSensorsInterface::OnConnectError(int aIndex) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mResultHandlerQ.IsEmpty()); + + switch (aIndex) { + case DATA_SOCKET: + // Stop daemon and close listen socket + mozilla::hal::StopSystemService("sensorsd"); + mListenSocket->Close(); + // fall through + case LISTEN_SOCKET: + if (!mResultHandlerQ.IsEmpty()) { + // Signal error to caller + RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0); + mResultHandlerQ.RemoveElementAt(0); + if (res) { + DispatchError(res, SENSORS_ERROR_FAIL); + } + } + break; + } +} + +/* + * Disconnects can happend + * + * (a) during startup, + * (b) during regular service, or + * (c) during shutdown. + * + * For cases (a) and (c), |mResultHandlerQ| contains an element. For + * case (b) |mResultHandlerQ| will be empty. This distinguishes a crash in + * the daemon. The following procedure to recover from crashes consists of + * several steps for case (b). + * + * (1) Close listen socket. + * (2) Wait for all sockets to be disconnected and inform caller about + * the crash. + * (3) After all resources have been cleaned up, let the caller restart + * the daemon. + */ +void +GonkSensorsInterface::OnDisconnect(int aIndex) +{ + MOZ_ASSERT(NS_IsMainThread()); + + switch (aIndex) { + case DATA_SOCKET: + // Cleanup, step 2 (Recovery, step 1): Close listen socket + mListenSocket->Close(); + break; + case LISTEN_SOCKET: + // Cleanup, step 3: Signal success to caller + if (!mResultHandlerQ.IsEmpty()) { + RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0); + mResultHandlerQ.RemoveElementAt(0); + if (res) { + res->Disconnect(); + } + } + break; + } + + /* For recovery make sure all sockets disconnected, in order to avoid + * the remaining disconnects interfere with the restart procedure. + */ + if (mNotificationHandler && mResultHandlerQ.IsEmpty()) { + if (mListenSocket->GetConnectionStatus() == SOCKET_DISCONNECTED && + mDataSocket->GetConnectionStatus() == SOCKET_DISCONNECTED) { + // Recovery, step 2: Notify the caller to prepare the restart procedure. + mNotificationHandler->BackendErrorNotification(true); + mNotificationHandler = nullptr; + } + } +} + +} // namespace hal +} // namespace mozilla diff --git a/hal/gonk/GonkSensorsInterface.h b/hal/gonk/GonkSensorsInterface.h new file mode 100644 index 0000000000..6e356dc364 --- /dev/null +++ b/hal/gonk/GonkSensorsInterface.h @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * The sensors interface gives you access to the low-level sensors code + * in a platform-independent manner. The interfaces in this file allow + * for starting an stopping the sensors driver. Specific functionality + * is implemented in sub-interfaces. + */ + +#ifndef hal_gonk_GonkSensorsInterface_h +#define hal_gonk_GonkSensorsInterface_h + +#include <mozilla/ipc/DaemonSocketConsumer.h> +#include <mozilla/ipc/DaemonSocketMessageHandlers.h> +#include <mozilla/ipc/ListenSocketConsumer.h> +#include <mozilla/UniquePtr.h> +#include "SensorsTypes.h" + +namespace mozilla { +namespace ipc { + +class DaemonSocket; +class ListenSocket; + +} +} + +namespace mozilla { +namespace hal { + +class GonkSensorsPollInterface; +class GonkSensorsProtocol; +class GonkSensorsRegistryInterface; + +/** + * This class is the result-handler interface for the Sensors + * interface. Methods always run on the main thread. + */ +class GonkSensorsResultHandler + : public mozilla::ipc::DaemonSocketResultHandler +{ +public: + + /** + * Called if a command failed. + * + * @param aError The error code. + */ + virtual void OnError(SensorsError aError); + + /** + * The callback method for |GonkSensorsInterface::Connect|. + */ + virtual void Connect(); + + /** + * The callback method for |GonkSensorsInterface::Connect|. + */ + virtual void Disconnect(); + +protected: + virtual ~GonkSensorsResultHandler(); +}; + +/** + * This is the notification-handler interface. Implement this classes + * methods to handle event and notifications from the sensors daemon. + * All methods run on the main thread. + */ +class GonkSensorsNotificationHandler +{ +public: + + /** + * This notification is called when the backend code fails + * unexpectedly. Save state in the high-level code and restart + * the driver. + * + * @param aCrash True is the sensors driver crashed. + */ + virtual void BackendErrorNotification(bool aCrashed); + +protected: + virtual ~GonkSensorsNotificationHandler(); +}; + +/** + * This class implements the public interface to the Sensors functionality + * and driver. Use |GonkSensorsInterface::GetInstance| to retrieve an instance. + * All methods run on the main thread. + */ +class GonkSensorsInterface final + : public mozilla::ipc::DaemonSocketConsumer + , public mozilla::ipc::ListenSocketConsumer +{ +public: + /** + * Returns an instance of the Sensors backend. This code can return + * |nullptr| if no Sensors backend is available. + * + * @return An instance of |GonkSensorsInterface|. + */ + static GonkSensorsInterface* GetInstance(); + + /** + * This method sets the notification handler for sensor notifications. Call + * this method immediately after retreiving an instance of the class, or you + * won't be able able to receive notifications. You may not free the handler + * class while the Sensors backend is connected. + * + * @param aNotificationHandler An instance of a notification handler. + */ + void SetNotificationHandler( + GonkSensorsNotificationHandler* aNotificationHandler); + + /** + * This method starts the Sensors backend and establishes ad connection + * with Gecko. This is a multi-step process and errors are signalled by + * |GonkSensorsNotificationHandler::BackendErrorNotification|. If you see + * this notification before the connection has been established, it's + * certainly best to assume the Sensors backend to be not evailable. + * + * @param aRes The result handler. + */ + void Connect(GonkSensorsNotificationHandler* aNotificationHandler, + GonkSensorsResultHandler* aRes); + + /** + * This method disconnects Gecko from the Sensors backend and frees + * the backend's resources. This will invalidate all interfaces and + * state. Don't use any sensors functionality without reconnecting + * first. + * + * @param aRes The result handler. + */ + void Disconnect(GonkSensorsResultHandler* aRes); + + /** + * Returns the Registry interface for the connected Sensors backend. + * + * @return An instance of the Sensors Registry interface. + */ + GonkSensorsRegistryInterface* GetSensorsRegistryInterface(); + + /** + * Returns the Poll interface for the connected Sensors backend. + * + * @return An instance of the Sensors Poll interface. + */ + GonkSensorsPollInterface* GetSensorsPollInterface(); + +private: + enum Channel { + LISTEN_SOCKET, + DATA_SOCKET + }; + + GonkSensorsInterface(); + ~GonkSensorsInterface(); + + void DispatchError(GonkSensorsResultHandler* aRes, SensorsError aError); + void DispatchError(GonkSensorsResultHandler* aRes, nsresult aRv); + + // Methods for |DaemonSocketConsumer| and |ListenSocketConsumer| + // + + void OnConnectSuccess(int aIndex) override; + void OnConnectError(int aIndex) override; + void OnDisconnect(int aIndex) override; + + nsCString mListenSocketName; + RefPtr<mozilla::ipc::ListenSocket> mListenSocket; + RefPtr<mozilla::ipc::DaemonSocket> mDataSocket; + UniquePtr<GonkSensorsProtocol> mProtocol; + + nsTArray<RefPtr<GonkSensorsResultHandler> > mResultHandlerQ; + + GonkSensorsNotificationHandler* mNotificationHandler; + + UniquePtr<GonkSensorsRegistryInterface> mRegistryInterface; + UniquePtr<GonkSensorsPollInterface> mPollInterface; +}; + +} // namespace hal +} // namespace mozilla + +#endif // hal_gonk_GonkSensorsInterface_h diff --git a/hal/gonk/GonkSensorsPollInterface.cpp b/hal/gonk/GonkSensorsPollInterface.cpp new file mode 100644 index 0000000000..d4edc2e7af --- /dev/null +++ b/hal/gonk/GonkSensorsPollInterface.cpp @@ -0,0 +1,431 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GonkSensorsPollInterface.h" +#include "HalLog.h" +#include <mozilla/UniquePtr.h> + +namespace mozilla { +namespace hal { + +using namespace mozilla::ipc; + +// +// GonkSensorsPollResultHandler +// + +void +GonkSensorsPollResultHandler::OnError(SensorsError aError) +{ + HAL_ERR("Received error code %d", static_cast<int>(aError)); +} + +void +GonkSensorsPollResultHandler::EnableSensor() +{ } + +void +GonkSensorsPollResultHandler::DisableSensor() +{ } + +void +GonkSensorsPollResultHandler::SetPeriod() +{ } + +GonkSensorsPollResultHandler::~GonkSensorsPollResultHandler() +{ } + +// +// GonkSensorsPollNotificationHandler +// + +void +GonkSensorsPollNotificationHandler::ErrorNotification(SensorsError aError) +{ + HAL_ERR("Received error code %d", static_cast<int>(aError)); +} + +void +GonkSensorsPollNotificationHandler::SensorDetectedNotification( + int32_t aId, + SensorsType aType, + float aRange, + float aResolution, + float aPower, + int32_t aMinPeriod, + int32_t aMaxPeriod, + SensorsTriggerMode aTriggerMode, + SensorsDeliveryMode aDeliveryMode) +{ } + +void +GonkSensorsPollNotificationHandler::SensorLostNotification(int32_t aId) +{ } + +void +GonkSensorsPollNotificationHandler::EventNotification(int32_t aId, + const SensorsEvent& aEvent) +{ } + +GonkSensorsPollNotificationHandler::~GonkSensorsPollNotificationHandler() +{ } + +// +// GonkSensorsPollModule +// + +GonkSensorsPollModule::GonkSensorsPollModule() + : mProtocolVersion(0) +{ } + +GonkSensorsPollModule::~GonkSensorsPollModule() +{ } + +nsresult +GonkSensorsPollModule::SetProtocolVersion(unsigned long aProtocolVersion) +{ + if ((aProtocolVersion < MIN_PROTOCOL_VERSION) || + (aProtocolVersion > MAX_PROTOCOL_VERSION)) { + HAL_ERR("Sensors Poll protocol version %lu not supported", + aProtocolVersion); + return NS_ERROR_ILLEGAL_VALUE; + } + mProtocolVersion = aProtocolVersion; + return NS_OK; +} + +void +GonkSensorsPollModule::HandleSvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + static void (GonkSensorsPollModule::* const HandleOp[])( + const DaemonSocketPDUHeader&, DaemonSocketPDU&, + DaemonSocketResultHandler*) = { + [0] = &GonkSensorsPollModule::HandleRsp, + [1] = &GonkSensorsPollModule::HandleNtf + }; + + MOZ_ASSERT(!NS_IsMainThread()); // I/O thread + + // Negate twice to map bit to 0/1 + unsigned long isNtf = !!(aHeader.mOpcode & 0x80); + + (this->*(HandleOp[isNtf]))(aHeader, aPDU, aRes); +} + +// Commands +// + +nsresult +GonkSensorsPollModule::EnableSensorCmd(int32_t aId, GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr<DaemonSocketPDU> pdu = + MakeUnique<DaemonSocketPDU>(SERVICE_ID, OPCODE_ENABLE_SENSOR, 0); + + nsresult rv = PackPDU(aId, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = Send(pdu.get(), aRes); + if (NS_FAILED(rv)) { + return rv; + } + Unused << pdu.release(); + return NS_OK; +} + +nsresult +GonkSensorsPollModule::DisableSensorCmd(int32_t aId, GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr<DaemonSocketPDU> pdu = + MakeUnique<DaemonSocketPDU>(SERVICE_ID, OPCODE_DISABLE_SENSOR, 0); + + nsresult rv = PackPDU(aId, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = Send(pdu.get(), aRes); + if (NS_FAILED(rv)) { + return rv; + } + Unused << pdu.release(); + return NS_OK; +} + +nsresult +GonkSensorsPollModule::SetPeriodCmd(int32_t aId, uint64_t aPeriod, + GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr<DaemonSocketPDU> pdu = + MakeUnique<DaemonSocketPDU>(SERVICE_ID, OPCODE_SET_PERIOD, 0); + + nsresult rv = PackPDU(aId, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = PackPDU(aPeriod, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = Send(pdu.get(), aRes); + if (NS_FAILED(rv)) { + return rv; + } + Unused << pdu.release(); + return NS_OK; +} + +// Responses +// + +void +GonkSensorsPollModule::ErrorRsp( + const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, GonkSensorsPollResultHandler* aRes) +{ + ErrorRunnable::Dispatch( + aRes, &GonkSensorsPollResultHandler::OnError, UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::EnableSensorRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes) +{ + ResultRunnable::Dispatch( + aRes, &GonkSensorsPollResultHandler::EnableSensor, UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::DisableSensorRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes) +{ + ResultRunnable::Dispatch( + aRes, &GonkSensorsPollResultHandler::DisableSensor, UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::SetPeriodRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes) +{ + ResultRunnable::Dispatch( + aRes, &GonkSensorsPollResultHandler::SetPeriod, UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::HandleRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + static void (GonkSensorsPollModule::* const sHandleRsp[])( + const DaemonSocketPDUHeader&, DaemonSocketPDU&, + GonkSensorsPollResultHandler*) = { + [OPCODE_ERROR] = &GonkSensorsPollModule::ErrorRsp, + [OPCODE_ENABLE_SENSOR] = &GonkSensorsPollModule::EnableSensorRsp, + [OPCODE_DISABLE_SENSOR] = &GonkSensorsPollModule::DisableSensorRsp, + [OPCODE_SET_PERIOD] = &GonkSensorsPollModule::SetPeriodRsp, + }; + + MOZ_ASSERT(!NS_IsMainThread()); // I/O thread + + if (!(aHeader.mOpcode < MOZ_ARRAY_LENGTH(sHandleRsp)) || + !sHandleRsp[aHeader.mOpcode]) { + HAL_ERR("Sensors poll response opcode %d unknown", aHeader.mOpcode); + return; + } + + RefPtr<GonkSensorsPollResultHandler> res = + static_cast<GonkSensorsPollResultHandler*>(aRes); + + if (!res) { + return; // Return early if no result handler has been set for response + } + + (this->*(sHandleRsp[aHeader.mOpcode]))(aHeader, aPDU, res); +} + +// Notifications +// + +// Returns the current notification handler to a notification runnable +class GonkSensorsPollModule::NotificationHandlerWrapper final +{ +public: + typedef GonkSensorsPollNotificationHandler ObjectType; + + static ObjectType* GetInstance() + { + MOZ_ASSERT(NS_IsMainThread()); + + return sNotificationHandler; + } + + static GonkSensorsPollNotificationHandler* sNotificationHandler; +}; + +GonkSensorsPollNotificationHandler* + GonkSensorsPollModule::NotificationHandlerWrapper::sNotificationHandler; + +void +GonkSensorsPollModule::ErrorNtf( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU) +{ + ErrorNotification::Dispatch( + &GonkSensorsPollNotificationHandler::ErrorNotification, + UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::SensorDetectedNtf( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU) +{ + SensorDetectedNotification::Dispatch( + &GonkSensorsPollNotificationHandler::SensorDetectedNotification, + UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::SensorLostNtf( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU) +{ + SensorLostNotification::Dispatch( + &GonkSensorsPollNotificationHandler::SensorLostNotification, + UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::EventNtf( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU) +{ + EventNotification::Dispatch( + &GonkSensorsPollNotificationHandler::EventNotification, + UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::HandleNtf( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + static void (GonkSensorsPollModule::* const sHandleNtf[])( + const DaemonSocketPDUHeader&, DaemonSocketPDU&) = { + [0] = &GonkSensorsPollModule::ErrorNtf, + [1] = &GonkSensorsPollModule::SensorDetectedNtf, + [2] = &GonkSensorsPollModule::SensorLostNtf, + [3] = &GonkSensorsPollModule::EventNtf + }; + + MOZ_ASSERT(!NS_IsMainThread()); + + uint8_t index = aHeader.mOpcode - 0x80; + + if (!(index < MOZ_ARRAY_LENGTH(sHandleNtf)) || !sHandleNtf[index]) { + HAL_ERR("Sensors poll notification opcode %d unknown", aHeader.mOpcode); + return; + } + + (this->*(sHandleNtf[index]))(aHeader, aPDU); +} + +// +// GonkSensorsPollInterface +// + +GonkSensorsPollInterface::GonkSensorsPollInterface( + GonkSensorsPollModule* aModule) + : mModule(aModule) +{ } + +GonkSensorsPollInterface::~GonkSensorsPollInterface() +{ } + +void +GonkSensorsPollInterface::SetNotificationHandler( + GonkSensorsPollNotificationHandler* aNotificationHandler) +{ + MOZ_ASSERT(NS_IsMainThread()); + + GonkSensorsPollModule::NotificationHandlerWrapper::sNotificationHandler = + aNotificationHandler; +} + +nsresult +GonkSensorsPollInterface::SetProtocolVersion(unsigned long aProtocolVersion) +{ + MOZ_ASSERT(mModule); + + return mModule->SetProtocolVersion(aProtocolVersion); +} + +void +GonkSensorsPollInterface::EnableSensor(int32_t aId, + GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(mModule); + + nsresult rv = mModule->EnableSensorCmd(aId, aRes); + if (NS_FAILED(rv)) { + DispatchError(aRes, rv); + } +} + +void +GonkSensorsPollInterface::DisableSensor(int32_t aId, + GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(mModule); + + nsresult rv = mModule->DisableSensorCmd(aId, aRes); + if (NS_FAILED(rv)) { + DispatchError(aRes, rv); + } +} + +void +GonkSensorsPollInterface::SetPeriod(int32_t aId, uint64_t aPeriod, + GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(mModule); + + nsresult rv = mModule->SetPeriodCmd(aId, aPeriod, aRes); + if (NS_FAILED(rv)) { + DispatchError(aRes, rv); + } +} + +void +GonkSensorsPollInterface::DispatchError( + GonkSensorsPollResultHandler* aRes, SensorsError aError) +{ + DaemonResultRunnable1<GonkSensorsPollResultHandler, void, + SensorsError, SensorsError>::Dispatch( + aRes, &GonkSensorsPollResultHandler::OnError, + ConstantInitOp1<SensorsError>(aError)); +} + +void +GonkSensorsPollInterface::DispatchError( + GonkSensorsPollResultHandler* aRes, nsresult aRv) +{ + SensorsError error; + + if (NS_FAILED(Convert(aRv, error))) { + error = SENSORS_ERROR_FAIL; + } + DispatchError(aRes, error); +} + +} // namespace hal +} // namespace mozilla diff --git a/hal/gonk/GonkSensorsPollInterface.h b/hal/gonk/GonkSensorsPollInterface.h new file mode 100644 index 0000000000..89381a9bdb --- /dev/null +++ b/hal/gonk/GonkSensorsPollInterface.h @@ -0,0 +1,340 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * The poll interface gives yo access to the Sensors daemon's Poll service, + * which handles sensors. The poll service will inform you when sensors are + * detected or removed from the system. You can activate (or deactivate) + * existing sensors and poll will deliver the sensors' events. + * + * All public methods and callback methods run on the main thread. + */ + +#ifndef hal_gonk_GonkSensorsPollInterface_h +#define hal_gonk_GonkSensorsPollInterface_h + +#include <mozilla/ipc/DaemonRunnables.h> +#include <mozilla/ipc/DaemonSocketMessageHandlers.h> +#include "SensorsTypes.h" + +namespace mozilla { +namespace ipc { + +class DaemonSocketPDU; +class DaemonSocketPDUHeader; + +} +} + +namespace mozilla { +namespace hal { + +class SensorsInterface; + +using mozilla::ipc::DaemonSocketPDU; +using mozilla::ipc::DaemonSocketPDUHeader; +using mozilla::ipc::DaemonSocketResultHandler; + +/** + * This class is the result-handler interface for the Sensors + * Poll interface. Methods always run on the main thread. + */ +class GonkSensorsPollResultHandler : public DaemonSocketResultHandler +{ +public: + + /** + * Called if a poll command failed. + * + * @param aError The error code. + */ + virtual void OnError(SensorsError aError); + + /** + * The callback method for |GonkSensorsPollInterface::EnableSensor|. + */ + virtual void EnableSensor(); + + /** + * The callback method for |GonkSensorsPollInterface::DisableSensor|. + */ + virtual void DisableSensor(); + + /** + * The callback method for |GonkSensorsPollInterface::SetPeriod|. + */ + virtual void SetPeriod(); + +protected: + virtual ~GonkSensorsPollResultHandler(); +}; + +/** + * This is the notification-handler interface. Implement this classes + * methods to handle event and notifications from the sensors daemon. + */ +class GonkSensorsPollNotificationHandler +{ +public: + + /** + * The notification handler for errors. You'll receive this call if + * there's been a critical error in the daemon. Either try to handle + * the error, or restart the daemon. + * + * @param aError The error code. + */ + virtual void ErrorNotification(SensorsError aError); + + /** + * This methods gets call when a new sensor has been detected. + * + * @param aId The sensor's id. + * @param aType The sensor's type. + * @param aRange The sensor's maximum value. + * @param aResolution The minimum difference between two consecutive values. + * @param aPower The sensor's power consumption (in mA). + * @param aMinPeriod The minimum time between two events (in ns). + * @param aMaxPeriod The maximum time between two events (in ns). + * @param aTriggerMode The sensor's mode for triggering events. + * @param aDeliveryMode The sensor's urgency for event delivery. + */ + virtual void SensorDetectedNotification(int32_t aId, SensorsType aType, + float aRange, float aResolution, + float aPower, int32_t aMinPeriod, + int32_t aMaxPeriod, + SensorsTriggerMode aTriggerMode, + SensorsDeliveryMode aDeliveryMode); + + /** + * This methods gets call when an existing sensor has been removed. + * + * @param aId The sensor's id. + */ + virtual void SensorLostNotification(int32_t aId); + + /** + * This is the callback methods for sensor events. Only activated sensors + * generate events. All sensors are disabled by default. The actual data + * of the event depends on the sensor type. + * + * @param aId The sensor's id. + * @param aEvent The event's data. + */ + virtual void EventNotification(int32_t aId, const SensorsEvent& aEvent); + +protected: + virtual ~GonkSensorsPollNotificationHandler(); +}; + +/** + * This is the module class for the Sensors poll component. It handles PDU + * packing and unpacking. Methods are either executed on the main thread or + * the I/O thread. + * + * This is an internal class, use |GonkSensorsPollInterface| instead. + */ +class GonkSensorsPollModule +{ +public: + class NotificationHandlerWrapper; + + enum { + SERVICE_ID = 0x01 + }; + + enum { + OPCODE_ERROR = 0x00, + OPCODE_ENABLE_SENSOR = 0x01, + OPCODE_DISABLE_SENSOR = 0x02, + OPCODE_SET_PERIOD = 0x03 + }; + + enum { + MIN_PROTOCOL_VERSION = 1, + MAX_PROTOCOL_VERSION = 1 + }; + + virtual nsresult Send(DaemonSocketPDU* aPDU, + DaemonSocketResultHandler* aRes) = 0; + + nsresult SetProtocolVersion(unsigned long aProtocolVersion); + + // + // Commands + // + + nsresult EnableSensorCmd(int32_t aId, + GonkSensorsPollResultHandler* aRes); + + nsresult DisableSensorCmd(int32_t aId, + GonkSensorsPollResultHandler* aRes); + + nsresult SetPeriodCmd(int32_t aId, uint64_t aPeriod, + GonkSensorsPollResultHandler* aRes); + +protected: + GonkSensorsPollModule(); + virtual ~GonkSensorsPollModule(); + + void HandleSvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes); + +private: + + // + // Responses + // + + typedef mozilla::ipc::DaemonResultRunnable0< + GonkSensorsPollResultHandler, void> + ResultRunnable; + + typedef mozilla::ipc::DaemonResultRunnable1< + GonkSensorsPollResultHandler, void, SensorsError, SensorsError> + ErrorRunnable; + + void ErrorRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes); + + void EnableSensorRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes); + + void DisableSensorRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes); + + void SetPeriodRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes); + + void HandleRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes); + + // + // Notifications + // + + typedef mozilla::ipc::DaemonNotificationRunnable1< + NotificationHandlerWrapper, void, SensorsError> + ErrorNotification; + + typedef mozilla::ipc::DaemonNotificationRunnable9< + NotificationHandlerWrapper, void, int32_t, SensorsType, + float, float, float, int32_t, int32_t, SensorsTriggerMode, + SensorsDeliveryMode> + SensorDetectedNotification; + + typedef mozilla::ipc::DaemonNotificationRunnable1< + NotificationHandlerWrapper, void, int32_t> + SensorLostNotification; + + typedef mozilla::ipc::DaemonNotificationRunnable2< + NotificationHandlerWrapper, void, int32_t, SensorsEvent, int32_t, + const SensorsEvent&> + EventNotification; + + class SensorDetectedInitOp; + class SensorLostInitOp; + class EventInitOp; + + void ErrorNtf(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU); + + void SensorDetectedNtf(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU); + + void SensorLostNtf(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU); + + void EventNtf(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU); + + void HandleNtf(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes); + +private: + unsigned long mProtocolVersion; +}; + +/** + * This class implements the public interface to the Sensors poll + * component. Use |SensorsInterface::GetPollInterface| to retrieve + * an instance. All methods run on the main thread. + */ +class GonkSensorsPollInterface final +{ +public: + GonkSensorsPollInterface(GonkSensorsPollModule* aModule); + ~GonkSensorsPollInterface(); + + /** + * This method sets the notification handler for poll notifications. Call + * this method immediately after registering the module. Otherwise you won't + * be able able to receive poll notifications. You may not free the handler + * class while the poll component is regsitered. + * + * @param aNotificationHandler An instance of a poll notification handler. + */ + void SetNotificationHandler( + GonkSensorsPollNotificationHandler* aNotificationHandler); + + /** + * This method sets the protocol version. You should set it to the + * value that has been returned from the backend when registering the + * Poll service. You cannot send or receive messages before setting + * the protocol version. + * + * @param aProtocolVersion + * @return NS_OK for supported versions, or an XPCOM error code otherwise. + */ + nsresult SetProtocolVersion(unsigned long aProtocolVersion); + + /** + * Enables an existing sensor. The sensor id will have been delivered in + * a SensorDetectedNotification. + * + * @param aId The sensor's id. + * @param aRes The result handler. + */ + void EnableSensor(int32_t aId, GonkSensorsPollResultHandler* aRes); + + /** + * Disables an existing sensor. The sensor id will have been delivered in + * a SensorDetectedNotification. + * + * @param aId The sensor's id. + * @param aRes The result handler. + */ + void DisableSensor(int32_t aId, GonkSensorsPollResultHandler* aRes); + + /** + * Sets the period for a sensor. The sensor id will have been delivered in + * a SensorDetectedNotification. The value for the period should be between + * the sensor's minimum and maximum period. + * + * @param aId The sensor's id. + * @param aPeriod The sensor's new period. + * @param aRes The result handler. + */ + void SetPeriod(int32_t aId, uint64_t aPeriod, GonkSensorsPollResultHandler* aRes); + +private: + void DispatchError(GonkSensorsPollResultHandler* aRes, SensorsError aError); + void DispatchError(GonkSensorsPollResultHandler* aRes, nsresult aRv); + + GonkSensorsPollModule* mModule; +}; + +} // hal +} // namespace mozilla + +#endif // hal_gonk_GonkSensorsPollInterface_h diff --git a/hal/gonk/GonkSensorsRegistryInterface.cpp b/hal/gonk/GonkSensorsRegistryInterface.cpp new file mode 100644 index 0000000000..601dc7a2a6 --- /dev/null +++ b/hal/gonk/GonkSensorsRegistryInterface.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GonkSensorsRegistryInterface.h" +#include "GonkSensorsHelpers.h" +#include "HalLog.h" +#include <mozilla/UniquePtr.h> + +namespace mozilla { +namespace hal { + +using namespace mozilla::ipc; + +// +// GonkSensorsRegistryResultHandler +// + +void +GonkSensorsRegistryResultHandler::OnError(SensorsError aError) +{ + HAL_ERR("Received error code %d", static_cast<int>(aError)); +} + +void +GonkSensorsRegistryResultHandler::RegisterModule(uint32_t aProtocolVersion) +{ } + +void +GonkSensorsRegistryResultHandler::UnregisterModule() +{ } + +GonkSensorsRegistryResultHandler::~GonkSensorsRegistryResultHandler() +{ } + +// +// GonkSensorsRegistryModule +// + +GonkSensorsRegistryModule::~GonkSensorsRegistryModule() +{ } + +void +GonkSensorsRegistryModule::HandleSvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + static void (GonkSensorsRegistryModule::* const HandleRsp[])( + const DaemonSocketPDUHeader&, + DaemonSocketPDU&, + GonkSensorsRegistryResultHandler*) = { + [OPCODE_ERROR] = &GonkSensorsRegistryModule::ErrorRsp, + [OPCODE_REGISTER_MODULE] = &GonkSensorsRegistryModule::RegisterModuleRsp, + [OPCODE_UNREGISTER_MODULE] = &GonkSensorsRegistryModule::UnregisterModuleRsp + }; + + if ((aHeader.mOpcode >= MOZ_ARRAY_LENGTH(HandleRsp)) || + !HandleRsp[aHeader.mOpcode]) { + HAL_ERR("Sensors registry response opcode %d unknown", aHeader.mOpcode); + return; + } + + RefPtr<GonkSensorsRegistryResultHandler> res = + static_cast<GonkSensorsRegistryResultHandler*>(aRes); + + if (!res) { + return; // Return early if no result handler has been set + } + + (this->*(HandleRsp[aHeader.mOpcode]))(aHeader, aPDU, res); +} + +// Commands +// + +nsresult +GonkSensorsRegistryModule::RegisterModuleCmd( + uint8_t aId, GonkSensorsRegistryResultHandler* aRes) +{ + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr<DaemonSocketPDU> pdu = + MakeUnique<DaemonSocketPDU>(SERVICE_ID, OPCODE_REGISTER_MODULE, 0); + + nsresult rv = PackPDU(aId, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = Send(pdu.get(), aRes); + if (NS_FAILED(rv)) { + return rv; + } + Unused << pdu.release(); + return NS_OK; +} + +nsresult +GonkSensorsRegistryModule::UnregisterModuleCmd( + uint8_t aId, GonkSensorsRegistryResultHandler* aRes) +{ + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr<DaemonSocketPDU> pdu = + MakeUnique<DaemonSocketPDU>(SERVICE_ID, OPCODE_UNREGISTER_MODULE, 0); + + nsresult rv = PackPDU(aId, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = Send(pdu.get(), aRes); + if (NS_FAILED(rv)) { + return rv; + } + Unused << pdu.release(); + return NS_OK; +} + +// Responses +// + +void +GonkSensorsRegistryModule::ErrorRsp( + const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, GonkSensorsRegistryResultHandler* aRes) +{ + ErrorRunnable::Dispatch( + aRes, &GonkSensorsRegistryResultHandler::OnError, UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsRegistryModule::RegisterModuleRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + GonkSensorsRegistryResultHandler* aRes) +{ + Uint32ResultRunnable::Dispatch( + aRes, + &GonkSensorsRegistryResultHandler::RegisterModule, + UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsRegistryModule::UnregisterModuleRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + GonkSensorsRegistryResultHandler* aRes) +{ + ResultRunnable::Dispatch( + aRes, + &GonkSensorsRegistryResultHandler::UnregisterModule, + UnpackPDUInitOp(aPDU)); +} + +// +// GonkSensorsRegistryInterface +// + +GonkSensorsRegistryInterface::GonkSensorsRegistryInterface( + GonkSensorsRegistryModule* aModule) + : mModule(aModule) +{ } + +GonkSensorsRegistryInterface::~GonkSensorsRegistryInterface() +{ } + +void +GonkSensorsRegistryInterface::RegisterModule( + uint8_t aId, GonkSensorsRegistryResultHandler* aRes) +{ + MOZ_ASSERT(mModule); + + nsresult rv = mModule->RegisterModuleCmd(aId, aRes); + if (NS_FAILED(rv)) { + DispatchError(aRes, rv); + } +} + +void +GonkSensorsRegistryInterface::UnregisterModule( + uint8_t aId, GonkSensorsRegistryResultHandler* aRes) +{ + MOZ_ASSERT(mModule); + + nsresult rv = mModule->UnregisterModuleCmd(aId, aRes); + if (NS_FAILED(rv)) { + DispatchError(aRes, rv); + } +} + +void +GonkSensorsRegistryInterface::DispatchError( + GonkSensorsRegistryResultHandler* aRes, SensorsError aError) +{ + DaemonResultRunnable1<GonkSensorsRegistryResultHandler, void, + SensorsError, SensorsError>::Dispatch( + aRes, &GonkSensorsRegistryResultHandler::OnError, + ConstantInitOp1<SensorsError>(aError)); +} + +void +GonkSensorsRegistryInterface::DispatchError( + GonkSensorsRegistryResultHandler* aRes, nsresult aRv) +{ + SensorsError error; + + if (NS_FAILED(Convert(aRv, error))) { + error = SENSORS_ERROR_FAIL; + } + DispatchError(aRes, error); +} + +} // namespace hal +} // namespace mozilla diff --git a/hal/gonk/GonkSensorsRegistryInterface.h b/hal/gonk/GonkSensorsRegistryInterface.h new file mode 100644 index 0000000000..a9d98d653f --- /dev/null +++ b/hal/gonk/GonkSensorsRegistryInterface.h @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * The registry interface gives yo access to the Sensors daemon's Registry + * service. The purpose of the service is to register and setup all other + * services, and make them available. + * + * All public methods and callback methods run on the main thread. + */ + +#ifndef hal_gonk_GonkSensorsRegistryInterface_h +#define hal_gonk_GonkSensorsRegistryInterface_h + +#include <mozilla/ipc/DaemonRunnables.h> +#include <mozilla/ipc/DaemonSocketMessageHandlers.h> +#include "SensorsTypes.h" + +namespace mozilla { +namespace ipc { + +class DaemonSocketPDU; +class DaemonSocketPDUHeader; + +} +} + +namespace mozilla { +namespace hal { + +class SensorsInterface; + +using mozilla::ipc::DaemonSocketPDU; +using mozilla::ipc::DaemonSocketPDUHeader; +using mozilla::ipc::DaemonSocketResultHandler; + +/** + * This class is the result-handler interface for the Sensors + * Registry interface. Methods always run on the main thread. + */ +class GonkSensorsRegistryResultHandler : public DaemonSocketResultHandler +{ +public: + + /** + * Called if a registry command failed. + * + * @param aError The error code. + */ + virtual void OnError(SensorsError aError); + + /** + * The callback method for |GonkSensorsRegistryInterface::RegisterModule|. + * + * @param aProtocolVersion The daemon's protocol version. Make sure it's + * compatible with Gecko's implementation. + */ + virtual void RegisterModule(uint32_t aProtocolVersion); + + /** + * The callback method for |SensorsRegsitryInterface::UnregisterModule|. + */ + virtual void UnregisterModule(); + +protected: + virtual ~GonkSensorsRegistryResultHandler(); +}; + +/** + * This is the module class for the Sensors registry component. It handles + * PDU packing and unpacking. Methods are either executed on the main thread + * or the I/O thread. + * + * This is an internal class, use |GonkSensorsRegistryInterface| instead. + */ +class GonkSensorsRegistryModule +{ +public: + enum { + SERVICE_ID = 0x00 + }; + + enum { + OPCODE_ERROR = 0x00, + OPCODE_REGISTER_MODULE = 0x01, + OPCODE_UNREGISTER_MODULE = 0x02 + }; + + virtual nsresult Send(DaemonSocketPDU* aPDU, + DaemonSocketResultHandler* aRes) = 0; + + // + // Commands + // + + nsresult RegisterModuleCmd(uint8_t aId, + GonkSensorsRegistryResultHandler* aRes); + + nsresult UnregisterModuleCmd(uint8_t aId, + GonkSensorsRegistryResultHandler* aRes); + +protected: + virtual ~GonkSensorsRegistryModule(); + + void HandleSvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, DaemonSocketResultHandler* aRes); + + // + // Responses + // + + typedef mozilla::ipc::DaemonResultRunnable0< + GonkSensorsRegistryResultHandler, void> + ResultRunnable; + + typedef mozilla::ipc::DaemonResultRunnable1< + GonkSensorsRegistryResultHandler, void, uint32_t, uint32_t> + Uint32ResultRunnable; + + typedef mozilla::ipc::DaemonResultRunnable1< + GonkSensorsRegistryResultHandler, void, SensorsError, SensorsError> + ErrorRunnable; + + void ErrorRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsRegistryResultHandler* aRes); + + void RegisterModuleRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsRegistryResultHandler* aRes); + + void UnregisterModuleRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsRegistryResultHandler* aRes); +}; + +/** + * This class implements the public interface to the Sensors Registry + * component. Use |SensorsInterface::GetRegistryInterface| to retrieve + * an instance. All methods run on the main thread. + */ +class GonkSensorsRegistryInterface final +{ +public: + GonkSensorsRegistryInterface(GonkSensorsRegistryModule* aModule); + ~GonkSensorsRegistryInterface(); + + /** + * Sends a RegisterModule command to the Sensors daemon. When the + * result handler's |RegisterModule| method gets called, the service + * has been registered successfully and can be used. + * + * @param aId The id of the service that is to be registered. + * @param aRes The result handler. + */ + void RegisterModule(uint8_t aId, GonkSensorsRegistryResultHandler* aRes); + + /** + * Sends an UnregisterModule command to the Sensors daemon. The service + * should not be used afterwards until it has been registered again. + * + * @param aId The id of the service that is to be unregistered. + * @param aRes The result handler. + */ + void UnregisterModule(uint8_t aId, GonkSensorsRegistryResultHandler* aRes); + +private: + void DispatchError(GonkSensorsRegistryResultHandler* aRes, + SensorsError aError); + void DispatchError(GonkSensorsRegistryResultHandler* aRes, + nsresult aRv); + + GonkSensorsRegistryModule* mModule; +}; + +} // namespace hal +} // namespace mozilla + +#endif // hal_gonk_GonkSensorsRegistryInterface_h diff --git a/hal/gonk/GonkSwitch.cpp b/hal/gonk/GonkSwitch.cpp new file mode 100644 index 0000000000..b2c31c9732 --- /dev/null +++ b/hal/gonk/GonkSwitch.cpp @@ -0,0 +1,479 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 <fcntl.h> +#include <sysutils/NetlinkEvent.h> + +#include "base/message_loop.h" +#include "base/task.h" + +#include "Hal.h" +#include "HalLog.h" +#include "mozilla/FileUtils.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Monitor.h" +#include "nsPrintfCString.h" +#include "nsXULAppAPI.h" +#include "nsThreadUtils.h" +#include "UeventPoller.h" + +using namespace mozilla::hal; + +#define SWITCH_HEADSET_DEVPATH "/devices/virtual/switch/h2w" +#define SWITCH_USB_DEVPATH_GB "/devices/virtual/switch/usb_configuration" +#define SWITCH_USB_DEVPATH_ICS "/devices/virtual/android_usb/android0" + +namespace mozilla { +namespace hal_impl { +/** + * The uevent for a usb on GB insertion looks like: + * + * change@/devices/virtual/switch/usb_configuration + * ACTION=change + * DEVPATH=/devices/virtual/switch/usb_configuration + * SUBSYSTEM=switch + * SWITCH_NAME=usb_configuration + * SWITCH_STATE=0 + * SEQNUM=5038 + */ +class SwitchHandler +{ +public: + NS_INLINE_DECL_REFCOUNTING(SwitchHandler) + + SwitchHandler(const char* aDevPath, SwitchDevice aDevice) + : mDevPath(aDevPath), + mState(SWITCH_STATE_UNKNOWN), + mDevice(aDevice) + { + GetInitialState(); + } + + bool CheckEvent(NetlinkEvent* aEvent) + { + if (strcmp(GetSubsystem(), aEvent->getSubsystem()) || + strcmp(mDevPath, aEvent->findParam("DEVPATH"))) { + return false; + } + + mState = ConvertState(GetStateString(aEvent)); + return mState != SWITCH_STATE_UNKNOWN; + } + + SwitchState GetState() + { + return mState; + } + + SwitchDevice GetType() + { + return mDevice; + } +protected: + virtual ~SwitchHandler() + { + } + + virtual const char* GetSubsystem() + { + return "switch"; + } + + virtual const char* GetStateString(NetlinkEvent* aEvent) + { + return aEvent->findParam("SWITCH_STATE"); + } + + void GetInitialState() + { + nsPrintfCString statePath("/sys%s/state", mDevPath); + int fd = open(statePath.get(), O_RDONLY); + if (fd <= 0) { + return; + } + + ScopedClose autoClose(fd); + char state[16]; + ssize_t bytesRead = read(fd, state, sizeof(state)); + if (bytesRead < 0) { + HAL_ERR("Read data from %s fails", statePath.get()); + return; + } + + if (state[bytesRead - 1] == '\n') { + bytesRead--; + } + + state[bytesRead] = '\0'; + mState = ConvertState(state); + } + + virtual SwitchState ConvertState(const char* aState) + { + MOZ_ASSERT(aState); + return aState[0] == '0' ? SWITCH_STATE_OFF : SWITCH_STATE_ON; + } + + const char* mDevPath; + SwitchState mState; + SwitchDevice mDevice; +}; + +/** + * The uevent delivered for the USB configuration under ICS looks like, + * + * change@/devices/virtual/android_usb/android0 + * ACTION=change + * DEVPATH=/devices/virtual/android_usb/android0 + * SUBSYSTEM=android_usb + * USB_STATE=CONFIGURED + * SEQNUM=1802 + */ +class SwitchHandlerUsbIcs: public SwitchHandler +{ +public: + SwitchHandlerUsbIcs(const char* aDevPath) : SwitchHandler(aDevPath, SWITCH_USB) + { + SwitchHandler::GetInitialState(); + } + + virtual ~SwitchHandlerUsbIcs() { } + +protected: + virtual const char* GetSubsystem() + { + return "android_usb"; + } + + virtual const char* GetStateString(NetlinkEvent* aEvent) + { + return aEvent->findParam("USB_STATE"); + } + + SwitchState ConvertState(const char* aState) + { + MOZ_ASSERT(aState); + return strcmp(aState, "CONFIGURED") == 0 ? SWITCH_STATE_ON : SWITCH_STATE_OFF; + } +}; + +/** + * The uevent delivered for the headset under ICS looks like, + * + * change@/devices/virtual/switch/h2w + * ACTION=change + * DEVPATH=/devices/virtual/switch/h2w + * SUBSYSTEM=switch + * SWITCH_NAME=h2w + * SWITCH_STATE=2 // Headset with no mic + * SEQNUM=2581 + * On Otoro, SWITCH_NAME could be Headset/No Device when plug/unplug. + * change@/devices/virtual/switch/h2w + * ACTION=change + * DEVPATH=/devices/virtual/switch/h2w + * SUBSYSTEM=switch + * SWITCH_NAME=Headset + * SWITCH_STATE=1 // Headset with mic + * SEQNUM=1602 + */ +class SwitchHandlerHeadphone: public SwitchHandler +{ +public: + SwitchHandlerHeadphone(const char* aDevPath) : + SwitchHandler(aDevPath, SWITCH_HEADPHONES) + { + SwitchHandler::GetInitialState(); + } + + virtual ~SwitchHandlerHeadphone() { } + +protected: + SwitchState ConvertState(const char* aState) + { + MOZ_ASSERT(aState); + + return aState[0] == '0' ? SWITCH_STATE_OFF : + (aState[0] == '1' ? SWITCH_STATE_HEADSET : SWITCH_STATE_HEADPHONE); + } +}; + + +typedef nsTArray<RefPtr<SwitchHandler> > SwitchHandlerArray; + +class SwitchEventRunnable : public Runnable +{ +public: + SwitchEventRunnable(SwitchEvent& aEvent) : mEvent(aEvent) + { + } + + NS_IMETHOD Run() override + { + NotifySwitchChange(mEvent); + return NS_OK; + } +private: + SwitchEvent mEvent; +}; + +class SwitchEventObserver final : public IUeventObserver +{ + ~SwitchEventObserver() + { + mHandler.Clear(); + } + +public: + NS_INLINE_DECL_REFCOUNTING(SwitchEventObserver) + SwitchEventObserver() + : mEnableCount(0), + mHeadphonesFromInputDev(false) + { + Init(); + } + + int GetEnableCount() + { + return mEnableCount; + } + + void EnableSwitch(SwitchDevice aDevice) + { + mEventInfo[aDevice].mEnabled = true; + mEnableCount++; + } + + void DisableSwitch(SwitchDevice aDevice) + { + mEventInfo[aDevice].mEnabled = false; + mEnableCount--; + } + + void Notify(const NetlinkEvent& aEvent) + { + SwitchState currState; + + SwitchDevice device = GetEventInfo(aEvent, currState); + if (device == SWITCH_DEVICE_UNKNOWN) { + return; + } + + EventInfo& info = mEventInfo[device]; + if (currState == info.mEvent.status()) { + return; + } + + info.mEvent.status() = currState; + + if (info.mEnabled) { + NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); + } + } + + void Notify(SwitchDevice aDevice, SwitchState aState) + { + EventInfo& info = mEventInfo[aDevice]; + if (aState == info.mEvent.status()) { + return; + } + + info.mEvent.status() = aState; + + if (info.mEnabled) { + NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); + } + } + + SwitchState GetCurrentInformation(SwitchDevice aDevice) + { + return mEventInfo[aDevice].mEvent.status(); + } + + void NotifyAnEvent(SwitchDevice aDevice) + { + EventInfo& info = mEventInfo[aDevice]; + if (info.mEvent.status() != SWITCH_STATE_UNKNOWN) { + NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); + } + } + + bool GetHeadphonesFromInputDev() + { + return mHeadphonesFromInputDev; + } + +private: + class EventInfo + { + public: + EventInfo() : mEnabled(false) + { + mEvent.status() = SWITCH_STATE_UNKNOWN; + mEvent.device() = SWITCH_DEVICE_UNKNOWN; + } + SwitchEvent mEvent; + bool mEnabled; + }; + + EventInfo mEventInfo[NUM_SWITCH_DEVICE]; + size_t mEnableCount; + SwitchHandlerArray mHandler; + bool mHeadphonesFromInputDev; + + // This function might also get called on the main thread + // (from IsHeadphoneEventFromInputDev) + void Init() + { + RefPtr<SwitchHandlerHeadphone> switchHeadPhone = + new SwitchHandlerHeadphone(SWITCH_HEADSET_DEVPATH); + + // If the initial state is unknown, it means the headphone event is from input dev + mHeadphonesFromInputDev = switchHeadPhone->GetState() == SWITCH_STATE_UNKNOWN ? true : false; + + if (!mHeadphonesFromInputDev) { + mHandler.AppendElement(switchHeadPhone); + } else { + // If headphone status will be notified from input dev then initialize + // status to "off" and wait for event notification. + mEventInfo[SWITCH_HEADPHONES].mEvent.device() = SWITCH_HEADPHONES; + mEventInfo[SWITCH_HEADPHONES].mEvent.status() = SWITCH_STATE_OFF; + } + mHandler.AppendElement(new SwitchHandler(SWITCH_USB_DEVPATH_GB, SWITCH_USB)); + mHandler.AppendElement(new SwitchHandlerUsbIcs(SWITCH_USB_DEVPATH_ICS)); + + SwitchHandlerArray::index_type handlerIndex; + SwitchHandlerArray::size_type numHandlers = mHandler.Length(); + + for (handlerIndex = 0; handlerIndex < numHandlers; handlerIndex++) { + SwitchState state = mHandler[handlerIndex]->GetState(); + if (state == SWITCH_STATE_UNKNOWN) { + continue; + } + + SwitchDevice device = mHandler[handlerIndex]->GetType(); + mEventInfo[device].mEvent.device() = device; + mEventInfo[device].mEvent.status() = state; + } + } + + SwitchDevice GetEventInfo(const NetlinkEvent& aEvent, SwitchState& aState) + { + //working around the android code not being const-correct + NetlinkEvent *e = const_cast<NetlinkEvent*>(&aEvent); + + for (size_t i = 0; i < mHandler.Length(); i++) { + if (mHandler[i]->CheckEvent(e)) { + aState = mHandler[i]->GetState(); + return mHandler[i]->GetType(); + } + } + return SWITCH_DEVICE_UNKNOWN; + } +}; + +static RefPtr<SwitchEventObserver> sSwitchObserver; + +static void +InitializeResourceIfNeed() +{ + if (!sSwitchObserver) { + sSwitchObserver = new SwitchEventObserver(); + RegisterUeventListener(sSwitchObserver); + } +} + +static void +ReleaseResourceIfNeed() +{ + if (sSwitchObserver->GetEnableCount() == 0) { + UnregisterUeventListener(sSwitchObserver); + sSwitchObserver = nullptr; + } +} + +static void +EnableSwitchNotificationsIOThread(SwitchDevice aDevice, Monitor *aMonitor) +{ + InitializeResourceIfNeed(); + sSwitchObserver->EnableSwitch(aDevice); + { + MonitorAutoLock lock(*aMonitor); + lock.Notify(); + } + + // Notify the latest state if IO thread has the information. + if (sSwitchObserver->GetEnableCount() > 1) { + sSwitchObserver->NotifyAnEvent(aDevice); + } +} + +void +EnableSwitchNotifications(SwitchDevice aDevice) +{ + Monitor monitor("EnableSwitch.monitor"); + { + MonitorAutoLock lock(monitor); + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(EnableSwitchNotificationsIOThread, aDevice, &monitor)); + lock.Wait(); + } +} + +static void +DisableSwitchNotificationsIOThread(SwitchDevice aDevice) +{ + MOZ_ASSERT(sSwitchObserver->GetEnableCount()); + sSwitchObserver->DisableSwitch(aDevice); + ReleaseResourceIfNeed(); +} + +void +DisableSwitchNotifications(SwitchDevice aDevice) +{ + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(DisableSwitchNotificationsIOThread, aDevice)); +} + +SwitchState +GetCurrentSwitchState(SwitchDevice aDevice) +{ + MOZ_ASSERT(sSwitchObserver && sSwitchObserver->GetEnableCount()); + return sSwitchObserver->GetCurrentInformation(aDevice); +} + +static void +NotifySwitchStateIOThread(SwitchDevice aDevice, SwitchState aState) +{ + InitializeResourceIfNeed(); + sSwitchObserver->Notify(aDevice, aState); +} + +void NotifySwitchStateFromInputDevice(SwitchDevice aDevice, SwitchState aState) +{ + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(NotifySwitchStateIOThread, aDevice, aState)); +} + +bool IsHeadphoneEventFromInputDev() +{ + // Instead of calling InitializeResourceIfNeed, create new SwitchEventObserver + // to prevent calling RegisterUeventListener in main thread. + RefPtr<SwitchEventObserver> switchObserver = new SwitchEventObserver(); + return switchObserver->GetHeadphonesFromInputDev(); +} + +} // hal_impl +} //mozilla diff --git a/hal/gonk/SensorsTypes.h b/hal/gonk/SensorsTypes.h new file mode 100644 index 0000000000..35c852f5a8 --- /dev/null +++ b/hal/gonk/SensorsTypes.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef hal_gonk_SensorsTypes_h +#define hal_gonk_SensorsTypes_h + +namespace mozilla { +namespace hal { + +enum SensorsDeliveryMode { + SENSORS_DELIVERY_MODE_BEST_EFFORT, + SENSORS_DELIVERY_MODE_IMMEDIATE +}; + +enum SensorsError { + SENSORS_ERROR_NONE, + SENSORS_ERROR_FAIL, + SENSORS_ERROR_NOT_READY, + SENSORS_ERROR_NOMEM, + SENSORS_ERROR_BUSY, + SENSORS_ERROR_DONE, + SENSORS_ERROR_UNSUPPORTED, + SENSORS_ERROR_PARM_INVALID +}; + +enum SensorsStatus { + SENSORS_STATUS_NO_CONTACT, + SENSORS_STATUS_UNRELIABLE, + SENSORS_STATUS_ACCURACY_LOW, + SENSORS_STATUS_ACCURACY_MEDIUM, + SENSORS_STATUS_ACCURACY_HIGH +}; + +enum SensorsTriggerMode { + SENSORS_TRIGGER_MODE_CONTINUOUS, + SENSORS_TRIGGER_MODE_ON_CHANGE, + SENSORS_TRIGGER_MODE_ONE_SHOT, + SENSORS_TRIGGER_MODE_SPECIAL +}; + +enum SensorsType { + SENSORS_TYPE_ACCELEROMETER, + SENSORS_TYPE_GEOMAGNETIC_FIELD, + SENSORS_TYPE_ORIENTATION, + SENSORS_TYPE_GYROSCOPE, + SENSORS_TYPE_LIGHT, + SENSORS_TYPE_PRESSURE, + SENSORS_TYPE_TEMPERATURE, + SENSORS_TYPE_PROXIMITY, + SENSORS_TYPE_GRAVITY, + SENSORS_TYPE_LINEAR_ACCELERATION, + SENSORS_TYPE_ROTATION_VECTOR, + SENSORS_TYPE_RELATIVE_HUMIDITY, + SENSORS_TYPE_AMBIENT_TEMPERATURE, + SENSORS_TYPE_MAGNETIC_FIELD_UNCALIBRATED, + SENSORS_TYPE_GAME_ROTATION_VECTOR, + SENSORS_TYPE_GYROSCOPE_UNCALIBRATED, + SENSORS_TYPE_SIGNIFICANT_MOTION, + SENSORS_TYPE_STEP_DETECTED, + SENSORS_TYPE_STEP_COUNTER, + SENSORS_TYPE_GEOMAGNETIC_ROTATION_VECTOR, + SENSORS_TYPE_HEART_RATE, + SENSORS_TYPE_TILT_DETECTOR, + SENSORS_TYPE_WAKE_GESTURE, + SENSORS_TYPE_GLANCE_GESTURE, + SENSORS_TYPE_PICK_UP_GESTURE, + SENSORS_TYPE_WRIST_TILT_GESTURE, + SENSORS_NUM_TYPES +}; + +struct SensorsEvent { + SensorsType mType; + SensorsStatus mStatus; + SensorsDeliveryMode mDeliveryMode; + int64_t mTimestamp; + union { + float mFloat[6]; + uint64_t mUint[1]; + } mData; +}; + +/** + * |SensorsSensor| represents a device sensor; either single or composite. + */ +struct SensorsSensor { + SensorsSensor(int32_t aId, SensorsType aType, + float aRange, float aResolution, + float aPower, int32_t aMinPeriod, + int32_t aMaxPeriod, + SensorsTriggerMode aTriggerMode, + SensorsDeliveryMode aDeliveryMode) + : mId(aId) + , mType(aType) + , mRange(aRange) + , mResolution(aResolution) + , mPower(aPower) + , mMinPeriod(aMinPeriod) + , mMaxPeriod(aMaxPeriod) + , mTriggerMode(aTriggerMode) + , mDeliveryMode(aDeliveryMode) + { } + + int32_t mId; + SensorsType mType; + float mRange; + float mResolution; + float mPower; + int32_t mMinPeriod; + int32_t mMaxPeriod; + SensorsTriggerMode mTriggerMode; + SensorsDeliveryMode mDeliveryMode; +}; + +/** + * |SensorClass| represents the status of a specific sensor type. + */ +struct SensorsSensorClass { + SensorsSensorClass() + : mActivated(0) + , mMinValue(0) + , mMaxValue(0) + { } + + void UpdateFromSensor(const SensorsSensor& aSensor) + { + mMaxValue = std::max(aSensor.mRange, mMaxValue); + } + + uint32_t mActivated; + float mMinValue; + float mMaxValue; +}; + +} // namespace hal +} // namespace mozilla + +#endif // hal_gonk_SensorsTypes_h diff --git a/hal/gonk/SystemService.cpp b/hal/gonk/SystemService.cpp new file mode 100644 index 0000000000..2b98f5fdd6 --- /dev/null +++ b/hal/gonk/SystemService.cpp @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" + +#include <cutils/properties.h> +#include <stdio.h> +#include <string.h> + +#include "HalLog.h" +#include "nsITimer.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace hal_impl { + +static const int sRetryInterval = 100; // ms + +bool +SystemServiceIsRunning(const char* aSvcName) +{ + MOZ_ASSERT(NS_IsMainThread()); + + char key[PROPERTY_KEY_MAX]; + auto res = snprintf(key, sizeof(key), "init.svc.%s", aSvcName); + + if (res < 0) { + HAL_ERR("snprintf: %s", strerror(errno)); + return false; + } else if (static_cast<size_t>(res) >= sizeof(key)) { + HAL_ERR("snprintf: trunctated service name %s", aSvcName); + return false; + } + + char value[PROPERTY_VALUE_MAX]; + Unused << NS_WARN_IF(property_get(key, value, "") < 0); + + return !strcmp(value, "running"); +} + +class StartSystemServiceTimerCallback final : public nsITimerCallback +{ + NS_DECL_THREADSAFE_ISUPPORTS; + +public: + StartSystemServiceTimerCallback(const char* aSvcName, const char* aArgs) + : mSvcName(aSvcName) + , mArgs(aArgs) + { + MOZ_COUNT_CTOR_INHERITED(StartSystemServiceTimerCallback, + nsITimerCallback); + } + + NS_IMETHOD Notify(nsITimer* aTimer) override + { + MOZ_ASSERT(NS_IsMainThread()); + + return StartSystemService(mSvcName.get(), mArgs.get()); + } + +protected: + ~StartSystemServiceTimerCallback() + { + MOZ_COUNT_DTOR_INHERITED(StartSystemServiceTimerCallback, + nsITimerCallback); + } + +private: + nsCString mSvcName; + nsCString mArgs; +}; + +NS_IMPL_ISUPPORTS0(StartSystemServiceTimerCallback); + +nsresult +StartSystemService(const char* aSvcName, const char* aArgs) +{ + MOZ_ASSERT(NS_IsMainThread()); + + char value[PROPERTY_VALUE_MAX]; + auto res = snprintf(value, sizeof(value), "%s:%s", aSvcName, aArgs); + + if (res < 0) { + HAL_ERR("snprintf: %s", strerror(errno)); + return NS_ERROR_FAILURE; + } else if (static_cast<size_t>(res) >= sizeof(value)) { + HAL_ERR("snprintf: trunctated service name %s", aSvcName); + return NS_ERROR_OUT_OF_MEMORY; + } + + if (NS_WARN_IF(property_set("ctl.start", value) < 0)) { + return NS_ERROR_FAILURE; + } + + /* If the system service is not running, re-try later to start it. + * + * This condition happens when we restart a service immediately + * after it crashed, as the service state remains 'stopping' + * instead of 'stopped'. Due to the limitation of property service, + * hereby add delay. See Bug 1143925 Comment 41. + */ + if (!SystemServiceIsRunning(aSvcName)) { + nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1"); + if (!timer) { + return NS_ERROR_FAILURE; + } + + RefPtr<StartSystemServiceTimerCallback> timerCallback = + new StartSystemServiceTimerCallback(aSvcName, aArgs); + + timer->InitWithCallback(timerCallback, + sRetryInterval, + nsITimer::TYPE_ONE_SHOT); + } + + return NS_OK; +} + +void +StopSystemService(const char* aSvcName) +{ + MOZ_ASSERT(NS_IsMainThread()); + + Unused << NS_WARN_IF(property_set("ctl.stop", aSvcName)); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/gonk/UeventPoller.cpp b/hal/gonk/UeventPoller.cpp new file mode 100644 index 0000000000..3fbe850ed5 --- /dev/null +++ b/hal/gonk/UeventPoller.cpp @@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <linux/types.h> +#include <linux/netlink.h> +#include <netinet/in.h> +#include <sys/socket.h> + +#include "HalLog.h" +#include "nsDebug.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/FileUtils.h" +#include "mozilla/Monitor.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +#include "UeventPoller.h" + +using namespace mozilla; + +namespace mozilla { +namespace hal_impl { + +static void ShutdownUevent(); + +class NetlinkPoller : public MessageLoopForIO::Watcher +{ +public: + NetlinkPoller() : mSocket(-1), + mIOLoop(MessageLoopForIO::current()) + { + } + + virtual ~NetlinkPoller() {} + + bool OpenSocket(); + + virtual void OnFileCanReadWithoutBlocking(int fd); + + // no writing to the netlink socket + virtual void OnFileCanWriteWithoutBlocking(int fd) + { + MOZ_CRASH("Must not write to netlink socket"); + } + + MessageLoopForIO *GetIOLoop () const { return mIOLoop; } + void RegisterObserver(IUeventObserver *aObserver) + { + mUeventObserverList.AddObserver(aObserver); + } + + void UnregisterObserver(IUeventObserver *aObserver) + { + mUeventObserverList.RemoveObserver(aObserver); + if (mUeventObserverList.Length() == 0) { + ShutdownUevent(); // this will destroy self + } + } + +private: + ScopedClose mSocket; + MessageLoopForIO* mIOLoop; + MessageLoopForIO::FileDescriptorWatcher mReadWatcher; + + const static int kBuffsize = 64 * 1024; + uint8_t mBuffer [kBuffsize]; + + typedef ObserverList<NetlinkEvent> UeventObserverList; + UeventObserverList mUeventObserverList; +}; + +bool +NetlinkPoller::OpenSocket() +{ + mSocket.rwget() = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + if (mSocket.get() < 0) { + return false; + } + + int sz = kBuffsize; + + if (setsockopt(mSocket.get(), SOL_SOCKET, SO_RCVBUFFORCE, &sz, + sizeof(sz)) < 0) { + return false; + } + + // add FD_CLOEXEC flag + int flags = fcntl(mSocket.get(), F_GETFD); + if (flags == -1) { + return false; + } + flags |= FD_CLOEXEC; + if (fcntl(mSocket.get(), F_SETFD, flags) == -1) { + return false; + } + + // set non-blocking + if (fcntl(mSocket.get(), F_SETFL, O_NONBLOCK) == -1) { + return false; + } + + struct sockaddr_nl saddr; + bzero(&saddr, sizeof(saddr)); + saddr.nl_family = AF_NETLINK; + saddr.nl_groups = 1; + saddr.nl_pid = gettid(); + + do { + if (bind(mSocket.get(), (struct sockaddr *)&saddr, sizeof(saddr)) == 0) { + break; + } + + if (errno != EADDRINUSE) { + return false; + } + + if (saddr.nl_pid == 0) { + return false; + } + + // Once there was any other place in the same process assigning saddr.nl_pid by + // gettid(), we can detect it and print warning message. + HAL_LOG("The netlink socket address saddr.nl_pid=%u is in use. " + "Let the kernel re-assign.\n", saddr.nl_pid); + saddr.nl_pid = 0; + } while (true); + + if (!mIOLoop->WatchFileDescriptor(mSocket.get(), + true, + MessageLoopForIO::WATCH_READ, + &mReadWatcher, + this)) { + return false; + } + + return true; +} + +static StaticAutoPtr<NetlinkPoller> sPoller; + +class UeventInitTask : public Runnable +{ + NS_IMETHOD Run() override + { + if (!sPoller) { + return NS_OK; + } + if (sPoller->OpenSocket()) { + return NS_OK; + } + sPoller->GetIOLoop()->PostDelayedTask(MakeAndAddRef<UeventInitTask>(), + 1000); + return NS_OK; + } +}; + +void +NetlinkPoller::OnFileCanReadWithoutBlocking(int fd) +{ + MOZ_ASSERT(fd == mSocket.get()); + while (true) { + int ret = read(fd, mBuffer, kBuffsize); + if (ret == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return; + } + if (errno == EINTR) { + continue; + } + } + if (ret <= 0) { + // fatal error on netlink socket which should not happen + _exit(1); + } + NetlinkEvent netlinkEvent; + netlinkEvent.decode(reinterpret_cast<char*>(mBuffer), ret); + mUeventObserverList.Broadcast(netlinkEvent); + } +} + +static bool sShutdown = false; + +class ShutdownNetlinkPoller; +static StaticAutoPtr<ShutdownNetlinkPoller> sShutdownPoller; +static Monitor* sMonitor = nullptr; + +class ShutdownNetlinkPoller { +public: + ~ShutdownNetlinkPoller() + { + // This is called from KillClearOnShutdown() on the main thread. + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_GetIOMessageLoop()); + + { + MonitorAutoLock lock(*sMonitor); + + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(ShutdownUeventIOThread)); + + while (!sShutdown) { + lock.Wait(); + } + } + + sShutdown = true; + delete sMonitor; + } + + static void MaybeInit() + { + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + if (sShutdown || sMonitor) { + // Don't init twice or init after shutdown. + return; + } + + sMonitor = new Monitor("ShutdownNetlinkPoller.monitor"); + { + ShutdownNetlinkPoller* shutdownPoller = new ShutdownNetlinkPoller(); + + nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=] () -> void + { + sShutdownPoller = shutdownPoller; + ClearOnShutdown(&sShutdownPoller); // Must run on the main thread. + }); + MOZ_ASSERT(runnable); + MOZ_ALWAYS_SUCCEEDS( + NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL)); + } + } +private: + ShutdownNetlinkPoller() = default; + static void ShutdownUeventIOThread() + { + MonitorAutoLock l(*sMonitor); + ShutdownUevent(); // Must run on the IO thread. + sShutdown = true; + l.NotifyAll(); + } +}; + +static void +InitializeUevent() +{ + MOZ_ASSERT(!sPoller); + sPoller = new NetlinkPoller(); + sPoller->GetIOLoop()->PostTask(MakeAndAddRef<UeventInitTask>()); + + ShutdownNetlinkPoller::MaybeInit(); +} + +static void +ShutdownUevent() +{ + sPoller = nullptr; +} + +void +RegisterUeventListener(IUeventObserver *aObserver) +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + if (sShutdown) { + return; + } + + if (!sPoller) { + InitializeUevent(); + } + sPoller->RegisterObserver(aObserver); +} + +void +UnregisterUeventListener(IUeventObserver *aObserver) +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + if (sShutdown) { + return; + } + + sPoller->UnregisterObserver(aObserver); +} + +} // hal_impl +} // mozilla + diff --git a/hal/gonk/UeventPoller.h b/hal/gonk/UeventPoller.h new file mode 100644 index 0000000000..ba121cec2e --- /dev/null +++ b/hal/gonk/UeventPoller.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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. + */ + +#ifndef _mozilla_uevent_poller_h_ +#define _mozilla_uevent_poller_h_ + +#include <sysutils/NetlinkEvent.h> +#include "mozilla/Observer.h" + +class NetlinkEvent; + +namespace mozilla { +namespace hal_impl { + +typedef mozilla::Observer<NetlinkEvent> IUeventObserver; + +/** + * Register for uevent notification. Note that the method should run on the + * <b> IO Thread </b> + * @aObserver the observer to be added. The observer's Notify() is only called + * on the <b> IO Thread </b> + */ +void RegisterUeventListener(IUeventObserver *aObserver); + +/** + * Unregister for uevent notification. Note that the method should run on the + * <b> IO Thread </b> + * @aObserver the observer to be removed + */ +void UnregisterUeventListener(IUeventObserver *aObserver); + +} +} + +#endif + diff --git a/hal/gonk/fanotify.h b/hal/gonk/fanotify.h new file mode 100644 index 0000000000..e715d3bf95 --- /dev/null +++ b/hal/gonk/fanotify.h @@ -0,0 +1,118 @@ +#ifndef _LINUX_FANOTIFY_H +#define _LINUX_FANOTIFY_H + +/* This is a Linux header generated by "make headers_install" */ + +#include <linux/types.h> + +/* the following events that user-space can register for */ +#define FAN_ACCESS 0x00000001 /* File was accessed */ +#define FAN_MODIFY 0x00000002 /* File was modified */ +#define FAN_CLOSE_WRITE 0x00000008 /* Writtable file closed */ +#define FAN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed */ +#define FAN_OPEN 0x00000020 /* File was opened */ + +#define FAN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ + +#define FAN_OPEN_PERM 0x00010000 /* File open in perm check */ +#define FAN_ACCESS_PERM 0x00020000 /* File accessed in perm check */ + +#define FAN_ONDIR 0x40000000 /* event occurred against dir */ + +#define FAN_EVENT_ON_CHILD 0x08000000 /* interested in child events */ + +/* helper events */ +#define FAN_CLOSE (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */ + +/* flags used for fanotify_init() */ +#define FAN_CLOEXEC 0x00000001 +#define FAN_NONBLOCK 0x00000002 + +/* These are NOT bitwise flags. Both bits are used togther. */ +#define FAN_CLASS_NOTIF 0x00000000 +#define FAN_CLASS_CONTENT 0x00000004 +#define FAN_CLASS_PRE_CONTENT 0x00000008 +#define FAN_ALL_CLASS_BITS (FAN_CLASS_NOTIF | FAN_CLASS_CONTENT | \ + FAN_CLASS_PRE_CONTENT) + +#define FAN_UNLIMITED_QUEUE 0x00000010 +#define FAN_UNLIMITED_MARKS 0x00000020 + +#define FAN_ALL_INIT_FLAGS (FAN_CLOEXEC | FAN_NONBLOCK | \ + FAN_ALL_CLASS_BITS | FAN_UNLIMITED_QUEUE |\ + FAN_UNLIMITED_MARKS) + +/* flags used for fanotify_modify_mark() */ +#define FAN_MARK_ADD 0x00000001 +#define FAN_MARK_REMOVE 0x00000002 +#define FAN_MARK_DONT_FOLLOW 0x00000004 +#define FAN_MARK_ONLYDIR 0x00000008 +#define FAN_MARK_MOUNT 0x00000010 +#define FAN_MARK_IGNORED_MASK 0x00000020 +#define FAN_MARK_IGNORED_SURV_MODIFY 0x00000040 +#define FAN_MARK_FLUSH 0x00000080 + +#define FAN_ALL_MARK_FLAGS (FAN_MARK_ADD |\ + FAN_MARK_REMOVE |\ + FAN_MARK_DONT_FOLLOW |\ + FAN_MARK_ONLYDIR |\ + FAN_MARK_MOUNT |\ + FAN_MARK_IGNORED_MASK |\ + FAN_MARK_IGNORED_SURV_MODIFY |\ + FAN_MARK_FLUSH) + +/* + * All of the events - we build the list by hand so that we can add flags in + * the future and not break backward compatibility. Apps will get only the + * events that they originally wanted. Be sure to add new events here! + */ +#define FAN_ALL_EVENTS (FAN_ACCESS |\ + FAN_MODIFY |\ + FAN_CLOSE |\ + FAN_OPEN) + +/* + * All events which require a permission response from userspace + */ +#define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM |\ + FAN_ACCESS_PERM) + +#define FAN_ALL_OUTGOING_EVENTS (FAN_ALL_EVENTS |\ + FAN_ALL_PERM_EVENTS |\ + FAN_Q_OVERFLOW) + +#define FANOTIFY_METADATA_VERSION 3 + +struct fanotify_event_metadata { + __u32 event_len; + __u8 vers; + __u8 reserved; + __u16 metadata_len; + __u64 mask; + __s32 fd; + __s32 pid; +}; + +struct fanotify_response { + __s32 fd; + __u32 response; +}; + +/* Legit userspace responses to a _PERM event */ +#define FAN_ALLOW 0x01 +#define FAN_DENY 0x02 +/* No fd set in event */ +#define FAN_NOFD -1 + +/* Helper functions to deal with fanotify_event_metadata buffers */ +#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata)) + +#define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, \ + (struct fanotify_event_metadata*)(((char *)(meta)) + \ + (meta)->event_len)) + +#define FAN_EVENT_OK(meta, len) ((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \ + (long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \ + (long)(meta)->event_len <= (long)(len)) + +#endif /* _LINUX_FANOTIFY_H */ diff --git a/hal/gonk/nsIRecoveryService.idl b/hal/gonk/nsIRecoveryService.idl new file mode 100644 index 0000000000..ecbb39c0e1 --- /dev/null +++ b/hal/gonk/nsIRecoveryService.idl @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(bc24fb33-a0c1-49ca-aa43-05f167e02fb6)] +interface nsIRecoveryService : nsISupports +{ + /** + * Possible values of fotaStatus.result. These should stay in sync with + * librecovery/librecovery.h + */ + const long FOTA_UPDATE_UNKNOWN = 0; + const long FOTA_UPDATE_FAIL = 1; + const long FOTA_UPDATE_SUCCESS = 2; + + /** + * Uses recovery to wipe the data and cache partitions. If this call is + * successful, the device should reboot before the function call ever returns. + * + * @throws NS_ERROR_FAILURE when rebooting into recovery fails for some reason. + */ + void factoryReset(in string reason); + + /** + * Use recovery to install an OTA update.zip. If this call is + * successful, the device should reboot before the function call ever returns. + * + * @throws NS_ERROR_FAILURE when rebooting into recovery fails for some reason. + */ + void installFotaUpdate(in string updatePath); + + /** + * @return The status of the last FOTA update. One of FOTA_UPDATE_UNKNOWN, + * FOTA_UPDATE_FAIL, FOTA_UPDATE_SUCCESS. + */ + long getFotaUpdateStatus(); +}; diff --git a/hal/gonk/tavarua.h b/hal/gonk/tavarua.h new file mode 100644 index 0000000000..4eb3483a84 --- /dev/null +++ b/hal/gonk/tavarua.h @@ -0,0 +1,484 @@ +#ifndef __LINUX_TAVARUA_H +#define __LINUX_TAVARUA_H + +/* This is a Linux header generated by "make headers_install" */ + +#include <stdint.h> +#include <linux/ioctl.h> +#include <linux/videodev2.h> + + +#undef FM_DEBUG + +/* constants */ +#define RDS_BLOCKS_NUM (4) +#define BYTES_PER_BLOCK (3) +#define MAX_PS_LENGTH (96) +#define MAX_RT_LENGTH (64) + +#define XFRDAT0 (0x20) +#define XFRDAT1 (0x21) +#define XFRDAT2 (0x22) + +#define INTDET_PEEK_MSB (0x88) +#define INTDET_PEEK_LSB (0x26) + +#define RMSSI_PEEK_MSB (0x88) +#define RMSSI_PEEK_LSB (0xA8) + +#define MPX_DCC_BYPASS_POKE_MSB (0x88) +#define MPX_DCC_BYPASS_POKE_LSB (0xC0) + +#define MPX_DCC_PEEK_MSB_REG1 (0x88) +#define MPX_DCC_PEEK_LSB_REG1 (0xC2) + +#define MPX_DCC_PEEK_MSB_REG2 (0x88) +#define MPX_DCC_PEEK_LSB_REG2 (0xC3) + +#define MPX_DCC_PEEK_MSB_REG3 (0x88) +#define MPX_DCC_PEEK_LSB_REG3 (0xC4) + +#define ON_CHANNEL_TH_MSB (0x0B) +#define ON_CHANNEL_TH_LSB (0xA8) + +#define OFF_CHANNEL_TH_MSB (0x0B) +#define OFF_CHANNEL_TH_LSB (0xAC) + +#define ENF_200Khz (1) +#define SRCH200KHZ_OFFSET (7) +#define SRCH_MASK (1 << SRCH200KHZ_OFFSET) + +/* Standard buffer size */ +#define STD_BUF_SIZE (128) +/* Search direction */ +#define SRCH_DIR_UP (0) +#define SRCH_DIR_DOWN (1) + +/* control options */ +#define CTRL_ON (1) +#define CTRL_OFF (0) + +#define US_LOW_BAND (87.5) +#define US_HIGH_BAND (108) + +/* constant for Tx */ + +#define MASK_PI (0x0000FFFF) +#define MASK_PI_MSB (0x0000FF00) +#define MASK_PI_LSB (0x000000FF) +#define MASK_PTY (0x0000001F) +#define MASK_TXREPCOUNT (0x0000000F) + +#undef FMDBG +#ifdef FM_DEBUG + #define FMDBG(fmt, args...) printk(KERN_INFO "tavarua_radio: " fmt, ##args) +#else + #define FMDBG(fmt, args...) +#endif + +#undef FMDERR +#define FMDERR(fmt, args...) printk(KERN_INFO "tavarua_radio: " fmt, ##args) + +#undef FMDBG_I2C +#ifdef FM_DEBUG_I2C + #define FMDBG_I2C(fmt, args...) printk(KERN_INFO "fm_i2c: " fmt, ##args) +#else + #define FMDBG_I2C(fmt, args...) +#endif + +/* function declarations */ +/* FM Core audio paths. */ +#define TAVARUA_AUDIO_OUT_ANALOG_OFF (0) +#define TAVARUA_AUDIO_OUT_ANALOG_ON (1) +#define TAVARUA_AUDIO_OUT_DIGITAL_OFF (0) +#define TAVARUA_AUDIO_OUT_DIGITAL_ON (1) + +int tavarua_set_audio_path(int digital_on, int analog_on); + +/* defines and enums*/ + +#define MARIMBA_A0 0x01010013 +#define MARIMBA_2_1 0x02010204 +#define BAHAMA_1_0 0x0302010A +#define BAHAMA_2_0 0x04020205 +#define WAIT_TIMEOUT 2000 +#define RADIO_INIT_TIME 15 +#define TAVARUA_DELAY 10 +/* + * The frequency is set in units of 62.5 Hz when using V4L2_TUNER_CAP_LOW, + * 62.5 kHz otherwise. + * The tuner is able to have a channel spacing of 50, 100 or 200 kHz. + * tuner->capability is therefore set to V4L2_TUNER_CAP_LOW + * The FREQ_MUL is then: 1 MHz / 62.5 Hz = 16000 + */ +#define FREQ_MUL (1000000 / 62.5) + +enum v4l2_cid_private_tavarua_t { + V4L2_CID_PRIVATE_TAVARUA_SRCHMODE = (V4L2_CID_PRIVATE_BASE + 1), + V4L2_CID_PRIVATE_TAVARUA_SCANDWELL, + V4L2_CID_PRIVATE_TAVARUA_SRCHON, + V4L2_CID_PRIVATE_TAVARUA_STATE, + V4L2_CID_PRIVATE_TAVARUA_TRANSMIT_MODE, + V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK, + V4L2_CID_PRIVATE_TAVARUA_REGION, + V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH, + V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY, + V4L2_CID_PRIVATE_TAVARUA_SRCH_PI, + V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT, + V4L2_CID_PRIVATE_TAVARUA_EMPHASIS, + V4L2_CID_PRIVATE_TAVARUA_RDS_STD, + V4L2_CID_PRIVATE_TAVARUA_SPACING, + V4L2_CID_PRIVATE_TAVARUA_RDSON, + V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC, + V4L2_CID_PRIVATE_TAVARUA_LP_MODE, + V4L2_CID_PRIVATE_TAVARUA_ANTENNA, + V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF, + V4L2_CID_PRIVATE_TAVARUA_PSALL, + /*v4l2 Tx controls*/ + V4L2_CID_PRIVATE_TAVARUA_TX_SETPSREPEATCOUNT, + V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_PS_NAME, + V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_RT, + V4L2_CID_PRIVATE_TAVARUA_IOVERC, + V4L2_CID_PRIVATE_TAVARUA_INTDET, + V4L2_CID_PRIVATE_TAVARUA_MPX_DCC, + V4L2_CID_PRIVATE_TAVARUA_AF_JUMP, + V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA, + V4L2_CID_PRIVATE_TAVARUA_HLSI, + + /* + * Here we have IOCTl's that are specific to IRIS + * (V4L2_CID_PRIVATE_BASE + 0x1E to V4L2_CID_PRIVATE_BASE + 0x28) + */ + V4L2_CID_PRIVATE_SOFT_MUTE,/* 0x800001E*/ + V4L2_CID_PRIVATE_RIVA_ACCS_ADDR, + V4L2_CID_PRIVATE_RIVA_ACCS_LEN, + V4L2_CID_PRIVATE_RIVA_PEEK, + V4L2_CID_PRIVATE_RIVA_POKE, + V4L2_CID_PRIVATE_SSBI_ACCS_ADDR, + V4L2_CID_PRIVATE_SSBI_PEEK, + V4L2_CID_PRIVATE_SSBI_POKE, + V4L2_CID_PRIVATE_TX_TONE, + V4L2_CID_PRIVATE_RDS_GRP_COUNTERS, + V4L2_CID_PRIVATE_SET_NOTCH_FILTER,/* 0x8000028 */ + + V4L2_CID_PRIVATE_TAVARUA_SET_AUDIO_PATH,/* 0x8000029 */ + V4L2_CID_PRIVATE_TAVARUA_DO_CALIBRATION,/* 0x800002A : IRIS */ + V4L2_CID_PRIVATE_TAVARUA_SRCH_ALGORITHM,/* 0x800002B */ + V4L2_CID_PRIVATE_IRIS_GET_SINR, /* 0x800002C : IRIS */ + V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD, /* 0x800002D */ + V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD, /* 0x800002E */ + V4L2_CID_PRIVATE_SINR_THRESHOLD, /* 0x800002F : IRIS */ + V4L2_CID_PRIVATE_SINR_SAMPLES, /* 0x8000030 : IRIS */ + +}; + +enum tavarua_buf_t { + TAVARUA_BUF_SRCH_LIST, + TAVARUA_BUF_EVENTS, + TAVARUA_BUF_RT_RDS, + TAVARUA_BUF_PS_RDS, + TAVARUA_BUF_RAW_RDS, + TAVARUA_BUF_AF_LIST, + TAVARUA_BUF_MAX +}; + +enum tavarua_xfr_t { + TAVARUA_XFR_SYNC, + TAVARUA_XFR_ERROR, + TAVARUA_XFR_SRCH_LIST, + TAVARUA_XFR_RT_RDS, + TAVARUA_XFR_PS_RDS, + TAVARUA_XFR_AF_LIST, + TAVARUA_XFR_MAX +}; + +enum channel_spacing { + FM_CH_SPACE_200KHZ, + FM_CH_SPACE_100KHZ, + FM_CH_SPACE_50KHZ +}; + +enum step_size { + NO_SRCH200khz, + ENF_SRCH200khz +}; + +enum emphasis { + EMP_75, + EMP_50 +}; + +enum rds_std { + RBDS_STD, + RDS_STD +}; + +/* offsets */ +#define RAW_RDS 0x0F +#define RDS_BLOCK 3 + +/* registers*/ +#define MARIMBA_XO_BUFF_CNTRL 0x07 +#define RADIO_REGISTERS 0x30 +#define XFR_REG_NUM 16 +#define STATUS_REG_NUM 3 + +/* TX constants */ +#define HEADER_SIZE 4 +#define TX_ON 0x80 +#define TAVARUA_TX_RT RDS_RT_0 +#define TAVARUA_TX_PS RDS_PS_0 + +enum register_t { + STATUS_REG1 = 0, + STATUS_REG2, + STATUS_REG3, + RDCTRL, + FREQ, + TUNECTRL, + SRCHRDS1, + SRCHRDS2, + SRCHCTRL, + IOCTRL, + RDSCTRL, + ADVCTRL, + AUDIOCTRL, + RMSSI, + IOVERC, + AUDIOIND = 0x1E, + XFRCTRL, + FM_CTL0 = 0xFF, + LEAKAGE_CNTRL = 0xFE, +}; +#define BAHAMA_RBIAS_CTL1 0x07 +#define BAHAMA_FM_MODE_REG 0xFD +#define BAHAMA_FM_CTL1_REG 0xFE +#define BAHAMA_FM_CTL0_REG 0xFF +#define BAHAMA_FM_MODE_NORMAL 0x00 +#define BAHAMA_LDO_DREG_CTL0 0xF0 +#define BAHAMA_LDO_AREG_CTL0 0xF4 + +/* Radio Control */ +#define RDCTRL_STATE_OFFSET 0 +#define RDCTRL_STATE_MASK (3 << RDCTRL_STATE_OFFSET) +#define RDCTRL_BAND_OFFSET 2 +#define RDCTRL_BAND_MASK (1 << RDCTRL_BAND_OFFSET) +#define RDCTRL_CHSPACE_OFFSET 3 +#define RDCTRL_CHSPACE_MASK (3 << RDCTRL_CHSPACE_OFFSET) +#define RDCTRL_DEEMPHASIS_OFFSET 5 +#define RDCTRL_DEEMPHASIS_MASK (1 << RDCTRL_DEEMPHASIS_OFFSET) +#define RDCTRL_HLSI_OFFSET 6 +#define RDCTRL_HLSI_MASK (3 << RDCTRL_HLSI_OFFSET) +#define RDSAF_OFFSET 6 +#define RDSAF_MASK (1 << RDSAF_OFFSET) + +/* Tune Control */ +#define TUNE_STATION 0x01 +#define ADD_OFFSET (1 << 1) +#define SIGSTATE (1 << 5) +#define MOSTSTATE (1 << 6) +#define RDSSYNC (1 << 7) +/* Search Control */ +#define SRCH_MODE_OFFSET 0 +#define SRCH_MODE_MASK (7 << SRCH_MODE_OFFSET) +#define SRCH_DIR_OFFSET 3 +#define SRCH_DIR_MASK (1 << SRCH_DIR_OFFSET) +#define SRCH_DWELL_OFFSET 4 +#define SRCH_DWELL_MASK (7 << SRCH_DWELL_OFFSET) +#define SRCH_STATE_OFFSET 7 +#define SRCH_STATE_MASK (1 << SRCH_STATE_OFFSET) + +/* I/O Control */ +#define IOC_HRD_MUTE 0x03 +#define IOC_SFT_MUTE (1 << 2) +#define IOC_MON_STR (1 << 3) +#define IOC_SIG_BLND (1 << 4) +#define IOC_INTF_BLND (1 << 5) +#define IOC_ANTENNA (1 << 6) +#define IOC_ANTENNA_OFFSET 6 +#define IOC_ANTENNA_MASK (1 << IOC_ANTENNA_OFFSET) + +/* RDS Control */ +#define RDS_ON 0x01 +#define RDSCTRL_STANDARD_OFFSET 1 +#define RDSCTRL_STANDARD_MASK (1 << RDSCTRL_STANDARD_OFFSET) + +/* Advanced features controls */ +#define RDSRTEN (1 << 3) +#define RDSPSEN (1 << 4) + +/* Audio path control */ +#define AUDIORX_ANALOG_OFFSET 0 +#define AUDIORX_ANALOG_MASK (1 << AUDIORX_ANALOG_OFFSET) +#define AUDIORX_DIGITAL_OFFSET 1 +#define AUDIORX_DIGITAL_MASK (1 << AUDIORX_DIGITAL_OFFSET) +#define AUDIOTX_OFFSET 2 +#define AUDIOTX_MASK (1 << AUDIOTX_OFFSET) +#define I2SCTRL_OFFSET 3 +#define I2SCTRL_MASK (1 << I2SCTRL_OFFSET) + +/* Search options */ +enum search_t { + SEEK, + SCAN, + SCAN_FOR_STRONG, + SCAN_FOR_WEAK, + RDS_SEEK_PTY, + RDS_SCAN_PTY, + RDS_SEEK_PI, + RDS_AF_JUMP, +}; + +enum audio_path { + FM_DIGITAL_PATH, + FM_ANALOG_PATH +}; +#define SRCH_MODE 0x07 +#define SRCH_DIR 0x08 /* 0-up 1-down */ +#define SCAN_DWELL 0x70 +#define SRCH_ON 0x80 + +/* RDS CONFIG */ +#define RDS_CONFIG_PSALL 0x01 + +#define FM_ENABLE 0x22 +#define SET_REG_FIELD(reg, val, offset, mask) \ + (reg = (reg & ~mask) | (((val) << offset) & mask)) +#define GET_REG_FIELD(reg, offset, mask) ((reg & mask) >> offset) +#define RSH_DATA(val, offset) ((val) >> (offset)) +#define LSH_DATA(val, offset) ((val) << (offset)) +#define GET_ABS_VAL(val) ((val) & (0xFF)) + +enum radio_state_t { + FM_OFF, + FM_RECV, + FM_TRANS, + FM_RESET, +}; + +#define XFRCTRL_WRITE (1 << 7) + +/* Interrupt status */ + +/* interrupt register 1 */ +#define READY (1 << 0) /* Radio ready after powerup or reset */ +#define TUNE (1 << 1) /* Tune completed */ +#define SEARCH (1 << 2) /* Search completed (read FREQ) */ +#define SCANNEXT (1 << 3) /* Scanning for next station */ +#define SIGNAL (1 << 4) /* Signal indicator change (read SIGSTATE) */ +#define INTF (1 << 5) /* Interference cnt has fallen outside range */ +#define SYNC (1 << 6) /* RDS sync state change (read RDSSYNC) */ +#define AUDIO (1 << 7) /* Audio Control indicator (read AUDIOIND) */ + +/* interrupt register 2 */ +#define RDSDAT (1 << 0) /* New unread RDS data group available */ +#define BLOCKB (1 << 1) /* Block-B match condition exists */ +#define PROGID (1 << 2) /* Block-A or Block-C matched stored PI value*/ +#define RDSPS (1 << 3) /* New RDS Program Service Table available */ +#define RDSRT (1 << 4) /* New RDS Radio Text available */ +#define RDSAF (1 << 5) /* New RDS AF List available */ +#define TXRDSDAT (1 << 6) /* Transmitted an RDS group */ +#define TXRDSDONE (1 << 7) /* RDS raw group one-shot transmit completed */ + +/* interrupt register 3 */ +#define TRANSFER (1 << 0) /* Data transfer (XFR) completed */ +#define RDSPROC (1 << 1) /* Dynamic RDS Processing complete */ +#define ERROR (1 << 7) /* Err occurred.Read code to determine cause */ + + +#define FM_TX_PWR_LVL_0 0 /* Lowest power lvl that can be set for Tx */ +#define FM_TX_PWR_LVL_MAX 7 /* Max power lvl for Tx */ +/* Transfer */ +enum tavarua_xfr_ctrl_t { + RDS_PS_0 = 0x01, + RDS_PS_1, + RDS_PS_2, + RDS_PS_3, + RDS_PS_4, + RDS_PS_5, + RDS_PS_6, + RDS_RT_0, + RDS_RT_1, + RDS_RT_2, + RDS_RT_3, + RDS_RT_4, + RDS_AF_0, + RDS_AF_1, + RDS_CONFIG, + RDS_TX_GROUPS, + RDS_COUNT_0, + RDS_COUNT_1, + RDS_COUNT_2, + RADIO_CONFIG, + RX_CONFIG, + RX_TIMERS, + RX_STATIONS_0, + RX_STATIONS_1, + INT_CTRL, + ERROR_CODE, + CHIPID, + CAL_DAT_0 = 0x20, + CAL_DAT_1, + CAL_DAT_2, + CAL_DAT_3, + CAL_CFG_0, + CAL_CFG_1, + DIG_INTF_0, + DIG_INTF_1, + DIG_AGC_0, + DIG_AGC_1, + DIG_AGC_2, + DIG_AUDIO_0, + DIG_AUDIO_1, + DIG_AUDIO_2, + DIG_AUDIO_3, + DIG_AUDIO_4, + DIG_RXRDS, + DIG_DCC, + DIG_SPUR, + DIG_MPXDCC, + DIG_PILOT, + DIG_DEMOD, + DIG_MOST, + DIG_TX_0, + DIG_TX_1, + PHY_TXGAIN = 0x3B, + PHY_CONFIG, + PHY_TXBLOCK, + PHY_TCB, + XFR_PEEK_MODE = 0x40, + XFR_POKE_MODE = 0xC0, + TAVARUA_XFR_CTRL_MAX +}; + +enum tavarua_evt_t { + TAVARUA_EVT_RADIO_READY, + TAVARUA_EVT_TUNE_SUCC, + TAVARUA_EVT_SEEK_COMPLETE, + TAVARUA_EVT_SCAN_NEXT, + TAVARUA_EVT_NEW_RAW_RDS, + TAVARUA_EVT_NEW_RT_RDS, + TAVARUA_EVT_NEW_PS_RDS, + TAVARUA_EVT_ERROR, + TAVARUA_EVT_BELOW_TH, + TAVARUA_EVT_ABOVE_TH, + TAVARUA_EVT_STEREO, + TAVARUA_EVT_MONO, + TAVARUA_EVT_RDS_AVAIL, + TAVARUA_EVT_RDS_NOT_AVAIL, + TAVARUA_EVT_NEW_SRCH_LIST, + TAVARUA_EVT_NEW_AF_LIST, + TAVARUA_EVT_TXRDSDAT, + TAVARUA_EVT_TXRDSDONE, + TAVARUA_EVT_RADIO_DISABLED +}; + +enum tavarua_region_t { + TAVARUA_REGION_US, + TAVARUA_REGION_EU, + TAVARUA_REGION_JAPAN, + TAVARUA_REGION_JAPAN_WIDE, + TAVARUA_REGION_OTHER +}; + +#endif /* __LINUX_TAVARUA_H */ |