blob: 5c5d12f685f9b37a38f7d37de25ec7ad0b79ab7f [file] [log] [blame]
/*
*
* oFono - Open Source Telephony
*
* Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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 <stdint.h>
#include <errno.h>
#include <glib.h>
#include "client.h"
#include "pipe.h"
#define PN_PIPE 0xD9
#define PN_PIPE_INVALID_HANDLE 0xFF
struct isi_pipe_create_req {
uint8_t cmd;
uint8_t state_after;
uint8_t priority;
uint8_t device1;
uint8_t object1;
uint8_t type1;
uint8_t pad;
uint8_t device2;
uint8_t object2;
uint8_t type2;
uint8_t n_sb;
};
struct isi_pipe_enable_req {
uint8_t cmd;
uint8_t pipe_handle;
uint8_t pad;
};
struct isi_pipe_reset_req {
uint8_t cmd;
uint8_t pipe_handle;
uint8_t state_after;
};
struct isi_pipe_remove_req {
uint8_t cmd;
uint8_t pipe_handle;
};
struct isi_pipe_resp {
uint8_t pipe_handle;
uint8_t error_code;
uint8_t error1;
uint8_t error2;
};
enum isi_pipe_message_id {
PNS_PIPE_CREATE_REQ,
PNS_PIPE_CREATE_RESP,
PNS_PIPE_REMOVE_REQ,
PNS_PIPE_REMOVE_RESP,
PNS_PIPE_RESET_REQ,
PNS_PIPE_RESET_RESP,
PNS_PIPE_ENABLE_REQ,
PNS_PIPE_ENABLE_RESP,
PNS_PIPE_REDIRECT_REQ,
PNS_PIPE_REDIRECT_RESP,
PNS_PIPE_DISABLE_REQ,
PNS_PIPE_DISABLE_RESP,
};
enum pn_pipe_error { /* error codes */
PN_PIPE_ERR_NO_ERROR,
PN_PIPE_ERR_INVALID_PARAM,
PN_PIPE_ERR_INVALID_HANDLE,
PN_PIPE_ERR_INVALID_CTRL_ID,
PN_PIPE_ERR_NOT_ALLOWED,
PN_PIPE_ERR_PEP_IN_USE,
PN_PIPE_ERR_OVERLOAD,
PN_PIPE_ERR_DEV_DISCONNECTED,
PN_PIPE_ERR_TIMEOUT,
PN_PIPE_ERR_ALL_PIPES_IN_USE,
PN_PIPE_ERR_GENERAL,
PN_PIPE_ERR_NOT_SUPPORTED,
};
enum pn_pipe_state { /* initial pipe state */
PN_PIPE_DISABLE,
PN_PIPE_ENABLE,
};
enum pn_msg_priority {
PN_MSG_PRIORITY_LOW = 1,
PN_MSG_PRIORITY_HIGH,
};
struct _GIsiPipe {
GIsiClient *client;
GIsiPipeHandler handler;
GIsiPipeErrorHandler error_handler;
void *opaque;
int error;
uint8_t handle;
gboolean enabled;
gboolean enabling;
};
static int g_isi_pipe_error(enum pn_pipe_error code)
{
switch (code) {
case PN_PIPE_ERR_NO_ERROR:
return 0;
case PN_PIPE_ERR_INVALID_PARAM:
return -EINVAL;
case PN_PIPE_ERR_INVALID_HANDLE:
return -EBADF;
case PN_PIPE_ERR_INVALID_CTRL_ID:
return -ENOTSUP;
case PN_PIPE_ERR_NOT_ALLOWED:
return -EPERM;
case PN_PIPE_ERR_PEP_IN_USE:
return -EBUSY;
case PN_PIPE_ERR_OVERLOAD:
return -ENOBUFS;
case PN_PIPE_ERR_DEV_DISCONNECTED:
return -ENETDOWN;
case PN_PIPE_ERR_TIMEOUT:
return -ETIMEDOUT;
case PN_PIPE_ERR_ALL_PIPES_IN_USE:
return -ENFILE;
case PN_PIPE_ERR_GENERAL:
return -EAGAIN;
case PN_PIPE_ERR_NOT_SUPPORTED:
return -ENOSYS;
}
return -EBADMSG;
}
static void g_isi_pipe_handle_error(GIsiPipe *pipe, uint8_t code)
{
int err = g_isi_pipe_error(code);
if (err == 0)
return;
pipe->error = err;
if (pipe->error_handler)
pipe->error_handler(pipe);
}
static void g_isi_pipe_created(const GIsiMessage *msg, void *data)
{
struct isi_pipe_resp *resp;
size_t len = sizeof(struct isi_pipe_resp);
GIsiPipe *pipe = data;
if (g_isi_msg_error(msg) < 0) {
g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
return;
}
if (g_isi_msg_id(msg) != PNS_PIPE_CREATE_RESP)
return;
if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
return;
if (resp->pipe_handle == PN_PIPE_INVALID_HANDLE) {
g_isi_pipe_handle_error(pipe, resp->error_code);
return;
}
pipe->handle = resp->pipe_handle;
if (pipe->enabling)
g_isi_pipe_start(pipe);
if (pipe->handler)
pipe->handler(pipe);
}
/**
* Create a Phonet pipe in disabled state and with low priority.
* @param modem ISI modem to create a pipe with
* @param created optional callback for created event
* @param obj1 Object handle of the first end point
* @param obj2 Object handle of the second end point
* @param type1 Type of the first end point
* @param type2 Type of the second end point
* @return a pipe object on success, NULL on error.
*/
GIsiPipe *g_isi_pipe_create(GIsiModem *modem, GIsiPipeHandler cb, uint16_t obj1,
uint16_t obj2, uint8_t type1, uint8_t type2)
{
struct isi_pipe_create_req msg = {
.cmd = PNS_PIPE_CREATE_REQ,
.state_after = PN_PIPE_DISABLE,
.priority = PN_MSG_PRIORITY_LOW,
.device1 = obj1 >> 8,
.object1 = obj1 & 0xff,
.type1 = type1,
.device2 = obj2 >> 8,
.object2 = obj2 & 0xff,
.type2 = type2,
.n_sb = 0,
};
size_t len = sizeof(msg);
GIsiPipe *pipe;
pipe = g_try_new0(GIsiPipe, 1);
if (pipe == NULL) {
errno = ENOMEM;
return NULL;
}
pipe->client = g_isi_client_create(modem, PN_PIPE);
if (pipe->client == NULL) {
errno = ENOMEM;
g_free(pipe);
return NULL;
}
pipe->handler = cb;
pipe->error_handler = NULL;
pipe->error = 0;
pipe->enabling = FALSE;
pipe->enabled = FALSE;
pipe->handle = PN_PIPE_INVALID_HANDLE;
if (g_isi_client_send(pipe->client, &msg, len,
g_isi_pipe_created, pipe, NULL))
return pipe;
g_isi_client_destroy(pipe->client);
g_free(pipe);
return NULL;
}
static void g_isi_pipe_enabled(const GIsiMessage *msg, void *data)
{
GIsiPipe *pipe = data;
const struct isi_pipe_resp *resp;
size_t len = sizeof(struct isi_pipe_resp);
if (g_isi_msg_error(msg) < 0) {
g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
return;
}
if (g_isi_msg_id(msg) != PNS_PIPE_ENABLE_RESP)
return;
if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
return;
if (pipe->handle != resp->pipe_handle)
return;
g_isi_pipe_handle_error(pipe, resp->error_code);
pipe->enabling = FALSE;
if (!pipe->error)
pipe->enabled = TRUE;
}
static void g_isi_pipe_enable(GIsiPipe *pipe)
{
struct isi_pipe_enable_req msg = {
.cmd = PNS_PIPE_ENABLE_REQ,
.pipe_handle = pipe->handle,
};
size_t len = sizeof(msg);
g_isi_client_send(pipe->client, &msg, len,
g_isi_pipe_enabled, pipe, NULL);
}
/**
* Enable a pipe, i.e. turn on data transfer between the two end points.
* @param pipe pipe as returned from g_isi_pipe_create()
* @return 0 on success or an error code
*/
int g_isi_pipe_start(GIsiPipe *pipe)
{
if (pipe->error)
return pipe->error;
if (pipe->enabling || pipe->enabled)
return 0;
if (pipe->handle != PN_PIPE_INVALID_HANDLE)
g_isi_pipe_enable(pipe);
else
pipe->enabling = TRUE;
return 0;
}
/* Not very useful, it will never have time to trigger */
static void g_isi_pipe_removed(const GIsiMessage *msg, void *data)
{
GIsiPipe *pipe = data;
struct isi_pipe_resp *resp;
size_t len = sizeof(struct isi_pipe_resp);
if (g_isi_msg_error(msg) < 0) {
g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
return;
}
if (g_isi_msg_id(msg) != PNS_PIPE_REMOVE_RESP)
return;
if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
return;
if (pipe->handle != resp->pipe_handle)
return;
pipe->handle = PN_PIPE_INVALID_HANDLE;
pipe->error = -EPIPE;
}
static void g_isi_pipe_remove(GIsiPipe *pipe)
{
struct isi_pipe_remove_req msg = {
.cmd = PNS_PIPE_REMOVE_REQ,
.pipe_handle = pipe->handle,
};
size_t len = sizeof(msg);
g_isi_client_send(pipe->client, &msg, len,
g_isi_pipe_removed, pipe, NULL);
}
/**
* Destroy a pipe. If it was connected, it is removed.
* @param pipe pipe as returned from g_isi_pipe_create()
*/
void g_isi_pipe_destroy(GIsiPipe *pipe)
{
if (!pipe->error)
g_isi_pipe_remove(pipe);
g_isi_client_destroy(pipe->client);
g_free(pipe);
}
void g_isi_pipe_set_error_handler(GIsiPipe *pipe, GIsiPipeErrorHandler cb)
{
pipe->error_handler = cb;
}
int g_isi_pipe_get_error(const GIsiPipe *pipe)
{
return pipe->error;
}
void *g_isi_pipe_set_userdata(GIsiPipe *pipe, void *opaque)
{
void *old = pipe->opaque;
pipe->opaque = opaque;
return old;
}
void *g_isi_pipe_get_userdata(GIsiPipe *pipe)
{
return pipe->opaque;
}
/**
* Return a pipe handle.
* @param pipe a ready-made pipe with handler data present. Available
* after the pipe creation callback is called.
* @return uint8_t handle.
*/
uint8_t g_isi_pipe_get_handle(GIsiPipe *pipe)
{
return pipe->handle;
}