blob: 0372b82bba7e81bb595a5779b5298c4adc1dbe47 [file] [log] [blame]
/*
* Connection data.
*
* <-- Copyright Giesecke & Devrient GmbH 2009 - 2012 -->
*
* 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.
*/
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <linux/semaphore.h>
#include <linux/time.h>
#include <net/sock.h>
#include <net/net_namespace.h>
#include "connection.h"
#include "common.h"
/* Define the initial state of the Data Available Semaphore */
#define SEM_NO_DATA_AVAILABLE 0
struct connection *connection_new(void)
{
struct connection *conn;
conn = kzalloc(sizeof(*conn), GFP_KERNEL);
if (conn == NULL) {
MCDRV_DBG_ERROR(mc_kapi, "Allocation failure");
return NULL;
}
conn->sequence_magic = mcapi_unique_id();
mutex_init(&conn->data_lock);
sema_init(&conn->data_available_sem, SEM_NO_DATA_AVAILABLE);
mcapi_insert_connection(conn);
return conn;
}
void connection_cleanup(struct connection *conn)
{
if (!conn)
return;
kfree_skb(conn->skb);
mcapi_remove_connection(conn->sequence_magic);
kfree(conn);
}
bool connection_connect(struct connection *conn, pid_t dest)
{
/* Nothing to connect */
conn->peer_pid = dest;
return true;
}
size_t connection_read_data_msg(struct connection *conn, void *buffer,
uint32_t len)
{
size_t ret = -1;
MCDRV_DBG_VERBOSE(mc_kapi,
"reading connection data %u, connection data left %u",
len, conn->data_len);
/* trying to read more than the left data */
if (len > conn->data_len) {
ret = conn->data_len;
memcpy(buffer, conn->data_start, conn->data_len);
conn->data_len = 0;
} else {
ret = len;
memcpy(buffer, conn->data_start, len);
conn->data_len -= len;
conn->data_start += len;
}
if (conn->data_len == 0) {
conn->data_start = NULL;
kfree_skb(conn->skb);
conn->skb = NULL;
}
MCDRV_DBG_VERBOSE(mc_kapi, "read %u", ret);
return ret;
}
size_t connection_read_datablock(struct connection *conn, void *buffer,
uint32_t len)
{
return connection_read_data(conn, buffer, len, -1);
}
size_t connection_read_data(struct connection *conn, void *buffer, uint32_t len,
int32_t timeout)
{
size_t ret = 0;
MCDRV_ASSERT(buffer != NULL);
MCDRV_ASSERT(conn->socket_descriptor != NULL);
MCDRV_DBG_VERBOSE(mc_kapi, "read data len = %u for PID = %u",
len, conn->sequence_magic);
do {
/*
* Wait until data is available or timeout
* msecs_to_jiffies(-1) -> wait forever for the sem
*/
if (down_timeout(&(conn->data_available_sem),
msecs_to_jiffies(timeout))) {
MCDRV_DBG_VERBOSE(mc_kapi,
"Timeout reading the data sem");
ret = -2;
break;
}
if (mutex_lock_interruptible(&(conn->data_lock))) {
MCDRV_DBG_ERROR(mc_kapi,
"interrupted reading the data sem");
ret = -1;
break;
}
/* Have data, use it */
if (conn->data_len > 0)
ret = connection_read_data_msg(conn, buffer, len);
mutex_unlock(&(conn->data_lock));
/* There is still some data left */
if (conn->data_len > 0)
up(&conn->data_available_sem);
} while (0);
return ret;
}
size_t connection_write_data(struct connection *conn, void *buffer,
uint32_t len)
{
struct sk_buff *skb = NULL;
struct nlmsghdr *nlh;
int ret = 0;
MCDRV_DBG_VERBOSE(mc_kapi, "buffer length %u from pid %u\n",
len, conn->sequence_magic);
do {
skb = nlmsg_new(NLMSG_SPACE(len), GFP_KERNEL);
if (!skb) {
ret = -1;
break;
}
nlh = nlmsg_put(skb, 0, conn->sequence_magic, 2,
NLMSG_LENGTH(len), NLM_F_REQUEST);
if (!nlh) {
ret = -1;
break;
}
memcpy(NLMSG_DATA(nlh), buffer, len);
netlink_unicast(conn->socket_descriptor, skb,
conn->peer_pid, MSG_DONTWAIT);
ret = len;
} while (0);
if (!ret && skb != NULL)
kfree_skb(skb);
return ret;
}
int connection_process(struct connection *conn, struct sk_buff *skb)
{
int ret = 0;
do {
if (mutex_lock_interruptible(&(conn->data_lock))) {
MCDRV_DBG_ERROR(mc_kapi,
"Interrupted getting data semaphore!");
ret = -1;
break;
}
kfree_skb(conn->skb);
/* Get a reference to the incoming skb */
conn->skb = skb_get(skb);
if (conn->skb) {
conn->data_msg = nlmsg_hdr(conn->skb);
conn->data_len = NLMSG_PAYLOAD(conn->data_msg, 0);
conn->data_start = NLMSG_DATA(conn->data_msg);
up(&(conn->data_available_sem));
}
mutex_unlock(&(conn->data_lock));
ret = 0;
} while (0);
return ret;
}