3178 lines
70 KiB
C
3178 lines
70 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
|
|
* Copyright (C) 2010 Signove
|
|
* Copyright (C) 2014 Intel Corporation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; 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 <netinet/in.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include "lib/bluetooth.h"
|
|
#include "bluetooth/l2cap.h"
|
|
#include "btio/btio.h"
|
|
#include "src/log.h"
|
|
|
|
#include "mcap-lib.h"
|
|
|
|
#define MCAP_BTCLOCK_HALF (MCAP_BTCLOCK_FIELD / 2)
|
|
#define CLK CLOCK_MONOTONIC
|
|
|
|
#define MCAP_CSP_ERROR g_quark_from_static_string("mcap-csp-error-quark")
|
|
#define MAX_RETRIES 10
|
|
#define SAMPLE_COUNT 20
|
|
|
|
#define RESPONSE_TIMER 6 /* seconds */
|
|
#define MAX_CACHED 10 /* 10 devices */
|
|
|
|
#define MCAP_ERROR g_quark_from_static_string("mcap-error-quark")
|
|
|
|
#define RELEASE_TIMER(__mcl) do { \
|
|
if (__mcl->tid) { \
|
|
g_source_remove(__mcl->tid); \
|
|
__mcl->tid = 0; \
|
|
} \
|
|
} while(0)
|
|
|
|
struct mcap_csp {
|
|
uint64_t base_tmstamp; /* CSP base timestamp */
|
|
struct timespec base_time; /* CSP base time when timestamp set */
|
|
guint local_caps; /* CSP-Master: have got remote caps */
|
|
guint remote_caps; /* CSP-Slave: remote master got caps */
|
|
guint rem_req_acc; /* CSP-Slave: accuracy required by master */
|
|
guint ind_expected; /* CSP-Master: indication expected */
|
|
uint8_t csp_req; /* CSP-Master: Request control flag */
|
|
guint ind_timer; /* CSP-Slave: indication timer */
|
|
guint set_timer; /* CSP-Slave: delayed set timer */
|
|
void *set_data; /* CSP-Slave: delayed set data */
|
|
void *csp_priv_data; /* CSP-Master: In-flight request data */
|
|
};
|
|
|
|
struct mcap_sync_cap_cbdata {
|
|
mcap_sync_cap_cb cb;
|
|
gpointer user_data;
|
|
};
|
|
|
|
struct mcap_sync_set_cbdata {
|
|
mcap_sync_set_cb cb;
|
|
gpointer user_data;
|
|
};
|
|
|
|
struct csp_caps {
|
|
int ts_acc; /* timestamp accuracy */
|
|
int ts_res; /* timestamp resolution */
|
|
int latency; /* Read BT clock latency */
|
|
int preempt_thresh; /* Preemption threshold for latency */
|
|
int syncleadtime_ms; /* SyncLeadTime in ms */
|
|
};
|
|
|
|
struct sync_set_data {
|
|
uint8_t update;
|
|
uint32_t sched_btclock;
|
|
uint64_t timestamp;
|
|
int ind_freq;
|
|
gboolean role;
|
|
};
|
|
|
|
struct connect_mcl {
|
|
struct mcap_mcl *mcl; /* MCL for this operation */
|
|
mcap_mcl_connect_cb connect_cb; /* Connect callback */
|
|
GDestroyNotify destroy; /* Destroy callback */
|
|
gpointer user_data; /* Callback user data */
|
|
};
|
|
|
|
typedef union {
|
|
mcap_mdl_operation_cb op;
|
|
mcap_mdl_operation_conf_cb op_conf;
|
|
mcap_mdl_notify_cb notify;
|
|
} mcap_cb_type;
|
|
|
|
struct mcap_mdl_op_cb {
|
|
struct mcap_mdl *mdl; /* MDL for this operation */
|
|
mcap_cb_type cb; /* Operation callback */
|
|
GDestroyNotify destroy; /* Destroy callback */
|
|
gpointer user_data; /* Callback user data */
|
|
};
|
|
|
|
/* MCAP finite state machine functions */
|
|
static void proc_req_connected(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
|
|
static void proc_req_pending(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
|
|
static void proc_req_active(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
|
|
|
|
static void (*proc_req[])(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) = {
|
|
proc_req_connected,
|
|
proc_req_pending,
|
|
proc_req_active
|
|
};
|
|
|
|
static gboolean csp_caps_initialized = FALSE;
|
|
struct csp_caps _caps;
|
|
|
|
static void mcap_cache_mcl(struct mcap_mcl *mcl);
|
|
|
|
static void default_mdl_connected_cb(struct mcap_mdl *mdl, gpointer data)
|
|
{
|
|
DBG("MCAP Unmanaged mdl connection");
|
|
}
|
|
|
|
static void default_mdl_closed_cb(struct mcap_mdl *mdl, gpointer data)
|
|
{
|
|
DBG("MCAP Unmanaged mdl closed");
|
|
}
|
|
|
|
static void default_mdl_deleted_cb(struct mcap_mdl *mdl, gpointer data)
|
|
{
|
|
DBG("MCAP Unmanaged mdl deleted");
|
|
}
|
|
|
|
static void default_mdl_aborted_cb(struct mcap_mdl *mdl, gpointer data)
|
|
{
|
|
DBG("MCAP Unmanaged mdl aborted");
|
|
}
|
|
|
|
static uint8_t default_mdl_conn_req_cb(struct mcap_mcl *mcl,
|
|
uint8_t mdepid, uint16_t mdlid,
|
|
uint8_t *conf, gpointer data)
|
|
{
|
|
DBG("MCAP mdl remote connection aborted");
|
|
/* Due to this callback isn't managed this request won't be supported */
|
|
return MCAP_REQUEST_NOT_SUPPORTED;
|
|
}
|
|
|
|
static uint8_t default_mdl_reconn_req_cb(struct mcap_mdl *mdl,
|
|
gpointer data)
|
|
{
|
|
DBG("MCAP mdl remote reconnection aborted");
|
|
/* Due to this callback isn't managed this request won't be supported */
|
|
return MCAP_REQUEST_NOT_SUPPORTED;
|
|
}
|
|
|
|
static void set_default_cb(struct mcap_mcl *mcl)
|
|
{
|
|
if (!mcl->cb)
|
|
mcl->cb = g_new0(struct mcap_mdl_cb, 1);
|
|
|
|
mcl->cb->mdl_connected = default_mdl_connected_cb;
|
|
mcl->cb->mdl_closed = default_mdl_closed_cb;
|
|
mcl->cb->mdl_deleted = default_mdl_deleted_cb;
|
|
mcl->cb->mdl_aborted = default_mdl_aborted_cb;
|
|
mcl->cb->mdl_conn_req = default_mdl_conn_req_cb;
|
|
mcl->cb->mdl_reconn_req = default_mdl_reconn_req_cb;
|
|
}
|
|
|
|
static char *error2str(uint8_t rc)
|
|
{
|
|
switch (rc) {
|
|
case MCAP_SUCCESS:
|
|
return "Success";
|
|
case MCAP_INVALID_OP_CODE:
|
|
return "Invalid Op Code";
|
|
case MCAP_INVALID_PARAM_VALUE:
|
|
return "Invalid Parameter Value";
|
|
case MCAP_INVALID_MDEP:
|
|
return "Invalid MDEP";
|
|
case MCAP_MDEP_BUSY:
|
|
return "MDEP Busy";
|
|
case MCAP_INVALID_MDL:
|
|
return "Invalid MDL";
|
|
case MCAP_MDL_BUSY:
|
|
return "MDL Busy";
|
|
case MCAP_INVALID_OPERATION:
|
|
return "Invalid Operation";
|
|
case MCAP_RESOURCE_UNAVAILABLE:
|
|
return "Resource Unavailable";
|
|
case MCAP_UNSPECIFIED_ERROR:
|
|
return "Unspecified Error";
|
|
case MCAP_REQUEST_NOT_SUPPORTED:
|
|
return "Request Not Supported";
|
|
case MCAP_CONFIGURATION_REJECTED:
|
|
return "Configuration Rejected";
|
|
default:
|
|
return "Unknown Response Code";
|
|
}
|
|
}
|
|
|
|
static gboolean mcap_send_std_opcode(struct mcap_mcl *mcl, void *cmd,
|
|
uint32_t size, GError **err)
|
|
{
|
|
if (mcl->state == MCL_IDLE) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
|
|
"MCL is not connected");
|
|
return FALSE;
|
|
}
|
|
|
|
if (mcl->req != MCL_AVAILABLE) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_RESOURCE_UNAVAILABLE,
|
|
"Pending request");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!(mcl->ctrl & MCAP_CTRL_STD_OP)) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_REQUEST_NOT_SUPPORTED,
|
|
"Remote does not support standard opcodes");
|
|
return FALSE;
|
|
}
|
|
|
|
if (mcl->state == MCL_PENDING) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_OPERATION,
|
|
"Not Std Op. Codes can be sent in PENDING State");
|
|
return FALSE;
|
|
}
|
|
|
|
if (mcap_send_data(g_io_channel_unix_get_fd(mcl->cc), cmd, size) < 0) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
|
|
"Command can't be sent, write error");
|
|
return FALSE;
|
|
}
|
|
|
|
mcl->lcmd = cmd;
|
|
mcl->req = MCL_WAITING_RSP;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void update_mcl_state(struct mcap_mcl *mcl)
|
|
{
|
|
GSList *l;
|
|
struct mcap_mdl *mdl;
|
|
|
|
if (mcl->state == MCL_PENDING)
|
|
return;
|
|
|
|
for (l = mcl->mdls; l; l = l->next) {
|
|
mdl = l->data;
|
|
|
|
if (mdl->state == MDL_CONNECTED) {
|
|
mcl->state = MCL_ACTIVE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
mcl->state = MCL_CONNECTED;
|
|
}
|
|
|
|
static void shutdown_mdl(struct mcap_mdl *mdl)
|
|
{
|
|
mdl->state = MDL_CLOSED;
|
|
|
|
if (mdl->wid) {
|
|
g_source_remove(mdl->wid);
|
|
mdl->wid = 0;
|
|
}
|
|
|
|
if (mdl->dc) {
|
|
g_io_channel_shutdown(mdl->dc, TRUE, NULL);
|
|
g_io_channel_unref(mdl->dc);
|
|
mdl->dc = NULL;
|
|
}
|
|
}
|
|
|
|
static void free_mdl(struct mcap_mdl *mdl)
|
|
{
|
|
if (!mdl)
|
|
return;
|
|
|
|
mcap_mcl_unref(mdl->mcl);
|
|
g_free(mdl);
|
|
}
|
|
|
|
static int cmp_mdl_state(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct mcap_mdl *mdl = a;
|
|
const MDLState *st = b;
|
|
|
|
if (mdl->state == *st)
|
|
return 0;
|
|
else if (mdl->state < *st)
|
|
return -1;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
static void free_mcap_mdl_op(struct mcap_mdl_op_cb *op)
|
|
{
|
|
if (op->destroy)
|
|
op->destroy(op->user_data);
|
|
|
|
if (op->mdl)
|
|
mcap_mdl_unref(op->mdl);
|
|
|
|
g_free(op);
|
|
}
|
|
|
|
static void free_mcl_priv_data(struct mcap_mcl *mcl)
|
|
{
|
|
free_mcap_mdl_op(mcl->priv_data);
|
|
mcl->priv_data = NULL;
|
|
}
|
|
|
|
static void mcap_notify_error(struct mcap_mcl *mcl, GError *err)
|
|
{
|
|
struct mcap_mdl_op_cb *con = mcl->priv_data;
|
|
struct mcap_mdl *mdl;
|
|
MDLState st;
|
|
GSList *l;
|
|
|
|
if (!con || !mcl->lcmd)
|
|
return;
|
|
|
|
switch (mcl->lcmd[0]) {
|
|
case MCAP_MD_CREATE_MDL_REQ:
|
|
st = MDL_WAITING;
|
|
l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
|
|
mdl = l->data;
|
|
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
|
|
mcap_mdl_unref(mdl);
|
|
update_mcl_state(mcl);
|
|
con->cb.op_conf(NULL, 0, err, con->user_data);
|
|
break;
|
|
case MCAP_MD_ABORT_MDL_REQ:
|
|
st = MDL_WAITING;
|
|
l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
|
|
shutdown_mdl(l->data);
|
|
update_mcl_state(mcl);
|
|
con->cb.notify(err, con->user_data);
|
|
break;
|
|
case MCAP_MD_DELETE_MDL_REQ:
|
|
for (l = mcl->mdls; l; l = l->next) {
|
|
mdl = l->data;
|
|
if (mdl->state == MDL_DELETING)
|
|
mdl->state = (mdl->dc) ? MDL_CONNECTED :
|
|
MDL_CLOSED;
|
|
}
|
|
update_mcl_state(mcl);
|
|
con->cb.notify(err, con->user_data);
|
|
break;
|
|
case MCAP_MD_RECONNECT_MDL_REQ:
|
|
st = MDL_WAITING;
|
|
l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
|
|
shutdown_mdl(l->data);
|
|
update_mcl_state(mcl);
|
|
con->cb.op(NULL, err, con->user_data);
|
|
break;
|
|
}
|
|
|
|
free_mcl_priv_data(mcl);
|
|
g_free(mcl->lcmd);
|
|
mcl->lcmd = NULL;
|
|
}
|
|
|
|
int mcap_send_data(int sock, const void *buf, uint32_t size)
|
|
{
|
|
const uint8_t *buf_b = buf;
|
|
uint32_t sent = 0;
|
|
|
|
while (sent < size) {
|
|
int n = write(sock, buf_b + sent, size - sent);
|
|
if (n < 0)
|
|
return -1;
|
|
sent += n;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mcap_send_cmd(struct mcap_mcl *mcl, uint8_t oc, uint8_t rc,
|
|
uint16_t mdl, uint8_t *data, size_t len)
|
|
{
|
|
mcap_rsp *cmd;
|
|
int sock, sent;
|
|
|
|
if (mcl->cc == NULL)
|
|
return -1;
|
|
|
|
sock = g_io_channel_unix_get_fd(mcl->cc);
|
|
|
|
cmd = g_malloc(sizeof(mcap_rsp) + len);
|
|
cmd->op = oc;
|
|
cmd->rc = rc;
|
|
cmd->mdl = htons(mdl);
|
|
|
|
if (data && len > 0)
|
|
memcpy(cmd->data, data, len);
|
|
|
|
sent = mcap_send_data(sock, cmd, sizeof(mcap_rsp) + len);
|
|
g_free(cmd);
|
|
|
|
return sent;
|
|
}
|
|
|
|
static struct mcap_mdl *get_mdl(struct mcap_mcl *mcl, uint16_t mdlid)
|
|
{
|
|
GSList *l;
|
|
struct mcap_mdl *mdl;
|
|
|
|
for (l = mcl->mdls; l; l = l->next) {
|
|
mdl = l->data;
|
|
if (mdlid == mdl->mdlid)
|
|
return mdl;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static uint16_t generate_mdlid(struct mcap_mcl *mcl)
|
|
{
|
|
uint16_t mdlid = mcl->next_mdl;
|
|
struct mcap_mdl *mdl;
|
|
|
|
do {
|
|
mdl = get_mdl(mcl, mdlid);
|
|
if (!mdl) {
|
|
mcl->next_mdl = (mdlid % MCAP_MDLID_FINAL) + 1;
|
|
return mdlid;
|
|
} else
|
|
mdlid = (mdlid % MCAP_MDLID_FINAL) + 1;
|
|
} while (mdlid != mcl->next_mdl);
|
|
|
|
/* No more mdlids availables */
|
|
return 0;
|
|
}
|
|
|
|
static mcap_md_req *create_req(uint8_t op, uint16_t mdl_id)
|
|
{
|
|
mcap_md_req *req_cmd;
|
|
|
|
req_cmd = g_new0(mcap_md_req, 1);
|
|
|
|
req_cmd->op = op;
|
|
req_cmd->mdl = htons(mdl_id);
|
|
|
|
return req_cmd;
|
|
}
|
|
|
|
static mcap_md_create_mdl_req *create_mdl_req(uint16_t mdl_id, uint8_t mdep,
|
|
uint8_t conf)
|
|
{
|
|
mcap_md_create_mdl_req *req_mdl;
|
|
|
|
req_mdl = g_new0(mcap_md_create_mdl_req, 1);
|
|
|
|
req_mdl->op = MCAP_MD_CREATE_MDL_REQ;
|
|
req_mdl->mdl = htons(mdl_id);
|
|
req_mdl->mdep = mdep;
|
|
req_mdl->conf = conf;
|
|
|
|
return req_mdl;
|
|
}
|
|
|
|
static int compare_mdl(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct mcap_mdl *mdla = a;
|
|
const struct mcap_mdl *mdlb = b;
|
|
|
|
if (mdla->mdlid == mdlb->mdlid)
|
|
return 0;
|
|
else if (mdla->mdlid < mdlb->mdlid)
|
|
return -1;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
static gboolean wait_response_timer(gpointer data)
|
|
{
|
|
struct mcap_mcl *mcl = data;
|
|
|
|
GError *gerr = NULL;
|
|
|
|
RELEASE_TIMER(mcl);
|
|
|
|
g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_FAILED,
|
|
"Timeout waiting response");
|
|
|
|
mcap_notify_error(mcl, gerr);
|
|
|
|
g_error_free(gerr);
|
|
mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data);
|
|
mcap_cache_mcl(mcl);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean mcap_create_mdl(struct mcap_mcl *mcl,
|
|
uint8_t mdepid,
|
|
uint8_t conf,
|
|
mcap_mdl_operation_conf_cb connect_cb,
|
|
gpointer user_data,
|
|
GDestroyNotify destroy,
|
|
GError **err)
|
|
{
|
|
struct mcap_mdl *mdl;
|
|
struct mcap_mdl_op_cb *con;
|
|
mcap_md_create_mdl_req *cmd;
|
|
uint16_t id;
|
|
|
|
id = generate_mdlid(mcl);
|
|
if (!id) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
|
|
"Not more mdlids available");
|
|
return FALSE;
|
|
}
|
|
|
|
mdl = g_new0(struct mcap_mdl, 1);
|
|
mdl->mcl = mcap_mcl_ref(mcl);
|
|
mdl->mdlid = id;
|
|
mdl->mdep_id = mdepid;
|
|
mdl->state = MDL_WAITING;
|
|
|
|
con = g_new0(struct mcap_mdl_op_cb, 1);
|
|
con->mdl = mcap_mdl_ref(mdl);
|
|
con->cb.op_conf = connect_cb;
|
|
con->destroy = destroy;
|
|
con->user_data = user_data;
|
|
|
|
cmd = create_mdl_req(id, mdepid, conf);
|
|
if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_create_mdl_req),
|
|
err)) {
|
|
mcap_mdl_unref(con->mdl);
|
|
g_free(con);
|
|
g_free(cmd);
|
|
return FALSE;
|
|
}
|
|
|
|
mcl->state = MCL_ACTIVE;
|
|
mcl->priv_data = con;
|
|
|
|
mcl->mdls = g_slist_insert_sorted(mcl->mdls, mcap_mdl_ref(mdl),
|
|
compare_mdl);
|
|
mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
|
|
mcl);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean mcap_reconnect_mdl(struct mcap_mdl *mdl,
|
|
mcap_mdl_operation_cb reconnect_cb,
|
|
gpointer user_data,
|
|
GDestroyNotify destroy,
|
|
GError **err)
|
|
{
|
|
struct mcap_mdl_op_cb *con;
|
|
struct mcap_mcl *mcl = mdl->mcl;
|
|
mcap_md_req *cmd;
|
|
|
|
if (mdl->state != MDL_CLOSED) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
|
|
"MDL is not closed");
|
|
return FALSE;
|
|
}
|
|
|
|
cmd = create_req(MCAP_MD_RECONNECT_MDL_REQ, mdl->mdlid);
|
|
if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
|
|
g_free(cmd);
|
|
return FALSE;
|
|
}
|
|
|
|
mdl->state = MDL_WAITING;
|
|
|
|
con = g_new0(struct mcap_mdl_op_cb, 1);
|
|
con->mdl = mcap_mdl_ref(mdl);
|
|
con->cb.op = reconnect_cb;
|
|
con->destroy = destroy;
|
|
con->user_data = user_data;
|
|
|
|
mcl->state = MCL_ACTIVE;
|
|
mcl->priv_data = con;
|
|
|
|
mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
|
|
mcl);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean send_delete_req(struct mcap_mcl *mcl,
|
|
struct mcap_mdl_op_cb *con,
|
|
uint16_t mdlid,
|
|
GError **err)
|
|
{
|
|
mcap_md_req *cmd;
|
|
|
|
cmd = create_req(MCAP_MD_DELETE_MDL_REQ, mdlid);
|
|
if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
|
|
g_free(cmd);
|
|
return FALSE;
|
|
}
|
|
|
|
mcl->priv_data = con;
|
|
|
|
mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
|
|
mcl);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean mcap_delete_all_mdls(struct mcap_mcl *mcl,
|
|
mcap_mdl_notify_cb delete_cb,
|
|
gpointer user_data,
|
|
GDestroyNotify destroy,
|
|
GError **err)
|
|
{
|
|
GSList *l;
|
|
struct mcap_mdl *mdl;
|
|
struct mcap_mdl_op_cb *con;
|
|
|
|
DBG("MCL in state: %d", mcl->state);
|
|
if (!mcl->mdls) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
|
|
"There are not MDLs created");
|
|
return FALSE;
|
|
}
|
|
|
|
for (l = mcl->mdls; l; l = l->next) {
|
|
mdl = l->data;
|
|
if (mdl->state != MDL_WAITING)
|
|
mdl->state = MDL_DELETING;
|
|
}
|
|
|
|
con = g_new0(struct mcap_mdl_op_cb, 1);
|
|
con->mdl = NULL;
|
|
con->cb.notify = delete_cb;
|
|
con->destroy = destroy;
|
|
con->user_data = user_data;
|
|
|
|
|
|
if (!send_delete_req(mcl, con, MCAP_ALL_MDLIDS, err)) {
|
|
g_free(con);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean mcap_delete_mdl(struct mcap_mdl *mdl, mcap_mdl_notify_cb delete_cb,
|
|
gpointer user_data,
|
|
GDestroyNotify destroy,
|
|
GError **err)
|
|
{
|
|
struct mcap_mcl *mcl= mdl->mcl;
|
|
struct mcap_mdl_op_cb *con;
|
|
GSList *l;
|
|
|
|
l = g_slist_find(mcl->mdls, mdl);
|
|
|
|
if (!l) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_MDL,
|
|
"%s" , error2str(MCAP_INVALID_MDEP));
|
|
return FALSE;
|
|
}
|
|
|
|
if (mdl->state == MDL_WAITING) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
|
|
"Mdl is not created");
|
|
return FALSE;
|
|
}
|
|
|
|
mdl->state = MDL_DELETING;
|
|
|
|
con = g_new0(struct mcap_mdl_op_cb, 1);
|
|
con->mdl = mcap_mdl_ref(mdl);
|
|
con->cb.notify = delete_cb;
|
|
con->destroy = destroy;
|
|
con->user_data = user_data;
|
|
|
|
if (!send_delete_req(mcl, con, mdl->mdlid, err)) {
|
|
mcap_mdl_unref(con->mdl);
|
|
g_free(con);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean mcap_mdl_abort(struct mcap_mdl *mdl, mcap_mdl_notify_cb abort_cb,
|
|
gpointer user_data,
|
|
GDestroyNotify destroy,
|
|
GError **err)
|
|
{
|
|
struct mcap_mdl_op_cb *con;
|
|
struct mcap_mcl *mcl = mdl->mcl;
|
|
mcap_md_req *cmd;
|
|
|
|
if (mdl->state != MDL_WAITING) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
|
|
"Mdl in invalid state");
|
|
return FALSE;
|
|
}
|
|
|
|
cmd = create_req(MCAP_MD_ABORT_MDL_REQ, mdl->mdlid);
|
|
if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
|
|
g_free(cmd);
|
|
return FALSE;
|
|
}
|
|
|
|
con = g_new0(struct mcap_mdl_op_cb, 1);
|
|
con->mdl = mcap_mdl_ref(mdl);
|
|
con->cb.notify = abort_cb;
|
|
con->destroy = destroy;
|
|
con->user_data = user_data;
|
|
|
|
mcl->priv_data = con;
|
|
mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
|
|
mcl);
|
|
return TRUE;
|
|
}
|
|
|
|
static struct mcap_mcl *find_mcl(GSList *list, const bdaddr_t *addr)
|
|
{
|
|
struct mcap_mcl *mcl;
|
|
|
|
for (; list; list = list->next) {
|
|
mcl = list->data;
|
|
|
|
if (!bacmp(&mcl->addr, addr))
|
|
return mcl;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int mcap_mdl_get_fd(struct mcap_mdl *mdl)
|
|
{
|
|
if (!mdl || mdl->state != MDL_CONNECTED)
|
|
return -ENOTCONN;
|
|
|
|
return g_io_channel_unix_get_fd(mdl->dc);
|
|
}
|
|
|
|
uint16_t mcap_mdl_get_mdlid(struct mcap_mdl *mdl)
|
|
{
|
|
if (!mdl)
|
|
return MCAP_MDLID_RESERVED;
|
|
|
|
return mdl->mdlid;
|
|
}
|
|
|
|
static void close_mcl(struct mcap_mcl *mcl, gboolean cache_requested)
|
|
{
|
|
gboolean save = ((!(mcl->ctrl & MCAP_CTRL_FREE)) && cache_requested);
|
|
|
|
RELEASE_TIMER(mcl);
|
|
|
|
if (mcl->cc) {
|
|
g_io_channel_shutdown(mcl->cc, TRUE, NULL);
|
|
g_io_channel_unref(mcl->cc);
|
|
mcl->cc = NULL;
|
|
}
|
|
|
|
if (mcl->wid) {
|
|
g_source_remove(mcl->wid);
|
|
mcl->wid = 0;
|
|
}
|
|
|
|
if (mcl->lcmd) {
|
|
g_free(mcl->lcmd);
|
|
mcl->lcmd = NULL;
|
|
}
|
|
|
|
if (mcl->priv_data)
|
|
free_mcl_priv_data(mcl);
|
|
|
|
g_slist_foreach(mcl->mdls, (GFunc) shutdown_mdl, NULL);
|
|
|
|
mcap_sync_stop(mcl);
|
|
|
|
mcl->state = MCL_IDLE;
|
|
|
|
if (save)
|
|
return;
|
|
|
|
g_slist_foreach(mcl->mdls, (GFunc) mcap_mdl_unref, NULL);
|
|
g_slist_free(mcl->mdls);
|
|
mcl->mdls = NULL;
|
|
}
|
|
|
|
static void mcap_mcl_shutdown(struct mcap_mcl *mcl)
|
|
{
|
|
close_mcl(mcl, TRUE);
|
|
}
|
|
|
|
static void mcap_mcl_release(struct mcap_mcl *mcl)
|
|
{
|
|
close_mcl(mcl, FALSE);
|
|
}
|
|
|
|
static void mcap_cache_mcl(struct mcap_mcl *mcl)
|
|
{
|
|
GSList *l;
|
|
struct mcap_mcl *last;
|
|
int len;
|
|
|
|
if (mcl->ctrl & MCAP_CTRL_CACHED)
|
|
return;
|
|
|
|
mcl->mi->mcls = g_slist_remove(mcl->mi->mcls, mcl);
|
|
|
|
if (mcl->ctrl & MCAP_CTRL_NOCACHE) {
|
|
mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl);
|
|
mcap_mcl_release(mcl);
|
|
mcap_mcl_unref(mcl);
|
|
return;
|
|
}
|
|
|
|
DBG("Caching MCL");
|
|
|
|
len = g_slist_length(mcl->mi->cached);
|
|
if (len == MAX_CACHED) {
|
|
/* Remove the latest cached mcl */
|
|
l = g_slist_last(mcl->mi->cached);
|
|
last = l->data;
|
|
mcl->mi->cached = g_slist_remove(mcl->mi->cached, last);
|
|
last->ctrl &= ~MCAP_CTRL_CACHED;
|
|
if (last->ctrl & MCAP_CTRL_CONN) {
|
|
/*
|
|
* We have to release this MCL if connection is not
|
|
* successful
|
|
*/
|
|
last->ctrl |= MCAP_CTRL_FREE;
|
|
} else {
|
|
mcap_mcl_release(last);
|
|
last->mi->mcl_uncached_cb(last, last->mi->user_data);
|
|
}
|
|
mcap_mcl_unref(last);
|
|
}
|
|
|
|
mcl->mi->cached = g_slist_prepend(mcl->mi->cached, mcl);
|
|
mcl->ctrl |= MCAP_CTRL_CACHED;
|
|
mcap_mcl_shutdown(mcl);
|
|
}
|
|
|
|
static void mcap_uncache_mcl(struct mcap_mcl *mcl)
|
|
{
|
|
if (!(mcl->ctrl & MCAP_CTRL_CACHED))
|
|
return;
|
|
|
|
DBG("Got MCL from cache");
|
|
|
|
mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl);
|
|
mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls, mcl);
|
|
mcl->ctrl &= ~MCAP_CTRL_CACHED;
|
|
mcl->ctrl &= ~MCAP_CTRL_FREE;
|
|
}
|
|
|
|
void mcap_close_mcl(struct mcap_mcl *mcl, gboolean cache)
|
|
{
|
|
if (!mcl)
|
|
return;
|
|
|
|
if (mcl->ctrl & MCAP_CTRL_FREE) {
|
|
mcap_mcl_release(mcl);
|
|
return;
|
|
}
|
|
|
|
if (!cache)
|
|
mcl->ctrl |= MCAP_CTRL_NOCACHE;
|
|
|
|
if (mcl->cc) {
|
|
g_io_channel_shutdown(mcl->cc, TRUE, NULL);
|
|
g_io_channel_unref(mcl->cc);
|
|
mcl->cc = NULL;
|
|
mcl->state = MCL_IDLE;
|
|
} else if ((mcl->ctrl & MCAP_CTRL_CACHED) &&
|
|
(mcl->ctrl & MCAP_CTRL_NOCACHE)) {
|
|
mcl->ctrl &= ~MCAP_CTRL_CACHED;
|
|
mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl);
|
|
mcap_mcl_release(mcl);
|
|
mcap_mcl_unref(mcl);
|
|
}
|
|
}
|
|
|
|
struct mcap_mcl *mcap_mcl_ref(struct mcap_mcl *mcl)
|
|
{
|
|
mcl->ref++;
|
|
|
|
DBG("mcap_mcl_ref(%p): ref=%d", mcl, mcl->ref);
|
|
|
|
return mcl;
|
|
}
|
|
|
|
void mcap_mcl_unref(struct mcap_mcl *mcl)
|
|
{
|
|
mcl->ref--;
|
|
|
|
DBG("mcap_mcl_unref(%p): ref=%d", mcl, mcl->ref);
|
|
|
|
if (mcl->ref > 0)
|
|
return;
|
|
|
|
mcap_mcl_release(mcl);
|
|
mcap_instance_unref(mcl->mi);
|
|
g_free(mcl->cb);
|
|
g_free(mcl);
|
|
}
|
|
|
|
static gboolean parse_set_opts(struct mcap_mdl_cb *mdl_cb, GError **err,
|
|
McapMclCb cb1, va_list args)
|
|
{
|
|
McapMclCb cb = cb1;
|
|
struct mcap_mdl_cb *c;
|
|
|
|
c = g_new0(struct mcap_mdl_cb, 1);
|
|
|
|
while (cb != MCAP_MDL_CB_INVALID) {
|
|
switch (cb) {
|
|
case MCAP_MDL_CB_CONNECTED:
|
|
c->mdl_connected = va_arg(args, mcap_mdl_event_cb);
|
|
break;
|
|
case MCAP_MDL_CB_CLOSED:
|
|
c->mdl_closed = va_arg(args, mcap_mdl_event_cb);
|
|
break;
|
|
case MCAP_MDL_CB_DELETED:
|
|
c->mdl_deleted = va_arg(args, mcap_mdl_event_cb);
|
|
break;
|
|
case MCAP_MDL_CB_ABORTED:
|
|
c->mdl_aborted = va_arg(args, mcap_mdl_event_cb);
|
|
break;
|
|
case MCAP_MDL_CB_REMOTE_CONN_REQ:
|
|
c->mdl_conn_req = va_arg(args,
|
|
mcap_remote_mdl_conn_req_cb);
|
|
break;
|
|
case MCAP_MDL_CB_REMOTE_RECONN_REQ:
|
|
c->mdl_reconn_req = va_arg(args,
|
|
mcap_remote_mdl_reconn_req_cb);
|
|
break;
|
|
default:
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
|
|
"Unknown option %d", cb);
|
|
g_free(c);
|
|
return FALSE;
|
|
}
|
|
cb = va_arg(args, int);
|
|
}
|
|
|
|
/* Set new callbacks */
|
|
if (c->mdl_connected)
|
|
mdl_cb->mdl_connected = c->mdl_connected;
|
|
if (c->mdl_closed)
|
|
mdl_cb->mdl_closed = c->mdl_closed;
|
|
if (c->mdl_deleted)
|
|
mdl_cb->mdl_deleted = c->mdl_deleted;
|
|
if (c->mdl_aborted)
|
|
mdl_cb->mdl_aborted = c->mdl_aborted;
|
|
if (c->mdl_conn_req)
|
|
mdl_cb->mdl_conn_req = c->mdl_conn_req;
|
|
if (c->mdl_reconn_req)
|
|
mdl_cb->mdl_reconn_req = c->mdl_reconn_req;
|
|
|
|
g_free(c);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean mcap_mcl_set_cb(struct mcap_mcl *mcl, gpointer user_data,
|
|
GError **gerr, McapMclCb cb1, ...)
|
|
{
|
|
va_list args;
|
|
gboolean ret;
|
|
|
|
va_start(args, cb1);
|
|
ret = parse_set_opts(mcl->cb, gerr, cb1, args);
|
|
va_end(args);
|
|
|
|
if (!ret)
|
|
return FALSE;
|
|
|
|
mcl->cb->user_data = user_data;
|
|
return TRUE;
|
|
}
|
|
|
|
void mcap_mcl_get_addr(struct mcap_mcl *mcl, bdaddr_t *addr)
|
|
{
|
|
bacpy(addr, &mcl->addr);
|
|
}
|
|
|
|
static void mcap_del_mdl(gpointer elem, gpointer user_data)
|
|
{
|
|
struct mcap_mdl *mdl = elem;
|
|
gboolean notify = *(gboolean *) user_data;
|
|
|
|
shutdown_mdl(mdl);
|
|
if (notify)
|
|
mdl->mcl->cb->mdl_deleted(mdl, mdl->mcl->cb->user_data);
|
|
|
|
mcap_mdl_unref(mdl);
|
|
}
|
|
|
|
static gboolean check_cmd_req_length(struct mcap_mcl *mcl, void *cmd,
|
|
uint32_t rlen, uint32_t explen, uint8_t rspcod)
|
|
{
|
|
mcap_md_req *req;
|
|
uint16_t mdl_id;
|
|
|
|
if (rlen != explen) {
|
|
if (rlen >= sizeof(mcap_md_req)) {
|
|
req = cmd;
|
|
mdl_id = ntohs(req->mdl);
|
|
} else {
|
|
/* We can't get mdlid */
|
|
mdl_id = MCAP_MDLID_RESERVED;
|
|
}
|
|
mcap_send_cmd(mcl, rspcod, MCAP_INVALID_PARAM_VALUE, mdl_id,
|
|
NULL, 0);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void process_md_create_mdl_req(struct mcap_mcl *mcl, void *cmd,
|
|
uint32_t len)
|
|
{
|
|
mcap_md_create_mdl_req *req;
|
|
struct mcap_mdl *mdl;
|
|
uint16_t mdl_id;
|
|
uint8_t mdep_id;
|
|
uint8_t cfga, conf;
|
|
uint8_t rsp;
|
|
|
|
if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_create_mdl_req),
|
|
MCAP_MD_CREATE_MDL_RSP))
|
|
return;
|
|
|
|
req = cmd;
|
|
mdl_id = ntohs(req->mdl);
|
|
if (mdl_id < MCAP_MDLID_INITIAL || mdl_id > MCAP_MDLID_FINAL) {
|
|
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_INVALID_MDL,
|
|
mdl_id, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
mdep_id = req->mdep;
|
|
if (mdep_id > MCAP_MDEPID_FINAL) {
|
|
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_INVALID_MDEP,
|
|
mdl_id, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
mdl = get_mdl(mcl, mdl_id);
|
|
if (mdl && (mdl->state == MDL_WAITING || mdl->state == MDL_DELETING )) {
|
|
/*
|
|
* Creation request arrives for a MDL that is being managed
|
|
* at current moment
|
|
*/
|
|
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_MDL_BUSY,
|
|
mdl_id, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
cfga = conf = req->conf;
|
|
/* Callback to upper layer */
|
|
rsp = mcl->cb->mdl_conn_req(mcl, mdep_id, mdl_id, &conf,
|
|
mcl->cb->user_data);
|
|
if (mcl->state == MCL_IDLE) {
|
|
/* MCL has been closed int the callback */
|
|
return;
|
|
}
|
|
|
|
if (cfga != 0 && cfga != conf) {
|
|
/*
|
|
* Remote device set default configuration but upper profile
|
|
* has changed it. Protocol Error: force closing the MCL by
|
|
* remote device using UNSPECIFIED_ERROR response
|
|
*/
|
|
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP,
|
|
MCAP_UNSPECIFIED_ERROR, mdl_id, NULL, 0);
|
|
return;
|
|
}
|
|
if (rsp != MCAP_SUCCESS) {
|
|
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, rsp, mdl_id,
|
|
NULL, 0);
|
|
return;
|
|
}
|
|
|
|
if (!mdl) {
|
|
mdl = g_new0(struct mcap_mdl, 1);
|
|
mdl->mcl = mcap_mcl_ref(mcl);
|
|
mdl->mdlid = mdl_id;
|
|
mcl->mdls = g_slist_insert_sorted(mcl->mdls, mcap_mdl_ref(mdl),
|
|
compare_mdl);
|
|
} else if (mdl->state == MDL_CONNECTED) {
|
|
/*
|
|
* MCAP specification says that we should close the MCL if
|
|
* it is open when we receive a MD_CREATE_MDL_REQ
|
|
*/
|
|
shutdown_mdl(mdl);
|
|
}
|
|
|
|
mdl->mdep_id = mdep_id;
|
|
mdl->state = MDL_WAITING;
|
|
|
|
mcl->state = MCL_PENDING;
|
|
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_SUCCESS, mdl_id,
|
|
&conf, 1);
|
|
}
|
|
|
|
static void process_md_reconnect_mdl_req(struct mcap_mcl *mcl, void *cmd,
|
|
uint32_t len)
|
|
{
|
|
mcap_md_req *req;
|
|
struct mcap_mdl *mdl;
|
|
uint16_t mdl_id;
|
|
uint8_t rsp;
|
|
|
|
if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
|
|
MCAP_MD_RECONNECT_MDL_RSP))
|
|
return;
|
|
|
|
req = cmd;
|
|
mdl_id = ntohs(req->mdl);
|
|
|
|
mdl = get_mdl(mcl, mdl_id);
|
|
if (!mdl) {
|
|
mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_INVALID_MDL,
|
|
mdl_id, NULL, 0);
|
|
return;
|
|
} else if (mdl->state == MDL_WAITING || mdl->state == MDL_DELETING ) {
|
|
/*
|
|
* Creation request arrives for a MDL that is being managed
|
|
* at current moment
|
|
*/
|
|
mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_MDL_BUSY,
|
|
mdl_id, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
/* Callback to upper layer */
|
|
rsp = mcl->cb->mdl_reconn_req(mdl, mcl->cb->user_data);
|
|
if (mcl->state == MCL_IDLE)
|
|
return;
|
|
|
|
if (rsp != MCAP_SUCCESS) {
|
|
mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, rsp, mdl_id,
|
|
NULL, 0);
|
|
return;
|
|
}
|
|
|
|
if (mdl->state == MDL_CONNECTED)
|
|
shutdown_mdl(mdl);
|
|
|
|
mdl->state = MDL_WAITING;
|
|
mcl->state = MCL_PENDING;
|
|
mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_SUCCESS, mdl_id,
|
|
NULL, 0);
|
|
}
|
|
|
|
static void process_md_abort_mdl_req(struct mcap_mcl *mcl, void *cmd,
|
|
uint32_t len)
|
|
{
|
|
mcap_md_req *req;
|
|
GSList *l;
|
|
struct mcap_mdl *mdl, *abrt;
|
|
uint16_t mdl_id;
|
|
|
|
if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
|
|
MCAP_MD_ABORT_MDL_RSP))
|
|
return;
|
|
|
|
req = cmd;
|
|
mdl_id = ntohs(req->mdl);
|
|
mcl->state = MCL_CONNECTED;
|
|
abrt = NULL;
|
|
for (l = mcl->mdls; l; l = l->next) {
|
|
mdl = l->data;
|
|
if (mdl_id == mdl->mdlid && mdl->state == MDL_WAITING) {
|
|
abrt = mdl;
|
|
if (mcl->state != MCL_CONNECTED)
|
|
break;
|
|
continue;
|
|
}
|
|
if (mdl->state == MDL_CONNECTED && mcl->state != MCL_ACTIVE)
|
|
mcl->state = MCL_ACTIVE;
|
|
|
|
if (abrt && mcl->state == MCL_ACTIVE)
|
|
break;
|
|
}
|
|
|
|
if (!abrt) {
|
|
mcap_send_cmd(mcl, MCAP_MD_ABORT_MDL_RSP, MCAP_INVALID_MDL,
|
|
mdl_id, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
mcl->cb->mdl_aborted(abrt, mcl->cb->user_data);
|
|
abrt->state = MDL_CLOSED;
|
|
mcap_send_cmd(mcl, MCAP_MD_ABORT_MDL_RSP, MCAP_SUCCESS, mdl_id,
|
|
NULL, 0);
|
|
}
|
|
|
|
static void process_md_delete_mdl_req(struct mcap_mcl *mcl, void *cmd,
|
|
uint32_t len)
|
|
{
|
|
mcap_md_req *req;
|
|
struct mcap_mdl *mdl, *aux;
|
|
uint16_t mdlid;
|
|
gboolean notify;
|
|
GSList *l;
|
|
|
|
if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
|
|
MCAP_MD_DELETE_MDL_RSP))
|
|
return;
|
|
|
|
req = cmd;
|
|
mdlid = ntohs(req->mdl);
|
|
if (mdlid == MCAP_ALL_MDLIDS) {
|
|
notify = FALSE;
|
|
g_slist_foreach(mcl->mdls, mcap_del_mdl, ¬ify);
|
|
g_slist_free(mcl->mdls);
|
|
mcl->mdls = NULL;
|
|
mcl->state = MCL_CONNECTED;
|
|
/* NULL mdl means ALL_MDLS */
|
|
mcl->cb->mdl_deleted(NULL, mcl->cb->user_data);
|
|
goto resp;
|
|
}
|
|
|
|
if (mdlid < MCAP_MDLID_INITIAL || mdlid > MCAP_MDLID_FINAL) {
|
|
mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_INVALID_MDL,
|
|
mdlid, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
for (l = mcl->mdls, mdl = NULL; l; l = l->next) {
|
|
aux = l->data;
|
|
if (aux->mdlid == mdlid) {
|
|
mdl = aux;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!mdl || mdl->state == MDL_WAITING) {
|
|
mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_INVALID_MDL,
|
|
mdlid, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
|
|
update_mcl_state(mcl);
|
|
notify = TRUE;
|
|
mcap_del_mdl(mdl, ¬ify);
|
|
|
|
resp:
|
|
mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_SUCCESS, mdlid,
|
|
NULL, 0);
|
|
}
|
|
|
|
static void invalid_req_state(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
|
|
{
|
|
uint16_t mdlr;
|
|
|
|
error("Invalid cmd received (op code = %d) in state %d", cmd[0],
|
|
mcl->state);
|
|
/*
|
|
* Get previously mdlid sent to generate an appropriate
|
|
* response if it is possible
|
|
*/
|
|
mdlr = len < sizeof(mcap_md_req) ? MCAP_MDLID_RESERVED :
|
|
ntohs(((mcap_md_req *) cmd)->mdl);
|
|
mcap_send_cmd(mcl, cmd[0]+1, MCAP_INVALID_OPERATION, mdlr, NULL, 0);
|
|
}
|
|
|
|
/* Function used to process commands depending of MCL state */
|
|
static void proc_req_connected(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
|
|
{
|
|
switch (cmd[0]) {
|
|
case MCAP_MD_CREATE_MDL_REQ:
|
|
process_md_create_mdl_req(mcl, cmd, len);
|
|
break;
|
|
case MCAP_MD_RECONNECT_MDL_REQ:
|
|
process_md_reconnect_mdl_req(mcl, cmd, len);
|
|
break;
|
|
case MCAP_MD_DELETE_MDL_REQ:
|
|
process_md_delete_mdl_req(mcl, cmd, len);
|
|
break;
|
|
default:
|
|
invalid_req_state(mcl, cmd, len);
|
|
}
|
|
}
|
|
|
|
static void proc_req_pending(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
|
|
{
|
|
if (cmd[0] == MCAP_MD_ABORT_MDL_REQ)
|
|
process_md_abort_mdl_req(mcl, cmd, len);
|
|
else
|
|
invalid_req_state(mcl, cmd, len);
|
|
}
|
|
|
|
static void proc_req_active(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
|
|
{
|
|
switch (cmd[0]) {
|
|
case MCAP_MD_CREATE_MDL_REQ:
|
|
process_md_create_mdl_req(mcl, cmd, len);
|
|
break;
|
|
case MCAP_MD_RECONNECT_MDL_REQ:
|
|
process_md_reconnect_mdl_req(mcl, cmd, len);
|
|
break;
|
|
case MCAP_MD_DELETE_MDL_REQ:
|
|
process_md_delete_mdl_req(mcl, cmd, len);
|
|
break;
|
|
default:
|
|
invalid_req_state(mcl, cmd, len);
|
|
}
|
|
}
|
|
|
|
/* Function used to process replies */
|
|
static gboolean check_err_rsp(struct mcap_mcl *mcl, mcap_rsp *rsp,
|
|
uint32_t rlen, uint32_t len, GError **gerr)
|
|
{
|
|
mcap_md_req *cmdlast = (mcap_md_req *) mcl->lcmd;
|
|
int err = MCAP_ERROR_FAILED;
|
|
gboolean close = FALSE;
|
|
char *msg;
|
|
|
|
if (rsp->op == MCAP_ERROR_RSP) {
|
|
msg = "MCAP_ERROR_RSP received";
|
|
close = FALSE;
|
|
goto fail;
|
|
}
|
|
|
|
/* Check if the response matches with the last request */
|
|
if (rlen < sizeof(mcap_rsp) || (mcl->lcmd[0] + 1) != rsp->op) {
|
|
msg = "Protocol error";
|
|
close = FALSE;
|
|
goto fail;
|
|
}
|
|
|
|
if (rlen < len) {
|
|
msg = "Protocol error";
|
|
close = FALSE;
|
|
goto fail;
|
|
}
|
|
|
|
if (rsp->mdl != cmdlast->mdl) {
|
|
msg = "MDLID received doesn't match with MDLID sent";
|
|
close = TRUE;
|
|
goto fail;
|
|
}
|
|
|
|
if (rsp->rc == MCAP_REQUEST_NOT_SUPPORTED) {
|
|
msg = "Remote does not support opcodes";
|
|
mcl->ctrl &= ~MCAP_CTRL_STD_OP;
|
|
goto fail;
|
|
}
|
|
|
|
if (rsp->rc == MCAP_UNSPECIFIED_ERROR) {
|
|
msg = "Unspecified error";
|
|
close = TRUE;
|
|
goto fail;
|
|
}
|
|
|
|
if (rsp->rc != MCAP_SUCCESS) {
|
|
msg = error2str(rsp->rc);
|
|
err = rsp->rc;
|
|
goto fail;
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
fail:
|
|
g_set_error(gerr, MCAP_ERROR, err, "%s", msg);
|
|
return close;
|
|
}
|
|
|
|
static gboolean process_md_create_mdl_rsp(struct mcap_mcl *mcl,
|
|
mcap_rsp *rsp, uint32_t len)
|
|
{
|
|
mcap_md_create_mdl_req *cmdlast = (mcap_md_create_mdl_req *) mcl->lcmd;
|
|
struct mcap_mdl_op_cb *conn = mcl->priv_data;
|
|
mcap_mdl_operation_conf_cb connect_cb = conn->cb.op_conf;
|
|
gpointer user_data = conn->user_data;
|
|
struct mcap_mdl *mdl = conn->mdl;
|
|
uint8_t conf = cmdlast->conf;
|
|
gboolean close;
|
|
GError *gerr = NULL;
|
|
|
|
close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp) + 1, &gerr);
|
|
g_free(mcl->lcmd);
|
|
mcl->lcmd = NULL;
|
|
mcl->req = MCL_AVAILABLE;
|
|
|
|
if (gerr)
|
|
goto fail;
|
|
|
|
/* Check if preferences changed */
|
|
if (conf != 0x00 && rsp->data[0] != conf) {
|
|
g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_FAILED,
|
|
"Configuration changed");
|
|
close = TRUE;
|
|
goto fail;
|
|
}
|
|
|
|
connect_cb(mdl, rsp->data[0], gerr, user_data);
|
|
return close;
|
|
|
|
fail:
|
|
connect_cb(NULL, 0, gerr, user_data);
|
|
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
|
|
mcap_mdl_unref(mdl);
|
|
g_error_free(gerr);
|
|
update_mcl_state(mcl);
|
|
return close;
|
|
}
|
|
|
|
static gboolean process_md_reconnect_mdl_rsp(struct mcap_mcl *mcl,
|
|
mcap_rsp *rsp, uint32_t len)
|
|
{
|
|
struct mcap_mdl_op_cb *reconn = mcl->priv_data;
|
|
mcap_mdl_operation_cb reconn_cb = reconn->cb.op;
|
|
gpointer user_data = reconn->user_data;
|
|
struct mcap_mdl *mdl = reconn->mdl;
|
|
GError *gerr = NULL;
|
|
gboolean close;
|
|
|
|
close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
|
|
|
|
g_free(mcl->lcmd);
|
|
mcl->lcmd = NULL;
|
|
mcl->req = MCL_AVAILABLE;
|
|
|
|
reconn_cb(mdl, gerr, user_data);
|
|
if (!gerr)
|
|
return close;
|
|
|
|
g_error_free(gerr);
|
|
shutdown_mdl(mdl);
|
|
update_mcl_state(mcl);
|
|
|
|
if (rsp->rc != MCAP_INVALID_MDL)
|
|
return close;
|
|
|
|
/* Remove cached mdlid */
|
|
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
|
|
mcl->cb->mdl_deleted(mdl, mcl->cb->user_data);
|
|
mcap_mdl_unref(mdl);
|
|
|
|
return close;
|
|
}
|
|
|
|
static gboolean process_md_abort_mdl_rsp(struct mcap_mcl *mcl,
|
|
mcap_rsp *rsp, uint32_t len)
|
|
{
|
|
struct mcap_mdl_op_cb *abrt = mcl->priv_data;
|
|
mcap_mdl_notify_cb abrt_cb = abrt->cb.notify;
|
|
gpointer user_data = abrt->user_data;
|
|
struct mcap_mdl *mdl = abrt->mdl;
|
|
GError *gerr = NULL;
|
|
gboolean close;
|
|
|
|
close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
|
|
|
|
g_free(mcl->lcmd);
|
|
mcl->lcmd = NULL;
|
|
mcl->req = MCL_AVAILABLE;
|
|
|
|
abrt_cb(gerr, user_data);
|
|
shutdown_mdl(mdl);
|
|
|
|
if (len >= sizeof(mcap_rsp) && rsp->rc == MCAP_INVALID_MDL) {
|
|
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
|
|
mcl->cb->mdl_deleted(mdl, mcl->cb->user_data);
|
|
mcap_mdl_unref(mdl);
|
|
}
|
|
|
|
if (gerr)
|
|
g_error_free(gerr);
|
|
|
|
update_mcl_state(mcl);
|
|
|
|
return close;
|
|
}
|
|
|
|
static void restore_mdl(gpointer elem, gpointer data)
|
|
{
|
|
struct mcap_mdl *mdl = elem;
|
|
|
|
if (mdl->state == MDL_DELETING) {
|
|
if (mdl->dc)
|
|
mdl->state = MDL_CONNECTED;
|
|
else
|
|
mdl->state = MDL_CLOSED;
|
|
} else if (mdl->state == MDL_CLOSED)
|
|
mdl->mcl->cb->mdl_closed(mdl, mdl->mcl->cb->user_data);
|
|
}
|
|
|
|
static void check_mdl_del_err(struct mcap_mdl *mdl, mcap_rsp *rsp)
|
|
{
|
|
if (rsp->rc != MCAP_ERROR_INVALID_MDL) {
|
|
restore_mdl(mdl, NULL);
|
|
return;
|
|
}
|
|
|
|
/* MDL does not exist in remote side, we can delete it */
|
|
mdl->mcl->mdls = g_slist_remove(mdl->mcl->mdls, mdl);
|
|
mcap_mdl_unref(mdl);
|
|
}
|
|
|
|
static gboolean process_md_delete_mdl_rsp(struct mcap_mcl *mcl, mcap_rsp *rsp,
|
|
uint32_t len)
|
|
{
|
|
struct mcap_mdl_op_cb *del = mcl->priv_data;
|
|
struct mcap_mdl *mdl = del->mdl;
|
|
mcap_mdl_notify_cb deleted_cb = del->cb.notify;
|
|
gpointer user_data = del->user_data;
|
|
mcap_md_req *cmdlast = (mcap_md_req *) mcl->lcmd;
|
|
uint16_t mdlid = ntohs(cmdlast->mdl);
|
|
GError *gerr = NULL;
|
|
gboolean close;
|
|
gboolean notify = FALSE;
|
|
|
|
close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
|
|
|
|
g_free(mcl->lcmd);
|
|
mcl->lcmd = NULL;
|
|
mcl->req = MCL_AVAILABLE;
|
|
|
|
if (gerr) {
|
|
if (mdl)
|
|
check_mdl_del_err(mdl, rsp);
|
|
else
|
|
g_slist_foreach(mcl->mdls, restore_mdl, NULL);
|
|
deleted_cb(gerr, user_data);
|
|
g_error_free(gerr);
|
|
return close;
|
|
}
|
|
|
|
if (mdlid == MCAP_ALL_MDLIDS) {
|
|
g_slist_foreach(mcl->mdls, mcap_del_mdl, ¬ify);
|
|
g_slist_free(mcl->mdls);
|
|
mcl->mdls = NULL;
|
|
mcl->state = MCL_CONNECTED;
|
|
} else {
|
|
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
|
|
update_mcl_state(mcl);
|
|
mcap_del_mdl(mdl, ¬ify);
|
|
}
|
|
|
|
deleted_cb(gerr, user_data);
|
|
|
|
return close;
|
|
}
|
|
|
|
static void post_process_rsp(struct mcap_mcl *mcl, struct mcap_mdl_op_cb *op)
|
|
{
|
|
if (mcl->priv_data != op) {
|
|
/*
|
|
* Queued MCAP request in some callback.
|
|
* We should not delete the mcl private data
|
|
*/
|
|
free_mcap_mdl_op(op);
|
|
} else {
|
|
/*
|
|
* This is not a queued request. It's safe
|
|
* delete the mcl private data here.
|
|
*/
|
|
free_mcl_priv_data(mcl);
|
|
}
|
|
}
|
|
|
|
static void proc_response(struct mcap_mcl *mcl, void *buf, uint32_t len)
|
|
{
|
|
struct mcap_mdl_op_cb *op = mcl->priv_data;
|
|
mcap_rsp *rsp = buf;
|
|
gboolean close;
|
|
|
|
RELEASE_TIMER(mcl);
|
|
|
|
switch (mcl->lcmd[0] + 1) {
|
|
case MCAP_MD_CREATE_MDL_RSP:
|
|
close = process_md_create_mdl_rsp(mcl, rsp, len);
|
|
post_process_rsp(mcl, op);
|
|
break;
|
|
case MCAP_MD_RECONNECT_MDL_RSP:
|
|
close = process_md_reconnect_mdl_rsp(mcl, rsp, len);
|
|
post_process_rsp(mcl, op);
|
|
break;
|
|
case MCAP_MD_ABORT_MDL_RSP:
|
|
close = process_md_abort_mdl_rsp(mcl, rsp, len);
|
|
post_process_rsp(mcl, op);
|
|
break;
|
|
case MCAP_MD_DELETE_MDL_RSP:
|
|
close = process_md_delete_mdl_rsp(mcl, rsp, len);
|
|
post_process_rsp(mcl, op);
|
|
break;
|
|
default:
|
|
DBG("Unknown cmd response received (op code = %d)", rsp->op);
|
|
close = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (close) {
|
|
mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data);
|
|
mcap_cache_mcl(mcl);
|
|
}
|
|
}
|
|
|
|
static void proc_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
|
|
{
|
|
GError *gerr = NULL;
|
|
|
|
if (cmd[0] > MCAP_MD_SYNC_INFO_IND ||
|
|
(cmd[0] > MCAP_MD_DELETE_MDL_RSP &&
|
|
cmd[0] < MCAP_MD_SYNC_CAP_REQ)) {
|
|
error("Unknown cmd received (op code = %d)", cmd[0]);
|
|
mcap_send_cmd(mcl, MCAP_ERROR_RSP, MCAP_INVALID_OP_CODE,
|
|
MCAP_MDLID_RESERVED, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
if (cmd[0] >= MCAP_MD_SYNC_CAP_REQ &&
|
|
cmd[0] <= MCAP_MD_SYNC_INFO_IND) {
|
|
proc_sync_cmd(mcl, cmd, len);
|
|
return;
|
|
}
|
|
|
|
if (!(mcl->ctrl & MCAP_CTRL_STD_OP)) {
|
|
/* In case the remote device doesn't work correctly */
|
|
error("Remote device does not support opcodes, cmd ignored");
|
|
return;
|
|
}
|
|
|
|
if (mcl->req == MCL_WAITING_RSP) {
|
|
if (cmd[0] & 0x01) {
|
|
/* Request arrived when a response is expected */
|
|
if (mcl->role == MCL_INITIATOR)
|
|
/* ignore */
|
|
return;
|
|
/* Initiator will ignore our last request */
|
|
RELEASE_TIMER(mcl);
|
|
mcl->req = MCL_AVAILABLE;
|
|
g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_REQ_IGNORED,
|
|
"Initiator sent a request with more priority");
|
|
mcap_notify_error(mcl, gerr);
|
|
proc_req[mcl->state](mcl, cmd, len);
|
|
return;
|
|
}
|
|
proc_response(mcl, cmd, len);
|
|
} else if (cmd[0] & 0x01)
|
|
proc_req[mcl->state](mcl, cmd, len);
|
|
}
|
|
|
|
static gboolean mdl_event_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
|
|
{
|
|
|
|
struct mcap_mdl *mdl = data;
|
|
gboolean notify;
|
|
|
|
DBG("Close MDL %d", mdl->mdlid);
|
|
|
|
notify = (mdl->state == MDL_CONNECTED);
|
|
shutdown_mdl(mdl);
|
|
|
|
update_mcl_state(mdl->mcl);
|
|
|
|
if (notify) {
|
|
/*Callback to upper layer */
|
|
mdl->mcl->cb->mdl_closed(mdl, mdl->mcl->cb->user_data);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void mcap_connect_mdl_cb(GIOChannel *chan, GError *conn_err,
|
|
gpointer data)
|
|
{
|
|
struct mcap_mdl_op_cb *con = data;
|
|
struct mcap_mdl *mdl = con->mdl;
|
|
mcap_mdl_operation_cb cb = con->cb.op;
|
|
gpointer user_data = con->user_data;
|
|
|
|
DBG("mdl connect callback");
|
|
|
|
if (conn_err) {
|
|
DBG("ERROR: mdl connect callback");
|
|
mdl->state = MDL_CLOSED;
|
|
g_io_channel_unref(mdl->dc);
|
|
mdl->dc = NULL;
|
|
cb(mdl, conn_err, user_data);
|
|
return;
|
|
}
|
|
|
|
mdl->state = MDL_CONNECTED;
|
|
mdl->wid = g_io_add_watch_full(mdl->dc, G_PRIORITY_DEFAULT,
|
|
G_IO_ERR | G_IO_HUP | G_IO_NVAL,
|
|
(GIOFunc) mdl_event_cb,
|
|
mcap_mdl_ref(mdl),
|
|
(GDestroyNotify) mcap_mdl_unref);
|
|
|
|
cb(mdl, conn_err, user_data);
|
|
}
|
|
|
|
gboolean mcap_connect_mdl(struct mcap_mdl *mdl, uint8_t mode,
|
|
uint16_t dcpsm,
|
|
mcap_mdl_operation_cb connect_cb,
|
|
gpointer user_data,
|
|
GDestroyNotify destroy,
|
|
GError **err)
|
|
{
|
|
struct mcap_mdl_op_cb *con;
|
|
|
|
if (mdl->state != MDL_WAITING) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_MDL,
|
|
"%s", error2str(MCAP_INVALID_MDL));
|
|
return FALSE;
|
|
}
|
|
|
|
if ((mode != L2CAP_MODE_ERTM) && (mode != L2CAP_MODE_STREAMING)) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
|
|
"Invalid MDL configuration");
|
|
return FALSE;
|
|
}
|
|
|
|
con = g_new0(struct mcap_mdl_op_cb, 1);
|
|
con->mdl = mcap_mdl_ref(mdl);
|
|
con->cb.op = connect_cb;
|
|
con->destroy = destroy;
|
|
con->user_data = user_data;
|
|
|
|
mdl->dc = bt_io_connect(mcap_connect_mdl_cb, con,
|
|
(GDestroyNotify) free_mcap_mdl_op, err,
|
|
BT_IO_OPT_SOURCE_BDADDR, &mdl->mcl->mi->src,
|
|
BT_IO_OPT_DEST_BDADDR, &mdl->mcl->addr,
|
|
BT_IO_OPT_PSM, dcpsm,
|
|
BT_IO_OPT_MTU, MCAP_DC_MTU,
|
|
BT_IO_OPT_SEC_LEVEL, mdl->mcl->mi->sec,
|
|
BT_IO_OPT_MODE, mode,
|
|
BT_IO_OPT_INVALID);
|
|
if (!mdl->dc) {
|
|
DBG("MDL Connection error");
|
|
mdl->state = MDL_CLOSED;
|
|
mcap_mdl_unref(con->mdl);
|
|
g_free(con);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean mcl_control_cb(GIOChannel *chan, GIOCondition cond,
|
|
gpointer data)
|
|
{
|
|
GError *gerr = NULL;
|
|
struct mcap_mcl *mcl = data;
|
|
int sk, len;
|
|
uint8_t buf[MCAP_CC_MTU];
|
|
|
|
if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
|
|
goto fail;
|
|
|
|
sk = g_io_channel_unix_get_fd(chan);
|
|
len = read(sk, buf, sizeof(buf));
|
|
if (len < 0)
|
|
goto fail;
|
|
|
|
proc_cmd(mcl, buf, (uint32_t) len);
|
|
return TRUE;
|
|
|
|
fail:
|
|
if (mcl->state != MCL_IDLE) {
|
|
if (mcl->req == MCL_WAITING_RSP) {
|
|
/* notify error in pending callback */
|
|
g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_MCL_CLOSED,
|
|
"MCL closed");
|
|
mcap_notify_error(mcl, gerr);
|
|
g_error_free(gerr);
|
|
}
|
|
mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data);
|
|
}
|
|
mcap_cache_mcl(mcl);
|
|
return FALSE;
|
|
}
|
|
|
|
static void mcap_connect_mcl_cb(GIOChannel *chan, GError *conn_err,
|
|
gpointer user_data)
|
|
{
|
|
char dstaddr[18];
|
|
struct connect_mcl *con = user_data;
|
|
struct mcap_mcl *aux, *mcl = con->mcl;
|
|
mcap_mcl_connect_cb connect_cb = con->connect_cb;
|
|
gpointer data = con->user_data;
|
|
GError *gerr = NULL;
|
|
|
|
mcl->ctrl &= ~MCAP_CTRL_CONN;
|
|
|
|
if (conn_err) {
|
|
if (mcl->ctrl & MCAP_CTRL_FREE) {
|
|
mcap_mcl_release(mcl);
|
|
mcl->mi->mcl_uncached_cb(mcl, mcl->mi->user_data);
|
|
}
|
|
connect_cb(NULL, conn_err, data);
|
|
return;
|
|
}
|
|
|
|
ba2str(&mcl->addr, dstaddr);
|
|
|
|
aux = find_mcl(mcl->mi->mcls, &mcl->addr);
|
|
if (aux) {
|
|
/* Double MCL connection case */
|
|
error("MCL error: Device %s is already connected", dstaddr);
|
|
g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_ALREADY_EXISTS,
|
|
"MCL %s is already connected", dstaddr);
|
|
connect_cb(NULL, gerr, data);
|
|
g_error_free(gerr);
|
|
return;
|
|
}
|
|
|
|
mcl->state = MCL_CONNECTED;
|
|
mcl->role = MCL_INITIATOR;
|
|
mcl->req = MCL_AVAILABLE;
|
|
mcl->ctrl |= MCAP_CTRL_STD_OP;
|
|
|
|
mcap_sync_init(mcl);
|
|
|
|
if (mcl->ctrl & MCAP_CTRL_CACHED)
|
|
mcap_uncache_mcl(mcl);
|
|
else {
|
|
mcl->ctrl &= ~MCAP_CTRL_FREE;
|
|
mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls,
|
|
mcap_mcl_ref(mcl));
|
|
}
|
|
|
|
mcl->wid = g_io_add_watch_full(mcl->cc, G_PRIORITY_DEFAULT,
|
|
G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
|
|
(GIOFunc) mcl_control_cb,
|
|
mcap_mcl_ref(mcl),
|
|
(GDestroyNotify) mcap_mcl_unref);
|
|
connect_cb(mcl, gerr, data);
|
|
}
|
|
|
|
static void set_mdl_properties(GIOChannel *chan, struct mcap_mdl *mdl)
|
|
{
|
|
struct mcap_mcl *mcl = mdl->mcl;
|
|
|
|
mdl->state = MDL_CONNECTED;
|
|
mdl->dc = g_io_channel_ref(chan);
|
|
mdl->wid = g_io_add_watch_full(mdl->dc, G_PRIORITY_DEFAULT,
|
|
G_IO_ERR | G_IO_HUP | G_IO_NVAL,
|
|
(GIOFunc) mdl_event_cb,
|
|
mcap_mdl_ref(mdl),
|
|
(GDestroyNotify) mcap_mdl_unref);
|
|
|
|
mcl->state = MCL_ACTIVE;
|
|
mcl->cb->mdl_connected(mdl, mcl->cb->user_data);
|
|
}
|
|
|
|
static void mcl_io_destroy(gpointer data)
|
|
{
|
|
struct connect_mcl *con = data;
|
|
|
|
mcap_mcl_unref(con->mcl);
|
|
if (con->destroy)
|
|
con->destroy(con->user_data);
|
|
g_free(con);
|
|
}
|
|
|
|
gboolean mcap_create_mcl(struct mcap_instance *mi,
|
|
const bdaddr_t *addr,
|
|
uint16_t ccpsm,
|
|
mcap_mcl_connect_cb connect_cb,
|
|
gpointer user_data,
|
|
GDestroyNotify destroy,
|
|
GError **err)
|
|
{
|
|
struct mcap_mcl *mcl;
|
|
struct connect_mcl *con;
|
|
|
|
mcl = find_mcl(mi->mcls, addr);
|
|
if (mcl) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_ALREADY_EXISTS,
|
|
"MCL is already connected.");
|
|
return FALSE;
|
|
}
|
|
|
|
mcl = find_mcl(mi->cached, addr);
|
|
if (!mcl) {
|
|
mcl = g_new0(struct mcap_mcl, 1);
|
|
mcl->mi = mcap_instance_ref(mi);
|
|
mcl->state = MCL_IDLE;
|
|
bacpy(&mcl->addr, addr);
|
|
set_default_cb(mcl);
|
|
mcl->next_mdl = (rand() % MCAP_MDLID_FINAL) + 1;
|
|
}
|
|
|
|
mcl->ctrl |= MCAP_CTRL_CONN;
|
|
|
|
con = g_new0(struct connect_mcl, 1);
|
|
con->mcl = mcap_mcl_ref(mcl);
|
|
con->connect_cb = connect_cb;
|
|
con->destroy = destroy;
|
|
con->user_data = user_data;
|
|
|
|
mcl->cc = bt_io_connect(mcap_connect_mcl_cb, con,
|
|
mcl_io_destroy, err,
|
|
BT_IO_OPT_SOURCE_BDADDR, &mi->src,
|
|
BT_IO_OPT_DEST_BDADDR, addr,
|
|
BT_IO_OPT_PSM, ccpsm,
|
|
BT_IO_OPT_MTU, MCAP_CC_MTU,
|
|
BT_IO_OPT_SEC_LEVEL, mi->sec,
|
|
BT_IO_OPT_MODE, L2CAP_MODE_ERTM,
|
|
BT_IO_OPT_INVALID);
|
|
if (!mcl->cc) {
|
|
mcl->ctrl &= ~MCAP_CTRL_CONN;
|
|
if (mcl->ctrl & MCAP_CTRL_FREE) {
|
|
mcap_mcl_release(mcl);
|
|
mcl->mi->mcl_uncached_cb(mcl, mcl->mi->user_data);
|
|
}
|
|
mcap_mcl_unref(con->mcl);
|
|
g_free(con);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void connect_dc_event_cb(GIOChannel *chan, GError *gerr,
|
|
gpointer user_data)
|
|
{
|
|
struct mcap_instance *mi = user_data;
|
|
struct mcap_mcl *mcl;
|
|
struct mcap_mdl *mdl;
|
|
GError *err = NULL;
|
|
bdaddr_t dst;
|
|
GSList *l;
|
|
|
|
if (gerr)
|
|
return;
|
|
|
|
bt_io_get(chan, &err, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_INVALID);
|
|
if (err) {
|
|
error("%s", err->message);
|
|
g_error_free(err);
|
|
goto drop;
|
|
}
|
|
|
|
mcl = find_mcl(mi->mcls, &dst);
|
|
if (!mcl || mcl->state != MCL_PENDING)
|
|
goto drop;
|
|
|
|
for (l = mcl->mdls; l; l = l->next) {
|
|
mdl = l->data;
|
|
if (mdl->state == MDL_WAITING) {
|
|
set_mdl_properties(chan, mdl);
|
|
return;
|
|
}
|
|
}
|
|
|
|
drop:
|
|
g_io_channel_shutdown(chan, TRUE, NULL);
|
|
}
|
|
|
|
static void set_mcl_conf(GIOChannel *chan, struct mcap_mcl *mcl)
|
|
{
|
|
gboolean reconn;
|
|
|
|
mcl->state = MCL_CONNECTED;
|
|
mcl->role = MCL_ACCEPTOR;
|
|
mcl->req = MCL_AVAILABLE;
|
|
mcl->cc = g_io_channel_ref(chan);
|
|
mcl->ctrl |= MCAP_CTRL_STD_OP;
|
|
|
|
mcap_sync_init(mcl);
|
|
|
|
reconn = (mcl->ctrl & MCAP_CTRL_CACHED);
|
|
if (reconn)
|
|
mcap_uncache_mcl(mcl);
|
|
else
|
|
mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls,
|
|
mcap_mcl_ref(mcl));
|
|
|
|
mcl->wid = g_io_add_watch_full(mcl->cc, G_PRIORITY_DEFAULT,
|
|
G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
|
|
(GIOFunc) mcl_control_cb,
|
|
mcap_mcl_ref(mcl),
|
|
(GDestroyNotify) mcap_mcl_unref);
|
|
|
|
/* Callback to report new MCL */
|
|
if (reconn)
|
|
mcl->mi->mcl_reconnected_cb(mcl, mcl->mi->user_data);
|
|
else
|
|
mcl->mi->mcl_connected_cb(mcl, mcl->mi->user_data);
|
|
}
|
|
|
|
static void connect_mcl_event_cb(GIOChannel *chan, GError *gerr,
|
|
gpointer user_data)
|
|
{
|
|
struct mcap_instance *mi = user_data;
|
|
struct mcap_mcl *mcl;
|
|
bdaddr_t dst;
|
|
char address[18], srcstr[18];
|
|
GError *err = NULL;
|
|
|
|
if (gerr)
|
|
return;
|
|
|
|
bt_io_get(chan, &err,
|
|
BT_IO_OPT_DEST_BDADDR, &dst,
|
|
BT_IO_OPT_DEST, address,
|
|
BT_IO_OPT_INVALID);
|
|
if (err) {
|
|
error("%s", err->message);
|
|
g_error_free(err);
|
|
goto drop;
|
|
}
|
|
|
|
ba2str(&mi->src, srcstr);
|
|
mcl = find_mcl(mi->mcls, &dst);
|
|
if (mcl) {
|
|
error("Control channel already created with %s on adapter %s",
|
|
address, srcstr);
|
|
goto drop;
|
|
}
|
|
|
|
mcl = find_mcl(mi->cached, &dst);
|
|
if (!mcl) {
|
|
mcl = g_new0(struct mcap_mcl, 1);
|
|
mcl->mi = mcap_instance_ref(mi);
|
|
bacpy(&mcl->addr, &dst);
|
|
set_default_cb(mcl);
|
|
mcl->next_mdl = (rand() % MCAP_MDLID_FINAL) + 1;
|
|
}
|
|
|
|
set_mcl_conf(chan, mcl);
|
|
|
|
return;
|
|
drop:
|
|
g_io_channel_shutdown(chan, TRUE, NULL);
|
|
}
|
|
|
|
struct mcap_instance *mcap_create_instance(const bdaddr_t *src,
|
|
BtIOSecLevel sec,
|
|
uint16_t ccpsm,
|
|
uint16_t dcpsm,
|
|
mcap_mcl_event_cb mcl_connected,
|
|
mcap_mcl_event_cb mcl_reconnected,
|
|
mcap_mcl_event_cb mcl_disconnected,
|
|
mcap_mcl_event_cb mcl_uncached,
|
|
mcap_info_ind_event_cb mcl_sync_info_ind,
|
|
gpointer user_data,
|
|
GError **gerr)
|
|
{
|
|
struct mcap_instance *mi;
|
|
|
|
if (sec < BT_IO_SEC_MEDIUM) {
|
|
g_set_error(gerr, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
|
|
"Security level can't be minor of %d",
|
|
BT_IO_SEC_MEDIUM);
|
|
return NULL;
|
|
}
|
|
|
|
if (!(mcl_connected && mcl_reconnected &&
|
|
mcl_disconnected && mcl_uncached)) {
|
|
g_set_error(gerr, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
|
|
"The callbacks can't be null");
|
|
return NULL;
|
|
}
|
|
|
|
mi = g_new0(struct mcap_instance, 1);
|
|
|
|
bacpy(&mi->src, src);
|
|
|
|
mi->sec = sec;
|
|
mi->mcl_connected_cb = mcl_connected;
|
|
mi->mcl_reconnected_cb = mcl_reconnected;
|
|
mi->mcl_disconnected_cb = mcl_disconnected;
|
|
mi->mcl_uncached_cb = mcl_uncached;
|
|
mi->mcl_sync_infoind_cb = mcl_sync_info_ind;
|
|
mi->user_data = user_data;
|
|
mi->csp_enabled = FALSE;
|
|
|
|
/* Listen incoming connections in control channel */
|
|
mi->ccio = bt_io_listen(connect_mcl_event_cb, NULL, mi,
|
|
NULL, gerr,
|
|
BT_IO_OPT_SOURCE_BDADDR, &mi->src,
|
|
BT_IO_OPT_PSM, ccpsm,
|
|
BT_IO_OPT_MTU, MCAP_CC_MTU,
|
|
BT_IO_OPT_SEC_LEVEL, sec,
|
|
BT_IO_OPT_MODE, L2CAP_MODE_ERTM,
|
|
BT_IO_OPT_INVALID);
|
|
if (!mi->ccio) {
|
|
error("%s", (*gerr)->message);
|
|
g_free(mi);
|
|
return NULL;
|
|
}
|
|
|
|
/* Listen incoming connections in data channels */
|
|
mi->dcio = bt_io_listen(connect_dc_event_cb, NULL, mi,
|
|
NULL, gerr,
|
|
BT_IO_OPT_SOURCE_BDADDR, &mi->src,
|
|
BT_IO_OPT_PSM, dcpsm,
|
|
BT_IO_OPT_MTU, MCAP_DC_MTU,
|
|
BT_IO_OPT_SEC_LEVEL, sec,
|
|
BT_IO_OPT_INVALID);
|
|
if (!mi->dcio) {
|
|
g_io_channel_shutdown(mi->ccio, TRUE, NULL);
|
|
g_io_channel_unref(mi->ccio);
|
|
mi->ccio = NULL;
|
|
error("%s", (*gerr)->message);
|
|
g_free(mi);
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize random seed to generate mdlids for this instance */
|
|
srand(time(NULL));
|
|
|
|
return mcap_instance_ref(mi);
|
|
}
|
|
|
|
void mcap_release_instance(struct mcap_instance *mi)
|
|
{
|
|
GSList *l;
|
|
|
|
if (!mi)
|
|
return;
|
|
|
|
if (mi->ccio) {
|
|
g_io_channel_shutdown(mi->ccio, TRUE, NULL);
|
|
g_io_channel_unref(mi->ccio);
|
|
mi->ccio = NULL;
|
|
}
|
|
|
|
if (mi->dcio) {
|
|
g_io_channel_shutdown(mi->dcio, TRUE, NULL);
|
|
g_io_channel_unref(mi->dcio);
|
|
mi->dcio = NULL;
|
|
}
|
|
|
|
for (l = mi->mcls; l; l = l->next) {
|
|
mcap_mcl_release(l->data);
|
|
mcap_mcl_unref(l->data);
|
|
}
|
|
|
|
g_slist_free(mi->mcls);
|
|
mi->mcls = NULL;
|
|
|
|
for (l = mi->cached; l; l = l->next) {
|
|
mcap_mcl_release(l->data);
|
|
mcap_mcl_unref(l->data);
|
|
}
|
|
|
|
g_slist_free(mi->cached);
|
|
mi->cached = NULL;
|
|
}
|
|
|
|
struct mcap_instance *mcap_instance_ref(struct mcap_instance *mi)
|
|
{
|
|
mi->ref++;
|
|
|
|
DBG("mcap_instance_ref(%p): ref=%d", mi, mi->ref);
|
|
|
|
return mi;
|
|
}
|
|
|
|
void mcap_instance_unref(struct mcap_instance *mi)
|
|
{
|
|
mi->ref--;
|
|
|
|
DBG("mcap_instance_unref(%p): ref=%d", mi, mi->ref);
|
|
|
|
if (mi->ref > 0)
|
|
return;
|
|
|
|
mcap_release_instance(mi);
|
|
g_free(mi);
|
|
}
|
|
|
|
uint16_t mcap_get_ctrl_psm(struct mcap_instance *mi, GError **err)
|
|
{
|
|
uint16_t lpsm;
|
|
|
|
if (!(mi && mi->ccio)) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
|
|
"Invalid MCAP instance");
|
|
return 0;
|
|
}
|
|
|
|
if (!bt_io_get(mi->ccio, err, BT_IO_OPT_PSM, &lpsm, BT_IO_OPT_INVALID))
|
|
return 0;
|
|
|
|
return lpsm;
|
|
}
|
|
|
|
uint16_t mcap_get_data_psm(struct mcap_instance *mi, GError **err)
|
|
{
|
|
uint16_t lpsm;
|
|
|
|
if (!(mi && mi->dcio)) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
|
|
"Invalid MCAP instance");
|
|
return 0;
|
|
}
|
|
|
|
if (!bt_io_get(mi->dcio, err, BT_IO_OPT_PSM, &lpsm, BT_IO_OPT_INVALID))
|
|
return 0;
|
|
|
|
return lpsm;
|
|
}
|
|
|
|
gboolean mcap_set_data_chan_mode(struct mcap_instance *mi, uint8_t mode,
|
|
GError **err)
|
|
{
|
|
if (!(mi && mi->dcio)) {
|
|
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
|
|
"Invalid MCAP instance");
|
|
return FALSE;
|
|
}
|
|
|
|
return bt_io_set(mi->dcio, err, BT_IO_OPT_MODE, mode,
|
|
BT_IO_OPT_INVALID);
|
|
}
|
|
|
|
struct mcap_mdl *mcap_mdl_ref(struct mcap_mdl *mdl)
|
|
{
|
|
mdl->ref++;
|
|
|
|
DBG("mcap_mdl_ref(%p): ref=%d", mdl, mdl->ref);
|
|
|
|
return mdl;
|
|
}
|
|
|
|
void mcap_mdl_unref(struct mcap_mdl *mdl)
|
|
{
|
|
mdl->ref--;
|
|
|
|
DBG("mcap_mdl_unref(%p): ref=%d", mdl, mdl->ref);
|
|
|
|
if (mdl->ref > 0)
|
|
return;
|
|
|
|
free_mdl(mdl);
|
|
}
|
|
|
|
|
|
static int send_sync_cmd(struct mcap_mcl *mcl, const void *buf, uint32_t size)
|
|
{
|
|
int sock;
|
|
|
|
if (mcl->cc == NULL)
|
|
return -1;
|
|
|
|
sock = g_io_channel_unix_get_fd(mcl->cc);
|
|
return mcap_send_data(sock, buf, size);
|
|
}
|
|
|
|
static int send_unsupported_cap_req(struct mcap_mcl *mcl)
|
|
{
|
|
mcap_md_sync_cap_rsp *cmd;
|
|
int sent;
|
|
|
|
cmd = g_new0(mcap_md_sync_cap_rsp, 1);
|
|
cmd->op = MCAP_MD_SYNC_CAP_RSP;
|
|
cmd->rc = MCAP_REQUEST_NOT_SUPPORTED;
|
|
|
|
sent = send_sync_cmd(mcl, cmd, sizeof(*cmd));
|
|
g_free(cmd);
|
|
|
|
return sent;
|
|
}
|
|
|
|
static int send_unsupported_set_req(struct mcap_mcl *mcl)
|
|
{
|
|
mcap_md_sync_set_rsp *cmd;
|
|
int sent;
|
|
|
|
cmd = g_new0(mcap_md_sync_set_rsp, 1);
|
|
cmd->op = MCAP_MD_SYNC_SET_RSP;
|
|
cmd->rc = MCAP_REQUEST_NOT_SUPPORTED;
|
|
|
|
sent = send_sync_cmd(mcl, cmd, sizeof(*cmd));
|
|
g_free(cmd);
|
|
|
|
return sent;
|
|
}
|
|
|
|
static void reset_tmstamp(struct mcap_csp *csp, struct timespec *base_time,
|
|
uint64_t new_tmstamp)
|
|
{
|
|
csp->base_tmstamp = new_tmstamp;
|
|
if (base_time)
|
|
csp->base_time = *base_time;
|
|
else
|
|
clock_gettime(CLK, &csp->base_time);
|
|
}
|
|
|
|
void mcap_sync_init(struct mcap_mcl *mcl)
|
|
{
|
|
if (!mcl->mi->csp_enabled) {
|
|
mcl->csp = NULL;
|
|
return;
|
|
}
|
|
|
|
mcl->csp = g_new0(struct mcap_csp, 1);
|
|
|
|
mcl->csp->rem_req_acc = 10000; /* safe divisor */
|
|
mcl->csp->set_data = NULL;
|
|
mcl->csp->csp_priv_data = NULL;
|
|
|
|
reset_tmstamp(mcl->csp, NULL, 0);
|
|
}
|
|
|
|
void mcap_sync_stop(struct mcap_mcl *mcl)
|
|
{
|
|
if (!mcl->csp)
|
|
return;
|
|
|
|
if (mcl->csp->ind_timer)
|
|
g_source_remove(mcl->csp->ind_timer);
|
|
|
|
if (mcl->csp->set_timer)
|
|
g_source_remove(mcl->csp->set_timer);
|
|
|
|
if (mcl->csp->set_data)
|
|
g_free(mcl->csp->set_data);
|
|
|
|
if (mcl->csp->csp_priv_data)
|
|
g_free(mcl->csp->csp_priv_data);
|
|
|
|
mcl->csp->ind_timer = 0;
|
|
mcl->csp->set_timer = 0;
|
|
mcl->csp->set_data = NULL;
|
|
mcl->csp->csp_priv_data = NULL;
|
|
|
|
g_free(mcl->csp);
|
|
mcl->csp = NULL;
|
|
}
|
|
|
|
static uint64_t time_us(struct timespec *tv)
|
|
{
|
|
return tv->tv_sec * 1000000 + tv->tv_nsec / 1000;
|
|
}
|
|
|
|
static int64_t bt2us(int bt)
|
|
{
|
|
return bt * 312.5;
|
|
}
|
|
|
|
static int bt2ms(int bt)
|
|
{
|
|
return bt * 312.5 / 1000;
|
|
}
|
|
|
|
static int btoffset(uint32_t btclk1, uint32_t btclk2)
|
|
{
|
|
int offset = btclk2 - btclk1;
|
|
|
|
if (offset <= -MCAP_BTCLOCK_HALF)
|
|
offset += MCAP_BTCLOCK_FIELD;
|
|
else if (offset > MCAP_BTCLOCK_HALF)
|
|
offset -= MCAP_BTCLOCK_FIELD;
|
|
|
|
return offset;
|
|
}
|
|
|
|
static int btdiff(uint32_t btclk1, uint32_t btclk2)
|
|
{
|
|
return btoffset(btclk1, btclk2);
|
|
}
|
|
|
|
static gboolean valid_btclock(uint32_t btclk)
|
|
{
|
|
return btclk <= MCAP_BTCLOCK_MAX;
|
|
}
|
|
|
|
/* This call may fail; either deal with retry or use read_btclock_retry */
|
|
static gboolean read_btclock(struct mcap_mcl *mcl, uint32_t *btclock,
|
|
uint16_t *btaccuracy)
|
|
{
|
|
/*
|
|
* FIXME: btd_adapter_read_clock(...) always return FALSE, current
|
|
* code doesn't support CSP (Clock Synchronization Protocol). To avoid
|
|
* build dependancy on struct 'btd_adapter', removing this code.
|
|
*/
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean read_btclock_retry(struct mcap_mcl *mcl, uint32_t *btclock,
|
|
uint16_t *btaccuracy)
|
|
{
|
|
int retries = 5;
|
|
|
|
while (--retries >= 0) {
|
|
if (read_btclock(mcl, btclock, btaccuracy))
|
|
return TRUE;
|
|
DBG("CSP: retrying to read bt clock...");
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean get_btrole(struct mcap_mcl *mcl)
|
|
{
|
|
int sock, flags;
|
|
socklen_t len;
|
|
|
|
if (mcl->cc == NULL)
|
|
return -1;
|
|
|
|
sock = g_io_channel_unix_get_fd(mcl->cc);
|
|
len = sizeof(flags);
|
|
|
|
if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len))
|
|
DBG("CSP: could not read role");
|
|
|
|
return flags & L2CAP_LM_MASTER;
|
|
}
|
|
|
|
uint64_t mcap_get_timestamp(struct mcap_mcl *mcl,
|
|
struct timespec *given_time)
|
|
{
|
|
struct timespec now;
|
|
uint64_t tmstamp;
|
|
|
|
if (!mcl->csp)
|
|
return MCAP_TMSTAMP_DONTSET;
|
|
|
|
if (given_time)
|
|
now = *given_time;
|
|
else
|
|
clock_gettime(CLK, &now);
|
|
|
|
tmstamp = time_us(&now) - time_us(&mcl->csp->base_time)
|
|
+ mcl->csp->base_tmstamp;
|
|
|
|
return tmstamp;
|
|
}
|
|
|
|
uint32_t mcap_get_btclock(struct mcap_mcl *mcl)
|
|
{
|
|
uint32_t btclock;
|
|
uint16_t accuracy;
|
|
|
|
if (!mcl->csp)
|
|
return MCAP_BTCLOCK_IMMEDIATE;
|
|
|
|
if (!read_btclock_retry(mcl, &btclock, &accuracy))
|
|
btclock = 0xffffffff;
|
|
|
|
return btclock;
|
|
}
|
|
|
|
static gboolean initialize_caps(struct mcap_mcl *mcl)
|
|
{
|
|
struct timespec t1, t2;
|
|
int latencies[SAMPLE_COUNT];
|
|
int latency, avg, dev;
|
|
uint32_t btclock;
|
|
uint16_t btaccuracy;
|
|
int i;
|
|
int retries;
|
|
|
|
clock_getres(CLK, &t1);
|
|
|
|
_caps.ts_res = time_us(&t1);
|
|
if (_caps.ts_res < 1)
|
|
_caps.ts_res = 1;
|
|
|
|
_caps.ts_acc = 20; /* ppm, estimated */
|
|
|
|
/* A little exercise before measuing latency */
|
|
clock_gettime(CLK, &t1);
|
|
read_btclock_retry(mcl, &btclock, &btaccuracy);
|
|
|
|
/* Read clock a number of times and measure latency */
|
|
avg = 0;
|
|
i = 0;
|
|
retries = MAX_RETRIES;
|
|
while (i < SAMPLE_COUNT && retries > 0) {
|
|
clock_gettime(CLK, &t1);
|
|
if (!read_btclock(mcl, &btclock, &btaccuracy)) {
|
|
retries--;
|
|
continue;
|
|
}
|
|
clock_gettime(CLK, &t2);
|
|
|
|
latency = time_us(&t2) - time_us(&t1);
|
|
latencies[i] = latency;
|
|
avg += latency;
|
|
i++;
|
|
}
|
|
|
|
if (retries <= 0)
|
|
return FALSE;
|
|
|
|
/* Calculate average and deviation */
|
|
avg /= SAMPLE_COUNT;
|
|
dev = 0;
|
|
for (i = 0; i < SAMPLE_COUNT; ++i)
|
|
dev += abs(latencies[i] - avg);
|
|
dev /= SAMPLE_COUNT;
|
|
|
|
/* Calculate corrected average, without 'freak' latencies */
|
|
latency = 0;
|
|
for (i = 0; i < SAMPLE_COUNT; ++i) {
|
|
if (latencies[i] > (avg + dev * 6))
|
|
latency += avg;
|
|
else
|
|
latency += latencies[i];
|
|
}
|
|
latency /= SAMPLE_COUNT;
|
|
|
|
_caps.latency = latency;
|
|
_caps.preempt_thresh = latency * 4;
|
|
_caps.syncleadtime_ms = latency * 50 / 1000;
|
|
|
|
csp_caps_initialized = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
static struct csp_caps *caps(struct mcap_mcl *mcl)
|
|
{
|
|
if (!csp_caps_initialized)
|
|
if (!initialize_caps(mcl)) {
|
|
/* Temporary failure in reading BT clock */
|
|
return NULL;
|
|
}
|
|
|
|
return &_caps;
|
|
}
|
|
|
|
static int send_sync_cap_rsp(struct mcap_mcl *mcl, uint8_t rspcode,
|
|
uint8_t btclockres, uint16_t synclead,
|
|
uint16_t tmstampres, uint16_t tmstampacc)
|
|
{
|
|
mcap_md_sync_cap_rsp *rsp;
|
|
int sent;
|
|
|
|
rsp = g_new0(mcap_md_sync_cap_rsp, 1);
|
|
|
|
rsp->op = MCAP_MD_SYNC_CAP_RSP;
|
|
rsp->rc = rspcode;
|
|
|
|
rsp->btclock = btclockres;
|
|
rsp->sltime = htons(synclead);
|
|
rsp->timestnr = htons(tmstampres);
|
|
rsp->timestna = htons(tmstampacc);
|
|
|
|
sent = send_sync_cmd(mcl, rsp, sizeof(*rsp));
|
|
g_free(rsp);
|
|
|
|
return sent;
|
|
}
|
|
|
|
static void proc_sync_cap_req(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
|
|
{
|
|
mcap_md_sync_cap_req *req;
|
|
uint16_t required_accuracy;
|
|
uint16_t our_accuracy;
|
|
uint32_t btclock;
|
|
uint16_t btres;
|
|
|
|
if (len != sizeof(mcap_md_sync_cap_req)) {
|
|
send_sync_cap_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
|
|
0, 0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
if (!caps(mcl)) {
|
|
send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE,
|
|
0, 0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
req = (mcap_md_sync_cap_req *) cmd;
|
|
required_accuracy = ntohs(req->timest);
|
|
our_accuracy = caps(mcl)->ts_acc;
|
|
btres = 0;
|
|
|
|
if (required_accuracy < our_accuracy || required_accuracy < 1) {
|
|
send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE,
|
|
0, 0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
if (!read_btclock_retry(mcl, &btclock, &btres)) {
|
|
send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE,
|
|
0, 0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
mcl->csp->remote_caps = 1;
|
|
mcl->csp->rem_req_acc = required_accuracy;
|
|
|
|
send_sync_cap_rsp(mcl, MCAP_SUCCESS, btres,
|
|
caps(mcl)->syncleadtime_ms,
|
|
caps(mcl)->ts_res, our_accuracy);
|
|
}
|
|
|
|
static int send_sync_set_rsp(struct mcap_mcl *mcl, uint8_t rspcode,
|
|
uint32_t btclock, uint64_t timestamp,
|
|
uint16_t tmstampres)
|
|
{
|
|
mcap_md_sync_set_rsp *rsp;
|
|
int sent;
|
|
|
|
rsp = g_new0(mcap_md_sync_set_rsp, 1);
|
|
|
|
rsp->op = MCAP_MD_SYNC_SET_RSP;
|
|
rsp->rc = rspcode;
|
|
rsp->btclock = htonl(btclock);
|
|
rsp->timestst = hton64(timestamp);
|
|
rsp->timestsa = htons(tmstampres);
|
|
|
|
sent = send_sync_cmd(mcl, rsp, sizeof(*rsp));
|
|
g_free(rsp);
|
|
|
|
return sent;
|
|
}
|
|
|
|
static gboolean get_all_clocks(struct mcap_mcl *mcl, uint32_t *btclock,
|
|
struct timespec *base_time,
|
|
uint64_t *timestamp)
|
|
{
|
|
int latency;
|
|
int retry = 5;
|
|
uint16_t btres;
|
|
struct timespec t0;
|
|
|
|
if (!caps(mcl))
|
|
return FALSE;
|
|
|
|
latency = caps(mcl)->preempt_thresh + 1;
|
|
|
|
while (latency > caps(mcl)->preempt_thresh && --retry >= 0) {
|
|
|
|
clock_gettime(CLK, &t0);
|
|
|
|
if (!read_btclock(mcl, btclock, &btres))
|
|
continue;
|
|
|
|
clock_gettime(CLK, base_time);
|
|
|
|
/*
|
|
* Tries to detect preemption between clock_gettime
|
|
* and read_btclock by measuring transaction time
|
|
*/
|
|
latency = time_us(base_time) - time_us(&t0);
|
|
}
|
|
|
|
*timestamp = mcap_get_timestamp(mcl, base_time);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean sync_send_indication(gpointer user_data)
|
|
{
|
|
struct mcap_mcl *mcl;
|
|
mcap_md_sync_info_ind *cmd;
|
|
uint32_t btclock;
|
|
uint64_t tmstamp;
|
|
struct timespec base_time;
|
|
int sent;
|
|
|
|
if (!user_data)
|
|
return FALSE;
|
|
|
|
btclock = 0;
|
|
mcl = user_data;
|
|
|
|
if (!caps(mcl))
|
|
return FALSE;
|
|
|
|
if (!get_all_clocks(mcl, &btclock, &base_time, &tmstamp))
|
|
return FALSE;
|
|
|
|
cmd = g_new0(mcap_md_sync_info_ind, 1);
|
|
|
|
cmd->op = MCAP_MD_SYNC_INFO_IND;
|
|
cmd->btclock = htonl(btclock);
|
|
cmd->timestst = hton64(tmstamp);
|
|
cmd->timestsa = htons(caps(mcl)->latency);
|
|
|
|
sent = send_sync_cmd(mcl, cmd, sizeof(*cmd));
|
|
g_free(cmd);
|
|
|
|
return !sent;
|
|
}
|
|
|
|
static gboolean proc_sync_set_req_phase2(gpointer user_data)
|
|
{
|
|
struct mcap_mcl *mcl;
|
|
struct sync_set_data *data;
|
|
uint8_t update;
|
|
uint32_t sched_btclock;
|
|
uint64_t new_tmstamp;
|
|
int ind_freq;
|
|
int role;
|
|
uint32_t btclock;
|
|
uint64_t tmstamp;
|
|
struct timespec base_time;
|
|
uint16_t tmstampacc;
|
|
gboolean reset;
|
|
int delay;
|
|
|
|
if (!user_data)
|
|
return FALSE;
|
|
|
|
mcl = user_data;
|
|
|
|
if (!mcl->csp->set_data)
|
|
return FALSE;
|
|
|
|
btclock = 0;
|
|
data = mcl->csp->set_data;
|
|
update = data->update;
|
|
sched_btclock = data->sched_btclock;
|
|
new_tmstamp = data->timestamp;
|
|
ind_freq = data->ind_freq;
|
|
role = data->role;
|
|
|
|
if (!caps(mcl)) {
|
|
send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!get_all_clocks(mcl, &btclock, &base_time, &tmstamp)) {
|
|
send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
|
|
return FALSE;
|
|
}
|
|
|
|
if (get_btrole(mcl) != role) {
|
|
send_sync_set_rsp(mcl, MCAP_INVALID_OPERATION, 0, 0, 0);
|
|
return FALSE;
|
|
}
|
|
|
|
reset = (new_tmstamp != MCAP_TMSTAMP_DONTSET);
|
|
|
|
if (reset) {
|
|
if (sched_btclock != MCAP_BTCLOCK_IMMEDIATE) {
|
|
delay = bt2us(btdiff(sched_btclock, btclock));
|
|
if (delay >= 0 || ((new_tmstamp - delay) > 0)) {
|
|
new_tmstamp += delay;
|
|
DBG("CSP: reset w/ delay %dus, compensated",
|
|
delay);
|
|
} else
|
|
DBG("CSP: reset w/ delay %dus, uncompensated",
|
|
delay);
|
|
}
|
|
|
|
reset_tmstamp(mcl->csp, &base_time, new_tmstamp);
|
|
tmstamp = new_tmstamp;
|
|
}
|
|
|
|
tmstampacc = caps(mcl)->latency + caps(mcl)->ts_acc;
|
|
|
|
if (mcl->csp->ind_timer) {
|
|
g_source_remove(mcl->csp->ind_timer);
|
|
mcl->csp->ind_timer = 0;
|
|
}
|
|
|
|
if (update) {
|
|
int when = ind_freq + caps(mcl)->syncleadtime_ms;
|
|
mcl->csp->ind_timer = g_timeout_add(when,
|
|
sync_send_indication,
|
|
mcl);
|
|
}
|
|
|
|
send_sync_set_rsp(mcl, MCAP_SUCCESS, btclock, tmstamp, tmstampacc);
|
|
|
|
/* First indication after set is immediate */
|
|
if (update)
|
|
sync_send_indication(mcl);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void proc_sync_set_req(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
|
|
{
|
|
mcap_md_sync_set_req *req;
|
|
uint32_t sched_btclock, cur_btclock;
|
|
uint16_t btres;
|
|
uint8_t update;
|
|
uint64_t timestamp;
|
|
struct sync_set_data *set_data;
|
|
int phase2_delay, ind_freq, when;
|
|
|
|
if (len != sizeof(mcap_md_sync_set_req)) {
|
|
send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
req = (mcap_md_sync_set_req *) cmd;
|
|
sched_btclock = ntohl(req->btclock);
|
|
update = req->timestui;
|
|
timestamp = ntoh64(req->timestst);
|
|
cur_btclock = 0;
|
|
|
|
if (sched_btclock != MCAP_BTCLOCK_IMMEDIATE &&
|
|
!valid_btclock(sched_btclock)) {
|
|
send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
if (update > 1) {
|
|
send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
if (!mcl->csp->remote_caps) {
|
|
/* Remote side did not ask our capabilities yet */
|
|
send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
if (!caps(mcl)) {
|
|
send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
if (!read_btclock_retry(mcl, &cur_btclock, &btres)) {
|
|
send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
if (sched_btclock == MCAP_BTCLOCK_IMMEDIATE)
|
|
phase2_delay = 0;
|
|
else {
|
|
phase2_delay = btdiff(cur_btclock, sched_btclock);
|
|
|
|
if (phase2_delay < 0) {
|
|
/* can not reset in the past tense */
|
|
send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
|
|
0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
/* Convert to miliseconds */
|
|
phase2_delay = bt2ms(phase2_delay);
|
|
|
|
if (phase2_delay > 61*1000) {
|
|
/* More than 60 seconds in the future */
|
|
send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
|
|
0, 0, 0);
|
|
return;
|
|
} else if (phase2_delay < caps(mcl)->latency / 1000) {
|
|
/* Too fast for us to do in time */
|
|
send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
|
|
0, 0, 0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (update) {
|
|
/*
|
|
* Indication frequency: required accuracy divided by ours
|
|
* Converted to milisseconds
|
|
*/
|
|
ind_freq = (1000 * mcl->csp->rem_req_acc) / caps(mcl)->ts_acc;
|
|
|
|
if (ind_freq < MAX(caps(mcl)->latency * 2 / 1000, 100)) {
|
|
/* Too frequent, we can't handle */
|
|
send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
|
|
0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
DBG("CSP: indication every %dms", ind_freq);
|
|
} else
|
|
ind_freq = 0;
|
|
|
|
if (mcl->csp->ind_timer) {
|
|
/* Old indications are no longer sent */
|
|
g_source_remove(mcl->csp->ind_timer);
|
|
mcl->csp->ind_timer = 0;
|
|
}
|
|
|
|
if (!mcl->csp->set_data)
|
|
mcl->csp->set_data = g_new0(struct sync_set_data, 1);
|
|
|
|
set_data = (struct sync_set_data *) mcl->csp->set_data;
|
|
|
|
set_data->update = update;
|
|
set_data->sched_btclock = sched_btclock;
|
|
set_data->timestamp = timestamp;
|
|
set_data->ind_freq = ind_freq;
|
|
set_data->role = get_btrole(mcl);
|
|
|
|
/*
|
|
* TODO is there some way to schedule a call based directly on
|
|
* a BT clock value, instead of this estimation that uses
|
|
* the SO clock?
|
|
*/
|
|
|
|
if (phase2_delay > 0) {
|
|
when = phase2_delay + caps(mcl)->syncleadtime_ms;
|
|
mcl->csp->set_timer = g_timeout_add(when,
|
|
proc_sync_set_req_phase2,
|
|
mcl);
|
|
} else
|
|
proc_sync_set_req_phase2(mcl);
|
|
|
|
/* First indication is immediate */
|
|
if (update)
|
|
sync_send_indication(mcl);
|
|
}
|
|
|
|
static void proc_sync_cap_rsp(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
|
|
{
|
|
mcap_md_sync_cap_rsp *rsp;
|
|
uint8_t mcap_err;
|
|
uint8_t btclockres;
|
|
uint16_t synclead;
|
|
uint16_t tmstampres;
|
|
uint16_t tmstampacc;
|
|
struct mcap_sync_cap_cbdata *cbdata;
|
|
mcap_sync_cap_cb cb;
|
|
gpointer user_data;
|
|
|
|
if (mcl->csp->csp_req != MCAP_MD_SYNC_CAP_REQ) {
|
|
DBG("CSP: got unexpected cap respose");
|
|
return;
|
|
}
|
|
|
|
if (!mcl->csp->csp_priv_data) {
|
|
DBG("CSP: no priv data for cap respose");
|
|
return;
|
|
}
|
|
|
|
cbdata = mcl->csp->csp_priv_data;
|
|
cb = cbdata->cb;
|
|
user_data = cbdata->user_data;
|
|
g_free(cbdata);
|
|
|
|
mcl->csp->csp_priv_data = NULL;
|
|
mcl->csp->csp_req = 0;
|
|
|
|
if (len != sizeof(mcap_md_sync_cap_rsp)) {
|
|
DBG("CSP: got corrupted cap respose");
|
|
return;
|
|
}
|
|
|
|
rsp = (mcap_md_sync_cap_rsp *) cmd;
|
|
mcap_err = rsp->rc;
|
|
btclockres = rsp->btclock;
|
|
synclead = ntohs(rsp->sltime);
|
|
tmstampres = ntohs(rsp->timestnr);
|
|
tmstampacc = ntohs(rsp->timestna);
|
|
|
|
if (!mcap_err)
|
|
mcl->csp->local_caps = TRUE;
|
|
|
|
cb(mcl, mcap_err, btclockres, synclead, tmstampres, tmstampacc, NULL,
|
|
user_data);
|
|
}
|
|
|
|
static void proc_sync_set_rsp(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
|
|
{
|
|
mcap_md_sync_set_rsp *rsp;
|
|
uint8_t mcap_err;
|
|
uint32_t btclock;
|
|
uint64_t timestamp;
|
|
uint16_t accuracy;
|
|
struct mcap_sync_set_cbdata *cbdata;
|
|
mcap_sync_set_cb cb;
|
|
gpointer user_data;
|
|
|
|
if (mcl->csp->csp_req != MCAP_MD_SYNC_SET_REQ) {
|
|
DBG("CSP: got unexpected set respose");
|
|
return;
|
|
}
|
|
|
|
if (!mcl->csp->csp_priv_data) {
|
|
DBG("CSP: no priv data for set respose");
|
|
return;
|
|
}
|
|
|
|
cbdata = mcl->csp->csp_priv_data;
|
|
cb = cbdata->cb;
|
|
user_data = cbdata->user_data;
|
|
g_free(cbdata);
|
|
|
|
mcl->csp->csp_priv_data = NULL;
|
|
mcl->csp->csp_req = 0;
|
|
|
|
if (len != sizeof(mcap_md_sync_set_rsp)) {
|
|
DBG("CSP: got corrupted set respose");
|
|
return;
|
|
}
|
|
|
|
rsp = (mcap_md_sync_set_rsp *) cmd;
|
|
mcap_err = rsp->rc;
|
|
btclock = ntohl(rsp->btclock);
|
|
timestamp = ntoh64(rsp->timestst);
|
|
accuracy = ntohs(rsp->timestsa);
|
|
|
|
if (!mcap_err && !valid_btclock(btclock))
|
|
mcap_err = MCAP_ERROR_INVALID_ARGS;
|
|
|
|
cb(mcl, mcap_err, btclock, timestamp, accuracy, NULL, user_data);
|
|
}
|
|
|
|
static void proc_sync_info_ind(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
|
|
{
|
|
mcap_md_sync_info_ind *req;
|
|
struct sync_info_ind_data data;
|
|
uint32_t btclock;
|
|
|
|
if (!mcl->csp->ind_expected) {
|
|
DBG("CSP: received unexpected info indication");
|
|
return;
|
|
}
|
|
|
|
if (len != sizeof(mcap_md_sync_info_ind))
|
|
return;
|
|
|
|
req = (mcap_md_sync_info_ind *) cmd;
|
|
|
|
btclock = ntohl(req->btclock);
|
|
|
|
if (!valid_btclock(btclock))
|
|
return;
|
|
|
|
data.btclock = btclock;
|
|
data.timestamp = ntoh64(req->timestst);
|
|
data.accuracy = ntohs(req->timestsa);
|
|
|
|
if (mcl->mi->mcl_sync_infoind_cb)
|
|
mcl->mi->mcl_sync_infoind_cb(mcl, &data);
|
|
}
|
|
|
|
void proc_sync_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
|
|
{
|
|
if (!mcl->mi->csp_enabled || !mcl->csp) {
|
|
switch (cmd[0]) {
|
|
case MCAP_MD_SYNC_CAP_REQ:
|
|
send_unsupported_cap_req(mcl);
|
|
break;
|
|
case MCAP_MD_SYNC_SET_REQ:
|
|
send_unsupported_set_req(mcl);
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch (cmd[0]) {
|
|
case MCAP_MD_SYNC_CAP_REQ:
|
|
proc_sync_cap_req(mcl, cmd, len);
|
|
break;
|
|
case MCAP_MD_SYNC_CAP_RSP:
|
|
proc_sync_cap_rsp(mcl, cmd, len);
|
|
break;
|
|
case MCAP_MD_SYNC_SET_REQ:
|
|
proc_sync_set_req(mcl, cmd, len);
|
|
break;
|
|
case MCAP_MD_SYNC_SET_RSP:
|
|
proc_sync_set_rsp(mcl, cmd, len);
|
|
break;
|
|
case MCAP_MD_SYNC_INFO_IND:
|
|
proc_sync_info_ind(mcl, cmd, len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void mcap_sync_cap_req(struct mcap_mcl *mcl, uint16_t reqacc,
|
|
mcap_sync_cap_cb cb, gpointer user_data,
|
|
GError **err)
|
|
{
|
|
struct mcap_sync_cap_cbdata *cbdata;
|
|
mcap_md_sync_cap_req *cmd;
|
|
|
|
if (!mcl->mi->csp_enabled || !mcl->csp) {
|
|
g_set_error(err,
|
|
MCAP_CSP_ERROR,
|
|
MCAP_ERROR_RESOURCE_UNAVAILABLE,
|
|
"CSP not enabled for the instance");
|
|
return;
|
|
}
|
|
|
|
if (mcl->csp->csp_req) {
|
|
g_set_error(err,
|
|
MCAP_CSP_ERROR,
|
|
MCAP_ERROR_RESOURCE_UNAVAILABLE,
|
|
"Pending CSP request");
|
|
return;
|
|
}
|
|
|
|
mcl->csp->csp_req = MCAP_MD_SYNC_CAP_REQ;
|
|
cmd = g_new0(mcap_md_sync_cap_req, 1);
|
|
|
|
cmd->op = MCAP_MD_SYNC_CAP_REQ;
|
|
cmd->timest = htons(reqacc);
|
|
|
|
cbdata = g_new0(struct mcap_sync_cap_cbdata, 1);
|
|
cbdata->cb = cb;
|
|
cbdata->user_data = user_data;
|
|
mcl->csp->csp_priv_data = cbdata;
|
|
|
|
send_sync_cmd(mcl, cmd, sizeof(*cmd));
|
|
|
|
g_free(cmd);
|
|
}
|
|
|
|
void mcap_sync_set_req(struct mcap_mcl *mcl, uint8_t update, uint32_t btclock,
|
|
uint64_t timestamp, mcap_sync_set_cb cb,
|
|
gpointer user_data, GError **err)
|
|
{
|
|
mcap_md_sync_set_req *cmd;
|
|
struct mcap_sync_set_cbdata *cbdata;
|
|
|
|
if (!mcl->mi->csp_enabled || !mcl->csp) {
|
|
g_set_error(err,
|
|
MCAP_CSP_ERROR,
|
|
MCAP_ERROR_RESOURCE_UNAVAILABLE,
|
|
"CSP not enabled for the instance");
|
|
return;
|
|
}
|
|
|
|
if (!mcl->csp->local_caps) {
|
|
g_set_error(err,
|
|
MCAP_CSP_ERROR,
|
|
MCAP_ERROR_RESOURCE_UNAVAILABLE,
|
|
"Did not get CSP caps from slave yet");
|
|
return;
|
|
}
|
|
|
|
if (mcl->csp->csp_req) {
|
|
g_set_error(err,
|
|
MCAP_CSP_ERROR,
|
|
MCAP_ERROR_RESOURCE_UNAVAILABLE,
|
|
"Pending CSP request");
|
|
return;
|
|
}
|
|
|
|
mcl->csp->csp_req = MCAP_MD_SYNC_SET_REQ;
|
|
cmd = g_new0(mcap_md_sync_set_req, 1);
|
|
|
|
cmd->op = MCAP_MD_SYNC_SET_REQ;
|
|
cmd->timestui = update;
|
|
cmd->btclock = htonl(btclock);
|
|
cmd->timestst = hton64(timestamp);
|
|
|
|
mcl->csp->ind_expected = update;
|
|
|
|
cbdata = g_new0(struct mcap_sync_set_cbdata, 1);
|
|
cbdata->cb = cb;
|
|
cbdata->user_data = user_data;
|
|
mcl->csp->csp_priv_data = cbdata;
|
|
|
|
send_sync_cmd(mcl, cmd, sizeof(*cmd));
|
|
|
|
g_free(cmd);
|
|
}
|
|
|
|
void mcap_enable_csp(struct mcap_instance *mi)
|
|
{
|
|
mi->csp_enabled = TRUE;
|
|
}
|
|
|
|
void mcap_disable_csp(struct mcap_instance *mi)
|
|
{
|
|
mi->csp_enabled = FALSE;
|
|
}
|