/* * 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 #include #include #include #include #include #include #include "lib/circ_buf.h" #include "lib/circ_buf_packet.h" #include #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 #else #include #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; } }