/* * 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 #include #include #include #include #include #include #include #include #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); 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; } struct connection *connection_create(int socket_descriptor, pid_t dest) { struct connection *conn = connection_new(); conn->peer_pid = dest; 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; }