[PATCH v3 3/3] cmd: mtd: add nand_read_test command support

2024-10-01 Thread Mikhail Kshevetskiy
This patch implements read-only test of nand flash devices.

Test reads blocks of NAND flash in normal and raw modes and compares
results. The following statuses can be returned for a block:
 * non-ecc reading failed,
 * ecc reading failed,
 * block is bad,
 * bitflips is above maximum,
 * actual number of biflips above reported one,
 * bitflips reached it maximum value,
 * block is ok.

Signed-off-by: Mikhail Kshevetskiy 
---
 cmd/Kconfig |  16 +
 cmd/mtd.c   | 193 
 2 files changed, 209 insertions(+)

diff --git a/cmd/Kconfig b/cmd/Kconfig
index ea0491d3155..5a0efb7b173 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1471,6 +1471,22 @@ config CMD_MTD_NAND_WRITE_TEST
 
  WARNING: This test will destroy any data on blocks being tested.
 
+config CMD_MTD_NAND_READ_TEST
+   bool "mtd nand_read_test"
+   depends on CMD_MTD
+   help
+ MTD nand_read_test command support.
+
+ Reads blocks of NAND flash in normal and raw modes and compares 
results.
+ The following statuses can be returned for a block:
+   * non-ecc reading failed,
+   * ecc reading failed,
+   * block is bad,
+   * bitflips is above maximum,
+   * actual number of biflips above reported one,
+   * bitflips reached it maximum value,
+   * block is ok.
+
 config CMD_MUX
bool "mux"
depends on MULTIPLEXER
diff --git a/cmd/mtd.c b/cmd/mtd.c
index 739a8a52cbd..1bfe9fc97da 100644
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
@@ -931,6 +931,191 @@ out_put_mtd:
 }
 #endif
 
+#ifdef CONFIG_CMD_MTD_NAND_READ_TEST
+enum nand_read_status {
+   NAND_READ_STATUS_UNKNOWN = 0,
+   NAND_READ_STATUS_NONECC_READ_FAIL,
+   NAND_READ_STATUS_ECC_READ_FAIL,
+   NAND_READ_STATUS_BAD_BLOCK,
+   NAND_READ_STATUS_BITFLIP_ABOVE_MAX,
+   NAND_READ_STATUS_BITFLIP_MISMATCH,
+   NAND_READ_STATUS_BITFLIP_MAX,
+   NAND_READ_STATUS_OK,
+};
+
+static enum nand_read_status nand_read_block_check(struct mtd_info *mtd,
+  loff_t off, size_t blocksize)
+{
+   struct mtd_oob_ops  ops = {};
+   u_char  *buf;
+   int i, d, ret, len, pos, cnt, max;
+
+   if (blocksize % mtd->writesize != 0) {
+   printf("\r  block at %llx: bad block size\n", off);
+   return NAND_READ_STATUS_UNKNOWN;
+   }
+
+   buf = malloc_cache_aligned(2 * blocksize);
+   if (buf == NULL) {
+   printf("\r  block at %llx: can't allocate memory\n", off);
+   return NAND_READ_STATUS_UNKNOWN;
+   }
+
+   ops.mode = MTD_OPS_RAW;
+   ops.len = blocksize;
+   ops.datbuf = buf;
+   ops.ooblen = 0;
+   ops.oobbuf = NULL;
+
+   if (mtd->_read_oob)
+   ret = mtd->_read_oob(mtd, off, &ops);
+   else
+   ret = mtd->_read(mtd, off, ops.len, &ops.retlen, ops.datbuf);
+
+   if (ret != 0) {
+   free(buf);
+   printf("\r  block at %llx: non-ecc reading error %d\n",
+  off, ret);
+   return NAND_READ_STATUS_NONECC_READ_FAIL;
+   }
+
+   ops.mode = MTD_OPS_AUTO_OOB;
+   ops.datbuf = buf + blocksize;
+
+   if (mtd->_read_oob)
+   ret = mtd->_read_oob(mtd, off, &ops);
+   else
+   ret = mtd->_read(mtd, off, ops.len, &ops.retlen, ops.datbuf);
+
+   if (ret == -EBADMSG) {
+   free(buf);
+   printf("\r  block at %llx: bad block\n", off);
+   return NAND_READ_STATUS_BAD_BLOCK;
+   }
+
+   if (ret < 0) {
+   free(buf);
+   printf("\r  block at %llx: ecc reading error %d\n", off, ret);
+   return NAND_READ_STATUS_ECC_READ_FAIL;
+   }
+
+   if (mtd->ecc_strength == 0) {
+   free(buf);
+   return NAND_READ_STATUS_OK;
+   }
+
+   if (ret > mtd->ecc_strength) {
+   free(buf);
+   printf("\r  block at %llx: returned bit-flips value %d "
+  "is above maximum value %d\n",
+  off, ret, mtd->ecc_strength);
+   return NAND_READ_STATUS_BITFLIP_ABOVE_MAX;
+   }
+
+   max = 0;
+   pos = 0;
+   len = blocksize;
+   while (len > 0) {
+   cnt = 0;
+   for (i = 0; i < mtd->ecc_step_size; i++) {
+   d = buf[pos + i] ^ buf[blocksize + pos + i];
+   if (d == 0)
+   continue;
+
+   while (d > 0) {
+   d &= (d - 1);
+   cnt++;
+   }
+   }
+   if (cnt > max)
+  

[PATCH v3 1/3] cmd: mtd: add markbad command support

2024-10-01 Thread Mikhail Kshevetskiy
Some nand flashes (like spi-nand one) are registered with mtd
subsystem only, thus nand command can't be used to work with
such flashes. As result some functionality is missing.

This patch implements 'nand markbad' functionality for mtd command.

Signed-off-by: Mikhail Kshevetskiy 
---
 cmd/Kconfig |  8 
 cmd/mtd.c   | 58 +
 2 files changed, 66 insertions(+)

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 5ef3c8a8748..e830b731d24 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1449,6 +1449,14 @@ config CMD_MTD_OTP
help
  MTD commands for OTP access.
 
+config CMD_MTD_MARKBAD
+   bool "mtd markbad"
+   depends on CMD_MTD
+   help
+ MTD markbad command support.
+
+ This is a clone of "nand markbad" command, but for 'mtd' subsystem.
+
 config CMD_MUX
bool "mux"
depends on MULTIPLEXER
diff --git a/cmd/mtd.c b/cmd/mtd.c
index 795aaa2b37d..45084d39d3a 100644
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
@@ -691,6 +691,57 @@ out_put_mtd:
return ret;
 }
 
+#ifdef CONFIG_CMD_MTD_MARKBAD
+static int do_mtd_markbad(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+   struct mtd_info *mtd;
+   loff_t off;
+   int ret = 0;
+
+   if (argc < 3)
+   return CMD_RET_USAGE;
+
+   mtd = get_mtd_by_name(argv[1]);
+   if (IS_ERR_OR_NULL(mtd))
+   return CMD_RET_FAILURE;
+
+   if (!mtd_can_have_bb(mtd)) {
+   printf("Only NAND-based devices can have bad blocks\n");
+   goto out_put_mtd;
+   }
+
+   argc -= 2;
+   argv += 2;
+   while (argc > 0) {
+   off = hextoul(argv[0], NULL);
+   if (!mtd_is_aligned_with_block_size(mtd, off)) {
+   printf("Offset not aligned with a block (0x%x)\n",
+  mtd->erasesize);
+   ret = CMD_RET_FAILURE;
+   goto out_put_mtd;
+   }
+
+   ret = mtd_block_markbad(mtd, off);
+   if (ret) {
+   printf("block 0x%08llx NOT marked as bad! ERROR %d\n",
+  off, ret);
+   ret = CMD_RET_FAILURE;
+   } else {
+   printf("block 0x%08llx successfully marked as bad\n",
+  off);
+   }
+   --argc;
+   ++argv;
+   }
+
+out_put_mtd:
+   put_mtd_device(mtd);
+
+   return ret;
+}
+#endif
+
 static int do_mtd_bad(struct cmd_tbl *cmdtp, int flag, int argc,
  char *const argv[])
 {
@@ -773,6 +824,9 @@ U_BOOT_LONGHELP(mtd,
"mtd otpwrite\n"
"mtd otplock \n"
"mtd otpinfo[u|f]\n"
+#endif
+#ifdef CONFIG_CMD_MTD_MARKBAD
+   "mtd markbad [ 
...]\n"
 #endif
"\n"
"With:\n"
@@ -807,5 +861,9 @@ U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text,
 mtd_name_complete),
U_BOOT_SUBCMD_MKENT_COMPLETE(erase, 4, 0, do_mtd_erase,
 mtd_name_complete),
+#ifdef CONFIG_CMD_MTD_MARKBAD
+   U_BOOT_SUBCMD_MKENT_COMPLETE(markbad, 20, 0, do_mtd_markbad,
+mtd_name_complete),
+#endif
U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad,
 mtd_name_complete));
-- 
2.45.2



[PATCH v3 2/3] cmd: mtd: add nand_write_test command support

2024-10-01 Thread Mikhail Kshevetskiy
Some nand flashes (like spi-nand one) are registered with mtd
subsystem only, thus nand command can't be used to work with
such flashes. As result some functionality is missing.

This patch implements 'nand torture' functionality for mtd command.

Signed-off-by: Mikhail Kshevetskiy 
---
 cmd/Kconfig |  14 
 cmd/mtd.c   | 197 
 2 files changed, 211 insertions(+)

diff --git a/cmd/Kconfig b/cmd/Kconfig
index e830b731d24..ea0491d3155 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1457,6 +1457,20 @@ config CMD_MTD_MARKBAD
 
  This is a clone of "nand markbad" command, but for 'mtd' subsystem.
 
+config CMD_MTD_NAND_WRITE_TEST
+   bool "mtd nand_write_test (destructive)"
+   depends on CMD_MTD
+   help
+ MTD nand_write_test command support.
+
+ Writes blocks of NAND flash with different patterns.
+ This is useful to determine if a block that caused a write error
+ is still good or should be marked as bad.
+
+ This is a clone of "nand torture" command, but for 'mtd' subsystem.
+
+ WARNING: This test will destroy any data on blocks being tested.
+
 config CMD_MUX
bool "mux"
depends on MULTIPLEXER
diff --git a/cmd/mtd.c b/cmd/mtd.c
index 45084d39d3a..739a8a52cbd 100644
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
@@ -18,6 +18,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include 
 
@@ -742,6 +743,194 @@ out_put_mtd:
 }
 #endif
 
+#ifdef CONFIG_CMD_MTD_NAND_WRITE_TEST
+/**
+ * nand_check_pattern:
+ *
+ * Check if buffer contains only a certain byte pattern.
+ *
+ * @param buf buffer to check
+ * @param patt the pattern to check
+ * @param size buffer size in bytes
+ * Return: 1 if there are only patt bytes in buf
+ * 0 if something else was found
+ */
+static int nand_check_pattern(const u_char *buf, u_char patt, int size)
+{
+   int i;
+
+   for (i = 0; i < size; i++)
+   if (buf[i] != patt)
+   return 0;
+   return 1;
+}
+
+/**
+ * nand_write_test:
+ *
+ * Writes a block of NAND flash with different patterns.
+ * This is useful to determine if a block that caused a write error is still
+ * good or should be marked as bad.
+ *
+ * @param mtd nand mtd instance
+ * @param offset offset in flash
+ * Return: 0 if the block is still good
+ */
+static int nand_write_test(struct mtd_info *mtd, loff_t offset)
+{
+   u_char patterns[] = {0xa5, 0x5a, 0x00};
+   struct erase_info instr = {
+   .mtd = mtd,
+   .addr = offset,
+   .len = mtd->erasesize,
+   };
+   size_t retlen;
+   int err, ret = -1, i, patt_count;
+   u_char *buf;
+
+   if ((offset & (mtd->erasesize - 1)) != 0) {
+   puts("Attempt to torture a block at a non block-aligned 
offset\n");
+   return -EINVAL;
+   }
+
+   if (offset + mtd->erasesize > mtd->size) {
+   puts("Attempt to torture a block outside the flash area\n");
+   return -EINVAL;
+   }
+
+   patt_count = ARRAY_SIZE(patterns);
+
+   buf = malloc_cache_aligned(mtd->erasesize);
+   if (buf == NULL) {
+   puts("Out of memory for erase block buffer\n");
+   return -ENOMEM;
+   }
+
+   for (i = 0; i < patt_count; i++) {
+   err = mtd_erase(mtd, &instr);
+   if (err) {
+   printf("%s: erase() failed for block at 0x%llx: %d\n",
+  mtd->name, instr.addr, err);
+   goto out;
+   }
+
+   /* Make sure the block contains only 0xff bytes */
+   err = mtd_read(mtd, offset, mtd->erasesize, &retlen, buf);
+   if ((err && err != -EUCLEAN) || retlen != mtd->erasesize) {
+   printf("%s: read() failed for block at 0x%llx: %d\n",
+  mtd->name, instr.addr, err);
+   goto out;
+   }
+
+   err = nand_check_pattern(buf, 0xff, mtd->erasesize);
+   if (!err) {
+   printf("Erased block at 0x%llx, but a non-0xff byte was 
found\n",
+  offset);
+   ret = -EIO;
+   goto out;
+   }
+
+   /* Write a pattern and check it */
+   memset(buf, patterns[i], mtd->erasesize);
+   err = mtd_write(mtd, offset, mtd->erasesize, &retlen, buf);
+   if (err || retlen != mtd->erasesize) {
+   printf("%s: write() failed for block at 0x%llx: %d\n",
+  mtd->name, instr.addr, err);
+   goto out;
+   }
+
+ 

[PATCH v3 0/3] cmd/mtd: add missed featuries

2024-10-01 Thread Mikhail Kshevetskiy
Some nand flashes (like spi-nand one) are registered with mtd
subsystem only, thus nand command can't be used to work with
such flashes. As result some functionality is missing.

This patch series implements following subcommands:
 * markbad -- mark block as bad (clone of 'nand markbad')
 * nand_write_test -- destructive test of flash blocks (clone of 'nand torture')
 * nand_read_test  -- non-destructive test of nand flashes

Changes v2:
 * add cover letter

Changes v3:
 * rename 'mtd torture' to 'mtd nand_write_test'
 * rename 'mtd nandread' to 'mtd nand_read_test'
 * improve features description
 * code fixes suggested by Heinrich Schuchardt

Mikhail Kshevetskiy (3):
  cmd: mtd: add markbad command support
  cmd: mtd: add nand_write_test command support
  cmd: mtd: add nand_read_test command support

 cmd/Kconfig |  38 +
 cmd/mtd.c   | 448 
 2 files changed, 486 insertions(+)

-- 
2.45.2



[RESEND PATCH v4 10/10] mtd: nand: add initial ecc engine support

2024-10-01 Thread Mikhail Kshevetskiy
only spinand on_die ecc is supported for a moment

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/Makefile   |   2 +-
 drivers/mtd/nand/core.c | 130 +++-
 drivers/mtd/nand/ecc.c  | 249 ++
 drivers/mtd/nand/spi/core.c | 207 -
 drivers/mtd/nand/spi/foresee.c  |   2 +-
 drivers/mtd/nand/spi/macronix.c |   7 +-
 drivers/mtd/nand/spi/micron.c   |   2 +-
 drivers/mtd/nand/spi/toshiba.c  |  10 +-
 drivers/mtd/nand/spi/winbond.c  |  10 +-
 include/linux/mtd/nand.h| 261 ++--
 include/linux/mtd/spinand.h |  13 +-
 include/spi-mem.h   |   2 +
 12 files changed, 830 insertions(+), 65 deletions(-)
 create mode 100644 drivers/mtd/nand/ecc.c

diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 96e186600a1..56179188e92 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0+
 
 ifeq ($(CONFIG_SPL_BUILD)$(CONFIG_TPL_BUILD),)
-nandcore-objs := core.o bbt.o
+nandcore-objs := core.o bbt.o ecc.o
 obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
 obj-$(CONFIG_MTD_RAW_NAND) += raw/
 obj-$(CONFIG_MTD_SPI_NAND) += spi/
diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c
index 472ad0bdefb..6c90d576de3 100644
--- a/drivers/mtd/nand/core.c
+++ b/drivers/mtd/nand/core.c
@@ -129,7 +129,7 @@ EXPORT_SYMBOL_GPL(nanddev_isreserved);
  *
  * Return: 0 in case of success, a negative error code otherwise.
  */
-static int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos)
+int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos)
 {
unsigned int entry;
 
@@ -187,6 +187,134 @@ int nanddev_mtd_erase(struct mtd_info *mtd, struct 
erase_info *einfo)
 }
 EXPORT_SYMBOL_GPL(nanddev_mtd_erase);
 
+/**
+ * nanddev_get_ecc_engine() - Find and get a suitable ECC engine
+ * @nand: NAND device
+ */
+static int nanddev_get_ecc_engine(struct nand_device *nand)
+{
+   int engine_type;
+
+   /* Read the user desires in terms of ECC engine/configuration */
+   of_get_nand_ecc_user_config(nand);
+
+   engine_type = nand->ecc.user_conf.engine_type;
+   if (engine_type == NAND_ECC_ENGINE_TYPE_INVALID)
+   engine_type = nand->ecc.defaults.engine_type;
+
+   switch (engine_type) {
+   case NAND_ECC_ENGINE_TYPE_NONE:
+   return 0;
+   case NAND_ECC_ENGINE_TYPE_SOFT:
+   nand->ecc.engine = nand_ecc_get_sw_engine(nand);
+   break;
+   case NAND_ECC_ENGINE_TYPE_ON_DIE:
+   nand->ecc.engine = nand_ecc_get_on_die_hw_engine(nand);
+   break;
+   case NAND_ECC_ENGINE_TYPE_ON_HOST:
+   nand->ecc.engine = nand_ecc_get_on_host_hw_engine(nand);
+   if (PTR_ERR(nand->ecc.engine) == -EPROBE_DEFER)
+   return -EPROBE_DEFER;
+   break;
+   default:
+   pr_err("Missing ECC engine type\n");
+   }
+
+   if (!nand->ecc.engine)
+   return  -EINVAL;
+
+   return 0;
+}
+
+/**
+ * nanddev_put_ecc_engine() - Dettach and put the in-use ECC engine
+ * @nand: NAND device
+ */
+static int nanddev_put_ecc_engine(struct nand_device *nand)
+{
+   switch (nand->ecc.ctx.conf.engine_type) {
+   case NAND_ECC_ENGINE_TYPE_ON_HOST:
+   nand_ecc_put_on_host_hw_engine(nand);
+   break;
+   case NAND_ECC_ENGINE_TYPE_NONE:
+   case NAND_ECC_ENGINE_TYPE_SOFT:
+   case NAND_ECC_ENGINE_TYPE_ON_DIE:
+   default:
+   break;
+   }
+
+   return 0;
+}
+
+/**
+ * nanddev_find_ecc_configuration() - Find a suitable ECC configuration
+ * @nand: NAND device
+ */
+static int nanddev_find_ecc_configuration(struct nand_device *nand)
+{
+   int ret;
+
+   if (!nand->ecc.engine)
+   return -ENOTSUPP;
+
+   ret = nand_ecc_init_ctx(nand);
+   if (ret)
+   return ret;
+
+   if (!nand_ecc_is_strong_enough(nand))
+   pr_warn("WARNING: %s: the ECC used on your system is too weak 
compared to the one required by the NAND chip\n",
+   nand->mtd->name);
+
+   return 0;
+}
+
+/**
+ * nanddev_ecc_engine_init() - Initialize an ECC engine for the chip
+ * @nand: NAND device
+ */
+int nanddev_ecc_engine_init(struct nand_device *nand)
+{
+   int ret;
+
+   /* Look for the ECC engine to use */
+   ret = nanddev_get_ecc_engine(nand);
+   if (ret) {
+   if (ret != -EPROBE_DEFER)
+   pr_err("No ECC engine found\n");
+
+   return ret;
+   }
+
+   /* No ECC engine requested */
+   if (!nand->ecc.engine)
+   return 0;
+
+   /* Configure the engine: balance user input and chip requirements */
+   ret = nanddev_find_ecc_configuration(nand);
+   

[RESEND PATCH v4 09/10] mtd: spinand: sync supported flashes with linux-6.10

2024-10-01 Thread Mikhail Kshevetskiy
Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/Makefile |   4 +-
 drivers/mtd/nand/spi/alliancememory.c | 155 
 drivers/mtd/nand/spi/ato.c|  84 +++
 drivers/mtd/nand/spi/core.c   |   5 +-
 drivers/mtd/nand/spi/esmt.c   |  16 ++-
 drivers/mtd/nand/spi/foresee.c|  97 +
 drivers/mtd/nand/spi/gigadevice.c | 194 +-
 drivers/mtd/nand/spi/macronix.c   |  25 +++-
 drivers/mtd/nand/spi/toshiba.c|  33 +
 drivers/mtd/nand/spi/winbond.c|  57 
 include/linux/mtd/spinand.h   |   5 +-
 11 files changed, 664 insertions(+), 11 deletions(-)
 create mode 100644 drivers/mtd/nand/spi/alliancememory.c
 create mode 100644 drivers/mtd/nand/spi/ato.c
 create mode 100644 drivers/mtd/nand/spi/foresee.c

diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
index 65b836b34ca..d438747cf37 100644
--- a/drivers/mtd/nand/spi/Makefile
+++ b/drivers/mtd/nand/spi/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
 
-spinand-objs := core.o esmt.o gigadevice.o macronix.o micron.o paragon.o
-spinand-objs += toshiba.o winbond.o xtx.o
+spinand-objs := core.o alliancememory.o ato.o esmt.o foresee.o gigadevice.o 
macronix.o
+spinand-objs += micron.o paragon.o toshiba.o winbond.o xtx.o
 obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
diff --git a/drivers/mtd/nand/spi/alliancememory.c 
b/drivers/mtd/nand/spi/alliancememory.c
new file mode 100644
index 000..e29e4cc77ec
--- /dev/null
+++ b/drivers/mtd/nand/spi/alliancememory.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author: Mario Kicherer 
+ */
+
+#ifndef __UBOOT__
+#include 
+#include 
+#endif
+#include 
+
+#define SPINAND_MFR_ALLIANCEMEMORY 0x52
+
+#define AM_STATUS_ECC_BITMASK  (3 << 4)
+
+#define AM_STATUS_ECC_NONE_DETECTED(0 << 4)
+#define AM_STATUS_ECC_CORRECTED(1 << 4)
+#define AM_STATUS_ECC_ERRORED  (2 << 4)
+#define AM_STATUS_ECC_MAX_CORRECTED(3 << 4)
+
+static SPINAND_OP_VARIANTS(read_cache_variants,
+   SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0),
+   SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
+   SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
+   SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
+   SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
+   SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
+
+static SPINAND_OP_VARIANTS(write_cache_variants,
+  SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
+  SPINAND_PROG_LOAD(true, 0, NULL, 0));
+
+static SPINAND_OP_VARIANTS(update_cache_variants,
+  SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
+  SPINAND_PROG_LOAD(false, 0, NULL, 0));
+
+static int am_get_eccsize(struct mtd_info *mtd)
+{
+   if (mtd->oobsize == 64)
+   return 0x20;
+   else if (mtd->oobsize == 128)
+   return 0x38;
+   else if (mtd->oobsize == 256)
+   return 0x70;
+   else
+   return -EINVAL;
+}
+
+static int am_ooblayout_ecc(struct mtd_info *mtd, int section,
+   struct mtd_oob_region *region)
+{
+   int ecc_bytes;
+
+   ecc_bytes = am_get_eccsize(mtd);
+   if (ecc_bytes < 0)
+   return ecc_bytes;
+
+   region->offset = mtd->oobsize - ecc_bytes;
+   region->length = ecc_bytes;
+
+   return 0;
+}
+
+static int am_ooblayout_free(struct mtd_info *mtd, int section,
+struct mtd_oob_region *region)
+{
+   int ecc_bytes;
+
+   if (section)
+   return -ERANGE;
+
+   ecc_bytes = am_get_eccsize(mtd);
+   if (ecc_bytes < 0)
+   return ecc_bytes;
+
+   /*
+* It is unclear how many bytes are used for the bad block marker. We
+* reserve the common two bytes here.
+*
+* The free area in this kind of flash is divided into chunks where the
+* first 4 bytes of each chunk are unprotected. The number of chunks
+* depends on the specific model. The models with 4096+256 bytes pages
+* have 8 chunks, the others 4 chunks.
+*/
+
+   region->offset = 2;
+   region->length = mtd->oobsize - 2 - ecc_bytes;
+
+   return 0;
+}
+
+static const struct mtd_ooblayout_ops am_ooblayout = {
+   .ecc = am_ooblayout_ecc,
+   .rfree = am_ooblayout_free,
+};
+
+static int am_ecc_get_status(struct spinand_device *spinand, u8 status)
+{
+   switch (status & AM_STATUS_ECC_BITMASK) {
+   case AM_STATUS_ECC_NONE_DETECTED:
+   return 0;
+
+   case AM_STATUS_ECC_CORRECTED:
+   /*
+* use oobsize to determine the flash model and the maximum of
+  

[RESEND PATCH v4 08/10] mtd: spinand: more refactoring

2024-10-01 Thread Mikhail Kshevetskiy
changes:
 * Move spinand_check_ecc_status(), spinand_noecc_ooblayout_ecc(),
   spinand_noecc_ooblayout_free() and spinand_noecc_ooblayout close
   to each other.
 * some code formatting
 * remove comments not present in linux driver

This make code more close to linux-6.10 kernel driver

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 115 +---
 1 file changed, 55 insertions(+), 60 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 6ca8b7c80cc..548a7144ee3 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -222,6 +222,59 @@ static int spinand_ecc_enable(struct spinand_device 
*spinand,
   enable ? CFG_ECC_ENABLE : 0);
 }
 
+static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status)
+{
+   struct nand_device *nand = spinand_to_nand(spinand);
+
+   if (spinand->eccinfo.get_status)
+   return spinand->eccinfo.get_status(spinand, status);
+
+   switch (status & STATUS_ECC_MASK) {
+   case STATUS_ECC_NO_BITFLIPS:
+   return 0;
+
+   case STATUS_ECC_HAS_BITFLIPS:
+   /*
+* We have no way to know exactly how many bitflips have been
+* fixed, so let's return the maximum possible value so that
+* wear-leveling layers move the data immediately.
+*/
+   return nand->eccreq.strength;
+
+   case STATUS_ECC_UNCOR_ERROR:
+   return -EBADMSG;
+
+   default:
+   break;
+   }
+
+   return -EINVAL;
+}
+
+static int spinand_noecc_ooblayout_ecc(struct mtd_info *mtd, int section,
+  struct mtd_oob_region *region)
+{
+   return -ERANGE;
+}
+
+static int spinand_noecc_ooblayout_free(struct mtd_info *mtd, int section,
+   struct mtd_oob_region *region)
+{
+   if (section)
+   return -ERANGE;
+
+   /* Reserve 2 bytes for the BBM. */
+   region->offset = 2;
+   region->length = 62;
+
+   return 0;
+}
+
+static const struct mtd_ooblayout_ops spinand_noecc_ooblayout = {
+   .ecc = spinand_noecc_ooblayout_ecc,
+   .rfree = spinand_noecc_ooblayout_free,
+};
+
 static int spinand_write_enable_op(struct spinand_device *spinand)
 {
struct spi_mem_op op = SPINAND_WR_EN_DIS_OP(true);
@@ -413,9 +466,8 @@ out:
 static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr,
  u8 ndummy, u8 *buf)
 {
-   struct spi_mem_op op = SPINAND_READID_OP(naddr, ndummy,
-spinand->scratchbuf,
-SPINAND_MAX_ID_LEN);
+   struct spi_mem_op op = SPINAND_READID_OP(
+   naddr, ndummy, spinand->scratchbuf, SPINAND_MAX_ID_LEN);
int ret;
 
ret = spi_mem_exec_op(spinand->slave, &op);
@@ -445,35 +497,6 @@ static int spinand_lock_block(struct spinand_device 
*spinand, u8 lock)
return spinand_write_reg_op(spinand, REG_BLOCK_LOCK, lock);
 }
 
-static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status)
-{
-   struct nand_device *nand = spinand_to_nand(spinand);
-
-   if (spinand->eccinfo.get_status)
-   return spinand->eccinfo.get_status(spinand, status);
-
-   switch (status & STATUS_ECC_MASK) {
-   case STATUS_ECC_NO_BITFLIPS:
-   return 0;
-
-   case STATUS_ECC_HAS_BITFLIPS:
-   /*
-* We have no way to know exactly how many bitflips have been
-* fixed, so let's return the maximum possible value so that
-* wear-leveling layers move the data immediately.
-*/
-   return nand->eccreq.strength;
-
-   case STATUS_ECC_UNCOR_ERROR:
-   return -EBADMSG;
-
-   default:
-   break;
-   }
-
-   return -EINVAL;
-}
-
 static int spinand_read_page(struct spinand_device *spinand,
 const struct nand_page_io_req *req,
 bool ecc_enabled)
@@ -1056,30 +1079,6 @@ static int spinand_detect(struct spinand_device *spinand)
return 0;
 }
 
-static int spinand_noecc_ooblayout_ecc(struct mtd_info *mtd, int section,
-  struct mtd_oob_region *region)
-{
-   return -ERANGE;
-}
-
-static int spinand_noecc_ooblayout_free(struct mtd_info *mtd, int section,
-   struct mtd_oob_region *region)
-{
-   if (section)
-   return -ERANGE;
-
-   /* Reserve 2 bytes for the BBM. */
-   region->offset = 2;
-   region->length = 62;
-
-   return 0;
-}
-
-static const struct mtd_ooblayout_ops spinand_noecc_ooblayout = {
-   .ecc = spinand_noecc_ooblayout_ecc,
-  

[RESEND PATCH v4 07/10] mtd: spinand: minor refactoring

2024-10-01 Thread Mikhail Kshevetskiy
No functional changes, just some refactoring to match better linux
kernel driver.

changes:
 * move spinand configuration reading out from spinand_init_cfg_cache()
   to separate function spinand_read_cfg()
 * move spinand flash initialization to separate function
   spinand_init_flash()
 * move direct mapping initialization to the end of spinand_init()

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 112 ++--
 1 file changed, 70 insertions(+), 42 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 9629fac3388..6ca8b7c80cc 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -160,20 +160,12 @@ int spinand_select_target(struct spinand_device *spinand, 
unsigned int target)
return 0;
 }
 
-static int spinand_init_cfg_cache(struct spinand_device *spinand)
+static int spinand_read_cfg(struct spinand_device *spinand)
 {
struct nand_device *nand = spinand_to_nand(spinand);
-   struct udevice *dev = spinand->slave->dev;
unsigned int target;
int ret;
 
-   spinand->cfg_cache = devm_kzalloc(dev,
- sizeof(*spinand->cfg_cache) *
- nand->memorg.ntargets,
- GFP_KERNEL);
-   if (!spinand->cfg_cache)
-   return -ENOMEM;
-
for (target = 0; target < nand->memorg.ntargets; target++) {
ret = spinand_select_target(spinand, target);
if (ret)
@@ -192,6 +184,21 @@ static int spinand_init_cfg_cache(struct spinand_device 
*spinand)
return 0;
 }
 
+static int spinand_init_cfg_cache(struct spinand_device *spinand)
+{
+   struct nand_device *nand = spinand_to_nand(spinand);
+   struct udevice *dev = spinand->slave->dev;
+
+   spinand->cfg_cache = devm_kcalloc(dev,
+ nand->memorg.ntargets,
+ sizeof(*spinand->cfg_cache),
+ GFP_KERNEL);
+   if (!spinand->cfg_cache)
+   return -ENOMEM;
+
+   return 0;
+}
+
 static int spinand_init_quad_enable(struct spinand_device *spinand)
 {
bool enable = false;
@@ -1073,11 +1080,55 @@ static const struct mtd_ooblayout_ops 
spinand_noecc_ooblayout = {
.rfree = spinand_noecc_ooblayout_free,
 };
 
+static int spinand_init_flash(struct spinand_device *spinand)
+{
+   struct udevice *dev = spinand->slave->dev;
+   struct nand_device *nand = spinand_to_nand(spinand);
+   int ret, i;
+
+   ret = spinand_read_cfg(spinand);
+   if (ret)
+   return ret;
+
+   ret = spinand_init_quad_enable(spinand);
+   if (ret)
+   return ret;
+
+   ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0);
+   if (ret)
+   return ret;
+
+   ret = spinand_manufacturer_init(spinand);
+   if (ret) {
+   dev_err(dev,
+   "Failed to initialize the SPI NAND chip (err = %d)\n",
+   ret);
+   return ret;
+   }
+
+   /* After power up, all blocks are locked, so unlock them here. */
+   for (i = 0; i < nand->memorg.ntargets; i++) {
+   ret = spinand_select_target(spinand, i);
+   if (ret)
+   break;
+
+   ret = spinand_lock_block(spinand, BL_ALL_UNLOCKED);
+   if (ret)
+   break;
+   }
+
+   if (ret)
+   spinand_manufacturer_cleanup(spinand);
+
+   return ret;
+}
+
 static int spinand_init(struct spinand_device *spinand)
 {
+   struct udevice *dev = spinand->slave->dev;
struct mtd_info *mtd = spinand_to_mtd(spinand);
struct nand_device *nand = mtd_to_nanddev(mtd);
-   int ret, i;
+   int ret;
 
/*
 * We need a scratch buffer because the spi_mem interface requires that
@@ -1110,41 +1161,10 @@ static int spinand_init(struct spinand_device *spinand)
if (ret)
goto err_free_bufs;
 
-   ret = spinand_init_quad_enable(spinand);
+   ret = spinand_init_flash(spinand);
if (ret)
goto err_free_bufs;
 
-   ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0);
-   if (ret)
-   goto err_free_bufs;
-
-   ret = spinand_manufacturer_init(spinand);
-   if (ret) {
-   dev_err(spinand->slave->dev,
-   "Failed to initialize the SPI NAND chip (err = %d)\n",
-   ret);
-   goto err_free_bufs;
-   }
-
-   ret = spinand_create_dirmaps(spinand);
-   if (ret) {
-   dev_err(spinand->slave->dev,
-   "Failed to create direct mappings for read/write 
operations (err = %d)\n",
-

[RESEND PATCH v4 06/10] mtd: spinand: replace enable_ecc variable with disable_ecc and update corresponding logic

2024-10-01 Thread Mikhail Kshevetskiy
Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 24 +++-
 1 file changed, 15 insertions(+), 9 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index b58d9e00907..9629fac3388 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -530,12 +530,12 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t 
from,
struct nand_device *nand = mtd_to_nanddev(mtd);
unsigned int max_bitflips = 0;
struct nand_io_iter iter;
-   bool enable_ecc = false;
+   bool disable_ecc = false;
bool ecc_failed = false;
int ret = 0;
 
-   if (ops->mode != MTD_OPS_RAW && spinand->eccinfo.ooblayout)
-   enable_ecc = true;
+   if (ops->mode == MTD_OPS_RAW || !spinand->eccinfo.ooblayout)
+   disable_ecc = true;
 
 #ifndef __UBOOT__
mutex_lock(&spinand->lock);
@@ -543,15 +543,18 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t 
from,
 
nanddev_io_for_each_page(nand, NAND_PAGE_READ, from, ops, &iter) {
schedule();
+   if (disable_ecc)
+   iter.req.mode = MTD_OPS_RAW;
+
ret = spinand_select_target(spinand, iter.req.pos.target);
if (ret)
break;
 
-   ret = spinand_ecc_enable(spinand, enable_ecc);
+   ret = spinand_ecc_enable(spinand, !disable_ecc);
if (ret)
break;
 
-   ret = spinand_read_page(spinand, &iter.req, enable_ecc);
+   ret = spinand_read_page(spinand, &iter.req, !disable_ecc);
if (ret < 0 && ret != -EBADMSG)
break;
 
@@ -583,11 +586,11 @@ static int spinand_mtd_write(struct mtd_info *mtd, loff_t 
to,
struct spinand_device *spinand = mtd_to_spinand(mtd);
struct nand_device *nand = mtd_to_nanddev(mtd);
struct nand_io_iter iter;
-   bool enable_ecc = false;
+   bool disable_ecc = false;
int ret = 0;
 
-   if (ops->mode != MTD_OPS_RAW && mtd->ooblayout)
-   enable_ecc = true;
+   if (ops->mode == MTD_OPS_RAW || !mtd->ooblayout)
+   disable_ecc = true;
 
 #ifndef __UBOOT__
mutex_lock(&spinand->lock);
@@ -595,11 +598,14 @@ static int spinand_mtd_write(struct mtd_info *mtd, loff_t 
to,
 
nanddev_io_for_each_page(nand, NAND_PAGE_WRITE, to, ops, &iter) {
schedule();
+   if (disable_ecc)
+   iter.req.mode = MTD_OPS_RAW;
+
ret = spinand_select_target(spinand, iter.req.pos.target);
if (ret)
break;
 
-   ret = spinand_ecc_enable(spinand, enable_ecc);
+   ret = spinand_ecc_enable(spinand, !disable_ecc);
if (ret)
break;
 
-- 
2.45.2



[RESEND PATCH v4 04/10] mtd: spinand: simulate behavior of linux's function spinand_wait()

2024-10-01 Thread Mikhail Kshevetskiy
also call schedule() to allow periodic actions

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 35 ---
 include/linux/mtd/spinand.h | 22 ++
 2 files changed, 50 insertions(+), 7 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 62779dd3e51..a10605487f3 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -32,6 +32,7 @@
 #include 
 #include 
 #include 
+#include 
 #endif
 
 struct spinand_plat {
@@ -362,21 +363,29 @@ static int spinand_erase_op(struct spinand_device 
*spinand,
return spi_mem_exec_op(spinand->slave, &op);
 }
 
-static int spinand_wait(struct spinand_device *spinand, u8 *s)
+static int spinand_wait(struct spinand_device *spinand,
+   unsigned long initial_delay_us,
+   unsigned long poll_delay_us,
+   u8 *s)
 {
unsigned long start, stop;
u8 status;
int ret;
 
+   udelay(initial_delay_us);
start = get_timer(0);
-   stop = 400;
+   stop = SPINAND_WAITRDY_TIMEOUT_MS;
do {
+   schedule();
+
ret = spinand_read_status(spinand, &status);
if (ret)
return ret;
 
if (!(status & STATUS_BUSY))
goto out;
+
+   udelay(poll_delay_us);
} while (get_timer(start) < stop);
 
/*
@@ -418,7 +427,10 @@ static int spinand_reset_op(struct spinand_device *spinand)
if (ret)
return ret;
 
-   return spinand_wait(spinand, NULL);
+   return spinand_wait(spinand,
+   SPINAND_RESET_INITIAL_DELAY_US,
+   SPINAND_RESET_POLL_DELAY_US,
+   NULL);
 }
 
 static int spinand_lock_block(struct spinand_device *spinand, u8 lock)
@@ -466,7 +478,10 @@ static int spinand_read_page(struct spinand_device 
*spinand,
if (ret)
return ret;
 
-   ret = spinand_wait(spinand, &status);
+   ret = spinand_wait(spinand,
+  SPINAND_READ_INITIAL_DELAY_US,
+  SPINAND_READ_POLL_DELAY_US,
+  &status);
if (ret < 0)
return ret;
 
@@ -498,9 +513,12 @@ static int spinand_write_page(struct spinand_device 
*spinand,
if (ret)
return ret;
 
-   ret = spinand_wait(spinand, &status);
+   ret = spinand_wait(spinand,
+  SPINAND_WRITE_INITIAL_DELAY_US,
+  SPINAND_WRITE_POLL_DELAY_US,
+  &status);
if (!ret && (status & STATUS_PROG_FAILED))
-   ret = -EIO;
+   return -EIO;
 
return ret;
 }
@@ -702,7 +720,10 @@ static int spinand_erase(struct nand_device *nand, const 
struct nand_pos *pos)
if (ret)
return ret;
 
-   ret = spinand_wait(spinand, &status);
+   ret = spinand_wait(spinand,
+  SPINAND_ERASE_INITIAL_DELAY_US,
+  SPINAND_ERASE_POLL_DELAY_US,
+  &status);
if (!ret && (status & STATUS_ERASE_FAILED))
ret = -EIO;
 
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index 5934b7604cc..b701d25f73d 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -176,6 +176,28 @@ struct spinand_op;
 struct spinand_device;
 
 #define SPINAND_MAX_ID_LEN 4
+/*
+ * For erase, write and read operation, we got the following timings :
+ * tBERS (erase) 1ms to 4ms
+ * tPROG 300us to 400us
+ * tREAD 25us to 100us
+ * In order to minimize latency, the min value is divided by 4 for the
+ * initial delay, and dividing by 20 for the poll delay.
+ * For reset, 5us/10us/500us if the device is respectively
+ * reading/programming/erasing when the RESET occurs. Since we always
+ * issue a RESET when the device is IDLE, 5us is selected for both initial
+ * and poll delay.
+ */
+#define SPINAND_READ_INITIAL_DELAY_US  6
+#define SPINAND_READ_POLL_DELAY_US 5
+#define SPINAND_RESET_INITIAL_DELAY_US 5
+#define SPINAND_RESET_POLL_DELAY_US5
+#define SPINAND_WRITE_INITIAL_DELAY_US 75
+#define SPINAND_WRITE_POLL_DELAY_US15
+#define SPINAND_ERASE_INITIAL_DELAY_US 250
+#define SPINAND_ERASE_POLL_DELAY_US50
+
+#define SPINAND_WAITRDY_TIMEOUT_MS 400
 
 /**
  * struct spinand_id - SPI NAND id structure
-- 
2.45.2



[RESEND PATCH v4 05/10] mtd: spinand: more use of spinand_to_{something} helpers

2024-10-01 Thread Mikhail Kshevetskiy
Use spinand_to_nand() and spinand_to_mtd() helpers instead of
nanddev_to_mtd() and direct access to spinand structure fields.

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index a10605487f3..b58d9e00907 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -236,7 +236,7 @@ static int spinand_read_from_cache_op(struct spinand_device 
*spinand,
  const struct nand_page_io_req *req)
 {
struct nand_device *nand = spinand_to_nand(spinand);
-   struct mtd_info *mtd = nanddev_to_mtd(nand);
+   struct mtd_info *mtd = spinand_to_mtd(spinand);
struct spi_mem_dirmap_desc *rdesc;
unsigned int nbytes = 0;
void *buf = NULL;
@@ -294,7 +294,7 @@ static int spinand_write_to_cache_op(struct spinand_device 
*spinand,
 const struct nand_page_io_req *req)
 {
struct nand_device *nand = spinand_to_nand(spinand);
-   struct mtd_info *mtd = nanddev_to_mtd(nand);
+   struct mtd_info *mtd = spinand_to_mtd(spinand);
struct spi_mem_dirmap_desc *wdesc;
unsigned int nbytes, column = 0;
void *buf = spinand->databuf;
@@ -356,7 +356,7 @@ static int spinand_program_op(struct spinand_device 
*spinand,
 static int spinand_erase_op(struct spinand_device *spinand,
const struct nand_pos *pos)
 {
-   struct nand_device *nand = &spinand->base;
+   struct nand_device *nand = spinand_to_nand(spinand);
unsigned int row = nanddev_pos_to_row(nand, pos);
struct spi_mem_op op = SPINAND_BLK_ERASE_OP(row);
 
-- 
2.45.2



[RESEND PATCH v4 02/10] mtd: spinand: Add a NAND page I/O request type

2024-10-01 Thread Mikhail Kshevetskiy
Use an enum to differentiate the type of I/O (reading or writing a
page). Also update the request iterator.

This is a port of linux patch 701981cab01696584a12e5f0e7c2ad931a326059
created by Miquel Raynal 

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c |  4 ++--
 include/linux/mtd/nand.h| 18 --
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index ea00cd7dcf0..8f227ce81fa 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -523,7 +523,7 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t 
from,
mutex_lock(&spinand->lock);
 #endif
 
-   nanddev_io_for_each_page(nand, from, ops, &iter) {
+   nanddev_io_for_each_page(nand, NAND_PAGE_READ, from, ops, &iter) {
schedule();
ret = spinand_select_target(spinand, iter.req.pos.target);
if (ret)
@@ -575,7 +575,7 @@ static int spinand_mtd_write(struct mtd_info *mtd, loff_t 
to,
mutex_lock(&spinand->lock);
 #endif
 
-   nanddev_io_for_each_page(nand, to, ops, &iter) {
+   nanddev_io_for_each_page(nand, NAND_PAGE_WRITE, to, ops, &iter) {
schedule();
ret = spinand_select_target(spinand, iter.req.pos.target);
if (ret)
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 651f8706df5..0afdaed5715 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -80,8 +80,19 @@ struct nand_pos {
unsigned int page;
 };
 
+/**
+ * enum nand_page_io_req_type - Direction of an I/O request
+ * @NAND_PAGE_READ: from the chip, to the controller
+ * @NAND_PAGE_WRITE: from the controller, to the chip
+ */
+enum nand_page_io_req_type {
+   NAND_PAGE_READ = 0,
+   NAND_PAGE_WRITE,
+};
+
 /**
  * struct nand_page_io_req - NAND I/O request object
+ * @type: the type of page I/O: read or write
  * @pos: the position this I/O request is targeting
  * @dataoffs: the offset within the page
  * @datalen: number of data bytes to read from/write to this page
@@ -97,6 +108,7 @@ struct nand_pos {
  * specific commands/operations.
  */
 struct nand_page_io_req {
+   enum nand_page_io_req_type type;
struct nand_pos pos;
unsigned int dataoffs;
unsigned int datalen;
@@ -613,11 +625,13 @@ static inline void nanddev_pos_next_page(struct 
nand_device *nand,
  * layer.
  */
 static inline void nanddev_io_iter_init(struct nand_device *nand,
+   enum nand_page_io_req_type reqtype,
loff_t offs, struct mtd_oob_ops *req,
struct nand_io_iter *iter)
 {
struct mtd_info *mtd = nanddev_to_mtd(nand);
 
+   iter->req.type = reqtype;
iter->req.mode = req->mode;
iter->req.dataoffs = nanddev_offs_to_pos(nand, offs, &iter->req.pos);
iter->req.ooboffs = req->ooboffs;
@@ -687,8 +701,8 @@ static inline bool nanddev_io_iter_end(struct nand_device 
*nand,
  *
  * Should be used for iterate over pages that are contained in an MTD request.
  */
-#define nanddev_io_for_each_page(nand, start, req, iter)   \
-   for (nanddev_io_iter_init(nand, start, req, iter);  \
+#define nanddev_io_for_each_page(nand, type, start, req, iter) \
+   for (nanddev_io_iter_init(nand, type, start, req, iter);\
 !nanddev_io_iter_end(nand, iter);  \
 nanddev_io_iter_next_page(nand, iter))
 
-- 
2.45.2



[RESEND PATCH v4 03/10] mtd: spinand: add missed add missed MODULE_DEVICE_TABLE()

2024-10-01 Thread Mikhail Kshevetskiy
Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 8f227ce81fa..62779dd3e51 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1267,12 +1267,14 @@ static const struct spi_device_id spinand_ids[] = {
{ .name = "spi-nand" },
{ /* sentinel */ },
 };
+MODULE_DEVICE_TABLE(spi, spinand_ids);
 
 #ifdef CONFIG_OF
 static const struct of_device_id spinand_of_ids[] = {
{ .compatible = "spi-nand" },
{ /* sentinel */ },
 };
+MODULE_DEVICE_TABLE(of, spinand_of_ids);
 #endif
 
 static struct spi_mem_driver spinand_drv = {
-- 
2.45.2



[RESEND PATCH v4 01/10] mtd: spinand: Use the spi-mem dirmap API

2024-10-01 Thread Mikhail Kshevetskiy
Make use of the spi-mem direct mapping API to let advanced controllers
optimize read/write operations when they support direct mapping.

This is a port of linux patch 981d1aa0697ce1393e00933f154d181e965703d0
created by Boris Brezillon .

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 185 +---
 include/linux/mtd/spinand.h |   7 ++
 2 files changed, 95 insertions(+), 97 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index f5ddfbf4b83..ea00cd7dcf0 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -41,21 +41,6 @@ struct spinand_plat {
 /* SPI NAND index visible in MTD names */
 static int spi_nand_idx;
 
-static void spinand_cache_op_adjust_colum(struct spinand_device *spinand,
- const struct nand_page_io_req *req,
- u16 *column)
-{
-   struct nand_device *nand = spinand_to_nand(spinand);
-   unsigned int shift;
-
-   if (nand->memorg.planes_per_lun < 2)
-   return;
-
-   /* The plane number is passed in MSB just above the column address */
-   shift = fls(nand->memorg.pagesize);
-   *column |= req->pos.plane << shift;
-}
-
 static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
 {
struct spi_mem_op op = SPINAND_GET_FEATURE_OP(reg,
@@ -249,27 +234,21 @@ static int spinand_load_page_op(struct spinand_device 
*spinand,
 static int spinand_read_from_cache_op(struct spinand_device *spinand,
  const struct nand_page_io_req *req)
 {
-   struct spi_mem_op op = *spinand->op_templates.read_cache;
struct nand_device *nand = spinand_to_nand(spinand);
struct mtd_info *mtd = nanddev_to_mtd(nand);
-   struct nand_page_io_req adjreq = *req;
+   struct spi_mem_dirmap_desc *rdesc;
unsigned int nbytes = 0;
void *buf = NULL;
u16 column = 0;
-   int ret;
+   ssize_t ret;
 
if (req->datalen) {
-   adjreq.datalen = nanddev_page_size(nand);
-   adjreq.dataoffs = 0;
-   adjreq.databuf.in = spinand->databuf;
buf = spinand->databuf;
-   nbytes = adjreq.datalen;
+   nbytes = nanddev_page_size(nand);
+   column = 0;
}
 
if (req->ooblen) {
-   adjreq.ooblen = nanddev_per_page_oobsize(nand);
-   adjreq.ooboffs = 0;
-   adjreq.oobbuf.in = spinand->oobbuf;
nbytes += nanddev_per_page_oobsize(nand);
if (!buf) {
buf = spinand->oobbuf;
@@ -277,28 +256,19 @@ static int spinand_read_from_cache_op(struct 
spinand_device *spinand,
}
}
 
-   spinand_cache_op_adjust_colum(spinand, &adjreq, &column);
-   op.addr.val = column;
+   rdesc = spinand->dirmaps[req->pos.plane].rdesc;
 
-   /*
-* Some controllers are limited in term of max RX data size. In this
-* case, just repeat the READ_CACHE operation after updating the
-* column.
-*/
while (nbytes) {
-   op.data.buf.in = buf;
-   op.data.nbytes = nbytes;
-   ret = spi_mem_adjust_op_size(spinand->slave, &op);
-   if (ret)
+   ret = spi_mem_dirmap_read(rdesc, column, nbytes, buf);
+   if (ret < 0)
return ret;
 
-   ret = spi_mem_exec_op(spinand->slave, &op);
-   if (ret)
-   return ret;
+   if (!ret || ret > nbytes)
+   return -EIO;
 
-   buf += op.data.nbytes;
-   nbytes -= op.data.nbytes;
-   op.addr.val += op.data.nbytes;
+   nbytes -= ret;
+   column += ret;
+   buf += ret;
}
 
if (req->datalen)
@@ -322,14 +292,12 @@ static int spinand_read_from_cache_op(struct 
spinand_device *spinand,
 static int spinand_write_to_cache_op(struct spinand_device *spinand,
 const struct nand_page_io_req *req)
 {
-   struct spi_mem_op op = *spinand->op_templates.write_cache;
struct nand_device *nand = spinand_to_nand(spinand);
struct mtd_info *mtd = nanddev_to_mtd(nand);
-   struct nand_page_io_req adjreq = *req;
-   unsigned int nbytes = 0;
-   void *buf = NULL;
-   u16 column = 0;
-   int ret;
+   struct spi_mem_dirmap_desc *wdesc;
+   unsigned int nbytes, column = 0;
+   void *buf = spinand->databuf;
+   ssize_t ret;
 
/*
 * Looks like PROGRAM LOAD (AKA write cache) does not necessarily reset
@@ -338,19 +306,12 @@ static int spinand_write_to_cache_op(struct 
spinand_device *spinand,
 * the data portion of the page, otherwise 

[RESEND PATCH v4 00/10] mtd: spinand: initial support of ecc engines

2024-10-01 Thread Mikhail Kshevetskiy
This patch series:
 * sync spinand driver code with linux-6.10
 * sync spinand flash support with linux-6.10
 * add initial support of ecc engines

Up to now only software ecc is supported, but other engines can be add quite 
easily

Changes v2
 * update description of some patches

Changes v3:
 * split some patches to a smaller one for more easy checking/verification
 * sync spinand flash support with linux-6.10
 * add some ecc engine comments (taken from linux driver)
 * slightly change patch order

Changes v4:
 * avoid double increments of error counters

Mikhail Kshevetskiy (10):
  mtd: spinand: Use the spi-mem dirmap API
  mtd: spinand: Add a NAND page I/O request type
  mtd: spinand: add missed add missed MODULE_DEVICE_TABLE()
  mtd: spinand: simulate behavior of linux's function spinand_wait()
  mtd: spinand: more use of spinand_to_{something} helpers
  mtd: spinand: replace enable_ecc variable with disable_ecc and update
corresponding logic
  mtd: spinand: minor refactoring
  mtd: spinand: more refactoring
  mtd: spinand: sync supported flashes with linux-6.10
  mtd: nand: add initial ecc engine support

 drivers/mtd/nand/Makefile |   2 +-
 drivers/mtd/nand/core.c   | 130 +-
 drivers/mtd/nand/ecc.c| 249 ++
 drivers/mtd/nand/spi/Makefile |   4 +-
 drivers/mtd/nand/spi/alliancememory.c | 155 ++
 drivers/mtd/nand/spi/ato.c|  84 
 drivers/mtd/nand/spi/core.c   | 649 --
 drivers/mtd/nand/spi/esmt.c   |  16 +-
 drivers/mtd/nand/spi/foresee.c|  97 
 drivers/mtd/nand/spi/gigadevice.c | 194 +++-
 drivers/mtd/nand/spi/macronix.c   |  32 +-
 drivers/mtd/nand/spi/micron.c |   2 +-
 drivers/mtd/nand/spi/toshiba.c|  43 +-
 drivers/mtd/nand/spi/winbond.c|  67 ++-
 include/linux/mtd/nand.h  | 279 ++-
 include/linux/mtd/spinand.h   |  47 +-
 include/spi-mem.h |   2 +
 17 files changed, 1778 insertions(+), 274 deletions(-)
 create mode 100644 drivers/mtd/nand/ecc.c
 create mode 100644 drivers/mtd/nand/spi/alliancememory.c
 create mode 100644 drivers/mtd/nand/spi/ato.c
 create mode 100644 drivers/mtd/nand/spi/foresee.c

-- 
2.45.2



[PATCH v9 12/13] net/httpd: add httpd common code

2024-10-01 Thread Mikhail Kshevetskiy
This patch adds HTTP/1.1 compatible web-server that can be used
by other. Server supports GET, POST, and HEAD requests. On client
request it will call user specified GET/POST callback. Then results
will be transmitted to client.

The following restrictions exist on the POST request
at the moment:
  * only multipart/form-data with a single file object
  * object will be stored to a memory area specified in
image_load_addr variable

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net.h   |   2 +-
 include/net/httpd.h |  71 +
 net/Kconfig |  14 +
 net/Makefile|   1 +
 net/httpd.c | 735 
 net/net.c   |   6 +
 6 files changed, 828 insertions(+), 1 deletion(-)
 create mode 100644 include/net/httpd.h
 create mode 100644 net/httpd.c

diff --git a/include/net.h b/include/net.h
index 0af6493788a..154885a2b7e 100644
--- a/include/net.h
+++ b/include/net.h
@@ -515,7 +515,7 @@ extern int  net_restart_wrap;   /* Tried all 
network devices */
 enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
-   WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, RS
+   WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, HTTPD, RS
 };
 
 extern charnet_boot_file_name[1024];/* Boot File name */
diff --git a/include/net/httpd.h b/include/net/httpd.h
new file mode 100644
index 000..ef6c37ece7c
--- /dev/null
+++ b/include/net/httpd.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * httpd support header file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ *
+ */
+#ifndef __NET_HTTPD_COMMON_H__
+#define __NET_HTTPD_COMMON_H__
+
+struct tcp_stream;
+
+struct http_reply {
+   int code;
+   const char  *code_msg;
+   const char  *data_type;
+   void*data;
+   u32 len;
+};
+
+struct httpd_post_data {
+   const char  *name;
+   const char  *filename;
+   void*addr;
+   u32 size;
+};
+
+enum httpd_req_check {
+   HTTPD_REQ_OK,
+   HTTPD_BAD_URL,
+   HTTPD_BAD_REQ,
+   HTTPD_CLNT_RST
+};
+
+struct httpd_config {
+   enum net_loop_state (*on_stop)(void);
+   void(*on_req_end)(void *req_id);
+
+   enum httpd_req_check(*pre_get)(void *req_id, const char *url);
+   enum httpd_req_check(*pre_post)(void *req_id, const char *url,
+   struct httpd_post_data *post);
+
+   struct http_reply * (*get)(void *req_id, const char *url);
+   struct http_reply * (*post)(void *req_id, const char *url,
+   struct httpd_post_data *post);
+
+   struct http_reply   *error_400;
+   struct http_reply   *error_404;
+};
+
+/**
+ * httpd_setup() - configure the webserver
+ */
+void httpd_setup(struct httpd_config *config);
+
+/**
+ * httpd_stop() - start stopping of the webserver
+ */
+void httpd_stop(void);
+
+/**
+ * httpd_start() - start the webserver
+ */
+void httpd_start(void);
+
+/**
+ * httpd_get_tcp_stream() - get underlying tcp stream
+ */
+struct tcp_stream *httpd_get_tcp_stream(void *req_id);
+
+#endif /* __NET_HTTPD_COMMON_H__ */
diff --git a/net/Kconfig b/net/Kconfig
index 7cb80b880a9..55739b9bc98 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -243,6 +243,20 @@ config PROT_TCP_SACK
  This option should be turn on if you want to achieve the fastest
  file transfer possible.
 
+config HTTPD_COMMON
+   bool "HTTP server common code"
+   depends on PROT_TCP
+   help
+ HTTP/1.1 compatible web-server common code. It supports standard
+ GET/POST requests. User MUST provide a configuration to the
+ web-server. On client request web-server will call user specified
+ GET/POST callback. Then results will be transmitted to the client.
+ The following restricions on the POST request are present at the
+ moment:
+   * only mulipart/form-data with a single binary object
+   * object will be stored to a memory area specified in
+ image_load_addr variable
+
 config IPV6
bool "IPv6 support"
help
diff --git a/net/Makefile b/net/Makefile
index dac7b4859fb..c1f491fad02 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_PROT_UDP) += udp.o
 obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
 obj-$(CONFIG_CMD_NETCAT) += netcat.o
+obj-$(CONFIG_HTTPD_COMMON) += httpd.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/httpd.c b/net/httpd.c
new file mode 100644
index 000..82181e291c8
--- /dev/null
+++ b/net/httpd.c
@@ -0,0 +1,735

[PATCH v9 10/13] net/net: fix include ordering

2024-10-01 Thread Mikhail Kshevetskiy
fix include ordering to follow
  https://docs.u-boot.org/en/latest/develop/codingstyle.html#include-files

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/net.c | 24 +---
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/net/net.c b/net/net.c
index 4b0b0061ba7..dde6009abd2 100644
--- a/net/net.c
+++ b/net/net.c
@@ -88,41 +88,43 @@
 #include 
 #include 
 #include 
+#if defined(CONFIG_LED_STATUS)
+#include 
+#endif
 #include 
 #include 
 #include 
+#if defined(CONFIG_LED_STATUS)
+#include 
+#endif
+#include 
+#include 
 #include 
 #include 
-#include 
 #include 
 #if defined(CONFIG_CMD_PCAP)
 #include 
 #endif
-#include 
-#if defined(CONFIG_LED_STATUS)
-#include 
-#include 
-#endif
-#include 
-#include 
-#include 
 #include 
+#include 
+#include 
 #include 
+#include 
 #include "arp.h"
 #include "bootp.h"
 #include "cdp.h"
+#include "dhcpv6.h"
 #if defined(CONFIG_CMD_DNS)
 #include "dns.h"
 #endif
 #include "link_local.h"
+#include "net_rand.h"
 #include "nfs.h"
 #include "ping.h"
 #include "rarp.h"
 #if defined(CONFIG_CMD_WOL)
 #include "wol.h"
 #endif
-#include "dhcpv6.h"
-#include "net_rand.h"
 
 /** BOOTP EXTENTIONS **/
 
-- 
2.45.2



[PATCH v9 06/13] net/tcp: improve tcp framework, use better state machine

2024-10-01 Thread Mikhail Kshevetskiy
Changes:
 * Fix initial send sequence always zero issue
 * Use state machine close to RFC 9293. This should make TCP
   transfers more reliable (now we can upload a huge array
   of data from the board to external server)
 * Improve TCP framework a lot. This should make tcp client
   code much more simple.
 * rewrite wget with new tcp stack
 * rewrite fastboot_tcp with new tcp stack

It's quite hard to fix the initial send sequence (ISS) issue
with the separate patch. A naive attempt to fix an issue
inside the tcp_set_tcp_header() function will break tcp packet
retransmit logic in wget and other clients.

Example:
  Wget stores tcp_seq_num value before tcp_set_tcp_header() will
  be called and (on failure) retransmit the packet with the stored
  tcp_seq_num value. Thus:
* the same ISS must allways be used (current case)
* or tcp clients needs to generate a proper ISS when
  required.

A proper ISS fix will require a big redesing comparable with
a this one.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h  | 198 +--
 include/net/wget.h |   8 -
 net/fastboot_tcp.c | 193 +-
 net/net.c  |   4 +
 net/tcp.c  | 860 ++---
 net/wget.c | 461 +++-
 6 files changed, 1050 insertions(+), 674 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 0694af9d5b1..b0d003bd2d8 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -265,6 +265,7 @@ union tcp_build_pkt {
  * @TCP_CLOSING: Rec FIN, sent FIN, ACK waiting for ACK
  * @TCP_FIN_WAIT_1: Sent FIN waiting for response
  * @TCP_FIN_WAIT_2: Rec ACK from FIN sent, waiting for FIN
+ * @TCP_LAST_ACK: Waiting for ACK of the connection termination
  */
 enum tcp_state {
TCP_CLOSED,
@@ -274,7 +275,20 @@ enum tcp_state {
TCP_CLOSE_WAIT,
TCP_CLOSING,
TCP_FIN_WAIT_1,
-   TCP_FIN_WAIT_2
+   TCP_FIN_WAIT_2,
+   TCP_LAST_ACK,
+};
+
+/**
+ * enum tcp_status - TCP stream status for connection
+ * @TCP_ERR_OK: no rx/tx errors
+ * @TCP_ERR_TOUT: rx/tx timeout happened
+ * @TCP_ERR_RST: connection was reset
+ */
+enum tcp_status {
+   TCP_ERR_OK = 0,
+   TCP_ERR_TOUT,
+   TCP_ERR_RST,
 };
 
 /**
@@ -283,51 +297,154 @@ enum tcp_state {
  * @rport: Remote port, host byte order
  * @lport: Local port, host byte order
  *
+ * @priv:  User private data (not used by tcp module)
+ *
+ * @max_retry_count:   Maximum retransmit attempts (default 3)
+ * @initial_timeout:   Timeout from initial TX to reTX (default 2 sec)
+ * @rx_inactiv_timeout:Maximum time from last rx till connection drop
+ *   (default 30 sec)
+ *
+ * @on_closed: User callback, called just before destroying TCP stream
+ * @on_established:User callback, called when TCP stream enters
+ *   TCP_ESTABLISHED state
+ * @on_rcv_nxt_update: User callback, called when all data in the segment
+ *   [0..rx_bytes - 1] was received
+ * @on_snd_una_update: User callback, called when all data in the segment
+ *   [0..tx_bytes - 1] were transferred and acknowledged
+ * @rx:User callback, called on receive of segment
+ *   [rx_offs..rx_offs+len-1]. If NULL -- all incoming data
+ *   will be ignored. User SHOULD store the segment and
+ *   return the number of accepted bytes.
+ *   WARNING: Previous segmengs may not be received yet
+ * @tx:User callback, called on transmit/retransmit of 
segment
+ *   [tx_offs..tx_offs+maxlen-1]. If NULL -- no data will
+ *   be transmitted. User SHOULD fill provided buffer and
+ *   return the number of bytes in the buffer.
+ *   WARNING: do not use tcp_stream_close() from this
+ * callback (it will break stream). Better use
+ * on_snd_una_update() callback for such purposes.
+ *
+ * @time_last_rx:  Arrival time of last valid incoming package (ticks)
+ * @time_start:Timeout start time (ticks)
+ * @time_delta:Timeout duration (ticks)
+ * @time_handler   Timeout handler for a stream
+ *
  * @state: TCP connection state
+ * @status:TCP stream status (OK or ERR)
+ * @rx_packets:total number of received packets
+ * @tx_packets:total number of transmitted packets
+ *
+ * @fin_rx:Non-zero if TCP_FIN was received
+ * @fin_rx_seq:TCP sequence of rx FIN bit
+ * @fin_tx:Non-zero if TCP_FIN was sent (or planned to send)
+ * @fin_tx_seq:TCP sequence of tx FIN bit
+ *
+ * @iss:   Initial send sequence number
+ * @snd_una:   Send unacknowl

[PATCH v9 11/13] net/netcat: add netcat over tcp support

2024-10-01 Thread Mikhail Kshevetskiy
This patch adds downloading/uploading of data with netcat.
Client/server mode both supported.

How to test:

  netcat-openbsd=1.219-1 from debian were used for a tests

  a) Load data from remote host.
   * U-Boot listen on tcp port 3456
   * PC connects

 u-boot: netcat load ${loadaddr} 3456
 PC: netcat -q1 ${UBOOT_IP} 3456 < image.itb

  b) Load data from remote host.
   * PC listen on tcp port 3456
   * U-Boot connects

 PC: netcat -q1 -l -p 3456 < image.itb
 u-boot: netcat load ${loadaddr} ${PC_IP}:3456

  c) Save data to remote host
   * U-Boot listen on tcp port 3456
   * PC connects

 u-boot: netcat save ${loadaddr} ${data_size_in_hex} 3456
 PC: netcat -w1 ${UBOOT_IP} 3456 >image.itb

  d) Save data to remote host
   * PC listen on tcp port 3456
   * U-Boot connects

 PC: netcat -w1 -l -p 3456 >image.itb
 u-boot: netcat save ${loadaddr} ${data_size_in_hex} ${PC_IP}:3456

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 cmd/Kconfig  |  10 +++
 cmd/net.c|  35 ++--
 include/net.h|   2 +-
 include/net/netcat.h |  20 +
 net/Makefile |   1 +
 net/net.c|   9 ++
 net/netcat.c | 191 +++
 7 files changed, 262 insertions(+), 6 deletions(-)
 create mode 100644 include/net/netcat.h
 create mode 100644 net/netcat.c

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 5ef3c8a8748..78d9b00058a 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2025,6 +2025,16 @@ config CMD_WGET
  wget is a simple command to download kernel, or other files,
  from a http server over TCP.
 
+config CMD_NETCAT
+   bool "netcat"
+   select PROT_TCP
+   help
+ netcat is a simple command to load/store kernel, or other files,
+ using well-known netcat (nc) utility. Unlike classic netcat utility
+ this command supports TCP-based data transfer only, thus no data
+ will be lost or reordered. Any netcat implementation should work,
+ but openbsd one was tested only.
+
 config CMD_MII
bool "mii"
imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 53ce2bc5d0c..79bb126dbd4 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -206,6 +206,29 @@ U_BOOT_CMD(
 );
 #endif
 
+#if defined(CONFIG_CMD_NETCAT)
+static int do_netcat_load(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+   return netboot_common(NETCAT_LOAD, cmdtp, argc, argv);
+}
+
+static int do_netcat_save(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+   return netboot_common(NETCAT_SAVE, cmdtp, argc, argv);
+}
+
+U_BOOT_LONGHELP(netcat,
+   "load [loadAddress] [[hostIPaddr:]port]\n"
+   "save Address Size [[hostIPaddr:]port]");
+
+U_BOOT_CMD_WITH_SUBCMDS(netcat,
+   "load/store data over plain TCP via netcat utility", netcat_help_text,
+   U_BOOT_SUBCMD_MKENT(load, 1, 0, do_netcat_load),
+   U_BOOT_SUBCMD_MKENT(save, 3, 0, do_netcat_save));
+#endif
+
 static void netboot_update_env(void)
 {
char tmp[46];
@@ -323,16 +346,17 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
 
switch (argc) {
case 1:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
return 1;
-
/* refresh bootfile name from env */
copy_filename(net_boot_file_name, env_get("bootfile"),
  sizeof(net_boot_file_name));
break;
 
case 2:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
return 1;
/*
 * Only one arg - accept two forms:
@@ -354,7 +378,8 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
break;
 
case 3:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) {
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE)) {
if (parse_addr_size(argv))
return 1;
} else {
@@ -365,7 +390,7 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
}
break;
 
-#ifdef CONFIG_CMD_TFTPPUT
+#if defined(CONFIG_CMD_TFTPPUT) || defined(CONFIG_CMD_NETCAT)
c

[PATCH v9 07/13] test/cmd/wget: fix the test

2024-10-01 Thread Mikhail Kshevetskiy
Changes:
 * update to new tcp stack
 * fix zero values for ISS and IRS issue (see RFC 9293)

Previously this patch also fix incorrect values for tcp_ack & tcp_seq,
but the issue was fixed in
 * dbb6b5a: sandbox: fix wget test failure after fixing wget issue

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 arch/sandbox/include/asm/eth.h |  4 +++
 test/cmd/wget.c| 58 --
 2 files changed, 32 insertions(+), 30 deletions(-)

diff --git a/arch/sandbox/include/asm/eth.h b/arch/sandbox/include/asm/eth.h
index f042a5f3b92..083a7371a3f 100644
--- a/arch/sandbox/include/asm/eth.h
+++ b/arch/sandbox/include/asm/eth.h
@@ -77,6 +77,8 @@ typedef int sandbox_eth_tx_hand_f(struct udevice *dev, void 
*pkt,
  * fake_host_hwaddr - MAC address of mocked machine
  * fake_host_ipaddr - IP address of mocked machine
  * disabled - Will not respond
+ * irs - tcp initial receive sequence
+ * iss - tcp initial send sequence
  * recv_packet_buffer - buffers of the packet returned as received
  * recv_packet_length - lengths of the packet returned as received
  * recv_packets - number of packets returned
@@ -87,6 +89,8 @@ struct eth_sandbox_priv {
uchar fake_host_hwaddr[ARP_HLEN];
struct in_addr fake_host_ipaddr;
bool disabled;
+   u32 irs;
+   u32 iss;
uchar * recv_packet_buffer[PKTBUFSRX];
int recv_packet_length[PKTBUFSRX];
int recv_packets;
diff --git a/test/cmd/wget.c b/test/cmd/wget.c
index fe26fee54c9..a90ca4fad8c 100644
--- a/test/cmd/wget.c
+++ b/test/cmd/wget.c
@@ -25,8 +25,7 @@
 
 #define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4)
 #define LEN_B_TO_DW(x) ((x) >> 2)
-
-int net_set_ack_options(union tcp_build_pkt *b);
+#define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
 
 static int sb_arp_handler(struct udevice *dev, void *packet,
  unsigned int len)
@@ -64,12 +63,14 @@ static int sb_syn_handler(struct udevice *dev, void *packet,
eth_send = (void *)priv->recv_packet_buffer[priv->recv_packets];
memcpy(eth_send->et_dest, eth->et_src, ARP_HLEN);
memcpy(eth_send->et_src, priv->fake_host_hwaddr, ARP_HLEN);
+   priv->irs = ntohl(tcp->tcp_seq);
+   priv->iss = ~priv->irs; /* just to differ from irs */
eth_send->et_protlen = htons(PROT_IP);
tcp_send = (void *)eth_send + ETHER_HDR_SIZE;
tcp_send->tcp_src = tcp->tcp_dst;
tcp_send->tcp_dst = tcp->tcp_src;
-   tcp_send->tcp_seq = htonl(0);
-   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1);
+   tcp_send->tcp_seq = htonl(priv->iss);
+   tcp_send->tcp_ack = htonl(priv->irs + 1);
tcp_send->tcp_hlen = 
SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
tcp_send->tcp_flags = TCP_SYN | TCP_ACK;
tcp_send->tcp_win = htons(PKTBUFSRX * TCP_MSS >> TCP_SCALE);
@@ -104,13 +105,11 @@ static int sb_ack_handler(struct udevice *dev, void 
*packet,
void *data;
int pkt_len;
int payload_len = 0;
+   u32 tcp_seq, tcp_ack;
+   int tcp_data_len;
const char *payload1 = "HTTP/1.1 200 OK\r\n"
"Content-Length: 30\r\n\r\n\r\n"
"Hi\r\n";
-   union tcp_build_pkt *b = (union tcp_build_pkt *)tcp;
-   const int recv_payload_len = len - net_set_ack_options(b) - IP_HDR_SIZE 
- ETHER_HDR_SIZE;
-   static int next_seq;
-   const int bottom_payload_len = 10;
 
/* Don't allow the buffer to overrun */
if (priv->recv_packets >= PKTBUFSRX)
@@ -125,35 +124,32 @@ static int sb_ack_handler(struct udevice *dev, void 
*packet,
tcp_send->tcp_dst = tcp->tcp_src;
data = (void *)tcp_send + IP_TCP_HDR_SIZE;
 
-   if (ntohl(tcp->tcp_seq) == 1 && ntohl(tcp->tcp_ack) == 1 && 
recv_payload_len == 0) {
-   // ignore ACK for three-way handshaking
-   return 0;
-   } else if (ntohl(tcp->tcp_seq) == 1 && ntohl(tcp->tcp_ack) == 1) {
-   // recv HTTP request message and reply top half data
-   tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack));
-   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 
recv_payload_len);
+   tcp_seq = ntohl(tcp->tcp_seq) - priv->irs;
+   tcp_ack = ntohl(tcp->tcp_ack) - priv->iss;
+   tcp_data_len = len - ETHER_HDR_SIZE - IP_HDR_SIZE - 
GET_TCP_HDR_LEN_IN_BYTES(tcp->tcp_hlen);
 
-   payload_len = strlen(payload1) - bottom_payload_len;
-   memcpy(data, payload1, payload_len);
-   tcp_send->tcp_flags = TCP_ACK;
+   if (tcp->tcp_flags & TCP_FIN)
+   tcp_data_len++;
 
-   next_seq = ntohl(tcp_send->tcp_seq) + payload_len;
-   } else if (ntohl(tcp->tcp_ack) == next_seq) {
-   // re

[PATCH v9 04/13] net/tcp: add connection info to tcp_stream structure

2024-10-01 Thread Mikhail Kshevetskiy
Changes:
 * Avoid use net_server_ip in tcp code, use tcp_stream data instead
 * Ignore packets from other connections if connection already created.
   This prevents us from connection break caused by other tcp stream.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net.h  |   5 +-
 include/net/tcp.h  |  57 +---
 net/fastboot_tcp.c |  50 +
 net/net.c  |  12 ++---
 net/tcp.c  | 131 ++---
 net/wget.c |  52 +++---
 6 files changed, 204 insertions(+), 103 deletions(-)

diff --git a/include/net.h b/include/net.h
index bb2ae20f52a..b0ce13e0a9d 100644
--- a/include/net.h
+++ b/include/net.h
@@ -667,6 +667,7 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
 /**
  * net_send_tcp_packet() - Transmit TCP packet.
  * @payload_len: length of payload
+ * @dhost: Destination host
  * @dport: Destination TCP port
  * @sport: Source TCP port
  * @action: TCP action to be performed
@@ -675,8 +676,8 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
  *
  * Return: 0 on success, other value on failure
  */
-int net_send_tcp_packet(int payload_len, int dport, int sport, u8 action,
-   u32 tcp_seq_num, u32 tcp_ack_num);
+int net_send_tcp_packet(int payload_len, struct in_addr dhost, int dport,
+   int sport, u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport,
int sport, int payload_len);
 
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 14aee64cb1c..f224d0cae2f 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -279,6 +279,9 @@ enum tcp_state {
 
 /**
  * struct tcp_stream - TCP data stream structure
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ * @lport: Local port, host byte order
  *
  * @state: TCP connection state
  *
@@ -291,6 +294,10 @@ enum tcp_state {
  * @lost:  Used for SACK
  */
 struct tcp_stream {
+   struct in_addr  rhost;
+   u16 rport;
+   u16 lport;
+
/* TCP connection state */
enum tcp_state  state;
 
@@ -305,16 +312,53 @@ struct tcp_stream {
struct tcp_sack_v lost;
 };
 
-struct tcp_stream *tcp_stream_get(void);
+void tcp_init(void);
+
+typedef int tcp_incoming_filter(struct in_addr rhost,
+   u16 rport, u16 sport);
+
+/*
+ * This function sets user callback used to accept/drop incoming
+ * connections. Callback should:
+ *  + Check TCP stream endpoint and make connection verdict
+ *- return non-zero value to accept connection
+ *- return zero to drop connection
+ *
+ * WARNING: If callback is NOT defined, all incoming connections
+ *  will be dropped.
+ */
+void tcp_set_incoming_filter(tcp_incoming_filter *filter);
+
+/*
+ * tcp_stream_get -- Get or create TCP stream
+ * @is_new:if non-zero and no stream found, then create a new one
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ * @lport: Local port, host byte order
+ *
+ * Returns: TCP stream structure or NULL (if not found/created)
+ */
+struct tcp_stream *tcp_stream_get(int is_new, struct in_addr rhost,
+ u16 rport, u16 lport);
+
+/*
+ * tcp_stream_connect -- Create new TCP stream for remote connection.
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ *
+ * Returns: TCP new stream structure or NULL (if not created).
+ *  Random local port will be used.
+ */
+struct tcp_stream *tcp_stream_connect(struct in_addr rhost, u16 rport);
+
+enum tcp_state tcp_stream_get_state(struct tcp_stream *tcp);
 
-enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp);
-void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state);
-int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport,
-  int sport, int payload_len,
+int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 
 /**
  * rxhand_tcp() - An incoming packet handler.
+ * @tcp: TCP stream
  * @pkt: pointer to the application packet
  * @dport: destination TCP port
  * @sip: source IP address
@@ -324,8 +368,7 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, 
int dport,
  * @action: TCP action (SYN, ACK, FIN, etc)
  * @len: packet length
  */
-typedef void rxhand_tcp(uchar *pkt, u16 dport,
-   struct in_addr sip, u16 sport,
+typedef void rxhand_tcp(struct tcp_stream *tcp, uchar *pkt,
u32 tcp_seq_num, u32 tcp_ack_num,
u8 action, unsigned int len);
 void tcp_set_tcp_handler(rxhand_tcp *f);
diff --git a/net

[PATCH v9 08/13] net/tcp: simplify tcp header filling code

2024-10-01 Thread Mikhail Kshevetskiy
This patch:
 * remove useless code,
 * use a special function for pretty printing of tcp flags,
 * simplify the code

The behavior should not be changed.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 66 +++
 1 file changed, 33 insertions(+), 33 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index 6bb68a04c5d..2a95c44b141 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -543,10 +543,33 @@ void net_set_syn_options(struct tcp_stream *tcp, union 
tcp_build_pkt *b)
b->ip.end = TCP_O_END;
 }
 
+const char *tcpflags_to_str(char tcpflags, char *buf, int size)
+{
+   int i;
+   static const struct {
+   int bit;
+   const char  *name;
+   } desc[] = {{TCP_RST, "RST"}, {TCP_SYN, "SYN"}, {TCP_PUSH, "PSH"},
+   {TCP_FIN, "FIN"}, {TCP_ACK, "ACK"}};
+
+   *buf = '\0';
+   for (i = 0; i < ARRAY_SIZE(desc); i++) {
+   if (!(tcpflags & desc[i].bit))
+   continue;
+
+   if (*buf)
+   strlcat(buf, ",", size);
+   strlcat(buf, desc[i].name, size);
+   }
+
+   return buf;
+}
+
 int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num)
 {
union tcp_build_pkt *b = (union tcp_build_pkt *)pkt;
+   char buf[24];
int pkt_hdr_len;
int pkt_len;
int tcp_len;
@@ -556,55 +579,32 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar 
*pkt, int payload_len,
 * 4 bits reserved options
 */
b->ip.hdr.tcp_flags = action;
-   pkt_hdr_len = IP_TCP_HDR_SIZE;
b->ip.hdr.tcp_hlen = 
SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
 
switch (action) {
case TCP_SYN:
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:SYN (%pI4, %pI4, sq=%u, ak=%u)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num);
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
+  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
net_set_syn_options(tcp, b);
pkt_hdr_len = IP_TCP_O_SIZE;
break;
-   case TCP_SYN | TCP_ACK:
-   case TCP_ACK:
-   pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b);
-   b->ip.hdr.tcp_flags = action;
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:ACK (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
-  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num,
-  action);
-   break;
-   case TCP_FIN:
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:FIN  (%pI4, %pI4, s=%u, a=%u)\n",
-  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
-   payload_len = 0;
-   pkt_hdr_len = IP_TCP_HDR_SIZE;
-   break;
case TCP_RST | TCP_ACK:
case TCP_RST:
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:RST  (%pI4, %pI4, s=%u, a=%u)\n",
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
   &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
+   pkt_hdr_len = IP_TCP_HDR_SIZE;
break;
-   /* Notify connection closing */
-   case (TCP_FIN | TCP_ACK):
-   case (TCP_FIN | TCP_ACK | TCP_PUSH):
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%u, a=%u, 
A=%x)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num, action);
-   fallthrough;
default:
pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b);
-   b->ip.hdr.tcp_flags = action | TCP_PUSH | TCP_ACK;
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:dft  (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num, action);
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
+  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
+   break;
}
 
pkt_len = pkt_hdr_len + payload_len;
-- 
2.45.2



[PATCH v9 01/13] net/tcp: fix TCP options processing

2024-10-01 Thread Mikhail Kshevetskiy
Current TCP code may miss an option if TCP_O_NOP option was used before
it for proper aligning.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 6 --
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index b0cc8a1fe3e..3e3118de450 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -475,7 +475,7 @@ void tcp_parse_options(uchar *o, int o_len)
 * NOPs are options with a zero length, and thus are special.
 * All other options have length fields.
 */
-   for (p = o; p < (o + o_len); p = p + p[1]) {
+   for (p = o; p < (o + o_len); ) {
if (!p[1])
return; /* Finished processing options */
 
@@ -490,12 +490,14 @@ void tcp_parse_options(uchar *o, int o_len)
case TCP_O_TS:
tsopt = (struct tcp_t_opt *)p;
rmt_timestamp = tsopt->t_snd;
-   return;
+   break;
}
 
/* Process optional NOPs */
if (p[0] == TCP_O_NOP)
p++;
+   else
+   p += p[1];
}
 }
 
-- 
2.45.2



[PATCH v9 13/13] net/httpd-upload: an example web-server implementation for file uploading

2024-10-01 Thread Mikhail Kshevetskiy
This is an example web-server implementation. It can be used for files
uploading to u-boot using a web-browser. It acts much like tftpget, but no
special servers needs to be installed by the user.

This code can be used as a base for other implementations like firmware
upgrade web-server used by some vendors.

Usage:
  u-boot: start the we-server using the "httpd_upload" command
  PC: open the "http://your_uboot_ip"; link in the browser

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 cmd/Kconfig|  25 ++
 cmd/net.c  |  20 +
 include/net/httpd-upload.h |  12 +++
 net/Makefile   |   1 +
 net/httpd-upload.c | 170 +
 5 files changed, 228 insertions(+)
 create mode 100644 include/net/httpd-upload.h
 create mode 100644 net/httpd-upload.c

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 78d9b00058a..41162600671 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2035,6 +2035,31 @@ config CMD_NETCAT
  will be lost or reordered. Any netcat implementation should work,
  but openbsd one was tested only.
 
+config CMD_HTTPD_UPLOAD
+   bool "an example HTTP server for file uploading"
+   depends on HTTPD_COMMON
+   help
+ An example HTTP/1.1 compatible web-server implementation for file
+ uploading. It acts much like tftpboot command: put user data to a
+ specified memory location, but no special tools needs to be installed
+ on the user side. The only required tool is browser.
+
+ Start 'httpd_upload' command, open a browser, connect to the board IP,
+ select file to upload and press 'Upload' button. This is enougth.
+
+ There is no big profit from this code, but it can be used as a
+ reference for other web-server implementations (ex: web-based
+ firmware upgrade/recovery used by some router vendors)
+
+config CMD_HTTPD_UPLOAD_MAX_SIZE
+   int "Maximum uploading size"
+   depends on CMD_HTTPD_UPLOAD
+   default 209715200
+   help
+ This option sets the resriction on the size of any uploaded file.
+ Please reserve 2--4 Kb more space due to additional space required
+ for storing of multipart/form-data header and footer. 
+
 config CMD_MII
bool "mii"
imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 79bb126dbd4..f2df1eed2ef 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -21,6 +21,9 @@
 #include 
 #include 
 #include 
+#if defined(CONFIG_CMD_HTTPD_UPLOAD)
+#include 
+#endif
 
 static int netboot_common(enum proto_t, struct cmd_tbl *, int, char * const 
[]);
 
@@ -229,6 +232,23 @@ U_BOOT_CMD_WITH_SUBCMDS(netcat,
U_BOOT_SUBCMD_MKENT(save, 3, 0, do_netcat_save));
 #endif
 
+#if defined(CONFIG_CMD_HTTPD_UPLOAD)
+static int do_httpd_upload(struct cmd_tbl *cmdtp, int flag, int argc, char 
*const argv[])
+{
+   if (argc < 2)
+   return 1;
+
+   httpd_upload_prepare();
+   return netboot_common(HTTPD, cmdtp, argc, argv);
+}
+
+U_BOOT_CMD(
+   httpd_upload,   2,  1,  do_httpd_upload,
+   "starts httpd server for file uploading",
+   "[loadAddress]\n"
+);
+#endif
+
 static void netboot_update_env(void)
 {
char tmp[46];
diff --git a/include/net/httpd-upload.h b/include/net/httpd-upload.h
new file mode 100644
index 000..a80df214668
--- /dev/null
+++ b/include/net/httpd-upload.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ *
+ * httpd-upload include file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ */
+#ifndef __NET_HTTPD_UPLOAD_TCP_H__
+#define __NET_HTTPD_UPLOAD_TCP_H__
+
+void httpd_upload_prepare(void);
+
+#endif /* __NET_HTTPD_UPLOAD_TCP_H__ */
diff --git a/net/Makefile b/net/Makefile
index c1f491fad02..c1b41240aab 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
 obj-$(CONFIG_CMD_NETCAT) += netcat.o
 obj-$(CONFIG_HTTPD_COMMON) += httpd.o
+obj-$(CONFIG_CMD_HTTPD_UPLOAD) += httpd-upload.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/httpd-upload.c b/net/httpd-upload.c
new file mode 100644
index 000..8e43860fa74
--- /dev/null
+++ b/net/httpd-upload.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * httpd-upload support driver
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ */
+
+#include 
+#include 
+#include 
+#include 
+
+#define MAX_FILE_SIZE  CONFIG_CMD_HTTPD_UPLOAD_MAX_SIZE
+
+static enum net_loop_state  httpd_on_stop(void);
+
+static enum httpd_req_check httpd_pre_post(void *req_id, const char *url,
+  struct httpd_post_data *post);
+static struct http_reply

[PATCH v9 09/13] net/tcp: define a fallback value for rcv_wnd size

2024-10-01 Thread Mikhail Kshevetskiy
Some driver implements it's own network packet pool, so PKTBUFSRX is zero.
This results in zero-size TCP receive window, so data transfer doesn't
work. Avoid it by setting a reasonable fallback value.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 6 +-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/net/tcp.c b/net/tcp.c
index 2a95c44b141..b950d2b7727 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -36,7 +36,11 @@
 #define TCP_SEND_RETRY 3
 #define TCP_SEND_TIMEOUT   2000UL
 #define TCP_RX_INACTIVE_TIMEOUT3UL
-#define TCP_RCV_WND_SIZE   (PKTBUFSRX * TCP_MSS)
+#if PKTBUFSRX != 0
+  #define TCP_RCV_WND_SIZE (PKTBUFSRX * TCP_MSS)
+#else
+  #define TCP_RCV_WND_SIZE (4 * TCP_MSS)
+#endif
 
 #define TCP_PACKET_OK  0
 #define TCP_PACKET_DROP1
-- 
2.45.2



[PATCH v9 05/13] net/tcp: rename ack_edge and seq_init to more common rcv_nxt and irs

2024-10-01 Thread Mikhail Kshevetskiy
Use the names from RFC 9293

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h |  8 
 net/tcp.c | 32 
 2 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index f224d0cae2f..0694af9d5b1 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -285,8 +285,8 @@ enum tcp_state {
  *
  * @state: TCP connection state
  *
- * @seq_init:  Initial receive sequence number
- * @ack_edge:  Receive next
+ * @irs:   Initial receive sequence number
+ * @rcv_nxt:   Receive next
  *
  * @loc_timestamp: Local timestamp
  * @rmt_timestamp: Remote timestamp
@@ -301,8 +301,8 @@ struct tcp_stream {
/* TCP connection state */
enum tcp_state  state;
 
-   u32 seq_init;
-   u32 ack_edge;
+   u32 irs;
+   u32 rcv_nxt;
 
/* TCP option timestamp */
u32 loc_timestamp;
diff --git a/net/tcp.c b/net/tcp.c
index 0c32c5d7c92..7e445eaffd6 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -360,9 +360,9 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, 
int payload_len,
pkt_len = pkt_hdr_len + payload_len;
tcp_len = pkt_len - IP_HDR_SIZE;
 
-   tcp->ack_edge = tcp_ack_num;
+   tcp->rcv_nxt = tcp_ack_num;
/* TCP Header */
-   b->ip.hdr.tcp_ack = htonl(tcp->ack_edge);
+   b->ip.hdr.tcp_ack = htonl(tcp->rcv_nxt);
b->ip.hdr.tcp_src = htons(tcp->lport);
b->ip.hdr.tcp_dst = htons(tcp->rport);
b->ip.hdr.tcp_seq = htonl(tcp_seq_num);
@@ -396,10 +396,10 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar 
*pkt, int payload_len,
return pkt_hdr_len;
 }
 
-static void tcp_update_ack_edge(struct tcp_stream *tcp)
+static void tcp_update_rcv_nxt(struct tcp_stream *tcp)
 {
-   if (tcp_seq_cmp(tcp->ack_edge, tcp->lost.hill[0].l) >= 0) {
-   tcp->ack_edge = tcp->lost.hill[0].r;
+   if (tcp_seq_cmp(tcp->rcv_nxt, tcp->lost.hill[0].l) >= 0) {
+   tcp->rcv_nxt = tcp->lost.hill[0].r;
 
memmove(&tcp->lost.hill[0], &tcp->lost.hill[1],
(TCP_SACK_HILLS - 1) * sizeof(struct sack_edges));
@@ -434,7 +434,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 
len)
tcp_seq_num = tcp->lost.hill[i].l;
}
if (tcp_seq_cmp(tcp->lost.hill[i].r, tcp_seq_num + len) >= 0) {
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
@@ -463,12 +463,12 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, 
u32 len)
}
}
 
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
if (i == TCP_SACK_HILLS) {
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
@@ -489,7 +489,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 
len)
tcp->lost.hill[i].r = tcp_seq_num + len;
tcp->lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8;
 
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
 };
 
 /**
@@ -566,8 +566,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
debug_cond(DEBUG_INT_STATE, "TCP CLOSED %x\n", tcp_flags);
if (tcp_syn) {
action = TCP_SYN | TCP_ACK;
-   tcp->seq_init = tcp_seq_num;
-   tcp->ack_edge = tcp_seq_num + 1;
+   tcp->irs = tcp_seq_num;
+   tcp->rcv_nxt = tcp_seq_num + 1;
tcp->lost.len = TCP_OPT_LEN_2;
tcp->state = TCP_SYN_RECEIVED;
} else if (tcp_ack || tcp_fin) {
@@ -583,8 +583,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
tcp->state = TCP_CLOSE_WAIT;
} else if (tcp_ack || (tcp_syn && tcp_ack)) {
action |= TCP_ACK;
-   tcp->seq_init = tcp_seq_num;
-   tcp->ack_edge = tcp_seq_num + 1;
+   tcp->irs = tcp_seq_num;
+   tcp->rcv_nxt = tcp_seq_num + 1;
tcp->state = TCP_ESTABLISHED;
 
if (tcp_syn && tcp_ack)
@@ -633,7 +633,7 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
case TCP_FIN_WAIT_1:
debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_1 (%x)\n", tcp_flags);
if (tcp_fin) {
-   

[PATCH v9 03/13] net/tcp: put connection specific data into a tcp_stream structure

2024-10-01 Thread Mikhail Kshevetskiy
no functional changes

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h |  37 +++-
 net/net.c |  11 ++-
 net/tcp.c | 231 +++---
 net/wget.c|   3 +-
 4 files changed, 163 insertions(+), 119 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index c29d4ce24a7..14aee64cb1c 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -277,9 +277,40 @@ enum tcp_state {
TCP_FIN_WAIT_2
 };
 
-enum tcp_state tcp_get_tcp_state(void);
-void tcp_set_tcp_state(enum tcp_state new_state);
-int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len,
+/**
+ * struct tcp_stream - TCP data stream structure
+ *
+ * @state: TCP connection state
+ *
+ * @seq_init:  Initial receive sequence number
+ * @ack_edge:  Receive next
+ *
+ * @loc_timestamp: Local timestamp
+ * @rmt_timestamp: Remote timestamp
+ *
+ * @lost:  Used for SACK
+ */
+struct tcp_stream {
+   /* TCP connection state */
+   enum tcp_state  state;
+
+   u32 seq_init;
+   u32 ack_edge;
+
+   /* TCP option timestamp */
+   u32 loc_timestamp;
+   u32 rmt_timestamp;
+
+   /* TCP sliding window control used to request re-TX */
+   struct tcp_sack_v lost;
+};
+
+struct tcp_stream *tcp_stream_get(void);
+
+enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp);
+void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state);
+int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport,
+  int sport, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 
 /**
diff --git a/net/net.c b/net/net.c
index 1e0b7c85624..1bbf0556cef 100644
--- a/net/net.c
+++ b/net/net.c
@@ -419,7 +419,7 @@ int net_init(void)
/* Only need to setup buffer pointers once. */
first_call = 0;
if (IS_ENABLED(CONFIG_PROT_TCP))
-   tcp_set_tcp_state(TCP_CLOSED);
+   tcp_set_tcp_state(tcp_stream_get(), TCP_CLOSED);
}
 
return net_init_loop();
@@ -920,6 +920,9 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
uchar *pkt;
int eth_hdr_size;
int pkt_hdr_size;
+#if defined(CONFIG_PROT_TCP)
+   struct tcp_stream *tcp;
+#endif
 
/* make sure the net_tx_packet is initialized (net_init() was called) */
assert(net_tx_packet != NULL);
@@ -946,8 +949,12 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
break;
 #if defined(CONFIG_PROT_TCP)
case IPPROTO_TCP:
+   tcp = tcp_stream_get();
+   if (tcp == NULL)
+   return -EINVAL;
+
pkt_hdr_size = eth_hdr_size
-   + tcp_set_tcp_header(pkt + eth_hdr_size, dport, sport,
+   + tcp_set_tcp_header(tcp, pkt + eth_hdr_size, dport, 
sport,
 payload_len, action, tcp_seq_num,
 tcp_ack_num);
break;
diff --git a/net/tcp.c b/net/tcp.c
index 724536cb352..6646f171b83 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -24,19 +24,8 @@
 #include 
 #include 
 
-/*
- * TCP sliding window  control used by us to request re-TX
- */
-static struct tcp_sack_v tcp_lost;
-
-/* TCP option timestamp */
-static u32 loc_timestamp;
-static u32 rmt_timestamp;
-
-static u32 tcp_seq_init;
-static u32 tcp_ack_edge;
-
 static int tcp_activity_count;
+static struct tcp_stream tcp_stream;
 
 /*
  * TCP lengths are stored as a rounded up number of 32 bit words.
@@ -48,9 +37,6 @@ static int tcp_activity_count;
 #define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4)
 #define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
 
-/* TCP connection state */
-static enum tcp_state current_tcp_state;
-
 /* Current TCP RX packet handler */
 static rxhand_tcp *tcp_packet_handler;
 
@@ -60,22 +46,30 @@ static inline s32 tcp_seq_cmp(u32 a, u32 b)
 }
 
 /**
- * tcp_get_tcp_state() - get current TCP state
+ * tcp_get_tcp_state() - get TCP stream state
+ * @tcp: tcp stream
  *
- * Return: Current TCP state
+ * Return: TCP stream state
  */
-enum tcp_state tcp_get_tcp_state(void)
+enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp)
 {
-   return current_tcp_state;
+   return tcp->state;
 }
 
 /**
- * tcp_set_tcp_state() - set current TCP state
+ * tcp_set_tcp_state() - set TCP stream state
+ * @tcp: tcp stream
  * @new_state: new TCP state
  */
-void tcp_set_tcp_state(enum tcp_state new_state)
+void tcp_set_tcp_state(struct tcp_stream *tcp,
+  enum tcp_state new_state)
 {
-   current_tcp_state = new_state;
+   tcp->state = new_state;
+}
+
+struct tcp_stream *tcp_stream_get(void)
+{
+   return &tcp_stream;
 }
 
 sta

[PATCH v9 02/13] net/tcp: fix selective acknowledge

2024-10-01 Thread Mikhail Kshevetskiy
Current code assume that all (except last) packets are of the same size.
This is definitely wrong. Replace SACK code with a new one, that does
not rely on this assumption. Also this code uses less memory.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 200 +++---
 1 file changed, 86 insertions(+), 114 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index 3e3118de450..724536cb352 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -38,21 +38,6 @@ static u32 tcp_ack_edge;
 
 static int tcp_activity_count;
 
-/*
- * Search for TCP_SACK and review the comments before the code section
- * TCP_SACK is the number of packets at the front of the stream
- */
-
-enum pkt_state {PKT, NOPKT};
-struct sack_r {
-   struct sack_edges se;
-   enum pkt_state st;
-};
-
-static struct sack_r edge_a[TCP_SACK];
-static unsigned int sack_idx;
-static unsigned int prev_len;
-
 /*
  * TCP lengths are stored as a rounded up number of 32 bit words.
  * Add 3 to length round up, rounded, then divided into the
@@ -69,6 +54,11 @@ static enum tcp_state current_tcp_state;
 /* Current TCP RX packet handler */
 static rxhand_tcp *tcp_packet_handler;
 
+static inline s32 tcp_seq_cmp(u32 a, u32 b)
+{
+   return (s32)(a - b);
+}
+
 /**
  * tcp_get_tcp_state() - get current TCP state
  *
@@ -267,6 +257,7 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
action = TCP_FIN;
current_tcp_state = TCP_FIN_WAIT_1;
} else {
+   tcp_lost.len = TCP_OPT_LEN_2;
current_tcp_state = TCP_SYN_SENT;
}
break;
@@ -353,6 +344,20 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
return pkt_hdr_len;
 }
 
+static void tcp_update_ack_edge(void)
+{
+   if (tcp_seq_cmp(tcp_ack_edge, tcp_lost.hill[0].l) >= 0) {
+   tcp_ack_edge = tcp_lost.hill[0].r;
+
+   memmove(&tcp_lost.hill[0], &tcp_lost.hill[1],
+   (TCP_SACK_HILLS - 1) * sizeof(struct sack_edges));
+
+   tcp_lost.len -= TCP_OPT_LEN_8;
+   tcp_lost.hill[TCP_SACK_HILLS - 1].l = TCP_O_NOP;
+   tcp_lost.hill[TCP_SACK_HILLS - 1].r = TCP_O_NOP;
+   }
+}
+
 /**
  * tcp_hole() - Selective Acknowledgment (Essential for fast stream transfer)
  * @tcp_seq_num: TCP sequence start number
@@ -360,106 +365,79 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
  */
 void tcp_hole(u32 tcp_seq_num, u32 len)
 {
-   u32 idx_sack, sack_in;
-   u32 sack_end = TCP_SACK - 1;
-   u32 hill = 0;
-   enum pkt_state expect = PKT;
-   u32 seq = tcp_seq_num - tcp_seq_init;
-   u32 hol_l = tcp_ack_edge - tcp_seq_init;
-   u32 hol_r = 0;
-
-   /* Place new seq number in correct place in receive array */
-   if (prev_len == 0)
-   prev_len = len;
-
-   idx_sack = sack_idx + ((tcp_seq_num - tcp_ack_edge) / prev_len);
-   if (idx_sack < TCP_SACK) {
-   edge_a[idx_sack].se.l = tcp_seq_num;
-   edge_a[idx_sack].se.r = tcp_seq_num + len;
-   edge_a[idx_sack].st = PKT;
+   int i, j, cnt, cnt_move;
 
-   /*
-* The fin (last) packet is not the same length as data
-* packets, and if it's length is recorded and used for
-*  array index calculation, calculation breaks.
-*/
-   if (prev_len < len)
-   prev_len = len;
-   }
+   cnt = (tcp_lost.len - TCP_OPT_LEN_2) / TCP_OPT_LEN_8;
+   for (i = 0; i < cnt; i++) {
+   if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num) < 0)
+   continue;
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num + len) > 0)
+   break;
 
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP 1 seq %d, edg %d, len %d, sack_idx %d, sack_end %d\n",
-   seq, hol_l, len, sack_idx, sack_end);
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) > 0)
+   tcp_lost.hill[i].l = tcp_seq_num;
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) < 0) {
+   len += tcp_seq_num - tcp_lost.hill[i].l;
+   tcp_seq_num = tcp_lost.hill[i].l;
+   }
+   if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num + len) >= 0) {
+   tcp_update_ack_edge();
+   return;
+   }
 
-   /* Right edge of contiguous stream, is the left edge of first hill */
-   hol_l = tcp_seq_num - tcp_seq_init;
-   hol_r = hol_l + len;
+   /* check overlapping with next hills */
+   cnt_move = 0;
+   tcp_lost.hill[i].r = tcp_seq_num

[PATCH v9 00/13] net: tcp: improve tcp support

2024-10-01 Thread Mikhail Kshevetskiy
U-Boot support of LWIP is not ready for a moment, but we already have
some kind of tcp support. Unfrotunately this support is really bad.

Some of the known issues:
 * tcp packet from other connection can break a current one
 * tcp send sequence always starts from zero
 * bad tcp options processing
 * strange assumptions on packet size for selectiv acknowledge
 * tcp interface assumes one of the two scenarios:
 - data downloading from remote host to a board
 - request-response exchange with a small packets
   so it's not possible to upload large amount of data from the
   board to remote host.
 * wget test generate bad tcp stream, test should fail but it passes instead

This series of patches fixes all of the above issuess.

The benefits:
 * A lot of bug was fixed
 * Better and more reliable TCP state machine
 * Tcp cliens becomes smaller/simpler
 * Data uploading was fixed (now it's possible to transmit a huge amount of
   data from the board to remote host)
 * Netcat over tcp was implemented. Netcat supports data downloading/uploading
   from/to remote host in client/server mode.
 * An example web-server implementation. This code can be used as a base
   for web-based firmware uploading used by some vendors.

Modification was verified with
 * firmware downloading via u-boot wget command
 * fastboot over tcp
 * netcat linux client
 * Firefox/Chrome/Edge using example web-server implementation

Changes v2:
 * cover letter was added
 * some patches were removed

Changes v3:
 * better cover letter

Changes v4:
 * fix bug in debug output
 * add more comments
 * codestyle fixes

Changes v5:
 * old patches were ocasionally sent with v4
 * add back web-server patches
 * fix bug in debug output
 * add more comments
 * codestyle fixes

Changes v6:
 * fix the wget test
 * improve description of "simplify tcp header filling code" patch

Changes v7:
 * fix include ordering
 * improve option descriptions
 * fix a lot of extra brackets and comparisons against NULL / 0
 * add empty lines before final returns
 * fixed a bug with zero size httpd uploads

Changes v8:
 * [tcp] add function to restart rx inactivity timeout. This may help
in the cases where remote peer waits for u-boot data, but u-boot
is busy with long executing action
 * [httpd] add function returning underlying tcp stream from request id.
This (with the above change) allows avoid connection break
for the long processing post requests.

Changes v9:
 * rebase all changes on top of origin/next
 * update test/cmd/wget patch description (bad tcp ack/seq issue was already
   fixed in origin/next)
 * tcp: add rx/tx packet counters to a tcp_stream structure
 * wget: use tcp rx packet counter instead of own one
 * netcat: use tcp rx/tx packet counters instead of own one

Mikhail Kshevetskiy (13):
  net/tcp: fix TCP options processing
  net/tcp: fix selective acknowledge
  net/tcp: put connection specific data into a tcp_stream structure
  net/tcp: add connection info to tcp_stream structure
  net/tcp: rename ack_edge and seq_init to more common rcv_nxt and irs
  net/tcp: improve tcp framework, use better state machine
  test/cmd/wget: fix the test
  net/tcp: simplify tcp header filling code
  net/tcp: define a fallback value for rcv_wnd size
  net/net: fix include ordering
  net/netcat: add netcat over tcp support
  net/httpd: add httpd common code
  net/httpd-upload: an example web-server implementation for file
uploading

 arch/sandbox/include/asm/eth.h |4 +
 cmd/Kconfig|   35 +
 cmd/net.c  |   55 +-
 include/net.h  |7 +-
 include/net/httpd-upload.h |   12 +
 include/net/httpd.h|   71 ++
 include/net/netcat.h   |   20 +
 include/net/tcp.h  |  254 ++-
 include/net/wget.h |8 -
 net/Kconfig|   14 +
 net/Makefile   |3 +
 net/fastboot_tcp.c |  193 +++--
 net/httpd-upload.c |  170 +
 net/httpd.c|  735 ++
 net/net.c  |   60 +-
 net/netcat.c   |  191 +
 net/tcp.c  | 1266 ++--
 net/wget.c |  476 
 test/cmd/wget.c|   58 +-
 19 files changed, 2723 insertions(+), 909 deletions(-)
 create mode 100644 include/net/httpd-upload.h
 create mode 100644 include/net/httpd.h
 create mode 100644 include/net/netcat.h
 create mode 100644 net/httpd-upload.c
 create mode 100644 net/httpd.c
 create mode 100644 net/netcat.c

-- 
2.45.2



[PATCH v8 12/13] net/httpd: add httpd common code

2024-09-24 Thread Mikhail Kshevetskiy
This patch adds HTTP/1.1 compatible web-server that can be used
by other. Server supports GET, POST, and HEAD requests. On client
request it will call user specified GET/POST callback. Then results
will be transmitted to client.

The following restrictions exist on the POST request
at the moment:
  * only multipart/form-data with a single file object
  * object will be stored to a memory area specified in
image_load_addr variable

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net.h   |   2 +-
 include/net/httpd.h |  71 +
 net/Kconfig |  14 +
 net/Makefile|   1 +
 net/httpd.c | 735 
 net/net.c   |   6 +
 6 files changed, 828 insertions(+), 1 deletion(-)
 create mode 100644 include/net/httpd.h
 create mode 100644 net/httpd.c

diff --git a/include/net.h b/include/net.h
index 0af6493788a..154885a2b7e 100644
--- a/include/net.h
+++ b/include/net.h
@@ -515,7 +515,7 @@ extern int  net_restart_wrap;   /* Tried all 
network devices */
 enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
-   WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, RS
+   WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, HTTPD, RS
 };
 
 extern charnet_boot_file_name[1024];/* Boot File name */
diff --git a/include/net/httpd.h b/include/net/httpd.h
new file mode 100644
index 000..ef6c37ece7c
--- /dev/null
+++ b/include/net/httpd.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * httpd support header file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ *
+ */
+#ifndef __NET_HTTPD_COMMON_H__
+#define __NET_HTTPD_COMMON_H__
+
+struct tcp_stream;
+
+struct http_reply {
+   int code;
+   const char  *code_msg;
+   const char  *data_type;
+   void*data;
+   u32 len;
+};
+
+struct httpd_post_data {
+   const char  *name;
+   const char  *filename;
+   void*addr;
+   u32 size;
+};
+
+enum httpd_req_check {
+   HTTPD_REQ_OK,
+   HTTPD_BAD_URL,
+   HTTPD_BAD_REQ,
+   HTTPD_CLNT_RST
+};
+
+struct httpd_config {
+   enum net_loop_state (*on_stop)(void);
+   void(*on_req_end)(void *req_id);
+
+   enum httpd_req_check(*pre_get)(void *req_id, const char *url);
+   enum httpd_req_check(*pre_post)(void *req_id, const char *url,
+   struct httpd_post_data *post);
+
+   struct http_reply * (*get)(void *req_id, const char *url);
+   struct http_reply * (*post)(void *req_id, const char *url,
+   struct httpd_post_data *post);
+
+   struct http_reply   *error_400;
+   struct http_reply   *error_404;
+};
+
+/**
+ * httpd_setup() - configure the webserver
+ */
+void httpd_setup(struct httpd_config *config);
+
+/**
+ * httpd_stop() - start stopping of the webserver
+ */
+void httpd_stop(void);
+
+/**
+ * httpd_start() - start the webserver
+ */
+void httpd_start(void);
+
+/**
+ * httpd_get_tcp_stream() - get underlying tcp stream
+ */
+struct tcp_stream *httpd_get_tcp_stream(void *req_id);
+
+#endif /* __NET_HTTPD_COMMON_H__ */
diff --git a/net/Kconfig b/net/Kconfig
index 7cb80b880a9..55739b9bc98 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -243,6 +243,20 @@ config PROT_TCP_SACK
  This option should be turn on if you want to achieve the fastest
  file transfer possible.
 
+config HTTPD_COMMON
+   bool "HTTP server common code"
+   depends on PROT_TCP
+   help
+ HTTP/1.1 compatible web-server common code. It supports standard
+ GET/POST requests. User MUST provide a configuration to the
+ web-server. On client request web-server will call user specified
+ GET/POST callback. Then results will be transmitted to the client.
+ The following restricions on the POST request are present at the
+ moment:
+   * only mulipart/form-data with a single binary object
+   * object will be stored to a memory area specified in
+ image_load_addr variable
+
 config IPV6
bool "IPv6 support"
help
diff --git a/net/Makefile b/net/Makefile
index dac7b4859fb..c1f491fad02 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_PROT_UDP) += udp.o
 obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
 obj-$(CONFIG_CMD_NETCAT) += netcat.o
+obj-$(CONFIG_HTTPD_COMMON) += httpd.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/httpd.c b/net/httpd.c
new file mode 100644
index 000..82181e291c8
--- /dev/null
+++ b/net/httpd.c
@@ -0,0 +1,735

[PATCH v8 13/13] net/httpd-upload: an example web-server implementation for file uploading

2024-09-24 Thread Mikhail Kshevetskiy
This is an example web-server implementation. It can be used for files
uploading to u-boot using a web-browser. It acts much like tftpget, but no
special servers needs to be installed by the user.

This code can be used as a base for other implementations like firmware
upgrade web-server used by some vendors.

Usage:
  u-boot: start the we-server using the "httpd_upload" command
  PC: open the "http://your_uboot_ip"; link in the browser

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 cmd/Kconfig|  25 ++
 cmd/net.c  |  20 +
 include/net/httpd-upload.h |  12 +++
 net/Makefile   |   1 +
 net/httpd-upload.c | 170 +
 5 files changed, 228 insertions(+)
 create mode 100644 include/net/httpd-upload.h
 create mode 100644 net/httpd-upload.c

diff --git a/cmd/Kconfig b/cmd/Kconfig
index cfdc53e494a..b7749525ae3 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2017,6 +2017,31 @@ config CMD_NETCAT
  will be lost or reordered. Any netcat implementation should work,
  but openbsd one was tested only.
 
+config CMD_HTTPD_UPLOAD
+   bool "an example HTTP server for file uploading"
+   depends on HTTPD_COMMON
+   help
+ An example HTTP/1.1 compatible web-server implementation for file
+ uploading. It acts much like tftpboot command: put user data to a
+ specified memory location, but no special tools needs to be installed
+ on the user side. The only required tool is browser.
+
+ Start 'httpd_upload' command, open a browser, connect to the board IP,
+ select file to upload and press 'Upload' button. This is enougth.
+
+ There is no big profit from this code, but it can be used as a
+ reference for other web-server implementations (ex: web-based
+ firmware upgrade/recovery used by some router vendors)
+
+config CMD_HTTPD_UPLOAD_MAX_SIZE
+   int "Maximum uploading size"
+   depends on CMD_HTTPD_UPLOAD
+   default 209715200
+   help
+ This option sets the resriction on the size of any uploaded file.
+ Please reserve 2--4 Kb more space due to additional space required
+ for storing of multipart/form-data header and footer. 
+
 config CMD_MII
bool "mii"
imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 79bb126dbd4..f2df1eed2ef 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -21,6 +21,9 @@
 #include 
 #include 
 #include 
+#if defined(CONFIG_CMD_HTTPD_UPLOAD)
+#include 
+#endif
 
 static int netboot_common(enum proto_t, struct cmd_tbl *, int, char * const 
[]);
 
@@ -229,6 +232,23 @@ U_BOOT_CMD_WITH_SUBCMDS(netcat,
U_BOOT_SUBCMD_MKENT(save, 3, 0, do_netcat_save));
 #endif
 
+#if defined(CONFIG_CMD_HTTPD_UPLOAD)
+static int do_httpd_upload(struct cmd_tbl *cmdtp, int flag, int argc, char 
*const argv[])
+{
+   if (argc < 2)
+   return 1;
+
+   httpd_upload_prepare();
+   return netboot_common(HTTPD, cmdtp, argc, argv);
+}
+
+U_BOOT_CMD(
+   httpd_upload,   2,  1,  do_httpd_upload,
+   "starts httpd server for file uploading",
+   "[loadAddress]\n"
+);
+#endif
+
 static void netboot_update_env(void)
 {
char tmp[46];
diff --git a/include/net/httpd-upload.h b/include/net/httpd-upload.h
new file mode 100644
index 000..a80df214668
--- /dev/null
+++ b/include/net/httpd-upload.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ *
+ * httpd-upload include file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ */
+#ifndef __NET_HTTPD_UPLOAD_TCP_H__
+#define __NET_HTTPD_UPLOAD_TCP_H__
+
+void httpd_upload_prepare(void);
+
+#endif /* __NET_HTTPD_UPLOAD_TCP_H__ */
diff --git a/net/Makefile b/net/Makefile
index c1f491fad02..c1b41240aab 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
 obj-$(CONFIG_CMD_NETCAT) += netcat.o
 obj-$(CONFIG_HTTPD_COMMON) += httpd.o
+obj-$(CONFIG_CMD_HTTPD_UPLOAD) += httpd-upload.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/httpd-upload.c b/net/httpd-upload.c
new file mode 100644
index 000..8e43860fa74
--- /dev/null
+++ b/net/httpd-upload.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * httpd-upload support driver
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ */
+
+#include 
+#include 
+#include 
+#include 
+
+#define MAX_FILE_SIZE  CONFIG_CMD_HTTPD_UPLOAD_MAX_SIZE
+
+static enum net_loop_state  httpd_on_stop(void);
+
+static enum httpd_req_check httpd_pre_post(void *req_id, const char *url,
+  struct httpd_post_data *post);
+static struct http_reply

[PATCH v8 10/13] net/net: fix include ordering

2024-09-24 Thread Mikhail Kshevetskiy
fix include ordering to follow
  https://docs.u-boot.org/en/latest/develop/codingstyle.html#include-files

Signed-off-by: Mikhail Kshevetskiy 
---
 net/net.c | 24 +---
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/net/net.c b/net/net.c
index 86182cc504e..92156b5058b 100644
--- a/net/net.c
+++ b/net/net.c
@@ -88,41 +88,43 @@
 #include 
 #include 
 #include 
+#if defined(CONFIG_LED_STATUS)
+#include 
+#endif
 #include 
 #include 
 #include 
+#if defined(CONFIG_LED_STATUS)
+#include 
+#endif
+#include 
+#include 
 #include 
 #include 
-#include 
 #include 
 #if defined(CONFIG_CMD_PCAP)
 #include 
 #endif
-#include 
-#if defined(CONFIG_LED_STATUS)
-#include 
-#include 
-#endif
-#include 
-#include 
-#include 
 #include 
+#include 
+#include 
 #include 
+#include 
 #include "arp.h"
 #include "bootp.h"
 #include "cdp.h"
+#include "dhcpv6.h"
 #if defined(CONFIG_CMD_DNS)
 #include "dns.h"
 #endif
 #include "link_local.h"
+#include "net_rand.h"
 #include "nfs.h"
 #include "ping.h"
 #include "rarp.h"
 #if defined(CONFIG_CMD_WOL)
 #include "wol.h"
 #endif
-#include "dhcpv6.h"
-#include "net_rand.h"
 
 /** BOOTP EXTENTIONS **/
 
-- 
2.45.2



[PATCH v8 11/13] net/netcat: add netcat over tcp support

2024-09-24 Thread Mikhail Kshevetskiy
This patch adds downloading/uploading of data with netcat.
Client/server mode both supported.

How to test:

  netcat-openbsd=1.219-1 from debian were used for a tests

  a) Load data from remote host.
   * U-Boot listen on tcp port 3456
   * PC connects

 u-boot: netcat load ${loadaddr} 3456
 PC: netcat -q1 ${UBOOT_IP} 3456 < image.itb

  b) Load data from remote host.
   * PC listen on tcp port 3456
   * U-Boot connects

 PC: netcat -q1 -l -p 3456 < image.itb
 u-boot: netcat load ${loadaddr} ${PC_IP}:3456

  c) Save data to remote host
   * U-Boot listen on tcp port 3456
   * PC connects

 u-boot: netcat save ${loadaddr} ${data_size_in_hex} 3456
 PC: netcat -w1 ${UBOOT_IP} 3456 >image.itb

  d) Save data to remote host
   * PC listen on tcp port 3456
   * U-Boot connects

 PC: netcat -w1 -l -p 3456 >image.itb
 u-boot: netcat save ${loadaddr} ${data_size_in_hex} ${PC_IP}:3456

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 cmd/Kconfig  |  10 +++
 cmd/net.c|  35 ++--
 include/net.h|   2 +-
 include/net/netcat.h |  20 +
 net/Makefile |   1 +
 net/net.c|   9 ++
 net/netcat.c | 194 +++
 7 files changed, 265 insertions(+), 6 deletions(-)
 create mode 100644 include/net/netcat.h
 create mode 100644 net/netcat.c

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 978f44eda42..cfdc53e494a 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2007,6 +2007,16 @@ config CMD_WGET
  wget is a simple command to download kernel, or other files,
  from a http server over TCP.
 
+config CMD_NETCAT
+   bool "netcat"
+   select PROT_TCP
+   help
+ netcat is a simple command to load/store kernel, or other files,
+ using well-known netcat (nc) utility. Unlike classic netcat utility
+ this command supports TCP-based data transfer only, thus no data
+ will be lost or reordered. Any netcat implementation should work,
+ but openbsd one was tested only.
+
 config CMD_MII
bool "mii"
imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 53ce2bc5d0c..79bb126dbd4 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -206,6 +206,29 @@ U_BOOT_CMD(
 );
 #endif
 
+#if defined(CONFIG_CMD_NETCAT)
+static int do_netcat_load(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+   return netboot_common(NETCAT_LOAD, cmdtp, argc, argv);
+}
+
+static int do_netcat_save(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+   return netboot_common(NETCAT_SAVE, cmdtp, argc, argv);
+}
+
+U_BOOT_LONGHELP(netcat,
+   "load [loadAddress] [[hostIPaddr:]port]\n"
+   "save Address Size [[hostIPaddr:]port]");
+
+U_BOOT_CMD_WITH_SUBCMDS(netcat,
+   "load/store data over plain TCP via netcat utility", netcat_help_text,
+   U_BOOT_SUBCMD_MKENT(load, 1, 0, do_netcat_load),
+   U_BOOT_SUBCMD_MKENT(save, 3, 0, do_netcat_save));
+#endif
+
 static void netboot_update_env(void)
 {
char tmp[46];
@@ -323,16 +346,17 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
 
switch (argc) {
case 1:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
return 1;
-
/* refresh bootfile name from env */
copy_filename(net_boot_file_name, env_get("bootfile"),
  sizeof(net_boot_file_name));
break;
 
case 2:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
return 1;
/*
 * Only one arg - accept two forms:
@@ -354,7 +378,8 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
break;
 
case 3:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) {
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE)) {
if (parse_addr_size(argv))
return 1;
} else {
@@ -365,7 +390,7 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
}
break;
 
-#ifdef CONFIG_CMD_TFTPPUT
+#if defined(CONFIG_CMD_TFTPPUT) || defined(CONFIG_CMD_NETCAT)
c

[PATCH v8 09/13] net/tcp: define a fallback value for rcv_wnd size

2024-09-24 Thread Mikhail Kshevetskiy
Some driver implements it's own network packet pool, so PKTBUFSRX is zero.
This results in zero-size TCP receive window, so data transfer doesn't
work. Avoid it by setting a reasonable fallback value.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 6 +-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/net/tcp.c b/net/tcp.c
index 149670e77f9..ec41aa92a68 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -36,7 +36,11 @@
 #define TCP_SEND_RETRY 3
 #define TCP_SEND_TIMEOUT   2000UL
 #define TCP_RX_INACTIVE_TIMEOUT3UL
-#define TCP_RCV_WND_SIZE   (PKTBUFSRX * TCP_MSS)
+#if PKTBUFSRX != 0
+  #define TCP_RCV_WND_SIZE (PKTBUFSRX * TCP_MSS)
+#else
+  #define TCP_RCV_WND_SIZE (4 * TCP_MSS)
+#endif
 
 #define TCP_PACKET_OK  0
 #define TCP_PACKET_DROP1
-- 
2.45.2



[PATCH v8 07/13] test/cmd/wget: fix the test

2024-09-24 Thread Mikhail Kshevetskiy
The wget test seriously broken:
 * Uses zero values for ISS and IRS values (see RFC 9293).
 * Writes incorrect values to Sequence and Acknowledgment numbers fields
   of tcp packets. In the real life such packets will break the tcp stream.
 * The test should fail (see above), but due to buggy old tcp implementation
   it passes.

This patch fix all bugs mentioned above, so the test passes with new
and better tcp implementation.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 arch/sandbox/include/asm/eth.h |  4 +++
 test/cmd/wget.c| 45 --
 2 files changed, 36 insertions(+), 13 deletions(-)

diff --git a/arch/sandbox/include/asm/eth.h b/arch/sandbox/include/asm/eth.h
index f042a5f3b92..083a7371a3f 100644
--- a/arch/sandbox/include/asm/eth.h
+++ b/arch/sandbox/include/asm/eth.h
@@ -77,6 +77,8 @@ typedef int sandbox_eth_tx_hand_f(struct udevice *dev, void 
*pkt,
  * fake_host_hwaddr - MAC address of mocked machine
  * fake_host_ipaddr - IP address of mocked machine
  * disabled - Will not respond
+ * irs - tcp initial receive sequence
+ * iss - tcp initial send sequence
  * recv_packet_buffer - buffers of the packet returned as received
  * recv_packet_length - lengths of the packet returned as received
  * recv_packets - number of packets returned
@@ -87,6 +89,8 @@ struct eth_sandbox_priv {
uchar fake_host_hwaddr[ARP_HLEN];
struct in_addr fake_host_ipaddr;
bool disabled;
+   u32 irs;
+   u32 iss;
uchar * recv_packet_buffer[PKTBUFSRX];
int recv_packet_length[PKTBUFSRX];
int recv_packets;
diff --git a/test/cmd/wget.c b/test/cmd/wget.c
index 356a4dcd8fa..3e9a5f354fc 100644
--- a/test/cmd/wget.c
+++ b/test/cmd/wget.c
@@ -26,6 +26,8 @@
 #define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4)
 #define LEN_B_TO_DW(x) ((x) >> 2)
 
+#define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
+
 static int sb_arp_handler(struct udevice *dev, void *packet,
  unsigned int len)
 {
@@ -62,12 +64,14 @@ static int sb_syn_handler(struct udevice *dev, void *packet,
eth_send = (void *)priv->recv_packet_buffer[priv->recv_packets];
memcpy(eth_send->et_dest, eth->et_src, ARP_HLEN);
memcpy(eth_send->et_src, priv->fake_host_hwaddr, ARP_HLEN);
+   priv->irs = ntohl(tcp->tcp_seq);
+   priv->iss = ~priv->irs; /* just to differ from irs */
eth_send->et_protlen = htons(PROT_IP);
tcp_send = (void *)eth_send + ETHER_HDR_SIZE;
tcp_send->tcp_src = tcp->tcp_dst;
tcp_send->tcp_dst = tcp->tcp_src;
-   tcp_send->tcp_seq = htonl(0);
-   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1);
+   tcp_send->tcp_seq = htonl(priv->iss);
+   tcp_send->tcp_ack = htonl(priv->irs + 1);
tcp_send->tcp_hlen = 
SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
tcp_send->tcp_flags = TCP_SYN | TCP_ACK;
tcp_send->tcp_win = htons(PKTBUFSRX * TCP_MSS >> TCP_SCALE);
@@ -102,6 +106,8 @@ static int sb_ack_handler(struct udevice *dev, void *packet,
void *data;
int pkt_len;
int payload_len = 0;
+   u32 tcp_seq, tcp_ack;
+   int tcp_data_len;
const char *payload1 = "HTTP/1.1 200 OK\r\n"
"Content-Length: 30\r\n\r\n\r\n"
"Hi\r\n";
@@ -119,17 +125,32 @@ static int sb_ack_handler(struct udevice *dev, void 
*packet,
tcp_send->tcp_dst = tcp->tcp_src;
data = (void *)tcp_send + IP_TCP_HDR_SIZE;
 
-   if (ntohl(tcp->tcp_seq) == 1 && ntohl(tcp->tcp_ack) == 1) {
-   tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack));
-   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1);
+   tcp_seq = ntohl(tcp->tcp_seq) - priv->irs;
+   tcp_ack = ntohl(tcp->tcp_ack) - priv->iss;
+   tcp_data_len = len - ETHER_HDR_SIZE - IP_HDR_SIZE - 
GET_TCP_HDR_LEN_IN_BYTES(tcp->tcp_hlen);
+
+   if (tcp->tcp_flags & TCP_FIN)
+   tcp_data_len++;
+
+   tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack));
+   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + tcp_data_len);
+
+   if (tcp_seq == 1 && tcp_ack == 1) {
+   if (tcp_data_len == 0) {
+   /* no data, wait for GET request */
+   return -1;
+   }
+
+   /* reply to GET request */
payload_len = strlen(payload1);
memcpy(data, payload1, payload_len);
tcp_send->tcp_flags = TCP_ACK;
-   } else if (ntohl(tcp->tcp_seq) == 2) {
-   tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack));
-   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1);
+   } else if (tcp_ack == 1 + strlen(payload1)) {
payload_len = 0;

[PATCH v8 08/13] net/tcp: simplify tcp header filling code

2024-09-24 Thread Mikhail Kshevetskiy
This patch:
 * remove useless code,
 * use a special function for pretty printing of tcp flags,
 * simplify the code

The behavior should not be changed.

Signed-off-by: Mikhail Kshevetskiy 
---
 net/tcp.c | 66 +++
 1 file changed, 33 insertions(+), 33 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index 57d6acf2dc5..149670e77f9 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -542,10 +542,33 @@ void net_set_syn_options(struct tcp_stream *tcp, union 
tcp_build_pkt *b)
b->ip.end = TCP_O_END;
 }
 
+const char *tcpflags_to_str(char tcpflags, char *buf, int size)
+{
+   int i;
+   static const struct {
+   int bit;
+   const char  *name;
+   } desc[] = {{TCP_RST, "RST"}, {TCP_SYN, "SYN"}, {TCP_PUSH, "PSH"},
+   {TCP_FIN, "FIN"}, {TCP_ACK, "ACK"}};
+
+   *buf = '\0';
+   for (i = 0; i < ARRAY_SIZE(desc); i++) {
+   if (!(tcpflags & desc[i].bit))
+   continue;
+
+   if (*buf)
+   strlcat(buf, ",", size);
+   strlcat(buf, desc[i].name, size);
+   }
+
+   return buf;
+}
+
 int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num)
 {
union tcp_build_pkt *b = (union tcp_build_pkt *)pkt;
+   char buf[24];
int pkt_hdr_len;
int pkt_len;
int tcp_len;
@@ -555,55 +578,32 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar 
*pkt, int payload_len,
 * 4 bits reserved options
 */
b->ip.hdr.tcp_flags = action;
-   pkt_hdr_len = IP_TCP_HDR_SIZE;
b->ip.hdr.tcp_hlen = 
SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
 
switch (action) {
case TCP_SYN:
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:SYN (%pI4, %pI4, sq=%u, ak=%u)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num);
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
+  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
net_set_syn_options(tcp, b);
pkt_hdr_len = IP_TCP_O_SIZE;
break;
-   case TCP_SYN | TCP_ACK:
-   case TCP_ACK:
-   pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b);
-   b->ip.hdr.tcp_flags = action;
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:ACK (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
-  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num,
-  action);
-   break;
-   case TCP_FIN:
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:FIN  (%pI4, %pI4, s=%u, a=%u)\n",
-  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
-   payload_len = 0;
-   pkt_hdr_len = IP_TCP_HDR_SIZE;
-   break;
case TCP_RST | TCP_ACK:
case TCP_RST:
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:RST  (%pI4, %pI4, s=%u, a=%u)\n",
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
   &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
+   pkt_hdr_len = IP_TCP_HDR_SIZE;
break;
-   /* Notify connection closing */
-   case (TCP_FIN | TCP_ACK):
-   case (TCP_FIN | TCP_ACK | TCP_PUSH):
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%u, a=%u, 
A=%x)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num, action);
-   fallthrough;
default:
pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b);
-   b->ip.hdr.tcp_flags = action | TCP_PUSH | TCP_ACK;
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:dft  (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num, action);
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
+  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
+   break;
}
 
pkt_len = pkt_hdr_len + payload_len;
-- 
2.45.2



[PATCH v8 06/13] net/tcp: improve tcp framework, use better state machine

2024-09-24 Thread Mikhail Kshevetskiy
Changes:
 * Fix initial send sequence always zero issue
 * Use state machine close to RFC 9293. This should make TCP
   transfers more reliable (now we can upload a huge array
   of data from the board to external server)
 * Improve TCP framework a lot. This should make tcp client
   code much more simple.
 * rewrite wget with new tcp stack
 * rewrite fastboot_tcp with new tcp stack

It's quite hard to fix the initial send sequence (ISS) issue
with the separate patch. A naive attempt to fix an issue
inside the tcp_set_tcp_header() function will break tcp packet
retransmit logic in wget and other clients.

Example:
  Wget stores tcp_seq_num value before tcp_set_tcp_header() will
  be called and (on failure) retransmit the packet with the stored
  tcp_seq_num value. Thus:
* the same ISS must allways be used (current case)
* or tcp clients needs to generate a proper ISS when
  required.

A proper ISS fix will require a big redesing comparable with
a this one.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h  | 194 --
 include/net/wget.h |   8 -
 net/fastboot_tcp.c | 193 +-
 net/net.c  |   4 +
 net/tcp.c  | 858 ++---
 net/wget.c | 464 +++-
 6 files changed, 1042 insertions(+), 679 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 0694af9d5b1..cf4a6a812f4 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -265,6 +265,7 @@ union tcp_build_pkt {
  * @TCP_CLOSING: Rec FIN, sent FIN, ACK waiting for ACK
  * @TCP_FIN_WAIT_1: Sent FIN waiting for response
  * @TCP_FIN_WAIT_2: Rec ACK from FIN sent, waiting for FIN
+ * @TCP_LAST_ACK: Waiting for ACK of the connection termination
  */
 enum tcp_state {
TCP_CLOSED,
@@ -274,7 +275,20 @@ enum tcp_state {
TCP_CLOSE_WAIT,
TCP_CLOSING,
TCP_FIN_WAIT_1,
-   TCP_FIN_WAIT_2
+   TCP_FIN_WAIT_2,
+   TCP_LAST_ACK,
+};
+
+/**
+ * enum tcp_status - TCP stream status for connection
+ * @TCP_ERR_OK: no rx/tx errors
+ * @TCP_ERR_TOUT: rx/tx timeout happened
+ * @TCP_ERR_RST: connection was reset
+ */
+enum tcp_status {
+   TCP_ERR_OK = 0,
+   TCP_ERR_TOUT,
+   TCP_ERR_RST,
 };
 
 /**
@@ -283,51 +297,150 @@ enum tcp_state {
  * @rport: Remote port, host byte order
  * @lport: Local port, host byte order
  *
+ * @priv:  User private data (not used by tcp module)
+ *
+ * @max_retry_count:   Maximum retransmit attempts (default 3)
+ * @initial_timeout:   Timeout from initial TX to reTX (default 2 sec)
+ * @rx_inactiv_timeout:Maximum time from last rx till connection drop
+ *   (default 30 sec)
+ *
+ * @on_closed: User callback, called just before destroying TCP stream
+ * @on_established:User callback, called when TCP stream enters
+ *   TCP_ESTABLISHED state
+ * @on_rcv_nxt_update: User callback, called when all data in the segment
+ *   [0..rx_bytes - 1] was received
+ * @on_snd_una_update: User callback, called when all data in the segment
+ *   [0..tx_bytes - 1] were transferred and acknowledged
+ * @rx:User callback, called on receive of segment
+ *   [rx_offs..rx_offs+len-1]. If NULL -- all incoming data
+ *   will be ignored. User SHOULD store the segment and
+ *   return the number of accepted bytes.
+ *   WARNING: Previous segmengs may not be received yet
+ * @tx:User callback, called on transmit/retransmit of 
segment
+ *   [tx_offs..tx_offs+maxlen-1]. If NULL -- no data will
+ *   be transmitted. User SHOULD fill provided buffer and
+ *   return the number of bytes in the buffer.
+ *   WARNING: do not use tcp_stream_close() from this
+ * callback (it will break stream). Better use
+ * on_snd_una_update() callback for such purposes.
+ *
+ * @time_last_rx:  Arrival time of last valid incoming package (ticks)
+ * @time_start:Timeout start time (ticks)
+ * @time_delta:Timeout duration (ticks)
+ * @time_handler   Timeout handler for a stream
+ *
  * @state: TCP connection state
+ * @status:TCP stream status (OK or ERR)
+ *
+ * @fin_rx:Non-zero if TCP_FIN was received
+ * @fin_rx_seq:TCP sequence of rx FIN bit
+ * @fin_tx:Non-zero if TCP_FIN was sent (or planned to send)
+ * @fin_tx_seq:TCP sequence of tx FIN bit
+ *
+ * @iss:   Initial send sequence number
+ * @snd_una:   Send unacknowledged
+ * @snd_nxt:   Send next
+ * @snd_wnd:   Send window (in bytes)
+ * @snd_wl1:   Segment sequence number used for

[PATCH v8 05/13] net/tcp: rename ack_edge and seq_init to more common rcv_nxt and irs

2024-09-24 Thread Mikhail Kshevetskiy
Use the names from RFC 9293

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h |  8 
 net/tcp.c | 32 
 2 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index f224d0cae2f..0694af9d5b1 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -285,8 +285,8 @@ enum tcp_state {
  *
  * @state: TCP connection state
  *
- * @seq_init:  Initial receive sequence number
- * @ack_edge:  Receive next
+ * @irs:   Initial receive sequence number
+ * @rcv_nxt:   Receive next
  *
  * @loc_timestamp: Local timestamp
  * @rmt_timestamp: Remote timestamp
@@ -301,8 +301,8 @@ struct tcp_stream {
/* TCP connection state */
enum tcp_state  state;
 
-   u32 seq_init;
-   u32 ack_edge;
+   u32 irs;
+   u32 rcv_nxt;
 
/* TCP option timestamp */
u32 loc_timestamp;
diff --git a/net/tcp.c b/net/tcp.c
index 0c32c5d7c92..7e445eaffd6 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -360,9 +360,9 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, 
int payload_len,
pkt_len = pkt_hdr_len + payload_len;
tcp_len = pkt_len - IP_HDR_SIZE;
 
-   tcp->ack_edge = tcp_ack_num;
+   tcp->rcv_nxt = tcp_ack_num;
/* TCP Header */
-   b->ip.hdr.tcp_ack = htonl(tcp->ack_edge);
+   b->ip.hdr.tcp_ack = htonl(tcp->rcv_nxt);
b->ip.hdr.tcp_src = htons(tcp->lport);
b->ip.hdr.tcp_dst = htons(tcp->rport);
b->ip.hdr.tcp_seq = htonl(tcp_seq_num);
@@ -396,10 +396,10 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar 
*pkt, int payload_len,
return pkt_hdr_len;
 }
 
-static void tcp_update_ack_edge(struct tcp_stream *tcp)
+static void tcp_update_rcv_nxt(struct tcp_stream *tcp)
 {
-   if (tcp_seq_cmp(tcp->ack_edge, tcp->lost.hill[0].l) >= 0) {
-   tcp->ack_edge = tcp->lost.hill[0].r;
+   if (tcp_seq_cmp(tcp->rcv_nxt, tcp->lost.hill[0].l) >= 0) {
+   tcp->rcv_nxt = tcp->lost.hill[0].r;
 
memmove(&tcp->lost.hill[0], &tcp->lost.hill[1],
(TCP_SACK_HILLS - 1) * sizeof(struct sack_edges));
@@ -434,7 +434,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 
len)
tcp_seq_num = tcp->lost.hill[i].l;
}
if (tcp_seq_cmp(tcp->lost.hill[i].r, tcp_seq_num + len) >= 0) {
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
@@ -463,12 +463,12 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, 
u32 len)
}
}
 
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
if (i == TCP_SACK_HILLS) {
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
@@ -489,7 +489,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 
len)
tcp->lost.hill[i].r = tcp_seq_num + len;
tcp->lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8;
 
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
 };
 
 /**
@@ -566,8 +566,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
debug_cond(DEBUG_INT_STATE, "TCP CLOSED %x\n", tcp_flags);
if (tcp_syn) {
action = TCP_SYN | TCP_ACK;
-   tcp->seq_init = tcp_seq_num;
-   tcp->ack_edge = tcp_seq_num + 1;
+   tcp->irs = tcp_seq_num;
+   tcp->rcv_nxt = tcp_seq_num + 1;
tcp->lost.len = TCP_OPT_LEN_2;
tcp->state = TCP_SYN_RECEIVED;
} else if (tcp_ack || tcp_fin) {
@@ -583,8 +583,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
tcp->state = TCP_CLOSE_WAIT;
} else if (tcp_ack || (tcp_syn && tcp_ack)) {
action |= TCP_ACK;
-   tcp->seq_init = tcp_seq_num;
-   tcp->ack_edge = tcp_seq_num + 1;
+   tcp->irs = tcp_seq_num;
+   tcp->rcv_nxt = tcp_seq_num + 1;
tcp->state = TCP_ESTABLISHED;
 
if (tcp_syn && tcp_ack)
@@ -633,7 +633,7 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
case TCP_FIN_WAIT_1:
debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_1 (%x)\n", tcp_flags);
if (tcp_fin) {
-   

[PATCH v8 03/13] net/tcp: put connection specific data into a tcp_stream structure

2024-09-24 Thread Mikhail Kshevetskiy
no functional changes

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h |  37 +++-
 net/net.c |  11 ++-
 net/tcp.c | 231 +++---
 net/wget.c|   3 +-
 4 files changed, 163 insertions(+), 119 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index c29d4ce24a7..14aee64cb1c 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -277,9 +277,40 @@ enum tcp_state {
TCP_FIN_WAIT_2
 };
 
-enum tcp_state tcp_get_tcp_state(void);
-void tcp_set_tcp_state(enum tcp_state new_state);
-int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len,
+/**
+ * struct tcp_stream - TCP data stream structure
+ *
+ * @state: TCP connection state
+ *
+ * @seq_init:  Initial receive sequence number
+ * @ack_edge:  Receive next
+ *
+ * @loc_timestamp: Local timestamp
+ * @rmt_timestamp: Remote timestamp
+ *
+ * @lost:  Used for SACK
+ */
+struct tcp_stream {
+   /* TCP connection state */
+   enum tcp_state  state;
+
+   u32 seq_init;
+   u32 ack_edge;
+
+   /* TCP option timestamp */
+   u32 loc_timestamp;
+   u32 rmt_timestamp;
+
+   /* TCP sliding window control used to request re-TX */
+   struct tcp_sack_v lost;
+};
+
+struct tcp_stream *tcp_stream_get(void);
+
+enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp);
+void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state);
+int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport,
+  int sport, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 
 /**
diff --git a/net/net.c b/net/net.c
index d9bc9df643f..6c5ee7e0925 100644
--- a/net/net.c
+++ b/net/net.c
@@ -414,7 +414,7 @@ int net_init(void)
/* Only need to setup buffer pointers once. */
first_call = 0;
if (IS_ENABLED(CONFIG_PROT_TCP))
-   tcp_set_tcp_state(TCP_CLOSED);
+   tcp_set_tcp_state(tcp_stream_get(), TCP_CLOSED);
}
 
return net_init_loop();
@@ -915,6 +915,9 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
uchar *pkt;
int eth_hdr_size;
int pkt_hdr_size;
+#if defined(CONFIG_PROT_TCP)
+   struct tcp_stream *tcp;
+#endif
 
/* make sure the net_tx_packet is initialized (net_init() was called) */
assert(net_tx_packet != NULL);
@@ -941,8 +944,12 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
break;
 #if defined(CONFIG_PROT_TCP)
case IPPROTO_TCP:
+   tcp = tcp_stream_get();
+   if (tcp == NULL)
+   return -EINVAL;
+
pkt_hdr_size = eth_hdr_size
-   + tcp_set_tcp_header(pkt + eth_hdr_size, dport, sport,
+   + tcp_set_tcp_header(tcp, pkt + eth_hdr_size, dport, 
sport,
 payload_len, action, tcp_seq_num,
 tcp_ack_num);
break;
diff --git a/net/tcp.c b/net/tcp.c
index 724536cb352..6646f171b83 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -24,19 +24,8 @@
 #include 
 #include 
 
-/*
- * TCP sliding window  control used by us to request re-TX
- */
-static struct tcp_sack_v tcp_lost;
-
-/* TCP option timestamp */
-static u32 loc_timestamp;
-static u32 rmt_timestamp;
-
-static u32 tcp_seq_init;
-static u32 tcp_ack_edge;
-
 static int tcp_activity_count;
+static struct tcp_stream tcp_stream;
 
 /*
  * TCP lengths are stored as a rounded up number of 32 bit words.
@@ -48,9 +37,6 @@ static int tcp_activity_count;
 #define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4)
 #define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
 
-/* TCP connection state */
-static enum tcp_state current_tcp_state;
-
 /* Current TCP RX packet handler */
 static rxhand_tcp *tcp_packet_handler;
 
@@ -60,22 +46,30 @@ static inline s32 tcp_seq_cmp(u32 a, u32 b)
 }
 
 /**
- * tcp_get_tcp_state() - get current TCP state
+ * tcp_get_tcp_state() - get TCP stream state
+ * @tcp: tcp stream
  *
- * Return: Current TCP state
+ * Return: TCP stream state
  */
-enum tcp_state tcp_get_tcp_state(void)
+enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp)
 {
-   return current_tcp_state;
+   return tcp->state;
 }
 
 /**
- * tcp_set_tcp_state() - set current TCP state
+ * tcp_set_tcp_state() - set TCP stream state
+ * @tcp: tcp stream
  * @new_state: new TCP state
  */
-void tcp_set_tcp_state(enum tcp_state new_state)
+void tcp_set_tcp_state(struct tcp_stream *tcp,
+  enum tcp_state new_state)
 {
-   current_tcp_state = new_state;
+   tcp->state = new_state;
+}
+
+struct tcp_stream *tcp_stream_get(void)
+{
+   return &tcp_stream;
 }
 
 sta

[PATCH v8 04/13] net/tcp: add connection info to tcp_stream structure

2024-09-24 Thread Mikhail Kshevetskiy
Changes:
 * Avoid use net_server_ip in tcp code, use tcp_stream data instead
 * Ignore packets from other connections if connection already created.
   This prevents us from connection break caused by other tcp stream.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net.h  |   5 +-
 include/net/tcp.h  |  57 +---
 net/fastboot_tcp.c |  50 +
 net/net.c  |  12 ++---
 net/tcp.c  | 131 ++---
 net/wget.c |  52 +++---
 6 files changed, 204 insertions(+), 103 deletions(-)

diff --git a/include/net.h b/include/net.h
index bb2ae20f52a..b0ce13e0a9d 100644
--- a/include/net.h
+++ b/include/net.h
@@ -667,6 +667,7 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
 /**
  * net_send_tcp_packet() - Transmit TCP packet.
  * @payload_len: length of payload
+ * @dhost: Destination host
  * @dport: Destination TCP port
  * @sport: Source TCP port
  * @action: TCP action to be performed
@@ -675,8 +676,8 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
  *
  * Return: 0 on success, other value on failure
  */
-int net_send_tcp_packet(int payload_len, int dport, int sport, u8 action,
-   u32 tcp_seq_num, u32 tcp_ack_num);
+int net_send_tcp_packet(int payload_len, struct in_addr dhost, int dport,
+   int sport, u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport,
int sport, int payload_len);
 
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 14aee64cb1c..f224d0cae2f 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -279,6 +279,9 @@ enum tcp_state {
 
 /**
  * struct tcp_stream - TCP data stream structure
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ * @lport: Local port, host byte order
  *
  * @state: TCP connection state
  *
@@ -291,6 +294,10 @@ enum tcp_state {
  * @lost:  Used for SACK
  */
 struct tcp_stream {
+   struct in_addr  rhost;
+   u16 rport;
+   u16 lport;
+
/* TCP connection state */
enum tcp_state  state;
 
@@ -305,16 +312,53 @@ struct tcp_stream {
struct tcp_sack_v lost;
 };
 
-struct tcp_stream *tcp_stream_get(void);
+void tcp_init(void);
+
+typedef int tcp_incoming_filter(struct in_addr rhost,
+   u16 rport, u16 sport);
+
+/*
+ * This function sets user callback used to accept/drop incoming
+ * connections. Callback should:
+ *  + Check TCP stream endpoint and make connection verdict
+ *- return non-zero value to accept connection
+ *- return zero to drop connection
+ *
+ * WARNING: If callback is NOT defined, all incoming connections
+ *  will be dropped.
+ */
+void tcp_set_incoming_filter(tcp_incoming_filter *filter);
+
+/*
+ * tcp_stream_get -- Get or create TCP stream
+ * @is_new:if non-zero and no stream found, then create a new one
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ * @lport: Local port, host byte order
+ *
+ * Returns: TCP stream structure or NULL (if not found/created)
+ */
+struct tcp_stream *tcp_stream_get(int is_new, struct in_addr rhost,
+ u16 rport, u16 lport);
+
+/*
+ * tcp_stream_connect -- Create new TCP stream for remote connection.
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ *
+ * Returns: TCP new stream structure or NULL (if not created).
+ *  Random local port will be used.
+ */
+struct tcp_stream *tcp_stream_connect(struct in_addr rhost, u16 rport);
+
+enum tcp_state tcp_stream_get_state(struct tcp_stream *tcp);
 
-enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp);
-void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state);
-int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport,
-  int sport, int payload_len,
+int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 
 /**
  * rxhand_tcp() - An incoming packet handler.
+ * @tcp: TCP stream
  * @pkt: pointer to the application packet
  * @dport: destination TCP port
  * @sip: source IP address
@@ -324,8 +368,7 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, 
int dport,
  * @action: TCP action (SYN, ACK, FIN, etc)
  * @len: packet length
  */
-typedef void rxhand_tcp(uchar *pkt, u16 dport,
-   struct in_addr sip, u16 sport,
+typedef void rxhand_tcp(struct tcp_stream *tcp, uchar *pkt,
u32 tcp_seq_num, u32 tcp_ack_num,
u8 action, unsigned int len);
 void tcp_set_tcp_handler(rxhand_tcp *f);
diff --git a/net

[PATCH v8 01/13] net/tcp: fix TCP options processing

2024-09-24 Thread Mikhail Kshevetskiy
Current TCP code may miss an option if TCP_O_NOP option was used before
it for proper aligning.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 6 --
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index b0cc8a1fe3e..3e3118de450 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -475,7 +475,7 @@ void tcp_parse_options(uchar *o, int o_len)
 * NOPs are options with a zero length, and thus are special.
 * All other options have length fields.
 */
-   for (p = o; p < (o + o_len); p = p + p[1]) {
+   for (p = o; p < (o + o_len); ) {
if (!p[1])
return; /* Finished processing options */
 
@@ -490,12 +490,14 @@ void tcp_parse_options(uchar *o, int o_len)
case TCP_O_TS:
tsopt = (struct tcp_t_opt *)p;
rmt_timestamp = tsopt->t_snd;
-   return;
+   break;
}
 
/* Process optional NOPs */
if (p[0] == TCP_O_NOP)
p++;
+   else
+   p += p[1];
}
 }
 
-- 
2.45.2



[PATCH v8 02/13] net/tcp: fix selective acknowledge

2024-09-24 Thread Mikhail Kshevetskiy
Current code assume that all (except last) packets are of the same size.
This is definitely wrong. Replace SACK code with a new one, that does
not rely on this assumption. Also this code uses less memory.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 200 +++---
 1 file changed, 86 insertions(+), 114 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index 3e3118de450..724536cb352 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -38,21 +38,6 @@ static u32 tcp_ack_edge;
 
 static int tcp_activity_count;
 
-/*
- * Search for TCP_SACK and review the comments before the code section
- * TCP_SACK is the number of packets at the front of the stream
- */
-
-enum pkt_state {PKT, NOPKT};
-struct sack_r {
-   struct sack_edges se;
-   enum pkt_state st;
-};
-
-static struct sack_r edge_a[TCP_SACK];
-static unsigned int sack_idx;
-static unsigned int prev_len;
-
 /*
  * TCP lengths are stored as a rounded up number of 32 bit words.
  * Add 3 to length round up, rounded, then divided into the
@@ -69,6 +54,11 @@ static enum tcp_state current_tcp_state;
 /* Current TCP RX packet handler */
 static rxhand_tcp *tcp_packet_handler;
 
+static inline s32 tcp_seq_cmp(u32 a, u32 b)
+{
+   return (s32)(a - b);
+}
+
 /**
  * tcp_get_tcp_state() - get current TCP state
  *
@@ -267,6 +257,7 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
action = TCP_FIN;
current_tcp_state = TCP_FIN_WAIT_1;
} else {
+   tcp_lost.len = TCP_OPT_LEN_2;
current_tcp_state = TCP_SYN_SENT;
}
break;
@@ -353,6 +344,20 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
return pkt_hdr_len;
 }
 
+static void tcp_update_ack_edge(void)
+{
+   if (tcp_seq_cmp(tcp_ack_edge, tcp_lost.hill[0].l) >= 0) {
+   tcp_ack_edge = tcp_lost.hill[0].r;
+
+   memmove(&tcp_lost.hill[0], &tcp_lost.hill[1],
+   (TCP_SACK_HILLS - 1) * sizeof(struct sack_edges));
+
+   tcp_lost.len -= TCP_OPT_LEN_8;
+   tcp_lost.hill[TCP_SACK_HILLS - 1].l = TCP_O_NOP;
+   tcp_lost.hill[TCP_SACK_HILLS - 1].r = TCP_O_NOP;
+   }
+}
+
 /**
  * tcp_hole() - Selective Acknowledgment (Essential for fast stream transfer)
  * @tcp_seq_num: TCP sequence start number
@@ -360,106 +365,79 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
  */
 void tcp_hole(u32 tcp_seq_num, u32 len)
 {
-   u32 idx_sack, sack_in;
-   u32 sack_end = TCP_SACK - 1;
-   u32 hill = 0;
-   enum pkt_state expect = PKT;
-   u32 seq = tcp_seq_num - tcp_seq_init;
-   u32 hol_l = tcp_ack_edge - tcp_seq_init;
-   u32 hol_r = 0;
-
-   /* Place new seq number in correct place in receive array */
-   if (prev_len == 0)
-   prev_len = len;
-
-   idx_sack = sack_idx + ((tcp_seq_num - tcp_ack_edge) / prev_len);
-   if (idx_sack < TCP_SACK) {
-   edge_a[idx_sack].se.l = tcp_seq_num;
-   edge_a[idx_sack].se.r = tcp_seq_num + len;
-   edge_a[idx_sack].st = PKT;
+   int i, j, cnt, cnt_move;
 
-   /*
-* The fin (last) packet is not the same length as data
-* packets, and if it's length is recorded and used for
-*  array index calculation, calculation breaks.
-*/
-   if (prev_len < len)
-   prev_len = len;
-   }
+   cnt = (tcp_lost.len - TCP_OPT_LEN_2) / TCP_OPT_LEN_8;
+   for (i = 0; i < cnt; i++) {
+   if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num) < 0)
+   continue;
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num + len) > 0)
+   break;
 
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP 1 seq %d, edg %d, len %d, sack_idx %d, sack_end %d\n",
-   seq, hol_l, len, sack_idx, sack_end);
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) > 0)
+   tcp_lost.hill[i].l = tcp_seq_num;
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) < 0) {
+   len += tcp_seq_num - tcp_lost.hill[i].l;
+   tcp_seq_num = tcp_lost.hill[i].l;
+   }
+   if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num + len) >= 0) {
+   tcp_update_ack_edge();
+   return;
+   }
 
-   /* Right edge of contiguous stream, is the left edge of first hill */
-   hol_l = tcp_seq_num - tcp_seq_init;
-   hol_r = hol_l + len;
+   /* check overlapping with next hills */
+   cnt_move = 0;
+   tcp_lost.hill[i].r = tcp_seq_num

[PATCH v8 00/13] net: tcp: improve tcp support

2024-09-24 Thread Mikhail Kshevetskiy
U-Boot support of LWIP is not ready for a moment, but we already have
some kind of tcp support. Unfrotunately this support is really bad.

Some of the known issues:
 * tcp packet from other connection can break a current one
 * tcp send sequence always starts from zero
 * bad tcp options processing
 * strange assumptions on packet size for selectiv acknowledge
 * tcp interface assumes one of the two scenarios:
 - data downloading from remote host to a board
 - request-response exchange with a small packets
   so it's not possible to upload large amount of data from the
   board to remote host.
 * wget test generate bad tcp stream, test should fail but it passes instead

This series of patches fixes all of the above issuess.

The benefits:
 * A lot of bug was fixed
 * Better and more reliable TCP state machine
 * Tcp cliens becomes smaller/simpler
 * Data uploading was fixed (now it's possible to transmit a huge amount of
   data from the board to remote host)
 * Netcat over tcp was implemented. Netcat supports data downloading/uploading
   from/to remote host in client/server mode.
 * An example web-server implementation. This code can be used as a base
   for web-based firmware uploading used by some vendors.

Modification was verified with
 * firmware downloading via u-boot wget command
 * fastboot over tcp
 * netcat linux client
 * Firefox/Chrome/Edge using example web-server implementation

Changes v2:
 * cover letter was added
 * some patches were removed

Changes v3:
 * better cover letter

Changes v4:
 * fix bug in debug output
 * add more comments
 * codestyle fixes

Changes v5:
 * old patches were ocasionally sent with v4
 * add back web-server patches
 * fix bug in debug output
 * add more comments
 * codestyle fixes

Changes v6:
 * fix the wget test
 * improve description of "simplify tcp header filling code" patch

Changes v7:
 * fix include ordering
 * improve option descriptions
 * fix a lot of extra brackets and comparisons against NULL / 0
 * add empty lines before final returns
 * fixed a bug with zero size httpd uploads

Changes v8:
 * [tcp] add function to restart rx inactivity timeout. This may help
in the cases where remote peer waits for u-boot data, but u-boot
is busy with long executing action
 * [httpd] add function returning underlying tcp stream from request id.
This (with the above change) allows avoid connection break
for the long processing post requests.

Mikhail Kshevetskiy (13):
  net/tcp: fix TCP options processing
  net/tcp: fix selective acknowledge
  net/tcp: put connection specific data into a tcp_stream structure
  net/tcp: add connection info to tcp_stream structure
  net/tcp: rename ack_edge and seq_init to more common rcv_nxt and irs
  net/tcp: improve tcp framework, use better state machine
  test/cmd/wget: fix the test
  net/tcp: simplify tcp header filling code
  net/tcp: define a fallback value for rcv_wnd size
  net/net: fix include ordering
  net/netcat: add netcat over tcp support
  net/httpd: add httpd common code
  net/httpd-upload: an example web-server implementation for file
uploading

 arch/sandbox/include/asm/eth.h |4 +
 cmd/Kconfig|   35 +
 cmd/net.c  |   55 +-
 include/net.h  |7 +-
 include/net/httpd-upload.h |   12 +
 include/net/httpd.h|   71 ++
 include/net/netcat.h   |   20 +
 include/net/tcp.h  |  250 ++-
 include/net/wget.h |8 -
 net/Kconfig|   14 +
 net/Makefile   |3 +
 net/fastboot_tcp.c |  193 +++--
 net/httpd-upload.c |  170 +
 net/httpd.c|  735 +++
 net/net.c  |   60 +-
 net/netcat.c   |  194 +
 net/tcp.c  | 1264 ++--
 net/wget.c |  481 
 test/cmd/wget.c|   45 +-
 19 files changed, 2723 insertions(+), 898 deletions(-)
 create mode 100644 include/net/httpd-upload.h
 create mode 100644 include/net/httpd.h
 create mode 100644 include/net/netcat.h
 create mode 100644 net/httpd-upload.c
 create mode 100644 net/httpd.c
 create mode 100644 net/netcat.c

-- 
2.45.2



[PATCH v7 10/13] net/net: fix include ordering

2024-09-20 Thread Mikhail Kshevetskiy
fix include ordering to follow
  https://docs.u-boot.org/en/latest/develop/codingstyle.html#include-files

Signed-off-by: Mikhail Kshevetskiy 
---
 net/net.c | 24 +---
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/net/net.c b/net/net.c
index 86182cc504e..92156b5058b 100644
--- a/net/net.c
+++ b/net/net.c
@@ -88,41 +88,43 @@
 #include 
 #include 
 #include 
+#if defined(CONFIG_LED_STATUS)
+#include 
+#endif
 #include 
 #include 
 #include 
+#if defined(CONFIG_LED_STATUS)
+#include 
+#endif
+#include 
+#include 
 #include 
 #include 
-#include 
 #include 
 #if defined(CONFIG_CMD_PCAP)
 #include 
 #endif
-#include 
-#if defined(CONFIG_LED_STATUS)
-#include 
-#include 
-#endif
-#include 
-#include 
-#include 
 #include 
+#include 
+#include 
 #include 
+#include 
 #include "arp.h"
 #include "bootp.h"
 #include "cdp.h"
+#include "dhcpv6.h"
 #if defined(CONFIG_CMD_DNS)
 #include "dns.h"
 #endif
 #include "link_local.h"
+#include "net_rand.h"
 #include "nfs.h"
 #include "ping.h"
 #include "rarp.h"
 #if defined(CONFIG_CMD_WOL)
 #include "wol.h"
 #endif
-#include "dhcpv6.h"
-#include "net_rand.h"
 
 /** BOOTP EXTENTIONS **/
 
-- 
2.45.2



[PATCH v7 13/13] net/httpd-upload: an example web-server implementation for file uploading

2024-09-20 Thread Mikhail Kshevetskiy
This is an example web-server implementation. It can be used for files
uploading to u-boot using a web-browser. It acts much like tftpget, but no
special servers needs to be installed by the user.

This code can be used as a base for other implementations like firmware
upgrade web-server used by some vendors.

Usage:
  u-boot: start the we-server using the "httpd_upload" command
  PC: open the "http://your_uboot_ip"; link in the browser

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 cmd/Kconfig|  25 ++
 cmd/net.c  |  20 +
 include/net/httpd-upload.h |  12 +++
 net/Makefile   |   1 +
 net/httpd-upload.c | 170 +
 5 files changed, 228 insertions(+)
 create mode 100644 include/net/httpd-upload.h
 create mode 100644 net/httpd-upload.c

diff --git a/cmd/Kconfig b/cmd/Kconfig
index cfdc53e494a..b7749525ae3 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2017,6 +2017,31 @@ config CMD_NETCAT
  will be lost or reordered. Any netcat implementation should work,
  but openbsd one was tested only.
 
+config CMD_HTTPD_UPLOAD
+   bool "an example HTTP server for file uploading"
+   depends on HTTPD_COMMON
+   help
+ An example HTTP/1.1 compatible web-server implementation for file
+ uploading. It acts much like tftpboot command: put user data to a
+ specified memory location, but no special tools needs to be installed
+ on the user side. The only required tool is browser.
+
+ Start 'httpd_upload' command, open a browser, connect to the board IP,
+ select file to upload and press 'Upload' button. This is enougth.
+
+ There is no big profit from this code, but it can be used as a
+ reference for other web-server implementations (ex: web-based
+ firmware upgrade/recovery used by some router vendors)
+
+config CMD_HTTPD_UPLOAD_MAX_SIZE
+   int "Maximum uploading size"
+   depends on CMD_HTTPD_UPLOAD
+   default 209715200
+   help
+ This option sets the resriction on the size of any uploaded file.
+ Please reserve 2--4 Kb more space due to additional space required
+ for storing of multipart/form-data header and footer. 
+
 config CMD_MII
bool "mii"
imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 79bb126dbd4..f2df1eed2ef 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -21,6 +21,9 @@
 #include 
 #include 
 #include 
+#if defined(CONFIG_CMD_HTTPD_UPLOAD)
+#include 
+#endif
 
 static int netboot_common(enum proto_t, struct cmd_tbl *, int, char * const 
[]);
 
@@ -229,6 +232,23 @@ U_BOOT_CMD_WITH_SUBCMDS(netcat,
U_BOOT_SUBCMD_MKENT(save, 3, 0, do_netcat_save));
 #endif
 
+#if defined(CONFIG_CMD_HTTPD_UPLOAD)
+static int do_httpd_upload(struct cmd_tbl *cmdtp, int flag, int argc, char 
*const argv[])
+{
+   if (argc < 2)
+   return 1;
+
+   httpd_upload_prepare();
+   return netboot_common(HTTPD, cmdtp, argc, argv);
+}
+
+U_BOOT_CMD(
+   httpd_upload,   2,  1,  do_httpd_upload,
+   "starts httpd server for file uploading",
+   "[loadAddress]\n"
+);
+#endif
+
 static void netboot_update_env(void)
 {
char tmp[46];
diff --git a/include/net/httpd-upload.h b/include/net/httpd-upload.h
new file mode 100644
index 000..a80df214668
--- /dev/null
+++ b/include/net/httpd-upload.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ *
+ * httpd-upload include file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ */
+#ifndef __NET_HTTPD_UPLOAD_TCP_H__
+#define __NET_HTTPD_UPLOAD_TCP_H__
+
+void httpd_upload_prepare(void);
+
+#endif /* __NET_HTTPD_UPLOAD_TCP_H__ */
diff --git a/net/Makefile b/net/Makefile
index c1f491fad02..c1b41240aab 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
 obj-$(CONFIG_CMD_NETCAT) += netcat.o
 obj-$(CONFIG_HTTPD_COMMON) += httpd.o
+obj-$(CONFIG_CMD_HTTPD_UPLOAD) += httpd-upload.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/httpd-upload.c b/net/httpd-upload.c
new file mode 100644
index 000..8e43860fa74
--- /dev/null
+++ b/net/httpd-upload.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * httpd-upload support driver
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ */
+
+#include 
+#include 
+#include 
+#include 
+
+#define MAX_FILE_SIZE  CONFIG_CMD_HTTPD_UPLOAD_MAX_SIZE
+
+static enum net_loop_state  httpd_on_stop(void);
+
+static enum httpd_req_check httpd_pre_post(void *req_id, const char *url,
+  struct httpd_post_data *post);
+static struct http_reply

[PATCH v7 12/13] net/httpd: add httpd common code

2024-09-20 Thread Mikhail Kshevetskiy
This patch adds HTTP/1.1 compatible web-server that can be used
by other. Server supports GET, POST, and HEAD requests. On client
request it will call user specified GET/POST callback. Then results
will be transmitted to client.

The following restrictions exist on the POST request
at the moment:
  * only multipart/form-data with a single file object
  * object will be stored to a memory area specified in
image_load_addr variable

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net.h   |   2 +-
 include/net/httpd.h |  64 
 net/Kconfig |  14 +
 net/Makefile|   1 +
 net/httpd.c | 730 
 net/net.c   |   6 +
 6 files changed, 816 insertions(+), 1 deletion(-)
 create mode 100644 include/net/httpd.h
 create mode 100644 net/httpd.c

diff --git a/include/net.h b/include/net.h
index 0af6493788a..154885a2b7e 100644
--- a/include/net.h
+++ b/include/net.h
@@ -515,7 +515,7 @@ extern int  net_restart_wrap;   /* Tried all 
network devices */
 enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
-   WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, RS
+   WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, HTTPD, RS
 };
 
 extern charnet_boot_file_name[1024];/* Boot File name */
diff --git a/include/net/httpd.h b/include/net/httpd.h
new file mode 100644
index 000..ff0dc93ecf5
--- /dev/null
+++ b/include/net/httpd.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * httpd support header file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ *
+ */
+#ifndef __NET_HTTPD_COMMON_H__
+#define __NET_HTTPD_COMMON_H__
+
+struct http_reply {
+   int code;
+   const char  *code_msg;
+   const char  *data_type;
+   void*data;
+   u32 len;
+};
+
+struct httpd_post_data {
+   const char  *name;
+   const char  *filename;
+   void*addr;
+   u32 size;
+};
+
+enum httpd_req_check {
+   HTTPD_REQ_OK,
+   HTTPD_BAD_URL,
+   HTTPD_BAD_REQ,
+   HTTPD_CLNT_RST
+};
+
+struct httpd_config {
+   enum net_loop_state (*on_stop)(void);
+   void(*on_req_end)(void *req_id);
+
+   enum httpd_req_check(*pre_get)(void *req_id, const char *url);
+   enum httpd_req_check(*pre_post)(void *req_id, const char *url,
+   struct httpd_post_data *post);
+
+   struct http_reply * (*get)(void *req_id, const char *url);
+   struct http_reply * (*post)(void *req_id, const char *url,
+   struct httpd_post_data *post);
+
+   struct http_reply   *error_400;
+   struct http_reply   *error_404;
+};
+
+/**
+ * httpd_setup() - configure the webserver
+ */
+void httpd_setup(struct httpd_config *config);
+
+/**
+ * httpd_stop() - start stopping of the webserver
+ */
+void httpd_stop(void);
+
+/**
+ * httpd_start() - start the webserver
+ */
+void httpd_start(void);
+
+#endif /* __NET_HTTPD_COMMON_H__ */
diff --git a/net/Kconfig b/net/Kconfig
index 7cb80b880a9..55739b9bc98 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -243,6 +243,20 @@ config PROT_TCP_SACK
  This option should be turn on if you want to achieve the fastest
  file transfer possible.
 
+config HTTPD_COMMON
+   bool "HTTP server common code"
+   depends on PROT_TCP
+   help
+ HTTP/1.1 compatible web-server common code. It supports standard
+ GET/POST requests. User MUST provide a configuration to the
+ web-server. On client request web-server will call user specified
+ GET/POST callback. Then results will be transmitted to the client.
+ The following restricions on the POST request are present at the
+ moment:
+   * only mulipart/form-data with a single binary object
+   * object will be stored to a memory area specified in
+ image_load_addr variable
+
 config IPV6
bool "IPv6 support"
help
diff --git a/net/Makefile b/net/Makefile
index dac7b4859fb..c1f491fad02 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_PROT_UDP) += udp.o
 obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
 obj-$(CONFIG_CMD_NETCAT) += netcat.o
+obj-$(CONFIG_HTTPD_COMMON) += httpd.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/httpd.c b/net/httpd.c
new file mode 100644
index 000..743c2b712b8
--- /dev/null
+++ b/net/httpd.c
@@ -0,0 +1,730 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * httpd support driver
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail 

[PATCH v7 11/13] net/netcat: add netcat over tcp support

2024-09-20 Thread Mikhail Kshevetskiy
This patch adds downloading/uploading of data with netcat.
Client/server mode both supported.

How to test:

  netcat-openbsd=1.219-1 from debian were used for a tests

  a) Load data from remote host.
   * U-Boot listen on tcp port 3456
   * PC connects

 u-boot: netcat load ${loadaddr} 3456
 PC: netcat -q1 ${UBOOT_IP} 3456 < image.itb

  b) Load data from remote host.
   * PC listen on tcp port 3456
   * U-Boot connects

 PC: netcat -q1 -l -p 3456 < image.itb
 u-boot: netcat load ${loadaddr} ${PC_IP}:3456

  c) Save data to remote host
   * U-Boot listen on tcp port 3456
   * PC connects

 u-boot: netcat save ${loadaddr} ${data_size_in_hex} 3456
 PC: netcat -w1 ${UBOOT_IP} 3456 >image.itb

  d) Save data to remote host
   * PC listen on tcp port 3456
   * U-Boot connects

 PC: netcat -w1 -l -p 3456 >image.itb
 u-boot: netcat save ${loadaddr} ${data_size_in_hex} ${PC_IP}:3456

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 cmd/Kconfig  |  10 +++
 cmd/net.c|  35 ++--
 include/net.h|   2 +-
 include/net/netcat.h |  20 +
 net/Makefile |   1 +
 net/net.c|   9 ++
 net/netcat.c | 194 +++
 7 files changed, 265 insertions(+), 6 deletions(-)
 create mode 100644 include/net/netcat.h
 create mode 100644 net/netcat.c

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 978f44eda42..cfdc53e494a 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2007,6 +2007,16 @@ config CMD_WGET
  wget is a simple command to download kernel, or other files,
  from a http server over TCP.
 
+config CMD_NETCAT
+   bool "netcat"
+   select PROT_TCP
+   help
+ netcat is a simple command to load/store kernel, or other files,
+ using well-known netcat (nc) utility. Unlike classic netcat utility
+ this command supports TCP-based data transfer only, thus no data
+ will be lost or reordered. Any netcat implementation should work,
+ but openbsd one was tested only.
+
 config CMD_MII
bool "mii"
imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 53ce2bc5d0c..79bb126dbd4 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -206,6 +206,29 @@ U_BOOT_CMD(
 );
 #endif
 
+#if defined(CONFIG_CMD_NETCAT)
+static int do_netcat_load(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+   return netboot_common(NETCAT_LOAD, cmdtp, argc, argv);
+}
+
+static int do_netcat_save(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+   return netboot_common(NETCAT_SAVE, cmdtp, argc, argv);
+}
+
+U_BOOT_LONGHELP(netcat,
+   "load [loadAddress] [[hostIPaddr:]port]\n"
+   "save Address Size [[hostIPaddr:]port]");
+
+U_BOOT_CMD_WITH_SUBCMDS(netcat,
+   "load/store data over plain TCP via netcat utility", netcat_help_text,
+   U_BOOT_SUBCMD_MKENT(load, 1, 0, do_netcat_load),
+   U_BOOT_SUBCMD_MKENT(save, 3, 0, do_netcat_save));
+#endif
+
 static void netboot_update_env(void)
 {
char tmp[46];
@@ -323,16 +346,17 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
 
switch (argc) {
case 1:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
return 1;
-
/* refresh bootfile name from env */
copy_filename(net_boot_file_name, env_get("bootfile"),
  sizeof(net_boot_file_name));
break;
 
case 2:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
return 1;
/*
 * Only one arg - accept two forms:
@@ -354,7 +378,8 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
break;
 
case 3:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) {
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE)) {
if (parse_addr_size(argv))
return 1;
} else {
@@ -365,7 +390,7 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
}
break;
 
-#ifdef CONFIG_CMD_TFTPPUT
+#if defined(CONFIG_CMD_TFTPPUT) || defined(CONFIG_CMD_NETCAT)
c

[PATCH v7 09/13] net/tcp: define a fallback value for rcv_wnd size

2024-09-20 Thread Mikhail Kshevetskiy
Some driver implements it's own network packet pool, so PKTBUFSRX is zero.
This results in zero-size TCP receive window, so data transfer doesn't
work. Avoid it by setting a reasonable fallback value.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 6 +-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/net/tcp.c b/net/tcp.c
index 647454fc064..90f2ee1f5f7 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -36,7 +36,11 @@
 #define TCP_SEND_RETRY 3
 #define TCP_SEND_TIMEOUT   2000UL
 #define TCP_RX_INACTIVE_TIMEOUT3UL
-#define TCP_RCV_WND_SIZE   (PKTBUFSRX * TCP_MSS)
+#if PKTBUFSRX != 0
+  #define TCP_RCV_WND_SIZE (PKTBUFSRX * TCP_MSS)
+#else
+  #define TCP_RCV_WND_SIZE (4 * TCP_MSS)
+#endif
 
 #define TCP_PACKET_OK  0
 #define TCP_PACKET_DROP1
-- 
2.45.2



[PATCH v7 08/13] net/tcp: simplify tcp header filling code

2024-09-20 Thread Mikhail Kshevetskiy
This patch:
 * remove useless code,
 * use a special function for pretty printing of tcp flags,
 * simplify the code

The behavior should not be changed.

Signed-off-by: Mikhail Kshevetskiy 
---
 net/tcp.c | 66 +++
 1 file changed, 33 insertions(+), 33 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index 07c57d3ea8b..647454fc064 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -537,10 +537,33 @@ void net_set_syn_options(struct tcp_stream *tcp, union 
tcp_build_pkt *b)
b->ip.end = TCP_O_END;
 }
 
+const char *tcpflags_to_str(char tcpflags, char *buf, int size)
+{
+   int i;
+   static const struct {
+   int bit;
+   const char  *name;
+   } desc[] = {{TCP_RST, "RST"}, {TCP_SYN, "SYN"}, {TCP_PUSH, "PSH"},
+   {TCP_FIN, "FIN"}, {TCP_ACK, "ACK"}};
+
+   *buf = '\0';
+   for (i = 0; i < ARRAY_SIZE(desc); i++) {
+   if (!(tcpflags & desc[i].bit))
+   continue;
+
+   if (*buf)
+   strlcat(buf, ",", size);
+   strlcat(buf, desc[i].name, size);
+   }
+
+   return buf;
+}
+
 int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num)
 {
union tcp_build_pkt *b = (union tcp_build_pkt *)pkt;
+   char buf[24];
int pkt_hdr_len;
int pkt_len;
int tcp_len;
@@ -550,55 +573,32 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar 
*pkt, int payload_len,
 * 4 bits reserved options
 */
b->ip.hdr.tcp_flags = action;
-   pkt_hdr_len = IP_TCP_HDR_SIZE;
b->ip.hdr.tcp_hlen = 
SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
 
switch (action) {
case TCP_SYN:
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:SYN (%pI4, %pI4, sq=%u, ak=%u)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num);
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
+  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
net_set_syn_options(tcp, b);
pkt_hdr_len = IP_TCP_O_SIZE;
break;
-   case TCP_SYN | TCP_ACK:
-   case TCP_ACK:
-   pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b);
-   b->ip.hdr.tcp_flags = action;
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:ACK (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
-  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num,
-  action);
-   break;
-   case TCP_FIN:
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:FIN  (%pI4, %pI4, s=%u, a=%u)\n",
-  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
-   payload_len = 0;
-   pkt_hdr_len = IP_TCP_HDR_SIZE;
-   break;
case TCP_RST | TCP_ACK:
case TCP_RST:
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:RST  (%pI4, %pI4, s=%u, a=%u)\n",
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
   &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
+   pkt_hdr_len = IP_TCP_HDR_SIZE;
break;
-   /* Notify connection closing */
-   case (TCP_FIN | TCP_ACK):
-   case (TCP_FIN | TCP_ACK | TCP_PUSH):
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%u, a=%u, 
A=%x)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num, action);
-   fallthrough;
default:
pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b);
-   b->ip.hdr.tcp_flags = action | TCP_PUSH | TCP_ACK;
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:dft  (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num, action);
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
+  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
+   break;
}
 
pkt_len = pkt_hdr_len + payload_len;
-- 
2.45.2



[PATCH v7 06/13] net/tcp: improve tcp framework, use better state machine

2024-09-20 Thread Mikhail Kshevetskiy
Changes:
 * Fix initial send sequence always zero issue
 * Use state machine close to RFC 9293. This should make TCP
   transfers more reliable (now we can upload a huge array
   of data from the board to external server)
 * Improve TCP framework a lot. This should make tcp client
   code much more simple.
 * rewrite wget with new tcp stack
 * rewrite fastboot_tcp with new tcp stack

It's quite hard to fix the initial send sequence (ISS) issue
with the separate patch. A naive attempt to fix an issue
inside the tcp_set_tcp_header() function will break tcp packet
retransmit logic in wget and other clients.

Example:
  Wget stores tcp_seq_num value before tcp_set_tcp_header() will
  be called and (on failure) retransmit the packet with the stored
  tcp_seq_num value. Thus:
* the same ISS must allways be used (current case)
* or tcp clients needs to generate a proper ISS when
  required.

A proper ISS fix will require a big redesing comparable with
a this one.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h  | 179 --
 include/net/wget.h |   8 -
 net/fastboot_tcp.c | 193 +-
 net/net.c  |   4 +
 net/tcp.c  | 853 ++---
 net/wget.c | 464 +++-
 6 files changed, 1022 insertions(+), 679 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 0694af9d5b1..0b18475645d 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -265,6 +265,7 @@ union tcp_build_pkt {
  * @TCP_CLOSING: Rec FIN, sent FIN, ACK waiting for ACK
  * @TCP_FIN_WAIT_1: Sent FIN waiting for response
  * @TCP_FIN_WAIT_2: Rec ACK from FIN sent, waiting for FIN
+ * @TCP_LAST_ACK: Waiting for ACK of the connection termination
  */
 enum tcp_state {
TCP_CLOSED,
@@ -274,7 +275,20 @@ enum tcp_state {
TCP_CLOSE_WAIT,
TCP_CLOSING,
TCP_FIN_WAIT_1,
-   TCP_FIN_WAIT_2
+   TCP_FIN_WAIT_2,
+   TCP_LAST_ACK,
+};
+
+/**
+ * enum tcp_status - TCP stream status for connection
+ * @TCP_ERR_OK: no rx/tx errors
+ * @TCP_ERR_TOUT: rx/tx timeout happened
+ * @TCP_ERR_RST: connection was reset
+ */
+enum tcp_status {
+   TCP_ERR_OK = 0,
+   TCP_ERR_TOUT,
+   TCP_ERR_RST,
 };
 
 /**
@@ -283,51 +297,150 @@ enum tcp_state {
  * @rport: Remote port, host byte order
  * @lport: Local port, host byte order
  *
+ * @priv:  User private data (not used by tcp module)
+ *
+ * @max_retry_count:   Maximum retransmit attempts (default 3)
+ * @initial_timeout:   Timeout from initial TX to reTX (default 2 sec)
+ * @rx_inactiv_timeout:Maximum time from last rx till connection drop
+ *   (default 30 sec)
+ *
+ * @on_closed: User callback, called just before destroying TCP stream
+ * @on_established:User callback, called when TCP stream enters
+ *   TCP_ESTABLISHED state
+ * @on_rcv_nxt_update: User callback, called when all data in the segment
+ *   [0..rx_bytes - 1] was received
+ * @on_snd_una_update: User callback, called when all data in the segment
+ *   [0..tx_bytes - 1] were transferred and acknowledged
+ * @rx:User callback, called on receive of segment
+ *   [rx_offs..rx_offs+len-1]. If NULL -- all incoming data
+ *   will be ignored. User SHOULD store the segment and
+ *   return the number of accepted bytes.
+ *   WARNING: Previous segmengs may not be received yet
+ * @tx:User callback, called on transmit/retransmit of 
segment
+ *   [tx_offs..tx_offs+maxlen-1]. If NULL -- no data will
+ *   be transmitted. User SHOULD fill provided buffer and
+ *   return the number of bytes in the buffer.
+ *   WARNING: do not use tcp_stream_close() from this
+ * callback (it will break stream). Better use
+ * on_snd_una_update() callback for such purposes.
+ *
+ * @time_last_rx:  Arrival time of last valid incoming package (ticks)
+ * @time_start:Timeout start time (ticks)
+ * @time_delta:Timeout duration (ticks)
+ * @time_handler   Timeout handler for a stream
+ *
  * @state: TCP connection state
+ * @status:TCP stream status (OK or ERR)
+ *
+ * @fin_rx:Non-zero if TCP_FIN was received
+ * @fin_rx_seq:TCP sequence of rx FIN bit
+ * @fin_tx:Non-zero if TCP_FIN was sent (or planned to send)
+ * @fin_tx_seq:TCP sequence of tx FIN bit
+ *
+ * @iss:   Initial send sequence number
+ * @snd_una:   Send unacknowledged
+ * @snd_nxt:   Send next
+ * @snd_wnd:   Send window (in bytes)
+ * @snd_wl1:   Segment sequence number used for

[PATCH v7 07/13] test/cmd/wget: fix the test

2024-09-20 Thread Mikhail Kshevetskiy
The wget test seriously broken:
 * Uses zero values for ISS and IRS values (see RFC 9293).
 * Writes incorrect values to Sequence and Acknowledgment numbers fields
   of tcp packets. In the real life such packets will break the tcp stream.
 * The test should fail (see above), but due to buggy old tcp implementation
   it passes.

This patch fix all bugs mentioned above, so the test passes with new
and better tcp implementation.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 arch/sandbox/include/asm/eth.h |  4 +++
 test/cmd/wget.c| 45 --
 2 files changed, 36 insertions(+), 13 deletions(-)

diff --git a/arch/sandbox/include/asm/eth.h b/arch/sandbox/include/asm/eth.h
index f042a5f3b92..083a7371a3f 100644
--- a/arch/sandbox/include/asm/eth.h
+++ b/arch/sandbox/include/asm/eth.h
@@ -77,6 +77,8 @@ typedef int sandbox_eth_tx_hand_f(struct udevice *dev, void 
*pkt,
  * fake_host_hwaddr - MAC address of mocked machine
  * fake_host_ipaddr - IP address of mocked machine
  * disabled - Will not respond
+ * irs - tcp initial receive sequence
+ * iss - tcp initial send sequence
  * recv_packet_buffer - buffers of the packet returned as received
  * recv_packet_length - lengths of the packet returned as received
  * recv_packets - number of packets returned
@@ -87,6 +89,8 @@ struct eth_sandbox_priv {
uchar fake_host_hwaddr[ARP_HLEN];
struct in_addr fake_host_ipaddr;
bool disabled;
+   u32 irs;
+   u32 iss;
uchar * recv_packet_buffer[PKTBUFSRX];
int recv_packet_length[PKTBUFSRX];
int recv_packets;
diff --git a/test/cmd/wget.c b/test/cmd/wget.c
index 356a4dcd8fa..3e9a5f354fc 100644
--- a/test/cmd/wget.c
+++ b/test/cmd/wget.c
@@ -26,6 +26,8 @@
 #define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4)
 #define LEN_B_TO_DW(x) ((x) >> 2)
 
+#define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
+
 static int sb_arp_handler(struct udevice *dev, void *packet,
  unsigned int len)
 {
@@ -62,12 +64,14 @@ static int sb_syn_handler(struct udevice *dev, void *packet,
eth_send = (void *)priv->recv_packet_buffer[priv->recv_packets];
memcpy(eth_send->et_dest, eth->et_src, ARP_HLEN);
memcpy(eth_send->et_src, priv->fake_host_hwaddr, ARP_HLEN);
+   priv->irs = ntohl(tcp->tcp_seq);
+   priv->iss = ~priv->irs; /* just to differ from irs */
eth_send->et_protlen = htons(PROT_IP);
tcp_send = (void *)eth_send + ETHER_HDR_SIZE;
tcp_send->tcp_src = tcp->tcp_dst;
tcp_send->tcp_dst = tcp->tcp_src;
-   tcp_send->tcp_seq = htonl(0);
-   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1);
+   tcp_send->tcp_seq = htonl(priv->iss);
+   tcp_send->tcp_ack = htonl(priv->irs + 1);
tcp_send->tcp_hlen = 
SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
tcp_send->tcp_flags = TCP_SYN | TCP_ACK;
tcp_send->tcp_win = htons(PKTBUFSRX * TCP_MSS >> TCP_SCALE);
@@ -102,6 +106,8 @@ static int sb_ack_handler(struct udevice *dev, void *packet,
void *data;
int pkt_len;
int payload_len = 0;
+   u32 tcp_seq, tcp_ack;
+   int tcp_data_len;
const char *payload1 = "HTTP/1.1 200 OK\r\n"
"Content-Length: 30\r\n\r\n\r\n"
"Hi\r\n";
@@ -119,17 +125,32 @@ static int sb_ack_handler(struct udevice *dev, void 
*packet,
tcp_send->tcp_dst = tcp->tcp_src;
data = (void *)tcp_send + IP_TCP_HDR_SIZE;
 
-   if (ntohl(tcp->tcp_seq) == 1 && ntohl(tcp->tcp_ack) == 1) {
-   tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack));
-   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1);
+   tcp_seq = ntohl(tcp->tcp_seq) - priv->irs;
+   tcp_ack = ntohl(tcp->tcp_ack) - priv->iss;
+   tcp_data_len = len - ETHER_HDR_SIZE - IP_HDR_SIZE - 
GET_TCP_HDR_LEN_IN_BYTES(tcp->tcp_hlen);
+
+   if (tcp->tcp_flags & TCP_FIN)
+   tcp_data_len++;
+
+   tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack));
+   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + tcp_data_len);
+
+   if (tcp_seq == 1 && tcp_ack == 1) {
+   if (tcp_data_len == 0) {
+   /* no data, wait for GET request */
+   return -1;
+   }
+
+   /* reply to GET request */
payload_len = strlen(payload1);
memcpy(data, payload1, payload_len);
tcp_send->tcp_flags = TCP_ACK;
-   } else if (ntohl(tcp->tcp_seq) == 2) {
-   tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack));
-   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1);
+   } else if (tcp_ack == 1 + strlen(payload1)) {
payload_len = 0;

[PATCH v7 05/13] net/tcp: rename ack_edge and seq_init to more common rcv_nxt and irs

2024-09-20 Thread Mikhail Kshevetskiy
Use the names from RFC 9293

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h |  8 
 net/tcp.c | 32 
 2 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index f224d0cae2f..0694af9d5b1 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -285,8 +285,8 @@ enum tcp_state {
  *
  * @state: TCP connection state
  *
- * @seq_init:  Initial receive sequence number
- * @ack_edge:  Receive next
+ * @irs:   Initial receive sequence number
+ * @rcv_nxt:   Receive next
  *
  * @loc_timestamp: Local timestamp
  * @rmt_timestamp: Remote timestamp
@@ -301,8 +301,8 @@ struct tcp_stream {
/* TCP connection state */
enum tcp_state  state;
 
-   u32 seq_init;
-   u32 ack_edge;
+   u32 irs;
+   u32 rcv_nxt;
 
/* TCP option timestamp */
u32 loc_timestamp;
diff --git a/net/tcp.c b/net/tcp.c
index 0c32c5d7c92..7e445eaffd6 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -360,9 +360,9 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, 
int payload_len,
pkt_len = pkt_hdr_len + payload_len;
tcp_len = pkt_len - IP_HDR_SIZE;
 
-   tcp->ack_edge = tcp_ack_num;
+   tcp->rcv_nxt = tcp_ack_num;
/* TCP Header */
-   b->ip.hdr.tcp_ack = htonl(tcp->ack_edge);
+   b->ip.hdr.tcp_ack = htonl(tcp->rcv_nxt);
b->ip.hdr.tcp_src = htons(tcp->lport);
b->ip.hdr.tcp_dst = htons(tcp->rport);
b->ip.hdr.tcp_seq = htonl(tcp_seq_num);
@@ -396,10 +396,10 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar 
*pkt, int payload_len,
return pkt_hdr_len;
 }
 
-static void tcp_update_ack_edge(struct tcp_stream *tcp)
+static void tcp_update_rcv_nxt(struct tcp_stream *tcp)
 {
-   if (tcp_seq_cmp(tcp->ack_edge, tcp->lost.hill[0].l) >= 0) {
-   tcp->ack_edge = tcp->lost.hill[0].r;
+   if (tcp_seq_cmp(tcp->rcv_nxt, tcp->lost.hill[0].l) >= 0) {
+   tcp->rcv_nxt = tcp->lost.hill[0].r;
 
memmove(&tcp->lost.hill[0], &tcp->lost.hill[1],
(TCP_SACK_HILLS - 1) * sizeof(struct sack_edges));
@@ -434,7 +434,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 
len)
tcp_seq_num = tcp->lost.hill[i].l;
}
if (tcp_seq_cmp(tcp->lost.hill[i].r, tcp_seq_num + len) >= 0) {
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
@@ -463,12 +463,12 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, 
u32 len)
}
}
 
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
if (i == TCP_SACK_HILLS) {
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
@@ -489,7 +489,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 
len)
tcp->lost.hill[i].r = tcp_seq_num + len;
tcp->lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8;
 
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
 };
 
 /**
@@ -566,8 +566,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
debug_cond(DEBUG_INT_STATE, "TCP CLOSED %x\n", tcp_flags);
if (tcp_syn) {
action = TCP_SYN | TCP_ACK;
-   tcp->seq_init = tcp_seq_num;
-   tcp->ack_edge = tcp_seq_num + 1;
+   tcp->irs = tcp_seq_num;
+   tcp->rcv_nxt = tcp_seq_num + 1;
tcp->lost.len = TCP_OPT_LEN_2;
tcp->state = TCP_SYN_RECEIVED;
} else if (tcp_ack || tcp_fin) {
@@ -583,8 +583,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
tcp->state = TCP_CLOSE_WAIT;
} else if (tcp_ack || (tcp_syn && tcp_ack)) {
action |= TCP_ACK;
-   tcp->seq_init = tcp_seq_num;
-   tcp->ack_edge = tcp_seq_num + 1;
+   tcp->irs = tcp_seq_num;
+   tcp->rcv_nxt = tcp_seq_num + 1;
tcp->state = TCP_ESTABLISHED;
 
if (tcp_syn && tcp_ack)
@@ -633,7 +633,7 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
case TCP_FIN_WAIT_1:
debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_1 (%x)\n", tcp_flags);
if (tcp_fin) {
-   

[PATCH v7 04/13] net/tcp: add connection info to tcp_stream structure

2024-09-20 Thread Mikhail Kshevetskiy
Changes:
 * Avoid use net_server_ip in tcp code, use tcp_stream data instead
 * Ignore packets from other connections if connection already created.
   This prevents us from connection break caused by other tcp stream.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net.h  |   5 +-
 include/net/tcp.h  |  57 +---
 net/fastboot_tcp.c |  50 +
 net/net.c  |  12 ++---
 net/tcp.c  | 131 ++---
 net/wget.c |  52 +++---
 6 files changed, 204 insertions(+), 103 deletions(-)

diff --git a/include/net.h b/include/net.h
index bb2ae20f52a..b0ce13e0a9d 100644
--- a/include/net.h
+++ b/include/net.h
@@ -667,6 +667,7 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
 /**
  * net_send_tcp_packet() - Transmit TCP packet.
  * @payload_len: length of payload
+ * @dhost: Destination host
  * @dport: Destination TCP port
  * @sport: Source TCP port
  * @action: TCP action to be performed
@@ -675,8 +676,8 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
  *
  * Return: 0 on success, other value on failure
  */
-int net_send_tcp_packet(int payload_len, int dport, int sport, u8 action,
-   u32 tcp_seq_num, u32 tcp_ack_num);
+int net_send_tcp_packet(int payload_len, struct in_addr dhost, int dport,
+   int sport, u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport,
int sport, int payload_len);
 
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 14aee64cb1c..f224d0cae2f 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -279,6 +279,9 @@ enum tcp_state {
 
 /**
  * struct tcp_stream - TCP data stream structure
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ * @lport: Local port, host byte order
  *
  * @state: TCP connection state
  *
@@ -291,6 +294,10 @@ enum tcp_state {
  * @lost:  Used for SACK
  */
 struct tcp_stream {
+   struct in_addr  rhost;
+   u16 rport;
+   u16 lport;
+
/* TCP connection state */
enum tcp_state  state;
 
@@ -305,16 +312,53 @@ struct tcp_stream {
struct tcp_sack_v lost;
 };
 
-struct tcp_stream *tcp_stream_get(void);
+void tcp_init(void);
+
+typedef int tcp_incoming_filter(struct in_addr rhost,
+   u16 rport, u16 sport);
+
+/*
+ * This function sets user callback used to accept/drop incoming
+ * connections. Callback should:
+ *  + Check TCP stream endpoint and make connection verdict
+ *- return non-zero value to accept connection
+ *- return zero to drop connection
+ *
+ * WARNING: If callback is NOT defined, all incoming connections
+ *  will be dropped.
+ */
+void tcp_set_incoming_filter(tcp_incoming_filter *filter);
+
+/*
+ * tcp_stream_get -- Get or create TCP stream
+ * @is_new:if non-zero and no stream found, then create a new one
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ * @lport: Local port, host byte order
+ *
+ * Returns: TCP stream structure or NULL (if not found/created)
+ */
+struct tcp_stream *tcp_stream_get(int is_new, struct in_addr rhost,
+ u16 rport, u16 lport);
+
+/*
+ * tcp_stream_connect -- Create new TCP stream for remote connection.
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ *
+ * Returns: TCP new stream structure or NULL (if not created).
+ *  Random local port will be used.
+ */
+struct tcp_stream *tcp_stream_connect(struct in_addr rhost, u16 rport);
+
+enum tcp_state tcp_stream_get_state(struct tcp_stream *tcp);
 
-enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp);
-void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state);
-int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport,
-  int sport, int payload_len,
+int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 
 /**
  * rxhand_tcp() - An incoming packet handler.
+ * @tcp: TCP stream
  * @pkt: pointer to the application packet
  * @dport: destination TCP port
  * @sip: source IP address
@@ -324,8 +368,7 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, 
int dport,
  * @action: TCP action (SYN, ACK, FIN, etc)
  * @len: packet length
  */
-typedef void rxhand_tcp(uchar *pkt, u16 dport,
-   struct in_addr sip, u16 sport,
+typedef void rxhand_tcp(struct tcp_stream *tcp, uchar *pkt,
u32 tcp_seq_num, u32 tcp_ack_num,
u8 action, unsigned int len);
 void tcp_set_tcp_handler(rxhand_tcp *f);
diff --git a/net

[PATCH v7 03/13] net/tcp: put connection specific data into a tcp_stream structure

2024-09-20 Thread Mikhail Kshevetskiy
no functional changes

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h |  37 +++-
 net/net.c |  11 ++-
 net/tcp.c | 231 +++---
 net/wget.c|   3 +-
 4 files changed, 163 insertions(+), 119 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index c29d4ce24a7..14aee64cb1c 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -277,9 +277,40 @@ enum tcp_state {
TCP_FIN_WAIT_2
 };
 
-enum tcp_state tcp_get_tcp_state(void);
-void tcp_set_tcp_state(enum tcp_state new_state);
-int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len,
+/**
+ * struct tcp_stream - TCP data stream structure
+ *
+ * @state: TCP connection state
+ *
+ * @seq_init:  Initial receive sequence number
+ * @ack_edge:  Receive next
+ *
+ * @loc_timestamp: Local timestamp
+ * @rmt_timestamp: Remote timestamp
+ *
+ * @lost:  Used for SACK
+ */
+struct tcp_stream {
+   /* TCP connection state */
+   enum tcp_state  state;
+
+   u32 seq_init;
+   u32 ack_edge;
+
+   /* TCP option timestamp */
+   u32 loc_timestamp;
+   u32 rmt_timestamp;
+
+   /* TCP sliding window control used to request re-TX */
+   struct tcp_sack_v lost;
+};
+
+struct tcp_stream *tcp_stream_get(void);
+
+enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp);
+void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state);
+int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport,
+  int sport, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 
 /**
diff --git a/net/net.c b/net/net.c
index d9bc9df643f..6c5ee7e0925 100644
--- a/net/net.c
+++ b/net/net.c
@@ -414,7 +414,7 @@ int net_init(void)
/* Only need to setup buffer pointers once. */
first_call = 0;
if (IS_ENABLED(CONFIG_PROT_TCP))
-   tcp_set_tcp_state(TCP_CLOSED);
+   tcp_set_tcp_state(tcp_stream_get(), TCP_CLOSED);
}
 
return net_init_loop();
@@ -915,6 +915,9 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
uchar *pkt;
int eth_hdr_size;
int pkt_hdr_size;
+#if defined(CONFIG_PROT_TCP)
+   struct tcp_stream *tcp;
+#endif
 
/* make sure the net_tx_packet is initialized (net_init() was called) */
assert(net_tx_packet != NULL);
@@ -941,8 +944,12 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
break;
 #if defined(CONFIG_PROT_TCP)
case IPPROTO_TCP:
+   tcp = tcp_stream_get();
+   if (tcp == NULL)
+   return -EINVAL;
+
pkt_hdr_size = eth_hdr_size
-   + tcp_set_tcp_header(pkt + eth_hdr_size, dport, sport,
+   + tcp_set_tcp_header(tcp, pkt + eth_hdr_size, dport, 
sport,
 payload_len, action, tcp_seq_num,
 tcp_ack_num);
break;
diff --git a/net/tcp.c b/net/tcp.c
index 724536cb352..6646f171b83 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -24,19 +24,8 @@
 #include 
 #include 
 
-/*
- * TCP sliding window  control used by us to request re-TX
- */
-static struct tcp_sack_v tcp_lost;
-
-/* TCP option timestamp */
-static u32 loc_timestamp;
-static u32 rmt_timestamp;
-
-static u32 tcp_seq_init;
-static u32 tcp_ack_edge;
-
 static int tcp_activity_count;
+static struct tcp_stream tcp_stream;
 
 /*
  * TCP lengths are stored as a rounded up number of 32 bit words.
@@ -48,9 +37,6 @@ static int tcp_activity_count;
 #define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4)
 #define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
 
-/* TCP connection state */
-static enum tcp_state current_tcp_state;
-
 /* Current TCP RX packet handler */
 static rxhand_tcp *tcp_packet_handler;
 
@@ -60,22 +46,30 @@ static inline s32 tcp_seq_cmp(u32 a, u32 b)
 }
 
 /**
- * tcp_get_tcp_state() - get current TCP state
+ * tcp_get_tcp_state() - get TCP stream state
+ * @tcp: tcp stream
  *
- * Return: Current TCP state
+ * Return: TCP stream state
  */
-enum tcp_state tcp_get_tcp_state(void)
+enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp)
 {
-   return current_tcp_state;
+   return tcp->state;
 }
 
 /**
- * tcp_set_tcp_state() - set current TCP state
+ * tcp_set_tcp_state() - set TCP stream state
+ * @tcp: tcp stream
  * @new_state: new TCP state
  */
-void tcp_set_tcp_state(enum tcp_state new_state)
+void tcp_set_tcp_state(struct tcp_stream *tcp,
+  enum tcp_state new_state)
 {
-   current_tcp_state = new_state;
+   tcp->state = new_state;
+}
+
+struct tcp_stream *tcp_stream_get(void)
+{
+   return &tcp_stream;
 }
 
 sta

[PATCH v7 02/13] net/tcp: fix selective acknowledge

2024-09-20 Thread Mikhail Kshevetskiy
Current code assume that all (except last) packets are of the same size.
This is definitely wrong. Replace SACK code with a new one, that does
not rely on this assumption. Also this code uses less memory.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 200 +++---
 1 file changed, 86 insertions(+), 114 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index 3e3118de450..724536cb352 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -38,21 +38,6 @@ static u32 tcp_ack_edge;
 
 static int tcp_activity_count;
 
-/*
- * Search for TCP_SACK and review the comments before the code section
- * TCP_SACK is the number of packets at the front of the stream
- */
-
-enum pkt_state {PKT, NOPKT};
-struct sack_r {
-   struct sack_edges se;
-   enum pkt_state st;
-};
-
-static struct sack_r edge_a[TCP_SACK];
-static unsigned int sack_idx;
-static unsigned int prev_len;
-
 /*
  * TCP lengths are stored as a rounded up number of 32 bit words.
  * Add 3 to length round up, rounded, then divided into the
@@ -69,6 +54,11 @@ static enum tcp_state current_tcp_state;
 /* Current TCP RX packet handler */
 static rxhand_tcp *tcp_packet_handler;
 
+static inline s32 tcp_seq_cmp(u32 a, u32 b)
+{
+   return (s32)(a - b);
+}
+
 /**
  * tcp_get_tcp_state() - get current TCP state
  *
@@ -267,6 +257,7 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
action = TCP_FIN;
current_tcp_state = TCP_FIN_WAIT_1;
} else {
+   tcp_lost.len = TCP_OPT_LEN_2;
current_tcp_state = TCP_SYN_SENT;
}
break;
@@ -353,6 +344,20 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
return pkt_hdr_len;
 }
 
+static void tcp_update_ack_edge(void)
+{
+   if (tcp_seq_cmp(tcp_ack_edge, tcp_lost.hill[0].l) >= 0) {
+   tcp_ack_edge = tcp_lost.hill[0].r;
+
+   memmove(&tcp_lost.hill[0], &tcp_lost.hill[1],
+   (TCP_SACK_HILLS - 1) * sizeof(struct sack_edges));
+
+   tcp_lost.len -= TCP_OPT_LEN_8;
+   tcp_lost.hill[TCP_SACK_HILLS - 1].l = TCP_O_NOP;
+   tcp_lost.hill[TCP_SACK_HILLS - 1].r = TCP_O_NOP;
+   }
+}
+
 /**
  * tcp_hole() - Selective Acknowledgment (Essential for fast stream transfer)
  * @tcp_seq_num: TCP sequence start number
@@ -360,106 +365,79 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
  */
 void tcp_hole(u32 tcp_seq_num, u32 len)
 {
-   u32 idx_sack, sack_in;
-   u32 sack_end = TCP_SACK - 1;
-   u32 hill = 0;
-   enum pkt_state expect = PKT;
-   u32 seq = tcp_seq_num - tcp_seq_init;
-   u32 hol_l = tcp_ack_edge - tcp_seq_init;
-   u32 hol_r = 0;
-
-   /* Place new seq number in correct place in receive array */
-   if (prev_len == 0)
-   prev_len = len;
-
-   idx_sack = sack_idx + ((tcp_seq_num - tcp_ack_edge) / prev_len);
-   if (idx_sack < TCP_SACK) {
-   edge_a[idx_sack].se.l = tcp_seq_num;
-   edge_a[idx_sack].se.r = tcp_seq_num + len;
-   edge_a[idx_sack].st = PKT;
+   int i, j, cnt, cnt_move;
 
-   /*
-* The fin (last) packet is not the same length as data
-* packets, and if it's length is recorded and used for
-*  array index calculation, calculation breaks.
-*/
-   if (prev_len < len)
-   prev_len = len;
-   }
+   cnt = (tcp_lost.len - TCP_OPT_LEN_2) / TCP_OPT_LEN_8;
+   for (i = 0; i < cnt; i++) {
+   if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num) < 0)
+   continue;
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num + len) > 0)
+   break;
 
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP 1 seq %d, edg %d, len %d, sack_idx %d, sack_end %d\n",
-   seq, hol_l, len, sack_idx, sack_end);
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) > 0)
+   tcp_lost.hill[i].l = tcp_seq_num;
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) < 0) {
+   len += tcp_seq_num - tcp_lost.hill[i].l;
+   tcp_seq_num = tcp_lost.hill[i].l;
+   }
+   if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num + len) >= 0) {
+   tcp_update_ack_edge();
+   return;
+   }
 
-   /* Right edge of contiguous stream, is the left edge of first hill */
-   hol_l = tcp_seq_num - tcp_seq_init;
-   hol_r = hol_l + len;
+   /* check overlapping with next hills */
+   cnt_move = 0;
+   tcp_lost.hill[i].r = tcp_seq_num

[PATCH v7 00/13] net: tcp: improve tcp support

2024-09-20 Thread Mikhail Kshevetskiy
U-Boot support of LWIP is not ready for a moment, but we already have
some kind of tcp support. Unfrotunately this support is really bad.

Some of the known issues:
 * tcp packet from other connection can break a current one
 * tcp send sequence always starts from zero
 * bad tcp options processing
 * strange assumptions on packet size for selectiv acknowledge
 * tcp interface assumes one of the two scenarios:
 - data downloading from remote host to a board
 - request-response exchange with a small packets
   so it's not possible to upload large amount of data from the
   board to remote host.
 * wget test generate bad tcp stream, test should fail but it passes instead

This series of patches fixes all of the above issuess.

The benefits:
 * A lot of bug was fixed
 * Better and more reliable TCP state machine
 * Tcp cliens becomes smaller/simpler
 * Data uploading was fixed (now it's possible to transmit a huge amount of
   data from the board to remote host)
 * Netcat over tcp was implemented. Netcat supports data downloading/uploading
   from/to remote host in client/server mode.
 * An example web-server implementation. This code can be used as a base
   for web-based firmware uploading used by some vendors.

Modification was verified with
 * firmware downloading via u-boot wget command
 * fastboot over tcp
 * netcat linux client
 * Firefox/Chrome/Edge using example web-server implementation

Changes v2:
 * cover letter was added
 * some patches were removed

Changes v3:
 * better cover letter

Changes v4:
 * fix bug in debug output
 * add more comments
 * codestyle fixes

Changes v5:
 * old patches were ocasionally sent with v4
 * add back web-server patches
 * fix bug in debug output
 * add more comments
 * codestyle fixes

Changes v6:
 * fix the wget test
 * improve description of "simplify tcp header filling code" patch

Changes v7:
 * fix include ordering
 * improve option descriptions
 * fix a lot of extra brackets and comparisons against NULL / 0
 * add empty lines before final returns
 * fixed a bug with zero size httpd uploads

Mikhail Kshevetskiy (13):
  net/tcp: fix TCP options processing
  net/tcp: fix selective acknowledge
  net/tcp: put connection specific data into a tcp_stream structure
  net/tcp: add connection info to tcp_stream structure
  net/tcp: rename ack_edge and seq_init to more common rcv_nxt and irs
  net/tcp: improve tcp framework, use better state machine
  test/cmd/wget: fix the test
  net/tcp: simplify tcp header filling code
  net/tcp: define a fallback value for rcv_wnd size
  net/net: fix include ordering
  net/netcat: add netcat over tcp support
  net/httpd: add httpd common code
  net/httpd-upload: an example web-server implementation for file
uploading

 arch/sandbox/include/asm/eth.h |4 +
 cmd/Kconfig|   35 +
 cmd/net.c  |   55 +-
 include/net.h  |7 +-
 include/net/httpd-upload.h |   12 +
 include/net/httpd.h|   64 ++
 include/net/netcat.h   |   20 +
 include/net/tcp.h  |  235 +-
 include/net/wget.h |8 -
 net/Kconfig|   14 +
 net/Makefile   |3 +
 net/fastboot_tcp.c |  193 +++--
 net/httpd-upload.c |  170 +
 net/httpd.c|  730 ++
 net/net.c  |   60 +-
 net/netcat.c   |  194 +
 net/tcp.c  | 1259 ++--
 net/wget.c |  481 
 test/cmd/wget.c|   45 +-
 19 files changed, 2691 insertions(+), 898 deletions(-)
 create mode 100644 include/net/httpd-upload.h
 create mode 100644 include/net/httpd.h
 create mode 100644 include/net/netcat.h
 create mode 100644 net/httpd-upload.c
 create mode 100644 net/httpd.c
 create mode 100644 net/netcat.c

-- 
2.45.2



[PATCH v7 01/13] net/tcp: fix TCP options processing

2024-09-20 Thread Mikhail Kshevetskiy
Current TCP code may miss an option if TCP_O_NOP option was used before
it for proper aligning.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 6 --
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index b0cc8a1fe3e..3e3118de450 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -475,7 +475,7 @@ void tcp_parse_options(uchar *o, int o_len)
 * NOPs are options with a zero length, and thus are special.
 * All other options have length fields.
 */
-   for (p = o; p < (o + o_len); p = p + p[1]) {
+   for (p = o; p < (o + o_len); ) {
if (!p[1])
return; /* Finished processing options */
 
@@ -490,12 +490,14 @@ void tcp_parse_options(uchar *o, int o_len)
case TCP_O_TS:
tsopt = (struct tcp_t_opt *)p;
rmt_timestamp = tsopt->t_snd;
-   return;
+   break;
}
 
/* Process optional NOPs */
if (p[0] == TCP_O_NOP)
p++;
+   else
+   p += p[1];
}
 }
 
-- 
2.45.2



Re: [PATCH v6 10/12] net/netcat: add netcat over tcp support

2024-09-15 Thread Mikhail Kshevetskiy


On 12.09.2024 03:59, Simon Glass wrote:
> Hi Mikhail,
>
> On Mon, 9 Sept 2024 at 16:27, Mikhail Kshevetskiy
>  wrote:
>> This patch adds downloading/uploading of data with netcat.
>> Client/server mode both supported.
>>
>> How to test:
>> 
>>   netcat-openbsd=1.219-1 from debian were used for a tests
>>
>>   a) Load data from remote host.
>>* U-Boot listen on tcp port 3456
>>* PC connects
>>
>>  u-boot: netcat load ${loadaddr} 3456
>>  PC: netcat -q1 ${UBOOT_IP} 3456 < image.itb
>>
>>   b) Load data from remote host.
>>* PC listen on tcp port 3456
>>* U-Boot connects
>>
>>  PC: netcat -q1 -l -p 3456 < image.itb
>>  u-boot: netcat load ${loadaddr} ${PC_IP}:3456
>>
>>   c) Save data to remote host
>>* U-Boot listen on tcp port 3456
>>* PC connects
>>
>>  u-boot: netcat save ${loadaddr} ${data_size_in_hex} 3456
>>  PC: netcat -w1 ${UBOOT_IP} 3456 >image.itb
>>
>>   d) Save data to remote host
>>* PC listen on tcp port 3456
>>* U-Boot connects
>>
>>  PC: netcat -w1 -l -p 3456 >image.itb
>>  u-boot: netcat save ${loadaddr} ${data_size_in_hex} ${PC_IP}:3456
>>
>> Signed-off-by: Mikhail Kshevetskiy 
>> ---
>>  cmd/Kconfig  |   7 ++
>>  cmd/net.c|  34 ++--
>>  include/net.h|   2 +-
>>  include/net/netcat.h |  20 +
>>  net/Makefile |   1 +
>>  net/net.c|   9 +++
>>  net/netcat.c | 181 +++
>>  7 files changed, 248 insertions(+), 6 deletions(-)
>>  create mode 100644 include/net/netcat.h
>>  create mode 100644 net/netcat.c
> Reviewed-by: Simon Glass 

Can/should I add this "Reviewed-by" to the next patch version?

>
> nits below
>
>> diff --git a/cmd/Kconfig b/cmd/Kconfig
>> index 978f44eda42..abcd003f7f1 100644
>> --- a/cmd/Kconfig
>> +++ b/cmd/Kconfig
>> @@ -2007,6 +2007,13 @@ config CMD_WGET
>>   wget is a simple command to download kernel, or other files,
>>   from a http server over TCP.
>>
>> +config CMD_NETCAT
>> +   bool "netcat"
>> +   select PROT_TCP
>> +   help
>> + netcat is a simple command to load/store kernel, or other files,
>> + using netcat like manner over TCP.
> netcat-like
>
> Please add a reference to the protocol or the 'netcat' tool or both.
> Help must be at least 3 lines.

Netcat (https://en.wikipedia.org/wiki/Netcat)  uses plain TCP/UDP for
data delivery. In the TCP case, TCP by itself provides reliable,
ordered, and error-checked data delivery, so no additional protocols are
required.

Is it enough to write the following description?

netcat is a simple command to load/store kernel, or other files,
using well-known netcat (nc) utility. Unlike classic netcat utility
this command supports TCP-based data transfer only, thus no data
will be lost or reordered. Any netcat implementation should work,
but openbsd one was tested only.


>> +
>>  config CMD_MII
>> bool "mii"
>> imply CMD_MDIO
>> diff --git a/cmd/net.c b/cmd/net.c
>> index 53ce2bc5d0c..364139ec5b9 100644
>> --- a/cmd/net.c
>> +++ b/cmd/net.c
>> @@ -206,6 +206,28 @@ U_BOOT_CMD(
>>  );
>>  #endif
>>
>> +#if defined(CONFIG_CMD_NETCAT)
>> +static int do_netcat(struct cmd_tbl *cmdtp, int flag, int argc, char *const 
>> argv[])
>> +{
>> +   if (argc < 2)
>> +   return 1;
>> +
>> +   if (!strcmp(argv[1], "load"))
>> +   return netboot_common(NETCAT_LOAD, cmdtp, argc - 1, argv + 
>> 1);
>> +   else if (!strcmp(argv[1], "save"))
>> +   return netboot_common(NETCAT_SAVE, cmdtp, argc - 1, argv + 
>> 1);
>> +   else
>> +   return 1;
>> +}
>> +
> Needs longhelp
Could you explain? Usage examples?
>
>> +U_BOOT_CMD(
>> +   netcat,   5,  1,  do_netcat,
>> +   "load/store data via tcp netcat utility",
>> +   "load [loadAddress] [[hostIPaddr:]port]\n"
>> +   "save Address Size [[hostIPaddr:]port]\n"
>> +);
>> +#endif
>> +
>>  static void netboot_update_env(void)
>>  {
>> char tmp[46];
>> @@ -323,16 +345,17 @@ static int parse_args(enum proto_t proto, int argc, 
>> char *const argv[])
>>
>> switc

[PATCH v6 12/12] net/httpd-upload: an example web-server implementation for file uploading

2024-09-09 Thread Mikhail Kshevetskiy
This is an example web-server implementation. It can be used for files
uploading to u-boot using a web-browser. It acts much like tftpget, but no
special servers needs to be installed by the user.

This code can be used as a base for other implementations like firmware
upgrade web-server used by some vendors.

Usage:
  u-boot: start the we-server using the "httpd_upload" command
  PC: open the "http://your_uboot_ip"; link in the browser

Signed-off-by: Mikhail Kshevetskiy 
---
 cmd/Kconfig |  14 
 cmd/net.c   |  20 ++
 include/net/httpd-upload.h  |  12 
 net/Makefile|  19 +
 net/httpd-upload.c  | 123 
 net/httpd_upload/error_400.html |   9 +++
 net/httpd_upload/error_404.html |  10 +++
 net/httpd_upload/index.html |  14 
 net/httpd_upload/upload_ok.html |   7 ++
 9 files changed, 228 insertions(+)
 create mode 100644 include/net/httpd-upload.h
 create mode 100644 net/httpd-upload.c
 create mode 100644 net/httpd_upload/error_400.html
 create mode 100644 net/httpd_upload/error_404.html
 create mode 100644 net/httpd_upload/index.html
 create mode 100644 net/httpd_upload/upload_ok.html

diff --git a/cmd/Kconfig b/cmd/Kconfig
index abcd003f7f1..55b9d04f2fa 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2014,6 +2014,20 @@ config CMD_NETCAT
  netcat is a simple command to load/store kernel, or other files,
  using netcat like manner over TCP.
 
+config CMD_HTTPD_UPLOAD
+   bool "an example HTTP server for file uploading"
+   depends on HTTPD_COMMON
+   help
+ HTTP/1.1 compatible server for file uploading.
+
+config CMD_HTTPD_UPLOAD_MAX_SIZE
+   int "Maximum uploading size"
+   depends on CMD_HTTPD_UPLOAD
+   default 209715200
+   help
+ This sets maximum size of uploaded file. Real transfer will be
+ slightly more than this limit.
+
 config CMD_MII
bool "mii"
imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 364139ec5b9..e5fddc8c7c5 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -21,6 +21,9 @@
 #include 
 #include 
 #include 
+#if defined(CONFIG_CMD_HTTPD_UPLOAD)
+#include 
+#endif
 
 static int netboot_common(enum proto_t, struct cmd_tbl *, int, char * const 
[]);
 
@@ -228,6 +231,23 @@ U_BOOT_CMD(
 );
 #endif
 
+#if defined(CONFIG_CMD_HTTPD_UPLOAD)
+static int do_httpd_upload(struct cmd_tbl *cmdtp, int flag, int argc, char 
*const argv[])
+{
+   if (argc < 2)
+   return 1;
+
+   httpd_upload_prepare();
+   return netboot_common(HTTPD, cmdtp, argc, argv);
+}
+
+U_BOOT_CMD(
+   httpd_upload,   2,  1,  do_httpd_upload,
+   "starts httpd server for file uploading",
+   "[loadAddress]\n"
+);
+#endif
+
 static void netboot_update_env(void)
 {
char tmp[46];
diff --git a/include/net/httpd-upload.h b/include/net/httpd-upload.h
new file mode 100644
index 000..a80df214668
--- /dev/null
+++ b/include/net/httpd-upload.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ *
+ * httpd-upload include file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ */
+#ifndef __NET_HTTPD_UPLOAD_TCP_H__
+#define __NET_HTTPD_UPLOAD_TCP_H__
+
+void httpd_upload_prepare(void);
+
+#endif /* __NET_HTTPD_UPLOAD_TCP_H__ */
diff --git a/net/Makefile b/net/Makefile
index c1f491fad02..e7cbbc2248e 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -35,8 +35,27 @@ obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
 obj-$(CONFIG_CMD_NETCAT) += netcat.o
 obj-$(CONFIG_HTTPD_COMMON) += httpd.o
+obj-$(CONFIG_CMD_HTTPD_UPLOAD) += httpd-upload.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
 # and this is intentional usage.
 CFLAGS_eth_common.o += -Wno-format-extra-args
+
+STATIC_SUBST := 's/^\(unsigned char \)/static \1/'
+SIZE_REMOVE_SUBST := 's/^unsigned int .*//'
+
+httpd_upload_generated:
+   rm -rf $(src)/httpd_upload_generated
+   mkdir -p $(src)/httpd_upload_generated
+   cd $(src)/httpd_upload && find . -type f | while read fname; do \
+   name="$${fname##*/}" && \
+   name="$${name%%.*}"  && \
+   set -o pipefail && xxd -i "$${fname##./}" | \
+   sed $(STATIC_SUBST) | \
+   sed $(SIZE_REMOVE_SUBST) > 
../httpd_upload_generated/$${name}.h; \
+   done
+
+.PHONY: httpd_upload_generated
+
+net/httpd-upload.o:httpd_upload_generated
diff --git a/net/httpd-upload.c b/net/httpd-upload.c
new file mode 100644
index 000..1e1e0b1cf75
--- /dev/null
+++ b/net/httpd-upload.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * httpd-upload support driver
+ * Copyright (C) 2024 IOPSYS S

[PATCH v6 11/12] net/httpd: add httpd common code

2024-09-09 Thread Mikhail Kshevetskiy
This patch adds HTTP/1.1 compatible web-server that can be used
by other. Server supports GET, POST, and HEAD requests. On client
request it will call user specified GET/POST callback. Then results
will be transmitted to client.

The following restrictions exist on the POST request
at the moment:
  * only multipart/form-data with a single file object
  * object will be stored to a memory area specified in
image_load_addr variable

Signed-off-by: Mikhail Kshevetskiy 
---
 include/net.h   |   2 +-
 include/net/httpd.h |  64 
 net/Kconfig |  14 +
 net/Makefile|   1 +
 net/httpd.c | 692 
 net/net.c   |   6 +
 6 files changed, 778 insertions(+), 1 deletion(-)
 create mode 100644 include/net/httpd.h
 create mode 100644 net/httpd.c

diff --git a/include/net.h b/include/net.h
index 0af6493788a..154885a2b7e 100644
--- a/include/net.h
+++ b/include/net.h
@@ -515,7 +515,7 @@ extern int  net_restart_wrap;   /* Tried all 
network devices */
 enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
-   WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, RS
+   WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, HTTPD, RS
 };
 
 extern charnet_boot_file_name[1024];/* Boot File name */
diff --git a/include/net/httpd.h b/include/net/httpd.h
new file mode 100644
index 000..ff0dc93ecf5
--- /dev/null
+++ b/include/net/httpd.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * httpd support header file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ *
+ */
+#ifndef __NET_HTTPD_COMMON_H__
+#define __NET_HTTPD_COMMON_H__
+
+struct http_reply {
+   int code;
+   const char  *code_msg;
+   const char  *data_type;
+   void*data;
+   u32 len;
+};
+
+struct httpd_post_data {
+   const char  *name;
+   const char  *filename;
+   void*addr;
+   u32 size;
+};
+
+enum httpd_req_check {
+   HTTPD_REQ_OK,
+   HTTPD_BAD_URL,
+   HTTPD_BAD_REQ,
+   HTTPD_CLNT_RST
+};
+
+struct httpd_config {
+   enum net_loop_state (*on_stop)(void);
+   void(*on_req_end)(void *req_id);
+
+   enum httpd_req_check(*pre_get)(void *req_id, const char *url);
+   enum httpd_req_check(*pre_post)(void *req_id, const char *url,
+   struct httpd_post_data *post);
+
+   struct http_reply * (*get)(void *req_id, const char *url);
+   struct http_reply * (*post)(void *req_id, const char *url,
+   struct httpd_post_data *post);
+
+   struct http_reply   *error_400;
+   struct http_reply   *error_404;
+};
+
+/**
+ * httpd_setup() - configure the webserver
+ */
+void httpd_setup(struct httpd_config *config);
+
+/**
+ * httpd_stop() - start stopping of the webserver
+ */
+void httpd_stop(void);
+
+/**
+ * httpd_start() - start the webserver
+ */
+void httpd_start(void);
+
+#endif /* __NET_HTTPD_COMMON_H__ */
diff --git a/net/Kconfig b/net/Kconfig
index 7cb80b880a9..55739b9bc98 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -243,6 +243,20 @@ config PROT_TCP_SACK
  This option should be turn on if you want to achieve the fastest
  file transfer possible.
 
+config HTTPD_COMMON
+   bool "HTTP server common code"
+   depends on PROT_TCP
+   help
+ HTTP/1.1 compatible web-server common code. It supports standard
+ GET/POST requests. User MUST provide a configuration to the
+ web-server. On client request web-server will call user specified
+ GET/POST callback. Then results will be transmitted to the client.
+ The following restricions on the POST request are present at the
+ moment:
+   * only mulipart/form-data with a single binary object
+   * object will be stored to a memory area specified in
+ image_load_addr variable
+
 config IPV6
bool "IPv6 support"
help
diff --git a/net/Makefile b/net/Makefile
index dac7b4859fb..c1f491fad02 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_PROT_UDP) += udp.o
 obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
 obj-$(CONFIG_CMD_NETCAT) += netcat.o
+obj-$(CONFIG_HTTPD_COMMON) += httpd.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/httpd.c b/net/httpd.c
new file mode 100644
index 000..052fae9ced0
--- /dev/null
+++ b/net/httpd.c
@@ -0,0 +1,692 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * httpd support driver
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ */
+
+#include 
+#

[PATCH v6 10/12] net/netcat: add netcat over tcp support

2024-09-09 Thread Mikhail Kshevetskiy
This patch adds downloading/uploading of data with netcat.
Client/server mode both supported.

How to test:

  netcat-openbsd=1.219-1 from debian were used for a tests

  a) Load data from remote host.
   * U-Boot listen on tcp port 3456
   * PC connects

 u-boot: netcat load ${loadaddr} 3456
 PC: netcat -q1 ${UBOOT_IP} 3456 < image.itb

  b) Load data from remote host.
   * PC listen on tcp port 3456
   * U-Boot connects

 PC: netcat -q1 -l -p 3456 < image.itb
 u-boot: netcat load ${loadaddr} ${PC_IP}:3456

  c) Save data to remote host
   * U-Boot listen on tcp port 3456
   * PC connects

 u-boot: netcat save ${loadaddr} ${data_size_in_hex} 3456
 PC: netcat -w1 ${UBOOT_IP} 3456 >image.itb

  d) Save data to remote host
   * PC listen on tcp port 3456
   * U-Boot connects

 PC: netcat -w1 -l -p 3456 >image.itb
 u-boot: netcat save ${loadaddr} ${data_size_in_hex} ${PC_IP}:3456

Signed-off-by: Mikhail Kshevetskiy 
---
 cmd/Kconfig  |   7 ++
 cmd/net.c|  34 ++--
 include/net.h|   2 +-
 include/net/netcat.h |  20 +
 net/Makefile |   1 +
 net/net.c|   9 +++
 net/netcat.c | 181 +++
 7 files changed, 248 insertions(+), 6 deletions(-)
 create mode 100644 include/net/netcat.h
 create mode 100644 net/netcat.c

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 978f44eda42..abcd003f7f1 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2007,6 +2007,13 @@ config CMD_WGET
  wget is a simple command to download kernel, or other files,
  from a http server over TCP.
 
+config CMD_NETCAT
+   bool "netcat"
+   select PROT_TCP
+   help
+ netcat is a simple command to load/store kernel, or other files,
+ using netcat like manner over TCP.
+
 config CMD_MII
bool "mii"
imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 53ce2bc5d0c..364139ec5b9 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -206,6 +206,28 @@ U_BOOT_CMD(
 );
 #endif
 
+#if defined(CONFIG_CMD_NETCAT)
+static int do_netcat(struct cmd_tbl *cmdtp, int flag, int argc, char *const 
argv[])
+{
+   if (argc < 2)
+   return 1;
+
+   if (!strcmp(argv[1], "load"))
+   return netboot_common(NETCAT_LOAD, cmdtp, argc - 1, argv + 1);
+   else if (!strcmp(argv[1], "save"))
+   return netboot_common(NETCAT_SAVE, cmdtp, argc - 1, argv + 1);
+   else
+   return 1;
+}
+
+U_BOOT_CMD(
+   netcat,   5,  1,  do_netcat,
+   "load/store data via tcp netcat utility",
+   "load [loadAddress] [[hostIPaddr:]port]\n"
+   "save Address Size [[hostIPaddr:]port]\n"
+);
+#endif
+
 static void netboot_update_env(void)
 {
char tmp[46];
@@ -323,16 +345,17 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
 
switch (argc) {
case 1:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
return 1;
-
/* refresh bootfile name from env */
copy_filename(net_boot_file_name, env_get("bootfile"),
  sizeof(net_boot_file_name));
break;
 
case 2:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
return 1;
/*
 * Only one arg - accept two forms:
@@ -354,7 +377,8 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
break;
 
case 3:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) {
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE)) {
if (parse_addr_size(argv))
return 1;
} else {
@@ -365,7 +389,7 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
}
break;
 
-#ifdef CONFIG_CMD_TFTPPUT
+#if defined(CONFIG_CMD_TFTPPUT) || defined(CONFIG_CMD_NETCAT)
case 4:
if (parse_addr_size(argv))
return 1;
diff --git a/include/net.h b/include/net.h
index b0ce13e0a9d..0af6493788a 100644
--- a/include/net.h
+++ b/include/net.h
@@ -515,7 +515,7 @@ extern int  net_restart_wrap;   /* Tried all 
network devices */
 enum proto_t {
BOO

[PATCH v6 09/12] net/tcp: define a fallback value for rcv_wnd size

2024-09-09 Thread Mikhail Kshevetskiy
Some driver implements it's own network packet pool, so PKTBUFSRX is zero.
This results in zero-size TCP receive window, so data transfer doesn't
work. Avoid it by setting a reasonable fallback value.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 6 +-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/net/tcp.c b/net/tcp.c
index 0778c153aca..32ee1fe03ce 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -36,7 +36,11 @@
 #define TCP_SEND_RETRY 3
 #define TCP_SEND_TIMEOUT   2000UL
 #define TCP_RX_INACTIVE_TIMEOUT3UL
-#define TCP_RCV_WND_SIZE   (PKTBUFSRX * TCP_MSS)
+#if PKTBUFSRX != 0
+  #define TCP_RCV_WND_SIZE (PKTBUFSRX * TCP_MSS)
+#else
+  #define TCP_RCV_WND_SIZE (4 * TCP_MSS)
+#endif
 
 #define TCP_PACKET_OK  0
 #define TCP_PACKET_DROP1
-- 
2.45.2



[PATCH v6 08/12] net/tcp: simplify tcp header filling code

2024-09-09 Thread Mikhail Kshevetskiy
This patch:
 * remove useless code,
 * use a special function for pretty printing of tcp flags,
 * simplify the code

The behavior should not be changed.

Signed-off-by: Mikhail Kshevetskiy 
---
 net/tcp.c | 74 ++-
 1 file changed, 41 insertions(+), 33 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index 37dda6f84f9..0778c153aca 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -535,10 +535,41 @@ void net_set_syn_options(struct tcp_stream *tcp, union 
tcp_build_pkt *b)
b->ip.end = TCP_O_END;
 }
 
+const char *tcpflags_to_str(char tcpflags, char *buf, int size)
+{
+   int i, len;
+   char *orig = buf;
+   const struct {
+   int bit;
+   const char  *name;
+   } desc[] = {{TCP_RST, "RST"}, {TCP_SYN, "SYN"}, {TCP_PUSH, "PSH"},
+   {TCP_FIN, "FIN"}, {TCP_ACK, "ACK"}, {0, NULL}};
+
+   *orig = '\0';
+   for (i = 0; desc[i].name; i++) {
+   if (!(tcpflags & desc[i].bit))
+   continue;
+
+   len = strlen(desc[i].name);
+   if (size <= len + 1)
+   break;
+   if (buf != orig) {
+   *buf++ = ',';
+   size--;
+   }
+
+   strcpy(buf, desc[i].name);
+   buf += len;
+   size -= len;
+   }
+   return orig;
+}
+
 int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num)
 {
union tcp_build_pkt *b = (union tcp_build_pkt *)pkt;
+   char buf[24];
int pkt_hdr_len;
int pkt_len;
int tcp_len;
@@ -548,55 +579,32 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar 
*pkt, int payload_len,
 * 4 bits reserved options
 */
b->ip.hdr.tcp_flags = action;
-   pkt_hdr_len = IP_TCP_HDR_SIZE;
b->ip.hdr.tcp_hlen = 
SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
 
switch (action) {
case TCP_SYN:
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:SYN (%pI4, %pI4, sq=%u, ak=%u)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num);
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
+  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
net_set_syn_options(tcp, b);
pkt_hdr_len = IP_TCP_O_SIZE;
break;
-   case TCP_SYN | TCP_ACK:
-   case TCP_ACK:
-   pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b);
-   b->ip.hdr.tcp_flags = action;
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:ACK (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
-  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num,
-  action);
-   break;
-   case TCP_FIN:
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:FIN  (%pI4, %pI4, s=%u, a=%u)\n",
-  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
-   payload_len = 0;
-   pkt_hdr_len = IP_TCP_HDR_SIZE;
-   break;
case TCP_RST | TCP_ACK:
case TCP_RST:
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:RST  (%pI4, %pI4, s=%u, a=%u)\n",
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
   &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
+   pkt_hdr_len = IP_TCP_HDR_SIZE;
break;
-   /* Notify connection closing */
-   case (TCP_FIN | TCP_ACK):
-   case (TCP_FIN | TCP_ACK | TCP_PUSH):
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%u, a=%u, 
A=%x)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num, action);
-   fallthrough;
default:
pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b);
-   b->ip.hdr.tcp_flags = action | TCP_PUSH | TCP_ACK;
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:dft  (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num, action);
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
+  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
+   break;
}
 
pkt_len = pkt_hdr_len + payload_len;
-- 
2.45.2



[PATCH v6 06/12] net/tcp: improve tcp framework, use better state machine

2024-09-09 Thread Mikhail Kshevetskiy
Changes:
 * Fix initial send sequence always zero issue
 * Use state machine close to RFC 9293. This should make TCP
   transfers more reliable (now we can upload a huge array
   of data from the board to external server)
 * Improve TCP framework a lot. This should make tcp client
   code much more simple.
 * rewrite wget with new tcp stack
 * rewrite fastboot_tcp with new tcp stack

It's quite hard to fix the initial send sequence (ISS) issue
with the separate patch. A naive attempt to fix an issue
inside the tcp_set_tcp_header() function will break tcp packet
retransmit logic in wget and other clients.

Example:
  Wget stores tcp_seq_num value before tcp_set_tcp_header() will
  be called and (on failure) retransmit the packet with the stored
  tcp_seq_num value. Thus:
* the same ISS must allways be used (current case)
* or tcp clients needs to generate a proper ISS when
  required.

A proper ISS fix will require a big redesing comparable with
a this one.

Signed-off-by: Mikhail Kshevetskiy 
---
 include/net/tcp.h  | 179 --
 include/net/wget.h |   8 -
 net/fastboot_tcp.c | 190 +-
 net/net.c  |   4 +
 net/tcp.c  | 845 ++---
 net/wget.c | 460 +++-
 6 files changed, 1008 insertions(+), 678 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 0694af9d5b1..0b18475645d 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -265,6 +265,7 @@ union tcp_build_pkt {
  * @TCP_CLOSING: Rec FIN, sent FIN, ACK waiting for ACK
  * @TCP_FIN_WAIT_1: Sent FIN waiting for response
  * @TCP_FIN_WAIT_2: Rec ACK from FIN sent, waiting for FIN
+ * @TCP_LAST_ACK: Waiting for ACK of the connection termination
  */
 enum tcp_state {
TCP_CLOSED,
@@ -274,7 +275,20 @@ enum tcp_state {
TCP_CLOSE_WAIT,
TCP_CLOSING,
TCP_FIN_WAIT_1,
-   TCP_FIN_WAIT_2
+   TCP_FIN_WAIT_2,
+   TCP_LAST_ACK,
+};
+
+/**
+ * enum tcp_status - TCP stream status for connection
+ * @TCP_ERR_OK: no rx/tx errors
+ * @TCP_ERR_TOUT: rx/tx timeout happened
+ * @TCP_ERR_RST: connection was reset
+ */
+enum tcp_status {
+   TCP_ERR_OK = 0,
+   TCP_ERR_TOUT,
+   TCP_ERR_RST,
 };
 
 /**
@@ -283,51 +297,150 @@ enum tcp_state {
  * @rport: Remote port, host byte order
  * @lport: Local port, host byte order
  *
+ * @priv:  User private data (not used by tcp module)
+ *
+ * @max_retry_count:   Maximum retransmit attempts (default 3)
+ * @initial_timeout:   Timeout from initial TX to reTX (default 2 sec)
+ * @rx_inactiv_timeout:Maximum time from last rx till connection drop
+ *   (default 30 sec)
+ *
+ * @on_closed: User callback, called just before destroying TCP stream
+ * @on_established:User callback, called when TCP stream enters
+ *   TCP_ESTABLISHED state
+ * @on_rcv_nxt_update: User callback, called when all data in the segment
+ *   [0..rx_bytes - 1] was received
+ * @on_snd_una_update: User callback, called when all data in the segment
+ *   [0..tx_bytes - 1] were transferred and acknowledged
+ * @rx:User callback, called on receive of segment
+ *   [rx_offs..rx_offs+len-1]. If NULL -- all incoming data
+ *   will be ignored. User SHOULD store the segment and
+ *   return the number of accepted bytes.
+ *   WARNING: Previous segmengs may not be received yet
+ * @tx:User callback, called on transmit/retransmit of 
segment
+ *   [tx_offs..tx_offs+maxlen-1]. If NULL -- no data will
+ *   be transmitted. User SHOULD fill provided buffer and
+ *   return the number of bytes in the buffer.
+ *   WARNING: do not use tcp_stream_close() from this
+ * callback (it will break stream). Better use
+ * on_snd_una_update() callback for such purposes.
+ *
+ * @time_last_rx:  Arrival time of last valid incoming package (ticks)
+ * @time_start:Timeout start time (ticks)
+ * @time_delta:Timeout duration (ticks)
+ * @time_handler   Timeout handler for a stream
+ *
  * @state: TCP connection state
+ * @status:TCP stream status (OK or ERR)
+ *
+ * @fin_rx:Non-zero if TCP_FIN was received
+ * @fin_rx_seq:TCP sequence of rx FIN bit
+ * @fin_tx:Non-zero if TCP_FIN was sent (or planned to send)
+ * @fin_tx_seq:TCP sequence of tx FIN bit
+ *
+ * @iss:   Initial send sequence number
+ * @snd_una:   Send unacknowledged
+ * @snd_nxt:   Send next
+ * @snd_wnd:   Send window (in bytes)
+ * @snd_wl1:   Segment sequence number used for last window update
+ * @sn

[PATCH v6 07/12] test/cmd/wget: fix the test

2024-09-09 Thread Mikhail Kshevetskiy
The wget test seriously broken:
 * Uses zero values for ISS and IRS values (see RFC 9293).
 * Writes incorrect values to Sequence and Acknowledgment numbers fields
   of tcp packets. In the real life such packets will break the tcp stream.
 * The test should fail (see above), but due to buggy old tcp implementation
   it passes.

This patch fix all bugs mentioned above, so the test passes with new
and better tcp implementation.

Signed-off-by: Mikhail Kshevetskiy 
---
 arch/sandbox/include/asm/eth.h |  4 +++
 test/cmd/wget.c| 45 --
 2 files changed, 36 insertions(+), 13 deletions(-)

diff --git a/arch/sandbox/include/asm/eth.h b/arch/sandbox/include/asm/eth.h
index f042a5f3b92..083a7371a3f 100644
--- a/arch/sandbox/include/asm/eth.h
+++ b/arch/sandbox/include/asm/eth.h
@@ -77,6 +77,8 @@ typedef int sandbox_eth_tx_hand_f(struct udevice *dev, void 
*pkt,
  * fake_host_hwaddr - MAC address of mocked machine
  * fake_host_ipaddr - IP address of mocked machine
  * disabled - Will not respond
+ * irs - tcp initial receive sequence
+ * iss - tcp initial send sequence
  * recv_packet_buffer - buffers of the packet returned as received
  * recv_packet_length - lengths of the packet returned as received
  * recv_packets - number of packets returned
@@ -87,6 +89,8 @@ struct eth_sandbox_priv {
uchar fake_host_hwaddr[ARP_HLEN];
struct in_addr fake_host_ipaddr;
bool disabled;
+   u32 irs;
+   u32 iss;
uchar * recv_packet_buffer[PKTBUFSRX];
int recv_packet_length[PKTBUFSRX];
int recv_packets;
diff --git a/test/cmd/wget.c b/test/cmd/wget.c
index 356a4dcd8fa..3e9a5f354fc 100644
--- a/test/cmd/wget.c
+++ b/test/cmd/wget.c
@@ -26,6 +26,8 @@
 #define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4)
 #define LEN_B_TO_DW(x) ((x) >> 2)
 
+#define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
+
 static int sb_arp_handler(struct udevice *dev, void *packet,
  unsigned int len)
 {
@@ -62,12 +64,14 @@ static int sb_syn_handler(struct udevice *dev, void *packet,
eth_send = (void *)priv->recv_packet_buffer[priv->recv_packets];
memcpy(eth_send->et_dest, eth->et_src, ARP_HLEN);
memcpy(eth_send->et_src, priv->fake_host_hwaddr, ARP_HLEN);
+   priv->irs = ntohl(tcp->tcp_seq);
+   priv->iss = ~priv->irs; /* just to differ from irs */
eth_send->et_protlen = htons(PROT_IP);
tcp_send = (void *)eth_send + ETHER_HDR_SIZE;
tcp_send->tcp_src = tcp->tcp_dst;
tcp_send->tcp_dst = tcp->tcp_src;
-   tcp_send->tcp_seq = htonl(0);
-   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1);
+   tcp_send->tcp_seq = htonl(priv->iss);
+   tcp_send->tcp_ack = htonl(priv->irs + 1);
tcp_send->tcp_hlen = 
SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
tcp_send->tcp_flags = TCP_SYN | TCP_ACK;
tcp_send->tcp_win = htons(PKTBUFSRX * TCP_MSS >> TCP_SCALE);
@@ -102,6 +106,8 @@ static int sb_ack_handler(struct udevice *dev, void *packet,
void *data;
int pkt_len;
int payload_len = 0;
+   u32 tcp_seq, tcp_ack;
+   int tcp_data_len;
const char *payload1 = "HTTP/1.1 200 OK\r\n"
"Content-Length: 30\r\n\r\n\r\n"
"Hi\r\n";
@@ -119,17 +125,32 @@ static int sb_ack_handler(struct udevice *dev, void 
*packet,
tcp_send->tcp_dst = tcp->tcp_src;
data = (void *)tcp_send + IP_TCP_HDR_SIZE;
 
-   if (ntohl(tcp->tcp_seq) == 1 && ntohl(tcp->tcp_ack) == 1) {
-   tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack));
-   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1);
+   tcp_seq = ntohl(tcp->tcp_seq) - priv->irs;
+   tcp_ack = ntohl(tcp->tcp_ack) - priv->iss;
+   tcp_data_len = len - ETHER_HDR_SIZE - IP_HDR_SIZE - 
GET_TCP_HDR_LEN_IN_BYTES(tcp->tcp_hlen);
+
+   if (tcp->tcp_flags & TCP_FIN)
+   tcp_data_len++;
+
+   tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack));
+   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + tcp_data_len);
+
+   if (tcp_seq == 1 && tcp_ack == 1) {
+   if (tcp_data_len == 0) {
+   /* no data, wait for GET request */
+   return -1;
+   }
+
+   /* reply to GET request */
payload_len = strlen(payload1);
memcpy(data, payload1, payload_len);
tcp_send->tcp_flags = TCP_ACK;
-   } else if (ntohl(tcp->tcp_seq) == 2) {
-   tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack));
-   tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1);
+   } else if (tcp_ack == 1 + strlen(payload1)) {
payload_len = 0;
tcp_send->tcp_fla

[PATCH v6 05/12] net/tcp: rename ack_edge and seq_init to more common rcv_nxt and irs

2024-09-09 Thread Mikhail Kshevetskiy
Use the names from RFC 9293

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h |  8 
 net/tcp.c | 32 
 2 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index f224d0cae2f..0694af9d5b1 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -285,8 +285,8 @@ enum tcp_state {
  *
  * @state: TCP connection state
  *
- * @seq_init:  Initial receive sequence number
- * @ack_edge:  Receive next
+ * @irs:   Initial receive sequence number
+ * @rcv_nxt:   Receive next
  *
  * @loc_timestamp: Local timestamp
  * @rmt_timestamp: Remote timestamp
@@ -301,8 +301,8 @@ struct tcp_stream {
/* TCP connection state */
enum tcp_state  state;
 
-   u32 seq_init;
-   u32 ack_edge;
+   u32 irs;
+   u32 rcv_nxt;
 
/* TCP option timestamp */
u32 loc_timestamp;
diff --git a/net/tcp.c b/net/tcp.c
index 0c32c5d7c92..7e445eaffd6 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -360,9 +360,9 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, 
int payload_len,
pkt_len = pkt_hdr_len + payload_len;
tcp_len = pkt_len - IP_HDR_SIZE;
 
-   tcp->ack_edge = tcp_ack_num;
+   tcp->rcv_nxt = tcp_ack_num;
/* TCP Header */
-   b->ip.hdr.tcp_ack = htonl(tcp->ack_edge);
+   b->ip.hdr.tcp_ack = htonl(tcp->rcv_nxt);
b->ip.hdr.tcp_src = htons(tcp->lport);
b->ip.hdr.tcp_dst = htons(tcp->rport);
b->ip.hdr.tcp_seq = htonl(tcp_seq_num);
@@ -396,10 +396,10 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar 
*pkt, int payload_len,
return pkt_hdr_len;
 }
 
-static void tcp_update_ack_edge(struct tcp_stream *tcp)
+static void tcp_update_rcv_nxt(struct tcp_stream *tcp)
 {
-   if (tcp_seq_cmp(tcp->ack_edge, tcp->lost.hill[0].l) >= 0) {
-   tcp->ack_edge = tcp->lost.hill[0].r;
+   if (tcp_seq_cmp(tcp->rcv_nxt, tcp->lost.hill[0].l) >= 0) {
+   tcp->rcv_nxt = tcp->lost.hill[0].r;
 
memmove(&tcp->lost.hill[0], &tcp->lost.hill[1],
(TCP_SACK_HILLS - 1) * sizeof(struct sack_edges));
@@ -434,7 +434,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 
len)
tcp_seq_num = tcp->lost.hill[i].l;
}
if (tcp_seq_cmp(tcp->lost.hill[i].r, tcp_seq_num + len) >= 0) {
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
@@ -463,12 +463,12 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, 
u32 len)
}
}
 
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
if (i == TCP_SACK_HILLS) {
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
@@ -489,7 +489,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 
len)
tcp->lost.hill[i].r = tcp_seq_num + len;
tcp->lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8;
 
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
 };
 
 /**
@@ -566,8 +566,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
debug_cond(DEBUG_INT_STATE, "TCP CLOSED %x\n", tcp_flags);
if (tcp_syn) {
action = TCP_SYN | TCP_ACK;
-   tcp->seq_init = tcp_seq_num;
-   tcp->ack_edge = tcp_seq_num + 1;
+   tcp->irs = tcp_seq_num;
+   tcp->rcv_nxt = tcp_seq_num + 1;
tcp->lost.len = TCP_OPT_LEN_2;
tcp->state = TCP_SYN_RECEIVED;
} else if (tcp_ack || tcp_fin) {
@@ -583,8 +583,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
tcp->state = TCP_CLOSE_WAIT;
} else if (tcp_ack || (tcp_syn && tcp_ack)) {
action |= TCP_ACK;
-   tcp->seq_init = tcp_seq_num;
-   tcp->ack_edge = tcp_seq_num + 1;
+   tcp->irs = tcp_seq_num;
+   tcp->rcv_nxt = tcp_seq_num + 1;
tcp->state = TCP_ESTABLISHED;
 
if (tcp_syn && tcp_ack)
@@ -633,7 +633,7 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
case TCP_FIN_WAIT_1:
debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_1 (%x)\n", tcp_flags);
if (tcp_fin) {
-   

[PATCH v6 04/12] net/tcp: add connection info to tcp_stream structure

2024-09-09 Thread Mikhail Kshevetskiy
Changes:
 * Avoid use net_server_ip in tcp code, use tcp_stream data instead
 * Ignore packets from other connections if connection already created.
   This prevents us from connection break caused by other tcp stream.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net.h  |   5 +-
 include/net/tcp.h  |  57 +---
 net/fastboot_tcp.c |  50 +
 net/net.c  |  12 ++---
 net/tcp.c  | 131 ++---
 net/wget.c |  52 +++---
 6 files changed, 204 insertions(+), 103 deletions(-)

diff --git a/include/net.h b/include/net.h
index bb2ae20f52a..b0ce13e0a9d 100644
--- a/include/net.h
+++ b/include/net.h
@@ -667,6 +667,7 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
 /**
  * net_send_tcp_packet() - Transmit TCP packet.
  * @payload_len: length of payload
+ * @dhost: Destination host
  * @dport: Destination TCP port
  * @sport: Source TCP port
  * @action: TCP action to be performed
@@ -675,8 +676,8 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
  *
  * Return: 0 on success, other value on failure
  */
-int net_send_tcp_packet(int payload_len, int dport, int sport, u8 action,
-   u32 tcp_seq_num, u32 tcp_ack_num);
+int net_send_tcp_packet(int payload_len, struct in_addr dhost, int dport,
+   int sport, u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport,
int sport, int payload_len);
 
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 14aee64cb1c..f224d0cae2f 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -279,6 +279,9 @@ enum tcp_state {
 
 /**
  * struct tcp_stream - TCP data stream structure
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ * @lport: Local port, host byte order
  *
  * @state: TCP connection state
  *
@@ -291,6 +294,10 @@ enum tcp_state {
  * @lost:  Used for SACK
  */
 struct tcp_stream {
+   struct in_addr  rhost;
+   u16 rport;
+   u16 lport;
+
/* TCP connection state */
enum tcp_state  state;
 
@@ -305,16 +312,53 @@ struct tcp_stream {
struct tcp_sack_v lost;
 };
 
-struct tcp_stream *tcp_stream_get(void);
+void tcp_init(void);
+
+typedef int tcp_incoming_filter(struct in_addr rhost,
+   u16 rport, u16 sport);
+
+/*
+ * This function sets user callback used to accept/drop incoming
+ * connections. Callback should:
+ *  + Check TCP stream endpoint and make connection verdict
+ *- return non-zero value to accept connection
+ *- return zero to drop connection
+ *
+ * WARNING: If callback is NOT defined, all incoming connections
+ *  will be dropped.
+ */
+void tcp_set_incoming_filter(tcp_incoming_filter *filter);
+
+/*
+ * tcp_stream_get -- Get or create TCP stream
+ * @is_new:if non-zero and no stream found, then create a new one
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ * @lport: Local port, host byte order
+ *
+ * Returns: TCP stream structure or NULL (if not found/created)
+ */
+struct tcp_stream *tcp_stream_get(int is_new, struct in_addr rhost,
+ u16 rport, u16 lport);
+
+/*
+ * tcp_stream_connect -- Create new TCP stream for remote connection.
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ *
+ * Returns: TCP new stream structure or NULL (if not created).
+ *  Random local port will be used.
+ */
+struct tcp_stream *tcp_stream_connect(struct in_addr rhost, u16 rport);
+
+enum tcp_state tcp_stream_get_state(struct tcp_stream *tcp);
 
-enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp);
-void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state);
-int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport,
-  int sport, int payload_len,
+int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 
 /**
  * rxhand_tcp() - An incoming packet handler.
+ * @tcp: TCP stream
  * @pkt: pointer to the application packet
  * @dport: destination TCP port
  * @sip: source IP address
@@ -324,8 +368,7 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, 
int dport,
  * @action: TCP action (SYN, ACK, FIN, etc)
  * @len: packet length
  */
-typedef void rxhand_tcp(uchar *pkt, u16 dport,
-   struct in_addr sip, u16 sport,
+typedef void rxhand_tcp(struct tcp_stream *tcp, uchar *pkt,
u32 tcp_seq_num, u32 tcp_ack_num,
u8 action, unsigned int len);
 void tcp_set_tcp_handler(rxhand_tcp *f);
diff --git a/net

[PATCH v6 03/12] net/tcp: put connection specific data into a tcp_stream structure

2024-09-09 Thread Mikhail Kshevetskiy
no functional changes

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h |  37 +++-
 net/net.c |  11 ++-
 net/tcp.c | 231 +++---
 net/wget.c|   3 +-
 4 files changed, 163 insertions(+), 119 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index c29d4ce24a7..14aee64cb1c 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -277,9 +277,40 @@ enum tcp_state {
TCP_FIN_WAIT_2
 };
 
-enum tcp_state tcp_get_tcp_state(void);
-void tcp_set_tcp_state(enum tcp_state new_state);
-int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len,
+/**
+ * struct tcp_stream - TCP data stream structure
+ *
+ * @state: TCP connection state
+ *
+ * @seq_init:  Initial receive sequence number
+ * @ack_edge:  Receive next
+ *
+ * @loc_timestamp: Local timestamp
+ * @rmt_timestamp: Remote timestamp
+ *
+ * @lost:  Used for SACK
+ */
+struct tcp_stream {
+   /* TCP connection state */
+   enum tcp_state  state;
+
+   u32 seq_init;
+   u32 ack_edge;
+
+   /* TCP option timestamp */
+   u32 loc_timestamp;
+   u32 rmt_timestamp;
+
+   /* TCP sliding window control used to request re-TX */
+   struct tcp_sack_v lost;
+};
+
+struct tcp_stream *tcp_stream_get(void);
+
+enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp);
+void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state);
+int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport,
+  int sport, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 
 /**
diff --git a/net/net.c b/net/net.c
index d9bc9df643f..6c5ee7e0925 100644
--- a/net/net.c
+++ b/net/net.c
@@ -414,7 +414,7 @@ int net_init(void)
/* Only need to setup buffer pointers once. */
first_call = 0;
if (IS_ENABLED(CONFIG_PROT_TCP))
-   tcp_set_tcp_state(TCP_CLOSED);
+   tcp_set_tcp_state(tcp_stream_get(), TCP_CLOSED);
}
 
return net_init_loop();
@@ -915,6 +915,9 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
uchar *pkt;
int eth_hdr_size;
int pkt_hdr_size;
+#if defined(CONFIG_PROT_TCP)
+   struct tcp_stream *tcp;
+#endif
 
/* make sure the net_tx_packet is initialized (net_init() was called) */
assert(net_tx_packet != NULL);
@@ -941,8 +944,12 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
break;
 #if defined(CONFIG_PROT_TCP)
case IPPROTO_TCP:
+   tcp = tcp_stream_get();
+   if (tcp == NULL)
+   return -EINVAL;
+
pkt_hdr_size = eth_hdr_size
-   + tcp_set_tcp_header(pkt + eth_hdr_size, dport, sport,
+   + tcp_set_tcp_header(tcp, pkt + eth_hdr_size, dport, 
sport,
 payload_len, action, tcp_seq_num,
 tcp_ack_num);
break;
diff --git a/net/tcp.c b/net/tcp.c
index 724536cb352..6646f171b83 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -24,19 +24,8 @@
 #include 
 #include 
 
-/*
- * TCP sliding window  control used by us to request re-TX
- */
-static struct tcp_sack_v tcp_lost;
-
-/* TCP option timestamp */
-static u32 loc_timestamp;
-static u32 rmt_timestamp;
-
-static u32 tcp_seq_init;
-static u32 tcp_ack_edge;
-
 static int tcp_activity_count;
+static struct tcp_stream tcp_stream;
 
 /*
  * TCP lengths are stored as a rounded up number of 32 bit words.
@@ -48,9 +37,6 @@ static int tcp_activity_count;
 #define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4)
 #define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
 
-/* TCP connection state */
-static enum tcp_state current_tcp_state;
-
 /* Current TCP RX packet handler */
 static rxhand_tcp *tcp_packet_handler;
 
@@ -60,22 +46,30 @@ static inline s32 tcp_seq_cmp(u32 a, u32 b)
 }
 
 /**
- * tcp_get_tcp_state() - get current TCP state
+ * tcp_get_tcp_state() - get TCP stream state
+ * @tcp: tcp stream
  *
- * Return: Current TCP state
+ * Return: TCP stream state
  */
-enum tcp_state tcp_get_tcp_state(void)
+enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp)
 {
-   return current_tcp_state;
+   return tcp->state;
 }
 
 /**
- * tcp_set_tcp_state() - set current TCP state
+ * tcp_set_tcp_state() - set TCP stream state
+ * @tcp: tcp stream
  * @new_state: new TCP state
  */
-void tcp_set_tcp_state(enum tcp_state new_state)
+void tcp_set_tcp_state(struct tcp_stream *tcp,
+  enum tcp_state new_state)
 {
-   current_tcp_state = new_state;
+   tcp->state = new_state;
+}
+
+struct tcp_stream *tcp_stream_get(void)
+{
+   return &tcp_stream;
 }
 
 sta

[PATCH v6 02/12] net/tcp: fix selective acknowledge

2024-09-09 Thread Mikhail Kshevetskiy
Current code assume that all (except last) packets are of the same size.
This is definitely wrong. Replace SACK code with a new one, that does
not rely on this assumption. Also this code uses less memory.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 200 +++---
 1 file changed, 86 insertions(+), 114 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index 3e3118de450..724536cb352 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -38,21 +38,6 @@ static u32 tcp_ack_edge;
 
 static int tcp_activity_count;
 
-/*
- * Search for TCP_SACK and review the comments before the code section
- * TCP_SACK is the number of packets at the front of the stream
- */
-
-enum pkt_state {PKT, NOPKT};
-struct sack_r {
-   struct sack_edges se;
-   enum pkt_state st;
-};
-
-static struct sack_r edge_a[TCP_SACK];
-static unsigned int sack_idx;
-static unsigned int prev_len;
-
 /*
  * TCP lengths are stored as a rounded up number of 32 bit words.
  * Add 3 to length round up, rounded, then divided into the
@@ -69,6 +54,11 @@ static enum tcp_state current_tcp_state;
 /* Current TCP RX packet handler */
 static rxhand_tcp *tcp_packet_handler;
 
+static inline s32 tcp_seq_cmp(u32 a, u32 b)
+{
+   return (s32)(a - b);
+}
+
 /**
  * tcp_get_tcp_state() - get current TCP state
  *
@@ -267,6 +257,7 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
action = TCP_FIN;
current_tcp_state = TCP_FIN_WAIT_1;
} else {
+   tcp_lost.len = TCP_OPT_LEN_2;
current_tcp_state = TCP_SYN_SENT;
}
break;
@@ -353,6 +344,20 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
return pkt_hdr_len;
 }
 
+static void tcp_update_ack_edge(void)
+{
+   if (tcp_seq_cmp(tcp_ack_edge, tcp_lost.hill[0].l) >= 0) {
+   tcp_ack_edge = tcp_lost.hill[0].r;
+
+   memmove(&tcp_lost.hill[0], &tcp_lost.hill[1],
+   (TCP_SACK_HILLS - 1) * sizeof(struct sack_edges));
+
+   tcp_lost.len -= TCP_OPT_LEN_8;
+   tcp_lost.hill[TCP_SACK_HILLS - 1].l = TCP_O_NOP;
+   tcp_lost.hill[TCP_SACK_HILLS - 1].r = TCP_O_NOP;
+   }
+}
+
 /**
  * tcp_hole() - Selective Acknowledgment (Essential for fast stream transfer)
  * @tcp_seq_num: TCP sequence start number
@@ -360,106 +365,79 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
  */
 void tcp_hole(u32 tcp_seq_num, u32 len)
 {
-   u32 idx_sack, sack_in;
-   u32 sack_end = TCP_SACK - 1;
-   u32 hill = 0;
-   enum pkt_state expect = PKT;
-   u32 seq = tcp_seq_num - tcp_seq_init;
-   u32 hol_l = tcp_ack_edge - tcp_seq_init;
-   u32 hol_r = 0;
-
-   /* Place new seq number in correct place in receive array */
-   if (prev_len == 0)
-   prev_len = len;
-
-   idx_sack = sack_idx + ((tcp_seq_num - tcp_ack_edge) / prev_len);
-   if (idx_sack < TCP_SACK) {
-   edge_a[idx_sack].se.l = tcp_seq_num;
-   edge_a[idx_sack].se.r = tcp_seq_num + len;
-   edge_a[idx_sack].st = PKT;
+   int i, j, cnt, cnt_move;
 
-   /*
-* The fin (last) packet is not the same length as data
-* packets, and if it's length is recorded and used for
-*  array index calculation, calculation breaks.
-*/
-   if (prev_len < len)
-   prev_len = len;
-   }
+   cnt = (tcp_lost.len - TCP_OPT_LEN_2) / TCP_OPT_LEN_8;
+   for (i = 0; i < cnt; i++) {
+   if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num) < 0)
+   continue;
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num + len) > 0)
+   break;
 
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP 1 seq %d, edg %d, len %d, sack_idx %d, sack_end %d\n",
-   seq, hol_l, len, sack_idx, sack_end);
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) > 0)
+   tcp_lost.hill[i].l = tcp_seq_num;
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) < 0) {
+   len += tcp_seq_num - tcp_lost.hill[i].l;
+   tcp_seq_num = tcp_lost.hill[i].l;
+   }
+   if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num + len) >= 0) {
+   tcp_update_ack_edge();
+   return;
+   }
 
-   /* Right edge of contiguous stream, is the left edge of first hill */
-   hol_l = tcp_seq_num - tcp_seq_init;
-   hol_r = hol_l + len;
+   /* check overlapping with next hills */
+   cnt_move = 0;
+   tcp_lost.hill[i].r = tcp_seq_num

[PATCH v6 01/12] net/tcp: fix TCP options processing

2024-09-09 Thread Mikhail Kshevetskiy
Current TCP code may miss an option if TCP_O_NOP option was used before
it for proper aligning.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 6 --
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index b0cc8a1fe3e..3e3118de450 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -475,7 +475,7 @@ void tcp_parse_options(uchar *o, int o_len)
 * NOPs are options with a zero length, and thus are special.
 * All other options have length fields.
 */
-   for (p = o; p < (o + o_len); p = p + p[1]) {
+   for (p = o; p < (o + o_len); ) {
if (!p[1])
return; /* Finished processing options */
 
@@ -490,12 +490,14 @@ void tcp_parse_options(uchar *o, int o_len)
case TCP_O_TS:
tsopt = (struct tcp_t_opt *)p;
rmt_timestamp = tsopt->t_snd;
-   return;
+   break;
}
 
/* Process optional NOPs */
if (p[0] == TCP_O_NOP)
p++;
+   else
+   p += p[1];
}
 }
 
-- 
2.45.2



[PATCH v6 00/12] net: tcp: improve tcp support

2024-09-09 Thread Mikhail Kshevetskiy
U-Boot support of LWIP is not ready for a moment, but we already have
some kind of tcp support. Unfrotunately this support is really bad.

Some of the known issues:
 * tcp packet from other connection can break a current one
 * tcp send sequence always starts from zero
 * bad tcp options processing
 * strange assumptions on packet size for selectiv acknowledge
 * tcp interface assumes one of the two scenarios:
 - data downloading from remote host to a board
 - request-response exchange with a small packets
   so it's not possible to upload large amount of data from the
   board to remote host.
 * wget generate bad tcp stream, test should fail but it passes instead

This series of patches fixes all of the above issuess.

The benefits:
 * A lot of bug was fixed
 * Better and more reliable TCP state machine
 * Tcp cliens becomes smaller/simpler
 * Data uploading was fixed (now it's possible to transmit a huge amount of
   data from the board to remote host)
 * Netcat over tcp was implemented. Netcat supports data downloading/uploading
   from/to remote host in client/server mode.
 * An example web-server implementation. This code can be used as a base
   for web-based firmware uploading used by some vendors.

Modification was verified with
 * firmware downloading via u-boot wget command
 * fastboot over tcp
 * netcat linux client
 * Firefox/Chrome/Edge using example web-server implementation

Changes v2:
 * cover letter was added
 * some patches were removed

Changes v3:
 * better cover letter

Changes v4:
 * fix bug in debug output
 * add more comments
 * codestyle fixes

Changes v5:
 * old patches were ocasionally sent with v4
 * add back web-server patches
 * fix bug in debug output
 * add more comments
 * codestyle fixes

Changes v6:
 * fix the wget test
 * improve description of "simplify tcp header filling code" patch

Mikhail Kshevetskiy (12):
  net/tcp: fix TCP options processing
  net/tcp: fix selective acknowledge
  net/tcp: put connection specific data into a tcp_stream structure
  net/tcp: add connection info to tcp_stream structure
  net/tcp: rename ack_edge and seq_init to more common rcv_nxt and irs
  net/tcp: improve tcp framework, use better state machine
  test/cmd/wget: fix the test
  net/tcp: simplify tcp header filling code
  net/tcp: define a fallback value for rcv_wnd size
  net/netcat: add netcat over tcp support
  net/httpd: add httpd common code
  net/httpd-upload: an example web-server implementation for file
uploading

 arch/sandbox/include/asm/eth.h  |4 +
 cmd/Kconfig |   21 +
 cmd/net.c   |   54 +-
 include/net.h   |7 +-
 include/net/httpd-upload.h  |   12 +
 include/net/httpd.h |   64 ++
 include/net/netcat.h|   20 +
 include/net/tcp.h   |  235 +-
 include/net/wget.h  |8 -
 net/Kconfig |   14 +
 net/Makefile|   21 +
 net/fastboot_tcp.c  |  190 ++---
 net/httpd-upload.c  |  123 +++
 net/httpd.c |  692 +
 net/httpd_upload/error_400.html |9 +
 net/httpd_upload/error_404.html |   10 +
 net/httpd_upload/index.html |   14 +
 net/httpd_upload/upload_ok.html |7 +
 net/net.c   |   36 +-
 net/netcat.c|  181 +
 net/tcp.c   | 1259 ++-
 net/wget.c  |  479 
 test/cmd/wget.c |   45 +-
 23 files changed, 2618 insertions(+), 887 deletions(-)
 create mode 100644 include/net/httpd-upload.h
 create mode 100644 include/net/httpd.h
 create mode 100644 include/net/netcat.h
 create mode 100644 net/httpd-upload.c
 create mode 100644 net/httpd.c
 create mode 100644 net/httpd_upload/error_400.html
 create mode 100644 net/httpd_upload/error_404.html
 create mode 100644 net/httpd_upload/index.html
 create mode 100644 net/httpd_upload/upload_ok.html
 create mode 100644 net/netcat.c

-- 
2.45.2



[RESEND PATCH v4 10/10] mtd: nand: add initial ecc engine support

2024-09-09 Thread Mikhail Kshevetskiy
only spinand on_die ecc is supported for a moment

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/Makefile   |   2 +-
 drivers/mtd/nand/core.c | 130 +++-
 drivers/mtd/nand/ecc.c  | 249 ++
 drivers/mtd/nand/spi/core.c | 207 -
 drivers/mtd/nand/spi/foresee.c  |   2 +-
 drivers/mtd/nand/spi/macronix.c |   7 +-
 drivers/mtd/nand/spi/micron.c   |   2 +-
 drivers/mtd/nand/spi/toshiba.c  |  10 +-
 drivers/mtd/nand/spi/winbond.c  |  10 +-
 include/linux/mtd/nand.h| 261 ++--
 include/linux/mtd/spinand.h |  13 +-
 include/spi-mem.h   |   2 +
 12 files changed, 830 insertions(+), 65 deletions(-)
 create mode 100644 drivers/mtd/nand/ecc.c

diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 96e186600a1..56179188e92 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0+
 
 ifeq ($(CONFIG_SPL_BUILD)$(CONFIG_TPL_BUILD),)
-nandcore-objs := core.o bbt.o
+nandcore-objs := core.o bbt.o ecc.o
 obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
 obj-$(CONFIG_MTD_RAW_NAND) += raw/
 obj-$(CONFIG_MTD_SPI_NAND) += spi/
diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c
index 472ad0bdefb..6c90d576de3 100644
--- a/drivers/mtd/nand/core.c
+++ b/drivers/mtd/nand/core.c
@@ -129,7 +129,7 @@ EXPORT_SYMBOL_GPL(nanddev_isreserved);
  *
  * Return: 0 in case of success, a negative error code otherwise.
  */
-static int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos)
+int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos)
 {
unsigned int entry;
 
@@ -187,6 +187,134 @@ int nanddev_mtd_erase(struct mtd_info *mtd, struct 
erase_info *einfo)
 }
 EXPORT_SYMBOL_GPL(nanddev_mtd_erase);
 
+/**
+ * nanddev_get_ecc_engine() - Find and get a suitable ECC engine
+ * @nand: NAND device
+ */
+static int nanddev_get_ecc_engine(struct nand_device *nand)
+{
+   int engine_type;
+
+   /* Read the user desires in terms of ECC engine/configuration */
+   of_get_nand_ecc_user_config(nand);
+
+   engine_type = nand->ecc.user_conf.engine_type;
+   if (engine_type == NAND_ECC_ENGINE_TYPE_INVALID)
+   engine_type = nand->ecc.defaults.engine_type;
+
+   switch (engine_type) {
+   case NAND_ECC_ENGINE_TYPE_NONE:
+   return 0;
+   case NAND_ECC_ENGINE_TYPE_SOFT:
+   nand->ecc.engine = nand_ecc_get_sw_engine(nand);
+   break;
+   case NAND_ECC_ENGINE_TYPE_ON_DIE:
+   nand->ecc.engine = nand_ecc_get_on_die_hw_engine(nand);
+   break;
+   case NAND_ECC_ENGINE_TYPE_ON_HOST:
+   nand->ecc.engine = nand_ecc_get_on_host_hw_engine(nand);
+   if (PTR_ERR(nand->ecc.engine) == -EPROBE_DEFER)
+   return -EPROBE_DEFER;
+   break;
+   default:
+   pr_err("Missing ECC engine type\n");
+   }
+
+   if (!nand->ecc.engine)
+   return  -EINVAL;
+
+   return 0;
+}
+
+/**
+ * nanddev_put_ecc_engine() - Dettach and put the in-use ECC engine
+ * @nand: NAND device
+ */
+static int nanddev_put_ecc_engine(struct nand_device *nand)
+{
+   switch (nand->ecc.ctx.conf.engine_type) {
+   case NAND_ECC_ENGINE_TYPE_ON_HOST:
+   nand_ecc_put_on_host_hw_engine(nand);
+   break;
+   case NAND_ECC_ENGINE_TYPE_NONE:
+   case NAND_ECC_ENGINE_TYPE_SOFT:
+   case NAND_ECC_ENGINE_TYPE_ON_DIE:
+   default:
+   break;
+   }
+
+   return 0;
+}
+
+/**
+ * nanddev_find_ecc_configuration() - Find a suitable ECC configuration
+ * @nand: NAND device
+ */
+static int nanddev_find_ecc_configuration(struct nand_device *nand)
+{
+   int ret;
+
+   if (!nand->ecc.engine)
+   return -ENOTSUPP;
+
+   ret = nand_ecc_init_ctx(nand);
+   if (ret)
+   return ret;
+
+   if (!nand_ecc_is_strong_enough(nand))
+   pr_warn("WARNING: %s: the ECC used on your system is too weak 
compared to the one required by the NAND chip\n",
+   nand->mtd->name);
+
+   return 0;
+}
+
+/**
+ * nanddev_ecc_engine_init() - Initialize an ECC engine for the chip
+ * @nand: NAND device
+ */
+int nanddev_ecc_engine_init(struct nand_device *nand)
+{
+   int ret;
+
+   /* Look for the ECC engine to use */
+   ret = nanddev_get_ecc_engine(nand);
+   if (ret) {
+   if (ret != -EPROBE_DEFER)
+   pr_err("No ECC engine found\n");
+
+   return ret;
+   }
+
+   /* No ECC engine requested */
+   if (!nand->ecc.engine)
+   return 0;
+
+   /* Configure the engine: balance user input and chip requirements */
+   ret = nanddev_find_ecc_configuration(nand);
+   

[RESEND PATCH v4 09/10] mtd: spinand: sync supported flashes with linux-6.10

2024-09-09 Thread Mikhail Kshevetskiy
Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/Makefile |   4 +-
 drivers/mtd/nand/spi/alliancememory.c | 155 
 drivers/mtd/nand/spi/ato.c|  84 +++
 drivers/mtd/nand/spi/core.c   |   5 +-
 drivers/mtd/nand/spi/esmt.c   |  16 ++-
 drivers/mtd/nand/spi/foresee.c|  97 +
 drivers/mtd/nand/spi/gigadevice.c | 194 +-
 drivers/mtd/nand/spi/macronix.c   |  25 +++-
 drivers/mtd/nand/spi/toshiba.c|  33 +
 drivers/mtd/nand/spi/winbond.c|  57 
 include/linux/mtd/spinand.h   |   5 +-
 11 files changed, 664 insertions(+), 11 deletions(-)
 create mode 100644 drivers/mtd/nand/spi/alliancememory.c
 create mode 100644 drivers/mtd/nand/spi/ato.c
 create mode 100644 drivers/mtd/nand/spi/foresee.c

diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
index 65b836b34ca..d438747cf37 100644
--- a/drivers/mtd/nand/spi/Makefile
+++ b/drivers/mtd/nand/spi/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
 
-spinand-objs := core.o esmt.o gigadevice.o macronix.o micron.o paragon.o
-spinand-objs += toshiba.o winbond.o xtx.o
+spinand-objs := core.o alliancememory.o ato.o esmt.o foresee.o gigadevice.o 
macronix.o
+spinand-objs += micron.o paragon.o toshiba.o winbond.o xtx.o
 obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
diff --git a/drivers/mtd/nand/spi/alliancememory.c 
b/drivers/mtd/nand/spi/alliancememory.c
new file mode 100644
index 000..e29e4cc77ec
--- /dev/null
+++ b/drivers/mtd/nand/spi/alliancememory.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author: Mario Kicherer 
+ */
+
+#ifndef __UBOOT__
+#include 
+#include 
+#endif
+#include 
+
+#define SPINAND_MFR_ALLIANCEMEMORY 0x52
+
+#define AM_STATUS_ECC_BITMASK  (3 << 4)
+
+#define AM_STATUS_ECC_NONE_DETECTED(0 << 4)
+#define AM_STATUS_ECC_CORRECTED(1 << 4)
+#define AM_STATUS_ECC_ERRORED  (2 << 4)
+#define AM_STATUS_ECC_MAX_CORRECTED(3 << 4)
+
+static SPINAND_OP_VARIANTS(read_cache_variants,
+   SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0),
+   SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
+   SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
+   SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
+   SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
+   SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
+
+static SPINAND_OP_VARIANTS(write_cache_variants,
+  SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
+  SPINAND_PROG_LOAD(true, 0, NULL, 0));
+
+static SPINAND_OP_VARIANTS(update_cache_variants,
+  SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
+  SPINAND_PROG_LOAD(false, 0, NULL, 0));
+
+static int am_get_eccsize(struct mtd_info *mtd)
+{
+   if (mtd->oobsize == 64)
+   return 0x20;
+   else if (mtd->oobsize == 128)
+   return 0x38;
+   else if (mtd->oobsize == 256)
+   return 0x70;
+   else
+   return -EINVAL;
+}
+
+static int am_ooblayout_ecc(struct mtd_info *mtd, int section,
+   struct mtd_oob_region *region)
+{
+   int ecc_bytes;
+
+   ecc_bytes = am_get_eccsize(mtd);
+   if (ecc_bytes < 0)
+   return ecc_bytes;
+
+   region->offset = mtd->oobsize - ecc_bytes;
+   region->length = ecc_bytes;
+
+   return 0;
+}
+
+static int am_ooblayout_free(struct mtd_info *mtd, int section,
+struct mtd_oob_region *region)
+{
+   int ecc_bytes;
+
+   if (section)
+   return -ERANGE;
+
+   ecc_bytes = am_get_eccsize(mtd);
+   if (ecc_bytes < 0)
+   return ecc_bytes;
+
+   /*
+* It is unclear how many bytes are used for the bad block marker. We
+* reserve the common two bytes here.
+*
+* The free area in this kind of flash is divided into chunks where the
+* first 4 bytes of each chunk are unprotected. The number of chunks
+* depends on the specific model. The models with 4096+256 bytes pages
+* have 8 chunks, the others 4 chunks.
+*/
+
+   region->offset = 2;
+   region->length = mtd->oobsize - 2 - ecc_bytes;
+
+   return 0;
+}
+
+static const struct mtd_ooblayout_ops am_ooblayout = {
+   .ecc = am_ooblayout_ecc,
+   .rfree = am_ooblayout_free,
+};
+
+static int am_ecc_get_status(struct spinand_device *spinand, u8 status)
+{
+   switch (status & AM_STATUS_ECC_BITMASK) {
+   case AM_STATUS_ECC_NONE_DETECTED:
+   return 0;
+
+   case AM_STATUS_ECC_CORRECTED:
+   /*
+* use oobsize to determine the flash model and the maximum of
+  

[RESEND PATCH v4 08/10] mtd: spinand: more refactoring

2024-09-09 Thread Mikhail Kshevetskiy
changes:
 * Move spinand_check_ecc_status(), spinand_noecc_ooblayout_ecc(),
   spinand_noecc_ooblayout_free() and spinand_noecc_ooblayout close
   to each other.
 * some code formatting
 * remove comments not present in linux driver

This make code more close to linux-6.10 kernel driver

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 115 +---
 1 file changed, 55 insertions(+), 60 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 6ca8b7c80cc..548a7144ee3 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -222,6 +222,59 @@ static int spinand_ecc_enable(struct spinand_device 
*spinand,
   enable ? CFG_ECC_ENABLE : 0);
 }
 
+static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status)
+{
+   struct nand_device *nand = spinand_to_nand(spinand);
+
+   if (spinand->eccinfo.get_status)
+   return spinand->eccinfo.get_status(spinand, status);
+
+   switch (status & STATUS_ECC_MASK) {
+   case STATUS_ECC_NO_BITFLIPS:
+   return 0;
+
+   case STATUS_ECC_HAS_BITFLIPS:
+   /*
+* We have no way to know exactly how many bitflips have been
+* fixed, so let's return the maximum possible value so that
+* wear-leveling layers move the data immediately.
+*/
+   return nand->eccreq.strength;
+
+   case STATUS_ECC_UNCOR_ERROR:
+   return -EBADMSG;
+
+   default:
+   break;
+   }
+
+   return -EINVAL;
+}
+
+static int spinand_noecc_ooblayout_ecc(struct mtd_info *mtd, int section,
+  struct mtd_oob_region *region)
+{
+   return -ERANGE;
+}
+
+static int spinand_noecc_ooblayout_free(struct mtd_info *mtd, int section,
+   struct mtd_oob_region *region)
+{
+   if (section)
+   return -ERANGE;
+
+   /* Reserve 2 bytes for the BBM. */
+   region->offset = 2;
+   region->length = 62;
+
+   return 0;
+}
+
+static const struct mtd_ooblayout_ops spinand_noecc_ooblayout = {
+   .ecc = spinand_noecc_ooblayout_ecc,
+   .rfree = spinand_noecc_ooblayout_free,
+};
+
 static int spinand_write_enable_op(struct spinand_device *spinand)
 {
struct spi_mem_op op = SPINAND_WR_EN_DIS_OP(true);
@@ -413,9 +466,8 @@ out:
 static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr,
  u8 ndummy, u8 *buf)
 {
-   struct spi_mem_op op = SPINAND_READID_OP(naddr, ndummy,
-spinand->scratchbuf,
-SPINAND_MAX_ID_LEN);
+   struct spi_mem_op op = SPINAND_READID_OP(
+   naddr, ndummy, spinand->scratchbuf, SPINAND_MAX_ID_LEN);
int ret;
 
ret = spi_mem_exec_op(spinand->slave, &op);
@@ -445,35 +497,6 @@ static int spinand_lock_block(struct spinand_device 
*spinand, u8 lock)
return spinand_write_reg_op(spinand, REG_BLOCK_LOCK, lock);
 }
 
-static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status)
-{
-   struct nand_device *nand = spinand_to_nand(spinand);
-
-   if (spinand->eccinfo.get_status)
-   return spinand->eccinfo.get_status(spinand, status);
-
-   switch (status & STATUS_ECC_MASK) {
-   case STATUS_ECC_NO_BITFLIPS:
-   return 0;
-
-   case STATUS_ECC_HAS_BITFLIPS:
-   /*
-* We have no way to know exactly how many bitflips have been
-* fixed, so let's return the maximum possible value so that
-* wear-leveling layers move the data immediately.
-*/
-   return nand->eccreq.strength;
-
-   case STATUS_ECC_UNCOR_ERROR:
-   return -EBADMSG;
-
-   default:
-   break;
-   }
-
-   return -EINVAL;
-}
-
 static int spinand_read_page(struct spinand_device *spinand,
 const struct nand_page_io_req *req,
 bool ecc_enabled)
@@ -1056,30 +1079,6 @@ static int spinand_detect(struct spinand_device *spinand)
return 0;
 }
 
-static int spinand_noecc_ooblayout_ecc(struct mtd_info *mtd, int section,
-  struct mtd_oob_region *region)
-{
-   return -ERANGE;
-}
-
-static int spinand_noecc_ooblayout_free(struct mtd_info *mtd, int section,
-   struct mtd_oob_region *region)
-{
-   if (section)
-   return -ERANGE;
-
-   /* Reserve 2 bytes for the BBM. */
-   region->offset = 2;
-   region->length = 62;
-
-   return 0;
-}
-
-static const struct mtd_ooblayout_ops spinand_noecc_ooblayout = {
-   .ecc = spinand_noecc_ooblayout_ecc,
-  

[RESEND PATCH v4 07/10] mtd: spinand: minor refactoring

2024-09-09 Thread Mikhail Kshevetskiy
No functional changes, just some refactoring to match better linux
kernel driver.

changes:
 * move spinand configuration reading out from spinand_init_cfg_cache()
   to separate function spinand_read_cfg()
 * move spinand flash initialization to separate function
   spinand_init_flash()
 * move direct mapping initialization to the end of spinand_init()

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 112 ++--
 1 file changed, 70 insertions(+), 42 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 9629fac3388..6ca8b7c80cc 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -160,20 +160,12 @@ int spinand_select_target(struct spinand_device *spinand, 
unsigned int target)
return 0;
 }
 
-static int spinand_init_cfg_cache(struct spinand_device *spinand)
+static int spinand_read_cfg(struct spinand_device *spinand)
 {
struct nand_device *nand = spinand_to_nand(spinand);
-   struct udevice *dev = spinand->slave->dev;
unsigned int target;
int ret;
 
-   spinand->cfg_cache = devm_kzalloc(dev,
- sizeof(*spinand->cfg_cache) *
- nand->memorg.ntargets,
- GFP_KERNEL);
-   if (!spinand->cfg_cache)
-   return -ENOMEM;
-
for (target = 0; target < nand->memorg.ntargets; target++) {
ret = spinand_select_target(spinand, target);
if (ret)
@@ -192,6 +184,21 @@ static int spinand_init_cfg_cache(struct spinand_device 
*spinand)
return 0;
 }
 
+static int spinand_init_cfg_cache(struct spinand_device *spinand)
+{
+   struct nand_device *nand = spinand_to_nand(spinand);
+   struct udevice *dev = spinand->slave->dev;
+
+   spinand->cfg_cache = devm_kcalloc(dev,
+ nand->memorg.ntargets,
+ sizeof(*spinand->cfg_cache),
+ GFP_KERNEL);
+   if (!spinand->cfg_cache)
+   return -ENOMEM;
+
+   return 0;
+}
+
 static int spinand_init_quad_enable(struct spinand_device *spinand)
 {
bool enable = false;
@@ -1073,11 +1080,55 @@ static const struct mtd_ooblayout_ops 
spinand_noecc_ooblayout = {
.rfree = spinand_noecc_ooblayout_free,
 };
 
+static int spinand_init_flash(struct spinand_device *spinand)
+{
+   struct udevice *dev = spinand->slave->dev;
+   struct nand_device *nand = spinand_to_nand(spinand);
+   int ret, i;
+
+   ret = spinand_read_cfg(spinand);
+   if (ret)
+   return ret;
+
+   ret = spinand_init_quad_enable(spinand);
+   if (ret)
+   return ret;
+
+   ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0);
+   if (ret)
+   return ret;
+
+   ret = spinand_manufacturer_init(spinand);
+   if (ret) {
+   dev_err(dev,
+   "Failed to initialize the SPI NAND chip (err = %d)\n",
+   ret);
+   return ret;
+   }
+
+   /* After power up, all blocks are locked, so unlock them here. */
+   for (i = 0; i < nand->memorg.ntargets; i++) {
+   ret = spinand_select_target(spinand, i);
+   if (ret)
+   break;
+
+   ret = spinand_lock_block(spinand, BL_ALL_UNLOCKED);
+   if (ret)
+   break;
+   }
+
+   if (ret)
+   spinand_manufacturer_cleanup(spinand);
+
+   return ret;
+}
+
 static int spinand_init(struct spinand_device *spinand)
 {
+   struct udevice *dev = spinand->slave->dev;
struct mtd_info *mtd = spinand_to_mtd(spinand);
struct nand_device *nand = mtd_to_nanddev(mtd);
-   int ret, i;
+   int ret;
 
/*
 * We need a scratch buffer because the spi_mem interface requires that
@@ -1110,41 +1161,10 @@ static int spinand_init(struct spinand_device *spinand)
if (ret)
goto err_free_bufs;
 
-   ret = spinand_init_quad_enable(spinand);
+   ret = spinand_init_flash(spinand);
if (ret)
goto err_free_bufs;
 
-   ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0);
-   if (ret)
-   goto err_free_bufs;
-
-   ret = spinand_manufacturer_init(spinand);
-   if (ret) {
-   dev_err(spinand->slave->dev,
-   "Failed to initialize the SPI NAND chip (err = %d)\n",
-   ret);
-   goto err_free_bufs;
-   }
-
-   ret = spinand_create_dirmaps(spinand);
-   if (ret) {
-   dev_err(spinand->slave->dev,
-   "Failed to create direct mappings for read/write 
operations (err = %d)\n",
-

[RESEND PATCH v4 06/10] mtd: spinand: replace enable_ecc variable with disable_ecc and update corresponding logic

2024-09-09 Thread Mikhail Kshevetskiy
Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 24 +++-
 1 file changed, 15 insertions(+), 9 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index b58d9e00907..9629fac3388 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -530,12 +530,12 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t 
from,
struct nand_device *nand = mtd_to_nanddev(mtd);
unsigned int max_bitflips = 0;
struct nand_io_iter iter;
-   bool enable_ecc = false;
+   bool disable_ecc = false;
bool ecc_failed = false;
int ret = 0;
 
-   if (ops->mode != MTD_OPS_RAW && spinand->eccinfo.ooblayout)
-   enable_ecc = true;
+   if (ops->mode == MTD_OPS_RAW || !spinand->eccinfo.ooblayout)
+   disable_ecc = true;
 
 #ifndef __UBOOT__
mutex_lock(&spinand->lock);
@@ -543,15 +543,18 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t 
from,
 
nanddev_io_for_each_page(nand, NAND_PAGE_READ, from, ops, &iter) {
schedule();
+   if (disable_ecc)
+   iter.req.mode = MTD_OPS_RAW;
+
ret = spinand_select_target(spinand, iter.req.pos.target);
if (ret)
break;
 
-   ret = spinand_ecc_enable(spinand, enable_ecc);
+   ret = spinand_ecc_enable(spinand, !disable_ecc);
if (ret)
break;
 
-   ret = spinand_read_page(spinand, &iter.req, enable_ecc);
+   ret = spinand_read_page(spinand, &iter.req, !disable_ecc);
if (ret < 0 && ret != -EBADMSG)
break;
 
@@ -583,11 +586,11 @@ static int spinand_mtd_write(struct mtd_info *mtd, loff_t 
to,
struct spinand_device *spinand = mtd_to_spinand(mtd);
struct nand_device *nand = mtd_to_nanddev(mtd);
struct nand_io_iter iter;
-   bool enable_ecc = false;
+   bool disable_ecc = false;
int ret = 0;
 
-   if (ops->mode != MTD_OPS_RAW && mtd->ooblayout)
-   enable_ecc = true;
+   if (ops->mode == MTD_OPS_RAW || !mtd->ooblayout)
+   disable_ecc = true;
 
 #ifndef __UBOOT__
mutex_lock(&spinand->lock);
@@ -595,11 +598,14 @@ static int spinand_mtd_write(struct mtd_info *mtd, loff_t 
to,
 
nanddev_io_for_each_page(nand, NAND_PAGE_WRITE, to, ops, &iter) {
schedule();
+   if (disable_ecc)
+   iter.req.mode = MTD_OPS_RAW;
+
ret = spinand_select_target(spinand, iter.req.pos.target);
if (ret)
break;
 
-   ret = spinand_ecc_enable(spinand, enable_ecc);
+   ret = spinand_ecc_enable(spinand, !disable_ecc);
if (ret)
break;
 
-- 
2.45.2



[RESEND PATCH v4 04/10] mtd: spinand: simulate behavior of linux's function spinand_wait()

2024-09-09 Thread Mikhail Kshevetskiy
also call schedule() to allow periodic actions

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 35 ---
 include/linux/mtd/spinand.h | 22 ++
 2 files changed, 50 insertions(+), 7 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 62779dd3e51..a10605487f3 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -32,6 +32,7 @@
 #include 
 #include 
 #include 
+#include 
 #endif
 
 struct spinand_plat {
@@ -362,21 +363,29 @@ static int spinand_erase_op(struct spinand_device 
*spinand,
return spi_mem_exec_op(spinand->slave, &op);
 }
 
-static int spinand_wait(struct spinand_device *spinand, u8 *s)
+static int spinand_wait(struct spinand_device *spinand,
+   unsigned long initial_delay_us,
+   unsigned long poll_delay_us,
+   u8 *s)
 {
unsigned long start, stop;
u8 status;
int ret;
 
+   udelay(initial_delay_us);
start = get_timer(0);
-   stop = 400;
+   stop = SPINAND_WAITRDY_TIMEOUT_MS;
do {
+   schedule();
+
ret = spinand_read_status(spinand, &status);
if (ret)
return ret;
 
if (!(status & STATUS_BUSY))
goto out;
+
+   udelay(poll_delay_us);
} while (get_timer(start) < stop);
 
/*
@@ -418,7 +427,10 @@ static int spinand_reset_op(struct spinand_device *spinand)
if (ret)
return ret;
 
-   return spinand_wait(spinand, NULL);
+   return spinand_wait(spinand,
+   SPINAND_RESET_INITIAL_DELAY_US,
+   SPINAND_RESET_POLL_DELAY_US,
+   NULL);
 }
 
 static int spinand_lock_block(struct spinand_device *spinand, u8 lock)
@@ -466,7 +478,10 @@ static int spinand_read_page(struct spinand_device 
*spinand,
if (ret)
return ret;
 
-   ret = spinand_wait(spinand, &status);
+   ret = spinand_wait(spinand,
+  SPINAND_READ_INITIAL_DELAY_US,
+  SPINAND_READ_POLL_DELAY_US,
+  &status);
if (ret < 0)
return ret;
 
@@ -498,9 +513,12 @@ static int spinand_write_page(struct spinand_device 
*spinand,
if (ret)
return ret;
 
-   ret = spinand_wait(spinand, &status);
+   ret = spinand_wait(spinand,
+  SPINAND_WRITE_INITIAL_DELAY_US,
+  SPINAND_WRITE_POLL_DELAY_US,
+  &status);
if (!ret && (status & STATUS_PROG_FAILED))
-   ret = -EIO;
+   return -EIO;
 
return ret;
 }
@@ -702,7 +720,10 @@ static int spinand_erase(struct nand_device *nand, const 
struct nand_pos *pos)
if (ret)
return ret;
 
-   ret = spinand_wait(spinand, &status);
+   ret = spinand_wait(spinand,
+  SPINAND_ERASE_INITIAL_DELAY_US,
+  SPINAND_ERASE_POLL_DELAY_US,
+  &status);
if (!ret && (status & STATUS_ERASE_FAILED))
ret = -EIO;
 
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index 5934b7604cc..b701d25f73d 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -176,6 +176,28 @@ struct spinand_op;
 struct spinand_device;
 
 #define SPINAND_MAX_ID_LEN 4
+/*
+ * For erase, write and read operation, we got the following timings :
+ * tBERS (erase) 1ms to 4ms
+ * tPROG 300us to 400us
+ * tREAD 25us to 100us
+ * In order to minimize latency, the min value is divided by 4 for the
+ * initial delay, and dividing by 20 for the poll delay.
+ * For reset, 5us/10us/500us if the device is respectively
+ * reading/programming/erasing when the RESET occurs. Since we always
+ * issue a RESET when the device is IDLE, 5us is selected for both initial
+ * and poll delay.
+ */
+#define SPINAND_READ_INITIAL_DELAY_US  6
+#define SPINAND_READ_POLL_DELAY_US 5
+#define SPINAND_RESET_INITIAL_DELAY_US 5
+#define SPINAND_RESET_POLL_DELAY_US5
+#define SPINAND_WRITE_INITIAL_DELAY_US 75
+#define SPINAND_WRITE_POLL_DELAY_US15
+#define SPINAND_ERASE_INITIAL_DELAY_US 250
+#define SPINAND_ERASE_POLL_DELAY_US50
+
+#define SPINAND_WAITRDY_TIMEOUT_MS 400
 
 /**
  * struct spinand_id - SPI NAND id structure
-- 
2.45.2



[RESEND PATCH v4 05/10] mtd: spinand: more use of spinand_to_{something} helpers

2024-09-09 Thread Mikhail Kshevetskiy
Use spinand_to_nand() and spinand_to_mtd() helpers instead of
nanddev_to_mtd() and direct access to spinand structure fields.

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index a10605487f3..b58d9e00907 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -236,7 +236,7 @@ static int spinand_read_from_cache_op(struct spinand_device 
*spinand,
  const struct nand_page_io_req *req)
 {
struct nand_device *nand = spinand_to_nand(spinand);
-   struct mtd_info *mtd = nanddev_to_mtd(nand);
+   struct mtd_info *mtd = spinand_to_mtd(spinand);
struct spi_mem_dirmap_desc *rdesc;
unsigned int nbytes = 0;
void *buf = NULL;
@@ -294,7 +294,7 @@ static int spinand_write_to_cache_op(struct spinand_device 
*spinand,
 const struct nand_page_io_req *req)
 {
struct nand_device *nand = spinand_to_nand(spinand);
-   struct mtd_info *mtd = nanddev_to_mtd(nand);
+   struct mtd_info *mtd = spinand_to_mtd(spinand);
struct spi_mem_dirmap_desc *wdesc;
unsigned int nbytes, column = 0;
void *buf = spinand->databuf;
@@ -356,7 +356,7 @@ static int spinand_program_op(struct spinand_device 
*spinand,
 static int spinand_erase_op(struct spinand_device *spinand,
const struct nand_pos *pos)
 {
-   struct nand_device *nand = &spinand->base;
+   struct nand_device *nand = spinand_to_nand(spinand);
unsigned int row = nanddev_pos_to_row(nand, pos);
struct spi_mem_op op = SPINAND_BLK_ERASE_OP(row);
 
-- 
2.45.2



[RESEND PATCH v4 03/10] mtd: spinand: add missed add missed MODULE_DEVICE_TABLE()

2024-09-09 Thread Mikhail Kshevetskiy
Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 8f227ce81fa..62779dd3e51 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1267,12 +1267,14 @@ static const struct spi_device_id spinand_ids[] = {
{ .name = "spi-nand" },
{ /* sentinel */ },
 };
+MODULE_DEVICE_TABLE(spi, spinand_ids);
 
 #ifdef CONFIG_OF
 static const struct of_device_id spinand_of_ids[] = {
{ .compatible = "spi-nand" },
{ /* sentinel */ },
 };
+MODULE_DEVICE_TABLE(of, spinand_of_ids);
 #endif
 
 static struct spi_mem_driver spinand_drv = {
-- 
2.45.2



[RESEND PATCH v4 02/10] mtd: spinand: Add a NAND page I/O request type

2024-09-09 Thread Mikhail Kshevetskiy
Use an enum to differentiate the type of I/O (reading or writing a
page). Also update the request iterator.

This is a port of linux patch 701981cab01696584a12e5f0e7c2ad931a326059
created by Miquel Raynal 

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c |  4 ++--
 include/linux/mtd/nand.h| 18 --
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index ea00cd7dcf0..8f227ce81fa 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -523,7 +523,7 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t 
from,
mutex_lock(&spinand->lock);
 #endif
 
-   nanddev_io_for_each_page(nand, from, ops, &iter) {
+   nanddev_io_for_each_page(nand, NAND_PAGE_READ, from, ops, &iter) {
schedule();
ret = spinand_select_target(spinand, iter.req.pos.target);
if (ret)
@@ -575,7 +575,7 @@ static int spinand_mtd_write(struct mtd_info *mtd, loff_t 
to,
mutex_lock(&spinand->lock);
 #endif
 
-   nanddev_io_for_each_page(nand, to, ops, &iter) {
+   nanddev_io_for_each_page(nand, NAND_PAGE_WRITE, to, ops, &iter) {
schedule();
ret = spinand_select_target(spinand, iter.req.pos.target);
if (ret)
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 651f8706df5..0afdaed5715 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -80,8 +80,19 @@ struct nand_pos {
unsigned int page;
 };
 
+/**
+ * enum nand_page_io_req_type - Direction of an I/O request
+ * @NAND_PAGE_READ: from the chip, to the controller
+ * @NAND_PAGE_WRITE: from the controller, to the chip
+ */
+enum nand_page_io_req_type {
+   NAND_PAGE_READ = 0,
+   NAND_PAGE_WRITE,
+};
+
 /**
  * struct nand_page_io_req - NAND I/O request object
+ * @type: the type of page I/O: read or write
  * @pos: the position this I/O request is targeting
  * @dataoffs: the offset within the page
  * @datalen: number of data bytes to read from/write to this page
@@ -97,6 +108,7 @@ struct nand_pos {
  * specific commands/operations.
  */
 struct nand_page_io_req {
+   enum nand_page_io_req_type type;
struct nand_pos pos;
unsigned int dataoffs;
unsigned int datalen;
@@ -613,11 +625,13 @@ static inline void nanddev_pos_next_page(struct 
nand_device *nand,
  * layer.
  */
 static inline void nanddev_io_iter_init(struct nand_device *nand,
+   enum nand_page_io_req_type reqtype,
loff_t offs, struct mtd_oob_ops *req,
struct nand_io_iter *iter)
 {
struct mtd_info *mtd = nanddev_to_mtd(nand);
 
+   iter->req.type = reqtype;
iter->req.mode = req->mode;
iter->req.dataoffs = nanddev_offs_to_pos(nand, offs, &iter->req.pos);
iter->req.ooboffs = req->ooboffs;
@@ -687,8 +701,8 @@ static inline bool nanddev_io_iter_end(struct nand_device 
*nand,
  *
  * Should be used for iterate over pages that are contained in an MTD request.
  */
-#define nanddev_io_for_each_page(nand, start, req, iter)   \
-   for (nanddev_io_iter_init(nand, start, req, iter);  \
+#define nanddev_io_for_each_page(nand, type, start, req, iter) \
+   for (nanddev_io_iter_init(nand, type, start, req, iter);\
 !nanddev_io_iter_end(nand, iter);  \
 nanddev_io_iter_next_page(nand, iter))
 
-- 
2.45.2



[RESEND PATCH v4 01/10] mtd: spinand: Use the spi-mem dirmap API

2024-09-09 Thread Mikhail Kshevetskiy
Make use of the spi-mem direct mapping API to let advanced controllers
optimize read/write operations when they support direct mapping.

This is a port of linux patch 981d1aa0697ce1393e00933f154d181e965703d0
created by Boris Brezillon .

Signed-off-by: Mikhail Kshevetskiy 
---
 drivers/mtd/nand/spi/core.c | 185 +---
 include/linux/mtd/spinand.h |   7 ++
 2 files changed, 95 insertions(+), 97 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index f5ddfbf4b83..ea00cd7dcf0 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -41,21 +41,6 @@ struct spinand_plat {
 /* SPI NAND index visible in MTD names */
 static int spi_nand_idx;
 
-static void spinand_cache_op_adjust_colum(struct spinand_device *spinand,
- const struct nand_page_io_req *req,
- u16 *column)
-{
-   struct nand_device *nand = spinand_to_nand(spinand);
-   unsigned int shift;
-
-   if (nand->memorg.planes_per_lun < 2)
-   return;
-
-   /* The plane number is passed in MSB just above the column address */
-   shift = fls(nand->memorg.pagesize);
-   *column |= req->pos.plane << shift;
-}
-
 static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
 {
struct spi_mem_op op = SPINAND_GET_FEATURE_OP(reg,
@@ -249,27 +234,21 @@ static int spinand_load_page_op(struct spinand_device 
*spinand,
 static int spinand_read_from_cache_op(struct spinand_device *spinand,
  const struct nand_page_io_req *req)
 {
-   struct spi_mem_op op = *spinand->op_templates.read_cache;
struct nand_device *nand = spinand_to_nand(spinand);
struct mtd_info *mtd = nanddev_to_mtd(nand);
-   struct nand_page_io_req adjreq = *req;
+   struct spi_mem_dirmap_desc *rdesc;
unsigned int nbytes = 0;
void *buf = NULL;
u16 column = 0;
-   int ret;
+   ssize_t ret;
 
if (req->datalen) {
-   adjreq.datalen = nanddev_page_size(nand);
-   adjreq.dataoffs = 0;
-   adjreq.databuf.in = spinand->databuf;
buf = spinand->databuf;
-   nbytes = adjreq.datalen;
+   nbytes = nanddev_page_size(nand);
+   column = 0;
}
 
if (req->ooblen) {
-   adjreq.ooblen = nanddev_per_page_oobsize(nand);
-   adjreq.ooboffs = 0;
-   adjreq.oobbuf.in = spinand->oobbuf;
nbytes += nanddev_per_page_oobsize(nand);
if (!buf) {
buf = spinand->oobbuf;
@@ -277,28 +256,19 @@ static int spinand_read_from_cache_op(struct 
spinand_device *spinand,
}
}
 
-   spinand_cache_op_adjust_colum(spinand, &adjreq, &column);
-   op.addr.val = column;
+   rdesc = spinand->dirmaps[req->pos.plane].rdesc;
 
-   /*
-* Some controllers are limited in term of max RX data size. In this
-* case, just repeat the READ_CACHE operation after updating the
-* column.
-*/
while (nbytes) {
-   op.data.buf.in = buf;
-   op.data.nbytes = nbytes;
-   ret = spi_mem_adjust_op_size(spinand->slave, &op);
-   if (ret)
+   ret = spi_mem_dirmap_read(rdesc, column, nbytes, buf);
+   if (ret < 0)
return ret;
 
-   ret = spi_mem_exec_op(spinand->slave, &op);
-   if (ret)
-   return ret;
+   if (!ret || ret > nbytes)
+   return -EIO;
 
-   buf += op.data.nbytes;
-   nbytes -= op.data.nbytes;
-   op.addr.val += op.data.nbytes;
+   nbytes -= ret;
+   column += ret;
+   buf += ret;
}
 
if (req->datalen)
@@ -322,14 +292,12 @@ static int spinand_read_from_cache_op(struct 
spinand_device *spinand,
 static int spinand_write_to_cache_op(struct spinand_device *spinand,
 const struct nand_page_io_req *req)
 {
-   struct spi_mem_op op = *spinand->op_templates.write_cache;
struct nand_device *nand = spinand_to_nand(spinand);
struct mtd_info *mtd = nanddev_to_mtd(nand);
-   struct nand_page_io_req adjreq = *req;
-   unsigned int nbytes = 0;
-   void *buf = NULL;
-   u16 column = 0;
-   int ret;
+   struct spi_mem_dirmap_desc *wdesc;
+   unsigned int nbytes, column = 0;
+   void *buf = spinand->databuf;
+   ssize_t ret;
 
/*
 * Looks like PROGRAM LOAD (AKA write cache) does not necessarily reset
@@ -338,19 +306,12 @@ static int spinand_write_to_cache_op(struct 
spinand_device *spinand,
 * the data portion of the page, otherwise 

[RESEND PATCH v4 00/10] mtd: spinand: initial support of ecc engines

2024-09-09 Thread Mikhail Kshevetskiy
This patch series:
 * sync spinand driver code with linux-6.10
 * sync spinand flash support with linux-6.10
 * add initial support of ecc engines

Up to now only software ecc is supported, but other engines can be add quite 
easily

Changes v2
 * update description of some patches

Changes v3:
 * split some patches to a smaller one for more easy checking/verification
 * sync spinand flash support with linux-6.10
 * add some ecc engine comments (taken from linux driver)
 * slightly change patch order

Changes v4:
 * avoid double increments of error counters

Mikhail Kshevetskiy (10):
  mtd: spinand: Use the spi-mem dirmap API
  mtd: spinand: Add a NAND page I/O request type
  mtd: spinand: add missed add missed MODULE_DEVICE_TABLE()
  mtd: spinand: simulate behavior of linux's function spinand_wait()
  mtd: spinand: more use of spinand_to_{something} helpers
  mtd: spinand: replace enable_ecc variable with disable_ecc and update
corresponding logic
  mtd: spinand: minor refactoring
  mtd: spinand: more refactoring
  mtd: spinand: sync supported flashes with linux-6.10
  mtd: nand: add initial ecc engine support

 drivers/mtd/nand/Makefile |   2 +-
 drivers/mtd/nand/core.c   | 130 +-
 drivers/mtd/nand/ecc.c| 249 ++
 drivers/mtd/nand/spi/Makefile |   4 +-
 drivers/mtd/nand/spi/alliancememory.c | 155 ++
 drivers/mtd/nand/spi/ato.c|  84 
 drivers/mtd/nand/spi/core.c   | 649 --
 drivers/mtd/nand/spi/esmt.c   |  16 +-
 drivers/mtd/nand/spi/foresee.c|  97 
 drivers/mtd/nand/spi/gigadevice.c | 194 +++-
 drivers/mtd/nand/spi/macronix.c   |  32 +-
 drivers/mtd/nand/spi/micron.c |   2 +-
 drivers/mtd/nand/spi/toshiba.c|  43 +-
 drivers/mtd/nand/spi/winbond.c|  67 ++-
 include/linux/mtd/nand.h  | 279 ++-
 include/linux/mtd/spinand.h   |  47 +-
 include/spi-mem.h |   2 +
 17 files changed, 1778 insertions(+), 274 deletions(-)
 create mode 100644 drivers/mtd/nand/ecc.c
 create mode 100644 drivers/mtd/nand/spi/alliancememory.c
 create mode 100644 drivers/mtd/nand/spi/ato.c
 create mode 100644 drivers/mtd/nand/spi/foresee.c

-- 
2.45.2



[RESEND PATCH v2 3/3] cmd: mtd: add nandtest command support

2024-09-09 Thread Mikhail Kshevetskiy
This patch implements readonly test of nand flashes.

Signed-off-by: Mikhail Kshevetskiy 
---
 cmd/Kconfig |   6 ++
 cmd/mtd.c   | 203 
 2 files changed, 209 insertions(+)

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 698e0e697f2..db5113e016b 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1445,6 +1445,12 @@ config CMD_MTD_TORTURE
help
  MTD torture command support.
 
+config CMD_MTD_NANDTEST
+   bool "mtd nandtest"
+   depends on CMD_MTD
+   help
+ MTD nandtest command support.
+
 config CMD_MUX
bool "mux"
depends on MULTIPLEXER
diff --git a/cmd/mtd.c b/cmd/mtd.c
index 69ddfe1d9c6..4a380dcaf83 100644
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
@@ -931,6 +931,202 @@ out_put_mtd:
 }
 #endif
 
+#ifdef CONFIG_CMD_MTD_NANDTEST
+enum nandtest_status {
+   NANDTEST_STATUS_UNKNOWN = 0,
+   NANDTEST_STATUS_NONECC_READ_FAIL,
+   NANDTEST_STATUS_ECC_READ_FAIL,
+   NANDTEST_STATUS_BAD_BLOCK,
+   NANDTEST_STATUS_BITFLIP_ABOVE_MAX,
+   NANDTEST_STATUS_BITFLIP_MISMATCH,
+   NANDTEST_STATUS_BITFLIP_MAX,
+   NANDTEST_STATUS_OK,
+};
+
+static enum nandtest_status nandtest_block_check(struct mtd_info *mtd,
+loff_t off, size_t blocksize)
+{
+   struct mtd_oob_ops  ops = {};
+   u_char  *buf;
+   int i, d, ret, len, pos, cnt, max;
+
+   if (blocksize % mtd->writesize != 0) {
+   printf("\r  block at %llx: bad block size\n", off);
+   return NANDTEST_STATUS_UNKNOWN;
+   }
+
+   buf = malloc_cache_aligned(2 * blocksize);
+   if (buf == NULL) {
+   printf("\r  block at %llx: can't allocate memory\n", off);
+   return NANDTEST_STATUS_UNKNOWN;
+   }
+
+   ops.mode = MTD_OPS_RAW;
+   ops.len = blocksize;
+   ops.datbuf = buf;
+   ops.ooblen = 0;
+   ops.oobbuf = NULL;
+
+   if (mtd->_read_oob)
+   ret = mtd->_read_oob(mtd, off, &ops);
+   else
+   ret = mtd->_read(mtd, off, ops.len, &ops.retlen, ops.datbuf);
+
+   if (ret != 0) {
+   free(buf);
+   printf("\r  block at %llx: non-ecc reading error %d\n",
+  off, ret);
+   return NANDTEST_STATUS_NONECC_READ_FAIL;
+   }
+
+   ops.mode = MTD_OPS_AUTO_OOB;
+   ops.datbuf = buf + blocksize;
+
+   if (mtd->_read_oob)
+   ret = mtd->_read_oob(mtd, off, &ops);
+   else
+   ret = mtd->_read(mtd, off, ops.len, &ops.retlen, ops.datbuf);
+
+   if (ret == -EBADMSG) {
+   free(buf);
+   printf("\r  block at %llx: bad block\n", off);
+   return NANDTEST_STATUS_BAD_BLOCK;
+   }
+
+   if (ret < 0) {
+   free(buf);
+   printf("\r  block at %llx: ecc reading error %d\n", off, ret);
+   return NANDTEST_STATUS_ECC_READ_FAIL;
+   }
+
+   if (mtd->ecc_strength == 0) {
+   free(buf);
+   return NANDTEST_STATUS_OK;
+   }
+
+   if (ret > mtd->ecc_strength) {
+   free(buf);
+   printf("\r  block at %llx: returned bit-flips value %d "
+  "is above maximum value %d\n",
+  off, ret, mtd->ecc_strength);
+   return NANDTEST_STATUS_BITFLIP_ABOVE_MAX;
+   }
+
+   max = 0;
+   pos = 0;
+   len = blocksize;
+   while (len > 0) {
+   cnt = 0;
+   for (i = 0; i < mtd->ecc_step_size; i++) {
+   d = buf[pos + i] ^ buf[blocksize + pos + i];
+   if (d == 0)
+   continue;
+
+   while (d > 0) {
+   d &= (d - 1);
+   cnt++;
+   }
+   }
+   if (cnt > max)
+   max = cnt;
+
+   len -= mtd->ecc_step_size;
+   pos += mtd->ecc_step_size;
+   }
+
+   free(buf);
+
+   if (max > ret) {
+   printf("\r  block at %llx: bitflip mismatch, "
+  "read %d but actual %d\n", off, ret, max);
+   return NANDTEST_STATUS_BITFLIP_MISMATCH;
+   }
+
+   if (ret == mtd->ecc_strength) {
+   printf("\r  block at %llx: max bitflip reached, "
+  "block is unreliable\n", off);
+   return NANDTEST_STATUS_BITFLIP_MAX;
+   }
+
+   return NANDTEST_STATUS_OK;
+}
+
+static int do_mtd_nandtest(struct cmd_tbl *cmdtp, int flag, int argc,
+  char *const argv[])
+{
+   struct mtd_info

[RESEND PATCH v2 2/3] cmd: mtd: add torture command support

2024-09-09 Thread Mikhail Kshevetskiy
Some nand flashes (like spi-nand one) are registered with mtd
subsystem only, thus nand command can't be used to work with
such flashes. As result some functionality is missing.

This patch implements 'nand torture' functionality for mtd command.

Signed-off-by: Mikhail Kshevetskiy 
---
 cmd/Kconfig |   6 ++
 cmd/mtd.c   | 196 
 2 files changed, 202 insertions(+)

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 53ebafcb610..698e0e697f2 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1439,6 +1439,12 @@ config CMD_MTD_MARKBAD
help
  MTD markbad command support.
 
+config CMD_MTD_TORTURE
+   bool "mtd torture"
+   depends on CMD_MTD
+   help
+ MTD torture command support.
+
 config CMD_MUX
bool "mux"
depends on MULTIPLEXER
diff --git a/cmd/mtd.c b/cmd/mtd.c
index 45084d39d3a..69ddfe1d9c6 100644
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
@@ -18,6 +18,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include 
 
@@ -742,6 +743,194 @@ out_put_mtd:
 }
 #endif
 
+#ifdef CONFIG_CMD_MTD_TORTURE
+/**
+ * nand_check_pattern:
+ *
+ * Check if buffer contains only a certain byte pattern.
+ *
+ * @param buf buffer to check
+ * @param patt the pattern to check
+ * @param size buffer size in bytes
+ * Return: 1 if there are only patt bytes in buf
+ * 0 if something else was found
+ */
+static int nand_check_pattern(const u_char *buf, u_char patt, int size)
+{
+   int i;
+
+   for (i = 0; i < size; i++)
+   if (buf[i] != patt)
+   return 0;
+   return 1;
+}
+
+/**
+ * nand_torture:
+ *
+ * Torture a block of NAND flash.
+ * This is useful to determine if a block that caused a write error is still
+ * good or should be marked as bad.
+ *
+ * @param mtd nand mtd instance
+ * @param offset offset in flash
+ * Return: 0 if the block is still good
+ */
+static int nand_torture(struct mtd_info *mtd, loff_t offset)
+{
+   u_char patterns[] = {0xa5, 0x5a, 0x00};
+   struct erase_info instr = {
+   .mtd = mtd,
+   .addr = offset,
+   .len = mtd->erasesize,
+   };
+   size_t retlen;
+   int err, ret = -1, i, patt_count;
+   u_char *buf;
+
+   if ((offset & (mtd->erasesize - 1)) != 0) {
+   puts("Attempt to torture a block at a non block-aligned 
offset\n");
+   return -EINVAL;
+   }
+
+   if (offset + mtd->erasesize > mtd->size) {
+   puts("Attempt to torture a block outside the flash area\n");
+   return -EINVAL;
+   }
+
+   patt_count = ARRAY_SIZE(patterns);
+
+   buf = malloc_cache_aligned(mtd->erasesize);
+   if (buf == NULL) {
+   puts("Out of memory for erase block buffer\n");
+   return -ENOMEM;
+   }
+
+   for (i = 0; i < patt_count; i++) {
+   err = mtd_erase(mtd, &instr);
+   if (err) {
+   printf("%s: erase() failed for block at 0x%llx: %d\n",
+  mtd->name, instr.addr, err);
+   goto out;
+   }
+
+   /* Make sure the block contains only 0xff bytes */
+   err = mtd_read(mtd, offset, mtd->erasesize, &retlen, buf);
+   if ((err && err != -EUCLEAN) || retlen != mtd->erasesize) {
+   printf("%s: read() failed for block at 0x%llx: %d\n",
+  mtd->name, instr.addr, err);
+   goto out;
+   }
+
+   err = nand_check_pattern(buf, 0xff, mtd->erasesize);
+   if (!err) {
+   printf("Erased block at 0x%llx, but a non-0xff byte was 
found\n",
+  offset);
+   ret = -EIO;
+   goto out;
+   }
+
+   /* Write a pattern and check it */
+   memset(buf, patterns[i], mtd->erasesize);
+   err = mtd_write(mtd, offset, mtd->erasesize, &retlen, buf);
+   if (err || retlen != mtd->erasesize) {
+   printf("%s: write() failed for block at 0x%llx: %d\n",
+  mtd->name, instr.addr, err);
+   goto out;
+   }
+
+   err = mtd_read(mtd, offset, mtd->erasesize, &retlen, buf);
+   if ((err && err != -EUCLEAN) || retlen != mtd->erasesize) {
+   printf("%s: read() failed for block at 0x%llx: %d\n",
+  mtd->name, instr.addr, err);
+   goto out;
+   }
+
+   err = nand_check_pattern(buf, patterns[i], mtd->erasesize);
+   if (!err)

[RESEND PATCH v2 0/3] cmd/mtd: add missed featuries

2024-09-09 Thread Mikhail Kshevetskiy
Some nand flashes (like spi-nand one) are registered with mtd
subsystem only, thus nand command can't be used to work with
such flashes. As result some functionality is missing.

This patch series implements following subcommands:
 * markbad  -- mark block as bad (copy of 'nand markbad')
 * torture  -- destructive test of flash blocks (copy of 'nand torture')
 * nandtest -- non-destructive test of nand flashes

v2 changes:
 * add cover letter

Mikhail Kshevetskiy (3):
  cmd: mtd: add markbad command support
  cmd: mtd: add torture command support
  cmd: mtd: add nandtest command support

 cmd/Kconfig |  18 +++
 cmd/mtd.c   | 457 
 2 files changed, 475 insertions(+)

-- 
2.45.2



[RESEND PATCH v2 1/3] cmd: mtd: add markbad command support

2024-09-09 Thread Mikhail Kshevetskiy
Some nand flashes (like spi-nand one) are registered with mtd
subsystem only, thus nand command can't be used to work with
such flashes. As result some functionality is missing.

This patch implements 'nand markbad' functionality for mtd command.

Signed-off-by: Mikhail Kshevetskiy 
---
 cmd/Kconfig |  6 ++
 cmd/mtd.c   | 58 +
 2 files changed, 64 insertions(+)

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 978f44eda42..53ebafcb610 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1433,6 +1433,12 @@ config CMD_MTD_OTP
help
  MTD commands for OTP access.
 
+config CMD_MTD_MARKBAD
+   bool "mtd markbad"
+   depends on CMD_MTD
+   help
+ MTD markbad command support.
+
 config CMD_MUX
bool "mux"
depends on MULTIPLEXER
diff --git a/cmd/mtd.c b/cmd/mtd.c
index 795aaa2b37d..45084d39d3a 100644
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
@@ -691,6 +691,57 @@ out_put_mtd:
return ret;
 }
 
+#ifdef CONFIG_CMD_MTD_MARKBAD
+static int do_mtd_markbad(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+   struct mtd_info *mtd;
+   loff_t off;
+   int ret = 0;
+
+   if (argc < 3)
+   return CMD_RET_USAGE;
+
+   mtd = get_mtd_by_name(argv[1]);
+   if (IS_ERR_OR_NULL(mtd))
+   return CMD_RET_FAILURE;
+
+   if (!mtd_can_have_bb(mtd)) {
+   printf("Only NAND-based devices can have bad blocks\n");
+   goto out_put_mtd;
+   }
+
+   argc -= 2;
+   argv += 2;
+   while (argc > 0) {
+   off = hextoul(argv[0], NULL);
+   if (!mtd_is_aligned_with_block_size(mtd, off)) {
+   printf("Offset not aligned with a block (0x%x)\n",
+  mtd->erasesize);
+   ret = CMD_RET_FAILURE;
+   goto out_put_mtd;
+   }
+
+   ret = mtd_block_markbad(mtd, off);
+   if (ret) {
+   printf("block 0x%08llx NOT marked as bad! ERROR %d\n",
+  off, ret);
+   ret = CMD_RET_FAILURE;
+   } else {
+   printf("block 0x%08llx successfully marked as bad\n",
+  off);
+   }
+   --argc;
+   ++argv;
+   }
+
+out_put_mtd:
+   put_mtd_device(mtd);
+
+   return ret;
+}
+#endif
+
 static int do_mtd_bad(struct cmd_tbl *cmdtp, int flag, int argc,
  char *const argv[])
 {
@@ -773,6 +824,9 @@ U_BOOT_LONGHELP(mtd,
"mtd otpwrite\n"
"mtd otplock \n"
"mtd otpinfo[u|f]\n"
+#endif
+#ifdef CONFIG_CMD_MTD_MARKBAD
+   "mtd markbad [ 
...]\n"
 #endif
"\n"
"With:\n"
@@ -807,5 +861,9 @@ U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text,
 mtd_name_complete),
U_BOOT_SUBCMD_MKENT_COMPLETE(erase, 4, 0, do_mtd_erase,
 mtd_name_complete),
+#ifdef CONFIG_CMD_MTD_MARKBAD
+   U_BOOT_SUBCMD_MKENT_COMPLETE(markbad, 20, 0, do_mtd_markbad,
+mtd_name_complete),
+#endif
U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad,
 mtd_name_complete));
-- 
2.45.2



Re: [RESEND PATCH v3 7/9] net/tcp: simplify tcp header filling code

2024-08-24 Thread Mikhail Kshevetskiy
actually I know nothing about sandbox emulator


On 23.08.2024 23:47, Simon Glass wrote:
> Hi Mikhail,
>
> On Fri, 23 Aug 2024 at 09:12, Mikhail Kshevetskiy
>  wrote:
>> By the way, I'd like to know your opinion:
>>  * should I add a sample implementation of http/1.1 compatible
>> web-server to this patch series?
>>
>> This can be used as a base for other implementations like firmware
>> upgrade web-server used by some vendors.
> Do you mean using the sandbox emulator? Yes I think that would be very 
> helpful.
>
> Regards,
> Simon
>
>
>> Mikhail Kshevetskiy
>>
>>
>> On 17.08.2024 18:58, Simon Glass wrote:
>>> Hi Mikhail,
>>>
>>> On Wed, 14 Aug 2024 at 04:32, Mikhail Kshevetskiy
>>>  wrote:
>>>> Signed-off-by: Mikhail Kshevetskiy 
>>>> ---
>>>>  net/tcp.c | 70 +--
>>>>  1 file changed, 37 insertions(+), 33 deletions(-)
>>>>
>>> Reviewed-by: Simon Glass 
>>>
>>> nits below
>>>
>>>> diff --git a/net/tcp.c b/net/tcp.c
>>>> index 2c34556c26d..7014d5b4f43 100644
>>>> --- a/net/tcp.c
>>>> +++ b/net/tcp.c
>>>> @@ -527,10 +527,37 @@ void net_set_syn_options(struct tcp_stream *tcp, 
>>>> union tcp_build_pkt *b)
>>>> b->ip.end = TCP_O_END;
>>>>  }
>>>>
>>>> +const char *tcpflags_to_str(char tcpflags, char *buf, int size)
>>>> +{
>>>> +   int i = 0, len;
>>>> +   char *orig = buf;
>>>> +   const struct {
>>>> +   int bit;
>>>> +   const char  *name;
>>>> +   } desc[] = {{TCP_RST, "RST"}, {TCP_SYN, "SYN"}, {TCP_PUSH, "PSH"},
>>>> +   {TCP_FIN, "FIN"}, {TCP_ACK, "ACK"}, {0, NULL}};
>>>> +
>>>> +   *orig = '\0';
>>>> +   while (desc[i].name != NULL) {
>>> try to avoid comparing with NULL or 0
>>>
>>>> +   len = strlen(desc[i].name);
>>>> +   if (size <= len + 1)
>>>> +   break;
>>>> +   if (buf != orig) {
>>>> +   *buf++ = ',';
>>>> +   size--;
>>>> +   }
>>>> +   strcpy(buf, desc[i].name);
>>>> +   buf += len;
>>>> +   size -= len;
>>>> +   }
>>>> +   return orig;
>>>> +}
>>>> +
>>>>  int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int 
>>>> payload_len,
>>>>u8 action, u32 tcp_seq_num, u32 tcp_ack_num)
>>>>  {
>>>> union tcp_build_pkt *b = (union tcp_build_pkt *)pkt;
>>>> +   char buf[24];
>>>> int pkt_hdr_len;
>>>> int pkt_len;
>>>> int tcp_len;
>>>> @@ -540,55 +567,32 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar 
>>>> *pkt, int payload_len,
>>>>  * 4 bits reserved options
>>>>  */
>>>> b->ip.hdr.tcp_flags = action;
>>>> -   pkt_hdr_len = IP_TCP_HDR_SIZE;
>>>> b->ip.hdr.tcp_hlen = 
>>>> SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
>>>>
>>>> switch (action) {
>>>> case TCP_SYN:
>>>> debug_cond(DEBUG_DEV_PKT,
>>>> -  "TCP Hdr:SYN (%pI4, %pI4, sq=%u, ak=%u)\n",
>>>> -  &tcp->rhost, &net_ip,
>>>> -  tcp_seq_num, tcp_ack_num);
>>>> +  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
>>>> +  tcpflags_to_str(action, buf, sizeof(buf)),
>>>> +  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
>>>> net_set_syn_options(tcp, b);
>>>> pkt_hdr_len = IP_TCP_O_SIZE;
>>>> break;
>>>> -   case TCP_SYN | TCP_ACK:
>>>> -   case TCP_ACK:
>>>> -   pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b);
>>>> -   b->ip.hdr.tcp_flags = action;
>>>> -   debug_cond(DEB

[PATCH v5 10/11] net/httpd: add httpd common code

2024-08-24 Thread Mikhail Kshevetskiy
This patch adds HTTP/1.1 compatible web-server that can be used
by other. Server supports GET, POST, and HEAD requests. On client
request it will call user specified GET/POST callback. Then results
will be transmitted to client.

The following restrictions exist on the POST request
at the moment:
  * only multipart/form-data with a single file object
  * object will be stored to a memory area specified in
image_load_addr variable

Signed-off-by: Mikhail Kshevetskiy 
---
 include/net.h   |   2 +-
 include/net/httpd.h |  64 
 net/Kconfig |  14 +
 net/Makefile|   1 +
 net/httpd.c | 692 
 net/net.c   |   6 +
 6 files changed, 778 insertions(+), 1 deletion(-)
 create mode 100644 include/net/httpd.h
 create mode 100644 net/httpd.c

diff --git a/include/net.h b/include/net.h
index 0af6493788a..154885a2b7e 100644
--- a/include/net.h
+++ b/include/net.h
@@ -515,7 +515,7 @@ extern int  net_restart_wrap;   /* Tried all 
network devices */
 enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
-   WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, RS
+   WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, HTTPD, RS
 };
 
 extern charnet_boot_file_name[1024];/* Boot File name */
diff --git a/include/net/httpd.h b/include/net/httpd.h
new file mode 100644
index 000..ff0dc93ecf5
--- /dev/null
+++ b/include/net/httpd.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * httpd support header file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ *
+ */
+#ifndef __NET_HTTPD_COMMON_H__
+#define __NET_HTTPD_COMMON_H__
+
+struct http_reply {
+   int code;
+   const char  *code_msg;
+   const char  *data_type;
+   void*data;
+   u32 len;
+};
+
+struct httpd_post_data {
+   const char  *name;
+   const char  *filename;
+   void*addr;
+   u32 size;
+};
+
+enum httpd_req_check {
+   HTTPD_REQ_OK,
+   HTTPD_BAD_URL,
+   HTTPD_BAD_REQ,
+   HTTPD_CLNT_RST
+};
+
+struct httpd_config {
+   enum net_loop_state (*on_stop)(void);
+   void(*on_req_end)(void *req_id);
+
+   enum httpd_req_check(*pre_get)(void *req_id, const char *url);
+   enum httpd_req_check(*pre_post)(void *req_id, const char *url,
+   struct httpd_post_data *post);
+
+   struct http_reply * (*get)(void *req_id, const char *url);
+   struct http_reply * (*post)(void *req_id, const char *url,
+   struct httpd_post_data *post);
+
+   struct http_reply   *error_400;
+   struct http_reply   *error_404;
+};
+
+/**
+ * httpd_setup() - configure the webserver
+ */
+void httpd_setup(struct httpd_config *config);
+
+/**
+ * httpd_stop() - start stopping of the webserver
+ */
+void httpd_stop(void);
+
+/**
+ * httpd_start() - start the webserver
+ */
+void httpd_start(void);
+
+#endif /* __NET_HTTPD_COMMON_H__ */
diff --git a/net/Kconfig b/net/Kconfig
index 7cb80b880a9..55739b9bc98 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -243,6 +243,20 @@ config PROT_TCP_SACK
  This option should be turn on if you want to achieve the fastest
  file transfer possible.
 
+config HTTPD_COMMON
+   bool "HTTP server common code"
+   depends on PROT_TCP
+   help
+ HTTP/1.1 compatible web-server common code. It supports standard
+ GET/POST requests. User MUST provide a configuration to the
+ web-server. On client request web-server will call user specified
+ GET/POST callback. Then results will be transmitted to the client.
+ The following restricions on the POST request are present at the
+ moment:
+   * only mulipart/form-data with a single binary object
+   * object will be stored to a memory area specified in
+ image_load_addr variable
+
 config IPV6
bool "IPv6 support"
help
diff --git a/net/Makefile b/net/Makefile
index dac7b4859fb..c1f491fad02 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_PROT_UDP) += udp.o
 obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
 obj-$(CONFIG_CMD_NETCAT) += netcat.o
+obj-$(CONFIG_HTTPD_COMMON) += httpd.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/httpd.c b/net/httpd.c
new file mode 100644
index 000..052fae9ced0
--- /dev/null
+++ b/net/httpd.c
@@ -0,0 +1,692 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * httpd support driver
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ */
+
+#include 
+#

[PATCH v5 11/11] net/httpd-upload: an example web-server implementation for file uploading

2024-08-24 Thread Mikhail Kshevetskiy
This is an example web-server implementation. It can be used for files
uploading to u-boot using a web-browser. It acts much like tftpget, but no
special servers needs to be installed by the user.

This code can be used as a base for other implementations like firmware
upgrade web-server used by some vendors.

Usage:
  u-boot: start the we-server using the "httpd_upload" command
  PC: open the "http://your_uboot_ip"; link in the browser

Signed-off-by: Mikhail Kshevetskiy 
---
 cmd/Kconfig |  14 
 cmd/net.c   |  20 ++
 include/net/httpd-upload.h  |  12 
 net/Makefile|  19 +
 net/httpd-upload.c  | 123 
 net/httpd_upload/error_400.html |   9 +++
 net/httpd_upload/error_404.html |  10 +++
 net/httpd_upload/index.html |  14 
 net/httpd_upload/upload_ok.html |   7 ++
 9 files changed, 228 insertions(+)
 create mode 100644 include/net/httpd-upload.h
 create mode 100644 net/httpd-upload.c
 create mode 100644 net/httpd_upload/error_400.html
 create mode 100644 net/httpd_upload/error_404.html
 create mode 100644 net/httpd_upload/index.html
 create mode 100644 net/httpd_upload/upload_ok.html

diff --git a/cmd/Kconfig b/cmd/Kconfig
index abcd003f7f1..55b9d04f2fa 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2014,6 +2014,20 @@ config CMD_NETCAT
  netcat is a simple command to load/store kernel, or other files,
  using netcat like manner over TCP.
 
+config CMD_HTTPD_UPLOAD
+   bool "an example HTTP server for file uploading"
+   depends on HTTPD_COMMON
+   help
+ HTTP/1.1 compatible server for file uploading.
+
+config CMD_HTTPD_UPLOAD_MAX_SIZE
+   int "Maximum uploading size"
+   depends on CMD_HTTPD_UPLOAD
+   default 209715200
+   help
+ This sets maximum size of uploaded file. Real transfer will be
+ slightly more than this limit.
+
 config CMD_MII
bool "mii"
imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 364139ec5b9..e5fddc8c7c5 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -21,6 +21,9 @@
 #include 
 #include 
 #include 
+#if defined(CONFIG_CMD_HTTPD_UPLOAD)
+#include 
+#endif
 
 static int netboot_common(enum proto_t, struct cmd_tbl *, int, char * const 
[]);
 
@@ -228,6 +231,23 @@ U_BOOT_CMD(
 );
 #endif
 
+#if defined(CONFIG_CMD_HTTPD_UPLOAD)
+static int do_httpd_upload(struct cmd_tbl *cmdtp, int flag, int argc, char 
*const argv[])
+{
+   if (argc < 2)
+   return 1;
+
+   httpd_upload_prepare();
+   return netboot_common(HTTPD, cmdtp, argc, argv);
+}
+
+U_BOOT_CMD(
+   httpd_upload,   2,  1,  do_httpd_upload,
+   "starts httpd server for file uploading",
+   "[loadAddress]\n"
+);
+#endif
+
 static void netboot_update_env(void)
 {
char tmp[46];
diff --git a/include/net/httpd-upload.h b/include/net/httpd-upload.h
new file mode 100644
index 000..a80df214668
--- /dev/null
+++ b/include/net/httpd-upload.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ *
+ * httpd-upload include file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ */
+#ifndef __NET_HTTPD_UPLOAD_TCP_H__
+#define __NET_HTTPD_UPLOAD_TCP_H__
+
+void httpd_upload_prepare(void);
+
+#endif /* __NET_HTTPD_UPLOAD_TCP_H__ */
diff --git a/net/Makefile b/net/Makefile
index c1f491fad02..e7cbbc2248e 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -35,8 +35,27 @@ obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
 obj-$(CONFIG_CMD_NETCAT) += netcat.o
 obj-$(CONFIG_HTTPD_COMMON) += httpd.o
+obj-$(CONFIG_CMD_HTTPD_UPLOAD) += httpd-upload.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
 # and this is intentional usage.
 CFLAGS_eth_common.o += -Wno-format-extra-args
+
+STATIC_SUBST := 's/^\(unsigned char \)/static \1/'
+SIZE_REMOVE_SUBST := 's/^unsigned int .*//'
+
+httpd_upload_generated:
+   rm -rf $(src)/httpd_upload_generated
+   mkdir -p $(src)/httpd_upload_generated
+   cd $(src)/httpd_upload && find . -type f | while read fname; do \
+   name="$${fname##*/}" && \
+   name="$${name%%.*}"  && \
+   set -o pipefail && xxd -i "$${fname##./}" | \
+   sed $(STATIC_SUBST) | \
+   sed $(SIZE_REMOVE_SUBST) > 
../httpd_upload_generated/$${name}.h; \
+   done
+
+.PHONY: httpd_upload_generated
+
+net/httpd-upload.o:httpd_upload_generated
diff --git a/net/httpd-upload.c b/net/httpd-upload.c
new file mode 100644
index 000..1e1e0b1cf75
--- /dev/null
+++ b/net/httpd-upload.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * httpd-upload support driver
+ * Copyright (C) 2024 IOPSYS S

[PATCH v5 09/11] net/netcat: add netcat over tcp support

2024-08-24 Thread Mikhail Kshevetskiy
This patch adds downloading/uploading of data with netcat.
Client/server mode both supported.

How to test:

  netcat-openbsd=1.219-1 from debian were used for a tests

  a) Load data from remote host.
   * U-Boot listen on tcp port 3456
   * PC connects

 u-boot: netcat load ${loadaddr} 3456
 PC: netcat -q1 ${UBOOT_IP} 3456 < image.itb

  b) Load data from remote host.
   * PC listen on tcp port 3456
   * U-Boot connects

 PC: netcat -q1 -l -p 3456 < image.itb
 u-boot: netcat load ${loadaddr} ${PC_IP}:3456

  c) Save data to remote host
   * U-Boot listen on tcp port 3456
   * PC connects

 u-boot: netcat save ${loadaddr} ${data_size_in_hex} 3456
 PC: netcat -w1 ${UBOOT_IP} 3456 >image.itb

  d) Save data to remote host
   * PC listen on tcp port 3456
   * U-Boot connects

 PC: netcat -w1 -l -p 3456 >image.itb
 u-boot: netcat save ${loadaddr} ${data_size_in_hex} ${PC_IP}:3456

Signed-off-by: Mikhail Kshevetskiy 

xxx
---
 cmd/Kconfig  |   7 ++
 cmd/net.c|  34 ++--
 include/net.h|   2 +-
 include/net/netcat.h |  20 +
 net/Makefile |   1 +
 net/net.c|   9 +++
 net/netcat.c | 181 +++
 7 files changed, 248 insertions(+), 6 deletions(-)
 create mode 100644 include/net/netcat.h
 create mode 100644 net/netcat.c

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 978f44eda42..abcd003f7f1 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2007,6 +2007,13 @@ config CMD_WGET
  wget is a simple command to download kernel, or other files,
  from a http server over TCP.
 
+config CMD_NETCAT
+   bool "netcat"
+   select PROT_TCP
+   help
+ netcat is a simple command to load/store kernel, or other files,
+ using netcat like manner over TCP.
+
 config CMD_MII
bool "mii"
imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 53ce2bc5d0c..364139ec5b9 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -206,6 +206,28 @@ U_BOOT_CMD(
 );
 #endif
 
+#if defined(CONFIG_CMD_NETCAT)
+static int do_netcat(struct cmd_tbl *cmdtp, int flag, int argc, char *const 
argv[])
+{
+   if (argc < 2)
+   return 1;
+
+   if (!strcmp(argv[1], "load"))
+   return netboot_common(NETCAT_LOAD, cmdtp, argc - 1, argv + 1);
+   else if (!strcmp(argv[1], "save"))
+   return netboot_common(NETCAT_SAVE, cmdtp, argc - 1, argv + 1);
+   else
+   return 1;
+}
+
+U_BOOT_CMD(
+   netcat,   5,  1,  do_netcat,
+   "load/store data via tcp netcat utility",
+   "load [loadAddress] [[hostIPaddr:]port]\n"
+   "save Address Size [[hostIPaddr:]port]\n"
+);
+#endif
+
 static void netboot_update_env(void)
 {
char tmp[46];
@@ -323,16 +345,17 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
 
switch (argc) {
case 1:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
return 1;
-
/* refresh bootfile name from env */
copy_filename(net_boot_file_name, env_get("bootfile"),
  sizeof(net_boot_file_name));
break;
 
case 2:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
return 1;
/*
 * Only one arg - accept two forms:
@@ -354,7 +377,8 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
break;
 
case 3:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) {
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE)) {
if (parse_addr_size(argv))
return 1;
} else {
@@ -365,7 +389,7 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
}
break;
 
-#ifdef CONFIG_CMD_TFTPPUT
+#if defined(CONFIG_CMD_TFTPPUT) || defined(CONFIG_CMD_NETCAT)
case 4:
if (parse_addr_size(argv))
return 1;
diff --git a/include/net.h b/include/net.h
index b0ce13e0a9d..0af6493788a 100644
--- a/include/net.h
+++ b/include/net.h
@@ -515,7 +515,7 @@ extern int  net_restart_wrap;   /* Tried all 
network devices */
 enum proto_t {
 

[PATCH v5 08/11] net/tcp: define a fallback value for rcv_wnd size

2024-08-24 Thread Mikhail Kshevetskiy
Some driver implements it's own network packet pool, so PKTBUFSRX is zero.
This results in zero-size TCP receive window, so data transfer doesn't
work. Avoid it by setting a reasonable fallback value.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 6 +-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/net/tcp.c b/net/tcp.c
index 0778c153aca..32ee1fe03ce 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -36,7 +36,11 @@
 #define TCP_SEND_RETRY 3
 #define TCP_SEND_TIMEOUT   2000UL
 #define TCP_RX_INACTIVE_TIMEOUT3UL
-#define TCP_RCV_WND_SIZE   (PKTBUFSRX * TCP_MSS)
+#if PKTBUFSRX != 0
+  #define TCP_RCV_WND_SIZE (PKTBUFSRX * TCP_MSS)
+#else
+  #define TCP_RCV_WND_SIZE (4 * TCP_MSS)
+#endif
 
 #define TCP_PACKET_OK  0
 #define TCP_PACKET_DROP1
-- 
2.43.0



[PATCH v5 06/11] net/tcp: improve tcp framework, use better state machine

2024-08-24 Thread Mikhail Kshevetskiy
Changes:
 * Fix initial send sequence always zero issue
 * Use state machine close to RFC 9293. This should make TCP
   transfers more reliable (now we can upload a huge array
   of data from the board to external server)
 * Improve TCP framework a lot. This should make tcp client
   code much more simple.
 * rewrite wget with new tcp stack
 * rewrite fastboot_tcp with new tcp stack

It's quite hard to fix the initial send sequence (ISS) issue
with the separate patch. A naive attempt to fix an issue
inside the tcp_set_tcp_header() function will break tcp packet
retransmit logic in wget and other clients.

Example:
  Wget stores tcp_seq_num value before tcp_set_tcp_header() will
  be called and (on failure) retransmit the packet with the stored
  tcp_seq_num value. Thus:
* the same ISS must allways be used (current case)
* or tcp clients needs to generate a proper ISS when
  required.

A proper ISS fix will require a big redesing comparable with
a this one.

Signed-off-by: Mikhail Kshevetskiy 
---
 include/net/tcp.h  | 179 --
 include/net/wget.h |   8 -
 net/fastboot_tcp.c | 190 +-
 net/net.c  |   4 +
 net/tcp.c  | 845 ++---
 net/wget.c | 460 +++-
 6 files changed, 1008 insertions(+), 678 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 0694af9d5b1..0b18475645d 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -265,6 +265,7 @@ union tcp_build_pkt {
  * @TCP_CLOSING: Rec FIN, sent FIN, ACK waiting for ACK
  * @TCP_FIN_WAIT_1: Sent FIN waiting for response
  * @TCP_FIN_WAIT_2: Rec ACK from FIN sent, waiting for FIN
+ * @TCP_LAST_ACK: Waiting for ACK of the connection termination
  */
 enum tcp_state {
TCP_CLOSED,
@@ -274,7 +275,20 @@ enum tcp_state {
TCP_CLOSE_WAIT,
TCP_CLOSING,
TCP_FIN_WAIT_1,
-   TCP_FIN_WAIT_2
+   TCP_FIN_WAIT_2,
+   TCP_LAST_ACK,
+};
+
+/**
+ * enum tcp_status - TCP stream status for connection
+ * @TCP_ERR_OK: no rx/tx errors
+ * @TCP_ERR_TOUT: rx/tx timeout happened
+ * @TCP_ERR_RST: connection was reset
+ */
+enum tcp_status {
+   TCP_ERR_OK = 0,
+   TCP_ERR_TOUT,
+   TCP_ERR_RST,
 };
 
 /**
@@ -283,51 +297,150 @@ enum tcp_state {
  * @rport: Remote port, host byte order
  * @lport: Local port, host byte order
  *
+ * @priv:  User private data (not used by tcp module)
+ *
+ * @max_retry_count:   Maximum retransmit attempts (default 3)
+ * @initial_timeout:   Timeout from initial TX to reTX (default 2 sec)
+ * @rx_inactiv_timeout:Maximum time from last rx till connection drop
+ *   (default 30 sec)
+ *
+ * @on_closed: User callback, called just before destroying TCP stream
+ * @on_established:User callback, called when TCP stream enters
+ *   TCP_ESTABLISHED state
+ * @on_rcv_nxt_update: User callback, called when all data in the segment
+ *   [0..rx_bytes - 1] was received
+ * @on_snd_una_update: User callback, called when all data in the segment
+ *   [0..tx_bytes - 1] were transferred and acknowledged
+ * @rx:User callback, called on receive of segment
+ *   [rx_offs..rx_offs+len-1]. If NULL -- all incoming data
+ *   will be ignored. User SHOULD store the segment and
+ *   return the number of accepted bytes.
+ *   WARNING: Previous segmengs may not be received yet
+ * @tx:User callback, called on transmit/retransmit of 
segment
+ *   [tx_offs..tx_offs+maxlen-1]. If NULL -- no data will
+ *   be transmitted. User SHOULD fill provided buffer and
+ *   return the number of bytes in the buffer.
+ *   WARNING: do not use tcp_stream_close() from this
+ * callback (it will break stream). Better use
+ * on_snd_una_update() callback for such purposes.
+ *
+ * @time_last_rx:  Arrival time of last valid incoming package (ticks)
+ * @time_start:Timeout start time (ticks)
+ * @time_delta:Timeout duration (ticks)
+ * @time_handler   Timeout handler for a stream
+ *
  * @state: TCP connection state
+ * @status:TCP stream status (OK or ERR)
+ *
+ * @fin_rx:Non-zero if TCP_FIN was received
+ * @fin_rx_seq:TCP sequence of rx FIN bit
+ * @fin_tx:Non-zero if TCP_FIN was sent (or planned to send)
+ * @fin_tx_seq:TCP sequence of tx FIN bit
+ *
+ * @iss:   Initial send sequence number
+ * @snd_una:   Send unacknowledged
+ * @snd_nxt:   Send next
+ * @snd_wnd:   Send window (in bytes)
+ * @snd_wl1:   Segment sequence number used for last window update
+ * @sn

[PATCH v5 07/11] net/tcp: simplify tcp header filling code

2024-08-24 Thread Mikhail Kshevetskiy
Signed-off-by: Mikhail Kshevetskiy 
---
 net/tcp.c | 74 ++-
 1 file changed, 41 insertions(+), 33 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index 37dda6f84f9..0778c153aca 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -535,10 +535,41 @@ void net_set_syn_options(struct tcp_stream *tcp, union 
tcp_build_pkt *b)
b->ip.end = TCP_O_END;
 }
 
+const char *tcpflags_to_str(char tcpflags, char *buf, int size)
+{
+   int i, len;
+   char *orig = buf;
+   const struct {
+   int bit;
+   const char  *name;
+   } desc[] = {{TCP_RST, "RST"}, {TCP_SYN, "SYN"}, {TCP_PUSH, "PSH"},
+   {TCP_FIN, "FIN"}, {TCP_ACK, "ACK"}, {0, NULL}};
+
+   *orig = '\0';
+   for (i = 0; desc[i].name; i++) {
+   if (!(tcpflags & desc[i].bit))
+   continue;
+
+   len = strlen(desc[i].name);
+   if (size <= len + 1)
+   break;
+   if (buf != orig) {
+   *buf++ = ',';
+   size--;
+   }
+
+   strcpy(buf, desc[i].name);
+   buf += len;
+   size -= len;
+   }
+   return orig;
+}
+
 int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num)
 {
union tcp_build_pkt *b = (union tcp_build_pkt *)pkt;
+   char buf[24];
int pkt_hdr_len;
int pkt_len;
int tcp_len;
@@ -548,55 +579,32 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar 
*pkt, int payload_len,
 * 4 bits reserved options
 */
b->ip.hdr.tcp_flags = action;
-   pkt_hdr_len = IP_TCP_HDR_SIZE;
b->ip.hdr.tcp_hlen = 
SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
 
switch (action) {
case TCP_SYN:
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:SYN (%pI4, %pI4, sq=%u, ak=%u)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num);
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
+  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
net_set_syn_options(tcp, b);
pkt_hdr_len = IP_TCP_O_SIZE;
break;
-   case TCP_SYN | TCP_ACK:
-   case TCP_ACK:
-   pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b);
-   b->ip.hdr.tcp_flags = action;
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:ACK (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
-  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num,
-  action);
-   break;
-   case TCP_FIN:
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:FIN  (%pI4, %pI4, s=%u, a=%u)\n",
-  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
-   payload_len = 0;
-   pkt_hdr_len = IP_TCP_HDR_SIZE;
-   break;
case TCP_RST | TCP_ACK:
case TCP_RST:
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:RST  (%pI4, %pI4, s=%u, a=%u)\n",
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
   &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
+   pkt_hdr_len = IP_TCP_HDR_SIZE;
break;
-   /* Notify connection closing */
-   case (TCP_FIN | TCP_ACK):
-   case (TCP_FIN | TCP_ACK | TCP_PUSH):
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%u, a=%u, 
A=%x)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num, action);
-   fallthrough;
default:
pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b);
-   b->ip.hdr.tcp_flags = action | TCP_PUSH | TCP_ACK;
debug_cond(DEBUG_DEV_PKT,
-  "TCP Hdr:dft  (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
-  &tcp->rhost, &net_ip,
-  tcp_seq_num, tcp_ack_num, action);
+  "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n",
+  tcpflags_to_str(action, buf, sizeof(buf)),
+  &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num);
+   break;
}
 
pkt_len = pkt_hdr_len + payload_len;
-- 
2.43.0



[PATCH v5 05/11] net/tcp: rename ack_edge and seq_init to more common rcv_nxt and irs

2024-08-24 Thread Mikhail Kshevetskiy
Use the names from RFC 9293

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h |  8 
 net/tcp.c | 32 
 2 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index f224d0cae2f..0694af9d5b1 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -285,8 +285,8 @@ enum tcp_state {
  *
  * @state: TCP connection state
  *
- * @seq_init:  Initial receive sequence number
- * @ack_edge:  Receive next
+ * @irs:   Initial receive sequence number
+ * @rcv_nxt:   Receive next
  *
  * @loc_timestamp: Local timestamp
  * @rmt_timestamp: Remote timestamp
@@ -301,8 +301,8 @@ struct tcp_stream {
/* TCP connection state */
enum tcp_state  state;
 
-   u32 seq_init;
-   u32 ack_edge;
+   u32 irs;
+   u32 rcv_nxt;
 
/* TCP option timestamp */
u32 loc_timestamp;
diff --git a/net/tcp.c b/net/tcp.c
index 0c32c5d7c92..7e445eaffd6 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -360,9 +360,9 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, 
int payload_len,
pkt_len = pkt_hdr_len + payload_len;
tcp_len = pkt_len - IP_HDR_SIZE;
 
-   tcp->ack_edge = tcp_ack_num;
+   tcp->rcv_nxt = tcp_ack_num;
/* TCP Header */
-   b->ip.hdr.tcp_ack = htonl(tcp->ack_edge);
+   b->ip.hdr.tcp_ack = htonl(tcp->rcv_nxt);
b->ip.hdr.tcp_src = htons(tcp->lport);
b->ip.hdr.tcp_dst = htons(tcp->rport);
b->ip.hdr.tcp_seq = htonl(tcp_seq_num);
@@ -396,10 +396,10 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar 
*pkt, int payload_len,
return pkt_hdr_len;
 }
 
-static void tcp_update_ack_edge(struct tcp_stream *tcp)
+static void tcp_update_rcv_nxt(struct tcp_stream *tcp)
 {
-   if (tcp_seq_cmp(tcp->ack_edge, tcp->lost.hill[0].l) >= 0) {
-   tcp->ack_edge = tcp->lost.hill[0].r;
+   if (tcp_seq_cmp(tcp->rcv_nxt, tcp->lost.hill[0].l) >= 0) {
+   tcp->rcv_nxt = tcp->lost.hill[0].r;
 
memmove(&tcp->lost.hill[0], &tcp->lost.hill[1],
(TCP_SACK_HILLS - 1) * sizeof(struct sack_edges));
@@ -434,7 +434,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 
len)
tcp_seq_num = tcp->lost.hill[i].l;
}
if (tcp_seq_cmp(tcp->lost.hill[i].r, tcp_seq_num + len) >= 0) {
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
@@ -463,12 +463,12 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, 
u32 len)
}
}
 
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
if (i == TCP_SACK_HILLS) {
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
return;
}
 
@@ -489,7 +489,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 
len)
tcp->lost.hill[i].r = tcp_seq_num + len;
tcp->lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8;
 
-   tcp_update_ack_edge(tcp);
+   tcp_update_rcv_nxt(tcp);
 };
 
 /**
@@ -566,8 +566,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
debug_cond(DEBUG_INT_STATE, "TCP CLOSED %x\n", tcp_flags);
if (tcp_syn) {
action = TCP_SYN | TCP_ACK;
-   tcp->seq_init = tcp_seq_num;
-   tcp->ack_edge = tcp_seq_num + 1;
+   tcp->irs = tcp_seq_num;
+   tcp->rcv_nxt = tcp_seq_num + 1;
tcp->lost.len = TCP_OPT_LEN_2;
tcp->state = TCP_SYN_RECEIVED;
} else if (tcp_ack || tcp_fin) {
@@ -583,8 +583,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
tcp->state = TCP_CLOSE_WAIT;
} else if (tcp_ack || (tcp_syn && tcp_ack)) {
action |= TCP_ACK;
-   tcp->seq_init = tcp_seq_num;
-   tcp->ack_edge = tcp_seq_num + 1;
+   tcp->irs = tcp_seq_num;
+   tcp->rcv_nxt = tcp_seq_num + 1;
tcp->state = TCP_ESTABLISHED;
 
if (tcp_syn && tcp_ack)
@@ -633,7 +633,7 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 
tcp_flags,
case TCP_FIN_WAIT_1:
debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_1 (%x)\n", tcp_flags);
if (tcp_fin) {
-   

[PATCH v5 04/11] net/tcp: add connection info to tcp_stream structure

2024-08-24 Thread Mikhail Kshevetskiy
Changes:
 * Avoid use net_server_ip in tcp code, use tcp_stream data instead
 * Ignore packets from other connections if connection already created.
   This prevents us from connection break caused by other tcp stream.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net.h  |   5 +-
 include/net/tcp.h  |  57 +---
 net/fastboot_tcp.c |  50 +
 net/net.c  |  12 ++---
 net/tcp.c  | 131 ++---
 net/wget.c |  52 +++---
 6 files changed, 204 insertions(+), 103 deletions(-)

diff --git a/include/net.h b/include/net.h
index bb2ae20f52a..b0ce13e0a9d 100644
--- a/include/net.h
+++ b/include/net.h
@@ -667,6 +667,7 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
 /**
  * net_send_tcp_packet() - Transmit TCP packet.
  * @payload_len: length of payload
+ * @dhost: Destination host
  * @dport: Destination TCP port
  * @sport: Source TCP port
  * @action: TCP action to be performed
@@ -675,8 +676,8 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
  *
  * Return: 0 on success, other value on failure
  */
-int net_send_tcp_packet(int payload_len, int dport, int sport, u8 action,
-   u32 tcp_seq_num, u32 tcp_ack_num);
+int net_send_tcp_packet(int payload_len, struct in_addr dhost, int dport,
+   int sport, u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport,
int sport, int payload_len);
 
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 14aee64cb1c..f224d0cae2f 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -279,6 +279,9 @@ enum tcp_state {
 
 /**
  * struct tcp_stream - TCP data stream structure
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ * @lport: Local port, host byte order
  *
  * @state: TCP connection state
  *
@@ -291,6 +294,10 @@ enum tcp_state {
  * @lost:  Used for SACK
  */
 struct tcp_stream {
+   struct in_addr  rhost;
+   u16 rport;
+   u16 lport;
+
/* TCP connection state */
enum tcp_state  state;
 
@@ -305,16 +312,53 @@ struct tcp_stream {
struct tcp_sack_v lost;
 };
 
-struct tcp_stream *tcp_stream_get(void);
+void tcp_init(void);
+
+typedef int tcp_incoming_filter(struct in_addr rhost,
+   u16 rport, u16 sport);
+
+/*
+ * This function sets user callback used to accept/drop incoming
+ * connections. Callback should:
+ *  + Check TCP stream endpoint and make connection verdict
+ *- return non-zero value to accept connection
+ *- return zero to drop connection
+ *
+ * WARNING: If callback is NOT defined, all incoming connections
+ *  will be dropped.
+ */
+void tcp_set_incoming_filter(tcp_incoming_filter *filter);
+
+/*
+ * tcp_stream_get -- Get or create TCP stream
+ * @is_new:if non-zero and no stream found, then create a new one
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ * @lport: Local port, host byte order
+ *
+ * Returns: TCP stream structure or NULL (if not found/created)
+ */
+struct tcp_stream *tcp_stream_get(int is_new, struct in_addr rhost,
+ u16 rport, u16 lport);
+
+/*
+ * tcp_stream_connect -- Create new TCP stream for remote connection.
+ * @rhost: Remote host, network byte order
+ * @rport: Remote port, host byte order
+ *
+ * Returns: TCP new stream structure or NULL (if not created).
+ *  Random local port will be used.
+ */
+struct tcp_stream *tcp_stream_connect(struct in_addr rhost, u16 rport);
+
+enum tcp_state tcp_stream_get_state(struct tcp_stream *tcp);
 
-enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp);
-void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state);
-int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport,
-  int sport, int payload_len,
+int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 
 /**
  * rxhand_tcp() - An incoming packet handler.
+ * @tcp: TCP stream
  * @pkt: pointer to the application packet
  * @dport: destination TCP port
  * @sip: source IP address
@@ -324,8 +368,7 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, 
int dport,
  * @action: TCP action (SYN, ACK, FIN, etc)
  * @len: packet length
  */
-typedef void rxhand_tcp(uchar *pkt, u16 dport,
-   struct in_addr sip, u16 sport,
+typedef void rxhand_tcp(struct tcp_stream *tcp, uchar *pkt,
u32 tcp_seq_num, u32 tcp_ack_num,
u8 action, unsigned int len);
 void tcp_set_tcp_handler(rxhand_tcp *f);
diff --git a/net

[PATCH v5 03/11] net/tcp: put connection specific data into a tcp_stream structure

2024-08-24 Thread Mikhail Kshevetskiy
no functional changes

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 include/net/tcp.h |  37 +++-
 net/net.c |  11 ++-
 net/tcp.c | 231 +++---
 net/wget.c|   3 +-
 4 files changed, 163 insertions(+), 119 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index c29d4ce24a7..14aee64cb1c 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -277,9 +277,40 @@ enum tcp_state {
TCP_FIN_WAIT_2
 };
 
-enum tcp_state tcp_get_tcp_state(void);
-void tcp_set_tcp_state(enum tcp_state new_state);
-int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len,
+/**
+ * struct tcp_stream - TCP data stream structure
+ *
+ * @state: TCP connection state
+ *
+ * @seq_init:  Initial receive sequence number
+ * @ack_edge:  Receive next
+ *
+ * @loc_timestamp: Local timestamp
+ * @rmt_timestamp: Remote timestamp
+ *
+ * @lost:  Used for SACK
+ */
+struct tcp_stream {
+   /* TCP connection state */
+   enum tcp_state  state;
+
+   u32 seq_init;
+   u32 ack_edge;
+
+   /* TCP option timestamp */
+   u32 loc_timestamp;
+   u32 rmt_timestamp;
+
+   /* TCP sliding window control used to request re-TX */
+   struct tcp_sack_v lost;
+};
+
+struct tcp_stream *tcp_stream_get(void);
+
+enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp);
+void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state);
+int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport,
+  int sport, int payload_len,
   u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
 
 /**
diff --git a/net/net.c b/net/net.c
index d9bc9df643f..6c5ee7e0925 100644
--- a/net/net.c
+++ b/net/net.c
@@ -414,7 +414,7 @@ int net_init(void)
/* Only need to setup buffer pointers once. */
first_call = 0;
if (IS_ENABLED(CONFIG_PROT_TCP))
-   tcp_set_tcp_state(TCP_CLOSED);
+   tcp_set_tcp_state(tcp_stream_get(), TCP_CLOSED);
}
 
return net_init_loop();
@@ -915,6 +915,9 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
uchar *pkt;
int eth_hdr_size;
int pkt_hdr_size;
+#if defined(CONFIG_PROT_TCP)
+   struct tcp_stream *tcp;
+#endif
 
/* make sure the net_tx_packet is initialized (net_init() was called) */
assert(net_tx_packet != NULL);
@@ -941,8 +944,12 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, 
int dport, int sport,
break;
 #if defined(CONFIG_PROT_TCP)
case IPPROTO_TCP:
+   tcp = tcp_stream_get();
+   if (tcp == NULL)
+   return -EINVAL;
+
pkt_hdr_size = eth_hdr_size
-   + tcp_set_tcp_header(pkt + eth_hdr_size, dport, sport,
+   + tcp_set_tcp_header(tcp, pkt + eth_hdr_size, dport, 
sport,
 payload_len, action, tcp_seq_num,
 tcp_ack_num);
break;
diff --git a/net/tcp.c b/net/tcp.c
index 724536cb352..6646f171b83 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -24,19 +24,8 @@
 #include 
 #include 
 
-/*
- * TCP sliding window  control used by us to request re-TX
- */
-static struct tcp_sack_v tcp_lost;
-
-/* TCP option timestamp */
-static u32 loc_timestamp;
-static u32 rmt_timestamp;
-
-static u32 tcp_seq_init;
-static u32 tcp_ack_edge;
-
 static int tcp_activity_count;
+static struct tcp_stream tcp_stream;
 
 /*
  * TCP lengths are stored as a rounded up number of 32 bit words.
@@ -48,9 +37,6 @@ static int tcp_activity_count;
 #define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4)
 #define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
 
-/* TCP connection state */
-static enum tcp_state current_tcp_state;
-
 /* Current TCP RX packet handler */
 static rxhand_tcp *tcp_packet_handler;
 
@@ -60,22 +46,30 @@ static inline s32 tcp_seq_cmp(u32 a, u32 b)
 }
 
 /**
- * tcp_get_tcp_state() - get current TCP state
+ * tcp_get_tcp_state() - get TCP stream state
+ * @tcp: tcp stream
  *
- * Return: Current TCP state
+ * Return: TCP stream state
  */
-enum tcp_state tcp_get_tcp_state(void)
+enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp)
 {
-   return current_tcp_state;
+   return tcp->state;
 }
 
 /**
- * tcp_set_tcp_state() - set current TCP state
+ * tcp_set_tcp_state() - set TCP stream state
+ * @tcp: tcp stream
  * @new_state: new TCP state
  */
-void tcp_set_tcp_state(enum tcp_state new_state)
+void tcp_set_tcp_state(struct tcp_stream *tcp,
+  enum tcp_state new_state)
 {
-   current_tcp_state = new_state;
+   tcp->state = new_state;
+}
+
+struct tcp_stream *tcp_stream_get(void)
+{
+   return &tcp_stream;
 }
 
 sta

[PATCH v5 02/11] net/tcp: fix selective acknowledge

2024-08-24 Thread Mikhail Kshevetskiy
Current code assume that all (except last) packets are of the same size.
This is definitely wrong. Replace SACK code with a new one, that does
not rely on this assumption. Also this code uses less memory.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 200 +++---
 1 file changed, 86 insertions(+), 114 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index 3e3118de450..724536cb352 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -38,21 +38,6 @@ static u32 tcp_ack_edge;
 
 static int tcp_activity_count;
 
-/*
- * Search for TCP_SACK and review the comments before the code section
- * TCP_SACK is the number of packets at the front of the stream
- */
-
-enum pkt_state {PKT, NOPKT};
-struct sack_r {
-   struct sack_edges se;
-   enum pkt_state st;
-};
-
-static struct sack_r edge_a[TCP_SACK];
-static unsigned int sack_idx;
-static unsigned int prev_len;
-
 /*
  * TCP lengths are stored as a rounded up number of 32 bit words.
  * Add 3 to length round up, rounded, then divided into the
@@ -69,6 +54,11 @@ static enum tcp_state current_tcp_state;
 /* Current TCP RX packet handler */
 static rxhand_tcp *tcp_packet_handler;
 
+static inline s32 tcp_seq_cmp(u32 a, u32 b)
+{
+   return (s32)(a - b);
+}
+
 /**
  * tcp_get_tcp_state() - get current TCP state
  *
@@ -267,6 +257,7 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
action = TCP_FIN;
current_tcp_state = TCP_FIN_WAIT_1;
} else {
+   tcp_lost.len = TCP_OPT_LEN_2;
current_tcp_state = TCP_SYN_SENT;
}
break;
@@ -353,6 +344,20 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
return pkt_hdr_len;
 }
 
+static void tcp_update_ack_edge(void)
+{
+   if (tcp_seq_cmp(tcp_ack_edge, tcp_lost.hill[0].l) >= 0) {
+   tcp_ack_edge = tcp_lost.hill[0].r;
+
+   memmove(&tcp_lost.hill[0], &tcp_lost.hill[1],
+   (TCP_SACK_HILLS - 1) * sizeof(struct sack_edges));
+
+   tcp_lost.len -= TCP_OPT_LEN_8;
+   tcp_lost.hill[TCP_SACK_HILLS - 1].l = TCP_O_NOP;
+   tcp_lost.hill[TCP_SACK_HILLS - 1].r = TCP_O_NOP;
+   }
+}
+
 /**
  * tcp_hole() - Selective Acknowledgment (Essential for fast stream transfer)
  * @tcp_seq_num: TCP sequence start number
@@ -360,106 +365,79 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, 
int payload_len,
  */
 void tcp_hole(u32 tcp_seq_num, u32 len)
 {
-   u32 idx_sack, sack_in;
-   u32 sack_end = TCP_SACK - 1;
-   u32 hill = 0;
-   enum pkt_state expect = PKT;
-   u32 seq = tcp_seq_num - tcp_seq_init;
-   u32 hol_l = tcp_ack_edge - tcp_seq_init;
-   u32 hol_r = 0;
-
-   /* Place new seq number in correct place in receive array */
-   if (prev_len == 0)
-   prev_len = len;
-
-   idx_sack = sack_idx + ((tcp_seq_num - tcp_ack_edge) / prev_len);
-   if (idx_sack < TCP_SACK) {
-   edge_a[idx_sack].se.l = tcp_seq_num;
-   edge_a[idx_sack].se.r = tcp_seq_num + len;
-   edge_a[idx_sack].st = PKT;
+   int i, j, cnt, cnt_move;
 
-   /*
-* The fin (last) packet is not the same length as data
-* packets, and if it's length is recorded and used for
-*  array index calculation, calculation breaks.
-*/
-   if (prev_len < len)
-   prev_len = len;
-   }
+   cnt = (tcp_lost.len - TCP_OPT_LEN_2) / TCP_OPT_LEN_8;
+   for (i = 0; i < cnt; i++) {
+   if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num) < 0)
+   continue;
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num + len) > 0)
+   break;
 
-   debug_cond(DEBUG_DEV_PKT,
-  "TCP 1 seq %d, edg %d, len %d, sack_idx %d, sack_end %d\n",
-   seq, hol_l, len, sack_idx, sack_end);
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) > 0)
+   tcp_lost.hill[i].l = tcp_seq_num;
+   if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) < 0) {
+   len += tcp_seq_num - tcp_lost.hill[i].l;
+   tcp_seq_num = tcp_lost.hill[i].l;
+   }
+   if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num + len) >= 0) {
+   tcp_update_ack_edge();
+   return;
+   }
 
-   /* Right edge of contiguous stream, is the left edge of first hill */
-   hol_l = tcp_seq_num - tcp_seq_init;
-   hol_r = hol_l + len;
+   /* check overlapping with next hills */
+   cnt_move = 0;
+   tcp_lost.hill[i].r = tcp_seq_num

[PATCH v5 01/11] net/tcp: fix TCP options processing

2024-08-24 Thread Mikhail Kshevetskiy
Current TCP code may miss an option if TCP_O_NOP option was used before
it for proper aligning.

Signed-off-by: Mikhail Kshevetskiy 
Reviewed-by: Simon Glass 
---
 net/tcp.c | 6 --
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/net/tcp.c b/net/tcp.c
index b0cc8a1fe3e..3e3118de450 100644
--- a/net/tcp.c
+++ b/net/tcp.c
@@ -475,7 +475,7 @@ void tcp_parse_options(uchar *o, int o_len)
 * NOPs are options with a zero length, and thus are special.
 * All other options have length fields.
 */
-   for (p = o; p < (o + o_len); p = p + p[1]) {
+   for (p = o; p < (o + o_len); ) {
if (!p[1])
return; /* Finished processing options */
 
@@ -490,12 +490,14 @@ void tcp_parse_options(uchar *o, int o_len)
case TCP_O_TS:
tsopt = (struct tcp_t_opt *)p;
rmt_timestamp = tsopt->t_snd;
-   return;
+   break;
}
 
/* Process optional NOPs */
if (p[0] == TCP_O_NOP)
p++;
+   else
+   p += p[1];
}
 }
 
-- 
2.43.0



[PATCH v5 00/11] net: tcp: improve tcp support

2024-08-24 Thread Mikhail Kshevetskiy
U-Boot support of LWIP is not ready for a moment, but we already have
some kind of tcp support. Unfrotunately this support is really bad.

Some of the known issues:
 * tcp packet from other connection can break a current one
 * tcp send sequence always starts from zero
 * bad tcp options processing
 * strange assumptions on packet size for selectiv acknowledge
 * tcp interface assumes one of the two scenarios:
 - data downloading from remote host to a board
 - request-response exchange with a small packets
   so it's not possible to upload large amount of data from the
   board to remote host.

This series of patches fixes all of the above issuess.

The benefits:
 * A lot of bug was fixed
 * Better and more reliable TCP state machine
 * Tcp cliens becomes smaller/simpler
 * Data uploading was fixed (now it's possible to transmit a huge amount of
   data from the board to remote host)
 * Netcat over tcp was implemented. Netcat supports data downloading/uploading
   from/to remote host in client/server mode.
 * An example web-server implementation. This code can be used as a base
   for web-based firmware uploading used by some vendors.

Modification was verified with
 * firmware downloading via u-boot wget command
 * fastboot over tcp
 * netcat linux client
 * Firefox/Chrome/Edge using example web-server implementation

Changes v2:
 * cover letter was added
 * some patches were removed

Changes v3:
 * better cover letter

Changes v4:
 * fix bug in debug output
 * add more comments
 * codestyle fixes

Changes v5:
 * old patches were ocasionally sent with v4
 * add back web-server patches
 * fix bug in debug output
 * add more comments
 * codestyle fixes


Mikhail Kshevetskiy (11):
  net/tcp: fix TCP options processing
  net/tcp: fix selective acknowledge
  net/tcp: put connection specific data into a tcp_stream structure
  net/tcp: add connection info to tcp_stream structure
  net/tcp: rename ack_edge and seq_init to more common rcv_nxt and irs
  net/tcp: improve tcp framework, use better state machine
  net/tcp: simplify tcp header filling code
  net/tcp: define a fallback value for rcv_wnd size
  net/netcat: add netcat over tcp support
  net/httpd: add httpd common code
  net/httpd-upload: an example web-server implementation for file
uploading

 cmd/Kconfig |   21 +
 cmd/net.c   |   54 +-
 include/net.h   |7 +-
 include/net/httpd-upload.h  |   12 +
 include/net/httpd.h |   64 ++
 include/net/netcat.h|   20 +
 include/net/tcp.h   |  235 +-
 include/net/wget.h  |8 -
 net/Kconfig |   14 +
 net/Makefile|   21 +
 net/fastboot_tcp.c  |  190 ++---
 net/httpd-upload.c  |  123 +++
 net/httpd.c |  692 +
 net/httpd_upload/error_400.html |9 +
 net/httpd_upload/error_404.html |   10 +
 net/httpd_upload/index.html |   14 +
 net/httpd_upload/upload_ok.html |7 +
 net/net.c   |   36 +-
 net/netcat.c|  181 +
 net/tcp.c   | 1259 ++-
 net/wget.c  |  479 
 21 files changed, 2582 insertions(+), 874 deletions(-)
 create mode 100644 include/net/httpd-upload.h
 create mode 100644 include/net/httpd.h
 create mode 100644 include/net/netcat.h
 create mode 100644 net/httpd-upload.c
 create mode 100644 net/httpd.c
 create mode 100644 net/httpd_upload/error_400.html
 create mode 100644 net/httpd_upload/error_404.html
 create mode 100644 net/httpd_upload/index.html
 create mode 100644 net/httpd_upload/upload_ok.html
 create mode 100644 net/netcat.c

-- 
2.43.0



[PATCH v4 9/9] net/netcat: add netcat over tcp support

2024-08-23 Thread Mikhail Kshevetskiy
This patch adds downloading/uploading of data with netcat.
Client/server mode both supported.

Signed-off-by: Mikhail Kshevetskiy 
---
 cmd/Kconfig  |   7 ++
 cmd/net.c|  34 +++--
 include/net.h|   2 +-
 include/net/netcat.h |  20 ++
 net/Makefile |   1 +
 net/net.c|   9 +++
 net/netcat.c | 159 +++
 7 files changed, 226 insertions(+), 6 deletions(-)
 create mode 100644 include/net/netcat.h
 create mode 100644 net/netcat.c

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 978f44eda42..abcd003f7f1 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2007,6 +2007,13 @@ config CMD_WGET
  wget is a simple command to download kernel, or other files,
  from a http server over TCP.
 
+config CMD_NETCAT
+   bool "netcat"
+   select PROT_TCP
+   help
+ netcat is a simple command to load/store kernel, or other files,
+ using netcat like manner over TCP.
+
 config CMD_MII
bool "mii"
imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 53ce2bc5d0c..6ac81415f8a 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -206,6 +206,28 @@ U_BOOT_CMD(
 );
 #endif
 
+#if defined(CONFIG_CMD_NETCAT)
+static int do_netcat(struct cmd_tbl *cmdtp, int flag, int argc, char *const 
argv[])
+{
+   if (argc < 2)
+   return 1;
+
+   if (strcmp(argv[1], "load") == 0)
+   return netboot_common(NETCAT_LOAD, cmdtp, argc - 1, argv + 1);
+   else if (strcmp(argv[1], "store") == 0)
+   return netboot_common(NETCAT_STORE, cmdtp, argc - 1, argv + 1);
+   else
+   return 1;
+}
+
+U_BOOT_CMD(
+   netcat,   5,  1,  do_netcat,
+   "load/store data via tcp netcat utility",
+   "load [loadAddress] [[hostIPaddr:]port]\n"
+   "store Address Size [[hostIPaddr:]port]\n"
+);
+#endif
+
 static void netboot_update_env(void)
 {
char tmp[46];
@@ -323,16 +345,17 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
 
switch (argc) {
case 1:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_STORE))
return 1;
-
/* refresh bootfile name from env */
copy_filename(net_boot_file_name, env_get("bootfile"),
  sizeof(net_boot_file_name));
break;
 
case 2:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_STORE))
return 1;
/*
 * Only one arg - accept two forms:
@@ -354,7 +377,8 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
break;
 
case 3:
-   if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) {
+   if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+   (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_STORE)) {
if (parse_addr_size(argv))
return 1;
} else {
@@ -365,7 +389,7 @@ static int parse_args(enum proto_t proto, int argc, char 
*const argv[])
}
break;
 
-#ifdef CONFIG_CMD_TFTPPUT
+#if defined(CONFIG_CMD_TFTPPUT) || defined(CONFIG_CMD_NETCAT)
case 4:
if (parse_addr_size(argv))
return 1;
diff --git a/include/net.h b/include/net.h
index b0ce13e0a9d..5ec826209f0 100644
--- a/include/net.h
+++ b/include/net.h
@@ -515,7 +515,7 @@ extern int  net_restart_wrap;   /* Tried all 
network devices */
 enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
-   WOL, UDP, NCSI, WGET, RS
+   WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_STORE, RS
 };
 
 extern charnet_boot_file_name[1024];/* Boot File name */
diff --git a/include/net/netcat.h b/include/net/netcat.h
new file mode 100644
index 000..09470e7f0ce
--- /dev/null
+++ b/include/net/netcat.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ *
+ * netcat include file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy 
+ */
+#ifndef __NET_NETCAT_TCP_H__
+#define __NET_NETCAT_TCP_H__
+
+/**
+ * netcat_load_start() - begin netcat in loading mode
+ */
+void netcat_load_start(void);
+
+/**
+ * netcat_store_start() - begin netcat in data storing mode
+ */
+void netcat_store_st

  1   2   3   4   >