[PATCH v3 3/3] cmd: mtd: add nand_read_test command support
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
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
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
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
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
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
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
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
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()
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
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
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()
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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()
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
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()
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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