From 97d2ba6a1130b36df752d631d002fd316165f710 Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Thu, 5 Mar 2015 12:52:14 -0800 Subject: radio stub HAL. radio HAL stub implementation to be used for test or reference. Change-Id: I83294501d0bc138bcce68eb83296da6f6f4438d3 --- modules/radio/Android.mk | 27 ++ modules/radio/radio_hw.c | 726 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 753 insertions(+) create mode 100644 modules/radio/Android.mk create mode 100644 modules/radio/radio_hw.c (limited to 'modules') diff --git a/modules/radio/Android.mk b/modules/radio/Android.mk new file mode 100644 index 0000000..f433c85 --- /dev/null +++ b/modules/radio/Android.mk @@ -0,0 +1,27 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# 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. + +LOCAL_PATH := $(call my-dir) + +# Stub radio HAL module, used for tests +include $(CLEAR_VARS) + +LOCAL_MODULE := radio.fm.default +LOCAL_MODULE_RELATIVE_PATH := hw +LOCAL_SRC_FILES := radio_hw.c +LOCAL_SHARED_LIBRARIES := liblog libcutils libradio_metadata +LOCAL_MODULE_TAGS := optional +LOCAL_32_BIT_ONLY := true + +include $(BUILD_SHARED_LIBRARY) diff --git a/modules/radio/radio_hw.c b/modules/radio/radio_hw.c new file mode 100644 index 0000000..b1a4d26 --- /dev/null +++ b/modules/radio/radio_hw.c @@ -0,0 +1,726 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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. + */ + +#define LOG_TAG "radio_hw_stub" +#define LOG_NDEBUG 0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const radio_hal_properties_t hw_properties = { + .class_id = RADIO_CLASS_AM_FM, + .implementor = "The Android Open Source Project", + .product = "Radio stub HAL", + .version = "0.1", + .serial = "0123456789", + .num_tuners = 1, + .num_audio_sources = 1, + .supports_capture = false, + .num_bands = 2, + .bands = { + { + .type = RADIO_BAND_FM, + .antenna_connected = false, + .lower_limit = 87900, + .upper_limit = 107900, + .num_spacings = 1, + .spacings = { 200 }, + .fm = { + .deemphasis = RADIO_DEEMPHASIS_75, + .stereo = true, + .rds = RADIO_RDS_US, + .ta = false, + .af = false, + } + }, + { + .type = RADIO_BAND_AM, + .antenna_connected = true, + .lower_limit = 540, + .upper_limit = 1610, + .num_spacings = 1, + .spacings = { 10 }, + .am = { + .stereo = true, + } + } + } +}; + +struct stub_radio_tuner { + struct radio_tuner interface; + struct stub_radio_device *dev; + radio_callback_t callback; + void *cookie; + radio_hal_band_config_t config; + radio_program_info_t program; + bool audio; + pthread_t callback_thread; + pthread_mutex_t lock; + pthread_cond_t cond; + struct listnode command_list; +}; + +struct stub_radio_device { + struct radio_hw_device device; + struct stub_radio_tuner *tuner; + pthread_mutex_t lock; +}; + + +typedef enum { + CMD_EXIT, + CMD_CONFIG, + CMD_STEP, + CMD_SCAN, + CMD_TUNE, + CMD_CANCEL, + CMD_METADATA, +} thread_cmd_type_t; + +struct thread_command { + struct listnode node; + thread_cmd_type_t type; + struct timespec ts; + union { + unsigned int param; + radio_hal_band_config_t config; + }; +}; + +/* must be called with out->lock locked */ +static int send_command_l(struct stub_radio_tuner *tuner, + thread_cmd_type_t type, + unsigned int delay_ms, + void *param) +{ + struct thread_command *cmd = (struct thread_command *)calloc(1, sizeof(struct thread_command)); + struct timespec ts; + + if (cmd == NULL) + return -ENOMEM; + + ALOGV("%s %d delay_ms %d", __func__, type, delay_ms); + + cmd->type = type; + if (param != NULL) { + if (cmd->type == CMD_CONFIG) { + cmd->config = *(radio_hal_band_config_t *)param; + ALOGV("%s CMD_CONFIG type %d", __func__, cmd->config.type); + } else + cmd->param = *(unsigned int *)param; + } + + clock_gettime(CLOCK_REALTIME, &ts); + + ts.tv_sec += delay_ms/1000; + ts.tv_nsec += (delay_ms%1000) * 1000000; + if (ts.tv_nsec >= 1000000000) { + ts.tv_nsec -= 1000000000; + ts.tv_sec += 1; + } + cmd->ts = ts; + list_add_tail(&tuner->command_list, &cmd->node); + pthread_cond_signal(&tuner->cond); + return 0; +} + +#define BITMAP_FILE_PATH "/data/misc/media/android.png" + +static int add_bitmap_metadata(radio_metadata_t **metadata, radio_metadata_key_t key, + const char *source) +{ + int fd; + ssize_t ret = 0; + struct stat info; + void *data = NULL; + size_t size; + + fd = open(source, O_RDONLY); + if (fd < 0) + return -EPIPE; + + fstat(fd, &info); + size = info.st_size; + data = malloc(size); + if (data == NULL) { + ret = -ENOMEM; + goto exit; + } + ret = read(fd, data, size); + if (ret < 0) + goto exit; + ret = radio_metadata_add_raw(metadata, key, (const unsigned char *)data, size); + +exit: + close(fd); + free(data); + ALOGE_IF(ret != 0, "%s error %d", __func__, ret); + return (int)ret; +} + +static int prepare_metadata(struct stub_radio_tuner *tuner, + radio_metadata_t **metadata, bool program) +{ + int ret = 0; + char text[RADIO_STRING_LEN_MAX]; + struct timespec ts; + + if (metadata == NULL) + return -EINVAL; + + if (*metadata != NULL) + radio_metadata_deallocate(*metadata); + + *metadata = NULL; + + ret = radio_metadata_allocate(metadata, tuner->program.channel, 0); + if (ret != 0) + return ret; + + if (program) { + ret = radio_metadata_add_int(metadata, RADIO_METADATA_KEY_RBDS_PTY, 5); + if (ret != 0) + goto exit; + ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_RDS_PS, "RockBand"); + if (ret != 0) + goto exit; + ret = add_bitmap_metadata(metadata, RADIO_METADATA_KEY_ICON, BITMAP_FILE_PATH); + if (ret != 0) + goto exit; + } else { + ret = add_bitmap_metadata(metadata, RADIO_METADATA_KEY_ART, BITMAP_FILE_PATH); + if (ret != 0) + goto exit; + } + + clock_gettime(CLOCK_REALTIME, &ts); + snprintf(text, RADIO_STRING_LEN_MAX, "Artist %ld", ts.tv_sec % 10); + ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_ARTIST, text); + if (ret != 0) + goto exit; + + snprintf(text, RADIO_STRING_LEN_MAX, "Song %ld", ts.tv_nsec % 10); + ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_TITLE, text); + if (ret != 0) + goto exit; + + return 0; + +exit: + radio_metadata_deallocate(*metadata); + *metadata = NULL; + return ret; +} + +static void *callback_thread_loop(void *context) +{ + struct stub_radio_tuner *tuner = (struct stub_radio_tuner *)context; + struct timespec ts = {0, 0}; + + ALOGI("%s", __func__); + + prctl(PR_SET_NAME, (unsigned long)"sound trigger callback", 0, 0, 0); + + pthread_mutex_lock(&tuner->lock); + + while (true) { + struct thread_command *cmd = NULL; + struct listnode *item; + struct listnode *tmp; + struct timespec cur_ts; + + if (list_empty(&tuner->command_list) || ts.tv_sec != 0) { + ALOGV("%s SLEEPING", __func__); + if (ts.tv_sec != 0) { + ALOGV("%s SLEEPING with timeout", __func__); + pthread_cond_timedwait(&tuner->cond, &tuner->lock, &ts); + } else { + ALOGV("%s SLEEPING forever", __func__); + pthread_cond_wait(&tuner->cond, &tuner->lock); + } + ts.tv_sec = 0; + ALOGV("%s RUNNING", __func__); + } + + clock_gettime(CLOCK_REALTIME, &cur_ts); + + list_for_each_safe(item, tmp, &tuner->command_list) { + cmd = node_to_item(item, struct thread_command, node); + + if ((cmd->ts.tv_sec < cur_ts.tv_sec) || + ((cmd->ts.tv_sec == cur_ts.tv_sec) && (cmd->ts.tv_nsec < cur_ts.tv_nsec))) { + radio_hal_event_t event; + + event.type = RADIO_EVENT_HW_FAILURE; + list_remove(item); + + ALOGV("%s processing command %d time %ld.%ld", __func__, cmd->type, cmd->ts.tv_sec, + cmd->ts.tv_nsec); + + switch (cmd->type) { + default: + case CMD_EXIT: + free(cmd); + goto exit; + + case CMD_CONFIG: { + tuner->config = cmd->config; + event.type = RADIO_EVENT_CONFIG; + event.config = tuner->config; + ALOGV("%s CMD_CONFIG type %d low %d up %d", + __func__, tuner->config.type, + tuner->config.lower_limit, tuner->config.upper_limit); + if (tuner->config.type == RADIO_BAND_FM) { + ALOGV(" - stereo %d\n - rds %d\n - ta %d\n - af %d", + tuner->config.fm.stereo, tuner->config.fm.rds, + tuner->config.fm.ta, tuner->config.fm.af); + } else { + ALOGV(" - stereo %d", tuner->config.am.stereo); + } + } break; + + case CMD_STEP: { + int frequency; + frequency = tuner->program.channel; + if (cmd->param == RADIO_DIRECTION_UP) { + frequency += tuner->config.spacings[0]; + } else { + frequency -= tuner->config.spacings[0]; + } + if (frequency > (int)tuner->config.upper_limit) { + frequency = tuner->config.lower_limit; + } + if (frequency < (int)tuner->config.lower_limit) { + frequency = tuner->config.upper_limit; + } + tuner->program.channel = frequency; + tuner->program.tuned = (frequency / (tuner->config.spacings[0] * 5)) % 2; + tuner->program.signal_strength = 20; + if (tuner->config.type == RADIO_BAND_FM) + tuner->program.stereo = false; + else + tuner->program.stereo = false; + + event.type = RADIO_EVENT_TUNED; + event.info = tuner->program; + } break; + + case CMD_SCAN: { + int frequency; + frequency = tuner->program.channel; + if (cmd->param == RADIO_DIRECTION_UP) { + frequency += tuner->config.spacings[0] * 25; + } else { + frequency -= tuner->config.spacings[0] * 25; + } + if (frequency > (int)tuner->config.upper_limit) { + frequency = tuner->config.lower_limit; + } + if (frequency < (int)tuner->config.lower_limit) { + frequency = tuner->config.upper_limit; + } + tuner->program.channel = (unsigned int)frequency; + tuner->program.tuned = true; + if (tuner->config.type == RADIO_BAND_FM) + tuner->program.stereo = tuner->config.fm.stereo; + else + tuner->program.stereo = tuner->config.am.stereo; + tuner->program.signal_strength = 50; + + event.type = RADIO_EVENT_TUNED; + event.info = tuner->program; + if (tuner->program.metadata != NULL) + radio_metadata_deallocate(tuner->program.metadata); + tuner->program.metadata = NULL; + send_command_l(tuner, CMD_METADATA, 2000, NULL); + } break; + + case CMD_TUNE: { + tuner->program.channel = cmd->param; + tuner->program.tuned = (tuner->program.channel / + (tuner->config.spacings[0] * 5)) % 2; + + if (tuner->program.tuned) { + prepare_metadata(tuner, &tuner->program.metadata, true); + send_command_l(tuner, CMD_METADATA, 5000, NULL); + } else { + if (tuner->program.metadata != NULL) + radio_metadata_deallocate(tuner->program.metadata); + tuner->program.metadata = NULL; + } + tuner->program.signal_strength = 100; + if (tuner->config.type == RADIO_BAND_FM) + tuner->program.stereo = + tuner->program.tuned ? tuner->config.fm.stereo : false; + else + tuner->program.stereo = + tuner->program.tuned ? tuner->config.am.stereo : false; + event.type = RADIO_EVENT_TUNED; + event.info = tuner->program; + } break; + + case CMD_METADATA: { + prepare_metadata(tuner, &tuner->program.metadata, false); + event.type = RADIO_EVENT_METADATA; + event.metadata = tuner->program.metadata; + } break; + + case CMD_CANCEL: { + struct listnode *tmp2; + list_for_each_safe(item, tmp2, &tuner->command_list) { + cmd = node_to_item(item, struct thread_command, node); + if (cmd->type == CMD_STEP || cmd->type == CMD_SCAN || + cmd->type == CMD_TUNE || cmd->type == CMD_METADATA) { + list_remove(item); + free(cmd); + } + } + } break; + + } + if (event.type != RADIO_EVENT_HW_FAILURE && tuner->callback != NULL) { + pthread_mutex_unlock(&tuner->lock); + tuner->callback(&event, tuner->cookie); + pthread_mutex_lock(&tuner->lock); + } + ALOGV("%s processed command %d", __func__, cmd->type); + free(cmd); + } else { + if ((ts.tv_sec == 0) || + (cmd->ts.tv_sec < ts.tv_sec) || + ((cmd->ts.tv_sec == ts.tv_sec) && (cmd->ts.tv_nsec < ts.tv_nsec))) { + ts.tv_sec = cmd->ts.tv_sec; + ts.tv_nsec = cmd->ts.tv_nsec; + } + } + } + } + +exit: + pthread_mutex_unlock(&tuner->lock); + + ALOGV("%s Exiting", __func__); + + return NULL; +} + + +static int tuner_set_configuration(const struct radio_tuner *tuner, + const radio_hal_band_config_t *config) +{ + struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; + int status = 0; + + ALOGI("%s stub_tuner %p", __func__, stub_tuner); + pthread_mutex_lock(&stub_tuner->lock); + if (config == NULL) { + status = -EINVAL; + goto exit; + } + send_command_l(stub_tuner, CMD_CANCEL, 0, NULL); + send_command_l(stub_tuner, CMD_CONFIG, 500, (void *)config); + +exit: + pthread_mutex_unlock(&stub_tuner->lock); + return status; +} + +static int tuner_get_configuration(const struct radio_tuner *tuner, + radio_hal_band_config_t *config) +{ + struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; + int status = 0; + struct listnode *item; + radio_hal_band_config_t *src_config; + + ALOGI("%s stub_tuner %p", __func__, stub_tuner); + pthread_mutex_lock(&stub_tuner->lock); + src_config = &stub_tuner->config; + + if (config == NULL) { + status = -EINVAL; + goto exit; + } + list_for_each(item, &stub_tuner->command_list) { + struct thread_command *cmd = node_to_item(item, struct thread_command, node); + if (cmd->type == CMD_CONFIG) { + src_config = &cmd->config; + } + } + *config = *src_config; + +exit: + pthread_mutex_unlock(&stub_tuner->lock); + return status; +} + +static int tuner_step(const struct radio_tuner *tuner, + radio_direction_t direction, bool skip_sub_channel) +{ + struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; + + ALOGI("%s stub_tuner %p direction %d, skip_sub_channel %d", + __func__, stub_tuner, direction, skip_sub_channel); + + pthread_mutex_lock(&stub_tuner->lock); + send_command_l(stub_tuner, CMD_STEP, 20, &direction); + pthread_mutex_unlock(&stub_tuner->lock); + return 0; +} + +static int tuner_scan(const struct radio_tuner *tuner, + radio_direction_t direction, bool skip_sub_channel) +{ + struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; + + ALOGI("%s stub_tuner %p direction %d, skip_sub_channel %d", + __func__, stub_tuner, direction, skip_sub_channel); + + pthread_mutex_lock(&stub_tuner->lock); + send_command_l(stub_tuner, CMD_SCAN, 200, &direction); + pthread_mutex_unlock(&stub_tuner->lock); + return 0; +} + +static int tuner_tune(const struct radio_tuner *tuner, + unsigned int channel, unsigned int sub_channel) +{ + struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; + + ALOGI("%s stub_tuner %p channel %d, sub_channel %d", + __func__, stub_tuner, channel, sub_channel); + + pthread_mutex_lock(&stub_tuner->lock); + if (channel < stub_tuner->config.lower_limit || channel > stub_tuner->config.upper_limit) { + pthread_mutex_unlock(&stub_tuner->lock); + ALOGI("%s channel out of range", __func__); + return -EINVAL; + } + send_command_l(stub_tuner, CMD_TUNE, 100, &channel); + pthread_mutex_unlock(&stub_tuner->lock); + return 0; +} + +static int tuner_cancel(const struct radio_tuner *tuner) +{ + struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; + + ALOGI("%s stub_tuner %p", __func__, stub_tuner); + + pthread_mutex_lock(&stub_tuner->lock); + send_command_l(stub_tuner, CMD_CANCEL, 0, NULL); + pthread_mutex_unlock(&stub_tuner->lock); + return 0; +} + +static int tuner_get_program_information(const struct radio_tuner *tuner, + radio_program_info_t *info) +{ + struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; + int status = 0; + radio_metadata_t *metadata; + + ALOGI("%s stub_tuner %p", __func__, stub_tuner); + pthread_mutex_lock(&stub_tuner->lock); + if (info == NULL) { + status = -EINVAL; + goto exit; + } + metadata = info->metadata; + *info = stub_tuner->program; + info->metadata = metadata; + if (metadata != NULL) + radio_metadata_add_metadata(&info->metadata, stub_tuner->program.metadata); + +exit: + pthread_mutex_unlock(&stub_tuner->lock); + return status; +} + +static int rdev_get_properties(const struct radio_hw_device *dev, + radio_hal_properties_t *properties) +{ + struct stub_radio_device *rdev = (struct stub_radio_device *)dev; + + ALOGI("%s", __func__); + if (properties == NULL) + return -EINVAL; + memcpy(properties, &hw_properties, sizeof(radio_hal_properties_t)); + return 0; +} + +static int rdev_open_tuner(const struct radio_hw_device *dev, + const radio_hal_band_config_t *config, + bool audio, + radio_callback_t callback, + void *cookie, + const struct radio_tuner **tuner) +{ + struct stub_radio_device *rdev = (struct stub_radio_device *)dev; + int status = 0; + + ALOGI("%s rdev %p", __func__, rdev); + pthread_mutex_lock(&rdev->lock); + + if (rdev->tuner != NULL) { + status = -ENOSYS; + goto exit; + } + + if (config == NULL || callback == NULL || tuner == NULL) { + status = -EINVAL; + goto exit; + } + + rdev->tuner = (struct stub_radio_tuner *)calloc(1, sizeof(struct stub_radio_tuner)); + if (rdev->tuner == NULL) { + status = -ENOMEM; + goto exit; + } + + rdev->tuner->interface.set_configuration = tuner_set_configuration; + rdev->tuner->interface.get_configuration = tuner_get_configuration; + rdev->tuner->interface.scan = tuner_scan; + rdev->tuner->interface.step = tuner_step; + rdev->tuner->interface.tune = tuner_tune; + rdev->tuner->interface.cancel = tuner_cancel; + rdev->tuner->interface.get_program_information = tuner_get_program_information; + + rdev->tuner->audio = audio; + rdev->tuner->callback = callback; + rdev->tuner->cookie = cookie; + + rdev->tuner->dev = rdev; + + pthread_mutex_init(&rdev->tuner->lock, (const pthread_mutexattr_t *) NULL); + pthread_cond_init(&rdev->tuner->cond, (const pthread_condattr_t *) NULL); + pthread_create(&rdev->tuner->callback_thread, (const pthread_attr_t *) NULL, + callback_thread_loop, rdev->tuner); + list_init(&rdev->tuner->command_list); + + pthread_mutex_lock(&rdev->tuner->lock); + send_command_l(rdev->tuner, CMD_CONFIG, 500, (void *)config); + pthread_mutex_unlock(&rdev->tuner->lock); + + *tuner = &rdev->tuner->interface; + +exit: + pthread_mutex_unlock(&rdev->lock); + ALOGI("%s DONE", __func__); + return status; +} + +static int rdev_close_tuner(const struct radio_hw_device *dev, + const struct radio_tuner *tuner) +{ + struct stub_radio_device *rdev = (struct stub_radio_device *)dev; + struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; + int status = 0; + + ALOGI("%s tuner %p", __func__, tuner); + pthread_mutex_lock(&rdev->lock); + + if (tuner == NULL) { + status = -EINVAL; + goto exit; + } + + pthread_mutex_lock(&stub_tuner->lock); + stub_tuner->callback = NULL; + send_command_l(stub_tuner, CMD_EXIT, 0, NULL); + pthread_mutex_unlock(&stub_tuner->lock); + pthread_join(stub_tuner->callback_thread, (void **) NULL); + + if (stub_tuner->program.metadata != NULL) + radio_metadata_deallocate(stub_tuner->program.metadata); + + free(stub_tuner); + rdev->tuner = NULL; + +exit: + pthread_mutex_unlock(&rdev->lock); + return status; +} + +static int rdev_close(hw_device_t *device) +{ + struct stub_radio_device *rdev = (struct stub_radio_device *)device; + if (rdev != NULL) { + free(rdev->tuner); + } + free(rdev); + return 0; +} + +static int rdev_open(const hw_module_t* module, const char* name, + hw_device_t** device) +{ + struct stub_radio_device *rdev; + int ret; + + if (strcmp(name, RADIO_HARDWARE_DEVICE) != 0) + return -EINVAL; + + rdev = calloc(1, sizeof(struct stub_radio_device)); + if (!rdev) + return -ENOMEM; + + rdev->device.common.tag = HARDWARE_DEVICE_TAG; + rdev->device.common.version = RADIO_DEVICE_API_VERSION_1_0; + rdev->device.common.module = (struct hw_module_t *) module; + rdev->device.common.close = rdev_close; + rdev->device.get_properties = rdev_get_properties; + rdev->device.open_tuner = rdev_open_tuner; + rdev->device.close_tuner = rdev_close_tuner; + + pthread_mutex_init(&rdev->lock, (const pthread_mutexattr_t *) NULL); + + *device = &rdev->device.common; + + return 0; +} + + +static struct hw_module_methods_t hal_module_methods = { + .open = rdev_open, +}; + +struct radio_module HAL_MODULE_INFO_SYM = { + .common = { + .tag = HARDWARE_MODULE_TAG, + .module_api_version = RADIO_MODULE_API_VERSION_1_0, + .hal_api_version = HARDWARE_HAL_API_VERSION, + .id = RADIO_HARDWARE_MODULE_ID, + .name = "Stub radio HAL", + .author = "The Android Open Source Project", + .methods = &hal_module_methods, + }, +}; -- cgit v1.1