/* * Copyright (C) 2013 Intel Corporation * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include "hal.h" #include "hal-msg.h" #include "hal-log.h" #include "ipc-common.h" #include "hal-ipc.h" #define CONNECT_TIMEOUT (10 * 1000) static int listen_sk = -1; static int cmd_sk = -1; static int notif_sk = -1; static pthread_mutex_t cmd_sk_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_t notif_th = 0; struct service_handler { const struct hal_ipc_handler *handler; uint8_t size; }; static struct service_handler services[HAL_SERVICE_ID_MAX + 1]; void hal_ipc_register(uint8_t service, const struct hal_ipc_handler *handlers, uint8_t size) { services[service].handler = handlers; services[service].size = size; } void hal_ipc_unregister(uint8_t service) { services[service].handler = NULL; services[service].size = 0; } static bool handle_msg(void *buf, ssize_t len, int fd) { struct ipc_hdr *msg = buf; const struct hal_ipc_handler *handler; uint8_t opcode; if (len < (ssize_t) sizeof(*msg)) { error("IPC: message too small (%zd bytes)", len); return false; } if (len != (ssize_t) (sizeof(*msg) + msg->len)) { error("IPC: message malformed (%zd bytes)", len); return false; } /* if service is valid */ if (msg->service_id > HAL_SERVICE_ID_MAX) { error("IPC: unknown service (0x%x)", msg->service_id); return false; } /* if service is registered */ if (!services[msg->service_id].handler) { error("IPC: unregistered service (0x%x)", msg->service_id); return false; } /* if opcode fit valid range */ if (msg->opcode < HAL_MINIMUM_EVENT) { error("IPC: invalid opcode for service 0x%x (0x%x)", msg->service_id, msg->opcode); return false; } /* * opcode is used as table offset and must be adjusted as events start * with HAL_MINIMUM_EVENT offset */ opcode = msg->opcode - HAL_MINIMUM_EVENT; /* if opcode is valid */ if (opcode >= services[msg->service_id].size) { error("IPC: invalid opcode for service 0x%x (0x%x)", msg->service_id, msg->opcode); return false; } handler = &services[msg->service_id].handler[opcode]; /* if payload size is valid */ if ((handler->var_len && handler->data_len > msg->len) || (!handler->var_len && handler->data_len != msg->len)) { error("IPC: message size invalid for service 0x%x opcode 0x%x " "(%u bytes)", msg->service_id, msg->opcode, msg->len); return false; } handler->handler(msg->payload, msg->len, fd); return true; } static void *notification_handler(void *data) { struct msghdr msg; struct iovec iv; struct cmsghdr *cmsg; char cmsgbuf[CMSG_SPACE(sizeof(int))]; char buf[IPC_MTU]; ssize_t ret; int fd; bt_thread_associate(); while (true) { memset(&msg, 0, sizeof(msg)); memset(buf, 0, sizeof(buf)); memset(cmsgbuf, 0, sizeof(cmsgbuf)); iv.iov_base = buf; iv.iov_len = sizeof(buf); msg.msg_iov = &iv; msg.msg_iovlen = 1; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); ret = recvmsg(notif_sk, &msg, 0); if (ret < 0) { error("Receiving notifications failed: %s", strerror(errno)); goto failed; } /* socket was shutdown */ if (ret == 0) { pthread_mutex_lock(&cmd_sk_mutex); if (cmd_sk == -1) { pthread_mutex_unlock(&cmd_sk_mutex); break; } pthread_mutex_unlock(&cmd_sk_mutex); error("Notification socket closed"); goto failed; } fd = -1; /* Receive auxiliary data in msg */ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { memcpy(&fd, CMSG_DATA(cmsg), sizeof(int)); break; } } if (!handle_msg(buf, ret, fd)) goto failed; } close(notif_sk); notif_sk = -1; bt_thread_disassociate(); DBG("exit"); return NULL; failed: exit(EXIT_FAILURE); } static int accept_connection(int sk) { int err; struct pollfd pfd; int new_sk; memset(&pfd, 0 , sizeof(pfd)); pfd.fd = sk; pfd.events = POLLIN; err = poll(&pfd, 1, CONNECT_TIMEOUT); if (err < 0) { err = errno; error("Failed to poll: %d (%s)", err, strerror(err)); return -1; } if (err == 0) { error("bluetoothd connect timeout"); return -1; } new_sk = accept(sk, NULL, NULL); if (new_sk < 0) { err = errno; error("Failed to accept socket: %d (%s)", err, strerror(err)); return -1; } return new_sk; } bool hal_ipc_accept(void) { int err; cmd_sk = accept_connection(listen_sk); if (cmd_sk < 0) return false; notif_sk = accept_connection(listen_sk); if (notif_sk < 0) { close(cmd_sk); cmd_sk = -1; return false; } err = pthread_create(¬if_th, NULL, notification_handler, NULL); if (err) { notif_th = 0; error("Failed to start notification thread: %d (%s)", err, strerror(err)); close(cmd_sk); cmd_sk = -1; close(notif_sk); notif_sk = -1; return false; } info("IPC connected"); return true; } bool hal_ipc_init(const char *path, size_t size) { struct sockaddr_un addr; int sk; int err; sk = socket(AF_LOCAL, SOCK_SEQPACKET, 0); if (sk < 0) { err = errno; error("Failed to create socket: %d (%s)", err, strerror(err)); return false; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; memcpy(addr.sun_path, path, size); if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { err = errno; error("Failed to bind socket: %d (%s)", err, strerror(err)); close(sk); return false; } if (listen(sk, 2) < 0) { err = errno; error("Failed to listen on socket: %d (%s)", err, strerror(err)); close(sk); return false; } listen_sk = sk; return true; } void hal_ipc_cleanup(void) { close(listen_sk); listen_sk = -1; pthread_mutex_lock(&cmd_sk_mutex); if (cmd_sk >= 0) { close(cmd_sk); cmd_sk = -1; } pthread_mutex_unlock(&cmd_sk_mutex); if (notif_sk < 0) return; shutdown(notif_sk, SHUT_RD); pthread_join(notif_th, NULL); notif_th = 0; } int hal_ipc_cmd(uint8_t service_id, uint8_t opcode, uint16_t len, void *param, size_t *rsp_len, void *rsp, int *fd) { ssize_t ret; struct msghdr msg; struct iovec iv[2]; struct ipc_hdr cmd; char cmsgbuf[CMSG_SPACE(sizeof(int))]; struct ipc_status s; size_t s_len = sizeof(s); if (cmd_sk < 0) { error("Invalid cmd socket passed to hal_ipc_cmd"); goto failed; } if (!rsp || !rsp_len) { memset(&s, 0, s_len); rsp_len = &s_len; rsp = &s; } memset(&msg, 0, sizeof(msg)); memset(&cmd, 0, sizeof(cmd)); cmd.service_id = service_id; cmd.opcode = opcode; cmd.len = len; iv[0].iov_base = &cmd; iv[0].iov_len = sizeof(cmd); iv[1].iov_base = param; iv[1].iov_len = len; msg.msg_iov = iv; msg.msg_iovlen = 2; pthread_mutex_lock(&cmd_sk_mutex); ret = sendmsg(cmd_sk, &msg, 0); if (ret < 0) { error("Sending command failed:%s", strerror(errno)); pthread_mutex_unlock(&cmd_sk_mutex); goto failed; } /* socket was shutdown */ if (ret == 0) { error("Command socket closed"); pthread_mutex_unlock(&cmd_sk_mutex); goto failed; } memset(&msg, 0, sizeof(msg)); memset(&cmd, 0, sizeof(cmd)); iv[0].iov_base = &cmd; iv[0].iov_len = sizeof(cmd); iv[1].iov_base = rsp; iv[1].iov_len = *rsp_len; msg.msg_iov = iv; msg.msg_iovlen = 2; if (fd) { memset(cmsgbuf, 0, sizeof(cmsgbuf)); msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); } ret = recvmsg(cmd_sk, &msg, 0); pthread_mutex_unlock(&cmd_sk_mutex); if (ret < 0) { error("Receiving command response failed: %s", strerror(errno)); goto failed; } if (ret < (ssize_t) sizeof(cmd)) { error("Too small response received(%zd bytes)", ret); goto failed; } if (cmd.service_id != service_id) { error("Invalid service id (0x%x vs 0x%x)", cmd.service_id, service_id); goto failed; } if (ret != (ssize_t) (sizeof(cmd) + cmd.len)) { error("Malformed response received(%zd bytes)", ret); goto failed; } if (cmd.opcode != opcode && cmd.opcode != HAL_OP_STATUS) { error("Invalid opcode received (0x%x vs 0x%x)", cmd.opcode, opcode); goto failed; } if (cmd.opcode == HAL_OP_STATUS) { struct ipc_status *s = rsp; if (sizeof(*s) != cmd.len) { error("Invalid status length"); goto failed; } if (s->code == HAL_STATUS_SUCCESS) { error("Invalid success status response"); goto failed; } return s->code; } /* Receive auxiliary data in msg */ if (fd) { struct cmsghdr *cmsg; *fd = -1; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { memcpy(fd, CMSG_DATA(cmsg), sizeof(int)); break; } } } *rsp_len = cmd.len; return BT_STATUS_SUCCESS; failed: exit(EXIT_FAILURE); }