|  | /* | 
|  | * 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 <pthread.h> | 
|  | #include <errno.h> | 
|  | #include <sys/socket.h> | 
|  | #include <sys/un.h> | 
|  | #include <stdbool.h> | 
|  | #include <poll.h> | 
|  | #include <unistd.h> | 
|  | #include <stdint.h> | 
|  | #include <stdlib.h> | 
|  |  | 
|  | #include <cutils/properties.h> | 
|  |  | 
|  | #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); | 
|  | } |