]>
Commit | Line | Data |
---|---|---|
c609719b | 1 | /* |
aba4b69d | 2 | * (C) Copyright 2001-2010 |
c609719b WD |
3 | * Wolfgang Denk, DENX Software Engineering, [email protected]. |
4 | * | |
1a459660 | 5 | * SPDX-License-Identifier: GPL-2.0+ |
c609719b WD |
6 | */ |
7 | ||
8 | #include <common.h> | |
9 | #include <command.h> | |
10 | #include <net.h> | |
d9785c14 | 11 | #include <miiphy.h> |
5f184715 | 12 | #include <phy.h> |
c609719b | 13 | |
3f6e6993 MF |
14 | void eth_parse_enetaddr(const char *addr, uchar *enetaddr) |
15 | { | |
16 | char *end; | |
17 | int i; | |
18 | ||
19 | for (i = 0; i < 6; ++i) { | |
20 | enetaddr[i] = addr ? simple_strtoul(addr, &end, 16) : 0; | |
21 | if (addr) | |
22 | addr = (*end) ? end + 1 : end; | |
23 | } | |
24 | } | |
25 | ||
26 | int eth_getenv_enetaddr(char *name, uchar *enetaddr) | |
27 | { | |
28 | eth_parse_enetaddr(getenv(name), enetaddr); | |
29 | return is_valid_ether_addr(enetaddr); | |
30 | } | |
31 | ||
32 | int eth_setenv_enetaddr(char *name, const uchar *enetaddr) | |
33 | { | |
34 | char buf[20]; | |
35 | ||
36 | sprintf(buf, "%pM", enetaddr); | |
37 | ||
38 | return setenv(name, buf); | |
39 | } | |
86848a74 | 40 | |
7616e785 SG |
41 | int eth_getenv_enetaddr_by_index(const char *base_name, int index, |
42 | uchar *enetaddr) | |
86848a74 MF |
43 | { |
44 | char enetvar[32]; | |
7616e785 | 45 | sprintf(enetvar, index ? "%s%daddr" : "%saddr", base_name, index); |
86848a74 MF |
46 | return eth_getenv_enetaddr(enetvar, enetaddr); |
47 | } | |
3f6e6993 | 48 | |
154177e1 | 49 | static inline int eth_setenv_enetaddr_by_index(const char *base_name, int index, |
c88ef3c1 RH |
50 | uchar *enetaddr) |
51 | { | |
52 | char enetvar[32]; | |
53 | sprintf(enetvar, index ? "%s%daddr" : "%saddr", base_name, index); | |
54 | return eth_setenv_enetaddr(enetvar, enetaddr); | |
55 | } | |
56 | ||
57 | ||
ecee9324 BW |
58 | static int eth_mac_skip(int index) |
59 | { | |
60 | char enetvar[15]; | |
61 | char *skip_state; | |
62 | sprintf(enetvar, index ? "eth%dmacskip" : "ethmacskip", index); | |
63 | return ((skip_state = getenv(enetvar)) != NULL); | |
64 | } | |
65 | ||
03c1b04f MW |
66 | #ifdef CONFIG_RANDOM_MACADDR |
67 | void eth_random_enetaddr(uchar *enetaddr) | |
68 | { | |
69 | uint32_t rval; | |
70 | ||
71 | srand(get_timer(0)); | |
72 | ||
73 | rval = rand(); | |
74 | enetaddr[0] = rval & 0xff; | |
75 | enetaddr[1] = (rval >> 8) & 0xff; | |
76 | enetaddr[2] = (rval >> 16) & 0xff; | |
77 | ||
78 | rval = rand(); | |
79 | enetaddr[3] = rval & 0xff; | |
80 | enetaddr[4] = (rval >> 8) & 0xff; | |
81 | enetaddr[5] = (rval >> 16) & 0xff; | |
82 | ||
83 | /* make sure it's local and unicast */ | |
84 | enetaddr[0] = (enetaddr[0] | 0x02) & ~0x01; | |
85 | } | |
86 | #endif | |
87 | ||
dd35479a BW |
88 | /* |
89 | * CPU and board-specific Ethernet initializations. Aliased function | |
90 | * signals caller to move on | |
91 | */ | |
92 | static int __def_eth_init(bd_t *bis) | |
93 | { | |
94 | return -1; | |
95 | } | |
f9a109b3 PT |
96 | int cpu_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init"))); |
97 | int board_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init"))); | |
dd35479a | 98 | |
f85b6071 | 99 | #ifdef CONFIG_API |
f85b6071 RJ |
100 | static struct { |
101 | uchar data[PKTSIZE]; | |
102 | int length; | |
103 | } eth_rcv_bufs[PKTBUFSRX]; | |
104 | ||
66c7385a | 105 | static unsigned int eth_rcv_current, eth_rcv_last; |
f85b6071 RJ |
106 | #endif |
107 | ||
f8be7d65 JH |
108 | static struct eth_device *eth_devices; |
109 | struct eth_device *eth_current; | |
c609719b | 110 | |
d7fb9bcf | 111 | struct eth_device *eth_get_dev_by_name(const char *devname) |
63ff004c MB |
112 | { |
113 | struct eth_device *dev, *target_dev; | |
114 | ||
7e7f903f HR |
115 | BUG_ON(devname == NULL); |
116 | ||
63ff004c MB |
117 | if (!eth_devices) |
118 | return NULL; | |
119 | ||
120 | dev = eth_devices; | |
121 | target_dev = NULL; | |
122 | do { | |
123 | if (strcmp(devname, dev->name) == 0) { | |
124 | target_dev = dev; | |
125 | break; | |
126 | } | |
127 | dev = dev->next; | |
128 | } while (dev != eth_devices); | |
129 | ||
130 | return target_dev; | |
131 | } | |
132 | ||
9e56986a AF |
133 | struct eth_device *eth_get_dev_by_index(int index) |
134 | { | |
135 | struct eth_device *dev, *target_dev; | |
9e56986a AF |
136 | |
137 | if (!eth_devices) | |
138 | return NULL; | |
139 | ||
140 | dev = eth_devices; | |
141 | target_dev = NULL; | |
142 | do { | |
fea7dcae | 143 | if (dev->index == index) { |
9e56986a AF |
144 | target_dev = dev; |
145 | break; | |
146 | } | |
147 | dev = dev->next; | |
9e56986a AF |
148 | } while (dev != eth_devices); |
149 | ||
150 | return target_dev; | |
151 | } | |
152 | ||
66c7385a | 153 | int eth_get_dev_index(void) |
c609719b | 154 | { |
66c7385a | 155 | if (!eth_current) |
fea7dcae | 156 | return -1; |
c609719b | 157 | |
fea7dcae | 158 | return eth_current->index; |
c609719b WD |
159 | } |
160 | ||
89d48367 | 161 | static void eth_current_changed(void) |
c609719b | 162 | { |
e2a53458 MF |
163 | char *act = getenv("ethact"); |
164 | /* update current ethernet name */ | |
165 | if (eth_current) { | |
166 | if (act == NULL || strcmp(act, eth_current->name) != 0) | |
167 | setenv("ethact", eth_current->name); | |
89d48367 | 168 | } |
e2a53458 MF |
169 | /* |
170 | * remove the variable completely if there is no active | |
171 | * interface | |
172 | */ | |
173 | else if (act != NULL) | |
174 | setenv("ethact", NULL); | |
89d48367 SG |
175 | } |
176 | ||
7616e785 SG |
177 | int eth_write_hwaddr(struct eth_device *dev, const char *base_name, |
178 | int eth_number) | |
179 | { | |
180 | unsigned char env_enetaddr[6]; | |
181 | int ret = 0; | |
182 | ||
69376644 | 183 | eth_getenv_enetaddr_by_index(base_name, eth_number, env_enetaddr); |
7616e785 SG |
184 | |
185 | if (memcmp(env_enetaddr, "\0\0\0\0\0\0", 6)) { | |
186 | if (memcmp(dev->enetaddr, "\0\0\0\0\0\0", 6) && | |
66c7385a | 187 | memcmp(dev->enetaddr, env_enetaddr, 6)) { |
7616e785 SG |
188 | printf("\nWarning: %s MAC addresses don't match:\n", |
189 | dev->name); | |
190 | printf("Address in SROM is %pM\n", | |
191 | dev->enetaddr); | |
192 | printf("Address in environment is %pM\n", | |
193 | env_enetaddr); | |
194 | } | |
195 | ||
196 | memcpy(dev->enetaddr, env_enetaddr, 6); | |
c88ef3c1 RH |
197 | } else if (is_valid_ether_addr(dev->enetaddr)) { |
198 | eth_setenv_enetaddr_by_index(base_name, eth_number, | |
199 | dev->enetaddr); | |
200 | printf("\nWarning: %s using MAC address from net device\n", | |
201 | dev->name); | |
7616e785 SG |
202 | } |
203 | ||
204 | if (dev->write_hwaddr && | |
460f949f BT |
205 | !eth_mac_skip(eth_number)) { |
206 | if (!is_valid_ether_addr(dev->enetaddr)) | |
207 | return -1; | |
208 | ||
7616e785 | 209 | ret = dev->write_hwaddr(dev); |
460f949f | 210 | } |
7616e785 SG |
211 | |
212 | return ret; | |
213 | } | |
214 | ||
89d48367 SG |
215 | int eth_register(struct eth_device *dev) |
216 | { | |
217 | struct eth_device *d; | |
66c7385a | 218 | static int index; |
58c583b6 | 219 | |
f6add132 | 220 | assert(strlen(dev->name) < sizeof(dev->name)); |
58c583b6 | 221 | |
89d48367 SG |
222 | if (!eth_devices) { |
223 | eth_current = eth_devices = dev; | |
224 | eth_current_changed(); | |
c609719b | 225 | } else { |
66c7385a | 226 | for (d = eth_devices; d->next != eth_devices; d = d->next) |
aba4b69d | 227 | ; |
c609719b WD |
228 | d->next = dev; |
229 | } | |
230 | ||
231 | dev->state = ETH_STATE_INIT; | |
232 | dev->next = eth_devices; | |
fea7dcae | 233 | dev->index = index++; |
c609719b WD |
234 | |
235 | return 0; | |
236 | } | |
237 | ||
e7e982d6 VP |
238 | int eth_unregister(struct eth_device *dev) |
239 | { | |
240 | struct eth_device *cur; | |
241 | ||
242 | /* No device */ | |
243 | if (!eth_devices) | |
244 | return -1; | |
245 | ||
246 | for (cur = eth_devices; cur->next != eth_devices && cur->next != dev; | |
247 | cur = cur->next) | |
248 | ; | |
249 | ||
250 | /* Device not found */ | |
251 | if (cur->next != dev) | |
252 | return -1; | |
253 | ||
254 | cur->next = dev->next; | |
255 | ||
256 | if (eth_devices == dev) | |
257 | eth_devices = dev->next == eth_devices ? NULL : dev->next; | |
258 | ||
259 | if (eth_current == dev) { | |
260 | eth_current = eth_devices; | |
261 | eth_current_changed(); | |
262 | } | |
263 | ||
264 | return 0; | |
265 | } | |
266 | ||
de30122b MF |
267 | static void eth_env_init(bd_t *bis) |
268 | { | |
269 | const char *s; | |
270 | ||
271 | if ((s = getenv("bootfile")) != NULL) | |
272 | copy_filename(BootFile, s, sizeof(BootFile)); | |
273 | } | |
274 | ||
c609719b WD |
275 | int eth_initialize(bd_t *bis) |
276 | { | |
fea7dcae | 277 | int num_devices = 0; |
c609719b WD |
278 | eth_devices = NULL; |
279 | eth_current = NULL; | |
280 | ||
770605e4 | 281 | bootstage_mark(BOOTSTAGE_ID_NET_ETH_START); |
27ee59af | 282 | #if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB) |
d9785c14 MB |
283 | miiphy_init(); |
284 | #endif | |
5f184715 AF |
285 | |
286 | #ifdef CONFIG_PHYLIB | |
287 | phy_init(); | |
288 | #endif | |
289 | ||
de30122b MF |
290 | eth_env_init(bis); |
291 | ||
8ad25bf8 BW |
292 | /* |
293 | * If board-specific initialization exists, call it. | |
294 | * If not, call a CPU-specific one | |
295 | */ | |
296 | if (board_eth_init != __def_eth_init) { | |
297 | if (board_eth_init(bis) < 0) | |
298 | printf("Board Net Initialization Failed\n"); | |
299 | } else if (cpu_eth_init != __def_eth_init) { | |
300 | if (cpu_eth_init(bis) < 0) | |
301 | printf("CPU Net Initialization Failed\n"); | |
302 | } else | |
303 | printf("Net Initialization Skipped\n"); | |
d9785c14 | 304 | |
c609719b | 305 | if (!eth_devices) { |
66c7385a | 306 | puts("No ethernet found.\n"); |
770605e4 | 307 | bootstage_error(BOOTSTAGE_ID_NET_ETH_START); |
c609719b WD |
308 | } else { |
309 | struct eth_device *dev = eth_devices; | |
66c7385a | 310 | char *ethprime = getenv("ethprime"); |
c609719b | 311 | |
770605e4 | 312 | bootstage_mark(BOOTSTAGE_ID_NET_ETH_INIT); |
c609719b | 313 | do { |
fea7dcae | 314 | if (dev->index) |
66c7385a | 315 | puts(", "); |
c609719b WD |
316 | |
317 | printf("%s", dev->name); | |
318 | ||
66c7385a | 319 | if (ethprime && strcmp(dev->name, ethprime) == 0) { |
c609719b | 320 | eth_current = dev; |
66c7385a | 321 | puts(" [PRIME]"); |
c609719b WD |
322 | } |
323 | ||
1384f3bb | 324 | if (strchr(dev->name, ' ')) |
66c7385a JH |
325 | puts("\nWarning: eth device name has a space!" |
326 | "\n"); | |
1384f3bb | 327 | |
fea7dcae | 328 | if (eth_write_hwaddr(dev, "eth", dev->index)) |
a9a730e0 | 329 | puts("\nWarning: failed to set MAC address\n"); |
c609719b | 330 | |
c609719b | 331 | dev = dev->next; |
fea7dcae | 332 | num_devices++; |
66c7385a | 333 | } while (dev != eth_devices); |
c609719b | 334 | |
89d48367 | 335 | eth_current_changed(); |
66c7385a | 336 | putc('\n'); |
c609719b WD |
337 | } |
338 | ||
fea7dcae | 339 | return num_devices; |
c609719b WD |
340 | } |
341 | ||
53a5c424 DU |
342 | #ifdef CONFIG_MCAST_TFTP |
343 | /* Multicast. | |
344 | * mcast_addr: multicast ipaddr from which multicast Mac is made | |
85eb5caf | 345 | * join: 1=join, 0=leave. |
53a5c424 | 346 | */ |
66c7385a | 347 | int eth_mcast_join(IPaddr_t mcast_ip, u8 join) |
53a5c424 | 348 | { |
66c7385a | 349 | u8 mcast_mac[6]; |
85eb5caf | 350 | if (!eth_current || !eth_current->mcast) |
53a5c424 DU |
351 | return -1; |
352 | mcast_mac[5] = htonl(mcast_ip) & 0xff; | |
353 | mcast_mac[4] = (htonl(mcast_ip)>>8) & 0xff; | |
354 | mcast_mac[3] = (htonl(mcast_ip)>>16) & 0x7f; | |
355 | mcast_mac[2] = 0x5e; | |
356 | mcast_mac[1] = 0x0; | |
357 | mcast_mac[0] = 0x1; | |
358 | return eth_current->mcast(eth_current, mcast_mac, join); | |
359 | } | |
360 | ||
85eb5caf WD |
361 | /* the 'way' for ethernet-CRC-32. Spliced in from Linux lib/crc32.c |
362 | * and this is the ethernet-crc method needed for TSEC -- and perhaps | |
53a5c424 DU |
363 | * some other adapter -- hash tables |
364 | */ | |
365 | #define CRCPOLY_LE 0xedb88320 | |
66c7385a | 366 | u32 ether_crc(size_t len, unsigned char const *p) |
53a5c424 DU |
367 | { |
368 | int i; | |
369 | u32 crc; | |
370 | crc = ~0; | |
371 | while (len--) { | |
372 | crc ^= *p++; | |
373 | for (i = 0; i < 8; i++) | |
374 | crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0); | |
375 | } | |
376 | /* an reverse the bits, cuz of way they arrive -- last-first */ | |
377 | crc = (crc >> 16) | (crc << 16); | |
378 | crc = (crc >> 8 & 0x00ff00ff) | (crc << 8 & 0xff00ff00); | |
379 | crc = (crc >> 4 & 0x0f0f0f0f) | (crc << 4 & 0xf0f0f0f0); | |
380 | crc = (crc >> 2 & 0x33333333) | (crc << 2 & 0xcccccccc); | |
381 | crc = (crc >> 1 & 0x55555555) | (crc << 1 & 0xaaaaaaaa); | |
382 | return crc; | |
383 | } | |
384 | ||
385 | #endif | |
386 | ||
c609719b WD |
387 | |
388 | int eth_init(bd_t *bis) | |
389 | { | |
86848a74 | 390 | struct eth_device *old_current, *dev; |
c609719b | 391 | |
6bc11388 | 392 | if (!eth_current) { |
66c7385a | 393 | puts("No ethernet found.\n"); |
505be87a | 394 | return -1; |
6bc11388 | 395 | } |
c609719b | 396 | |
86848a74 | 397 | /* Sync environment with network devices */ |
86848a74 MF |
398 | dev = eth_devices; |
399 | do { | |
400 | uchar env_enetaddr[6]; | |
401 | ||
fea7dcae | 402 | if (eth_getenv_enetaddr_by_index("eth", dev->index, |
7616e785 | 403 | env_enetaddr)) |
86848a74 MF |
404 | memcpy(dev->enetaddr, env_enetaddr, 6); |
405 | ||
86848a74 MF |
406 | dev = dev->next; |
407 | } while (dev != eth_devices); | |
408 | ||
c609719b WD |
409 | old_current = eth_current; |
410 | do { | |
0ebf04c6 | 411 | debug("Trying %s\n", eth_current->name); |
c609719b | 412 | |
66c7385a | 413 | if (eth_current->init(eth_current, bis) >= 0) { |
c609719b WD |
414 | eth_current->state = ETH_STATE_ACTIVE; |
415 | ||
505be87a | 416 | return 0; |
c609719b | 417 | } |
0ebf04c6 | 418 | debug("FAIL\n"); |
c609719b WD |
419 | |
420 | eth_try_another(0); | |
421 | } while (old_current != eth_current); | |
422 | ||
505be87a | 423 | return -1; |
c609719b WD |
424 | } |
425 | ||
426 | void eth_halt(void) | |
427 | { | |
428 | if (!eth_current) | |
429 | return; | |
430 | ||
431 | eth_current->halt(eth_current); | |
432 | ||
433 | eth_current->state = ETH_STATE_PASSIVE; | |
434 | } | |
435 | ||
db288a96 | 436 | int eth_send(void *packet, int length) |
c609719b WD |
437 | { |
438 | if (!eth_current) | |
439 | return -1; | |
440 | ||
441 | return eth_current->send(eth_current, packet, length); | |
442 | } | |
443 | ||
444 | int eth_rx(void) | |
445 | { | |
446 | if (!eth_current) | |
447 | return -1; | |
448 | ||
449 | return eth_current->recv(eth_current); | |
450 | } | |
451 | ||
f85b6071 | 452 | #ifdef CONFIG_API |
db288a96 | 453 | static void eth_save_packet(void *packet, int length) |
f85b6071 | 454 | { |
db288a96 | 455 | char *p = packet; |
f85b6071 RJ |
456 | int i; |
457 | ||
458 | if ((eth_rcv_last+1) % PKTBUFSRX == eth_rcv_current) | |
459 | return; | |
460 | ||
461 | if (PKTSIZE < length) | |
462 | return; | |
463 | ||
464 | for (i = 0; i < length; i++) | |
465 | eth_rcv_bufs[eth_rcv_last].data[i] = p[i]; | |
466 | ||
467 | eth_rcv_bufs[eth_rcv_last].length = length; | |
468 | eth_rcv_last = (eth_rcv_last + 1) % PKTBUFSRX; | |
469 | } | |
470 | ||
db288a96 | 471 | int eth_receive(void *packet, int length) |
f85b6071 | 472 | { |
db288a96 | 473 | char *p = packet; |
f85b6071 RJ |
474 | void *pp = push_packet; |
475 | int i; | |
476 | ||
477 | if (eth_rcv_current == eth_rcv_last) { | |
478 | push_packet = eth_save_packet; | |
479 | eth_rx(); | |
480 | push_packet = pp; | |
481 | ||
482 | if (eth_rcv_current == eth_rcv_last) | |
483 | return -1; | |
484 | } | |
485 | ||
46c07bcf | 486 | length = min(eth_rcv_bufs[eth_rcv_current].length, length); |
f85b6071 RJ |
487 | |
488 | for (i = 0; i < length; i++) | |
489 | p[i] = eth_rcv_bufs[eth_rcv_current].data[i]; | |
490 | ||
491 | eth_rcv_current = (eth_rcv_current + 1) % PKTBUFSRX; | |
492 | return length; | |
493 | } | |
494 | #endif /* CONFIG_API */ | |
495 | ||
c609719b WD |
496 | void eth_try_another(int first_restart) |
497 | { | |
66c7385a | 498 | static struct eth_device *first_failed; |
c650e1be | 499 | char *ethrotate; |
e1692577 MF |
500 | |
501 | /* | |
502 | * Do not rotate between network interfaces when | |
503 | * 'ethrotate' variable is set to 'no'. | |
504 | */ | |
66c7385a JH |
505 | ethrotate = getenv("ethrotate"); |
506 | if ((ethrotate != NULL) && (strcmp(ethrotate, "no") == 0)) | |
e1692577 | 507 | return; |
c609719b WD |
508 | |
509 | if (!eth_current) | |
510 | return; | |
511 | ||
66c7385a | 512 | if (first_restart) |
c609719b | 513 | first_failed = eth_current; |
c609719b WD |
514 | |
515 | eth_current = eth_current->next; | |
516 | ||
89d48367 | 517 | eth_current_changed(); |
a3d991bd | 518 | |
66c7385a | 519 | if (first_failed == eth_current) |
c609719b | 520 | NetRestartWrap = 1; |
c609719b WD |
521 | } |
522 | ||
a3d991bd WD |
523 | void eth_set_current(void) |
524 | { | |
66c7385a JH |
525 | static char *act; |
526 | static int env_changed_id; | |
527 | struct eth_device *old_current; | |
2f70c49e | 528 | int env_id; |
a3d991bd WD |
529 | |
530 | if (!eth_current) /* XXX no current */ | |
531 | return; | |
532 | ||
2f70c49e HS |
533 | env_id = get_env_id(); |
534 | if ((act == NULL) || (env_changed_id != env_id)) { | |
535 | act = getenv("ethact"); | |
536 | env_changed_id = env_id; | |
537 | } | |
a3d991bd WD |
538 | if (act != NULL) { |
539 | old_current = eth_current; | |
540 | do { | |
541 | if (strcmp(eth_current->name, act) == 0) | |
542 | return; | |
543 | eth_current = eth_current->next; | |
544 | } while (old_current != eth_current); | |
545 | } | |
546 | ||
89d48367 | 547 | eth_current_changed(); |
a3d991bd | 548 | } |
a3d991bd | 549 | |
66c7385a | 550 | char *eth_get_name(void) |
5bb226e8 | 551 | { |
66c7385a | 552 | return eth_current ? eth_current->name : "unknown"; |
5bb226e8 | 553 | } |