diff options
Diffstat (limited to 'test/image/spl_load_net.c')
-rw-r--r-- | test/image/spl_load_net.c | 252 |
1 files changed, 252 insertions, 0 deletions
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); |