[PATCH v3 2/8] usb: mausb_host: Add link layer implementation
From: Vladimir Stankovic
Date: Thu Mar 12 2020 - 10:43:04 EST
Implemented link layer using kernel sockets. Link layer
manages network communication and provides interface for upper
layers of MA-USB stack.
Signed-off-by: Vladimir Stankovic <vladimir.stankovic@xxxxxxxxxxxxxxx>
---
drivers/usb/mausb_host/Makefile | 1 +
drivers/usb/mausb_host/ip_link.c | 374 +++++++++++++++++++++++++++++++
drivers/usb/mausb_host/ip_link.h | 87 +++++++
3 files changed, 462 insertions(+)
create mode 100644 drivers/usb/mausb_host/ip_link.c
create mode 100644 drivers/usb/mausb_host/ip_link.h
diff --git a/drivers/usb/mausb_host/Makefile
b/drivers/usb/mausb_host/Makefile
index 2e353fa0958b..19445b73b50b 100644
--- a/drivers/usb/mausb_host/Makefile
+++ b/drivers/usb/mausb_host/Makefile
@@ -8,5 +8,6 @@
obj-$(CONFIG_HOST_MAUSB) += mausb_host.o
mausb_host-y := mausb_core.o
mausb_host-y += utils.o
+mausb_host-y += ip_link.o
ccflags-y += -I$(srctree)/$(src)
diff --git a/drivers/usb/mausb_host/ip_link.c
b/drivers/usb/mausb_host/ip_link.c
new file mode 100644
index 000000000000..49b592c02210
--- /dev/null
+++ b/drivers/usb/mausb_host/ip_link.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 - 2020 DisplayLink (UK) Ltd.
+ */
+#include "ip_link.h"
+
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/socket.h>
+#include <linux/workqueue.h>
+#include <net/sock.h>
+#include <net/tcp.h>
+
+#include "utils.h"
+
+static void __mausb_ip_connect(struct work_struct *work);
+static int __mausb_ip_recv(struct mausb_ip_ctx *ip_ctx);
+static void __mausb_ip_recv_work(struct work_struct *work);
+static inline void __mausb_ip_recv_ctx_clear(struct mausb_ip_recv_ctx
+ *recv_ctx);
+static inline void __mausb_ip_recv_ctx_free(struct mausb_ip_recv_ctx
+ *recv_ctx);
+
+int mausb_init_ip_ctx(struct mausb_ip_ctx **ip_ctx,
+ struct net *net_ns,
+ char ip_addr[INET6_ADDRSTRLEN],
+ u16 port, void *context,
+ void (*fn_callback)(void *ctx, enum mausb_channel channel,
+ enum mausb_link_action act,
+ int status, void *data),
+ enum mausb_channel channel)
+{
+ struct mausb_ip_ctx *ctx;
+
+ *ip_ctx = kzalloc(sizeof(**ip_ctx), GFP_ATOMIC);
+ if (!*ip_ctx)
+ return -ENOMEM;
+
+ ctx = *ip_ctx;
+ ctx->client_socket = NULL;
+ __mausb_ip_recv_ctx_clear(&ctx->recv_ctx);
+
+ if (in4_pton(ip_addr, -1,
+ (u8 *)&ctx->dev_addr_in.sa_in.sin_addr.s_addr, -1,
+ NULL) == 1) {
+ ctx->dev_addr_in.sa_in.sin_family = AF_INET;
+ ctx->dev_addr_in.sa_in.sin_port = htons(port);
+#if IS_ENABLED(CONFIG_IPV6)
+ } else if (in6_pton(ip_addr, -1,
+ (u8 *)&ctx->dev_addr_in.sa_in6.sin6_addr.in6_u, -1,
+ NULL) == 1) {
+ ctx->dev_addr_in.sa_in6.sin6_family = AF_INET6;
+ ctx->dev_addr_in.sa_in6.sin6_port = htons(port);
+#endif
+ } else {
+ mausb_pr_err("Invalid IP address received: address=%s",
+ ip_addr);
+ kfree(ctx);
+ return -EINVAL;
+ }
+
+ ctx->net_ns = net_ns;
+
+ if (channel == MAUSB_ISOCH_CHANNEL)
+ ctx->udp = true;
+
+ ctx->connect_workq = alloc_ordered_workqueue("connect_workq",
+ WQ_MEM_RECLAIM);
+ if (!ctx->connect_workq) {
+ kfree(ctx);
+ return -ENOMEM;
+ }
+
+ ctx->recv_workq = alloc_ordered_workqueue("recv_workq", WQ_MEM_RECLAIM);
+ if (!ctx->recv_workq) {
+ destroy_workqueue(ctx->connect_workq);
+ kfree(ctx);
+ return -ENOMEM;
+ }
+
+ INIT_WORK(&ctx->connect_work, __mausb_ip_connect);
+ INIT_WORK(&ctx->recv_work, __mausb_ip_recv_work);
+
+ ctx->channel = channel;
+ ctx->ctx = context;
+ ctx->fn_callback = fn_callback;
+
+ return 0;
+}
+
+void mausb_destroy_ip_ctx(struct mausb_ip_ctx *ip_ctx)
+{
+ if (!ip_ctx)
+ return;
+
+ if (ip_ctx->connect_workq) {
+ flush_workqueue(ip_ctx->connect_workq);
+ destroy_workqueue(ip_ctx->connect_workq);
+ }
+
+ if (ip_ctx->recv_workq) {
+ flush_workqueue(ip_ctx->recv_workq);
+ destroy_workqueue(ip_ctx->recv_workq);
+ }
+ if (ip_ctx->client_socket)
+ sock_release(ip_ctx->client_socket);
+ __mausb_ip_recv_ctx_free(&ip_ctx->recv_ctx);
+
+ kfree(ip_ctx);
+}
+
+static void __mausb_ip_set_options(struct socket *sock, bool udp)
+{
+ u32 optval = 0;
+ unsigned int optlen = sizeof(optval);
+ int status = 0;
+ struct __kernel_sock_timeval timeo = {.tv_sec = 0, .tv_usec = 500000U };
+ struct __kernel_sock_timeval send_timeo = {.tv_sec = 1, .tv_usec = 0 };
+
+ if (!udp) {
+ optval = 1;
+ status = kernel_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
+ (char *)&optval, optlen);
+ if (status < 0)
+ mausb_pr_warn("Failed to set tcp no delay option: status=%d",
+ status);
+ }
+
+ status = kernel_setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO_NEW,
+ (char *)&timeo, sizeof(timeo));
+ if (status < 0)
+ mausb_pr_warn("Failed to set recv timeout option: status=%d",
+ status);
+
+ status = kernel_setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO_NEW,
+ (char *)&send_timeo, sizeof(send_timeo));
+ if (status < 0)
+ mausb_pr_warn("Failed to set snd timeout option: status=%d",
+ status);
+
+ optval = MAUSB_LINK_BUFF_SIZE;
+ status = kernel_setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
+ (char *)&optval, optlen);
+ if (status < 0)
+ mausb_pr_warn("Failed to set recv buffer size: status=%d",
+ status);
+
+ optval = MAUSB_LINK_BUFF_SIZE;
+ status = kernel_setsockopt(sock, SOL_SOCKET, SO_SNDBUF,
+ (char *)&optval, optlen);
+ if (status < 0)
+ mausb_pr_warn("Failed to set send buffer size: status=%d",
+ status);
+
+ optval = MAUSB_LINK_TOS_LEVEL_EF;
+ status = kernel_setsockopt(sock, IPPROTO_IP, IP_TOS,
+ (char *)&optval, optlen);
+ if (status < 0)
+ mausb_pr_warn("Failed to set QOS: status=%d", status);
+}
+
+static void __mausb_ip_connect(struct work_struct *work)
+{
+ int status = 0;
+ struct sockaddr *sa;
+ int sa_size;
+ struct mausb_ip_ctx *ip_ctx = container_of(work, struct mausb_ip_ctx,
+ connect_work);
+ unsigned short int family = ip_ctx->dev_addr_in.sa_in.sin_family;
+
+ if (!ip_ctx->udp) {
+ status = sock_create_kern(ip_ctx->net_ns, family, SOCK_STREAM,
+ IPPROTO_TCP, &ip_ctx->client_socket);
+ if (status < 0) {
+ mausb_pr_err("Failed to create socket: status=%d",
+ status);
+ goto callback;
+ }
+ } else {
+ status = sock_create_kern(ip_ctx->net_ns, family, SOCK_DGRAM,
+ IPPROTO_UDP, &ip_ctx->client_socket);
+ if (status < 0) {
+ mausb_pr_err("Failed to create socket: status=%d",
+ status);
+ goto callback;
+ }
+ }
+
+ __mausb_ip_set_options((struct socket *)ip_ctx->client_socket,
+ ip_ctx->udp);
+
+ if (family == AF_INET) {
+ sa = (struct sockaddr *)&ip_ctx->dev_addr_in.sa_in;
+ sa_size = sizeof(ip_ctx->dev_addr_in.sa_in);
+ mausb_pr_info("Connecting to %pI4:%d, status=%d",
+ &ip_ctx->dev_addr_in.sa_in.sin_addr,
+ htons(ip_ctx->dev_addr_in.sa_in.sin_port),
+ status);
+#if IS_ENABLED(CONFIG_IPV6)
+ } else if (family == AF_INET6) {
+ sa = (struct sockaddr *)&ip_ctx->dev_addr_in.sa_in6;
+ sa_size = sizeof(ip_ctx->dev_addr_in.sa_in6);
+ mausb_pr_info("Connecting to %pI6c:%d, status=%d",
+ &ip_ctx->dev_addr_in.sa_in6.sin6_addr,
+ htons(ip_ctx->dev_addr_in.sa_in6.sin6_port),
+ status);
+#endif
+ } else {
+ mausb_pr_err("Wrong network family provided");
+ status = -EINVAL;
+ goto callback;
+ }
+
+ status = kernel_connect(ip_ctx->client_socket, sa, sa_size, O_RDWR);
+ if (status < 0) {
+ mausb_pr_err("Failed to connect to host, status=%d", status);
+ goto clear_socket;
+ }
+
+ queue_work(ip_ctx->recv_workq, &ip_ctx->recv_work);
+
+ goto callback;
+
+clear_socket:
+ sock_release(ip_ctx->client_socket);
+ ip_ctx->client_socket = NULL;
+callback:
+ ip_ctx->fn_callback(ip_ctx->ctx, ip_ctx->channel, MAUSB_LINK_CONNECT,
+ status, NULL);
+}
+
+void mausb_ip_connect_async(struct mausb_ip_ctx *ip_ctx)
+{
+ queue_work(ip_ctx->connect_workq, &ip_ctx->connect_work);
+}
+
+int mausb_ip_disconnect(struct mausb_ip_ctx *ip_ctx)
+{
+ if (ip_ctx && ip_ctx->client_socket)
+ return kernel_sock_shutdown(ip_ctx->client_socket, SHUT_RDWR);
+ return 0;
+}
+
+int mausb_ip_send(struct mausb_ip_ctx *ip_ctx,
+ struct mausb_kvec_data_wrapper *wrapper)
+{
+ struct msghdr msghd;
+
+ if (!ip_ctx) {
+ mausb_pr_alert("Socket ctx is NULL!");
+ return -EINVAL;
+ }
+
+ memset(&msghd, 0, sizeof(msghd));
+ msghd.msg_flags = MSG_WAITALL;
+
+ return kernel_sendmsg(ip_ctx->client_socket, &msghd, wrapper->kvec,
+ wrapper->kvec_num, wrapper->length);
+}
+
+static inline void __mausb_ip_recv_ctx_clear(struct mausb_ip_recv_ctx
*recv_ctx)
+{
+ recv_ctx->buffer = NULL;
+ recv_ctx->left = 0;
+ recv_ctx->received = 0;
+}
+
+static inline void __mausb_ip_recv_ctx_free(struct mausb_ip_recv_ctx
*recv_ctx)
+{
+ kfree(recv_ctx->buffer);
+ __mausb_ip_recv_ctx_clear(recv_ctx);
+}
+
+static int __mausb_ip_recv(struct mausb_ip_ctx *ip_ctx)
+{
+ struct msghdr msghd;
+ struct kvec vec;
+ int status;
+ bool peek = true;
+ unsigned int optval = 1;
+ struct socket *client_socket = (struct socket *)ip_ctx->client_socket;
+
+ /* receive with timeout of 0.5s */
+ while (true) {
+ memset(&msghd, 0, sizeof(msghd));
+ if (peek) {
+ vec.iov_base = ip_ctx->recv_ctx.common_hdr;
+ vec.iov_len = sizeof(ip_ctx->recv_ctx.common_hdr);
+ msghd.msg_flags = MSG_PEEK;
+ } else {
+ vec.iov_base =
+ ip_ctx->recv_ctx.buffer +
+ ip_ctx->recv_ctx.received;
+ vec.iov_len = ip_ctx->recv_ctx.left;
+ msghd.msg_flags = MSG_WAITALL;
+ }
+
+ if (!ip_ctx->udp) {
+ status = kernel_setsockopt(client_socket, IPPROTO_TCP,
+ TCP_QUICKACK,
+ (char *)&optval,
+ sizeof(optval));
+ if (status != 0) {
+ mausb_pr_warn("Setting TCP_QUICKACK failed, status=%d",
+ status);
+ }
+ }
+
+ status = kernel_recvmsg(client_socket, &msghd, &vec, 1,
+ vec.iov_len, (int)msghd.msg_flags);
+ if (status == -EAGAIN) {
+ return -EAGAIN;
+ } else if (status <= 0) {
+ mausb_pr_warn("kernel_recvmsg, status=%d", status);
+
+ __mausb_ip_recv_ctx_free(&ip_ctx->recv_ctx);
+ ip_ctx->fn_callback(ip_ctx->ctx, ip_ctx->channel,
+ MAUSB_LINK_RECV, status, NULL);
+ return status;
+ }
+
+ mausb_pr_debug("kernel_recvmsg, status=%d", status);
+
+ if (peek) {
+ if ((unsigned int)status <
+ sizeof(ip_ctx->recv_ctx.common_hdr))
+ return -EAGAIN;
+ /* length field of mausb_common_hdr */
+ ip_ctx->recv_ctx.left =
+ *(u16 *)(&ip_ctx->recv_ctx.common_hdr[2]);
+ ip_ctx->recv_ctx.received = 0;
+ ip_ctx->recv_ctx.buffer =
+ kzalloc(ip_ctx->recv_ctx.left, GFP_KERNEL);
+ peek = false;
+ if (!ip_ctx->recv_ctx.buffer) {
+ ip_ctx->fn_callback(ip_ctx->ctx,
+ ip_ctx->channel,
+ MAUSB_LINK_RECV,
+ -ENOMEM, NULL);
+ return -ENOMEM;
+ }
+ } else {
+ if (status < ip_ctx->recv_ctx.left) {
+ ip_ctx->recv_ctx.left -= (u16)status;
+ ip_ctx->recv_ctx.received += (u16)status;
+ } else {
+ ip_ctx->fn_callback(ip_ctx->ctx,
+ ip_ctx->channel,
+ MAUSB_LINK_RECV, status,
+ ip_ctx->recv_ctx.buffer);
+ __mausb_ip_recv_ctx_clear(&ip_ctx->recv_ctx);
+ peek = true;
+ }
+ }
+ }
+
+ return status;
+}
+
+static void __mausb_ip_recv_work(struct work_struct *work)
+{
+ struct mausb_ip_ctx *ip_ctx = container_of(work, struct mausb_ip_ctx,
+ recv_work);
+ int status = __mausb_ip_recv(ip_ctx);
+
+ if (status <= 0 && status != -EAGAIN)
+ return;
+
+ queue_work(ip_ctx->recv_workq, &ip_ctx->recv_work);
+}
diff --git a/drivers/usb/mausb_host/ip_link.h
b/drivers/usb/mausb_host/ip_link.h
new file mode 100644
index 000000000000..5946151e4e4e
--- /dev/null
+++ b/drivers/usb/mausb_host/ip_link.h
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2019 - 2020 DisplayLink (UK) Ltd.
+ */
+#ifndef __MAUSB_IP_LINK_H__
+#define __MAUSB_IP_LINK_H__
+
+#include <linux/inet.h>
+#include <linux/list.h>
+#include <linux/workqueue.h>
+#include <net/net_namespace.h>
+
+#define MAUSB_LINK_BUFF_SIZE 16777216
+#define MAUSB_LINK_TOS_LEVEL_EF 0xB8
+
+enum mausb_link_action {
+ MAUSB_LINK_CONNECT = 0,
+ MAUSB_LINK_DISCONNECT = 1,
+ MAUSB_LINK_RECV = 2,
+ MAUSB_LINK_SEND = 3
+};
+
+enum mausb_channel {
+ MAUSB_CTRL_CHANNEL = 0,
+ MAUSB_ISOCH_CHANNEL = 1,
+ MAUSB_BULK_CHANNEL = 2,
+ MAUSB_INTR_CHANNEL = 3,
+ MAUSB_MGMT_CHANNEL = 4
+};
+
+struct mausb_kvec_data_wrapper {
+ struct kvec *kvec;
+ u32 kvec_num;
+ u32 length;
+};
+
+struct mausb_ip_recv_ctx {
+ u16 left;
+ u16 received;
+ char *buffer;
+ char common_hdr[12] __aligned(4);
+};
+
+struct mausb_ip_ctx {
+ struct socket *client_socket;
+ union {
+ struct sockaddr_in sa_in;
+#if IS_ENABLED(CONFIG_IPV6)
+ struct sockaddr_in6 sa_in6;
+#endif
+ } dev_addr_in;
+ struct net *net_ns;
+ bool udp;
+
+ /* Queues to schedule rx work */
+ struct workqueue_struct *recv_workq;
+ struct workqueue_struct *connect_workq;
+ struct work_struct recv_work;
+ struct work_struct connect_work;
+
+ struct mausb_ip_recv_ctx recv_ctx; /* recv buffer */
+
+ enum mausb_channel channel;
+ void *ctx;
+ /* callback should store task into hpal queue */
+ void (*fn_callback)(void *ctx, enum mausb_channel channel,
+ enum mausb_link_action act, int status, void *data);
+};
+
+int mausb_init_ip_ctx(struct mausb_ip_ctx **ip_ctx,
+ struct net *net_ns,
+ char ip_addr[INET6_ADDRSTRLEN],
+ u16 port,
+ void *ctx,
+ void (*ctx_callback)(void *ctx,
+ enum mausb_channel channel,
+ enum mausb_link_action act,
+ int status, void *data),
+ enum mausb_channel channel);
+int mausb_ip_disconnect(struct mausb_ip_ctx *ip_ctx);
+int mausb_ip_send(struct mausb_ip_ctx *ip_ctx,
+ struct mausb_kvec_data_wrapper *wrapper);
+
+void mausb_destroy_ip_ctx(struct mausb_ip_ctx *ip_ctx);
+void mausb_ip_connect_async(struct mausb_ip_ctx *ip_ctx);
+
+#endif /* __MAUSB_IP_LINK_H__ */
--
2.17.1