radio_hw.c revision 97d2ba6a1130b36df752d631d002fd316165f710
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_TAG "radio_hw_stub" 18#define LOG_NDEBUG 0 19 20#include <string.h> 21#include <stdlib.h> 22#include <errno.h> 23#include <pthread.h> 24#include <sys/prctl.h> 25#include <sys/time.h> 26#include <sys/types.h> 27#include <sys/stat.h> 28#include <fcntl.h> 29#include <unistd.h> 30#include <cutils/log.h> 31#include <cutils/list.h> 32#include <system/radio.h> 33#include <system/radio_metadata.h> 34#include <hardware/hardware.h> 35#include <hardware/radio.h> 36 37static const radio_hal_properties_t hw_properties = { 38 .class_id = RADIO_CLASS_AM_FM, 39 .implementor = "The Android Open Source Project", 40 .product = "Radio stub HAL", 41 .version = "0.1", 42 .serial = "0123456789", 43 .num_tuners = 1, 44 .num_audio_sources = 1, 45 .supports_capture = false, 46 .num_bands = 2, 47 .bands = { 48 { 49 .type = RADIO_BAND_FM, 50 .antenna_connected = false, 51 .lower_limit = 87900, 52 .upper_limit = 107900, 53 .num_spacings = 1, 54 .spacings = { 200 }, 55 .fm = { 56 .deemphasis = RADIO_DEEMPHASIS_75, 57 .stereo = true, 58 .rds = RADIO_RDS_US, 59 .ta = false, 60 .af = false, 61 } 62 }, 63 { 64 .type = RADIO_BAND_AM, 65 .antenna_connected = true, 66 .lower_limit = 540, 67 .upper_limit = 1610, 68 .num_spacings = 1, 69 .spacings = { 10 }, 70 .am = { 71 .stereo = true, 72 } 73 } 74 } 75}; 76 77struct stub_radio_tuner { 78 struct radio_tuner interface; 79 struct stub_radio_device *dev; 80 radio_callback_t callback; 81 void *cookie; 82 radio_hal_band_config_t config; 83 radio_program_info_t program; 84 bool audio; 85 pthread_t callback_thread; 86 pthread_mutex_t lock; 87 pthread_cond_t cond; 88 struct listnode command_list; 89}; 90 91struct stub_radio_device { 92 struct radio_hw_device device; 93 struct stub_radio_tuner *tuner; 94 pthread_mutex_t lock; 95}; 96 97 98typedef enum { 99 CMD_EXIT, 100 CMD_CONFIG, 101 CMD_STEP, 102 CMD_SCAN, 103 CMD_TUNE, 104 CMD_CANCEL, 105 CMD_METADATA, 106} thread_cmd_type_t; 107 108struct thread_command { 109 struct listnode node; 110 thread_cmd_type_t type; 111 struct timespec ts; 112 union { 113 unsigned int param; 114 radio_hal_band_config_t config; 115 }; 116}; 117 118/* must be called with out->lock locked */ 119static int send_command_l(struct stub_radio_tuner *tuner, 120 thread_cmd_type_t type, 121 unsigned int delay_ms, 122 void *param) 123{ 124 struct thread_command *cmd = (struct thread_command *)calloc(1, sizeof(struct thread_command)); 125 struct timespec ts; 126 127 if (cmd == NULL) 128 return -ENOMEM; 129 130 ALOGV("%s %d delay_ms %d", __func__, type, delay_ms); 131 132 cmd->type = type; 133 if (param != NULL) { 134 if (cmd->type == CMD_CONFIG) { 135 cmd->config = *(radio_hal_band_config_t *)param; 136 ALOGV("%s CMD_CONFIG type %d", __func__, cmd->config.type); 137 } else 138 cmd->param = *(unsigned int *)param; 139 } 140 141 clock_gettime(CLOCK_REALTIME, &ts); 142 143 ts.tv_sec += delay_ms/1000; 144 ts.tv_nsec += (delay_ms%1000) * 1000000; 145 if (ts.tv_nsec >= 1000000000) { 146 ts.tv_nsec -= 1000000000; 147 ts.tv_sec += 1; 148 } 149 cmd->ts = ts; 150 list_add_tail(&tuner->command_list, &cmd->node); 151 pthread_cond_signal(&tuner->cond); 152 return 0; 153} 154 155#define BITMAP_FILE_PATH "/data/misc/media/android.png" 156 157static int add_bitmap_metadata(radio_metadata_t **metadata, radio_metadata_key_t key, 158 const char *source) 159{ 160 int fd; 161 ssize_t ret = 0; 162 struct stat info; 163 void *data = NULL; 164 size_t size; 165 166 fd = open(source, O_RDONLY); 167 if (fd < 0) 168 return -EPIPE; 169 170 fstat(fd, &info); 171 size = info.st_size; 172 data = malloc(size); 173 if (data == NULL) { 174 ret = -ENOMEM; 175 goto exit; 176 } 177 ret = read(fd, data, size); 178 if (ret < 0) 179 goto exit; 180 ret = radio_metadata_add_raw(metadata, key, (const unsigned char *)data, size); 181 182exit: 183 close(fd); 184 free(data); 185 ALOGE_IF(ret != 0, "%s error %d", __func__, ret); 186 return (int)ret; 187} 188 189static int prepare_metadata(struct stub_radio_tuner *tuner, 190 radio_metadata_t **metadata, bool program) 191{ 192 int ret = 0; 193 char text[RADIO_STRING_LEN_MAX]; 194 struct timespec ts; 195 196 if (metadata == NULL) 197 return -EINVAL; 198 199 if (*metadata != NULL) 200 radio_metadata_deallocate(*metadata); 201 202 *metadata = NULL; 203 204 ret = radio_metadata_allocate(metadata, tuner->program.channel, 0); 205 if (ret != 0) 206 return ret; 207 208 if (program) { 209 ret = radio_metadata_add_int(metadata, RADIO_METADATA_KEY_RBDS_PTY, 5); 210 if (ret != 0) 211 goto exit; 212 ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_RDS_PS, "RockBand"); 213 if (ret != 0) 214 goto exit; 215 ret = add_bitmap_metadata(metadata, RADIO_METADATA_KEY_ICON, BITMAP_FILE_PATH); 216 if (ret != 0) 217 goto exit; 218 } else { 219 ret = add_bitmap_metadata(metadata, RADIO_METADATA_KEY_ART, BITMAP_FILE_PATH); 220 if (ret != 0) 221 goto exit; 222 } 223 224 clock_gettime(CLOCK_REALTIME, &ts); 225 snprintf(text, RADIO_STRING_LEN_MAX, "Artist %ld", ts.tv_sec % 10); 226 ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_ARTIST, text); 227 if (ret != 0) 228 goto exit; 229 230 snprintf(text, RADIO_STRING_LEN_MAX, "Song %ld", ts.tv_nsec % 10); 231 ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_TITLE, text); 232 if (ret != 0) 233 goto exit; 234 235 return 0; 236 237exit: 238 radio_metadata_deallocate(*metadata); 239 *metadata = NULL; 240 return ret; 241} 242 243static void *callback_thread_loop(void *context) 244{ 245 struct stub_radio_tuner *tuner = (struct stub_radio_tuner *)context; 246 struct timespec ts = {0, 0}; 247 248 ALOGI("%s", __func__); 249 250 prctl(PR_SET_NAME, (unsigned long)"sound trigger callback", 0, 0, 0); 251 252 pthread_mutex_lock(&tuner->lock); 253 254 while (true) { 255 struct thread_command *cmd = NULL; 256 struct listnode *item; 257 struct listnode *tmp; 258 struct timespec cur_ts; 259 260 if (list_empty(&tuner->command_list) || ts.tv_sec != 0) { 261 ALOGV("%s SLEEPING", __func__); 262 if (ts.tv_sec != 0) { 263 ALOGV("%s SLEEPING with timeout", __func__); 264 pthread_cond_timedwait(&tuner->cond, &tuner->lock, &ts); 265 } else { 266 ALOGV("%s SLEEPING forever", __func__); 267 pthread_cond_wait(&tuner->cond, &tuner->lock); 268 } 269 ts.tv_sec = 0; 270 ALOGV("%s RUNNING", __func__); 271 } 272 273 clock_gettime(CLOCK_REALTIME, &cur_ts); 274 275 list_for_each_safe(item, tmp, &tuner->command_list) { 276 cmd = node_to_item(item, struct thread_command, node); 277 278 if ((cmd->ts.tv_sec < cur_ts.tv_sec) || 279 ((cmd->ts.tv_sec == cur_ts.tv_sec) && (cmd->ts.tv_nsec < cur_ts.tv_nsec))) { 280 radio_hal_event_t event; 281 282 event.type = RADIO_EVENT_HW_FAILURE; 283 list_remove(item); 284 285 ALOGV("%s processing command %d time %ld.%ld", __func__, cmd->type, cmd->ts.tv_sec, 286 cmd->ts.tv_nsec); 287 288 switch (cmd->type) { 289 default: 290 case CMD_EXIT: 291 free(cmd); 292 goto exit; 293 294 case CMD_CONFIG: { 295 tuner->config = cmd->config; 296 event.type = RADIO_EVENT_CONFIG; 297 event.config = tuner->config; 298 ALOGV("%s CMD_CONFIG type %d low %d up %d", 299 __func__, tuner->config.type, 300 tuner->config.lower_limit, tuner->config.upper_limit); 301 if (tuner->config.type == RADIO_BAND_FM) { 302 ALOGV(" - stereo %d\n - rds %d\n - ta %d\n - af %d", 303 tuner->config.fm.stereo, tuner->config.fm.rds, 304 tuner->config.fm.ta, tuner->config.fm.af); 305 } else { 306 ALOGV(" - stereo %d", tuner->config.am.stereo); 307 } 308 } break; 309 310 case CMD_STEP: { 311 int frequency; 312 frequency = tuner->program.channel; 313 if (cmd->param == RADIO_DIRECTION_UP) { 314 frequency += tuner->config.spacings[0]; 315 } else { 316 frequency -= tuner->config.spacings[0]; 317 } 318 if (frequency > (int)tuner->config.upper_limit) { 319 frequency = tuner->config.lower_limit; 320 } 321 if (frequency < (int)tuner->config.lower_limit) { 322 frequency = tuner->config.upper_limit; 323 } 324 tuner->program.channel = frequency; 325 tuner->program.tuned = (frequency / (tuner->config.spacings[0] * 5)) % 2; 326 tuner->program.signal_strength = 20; 327 if (tuner->config.type == RADIO_BAND_FM) 328 tuner->program.stereo = false; 329 else 330 tuner->program.stereo = false; 331 332 event.type = RADIO_EVENT_TUNED; 333 event.info = tuner->program; 334 } break; 335 336 case CMD_SCAN: { 337 int frequency; 338 frequency = tuner->program.channel; 339 if (cmd->param == RADIO_DIRECTION_UP) { 340 frequency += tuner->config.spacings[0] * 25; 341 } else { 342 frequency -= tuner->config.spacings[0] * 25; 343 } 344 if (frequency > (int)tuner->config.upper_limit) { 345 frequency = tuner->config.lower_limit; 346 } 347 if (frequency < (int)tuner->config.lower_limit) { 348 frequency = tuner->config.upper_limit; 349 } 350 tuner->program.channel = (unsigned int)frequency; 351 tuner->program.tuned = true; 352 if (tuner->config.type == RADIO_BAND_FM) 353 tuner->program.stereo = tuner->config.fm.stereo; 354 else 355 tuner->program.stereo = tuner->config.am.stereo; 356 tuner->program.signal_strength = 50; 357 358 event.type = RADIO_EVENT_TUNED; 359 event.info = tuner->program; 360 if (tuner->program.metadata != NULL) 361 radio_metadata_deallocate(tuner->program.metadata); 362 tuner->program.metadata = NULL; 363 send_command_l(tuner, CMD_METADATA, 2000, NULL); 364 } break; 365 366 case CMD_TUNE: { 367 tuner->program.channel = cmd->param; 368 tuner->program.tuned = (tuner->program.channel / 369 (tuner->config.spacings[0] * 5)) % 2; 370 371 if (tuner->program.tuned) { 372 prepare_metadata(tuner, &tuner->program.metadata, true); 373 send_command_l(tuner, CMD_METADATA, 5000, NULL); 374 } else { 375 if (tuner->program.metadata != NULL) 376 radio_metadata_deallocate(tuner->program.metadata); 377 tuner->program.metadata = NULL; 378 } 379 tuner->program.signal_strength = 100; 380 if (tuner->config.type == RADIO_BAND_FM) 381 tuner->program.stereo = 382 tuner->program.tuned ? tuner->config.fm.stereo : false; 383 else 384 tuner->program.stereo = 385 tuner->program.tuned ? tuner->config.am.stereo : false; 386 event.type = RADIO_EVENT_TUNED; 387 event.info = tuner->program; 388 } break; 389 390 case CMD_METADATA: { 391 prepare_metadata(tuner, &tuner->program.metadata, false); 392 event.type = RADIO_EVENT_METADATA; 393 event.metadata = tuner->program.metadata; 394 } break; 395 396 case CMD_CANCEL: { 397 struct listnode *tmp2; 398 list_for_each_safe(item, tmp2, &tuner->command_list) { 399 cmd = node_to_item(item, struct thread_command, node); 400 if (cmd->type == CMD_STEP || cmd->type == CMD_SCAN || 401 cmd->type == CMD_TUNE || cmd->type == CMD_METADATA) { 402 list_remove(item); 403 free(cmd); 404 } 405 } 406 } break; 407 408 } 409 if (event.type != RADIO_EVENT_HW_FAILURE && tuner->callback != NULL) { 410 pthread_mutex_unlock(&tuner->lock); 411 tuner->callback(&event, tuner->cookie); 412 pthread_mutex_lock(&tuner->lock); 413 } 414 ALOGV("%s processed command %d", __func__, cmd->type); 415 free(cmd); 416 } else { 417 if ((ts.tv_sec == 0) || 418 (cmd->ts.tv_sec < ts.tv_sec) || 419 ((cmd->ts.tv_sec == ts.tv_sec) && (cmd->ts.tv_nsec < ts.tv_nsec))) { 420 ts.tv_sec = cmd->ts.tv_sec; 421 ts.tv_nsec = cmd->ts.tv_nsec; 422 } 423 } 424 } 425 } 426 427exit: 428 pthread_mutex_unlock(&tuner->lock); 429 430 ALOGV("%s Exiting", __func__); 431 432 return NULL; 433} 434 435 436static int tuner_set_configuration(const struct radio_tuner *tuner, 437 const radio_hal_band_config_t *config) 438{ 439 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 440 int status = 0; 441 442 ALOGI("%s stub_tuner %p", __func__, stub_tuner); 443 pthread_mutex_lock(&stub_tuner->lock); 444 if (config == NULL) { 445 status = -EINVAL; 446 goto exit; 447 } 448 send_command_l(stub_tuner, CMD_CANCEL, 0, NULL); 449 send_command_l(stub_tuner, CMD_CONFIG, 500, (void *)config); 450 451exit: 452 pthread_mutex_unlock(&stub_tuner->lock); 453 return status; 454} 455 456static int tuner_get_configuration(const struct radio_tuner *tuner, 457 radio_hal_band_config_t *config) 458{ 459 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 460 int status = 0; 461 struct listnode *item; 462 radio_hal_band_config_t *src_config; 463 464 ALOGI("%s stub_tuner %p", __func__, stub_tuner); 465 pthread_mutex_lock(&stub_tuner->lock); 466 src_config = &stub_tuner->config; 467 468 if (config == NULL) { 469 status = -EINVAL; 470 goto exit; 471 } 472 list_for_each(item, &stub_tuner->command_list) { 473 struct thread_command *cmd = node_to_item(item, struct thread_command, node); 474 if (cmd->type == CMD_CONFIG) { 475 src_config = &cmd->config; 476 } 477 } 478 *config = *src_config; 479 480exit: 481 pthread_mutex_unlock(&stub_tuner->lock); 482 return status; 483} 484 485static int tuner_step(const struct radio_tuner *tuner, 486 radio_direction_t direction, bool skip_sub_channel) 487{ 488 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 489 490 ALOGI("%s stub_tuner %p direction %d, skip_sub_channel %d", 491 __func__, stub_tuner, direction, skip_sub_channel); 492 493 pthread_mutex_lock(&stub_tuner->lock); 494 send_command_l(stub_tuner, CMD_STEP, 20, &direction); 495 pthread_mutex_unlock(&stub_tuner->lock); 496 return 0; 497} 498 499static int tuner_scan(const struct radio_tuner *tuner, 500 radio_direction_t direction, bool skip_sub_channel) 501{ 502 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 503 504 ALOGI("%s stub_tuner %p direction %d, skip_sub_channel %d", 505 __func__, stub_tuner, direction, skip_sub_channel); 506 507 pthread_mutex_lock(&stub_tuner->lock); 508 send_command_l(stub_tuner, CMD_SCAN, 200, &direction); 509 pthread_mutex_unlock(&stub_tuner->lock); 510 return 0; 511} 512 513static int tuner_tune(const struct radio_tuner *tuner, 514 unsigned int channel, unsigned int sub_channel) 515{ 516 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 517 518 ALOGI("%s stub_tuner %p channel %d, sub_channel %d", 519 __func__, stub_tuner, channel, sub_channel); 520 521 pthread_mutex_lock(&stub_tuner->lock); 522 if (channel < stub_tuner->config.lower_limit || channel > stub_tuner->config.upper_limit) { 523 pthread_mutex_unlock(&stub_tuner->lock); 524 ALOGI("%s channel out of range", __func__); 525 return -EINVAL; 526 } 527 send_command_l(stub_tuner, CMD_TUNE, 100, &channel); 528 pthread_mutex_unlock(&stub_tuner->lock); 529 return 0; 530} 531 532static int tuner_cancel(const struct radio_tuner *tuner) 533{ 534 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 535 536 ALOGI("%s stub_tuner %p", __func__, stub_tuner); 537 538 pthread_mutex_lock(&stub_tuner->lock); 539 send_command_l(stub_tuner, CMD_CANCEL, 0, NULL); 540 pthread_mutex_unlock(&stub_tuner->lock); 541 return 0; 542} 543 544static int tuner_get_program_information(const struct radio_tuner *tuner, 545 radio_program_info_t *info) 546{ 547 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 548 int status = 0; 549 radio_metadata_t *metadata; 550 551 ALOGI("%s stub_tuner %p", __func__, stub_tuner); 552 pthread_mutex_lock(&stub_tuner->lock); 553 if (info == NULL) { 554 status = -EINVAL; 555 goto exit; 556 } 557 metadata = info->metadata; 558 *info = stub_tuner->program; 559 info->metadata = metadata; 560 if (metadata != NULL) 561 radio_metadata_add_metadata(&info->metadata, stub_tuner->program.metadata); 562 563exit: 564 pthread_mutex_unlock(&stub_tuner->lock); 565 return status; 566} 567 568static int rdev_get_properties(const struct radio_hw_device *dev, 569 radio_hal_properties_t *properties) 570{ 571 struct stub_radio_device *rdev = (struct stub_radio_device *)dev; 572 573 ALOGI("%s", __func__); 574 if (properties == NULL) 575 return -EINVAL; 576 memcpy(properties, &hw_properties, sizeof(radio_hal_properties_t)); 577 return 0; 578} 579 580static int rdev_open_tuner(const struct radio_hw_device *dev, 581 const radio_hal_band_config_t *config, 582 bool audio, 583 radio_callback_t callback, 584 void *cookie, 585 const struct radio_tuner **tuner) 586{ 587 struct stub_radio_device *rdev = (struct stub_radio_device *)dev; 588 int status = 0; 589 590 ALOGI("%s rdev %p", __func__, rdev); 591 pthread_mutex_lock(&rdev->lock); 592 593 if (rdev->tuner != NULL) { 594 status = -ENOSYS; 595 goto exit; 596 } 597 598 if (config == NULL || callback == NULL || tuner == NULL) { 599 status = -EINVAL; 600 goto exit; 601 } 602 603 rdev->tuner = (struct stub_radio_tuner *)calloc(1, sizeof(struct stub_radio_tuner)); 604 if (rdev->tuner == NULL) { 605 status = -ENOMEM; 606 goto exit; 607 } 608 609 rdev->tuner->interface.set_configuration = tuner_set_configuration; 610 rdev->tuner->interface.get_configuration = tuner_get_configuration; 611 rdev->tuner->interface.scan = tuner_scan; 612 rdev->tuner->interface.step = tuner_step; 613 rdev->tuner->interface.tune = tuner_tune; 614 rdev->tuner->interface.cancel = tuner_cancel; 615 rdev->tuner->interface.get_program_information = tuner_get_program_information; 616 617 rdev->tuner->audio = audio; 618 rdev->tuner->callback = callback; 619 rdev->tuner->cookie = cookie; 620 621 rdev->tuner->dev = rdev; 622 623 pthread_mutex_init(&rdev->tuner->lock, (const pthread_mutexattr_t *) NULL); 624 pthread_cond_init(&rdev->tuner->cond, (const pthread_condattr_t *) NULL); 625 pthread_create(&rdev->tuner->callback_thread, (const pthread_attr_t *) NULL, 626 callback_thread_loop, rdev->tuner); 627 list_init(&rdev->tuner->command_list); 628 629 pthread_mutex_lock(&rdev->tuner->lock); 630 send_command_l(rdev->tuner, CMD_CONFIG, 500, (void *)config); 631 pthread_mutex_unlock(&rdev->tuner->lock); 632 633 *tuner = &rdev->tuner->interface; 634 635exit: 636 pthread_mutex_unlock(&rdev->lock); 637 ALOGI("%s DONE", __func__); 638 return status; 639} 640 641static int rdev_close_tuner(const struct radio_hw_device *dev, 642 const struct radio_tuner *tuner) 643{ 644 struct stub_radio_device *rdev = (struct stub_radio_device *)dev; 645 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 646 int status = 0; 647 648 ALOGI("%s tuner %p", __func__, tuner); 649 pthread_mutex_lock(&rdev->lock); 650 651 if (tuner == NULL) { 652 status = -EINVAL; 653 goto exit; 654 } 655 656 pthread_mutex_lock(&stub_tuner->lock); 657 stub_tuner->callback = NULL; 658 send_command_l(stub_tuner, CMD_EXIT, 0, NULL); 659 pthread_mutex_unlock(&stub_tuner->lock); 660 pthread_join(stub_tuner->callback_thread, (void **) NULL); 661 662 if (stub_tuner->program.metadata != NULL) 663 radio_metadata_deallocate(stub_tuner->program.metadata); 664 665 free(stub_tuner); 666 rdev->tuner = NULL; 667 668exit: 669 pthread_mutex_unlock(&rdev->lock); 670 return status; 671} 672 673static int rdev_close(hw_device_t *device) 674{ 675 struct stub_radio_device *rdev = (struct stub_radio_device *)device; 676 if (rdev != NULL) { 677 free(rdev->tuner); 678 } 679 free(rdev); 680 return 0; 681} 682 683static int rdev_open(const hw_module_t* module, const char* name, 684 hw_device_t** device) 685{ 686 struct stub_radio_device *rdev; 687 int ret; 688 689 if (strcmp(name, RADIO_HARDWARE_DEVICE) != 0) 690 return -EINVAL; 691 692 rdev = calloc(1, sizeof(struct stub_radio_device)); 693 if (!rdev) 694 return -ENOMEM; 695 696 rdev->device.common.tag = HARDWARE_DEVICE_TAG; 697 rdev->device.common.version = RADIO_DEVICE_API_VERSION_1_0; 698 rdev->device.common.module = (struct hw_module_t *) module; 699 rdev->device.common.close = rdev_close; 700 rdev->device.get_properties = rdev_get_properties; 701 rdev->device.open_tuner = rdev_open_tuner; 702 rdev->device.close_tuner = rdev_close_tuner; 703 704 pthread_mutex_init(&rdev->lock, (const pthread_mutexattr_t *) NULL); 705 706 *device = &rdev->device.common; 707 708 return 0; 709} 710 711 712static struct hw_module_methods_t hal_module_methods = { 713 .open = rdev_open, 714}; 715 716struct radio_module HAL_MODULE_INFO_SYM = { 717 .common = { 718 .tag = HARDWARE_MODULE_TAG, 719 .module_api_version = RADIO_MODULE_API_VERSION_1_0, 720 .hal_api_version = HARDWARE_HAL_API_VERSION, 721 .id = RADIO_HARDWARE_MODULE_ID, 722 .name = "Stub radio HAL", 723 .author = "The Android Open Source Project", 724 .methods = &hal_module_methods, 725 }, 726}; 727