Bug 1041085 - FM RDS support in HAL, r=dhylands
--- 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
@@ -37,27 +38,47 @@
#define V4L2_CID_FM_RX_CLASS_BASE (V4L2_CTRL_CLASS_FM_RX | 0x900)
#define V4L2_CID_TUNE_DEEMPHASIS (V4L2_CID_FM_RX_CLASS_BASE + 1)
#define V4L2_DEEMPHASIS_DISABLED 0
#define V4L2_DEEMPHASIS_50_uS 1
#define V4L2_DEEMPHASIS_75_uS 2
#define V4L2_CID_RDS_RECEPTION (V4L2_CID_FM_RX_CLASS_BASE + 2)
#endif
+#ifndef V4L2_RDS_BLOCK_MSK
+struct v4l2_rds_data {
+ uint8_t lsb;
+ uint8_t msb;
+ uint8_t block;
+} __attribute__ ((packed));
+#define V4L2_RDS_BLOCK_MSK 0x7
+#define V4L2_RDS_BLOCK_A 0
+#define V4L2_RDS_BLOCK_B 1
+#define V4L2_RDS_BLOCK_C 2
+#define V4L2_RDS_BLOCK_D 3
+#define V4L2_RDS_BLOCK_C_ALT 4
+#define V4L2_RDS_BLOCK_INVALID 7
+#define V4L2_RDS_BLOCK_CORRECTED 0x40
+#define V4L2_RDS_BLOCK_ERROR 0x80
+#endif
+
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 +327,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 +384,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 +515,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"));
}
}