1361 lines
30 KiB
C
1361 lines
30 KiB
C
|
/*
|
||
|
* Copyright (C) 2012-2017, Samsung Electronics Co., Ltd.
|
||
|
*
|
||
|
* This software is licensed under the terms of the GNU General Public
|
||
|
* License version 2, as published by the Free Software Foundation, and
|
||
|
* may be copied, distributed, and modified under those terms.
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
#include <linux/crypto.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/mutex.h>
|
||
|
#include <linux/socket.h>
|
||
|
#include <linux/vmalloc.h>
|
||
|
#include <linux/workqueue.h>
|
||
|
|
||
|
#include "lib/circ_buf.h"
|
||
|
#include "lib/circ_buf_packet.h"
|
||
|
|
||
|
#include <tzdev/tzdev.h>
|
||
|
|
||
|
#include "sysdep.h"
|
||
|
#include "tzdev.h"
|
||
|
#include "tz_cred.h"
|
||
|
#include "tz_iwio.h"
|
||
|
#include "tz_iwsock.h"
|
||
|
#include "tz_kthread_pool.h"
|
||
|
|
||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
|
||
|
#include <linux/sched.h>
|
||
|
#else
|
||
|
#include <linux/sched/signal.h>
|
||
|
#endif
|
||
|
|
||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)
|
||
|
#define tz_iwsock_wait_event(wq, condition) \
|
||
|
wait_event(wq, condition)
|
||
|
|
||
|
#define tz_iwsock_wait_event_interruptible(wq, condition) \
|
||
|
wait_event_interruptible(wq, condition)
|
||
|
#else
|
||
|
#define tz_iwsock_wait_event(wq, condition) \
|
||
|
({ \
|
||
|
DEFINE_WAIT_FUNC(wait, woken_wake_function); \
|
||
|
add_wait_queue(&wq, &wait); \
|
||
|
while (!(condition)) \
|
||
|
wait_woken(&wait, TASK_UNINTERRUPTIBLE, \
|
||
|
MAX_SCHEDULE_TIMEOUT); \
|
||
|
remove_wait_queue(&wq, &wait); \
|
||
|
})
|
||
|
|
||
|
#define tz_iwsock_wait_event_interruptible(wq, condition) \
|
||
|
({ \
|
||
|
DEFINE_WAIT_FUNC(wait, woken_wake_function); \
|
||
|
long __ret = 0; \
|
||
|
add_wait_queue(&wq, &wait); \
|
||
|
while (!(condition)) { \
|
||
|
if (signal_pending(current)) { \
|
||
|
__ret = -ERESTARTSYS; \
|
||
|
break; \
|
||
|
} \
|
||
|
wait_woken(&wait, TASK_INTERRUPTIBLE, \
|
||
|
MAX_SCHEDULE_TIMEOUT); \
|
||
|
} \
|
||
|
remove_wait_queue(&wq, &wait); \
|
||
|
__ret; \
|
||
|
})
|
||
|
#endif
|
||
|
|
||
|
#define IWD_SOCKET_EVENTS_BUF_NUM_PAGES 1
|
||
|
|
||
|
#define DEFAULT_MAX_MSG_SIZE 0
|
||
|
#define DEFAULT_OOB_BUFFER_SIZE 128
|
||
|
|
||
|
/* Socket's transmission buffer layout.
|
||
|
*
|
||
|
* [.....write.buffer.....][.....read.buffer.....][..oob.write.buffer..]
|
||
|
* |<-----------------------------page-size--------------------------->|
|
||
|
*
|
||
|
* DEFAULT_TRANSMIT_BUFFER_SIZE defines default size of write and read buffers
|
||
|
* available for transmitting data calculated to fit one page.
|
||
|
*/
|
||
|
#define DEFAULT_TRANSMIT_BUFFER_SIZE \
|
||
|
round_down( \
|
||
|
(PAGE_SIZE - \
|
||
|
3 * round_up(CIRC_BUF_META_SIZE, sizeof(int32_t)) - \
|
||
|
2 * sizeof(int) - DEFAULT_OOB_BUFFER_SIZE) / 2, \
|
||
|
sizeof(int32_t))
|
||
|
|
||
|
#define IWD_EVENTS_BUFFER_SIZE \
|
||
|
round_down( \
|
||
|
IWD_SOCKET_EVENTS_BUF_NUM_PAGES * PAGE_SIZE / 2 - \
|
||
|
CIRC_BUF_META_SIZE, \
|
||
|
sizeof(int32_t))
|
||
|
|
||
|
#define IWD_EVENTS_ALLOCATION_BUFFER_SIZE \
|
||
|
round_up(IWD_EVENTS_BUFFER_SIZE + \
|
||
|
CIRC_BUF_EMPTY_FLAG_SIZE, sizeof(int32_t))
|
||
|
|
||
|
#define INTERNAL_EVENTS_BUF_SIZE 512
|
||
|
|
||
|
/* Macros used for connection of circ buffers inside of shared socket's buffer */
|
||
|
#define GET_SOCKET_SEND_BUF(iwd_buf) \
|
||
|
(struct circ_buf *)((char *)iwd_buf + sizeof(struct iwd_sock_buf_head))
|
||
|
|
||
|
#define GET_SOCKET_RECEIVE_BUF(iwd_buf, rcv_buf_size) \
|
||
|
(struct circ_buf *)((char *)iwd_buf + \
|
||
|
sizeof(struct iwd_sock_buf_head) + \
|
||
|
circ_buf_total_size(rcv_buf_size))
|
||
|
|
||
|
#define GET_SOCKET_OOB_BUF(iwd_buf, rcv_buf_size, snd_buf_size) \
|
||
|
(struct circ_buf *)((char *)iwd_buf + \
|
||
|
sizeof(struct iwd_sock_buf_head) + \
|
||
|
circ_buf_total_size(rcv_buf_size) + \
|
||
|
circ_buf_total_size(snd_buf_size))
|
||
|
|
||
|
enum operation {
|
||
|
CONNECT_FROM_NWD,
|
||
|
NEW_NWD_LISTEN,
|
||
|
ACCEPT_FROM_NWD
|
||
|
};
|
||
|
|
||
|
enum state {
|
||
|
NOT_INITIALIZED,
|
||
|
READY,
|
||
|
RELEASED
|
||
|
};
|
||
|
|
||
|
struct iwd_sock_events_buf {
|
||
|
struct circ_buf nwd_events_head;
|
||
|
char nwd_events_buf[IWD_EVENTS_ALLOCATION_BUFFER_SIZE];
|
||
|
|
||
|
struct circ_buf swd_events_head;
|
||
|
char swd_events_buf[IWD_EVENTS_ALLOCATION_BUFFER_SIZE];
|
||
|
} __packed;
|
||
|
|
||
|
struct iwd_sock_connect_request {
|
||
|
int32_t swd_id;
|
||
|
uint32_t snd_buf_size;
|
||
|
uint32_t rcv_buf_size;
|
||
|
uint32_t oob_buf_size;
|
||
|
uint32_t max_msg_size;
|
||
|
} __packed;
|
||
|
|
||
|
struct connect_ext_data {
|
||
|
enum operation op;
|
||
|
struct sock_desc *sd;
|
||
|
const char *name;
|
||
|
int swd_id;
|
||
|
};
|
||
|
|
||
|
static DEFINE_IDR(tzdev_sock_map);
|
||
|
static DEFINE_MUTEX(tzdev_sock_map_lock);
|
||
|
|
||
|
static struct iwd_sock_events_buf *sock_events;
|
||
|
static struct circ_buf_desc nwd_sock_events;
|
||
|
static struct circ_buf_desc swd_sock_events;
|
||
|
|
||
|
static DEFINE_SPINLOCK(internal_events_in_lock);
|
||
|
static DECLARE_WAIT_QUEUE_HEAD(internal_events_wq);
|
||
|
|
||
|
static struct circ_buf_desc internal_events_in;
|
||
|
static struct circ_buf_desc internal_events_out;
|
||
|
|
||
|
static DEFINE_MUTEX(nwd_events_list_lock);
|
||
|
|
||
|
static DECLARE_WAIT_QUEUE_HEAD(tz_iwsock_wq);
|
||
|
static DECLARE_WAIT_QUEUE_HEAD(tz_iwsock_full_event_buf_wq);
|
||
|
|
||
|
static struct task_struct *notification_kthread;
|
||
|
|
||
|
static unsigned int tz_iwsock_state = NOT_INITIALIZED;
|
||
|
|
||
|
static void tz_iwsock_free_resources(struct sock_desc *sd)
|
||
|
{
|
||
|
tz_iwio_free_iw_channel(sd->iwd_buf);
|
||
|
|
||
|
kfree(sd);
|
||
|
}
|
||
|
|
||
|
static void tz_iwsock_get_sd(struct sock_desc *sd)
|
||
|
{
|
||
|
atomic_inc(&sd->ref_count);
|
||
|
}
|
||
|
|
||
|
static struct sock_desc *tz_iwsock_get_sd_by_sid(unsigned int sid)
|
||
|
{
|
||
|
struct sock_desc *sd;
|
||
|
|
||
|
mutex_lock(&tzdev_sock_map_lock);
|
||
|
sd = idr_find(&tzdev_sock_map, sid);
|
||
|
if (sd)
|
||
|
tz_iwsock_get_sd(sd);
|
||
|
mutex_unlock(&tzdev_sock_map_lock);
|
||
|
|
||
|
return sd;
|
||
|
}
|
||
|
|
||
|
void tz_iwsock_put_sd(struct sock_desc *sd)
|
||
|
{
|
||
|
if (atomic_sub_return(1, &sd->ref_count) == 0)
|
||
|
tz_iwsock_free_resources(sd);
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_publish_sd(struct sock_desc *sd)
|
||
|
{
|
||
|
int sid;
|
||
|
|
||
|
mutex_lock(&tzdev_sock_map_lock);
|
||
|
|
||
|
sid = sysdep_idr_alloc(&tzdev_sock_map, sd);
|
||
|
if (sid > 0) {
|
||
|
sd->id = sid;
|
||
|
tz_iwsock_get_sd(sd);
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&tzdev_sock_map_lock);
|
||
|
|
||
|
return sid;
|
||
|
}
|
||
|
|
||
|
static void tz_iwsock_unpublish_sd(struct sock_desc *sd)
|
||
|
{
|
||
|
mutex_lock(&tzdev_sock_map_lock);
|
||
|
idr_remove(&tzdev_sock_map, sd->id);
|
||
|
mutex_unlock(&tzdev_sock_map_lock);
|
||
|
|
||
|
tz_iwsock_put_sd(sd);
|
||
|
}
|
||
|
|
||
|
unsigned long circ_buf_total_size(unsigned long size)
|
||
|
{
|
||
|
return round_up(size + CIRC_BUF_META_SIZE, sizeof(u32));
|
||
|
}
|
||
|
|
||
|
static unsigned long connected_iwd_buf_total_size(struct sock_desc *sd)
|
||
|
{
|
||
|
return round_up(sizeof(struct iwd_sock_buf_head) +
|
||
|
circ_buf_total_size(sd->snd_buf_size) +
|
||
|
circ_buf_total_size(sd->rcv_buf_size) +
|
||
|
circ_buf_total_size(sd->oob_buf_size), PAGE_SIZE);
|
||
|
}
|
||
|
|
||
|
static unsigned long listen_iwd_buf_total_size(struct sock_desc *sd)
|
||
|
{
|
||
|
return round_up(sizeof(struct iwd_sock_buf_head) +
|
||
|
circ_buf_total_size(sd->snd_buf_size) +
|
||
|
circ_buf_total_size(sd->rcv_buf_size), PAGE_SIZE);
|
||
|
}
|
||
|
static int tz_iwsock_pre_connect_callback(void *buf,
|
||
|
unsigned long num_pages, void *ext_data)
|
||
|
{
|
||
|
struct connect_ext_data *data = ext_data;
|
||
|
struct sock_desc *sd = data->sd;
|
||
|
struct iwd_sock_buf_head *iwd_buf = buf;
|
||
|
struct circ_buf *rcv_buf, *snd_buf, *oob_buf;
|
||
|
int32_t nwd_id;
|
||
|
unsigned long ret;
|
||
|
|
||
|
sd->iwd_buf = buf;
|
||
|
|
||
|
snd_buf = GET_SOCKET_SEND_BUF(iwd_buf);
|
||
|
rcv_buf = GET_SOCKET_RECEIVE_BUF(iwd_buf, sd->snd_buf_size);
|
||
|
|
||
|
/* Write buffer */
|
||
|
circ_buf_connect(&sd->write_buf, circ_buf_set(snd_buf), sd->snd_buf_size);
|
||
|
|
||
|
/* Receive buffer */
|
||
|
circ_buf_connect(&sd->read_buf, circ_buf_set(rcv_buf), sd->rcv_buf_size);
|
||
|
|
||
|
iwd_buf->nwd_state = BUF_SK_CONNECTED;
|
||
|
iwd_buf->swd_state = BUF_SK_NEW;
|
||
|
|
||
|
nwd_id = sd->id;
|
||
|
|
||
|
/* Place operation's ID into shared buffer */
|
||
|
ret = circ_buf_write_packet(&sd->write_buf, (void *)&data->op,
|
||
|
sizeof(data->op), CIRC_BUF_MODE_KERNEL);
|
||
|
if (IS_ERR_VALUE(ret))
|
||
|
return ret;
|
||
|
|
||
|
/* Place socket's ID into shared buffer */
|
||
|
ret = circ_buf_write_packet(&sd->write_buf, (void *)&nwd_id,
|
||
|
sizeof(nwd_id), CIRC_BUF_MODE_KERNEL);
|
||
|
if (IS_ERR_VALUE(ret))
|
||
|
return ret;
|
||
|
|
||
|
switch (data->op) {
|
||
|
case CONNECT_FROM_NWD:
|
||
|
case NEW_NWD_LISTEN:
|
||
|
/* Place name of the socket to connect to or listening socket name
|
||
|
* in NWd into shared buffer. */
|
||
|
ret = circ_buf_write_packet(&sd->write_buf, (char *)data->name,
|
||
|
strlen(data->name) + 1, CIRC_BUF_MODE_KERNEL);
|
||
|
if (IS_ERR_VALUE(ret))
|
||
|
return ret;
|
||
|
break;
|
||
|
|
||
|
case ACCEPT_FROM_NWD:
|
||
|
/* Place SWd ID into shared buffer to identify socket connecting
|
||
|
* from SWd */
|
||
|
ret = circ_buf_write_packet(&sd->write_buf,
|
||
|
(char *)&data->swd_id, sizeof(data->swd_id),
|
||
|
CIRC_BUF_MODE_KERNEL);
|
||
|
if (IS_ERR_VALUE(ret))
|
||
|
return ret;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Write buffers size */
|
||
|
ret = circ_buf_write_packet(&sd->write_buf, (void *)&sd->snd_buf_size,
|
||
|
sizeof(sd->snd_buf_size), CIRC_BUF_MODE_KERNEL);
|
||
|
if (IS_ERR_VALUE(ret))
|
||
|
return ret;
|
||
|
|
||
|
ret = circ_buf_write_packet(&sd->write_buf, (void *)&sd->rcv_buf_size,
|
||
|
sizeof(sd->rcv_buf_size), CIRC_BUF_MODE_KERNEL);
|
||
|
if (IS_ERR_VALUE(ret))
|
||
|
return ret;
|
||
|
|
||
|
switch (data->op) {
|
||
|
case CONNECT_FROM_NWD:
|
||
|
case ACCEPT_FROM_NWD:
|
||
|
oob_buf = GET_SOCKET_OOB_BUF(iwd_buf,
|
||
|
sd->snd_buf_size, sd->rcv_buf_size);
|
||
|
|
||
|
/* Connect OOB buffer */
|
||
|
circ_buf_connect(&sd->oob_buf,
|
||
|
circ_buf_set(oob_buf), sd->oob_buf_size);
|
||
|
|
||
|
ret = circ_buf_write_packet(&sd->write_buf,
|
||
|
(void *)&sd->oob_buf_size,
|
||
|
sizeof(sd->oob_buf_size), CIRC_BUF_MODE_KERNEL);
|
||
|
if (IS_ERR_VALUE(ret))
|
||
|
return ret;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_check_ready(void)
|
||
|
{
|
||
|
smp_rmb();
|
||
|
|
||
|
return (tz_iwsock_state == READY);
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_check_init_done(void)
|
||
|
{
|
||
|
smp_rmb();
|
||
|
|
||
|
if (tz_iwsock_state == NOT_INITIALIZED)
|
||
|
return -EAGAIN;
|
||
|
else if (tz_iwsock_state == READY)
|
||
|
return 0;
|
||
|
else
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_try_write(struct circ_buf_desc *cd,
|
||
|
void *buf, unsigned int size)
|
||
|
{
|
||
|
unsigned long ret;
|
||
|
|
||
|
if (!tz_iwsock_check_ready())
|
||
|
return -ECONNRESET;
|
||
|
|
||
|
ret = circ_buf_write(cd, (char *)buf, size, CIRC_BUF_MODE_KERNEL);
|
||
|
|
||
|
if (ret == -EAGAIN)
|
||
|
tz_kthread_pool_enter_swd();
|
||
|
else if (IS_ERR_VALUE(ret))
|
||
|
BUG();
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_try_write_swd_event(int32_t id)
|
||
|
{
|
||
|
return tz_iwsock_try_write(&nwd_sock_events, &id, sizeof(id));
|
||
|
}
|
||
|
|
||
|
static void tz_iwsock_notify_swd(int32_t sid)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
might_sleep();
|
||
|
|
||
|
smp_wmb();
|
||
|
|
||
|
mutex_lock(&nwd_events_list_lock);
|
||
|
wait_event(tz_iwsock_full_event_buf_wq,
|
||
|
(ret = tz_iwsock_try_write_swd_event(sid)) != -EAGAIN);
|
||
|
mutex_unlock(&nwd_events_list_lock);
|
||
|
|
||
|
if (ret > 0)
|
||
|
tz_kthread_pool_enter_swd();
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_try_write_internal_event(int32_t id)
|
||
|
{
|
||
|
unsigned long ret;
|
||
|
|
||
|
if (!tz_iwsock_check_ready())
|
||
|
return -ECONNRESET;
|
||
|
|
||
|
spin_lock(&internal_events_in_lock);
|
||
|
ret = circ_buf_write(&internal_events_in,
|
||
|
(char *)&id, sizeof(id), CIRC_BUF_MODE_KERNEL);
|
||
|
spin_unlock(&internal_events_in_lock);
|
||
|
|
||
|
if (ret == -EAGAIN)
|
||
|
wake_up(&tz_iwsock_wq);
|
||
|
else if (IS_ERR_VALUE(ret))
|
||
|
BUG();
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void tz_iwsock_notify_internally(int32_t sid)
|
||
|
{
|
||
|
wait_event(internal_events_wq,
|
||
|
tz_iwsock_try_write_internal_event(sid) != -EAGAIN);
|
||
|
wake_up(&tz_iwsock_wq);
|
||
|
}
|
||
|
|
||
|
struct sock_desc *tz_iwsock_socket(unsigned int is_kern)
|
||
|
{
|
||
|
struct sock_desc *sd;
|
||
|
int ret;
|
||
|
|
||
|
/* Allocate sock_desc structure */
|
||
|
sd = kmalloc(sizeof(struct sock_desc), GFP_KERNEL);
|
||
|
if (!sd)
|
||
|
return ERR_PTR(-ENOMEM);
|
||
|
|
||
|
memset(sd, 0, sizeof(struct sock_desc));
|
||
|
|
||
|
atomic_set(&sd->ref_count, 1);
|
||
|
init_waitqueue_head(&sd->wq);
|
||
|
mutex_init(&sd->lock);
|
||
|
|
||
|
sd->mm = ERR_PTR(-EINVAL);
|
||
|
|
||
|
sd->cred.cmsghdr.cmsg_len = TZ_CMSG_LEN(sizeof(struct tz_cred));
|
||
|
sd->cred.cmsghdr.cmsg_level = SOL_UNSPEC;
|
||
|
sd->cred.cmsghdr.cmsg_type = TZ_SCM_CREDENTIALS;
|
||
|
|
||
|
sd->snd_buf_size = DEFAULT_TRANSMIT_BUFFER_SIZE;
|
||
|
sd->rcv_buf_size = DEFAULT_TRANSMIT_BUFFER_SIZE;
|
||
|
sd->oob_buf_size = DEFAULT_OOB_BUFFER_SIZE;
|
||
|
sd->max_msg_size = DEFAULT_MAX_MSG_SIZE;
|
||
|
|
||
|
sd->is_kern = is_kern;
|
||
|
|
||
|
sd->state = TZ_SK_NEW;
|
||
|
|
||
|
if ((ret = tz_iwsock_publish_sd(sd)) <= 0) {
|
||
|
tz_iwsock_put_sd(sd);
|
||
|
sd = ERR_PTR(ret);
|
||
|
}
|
||
|
|
||
|
return sd;
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_copy_from(void *dst, void *src, int size, unsigned int is_kern)
|
||
|
{
|
||
|
if (is_kern) {
|
||
|
memcpy(dst, src, size);
|
||
|
return 0;
|
||
|
} else {
|
||
|
return copy_from_user(dst, src, size);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_copy_to(void *dst, void *src, int size, unsigned int is_kern)
|
||
|
{
|
||
|
if (is_kern) {
|
||
|
memcpy(dst, src, size);
|
||
|
return 0;
|
||
|
} else {
|
||
|
return copy_to_user(dst, src, size);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_getsockopt_socket(struct sock_desc *sd,
|
||
|
int optname, void *optval, socklen_t *optlen)
|
||
|
{
|
||
|
unsigned int size = sizeof(uint32_t);
|
||
|
|
||
|
switch(optname) {
|
||
|
case SO_SNDBUF:
|
||
|
if (tz_iwsock_copy_to(optlen, &size, sizeof(size), sd->is_kern))
|
||
|
return -EFAULT;
|
||
|
|
||
|
if (tz_iwsock_copy_to(optval, &sd->snd_buf_size,
|
||
|
sizeof(sd->snd_buf_size), sd->is_kern))
|
||
|
return -EFAULT;
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
case SO_RCVBUF:
|
||
|
if (tz_iwsock_copy_to(optlen, &size, sizeof(size), sd->is_kern))
|
||
|
return -EFAULT;
|
||
|
|
||
|
if (tz_iwsock_copy_to(optval, &sd->rcv_buf_size,
|
||
|
sizeof(sd->rcv_buf_size), sd->is_kern))
|
||
|
return -EFAULT;
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
default:
|
||
|
return -ENOPROTOOPT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_getsockopt_iwd(struct sock_desc *sd,
|
||
|
int optname, void *optval, socklen_t *optlen)
|
||
|
{
|
||
|
unsigned int size = sizeof(uint32_t);
|
||
|
|
||
|
switch (optname) {
|
||
|
case SO_IWD_MAX_MSG_SIZE:
|
||
|
if (tz_iwsock_copy_to(optlen, &size, sizeof(size), sd->is_kern))
|
||
|
return -EFAULT;
|
||
|
|
||
|
if (tz_iwsock_copy_to(optval, &sd->max_msg_size,
|
||
|
sizeof(sd->max_msg_size), sd->is_kern))
|
||
|
return -EFAULT;
|
||
|
|
||
|
return 0;
|
||
|
default:
|
||
|
return -ENOPROTOOPT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int tz_iwsock_getsockopt(struct sock_desc *sd, int level,
|
||
|
int optname, void *optval, socklen_t *optlen)
|
||
|
{
|
||
|
switch (level) {
|
||
|
case SOL_SOCKET:
|
||
|
return tz_iwsock_getsockopt_socket(sd, optname, optval, optlen);
|
||
|
case SOL_IWD:
|
||
|
return tz_iwsock_getsockopt_iwd(sd, optname, optval, optlen);
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_setsockopt_socket(struct sock_desc *sd,
|
||
|
int optname, void *optval, socklen_t optlen)
|
||
|
{
|
||
|
unsigned int size;
|
||
|
|
||
|
switch(optname) {
|
||
|
case SO_SNDBUF:
|
||
|
if (optlen != sizeof(unsigned int))
|
||
|
return -EINVAL;
|
||
|
if (tz_iwsock_copy_from(&size, optval, optlen, sd->is_kern))
|
||
|
return -EFAULT;
|
||
|
if (!size)
|
||
|
return -EINVAL;
|
||
|
|
||
|
sd->snd_buf_size = (unsigned int)(circ_buf_size_for_packet(size) +
|
||
|
circ_buf_size_for_packet(sizeof(struct tz_cmsghdr_cred)));
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
case SO_RCVBUF:
|
||
|
if (optlen != sizeof(unsigned int))
|
||
|
return -EINVAL;
|
||
|
if (tz_iwsock_copy_from(&size, optval, optlen, sd->is_kern))
|
||
|
return -EFAULT;
|
||
|
if (!size)
|
||
|
return -EINVAL;
|
||
|
|
||
|
sd->rcv_buf_size = (unsigned int)(circ_buf_size_for_packet(size) +
|
||
|
circ_buf_size_for_packet(sizeof(struct tz_cmsghdr_cred)));
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
default:
|
||
|
return -ENOPROTOOPT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_setsockopt_iwd(struct sock_desc *sd,
|
||
|
int optname, void *optval, socklen_t optlen)
|
||
|
{
|
||
|
unsigned int size;
|
||
|
|
||
|
switch (optname) {
|
||
|
case SO_IWD_MAX_MSG_SIZE:
|
||
|
if (optlen != sizeof(unsigned int))
|
||
|
return -EINVAL;
|
||
|
if (tz_iwsock_copy_from(&size, optval, optlen, sd->is_kern))
|
||
|
return -EFAULT;
|
||
|
|
||
|
if (size)
|
||
|
sd->max_msg_size = (unsigned int)(circ_buf_size_for_packet(size) +
|
||
|
circ_buf_size_for_packet(sizeof(struct tz_cmsghdr_cred)));
|
||
|
else
|
||
|
sd->max_msg_size = 0;
|
||
|
|
||
|
return 0;
|
||
|
default:
|
||
|
return -ENOPROTOOPT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int tz_iwsock_setsockopt(struct sock_desc *sd, int level,
|
||
|
int optname, void *optval, socklen_t optlen)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
mutex_lock(&sd->lock);
|
||
|
|
||
|
if (sd->state != TZ_SK_NEW) {
|
||
|
ret = -EBUSY;
|
||
|
goto unlock;
|
||
|
}
|
||
|
|
||
|
switch (level) {
|
||
|
case SOL_SOCKET:
|
||
|
ret = tz_iwsock_setsockopt_socket(sd, optname, optval, optlen);
|
||
|
break;
|
||
|
case SOL_IWD:
|
||
|
ret = tz_iwsock_setsockopt_iwd(sd, optname, optval, optlen);
|
||
|
break;
|
||
|
default:
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
unlock:
|
||
|
mutex_unlock(&sd->lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int tz_iwsock_connect(struct sock_desc *sd, const char *name, int flags)
|
||
|
{
|
||
|
struct connect_ext_data ext_data = {.op = CONNECT_FROM_NWD, .sd = sd, .name = name};
|
||
|
void *buf;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (unlikely(tz_iwsock_state != READY))
|
||
|
return -ENODEV;
|
||
|
|
||
|
smp_rmb();
|
||
|
|
||
|
if (strnlen(name, TZ_FILE_NAME_LEN) == TZ_FILE_NAME_LEN)
|
||
|
return -ENAMETOOLONG;
|
||
|
|
||
|
mutex_lock(&sd->lock);
|
||
|
|
||
|
switch (sd->state) {
|
||
|
case TZ_SK_CONNECTED:
|
||
|
ret = -EISCONN;
|
||
|
goto wrong_state;
|
||
|
case TZ_SK_CONNECT_IN_PROGRESS:
|
||
|
ret = -EALREADY;
|
||
|
goto wrong_state;
|
||
|
case TZ_SK_NEW:
|
||
|
sd->state = TZ_SK_CONNECT_IN_PROGRESS;
|
||
|
break;
|
||
|
case TZ_SK_LISTENING:
|
||
|
ret = -EINVAL;
|
||
|
goto wrong_state;
|
||
|
case TZ_SK_RELEASED:
|
||
|
BUG();
|
||
|
}
|
||
|
|
||
|
buf = tz_iwio_alloc_iw_channel(TZ_IWIO_CONNECT_SOCKET,
|
||
|
connected_iwd_buf_total_size(sd) / PAGE_SIZE,
|
||
|
tz_iwsock_pre_connect_callback,
|
||
|
NULL, &ext_data);
|
||
|
|
||
|
if (IS_ERR(buf)) {
|
||
|
sd->state = TZ_SK_NEW;
|
||
|
sd->iwd_buf = NULL;
|
||
|
ret = PTR_ERR(buf);
|
||
|
goto sock_connect_failed;
|
||
|
}
|
||
|
|
||
|
if (flags & MSG_DONTWAIT) {
|
||
|
smp_rmb();
|
||
|
/* Non-blocking mode */
|
||
|
if (sd->iwd_buf->swd_state != BUF_SK_CONNECTED)
|
||
|
ret = -EINPROGRESS;
|
||
|
else
|
||
|
sd->state = TZ_SK_CONNECTED;
|
||
|
} else {
|
||
|
mutex_unlock(&sd->lock);
|
||
|
|
||
|
/* Blocking mode */
|
||
|
return tz_iwsock_wait_connection(sd);
|
||
|
}
|
||
|
|
||
|
wrong_state:
|
||
|
sock_connect_failed:
|
||
|
mutex_unlock(&sd->lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_is_connection_done(struct sock_desc *sd)
|
||
|
{
|
||
|
smp_rmb();
|
||
|
|
||
|
return sd->state != TZ_SK_CONNECT_IN_PROGRESS ||
|
||
|
sd->iwd_buf->swd_state != BUF_SK_NEW;
|
||
|
}
|
||
|
|
||
|
int tz_iwsock_wait_connection(struct sock_desc *sd)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
if (unlikely(tz_iwsock_state != READY))
|
||
|
return -ENODEV;
|
||
|
|
||
|
smp_rmb();
|
||
|
|
||
|
mutex_lock(&sd->lock);
|
||
|
|
||
|
switch (sd->state) {
|
||
|
case TZ_SK_NEW:
|
||
|
case TZ_SK_LISTENING:
|
||
|
ret = -EINVAL;
|
||
|
goto unlock_sd;
|
||
|
|
||
|
case TZ_SK_CONNECT_IN_PROGRESS:
|
||
|
break;
|
||
|
|
||
|
case TZ_SK_CONNECTED:
|
||
|
goto unlock_sd;
|
||
|
|
||
|
case TZ_SK_RELEASED:
|
||
|
BUG();
|
||
|
}
|
||
|
|
||
|
if (sd->is_kern)
|
||
|
wait_event(sd->wq,
|
||
|
!tz_iwsock_check_ready() ||
|
||
|
tz_iwsock_is_connection_done(sd));
|
||
|
else
|
||
|
ret = wait_event_interruptible(sd->wq,
|
||
|
!tz_iwsock_check_ready() ||
|
||
|
tz_iwsock_is_connection_done(sd));
|
||
|
|
||
|
if (ret)
|
||
|
goto unlock_sd;
|
||
|
|
||
|
if (!tz_iwsock_check_ready()) {
|
||
|
ret = -ECONNREFUSED;
|
||
|
goto unlock_sd;
|
||
|
}
|
||
|
|
||
|
switch (sd->iwd_buf->swd_state) {
|
||
|
case BUF_SK_CONNECTED:
|
||
|
sd->state = TZ_SK_CONNECTED;
|
||
|
ret = 0;
|
||
|
break;
|
||
|
case BUF_SK_CLOSED:
|
||
|
ret = -ECONNREFUSED;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
unlock_sd:
|
||
|
mutex_unlock(&sd->lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int tz_iwsock_listen(struct sock_desc *sd, const char *name)
|
||
|
{
|
||
|
struct connect_ext_data ext_data = {.op = NEW_NWD_LISTEN, .sd = sd, .name = name};
|
||
|
void *buf;
|
||
|
unsigned int snd_buf_size = sd->snd_buf_size, rcv_buf_size = sd->rcv_buf_size;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (sd->is_kern)
|
||
|
wait_event(tz_iwsock_wq, tz_iwsock_check_init_done() != -EAGAIN);
|
||
|
else if ((ret = wait_event_interruptible(
|
||
|
tz_iwsock_wq, tz_iwsock_check_init_done() != -EAGAIN)))
|
||
|
return ret;
|
||
|
|
||
|
if (!tz_iwsock_check_ready())
|
||
|
return -ENODEV;
|
||
|
|
||
|
if (strnlen(name, TZ_FILE_NAME_LEN) == TZ_FILE_NAME_LEN)
|
||
|
return -ENAMETOOLONG;
|
||
|
|
||
|
mutex_lock(&sd->lock);
|
||
|
|
||
|
if (sd->state != TZ_SK_NEW) {
|
||
|
ret = -EBADF;
|
||
|
goto unlock;
|
||
|
}
|
||
|
|
||
|
strcpy(sd->listen_name, name);
|
||
|
|
||
|
sd->snd_buf_size = DEFAULT_TRANSMIT_BUFFER_SIZE;
|
||
|
sd->rcv_buf_size = DEFAULT_TRANSMIT_BUFFER_SIZE;
|
||
|
|
||
|
buf = tz_iwio_alloc_iw_channel(TZ_IWIO_CONNECT_SOCKET,
|
||
|
listen_iwd_buf_total_size(sd) / PAGE_SIZE,
|
||
|
tz_iwsock_pre_connect_callback, NULL, &ext_data);
|
||
|
|
||
|
if (IS_ERR(buf)) {
|
||
|
sd->iwd_buf = NULL;
|
||
|
|
||
|
sd->snd_buf_size = snd_buf_size;
|
||
|
sd->rcv_buf_size = rcv_buf_size;
|
||
|
|
||
|
ret = PTR_ERR(buf);
|
||
|
} else {
|
||
|
sd->state = TZ_SK_LISTENING;
|
||
|
}
|
||
|
|
||
|
unlock:
|
||
|
mutex_unlock(&sd->lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
struct sock_desc *tz_iwsock_accept(struct sock_desc *sd)
|
||
|
{
|
||
|
struct connect_ext_data ext_data = {.op = ACCEPT_FROM_NWD};
|
||
|
struct iwd_sock_connect_request conn_req;
|
||
|
struct sock_desc *new_sd, *ret;
|
||
|
void *iwd_buf;
|
||
|
int res;
|
||
|
|
||
|
if (unlikely(tz_iwsock_state != READY))
|
||
|
return ERR_PTR(-ENODEV);
|
||
|
|
||
|
smp_rmb();
|
||
|
|
||
|
mutex_lock(&sd->lock);
|
||
|
if (sd->is_kern)
|
||
|
wait_event(sd->wq, !tz_iwsock_check_ready() ||
|
||
|
sd->state != TZ_SK_LISTENING ||
|
||
|
!circ_buf_is_empty(&sd->read_buf));
|
||
|
else if ((res = wait_event_interruptible(sd->wq,
|
||
|
!tz_iwsock_check_ready() ||
|
||
|
sd->state != TZ_SK_LISTENING ||
|
||
|
!circ_buf_is_empty(&sd->read_buf)))) {
|
||
|
mutex_unlock(&sd->lock);
|
||
|
return ERR_PTR(res);
|
||
|
}
|
||
|
|
||
|
if (!tz_iwsock_check_ready() || sd->state != TZ_SK_LISTENING) {
|
||
|
mutex_unlock(&sd->lock);
|
||
|
return ERR_PTR(-EINVAL);
|
||
|
}
|
||
|
|
||
|
res = circ_buf_read(&sd->read_buf, (char *)&conn_req,
|
||
|
sizeof(struct iwd_sock_connect_request), CIRC_BUF_MODE_KERNEL);
|
||
|
mutex_unlock(&sd->lock);
|
||
|
|
||
|
if (res != sizeof(struct iwd_sock_connect_request))
|
||
|
return ERR_PTR(res);
|
||
|
|
||
|
new_sd = tz_iwsock_socket(sd->is_kern);
|
||
|
if (IS_ERR(new_sd)) {
|
||
|
ret = new_sd;
|
||
|
goto socket_allocation_failed;
|
||
|
}
|
||
|
|
||
|
new_sd->snd_buf_size = (unsigned int)(circ_buf_size_for_packet(conn_req.snd_buf_size) +
|
||
|
circ_buf_size_for_packet(sizeof(struct tz_cmsghdr_cred)));
|
||
|
new_sd->rcv_buf_size = (unsigned int)(circ_buf_size_for_packet(conn_req.rcv_buf_size) +
|
||
|
circ_buf_size_for_packet(sizeof(struct tz_cmsghdr_cred)));
|
||
|
new_sd->oob_buf_size = (unsigned int)(circ_buf_size_for_packet(conn_req.oob_buf_size) +
|
||
|
circ_buf_size_for_packet(sizeof(struct tz_cmsghdr_cred)));
|
||
|
|
||
|
if (conn_req.max_msg_size)
|
||
|
new_sd->max_msg_size = (unsigned int)(circ_buf_size_for_packet(conn_req.max_msg_size) +
|
||
|
circ_buf_size_for_packet(sizeof(struct tz_cmsghdr_cred)));
|
||
|
|
||
|
ext_data.swd_id = conn_req.swd_id;
|
||
|
|
||
|
ext_data.sd = new_sd;
|
||
|
|
||
|
iwd_buf = tz_iwio_alloc_iw_channel(TZ_IWIO_CONNECT_SOCKET,
|
||
|
connected_iwd_buf_total_size(new_sd) / PAGE_SIZE,
|
||
|
tz_iwsock_pre_connect_callback,
|
||
|
NULL, &ext_data);
|
||
|
if (IS_ERR(iwd_buf)) {
|
||
|
new_sd->iwd_buf = NULL;
|
||
|
ret = iwd_buf;
|
||
|
tzdev_print(0, "accept from NWd failed, err=%ld\n", PTR_ERR(iwd_buf));
|
||
|
goto iwio_connect_failed;
|
||
|
}
|
||
|
|
||
|
/* Mark new socket as connected */
|
||
|
new_sd->state = TZ_SK_CONNECTED;
|
||
|
|
||
|
ret = new_sd;
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
iwio_connect_failed:
|
||
|
tz_iwsock_release(new_sd);
|
||
|
|
||
|
socket_allocation_failed:
|
||
|
/* Write swd_id to unblock waiting threads in SWd */
|
||
|
mutex_lock(&sd->lock);
|
||
|
wait_event(sd->wq, tz_iwsock_try_write(&sd->write_buf,
|
||
|
&conn_req.swd_id, sizeof(conn_req.swd_id)) != -EAGAIN);
|
||
|
mutex_unlock(&sd->lock);
|
||
|
|
||
|
tz_iwsock_notify_swd(sd->id);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void tz_iwsock_release(struct sock_desc *sd)
|
||
|
{
|
||
|
unsigned int notify_swd = 0, notify_nwd = 0;
|
||
|
struct iwd_sock_connect_request conn_req;
|
||
|
|
||
|
mutex_lock(&sd->lock);
|
||
|
|
||
|
switch (sd->state) {
|
||
|
case TZ_SK_NEW:
|
||
|
tz_iwsock_unpublish_sd(sd);
|
||
|
break;
|
||
|
|
||
|
case TZ_SK_LISTENING:
|
||
|
/* Mark NWd state as closed to avoid new connect requests */
|
||
|
sd->iwd_buf->nwd_state = BUF_SK_CLOSED;
|
||
|
|
||
|
smp_mb();
|
||
|
|
||
|
/* Read all requests and write IDs back to wake waiting threads */
|
||
|
while (sd->iwd_buf->swd_state != BUF_SK_CLOSED &&
|
||
|
circ_buf_read(&sd->read_buf, (char *)&conn_req,
|
||
|
sizeof(conn_req), CIRC_BUF_MODE_KERNEL) > 0) {
|
||
|
wait_event(sd->wq, tz_iwsock_try_write(&sd->write_buf,
|
||
|
(char *)&conn_req.swd_id, sizeof(conn_req.swd_id)) != -EAGAIN);
|
||
|
}
|
||
|
|
||
|
/* Listen socket has connected IWd buffer, so proceed to check
|
||
|
* it's state. */
|
||
|
case TZ_SK_CONNECT_IN_PROGRESS:
|
||
|
case TZ_SK_CONNECTED:
|
||
|
/* Mark NWd state as closed */
|
||
|
sd->iwd_buf->nwd_state = BUF_SK_CLOSED;
|
||
|
|
||
|
smp_mb();
|
||
|
|
||
|
if (sd->iwd_buf->swd_state != BUF_SK_CLOSED)
|
||
|
notify_swd = 1;
|
||
|
else
|
||
|
notify_nwd = 1;
|
||
|
|
||
|
break;
|
||
|
|
||
|
case TZ_SK_RELEASED:
|
||
|
BUG();
|
||
|
}
|
||
|
|
||
|
sd->state = TZ_SK_RELEASED;
|
||
|
|
||
|
mutex_unlock(&sd->lock);
|
||
|
|
||
|
if (notify_swd)
|
||
|
tz_iwsock_notify_swd(sd->id);
|
||
|
else if (notify_nwd)
|
||
|
tz_iwsock_notify_internally(sd->id);
|
||
|
|
||
|
tz_iwsock_put_sd(sd);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static int __tz_iwsock_read(struct sock_desc *sd, struct circ_buf_desc *read_buf,
|
||
|
void *buf1, size_t len1, void *buf2, size_t len2, int flags,
|
||
|
unsigned int *need_notify)
|
||
|
{
|
||
|
int mode = (sd->is_kern) ? CIRC_BUF_MODE_KERNEL : CIRC_BUF_MODE_USER;
|
||
|
int swd_state;
|
||
|
unsigned long ret;
|
||
|
size_t nbytes;
|
||
|
|
||
|
if (unlikely(tz_iwsock_state != READY))
|
||
|
return -ENODEV;
|
||
|
|
||
|
mutex_lock(&sd->lock);
|
||
|
|
||
|
switch (sd->state) {
|
||
|
case TZ_SK_NEW:
|
||
|
case TZ_SK_LISTENING:
|
||
|
case TZ_SK_CONNECT_IN_PROGRESS:
|
||
|
ret = -ENOTCONN;
|
||
|
goto unlock;
|
||
|
case TZ_SK_CONNECTED:
|
||
|
break;
|
||
|
case TZ_SK_RELEASED:
|
||
|
BUG();
|
||
|
}
|
||
|
|
||
|
swd_state = sd->iwd_buf->swd_state;
|
||
|
|
||
|
smp_rmb();
|
||
|
|
||
|
ret = circ_buf_read_packet_local(read_buf,
|
||
|
(char *)buf1, len1, CIRC_BUF_MODE_KERNEL);
|
||
|
if (IS_ERR_VALUE(ret))
|
||
|
goto recheck;
|
||
|
|
||
|
ret = circ_buf_read_packet(read_buf,
|
||
|
buf2, len2, mode);
|
||
|
if (IS_ERR_VALUE(ret))
|
||
|
circ_buf_rollback_read(&sd->read_buf);
|
||
|
|
||
|
if (sd->max_msg_size) {
|
||
|
nbytes = circ_buf_size_for_packet(sd->max_msg_size) * 2;
|
||
|
|
||
|
if (circ_buf_bytes_free(read_buf) < nbytes)
|
||
|
*need_notify = 1;
|
||
|
} else {
|
||
|
*need_notify = 1;
|
||
|
}
|
||
|
|
||
|
recheck:
|
||
|
if (ret == -EAGAIN && swd_state == BUF_SK_CLOSED)
|
||
|
ret = 0;
|
||
|
|
||
|
unlock:
|
||
|
mutex_unlock(&sd->lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ssize_t tz_iwsock_read(struct sock_desc *sd, void *buf, size_t count, int flags)
|
||
|
{
|
||
|
struct tz_cmsghdr_cred cred;
|
||
|
int res = 0, ret = 0;
|
||
|
unsigned int need_notify = 0;
|
||
|
|
||
|
if (sd->is_kern)
|
||
|
tz_iwsock_wait_event(sd->wq,
|
||
|
(ret = __tz_iwsock_read(sd, &sd->read_buf, &cred,
|
||
|
sizeof(cred), buf, count, flags,
|
||
|
&need_notify)) != -EAGAIN);
|
||
|
else
|
||
|
res = tz_iwsock_wait_event_interruptible(sd->wq,
|
||
|
(ret = __tz_iwsock_read(sd, &sd->read_buf, &cred,
|
||
|
sizeof(cred), buf, count, flags,
|
||
|
&need_notify)) != -EAGAIN);
|
||
|
|
||
|
if (res)
|
||
|
ret = res;
|
||
|
|
||
|
if (ret > 0 && need_notify)
|
||
|
tz_iwsock_notify_swd(sd->id);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_format_cred_scm(struct sock_desc *sd)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
if (sd->mm == current->mm)
|
||
|
return 0;
|
||
|
|
||
|
ret = tz_format_cred(&sd->cred.cred);
|
||
|
if (ret) {
|
||
|
tzdev_print(0, "Failed to format socket credentials %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
sd->mm = current->mm;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int __tz_iwsock_write(struct sock_desc *sd, struct circ_buf_desc *write_buf,
|
||
|
void *scm_buf, size_t scm_len, void *data_buf, size_t data_len, int flags)
|
||
|
{
|
||
|
unsigned long ret;
|
||
|
int mode = (sd->is_kern) ? CIRC_BUF_MODE_KERNEL : CIRC_BUF_MODE_USER;
|
||
|
|
||
|
if (unlikely(tz_iwsock_state != READY))
|
||
|
return -ENODEV;
|
||
|
|
||
|
mutex_lock(&sd->lock);
|
||
|
|
||
|
switch (sd->state) {
|
||
|
case TZ_SK_NEW:
|
||
|
case TZ_SK_LISTENING:
|
||
|
case TZ_SK_CONNECT_IN_PROGRESS:
|
||
|
ret = -ENOTCONN;
|
||
|
goto unlock;
|
||
|
case TZ_SK_CONNECTED:
|
||
|
break;
|
||
|
case TZ_SK_RELEASED:
|
||
|
BUG();
|
||
|
}
|
||
|
|
||
|
smp_rmb();
|
||
|
|
||
|
if (sd->iwd_buf->swd_state == BUF_SK_CLOSED) {
|
||
|
ret = -ECONNRESET;
|
||
|
goto unlock;
|
||
|
}
|
||
|
|
||
|
if (sd->max_msg_size && data_len > sd->max_msg_size) {
|
||
|
ret = -EMSGSIZE;
|
||
|
goto unlock;
|
||
|
}
|
||
|
|
||
|
ret = circ_buf_write_packet_local(write_buf,
|
||
|
(char *)scm_buf, scm_len, CIRC_BUF_MODE_KERNEL);
|
||
|
if (IS_ERR_VALUE(ret))
|
||
|
goto unlock;
|
||
|
|
||
|
ret = circ_buf_write_packet(write_buf,
|
||
|
(char *)data_buf, data_len, mode);
|
||
|
if (IS_ERR_VALUE(ret))
|
||
|
circ_buf_rollback_write(write_buf);
|
||
|
|
||
|
unlock:
|
||
|
mutex_unlock(&sd->lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ssize_t tz_iwsock_write(struct sock_desc *sd, void *buf, size_t count, int flags)
|
||
|
{
|
||
|
struct circ_buf_desc *write_buf;
|
||
|
int ret, res = 0;
|
||
|
|
||
|
write_buf = (flags & MSG_OOB) ? &sd->oob_buf : &sd->write_buf;
|
||
|
|
||
|
if ((ret = tz_iwsock_format_cred_scm(sd)))
|
||
|
return ret;
|
||
|
|
||
|
if (sd->is_kern)
|
||
|
tz_iwsock_wait_event(sd->wq,
|
||
|
(ret = __tz_iwsock_write(sd, write_buf, &sd->cred,
|
||
|
sizeof(sd->cred), buf, count, flags)) != -EAGAIN);
|
||
|
else
|
||
|
res = tz_iwsock_wait_event_interruptible(sd->wq,
|
||
|
(ret = __tz_iwsock_write(sd, write_buf, &sd->cred,
|
||
|
sizeof(sd->cred), buf, count, flags)) != -EAGAIN);
|
||
|
|
||
|
if (res)
|
||
|
ret = res;
|
||
|
|
||
|
if (ret > 0)
|
||
|
tz_iwsock_notify_swd(sd->id);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void tz_iwsock_wake_up_all(void)
|
||
|
{
|
||
|
struct sock_desc *sd;
|
||
|
int id;
|
||
|
|
||
|
mutex_lock(&tzdev_sock_map_lock);
|
||
|
idr_for_each_entry(&tzdev_sock_map, sd, id)
|
||
|
wake_up(&sd->wq);
|
||
|
mutex_unlock(&tzdev_sock_map_lock);
|
||
|
}
|
||
|
|
||
|
void tz_iwsock_kernel_panic_handler(void)
|
||
|
{
|
||
|
if (likely(tz_iwsock_state == READY)) {
|
||
|
smp_rmb();
|
||
|
|
||
|
tz_iwsock_state = RELEASED;
|
||
|
|
||
|
smp_wmb();
|
||
|
|
||
|
tz_iwsock_wake_up_all();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void tz_iwsock_check_notifications(void)
|
||
|
{
|
||
|
if (unlikely(tz_iwsock_state != READY))
|
||
|
return;
|
||
|
|
||
|
smp_rmb();
|
||
|
|
||
|
wake_up_all(&tz_iwsock_full_event_buf_wq);
|
||
|
|
||
|
if (!circ_buf_is_empty(&swd_sock_events))
|
||
|
wake_up(&tz_iwsock_wq);
|
||
|
}
|
||
|
|
||
|
static void tz_iwsock_process_notifications(void)
|
||
|
{
|
||
|
struct sock_desc *sd;
|
||
|
int swd_event_buffer_is_full;
|
||
|
int32_t id;
|
||
|
ssize_t res;
|
||
|
|
||
|
while (1) {
|
||
|
/* Events are read from this thread only, so no need of lock. */
|
||
|
swd_event_buffer_is_full =
|
||
|
circ_buf_bytes_free(&swd_sock_events) < sizeof(int32_t);
|
||
|
|
||
|
res = circ_buf_read(&swd_sock_events, (char *)&id,
|
||
|
sizeof(id), CIRC_BUF_MODE_KERNEL);
|
||
|
|
||
|
if (res != sizeof(id)) {
|
||
|
res = circ_buf_read(&internal_events_out, (char *)&id,
|
||
|
sizeof(id), CIRC_BUF_MODE_KERNEL);
|
||
|
if (res == sizeof(id))
|
||
|
wake_up(&internal_events_wq);
|
||
|
else if (res != -EAGAIN)
|
||
|
BUG();
|
||
|
}
|
||
|
|
||
|
if (res != sizeof(id))
|
||
|
break;
|
||
|
|
||
|
if (swd_event_buffer_is_full)
|
||
|
/* Request re-entry in to SWd to wake sleeping socket's notifiers.
|
||
|
* We do that because cpu mask can be zero on return to NWd. */
|
||
|
tz_kthread_pool_enter_swd();
|
||
|
|
||
|
sd = tz_iwsock_get_sd_by_sid(id);
|
||
|
if (sd) {
|
||
|
smp_rmb();
|
||
|
|
||
|
if (sd->state == TZ_SK_RELEASED &&
|
||
|
sd->iwd_buf->swd_state == BUF_SK_CLOSED)
|
||
|
tz_iwsock_unpublish_sd(sd);
|
||
|
else
|
||
|
wake_up(&sd->wq);
|
||
|
|
||
|
tz_iwsock_put_sd(sd);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_kthread(void *data)
|
||
|
{
|
||
|
(void)data;
|
||
|
|
||
|
while (!kthread_should_stop()) {
|
||
|
wait_event(tz_iwsock_wq,
|
||
|
!circ_buf_is_empty(&swd_sock_events) ||
|
||
|
!circ_buf_is_empty(&internal_events_out) ||
|
||
|
kthread_should_stop());
|
||
|
|
||
|
if (kthread_should_stop())
|
||
|
return 0;
|
||
|
|
||
|
tz_iwsock_process_notifications();
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int tz_iwsock_events_pre_connect_callback(void *buf, unsigned long num_pages, void *ext_data)
|
||
|
{
|
||
|
struct iwd_sock_events_buf *sock_events = (struct iwd_sock_events_buf *)buf;
|
||
|
|
||
|
/* Set counters in events shared buffer */
|
||
|
circ_buf_init(&sock_events->nwd_events_head);
|
||
|
circ_buf_init(&sock_events->swd_events_head);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int tz_iwsock_init(void)
|
||
|
{
|
||
|
struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
|
||
|
struct circ_buf *circ_buf;
|
||
|
int ret;
|
||
|
|
||
|
circ_buf = circ_buf_alloc(INTERNAL_EVENTS_BUF_SIZE);
|
||
|
if (!circ_buf)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
circ_buf_connect(&internal_events_in, circ_buf, INTERNAL_EVENTS_BUF_SIZE);
|
||
|
circ_buf_connect(&internal_events_out, circ_buf, INTERNAL_EVENTS_BUF_SIZE);
|
||
|
|
||
|
sock_events = tz_iwio_alloc_iw_channel(TZ_IWIO_CONNECT_SOCKET_EVENTS,
|
||
|
IWD_SOCKET_EVENTS_BUF_NUM_PAGES,
|
||
|
tz_iwsock_events_pre_connect_callback, NULL, NULL);
|
||
|
if (IS_ERR(sock_events)) {
|
||
|
ret = PTR_ERR(sock_events);
|
||
|
sock_events = NULL;
|
||
|
goto sock_events_connect_failed;
|
||
|
}
|
||
|
|
||
|
circ_buf_connect(&nwd_sock_events,
|
||
|
&sock_events->nwd_events_head, IWD_EVENTS_BUFFER_SIZE);
|
||
|
|
||
|
circ_buf_connect(&swd_sock_events,
|
||
|
&sock_events->swd_events_head, IWD_EVENTS_BUFFER_SIZE);
|
||
|
|
||
|
smp_wmb();
|
||
|
|
||
|
notification_kthread = kthread_run(tz_iwsock_kthread, NULL, "tz_iwsock");
|
||
|
if (IS_ERR(notification_kthread)) {
|
||
|
ret = PTR_ERR(notification_kthread);
|
||
|
goto kthread_start_failed;
|
||
|
}
|
||
|
|
||
|
sched_setscheduler(notification_kthread, SCHED_FIFO, ¶m);
|
||
|
|
||
|
tz_iwsock_state = READY;
|
||
|
|
||
|
smp_wmb();
|
||
|
|
||
|
wake_up(&tz_iwsock_wq);
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
kthread_start_failed:
|
||
|
tz_iwio_free_iw_channel(sock_events);
|
||
|
notification_kthread = NULL;
|
||
|
|
||
|
sock_events_connect_failed:
|
||
|
circ_buf_free(internal_events_in.circ_buf);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void tz_iwsock_fini(void)
|
||
|
{
|
||
|
if (likely(tz_iwsock_state == READY)) {
|
||
|
smp_rmb();
|
||
|
|
||
|
tz_iwsock_state = RELEASED;
|
||
|
|
||
|
smp_wmb();
|
||
|
|
||
|
tz_iwsock_wake_up_all();
|
||
|
|
||
|
wake_up(&tz_iwsock_wq);
|
||
|
wake_up(&internal_events_wq);
|
||
|
wake_up(&tz_iwsock_full_event_buf_wq);
|
||
|
|
||
|
kthread_stop(notification_kthread);
|
||
|
|
||
|
notification_kthread = NULL;
|
||
|
}
|
||
|
}
|