From 492ff73de699a40d9eec22b9afe631061b8baef0 Mon Sep 17 00:00:00 2001 From: Andrew Goodbody Date: Fri, 5 Dec 2025 09:46:48 +0000 Subject: [PATCH 01/13] net:lwip: Add debug line to net-lwip When debugging the LWIP NFS implementation this debug line helped to show the cause of an error. This could be useful to someone in the future. Signed-off-by: Andrew Goodbody Reviewed-by: Jerome Forissier --- net/lwip/net-lwip.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/lwip/net-lwip.c b/net/lwip/net-lwip.c index 8741f65fe12..f70857204b2 100644 --- a/net/lwip/net-lwip.c +++ b/net/lwip/net-lwip.c @@ -297,6 +297,7 @@ static struct pbuf *alloc_pbuf_and_copy(uchar *data, int len) /* We allocate a pbuf chain of pbufs from the pool. */ p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); if (!p) { + debug("Failed to allocate pbuf !!!!!\n"); LINK_STATS_INC(link.memerr); LINK_STATS_INC(link.drop); return NULL; From 60c228c07734a6f3d670c89d2db2e3addd8a73a8 Mon Sep 17 00:00:00 2001 From: Andrew Goodbody Date: Fri, 12 Dec 2025 11:32:24 +0000 Subject: [PATCH 02/13] net: move net_state to net-common Move the net_state variable into common code so that it can be used by either the legacy network code or lwIP. This is needed for porting across the NFS support code for use with lwIP. Signed-off-by: Andrew Goodbody Reviewed-by: Jerome Forissier --- include/net-common.h | 17 +++++++++++++++++ include/net-legacy.h | 17 ----------------- net/net-common.c | 3 +++ net/net.c | 2 -- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/include/net-common.h b/include/net-common.h index f5cff3e7c0c..d7a0f7dff7e 100644 --- a/include/net-common.h +++ b/include/net-common.h @@ -13,6 +13,7 @@ #include #define DEBUG_NET_PKT_TRACE 0 /* Trace all packet data */ +#define DEBUG_INT_STATE 0 /* Internal network state changes */ /* * The number of receive packet buffers, and the required packet buffer @@ -114,6 +115,22 @@ struct ip_udp_hdr { #define RINGSZ 4 #define RINGSZ_LOG2 2 +/* Network loop state */ +enum net_loop_state { + NETLOOP_CONTINUE, + NETLOOP_RESTART, + NETLOOP_SUCCESS, + NETLOOP_FAIL +}; + +extern enum net_loop_state net_state; + +static inline void net_set_state(enum net_loop_state state) +{ + debug_cond(DEBUG_INT_STATE, "--- NetState set to %d\n", state); + net_state = state; +} + extern int net_restart_wrap; /* Tried all network devices */ extern uchar *net_rx_packets[PKTBUFSRX]; /* Receive packets */ extern const u8 net_bcast_ethaddr[ARP_HLEN]; /* Ethernet broadcast address */ diff --git a/include/net-legacy.h b/include/net-legacy.h index 9564e97d238..0a10121b0cf 100644 --- a/include/net-legacy.h +++ b/include/net-legacy.h @@ -25,7 +25,6 @@ struct udevice; #define DEBUG_LL_STATE 0 /* Link local state machine changes */ #define DEBUG_DEV_PKT 0 /* Packets or info directed to the device */ #define DEBUG_NET_PKT 0 /* Packets on info on the network at large */ -#define DEBUG_INT_STATE 0 /* Internal network state changes */ /* ARP hardware address length */ #define ARP_HLEN 6 @@ -369,22 +368,6 @@ bool arp_is_waiting(void); /* Waiting for ARP reply? */ void net_set_icmp_handler(rxhand_icmp_f *f); /* Set ICMP RX handler */ void net_set_timeout_handler(ulong t, thand_f *f);/* Set timeout handler */ -/* Network loop state */ -enum net_loop_state { - NETLOOP_CONTINUE, - NETLOOP_RESTART, - NETLOOP_SUCCESS, - NETLOOP_FAIL -}; - -extern enum net_loop_state net_state; - -static inline void net_set_state(enum net_loop_state state) -{ - debug_cond(DEBUG_INT_STATE, "--- NetState set to %d\n", state); - net_state = state; -} - /* * net_get_async_tx_pkt_buf - Get a packet buffer that is not in use for * sending an asynchronous reply diff --git a/net/net-common.c b/net/net-common.c index c68e19fc03e..ec1e179f7d9 100644 --- a/net/net-common.c +++ b/net/net-common.c @@ -6,6 +6,9 @@ #include #include +/* Network loop state */ +enum net_loop_state net_state; + void copy_filename(char *dst, const char *src, int size) { if (src && *src && (*src == '"')) { diff --git a/net/net.c b/net/net.c index 44ce0ba4011..096f01427ba 100644 --- a/net/net.c +++ b/net/net.c @@ -157,8 +157,6 @@ const u8 net_null_ethaddr[6]; #if defined(CONFIG_API) || defined(CONFIG_EFI_LOADER) void (*push_packet)(void *, int len) = 0; #endif -/* Network loop state */ -enum net_loop_state net_state; /* Tried all network devices */ int net_restart_wrap; /* Network loop restarted */ From 3938ae6457193cd3b0abb8b74957a388a6b3e639 Mon Sep 17 00:00:00 2001 From: Andrew Goodbody Date: Fri, 12 Dec 2025 11:32:25 +0000 Subject: [PATCH 03/13] net: Move some variables to net-common files Make some variables available to be used by either the legacy network code or lwIP by moving them into the net-common files. This also allowed removing a small number of duplicated variables from the lwIP code. Signed-off-by: Andrew Goodbody Reviewed-by: Jerome Forissier --- include/net-common.h | 12 ++++++++++-- include/net-legacy.h | 5 ----- net/lwip/net-lwip.c | 5 ----- net/net-common.c | 9 +++++++++ net/net.c | 10 ---------- 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/include/net-common.h b/include/net-common.h index d7a0f7dff7e..f293b21bc0b 100644 --- a/include/net-common.h +++ b/include/net-common.h @@ -132,13 +132,21 @@ static inline void net_set_state(enum net_loop_state state) } extern int net_restart_wrap; /* Tried all network devices */ -extern uchar *net_rx_packets[PKTBUFSRX]; /* Receive packets */ +extern uchar *net_rx_packets[PKTBUFSRX]; /* Receive packets */ extern const u8 net_bcast_ethaddr[ARP_HLEN]; /* Ethernet broadcast address */ -extern char net_boot_file_name[1024];/* Boot File name */ extern struct in_addr net_ip; /* Our IP addr (0 = unknown) */ /* Indicates whether the pxe path prefix / config file was specified in dhcp option */ extern char *pxelinux_configfile; +/* Our IP addr (0 = unknown) */ +extern struct in_addr net_ip; +/* Boot File name */ +extern char net_boot_file_name[1024]; +/* The actual transferred size of the bootfile (in bytes) */ +extern u32 net_boot_file_size; +/* Boot file size in blocks as reported by the DHCP server */ +extern u32 net_boot_file_expected_size_in_blocks; + /** * compute_ip_checksum() - Compute IP checksum * diff --git a/include/net-legacy.h b/include/net-legacy.h index 0a10121b0cf..d489c2480cd 100644 --- a/include/net-legacy.h +++ b/include/net-legacy.h @@ -289,7 +289,6 @@ extern u8 net_ethaddr[ARP_HLEN]; /* Our ethernet address */ extern u8 net_server_ethaddr[ARP_HLEN]; /* Boot server enet address */ extern struct in_addr net_server_ip; /* Server IP addr (0 = unknown) */ extern uchar *net_tx_packet; /* THE transmit packet */ -extern uchar *net_rx_packets[PKTBUFSRX]; /* Receive packets */ extern uchar *net_rx_packet; /* Current receive packet */ extern int net_rx_packet_len; /* Current rx packet length */ extern const u8 net_null_ethaddr[ARP_HLEN]; @@ -309,10 +308,6 @@ enum proto_t { /* Indicates whether the file name was specified on the command line */ extern bool net_boot_file_name_explicit; -/* The actual transferred size of the bootfile (in bytes) */ -extern u32 net_boot_file_size; -/* Boot file size in blocks as reported by the DHCP server */ -extern u32 net_boot_file_expected_size_in_blocks; #if defined(CONFIG_DNS) extern char *net_dns_resolve; /* The host to resolve */ diff --git a/net/lwip/net-lwip.c b/net/lwip/net-lwip.c index f70857204b2..c325e923d20 100644 --- a/net/lwip/net-lwip.c +++ b/net/lwip/net-lwip.c @@ -33,13 +33,8 @@ static int net_restarted; int net_restart_wrap; static uchar net_pkt_buf[(PKTBUFSRX) * PKTSIZE_ALIGN + PKTALIGN] __aligned(PKTALIGN); -uchar *net_rx_packets[PKTBUFSRX]; -uchar *net_rx_packet; const u8 net_bcast_ethaddr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; char *pxelinux_configfile; -/* Our IP addr (0 = unknown) */ -struct in_addr net_ip; -char net_boot_file_name[1024]; static err_t net_lwip_tx(struct netif *netif, struct pbuf *p) { diff --git a/net/net-common.c b/net/net-common.c index ec1e179f7d9..f21a80357f6 100644 --- a/net/net-common.c +++ b/net/net-common.c @@ -8,6 +8,15 @@ /* Network loop state */ enum net_loop_state net_state; +/* Our IP addr (0 = unknown) */ +struct in_addr net_ip; +/* Boot File name */ +char net_boot_file_name[1024]; +/* The actual transferred size of the bootfile (in bytes) */ +u32 net_boot_file_size; +/* Boot file size in blocks as reported by the DHCP server */ +u32 net_boot_file_expected_size_in_blocks; +uchar *net_rx_packets[PKTBUFSRX]; void copy_filename(char *dst, const char *src, int size) { diff --git a/net/net.c b/net/net.c index 096f01427ba..8a8160e633f 100644 --- a/net/net.c +++ b/net/net.c @@ -141,8 +141,6 @@ char *pxelinux_configfile; u8 net_ethaddr[6]; /* Boot server enet address */ u8 net_server_ethaddr[6]; -/* Our IP addr (0 = unknown) */ -struct in_addr net_ip; /* Server IP addr (0 = unknown) */ struct in_addr net_server_ip; /* Current receive packet */ @@ -170,18 +168,10 @@ ushort net_our_vlan = 0xFFFF; /* ditto */ ushort net_native_vlan = 0xFFFF; -/* Boot File name */ -char net_boot_file_name[1024]; /* Indicates whether the file name was specified on the command line */ bool net_boot_file_name_explicit; -/* The actual transferred size of the bootfile (in bytes) */ -u32 net_boot_file_size; -/* Boot file size in blocks as reported by the DHCP server */ -u32 net_boot_file_expected_size_in_blocks; static uchar net_pkt_buf[(PKTBUFSRX+1) * PKTSIZE_ALIGN + PKTALIGN]; -/* Receive packets */ -uchar *net_rx_packets[PKTBUFSRX]; /* Current UDP RX packet handler */ static rxhand_f *udp_packet_handler; /* Current ARP RX packet handler */ From 046f553fe872317707055dd308a5f0e4baec910a Mon Sep 17 00:00:00 2001 From: Andrew Goodbody Date: Fri, 12 Dec 2025 11:32:26 +0000 Subject: [PATCH 04/13] net: nfs: Add licence header Add the same GPL2+ licence header to the NFS code as appears on other NFS related files. Acked-by: Jerome Forissier Signed-off-by: Andrew Goodbody --- net/nfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/nfs.c b/net/nfs.c index 537d4c62de2..7bae7c78642 100644 --- a/net/nfs.c +++ b/net/nfs.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * NFS support driver - based on etherboot and U-BOOT's tftp.c * From 3bc1197e3d7c54bcbd2dfb481cb4a9ae47f3e8d3 Mon Sep 17 00:00:00 2001 From: Andrew Goodbody Date: Fri, 12 Dec 2025 11:32:27 +0000 Subject: [PATCH 05/13] net: nfs: Move most NFS code to common files Move most of the NFS code into common files so that it can be used by an lwIP port of NFS. Acked-by: Jerome Forissier Signed-off-by: Andrew Goodbody --- net/Makefile | 1 + net/nfs-common.c | 861 +++++++++++++++++++++++++++++++++++++++++++++++ net/nfs-common.h | 123 +++++++ net/nfs.c | 848 +--------------------------------------------- net/nfs.h | 59 ---- 5 files changed, 998 insertions(+), 894 deletions(-) create mode 100644 net/nfs-common.c create mode 100644 net/nfs-common.h diff --git a/net/Makefile b/net/Makefile index 468820186cf..3a32bc8b0e7 100644 --- a/net/Makefile +++ b/net/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_$(PHASE_)DM_ETH) += eth-uclass.o obj-$(CONFIG_$(PHASE_)BOOTDEV_ETH) += eth_bootdev.o obj-$(CONFIG_DM_MDIO) += mdio-uclass.o obj-$(CONFIG_DM_MDIO_MUX) += mdio-mux-uclass.o +obj-$(CONFIG_CMD_NFS) += nfs-common.o obj-$(CONFIG_$(PHASE_)DM_ETH) += eth_common.o obj-y += net-common.o endif diff --git a/net/nfs-common.c b/net/nfs-common.c new file mode 100644 index 00000000000..a6a70677bd2 --- /dev/null +++ b/net/nfs-common.c @@ -0,0 +1,861 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NFS support driver - based on etherboot and U-BOOT's tftp.c + * + * Masami Komiya 2004 + * + */ + +/* NOTE: the NFS code is heavily inspired by the NetBSD netboot code (read: + * large portions are copied verbatim) as distributed in OSKit 0.97. A few + * changes were necessary to adapt the code to Etherboot and to fix several + * inconsistencies. Also the RPC message preparation is done "by hand" to + * avoid adding netsprintf() which I find hard to understand and use. + */ + +/* NOTE 2: Etherboot does not care about things beyond the kernel image, so + * it loads the kernel image off the boot server (ARP_SERVER) and does not + * access the client root disk (root-path in dhcpd.conf), which would use + * ARP_ROOTSERVER. The root disk is something the operating system we are + * about to load needs to use. This is different from the OSKit 0.97 logic. + */ + +/* NOTE 3: Symlink handling introduced by Anselm M Hoffmeister, 2003-July-14 + * If a symlink is encountered, it is followed as far as possible (recursion + * possible, maximum 16 steps). There is no clearing of ".."'s inside the + * path, so please DON'T DO THAT. thx. + */ + +/* NOTE 4: NFSv3 support added by Guillaume GARDET, 2016-June-20. + * NFSv2 is still used by default. But if server does not support NFSv2, then + * NFSv3 is used, if available on NFS server. + */ + +/* NOTE 5: NFSv1 support added by Christian Gmeiner, Thomas Rienoessl, + * September 27, 2018. As of now, NFSv3 is the default choice. If the server + * does not support NFSv3, we fall back to versions 2 or 1. + */ + +#ifdef CONFIG_SYS_DIRECT_FLASH_NFS +#include +#endif +#include +#include +#include +#include +#include "nfs.h" +#include "nfs-common.h" + +static int fs_mounted; +static char dirfh[NFS3_FHSIZE]; /* NFSv2 / NFSv3 file handle of directory */ +static unsigned int dirfh3_length; /* (variable) length of dirfh when NFSv3 */ +static char filefh[NFS3_FHSIZE]; /* NFSv2 / NFSv3 file handle */ +static unsigned int filefh3_length; /* (variable) length of filefh when NFSv3 */ + +enum net_loop_state nfs_download_state; +char *nfs_filename; +char *nfs_path; +char nfs_path_buff[2048]; +struct in_addr nfs_server_ip; +int nfs_server_mount_port; +int nfs_server_port; +int nfs_our_port; +int nfs_state; +int nfs_timeout_count; +unsigned long rpc_id; +int nfs_offset = -1; +int nfs_len; + +const ulong nfs_timeout = CONFIG_NFS_TIMEOUT; + +enum nfs_version choosen_nfs_version = NFS_V3; + +static inline int store_block(uchar *src, unsigned int offset, unsigned int len) +{ + ulong newsize = offset + len; +#ifdef CONFIG_SYS_DIRECT_FLASH_NFS + int i, rc = 0; + + for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) { + /* start address in flash? */ + if (image_load_addr + offset >= flash_info[i].start[0]) { + rc = 1; + break; + } + } + + if (rc) { /* Flash is destination for this packet */ + rc = flash_write((uchar *)src, (ulong)image_load_addr + offset, + len); + if (rc) { + flash_perror(rc); + return -1; + } + } else +#endif /* CONFIG_SYS_DIRECT_FLASH_NFS */ + { + void *ptr = map_sysmem(image_load_addr + offset, len); + + memcpy(ptr, src, len); + unmap_sysmem(ptr); + } + + if (net_boot_file_size < (offset + len)) + net_boot_file_size = newsize; + return 0; +} + +char *nfs_basename(char *path) +{ + char *fname; + + fname = path + strlen(path) - 1; + while (fname >= path) { + if (*fname == '/') { + fname++; + break; + } + fname--; + } + return fname; +} + +char *nfs_dirname(char *path) +{ + char *fname; + + fname = nfs_basename(path); + --fname; + *fname = '\0'; + return path; +} + +/************************************************************************** + * RPC_ADD_CREDENTIALS - Add RPC authentication/verifier entries + ************************************************************************** + */ +static uint32_t *rpc_add_credentials(uint32_t *p) +{ + /* Here's the executive summary on authentication requirements of the + * various NFS server implementations: Linux accepts both AUTH_NONE + * and AUTH_UNIX authentication (also accepts an empty hostname field + * in the AUTH_UNIX scheme). *BSD refuses AUTH_NONE, but accepts + * AUTH_UNIX (also accepts an empty hostname field in the AUTH_UNIX + * scheme). To be safe, use AUTH_UNIX and pass the hostname if we have + * it (if the BOOTP/DHCP reply didn't give one, just use an empty + * hostname). + */ + + /* Provide an AUTH_UNIX credential. */ + *p++ = htonl(1); /* AUTH_UNIX */ + *p++ = htonl(20); /* auth length */ + *p++ = 0; /* stamp */ + *p++ = 0; /* hostname string */ + *p++ = 0; /* uid */ + *p++ = 0; /* gid */ + *p++ = 0; /* auxiliary gid list */ + + /* Provide an AUTH_NONE verifier. */ + *p++ = 0; /* AUTH_NONE */ + *p++ = 0; /* auth length */ + + return p; +} + +void rpc_req_common(int rpc_prog, int rpc_proc, uint32_t *data, int datalen, + uchar *txbuff, int *pktlen, int *sport) +{ + struct rpc_t rpc_pkt; + unsigned long id; + u32 *p; + + id = ++rpc_id; + rpc_pkt.u.call.id = htonl(id); + rpc_pkt.u.call.type = htonl(MSG_CALL); + rpc_pkt.u.call.rpcvers = htonl(2); /* use RPC version 2 */ + rpc_pkt.u.call.prog = htonl(rpc_prog); + switch (rpc_prog) { + case PROG_NFS: + switch (choosen_nfs_version) { + case NFS_V1: + case NFS_V2: + rpc_pkt.u.call.vers = htonl(2); + break; + + case NFS_V3: + rpc_pkt.u.call.vers = htonl(3); + break; + + case NFS_UNKOWN: + /* nothing to do */ + break; + } + break; + case PROG_MOUNT: + switch (choosen_nfs_version) { + case NFS_V1: + rpc_pkt.u.call.vers = htonl(1); + break; + + case NFS_V2: + rpc_pkt.u.call.vers = htonl(2); + break; + + case NFS_V3: + rpc_pkt.u.call.vers = htonl(3); + break; + + case NFS_UNKOWN: + /* nothing to do */ + break; + } + break; + case PROG_PORTMAP: + default: + rpc_pkt.u.call.vers = htonl(2); /* portmapper is version 2 */ + } + rpc_pkt.u.call.proc = htonl(rpc_proc); + p = rpc_pkt.u.call.data; + + if (datalen) + memcpy(p, data, datalen * sizeof(uint32_t)); + + *pktlen = (char *)p + datalen * sizeof(uint32_t) - (char *)&rpc_pkt; + + memcpy(txbuff, &rpc_pkt.u.data[0], *pktlen); + + if (rpc_prog == PROG_PORTMAP) + *sport = SUNRPC_PORT; + else if (rpc_prog == PROG_MOUNT) + *sport = nfs_server_mount_port; + else + *sport = nfs_server_port; +} + +/************************************************************************** + * RPC_LOOKUP - Lookup RPC Port numbers + ************************************************************************** + */ +static void rpc_lookup_req(int prog, int ver) +{ + u32 data[16]; + + data[0] = 0; data[1] = 0; /* auth credential */ + data[2] = 0; data[3] = 0; /* auth verifier */ + data[4] = htonl(prog); + data[5] = htonl(ver); + data[6] = htonl(17); /* IP_UDP */ + data[7] = 0; + rpc_req(PROG_PORTMAP, PORTMAP_GETPORT, data, 8); +} + +/************************************************************************** + * NFS_MOUNT - Mount an NFS Filesystem + ************************************************************************** + */ +static void nfs_mount_req(char *path) +{ + u32 data[1024]; + u32 *p; + int len; + int pathlen; + + pathlen = strlen(path); + + p = &data[0]; + p = rpc_add_credentials(p); + + *p++ = htonl(pathlen); + if (pathlen & 3) + *(p + pathlen / 4) = 0; + memcpy(p, path, pathlen); + p += (pathlen + 3) / 4; + + len = (uint32_t *)p - (uint32_t *)&data[0]; + + rpc_req(PROG_MOUNT, MOUNT_ADDENTRY, data, len); +} + +/************************************************************************** + * NFS_UMOUNTALL - Unmount all our NFS Filesystems on the Server + ************************************************************************** + */ +static void nfs_umountall_req(void) +{ + u32 data[1024]; + u32 *p; + int len; + + if ((nfs_server_mount_port == -1) || !fs_mounted) + /* Nothing mounted, nothing to umount */ + return; + + p = &data[0]; + p = rpc_add_credentials(p); + + len = (uint32_t *)p - (uint32_t *)&data[0]; + + rpc_req(PROG_MOUNT, MOUNT_UMOUNTALL, data, len); +} + +/*************************************************************************** + * NFS_READLINK (AH 2003-07-14) + * This procedure is called when read of the first block fails - + * this probably happens when it's a directory or a symlink + * In case of successful readlink(), the dirname is manipulated, + * so that inside the nfs() function a recursion can be done. + *************************************************************************** + */ +static void nfs_readlink_req(void) +{ + u32 data[1024]; + u32 *p; + int len; + + p = &data[0]; + p = rpc_add_credentials(p); + + if (choosen_nfs_version != NFS_V3) { + memcpy(p, filefh, NFS_FHSIZE); + p += (NFS_FHSIZE / 4); + } else { /* NFS_V3 */ + *p++ = htonl(filefh3_length); + memcpy(p, filefh, filefh3_length); + p += (filefh3_length / 4); + } + + len = (uint32_t *)p - (uint32_t *)&data[0]; + + rpc_req(PROG_NFS, NFS_READLINK, data, len); +} + +/************************************************************************** + * NFS_LOOKUP - Lookup Pathname + ************************************************************************** + */ +static void nfs_lookup_req(char *fname) +{ + u32 data[1024]; + u32 *p; + int len; + int fnamelen; + + fnamelen = strlen(fname); + + p = &data[0]; + p = rpc_add_credentials(p); + + if (choosen_nfs_version != NFS_V3) { + memcpy(p, dirfh, NFS_FHSIZE); + p += (NFS_FHSIZE / 4); + *p++ = htonl(fnamelen); + if (fnamelen & 3) + *(p + fnamelen / 4) = 0; + memcpy(p, fname, fnamelen); + p += (fnamelen + 3) / 4; + + len = (uint32_t *)p - (uint32_t *)&data[0]; + + rpc_req(PROG_NFS, NFS_LOOKUP, data, len); + } else { /* NFS_V3 */ + *p++ = htonl(dirfh3_length); /* Dir handle length */ + memcpy(p, dirfh, dirfh3_length); + p += (dirfh3_length / 4); + *p++ = htonl(fnamelen); + if (fnamelen & 3) + *(p + fnamelen / 4) = 0; + memcpy(p, fname, fnamelen); + p += (fnamelen + 3) / 4; + + len = (uint32_t *)p - (uint32_t *)&data[0]; + + rpc_req(PROG_NFS, NFS3PROC_LOOKUP, data, len); + } +} + +/************************************************************************** + * NFS_READ - Read File on NFS Server + ************************************************************************** + */ +static void nfs_read_req(int offset, int readlen) +{ + u32 data[1024]; + u32 *p; + int len; + + p = &data[0]; + p = rpc_add_credentials(p); + + if (choosen_nfs_version != NFS_V3) { + memcpy(p, filefh, NFS_FHSIZE); + p += (NFS_FHSIZE / 4); + *p++ = htonl(offset); + *p++ = htonl(readlen); + *p++ = 0; + } else { /* NFS_V3 */ + *p++ = htonl(filefh3_length); + memcpy(p, filefh, filefh3_length); + p += (filefh3_length / 4); + *p++ = htonl(0); /* offset is 64-bit long, so fill with 0 */ + *p++ = htonl(offset); + *p++ = htonl(readlen); + *p++ = 0; + } + + len = (uint32_t *)p - (uint32_t *)&data[0]; + + rpc_req(PROG_NFS, NFS_READ, data, len); +} + +/************************************************************************** + * RPC request dispatcher + ************************************************************************** + */ +void nfs_send(void) +{ + switch (nfs_state) { + case STATE_PRCLOOKUP_PROG_MOUNT_REQ: + if (choosen_nfs_version != NFS_V3) + rpc_lookup_req(PROG_MOUNT, 1); + else /* NFS_V3 */ + rpc_lookup_req(PROG_MOUNT, 3); + break; + case STATE_PRCLOOKUP_PROG_NFS_REQ: + if (choosen_nfs_version != NFS_V3) + rpc_lookup_req(PROG_NFS, 2); + else /* NFS_V3 */ + rpc_lookup_req(PROG_NFS, 3); + break; + case STATE_MOUNT_REQ: + nfs_mount_req(nfs_path); + break; + case STATE_UMOUNT_REQ: + nfs_umountall_req(); + break; + case STATE_LOOKUP_REQ: + nfs_lookup_req(nfs_filename); + break; + case STATE_READ_REQ: + nfs_read_req(nfs_offset, nfs_len); + break; + case STATE_READLINK_REQ: + nfs_readlink_req(); + break; + } +} + +/************************************************************************** + * Handlers for the reply from server + ************************************************************************** + */ + +static int rpc_handle_error(struct rpc_t *rpc_pkt) +{ + if (rpc_pkt->u.reply.rstatus || + rpc_pkt->u.reply.verifier || + rpc_pkt->u.reply.astatus || + rpc_pkt->u.reply.data[0]) { + switch (ntohl(rpc_pkt->u.reply.astatus)) { + case NFS_RPC_SUCCESS: /* Not an error */ + break; + case NFS_RPC_PROG_MISMATCH: { + /* Remote can't support NFS version */ + const int min = ntohl(rpc_pkt->u.reply.data[0]); + const int max = ntohl(rpc_pkt->u.reply.data[1]); + + if (max < NFS_V1 || max > NFS_V3 || min > NFS_V3) { + puts("*** ERROR: NFS version not supported"); + debug(": Requested: V%d, accepted: min V%d - max V%d\n", + choosen_nfs_version, + ntohl(rpc_pkt->u.reply.data[0]), + ntohl(rpc_pkt->u.reply.data[1])); + puts("\n"); + choosen_nfs_version = NFS_UNKOWN; + break; + } + + debug("*** Warning: NFS version not supported: Requested: V%d, accepted: min V%d - max V%d\n", + choosen_nfs_version, + ntohl(rpc_pkt->u.reply.data[0]), + ntohl(rpc_pkt->u.reply.data[1])); + debug("Will retry with NFSv%d\n", min); + choosen_nfs_version = min; + return -NFS_RPC_PROG_MISMATCH; + } + case NFS_RPC_PROG_UNAVAIL: + case NFS_RPC_PROC_UNAVAIL: + case NFS_RPC_GARBAGE_ARGS: + case NFS_RPC_SYSTEM_ERR: + default: /* Unknown error on 'accept state' flag */ + debug("*** ERROR: accept state error (%d)\n", + ntohl(rpc_pkt->u.reply.astatus)); + break; + } + return -1; + } + + return 0; +} + +static int rpc_lookup_reply(int prog, uchar *pkt, unsigned int len) +{ + struct rpc_t rpc_pkt; + + memcpy(&rpc_pkt.u.data[0], pkt, len); + + if (ntohl(rpc_pkt.u.reply.id) > rpc_id) + return -NFS_RPC_ERR; + else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) + return -NFS_RPC_DROP; + + if (rpc_pkt.u.reply.rstatus || + rpc_pkt.u.reply.verifier || + rpc_pkt.u.reply.astatus) + return -1; + + switch (prog) { + case PROG_MOUNT: + nfs_server_mount_port = ntohl(rpc_pkt.u.reply.data[0]); + break; + case PROG_NFS: + nfs_server_port = ntohl(rpc_pkt.u.reply.data[0]); + break; + } + + return 0; +} + +static int nfs_mount_reply(uchar *pkt, unsigned int len) +{ + struct rpc_t rpc_pkt; + int ret; + + memcpy(&rpc_pkt.u.data[0], pkt, len); + + if (ntohl(rpc_pkt.u.reply.id) > rpc_id) + return -NFS_RPC_ERR; + else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) + return -NFS_RPC_DROP; + + ret = rpc_handle_error(&rpc_pkt); + if (ret) + return ret; + + fs_mounted = 1; + /* NFSv2 and NFSv3 use same structure */ + if (choosen_nfs_version != NFS_V3) { + memcpy(dirfh, rpc_pkt.u.reply.data + 1, NFS_FHSIZE); + } else { + dirfh3_length = ntohl(rpc_pkt.u.reply.data[1]); + if (dirfh3_length > NFS3_FHSIZE) + dirfh3_length = NFS3_FHSIZE; + memcpy(dirfh, rpc_pkt.u.reply.data + 2, dirfh3_length); + } + + return 0; +} + +static int nfs_umountall_reply(uchar *pkt, unsigned int len) +{ + struct rpc_t rpc_pkt; + + memcpy(&rpc_pkt.u.data[0], pkt, len); + + if (ntohl(rpc_pkt.u.reply.id) > rpc_id) + return -NFS_RPC_ERR; + else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) + return -NFS_RPC_DROP; + + if (rpc_pkt.u.reply.rstatus || + rpc_pkt.u.reply.verifier || + rpc_pkt.u.reply.astatus) + return -1; + + fs_mounted = 0; + memset(dirfh, 0, sizeof(dirfh)); + + return 0; +} + +static int nfs_lookup_reply(uchar *pkt, unsigned int len) +{ + struct rpc_t rpc_pkt; + int ret; + + memcpy(&rpc_pkt.u.data[0], pkt, len); + + if (ntohl(rpc_pkt.u.reply.id) > rpc_id) + return -NFS_RPC_ERR; + else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) + return -NFS_RPC_DROP; + + ret = rpc_handle_error(&rpc_pkt); + if (ret) + return ret; + + if (choosen_nfs_version != NFS_V3) { + if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt + NFS_FHSIZE) > len) + return -NFS_RPC_DROP; + memcpy(filefh, rpc_pkt.u.reply.data + 1, NFS_FHSIZE); + } else { /* NFS_V3 */ + filefh3_length = ntohl(rpc_pkt.u.reply.data[1]); + if (filefh3_length > NFS3_FHSIZE) + filefh3_length = NFS3_FHSIZE; + memcpy(filefh, rpc_pkt.u.reply.data + 2, filefh3_length); + } + + return 0; +} + +static int nfs3_get_attributes_offset(uint32_t *data) +{ + if (data[1]) { + /* 'attributes_follow' flag is TRUE, + * so we have attributes on 21 dwords + */ + /* Skip unused values : + * type; 32 bits value, + * mode; 32 bits value, + * nlink; 32 bits value, + * uid; 32 bits value, + * gid; 32 bits value, + * size; 64 bits value, + * used; 64 bits value, + * rdev; 64 bits value, + * fsid; 64 bits value, + * fileid; 64 bits value, + * atime; 64 bits value, + * mtime; 64 bits value, + * ctime; 64 bits value, + */ + return 22; + } + + /* 'attributes_follow' flag is FALSE, + * so we don't have any attributes + */ + return 1; +} + +static int nfs_readlink_reply(uchar *pkt, unsigned int len) +{ + struct rpc_t rpc_pkt; + int rlen; + int nfsv3_data_offset = 0; + + memcpy((unsigned char *)&rpc_pkt, pkt, len); + + if (ntohl(rpc_pkt.u.reply.id) > rpc_id) + return -NFS_RPC_ERR; + else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) + return -NFS_RPC_DROP; + + if (rpc_pkt.u.reply.rstatus || + rpc_pkt.u.reply.verifier || + rpc_pkt.u.reply.astatus || + rpc_pkt.u.reply.data[0]) + return -1; + + if (choosen_nfs_version == NFS_V3) { + nfsv3_data_offset = + nfs3_get_attributes_offset(rpc_pkt.u.reply.data); + } + + /* new path length */ + rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]); + + if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt + rlen) > len) + return -NFS_RPC_DROP; + + if (*((char *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset]) != '/') { + int pathlen; + + strcat(nfs_path, "/"); + pathlen = strlen(nfs_path); + memcpy(nfs_path + pathlen, + (uchar *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset], + rlen); + nfs_path[pathlen + rlen] = 0; + } else { + memcpy(nfs_path, + (uchar *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset], + rlen); + nfs_path[rlen] = 0; + } + return 0; +} + +static int nfs_read_reply(uchar *pkt, unsigned int len) +{ + struct rpc_t rpc_pkt; + int rlen; + uchar *data_ptr; + + memcpy(&rpc_pkt.u.data[0], pkt, sizeof(rpc_pkt.u.reply)); + + if (ntohl(rpc_pkt.u.reply.id) > rpc_id) + return -NFS_RPC_ERR; + else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) + return -NFS_RPC_DROP; + + if (rpc_pkt.u.reply.rstatus || + rpc_pkt.u.reply.verifier || + rpc_pkt.u.reply.astatus || + rpc_pkt.u.reply.data[0]) { + if (rpc_pkt.u.reply.rstatus) + return -9999; + if (rpc_pkt.u.reply.astatus) + return -9999; + return -ntohl(rpc_pkt.u.reply.data[0]); + } + + if (nfs_offset != 0 && !((nfs_offset) % + (NFS_READ_SIZE / 2 * 10 * HASHES_PER_LINE))) + puts("\n\t "); + if (!(nfs_offset % ((NFS_READ_SIZE / 2) * 10))) + putc('#'); + + if (choosen_nfs_version != NFS_V3) { + rlen = ntohl(rpc_pkt.u.reply.data[18]); + data_ptr = (uchar *)&rpc_pkt.u.reply.data[19]; + } else { /* NFS_V3 */ + int nfsv3_data_offset = + nfs3_get_attributes_offset(rpc_pkt.u.reply.data); + + /* count value */ + rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]); + /* Skip unused values : + * EOF: 32 bits value, + * data_size: 32 bits value, + */ + data_ptr = (uchar *) + &rpc_pkt.u.reply.data[4 + nfsv3_data_offset]; + } + + if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt + rlen) > len) + return -9999; + + if (store_block(data_ptr, nfs_offset, rlen)) + return -9999; + + return rlen; +} + +void nfs_pkt_recv(uchar *pkt, unsigned int len) +{ + int rlen; + int reply; + + switch (nfs_state) { + case STATE_PRCLOOKUP_PROG_MOUNT_REQ: + if (rpc_lookup_reply(PROG_MOUNT, pkt, len) == -NFS_RPC_DROP) + break; + nfs_state = STATE_PRCLOOKUP_PROG_NFS_REQ; + nfs_send(); + break; + + case STATE_PRCLOOKUP_PROG_NFS_REQ: + if (rpc_lookup_reply(PROG_NFS, pkt, len) == -NFS_RPC_DROP) + break; + nfs_state = STATE_MOUNT_REQ; + nfs_send(); + break; + + case STATE_MOUNT_REQ: + reply = nfs_mount_reply(pkt, len); + if (reply == -NFS_RPC_DROP) { + break; + } else if (reply == -NFS_RPC_ERR) { + puts("*** ERROR: Cannot mount\n"); + /* just to be sure... */ + nfs_state = STATE_UMOUNT_REQ; + nfs_send(); + } else if (reply == -NFS_RPC_PROG_MISMATCH && + choosen_nfs_version != NFS_UNKOWN) { + nfs_state = STATE_MOUNT_REQ; + nfs_send(); + } else { + nfs_state = STATE_LOOKUP_REQ; + nfs_send(); + } + break; + + case STATE_UMOUNT_REQ: + reply = nfs_umountall_reply(pkt, len); + if (reply == -NFS_RPC_DROP) { + break; + } else if (reply == -NFS_RPC_ERR) { + debug("*** ERROR: Cannot umount\n"); + net_set_state(NETLOOP_FAIL); + } else { + puts("\ndone\n"); + net_set_state(nfs_download_state); + } + break; + + case STATE_LOOKUP_REQ: + reply = nfs_lookup_reply(pkt, len); + if (reply == -NFS_RPC_DROP) { + break; + } else if (reply == -NFS_RPC_ERR) { + puts("*** ERROR: File lookup fail\n"); + nfs_state = STATE_UMOUNT_REQ; + nfs_send(); + } else if (reply == -NFS_RPC_PROG_MISMATCH && + choosen_nfs_version != NFS_UNKOWN) { + /* umount */ + nfs_state = STATE_UMOUNT_REQ; + nfs_send(); + /* And retry with another supported version */ + nfs_state = STATE_PRCLOOKUP_PROG_MOUNT_REQ; + nfs_send(); + } else { + nfs_state = STATE_READ_REQ; + nfs_offset = 0; + nfs_len = NFS_READ_SIZE; + nfs_send(); + } + break; + + case STATE_READLINK_REQ: + reply = nfs_readlink_reply(pkt, len); + if (reply == -NFS_RPC_DROP) { + break; + } else if (reply == -NFS_RPC_ERR) { + puts("*** ERROR: Symlink fail\n"); + nfs_state = STATE_UMOUNT_REQ; + nfs_send(); + } else { + debug("Symlink --> %s\n", nfs_path); + nfs_filename = nfs_basename(nfs_path); + nfs_path = nfs_dirname(nfs_path); + + nfs_state = STATE_MOUNT_REQ; + nfs_send(); + } + break; + + case STATE_READ_REQ: + rlen = nfs_read_reply(pkt, len); + if (rlen == -NFS_RPC_DROP) + break; + nfs_refresh_timeout(); + if (rlen > 0) { + nfs_offset += rlen; + nfs_send(); + } else if ((rlen == -NFSERR_ISDIR) || (rlen == -NFSERR_INVAL)) { + /* symbolic link */ + nfs_state = STATE_READLINK_REQ; + nfs_send(); + } else { + if (!rlen) + nfs_download_state = NETLOOP_SUCCESS; + if (rlen < 0) + debug("NFS READ error (%d)\n", rlen); + nfs_state = STATE_UMOUNT_REQ; + nfs_send(); + } + break; + } +} + diff --git a/net/nfs-common.h b/net/nfs-common.h new file mode 100644 index 00000000000..a19b98b7b1f --- /dev/null +++ b/net/nfs-common.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Masami Komiya 2004 + */ + +#ifndef __NFS_COMMON_H__ +#define __NFS_COMMON_H__ + +#define SUNRPC_PORT 111 + +#define PROG_PORTMAP 100000 +#define PROG_NFS 100003 +#define PROG_MOUNT 100005 + +#define MSG_CALL 0 +#define MSG_REPLY 1 + +#define HASHES_PER_LINE 65 /* Number of "loading" hashes per line */ +#define NFS_RETRY_COUNT 30 + +#define NFS_RPC_ERR 1 +#define NFS_RPC_DROP 124 + +#define NFSERR_PERM 1 +#define NFSERR_NOENT 2 +#define NFSERR_ACCES 13 +#define NFSERR_ISDIR 21 +#define NFSERR_INVAL 22 + +/* Values for Accept State flag on RPC answers (See: rfc1831) */ +enum rpc_accept_stat { + NFS_RPC_SUCCESS = 0, /* RPC executed successfully */ + NFS_RPC_PROG_UNAVAIL = 1, /* remote hasn't exported program */ + NFS_RPC_PROG_MISMATCH = 2, /* remote can't support version # */ + NFS_RPC_PROC_UNAVAIL = 3, /* program can't support procedure */ + NFS_RPC_GARBAGE_ARGS = 4, /* procedure can't decode params */ + NFS_RPC_SYSTEM_ERR = 5 /* errors like memory allocation failure */ +}; + +enum nfs_version { + NFS_UNKOWN = 0, + NFS_V1 = 1, + NFS_V2 = 2, + NFS_V3 = 3, +}; + +extern enum net_loop_state nfs_download_state; +extern char *nfs_filename; +extern char *nfs_path; +extern char nfs_path_buff[2048]; +extern struct in_addr nfs_server_ip; +extern int nfs_server_mount_port; +extern int nfs_server_port; +extern int nfs_our_port; +extern int nfs_timeout_count; +extern unsigned long rpc_id; +extern int nfs_offset; +extern int nfs_len; + +extern const ulong nfs_timeout; + +extern int nfs_state; +#define STATE_PRCLOOKUP_PROG_MOUNT_REQ 1 +#define STATE_PRCLOOKUP_PROG_NFS_REQ 2 +#define STATE_MOUNT_REQ 3 +#define STATE_UMOUNT_REQ 4 +#define STATE_LOOKUP_REQ 5 +#define STATE_READ_REQ 6 +#define STATE_READLINK_REQ 7 + +/* + * Block size used for NFS read accesses. A RPC reply packet (including all + * headers) must fit within a single Ethernet frame to avoid fragmentation. + * However, if CONFIG_IP_DEFRAG is set, a bigger value could be used. In any + * case, most NFS servers are optimized for a power of 2. + */ +#define NFS_READ_SIZE 1024 /* biggest power of two that fits Ether frame */ +#define NFS_MAX_ATTRS 26 + +struct rpc_t { + union { + u8 data[NFS_READ_SIZE + (6 + NFS_MAX_ATTRS) * + sizeof(uint32_t)]; + struct { + u32 id; + u32 type; + u32 rpcvers; + u32 prog; + u32 vers; + u32 proc; + u32 data[1]; + } call; + struct { + u32 id; + u32 type; + u32 rstatus; + u32 verifier; + u32 v2; + u32 astatus; + u32 data[NFS_READ_SIZE / sizeof(u32) + + NFS_MAX_ATTRS]; + } reply; + } u; +}; + +char *nfs_basename(char *path); +char *nfs_dirname(char *path); +void rpc_req_common(int rpc_prog, int rpc_proc, uint32_t *data, int datalen, + uchar *txbuff, int *pktlen, int *sport); +void nfs_send(void); +void nfs_pkt_recv(uchar *pkt, unsigned int len); + +extern enum nfs_version choosen_nfs_version; + +/* + * Implementation specific functions called from common code + */ +void rpc_req(int rpc_prog, int rpc_proc, uint32_t *data, int datalen); +void nfs_refresh_timeout(void); + +/**********************************************************************/ + +#endif /* __NFS_COMMON_H__ */ diff --git a/net/nfs.c b/net/nfs.c index 7bae7c78642..f5edc9b043a 100644 --- a/net/nfs.c +++ b/net/nfs.c @@ -31,745 +31,31 @@ * September 27, 2018. As of now, NFSv3 is the default choice. If the server * does not support NFSv3, we fall back to versions 2 or 1. */ -#include #include -#ifdef CONFIG_SYS_DIRECT_FLASH_NFS -#include -#endif #include #include #include #include -#include #include "nfs.h" -#include "bootp.h" +#include "nfs-common.h" #include -#define HASHES_PER_LINE 65 /* Number of "loading" hashes per line */ -#define NFS_RETRY_COUNT 30 - -#define NFS_RPC_ERR 1 -#define NFS_RPC_DROP 124 - -static int fs_mounted; -static unsigned long rpc_id; -static int nfs_offset = -1; -static int nfs_len; -static const ulong nfs_timeout = CONFIG_NFS_TIMEOUT; - -static char dirfh[NFS3_FHSIZE]; /* NFSv2 / NFSv3 file handle of directory */ -static unsigned int dirfh3_length; /* (variable) length of dirfh when NFSv3 */ -static char filefh[NFS3_FHSIZE]; /* NFSv2 / NFSv3 file handle */ -static unsigned int filefh3_length; /* (variable) length of filefh when NFSv3 */ - -static enum net_loop_state nfs_download_state; -static struct in_addr nfs_server_ip; -static int nfs_server_mount_port; -static int nfs_server_port; -static int nfs_our_port; -static int nfs_timeout_count; -static int nfs_state; -#define STATE_PRCLOOKUP_PROG_MOUNT_REQ 1 -#define STATE_PRCLOOKUP_PROG_NFS_REQ 2 -#define STATE_MOUNT_REQ 3 -#define STATE_UMOUNT_REQ 4 -#define STATE_LOOKUP_REQ 5 -#define STATE_READ_REQ 6 -#define STATE_READLINK_REQ 7 - -static char *nfs_filename; -static char *nfs_path; -static char nfs_path_buff[2048]; - -enum nfs_version { - NFS_UNKOWN = 0, - NFS_V1 = 1, - NFS_V2 = 2, - NFS_V3 = 3, -}; - -static enum nfs_version choosen_nfs_version = NFS_V3; -static inline int store_block(uchar *src, unsigned offset, unsigned len) -{ - ulong newsize = offset + len; -#ifdef CONFIG_SYS_DIRECT_FLASH_NFS - int i, rc = 0; - - for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) { - /* start address in flash? */ - if (image_load_addr + offset >= flash_info[i].start[0]) { - rc = 1; - break; - } - } - - if (rc) { /* Flash is destination for this packet */ - rc = flash_write((uchar *)src, (ulong)image_load_addr + offset, - len); - if (rc) { - flash_perror(rc); - return -1; - } - } else -#endif /* CONFIG_SYS_DIRECT_FLASH_NFS */ - { - void *ptr = map_sysmem(image_load_addr + offset, len); - - memcpy(ptr, src, len); - unmap_sysmem(ptr); - } - - if (net_boot_file_size < (offset + len)) - net_boot_file_size = newsize; - return 0; -} - -static char *basename(char *path) -{ - char *fname; - - fname = path + strlen(path) - 1; - while (fname >= path) { - if (*fname == '/') { - fname++; - break; - } - fname--; - } - return fname; -} - -static char *dirname(char *path) -{ - char *fname; - - fname = basename(path); - --fname; - *fname = '\0'; - return path; -} - -/************************************************************************** -RPC_ADD_CREDENTIALS - Add RPC authentication/verifier entries -**************************************************************************/ -static uint32_t *rpc_add_credentials(uint32_t *p) -{ - /* Here's the executive summary on authentication requirements of the - * various NFS server implementations: Linux accepts both AUTH_NONE - * and AUTH_UNIX authentication (also accepts an empty hostname field - * in the AUTH_UNIX scheme). *BSD refuses AUTH_NONE, but accepts - * AUTH_UNIX (also accepts an empty hostname field in the AUTH_UNIX - * scheme). To be safe, use AUTH_UNIX and pass the hostname if we have - * it (if the BOOTP/DHCP reply didn't give one, just use an empty - * hostname). */ - - /* Provide an AUTH_UNIX credential. */ - *p++ = htonl(1); /* AUTH_UNIX */ - *p++ = htonl(20); /* auth length */ - *p++ = 0; /* stamp */ - *p++ = 0; /* hostname string */ - *p++ = 0; /* uid */ - *p++ = 0; /* gid */ - *p++ = 0; /* auxiliary gid list */ - - /* Provide an AUTH_NONE verifier. */ - *p++ = 0; /* AUTH_NONE */ - *p++ = 0; /* auth length */ - - return p; -} - /************************************************************************** RPC_LOOKUP - Lookup RPC Port numbers **************************************************************************/ -static void rpc_req(int rpc_prog, int rpc_proc, uint32_t *data, int datalen) +void rpc_req(int rpc_prog, int rpc_proc, uint32_t *data, int datalen) { - struct rpc_t rpc_pkt; - unsigned long id; - uint32_t *p; int pktlen; int sport; - id = ++rpc_id; - rpc_pkt.u.call.id = htonl(id); - rpc_pkt.u.call.type = htonl(MSG_CALL); - rpc_pkt.u.call.rpcvers = htonl(2); /* use RPC version 2 */ - rpc_pkt.u.call.prog = htonl(rpc_prog); - switch (rpc_prog) { - case PROG_NFS: - switch (choosen_nfs_version) { - case NFS_V1: - case NFS_V2: - rpc_pkt.u.call.vers = htonl(2); - break; - - case NFS_V3: - rpc_pkt.u.call.vers = htonl(3); - break; - - case NFS_UNKOWN: - /* nothing to do */ - break; - } - break; - case PROG_MOUNT: - switch (choosen_nfs_version) { - case NFS_V1: - rpc_pkt.u.call.vers = htonl(1); - break; - - case NFS_V2: - rpc_pkt.u.call.vers = htonl(2); - break; - - case NFS_V3: - rpc_pkt.u.call.vers = htonl(3); - break; - - case NFS_UNKOWN: - /* nothing to do */ - break; - } - break; - case PROG_PORTMAP: - default: - rpc_pkt.u.call.vers = htonl(2); /* portmapper is version 2 */ - } - rpc_pkt.u.call.proc = htonl(rpc_proc); - p = rpc_pkt.u.call.data; - - if (datalen) - memcpy(p, data, datalen * sizeof(uint32_t)); - - pktlen = (char *)p + datalen * sizeof(uint32_t) - (char *)&rpc_pkt; - - memcpy((char *)net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE, - &rpc_pkt.u.data[0], pktlen); - - if (rpc_prog == PROG_PORTMAP) - sport = SUNRPC_PORT; - else if (rpc_prog == PROG_MOUNT) - sport = nfs_server_mount_port; - else - sport = nfs_server_port; + rpc_req_common(rpc_prog, rpc_proc, data, datalen, + (char *)net_tx_packet + net_eth_hdr_size() + + IP_UDP_HDR_SIZE, &pktlen, &sport); net_send_udp_packet(net_server_ethaddr, nfs_server_ip, sport, nfs_our_port, pktlen); } -/************************************************************************** -RPC_LOOKUP - Lookup RPC Port numbers -**************************************************************************/ -static void rpc_lookup_req(int prog, int ver) -{ - uint32_t data[16]; - - data[0] = 0; data[1] = 0; /* auth credential */ - data[2] = 0; data[3] = 0; /* auth verifier */ - data[4] = htonl(prog); - data[5] = htonl(ver); - data[6] = htonl(17); /* IP_UDP */ - data[7] = 0; - rpc_req(PROG_PORTMAP, PORTMAP_GETPORT, data, 8); -} - -/************************************************************************** -NFS_MOUNT - Mount an NFS Filesystem -**************************************************************************/ -static void nfs_mount_req(char *path) -{ - uint32_t data[1024]; - uint32_t *p; - int len; - int pathlen; - - pathlen = strlen(path); - - p = &(data[0]); - p = rpc_add_credentials(p); - - *p++ = htonl(pathlen); - if (pathlen & 3) - *(p + pathlen / 4) = 0; - memcpy(p, path, pathlen); - p += (pathlen + 3) / 4; - - len = (uint32_t *)p - (uint32_t *)&(data[0]); - - rpc_req(PROG_MOUNT, MOUNT_ADDENTRY, data, len); -} - -/************************************************************************** -NFS_UMOUNTALL - Unmount all our NFS Filesystems on the Server -**************************************************************************/ -static void nfs_umountall_req(void) -{ - uint32_t data[1024]; - uint32_t *p; - int len; - - if ((nfs_server_mount_port == -1) || (!fs_mounted)) - /* Nothing mounted, nothing to umount */ - return; - - p = &(data[0]); - p = rpc_add_credentials(p); - - len = (uint32_t *)p - (uint32_t *)&(data[0]); - - rpc_req(PROG_MOUNT, MOUNT_UMOUNTALL, data, len); -} - -/*************************************************************************** - * NFS_READLINK (AH 2003-07-14) - * This procedure is called when read of the first block fails - - * this probably happens when it's a directory or a symlink - * In case of successful readlink(), the dirname is manipulated, - * so that inside the nfs() function a recursion can be done. - **************************************************************************/ -static void nfs_readlink_req(void) -{ - uint32_t data[1024]; - uint32_t *p; - int len; - - p = &(data[0]); - p = rpc_add_credentials(p); - - if (choosen_nfs_version != NFS_V3) { - memcpy(p, filefh, NFS_FHSIZE); - p += (NFS_FHSIZE / 4); - } else { /* NFS_V3 */ - *p++ = htonl(filefh3_length); - memcpy(p, filefh, filefh3_length); - p += (filefh3_length / 4); - } - - len = (uint32_t *)p - (uint32_t *)&(data[0]); - - rpc_req(PROG_NFS, NFS_READLINK, data, len); -} - -/************************************************************************** -NFS_LOOKUP - Lookup Pathname -**************************************************************************/ -static void nfs_lookup_req(char *fname) -{ - uint32_t data[1024]; - uint32_t *p; - int len; - int fnamelen; - - fnamelen = strlen(fname); - - p = &(data[0]); - p = rpc_add_credentials(p); - - if (choosen_nfs_version != NFS_V3) { - memcpy(p, dirfh, NFS_FHSIZE); - p += (NFS_FHSIZE / 4); - *p++ = htonl(fnamelen); - if (fnamelen & 3) - *(p + fnamelen / 4) = 0; - memcpy(p, fname, fnamelen); - p += (fnamelen + 3) / 4; - - len = (uint32_t *)p - (uint32_t *)&(data[0]); - - rpc_req(PROG_NFS, NFS_LOOKUP, data, len); - } else { /* NFS_V3 */ - *p++ = htonl(dirfh3_length); /* Dir handle length */ - memcpy(p, dirfh, dirfh3_length); - p += (dirfh3_length / 4); - *p++ = htonl(fnamelen); - if (fnamelen & 3) - *(p + fnamelen / 4) = 0; - memcpy(p, fname, fnamelen); - p += (fnamelen + 3) / 4; - - len = (uint32_t *)p - (uint32_t *)&(data[0]); - - rpc_req(PROG_NFS, NFS3PROC_LOOKUP, data, len); - } -} - -/************************************************************************** -NFS_READ - Read File on NFS Server -**************************************************************************/ -static void nfs_read_req(int offset, int readlen) -{ - uint32_t data[1024]; - uint32_t *p; - int len; - - p = &(data[0]); - p = rpc_add_credentials(p); - - if (choosen_nfs_version != NFS_V3) { - memcpy(p, filefh, NFS_FHSIZE); - p += (NFS_FHSIZE / 4); - *p++ = htonl(offset); - *p++ = htonl(readlen); - *p++ = 0; - } else { /* NFS_V3 */ - *p++ = htonl(filefh3_length); - memcpy(p, filefh, filefh3_length); - p += (filefh3_length / 4); - *p++ = htonl(0); /* offset is 64-bit long, so fill with 0 */ - *p++ = htonl(offset); - *p++ = htonl(readlen); - *p++ = 0; - } - - len = (uint32_t *)p - (uint32_t *)&(data[0]); - - rpc_req(PROG_NFS, NFS_READ, data, len); -} - -/************************************************************************** -RPC request dispatcher -**************************************************************************/ -static void nfs_send(void) -{ - debug("%s\n", __func__); - - switch (nfs_state) { - case STATE_PRCLOOKUP_PROG_MOUNT_REQ: - if (choosen_nfs_version != NFS_V3) - rpc_lookup_req(PROG_MOUNT, 1); - else /* NFS_V3 */ - rpc_lookup_req(PROG_MOUNT, 3); - break; - case STATE_PRCLOOKUP_PROG_NFS_REQ: - if (choosen_nfs_version != NFS_V3) - rpc_lookup_req(PROG_NFS, 2); - else /* NFS_V3 */ - rpc_lookup_req(PROG_NFS, 3); - break; - case STATE_MOUNT_REQ: - nfs_mount_req(nfs_path); - break; - case STATE_UMOUNT_REQ: - nfs_umountall_req(); - break; - case STATE_LOOKUP_REQ: - nfs_lookup_req(nfs_filename); - break; - case STATE_READ_REQ: - nfs_read_req(nfs_offset, nfs_len); - break; - case STATE_READLINK_REQ: - nfs_readlink_req(); - break; - } -} - -/************************************************************************** -Handlers for the reply from server -**************************************************************************/ - -static int rpc_handle_error(struct rpc_t *rpc_pkt) -{ - if (rpc_pkt->u.reply.rstatus || - rpc_pkt->u.reply.verifier || - rpc_pkt->u.reply.astatus || - rpc_pkt->u.reply.data[0]) { - switch (ntohl(rpc_pkt->u.reply.astatus)) { - case NFS_RPC_SUCCESS: /* Not an error */ - break; - case NFS_RPC_PROG_MISMATCH: { - /* Remote can't support NFS version */ - const int min = ntohl(rpc_pkt->u.reply.data[0]); - const int max = ntohl(rpc_pkt->u.reply.data[1]); - - if (max < NFS_V1 || max > NFS_V3 || min > NFS_V3) { - puts("*** ERROR: NFS version not supported"); - debug(": Requested: V%d, accepted: min V%d - max V%d\n", - choosen_nfs_version, - ntohl(rpc_pkt->u.reply.data[0]), - ntohl(rpc_pkt->u.reply.data[1])); - puts("\n"); - choosen_nfs_version = NFS_UNKOWN; - break; - } - - debug("*** Warning: NFS version not supported: Requested: V%d, accepted: min V%d - max V%d\n", - choosen_nfs_version, - ntohl(rpc_pkt->u.reply.data[0]), - ntohl(rpc_pkt->u.reply.data[1])); - debug("Will retry with NFSv%d\n", min); - choosen_nfs_version = min; - return -NFS_RPC_PROG_MISMATCH; - } - case NFS_RPC_PROG_UNAVAIL: - case NFS_RPC_PROC_UNAVAIL: - case NFS_RPC_GARBAGE_ARGS: - case NFS_RPC_SYSTEM_ERR: - default: /* Unknown error on 'accept state' flag */ - debug("*** ERROR: accept state error (%d)\n", - ntohl(rpc_pkt->u.reply.astatus)); - break; - } - return -1; - } - - return 0; -} - -static int rpc_lookup_reply(int prog, uchar *pkt, unsigned len) -{ - struct rpc_t rpc_pkt; - - memcpy(&rpc_pkt.u.data[0], pkt, len); - - debug("%s\n", __func__); - - if (ntohl(rpc_pkt.u.reply.id) > rpc_id) - return -NFS_RPC_ERR; - else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) - return -NFS_RPC_DROP; - - if (rpc_pkt.u.reply.rstatus || - rpc_pkt.u.reply.verifier || - rpc_pkt.u.reply.astatus) - return -1; - - switch (prog) { - case PROG_MOUNT: - nfs_server_mount_port = ntohl(rpc_pkt.u.reply.data[0]); - break; - case PROG_NFS: - nfs_server_port = ntohl(rpc_pkt.u.reply.data[0]); - break; - } - - return 0; -} - -static int nfs_mount_reply(uchar *pkt, unsigned len) -{ - struct rpc_t rpc_pkt; - int ret; - - debug("%s\n", __func__); - - memcpy(&rpc_pkt.u.data[0], pkt, len); - - if (ntohl(rpc_pkt.u.reply.id) > rpc_id) - return -NFS_RPC_ERR; - else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) - return -NFS_RPC_DROP; - - ret = rpc_handle_error(&rpc_pkt); - if (ret) - return ret; - - fs_mounted = 1; - /* NFSv2 and NFSv3 use same structure */ - if (choosen_nfs_version != NFS_V3) { - memcpy(dirfh, rpc_pkt.u.reply.data + 1, NFS_FHSIZE); - } else { - dirfh3_length = ntohl(rpc_pkt.u.reply.data[1]); - if (dirfh3_length > NFS3_FHSIZE) - dirfh3_length = NFS3_FHSIZE; - memcpy(dirfh, rpc_pkt.u.reply.data + 2, dirfh3_length); - } - - return 0; -} - -static int nfs_umountall_reply(uchar *pkt, unsigned len) -{ - struct rpc_t rpc_pkt; - - debug("%s\n", __func__); - - memcpy(&rpc_pkt.u.data[0], pkt, len); - - if (ntohl(rpc_pkt.u.reply.id) > rpc_id) - return -NFS_RPC_ERR; - else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) - return -NFS_RPC_DROP; - - if (rpc_pkt.u.reply.rstatus || - rpc_pkt.u.reply.verifier || - rpc_pkt.u.reply.astatus) - return -1; - - fs_mounted = 0; - memset(dirfh, 0, sizeof(dirfh)); - - return 0; -} - -static int nfs_lookup_reply(uchar *pkt, unsigned len) -{ - struct rpc_t rpc_pkt; - int ret; - - debug("%s\n", __func__); - - memcpy(&rpc_pkt.u.data[0], pkt, len); - - if (ntohl(rpc_pkt.u.reply.id) > rpc_id) - return -NFS_RPC_ERR; - else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) - return -NFS_RPC_DROP; - - ret = rpc_handle_error(&rpc_pkt); - if (ret) - return ret; - - if (choosen_nfs_version != NFS_V3) { - if (((uchar *)&(rpc_pkt.u.reply.data[0]) - (uchar *)(&rpc_pkt) + NFS_FHSIZE) > len) - return -NFS_RPC_DROP; - memcpy(filefh, rpc_pkt.u.reply.data + 1, NFS_FHSIZE); - } else { /* NFS_V3 */ - filefh3_length = ntohl(rpc_pkt.u.reply.data[1]); - if (filefh3_length > NFS3_FHSIZE) - filefh3_length = NFS3_FHSIZE; - memcpy(filefh, rpc_pkt.u.reply.data + 2, filefh3_length); - } - - return 0; -} - -static int nfs3_get_attributes_offset(uint32_t *data) -{ - if (data[1]) { - /* 'attributes_follow' flag is TRUE, - * so we have attributes on 21 dwords */ - /* Skip unused values : - type; 32 bits value, - mode; 32 bits value, - nlink; 32 bits value, - uid; 32 bits value, - gid; 32 bits value, - size; 64 bits value, - used; 64 bits value, - rdev; 64 bits value, - fsid; 64 bits value, - fileid; 64 bits value, - atime; 64 bits value, - mtime; 64 bits value, - ctime; 64 bits value, - */ - return 22; - } else { - /* 'attributes_follow' flag is FALSE, - * so we don't have any attributes */ - return 1; - } -} - -static int nfs_readlink_reply(uchar *pkt, unsigned len) -{ - struct rpc_t rpc_pkt; - int rlen; - int nfsv3_data_offset = 0; - - debug("%s\n", __func__); - - memcpy((unsigned char *)&rpc_pkt, pkt, len); - - if (ntohl(rpc_pkt.u.reply.id) > rpc_id) - return -NFS_RPC_ERR; - else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) - return -NFS_RPC_DROP; - - if (rpc_pkt.u.reply.rstatus || - rpc_pkt.u.reply.verifier || - rpc_pkt.u.reply.astatus || - rpc_pkt.u.reply.data[0]) - return -1; - - if (choosen_nfs_version == NFS_V3) { - nfsv3_data_offset = - nfs3_get_attributes_offset(rpc_pkt.u.reply.data); - } - - /* new path length */ - rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]); - - if (((uchar *)&(rpc_pkt.u.reply.data[0]) - (uchar *)(&rpc_pkt) + rlen) > len) - return -NFS_RPC_DROP; - - if (*((char *)&(rpc_pkt.u.reply.data[2 + nfsv3_data_offset])) != '/') { - int pathlen; - - strcat(nfs_path, "/"); - pathlen = strlen(nfs_path); - memcpy(nfs_path + pathlen, - (uchar *)&(rpc_pkt.u.reply.data[2 + nfsv3_data_offset]), - rlen); - nfs_path[pathlen + rlen] = 0; - } else { - memcpy(nfs_path, - (uchar *)&(rpc_pkt.u.reply.data[2 + nfsv3_data_offset]), - rlen); - nfs_path[rlen] = 0; - } - return 0; -} - -static int nfs_read_reply(uchar *pkt, unsigned len) -{ - struct rpc_t rpc_pkt; - int rlen; - uchar *data_ptr; - - debug("%s\n", __func__); - - memcpy(&rpc_pkt.u.data[0], pkt, sizeof(rpc_pkt.u.reply)); - - if (ntohl(rpc_pkt.u.reply.id) > rpc_id) - return -NFS_RPC_ERR; - else if (ntohl(rpc_pkt.u.reply.id) < rpc_id) - return -NFS_RPC_DROP; - - if (rpc_pkt.u.reply.rstatus || - rpc_pkt.u.reply.verifier || - rpc_pkt.u.reply.astatus || - rpc_pkt.u.reply.data[0]) { - if (rpc_pkt.u.reply.rstatus) - return -9999; - if (rpc_pkt.u.reply.astatus) - return -9999; - return -ntohl(rpc_pkt.u.reply.data[0]); - } - - if ((nfs_offset != 0) && !((nfs_offset) % - (NFS_READ_SIZE / 2 * 10 * HASHES_PER_LINE))) - puts("\n\t "); - if (!(nfs_offset % ((NFS_READ_SIZE / 2) * 10))) - putc('#'); - - if (choosen_nfs_version != NFS_V3) { - rlen = ntohl(rpc_pkt.u.reply.data[18]); - data_ptr = (uchar *)&(rpc_pkt.u.reply.data[19]); - } else { /* NFS_V3 */ - int nfsv3_data_offset = - nfs3_get_attributes_offset(rpc_pkt.u.reply.data); - - /* count value */ - rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]); - /* Skip unused values : - EOF: 32 bits value, - data_size: 32 bits value, - */ - data_ptr = (uchar *) - &(rpc_pkt.u.reply.data[4 + nfsv3_data_offset]); - } - - if (((uchar *)&(rpc_pkt.u.reply.data[0]) - (uchar *)(&rpc_pkt) + rlen) > len) - return -9999; - - if (store_block(data_ptr, nfs_offset, rlen)) - return -9999; - - return rlen; -} - /************************************************************************** Interfaces of U-BOOT **************************************************************************/ @@ -787,12 +73,14 @@ static void nfs_timeout_handler(void) } } +void nfs_refresh_timeout(void) +{ + net_set_timeout_handler(nfs_timeout, nfs_timeout_handler); +} + static void nfs_handler(uchar *pkt, unsigned dest, struct in_addr sip, unsigned src, unsigned len) { - int rlen; - int reply; - debug("%s\n", __func__); if (len > sizeof(struct rpc_t)) @@ -801,117 +89,7 @@ static void nfs_handler(uchar *pkt, unsigned dest, struct in_addr sip, if (dest != nfs_our_port) return; - switch (nfs_state) { - case STATE_PRCLOOKUP_PROG_MOUNT_REQ: - if (rpc_lookup_reply(PROG_MOUNT, pkt, len) == -NFS_RPC_DROP) - break; - nfs_state = STATE_PRCLOOKUP_PROG_NFS_REQ; - nfs_send(); - break; - - case STATE_PRCLOOKUP_PROG_NFS_REQ: - if (rpc_lookup_reply(PROG_NFS, pkt, len) == -NFS_RPC_DROP) - break; - nfs_state = STATE_MOUNT_REQ; - nfs_send(); - break; - - case STATE_MOUNT_REQ: - reply = nfs_mount_reply(pkt, len); - if (reply == -NFS_RPC_DROP) { - break; - } else if (reply == -NFS_RPC_ERR) { - puts("*** ERROR: Cannot mount\n"); - /* just to be sure... */ - nfs_state = STATE_UMOUNT_REQ; - nfs_send(); - } else if (reply == -NFS_RPC_PROG_MISMATCH && - choosen_nfs_version != NFS_UNKOWN) { - nfs_state = STATE_MOUNT_REQ; - nfs_send(); - } else { - nfs_state = STATE_LOOKUP_REQ; - nfs_send(); - } - break; - - case STATE_UMOUNT_REQ: - reply = nfs_umountall_reply(pkt, len); - if (reply == -NFS_RPC_DROP) { - break; - } else if (reply == -NFS_RPC_ERR) { - debug("*** ERROR: Cannot umount\n"); - net_set_state(NETLOOP_FAIL); - } else { - puts("\ndone\n"); - net_set_state(nfs_download_state); - } - break; - - case STATE_LOOKUP_REQ: - reply = nfs_lookup_reply(pkt, len); - if (reply == -NFS_RPC_DROP) { - break; - } else if (reply == -NFS_RPC_ERR) { - puts("*** ERROR: File lookup fail\n"); - nfs_state = STATE_UMOUNT_REQ; - nfs_send(); - } else if (reply == -NFS_RPC_PROG_MISMATCH && - choosen_nfs_version != NFS_UNKOWN) { - /* umount */ - nfs_state = STATE_UMOUNT_REQ; - nfs_send(); - /* And retry with another supported version */ - nfs_state = STATE_PRCLOOKUP_PROG_MOUNT_REQ; - nfs_send(); - } else { - nfs_state = STATE_READ_REQ; - nfs_offset = 0; - nfs_len = NFS_READ_SIZE; - nfs_send(); - } - break; - - case STATE_READLINK_REQ: - reply = nfs_readlink_reply(pkt, len); - if (reply == -NFS_RPC_DROP) { - break; - } else if (reply == -NFS_RPC_ERR) { - puts("*** ERROR: Symlink fail\n"); - nfs_state = STATE_UMOUNT_REQ; - nfs_send(); - } else { - debug("Symlink --> %s\n", nfs_path); - nfs_filename = basename(nfs_path); - nfs_path = dirname(nfs_path); - - nfs_state = STATE_MOUNT_REQ; - nfs_send(); - } - break; - - case STATE_READ_REQ: - rlen = nfs_read_reply(pkt, len); - if (rlen == -NFS_RPC_DROP) - break; - net_set_timeout_handler(nfs_timeout, nfs_timeout_handler); - if (rlen > 0) { - nfs_offset += rlen; - nfs_send(); - } else if ((rlen == -NFSERR_ISDIR) || (rlen == -NFSERR_INVAL)) { - /* symbolic link */ - nfs_state = STATE_READLINK_REQ; - nfs_send(); - } else { - if (!rlen) - nfs_download_state = NETLOOP_SUCCESS; - if (rlen < 0) - debug("NFS READ error (%d)\n", rlen); - nfs_state = STATE_UMOUNT_REQ; - nfs_send(); - } - break; - } + nfs_pkt_recv(pkt, len); } void nfs_start(void) @@ -940,8 +118,8 @@ void nfs_start(void) nfs_path); } - nfs_filename = basename(nfs_path); - nfs_path = dirname(nfs_path); + nfs_filename = nfs_basename(nfs_path); + nfs_path = nfs_dirname(nfs_path); printf("Using %s device\n", eth_get_name()); diff --git a/net/nfs.h b/net/nfs.h index 6bf1cb76bd5..0dd180ce222 100644 --- a/net/nfs.h +++ b/net/nfs.h @@ -6,15 +6,6 @@ #ifndef __NFS_H__ #define __NFS_H__ -#define SUNRPC_PORT 111 - -#define PROG_PORTMAP 100000 -#define PROG_NFS 100003 -#define PROG_MOUNT 100005 - -#define MSG_CALL 0 -#define MSG_REPLY 1 - #define PORTMAP_GETPORT 3 #define MOUNT_ADDENTRY 1 @@ -29,56 +20,6 @@ #define NFS_FHSIZE 32 #define NFS3_FHSIZE 64 -#define NFSERR_PERM 1 -#define NFSERR_NOENT 2 -#define NFSERR_ACCES 13 -#define NFSERR_ISDIR 21 -#define NFSERR_INVAL 22 - -/* - * Block size used for NFS read accesses. A RPC reply packet (including all - * headers) must fit within a single Ethernet frame to avoid fragmentation. - * However, if CONFIG_IP_DEFRAG is set, a bigger value could be used. In any - * case, most NFS servers are optimized for a power of 2. - */ -#define NFS_READ_SIZE 1024 /* biggest power of two that fits Ether frame */ -#define NFS_MAX_ATTRS 26 - -/* Values for Accept State flag on RPC answers (See: rfc1831) */ -enum rpc_accept_stat { - NFS_RPC_SUCCESS = 0, /* RPC executed successfully */ - NFS_RPC_PROG_UNAVAIL = 1, /* remote hasn't exported program */ - NFS_RPC_PROG_MISMATCH = 2, /* remote can't support version # */ - NFS_RPC_PROC_UNAVAIL = 3, /* program can't support procedure */ - NFS_RPC_GARBAGE_ARGS = 4, /* procedure can't decode params */ - NFS_RPC_SYSTEM_ERR = 5 /* errors like memory allocation failure */ -}; - -struct rpc_t { - union { - uint8_t data[NFS_READ_SIZE + (6 + NFS_MAX_ATTRS) * - sizeof(uint32_t)]; - struct { - uint32_t id; - uint32_t type; - uint32_t rpcvers; - uint32_t prog; - uint32_t vers; - uint32_t proc; - uint32_t data[1]; - } call; - struct { - uint32_t id; - uint32_t type; - uint32_t rstatus; - uint32_t verifier; - uint32_t v2; - uint32_t astatus; - uint32_t data[NFS_READ_SIZE / sizeof(uint32_t) + - NFS_MAX_ATTRS]; - } reply; - } u; -}; void nfs_start(void); /* Begin NFS */ /**********************************************************************/ From 230cf3bc277622081b27e33469f5f1f59fc48180 Mon Sep 17 00:00:00 2001 From: Andrew Goodbody Date: Fri, 12 Dec 2025 11:32:28 +0000 Subject: [PATCH 06/13] net: lwip: nfs: Port the NFS code to work with lwIP After the preparatory patches moved most of the NFS code into common files we now add the code to enable NFS support with lwIP. Signed-off-by: Andrew Goodbody Acked-by: Jerome Forissier --- cmd/Kconfig | 28 ++--- cmd/lwip/Makefile | 1 + cmd/lwip/nfs.c | 11 ++ include/net-lwip.h | 1 + net/lwip/Makefile | 1 + net/lwip/nfs.c | 286 +++++++++++++++++++++++++++++++++++++++++++++ net/nfs-common.c | 4 +- 7 files changed, 317 insertions(+), 15 deletions(-) create mode 100644 cmd/lwip/nfs.c create mode 100644 net/lwip/nfs.c diff --git a/cmd/Kconfig b/cmd/Kconfig index b71ac554c0b..595ac49da41 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -2116,20 +2116,6 @@ config CMD_RARP help Boot image via network using RARP/TFTP protocol -config CMD_NFS - bool "nfs" - help - Boot image via network using NFS protocol. - -config NFS_TIMEOUT - int "Timeout in milliseconds for NFS mounts" - depends on CMD_NFS - default 2000 - help - Timeout in milliseconds used in NFS protocol. If you encounter - "ERROR: Cannot umount" in nfs command, try longer timeout such as - 10000. - config SYS_DISABLE_AUTOLOAD bool "Disable automatically loading files over the network" depends on CMD_BOOTP || CMD_DHCP || CMD_NFS || CMD_RARP @@ -2224,6 +2210,20 @@ config CMD_MDIO The MDIO interface is orthogonal to the MII interface and extends it by adding access to more registers through indirect addressing. +config CMD_NFS + bool "nfs" + help + Boot image via network using NFS protocol. + +config NFS_TIMEOUT + int "Timeout in milliseconds for NFS mounts" + depends on CMD_NFS + default 2000 + help + Timeout in milliseconds used in NFS protocol. If you encounter + "ERROR: Cannot umount" in nfs command, try longer timeout such as + 10000. + config CMD_PING bool "ping" select PROT_RAW_LWIP if NET_LWIP diff --git a/cmd/lwip/Makefile b/cmd/lwip/Makefile index a7f8976af3f..90df1f5511c 100644 --- a/cmd/lwip/Makefile +++ b/cmd/lwip/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_CMD_DHCP) += dhcp.o obj-$(CONFIG_CMD_DNS) += dns.o +obj-$(CONFIG_CMD_NFS) += nfs.o obj-$(CONFIG_CMD_PING) += ping.o obj-$(CONFIG_CMD_SNTP) += sntp.o obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o diff --git a/cmd/lwip/nfs.c b/cmd/lwip/nfs.c new file mode 100644 index 00000000000..f22db582fdb --- /dev/null +++ b/cmd/lwip/nfs.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2025 Linaro Ltd. */ + +#include +#include + +U_BOOT_CMD(nfs, 3, 1, do_nfs, + "boot image via network using NFS protocol", + "[loadAddress] [[hostIPaddr:]bootfilename]" + ); + diff --git a/include/net-lwip.h b/include/net-lwip.h index c910def5719..20cb0992cce 100644 --- a/include/net-lwip.h +++ b/include/net-lwip.h @@ -51,6 +51,7 @@ int net_lwip_dns_resolve(char *name_or_ip, ip_addr_t *ip); bool wget_validate_uri(char *uri); int do_dns(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); +int do_nfs(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); int do_wget(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]); #endif /* __NET_LWIP_H__ */ diff --git a/net/lwip/Makefile b/net/lwip/Makefile index 1b48ae4d508..5291cbb2a1c 100644 --- a/net/lwip/Makefile +++ b/net/lwip/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_$(PHASE_)DM_ETH) += net-lwip.o obj-$(CONFIG_CMD_DHCP) += dhcp.o obj-$(CONFIG_DNS) += dns.o obj-$(CONFIG_LWIP_ICMP_SHOW_UNREACH) += icmp_unreach.o +obj-$(CONFIG_CMD_NFS) += nfs.o obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o obj-$(CONFIG_WGET) += wget.o diff --git a/net/lwip/nfs.c b/net/lwip/nfs.c new file mode 100644 index 00000000000..27579af8f1f --- /dev/null +++ b/net/lwip/nfs.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2025 Linaro Ltd. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../nfs-common.h" +#include + +static ulong timer_start; + +static struct nfs_ctx { + ip_addr_t nfs_server; + struct udp_pcb *pcb; +} sess_ctx; + +/************************************************************************** + * RPC_LOOKUP - Lookup RPC Port numbers + ************************************************************************** + */ +void rpc_req(int rpc_prog, int rpc_proc, uint32_t *data, int datalen) +{ + struct pbuf *pb; + int pktlen; + int sport; + err_t err; + + pb = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct rpc_t), PBUF_RAM); + if (!pb) { + debug("Failed to allocate pbuf to build RPC packet\n"); + return; + } + + rpc_req_common(rpc_prog, rpc_proc, data, datalen, + pb->payload, &pktlen, &sport); + + pbuf_realloc(pb, (u16_t)pktlen); + + err = udp_sendto(sess_ctx.pcb, pb, &sess_ctx.nfs_server, sport); + debug_cond((err != ERR_OK), "Failed to send UDP packet err = %d\n", err); + pbuf_free(pb); +} + +void nfs_refresh_timeout(void) +{ + timer_start = get_timer(0); +} + +static void nfs_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, + const ip_addr_t *addr, u16_t port) +{ + struct nfs_ctx *ctx = arg; + int plen; + struct rpc_t rpc_pkt; + + if (addr->addr != ctx->nfs_server.addr) + goto exitfree; + + if (p->tot_len > sizeof(struct rpc_t)) + goto exitfree; + + plen = pbuf_copy_partial(p, &rpc_pkt.u.data[0], sizeof(rpc_pkt), 0); + nfs_pkt_recv(&rpc_pkt.u.data[0], plen); + +exitfree: + pbuf_free(p); +} + +static int nfs_udp_init(struct nfs_ctx *ctx) +{ + ctx->pcb = udp_new(); + if (!ctx->pcb) + return -ENOMEM; + + ctx->pcb->local_port = nfs_our_port; + udp_recv(ctx->pcb, nfs_recv, ctx); + + return 0; +} + +static int nfs_timeout_check(void) +{ + if (get_timer(timer_start) < nfs_timeout) + return 0; + if (++nfs_timeout_count < NFS_RETRY_COUNT) { + puts("T "); + timer_start = get_timer(0); + nfs_send(); + return 0; + } + + return 1; +} + +static int nfs_loop(struct udevice *udev, ulong addr, char *fname, + ip_addr_t srvip) +{ + struct netif *netif; + int ret; + + nfs_download_state = NETLOOP_FAIL; + net_set_state(NETLOOP_FAIL); + + if (!fname || addr == 0) + return -1; + + netif = net_lwip_new_netif(udev); + if (!netif) + return -1; + + nfs_filename = nfs_basename(fname); + nfs_path = nfs_dirname(fname); + + printf("Using %s device\n", eth_get_name()); + + printf("File transfer via NFS from server %s; our IP address is %s\n", + ip4addr_ntoa(&srvip), env_get("ipaddr")); + + printf("\nFilename '%s/%s'.", nfs_path, nfs_filename); + + if (net_boot_file_expected_size_in_blocks) { + printf(" Size is 0x%x Bytes = ", + net_boot_file_expected_size_in_blocks << 9); + print_size(net_boot_file_expected_size_in_blocks << 9, ""); + } + printf("\nLoad address: 0x%lx\nLoading: *\b", addr); + image_load_addr = addr; + + nfs_timeout_count = 0; + nfs_state = STATE_PRCLOOKUP_PROG_MOUNT_REQ; + + ret = nfs_udp_init(&sess_ctx); + if (ret < 0) { + net_lwip_remove_netif(netif); + debug("Failed to init network interface, aborting for error = %d\n", ret); + return ret; + } + + net_set_state(NETLOOP_CONTINUE); + + sess_ctx.nfs_server.addr = srvip.addr; + + nfs_send(); + + timer_start = get_timer(0); + do { + net_lwip_rx(udev, netif); + if (net_state != NETLOOP_CONTINUE) + break; + if (ctrlc()) { + printf("\nAbort\n"); + break; + } + + if (nfs_timeout_check()) + break; + } while (true); + debug("%s: Loop exit at %lu\n", __func__, get_timer(0)); + + net_lwip_remove_netif(netif); + + if (net_state == NETLOOP_SUCCESS) { + ret = 0; + if (net_boot_file_size > 0) { + printf("Bytes transferred = %u (%x hex)\n", + net_boot_file_size, net_boot_file_size); + env_set_hex("filesize", net_boot_file_size); + env_set_hex("fileaddr", image_load_addr); + } + + } else { + debug("%s: NFS loop failed\n", __func__); + ret = -1; + } + + return ret; +} + +int do_nfs(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int ret = CMD_RET_SUCCESS; + char *arg = NULL; + char *words[2] = { }; + char *fname = NULL; + char *server_ip = NULL; + char *end; + ip_addr_t srvip; + ulong laddr; + ulong addr; + int i; + + laddr = env_get_ulong("loadaddr", 16, image_load_addr); + + switch (argc) { + case 1: + fname = env_get("bootfile"); + break; + case 2: + /* + * Only one arg - accept two forms: + * Just load address, or just boot file name. The latter + * form must be written in a format which can not be + * mis-interpreted as a valid number. + */ + addr = hextoul(argv[1], &end); + if (end == (argv[1] + strlen(argv[1]))) { + laddr = addr; + fname = env_get("bootfile"); + } else { + arg = strdup(argv[1]); + } + break; + case 3: + laddr = hextoul(argv[1], NULL); + arg = strdup(argv[2]); + break; + default: + ret = CMD_RET_USAGE; + goto out; + } + + if (!arg) + arg = net_boot_file_name; + + if (*arg) { + /* Parse [ip:]fname */ + i = 0; + while ((*(words + i) = strsep(&arg, ":"))) + i++; + + switch (i) { + case 2: + server_ip = words[0]; + fname = words[1]; + break; + case 1: + fname = words[0]; + break; + default: + break; + } + } + + if (!server_ip) + server_ip = env_get("serverip"); + if (!server_ip) { + log_err("*** ERROR: 'serverip' not set\n"); + ret = CMD_RET_FAILURE; + goto out; + } + + if (!ipaddr_aton(server_ip, &srvip)) { + log_err("error: ipaddr_aton\n"); + ret = CMD_RET_FAILURE; + goto out; + } + + if (!fname) { + log_err("error: no file name\n"); + ret = CMD_RET_FAILURE; + goto out; + } + + if (!laddr) { + log_err("error: no load address\n"); + ret = CMD_RET_FAILURE; + goto out; + } + + if (net_lwip_eth_start() < 0) { + ret = CMD_RET_FAILURE; + goto out; + } + + if (nfs_loop(eth_get_dev(), laddr, fname, srvip) < 0) + ret = CMD_RET_FAILURE; +out: + if (arg != net_boot_file_name) + free(arg); + return ret; +} diff --git a/net/nfs-common.c b/net/nfs-common.c index a6a70677bd2..4fbde67a760 100644 --- a/net/nfs-common.c +++ b/net/nfs-common.c @@ -286,9 +286,11 @@ static void nfs_umountall_req(void) u32 *p; int len; - if ((nfs_server_mount_port == -1) || !fs_mounted) + if ((nfs_server_mount_port == -1) || !fs_mounted) { /* Nothing mounted, nothing to umount */ + net_set_state(NETLOOP_FAIL); return; + } p = &data[0]; p = rpc_add_credentials(p); From f916c819a33e7e6314e92e37fb9fbdba46432a05 Mon Sep 17 00:00:00 2001 From: Andrew Goodbody Date: Fri, 12 Dec 2025 11:32:29 +0000 Subject: [PATCH 07/13] configs: qemu_arm64_lwip_defconfig: enable CMD_NFS Enable NFS command so that it gets built by CI and can be tested more easily. Signed-off-by: Andrew Goodbody Acked-by: Jerome Forissier --- configs/qemu_arm64_lwip_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/qemu_arm64_lwip_defconfig b/configs/qemu_arm64_lwip_defconfig index e8f976efaf7..a974970c3d3 100644 --- a/configs/qemu_arm64_lwip_defconfig +++ b/configs/qemu_arm64_lwip_defconfig @@ -5,6 +5,7 @@ CONFIG_ARCH_QEMU=y CONFIG_NET_LWIP=y CONFIG_CMD_DNS=y +CONFIG_CMD_NFS=y CONFIG_CMD_SNTP=y CONFIG_CMD_WGET=y CONFIG_EFI_HTTP_BOOT=y From c832cd3b49a51e34c3672201881f3da64df45031 Mon Sep 17 00:00:00 2001 From: Tom Rini Date: Thu, 25 Dec 2025 09:37:21 -0600 Subject: [PATCH 08/13] net: tftpput: Rework to exclude code from xPL phases Given how the support for CONFIG_CMD_TFTPPUT is woven through the support for the tftp protocol we currently end up including "put" support in xPL phases, if enabled. This in turn can lead to size overflow on those platforms as xPL tends to be constrained. To resolve this, use "CMD_TFTPPUT" in the code to check for both CONFIG_CMD_TFTPPUT being true and not being in an xPL build phase. Signed-off-by: Tom Rini Reviewed-by: Jerome Forissier --- net/tftp.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/net/tftp.c b/net/tftp.c index 3b0f4cd2006..78ec44159c1 100644 --- a/net/tftp.c +++ b/net/tftp.c @@ -22,6 +22,15 @@ DECLARE_GLOBAL_DATA_PTR; +/* + * We cannot use the 'tftpput' command in xPL phases. Given how the + * support is integrated in the code, this is how we disable that support + * in xPL. + */ +#if defined(CONFIG_CMD_TFTPPUT) && !defined(CONFIG_XPL_BUILD) +#define CMD_TFTPPUT +#endif + /* Well known TFTP port # */ #define WELL_KNOWN_PORT 69 /* Millisecs to timeout for lost pkt */ @@ -95,7 +104,7 @@ static ushort tftp_windowsize; static ushort tftp_next_ack; /* Last nack block we send */ static ushort tftp_last_nack; -#ifdef CONFIG_CMD_TFTPPUT +#ifdef CMD_TFTPPUT /* 1 if writing, else 0 */ static int tftp_put_active; /* 1 if we have sent the last block */ @@ -183,13 +192,13 @@ static void new_transfer(void) tftp_prev_block = 0; tftp_block_wrap = 0; tftp_block_wrap_offset = 0; -#ifdef CONFIG_CMD_TFTPPUT +#ifdef CMD_TFTPPUT tftp_put_final_block_sent = 0; #endif led_activity_blink(); } -#ifdef CONFIG_CMD_TFTPPUT +#ifdef CMD_TFTPPUT /** * Load the next block from memory to be sent over tftp. * @@ -329,7 +338,7 @@ static void tftp_send(void) case STATE_SEND_WRQ: xp = pkt; s = (ushort *)pkt; -#ifdef CONFIG_CMD_TFTPPUT +#ifdef CMD_TFTPPUT *s++ = htons(tftp_state == STATE_SEND_RRQ ? TFTP_RRQ : TFTP_WRQ); #else @@ -372,7 +381,7 @@ static void tftp_send(void) s[0] = htons(TFTP_ACK); s[1] = htons(tftp_cur_block); pkt = (uchar *)(s + 2); -#ifdef CONFIG_CMD_TFTPPUT +#ifdef CMD_TFTPPUT if (tftp_put_active) { int toload = tftp_block_size; int loaded = load_block(tftp_cur_block, pkt, toload); @@ -437,7 +446,7 @@ static void tftp_send(void) net_set_state(NETLOOP_FAIL); } -#ifdef CONFIG_CMD_TFTPPUT +#ifdef CMD_TFTPPUT static void icmp_handler(unsigned type, unsigned code, unsigned dest, struct in_addr sip, unsigned src, uchar *pkt, unsigned len) @@ -476,7 +485,7 @@ static void tftp_handler(uchar *pkt, unsigned dest, struct in_addr sip, break; case TFTP_ACK: -#ifdef CONFIG_CMD_TFTPPUT +#ifdef CMD_TFTPPUT if (tftp_put_active) { timeout_count = 0; if (tftp_put_final_block_sent) { @@ -578,7 +587,7 @@ static void tftp_handler(uchar *pkt, unsigned dest, struct in_addr sip, tftp_next_ack = tftp_windowsize; -#ifdef CONFIG_CMD_TFTPPUT +#ifdef CMD_TFTPPUT if (tftp_put_active && tftp_state == STATE_OACK) { /* Get ready to send the first block */ tftp_state = STATE_DATA; @@ -854,7 +863,7 @@ void tftp_start(enum proto_t protocol) tftp_block_size_option = TFTP_MTU_BLOCKSIZE6; } else { printf("TFTP %s server %pI4; our IP address is %pI4", -#ifdef CONFIG_CMD_TFTPPUT +#ifdef CMD_TFTPPUT protocol == TFTPPUT ? "to" : "from", #else "from", @@ -888,7 +897,7 @@ void tftp_start(enum proto_t protocol) } putc('\n'); -#ifdef CONFIG_CMD_TFTPPUT +#ifdef CMD_TFTPPUT tftp_put_active = (protocol == TFTPPUT); if (tftp_put_active) { printf("Save address: 0x%lx\n", image_save_addr); @@ -911,7 +920,7 @@ void tftp_start(enum proto_t protocol) net_set_timeout_handler(timeout_ms, tftp_timeout_handler); net_set_udp_handler(tftp_handler); -#ifdef CONFIG_CMD_TFTPPUT +#ifdef CMD_TFTPPUT net_set_icmp_handler(icmp_handler); #endif tftp_remote_port = WELL_KNOWN_PORT; From dfc39f9caf3c1a5e56a6c2b65b3bee83736e29d8 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 Dec 2025 22:06:03 +0100 Subject: [PATCH 09/13] net: add Microsemi/Microchip MDIO driver Add Microsemi/Microchip MDIO driver for interfaces found in their network switches. Driver is based on the Linux version. Signed-off-by: Robert Marko Acked-by: Jerome Forissier --- drivers/net/Kconfig | 8 +++ drivers/net/Makefile | 1 + drivers/net/mdio-mscc-miim.c | 136 +++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 drivers/net/mdio-mscc-miim.c diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 13e631c55dc..0089f74c69d 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -1069,6 +1069,14 @@ config ASPEED_MDIO This driver supports the MDIO bus of Aspeed AST2600 SOC. The driver currently supports Clause 22. +config MDIO_MSCC_MIIM + bool "Microsemi MIIM interface support" + depends on DM_MDIO + select REGMAP + help + This driver supports MDIO interface found in Microsemi and Microchip + network switches. + config MDIO_MUX_MMIOREG bool "MDIO MUX accessed as a MMIO register access" depends on DM_MDIO_MUX diff --git a/drivers/net/Makefile b/drivers/net/Makefile index a3c3420898c..5bb40480d88 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_LITEETH) += liteeth.o obj-$(CONFIG_MACB) += macb.o obj-$(CONFIG_MCFFEC) += mcffec.o mcfmii.o obj-$(CONFIG_MDIO_IPQ4019) += mdio-ipq4019.o +obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-mscc-miim.o obj-$(CONFIG_MDIO_GPIO_BITBANG) += mdio_gpio.o obj-$(CONFIG_MDIO_MT7531_MMIO) += mdio-mt7531-mmio.o obj-$(CONFIG_MDIO_MUX_I2CREG) += mdio_mux_i2creg.o diff --git a/drivers/net/mdio-mscc-miim.c b/drivers/net/mdio-mscc-miim.c new file mode 100644 index 00000000000..5700b872586 --- /dev/null +++ b/drivers/net/mdio-mscc-miim.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include +#include +#include +#include + +#define MSCC_MIIM_REG_STATUS 0x0 +#define MSCC_MIIM_STATUS_STAT_PENDING BIT(2) +#define MSCC_MIIM_STATUS_STAT_BUSY BIT(3) +#define MSCC_MIIM_REG_CMD 0x8 +#define MSCC_MIIM_CMD_OPR_WRITE BIT(1) +#define MSCC_MIIM_CMD_OPR_READ BIT(2) +#define MSCC_MIIM_CMD_WRDATA_SHIFT 4 +#define MSCC_MIIM_CMD_REGAD_SHIFT 20 +#define MSCC_MIIM_CMD_PHYAD_SHIFT 25 +#define MSCC_MIIM_CMD_VLD BIT(31) +#define MSCC_MIIM_REG_DATA 0xC +#define MSCC_MIIM_DATA_ERROR (BIT(16) | BIT(17)) +#define MSCC_MIIM_DATA_MASK GENMASK(15, 0) +#define MSCC_MIIM_REG_CFG 0x10 +#define MSCC_MIIM_CFG_PRESCALE_MASK GENMASK(7, 0) +/* 01 = Clause 22, 00 = Clause 45 */ +#define MSCC_MIIM_CFG_ST_CFG_MASK GENMASK(10, 9) +#define MSCC_MIIM_C22 1 +#define MSCC_MIIM_C45 0 + +#define MSCC_MDIO_TIMEOUT 10000 +#define MSCC_MDIO_SLEEP 50 + +struct mscc_mdio_priv { + struct regmap *map; +}; + +static int mscc_mdio_wait_busy(struct mscc_mdio_priv *priv) +{ + u32 busy; + + return regmap_read_poll_timeout(priv->map, MSCC_MIIM_REG_STATUS, busy, + (busy & MSCC_MIIM_STATUS_STAT_BUSY) == 0, + MSCC_MDIO_SLEEP, + MSCC_MDIO_TIMEOUT); +} + +static int mscc_mdio_read(struct udevice *dev, int addr, int devad, int reg) +{ + struct mscc_mdio_priv *priv = dev_get_priv(dev); + u32 val; + int ret; + + if (mscc_mdio_wait_busy(priv)) + return -ETIMEDOUT; + + ret = regmap_write(priv->map, MSCC_MIIM_REG_CMD, + MSCC_MIIM_CMD_VLD | + (addr << MSCC_MIIM_CMD_PHYAD_SHIFT) | + (reg << MSCC_MIIM_CMD_REGAD_SHIFT) | + MSCC_MIIM_CMD_OPR_READ); + if (ret) + return ret; + + if (mscc_mdio_wait_busy(priv)) + return -ETIMEDOUT; + + regmap_read(priv->map, MSCC_MIIM_REG_DATA, &val); + if (val & MSCC_MIIM_DATA_ERROR) + return -EIO; + + return FIELD_GET(MSCC_MIIM_DATA_MASK, val); +} + +int mscc_mdio_write(struct udevice *dev, int addr, int devad, int reg, u16 val) +{ + struct mscc_mdio_priv *priv = dev_get_priv(dev); + int ret; + + if (mscc_mdio_wait_busy(priv)) + return -ETIMEDOUT; + + ret = regmap_write(priv->map, MSCC_MIIM_REG_CMD, + MSCC_MIIM_CMD_VLD | + (addr << MSCC_MIIM_CMD_PHYAD_SHIFT) | + (reg << MSCC_MIIM_CMD_REGAD_SHIFT) | + (val << MSCC_MIIM_CMD_WRDATA_SHIFT) | + MSCC_MIIM_CMD_OPR_WRITE); + + return ret; +} + +static const struct mdio_ops mscc_mdio_ops = { + .read = mscc_mdio_read, + .write = mscc_mdio_write, +}; + +static int mscc_mdio_bind(struct udevice *dev) +{ + if (ofnode_valid(dev_ofnode(dev))) + device_set_name(dev, ofnode_get_name(dev_ofnode(dev))); + + return 0; +} + +static int mscc_mdio_probe(struct udevice *dev) +{ + struct mscc_mdio_priv *priv = dev_get_priv(dev); + int ret; + + ret = regmap_init_mem(dev_ofnode(dev), &priv->map); + if (ret) + return -EINVAL; + + /* Enter Clause 22 mode */ + ret = regmap_update_bits(priv->map, MSCC_MIIM_REG_CFG, + MSCC_MIIM_CFG_ST_CFG_MASK, + FIELD_PREP(MSCC_MIIM_CFG_ST_CFG_MASK, + MSCC_MIIM_C22)); + + return ret; +} + +static const struct udevice_id mscc_mdio_ids[] = { + { .compatible = "mscc,ocelot-miim", }, + { } +}; + +U_BOOT_DRIVER(mscc_mdio) = { + .name = "mscc_mdio", + .id = UCLASS_MDIO, + .of_match = mscc_mdio_ids, + .bind = mscc_mdio_bind, + .probe = mscc_mdio_probe, + .ops = &mscc_mdio_ops, + .priv_auto = sizeof(struct mscc_mdio_priv), +}; From 7d650e7f90892e3ed1c79bab88955bbbaeb660e4 Mon Sep 17 00:00:00 2001 From: Link Mauve Date: Sat, 27 Dec 2025 19:18:48 +0100 Subject: [PATCH 10/13] =?UTF-8?q?Add=20missing=20=E2=80=9Cnet=E2=80=9D=20p?= =?UTF-8?q?refix=20in=20help=20net?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The usage of the net sub-system was missing the complete command for “net stats”. Signed-off-by: Link Mauve Reviewed-by: Jerome Forissier --- cmd/net-common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/net-common.c b/cmd/net-common.c index 1c6f11cd435..6f33d15d695 100644 --- a/cmd/net-common.c +++ b/cmd/net-common.c @@ -103,4 +103,4 @@ static int do_net(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) U_BOOT_CMD(net, 3, 1, do_net, "NET sub-system", "list - list available devices\n" - "stats - dump statistics for specified device\n"); + "net stats - dump statistics for specified device\n"); From 2ee6bf4c65d906205aa9e92b96b35cbe75936afc Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 1 Jan 2026 17:51:44 +0100 Subject: [PATCH 11/13] net: phy: marvell10g: Fix PHY mode bitmap handling Replace PHY interface mode bitmap handling with comparison test to match U-Boot PHY subsystem behavior. U-Boot currently implements only single PHY interface mode for each PHY. Linux currently uses bitmap of PHY interface modes for each PHY. The reason why in Linux uses bitmap of supported interface modes is so that Linux can select the best serdes mode switching behavior for the PHY. For example if the host only supports 10gbase-r serdes mode, then the PHY must always talk to the host in 10gbase-r mode, even if the RJ-45 copper speed was autonegotiated to lower speed (i.e. 1Gbps). If the host supports both 10gbase-r and sgmii serdes modes, we want the PHY to switch to sgmii if the RJ-45 speed is 1000/100/10, and to switch to 10gbase-r if the RJ-45 speed is 10000. U-Boot does not implement this functionality yet, therefore remove modes which cannot be currently supported and switch mv_test_bit() to plain mode comparison. Fixes: b6fcab0728cb ("net: phy: marvell10g: Adapt Marvell 10G PHY driver from Linux") Signed-off-by: Marek Vasut --- drivers/net/phy/marvell10g.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c index 8c95bcbb9ad..d6115eea025 100644 --- a/drivers/net/phy/marvell10g.c +++ b/drivers/net/phy/marvell10g.c @@ -342,8 +342,7 @@ static int mv2110_select_mactype(struct phy_device *phydev) { if (phydev->interface == PHY_INTERFACE_MODE_USXGMII) return MV_PMA_21X0_PORT_CTRL_MACTYPE_USXGMII; - else if (phydev->interface == PHY_INTERFACE_MODE_SGMII && - !(phydev->interface == PHY_INTERFACE_MODE_10GBASER)) + else if (phydev->interface == PHY_INTERFACE_MODE_SGMII) return MV_PMA_21X0_PORT_CTRL_MACTYPE_5GBASER; else if (phydev->interface == PHY_INTERFACE_MODE_10GBASER) return MV_PMA_21X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH; @@ -381,15 +380,6 @@ static int mv3310_select_mactype(struct phy_device *phydev) { if (phydev->interface == PHY_INTERFACE_MODE_USXGMII) return MV_V2_33X0_PORT_CTRL_MACTYPE_USXGMII; - else if (phydev->interface == PHY_INTERFACE_MODE_SGMII && - phydev->interface == PHY_INTERFACE_MODE_10GBASER) - return MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER; - else if (phydev->interface == PHY_INTERFACE_MODE_SGMII && - phydev->interface == PHY_INTERFACE_MODE_RXAUI) - return MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI; - else if (phydev->interface == PHY_INTERFACE_MODE_SGMII && - phydev->interface == PHY_INTERFACE_MODE_XAUI) - return MV_V2_3310_PORT_CTRL_MACTYPE_XAUI; else if (phydev->interface == PHY_INTERFACE_MODE_10GBASER) return MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH; else if (phydev->interface == PHY_INTERFACE_MODE_RXAUI) @@ -542,7 +532,7 @@ static bool mv3310_has_downshift(struct phy_device *phydev) } #define mv_test_bit(iface, phydev) \ - ({ if ((phydev)->interface & (iface)) return 0; }) + ({ if ((phydev)->interface == (iface)) return 0; }) static int mv3310_mv3340_test_supported_interfaces(struct phy_device *phydev) { From 60545cf032f097bd096be9d8285320704fc61088 Mon Sep 17 00:00:00 2001 From: Markus Niebel Date: Tue, 2 Dec 2025 09:13:42 +0100 Subject: [PATCH 12/13] net: phy: micrel_ksz90x1: disable asymmetric pause for KSZ9031 and KSZ9021 Disable the support due to chip errata and call genphy_config_aneg instead of genphy_config. For a complete describtion look at the KSZ9031 errata sheets: DS80000691D or DS80000692D. Micrel KSZ9021 has no errata, but has the same issue with Asymmetric Pause. This patch apply the same workaround as the one for KSZ9031. This follows linux implementation in commits 3aed3e2a143c ("net: phy: micrel: add Asym Pause workaround") 407d8098cb1a ("net: phy: micrel: add Asym Pause workaround for KSZ9021") Signed-off-by: Markus Niebel Signed-off-by: Max Merchel --- drivers/net/phy/micrel_ksz90x1.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/net/phy/micrel_ksz90x1.c b/drivers/net/phy/micrel_ksz90x1.c index a02dbe900b8..a669a5789b9 100644 --- a/drivers/net/phy/micrel_ksz90x1.c +++ b/drivers/net/phy/micrel_ksz90x1.c @@ -217,6 +217,31 @@ static int ksz9031_center_flp_timing(struct phy_device *phydev) return ret; } +static void ksz90x1_workaround_asymmetric_pause(struct phy_device *phydev) +{ + u32 features = phydev->drv->features; + + /* Silicon Errata Sheet (DS80000691D or DS80000692D): + * Whenever the device's Asymmetric Pause capability is set to 1, + * link-up may fail after a link-up to link-down transition. + * + * The Errata Sheet is for ksz9031, but ksz9021 has the same issue + * + * Workaround: + * Do not enable the Asymmetric Pause capability bit. + */ + features &= ~ADVERTISE_PAUSE_ASYM; + + /* We force setting the Pause capability as the core will force the + * Asymmetric Pause capability to 1 otherwise. + */ + features |= ADVERTISE_PAUSE_CAP; + + /* update feature support and forward to advertised features */ + phydev->supported = features; + phydev->advertising = phydev->supported; +} + /* * KSZ9021 */ @@ -260,6 +285,8 @@ static int ksz9021_config(struct phy_device *phydev) if (ret) return ret; + ksz90x1_workaround_asymmetric_pause(phydev); + if (env_get("disable_giga")) features &= ~(SUPPORTED_1000baseT_Half | SUPPORTED_1000baseT_Full); @@ -345,6 +372,8 @@ static int ksz9031_config(struct phy_device *phydev) if (ret) return ret; + ksz90x1_workaround_asymmetric_pause(phydev); + /* add an option to disable the gigabit feature of this PHY */ if (env_get("disable_giga")) { unsigned features; From b61d7d95cc62525060f0d05881bdaaf994a55b11 Mon Sep 17 00:00:00 2001 From: Markus Niebel Date: Tue, 2 Dec 2025 09:13:43 +0100 Subject: [PATCH 13/13] net: phy: micrel_ksz90x1: support forced GIGE master for KSZ9031 The micrel KSZ9031 phy has a optional clock pin (CLK125_NDO) which can be used as reference clock for the MAC unit. The clock signal must meet the RGMII requirements to ensure the correct data transmission between the MAC and the PHY. The KSZ9031 phy does not fulfill the duty cycle requirement if the phy is configured as slave. For a complete describtion look at the errata sheets: DS80000691D or DS80000692D. The errata sheet recommends to force the phy into master mode whenever there is a 1000Base-T link-up as work around. Only set the "micrel,force-master" property if you use the phy reference clock provided by CLK125_NDO pin as MAC reference clock in your application. Attention: this workaround is only usable if the link partner can be configured to slave mode for 1000Base-T. This follows linux implementation in commit e1b505a60366 ("net: phy: micrel: add 125MHz reference clock workaround") Signed-off-by: Markus Niebel Signed-off-by: Max Merchel --- drivers/net/phy/micrel_ksz90x1.c | 37 +++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/micrel_ksz90x1.c b/drivers/net/phy/micrel_ksz90x1.c index a669a5789b9..f357e0f1c77 100644 --- a/drivers/net/phy/micrel_ksz90x1.c +++ b/drivers/net/phy/micrel_ksz90x1.c @@ -189,7 +189,10 @@ static int ksz9031_of_config(struct phy_device *phydev) { MII_KSZ9031_EXT_RGMII_TX_DATA_SKEW, 2, ksz90x1_txd_grp, 4 }, { MII_KSZ9031_EXT_RGMII_CLOCK_SKEW, 2, ksz9031_clk_grp, 2 }, }; + const unsigned int master = CTRL1000_CONFIG_MASTER | CTRL1000_MANUAL_CONFIG; + struct udevice *dev = phydev->dev; int i, ret = 0; + ofnode node; for (i = 0; i < ARRAY_SIZE(ofcfg); i++) { ret = ksz90x1_of_config_group(phydev, &ofcfg[i], @@ -198,7 +201,39 @@ static int ksz9031_of_config(struct phy_device *phydev) return ret; } - return 0; + node = phydev->node; + + /* Look for a PHY node under the Ethernet node */ + if (!ofnode_valid(node)) + node = dev_read_subnode(dev, "ethernet-phy"); + + /* No node found, look in the Ethernet node */ + if (!ofnode_valid(node)) + node = dev_ofnode(dev); + + /* Silicon Errata Sheet (DS80000691D or DS80000692D): + * When the device links in the 1000BASE-T slave mode only, + * the optional 125MHz reference output clock (CLK125_NDO) + * has wide duty cycle variation. + * + * The optional CLK125_NDO clock does not meet the RGMII + * 45/55 percent (min/max) duty cycle requirement and therefore + * cannot be used directly by the MAC side for clocking + * applications that have setup/hold time requirements on + * rising and falling clock edges. + * + * Workaround: + * Force the phy to be the master to receive a stable clock + * which meets the duty cycle requirement. + */ + if (ofnode_read_bool(node, "micrel,force-master")) { + ret = phy_modify(phydev, MDIO_DEVAD_NONE, MII_CTRL1000, + master | CTRL1000_PREFER_MASTER, master); + if (ret < 0) + pr_err("KSZ9031: error applying 'micrel,force-master'\n"); + } + + return ret; } static int ksz9031_center_flp_timing(struct phy_device *phydev)