Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2215691/?format=api
{ "id": 2215691, "url": "http://patchwork.ozlabs.org/api/patches/2215691/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-cifs-client/patch/91c6fd0a44eb15a98f945171ead8062badb89a60.1774410440.git.lucien.xin@gmail.com/", "project": { "id": 12, "url": "http://patchwork.ozlabs.org/api/projects/12/?format=api", "name": "Linux CIFS Client", "link_name": "linux-cifs-client", "list_id": "linux-cifs.vger.kernel.org", "list_email": "linux-cifs@vger.kernel.org", "web_url": "", "scm_url": "", "webscm_url": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<91c6fd0a44eb15a98f945171ead8062badb89a60.1774410440.git.lucien.xin@gmail.com>", "list_archive_url": null, "date": "2026-03-25T03:47:11", "name": "[net-next,v11,06/15] quic: add stream management", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "62377f3fff0520c41b2fd02b29e82d543b0df383", "submitter": { "id": 61073, "url": "http://patchwork.ozlabs.org/api/people/61073/?format=api", "name": "Xin Long", "email": "lucien.xin@gmail.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/linux-cifs-client/patch/91c6fd0a44eb15a98f945171ead8062badb89a60.1774410440.git.lucien.xin@gmail.com/mbox/", "series": [ { "id": 497380, "url": "http://patchwork.ozlabs.org/api/series/497380/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-cifs-client/list/?series=497380", "date": "2026-03-25T03:47:06", "name": "net: introduce QUIC infrastructure and core subcomponents", "version": 11, "mbox": "http://patchwork.ozlabs.org/series/497380/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2215691/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2215691/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "\n <linux-cifs+bounces-10508-incoming=patchwork.ozlabs.org@vger.kernel.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "linux-cifs@vger.kernel.org" ], "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=ilMwRZO3;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=172.105.105.114; helo=tor.lore.kernel.org;\n envelope-from=linux-cifs+bounces-10508-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)", "smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com\n header.b=\"ilMwRZO3\"", "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=209.85.160.181", "smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com", "smtp.subspace.kernel.org;\n spf=pass smtp.mailfrom=gmail.com" ], "Received": [ "from tor.lore.kernel.org (tor.lore.kernel.org [172.105.105.114])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fgXzf2KtMz1y1G\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 25 Mar 2026 14:52:30 +1100 (AEDT)", "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby tor.lore.kernel.org (Postfix) with ESMTP id 3908E30BC20C\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 25 Mar 2026 03:49:45 +0000 (UTC)", "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 46F1B286D70;\n\tWed, 25 Mar 2026 03:49:41 +0000 (UTC)", "from mail-qt1-f181.google.com (mail-qt1-f181.google.com\n [209.85.160.181])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id 2871B2E7185\n\tfor <linux-cifs@vger.kernel.org>; Wed, 25 Mar 2026 03:49:38 +0000 (UTC)", "by mail-qt1-f181.google.com with SMTP id\n d75a77b69052e-506bcb23a78so46741891cf.3\n for <linux-cifs@vger.kernel.org>;\n Tue, 24 Mar 2026 20:49:38 -0700 (PDT)", "from wsfd-netdev58.anl.eng.rdu2.dc.redhat.com ([66.187.232.140])\n by smtp.gmail.com with ESMTPSA id\n d75a77b69052e-50b36cb2e29sm150093001cf.1.2026.03.24.20.49.35\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Tue, 24 Mar 2026 20:49:37 -0700 (PDT)" ], "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1774410581; cv=none;\n b=WQT3SeA/ad90NjgGs3fX9PMZTvnv84KenklkwQH6t27GdOjs/aWtUIMfUJh/Ib7H1iAm6fG4KWjXxU88EmUbXjiU79vOFTN8Kzt0CLfm4TyQl2zcEkzBjEvcEQ994FwjrWYCw+e4fVFY14dIKnMd/ahMQH6JHi9cdP0JL6OpFnI=", "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1774410581; c=relaxed/simple;\n\tbh=/hCh8Zx3/BY8jF+1KwPgEwsvzaT5gDv5mdxJ/lR9gsI=;\n\th=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version:Content-Type;\n b=LKXs39vdS36rF4PsbDa583E2eq3QU1YHqS8YPT3NFH8LYjpQo/BgwjQXPbcgsQvJ1xjoLHAavM1xqQi12iKxpK8kwEqtn+SJ5V+yfwgTr2T/UDWRTYZvlaYO6jqDpDtLptAGRtTWlG8u+1112JUtqonGbbu0g2FADWBMeK6fLno=", "ARC-Authentication-Results": "i=1; smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com;\n spf=pass smtp.mailfrom=gmail.com;\n dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com\n header.b=ilMwRZO3; arc=none smtp.client-ip=209.85.160.181", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20251104; t=1774410578; x=1775015378;\n darn=vger.kernel.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=8FJEIVV5+/NFJkyakwv2PtRYAzHhtS8a7PnuVSByDsY=;\n b=ilMwRZO3hFoLimIxliqZcRIPGOuqczWrxERFVbjOJi5CbimP1cn6v6atJ45rXCmDJO\n VXxqfYzSk4TKCwxzwTxQWgkQkwc//j04Z5MzvElOZpgmDqob5VrCxhK/WNbFC1n/rCrf\n xROZg7EXcSG2Rhq10nGljKtrS40N62HlIemLHDKraHMGErPCMFqItccbnPSVOxmyQTq8\n YtTkJ9Zi69jkVqsdk0iAZOyFtS965d+C9BIdwJ6VMT+JCqH0oS3jNXhoN32irXyt1fa0\n ttaGyejE8b4osmfZH3Ep1J2DowdiW2mN2QY9te0ruc/G3I0FuC+5U9iIpU74YmOf2Cmb\n ia6w==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1774410578; x=1775015378;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n :to:cc:subject:date:message-id:reply-to;\n bh=8FJEIVV5+/NFJkyakwv2PtRYAzHhtS8a7PnuVSByDsY=;\n b=Mj3htpZ+KY0uwuRHmyqUX/lGNzH9OGoCn4Z8F2/yPqK72XfdD+KNBJjiAGSoaopoQP\n NPXIADNNSe0SdTFLAfteJJYLkpNoqPGmfqgDvVxIYX3DLQwRySdt8LzMGO+Dv5adrTEY\n 3IR0uF4BAcJseEe/NKQ9fnY+5zaFMk2Ks2REIxjOMhjpGalBVsW4aIEKLarGckKpfCgM\n 51TbE/2QwgT5zYsG5wph5C0z0Uih6VX8cZws9wFI06BHGzIQYIddw7zuUhdt06kATEy8\n pHZHNKehJTQoNbaR2PC+W8eWCXa/AyjAYVk2BGA5p3m2pt2YWQiMGdb/ZW43fDDrCB+V\n yBLA==", "X-Forwarded-Encrypted": "i=1;\n AJvYcCVPaa+8gVNm6SYrWeJy51/O/1LM74+0Kcuj79JwrGbkOx4J9WjBCLnDkex/sgn3ELHQC1yZ7mF8mTbG@vger.kernel.org", "X-Gm-Message-State": "AOJu0YyQsOG/yBgx6VCB25NL8MRja7VCEjwJzthvRIrXlV70xShWK0ay\n\tdtp9yLIXxp0B9Ke/c0YOVv7IS86xQYFfIjL30ljMrgNG5bY8u6ikavme", "X-Gm-Gg": "ATEYQzyy2WPQJyG7BxIiOvoK+PJfOT3D8NvAgTkGjh1DYSx7R0NNR5KOqgKVaq//Wl8\n\t+JsnY0bjxHjrm2NIraET2cN5KzGyX7UK9vXKnRb5SOBlAJIGA8mbk3ufVa4OIrTuKIg+kSZuqK/\n\t8lnIRlcFgKEVCyWLhMALNYyo8PdHsp+7O3YFQsqrmCfUMf6z751LZk9gNHNK3TdMx9LtcvN8VpF\n\toxAK73v6BglMG54q+IQw8HYgVap9bX5wIjAcPOpvlXReWk9RkVAMnaM+vdFWp20qDeKRxDp/AdM\n\tX/60MBBOmljk2co6NYgpoVNOjVF3YBzrcHvRHYG6iK41yGAij1di3voyGYIV4SJq1UGcCI3TZ1s\n\t+SjVH4ZAQ9RW0aGfM9LhE/cJ9HAZQ+6uSsf9VpBlOvwsMDuwlrJvxKstS06+phvqwSaSWP4FvEA\n\t6Vscy+ePfkh8womgYdnz0tbKIA5088Az2/GyRCQQgP+wuwyFhsyP7+Bi/RrpkRg/F2egvLjzkfl\n\t8IDIcncvIqZwwRKLUywiZ7axS/Le7oDsbC5OqIKvZ1F59Ixp+5teTSrQ+9+hdjJHQ==", "X-Received": "by 2002:a05:622a:1393:b0:50b:29f0:299c with SMTP id\n d75a77b69052e-50b80e8c042mr31675151cf.60.1774410577844;\n Tue, 24 Mar 2026 20:49:37 -0700 (PDT)", "From": "Xin Long <lucien.xin@gmail.com>", "To": "network dev <netdev@vger.kernel.org>,\n\tquic@lists.linux.dev", "Cc": "davem@davemloft.net,\n\tkuba@kernel.org,\n\tEric Dumazet <edumazet@google.com>,\n\tPaolo Abeni <pabeni@redhat.com>,\n\tSimon Horman <horms@kernel.org>,\n\tStefan Metzmacher <metze@samba.org>,\n\tMoritz Buhl <mbuhl@openbsd.org>,\n\tTyler Fanelli <tfanelli@redhat.com>,\n\tPengtao He <hepengtao@xiaomi.com>,\n\tThomas Dreibholz <dreibh@simula.no>,\n\tlinux-cifs@vger.kernel.org,\n\tSteve French <smfrench@gmail.com>,\n\tNamjae Jeon <linkinjeon@kernel.org>,\n\tPaulo Alcantara <pc@manguebit.com>,\n\tTom Talpey <tom@talpey.com>,\n\tkernel-tls-handshake@lists.linux.dev,\n\tChuck Lever <chuck.lever@oracle.com>,\n\tJeff Layton <jlayton@kernel.org>,\n\tSteve Dickson <steved@redhat.com>,\n\tHannes Reinecke <hare@suse.de>,\n\tAlexander Aring <aahringo@redhat.com>,\n\tDavid Howells <dhowells@redhat.com>,\n\tMatthieu Baerts <matttbe@kernel.org>,\n\tJohn Ericson <mail@johnericson.me>,\n\tCong Wang <xiyou.wangcong@gmail.com>,\n\t\"D . Wythe\" <alibuda@linux.alibaba.com>,\n\tJason Baron <jbaron@akamai.com>,\n\tilliliti <illiliti@protonmail.com>,\n\tSabrina Dubroca <sd@queasysnail.net>,\n\tMarcelo Ricardo Leitner <marcelo.leitner@gmail.com>,\n\tDaniel Stenberg <daniel@haxx.se>,\n\tAndy Gospodarek <andrew.gospodarek@broadcom.com>,\n\t\"Marc E . Fiuczynski\" <marc@fiuczynski.com>", "Subject": "[PATCH net-next v11 06/15] quic: add stream management", "Date": "Tue, 24 Mar 2026 23:47:11 -0400", "Message-ID": "\n <91c6fd0a44eb15a98f945171ead8062badb89a60.1774410440.git.lucien.xin@gmail.com>", "X-Mailer": "git-send-email 2.47.1", "In-Reply-To": "<cover.1774410440.git.lucien.xin@gmail.com>", "References": "<cover.1774410440.git.lucien.xin@gmail.com>", "Precedence": "bulk", "X-Mailing-List": "linux-cifs@vger.kernel.org", "List-Id": "<linux-cifs.vger.kernel.org>", "List-Subscribe": "<mailto:linux-cifs+subscribe@vger.kernel.org>", "List-Unsubscribe": "<mailto:linux-cifs+unsubscribe@vger.kernel.org>", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=UTF-8", "Content-Transfer-Encoding": "8bit" }, "content": "This patch introduces 'struct quic_stream_table' for managing QUIC streams,\neach represented by 'struct quic_stream'.\n\nIt implements mechanisms for acquiring and releasing streams on both the\nsend and receive paths, ensuring efficient lifecycle management during\ntransmission and reception.\n\n- quic_stream_get(): Acquire a send-side stream by ID and flags during\n TX path, or a receive-side stream by ID during RX path.\n\n- quic_stream_put(): Release a send-side stream when sending is done,\n or a receive-side stream when receiving is done.\n\nIt includes logic to detect when stream ID limits are reached and when\ncontrol frames should be sent to update or request limits from the peer.\n\n- quic_stream_id_exceeds(): Check a stream ID would exceed local (recv)\n or peer (send) limits.\n\n- quic_stream_max_streams_update(): Determines whether a\n MAX_STREAMS_UNI/BIDI frame should be sent to the peer.\n\nNote stream hash table is per socket, the operations on it are always\nprotected by the sock lock.\n\nSigned-off-by: Xin Long <lucien.xin@gmail.com>\nAcked-by: Paolo Abeni <pabeni@redhat.com>\n---\nv3:\n - Merge send/recv stream helpers into unified functions to reduce code:\n * quic_stream_id_send/recv() → quic_stream_id_valid()\n * quic_stream_id_send/recv_closed() → quic_stream_id_closed()\n * quic_stream_id_send/recv_exceeds() → quic_stream_id_exceeds()\n (pointed out by Paolo).\n - Clarify in changelog that stream hash table is always protected by sock\n lock (suggested by Paolo).\n - quic_stream_init/free(): adjust for new hashtable type; call\n quic_stream_delete() in quic_stream_free() to avoid open-coded logic.\n - Receiving streams: delete stream only when fully read or reset, instead\n of when no data was received. Prevents freeing a stream while a FIN\n with no data is still queued.\nv4:\n - Replace struct quic_shash_table with struct hlist_head for the\n stream hashtable. Since they are protected by the socket lock,\n no per-chain lock is needed.\n - Initialize stream to NULL in stream creation functions to avoid\n warnings from Smatch (reported by Simon).\n - Allocate send streams with GFP_KERNEL_ACCOUNT and receive streams\n with GFP_ATOMIC | __GFP_ACCOUNT for memory accounting (suggested\n by Paolo).\nv5:\n - Introduce struct quic_stream_limits to merge quic_stream_send_create()\n and quic_stream_recv_create(), and to simplify quic_stream_get_param()\n (suggested by Paolo).\n - Annotate the sock-lock requirement for quic_stream_send/recv_get()\n and quic_stream_send/recv_put() (notied by Paolo).\n - Add quic_stream_bidi_put() to deduplicate the common logic between\n quic_stream_send_put() and quic_stream_recv_put().\n - Remove the unnecessary check when incrementing\n streams->send.next_bidi/uni_stream_id in quic_stream_create().\n - Remove the unused 'is_serv' parameter from quic_stream_get_param().\nv7:\n - Free the allocated streams on error path in quic_stream_create() (noted\n by Paolo).\n - Merge quic_stream_send_get/put() and quic_stream_recv_get/put() helpers\n to quic_stream_get/put() (suggested by Paolo).\n - Add more comments in quic_stream_id_exceeds() and quic_stream_create().\nv8:\n - Replace bitfields with plain u8 in struct quic_stream_limits and struct\n quic_stream (suggested by Paolo).\nv9:\n - Fix grammar in the comment for quic_stream::send.window.\nv10:\n - Move quic_stream_init() to after sock_prot_inuse_add() ensure counters\n are incremented before any early return paths in quic_init_sock(),\n preventing underflow in quic_destroy_sock() (noted by AI review).\n - Initialize the output parameters '*max_uni' and '*max_bidi' to 0 at the\n start of quic_stream_max_streams_update()\n - Use 'stream->recv.state > QUIC_STREAM_RECV_STATE_RECVD' instead of '!='\n for clearer intent.\n - Simplify some state checks in quic_stream_put() by using range\n comparisons (> or <) instead of multiple != conditions.\n - streams_uni/bidi are u16 type, and their overflow is already prevented\n by QUIC_MAX_STREAMS indirectly. Update comment in quic_stream_create().\n - Replace open-coded kzalloc(sizeof(*stream)) with kzalloc_obj(*stream)\n in quic_stream_create().\nv11:\n - Set maximum line length to 80 characters.\n - Change is_serv parameter type to bool in quic_stream_id_local().\n---\n net/quic/Makefile | 2 +-\n net/quic/socket.c | 5 +\n net/quic/socket.h | 8 +\n net/quic/stream.c | 444 ++++++++++++++++++++++++++++++++++++++++++++++\n net/quic/stream.h | 133 ++++++++++++++\n 5 files changed, 591 insertions(+), 1 deletion(-)\n create mode 100644 net/quic/stream.c\n create mode 100644 net/quic/stream.h", "diff": "diff --git a/net/quic/Makefile b/net/quic/Makefile\nindex 13bf4a4e5442..094e9da5d739 100644\n--- a/net/quic/Makefile\n+++ b/net/quic/Makefile\n@@ -5,4 +5,4 @@\n \n obj-$(CONFIG_IP_QUIC) += quic.o\n \n-quic-y := common.o family.o protocol.o socket.o\n+quic-y := common.o family.o protocol.o socket.o stream.o\ndiff --git a/net/quic/socket.c b/net/quic/socket.c\nindex 8dc2cb7628db..0006668551f4 100644\n--- a/net/quic/socket.c\n+++ b/net/quic/socket.c\n@@ -45,11 +45,16 @@ static int quic_init_sock(struct sock *sk)\n \tsk_sockets_allocated_inc(sk);\n \tsock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);\n \n+\tif (quic_stream_init(quic_streams(sk)))\n+\t\treturn -ENOMEM;\n+\n \treturn 0;\n }\n \n static void quic_destroy_sock(struct sock *sk)\n {\n+\tquic_stream_free(quic_streams(sk));\n+\n \tquic_data_free(quic_ticket(sk));\n \tquic_data_free(quic_token(sk));\n \tquic_data_free(quic_alpn(sk));\ndiff --git a/net/quic/socket.h b/net/quic/socket.h\nindex 61df0c5867be..e76737b9b74b 100644\n--- a/net/quic/socket.h\n+++ b/net/quic/socket.h\n@@ -13,6 +13,7 @@\n \n #include \"common.h\"\n #include \"family.h\"\n+#include \"stream.h\"\n \n #include \"protocol.h\"\n \n@@ -33,6 +34,8 @@ struct quic_sock {\n \tstruct quic_data\t\tticket;\n \tstruct quic_data\t\ttoken;\n \tstruct quic_data\t\talpn;\n+\n+\tstruct quic_stream_table\tstreams;\n };\n \n struct quic6_sock {\n@@ -65,6 +68,11 @@ static inline struct quic_data *quic_alpn(const struct sock *sk)\n \treturn &quic_sk(sk)->alpn;\n }\n \n+static inline struct quic_stream_table *quic_streams(const struct sock *sk)\n+{\n+\treturn &quic_sk(sk)->streams;\n+}\n+\n static inline bool quic_is_serv(const struct sock *sk)\n {\n \treturn !!sk->sk_max_ack_backlog;\ndiff --git a/net/quic/stream.c b/net/quic/stream.c\nnew file mode 100644\nindex 000000000000..4d980f9b03ce\n--- /dev/null\n+++ b/net/quic/stream.c\n@@ -0,0 +1,444 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ * Xin Long <lucien.xin@gmail.com>\n+ */\n+\n+#include <linux/quic.h>\n+\n+#include \"common.h\"\n+#include \"stream.h\"\n+\n+/* Check if a stream ID is valid for sending or receiving. */\n+static bool quic_stream_id_valid(s64 stream_id, bool is_serv, bool send)\n+{\n+\tu8 type = (stream_id & QUIC_STREAM_TYPE_MASK);\n+\n+\tif (send) {\n+\t\tif (is_serv)\n+\t\t\treturn type != QUIC_STREAM_TYPE_CLIENT_UNI;\n+\t\treturn type != QUIC_STREAM_TYPE_SERVER_UNI;\n+\t}\n+\tif (is_serv)\n+\t\treturn type != QUIC_STREAM_TYPE_SERVER_UNI;\n+\treturn type != QUIC_STREAM_TYPE_CLIENT_UNI;\n+}\n+\n+/* Check if a stream ID was initiated locally. */\n+static bool quic_stream_id_local(s64 stream_id, bool is_serv)\n+{\n+\treturn is_serv ^ !(stream_id & QUIC_STREAM_TYPE_SERVER_MASK);\n+}\n+\n+/* Check if a stream ID represents a unidirectional stream. */\n+static bool quic_stream_id_uni(s64 stream_id)\n+{\n+\treturn stream_id & QUIC_STREAM_TYPE_UNI_MASK;\n+}\n+\n+#define QUIC_STREAM_HT_SIZE\t64\n+\n+static struct hlist_head *quic_stream_head(struct quic_stream_table *streams,\n+\t\t\t\t\t s64 stream_id)\n+{\n+\treturn &streams->head[stream_id & (QUIC_STREAM_HT_SIZE - 1)];\n+}\n+\n+struct quic_stream *quic_stream_find(struct quic_stream_table *streams,\n+\t\t\t\t s64 stream_id)\n+{\n+\tstruct hlist_head *head = quic_stream_head(streams, stream_id);\n+\tstruct quic_stream *stream;\n+\n+\thlist_for_each_entry(stream, head, node) {\n+\t\tif (stream->id == stream_id)\n+\t\t\tbreak;\n+\t}\n+\treturn stream;\n+}\n+\n+static void quic_stream_add(struct quic_stream_table *streams,\n+\t\t\t struct quic_stream *stream)\n+{\n+\tstruct hlist_head *head;\n+\n+\thead = quic_stream_head(streams, stream->id);\n+\thlist_add_head(&stream->node, head);\n+}\n+\n+static void quic_stream_delete(struct quic_stream *stream)\n+{\n+\thlist_del_init(&stream->node);\n+\tkfree(stream);\n+}\n+\n+/* Create and register new streams for sending or receiving. */\n+static struct quic_stream *quic_stream_create(struct quic_stream_table *streams,\n+\t\t\t\t\t s64 max_stream_id, bool send,\n+\t\t\t\t\t bool is_serv)\n+{\n+\tstruct quic_stream_limits *limits = &streams->send;\n+\tstruct quic_stream *pos, *stream = NULL;\n+\tgfp_t gfp = GFP_KERNEL_ACCOUNT;\n+\tstruct hlist_node *tmp;\n+\tHLIST_HEAD(head);\n+\ts64 stream_id;\n+\tu32 count = 0;\n+\n+\tif (!send) {\n+\t\tlimits = &streams->recv;\n+\t\tgfp = GFP_ATOMIC | __GFP_ACCOUNT;\n+\t}\n+\tstream_id = limits->next_bidi_stream_id;\n+\tif (quic_stream_id_uni(max_stream_id))\n+\t\tstream_id = limits->next_uni_stream_id;\n+\n+\t/* rfc9000#section-2.1: A stream ID that is used out of order results in\n+\t * all streams of that type with lower-numbered stream IDs also being\n+\t * opened.\n+\t */\n+\twhile (stream_id <= max_stream_id) {\n+\t\tstream = kzalloc_obj(*stream, gfp);\n+\t\tif (!stream)\n+\t\t\tgoto free;\n+\n+\t\tstream->id = stream_id;\n+\t\tif (quic_stream_id_uni(stream_id)) {\n+\t\t\tif (send) {\n+\t\t\t\tstream->send.max_bytes =\n+\t\t\t\t\tlimits->max_stream_data_uni;\n+\t\t\t} else {\n+\t\t\t\tstream->recv.max_bytes =\n+\t\t\t\t\tlimits->max_stream_data_uni;\n+\t\t\t\tstream->recv.window = stream->recv.max_bytes;\n+\t\t\t}\n+\t\t\thlist_add_head(&stream->node, &head);\n+\t\t\tstream_id += QUIC_STREAM_ID_STEP;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (quic_stream_id_local(stream_id, is_serv)) {\n+\t\t\tstream->send.max_bytes =\n+\t\t\t\tstreams->send.max_stream_data_bidi_remote;\n+\t\t\tstream->recv.max_bytes =\n+\t\t\t\tstreams->recv.max_stream_data_bidi_local;\n+\t\t} else {\n+\t\t\tstream->send.max_bytes =\n+\t\t\t\tstreams->send.max_stream_data_bidi_local;\n+\t\t\tstream->recv.max_bytes =\n+\t\t\t\tstreams->recv.max_stream_data_bidi_remote;\n+\t\t}\n+\t\tstream->recv.window = stream->recv.max_bytes;\n+\t\thlist_add_head(&stream->node, &head);\n+\t\tstream_id += QUIC_STREAM_ID_STEP;\n+\t}\n+\n+\thlist_for_each_entry_safe(pos, tmp, &head, node) {\n+\t\thlist_del_init(&pos->node);\n+\t\tquic_stream_add(streams, pos);\n+\t\tcount++;\n+\t}\n+\n+\t/* Streams must be opened sequentially. Update the next stream ID so the\n+\t * correct starting point is known if an out-of-order open is requested.\n+\t * Note overflow of next_uni/bidi_stream_id is impossible with s64.\n+\t */\n+\tif (quic_stream_id_uni(stream_id)) {\n+\t\tlimits->next_uni_stream_id = stream_id;\n+\t\tlimits->streams_uni += count;\n+\t\treturn stream;\n+\t}\n+\n+\tlimits->next_bidi_stream_id = stream_id;\n+\tlimits->streams_bidi += count;\n+\treturn stream;\n+\n+free:\n+\thlist_for_each_entry_safe(pos, tmp, &head, node) {\n+\t\thlist_del_init(&pos->node);\n+\t\tkfree(pos);\n+\t}\n+\treturn NULL;\n+}\n+\n+/* Check if a send or receive stream ID is already closed. */\n+static bool quic_stream_id_closed(struct quic_stream_table *streams,\n+\t\t\t\t s64 stream_id, bool send)\n+{\n+\tstruct quic_stream_limits *limits = send ? &streams->send :\n+\t\t\t\t\t\t &streams->recv;\n+\n+\tif (quic_stream_id_uni(stream_id))\n+\t\treturn stream_id < limits->next_uni_stream_id;\n+\treturn stream_id < limits->next_bidi_stream_id;\n+}\n+\n+/* Check if a stream ID would exceed local (recv) or peer (send) limits. */\n+bool quic_stream_id_exceeds(struct quic_stream_table *streams, s64 stream_id,\n+\t\t\t bool send)\n+{\n+\tu64 nstreams;\n+\n+\tif (!send) {\n+\t\t/* recv.max_uni_stream_id is updated in\n+\t\t * quic_stream_max_streams_update() already based on\n+\t\t * next_uni/bidi_stream_id, max_streams_uni/bidi, and\n+\t\t * streams_uni/bidi, so only recv.max_uni_stream_id needs to be\n+\t\t * checked.\n+\t\t */\n+\t\tif (quic_stream_id_uni(stream_id))\n+\t\t\treturn stream_id > streams->recv.max_uni_stream_id;\n+\n+\t\treturn stream_id > streams->recv.max_bidi_stream_id;\n+\t}\n+\n+\tif (quic_stream_id_uni(stream_id)) {\n+\t\tif (stream_id > streams->send.max_uni_stream_id)\n+\t\t\treturn true;\n+\t\tstream_id -= streams->send.next_uni_stream_id;\n+\t\tnstreams = quic_stream_id_to_streams(stream_id);\n+\n+\t\treturn nstreams + streams->send.streams_uni >\n+\t\t streams->send.max_streams_uni;\n+\t}\n+\n+\tif (stream_id > streams->send.max_bidi_stream_id)\n+\t\treturn true;\n+\tstream_id -= streams->send.next_bidi_stream_id;\n+\tnstreams = quic_stream_id_to_streams(stream_id);\n+\n+\treturn nstreams + streams->send.streams_bidi >\n+\t streams->send.max_streams_bidi;\n+}\n+\n+/* Get or create a send or recv stream by ID. Requires sock lock held. */\n+struct quic_stream *quic_stream_get(struct quic_stream_table *streams,\n+\t\t\t\t s64 stream_id, u32 flags, bool is_serv,\n+\t\t\t\t bool send)\n+{\n+\tstruct quic_stream *stream;\n+\n+\tif (!quic_stream_id_valid(stream_id, is_serv, send))\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tstream = quic_stream_find(streams, stream_id);\n+\tif (stream) {\n+\t\tif (send && (flags & MSG_QUIC_STREAM_NEW) &&\n+\t\t stream->send.state != QUIC_STREAM_SEND_STATE_READY)\n+\t\t\treturn ERR_PTR(-EINVAL);\n+\t\treturn stream;\n+\t}\n+\n+\tif (!send && quic_stream_id_local(stream_id, is_serv)) {\n+\t\tif (quic_stream_id_closed(streams, stream_id, !send))\n+\t\t\treturn ERR_PTR(-ENOSTR);\n+\t\treturn ERR_PTR(-EINVAL);\n+\t}\n+\n+\tif (quic_stream_id_closed(streams, stream_id, send))\n+\t\treturn ERR_PTR(-ENOSTR);\n+\n+\tif (send && !(flags & MSG_QUIC_STREAM_NEW))\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tif (quic_stream_id_exceeds(streams, stream_id, send))\n+\t\treturn ERR_PTR(-EAGAIN);\n+\n+\tstream = quic_stream_create(streams, stream_id, send, is_serv);\n+\tif (!stream)\n+\t\treturn ERR_PTR(-ENOSTR);\n+\n+\tif (send || quic_stream_id_valid(stream_id, is_serv, !send))\n+\t\tstreams->send.active_stream_id = stream_id;\n+\n+\treturn stream;\n+}\n+\n+/* Release or clean up a send or recv stream. This function updates stream\n+ * counters and state when a send stream has either successfully sent all data\n+ * or has been reset, or when a recv stream has either consumed all data or has\n+ * been reset. Requires sock lock held.\n+ */\n+void quic_stream_put(struct quic_stream_table *streams,\n+\t\t struct quic_stream *stream, bool is_serv, bool send)\n+{\n+\tif (quic_stream_id_uni(stream->id)) {\n+\t\tif (send) {\n+\t\t\t/* For uni streams, decrement uni count and delete\n+\t\t\t * immediately.\n+\t\t\t */\n+\t\t\tstreams->send.streams_uni--;\n+\t\t\tquic_stream_delete(stream);\n+\t\t\treturn;\n+\t\t}\n+\t\t/* For uni streams, decrement uni count and mark done. */\n+\t\tif (!stream->recv.done) {\n+\t\t\tstream->recv.done = 1;\n+\t\t\tstreams->recv.streams_uni--;\n+\t\t\tstreams->recv.uni_pending = 1;\n+\t\t}\n+\t\t/* Delete stream if fully read or reset. */\n+\t\tif (stream->recv.state > QUIC_STREAM_RECV_STATE_RECVD)\n+\t\t\tquic_stream_delete(stream);\n+\t\treturn;\n+\t}\n+\n+\tif (send) {\n+\t\t/* For bidi streams, only proceed if receive side is in a final\n+\t\t * state.\n+\t\t */\n+\t\tif (stream->recv.state < QUIC_STREAM_RECV_STATE_RECVD)\n+\t\t\treturn;\n+\t} else {\n+\t\t/* For bidi streams, only proceed if send side is in a final\n+\t\t * state.\n+\t\t */\n+\t\tif (stream->send.state != QUIC_STREAM_SEND_STATE_RECVD &&\n+\t\t stream->send.state != QUIC_STREAM_SEND_STATE_RESET_RECVD)\n+\t\t\treturn;\n+\t}\n+\n+\tif (quic_stream_id_local(stream->id, is_serv)) {\n+\t\t/* Local-initiated stream: mark send done and decrement\n+\t\t * send.bidi count.\n+\t\t */\n+\t\tif (!stream->send.done) {\n+\t\t\tstream->send.done = 1;\n+\t\t\tstreams->send.streams_bidi--;\n+\t\t}\n+\t} else {\n+\t\t/* Remote-initiated stream: mark recv done and decrement recv\n+\t\t * bidi count.\n+\t\t */\n+\t\tif (!stream->recv.done) {\n+\t\t\tstream->recv.done = 1;\n+\t\t\tstreams->recv.streams_bidi--;\n+\t\t\tstreams->recv.bidi_pending = 1;\n+\t\t}\n+\t}\n+\n+\t/* Delete stream if fully read or reset. */\n+\tif (stream->recv.state > QUIC_STREAM_RECV_STATE_RECVD)\n+\t\tquic_stream_delete(stream);\n+}\n+\n+/* Updates the maximum allowed incoming stream IDs if any streams were recently\n+ * closed. Recalculates the max_uni and max_bidi stream ID limits based on the\n+ * number of open streams and whether any were marked for deletion.\n+ *\n+ * Returns true if either max_uni or max_bidi was updated, indicating that a\n+ * MAX_STREAMS_UNI or MAX_STREAMS_BIDI frame should be sent to the peer.\n+ */\n+bool quic_stream_max_streams_update(struct quic_stream_table *streams,\n+\t\t\t\t s64 *max_uni, s64 *max_bidi)\n+{\n+\ts64 max, rem;\n+\n+\t*max_uni = 0;\n+\t*max_bidi = 0;\n+\tif (streams->recv.uni_pending) {\n+\t\trem = streams->recv.max_streams_uni - streams->recv.streams_uni;\n+\t\tmax = streams->recv.next_uni_stream_id - QUIC_STREAM_ID_STEP +\n+\t\t (rem << QUIC_STREAM_TYPE_BITS);\n+\n+\t\tstreams->recv.max_uni_stream_id = max;\n+\t\t*max_uni = quic_stream_id_to_streams(max);\n+\t\tstreams->recv.uni_pending = 0;\n+\t}\n+\tif (streams->recv.bidi_pending) {\n+\t\trem = streams->recv.max_streams_bidi -\n+\t\t streams->recv.streams_bidi;\n+\t\tmax = streams->recv.next_bidi_stream_id - QUIC_STREAM_ID_STEP +\n+\t\t\t(rem << QUIC_STREAM_TYPE_BITS);\n+\n+\t\tstreams->recv.max_bidi_stream_id = max;\n+\t\t*max_bidi = quic_stream_id_to_streams(max);\n+\t\tstreams->recv.bidi_pending = 0;\n+\t}\n+\n+\treturn *max_uni || *max_bidi;\n+}\n+\n+int quic_stream_init(struct quic_stream_table *streams)\n+{\n+\tstruct hlist_head *head;\n+\tint i;\n+\n+\thead = kmalloc_array(QUIC_STREAM_HT_SIZE, sizeof(*head), GFP_KERNEL);\n+\tif (!head)\n+\t\treturn -ENOMEM;\n+\tfor (i = 0; i < QUIC_STREAM_HT_SIZE; i++)\n+\t\tINIT_HLIST_HEAD(&head[i]);\n+\tstreams->head = head;\n+\treturn 0;\n+}\n+\n+void quic_stream_free(struct quic_stream_table *streams)\n+{\n+\tstruct quic_stream *stream;\n+\tstruct hlist_head *head;\n+\tstruct hlist_node *tmp;\n+\tint i;\n+\n+\tif (!streams->head)\n+\t\treturn;\n+\n+\tfor (i = 0; i < QUIC_STREAM_HT_SIZE; i++) {\n+\t\thead = &streams->head[i];\n+\t\thlist_for_each_entry_safe(stream, tmp, head, node)\n+\t\t\tquic_stream_delete(stream);\n+\t}\n+\tkfree(streams->head);\n+}\n+\n+/* Populate transport parameters from stream hash table. */\n+void quic_stream_get_param(struct quic_stream_table *streams,\n+\t\t\t struct quic_transport_param *p)\n+{\n+\tstruct quic_stream_limits *limits = p->remote ? &streams->send :\n+\t\t\t\t\t\t\t&streams->recv;\n+\n+\tp->max_stream_data_bidi_remote = limits->max_stream_data_bidi_remote;\n+\tp->max_stream_data_bidi_local = limits->max_stream_data_bidi_local;\n+\tp->max_stream_data_uni = limits->max_stream_data_uni;\n+\tp->max_streams_bidi = limits->max_streams_bidi;\n+\tp->max_streams_uni = limits->max_streams_uni;\n+}\n+\n+/* Configure stream hashtable from transport parameters. */\n+void quic_stream_set_param(struct quic_stream_table *streams,\n+\t\t\t struct quic_transport_param *p, bool is_serv)\n+{\n+\tstruct quic_stream_limits *limits = p->remote ? &streams->send :\n+\t\t\t\t\t\t\t&streams->recv;\n+\tu8 bidi_type, uni_type;\n+\n+\tlimits->max_stream_data_bidi_local = p->max_stream_data_bidi_local;\n+\tlimits->max_stream_data_bidi_remote = p->max_stream_data_bidi_remote;\n+\tlimits->max_stream_data_uni = p->max_stream_data_uni;\n+\tlimits->max_streams_bidi = p->max_streams_bidi;\n+\tlimits->max_streams_uni = p->max_streams_uni;\n+\tlimits->active_stream_id = -1;\n+\n+\tif (p->remote ^ is_serv) {\n+\t\tbidi_type = QUIC_STREAM_TYPE_CLIENT_BIDI;\n+\t\tuni_type = QUIC_STREAM_TYPE_CLIENT_UNI;\n+\t} else {\n+\t\tbidi_type = QUIC_STREAM_TYPE_SERVER_BIDI;\n+\t\tuni_type = QUIC_STREAM_TYPE_SERVER_UNI;\n+\t}\n+\n+\tlimits->max_bidi_stream_id =\n+\t\tquic_stream_streams_to_id(p->max_streams_bidi, bidi_type);\n+\tlimits->next_bidi_stream_id = bidi_type;\n+\n+\tlimits->max_uni_stream_id =\n+\t\tquic_stream_streams_to_id(p->max_streams_uni, uni_type);\n+\tlimits->next_uni_stream_id = uni_type;\n+}\ndiff --git a/net/quic/stream.h b/net/quic/stream.h\nnew file mode 100644\nindex 000000000000..435ae1246e05\n--- /dev/null\n+++ b/net/quic/stream.h\n@@ -0,0 +1,133 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ * Xin Long <lucien.xin@gmail.com>\n+ */\n+\n+#define QUIC_DEF_STREAMS\t100\n+#define QUIC_MAX_STREAMS\t4096ULL\n+\n+/*\n+ * rfc9000#section-2.1:\n+ *\n+ * The least significant bit (0x01) of the stream ID identifies the initiator\n+ * of the stream. Client-initiated streams have even-numbered stream IDs\n+ * (with the bit set to 0), and server-initiated streams have odd-numbered\n+ * stream IDs (with the bit set to 1).\n+ *\n+ * The second least significant bit (0x02) of the stream ID distinguishes\n+ * between bidirectional streams (with the bit set to 0) and unidirectional\n+ * streams (with the bit set to 1).\n+ */\n+#define QUIC_STREAM_TYPE_BITS\t2\n+#define QUIC_STREAM_ID_STEP\tBIT(QUIC_STREAM_TYPE_BITS)\n+\n+#define QUIC_STREAM_TYPE_CLIENT_BIDI\t0x00\n+#define QUIC_STREAM_TYPE_SERVER_BIDI\t0x01\n+#define QUIC_STREAM_TYPE_CLIENT_UNI\t0x02\n+#define QUIC_STREAM_TYPE_SERVER_UNI\t0x03\n+\n+struct quic_stream {\n+\tstruct hlist_node node;\n+\ts64 id; /* Stream ID as defined in RFC 9000 Section 2.1 */\n+\tstruct {\n+\t\t/* Sending-side stream level flow control */\n+\t\tu64 last_max_bytes; /* Max send offset advertised by peer */\n+\t\tu64 max_bytes; /* Max offset allowed to send */\n+\t\tu64 bytes; /* Bytes already sent to peer */\n+\n+\t\tu32 errcode; /* App error code for RESET_STREAM */\n+\t\tu32 frags; /* STREAM frames sent but not yet acked */\n+\t\tu8 state; /* Send stream state, per rfc9000#section-3.1 */\n+\n+\t\tu8 data_blocked; /* True if flow control blocks sending */\n+\t\tu8 done; /* True if FIN has been sent */\n+\t} send;\n+\tstruct {\n+\t\t/* Receiving-side stream level flow control */\n+\t\tu64 max_bytes; /* Max offset peer can send */\n+\t\tu64 window; /* Remaining receive window */\n+\t\tu64 bytes; /* Bytes consumed by app */\n+\n+\t\tu64 highest; /* Highest received offset */\n+\t\tu64 offset; /* Data buffered or consumed */\n+\t\tu64 finalsz; /* Final stream size if FIN received */\n+\n+\t\tu32 frags; /* STREAM frames pending reassembly */\n+\t\tu8 state; /* Receive stream state, per rfc9000#section-3.2 */\n+\n+\t\tu8 stop_sent; /* True if STOP_SENDING has been sent */\n+\t\tu8 done; /* True if all data has been received or read */\n+\t} recv;\n+};\n+\n+struct quic_stream_limits {\n+\t/* Stream limit parameters defined in rfc9000#section-18.2:\n+\t *\n+\t * - initial_max_stream_data_bidi_remote\n+\t * - initial_max_stream_data_bidi_local\n+\t * - initial_max_stream_data_uni\n+\t * - initial_max_streams_bidi\n+\t * - initial_max_streams_uni\n+\t */\n+\tu64 max_stream_data_bidi_remote;\n+\tu64 max_stream_data_bidi_local;\n+\tu64 max_stream_data_uni;\n+\tu64 max_streams_bidi;\n+\tu64 max_streams_uni;\n+\n+\ts64 next_bidi_stream_id; /* Next bidi stream ID to open or accept */\n+\ts64 next_uni_stream_id; /* Next uni stream ID to open or accept */\n+\ts64 max_bidi_stream_id; /* Highest allowed bidi stream ID */\n+\ts64 max_uni_stream_id; /* Highest allowed uni stream ID */\n+\ts64 active_stream_id; /* Most recently opened stream ID */\n+\n+\tu8 bidi_blocked; /* STREAMS_BLOCKED_BIDI sent, awaiting ACK */\n+\tu8 uni_blocked; /* STREAMS_BLOCKED_UNI sent, awaiting ACK */\n+\tu8 bidi_pending; /* MAX_STREAMS_BIDI needs to be sent */\n+\tu8 uni_pending; /* MAX_STREAMS_UNI needs to be sent */\n+\n+\tu16 streams_bidi; /* Number of open bidi streams */\n+\tu16 streams_uni; /* Number of open uni streams */\n+};\n+\n+struct quic_stream_table {\n+\tstruct hlist_head *head; /* Hash table storing all active streams */\n+\n+\tstruct quic_stream_limits send; /* Limits advertised by peer */\n+\tstruct quic_stream_limits recv; /* Limits we advertise to peer */\n+};\n+\n+static inline u64 quic_stream_id_to_streams(s64 stream_id)\n+{\n+\treturn (u64)(stream_id >> QUIC_STREAM_TYPE_BITS) + 1;\n+}\n+\n+static inline s64 quic_stream_streams_to_id(u64 streams, u8 type)\n+{\n+\treturn (s64)((streams - 1) << QUIC_STREAM_TYPE_BITS) | type;\n+}\n+\n+struct quic_stream *quic_stream_get(struct quic_stream_table *streams,\n+\t\t\t\t s64 stream_id, u32 flags, bool is_serv,\n+\t\t\t\t bool send);\n+void quic_stream_put(struct quic_stream_table *streams,\n+\t\t struct quic_stream *stream, bool is_serv, bool send);\n+\n+bool quic_stream_max_streams_update(struct quic_stream_table *streams,\n+\t\t\t\t s64 *max_uni, s64 *max_bidi);\n+bool quic_stream_id_exceeds(struct quic_stream_table *streams,\n+\t\t\t s64 stream_id, bool send);\n+struct quic_stream *quic_stream_find(struct quic_stream_table *streams,\n+\t\t\t\t s64 stream_id);\n+\n+void quic_stream_get_param(struct quic_stream_table *streams,\n+\t\t\t struct quic_transport_param *p);\n+void quic_stream_set_param(struct quic_stream_table *streams,\n+\t\t\t struct quic_transport_param *p, bool is_serv);\n+void quic_stream_free(struct quic_stream_table *streams);\n+int quic_stream_init(struct quic_stream_table *streams);\n", "prefixes": [ "net-next", "v11", "06/15" ] }