| #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); |