diff options
author | Sean Anderson <seanga2@gmail.com> | 2023-10-14 16:48:03 -0400 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2023-10-17 20:50:52 -0400 |
commit | 53d8bf8f9cff89dabf3c6461a1edeeddd49bccc6 (patch) | |
tree | c24471df064d8184b5b6d818fc232a89ca663275 /test/image | |
parent | 6ba8ecaa96cc4ede483a353b18c19715b27ba19a (diff) | |
download | u-boot-53d8bf8f9cff89dabf3c6461a1edeeddd49bccc6.tar.gz u-boot-53d8bf8f9cff89dabf3c6461a1edeeddd49bccc6.tar.bz2 u-boot-53d8bf8f9cff89dabf3c6461a1edeeddd49bccc6.zip |
test: spl: Add a test for the NET load method
Add a test for loading U-Boot over TFTP. As with other sandbox net
routines, we need to initialize our packets manually since things like
net_set_ether and net_set_udp_header always use "our" addresses. We use
BOOTP instead of DHCP, since DHCP has a tag/length-based format which is
harder to parse. Our TFTP implementation doesn't define as many constants
as I'd like, so I create some here. Note that the TFTP block size is
one-based, but offsets are zero-based.
In order to avoid address errors, we need to set up/define some additional
address information settings. dram_init_banksize would be a good candidate
for settig up bi_dram, but it gets called too late in board_init_r.
Signed-off-by: Sean Anderson <seanga2@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Diffstat (limited to 'test/image')
-rw-r--r-- | test/image/Kconfig | 9 | ||||
-rw-r--r-- | test/image/Makefile | 1 | ||||
-rw-r--r-- | test/image/spl_load_net.c | 252 |
3 files changed, 262 insertions, 0 deletions
diff --git a/test/image/Kconfig b/test/image/Kconfig index e6be1b829f..99c5078781 100644 --- a/test/image/Kconfig +++ b/test/image/Kconfig @@ -23,6 +23,15 @@ config SPL_UT_LOAD_FS help Test filesystems and the various load methods which use them. +config SPL_UT_LOAD_NET + bool "Test loading over TFTP" + depends on SANDBOX && SPL_OF_REAL + depends on SPL_ETH + depends on USE_BOOTFILE + default y + help + Test loading images over TFTP using the NET image load method. + config SPL_UT_LOAD_OS bool "Test loading from the host OS" depends on SANDBOX && SPL_LOAD_FIT diff --git a/test/image/Makefile b/test/image/Makefile index 6bd77bf0aa..245672f8d8 100644 --- a/test/image/Makefile +++ b/test/image/Makefile @@ -4,4 +4,5 @@ obj-y += spl_load.o obj-$(CONFIG_SPL_UT_LOAD_FS) += spl_load_fs.o +obj-$(CONFIG_SPL_UT_LOAD_NET) += spl_load_net.o obj-$(CONFIG_SPL_UT_LOAD_OS) += spl_load_os.o diff --git a/test/image/spl_load_net.c b/test/image/spl_load_net.c new file mode 100644 index 0000000000..f570cef163 --- /dev/null +++ b/test/image/spl_load_net.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Sean Anderson <seanga2@gmail.com> + */ + +#include <common.h> +#include <dm.h> +#include <spl.h> +#include <test/spl.h> +#include <asm/eth.h> +#include <test/ut.h> +#include "../../net/bootp.h" + +/* + * sandbox_eth_bootp_req_to_reply() + * + * Check if a BOOTP request was sent. If so, inject a reply + * + * returns 0 if injected, -EAGAIN if not + */ +static int sandbox_eth_bootp_req_to_reply(struct udevice *dev, void *packet, + unsigned int len) +{ + struct eth_sandbox_priv *priv = dev_get_priv(dev); + struct ethernet_hdr *eth = packet; + struct ip_udp_hdr *ip; + struct bootp_hdr *bp; + struct ethernet_hdr *eth_recv; + struct ip_udp_hdr *ipr; + struct bootp_hdr *bpr; + + if (ntohs(eth->et_protlen) != PROT_IP) + return -EAGAIN; + + ip = packet + ETHER_HDR_SIZE; + if (ip->ip_p != IPPROTO_UDP) + return -EAGAIN; + + if (ntohs(ip->udp_dst) != PORT_BOOTPS) + return -EAGAIN; + + bp = (void *)ip + IP_UDP_HDR_SIZE; + if (bp->bp_op != OP_BOOTREQUEST) + return -EAGAIN; + + /* Don't allow the buffer to overrun */ + if (priv->recv_packets >= PKTBUFSRX) + return 0; + + /* reply to the request */ + eth_recv = (void *)priv->recv_packet_buffer[priv->recv_packets]; + memcpy(eth_recv, packet, len); + ipr = (void *)eth_recv + ETHER_HDR_SIZE; + bpr = (void *)ipr + IP_UDP_HDR_SIZE; + memcpy(eth_recv->et_dest, eth->et_src, ARP_HLEN); + memcpy(eth_recv->et_src, priv->fake_host_hwaddr, ARP_HLEN); + ipr->ip_sum = 0; + ipr->ip_off = 0; + net_write_ip(&ipr->ip_dst, net_ip); + net_write_ip(&ipr->ip_src, priv->fake_host_ipaddr); + ipr->ip_sum = compute_ip_checksum(ipr, IP_HDR_SIZE); + ipr->udp_src = ip->udp_dst; + ipr->udp_dst = ip->udp_src; + + bpr->bp_op = OP_BOOTREPLY; + net_write_ip(&bpr->bp_yiaddr, net_ip); + net_write_ip(&bpr->bp_siaddr, priv->fake_host_ipaddr); + copy_filename(bpr->bp_file, CONFIG_BOOTFILE, sizeof(CONFIG_BOOTFILE)); + memset(&bpr->bp_vend, 0, sizeof(bpr->bp_vend)); + + priv->recv_packet_length[priv->recv_packets] = len; + ++priv->recv_packets; + + return 0; +} + +struct spl_test_net_priv { + struct unit_test_state *uts; + void *img; + size_t img_size; + u16 port; +}; + +/* Well known TFTP port # */ +#define TFTP_PORT 69 +/* Transaction ID, chosen at random */ +#define TFTP_TID 21313 + +/* + * TFTP operations. + */ +#define TFTP_RRQ 1 +#define TFTP_DATA 3 +#define TFTP_ACK 4 + +/* default TFTP block size */ +#define TFTP_BLOCK_SIZE 512 + +struct tftp_hdr { + u16 opcode; + u16 block; +}; + +#define TFTP_HDR_SIZE sizeof(struct tftp_hdr) + +/* + * sandbox_eth_tftp_req_to_reply() + * + * Check if a TFTP request was sent. If so, inject a reply. We don't support + * options, and we don't check for rollover, so we are limited files of less + * than 32M. + * + * returns 0 if injected, -EAGAIN if not + */ +static int sandbox_eth_tftp_req_to_reply(struct udevice *dev, void *packet, + unsigned int len) +{ + struct eth_sandbox_priv *priv = dev_get_priv(dev); + struct spl_test_net_priv *test_priv = priv->priv; + struct ethernet_hdr *eth = packet; + struct ip_udp_hdr *ip; + struct tftp_hdr *tftp; + struct ethernet_hdr *eth_recv; + struct ip_udp_hdr *ipr; + struct tftp_hdr *tftpr; + size_t size; + u16 block; + + if (ntohs(eth->et_protlen) != PROT_IP) + return -EAGAIN; + + ip = packet + ETHER_HDR_SIZE; + if (ip->ip_p != IPPROTO_UDP) + return -EAGAIN; + + if (ntohs(ip->udp_dst) == TFTP_PORT) { + tftp = (void *)ip + IP_UDP_HDR_SIZE; + if (htons(tftp->opcode) != TFTP_RRQ) + return -EAGAIN; + + block = 0; + } else if (ntohs(ip->udp_dst) == TFTP_TID) { + tftp = (void *)ip + IP_UDP_HDR_SIZE; + if (htons(tftp->opcode) != TFTP_ACK) + return -EAGAIN; + + block = htons(tftp->block); + } else { + return -EAGAIN; + } + + if (block * TFTP_BLOCK_SIZE > test_priv->img_size) + return 0; + + size = min(test_priv->img_size - block * TFTP_BLOCK_SIZE, + (size_t)TFTP_BLOCK_SIZE); + + /* Don't allow the buffer to overrun */ + if (priv->recv_packets >= PKTBUFSRX) + return 0; + + /* reply to the request */ + eth_recv = (void *)priv->recv_packet_buffer[priv->recv_packets]; + memcpy(eth_recv->et_dest, eth->et_src, ARP_HLEN); + memcpy(eth_recv->et_src, priv->fake_host_hwaddr, ARP_HLEN); + eth_recv->et_protlen = htons(PROT_IP); + + ipr = (void *)eth_recv + ETHER_HDR_SIZE; + ipr->ip_hl_v = 0x45; + ipr->ip_len = htons(IP_UDP_HDR_SIZE + TFTP_HDR_SIZE + size); + ipr->ip_off = htons(IP_FLAGS_DFRAG); + ipr->ip_ttl = 255; + ipr->ip_p = IPPROTO_UDP; + ipr->ip_sum = 0; + net_copy_ip(&ipr->ip_dst, &ip->ip_src); + net_copy_ip(&ipr->ip_src, &ip->ip_dst); + ipr->ip_sum = compute_ip_checksum(ipr, IP_HDR_SIZE); + + ipr->udp_src = htons(TFTP_TID); + ipr->udp_dst = ip->udp_src; + ipr->udp_len = htons(UDP_HDR_SIZE + TFTP_HDR_SIZE + size); + ipr->udp_xsum = 0; + + tftpr = (void *)ipr + IP_UDP_HDR_SIZE; + tftpr->opcode = htons(TFTP_DATA); + tftpr->block = htons(block + 1); + memcpy((void *)tftpr + TFTP_HDR_SIZE, + test_priv->img + block * TFTP_BLOCK_SIZE, size); + + priv->recv_packet_length[priv->recv_packets] = + ETHER_HDR_SIZE + IP_UDP_HDR_SIZE + TFTP_HDR_SIZE + size; + ++priv->recv_packets; + + return 0; +} + +static int spl_net_handler(struct udevice *dev, void *packet, + unsigned int len) +{ + struct eth_sandbox_priv *priv = dev_get_priv(dev); + int old_packets = priv->recv_packets; + + priv->fake_host_ipaddr = string_to_ip("1.1.2.4"); + net_ip = string_to_ip("1.1.2.2"); + + sandbox_eth_arp_req_to_reply(dev, packet, len); + sandbox_eth_bootp_req_to_reply(dev, packet, len); + sandbox_eth_tftp_req_to_reply(dev, packet, len); + + if (old_packets == priv->recv_packets) + return 0; + + return 0; +} + +static int spl_test_net_write_image(struct unit_test_state *uts, void *img, + size_t img_size) +{ + struct spl_test_net_priv *test_priv = malloc(sizeof(*test_priv)); + + ut_assertnonnull(test_priv); + test_priv->uts = uts; + test_priv->img = img; + test_priv->img_size = img_size; + + sandbox_eth_set_tx_handler(0, spl_net_handler); + sandbox_eth_set_priv(0, test_priv); + return 0; +} + +static int spl_test_net(struct unit_test_state *uts, const char *test_name, + enum spl_test_image type) +{ + struct eth_sandbox_priv *priv; + struct udevice *dev; + int ret; + + net_server_ip = string_to_ip("1.1.2.4"); + ret = do_spl_test_load(uts, test_name, type, + SPL_LOAD_IMAGE_GET(0, BOOT_DEVICE_CPGMAC, + spl_net_load_image_cpgmac), + spl_test_net_write_image); + + sandbox_eth_set_tx_handler(0, NULL); + ut_assertok(uclass_get_device(UCLASS_ETH, 0, &dev)); + priv = dev_get_priv(dev); + free(priv->priv); + return ret; +} +SPL_IMG_TEST(spl_test_net, LEGACY, DM_FLAGS); +SPL_IMG_TEST(spl_test_net, FIT_INTERNAL, DM_FLAGS); +SPL_IMG_TEST(spl_test_net, FIT_EXTERNAL, DM_FLAGS); |