blob: 5f2ce632415aa2ef6cc5a897aa53039c165f678d [file] [log] [blame]
/* Copyright (c) 2012 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.
*/
#define _GNU_SOURCE /* Needed for Linux socket credential passing. */
#ifdef CRAS_DBUS
#include <dbus/dbus.h>
#endif
#include <errno.h>
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <syslog.h>
#include <unistd.h>
#ifdef CRAS_DBUS
#include "cras_a2dp_endpoint.h"
#include "cras_bt_manager.h"
#include "cras_bt_device.h"
#include "cras_bt_player.h"
#include "cras_dbus.h"
#include "cras_dbus_control.h"
#include "cras_hfp_ag_profile.h"
#include "cras_telephony.h"
#endif
#include "cras_alert.h"
#include "cras_audio_thread_monitor.h"
#include "cras_config.h"
#include "cras_device_monitor.h"
#include "cras_hotword_handler.h"
#include "cras_iodev_list.h"
#include "cras_main_message.h"
#include "cras_messages.h"
#include "cras_metrics.h"
#include "cras_non_empty_audio_handler.h"
#include "cras_observer.h"
#include "cras_rclient.h"
#include "cras_server.h"
#include "cras_server_metrics.h"
#include "cras_system_state.h"
#include "cras_tm.h"
#include "cras_types.h"
#include "cras_udev.h"
#include "cras_util.h"
#include "cras_mix.h"
#include "utlist.h"
/* Store a list of clients that are attached to the server.
* Members:
* id - Unique identifier for this client.
* fd - socket file descriptor used to communicate with client.
* ucred - Process, user, and group ID of the client.
* client - rclient to handle messages from this client.
* pollfd - Pointer to struct pollfd for this callback.
*/
struct attached_client {
size_t id;
int fd;
struct ucred ucred;
struct cras_rclient *client;
struct pollfd *pollfd;
struct attached_client *next, *prev;
};
/* Stores file descriptors to callback mappings for clients. Callback/fd/data
* args are registered by clients. When fd is ready, the callback will be
* called on the main server thread and the callback data will be passed back to
* it. This allows the use of the main server loop instead of spawning a thread
* to watch file descriptors. The client can then read or write the fd.
* Members:
* fd - The file descriptor passed to select.
* callback - The funciton to call when fd is ready.
* callback_data - Pointer passed to the callback.
* pollfd - Pointer to struct pollfd for this callback.
* events - The events to poll for.
*/
struct client_callback {
int select_fd;
void (*callback)(void *data, int revents);
void *callback_data;
struct pollfd *pollfd;
int deleted;
int events;
struct client_callback *prev, *next;
};
/* Stores callback function and argument data to be executed later. */
struct system_task {
void (*callback)(void *);
void *callback_data;
struct system_task *next, *prev;
};
/* A structure wraps data related to server socket. */
struct server_socket {
struct sockaddr_un addr;
int fd;
enum CRAS_CONNECTION_TYPE type;
};
/* Local server data. */
struct server_data {
struct attached_client *clients_head;
size_t num_clients;
struct client_callback *client_callbacks;
struct system_task *system_tasks;
size_t num_client_callbacks;
size_t next_client_id;
struct server_socket server_sockets[CRAS_NUM_CONN_TYPE];
} server_instance;
/* Cleanup a given server_socket */
static void server_socket_cleanup(struct server_socket *socket)
{
if (socket && socket->fd >= 0) {
close(socket->fd);
socket->fd = -1;
unlink(socket->addr.sun_path);
}
}
/* Remove a client from the list and destroy it. Calling rclient_destroy will
* also free all the streams owned by the client */
static void remove_client(struct attached_client *client)
{
close(client->fd);
DL_DELETE(server_instance.clients_head, client);
server_instance.num_clients--;
cras_rclient_destroy(client->client);
free(client);
}
/* This is called when "select" indicates that the client has written data to
* the socket. Read out one message and pass it to the client message handler.
*/
static void handle_message_from_client(struct attached_client *client)
{
uint8_t buf[CRAS_SERV_MAX_MSG_SIZE];
int nread;
unsigned int num_fds = 2;
int fds[num_fds];
nread = cras_recv_with_fds(client->fd, buf, sizeof(buf), fds, &num_fds);
if (nread < 0)
goto read_error;
if (cras_rclient_buffer_from_client(client->client, buf, nread, fds,
num_fds) < 0)
goto read_error;
return;
read_error:
for (int i = 0; i < num_fds; i++)
if (fds[i] >= 0)
close(fds[i]);
switch (nread) {
case 0:
break;
default:
syslog(LOG_DEBUG, "read err [%d] '%s', removing client %zu",
-nread, strerror(-nread), client->id);
break;
}
remove_client(client);
}
/* Discovers and fills in info about the client that can be obtained from the
* socket. The pid of the attaching client identifies it in logs. */
static void fill_client_info(struct attached_client *client)
{
socklen_t ucred_length = sizeof(client->ucred);
if (getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &client->ucred,
&ucred_length))
syslog(LOG_INFO, "Failed to get client socket info\n");
}
/* Fills the server_state with the current list of attached clients. */
static void send_client_list_to_clients(struct server_data *serv)
{
struct attached_client *c;
struct cras_attached_client_info *info;
struct cras_server_state *state;
unsigned i;
state = cras_system_state_update_begin();
if (!state)
return;
state->num_attached_clients =
MIN(CRAS_MAX_ATTACHED_CLIENTS, serv->num_clients);
info = state->client_info;
i = 0;
DL_FOREACH (serv->clients_head, c) {
info->id = c->id;
info->pid = c->ucred.pid;
info->uid = c->ucred.uid;
info->gid = c->ucred.gid;
info++;
if (++i == CRAS_MAX_ATTACHED_CLIENTS)
break;
}
cras_system_state_update_complete();
}
/* Handles requests from a client to attach to the server. Create a local
* structure to track the client, assign it a unique id and let it attach */
static void handle_new_connection(struct server_socket *server_socket)
{
int connection_fd;
struct attached_client *poll_client;
socklen_t address_length;
poll_client = malloc(sizeof(struct attached_client));
if (poll_client == NULL) {
syslog(LOG_ERR, "Allocating poll_client");
return;
}
memset(&address_length, 0, sizeof(address_length));
connection_fd = accept(server_socket->fd,
(struct sockaddr *)&server_socket->addr,
&address_length);
if (connection_fd < 0) {
syslog(LOG_ERR, "connecting");
free(poll_client);
return;
}
/* find next available client id */
while (1) {
struct attached_client *out;
DL_SEARCH_SCALAR(server_instance.clients_head, out, id,
server_instance.next_client_id);
poll_client->id = server_instance.next_client_id;
server_instance.next_client_id++;
if (out == NULL)
break;
}
/* When full, getting an error is preferable to blocking. */
cras_make_fd_nonblocking(connection_fd);
poll_client->fd = connection_fd;
poll_client->next = NULL;
poll_client->pollfd = NULL;
fill_client_info(poll_client);
poll_client->client = cras_rclient_create(
connection_fd, poll_client->id, server_socket->type);
if (poll_client->client == NULL) {
syslog(LOG_ERR, "failed to create client");
goto error;
}
DL_APPEND(server_instance.clients_head, poll_client);
server_instance.num_clients++;
/* Send a current list of available inputs and outputs. */
cras_iodev_list_update_device_list();
send_client_list_to_clients(&server_instance);
return;
error:
close(connection_fd);
free(poll_client);
return;
}
/* Add a file descriptor to be passed to select in the main loop. This is
* registered with system state so that it is called when any client asks to
* have a callback triggered based on an fd being readable. */
static int add_select_fd(int fd, void (*cb)(void *data, int events),
void *callback_data, int events, void *server_data)
{
struct client_callback *new_cb;
struct client_callback *client_cb;
struct server_data *serv;
serv = (struct server_data *)server_data;
if (serv == NULL)
return -EINVAL;
/* Check if fd already exists. */
DL_FOREACH (serv->client_callbacks, client_cb)
if (client_cb->select_fd == fd && !client_cb->deleted)
return -EEXIST;
new_cb = (struct client_callback *)calloc(1, sizeof(*new_cb));
if (new_cb == NULL)
return -ENOMEM;
new_cb->select_fd = fd;
new_cb->callback = cb;
new_cb->callback_data = callback_data;
new_cb->deleted = 0;
new_cb->events = events;
new_cb->pollfd = NULL;
DL_APPEND(serv->client_callbacks, new_cb);
server_instance.num_client_callbacks++;
return 0;
}
/* Removes a file descriptor to be passed to select in the main loop. This is
* registered with system state so that it is called when any client asks to
* remove a callback added with add_select_fd. */
static void rm_select_fd(int fd, void *server_data)
{
struct server_data *serv;
struct client_callback *client_cb;
serv = (struct server_data *)server_data;
if (serv == NULL)
return;
DL_FOREACH (serv->client_callbacks, client_cb)
if (client_cb->select_fd == fd)
client_cb->deleted = 1;
}
/* Creates a new task entry and append to system_tasks list, which will be
* executed in main loop later without wait time.
*/
static int add_task(void (*cb)(void *data), void *callback_data,
void *server_data)
{
struct server_data *serv;
struct system_task *new_task;
serv = (struct server_data *)server_data;
if (serv == NULL)
return -EINVAL;
new_task = (struct system_task *)calloc(1, sizeof(*new_task));
if (new_task == NULL)
return -ENOMEM;
new_task->callback = cb;
new_task->callback_data = callback_data;
DL_APPEND(serv->system_tasks, new_task);
return 0;
}
/* Cleans up the file descriptor list removing items deleted during the main
* loop iteration. */
static void cleanup_select_fds(void *server_data)
{
struct server_data *serv;
struct client_callback *client_cb;
serv = (struct server_data *)server_data;
if (serv == NULL)
return;
DL_FOREACH (serv->client_callbacks, client_cb)
if (client_cb->deleted) {
DL_DELETE(serv->client_callbacks, client_cb);
server_instance.num_client_callbacks--;
free(client_cb);
}
}
/* Checks that at least two outputs are present (one will be the "empty"
* default device. */
void check_output_exists(struct cras_timer *t, void *data)
{
if (cras_iodev_list_get_outputs(NULL) < 2)
cras_metrics_log_event(kNoCodecsFoundMetric);
}
#if defined(__amd64__)
/* CPU detection - probaby best to move this elsewhere */
static void cpuid(unsigned int *eax, unsigned int *ebx, unsigned int *ecx,
unsigned int *edx, unsigned int op)
{
// clang-format off
__asm__ __volatile__ (
"cpuid"
: "=a" (*eax),
"=b" (*ebx),
"=c" (*ecx),
"=d" (*edx)
: "a" (op), "c" (0)
);
// clang-format on
}
static unsigned int cpu_x86_flags(void)
{
unsigned int eax, ebx, ecx, edx, id;
unsigned int cpu_flags = 0;
cpuid(&id, &ebx, &ecx, &edx, 0);
if (id >= 1) {
cpuid(&eax, &ebx, &ecx, &edx, 1);
if (ecx & (1 << 20))
cpu_flags |= CPU_X86_SSE4_2;
if (ecx & (1 << 28))
cpu_flags |= CPU_X86_AVX;
if (ecx & (1 << 12))
cpu_flags |= CPU_X86_FMA;
}
if (id >= 7) {
cpuid(&eax, &ebx, &ecx, &edx, 7);
if (ebx & (1 << 5))
cpu_flags |= CPU_X86_AVX2;
}
return cpu_flags;
}
#endif
int cpu_get_flags(void)
{
#if defined(__amd64__)
return cpu_x86_flags();
#endif
return 0;
}
/*
* Exported Interface.
*/
int cras_server_init()
{
/* Log to syslog. */
openlog("cras_server", LOG_PID, LOG_USER);
server_instance.next_client_id = RESERVED_CLIENT_IDS;
/* Initialize global observer. */
cras_observer_server_init();
/* init mixer with CPU capabilities */
cras_mix_init(cpu_get_flags());
/* Allow clients to register callbacks for file descriptors.
* add_select_fd and rm_select_fd will add and remove file descriptors
* from the list that are passed to select in the main loop below. */
cras_system_set_select_handler(add_select_fd, rm_select_fd,
&server_instance);
cras_system_set_add_task_handler(add_task, &server_instance);
cras_main_message_init();
/* Initializes all server_sockets */
for (int conn_type = 0; conn_type < CRAS_NUM_CONN_TYPE; conn_type++) {
server_instance.server_sockets[conn_type].fd = -1;
}
return 0;
}
/*
* Creates a server socket with given connection type and listens on it.
* The socket_file will be created under cras_config_get_system_socket_file_dir
* with permission=0770. The socket_fd will be listened with parameter
* backlog=5.
*
* Returns 0 on success and leaves the created fd and the address information
* in server_socket.
* When error occurs, the created fd will be closed and the file path will be
* unlinked and returns negative error code.
*/
static int create_and_listen_server_socket(enum CRAS_CONNECTION_TYPE conn_type,
struct server_socket *server_socket)
{
int socket_fd = -1;
int rc = 0;
struct sockaddr_un *addr = &server_socket->addr;
socket_fd = socket(PF_UNIX, SOCK_SEQPACKET, 0);
if (socket_fd < 0) {
syslog(LOG_ERR, "Main server socket failed.");
rc = socket_fd;
goto error;
}
memset(addr, 0, sizeof(*addr));
addr->sun_family = AF_UNIX;
rc = cras_fill_socket_path(conn_type, addr->sun_path);
if (rc < 0)
goto error;
unlink(addr->sun_path);
/* Linux quirk: calling fchmod before bind, sets the permissions of the
* file created by bind, leaving no window for it to be modified. Start
* with very restricted permissions. */
rc = fchmod(socket_fd, 0700);
if (rc < 0)
goto error;
rc = bind(socket_fd, (struct sockaddr *)addr,
sizeof(struct sockaddr_un));
if (rc < 0) {
syslog(LOG_ERR, "Bind to server socket failed.");
rc = -errno;
goto error;
}
/* Let other members in our group play audio through this socket. */
rc = chmod(addr->sun_path, 0770);
if (rc < 0)
goto error;
if (listen(socket_fd, 5) != 0) {
syslog(LOG_ERR, "Listen on server socket failed.");
rc = -errno;
goto error;
}
server_socket->fd = socket_fd;
server_socket->type = conn_type;
return 0;
error:
if (socket_fd >= 0) {
close(socket_fd);
unlink(addr->sun_path);
}
return rc;
}
/* Cleans up all server_socket in server_instance */
static void cleanup_server_sockets()
{
for (int conn_type = 0; conn_type < CRAS_NUM_CONN_TYPE; conn_type++) {
server_socket_cleanup(
&server_instance.server_sockets[conn_type]);
}
}
int cras_server_run(unsigned int profile_disable_mask)
{
static const unsigned int OUTPUT_CHECK_MS = 5 * 1000;
#ifdef CRAS_DBUS
DBusConnection *dbus_conn;
#endif
int rc = 0;
struct attached_client *elm;
struct client_callback *client_cb;
struct system_task *tasks;
struct system_task *system_task;
struct cras_tm *tm;
struct timespec ts, *poll_timeout;
int timers_active;
struct pollfd *pollfds;
unsigned int pollfds_size = 32;
unsigned int num_pollfds, poll_size_needed;
pollfds = malloc(sizeof(*pollfds) * pollfds_size);
cras_udev_start_sound_subsystem_monitor();
#ifdef CRAS_DBUS
cras_bt_device_start_monitor();
#endif
cras_server_metrics_init();
cras_device_monitor_init();
cras_hotword_handler_init();
cras_non_empty_audio_handler_init();
cras_audio_thread_monitor_init();
#ifdef CRAS_DBUS
dbus_threads_init_default();
dbus_conn = cras_dbus_connect_system_bus();
if (dbus_conn) {
cras_bt_start(dbus_conn);
if (!(profile_disable_mask & CRAS_SERVER_PROFILE_MASK_HFP))
cras_hfp_ag_profile_create(dbus_conn);
if (!(profile_disable_mask & CRAS_SERVER_PROFILE_MASK_HSP))
cras_hsp_ag_profile_create(dbus_conn);
cras_telephony_start(dbus_conn);
if (!(profile_disable_mask & CRAS_SERVER_PROFILE_MASK_A2DP))
cras_a2dp_endpoint_create(dbus_conn);
cras_bt_player_create(dbus_conn);
cras_dbus_control_start(dbus_conn);
}
#endif
for (int conn_type = 0; conn_type < CRAS_NUM_CONN_TYPE; conn_type++) {
rc = create_and_listen_server_socket(
conn_type, &server_instance.server_sockets[conn_type]);
if (rc < 0)
goto bail;
}
tm = cras_system_state_get_tm();
if (!tm) {
syslog(LOG_ERR, "Getting timer manager.");
rc = -ENOMEM;
goto bail;
}
/* After a delay, make sure there is at least one real output device. */
cras_tm_create_timer(tm, OUTPUT_CHECK_MS, check_output_exists, 0);
/* Main server loop - client callbacks are run from this context. */
while (1) {
poll_size_needed = CRAS_NUM_CONN_TYPE +
server_instance.num_clients +
server_instance.num_client_callbacks;
if (poll_size_needed > pollfds_size) {
pollfds_size = 2 * poll_size_needed;
pollfds = realloc(pollfds,
sizeof(*pollfds) * pollfds_size);
}
for (int conn_type = 0; conn_type < CRAS_NUM_CONN_TYPE;
conn_type++) {
pollfds[conn_type].fd =
server_instance.server_sockets[conn_type].fd;
pollfds[conn_type].events = POLLIN;
}
num_pollfds = CRAS_NUM_CONN_TYPE;
DL_FOREACH (server_instance.clients_head, elm) {
pollfds[num_pollfds].fd = elm->fd;
pollfds[num_pollfds].events = POLLIN;
elm->pollfd = &pollfds[num_pollfds];
num_pollfds++;
}
DL_FOREACH (server_instance.client_callbacks, client_cb) {
if (client_cb->deleted)
continue;
pollfds[num_pollfds].fd = client_cb->select_fd;
pollfds[num_pollfds].events = client_cb->events;
client_cb->pollfd = &pollfds[num_pollfds];
num_pollfds++;
}
tasks = server_instance.system_tasks;
server_instance.system_tasks = NULL;
DL_FOREACH (tasks, system_task) {
system_task->callback(system_task->callback_data);
DL_DELETE(tasks, system_task);
free(system_task);
}
timers_active = cras_tm_get_next_timeout(tm, &ts);
/*
* If new client task has been scheduled, no need to wait
* for timeout, just do another loop to execute them.
*/
if (server_instance.system_tasks)
poll_timeout = NULL;
else
poll_timeout = timers_active ? &ts : NULL;
rc = ppoll(pollfds, num_pollfds, poll_timeout, NULL);
if (rc < 0)
continue;
cras_tm_call_callbacks(tm);
/* Check for new connections. */
for (int conn_type = 0; conn_type < CRAS_NUM_CONN_TYPE;
conn_type++) {
if (pollfds[conn_type].revents & POLLIN)
handle_new_connection(
&server_instance
.server_sockets[conn_type]);
}
/* Check if there are messages pending for any clients. */
DL_FOREACH (server_instance.clients_head, elm)
if (elm->pollfd && elm->pollfd->revents & POLLIN)
handle_message_from_client(elm);
/* Check any client-registered fd/callback pairs. */
DL_FOREACH (server_instance.client_callbacks, client_cb)
if (!client_cb->deleted && client_cb->pollfd &&
(client_cb->pollfd->revents & client_cb->events))
client_cb->callback(client_cb->callback_data,
client_cb->pollfd->revents);
cleanup_select_fds(&server_instance);
#ifdef CRAS_DBUS
if (dbus_conn)
cras_dbus_dispatch(dbus_conn);
#endif
cras_alert_process_all_pending_alerts();
}
bail:
cleanup_server_sockets();
free(pollfds);
cras_observer_server_free();
return rc;
}
void cras_server_send_to_all_clients(const struct cras_client_message *msg)
{
struct attached_client *client;
DL_FOREACH (server_instance.clients_head, client)
cras_rclient_send_message(client->client, msg, NULL, 0);
}