diff options
Diffstat (limited to 'hal/gonk/UeventPoller.cpp')
-rw-r--r-- | hal/gonk/UeventPoller.cpp | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/hal/gonk/UeventPoller.cpp b/hal/gonk/UeventPoller.cpp new file mode 100644 index 000000000..3fbe850ed --- /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 + |