mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-11-26 13:44:23 +08:00
7d95d027b1
This header contains IPC specific structures and code not related to BT and audio HAL protocols. This allows to fully decouple IPC from HAL messages. This is first step to make HAL part of IPC unit-testable and reusable between BT HAL and audio HAL.
427 lines
9.1 KiB
C
427 lines
9.1 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2013-2014 Intel Corporation. All rights reserved.
|
|
*
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stddef.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <unistd.h>
|
|
#include <glib.h>
|
|
|
|
#include "ipc-common.h"
|
|
#include "ipc.h"
|
|
#include "src/log.h"
|
|
|
|
struct ipc {
|
|
struct service_handler *services;
|
|
int service_max;
|
|
|
|
const char *path;
|
|
size_t size;
|
|
|
|
GIOChannel *cmd_io;
|
|
guint cmd_watch;
|
|
|
|
bool notifications;
|
|
GIOChannel *notif_io;
|
|
guint notif_watch;
|
|
|
|
ipc_disconnect_cb disconnect_cb;
|
|
void *disconnect_cb_data;
|
|
};
|
|
|
|
static void ipc_disconnect(struct ipc *ipc, bool in_cleanup)
|
|
{
|
|
if (ipc->cmd_watch) {
|
|
g_source_remove(ipc->cmd_watch);
|
|
ipc->cmd_watch = 0;
|
|
}
|
|
|
|
if (ipc->cmd_io) {
|
|
g_io_channel_shutdown(ipc->cmd_io, TRUE, NULL);
|
|
g_io_channel_unref(ipc->cmd_io);
|
|
ipc->cmd_io = NULL;
|
|
}
|
|
|
|
if (ipc->notif_watch) {
|
|
g_source_remove(ipc->notif_watch);
|
|
ipc->notif_watch = 0;
|
|
}
|
|
|
|
if (ipc->notif_io) {
|
|
g_io_channel_shutdown(ipc->notif_io, TRUE, NULL);
|
|
g_io_channel_unref(ipc->notif_io);
|
|
ipc->notif_io = NULL;
|
|
}
|
|
|
|
if (in_cleanup)
|
|
return;
|
|
|
|
if (ipc->disconnect_cb)
|
|
ipc->disconnect_cb(ipc->disconnect_cb_data);
|
|
}
|
|
|
|
static int ipc_handle_msg(struct service_handler *handlers, size_t max_index,
|
|
const void *buf, ssize_t len)
|
|
{
|
|
const struct ipc_hdr *msg = buf;
|
|
const struct ipc_handler *handler;
|
|
|
|
if (len < (ssize_t) sizeof(*msg)) {
|
|
DBG("message too small (%zd bytes)", len);
|
|
return -EBADMSG;
|
|
}
|
|
|
|
if (len != (ssize_t) (sizeof(*msg) + msg->len)) {
|
|
DBG("message malformed (%zd bytes)", len);
|
|
return -EBADMSG;
|
|
}
|
|
|
|
/* if service is valid */
|
|
if (msg->service_id > max_index) {
|
|
DBG("unknown service (0x%x)", msg->service_id);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* if service is registered */
|
|
if (!handlers[msg->service_id].handler) {
|
|
DBG("service not registered (0x%x)", msg->service_id);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* if opcode is valid */
|
|
if (msg->opcode == IPC_OP_STATUS ||
|
|
msg->opcode > handlers[msg->service_id].size) {
|
|
DBG("invalid opcode 0x%x for service 0x%x", msg->opcode,
|
|
msg->service_id);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* opcode is table offset + 1 */
|
|
handler = &handlers[msg->service_id].handler[msg->opcode - 1];
|
|
|
|
/* if payload size is valid */
|
|
if ((handler->var_len && handler->data_len > msg->len) ||
|
|
(!handler->var_len && handler->data_len != msg->len)) {
|
|
DBG("invalid size for opcode 0x%x service 0x%x",
|
|
msg->opcode, msg->service_id);
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
handler->handler(msg->payload, msg->len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean cmd_watch_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer user_data)
|
|
{
|
|
struct ipc *ipc = user_data;
|
|
|
|
char buf[IPC_MTU];
|
|
ssize_t ret;
|
|
int fd, err;
|
|
|
|
if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
|
|
info("IPC: command socket closed");
|
|
|
|
ipc->cmd_watch = 0;
|
|
goto fail;
|
|
}
|
|
|
|
fd = g_io_channel_unix_get_fd(io);
|
|
|
|
ret = read(fd, buf, sizeof(buf));
|
|
if (ret < 0) {
|
|
error("IPC: command read failed (%s)", strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
err = ipc_handle_msg(ipc->services, ipc->service_max, buf, ret);
|
|
if (err < 0) {
|
|
error("IPC: failed to handle message (%s)", strerror(-err));
|
|
goto fail;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
fail:
|
|
ipc_disconnect(ipc, false);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean notif_watch_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer user_data)
|
|
{
|
|
struct ipc *ipc = user_data;
|
|
|
|
info("IPC: notification socket closed");
|
|
|
|
ipc->notif_watch = 0;
|
|
|
|
ipc_disconnect(ipc, false);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static GIOChannel *ipc_connect(const char *path, size_t size,
|
|
GIOFunc connect_cb, void *user_data)
|
|
{
|
|
struct sockaddr_un addr;
|
|
GIOCondition cond;
|
|
GIOChannel *io;
|
|
int sk;
|
|
|
|
sk = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
|
|
if (sk < 0) {
|
|
error("IPC: failed to create socket: %d (%s)", errno,
|
|
strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
io = g_io_channel_unix_new(sk);
|
|
|
|
g_io_channel_set_close_on_unref(io, TRUE);
|
|
g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL);
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sun_family = AF_UNIX;
|
|
|
|
memcpy(addr.sun_path, path, size);
|
|
|
|
connect(sk, (struct sockaddr *) &addr, sizeof(addr));
|
|
|
|
cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
|
|
|
|
g_io_add_watch(io, cond, connect_cb, user_data);
|
|
|
|
return io;
|
|
}
|
|
|
|
static gboolean notif_connect_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer user_data)
|
|
{
|
|
struct ipc *ipc = user_data;
|
|
|
|
DBG("");
|
|
|
|
if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
|
|
error("IPC: notification socket connect failed");
|
|
|
|
ipc_disconnect(ipc, false);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
cond = G_IO_ERR | G_IO_HUP | G_IO_NVAL;
|
|
|
|
ipc->notif_watch = g_io_add_watch(io, cond, notif_watch_cb, ipc);
|
|
|
|
cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
|
|
|
|
ipc->cmd_watch = g_io_add_watch(ipc->cmd_io, cond, cmd_watch_cb, ipc);
|
|
|
|
info("IPC: successfully connected (with notifications)");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean cmd_connect_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer user_data)
|
|
{
|
|
struct ipc *ipc = user_data;
|
|
|
|
DBG("");
|
|
|
|
if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
|
|
error("IPC: command socket connect failed");
|
|
ipc_disconnect(ipc, false);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (ipc->notifications) {
|
|
ipc->notif_io = ipc_connect(ipc->path, ipc->size,
|
|
notif_connect_cb, ipc);
|
|
if (!ipc->notif_io)
|
|
ipc_disconnect(ipc, false);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
|
|
|
|
ipc->cmd_watch = g_io_add_watch(ipc->cmd_io, cond, cmd_watch_cb, ipc);
|
|
|
|
info("IPC: successfully connected (without notifications)");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
struct ipc *ipc_init(const char *path, size_t size, int max_service_id,
|
|
bool notifications,
|
|
ipc_disconnect_cb cb, void *cb_data)
|
|
{
|
|
struct ipc *ipc;
|
|
|
|
ipc = g_new0(struct ipc, 1);
|
|
|
|
ipc->services = g_new0(struct service_handler, max_service_id + 1);
|
|
ipc->service_max = max_service_id;
|
|
|
|
ipc->path = path;
|
|
ipc->size = size;
|
|
|
|
ipc->notifications = notifications;
|
|
|
|
ipc->cmd_io = ipc_connect(path, size, cmd_connect_cb, ipc);
|
|
if (!ipc->cmd_io) {
|
|
g_free(ipc->services);
|
|
g_free(ipc);
|
|
return NULL;
|
|
}
|
|
|
|
ipc->disconnect_cb = cb;
|
|
ipc->disconnect_cb_data = cb_data;
|
|
|
|
return ipc;
|
|
}
|
|
|
|
void ipc_cleanup(struct ipc *ipc)
|
|
{
|
|
ipc_disconnect(ipc, true);
|
|
|
|
g_free(ipc->services);
|
|
g_free(ipc);
|
|
}
|
|
|
|
static void ipc_send(int sk, uint8_t service_id, uint8_t opcode, uint16_t len,
|
|
void *param, int fd)
|
|
{
|
|
struct msghdr msg;
|
|
struct iovec iv[2];
|
|
struct ipc_hdr m;
|
|
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
|
struct cmsghdr *cmsg;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
memset(&m, 0, sizeof(m));
|
|
memset(cmsgbuf, 0, sizeof(cmsgbuf));
|
|
|
|
m.service_id = service_id;
|
|
m.opcode = opcode;
|
|
m.len = len;
|
|
|
|
iv[0].iov_base = &m;
|
|
iv[0].iov_len = sizeof(m);
|
|
|
|
iv[1].iov_base = param;
|
|
iv[1].iov_len = len;
|
|
|
|
msg.msg_iov = iv;
|
|
msg.msg_iovlen = 2;
|
|
|
|
if (fd >= 0) {
|
|
msg.msg_control = cmsgbuf;
|
|
msg.msg_controllen = sizeof(cmsgbuf);
|
|
|
|
cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
|
|
|
/* Initialize the payload */
|
|
memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
|
|
}
|
|
|
|
if (sendmsg(sk, &msg, 0) < 0) {
|
|
error("IPC send failed :%s", strerror(errno));
|
|
|
|
/* TODO disconnect IPC here when this function becomes static */
|
|
raise(SIGTERM);
|
|
}
|
|
}
|
|
|
|
void ipc_send_rsp(struct ipc *ipc, uint8_t service_id, uint8_t opcode,
|
|
uint8_t status)
|
|
{
|
|
struct ipc_status s;
|
|
int sk;
|
|
|
|
sk = g_io_channel_unix_get_fd(ipc->cmd_io);
|
|
|
|
if (status == IPC_STATUS_SUCCESS) {
|
|
ipc_send(sk, service_id, opcode, 0, NULL, -1);
|
|
return;
|
|
}
|
|
|
|
s.code = status;
|
|
|
|
ipc_send(sk, service_id, IPC_OP_STATUS, sizeof(s), &s, -1);
|
|
}
|
|
|
|
void ipc_send_rsp_full(struct ipc *ipc, uint8_t service_id, uint8_t opcode,
|
|
uint16_t len, void *param, int fd)
|
|
{
|
|
ipc_send(g_io_channel_unix_get_fd(ipc->cmd_io), service_id, opcode, len,
|
|
param, fd);
|
|
}
|
|
|
|
void ipc_send_notif(struct ipc *ipc, uint8_t service_id, uint8_t opcode,
|
|
uint16_t len, void *param)
|
|
{
|
|
if (!ipc || !ipc->notif_io)
|
|
return;
|
|
|
|
ipc_send(g_io_channel_unix_get_fd(ipc->notif_io), service_id, opcode,
|
|
len, param, -1);
|
|
}
|
|
|
|
void ipc_register(struct ipc *ipc, uint8_t service,
|
|
const struct ipc_handler *handlers, uint8_t size)
|
|
{
|
|
if (service > ipc->service_max)
|
|
return;
|
|
|
|
ipc->services[service].handler = handlers;
|
|
ipc->services[service].size = size;
|
|
}
|
|
|
|
void ipc_unregister(struct ipc *ipc, uint8_t service)
|
|
{
|
|
if (service > ipc->service_max)
|
|
return;
|
|
|
|
ipc->services[service].handler = NULL;
|
|
ipc->services[service].size = 0;
|
|
}
|