Bug 1041085 - FM RDS support in HAL, r=dhylands
☠☠ backed out by a429129c1ceb ☠ ☠
authorMichael Wu <mwu@mozilla.com>
Thu, 26 Jun 2014 05:07:23 -0400
changeset 208042 d1ad5d5bc00f29efc3bee980a31095814100fa29
parent 208041 8eb61163582b998f8a6d16b32e515a9f18763403
child 208043 dfbae87208dd532ab3280fb8b63837829174448b
push id9082
push userkwierso@gmail.com
push dateWed, 01 Oct 2014 00:13:52 +0000
treeherderfx-team@3e171f1759c9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdhylands
bugs1041085
milestone35.0a1
Bug 1041085 - FM RDS support in HAL, r=dhylands
hal/Hal.cpp
hal/Hal.h
hal/HalTypes.h
hal/fallback/FallbackFMRadio.cpp
hal/gonk/GonkFMRadio.cpp
hal/sandbox/PHal.ipdl
hal/sandbox/SandboxHal.cpp
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -999,22 +999,25 @@ ProcessPriorityToString(ProcessPriority 
     break;
   }
 
   MOZ_ASSERT(false);
   return "???";
 }
 
 static StaticAutoPtr<ObserverList<FMRadioOperationInformation> > sFMRadioObservers;
+static StaticAutoPtr<ObserverList<FMRadioRDSGroup> > sFMRadioRDSObservers;
 
 static void
 InitializeFMRadioObserver()
 {
   if (!sFMRadioObservers) {
     sFMRadioObservers = new ObserverList<FMRadioOperationInformation>;
+    sFMRadioRDSObservers = new ObserverList<FMRadioRDSGroup>;
+    ClearOnShutdown(&sFMRadioRDSObservers);
     ClearOnShutdown(&sFMRadioObservers);
   }
 }
 
 void
 RegisterFMRadioObserver(FMRadioObserver* aFMRadioObserver) {
   AssertMainThread();
   InitializeFMRadioObserver();
@@ -1030,16 +1033,37 @@ UnregisterFMRadioObserver(FMRadioObserve
 
 void
 NotifyFMRadioStatus(const FMRadioOperationInformation& aFMRadioState) {
   InitializeFMRadioObserver();
   sFMRadioObservers->Broadcast(aFMRadioState);
 }
 
 void
+RegisterFMRadioRDSObserver(FMRadioRDSObserver* aFMRadioRDSObserver) {
+  AssertMainThread();
+  InitializeFMRadioObserver();
+  sFMRadioRDSObservers->AddObserver(aFMRadioRDSObserver);
+}
+
+void
+UnregisterFMRadioRDSObserver(FMRadioRDSObserver* aFMRadioRDSObserver) {
+  AssertMainThread();
+  InitializeFMRadioObserver();
+  sFMRadioRDSObservers->RemoveObserver(aFMRadioRDSObserver);
+}
+
+
+void
+NotifyFMRadioRDSGroup(const FMRadioRDSGroup& aRDSGroup) {
+  InitializeFMRadioObserver();
+  sFMRadioRDSObservers->Broadcast(aRDSGroup);
+}
+
+void
 EnableFMRadio(const FMRadioSettings& aInfo) {
   AssertMainThread();
   PROXY_IF_SANDBOXED(EnableFMRadio(aInfo));
 }
 
 void
 DisableFMRadio() {
   AssertMainThread();
@@ -1083,16 +1107,28 @@ GetFMRadioSignalStrength() {
 }
 
 void
 CancelFMRadioSeek() {
   AssertMainThread();
   PROXY_IF_SANDBOXED(CancelFMRadioSeek());
 }
 
+void
+EnableRDS(uint32_t aMask) {
+  AssertMainThread();
+  PROXY_IF_SANDBOXED(EnableRDS(aMask));
+}
+
+void
+DisableRDS() {
+  AssertMainThread();
+  PROXY_IF_SANDBOXED(DisableRDS());
+}
+
 FMRadioSettings
 GetFMBandSettings(FMRadioCountry aCountry) {
   FMRadioSettings settings;
 
   switch (aCountry) {
     case FM_RADIO_COUNTRY_US:
     case FM_RADIO_COUNTRY_EU:
       settings.upperLimit() = 108000;
--- a/hal/Hal.h
+++ b/hal/Hal.h
@@ -499,22 +499,38 @@ void SetCurrentThreadPriority(hal::Threa
 void RegisterFMRadioObserver(hal::FMRadioObserver* aRadioObserver);
 
 /**
  * Unregister the observer for the FM radio.
  */
 void UnregisterFMRadioObserver(hal::FMRadioObserver* aRadioObserver);
 
 /**
+ * Register an observer for the FM radio.
+ */
+void RegisterFMRadioRDSObserver(hal::FMRadioRDSObserver* aRDSObserver);
+
+/**
+ * Unregister the observer for the FM radio.
+ */
+void UnregisterFMRadioRDSObserver(hal::FMRadioRDSObserver* aRDSObserver);
+
+/**
  * Notify observers that a call to EnableFMRadio, DisableFMRadio, or FMRadioSeek
  * has completed, and indicate what the call returned.
  */
 void NotifyFMRadioStatus(const hal::FMRadioOperationInformation& aRadioState);
 
 /**
+ * Notify observers of new RDS data
+ * This can be called on any thread.
+ */
+void NotifyFMRadioRDSGroup(const hal::FMRadioRDSGroup& aRDSGroup);
+
+/**
  * Enable the FM radio and configure it according to the settings in aInfo.
  */
 void EnableFMRadio(const hal::FMRadioSettings& aInfo);
 
 /**
  * Disable the FM radio.
  */
 void DisableFMRadio();
@@ -561,16 +577,26 @@ uint32_t GetFMRadioSignalStrength();
 void CancelFMRadioSeek();
 
 /**
  * Get FM radio band settings by country.
  */
 hal::FMRadioSettings GetFMBandSettings(hal::FMRadioCountry aCountry);
 
 /**
+ * Enable RDS data reception
+ */
+void EnableRDS(uint32_t aMask);
+
+/**
+ * Disable RDS data reception
+ */
+void DisableRDS();
+
+/**
  * Start a watchdog to compulsively shutdown the system if it hangs.
  * @param aMode Specify how to shutdown the system.
  * @param aTimeoutSecs Specify the delayed seconds to shutdown the system.
  *
  * This API is currently only allowed to be used from the main process.
  */
 void StartForceQuitWatchdog(hal::ShutdownMode aMode, int32_t aTimeoutSecs);
 
--- a/hal/HalTypes.h
+++ b/hal/HalTypes.h
@@ -220,17 +220,19 @@ enum FMRadioCountry {
   FM_RADIO_COUNTRY_CH,  //Switzerland
   FM_RADIO_COUNTRY_TW,  //Taiwan
   FM_RADIO_COUNTRY_TR,  //Turkey
   FM_RADIO_COUNTRY_UA,  //Ukraine
   FM_RADIO_COUNTRY_USER_DEFINED,
   NUM_FM_RADIO_COUNTRY
 };
 
+class FMRadioRDSGroup;
 typedef Observer<FMRadioOperationInformation> FMRadioObserver;
+typedef Observer<FMRadioRDSGroup> FMRadioRDSObserver;
 } // namespace hal
 } // namespace mozilla
 
 namespace IPC {
 
 /**
  * Light type serializer.
  */
--- a/hal/fallback/FallbackFMRadio.cpp
+++ b/hal/fallback/FallbackFMRadio.cpp
@@ -52,10 +52,18 @@ GetFMRadioSignalStrength()
 {
   return 0;
 }
 
 void
 CancelFMRadioSeek()
 {}
 
+void
+EnableRDS(uint32_t aMask)
+{}
+
+void
+DisableRDS()
+{}
+
 } // hal_impl
 } // namespace mozilla
--- a/hal/gonk/GonkFMRadio.cpp
+++ b/hal/gonk/GonkFMRadio.cpp
@@ -20,16 +20,17 @@
 #include "mozilla/FileUtils.h"
 
 #include <cutils/properties.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/videodev2.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/epoll.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
 
 /* Bionic might not have the newer version of the v4l2 headers that
  * define these controls, so we define them here if they're not found.
  */
 #ifndef V4L2_CTRL_CLASS_FM_RX
@@ -44,20 +45,23 @@
 
 namespace mozilla {
 namespace hal_impl {
 
 uint32_t GetFMRadioFrequency();
 
 static int sRadioFD;
 static bool sRadioEnabled;
+static bool sRDSEnabled;
 static pthread_t sRadioThread;
+static pthread_t sRDSThread;
 static hal::FMRadioSettings sRadioSettings;
 static int sMsmFMVersion;
 static bool sMsmFMMode;
+static bool sRDSSupported;
 
 static int
 setControl(uint32_t id, int32_t value)
 {
   struct v4l2_control control;
   control.id = id;
   control.value = value;
   return ioctl(sRadioFD, VIDIOC_S_CTRL, &control);
@@ -306,16 +310,18 @@ EnableFMRadio(const hal::FMRadioSettings
     return;
   }
 
   if (!(cap.capabilities & V4L2_CAP_TUNER)) {
     HAL_LOG("/dev/radio0 doesn't support the tuner interface");
     hal::NotifyFMRadioStatus(info);
     return;
   }
+
+  sRDSSupported = cap.capabilities & V4L2_CAP_RDS_CAPTURE;
   sRadioSettings = aInfo;
 
   if (sMsmFMMode) {
     sRadioFD = fd.forget();
     sMsmFMVersion = cap.version;
     if (pthread_create(&sRadioThread, nullptr, runMsmFMRadio, nullptr)) {
       HAL_LOG("Couldn't create radio thread");
       hal::NotifyFMRadioStatus(info);
@@ -361,16 +367,19 @@ EnableFMRadio(const hal::FMRadioSettings
 }
 
 void
 DisableFMRadio()
 {
   if (!sRadioEnabled)
     return;
 
+  if (sRDSEnabled)
+    DisableRDS();
+
   sRadioEnabled = false;
 
   if (sMsmFMMode) {
     int rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_OFF);
     if (rc < 0) {
       HAL_LOG("Unable to turn off radio");
     }
 
@@ -489,10 +498,191 @@ GetFMRadioSignalStrength()
 
   return tuner.signal;
 }
 
 void
 CancelFMRadioSeek()
 {}
 
+/* Runs on the rds thread */
+static void*
+readRDSDataThread(void* data)
+{
+  v4l2_rds_data rdsblocks[16];
+  uint16_t blocks[4];
+
+  ScopedClose pipefd((int)data);
+
+  ScopedClose epollfd(epoll_create(2));
+  if (epollfd < 0) {
+    HAL_LOG(("Could not create epoll FD for RDS thread (%d)", errno));
+    return nullptr;
+  }
+
+  epoll_event event = {
+    EPOLLIN,
+    { 0 }
+  };
+
+  event.data.fd = pipefd;
+  if (epoll_ctl(epollfd, EPOLL_CTL_ADD, pipefd, &event) < 0) {
+    HAL_LOG(("Could not set up epoll FD for RDS thread (%d)", errno));
+    return nullptr;
+  }
+
+  event.data.fd = sRadioFD;
+  if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sRadioFD, &event) < 0) {
+    HAL_LOG(("Could not set up epoll FD for RDS thread (%d)", errno));
+    return nullptr;
+  }
+
+  epoll_event events[2] = {{ 0 }};
+  int event_count;
+  uint32_t block_bitmap = 0;
+  while ((event_count = epoll_wait(epollfd, events, 2, -1)) > 0 ||
+         errno == EINTR) {
+    bool RDSDataAvailable = false;
+    for (int i = 0; i < event_count; i++) {
+      if (events[i].data.fd == pipefd) {
+        if (!sRDSEnabled)
+          return nullptr;
+        char tmp[32];
+        TEMP_FAILURE_RETRY(read(pipefd, tmp, sizeof(tmp)));
+      } else if (events[i].data.fd == sRadioFD) {
+        RDSDataAvailable = true;
+      }
+    }
+
+    if (!RDSDataAvailable)
+      continue;
+
+    ssize_t len =
+      TEMP_FAILURE_RETRY(read(sRadioFD, rdsblocks, sizeof(rdsblocks)));
+    if (len < 0) {
+      HAL_LOG(("Unexpected error while reading RDS data %d", errno));
+      return nullptr;
+    }
+
+    int blockcount = len / sizeof(rdsblocks[0]);
+    int lastblock = -1;
+    for (int i = 0; i < blockcount; i++) {
+      if ((rdsblocks[i].block & V4L2_RDS_BLOCK_MSK) == V4L2_RDS_BLOCK_INVALID ||
+           rdsblocks[i].block & V4L2_RDS_BLOCK_ERROR) {
+        block_bitmap |= 1 << V4L2_RDS_BLOCK_INVALID;
+        continue;
+      }
+
+      int blocknum = rdsblocks[i].block & V4L2_RDS_BLOCK_MSK;
+      // In some cases, the full set of bits in an RDS group isn't
+      // needed, in which case version B RDS groups can be sent.
+      // Version B groups replace block C with block C' (V4L2_RDS_BLOCK_C_ALT).
+      // Block C' always stores the PI code, so receivers can find the PI
+      // code more quickly/reliably.
+      // However, we only process whole RDS groups, so it doesn't matter here.
+      if (blocknum == V4L2_RDS_BLOCK_C_ALT)
+        blocknum = V4L2_RDS_BLOCK_C;
+      if (blocknum > V4L2_RDS_BLOCK_D) {
+        HAL_LOG(("Unexpected RDS block number %d. This is a driver bug.",
+                blocknum));
+        continue;
+      }
+
+      if (blocknum == V4L2_RDS_BLOCK_A)
+        block_bitmap = 0;
+
+      // Skip the group if we skipped a block.
+      // This stops us from processing blocks sent out of order.
+      if (block_bitmap != ((1 << blocknum) - 1)) {
+        block_bitmap |= 1 << V4L2_RDS_BLOCK_INVALID;
+        continue;
+      }
+
+      block_bitmap |= 1 << blocknum;
+
+      lastblock = blocknum;
+      blocks[blocknum] = (rdsblocks[i].msb << 8) | rdsblocks[i].lsb;
+
+      // Make sure we have all 4 blocks and that they're valid
+      if (block_bitmap != 0x0F)
+        continue;
+
+      FMRadioRDSGroup group;
+      group.blockA() = blocks[V4L2_RDS_BLOCK_A];
+      group.blockB() = blocks[V4L2_RDS_BLOCK_B];
+      group.blockC() = blocks[V4L2_RDS_BLOCK_C];
+      group.blockD() = blocks[V4L2_RDS_BLOCK_D];
+      NotifyFMRadioRDSGroup(group);
+    }
+  }
+
+  return nullptr;
+}
+
+static int sRDSPipeFD;
+
+void
+EnableRDS(uint32_t aMask)
+{
+  if (!sRadioEnabled || !sRDSSupported)
+    return;
+
+  if (sMsmFMMode)
+    setControl(V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK, aMask);
+
+  if (sRDSEnabled)
+    return;
+
+  int pipefd[2];
+  int rc = pipe2(pipefd, O_NONBLOCK);
+  if (rc < 0) {
+    HAL_LOG(("Could not create RDS thread signaling pipes (%d)", rc));
+    return;
+  }
+
+  ScopedClose writefd(pipefd[1]);
+  ScopedClose readfd(pipefd[0]);
+
+  rc = setControl(V4L2_CID_RDS_RECEPTION, true);
+  if (rc < 0) {
+    HAL_LOG(("Could not enable RDS reception (%d)", rc));
+    return;
+  }
+
+  sRDSPipeFD = writefd;
+
+  sRDSEnabled = true;
+
+  rc = pthread_create(&sRDSThread, nullptr,
+                      readRDSDataThread, (void*)pipefd[0]);
+  if (rc) {
+    HAL_LOG(("Could not start RDS reception thread (%d)", rc));
+    setControl(V4L2_CID_RDS_RECEPTION, false);
+    sRDSEnabled = false;
+    return;
+  }
+
+  readfd.forget();
+  writefd.forget();
+}
+
+void
+DisableRDS()
+{
+  if (!sRadioEnabled || !sRDSEnabled)
+    return;
+
+  int rc = setControl(V4L2_CID_RDS_RECEPTION, false);
+  if (rc < 0) {
+    HAL_LOG(("Could not disable RDS reception (%d)", rc));
+  }
+
+  sRDSEnabled = false;
+
+  write(sRDSPipeFD, "x", 1);
+
+  pthread_join(sRDSThread, nullptr);
+
+  close(sRDSPipeFD);
+}
+
 } // hal_impl
 } // namespace mozilla
--- a/hal/sandbox/PHal.ipdl
+++ b/hal/sandbox/PHal.ipdl
@@ -64,16 +64,23 @@ struct ScreenConfiguration {
 };
 
 struct FMRadioOperationInformation {
   FMRadioOperation operation;
   FMRadioOperationStatus status;
   uint32_t frequency;
 };
 
+struct FMRadioRDSGroup {
+  uint16_t blockA;
+  uint16_t blockB;
+  uint16_t blockC;
+  uint16_t blockD;
+};
+
 struct FMRadioSettings {
   FMRadioCountry country;
   uint32_t upperLimit;
   uint32_t lowerLimit;
   uint32_t spaceType;
   uint32_t preEmphasis;
 };
 
--- a/hal/sandbox/SandboxHal.cpp
+++ b/hal/sandbox/SandboxHal.cpp
@@ -422,16 +422,28 @@ GetFMRadioSignalStrength()
 
 void
 CancelFMRadioSeek()
 {
   NS_RUNTIMEABORT("FM radio cannot be called from sandboxed contexts.");
 }
 
 void
+EnableRDS(uint32_t aMask)
+{
+  NS_RUNTIMEABORT("FM radio cannot be called from sandboxed contexts.");
+}
+
+void
+DisableRDS()
+{
+  NS_RUNTIMEABORT("FM radio cannot be called from sandboxed contexts.");
+}
+
+void
 FactoryReset(FactoryResetReason& aReason)
 {
   if (aReason == FactoryResetReason::Normal) {
     Hal()->SendFactoryReset(NS_LITERAL_STRING("normal"));
   } else if (aReason == FactoryResetReason::Wipe) {
     Hal()->SendFactoryReset(NS_LITERAL_STRING("wipe"));
   }
 }