/*
* Copyright (c) 2008 Jiri Svoboda
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** @addtogroup trace
* @{
*/
/** @file
*/
#include <stdio.h>
#include <stdlib.h>
#include <libadt/hash_table.h>
#include "ipc_desc.h"
#include "proto.h"
#include "trace.h"
#include "ipcp.h"
#define IPCP_CALLID_SYNC 0
typedef struct {
ipcarg_t phone_hash;
ipc_call_t question;
oper_t *oper;
ipc_callid_t call_hash;
link_t link;
} pending_call_t;
typedef struct {
int server;
proto_t *proto;
} connection_t;
#define MAX_PHONE 64
connection_t connections[MAX_PHONE];
int have_conn[MAX_PHONE];
#define PCALL_TABLE_CHAINS 32
hash_table_t pending_calls;
/*
* Pseudo-protocols
*/
proto_t *proto_system; /**< Protocol describing system IPC methods. */
proto_t *proto_unknown; /**< Protocol with no known methods. */
static hash_index_t pending_call_hash(unsigned long key[]);
static int pending_call_compare(unsigned long key[], hash_count_t keys,
link_t *item);
static void pending_call_remove_callback(link_t *item);
hash_table_operations_t pending_call_ops = {
.hash = pending_call_hash,
.compare = pending_call_compare,
.remove_callback = pending_call_remove_callback
};
static hash_index_t pending_call_hash(unsigned long key[])
{
// printf("pending_call_hash\n");
return key[0] % PCALL_TABLE_CHAINS;
}
static int pending_call_compare(unsigned long key[], hash_count_t keys,
link_t *item)
{
pending_call_t *hs;
// printf("pending_call_compare\n");
hs = hash_table_get_instance(item, pending_call_t, link);
// FIXME: this will fail if sizeof(long) < sizeof(void *).
return key[0] == hs->call_hash;
}
static void pending_call_remove_callback(link_t *item)
{
// printf("pending_call_remove_callback\n");
}
void ipcp_connection_set(int phone, int server, proto_t *proto)
{
if (phone <0 || phone >= MAX_PHONE) return;
connections[phone].server = server;
connections[phone].proto = proto;
have_conn[phone] = 1;
}
void ipcp_connection_clear(int phone)
{
have_conn[phone] = 0;
connections[phone].server = 0;
connections[phone].proto = NULL;
}
static void ipc_m_print(proto_t *proto, ipcarg_t method)
{
oper_t *oper;
/* Try system methods first */
oper = proto_get_oper(proto_system, method);
if (oper == NULL && proto != NULL) {
/* Not a system method, try the user protocol. */
oper = proto_get_oper(proto, method);
}
if (oper != NULL) {
printf("%s (%ld)", oper
->name
, method
);
return;
}
}
void ipcp_init(void)
{
ipc_m_desc_t *desc;
oper_t *oper;
val_type_t arg_def[OPER_MAX_ARGS] = {
V_INTEGER,
V_INTEGER,
V_INTEGER,
V_INTEGER,
V_INTEGER
};
/*
* Create a pseudo-protocol 'unknown' that has no known methods.
*/
proto_unknown = proto_new("unknown");
/*
* Create a pseudo-protocol 'system' defining names of system IPC
* methods.
*/
proto_system = proto_new("system");
desc = ipc_methods;
while (desc->number != 0) {
oper = oper_new(desc->name, OPER_MAX_ARGS, arg_def, V_INTEGER,
OPER_MAX_ARGS, arg_def);
proto_add_oper(proto_system, desc->number, oper);
++desc;
}
hash_table_create(&pending_calls, PCALL_TABLE_CHAINS, 1, &pending_call_ops);
}
void ipcp_cleanup(void)
{
proto_delete(proto_system);
hash_table_destroy(&pending_calls);
}
void ipcp_call_out(int phone, ipc_call_t *call, ipc_callid_t hash)
{
pending_call_t *pcall;
proto_t *proto;
unsigned long key[1];
oper_t *oper;
ipcarg_t *args;
int i;
if (have_conn[phone]) proto = connections[phone].proto;
else proto = NULL;
args = call->args;
if ((display_mask & DM_IPC) != 0) {
printf("Call ID: 0x%lx, phone: %d, proto: %s, method: ", hash
,
phone, (proto ? proto->name : "n/a"));
ipc_m_print(proto, IPC_GET_METHOD(*call));
printf(" args: (%lu, %lu, %lu, %lu, %lu)\n", args
[1], args
[2],
args[3], args[4], args[5]);
}
if ((display_mask & DM_USER) != 0) {
if (proto != NULL) {
oper = proto_get_oper(proto, IPC_GET_METHOD(*call));
} else {
oper = NULL;
}
if (oper != NULL) {
printf("%s(%d).%s", (proto
? proto
->name
: "n/a"),
phone, (oper ? oper->name : "unknown"));
for (i = 1; i <= oper->argc; ++i) {
val_print(args[i], oper->arg_type[i - 1]);
}
if (oper->rv_type == V_VOID && oper->respc == 0) {
/*
* No response data (typically the task will
* not be interested in the response).
* We will not display response.
*/
}
}
} else {
oper = NULL;
}
/* Store call in hash table for response matching */
pcall
= malloc(sizeof(pending_call_t
));
pcall->phone_hash = phone;
pcall->question = *call;
pcall->call_hash = hash;
pcall->oper = oper;
key[0] = hash;
hash_table_insert(&pending_calls, key, &pcall->link);
}
static void parse_answer(ipc_callid_t hash, pending_call_t *pcall,
ipc_call_t *answer)
{
ipcarg_t phone;
ipcarg_t method;
ipcarg_t service;
ipcarg_t retval;
proto_t *proto;
int cphone;
ipcarg_t *resp;
oper_t *oper;
int i;
// printf("parse_answer\n");
phone = pcall->phone_hash;
method = IPC_GET_METHOD(pcall->question);
retval = IPC_GET_RETVAL(*answer);
resp = answer->args;
if ((display_mask & DM_IPC) != 0) {
printf("Response to 0x%lx: retval=%ld, args = (%lu, %lu, %lu, %lu, %lu)\n",
hash, retval, IPC_GET_ARG1(*answer),
IPC_GET_ARG2(*answer), IPC_GET_ARG3(*answer),
IPC_GET_ARG4(*answer), IPC_GET_ARG5(*answer));
}
if ((display_mask & DM_USER) != 0) {
oper = pcall->oper;
if (oper != NULL && (oper->rv_type != V_VOID || oper->respc > 0)) {
if (oper->rv_type != V_VOID) {
val_print(retval, oper->rv_type);
}
if (oper->respc > 0) {
for (i = 1; i <= oper->respc; ++i) {
val_print(resp[i], oper->resp_type[i - 1]);
}
}
}
}
if (phone == 0 && method == IPC_M_CONNECT_ME_TO && retval == 0) {
/* Connected to a service (through NS) */
service = IPC_GET_ARG1(pcall->question);
proto = proto_get_by_srv(service);
if (proto == NULL) proto = proto_unknown;
cphone = IPC_GET_ARG5(*answer);
if ((display_mask & DM_SYSTEM) != 0) {
printf("Registering connection (phone %d, protocol: %s)\n", cphone
,
proto->name);
}
ipcp_connection_set(cphone, 0, proto);
}
}
void ipcp_call_in(ipc_call_t *call, ipc_callid_t hash)
{
link_t *item;
pending_call_t *pcall;
unsigned long key[1];
// printf("ipcp_call_in()\n");
if ((hash & IPC_CALLID_ANSWERED) == 0 && hash != IPCP_CALLID_SYNC) {
/* Not a response */
if ((display_mask & DM_IPC) != 0) {
printf("Not a response (hash 0x%lx)\n", hash
);
}
return;
}
hash = hash & ~IPC_CALLID_ANSWERED;
key[0] = hash;
item = hash_table_find(&pending_calls, key);
if (item == NULL) return; // No matching question found
/*
* Response matched to question.
*/
pcall = hash_table_get_instance(item, pending_call_t, link);
hash_table_remove(&pending_calls, key, 1);
parse_answer(hash, pcall, call);
}
void ipcp_call_sync(int phone, ipc_call_t *call, ipc_call_t *answer)
{
ipcp_call_out(phone, call, IPCP_CALLID_SYNC);
ipcp_call_in(answer, IPCP_CALLID_SYNC);
}
void ipcp_hangup(int phone, int rc)
{
if ((display_mask & DM_SYSTEM) != 0) {
printf("Hang phone %d up -> %d\n", phone
, rc
);
ipcp_connection_clear(phone);
}
}
/** @}
*/