1 // SPDX-License-Identifier: GPL-2.0+
2 /* Copyright (C) 2024 Linaro Ltd. */
3 
4 #include <command.h>
5 #include <console.h>
6 #include <env.h>
7 #include <log.h>
8 #include <dm/device.h>
9 #include <linux/delay.h>
10 #include <linux/errno.h>
11 #include <lwip/dhcp.h>
12 #include <lwip/dns.h>
13 #include <lwip/timeouts.h>
14 #include <net.h>
15 #include <time.h>
16 
17 #define DHCP_TIMEOUT_MS 10000
18 
19 #ifdef CONFIG_CMD_TFTPBOOT
20 /* Boot file obtained from DHCP (if present) */
21 static char boot_file_name[DHCP_BOOT_FILE_LEN];
22 #endif
23 
call_lwip_dhcp_fine_tmr(void * ctx)24 static void call_lwip_dhcp_fine_tmr(void *ctx)
25 {
26 	dhcp_fine_tmr();
27 	sys_timeout(10, call_lwip_dhcp_fine_tmr, NULL);
28 }
29 
dhcp_loop(struct udevice * udev)30 static int dhcp_loop(struct udevice *udev)
31 {
32 	char ipstr[] = "ipaddr\0\0";
33 	char maskstr[] = "netmask\0\0";
34 	char gwstr[] = "gatewayip\0\0";
35 	unsigned long start;
36 	struct netif *netif;
37 	struct dhcp *dhcp;
38 	bool bound;
39 	int idx;
40 
41 	idx = dev_seq(udev);
42 	if (idx < 0 || idx > 99) {
43 		log_err("unexpected idx %d\n", idx);
44 		return CMD_RET_FAILURE;
45 	}
46 
47 	netif = net_lwip_new_netif_noip(udev);
48 	if (!netif)
49 		return CMD_RET_FAILURE;
50 
51 	start = get_timer(0);
52 
53 	if (dhcp_start(netif))
54 		return CMD_RET_FAILURE;
55 
56 	call_lwip_dhcp_fine_tmr(NULL);
57 
58 	/* Wait for DHCP to complete */
59 	do {
60 		net_lwip_rx(udev, netif);
61 		bound = dhcp_supplied_address(netif);
62 		if (bound)
63 			break;
64 		if (ctrlc()) {
65 			printf("Abort\n");
66 			break;
67 		}
68 		mdelay(1);
69 	} while (get_timer(start) < DHCP_TIMEOUT_MS);
70 
71 	sys_untimeout(call_lwip_dhcp_fine_tmr, NULL);
72 
73 	if (!bound) {
74 		net_lwip_remove_netif(netif);
75 		return CMD_RET_FAILURE;
76 	}
77 
78 	dhcp = netif_dhcp_data(netif);
79 
80 	env_set("bootfile", dhcp->boot_file_name);
81 
82 	if (idx > 0) {
83 		sprintf(ipstr, "ipaddr%d", idx);
84 		sprintf(maskstr, "netmask%d", idx);
85 		sprintf(gwstr, "gatewayip%d", idx);
86 	} else {
87 		net_ip.s_addr = dhcp->offered_ip_addr.addr;
88 	}
89 
90 	env_set(ipstr, ip4addr_ntoa(&dhcp->offered_ip_addr));
91 	env_set(maskstr, ip4addr_ntoa(&dhcp->offered_sn_mask));
92 	env_set("serverip", ip4addr_ntoa(&dhcp->server_ip_addr));
93 	if (dhcp->offered_gw_addr.addr != 0)
94 		env_set(gwstr, ip4addr_ntoa(&dhcp->offered_gw_addr));
95 
96 #ifdef CONFIG_PROT_DNS_LWIP
97 	env_set("dnsip", ip4addr_ntoa(dns_getserver(0)));
98 	env_set("dnsip2", ip4addr_ntoa(dns_getserver(1)));
99 #endif
100 #ifdef CONFIG_CMD_TFTPBOOT
101 	if (dhcp->boot_file_name[0] != '\0')
102 		strncpy(boot_file_name, dhcp->boot_file_name,
103 			sizeof(boot_file_name));
104 #endif
105 
106 	printf("DHCP client bound to address %pI4 (%lu ms)\n",
107 	       &dhcp->offered_ip_addr, get_timer(start));
108 
109 	net_lwip_remove_netif(netif);
110 	return CMD_RET_SUCCESS;
111 }
112 
do_dhcp(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])113 int do_dhcp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
114 {
115 	int ret;
116 	struct udevice *dev;
117 
118 	if (net_lwip_eth_start() < 0)
119 		return CMD_RET_FAILURE;
120 
121 	dev = eth_get_dev();
122 	if (!dev) {
123 		log_err("No network device\n");
124 		return CMD_RET_FAILURE;
125 	}
126 
127 	ret = dhcp_loop(dev);
128 	if (ret)
129 		return ret;
130 
131 	if (argc > 1) {
132 		struct cmd_tbl cmdtp = {};
133 
134 		return do_tftpb(&cmdtp, 0, argc, argv);
135 	}
136 
137 	return CMD_RET_SUCCESS;
138 }
139 
dhcp_run(ulong addr,const char * fname,bool autoload)140 int dhcp_run(ulong addr, const char *fname, bool autoload)
141 {
142 	char *dhcp_argv[] = {"dhcp", NULL, };
143 #ifdef CONFIG_CMD_TFTPBOOT
144 	char *tftp_argv[] = {"tftpboot", boot_file_name, NULL, };
145 #endif
146 	struct cmd_tbl cmdtp = {};	/* dummy */
147 
148 	if (autoload) {
149 #ifdef CONFIG_CMD_TFTPBOOT
150 		/* Assume DHCP was already performed */
151 		if (boot_file_name[0])
152 			return do_tftpb(&cmdtp, 0, 2, tftp_argv);
153 		return 0;
154 #else
155 		return -EOPNOTSUPP;
156 #endif
157 	}
158 
159 	return do_dhcp(&cmdtp, 0, 1, dhcp_argv);
160 }
161