| /* Copyright 2019 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <syslog.h> |
| |
| #include "audio_thread.h" |
| #include "audio_thread_log.h" |
| #include "cras_apm_list.h" |
| #include "cras_bt_log.h" |
| #include "cras_config.h" |
| #include "cras_control_rclient.h" |
| #include "cras_dsp.h" |
| #include "cras_iodev.h" |
| #include "cras_iodev_list.h" |
| #include "cras_hfp_ag_profile.h" |
| #include "cras_main_thread_log.h" |
| #include "cras_messages.h" |
| #include "cras_observer.h" |
| #include "cras_rclient.h" |
| #include "cras_rclient_util.h" |
| #include "cras_rstream.h" |
| #include "cras_system_state.h" |
| #include "cras_types.h" |
| #include "cras_util.h" |
| #include "utlist.h" |
| |
| /* Handles dumping audio thread debug info back to the client. */ |
| static void dump_audio_thread_info(struct cras_rclient *client) |
| { |
| struct cras_client_audio_debug_info_ready msg; |
| struct cras_server_state *state; |
| |
| cras_fill_client_audio_debug_info_ready(&msg); |
| state = cras_system_state_get_no_lock(); |
| audio_thread_dump_thread_info(cras_iodev_list_get_audio_thread(), |
| &state->audio_debug_info); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| /* Sends shared memory fd for audio thread event log back to the client. */ |
| static void get_atlog_fd(struct cras_rclient *client) |
| { |
| struct cras_client_atlog_fd_ready msg; |
| int atlog_fd; |
| |
| cras_fill_client_atlog_fd_ready(&msg); |
| atlog_fd = audio_thread_event_log_shm_fd(); |
| client->ops->send_message_to_client(client, &msg.header, &atlog_fd, 1); |
| } |
| |
| /* Handles dumping audio snapshots to shared memory for the client. */ |
| static void dump_audio_thread_snapshots(struct cras_rclient *client) |
| { |
| struct cras_client_audio_debug_info_ready msg; |
| |
| cras_fill_client_audio_debug_info_ready(&msg); |
| cras_system_state_dump_snapshots(); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| static void handle_get_hotword_models(struct cras_rclient *client, |
| cras_node_id_t node_id) |
| { |
| struct cras_client_get_hotword_models_ready *msg; |
| char *hotword_models; |
| unsigned hotword_models_size; |
| uint8_t buf[CRAS_CLIENT_MAX_MSG_SIZE]; |
| |
| msg = (struct cras_client_get_hotword_models_ready *)buf; |
| hotword_models = cras_iodev_list_get_hotword_models(node_id); |
| if (!hotword_models) |
| goto empty_reply; |
| hotword_models_size = strlen(hotword_models); |
| if (hotword_models_size > CRAS_MAX_HOTWORD_MODELS) { |
| free(hotword_models); |
| goto empty_reply; |
| } |
| |
| cras_fill_client_get_hotword_models_ready(msg, hotword_models, |
| hotword_models_size); |
| client->ops->send_message_to_client(client, &msg->header, NULL, 0); |
| free(hotword_models); |
| return; |
| |
| empty_reply: |
| cras_fill_client_get_hotword_models_ready(msg, NULL, 0); |
| client->ops->send_message_to_client(client, &msg->header, NULL, 0); |
| } |
| |
| /* Client notification callback functions. */ |
| |
| static void send_output_volume_changed(void *context, int32_t volume) |
| { |
| struct cras_client_volume_changed msg; |
| struct cras_rclient *client = (struct cras_rclient *)context; |
| |
| cras_fill_client_output_volume_changed(&msg, volume); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| static void send_output_mute_changed(void *context, int muted, int user_muted, |
| int mute_locked) |
| { |
| struct cras_client_mute_changed msg; |
| struct cras_rclient *client = (struct cras_rclient *)context; |
| |
| cras_fill_client_output_mute_changed(&msg, muted, user_muted, |
| mute_locked); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| static void send_capture_gain_changed(void *context, int32_t gain) |
| { |
| struct cras_client_volume_changed msg; |
| struct cras_rclient *client = (struct cras_rclient *)context; |
| |
| cras_fill_client_capture_gain_changed(&msg, gain); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| static void send_capture_mute_changed(void *context, int muted, int mute_locked) |
| { |
| struct cras_client_mute_changed msg; |
| struct cras_rclient *client = (struct cras_rclient *)context; |
| |
| cras_fill_client_capture_mute_changed(&msg, muted, mute_locked); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| static void send_nodes_changed(void *context) |
| { |
| struct cras_client_nodes_changed msg; |
| struct cras_rclient *client = (struct cras_rclient *)context; |
| |
| cras_fill_client_nodes_changed(&msg); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| static void send_active_node_changed(void *context, |
| enum CRAS_STREAM_DIRECTION dir, |
| cras_node_id_t node_id) |
| { |
| struct cras_client_active_node_changed msg; |
| struct cras_rclient *client = (struct cras_rclient *)context; |
| |
| cras_fill_client_active_node_changed(&msg, dir, node_id); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| static void send_output_node_volume_changed(void *context, |
| cras_node_id_t node_id, |
| int32_t volume) |
| { |
| struct cras_client_node_value_changed msg; |
| struct cras_rclient *client = (struct cras_rclient *)context; |
| |
| cras_fill_client_output_node_volume_changed(&msg, node_id, volume); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| static void send_node_left_right_swapped_changed(void *context, |
| cras_node_id_t node_id, |
| int swapped) |
| { |
| struct cras_client_node_value_changed msg; |
| struct cras_rclient *client = (struct cras_rclient *)context; |
| |
| cras_fill_client_node_left_right_swapped_changed(&msg, node_id, |
| swapped); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| static void send_input_node_gain_changed(void *context, cras_node_id_t node_id, |
| int32_t gain) |
| { |
| struct cras_client_node_value_changed msg; |
| struct cras_rclient *client = (struct cras_rclient *)context; |
| |
| cras_fill_client_input_node_gain_changed(&msg, node_id, gain); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| static void send_num_active_streams_changed(void *context, |
| enum CRAS_STREAM_DIRECTION dir, |
| uint32_t num_active_streams) |
| { |
| struct cras_client_num_active_streams_changed msg; |
| struct cras_rclient *client = (struct cras_rclient *)context; |
| |
| cras_fill_client_num_active_streams_changed(&msg, dir, |
| num_active_streams); |
| client->ops->send_message_to_client(client, &msg.header, NULL, 0); |
| } |
| |
| static void register_for_notification(struct cras_rclient *client, |
| enum CRAS_CLIENT_MESSAGE_ID msg_id, |
| int do_register) |
| { |
| struct cras_observer_ops observer_ops; |
| int empty; |
| |
| cras_observer_get_ops(client->observer, &observer_ops); |
| |
| switch (msg_id) { |
| case CRAS_CLIENT_OUTPUT_VOLUME_CHANGED: |
| observer_ops.output_volume_changed = |
| do_register ? send_output_volume_changed : NULL; |
| break; |
| case CRAS_CLIENT_OUTPUT_MUTE_CHANGED: |
| observer_ops.output_mute_changed = |
| do_register ? send_output_mute_changed : NULL; |
| break; |
| case CRAS_CLIENT_CAPTURE_GAIN_CHANGED: |
| observer_ops.capture_gain_changed = |
| do_register ? send_capture_gain_changed : NULL; |
| break; |
| case CRAS_CLIENT_CAPTURE_MUTE_CHANGED: |
| observer_ops.capture_mute_changed = |
| do_register ? send_capture_mute_changed : NULL; |
| break; |
| case CRAS_CLIENT_NODES_CHANGED: |
| observer_ops.nodes_changed = |
| do_register ? send_nodes_changed : NULL; |
| break; |
| case CRAS_CLIENT_ACTIVE_NODE_CHANGED: |
| observer_ops.active_node_changed = |
| do_register ? send_active_node_changed : NULL; |
| break; |
| case CRAS_CLIENT_OUTPUT_NODE_VOLUME_CHANGED: |
| observer_ops.output_node_volume_changed = |
| do_register ? send_output_node_volume_changed : NULL; |
| break; |
| case CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED: |
| observer_ops.node_left_right_swapped_changed = |
| do_register ? send_node_left_right_swapped_changed : |
| NULL; |
| break; |
| case CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED: |
| observer_ops.input_node_gain_changed = |
| do_register ? send_input_node_gain_changed : NULL; |
| break; |
| case CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED: |
| observer_ops.num_active_streams_changed = |
| do_register ? send_num_active_streams_changed : NULL; |
| break; |
| default: |
| syslog(LOG_ERR, "Invalid client notification message ID: %u", |
| msg_id); |
| break; |
| } |
| |
| empty = cras_observer_ops_are_empty(&observer_ops); |
| if (client->observer) { |
| if (empty) { |
| cras_observer_remove(client->observer); |
| client->observer = NULL; |
| } else { |
| cras_observer_set_ops(client->observer, &observer_ops); |
| } |
| } else if (!empty) { |
| client->observer = cras_observer_add(&observer_ops, client); |
| } |
| } |
| |
| static int direction_valid(enum CRAS_STREAM_DIRECTION direction) |
| { |
| return direction < CRAS_NUM_DIRECTIONS && |
| direction != CRAS_STREAM_UNDEFINED; |
| } |
| |
| /* Entry point for handling a message from the client. Called from the main |
| * server context. |
| * |
| * If the message from clients has incorrect length (truncated message), return |
| * an error up to CRAS server. |
| * If the message from clients has invalid content, should return the errors to |
| * clients by send_message_to_client and return 0 here. |
| * |
| */ |
| static int ccr_handle_message_from_client(struct cras_rclient *client, |
| const struct cras_server_message *msg, |
| int *fds, unsigned int num_fds) |
| { |
| int rc = 0; |
| assert(client && msg); |
| |
| rc = rclient_validate_message_fds(msg, fds, num_fds); |
| if (rc < 0) { |
| for (int i = 0; i < (int)num_fds; i++) |
| if (fds[i] >= 0) |
| close(fds[i]); |
| return rc; |
| } |
| int fd = num_fds > 0 ? fds[0] : -1; |
| |
| switch (msg->id) { |
| case CRAS_SERVER_CONNECT_STREAM: { |
| int client_shm_fd = num_fds > 1 ? fds[1] : -1; |
| if (MSG_LEN_VALID(msg, struct cras_connect_message)) { |
| rclient_handle_client_stream_connect( |
| client, |
| (const struct cras_connect_message *)msg, fd, |
| client_shm_fd); |
| } else { |
| return -EINVAL; |
| } |
| break; |
| } |
| case CRAS_SERVER_DISCONNECT_STREAM: |
| if (!MSG_LEN_VALID(msg, struct cras_disconnect_stream_message)) |
| return -EINVAL; |
| rclient_handle_client_stream_disconnect( |
| client, |
| (const struct cras_disconnect_stream_message *)msg); |
| break; |
| case CRAS_SERVER_SET_SYSTEM_VOLUME: |
| if (!MSG_LEN_VALID(msg, struct cras_set_system_volume)) |
| return -EINVAL; |
| cras_system_set_volume( |
| ((const struct cras_set_system_volume *)msg)->volume); |
| break; |
| case CRAS_SERVER_SET_SYSTEM_MUTE: |
| if (!MSG_LEN_VALID(msg, struct cras_set_system_mute)) |
| return -EINVAL; |
| cras_system_set_mute( |
| ((const struct cras_set_system_mute *)msg)->mute); |
| break; |
| case CRAS_SERVER_SET_USER_MUTE: |
| if (!MSG_LEN_VALID(msg, struct cras_set_system_mute)) |
| return -EINVAL; |
| cras_system_set_user_mute( |
| ((const struct cras_set_system_mute *)msg)->mute); |
| break; |
| case CRAS_SERVER_SET_SYSTEM_MUTE_LOCKED: |
| if (!MSG_LEN_VALID(msg, struct cras_set_system_mute)) |
| return -EINVAL; |
| cras_system_set_mute_locked( |
| ((const struct cras_set_system_mute *)msg)->mute); |
| break; |
| case CRAS_SERVER_SET_SYSTEM_CAPTURE_MUTE: |
| if (!MSG_LEN_VALID(msg, struct cras_set_system_mute)) |
| return -EINVAL; |
| cras_system_set_capture_mute( |
| ((const struct cras_set_system_mute *)msg)->mute); |
| break; |
| case CRAS_SERVER_SET_SYSTEM_CAPTURE_MUTE_LOCKED: |
| if (!MSG_LEN_VALID(msg, struct cras_set_system_mute)) |
| return -EINVAL; |
| cras_system_set_capture_mute_locked( |
| ((const struct cras_set_system_mute *)msg)->mute); |
| break; |
| case CRAS_SERVER_SET_NODE_ATTR: { |
| const struct cras_set_node_attr *m = |
| (const struct cras_set_node_attr *)msg; |
| if (!MSG_LEN_VALID(msg, struct cras_set_node_attr)) |
| return -EINVAL; |
| cras_iodev_list_set_node_attr(m->node_id, m->attr, m->value); |
| break; |
| } |
| case CRAS_SERVER_SELECT_NODE: { |
| const struct cras_select_node *m = |
| (const struct cras_select_node *)msg; |
| if (!MSG_LEN_VALID(msg, struct cras_select_node) || |
| !direction_valid(m->direction)) |
| return -EINVAL; |
| cras_iodev_list_select_node(m->direction, m->node_id); |
| break; |
| } |
| case CRAS_SERVER_ADD_ACTIVE_NODE: { |
| const struct cras_add_active_node *m = |
| (const struct cras_add_active_node *)msg; |
| if (!MSG_LEN_VALID(msg, struct cras_add_active_node) || |
| !direction_valid(m->direction)) |
| return -EINVAL; |
| cras_iodev_list_add_active_node(m->direction, m->node_id); |
| break; |
| } |
| case CRAS_SERVER_RM_ACTIVE_NODE: { |
| const struct cras_rm_active_node *m = |
| (const struct cras_rm_active_node *)msg; |
| if (!MSG_LEN_VALID(msg, struct cras_rm_active_node) || |
| !direction_valid(m->direction)) |
| return -EINVAL; |
| cras_iodev_list_rm_active_node(m->direction, m->node_id); |
| break; |
| } |
| case CRAS_SERVER_RELOAD_DSP: |
| cras_dsp_reload_ini(); |
| break; |
| case CRAS_SERVER_DUMP_DSP_INFO: |
| cras_dsp_dump_info(); |
| break; |
| case CRAS_SERVER_DUMP_AUDIO_THREAD: |
| dump_audio_thread_info(client); |
| break; |
| case CRAS_SERVER_GET_ATLOG_FD: |
| get_atlog_fd(client); |
| break; |
| case CRAS_SERVER_DUMP_MAIN: { |
| struct cras_client_audio_debug_info_ready msg; |
| struct cras_server_state *state; |
| |
| state = cras_system_state_get_no_lock(); |
| memcpy(&state->main_thread_debug_info.main_log, main_log, |
| sizeof(struct main_thread_event_log)); |
| |
| cras_fill_client_audio_debug_info_ready(&msg); |
| client->ops->send_message_to_client(client, &msg.header, NULL, |
| 0); |
| break; |
| } |
| case CRAS_SERVER_DUMP_BT: { |
| struct cras_client_audio_debug_info_ready msg; |
| struct cras_server_state *state; |
| |
| state = cras_system_state_get_no_lock(); |
| #ifdef CRAS_DBUS |
| memcpy(&state->bt_debug_info.bt_log, btlog, |
| sizeof(struct cras_bt_event_log)); |
| memcpy(&state->bt_debug_info.wbs_logger, |
| cras_hfp_ag_get_wbs_logger(), |
| sizeof(struct packet_status_logger)); |
| #else |
| memset(&state->bt_debug_info.bt_log, 0, |
| sizeof(struct cras_bt_debug_info)); |
| memset(&state->bt_debug_info.wbs_logger, 0, |
| sizeof(struct packet_status_logger)); |
| #endif |
| |
| cras_fill_client_audio_debug_info_ready(&msg); |
| client->ops->send_message_to_client(client, &msg.header, NULL, |
| 0); |
| break; |
| } |
| case CRAS_SERVER_SET_BT_WBS_ENABLED: { |
| const struct cras_set_bt_wbs_enabled *m = |
| (const struct cras_set_bt_wbs_enabled *)msg; |
| if (!MSG_LEN_VALID(msg, struct cras_set_bt_wbs_enabled)) |
| return -EINVAL; |
| cras_system_set_bt_wbs_enabled(m->enabled); |
| break; |
| } |
| case CRAS_SERVER_DUMP_SNAPSHOTS: |
| dump_audio_thread_snapshots(client); |
| break; |
| case CRAS_SERVER_ADD_TEST_DEV: { |
| const struct cras_add_test_dev *m = |
| (const struct cras_add_test_dev *)msg; |
| if (!MSG_LEN_VALID(msg, struct cras_add_test_dev)) |
| return -EINVAL; |
| cras_iodev_list_add_test_dev(m->type); |
| break; |
| } |
| case CRAS_SERVER_TEST_DEV_COMMAND: { |
| const struct cras_test_dev_command *m = |
| (const struct cras_test_dev_command *)msg; |
| if (!MSG_LEN_VALID(msg, struct cras_test_dev_command)) |
| return -EINVAL; |
| cras_iodev_list_test_dev_command( |
| m->iodev_idx, (enum CRAS_TEST_IODEV_CMD)m->command, |
| m->data_len, m->data); |
| break; |
| } |
| case CRAS_SERVER_SUSPEND: |
| cras_system_set_suspended(1); |
| break; |
| case CRAS_SERVER_RESUME: |
| cras_system_set_suspended(0); |
| break; |
| case CRAS_CONFIG_GLOBAL_REMIX: { |
| const struct cras_config_global_remix *m = |
| (const struct cras_config_global_remix *)msg; |
| float *coefficient; |
| |
| if (!MSG_LEN_VALID(msg, struct cras_config_global_remix) || |
| m->num_channels > CRAS_MAX_REMIX_CHANNELS) |
| return -EINVAL; |
| const size_t coefficient_len = |
| (size_t)m->num_channels * (size_t)m->num_channels; |
| const size_t size_with_coefficients = |
| sizeof(*m) + |
| coefficient_len * sizeof(m->coefficient[0]); |
| if (size_with_coefficients != msg->length) |
| return -EINVAL; |
| |
| coefficient = |
| (float *)calloc(coefficient_len, sizeof(coefficient)); |
| if (!coefficient) { |
| syslog(LOG_ERR, |
| "Failed to create local coefficient array."); |
| break; |
| } |
| memcpy(coefficient, m->coefficient, |
| coefficient_len * sizeof(coefficient)); |
| |
| audio_thread_config_global_remix( |
| cras_iodev_list_get_audio_thread(), m->num_channels, |
| coefficient); |
| free(coefficient); |
| break; |
| } |
| case CRAS_SERVER_GET_HOTWORD_MODELS: { |
| if (!MSG_LEN_VALID(msg, struct cras_get_hotword_models)) |
| return -EINVAL; |
| handle_get_hotword_models( |
| client, |
| ((const struct cras_get_hotword_models *)msg)->node_id); |
| break; |
| } |
| case CRAS_SERVER_SET_HOTWORD_MODEL: { |
| const struct cras_set_hotword_model *m = |
| (const struct cras_set_hotword_model *)msg; |
| if (!MSG_LEN_VALID(msg, struct cras_set_hotword_model)) |
| return -EINVAL; |
| cras_iodev_list_set_hotword_model(m->node_id, m->model_name); |
| break; |
| } |
| case CRAS_SERVER_REGISTER_NOTIFICATION: { |
| const struct cras_register_notification *m = |
| (struct cras_register_notification *)msg; |
| if (!MSG_LEN_VALID(msg, struct cras_register_notification)) |
| return -EINVAL; |
| register_for_notification( |
| client, (enum CRAS_CLIENT_MESSAGE_ID)m->msg_id, |
| m->do_register); |
| break; |
| } |
| case CRAS_SERVER_SET_AEC_DUMP: { |
| const struct cras_set_aec_dump *m = |
| (const struct cras_set_aec_dump *)msg; |
| if (!MSG_LEN_VALID(msg, struct cras_set_aec_dump)) |
| return -EINVAL; |
| audio_thread_set_aec_dump(cras_iodev_list_get_audio_thread(), |
| m->stream_id, m->start, fd); |
| break; |
| } |
| case CRAS_SERVER_RELOAD_AEC_CONFIG: |
| cras_apm_list_reload_aec_config(); |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /* Declarations of cras_rclient operators for cras_control_rclient. */ |
| static const struct cras_rclient_ops cras_control_rclient_ops = { |
| .handle_message_from_client = ccr_handle_message_from_client, |
| .send_message_to_client = rclient_send_message_to_client, |
| .destroy = rclient_destroy, |
| }; |
| |
| /* |
| * Exported Functions. |
| */ |
| |
| /* Creates a client structure and sends a message back informing the client that |
| * the conneciton has succeeded. */ |
| struct cras_rclient *cras_control_rclient_create(int fd, size_t id) |
| { |
| /* Supports all directions but not CRAS_STREAM_UNDEFINED. */ |
| int supported_directions = |
| CRAS_STREAM_ALL_DIRECTION ^ |
| cras_stream_direction_mask(CRAS_STREAM_UNDEFINED); |
| |
| return rclient_generic_create(fd, id, &cras_control_rclient_ops, |
| supported_directions); |
| } |