diff options
author | 2023-10-10 14:33:42 +0000 | |
---|---|---|
committer | 2023-10-10 14:33:42 +0000 | |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/u-boot/net/eth-uclass.c | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/u-boot/net/eth-uclass.c')
-rw-r--r-- | roms/u-boot/net/eth-uclass.c | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/roms/u-boot/net/eth-uclass.c b/roms/u-boot/net/eth-uclass.c new file mode 100644 index 000000000..5146bd666 --- /dev/null +++ b/roms/u-boot/net/eth-uclass.c @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2001-2015 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * Joe Hershberger, National Instruments + */ + +#include <common.h> +#include <bootstage.h> +#include <dm.h> +#include <env.h> +#include <log.h> +#include <net.h> +#include <asm/global_data.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> +#include <net/pcap.h> +#include "eth_internal.h" +#include <eth_phy.h> + +DECLARE_GLOBAL_DATA_PTR; + +/** + * struct eth_device_priv - private structure for each Ethernet device + * + * @state: The state of the Ethernet MAC driver (defined by enum eth_state_t) + */ +struct eth_device_priv { + enum eth_state_t state; + bool running; +}; + +/** + * struct eth_uclass_priv - The structure attached to the uclass itself + * + * @current: The Ethernet device that the network functions are using + */ +struct eth_uclass_priv { + struct udevice *current; +}; + +/* eth_errno - This stores the most recent failure code from DM functions */ +static int eth_errno; + +static struct eth_uclass_priv *eth_get_uclass_priv(void) +{ + struct uclass *uc; + int ret; + + ret = uclass_get(UCLASS_ETH, &uc); + if (ret) + return NULL; + + assert(uc); + return uclass_get_priv(uc); +} + +void eth_set_current_to_next(void) +{ + struct eth_uclass_priv *uc_priv; + + uc_priv = eth_get_uclass_priv(); + if (uc_priv->current) + uclass_next_device(&uc_priv->current); + if (!uc_priv->current) + uclass_first_device(UCLASS_ETH, &uc_priv->current); +} + +/* + * Typically this will simply return the active device. + * In the case where the most recent active device was unset, this will attempt + * to return the device with sequence id 0 (which can be configured by the + * device tree). If this fails, fall back to just getting the first device. + * The latter is non-deterministic and depends on the order of the probing. + * If that device doesn't exist or fails to probe, this function will return + * NULL. + */ +struct udevice *eth_get_dev(void) +{ + struct eth_uclass_priv *uc_priv; + + uc_priv = eth_get_uclass_priv(); + if (!uc_priv) + return NULL; + + if (!uc_priv->current) { + eth_errno = uclass_get_device_by_seq(UCLASS_ETH, 0, + &uc_priv->current); + if (eth_errno) + eth_errno = uclass_first_device(UCLASS_ETH, + &uc_priv->current); + } + return uc_priv->current; +} + +/* + * Typically this will just store a device pointer. + * In case it was not probed, we will attempt to do so. + * dev may be NULL to unset the active device. + */ +void eth_set_dev(struct udevice *dev) +{ + if (dev && !device_active(dev)) { + eth_errno = device_probe(dev); + if (eth_errno) + dev = NULL; + } + + eth_get_uclass_priv()->current = dev; +} + +/* + * Find the udevice that either has the name passed in as devname or has an + * alias named devname. + */ +struct udevice *eth_get_dev_by_name(const char *devname) +{ + int seq = -1; + char *endp = NULL; + const char *startp = NULL; + struct udevice *it; + struct uclass *uc; + int len = strlen("eth"); + int ret; + + /* Must be longer than 3 to be an alias */ + if (!strncmp(devname, "eth", len) && strlen(devname) > len) { + startp = devname + len; + seq = simple_strtoul(startp, &endp, 10); + } + + ret = uclass_get(UCLASS_ETH, &uc); + if (ret) + return NULL; + + uclass_foreach_dev(it, uc) { + /* + * We don't care about errors from probe here. Either they won't + * match an alias or it will match a literal name and we'll pick + * up the error when we try to probe again in eth_set_dev(). + */ + if (device_probe(it)) + continue; + /* Check for the name or the sequence number to match */ + if (strcmp(it->name, devname) == 0 || + (endp > startp && dev_seq(it) == seq)) + return it; + } + + return NULL; +} + +unsigned char *eth_get_ethaddr(void) +{ + struct eth_pdata *pdata; + + if (eth_get_dev()) { + pdata = dev_get_plat(eth_get_dev()); + return pdata->enetaddr; + } + + return NULL; +} + +/* Set active state without calling start on the driver */ +int eth_init_state_only(void) +{ + struct udevice *current; + struct eth_device_priv *priv; + + current = eth_get_dev(); + if (!current || !device_active(current)) + return -EINVAL; + + priv = dev_get_uclass_priv(current); + priv->state = ETH_STATE_ACTIVE; + + return 0; +} + +/* Set passive state without calling stop on the driver */ +void eth_halt_state_only(void) +{ + struct udevice *current; + struct eth_device_priv *priv; + + current = eth_get_dev(); + if (!current || !device_active(current)) + return; + + priv = dev_get_uclass_priv(current); + priv->state = ETH_STATE_PASSIVE; +} + +int eth_get_dev_index(void) +{ + if (eth_get_dev()) + return dev_seq(eth_get_dev()); + return -1; +} + +static int eth_write_hwaddr(struct udevice *dev) +{ + struct eth_pdata *pdata; + int ret = 0; + + if (!dev || !device_active(dev)) + return -EINVAL; + + /* seq is valid since the device is active */ + if (eth_get_ops(dev)->write_hwaddr && !eth_mac_skip(dev_seq(dev))) { + pdata = dev_get_plat(dev); + if (!is_valid_ethaddr(pdata->enetaddr)) { + printf("\nError: %s address %pM illegal value\n", + dev->name, pdata->enetaddr); + return -EINVAL; + } + + /* + * Drivers are allowed to decide not to implement this at + * run-time. E.g. Some devices may use it and some may not. + */ + ret = eth_get_ops(dev)->write_hwaddr(dev); + if (ret == -ENOSYS) + ret = 0; + if (ret) + printf("\nWarning: %s failed to set MAC address\n", + dev->name); + } + + return ret; +} + +static int on_ethaddr(const char *name, const char *value, enum env_op op, + int flags) +{ + int index; + int retval; + struct udevice *dev; + + /* look for an index after "eth" */ + index = simple_strtoul(name + 3, NULL, 10); + + retval = uclass_find_device_by_seq(UCLASS_ETH, index, &dev); + if (!retval) { + struct eth_pdata *pdata = dev_get_plat(dev); + switch (op) { + case env_op_create: + case env_op_overwrite: + string_to_enetaddr(value, pdata->enetaddr); + eth_write_hwaddr(dev); + break; + case env_op_delete: + memset(pdata->enetaddr, 0, ARP_HLEN); + } + } + + return 0; +} +U_BOOT_ENV_CALLBACK(ethaddr, on_ethaddr); + +int eth_init(void) +{ + char *ethact = env_get("ethact"); + char *ethrotate = env_get("ethrotate"); + struct udevice *current = NULL; + struct udevice *old_current; + int ret = -ENODEV; + + /* + * When 'ethrotate' variable is set to 'no' and 'ethact' variable + * is already set to an ethernet device, we should stick to 'ethact'. + */ + if ((ethrotate != NULL) && (strcmp(ethrotate, "no") == 0)) { + if (ethact) { + current = eth_get_dev_by_name(ethact); + if (!current) + return -EINVAL; + } + } + + if (!current) { + current = eth_get_dev(); + if (!current) { + log_err("No ethernet found.\n"); + return -ENODEV; + } + } + + old_current = current; + do { + if (current) { + debug("Trying %s\n", current->name); + + if (device_active(current)) { + ret = eth_get_ops(current)->start(current); + if (ret >= 0) { + struct eth_device_priv *priv = + dev_get_uclass_priv(current); + + priv->state = ETH_STATE_ACTIVE; + priv->running = true; + return 0; + } + } else { + ret = eth_errno; + } + + debug("FAIL\n"); + } else { + debug("PROBE FAIL\n"); + } + + /* + * If ethrotate is enabled, this will change "current", + * otherwise we will drop out of this while loop immediately + */ + eth_try_another(0); + /* This will ensure the new "current" attempted to probe */ + current = eth_get_dev(); + } while (old_current != current); + + return ret; +} + +void eth_halt(void) +{ + struct udevice *current; + struct eth_device_priv *priv; + + current = eth_get_dev(); + if (!current) + return; + + priv = dev_get_uclass_priv(current); + if (!priv || !priv->running) + return; + + eth_get_ops(current)->stop(current); + priv->state = ETH_STATE_PASSIVE; + priv->running = false; +} + +int eth_is_active(struct udevice *dev) +{ + struct eth_device_priv *priv; + + if (!dev || !device_active(dev)) + return 0; + + priv = dev_get_uclass_priv(dev); + return priv->state == ETH_STATE_ACTIVE; +} + +int eth_send(void *packet, int length) +{ + struct udevice *current; + int ret; + + current = eth_get_dev(); + if (!current) + return -ENODEV; + + if (!eth_is_active(current)) + return -EINVAL; + + ret = eth_get_ops(current)->send(current, packet, length); + if (ret < 0) { + /* We cannot completely return the error at present */ + debug("%s: send() returned error %d\n", __func__, ret); + } +#if defined(CONFIG_CMD_PCAP) + if (ret >= 0) + pcap_post(packet, length, true); +#endif + return ret; +} + +int eth_rx(void) +{ + struct udevice *current; + uchar *packet; + int flags; + int ret; + int i; + + current = eth_get_dev(); + if (!current) + return -ENODEV; + + if (!eth_is_active(current)) + return -EINVAL; + + /* Process up to 32 packets at one time */ + flags = ETH_RECV_CHECK_DEVICE; + for (i = 0; i < ETH_PACKETS_BATCH_RECV; i++) { + ret = eth_get_ops(current)->recv(current, flags, &packet); + flags = 0; + if (ret > 0) + net_process_received_packet(packet, ret); + if (ret >= 0 && eth_get_ops(current)->free_pkt) + eth_get_ops(current)->free_pkt(current, packet, ret); + if (ret <= 0) + break; + } + if (ret == -EAGAIN) + ret = 0; + if (ret < 0) { + /* We cannot completely return the error at present */ + debug("%s: recv() returned error %d\n", __func__, ret); + } + return ret; +} + +int eth_initialize(void) +{ + int num_devices = 0; + struct udevice *dev; + + eth_common_init(); + + /* + * Devices need to write the hwaddr even if not started so that Linux + * will have access to the hwaddr that u-boot stored for the device. + * This is accomplished by attempting to probe each device and calling + * their write_hwaddr() operation. + */ + uclass_first_device_check(UCLASS_ETH, &dev); + if (!dev) { + log_err("No ethernet found.\n"); + bootstage_error(BOOTSTAGE_ID_NET_ETH_START); + } else { + char *ethprime = env_get("ethprime"); + struct udevice *prime_dev = NULL; + + if (ethprime) + prime_dev = eth_get_dev_by_name(ethprime); + if (prime_dev) { + eth_set_dev(prime_dev); + eth_current_changed(); + } else { + eth_set_dev(NULL); + } + + bootstage_mark(BOOTSTAGE_ID_NET_ETH_INIT); + do { + if (device_active(dev)) { + if (num_devices) + printf(", "); + + printf("eth%d: %s", dev_seq(dev), dev->name); + + if (ethprime && dev == prime_dev) + printf(" [PRIME]"); + } + + eth_write_hwaddr(dev); + + if (device_active(dev)) + num_devices++; + uclass_next_device_check(&dev); + } while (dev); + + if (!num_devices) + log_err("No ethernet found.\n"); + putc('\n'); + } + + return num_devices; +} + +static int eth_post_bind(struct udevice *dev) +{ + if (strchr(dev->name, ' ')) { + printf("\nError: eth device name \"%s\" has a space!\n", + dev->name); + return -EINVAL; + } + +#ifdef CONFIG_DM_ETH_PHY + eth_phy_binds_nodes(dev); +#endif + + return 0; +} + +static int eth_pre_unbind(struct udevice *dev) +{ + /* Don't hang onto a pointer that is going away */ + if (dev == eth_get_uclass_priv()->current) + eth_set_dev(NULL); + + return 0; +} + +static bool eth_dev_get_mac_address(struct udevice *dev, u8 mac[ARP_HLEN]) +{ +#if CONFIG_IS_ENABLED(OF_CONTROL) + const uint8_t *p; + + p = dev_read_u8_array_ptr(dev, "mac-address", ARP_HLEN); + if (!p) + p = dev_read_u8_array_ptr(dev, "local-mac-address", ARP_HLEN); + + if (!p) + return false; + + memcpy(mac, p, ARP_HLEN); + + return true; +#else + return false; +#endif +} + +static int eth_post_probe(struct udevice *dev) +{ + struct eth_device_priv *priv = dev_get_uclass_priv(dev); + struct eth_pdata *pdata = dev_get_plat(dev); + unsigned char env_enetaddr[ARP_HLEN]; + char *source = "DT"; + +#if defined(CONFIG_NEEDS_MANUAL_RELOC) + struct eth_ops *ops = eth_get_ops(dev); + static int reloc_done; + + if (!reloc_done) { + if (ops->start) + ops->start += gd->reloc_off; + if (ops->send) + ops->send += gd->reloc_off; + if (ops->recv) + ops->recv += gd->reloc_off; + if (ops->free_pkt) + ops->free_pkt += gd->reloc_off; + if (ops->stop) + ops->stop += gd->reloc_off; + if (ops->mcast) + ops->mcast += gd->reloc_off; + if (ops->write_hwaddr) + ops->write_hwaddr += gd->reloc_off; + if (ops->read_rom_hwaddr) + ops->read_rom_hwaddr += gd->reloc_off; + + reloc_done++; + } +#endif + + priv->state = ETH_STATE_INIT; + priv->running = false; + + /* Check if the device has a valid MAC address in device tree */ + if (!eth_dev_get_mac_address(dev, pdata->enetaddr) || + !is_valid_ethaddr(pdata->enetaddr)) { + source = "ROM"; + /* Check if the device has a MAC address in ROM */ + if (eth_get_ops(dev)->read_rom_hwaddr) + eth_get_ops(dev)->read_rom_hwaddr(dev); + } + + eth_env_get_enetaddr_by_index("eth", dev_seq(dev), env_enetaddr); + if (!is_zero_ethaddr(env_enetaddr)) { + if (!is_zero_ethaddr(pdata->enetaddr) && + memcmp(pdata->enetaddr, env_enetaddr, ARP_HLEN)) { + printf("\nWarning: %s MAC addresses don't match:\n", + dev->name); + printf("Address in %s is\t\t%pM\n", + source, pdata->enetaddr); + printf("Address in environment is\t%pM\n", + env_enetaddr); + } + + /* Override the ROM MAC address */ + memcpy(pdata->enetaddr, env_enetaddr, ARP_HLEN); + } else if (is_valid_ethaddr(pdata->enetaddr)) { + eth_env_set_enetaddr_by_index("eth", dev_seq(dev), + pdata->enetaddr); + } else if (is_zero_ethaddr(pdata->enetaddr) || + !is_valid_ethaddr(pdata->enetaddr)) { +#ifdef CONFIG_NET_RANDOM_ETHADDR + net_random_ethaddr(pdata->enetaddr); + printf("\nWarning: %s (eth%d) using random MAC address - %pM\n", + dev->name, dev_seq(dev), pdata->enetaddr); +#else + printf("\nError: %s address not set.\n", + dev->name); + return -EINVAL; +#endif + } + + eth_write_hwaddr(dev); + + return 0; +} + +static int eth_pre_remove(struct udevice *dev) +{ + struct eth_pdata *pdata = dev_get_plat(dev); + + eth_get_ops(dev)->stop(dev); + + /* clear the MAC address */ + memset(pdata->enetaddr, 0, ARP_HLEN); + + return 0; +} + +UCLASS_DRIVER(ethernet) = { + .name = "ethernet", + .id = UCLASS_ETH, + .post_bind = eth_post_bind, + .pre_unbind = eth_pre_unbind, + .post_probe = eth_post_probe, + .pre_remove = eth_pre_remove, + .priv_auto = sizeof(struct eth_uclass_priv), + .per_device_auto = sizeof(struct eth_device_priv), + .flags = DM_UC_FLAG_SEQ_ALIAS, +}; |