blob: 3866fe6ff541d5677396746df5d66512cd281572 [file] [log] [blame] [edit]
#include <asm/ioctls.h>
#include <linux/io_uring/net.h>
#include <linux/errqueue.h>
#include <net/sock.h>
#include "uring_cmd.h"
static inline int io_uring_cmd_getsockopt(struct socket *sock,
struct io_uring_cmd *cmd,
unsigned int issue_flags)
{
const struct io_uring_sqe *sqe = cmd->sqe;
bool compat = !!(issue_flags & IO_URING_F_COMPAT);
int optlen, optname, level, err;
void __user *optval;
level = READ_ONCE(sqe->level);
if (level != SOL_SOCKET)
return -EOPNOTSUPP;
optval = u64_to_user_ptr(READ_ONCE(sqe->optval));
optname = READ_ONCE(sqe->optname);
optlen = READ_ONCE(sqe->optlen);
err = do_sock_getsockopt(sock, compat, level, optname,
USER_SOCKPTR(optval),
KERNEL_SOCKPTR(&optlen));
if (err)
return err;
/* On success, return optlen */
return optlen;
}
static inline int io_uring_cmd_setsockopt(struct socket *sock,
struct io_uring_cmd *cmd,
unsigned int issue_flags)
{
const struct io_uring_sqe *sqe = cmd->sqe;
bool compat = !!(issue_flags & IO_URING_F_COMPAT);
int optname, optlen, level;
void __user *optval;
sockptr_t optval_s;
optval = u64_to_user_ptr(READ_ONCE(sqe->optval));
optname = READ_ONCE(sqe->optname);
optlen = READ_ONCE(sqe->optlen);
level = READ_ONCE(sqe->level);
optval_s = USER_SOCKPTR(optval);
return do_sock_setsockopt(sock, compat, level, optname, optval_s,
optlen);
}
static bool io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk,
struct sk_buff *skb, unsigned issue_flags)
{
struct sock_exterr_skb *serr = SKB_EXT_ERR(skb);
struct io_uring_cqe cqe[2];
struct io_timespec *iots;
struct timespec64 ts;
u32 tstype, tskey;
int ret;
BUILD_BUG_ON(sizeof(struct io_uring_cqe) != sizeof(struct io_timespec));
ret = skb_get_tx_timestamp(skb, sk, &ts);
if (ret < 0)
return false;
tskey = serr->ee.ee_data;
tstype = serr->ee.ee_info;
cqe->user_data = 0;
cqe->res = tskey;
cqe->flags = IORING_CQE_F_MORE;
cqe->flags |= tstype << IORING_TIMESTAMP_TYPE_SHIFT;
if (ret == SOF_TIMESTAMPING_TX_HARDWARE)
cqe->flags |= IORING_CQE_F_TSTAMP_HW;
iots = (struct io_timespec *)&cqe[1];
iots->tv_sec = ts.tv_sec;
iots->tv_nsec = ts.tv_nsec;
return io_uring_cmd_post_mshot_cqe32(cmd, issue_flags, cqe);
}
static int io_uring_cmd_timestamp(struct socket *sock,
struct io_uring_cmd *cmd,
unsigned int issue_flags)
{
struct sock *sk = sock->sk;
struct sk_buff_head *q = &sk->sk_error_queue;
struct sk_buff *skb, *tmp;
struct sk_buff_head list;
int ret;
if (!(issue_flags & IO_URING_F_CQE32))
return -EINVAL;
ret = io_cmd_poll_multishot(cmd, issue_flags, EPOLLERR);
if (unlikely(ret))
return ret;
if (skb_queue_empty_lockless(q))
return -EAGAIN;
__skb_queue_head_init(&list);
scoped_guard(spinlock_irq, &q->lock) {
skb_queue_walk_safe(q, skb, tmp) {
/* don't support skbs with payload */
if (!skb_has_tx_timestamp(skb, sk) || skb->len)
continue;
__skb_unlink(skb, q);
__skb_queue_tail(&list, skb);
}
}
while (1) {
skb = skb_peek(&list);
if (!skb)
break;
if (!io_process_timestamp_skb(cmd, sk, skb, issue_flags))
break;
__skb_dequeue(&list);
consume_skb(skb);
}
if (!unlikely(skb_queue_empty(&list))) {
scoped_guard(spinlock_irqsave, &q->lock)
skb_queue_splice(q, &list);
}
return -EAGAIN;
}
int io_uring_cmd_sock(struct io_uring_cmd *cmd, unsigned int issue_flags)
{
struct socket *sock = cmd->file->private_data;
struct sock *sk = sock->sk;
struct proto *prot = READ_ONCE(sk->sk_prot);
int ret, arg = 0;
if (!prot || !prot->ioctl)
return -EOPNOTSUPP;
switch (cmd->cmd_op) {
case SOCKET_URING_OP_SIOCINQ:
ret = prot->ioctl(sk, SIOCINQ, &arg);
if (ret)
return ret;
return arg;
case SOCKET_URING_OP_SIOCOUTQ:
ret = prot->ioctl(sk, SIOCOUTQ, &arg);
if (ret)
return ret;
return arg;
case SOCKET_URING_OP_GETSOCKOPT:
return io_uring_cmd_getsockopt(sock, cmd, issue_flags);
case SOCKET_URING_OP_SETSOCKOPT:
return io_uring_cmd_setsockopt(sock, cmd, issue_flags);
case SOCKET_URING_OP_TX_TIMESTAMP:
return io_uring_cmd_timestamp(sock, cmd, issue_flags);
default:
return -EOPNOTSUPP;
}
}
EXPORT_SYMBOL_GPL(io_uring_cmd_sock);