blob: aa8a632569bddfdac888741e9d2de4b4e017acb9 [file] [log] [blame]
/*
* Copyright (C) 2012-2018, 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/anon_inodes.h>
#include <linux/module.h>
#include <linux/poll.h>
#include "tzdev.h"
#include "tz_iwsock.h"
#define IS_EINTR(x) \
(((x) == -EINTR) \
|| ((x) == -ERESTARTSYS) \
|| ((x) == -ERESTARTNOHAND) \
|| ((x) == -ERESTARTNOINTR) \
|| ((x) == -ERESTART_RESTARTBLOCK))
static const struct file_operations tz_uiwsock_fops;
static int tz_uiwsock_open(struct inode *inode, struct file *filp)
{
struct sock_desc *sd;
sd = tz_iwsock_socket(0);
if (IS_ERR(sd)) {
tzdev_uiwsock_error("Failed to create new socket, ret=%ld\n", PTR_ERR(sd));
return PTR_ERR(sd);
}
filp->private_data = sd;
return 0;
}
static long tz_uiwsock_connect(struct file *filp, unsigned long arg)
{
long ret;
struct tz_uiwsock_connection connection;
struct sock_desc *sd = filp->private_data;
struct tz_uiwsock_connection __user *argp = (struct tz_uiwsock_connection __user *)arg;
tzdev_uiwsock_debug("Enter, filp=%pK\n", filp);
if (copy_from_user(&connection, argp, sizeof(struct tz_uiwsock_connection))) {
tzdev_uiwsock_error("Invalid user space pointer %pK, filp=%pK\n", argp, filp);
return -EFAULT;
}
connection.name[TZ_UIWSOCK_MAX_NAME_LENGTH - 1] = 0;
ret = tz_iwsock_connect(sd, connection.name, 0);
if (ret < 0) {
tzdev_uiwsock_error("Failed to connect to socket %s, filp=%pK, error %ld\n", connection.name, filp, ret);
return ret;
}
tzdev_uiwsock_debug("Exit, filp=%pK\n", filp);
return ret;
}
static long tz_uiwsock_wait_connection(struct file *filp, unsigned long arg)
{
long ret;
struct sock_desc *sd = (struct sock_desc *)filp->private_data;
ret = tz_iwsock_wait_connection(sd);
if (IS_EINTR(ret))
tzdev_uiwsock_debug("Wait interrupted, filp=%pK\n", filp);
else if (ret < 0)
tzdev_uiwsock_error("Failed to wait connection to socket, filp=%pK socket=%d ret=%ld\n",
filp, sd->id, ret);
tzdev_uiwsock_debug("Exit, filp=%pK\n", filp);
return ret;
}
static long tz_uiwsock_listen(struct file *filp, unsigned long arg)
{
long ret;
struct tz_uiwsock_connection connection;
struct sock_desc *sd = (struct sock_desc *)filp->private_data;
struct tz_uiwsock_connection __user *argp =
(struct tz_uiwsock_connection __user *)arg;
tzdev_uiwsock_debug("Enter, filp=%pK\n", filp);
if (copy_from_user(&connection, argp, sizeof(struct tz_uiwsock_connection))) {
tzdev_uiwsock_error("Invalid user space pointer %pK, filp=%pK\n",
argp, filp);
return -EFAULT;
}
connection.name[TZ_UIWSOCK_MAX_NAME_LENGTH - 1] = 0;
ret = tz_iwsock_listen(sd, connection.name);
if (ret) {
tzdev_uiwsock_error("Failed to listen, error %ld\n", ret);
return ret;
}
tzdev_uiwsock_debug("Exit, filp=%pK\n", filp);
return ret;
}
static long tz_uiwsock_accept(struct file *filp, unsigned long arg)
{
struct sock_desc *sd = filp->private_data;
struct sock_desc *new_sd;
long ret;
tzdev_uiwsock_debug("Enter, filp=%pK\n", filp);
new_sd = tz_iwsock_accept(sd);
if (IS_ERR(new_sd)) {
tzdev_uiwsock_error("Failed to accept, error %ld\n", PTR_ERR(new_sd));
return PTR_ERR(new_sd);
}
ret = anon_inode_getfd("[tz-uiwsock]", &tz_uiwsock_fops, new_sd, O_CLOEXEC);
if (ret < 0) {
tzdev_uiwsock_error("Failed to get new anon inode fd, ret=%ld\n", ret);
tz_iwsock_release(new_sd);
}
tzdev_uiwsock_debug("Exit, filp=%pK\n", filp);
return ret;
}
static long tz_uiwsock_send(struct file *filp, unsigned long arg)
{
struct tz_uiwsock_data data;
struct sock_desc *sd = (struct sock_desc *)filp->private_data;
struct tz_uiwsock_data __user *argp = (struct tz_uiwsock_data __user *)arg;
long ret;
tzdev_uiwsock_debug("Enter, filp=%pK\n", filp);
if (copy_from_user(&data, argp, sizeof(struct tz_uiwsock_data))) {
tzdev_uiwsock_error("Invalid user space pointer %pK, filp=%pK\n", argp, filp);
return -EFAULT;
}
ret = tz_iwsock_write(sd, (void __user *)(uintptr_t)data.buffer,
data.size, data.flags);
tzdev_uiwsock_debug("Exit, filp=%pK\n", filp);
return ret;
}
static long tz_uiwsock_recv(struct file *filp, unsigned long arg)
{
struct tz_uiwsock_data data;
struct sock_desc *sd = (struct sock_desc *)filp->private_data;
struct tz_uiwsock_data __user *argp = (struct tz_uiwsock_data __user *)arg;
long ret;
tzdev_uiwsock_debug("Enter, filp=%pK\n", filp);
if (copy_from_user(&data, argp, sizeof(struct tz_uiwsock_data))) {
tzdev_uiwsock_error("Invalid user space pointer %pK, filp=%pK\n", argp, filp);
return -EFAULT;
}
ret = tz_iwsock_read(sd, (void __user *)(uintptr_t)data.buffer,
data.size, data.flags);
if (ret < 0)
tzdev_uiwsock_error("Failed to read data ret=%ld, filp=%pK\n", ret, filp);
tzdev_uiwsock_debug("Exit, filp=%pK\n", filp);
return ret;
}
static long tz_uiwsock_getsockopt(struct file *filp, unsigned long arg)
{
struct sock_desc *sd = (struct sock_desc *)filp->private_data;
struct tz_uiwsock_sockopt __user *argp = (struct tz_uiwsock_sockopt __user *)arg;
struct tz_uiwsock_sockopt sockopt;
int ret;
tzdev_uiwsock_debug("Enter, filp=%pK\n", filp);
if (copy_from_user(&sockopt, argp, sizeof(struct tz_uiwsock_sockopt))) {
tzdev_uiwsock_error("Invalid user space pointer %pK, filp=%pK\n",
argp, filp);
return -EFAULT;
}
ret = tz_iwsock_getsockopt(sd, sockopt.level, sockopt.optname,
(void *)(uintptr_t)sockopt.optval,
(socklen_t *)(uintptr_t)sockopt.optlen);
tzdev_uiwsock_debug("Exit, filp=%pK\n", filp);
return ret;
}
static long tz_uiwsock_setsockopt(struct file *filp, unsigned long arg)
{
struct sock_desc *sd = (struct sock_desc *)filp->private_data;
struct tz_uiwsock_sockopt sockopt;
struct tz_uiwsock_sockopt __user *argp = (struct tz_uiwsock_sockopt __user *)arg;
int ret;
tzdev_uiwsock_debug("Enter, filp=%pK\n", filp);
if (copy_from_user(&sockopt, argp, sizeof(struct tz_uiwsock_sockopt))) {
tzdev_uiwsock_error("Invalid user space pointer %pK, filp=%pK\n",
argp, filp);
return -EFAULT;
}
ret = tz_iwsock_setsockopt(sd, sockopt.level, sockopt.optname,
(void *)(uintptr_t)sockopt.optval, sockopt.optlen);
tzdev_uiwsock_debug("Exit, filp=%pK\n", filp);
return ret;
}
static long tz_uiwsock_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
long ret;
switch (cmd) {
case TZIO_UIWSOCK_CONNECT:
ret = tz_uiwsock_connect(filp, arg);
break;
case TZIO_UIWSOCK_WAIT_CONNECTION:
ret = tz_uiwsock_wait_connection(filp, arg);
break;
case TZIO_UIWSOCK_SEND:
ret = tz_uiwsock_send(filp, arg);
break;
case TZIO_UIWSOCK_RECV:
ret = tz_uiwsock_recv(filp, arg);
break;
case TZIO_UIWSOCK_LISTEN:
ret = tz_uiwsock_listen(filp, arg);
break;
case TZIO_UIWSOCK_ACCEPT:
ret = tz_uiwsock_accept(filp, arg);
break;
case TZIO_UIWSOCK_GETSOCKOPT:
ret = tz_uiwsock_getsockopt(filp, arg);
break;
case TZIO_UIWSOCK_SETSOCKOPT:
ret = tz_uiwsock_setsockopt(filp, arg);
break;
default:
ret = -ENOTTY;
break;
}
if (IS_EINTR(ret))
ret = -ERESTARTNOINTR;
return ret;
}
static int tz_uiwsock_release(struct inode *inode, struct file *filp)
{
struct sock_desc *sd = (struct sock_desc *)filp->private_data;
(void)inode;
tzdev_uiwsock_debug("Enter, filp=%pK\n", filp);
tz_iwsock_release(sd);
tzdev_uiwsock_debug("Exit, filp=%pK\n", filp);
return 0;
}
static unsigned int tz_uiwsock_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct sock_desc *sd = filp->private_data;
poll_wait(filp, &sd->wq, wait);
if (sd->state != TZ_SK_CONNECTED && sd->state != TZ_SK_LISTENING)
return 0;
switch (sd->iwd_buf->nwd_state) {
case BUF_SK_NEW:
break;
case BUF_SK_CONNECTED:
if (!circ_buf_is_empty(&sd->read_buf))
mask |= POLLIN | POLLRDNORM;
switch(sd->iwd_buf->swd_state) {
case BUF_SK_NEW:
break;
case BUF_SK_CLOSED:
mask |= POLLHUP;
break;
case BUF_SK_CONNECTED:
if (!circ_buf_is_full(&sd->write_buf))
mask |= POLLOUT | POLLWRNORM;
if (!circ_buf_is_full(&sd->oob_buf))
mask |= POLLOUT | POLLWRBAND;
break;
}
break;
case BUF_SK_CLOSED:
BUG();
}
return mask;
}
static const struct file_operations tz_uiwsock_fops = {
.owner = THIS_MODULE,
.open = tz_uiwsock_open,
.poll = tz_uiwsock_poll,
.unlocked_ioctl = tz_uiwsock_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = tz_uiwsock_ioctl,
#endif /* CONFIG_COMPAT */
.release = tz_uiwsock_release,
};
static struct tz_cdev tz_uiwsock_cdev = {
.name = "tziwsock",
.fops = &tz_uiwsock_fops,
.owner = THIS_MODULE,
};
int tz_uiwsock_init(void)
{
int ret;
ret = tz_cdev_register(&tz_uiwsock_cdev);
if (ret) {
tzdev_print(0, "failed to register iwsock device, error=%d\n", ret);
return ret;
}
return 0;
}
void tz_uiwsock_fini(void)
{
tz_cdev_unregister(&tz_uiwsock_cdev);
}