]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
c609719b | 2 | /* |
05c3e68f | 3 | * (C) Copyright 2001-2015 |
c609719b | 4 | * Wolfgang Denk, DENX Software Engineering, [email protected]. |
05c3e68f | 5 | * Joe Hershberger, National Instruments |
c609719b WD |
6 | */ |
7 | ||
8 | #include <common.h> | |
52f24238 | 9 | #include <bootstage.h> |
c609719b | 10 | #include <command.h> |
2a64ada7 | 11 | #include <dm.h> |
9fb625ce | 12 | #include <env.h> |
f7ae49fc | 13 | #include <log.h> |
c609719b | 14 | #include <net.h> |
5f184715 | 15 | #include <phy.h> |
401d1c4f | 16 | #include <asm/global_data.h> |
eb41d8a1 | 17 | #include <linux/bug.h> |
1221ce45 | 18 | #include <linux/errno.h> |
3eaac630 | 19 | #include <net/pcap.h> |
818f91eb | 20 | #include "eth_internal.h" |
c609719b | 21 | |
d2eaec60 JH |
22 | DECLARE_GLOBAL_DATA_PTR; |
23 | ||
3bc42700 SG |
24 | /* |
25 | * CPU and board-specific Ethernet initializations. Aliased function | |
26 | * signals caller to move on | |
27 | */ | |
b75d8dc5 | 28 | static int __def_eth_init(struct bd_info *bis) |
3bc42700 SG |
29 | { |
30 | return -1; | |
31 | } | |
b75d8dc5 MY |
32 | int cpu_eth_init(struct bd_info *bis) __attribute__((weak, alias("__def_eth_init"))); |
33 | int board_eth_init(struct bd_info *bis) __attribute__((weak, alias("__def_eth_init"))); | |
3bc42700 | 34 | |
f85b6071 | 35 | #ifdef CONFIG_API |
f85b6071 RJ |
36 | static struct { |
37 | uchar data[PKTSIZE]; | |
38 | int length; | |
39 | } eth_rcv_bufs[PKTBUFSRX]; | |
40 | ||
66c7385a | 41 | static unsigned int eth_rcv_current, eth_rcv_last; |
f85b6071 RJ |
42 | #endif |
43 | ||
f8be7d65 JH |
44 | static struct eth_device *eth_devices; |
45 | struct eth_device *eth_current; | |
c609719b | 46 | |
8607a6bf | 47 | void eth_set_current_to_next(void) |
84eb1fba JH |
48 | { |
49 | eth_current = eth_current->next; | |
50 | } | |
51 | ||
8607a6bf | 52 | void eth_set_dev(struct eth_device *dev) |
e58780dc JH |
53 | { |
54 | eth_current = dev; | |
55 | } | |
56 | ||
d7fb9bcf | 57 | struct eth_device *eth_get_dev_by_name(const char *devname) |
63ff004c MB |
58 | { |
59 | struct eth_device *dev, *target_dev; | |
60 | ||
7e7f903f HR |
61 | BUG_ON(devname == NULL); |
62 | ||
63ff004c MB |
63 | if (!eth_devices) |
64 | return NULL; | |
65 | ||
66 | dev = eth_devices; | |
67 | target_dev = NULL; | |
68 | do { | |
69 | if (strcmp(devname, dev->name) == 0) { | |
70 | target_dev = dev; | |
71 | break; | |
72 | } | |
73 | dev = dev->next; | |
74 | } while (dev != eth_devices); | |
75 | ||
76 | return target_dev; | |
77 | } | |
78 | ||
9e56986a AF |
79 | struct eth_device *eth_get_dev_by_index(int index) |
80 | { | |
81 | struct eth_device *dev, *target_dev; | |
9e56986a AF |
82 | |
83 | if (!eth_devices) | |
84 | return NULL; | |
85 | ||
86 | dev = eth_devices; | |
87 | target_dev = NULL; | |
88 | do { | |
fea7dcae | 89 | if (dev->index == index) { |
9e56986a AF |
90 | target_dev = dev; |
91 | break; | |
92 | } | |
93 | dev = dev->next; | |
9e56986a AF |
94 | } while (dev != eth_devices); |
95 | ||
96 | return target_dev; | |
97 | } | |
98 | ||
66c7385a | 99 | int eth_get_dev_index(void) |
c609719b | 100 | { |
66c7385a | 101 | if (!eth_current) |
fea7dcae | 102 | return -1; |
c609719b | 103 | |
fea7dcae | 104 | return eth_current->index; |
c609719b WD |
105 | } |
106 | ||
6e0d26c0 JH |
107 | static int on_ethaddr(const char *name, const char *value, enum env_op op, |
108 | int flags) | |
109 | { | |
110 | int index; | |
111 | struct eth_device *dev; | |
112 | ||
113 | if (!eth_devices) | |
114 | return 0; | |
115 | ||
116 | /* look for an index after "eth" */ | |
0b1284eb | 117 | index = dectoul(name + 3, NULL); |
6e0d26c0 JH |
118 | |
119 | dev = eth_devices; | |
120 | do { | |
121 | if (dev->index == index) { | |
122 | switch (op) { | |
123 | case env_op_create: | |
124 | case env_op_overwrite: | |
fb8977c5 | 125 | string_to_enetaddr(value, dev->enetaddr); |
73d570a7 | 126 | eth_write_hwaddr(dev, "eth", dev->index); |
6e0d26c0 JH |
127 | break; |
128 | case env_op_delete: | |
a40db6d5 | 129 | memset(dev->enetaddr, 0, ARP_HLEN); |
6e0d26c0 JH |
130 | } |
131 | } | |
7aba0f2c | 132 | dev = dev->next; |
6e0d26c0 JH |
133 | } while (dev != eth_devices); |
134 | ||
135 | return 0; | |
136 | } | |
137 | U_BOOT_ENV_CALLBACK(ethaddr, on_ethaddr); | |
138 | ||
7616e785 SG |
139 | int eth_write_hwaddr(struct eth_device *dev, const char *base_name, |
140 | int eth_number) | |
141 | { | |
a40db6d5 | 142 | unsigned char env_enetaddr[ARP_HLEN]; |
7616e785 SG |
143 | int ret = 0; |
144 | ||
35affd7a | 145 | eth_env_get_enetaddr_by_index(base_name, eth_number, env_enetaddr); |
7616e785 | 146 | |
0adb5b76 JH |
147 | if (!is_zero_ethaddr(env_enetaddr)) { |
148 | if (!is_zero_ethaddr(dev->enetaddr) && | |
a40db6d5 | 149 | memcmp(dev->enetaddr, env_enetaddr, ARP_HLEN)) { |
7616e785 | 150 | printf("\nWarning: %s MAC addresses don't match:\n", |
ff819a3a | 151 | dev->name); |
7616e785 | 152 | printf("Address in SROM is %pM\n", |
ff819a3a | 153 | dev->enetaddr); |
7616e785 | 154 | printf("Address in environment is %pM\n", |
ff819a3a | 155 | env_enetaddr); |
7616e785 SG |
156 | } |
157 | ||
a40db6d5 | 158 | memcpy(dev->enetaddr, env_enetaddr, ARP_HLEN); |
0adb5b76 | 159 | } else if (is_valid_ethaddr(dev->enetaddr)) { |
fd1e959e SG |
160 | eth_env_set_enetaddr_by_index(base_name, eth_number, |
161 | dev->enetaddr); | |
0adb5b76 | 162 | } else if (is_zero_ethaddr(dev->enetaddr)) { |
bef1014b JH |
163 | #ifdef CONFIG_NET_RANDOM_ETHADDR |
164 | net_random_ethaddr(dev->enetaddr); | |
165 | printf("\nWarning: %s (eth%d) using random MAC address - %pM\n", | |
166 | dev->name, eth_number, dev->enetaddr); | |
381e6e54 MS |
167 | eth_env_set_enetaddr_by_index("eth", eth_number, |
168 | dev->enetaddr); | |
bef1014b | 169 | #else |
75d9a45c PM |
170 | printf("\nError: %s address not set.\n", |
171 | dev->name); | |
172 | return -EINVAL; | |
bef1014b | 173 | #endif |
7616e785 SG |
174 | } |
175 | ||
75d9a45c | 176 | if (dev->write_hwaddr && !eth_mac_skip(eth_number)) { |
0adb5b76 | 177 | if (!is_valid_ethaddr(dev->enetaddr)) { |
75d9a45c | 178 | printf("\nError: %s address %pM illegal value\n", |
ff819a3a | 179 | dev->name, dev->enetaddr); |
75d9a45c PM |
180 | return -EINVAL; |
181 | } | |
460f949f | 182 | |
7616e785 | 183 | ret = dev->write_hwaddr(dev); |
75d9a45c | 184 | if (ret) |
ff819a3a JH |
185 | printf("\nWarning: %s failed to set MAC address\n", |
186 | dev->name); | |
460f949f | 187 | } |
7616e785 SG |
188 | |
189 | return ret; | |
190 | } | |
191 | ||
89d48367 SG |
192 | int eth_register(struct eth_device *dev) |
193 | { | |
194 | struct eth_device *d; | |
66c7385a | 195 | static int index; |
58c583b6 | 196 | |
f6add132 | 197 | assert(strlen(dev->name) < sizeof(dev->name)); |
58c583b6 | 198 | |
89d48367 | 199 | if (!eth_devices) { |
ff819a3a JH |
200 | eth_devices = dev; |
201 | eth_current = dev; | |
89d48367 | 202 | eth_current_changed(); |
c609719b | 203 | } else { |
66c7385a | 204 | for (d = eth_devices; d->next != eth_devices; d = d->next) |
aba4b69d | 205 | ; |
c609719b WD |
206 | d->next = dev; |
207 | } | |
208 | ||
209 | dev->state = ETH_STATE_INIT; | |
210 | dev->next = eth_devices; | |
fea7dcae | 211 | dev->index = index++; |
c609719b WD |
212 | |
213 | return 0; | |
214 | } | |
215 | ||
e7e982d6 VP |
216 | int eth_unregister(struct eth_device *dev) |
217 | { | |
218 | struct eth_device *cur; | |
219 | ||
220 | /* No device */ | |
221 | if (!eth_devices) | |
05324a48 | 222 | return -ENODEV; |
e7e982d6 VP |
223 | |
224 | for (cur = eth_devices; cur->next != eth_devices && cur->next != dev; | |
225 | cur = cur->next) | |
226 | ; | |
227 | ||
228 | /* Device not found */ | |
229 | if (cur->next != dev) | |
05324a48 | 230 | return -ENODEV; |
e7e982d6 VP |
231 | |
232 | cur->next = dev->next; | |
233 | ||
234 | if (eth_devices == dev) | |
235 | eth_devices = dev->next == eth_devices ? NULL : dev->next; | |
236 | ||
237 | if (eth_current == dev) { | |
238 | eth_current = eth_devices; | |
239 | eth_current_changed(); | |
240 | } | |
241 | ||
242 | return 0; | |
243 | } | |
244 | ||
d2eaec60 | 245 | int eth_initialize(void) |
c609719b | 246 | { |
fea7dcae | 247 | int num_devices = 0; |
3bc42700 | 248 | |
c609719b WD |
249 | eth_devices = NULL; |
250 | eth_current = NULL; | |
3bc42700 | 251 | eth_common_init(); |
818f91eb SG |
252 | /* |
253 | * If board-specific initialization exists, call it. | |
254 | * If not, call a CPU-specific one | |
255 | */ | |
256 | if (board_eth_init != __def_eth_init) { | |
257 | if (board_eth_init(gd->bd) < 0) | |
258 | printf("Board Net Initialization Failed\n"); | |
259 | } else if (cpu_eth_init != __def_eth_init) { | |
260 | if (cpu_eth_init(gd->bd) < 0) | |
261 | printf("CPU Net Initialization Failed\n"); | |
262 | } else { | |
263 | printf("Net Initialization Skipped\n"); | |
264 | } | |
d9785c14 | 265 | |
c609719b | 266 | if (!eth_devices) { |
ad895913 | 267 | log_err("No ethernet found.\n"); |
770605e4 | 268 | bootstage_error(BOOTSTAGE_ID_NET_ETH_START); |
c609719b WD |
269 | } else { |
270 | struct eth_device *dev = eth_devices; | |
00caae6d | 271 | char *ethprime = env_get("ethprime"); |
c609719b | 272 | |
770605e4 | 273 | bootstage_mark(BOOTSTAGE_ID_NET_ETH_INIT); |
c609719b | 274 | do { |
fea7dcae | 275 | if (dev->index) |
66c7385a | 276 | puts(", "); |
c609719b WD |
277 | |
278 | printf("%s", dev->name); | |
279 | ||
66c7385a | 280 | if (ethprime && strcmp(dev->name, ethprime) == 0) { |
c609719b | 281 | eth_current = dev; |
66c7385a | 282 | puts(" [PRIME]"); |
c609719b WD |
283 | } |
284 | ||
1384f3bb | 285 | if (strchr(dev->name, ' ')) |
66c7385a JH |
286 | puts("\nWarning: eth device name has a space!" |
287 | "\n"); | |
1384f3bb | 288 | |
75d9a45c | 289 | eth_write_hwaddr(dev, "eth", dev->index); |
c609719b | 290 | |
c609719b | 291 | dev = dev->next; |
fea7dcae | 292 | num_devices++; |
66c7385a | 293 | } while (dev != eth_devices); |
c609719b | 294 | |
89d48367 | 295 | eth_current_changed(); |
66c7385a | 296 | putc('\n'); |
c609719b WD |
297 | } |
298 | ||
fea7dcae | 299 | return num_devices; |
c609719b WD |
300 | } |
301 | ||
53a5c424 DU |
302 | /* Multicast. |
303 | * mcast_addr: multicast ipaddr from which multicast Mac is made | |
85eb5caf | 304 | * join: 1=join, 0=leave. |
53a5c424 | 305 | */ |
049a95a7 | 306 | int eth_mcast_join(struct in_addr mcast_ip, int join) |
53a5c424 | 307 | { |
a40db6d5 | 308 | u8 mcast_mac[ARP_HLEN]; |
85eb5caf | 309 | if (!eth_current || !eth_current->mcast) |
53a5c424 | 310 | return -1; |
049a95a7 JH |
311 | mcast_mac[5] = htonl(mcast_ip.s_addr) & 0xff; |
312 | mcast_mac[4] = (htonl(mcast_ip.s_addr)>>8) & 0xff; | |
313 | mcast_mac[3] = (htonl(mcast_ip.s_addr)>>16) & 0x7f; | |
53a5c424 DU |
314 | mcast_mac[2] = 0x5e; |
315 | mcast_mac[1] = 0x0; | |
316 | mcast_mac[0] = 0x1; | |
317 | return eth_current->mcast(eth_current, mcast_mac, join); | |
318 | } | |
319 | ||
d2eaec60 | 320 | int eth_init(void) |
c609719b | 321 | { |
6e0d26c0 | 322 | struct eth_device *old_current; |
c609719b | 323 | |
6bc11388 | 324 | if (!eth_current) { |
ad895913 | 325 | log_err("No ethernet found.\n"); |
05324a48 | 326 | return -ENODEV; |
6bc11388 | 327 | } |
c609719b WD |
328 | |
329 | old_current = eth_current; | |
330 | do { | |
0ebf04c6 | 331 | debug("Trying %s\n", eth_current->name); |
c609719b | 332 | |
d2eaec60 | 333 | if (eth_current->init(eth_current, gd->bd) >= 0) { |
c609719b WD |
334 | eth_current->state = ETH_STATE_ACTIVE; |
335 | ||
505be87a | 336 | return 0; |
c609719b | 337 | } |
0ebf04c6 | 338 | debug("FAIL\n"); |
c609719b WD |
339 | |
340 | eth_try_another(0); | |
341 | } while (old_current != eth_current); | |
342 | ||
05324a48 | 343 | return -ETIMEDOUT; |
c609719b WD |
344 | } |
345 | ||
346 | void eth_halt(void) | |
347 | { | |
348 | if (!eth_current) | |
349 | return; | |
350 | ||
351 | eth_current->halt(eth_current); | |
352 | ||
353 | eth_current->state = ETH_STATE_PASSIVE; | |
354 | } | |
355 | ||
eaa8a195 BN |
356 | int eth_is_active(struct eth_device *dev) |
357 | { | |
358 | return dev && dev->state == ETH_STATE_ACTIVE; | |
359 | } | |
360 | ||
db288a96 | 361 | int eth_send(void *packet, int length) |
c609719b | 362 | { |
3eaac630 RF |
363 | int ret; |
364 | ||
c609719b | 365 | if (!eth_current) |
05324a48 | 366 | return -ENODEV; |
c609719b | 367 | |
3eaac630 RF |
368 | ret = eth_current->send(eth_current, packet, length); |
369 | #if defined(CONFIG_CMD_PCAP) | |
370 | if (ret >= 0) | |
c1ab7381 | 371 | pcap_post(packet, length, true); |
3eaac630 RF |
372 | #endif |
373 | return ret; | |
c609719b WD |
374 | } |
375 | ||
376 | int eth_rx(void) | |
377 | { | |
378 | if (!eth_current) | |
05324a48 | 379 | return -ENODEV; |
c609719b WD |
380 | |
381 | return eth_current->recv(eth_current); | |
382 | } | |
383 | ||
f85b6071 | 384 | #ifdef CONFIG_API |
db288a96 | 385 | static void eth_save_packet(void *packet, int length) |
f85b6071 | 386 | { |
db288a96 | 387 | char *p = packet; |
f85b6071 RJ |
388 | int i; |
389 | ||
390 | if ((eth_rcv_last+1) % PKTBUFSRX == eth_rcv_current) | |
391 | return; | |
392 | ||
393 | if (PKTSIZE < length) | |
394 | return; | |
395 | ||
396 | for (i = 0; i < length; i++) | |
397 | eth_rcv_bufs[eth_rcv_last].data[i] = p[i]; | |
398 | ||
399 | eth_rcv_bufs[eth_rcv_last].length = length; | |
400 | eth_rcv_last = (eth_rcv_last + 1) % PKTBUFSRX; | |
401 | } | |
402 | ||
db288a96 | 403 | int eth_receive(void *packet, int length) |
f85b6071 | 404 | { |
db288a96 | 405 | char *p = packet; |
f85b6071 RJ |
406 | void *pp = push_packet; |
407 | int i; | |
408 | ||
409 | if (eth_rcv_current == eth_rcv_last) { | |
410 | push_packet = eth_save_packet; | |
411 | eth_rx(); | |
412 | push_packet = pp; | |
413 | ||
414 | if (eth_rcv_current == eth_rcv_last) | |
415 | return -1; | |
416 | } | |
417 | ||
46c07bcf | 418 | length = min(eth_rcv_bufs[eth_rcv_current].length, length); |
f85b6071 RJ |
419 | |
420 | for (i = 0; i < length; i++) | |
421 | p[i] = eth_rcv_bufs[eth_rcv_current].data[i]; | |
422 | ||
423 | eth_rcv_current = (eth_rcv_current + 1) % PKTBUFSRX; | |
424 | return length; | |
425 | } | |
426 | #endif /* CONFIG_API */ |